diff --git a/apps/web/tests/creation-recommendations.e2e.ts b/apps/web/tests/creation-recommendations.e2e.ts index 99010ba9fd..2aa19d0945 100644 --- a/apps/web/tests/creation-recommendations.e2e.ts +++ b/apps/web/tests/creation-recommendations.e2e.ts @@ -1,4 +1,4 @@ -/** The completed answer offers creation; choosing a location does not start it. */ +/** The completed answer offers creation through a propose_action card; choosing a location does not start it. */ import { readFile } from 'node:fs/promises' import { join } from 'node:path' import { fileURLToPath } from 'node:url' @@ -37,17 +37,17 @@ describe('creation recommendation after an explicit wish', () => { await input.fill(fixtureUserPrompts(await readFile(fixture, 'utf8'))[0]!) await input.press('Enter') await settled - const card = page.locator('[data-creation-recommendation]') + const card = page.locator('[data-action-card]') await card.waitFor({ state: 'visible' }) expect(await card.count()).toBe(1) - expect(await page.locator('[data-turn-tail] [data-creation-recommendation]').count()).toBe(1) + expect(await page.locator('[data-turn-tail] [data-action-card]').count()).toBe(1) const answer = page.getByText('I can create a persistent counter with increment and reset controls. Choose its location below.', { exact: true }) const answerBox = await answer.boundingBox() const cardBox = await card.boundingBox() expect(answerBox).not.toBeNull() expect(cardBox!.y).toBeGreaterThanOrEqual(answerBox!.y + answerBox!.height) expect(await card.getByRole('button', { name: 'Create', exact: true }).isEnabled()).toBe(true) - await compareOrRefreshGolden(join(directory, 'offered.expected.md'), await captureStableAria(page, '[data-creation-recommendation]', scaffold.workspaceCwd), scaffold.mode) + await compareOrRefreshGolden(join(directory, 'offered.expected.md'), await captureStableAria(page, '[data-action-card]', scaffold.workspaceCwd), scaffold.mode) const info = card.getByRole('button', { name: 'How creation works and token usage', exact: true }) expect(await info.getAttribute('aria-expanded')).toBe('true') expect(await card.getByRole('note').innerText()).toContain('large number of tokens') @@ -66,11 +66,11 @@ describe('creation recommendation after an explicit wish', () => { expect(await card.getByRole('note').innerText()).toContain('keep chatting here') expect(await card.getByRole('button', { name: 'Create', exact: true }).isEnabled()).toBe(true) expect(await card.evaluate(element => element.scrollWidth <= element.clientWidth)).toBe(true) - await compareOrRefreshGolden(join(directory, 'info.expected.md'), await captureStableAria(page, '[data-creation-recommendation]', scaffold.workspaceCwd), scaffold.mode) + await compareOrRefreshGolden(join(directory, 'info.expected.md'), await captureStableAria(page, '[data-action-card]', scaffold.workspaceCwd), scaffold.mode) await info.press('Space') expect(await info.getAttribute('aria-expanded')).toBe('false') expect(await card.getByRole('note').count()).toBe(0) - const expand = card.getByRole('button', { name: 'View full plan', exact: true }) + const expand = card.getByRole('button', { name: 'Show more', exact: true }) const plan = page.locator(`[id="${await expand.getAttribute('aria-controls')}"]`) const compactHeight = await plan.evaluate(element => element.clientHeight) const previewText = await plan.innerText() @@ -82,8 +82,8 @@ describe('creation recommendation after an explicit wish', () => { await expand.click() expect(await plan.evaluate(element => element.clientHeight)).toBeGreaterThan(compactHeight) expect(await card.getByRole('button', { name: 'Create', exact: true }).isEnabled()).toBe(true) - await compareOrRefreshGolden(join(directory, 'expanded.expected.md'), await captureStableAria(page, '[data-creation-recommendation]', scaffold.workspaceCwd), scaffold.mode) - await card.getByRole('button', { name: 'Collapse plan', exact: true }).click() + await compareOrRefreshGolden(join(directory, 'expanded.expected.md'), await captureStableAria(page, '[data-action-card]', scaffold.workspaceCwd), scaffold.mode) + await card.getByRole('button', { name: 'Show less', exact: true }).click() const location = card.getByRole('button', { name: 'Add to: Right sidebar', exact: true }) await location.click() expect(await page.getByRole('menuitem').count()).toBe(4) @@ -97,12 +97,12 @@ describe('creation recommendation after an explicit wish', () => { expect(await card.getByRole('button', { name: 'Create', exact: true }).isEnabled()).toBe(false) await custom.fill('A left sidebar entry that opens a full-page calendar') expect(await card.getByRole('button', { name: 'Create', exact: true }).isEnabled()).toBe(true) - await compareOrRefreshGolden(join(directory, 'custom-location.expected.md'), await captureStableAria(page, '[data-creation-recommendation]', scaffold.workspaceCwd), scaffold.mode) + await compareOrRefreshGolden(join(directory, 'custom-location.expected.md'), await captureStableAria(page, '[data-action-card]', scaffold.workspaceCwd), scaffold.mode) await card.getByRole('button', { name: 'Add to: Write your own', exact: true }).click() await page.getByRole('menuitem', { name: /Session tab/ }).click() expect(await card.getByRole('button', { name: 'Add to: Session tab', exact: true }).isVisible()).toBe(true) expect(await custom.count()).toBe(0) - await compareOrRefreshGolden(join(directory, 'options.expected.md'), await captureStableAria(page, '[data-creation-recommendation]', scaffold.workspaceCwd), scaffold.mode) + await compareOrRefreshGolden(join(directory, 'options.expected.md'), await captureStableAria(page, '[data-action-card]', scaffold.workspaceCwd), scaffold.mode) expect(await card.evaluate(element => element.scrollWidth <= element.clientWidth)).toBe(true) await card.getByRole('button', { name: 'Not now', exact: true }).click() await card.getByRole('status').getByText('Dismissed', { exact: true }).waitFor() @@ -111,9 +111,9 @@ describe('creation recommendation after an explicit wish', () => { await card.getByRole('status').getByText('Dismissed', { exact: true }).waitFor() acknowledgeReloadConnectionLoss(tripwire, warnings) expect(await card.getByRole('button', { name: 'Create', exact: true }).count()).toBe(0) - expect(await card.getByRole('button', { name: 'View full plan', exact: true }).isVisible()).toBe(true) + expect(await card.getByRole('button', { name: 'Show more', exact: true }).isVisible()).toBe(true) expect((await scaffold.ctx.pluginManager.listBundles()).some(bundle => bundle.name.startsWith('@dsh-local/view-'))).toBe(false) - await compareOrRefreshGolden(join(directory, 'dismissed.expected.md'), await captureStableAria(page, '[data-creation-recommendation]', scaffold.workspaceCwd), scaffold.mode) + await compareOrRefreshGolden(join(directory, 'dismissed.expected.md'), await captureStableAria(page, '[data-action-card]', scaffold.workspaceCwd), scaffold.mode) const statusSettled = scaffold.whenTurnSettled() await input.fill(fixtureUserPrompts(await readFile(fixture, 'utf8'))[1]!) await input.press('Enter') diff --git a/snapshots/web/creation-recommendations/custom-location.expected.md b/snapshots/web/creation-recommendations/custom-location.expected.md index 00cfed3aa5..adba85e567 100644 --- a/snapshots/web/creation-recommendations/custom-location.expected.md +++ b/snapshots/web/creation-recommendations/custom-location.expected.md @@ -1,9 +1,9 @@ -- region "Personalized pages": +- region "Counter": - text: Personalized pages - button "How creation works and token usage" - strong: Counter - paragraph: "Need: Keep a small counter visible across conversations. Location: A card titled Today’s achievements in the right sidebar. Controls: Increment and reset buttons beside the current value. Storage: Save changes in browser storage across navigation and refresh.…" - - button "View full plan" + - button "Show more" - text: Add to - 'button "Add to: Write your own"': Write your own - text: Where should it appear? diff --git a/snapshots/web/creation-recommendations/dismissed.expected.md b/snapshots/web/creation-recommendations/dismissed.expected.md index a860d1ec53..8776081964 100644 --- a/snapshots/web/creation-recommendations/dismissed.expected.md +++ b/snapshots/web/creation-recommendations/dismissed.expected.md @@ -1,8 +1,8 @@ -- region "Personalized pages": +- region "Counter": - text: Personalized pages - button "How creation works and token usage" - status: Dismissed - strong: Counter - paragraph: "Need: Keep a small counter visible across conversations. Location: A card titled Today’s achievements in the right sidebar. Controls: Increment and reset buttons beside the current value. Storage: Save changes in browser storage across navigation and refresh.…" - - button "View full plan" + - button "Show more" - paragraph: Add to · Session tab diff --git a/snapshots/web/creation-recommendations/expanded.expected.md b/snapshots/web/creation-recommendations/expanded.expected.md index 8b3eabe1c3..db412c5be9 100644 --- a/snapshots/web/creation-recommendations/expanded.expected.md +++ b/snapshots/web/creation-recommendations/expanded.expected.md @@ -1,9 +1,9 @@ -- region "Personalized pages": +- region "Counter": - text: Personalized pages - button "How creation works and token usage" - strong: Counter - paragraph: "Need: Keep a small counter visible across conversations. Location: A card titled Today’s achievements in the right sidebar. Controls: Increment and reset buttons beside the current value. Storage: Save changes in browser storage across navigation and refresh. Appearance: Follow the existing sidebar theme in light and dark mode. Completion: Both controls work and reopening restores the saved value." - - button "Collapse plan" [expanded] + - button "Show less" [expanded] - text: Add to - 'button "Add to: Right sidebar"': Right sidebar - button "Not now" diff --git a/snapshots/web/creation-recommendations/info.expected.md b/snapshots/web/creation-recommendations/info.expected.md index c2d3ccc757..eb6e6a78a3 100644 --- a/snapshots/web/creation-recommendations/info.expected.md +++ b/snapshots/web/creation-recommendations/info.expected.md @@ -1,4 +1,4 @@ -- region "Personalized pages": +- region "Counter": - text: Personalized pages - button "How creation works and token usage" [expanded] - note: @@ -8,7 +8,7 @@ - paragraph: Click Create to start in the background. You can keep chatting here. - strong: Counter - paragraph: "Need: Keep a small counter visible across conversations. Location: A card titled Today’s achievements in the right sidebar. Controls: Increment and reset buttons beside the current value. Storage: Save changes in browser storage across navigation and refresh.…" - - button "View full plan" + - button "Show more" - text: Add to - 'button "Add to: Right sidebar"': Right sidebar - button "Not now" diff --git a/snapshots/web/creation-recommendations/offered.expected.md b/snapshots/web/creation-recommendations/offered.expected.md index c2d3ccc757..eb6e6a78a3 100644 --- a/snapshots/web/creation-recommendations/offered.expected.md +++ b/snapshots/web/creation-recommendations/offered.expected.md @@ -1,4 +1,4 @@ -- region "Personalized pages": +- region "Counter": - text: Personalized pages - button "How creation works and token usage" [expanded] - note: @@ -8,7 +8,7 @@ - paragraph: Click Create to start in the background. You can keep chatting here. - strong: Counter - paragraph: "Need: Keep a small counter visible across conversations. Location: A card titled Today’s achievements in the right sidebar. Controls: Increment and reset buttons beside the current value. Storage: Save changes in browser storage across navigation and refresh.…" - - button "View full plan" + - button "Show more" - text: Add to - 'button "Add to: Right sidebar"': Right sidebar - button "Not now" diff --git a/snapshots/web/creation-recommendations/options.expected.md b/snapshots/web/creation-recommendations/options.expected.md index 6853871727..61faf6f14d 100644 --- a/snapshots/web/creation-recommendations/options.expected.md +++ b/snapshots/web/creation-recommendations/options.expected.md @@ -1,9 +1,9 @@ -- region "Personalized pages": +- region "Counter": - text: Personalized pages - button "How creation works and token usage" - strong: Counter - paragraph: "Need: Keep a small counter visible across conversations. Location: A card titled Today’s achievements in the right sidebar. Controls: Increment and reset buttons beside the current value. Storage: Save changes in browser storage across navigation and refresh.…" - - button "View full plan" + - button "Show more" - text: Add to - 'button "Add to: Session tab"': Session tab - button "Not now" diff --git a/snapshots/web/creation-recommendations/session.v4.jsonl b/snapshots/web/creation-recommendations/session.v4.jsonl index 1be290ad75..e9452685ac 100644 --- a/snapshots/web/creation-recommendations/session.v4.jsonl +++ b/snapshots/web/creation-recommendations/session.v4.jsonl @@ -11,24 +11,31 @@ {"type":"user/message","data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent working directory: \"{{cwd}}\".\n\nCurrent DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable.\n\nApproval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed.\n\nCurrent DeepSeek Harness implementation checkout: \"{{sourceRoot}}\".\n\nCurrent DeepSeek Harness Web GUI URL: \"{{webUrl}}\"."}],"source":{"kind":"runtime-context","form":"snapshot","sections":[{"name":"working-directory:current","text":"Current working directory: \"{{cwd}}\"."},{"name":"sandbox:policy","text":"Current DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable."},{"name":"approval:policy","text":"Approval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."},{"name":"harness:source","text":"Current DeepSeek Harness implementation checkout: \"{{sourceRoot}}\"."},{"name":"app:web-surface","text":"Current DeepSeek Harness Web GUI URL: \"{{webUrl}}\"."}]},"role":"user","id":"{{message:3}}"},"surfaceOp":"append"} {"type":"user/message","data":{"content":[{"type":"text","text":"\nA skill is a reusable set of task-specific instructions. The following skills are available in this session:\n\n\n- `personalized-pages`: Plan an optional page or small tool when the user asks for a page, tab, panel, tool or interface in this app (even for one-off use), wants something to keep, revisit or update later, will keep changing data in your answer, or wants a live view of this app’s own data (Sessions, tasks, logs, workspace). Load this before reading any files or source; it says how to submit the proposal. Not for requested files or code, ordinary answers, existing-feature questions, quoted wishes or declined offers.\n\n\nIf the user names a skill, or the task clearly matches a skill's description, call the `skill` tool with the exact skill name before taking task actions. Load all applicable skills, then follow their full instructions. This catalog contains summaries only; do not infer or follow a skill's instructions until it has been loaded.\nA user may also invoke a skill directly; its block then appears in this conversation. Follow it, and do not call the `skill` tool again for that skill.\n"}],"source":{"kind":"skill-catalog","form":"catalog","entries":[{"name":"personalized-pages","description":"Plan an optional page or small tool when the user asks for a page, tab, panel, tool or interface in this app (even for one-off use), wants something to keep, revisit or update later, will keep changing data in your answer, or wants a live view of this app’s own data (Sessions, tasks, logs, workspace). Load this before reading any files or source; it says how to submit the proposal. Not for requested files or code, ordinary answers, existing-feature questions, quoted wishes or declined offers."}]},"role":"user","id":"{{message:4}}"},"surfaceOp":"append"} {"type":"user/message","data":{"content":[{"type":"text","text":"Time sampled while preparing turn 1, step 1: {{timeContextTimestamp}}\nBrowser time zone for this request: {{clientTimeZone}}. Interpret otherwise-unqualified dates and times in this zone.\nElapsed since the preceding model-visible message: {{elapsed}}"}],"source":{"kind":"time-context","form":"snapshot","sections":[{"name":"time-context","text":"Time sampled while preparing turn 1, step 1: {{timeContextTimestamp}}\nBrowser time zone for this request: {{clientTimeZone}}. Interpret otherwise-unqualified dates and times in this zone.\nElapsed since the preceding model-visible message: {{elapsed}}"}]},"role":"user","id":"{{message:5}}"},"surfaceOp":"append"} -{"type":"request/header","data":{"header":{"config":{"provider":"deepseek-official","model":"deepseek-v4-flash"},"tools":"{{tools}}"},"reason":"initial"}} +{"type":"request/header","data":{"header":{"config":{"provider":"deepseek-official","model":"deepseek-v4-flash"},"tools":["ask_user_question","bash","create_goal","edit","get_goal","glob","grep","interrupt_agent","job_kill","job_list","job_output","list_agents","list_subagent_models","plugin_manager","present","read","read_image","schedule_create","schedule_delete","schedule_list","schedule_update","send_message","skill","subagent","subagent_fork","todo_write","update_goal","web_fetch","web_search","workflow","working_directory","write"]},"reason":"initial"}} {"type":"request/context","data":{"provider":"deepseek-official","model":"deepseek-v4-flash","contextWindow":128000}} {"type":"session/title","data":{"title":"I wish I had a","messageSeqs":[8],"source":{"kind":"fallback"}}} -{"type":"assistant/message","data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"tool-call","id":"call_creation_skill","name":"skill","arguments":"{\"name\":\"personalized-pages\"}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{message:6}}"},"stream":[{"type":"chunk","time":1792505538988,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}},{"type":"chunk","time":1792505538988,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"call_creation_skill","name":"skill","arguments":"{\"name\":\"personalized-pages\"}"}}},{"type":"chunk","time":1792505538988,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}]},"surfaceOp":"append"} +{"type":"assistant/message","data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"tool-call","id":"call_creation_skill","name":"skill","arguments":"{\"name\":\"personalized-pages\"}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{message:6}}"},"stream":[{"type":"chunk","time":1792674450282,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}},{"type":"chunk","time":1792674450282,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"call_creation_skill","name":"skill","arguments":"{\"name\":\"personalized-pages\"}"}}},{"type":"chunk","time":1792674450283,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}]},"surfaceOp":"append"} {"type":"tool/call","data":{"turn":1,"step":1,"callId":"call_creation_skill","name":"skill","arguments":"{\"name\":\"personalized-pages\"}"}} -{"type":"tool/result","data":{"turn":1,"step":1,"message":{"role":"tool","source":{"kind":"tool","callId":"call_creation_skill"},"toolCallId":"call_creation_skill","content":[{"type":"text","text":"\n\nResources for this skill are managed by provider \"dsh-personalized-pages\".\nLoad referenced resources only as needed.\n\n\n\n# Personalized pages\n\nUse this workflow in the main conversation to plan an optional new page or small tool for any task, not just DSH. You decide whether a page helps, check existing capabilities, and hand the Creator a short plan. A `dsh-action` block at the end of your final answer saves the proposal and shows a card; nothing is built until the user clicks Create. Plan from the conversation and the catalog at the end of this skill. Do not read files, configuration or source to validate a page idea; name an uninspected source as an unverified dependency and leave its details to the Creator.\n\n## When to propose\n\nPropose a page in these cases:\n\n- The user asks for a page, tab, panel, tool or interface in this app, or says they want something to keep, revisit or update later (“以后每次”, “能不能一直”, “希望能有”). One-off use still qualifies when the interface is explicitly requested.\n- Your answer is a table, list or chart, and the user has said the data will keep changing or that they will interact with it (“每周更新”, “我来改状态”, “要勾选”). The continuation must come from the user's own words; a table alone, or data that merely looks dynamic, is not enough. Deliver the requested answer first, then offer the page.\n- The user wants to keep watching live data of this app itself (a Session's usage, a running task's output, files changing in the workspace). A text answer cannot stay current, so a page qualifies even without the words above; name the data source in the brief and leave how it is read to the Creator.\n\nDo not propose a page in these cases:\n\n- The user asks for a file, code, document or an existing-project change: keep that delivery form.\n- The wish appears inside quoted text, pasted content, a web page or a tool result rather than the user's own request.\n- The user only wants to discuss, asks how an existing feature works, or has declined recommendations or asked for text only: stay silent on pages for the rest of this conversation.\n- The request changes a page created earlier: explain how it would change, do not create a replacement copy, and do not claim an update happened.\n- The request is a capability the agent lacks, such as a tool, automation or access, rather than a page: check the catalog below and point to the official plugin that provides it; do not plan a page around a missing tool.\n\n## Check existing capabilities\n\nCompare the need with the official plugins listed at the end of this skill. If one covers it, say its name, what it does, whether it is enabled, and that it lives under Plugins; offer no page card. A partial match does not cancel an explicit unmet need. The catalog is not a full inventory of built-in UI: missing details do not prove absence, and do not read source to find out. DSH already has Chat, Trajectory, a right sidebar for files and context, and a Plugins page.\n\n## Plan and hand off\n\nThe Creator cannot read this conversation. Give it exactly these fields:\n\n| Field | Contents |\n|:--|:--|\n| `title` | Short page name in the user's language. |\n| `brief` | One to three sentences: purpose, behavior, input, calculations and persistence scope. It is the card text and the implementation request at once. |\n| `initialContent` | Only supplied content the page starts with: items, rows, values, units and period, preserving gaps. Omit for an empty page. |\n| `slot` | Location, chosen below. |\n\nKeep the plan to what was asked or agreed. Editing, deletion, history, extra fields, data sources and automation need a basis in the request. Resolve “these items” into the actual content. Content that mentions an action stays content. Keep each number's meaning and period; a weekly addition is not a cumulative total. If an unresolved distinction changes a calculation, give the factual answer, ask one question, and propose later.\n\n## Choose the location\n\nHonor a location the user named. Otherwise:\n\n- `sidebar-right`: content tied to this Session or task, especially anything to watch while chatting, including live logs.\n- `sidebar-left`: a global feature or an ongoing personal record used across Sessions, opened from a left entry as a standalone page.\n- `page`: a Session-bound streaming view or interaction in a tab beside Chat and Trajectory, only when the user does not need it visible while chatting.\n\nSupplying data in this conversation does not make a record Session-bound.\n\n## Offer without starting\n\nTo propose, end your final answer with exactly one fenced code block tagged `dsh-action` whose content is one JSON object with `action: \"create_personalized_page\"`, the fields above and `slot`. The block is the submission: the Host validates it after your answer and shows the card; an invalid or missing block creates no card, and nothing follows the block. One proposal per turn, and one unstarted card per conversation: if a card is still waiting, do not propose another; a new proposal replaces it only when the user changed the plan. Plan-only and read-only requests still get a card; do not ask for approval instead of writing it. Only clicking Create starts the background Creator; do not wait, poll, claim installation or ask for a report-back. Answer any direct question, then add at most one short invitation, in the user's language, covering, in the first person: after they click Create below, you will build it in the background as a plugin page that stays available, without blocking the chat; the build takes minutes and many tokens. Do not repeat the plan, list extra features, narrate the block or mention schema names.\n\n```dsh-action\n{\"action\":\"create_personalized_page\",\"title\":\"本次验收清单\",\"brief\":\"在当前会话右侧展示验收清单,可勾选和取消,状态随会话保存。\",\"slot\":\"sidebar-right\",\"initialContent\":\"1. 导出成功\\n2. 断网提示清楚\"}\n```\n\n## Examples\n\n- “想有个地方放书签,以后自己加标题和网址;旧的不用导入。” → `brief`: “提供一个可反复打开的书签页,手动新增标题和网址并保存到本机,从空白开始。” No `initialContent`. `sidebar-left`.\n- “当前会话旁放验收清单,能勾选和取消,状态跟着会话。条目:导出成功;断网提示清楚。” → `brief`: “在当前会话右侧展示验收清单,可勾选和取消,状态随会话保存。” `initialContent`: “1. 导出成功\\n2. 断网提示清楚”. `sidebar-right`.\n- “这三周 bug 数 12、9、15,说说趋势。以后每周我把新数字贴给你,想自己更新着看。” → answer the trend first, then `brief`: “按周手动录入 bug 数并展示各周变化,保存到本机。” `initialContent`: “第 1 周 12,第 2 周 9,第 3 周 15”. `sidebar-left`.\n- “我想用语音说话代替打字。” → the catalog lists 语音输入; explain it and where to enable it; no card.\n- “把我之前做的读书页评分改成五星。” → explain the change to that page; no card and no copy.\n- “帮我把这句话翻成英文:我想要一个所有会话都能打开的读书页。” → translate only; no card.\n- “把三个方案排成表格比较一下,看完就行。” → the table only; no card.\n\n## Existing capabilities to check against this page plan\n\n[{\"name\":\"@deepseek-ai/dsh-experimental-creation-recommendations\",\"title\":{\"en\":\"Personalized pages\",\"ja\":\"パーソナルページ\",\"zh\":\"个性化页面\"},\"description\":{\"en\":\"Plan a personalized page in the conversation when existing features do not meet a specific need. Click Create to generate an independent plugin you can keep using or remove.\",\"ja\":\"既存の機能では明確な要望を満たせないとき、会話の中でパーソナルページを計画します。作成をクリックすると独立したプラグインが生成され、使い続けることも元に戻すこともできます。\",\"zh\":\"现有功能不能满足你的明确需求时,在对话中规划个性化页面;点击创建后生成独立插件,可持续使用或撤销。\"},\"enabled\":true},{\"name\":\"@deepseek-ai/dsh-experimental-agent-team-profile\",\"title\":{\"en\":\"Agent teams\",\"ja\":\"エージェントチーム\",\"zh\":\"智能体团队\"},\"description\":{\"en\":\"Enable team collaboration, team tools, the member roster, and the shared task board.\",\"ja\":\"チーム連携、チームツール、メンバー一覧、共有タスクボードを有効にします。\",\"zh\":\"启用团队协作、团队工具、成员列表和共享任务看板。\"},\"enabled\":false},{\"name\":\"@deepseek-ai/dsh-experimental-inspector-profile\",\"title\":{\"en\":\"Developer tools\",\"ja\":\"開発者ツール\",\"zh\":\"开发者工具\"},\"description\":{\"en\":\"Inspect and debug raw session data, grouped chat messages, and the NodeJS backend.\",\"ja\":\"セッションの生データ、グループ化されたチャットメッセージ、NodeJS バックエンドを確認、デバッグします。\",\"zh\":\"查看调试会话原始数据、聊天消息分组数据,以及调试 NodeJS 后端\"},\"enabled\":false},{\"name\":\"@deepseek-ai/dsh-experimental-voice-input-bundle\",\"title\":{\"en\":\"Voice input\",\"ja\":\"音声入力\",\"zh\":\"语音输入\"},\"description\":{\"en\":\"Transcribe recordings locally with SenseVoice; first use requires installing dependencies.\",\"ja\":\"ローカルの SenseVoice で録音を文字に起こします。初回利用時には依存関係のインストールが必要です。\",\"zh\":\"在本机使用 SenseVoice 转写录音,首次使用需安装依赖。\"},\"enabled\":false},{\"name\":\"@deepseek-ai/dsh-experimental-auto-review\",\"title\":{\"en\":\"Auto authorization review\",\"ja\":\"自動認可レビュー\",\"zh\":\"自动授权审查\"},\"description\":{\"en\":\"Add an Auto review permission mode that uses the model to assess authorization before each tool call.\",\"ja\":\"各ツール呼び出しの前にモデルが認可を判断する、自動認可レビュー権限モードを提供します。\",\"zh\":\"提供自动审查权限模式,由模型在每次工具调用前判断是否授权。\"},\"enabled\":false},{\"name\":\"@deepseek-ai/dsh-experimental-cot-translation-bundle\",\"title\":{\"en\":\"Machine translation of reasoning\",\"ja\":\"思考内容の機械翻訳\",\"zh\":\"思考过程机器翻译\"},\"description\":{\"en\":\"Machine translation of expanded reasoning; source text is sent to the selected service.\",\"ja\":\"展開した思考内容を機械翻訳します。原文は選択したサービスに送信されます。\",\"zh\":\"机器翻译展开的思考内容;原文会发送给所选服务\"},\"enabled\":false},{\"name\":\"@deepseek-ai/dsh-experimental-badge-skill-bundle\",\"title\":{\"en\":\"DSH badge skill\",\"ja\":\"DSH バッジスキル\",\"zh\":\"DSH 徽章技能\"},\"description\":{\"en\":\"Let agents add a “powered by dsh” badge to documents, pull requests, and other outputs.\",\"ja\":\"エージェントが文書や PR などの成果物に「powered by dsh」バッジを追加できます。\",\"zh\":\"让 Agent 为文档、PR 等产出添加「powered by dsh」徽章。\"},\"enabled\":false},{\"name\":\"@deepseek-ai/dsh-experimental-ralph-bundle\",\"title\":{\"en\":\"Ralph loops\",\"ja\":\"Ralph ループ\",\"zh\":\"Ralph 循环\"},\"description\":{\"en\":\"Run explicitly requested repeated work with fresh agents.\",\"ja\":\"ユーザーが明示的に依頼した繰り返し作業を、新しいエージェントで実行します。\",\"zh\":\"使用新智能体执行用户明确要求的循环工作。\"},\"enabled\":false},{\"name\":\"@deepseek-ai/dsh-experimental-session-titles-bundle\",\"title\":{\"en\":\"Conversation-following titles\",\"ja\":\"会話に追従するタイトル\",\"zh\":\"随对话更新的标题\"},\"description\":{\"en\":\"Refresh session titles as new human prompts arrive.\",\"ja\":\"ユーザーから新しい指示を受け取ると、セッションのタイトルを更新します。\",\"zh\":\"在收到新的用户提示时更新会话标题。\"},\"enabled\":false},{\"name\":\"@deepseek-ai/dsh-experimental-terminal-bundle\",\"title\":{\"en\":\"Persistent terminals\",\"ja\":\"永続ターミナル\",\"zh\":\"持久终端\"},\"description\":{\"en\":\"Keep shell processes alive across tool calls.\",\"ja\":\"複数のツール呼び出しの間もシェルプロセスを実行し続けます。\",\"zh\":\"让 shell 进程在多次工具调用之间保持运行。\"},\"enabled\":false},{\"name\":\"@deepseek-ai/dsh-experimental-session-search\",\"title\":{\"en\":\"Session search\",\"ja\":\"セッション検索\",\"zh\":\"会话搜索\"},\"description\":{\"en\":\"Give agents tools to search and read earlier sessions from the current workspace.\",\"ja\":\"現在のワークスペースの過去のセッションを検索、読み取りするツールをエージェントに提供します。\",\"zh\":\"为智能体提供搜索和读取当前工作区历史会话的工具。\"},\"enabled\":false},{\"name\":\"@deepseek-ai/dsh-experimental-tool-worktree\",\"title\":{\"en\":\"Git worktrees\",\"ja\":\"Git ワークツリー\",\"zh\":\"Git 工作树\"},\"description\":{\"en\":\"Let the agent create a new Git branch and worktree, then continue there.\",\"ja\":\"エージェントが新しい Git ブランチとワークツリーを作成し、そこで作業を続けられます。\",\"zh\":\"让智能体创建新的 Git 分支与工作树,并在其中继续工作。\"},\"enabled\":false},{\"name\":\"@deepseek-ai/dsh-cua-bundle\",\"title\":{\"en\":\"Computer use\",\"ja\":\"Computer Use\",\"zh\":\"Computer Use\"},\"description\":{\"en\":\"Currently supports macOS; Windows support is in development. Loads computer use for existing and new PTC sessions. Switch to PTC mode and grant Accessibility and Screen Recording permissions to use it.\",\"ja\":\"現在は macOS のみ対応し、Windows 対応は開発中です。既存および新規の PTC セッションに Computer Use を読み込みます。PTC モードに切り替え、アクセシビリティと画面収録の権限を許可して使用してください。\",\"zh\":\"目前仅支持 macOS,Windows 支持正在开发中。为已有及新建 PTC 会话加载 Computer Use。请切换到 PTC 模式来使用,并授予辅助功能和屏幕录制权限。\"},\"enabled\":false},{\"name\":\"@deepseek-ai/dsh-experimental-plugin-market-bundle\",\"title\":{\"en\":\"Custom plugin markets\",\"ja\":\"カスタムプラグインマーケット\",\"zh\":\"自定义插件市场\"},\"description\":{\"en\":\"Adds a Discover tab to Plugins for browsing and installing bundles from plugin market indexes you add.\",\"ja\":\"プラグインページに「見つける」タブを追加し、登録したマーケットのインデックスからプラグインを参照、インストールします。\",\"zh\":\"在插件页添加「发现」标签页,从你添加的插件市场索引浏览和安装插件。\"},\"enabled\":false},{\"name\":\"@deepseek-ai/dsh-subagent-claude-code\",\"title\":{\"en\":\"Claude Code subagent\",\"ja\":\"Claude Code サブエージェント\",\"zh\":\"Claude Code 子智能体\"},\"description\":{\"en\":\"Delegate one task to Claude Code using its native authentication and settings.\",\"ja\":\"Claude Code のネイティブの認証と設定を使用して、単独のタスクを委派します。\",\"zh\":\"使用 Claude Code 原生认证和设置委派单个任务。\"},\"enabled\":false},{\"name\":\"@deepseek-ai/dsh-subagent-codex\",\"title\":{\"en\":\"Codex subagent\",\"ja\":\"Codex サブエージェント\",\"zh\":\"Codex 子智能体\"},\"description\":{\"en\":\"Delegate one task to Codex using its native authentication and settings.\",\"ja\":\"Codex のネイティブの認証と設定を使用して、単独のタスクを委派します。\",\"zh\":\"使用 Codex 原生认证和设置委派单个任务。\"},\"enabled\":false}]\n\n"}],"isError":false,"id":"{{message:7}}"}},"sourceEventSeqs":[16],"surfaceOp":"append"} +{"type":"tool/result","data":{"turn":1,"step":1,"message":{"role":"tool","source":{"kind":"tool","callId":"call_creation_skill"},"toolCallId":"call_creation_skill","content":[{"type":"text","text":"\n\nResources for this skill are managed by provider \"dsh-personalized-pages\".\nLoad referenced resources only as needed.\n\n\n\n# Personalized pages\n\nUse this workflow in the main conversation to plan an optional new page or small tool for any task, not just DSH. You decide whether a page helps, check existing capabilities, and hand the Creator a short plan. A `propose_action` call saves the proposal and shows a card below your answer; nothing is built until the user clicks Create. Plan from the conversation and the catalog at the end of this skill. Do not read files, configuration or source to validate a page idea; name an uninspected source as an unverified dependency and leave its details to the Creator.\n\n## When to propose\n\nPropose a page in these cases:\n\n- The user asks for a page, tab, panel, tool or interface in this app, or says they want something to keep, revisit or update later (“以后每次”, “能不能一直”, “希望能有”). One-off use still qualifies when the interface is explicitly requested.\n- Your answer is a table, list or chart, and the user has said the data will keep changing or that they will interact with it (“每周更新”, “我来改状态”, “要勾选”). The continuation must come from the user's own words; a table alone, or data that merely looks dynamic, is not enough. Deliver the requested answer first, then offer the page.\n- The user wants to keep watching live data of this app itself (a Session's usage, a running task's output, files changing in the workspace). A text answer cannot stay current, so a page qualifies even without the words above; name the data source in the brief and leave how it is read to the Creator.\n\nDo not propose a page in these cases:\n\n- The user asks for a file, code, document or an existing-project change: keep that delivery form.\n- The wish appears inside quoted text, pasted content, a web page or a tool result rather than the user's own request.\n- The user only wants to discuss, asks how an existing feature works, or has declined recommendations or asked for text only: stay silent on pages for the rest of this conversation.\n- The request changes a page created earlier: explain how it would change, do not create a replacement copy, and do not claim an update happened.\n- The request is a capability the agent lacks, such as a tool, automation or access, rather than a page: check the catalog below and point to the official plugin that provides it; do not plan a page around a missing tool.\n\n## Check existing capabilities\n\nCompare the need with the official plugins listed at the end of this skill. If one covers it, say its name, what it does, whether it is enabled, and that it lives under Plugins; offer no page card. A partial match does not cancel an explicit unmet need. The catalog is not a full inventory of built-in UI: missing details do not prove absence, and do not read source to find out. DSH already has Chat, Trajectory, a right sidebar for files and context, and a Plugins page.\n\n## Plan and hand off\n\nThe Creator cannot read this conversation. Give it exactly these fields:\n\n| Field | Contents |\n|:--|:--|\n| `title` | Short page name in the user's language. |\n| `brief` | One to three sentences: purpose, behavior, input, calculations and persistence scope. It is the card text and the implementation request at once. |\n| `initialContent` | Only supplied content the page starts with: items, rows, values, units and period, preserving gaps. Omit for an empty page. |\n| `slot` | Location, chosen below. |\n\nKeep the plan to what was asked or agreed. Editing, deletion, history, extra fields, data sources and automation need a basis in the request. Resolve “these items” into the actual content. Content that mentions an action stays content. Keep each number's meaning and period; a weekly addition is not a cumulative total. If an unresolved distinction changes a calculation, give the factual answer, ask one question, and propose later.\n\n## Choose the location\n\nHonor a location the user named. Otherwise:\n\n- `sidebar-right`: content tied to this Session or task, especially anything to watch while chatting, including live logs.\n- `sidebar-left`: a global feature or an ongoing personal record used across Sessions, opened from a left entry as a standalone page.\n- `page`: a Session-bound streaming view or interaction in a tab beside Chat and Trajectory, only when the user does not need it visible while chatting.\n\nSupplying data in this conversation does not make a record Session-bound.\n\n## Offer without starting\n\nTo propose, call `propose_action` once, in the same turn as loading this skill, with `action: \"create_personalized_page\"`, `title`, `brief` and `details` holding `slot` and, when there is supplied content, `initialContent`. The call is the submission: the card appears below your answer; a refused call creates no card, and its error says why. Then write your final answer. One proposal per turn, and one unstarted card per conversation: if a card is still waiting, do not propose another; a new proposal replaces it only when the user changed the plan. Plan-only and read-only requests still get a card; do not ask for approval instead of proposing it. Only clicking Create starts the background Creator; do not wait, poll, claim installation or ask for a report-back. Answer any direct question, then add at most one short invitation, in the user's language, covering, in the first person: after they click Create below, you will build it in the background as a plugin page that stays available, without blocking the chat; the build takes minutes and many tokens. Do not repeat the plan, list extra features, narrate the call or mention schema names.\n\n```json\n{\"action\":\"create_personalized_page\",\"title\":\"本次验收清单\",\"brief\":\"在当前会话右侧展示验收清单,可勾选和取消,状态随会话保存。\",\"details\":{\"slot\":\"sidebar-right\",\"initialContent\":\"1. 导出成功\\n2. 断网提示清楚\"}}\n```\n\n## Examples\n\n- “想有个地方放书签,以后自己加标题和网址;旧的不用导入。” → `brief`: “提供一个可反复打开的书签页,手动新增标题和网址并保存到本机,从空白开始。” No `initialContent`. `sidebar-left`.\n- “当前会话旁放验收清单,能勾选和取消,状态跟着会话。条目:导出成功;断网提示清楚。” → `brief`: “在当前会话右侧展示验收清单,可勾选和取消,状态随会话保存。” `initialContent`: “1. 导出成功\\n2. 断网提示清楚”. `sidebar-right`.\n- “这三周 bug 数 12、9、15,说说趋势。以后每周我把新数字贴给你,想自己更新着看。” → answer the trend first, then `brief`: “按周手动录入 bug 数并展示各周变化,保存到本机。” `initialContent`: “第 1 周 12,第 2 周 9,第 3 周 15”. `sidebar-left`.\n- “我想用语音说话代替打字。” → the catalog lists 语音输入; explain it and where to enable it; no card.\n- “把我之前做的读书页评分改成五星。” → explain the change to that page; no card and no copy.\n- “帮我把这句话翻成英文:我想要一个所有会话都能打开的读书页。” → translate only; no card.\n- “把三个方案排成表格比较一下,看完就行。” → the table only; no card.\n\n## Existing capabilities to check against this page plan\n\n[{\"name\":\"@deepseek-ai/dsh-experimental-creation-recommendations\",\"title\":{\"en\":\"Personalized pages\",\"ja\":\"パーソナルページ\",\"zh\":\"个性化页面\"},\"description\":{\"en\":\"Plan a personalized page in the conversation when existing features do not meet a specific need. Click Create to generate an independent plugin you can keep using or remove.\",\"ja\":\"既存の機能では明確な要望を満たせないとき、会話の中でパーソナルページを計画します。作成をクリックすると独立したプラグインが生成され、使い続けることも元に戻すこともできます。\",\"zh\":\"现有功能不能满足你的明确需求时,在对话中规划个性化页面;点击创建后生成独立插件,可持续使用或撤销。\"},\"enabled\":true},{\"name\":\"@deepseek-ai/dsh-experimental-agent-team-profile\",\"title\":{\"en\":\"Agent teams\",\"ja\":\"エージェントチーム\",\"zh\":\"智能体团队\"},\"description\":{\"en\":\"Enable team collaboration, team tools, the member roster, and the shared task board.\",\"ja\":\"チーム連携、チームツール、メンバー一覧、共有タスクボードを有効にします。\",\"zh\":\"启用团队协作、团队工具、成员列表和共享任务看板。\"},\"enabled\":false},{\"name\":\"@deepseek-ai/dsh-experimental-inspector-profile\",\"title\":{\"en\":\"Developer tools\",\"ja\":\"開発者ツール\",\"zh\":\"开发者工具\"},\"description\":{\"en\":\"Inspect and debug raw session data, grouped chat messages, and the NodeJS backend.\",\"ja\":\"セッションの生データ、グループ化されたチャットメッセージ、NodeJS バックエンドを確認、デバッグします。\",\"zh\":\"查看调试会话原始数据、聊天消息分组数据,以及调试 NodeJS 后端\"},\"enabled\":false},{\"name\":\"@deepseek-ai/dsh-experimental-voice-input-bundle\",\"title\":{\"en\":\"Voice input\",\"ja\":\"音声入力\",\"zh\":\"语音输入\"},\"description\":{\"en\":\"Transcribe recordings locally with SenseVoice; first use requires installing dependencies.\",\"ja\":\"ローカルの SenseVoice で録音を文字に起こします。初回利用時には依存関係のインストールが必要です。\",\"zh\":\"在本机使用 SenseVoice 转写录音,首次使用需安装依赖。\"},\"enabled\":false},{\"name\":\"@deepseek-ai/dsh-experimental-auto-review\",\"title\":{\"en\":\"Auto authorization review\",\"ja\":\"自動認可レビュー\",\"zh\":\"自动授权审查\"},\"description\":{\"en\":\"Add an Auto review permission mode that uses the model to assess authorization before each tool call.\",\"ja\":\"各ツール呼び出しの前にモデルが認可を判断する、自動認可レビュー権限モードを提供します。\",\"zh\":\"提供自动审查权限模式,由模型在每次工具调用前判断是否授权。\"},\"enabled\":false},{\"name\":\"@deepseek-ai/dsh-experimental-cot-translation-bundle\",\"title\":{\"en\":\"Machine translation of reasoning\",\"ja\":\"思考内容の機械翻訳\",\"zh\":\"思考过程机器翻译\"},\"description\":{\"en\":\"Machine translation of expanded reasoning; source text is sent to the selected service.\",\"ja\":\"展開した思考内容を機械翻訳します。原文は選択したサービスに送信されます。\",\"zh\":\"机器翻译展开的思考内容;原文会发送给所选服务\"},\"enabled\":false},{\"name\":\"@deepseek-ai/dsh-experimental-badge-skill-bundle\",\"title\":{\"en\":\"DSH badge skill\",\"ja\":\"DSH バッジスキル\",\"zh\":\"DSH 徽章技能\"},\"description\":{\"en\":\"Let agents add a “powered by dsh” badge to documents, pull requests, and other outputs.\",\"ja\":\"エージェントが文書や PR などの成果物に「powered by dsh」バッジを追加できます。\",\"zh\":\"让 Agent 为文档、PR 等产出添加「powered by dsh」徽章。\"},\"enabled\":false},{\"name\":\"@deepseek-ai/dsh-experimental-ralph-bundle\",\"title\":{\"en\":\"Ralph loops\",\"ja\":\"Ralph ループ\",\"zh\":\"Ralph 循环\"},\"description\":{\"en\":\"Run explicitly requested repeated work with fresh agents.\",\"ja\":\"ユーザーが明示的に依頼した繰り返し作業を、新しいエージェントで実行します。\",\"zh\":\"使用新智能体执行用户明确要求的循环工作。\"},\"enabled\":false},{\"name\":\"@deepseek-ai/dsh-experimental-session-titles-bundle\",\"title\":{\"en\":\"Conversation-following titles\",\"ja\":\"会話に追従するタイトル\",\"zh\":\"随对话更新的标题\"},\"description\":{\"en\":\"Refresh session titles as new human prompts arrive.\",\"ja\":\"ユーザーから新しい指示を受け取ると、セッションのタイトルを更新します。\",\"zh\":\"在收到新的用户提示时更新会话标题。\"},\"enabled\":false},{\"name\":\"@deepseek-ai/dsh-experimental-terminal-bundle\",\"title\":{\"en\":\"Persistent terminals\",\"ja\":\"永続ターミナル\",\"zh\":\"持久终端\"},\"description\":{\"en\":\"Keep shell processes alive across tool calls.\",\"ja\":\"複数のツール呼び出しの間もシェルプロセスを実行し続けます。\",\"zh\":\"让 shell 进程在多次工具调用之间保持运行。\"},\"enabled\":false},{\"name\":\"@deepseek-ai/dsh-experimental-session-search\",\"title\":{\"en\":\"Session search\",\"ja\":\"セッション検索\",\"zh\":\"会话搜索\"},\"description\":{\"en\":\"Give agents tools to search and read earlier sessions from the current workspace.\",\"ja\":\"現在のワークスペースの過去のセッションを検索、読み取りするツールをエージェントに提供します。\",\"zh\":\"为智能体提供搜索和读取当前工作区历史会话的工具。\"},\"enabled\":false},{\"name\":\"@deepseek-ai/dsh-experimental-tool-worktree\",\"title\":{\"en\":\"Git worktrees\",\"ja\":\"Git ワークツリー\",\"zh\":\"Git 工作树\"},\"description\":{\"en\":\"Let the agent create a new Git branch and worktree, then continue there.\",\"ja\":\"エージェントが新しい Git ブランチとワークツリーを作成し、そこで作業を続けられます。\",\"zh\":\"让智能体创建新的 Git 分支与工作树,并在其中继续工作。\"},\"enabled\":false},{\"name\":\"@deepseek-ai/dsh-cua-bundle\",\"title\":{\"en\":\"Computer use\",\"ja\":\"Computer Use\",\"zh\":\"Computer Use\"},\"description\":{\"en\":\"Currently supports macOS; Windows support is in development. Loads computer use for existing and new PTC sessions. Switch to PTC mode and grant Accessibility and Screen Recording permissions to use it.\",\"ja\":\"現在は macOS のみ対応し、Windows 対応は開発中です。既存および新規の PTC セッションに Computer Use を読み込みます。PTC モードに切り替え、アクセシビリティと画面収録の権限を許可して使用してください。\",\"zh\":\"目前仅支持 macOS,Windows 支持正在开发中。为已有及新建 PTC 会话加载 Computer Use。请切换到 PTC 模式来使用,并授予辅助功能和屏幕录制权限。\"},\"enabled\":false},{\"name\":\"@deepseek-ai/dsh-experimental-plugin-market-bundle\",\"title\":{\"en\":\"Custom plugin markets\",\"ja\":\"カスタムプラグインマーケット\",\"zh\":\"自定义插件市场\"},\"description\":{\"en\":\"Adds a Discover tab to Plugins for browsing and installing bundles from plugin market indexes you add.\",\"ja\":\"プラグインページに「見つける」タブを追加し、登録したマーケットのインデックスからプラグインを参照、インストールします。\",\"zh\":\"在插件页添加「发现」标签页,从你添加的插件市场索引浏览和安装插件。\"},\"enabled\":false},{\"name\":\"@deepseek-ai/dsh-subagent-claude-code\",\"title\":{\"en\":\"Claude Code subagent\",\"ja\":\"Claude Code サブエージェント\",\"zh\":\"Claude Code 子智能体\"},\"description\":{\"en\":\"Delegate one task to Claude Code using its native authentication and settings.\",\"ja\":\"Claude Code のネイティブの認証と設定を使用して、単独のタスクを委派します。\",\"zh\":\"使用 Claude Code 原生认证和设置委派单个任务。\"},\"enabled\":false},{\"name\":\"@deepseek-ai/dsh-subagent-codex\",\"title\":{\"en\":\"Codex subagent\",\"ja\":\"Codex サブエージェント\",\"zh\":\"Codex 子智能体\"},\"description\":{\"en\":\"Delegate one task to Codex using its native authentication and settings.\",\"ja\":\"Codex のネイティブの認証と設定を使用して、単独のタスクを委派します。\",\"zh\":\"使用 Codex 原生认证和设置委派单个任务。\"},\"enabled\":false}]\n\n"}],"isError":false,"id":"{{message:7}}"}},"sourceEventSeqs":[16],"surfaceOp":"append"} {"type":"step/end","data":{"turn":1,"step":1}} {"type":"step/start","data":{"turn":1,"step":2}} -{"type":"assistant/message","data":{"turn":1,"step":2,"message":{"role":"assistant","content":[{"type":"text","text":"I can create a persistent counter with increment and reset controls. Choose its location below.\n\n```dsh-action\n{\"action\": \"create_personalized_page\", \"title\": \"Counter\", \"brief\": \"Need: Keep a small counter visible across conversations.\\nLocation: A card titled Today’s achievements in the right sidebar.\\nControls: Increment and reset buttons beside the current value.\\nStorage: Save changes in browser storage across navigation and refresh.\\nAppearance: Follow the existing sidebar theme in light and dark mode.\\nCompletion: Both controls work and reopening restores the saved value.\", \"slot\": \"sidebar-right\"}\n```"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{message:8}}"},"stream":[{"type":"chunk","time":1792505539242,"chunk":{"type":"block-start","index":0,"blockType":"text"}},{"type":"text-chunks","time0":1792505539242,"index":0,"dt":[],"texts":["I can create a persistent counter with increment and reset controls. Choose its location below.\n\n```dsh-action\n{\"action\": \"create_personalized_page\", \"title\": \"Counter\", \"brief\": \"Need: Keep a small counter visible across conversations.\\nLocation: A card titled Today’s achievements in the right sidebar.\\nControls: Increment and reset buttons beside the current value.\\nStorage: Save changes in browser storage across navigation and refresh.\\nAppearance: Follow the existing sidebar theme in light and dark mode.\\nCompletion: Both controls work and reopening restores the saved value.\", \"slot\": \"sidebar-right\"}\n```"]},{"type":"chunk","time":1792505539242,"chunk":{"type":"block-end","index":0,"block":{"type":"text","text":"I can create a persistent counter with increment and reset controls. Choose its location below.\n\n```dsh-action\n{\"action\": \"create_personalized_page\", \"title\": \"Counter\", \"brief\": \"Need: Keep a small counter visible across conversations.\\nLocation: A card titled Today’s achievements in the right sidebar.\\nControls: Increment and reset buttons beside the current value.\\nStorage: Save changes in browser storage across navigation and refresh.\\nAppearance: Follow the existing sidebar theme in light and dark mode.\\nCompletion: Both controls work and reopening restores the saved value.\", \"slot\": \"sidebar-right\"}\n```"}}},{"type":"chunk","time":1792505539242,"chunk":{"type":"finish","reason":{"kind":"stop"}}}]},"surfaceOp":"append"} +{"type":"request/header","data":{"header":{"config":{"provider":"deepseek-official","model":"deepseek-v4-flash"},"tools":["ask_user_question","bash","create_goal","edit","get_goal","glob","grep","interrupt_agent","job_kill","job_list","job_output","list_agents","list_subagent_models","plugin_manager","present","propose_action","read","read_image","schedule_create","schedule_delete","schedule_list","schedule_update","send_message","skill","subagent","subagent_fork","todo_write","update_goal","web_fetch","web_search","workflow","working_directory","write"]},"reason":"change"}} +{"type":"developer/message","data":{"turn":1,"step":2,"message":{"source":{"kind":"tool-registry"},"content":[{"type":"tool-addition","toolName":"propose_action"}],"role":"developer","id":"{{message:8}}"},"headerSeq":20},"surfaceOp":"append"} +{"type":"assistant/message","data":{"turn":1,"step":2,"message":{"role":"assistant","content":[{"type":"tool-call","id":"call_creation_card","name":"propose_action","arguments":"{\"action\":\"create_personalized_page\",\"title\":\"Counter\",\"brief\":\"Need: Keep a small counter visible across conversations.\\nLocation: A card titled Today’s achievements in the right sidebar.\\nControls: Increment and reset buttons beside the current value.\\nStorage: Save changes in browser storage across navigation and refresh.\\nAppearance: Follow the existing sidebar theme in light and dark mode.\\nCompletion: Both controls work and reopening restores the saved value.\",\"details\":{\"slot\":\"sidebar-right\"}}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{message:9}}"},"stream":[{"type":"chunk","time":1792674450652,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}},{"type":"chunk","time":1792674450652,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"call_creation_card","name":"propose_action","arguments":"{\"action\":\"create_personalized_page\",\"title\":\"Counter\",\"brief\":\"Need: Keep a small counter visible across conversations.\\nLocation: A card titled Today’s achievements in the right sidebar.\\nControls: Increment and reset buttons beside the current value.\\nStorage: Save changes in browser storage across navigation and refresh.\\nAppearance: Follow the existing sidebar theme in light and dark mode.\\nCompletion: Both controls work and reopening restores the saved value.\",\"details\":{\"slot\":\"sidebar-right\"}}"}}},{"type":"chunk","time":1792674450652,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}]},"surfaceOp":"append"} +{"type":"tool/call","data":{"turn":1,"step":2,"callId":"call_creation_card","name":"propose_action","arguments":"{\"action\":\"create_personalized_page\",\"title\":\"Counter\",\"brief\":\"Need: Keep a small counter visible across conversations.\\nLocation: A card titled Today’s achievements in the right sidebar.\\nControls: Increment and reset buttons beside the current value.\\nStorage: Save changes in browser storage across navigation and refresh.\\nAppearance: Follow the existing sidebar theme in light and dark mode.\\nCompletion: Both controls work and reopening restores the saved value.\",\"details\":{\"slot\":\"sidebar-right\"}}"}} +{"type":"tool/result","data":{"turn":1,"step":2,"message":{"role":"tool","source":{"kind":"tool","callId":"call_creation_card"},"toolCallId":"call_creation_card","content":[{"type":"text","text":"Card shown below your answer. Do not restate it; end with one short invitation."}],"isError":false,"id":"{{message:10}}"}},"sourceEventSeqs":[23],"surfaceOp":"append"} {"type":"step/end","data":{"turn":1,"step":2}} +{"type":"step/start","data":{"turn":1,"step":3}} +{"type":"assistant/message","data":{"turn":1,"step":3,"message":{"role":"assistant","content":[{"type":"text","text":"I can create a persistent counter with increment and reset controls. Choose its location below."}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{message:11}}"},"stream":[{"type":"chunk","time":1792674450679,"chunk":{"type":"block-start","index":0,"blockType":"text"}},{"type":"text-chunks","time0":1792674450679,"index":0,"dt":[],"texts":["I can create a persistent counter with increment and reset controls. Choose its location below."]},{"type":"chunk","time":1792674450679,"chunk":{"type":"block-end","index":0,"block":{"type":"text","text":"I can create a persistent counter with increment and reset controls. Choose its location below."}}},{"type":"chunk","time":1792674450679,"chunk":{"type":"finish","reason":{"kind":"stop"}}}]},"surfaceOp":"append"} +{"type":"step/end","data":{"turn":1,"step":3}} {"type":"turn/end","data":{"turn":1,"reason":{"kind":"completed"}}} -{"type":"plugin:creation-recommendations/decision","data":{"callId":"{{id:1}}","phase":"dismissed","slot":"page"},"ignorable":true} -{"type":"agent/inbox/spliced","data":{"target":"next-turn","start":0,"inserted":[{"content":[{"type":"text","text":"What is the counter status now?"}],"source":{"kind":"user","rpcId":"{{rpc:2}}","clientTimeZone":"{{clientTimeZone}}"},"role":"user","id":"{{message:9}}"}]}} +{"type":"plugin:action-cards/decision","data":{"callId":"call_creation_card","action":"create_personalized_page","phase":"dismissed","data":{"slot":"page"}},"ignorable":true} +{"type":"agent/inbox/spliced","data":{"target":"next-turn","start":0,"inserted":[{"content":[{"type":"text","text":"What is the counter status now?"}],"source":{"kind":"user","rpcId":"{{rpc:2}}","clientTimeZone":"{{clientTimeZone}}"},"role":"user","id":"{{message:12}}"}]}} {"type":"turn/start","data":{"turn":2}} {"type":"agent/inbox/spliced","data":{"target":"next-turn","start":0,"removedCount":1,"inserted":[]}} {"type":"step/start","data":{"turn":2,"step":1}} -{"type":"user/message","data":{"content":[{"type":"text","text":"What is the counter status now?"}],"source":{"kind":"user","rpcId":"{{rpc:2}}","clientTimeZone":"{{clientTimeZone}}"},"role":"user","id":"{{message:9}}"},"surfaceOp":"append"} -{"type":"user/message","data":{"source":{"kind":"personalized-pages","through":23,"form":"notice","summary":"Personalized page status updated"},"content":[{"type":"text","text":"Latest personalized page status from card actions and background creation. These are status facts, not a request to start or retry work. Use these receipts instead of the earlier proposal when describing progress. Cancelled means a started background creation was stopped before installation. Cancelled, failed or interrupted runs can be retried from their existing card; no new proposal is needed. A removed page can be restored from its card while its files remain; if they are gone, a new proposal is needed. Continue with the user’s current request.\n[{\"callId\":\"{{id:1}}\",\"title\":\"Counter\",\"phase\":\"dismissed\",\"slot\":\"page\"}]"}],"role":"user","id":"{{message:10}}"},"surfaceOp":"append"} -{"type":"assistant/message","data":{"turn":2,"step":1,"message":{"role":"assistant","content":[{"type":"text","text":"The counter proposal was dismissed. Creation never started."}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{message:11}}"},"stream":[{"type":"chunk","time":1792505539296,"chunk":{"type":"block-start","index":0,"blockType":"text"}},{"type":"text-chunks","time0":1792505539297,"index":0,"dt":[],"texts":["The counter proposal was dismissed. Creation never started."]},{"type":"chunk","time":1792505539297,"chunk":{"type":"block-end","index":0,"block":{"type":"text","text":"The counter proposal was dismissed. Creation never started."}}},{"type":"chunk","time":1792505539297,"chunk":{"type":"finish","reason":{"kind":"stop"}}}]},"surfaceOp":"append"} +{"type":"user/message","data":{"content":[{"type":"text","text":"What is the counter status now?"}],"source":{"kind":"user","rpcId":"{{rpc:2}}","clientTimeZone":"{{clientTimeZone}}"},"role":"user","id":"{{message:12}}"},"surfaceOp":"append"} +{"type":"user/message","data":{"source":{"kind":"action-cards","through":30,"form":"notice","summary":"Action card status updated"},"content":[{"type":"text","text":"Latest status of action cards from user actions and background work. These are status facts, not a request to start or retry work. Use these receipts instead of the earlier proposal when describing progress. Cancelled means a started background creation was stopped before installation. Cancelled, failed or interrupted runs can be retried from their existing card; no new proposal is needed. A removed page can be restored from its card while its files remain; if they are gone, a new proposal is needed. Continue with the user’s current request.\n[{\"action\":\"create_personalized_page\",\"callId\":\"call_creation_card\",\"title\":\"Counter\",\"phase\":\"dismissed\",\"data\":{\"slot\":\"page\"}}]"}],"role":"user","id":"{{message:13}}"},"surfaceOp":"append"} +{"type":"assistant/message","data":{"turn":2,"step":1,"message":{"role":"assistant","content":[{"type":"text","text":"The counter proposal was dismissed. Creation never started."}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{message:14}}"},"stream":[{"type":"chunk","time":1792674451829,"chunk":{"type":"block-start","index":0,"blockType":"text"}},{"type":"text-chunks","time0":1792674451829,"index":0,"dt":[],"texts":["The counter proposal was dismissed. Creation never started."]},{"type":"chunk","time":1792674451829,"chunk":{"type":"block-end","index":0,"block":{"type":"text","text":"The counter proposal was dismissed. Creation never started."}}},{"type":"chunk","time":1792674451829,"chunk":{"type":"finish","reason":{"kind":"stop"}}}]},"surfaceOp":"append"} {"type":"step/end","data":{"turn":2,"step":1}} {"type":"turn/end","data":{"turn":2,"reason":{"kind":"completed"}}} diff --git a/snapshots/web/creation-recommendations/tool-schemas.expected.json b/snapshots/web/creation-recommendations/tool-schemas.expected.json index 8aec2885ca..e2f8bc2429 100644 --- a/snapshots/web/creation-recommendations/tool-schemas.expected.json +++ b/snapshots/web/creation-recommendations/tool-schemas.expected.json @@ -1068,5 +1068,1107 @@ } } ], - "changes": [] + "changes": [ + [ + { + "name": "ask_user_question", + "description": "Ask the user a concise question when you need confirmation, a choice, or missing information before proceeding.", + "parameters": { + "type": "object", + "properties": { + "questions": { + "type": "array", + "description": "Questions to ask the user before continuing.", + "items": { + "type": "object", + "additionalProperties": true, + "properties": { + "id": { + "type": "string", + "description": "Stable id for this question; echoed in the answer." + }, + "question": { + "type": "string", + "description": "The specific question to ask the user." + }, + "header": { + "type": "string", + "description": "Optional short heading for the question, such as \"Confirm\" or \"Choose Mode\"." + }, + "options": { + "type": "array", + "description": "Optional choices to show the user. If you recommend one, put it first and append \"(Recommended)\" to that label.", + "items": { + "type": "object", + "additionalProperties": true, + "properties": { + "label": { + "type": "string", + "description": "Short user-facing option label." + }, + "description": { + "type": "string", + "description": "One sentence explaining the tradeoff or impact." + } + }, + "required": [ + "label" + ] + } + }, + "multi_select": { + "type": "boolean", + "description": "Whether the user may select more than one option. Defaults to false." + } + }, + "required": [ + "id", + "question" + ] + } + } + }, + "required": [ + "questions" + ] + } + }, + { + "name": "bash", + "description": "Execute a bash command (`bash -c`) and return its stdout/stderr. Each call runs in a fresh shell; pass `workdir` instead of using `cd`. Managed `$DSH_*` variables expose current harness environment facts. Long output is truncated to its tail; the full output is saved to a file whose path is reported when available. Provide `description` before `command` in the arguments. Before any delete or move, verify that the resolved absolute target path is the intended one; never run it against a computed path you have not checked. An unset variable expands to an empty string, so guard variables in such paths with `${VAR:?}`. Commands may run under a file sandbox; a blocked file operation is reported as `[sandbox: file access denied under mode]`, a policy denial: do not retry another way.", + "parameters": { + "type": "object", + "properties": { + "description": { + "type": "string", + "description": "Clear, concise description of what this command does in active voice, 5-10 words (shown in the UI). Examples: \"ls\" → \"List files in current directory\"; \"git status\" → \"Show working tree status\"; \"npm install\" → \"Install package dependencies\"." + }, + "command": { + "type": "string", + "description": "The bash command to execute." + }, + "timeoutMs": { + "type": "number", + "description": "Timeout in milliseconds. The executor applies its configured default and cap; on expiry the command moves to the background as a job instead of being killed." + }, + "workdir": { + "type": "string", + "description": "Working directory for this command. Defaults to the session workspace; a relative path is resolved against it." + }, + "run_in_background": { + "type": "boolean", + "description": "Run in the background and return a job id immediately (collect with job_output, stop with job_kill). No timeout applies." + }, + "sandbox_permissions": { + "type": "string", + "description": "The narrowest wider sandbox mode for a one-shot retry of the exact command the sandbox just denied; the retry asks the user for approval.", + "enum": [ + "workspace-write", + "danger-full-access" + ] + }, + "justification": { + "type": "string", + "description": "Required with sandbox_permissions: one sentence for the user explaining why this exact command needs the wider access. Use the language of the user’s current request." + } + }, + "required": [ + "description", + "command" + ] + } + }, + { + "name": "create_goal", + "description": "Create a persisted goal that keeps this session working across automatic continuation rounds. Use it when the direct human request is a long-running objective, even if the user did not say \"goal\"; not for single-turn work.", + "parameters": { + "type": "object", + "properties": { + "objective": { + "type": "string", + "description": "The concrete completion objective inferred from the direct human request." + }, + "max_goal_rounds": { + "type": "number", + "description": "Optional positive safe-integer limit on automatic continuation rounds." + } + }, + "required": [ + "objective" + ] + } + }, + { + "name": "edit", + "description": "Edit an existing UTF-8 text file by replacing literal text.", + "parameters": { + "type": "object", + "properties": { + "file_path": { + "type": "string", + "description": "Path to edit, resolved by the filesystem backend. Provide `file_path` before `old_string` and `new_string` in the arguments." + }, + "old_string": { + "type": "string", + "description": "Literal text to replace." + }, + "new_string": { + "type": "string", + "description": "Literal replacement text. Use an empty string to delete the match." + }, + "replace_all": { + "type": "boolean", + "description": "Replace all matches. Defaults to false; when false, old_string must appear exactly once." + }, + "sandbox_permissions": { + "type": "string", + "description": "The narrowest wider sandbox mode for a one-shot retry of the exact operation the sandbox just denied; the retry asks the user for approval.", + "enum": [ + "workspace-write", + "danger-full-access" + ] + }, + "justification": { + "type": "string", + "description": "Required with sandbox_permissions: one sentence for the user explaining why this exact file operation needs the wider access. Use the language of the user’s current request." + } + }, + "required": [ + "file_path", + "old_string", + "new_string" + ] + } + }, + { + "name": "get_goal", + "description": "Read the current session goal, including the id and revision that update_goal requires.", + "parameters": { + "type": "object", + "properties": {} + } + }, + { + "name": "glob", + "description": "Find files, not directories, whose paths match a glob pattern, including hidden and ignored files. Returns up to 100 paths in modification-time order; a larger result keeps the first paths and reports where the complete list was saved.", + "parameters": { + "type": "object", + "properties": { + "pattern": { + "type": "string", + "description": "Glob pattern to match file paths against (e.g. \"**/*.ts\", \"src/**/*.test.js\"). A pattern with no \"/\" matches the basename at any depth, so \"*\" and \"*.ts\" both search the whole tree; include a separator to anchor the depth." + }, + "path": { + "type": "string", + "description": "Directory to search in. Defaults to the session workspace; a relative path resolves against it." + } + }, + "required": [ + "pattern" + ] + } + }, + { + "name": "grep", + "description": "Search file contents with a ripgrep regular expression. Returns matching lines with line numbers, grouped by file. Returns up to 250 matches; a larger result reports where the complete match list was saved.", + "parameters": { + "type": "object", + "properties": { + "pattern": { + "type": "string", + "description": "Regular expression to search for (ripgrep syntax)." + }, + "path": { + "type": "string", + "description": "File or directory to search. Defaults to the session workspace; a relative path resolves against it." + }, + "include": { + "type": "string", + "description": "One glob filter for which files to search (e.g. \"*.ts\", \"*.{js,jsx}\"). Not a list; negation is not supported." + } + }, + "required": [ + "pattern" + ] + } + }, + { + "name": "interrupt_agent", + "description": "Ask a subagent to stop its current work. This call returns without waiting for it to stop. You can continue a local direct child's conversation later with send_message. External executions stop permanently and cannot receive follow-ups. Subagents it started will keep running.", + "parameters": { + "type": "object", + "properties": { + "agent_id": { + "type": "string", + "description": "The id of an agent created under you: your direct child or a deeper descendant." + } + }, + "required": [ + "agent_id" + ] + } + }, + { + "name": "job_kill", + "description": "Request cancellation of a running background job.", + "parameters": { + "type": "object", + "properties": { + "job_id": { + "type": "string", + "description": "Job id returned by the tool that started the background work." + }, + "reason": { + "type": "string", + "description": "Optional short reason, recorded in the log and forwarded to the job." + } + }, + "required": [ + "job_id" + ] + } + }, + { + "name": "job_list", + "description": "List your background jobs (running and finished) with their ids, kinds, and statuses.", + "parameters": { + "type": "object", + "properties": {} + } + }, + { + "name": "job_output", + "description": "Read a background job: output since the previous read for stream jobs, or the result of a finished final-output job.", + "parameters": { + "type": "object", + "properties": { + "job_id": { + "type": "string", + "description": "Job id returned by the tool that started the background work." + }, + "wait": { + "type": "boolean", + "description": "Block until the job finishes or the timeout expires; a timed-out wait leaves the job running. Defaults to false." + }, + "timeout_ms": { + "type": "number", + "description": "Max wait in milliseconds with wait: true. Defaults to and is capped by configuration." + } + }, + "required": [ + "job_id" + ] + } + }, + { + "name": "list_agents", + "description": "List subagents you started, with their ids, labels, and status. running means it is working; inactive means it is not currently working. You will be notified when a subagent finishes; there is no need to keep checking its status. Use send_message to continue the conversation.", + "parameters": { + "type": "object", + "properties": { + "scope": { + "type": "string", + "description": "children (default) lists direct children, which accept send_message in any status. descendants lists the whole tree below you with each entry's parent session id and depth; entries deeper than 1 accept only interrupt_agent.", + "enum": [ + "children", + "descendants" + ] + } + } + } + }, + { + "name": "list_subagent_models", + "description": "Inspect subagent models without changing the current Agent. No arguments: providers; `provider`: allowed models; `provider` + `model`: that model and its reasoning efforts. Inspect options here before selecting a child provider and model; use returned ids for delegation.", + "parameters": { + "type": "object", + "properties": { + "provider": { + "type": "string", + "description": "Registered LLM provider id." + }, + "model": { + "type": "string", + "description": "Exact model id; requires provider." + } + } + } + }, + { + "name": "plugin_manager", + "description": "List plugins or bundles in the current profile, enable or disable them, install a bundle, or remove an installed bundle. Every action requires danger-full-access permission or approval for this call. Approval does not change the session permission mode. Changes affect every session in this profile. List first to obtain exact identifiers. Package installation can execute allowed build scripts. Live profiles apply changes immediately, including upgrades of installed packages; a change result that is not applied carries nextStep, which says what to do or tell the user. Unmet DSH version requirements (engines.dsh, else DSH peer dependencies) block installation and activation. Version exemptions risk crashes and data loss: warn the user and obtain explicit permission for the exact plugin and runtime versions before granting one.", + "parameters": { + "type": "object", + "properties": { + "action": { + "type": "string", + "description": "Management operation.", + "enum": [ + "list_plugins", + "list_bundles", + "set_plugin", + "set_bundle", + "install_bundle", + "remove_bundle", + "list_version_exemptions", + "set_version_exemption" + ] + }, + "target": { + "type": "string", + "description": "Plugin entry id, bundle package name, or installation spec, according to action. For an Official catalog entry, pass list_bundles installTarget.spec to install_bundle." + }, + "enabled": { + "type": "boolean", + "description": "Required for set operations; defaults to true for installation. For set_version_exemption, true grants and false revokes." + }, + "runtimeVersion": { + "type": "string", + "description": "For set_version_exemption: exact DSH version from list_version_exemptions. Target must be the manifest package-name@version, not an alias or version range." + }, + "acceptRisk": { + "type": "boolean", + "description": "For granting an exemption: true only after warning the user about possible crashes and data loss and receiving explicit permission for this exact plugin/runtime pair. General installation permission is not enough." + }, + "approvedBuilds": { + "type": "array", + "description": "For install_bundle: pass names from pendingBuilds only after the user explicitly approves running their install scripts in the conversation. This grants persistent permission for this profile.", + "items": { + "type": "string" + } + }, + "registry": { + "type": "string", + "description": "For install_bundle: the npm registry URL asked first, when the user names one; otherwise the configured registry is asked, and its configured fallbacks while a registry is unreachable." + }, + "offset": { + "type": "number", + "description": "Zero-based list offset; defaults to 0." + }, + "limit": { + "type": "number", + "description": "List page size, from 1 to 100; defaults to 25." + } + }, + "required": [ + "action" + ] + } + }, + { + "name": "present", + "description": "Declare existing files as final deliverables for the user. Hosts may show presented files only after your turn ends, so call it once the files are final. If the user needs to review files earlier, such as before a tool call that waits for their answer, link them in your message instead. Use it when the user needs a separate file, especially Office documents, spreadsheets, and slide decks; prefer your final response when that suffices. The user opens the current files; their contents are not copied.", + "parameters": { + "type": "object", + "properties": { + "files": { + "type": "array", + "description": "Usually the 1-2 most important deliverables; at most 4 per call.", + "items": { + "type": "object", + "additionalProperties": false, + "properties": { + "path": { + "type": "string", + "description": "Path of an existing regular file. Relative paths use the Session working directory." + }, + "description": { + "type": "string", + "description": "Brief description for the user." + } + }, + "required": [ + "path" + ] + } + } + }, + "required": [ + "files" + ] + } + }, + { + "name": "propose_action", + "description": "Show the user one optional action card under this answer. Call only as the skill that names the action instructs.", + "parameters": { + "type": "object", + "properties": { + "action": { + "type": "string", + "description": "Action name from the skill." + }, + "title": { + "type": "string", + "description": "Card title." + }, + "brief": { + "type": "string", + "description": "What the action does, in the user's language." + }, + "details": { + "type": "object", + "description": "Fields the skill lists for this action.", + "additionalProperties": true + } + }, + "required": [ + "action", + "title", + "brief", + "details" + ] + } + }, + { + "name": "read", + "description": "Read a UTF-8 text file and return line-numbered content.", + "parameters": { + "type": "object", + "properties": { + "file_path": { + "type": "string", + "description": "Path to read, resolved by the filesystem backend." + }, + "offset": { + "type": "number", + "description": "1-based first line to return. Defaults to 1." + }, + "limit": { + "type": "number", + "description": "Maximum number of lines to return. Defaults to 2000." + } + }, + "required": [ + "file_path" + ] + } + }, + { + "name": "read_image", + "description": "Read a PNG/JPEG/WebP/GIF file and return the image itself. Large images are downscaled automatically; do not install image libraries or create thumbnails to inspect an image.", + "parameters": { + "type": "object", + "properties": { + "file_path": { + "type": "string", + "description": "Path to the image file, resolved by the filesystem backend." + } + }, + "required": [ + "file_path" + ] + } + }, + { + "name": "schedule_create", + "description": "Create a reminder in the current session that delivers prompt when it becomes due. Supply exactly one timing parameter: after_seconds, at, every_seconds, daily, weekly, or cron. Local times that do not exist in the zone are skipped; repeated local times fire once, at the earlier instant. After downtime, a recurring reminder delivers only its latest missed occurrence. Delivery can repeat after a crash.", + "parameters": { + "type": "object", + "properties": { + "prompt": { + "type": "string", + "description": "Reminder content to present when the target becomes due." + }, + "title": { + "type": "string", + "description": "Concise task name in the user's language, at most 120 characters. Name only the core action and subject; keep essential target identifiers such as PR numbers and branch names. Do not include execution frequency (e.g. daily, hourly), scheduled dates/times, time zones, task status, or configuration and execution details (e.g. read-only mode, profile-tag filters), even when embedded in the requested or existing title. Do not append parenthetical notes. Put scheduling information in the timing parameters and execution instructions in prompt." + }, + "after_seconds": { + "type": "number", + "description": "Delay in whole seconds." + }, + "every_seconds": { + "type": "number", + "description": "Fixed-rate interval in whole seconds, at least 60, aligned to the creation time; changing it with schedule_update re-aligns it to the save time." + }, + "daily": { + "type": "object", + "description": "Every day at a local time.", + "additionalProperties": false, + "properties": { + "time": { + "type": "string", + "description": "HH:mm:ss with optional 1-3 fractional digits, for example 23:00:00." + }, + "time_zone": { + "type": "string", + "description": "UTC or IANA Area/Location, for example Asia/Shanghai." + } + }, + "required": [ + "time", + "time_zone" + ] + }, + "weekly": { + "type": "object", + "description": "On the given weekdays at a local time.", + "additionalProperties": false, + "properties": { + "time": { + "type": "string", + "description": "HH:mm:ss with optional 1-3 fractional digits, for example 09:00:00." + }, + "time_zone": { + "type": "string", + "description": "UTC or IANA Area/Location, for example Asia/Shanghai." + }, + "weekdays": { + "type": "array", + "description": "ISO weekdays, Monday 1 through Sunday 7, without repetitions.", + "items": { + "type": "integer" + } + } + }, + "required": [ + "time", + "time_zone", + "weekdays" + ] + }, + "cron": { + "type": "object", + "description": "Five-field Vixie cron expression in a time zone.", + "additionalProperties": false, + "properties": { + "expression": { + "type": "string", + "description": "minute hour day-of-month month day-of-week, for example \"*/15 9-17 * * 1-5\". When both day fields are restricted, a date matches if either one matches." + }, + "time_zone": { + "type": "string", + "description": "UTC or IANA Area/Location, for example Asia/Shanghai." + } + }, + "required": [ + "expression", + "time_zone" + ] + }, + "at": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "object", + "additionalProperties": false, + "properties": { + "date": { + "type": "string" + }, + "time": { + "type": "string" + }, + "time_zone": { + "type": "string" + } + }, + "required": [ + "date", + "time", + "time_zone" + ] + } + ], + "description": "Absolute target: an RFC 3339 date-time with offset, or a local date, time, and IANA time_zone." + } + }, + "required": [ + "prompt", + "title" + ] + } + }, + { + "name": "schedule_delete", + "description": "Delete a reminder in the current session, active or inactive. Deletion does not retract a reminder message that is already queued.", + "parameters": { + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "Exact schedule id." + } + }, + "required": [ + "id" + ] + } + }, + { + "name": "schedule_list", + "description": "List the active reminders in the current session.", + "parameters": { + "type": "object", + "properties": {} + } + }, + { + "name": "schedule_update", + "description": "Change a reminder in place, keeping its id. Supply a new title, prompt, or at most one timing parameter; omitted fields keep their stored values. To change a relative delay, create a new reminder.", + "parameters": { + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "Schedule id returned by schedule_list." + }, + "title": { + "type": "string", + "description": "New task name; follow the schedule_create title rules." + }, + "prompt": { + "type": "string", + "description": "New reminder content." + }, + "every_seconds": { + "type": "number", + "description": "Fixed-rate interval in whole seconds, at least 60, aligned to the creation time; changing it with schedule_update re-aligns it to the save time." + }, + "daily": { + "type": "object", + "description": "Every day at a local time.", + "additionalProperties": false, + "properties": { + "time": { + "type": "string", + "description": "HH:mm:ss with optional 1-3 fractional digits, for example 23:00:00." + }, + "time_zone": { + "type": "string", + "description": "UTC or IANA Area/Location, for example Asia/Shanghai." + } + }, + "required": [ + "time", + "time_zone" + ] + }, + "weekly": { + "type": "object", + "description": "On the given weekdays at a local time.", + "additionalProperties": false, + "properties": { + "time": { + "type": "string", + "description": "HH:mm:ss with optional 1-3 fractional digits, for example 09:00:00." + }, + "time_zone": { + "type": "string", + "description": "UTC or IANA Area/Location, for example Asia/Shanghai." + }, + "weekdays": { + "type": "array", + "description": "ISO weekdays, Monday 1 through Sunday 7, without repetitions.", + "items": { + "type": "integer" + } + } + }, + "required": [ + "time", + "time_zone", + "weekdays" + ] + }, + "cron": { + "type": "object", + "description": "Five-field Vixie cron expression in a time zone.", + "additionalProperties": false, + "properties": { + "expression": { + "type": "string", + "description": "minute hour day-of-month month day-of-week, for example \"*/15 9-17 * * 1-5\". When both day fields are restricted, a date matches if either one matches." + }, + "time_zone": { + "type": "string", + "description": "UTC or IANA Area/Location, for example Asia/Shanghai." + } + }, + "required": [ + "expression", + "time_zone" + ] + }, + "at": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "object", + "additionalProperties": false, + "properties": { + "date": { + "type": "string" + }, + "time": { + "type": "string" + }, + "time_zone": { + "type": "string" + } + }, + "required": [ + "date", + "time", + "time_zone" + ] + } + ], + "description": "Absolute target: an RFC 3339 date-time with offset, or a local date, time, and IANA time_zone." + } + }, + "required": [ + "id" + ] + } + }, + { + "name": "send_message", + "description": "Send a message to an agent. A working agent receives it at its next step; an idle agent starts a new turn with it. Returns delivery confirmation, not the agent's answer.", + "parameters": { + "type": "object", + "properties": { + "agent_id": { + "type": "string", + "description": "The agent id of your direct continuable child, or your direct parent when you are a resident continuable child." + }, + "message": { + "type": "string", + "description": "The message to deliver to the agent." + } + }, + "required": [ + "agent_id", + "message" + ] + } + }, + { + "name": "skill", + "description": "Load the full instructions for a skill. Call it before acting on a task that names or clearly matches a skill in the session skill catalog.", + "parameters": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "The exact skill name from the available skills list." + } + }, + "required": [ + "name" + ] + } + }, + { + "name": "subagent", + "description": "Delegate a self-contained task to a subagent (a separate agent that works in its own context) to offload focused, independent work — research, a scoped implementation, an analysis — so it does not consume this conversation's context. The subagent returns its result, not its intermediate steps. This tool starts an independently managed subagent and immediately returns its id. The runtime notifies you when it finishes. The child reports results with `send_message`; use `send_message` to steer it while running or continue its conversation after it finishes. Supply `provider` and `model` together. Omitted values use child defaults, falling back to compatible parent values. When the effective route changes, an omitted `reasoning_effort` uses the model's default.", + "parameters": { + "type": "object", + "properties": { + "cwd": { + "type": "string", + "description": "Initial child working directory. Relative paths use your current directory; omitted inherits it. Later directory changes in either agent are independent." + }, + "description": { + "type": "string", + "description": "A short (3-5 word) description of the delegated task, for display." + }, + "prompt": { + "type": "string", + "description": "The complete, self-contained task for the subagent. It does not share this conversation's context, so include everything it needs." + }, + "provider": { + "type": "string", + "description": "Child LLM provider id." + }, + "model": { + "type": "string", + "description": "Model id within that provider." + }, + "reasoning_effort": { + "type": "string", + "description": "Model-specific reasoning effort id." + } + }, + "required": [ + "description", + "prompt" + ] + } + }, + { + "name": "subagent_fork", + "description": "Delegate a task to a subagent that inherits this conversation: a child agent seeded with all completed turns so far (it does not see the current in-flight turn). Use this when the subtask builds on this conversation's context — a follow-up analysis, a review, a continuation — without consuming this conversation's context for the work itself. You receive its result, not its intermediate steps. This tool starts an independently managed subagent and immediately returns its id. The runtime notifies you when it finishes. The child reports results with `send_message`; use `send_message` to steer it while running or continue its conversation after it finishes.", + "parameters": { + "type": "object", + "properties": { + "cwd": { + "type": "string", + "description": "Initial child working directory. Relative paths use your current directory; omitted inherits it. Later directory changes in either agent are independent." + }, + "description": { + "type": "string", + "description": "A short (3-5 word) description of the delegated task, for display." + }, + "prompt": { + "type": "string", + "description": "The task for the subagent. It already sees this conversation's completed turns, so build on them freely and state only what is new." + } + }, + "required": [ + "description", + "prompt" + ] + } + }, + { + "name": "todo_write", + "description": "Record and update a task list to plan multi-step work and show progress; skip it for trivial single-step tasks. Add one todo per concrete step before you start. While work remains, keep the todos being worked on `in_progress`, several only when work runs in parallel. Mark each todo `completed` as soon as it is done.", + "parameters": { + "type": "object", + "properties": { + "todos": { + "type": "array", + "description": "The COMPLETE task list, replacing any previous list.", + "items": { + "type": "object", + "additionalProperties": false, + "properties": { + "content": { + "type": "string", + "description": "What the task is — a short imperative line." + }, + "status": { + "type": "string", + "description": "pending (not started) | in_progress (now) | completed (done).", + "enum": [ + "pending", + "in_progress", + "completed" + ] + } + }, + "required": [ + "content", + "status" + ] + } + } + }, + "required": [ + "todos" + ] + } + }, + { + "name": "update_goal", + "description": "Update the current goal.", + "parameters": { + "type": "object", + "properties": { + "goal_id": { + "type": "string", + "description": "Exact id returned by get_goal." + }, + "revision": { + "type": "number", + "description": "Exact positive revision returned by get_goal." + }, + "action": { + "type": "string", + "description": "edit, pause, and resume require a direct top-level human request. complete and blocked are also allowed during an automatic continuation of this goal; blocked is rejected before the configured minimum round count.", + "enum": [ + "edit", + "pause", + "resume", + "complete", + "blocked" + ] + }, + "objective": { + "type": "string", + "description": "Replacement objective; valid only with action edit." + }, + "max_goal_rounds": { + "type": "number", + "description": "Replacement cap; valid only with action edit." + }, + "blocked_reason": { + "type": "string", + "description": "Required only with action blocked: the concrete condition that persisted across rounds and blocks progress." + } + }, + "required": [ + "goal_id", + "revision", + "action" + ] + } + }, + { + "name": "web_fetch", + "description": "Fetch the content of a specific HTTP(S) URL and return it decoded to text.", + "parameters": { + "type": "object", + "properties": { + "url": { + "type": "string", + "description": "The HTTP(S) URL to fetch." + } + }, + "required": [ + "url" + ] + } + }, + { + "name": "web_search", + "description": "Search the web for current information. Returns an optional summary answer and a list of source URLs.", + "parameters": { + "type": "object", + "properties": { + "queries": { + "type": "array", + "description": "1–4 search queries; their results are merged.", + "items": { + "type": "string" + } + } + }, + "required": [ + "queries" + ] + } + }, + { + "name": "workflow", + "description": "Run a JavaScript workflow script that orchestrates subagents at scale. Use this for work that fans out across many independent pieces — an audit over many files, a migration, multi-angle research, adversarial verification of findings — where you write the orchestration as a script instead of delegating turn by turn.\n\nScript-body hooks:\n- `agent(prompt, opts?): Promise` — run one subagent to completion. Without `opts.schema` it resolves to the child's final text; with `opts.schema` (an object-rooted JSON Schema using ONLY type/properties/required/additionalProperties/items/enum/const/oneOf) it resolves to the validated object. Resolves `null` when the child fails (filter with `.filter(Boolean)`). Other opts: `label` (display), `phase` (progress group), and independent `provider`/`model` LLM target overrides.\n- `pipeline(items, ...stages): Promise` — run each item through the stages independently with NO barrier between stages (prefer this for multi-stage work). Each stage receives `(prev, item, index)`. A stage throw drops that ITEM to `null` and skips its remaining stages.\n- `parallel(thunks): Promise` — run zero-argument functions concurrently and await ALL of them (a barrier; use only when a stage genuinely needs every prior result together). A throwing thunk resolves to `null`.\n- `phase(title)` — start a progress phase; `log(message)` — narrate progress; `args` — the tool call's `args` input, verbatim.\n\nMisused hooks (bad arguments, unknown options, unsupported schemas, tripped caps) end the whole script instead of producing `null`. The script has no filesystem, network, timer, or Node.js APIs; the agents do the work.", + "parameters": { + "type": "object", + "properties": { + "script": { + "type": "string", + "description": "The plain JavaScript body, not TypeScript and without an `export const meta` statement; top-level await is allowed. End with `return `; the JSON-serializable value is this tool's result." + }, + "meta": { + "type": "object", + "description": "The workflow identity as plain JSON, not code.", + "additionalProperties": true, + "properties": { + "name": { + "type": "string", + "description": "Short kebab-case workflow name." + }, + "description": { + "type": "string", + "description": "One-line description of what the workflow does." + }, + "whenToUse": { + "type": "string", + "description": "Optional guidance on when this workflow applies." + }, + "phases": { + "type": "array", + "description": "Optional phase declarations matched by phase() calls.", + "items": { + "type": "object", + "additionalProperties": true, + "properties": { + "title": { + "type": "string", + "description": "The phase title phase() calls match by exact string." + }, + "detail": { + "type": "string", + "description": "Optional one-line description of the phase." + }, + "provider": { + "type": "string", + "description": "Optional provider override this phase is expected to use." + }, + "model": { + "type": "string", + "description": "Optional model override this phase is expected to use." + } + }, + "required": [ + "title" + ] + } + } + }, + "required": [ + "name", + "description" + ] + }, + "args": { + "type": "object", + "description": "Optional JSON input exposed to the script as the `args` global (wrap a bare list as a field, e.g. {\"files\": [...]}).", + "additionalProperties": true + }, + "run_in_background": { + "type": "boolean", + "description": "Run as a background job: return a job id immediately instead of waiting; the return value arrives with the completion notice." + } + }, + "required": [ + "script", + "meta" + ] + } + }, + { + "name": "working_directory", + "description": "Read the current working directory, or change it with cd. Relative paths use the current directory. Existing shells and running processes keep their own directories.", + "parameters": { + "type": "object", + "properties": { + "cd": { + "type": "string", + "description": "Existing directory to enter. Omit to read the current directory." + } + } + } + }, + { + "name": "write", + "description": "Create or fully replace a UTF-8 text file.", + "parameters": { + "type": "object", + "properties": { + "file_path": { + "type": "string", + "description": "Path to write, resolved by the filesystem backend. Provide `file_path` before `content` in the arguments." + }, + "content": { + "type": "string", + "description": "Full UTF-8 text content to write." + }, + "sandbox_permissions": { + "type": "string", + "description": "The narrowest wider sandbox mode for a one-shot retry of the exact operation the sandbox just denied; the retry asks the user for approval.", + "enum": [ + "workspace-write", + "danger-full-access" + ] + }, + "justification": { + "type": "string", + "description": "Required with sandbox_permissions: one sentence for the user explaining why this exact file operation needs the wider access. Use the language of the user’s current request." + } + }, + "required": [ + "file_path", + "content" + ] + } + } + ] + ] }