fix(session-projection): close review findings from the fold migration
- permission-presets: register the permissions unit synchronously before the existing-session sweep, so a remount reads folded knob state instead of treating every session as fresh; add a regression test for that path - sandbox-policy/terminal-bash: mount the projection registry in the 5 pwsh terminal tests, the sdk-minimal bundle, and the e2b fixture composition; declare the new package dependency in both manifests - plan-mode: restore .strict() on the plan unit state schema and drop the deleted foldPlanMode from the bilingual READMEs - session-title/session-projection: sync bilingual READMEs to the mandatory projection seam and re-record translation pairing - docs: turnBoundary reader contract, subagent schema comment, token-meter import comment, sandbox-policy module docstring
This commit is contained in:
1 parent
df0e0960c2
commit
a6c7c70d4f
24 files changed
+95
-40
No files matched your search
@@ -33,6 +33,11 @@
|
||||
- id: sandbox
|
||||
name: '@deepseek-ai/dsh-sandbox-local'
|
||||
|
||||
# Shared projection registry: sandbox-policy and terminal-bash fold
|
||||
# sandbox-mode state through its units and require it as a hard injection.
|
||||
- id: session-projection
|
||||
name: '@deepseek-ai/dsh-session-projection'
|
||||
|
||||
- id: sandbox-policy
|
||||
name: '@deepseek-ai/dsh-sandbox-policy'
|
||||
config:
|
||||
|
||||
@@ -50,6 +50,7 @@
|
||||
"@deepseek-ai/dsh-sdk-jsonrpc-server": "workspace:^",
|
||||
"@deepseek-ai/dsh-session-log-deepseek": "workspace:^",
|
||||
"@deepseek-ai/dsh-session-persistence-jsonl": "workspace:^",
|
||||
"@deepseek-ai/dsh-session-projection": "workspace:^",
|
||||
"@deepseek-ai/dsh-subprocess-local": "workspace:^",
|
||||
"@deepseek-ai/dsh-terminal": "workspace:^",
|
||||
"@deepseek-ai/dsh-terminal-bash": "workspace:^",
|
||||
|
||||
@@ -34,7 +34,8 @@ export type InboxTarget = 'next-turn' | 'next-step'
|
||||
* Reader contract: the key is registered by `dsh-agent-loop` and absent
|
||||
* otherwise. Without agent-loop no turn events exist, so readers treat an
|
||||
* absent key as "no open turn / no boundaries" — capability absence, not a
|
||||
* corrupt state — and never treat it as an error.
|
||||
* corrupt state. A reader whose behavior has no safe fallback for that
|
||||
* absence (the step-open decision, for example) may fail loud instead.
|
||||
*/
|
||||
export interface TurnBoundaryProjection {
|
||||
/** Seq of the open turn's `turn/start`, or null between turns. */
|
||||
|
||||
@@ -50,6 +50,7 @@
|
||||
"@deepseek-ai/dsh-lsp-stdio": "workspace:^",
|
||||
"@deepseek-ai/dsh-sandbox-policy": "workspace:^",
|
||||
"@deepseek-ai/dsh-session": "workspace:^",
|
||||
"@deepseek-ai/dsh-session-projection": "workspace:^",
|
||||
"@deepseek-ai/dsh-subprocess-e2b": "workspace:^",
|
||||
"@deepseek-ai/dsh-terminal": "workspace:^",
|
||||
"@deepseek-ai/dsh-terminal-bash": "workspace:^",
|
||||
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
import TerminalSessionService, { TerminalSessionId } from '@deepseek-ai/dsh-terminal'
|
||||
import { BashTerminalBackend } from '@deepseek-ai/dsh-terminal-bash'
|
||||
import SandboxPolicyService from '@deepseek-ai/dsh-sandbox-policy'
|
||||
import SessionProjectionRegistry from '@deepseek-ai/dsh-session-projection'
|
||||
import { Session, SessionId } from '@deepseek-ai/dsh-session'
|
||||
import E2BSubprocessRuntime from '@deepseek-ai/dsh-subprocess-e2b'
|
||||
|
||||
@@ -52,6 +53,7 @@ describe.skipIf(!process.env.E2B_API_KEY)('E2B live Loader composition', () => {
|
||||
runtimeRoot: '/home/user/.dsh-e2b',
|
||||
getSandbox: async () => sandbox,
|
||||
} as never)
|
||||
await ctx.plugin(SessionProjectionRegistry)
|
||||
const sandboxPolicyFiber = await ctx.plugin(SandboxPolicyService, {
|
||||
mode: 'danger-full-access',
|
||||
workspaceRoot: '/home/user',
|
||||
|
||||
@@ -21,6 +21,9 @@
|
||||
- id: agents
|
||||
name: '@deepseek-ai/dsh-agent'
|
||||
|
||||
- id: session-projection
|
||||
name: '@deepseek-ai/dsh-session-projection'
|
||||
|
||||
- id: sandbox-policy
|
||||
name: '@deepseek-ai/dsh-sandbox-policy'
|
||||
config:
|
||||
|
||||
@@ -189,13 +189,6 @@ export class PermissionPresetService extends Service {
|
||||
onChange: () => {},
|
||||
})
|
||||
|
||||
ctx.on('session/created', (session) => {
|
||||
this.pinInitialPermission(session)
|
||||
})
|
||||
for (const session of ctx.sessions.list()) {
|
||||
this.pinInitialPermission(session)
|
||||
}
|
||||
|
||||
// zod `.optional()` types the key `string | undefined` while the domain
|
||||
// says `description?: string`; on the JSON wire the two serialize
|
||||
// identically (absent), so the cast records exactly that
|
||||
@@ -209,18 +202,25 @@ export class PermissionPresetService extends Service {
|
||||
currentValue: zod.string().min(1),
|
||||
}) as unknown as zod.ZodType<PermissionSelect>
|
||||
// The `permissions` projection unit folds the three whole-value knob
|
||||
// events; it registers through the projection registry.
|
||||
ctx.inject(['sessionProjections'], (projectionCtx) => {
|
||||
projectionCtx.sessionProjections.register({
|
||||
key: 'permissions',
|
||||
stateVersion: 1,
|
||||
stateSchema: knobStateSchema,
|
||||
init: () => EMPTY_KNOBS,
|
||||
apply: applyKnobEvent,
|
||||
wire: { viewSchema: selectSchema, view: state => this.selectFor(state) },
|
||||
})
|
||||
// events. `sessionProjections` is a hard injection, so the registration is
|
||||
// synchronous and lands before the sweep below; otherwise the sweep would
|
||||
// read an unregistered key and treat every existing session as fresh.
|
||||
ctx.sessionProjections.register({
|
||||
key: 'permissions',
|
||||
stateVersion: 1,
|
||||
stateSchema: knobStateSchema,
|
||||
init: () => EMPTY_KNOBS,
|
||||
apply: applyKnobEvent,
|
||||
wire: { viewSchema: selectSchema, view: state => this.selectFor(state) },
|
||||
})
|
||||
|
||||
ctx.on('session/created', (session) => {
|
||||
this.pinInitialPermission(session)
|
||||
})
|
||||
for (const session of ctx.sessions.list()) {
|
||||
this.pinInitialPermission(session)
|
||||
}
|
||||
|
||||
// The /permission command: the one write path a web client uses (the
|
||||
// popup contribution submits the picked preset as this line). The child
|
||||
// activates only when a command registry is composed.
|
||||
|
||||
@@ -267,6 +267,31 @@ describe('new-session default', () => {
|
||||
expect(ctx.permissionPresets.current(existing)).toBe('workspace-write')
|
||||
})
|
||||
|
||||
it('preserves existing knob overrides when the service remounts over a knob-bearing session', async () => {
|
||||
const ctx = new Context()
|
||||
await ctx.plugin(SessionStore)
|
||||
await ctx.plugin(SessionProjectionRegistry)
|
||||
ctx.provide('shell', {
|
||||
sandboxMode: 'workspace-write',
|
||||
resolve() { throw new Error('permission tests do not execute bash') },
|
||||
run() { throw new Error('permission tests do not execute bash') },
|
||||
start() { throw new Error('permission tests do not execute bash') },
|
||||
})
|
||||
ctx.provide('approval', { config: { policy: 'ask' } })
|
||||
const existing = ctx.sessions.create(SessionId('existing-knobs'))
|
||||
existing.append('sandbox/mode', { mode: 'read-only' })
|
||||
existing.append('approval/policy', { policy: 'never' })
|
||||
|
||||
await ctx.plugin(PermissionPresetService, {})
|
||||
// The remount sweep must read the folded knob events instead of treating
|
||||
// the session as fresh; no default preset events may overwrite the
|
||||
// overrides (read-only + never matches no preset table entry).
|
||||
expect(existing.events.map(event => event.type)).toEqual([
|
||||
'sandbox/mode', 'approval/policy',
|
||||
])
|
||||
expect(ctx.permissionPresets.current(existing)).toBe(CUSTOM_PRESET)
|
||||
})
|
||||
|
||||
it('fills only missing legacy facts and preserves an unmatched seeded combination', async () => {
|
||||
const ctx = await mountedStore()
|
||||
const partial = freshSession('partial-source')
|
||||
|
||||
@@ -10,6 +10,7 @@ import { BlockAssembler, deepFreeze } from '@deepseek-ai/dsh-llm'
|
||||
import type { LlmImageRequestPricing, Message, TokenUsage } from '@deepseek-ai/dsh-llm'
|
||||
import type { EpochHeader, Session, SessionEvent } from '@deepseek-ai/dsh-session'
|
||||
import { canonicalHeader, headerEquals, isSurfaceEvent } from '@deepseek-ai/dsh-session'
|
||||
// Type-only: activates the `ctx.sessionProjections` Context declaration.
|
||||
import type {} from '@deepseek-ai/dsh-session-projection'
|
||||
import type {
|
||||
TokenMeasurement,
|
||||
|
||||
@@ -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 packages/plan/plan-mode/README.md
|
||||
README.md: 14c24507accb642909c1c9ed050368d9fa9120f1
|
||||
README.zh.md: 716f22a7b5bf7970b1eae429155d669805ce7d00
|
||||
README.md: 063e91e40fb55ea24e4fbfcefc984cd581d065b3
|
||||
README.zh.md: 8da8a7b3e01c42e619f882105dfe9e99b891a918
|
||||
@@ -82,7 +82,7 @@ Plan mode is a product package, not a capability seam: there is no swappable bac
|
||||
|
||||
### Durable state and step-boundary appends
|
||||
|
||||
The package persists one log-only whole-value event, `plan/mode`, and the last logged value is the state. A mode change appends immediately when no turn is open; during an open turn it stays pending until the next accepted in-turn pre-step — the only append point while an agent runs — and an append failure cannot block the turn. The `set`/`get`/`foldPlanMode` helpers and their exact return states live in [`src/index.ts`](src/index.ts).
|
||||
The package persists one log-only whole-value event, `plan/mode`, and the last logged value is the state. A mode change appends immediately when no turn is open; during an open turn it stays pending until the next accepted in-turn pre-step — the only append point while an agent runs — and an append failure cannot block the turn. The `set`/`get` service methods and their exact return states live in [`src/index.ts`](src/index.ts); the registered `plan` projection unit folds the same state for readers.
|
||||
|
||||
### The `/plan` command
|
||||
|
||||
@@ -94,7 +94,7 @@ The command child activates only when a commands service is composed. It maps ba
|
||||
|
||||
### Session projection unit
|
||||
|
||||
When `ctx.sessionProjections` is composed, the package registers the `plan` unit under an injected child. The unit turns logged `/plan` command runs into a candidate target, commits the logged state on `plan/mode`, and derives `{ active, pending }` for `view`, where `pending` is true only while an unsettled or successful selection differs from the logged state — a pure replay quantity recoverable from the log alone. The key merges into `SessionProjectionMap` from [`src/types.ts`](src/types.ts); the framework drives the unit, and unloading the plugin fiber unregisters the key.
|
||||
The package requires `ctx.sessionProjections` and registers the `plan` unit on activation. The unit turns logged `/plan` command runs into a candidate target, commits the logged state on `plan/mode`, and derives `{ active, pending }` for `view`, where `pending` is true only while an unsettled or successful selection differs from the logged state — a pure replay quantity recoverable from the log alone. The key merges into `SessionProjectionMap` from [`src/types.ts`](src/types.ts); the framework drives the unit, and unloading the plugin fiber unregisters the key.
|
||||
|
||||
### Source map
|
||||
|
||||
|
||||
@@ -82,7 +82,7 @@ agent 完成计划后,会以 markdown 形式、从标题开头书写计划并
|
||||
|
||||
### 持久状态与步骤边界追加
|
||||
|
||||
本包持久化一条仅记日志、整值替换的事件 `plan/mode`,最后一条已记录值即为状态。没有轮次开启时,模式变更会立即追加;轮次开启期间,它保持待生效,直到下一个被接受的轮内 pre-step——agent 运行时唯一的追加点——且追加失败不能阻塞轮次。`set`/`get`/`foldPlanMode` 辅助函数及其确切返回状态见 [`src/index.ts`](src/index.ts)。
|
||||
本包持久化一条仅记日志、整值替换的事件 `plan/mode`,最后一条已记录值即为状态。没有轮次开启时,模式变更会立即追加;轮次开启期间,它保持待生效,直到下一个被接受的轮内 pre-step——agent 运行时唯一的追加点——且追加失败不能阻塞轮次。`set`/`get` 服务方法及其确切返回状态见 [`src/index.ts`](src/index.ts);已注册的 `plan` 投影单元为读取方折叠同一状态。
|
||||
|
||||
### `/plan` 命令
|
||||
|
||||
@@ -94,7 +94,7 @@ agent 完成计划后,会以 markdown 形式、从标题开头书写计划并
|
||||
|
||||
### 会话投影单元
|
||||
|
||||
组合 `ctx.sessionProjections` 时,本包在一个注入的子插件中注册 `plan` 单元。该单元把已记录的 `/plan` 命令运行转为候选目标,在 `plan/mode` 上提交已记录状态,并为 `view` 推导 `{ active, pending }`,其中 `pending` 仅在未结算或已成功的选择与已记录状态不同时为 true——这是仅凭日志即可恢复的纯回放量。key 由 [`src/types.ts`](src/types.ts) 的声明合并加入 `SessionProjectionMap`;框架负责驱动该单元,卸载插件 fiber 会注销该 key。
|
||||
本包要求 `ctx.sessionProjections` 并在激活时注册 `plan` 单元。该单元把已记录的 `/plan` 命令运行转为候选目标,在 `plan/mode` 上提交已记录状态,并为 `view` 推导 `{ active, pending }`,其中 `pending` 仅在未结算或已成功的选择与已记录状态不同时为 true——这是仅凭日志即可恢复的纯回放量。key 由 [`src/types.ts`](src/types.ts) 的声明合并加入 `SessionProjectionMap`;框架负责驱动该单元,卸载插件 fiber 会注销该 key。
|
||||
|
||||
### 源码地图
|
||||
|
||||
|
||||
@@ -113,12 +113,15 @@ export function resolveConfig(config: PlanModeConfig): PlanModeConfig {
|
||||
return { section }
|
||||
}
|
||||
|
||||
const planUnitStateSchema = zod.object({
|
||||
const planUnitStateSchema: ZodType<PlanUnitState> = zod.object({
|
||||
active: zod.boolean(),
|
||||
wanted: zod.boolean().nullable(),
|
||||
running: zod.object({ commandId: zod.string() as unknown as ZodType<CommandId>, wanted: zod.boolean() }).nullable(),
|
||||
running: zod.object({
|
||||
commandId: zod.string() as unknown as ZodType<CommandId>,
|
||||
wanted: zod.boolean(),
|
||||
}).strict().nullable(),
|
||||
activeAtLastHeader: zod.boolean().nullable(),
|
||||
})
|
||||
}).strict()
|
||||
|
||||
/** Wire payload schema of the `plan` projection. */
|
||||
const planProjectionSchema: ZodType<PlanProjection> = zod.object({
|
||||
|
||||
@@ -2,7 +2,9 @@
|
||||
* The sandbox POLICY home (`ctx.sandboxPolicy`): the single owner of the
|
||||
* deployment's sandbox fallbacks plus per-session resolution: the file-effect
|
||||
* {@link SandboxMode}, the `workspace-write` root, and the override kit (the
|
||||
* `sandbox/mode` event, its fold, and its write path, from `./session-mode.ts`).
|
||||
* `sandbox/mode` event, its fold, and its write path; the fold is the
|
||||
* `sandboxMode` session-projection unit registered here, while the event and
|
||||
* write path come from `./session-mode.ts`).
|
||||
* Before each agent request, the owner also contributes the resolved policy to
|
||||
* the cache-safe runtime-context snapshot. The agent loop logs that snapshot as
|
||||
* model history, so replay reconstructs the same mode and root the enforcing
|
||||
|
||||
@@ -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 packages/session/session-projection/README.md
|
||||
README.md: 95ee09236966c8cbfdaf79cdcb717e04ee0ec367
|
||||
README.zh.md: 0402fbe8e3c56e44f7597bb14fb0379843ecdbca
|
||||
README.md: 2ef45b42390227d4f5367ea2a4c6023b9e24b5d8
|
||||
README.zh.md: 59f0849f5d79b32bb1b5a11ca25aa9af2593ee7f
|
||||
@@ -29,7 +29,7 @@ Mount `dsh-session-projection` wherever client carriers need current values of l
|
||||
|
||||
### When to choose it
|
||||
|
||||
Choose it when a domain keeps state that clients should see without re-deriving it — a todo list, a goal snapshot, conversation statistics. The registry drives units eagerly over committed events, so any registered unit's value is current by construction. Skip it for host-only bookkeeping that no client reads: a unit without a `wire` block stays host-only, and headless assemblies without the registry are unaffected.
|
||||
Choose it when a domain keeps state that clients should see without re-deriving it — a todo list, a goal snapshot, conversation statistics. The registry drives units eagerly over committed events, so any registered unit's value is current by construction. Skip it for host-only bookkeeping that no client reads: a unit without a `wire` block stays host-only. Unit contributors and readers declare `sessionProjections` in their plugin `inject`, so a composition without the registry fails activation instead of silently degrading.
|
||||
|
||||
### Define a projection unit
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ kind: "package-reference"
|
||||
|
||||
### 何时选择
|
||||
|
||||
当领域保存客户端应看到、但不应自行重新派生的状态——todo 清单、goal 快照、对话统计——时选择本包。注册表在已提交事件上主动驱动单元,因此任何已注册单元的值按构造即为当前值。当维护的是无客户端读取的 host-only 记账时跳过:不带 `wire` 块的单元保持 host-only,而不带注册表的 headless 组装不受影响。
|
||||
当领域保存客户端应看到、但不应自行重新派生的状态——todo 清单、goal 快照、对话统计——时选择本包。注册表在已提交事件上主动驱动单元,因此任何已注册单元的值按构造即为当前值。当维护的是无客户端读取的 host-only 记账时跳过:不带 `wire` 块的单元保持 host-only。单元贡献方与读取方在插件 `inject` 中声明 `sessionProjections`,因此不带注册表的组合会在激活时失败,而不是静默降级。
|
||||
|
||||
### 定义投影单元
|
||||
|
||||
|
||||
@@ -171,9 +171,10 @@ interface Registration {
|
||||
* older than the registry, folds `init` over the in-memory log on first
|
||||
* touch (event or read). Registration is an effect (disposer rides the
|
||||
* calling fiber): an unloaded domain plugin's key disappears from snapshots
|
||||
* and clients read it as capability absence. Domain
|
||||
* plugins register under `ctx.inject(['sessionProjections'], …)` so headless
|
||||
* assemblies without the registry stay unaffected. Registrants sharing a key
|
||||
* and clients read it as capability absence. Unit contributors and host
|
||||
* readers declare `sessionProjections` in their plugin `inject`, so a
|
||||
* composition without the registry fails activation instead of silently
|
||||
* degrading. Registrants sharing a key
|
||||
* share one unit and are counted: the same tool package mounted in N agent
|
||||
* presets registers N times, and the key survives until the last one
|
||||
* unloads.
|
||||
|
||||
@@ -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 packages/session/session-title/README.md
|
||||
README.md: 12335afc1eccd7c9b9f4a33d72549df296cbfb1b
|
||||
README.zh.md: e61297ed555c93273efc108221cb0141a9c997b7
|
||||
README.md: debc737e3f572f15856f7f78a58abe8532ae15dc
|
||||
README.zh.md: cf8079cd5566023f3dc1da8480eeec5327eebb5a
|
||||
@@ -58,7 +58,7 @@ One optional asynchronous provider may be registered through `ctx.sessionTitle.r
|
||||
|
||||
### Reading titles
|
||||
|
||||
`get(session)` folds the latest accepted title from the live or replayed log, and `foldSessionTitle(events)` is the pure fold over a log. The service also registers a `title` projection unit — the plain title string — for client list rows when a projection registry is composed. An explicit `refresh(session)` materializes the fallback when needed, then explicitly runs the registered provider over the current eligible messages.
|
||||
`get(session)` reads the latest folded title from one live or replayed session, and `foldSessionTitle(events)` is the pure fold over a log. The service requires `ctx.sessionProjections` and registers two units: the client-visible `title` unit (the accepted title string for client list rows) and the host-only `titleInput` unit, which folds the first and latest eligible messages plus their count so scheduling and fallback reads are O(1) through `stateOf()`; the full eligible prefix for one provider generation is scanned from the session log at execution time. An explicit `refresh(session)` materializes the fallback when needed, then explicitly runs the registered provider over the current eligible messages.
|
||||
|
||||
### Failures and recovery
|
||||
|
||||
|
||||
@@ -58,7 +58,7 @@ kind: "package-reference"
|
||||
|
||||
### 读取标题
|
||||
|
||||
`get(session)` 从活跃或回放日志折叠最新已接受标题,`foldSessionTitle(events)` 是对日志的纯折叠。服务还会在组合了投影注册表时注册一个 `title` 投影单元——纯标题字符串——供客户端列表行使用。显式 `refresh(session)` 在需要时物化回退,然后对当前符合条件的消息显式运行已注册提供方。
|
||||
`get(session)` 从活跃或回放会话读取折叠出的最新标题,`foldSessionTitle(events)` 是对日志的纯折叠。服务要求 `ctx.sessionProjections` 并注册两个单元:客户端可见的 `title` 单元(供客户端列表行使用的已接受标题字符串)和仅供 host 使用的 `titleInput` 单元——后者折叠第一条与最新一条合格消息及其计数,使调度与回退读取通过 `stateOf()` 达到 O(1);某次提供方生成所需的完整合格前缀,则会在执行时从会话日志中扫描取得。显式 `refresh(session)` 在需要时物化回退,然后对当前符合条件的消息显式运行已注册提供方。
|
||||
|
||||
### 失败与恢复
|
||||
|
||||
|
||||
@@ -41,7 +41,6 @@ const timingStateSchema: z.ZodType<TimingState> = z.object({
|
||||
settledMs: z.number().int().nonnegative(),
|
||||
active: activeIntervalSchema.optional(),
|
||||
pendingTurnStart: z.number().int().nonnegative().optional(),
|
||||
/** Whether the fold has crossed a descriptor in this logical log. */
|
||||
descriptorSeen: z.boolean(),
|
||||
}).strict()
|
||||
|
||||
|
||||
@@ -354,6 +354,7 @@ describe('BashTerminalBackend startup rollback', () => {
|
||||
it('bootstraps a pwsh dialect through the prompt function and scrubs bash-only env', async () => {
|
||||
const ctx = new Context()
|
||||
await ctx.plugin(EmptySandbox)
|
||||
await ctx.plugin(SessionProjectionRegistry)
|
||||
await ctx.plugin(SandboxPolicyService, { mode: 'danger-full-access', workspaceRoot: '/workspace' })
|
||||
let spawned: SubprocessTerminalSpawnSpec | undefined
|
||||
let sent: TerminalSendRequest | undefined
|
||||
@@ -391,6 +392,7 @@ describe('BashTerminalBackend startup rollback', () => {
|
||||
it('keeps waiting for stdin_read when the first settled output only echoes the prompt literal', async () => {
|
||||
const ctx = new Context()
|
||||
await ctx.plugin(EmptySandbox)
|
||||
await ctx.plugin(SessionProjectionRegistry)
|
||||
await ctx.plugin(SandboxPolicyService, { mode: 'danger-full-access', workspaceRoot: '/workspace' })
|
||||
const sends: TerminalSendRequest[] = []
|
||||
const session = {
|
||||
@@ -425,6 +427,7 @@ describe('BashTerminalBackend startup rollback', () => {
|
||||
it('rejects a pwsh bootstrap whose shell exits or times out', async () => {
|
||||
const ctx = new Context()
|
||||
await ctx.plugin(EmptySandbox)
|
||||
await ctx.plugin(SessionProjectionRegistry)
|
||||
await ctx.plugin(SandboxPolicyService, { mode: 'danger-full-access', workspaceRoot: '/workspace' })
|
||||
const sessionFor = (waitReason: TerminalWaitReason): LocalPtySession => ({
|
||||
startSend: () => ({
|
||||
@@ -449,6 +452,7 @@ describe('BashTerminalBackend startup rollback', () => {
|
||||
try {
|
||||
const ctx = new Context()
|
||||
await ctx.plugin(EmptySandbox)
|
||||
await ctx.plugin(SessionProjectionRegistry)
|
||||
await ctx.plugin(SandboxPolicyService, { mode: 'danger-full-access', workspaceRoot: '/workspace' })
|
||||
const pending = Promise.withResolvers<{
|
||||
viewport: string
|
||||
@@ -501,6 +505,7 @@ describe('BashTerminalBackend startup rollback', () => {
|
||||
it('forwards the spawn signal into the pwsh bootstrap sends', async () => {
|
||||
const ctx = new Context()
|
||||
await ctx.plugin(EmptySandbox)
|
||||
await ctx.plugin(SessionProjectionRegistry)
|
||||
await ctx.plugin(SandboxPolicyService, { mode: 'danger-full-access', workspaceRoot: '/workspace' })
|
||||
const sends: TerminalSendRequest[] = []
|
||||
const session = {
|
||||
|
||||
Generated
+6
@@ -1345,6 +1345,9 @@ importers:
|
||||
'@deepseek-ai/dsh-session-persistence-jsonl':
|
||||
specifier: workspace:^
|
||||
version: link:../../session/session-persistence-jsonl
|
||||
'@deepseek-ai/dsh-session-projection':
|
||||
specifier: workspace:^
|
||||
version: link:../../session/session-projection
|
||||
'@deepseek-ai/dsh-subprocess-local':
|
||||
specifier: workspace:^
|
||||
version: link:../../subprocess/subprocess-local
|
||||
@@ -4453,6 +4456,9 @@ importers:
|
||||
'@deepseek-ai/dsh-session':
|
||||
specifier: workspace:^
|
||||
version: link:../../core/session
|
||||
'@deepseek-ai/dsh-session-projection':
|
||||
specifier: workspace:^
|
||||
version: link:../../session/session-projection
|
||||
'@deepseek-ai/dsh-subprocess-e2b':
|
||||
specifier: workspace:^
|
||||
version: link:../subprocess-e2b
|
||||
|
||||
Reference in new issue
Block a user