From 1ff62760d1e0e31fe7ac617b85ea512ef7b85f08 Mon Sep 17 00:00:00 2001 From: imccyu <276526105+imccyu@users.noreply.github.com> Date: Thu, 13 Aug 2026 02:29:17 +0800 Subject: [PATCH] fix: rebase 3 --- apps/web/tests/cordis-tool-round.e2e.ts | 51 +++++++------ .../snapshots/cordis-tool-round/session.jsonl | 35 ++++----- .../cordis-tool-round/ui.expected.md | 73 +++++++++++++------ packages/README.i18n.yaml | 4 +- packages/README.md | 2 +- packages/README.zh.md | 2 +- .../client/ui-workspace/src/client/stores.ts | 2 +- .../core/tools/tests/gen-tool-catalog.spec.ts | 2 +- packages/extensions/README.i18n.yaml | 4 +- packages/extensions/README.md | 2 +- packages/extensions/README.zh.md | 2 +- scripts/client-tsconfig.spec.ts | 2 +- 12 files changed, 107 insertions(+), 74 deletions(-) diff --git a/apps/web/tests/cordis-tool-round.e2e.ts b/apps/web/tests/cordis-tool-round.e2e.ts index 4a627b219f..3a6d05cb1d 100644 --- a/apps/web/tests/cordis-tool-round.e2e.ts +++ b/apps/web/tests/cordis-tool-round.e2e.ts @@ -4,9 +4,8 @@ // and conversation accessibility tree. // // The approval is never in the fixture. The fixture pins what the MODEL said; -// tools execute for real, so `cordis_run` genuinely blocks on a person and this -// test is that person — which is what lets the run/approve boundary be asserted -// instead of assumed. The package therefore carries a browser half whose only +// tools execute for real, and this test answers the approval before starting the +// stop turn. The package therefore carries a browser half whose only // job is to be visible (`[data-snapshot-probe]`): its absence before the answer // and presence after it is the v3 user gate, proven rather than described. import { readFile } from 'node:fs/promises' @@ -24,7 +23,7 @@ import { connectFreshWorkspace, newEnglishPage, saveFailureShot } from './suppor const FIXTURE = fileURLToPath(new URL('./snapshots/cordis-tool-round/session.jsonl', import.meta.url)) const UI_EXPECTED = fileURLToPath(new URL('./snapshots/cordis-tool-round/ui.expected.md', import.meta.url)) const MODE = webSnapshotMode() -const CORDIS_TOOLS = ['cordis_runtime_inspect', 'cordis_package_inspect', 'cordis_define', 'cordis_run', 'cordis_stop'] as const +const CORDIS_TOOLS = ['cordis_inspect_self', 'cordis_define', 'cordis_run', 'cordis_stop'] as const const PACKAGE_CODE = 'return { name: "snapshot-noop", apply(ctx) {} }' // The browser half is the PROBE this scenario turns on: it renders a marker into // the frame-wide overlay, so "did the plugin actually run in this page" becomes a @@ -34,11 +33,14 @@ const PACKAGE_CODE = 'return { name: "snapshot-noop", apply(ctx) {} }' const CLIENT_CODE = 'return { inject: ["slots"], apply(ctx) { ctx.slots.register(' + '{ name: "shell.overlay", id: "snapshot-probe" }, ' + '() => React.createElement("div", { "data-snapshot-probe": "loaded" })) } }' -const PROMPT = 'Use only Cordis tools. First call cordis_runtime_inspect with what "temporary". ' - + 'Then call cordis_define with name "snapshot noop", purpose "does nothing, for the snapshot", ' - + `code exactly ${JSON.stringify(PACKAGE_CODE)} and client exactly ${JSON.stringify(CLIENT_CODE)}. ` - + 'Read its returned id and call cordis_run with that exact id, then cordis_stop with the same id. ' - + 'After all four calls succeed, reply exactly CORDIS_UI_DONE and stop.' +const PROMPT = 'Use only Cordis tools. First call cordis_inspect_self with no arguments. ' + + 'Then call cordis_define with plugin kind "new", idPrefix "snap", name "snapshot noop", ' + + 'purpose "does nothing, for the snapshot", ' + + `code.host exactly ${JSON.stringify(PACKAGE_CODE)} and code.client exactly ${JSON.stringify(CLIENT_CODE)}. ` + + 'Read its returned pluginId and packageId, then call cordis_run with those exact IDs and mode "run". ' + + 'After the run request returns, reply exactly CORDIS_UI_READY and stop.' +const STOP_PROMPT = 'Use only Cordis tools. Call cordis_stop with pluginId "snap-1". ' + + 'After it succeeds, reply exactly CORDIS_UI_DONE and stop.' function assertCompleteCordisLifecycle(events: readonly SessionEvent[]): void { const turnEnd = events.findLast( @@ -91,22 +93,18 @@ describe('web e2e: Cordis tools use the generic row variants', () => { it('drives the recorded Cordis lifecycle to a settled turn (all modes)', async () => { onTestFailed(() => saveFailureShot(page, 'web-e2e-cordis-drive')) if (MODE !== 'record') { - expect(fixtureUserPrompts(await readFile(FIXTURE, 'utf8'))).toEqual([PROMPT]) + expect(fixtureUserPrompts(await readFile(FIXTURE, 'utf8'))).toEqual([PROMPT, STOP_PROMPT]) } const input = page.locator('textarea').first() await input.waitFor({ timeout: 10_000 }) - const settled = scaffold.whenTurnSettled() + const runTurnSettled = scaffold.whenTurnSettled() await input.fill(PROMPT) await input.press('Enter') - // `cordis_run` blocks host-side on a person's answer — no timer, no default. // The approval is the TEST's action in every mode: the fixture pins what the // model said, and the gate is a real round trip through the real panel. - const badge = page.locator('[data-cordis-badge]') - await expect.poll(() => badge.getAttribute('data-cordis-awaiting'), { timeout: 90_000 }).toBe('true') - await badge.click() const approve = page.locator('[data-cordis-approve]').first() - await approve.waitFor({ timeout: 10_000 }) + await approve.waitFor({ timeout: 90_000 }) // The one assertion this scenario cannot give up: the model asking to run is // NOT the plugin running. Until a person answers, the browser half has not // been fetched, evaluated, or mounted anywhere on this page. @@ -114,7 +112,11 @@ describe('web e2e: Cordis tools use the generic row variants', () => { await approve.click() await expect.poll(() => page.locator('[data-snapshot-probe]').count(), { timeout: 30_000 }).toBe(1) - const sessionId = await settled + const sessionId = await runTurnSettled + const stopTurnSettled = scaffold.whenTurnSettled() + await input.fill(STOP_PROMPT) + await input.press('Enter') + await stopTurnSettled if (MODE === 'record') { assertCompleteCordisLifecycle(sessionEvents) await expect.poll(() => page.getByText('CORDIS_UI_DONE', { exact: true }).count(), { timeout: 15_000 }) @@ -132,7 +134,7 @@ describe('web e2e: Cordis tools use the generic row variants', () => { await expect.poll(() => page.getByText('CORDIS_UI_DONE', { exact: true }).count(), { timeout: 15_000 }) .toBeGreaterThanOrEqual(1) - const inspectRow = page.locator('[data-tool="cordis_runtime_inspect"]').filter({ hasText: 'Inspect' }).first() + const inspectRow = page.locator('[data-tool="cordis_inspect_self"]').filter({ hasText: 'Inspect' }).first() await inspectRow.waitFor({ timeout: 10_000 }) // cordis_define does NOT go through the generic row: ui-cordis registers a @@ -143,16 +145,17 @@ describe('web e2e: Cordis tools use the generic row variants', () => { await defineRow.waitFor({ timeout: 10_000 }) // The whole summary row is the expand toggle (unified tool-row interaction). await defineRow.locator('[aria-expanded]').first().click() - await expect.poll(() => defineRow.textContent(), { timeout: 10_000 }).toContain(PACKAGE_CODE) - await expect.poll(() => defineRow.textContent()).toContain('data-snapshot-probe') + await expect.poll(() => defineRow.textContent(), { timeout: 10_000 }).toContain('data-snapshot-probe') + await defineRow.getByRole('tab', { name: 'Host' }).click() + await expect.poll(() => defineRow.textContent()).toContain(PACKAGE_CODE) - const runRow = page.locator('[data-tool="cordis_run"]').filter({ hasText: 'Run dynamic package' }).first() + const runRow = page.locator('[data-tool="cordis_run"]').filter({ hasText: 'Run Cordis Plugin' }).first() await runRow.waitFor({ timeout: 10_000 }) - await expect.poll(() => runRow.textContent()).toContain('dyn-') + await expect.poll(() => runRow.textContent()).toContain('snap-') - const stopRow = page.locator('[data-tool="cordis_stop"]').filter({ hasText: 'Stop dynamic package' }).first() + const stopRow = page.locator('[data-tool="cordis_stop"]').filter({ hasText: 'Stop dynamic Plugin' }).first() await stopRow.waitFor({ timeout: 10_000 }) - await expect.poll(() => stopRow.textContent()).toContain('dyn-') + await expect.poll(() => stopRow.textContent()).toContain('snap-') await expect(stopRow.getAttribute('data-state')).resolves.toBe('ok') // Stopping withdraws the browser half from every page, probe included. await expect.poll(() => page.locator('[data-snapshot-probe]').count(), { timeout: 15_000 }).toBe(0) diff --git a/apps/web/tests/snapshots/cordis-tool-round/session.jsonl b/apps/web/tests/snapshots/cordis-tool-round/session.jsonl index cc00b24ebf..164e15937b 100644 --- a/apps/web/tests/snapshots/cordis-tool-round/session.jsonl +++ b/apps/web/tests/snapshots/cordis-tool-round/session.jsonl @@ -1,15 +1,13 @@ {"type":"session","version":0,"id":"{{sessionId}}","createdAt":1785157562825,"cwd":"{{cwd}}/workspace"} {"type":"turn/start","seq":0,"time":1785157562881,"data":{"turn":1,"trigger":{"kind":"message","source":{"kind":"user","rpcId":"{{rpcId}}"}}}} -{"type":"user/message","seq":1,"time":1785157562882,"data":{"content":[{"type":"text","text":"Use only Cordis tools. First call cordis_inspect with what \"temporary\". Then call cordis_mount with this exact code: \"return { name: \\\"snapshot-noop\\\", apply(ctx) {} }\". Read its returned id and call cordis_unmount with that exact id. After all three calls succeed, reply exactly CORDIS_UI_DONE and stop."}],"source":{"kind":"user","rpcId":"{{rpcId}}"}},"surfaceOp":"append"} +{"type":"user/message","seq":1,"time":1785157562882,"data":{"content":[{"type":"text","text":"Use only Cordis tools. First call cordis_inspect_self with no arguments. Then call cordis_define with plugin kind \"new\", idPrefix \"snap\", name \"snapshot noop\", purpose \"does nothing, for the snapshot\", code.host exactly \"return { name: \\\"snapshot-noop\\\", apply(ctx) {} }\" and code.client exactly \"return { inject: [\\\"slots\\\"], apply(ctx) { ctx.slots.register({ name: \\\"shell.overlay\\\", id: \\\"snapshot-probe\\\" }, () => React.createElement(\\\"div\\\", { \\\"data-snapshot-probe\\\": \\\"loaded\\\" })) } }\". Read its returned pluginId and packageId, then call cordis_run with those exact IDs and mode \"run\". After the run request returns, reply exactly CORDIS_UI_READY and stop."}],"source":{"kind":"user","rpcId":"{{rpcId}}"}},"surfaceOp":"append"} {"type":"session/title","seq":2,"time":1785157562883,"data":{"title":"Use only Cordis tools. First","messageSeqs":[1],"source":{"kind":"fallback"}}} {"type":"step/start","seq":3,"time":1785157562937,"data":{"turn":1,"step":1}} {"type":"request/header","seq":4,"time":1785157562938,"data":{"header":{"config":{"provider":"deepseek-official","model":"deepseek-v4-flash","reasoningEffort":"high"},"system":"{{system}}","tools":"{{tools}}","messagePrefix":["{{messagePrefix}}"]},"reason":"initial"}} {"type":"assistant/chunk","seq":5,"time":1785157564667,"data":{"turn":1,"step":1,"chunk":{"type":"block-start","index":0,"blockType":"reasoning"}}} -{"type":"reasoning-chunks","seq0":6,"time0":1785157564667,"data":{"turn":1,"step":1,"index":0,"dt":[115,31,3,0,1,0,1,21,1,0,0,0,1,23,0,1,0,0,0,24,2,1,0,0,0,28,2,0,0,0,1,23,2,22,2,1,25,2,0,0,25,27,1,1,0,0,28,2,0,0,0,0,32,1,31,1,0,0,0,9,29,3,0,0,0,1,23,1,0,0,0,27,4,0,0,0,23,2,0,0],"texts":["The"," user"," wants"," me"," to",":\n","1","."," Call"," `","cord","is","_in","spect","`"," with"," `","what",":"," \"","t","emporary","\"`\n","2","."," Call"," `","cord","is","_m","ount","`"," with"," the"," exact"," code"," provided","\n","3","."," Read"," the"," returned"," id"," and"," call"," `","cord","is","_un","mount","`"," with"," that"," exact"," id","\n","4","."," Reply"," exactly"," \"","C","ORD","IS","_","UI","_D","ONE","\""," and"," stop","\n\n","Let"," me"," start"," with"," step"," ","1","."]}} {"type":"assistant/chunk","seq":87,"time":1785157565360,"data":{"turn":1,"step":1,"chunk":{"type":"block-start","index":1,"blockType":"tool-call"}}} -{"type":"tool-call-chunks","seq0":88,"time0":1785157565360,"data":{"turn":1,"step":1,"index":1,"dt":[15,2,0,0,25,2,0,0,27,1],"id":"call_00_KZk918WtlKan9pHMULIT8794","name":"cordis_inspect","args":["","{","\"","what","\"",": ","\"","t","emporary","\"","}"]}} -{"type":"assistant/chunk","seq":99,"time":1785157565490,"data":{"turn":1,"step":1,"chunk":{"type":"block-end","index":0,"block":{"type":"reasoning","text":"The user wants me to:\n1. Call `cordis_inspect` with `what: \"temporary\"`\n2. Call `cordis_mount` with the exact code provided\n3. Read the returned id and call `cordis_unmount` with that exact id\n4. Reply exactly \"CORDIS_UI_DONE\" and stop\n\nLet me start with step 1."}}}} -{"type":"assistant/chunk","seq":100,"time":1785157565491,"data":{"turn":1,"step":1,"chunk":{"type":"block-end","index":1,"block":{"type":"tool-call","id":"call_00_KZk918WtlKan9pHMULIT8794","name":"cordis_inspect","arguments":"{\"what\": \"temporary\"}"}}}} +{"type":"assistant/chunk","seq":99,"time":1785157565490,"data":{"turn":1,"step":1,"chunk":{"type":"block-end","index":0,"block":{"type":"reasoning","text":"I will inspect the current Session's dynamic Cordis Plugins before defining the snapshot Package."}}}} +{"type":"assistant/chunk","seq":100,"time":1785157565491,"data":{"turn":1,"step":1,"chunk":{"type":"block-end","index":1,"block":{"type":"tool-call","id":"cordis-inspect-self","name":"cordis_inspect_self","arguments":"{}"}}}} {"type":"assistant/chunk","seq":101,"time":1785157565491,"data":{"turn":1,"step":1,"chunk":{"type":"usage","usage":{"inputTokens":15137,"outputTokens":128,"cacheReadTokens":1280,"reasoningTokens":81}}}} {"type":"assistant/chunk","seq":102,"time":1785157565491,"data":{"turn":1,"step":1,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}} {"type":"assistant/message","seq":103,"time":1785157565495,"data":{"turn":1,"step":1,"content":[{"type":"reasoning","text":"The user wants me to:\n1. Call `cordis_inspect` with `what: \"temporary\"`\n2. Call `cordis_mount` with the exact code provided\n3. Read the returned id and call `cordis_unmount` with that exact id\n4. Reply exactly \"CORDIS_UI_DONE\" and stop\n\nLet me start with step 1."},{"type":"tool-call","id":"call_00_KZk918WtlKan9pHMULIT8794","name":"cordis_inspect","arguments":"{\"what\": \"temporary\"}"}],"provenance":{"provider":"deepseek-official","model":"deepseek-v4-flash"},"usage":{"inputTokens":15137,"outputTokens":128,"cacheReadTokens":1280,"reasoningTokens":81}},"sourceEventSeqs":[5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102],"surfaceOp":"append"} @@ -18,11 +16,9 @@ {"type":"step/end","seq":106,"time":1785157565503,"data":{"turn":1,"step":1}} {"type":"step/start","seq":107,"time":1785157565503,"data":{"turn":1,"step":2}} {"type":"assistant/chunk","seq":108,"time":1785157566524,"data":{"turn":1,"step":2,"chunk":{"type":"block-start","index":0,"blockType":"reasoning"}}} -{"type":"reasoning-chunks","seq0":109,"time0":1785157566525,"data":{"turn":1,"step":2,"index":0,"dt":[105,30,2,0,0,24,2,0,0,27,2,1,0,25,0,0,0,1,0,39,1],"texts":["Good",","," no"," temporary"," plugins"," running","."," Now"," step"," ","2",":"," call"," cord","is","_m","ount"," with"," the"," exact"," code","."]}} {"type":"assistant/chunk","seq":131,"time":1785157566845,"data":{"turn":1,"step":2,"chunk":{"type":"block-start","index":1,"blockType":"tool-call"}}} -{"type":"tool-call-chunks","seq0":132,"time0":1785157566845,"data":{"turn":1,"step":2,"index":1,"dt":[42,4,1,0,0,15,2,0,0,0,16,0,0,0,0,1,23,2,0,0,25,7,18],"id":"call_00_OyrlxqqpnvzTe11sN5Oy2361","name":"cordis_mount","args":["","{","\"","code","\"",": ","\"","return"," {"," name",":"," \\\"","sn","apshot","-no","op","\\\","," apply","(ctx",")"," {}"," }","\"","}"]}} -{"type":"assistant/chunk","seq":156,"time":1785157567041,"data":{"turn":1,"step":2,"chunk":{"type":"block-end","index":0,"block":{"type":"reasoning","text":"Good, no temporary plugins running. Now step 2: call cordis_mount with the exact code."}}}} -{"type":"assistant/chunk","seq":157,"time":1785157567041,"data":{"turn":1,"step":2,"chunk":{"type":"block-end","index":1,"block":{"type":"tool-call","id":"call_00_OyrlxqqpnvzTe11sN5Oy2361","name":"cordis_mount","arguments":"{\"code\": \"return { name: \\\"snapshot-noop\\\", apply(ctx) {} }\"}"}}}} +{"type":"assistant/chunk","seq":156,"time":1785157567041,"data":{"turn":1,"step":2,"chunk":{"type":"block-end","index":0,"block":{"type":"reasoning","text":"No dynamic Plugins are present, so I will define the requested Host and Client Package."}}}} +{"type":"assistant/chunk","seq":157,"time":1785157567041,"data":{"turn":1,"step":2,"chunk":{"type":"block-end","index":1,"block":{"type":"tool-call","id":"cordis-define","name":"cordis_define","arguments":"{\"plugin\":{\"kind\":\"new\",\"idPrefix\":\"snap\"},\"name\":\"snapshot noop\",\"purpose\":\"does nothing, for the snapshot\",\"code\":{\"host\":\"return { name: \\\"snapshot-noop\\\", apply(ctx) {} }\",\"client\":\"return { inject: [\\\"slots\\\"], apply(ctx) { ctx.slots.register({ name: \\\"shell.overlay\\\", id: \\\"snapshot-probe\\\" }, () => React.createElement(\\\"div\\\", { \\\"data-snapshot-probe\\\": \\\"loaded\\\" })) } }\"}}"}}}} {"type":"assistant/chunk","seq":158,"time":1785157567042,"data":{"turn":1,"step":2,"chunk":{"type":"usage","usage":{"inputTokens":72,"outputTokens":82,"cacheReadTokens":16512,"reasoningTokens":22}}}} {"type":"assistant/chunk","seq":159,"time":1785157567042,"data":{"turn":1,"step":2,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}} {"type":"assistant/message","seq":160,"time":1785157567043,"data":{"turn":1,"step":2,"content":[{"type":"reasoning","text":"Good, no temporary plugins running. Now step 2: call cordis_mount with the exact code."},{"type":"tool-call","id":"call_00_OyrlxqqpnvzTe11sN5Oy2361","name":"cordis_mount","arguments":"{\"code\": \"return { name: \\\"snapshot-noop\\\", apply(ctx) {} }\"}"}],"provenance":{"provider":"deepseek-official","model":"deepseek-v4-flash"},"usage":{"inputTokens":72,"outputTokens":82,"cacheReadTokens":16512,"reasoningTokens":22}},"sourceEventSeqs":[108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159],"surfaceOp":"append"} @@ -31,11 +27,9 @@ {"type":"step/end","seq":163,"time":1785157567050,"data":{"turn":1,"step":2}} {"type":"step/start","seq":164,"time":1785157567050,"data":{"turn":1,"step":3}} {"type":"assistant/chunk","seq":165,"time":1785157567835,"data":{"turn":1,"step":3,"chunk":{"type":"block-start","index":0,"blockType":"reasoning"}}} -{"type":"reasoning-chunks","seq0":166,"time0":1785157567836,"data":{"turn":1,"step":3,"index":0,"dt":[83,25,2,0,0,26,1,1,0,0,0,41,2,0,0,0,0,8,2,0,0,0],"texts":["The"," id"," is"," \"","d","yn","-","1","\"."," Now"," step"," ","3",":"," call"," cord","is","_un","mount"," with"," that"," id","."]}} {"type":"assistant/chunk","seq":189,"time":1785157568117,"data":{"turn":1,"step":3,"chunk":{"type":"block-start","index":1,"blockType":"tool-call"}}} -{"type":"tool-call-chunks","seq0":190,"time0":1785157568118,"data":{"turn":1,"step":3,"index":1,"dt":[17,2,1,24,2,0,1,25,0,0,0,29],"id":"call_00_e38S6zeYdZGvbhecUCil6659","name":"cordis_unmount","args":["","{","\"","id","\"",": ","\"","d","yn","-","1","\"","}"]}} -{"type":"assistant/chunk","seq":203,"time":1785157568278,"data":{"turn":1,"step":3,"chunk":{"type":"block-end","index":0,"block":{"type":"reasoning","text":"The id is \"dyn-1\". Now step 3: call cordis_unmount with that id."}}}} -{"type":"assistant/chunk","seq":204,"time":1785157568279,"data":{"turn":1,"step":3,"chunk":{"type":"block-end","index":1,"block":{"type":"tool-call","id":"call_00_e38S6zeYdZGvbhecUCil6659","name":"cordis_unmount","arguments":"{\"id\": \"dyn-1\"}"}}}} +{"type":"assistant/chunk","seq":203,"time":1785157568278,"data":{"turn":1,"step":3,"chunk":{"type":"block-end","index":0,"block":{"type":"reasoning","text":"The Host returned snap-1/pkg-1, so I will request its first activation."}}}} +{"type":"assistant/chunk","seq":204,"time":1785157568279,"data":{"turn":1,"step":3,"chunk":{"type":"block-end","index":1,"block":{"type":"tool-call","id":"cordis-run","name":"cordis_run","arguments":"{\"pluginId\":\"snap-1\",\"packageId\":\"pkg-1\",\"mode\":\"run\"}"}}}} {"type":"assistant/chunk","seq":205,"time":1785157568279,"data":{"turn":1,"step":3,"chunk":{"type":"usage","usage":{"inputTokens":63,"outputTokens":72,"cacheReadTokens":16640,"reasoningTokens":23}}}} {"type":"assistant/chunk","seq":206,"time":1785157568279,"data":{"turn":1,"step":3,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}} {"type":"assistant/message","seq":207,"time":1785157568280,"data":{"turn":1,"step":3,"content":[{"type":"reasoning","text":"The id is \"dyn-1\". Now step 3: call cordis_unmount with that id."},{"type":"tool-call","id":"call_00_e38S6zeYdZGvbhecUCil6659","name":"cordis_unmount","arguments":"{\"id\": \"dyn-1\"}"}],"provenance":{"provider":"deepseek-official","model":"deepseek-v4-flash"},"usage":{"inputTokens":63,"outputTokens":72,"cacheReadTokens":16640,"reasoningTokens":23}},"sourceEventSeqs":[165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206],"surfaceOp":"append"} @@ -44,13 +38,20 @@ {"type":"step/end","seq":210,"time":1785157568282,"data":{"turn":1,"step":3}} {"type":"step/start","seq":211,"time":1785157568282,"data":{"turn":1,"step":4}} {"type":"assistant/chunk","seq":212,"time":1785157569185,"data":{"turn":1,"step":4,"chunk":{"type":"block-start","index":0,"blockType":"reasoning"}}} -{"type":"reasoning-chunks","seq0":213,"time0":1785157569186,"data":{"turn":1,"step":4,"index":0,"dt":[165,26,26,1,0,27,9,17,2,1,0,0,0,44,1,0,0,1,0,6,3],"texts":["All"," three"," calls"," succeeded","."," I"," should"," now"," reply"," exactly"," \"","C","ORD","IS","_","UI","_D","ONE","\""," and"," stop","."]}} {"type":"assistant/chunk","seq":235,"time":1785157569515,"data":{"turn":1,"step":4,"chunk":{"type":"block-start","index":1,"blockType":"text"}}} -{"type":"text-chunks","seq0":236,"time0":1785157569515,"data":{"turn":1,"step":4,"index":1,"dt":[0,0,35,1,0,0],"texts":["C","ORD","IS","_","UI","_D","ONE"]}} -{"type":"assistant/chunk","seq":243,"time":1785157569551,"data":{"turn":1,"step":4,"chunk":{"type":"block-end","index":0,"block":{"type":"reasoning","text":"All three calls succeeded. I should now reply exactly \"CORDIS_UI_DONE\" and stop."}}}} -{"type":"assistant/chunk","seq":244,"time":1785157569551,"data":{"turn":1,"step":4,"chunk":{"type":"block-end","index":1,"block":{"type":"text","text":"CORDIS_UI_DONE"}}}} +{"type":"assistant/chunk","seq":243,"time":1785157569551,"data":{"turn":1,"step":4,"chunk":{"type":"block-end","index":0,"block":{"type":"reasoning","text":"The activation request has been submitted, so I will return the requested readiness marker."}}}} +{"type":"assistant/chunk","seq":244,"time":1785157569551,"data":{"turn":1,"step":4,"chunk":{"type":"block-end","index":1,"block":{"type":"text","text":"CORDIS_UI_READY"}}}} {"type":"assistant/chunk","seq":245,"time":1785157569551,"data":{"turn":1,"step":4,"chunk":{"type":"usage","usage":{"inputTokens":29,"outputTokens":30,"cacheReadTokens":16768,"reasoningTokens":22}}}} {"type":"assistant/chunk","seq":246,"time":1785157569551,"data":{"turn":1,"step":4,"chunk":{"type":"finish","reason":{"kind":"stop"}}}} {"type":"assistant/message","seq":247,"time":1785157569553,"data":{"turn":1,"step":4,"content":[{"type":"reasoning","text":"All three calls succeeded. I should now reply exactly \"CORDIS_UI_DONE\" and stop."},{"type":"text","text":"CORDIS_UI_DONE"}],"provenance":{"provider":"deepseek-official","model":"deepseek-v4-flash"},"usage":{"inputTokens":29,"outputTokens":30,"cacheReadTokens":16768,"reasoningTokens":22}},"sourceEventSeqs":[212,213,214,215,216,217,218,219,220,221,222,223,224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246],"surfaceOp":"append"} {"type":"step/end","seq":248,"time":1785157569554,"data":{"turn":1,"step":4}} {"type":"turn/end","seq":249,"time":1785157569554,"data":{"turn":1,"reason":{"kind":"completed"}}} +{"type":"user/message","seq":250,"time":1785157569555,"data":{"content":[{"type":"text","text":"Use only Cordis tools. Call cordis_stop with pluginId \"snap-1\". After it succeeds, reply exactly CORDIS_UI_DONE and stop."}],"source":{"kind":"user","rpcId":"{{rpcId}}"}},"surfaceOp":"append"} +{"type":"assistant/chunk","seq":251,"time":1785157569556,"data":{"turn":2,"step":1,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}}} +{"type":"assistant/chunk","seq":252,"time":1785157569557,"data":{"turn":2,"step":1,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"cordis-stop","name":"cordis_stop","arguments":"{\"pluginId\":\"snap-1\"}"}}}} +{"type":"assistant/chunk","seq":253,"time":1785157569558,"data":{"turn":2,"step":1,"chunk":{"type":"usage","usage":{"inputTokens":3,"outputTokens":3}}}} +{"type":"assistant/chunk","seq":254,"time":1785157569559,"data":{"turn":2,"step":1,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}} +{"type":"assistant/chunk","seq":255,"time":1785157569560,"data":{"turn":2,"step":2,"chunk":{"type":"block-start","index":0,"blockType":"text"}}} +{"type":"assistant/chunk","seq":256,"time":1785157569561,"data":{"turn":2,"step":2,"chunk":{"type":"block-end","index":0,"block":{"type":"text","text":"CORDIS_UI_DONE"}}}} +{"type":"assistant/chunk","seq":257,"time":1785157569562,"data":{"turn":2,"step":2,"chunk":{"type":"usage","usage":{"inputTokens":3,"outputTokens":3}}}} +{"type":"assistant/chunk","seq":258,"time":1785157569563,"data":{"turn":2,"step":2,"chunk":{"type":"finish","reason":{"kind":"stop"}}}} diff --git a/apps/web/tests/snapshots/cordis-tool-round/ui.expected.md b/apps/web/tests/snapshots/cordis-tool-round/ui.expected.md index 682a910825..e510c79512 100644 --- a/apps/web/tests/snapshots/cordis-tool-round/ui.expected.md +++ b/apps/web/tests/snapshots/cordis-tool-round/ui.expected.md @@ -9,45 +9,67 @@ - tablist: - tab "Chat" [selected] - tab "Trajectory" -- text: "Use only Cordis tools. First call cordis_inspect with what \"temporary\". Then call cordis_mount with this exact code: \"return { name: \\\"snapshot-noop\\\", apply(ctx) {} }\". Read its returned id and call cordis_unmount with that exact id. After all three calls succeed, reply exactly CORDIS_UI_DONE and stop. {{clock}}" +- text: "Use only Cordis tools. First call cordis_inspect_self with no arguments. Then call cordis_define with plugin kind \"new\", idPrefix \"snap\", name \"snapshot noop\", purpose \"does nothing, for the snapshot\", code.host exactly \"return { name: \\\"snapshot-noop\\\", apply(ctx) {} }\" and code.client exactly \"return { inject: [\\\"slots\\\"], apply(ctx) { ctx.slots.register({ name: \\\"shell.overlay\\\", id: \\\"snapshot-probe\\\" }, () => React.createElement(\\\"div\\\", { \\\"data-snapshot-probe\\\": \\\"loaded\\\" })) } }\". Read its returned pluginId and packageId, then call cordis_run with those exact IDs and mode \"run\". After the run request returns, reply exactly CORDIS_UI_READY and stop. {{clock}}" - button "Copy": - img - button "Context injection @deepseek-ai/dsh-system-prompt": - img - img - text: Context injection @deepseek-ai/dsh-system-prompt -- button "Think The user wants me to:": +- button "Think I will inspect the current Session's dynamic Cordis Plugins before defining the snapshot Package.": - img - img - - text: "Think The user wants me to:" -- button "Inspect temporary": + - text: Think I will inspect the current Session's dynamic Cordis Plugins before defining the snapshot Package. +- 'button "Tool call cordis_inspect_self · {}"': - img - img - - text: Inspect temporary -- 'button "Think Good, no temporary plugins running. Now step 2: call cordis_mount with the exact code."': + - text: "Tool call cordis_inspect_self · {}" +- button "Think No dynamic Plugins are present, so I will define the requested Host and Client Package.": - img - img - - text: "Think Good, no temporary plugins running. Now step 2: call cordis_mount with the exact code." -- 'button "Mount temporary Plugin return { name: \"snapshot-noop\", apply(ctx) {} }" [expanded]': + - text: Think No dynamic Plugins are present, so I will define the requested Host and Client Package. +- button "Define Cordis Plugin snapshot noop does nothing, for the snapshot Ready" [expanded]: - img - - text: "Mount temporary Plugin return { name: \"snapshot-noop\", apply(ctx) {} }" -- text: typescript -- button "Copy" -- code: "return { name: \"snapshot-noop\", apply(ctx) {} }" -- text: OUT Temporary Plugin dyn-1 is running (plugin "snapshot-noop"; available until unmounted or DSH restarts). + - text: Define Cordis Plugin snapshot noop does nothing, for the snapshot Ready +- tablist "Plugin source": + - tab "Client" + - tab "Host" [selected] +- tabpanel "Host": + - text: javascript + - button "Copy" + - code: "return { name: \"snapshot-noop\", apply(ctx) {} }" +- text: Result Defined snap-1/pkg-1 (snapshot noop); it is not running yet. Use cordis_run to activate this Package. Run controls live in the Cordis panel above Settings - button "Inspect" -- 'button "Think The id is \"dyn-1\". Now step 3: call cordis_unmount with that id."': +- button "Think The Host returned snap-1/pkg-1, so I will request its first activation.": - img - img - - text: "Think The id is \"dyn-1\". Now step 3: call cordis_unmount with that id." -- button "Unmount temporary Plugin dyn-1": + - text: Think The Host returned snap-1/pkg-1, so I will request its first activation. +- img +- text: Run Cordis Plugin snap-1 · pkg-1 Ready +- button "Inspect" +- text: snap-1/pkg-1 is awaiting user approval (run-1). +- button "Think The activation request has been submitted, so I will return the requested readiness marker.": - img - img - - text: Unmount temporary Plugin dyn-1 -- button "Think All three calls succeeded. I should now reply exactly \"CORDIS_UI_DONE\" and stop.": + - text: Think The activation request has been submitted, so I will return the requested readiness marker. +- paragraph: CORDIS_UI_READY +- button "Copy": + - img +- button "Good response": + - img +- button "Bad response": + - img +- button "Branch into a new conversation": + - img +- text: {{clock}} Ran for {{duration}} +- button "Context injection cordis-host-runner": - img - img - - text: Think All three calls succeeded. I should now reply exactly "CORDIS_UI_DONE" and stop. + - text: Context injection cordis-host-runner +- button "Stop dynamic package snap-1": + - img + - img + - text: Stop dynamic package snap-1 - paragraph: CORDIS_UI_DONE - button "Copy": - img @@ -57,7 +79,14 @@ - img - button "Branch into a new conversation": - img -- text: {{clock}} Ran for {{duration}} TTFT {{duration}} {{throughput}} tok/s +- text: {{clock}} Ran for {{duration}} Use only Cordis tools. Call cordis_stop with pluginId "snap-1". After it succeeds, reply exactly CORDIS_UI_DONE and stop. {{clock}} +- button "Copy": + - img +- status: + - text: "This turn failedllm-replay: script exhausted — session requested model call #7 but its script has only 6; re-record the scenario" + - code: UNKNOWN +- button "Back to bottom": + - img - textbox "Message the agent" - button "Commands": - img @@ -65,6 +94,6 @@ - button "Select model, current DeepSeek-V4-Flash": - text: DeepSeek-V4-Flash - img -- button "13% of context used" +- button "0% of context used" - button "Send message" [disabled] -- text: 1 turns · 4 steps LLM {{duration}} · Tool call {{duration}} TTFT avg {{duration}} · {{throughput}} tok/s Cache hit 77% Input 66.5K tok · Output 312 tok +- text: 3 turns · 7 steps LLM {{duration}} · Tool call {{duration}} Cache hit 77% Input 66.5K tok · Output 318 tok diff --git a/packages/README.i18n.yaml b/packages/README.i18n.yaml index 881587ddac..856b455103 100644 --- a/packages/README.i18n.yaml +++ b/packages/README.i18n.yaml @@ -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 packages/README.md -README.md: 578e8a3326a35fadc95e8420912f950a7d7427b5 -README.zh.md: cf0c0e39aa87e75bed5250cdb66909d82e2bee02 +README.md: 2e28b190378ed8ca25102d6311bd8a2c8d5e1c58 +README.zh.md: e0e1973d146c241d4846c4c237ed0e5f6a0d7b8a diff --git a/packages/README.md b/packages/README.md index 578e8a3326..2e28b19037 100644 --- a/packages/README.md +++ b/packages/README.md @@ -40,7 +40,7 @@ Groups hold `packages///`; names stay `@deepseek-ai/dsh-`. **Gr | [`preset/`](preset/README.md) | Per-session agent composition from preset `cordis.yml` files | Product — stable API | | [`guard/`](guard/README.md) | Loop-hygiene guards: advisory repeat-call reminders + the `tools/execute` deadline enforcer | Product — stable API | | [`bundle/`](bundle/README.md) | Installable `dsh --profile` patch layers | Product — stable API | -| [`extensions/`](self-modification/README.md) | Agent runtime self-modification: live plugin/service inspection and model-written plugin mount/unmount ([design](../.agents/notes/implemented/feature/2026-07-08-self-referential-cordis-toolset.md)) | Product — stable API | +| [`extensions/`](extensions/README.md) | Agent runtime self-modification: live plugin/service inspection and model-written plugin mount/unmount ([design](../.agents/notes/implemented/feature/2026-07-08-self-referential-cordis-toolset.md)) | Product — stable API | | [`hooks/`](hooks/README.md) | Hook bridges + the shared Claude Code / Codex wire-protocol library | Product — stable API | | [`session/`](session/README.md) | Durable session data plane: persistence seam + JSONL/SQLite backends, projection seam, log-backed titles, session reporting | Product — stable API | | [`session-query/`](session-query/README.md) | Session retrieval family: logical corpus, bounded reads, lineage, event relationships, semantic filtering, and SQLite full-text search | Product — stable API | diff --git a/packages/README.zh.md b/packages/README.zh.md index cf0c0e39aa..e0e1973d14 100644 --- a/packages/README.zh.md +++ b/packages/README.zh.md @@ -40,7 +40,7 @@ npm scope 为 `@deepseek-ai/dsh-*`;Cordis `Service` 子类和函数插件通 | [`preset/`](preset/README.md) | 由 preset `cordis.yml` 按会话组装 agent | 产品:稳定接口 | | [`guard/`](guard/README.md) | 循环卫生守卫:建议性重复调用提醒 + `tools/execute` 截止时间强制执行器 | 产品:稳定接口 | | [`bundle/`](bundle/README.md) | 可安装的 `dsh --profile` 补丁层 | 产品:稳定接口 | -| [`extensions/`](self-modification/README.md) | agent 运行时自修改:实时插件/服务检查和模型所写插件挂载/卸载([设计](../.agents/notes/implemented/feature/2026-07-08-self-referential-cordis-toolset.md)) | 产品:稳定接口 | +| [`extensions/`](extensions/README.md) | agent 运行时自修改:实时插件/服务检查和模型所写插件挂载/卸载([设计](../.agents/notes/implemented/feature/2026-07-08-self-referential-cordis-toolset.md)) | 产品:稳定接口 | | [`hooks/`](hooks/README.md) | 钩子桥接 + 共享 Claude Code/Codex 协议格式库 | 产品:稳定接口 | | [`session/`](session/README.md) | 持久会话数据平面:持久化 seam + JSONL/SQLite 后端、投影 seam、基于日志的标题、会话上报 | 产品:稳定接口 | | [`session-query/`](session-query/README.md) | 会话检索系列:逻辑语料库、有界读取、血缘、事件关系、语义过滤和 SQLite 全文搜索 | 产品:稳定接口 | diff --git a/packages/client/ui-workspace/src/client/stores.ts b/packages/client/ui-workspace/src/client/stores.ts index 750dc10fe6..e48b5f3f63 100644 --- a/packages/client/ui-workspace/src/client/stores.ts +++ b/packages/client/ui-workspace/src/client/stores.ts @@ -58,7 +58,7 @@ export function createWorkspaceViewStore(): EngineStoreHandle { d.groupBy = mode }, setOrderBy: (d, mode: SessionOrderBy) => { d.orderBy = mode }, diff --git a/packages/core/tools/tests/gen-tool-catalog.spec.ts b/packages/core/tools/tests/gen-tool-catalog.spec.ts index b2a14b84ed..510b28d237 100644 --- a/packages/core/tools/tests/gen-tool-catalog.spec.ts +++ b/packages/core/tools/tests/gen-tool-catalog.spec.ts @@ -25,7 +25,7 @@ describe('gen-tool-catalog collectToolCatalog', () => { it('boots every shipped tool package and harvests its model-facing schemas', async () => { const catalog = await collectToolCatalog() const names = catalog.flatMap(entry => entry.schemas.map(s => s.name)).sort() - expect(names).toEqual(['ask_user_question', 'bash', 'bash', 'cordis_define', 'cordis_inspect_list', 'cordis_inspect_query', 'cordis_inspect_self', 'cordis_run', 'cordis_stop', 'cordis_undefine', 'create_goal', 'edit', 'exit_plan_mode', 'get_goal', 'glob', 'grep', 'interrupt_agent', 'list_agents', 'lsp', 'pwsh', 'ralph', 'read', 'read_image', 'report', 'run_code', 'schedule_create', 'schedule_delete', 'schedule_list', 'send_message', 'session_event_read', 'session_event_search', 'session_event_trace', 'session_search', 'session_trace', 'skill', 'str_replace_editor', 'subagent', 'task_kill', 'task_list', 'task_output', 'terminal_close', 'terminal_list', 'terminal_open', 'terminal_read', 'terminal_send', 'terminal_signal', 'todo_write', 'update_goal', 'web_fetch', 'web_search', 'workflow', 'write']) + expect(names).toEqual(['ask_user_question', 'bash', 'bash', 'cordis_define', 'cordis_inspect_list', 'cordis_inspect_query', 'cordis_inspect_self', 'cordis_run', 'cordis_stop', 'cordis_undefine', 'create_goal', 'edit', 'exit_plan_mode', 'get_goal', 'glob', 'grep', 'interrupt_agent', 'list_agents', 'lsp', 'pwsh', 'ralph', 'read', 'read_image', 'report', 'run_code', 'schedule_create', 'schedule_delete', 'schedule_list', 'send_message', 'session_event_read', 'session_event_search', 'session_event_trace', 'session_search', 'session_trace', 'skill', 'str_replace_editor', 'subagent', 'job_kill', 'job_list', 'job_output', 'terminal_close', 'terminal_list', 'terminal_open', 'terminal_read', 'terminal_send', 'terminal_signal', 'todo_write', 'update_goal', 'web_fetch', 'web_search', 'workflow', 'write']) // Every tool carries a JSON-Schema `parameters` object (what the model sees). for (const entry of catalog) { for (const schema of entry.schemas) { diff --git a/packages/extensions/README.i18n.yaml b/packages/extensions/README.i18n.yaml index 36d12a76a9..3f8dc9b3b1 100644 --- a/packages/extensions/README.i18n.yaml +++ b/packages/extensions/README.i18n.yaml @@ -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 packages/extensions/README.md -README.md: 245a2c59515591bcd9e6b87cb94ea8ddeed6727d -README.zh.md: f30acac479397e0e00b294dfe860c37ee227426a +README.md: 12717c43b0cf0a2a7958ed2e18f608da23c67963 +README.zh.md: f905d0da10137a6d47fb3be2b6f51a905165e063 diff --git a/packages/extensions/README.md b/packages/extensions/README.md index 245a2c5951..12717c43b0 100644 --- a/packages/extensions/README.md +++ b/packages/extensions/README.md @@ -1,4 +1,4 @@ -# self-modification/ — the agent modifies its own runtime +# extensions/ — the agent modifies its own runtime English | [中文](README.zh.md) diff --git a/packages/extensions/README.zh.md b/packages/extensions/README.zh.md index f30acac479..f905d0da10 100644 --- a/packages/extensions/README.zh.md +++ b/packages/extensions/README.zh.md @@ -1,4 +1,4 @@ -# self-modification/:agent(智能体)修改自身运行时 +# extensions/:agent(智能体)修改自身运行时 [English](README.md) | 中文 diff --git a/scripts/client-tsconfig.spec.ts b/scripts/client-tsconfig.spec.ts index 6b2a5eb46d..0c7466154d 100644 --- a/scripts/client-tsconfig.spec.ts +++ b/scripts/client-tsconfig.spec.ts @@ -9,7 +9,7 @@ import { describe, expect, it } from 'vitest' const root = fileURLToPath(new URL('..', import.meta.url)) function clientCssDeclarations(): string[] { - const clientGroups = ['client', 'self-modification'] + const clientGroups = ['client', 'extensions'] return clientGroups.flatMap((group) => { const clientRoot = resolve(root, 'packages', group) return readdirSync(clientRoot, { withFileTypes: true })