Merge branch 'codex/tool-json-schema-dsl' into codex/canonical-tool-output

# Conflicts:
#	docs/event-producer-consumer.md
#	packages/context/time-context/tests/time-context.spec.ts
#	packages/context/workspace-context/tests/workspace-context.spec.ts
This commit is contained in:
Tianyi Cui
2026-07-21 19:56:43 +08:00
509 changed files with 12825 additions and 2596 deletions

View File

@@ -1,6 +1,6 @@
{
"name": "@deepseek-ai/dsh-tasks",
"description": "Background task registry (ctx.tasks) for the DeepSeek Harness \u2014 shared ids, owner isolation, polling, cancellation, and completion listeners for long-running tool work",
"description": "Background task registry (ctx.tasks) for the DeepSeek Harness shared ids, owner isolation, polling, cancellation, and completion listeners for long-running tool work",
"version": "0.0.1",
"private": true,
"type": "module",
@@ -11,11 +11,16 @@
"types": "./lib/types/index.d.ts",
"default": "./lib/index.js"
},
"./invariant": {
"types": "./lib/types/invariant.d.ts",
"default": "./lib/invariant.js"
},
"./src/*": "./src/*",
"./package.json": "./package.json"
},
"files": [
"lib/index.js",
"lib/invariant.js",
"lib/types/**/*.d.ts",
"lib/types/**/*.d.ts.map",
"src"
@@ -24,6 +29,7 @@
"peerDependencies": {
"@deepseek-ai/dsh-agent": "^0.0.1",
"@deepseek-ai/dsh-brand": "^0.0.1",
"@deepseek-ai/dsh-invariants": "^0.0.1",
"@deepseek-ai/dsh-session": "^0.0.1",
"@deepseek-ai/dsh-timeout": "^0.0.1",
"cordis": "^4.0.0-rc.6"
@@ -31,6 +37,7 @@
"devDependencies": {
"@deepseek-ai/dsh-agent": "workspace:^",
"@deepseek-ai/dsh-brand": "workspace:^",
"@deepseek-ai/dsh-invariants": "workspace:^",
"@deepseek-ai/dsh-session": "workspace:^",
"@deepseek-ai/dsh-timeout": "workspace:^",
"cordis": "^4.0.0-rc.6"

View File

@@ -0,0 +1,57 @@
/** Package-owned background-task snapshot invariants. @module @deepseek-ai/dsh-tasks/invariant */
import type { Context } from 'cordis'
import type { Agent } from '@deepseek-ai/dsh-agent'
import type { InvariantFailure, InvariantInstaller } from '@deepseek-ai/dsh-invariants'
import type { TaskSnapshot } from './types.ts'
const PACKAGE_NAME = '@deepseek-ai/dsh-tasks'
const TERMINAL_STATUSES = new Set(['completed', 'killed', 'failed'])
/** Cordis companion plugin name. */
export const name = 'tasks-invariant'
/** Service required before the companion can reserve package ownership. */
export const inject = ['invariants']
/** Validate the cross-field relationships in one registry snapshot. */
function validateSnapshot(snapshot: TaskSnapshot, owner: Agent | undefined, fail: InvariantFailure): void {
const id = String(snapshot.id)
const prefix = `${snapshot.kind}-`
const ordinal = Number(id.slice(prefix.length))
if (snapshot.kind.length === 0 || !id.startsWith(prefix)
|| !Number.isSafeInteger(ordinal) || ordinal < 1) {
fail(`task snapshot id ${JSON.stringify(id)} must be ${JSON.stringify(prefix)} followed by a positive ordinal`)
}
if (snapshot.label.length === 0) fail(`task ${JSON.stringify(id)} label must be non-empty`)
if (!Number.isSafeInteger(snapshot.startedAt) || snapshot.startedAt < 0) {
fail(`task ${JSON.stringify(id)} startedAt must be a non-negative epoch integer`)
}
const terminal = TERMINAL_STATUSES.has(snapshot.status)
if (terminal !== (snapshot.finishedAt !== undefined)) {
fail(`task ${JSON.stringify(id)} finishedAt must be present exactly for a terminal status`)
}
if (snapshot.finishedAt !== undefined
&& (!Number.isSafeInteger(snapshot.finishedAt) || snapshot.finishedAt < snapshot.startedAt)) {
fail(`task ${JSON.stringify(id)} finishedAt must be an epoch integer no earlier than startedAt`)
}
const expectedOwner = owner?.id
if (snapshot.ownerSession !== expectedOwner) {
fail(`task ${JSON.stringify(id)} ownerSession does not match its completion owner`)
}
}
/** Install checks over current unowned records and every terminal snapshot. */
const install: InvariantInstaller = Object.assign((ctx: Context, fail: InvariantFailure) => {
for (const snapshot of ctx.tasks.list()) validateSnapshot(snapshot, undefined, fail)
ctx.tasks.onTaskDone((snapshot, owner) => { validateSnapshot(snapshot, owner, fail) })
}, { inject: ['tasks'] })
/**
* Register the task-registry invariant companion.
* @param ctx - Cordis context carrying the invariant service.
* @returns the installed registration's disposer after setup succeeds.
*/
export const apply = (ctx: Context): Promise<() => void> =>
Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install))

View File

@@ -0,0 +1,87 @@
import { describe, expect, it } from 'vitest'
import { Context } from 'cordis'
import type { Agent } from '@deepseek-ai/dsh-agent'
import { SessionId } from '@deepseek-ai/dsh-session'
import TaskService, { TaskId } from '@deepseek-ai/dsh-tasks'
import type { TaskDoneListener, TaskSnapshot } from '@deepseek-ai/dsh-tasks'
import * as TasksInvariant from '@deepseek-ai/dsh-tasks/invariant'
import InvariantService from '@deepseek-ai/dsh-invariants'
const BASE: TaskSnapshot = {
id: TaskId('bash-1'),
kind: 'bash',
label: 'compile',
status: 'completed',
startedAt: 10,
finishedAt: 20,
reported: false,
}
const RUNNING: TaskSnapshot = {
id: TaskId('bash-1'),
kind: 'bash',
label: 'compile',
status: 'running',
startedAt: 10,
reported: false,
}
const TERMINAL_WITHOUT_FINISH: TaskSnapshot = {
id: TaskId('bash-1'),
kind: 'bash',
label: 'compile',
status: 'completed',
startedAt: 10,
reported: false,
}
async function setup(seed: TaskSnapshot[] = []): Promise<(snapshot: unknown, owner?: Agent) => void> {
const ctx = new Context()
let listener: TaskDoneListener | undefined
const probe = {
list: () => seed,
onTaskDone(value: TaskDoneListener) {
listener = value
return () => { listener = undefined }
},
} as unknown as TaskService
await ctx.plugin(InvariantService)
await ctx.plugin({
name: 'task-invariant-probe',
apply(child: Context) { child.provide('tasks', probe) },
})
await ctx.plugin(TasksInvariant)
if (listener === undefined) throw new Error('task invariant did not subscribe to terminal snapshots')
return (snapshot, owner) => { listener!(snapshot as TaskSnapshot, owner) }
}
describe('task-registry invariants', () => {
it('accepts coherent current and terminal snapshots', async () => {
const notify = await setup([RUNNING])
expect(() => { notify(BASE) }).not.toThrow()
const owner = { id: SessionId('owner') } as Agent
expect(() => { notify({ ...BASE, id: TaskId('subagent-2'), kind: 'subagent', ownerSession: owner.id }, owner) })
.not.toThrow()
})
it.each([
[{ ...BASE, id: TaskId('-1'), kind: '' }, undefined, /positive ordinal/],
[{ ...BASE, id: TaskId('other-1') }, undefined, /must be "bash-" followed by a positive ordinal/],
[{ ...BASE, id: TaskId('bash-x') }, undefined, /positive ordinal/],
[{ ...BASE, id: TaskId('bash-0') }, undefined, /positive ordinal/],
[{ ...BASE, startedAt: -1 }, undefined, /startedAt must be a non-negative epoch integer/],
[{ ...BASE, startedAt: 0.5 }, undefined, /startedAt must be a non-negative epoch integer/],
[{ ...BASE, status: 'running' }, undefined, /finishedAt must be present exactly for a terminal status/],
[TERMINAL_WITHOUT_FINISH, undefined, /finishedAt must be present exactly for a terminal status/],
[{ ...BASE, finishedAt: 9 }, undefined, /no earlier than startedAt/],
[{ ...BASE, finishedAt: 20.5 }, undefined, /no earlier than startedAt/],
[{ ...BASE, ownerSession: SessionId('recorded') }, { id: SessionId('actual') } as Agent, /does not match its completion owner/],
] as const)('rejects an incoherent registry snapshot', async (snapshot, owner, message) => {
const notify = await setup()
expect(() => { notify(snapshot, owner) }).toThrow(message)
})
it('rejects an incoherent record already present at installation', async () => {
await expect(setup([{ ...BASE, label: '' }])).rejects.toThrow(/label must be non-empty/)
})
})

View File

@@ -25,6 +25,9 @@
},
{
"path": "../../util/timeout"
},
{
"path": "../../support/invariants"
}
]
}

View File

@@ -11,11 +11,16 @@
"types": "./lib/types/index.d.ts",
"default": "./lib/index.js"
},
"./invariant": {
"types": "./lib/types/invariant.d.ts",
"default": "./lib/invariant.js"
},
"./src/*": "./src/*",
"./package.json": "./package.json"
},
"files": [
"lib/index.js",
"lib/invariant.js",
"lib/types/**/*.d.ts",
"lib/types/**/*.d.ts.map",
"src"
@@ -23,6 +28,7 @@
"license": "BSD-3-Clause",
"peerDependencies": {
"@deepseek-ai/dsh-agent": "^0.0.1",
"@deepseek-ai/dsh-invariants": "^0.0.1",
"@deepseek-ai/dsh-system-prompt": "^0.0.1",
"@deepseek-ai/dsh-tasks": "^0.0.1",
"@deepseek-ai/dsh-tools": "^0.0.1",
@@ -33,6 +39,7 @@
},
"devDependencies": {
"@deepseek-ai/dsh-agent": "workspace:^",
"@deepseek-ai/dsh-invariants": "workspace:^",
"@deepseek-ai/dsh-llm": "workspace:^",
"@deepseek-ai/dsh-session": "workspace:^",
"@deepseek-ai/dsh-system-prompt": "workspace:^",

View File

@@ -0,0 +1,30 @@
/**
* Package-owned invariant companion for `@deepseek-ai/dsh-tool-tasks`.
* @module @deepseek-ai/dsh-tool-tasks/invariant
*/
/* jscpd:ignore-start */
import type { Context } from 'cordis'
import type { InvariantInstaller } from '@deepseek-ai/dsh-invariants'
const PACKAGE_NAME = '@deepseek-ai/dsh-tool-tasks'
/** Cordis companion plugin name. */
export const name = 'tool-tasks-invariant'
/** Service required before the companion can reserve package ownership. */
export const inject = ['invariants']
/**
* No runtime invariant: this model-facing adapter has no independent lifecycle stream; execution
* relations are owned by the capability seam it calls.
*/
const install: InvariantInstaller = () => {}
/**
* Register this package's invariant companion.
* @param ctx - Cordis context carrying the invariant service.
* @returns the installed registration's disposer after setup succeeds.
*/
export const apply = (ctx: Context): Promise<() => void> =>
Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install))
/* jscpd:ignore-end */

View File

@@ -28,6 +28,9 @@
},
{
"path": "../tasks"
},
{
"path": "../../support/invariants"
}
]
}