mirror of
https://github.com/deepseek-ai/deepseek-harness
synced 2026-08-15 21:04:50 +00:00
Merge remote-tracking branch 'origin/master' into worktree/web-theme-settings-integration-fde706
This commit is contained in:
@@ -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 ──────────────────────────────────────────────────────────────────
|
||||
|
||||
@@ -124,7 +122,7 @@
|
||||
|
||||
Explore first. Use non-mutating reads, searches, static analysis, and checks to ground the plan in the actual repository. Do not edit or write files, change configuration, run formatters or code generation that rewrites tracked files, commit, or otherwise carry out the plan. Prefer existing functions and patterns over new machinery.
|
||||
|
||||
The tool catalog stays the same across modes for request-cache stability. These plan-mode rules override any later tool description or guidance that suggests using mutation tools; those tools remain listed only to keep the request shape stable. Do not use todo_write to track this planning phase: it tracks implementation after an approved plan, while the plan itself belongs in exit_plan_mode.
|
||||
The tool catalog stays the same across modes for request-cache stability. These plan-mode rules override any later tool description or guidance that suggests using mutation tools; those tools remain listed to keep the tool catalog unchanged. Do not use todo_write to track this planning phase: it tracks implementation after an approved plan, while the plan itself belongs in exit_plan_mode.
|
||||
|
||||
Resolve discoverable facts by inspection. Use ask_user_question only for user-owned choices or material ambiguity that inspection cannot answer. Do not ask the user where code lives or how current behavior works when you can find out.
|
||||
|
||||
|
||||
@@ -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 ───────────────────────────────────────────────────────────────────
|
||||
|
||||
@@ -105,7 +103,7 @@
|
||||
|
||||
Explore first. Use non-mutating reads, searches, static analysis, and checks to ground the plan in the actual repository. Do not edit or write files, change configuration, run formatters or code generation that rewrites tracked files, commit, or otherwise carry out the plan. Prefer existing functions and patterns over new machinery.
|
||||
|
||||
The tool catalog stays the same across modes for request-cache stability. These plan-mode rules override any later tool description or guidance that suggests using mutation tools; those tools remain listed only to keep the request shape stable. Do not use todo_write to track this planning phase: it tracks implementation after an approved plan, while the plan itself belongs in exit_plan_mode.
|
||||
The tool catalog stays the same across modes for request-cache stability. These plan-mode rules override any later tool description or guidance that suggests using mutation tools; those tools remain listed to keep the tool catalog unchanged. Do not use todo_write to track this planning phase: it tracks implementation after an approved plan, while the plan itself belongs in exit_plan_mode.
|
||||
|
||||
Resolve discoverable facts by inspection. Use ask_user_question only for user-owned choices or material ambiguity that inspection cannot answer. Do not ask the user where code lives or how current behavior works when you can find out.
|
||||
|
||||
|
||||
@@ -53,13 +53,13 @@ When a preset genuinely owns a service, wrap the provider **and every consumer t
|
||||
|
||||
A consumer left outside the group resolves the host's registry, which the preset did not populate, and then contributes nothing. That is the quietest failure here: the mount succeeds and a tool is simply missing.
|
||||
|
||||
Registry-shaped host capabilities need no realm at all: the host `tools` and `skills` registries are layered per scope, so rows like `skill-local` and `tool-skill` sit loose in the preset and their registrations file into this preset's layer automatically — the agent's catalog merges them with whatever the deployment registered globally.
|
||||
Host capabilities exposed through registries need no realm: the host `tools` and `skills` registries are layered per scope, so rows like `skill-local` and `tool-skill` sit loose in the preset and their registrations file into this preset's layer automatically — the agent's catalog merges them with whatever the deployment registered globally.
|
||||
|
||||
## Verifying a change
|
||||
|
||||
Read the live runtime with `cordis_inspect` — it reports the services, the plugin fibers, and the registered tools as they actually are, which is the only reliable check that a row did what its name suggests. Note it shows THIS session's composition: a preset you just wrote is not mounted anywhere until a session starts on it.
|
||||
|
||||
To check a preset you authored, re-read the files you wrote and walk the shape: a top-level YAML list, every row a map with a `name`, every group carrying its own list, service-publishing rows behind an `isolate` realm. The settings page's preset roster runs the same shape check and marks an unloadable preset broken in red — point the user there, and ask them to start a session on the new preset to confirm the tool list; you cannot start one yourself.
|
||||
To check a preset you authored, re-read the files and validate these fields: the top level is a YAML list, every row is a map with a `name`, every group carries its own list, and service-publishing rows sit behind an `isolate` realm. The settings page's preset roster validates the same fields and marks an unloadable preset broken in red — point the user there, and ask them to start a session on the new preset to confirm the tool list; you cannot start one yourself.
|
||||
|
||||
`cordis_mount` evaluates JavaScript against the live runtime and disappears on restart. It is for probing, not for shipping a capability: a capability belongs in a composition 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'
|
||||
|
||||
@@ -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 ──────────────────────────────────────────────────────────────────
|
||||
|
||||
@@ -117,7 +115,7 @@
|
||||
|
||||
Explore first. Use non-mutating reads, searches, static analysis, and checks to ground the plan in the actual repository. Do not edit or write files, change configuration, run formatters or code generation that rewrites tracked files, commit, or otherwise carry out the plan. Prefer existing functions and patterns over new machinery.
|
||||
|
||||
The tool catalog stays the same across modes for request-cache stability. These plan-mode rules override any later tool description or guidance that suggests using mutation tools; those tools remain listed only to keep the request shape stable. Do not use todo_write to track this planning phase: it tracks implementation after an approved plan, while the plan itself belongs in exit_plan_mode.
|
||||
The tool catalog stays the same across modes for request-cache stability. These plan-mode rules override any later tool description or guidance that suggests using mutation tools; those tools remain listed to keep the tool catalog unchanged. Do not use todo_write to track this planning phase: it tracks implementation after an approved plan, while the plan itself belongs in exit_plan_mode.
|
||||
|
||||
Resolve discoverable facts by inspection. Use ask_user_question only for user-owned choices or material ambiguity that inspection cannot answer. Do not ask the user where code lives or how current behavior works when you can find out.
|
||||
|
||||
|
||||
@@ -2,5 +2,5 @@
|
||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write apps/cli/reference/README.md
|
||||
README.md: 40807da0b1a88708e2e5e9efa3569bd0d6f6a020
|
||||
README.md: 0b5faf8993cd8065fffcfec5f240b0084508db91
|
||||
README.zh.md: b9c48c16dd4be186266d30a438329463c31aca70
|
||||
|
||||
@@ -61,7 +61,7 @@ New sessions default to the `workspace-write` permission preset. Bash and filesy
|
||||
|
||||
`DSH_TOOLS_MODE` selects `native`, `code`, or `both` for the process; another value fails at boot. [`config/core-web.cordis.yml`](../config/core-web.cordis.yml) is an optional RL-compatible `--patch` overlay that pins native mode, renders only `DSH_SYSTEM_PROMPT` or `You are a helpful software engineer assistant.` as the system prompt, disables Workspace instructions and every Web runtime prompt contribution, and exposes only persistent `bash` and `str_replace_editor` while retaining the shipped host, browser, workspace, persistence, and permission composition.
|
||||
|
||||
`DSH_SYSTEM_PROMPT` is passed as the system-prompt [`persona`](../../../packages/core/system-prompt/README.md#config): complete `{{…}}` groups use that contract's strict variable interpolation and have no literal-brace escape; any set value, including an empty string, is authoritative and an empty value therefore removes the system prompt, while only an unset variable selects the fallback.
|
||||
`DSH_SYSTEM_PROMPT` is passed as the system-prompt [`persona`](../../../packages/core/system-prompt/README.md#config): complete `{{…}}` groups use that contract's strict variable interpolation rules and have no literal-brace escape; any set value, including an empty string, is authoritative and an empty value therefore removes the system prompt, while only an unset variable selects the fallback.
|
||||
|
||||
## Shared deployment behavior
|
||||
|
||||
|
||||
@@ -138,7 +138,7 @@ Examples:
|
||||
resolved = { mode: 'profile', profile, patches }
|
||||
})
|
||||
|
||||
/** Reject parent options that crossed a subcommand boundary. */
|
||||
/** Reject parent options supplied before a subcommand. */
|
||||
const rejectParentOptions = (command: string): void => {
|
||||
const parent = program.opts<{
|
||||
profile?: string
|
||||
|
||||
@@ -14,8 +14,8 @@ export interface ProcessShutdown {
|
||||
/**
|
||||
* Create one process-exit controller around an application disposer.
|
||||
* @param dispose - Whole-application teardown that resolves at quiescence.
|
||||
* @param forceExit - Forced process exit boundary, replaceable by tests.
|
||||
* @param complete - Natural process completion boundary, replaceable by tests.
|
||||
* @param forceExit - Function that exits the process immediately, replaceable by tests.
|
||||
* @param complete - Function that records the natural completion code, replaceable by tests.
|
||||
* @param timeoutMs - Grace before forced exit, replaceable by tests.
|
||||
* @returns A controller whose normal calls coalesce and whose repeated signal call escalates.
|
||||
*/
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* The third-party memory examples stay config-only. This suite parses every
|
||||
* checked-in overlay, verifies its pin/transport/secret boundary, then replaces
|
||||
* checked-in overlay, verifies its package pin, transport, and secret handling, then replaces
|
||||
* only the upstream endpoint with the package-owned keyless MCP fixture and
|
||||
* proves the real Cordis Loader discovers a tool through the generic bridge.
|
||||
*/
|
||||
@@ -81,7 +81,7 @@ async function waitForTool(ctx: Context, name: string): Promise<void> {
|
||||
}
|
||||
|
||||
describe('third-party memory MCP example overlays', () => {
|
||||
it.each(examples)('parses $file with the documented generic boundary', (contract) => {
|
||||
it.each(examples)('parses $file with the documented generic plugin fields', (contract) => {
|
||||
const file = resolve(exampleDir, contract.file)
|
||||
const source = readFileSync(file, 'utf8')
|
||||
const row = insertedRow(loadOverlayPatches('memory-mcp-config-test', file))
|
||||
|
||||
@@ -5,7 +5,7 @@ import { describe, expect, it } from 'vitest'
|
||||
/**
|
||||
* Keyless smoke for the SOURCE `dsh` launcher: run `apps/cli/src/bin.ts`
|
||||
* with the exact production launch vector (`node --import tsx/esm`, the same
|
||||
* shape as `bin/dsh` and the root `dsh`/`demo:web` scripts) and assert the
|
||||
* executable and arguments as `bin/dsh` and the root `dsh`/`demo:web` scripts) and assert the
|
||||
* required-config diagnostic. The Node compatibility matrix runs this
|
||||
* WHOLE file, so a Node release changing module hooks or TypeScript handling
|
||||
* breaks this gate instead of every developer's `pnpm dsh`; the built-bin
|
||||
|
||||
@@ -35,9 +35,9 @@ const UI_EXPECTED = join(SNAPSHOT_DIR, 'ui.expected.md')
|
||||
const MODE = webSnapshotMode()
|
||||
|
||||
// Irreducible payload: the command has to be long enough to pass the card's
|
||||
// height cap, which is the only shape that reproduces an action row pushed off
|
||||
// screen. Unrelated tokens, not a repeated word — the model compresses a
|
||||
// repeated word into `printf 'alpha %.0s' {1..400}` when recording, and a
|
||||
// height cap, which is the only command length that reproduces an action row pushed off
|
||||
// screen. Unrelated tokens, not a repeated word — a repeated word is what the
|
||||
// model compressed into `printf 'alpha %.0s' {1..400}` while recording, and a
|
||||
// short command proves nothing here. The formula keeps the source small; the
|
||||
// model receives the expanded literal it has to put in the command.
|
||||
const TOKENS = Array.from({ length: 220 }, (_, index) => `tok${((index + 1) * 7919 % 99991).toString(36)}`).join(' ')
|
||||
|
||||
@@ -186,7 +186,7 @@ describe('web e2e: long Chat interaction contract', () => {
|
||||
const boundary = source.session.events.find((event): event is SessionEvent<'turn/end'> => (
|
||||
event.type === 'turn/end' && event.data.turn === BRANCH_TURN
|
||||
))
|
||||
if (boundary === undefined) throw new Error(`turn ${String(BRANCH_TURN)} has no completed boundary`)
|
||||
if (boundary === undefined) throw new Error(`turn ${String(BRANCH_TURN)} has no turn/end event`)
|
||||
const expectedUserText = textContent(branchUserEvent.data.content)
|
||||
|
||||
await wheelUntilMounted(page, `[data-chat-call-id="${TARGET_CALL_2}"]`, -1_100)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Opt-in browser benchmark for high-cardinality workspace and history
|
||||
// rendering. It reports measurements without timing assertions because host
|
||||
// speed is not a correctness contract; structural assertions keep the load
|
||||
// shape from silently shrinking.
|
||||
// speed is not a correctness contract; structural assertions keep the number
|
||||
// of workspaces and history entries from silently shrinking.
|
||||
import { mkdtemp, rm, writeFile } from 'node:fs/promises'
|
||||
import { tmpdir } from 'node:os'
|
||||
import { join } from 'node:path'
|
||||
|
||||
@@ -64,12 +64,12 @@ const DRAFT = Array.from({ length: DRAFT_LINES }, (_unused, index) => {
|
||||
}).join('\n')
|
||||
|
||||
/**
|
||||
* A draft ending in a newline: the shape where the two layers reserve their
|
||||
* A draft ending in a newline, where the two layers reserve their
|
||||
* final line box on different terms. A textarea keeps one for the caret after a
|
||||
* final newline; `white-space: pre-wrap` collapses a text node's trailing
|
||||
* newline and generates none. The hidden auto-grow mirror carries the newline
|
||||
* and so decides the height for both, which is why the backdrop needs no
|
||||
* padding of its own — but only a draft of this shape can show it.
|
||||
* padding of its own — but only a draft with a trailing newline can show it.
|
||||
*/
|
||||
const DRAFT_TRAILING_NEWLINE = `${DRAFT}\n`
|
||||
|
||||
@@ -381,7 +381,7 @@ describe('web e2e: composer draft scrolling', () => {
|
||||
const data = new DataTransfer()
|
||||
data.setData('text/plain', text)
|
||||
el.dispatchEvent(new ClipboardEvent('paste', { clipboardData: data, bubbles: true, cancelable: true }))
|
||||
// Ending in a newline is the shape the engines disagree on: the caret
|
||||
// The engines disagree when the draft ends in a newline: the caret
|
||||
// lands on a line with nothing on it, where chromium reports no client
|
||||
// rects at all for the collapsed position.
|
||||
}, `\n${DRAFT}\n`)
|
||||
@@ -401,7 +401,7 @@ describe('web e2e: composer draft scrolling', () => {
|
||||
|
||||
it('a draft ending in a newline scrolls to its true end, not a line above it', async () => {
|
||||
onTestFailed(() => saveFailureShot(page, 'web-e2e-composer-draft-scroll-trailing-newline'))
|
||||
// The layers reserve a final line box on different terms, so this shape is
|
||||
// The layers reserve a final line box on different terms, so the trailing-newline case is
|
||||
// the one that separates a height every layer agrees on from a box measured
|
||||
// one line short of the caret's own last position.
|
||||
const input = page.locator('textarea:enabled').first()
|
||||
@@ -453,7 +453,7 @@ describe('web e2e: composer draft scrolling', () => {
|
||||
const data = new DataTransfer()
|
||||
data.setData('text/plain', text)
|
||||
el.dispatchEvent(new ClipboardEvent('paste', { clipboardData: data, bubbles: true, cancelable: true }))
|
||||
// The ordinary shape — not ending in a newline — so the collapsed branch
|
||||
// The ordinary case, without a trailing newline, so the collapsed branch
|
||||
// of the reveal keeps a real engine under it; the case above owns the
|
||||
// after-newline branch.
|
||||
}, `\n${DRAFT}`)
|
||||
|
||||
@@ -42,7 +42,7 @@ function appFrame(page: Page) {
|
||||
return page.locator('[style*="grid-template-columns"]').first()
|
||||
}
|
||||
|
||||
/** Render the two boundary affordances without platform-dependent coordinates. */
|
||||
/** Render the two column-resize handles without platform-dependent coordinates. */
|
||||
async function handleSnapshot(page: Page): Promise<string> {
|
||||
const handles = await page.locator('[class*="handle"]').evaluateAll(elements =>
|
||||
elements.map(element => ({
|
||||
|
||||
@@ -164,8 +164,8 @@ describe('web e2e: navigation & panes over a rich seeded session', () => {
|
||||
sessionId = await settled
|
||||
}
|
||||
await recordFixture(scaffold, sessionId!, SEED)
|
||||
// Fixture honesty: the recording must carry the shape the replay
|
||||
// scenarios assert on — three calls in turn 1 and two closed turns.
|
||||
// Fixture honesty: the recording must contain the events the replay
|
||||
// scenarios assert on: three calls in turn 1 and two closed turns.
|
||||
const recorded = parseSessionLog(await readFile(SEED, 'utf8'))
|
||||
expect(recorded.filter(e => e.type === 'turn/end')).toHaveLength(2)
|
||||
const calls = recorded.filter((e): e is SessionEvent & { data: { name: string } } => e.type === 'tool/call')
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Keyless browser regression for pwsh UI parity with bash: a seeded session
|
||||
// whose pwsh call/result is presented by the REAL tool-pwsh on replay (the
|
||||
// api-proxy recomputes presentation views from logged args/result content)
|
||||
// must render as a bash-shaped terminal card with the parsed exit-status
|
||||
// must render with the same terminal card layout as bash and show the parsed exit-status
|
||||
// pill — not a generic console-fenced card. The seed is authored, not
|
||||
// recorded: its header line carries no `cwd`
|
||||
// field (seedSession writes the session cwd itself, and a Windows temp path
|
||||
@@ -43,7 +43,7 @@ const HAS_PWSH = MODE === 'record' ? false : spawnSync(
|
||||
{ encoding: 'utf8' },
|
||||
).status === 0
|
||||
|
||||
describe.skipIf(MODE === 'record' || !HAS_PWSH)('web e2e: pwsh calls render as bash-shaped terminal cards', () => {
|
||||
describe.skipIf(MODE === 'record' || !HAS_PWSH)('web e2e: pwsh calls use the bash terminal-card layout', () => {
|
||||
let scaffold: WebScaffold
|
||||
let browser: Browser
|
||||
let page: Page
|
||||
@@ -75,7 +75,7 @@ describe.skipIf(MODE === 'record' || !HAS_PWSH)('web e2e: pwsh calls render as b
|
||||
await expect.poll(() => result.count(), { timeout: 15_000 }).toBe(1)
|
||||
await result.click()
|
||||
await page.getByRole('tab', { name: 'Chat', exact: true }).waitFor({ timeout: 15_000 })
|
||||
// The tool row is expand-gated: the settled bash-shaped row carries the
|
||||
// The tool row is expand-gated: the settled row uses the bash layout and carries the
|
||||
// shell-family variant, and the terminal card lives in the expanded body.
|
||||
const row = page.locator('[data-tool="pwsh"]').first()
|
||||
await row.waitFor({ timeout: 15_000 })
|
||||
|
||||
@@ -31,7 +31,7 @@ const ANSWERED_EXPECTED = join(SNAPSHOT_DIR, 'answered.expected.md')
|
||||
const MODE = webSnapshotMode()
|
||||
|
||||
// The options carry long descriptions on purpose: the squeeze assertion below
|
||||
// needs option copy that WRAPS, which is the only shape that reproduces a
|
||||
// needs option copy that WRAPS, which is the only text layout that reproduces a
|
||||
// collapsed row painting its copy outside its own box.
|
||||
const PROMPT = 'Use the ask_user_question tool to ask me exactly one multi-select question with id "color", question "Which color do you prefer?", header "Pick one", and two options: label "Blue" with description "A cool recessive hue that reads as calm and trustworthy in long reading sessions and dense dashboards.", and label "Green" with description "A restful mid-spectrum hue with the highest perceived brightness, easiest on the eye over long sessions." Set multi_select to true. After I answer, reply with the single word DONE and stop.'
|
||||
|
||||
@@ -116,7 +116,7 @@ describe('web e2e: resident question composer round trip', () => {
|
||||
return {
|
||||
rows: rows.length,
|
||||
spill: Math.max(...spill),
|
||||
// Wrapped copy is the shape that overflows a collapsed row, and a
|
||||
// Wrapped option text is what overflows a collapsed row, and a
|
||||
// scrolling list proves the seat is genuinely capped. Without both,
|
||||
// the spill assertion would hold vacuously.
|
||||
wrappedRows: rows.filter(row => row.getBoundingClientRect().height > 42).length,
|
||||
|
||||
@@ -448,7 +448,7 @@ export async function launchWebScaffold(options: LaunchOptions = {}): Promise<We
|
||||
let replayHandle: ReplayHandle | undefined
|
||||
try {
|
||||
process.chdir(workspaceCwd)
|
||||
// The production resolution shape: an empty profile root inside the temp
|
||||
// The production module-resolution setup: an empty profile root inside the temp
|
||||
// harness home, with bare plugin names resolving through the flat module
|
||||
// fallback the launcher heals under <home>/profiles.
|
||||
healProfilesModuleFallback(INSTALL_ANCHOR, harnessHome)
|
||||
@@ -626,7 +626,7 @@ export function fixtureUserPrompts(fixtureText: string): string[] {
|
||||
* through the REAL backend API (throwaway Context + SessionStore + JSONL
|
||||
* plugin — the semantic-checkpoint precedent), never raw file writes: no
|
||||
* knowledge of bucket hashing, filename encoding, or compression, and
|
||||
* malformed shapes fail loud at seed time. The fixture's tokenized identity
|
||||
* malformed session events fail loud at seed time. The fixture's tokenized identity
|
||||
* ({{sessionId}}/{{cwd}}) is realized for this world before parsing.
|
||||
* @param scaffold - the target scaffold.
|
||||
* @param fixtureText - raw recorded session.jsonl contents.
|
||||
@@ -730,7 +730,7 @@ function normalizeAria(snapshot: string, workspaceCwd: string): string {
|
||||
// between local worktrees and CI scratch directories.
|
||||
.replace(/(Compacted \d+ history items \(~)\d+( tokens\))/g, '$1{{tokens}}$2')
|
||||
// Message IconActions clocks widen by calendar day/year; collapse every
|
||||
// shape so goldens stay stable across midnight and year boundaries.
|
||||
// format so goldens stay stable across midnight and year changes.
|
||||
.replace(/\d{4}年\d{1,2}月\d{1,2}日 \d{2}:\d{2}/g, '{{clock}}')
|
||||
.replace(/\d{1,2}月\d{1,2}日 \d{2}:\d{2}/g, '{{clock}}')
|
||||
.replace(/(?<!\d)\d{1,2}:\d{2}:\d{2}(?:\.\d+)?(?:\s*[AP]M)?(?!\d)/gi, '{{clock}}')
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
// FixtureApiClient transport (no API key, no model round), opens the fixture
|
||||
// session, and pins the search card the `grep` turn (fixture turn 67) renders in
|
||||
// the assembled application. The built-boot smoke proves the graph boots but
|
||||
// carries no behavior assertions by contract; this is the assembled-output check
|
||||
// intentionally carries no behavior assertions; this is the assembled-output check
|
||||
// that a broken SearchRow registration or a dropped card would fail — the
|
||||
// per-package suites bench over src and cannot see the bundled wiring.
|
||||
//
|
||||
@@ -13,7 +13,7 @@
|
||||
// fixture, not harvested from a live model. The recovery-footer arm is a pure
|
||||
// derivation over the result view, pinned at every render site by the
|
||||
// ui-conversation suite; here the fixture turn exercises the assembled card
|
||||
// shape and its cap.
|
||||
// fields and its cap.
|
||||
import { mkdirSync, writeFileSync } from 'node:fs'
|
||||
import { dirname, join } from 'node:path'
|
||||
import { act, fireEvent, screen, waitFor, within } from '@testing-library/react'
|
||||
@@ -24,7 +24,7 @@ const EXPECTED = join(process.cwd(), 'apps/web/tests/snapshots/search-card/grep-
|
||||
|
||||
installAssembledBootEnv()
|
||||
|
||||
/** Normalize a rendered search card to a stable text shape: the kind, the banner
|
||||
/** Normalize a rendered search card to stable text fields: the kind, the banner
|
||||
* summary, each file header (path + count), each visible match line, the expand
|
||||
* control label, and the recovery footer. */
|
||||
function cardShape(root: Element): string {
|
||||
@@ -63,7 +63,7 @@ describe('assembled search card', () => {
|
||||
}, { timeout: 10_000 })
|
||||
|
||||
// `data-tool` sits on the ToolRow root; the collapsed row is the expand
|
||||
// toggle. Click it so the card and its recovery footer mount, then shape the
|
||||
// toggle. Click it so the card and its recovery footer mount, then serialize the
|
||||
// whole row (the card lives inside ToolRow's body wrapper).
|
||||
const grepRow = document.querySelector('[data-tool="grep"]')!
|
||||
act(() => { fireEvent.click(grepRow.querySelector('[data-expandable]') ?? grepRow) })
|
||||
|
||||
@@ -102,7 +102,7 @@ function withCompaction(raw: string, meter: TokenMeterService): string {
|
||||
})
|
||||
// Load-bearing exactness: the projections subtract this count verbatim, so
|
||||
// it must equal what the host's fold prices for these nodes. The estimator
|
||||
// prices message CONTENT only, so a minimal wrapper per storage shape is
|
||||
// prices message CONTENT only, so a minimal wrapper for each stored event format is
|
||||
// exact — pre-identity rows carry bare `content` (the persistence read path
|
||||
// upgrades them), a current row carries the full `message` envelope.
|
||||
const priceRow = (row: (typeof events)[number]): number => {
|
||||
@@ -169,7 +169,7 @@ function withCompaction(raw: string, meter: TokenMeterService): string {
|
||||
},
|
||||
})
|
||||
// The persistence seed helper requires a terminal turn/end. Keep the manual
|
||||
// command standalone, then add a closed zero-step fixture boundary after it.
|
||||
// command standalone, then add a closed zero-step turn after it.
|
||||
const closureTurn = lastTurn + 1
|
||||
at({ type: 'turn/start', data: { turn: closureTurn } })
|
||||
at({ type: 'turn/end', data: { turn: closureTurn, reason: { kind: 'completed' } } })
|
||||
|
||||
@@ -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.
|
||||
@@ -90,7 +91,7 @@ it('assembles the shipped Web catalog with the confined access default', async (
|
||||
// `workspace-write` is not "the workspace and nothing else": the shared roots
|
||||
// helper always admits the temp directories too. Pinning it against an
|
||||
// explicit mode keeps the claim independent of this surface's default, and
|
||||
// keeps a future boundary test from being run inside /tmp — where an
|
||||
// keeps a future sandbox-confinement test from being run inside /tmp — where an
|
||||
// "escape" write succeeds by design and reads as a sandbox failure.
|
||||
expect(writableRoots(scaffold.ctx.sandboxPolicy.resolve({ mode: 'workspace-write' }))).toEqual(
|
||||
expect.arrayContaining([canonicalPath('/tmp'), canonicalPath(tmpdir())]),
|
||||
@@ -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)
|
||||
|
||||
@@ -17,11 +17,10 @@
|
||||
// replacing those nodes.
|
||||
//
|
||||
// The round-trip against a loopback host is far too fast to observe, so this
|
||||
// scenario HOLDS the `session.history` response open at the browser's network
|
||||
// boundary and asserts the visible frame while it is in flight. That gate is
|
||||
// what makes the assertions non-vacuous: without the phase exemption, the
|
||||
// held window is exactly when `settling` would be painted and the composer
|
||||
// hidden.
|
||||
// scenario HOLDS the `session.history` response open in the browser's network
|
||||
// handler and asserts the visible frame while it is in flight. That wait is
|
||||
// what makes the assertions non-vacuous: without the phase exemption, the held
|
||||
// window is exactly when `settling` would be painted and the composer hidden.
|
||||
//
|
||||
// Zero model calls: registering a workspace and opening its blank session are
|
||||
// host RPCs with no model involvement. A stray stream would fail loud with
|
||||
|
||||
@@ -20,7 +20,7 @@ const EXPECTED = join(process.cwd(), 'apps/web/tests/snapshots/todo-row/parallel
|
||||
|
||||
installAssembledBootEnv()
|
||||
|
||||
/** Normalize the todo row and the plan strip to a stable text shape: the row's
|
||||
/** Normalize the todo row and the plan strip to stable text fields: the row's
|
||||
* title, its truncatable summary, its non-shrinking suffix, then the panel's
|
||||
* per-status header and every list item with its status. */
|
||||
function todoShape(row: Element, panel: Element): string {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Web e2e scenario: assistant IconActions belong to the settled answer, so
|
||||
// they arrive with `turn/end` and not before. The recorded turn narrates in
|
||||
// plain text before its tool call, which is the shape that would hand the
|
||||
// footer to mid-turn narration for the seconds a tool runs and then move it
|
||||
// plain text before its tool call, which is the event order that would show the
|
||||
// footer beside mid-turn narration for the seconds a tool runs and then move it
|
||||
// down. A `hang` sidecar on the SECOND model call parks the turn after the
|
||||
// narration and the tool result are durable, so the running state is stable by
|
||||
// construction rather than by timing; stopping from that park writes the
|
||||
|
||||
@@ -31,8 +31,8 @@ const SEED = fileURLToPath(new URL('./snapshots/seeded-history/seed.jsonl', impo
|
||||
const MODE = webSnapshotMode()
|
||||
const BROWSER_EXPECTED = join(SNAPSHOT_DIR, 'directory-browser.expected.md')
|
||||
const SEED_ID = 'workspace-management-web-e2e'
|
||||
// Both waits exceed ui-primitives' 200ms POINTER_GRACE_MS. Keep them coupled
|
||||
// to that contract if the shared grace tuning changes.
|
||||
// Both waits exceed ui-primitives' 200ms POINTER_GRACE_MS. Keep them above
|
||||
// that value if the shared setting changes.
|
||||
const POINTER_TRANSIT_MS = 300
|
||||
const POINTER_HOLD_MS = 600
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ function rejectStandaloneServe(): Plugin {
|
||||
* editing shell code re-hashes only index and returning clients keep the
|
||||
* cached vendor chunk.
|
||||
*
|
||||
* Boundary invariant: every member must be react-free. A package that
|
||||
* Every member must be React-free. A package that
|
||||
* imports react/jsx-runtime must never be listed — rollup folds a module
|
||||
* shared between the entry and a manual chunk into the manual chunk, so one
|
||||
* react-importing member would drag the single shared react copy into
|
||||
@@ -77,8 +77,8 @@ const BOOT_GRAMMAR_FILES: readonly string[] = [
|
||||
const FONT_EXTENSIONS: readonly string[] = ['.woff2', '.woff', '.ttf']
|
||||
|
||||
/**
|
||||
* npm package name of a resolved module id (the segment after the LAST
|
||||
* `node_modules/` — pnpm nests the real package under an inner node_modules).
|
||||
* npm package name of a resolved module id: the segment after the last
|
||||
* `node_modules/`. pnpm nests the real package under an inner node_modules.
|
||||
*/
|
||||
function npmPackageOf(id: string): string | undefined {
|
||||
const parts = id.split('/node_modules/')
|
||||
|
||||
Reference in New Issue
Block a user