fix(jobs): use one durable job id and ordered shutdown
This commit is contained in:
1 parent
2be7329142
commit
54c13688a8
133 files changed
+855
-687
No files matched your search
@@ -9,11 +9,11 @@
|
||||
en: b3d0591cc559e466
|
||||
zh: aa4c385e84f7eaa1
|
||||
/agent-note-persist-owned-job-lifecycle-facts/decision:
|
||||
en: b7a176ff0f75ef42
|
||||
zh: 1170c9e32612d24a
|
||||
en: c982a59cbe5202d3
|
||||
zh: 9315962d2e4b8f0a
|
||||
/agent-note-persist-owned-job-lifecycle-facts/alternatives-considered:
|
||||
en: 0e3b8c785146662f
|
||||
zh: 1a248326522600c8
|
||||
en: 720a8c39eadd09aa
|
||||
zh: ed09e09eb6ea9b99
|
||||
/agent-note-persist-owned-job-lifecycle-facts/consequences:
|
||||
en: e5014d6bef085dd2
|
||||
zh: e2865641b7c8e2e8
|
||||
en: 306ba072e0c690c0
|
||||
zh: 2775f26c9206a386
|
||||
@@ -10,7 +10,7 @@ SDK consumers receive Session events, while job registration and settlement prev
|
||||
|
||||
## Decision
|
||||
|
||||
The local provider captures `job/registered` and `job/settled` facts for owned jobs, retaining the registry's epoch-millisecond timestamps, original owner, producer kind, label, and optional creating tool call. An execution UUID pairs records across restarts and copied history without changing the ids accepted by job controls. Unowned work has no Session journal.
|
||||
The local provider captures `job/registered` and `job/settled` facts for owned jobs, retaining the registry's epoch-millisecond timestamps, original owner, producer kind, label, and optional creating tool call. A UUID-backed `JobId` identifies both live controls and historical records across registry restarts and copied history. Unowned work has no Session journal.
|
||||
|
||||
The terminal record distinguishes a producer report from forced bookkeeping: `producerReported` is true when `JobHooks.done` fulfills, and false when that promise rejects or teardown cancellation throws. It reports fulfillment, not independently confirmed resource release. Missing terminal records remain incomplete and are not reconstructed from later reads or process restart.
|
||||
|
||||
@@ -22,12 +22,16 @@ The [registry contract](../../../../packages/jobs/jobs/README.md) and [seam cons
|
||||
|
||||
**Add timestamps only to `job_output`.** A model may never collect a job or may collect it after a long delay. Logging lifecycle commits records both cases without additional model calls or consuming output.
|
||||
|
||||
**Pair by job id alone.** The per-kind counter restarts with the registry, and Session history can outlive it. Execution UUIDs preserve equality across both lifecycle records without treating copied history as new execution.
|
||||
**Keep a reusable control id alongside an execution UUID.** Both values would identify one job. A unique `JobId` serves live controls and historical pairing.
|
||||
|
||||
**Pass cleanup only to `appExit`.** Signal exit bypasses the normal exit request and can close persistence before owned jobs settle. Ordered launcher cleanup serves both exit paths.
|
||||
|
||||
**Defer publication or propagate recording errors.** Deferral lets a synchronous hub subscriber journal a completion notice before the settlement that caused it. Throwing after the starter succeeds can hide an already-running job from its caller. Synchronous, contained recording preserves causal order without abandoning execution.
|
||||
|
||||
## Consequences
|
||||
|
||||
The log and both SDKs expose execution intervals without changing tool schemas, model messages, output cursors, completion notices, or job scheduling. These intervals run from registration commit through registry settlement, including the producer work and cleanup between those points; they are neither CPU time nor a directly additive rollout-latency metric. Hard process loss or an append failure can leave incomplete history. Persisted facts do not resume an external process or its output ring.
|
||||
The launcher drains registered resource disposers before providers on both normal and signal exit, within its existing five-second bound. Headless registers its Agent while acquisition is pending so job settlement can reach the Session writer.
|
||||
|
||||
Focused tests cover original timestamps, nested call identity, output preservation, delayed cancellation, producer rejection, forced teardown, late completion, reused control ids, causal publication order, and recording-failure containment. SDK snapshots cover the durable and protocol projections.
|
||||
The log and both SDKs expose execution intervals without changing tool schemas, output cursors, or job scheduling. Model-facing controls and notices carry the unique JobId; a bounded notice points to job_list when the full id cannot fit. These intervals run from registration commit through registry settlement, including the producer work and cleanup between those points; they are neither CPU time nor a directly additive rollout-latency metric. Hard process loss or an append failure can leave incomplete history. Persisted facts do not resume an external process or its output ring.
|
||||
|
||||
Focused tests cover original timestamps, nested call identity, output preservation, delayed cancellation, producer rejection, forced teardown, late completion, unique control ids, causal publication order, and recording-failure containment. SDK snapshots cover the durable and protocol projections.
|
||||
@@ -10,7 +10,7 @@ SDK 消费方接收 Session 事件,而 job 注册和结算此前只存在于
|
||||
|
||||
## Decision
|
||||
|
||||
本地提供方为有所有者的 job 捕获 `job/registered` 与 `job/settled` 事实,保留注册表的 Unix 毫秒时间戳、原始所有者、生产方 kind、label 和可选的创建工具调用。执行 UUID 在重启和历史复制后仍能配对记录,同时不改变 job 控制接口接受的 id。无主工作没有 Session 日志。
|
||||
本地提供方为有所有者的 job 捕获 `job/registered` 与 `job/settled` 事实,保留注册表的 Unix 毫秒时间戳、原始所有者、生产方 kind、label 和可选的创建工具调用。由 UUID 构成的 `JobId` 在注册表重启和历史复制后同时标识运行中的控制对象和历史记录。无主工作没有 Session 日志。
|
||||
|
||||
终态记录区分生产方报告与强制记账:`producerReported` 在 `JobHooks.done` 兑现时为 true,在该 promise 拒绝或销毁取消抛错时为 false。它记录兑现情况,不独立确认资源已释放。缺少终态记录时保持不完整,不根据较晚的读取或进程重启重建。
|
||||
|
||||
@@ -22,12 +22,16 @@ SDK 消费方接收 Session 事件,而 job 注册和结算此前只存在于
|
||||
|
||||
**仅向 `job_output` 添加时间戳。** 模型可能永远不收集某个 job,也可能很晚才收集。记录生命周期提交能覆盖这两种情况,无需额外模型调用或消费输出。
|
||||
|
||||
**仅按 job id 配对。** 每个 kind 的计数器随注册表重启,而 Session 历史可能更长久。执行 UUID 保留两条生命周期记录的相等关系,不将复制的历史视为新执行。
|
||||
**保留可重用的控制 id,另设执行 UUID。** 两个值会标识同一个 job。唯一的 `JobId` 同时服务运行期控制和历史配对。
|
||||
|
||||
**仅通过 `appExit` 传递清理。** 信号退出不调用正常退出请求,可能在有所有者的 job 结算前关闭持久化。启动器的有序清理覆盖两条退出路径。
|
||||
|
||||
**延后发布或传播记录错误。** 延后发布让同步 hub 订阅者可能先记录完成通知、后记录产生通知的结算事实。starter 成功后抛错可能使调用方拿不到已运行 job 的 id。同步并隔离记录错误既保留因果顺序,也不会放弃执行。
|
||||
|
||||
## Consequences
|
||||
|
||||
日志与两种 SDK 暴露执行区间,不改变工具 schema、模型消息、输出游标、完成通知或 job 调度。区间从注册提交延续到注册表结算,包含这两个时刻之间的生产方工作与清理;它既不是 CPU 时间,也不是可直接相加的 rollout 延迟指标。进程硬退出或追加失败可能留下不完整历史。持久化事实不会恢复外部进程或其输出环。
|
||||
启动器在正常退出和信号退出时,都在已有的五秒时限内先排空已注册资源,再销毁提供方。Headless 在取得 Agent 的操作仍在等待时注册清理,使 job 结算能够写入 Session。
|
||||
|
||||
定向测试覆盖原始时间戳、嵌套调用标识、输出保留、延后取消、生产方拒绝、强制销毁、较晚完成、控制 id 重用、因果发布顺序,以及记录错误隔离。SDK 快照覆盖持久化与协议投影。
|
||||
日志与两种 SDK 暴露执行区间,不改变工具 schema、输出游标或 job 调度。模型侧控制和通知使用唯一的 JobId;有界通知无法容纳完整 id 时指向 job_list。区间从注册提交延续到注册表结算,包含这两个时刻之间的生产方工作与清理;它既不是 CPU 时间,也不是可直接相加的 rollout 延迟指标。进程硬退出或追加失败可能留下不完整历史。持久化事实不会恢复外部进程或其输出环。
|
||||
|
||||
定向测试覆盖原始时间戳、嵌套调用标识、输出保留、延后取消、生产方拒绝、强制销毁、较晚完成、唯一控制 id、因果发布顺序,以及记录错误隔离。SDK 快照覆盖持久化与协议投影。
|
||||
@@ -9,11 +9,11 @@
|
||||
en: d7bf1b1699ce0dc8
|
||||
zh: f10dfd0e4787bb44
|
||||
/agent-note-raise-the-node-lts-engine-floor-to-22-19/decision:
|
||||
en: 355366989e655caf
|
||||
zh: 51ac6466f65e6d58
|
||||
en: 0051c0d8c1948f63
|
||||
zh: cbab7aa0f53f7200
|
||||
/agent-note-raise-the-node-lts-engine-floor-to-22-19/consequences:
|
||||
en: cf076eee91c2ec6a
|
||||
zh: d4dea1eb9535ed98
|
||||
en: 0b9fbe932d9a8b1f
|
||||
zh: 642e19d06caecef2
|
||||
/agent-note-raise-the-node-lts-engine-floor-to-22-19/alternatives-considered:
|
||||
en: c433389b2064eaf9
|
||||
zh: 57de2af812def1c4
|
||||
@@ -10,11 +10,7 @@ The Node 22 branch of the root `engines.node` range is a contract for the instal
|
||||
|
||||
## Decision
|
||||
|
||||
Set `engines.node` to `^22.19.0 || >=24.0.0` and test keyless CI on `['22.19', 24, 26]`. The primary Linux Node 24 jobs own the complete typecheck and unit coverage inventory; every version runs focused source-worker, Zstandard, source-launch, and [jsdom storage](../../archived/testing/2026-07-30-vitest-jsdom-webstorage-ownership.md) smokes without repeating that inventory. The real-API e2e workflow stays on Node 24 because it exercises API integration rather than the runtime floor.
|
||||
|
||||
<a id="windows-coverage-runtime"></a>
|
||||
|
||||
Windows PR coverage uses Node 26.10.0, which includes the [cppgc Realm cleanup fix](https://github.com/nodejs/node/pull/65778). Windows build and native-process checks use Node 24. Moving coverage back requires a Node 24 release containing the [pending backport](https://github.com/nodejs/node/pull/66168); the full test inventory, isolation and coverage thresholds remain required.
|
||||
Set `engines.node` to `^22.19.0 || >=24.0.0` and test keyless CI on `['22.19', 24, 26]`. The primary Node 24 jobs own the complete typecheck and unit coverage inventory; every version runs focused source-worker, Zstandard, source-launch, and [jsdom storage](../../archived/testing/2026-07-30-vitest-jsdom-webstorage-ownership.md) smokes without repeating that inventory. The real-API e2e workflow stays on Node 24 because it exercises API integration rather than the runtime floor.
|
||||
|
||||
Two Node features gate the source runtime:
|
||||
|
||||
@@ -28,7 +24,7 @@ Those source features clear on the 22.x line at **22.18**, but the installed Pi
|
||||
## Consequences
|
||||
|
||||
- The advertised LTS branch no longer undercuts the Pi adapter dependency floor.
|
||||
- CI proves the Node 22 LTS floor directly with Node 22.19, keeps primary Linux coverage on `node: 24`, and exercises Node 26 as the next even line; focused compatibility smokes run on all three versions.
|
||||
- CI proves the Node 22 LTS floor directly with Node 22.19, keeps primary coverage on `node: 24`, and exercises Node 26 as the next even line; focused compatibility smokes run on all three versions.
|
||||
- The built-mode smoke needs no version-conditional flag: at 22.19 type-stripping is already the default, so the example-owned TypeScript driver stays a plain `node fixture.ts` path.
|
||||
- A future dependency or source API that raises the runtime floor must move `engines.node`, the compatibility matrix, and this Agent Note in the same change.
|
||||
|
||||
|
||||
@@ -10,11 +10,7 @@ Status: implemented
|
||||
|
||||
## 决策
|
||||
|
||||
将 `engines.node` 设为 `^22.19.0 || >=24.0.0`,并在 `['22.19', 24, 26]` 上运行 keyless CI。主要的 Linux Node 24 任务负责整套类型检查和单元测试覆盖率任务;三个版本均运行 source-worker、Zstandard、source-launch 和 [jsdom 存储](../../archived/testing/2026-07-30-vitest-jsdom-webstorage-ownership.md) 专项冒烟测试,不重复这套类型检查和覆盖率任务。真实 API 的 e2e 工作流保持在 Node 24 上,因为它验证的是 API 集成而非运行时下限。
|
||||
|
||||
<a id="windows-coverage-runtime"></a>
|
||||
|
||||
Windows PR 覆盖率使用 Node 26.10.0,其中包含 [cppgc Realm 清理修复](https://github.com/nodejs/node/pull/65778)。Windows 构建和原生进程检查使用 Node 24。将覆盖率切回 Node 24 需要采用包含[待合入回移](https://github.com/nodejs/node/pull/66168)的正式版本;完整测试清单、隔离和覆盖率阈值仍是必需项。
|
||||
将 `engines.node` 设为 `^22.19.0 || >=24.0.0`,并在 `['22.19', 24, 26]` 上运行 keyless CI。主要的 Node 24 任务负责整套类型检查和单元测试覆盖率任务;三个版本均运行 source-worker、Zstandard、source-launch 和 [jsdom 存储](../../archived/testing/2026-07-30-vitest-jsdom-webstorage-ownership.md) 专项冒烟测试,不重复这套类型检查和覆盖率任务。真实 API 的 e2e 工作流保持在 Node 24 上,因为它验证的是 API 集成而非运行时下限。
|
||||
|
||||
两个 Node 特性决定了源码运行时的门槛:
|
||||
|
||||
@@ -28,7 +24,7 @@ Windows PR 覆盖率使用 Node 26.10.0,其中包含 [cppgc Realm 清理修复
|
||||
## 后果
|
||||
|
||||
- 宣传的 LTS 分支不再低于 Pi 适配器依赖的下限。
|
||||
- CI 通过 Node 22.19 直接验证 Node 22 LTS 下限,将主要 Linux 覆盖率任务保留在 `node: 24`,并用 Node 26 验证下一个偶数线;三个版本均运行聚焦的兼容性冒烟测试。
|
||||
- CI 通过 Node 22.19 直接验证 Node 22 LTS 下限,将主要覆盖率任务保留在 `node: 24`,并用 Node 26 验证下一个偶数线;三个版本均运行聚焦的兼容性冒烟测试。
|
||||
- 构建模式冒烟测试无需版本条件标志:在 22.19 上类型剥离已是默认行为,因此示例自有的 TypeScript driver 保持使用纯 `node fixture.ts` 路径。
|
||||
- 未来若依赖或源码 API 提高运行时下限,必须在同一变更中同步调整 `engines.node`、兼容性矩阵和本 Agent Note。
|
||||
|
||||
|
||||
@@ -584,7 +584,7 @@ jobs:
|
||||
&& github.event.pull_request.user.login != 'dependabot[bot]'
|
||||
&& fromJSON('["self-hosted", "dsh-win-ci", "windows"]')
|
||||
|| 'dsh-windows-2025-16core' }}
|
||||
name: windows node 26 / coverage
|
||||
name: windows node 24 / coverage
|
||||
timeout-minutes: 120
|
||||
env:
|
||||
# Shared hosts expose host CPUs, not an isolated allocation.
|
||||
@@ -620,12 +620,9 @@ jobs:
|
||||
- uses: pnpm/[email protected]
|
||||
with:
|
||||
dest: ${{ runner.temp }}/setup-pnpm-js-${{ github.run_id }}-${{ github.run_attempt }}-${{ github.job }}
|
||||
# Node 24 still awaits the cppgc Realm-lifetime backport (#66168).
|
||||
# 26.10.0 contains nodejs/node#65778; an exact version prevents a
|
||||
# runner cache from selecting an older Node 26 with the same crash.
|
||||
- uses: actions/[email protected]
|
||||
with:
|
||||
node-version: '26.10.0'
|
||||
node-version: ${{ env.PRIMARY_NODE_VERSION }}
|
||||
- name: Install (immutable)
|
||||
shell: pwsh
|
||||
# See 2026-08-30-windows-refs-store-block-clone-install for the
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
/** Bounded, escalating process shutdown for the long-lived CLI surfaces. */
|
||||
|
||||
import type { AppShutdown } from '@deepseek-ai/dsh-cmdline'
|
||||
|
||||
/** Maximum grace allowed for the application tree to dispose before process exit. */
|
||||
export const PROCESS_SHUTDOWN_TIMEOUT_MS = 5_000
|
||||
|
||||
/** Process-exit controller shared by normal completion and Unix signal handlers. */
|
||||
export interface ProcessShutdown {
|
||||
/** Start or join graceful disposal; caller-owned cleanup runs under the same bound before the tree. */
|
||||
shutdown(code: number, beforeDispose?: () => Promise<void>): Promise<void>
|
||||
export interface ProcessShutdown extends AppShutdown {
|
||||
/** Start or join graceful disposal before allowing natural completion with `code`. */
|
||||
shutdown(code: number): Promise<void>
|
||||
/** Start graceful disposal followed by exit, or force exit when shutdown is already running. */
|
||||
interrupt(code: number): void
|
||||
}
|
||||
@@ -29,6 +31,7 @@ export function createProcessShutdown(
|
||||
let timeout: ReturnType<typeof setTimeout> | undefined
|
||||
let completed = false
|
||||
let forceExited = false
|
||||
const disposers = new Set<{ dispose: () => Promise<void> }>()
|
||||
|
||||
const clearExitTimeout = (): void => {
|
||||
/* v8 ignore else -- shutdown() arms the timer before any asynchronous exit path can run. */
|
||||
@@ -49,15 +52,17 @@ export function createProcessShutdown(
|
||||
complete(code)
|
||||
}
|
||||
|
||||
const start = (code: number, forceAfterDispose: boolean, beforeDispose?: () => Promise<void>): Promise<void> => {
|
||||
const start = (code: number, forceAfterDispose: boolean): Promise<void> => {
|
||||
if (pending !== undefined) return pending
|
||||
timeout = setTimeout(() => { forceExitOnce(code) }, timeoutMs)
|
||||
pending = Promise.resolve().then(async () => {
|
||||
try {
|
||||
if (beforeDispose !== undefined) await beforeDispose()
|
||||
} finally {
|
||||
await dispose()
|
||||
const failures: unknown[] = []
|
||||
for (const entry of disposers) {
|
||||
disposers.delete(entry)
|
||||
try { await entry.dispose() } catch (error) { failures.push(error) }
|
||||
}
|
||||
try { await dispose() } catch (error) { failures.push(error) }
|
||||
if (failures.length > 0) throw new AggregateError(failures, 'process cleanup failed')
|
||||
}).then(
|
||||
() => {
|
||||
if (forceAfterDispose) forceExitOnce(code)
|
||||
@@ -69,8 +74,14 @@ export function createProcessShutdown(
|
||||
}
|
||||
|
||||
return {
|
||||
shutdown(code, beforeDispose) {
|
||||
return start(code, false, beforeDispose)
|
||||
addDisposer(dispose) {
|
||||
if (pending !== undefined) throw new Error('cannot register cleanup after shutdown starts')
|
||||
const entry = { dispose }
|
||||
disposers.add(entry)
|
||||
return () => { disposers.delete(entry) }
|
||||
},
|
||||
shutdown(code) {
|
||||
return start(code, false)
|
||||
},
|
||||
interrupt(code) {
|
||||
if (pending !== undefined) {
|
||||
|
||||
@@ -306,7 +306,8 @@ export async function runProfile(options: RunProfileOptions): Promise<{ ctx: Con
|
||||
// to every app plugin that injects the argument snapshot.
|
||||
provideCmdline(hostCtx, {
|
||||
args: options.args,
|
||||
exit: (code, beforeDispose) => void shutdown.shutdown(code, beforeDispose),
|
||||
exit: code => void shutdown.shutdown(code),
|
||||
shutdown,
|
||||
ready: appReady.service,
|
||||
})
|
||||
})
|
||||
|
||||
+8
@@ -8,6 +8,14 @@ import { existsSync } from 'node:fs'
|
||||
*/
|
||||
export function apply(ctx) {
|
||||
const keepAlive = setInterval(() => {}, 60_000)
|
||||
const shutdown = ctx.get('appShutdown')
|
||||
if (shutdown === undefined) throw new Error('missing ordered shutdown')
|
||||
ctx.effect(() => shutdown.addDisposer(async () => {
|
||||
const armFile = process.env.DSH_TEST_SHUTDOWN_ARM_FILE
|
||||
if (armFile !== undefined && existsSync(armFile)) {
|
||||
process.stderr.write('dsh-test: ordered cleanup started\n')
|
||||
}
|
||||
}))
|
||||
process.stderr.write('dsh-test: never-dispose ready\n')
|
||||
ctx.effect(() => async () => {
|
||||
clearInterval(keepAlive)
|
||||
|
||||
@@ -126,6 +126,9 @@ describe.skipIf(process.platform === 'win32')('headless process shutdown (real L
|
||||
const output = await runHeadlessPtySmoke()
|
||||
expect(output).not.toContain('dsh: observing at ')
|
||||
expect(output).toContain('dsh-test: never-dispose ready')
|
||||
expect(output).toContain('dsh-test: ordered cleanup started')
|
||||
expect(output).toContain('dsh-test: never-dispose started')
|
||||
expect(output.indexOf('dsh-test: ordered cleanup started'))
|
||||
.toBeLessThan(output.indexOf('dsh-test: never-dispose started'))
|
||||
}, LOADER_SMOKE_TEST_TIMEOUT_MS)
|
||||
})
|
||||
@@ -20,6 +20,30 @@ afterEach(() => {
|
||||
})
|
||||
|
||||
describe('process shutdown', () => {
|
||||
it.each(['normal', 'signal'] as const)('runs registered cleanup in order for %s exit', async (mode) => {
|
||||
const release = deferred()
|
||||
const entered = deferred()
|
||||
const order: string[] = []
|
||||
const exit = vi.fn()
|
||||
const complete = vi.fn()
|
||||
const shutdown = createProcessShutdown(async () => { order.push('providers') }, exit, complete)
|
||||
shutdown.addDisposer(async () => { order.push('first'); entered.resolve(); await release.promise })
|
||||
const removed = vi.fn(() => Promise.resolve())
|
||||
shutdown.addDisposer(removed)()
|
||||
shutdown.addDisposer(async () => { order.push('second') })
|
||||
if (mode === 'signal') shutdown.interrupt(130)
|
||||
else void shutdown.shutdown(0)
|
||||
await entered.promise
|
||||
expect(order).toEqual(['first'])
|
||||
expect(() => shutdown.addDisposer(() => Promise.resolve())).toThrow('after shutdown starts')
|
||||
release.resolve()
|
||||
await shutdown.shutdown(0)
|
||||
expect(order).toEqual(['first', 'second', 'providers'])
|
||||
expect(removed).not.toHaveBeenCalled()
|
||||
if (mode === 'signal') expect(exit).toHaveBeenCalledWith(130)
|
||||
else expect(complete).toHaveBeenCalledWith(0)
|
||||
})
|
||||
|
||||
it('releases caller-owned work before disposing its providers within the exit bound', async () => {
|
||||
vi.useFakeTimers()
|
||||
const release = deferred()
|
||||
@@ -28,7 +52,8 @@ describe('process shutdown', () => {
|
||||
const complete = vi.fn()
|
||||
const shutdown = createProcessShutdown(dispose, exit, complete)
|
||||
const cleanup = vi.fn(() => release.promise)
|
||||
const pending = shutdown.shutdown(0, cleanup)
|
||||
shutdown.addDisposer(cleanup)
|
||||
const pending = shutdown.shutdown(0)
|
||||
await vi.advanceTimersByTimeAsync(0)
|
||||
expect(cleanup).toHaveBeenCalledOnce()
|
||||
expect(dispose).not.toHaveBeenCalled()
|
||||
@@ -44,7 +69,11 @@ describe('process shutdown', () => {
|
||||
const dispose = vi.fn(() => Promise.resolve())
|
||||
const exit = vi.fn()
|
||||
const shutdown = createProcessShutdown(dispose, exit, vi.fn())
|
||||
await shutdown.shutdown(1, () => Promise.reject(new Error('cleanup failed')))
|
||||
shutdown.addDisposer(() => Promise.reject(new Error('cleanup failed')))
|
||||
const later = vi.fn(() => Promise.resolve())
|
||||
shutdown.addDisposer(later)
|
||||
await shutdown.shutdown(1)
|
||||
expect(later).toHaveBeenCalledOnce()
|
||||
expect(dispose).toHaveBeenCalledOnce()
|
||||
expect(exit).toHaveBeenCalledWith(1)
|
||||
})
|
||||
|
||||
+2
-2
@@ -15,8 +15,8 @@
|
||||
{"type":"session/title-llm-request","data":{"titleProvider":"session-title-first-prompt-llm","messageSeqs":[8],"route":{"provider":"cli-mock","model":"cli-mock"},"system":"Create a concise title for an AI coding-assistant session from the supplied human messages.\nReturn only the title on one line, **in plain text of natural language**, with no quotes, prefix, explanation, Markdown, XML, or terminal control codes. No code is allowed.\nUse the language of the messages.\nAim for about 5 words in non-CJK languages or 10 CJK characters.\nIf the messages give little to name, still return a short best-effort title, such as Greeting, instead of explaining.","messages":[{"content":[{"type":"text","text":"Generate the session title from this JSON array of human messages:\n[{\"seq\":8,\"text\":\"Prove the product headless profile path with one real tool round trip.\"}]"}],"source":{"kind":"dsh-session-title-llm"},"role":"user","id":"{{sessionId}}"}],"maxTokens":4096,"reasoningEffort":"off"}}
|
||||
{"type":"assistant/message","data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"reasoning","text":"Inspecting the task before the tool call."},{"type":"tool-call","id":"cli-smoke-call","name":"bash","arguments":"{\"command\":\"printf CLI_TOOL_ROUND_TRIP\",\"description\":\"Prove the CLI tool round trip.\"}"}],"source":{"kind":"model","provider":"cli-mock","model":"cli-mock"},"id":"{{sessionId}}"},"usage":{"inputTokens":11,"outputTokens":3,"cacheReadTokens":2},"stream":[{"type":"chunk","time":0,"chunk":{"type":"block-start","index":0,"blockType":"reasoning"}},{"type":"reasoning-chunks","time0":0,"index":0,"dt":[],"texts":["Inspecting the task before the tool call."]},{"type":"chunk","time":0,"chunk":{"type":"block-end","index":0,"block":{"type":"reasoning","text":"Inspecting the task before the tool call."}}},{"type":"chunk","time":0,"chunk":{"type":"block-start","index":1,"blockType":"tool-call"}},{"type":"tool-call-chunks","time0":0,"index":1,"dt":[],"id":"cli-smoke-call","name":"bash","args":["{\"command\":\"printf CLI_TOOL_ROUND_TRIP\",\"description\":\"Prove the CLI tool round trip.\"}"]},{"type":"chunk","time":0,"chunk":{"type":"block-end","index":1,"block":{"type":"tool-call","id":"cli-smoke-call","name":"bash","arguments":"{\"command\":\"printf CLI_TOOL_ROUND_TRIP\",\"description\":\"Prove the CLI tool round trip.\"}"}}},{"type":"chunk","time":0,"chunk":{"type":"usage","usage":{"inputTokens":11,"outputTokens":3,"cacheReadTokens":2}}},{"type":"chunk","time":0,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}]},"surfaceOp":"append"}
|
||||
{"type":"tool/call","data":{"turn":1,"step":1,"callId":"cli-smoke-call","name":"bash","arguments":"{\"command\":\"printf CLI_TOOL_ROUND_TRIP\",\"description\":\"Prove the CLI tool round trip.\"}"}}
|
||||
{"type":"job/registered","data":{"executionId":"{{id:1}}","jobId":"bash-1","owner":"{{sessionId}}","kind":"bash","label":"printf CLI_TOOL_ROUND_TRIP","startedAt":0,"toolCall":{"callId":"cli-smoke-call","rootCallId":"cli-smoke-call"}}}
|
||||
{"type":"job/settled","data":{"executionId":"{{id:1}}","jobId":"bash-1","owner":"{{sessionId}}","kind":"bash","label":"printf CLI_TOOL_ROUND_TRIP","startedAt":0,"toolCall":{"callId":"cli-smoke-call","rootCallId":"cli-smoke-call"},"finishedAt":0,"status":"completed","cause":"producer","producerReported":true,"detail":"exit code: 0"}}
|
||||
{"type":"job/registered","data":{"jobId":"{{id:1}}","owner":"{{sessionId}}","kind":"bash","label":"printf CLI_TOOL_ROUND_TRIP","startedAt":0,"toolCall":{"callId":"cli-smoke-call","rootCallId":"cli-smoke-call"}}}
|
||||
{"type":"job/settled","data":{"jobId":"{{id:1}}","owner":"{{sessionId}}","kind":"bash","label":"printf CLI_TOOL_ROUND_TRIP","startedAt":0,"toolCall":{"callId":"cli-smoke-call","rootCallId":"cli-smoke-call"},"finishedAt":0,"status":"completed","cause":"producer","producerReported":true,"detail":"exit code: 0"}}
|
||||
{"type":"tool/result","data":{"turn":1,"step":1,"message":{"role":"tool","source":{"kind":"tool","callId":"cli-smoke-call"},"toolCallId":"cli-smoke-call","content":[{"type":"text","text":"CLI_TOOL_ROUND_TRIP"}],"isError":false,"id":"{{sessionId}}"},"meta":{"cwd":"{{cwd}}"}},"sourceEventSeqs":[15],"surfaceOp":"append"}
|
||||
{"type":"step/end","data":{"turn":1,"step":1}}
|
||||
{"type":"step/start","data":{"turn":1,"step":2}}
|
||||
|
||||
@@ -212,7 +212,7 @@ describe('PTC mode typed values: keyless real-process contracts', () => {
|
||||
});
|
||||
return started.jobId;
|
||||
`))
|
||||
expect(jobId).toBe('bash-1')
|
||||
expect(jobId).toMatch(/^bash-[0-9a-f-]{36}$/)
|
||||
|
||||
const polled = completion(await runCode(ctx, `
|
||||
return await tools.job_output({ job_id: ${JSON.stringify(jobId)}, wait: true, timeout_ms: 5000 });
|
||||
@@ -257,7 +257,8 @@ describe('PTC mode typed values: keyless real-process contracts', () => {
|
||||
}),
|
||||
])
|
||||
const job = ctx.jobs.list()[0]
|
||||
expect(job).toMatchObject({ id: 'bash-1', status: 'running' })
|
||||
expect(job).toMatchObject({ status: 'running' })
|
||||
expect(job?.id).toMatch(/^bash-[0-9a-f-]{36}$/)
|
||||
afterPublication.abort('outer-call-cancelled')
|
||||
expect((await running).isError).toBe(true)
|
||||
expect(ctx.jobs.list()[0]).toMatchObject({ id: job!.id, status: 'running' })
|
||||
|
||||
@@ -9,8 +9,8 @@
|
||||
en: 1387c4505dde2faf
|
||||
zh: 48570fa1c13d6910
|
||||
/session-persistence-event-catalog/persistence-type-fingerprints:
|
||||
en: 5efd64128ee0beba
|
||||
zh: a3aeb74af3e4ceed
|
||||
en: f6421cacbfe510db
|
||||
zh: 6c24e981ac836d0c
|
||||
/session-persistence-event-catalog/event-envelope:
|
||||
en: 7ebb1a2e0dd1f916
|
||||
zh: 8946f415dda5c1b8
|
||||
@@ -123,11 +123,11 @@
|
||||
en: 941358a5fef6a5b4
|
||||
zh: 941358a5fef6a5b4
|
||||
/session-persistence-event-catalog/events/job/job-registered-log-only:
|
||||
en: 9bc39178c96d5299
|
||||
zh: d6111271de612006
|
||||
en: 37ab7f962b199555
|
||||
zh: af05a3274adbe530
|
||||
/session-persistence-event-catalog/events/job/job-settled-log-only:
|
||||
en: ea26934b0ef807b0
|
||||
zh: 3083ab90282177e2
|
||||
en: dde416e1c0d32dfb
|
||||
zh: fa08197cad8acb9d
|
||||
/session-persistence-event-catalog/events/llm:
|
||||
en: 8a5c09e48ec0a6ae
|
||||
zh: 8a5c09e48ec0a6ae
|
||||
@@ -1068,17 +1068,17 @@
|
||||
en: a3087ab7bebe9371
|
||||
zh: abf6afc2f9f5b692
|
||||
/session-persistence-event-catalog/resolved-persistence-types/inboxtarget:
|
||||
en: f6262b716c38eda8
|
||||
zh: 5283994136fa5c84
|
||||
en: 938113c1b01efad1
|
||||
zh: d6829dbe979e6819
|
||||
/session-persistence-event-catalog/resolved-persistence-types/jobregistereddata:
|
||||
en: ba731d919da94e90
|
||||
zh: 0a5c909faed0d777
|
||||
en: f604eebb973fe2cf
|
||||
zh: 0c0aba1e17ecf66e
|
||||
/session-persistence-event-catalog/resolved-persistence-types/jobsettlecause:
|
||||
en: 15b854ca2279b10a
|
||||
zh: bd5812e35d8e9e30
|
||||
en: 748a4269d86e007a
|
||||
zh: 4ce04c4451bdcfe8
|
||||
/session-persistence-event-catalog/resolved-persistence-types/jobsettleddata:
|
||||
en: 2e881aee85a405a0
|
||||
zh: aecc2aee48d2e151
|
||||
en: fc8caf270a7581f7
|
||||
zh: b467e1d4191106eb
|
||||
/session-persistence-event-catalog/resolved-persistence-types/jobtoolcall:
|
||||
en: 7ffee3cc96604a88
|
||||
zh: 026abfab0e46c21a
|
||||
@@ -1335,11 +1335,11 @@
|
||||
en: 5e7d82a493a1d4d8
|
||||
zh: d2d785891dd766fc
|
||||
/session-persistence-event-catalog/resolved-persistence-types/number~2:
|
||||
en: a80b187744cc8374
|
||||
zh: c115c644aac2c802
|
||||
en: 2786fef560851bb4
|
||||
zh: 5d787475356b2606
|
||||
/session-persistence-event-catalog/resolved-persistence-types/string:
|
||||
en: c2d10e67ca221c64
|
||||
zh: b7f58ee3db797d07
|
||||
en: b6921e3641b63763
|
||||
zh: ee8ef743a532417b
|
||||
/session-persistence-event-catalog/resolved-persistence-types/string~2:
|
||||
en: 0089620bf4638263
|
||||
zh: 1922bef1713094bb
|
||||
@@ -1986,14 +1986,14 @@
|
||||
en: 8b523e0be5327519
|
||||
zh: 86ec891087b8bf24
|
||||
/session-persistence-event-catalog/resolved-persistence-types/type-image-offload:
|
||||
en: 56e0870404de34d8
|
||||
zh: b1350f62badf6ed7
|
||||
en: bf512693267d4262
|
||||
zh: 805710898b4e913a
|
||||
/session-persistence-event-catalog/resolved-persistence-types/type-job-registered:
|
||||
en: 9724ff355d3c4eb7
|
||||
zh: e1d476636ddfc348
|
||||
en: 734d3bb283cc6f66
|
||||
zh: 75bcb55f71673e34
|
||||
/session-persistence-event-catalog/resolved-persistence-types/type-job-settled:
|
||||
en: a57217bb1b7e1f57
|
||||
zh: cf5ec59f480511e7
|
||||
en: 742c3a1543414ecb
|
||||
zh: e256fb92d72b8184
|
||||
/session-persistence-event-catalog/resolved-persistence-types/type-llm-retry:
|
||||
en: 6d026baeac30a0f1
|
||||
zh: 55131683e3d5ed27
|
||||
|
||||
+18
-24
@@ -44,8 +44,8 @@ The [format references](persistence-changes/historical-formats/README.md) cover
|
||||
| `event:hook/invoked` | event | `8a6e1ec9e8db346b0e02f027db73c07a94f067a26d40c1aef1abd09c47ce7ba0` | [`{ type: "hook/invoked" }`](#persistence-type-sha256-8a6e1ec9e8db346b0e02f027db73c07a94f067a26d40c1aef1abd09c47ce7ba0) |
|
||||
| `event:hook/result` | event | `e75916628f3f10c2d50658bd143052a46285fbf1a9a700ba54947614603d26b4` | [`{ type: "hook/result" }`](#persistence-type-sha256-e75916628f3f10c2d50658bd143052a46285fbf1a9a700ba54947614603d26b4) |
|
||||
| `event:image/offload` | event | `b222069eea2d768065161c1147b1f2c78c1b54328c84b3586ae5c8f91b8ed35e` | [`{ type: "image/offload" }`](#persistence-type-sha256-b222069eea2d768065161c1147b1f2c78c1b54328c84b3586ae5c8f91b8ed35e) |
|
||||
| `event:job/registered` | event | `f05be3e066ef054a610f0825251f19ee3fcfc98908b5ceaa869ee143d7e91fa5` | [`{ type: "job/registered" }`](#persistence-type-sha256-f05be3e066ef054a610f0825251f19ee3fcfc98908b5ceaa869ee143d7e91fa5) |
|
||||
| `event:job/settled` | event | `a103c03b3bc0ca7a7809d1eb2572dec08899f7a15a90072736777acd4e517648` | [`{ type: "job/settled" }`](#persistence-type-sha256-a103c03b3bc0ca7a7809d1eb2572dec08899f7a15a90072736777acd4e517648) |
|
||||
| `event:job/registered` | event | `f5520c1d261ca86ea03735799c311a0a9ede8babb588cf0a65422576d2ee0ad1` | [`{ type: "job/registered" }`](#persistence-type-sha256-f5520c1d261ca86ea03735799c311a0a9ede8babb588cf0a65422576d2ee0ad1) |
|
||||
| `event:job/settled` | event | `edd37b1e4974af648633014435572089b1e8fb71910468a0767a7e28fdd22abf` | [`{ type: "job/settled" }`](#persistence-type-sha256-edd37b1e4974af648633014435572089b1e8fb71910468a0767a7e28fdd22abf) |
|
||||
| `event:llm/retry` | event | `525254db03b1d1e6b74cf55aced52817568331ac1f96c0818728910b6692e336` | [`{ type: "llm/retry" }`](#persistence-type-sha256-525254db03b1d1e6b74cf55aced52817568331ac1f96c0818728910b6692e336) |
|
||||
| `event:llm/retry-started` | event | `48e5c9861f16ac07e78cb7b5ae9dabdf7bb85c58baed5a51b4ad275050ea58e3` | [`{ type: "llm/retry-started" }`](#persistence-type-sha256-48e5c9861f16ac07e78cb7b5ae9dabdf7bb85c58baed5a51b4ad275050ea58e3) |
|
||||
| `event:model/selection` | event | `35203ba7ad5ef6f97d556b85df20ae98f04f09c65748cecdf8eefdb8b6405ffc` | [`{ type: "model/selection" }`](#persistence-type-sha256-35203ba7ad5ef6f97d556b85df20ae98f04f09c65748cecdf8eefdb8b6405ffc) |
|
||||
@@ -643,7 +643,7 @@ Source: [`packages/compaction/compaction-image-offload/src/projection.ts:25`](..
|
||||
'job/registered': JobRegisteredData
|
||||
```
|
||||
|
||||
Source: [`packages/jobs/jobs/src/types.ts:61`](../packages/jobs/jobs/src/types.ts)
|
||||
Source: [`packages/jobs/jobs/src/types.ts:59`](../packages/jobs/jobs/src/types.ts)
|
||||
|
||||
<a id="jobsettled--log-only"></a>
|
||||
|
||||
@@ -651,13 +651,13 @@ Source: [`packages/jobs/jobs/src/types.ts:61`](../packages/jobs/jobs/src/types.t
|
||||
|
||||
```ts persistence-catalog
|
||||
/**
|
||||
* Log-only terminal record, paired by executionId rather than a reusable jobId.
|
||||
* Log-only terminal record, paired by the same unique jobId used for live control.
|
||||
* @param data - Registration facts plus settlement time, outcome, and producer acknowledgement.
|
||||
*/
|
||||
'job/settled': JobSettledData
|
||||
```
|
||||
|
||||
Source: [`packages/jobs/jobs/src/types.ts:66`](../packages/jobs/jobs/src/types.ts)
|
||||
Source: [`packages/jobs/jobs/src/types.ts:64`](../packages/jobs/jobs/src/types.ts)
|
||||
|
||||
### `llm/*`
|
||||
|
||||
@@ -3776,7 +3776,7 @@ One of:
|
||||
- `"next-step"`
|
||||
- `"next-turn"`
|
||||
|
||||
<a id="persistence-type-sha256-c98cb9b944f83bf77ad098039861cbad71c3b8377e19c2bfccf72e1db16a6be2"></a>
|
||||
<a id="persistence-type-sha256-e3056e0dda3a379e895386e368c3ce42131460ea65e410e1d091cca98a347305"></a>
|
||||
|
||||
<a id="persistence-type-jobregistereddata"></a>
|
||||
|
||||
@@ -3784,13 +3784,12 @@ One of:
|
||||
|
||||
### `JobRegisteredData`
|
||||
|
||||
SHA-256: `c98cb9b944f83bf77ad098039861cbad71c3b8377e19c2bfccf72e1db16a6be2`
|
||||
SHA-256: `e3056e0dda3a379e895386e368c3ce42131460ea65e410e1d091cca98a347305`
|
||||
|
||||
Sources: [`packages/jobs/jobs/src/types.ts:24`](../packages/jobs/jobs/src/types.ts)
|
||||
|
||||
| Property | Presence | Type |
|
||||
|---|---|---|
|
||||
| `executionId` | required | `string` |
|
||||
| `jobId` | required | `string` |
|
||||
| `kind` | required | `string` |
|
||||
| `label` | required | `string` |
|
||||
@@ -3808,7 +3807,7 @@ Sources: [`packages/jobs/jobs/src/types.ts:24`](../packages/jobs/jobs/src/types.
|
||||
|
||||
SHA-256: `750a2b129d67704df675c06823c20c80b65cbae89d5aaa914fd5a72e3df395b5`
|
||||
|
||||
Sources: [`packages/jobs/jobs/src/types.ts:249`](../packages/jobs/jobs/src/types.ts)
|
||||
Sources: [`packages/jobs/jobs/src/types.ts:247`](../packages/jobs/jobs/src/types.ts)
|
||||
|
||||
One of:
|
||||
|
||||
@@ -3816,7 +3815,7 @@ One of:
|
||||
- `"producer"`
|
||||
- `"teardown"`
|
||||
|
||||
<a id="persistence-type-sha256-9aaca69bf3c44a4486ee23a54aa9ec8e7189a935f818c8ed670de2352f3c12c9"></a>
|
||||
<a id="persistence-type-sha256-611d82d2dc50df706cf08a0fc04bc8eaf78083391fe6f10313de943e615dd2da"></a>
|
||||
|
||||
<a id="persistence-type-jobsettleddata"></a>
|
||||
|
||||
@@ -3824,15 +3823,14 @@ One of:
|
||||
|
||||
### `JobSettledData`
|
||||
|
||||
SHA-256: `9aaca69bf3c44a4486ee23a54aa9ec8e7189a935f818c8ed670de2352f3c12c9`
|
||||
SHA-256: `611d82d2dc50df706cf08a0fc04bc8eaf78083391fe6f10313de943e615dd2da`
|
||||
|
||||
Sources: [`packages/jobs/jobs/src/types.ts:42`](../packages/jobs/jobs/src/types.ts)
|
||||
Sources: [`packages/jobs/jobs/src/types.ts:40`](../packages/jobs/jobs/src/types.ts)
|
||||
|
||||
| Property | Presence | Type |
|
||||
|---|---|---|
|
||||
| `cause` | required | [`JobSettleCause`](#persistence-type-sha256-750a2b129d67704df675c06823c20c80b65cbae89d5aaa914fd5a72e3df395b5) |
|
||||
| `detail` | optional | `string` |
|
||||
| `executionId` | required | `string` |
|
||||
| `finishedAt` | required | `number` |
|
||||
| `jobId` | required | `string` |
|
||||
| `kind` | required | `string` |
|
||||
@@ -5462,8 +5460,6 @@ Array of `number`.
|
||||
|
||||
<a id="persistence-type-goalid"></a>
|
||||
|
||||
<a id="persistence-type-jobexecutionid"></a>
|
||||
|
||||
<a id="persistence-type-jobid"></a>
|
||||
|
||||
<a id="persistence-type-messagefeedbackversion"></a>
|
||||
@@ -5498,8 +5494,6 @@ Array of `number`.
|
||||
|
||||
<a id="persistence-type-packagesinteractionuser-approvalsrctypestsapprovalrequestid"></a>
|
||||
|
||||
<a id="persistence-type-packagesjobsjobssrcbrandtsjobexecutionid"></a>
|
||||
|
||||
<a id="persistence-type-packagesjobsjobssrcbrandtsjobid"></a>
|
||||
|
||||
<a id="persistence-type-packagesllmllm-retrysrcbrandtsretryid"></a>
|
||||
@@ -5554,7 +5548,7 @@ Array of `number`.
|
||||
|
||||
SHA-256: `4cd48f3e5108bd6ebedf7301c3638839b8b2b9db8dbd493974177fbdeb91a01b`
|
||||
|
||||
Sources: [`packages/api/session-controller/src/types.ts:398`](../packages/api/session-controller/src/types.ts) · [`packages/attachment/attachment/src/brand.ts:6`](../packages/attachment/attachment/src/brand.ts) · [`packages/compaction/compaction/src/brand.ts:4`](../packages/compaction/compaction/src/brand.ts) · [`packages/core/session/src/types.ts:20`](../packages/core/session/src/types.ts) · [`packages/experimental/agent-team/src/types.ts:20`](../packages/experimental/agent-team/src/types.ts) · [`packages/experimental/agent-team/src/types.ts:32`](../packages/experimental/agent-team/src/types.ts) · [`packages/experimental/agent-team/src/types.ts:8`](../packages/experimental/agent-team/src/types.ts) · [`packages/experimental/webhook/src/brand.ts:12`](../packages/experimental/webhook/src/brand.ts) · [`packages/experimental/webhook/src/brand.ts:6`](../packages/experimental/webhook/src/brand.ts) · [`packages/experimental/webhook/src/brand.ts:9`](../packages/experimental/webhook/src/brand.ts) · [`packages/feedback/message-feedback/src/types.ts:14`](../packages/feedback/message-feedback/src/types.ts) · [`packages/goal/goal/src/types.ts:17`](../packages/goal/goal/src/types.ts) · [`packages/interaction/commands/src/brand.ts:31`](../packages/interaction/commands/src/brand.ts) · [`packages/interaction/user-approval/src/types.ts:17`](../packages/interaction/user-approval/src/types.ts) · [`packages/jobs/jobs/src/brand.ts:19`](../packages/jobs/jobs/src/brand.ts) · [`packages/jobs/jobs/src/brand.ts:31`](../packages/jobs/jobs/src/brand.ts) · [`packages/llm/llm-retry/src/brand.ts:4`](../packages/llm/llm-retry/src/brand.ts) · [`packages/llm/llm/src/brand.ts:16`](../packages/llm/llm/src/brand.ts) · [`packages/llm/llm/src/brand.ts:31`](../packages/llm/llm/src/brand.ts) · [`packages/llm/llm/src/brand.ts:43`](../packages/llm/llm/src/brand.ts) · [`packages/llm/llm/src/brand.ts:67`](../packages/llm/llm/src/brand.ts) · [`packages/schedule/schedule/src/types.ts:14`](../packages/schedule/schedule/src/types.ts) · [`packages/session/session-title/src/index.ts:42`](../packages/session/session-title/src/index.ts) · [`packages/session/session-title/src/types.ts:17`](../packages/session/session-title/src/types.ts) · [`packages/workflow/workflow/src/types.ts:13`](../packages/workflow/workflow/src/types.ts)
|
||||
Sources: [`packages/api/session-controller/src/types.ts:398`](../packages/api/session-controller/src/types.ts) · [`packages/attachment/attachment/src/brand.ts:6`](../packages/attachment/attachment/src/brand.ts) · [`packages/compaction/compaction/src/brand.ts:4`](../packages/compaction/compaction/src/brand.ts) · [`packages/core/session/src/types.ts:20`](../packages/core/session/src/types.ts) · [`packages/experimental/agent-team/src/types.ts:20`](../packages/experimental/agent-team/src/types.ts) · [`packages/experimental/agent-team/src/types.ts:32`](../packages/experimental/agent-team/src/types.ts) · [`packages/experimental/agent-team/src/types.ts:8`](../packages/experimental/agent-team/src/types.ts) · [`packages/experimental/webhook/src/brand.ts:12`](../packages/experimental/webhook/src/brand.ts) · [`packages/experimental/webhook/src/brand.ts:6`](../packages/experimental/webhook/src/brand.ts) · [`packages/experimental/webhook/src/brand.ts:9`](../packages/experimental/webhook/src/brand.ts) · [`packages/feedback/message-feedback/src/types.ts:14`](../packages/feedback/message-feedback/src/types.ts) · [`packages/goal/goal/src/types.ts:17`](../packages/goal/goal/src/types.ts) · [`packages/interaction/commands/src/brand.ts:31`](../packages/interaction/commands/src/brand.ts) · [`packages/interaction/user-approval/src/types.ts:17`](../packages/interaction/user-approval/src/types.ts) · [`packages/jobs/jobs/src/brand.ts:19`](../packages/jobs/jobs/src/brand.ts) · [`packages/llm/llm-retry/src/brand.ts:4`](../packages/llm/llm-retry/src/brand.ts) · [`packages/llm/llm/src/brand.ts:16`](../packages/llm/llm/src/brand.ts) · [`packages/llm/llm/src/brand.ts:31`](../packages/llm/llm/src/brand.ts) · [`packages/llm/llm/src/brand.ts:43`](../packages/llm/llm/src/brand.ts) · [`packages/llm/llm/src/brand.ts:67`](../packages/llm/llm/src/brand.ts) · [`packages/schedule/schedule/src/types.ts:14`](../packages/schedule/schedule/src/types.ts) · [`packages/session/session-title/src/index.ts:42`](../packages/session/session-title/src/index.ts) · [`packages/session/session-title/src/types.ts:17`](../packages/session/session-title/src/types.ts) · [`packages/workflow/workflow/src/types.ts:13`](../packages/workflow/workflow/src/types.ts)
|
||||
|
||||
`string`
|
||||
|
||||
@@ -8307,33 +8301,33 @@ SHA-256: `b222069eea2d768065161c1147b1f2c78c1b54328c84b3586ae5c8f91b8ed35e`
|
||||
| `time` | required | `number` |
|
||||
| `type` | required | `"image/offload"` |
|
||||
|
||||
<a id="persistence-type-sha256-f05be3e066ef054a610f0825251f19ee3fcfc98908b5ceaa869ee143d7e91fa5"></a>
|
||||
<a id="persistence-type-sha256-f5520c1d261ca86ea03735799c311a0a9ede8babb588cf0a65422576d2ee0ad1"></a>
|
||||
|
||||
<a id="persistence-type-eventjobregistered"></a>
|
||||
|
||||
### `{ type: "job/registered" }`
|
||||
|
||||
SHA-256: `f05be3e066ef054a610f0825251f19ee3fcfc98908b5ceaa869ee143d7e91fa5`
|
||||
SHA-256: `f5520c1d261ca86ea03735799c311a0a9ede8babb588cf0a65422576d2ee0ad1`
|
||||
|
||||
| Property | Presence | Type |
|
||||
|---|---|---|
|
||||
| `data` | required | [`JobRegisteredData`](#persistence-type-sha256-c98cb9b944f83bf77ad098039861cbad71c3b8377e19c2bfccf72e1db16a6be2) |
|
||||
| `data` | required | [`JobRegisteredData`](#persistence-type-sha256-e3056e0dda3a379e895386e368c3ce42131460ea65e410e1d091cca98a347305) |
|
||||
| `ignorable` | optional | `true` |
|
||||
| `seq` | required | `number` |
|
||||
| `time` | required | `number` |
|
||||
| `type` | required | `"job/registered"` |
|
||||
|
||||
<a id="persistence-type-sha256-a103c03b3bc0ca7a7809d1eb2572dec08899f7a15a90072736777acd4e517648"></a>
|
||||
<a id="persistence-type-sha256-edd37b1e4974af648633014435572089b1e8fb71910468a0767a7e28fdd22abf"></a>
|
||||
|
||||
<a id="persistence-type-eventjobsettled"></a>
|
||||
|
||||
### `{ type: "job/settled" }`
|
||||
|
||||
SHA-256: `a103c03b3bc0ca7a7809d1eb2572dec08899f7a15a90072736777acd4e517648`
|
||||
SHA-256: `edd37b1e4974af648633014435572089b1e8fb71910468a0767a7e28fdd22abf`
|
||||
|
||||
| Property | Presence | Type |
|
||||
|---|---|---|
|
||||
| `data` | required | [`JobSettledData`](#persistence-type-sha256-9aaca69bf3c44a4486ee23a54aa9ec8e7189a935f818c8ed670de2352f3c12c9) |
|
||||
| `data` | required | [`JobSettledData`](#persistence-type-sha256-611d82d2dc50df706cf08a0fc04bc8eaf78083391fe6f10313de943e615dd2da) |
|
||||
| `ignorable` | optional | `true` |
|
||||
| `seq` | required | `number` |
|
||||
| `time` | required | `number` |
|
||||
|
||||
@@ -46,8 +46,8 @@
|
||||
| `event:hook/invoked` | event | `8a6e1ec9e8db346b0e02f027db73c07a94f067a26d40c1aef1abd09c47ce7ba0` | [`{ type: "hook/invoked" }`](#persistence-type-sha256-8a6e1ec9e8db346b0e02f027db73c07a94f067a26d40c1aef1abd09c47ce7ba0) |
|
||||
| `event:hook/result` | event | `e75916628f3f10c2d50658bd143052a46285fbf1a9a700ba54947614603d26b4` | [`{ type: "hook/result" }`](#persistence-type-sha256-e75916628f3f10c2d50658bd143052a46285fbf1a9a700ba54947614603d26b4) |
|
||||
| `event:image/offload` | event | `b222069eea2d768065161c1147b1f2c78c1b54328c84b3586ae5c8f91b8ed35e` | [`{ type: "image/offload" }`](#persistence-type-sha256-b222069eea2d768065161c1147b1f2c78c1b54328c84b3586ae5c8f91b8ed35e) |
|
||||
| `event:job/registered` | event | `f05be3e066ef054a610f0825251f19ee3fcfc98908b5ceaa869ee143d7e91fa5` | [`{ type: "job/registered" }`](#persistence-type-sha256-f05be3e066ef054a610f0825251f19ee3fcfc98908b5ceaa869ee143d7e91fa5) |
|
||||
| `event:job/settled` | event | `a103c03b3bc0ca7a7809d1eb2572dec08899f7a15a90072736777acd4e517648` | [`{ type: "job/settled" }`](#persistence-type-sha256-a103c03b3bc0ca7a7809d1eb2572dec08899f7a15a90072736777acd4e517648) |
|
||||
| `event:job/registered` | event | `f5520c1d261ca86ea03735799c311a0a9ede8babb588cf0a65422576d2ee0ad1` | [`{ type: "job/registered" }`](#persistence-type-sha256-f5520c1d261ca86ea03735799c311a0a9ede8babb588cf0a65422576d2ee0ad1) |
|
||||
| `event:job/settled` | event | `edd37b1e4974af648633014435572089b1e8fb71910468a0767a7e28fdd22abf` | [`{ type: "job/settled" }`](#persistence-type-sha256-edd37b1e4974af648633014435572089b1e8fb71910468a0767a7e28fdd22abf) |
|
||||
| `event:llm/retry` | event | `525254db03b1d1e6b74cf55aced52817568331ac1f96c0818728910b6692e336` | [`{ type: "llm/retry" }`](#persistence-type-sha256-525254db03b1d1e6b74cf55aced52817568331ac1f96c0818728910b6692e336) |
|
||||
| `event:llm/retry-started` | event | `48e5c9861f16ac07e78cb7b5ae9dabdf7bb85c58baed5a51b4ad275050ea58e3` | [`{ type: "llm/retry-started" }`](#persistence-type-sha256-48e5c9861f16ac07e78cb7b5ae9dabdf7bb85c58baed5a51b4ad275050ea58e3) |
|
||||
| `event:model/selection` | event | `35203ba7ad5ef6f97d556b85df20ae98f04f09c65748cecdf8eefdb8b6405ffc` | [`{ type: "model/selection" }`](#persistence-type-sha256-35203ba7ad5ef6f97d556b85df20ae98f04f09c65748cecdf8eefdb8b6405ffc) |
|
||||
@@ -645,7 +645,7 @@ export type SessionEvent<T extends SessionEventType = SessionEventType> = {
|
||||
'job/registered': JobRegisteredData
|
||||
```
|
||||
|
||||
来源:[`packages/jobs/jobs/src/types.ts:61`](../packages/jobs/jobs/src/types.ts)
|
||||
来源:[`packages/jobs/jobs/src/types.ts:59`](../packages/jobs/jobs/src/types.ts)
|
||||
|
||||
<a id="jobsettled--log-only"></a>
|
||||
|
||||
@@ -653,13 +653,13 @@ export type SessionEvent<T extends SessionEventType = SessionEventType> = {
|
||||
|
||||
```ts persistence-catalog
|
||||
/**
|
||||
* Log-only terminal record, paired by executionId rather than a reusable jobId.
|
||||
* Log-only terminal record, paired by the same unique jobId used for live control.
|
||||
* @param data - Registration facts plus settlement time, outcome, and producer acknowledgement.
|
||||
*/
|
||||
'job/settled': JobSettledData
|
||||
```
|
||||
|
||||
来源:[`packages/jobs/jobs/src/types.ts:66`](../packages/jobs/jobs/src/types.ts)
|
||||
来源:[`packages/jobs/jobs/src/types.ts:64`](../packages/jobs/jobs/src/types.ts)
|
||||
|
||||
### `llm/*`
|
||||
|
||||
@@ -3778,7 +3778,7 @@ SHA-256: `96f4a9c81fc0f940ef71528a8cc66731ae4cf1f40d79680c5fe3c39f96d7723c`
|
||||
- `"next-step"`
|
||||
- `"next-turn"`
|
||||
|
||||
<a id="persistence-type-sha256-c98cb9b944f83bf77ad098039861cbad71c3b8377e19c2bfccf72e1db16a6be2"></a>
|
||||
<a id="persistence-type-sha256-e3056e0dda3a379e895386e368c3ce42131460ea65e410e1d091cca98a347305"></a>
|
||||
|
||||
<a id="persistence-type-jobregistereddata"></a>
|
||||
|
||||
@@ -3786,13 +3786,12 @@ SHA-256: `96f4a9c81fc0f940ef71528a8cc66731ae4cf1f40d79680c5fe3c39f96d7723c`
|
||||
|
||||
### `JobRegisteredData`
|
||||
|
||||
SHA-256: `c98cb9b944f83bf77ad098039861cbad71c3b8377e19c2bfccf72e1db16a6be2`
|
||||
SHA-256: `e3056e0dda3a379e895386e368c3ce42131460ea65e410e1d091cca98a347305`
|
||||
|
||||
来源:[`packages/jobs/jobs/src/types.ts:24`](../packages/jobs/jobs/src/types.ts)
|
||||
|
||||
| 属性 | 存在性 | 类型 |
|
||||
|---|---|---|
|
||||
| `executionId` | 必需 | `string` |
|
||||
| `jobId` | 必需 | `string` |
|
||||
| `kind` | 必需 | `string` |
|
||||
| `label` | 必需 | `string` |
|
||||
@@ -3810,7 +3809,7 @@ SHA-256: `c98cb9b944f83bf77ad098039861cbad71c3b8377e19c2bfccf72e1db16a6be2`
|
||||
|
||||
SHA-256: `750a2b129d67704df675c06823c20c80b65cbae89d5aaa914fd5a72e3df395b5`
|
||||
|
||||
来源:[`packages/jobs/jobs/src/types.ts:249`](../packages/jobs/jobs/src/types.ts)
|
||||
来源:[`packages/jobs/jobs/src/types.ts:247`](../packages/jobs/jobs/src/types.ts)
|
||||
|
||||
以下类型之一:
|
||||
|
||||
@@ -3818,7 +3817,7 @@ SHA-256: `750a2b129d67704df675c06823c20c80b65cbae89d5aaa914fd5a72e3df395b5`
|
||||
- `"producer"`
|
||||
- `"teardown"`
|
||||
|
||||
<a id="persistence-type-sha256-9aaca69bf3c44a4486ee23a54aa9ec8e7189a935f818c8ed670de2352f3c12c9"></a>
|
||||
<a id="persistence-type-sha256-611d82d2dc50df706cf08a0fc04bc8eaf78083391fe6f10313de943e615dd2da"></a>
|
||||
|
||||
<a id="persistence-type-jobsettleddata"></a>
|
||||
|
||||
@@ -3826,15 +3825,14 @@ SHA-256: `750a2b129d67704df675c06823c20c80b65cbae89d5aaa914fd5a72e3df395b5`
|
||||
|
||||
### `JobSettledData`
|
||||
|
||||
SHA-256: `9aaca69bf3c44a4486ee23a54aa9ec8e7189a935f818c8ed670de2352f3c12c9`
|
||||
SHA-256: `611d82d2dc50df706cf08a0fc04bc8eaf78083391fe6f10313de943e615dd2da`
|
||||
|
||||
来源:[`packages/jobs/jobs/src/types.ts:42`](../packages/jobs/jobs/src/types.ts)
|
||||
来源:[`packages/jobs/jobs/src/types.ts:40`](../packages/jobs/jobs/src/types.ts)
|
||||
|
||||
| 属性 | 存在性 | 类型 |
|
||||
|---|---|---|
|
||||
| `cause` | 必需 | [`JobSettleCause`](#persistence-type-sha256-750a2b129d67704df675c06823c20c80b65cbae89d5aaa914fd5a72e3df395b5) |
|
||||
| `detail` | 可选 | `string` |
|
||||
| `executionId` | 必需 | `string` |
|
||||
| `finishedAt` | 必需 | `number` |
|
||||
| `jobId` | 必需 | `string` |
|
||||
| `kind` | 必需 | `string` |
|
||||
@@ -5464,8 +5462,6 @@ SHA-256: `5d03ba38734809bcbd2a55221bd938b3b3fc34b49bb686c6113e9e4931e3aa78`
|
||||
|
||||
<a id="persistence-type-goalid"></a>
|
||||
|
||||
<a id="persistence-type-jobexecutionid"></a>
|
||||
|
||||
<a id="persistence-type-jobid"></a>
|
||||
|
||||
<a id="persistence-type-messagefeedbackversion"></a>
|
||||
@@ -5500,8 +5496,6 @@ SHA-256: `5d03ba38734809bcbd2a55221bd938b3b3fc34b49bb686c6113e9e4931e3aa78`
|
||||
|
||||
<a id="persistence-type-packagesinteractionuser-approvalsrctypestsapprovalrequestid"></a>
|
||||
|
||||
<a id="persistence-type-packagesjobsjobssrcbrandtsjobexecutionid"></a>
|
||||
|
||||
<a id="persistence-type-packagesjobsjobssrcbrandtsjobid"></a>
|
||||
|
||||
<a id="persistence-type-packagesllmllm-retrysrcbrandtsretryid"></a>
|
||||
@@ -5556,7 +5550,7 @@ SHA-256: `5d03ba38734809bcbd2a55221bd938b3b3fc34b49bb686c6113e9e4931e3aa78`
|
||||
|
||||
SHA-256: `4cd48f3e5108bd6ebedf7301c3638839b8b2b9db8dbd493974177fbdeb91a01b`
|
||||
|
||||
来源:[`packages/api/session-controller/src/types.ts:398`](../packages/api/session-controller/src/types.ts) · [`packages/attachment/attachment/src/brand.ts:6`](../packages/attachment/attachment/src/brand.ts) · [`packages/compaction/compaction/src/brand.ts:4`](../packages/compaction/compaction/src/brand.ts) · [`packages/core/session/src/types.ts:20`](../packages/core/session/src/types.ts) · [`packages/experimental/agent-team/src/types.ts:20`](../packages/experimental/agent-team/src/types.ts) · [`packages/experimental/agent-team/src/types.ts:32`](../packages/experimental/agent-team/src/types.ts) · [`packages/experimental/agent-team/src/types.ts:8`](../packages/experimental/agent-team/src/types.ts) · [`packages/experimental/webhook/src/brand.ts:12`](../packages/experimental/webhook/src/brand.ts) · [`packages/experimental/webhook/src/brand.ts:6`](../packages/experimental/webhook/src/brand.ts) · [`packages/experimental/webhook/src/brand.ts:9`](../packages/experimental/webhook/src/brand.ts) · [`packages/feedback/message-feedback/src/types.ts:14`](../packages/feedback/message-feedback/src/types.ts) · [`packages/goal/goal/src/types.ts:17`](../packages/goal/goal/src/types.ts) · [`packages/interaction/commands/src/brand.ts:31`](../packages/interaction/commands/src/brand.ts) · [`packages/interaction/user-approval/src/types.ts:17`](../packages/interaction/user-approval/src/types.ts) · [`packages/jobs/jobs/src/brand.ts:19`](../packages/jobs/jobs/src/brand.ts) · [`packages/jobs/jobs/src/brand.ts:31`](../packages/jobs/jobs/src/brand.ts) · [`packages/llm/llm-retry/src/brand.ts:4`](../packages/llm/llm-retry/src/brand.ts) · [`packages/llm/llm/src/brand.ts:16`](../packages/llm/llm/src/brand.ts) · [`packages/llm/llm/src/brand.ts:31`](../packages/llm/llm/src/brand.ts) · [`packages/llm/llm/src/brand.ts:43`](../packages/llm/llm/src/brand.ts) · [`packages/llm/llm/src/brand.ts:67`](../packages/llm/llm/src/brand.ts) · [`packages/schedule/schedule/src/types.ts:14`](../packages/schedule/schedule/src/types.ts) · [`packages/session/session-title/src/index.ts:42`](../packages/session/session-title/src/index.ts) · [`packages/session/session-title/src/types.ts:17`](../packages/session/session-title/src/types.ts) · [`packages/workflow/workflow/src/types.ts:13`](../packages/workflow/workflow/src/types.ts)
|
||||
来源:[`packages/api/session-controller/src/types.ts:398`](../packages/api/session-controller/src/types.ts) · [`packages/attachment/attachment/src/brand.ts:6`](../packages/attachment/attachment/src/brand.ts) · [`packages/compaction/compaction/src/brand.ts:4`](../packages/compaction/compaction/src/brand.ts) · [`packages/core/session/src/types.ts:20`](../packages/core/session/src/types.ts) · [`packages/experimental/agent-team/src/types.ts:20`](../packages/experimental/agent-team/src/types.ts) · [`packages/experimental/agent-team/src/types.ts:32`](../packages/experimental/agent-team/src/types.ts) · [`packages/experimental/agent-team/src/types.ts:8`](../packages/experimental/agent-team/src/types.ts) · [`packages/experimental/webhook/src/brand.ts:12`](../packages/experimental/webhook/src/brand.ts) · [`packages/experimental/webhook/src/brand.ts:6`](../packages/experimental/webhook/src/brand.ts) · [`packages/experimental/webhook/src/brand.ts:9`](../packages/experimental/webhook/src/brand.ts) · [`packages/feedback/message-feedback/src/types.ts:14`](../packages/feedback/message-feedback/src/types.ts) · [`packages/goal/goal/src/types.ts:17`](../packages/goal/goal/src/types.ts) · [`packages/interaction/commands/src/brand.ts:31`](../packages/interaction/commands/src/brand.ts) · [`packages/interaction/user-approval/src/types.ts:17`](../packages/interaction/user-approval/src/types.ts) · [`packages/jobs/jobs/src/brand.ts:19`](../packages/jobs/jobs/src/brand.ts) · [`packages/llm/llm-retry/src/brand.ts:4`](../packages/llm/llm-retry/src/brand.ts) · [`packages/llm/llm/src/brand.ts:16`](../packages/llm/llm/src/brand.ts) · [`packages/llm/llm/src/brand.ts:31`](../packages/llm/llm/src/brand.ts) · [`packages/llm/llm/src/brand.ts:43`](../packages/llm/llm/src/brand.ts) · [`packages/llm/llm/src/brand.ts:67`](../packages/llm/llm/src/brand.ts) · [`packages/schedule/schedule/src/types.ts:14`](../packages/schedule/schedule/src/types.ts) · [`packages/session/session-title/src/index.ts:42`](../packages/session/session-title/src/index.ts) · [`packages/session/session-title/src/types.ts:17`](../packages/session/session-title/src/types.ts) · [`packages/workflow/workflow/src/types.ts:13`](../packages/workflow/workflow/src/types.ts)
|
||||
|
||||
`string`
|
||||
|
||||
@@ -8309,33 +8303,33 @@ SHA-256: `b222069eea2d768065161c1147b1f2c78c1b54328c84b3586ae5c8f91b8ed35e`
|
||||
| `time` | 必需 | `number` |
|
||||
| `type` | 必需 | `"image/offload"` |
|
||||
|
||||
<a id="persistence-type-sha256-f05be3e066ef054a610f0825251f19ee3fcfc98908b5ceaa869ee143d7e91fa5"></a>
|
||||
<a id="persistence-type-sha256-f5520c1d261ca86ea03735799c311a0a9ede8babb588cf0a65422576d2ee0ad1"></a>
|
||||
|
||||
<a id="persistence-type-eventjobregistered"></a>
|
||||
|
||||
### `{ type: "job/registered" }`
|
||||
|
||||
SHA-256: `f05be3e066ef054a610f0825251f19ee3fcfc98908b5ceaa869ee143d7e91fa5`
|
||||
SHA-256: `f5520c1d261ca86ea03735799c311a0a9ede8babb588cf0a65422576d2ee0ad1`
|
||||
|
||||
| 属性 | 存在性 | 类型 |
|
||||
|---|---|---|
|
||||
| `data` | 必需 | [`JobRegisteredData`](#persistence-type-sha256-c98cb9b944f83bf77ad098039861cbad71c3b8377e19c2bfccf72e1db16a6be2) |
|
||||
| `data` | 必需 | [`JobRegisteredData`](#persistence-type-sha256-e3056e0dda3a379e895386e368c3ce42131460ea65e410e1d091cca98a347305) |
|
||||
| `ignorable` | 可选 | `true` |
|
||||
| `seq` | 必需 | `number` |
|
||||
| `time` | 必需 | `number` |
|
||||
| `type` | 必需 | `"job/registered"` |
|
||||
|
||||
<a id="persistence-type-sha256-a103c03b3bc0ca7a7809d1eb2572dec08899f7a15a90072736777acd4e517648"></a>
|
||||
<a id="persistence-type-sha256-edd37b1e4974af648633014435572089b1e8fb71910468a0767a7e28fdd22abf"></a>
|
||||
|
||||
<a id="persistence-type-eventjobsettled"></a>
|
||||
|
||||
### `{ type: "job/settled" }`
|
||||
|
||||
SHA-256: `a103c03b3bc0ca7a7809d1eb2572dec08899f7a15a90072736777acd4e517648`
|
||||
SHA-256: `edd37b1e4974af648633014435572089b1e8fb71910468a0767a7e28fdd22abf`
|
||||
|
||||
| 属性 | 存在性 | 类型 |
|
||||
|---|---|---|
|
||||
| `data` | 必需 | [`JobSettledData`](#persistence-type-sha256-9aaca69bf3c44a4486ee23a54aa9ec8e7189a935f818c8ed670de2352f3c12c9) |
|
||||
| `data` | 必需 | [`JobSettledData`](#persistence-type-sha256-611d82d2dc50df706cf08a0fc04bc8eaf78083391fe6f10313de943e615dd2da) |
|
||||
| `ignorable` | 可选 | `true` |
|
||||
| `seq` | 必需 | `number` |
|
||||
| `time` | 必需 | `number` |
|
||||
|
||||
@@ -9,8 +9,8 @@
|
||||
en: 9be8dc8cb5ce646a
|
||||
zh: 6ce0040813c36e92
|
||||
/2026-09-30-job-session-lifecycle/summary:
|
||||
en: 0c13281672f78e6f
|
||||
zh: 4caef968f21ba1d5
|
||||
en: 7d98020db3395e2e
|
||||
zh: abe692ef42327e49
|
||||
/2026-09-30-job-session-lifecycle/table-of-contents:
|
||||
en: 6cf314a069ceb451
|
||||
zh: 9adff678e9ecab3b
|
||||
@@ -18,11 +18,11 @@
|
||||
en: 6907136f28e48c90
|
||||
zh: 02f8298485f3d8e7
|
||||
/2026-09-30-job-session-lifecycle/compatibility:
|
||||
en: ffa5be6797f3f89f
|
||||
zh: d1b2e288aa6c45d3
|
||||
en: 546fe003ad5293b6
|
||||
zh: 86d78c9704a03718
|
||||
/2026-09-30-job-session-lifecycle/verification:
|
||||
en: 934e86bda5522670
|
||||
zh: 03a773c06e4a7b52
|
||||
en: 2dd22e091ab487c1
|
||||
zh: fcae964bdfa6f32a
|
||||
/2026-09-30-job-session-lifecycle/dev-note:
|
||||
en: b75c8458928b72b9
|
||||
zh: 003ba4e17b4b5269
|
||||
@@ -9,7 +9,7 @@ English | [中文](2026-09-30-job-session-lifecycle.zh.md)
|
||||
|
||||
## Summary
|
||||
|
||||
Records owned job registration and settlement as log-only Session events with execution identity, original timestamps, creating tool calls, and producer completion reports.
|
||||
Records owned job registration and settlement with one unique JobId, original timestamps, creating tool calls, and producer completion reports.
|
||||
|
||||
## Table of Contents
|
||||
|
||||
@@ -28,23 +28,23 @@ baseline: false
|
||||
changes:
|
||||
- root: "event:job/registered"
|
||||
previous: null
|
||||
after: "f05be3e066ef054a610f0825251f19ee3fcfc98908b5ceaa869ee143d7e91fa5"
|
||||
after: "f5520c1d261ca86ea03735799c311a0a9ede8babb588cf0a65422576d2ee0ad1"
|
||||
decision: same-version
|
||||
- root: "event:job/settled"
|
||||
previous: null
|
||||
after: "a103c03b3bc0ca7a7809d1eb2572dec08899f7a15a90072736777acd4e517648"
|
||||
after: "edd37b1e4974af648633014435572089b1e8fb71910468a0767a7e28fdd22abf"
|
||||
decision: same-version
|
||||
```
|
||||
|
||||
<a id="compatibility"></a>
|
||||
## Compatibility
|
||||
|
||||
Adds event kinds without changing existing payloads or the Session envelope. Historical sessions remain valid and receive no fabricated job history. Older builds reject unknown required event types. producerReported describes fulfillment of the producer completion promise, not verified resource release.
|
||||
Adds required job event kinds without changing the Session envelope. New JobIds use a kind-prefixed UUID for both live controls and durable pairing; historical sessions receive no fabricated job history. Older builds reject unknown required events. producerReported describes completion-promise fulfillment, not verified resource release.
|
||||
|
||||
<a id="verification"></a>
|
||||
## Verification
|
||||
|
||||
Focused registry, normalizer, shutdown, and headless regressions cover causal publication order, distinct execution identities, and cleanup within the existing exit bound. Real CLI snapshot and SDK replay verify registration/settlement persistence and notifications.
|
||||
Registry, snapshot-normalizer, shutdown, and headless tests cover causal order, unique JobIds, and ordered cleanup for normal and signal exit. CLI and SDK snapshots verify durable registration/settlement and notices.
|
||||
|
||||
<a id="dev-note"></a>
|
||||
## Dev Note
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
"kind": "event",
|
||||
"event": "job/registered",
|
||||
"surface": false,
|
||||
"digest": "f05be3e066ef054a610f0825251f19ee3fcfc98908b5ceaa869ee143d7e91fa5",
|
||||
"digest": "f5520c1d261ca86ea03735799c311a0a9ede8babb588cf0a65422576d2ee0ad1",
|
||||
"schema": {
|
||||
"root": 0,
|
||||
"nodes": [
|
||||
@@ -44,11 +44,6 @@
|
||||
{
|
||||
"kind": "object",
|
||||
"properties": [
|
||||
{
|
||||
"name": "executionId",
|
||||
"type": 2,
|
||||
"optional": false
|
||||
},
|
||||
{
|
||||
"name": "jobId",
|
||||
"type": 2,
|
||||
@@ -122,7 +117,7 @@
|
||||
"kind": "event",
|
||||
"event": "job/settled",
|
||||
"surface": false,
|
||||
"digest": "a103c03b3bc0ca7a7809d1eb2572dec08899f7a15a90072736777acd4e517648",
|
||||
"digest": "edd37b1e4974af648633014435572089b1e8fb71910468a0767a7e28fdd22abf",
|
||||
"schema": {
|
||||
"root": 0,
|
||||
"nodes": [
|
||||
@@ -170,11 +165,6 @@
|
||||
"type": 6,
|
||||
"optional": true
|
||||
},
|
||||
{
|
||||
"name": "executionId",
|
||||
"type": 6,
|
||||
"optional": false
|
||||
},
|
||||
{
|
||||
"name": "finishedAt",
|
||||
"type": 7,
|
||||
|
||||
@@ -9,7 +9,7 @@ kind: persistence-change
|
||||
|
||||
## 概述
|
||||
|
||||
将有所有者 job 的注册与结算记录为仅日志 Session 事件,包含执行标识、原始时间戳、创建工具调用及生产方完成报告。
|
||||
以唯一 JobId 将有所有者 job 的注册与结算记录为 Session 事件,包含原始时间戳、创建工具调用和生产方完成报告。
|
||||
|
||||
## 目录
|
||||
|
||||
@@ -28,23 +28,23 @@ baseline: false
|
||||
changes:
|
||||
- root: "event:job/registered"
|
||||
previous: null
|
||||
after: "f05be3e066ef054a610f0825251f19ee3fcfc98908b5ceaa869ee143d7e91fa5"
|
||||
after: "f5520c1d261ca86ea03735799c311a0a9ede8babb588cf0a65422576d2ee0ad1"
|
||||
decision: same-version
|
||||
- root: "event:job/settled"
|
||||
previous: null
|
||||
after: "a103c03b3bc0ca7a7809d1eb2572dec08899f7a15a90072736777acd4e517648"
|
||||
after: "edd37b1e4974af648633014435572089b1e8fb71910468a0767a7e28fdd22abf"
|
||||
decision: same-version
|
||||
```
|
||||
|
||||
<a id="compatibility"></a>
|
||||
## 兼容性
|
||||
|
||||
增加事件类型,不修改现有负载或 Session 封装。历史会话仍有效,不补造 job 历史。旧构建会拒绝未知的必需事件类型。producerReported 描述生产方完成 promise 的兑现情况,不确认资源已释放。
|
||||
增加必需的 job 事件类型,不改变 Session 封装。新 JobId 用带 kind 前缀的 UUID 同时进行运行期控制与持久化配对;不为历史会话补造 job 事件。旧构建拒绝未知的必需事件。producerReported 表示完成 promise 兑现,不确认资源已释放。
|
||||
|
||||
<a id="verification"></a>
|
||||
## 验证
|
||||
|
||||
定向注册表、归一化、关停与 headless 回归覆盖因果发布顺序、不同执行标识,以及已有退出时限内的清理。真实 CLI 快照和 SDK 回放验证注册/结算持久化与通知。
|
||||
注册表、快照归一化、关停与 headless 测试覆盖因果顺序、唯一 JobId,以及正常退出和信号退出的有序清理。CLI 和 SDK 快照验证持久化注册、结算和通知。
|
||||
|
||||
<a id="dev-note"></a>
|
||||
## 开发备注
|
||||
|
||||
Generated
+31
-43
@@ -10344,7 +10344,7 @@
|
||||
"kind": "event",
|
||||
"event": "job/registered",
|
||||
"surface": false,
|
||||
"digest": "f05be3e066ef054a610f0825251f19ee3fcfc98908b5ceaa869ee143d7e91fa5",
|
||||
"digest": "f5520c1d261ca86ea03735799c311a0a9ede8babb588cf0a65422576d2ee0ad1",
|
||||
"schema": {
|
||||
"root": 0,
|
||||
"nodes": [
|
||||
@@ -10382,11 +10382,6 @@
|
||||
{
|
||||
"kind": "object",
|
||||
"properties": [
|
||||
{
|
||||
"name": "executionId",
|
||||
"type": 2,
|
||||
"optional": false
|
||||
},
|
||||
{
|
||||
"name": "jobId",
|
||||
"type": 2,
|
||||
@@ -10460,7 +10455,7 @@
|
||||
"kind": "event",
|
||||
"event": "job/settled",
|
||||
"surface": false,
|
||||
"digest": "a103c03b3bc0ca7a7809d1eb2572dec08899f7a15a90072736777acd4e517648",
|
||||
"digest": "edd37b1e4974af648633014435572089b1e8fb71910468a0767a7e28fdd22abf",
|
||||
"schema": {
|
||||
"root": 0,
|
||||
"nodes": [
|
||||
@@ -10508,11 +10503,6 @@
|
||||
"type": 6,
|
||||
"optional": true
|
||||
},
|
||||
{
|
||||
"name": "executionId",
|
||||
"type": 6,
|
||||
"optional": false
|
||||
},
|
||||
{
|
||||
"name": "finishedAt",
|
||||
"type": 7,
|
||||
@@ -23446,7 +23436,6 @@
|
||||
"packages/goal/goal/src/types.ts#GoalId",
|
||||
"packages/interaction/commands/src/brand.ts#CommandId",
|
||||
"packages/interaction/user-approval/src/types.ts#ApprovalRequestId",
|
||||
"packages/jobs/jobs/src/brand.ts#JobExecutionId",
|
||||
"packages/jobs/jobs/src/brand.ts#JobId",
|
||||
"packages/llm/llm-retry/src/brand.ts#RetryId",
|
||||
"packages/llm/llm/src/brand.ts#MessageId",
|
||||
@@ -23474,7 +23463,6 @@
|
||||
"packages/interaction/commands/src/brand.ts:31",
|
||||
"packages/interaction/user-approval/src/types.ts:17",
|
||||
"packages/jobs/jobs/src/brand.ts:19",
|
||||
"packages/jobs/jobs/src/brand.ts:31",
|
||||
"packages/llm/llm-retry/src/brand.ts:4",
|
||||
"packages/llm/llm/src/brand.ts:16",
|
||||
"packages/llm/llm/src/brand.ts:31",
|
||||
@@ -23801,6 +23789,15 @@
|
||||
"names": [],
|
||||
"sources": []
|
||||
},
|
||||
{
|
||||
"digest": "611d82d2dc50df706cf08a0fc04bc8eaf78083391fe6f10313de943e615dd2da",
|
||||
"names": [
|
||||
"packages/jobs/jobs/src/types.ts#JobSettledData"
|
||||
],
|
||||
"sources": [
|
||||
"packages/jobs/jobs/src/types.ts:40"
|
||||
]
|
||||
},
|
||||
{
|
||||
"digest": "616f93a5047de668e1c30d57b1786abd76beeb4123599637ec1dc5290320ede5",
|
||||
"names": [],
|
||||
@@ -24071,7 +24068,7 @@
|
||||
"packages/jobs/jobs/src/types.ts#JobSettleCause"
|
||||
],
|
||||
"sources": [
|
||||
"packages/jobs/jobs/src/types.ts:249"
|
||||
"packages/jobs/jobs/src/types.ts:247"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -24634,15 +24631,6 @@
|
||||
"packages/core/session/src/types.ts:240"
|
||||
]
|
||||
},
|
||||
{
|
||||
"digest": "9aaca69bf3c44a4486ee23a54aa9ec8e7189a935f818c8ed670de2352f3c12c9",
|
||||
"names": [
|
||||
"packages/jobs/jobs/src/types.ts#JobSettledData"
|
||||
],
|
||||
"sources": [
|
||||
"packages/jobs/jobs/src/types.ts:42"
|
||||
]
|
||||
},
|
||||
{
|
||||
"digest": "9ac52f0f84fec2eb9f71c2937e497d759db5ca0da4836d93a679d113684141f2",
|
||||
"names": [],
|
||||
@@ -24713,11 +24701,6 @@
|
||||
"names": [],
|
||||
"sources": []
|
||||
},
|
||||
{
|
||||
"digest": "a103c03b3bc0ca7a7809d1eb2572dec08899f7a15a90072736777acd4e517648",
|
||||
"names": [],
|
||||
"sources": []
|
||||
},
|
||||
{
|
||||
"digest": "a10c17474eaf2ddab7095a099e0fe3d046fc18e56c3e344fc8894c05ff9ef97b",
|
||||
"names": [],
|
||||
@@ -25352,15 +25335,6 @@
|
||||
"names": [],
|
||||
"sources": []
|
||||
},
|
||||
{
|
||||
"digest": "c98cb9b944f83bf77ad098039861cbad71c3b8377e19c2bfccf72e1db16a6be2",
|
||||
"names": [
|
||||
"packages/jobs/jobs/src/types.ts#JobRegisteredData"
|
||||
],
|
||||
"sources": [
|
||||
"packages/jobs/jobs/src/types.ts:24"
|
||||
]
|
||||
},
|
||||
{
|
||||
"digest": "c99e20441bf15341e9db54385e558e5c03d21dff2eb1ac08ccbf47c2d8747954",
|
||||
"names": [],
|
||||
@@ -25626,6 +25600,15 @@
|
||||
"names": [],
|
||||
"sources": []
|
||||
},
|
||||
{
|
||||
"digest": "e3056e0dda3a379e895386e368c3ce42131460ea65e410e1d091cca98a347305",
|
||||
"names": [
|
||||
"packages/jobs/jobs/src/types.ts#JobRegisteredData"
|
||||
],
|
||||
"sources": [
|
||||
"packages/jobs/jobs/src/types.ts:24"
|
||||
]
|
||||
},
|
||||
{
|
||||
"digest": "e308ccf867a5398e316e0af8cb6ce238a8d33a63b9b384c8250a686786285f72",
|
||||
"names": [],
|
||||
@@ -25767,6 +25750,11 @@
|
||||
"names": [],
|
||||
"sources": []
|
||||
},
|
||||
{
|
||||
"digest": "edd37b1e4974af648633014435572089b1e8fb71910468a0767a7e28fdd22abf",
|
||||
"names": [],
|
||||
"sources": []
|
||||
},
|
||||
{
|
||||
"digest": "ee577a0cc18664c9daee50e9455153321155604f8f1817362f8f4a4b6fb9e1ad",
|
||||
"names": [],
|
||||
@@ -25804,11 +25792,6 @@
|
||||
"names": [],
|
||||
"sources": []
|
||||
},
|
||||
{
|
||||
"digest": "f05be3e066ef054a610f0825251f19ee3fcfc98908b5ceaa869ee143d7e91fa5",
|
||||
"names": [],
|
||||
"sources": []
|
||||
},
|
||||
{
|
||||
"digest": "f05fb85e715af4ab4de0b6ba111166dd0435bd8bb74a0fa0f32b9d174113fc7c",
|
||||
"names": [],
|
||||
@@ -25886,6 +25869,11 @@
|
||||
"names": [],
|
||||
"sources": []
|
||||
},
|
||||
{
|
||||
"digest": "f5520c1d261ca86ea03735799c311a0a9ede8babb588cf0a65422576d2ee0ad1",
|
||||
"names": [],
|
||||
"sources": []
|
||||
},
|
||||
{
|
||||
"digest": "f56c5b59ca0161310c338d31dd16cf14287ccd602ff4181058df178c1c286d7f",
|
||||
"names": [
|
||||
|
||||
@@ -6,11 +6,11 @@
|
||||
en: dd515d8548864a91
|
||||
zh: 16a117d39989a373
|
||||
/background-job-runtime/ids-and-status:
|
||||
en: 21cd01e194bd3a83
|
||||
zh: 33cf767ac89e3406
|
||||
en: 3cb96fdb6ae34bd2
|
||||
zh: 7c85929b455f9431
|
||||
/background-job-runtime/session-lifecycle-records:
|
||||
en: 763d819a047751a0
|
||||
zh: 8204f81e8f4a686f
|
||||
en: 1b6b65e4c2983518
|
||||
zh: 12bd4c7158d5e55e
|
||||
/background-job-runtime/producer-contract:
|
||||
en: af0be338a0ba5e60
|
||||
zh: fc0eefb8c31f3a98
|
||||
|
||||
@@ -6,7 +6,7 @@ Types shared by long-running producers, `ctx.jobs`, and job controls. The [seam
|
||||
|
||||
## Ids and status
|
||||
|
||||
`JobId` is a [branded id](core.md#branded-ids) generated as `<kind>-N`. Access control relies on owner authorization, not id secrecy. `JobKind` derives from a merge-extensible map; the registry treats kinds as opaque id namespaces.
|
||||
`JobId` is a [branded id](core.md#branded-ids) generated as `<kind>-<uuid>`. Access control relies on owner authorization, not id secrecy. `JobKind` derives from a merge-extensible map; the registry treats kinds as opaque id namespaces.
|
||||
|
||||
```ts type-equiv
|
||||
/**
|
||||
@@ -24,16 +24,14 @@ interface JobKindMap {
|
||||
|
||||
## Session lifecycle records
|
||||
|
||||
Owned jobs publish `job/registered` and `job/settled` in their original owner Session. `executionId` identifies one execution across registry restarts and copied history; `jobId` remains the process-local control id. The SDK forwards these log-only records through `session.event`, independently of output reads. Timestamps describe registry commits, not CPU time or exact OS process boundaries; a missing settlement remains incomplete. `producerReported` means the producer fulfilled `done` with a terminal outcome; it is false for forced teardown or rejection and does not independently certify resource release.
|
||||
Owned jobs publish `job/registered` and `job/settled` in their original owner Session. The globally unique `jobId` pairs those records and controls the live job. The SDK forwards these log-only records through `session.event`, independently of output reads. Timestamps describe registry commits, not CPU time or exact OS process boundaries; a missing settlement remains incomplete. `producerReported` means the producer fulfilled `done` with a terminal outcome; it is false for forced teardown or rejection and does not independently certify resource release.
|
||||
|
||||
`job/settled` may follow `turn/end`. An SDK run result covers only its own observation window; consumers needing later settlements retain a Session subscription or read persisted history. Turn completion does not imply that every owned job stopped.
|
||||
|
||||
```ts type-equiv
|
||||
/** Durable identity and registration time of one owned job execution. */
|
||||
interface JobRegisteredData {
|
||||
/** UUID unique to this execution, even when the registry reuses a display id. */
|
||||
readonly executionId: JobExecutionId
|
||||
/** Runtime-local id accepted by job controls. */
|
||||
/** Unique execution id accepted by job controls and retained in Session history. */
|
||||
readonly jobId: JobId
|
||||
/** Original owner; copied history does not transfer execution ownership. */
|
||||
readonly owner: SessionId
|
||||
@@ -132,7 +130,7 @@ interface JobSpec {
|
||||
* throwing, so a producer's trailing flush cannot break its own teardown path.
|
||||
*/
|
||||
interface JobHandle {
|
||||
/** The registry-issued id (`<kind>-N`). */
|
||||
/** The registry-issued id (`<kind>-<uuid>`). */
|
||||
readonly id: JobId
|
||||
/**
|
||||
* Append one chunk to the output ring. Offsets advance by the chunk's UTF-8
|
||||
@@ -275,7 +273,7 @@ interface JobOutputRead {
|
||||
* stream all consume this one shape.
|
||||
*/
|
||||
interface JobView {
|
||||
/** The registry-issued id (`<kind>-N`). */
|
||||
/** The registry-issued id (`<kind>-<uuid>`). */
|
||||
readonly id: JobId
|
||||
/**
|
||||
* The producer kind the job was registered with: a Host-registered
|
||||
@@ -461,7 +459,7 @@ Implementations must honor these semantics:
|
||||
* rejection leaves no job id or execution resource. A throwing starter
|
||||
* leaves nothing registered; after it returns, registration cannot fail.
|
||||
* @param spec - job identity, owner, output sources, and synchronous starter.
|
||||
* @returns the registry-issued `<kind>-N` id.
|
||||
* @returns the registry-issued `<kind>-<uuid>` id.
|
||||
*/
|
||||
abstract start(spec: JobSpec): JobId
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
## ID 与状态
|
||||
|
||||
`JobId` 是按 `<kind>-N` 生成的[品牌化 id](core.zh.md#branded-ids)。访问控制依赖拥有者授权,而非 id 的保密性。`JobKind` 派生自可合并扩展的 map;注册表将各个 kind 视为不透明的 id 命名空间。
|
||||
`JobId` 是按 `<kind>-<uuid>` 生成的[品牌化 id](core.zh.md#branded-ids)。访问控制依赖拥有者授权,而非 id 的保密性。`JobKind` 派生自可合并扩展的 map;注册表将各个 kind 视为不透明的 id 命名空间。
|
||||
|
||||
```ts type-equiv
|
||||
/**
|
||||
@@ -24,16 +24,14 @@ interface JobKindMap {
|
||||
|
||||
## Session 生命周期记录
|
||||
|
||||
有所有者的 job 在原始所有者 Session 中发布 `job/registered` 与 `job/settled`。`executionId` 在注册表重启和历史复制后仍标识同一次执行;`jobId` 仍是进程内控制 id。SDK 通过 `session.event` 转发这些仅日志记录,不依赖输出读取。时间戳描述注册表提交时刻,不是 CPU 时间或精确的 OS 进程边界;缺少结算时保持不完整。`producerReported` 表示生产方通过兑现 `done` 报告了终态;强制销毁或 promise 拒绝时为 false,不独立确认资源已释放。
|
||||
有所有者的 job 在原始所有者 Session 中发布 `job/registered` 与 `job/settled`。全局唯一的 `jobId` 同时配对这两条记录并控制运行中的 job。SDK 通过 `session.event` 转发这些仅日志记录,不依赖输出读取。时间戳描述注册表提交时刻,不是 CPU 时间或精确的 OS 进程边界;缺少结算时保持不完整。`producerReported` 表示生产方通过兑现 `done` 报告了终态;强制销毁或 promise 拒绝时为 false,不独立确认资源已释放。
|
||||
|
||||
`job/settled` 可能出现在 `turn/end` 之后。SDK run 结果只覆盖自身观测窗口;需要较晚结算的消费方应保留 Session 订阅或读取持久化历史。轮次完成不表示所有有主 job 都已停止。
|
||||
|
||||
```ts type-equiv
|
||||
/** Durable identity and registration time of one owned job execution. */
|
||||
interface JobRegisteredData {
|
||||
/** UUID unique to this execution, even when the registry reuses a display id. */
|
||||
readonly executionId: JobExecutionId
|
||||
/** Runtime-local id accepted by job controls. */
|
||||
/** Unique execution id accepted by job controls and retained in Session history. */
|
||||
readonly jobId: JobId
|
||||
/** Original owner; copied history does not transfer execution ownership. */
|
||||
readonly owner: SessionId
|
||||
@@ -132,7 +130,7 @@ interface JobSpec {
|
||||
* throwing, so a producer's trailing flush cannot break its own teardown path.
|
||||
*/
|
||||
interface JobHandle {
|
||||
/** The registry-issued id (`<kind>-N`). */
|
||||
/** The registry-issued id (`<kind>-<uuid>`). */
|
||||
readonly id: JobId
|
||||
/**
|
||||
* Append one chunk to the output ring. Offsets advance by the chunk's UTF-8
|
||||
@@ -275,7 +273,7 @@ interface JobOutputRead {
|
||||
* stream all consume this one shape.
|
||||
*/
|
||||
interface JobView {
|
||||
/** The registry-issued id (`<kind>-N`). */
|
||||
/** The registry-issued id (`<kind>-<uuid>`). */
|
||||
readonly id: JobId
|
||||
/**
|
||||
* The producer kind the job was registered with: a Host-registered
|
||||
@@ -461,7 +459,7 @@ Implementations must honor these semantics:
|
||||
* rejection leaves no job id or execution resource. A throwing starter
|
||||
* leaves nothing registered; after it returns, registration cannot fail.
|
||||
* @param spec - job identity, owner, output sources, and synchronous starter.
|
||||
* @returns the registry-issued `<kind>-N` id.
|
||||
* @returns the registry-issued `<kind>-<uuid>` id.
|
||||
*/
|
||||
abstract start(spec: JobSpec): JobId
|
||||
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
# Bilingual-pair consistency record for guide.md (docs/i18n/README.md): per heading
|
||||
# section, a hash of its English and Chinese blocks outside code blocks and generated regions.
|
||||
# After editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write docs/upgrade-guide/v0.2.1-alpha.1/job-id-identity/guide.md
|
||||
/:
|
||||
en: d6fdce6b58c67691
|
||||
zh: a103e5028f5442d0
|
||||
/background-job-ids-identify-one-execution:
|
||||
en: 9b06780be69e93f6
|
||||
zh: c33945fd584c6a23
|
||||
/background-job-ids-identify-one-execution/change:
|
||||
en: 93ae25c874b916b9
|
||||
zh: 38dee1fc3edd2873
|
||||
/background-job-ids-identify-one-execution/migration:
|
||||
en: c5ec322772f41799
|
||||
zh: 8e9f27d749995be2
|
||||
@@ -0,0 +1,18 @@
|
||||
---
|
||||
kind: upgrade-guide
|
||||
description: "JobId now identifies one background execution across registry lifetimes."
|
||||
---
|
||||
|
||||
# Background job ids identify one execution
|
||||
|
||||
English | [中文](guide.zh.md)
|
||||
|
||||
## Change
|
||||
|
||||
The job registry returns `<kind>-<uuid>` instead of `<kind>-N`. The same `JobId` appears in live controls, `job/registered`, and `job/settled`; lifecycle records no longer carry `executionId`. The identifier remains owner-authorized. Existing Session histories remain unchanged and do not resume their jobs.
|
||||
|
||||
## Migration
|
||||
|
||||
1. Pass the exact id returned by a start result or `jobs.list()` to `job_output`, `job_wait`, and `job_kill`. Remove code that constructs an id from the kind and an ordinal.
|
||||
2. Pair SDK lifecycle events by `data.jobId`. Remove imports of `JobExecutionId` and code that maps it to a process-local `jobId`.
|
||||
3. Verify a new job's registration and settlement contain the same `jobId`, and a later registry instance issues a different one.
|
||||
@@ -0,0 +1,18 @@
|
||||
---
|
||||
kind: upgrade-guide
|
||||
description: "JobId 现在跨注册表生命周期标识一次后台执行。"
|
||||
---
|
||||
|
||||
# 后台 job id 标识一次执行
|
||||
|
||||
[English](guide.md) | 中文
|
||||
|
||||
## 变更
|
||||
|
||||
job 注册表返回 `<kind>-<uuid>`,不再返回 `<kind>-N`。运行期控制、`job/registered` 和 `job/settled` 使用同一个 `JobId`;生命周期记录不再携带 `executionId`。访问仍按所有者授权。已有 Session 历史保持原样,不会恢复其中的 job。
|
||||
|
||||
## 迁移
|
||||
|
||||
1. 把启动结果或 `jobs.list()` 返回的原始 id 传给 `job_output`、`job_wait` 和 `job_kill`。移除按 kind 和序号构造 id 的代码。
|
||||
2. 使用 `data.jobId` 配对 SDK 生命周期事件。移除 `JobExecutionId` 导入,以及将它映射为进程内 `jobId` 的代码。
|
||||
3. 核对新 job 的注册与结算含有相同 `jobId`,后续注册表实例发放不同 id。
|
||||
@@ -18,8 +18,8 @@
|
||||
en: ad6f55231a2cb77d
|
||||
zh: 9039fa43deeaa458
|
||||
/deepseek-ai-dsh-cmdline/use-this-package/the-launcher-values:
|
||||
en: ee861ab9618f1d80
|
||||
zh: d0ebe531322e1eac
|
||||
en: db6e8aa9f1d9c4a5
|
||||
zh: 3b0729d44a4b4558
|
||||
/deepseek-ai-dsh-cmdline/use-this-package/parsing-your-flags:
|
||||
en: e88383d7638cb933
|
||||
zh: 0d0608be857be763
|
||||
@@ -36,8 +36,8 @@
|
||||
en: 0753fe84c14e7d0b
|
||||
zh: acc5c2224fac229e
|
||||
/deepseek-ai-dsh-cmdline/understand-the-implementation/parsing-contract:
|
||||
en: 720b38c4c9afeb93
|
||||
zh: b43160d7176e5799
|
||||
en: b551c55a0dac339e
|
||||
zh: 4248c18756cf0b18
|
||||
/deepseek-ai-dsh-cmdline/understand-the-implementation/source-map:
|
||||
en: a5f51481ca8e8751
|
||||
zh: c512261b9a3bca74
|
||||
|
||||
@@ -29,13 +29,14 @@ Your app reads the invocation's inner arguments at startup, and any number of it
|
||||
|
||||
### The launcher values
|
||||
|
||||
The launcher makes three things available to your app:
|
||||
The launcher makes four things available to your app:
|
||||
|
||||
- `ctx.cmdlineArgs` — the inner arguments of your invocation. Reading them returns an immutable snapshot and never consumes or changes them: `dsh --profile tui --resume abc` gives your app `['--resume', 'abc']`.
|
||||
- `ctx.appExit` — a way to ask the process to exit once the tree has shut down, wired to the launcher's shutdown controller.
|
||||
- `ctx.appShutdown` — register owned cleanup before provider disposal for normal and signal exit.
|
||||
- `ctx.appReady` — the successful-startup signal, committed only after the Loader tree and launcher-owned setup succeed.
|
||||
|
||||
`ctx.appExit(code, beforeDispose)` can release caller-owned resources while their providers remain mounted. The callback and whole-tree disposal share the launcher's existing exit bound; rejecting cleanup still attempts tree disposal.
|
||||
Register cleanup with `ctx.appShutdown.addDisposer()` while providers are active. The launcher runs entries in registration order on both normal and signal exit under its existing bound, then disposes the provider tree. A rejection does not skip later cleanup or the tree.
|
||||
|
||||
An app launched with no arguments sees an empty list — that is the honest answer, not a missing value.
|
||||
|
||||
@@ -92,7 +93,7 @@ This section explains how the outcomes above are realized and points at the code
|
||||
|
||||
### Parsing contract
|
||||
|
||||
The parse path is one small family with two owners: `provideCmdline` freezes the host arguments and provides `cmdlineArgs` and `appExit` before any tree entry mounts, and `parseCmdline` runs your commander program against the immutable arguments, routing every command's help, version, and error output through the launcher. A rejected value, `--help`, or `--version` prints commander's text and requests `ctx.appExit` without publishing anything, so dependent rows never activate; Loader defers each row's `!!js` interpolation until its declared injections are active. Per-export contracts live in the code, not this README — see [`src/index.ts`](src/index.ts).
|
||||
The parse path is one small family with two owners: `provideCmdline` freezes the host arguments and provides `cmdlineArgs`, `appExit`, and `appShutdown` before any tree entry mounts, and `parseCmdline` runs your commander program against the immutable arguments, routing every command's help, version, and error output through the launcher. A rejected value, `--help`, or `--version` prints commander's text and requests `ctx.appExit` without publishing anything, so dependent rows never activate; Loader defers each row's `!!js` interpolation until its declared injections are active. Per-export contracts live in the code, not this README — see [`src/index.ts`](src/index.ts).
|
||||
|
||||
### Source map
|
||||
|
||||
|
||||
@@ -33,9 +33,10 @@ kind: "package-library"
|
||||
|
||||
- `ctx.cmdlineArgs`——本次调用的内层参数。读取它返回一份不可变快照,且绝不会消费或修改它们:`dsh --profile tui --resume abc` 给你的应用 `['--resume', 'abc']`。
|
||||
- `ctx.appExit`——在整棵树关闭后请求进程退出的方式,接到启动器的关停控制器上。
|
||||
- `ctx.appShutdown`——在正常退出或信号退出时,先于提供方销毁运行已注册的有序清理。
|
||||
- `ctx.appReady`——成功启动信号,只在 Loader 树与启动器自有设置成功后提交。
|
||||
|
||||
`ctx.appExit(code, beforeDispose)` 可以在提供方仍挂载时释放调用方拥有的资源。该回调与整棵树销毁共用启动器已有的退出时限;清理拒绝时仍会尝试销毁树。
|
||||
在提供方仍有效时,用 `ctx.appShutdown.addDisposer()` 注册清理。正常退出和信号退出共用启动器已有时限,按注册顺序先运行清理,再销毁提供方树。某个清理拒绝不会跳过后续清理或整棵树。
|
||||
|
||||
没有参数的启动会看到空列表——这是诚实的答案,而不是缺失的值。
|
||||
|
||||
@@ -92,7 +93,7 @@ kind: "package-library"
|
||||
|
||||
### 解析约定
|
||||
|
||||
解析流程由两部分负责:`provideCmdline` 冻结宿主参数,并在任何配置树条目挂载前提供 `cmdlineArgs` 与 `appExit`;`parseCmdline` 针对不可变参数运行你的 commander program,把每个命令的 help、version 与错误输出都接到启动器上。被拒绝的值、`--help` 或 `--version` 会打印 commander 文本并请求 `ctx.appExit`,且不发布任何内容,因此依赖行绝不会激活;Loader 会把每行的 `!!js` 插值推迟到该行声明的注入全部激活之后。各导出的约定在代码中,不在本 README——见 [`src/index.ts`](src/index.ts)。
|
||||
解析流程由两部分负责:`provideCmdline` 冻结宿主参数,并在任何配置树条目挂载前提供 `cmdlineArgs`、`appExit` 与 `appShutdown`;`parseCmdline` 针对不可变参数运行你的 commander program,把每个命令的 help、version 与错误输出都接到启动器上。被拒绝的值、`--help` 或 `--version` 会打印 commander 文本并请求 `ctx.appExit`,且不发布任何内容,因此依赖行绝不会激活;Loader 会把每行的 `!!js` 插值推迟到该行声明的注入全部激活之后。各导出的约定在代码中,不在本 README——见 [`src/index.ts`](src/index.ts)。
|
||||
|
||||
### 源码地图
|
||||
|
||||
|
||||
@@ -35,11 +35,21 @@ export interface CmdlineArgs {
|
||||
/** Request bounded process exit; the launcher wires it to its shutdown controller. */
|
||||
export interface AppExit {
|
||||
/**
|
||||
* Request exit after caller-owned cleanup and whole-tree disposal within the launcher's bound.
|
||||
* Request exit after registered cleanup and whole-tree disposal within the launcher's bound.
|
||||
* @param code - the process exit code.
|
||||
* @param beforeDispose - optional owned-resource cleanup that needs the tree's providers alive.
|
||||
*/
|
||||
(code: number, beforeDispose?: () => Promise<void>): void
|
||||
(code: number): void
|
||||
}
|
||||
|
||||
/** Ordered resource cleanup shared by normal and signal-triggered process exit. */
|
||||
export interface AppShutdown {
|
||||
/**
|
||||
* Register cleanup that runs once before application providers dispose, in registration order.
|
||||
* A failed cleanup does not skip later entries or whole-tree disposal; the exit bound covers all stages.
|
||||
* @param dispose - owned-resource cleanup requiring live application providers.
|
||||
* @returns an unregister function; registrations are refused after shutdown starts.
|
||||
*/
|
||||
addDisposer(dispose: () => Promise<void>): () => void
|
||||
}
|
||||
|
||||
/** Successful application-startup signal owned by the launcher. */
|
||||
@@ -59,6 +69,8 @@ declare module '@deepseek-ai/cordis' {
|
||||
cmdlineArgs?: CmdlineArgs
|
||||
/** Bounded process-exit request; provided by a launcher before the tree mounts. */
|
||||
appExit?: AppExit
|
||||
/** Ordered cleanup before provider disposal on normal or signal exit. */
|
||||
appShutdown?: AppShutdown
|
||||
/** Successful startup signal; provided by a launcher before the tree mounts. */
|
||||
appReady?: AppReady
|
||||
}
|
||||
@@ -70,6 +82,8 @@ export interface CmdlineHost {
|
||||
args: readonly string[]
|
||||
/** Bounded process-exit request. */
|
||||
exit: AppExit
|
||||
/** Ordered cleanup registry for normal and signal exit. */
|
||||
shutdown?: AppShutdown
|
||||
/** Successful startup signal for lifecycle work that must not mask boot failure. */
|
||||
ready?: AppReady
|
||||
}
|
||||
@@ -86,6 +100,7 @@ export function provideCmdline(ctx: Context, host: CmdlineHost): void {
|
||||
const snapshot: readonly string[] = Object.freeze([...host.args])
|
||||
ctx.provide('cmdlineArgs', { get: () => snapshot })
|
||||
ctx.provide('appExit', host.exit)
|
||||
if (host.shutdown !== undefined) ctx.provide('appShutdown', host.shutdown)
|
||||
if (host.ready !== undefined) ctx.provide('appReady', host.ready)
|
||||
}
|
||||
|
||||
|
||||
@@ -220,6 +220,13 @@ describe('parseCmdline', () => {
|
||||
})
|
||||
|
||||
describe('provideCmdline', () => {
|
||||
it('shares launcher-owned cleanup registration with application plugins', () => {
|
||||
const ctx = new Context()
|
||||
const shutdown = { addDisposer: () => () => {} }
|
||||
provideCmdline(ctx, { args: [], exit: () => {}, shutdown })
|
||||
expect(ctx.get('appShutdown')).toBe(shutdown)
|
||||
})
|
||||
|
||||
it('hands the app a snapshot the caller cannot mutate afterwards', () => {
|
||||
const ctx = new Context()
|
||||
const args = ['--resume', 'abc']
|
||||
|
||||
@@ -36,8 +36,8 @@
|
||||
en: be1b73a6d0ee3902
|
||||
zh: bdba099685d68b27
|
||||
/deepseek-ai-dsh-headless/understand-the-implementation/run-flow:
|
||||
en: 495441a707bf8cd6
|
||||
zh: b9ea418971ba8e42
|
||||
en: a559f832b862382b
|
||||
zh: 52f92dc8fe33c4c2
|
||||
/deepseek-ai-dsh-headless/understand-the-implementation/patch-surface-over-base:
|
||||
en: a22752c052878579
|
||||
zh: 1e42c946d8673eeb
|
||||
|
||||
@@ -77,7 +77,7 @@ The runner is a direct driver over the core API carrier: it resolves the Agent i
|
||||
|
||||
### Run flow
|
||||
|
||||
The runner awaits the complete application (`ctx.get('loader')?.await()`) so the composed tools and adapters are not half-mounted, reads the shared [`agentDefaultModel`](../../core/agent-default-model/README.md) selection, resolves the task from config or stdin, then resolves the Agent identity: a fresh `session-<uuid>` by default, or the persisted Session `--session-id` names, which it adopts through [`sessionQuery`](../../session-query/session-query/README.md) and refuses when no log exists. It submits the task as an ordinary user message. Without `--json` it streams that Agent's non-empty reasoning deltas to stderr; with `--json` it projects the run instead. It waits for child work and rechecks that the parent remains idle with an unchanged Session sequence before finishing, so settlement notices can drive a final parent answer. Idle children with parked inbox messages do not delay completion; process shutdown discards their unclaimed messages. It then flushes the Session and folds the owned interval (`firstSeq` onward) into the last non-empty `assistant/message` text and final `turn/end` reason. It writes the final text to stdout (or the `final` event) and requests exit with its owned Agent disposal. The launcher drains that Agent, including owned jobs and its Session write handle, before disposing the providers; this uses the existing bounded shutdown deadline.
|
||||
The runner awaits the complete application (`ctx.get('loader')?.await()`) so the composed tools and adapters are not half-mounted, reads the shared [`agentDefaultModel`](../../core/agent-default-model/README.md) selection, resolves the task from config or stdin, then resolves the Agent identity: a fresh `session-<uuid>` by default, or the persisted Session `--session-id` names, which it adopts through [`sessionQuery`](../../session-query/session-query/README.md) and refuses when no log exists. It submits the task as an ordinary user message. Without `--json` it streams that Agent's non-empty reasoning deltas to stderr; with `--json` it projects the run instead. It waits for child work and rechecks that the parent remains idle with an unchanged Session sequence before finishing, so settlement notices can drive a final parent answer. Idle children with parked inbox messages do not delay completion; process shutdown discards their unclaimed messages. It then flushes the Session and folds the owned interval (`firstSeq` onward) into the last non-empty `assistant/message` text and final `turn/end` reason. It registers Agent disposal with `appShutdown` before awaiting acquisition, writes the final text to stdout (or the `final` event), and requests exit. Normal and signal exit drain that Agent, including owned jobs and its Session write handle, before the providers under the existing shutdown deadline.
|
||||
|
||||
### Patch surface over base
|
||||
|
||||
|
||||
@@ -77,7 +77,7 @@ runner 是核心 API 载体之上的直接驱动器:它确定 Agent 标识—
|
||||
|
||||
### 运行流程
|
||||
|
||||
runner 等待整个应用结算(`ctx.get('loader')?.await()`),确保已组合的工具与适配器不会半挂载,读取共享的 [`agentDefaultModel`](../../core/agent-default-model/README.zh.md) 选择,从配置或 stdin 解析任务,然后确定 Agent 标识:默认是全新的 `session-<uuid>`,或是 `--session-id` 指名的持久化 Session——通过 [`sessionQuery`](../../session-query/session-query/README.zh.md) 沿用,日志不存在时拒绝。它把任务作为普通用户消息提交。不带 `--json` 时,它把该 Agent 的非空推理增量流式写入 stderr;带 `--json` 时改为投影本次运行。它等待子级工作,并在结束前复核父级仍空闲且 Session 序号未变,让结算通知可以驱动父级生成最终答案。收件箱消息处于停放状态的空闲子级不会延迟完成;进程关闭时丢弃这些未领取消息。随后它对会话执行 flush,并把所属区间(从 `firstSeq` 起)折叠为最后一条非空 `assistant/message` 文本与最终 `turn/end` 原因。最后,它把最终文本写入 stdout(或 `final` 事件),并携带自己拥有的 Agent 销毁动作请求退出。启动器先排空该 Agent(包括有所有者的 job 与 Session 写句柄),再销毁提供方;这共用已有的关停时限。
|
||||
runner 等待整个应用结算(`ctx.get('loader')?.await()`),确保已组合的工具与适配器不会半挂载,读取共享的 [`agentDefaultModel`](../../core/agent-default-model/README.zh.md) 选择,从配置或 stdin 解析任务,然后确定 Agent 标识:默认是全新的 `session-<uuid>`,或是 `--session-id` 指名的持久化 Session——通过 [`sessionQuery`](../../session-query/session-query/README.zh.md) 沿用,日志不存在时拒绝。它把任务作为普通用户消息提交。不带 `--json` 时,它把该 Agent 的非空推理增量流式写入 stderr;带 `--json` 时改为投影本次运行。它等待子级工作,并在结束前复核父级仍空闲且 Session 序号未变,让结算通知可以驱动父级生成最终答案。收件箱消息处于停放状态的空闲子级不会延迟完成;进程关闭时丢弃这些未领取消息。随后它对会话执行 flush,并把所属区间(从 `firstSeq` 起)折叠为最后一条非空 `assistant/message` 文本与最终 `turn/end` 原因。runner 在等待取得 Agent 之前将销毁动作注册到 `appShutdown`,随后把最终文本写入 stdout(或 `final` 事件)并请求退出。正常退出与信号退出都会在已有时限内先排空 Agent(包括有所有者的 job 与 Session 写句柄),再销毁提供方。
|
||||
|
||||
### 基于 base 的 patch 内容
|
||||
|
||||
|
||||
@@ -66,7 +66,7 @@ interface HeadlessIo {
|
||||
stdout: { write(chunk: string): unknown }
|
||||
stderr: { write(chunk: string): unknown }
|
||||
/** Request process exit with `code` after the tree disposes. */
|
||||
exit: (code: number, beforeDispose?: () => Promise<void>) => void
|
||||
exit: (code: number) => void
|
||||
}
|
||||
|
||||
/** Aggregate the last assistant text and turn outcome in one owned interval. */
|
||||
@@ -340,17 +340,22 @@ async function run(ctx: Context, config: Config, io: HeadlessIo): Promise<void>
|
||||
const sessionId = brandString<SessionId>(config.sessionId ?? `session-${randomUUID()}`)
|
||||
const fs = ctx.get('fs')
|
||||
const cwd = fs === undefined ? process.cwd() : fs.processPath(await fs.resolve('.'))
|
||||
const handle = config.sessionId === undefined
|
||||
? await agents.create({
|
||||
const shutdown = ctx.get('appShutdown')
|
||||
if (shutdown === undefined) throw new Error('headless-runner: the launcher must provide ctx.appShutdown')
|
||||
const pendingHandle = config.sessionId === undefined
|
||||
? agents.create({
|
||||
sessionId,
|
||||
meta: { cwd },
|
||||
agentOptions,
|
||||
setup,
|
||||
})
|
||||
: await resolveAgent(ctx, agents, sessionId, agentOptions, setup, cwd)
|
||||
const agent = handle.agent
|
||||
const exit = io.exit
|
||||
io.exit = (code) => { exit(code, () => handle.dispose()) }
|
||||
: resolveAgent(ctx, agents, sessionId, agentOptions, setup, cwd)
|
||||
ctx.effect(() => shutdown.addDisposer(async () => {
|
||||
// A failed acquisition owns its own rollback; the run reports the original error.
|
||||
const handle = await pendingHandle.catch((_acquisitionFailed: unknown) => undefined)
|
||||
await handle?.dispose()
|
||||
}))
|
||||
const { agent } = await pendingHandle
|
||||
await agent.whenIdle()
|
||||
if (config.sessionId !== undefined) {
|
||||
// The resume-time check read a snapshot; an overlay can still append a
|
||||
|
||||
@@ -54,6 +54,8 @@ interface BenchOptions {
|
||||
omitSessionQuery?: boolean
|
||||
/** Leave the persistence service unmounted to exercise the fail-loud path. */
|
||||
omitPersistence?: boolean
|
||||
/** Leave the launcher cleanup registry unavailable. */
|
||||
omitShutdown?: boolean
|
||||
/** Register a live Agent under `sessionId` before the runner starts. */
|
||||
prelive?: boolean
|
||||
/** Header facts for that pre-registered live Agent. */
|
||||
@@ -179,6 +181,13 @@ async function bench(script: Script, options: BenchOptions = {}): Promise<{
|
||||
return agent
|
||||
}
|
||||
|
||||
const disposers = new Set<() => Promise<void>>()
|
||||
if (options.omitShutdown !== true) {
|
||||
ctx.provide('appShutdown', { addDisposer(dispose) {
|
||||
disposers.add(dispose)
|
||||
return () => { disposers.delete(dispose) }
|
||||
} })
|
||||
}
|
||||
await ctx.plugin(SessionStore)
|
||||
await ctx.plugin(SessionProjectionRegistry)
|
||||
await ctx.plugin(AgentRegistry)
|
||||
@@ -215,8 +224,11 @@ async function bench(script: Script, options: BenchOptions = {}): Promise<{
|
||||
internals.stderr = { write: (chunk: string) => { err += chunk; return true } }
|
||||
if (options.readStdin !== undefined) internals.readStdin = options.readStdin
|
||||
const exited = new Promise<number>((resolve) => {
|
||||
ctx.provide('appExit', (code: number, beforeDispose?: () => Promise<void>) => {
|
||||
void Promise.resolve().then(beforeDispose).then(() => { order.push('exit'); resolve(code) })
|
||||
ctx.provide('appExit', (code: number) => {
|
||||
void (async () => {
|
||||
for (const dispose of disposers) await dispose()
|
||||
order.push('exit'); resolve(code)
|
||||
})()
|
||||
})
|
||||
})
|
||||
if (options.prelive === true || options.preliveMeta !== undefined) {
|
||||
@@ -1133,6 +1145,7 @@ describe('headless runner', () => {
|
||||
internals.stderr = { write: (chunk: string) => { err += chunk; return true } }
|
||||
const exited = new Promise<number>((resolve) => {
|
||||
ctx.provide('appExit', resolve)
|
||||
ctx.provide('appShutdown', { addDisposer: () => () => {} })
|
||||
})
|
||||
ctx.provide('agentDefaultModel', { currentSelection: () => ({ provider: 'p', model: 'm' }) } as never)
|
||||
ctx.provide('sessions', { flush: () => Promise.resolve(true) } as never)
|
||||
@@ -1150,6 +1163,7 @@ describe('headless runner', () => {
|
||||
internals.stderr = { write: (chunk: string) => { err += chunk; return true } }
|
||||
const exited = new Promise<number>((resolve) => {
|
||||
ctx.provide('appExit', resolve)
|
||||
ctx.provide('appShutdown', { addDisposer: () => () => {} })
|
||||
})
|
||||
ctx.provide('agentDefaultModel', { currentSelection: () => ({ provider: 'p', model: 'm' }) } as never)
|
||||
ctx.provide('sessions', { flush: () => Promise.resolve(true) } as never)
|
||||
@@ -1193,6 +1207,17 @@ describe('headless runner', () => {
|
||||
expect(() => { apply(ctx, { task: 't' }) }).toThrow('must provide ctx.appExit')
|
||||
})
|
||||
|
||||
it('fails before creating an Agent when the launcher has no ordered cleanup registry', async () => {
|
||||
const test = await bench({ afterPrompt() { throw new Error('must not prompt without shutdown') } }, { omitShutdown: true })
|
||||
try {
|
||||
await expect(test.run()).resolves.toMatchObject({
|
||||
code: 1, out: '', err: 'dsh: headless-runner: the launcher must provide ctx.appShutdown\n',
|
||||
})
|
||||
} finally {
|
||||
await test.ctx.fiber.dispose()
|
||||
}
|
||||
})
|
||||
|
||||
it('validates config: the task and run options are optional', () => {
|
||||
expect(new Config({})).toEqual({})
|
||||
expect(new Config({ task: 'x', sessionId: 'session-x', json: true }))
|
||||
|
||||
@@ -37,6 +37,7 @@ async function harness() {
|
||||
internals.stderr = { write: (chunk: string) => { err += chunk; return true } }
|
||||
const exited = Promise.withResolvers<number>()
|
||||
context.provide('appExit', exited.resolve)
|
||||
context.provide('appShutdown', { addDisposer: () => () => {} })
|
||||
return {
|
||||
ctx: context,
|
||||
async run(json = false) {
|
||||
|
||||
@@ -1348,7 +1348,7 @@ export const SERVICE_API: readonly ServiceApiEntry[] = [
|
||||
signature: 'abstract start(spec: JobSpec): JobId',
|
||||
description: 'Preflight access, validation, owner cleanup, and implementation-owned admission before starting and atomically registering work. Any preflight rejection leaves no job id or execution resource. A throwing starter leaves nothing registered; after it returns, registration cannot fail.',
|
||||
parameters: [{ name: 'spec', description: 'job identity, owner, output sources, and synchronous starter.' }],
|
||||
returns: 'the registry-issued `<kind>-N` id.',
|
||||
returns: 'the registry-issued `<kind>-<uuid>` id.',
|
||||
},
|
||||
{
|
||||
signature: 'abstract list(caller?: SessionId): JobView[]',
|
||||
|
||||
@@ -30,8 +30,8 @@
|
||||
en: 91dd372f742dcdb8
|
||||
zh: 58eeee0254d33cd1
|
||||
/deepseek-ai-dsh-jobs-local/use-this-package/session-timing-records:
|
||||
en: 51d69ff00d143272
|
||||
zh: 1022edadf46bad45
|
||||
en: c6feb8cd9f67a6f3
|
||||
zh: 0a0700d8e675855f
|
||||
/deepseek-ai-dsh-jobs-local/use-this-package/what-can-go-wrong:
|
||||
en: e1f4511c458dff3b
|
||||
zh: bea247c2a365a646
|
||||
|
||||
@@ -58,7 +58,7 @@ Jobs belong to their owner and backend, not to the producer tool, so producer or
|
||||
|
||||
### Session timing records
|
||||
|
||||
Owned jobs record registration and settlement in their original Session, independently of model output collection. SDK consumers receive the same events through `session.event`; the [jobs reference](../../../docs/subsystems/jobs.md#session-lifecycle-records) defines identities, timestamps, and termination confirmation. These records survive through normal Session persistence; live jobs and output rings are not resumed.
|
||||
Owned jobs record registration and settlement in their original Session, independently of model output collection. SDK consumers receive the same events through `session.event`; the [jobs reference](../../../docs/subsystems/jobs.md#session-lifecycle-records) defines identities, timestamps, and producer settlement reports. These records survive through normal Session persistence; live jobs and output rings are not resumed.
|
||||
|
||||
Lifecycle facts are appended synchronously at the registration or settlement commit, before live hub subscribers and completion waiters react. Append failures warn without abandoning execution or blocking cleanup; consumers must retain incomplete histories as incomplete.
|
||||
|
||||
|
||||
@@ -58,7 +58,7 @@ kind: "package-reference"
|
||||
|
||||
### Session 时间记录
|
||||
|
||||
有所有者的 job 在原始 Session 中记录注册与结算,不依赖模型收集输出。SDK 消费方通过 `session.event` 收到同样的事件;[jobs 参考](../../../docs/subsystems/jobs.zh.md#session-lifecycle-records)定义标识、时间戳和终止确认。这些记录通过正常 Session 持久化保留;运行中的 job 和输出环不会被恢复。
|
||||
有所有者的 job 在原始 Session 中记录注册与结算,不依赖模型收集输出。SDK 消费方通过 `session.event` 收到同样的事件;[jobs 参考](../../../docs/subsystems/jobs.zh.md#session-lifecycle-records)定义标识、时间戳和生产方结算报告。这些记录通过正常 Session 持久化保留;运行中的 job 和输出环不会被恢复。
|
||||
|
||||
生命周期事实在注册或结算提交时同步追加,先于运行期 hub 订阅者和完成等待方的反应。追加失败会记录警告,不放弃执行或阻塞清理;消费方必须将不完整历史保留为不完整。
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ import type { Agent } from '@deepseek-ai/dsh-agent'
|
||||
import { ScopedLayers, scopeOf } from '@deepseek-ai/dsh-scope'
|
||||
import type { SessionId } from '@deepseek-ai/dsh-session'
|
||||
import { deadline, timeoutOf } from '@deepseek-ai/dsh-timeout'
|
||||
import { JobRegistry, JobExecutionId, JobId } from '@deepseek-ai/dsh-jobs'
|
||||
import { JobRegistry, JobId } from '@deepseek-ai/dsh-jobs'
|
||||
import type {
|
||||
JobAppendOptions, JobEvent, JobEvents, JobHandle, JobKind, JobOutcome, JobOutputRead, JobOutputSource,
|
||||
JobRead, JobRegisteredData, JobSettleCause, JobSettledData, JobSpec, JobStatus, JobToolCall, JobView,
|
||||
@@ -76,7 +76,6 @@ interface ProducerState {
|
||||
/** The registry's mutable per-job record (never handed out — see {@link LocalJobRegistry.view}). */
|
||||
interface TrackedJob {
|
||||
id: JobId
|
||||
executionId: JobExecutionId
|
||||
toolCall: JobToolCall | undefined
|
||||
kind: JobKind
|
||||
label: string
|
||||
@@ -161,7 +160,6 @@ export class LocalJobRegistry extends JobRegistry {
|
||||
/** Schemastery-defaulted pull-source poll interval. */
|
||||
private readonly pumpPollMs: number
|
||||
private store = new Map<JobId, TrackedJob>()
|
||||
private counters = new Map<string, number>()
|
||||
/**
|
||||
* Controllers and scoped subscriptions layered by the scope that registered
|
||||
* them, in the tools-registry shape: a contribution files into its
|
||||
@@ -226,13 +224,8 @@ export class LocalJobRegistry extends JobRegistry {
|
||||
)
|
||||
}
|
||||
|
||||
// The id is issued before the starter runs so the producer face can carry
|
||||
// it; a throwing starter still leaves nothing registered — its ordinal is
|
||||
// simply skipped.
|
||||
const count = (this.counters.get(spec.kind) ?? 0) + 1
|
||||
this.counters.set(spec.kind, count)
|
||||
const id = JobId(`${spec.kind}-${count}`)
|
||||
const executionId = JobExecutionId(randomUUID())
|
||||
// The producer uses the same identity as controls and durable lifecycle records.
|
||||
const id = JobId(`${spec.kind}-${randomUUID()}`)
|
||||
const toolCall = spec.toolCall === undefined ? undefined : { ...spec.toolCall }
|
||||
const ring = new OutputRing()
|
||||
const state: ProducerState = { progress: undefined, job: undefined }
|
||||
@@ -247,7 +240,6 @@ export class LocalJobRegistry extends JobRegistry {
|
||||
const settled = new Promise<void>((resolve) => { markSettled = resolve })
|
||||
const job: TrackedJob = {
|
||||
id,
|
||||
executionId,
|
||||
toolCall,
|
||||
kind: spec.kind,
|
||||
label: spec.label,
|
||||
@@ -453,7 +445,7 @@ export class LocalJobRegistry extends JobRegistry {
|
||||
if (owner === undefined) return
|
||||
const session = owner.session
|
||||
const data: JobRegisteredData = {
|
||||
executionId: job.executionId, jobId: job.id, owner: owner.id,
|
||||
jobId: job.id, owner: owner.id,
|
||||
kind: job.kind, label: job.label, startedAt: job.startedAt,
|
||||
...job.toolCall !== undefined ? { toolCall: job.toolCall } : {},
|
||||
}
|
||||
@@ -462,7 +454,7 @@ export class LocalJobRegistry extends JobRegistry {
|
||||
else session.append('job/settled', { ...data, ...settlement })
|
||||
} catch (error: unknown) {
|
||||
// Recording cannot orphan already-started work or prevent waiter release.
|
||||
this.selfCtx.logger.warn(`jobs: failed to record lifecycle for ${job.executionId}: ${String(error)}`)
|
||||
this.selfCtx.logger.warn(`jobs: failed to record lifecycle for ${job.id}: ${String(error)}`)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -249,7 +249,7 @@ describe('LocalJobRegistry.start', () => {
|
||||
it('rejects before producer start and id allocation, then admits immediately after settlement', async () => {
|
||||
const ctx = await harness({ maxConcurrentJobsPerOwner: 1 })
|
||||
const first = producer()
|
||||
expect(ctx.jobs.start(first.spec)).toBe('bash-1')
|
||||
expect(ctx.jobs.start(first.spec)).toMatch(/^bash-[0-9a-f-]{36}$/)
|
||||
|
||||
const blocked = producer()
|
||||
const run = vi.fn((job: JobHandle) => blocked.spec.run(job))
|
||||
@@ -259,7 +259,7 @@ describe('LocalJobRegistry.start', () => {
|
||||
|
||||
first.settle({ status: 'completed' })
|
||||
await tick()
|
||||
expect(ctx.jobs.start(blocked.spec)).toBe('bash-2')
|
||||
expect(ctx.jobs.start(blocked.spec)).toMatch(/^bash-[0-9a-f-]{36}$/)
|
||||
})
|
||||
|
||||
it('keeps a stopping job in the bucket until producer settlement', async () => {
|
||||
@@ -273,7 +273,7 @@ describe('LocalJobRegistry.start', () => {
|
||||
|
||||
first.settle({ status: 'killed' })
|
||||
await tick()
|
||||
expect(ctx.jobs.start(replacement.spec)).toBe('bash-2')
|
||||
expect(ctx.jobs.start(replacement.spec)).toMatch(/^bash-[0-9a-f-]{36}$/)
|
||||
})
|
||||
|
||||
it.each(['completed', 'killed', 'failed'] as const)(
|
||||
@@ -313,12 +313,12 @@ describe('LocalJobRegistry.start', () => {
|
||||
await disposeAgentScope(oldOwner)
|
||||
})
|
||||
|
||||
it('issues kind-prefixed ids from per-kind counters', async () => {
|
||||
it('issues unique kind-prefixed ids', async () => {
|
||||
const ctx = await harness()
|
||||
expect(ctx.jobs.start(producer().spec)).toBe('bash-1')
|
||||
expect(ctx.jobs.start(producer().spec)).toBe('bash-2')
|
||||
expect(ctx.jobs.start(producer({ kind: 'test-task' }).spec)).toBe('test-task-1')
|
||||
expect(ctx.jobs.start(producer({ kind: 'workflow' }).spec)).toBe('workflow-1')
|
||||
expect(ctx.jobs.start(producer().spec)).toMatch(/^bash-[0-9a-f-]{36}$/)
|
||||
expect(ctx.jobs.start(producer().spec)).toMatch(/^bash-[0-9a-f-]{36}$/)
|
||||
expect(ctx.jobs.start(producer({ kind: 'test-task' }).spec)).toMatch(/^test-task-[0-9a-f-]{36}$/)
|
||||
expect(ctx.jobs.start(producer({ kind: 'workflow' }).spec)).toMatch(/^workflow-[0-9a-f-]{36}$/)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -725,9 +725,9 @@ describe('LocalJobRegistry owner isolation', () => {
|
||||
ctx.jobs.attachController('test-controller')
|
||||
expect(() => ctx.jobs.start({ ...producer().spec, owner: SessionId('a') }))
|
||||
.toThrow('background job ownership requires the agent registry')
|
||||
// The failed registration mutated nothing: no stored job, counter untouched.
|
||||
// The failed registration mutated nothing: no stored job.
|
||||
expect(ctx.jobs.list()).toEqual([])
|
||||
expect(ctx.jobs.start(producer().spec)).toBe('bash-1')
|
||||
expect(ctx.jobs.start(producer().spec)).toMatch(/^bash-[0-9a-f-]{36}$/)
|
||||
})
|
||||
|
||||
it('rejects a session without a live agent and attaches cleanup once it has one', async () => {
|
||||
@@ -752,7 +752,7 @@ describe('LocalJobRegistry owner isolation', () => {
|
||||
done: new Promise<JobOutcome>((res) => { settle = res }),
|
||||
}),
|
||||
})
|
||||
expect(id).toBe('bash-1') // the failed attempt burned no counter
|
||||
expect(id).toMatch(/^bash-[0-9a-f-]{36}$/)
|
||||
await disposeAgentScope(ghost)
|
||||
expect(cancels).toEqual(['owner disposed'])
|
||||
expect(ctx.jobs.list(ghost.id)).toEqual([])
|
||||
@@ -807,7 +807,9 @@ describe('LocalJobRegistry owner cleanup', () => {
|
||||
await disposeAgentScope(owner)
|
||||
expect(cancels).toEqual(['owner disposed'])
|
||||
expect(ctx.jobs.list(owner.id)).toEqual([])
|
||||
expect(seen.map(event => event.type === 'removed' ? event.job.id : undefined)).toEqual(['test-task-1', 'bash-1'])
|
||||
expect(seen.map(event => event.type === 'removed' ? event.job.id : undefined)).toEqual([
|
||||
expect.stringMatching(/^test-task-[0-9a-f-]{36}$/), expect.stringMatching(/^bash-[0-9a-f-]{36}$/),
|
||||
])
|
||||
})
|
||||
|
||||
it('announces teardown settlements with cause teardown', async () => {
|
||||
@@ -1108,8 +1110,8 @@ describe('LocalJobRegistry events', () => {
|
||||
it('announces an unowned job to every session filter, since every caller can see it', async () => {
|
||||
const ctx = await harness()
|
||||
const seen = collect(ctx, { owner: SessionId('anyone') }, ['registered'])
|
||||
ctx.jobs.start(producer().spec)
|
||||
expect(seen).toMatchObject([{ type: 'registered', job: { id: 'bash-1' } }])
|
||||
const id = ctx.jobs.start(producer().spec)
|
||||
expect(seen).toMatchObject([{ type: 'registered', job: { id } }])
|
||||
expect((seen[0] as { job: JobView }).job.owner).toBeUndefined()
|
||||
})
|
||||
|
||||
@@ -1141,7 +1143,7 @@ describe('LocalJobRegistry events', () => {
|
||||
const seen = collect(ctx, { owners: 'all' }, ['registered'])
|
||||
|
||||
const id = ctx.jobs.start(producer().spec)
|
||||
expect(id).toBe('bash-1')
|
||||
expect(id).toMatch(/^bash-[0-9a-f-]{36}$/)
|
||||
expect(seen).toHaveLength(1)
|
||||
expect(warn).toHaveBeenCalledWith(expect.stringContaining('event listener threw on registered'))
|
||||
})
|
||||
@@ -1586,7 +1588,7 @@ describe('owned job Session lifecycle records', () => {
|
||||
expect(ctx.jobs.list(owner.id)).toEqual([])
|
||||
})
|
||||
|
||||
it('keeps execution identity distinct when a fresh registry reuses a job id', async () => {
|
||||
it('keeps job identity distinct across fresh registries with the same owner', async () => {
|
||||
const other = await harness()
|
||||
try {
|
||||
const ids: JobId[] = []
|
||||
@@ -1600,9 +1602,10 @@ describe('owned job Session lifecycle records', () => {
|
||||
await registry.jobs.wait(id, 1000, owner.id)
|
||||
const event = owner.session.snapshotEvents().find(event => event.type === 'job/registered')
|
||||
if (event?.type !== 'job/registered') throw new Error('missing registration')
|
||||
executions.push(event.data.executionId)
|
||||
executions.push(event.data.jobId)
|
||||
}
|
||||
expect(ids).toEqual(['bash-1', 'bash-1'])
|
||||
expect(new Set(ids).size).toBe(2)
|
||||
expect(executions).toEqual(ids)
|
||||
expect(new Set(executions).size).toBe(2)
|
||||
} finally {
|
||||
await other.fiber.dispose()
|
||||
|
||||
@@ -9,8 +9,8 @@
|
||||
en: 721d849fde0fbebe
|
||||
zh: 9578ffb37edc5d7b
|
||||
/deepseek-ai-dsh-jobs/summary:
|
||||
en: f64d24a22c76423c
|
||||
zh: 5bfd3c04ce108bcb
|
||||
en: 64000a337f374627
|
||||
zh: 251c13da2768bbd1
|
||||
/deepseek-ai-dsh-jobs/table-of-contents:
|
||||
en: d152484eb41ac6b4
|
||||
zh: 09388d293f9be9cb
|
||||
@@ -18,11 +18,11 @@
|
||||
en: 1246c880d1c61927
|
||||
zh: 157c703fd2da6b9c
|
||||
/deepseek-ai-dsh-jobs/use-this-package/what-a-background-job-gives-you:
|
||||
en: 54d99a3ae1c6f20a
|
||||
zh: 0b2ffab9644abe66
|
||||
en: 92a9b9c0b6c9e748
|
||||
zh: fc354b68facee753
|
||||
/deepseek-ai-dsh-jobs/use-this-package/the-ownership-boundary:
|
||||
en: 873494750b38dae5
|
||||
zh: 2e6142ab889f7a8b
|
||||
en: 167ab1eab8fad704
|
||||
zh: 7da554826fb483c1
|
||||
/deepseek-ai-dsh-jobs/use-this-package/starting-background-work-needs-a-controller:
|
||||
en: 68075d00cde8763d
|
||||
zh: 4bc42c01d9e4dc8d
|
||||
@@ -39,8 +39,8 @@
|
||||
en: 2a42e1e06dfc469e
|
||||
zh: 5c2c77eca52846f0
|
||||
/deepseek-ai-dsh-jobs/understand-the-implementation/source-map:
|
||||
en: 19d23d01476c0562
|
||||
zh: bf5c7405044f93b3
|
||||
en: c9a58b4340999ae4
|
||||
zh: ef68a0b71a06d183
|
||||
/deepseek-ai-dsh-jobs/understand-the-implementation/service-operations:
|
||||
en: b2e4ef34ccf1e749
|
||||
zh: 128b57b57ed09316
|
||||
|
||||
@@ -9,7 +9,7 @@ English | [中文](README.zh.md)
|
||||
|
||||
## Summary
|
||||
|
||||
`dsh-jobs` lets tools keep long-running work active while an agent continues. Each job receives a stable `<kind>-N` id, and its owning agent can read output, wait with a timeout, or request cancellation. Ownership is scoped to the agent session, so other agents cannot inspect or stop the job; completion arrives as an in-session notice without polling. Users can watch retained live output without consuming what the agent can read. Background jobs can start only when the deployment supplies job execution.
|
||||
`dsh-jobs` lets tools keep long-running work active while an agent continues. Each job receives a stable `<kind>-<uuid>` id, and its owning agent can read output, wait with a timeout, or request cancellation. Ownership is scoped to the agent session, so other agents cannot inspect or stop the job; completion arrives as an in-session notice without polling. Users can watch retained live output without consuming what the agent can read. Background jobs can start only when the deployment supplies job execution.
|
||||
|
||||
## Table of Contents
|
||||
|
||||
@@ -29,7 +29,7 @@ Use this package when you are composing a background-job capability or writing a
|
||||
|
||||
### What a background job gives you
|
||||
|
||||
A producer registers work with a kind and a one-line label; the registry returns a `<kind>-N` id such as `bash-1`. Anyone who owns the job can read output, list jobs, wait up to a timeout for settlement, and request cancellation — each call returns a fresh projection of the job's status, from `running` and `stopping` to the terminal `completed`, `killed`, or `failed`. When a job settles, the registry's event stream announces it and `dsh-tool-jobs` turns the settlement into an in-session notice, so no polling is needed. A producer may attach an optional byte cap so each complete model-facing read or notice stays bounded.
|
||||
A producer registers work with a kind and a one-line label; the registry returns a `<kind>-<uuid>` id such as `bash-<uuid>`. Anyone who owns the job can read output, list jobs, wait up to a timeout for settlement, and request cancellation — each call returns a fresh projection of the job's status, from `running` and `stopping` to the terminal `completed`, `killed`, or `failed`. When a job settles, the registry's event stream announces it and `dsh-tool-jobs` turns the settlement into an in-session notice, so no polling is needed. A producer may attach an optional byte cap so each complete model-facing read or notice stays bounded.
|
||||
|
||||
A producer streams output by naming pull sources on its spec — non-consuming offset readers the registry pumps at its own cadence — or by pushing chunks through the `JobHandle` its starter receives; both land in the job's bounded ring, where `stdout` and `stderr` chunks reach the model and `log` chunks reach observers only. Observers read retained chunks at absolute byte offsets and are signaled on advancement; the settlement that ends the job also ends the stream, and `updateProgress` publishes a live progress line into every projection until then. Observation is invisible to the model: `readAt` consumes nothing and never touches notice state.
|
||||
|
||||
@@ -37,7 +37,7 @@ The shipped provider also records owned-job lifecycle metadata in the Session. P
|
||||
|
||||
### The ownership boundary
|
||||
|
||||
A job belongs to the agent session that started it: another agent cannot read or stop it. Ids such as `bash-1` are predictable, so this fence is authorization, not secrecy. A job started without an owner is open to any caller and lasts until the service is disposed.
|
||||
A job belongs to the agent session that started it: another agent cannot read or stop it. An id may appear in tool output; the registry authorizes every control operation using the owner. A job started without an owner is open to any caller and lasts until the service is disposed.
|
||||
|
||||
### Starting background work needs a controller
|
||||
|
||||
@@ -81,7 +81,7 @@ This section explains the design decisions behind the contract and points at the
|
||||
| [`src/index.ts`](src/index.ts) | Plugin entry: the abstract `JobRegistry` service and its contract |
|
||||
| [`src/types.ts`](src/types.ts) | Shared vocabulary: `JobSpec`, `JobHandle`, `JobHooks`, `JobOutcome`, `JobEvent`, and the read results |
|
||||
| [`src/view.ts`](src/view.ts) | Client-safe leaf: `JobView`, `JobChunk`, `JobStatus`, and the merge-extensible `JobKindMap` |
|
||||
| [`src/brand.ts`](src/brand.ts) | `JobId` and `JobExecutionId` branded identifiers, importable without the agent dependency |
|
||||
| [`src/brand.ts`](src/brand.ts) | the `JobId` branded identifier, importable without the agent dependency |
|
||||
| [`src/archive-admission.ts`](src/archive-admission.ts) | The `job` family of the Workspace registry's archive admission, installed by the seam's constructor for every implementation |
|
||||
|
||||
### Service operations
|
||||
|
||||
@@ -9,7 +9,7 @@ kind: "package-reference"
|
||||
|
||||
## 概述
|
||||
|
||||
`dsh-jobs` 让工具可以在 agent(智能体)继续推进时保持长时间工作运行。每项任务都会获得稳定的 `<kind>-N` id,拥有它的 agent 可以读取输出、带超时等待或请求取消。归属范围限定在 agent 会话内,因此其他 agent 无法查看或停止任务;任务完成时会通过会话内通知送达,无需轮询。用户可以查看保留的实时输出,而不消耗 agent 可读取的内容。只有部署提供任务执行能力时,后台任务才能启动。
|
||||
`dsh-jobs` 让工具可以在 agent(智能体)继续推进时保持长时间工作运行。每项任务都会获得稳定的 `<kind>-<uuid>` id,拥有它的 agent 可以读取输出、带超时等待或请求取消。归属范围限定在 agent 会话内,因此其他 agent 无法查看或停止任务;任务完成时会通过会话内通知送达,无需轮询。用户可以查看保留的实时输出,而不消耗 agent 可读取的内容。只有部署提供任务执行能力时,后台任务才能启动。
|
||||
|
||||
## 目录
|
||||
|
||||
@@ -29,7 +29,7 @@ kind: "package-reference"
|
||||
|
||||
### 后台任务提供什么
|
||||
|
||||
生产方以 kind 和一行标签注册工作;注册表返回 `<kind>-N` id,例如 `bash-1`。拥有任务的任何一方都可以读取输出、列出任务、带超时等待结算或请求取消——每次调用都返回任务状态的全新投影,从 `running`、`stopping` 到终止态的 `completed`、`killed` 或 `failed`。任务结算时,注册表的事件流宣布它,`dsh-tool-jobs` 把结算变成会话内通知,因此无需轮询。生产方可以附加一个可选的字节上限,让每次完整的模型侧读取或通知保持有界。
|
||||
生产方以 kind 和一行标签注册工作;注册表返回 `<kind>-<uuid>` id,例如 `bash-<uuid>`。拥有任务的任何一方都可以读取输出、列出任务、带超时等待结算或请求取消——每次调用都返回任务状态的全新投影,从 `running`、`stopping` 到终止态的 `completed`、`killed` 或 `failed`。任务结算时,注册表的事件流宣布它,`dsh-tool-jobs` 把结算变成会话内通知,因此无需轮询。生产方可以附加一个可选的字节上限,让每次完整的模型侧读取或通知保持有界。
|
||||
|
||||
生产方通过在 spec 上声明拉取源——由注册表按自己的节奏泵送的非消耗偏移读取器——或经 starter 收到的 `JobHandle` 推送块来流式输出;二者都落入该 job 的有界环,其中 `stdout` 与 `stderr` 块到达模型,`log` 块只到达观察者。观察者按绝对字节偏移读取保留块并在推进时收到信号;结束 job 的结算同时封流,在此之前 `updateProgress` 把实时进度行发布进每个投影。观测对模型不可见:`readAt` 不消耗任何东西,也从不触碰通知状态。
|
||||
|
||||
@@ -37,7 +37,7 @@ kind: "package-reference"
|
||||
|
||||
### 归属边界
|
||||
|
||||
任务属于启动它的 agent 会话:其他 agent 无法读取或停止它。`bash-1` 这样的 id 可预测,因此这道隔离是授权,而非保密。没有所有者启动的任务对任何调用方开放,并持续到服务被释放为止。
|
||||
任务属于启动它的 agent 会话:其他 agent 无法读取或停止它。id 可能出现在工具输出中;注册表按所有者校验每次控制操作。没有所有者启动的任务对任何调用方开放,并持续到服务被释放为止。
|
||||
|
||||
### 启动后台工作需要一个控制器
|
||||
|
||||
@@ -81,7 +81,7 @@ kind: "package-reference"
|
||||
| [`src/index.ts`](src/index.ts) | 插件入口:抽象 `JobRegistry` 服务及其约定 |
|
||||
| [`src/types.ts`](src/types.ts) | 共享词汇:`JobSpec`、`JobHandle`、`JobHooks`、`JobOutcome`、`JobEvent` 与读取结果 |
|
||||
| [`src/view.ts`](src/view.ts) | 客户端安全叶子:`JobView`、`JobChunk`、`JobStatus` 与可合并扩展的 `JobKindMap` |
|
||||
| [`src/brand.ts`](src/brand.ts) | `JobId` 与 `JobExecutionId` 带类型标记的标识符,无需 agent 依赖即可导入 |
|
||||
| [`src/brand.ts`](src/brand.ts) | 带类型标记的 `JobId` 标识符,无需 agent 依赖即可导入 |
|
||||
| [`src/archive-admission.ts`](src/archive-admission.ts) | Workspace 注册表归档准入中的 `job` 族,由接缝构造函数为每个实现安装 |
|
||||
|
||||
### 服务操作
|
||||
|
||||
@@ -13,28 +13,16 @@
|
||||
import type { Branded } from '@deepseek-ai/dsh-brand'
|
||||
|
||||
/**
|
||||
* Identifies a background job. The registry generates `<kind>-N`; predictable
|
||||
* ids rely on owner authorization rather than secrecy.
|
||||
* Identifies one job across registry lifetimes. The registry generates `<kind>-<uuid>`;
|
||||
* access still requires owner authorization.
|
||||
*/
|
||||
export type JobId = Branded<'JobId'>
|
||||
|
||||
/**
|
||||
* Brand a string as a {@link JobId}.
|
||||
* @param id - the raw job-id string (the registry generates `<kind>-N`).
|
||||
* @param id - the raw job-id string (the registry generates `<kind>-<uuid>`).
|
||||
* @returns the same string, branded; no validation is performed.
|
||||
*/
|
||||
export function JobId(id: string): JobId {
|
||||
return id as JobId
|
||||
}
|
||||
|
||||
/** Identifies one job execution across registry restarts and copied Session history. */
|
||||
export type JobExecutionId = Branded<'JobExecutionId'>
|
||||
|
||||
/**
|
||||
* Brand the registry-issued UUID of one execution.
|
||||
* @param id - the execution UUID.
|
||||
* @returns the same string, branded; no validation is performed.
|
||||
*/
|
||||
export function JobExecutionId(id: string): JobExecutionId {
|
||||
return id as JobExecutionId
|
||||
}
|
||||
@@ -15,7 +15,6 @@ import { installJobArchiveAdmission } from './archive-admission.ts'
|
||||
import type { JobEvents, JobId, JobOutputRead, JobRead, JobSpec, JobView } from './types.ts'
|
||||
|
||||
export { JobId } from './types.ts'
|
||||
export { JobExecutionId } from './brand.ts'
|
||||
export type {
|
||||
JobAppendOptions,
|
||||
JobChannel,
|
||||
@@ -109,7 +108,7 @@ export abstract class JobRegistry extends Service {
|
||||
* rejection leaves no job id or execution resource. A throwing starter
|
||||
* leaves nothing registered; after it returns, registration cannot fail.
|
||||
* @param spec - job identity, owner, output sources, and synchronous starter.
|
||||
* @returns the registry-issued `<kind>-N` id.
|
||||
* @returns the registry-issued `<kind>-<uuid>` id.
|
||||
*/
|
||||
abstract start(spec: JobSpec): JobId
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
*/
|
||||
|
||||
import type { SessionEventMap, SessionId } from '@deepseek-ai/dsh-session'
|
||||
import type { JobExecutionId, JobId } from './brand.ts'
|
||||
import type { JobId } from './brand.ts'
|
||||
import type { JobChannel, JobChunk, JobKind, JobView } from './view.ts'
|
||||
|
||||
export { JobId } from './brand.ts'
|
||||
@@ -22,9 +22,7 @@ export interface JobToolCall {
|
||||
|
||||
/** Durable identity and registration time of one owned job execution. */
|
||||
export interface JobRegisteredData {
|
||||
/** UUID unique to this execution, even when the registry reuses a display id. */
|
||||
readonly executionId: JobExecutionId
|
||||
/** Runtime-local id accepted by job controls. */
|
||||
/** Unique execution id accepted by job controls and retained in Session history. */
|
||||
readonly jobId: JobId
|
||||
/** Original owner; copied history does not transfer execution ownership. */
|
||||
readonly owner: SessionId
|
||||
@@ -60,7 +58,7 @@ declare module '@deepseek-ai/dsh-session/types' {
|
||||
*/
|
||||
'job/registered': JobRegisteredData
|
||||
/**
|
||||
* Log-only terminal record, paired by executionId rather than a reusable jobId.
|
||||
* Log-only terminal record, paired by the same unique jobId used for live control.
|
||||
* @param data - Registration facts plus settlement time, outcome, and producer acknowledgement.
|
||||
*/
|
||||
'job/settled': JobSettledData
|
||||
@@ -139,7 +137,7 @@ export interface JobAppendOptions {
|
||||
* throwing, so a producer's trailing flush cannot break its own teardown path.
|
||||
*/
|
||||
export interface JobHandle {
|
||||
/** The registry-issued id (`<kind>-N`). */
|
||||
/** The registry-issued id (`<kind>-<uuid>`). */
|
||||
readonly id: JobId
|
||||
/**
|
||||
* Append one chunk to the output ring. Offsets advance by the chunk's UTF-8
|
||||
|
||||
@@ -62,7 +62,7 @@ export interface JobChunk {
|
||||
* stream all consume this one shape.
|
||||
*/
|
||||
export interface JobView {
|
||||
/** The registry-issued id (`<kind>-N`). */
|
||||
/** The registry-issued id (`<kind>-<uuid>`). */
|
||||
readonly id: JobId
|
||||
/**
|
||||
* The producer kind the job was registered with: a Host-registered
|
||||
|
||||
@@ -39,8 +39,8 @@
|
||||
en: 6f006ff2d901bc92
|
||||
zh: 779d18ba7bec4202
|
||||
/deepseek-ai-dsh-tool-jobs/understand-the-implementation/output-capping:
|
||||
en: b47638fb0e80287a
|
||||
zh: c47fa4b99b6df4c3
|
||||
en: ca18e4bdfb12a1e2
|
||||
zh: 6592697c561c6e6d
|
||||
/deepseek-ai-dsh-tool-jobs/understand-the-implementation/notice-delivery-lanes:
|
||||
en: 2540b7abbb3c9710
|
||||
zh: 4fb355bceab2f865
|
||||
|
||||
@@ -87,7 +87,7 @@ This section explains the design decisions behind the tools and points at the co
|
||||
|
||||
### Output capping
|
||||
|
||||
`job_output` and `job_kill` capture the caller-visible job in a prepended pre-execute listener before policy runs, so the producer's cap applies to the complete rendered result. `job_output` preserves its output/status split when policy left the default rendering intact, bounding the output tail and the `[status: ...]` suffix; other single-text results — denials, short-circuits, normalized tool or pipeline failures, replacements, and blocks — are bounded as one text, while structured multi-block policy results keep their shape. A bounded completion notice reserves the stable `background job <id>` prefix and the `job_output` collection instruction before spending remaining bytes on variable kind, label, status, detail, and truncation marker, so the notice stays actionable at the PTY's supported 64-byte minimum; an existing producer truncation marker is reused rather than duplicated.
|
||||
`job_output` and `job_kill` capture the caller-visible job in a prepended pre-execute listener before policy runs, so the producer's cap applies to the complete rendered result. `job_output` preserves its output/status split when policy left the default rendering intact, bounding the output tail and the `[status: ...]` suffix; other single-text results — denials, short-circuits, normalized tool or pipeline failures, replacements, and blocks — are bounded as one text, while structured multi-block policy results keep their shape. A bounded completion notice retains the full job id and `job_output` action when both fit. Otherwise it directs the model to `job_list` when at least eight bytes fit; a smaller cap can carry only a prefix. The PTY's supported 64-byte minimum fits the complete `job_list` instruction. Existing producer truncation markers are reused.
|
||||
|
||||
### Notice delivery lanes
|
||||
|
||||
|
||||
@@ -87,7 +87,7 @@ kind: "package-reference"
|
||||
|
||||
### 输出上限
|
||||
|
||||
`job_output` 与 `job_kill` 会在策略运行前于前置的 pre-execute 监听器中捕获调用方可见任务,因此生产方上限适用于完整渲染结果。`job_output` 在策略保留默认渲染时保持其输出/状态拆分,对输出尾部与 `[status: ...]` 后缀设界;其他单文本结果——拒绝、短路、规范化工具或流水线失败、替换与阻止——按单个文本设界,而结构化多块策略结果保持自身形状。有界完成通知先为稳定的 `background job <id>` 前缀与 `job_output` 收集指令预留空间,再把剩余字节用于可变的 kind、label、status、detail 与截断标记,因此在 PTY 支持的 64 字节下限下通知仍可操作;已有的生产方截断标记会被复用,不会重复添加。
|
||||
`job_output` 与 `job_kill` 会在策略运行前于前置的 pre-execute 监听器中捕获调用方可见任务,因此生产方上限适用于完整渲染结果。`job_output` 在策略保留默认渲染时保持其输出/状态拆分,对输出尾部与 `[status: ...]` 后缀设界;其他单文本结果——拒绝、短路、规范化工具或流水线失败、替换与阻止——按单个文本设界,而结构化多块策略结果保持自身形状。有界完成通知在完整 job id 与 `job_output` 指令都能容纳时保留两者。否则当上限至少有八字节时,它提示模型使用 `job_list`;更小的上限只能容纳指令前缀。PTY 支持的 64 字节下限能容纳完整的 `job_list` 指令。已有生产方截断标记会被复用。
|
||||
|
||||
### 通知投递通道
|
||||
|
||||
|
||||
@@ -137,9 +137,10 @@ function fitCompletionNotice(job: JobView): string {
|
||||
const compact = `${prefix}${action}`
|
||||
const compactBytes = encoder.encode(compact).byteLength
|
||||
if (compactBytes <= maxBytes) return compact
|
||||
const actionBytes = encoder.encode(action).byteLength
|
||||
if (actionBytes >= maxBytes) return retainTail(action, maxBytes)
|
||||
return `${retainHead(prefix, maxBytes - actionBytes)}${action}`
|
||||
// A partial UUID cannot identify the job. Point the model at the listing instead.
|
||||
const recovery = 'background job ended; use job_list for id.'
|
||||
if (encoder.encode(recovery).byteLength <= maxBytes) return recovery
|
||||
return retainHead('job_list', maxBytes)
|
||||
}
|
||||
|
||||
function rawSingleText(content: readonly ContentBlock[]): string | undefined {
|
||||
|
||||
@@ -30,6 +30,17 @@ interface RegistryConfig {
|
||||
retainBytes?: number
|
||||
}
|
||||
|
||||
/** First-seen ids are test-local ordinals; the runtime id is opaque. */
|
||||
const issuedIds = new WeakMap<Context, Map<string, JobId[]>>()
|
||||
|
||||
function startedId(ctx: Context, legacy: string): JobId {
|
||||
const match = /^(.*)-([1-9]\d*)$/.exec(legacy)
|
||||
if (match === null) throw new Error(`invalid test job ordinal ${legacy}`)
|
||||
const id = issuedIds.get(ctx)?.get(match[1]!)?.[Number(match[2]) - 1]
|
||||
if (id === undefined) throw new Error(`job ${legacy} was not registered`)
|
||||
return id
|
||||
}
|
||||
|
||||
async function setup(config: ToolJobs.Config = {}, registry: RegistryConfig = {}) {
|
||||
const ctx = new Context()
|
||||
await ctx.plugin(SystemPrompt)
|
||||
@@ -37,6 +48,14 @@ async function setup(config: ToolJobs.Config = {}, registry: RegistryConfig = {}
|
||||
const agentsFiber = await ctx.plugin(AgentRegistry)
|
||||
await ctx.plugin(LocalJobRegistry, registry)
|
||||
const toolsFiber = await ctx.plugin(ToolJobs, config)
|
||||
const byKind = new Map<string, JobId[]>()
|
||||
issuedIds.set(ctx, byKind)
|
||||
ctx.effect(() => ctx.jobs.events.subscribe({ owners: 'all' }, (event) => {
|
||||
if (event.type !== 'registered') return
|
||||
const ids = byKind.get(event.job.kind) ?? []
|
||||
ids.push(event.job.id)
|
||||
byKind.set(event.job.kind, ids)
|
||||
}))
|
||||
return { ctx, agentsFiber, toolsFiber }
|
||||
}
|
||||
|
||||
@@ -151,12 +170,12 @@ describe('tool-jobs setup', () => {
|
||||
await ctx.plugin(LocalJobRegistry)
|
||||
await ctx.plugin(ToolJobs)
|
||||
const p = producer()
|
||||
ctx.jobs.start(p.spec)
|
||||
const id = ctx.jobs.start(p.spec)
|
||||
p.append('open\n')
|
||||
expect(text(await call(ctx, 'job_output', { job_id: 'bash-1' }))).toBe('open\n[status: running]')
|
||||
expect(text(await call(ctx, 'job_output', { job_id: id }))).toBe('open\n[status: running]')
|
||||
p.settle({ status: 'completed' })
|
||||
await tick()
|
||||
expect(text(await call(ctx, 'job_list', {}))).toBe('bash-1 [bash] completed — sleep 60')
|
||||
expect(text(await call(ctx, 'job_list', {}))).toBe(`${id} [bash] completed — sleep 60`)
|
||||
})
|
||||
|
||||
it('rejects a config whose default wait exceeds the cap', async () => {
|
||||
@@ -260,7 +279,7 @@ describe('tool-jobs setup', () => {
|
||||
? { text: 'x'.repeat(32), nextOffset: 32, lossy: false, spillPath: '/spill/out.log' }
|
||||
: { text: '', nextOffset: from, lossy: false, spillPath: '/spill/out.log' },
|
||||
}
|
||||
ctx.jobs.start({
|
||||
const id = ctx.jobs.start({
|
||||
kind: 'bash',
|
||||
label: 'noisy',
|
||||
owner: owner.id,
|
||||
@@ -268,7 +287,7 @@ describe('tool-jobs setup', () => {
|
||||
run: () => ({ cancel() {}, done: new Promise<JobOutcome>((resolve) => { settle = resolve }) }),
|
||||
})
|
||||
|
||||
expect(text(await call(ctx, 'job_output', { job_id: 'bash-1' }, owner)))
|
||||
expect(text(await call(ctx, 'job_output', { job_id: id }, owner)))
|
||||
.toBe(`${'x'.repeat(8)}\n[some output was dropped from memory; full output: /spill/out.log]\n[status: running]`)
|
||||
settle({ status: 'completed' })
|
||||
await tick()
|
||||
@@ -296,17 +315,17 @@ describe('job_output', () => {
|
||||
p.append('line one\n')
|
||||
|
||||
// A body already ending in a newline gets no doubled separator.
|
||||
const first = await call(ctx, 'job_output', { job_id: 'bash-1' })
|
||||
const first = await call(ctx, 'job_output', { job_id: startedId(ctx, 'bash-1') })
|
||||
if (first.isError) throw new Error('expected job_output success')
|
||||
const firstValue = first.value as { text: string; job: Record<string, unknown> }
|
||||
expect(firstValue).toMatchObject({
|
||||
text: 'line one\n',
|
||||
job: { id: 'bash-1', kind: 'bash', label: 'sleep 60', status: 'running' },
|
||||
job: { id: startedId(ctx, 'bash-1'), kind: 'bash', label: 'sleep 60', status: 'running' },
|
||||
})
|
||||
expect(firstValue.job).not.toHaveProperty('owner')
|
||||
expect(firstValue.job).not.toHaveProperty('output')
|
||||
expect(text(first)).toBe('line one\n[status: running]')
|
||||
expect(text(await call(ctx, 'job_output', { job_id: 'bash-1' }))).toBe('(no new output)\n[status: running]')
|
||||
expect(text(await call(ctx, 'job_output', { job_id: startedId(ctx, 'bash-1') }))).toBe('(no new output)\n[status: running]')
|
||||
})
|
||||
|
||||
it('renders stderr in a trailing section and never shows log narration', async () => {
|
||||
@@ -316,8 +335,8 @@ describe('job_output', () => {
|
||||
p.append('out\n')
|
||||
p.append('warn\n', { channel: 'stderr' })
|
||||
p.append('observer-only narration', { channel: 'log' })
|
||||
expect(text(await call(ctx, 'job_output', { job_id: 'bash-1' }))).toBe('out\n[stderr]\nwarn\n[status: running]')
|
||||
expect(text(await call(ctx, 'job_output', { job_id: 'bash-1' }))).toBe('(no new output)\n[status: running]')
|
||||
expect(text(await call(ctx, 'job_output', { job_id: startedId(ctx, 'bash-1') }))).toBe('out\n[stderr]\nwarn\n[status: running]')
|
||||
expect(text(await call(ctx, 'job_output', { job_id: startedId(ctx, 'bash-1') }))).toBe('(no new output)\n[status: running]')
|
||||
})
|
||||
|
||||
it('shows live progress beside the status until settlement replaces it with the terminal detail', async () => {
|
||||
@@ -325,36 +344,36 @@ describe('job_output', () => {
|
||||
const p = producer()
|
||||
ctx.jobs.start(p.spec)
|
||||
p.progress('3/10 files')
|
||||
expect(text(await call(ctx, 'job_output', { job_id: 'bash-1' }))).toBe('(no new output)\n[status: running, 3/10 files]')
|
||||
expect(text(await call(ctx, 'job_output', { job_id: startedId(ctx, 'bash-1') }))).toBe('(no new output)\n[status: running, 3/10 files]')
|
||||
p.settle({ status: 'completed', detail: 'exit code: 0' })
|
||||
await tick()
|
||||
expect(text(await call(ctx, 'job_output', { job_id: 'bash-1' }))).toBe('(no new output)\n[status: completed, exit code: 0]')
|
||||
expect(text(await call(ctx, 'job_output', { job_id: startedId(ctx, 'bash-1') }))).toBe('(no new output)\n[status: completed, exit code: 0]')
|
||||
})
|
||||
|
||||
it('flags a read whose cursor fell behind the ring', async () => {
|
||||
const { ctx } = await setup({}, { retainBytes: 16 })
|
||||
const p = producer()
|
||||
ctx.jobs.start(p.spec)
|
||||
const id = ctx.jobs.start(p.spec)
|
||||
p.append('x'.repeat(40))
|
||||
const first = text(await call(ctx, 'job_output', { job_id: 'bash-1' }))
|
||||
const first = text(await call(ctx, 'job_output', { job_id: startedId(ctx, 'bash-1') }))
|
||||
expect(first).toContain('[some output was dropped from memory; full output: (unavailable)]')
|
||||
expect(first).toContain('[status: running]')
|
||||
expect(first).not.toContain('x'.repeat(17))
|
||||
// The cursor now sits at the ring's end: the next read is clean.
|
||||
expect(text(await call(ctx, 'job_output', { job_id: 'bash-1' }))).toBe('(no new output)\n[status: running]')
|
||||
expect(text(await call(ctx, 'job_output', { job_id: id }))).toBe('(no new output)\n[status: running]')
|
||||
})
|
||||
|
||||
it('returns the result of a settled job exactly once, after the pending delta', async () => {
|
||||
const { ctx } = await setup()
|
||||
const p = producer({ kind: 'test-task', label: 'research' })
|
||||
ctx.jobs.start(p.spec)
|
||||
expect(text(await call(ctx, 'job_output', { job_id: 'test-task-1' }))).toBe('(no new output)\n[status: running]')
|
||||
expect(text(await call(ctx, 'job_output', { job_id: startedId(ctx, 'test-task-1') }))).toBe('(no new output)\n[status: running]')
|
||||
|
||||
p.append('partial')
|
||||
p.settle({ status: 'completed', detail: 'completed', result: 'the answer' })
|
||||
await tick()
|
||||
expect(text(await call(ctx, 'job_output', { job_id: 'test-task-1' }))).toBe('partial\nthe answer\n[status: completed, completed]')
|
||||
expect(text(await call(ctx, 'job_output', { job_id: 'test-task-1' }))).toBe('(no new output)\n[status: completed, completed]')
|
||||
expect(text(await call(ctx, 'job_output', { job_id: startedId(ctx, 'test-task-1') }))).toBe('partial\nthe answer\n[status: completed, completed]')
|
||||
expect(text(await call(ctx, 'job_output', { job_id: startedId(ctx, 'test-task-1') }))).toBe('(no new output)\n[status: completed, completed]')
|
||||
})
|
||||
|
||||
it('keeps a newline-terminated delta and the result on separate lines', async () => {
|
||||
@@ -364,7 +383,7 @@ describe('job_output', () => {
|
||||
p.append('progress log\n')
|
||||
p.settle({ status: 'completed', result: 'the answer' })
|
||||
await tick()
|
||||
expect(text(await call(ctx, 'job_output', { job_id: 'test-task-1' }))).toBe('progress log\nthe answer\n[status: completed]')
|
||||
expect(text(await call(ctx, 'job_output', { job_id: startedId(ctx, 'test-task-1') }))).toBe('progress log\nthe answer\n[status: completed]')
|
||||
})
|
||||
|
||||
it('applies a producer limit to the complete body and status result', async () => {
|
||||
@@ -373,7 +392,7 @@ describe('job_output', () => {
|
||||
ctx.jobs.start(p.spec)
|
||||
p.append('界'.repeat(100))
|
||||
|
||||
const output = text(await call(ctx, 'job_output', { job_id: 'bash-1' }))
|
||||
const output = text(await call(ctx, 'job_output', { job_id: startedId(ctx, 'bash-1') }))
|
||||
expect(Buffer.byteLength(output)).toBeLessThanOrEqual(48)
|
||||
expect(output).toContain('[status: running]')
|
||||
})
|
||||
@@ -383,10 +402,10 @@ describe('job_output', () => {
|
||||
const p = producer({ outputLimitBytes: 64 })
|
||||
ctx.jobs.start(p.spec)
|
||||
|
||||
expect(text(await call(ctx, 'job_output', { job_id: 'bash-1' })))
|
||||
expect(text(await call(ctx, 'job_output', { job_id: startedId(ctx, 'bash-1') })))
|
||||
.toBe('(no new output)\n[status: running]')
|
||||
p.append('line\n')
|
||||
expect(text(await call(ctx, 'job_output', { job_id: 'bash-1' })))
|
||||
expect(text(await call(ctx, 'job_output', { job_id: startedId(ctx, 'bash-1') })))
|
||||
.toBe('line\n[status: running]')
|
||||
})
|
||||
|
||||
@@ -400,7 +419,7 @@ describe('job_output', () => {
|
||||
return Promise.resolve({ kind: 'accept', content: [{ type: 'text', text: 'p'.repeat(1_000) }] })
|
||||
})
|
||||
|
||||
const result = await call(ctx, 'job_output', { job_id: 'bash-1' })
|
||||
const result = await call(ctx, 'job_output', { job_id: startedId(ctx, 'bash-1') })
|
||||
expect(Buffer.byteLength(text(result))).toBeLessThanOrEqual(64)
|
||||
expect(text(result)).toContain('[result truncated]')
|
||||
expect(text(result)).not.toContain('[status: running]')
|
||||
@@ -414,7 +433,7 @@ describe('job_output', () => {
|
||||
return Promise.resolve({ kind: 'accept', content: [{ type: 'text', text: `${'z'.repeat(1_000)}\n[result truncated]` }] })
|
||||
})
|
||||
|
||||
const result = await call(ctx, 'job_output', { job_id: 'bash-1' })
|
||||
const result = await call(ctx, 'job_output', { job_id: startedId(ctx, 'bash-1') })
|
||||
expect(Buffer.byteLength(text(result))).toBeLessThanOrEqual(64)
|
||||
expect(text(result).match(/\[result truncated\]/g)).toHaveLength(1)
|
||||
})
|
||||
@@ -425,11 +444,11 @@ describe('job_output', () => {
|
||||
ctx.jobs.start(producer({ owner: alice.id }).spec)
|
||||
const bob = await fakeAgent(ctx, 'sess-bob')
|
||||
|
||||
const result = await call(ctx, 'job_output', { job_id: 'bash-1' }, bob)
|
||||
const result = await call(ctx, 'job_output', { job_id: startedId(ctx, 'bash-1') }, bob)
|
||||
expect(result.isError).toBe(true)
|
||||
expect(text(result)).toContain('belongs to another session')
|
||||
expect((await call(ctx, 'job_output', { job_id: 'bash-1' })).isError).toBe(true)
|
||||
expect((await call(ctx, 'job_output', { job_id: 'bash-1' }, alice)).isError).toBe(false)
|
||||
expect((await call(ctx, 'job_output', { job_id: startedId(ctx, 'bash-1') })).isError).toBe(true)
|
||||
expect((await call(ctx, 'job_output', { job_id: startedId(ctx, 'bash-1') }, alice)).isError).toBe(false)
|
||||
})
|
||||
|
||||
it('bounds pre-, around-, and post-execute policy outcomes and failures', async () => {
|
||||
@@ -439,47 +458,47 @@ describe('job_output', () => {
|
||||
}
|
||||
ctx.on('tools/pre-execute', async (exec, next) => {
|
||||
const jobId = (exec.arguments as { job_id?: unknown }).job_id
|
||||
if (jobId === 'bash-1') return { kind: 'deny', reason: 'd'.repeat(1_000) }
|
||||
if (jobId === 'bash-3') throw new Error(`pre failed: ${'p'.repeat(1_000)}`)
|
||||
if (jobId === startedId(ctx, 'bash-1')) return { kind: 'deny', reason: 'd'.repeat(1_000) }
|
||||
if (jobId === startedId(ctx, 'bash-3')) throw new Error(`pre failed: ${'p'.repeat(1_000)}`)
|
||||
return next()
|
||||
})
|
||||
ctx.on('tools/execute', async (exec, next) => {
|
||||
const jobId = (exec.arguments as { job_id?: unknown }).job_id
|
||||
if (jobId === 'bash-2') {
|
||||
if (jobId === startedId(ctx, 'bash-2')) {
|
||||
return {
|
||||
content: [],
|
||||
isError: false,
|
||||
value: {
|
||||
text: 'a'.repeat(1_000),
|
||||
job: {
|
||||
id: 'bash-2', kind: 'bash', label: 'sleep 60', status: 'running', startedAt: 0,
|
||||
id: startedId(ctx, 'bash-2'), kind: 'bash', label: 'sleep 60', status: 'running', startedAt: 0,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
if (jobId === 'bash-4') throw new Error(`around failed: ${'e'.repeat(1_000)}`)
|
||||
if (jobId === startedId(ctx, 'bash-4')) throw new Error(`around failed: ${'e'.repeat(1_000)}`)
|
||||
return next()
|
||||
})
|
||||
ctx.on('tools/post-execute', async (exec, _result, next) => {
|
||||
const jobId = (exec.arguments as { job_id?: unknown }).job_id
|
||||
if (jobId === 'bash-5') throw new Error(`post failed: ${'o'.repeat(1_000)}`)
|
||||
if (jobId === startedId(ctx, 'bash-5')) throw new Error(`post failed: ${'o'.repeat(1_000)}`)
|
||||
return next()
|
||||
})
|
||||
|
||||
const denied = await call(ctx, 'job_output', { job_id: 'bash-1' })
|
||||
const denied = await call(ctx, 'job_output', { job_id: startedId(ctx, 'bash-1') })
|
||||
expect(denied.isError).toBe(true)
|
||||
expect(Buffer.byteLength(text(denied))).toBeLessThanOrEqual(64)
|
||||
expect(text(denied)).toContain('[result truncated]')
|
||||
|
||||
const shortCircuited = await call(ctx, 'job_output', { job_id: 'bash-2' })
|
||||
const shortCircuited = await call(ctx, 'job_output', { job_id: startedId(ctx, 'bash-2') })
|
||||
expect(shortCircuited.isError).toBe(false)
|
||||
expect(Buffer.byteLength(text(shortCircuited))).toBeLessThanOrEqual(64)
|
||||
expect(text(shortCircuited)).toContain('[output truncated]')
|
||||
|
||||
const failures = [
|
||||
await call(ctx, 'job_output', { job_id: 'bash-3' }),
|
||||
await call(ctx, 'job_output', { job_id: 'bash-4' }),
|
||||
await call(ctx, 'job_output', { job_id: 'bash-5' }),
|
||||
await call(ctx, 'job_output', { job_id: startedId(ctx, 'bash-3') }),
|
||||
await call(ctx, 'job_output', { job_id: startedId(ctx, 'bash-4') }),
|
||||
await call(ctx, 'job_output', { job_id: startedId(ctx, 'bash-5') }),
|
||||
]
|
||||
for (const failure of failures) {
|
||||
expect(failure.isError).toBe(true)
|
||||
@@ -493,7 +512,7 @@ describe('job_output', () => {
|
||||
const p = producer({ kind: 'test-task', label: 'research' })
|
||||
ctx.jobs.start(p.spec)
|
||||
|
||||
const pending = call(ctx, 'job_output', { job_id: 'test-task-1', wait: true })
|
||||
const pending = call(ctx, 'job_output', { job_id: startedId(ctx, 'test-task-1'), wait: true })
|
||||
p.settle({ status: 'completed', result: 'done deal' })
|
||||
expect(text(await pending)).toBe('done deal\n[status: completed]')
|
||||
})
|
||||
@@ -504,7 +523,7 @@ describe('job_output', () => {
|
||||
|
||||
// A model-supplied timeout far above the cap is clamped: this returns
|
||||
// promptly (≤ the 20ms cap), not after ten minutes.
|
||||
const result = await call(ctx, 'job_output', { job_id: 'bash-1', wait: true, timeout_ms: 600_000 })
|
||||
const result = await call(ctx, 'job_output', { job_id: startedId(ctx, 'bash-1'), wait: true, timeout_ms: 600_000 })
|
||||
expect(text(result)).toBe('(no new output)\n[status: running]')
|
||||
})
|
||||
|
||||
@@ -534,20 +553,20 @@ describe('job_list', () => {
|
||||
if (listed.isError) throw new Error('expected job_list success')
|
||||
const listedValue = listed.value as Array<Record<string, unknown>>
|
||||
expect(listedValue).toHaveLength(3)
|
||||
expect(listedValue[0]).toMatchObject({ id: 'bash-1', kind: 'bash', label: 'pnpm test', status: 'running' })
|
||||
expect(listedValue[2]).toMatchObject({ id: 'bash-2', kind: 'bash', label: 'build', status: 'completed', detail: 'exit code: 0' })
|
||||
expect(listedValue[0]).toMatchObject({ id: startedId(ctx, 'bash-1'), kind: 'bash', label: 'pnpm test', status: 'running' })
|
||||
expect(listedValue[2]).toMatchObject({ id: startedId(ctx, 'bash-2'), kind: 'bash', label: 'build', status: 'completed', detail: 'exit code: 0' })
|
||||
for (const job of listedValue) {
|
||||
expect(job).not.toHaveProperty('owner')
|
||||
expect(job).not.toHaveProperty('output')
|
||||
}
|
||||
expect(text(listed)).toBe([
|
||||
'bash-1 [bash] running — pnpm test',
|
||||
'test-task-1 [test-task] running — open research',
|
||||
'bash-2 [bash] completed — build',
|
||||
`${startedId(ctx, 'bash-1')} [bash] running — pnpm test`,
|
||||
`${startedId(ctx, 'test-task-1')} [test-task] running — open research`,
|
||||
`${startedId(ctx, 'bash-2')} [bash] completed — build`,
|
||||
].join('\n'))
|
||||
// A different caller sees only the unowned job.
|
||||
const bob = await fakeAgent(ctx, 'sess-bob')
|
||||
expect(text(await call(ctx, 'job_list', {}, bob))).toBe('test-task-1 [test-task] running — open research')
|
||||
expect(text(await call(ctx, 'job_list', {}, bob))).toBe(`${startedId(ctx, 'test-task-1')} [test-task] running — open research`)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -557,16 +576,16 @@ describe('job_kill', () => {
|
||||
const p = producer()
|
||||
ctx.jobs.start(p.spec)
|
||||
|
||||
const result = await call(ctx, 'job_kill', { job_id: 'bash-1', reason: 'superseded' })
|
||||
const result = await call(ctx, 'job_kill', { job_id: startedId(ctx, 'bash-1'), reason: 'superseded' })
|
||||
if (result.isError) throw new Error('expected job_kill success')
|
||||
const killValue = result.value as { outcome: string; job: Record<string, unknown> }
|
||||
expect(killValue).toMatchObject({
|
||||
outcome: 'cancellation-requested',
|
||||
job: { id: 'bash-1', kind: 'bash', label: 'sleep 60', status: 'stopping' },
|
||||
job: { id: startedId(ctx, 'bash-1'), kind: 'bash', label: 'sleep 60', status: 'stopping' },
|
||||
})
|
||||
expect(killValue.job).not.toHaveProperty('owner')
|
||||
expect(killValue.job).not.toHaveProperty('output')
|
||||
expect(text(result)).toBe('requested cancellation of job bash-1')
|
||||
expect(text(result)).toBe(`requested cancellation of job ${startedId(ctx, 'bash-1')}`)
|
||||
expect(p.cancels).toEqual(['superseded'])
|
||||
})
|
||||
|
||||
@@ -575,7 +594,7 @@ describe('job_kill', () => {
|
||||
const p = producer({ outputLimitBytes: 8 })
|
||||
ctx.jobs.start(p.spec)
|
||||
|
||||
const result = await call(ctx, 'job_kill', { job_id: 'bash-1' })
|
||||
const result = await call(ctx, 'job_kill', { job_id: startedId(ctx, 'bash-1') })
|
||||
expect(Buffer.byteLength(text(result))).toBeLessThanOrEqual(8)
|
||||
expect(p.cancels).toEqual([undefined])
|
||||
})
|
||||
@@ -587,11 +606,11 @@ describe('job_kill', () => {
|
||||
cancel: () => { throw new Error('cancel failed: '.repeat(100)) },
|
||||
}).spec)
|
||||
|
||||
const result = await call(ctx, 'job_kill', { job_id: 'bash-1' })
|
||||
const result = await call(ctx, 'job_kill', { job_id: startedId(ctx, 'bash-1') })
|
||||
expect(result.isError).toBe(true)
|
||||
expect(Buffer.byteLength(text(result))).toBeLessThanOrEqual(64)
|
||||
expect(text(result)).toContain('[result truncated]')
|
||||
expect(ctx.jobs.get(JobId('bash-1')).status).toBe('running')
|
||||
expect(ctx.jobs.get(JobId(startedId(ctx, 'bash-1'))).status).toBe('running')
|
||||
})
|
||||
|
||||
it('bounds single-text post policy while preserving structured policy results', async () => {
|
||||
@@ -620,20 +639,20 @@ describe('job_kill', () => {
|
||||
ctx.jobs.start(producer({ outputLimitBytes: 64 }).spec)
|
||||
}
|
||||
|
||||
const replaced = await call(ctx, 'job_kill', { job_id: 'bash-1', reason: 'replace' })
|
||||
const replaced = await call(ctx, 'job_kill', { job_id: startedId(ctx, 'bash-1'), reason: 'replace' })
|
||||
expect(replaced.isError).toBe(false)
|
||||
expect(Buffer.byteLength(text(replaced))).toBeLessThanOrEqual(64)
|
||||
expect(text(replaced)).toContain('[result truncated]')
|
||||
|
||||
const blocked = await call(ctx, 'job_kill', { job_id: 'bash-2', reason: 'block' })
|
||||
const blocked = await call(ctx, 'job_kill', { job_id: startedId(ctx, 'bash-2'), reason: 'block' })
|
||||
expect(blocked.isError).toBe(true)
|
||||
expect(Buffer.byteLength(text(blocked))).toBeLessThanOrEqual(64)
|
||||
expect(text(blocked)).toContain('[result truncated]')
|
||||
|
||||
const multi = await call(ctx, 'job_kill', { job_id: 'bash-3', reason: 'multi' })
|
||||
const multi = await call(ctx, 'job_kill', { job_id: startedId(ctx, 'bash-3'), reason: 'multi' })
|
||||
expect(multi.content).toEqual([{ type: 'text', text: 'first' }, { type: 'text', text: 'second' }])
|
||||
|
||||
const reasoning = await call(ctx, 'job_kill', { job_id: 'bash-4', reason: 'reasoning' })
|
||||
const reasoning = await call(ctx, 'job_kill', { job_id: startedId(ctx, 'bash-4'), reason: 'reasoning' })
|
||||
expect(reasoning.content).toEqual([{ type: 'reasoning', text: 'policy detail' }])
|
||||
})
|
||||
|
||||
@@ -645,15 +664,15 @@ describe('job_kill', () => {
|
||||
p.settle({ status: 'completed', detail: 'exit code: 0' })
|
||||
await tick()
|
||||
|
||||
const killed = await call(ctx, 'job_kill', { job_id: 'bash-1' })
|
||||
const killed = await call(ctx, 'job_kill', { job_id: startedId(ctx, 'bash-1') })
|
||||
if (killed.isError) throw new Error('expected job_kill success')
|
||||
expect(killed.value).toMatchObject({
|
||||
outcome: 'already-finished',
|
||||
job: { id: 'bash-1', kind: 'bash', label: 'sleep 60', status: 'completed', detail: 'exit code: 0' },
|
||||
job: { id: startedId(ctx, 'bash-1'), kind: 'bash', label: 'sleep 60', status: 'completed', detail: 'exit code: 0' },
|
||||
})
|
||||
expect(text(killed)).toBe('job bash-1 had already finished [status: completed, exit code: 0]')
|
||||
expect(text(killed)).toBe(`job ${startedId(ctx, 'bash-1')} had already finished [status: completed, exit code: 0]`)
|
||||
// The kill described the job via a non-consuming projection: the delta is intact.
|
||||
expect(text(await call(ctx, 'job_output', { job_id: 'bash-1' }))).toBe('unread tail\n[status: completed, exit code: 0]')
|
||||
expect(text(await call(ctx, 'job_output', { job_id: startedId(ctx, 'bash-1') }))).toBe('unread tail\n[status: completed, exit code: 0]')
|
||||
})
|
||||
|
||||
it('rejects an empty job id as an errored result', async () => {
|
||||
@@ -750,7 +769,7 @@ describe('completion notice delivery', () => {
|
||||
expect(inject).toHaveBeenCalledTimes(1)
|
||||
const message = inject.mock.calls[0]![0] as { content: readonly { type: string; text: string }[] }
|
||||
expect(message.content[0]!.text).toBe(
|
||||
'background job bash-1 (bash: pnpm run watch) finished '
|
||||
`background job ${startedId(ctx, 'bash-1')} (bash: pnpm run watch) finished `
|
||||
+ '[status: killed, signal: SIGTERM; cancelled by the user]. Read its output with job_output.',
|
||||
)
|
||||
})
|
||||
@@ -894,7 +913,7 @@ describe('completion notices', () => {
|
||||
expect(inject).toHaveBeenCalledWith({
|
||||
id: expect.any(String) as unknown,
|
||||
role: 'user',
|
||||
content: [{ type: 'text', text: 'background job bash-1 (bash: pnpm test) finished [status: completed, exit code: 0]. Read its output with job_output.' }],
|
||||
content: [{ type: 'text', text: `background job ${startedId(ctx, 'bash-1')} (bash: pnpm test) finished [status: completed, exit code: 0]. Read its output with job_output.` }],
|
||||
source: {
|
||||
kind: 'tool-jobs',
|
||||
form: 'notice',
|
||||
@@ -903,7 +922,7 @@ describe('completion notices', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('preserves job ids and collection guidance in bounded completion notices', async () => {
|
||||
it('keeps collection guidance when a bounded notice cannot carry the id', async () => {
|
||||
const { ctx } = await setup()
|
||||
const inject = vi.fn()
|
||||
const owner = await fakeAgent(ctx, 'sess-1', { inject })
|
||||
@@ -922,7 +941,7 @@ describe('completion notices', () => {
|
||||
{
|
||||
id: expect.any(String) as unknown,
|
||||
role: 'user',
|
||||
content: [{ type: 'text', text: 'background job test-task-1\nDone; job_output.' }],
|
||||
content: [{ type: 'text', text: 'background job ended; use job_list for id.' }],
|
||||
// The label and status detail are unbounded caller text, so the durable
|
||||
// one-line account caps itself rather than committing their full length.
|
||||
source: {
|
||||
@@ -946,11 +965,10 @@ describe('completion notices', () => {
|
||||
const content = (inject.mock.calls[1]?.[0] as { content?: Array<{ type: string; text?: string }> } | undefined)?.content
|
||||
const notice = content?.[0]?.text ?? ''
|
||||
expect(Buffer.byteLength(notice)).toBeLessThanOrEqual(80)
|
||||
expect(notice).toContain('background job test-task-2 (test-task: xxxx')
|
||||
expect(notice).toContain('[notice truncated]\nDone; job_output.')
|
||||
expect(notice).toBe(`background job ${startedId(ctx, 'test-task-2')}\nDone; job_output.`)
|
||||
})
|
||||
|
||||
it('keeps the complete PTY job id and collection action at the minimum PTY limit', async () => {
|
||||
it('guides collection through job_list when the PTY id exceeds its output limit', async () => {
|
||||
const { ctx } = await setup()
|
||||
for (let index = 0; index < 99; index += 1) {
|
||||
const prior = producer({ kind: 'pty-send' })
|
||||
@@ -974,28 +992,50 @@ describe('completion notices', () => {
|
||||
const content = (inject.mock.calls[0]?.[0] as { content?: Array<{ type: string; text?: string }> } | undefined)?.content
|
||||
const notice = content?.[0]?.text ?? ''
|
||||
expect(Buffer.byteLength(notice)).toBeLessThanOrEqual(64)
|
||||
expect(notice).toBe('background job pty-send-100\n[notice truncated]\nDone; job_output.')
|
||||
expect(notice).toBe('background job ended; use job_list for id.')
|
||||
})
|
||||
|
||||
it('reserves the collection-action tail when a producer supplies a smaller budget', async () => {
|
||||
it('keeps a complete job id at the exact notice boundary and uses spare bytes for context', async () => {
|
||||
const { ctx } = await setup()
|
||||
const inject = vi.fn()
|
||||
const owner = await fakeAgent(ctx, 'notice-boundary', { inject })
|
||||
const fixedBytes = Buffer.byteLength(`background job test-task-${'0'.repeat(36)}\n[notice truncated]\nDone; job_output.`)
|
||||
for (const [index, extra] of ['', ' (t'].entries()) {
|
||||
const p = producer({ owner: owner.id, kind: 'test-task', label: 'x'.repeat(1_000), outputLimitBytes: fixedBytes + extra.length })
|
||||
const id = ctx.jobs.start(p.spec)
|
||||
expect(id).toMatch(/^test-task-[0-9a-f-]{36}$/)
|
||||
p.settle({ status: 'completed' })
|
||||
await tick()
|
||||
const notice = (inject.mock.calls[index]?.[0] as { content?: Array<{ text?: string }> } | undefined)?.content?.[0]?.text
|
||||
expect(notice).toBe(`background job ${id}${extra}\n[notice truncated]\nDone; job_output.`)
|
||||
expect(Buffer.byteLength(notice ?? '')).toBe(fixedBytes + extra.length)
|
||||
}
|
||||
})
|
||||
|
||||
it('keeps a complete job_list action when the id exceeds a smaller budget', async () => {
|
||||
const { ctx } = await setup()
|
||||
const inject = vi.fn()
|
||||
const owner = await fakeAgent(ctx, 'sess-1', { inject })
|
||||
const tiny = producer({ owner: owner.id, kind: 'pty-send', label: 'x'.repeat(100), outputLimitBytes: 8 })
|
||||
const short = producer({ owner: owner.id, kind: 'pty-send', label: 'x'.repeat(100), outputLimitBytes: 32 })
|
||||
const minimal = producer({ owner: owner.id, kind: 'pty-send', label: 'x'.repeat(100), outputLimitBytes: 3 })
|
||||
ctx.jobs.start(tiny.spec)
|
||||
ctx.jobs.start(short.spec)
|
||||
ctx.jobs.start(minimal.spec)
|
||||
|
||||
tiny.settle({ status: 'completed' })
|
||||
short.settle({ status: 'completed' })
|
||||
minimal.settle({ status: 'completed' })
|
||||
await tick()
|
||||
|
||||
const tinyNotice = (inject.mock.calls[0]?.[0] as { content?: Array<{ text?: string }> } | undefined)?.content?.[0]?.text ?? ''
|
||||
const shortNotice = (inject.mock.calls[1]?.[0] as { content?: Array<{ text?: string }> } | undefined)?.content?.[0]?.text ?? ''
|
||||
expect(Buffer.byteLength(tinyNotice)).toBeLessThanOrEqual(8)
|
||||
expect(tinyNotice).toBe('_output.')
|
||||
expect(tinyNotice).toBe('job_list')
|
||||
expect(Buffer.byteLength(shortNotice)).toBeLessThanOrEqual(32)
|
||||
expect(shortNotice).toBe('background job\nDone; job_output.')
|
||||
expect(shortNotice).toBe('job_list')
|
||||
const minimalNotice = (inject.mock.calls[2]?.[0] as { content?: Array<{ text?: string }> } | undefined)?.content?.[0]?.text ?? ''
|
||||
expect(minimalNotice).toBe('job')
|
||||
})
|
||||
|
||||
it('suppresses the notice for a job the model already killed', async () => {
|
||||
@@ -1005,7 +1045,7 @@ describe('completion notices', () => {
|
||||
const p = producer({ owner: owner.id })
|
||||
ctx.jobs.start(p.spec)
|
||||
|
||||
await call(ctx, 'job_kill', { job_id: 'bash-1' }, owner)
|
||||
await call(ctx, 'job_kill', { job_id: startedId(ctx, 'bash-1') }, owner)
|
||||
p.settle({ status: 'killed' })
|
||||
await tick()
|
||||
expect(inject).not.toHaveBeenCalled()
|
||||
@@ -1018,7 +1058,7 @@ describe('completion notices', () => {
|
||||
const p = producer({ owner: owner.id, kind: 'test-task' })
|
||||
ctx.jobs.start(p.spec)
|
||||
|
||||
const pending = call(ctx, 'job_output', { job_id: 'test-task-1', wait: true }, owner)
|
||||
const pending = call(ctx, 'job_output', { job_id: startedId(ctx, 'test-task-1'), wait: true }, owner)
|
||||
p.settle({ status: 'completed', result: 'answer' })
|
||||
expect(text(await pending)).toContain('answer')
|
||||
await tick()
|
||||
@@ -1032,8 +1072,8 @@ describe('completion notices', () => {
|
||||
const p = producer({ owner: owner.id })
|
||||
ctx.jobs.start(p.spec)
|
||||
|
||||
const patient = call(ctx, 'job_output', { job_id: 'bash-1', wait: true, timeout_ms: 1000 }, owner)
|
||||
expect(text(await call(ctx, 'job_output', { job_id: 'bash-1', wait: true, timeout_ms: 10 }, owner))).toBe('(no new output)\n[status: running]')
|
||||
const patient = call(ctx, 'job_output', { job_id: startedId(ctx, 'bash-1'), wait: true, timeout_ms: 1000 }, owner)
|
||||
expect(text(await call(ctx, 'job_output', { job_id: startedId(ctx, 'bash-1'), wait: true, timeout_ms: 10 }, owner))).toBe('(no new output)\n[status: running]')
|
||||
p.settle({ status: 'completed' })
|
||||
expect(text(await patient)).toBe('(no new output)\n[status: completed]')
|
||||
await tick()
|
||||
@@ -1047,7 +1087,7 @@ describe('completion notices', () => {
|
||||
const p = producer({ owner: owner.id })
|
||||
ctx.jobs.start(p.spec)
|
||||
|
||||
expect(text(await call(ctx, 'job_output', { job_id: 'bash-1', wait: true }, owner))).toBe('(no new output)\n[status: running]')
|
||||
expect(text(await call(ctx, 'job_output', { job_id: startedId(ctx, 'bash-1'), wait: true }, owner))).toBe('(no new output)\n[status: running]')
|
||||
p.settle({ status: 'completed' })
|
||||
await tick()
|
||||
expect(inject).toHaveBeenCalledTimes(1)
|
||||
@@ -1065,7 +1105,7 @@ describe('completion notices', () => {
|
||||
signal: aborter.signal,
|
||||
callId: ToolCallId('call-aborted-wait'),
|
||||
name: 'job_output',
|
||||
arguments: { job_id: 'bash-1', wait: true },
|
||||
arguments: { job_id: startedId(ctx, 'bash-1'), wait: true },
|
||||
agent: owner,
|
||||
})
|
||||
await tick()
|
||||
@@ -1091,7 +1131,7 @@ describe('completion notices', () => {
|
||||
signal: aborter.signal,
|
||||
callId: ToolCallId('call-abort-then-settle'),
|
||||
name: 'job_output',
|
||||
arguments: { job_id: 'test-task-1', wait: true },
|
||||
arguments: { job_id: startedId(ctx, 'test-task-1'), wait: true },
|
||||
agent: owner,
|
||||
})
|
||||
await tick()
|
||||
@@ -1117,7 +1157,7 @@ describe('completion notices', () => {
|
||||
ctx.jobs.start(killed.spec)
|
||||
ctx.jobs.start(other.spec)
|
||||
|
||||
await call(ctx, 'job_kill', { job_id: 'bash-1' }, owner)
|
||||
await call(ctx, 'job_kill', { job_id: startedId(ctx, 'bash-1') }, owner)
|
||||
other.settle({ status: 'completed' })
|
||||
await tick()
|
||||
expect(inject).toHaveBeenCalledTimes(1)
|
||||
@@ -1153,7 +1193,7 @@ describe('completion notices', () => {
|
||||
|
||||
expect(oldInject).not.toHaveBeenCalled()
|
||||
expect(replacementInject).toHaveBeenCalledTimes(1)
|
||||
expect(text(await call(ctx, 'job_output', { job_id: 'bash-1' }, replacement))).toBe('(no new output)\n[status: completed]')
|
||||
expect(text(await call(ctx, 'job_output', { job_id: startedId(ctx, 'bash-1') }, replacement))).toBe('(no new output)\n[status: completed]')
|
||||
})
|
||||
|
||||
it('drops the notice when the agent registry left before settlement', async () => {
|
||||
|
||||
@@ -122,7 +122,7 @@ describe('background bash output', () => {
|
||||
workdir: '/nonexistent-dsh',
|
||||
run_in_background: true,
|
||||
})
|
||||
expect(text(started)).toMatch(/^started background job bash-\d+$/)
|
||||
expect(text(started)).toMatch(/^started background job bash-[0-9a-f-]{36}$/)
|
||||
const jobs = ctx.jobs
|
||||
const job = jobs.list()[0]
|
||||
await until(() => jobs.get(job!.id).status === 'killed' ? true : undefined)
|
||||
@@ -217,19 +217,21 @@ describe('foreground commands as jobs', () => {
|
||||
})
|
||||
await until(() => execution?.observed.stdout.readFrom(0).text.includes('early-output') ? true : undefined, task.timeout)
|
||||
// The job exists from the start: the command is listed while the call still waits.
|
||||
expect(ctx.jobs.list()[0]).toMatchObject({ id: 'bash-1', kind: 'bash', status: 'running', label: 'printf "early-output\\n"; sleep 30' })
|
||||
const jobId = ctx.jobs.list()[0]?.id
|
||||
expect(jobId).toMatch(/^bash-[0-9a-f-]{36}$/)
|
||||
expect(ctx.jobs.list()[0]).toMatchObject({ id: jobId, kind: 'bash', status: 'running', label: 'printf "early-output\\n"; sleep 30' })
|
||||
await vi.advanceTimersByTimeAsync(250)
|
||||
const result = await pending
|
||||
vi.useRealTimers()
|
||||
const body = text(result)
|
||||
expect(body).toContain('early-output')
|
||||
expect(body).toContain('[still running after 250ms; moved to background job bash-1]')
|
||||
expect(body).toContain(`[still running after 250ms; moved to background job ${jobId}]`)
|
||||
expect(body).toContain('read newer output with job_output, stop it with job_kill')
|
||||
expect(body.indexOf('early-output')).toBeLessThan(body.indexOf('[still running'))
|
||||
|
||||
const jobs = ctx.jobs
|
||||
const job = jobs.list()[0]
|
||||
expect(job).toMatchObject({ id: 'bash-1', status: 'running' })
|
||||
expect(job).toMatchObject({ id: jobId, status: 'running' })
|
||||
// The hand-over was one consuming read: the next read repeats nothing.
|
||||
expect(jobs.read(job!.id).chunks).toEqual([])
|
||||
expect(jobs.kill(job!.id, undefined, 'test cleanup')).toBe('requested')
|
||||
|
||||
@@ -218,8 +218,7 @@ describe('bash tool through the agent loop', () => {
|
||||
const dir = mkdtempSync(join(tmpdir(), 'dsh-bg-'))
|
||||
dirs.push(dir)
|
||||
const sentinel = join(dir, 'release')
|
||||
// The job id is deterministic (a fresh LocalJobRegistry counts per kind from 1),
|
||||
// so the script can name `bash-1` without threading a generated id.
|
||||
// The next model call reads the id from the prior tool result.
|
||||
const adapter = new MockAdapter([
|
||||
toolCallResponse('call-1', 'bash', {
|
||||
command: `while [ ! -f ${JSON.stringify(sentinel)} ]; do sleep 0.02; done; echo bg-ok`,
|
||||
@@ -227,7 +226,11 @@ describe('bash tool through the agent loop', () => {
|
||||
run_in_background: true,
|
||||
}),
|
||||
textResponse('Started it in the background.'),
|
||||
toolCallResponse('call-2', 'job_output', { job_id: 'bash-1' }),
|
||||
(options) => {
|
||||
const jobId = /started background job (bash-[0-9a-f-]{36})/.exec(JSON.stringify(options.messages))?.[1]
|
||||
if (jobId === undefined) throw new Error('model history lacks the started job id')
|
||||
return toolCallResponse('call-2', 'job_output', { job_id: jobId })
|
||||
},
|
||||
textResponse('Background job finished.'),
|
||||
])
|
||||
const ctx = await harness(adapter)
|
||||
@@ -238,7 +241,8 @@ describe('bash tool through the agent loop', () => {
|
||||
|
||||
const firstResult = findEvent(events(agent), 'tool/result')
|
||||
expect(firstResult.data.message.isError).toBe(false)
|
||||
expect(resultText(firstResult)).toBe('started background job bash-1')
|
||||
const jobId = /^started background job (bash-[0-9a-f-]{36})$/.exec(resultText(firstResult))?.[1]
|
||||
if (jobId === undefined) throw new Error('first tool result lacks a job id')
|
||||
// The turn closed with the task still running, so the notice cannot exist yet.
|
||||
const isNotice = (e: SessionEvent): e is SessionEvent<'user/message'> =>
|
||||
e.type === 'user/message' && e.data.source.kind === 'tool-jobs'
|
||||
@@ -261,7 +265,7 @@ describe('bash tool through the agent loop', () => {
|
||||
const notice = events(agent).find(isNotice)!
|
||||
const noticeText = notice.data.content
|
||||
.filter(block => block.type === 'text').map(block => block.text).join('')
|
||||
expect(noticeText).toContain('background job bash-1 (bash: ')
|
||||
expect(noticeText).toContain(`background job ${jobId} (bash: `)
|
||||
expect(noticeText).toContain('finished [status: completed, exit code: 0]')
|
||||
expect(notice.data.source).toMatchObject({
|
||||
kind: 'tool-jobs',
|
||||
|
||||
@@ -27,6 +27,13 @@ import * as BashEnvPlugin from '@deepseek-ai/dsh-shell-env'
|
||||
import { processOutcome } from '../src/background.ts'
|
||||
import { renderJobRead, renderResult } from '../src/render.ts'
|
||||
|
||||
/** Use the newest id returned by the real registry, including settled jobs. */
|
||||
function currentId(ctx: Context, owner?: Agent): string {
|
||||
const id = ctx.jobs.list(owner?.id).at(-1)?.id
|
||||
if (id === undefined) throw new Error('background job was not registered')
|
||||
return id
|
||||
}
|
||||
|
||||
/** Empty offset readers for fakes that never produce output. */
|
||||
const silentReader = { readFrom: (fromByte: number) => ({ text: '', nextOffset: fromByte, lossy: false }) }
|
||||
|
||||
@@ -539,8 +546,8 @@ describe('the background surface follows the job registry', () => {
|
||||
await new Promise(resolve => setTimeout(resolve, 0))
|
||||
expect(backgroundAdvertised(ctx)).toBe(true)
|
||||
const started = await call(ctx, 'bash', { command: 'echo swapped', description: 'test command', run_in_background: true })
|
||||
expect(text(started)).toBe('started background job bash-1')
|
||||
await callUntilText(ctx, 'job_output', { job_id: 'bash-1' }, '[status: completed')
|
||||
expect(text(started)).toBe(`started background job ${currentId(ctx)}`)
|
||||
await callUntilText(ctx, 'job_output', { job_id: currentId(ctx) }, '[status: completed')
|
||||
|
||||
// The registry leaves; the foreground-only variant returns in its place,
|
||||
// and the tool never disappears in between.
|
||||
@@ -572,13 +579,13 @@ describe('background execution through the job runtime', () => {
|
||||
const started = await call(ctx, 'bash', { command: 'echo bg-ok', description: 'test command', run_in_background: true })
|
||||
expect(started.isError).toBe(false)
|
||||
if (started.isError) throw new Error('expected background bash success')
|
||||
expect(started.value).toEqual({ kind: 'background', jobId: 'bash-1', cwd: process.cwd() })
|
||||
expect(text(started)).toBe('started background job bash-1')
|
||||
expect(started.value).toEqual({ kind: 'background', jobId: currentId(ctx), cwd: process.cwd() })
|
||||
expect(text(started)).toBe(`started background job ${currentId(ctx)}`)
|
||||
|
||||
const read = await callUntilText(ctx, 'job_output', { job_id: 'bash-1' }, 'bg-ok')
|
||||
const read = await callUntilText(ctx, 'job_output', { job_id: currentId(ctx) }, 'bg-ok')
|
||||
expect(text(read)).toContain('bg-ok')
|
||||
// A later read reports the terminal outcome in the generic status line.
|
||||
const final = await callUntilText(ctx, 'job_output', { job_id: 'bash-1' }, '[status: completed, exit code: 0]')
|
||||
const final = await callUntilText(ctx, 'job_output', { job_id: currentId(ctx) }, '[status: completed, exit code: 0]')
|
||||
expect(final.isError).toBe(false)
|
||||
})
|
||||
|
||||
@@ -586,11 +593,11 @@ describe('background execution through the job runtime', () => {
|
||||
const ctx = await setupWithJobs()
|
||||
await call(ctx, 'bash', { command: 'sleep 60', description: 'test command', run_in_background: true })
|
||||
|
||||
const killed = await call(ctx, 'job_kill', { job_id: 'bash-1' })
|
||||
expect(text(killed)).toBe('requested cancellation of job bash-1')
|
||||
const killed = await call(ctx, 'job_kill', { job_id: currentId(ctx) })
|
||||
expect(text(killed)).toBe(`requested cancellation of job ${currentId(ctx)}`)
|
||||
// The cancel reached the process handle; the task settles as killed with
|
||||
// the signal detail mapped by processOutcome.
|
||||
const final = await call(ctx, 'job_output', { job_id: 'bash-1', wait: true })
|
||||
const final = await call(ctx, 'job_output', { job_id: currentId(ctx), wait: true })
|
||||
expect(text(final)).toContain('[status: killed, signal: SIGTERM]')
|
||||
})
|
||||
|
||||
@@ -598,7 +605,7 @@ describe('background execution through the job runtime', () => {
|
||||
const ctx = await setupWithJobs()
|
||||
await call(ctx, 'bash', { command: 'kill -TERM $$', description: 'test command', run_in_background: true })
|
||||
|
||||
const final = await call(ctx, 'job_output', { job_id: 'bash-1', wait: true })
|
||||
const final = await call(ctx, 'job_output', { job_id: currentId(ctx), wait: true })
|
||||
expect(text(final)).toContain('[status: killed, signal: SIGTERM]')
|
||||
})
|
||||
|
||||
@@ -607,15 +614,15 @@ describe('background execution through the job runtime', () => {
|
||||
const ctx = await setupWithJobs()
|
||||
const agent = await registerFakeAgent(ctx, 'sess-owner')
|
||||
const started = await call(ctx, 'bash', { command: 'sleep 60', description: 'test command', run_in_background: true }, agent)
|
||||
expect(text(started)).toBe('started background job bash-1')
|
||||
expect(text(started)).toBe(`started background job ${currentId(ctx, agent)}`)
|
||||
|
||||
const anon = await call(ctx, 'job_output', { job_id: 'bash-1' })
|
||||
const anon = await call(ctx, 'job_output', { job_id: currentId(ctx, agent) })
|
||||
expect(anon.isError).toBe(true)
|
||||
expect(text(anon)).toMatch(/belongs to another session/)
|
||||
|
||||
const killed = await call(ctx, 'job_kill', { job_id: 'bash-1' }, agent)
|
||||
const killed = await call(ctx, 'job_kill', { job_id: currentId(ctx, agent) }, agent)
|
||||
expect(killed.isError).toBe(false)
|
||||
await call(ctx, 'job_output', { job_id: 'bash-1', wait: true }, agent) // await settlement — no orphan
|
||||
await call(ctx, 'job_output', { job_id: currentId(ctx, agent), wait: true }, agent) // await settlement — no orphan
|
||||
})
|
||||
|
||||
it('fails loud when the job runtime is not loaded', async () => {
|
||||
@@ -849,7 +856,7 @@ describe('sandbox escalation through the generic task producer', () => {
|
||||
// with the result, and the ordinal is never reused.
|
||||
expect(ctx.jobs.list(agent.id)).toEqual([])
|
||||
const background = await call(ctx, 'bash', { ...escalate, run_in_background: true }, agent)
|
||||
expect(text(background)).toBe('started background job bash-2')
|
||||
expect(text(background)).toBe(`started background job ${currentId(ctx, agent)}`)
|
||||
expect(bash.modes).toEqual(['workspace-write', 'workspace-write'])
|
||||
})
|
||||
|
||||
@@ -1463,7 +1470,7 @@ describe('the model-facing bash tool builds its request from named args only (no
|
||||
// The call really went down the background path (the recorder sees the real
|
||||
// request the consumer built, so the absent env/stdin below is a real
|
||||
// negative, not a recorder that drops everything).
|
||||
expect(text(result)).toBe('started background job bash-1')
|
||||
expect(text(result)).toBe(`started background job ${currentId(ctx)}`)
|
||||
expect(bash.requests).toHaveLength(1)
|
||||
const request = bash.requests[0]!
|
||||
expect(request.command).toBe('sleep 1')
|
||||
@@ -1501,8 +1508,8 @@ describe('resolved launch directory metadata', () => {
|
||||
expect(resolving).toHaveBeenCalledOnce()
|
||||
expect(resolving).toHaveBeenCalledWith(expect.objectContaining({ workdir: workdir === undefined ? current : join(current, workdir) }))
|
||||
expect(launch).toHaveBeenCalledWith(expect.objectContaining({ workdir: directory }))
|
||||
expect(text(result)).toBe(background ? 'started background job bash-1' : 'metadata-ok')
|
||||
if (background) await call(ctx, 'job_output', { job_id: 'bash-1', wait: true }, owner)
|
||||
expect(text(result)).toBe(background ? `started background job ${currentId(ctx, owner)}` : 'metadata-ok')
|
||||
if (background) await call(ctx, 'job_output', { job_id: currentId(ctx, owner), wait: true }, owner)
|
||||
} finally {
|
||||
ensure.mockRestore()
|
||||
resolving.mockRestore()
|
||||
|
||||
@@ -288,12 +288,14 @@ describe('foreground commands as jobs (pwsh)', () => {
|
||||
const result = await call(ctx, { command: 'Get-Slow', description: 'test command', timeoutMs: 250 })
|
||||
const body = (result.content[0] as { text: string }).text
|
||||
expect(body).toContain('early-output')
|
||||
expect(body).toContain('moved to background job pwsh-1]')
|
||||
const jobId = ctx.jobs.list()[0]?.id
|
||||
expect(jobId).toMatch(/^pwsh-[0-9a-f-]{36}$/)
|
||||
expect(body).toContain(`moved to background job ${jobId}]`)
|
||||
expect(body).toContain('read newer output with job_output, stop it with job_kill')
|
||||
|
||||
const jobs = ctx.jobs
|
||||
const job = jobs.list()[0]
|
||||
expect(job).toMatchObject({ id: 'pwsh-1', kind: 'pwsh', status: 'running' })
|
||||
expect(job).toMatchObject({ id: jobId, kind: 'pwsh', status: 'running' })
|
||||
// The hand-over was one consuming read: the next read repeats nothing.
|
||||
expect(jobs.read(job!.id).chunks).toEqual([])
|
||||
scripted.finish()
|
||||
|
||||
@@ -40,6 +40,13 @@ import { renderPwshJobRead, renderPwshResult } from '../src/render.ts'
|
||||
/** Empty offset readers for fakes that never produce output. */
|
||||
const silentReader = { readFrom: (fromByte: number) => ({ text: '', nextOffset: fromByte, lossy: false }) }
|
||||
|
||||
/** Use the newest id returned by the real registry, including settled jobs. */
|
||||
function currentId(ctx: Context, owner?: Agent): string {
|
||||
const id = ctx.jobs.list(owner?.id).at(-1)?.id
|
||||
if (id === undefined) throw new Error('background job was not registered')
|
||||
return id
|
||||
}
|
||||
|
||||
const testToolSignal = new AbortController().signal
|
||||
|
||||
/** Per-test temp dirs (session cwd/home fixtures), removed after each test. */
|
||||
@@ -720,7 +727,7 @@ describe('sandbox escalation through ctx.approval', () => {
|
||||
// with the result, and the ordinal is never reused.
|
||||
expect(ctx.jobs.list(agent.id)).toEqual([])
|
||||
const background = await call(ctx, 'pwsh', { ...escalate, run_in_background: true }, agent)
|
||||
expect(text(background)).toBe('started background job pwsh-2')
|
||||
expect(text(background)).toBe(`started background job ${currentId(ctx, agent)}`)
|
||||
expect(bash.modes).toEqual(['workspace-write', 'workspace-write'])
|
||||
})
|
||||
|
||||
@@ -803,13 +810,13 @@ describe('background execution through the job runtime', () => {
|
||||
const started = await call(ctx, 'pwsh', { command: 'Write-Output bg-ok', description: 'test command', run_in_background: true })
|
||||
expect(started.isError).toBe(false)
|
||||
if (started.isError) throw new Error('expected background pwsh success')
|
||||
expect(started.value).toEqual({ kind: 'background', jobId: 'pwsh-1', cwd: process.cwd() })
|
||||
expect(text(started)).toBe('started background job pwsh-1')
|
||||
expect(started.value).toEqual({ kind: 'background', jobId: currentId(ctx), cwd: process.cwd() })
|
||||
expect(text(started)).toBe(`started background job ${currentId(ctx)}`)
|
||||
|
||||
const read = await callUntilText(ctx, 'job_output', { job_id: 'pwsh-1' }, 'bg-ok')
|
||||
const read = await callUntilText(ctx, 'job_output', { job_id: currentId(ctx) }, 'bg-ok')
|
||||
expect(text(read)).toContain('bg-ok')
|
||||
// A later read reports the terminal outcome in the generic status line.
|
||||
const final = await callUntilText(ctx, 'job_output', { job_id: 'pwsh-1' }, '[status: completed, exit code: 0]')
|
||||
const final = await callUntilText(ctx, 'job_output', { job_id: currentId(ctx) }, '[status: completed, exit code: 0]')
|
||||
expect(final.isError).toBe(false)
|
||||
})
|
||||
|
||||
@@ -818,11 +825,11 @@ describe('background execution through the job runtime', () => {
|
||||
bash.backgroundHandler = () => killableProcess()
|
||||
await call(ctx, 'pwsh', { command: 'Start-Sleep -Seconds 60', description: 'test command', run_in_background: true })
|
||||
|
||||
const killed = await call(ctx, 'job_kill', { job_id: 'pwsh-1' })
|
||||
expect(text(killed)).toBe('requested cancellation of job pwsh-1')
|
||||
const killed = await call(ctx, 'job_kill', { job_id: currentId(ctx) })
|
||||
expect(text(killed)).toBe(`requested cancellation of job ${currentId(ctx)}`)
|
||||
// The cancel reached the process handle; the task settles as killed with
|
||||
// the signal detail mapped by processOutcome.
|
||||
const final = await call(ctx, 'job_output', { job_id: 'pwsh-1', wait: true })
|
||||
const final = await call(ctx, 'job_output', { job_id: currentId(ctx), wait: true })
|
||||
expect(text(final)).toContain('[status: killed, signal: SIGTERM]')
|
||||
})
|
||||
|
||||
@@ -830,15 +837,15 @@ describe('background execution through the job runtime', () => {
|
||||
const { ctx } = await setupWithJobs()
|
||||
const agent = await registerFakeAgent(ctx, 'sess-owner')
|
||||
const started = await call(ctx, 'pwsh', { command: 'Start-Sleep -Seconds 60', description: 'test command', run_in_background: true }, agent)
|
||||
expect(text(started)).toBe('started background job pwsh-1')
|
||||
expect(text(started)).toBe(`started background job ${currentId(ctx, agent)}`)
|
||||
|
||||
const anon = await call(ctx, 'job_output', { job_id: 'pwsh-1' })
|
||||
const anon = await call(ctx, 'job_output', { job_id: currentId(ctx, agent) })
|
||||
expect(anon.isError).toBe(true)
|
||||
expect(text(anon)).toMatch(/belongs to another session/)
|
||||
|
||||
const killed = await call(ctx, 'job_kill', { job_id: 'pwsh-1' }, agent)
|
||||
const killed = await call(ctx, 'job_kill', { job_id: currentId(ctx, agent) }, agent)
|
||||
expect(killed.isError).toBe(false)
|
||||
await call(ctx, 'job_output', { job_id: 'pwsh-1', wait: true }, agent) // await settlement — no orphan
|
||||
await call(ctx, 'job_output', { job_id: currentId(ctx, agent), wait: true }, agent) // await settlement — no orphan
|
||||
})
|
||||
|
||||
it('fails loud when the job runtime is not loaded', async () => {
|
||||
@@ -944,7 +951,7 @@ describe('the background surface follows the job registry', () => {
|
||||
await new Promise(resolve => setTimeout(resolve, 0))
|
||||
expect(backgroundAdvertised(ctx)).toBe(true)
|
||||
const started = await call(ctx, 'pwsh', { command: 'Write-Output swapped', description: 'test command', run_in_background: true })
|
||||
expect(text(started)).toBe('started background job pwsh-1')
|
||||
expect(text(started)).toBe(`started background job ${currentId(ctx)}`)
|
||||
|
||||
// The registry leaves; the foreground-only variant returns in its place,
|
||||
// and the tool never disappears in between.
|
||||
@@ -1247,8 +1254,8 @@ describe('resolved launch directory metadata', () => {
|
||||
expect(resolving).toHaveBeenCalledOnce()
|
||||
expect(resolving).toHaveBeenCalledWith(expect.objectContaining({ workdir: workdir === undefined ? current : join(current, workdir) }))
|
||||
expect(launch).toHaveBeenCalledWith(expect.objectContaining({ workdir: directory }))
|
||||
expect(text(result)).toBe(background ? 'started background job pwsh-1' : 'metadata-ok')
|
||||
if (background) await call(ctx, 'job_output', { job_id: 'pwsh-1', wait: true }, owner)
|
||||
expect(text(result)).toBe(background ? `started background job ${currentId(ctx, owner)}` : 'metadata-ok')
|
||||
if (background) await call(ctx, 'job_output', { job_id: currentId(ctx, owner), wait: true }, owner)
|
||||
} finally {
|
||||
ensure.mockRestore()
|
||||
resolving.mockRestore()
|
||||
|
||||
@@ -354,24 +354,35 @@ export function normalizeSessionLog(
|
||||
const cwdPathMode = options.cwdPathMode ?? 'canonical'
|
||||
const identityMode = options.identityMode ?? 'legacy'
|
||||
const lines = rawLog.split('\n').filter(line => line.trim().length > 0)
|
||||
const executionTokens = new Map<string, string>()
|
||||
const parsed = lines.map(line => JSON.parse(line) as Record<string, unknown>)
|
||||
const jobTokens = new Map<string, string>()
|
||||
const reservedTokens = new Set(rawLog.match(/\{\{id:[1-9]\d*\}\}/g) ?? [])
|
||||
let nextExecutionToken = 1
|
||||
const records = lines.map((line) => {
|
||||
const record = JSON.parse(line) as Record<string, unknown>
|
||||
if (identityMode === 'legacy' && (record.type === 'job/registered' || record.type === 'job/settled')
|
||||
&& record.data !== null && typeof record.data === 'object') {
|
||||
const data = record.data as Record<string, unknown>
|
||||
const id = data.executionId
|
||||
if (typeof id === 'string' && id.length === 36 && id.search(UUID_RE) === 0) {
|
||||
let token = executionTokens.get(id)
|
||||
if (token === undefined) {
|
||||
do { token = `{{id:${nextExecutionToken++}}}` } while (reservedTokens.has(token))
|
||||
executionTokens.set(id, token)
|
||||
}
|
||||
data.executionId = token
|
||||
}
|
||||
let nextJobToken = 1
|
||||
if (identityMode === 'legacy') {
|
||||
for (const record of parsed) {
|
||||
if ((record.type !== 'job/registered' && record.type !== 'job/settled')
|
||||
|| record.data === null || typeof record.data !== 'object') continue
|
||||
const id = (record.data as Record<string, unknown>).jobId
|
||||
if (typeof id !== 'string' || id.search(UUID_RE) < 0 || jobTokens.has(id)) continue
|
||||
let token: string
|
||||
do { token = `{{id:${nextJobToken++}}}` } while (reservedTokens.has(token))
|
||||
jobTokens.set(id, token)
|
||||
}
|
||||
}
|
||||
const replaceJobs = (value: unknown): unknown => {
|
||||
if (typeof value === 'string') {
|
||||
let output = value
|
||||
for (const [id, token] of jobTokens) output = output.split(id).join(token)
|
||||
return output
|
||||
}
|
||||
if (Array.isArray(value)) return value.map(replaceJobs)
|
||||
if (value !== null && typeof value === 'object') {
|
||||
return Object.fromEntries(Object.entries(value).map(([key, item]) => [key, replaceJobs(item)]))
|
||||
}
|
||||
return value
|
||||
}
|
||||
const records = parsed.map((raw) => {
|
||||
const record = jobTokens.size === 0 ? raw : replaceJobs(raw) as Record<string, unknown>
|
||||
if (record.type === 'session') {
|
||||
if ('createdAt' in record) record.createdAt = 0
|
||||
} else if (isPackedFixtureRow(record)) {
|
||||
|
||||
@@ -257,20 +257,26 @@ Additional instructions from: nested\AGENTS.md`,
|
||||
|
||||
describe('normalizeSessionLog', () => {
|
||||
it('keeps job execution identities distinct in single-log and single-snapshot paths', () => {
|
||||
const ids = ['aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa', 'bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb']
|
||||
const ids = ['bash-aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa', 'bash-bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb']
|
||||
const raw = [
|
||||
{ type: 'session', version: 0, id: ctx.sessionIds[0], cwd: ctx.cwd, createdAt: 100 },
|
||||
{ type: 'user/message', data: { text: 'Keep {{id:1}} literal.' } },
|
||||
...ids.flatMap(executionId => [
|
||||
{ type: 'job/registered', data: { executionId, startedAt: 100 } },
|
||||
{ type: 'job/settled', data: { executionId, startedAt: 100, finishedAt: 200 } },
|
||||
...ids.flatMap(jobId => [
|
||||
{ type: 'job/registered', data: { jobId, startedAt: 100 } },
|
||||
{ type: 'job/settled', data: { jobId, startedAt: 100, finishedAt: 200 } },
|
||||
]),
|
||||
{ type: 'tool/call', data: { arguments: JSON.stringify({ job_id: ids[0] }) } },
|
||||
{ type: 'tool/result', data: { message: { content: [{ type: 'text', text: `read ${ids[0]}` }] } } },
|
||||
].map(record => JSON.stringify(record)).join('\n') + '\n'
|
||||
for (const normalize of [normalizeSessionLog, normalizeSessionSnapshot]) {
|
||||
const output = normalize(raw, ctx)
|
||||
const jobs = output.split('\n').filter(Boolean).map(line => JSON.parse(line) as { type: string; data: { executionId: string } })
|
||||
const jobs = output.split('\n').filter(Boolean).map(line => JSON.parse(line) as { type: string; data: { jobId: string } })
|
||||
.filter(record => record.type.startsWith('job/'))
|
||||
expect(jobs.map(record => record.data.executionId)).toEqual(['{{id:2}}', '{{id:2}}', '{{id:3}}', '{{id:3}}'])
|
||||
expect(jobs.map(record => record.data.jobId)).toEqual(['{{id:2}}', '{{id:2}}', '{{id:3}}', '{{id:3}}'])
|
||||
const call = output.split('\n').filter(Boolean).map(line => JSON.parse(line) as { type: string; data: { arguments: string } })
|
||||
.find(record => record.type === 'tool/call')
|
||||
expect(JSON.parse(call!.data.arguments)).toEqual({ job_id: '{{id:2}}' })
|
||||
expect(output).toContain('read {{id:2}}')
|
||||
expect(output).toContain('Keep {{id:1}} literal.')
|
||||
expect(normalize(output, ctx)).toBe(output)
|
||||
}
|
||||
|
||||
@@ -475,14 +475,15 @@ describe('dsh-tool-workflow', () => {
|
||||
const result = await execute(ctx, { script: SCRIPT, meta: META, run_in_background: true }, { agent: parent })
|
||||
expect(result.isError).toBe(false)
|
||||
if (result.isError) throw new Error('expected background acceptance')
|
||||
expect(result.value).toEqual({ kind: 'background', jobId: 'workflow-1', runId: 'run-1' })
|
||||
const jobId = (result.value as { jobId: string }).jobId as JobId
|
||||
expect(result.value).toEqual({ kind: 'background', jobId, runId: 'run-1' })
|
||||
expect((result.content[0] as { text: string }).text)
|
||||
.toContain('workflow "audit" started in the background as job workflow-1')
|
||||
.toContain(`workflow "audit" started in the background as job ${jobId}`)
|
||||
// The engine run carries no tool-step signal: the job owns cancellation.
|
||||
expect(engine.requests[0]!.signal).toBeUndefined()
|
||||
|
||||
const jobs = ctx.jobs
|
||||
const job = jobs.get('workflow-1' as never, parent.id)
|
||||
const job = jobs.get(jobId, parent.id)
|
||||
expect(job).toMatchObject({ kind: 'workflow', label: 'audit', status: 'running', owner: parent.id })
|
||||
expect(job.output.total).toBe(0)
|
||||
|
||||
@@ -500,7 +501,7 @@ describe('dsh-tool-workflow', () => {
|
||||
signal: testToolSignal,
|
||||
callId: ToolCallId('call-early-read'),
|
||||
name: 'job_output',
|
||||
arguments: { job_id: 'workflow-1' },
|
||||
arguments: { job_id: jobId },
|
||||
agent: parent,
|
||||
})
|
||||
expect((early.content[0] as { text: string }).text).toBe('(no new output)\n[status: running, Scan]')
|
||||
@@ -549,18 +550,20 @@ describe('dsh-tool-workflow', () => {
|
||||
const { ctx, engine, parent } = await setupBackground()
|
||||
const result = await execute(ctx, { script: SCRIPT, meta: META, args: { files: ['a.ts'] }, run_in_background: true }, { agent: parent })
|
||||
if (result.isError) throw new Error('expected background acceptance')
|
||||
const firstId = (result.value as { jobId: string }).jobId as JobId
|
||||
expect(engine.requests[0]).toMatchObject({ args: { files: ['a.ts'] } })
|
||||
engine.settleRun(WorkflowRunId('run-1'), { value: null, stopReason: 'error', error: 'script exploded', agentsStarted: 2 })
|
||||
const jobs = ctx.jobs
|
||||
await vi.waitFor(() => { expect(jobs.get('workflow-1' as never, parent.id).status).toBe('failed') })
|
||||
expect(jobs.get('workflow-1' as never, parent.id).detail).toBe('script exploded')
|
||||
await vi.waitFor(() => { expect(jobs.get(firstId, parent.id).status).toBe('failed') })
|
||||
expect(jobs.get(firstId, parent.id).detail).toBe('script exploded')
|
||||
|
||||
// A failure without a message falls back to the unknown-error detail.
|
||||
const second = await execute(ctx, { script: SCRIPT, meta: META, run_in_background: true }, { agent: parent })
|
||||
if (second.isError) throw new Error('expected background acceptance')
|
||||
const secondId = (second.value as { jobId: string }).jobId as JobId
|
||||
engine.settleRun(WorkflowRunId('run-2'), { value: null, stopReason: 'error', agentsStarted: 0 })
|
||||
await vi.waitFor(() => { expect(jobs.get('workflow-2' as never, parent.id).status).toBe('failed') })
|
||||
expect(jobs.get('workflow-2' as never, parent.id).detail).toBe('unknown error')
|
||||
await vi.waitFor(() => { expect(jobs.get(secondId, parent.id).status).toBe('failed') })
|
||||
expect(jobs.get(secondId, parent.id).detail).toBe('unknown error')
|
||||
})
|
||||
|
||||
it('a nested transport call records job lifecycle but no workflow events, and a dispose failure still settles', async () => {
|
||||
@@ -573,10 +576,11 @@ describe('dsh-tool-workflow', () => {
|
||||
{ agent: parent, parent: {} as ToolExecutionToken },
|
||||
)
|
||||
if (result.isError) throw new Error('expected background acceptance')
|
||||
const jobId = (result.value as { jobId: string }).jobId as JobId
|
||||
engine.settleRun(WorkflowRunId('run-1'), { value: 'ok', stopReason: 'completed', agentsStarted: 1 })
|
||||
const jobs = ctx.jobs
|
||||
await vi.waitFor(() => { expect(jobs.get('workflow-1' as never, parent.id).status).toBe('completed') })
|
||||
expect(jobs.get('workflow-1' as never, parent.id).detail).toBe('1 agent')
|
||||
await vi.waitFor(() => { expect(jobs.get(jobId, parent.id).status).toBe('completed') })
|
||||
expect(jobs.get(jobId, parent.id).detail).toBe('1 agent')
|
||||
expect(warn.mock.calls.map(args => String(args[0])).join('\n')).toContain('dispose failed')
|
||||
expect(session.snapshotEvents().map(event => event.type)).toEqual(['job/registered', 'job/settled'])
|
||||
expect(session.snapshotEvents().at(-1)?.data).toMatchObject({ producerReported: true })
|
||||
|
||||
@@ -771,14 +771,14 @@ def test_live_turn_rejects_missing_standalone_acknowledgement(answer: str) -> No
|
||||
def test_job_snapshot_normalization_preserves_pairing_and_uncertainty():
|
||||
normalize = SMOKE["normalize_snapshot_value"]
|
||||
replacements = []
|
||||
first = {"type": "job/registered", "data": {"executionId": "job-a", "startedAt": 10}}
|
||||
last = {"type": "job/settled", "data": {"executionId": "job-a", "startedAt": 10,
|
||||
first = {"type": "job/registered", "data": {"jobId": "job-a", "startedAt": 10}}
|
||||
last = {"type": "job/settled", "data": {"jobId": "job-a", "startedAt": 10,
|
||||
"finishedAt": 20, "producerReported": False}}
|
||||
other = {"type": "job/registered", "data": {"executionId": "job-b", "startedAt": 30}}
|
||||
other = {"type": "job/registered", "data": {"jobId": "job-b", "startedAt": 30}}
|
||||
a, b, c = normalize([first, last, other], replacements)
|
||||
assert a["data"] == {"executionId": "{{id:1}}", "startedAt": 0}
|
||||
assert b["data"] == {"executionId": "{{id:1}}", "startedAt": 0, "finishedAt": 0, "producerReported": False}
|
||||
assert c["data"]["executionId"] == "{{id:2}}"
|
||||
assert a["data"] == {"jobId": "{{id:1}}", "startedAt": 0}
|
||||
assert b["data"] == {"jobId": "{{id:1}}", "startedAt": 0, "finishedAt": 0, "producerReported": False}
|
||||
assert c["data"]["jobId"] == "{{id:2}}"
|
||||
assert normalize([a, b, c], []) == [a, b, c]
|
||||
assert normalize({"type": "tool/result", "data": {"startedAt": 100, "finishedAt": 200}}, []) == {
|
||||
"type": "tool/result", "data": {"startedAt": 100, "finishedAt": 200},
|
||||
|
||||
@@ -187,20 +187,6 @@ describe('CI workflow', () => {
|
||||
expect(wine.steps).toContainEqual(expect.objectContaining({ name: 'Shut down wineserver', if: 'always()' }))
|
||||
})
|
||||
|
||||
it('runs Windows coverage with the released Realm cleanup fix while retaining Node 24 native checks', () => {
|
||||
const workflow = loadWorkflow('.github/workflows/ci.yml')
|
||||
const coverage = workflowJob(workflow, 'windows-coverage')
|
||||
expect(coverage.name).toBe('windows node 26 / coverage')
|
||||
expect(coverage.steps).toContainEqual(expect.objectContaining({
|
||||
uses: 'actions/[email protected]', with: { 'node-version': '26.10.0' },
|
||||
}))
|
||||
for (const name of ['windows-build', 'windows-native-tests']) {
|
||||
expect(workflowJob(workflow, name).steps).toContainEqual(expect.objectContaining({
|
||||
uses: 'actions/[email protected]', with: { 'node-version': '${{ env.PRIMARY_NODE_VERSION }}' },
|
||||
}))
|
||||
}
|
||||
})
|
||||
|
||||
it.each([
|
||||
['node-24-coverage', 'Linux', 'LINUX', 'pnpm run check:ci:coverage'],
|
||||
['windows-coverage', 'Windows', 'WINDOWS', 'pnpm run check:ci:coverage'],
|
||||
@@ -397,7 +383,7 @@ describe('CI workflow', () => {
|
||||
expect(install!.run).not.toContain('$cloneFlag')
|
||||
}
|
||||
|
||||
expect(windowsCoverage.name).toBe('windows node 26 / coverage')
|
||||
expect(windowsCoverage.name).toBe('windows node 24 / coverage')
|
||||
expect(windowsCoverage.env).toMatchObject({ DSH_COVERAGE_PARTITIONS: "${{ vars.DSH_CI_FAILOVER_WINDOWS == 'selfhosted' && github.event.pull_request.user.login != 'dependabot[bot]' && '4' || '' }}" })
|
||||
const coverageSteps = windowsCoverage.steps as unknown[]
|
||||
const coverageCommands = coverageSteps.filter((step): step is Record<string, unknown> & { run: string } => (
|
||||
|
||||
@@ -171,6 +171,7 @@ export const SERVICE_WALK_EXEMPTIONS: Record<string, string> = {
|
||||
webTerminals: 'client-side terminal view models — packages/api/terminal-controller/README.md owns the API',
|
||||
appReady: 'not a service: launcher-provided successful-startup signal — packages/boot/cmdline/README.md owns the launcher contract',
|
||||
appExit: 'not a service: launcher-provided bounded process-exit callback — packages/boot/cmdline/README.md owns the launcher contract',
|
||||
appShutdown: 'not a service: launcher-provided ordered cleanup registry — packages/boot/cmdline/README.md owns the launcher contract',
|
||||
cmdlineArgs: 'not a service: launcher-provided immutable app argument accessor — packages/boot/cmdline/README.md owns the launcher contract',
|
||||
configuredAgentIdentities: 'not a service: launcher-provided boot-context value (ConfiguredAgentIdentities | undefined) — packages/core/agent-loop/README.md owns this launcher contract',
|
||||
launcherSessionQueryPath: 'not a service: launcher-provided boot-context value (string | undefined) — packages/session-query/session-query-sqlite/README.md owns this launcher contract',
|
||||
|
||||
@@ -1610,7 +1610,7 @@ def smoke_sdk_snapshot_mode(
|
||||
jobs = [event for event in result.events if event.get("type") in ("job/registered", "job/settled")]
|
||||
assert [event["type"] for event in jobs] == ["job/registered", "job/settled"], jobs
|
||||
registered, settled = (event["data"] for event in jobs)
|
||||
assert registered["executionId"] == settled["executionId"]
|
||||
assert registered["jobId"] == settled["jobId"]
|
||||
assert registered["startedAt"] == settled["startedAt"] <= settled["finishedAt"]
|
||||
assert settled["producerReported"] is True and settled["status"] == "completed"
|
||||
root_call_id = "advanced-code" if mode == "ptc" else "advanced-native"
|
||||
@@ -2608,11 +2608,11 @@ def normalize_snapshot_value(
|
||||
|
||||
if value.get("type") in ("job/registered", "job/settled"):
|
||||
data = value.get("data")
|
||||
execution_id = data.get("executionId") if isinstance(data, dict) else None
|
||||
if (isinstance(execution_id, str) and not re.fullmatch(r"\{\{id:[1-9]\d*\}\}", execution_id)
|
||||
and not any(actual == execution_id for actual, _ in replacements)):
|
||||
job_id = data.get("jobId") if isinstance(data, dict) else None
|
||||
if (isinstance(job_id, str) and not re.fullmatch(r"\{\{id:[1-9]\d*\}\}", job_id)
|
||||
and not any(actual == job_id for actual, _ in replacements)):
|
||||
index = 1 + sum(token.startswith("{{id:") for _, token in replacements)
|
||||
replacements.append((execution_id, "{{id:" + str(index) + "}}"))
|
||||
replacements.append((job_id, "{{id:" + str(index) + "}}"))
|
||||
|
||||
normalized = {
|
||||
key: normalize_snapshot_value(item, replacements)
|
||||
|
||||
@@ -461,8 +461,7 @@
|
||||
"seq": 24,
|
||||
"time": 0,
|
||||
"data": {
|
||||
"executionId": "{{id:1}}",
|
||||
"jobId": "snapshot-1",
|
||||
"jobId": "{{id:1}}",
|
||||
"owner": "{{parent}}",
|
||||
"kind": "snapshot",
|
||||
"label": "snapshot_double",
|
||||
@@ -478,8 +477,7 @@
|
||||
"seq": 25,
|
||||
"time": 0,
|
||||
"data": {
|
||||
"executionId": "{{id:1}}",
|
||||
"jobId": "snapshot-1",
|
||||
"jobId": "{{id:1}}",
|
||||
"owner": "{{parent}}",
|
||||
"kind": "snapshot",
|
||||
"label": "snapshot_double",
|
||||
@@ -1605,8 +1603,7 @@
|
||||
"seq": 24,
|
||||
"time": 0,
|
||||
"data": {
|
||||
"executionId": "{{id:1}}",
|
||||
"jobId": "snapshot-1",
|
||||
"jobId": "{{id:1}}",
|
||||
"owner": "{{parent}}",
|
||||
"kind": "snapshot",
|
||||
"label": "snapshot_double",
|
||||
@@ -1628,8 +1625,7 @@
|
||||
"seq": 25,
|
||||
"time": 0,
|
||||
"data": {
|
||||
"executionId": "{{id:1}}",
|
||||
"jobId": "snapshot-1",
|
||||
"jobId": "{{id:1}}",
|
||||
"owner": "{{parent}}",
|
||||
"kind": "snapshot",
|
||||
"label": "snapshot_double",
|
||||
|
||||
@@ -23,8 +23,8 @@
|
||||
{"type":"assistant/message","data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"tool-call","id":"advanced-code","name":"run_code","arguments":"{\"code\": \"return await tools.snapshot_double({ value: 21 })\", \"description\": \"Call the configured Plugin tool\"}"}],"source":{"kind":"model","provider":"deepseek-official","model":"smoke-model","replayState":{"response":{"kind":"deepseek-messages","version":1,"model":"smoke-model"},"blocks":[{"type":"tool-call"}]}},"id":"{{messageId}}"},"usage":{"inputTokens":3,"outputTokens":3,"totalTokens":6},"stream":[{"type":"chunk","time":0,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}},{"type":"tool-call-chunks","time0":0,"index":0,"dt":[],"id":"advanced-code","name":"run_code","args":[""]},{"type":"tool-call-chunks","time0":0,"index":0,"dt":[],"id":"advanced-code","args":["{\"code\": \"return await tools.snapshot_double({ value: 21 })\", \"description\": \"Call the configured Plugin tool\"}"]},{"type":"chunk","time":0,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"advanced-code","name":"run_code","arguments":"{\"code\": \"return await tools.snapshot_double({ value: 21 })\", \"description\": \"Call the configured Plugin tool\"}"}}},{"type":"chunk","time":0,"chunk":{"type":"usage","usage":{"inputTokens":3,"outputTokens":3,"totalTokens":6}}},{"type":"chunk","time":0,"chunk":{"type":"finish","reason":{"kind":"tool-calls"},"replayState":{"response":{"kind":"deepseek-messages","version":1,"model":"smoke-model"},"blocks":[{"type":"tool-call"}]}}}]},"surfaceOp":"append"}
|
||||
{"type":"tool/call","data":{"turn":1,"step":1,"callId":"advanced-code","name":"run_code","arguments":"{\"code\": \"return await tools.snapshot_double({ value: 21 })\", \"description\": \"Call the configured Plugin tool\"}"}}
|
||||
{"type":"tool/ptc-dispatch-start","data":{"rootCallId":"advanced-code","parentCallId":"advanced-code","subCallId":"advanced-code:ptc:1","name":"snapshot_double","arguments":{"value":21}}}
|
||||
{"type":"job/registered","data":{"executionId":"{{id:1}}","jobId":"snapshot-1","owner":"{{parent}}","kind":"snapshot","label":"snapshot_double","startedAt":0,"toolCall":{"callId":"advanced-code:ptc:1","rootCallId":"advanced-code"}}}
|
||||
{"type":"job/settled","data":{"executionId":"{{id:1}}","jobId":"snapshot-1","owner":"{{parent}}","kind":"snapshot","label":"snapshot_double","startedAt":0,"toolCall":{"callId":"advanced-code:ptc:1","rootCallId":"advanced-code"},"finishedAt":0,"status":"completed","cause":"producer","producerReported":true}}
|
||||
{"type":"job/registered","data":{"jobId":"{{id:1}}","owner":"{{parent}}","kind":"snapshot","label":"snapshot_double","startedAt":0,"toolCall":{"callId":"advanced-code:ptc:1","rootCallId":"advanced-code"}}}
|
||||
{"type":"job/settled","data":{"jobId":"{{id:1}}","owner":"{{parent}}","kind":"snapshot","label":"snapshot_double","startedAt":0,"toolCall":{"callId":"advanced-code:ptc:1","rootCallId":"advanced-code"},"finishedAt":0,"status":"completed","cause":"producer","producerReported":true}}
|
||||
{"type":"tool/ptc-dispatch","data":{"rootCallId":"advanced-code","parentCallId":"advanced-code","subCallId":"advanced-code:ptc:1","name":"snapshot_double","arguments":{"value":21},"isError":false,"content":[{"type":"text","text":"42"}]}}
|
||||
{"type":"tool/result","data":{"turn":1,"step":1,"message":{"role":"tool","source":{"kind":"tool","callId":"advanced-code"},"toolCallId":"advanced-code","content":[{"type":"text","text":"42"}],"isError":false,"id":"{{messageId}}"}},"sourceEventSeqs":[22],"surfaceOp":"append"}
|
||||
{"type":"step/end","data":{"turn":1,"step":1}}
|
||||
|
||||
@@ -463,8 +463,7 @@
|
||||
"seq": 23,
|
||||
"time": 0,
|
||||
"data": {
|
||||
"executionId": "{{id:1}}",
|
||||
"jobId": "snapshot-1",
|
||||
"jobId": "{{id:1}}",
|
||||
"owner": "{{parent}}",
|
||||
"kind": "snapshot",
|
||||
"label": "snapshot_double",
|
||||
@@ -480,8 +479,7 @@
|
||||
"seq": 24,
|
||||
"time": 0,
|
||||
"data": {
|
||||
"executionId": "{{id:1}}",
|
||||
"jobId": "snapshot-1",
|
||||
"jobId": "{{id:1}}",
|
||||
"owner": "{{parent}}",
|
||||
"kind": "snapshot",
|
||||
"label": "snapshot_double",
|
||||
@@ -2073,8 +2071,7 @@
|
||||
"seq": 23,
|
||||
"time": 0,
|
||||
"data": {
|
||||
"executionId": "{{id:1}}",
|
||||
"jobId": "snapshot-1",
|
||||
"jobId": "{{id:1}}",
|
||||
"owner": "{{parent}}",
|
||||
"kind": "snapshot",
|
||||
"label": "snapshot_double",
|
||||
@@ -2096,8 +2093,7 @@
|
||||
"seq": 24,
|
||||
"time": 0,
|
||||
"data": {
|
||||
"executionId": "{{id:1}}",
|
||||
"jobId": "snapshot-1",
|
||||
"jobId": "{{id:1}}",
|
||||
"owner": "{{parent}}",
|
||||
"kind": "snapshot",
|
||||
"label": "snapshot_double",
|
||||
|
||||
@@ -22,8 +22,8 @@
|
||||
{"type":"session-log-deepseek/delivery-accepted","data":{"sessionId":"{{parent}}","sessionFormatVersion":4,"throughSeq":19}}
|
||||
{"type":"assistant/message","data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"tool-call","id":"advanced-native","name":"snapshot_double","arguments":"{\"value\": 21}"}],"source":{"kind":"model","provider":"deepseek-official","model":"smoke-model","replayState":{"response":{"kind":"deepseek-messages","version":1,"model":"smoke-model"},"blocks":[{"type":"tool-call"}]}},"id":"{{messageId}}"},"usage":{"inputTokens":3,"outputTokens":3,"totalTokens":6},"stream":[{"type":"chunk","time":0,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}},{"type":"tool-call-chunks","time0":0,"index":0,"dt":[],"id":"advanced-native","name":"snapshot_double","args":[""]},{"type":"tool-call-chunks","time0":0,"index":0,"dt":[],"id":"advanced-native","args":["{\"value\": 21}"]},{"type":"chunk","time":0,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"advanced-native","name":"snapshot_double","arguments":"{\"value\": 21}"}}},{"type":"chunk","time":0,"chunk":{"type":"usage","usage":{"inputTokens":3,"outputTokens":3,"totalTokens":6}}},{"type":"chunk","time":0,"chunk":{"type":"finish","reason":{"kind":"tool-calls"},"replayState":{"response":{"kind":"deepseek-messages","version":1,"model":"smoke-model"},"blocks":[{"type":"tool-call"}]}}}]},"surfaceOp":"append"}
|
||||
{"type":"tool/call","data":{"turn":1,"step":1,"callId":"advanced-native","name":"snapshot_double","arguments":"{\"value\": 21}"}}
|
||||
{"type":"job/registered","data":{"executionId":"{{id:1}}","jobId":"snapshot-1","owner":"{{parent}}","kind":"snapshot","label":"snapshot_double","startedAt":0,"toolCall":{"callId":"advanced-native","rootCallId":"advanced-native"}}}
|
||||
{"type":"job/settled","data":{"executionId":"{{id:1}}","jobId":"snapshot-1","owner":"{{parent}}","kind":"snapshot","label":"snapshot_double","startedAt":0,"toolCall":{"callId":"advanced-native","rootCallId":"advanced-native"},"finishedAt":0,"status":"completed","cause":"producer","producerReported":true}}
|
||||
{"type":"job/registered","data":{"jobId":"{{id:1}}","owner":"{{parent}}","kind":"snapshot","label":"snapshot_double","startedAt":0,"toolCall":{"callId":"advanced-native","rootCallId":"advanced-native"}}}
|
||||
{"type":"job/settled","data":{"jobId":"{{id:1}}","owner":"{{parent}}","kind":"snapshot","label":"snapshot_double","startedAt":0,"toolCall":{"callId":"advanced-native","rootCallId":"advanced-native"},"finishedAt":0,"status":"completed","cause":"producer","producerReported":true}}
|
||||
{"type":"tool/result","data":{"turn":1,"step":1,"message":{"role":"tool","source":{"kind":"tool","callId":"advanced-native"},"toolCallId":"advanced-native","content":[{"type":"text","text":"42"}],"isError":false,"id":"{{messageId}}"}},"sourceEventSeqs":[22],"surfaceOp":"append"}
|
||||
{"type":"step/end","data":{"turn":1,"step":1}}
|
||||
{"type":"step/start","data":{"turn":1,"step":2}}
|
||||
|
||||
@@ -14,8 +14,8 @@
|
||||
{"type":"session/title","data":{"title":"Run two shell commands: wait","messageSeqs":[8],"source":{"kind":"fallback"}}}
|
||||
{"type":"assistant/message","data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"tool-call","id":"call_wait","name":"bash","arguments":"{\"command\":\"node -e \\\"const fs=require('node:fs'); fs.writeFileSync('started.tmp', 'started'); fs.renameSync('started.tmp', 'started.txt'); setInterval(() => {}, 1000)\\\"\",\"description\":\"Wait until cancellation\"}"},{"type":"tool-call","id":"call_skipped","name":"bash","arguments":"{\"command\":\"printf skipped > skipped.txt\",\"description\":\"Write skipped marker\"}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{message:4}}"},"usage":{"inputTokens":10,"outputTokens":10},"stream":[{"type":"chunk","time":1791427932073,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}},{"type":"tool-call-chunks","time0":1791427932073,"index":0,"dt":[],"id":"call_wait","name":"bash","args":["{\"command\":\"node -e \\\"const fs=require('node:fs'); fs.writeFileSync('started.tmp', 'started'); fs.renameSync('started.tmp', 'started.txt'); setInterval(() => {}, 1000)\\\"\",\"description\":\"Wait until cancellation\"}"]},{"type":"chunk","time":1791427932073,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"call_wait","name":"bash","arguments":"{\"command\":\"node -e \\\"const fs=require('node:fs'); fs.writeFileSync('started.tmp', 'started'); fs.renameSync('started.tmp', 'started.txt'); setInterval(() => {}, 1000)\\\"\",\"description\":\"Wait until cancellation\"}"}}},{"type":"chunk","time":1791427932073,"chunk":{"type":"block-start","index":1,"blockType":"tool-call"}},{"type":"tool-call-chunks","time0":1791427932073,"index":1,"dt":[],"id":"call_skipped","name":"bash","args":["{\"command\":\"printf skipped > skipped.txt\",\"description\":\"Write skipped marker\"}"]},{"type":"chunk","time":1791427932073,"chunk":{"type":"block-end","index":1,"block":{"type":"tool-call","id":"call_skipped","name":"bash","arguments":"{\"command\":\"printf skipped > skipped.txt\",\"description\":\"Write skipped marker\"}"}}},{"type":"chunk","time":1791427932073,"chunk":{"type":"usage","usage":{"inputTokens":10,"outputTokens":10}}},{"type":"chunk","time":1791427932073,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}]},"surfaceOp":"append"}
|
||||
{"type":"tool/call","data":{"turn":1,"step":1,"callId":"call_wait","name":"bash","arguments":"{\"command\":\"node -e \\\"const fs=require('node:fs'); fs.writeFileSync('started.tmp', 'started'); fs.renameSync('started.tmp', 'started.txt'); setInterval(() => {}, 1000)\\\"\",\"description\":\"Wait until cancellation\"}"}}
|
||||
{"type":"job/registered","data":{"executionId":"{{id:1}}","jobId":"bash-1","owner":"{{session:1}}","kind":"bash","label":"node -e \"const fs=require('node:fs'); fs.writeFileSync('started.tmp', 'started'); fs.renameSync('started.tmp', 'started.txt'); setInterval(() => {}, 1000)\"","startedAt":1791427932086,"toolCall":{"callId":"call_wait","rootCallId":"call_wait"}}}
|
||||
{"type":"job/settled","data":{"executionId":"{{id:1}}","jobId":"bash-1","owner":"{{session:1}}","kind":"bash","label":"node -e \"const fs=require('node:fs'); fs.writeFileSync('started.tmp', 'started'); fs.renameSync('started.tmp', 'started.txt'); setInterval(() => {}, 1000)\"","startedAt":1791427932086,"toolCall":{"callId":"call_wait","rootCallId":"call_wait"},"finishedAt":1791427932134,"status":"killed","cause":"kill","producerReported":true,"detail":"signal: SIGTERM; tool call aborted"}}
|
||||
{"type":"job/registered","data":{"jobId":"{{id:1}}","owner":"{{session:1}}","kind":"bash","label":"node -e \"const fs=require('node:fs'); fs.writeFileSync('started.tmp', 'started'); fs.renameSync('started.tmp', 'started.txt'); setInterval(() => {}, 1000)\"","startedAt":1791427932086,"toolCall":{"callId":"call_wait","rootCallId":"call_wait"}}}
|
||||
{"type":"job/settled","data":{"jobId":"{{id:1}}","owner":"{{session:1}}","kind":"bash","label":"node -e \"const fs=require('node:fs'); fs.writeFileSync('started.tmp', 'started'); fs.renameSync('started.tmp', 'started.txt'); setInterval(() => {}, 1000)\"","startedAt":1791427932086,"toolCall":{"callId":"call_wait","rootCallId":"call_wait"},"finishedAt":1791427932134,"status":"killed","cause":"kill","producerReported":true,"detail":"signal: SIGTERM; tool call aborted"}}
|
||||
{"type":"tool/result","data":{"turn":1,"step":1,"message":{"role":"tool","source":{"kind":"tool","callId":"call_wait"},"toolCallId":"call_wait","content":[{"type":"text","text":"Error: tool call aborted"}],"isError":true,"id":"{{message:5}}"},"error":{"name":"AbortError","code":"ABORTED"}},"sourceEventSeqs":[14],"surfaceOp":"append"}
|
||||
{"type":"tool/call","data":{"turn":1,"step":1,"callId":"call_skipped","name":"bash","arguments":"{\"command\":\"printf skipped > skipped.txt\",\"description\":\"Write skipped marker\"}"}}
|
||||
{"type":"tool/result","data":{"turn":1,"step":1,"message":{"role":"tool","source":{"kind":"tool","callId":"call_skipped"},"toolCallId":"call_skipped","content":[{"type":"text","text":"Error: tool call aborted before dispatch"}],"isError":true,"id":"{{message:6}}"},"error":{"name":"AbortError","code":"ABORTED_BEFORE_DISPATCH"}},"sourceEventSeqs":[18],"surfaceOp":"append"}
|
||||
|
||||
@@ -16,8 +16,8 @@
|
||||
{"type":"tool/call","data":{"turn":1,"step":1,"callId":"call_00_d0sAHpJ9mYOJi0z7KNy30441","name":"bash","arguments":"{\"command\": \"printf 'escalated\\\\n' > /tmp/dsh-escalated.txt && cat /tmp/dsh-escalated.txt && rm /tmp/dsh-escalated.txt\", \"description\": \"Write file outside workspace and verify\", \"sandbox_permissions\": \"danger-full-access\", \"justification\": \"the user asked to write a file outside the workspace\"}"}}
|
||||
{"type":"approval/asked","data":{"id":"{{approval:1}}","toolName":"bash","callId":"call_00_d0sAHpJ9mYOJi0z7KNy30441","reason":"escalate sandbox to danger-full-access: the user asked to write a file outside the workspace"}}
|
||||
{"type":"approval/decided","data":{"id":"{{approval:1}}","outcome":"allowed-once"}}
|
||||
{"type":"job/registered","data":{"executionId":"{{id:1}}","jobId":"bash-1","owner":"{{session:1}}","kind":"bash","label":"printf 'escalated\\n' > /tmp/dsh-escalated.txt && cat /tmp/dsh-escalated.txt && rm /tmp/dsh-escalated.txt","startedAt":1791427933141,"toolCall":{"callId":"call_00_d0sAHpJ9mYOJi0z7KNy30441","rootCallId":"call_00_d0sAHpJ9mYOJi0z7KNy30441"}}}
|
||||
{"type":"job/settled","data":{"executionId":"{{id:1}}","jobId":"bash-1","owner":"{{session:1}}","kind":"bash","label":"printf 'escalated\\n' > /tmp/dsh-escalated.txt && cat /tmp/dsh-escalated.txt && rm /tmp/dsh-escalated.txt","startedAt":1791427933141,"toolCall":{"callId":"call_00_d0sAHpJ9mYOJi0z7KNy30441","rootCallId":"call_00_d0sAHpJ9mYOJi0z7KNy30441"},"finishedAt":1791427933148,"status":"completed","cause":"producer","producerReported":true,"detail":"exit code: 0"}}
|
||||
{"type":"job/registered","data":{"jobId":"{{id:1}}","owner":"{{session:1}}","kind":"bash","label":"printf 'escalated\\n' > /tmp/dsh-escalated.txt && cat /tmp/dsh-escalated.txt && rm /tmp/dsh-escalated.txt","startedAt":1791427933141,"toolCall":{"callId":"call_00_d0sAHpJ9mYOJi0z7KNy30441","rootCallId":"call_00_d0sAHpJ9mYOJi0z7KNy30441"}}}
|
||||
{"type":"job/settled","data":{"jobId":"{{id:1}}","owner":"{{session:1}}","kind":"bash","label":"printf 'escalated\\n' > /tmp/dsh-escalated.txt && cat /tmp/dsh-escalated.txt && rm /tmp/dsh-escalated.txt","startedAt":1791427933141,"toolCall":{"callId":"call_00_d0sAHpJ9mYOJi0z7KNy30441","rootCallId":"call_00_d0sAHpJ9mYOJi0z7KNy30441"},"finishedAt":1791427933148,"status":"completed","cause":"producer","producerReported":true,"detail":"exit code: 0"}}
|
||||
{"type":"tool/result","data":{"turn":1,"step":1,"message":{"role":"tool","source":{"kind":"tool","callId":"call_00_d0sAHpJ9mYOJi0z7KNy30441"},"toolCallId":"call_00_d0sAHpJ9mYOJi0z7KNy30441","content":[{"type":"text","text":"escalated\n"}],"isError":false,"id":"{{message:5}}"},"meta":{"cwd":"{{cwd}}"}},"sourceEventSeqs":[14],"surfaceOp":"append"}
|
||||
{"type":"step/end","data":{"turn":1,"step":1}}
|
||||
{"type":"step/start","data":{"turn":1,"step":2}}
|
||||
|
||||
@@ -11,8 +11,8 @@
|
||||
{"method":"session.event","params":{"sessionId":"{{session:1}}","event":{"type":"session/title","seq":12,"time":0,"data":{"title":"Run this exact command with","messageSeqs":[8],"source":{"kind":"fallback"}}}}}
|
||||
{"method":"session.event","params":{"sessionId":"{{session:1}}","event":{"type":"assistant/message","seq":13,"time":0,"data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"reasoning","text":"The user wants me to run a specific bash command and reply with its stdout only."},{"type":"tool-call","id":"call_00_Ry17evSfTr0uJnHhg3X93070","name":"bash","arguments":"{\"command\": \"echo dsh-sdk-proof-7391\", \"description\": \"Run the echo command as requested\"}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{message:4}}"},"usage":{"inputTokens":123,"outputTokens":89,"cacheReadTokens":1664,"reasoningTokens":17},"stream":[{"type":"chunk","time":0,"chunk":{"type":"block-start","index":0,"blockType":"reasoning"}},{"type":"reasoning-chunks","time0":0,"index":0,"dt":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"texts":["The"," user"," wants"," me"," to"," run"," a"," specific"," bash"," command"," and"," reply"," with"," its"," stdout"," only","."]},{"type":"chunk","time":0,"chunk":{"type":"block-start","index":1,"blockType":"tool-call"}},{"type":"tool-call-chunks","time0":0,"index":1,"dt":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"id":"call_00_Ry17evSfTr0uJnHhg3X93070","name":"bash","args":["","{","\"","command","\"",": ","\"","echo"," d","sh","-s","dk","-proof","-","739","1","\"",", ","\"","description","\"",": ","\"","Run"," the"," echo"," command"," as"," requested","\"","}"]},{"type":"chunk","time":0,"chunk":{"type":"block-end","index":0,"block":{"type":"reasoning","text":"The user wants me to run a specific bash command and reply with its stdout only."}}},{"type":"chunk","time":0,"chunk":{"type":"block-end","index":1,"block":{"type":"tool-call","id":"call_00_Ry17evSfTr0uJnHhg3X93070","name":"bash","arguments":"{\"command\": \"echo dsh-sdk-proof-7391\", \"description\": \"Run the echo command as requested\"}"}}},{"type":"chunk","time":0,"chunk":{"type":"usage","usage":{"inputTokens":123,"outputTokens":89,"cacheReadTokens":1664,"reasoningTokens":17}}},{"type":"chunk","time":0,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}]},"surfaceOp":"append"}}}
|
||||
{"method":"session.event","params":{"sessionId":"{{session:1}}","event":{"type":"tool/call","seq":14,"time":0,"data":{"turn":1,"step":1,"callId":"call_00_Ry17evSfTr0uJnHhg3X93070","name":"bash","arguments":"{\"command\": \"echo dsh-sdk-proof-7391\", \"description\": \"Run the echo command as requested\"}"}}}}
|
||||
{"method":"session.event","params":{"sessionId":"{{session:1}}","event":{"type":"job/registered","seq":15,"time":0,"data":{"executionId":"{{id:1}}","jobId":"bash-1","owner":"{{session:1}}","kind":"bash","label":"echo dsh-sdk-proof-7391","startedAt":0,"toolCall":{"callId":"call_00_Ry17evSfTr0uJnHhg3X93070","rootCallId":"call_00_Ry17evSfTr0uJnHhg3X93070"}}}}}
|
||||
{"method":"session.event","params":{"sessionId":"{{session:1}}","event":{"type":"job/settled","seq":16,"time":0,"data":{"executionId":"{{id:1}}","jobId":"bash-1","owner":"{{session:1}}","kind":"bash","label":"echo dsh-sdk-proof-7391","startedAt":0,"toolCall":{"callId":"call_00_Ry17evSfTr0uJnHhg3X93070","rootCallId":"call_00_Ry17evSfTr0uJnHhg3X93070"},"finishedAt":0,"status":"completed","cause":"producer","producerReported":true,"detail":"exit code: 0"}}}}
|
||||
{"method":"session.event","params":{"sessionId":"{{session:1}}","event":{"type":"job/registered","seq":15,"time":0,"data":{"jobId":"{{id:1}}","owner":"{{session:1}}","kind":"bash","label":"echo dsh-sdk-proof-7391","startedAt":0,"toolCall":{"callId":"call_00_Ry17evSfTr0uJnHhg3X93070","rootCallId":"call_00_Ry17evSfTr0uJnHhg3X93070"}}}}}
|
||||
{"method":"session.event","params":{"sessionId":"{{session:1}}","event":{"type":"job/settled","seq":16,"time":0,"data":{"jobId":"{{id:1}}","owner":"{{session:1}}","kind":"bash","label":"echo dsh-sdk-proof-7391","startedAt":0,"toolCall":{"callId":"call_00_Ry17evSfTr0uJnHhg3X93070","rootCallId":"call_00_Ry17evSfTr0uJnHhg3X93070"},"finishedAt":0,"status":"completed","cause":"producer","producerReported":true,"detail":"exit code: 0"}}}}
|
||||
{"method":"session.event","params":{"sessionId":"{{session:1}}","event":{"type":"tool/result","seq":17,"time":0,"data":{"turn":1,"step":1,"message":{"role":"tool","source":{"kind":"tool","callId":"call_00_Ry17evSfTr0uJnHhg3X93070"},"toolCallId":"call_00_Ry17evSfTr0uJnHhg3X93070","content":[{"type":"text","text":"dsh-sdk-proof-7391\n"}],"isError":false,"id":"{{message:5}}"},"meta":{"cwd":"{{cwd}}"}},"sourceEventSeqs":[14],"surfaceOp":"append"}}}
|
||||
{"method":"session.event","params":{"sessionId":"{{session:1}}","event":{"type":"step/end","seq":18,"time":0,"data":{"turn":1,"step":1}}}}
|
||||
{"method":"session.event","params":{"sessionId":"{{session:1}}","event":{"type":"step/start","seq":19,"time":0,"data":{"turn":1,"step":2}}}}
|
||||
|
||||
@@ -14,8 +14,8 @@
|
||||
{"type":"session/title","data":{"title":"Run this exact command with","messageSeqs":[8],"source":{"kind":"fallback"}}}
|
||||
{"type":"assistant/message","data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"reasoning","text":"The user wants me to run a specific bash command and reply with its stdout only."},{"type":"tool-call","id":"call_00_Ry17evSfTr0uJnHhg3X93070","name":"bash","arguments":"{\"command\": \"echo dsh-sdk-proof-7391\", \"description\": \"Run the echo command as requested\"}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{message:4}}"},"usage":{"inputTokens":123,"outputTokens":89,"cacheReadTokens":1664,"reasoningTokens":17},"stream":[{"type":"chunk","time":1791427912016,"chunk":{"type":"block-start","index":0,"blockType":"reasoning"}},{"type":"reasoning-chunks","time0":1791427912016,"index":0,"dt":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"texts":["The"," user"," wants"," me"," to"," run"," a"," specific"," bash"," command"," and"," reply"," with"," its"," stdout"," only","."]},{"type":"chunk","time":1791427912016,"chunk":{"type":"block-start","index":1,"blockType":"tool-call"}},{"type":"tool-call-chunks","time0":1791427912016,"index":1,"dt":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"id":"call_00_Ry17evSfTr0uJnHhg3X93070","name":"bash","args":["","{","\"","command","\"",": ","\"","echo"," d","sh","-s","dk","-proof","-","739","1","\"",", ","\"","description","\"",": ","\"","Run"," the"," echo"," command"," as"," requested","\"","}"]},{"type":"chunk","time":1791427912016,"chunk":{"type":"block-end","index":0,"block":{"type":"reasoning","text":"The user wants me to run a specific bash command and reply with its stdout only."}}},{"type":"chunk","time":1791427912016,"chunk":{"type":"block-end","index":1,"block":{"type":"tool-call","id":"call_00_Ry17evSfTr0uJnHhg3X93070","name":"bash","arguments":"{\"command\": \"echo dsh-sdk-proof-7391\", \"description\": \"Run the echo command as requested\"}"}}},{"type":"chunk","time":1791427912016,"chunk":{"type":"usage","usage":{"inputTokens":123,"outputTokens":89,"cacheReadTokens":1664,"reasoningTokens":17}}},{"type":"chunk","time":1791427912016,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}]},"surfaceOp":"append"}
|
||||
{"type":"tool/call","data":{"turn":1,"step":1,"callId":"call_00_Ry17evSfTr0uJnHhg3X93070","name":"bash","arguments":"{\"command\": \"echo dsh-sdk-proof-7391\", \"description\": \"Run the echo command as requested\"}"}}
|
||||
{"type":"job/registered","data":{"executionId":"{{id:1}}","jobId":"bash-1","owner":"{{session:1}}","kind":"bash","label":"echo dsh-sdk-proof-7391","startedAt":1791427912022,"toolCall":{"callId":"call_00_Ry17evSfTr0uJnHhg3X93070","rootCallId":"call_00_Ry17evSfTr0uJnHhg3X93070"}}}
|
||||
{"type":"job/settled","data":{"executionId":"{{id:1}}","jobId":"bash-1","owner":"{{session:1}}","kind":"bash","label":"echo dsh-sdk-proof-7391","startedAt":1791427912022,"toolCall":{"callId":"call_00_Ry17evSfTr0uJnHhg3X93070","rootCallId":"call_00_Ry17evSfTr0uJnHhg3X93070"},"finishedAt":1791427912033,"status":"completed","cause":"producer","producerReported":true,"detail":"exit code: 0"}}
|
||||
{"type":"job/registered","data":{"jobId":"{{id:1}}","owner":"{{session:1}}","kind":"bash","label":"echo dsh-sdk-proof-7391","startedAt":1791427912022,"toolCall":{"callId":"call_00_Ry17evSfTr0uJnHhg3X93070","rootCallId":"call_00_Ry17evSfTr0uJnHhg3X93070"}}}
|
||||
{"type":"job/settled","data":{"jobId":"{{id:1}}","owner":"{{session:1}}","kind":"bash","label":"echo dsh-sdk-proof-7391","startedAt":1791427912022,"toolCall":{"callId":"call_00_Ry17evSfTr0uJnHhg3X93070","rootCallId":"call_00_Ry17evSfTr0uJnHhg3X93070"},"finishedAt":1791427912033,"status":"completed","cause":"producer","producerReported":true,"detail":"exit code: 0"}}
|
||||
{"type":"tool/result","data":{"turn":1,"step":1,"message":{"role":"tool","source":{"kind":"tool","callId":"call_00_Ry17evSfTr0uJnHhg3X93070"},"toolCallId":"call_00_Ry17evSfTr0uJnHhg3X93070","content":[{"type":"text","text":"dsh-sdk-proof-7391\n"}],"isError":false,"id":"{{message:5}}"},"meta":{"cwd":"{{cwd}}"}},"sourceEventSeqs":[14],"surfaceOp":"append"}
|
||||
{"type":"step/end","data":{"turn":1,"step":1}}
|
||||
{"type":"step/start","data":{"turn":1,"step":2}}
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
{"type":"request/header","data":{"header":{"config":{"provider":"deepseek-official","model":"deepseek-v4-flash"},"tools":"{{tools}}"},"reason":"initial"}}
|
||||
{"type":"request/context","data":{"provider":"deepseek-official","model":"deepseek-v4-flash"}}
|
||||
{"type":"session/title","data":{"title":"Reply with exactly the word:","messageSeqs":[8],"source":{"kind":"fallback"}}}
|
||||
{"type":"assistant/message","data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"reasoning","text":"The user wants me to reply with exactly the word \"ONE\" and use no tools."},{"type":"text","text":"ONE"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{message:4}}"},"usage":{"inputTokens":2864,"outputTokens":20,"cacheReadTokens":0,"reasoningTokens":18},"stream":[{"type":"chunk","time":1791434053910,"chunk":{"type":"block-start","index":0,"blockType":"reasoning"}},{"type":"reasoning-chunks","time0":1791434053910,"index":0,"dt":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"texts":["The"," user"," wants"," me"," to"," reply"," with"," exactly"," the"," word"," \"","ONE","\""," and"," use"," no"," tools","."]},{"type":"chunk","time":1791434053910,"chunk":{"type":"block-start","index":1,"blockType":"text"}},{"type":"text-chunks","time0":1791434053910,"index":1,"dt":[],"texts":["ONE"]},{"type":"chunk","time":1791434053910,"chunk":{"type":"block-end","index":0,"block":{"type":"reasoning","text":"The user wants me to reply with exactly the word \"ONE\" and use no tools."}}},{"type":"chunk","time":1791434053910,"chunk":{"type":"block-end","index":1,"block":{"type":"text","text":"ONE"}}},{"type":"chunk","time":1791434053910,"chunk":{"type":"usage","usage":{"inputTokens":2864,"outputTokens":20,"cacheReadTokens":0,"reasoningTokens":18}}},{"type":"chunk","time":1791434053910,"chunk":{"type":"finish","reason":{"kind":"stop"}}}]},"surfaceOp":"append"}
|
||||
{"type":"assistant/message","data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"reasoning","text":"The user wants me to reply with exactly the word \"ONE\" and use no tools."},{"type":"text","text":"ONE"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{message:4}}"},"usage":{"inputTokens":2864,"outputTokens":20,"cacheReadTokens":0,"reasoningTokens":18},"stream":[{"type":"chunk","time":1791444991204,"chunk":{"type":"block-start","index":0,"blockType":"reasoning"}},{"type":"reasoning-chunks","time0":1791444991204,"index":0,"dt":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"texts":["The"," user"," wants"," me"," to"," reply"," with"," exactly"," the"," word"," \"","ONE","\""," and"," use"," no"," tools","."]},{"type":"chunk","time":1791444991204,"chunk":{"type":"block-start","index":1,"blockType":"text"}},{"type":"text-chunks","time0":1791444991204,"index":1,"dt":[],"texts":["ONE"]},{"type":"chunk","time":1791444991204,"chunk":{"type":"block-end","index":0,"block":{"type":"reasoning","text":"The user wants me to reply with exactly the word \"ONE\" and use no tools."}}},{"type":"chunk","time":1791444991204,"chunk":{"type":"block-end","index":1,"block":{"type":"text","text":"ONE"}}},{"type":"chunk","time":1791444991204,"chunk":{"type":"usage","usage":{"inputTokens":2864,"outputTokens":20,"cacheReadTokens":0,"reasoningTokens":18}}},{"type":"chunk","time":1791444991204,"chunk":{"type":"finish","reason":{"kind":"stop"}}}]},"surfaceOp":"append"}
|
||||
{"type":"step/end","data":{"turn":1,"step":1}}
|
||||
{"type":"turn/end","data":{"turn":1,"reason":{"kind":"completed"}}}
|
||||
{"type":"agent/inbox/spliced","data":{"target":"next-turn","start":0,"inserted":[{"content":[{"type":"text","text":"Reply with exactly the word: TWO. No tools."}],"source":{"kind":"user"},"role":"user","id":"{{message:5}}"}]}}
|
||||
@@ -20,6 +20,6 @@
|
||||
{"type":"agent/inbox/spliced","data":{"target":"next-turn","start":0,"removedCount":1,"inserted":[]}}
|
||||
{"type":"step/start","data":{"turn":2,"step":1}}
|
||||
{"type":"user/message","data":{"content":[{"type":"text","text":"Reply with exactly the word: TWO. No tools."}],"source":{"kind":"user"},"role":"user","id":"{{message:5}}"},"surfaceOp":"append"}
|
||||
{"type":"assistant/message","data":{"turn":2,"step":1,"message":{"role":"assistant","content":[{"type":"reasoning","text":"The user wants me to reply with exactly the word \"TWO\" and no tools."},{"type":"text","text":"TWO"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{message:6}}"},"usage":{"inputTokens":64,"outputTokens":21,"cacheReadTokens":2816,"reasoningTokens":18},"stream":[{"type":"chunk","time":1791434053927,"chunk":{"type":"block-start","index":0,"blockType":"reasoning"}},{"type":"reasoning-chunks","time0":1791434053927,"index":0,"dt":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"texts":["The"," user"," wants"," me"," to"," reply"," with"," exactly"," the"," word"," \"","T","WO","\""," and"," no"," tools","."]},{"type":"chunk","time":1791434053927,"chunk":{"type":"block-start","index":1,"blockType":"text"}},{"type":"text-chunks","time0":1791434053927,"index":1,"dt":[0],"texts":["T","WO"]},{"type":"chunk","time":1791434053927,"chunk":{"type":"block-end","index":0,"block":{"type":"reasoning","text":"The user wants me to reply with exactly the word \"TWO\" and no tools."}}},{"type":"chunk","time":1791434053927,"chunk":{"type":"block-end","index":1,"block":{"type":"text","text":"TWO"}}},{"type":"chunk","time":1791434053927,"chunk":{"type":"usage","usage":{"inputTokens":64,"outputTokens":21,"cacheReadTokens":2816,"reasoningTokens":18}}},{"type":"chunk","time":1791434053927,"chunk":{"type":"finish","reason":{"kind":"stop"}}}]},"surfaceOp":"append"}
|
||||
{"type":"assistant/message","data":{"turn":2,"step":1,"message":{"role":"assistant","content":[{"type":"reasoning","text":"The user wants me to reply with exactly the word \"TWO\" and no tools."},{"type":"text","text":"TWO"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{message:6}}"},"usage":{"inputTokens":64,"outputTokens":21,"cacheReadTokens":2816,"reasoningTokens":18},"stream":[{"type":"chunk","time":1791444991222,"chunk":{"type":"block-start","index":0,"blockType":"reasoning"}},{"type":"reasoning-chunks","time0":1791444991222,"index":0,"dt":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"texts":["The"," user"," wants"," me"," to"," reply"," with"," exactly"," the"," word"," \"","T","WO","\""," and"," no"," tools","."]},{"type":"chunk","time":1791444991222,"chunk":{"type":"block-start","index":1,"blockType":"text"}},{"type":"text-chunks","time0":1791444991222,"index":1,"dt":[0],"texts":["T","WO"]},{"type":"chunk","time":1791444991222,"chunk":{"type":"block-end","index":0,"block":{"type":"reasoning","text":"The user wants me to reply with exactly the word \"TWO\" and no tools."}}},{"type":"chunk","time":1791444991222,"chunk":{"type":"block-end","index":1,"block":{"type":"text","text":"TWO"}}},{"type":"chunk","time":1791444991222,"chunk":{"type":"usage","usage":{"inputTokens":64,"outputTokens":21,"cacheReadTokens":2816,"reasoningTokens":18}}},{"type":"chunk","time":1791444991222,"chunk":{"type":"finish","reason":{"kind":"stop"}}}]},"surfaceOp":"append"}
|
||||
{"type":"step/end","data":{"turn":2,"step":1}}
|
||||
{"type":"turn/end","data":{"turn":2,"reason":{"kind":"completed"}}}
|
||||
@@ -12,12 +12,12 @@
|
||||
{"method":"session.event","params":{"sessionId":"{{session:1}}","event":{"type":"assistant/message","seq":13,"time":0,"data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"reasoning","text":"The user wants me to write a single run_code program that:\n1. Calls bash tool twice: `echo CODE_ONE` and `echo CODE_TWO`\n2. console.log exactly `captured output`\n3. Return the two outputs joined with a plus sign\n\nLet me write this."},{"type":"tool-call","id":"call_00_UiQPVqoELyzBZCY5pm1z7875","name":"run_code","arguments":"{\"code\": \"\\nconst out1 = await tools.bash({command: \\\"echo CODE_ONE\\\", description: \\\"Print CODE_ONE\\\"});\\nconst out2 = await tools.bash({command: \\\"echo CODE_TWO\\\", description: \\\"Print CODE_TWO\\\"});\\nconsole.log(\\\"captured output\\\");\\nconst text1 = out1.stdout.text.trim();\\nconst text2 = out2.stdout.text.trim();\\nreturn text1 + \\\"+\\\" + text2;\\n\", \"description\": \"Run two echo commands and join outputs\"}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{message:4}}"},"usage":{"inputTokens":6152,"outputTokens":214,"cacheReadTokens":0,"reasoningTokens":60},"stream":[{"type":"chunk","time":0,"chunk":{"type":"block-start","index":0,"blockType":"reasoning"}},{"type":"reasoning-chunks","time0":0,"index":0,"dt":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"texts":["The"," user"," wants"," me"," to"," write"," a"," single"," run","_code"," program"," that",":\n","1","."," Calls"," bash"," tool"," twice",":"," `","echo"," CODE","_","ONE","`"," and"," `","echo"," CODE","_T","WO","`\n","2","."," console",".log"," exactly"," `","capt","ured"," output","`\n","3","."," Return"," the"," two"," outputs"," joined"," with"," a"," plus"," sign","\n\n","Let"," me"," write"," this","."]},{"type":"chunk","time":0,"chunk":{"type":"block-start","index":1,"blockType":"tool-call"}},{"type":"tool-call-chunks","time0":0,"index":1,"dt":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"id":"call_00_UiQPVqoELyzBZCY5pm1z7875","name":"run_code","args":["","{","\"","code","\"",": ","\"","\\n","const"," out","1"," ="," await"," tools",".b","ash","({","command",":"," \\\"","echo"," CODE","_","ONE","\\\","," description",":"," \\\"","Print"," CODE","_","ONE","\\\"","});\\n","const"," out","2"," ="," await"," tools",".b","ash","({","command",":"," \\\"","echo"," CODE","_T","WO","\\\","," description",":"," \\\"","Print"," CODE","_T","WO","\\\"","});\\n","console",".log","(\\\"","capt","ured"," output","\\\");\\n","const"," text","1"," ="," out","1",".stdout",".text",".trim","();\\n","const"," text","2"," ="," out","2",".stdout",".text",".trim","();\\n","return"," text","1"," +"," \\\"+","\\\""," +"," text","2",";\\n","\"",", ","\"","description","\"",": ","\"","Run"," two"," echo"," commands"," and"," join"," outputs","\"","}"]},{"type":"chunk","time":0,"chunk":{"type":"block-end","index":0,"block":{"type":"reasoning","text":"The user wants me to write a single run_code program that:\n1. Calls bash tool twice: `echo CODE_ONE` and `echo CODE_TWO`\n2. console.log exactly `captured output`\n3. Return the two outputs joined with a plus sign\n\nLet me write this."}}},{"type":"chunk","time":0,"chunk":{"type":"block-end","index":1,"block":{"type":"tool-call","id":"call_00_UiQPVqoELyzBZCY5pm1z7875","name":"run_code","arguments":"{\"code\": \"\\nconst out1 = await tools.bash({command: \\\"echo CODE_ONE\\\", description: \\\"Print CODE_ONE\\\"});\\nconst out2 = await tools.bash({command: \\\"echo CODE_TWO\\\", description: \\\"Print CODE_TWO\\\"});\\nconsole.log(\\\"captured output\\\");\\nconst text1 = out1.stdout.text.trim();\\nconst text2 = out2.stdout.text.trim();\\nreturn text1 + \\\"+\\\" + text2;\\n\", \"description\": \"Run two echo commands and join outputs\"}"}}},{"type":"chunk","time":0,"chunk":{"type":"usage","usage":{"inputTokens":6152,"outputTokens":214,"cacheReadTokens":0,"reasoningTokens":60}}},{"type":"chunk","time":0,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}]},"surfaceOp":"append"}}}
|
||||
{"method":"session.event","params":{"sessionId":"{{session:1}}","event":{"type":"tool/call","seq":14,"time":0,"data":{"turn":1,"step":1,"callId":"call_00_UiQPVqoELyzBZCY5pm1z7875","name":"run_code","arguments":"{\"code\": \"\\nconst out1 = await tools.bash({command: \\\"echo CODE_ONE\\\", description: \\\"Print CODE_ONE\\\"});\\nconst out2 = await tools.bash({command: \\\"echo CODE_TWO\\\", description: \\\"Print CODE_TWO\\\"});\\nconsole.log(\\\"captured output\\\");\\nconst text1 = out1.stdout.text.trim();\\nconst text2 = out2.stdout.text.trim();\\nreturn text1 + \\\"+\\\" + text2;\\n\", \"description\": \"Run two echo commands and join outputs\"}"}}}}
|
||||
{"method":"session.event","params":{"sessionId":"{{session:1}}","event":{"type":"tool/ptc-dispatch-start","seq":15,"time":0,"data":{"rootCallId":"call_00_UiQPVqoELyzBZCY5pm1z7875","parentCallId":"call_00_UiQPVqoELyzBZCY5pm1z7875","subCallId":"call_00_UiQPVqoELyzBZCY5pm1z7875:ptc:1","name":"bash","arguments":{"command":"echo CODE_ONE","description":"Print CODE_ONE"}}}}}
|
||||
{"method":"session.event","params":{"sessionId":"{{session:1}}","event":{"type":"job/registered","seq":16,"time":0,"data":{"executionId":"{{id:1}}","jobId":"bash-1","owner":"{{session:1}}","kind":"bash","label":"echo CODE_ONE","startedAt":0,"toolCall":{"callId":"call_00_UiQPVqoELyzBZCY5pm1z7875:ptc:1","rootCallId":"call_00_UiQPVqoELyzBZCY5pm1z7875"}}}}}
|
||||
{"method":"session.event","params":{"sessionId":"{{session:1}}","event":{"type":"job/settled","seq":17,"time":0,"data":{"executionId":"{{id:1}}","jobId":"bash-1","owner":"{{session:1}}","kind":"bash","label":"echo CODE_ONE","startedAt":0,"toolCall":{"callId":"call_00_UiQPVqoELyzBZCY5pm1z7875:ptc:1","rootCallId":"call_00_UiQPVqoELyzBZCY5pm1z7875"},"finishedAt":0,"status":"completed","cause":"producer","producerReported":true,"detail":"exit code: 0"}}}}
|
||||
{"method":"session.event","params":{"sessionId":"{{session:1}}","event":{"type":"job/registered","seq":16,"time":0,"data":{"jobId":"{{id:1}}","owner":"{{session:1}}","kind":"bash","label":"echo CODE_ONE","startedAt":0,"toolCall":{"callId":"call_00_UiQPVqoELyzBZCY5pm1z7875:ptc:1","rootCallId":"call_00_UiQPVqoELyzBZCY5pm1z7875"}}}}}
|
||||
{"method":"session.event","params":{"sessionId":"{{session:1}}","event":{"type":"job/settled","seq":17,"time":0,"data":{"jobId":"{{id:1}}","owner":"{{session:1}}","kind":"bash","label":"echo CODE_ONE","startedAt":0,"toolCall":{"callId":"call_00_UiQPVqoELyzBZCY5pm1z7875:ptc:1","rootCallId":"call_00_UiQPVqoELyzBZCY5pm1z7875"},"finishedAt":0,"status":"completed","cause":"producer","producerReported":true,"detail":"exit code: 0"}}}}
|
||||
{"method":"session.event","params":{"sessionId":"{{session:1}}","event":{"type":"tool/ptc-dispatch","seq":18,"time":0,"data":{"rootCallId":"call_00_UiQPVqoELyzBZCY5pm1z7875","parentCallId":"call_00_UiQPVqoELyzBZCY5pm1z7875","subCallId":"call_00_UiQPVqoELyzBZCY5pm1z7875:ptc:1","name":"bash","arguments":{"command":"echo CODE_ONE","description":"Print CODE_ONE"},"isError":false,"content":[{"type":"text","text":"CODE_ONE\n"}],"meta":{"cwd":"{{cwd}}"}}}}}
|
||||
{"method":"session.event","params":{"sessionId":"{{session:1}}","event":{"type":"tool/ptc-dispatch-start","seq":19,"time":0,"data":{"rootCallId":"call_00_UiQPVqoELyzBZCY5pm1z7875","parentCallId":"call_00_UiQPVqoELyzBZCY5pm1z7875","subCallId":"call_00_UiQPVqoELyzBZCY5pm1z7875:ptc:2","name":"bash","arguments":{"command":"echo CODE_TWO","description":"Print CODE_TWO"}}}}}
|
||||
{"method":"session.event","params":{"sessionId":"{{session:1}}","event":{"type":"job/registered","seq":20,"time":0,"data":{"executionId":"{{id:2}}","jobId":"bash-2","owner":"{{session:1}}","kind":"bash","label":"echo CODE_TWO","startedAt":0,"toolCall":{"callId":"call_00_UiQPVqoELyzBZCY5pm1z7875:ptc:2","rootCallId":"call_00_UiQPVqoELyzBZCY5pm1z7875"}}}}}
|
||||
{"method":"session.event","params":{"sessionId":"{{session:1}}","event":{"type":"job/settled","seq":21,"time":0,"data":{"executionId":"{{id:2}}","jobId":"bash-2","owner":"{{session:1}}","kind":"bash","label":"echo CODE_TWO","startedAt":0,"toolCall":{"callId":"call_00_UiQPVqoELyzBZCY5pm1z7875:ptc:2","rootCallId":"call_00_UiQPVqoELyzBZCY5pm1z7875"},"finishedAt":0,"status":"completed","cause":"producer","producerReported":true,"detail":"exit code: 0"}}}}
|
||||
{"method":"session.event","params":{"sessionId":"{{session:1}}","event":{"type":"job/registered","seq":20,"time":0,"data":{"jobId":"{{id:2}}","owner":"{{session:1}}","kind":"bash","label":"echo CODE_TWO","startedAt":0,"toolCall":{"callId":"call_00_UiQPVqoELyzBZCY5pm1z7875:ptc:2","rootCallId":"call_00_UiQPVqoELyzBZCY5pm1z7875"}}}}}
|
||||
{"method":"session.event","params":{"sessionId":"{{session:1}}","event":{"type":"job/settled","seq":21,"time":0,"data":{"jobId":"{{id:2}}","owner":"{{session:1}}","kind":"bash","label":"echo CODE_TWO","startedAt":0,"toolCall":{"callId":"call_00_UiQPVqoELyzBZCY5pm1z7875:ptc:2","rootCallId":"call_00_UiQPVqoELyzBZCY5pm1z7875"},"finishedAt":0,"status":"completed","cause":"producer","producerReported":true,"detail":"exit code: 0"}}}}
|
||||
{"method":"session.event","params":{"sessionId":"{{session:1}}","event":{"type":"tool/ptc-dispatch","seq":22,"time":0,"data":{"rootCallId":"call_00_UiQPVqoELyzBZCY5pm1z7875","parentCallId":"call_00_UiQPVqoELyzBZCY5pm1z7875","subCallId":"call_00_UiQPVqoELyzBZCY5pm1z7875:ptc:2","name":"bash","arguments":{"command":"echo CODE_TWO","description":"Print CODE_TWO"},"isError":false,"content":[{"type":"text","text":"CODE_TWO\n"}],"meta":{"cwd":"{{cwd}}"}}}}}
|
||||
{"method":"session.event","params":{"sessionId":"{{session:1}}","event":{"type":"tool/result","seq":23,"time":0,"data":{"turn":1,"step":1,"message":{"role":"tool","source":{"kind":"tool","callId":"call_00_UiQPVqoELyzBZCY5pm1z7875"},"toolCallId":"call_00_UiQPVqoELyzBZCY5pm1z7875","content":[{"type":"text","text":"captured output\nCODE_ONE+CODE_TWO"}],"isError":false,"id":"{{message:5}}"}},"sourceEventSeqs":[14],"surfaceOp":"append"}}}
|
||||
{"method":"session.event","params":{"sessionId":"{{session:1}}","event":{"type":"step/end","seq":24,"time":0,"data":{"turn":1,"step":1}}}}
|
||||
|
||||
@@ -15,12 +15,12 @@
|
||||
{"type":"assistant/message","data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"reasoning","text":"The user wants me to write a single run_code program that:\n1. Calls bash tool twice: `echo CODE_ONE` and `echo CODE_TWO`\n2. console.log exactly `captured output`\n3. Return the two outputs joined with a plus sign\n\nLet me write this."},{"type":"tool-call","id":"call_00_UiQPVqoELyzBZCY5pm1z7875","name":"run_code","arguments":"{\"code\": \"\\nconst out1 = await tools.bash({command: \\\"echo CODE_ONE\\\", description: \\\"Print CODE_ONE\\\"});\\nconst out2 = await tools.bash({command: \\\"echo CODE_TWO\\\", description: \\\"Print CODE_TWO\\\"});\\nconsole.log(\\\"captured output\\\");\\nconst text1 = out1.stdout.text.trim();\\nconst text2 = out2.stdout.text.trim();\\nreturn text1 + \\\"+\\\" + text2;\\n\", \"description\": \"Run two echo commands and join outputs\"}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{message:4}}"},"usage":{"inputTokens":6152,"outputTokens":214,"cacheReadTokens":0,"reasoningTokens":60},"stream":[{"type":"chunk","time":1791427916811,"chunk":{"type":"block-start","index":0,"blockType":"reasoning"}},{"type":"reasoning-chunks","time0":1791427916811,"index":0,"dt":[0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"texts":["The"," user"," wants"," me"," to"," write"," a"," single"," run","_code"," program"," that",":\n","1","."," Calls"," bash"," tool"," twice",":"," `","echo"," CODE","_","ONE","`"," and"," `","echo"," CODE","_T","WO","`\n","2","."," console",".log"," exactly"," `","capt","ured"," output","`\n","3","."," Return"," the"," two"," outputs"," joined"," with"," a"," plus"," sign","\n\n","Let"," me"," write"," this","."]},{"type":"chunk","time":1791427916812,"chunk":{"type":"block-start","index":1,"blockType":"tool-call"}},{"type":"tool-call-chunks","time0":1791427916812,"index":1,"dt":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"id":"call_00_UiQPVqoELyzBZCY5pm1z7875","name":"run_code","args":["","{","\"","code","\"",": ","\"","\\n","const"," out","1"," ="," await"," tools",".b","ash","({","command",":"," \\\"","echo"," CODE","_","ONE","\\\","," description",":"," \\\"","Print"," CODE","_","ONE","\\\"","});\\n","const"," out","2"," ="," await"," tools",".b","ash","({","command",":"," \\\"","echo"," CODE","_T","WO","\\\","," description",":"," \\\"","Print"," CODE","_T","WO","\\\"","});\\n","console",".log","(\\\"","capt","ured"," output","\\\");\\n","const"," text","1"," ="," out","1",".stdout",".text",".trim","();\\n","const"," text","2"," ="," out","2",".stdout",".text",".trim","();\\n","return"," text","1"," +"," \\\"+","\\\""," +"," text","2",";\\n","\"",", ","\"","description","\"",": ","\"","Run"," two"," echo"," commands"," and"," join"," outputs","\"","}"]},{"type":"chunk","time":1791427916812,"chunk":{"type":"block-end","index":0,"block":{"type":"reasoning","text":"The user wants me to write a single run_code program that:\n1. Calls bash tool twice: `echo CODE_ONE` and `echo CODE_TWO`\n2. console.log exactly `captured output`\n3. Return the two outputs joined with a plus sign\n\nLet me write this."}}},{"type":"chunk","time":1791427916812,"chunk":{"type":"block-end","index":1,"block":{"type":"tool-call","id":"call_00_UiQPVqoELyzBZCY5pm1z7875","name":"run_code","arguments":"{\"code\": \"\\nconst out1 = await tools.bash({command: \\\"echo CODE_ONE\\\", description: \\\"Print CODE_ONE\\\"});\\nconst out2 = await tools.bash({command: \\\"echo CODE_TWO\\\", description: \\\"Print CODE_TWO\\\"});\\nconsole.log(\\\"captured output\\\");\\nconst text1 = out1.stdout.text.trim();\\nconst text2 = out2.stdout.text.trim();\\nreturn text1 + \\\"+\\\" + text2;\\n\", \"description\": \"Run two echo commands and join outputs\"}"}}},{"type":"chunk","time":1791427916812,"chunk":{"type":"usage","usage":{"inputTokens":6152,"outputTokens":214,"cacheReadTokens":0,"reasoningTokens":60}}},{"type":"chunk","time":1791427916812,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}]},"surfaceOp":"append"}
|
||||
{"type":"tool/call","data":{"turn":1,"step":1,"callId":"call_00_UiQPVqoELyzBZCY5pm1z7875","name":"run_code","arguments":"{\"code\": \"\\nconst out1 = await tools.bash({command: \\\"echo CODE_ONE\\\", description: \\\"Print CODE_ONE\\\"});\\nconst out2 = await tools.bash({command: \\\"echo CODE_TWO\\\", description: \\\"Print CODE_TWO\\\"});\\nconsole.log(\\\"captured output\\\");\\nconst text1 = out1.stdout.text.trim();\\nconst text2 = out2.stdout.text.trim();\\nreturn text1 + \\\"+\\\" + text2;\\n\", \"description\": \"Run two echo commands and join outputs\"}"}}
|
||||
{"type":"tool/ptc-dispatch-start","data":{"rootCallId":"call_00_UiQPVqoELyzBZCY5pm1z7875","parentCallId":"call_00_UiQPVqoELyzBZCY5pm1z7875","subCallId":"call_00_UiQPVqoELyzBZCY5pm1z7875:ptc:1","name":"bash","arguments":{"command":"echo CODE_ONE","description":"Print CODE_ONE"}}}
|
||||
{"type":"job/registered","data":{"executionId":"{{id:1}}","jobId":"bash-1","owner":"{{session:1}}","kind":"bash","label":"echo CODE_ONE","startedAt":1791427916854,"toolCall":{"callId":"call_00_UiQPVqoELyzBZCY5pm1z7875:ptc:1","rootCallId":"call_00_UiQPVqoELyzBZCY5pm1z7875"}}}
|
||||
{"type":"job/settled","data":{"executionId":"{{id:1}}","jobId":"bash-1","owner":"{{session:1}}","kind":"bash","label":"echo CODE_ONE","startedAt":1791427916854,"toolCall":{"callId":"call_00_UiQPVqoELyzBZCY5pm1z7875:ptc:1","rootCallId":"call_00_UiQPVqoELyzBZCY5pm1z7875"},"finishedAt":1791427916857,"status":"completed","cause":"producer","producerReported":true,"detail":"exit code: 0"}}
|
||||
{"type":"job/registered","data":{"jobId":"{{id:1}}","owner":"{{session:1}}","kind":"bash","label":"echo CODE_ONE","startedAt":1791427916854,"toolCall":{"callId":"call_00_UiQPVqoELyzBZCY5pm1z7875:ptc:1","rootCallId":"call_00_UiQPVqoELyzBZCY5pm1z7875"}}}
|
||||
{"type":"job/settled","data":{"jobId":"{{id:1}}","owner":"{{session:1}}","kind":"bash","label":"echo CODE_ONE","startedAt":1791427916854,"toolCall":{"callId":"call_00_UiQPVqoELyzBZCY5pm1z7875:ptc:1","rootCallId":"call_00_UiQPVqoELyzBZCY5pm1z7875"},"finishedAt":1791427916857,"status":"completed","cause":"producer","producerReported":true,"detail":"exit code: 0"}}
|
||||
{"type":"tool/ptc-dispatch","data":{"rootCallId":"call_00_UiQPVqoELyzBZCY5pm1z7875","parentCallId":"call_00_UiQPVqoELyzBZCY5pm1z7875","subCallId":"call_00_UiQPVqoELyzBZCY5pm1z7875:ptc:1","name":"bash","arguments":{"command":"echo CODE_ONE","description":"Print CODE_ONE"},"isError":false,"content":[{"type":"text","text":"CODE_ONE\n"}],"meta":{"cwd":"{{cwd}}"}}}
|
||||
{"type":"tool/ptc-dispatch-start","data":{"rootCallId":"call_00_UiQPVqoELyzBZCY5pm1z7875","parentCallId":"call_00_UiQPVqoELyzBZCY5pm1z7875","subCallId":"call_00_UiQPVqoELyzBZCY5pm1z7875:ptc:2","name":"bash","arguments":{"command":"echo CODE_TWO","description":"Print CODE_TWO"}}}
|
||||
{"type":"job/registered","data":{"executionId":"{{id:2}}","jobId":"bash-2","owner":"{{session:1}}","kind":"bash","label":"echo CODE_TWO","startedAt":1791427916860,"toolCall":{"callId":"call_00_UiQPVqoELyzBZCY5pm1z7875:ptc:2","rootCallId":"call_00_UiQPVqoELyzBZCY5pm1z7875"}}}
|
||||
{"type":"job/settled","data":{"executionId":"{{id:2}}","jobId":"bash-2","owner":"{{session:1}}","kind":"bash","label":"echo CODE_TWO","startedAt":1791427916860,"toolCall":{"callId":"call_00_UiQPVqoELyzBZCY5pm1z7875:ptc:2","rootCallId":"call_00_UiQPVqoELyzBZCY5pm1z7875"},"finishedAt":1791427916862,"status":"completed","cause":"producer","producerReported":true,"detail":"exit code: 0"}}
|
||||
{"type":"job/registered","data":{"jobId":"{{id:2}}","owner":"{{session:1}}","kind":"bash","label":"echo CODE_TWO","startedAt":1791427916860,"toolCall":{"callId":"call_00_UiQPVqoELyzBZCY5pm1z7875:ptc:2","rootCallId":"call_00_UiQPVqoELyzBZCY5pm1z7875"}}}
|
||||
{"type":"job/settled","data":{"jobId":"{{id:2}}","owner":"{{session:1}}","kind":"bash","label":"echo CODE_TWO","startedAt":1791427916860,"toolCall":{"callId":"call_00_UiQPVqoELyzBZCY5pm1z7875:ptc:2","rootCallId":"call_00_UiQPVqoELyzBZCY5pm1z7875"},"finishedAt":1791427916862,"status":"completed","cause":"producer","producerReported":true,"detail":"exit code: 0"}}
|
||||
{"type":"tool/ptc-dispatch","data":{"rootCallId":"call_00_UiQPVqoELyzBZCY5pm1z7875","parentCallId":"call_00_UiQPVqoELyzBZCY5pm1z7875","subCallId":"call_00_UiQPVqoELyzBZCY5pm1z7875:ptc:2","name":"bash","arguments":{"command":"echo CODE_TWO","description":"Print CODE_TWO"},"isError":false,"content":[{"type":"text","text":"CODE_TWO\n"}],"meta":{"cwd":"{{cwd}}"}}}
|
||||
{"type":"tool/result","data":{"turn":1,"step":1,"message":{"role":"tool","source":{"kind":"tool","callId":"call_00_UiQPVqoELyzBZCY5pm1z7875"},"toolCallId":"call_00_UiQPVqoELyzBZCY5pm1z7875","content":[{"type":"text","text":"captured output\nCODE_ONE+CODE_TWO"}],"isError":false,"id":"{{message:5}}"}},"sourceEventSeqs":[14],"surfaceOp":"append"}
|
||||
{"type":"step/end","data":{"turn":1,"step":1}}
|
||||
|
||||
@@ -52,7 +52,7 @@
|
||||
"index": 0,
|
||||
"id": "async-confinement-result",
|
||||
"name": "job_output",
|
||||
"argumentsDelta": "{\"job_id\":\"bash-1\",\"wait\":true}"
|
||||
"argumentsDelta": "{\"job_id\":\"{{fromRequest:(bash-[0-9a-f-]{36})}}\",\"wait\":true}"
|
||||
},
|
||||
{
|
||||
"type": "block-end",
|
||||
@@ -61,7 +61,7 @@
|
||||
"type": "tool-call",
|
||||
"id": "async-confinement-result",
|
||||
"name": "job_output",
|
||||
"arguments": "{\"job_id\":\"bash-1\",\"wait\":true}"
|
||||
"arguments": "{\"job_id\":\"{{fromRequest:(bash-[0-9a-f-]{36})}}\",\"wait\":true}"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -14,16 +14,16 @@
|
||||
{"type":"session/title","data":{"title":"Start printf SHOULD_NOT_RUN as one","messageSeqs":[8],"source":{"kind":"fallback"}}}
|
||||
{"type":"assistant/message","data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"tool-call","id":"async-confinement-start","name":"bash","arguments":"{\"command\":\"printf SHOULD_NOT_RUN > unexpected-command.txt\",\"description\":\"Start a background command under confinement\",\"run_in_background\":true}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{message:4}}"},"usage":{"inputTokens":10,"outputTokens":5},"stream":[{"type":"chunk","time":1791434082496,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}},{"type":"tool-call-chunks","time0":1791434082496,"index":0,"dt":[],"id":"async-confinement-start","name":"bash","args":["{\"command\":\"printf SHOULD_NOT_RUN > unexpected-command.txt\",\"description\":\"Start a background command under confinement\",\"run_in_background\":true}"]},{"type":"chunk","time":1791434082496,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"async-confinement-start","name":"bash","arguments":"{\"command\":\"printf SHOULD_NOT_RUN > unexpected-command.txt\",\"description\":\"Start a background command under confinement\",\"run_in_background\":true}"}}},{"type":"chunk","time":1791434082496,"chunk":{"type":"usage","usage":{"inputTokens":10,"outputTokens":5}}},{"type":"chunk","time":1791434082496,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}]},"surfaceOp":"append"}
|
||||
{"type":"tool/call","data":{"turn":1,"step":1,"callId":"async-confinement-start","name":"bash","arguments":"{\"command\":\"printf SHOULD_NOT_RUN > unexpected-command.txt\",\"description\":\"Start a background command under confinement\",\"run_in_background\":true}"}}
|
||||
{"type":"job/registered","data":{"executionId":"{{id:1}}","jobId":"bash-1","owner":"{{session:1}}","kind":"bash","label":"printf SHOULD_NOT_RUN > unexpected-command.txt","startedAt":1791434082502,"toolCall":{"callId":"async-confinement-start","rootCallId":"async-confinement-start"}}}
|
||||
{"type":"job/settled","data":{"executionId":"{{id:1}}","jobId":"bash-1","owner":"{{session:1}}","kind":"bash","label":"printf SHOULD_NOT_RUN > unexpected-command.txt","startedAt":1791434082502,"toolCall":{"callId":"async-confinement-start","rootCallId":"async-confinement-start"},"finishedAt":1791434082504,"status":"failed","cause":"producer","producerReported":true,"detail":"sandbox mode \"read-only\" is requested but no sandbox backend is usable on this host; refusing to run the command unconfined. Install bubblewrap or run a Landlock-enforcing kernel (Linux), ensure sandbox-exec is usable (macOS), or ensure the ACL restricted-token runner can start (Windows) — otherwise switch the consumer to danger-full-access. Runner failure: fixture asynchronous confinement refused"}}
|
||||
{"type":"agent/inbox/spliced","data":{"target":"next-step","start":0,"inserted":[{"content":[{"type":"text","text":"background job bash-1 (bash: printf SHOULD_NOT_RUN > unexpected-command.txt) finished [status: failed, sandbox mode \"read-only\" is requested but no sandbox backend is usable on this host; refusing to run the command unconfined. Install bubblewrap or run a Landlock-enforcing kernel (Linux), ensure sandbox-exec is usable (macOS), or ensure the ACL restricted-token runner can start (Windows) — otherwise switch the consumer to danger-full-access. Runner failure: fixture asynchronous confinement refused]. Read its output with job_output."}],"source":{"kind":"tool-jobs","form":"notice","summary":"bash printf SHOULD_NOT_RUN > unexpected-command.txt [status: failed, sandbox mode \"read-only\" is requested but no sandb…"},"role":"user","id":"{{message:5}}"}]}}
|
||||
{"type":"tool/result","data":{"turn":1,"step":1,"message":{"role":"tool","source":{"kind":"tool","callId":"async-confinement-start"},"toolCallId":"async-confinement-start","content":[{"type":"text","text":"started background job bash-1"}],"isError":false,"id":"{{message:6}}"},"meta":{"cwd":"{{cwd}}"}},"sourceEventSeqs":[14],"surfaceOp":"append"}
|
||||
{"type":"job/registered","data":{"jobId":"{{id:1}}","owner":"{{session:1}}","kind":"bash","label":"printf SHOULD_NOT_RUN > unexpected-command.txt","startedAt":1791434082502,"toolCall":{"callId":"async-confinement-start","rootCallId":"async-confinement-start"}}}
|
||||
{"type":"job/settled","data":{"jobId":"{{id:1}}","owner":"{{session:1}}","kind":"bash","label":"printf SHOULD_NOT_RUN > unexpected-command.txt","startedAt":1791434082502,"toolCall":{"callId":"async-confinement-start","rootCallId":"async-confinement-start"},"finishedAt":1791434082504,"status":"failed","cause":"producer","producerReported":true,"detail":"sandbox mode \"read-only\" is requested but no sandbox backend is usable on this host; refusing to run the command unconfined. Install bubblewrap or run a Landlock-enforcing kernel (Linux), ensure sandbox-exec is usable (macOS), or ensure the ACL restricted-token runner can start (Windows) — otherwise switch the consumer to danger-full-access. Runner failure: fixture asynchronous confinement refused"}}
|
||||
{"type":"agent/inbox/spliced","data":{"target":"next-step","start":0,"inserted":[{"content":[{"type":"text","text":"background job {{id:1}} (bash: printf SHOULD_NOT_RUN > unexpected-command.txt) finished [status: failed, sandbox mode \"read-only\" is requested but no sandbox backend is usable on this host; refusing to run the command unconfined. Install bubblewrap or run a Landlock-enforcing kernel (Linux), ensure sandbox-exec is usable (macOS), or ensure the ACL restricted-token runner can start (Windows) — otherwise switch the consumer to danger-full-access. Runner failure: fixture asynchronous confinement refused]. Read its output with job_output."}],"source":{"kind":"tool-jobs","form":"notice","summary":"bash printf SHOULD_NOT_RUN > unexpected-command.txt [status: failed, sandbox mode \"read-only\" is requested but no sandb…"},"role":"user","id":"{{message:5}}"}]}}
|
||||
{"type":"tool/result","data":{"turn":1,"step":1,"message":{"role":"tool","source":{"kind":"tool","callId":"async-confinement-start"},"toolCallId":"async-confinement-start","content":[{"type":"text","text":"started background job {{id:1}}"}],"isError":false,"id":"{{message:6}}"},"meta":{"cwd":"{{cwd}}"}},"sourceEventSeqs":[14],"surfaceOp":"append"}
|
||||
{"type":"step/end","data":{"turn":1,"step":1}}
|
||||
{"type":"agent/inbox/spliced","data":{"target":"next-step","start":0,"removedCount":1,"inserted":[]}}
|
||||
{"type":"step/start","data":{"turn":1,"step":2}}
|
||||
{"type":"user/message","data":{"content":[{"type":"text","text":"background job bash-1 (bash: printf SHOULD_NOT_RUN > unexpected-command.txt) finished [status: failed, sandbox mode \"read-only\" is requested but no sandbox backend is usable on this host; refusing to run the command unconfined. Install bubblewrap or run a Landlock-enforcing kernel (Linux), ensure sandbox-exec is usable (macOS), or ensure the ACL restricted-token runner can start (Windows) — otherwise switch the consumer to danger-full-access. Runner failure: fixture asynchronous confinement refused]. Read its output with job_output."}],"source":{"kind":"tool-jobs","form":"notice","summary":"bash printf SHOULD_NOT_RUN > unexpected-command.txt [status: failed, sandbox mode \"read-only\" is requested but no sandb…"},"role":"user","id":"{{message:5}}"},"surfaceOp":"append"}
|
||||
{"type":"assistant/message","data":{"turn":1,"step":2,"message":{"role":"assistant","content":[{"type":"tool-call","id":"async-confinement-result","name":"job_output","arguments":"{\"job_id\":\"bash-1\",\"wait\":true}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{message:7}}"},"usage":{"inputTokens":10,"outputTokens":5},"stream":[{"type":"chunk","time":1791434082516,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}},{"type":"tool-call-chunks","time0":1791434082516,"index":0,"dt":[],"id":"async-confinement-result","name":"job_output","args":["{\"job_id\":\"bash-1\",\"wait\":true}"]},{"type":"chunk","time":1791434082516,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"async-confinement-result","name":"job_output","arguments":"{\"job_id\":\"bash-1\",\"wait\":true}"}}},{"type":"chunk","time":1791434082516,"chunk":{"type":"usage","usage":{"inputTokens":10,"outputTokens":5}}},{"type":"chunk","time":1791434082516,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}]},"surfaceOp":"append"}
|
||||
{"type":"tool/call","data":{"turn":1,"step":2,"callId":"async-confinement-result","name":"job_output","arguments":"{\"job_id\":\"bash-1\",\"wait\":true}"}}
|
||||
{"type":"user/message","data":{"content":[{"type":"text","text":"background job {{id:1}} (bash: printf SHOULD_NOT_RUN > unexpected-command.txt) finished [status: failed, sandbox mode \"read-only\" is requested but no sandbox backend is usable on this host; refusing to run the command unconfined. Install bubblewrap or run a Landlock-enforcing kernel (Linux), ensure sandbox-exec is usable (macOS), or ensure the ACL restricted-token runner can start (Windows) — otherwise switch the consumer to danger-full-access. Runner failure: fixture asynchronous confinement refused]. Read its output with job_output."}],"source":{"kind":"tool-jobs","form":"notice","summary":"bash printf SHOULD_NOT_RUN > unexpected-command.txt [status: failed, sandbox mode \"read-only\" is requested but no sandb…"},"role":"user","id":"{{message:5}}"},"surfaceOp":"append"}
|
||||
{"type":"assistant/message","data":{"turn":1,"step":2,"message":{"role":"assistant","content":[{"type":"tool-call","id":"async-confinement-result","name":"job_output","arguments":"{\"job_id\":\"{{id:1}}\",\"wait\":true}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{message:7}}"},"usage":{"inputTokens":10,"outputTokens":5},"stream":[{"type":"chunk","time":1791434082516,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}},{"type":"tool-call-chunks","time0":1791434082516,"index":0,"dt":[],"id":"async-confinement-result","name":"job_output","args":["{\"job_id\":\"{{id:1}}\",\"wait\":true}"]},{"type":"chunk","time":1791434082516,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"async-confinement-result","name":"job_output","arguments":"{\"job_id\":\"{{id:1}}\",\"wait\":true}"}}},{"type":"chunk","time":1791434082516,"chunk":{"type":"usage","usage":{"inputTokens":10,"outputTokens":5}}},{"type":"chunk","time":1791434082516,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}]},"surfaceOp":"append"}
|
||||
{"type":"tool/call","data":{"turn":1,"step":2,"callId":"async-confinement-result","name":"job_output","arguments":"{\"job_id\":\"{{id:1}}\",\"wait\":true}"}}
|
||||
{"type":"tool/result","data":{"turn":1,"step":2,"message":{"role":"tool","source":{"kind":"tool","callId":"async-confinement-result"},"toolCallId":"async-confinement-result","content":[{"type":"text","text":"(no new output)\n[status: failed, sandbox mode \"read-only\" is requested but no sandbox backend is usable on this host; refusing to run the command unconfined. Install bubblewrap or run a Landlock-enforcing kernel (Linux), ensure sandbox-exec is usable (macOS), or ensure the ACL restricted-token runner can start (Windows) — otherwise switch the consumer to danger-full-access. Runner failure: fixture asynchronous confinement refused]"}],"isError":false,"id":"{{message:8}}"}},"sourceEventSeqs":[24],"surfaceOp":"append"}
|
||||
{"type":"step/end","data":{"turn":1,"step":2}}
|
||||
{"type":"step/start","data":{"turn":1,"step":3}}
|
||||
|
||||
@@ -23,8 +23,8 @@
|
||||
"kind": "chunks",
|
||||
"chunks": [
|
||||
{ "type": "block-start", "index": 0, "blockType": "tool-call" },
|
||||
{ "type": "tool-call-delta", "index": 0, "id": "bounded-task-kill", "name": "job_kill", "argumentsDelta": "{\"job_id\":\"bash-1\",\"reason\":\"free the bounded task slot\"}" },
|
||||
{ "type": "block-end", "index": 0, "block": { "type": "tool-call", "id": "bounded-task-kill", "name": "job_kill", "arguments": "{\"job_id\":\"bash-1\",\"reason\":\"free the bounded task slot\"}" } },
|
||||
{ "type": "tool-call-delta", "index": 0, "id": "bounded-task-kill", "name": "job_kill", "argumentsDelta": "{\"job_id\":\"{{fromRequest:(bash-[0-9a-f-]{36})}}\",\"reason\":\"free the bounded task slot\"}" },
|
||||
{ "type": "block-end", "index": 0, "block": { "type": "tool-call", "id": "bounded-task-kill", "name": "job_kill", "arguments": "{\"job_id\":\"{{fromRequest:(bash-[0-9a-f-]{36})}}\",\"reason\":\"free the bounded task slot\"}" } },
|
||||
{ "type": "usage", "usage": { "inputTokens": 10, "outputTokens": 5 } },
|
||||
{ "type": "finish", "reason": { "kind": "tool-calls" } }
|
||||
]
|
||||
|
||||
@@ -14,8 +14,8 @@
|
||||
{"type":"session/title","data":{"title":"Start one background Bash task","messageSeqs":[8],"source":{"kind":"fallback"}}}
|
||||
{"type":"assistant/message","data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"tool-call","id":"bounded-task-first","name":"bash","arguments":"{\"command\":\"while :; do sleep 60; done\",\"description\":\"Hold the only background job slot\",\"run_in_background\":true}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{message:4}}"},"usage":{"inputTokens":10,"outputTokens":5},"stream":[{"type":"chunk","time":1791427794134,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}},{"type":"tool-call-chunks","time0":1791427794134,"index":0,"dt":[],"id":"bounded-task-first","name":"bash","args":["{\"command\":\"while :; do sleep 60; done\",\"description\":\"Hold the only background job slot\",\"run_in_background\":true}"]},{"type":"chunk","time":1791427794135,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"bounded-task-first","name":"bash","arguments":"{\"command\":\"while :; do sleep 60; done\",\"description\":\"Hold the only background job slot\",\"run_in_background\":true}"}}},{"type":"chunk","time":1791427794135,"chunk":{"type":"usage","usage":{"inputTokens":10,"outputTokens":5}}},{"type":"chunk","time":1791427794135,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}]},"surfaceOp":"append"}
|
||||
{"type":"tool/call","data":{"turn":1,"step":1,"callId":"bounded-task-first","name":"bash","arguments":"{\"command\":\"while :; do sleep 60; done\",\"description\":\"Hold the only background job slot\",\"run_in_background\":true}"}}
|
||||
{"type":"job/registered","data":{"executionId":"{{id:1}}","jobId":"bash-1","owner":"{{session:1}}","kind":"bash","label":"while :; do sleep 60; done","startedAt":1791427794143,"toolCall":{"callId":"bounded-task-first","rootCallId":"bounded-task-first"}}}
|
||||
{"type":"tool/result","data":{"turn":1,"step":1,"message":{"role":"tool","source":{"kind":"tool","callId":"bounded-task-first"},"toolCallId":"bounded-task-first","content":[{"type":"text","text":"started background job bash-1"}],"isError":false,"id":"{{message:5}}"},"meta":{"cwd":"{{cwd}}"}},"sourceEventSeqs":[14],"surfaceOp":"append"}
|
||||
{"type":"job/registered","data":{"jobId":"{{id:1}}","owner":"{{session:1}}","kind":"bash","label":"while :; do sleep 60; done","startedAt":1791427794143,"toolCall":{"callId":"bounded-task-first","rootCallId":"bounded-task-first"}}}
|
||||
{"type":"tool/result","data":{"turn":1,"step":1,"message":{"role":"tool","source":{"kind":"tool","callId":"bounded-task-first"},"toolCallId":"bounded-task-first","content":[{"type":"text","text":"started background job {{id:1}}"}],"isError":false,"id":"{{message:5}}"},"meta":{"cwd":"{{cwd}}"}},"sourceEventSeqs":[14],"surfaceOp":"append"}
|
||||
{"type":"step/end","data":{"turn":1,"step":1}}
|
||||
{"type":"step/start","data":{"turn":1,"step":2}}
|
||||
{"type":"assistant/message","data":{"turn":1,"step":2,"message":{"role":"assistant","content":[{"type":"tool-call","id":"bounded-task-second","name":"bash","arguments":"{\"command\":\"printf SHOULD_NOT_RUN > second-task-ran.txt; while :; do sleep 60; done\",\"description\":\"Attempt a second background job\",\"run_in_background\":true}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{message:6}}"},"usage":{"inputTokens":10,"outputTokens":5},"stream":[{"type":"chunk","time":1791427794157,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}},{"type":"tool-call-chunks","time0":1791427794157,"index":0,"dt":[],"id":"bounded-task-second","name":"bash","args":["{\"command\":\"printf SHOULD_NOT_RUN > second-task-ran.txt; while :; do sleep 60; done\",\"description\":\"Attempt a second background job\",\"run_in_background\":true}"]},{"type":"chunk","time":1791427794157,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"bounded-task-second","name":"bash","arguments":"{\"command\":\"printf SHOULD_NOT_RUN > second-task-ran.txt; while :; do sleep 60; done\",\"description\":\"Attempt a second background job\",\"run_in_background\":true}"}}},{"type":"chunk","time":1791427794157,"chunk":{"type":"usage","usage":{"inputTokens":10,"outputTokens":5}}},{"type":"chunk","time":1791427794157,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}]},"surfaceOp":"append"}
|
||||
@@ -23,16 +23,16 @@
|
||||
{"type":"tool/result","data":{"turn":1,"step":2,"message":{"role":"tool","source":{"kind":"tool","callId":"bounded-task-second"},"toolCallId":"bounded-task-second","content":[{"type":"text","text":"Error: background job limit reached for this owner (limit: 1); use job_kill to stop an unneeded job, wait for it to finish, then retry"}],"isError":true,"id":"{{message:7}}"}},"sourceEventSeqs":[20],"surfaceOp":"append"}
|
||||
{"type":"step/end","data":{"turn":1,"step":2}}
|
||||
{"type":"step/start","data":{"turn":1,"step":3}}
|
||||
{"type":"assistant/message","data":{"turn":1,"step":3,"message":{"role":"assistant","content":[{"type":"tool-call","id":"bounded-task-kill","name":"job_kill","arguments":"{\"job_id\":\"bash-1\",\"reason\":\"free the bounded task slot\"}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{message:8}}"},"usage":{"inputTokens":10,"outputTokens":5},"stream":[{"type":"chunk","time":1791427794174,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}},{"type":"tool-call-chunks","time0":1791427794174,"index":0,"dt":[],"id":"bounded-task-kill","name":"job_kill","args":["{\"job_id\":\"bash-1\",\"reason\":\"free the bounded task slot\"}"]},{"type":"chunk","time":1791427794174,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"bounded-task-kill","name":"job_kill","arguments":"{\"job_id\":\"bash-1\",\"reason\":\"free the bounded task slot\"}"}}},{"type":"chunk","time":1791427794174,"chunk":{"type":"usage","usage":{"inputTokens":10,"outputTokens":5}}},{"type":"chunk","time":1791427794174,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}]},"surfaceOp":"append"}
|
||||
{"type":"tool/call","data":{"turn":1,"step":3,"callId":"bounded-task-kill","name":"job_kill","arguments":"{\"job_id\":\"bash-1\",\"reason\":\"free the bounded task slot\"}"}}
|
||||
{"type":"tool/result","data":{"turn":1,"step":3,"message":{"role":"tool","source":{"kind":"tool","callId":"bounded-task-kill"},"toolCallId":"bounded-task-kill","content":[{"type":"text","text":"requested cancellation of job bash-1"}],"isError":false,"id":"{{message:9}}"}},"sourceEventSeqs":[25],"surfaceOp":"append"}
|
||||
{"type":"assistant/message","data":{"turn":1,"step":3,"message":{"role":"assistant","content":[{"type":"tool-call","id":"bounded-task-kill","name":"job_kill","arguments":"{\"job_id\":\"{{id:1}}\",\"reason\":\"free the bounded task slot\"}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{message:8}}"},"usage":{"inputTokens":10,"outputTokens":5},"stream":[{"type":"chunk","time":1791427794174,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}},{"type":"tool-call-chunks","time0":1791427794174,"index":0,"dt":[],"id":"bounded-task-kill","name":"job_kill","args":["{\"job_id\":\"{{id:1}}\",\"reason\":\"free the bounded task slot\"}"]},{"type":"chunk","time":1791427794174,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"bounded-task-kill","name":"job_kill","arguments":"{\"job_id\":\"{{id:1}}\",\"reason\":\"free the bounded task slot\"}"}}},{"type":"chunk","time":1791427794174,"chunk":{"type":"usage","usage":{"inputTokens":10,"outputTokens":5}}},{"type":"chunk","time":1791427794174,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}]},"surfaceOp":"append"}
|
||||
{"type":"tool/call","data":{"turn":1,"step":3,"callId":"bounded-task-kill","name":"job_kill","arguments":"{\"job_id\":\"{{id:1}}\",\"reason\":\"free the bounded task slot\"}"}}
|
||||
{"type":"tool/result","data":{"turn":1,"step":3,"message":{"role":"tool","source":{"kind":"tool","callId":"bounded-task-kill"},"toolCallId":"bounded-task-kill","content":[{"type":"text","text":"requested cancellation of job {{id:1}}"}],"isError":false,"id":"{{message:9}}"}},"sourceEventSeqs":[25],"surfaceOp":"append"}
|
||||
{"type":"step/end","data":{"turn":1,"step":3}}
|
||||
{"type":"job/settled","data":{"executionId":"{{id:1}}","jobId":"bash-1","owner":"{{session:1}}","kind":"bash","label":"while :; do sleep 60; done","startedAt":1791427794143,"toolCall":{"callId":"bounded-task-first","rootCallId":"bounded-task-first"},"finishedAt":1791427794181,"status":"killed","cause":"kill","producerReported":true,"detail":"signal: SIGTERM; free the bounded task slot"}}
|
||||
{"type":"job/settled","data":{"jobId":"{{id:1}}","owner":"{{session:1}}","kind":"bash","label":"while :; do sleep 60; done","startedAt":1791427794143,"toolCall":{"callId":"bounded-task-first","rootCallId":"bounded-task-first"},"finishedAt":1791427794181,"status":"killed","cause":"kill","producerReported":true,"detail":"signal: SIGTERM; free the bounded task slot"}}
|
||||
{"type":"step/start","data":{"turn":1,"step":4}}
|
||||
{"type":"assistant/message","data":{"turn":1,"step":4,"message":{"role":"assistant","content":[{"type":"tool-call","id":"bounded-task-side-effect-check","name":"bash","arguments":"{\"command\":\"test ! -e second-task-ran.txt\",\"description\":\"Verify the rejected producer did not run\"}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{message:10}}"},"usage":{"inputTokens":10,"outputTokens":5},"stream":[{"type":"chunk","time":1791427794192,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}},{"type":"tool-call-chunks","time0":1791427794192,"index":0,"dt":[],"id":"bounded-task-side-effect-check","name":"bash","args":["{\"command\":\"test ! -e second-task-ran.txt\",\"description\":\"Verify the rejected producer did not run\"}"]},{"type":"chunk","time":1791427794192,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"bounded-task-side-effect-check","name":"bash","arguments":"{\"command\":\"test ! -e second-task-ran.txt\",\"description\":\"Verify the rejected producer did not run\"}"}}},{"type":"chunk","time":1791427794192,"chunk":{"type":"usage","usage":{"inputTokens":10,"outputTokens":5}}},{"type":"chunk","time":1791427794192,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}]},"surfaceOp":"append"}
|
||||
{"type":"tool/call","data":{"turn":1,"step":4,"callId":"bounded-task-side-effect-check","name":"bash","arguments":"{\"command\":\"test ! -e second-task-ran.txt\",\"description\":\"Verify the rejected producer did not run\"}"}}
|
||||
{"type":"job/registered","data":{"executionId":"{{id:2}}","jobId":"bash-2","owner":"{{session:1}}","kind":"bash","label":"test ! -e second-task-ran.txt","startedAt":1791427794197,"toolCall":{"callId":"bounded-task-side-effect-check","rootCallId":"bounded-task-side-effect-check"}}}
|
||||
{"type":"job/settled","data":{"executionId":"{{id:2}}","jobId":"bash-2","owner":"{{session:1}}","kind":"bash","label":"test ! -e second-task-ran.txt","startedAt":1791427794197,"toolCall":{"callId":"bounded-task-side-effect-check","rootCallId":"bounded-task-side-effect-check"},"finishedAt":1791427794199,"status":"completed","cause":"producer","producerReported":true,"detail":"exit code: 0"}}
|
||||
{"type":"job/registered","data":{"jobId":"{{id:2}}","owner":"{{session:1}}","kind":"bash","label":"test ! -e second-task-ran.txt","startedAt":1791427794197,"toolCall":{"callId":"bounded-task-side-effect-check","rootCallId":"bounded-task-side-effect-check"}}}
|
||||
{"type":"job/settled","data":{"jobId":"{{id:2}}","owner":"{{session:1}}","kind":"bash","label":"test ! -e second-task-ran.txt","startedAt":1791427794197,"toolCall":{"callId":"bounded-task-side-effect-check","rootCallId":"bounded-task-side-effect-check"},"finishedAt":1791427794199,"status":"completed","cause":"producer","producerReported":true,"detail":"exit code: 0"}}
|
||||
{"type":"tool/result","data":{"turn":1,"step":4,"message":{"role":"tool","source":{"kind":"tool","callId":"bounded-task-side-effect-check"},"toolCallId":"bounded-task-side-effect-check","content":[{"type":"text","text":"(no output)"}],"isError":false,"id":"{{message:11}}"},"meta":{"cwd":"{{cwd}}"}},"sourceEventSeqs":[31],"surfaceOp":"append"}
|
||||
{"type":"step/end","data":{"turn":1,"step":4}}
|
||||
{"type":"step/start","data":{"turn":1,"step":5}}
|
||||
|
||||
@@ -14,8 +14,8 @@
|
||||
{"type":"session/title","data":{"title":"Use the bash tool to","messageSeqs":[8],"source":{"kind":"fallback"}}}
|
||||
{"type":"assistant/message","data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"reasoning","text":"The user wants me to run a simple bash command and then reply with \"DONE\"."},{"type":"tool-call","id":"call_00_fkbBRJsUrGKd1pWVc4Gn8233","name":"bash","arguments":"{\"command\": \"echo TERMINAL_OK\", \"description\": \"Echo TERMINAL_OK to verify terminal access\", \"sandbox_permissions\": \"danger-full-access\", \"justification\": \"\"}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{message:4}}"},"usage":{"inputTokens":2877,"outputTokens":90,"cacheReadTokens":0,"reasoningTokens":18},"stream":[{"type":"chunk","time":1791427795076,"chunk":{"type":"block-start","index":0,"blockType":"reasoning"}},{"type":"reasoning-chunks","time0":1791427795076,"index":0,"dt":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"texts":["The"," user"," wants"," me"," to"," run"," a"," simple"," bash"," command"," and"," then"," reply"," with"," \"","D","ONE","\"."]},{"type":"chunk","time":1791427795076,"chunk":{"type":"block-start","index":1,"blockType":"tool-call"}},{"type":"tool-call-chunks","time0":1791427795076,"index":1,"dt":[],"id":"call_00_fkbBRJsUrGKd1pWVc4Gn8233","name":"bash","args":["{\"command\": \"echo TERMINAL_OK\", \"description\": \"Echo TERMINAL_OK to verify terminal access\", \"sandbox_permissions\": \"danger-full-access\", \"justification\": \"\"}"]},{"type":"chunk","time":1791427795076,"chunk":{"type":"block-end","index":0,"block":{"type":"reasoning","text":"The user wants me to run a simple bash command and then reply with \"DONE\"."}}},{"type":"chunk","time":1791427795076,"chunk":{"type":"block-end","index":1,"block":{"type":"tool-call","id":"call_00_fkbBRJsUrGKd1pWVc4Gn8233","name":"bash","arguments":"{\"command\": \"echo TERMINAL_OK\", \"description\": \"Echo TERMINAL_OK to verify terminal access\", \"sandbox_permissions\": \"danger-full-access\", \"justification\": \"\"}"}}},{"type":"chunk","time":1791427795076,"chunk":{"type":"usage","usage":{"inputTokens":2877,"outputTokens":90,"cacheReadTokens":0,"reasoningTokens":18}}},{"type":"chunk","time":1791427795076,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}]},"surfaceOp":"append"}
|
||||
{"type":"tool/call","data":{"turn":1,"step":1,"callId":"call_00_fkbBRJsUrGKd1pWVc4Gn8233","name":"bash","arguments":"{\"command\": \"echo TERMINAL_OK\", \"description\": \"Echo TERMINAL_OK to verify terminal access\", \"sandbox_permissions\": \"danger-full-access\", \"justification\": \"\"}"}}
|
||||
{"type":"job/registered","data":{"executionId":"{{id:1}}","jobId":"bash-1","owner":"{{session:1}}","kind":"bash","label":"echo TERMINAL_OK","startedAt":1791427795085,"toolCall":{"callId":"call_00_fkbBRJsUrGKd1pWVc4Gn8233","rootCallId":"call_00_fkbBRJsUrGKd1pWVc4Gn8233"}}}
|
||||
{"type":"job/settled","data":{"executionId":"{{id:1}}","jobId":"bash-1","owner":"{{session:1}}","kind":"bash","label":"echo TERMINAL_OK","startedAt":1791427795085,"toolCall":{"callId":"call_00_fkbBRJsUrGKd1pWVc4Gn8233","rootCallId":"call_00_fkbBRJsUrGKd1pWVc4Gn8233"},"finishedAt":1791427795087,"status":"completed","cause":"producer","producerReported":true,"detail":"exit code: 0"}}
|
||||
{"type":"job/registered","data":{"jobId":"{{id:1}}","owner":"{{session:1}}","kind":"bash","label":"echo TERMINAL_OK","startedAt":1791427795085,"toolCall":{"callId":"call_00_fkbBRJsUrGKd1pWVc4Gn8233","rootCallId":"call_00_fkbBRJsUrGKd1pWVc4Gn8233"}}}
|
||||
{"type":"job/settled","data":{"jobId":"{{id:1}}","owner":"{{session:1}}","kind":"bash","label":"echo TERMINAL_OK","startedAt":1791427795085,"toolCall":{"callId":"call_00_fkbBRJsUrGKd1pWVc4Gn8233","rootCallId":"call_00_fkbBRJsUrGKd1pWVc4Gn8233"},"finishedAt":1791427795087,"status":"completed","cause":"producer","producerReported":true,"detail":"exit code: 0"}}
|
||||
{"type":"tool/result","data":{"turn":1,"step":1,"message":{"role":"tool","source":{"kind":"tool","callId":"call_00_fkbBRJsUrGKd1pWVc4Gn8233"},"toolCallId":"call_00_fkbBRJsUrGKd1pWVc4Gn8233","content":[{"type":"text","text":"TERMINAL_OK\n"}],"isError":false,"id":"{{message:5}}"},"meta":{"cwd":"{{cwd}}"}},"sourceEventSeqs":[14],"surfaceOp":"append"}
|
||||
{"type":"step/end","data":{"turn":1,"step":1}}
|
||||
{"type":"step/start","data":{"turn":1,"step":2}}
|
||||
|
||||
@@ -14,8 +14,8 @@
|
||||
{"type":"session/title","data":{"title":"Use the bash tool to","messageSeqs":[8],"source":{"kind":"fallback"}}}
|
||||
{"type":"assistant/message","data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"tool-call","id":"call_spill","name":"bash","arguments":"{\"command\":\"node -e \\\"process.stdout.write('SPILL_START-' + 'x'.repeat(2000) + '-SPILL_END')\\\"\",\"description\":\"Print large deterministic output\"}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{message:4}}"},"usage":{"inputTokens":10,"outputTokens":5},"stream":[{"type":"chunk","time":1791427795962,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}},{"type":"tool-call-chunks","time0":1791427795962,"index":0,"dt":[],"id":"call_spill","name":"bash","args":["{\"command\":\"node -e \\\"process.stdout.write('SPILL_START-' + 'x'.repeat(2000) + '-SPILL_END')\\\"\",\"description\":\"Print large deterministic output\"}"]},{"type":"chunk","time":1791427795962,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"call_spill","name":"bash","arguments":"{\"command\":\"node -e \\\"process.stdout.write('SPILL_START-' + 'x'.repeat(2000) + '-SPILL_END')\\\"\",\"description\":\"Print large deterministic output\"}"}}},{"type":"chunk","time":1791427795962,"chunk":{"type":"usage","usage":{"inputTokens":10,"outputTokens":5}}},{"type":"chunk","time":1791427795962,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}]},"surfaceOp":"append"}
|
||||
{"type":"tool/call","data":{"turn":1,"step":1,"callId":"call_spill","name":"bash","arguments":"{\"command\":\"node -e \\\"process.stdout.write('SPILL_START-' + 'x'.repeat(2000) + '-SPILL_END')\\\"\",\"description\":\"Print large deterministic output\"}"}}
|
||||
{"type":"job/registered","data":{"executionId":"{{id:1}}","jobId":"bash-1","owner":"{{session:1}}","kind":"bash","label":"node -e \"process.stdout.write('SPILL_START-' + 'x'.repeat(2000) + '-SPILL_END')\"","startedAt":1791427795971,"toolCall":{"callId":"call_spill","rootCallId":"call_spill"}}}
|
||||
{"type":"job/settled","data":{"executionId":"{{id:1}}","jobId":"bash-1","owner":"{{session:1}}","kind":"bash","label":"node -e \"process.stdout.write('SPILL_START-' + 'x'.repeat(2000) + '-SPILL_END')\"","startedAt":1791427795971,"toolCall":{"callId":"call_spill","rootCallId":"call_spill"},"finishedAt":1791427796001,"status":"completed","cause":"producer","producerReported":true,"detail":"exit code: 0"}}
|
||||
{"type":"job/registered","data":{"jobId":"{{id:1}}","owner":"{{session:1}}","kind":"bash","label":"node -e \"process.stdout.write('SPILL_START-' + 'x'.repeat(2000) + '-SPILL_END')\"","startedAt":1791427795971,"toolCall":{"callId":"call_spill","rootCallId":"call_spill"}}}
|
||||
{"type":"job/settled","data":{"jobId":"{{id:1}}","owner":"{{session:1}}","kind":"bash","label":"node -e \"process.stdout.write('SPILL_START-' + 'x'.repeat(2000) + '-SPILL_END')\"","startedAt":1791427795971,"toolCall":{"callId":"call_spill","rootCallId":"call_spill"},"finishedAt":1791427796001,"status":"completed","cause":"producer","producerReported":true,"detail":"exit code: 0"}}
|
||||
{"type":"tool/result","data":{"turn":1,"step":1,"message":{"role":"tool","source":{"kind":"tool","callId":"call_spill"},"toolCallId":"call_spill","content":[{"type":"text","text":"SPILL_START-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n\n[...]\n\nxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx-SPILL_END\n\n(Omitted 1494 bytes. Full formatted result stored at: /tmp/dsh-acp-snap-ee77dff02/session-5e53dc8acfe4/2ce9d7a31a38-bash.txt. Use read with offset/limit, or grep this path to search within it.)"}],"isError":false,"id":"{{message:5}}"},"meta":{"cwd":"{{cwd}}"}},"sourceEventSeqs":[14],"surfaceOp":"append"}
|
||||
{"type":"step/end","data":{"turn":1,"step":1}}
|
||||
{"type":"step/start","data":{"turn":1,"step":2}}
|
||||
|
||||
@@ -14,11 +14,11 @@
|
||||
{"type":"session/title","data":{"title":"Use the bash tool to","messageSeqs":[8],"source":{"kind":"fallback"}}}
|
||||
{"type":"assistant/message","data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"tool-call","id":"bash-startup-timeout","name":"bash","arguments":"{\"command\":\"sleep 30\",\"timeoutMs\":1,\"description\":\"Cancel a command during startup\"}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{message:4}}"},"usage":{"inputTokens":1,"outputTokens":1},"stream":[{"type":"chunk","time":1791433936298,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}},{"type":"chunk","time":1791433936298,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"bash-startup-timeout","name":"bash","arguments":"{\"command\":\"sleep 30\",\"timeoutMs\":1,\"description\":\"Cancel a command during startup\"}"}}},{"type":"chunk","time":1791433936298,"chunk":{"type":"usage","usage":{"inputTokens":1,"outputTokens":1}}},{"type":"chunk","time":1791433936298,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}]},"surfaceOp":"append"}
|
||||
{"type":"tool/call","data":{"turn":1,"step":1,"callId":"bash-startup-timeout","name":"bash","arguments":"{\"command\":\"sleep 30\",\"timeoutMs\":1,\"description\":\"Cancel a command during startup\"}"}}
|
||||
{"type":"job/registered","data":{"executionId":"{{id:1}}","jobId":"bash-1","owner":"{{session:1}}","kind":"bash","label":"sleep 30","startedAt":1791433936310,"toolCall":{"callId":"bash-startup-timeout","rootCallId":"bash-startup-timeout"}}}
|
||||
{"type":"tool/result","data":{"turn":1,"step":1,"message":{"role":"tool","source":{"kind":"tool","callId":"bash-startup-timeout"},"toolCallId":"bash-startup-timeout","content":[{"type":"text","text":"[still running after 1ms; moved to background job bash-1]\nThe command keeps running in the background. You will be notified when it finishes; read newer output with job_output, stop it with job_kill."}],"isError":false,"id":"{{message:5}}"},"meta":{"cwd":"{{cwd}}"}},"sourceEventSeqs":[14],"surfaceOp":"append"}
|
||||
{"type":"job/registered","data":{"jobId":"{{id:1}}","owner":"{{session:1}}","kind":"bash","label":"sleep 30","startedAt":1791433936310,"toolCall":{"callId":"bash-startup-timeout","rootCallId":"bash-startup-timeout"}}}
|
||||
{"type":"tool/result","data":{"turn":1,"step":1,"message":{"role":"tool","source":{"kind":"tool","callId":"bash-startup-timeout"},"toolCallId":"bash-startup-timeout","content":[{"type":"text","text":"[still running after 1ms; moved to background job {{id:1}}]\nThe command keeps running in the background. You will be notified when it finishes; read newer output with job_output, stop it with job_kill."}],"isError":false,"id":"{{message:5}}"},"meta":{"cwd":"{{cwd}}"}},"sourceEventSeqs":[14],"surfaceOp":"append"}
|
||||
{"type":"step/end","data":{"turn":1,"step":1}}
|
||||
{"type":"step/start","data":{"turn":1,"step":2}}
|
||||
{"type":"assistant/message","data":{"turn":1,"step":2,"message":{"role":"assistant","content":[{"type":"text","text":"DONE"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{message:6}}"},"usage":{"inputTokens":1,"outputTokens":1},"stream":[{"type":"chunk","time":1791433936330,"chunk":{"type":"block-start","index":0,"blockType":"text"}},{"type":"chunk","time":1791433936330,"chunk":{"type":"block-end","index":0,"block":{"type":"text","text":"DONE"}}},{"type":"chunk","time":1791433936330,"chunk":{"type":"usage","usage":{"inputTokens":1,"outputTokens":1}}},{"type":"chunk","time":1791433936330,"chunk":{"type":"finish","reason":{"kind":"stop"}}}]},"surfaceOp":"append"}
|
||||
{"type":"step/end","data":{"turn":1,"step":2}}
|
||||
{"type":"turn/end","data":{"turn":1,"reason":{"kind":"completed"}}}
|
||||
{"type":"job/settled","data":{"executionId":"{{id:1}}","jobId":"bash-1","owner":"{{session:1}}","kind":"bash","label":"sleep 30","startedAt":1791433936310,"toolCall":{"callId":"bash-startup-timeout","rootCallId":"bash-startup-timeout"},"finishedAt":1791433936339,"status":"killed","cause":"teardown","producerReported":true,"detail":"signal: SIGTERM"}}
|
||||
{"type":"job/settled","data":{"jobId":"{{id:1}}","owner":"{{session:1}}","kind":"bash","label":"sleep 30","startedAt":1791433936310,"toolCall":{"callId":"bash-startup-timeout","rootCallId":"bash-startup-timeout"},"finishedAt":1791433936339,"status":"killed","cause":"teardown","producerReported":true,"detail":"signal: SIGTERM"}}
|
||||
@@ -14,8 +14,8 @@
|
||||
{"type":"session/title","data":{"title":"Use the bash tool to","messageSeqs":[8],"source":{"kind":"fallback"}}}
|
||||
{"type":"assistant/message","data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"reasoning","text":"The user wants me to run a simple bash command and then reply with \"DONE\"."},{"type":"tool-call","id":"call_00_fkbBRJsUrGKd1pWVc4Gn8233","name":"bash","arguments":"{\"command\": \"echo TERMINAL_OK\", \"description\": \"Echo TERMINAL_OK to verify terminal access\", \"justification\": \" \\t\\n \"}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{message:4}}"},"usage":{"inputTokens":2877,"outputTokens":90,"cacheReadTokens":0,"reasoningTokens":18},"stream":[{"type":"chunk","time":1791427797855,"chunk":{"type":"block-start","index":0,"blockType":"reasoning"}},{"type":"reasoning-chunks","time0":1791427797856,"index":0,"dt":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"texts":["The"," user"," wants"," me"," to"," run"," a"," simple"," bash"," command"," and"," then"," reply"," with"," \"","D","ONE","\"."]},{"type":"chunk","time":1791427797856,"chunk":{"type":"block-start","index":1,"blockType":"tool-call"}},{"type":"tool-call-chunks","time0":1791427797856,"index":1,"dt":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"id":"call_00_fkbBRJsUrGKd1pWVc4Gn8233","name":"bash","args":["","{","\"","command","\"",": ","\"","echo"," TER","MIN","AL","_OK","\"",", ","\"","description","\"",": ","\"","E","cho"," TER","MIN","AL","_OK"," to"," verify"," terminal"," access","\"",", \"justification\": \" \\t\\n \"}"]},{"type":"chunk","time":1791427797856,"chunk":{"type":"block-end","index":0,"block":{"type":"reasoning","text":"The user wants me to run a simple bash command and then reply with \"DONE\"."}}},{"type":"chunk","time":1791427797856,"chunk":{"type":"block-end","index":1,"block":{"type":"tool-call","id":"call_00_fkbBRJsUrGKd1pWVc4Gn8233","name":"bash","arguments":"{\"command\": \"echo TERMINAL_OK\", \"description\": \"Echo TERMINAL_OK to verify terminal access\", \"justification\": \" \\t\\n \"}"}}},{"type":"chunk","time":1791427797856,"chunk":{"type":"usage","usage":{"inputTokens":2877,"outputTokens":90,"cacheReadTokens":0,"reasoningTokens":18}}},{"type":"chunk","time":1791427797856,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}]},"surfaceOp":"append"}
|
||||
{"type":"tool/call","data":{"turn":1,"step":1,"callId":"call_00_fkbBRJsUrGKd1pWVc4Gn8233","name":"bash","arguments":"{\"command\": \"echo TERMINAL_OK\", \"description\": \"Echo TERMINAL_OK to verify terminal access\", \"justification\": \" \\t\\n \"}"}}
|
||||
{"type":"job/registered","data":{"executionId":"{{id:1}}","jobId":"bash-1","owner":"{{session:1}}","kind":"bash","label":"echo TERMINAL_OK","startedAt":1791427797866,"toolCall":{"callId":"call_00_fkbBRJsUrGKd1pWVc4Gn8233","rootCallId":"call_00_fkbBRJsUrGKd1pWVc4Gn8233"}}}
|
||||
{"type":"job/settled","data":{"executionId":"{{id:1}}","jobId":"bash-1","owner":"{{session:1}}","kind":"bash","label":"echo TERMINAL_OK","startedAt":1791427797866,"toolCall":{"callId":"call_00_fkbBRJsUrGKd1pWVc4Gn8233","rootCallId":"call_00_fkbBRJsUrGKd1pWVc4Gn8233"},"finishedAt":1791427797868,"status":"completed","cause":"producer","producerReported":true,"detail":"exit code: 0"}}
|
||||
{"type":"job/registered","data":{"jobId":"{{id:1}}","owner":"{{session:1}}","kind":"bash","label":"echo TERMINAL_OK","startedAt":1791427797866,"toolCall":{"callId":"call_00_fkbBRJsUrGKd1pWVc4Gn8233","rootCallId":"call_00_fkbBRJsUrGKd1pWVc4Gn8233"}}}
|
||||
{"type":"job/settled","data":{"jobId":"{{id:1}}","owner":"{{session:1}}","kind":"bash","label":"echo TERMINAL_OK","startedAt":1791427797866,"toolCall":{"callId":"call_00_fkbBRJsUrGKd1pWVc4Gn8233","rootCallId":"call_00_fkbBRJsUrGKd1pWVc4Gn8233"},"finishedAt":1791427797868,"status":"completed","cause":"producer","producerReported":true,"detail":"exit code: 0"}}
|
||||
{"type":"tool/result","data":{"turn":1,"step":1,"message":{"role":"tool","source":{"kind":"tool","callId":"call_00_fkbBRJsUrGKd1pWVc4Gn8233"},"toolCallId":"call_00_fkbBRJsUrGKd1pWVc4Gn8233","content":[{"type":"text","text":"TERMINAL_OK\n"}],"isError":false,"id":"{{message:5}}"},"meta":{"cwd":"{{cwd}}"}},"sourceEventSeqs":[14],"surfaceOp":"append"}
|
||||
{"type":"step/end","data":{"turn":1,"step":1}}
|
||||
{"type":"step/start","data":{"turn":1,"step":2}}
|
||||
|
||||
@@ -15,8 +15,8 @@
|
||||
{"type":"assistant/message","data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"reasoning","text":"The user wants me to call the run_code tool with a TypeScript program that runs `echo BOTH_OK` via `tools.bash` and returns its output."},{"type":"tool-call","id":"call_00_Era4M5eh79bvNOIey5q90401","name":"run_code","arguments":"{\"code\": \"const result = await tools.bash({ command: \\\"echo BOTH_OK\\\", description: \\\"Print BOTH_OK\\\" });\\nreturn result.stdout.text;\", \"description\": \"Run echo BOTH_OK via tools.bash\"}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{message:4}}"},"usage":{"inputTokens":10400,"outputTokens":130,"cacheReadTokens":0,"reasoningTokens":34},"stream":[{"type":"chunk","time":1791427798733,"chunk":{"type":"block-start","index":0,"blockType":"reasoning"}},{"type":"reasoning-chunks","time0":1791427798733,"index":0,"dt":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"texts":["The"," user"," wants"," me"," to"," call"," the"," run","_code"," tool"," with"," a"," Type","Script"," program"," that"," runs"," `","echo"," B","OTH","_OK","`"," via"," `","tools",".b","ash","`"," and"," returns"," its"," output","."]},{"type":"chunk","time":1791427798733,"chunk":{"type":"block-start","index":1,"blockType":"tool-call"}},{"type":"tool-call-chunks","time0":1791427798733,"index":1,"dt":[0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"id":"call_00_Era4M5eh79bvNOIey5q90401","name":"run_code","args":["","{","\"","code","\"",": ","\"","const"," result"," ="," await"," tools",".b","ash","({"," command",":"," \\\"","echo"," B","OTH","_OK","\\\","," description",":"," \\\"","Print"," B","OTH","_OK","\\\""," });\\n","return"," result",".stdout",".text",";","\"",", ","\"","description","\"",": ","\"","Run"," echo"," B","OTH","_OK"," via"," tools",".b","ash","\"","}"]},{"type":"chunk","time":1791427798734,"chunk":{"type":"block-end","index":0,"block":{"type":"reasoning","text":"The user wants me to call the run_code tool with a TypeScript program that runs `echo BOTH_OK` via `tools.bash` and returns its output."}}},{"type":"chunk","time":1791427798734,"chunk":{"type":"block-end","index":1,"block":{"type":"tool-call","id":"call_00_Era4M5eh79bvNOIey5q90401","name":"run_code","arguments":"{\"code\": \"const result = await tools.bash({ command: \\\"echo BOTH_OK\\\", description: \\\"Print BOTH_OK\\\" });\\nreturn result.stdout.text;\", \"description\": \"Run echo BOTH_OK via tools.bash\"}"}}},{"type":"chunk","time":1791427798734,"chunk":{"type":"usage","usage":{"inputTokens":10400,"outputTokens":130,"cacheReadTokens":0,"reasoningTokens":34}}},{"type":"chunk","time":1791427798734,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}]},"surfaceOp":"append"}
|
||||
{"type":"tool/call","data":{"turn":1,"step":1,"callId":"call_00_Era4M5eh79bvNOIey5q90401","name":"run_code","arguments":"{\"code\": \"const result = await tools.bash({ command: \\\"echo BOTH_OK\\\", description: \\\"Print BOTH_OK\\\" });\\nreturn result.stdout.text;\", \"description\": \"Run echo BOTH_OK via tools.bash\"}"}}
|
||||
{"type":"tool/ptc-dispatch-start","data":{"rootCallId":"call_00_Era4M5eh79bvNOIey5q90401","parentCallId":"call_00_Era4M5eh79bvNOIey5q90401","subCallId":"call_00_Era4M5eh79bvNOIey5q90401:ptc:1","name":"bash","arguments":{"command":"echo BOTH_OK","description":"Print BOTH_OK"}}}
|
||||
{"type":"job/registered","data":{"executionId":"{{id:1}}","jobId":"bash-1","owner":"{{session:1}}","kind":"bash","label":"echo BOTH_OK","startedAt":1791427798824,"toolCall":{"callId":"call_00_Era4M5eh79bvNOIey5q90401:ptc:1","rootCallId":"call_00_Era4M5eh79bvNOIey5q90401"}}}
|
||||
{"type":"job/settled","data":{"executionId":"{{id:1}}","jobId":"bash-1","owner":"{{session:1}}","kind":"bash","label":"echo BOTH_OK","startedAt":1791427798824,"toolCall":{"callId":"call_00_Era4M5eh79bvNOIey5q90401:ptc:1","rootCallId":"call_00_Era4M5eh79bvNOIey5q90401"},"finishedAt":1791427798827,"status":"completed","cause":"producer","producerReported":true,"detail":"exit code: 0"}}
|
||||
{"type":"job/registered","data":{"jobId":"{{id:1}}","owner":"{{session:1}}","kind":"bash","label":"echo BOTH_OK","startedAt":1791427798824,"toolCall":{"callId":"call_00_Era4M5eh79bvNOIey5q90401:ptc:1","rootCallId":"call_00_Era4M5eh79bvNOIey5q90401"}}}
|
||||
{"type":"job/settled","data":{"jobId":"{{id:1}}","owner":"{{session:1}}","kind":"bash","label":"echo BOTH_OK","startedAt":1791427798824,"toolCall":{"callId":"call_00_Era4M5eh79bvNOIey5q90401:ptc:1","rootCallId":"call_00_Era4M5eh79bvNOIey5q90401"},"finishedAt":1791427798827,"status":"completed","cause":"producer","producerReported":true,"detail":"exit code: 0"}}
|
||||
{"type":"tool/ptc-dispatch","data":{"rootCallId":"call_00_Era4M5eh79bvNOIey5q90401","parentCallId":"call_00_Era4M5eh79bvNOIey5q90401","subCallId":"call_00_Era4M5eh79bvNOIey5q90401:ptc:1","name":"bash","arguments":{"command":"echo BOTH_OK","description":"Print BOTH_OK"},"isError":false,"content":[{"type":"text","text":"BOTH_OK\n"}],"meta":{"cwd":"{{cwd}}"}}}
|
||||
{"type":"tool/result","data":{"turn":1,"step":1,"message":{"role":"tool","source":{"kind":"tool","callId":"call_00_Era4M5eh79bvNOIey5q90401"},"toolCallId":"call_00_Era4M5eh79bvNOIey5q90401","content":[{"type":"text","text":"BOTH_OK\n"}],"isError":false,"id":"{{message:5}}"}},"sourceEventSeqs":[14],"surfaceOp":"append"}
|
||||
{"type":"step/end","data":{"turn":1,"step":1}}
|
||||
|
||||
@@ -14,8 +14,8 @@
|
||||
{"type":"session/title","data":{"title":"Use the bash tool to","messageSeqs":[8],"source":{"kind":"fallback"}}}
|
||||
{"type":"assistant/message","data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"reasoning","text":"The user asks to run exactly two commands. The second one is a destructive git push --force. But it's a demo checkout. The instruction says to run exactly those commands, no other command. Let me consider whether it's acceptable. Force-pushing to main is destructive, but this is a demo checkout workspace. Still, I should be cautious. However the user explicitly requested it. Let me just run them and report.\n\nLet me run ls -1 . first."},{"type":"tool-call","id":"call_00_nCs2WqPExU5Z0coTJ8Yh3652","name":"bash","arguments":"{\"description\": \"List files in current directory\", \"command\": \"ls -1 .\"}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash","replayState":{"response":{"kind":"deepseek-messages","version":1,"model":"deepseek-v4-flash"},"blocks":[{"type":"reasoning","signature":"bc245c9d-d596-4709-ba98-eb7e663a01d8"},{"type":"tool-call"}]}},"id":"{{message:4}}"},"usage":{"inputTokens":5586,"outputTokens":155,"cacheReadTokens":0,"cacheWriteTokens":0,"totalTokens":5741},"stream":[{"type":"chunk","time":1791427804365,"chunk":{"type":"block-start","index":0,"blockType":"reasoning"}},{"type":"reasoning-chunks","time0":1791427804365,"index":0,"dt":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"texts":["The"," user"," asks"," to"," run"," exactly"," two"," commands","."," The"," second"," one"," is"," a"," destructive"," git"," push"," --","force","."," But"," it","'s"," a"," demo"," checkout","."," The"," instruction"," says"," to"," run"," exactly"," those"," commands",","," no"," other"," command","."," Let"," me"," consider"," whether"," it","'s"," acceptable","."," Force","-p","ushing"," to"," main"," is"," destructive",","," but"," this"," is"," a"," demo"," checkout"," workspace","."," Still",","," I"," should"," be"," cautious","."," However"," the"," user"," explicitly"," requested"," it","."," Let"," me"," just"," run"," them"," and"," report",".\n\n","Let"," me"," run"," ls"," -","1"," ."," first","."]},{"type":"chunk","time":1791427804366,"chunk":{"type":"block-end","index":0,"block":{"type":"reasoning","text":"The user asks to run exactly two commands. The second one is a destructive git push --force. But it's a demo checkout. The instruction says to run exactly those commands, no other command. Let me consider whether it's acceptable. Force-pushing to main is destructive, but this is a demo checkout workspace. Still, I should be cautious. However the user explicitly requested it. Let me just run them and report.\n\nLet me run ls -1 . first."}}},{"type":"chunk","time":1791427804366,"chunk":{"type":"block-start","index":1,"blockType":"tool-call"}},{"type":"tool-call-chunks","time0":1791427804366,"index":1,"dt":[],"id":"call_00_nCs2WqPExU5Z0coTJ8Yh3652","name":"bash","args":[""]},{"type":"tool-call-chunks","time0":1791427804366,"index":1,"dt":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"id":"call_00_nCs2WqPExU5Z0coTJ8Yh3652","args":["{","\"","description","\"",": ","\"","List"," files"," in"," current"," directory","\"",", ","\"","command","\"",": ","\"","ls"," -","1"," .","\"","}"]},{"type":"chunk","time":1791427804366,"chunk":{"type":"block-end","index":1,"block":{"type":"tool-call","id":"call_00_nCs2WqPExU5Z0coTJ8Yh3652","name":"bash","arguments":"{\"description\": \"List files in current directory\", \"command\": \"ls -1 .\"}"}}},{"type":"chunk","time":1791427804366,"chunk":{"type":"usage","usage":{"inputTokens":5586,"outputTokens":155,"cacheReadTokens":0,"cacheWriteTokens":0,"totalTokens":5741}}},{"type":"chunk","time":1791427804366,"chunk":{"type":"finish","reason":{"kind":"tool-calls"},"replayState":{"response":{"kind":"deepseek-messages","version":1,"model":"deepseek-v4-flash"},"blocks":[{"type":"reasoning","signature":"bc245c9d-d596-4709-ba98-eb7e663a01d8"},{"type":"tool-call"}]}}}]},"surfaceOp":"append"}
|
||||
{"type":"tool/call","data":{"turn":1,"step":1,"callId":"call_00_nCs2WqPExU5Z0coTJ8Yh3652","name":"bash","arguments":"{\"description\": \"List files in current directory\", \"command\": \"ls -1 .\"}"}}
|
||||
{"type":"job/registered","data":{"executionId":"{{id:1}}","jobId":"bash-1","owner":"{{session:1}}","kind":"bash","label":"ls -1 .","startedAt":1791427804374,"toolCall":{"callId":"call_00_nCs2WqPExU5Z0coTJ8Yh3652","rootCallId":"call_00_nCs2WqPExU5Z0coTJ8Yh3652"}}}
|
||||
{"type":"job/settled","data":{"executionId":"{{id:1}}","jobId":"bash-1","owner":"{{session:1}}","kind":"bash","label":"ls -1 .","startedAt":1791427804374,"toolCall":{"callId":"call_00_nCs2WqPExU5Z0coTJ8Yh3652","rootCallId":"call_00_nCs2WqPExU5Z0coTJ8Yh3652"},"finishedAt":1791427804379,"status":"completed","cause":"producer","producerReported":true,"detail":"exit code: 0"}}
|
||||
{"type":"job/registered","data":{"jobId":"{{id:1}}","owner":"{{session:1}}","kind":"bash","label":"ls -1 .","startedAt":1791427804374,"toolCall":{"callId":"call_00_nCs2WqPExU5Z0coTJ8Yh3652","rootCallId":"call_00_nCs2WqPExU5Z0coTJ8Yh3652"}}}
|
||||
{"type":"job/settled","data":{"jobId":"{{id:1}}","owner":"{{session:1}}","kind":"bash","label":"ls -1 .","startedAt":1791427804374,"toolCall":{"callId":"call_00_nCs2WqPExU5Z0coTJ8Yh3652","rootCallId":"call_00_nCs2WqPExU5Z0coTJ8Yh3652"},"finishedAt":1791427804379,"status":"completed","cause":"producer","producerReported":true,"detail":"exit code: 0"}}
|
||||
{"type":"tool/result","data":{"turn":1,"step":1,"message":{"role":"tool","source":{"kind":"tool","callId":"call_00_nCs2WqPExU5Z0coTJ8Yh3652"},"toolCallId":"call_00_nCs2WqPExU5Z0coTJ8Yh3652","content":[{"type":"text","text":"about.txt\nnotes.txt\n"}],"isError":false,"id":"{{message:5}}"},"meta":{"cwd":"{{cwd}}"}},"sourceEventSeqs":[14],"surfaceOp":"append"}
|
||||
{"type":"step/end","data":{"turn":1,"step":1}}
|
||||
{"type":"step/start","data":{"turn":1,"step":2}}
|
||||
|
||||
@@ -15,8 +15,8 @@
|
||||
{"type":"session/title","data":{"title":"Establish a durable compaction premise","messageSeqs":[9],"source":{"kind":"fallback"}}}
|
||||
{"type":"assistant/message","data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"tool-call","id":"call_compaction_marker","name":"bash","arguments":"{\"command\":\"printf 'alpha\\n'\",\"description\":\"Emit compaction premise marker\"}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{message:4}}"},"usage":{"inputTokens":24,"outputTokens":6},"stream":[{"type":"chunk","time":1791427805327,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}},{"type":"tool-call-chunks","time0":1791427805327,"index":0,"dt":[],"id":"call_compaction_marker","name":"bash","args":["{\"command\":\"printf 'alpha\\n'\",\"description\":\"Emit compaction premise marker\"}"]},{"type":"chunk","time":1791427805327,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"call_compaction_marker","name":"bash","arguments":"{\"command\":\"printf 'alpha\\n'\",\"description\":\"Emit compaction premise marker\"}"}}},{"type":"chunk","time":1791427805327,"chunk":{"type":"usage","usage":{"inputTokens":24,"outputTokens":6}}},{"type":"chunk","time":1791427805327,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}]},"surfaceOp":"append"}
|
||||
{"type":"tool/call","data":{"turn":1,"step":1,"callId":"call_compaction_marker","name":"bash","arguments":"{\"command\":\"printf 'alpha\\n'\",\"description\":\"Emit compaction premise marker\"}"}}
|
||||
{"type":"job/registered","data":{"executionId":"{{id:1}}","jobId":"bash-1","owner":"{{session:1}}","kind":"bash","label":"printf 'alpha\n'","startedAt":1791427805336,"toolCall":{"callId":"call_compaction_marker","rootCallId":"call_compaction_marker"}}}
|
||||
{"type":"job/settled","data":{"executionId":"{{id:1}}","jobId":"bash-1","owner":"{{session:1}}","kind":"bash","label":"printf 'alpha\n'","startedAt":1791427805336,"toolCall":{"callId":"call_compaction_marker","rootCallId":"call_compaction_marker"},"finishedAt":1791427805338,"status":"completed","cause":"producer","producerReported":true,"detail":"exit code: 0"}}
|
||||
{"type":"job/registered","data":{"jobId":"{{id:1}}","owner":"{{session:1}}","kind":"bash","label":"printf 'alpha\n'","startedAt":1791427805336,"toolCall":{"callId":"call_compaction_marker","rootCallId":"call_compaction_marker"}}}
|
||||
{"type":"job/settled","data":{"jobId":"{{id:1}}","owner":"{{session:1}}","kind":"bash","label":"printf 'alpha\n'","startedAt":1791427805336,"toolCall":{"callId":"call_compaction_marker","rootCallId":"call_compaction_marker"},"finishedAt":1791427805338,"status":"completed","cause":"producer","producerReported":true,"detail":"exit code: 0"}}
|
||||
{"type":"tool/result","data":{"turn":1,"step":1,"message":{"role":"tool","source":{"kind":"tool","callId":"call_compaction_marker"},"toolCallId":"call_compaction_marker","content":[{"type":"text","text":"alpha\n"}],"isError":false,"id":"{{message:5}}"},"meta":{"cwd":"/tmp"}},"sourceEventSeqs":[15],"surfaceOp":"append"}
|
||||
{"type":"step/end","data":{"turn":1,"step":1}}
|
||||
{"type":"compaction/start","data":{"compactionId":"{{id:2}}","turn":1}}
|
||||
|
||||
@@ -14,8 +14,8 @@
|
||||
{"type":"session/title","data":{"title":"Establish a durable compaction premise","messageSeqs":[8],"source":{"kind":"fallback"}}}
|
||||
{"type":"assistant/message","data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"tool-call","id":"call_compaction_marker","name":"bash","arguments":"{\"command\":\"printf 'alpha\\n'\",\"description\":\"Emit compaction premise marker\"}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{message:4}}"},"usage":{"inputTokens":24,"outputTokens":6},"stream":[{"type":"chunk","time":1791427806185,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}},{"type":"tool-call-chunks","time0":1791427806185,"index":0,"dt":[],"id":"call_compaction_marker","name":"bash","args":["{\"command\":\"printf 'alpha\\n'\",\"description\":\"Emit compaction premise marker\"}"]},{"type":"chunk","time":1791427806185,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"call_compaction_marker","name":"bash","arguments":"{\"command\":\"printf 'alpha\\n'\",\"description\":\"Emit compaction premise marker\"}"}}},{"type":"chunk","time":1791427806185,"chunk":{"type":"usage","usage":{"inputTokens":24,"outputTokens":6}}},{"type":"chunk","time":1791427806185,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}]},"surfaceOp":"append"}
|
||||
{"type":"tool/call","data":{"turn":1,"step":1,"callId":"call_compaction_marker","name":"bash","arguments":"{\"command\":\"printf 'alpha\\n'\",\"description\":\"Emit compaction premise marker\"}"}}
|
||||
{"type":"job/registered","data":{"executionId":"{{id:1}}","jobId":"bash-1","owner":"{{session:1}}","kind":"bash","label":"printf 'alpha\n'","startedAt":1791427806194,"toolCall":{"callId":"call_compaction_marker","rootCallId":"call_compaction_marker"}}}
|
||||
{"type":"job/settled","data":{"executionId":"{{id:1}}","jobId":"bash-1","owner":"{{session:1}}","kind":"bash","label":"printf 'alpha\n'","startedAt":1791427806194,"toolCall":{"callId":"call_compaction_marker","rootCallId":"call_compaction_marker"},"finishedAt":1791427806196,"status":"completed","cause":"producer","producerReported":true,"detail":"exit code: 0"}}
|
||||
{"type":"job/registered","data":{"jobId":"{{id:1}}","owner":"{{session:1}}","kind":"bash","label":"printf 'alpha\n'","startedAt":1791427806194,"toolCall":{"callId":"call_compaction_marker","rootCallId":"call_compaction_marker"}}}
|
||||
{"type":"job/settled","data":{"jobId":"{{id:1}}","owner":"{{session:1}}","kind":"bash","label":"printf 'alpha\n'","startedAt":1791427806194,"toolCall":{"callId":"call_compaction_marker","rootCallId":"call_compaction_marker"},"finishedAt":1791427806196,"status":"completed","cause":"producer","producerReported":true,"detail":"exit code: 0"}}
|
||||
{"type":"tool/result","data":{"turn":1,"step":1,"message":{"role":"tool","source":{"kind":"tool","callId":"call_compaction_marker"},"toolCallId":"call_compaction_marker","content":[{"type":"text","text":"alpha\n"}],"isError":false,"id":"{{message:5}}"},"meta":{"cwd":"{{cwd}}"}},"sourceEventSeqs":[14],"surfaceOp":"append"}
|
||||
{"type":"step/end","data":{"turn":1,"step":1}}
|
||||
{"type":"step/start","data":{"turn":1,"step":2}}
|
||||
|
||||
@@ -15,8 +15,8 @@
|
||||
{"type":"session/title","data":{"title":"Establish a durable compaction premise","messageSeqs":[9],"source":{"kind":"fallback"}}}
|
||||
{"type":"assistant/message","data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"tool-call","id":"call_compaction_marker","name":"bash","arguments":"{\"command\":\"printf 'alpha\\n'\",\"description\":\"Emit compaction premise marker\"}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{message:4}}"},"usage":{"inputTokens":24,"outputTokens":6},"stream":[{"type":"chunk","time":1791427807038,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}},{"type":"tool-call-chunks","time0":1791427807038,"index":0,"dt":[],"id":"call_compaction_marker","name":"bash","args":["{\"command\":\"printf 'alpha\\n'\",\"description\":\"Emit compaction premise marker\"}"]},{"type":"chunk","time":1791427807038,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"call_compaction_marker","name":"bash","arguments":"{\"command\":\"printf 'alpha\\n'\",\"description\":\"Emit compaction premise marker\"}"}}},{"type":"chunk","time":1791427807038,"chunk":{"type":"usage","usage":{"inputTokens":24,"outputTokens":6}}},{"type":"chunk","time":1791427807038,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}]},"surfaceOp":"append"}
|
||||
{"type":"tool/call","data":{"turn":1,"step":1,"callId":"call_compaction_marker","name":"bash","arguments":"{\"command\":\"printf 'alpha\\n'\",\"description\":\"Emit compaction premise marker\"}"}}
|
||||
{"type":"job/registered","data":{"executionId":"{{id:1}}","jobId":"bash-1","owner":"{{session:1}}","kind":"bash","label":"printf 'alpha\n'","startedAt":1791427807048,"toolCall":{"callId":"call_compaction_marker","rootCallId":"call_compaction_marker"}}}
|
||||
{"type":"job/settled","data":{"executionId":"{{id:1}}","jobId":"bash-1","owner":"{{session:1}}","kind":"bash","label":"printf 'alpha\n'","startedAt":1791427807048,"toolCall":{"callId":"call_compaction_marker","rootCallId":"call_compaction_marker"},"finishedAt":1791427807051,"status":"completed","cause":"producer","producerReported":true,"detail":"exit code: 0"}}
|
||||
{"type":"job/registered","data":{"jobId":"{{id:1}}","owner":"{{session:1}}","kind":"bash","label":"printf 'alpha\n'","startedAt":1791427807048,"toolCall":{"callId":"call_compaction_marker","rootCallId":"call_compaction_marker"}}}
|
||||
{"type":"job/settled","data":{"jobId":"{{id:1}}","owner":"{{session:1}}","kind":"bash","label":"printf 'alpha\n'","startedAt":1791427807048,"toolCall":{"callId":"call_compaction_marker","rootCallId":"call_compaction_marker"},"finishedAt":1791427807051,"status":"completed","cause":"producer","producerReported":true,"detail":"exit code: 0"}}
|
||||
{"type":"tool/result","data":{"turn":1,"step":1,"message":{"role":"tool","source":{"kind":"tool","callId":"call_compaction_marker"},"toolCallId":"call_compaction_marker","content":[{"type":"text","text":"alpha\n"}],"isError":false,"id":"{{message:5}}"},"meta":{"cwd":"/tmp"}},"sourceEventSeqs":[15],"surfaceOp":"append"}
|
||||
{"type":"step/end","data":{"turn":1,"step":1}}
|
||||
{"type":"compaction/start","data":{"compactionId":"{{id:2}}","turn":1}}
|
||||
|
||||
@@ -14,8 +14,8 @@
|
||||
{"type":"session/title","data":{"title":"Use the bash tool to","messageSeqs":[8],"source":{"kind":"fallback"}}}
|
||||
{"type":"assistant/message","data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"reasoning","text":"The user wants me to run a simple bash command and then reply with \"DONE\"."},{"type":"tool-call","id":"call_00_fkbBRJsUrGKd1pWVc4Gn8233","name":"bash","arguments":"{\"command\": \"echo TERMINAL_OK\", \"description\": \"Echo TERMINAL_OK to verify terminal access\"}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash","replayState":{"response":{"kind":"deepseek-messages","version":2,"model":"deepseek-v4-flash"},"blocks":[{"type":"reasoning","signature":"snapshot-signature"},{"type":"tool-call"}]}},"id":"{{message:4}}"},"usage":{"inputTokens":2877,"outputTokens":90,"cacheReadTokens":0,"totalTokens":2967},"stream":[{"type":"chunk","time":1791427812803,"chunk":{"type":"block-start","index":0,"blockType":"reasoning"}},{"type":"reasoning-chunks","time0":1791427812804,"index":0,"dt":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"texts":["The"," user"," wants"," me"," to"," run"," a"," simple"," bash"," command"," and"," then"," reply"," with"," \"","D","ONE","\"."]},{"type":"chunk","time":1791427812804,"chunk":{"type":"block-start","index":1,"blockType":"tool-call"}},{"type":"tool-call-chunks","time0":1791427812804,"index":1,"dt":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"id":"call_00_fkbBRJsUrGKd1pWVc4Gn8233","name":"bash","args":["","{","\"","command","\"",": ","\"","echo"," TER","MIN","AL","_OK","\"",", ","\"","description","\"",": ","\"","E","cho"," TER","MIN","AL","_OK"," to"," verify"," terminal"," access","\"","}"]},{"type":"chunk","time":1791427812804,"chunk":{"type":"block-end","index":0,"block":{"type":"reasoning","text":"The user wants me to run a simple bash command and then reply with \"DONE\"."}}},{"type":"chunk","time":1791427812804,"chunk":{"type":"block-end","index":1,"block":{"type":"tool-call","id":"call_00_fkbBRJsUrGKd1pWVc4Gn8233","name":"bash","arguments":"{\"command\": \"echo TERMINAL_OK\", \"description\": \"Echo TERMINAL_OK to verify terminal access\"}"}}},{"type":"chunk","time":1791427812804,"chunk":{"type":"usage","usage":{"inputTokens":2877,"outputTokens":90,"cacheReadTokens":0,"totalTokens":2967}}},{"type":"chunk","time":1791427812804,"chunk":{"type":"finish","reason":{"kind":"tool-calls"},"replayState":{"response":{"kind":"deepseek-messages","version":2,"model":"deepseek-v4-flash"},"blocks":[{"type":"reasoning","signature":"snapshot-signature"},{"type":"tool-call"}]}}}]},"surfaceOp":"append"}
|
||||
{"type":"tool/call","data":{"turn":1,"step":1,"callId":"call_00_fkbBRJsUrGKd1pWVc4Gn8233","name":"bash","arguments":"{\"command\": \"echo TERMINAL_OK\", \"description\": \"Echo TERMINAL_OK to verify terminal access\"}"}}
|
||||
{"type":"job/registered","data":{"executionId":"{{id:1}}","jobId":"bash-1","owner":"{{session:1}}","kind":"bash","label":"echo TERMINAL_OK","startedAt":1791427812813,"toolCall":{"callId":"call_00_fkbBRJsUrGKd1pWVc4Gn8233","rootCallId":"call_00_fkbBRJsUrGKd1pWVc4Gn8233"}}}
|
||||
{"type":"job/settled","data":{"executionId":"{{id:1}}","jobId":"bash-1","owner":"{{session:1}}","kind":"bash","label":"echo TERMINAL_OK","startedAt":1791427812813,"toolCall":{"callId":"call_00_fkbBRJsUrGKd1pWVc4Gn8233","rootCallId":"call_00_fkbBRJsUrGKd1pWVc4Gn8233"},"finishedAt":1791427812816,"status":"completed","cause":"producer","producerReported":true,"detail":"exit code: 0"}}
|
||||
{"type":"job/registered","data":{"jobId":"{{id:1}}","owner":"{{session:1}}","kind":"bash","label":"echo TERMINAL_OK","startedAt":1791427812813,"toolCall":{"callId":"call_00_fkbBRJsUrGKd1pWVc4Gn8233","rootCallId":"call_00_fkbBRJsUrGKd1pWVc4Gn8233"}}}
|
||||
{"type":"job/settled","data":{"jobId":"{{id:1}}","owner":"{{session:1}}","kind":"bash","label":"echo TERMINAL_OK","startedAt":1791427812813,"toolCall":{"callId":"call_00_fkbBRJsUrGKd1pWVc4Gn8233","rootCallId":"call_00_fkbBRJsUrGKd1pWVc4Gn8233"},"finishedAt":1791427812816,"status":"completed","cause":"producer","producerReported":true,"detail":"exit code: 0"}}
|
||||
{"type":"tool/result","data":{"turn":1,"step":1,"message":{"role":"tool","source":{"kind":"tool","callId":"call_00_fkbBRJsUrGKd1pWVc4Gn8233"},"toolCallId":"call_00_fkbBRJsUrGKd1pWVc4Gn8233","content":[{"type":"text","text":"TERMINAL_OK\n"}],"isError":false,"id":"{{message:5}}"},"meta":{"cwd":"{{cwd}}"}},"sourceEventSeqs":[14],"surfaceOp":"append"}
|
||||
{"type":"step/end","data":{"turn":1,"step":1}}
|
||||
{"type":"step/start","data":{"turn":1,"step":2}}
|
||||
|
||||
@@ -14,8 +14,8 @@
|
||||
{"type":"session/title","data":{"title":"Use the bash tool to","messageSeqs":[8],"source":{"kind":"fallback"}}}
|
||||
{"type":"assistant/message","data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"reasoning","text":"The user wants me to run a simple bash command and then reply with \"DONE\"."},{"type":"tool-call","id":"call_00_fkbBRJsUrGKd1pWVc4Gn8233","name":"bash","arguments":"{\"command\": \"echo TERMINAL_OK\", \"description\": \"Echo TERMINAL_OK to verify terminal access\"}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash","replayState":{"response":{"kind":"deepseek-messages","version":1,"model":"deepseek-v4-flash"},"blocks":[{"type":"reasoning","signature":"snapshot-signature"},{"type":"tool-call"}]}},"id":"{{message:4}}"},"usage":{"inputTokens":2877,"outputTokens":90,"cacheReadTokens":0,"totalTokens":2967},"stream":[{"type":"chunk","time":1791427815257,"chunk":{"type":"block-start","index":0,"blockType":"reasoning"}},{"type":"reasoning-chunks","time0":1791427815257,"index":0,"dt":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"texts":["The"," user"," wants"," me"," to"," run"," a"," simple"," bash"," command"," and"," then"," reply"," with"," \"","D","ONE","\"."]},{"type":"chunk","time":1791427815257,"chunk":{"type":"block-start","index":1,"blockType":"tool-call"}},{"type":"tool-call-chunks","time0":1791427815257,"index":1,"dt":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0],"id":"call_00_fkbBRJsUrGKd1pWVc4Gn8233","name":"bash","args":["","{","\"","command","\"",": ","\"","echo"," TER","MIN","AL","_OK","\"",", ","\"","description","\"",": ","\"","E","cho"," TER","MIN","AL","_OK"," to"," verify"," terminal"," access","\"","}"]},{"type":"chunk","time":1791427815258,"chunk":{"type":"block-end","index":0,"block":{"type":"reasoning","text":"The user wants me to run a simple bash command and then reply with \"DONE\"."}}},{"type":"chunk","time":1791427815258,"chunk":{"type":"block-end","index":1,"block":{"type":"tool-call","id":"call_00_fkbBRJsUrGKd1pWVc4Gn8233","name":"bash","arguments":"{\"command\": \"echo TERMINAL_OK\", \"description\": \"Echo TERMINAL_OK to verify terminal access\"}"}}},{"type":"chunk","time":1791427815258,"chunk":{"type":"usage","usage":{"inputTokens":2877,"outputTokens":90,"cacheReadTokens":0,"totalTokens":2967}}},{"type":"chunk","time":1791427815258,"chunk":{"type":"finish","reason":{"kind":"tool-calls"},"replayState":{"response":{"kind":"deepseek-messages","version":1,"model":"deepseek-v4-flash"},"blocks":[{"type":"reasoning","signature":"snapshot-signature"},{"type":"tool-call"}]}}}]},"surfaceOp":"append"}
|
||||
{"type":"tool/call","data":{"turn":1,"step":1,"callId":"call_00_fkbBRJsUrGKd1pWVc4Gn8233","name":"bash","arguments":"{\"command\": \"echo TERMINAL_OK\", \"description\": \"Echo TERMINAL_OK to verify terminal access\"}"}}
|
||||
{"type":"job/registered","data":{"executionId":"{{id:1}}","jobId":"bash-1","owner":"{{session:1}}","kind":"bash","label":"echo TERMINAL_OK","startedAt":1791427815268,"toolCall":{"callId":"call_00_fkbBRJsUrGKd1pWVc4Gn8233","rootCallId":"call_00_fkbBRJsUrGKd1pWVc4Gn8233"}}}
|
||||
{"type":"job/settled","data":{"executionId":"{{id:1}}","jobId":"bash-1","owner":"{{session:1}}","kind":"bash","label":"echo TERMINAL_OK","startedAt":1791427815268,"toolCall":{"callId":"call_00_fkbBRJsUrGKd1pWVc4Gn8233","rootCallId":"call_00_fkbBRJsUrGKd1pWVc4Gn8233"},"finishedAt":1791427815271,"status":"completed","cause":"producer","producerReported":true,"detail":"exit code: 0"}}
|
||||
{"type":"job/registered","data":{"jobId":"{{id:1}}","owner":"{{session:1}}","kind":"bash","label":"echo TERMINAL_OK","startedAt":1791427815268,"toolCall":{"callId":"call_00_fkbBRJsUrGKd1pWVc4Gn8233","rootCallId":"call_00_fkbBRJsUrGKd1pWVc4Gn8233"}}}
|
||||
{"type":"job/settled","data":{"jobId":"{{id:1}}","owner":"{{session:1}}","kind":"bash","label":"echo TERMINAL_OK","startedAt":1791427815268,"toolCall":{"callId":"call_00_fkbBRJsUrGKd1pWVc4Gn8233","rootCallId":"call_00_fkbBRJsUrGKd1pWVc4Gn8233"},"finishedAt":1791427815271,"status":"completed","cause":"producer","producerReported":true,"detail":"exit code: 0"}}
|
||||
{"type":"tool/result","data":{"turn":1,"step":1,"message":{"role":"tool","source":{"kind":"tool","callId":"call_00_fkbBRJsUrGKd1pWVc4Gn8233"},"toolCallId":"call_00_fkbBRJsUrGKd1pWVc4Gn8233","content":[{"type":"text","text":"TERMINAL_OK\n"}],"isError":false,"id":"{{message:5}}"},"meta":{"cwd":"{{cwd}}"}},"sourceEventSeqs":[14],"surfaceOp":"append"}
|
||||
{"type":"step/end","data":{"turn":1,"step":1}}
|
||||
{"type":"step/start","data":{"turn":1,"step":2}}
|
||||
|
||||
@@ -12,9 +12,9 @@
|
||||
{"type":"request/header","data":{"header":{"config":{"provider":"deepseek-official","model":"deepseek-v4-flash"},"tools":"{{tools}}"},"reason":"initial"}}
|
||||
{"type":"request/context","data":{"provider":"deepseek-official","model":"deepseek-v4-flash"}}
|
||||
{"type":"session/title","data":{"title":"This prompt first receives an","messageSeqs":[8],"source":{"kind":"fallback"}}}
|
||||
{"type":"assistant/attempt","data":{"turn":1,"step":1,"stream":[{"type":"chunk","time":1791434106211,"chunk":{"type":"usage","usage":{"inputTokens":0,"outputTokens":0}}},{"type":"chunk","time":1791434106211,"chunk":{"type":"finish","reason":{"kind":"error","failure":{"message":"model returned a completed response with no content","code":"EMPTY_RESPONSE"}}}}]}}
|
||||
{"type":"assistant/attempt","data":{"turn":1,"step":1,"stream":[{"type":"chunk","time":1791444890557,"chunk":{"type":"usage","usage":{"inputTokens":0,"outputTokens":0}}},{"type":"chunk","time":1791444890557,"chunk":{"type":"finish","reason":{"kind":"error","failure":{"message":"model returned a completed response with no content","code":"EMPTY_RESPONSE"}}}}]}}
|
||||
{"type":"llm/retry","data":{"retryId":"{{retry:1}}","turn":1,"step":1,"provider":"deepseek-official","mode":"normal","policyKey":"[\"normal\",2,[\"EMPTY_RESPONSE\",\"RATE_LIMIT\",\"SERVER\",\"TIMEOUT\",\"TRANSPORT\"],1,1,0]","retry":1,"maxRetries":2,"delayMs":1,"failure":{"message":"model returned a completed response with no content","code":"EMPTY_RESPONSE"}}}
|
||||
{"type":"llm/retry-started","data":{"retryId":"{{retry:1}}","turn":1,"step":1,"retry":1}}
|
||||
{"type":"assistant/message","data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"text","text":"Recovered."}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{message:4}}"},"usage":{"inputTokens":12,"outputTokens":3},"stream":[{"type":"chunk","time":1791434106218,"chunk":{"type":"block-start","index":0,"blockType":"text"}},{"type":"text-chunks","time0":1791434106218,"index":0,"dt":[],"texts":["Recovered."]},{"type":"chunk","time":1791434106218,"chunk":{"type":"block-end","index":0,"block":{"type":"text","text":"Recovered."}}},{"type":"chunk","time":1791434106218,"chunk":{"type":"usage","usage":{"inputTokens":12,"outputTokens":3}}},{"type":"chunk","time":1791434106218,"chunk":{"type":"finish","reason":{"kind":"stop"}}}]},"surfaceOp":"append"}
|
||||
{"type":"assistant/message","data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"text","text":"Recovered."}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{message:4}}"},"usage":{"inputTokens":12,"outputTokens":3},"stream":[{"type":"chunk","time":1791444890563,"chunk":{"type":"block-start","index":0,"blockType":"text"}},{"type":"text-chunks","time0":1791444890564,"index":0,"dt":[],"texts":["Recovered."]},{"type":"chunk","time":1791444890564,"chunk":{"type":"block-end","index":0,"block":{"type":"text","text":"Recovered."}}},{"type":"chunk","time":1791444890564,"chunk":{"type":"usage","usage":{"inputTokens":12,"outputTokens":3}}},{"type":"chunk","time":1791444890564,"chunk":{"type":"finish","reason":{"kind":"stop"}}}]},"surfaceOp":"append"}
|
||||
{"type":"step/end","data":{"turn":1,"step":1}}
|
||||
{"type":"turn/end","data":{"turn":1,"reason":{"kind":"completed"}}}
|
||||
@@ -12,6 +12,6 @@
|
||||
{"type":"request/header","data":{"header":{"config":{"provider":"deepseek-official","model":"deepseek-v4-flash"},"tools":"{{tools}}"},"reason":"initial"}}
|
||||
{"type":"request/context","data":{"provider":"deepseek-official","model":"deepseek-v4-flash"}}
|
||||
{"type":"session/title","data":{"title":"This prompt triggers a recorded","messageSeqs":[8],"source":{"kind":"fallback"}}}
|
||||
{"type":"assistant/attempt","data":{"turn":1,"step":1,"stream":[{"type":"chunk","time":1791434107899,"chunk":{"type":"finish","reason":{"kind":"error","failure":{"message":"simulated provider error (HTTP 401)","code":"AUTH"}}}}]}}
|
||||
{"type":"assistant/attempt","data":{"turn":1,"step":1,"stream":[{"type":"chunk","time":1791444892025,"chunk":{"type":"finish","reason":{"kind":"error","failure":{"message":"simulated provider error (HTTP 401)","code":"AUTH"}}}}]}}
|
||||
{"type":"step/end","data":{"turn":1,"step":1}}
|
||||
{"type":"turn/end","data":{"turn":1,"reason":{"kind":"error","error":{"message":"simulated provider error (HTTP 401)","code":"AUTH"}}}}
|
||||
@@ -14,8 +14,8 @@
|
||||
{"type":"session/title","data":{"title":"Run printf SHOULD_NOT_RUN through the","messageSeqs":[8],"source":{"kind":"fallback"}}}
|
||||
{"type":"assistant/message","data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"tool-call","id":"foreground-confinement-timeout","name":"bash","arguments":"{\"command\":\"printf SHOULD_NOT_RUN > unexpected-command.txt\",\"timeoutMs\":10,\"description\":\"Bound confinement preparation before process launch\"}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{message:4}}"},"usage":{"inputTokens":1,"outputTokens":1},"stream":[{"type":"chunk","time":1791427822119,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}},{"type":"chunk","time":1791427822119,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"foreground-confinement-timeout","name":"bash","arguments":"{\"command\":\"printf SHOULD_NOT_RUN > unexpected-command.txt\",\"timeoutMs\":10,\"description\":\"Bound confinement preparation before process launch\"}"}}},{"type":"chunk","time":1791427822119,"chunk":{"type":"usage","usage":{"inputTokens":1,"outputTokens":1}}},{"type":"chunk","time":1791427822119,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}]},"surfaceOp":"append"}
|
||||
{"type":"tool/call","data":{"turn":1,"step":1,"callId":"foreground-confinement-timeout","name":"bash","arguments":"{\"command\":\"printf SHOULD_NOT_RUN > unexpected-command.txt\",\"timeoutMs\":10,\"description\":\"Bound confinement preparation before process launch\"}"}}
|
||||
{"type":"job/registered","data":{"executionId":"{{id:1}}","jobId":"bash-1","owner":"{{session:1}}","kind":"bash","label":"printf SHOULD_NOT_RUN > unexpected-command.txt","startedAt":1791427822125,"toolCall":{"callId":"foreground-confinement-timeout","rootCallId":"foreground-confinement-timeout"}}}
|
||||
{"type":"job/settled","data":{"executionId":"{{id:1}}","jobId":"bash-1","owner":"{{session:1}}","kind":"bash","label":"printf SHOULD_NOT_RUN > unexpected-command.txt","startedAt":1791427822125,"toolCall":{"callId":"foreground-confinement-timeout","rootCallId":"foreground-confinement-timeout"},"finishedAt":1791427822137,"status":"killed","cause":"kill","producerReported":true,"detail":"timed out during preparation; timed out during preparation"}}
|
||||
{"type":"job/registered","data":{"jobId":"{{id:1}}","owner":"{{session:1}}","kind":"bash","label":"printf SHOULD_NOT_RUN > unexpected-command.txt","startedAt":1791427822125,"toolCall":{"callId":"foreground-confinement-timeout","rootCallId":"foreground-confinement-timeout"}}}
|
||||
{"type":"job/settled","data":{"jobId":"{{id:1}}","owner":"{{session:1}}","kind":"bash","label":"printf SHOULD_NOT_RUN > unexpected-command.txt","startedAt":1791427822125,"toolCall":{"callId":"foreground-confinement-timeout","rootCallId":"foreground-confinement-timeout"},"finishedAt":1791427822137,"status":"killed","cause":"kill","producerReported":true,"detail":"timed out during preparation; timed out during preparation"}}
|
||||
{"type":"tool/result","data":{"turn":1,"step":1,"message":{"role":"tool","source":{"kind":"tool","callId":"foreground-confinement-timeout"},"toolCallId":"foreground-confinement-timeout","content":[{"type":"text","text":"(no output)\n[timed out after 10ms]\n[exit code: null]"}],"isError":false,"id":"{{message:5}}"},"meta":{"cwd":"{{cwd}}"}},"sourceEventSeqs":[14],"surfaceOp":"append"}
|
||||
{"type":"step/end","data":{"turn":1,"step":1}}
|
||||
{"type":"step/start","data":{"turn":1,"step":2}}
|
||||
|
||||
@@ -19,8 +19,8 @@
|
||||
{"type":"step/start","data":{"turn":1,"step":2}}
|
||||
{"type":"assistant/message","data":{"turn":1,"step":2,"message":{"role":"assistant","content":[{"type":"tool-call","id":"fs-delete-remove","name":"bash","arguments":"{\"command\":\"rm deleted.txt\",\"description\":\"Delete deleted.txt\"}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{message:6}}"},"usage":{"inputTokens":10,"outputTokens":5},"stream":[{"type":"chunk","time":1791427823079,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}},{"type":"tool-call-chunks","time0":1791427823079,"index":0,"dt":[],"id":"fs-delete-remove","name":"bash","args":["{\"command\":\"rm deleted.txt\",\"description\":\"Delete deleted.txt\"}"]},{"type":"chunk","time":1791427823079,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"fs-delete-remove","name":"bash","arguments":"{\"command\":\"rm deleted.txt\",\"description\":\"Delete deleted.txt\"}"}}},{"type":"chunk","time":1791427823079,"chunk":{"type":"usage","usage":{"inputTokens":10,"outputTokens":5}}},{"type":"chunk","time":1791427823079,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}]},"surfaceOp":"append"}
|
||||
{"type":"tool/call","data":{"turn":1,"step":2,"callId":"fs-delete-remove","name":"bash","arguments":"{\"command\":\"rm deleted.txt\",\"description\":\"Delete deleted.txt\"}"}}
|
||||
{"type":"job/registered","data":{"executionId":"{{id:1}}","jobId":"bash-1","owner":"{{session:1}}","kind":"bash","label":"rm deleted.txt","startedAt":1791427823087,"toolCall":{"callId":"fs-delete-remove","rootCallId":"fs-delete-remove"}}}
|
||||
{"type":"job/settled","data":{"executionId":"{{id:1}}","jobId":"bash-1","owner":"{{session:1}}","kind":"bash","label":"rm deleted.txt","startedAt":1791427823087,"toolCall":{"callId":"fs-delete-remove","rootCallId":"fs-delete-remove"},"finishedAt":1791427823094,"status":"completed","cause":"producer","producerReported":true,"detail":"exit code: 0"}}
|
||||
{"type":"job/registered","data":{"jobId":"{{id:1}}","owner":"{{session:1}}","kind":"bash","label":"rm deleted.txt","startedAt":1791427823087,"toolCall":{"callId":"fs-delete-remove","rootCallId":"fs-delete-remove"}}}
|
||||
{"type":"job/settled","data":{"jobId":"{{id:1}}","owner":"{{session:1}}","kind":"bash","label":"rm deleted.txt","startedAt":1791427823087,"toolCall":{"callId":"fs-delete-remove","rootCallId":"fs-delete-remove"},"finishedAt":1791427823094,"status":"completed","cause":"producer","producerReported":true,"detail":"exit code: 0"}}
|
||||
{"type":"tool/result","data":{"turn":1,"step":2,"message":{"role":"tool","source":{"kind":"tool","callId":"fs-delete-remove"},"toolCallId":"fs-delete-remove","content":[{"type":"text","text":"(no output)"}],"isError":false,"id":"{{message:7}}"},"meta":{"cwd":"{{cwd}}"}},"sourceEventSeqs":[19],"surfaceOp":"append"}
|
||||
{"type":"step/end","data":{"turn":1,"step":2}}
|
||||
{"type":"step/start","data":{"turn":1,"step":3}}
|
||||
|
||||
@@ -740,10 +740,16 @@ function verifyNoMcpServers(log: string, ptc: boolean): void {
|
||||
|
||||
/** Require an admitted failed job and zero process allocations before updating its recorded oracle. */
|
||||
async function verifyBackgroundConfinementFailure(log: string, cwd: string): Promise<void> {
|
||||
const results = parseSessionLog(log).flatMap(event => event.type === 'tool/result' ? [event.data.message] : [])
|
||||
const events = parseSessionLog(log)
|
||||
const results = events.flatMap(event => event.type === 'tool/result' ? [event.data.message] : [])
|
||||
const started = results.find(result => result.toolCallId === 'async-confinement-start')
|
||||
const inspected = results.find(result => result.toolCallId === 'async-confinement-result')
|
||||
expect(started).toMatchObject({ isError: false, content: [{ type: 'text', text: 'started background job bash-1' }] })
|
||||
const registered = events.find(event => event.type === 'job/registered')
|
||||
if (registered?.type !== 'job/registered') throw new Error('missing registered job')
|
||||
expect(registered.data.jobId).toMatch(/^bash-[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/)
|
||||
expect(started).toMatchObject({
|
||||
isError: false, content: [{ type: 'text', text: `started background job ${registered.data.jobId}` }],
|
||||
})
|
||||
expect(inspected?.isError).toBe(false)
|
||||
const text = inspected?.content.flatMap(block => block.type === 'text' ? [block.text] : []).join('')
|
||||
expect(text).toContain('[status: failed,')
|
||||
|
||||
Loaded 100 of 133 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user