refactor(session): trim projection migration diff
This commit is contained in:
parent
d9c0aa1cd0
commit
6717cb8d19
25 changed files with 170 additions and 87 deletions
|
|
@ -2,5 +2,5 @@
|
||||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
||||||
# after editing either side, bring the other along and re-record with:
|
# after editing either side, bring the other along and re-record with:
|
||||||
# pnpm run verify-translation-pairing --write docs/config-catalog.md
|
# pnpm run verify-translation-pairing --write docs/config-catalog.md
|
||||||
config-catalog.md: 88a7d34b5ed72c1e7535d62f3b9163ea7d677756
|
config-catalog.md: b7bc65cc2f2111ac549067f31518c18cf636c9b9
|
||||||
config-catalog.zh.md: f4ad394528dcde48a7c1c499acaece82ada894f4
|
config-catalog.zh.md: f934799dbab07d394732447ae2a4cd4184e10d61
|
||||||
|
|
|
||||||
|
|
@ -1508,7 +1508,7 @@ export interface PresetSpec {
|
||||||
|
|
||||||
Depends on: [`ApprovalPolicy`](subsystems/approval.md) · [`SandboxMode`](subsystems/sandbox.md)
|
Depends on: [`ApprovalPolicy`](subsystems/approval.md) · [`SandboxMode`](subsystems/sandbox.md)
|
||||||
|
|
||||||
Source: [`packages/interaction/permission-presets/src/index.ts:134`](../packages/interaction/permission-presets/src/index.ts)
|
Source: [`packages/interaction/permission-presets/src/index.ts:143`](../packages/interaction/permission-presets/src/index.ts)
|
||||||
|
|
||||||
<a id="deepseek-aidsh-persona"></a>
|
<a id="deepseek-aidsh-persona"></a>
|
||||||
|
|
||||||
|
|
@ -1992,7 +1992,7 @@ export interface Config {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Source: [`packages/session/session-title/src/index.ts:53`](../packages/session/session-title/src/index.ts)
|
Source: [`packages/session/session-title/src/index.ts:54`](../packages/session/session-title/src/index.ts)
|
||||||
|
|
||||||
<a id="deepseek-aidsh-session-title-all-prompts-llm"></a>
|
<a id="deepseek-aidsh-session-title-all-prompts-llm"></a>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1510,7 +1510,7 @@ export interface PresetSpec {
|
||||||
|
|
||||||
依赖:[`ApprovalPolicy`](subsystems/approval.zh.md) · [`SandboxMode`](subsystems/sandbox.zh.md)
|
依赖:[`ApprovalPolicy`](subsystems/approval.zh.md) · [`SandboxMode`](subsystems/sandbox.zh.md)
|
||||||
|
|
||||||
来源:[`packages/interaction/permission-presets/src/index.ts:134`](../packages/interaction/permission-presets/src/index.ts)
|
来源:[`packages/interaction/permission-presets/src/index.ts:143`](../packages/interaction/permission-presets/src/index.ts)
|
||||||
|
|
||||||
<a id="deepseek-aidsh-persona"></a>
|
<a id="deepseek-aidsh-persona"></a>
|
||||||
|
|
||||||
|
|
@ -1994,7 +1994,7 @@ export interface Config {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
来源:[`packages/session/session-title/src/index.ts:53`](../packages/session/session-title/src/index.ts)
|
来源:[`packages/session/session-title/src/index.ts:54`](../packages/session/session-title/src/index.ts)
|
||||||
|
|
||||||
<a id="deepseek-aidsh-session-title-all-prompts-llm"></a>
|
<a id="deepseek-aidsh-session-title-all-prompts-llm"></a>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,5 +2,5 @@
|
||||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
||||||
# after editing either side, bring the other along and re-record with:
|
# after editing either side, bring the other along and re-record with:
|
||||||
# pnpm run verify-translation-pairing --write docs/event-producer-consumer.md
|
# pnpm run verify-translation-pairing --write docs/event-producer-consumer.md
|
||||||
event-producer-consumer.md: 12efe17c1c02fa744c556996ed6e2bc9bf07deb9
|
event-producer-consumer.md: cb215c9e50902cac7eaa07f6bb294f798bcf848a
|
||||||
event-producer-consumer.zh.md: 108a8d1838cbd872137111735b35fe25e89303dc
|
event-producer-consumer.zh.md: ccf1fb91259ac63e019c82d2b4d283f3213ab991
|
||||||
|
|
|
||||||
|
|
@ -77,7 +77,7 @@ This matrix shows which packages dispatch each harness-owned event and which pac
|
||||||
|
|
||||||
| Event string | Dispatchers | Listeners |
|
| Event string | Dispatchers | Listeners |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
| `internal/dispatch` | - | [`commands`](../packages/interaction/commands), [`compaction`](../packages/compaction/compaction), [`fs`](../packages/fs/fs), [`goal`](../packages/goal/goal), [`goal-round-driver`](../packages/goal/goal-round-driver), [`hook-protocol`](../packages/hooks/hook-protocol), [`llm-retry`](../packages/llm/llm-retry), [`permission-presets`](../packages/interaction/permission-presets), [`plan-mode`](../packages/plan/plan-mode), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`schedule`](../packages/schedule/schedule), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`session-log-deepseek`](../packages/session/session-log-deepseek), [`session-title`](../packages/session/session-title), [`subagent`](../packages/subagent/subagent), [`terminal-bash`](../packages/terminal/terminal-bash), [`time-context`](../packages/context/time-context), [`tool-todo`](../packages/todo/tool-todo), [`tool-workflow`](../packages/workflow/tool-workflow), [`tools`](../packages/core/tools), [`user-approval`](../packages/interaction/user-approval), [`webhook`](../packages/webhook/webhook), [`workflow`](../packages/workflow/workflow) |
|
| `internal/dispatch` | - | `agent-team`, [`commands`](../packages/interaction/commands), [`compaction`](../packages/compaction/compaction), [`fs`](../packages/fs/fs), [`goal`](../packages/goal/goal), [`goal-round-driver`](../packages/goal/goal-round-driver), [`hook-protocol`](../packages/hooks/hook-protocol), [`llm-retry`](../packages/llm/llm-retry), [`permission-presets`](../packages/interaction/permission-presets), [`plan-mode`](../packages/plan/plan-mode), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`schedule`](../packages/schedule/schedule), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`session-log-deepseek`](../packages/session/session-log-deepseek), [`session-title`](../packages/session/session-title), [`subagent`](../packages/subagent/subagent), [`terminal-bash`](../packages/terminal/terminal-bash), [`time-context`](../packages/context/time-context), [`tool-todo`](../packages/todo/tool-todo), [`tool-workflow`](../packages/workflow/tool-workflow), [`tools`](../packages/core/tools), [`user-approval`](../packages/interaction/user-approval), [`webhook`](../packages/webhook/webhook), [`workflow`](../packages/workflow/workflow) |
|
||||||
| `internal/plugin` | - | `loader`, [`lsp-stdio`](../packages/lsp/lsp-stdio), `modules`, `webserver` |
|
| `internal/plugin` | - | `loader`, [`lsp-stdio`](../packages/lsp/lsp-stdio), `modules`, `webserver` |
|
||||||
| `internal/service` | - | [`agent-presets`](../packages/preset/agent-presets), `gateway` |
|
| `internal/service` | - | [`agent-presets`](../packages/preset/agent-presets), `gateway` |
|
||||||
| `internal/status` | - | [`agent`](../packages/core/agent) |
|
| `internal/status` | - | [`agent`](../packages/core/agent) |
|
||||||
|
|
|
||||||
|
|
@ -79,7 +79,7 @@
|
||||||
|
|
||||||
| 事件字符串 | 派发方 | 监听方 |
|
| 事件字符串 | 派发方 | 监听方 |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
| `internal/dispatch` | - | [`commands`](../packages/interaction/commands), [`compaction`](../packages/compaction/compaction), [`fs`](../packages/fs/fs), [`goal`](../packages/goal/goal), [`goal-round-driver`](../packages/goal/goal-round-driver), [`hook-protocol`](../packages/hooks/hook-protocol), [`llm-retry`](../packages/llm/llm-retry), [`permission-presets`](../packages/interaction/permission-presets), [`plan-mode`](../packages/plan/plan-mode), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`schedule`](../packages/schedule/schedule), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`session-log-deepseek`](../packages/session/session-log-deepseek), [`session-title`](../packages/session/session-title), [`subagent`](../packages/subagent/subagent), [`terminal-bash`](../packages/terminal/terminal-bash), [`time-context`](../packages/context/time-context), [`tool-todo`](../packages/todo/tool-todo), [`tool-workflow`](../packages/workflow/tool-workflow), [`tools`](../packages/core/tools), [`user-approval`](../packages/interaction/user-approval), [`webhook`](../packages/webhook/webhook), [`workflow`](../packages/workflow/workflow) |
|
| `internal/dispatch` | - | `agent-team`, [`commands`](../packages/interaction/commands), [`compaction`](../packages/compaction/compaction), [`fs`](../packages/fs/fs), [`goal`](../packages/goal/goal), [`goal-round-driver`](../packages/goal/goal-round-driver), [`hook-protocol`](../packages/hooks/hook-protocol), [`llm-retry`](../packages/llm/llm-retry), [`permission-presets`](../packages/interaction/permission-presets), [`plan-mode`](../packages/plan/plan-mode), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`schedule`](../packages/schedule/schedule), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`session-log-deepseek`](../packages/session/session-log-deepseek), [`session-title`](../packages/session/session-title), [`subagent`](../packages/subagent/subagent), [`terminal-bash`](../packages/terminal/terminal-bash), [`time-context`](../packages/context/time-context), [`tool-todo`](../packages/todo/tool-todo), [`tool-workflow`](../packages/workflow/tool-workflow), [`tools`](../packages/core/tools), [`user-approval`](../packages/interaction/user-approval), [`webhook`](../packages/webhook/webhook), [`workflow`](../packages/workflow/workflow) |
|
||||||
| `internal/plugin` | - | `loader`, [`lsp-stdio`](../packages/lsp/lsp-stdio), `modules`, `webserver` |
|
| `internal/plugin` | - | `loader`, [`lsp-stdio`](../packages/lsp/lsp-stdio), `modules`, `webserver` |
|
||||||
| `internal/service` | - | [`agent-presets`](../packages/preset/agent-presets), `gateway` |
|
| `internal/service` | - | [`agent-presets`](../packages/preset/agent-presets), `gateway` |
|
||||||
| `internal/status` | - | [`agent`](../packages/core/agent) |
|
| `internal/status` | - | [`agent`](../packages/core/agent) |
|
||||||
|
|
|
||||||
|
|
@ -2,5 +2,5 @@
|
||||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
||||||
# after editing either side, bring the other along and re-record with:
|
# after editing either side, bring the other along and re-record with:
|
||||||
# pnpm run verify-translation-pairing --write docs/persistence-catalog.md
|
# pnpm run verify-translation-pairing --write docs/persistence-catalog.md
|
||||||
persistence-catalog.md: 857d3783d57decf99596527ad3b8cc5f344be616
|
persistence-catalog.md: 29d07f674c2ebd054b0aafa47f729504807460bc
|
||||||
persistence-catalog.zh.md: 591a3951f094d470b4522fe22250154a0d1cf293
|
persistence-catalog.zh.md: 4c191e6f01a7513df2723502a88f3f973b7596e3
|
||||||
|
|
|
||||||
|
|
@ -519,7 +519,7 @@ Source: [`packages/api/session-controller/src/types.ts:40`](../packages/api/sess
|
||||||
'permission/preset': { preset: string }
|
'permission/preset': { preset: string }
|
||||||
```
|
```
|
||||||
|
|
||||||
Source: [`packages/interaction/permission-presets/src/index.ts:59`](../packages/interaction/permission-presets/src/index.ts)
|
Source: [`packages/interaction/permission-presets/src/index.ts:53`](../packages/interaction/permission-presets/src/index.ts)
|
||||||
|
|
||||||
### `plan/*`
|
### `plan/*`
|
||||||
|
|
||||||
|
|
@ -662,7 +662,7 @@ Source: [`packages/core/session/src/types.ts:324`](../packages/core/session/src/
|
||||||
|
|
||||||
Types: [SessionTitleEventData](subsystems/session-title.md)
|
Types: [SessionTitleEventData](subsystems/session-title.md)
|
||||||
|
|
||||||
Source: [`packages/session/session-title/src/index.ts:74`](../packages/session/session-title/src/index.ts)
|
Source: [`packages/session/session-title/src/index.ts:75`](../packages/session/session-title/src/index.ts)
|
||||||
|
|
||||||
<a id="sessiontitle-llm-request--log-only"></a>
|
<a id="sessiontitle-llm-request--log-only"></a>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -521,7 +521,7 @@ export type SessionEvent<T extends SessionEventType = SessionEventType> = {
|
||||||
'permission/preset': { preset: string }
|
'permission/preset': { preset: string }
|
||||||
```
|
```
|
||||||
|
|
||||||
来源:[`packages/interaction/permission-presets/src/index.ts:59`](../packages/interaction/permission-presets/src/index.ts)
|
来源:[`packages/interaction/permission-presets/src/index.ts:53`](../packages/interaction/permission-presets/src/index.ts)
|
||||||
|
|
||||||
### `plan/*`
|
### `plan/*`
|
||||||
|
|
||||||
|
|
@ -664,7 +664,7 @@ export type SessionEvent<T extends SessionEventType = SessionEventType> = {
|
||||||
|
|
||||||
类型:[SessionTitleEventData](subsystems/session-title.zh.md)
|
类型:[SessionTitleEventData](subsystems/session-title.zh.md)
|
||||||
|
|
||||||
来源:[`packages/session/session-title/src/index.ts:74`](../packages/session/session-title/src/index.ts)
|
来源:[`packages/session/session-title/src/index.ts:75`](../packages/session/session-title/src/index.ts)
|
||||||
|
|
||||||
<a id="sessiontitle-llm-request--log-only"></a>
|
<a id="sessiontitle-llm-request--log-only"></a>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,5 +2,5 @@
|
||||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
||||||
# after editing either side, bring the other along and re-record with:
|
# after editing either side, bring the other along and re-record with:
|
||||||
# pnpm run verify-translation-pairing --write docs/subsystems/sandbox.md
|
# pnpm run verify-translation-pairing --write docs/subsystems/sandbox.md
|
||||||
sandbox.md: 8afa1d2ef2673b240938f5c70c8f58f8e16c16df
|
sandbox.md: 7160ac699a21a319c991ae70262a8bf61da86b0c
|
||||||
sandbox.zh.md: 6ee68d7d26cdcaed6f62a27fecfc3604e4d4f7a5
|
sandbox.zh.md: 6d2b10d63a0e180bc0fe39cc47930e66dee72466
|
||||||
|
|
|
||||||
|
|
@ -192,7 +192,7 @@ Source: [`packages/sandbox/sandbox/src/index.ts`](../../packages/sandbox/sandbox
|
||||||
|
|
||||||
### `ctx.sandboxPolicy` — `SandboxPolicyService`
|
### `ctx.sandboxPolicy` — `SandboxPolicyService`
|
||||||
|
|
||||||
The sandbox policy seam: the deployment default mode and workspace-write root, with per-session overrides folded from the log.
|
The sandbox-policy service (`ctx.sandboxPolicy`). Owns the deployment default mode, fallback workspace root, and current request-time policy section. Tool layers call resolve for each execution so a session's mode log and immutable cwd travel together to every enforcing capability.
|
||||||
|
|
||||||
```ts cordis-catalog
|
```ts cordis-catalog
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -192,7 +192,7 @@ Source: [`packages/sandbox/sandbox/src/index.ts`](../../packages/sandbox/sandbox
|
||||||
|
|
||||||
### `ctx.sandboxPolicy` — `SandboxPolicyService`
|
### `ctx.sandboxPolicy` — `SandboxPolicyService`
|
||||||
|
|
||||||
The sandbox policy seam: the deployment default mode and workspace-write root, with per-session overrides folded from the log.
|
The sandbox-policy service (`ctx.sandboxPolicy`). Owns the deployment default mode, fallback workspace root, and current request-time policy section. Tool layers call resolve for each execution so a session's mode log and immutable cwd travel together to every enforcing capability.
|
||||||
|
|
||||||
```ts cordis-catalog
|
```ts cordis-catalog
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -39,7 +39,7 @@ const timeContextStateSchema = zod.object({
|
||||||
})
|
})
|
||||||
|
|
||||||
/** Folded time-context readings. */
|
/** Folded time-context readings. */
|
||||||
export type TimeContextProjection = zod.infer<typeof timeContextStateSchema>
|
type TimeContextProjection = zod.infer<typeof timeContextStateSchema>
|
||||||
|
|
||||||
/** The agent registry that owns pre-step processing. */
|
/** The agent registry that owns pre-step processing. */
|
||||||
export const inject = ['agents', 'sessionProjections']
|
export const inject = ['agents', 'sessionProjections']
|
||||||
|
|
@ -182,15 +182,13 @@ export function apply(ctx: Context, config: Config): void {
|
||||||
const decision = await next()
|
const decision = await next()
|
||||||
if (decision.kind === 'reject' || signal.aborted) return decision
|
if (decision.kind === 'reject' || signal.aborted) return decision
|
||||||
const now = Date.now()
|
const now = Date.now()
|
||||||
const state = ctx.sessionProjections.stateOf(agent.session, 'timeContext')
|
const state = ctx.sessionProjections.stateOf(agent.session, 'timeContext') as TimeContextProjection
|
||||||
if (state !== undefined && refreshIntervalMs !== undefined && refreshIntervalMs > 0) {
|
if (refreshIntervalMs !== undefined && refreshIntervalMs > 0) {
|
||||||
const lastInjection = state.lastInjectionTime
|
const lastInjection = state.lastInjectionTime
|
||||||
if (lastInjection != null
|
if (lastInjection != null
|
||||||
&& now >= lastInjection
|
&& now >= lastInjection
|
||||||
&& now - lastInjection < refreshIntervalMs) return decision
|
&& now - lastInjection < refreshIntervalMs) return decision
|
||||||
}
|
}
|
||||||
/* v8 ignore next 2 -- time-context registers its own unit in apply, so the key is always present */
|
|
||||||
if (state === undefined) return decision
|
|
||||||
/* v8 ignore next 6 -- every later step follows a recorded injection in the same turn */
|
/* v8 ignore next 6 -- every later step follows a recorded injection in the same turn */
|
||||||
const previous = step === 1
|
const previous = step === 1
|
||||||
? state.lastMessageTime ?? undefined
|
? state.lastMessageTime ?? undefined
|
||||||
|
|
|
||||||
|
|
@ -241,7 +241,7 @@ export function apply(ctx: Context, config: Config): void {
|
||||||
if (decision.kind === 'reject' || signal.aborted || step !== 1) return decision
|
if (decision.kind === 'reject' || signal.aborted || step !== 1) return decision
|
||||||
const bash = ctx.get('shell')
|
const bash = ctx.get('shell')
|
||||||
if (bash === undefined) return decision
|
if (bash === undefined) return decision
|
||||||
const previous = ctx.sessionProjections.stateOf(agent.session, 'tmuxContext') ?? null
|
const previous = ctx.sessionProjections.stateOf(agent.session, 'tmuxContext') as TmuxContextState
|
||||||
if (refreshIntervalMs !== undefined && refreshIntervalMs > 0 && previous !== null) {
|
if (refreshIntervalMs !== undefined && refreshIntervalMs > 0 && previous !== null) {
|
||||||
const now = Date.now()
|
const now = Date.now()
|
||||||
if (now >= previous.time && now - previous.time < refreshIntervalMs) return decision
|
if (now >= previous.time && now - previous.time < refreshIntervalMs) return decision
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,13 @@
|
||||||
/** Agent Teams runtime invariant companion. */
|
/** Agent Teams runtime invariant companion. */
|
||||||
|
|
||||||
import type { Context } from '@deepseek-ai/cordis'
|
import type { Context } from '@deepseek-ai/cordis'
|
||||||
import type { InvariantInstaller } from '@deepseek-ai/dsh-invariants'
|
import type { InvariantFailure, InvariantInstaller } from '@deepseek-ai/dsh-invariants'
|
||||||
|
import type { Session, SessionEvent } from '@deepseek-ai/dsh-session'
|
||||||
|
import {
|
||||||
|
isTeamEvent,
|
||||||
|
teamProjectionDefinition,
|
||||||
|
type TeamProjectionState,
|
||||||
|
} from './projection.ts'
|
||||||
|
|
||||||
const PACKAGE_NAME = '@deepseek-ai/dsh-experimental-agent-team'
|
const PACKAGE_NAME = '@deepseek-ai/dsh-experimental-agent-team'
|
||||||
|
|
||||||
|
|
@ -10,8 +16,20 @@ export const name = 'team-invariant'
|
||||||
/** Invariant registry required by the companion. */
|
/** Invariant registry required by the companion. */
|
||||||
export const inject = ['invariants']
|
export const inject = ['invariants']
|
||||||
|
|
||||||
/** No runtime invariant: the Team projection owns event decoding and relational state transitions. */
|
/** Validate candidate Team events against the projected committed prefix. */
|
||||||
const install: InvariantInstaller = () => {}
|
const install: InvariantInstaller = Object.assign((ctx: Context, fail: InvariantFailure) => {
|
||||||
|
ctx.on('internal/dispatch', (_mode, eventName, args) => {
|
||||||
|
if (eventName !== 'session/event') return
|
||||||
|
const [session, event] = args as [Session, SessionEvent]
|
||||||
|
/* v8 ignore next -- non-Team Session events have no Agent Teams invariant. */
|
||||||
|
if (!isTeamEvent(event)) return
|
||||||
|
const state = ctx.sessionProjections.stateOf(session, 'agentTeam') as TeamProjectionState
|
||||||
|
const candidate = teamProjectionDefinition.apply(structuredClone(state), event)
|
||||||
|
if (candidate.failure !== undefined) {
|
||||||
|
fail(`session event ${event.seq} violates the Agent Teams stream: ${candidate.failure}`)
|
||||||
|
}
|
||||||
|
}, { global: true })
|
||||||
|
}, { inject: ['sessionProjections'] })
|
||||||
|
|
||||||
/** Register the package invariant companion. */
|
/** Register the package invariant companion. */
|
||||||
export const apply = (ctx: Context): Promise<() => void> =>
|
export const apply = (ctx: Context): Promise<() => void> =>
|
||||||
|
|
|
||||||
|
|
@ -186,7 +186,7 @@ type TeamSessionEvent = SessionEvent<TeamEventType>
|
||||||
* @param event - candidate Session event.
|
* @param event - candidate Session event.
|
||||||
* @returns whether the event has a Team-owned type.
|
* @returns whether the event has a Team-owned type.
|
||||||
*/
|
*/
|
||||||
function isTeamEvent(event: SessionEvent): event is TeamSessionEvent {
|
export function isTeamEvent(event: SessionEvent): event is TeamSessionEvent {
|
||||||
return event.type === 'team/member'
|
return event.type === 'team/member'
|
||||||
|| event.type === 'team/task'
|
|| event.type === 'team/task'
|
||||||
|| event.type === 'team/message/queued'
|
|| event.type === 'team/message/queued'
|
||||||
|
|
@ -220,8 +220,8 @@ function parseCurrentTeamEvent(event: TeamSessionEvent): TeamSessionEvent {
|
||||||
}
|
}
|
||||||
|
|
||||||
function applyProjectionEvent(state: TeamProjectionState, event: SessionEvent): void {
|
function applyProjectionEvent(state: TeamProjectionState, event: SessionEvent): void {
|
||||||
if (!isTeamEvent(event)) return
|
|
||||||
if (state.failure !== undefined) return
|
if (state.failure !== undefined) return
|
||||||
|
if (!isTeamEvent(event)) return
|
||||||
try {
|
try {
|
||||||
const selector = parsePersisted(event.type, teamEventSelectorSchema, event.data)
|
const selector = parsePersisted(event.type, teamEventSelectorSchema, event.data)
|
||||||
if (selector.teamId !== state.id) return
|
if (selector.teamId !== state.id) return
|
||||||
|
|
|
||||||
74
packages/experimental/agent-team/tests/invariant.spec.ts
Normal file
74
packages/experimental/agent-team/tests/invariant.spec.ts
Normal file
|
|
@ -0,0 +1,74 @@
|
||||||
|
import { describe, expect, it } from 'vitest'
|
||||||
|
import { Context } from '@deepseek-ai/cordis'
|
||||||
|
import InvariantService, { InvariantError } from '@deepseek-ai/dsh-invariants'
|
||||||
|
import SessionStore, { SessionId } from '@deepseek-ai/dsh-session'
|
||||||
|
import SessionProjectionRegistry from '@deepseek-ai/dsh-session-projection'
|
||||||
|
import * as TeamInvariant from '../src/invariant.ts'
|
||||||
|
import { teamProjectionDefinition } from '../src/projection.ts'
|
||||||
|
import { TeamId, TeamTaskId } from '../src/types.ts'
|
||||||
|
|
||||||
|
async function setup(): Promise<Context> {
|
||||||
|
const ctx = new Context()
|
||||||
|
await ctx.plugin(SessionStore)
|
||||||
|
await ctx.plugin(SessionProjectionRegistry)
|
||||||
|
ctx.sessionProjections.register(teamProjectionDefinition)
|
||||||
|
await ctx.plugin(InvariantService, { enabled: true })
|
||||||
|
await ctx.plugin(TeamInvariant)
|
||||||
|
return ctx
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('Agent Teams stream invariant', () => {
|
||||||
|
it('accepts provisioning and rejects a terminal member as the first edge', async () => {
|
||||||
|
const ctx = await setup()
|
||||||
|
const session = ctx.sessions.create(SessionId('team-invariant'))
|
||||||
|
const member = {
|
||||||
|
id: SessionId('team-invariant-child'),
|
||||||
|
name: 'worker',
|
||||||
|
description: 'worker responsibility',
|
||||||
|
provider: 'spawn',
|
||||||
|
context: 'fresh' as const,
|
||||||
|
phase: 'provisioning' as const,
|
||||||
|
}
|
||||||
|
expect(() => {
|
||||||
|
session.append('team/member', { version: 1, teamId: TeamId(session.id), member })
|
||||||
|
}).not.toThrow()
|
||||||
|
|
||||||
|
const invalid = ctx.sessions.create(SessionId('team-invariant-invalid'))
|
||||||
|
expect(() => {
|
||||||
|
invalid.append('team/member', {
|
||||||
|
version: 1,
|
||||||
|
teamId: TeamId(invalid.id),
|
||||||
|
member: { ...member, phase: 'active' },
|
||||||
|
})
|
||||||
|
}).toThrow(expect.objectContaining<Partial<InvariantError>>({
|
||||||
|
code: 'INVARIANT',
|
||||||
|
packageName: '@deepseek-ai/dsh-experimental-agent-team',
|
||||||
|
}))
|
||||||
|
expect(invalid.events).toEqual([])
|
||||||
|
})
|
||||||
|
|
||||||
|
it('rejects an invalid task dependency before publication', async () => {
|
||||||
|
const ctx = await setup()
|
||||||
|
const session = ctx.sessions.create(SessionId('team-task-invariant'))
|
||||||
|
|
||||||
|
expect(() => {
|
||||||
|
session.append('team/task', {
|
||||||
|
version: 1,
|
||||||
|
teamId: TeamId(session.id),
|
||||||
|
task: {
|
||||||
|
id: TeamTaskId('task-1'),
|
||||||
|
revision: 1,
|
||||||
|
subject: 'invalid dependency',
|
||||||
|
description: 'references a missing blocker',
|
||||||
|
status: 'pending',
|
||||||
|
blockedBy: [TeamTaskId('missing')],
|
||||||
|
writeScopes: [],
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}).toThrow(expect.objectContaining<Partial<InvariantError>>({
|
||||||
|
code: 'INVARIANT',
|
||||||
|
packageName: '@deepseek-ai/dsh-experimental-agent-team',
|
||||||
|
}))
|
||||||
|
expect(session.events).toEqual([])
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
@ -1290,8 +1290,8 @@ export const SERVICE_API: readonly ServiceApiEntry[] = [
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'sandboxPolicy',
|
key: 'sandboxPolicy',
|
||||||
summary: 'The sandbox policy seam: the deployment default mode and workspace-write root, with per-session overrides folded from the log.',
|
summary: 'The sandbox-policy service (`ctx.sandboxPolicy`).',
|
||||||
description: 'The sandbox policy seam: the deployment default mode and workspace-write root, with per-session overrides folded from the log.',
|
description: 'The sandbox-policy service (`ctx.sandboxPolicy`). Owns the deployment default mode, fallback workspace root, and current request-time policy section. Tool layers call resolve for each execution so a session\'s mode log and immutable cwd travel together to every enforcing capability.',
|
||||||
methods: [
|
methods: [
|
||||||
{
|
{
|
||||||
signature: 'readonly defaultMode: SandboxMode',
|
signature: 'readonly defaultMode: SandboxMode',
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@
|
||||||
import { readFileSync } from 'node:fs'
|
import { readFileSync } from 'node:fs'
|
||||||
import type { Context } from '@deepseek-ai/cordis'
|
import type { Context } from '@deepseek-ai/cordis'
|
||||||
import z from '@deepseek-ai/schemastery'
|
import z from '@deepseek-ai/schemastery'
|
||||||
import type { Agent, PreStepDecision } from '@deepseek-ai/dsh-agent'
|
import type { Agent, PreStepDecision, TurnBoundaryProjection } from '@deepseek-ai/dsh-agent'
|
||||||
import type {} from '@deepseek-ai/dsh-session-projection'
|
import type {} from '@deepseek-ai/dsh-session-projection'
|
||||||
import { createUserMessage } from '@deepseek-ai/dsh-llm'
|
import { createUserMessage } from '@deepseek-ai/dsh-llm'
|
||||||
import type { ContentBlock, MessageSource } from '@deepseek-ai/dsh-llm'
|
import type { ContentBlock, MessageSource } from '@deepseek-ai/dsh-llm'
|
||||||
|
|
@ -310,8 +310,8 @@ const SUBAGENT_TYPE = 'general-purpose'
|
||||||
/** The last open turn number in the agent's log, or 0 without an agent. */
|
/** The last open turn number in the agent's log, or 0 without an agent. */
|
||||||
function lastTurn(ctx: Context, agent: Agent | undefined): number {
|
function lastTurn(ctx: Context, agent: Agent | undefined): number {
|
||||||
if (!agent) return 0
|
if (!agent) return 0
|
||||||
/* v8 ignore next -- agent-present hook points run inside AgentLoop, which owns this projection. */
|
const boundary = ctx.sessionProjections.stateOf(agent.session, 'turnBoundary') as TurnBoundaryProjection
|
||||||
return ctx.sessionProjections.stateOf(agent.session, 'turnBoundary')?.lastTurn ?? 0
|
return boundary.lastTurn
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Flatten content blocks to the text a hook payload carries (the common case). */
|
/** Flatten content blocks to the text a hook payload carries (the common case). */
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ import { installSettingsSection, settingsNamespace } from '@deepseek-ai/dsh-sett
|
||||||
// Type-only: resolves the optional projection and command children.
|
// Type-only: resolves the optional projection and command children.
|
||||||
import type {} from '@deepseek-ai/dsh-session-projection'
|
import type {} from '@deepseek-ai/dsh-session-projection'
|
||||||
import type {} from '@deepseek-ai/dsh-commands'
|
import type {} from '@deepseek-ai/dsh-commands'
|
||||||
import type { KnobState, PermissionSelect, PresetOption } from './types.ts'
|
import type { PermissionSelect, PresetOption } from './types.ts'
|
||||||
|
|
||||||
export type * from './types.ts'
|
export type * from './types.ts'
|
||||||
|
|
||||||
|
|
@ -35,12 +35,6 @@ declare module '@deepseek-ai/cordis' {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Projection state for permission overrides and constructor-seed provenance. */
|
|
||||||
interface PermissionProjectionState extends KnobState {
|
|
||||||
/** Whether the log contains a constructor-seed boundary. */
|
|
||||||
seeded: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
declare module '@deepseek-ai/dsh-session-projection/types' {
|
declare module '@deepseek-ai/dsh-session-projection/types' {
|
||||||
interface SessionProjectionStateMap {
|
interface SessionProjectionStateMap {
|
||||||
/** Latest logged permission overrides and constructor-seed provenance. */
|
/** Latest logged permission overrides and constructor-seed provenance. */
|
||||||
|
|
@ -81,8 +75,26 @@ export const CUSTOM_PRESET = 'custom'
|
||||||
/** Settings namespace carrying the default for future sessions. */
|
/** Settings namespace carrying the default for future sessions. */
|
||||||
export const PERMISSION_SETTINGS_NAMESPACE = settingsNamespace('permission')
|
export const PERMISSION_SETTINGS_NAMESPACE = settingsNamespace('permission')
|
||||||
|
|
||||||
const permissionStateSchema: zod.ZodType<PermissionProjectionState> = zod.object({
|
/**
|
||||||
|
* The projection unit's knob state: the last seen value of each knob event,
|
||||||
|
* null before an override (composition defaults apply at view time).
|
||||||
|
*/
|
||||||
|
export interface KnobState {
|
||||||
/** Last `permission/preset` payload, or null. */
|
/** Last `permission/preset` payload, or null. */
|
||||||
|
preset: string | null
|
||||||
|
/** Last `sandbox/mode` payload, or null. */
|
||||||
|
sandbox: SandboxMode | null
|
||||||
|
/** Last `approval/policy` payload, or null. */
|
||||||
|
approval: ApprovalPolicy | null
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Projection state for permission overrides and constructor-seed provenance. */
|
||||||
|
interface PermissionProjectionState extends KnobState {
|
||||||
|
/** Whether the log contains a constructor-seed boundary. */
|
||||||
|
seeded: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
const permissionStateSchema: zod.ZodType<PermissionProjectionState> = zod.object({
|
||||||
preset: zod.string().nullable(),
|
preset: zod.string().nullable(),
|
||||||
sandbox: zod.union([
|
sandbox: zod.union([
|
||||||
zod.literal('read-only'),
|
zod.literal('read-only'),
|
||||||
|
|
@ -96,9 +108,6 @@ const permissionStateSchema: zod.ZodType<PermissionProjectionState> = zod.object
|
||||||
/** State for the empty log: every knob at its composition default. */
|
/** State for the empty log: every knob at its composition default. */
|
||||||
const EMPTY_KNOBS: KnobState = { preset: null, sandbox: null, approval: null }
|
const EMPTY_KNOBS: KnobState = { preset: null, sandbox: null, approval: null }
|
||||||
|
|
||||||
/** Projection state for a genuinely fresh session. */
|
|
||||||
const EMPTY_PERMISSION_STATE: PermissionProjectionState = { ...EMPTY_KNOBS, seeded: false }
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* One-event permission-state transition (the projection unit's `apply`). Unrelated
|
* One-event permission-state transition (the projection unit's `apply`). Unrelated
|
||||||
* events return the same reference — the registry's change gate.
|
* events return the same reference — the registry's change gate.
|
||||||
|
|
@ -118,7 +127,7 @@ function applyPermissionEvent(
|
||||||
case 'approval/policy':
|
case 'approval/policy':
|
||||||
return { ...state, approval: event.data.policy }
|
return { ...state, approval: event.data.policy }
|
||||||
case 'session/end-seed':
|
case 'session/end-seed':
|
||||||
return state.seeded ? state : { ...state, seeded: true }
|
return { ...state, seeded: true }
|
||||||
default:
|
default:
|
||||||
return state
|
return state
|
||||||
}
|
}
|
||||||
|
|
@ -230,7 +239,7 @@ export class PermissionPresetService extends Service {
|
||||||
key: 'permissions',
|
key: 'permissions',
|
||||||
stateVersion: 2,
|
stateVersion: 2,
|
||||||
stateSchema: permissionStateSchema,
|
stateSchema: permissionStateSchema,
|
||||||
init: () => EMPTY_PERMISSION_STATE,
|
init: () => ({ ...EMPTY_KNOBS, seeded: false }),
|
||||||
apply: applyPermissionEvent,
|
apply: applyPermissionEvent,
|
||||||
wire: { viewSchema: selectSchema, view: state => this.selectFor(state) },
|
wire: { viewSchema: selectSchema, view: state => this.selectFor(state) },
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -2,15 +2,13 @@
|
||||||
* Pure types of the permission domain: the ONE home of the `permissions`
|
* Pure types of the permission domain: the ONE home of the `permissions`
|
||||||
* projection-key declaration plus its payload types, free of this package's
|
* projection-key declaration plus its payload types, free of this package's
|
||||||
* host-side value imports (cordis, schemastery). Two namespace projections
|
* host-side value imports (cordis, schemastery). Two namespace projections
|
||||||
* serve it — the package root re-export for host consumers and `./client` for
|
* serve it — the package root re-export for host consumers, `./client` (the
|
||||||
* client aggregates — with zero content duplication.
|
* browser half-entry's re-export) for client aggregates — with zero content
|
||||||
|
* duplication.
|
||||||
*
|
*
|
||||||
* @module @deepseek-ai/dsh-permission-presets/types
|
* @module @deepseek-ai/dsh-permission-presets/types
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { SandboxMode } from '@deepseek-ai/dsh-sandbox'
|
|
||||||
import type { ApprovalPolicy } from '@deepseek-ai/dsh-user-approval'
|
|
||||||
|
|
||||||
/** The select-option shape a presentation layer advertises for one preset (or for the derived `custom` state). */
|
/** The select-option shape a presentation layer advertises for one preset (or for the derived `custom` state). */
|
||||||
export interface PresetOption {
|
export interface PresetOption {
|
||||||
/** Stable option value: the table key, or `custom`. */
|
/** Stable option value: the table key, or `custom`. */
|
||||||
|
|
@ -33,16 +31,6 @@ export interface PermissionSelect {
|
||||||
currentValue: string
|
currentValue: string
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Latest logged permission overrides. */
|
|
||||||
export interface KnobState {
|
|
||||||
/** Last `permission/preset` payload, or null. */
|
|
||||||
preset: string | null
|
|
||||||
/** Last `sandbox/mode` payload, or null. */
|
|
||||||
sandbox: SandboxMode | null
|
|
||||||
/** Last `approval/policy` payload, or null. */
|
|
||||||
approval: ApprovalPolicy | null
|
|
||||||
}
|
|
||||||
|
|
||||||
declare module '@deepseek-ai/dsh-session-projection/types' {
|
declare module '@deepseek-ai/dsh-session-projection/types' {
|
||||||
interface SessionProjectionMap {
|
interface SessionProjectionMap {
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -217,8 +217,8 @@ export function apply(ctx: Context, config: Config = {}, internals: RetryInterna
|
||||||
}
|
}
|
||||||
|
|
||||||
const policyKey = retryPolicyKey(policy)
|
const policyKey = retryPolicyKey(policy)
|
||||||
const retryState = ctx.sessionProjections.stateOf(agent.session, 'llmRetry')
|
const retryState = ctx.sessionProjections.stateOf(agent.session, 'llmRetry') as LlmRetryState
|
||||||
const previous = retryState?.[retryStateKey(provider, policyKey)]
|
const previous = retryState[retryStateKey(provider, policyKey)]
|
||||||
const previousRetry = previous?.retry ?? 0
|
const previousRetry = previous?.retry ?? 0
|
||||||
if (policy.mode === 'normal' && previousRetry >= policy.maxRetries) return next()
|
if (policy.mode === 'normal' && previousRetry >= policy.maxRetries) return next()
|
||||||
const retry = previousRetry + 1
|
const retry = previousRetry + 1
|
||||||
|
|
|
||||||
|
|
@ -85,12 +85,6 @@ export interface SandboxPolicyRequest {
|
||||||
mode?: SandboxMode
|
mode?: SandboxMode
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* The sandbox-policy service (`ctx.sandboxPolicy`). Owns the deployment
|
|
||||||
* default mode, fallback workspace root, and current request-time policy
|
|
||||||
* section. Tool layers call {@link resolve} for each execution so a session's
|
|
||||||
* mode log and immutable cwd travel together to every enforcing capability.
|
|
||||||
*/
|
|
||||||
/** The sandbox-mode projection's state schema (state equals the public shape). */
|
/** The sandbox-mode projection's state schema (state equals the public shape). */
|
||||||
const sandboxModeStateSchema = zod.union([
|
const sandboxModeStateSchema = zod.union([
|
||||||
zod.literal('read-only'),
|
zod.literal('read-only'),
|
||||||
|
|
@ -106,7 +100,12 @@ declare module '@deepseek-ai/dsh-session-projection/types' {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** The sandbox policy seam: the deployment default mode and workspace-write root, with per-session overrides folded from the log. */
|
/**
|
||||||
|
* The sandbox-policy service (`ctx.sandboxPolicy`). Owns the deployment
|
||||||
|
* default mode, fallback workspace root, and current request-time policy
|
||||||
|
* section. Tool layers call {@link resolve} for each execution so a session's
|
||||||
|
* mode log and immutable cwd travel together to every enforcing capability.
|
||||||
|
*/
|
||||||
export class SandboxPolicyService extends Service {
|
export class SandboxPolicyService extends Service {
|
||||||
// Inline schema call: the config catalog walks `static Config` statically.
|
// Inline schema call: the config catalog walks `static Config` statically.
|
||||||
static Config: z<Config> = z.object({
|
static Config: z<Config> = z.object({
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ import { Context, FiberState, Service, type Fiber } from '@deepseek-ai/cordis'
|
||||||
import z from '@deepseek-ai/schemastery'
|
import z from '@deepseek-ai/schemastery'
|
||||||
import { z as zod } from 'zod'
|
import { z as zod } from 'zod'
|
||||||
import type { ZodType } from 'zod'
|
import type { ZodType } from 'zod'
|
||||||
|
import type { Branded } from '@deepseek-ai/dsh-brand'
|
||||||
import { assertNever, deepFreeze, isAgentLoopRequest } from '@deepseek-ai/dsh-llm'
|
import { assertNever, deepFreeze, isAgentLoopRequest } from '@deepseek-ai/dsh-llm'
|
||||||
import type { GenerateOptions } from '@deepseek-ai/dsh-llm'
|
import type { GenerateOptions } from '@deepseek-ai/dsh-llm'
|
||||||
import type {
|
import type {
|
||||||
|
|
@ -35,8 +36,8 @@ import type {
|
||||||
TitleProjection,
|
TitleProjection,
|
||||||
} from './types.ts'
|
} from './types.ts'
|
||||||
|
|
||||||
/** Branded session-title provider identity. */
|
/** Identifies one session-title provider registration. */
|
||||||
export type SessionTitleProviderId = import('./types.ts').SessionTitleProviderId
|
export type SessionTitleProviderId = Branded<'SessionTitleProviderId'>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Brand a raw provider id.
|
* Brand a raw provider id.
|
||||||
|
|
@ -192,15 +193,12 @@ function assertPositiveInteger(name: keyof Config, value: number): void {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** The title unit's folded state — the projection's full public face. */
|
|
||||||
export type TitleUnitState = TitleProjection
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convert title projection state into an immutable snapshot.
|
* Convert title projection state into an immutable snapshot.
|
||||||
* @param state - the title unit's folded state.
|
* @param state - the title unit's folded state.
|
||||||
* @returns the immutable snapshot.
|
* @returns the immutable snapshot.
|
||||||
*/
|
*/
|
||||||
export function titleSnapshotFromState(state: TitleUnitState): SessionTitleSnapshot {
|
function titleSnapshotFromState(state: TitleProjection): SessionTitleSnapshot {
|
||||||
return deepFreeze({
|
return deepFreeze({
|
||||||
title: state.title,
|
title: state.title,
|
||||||
messageSeqs: [...state.messageSeqs],
|
messageSeqs: [...state.messageSeqs],
|
||||||
|
|
@ -272,7 +270,7 @@ const titleProjectionSchema = zod.object({
|
||||||
]),
|
]),
|
||||||
eventSeq: zod.number().int().nonnegative(),
|
eventSeq: zod.number().int().nonnegative(),
|
||||||
updatedAt: zod.number(),
|
updatedAt: zod.number(),
|
||||||
}).nullable() as unknown as ZodType<TitleUnitState | null>
|
}).nullable() as unknown as ZodType<TitleProjection | null>
|
||||||
|
|
||||||
const titleViewSchema: ZodType<string | null> = zod.string().min(1).nullable()
|
const titleViewSchema: ZodType<string | null> = zod.string().min(1).nullable()
|
||||||
|
|
||||||
|
|
@ -295,7 +293,7 @@ export const titleProjectionDefinition = {
|
||||||
viewSchema: titleViewSchema,
|
viewSchema: titleViewSchema,
|
||||||
view: state => state?.title ?? null,
|
view: state => state?.title ?? null,
|
||||||
},
|
},
|
||||||
} satisfies ProjectionDefinition<'title', TitleUnitState | null>
|
} satisfies ProjectionDefinition<'title', TitleProjection | null>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fold the latest title from a session log.
|
* Fold the latest title from a session log.
|
||||||
|
|
@ -303,7 +301,7 @@ export const titleProjectionDefinition = {
|
||||||
* @returns the immutable latest title snapshot, or `undefined`.
|
* @returns the immutable latest title snapshot, or `undefined`.
|
||||||
*/
|
*/
|
||||||
export function foldSessionTitle(events: readonly SessionEvent[]): SessionTitleSnapshot | undefined {
|
export function foldSessionTitle(events: readonly SessionEvent[]): SessionTitleSnapshot | undefined {
|
||||||
let state: TitleUnitState | null = titleProjectionDefinition.init()
|
let state: TitleProjection | null = titleProjectionDefinition.init()
|
||||||
for (const event of events) state = titleProjectionDefinition.apply(state, event)
|
for (const event of events) state = titleProjectionDefinition.apply(state, event)
|
||||||
return state === null ? undefined : titleSnapshotFromState(state)
|
return state === null ? undefined : titleSnapshotFromState(state)
|
||||||
}
|
}
|
||||||
|
|
@ -400,8 +398,8 @@ export class SessionTitleService extends Service {
|
||||||
* @returns latest title snapshot, or `undefined` before eligible input.
|
* @returns latest title snapshot, or `undefined` before eligible input.
|
||||||
*/
|
*/
|
||||||
get(session: Session): SessionTitleSnapshot | undefined {
|
get(session: Session): SessionTitleSnapshot | undefined {
|
||||||
const state = this.ctx.sessionProjections.stateOf(session, 'title')
|
const state = this.ctx.sessionProjections.stateOf(session, 'title') as TitleProjection | null
|
||||||
return state === null || state === undefined ? undefined : titleSnapshotFromState(state)
|
return state === null ? undefined : titleSnapshotFromState(state)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -734,8 +732,7 @@ export class SessionTitleService extends Service {
|
||||||
}
|
}
|
||||||
|
|
||||||
private titleInputOf(session: Session): TitleInputState {
|
private titleInputOf(session: Session): TitleInputState {
|
||||||
/* v8 ignore next -- session-title registers its own titleInput unit, so the key is always present */
|
return this.ctx.sessionProjections.stateOf(session, 'titleInput') as TitleInputState
|
||||||
return this.ctx.sessionProjections.stateOf(session, 'titleInput') ?? EMPTY_TITLE_INPUT
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Queue detached service work and retain it through service disposal. */
|
/** Queue detached service work and retain it through service disposal. */
|
||||||
|
|
|
||||||
|
|
@ -46,7 +46,7 @@ export const subagentModelSelectionProjectionDefinition = {
|
||||||
if (allowedModels.length === 0) {
|
if (allowedModels.length === 0) {
|
||||||
throw new Error('subagent/model-selection-policy requires at least one route')
|
throw new Error('subagent/model-selection-policy requires at least one route')
|
||||||
}
|
}
|
||||||
return allowedModels.map(route => ({ ...route }))
|
return allowedModels
|
||||||
},
|
},
|
||||||
} satisfies ProjectionDefinition<'subagentModelSelectionPolicy', AllowedModelRoute[] | null>
|
} satisfies ProjectionDefinition<'subagentModelSelectionPolicy', AllowedModelRoute[] | null>
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue