mirror of
https://github.com/deepseek-ai/deepseek-harness.git
synced 2026-09-03 06:18:22 +00:00
refactor(session): trim projection migration diff
This commit is contained in:
@@ -2,5 +2,5 @@
|
||||
# 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:
|
||||
# pnpm run verify-translation-pairing --write docs/config-catalog.md
|
||||
config-catalog.md: 88a7d34b5ed72c1e7535d62f3b9163ea7d677756
|
||||
config-catalog.zh.md: f4ad394528dcde48a7c1c499acaece82ada894f4
|
||||
config-catalog.md: b7bc65cc2f2111ac549067f31518c18cf636c9b9
|
||||
config-catalog.zh.md: f934799dbab07d394732447ae2a4cd4184e10d61
|
||||
|
||||
@@ -1508,7 +1508,7 @@ export interface PresetSpec {
|
||||
|
||||
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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -1510,7 +1510,7 @@ export interface PresetSpec {
|
||||
|
||||
依赖:[`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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -2,5 +2,5 @@
|
||||
# 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:
|
||||
# pnpm run verify-translation-pairing --write docs/event-producer-consumer.md
|
||||
event-producer-consumer.md: 12efe17c1c02fa744c556996ed6e2bc9bf07deb9
|
||||
event-producer-consumer.zh.md: 108a8d1838cbd872137111735b35fe25e89303dc
|
||||
event-producer-consumer.md: cb215c9e50902cac7eaa07f6bb294f798bcf848a
|
||||
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 |
|
||||
| --- | --- | --- |
|
||||
| `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/service` | - | [`agent-presets`](../packages/preset/agent-presets), `gateway` |
|
||||
| `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/service` | - | [`agent-presets`](../packages/preset/agent-presets), `gateway` |
|
||||
| `internal/status` | - | [`agent`](../packages/core/agent) |
|
||||
|
||||
@@ -2,5 +2,5 @@
|
||||
# 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:
|
||||
# pnpm run verify-translation-pairing --write docs/persistence-catalog.md
|
||||
persistence-catalog.md: 857d3783d57decf99596527ad3b8cc5f344be616
|
||||
persistence-catalog.zh.md: 591a3951f094d470b4522fe22250154a0d1cf293
|
||||
persistence-catalog.md: 29d07f674c2ebd054b0aafa47f729504807460bc
|
||||
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 }
|
||||
```
|
||||
|
||||
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/*`
|
||||
|
||||
@@ -662,7 +662,7 @@ Source: [`packages/core/session/src/types.ts:324`](../packages/core/session/src/
|
||||
|
||||
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>
|
||||
|
||||
|
||||
@@ -521,7 +521,7 @@ export type SessionEvent<T extends SessionEventType = SessionEventType> = {
|
||||
'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/*`
|
||||
|
||||
@@ -664,7 +664,7 @@ export type SessionEvent<T extends SessionEventType = SessionEventType> = {
|
||||
|
||||
类型:[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>
|
||||
|
||||
|
||||
@@ -2,5 +2,5 @@
|
||||
# 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:
|
||||
# pnpm run verify-translation-pairing --write docs/subsystems/sandbox.md
|
||||
sandbox.md: 8afa1d2ef2673b240938f5c70c8f58f8e16c16df
|
||||
sandbox.zh.md: 6ee68d7d26cdcaed6f62a27fecfc3604e4d4f7a5
|
||||
sandbox.md: 7160ac699a21a319c991ae70262a8bf61da86b0c
|
||||
sandbox.zh.md: 6d2b10d63a0e180bc0fe39cc47930e66dee72466
|
||||
|
||||
@@ -192,7 +192,7 @@ Source: [`packages/sandbox/sandbox/src/index.ts`](../../packages/sandbox/sandbox
|
||||
|
||||
### `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
|
||||
/**
|
||||
|
||||
@@ -192,7 +192,7 @@ Source: [`packages/sandbox/sandbox/src/index.ts`](../../packages/sandbox/sandbox
|
||||
|
||||
### `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
|
||||
/**
|
||||
|
||||
@@ -39,7 +39,7 @@ const timeContextStateSchema = zod.object({
|
||||
})
|
||||
|
||||
/** 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. */
|
||||
export const inject = ['agents', 'sessionProjections']
|
||||
@@ -182,15 +182,13 @@ export function apply(ctx: Context, config: Config): void {
|
||||
const decision = await next()
|
||||
if (decision.kind === 'reject' || signal.aborted) return decision
|
||||
const now = Date.now()
|
||||
const state = ctx.sessionProjections.stateOf(agent.session, 'timeContext')
|
||||
if (state !== undefined && refreshIntervalMs !== undefined && refreshIntervalMs > 0) {
|
||||
const state = ctx.sessionProjections.stateOf(agent.session, 'timeContext') as TimeContextProjection
|
||||
if (refreshIntervalMs !== undefined && refreshIntervalMs > 0) {
|
||||
const lastInjection = state.lastInjectionTime
|
||||
if (lastInjection != null
|
||||
&& now >= lastInjection
|
||||
&& 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 */
|
||||
const previous = step === 1
|
||||
? 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
|
||||
const bash = ctx.get('shell')
|
||||
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) {
|
||||
const now = Date.now()
|
||||
if (now >= previous.time && now - previous.time < refreshIntervalMs) return decision
|
||||
|
||||
@@ -1,7 +1,13 @@
|
||||
/** Agent Teams runtime invariant companion. */
|
||||
|
||||
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'
|
||||
|
||||
@@ -10,8 +16,20 @@ export const name = 'team-invariant'
|
||||
/** Invariant registry required by the companion. */
|
||||
export const inject = ['invariants']
|
||||
|
||||
/** No runtime invariant: the Team projection owns event decoding and relational state transitions. */
|
||||
const install: InvariantInstaller = () => {}
|
||||
/** Validate candidate Team events against the projected committed prefix. */
|
||||
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. */
|
||||
export const apply = (ctx: Context): Promise<() => void> =>
|
||||
|
||||
@@ -186,7 +186,7 @@ type TeamSessionEvent = SessionEvent<TeamEventType>
|
||||
* @param event - candidate Session event.
|
||||
* @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'
|
||||
|| event.type === 'team/task'
|
||||
|| event.type === 'team/message/queued'
|
||||
@@ -220,8 +220,8 @@ function parseCurrentTeamEvent(event: TeamSessionEvent): TeamSessionEvent {
|
||||
}
|
||||
|
||||
function applyProjectionEvent(state: TeamProjectionState, event: SessionEvent): void {
|
||||
if (!isTeamEvent(event)) return
|
||||
if (state.failure !== undefined) return
|
||||
if (!isTeamEvent(event)) return
|
||||
try {
|
||||
const selector = parsePersisted(event.type, teamEventSelectorSchema, event.data)
|
||||
if (selector.teamId !== state.id) return
|
||||
|
||||
@@ -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',
|
||||
summary: 'The sandbox policy seam: the deployment default mode and workspace-write root, with per-session overrides folded from the log.',
|
||||
description: '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 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: [
|
||||
{
|
||||
signature: 'readonly defaultMode: SandboxMode',
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
import { readFileSync } from 'node:fs'
|
||||
import type { Context } from '@deepseek-ai/cordis'
|
||||
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 { createUserMessage } 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. */
|
||||
function lastTurn(ctx: Context, agent: Agent | undefined): number {
|
||||
if (!agent) return 0
|
||||
/* v8 ignore next -- agent-present hook points run inside AgentLoop, which owns this projection. */
|
||||
return ctx.sessionProjections.stateOf(agent.session, 'turnBoundary')?.lastTurn ?? 0
|
||||
const boundary = ctx.sessionProjections.stateOf(agent.session, 'turnBoundary') as TurnBoundaryProjection
|
||||
return boundary.lastTurn
|
||||
}
|
||||
|
||||
/** 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.
|
||||
import type {} from '@deepseek-ai/dsh-session-projection'
|
||||
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'
|
||||
|
||||
@@ -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' {
|
||||
interface SessionProjectionStateMap {
|
||||
/** 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. */
|
||||
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. */
|
||||
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(),
|
||||
sandbox: zod.union([
|
||||
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. */
|
||||
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
|
||||
* events return the same reference — the registry's change gate.
|
||||
@@ -118,7 +127,7 @@ function applyPermissionEvent(
|
||||
case 'approval/policy':
|
||||
return { ...state, approval: event.data.policy }
|
||||
case 'session/end-seed':
|
||||
return state.seeded ? state : { ...state, seeded: true }
|
||||
return { ...state, seeded: true }
|
||||
default:
|
||||
return state
|
||||
}
|
||||
@@ -230,7 +239,7 @@ export class PermissionPresetService extends Service {
|
||||
key: 'permissions',
|
||||
stateVersion: 2,
|
||||
stateSchema: permissionStateSchema,
|
||||
init: () => EMPTY_PERMISSION_STATE,
|
||||
init: () => ({ ...EMPTY_KNOBS, seeded: false }),
|
||||
apply: applyPermissionEvent,
|
||||
wire: { viewSchema: selectSchema, view: state => this.selectFor(state) },
|
||||
})
|
||||
|
||||
@@ -2,15 +2,13 @@
|
||||
* Pure types of the permission domain: the ONE home of the `permissions`
|
||||
* projection-key declaration plus its payload types, free of this package's
|
||||
* host-side value imports (cordis, schemastery). Two namespace projections
|
||||
* serve it — the package root re-export for host consumers and `./client` for
|
||||
* client aggregates — with zero content duplication.
|
||||
* serve it — the package root re-export for host consumers, `./client` (the
|
||||
* browser half-entry's re-export) for client aggregates — with zero content
|
||||
* duplication.
|
||||
*
|
||||
* @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). */
|
||||
export interface PresetOption {
|
||||
/** Stable option value: the table key, or `custom`. */
|
||||
@@ -33,16 +31,6 @@ export interface PermissionSelect {
|
||||
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' {
|
||||
interface SessionProjectionMap {
|
||||
/**
|
||||
|
||||
@@ -217,8 +217,8 @@ export function apply(ctx: Context, config: Config = {}, internals: RetryInterna
|
||||
}
|
||||
|
||||
const policyKey = retryPolicyKey(policy)
|
||||
const retryState = ctx.sessionProjections.stateOf(agent.session, 'llmRetry')
|
||||
const previous = retryState?.[retryStateKey(provider, policyKey)]
|
||||
const retryState = ctx.sessionProjections.stateOf(agent.session, 'llmRetry') as LlmRetryState
|
||||
const previous = retryState[retryStateKey(provider, policyKey)]
|
||||
const previousRetry = previous?.retry ?? 0
|
||||
if (policy.mode === 'normal' && previousRetry >= policy.maxRetries) return next()
|
||||
const retry = previousRetry + 1
|
||||
|
||||
@@ -85,12 +85,6 @@ export interface SandboxPolicyRequest {
|
||||
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). */
|
||||
const sandboxModeStateSchema = zod.union([
|
||||
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 {
|
||||
// Inline schema call: the config catalog walks `static Config` statically.
|
||||
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 as zod } 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 type { GenerateOptions } from '@deepseek-ai/dsh-llm'
|
||||
import type {
|
||||
@@ -35,8 +36,8 @@ import type {
|
||||
TitleProjection,
|
||||
} from './types.ts'
|
||||
|
||||
/** Branded session-title provider identity. */
|
||||
export type SessionTitleProviderId = import('./types.ts').SessionTitleProviderId
|
||||
/** Identifies one session-title provider registration. */
|
||||
export type SessionTitleProviderId = Branded<'SessionTitleProviderId'>
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* @param state - the title unit's folded state.
|
||||
* @returns the immutable snapshot.
|
||||
*/
|
||||
export function titleSnapshotFromState(state: TitleUnitState): SessionTitleSnapshot {
|
||||
function titleSnapshotFromState(state: TitleProjection): SessionTitleSnapshot {
|
||||
return deepFreeze({
|
||||
title: state.title,
|
||||
messageSeqs: [...state.messageSeqs],
|
||||
@@ -272,7 +270,7 @@ const titleProjectionSchema = zod.object({
|
||||
]),
|
||||
eventSeq: zod.number().int().nonnegative(),
|
||||
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()
|
||||
|
||||
@@ -295,7 +293,7 @@ export const titleProjectionDefinition = {
|
||||
viewSchema: titleViewSchema,
|
||||
view: state => state?.title ?? null,
|
||||
},
|
||||
} satisfies ProjectionDefinition<'title', TitleUnitState | null>
|
||||
} satisfies ProjectionDefinition<'title', TitleProjection | null>
|
||||
|
||||
/**
|
||||
* Fold the latest title from a session log.
|
||||
@@ -303,7 +301,7 @@ export const titleProjectionDefinition = {
|
||||
* @returns the immutable latest title snapshot, or `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)
|
||||
return state === null ? undefined : titleSnapshotFromState(state)
|
||||
}
|
||||
@@ -400,8 +398,8 @@ export class SessionTitleService extends Service {
|
||||
* @returns latest title snapshot, or `undefined` before eligible input.
|
||||
*/
|
||||
get(session: Session): SessionTitleSnapshot | undefined {
|
||||
const state = this.ctx.sessionProjections.stateOf(session, 'title')
|
||||
return state === null || state === undefined ? undefined : titleSnapshotFromState(state)
|
||||
const state = this.ctx.sessionProjections.stateOf(session, 'title') as TitleProjection | null
|
||||
return state === null ? undefined : titleSnapshotFromState(state)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -734,8 +732,7 @@ export class SessionTitleService extends Service {
|
||||
}
|
||||
|
||||
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') ?? EMPTY_TITLE_INPUT
|
||||
return this.ctx.sessionProjections.stateOf(session, 'titleInput') as TitleInputState
|
||||
}
|
||||
|
||||
/** Queue detached service work and retain it through service disposal. */
|
||||
|
||||
@@ -46,7 +46,7 @@ export const subagentModelSelectionProjectionDefinition = {
|
||||
if (allowedModels.length === 0) {
|
||||
throw new Error('subagent/model-selection-policy requires at least one route')
|
||||
}
|
||||
return allowedModels.map(route => ({ ...route }))
|
||||
return allowedModels
|
||||
},
|
||||
} satisfies ProjectionDefinition<'subagentModelSelectionPolicy', AllowedModelRoute[] | null>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user