Merge commit 'f1a9ab14fe882f9b855ea9cc3f16ddc9a3b0284d' into codex/bounded-background-tasks-v2

# Conflicts:
#	.agents/notes/implemented/architecture/2026-06-20-generic-long-running-tool-runtime.i18n.yaml
#	.agents/notes/implemented/architecture/2026-06-20-generic-long-running-tool-runtime.md
#	.agents/notes/implemented/architecture/2026-06-20-generic-long-running-tool-runtime.zh.md
#	.agents/notes/implemented/architecture/2026-07-26-task-registry-seam.i18n.yaml
#	.agents/notes/implemented/architecture/2026-07-26-task-registry-seam.md
#	.agents/notes/implemented/architecture/2026-07-26-task-registry-seam.zh.md
#	docs/config-catalog.i18n.yaml
#	docs/config-catalog.md
#	docs/config-catalog.zh.md
#	docs/subsystems/tasks.i18n.yaml
#	docs/subsystems/tasks.md
#	docs/subsystems/tasks.zh.md
#	packages/examples/acp-demo/src/index.ts
#	packages/tasks/tasks-local/tests/tasks.spec.ts
#	packages/tasks/tasks/README.i18n.yaml
#	packages/tasks/tasks/README.md
#	packages/tasks/tasks/README.zh.md
This commit is contained in:
pku-xht
2026-08-11 16:44:05 +08:00
778 changed files with 4933 additions and 2062 deletions

View File

@@ -3,7 +3,7 @@
* (`ctx.tasks`). It keeps every record in memory and hands out fresh
* snapshots, never live state.
*
* Registrations outlive producer and control-surface fibers. Agent or service
* Registrations outlive producer and controller fibers. Agent or service
* disposal cancels live work and awaits compliant producers; a throwing
* teardown cancel force-fails only the record and reports a possible orphan.
* @module @deepseek-ai/dsh-tasks-local
@@ -68,18 +68,18 @@ function isTerminal(status: TaskStatus): boolean {
}
/**
* One scope's contributions: the control surfaces attached from it and the
* One scope's contributions: the task controllers attached from it and the
* completion listeners registered there. Both tables are anonymous because a
* contribution is identified by its own disposer, never by a name a second
* registrant could shadow.
*/
class TaskLayer implements ScopeLayer {
readonly surfaces = new AnonymousEntries<symbol>()
readonly controllers = new AnonymousEntries<symbol>()
readonly listeners = new AnonymousEntries<TaskDoneListener>()
readonly changed = new AnonymousEntries<TasksChangedListener>()
isEmpty(): boolean {
return this.surfaces.isEmpty() && this.listeners.isEmpty() && this.changed.isEmpty()
return this.controllers.isEmpty() && this.listeners.isEmpty() && this.changed.isEmpty()
}
}
@@ -134,7 +134,7 @@ export class LocalTaskService extends TaskService {
start(spec: TaskStart): TaskId {
if (!this.servesOwner(spec.owner)) {
throw new Error('background tasks unavailable: no control surface serves this agent (load @deepseek-ai/dsh-tool-tasks in its composition)')
throw new Error('background tasks unavailable: no task controller serves this agent (load @deepseek-ai/dsh-tool-tasks in its composition)')
}
if (spec.kind.length === 0) throw new Error('invalid task kind: expected a non-empty string')
if (spec.label.length === 0) throw new Error('invalid task label: expected a non-empty string')
@@ -297,28 +297,28 @@ export class LocalTaskService extends TaskService {
)
}
attachSurface(name: string): () => void {
attachController(name: string): () => void {
// One token per call keeps duplicate labels independently disposable.
const token = Symbol(name)
return this.layers.effect(
this.ctx,
layer => layer.surfaces.append(token),
{ label: 'tasks.attachSurface()' },
layer => layer.controllers.append(token),
{ label: 'tasks.attachController()' },
)
}
/**
* Whether an attached control surface can collect and stop work owned by
* `owner`. The global layer holds every surface attached from an unscoped
* Whether an attached task controller can collect and stop work owned by
* `owner`. The global layer holds every controller attached from an unscoped
* context — a host composition's own controls — and therefore serves every
* owner; a scoped surface serves exactly the agents composed under it.
* owner; a scoped controller serves exactly the agents composed under it.
* @param owner - the task's owner, or undefined for unowned work.
* @returns whether some reachable surface serves the owner.
* @returns whether some reachable controller serves the owner.
*/
private servesOwner(owner?: Agent): boolean {
if (!this.layers.global.surfaces.isEmpty()) return true
if (!this.layers.global.controllers.isEmpty()) return true
return this.layers.chainLayers(owner === undefined ? undefined : scopeOf(owner.ctx))
.some(layer => !layer.surfaces.isEmpty())
.some(layer => !layer.controllers.isEmpty())
}
/** Count authoritative active records for one exact owner or the shared unowned bucket. */