Merge remote-tracking branch 'origin/master' into feature/workspace-picker-composer

This commit is contained in:
NI0317
2026-08-10 17:12:17 +08:00
39 changed files with 407 additions and 106 deletions

View File

@@ -70,17 +70,15 @@
# ── background tasks ────────────────────────────────────────────────────────
- id: tasks
name: cordis:group
group: true
isolate:
tasks: true
config:
- id: tasks-local
name: '@deepseek-ai/dsh-tasks-local'
- id: tool-tasks
name: '@deepseek-ai/dsh-tool-tasks'
# Only the model-facing controls. The task REGISTRY stays on the host plane:
# its producers sit outside any realm this file could put it in — `tool-bash`
# above resolves it with `ctx.get`, and an entry-local realm here is invisible
# to every sibling row, so `run_in_background` would answer "background tasks
# unavailable" while these controls sat in the catalog. The registry is keyed by
# owning agent anyway, so one host instance serves every session. What a preset
# chooses is whether its agent can collect and stop background work at all.
- id: tool-tasks
name: '@deepseek-ai/dsh-tool-tasks'
# ── skills ──────────────────────────────────────────────────────────────────

View File

@@ -64,17 +64,15 @@
# ── background tasks ────────────────────────────────────────────────────────
- id: tasks
name: cordis:group
group: true
isolate:
tasks: true
config:
- id: tasks-local
name: '@deepseek-ai/dsh-tasks-local'
- id: tool-tasks
name: '@deepseek-ai/dsh-tool-tasks'
# Only the model-facing controls. The task REGISTRY stays on the host plane:
# its producers sit outside any realm this file could put it in — `tool-bash`
# above resolves it with `ctx.get`, and an entry-local realm here is invisible
# to every sibling row, so `run_in_background` would answer "background tasks
# unavailable" while these controls sat in the catalog. The registry is keyed by
# owning agent anyway, so one host instance serves every session. What a preset
# chooses is whether its agent can collect and stop background work at all.
- id: tool-tasks
name: '@deepseek-ai/dsh-tool-tasks'
# ── goals ───────────────────────────────────────────────────────────────────

View File

@@ -22,8 +22,16 @@
# never reached the model's shell at all. `tool-bash` consumes the host registry
# from here; the executor behind it (`bash-sandbox`) is host-plane too, where the
# sandbox policy owns it.
#
# `run_in_background` is off because this preset mounts no `tool-tasks`. The
# host registry already refuses a start for an owner no attached control
# surface serves, so this is not the safety boundary — it is the model-facing
# one: an agent that could never collect a task should not be offered the
# parameter at all, and disabling it drops the parameter from the schema.
- id: tool-bash
name: '@deepseek-ai/dsh-tool-bash'
config:
enableRunInBackground: false
- id: tool-str-replace-editor
name: '@deepseek-ai/dsh-tool-str-replace-editor'

View File

@@ -63,17 +63,15 @@
# ── background tasks ────────────────────────────────────────────────────────
- id: tasks
name: cordis:group
group: true
isolate:
tasks: true
config:
- id: tasks-local
name: '@deepseek-ai/dsh-tasks-local'
- id: tool-tasks
name: '@deepseek-ai/dsh-tool-tasks'
# Only the model-facing controls. The task REGISTRY stays on the host plane:
# its producers sit outside any realm this file could put it in — `tool-bash`
# above resolves it with `ctx.get`, and an entry-local realm here is invisible
# to every sibling row, so `run_in_background` would answer "background tasks
# unavailable" while these controls sat in the catalog. The registry is keyed by
# owning agent anyway, so one host instance serves every session. What a preset
# chooses is whether its agent can collect and stop background work at all.
- id: tool-tasks
name: '@deepseek-ai/dsh-tool-tasks'
# ── skills ──────────────────────────────────────────────────────────────────

View File

@@ -5,6 +5,7 @@
// surface itself.
import { tmpdir } from 'node:os'
import { afterEach, expect, it } from 'vitest'
import { CallId } from '@deepseek-ai/dsh-llm'
import { canonicalPath, writableRoots } from '@deepseek-ai/dsh-sandbox'
import { SessionId } from '@deepseek-ai/dsh-session'
// Empty type imports carry the tools/sandboxPolicy/approval Context merges.
@@ -114,3 +115,64 @@ it('assembles the shipped Web catalog with the confined access default', async (
await commandHandle.dispose()
}
}, 120_000)
it('lets a preset producer reach the background-task registry', async () => {
scaffold = await launchWebScaffold()
const ctx = scaffold.ctx
const handle = await ctx.agents.create({
sessionId: SessionId('shipped-background-task'),
meta: { cwd: scaffold.workspaceCwd },
setup: agentCtx => ctx.agentPresets.mount(agentCtx).then(() => undefined),
})
try {
const signal = new AbortController().signal
// `tool-bash` is a preset row and `tasks` is a host registry; the producer
// resolves it with `ctx.get`, so a registry hidden behind a preset realm
// fails here — with every task control still listed in the catalog above.
const started = await ctx.tools.execute({
signal,
callId: CallId('shipped-bash-background'),
name: 'bash',
arguments: {
command: 'printf SHIPPED_BACKGROUND_OK',
description: 'shipped background probe',
run_in_background: true,
},
agent: handle.agent,
})
expect({ isError: started.isError, content: started.content }).toEqual({
isError: false,
content: [{ type: 'text', text: 'started background task bash-1' }],
})
// The control surface reads what the producer started: same registry, one
// owner. A per-preset registry would list nothing here even on success.
const listed = await ctx.tools.execute({
signal,
callId: CallId('shipped-task-list'),
name: 'task_list',
arguments: {},
agent: handle.agent,
})
expect(listed.isError).toBe(false)
expect(listed.content).toEqual([
{ type: 'text', text: expect.stringContaining('bash-1 [bash]') as unknown as string },
])
// The full round trip: the output a host-plane producer wrote is collected
// through a preset-plane control, which is the linkage the realm severed.
const collected = await ctx.tools.execute({
signal,
callId: CallId('shipped-task-output'),
name: 'task_output',
arguments: { task_id: 'bash-1', wait: true },
agent: handle.agent,
})
expect(collected.isError).toBe(false)
expect(collected.content).toEqual([
{ type: 'text', text: expect.stringContaining('SHIPPED_BACKGROUND_OK') as unknown as string },
])
} finally {
await handle.dispose()
}
}, 120_000)