diff --git a/apps/cli/package.json b/apps/cli/package.json index f6d4f804f1..85cb9fd709 100644 --- a/apps/cli/package.json +++ b/apps/cli/package.json @@ -107,7 +107,9 @@ "@deepseek-ai/dsh-experimental-session-search": "workspace:*", "@deepseek-ai/dsh-experimental-tool-worktree": "workspace:*", "@deepseek-ai/dsh-cua-bundle": "workspace:*", - "@deepseek-ai/dsh-experimental-plugin-market-bundle": "workspace:*" + "@deepseek-ai/dsh-experimental-plugin-market-bundle": "workspace:*", + "@deepseek-ai/dsh-creation-recommendations": "workspace:*", + "@deepseek-ai/dsh-plugin-recommendations": "workspace:*" }, "devDependencies": { "@deepseek-ai/dsh-skill-badge": "workspace:*", diff --git a/apps/web/tests/creation-recommendations.e2e.ts b/apps/web/tests/creation-recommendations.e2e.ts new file mode 100644 index 0000000000..2aa19d0945 --- /dev/null +++ b/apps/web/tests/creation-recommendations.e2e.ts @@ -0,0 +1,130 @@ +/** 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' +import { chromium, type Browser, type Page } from 'playwright' +import { afterAll, beforeAll, describe, expect, it } from 'vitest' +import { + acknowledgeReloadConnectionLoss, assertFixtureInventory, captureStableAria, compareOrRefreshGolden, + fixtureUserPrompts, launchWebScaffold, watchConsole, type WebScaffold, +} from './scaffold.ts' +import { connectFreshWorkspace, newEnglishPage } from './support.ts' + +const directory = fileURLToPath(new URL('../../../snapshots/web/creation-recommendations', import.meta.url)) +const fixture = join(directory, 'session.v4.jsonl') + +describe('creation recommendation after an explicit wish', () => { + let scaffold: WebScaffold + let browser: Browser + let page: Page + + beforeAll(async () => { + scaffold = await launchWebScaffold({ replayFixture: fixture, compareReplaySession: true, + profile: { packages: [{ dir: fileURLToPath(new URL('../../../packages/experimental/creation-recommendations', import.meta.url)), enabled: true }] }, + }) + browser = await chromium.launch() + page = await newEnglishPage(browser) + }) + afterAll(async () => { await browser?.close(); await scaffold?.close() }) + + it('shows one card below the answer, lets the user change location, and restores a dismissal', async () => { + const tripwire = watchConsole(page) + await page.setViewportSize({ width: 620, height: 900 }) + await page.goto(scaffold.authenticatedUrl, { waitUntil: 'load' }) + await connectFreshWorkspace(page, scaffold.workspaceCwd) + const input = page.locator('[data-composer-input]').first() + const settled = scaffold.whenTurnSettled() + await input.fill(fixtureUserPrompts(await readFile(fixture, 'utf8'))[0]!) + await input.press('Enter') + await settled + 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-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-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') + await info.focus() + await info.press('Space') + expect(await card.getByRole('note').count()).toBe(0) + const infoWarnings = tripwire.warnings.length + await page.reload({ waitUntil: 'load' }) + await card.waitFor({ state: 'visible' }) + acknowledgeReloadConnectionLoss(tripwire, infoWarnings) + expect(await info.getAttribute('aria-expanded')).toBe('false') + await info.focus() + await info.press('Enter') + expect(await info.getAttribute('aria-expanded')).toBe('true') + expect(await card.getByRole('note').innerText()).toContain('large number of tokens') + 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-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: '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() + expect(previewText.endsWith('…')).toBe(true) + // The preview keeps about 260 Latin-letter widths before the ellipsis; a trailing space may be trimmed. + const previewLength = Array.from(new Intl.Segmenter(undefined, { granularity: 'grapheme' }).segment(previewText)).length + expect(previewLength).toBeGreaterThan(200) + expect(previewLength).toBeLessThanOrEqual(261) + 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-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) + await compareOrRefreshGolden(join(directory, 'location-menu.expected.md'), await captureStableAria(page, '[role="menu"]', scaffold.workspaceCwd), scaffold.mode) + await page.getByRole('menuitem').first().press('Escape') + expect(await page.getByRole('menu').count()).toBe(0) + expect(await location.evaluate(element => element === document.activeElement)).toBe(true) + await location.click() + await page.getByRole('menuitem', { name: /Write your own/ }).click() + const custom = card.getByRole('textbox', { name: 'Where should it appear?', exact: true }) + 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-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-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() + const warnings = tripwire.warnings.length + await page.reload({ waitUntil: 'load' }) + 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: '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-action-card]', scaffold.workspaceCwd), scaffold.mode) + const statusSettled = scaffold.whenTurnSettled() + await input.fill(fixtureUserPrompts(await readFile(fixture, 'utf8'))[1]!) + await input.press('Enter') + await statusSettled + await page.getByText('The counter proposal was dismissed. Creation never started.', { exact: true }).waitFor() + expect(await card.count()).toBe(1) + expect(await card.getByRole('button', { name: 'Create', exact: true }).count()).toBe(0) + expect(tripwire.pageErrors).toEqual([]) + expect(tripwire.warnings).toEqual([]) + if (scaffold.mode === 'replay') await assertFixtureInventory(directory, [ + 'session.v4.jsonl', 'offered.expected.md', 'info.expected.md', 'expanded.expected.md', 'options.expected.md', 'dismissed.expected.md', 'location-menu.expected.md', 'custom-location.expected.md', 'system-prompt.expected.md', 'tool-schemas.expected.json', + ]) + }) +}) diff --git a/apps/web/tests/plugin-manager.e2e.ts b/apps/web/tests/plugin-manager.e2e.ts index a85297938f..1e888e2e12 100644 --- a/apps/web/tests/plugin-manager.e2e.ts +++ b/apps/web/tests/plugin-manager.e2e.ts @@ -863,6 +863,8 @@ describe('web e2e: plugin manager', () => { ['@deepseek-ai/dsh-experimental-voice-input-bundle', 1], ['@deepseek-ai/dsh-experimental-plugin-market-bundle', 1], ['@deepseek-ai/dsh-experimental-auto-review', 1], + ['@deepseek-ai/dsh-creation-recommendations', 1], + ['@deepseek-ai/dsh-plugin-recommendations', 1], ['@deepseek-ai/dsh-experimental-inspector-profile', 1], ['@deepseek-ai/dsh-cua-bundle', 1], ['@deepseek-ai/dsh-experimental-tool-worktree', 1], diff --git a/apps/web/tests/plugin-recommendations.e2e.ts b/apps/web/tests/plugin-recommendations.e2e.ts new file mode 100644 index 0000000000..8e870431fd --- /dev/null +++ b/apps/web/tests/plugin-recommendations.e2e.ts @@ -0,0 +1,87 @@ +/** Replay model output through the shipped Web profile and decide its real propose_action card. */ +import { readFile } from 'node:fs/promises' +import { join } from 'node:path' +import { fileURLToPath } from 'node:url' +import { chromium, type Browser, type Page } from 'playwright' +import { afterAll, beforeAll, describe, expect, it } from 'vitest' +import type {} from '@deepseek-ai/dsh-plugin-manager' +import { + acknowledgeReloadConnectionLoss, assertFixtureInventory, captureStableAria, compareOrRefreshGolden, + fixtureUserPrompts, launchWebScaffold, watchConsole, type WebScaffold, +} from './scaffold.ts' +import { connectFreshWorkspace, newEnglishPage } from './support.ts' + +const directory = fileURLToPath(new URL('../../../snapshots/web/plugin-recommendations', import.meta.url)) +const fixture = join(directory, 'session.v4.jsonl') +const voiceBundle = '@deepseek-ai/dsh-experimental-voice-input-bundle' + +describe('official plugin recommendation in a conversation', () => { + let scaffold: WebScaffold + let browser: Browser + let page: Page + + beforeAll(async () => { + scaffold = await launchWebScaffold({ replayFixture: fixture, compareReplaySession: true, + profile: { packages: [{ dir: fileURLToPath(new URL('../../../packages/experimental/plugin-recommendations', import.meta.url)), enabled: true }] }, + }) + browser = await chromium.launch() + page = await newEnglishPage(browser) + }) + + afterAll(async () => { + await browser?.close() + await scaffold?.close() + }) + + it('offers the propose_action call as one card below the answer, and reports a dismissal on the next request', async () => { + const tripwire = watchConsole(page) + await page.goto(scaffold.authenticatedUrl, { waitUntil: 'load' }) + await connectFreshWorkspace(page, scaffold.workspaceCwd) + const input = page.locator('[data-composer-input]').first() + const settled = scaffold.whenTurnSettled() + const prompts = fixtureUserPrompts(await readFile(fixture, 'utf8')) + await input.fill(prompts[0]!) + await input.press('Enter') + await settled + 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-action-card][data-action="enable_official_plugin"]').count()).toBe(1) + const answer = page.getByText('Voice input transcribes your recordings locally into the composer. You can enable it 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 page.getByText('enable_official_plugin').count()).toBe(0) + await card.getByText('Voice input', { exact: true }).waitFor() + expect(await card.getByRole('button', { name: 'Enable', exact: true }).isEnabled()).toBe(true) + expect((await scaffold.ctx.pluginManager.listBundles()).find(bundle => bundle.name === voiceBundle)?.enabled).toBe(false) + await compareOrRefreshGolden(join(directory, 'offered.expected.md'), await captureStableAria(page, '[data-action-card]', scaffold.workspaceCwd), scaffold.mode) + await page.setViewportSize({ width: 620, height: 900 }) + 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() + const warnings = tripwire.warnings.length + await page.reload({ waitUntil: 'load' }) + await card.getByRole('status').getByText('Dismissed', { exact: true }).waitFor() + acknowledgeReloadConnectionLoss(tripwire, warnings) + expect(await card.count()).toBe(1) + expect(await card.getByRole('button').count()).toBe(1) + expect((await scaffold.ctx.pluginManager.listBundles()).find(bundle => bundle.name === voiceBundle)?.enabled).toBe(false) + await compareOrRefreshGolden(join(directory, 'dismissed.expected.md'), await captureStableAria(page, '[data-action-card]', scaffold.workspaceCwd), scaffold.mode) + const statusSettled = scaffold.whenTurnSettled() + await input.fill(prompts[1]!) + await input.press('Enter') + await statusSettled + await page.getByText('Voice input is not enabled: you dismissed the card.', { exact: true }).waitFor() + expect(await card.count()).toBe(1) + await card.getByRole('button', { name: 'View details: Voice input', exact: true }).click() + await page.locator(`[data-plugin-detail="${voiceBundle}"]`).waitFor({ state: 'visible' }) + expect((await scaffold.ctx.pluginManager.listBundles()).find(bundle => bundle.name === voiceBundle)?.enabled).toBe(false) + expect(tripwire.pageErrors).toEqual([]) + expect(tripwire.warnings).toEqual([]) + if (scaffold.mode === 'replay') await assertFixtureInventory(directory, [ + 'session.v4.jsonl', 'offered.expected.md', 'dismissed.expected.md', 'system-prompt.expected.md', 'tool-schemas.expected.json', + ]) + }) +}) diff --git a/apps/web/tsconfig.json b/apps/web/tsconfig.json index 7a5f0bd147..0336b8cf1a 100644 --- a/apps/web/tsconfig.json +++ b/apps/web/tsconfig.json @@ -23,6 +23,8 @@ // this client-registered project must not also hold them (one program // cannot see both sides of the cordis Context merges). "exclude": [ + "tests/creation-recommendations.e2e.ts", + "tests/plugin-recommendations.e2e.ts", "tests/japanese-plugin-metadata.e2e.ts", "tests/additional-locales.e2e.ts", "tests/rich-text-quotes.e2e.ts", diff --git a/docs/capability-seams.i18n.yaml b/docs/capability-seams.i18n.yaml index e2cd201a0d..fba6972189 100644 --- a/docs/capability-seams.i18n.yaml +++ b/docs/capability-seams.i18n.yaml @@ -6,5 +6,5 @@ en: 41d772192075e133 zh: 5ef3da69b4de1d8a /capability-seams-and-core-services: - en: f2546ac3454d220d - zh: 642a473c0405e81f + en: 506d61dfa8c71186 + zh: 46f05c4ef2f5c322 diff --git a/docs/capability-seams.md b/docs/capability-seams.md index 07f93f002f..d28f3f3aee 100644 --- a/docs/capability-seams.md +++ b/docs/capability-seams.md @@ -7,6 +7,13 @@ A service can be a core spine service, a swappable capability seam, a bundle/com ```mermaid flowchart LR + pkg_action_cards["action-cards"] + svc_actionCards["ctx.actionCards
Optional action cards"] + pkg_creation_recommendations["creation-recommendations"] + pkg_client_ui_action_cards["client-ui-action-cards"] + pkg_plugin_recommendations["plugin-recommendations"] + svc_pluginRecommendations["ctx.pluginRecommendations
Explicit-need plugin recommendations"] + pkg_client_ui_plugin_recommendations["client-ui-plugin-recommendations"] pkg_headless["headless"] svc_headlessStartup["ctx.headlessStartup
Headless invocation options"] pkg_experimental_client_ui_cot_translation["experimental-client-ui-cot-translation"] @@ -298,6 +305,7 @@ flowchart LR pkg_cordis_host_runner["cordis-host-runner"] svc_dynamicCordisRunner["ctx.dynamicCordisRunner
Dynamic Cordis package host runner"] svc_cordisInspect["ctx.cordisInspect
Dynamic Cordis inspect registry"] + pkg_action_cards --> svc_actionCards pkg_agent --> svc_agents pkg_agent_default_model --> svc_agentDefaultModel pkg_agent_loop --> svc_agentLoop @@ -388,6 +396,7 @@ flowchart LR pkg_plugin_manager --> svc_pluginManager pkg_plugin_market --> svc_pluginMarket pkg_plugin_package_inventory_deepseek --> svc_deepseekLlmApiExtensions + pkg_plugin_recommendations --> svc_pluginRecommendations pkg_ptc_runtime --> svc_ptcRuntime pkg_ptc_runtime_node --> svc_ptcRuntime pkg_pwsh_local --> svc_shell @@ -457,6 +466,8 @@ flowchart LR pkg_working_directory --> svc_workingDirectory pkg_workspace --> svc_workspaceRegistry pkg_workspace_changes --> svc_workspaceChanges + svc_actionCards --> pkg_client_ui_action_cards + svc_actionCards --> pkg_creation_recommendations svc_agentDefaultModel --> pkg_api_session_controller svc_agentDefaultModel --> pkg_headless svc_agentLoop --> pkg_base @@ -515,6 +526,7 @@ flowchart LR svc_pluginManager --> pkg_ui_settings_plugin_inventory svc_pluginMarket --> pkg_client_ui_plugin_market svc_pluginMarket --> pkg_plugin_market + svc_pluginRecommendations --> pkg_client_ui_plugin_recommendations svc_pluginRegistryProbe --> pkg_client_ui_plugin_manager svc_profileContext --> pkg_plugin_manager svc_ptcRuntime --> pkg_tool_executor_ptc @@ -619,6 +631,8 @@ flowchart LR | ctx key | Role | Owner | Implementations | Direct consumers | Companion plugins | Note | | --- | --- | --- | --- | --- | --- | --- | +| `ctx.actionCards` | `service` | [`action-cards`](../packages/experimental/action-cards) | - | [`creation-recommendations`](../packages/experimental/creation-recommendations), [`client-ui-action-cards`](../packages/experimental/client-ui-action-cards) | - | Owns the card tool, card state, user actions and status notices; card kinds register their actions and handlers. | +| `ctx.pluginRecommendations` | `service` | [`plugin-recommendations`](../packages/experimental/plugin-recommendations) | - | [`client-ui-plugin-recommendations`](../packages/experimental/client-ui-plugin-recommendations) | - | Reads the current catalog text of an official plugin card and enables the plugin through the profile plugin manager after a click. | | `ctx.headlessStartup` | `core` | [`headless`](../packages/bundle/headless) | - | [`headless`](../packages/bundle/headless) | - | Supplies parsed task and run options to the headless runner and its sandbox-policy configuration. | | `ctx.cotTranslation` | `service` | [`experimental-client-ui-cot-translation`](../packages/experimental/client-ui-cot-translation) | - | - | - | Serves reasoning translations, reuses saved results, and joins ordinary Session activation for uncached inactive Sessions. | | `ctx.translator` | `service` | [`experimental-translator`](../packages/experimental/translator) | - | [`experimental-client-ui-cot-translation`](../packages/experimental/client-ui-cot-translation) | - | Resolves Google or Bing requests and retains shared experimental records through the active Session writer. | diff --git a/docs/capability-seams.zh.md b/docs/capability-seams.zh.md index 1b10ace20d..8a2963f3cc 100644 --- a/docs/capability-seams.zh.md +++ b/docs/capability-seams.zh.md @@ -9,6 +9,13 @@ ```mermaid flowchart LR + pkg_action_cards["action-cards"] + svc_actionCards["ctx.actionCards
Optional action cards"] + pkg_creation_recommendations["creation-recommendations"] + pkg_client_ui_action_cards["client-ui-action-cards"] + pkg_plugin_recommendations["plugin-recommendations"] + svc_pluginRecommendations["ctx.pluginRecommendations
Explicit-need plugin recommendations"] + pkg_client_ui_plugin_recommendations["client-ui-plugin-recommendations"] pkg_headless["headless"] svc_headlessStartup["ctx.headlessStartup
Headless invocation options"] pkg_experimental_client_ui_cot_translation["experimental-client-ui-cot-translation"] @@ -300,6 +307,7 @@ flowchart LR pkg_cordis_host_runner["cordis-host-runner"] svc_dynamicCordisRunner["ctx.dynamicCordisRunner
Dynamic Cordis package host runner"] svc_cordisInspect["ctx.cordisInspect
Dynamic Cordis inspect registry"] + pkg_action_cards --> svc_actionCards pkg_agent --> svc_agents pkg_agent_default_model --> svc_agentDefaultModel pkg_agent_loop --> svc_agentLoop @@ -390,6 +398,7 @@ flowchart LR pkg_plugin_manager --> svc_pluginManager pkg_plugin_market --> svc_pluginMarket pkg_plugin_package_inventory_deepseek --> svc_deepseekLlmApiExtensions + pkg_plugin_recommendations --> svc_pluginRecommendations pkg_ptc_runtime --> svc_ptcRuntime pkg_ptc_runtime_node --> svc_ptcRuntime pkg_pwsh_local --> svc_shell @@ -459,6 +468,8 @@ flowchart LR pkg_working_directory --> svc_workingDirectory pkg_workspace --> svc_workspaceRegistry pkg_workspace_changes --> svc_workspaceChanges + svc_actionCards --> pkg_client_ui_action_cards + svc_actionCards --> pkg_creation_recommendations svc_agentDefaultModel --> pkg_api_session_controller svc_agentDefaultModel --> pkg_headless svc_agentLoop --> pkg_base @@ -517,6 +528,7 @@ flowchart LR svc_pluginManager --> pkg_ui_settings_plugin_inventory svc_pluginMarket --> pkg_client_ui_plugin_market svc_pluginMarket --> pkg_plugin_market + svc_pluginRecommendations --> pkg_client_ui_plugin_recommendations svc_pluginRegistryProbe --> pkg_client_ui_plugin_manager svc_profileContext --> pkg_plugin_manager svc_ptcRuntime --> pkg_tool_executor_ptc @@ -621,6 +633,8 @@ flowchart LR | ctx 键 | 角色 | 所属包 | 实现 | 直接消费方 | 配套插件 | 说明 | | --- | --- | --- | --- | --- | --- | --- | +| `ctx.actionCards` | `service` | [`action-cards`](../packages/experimental/action-cards) | - | [`creation-recommendations`](../packages/experimental/creation-recommendations), [`client-ui-action-cards`](../packages/experimental/client-ui-action-cards) | - | 负责卡片工具、卡片状态、用户操作和状态通知;各卡片种类注册自己的动作和处理器。 | +| `ctx.pluginRecommendations` | `service` | [`plugin-recommendations`](../packages/experimental/plugin-recommendations) | - | [`client-ui-plugin-recommendations`](../packages/experimental/client-ui-plugin-recommendations) | - | 读取官方插件卡片的当前目录文本,并在用户点击后通过配置插件管理器启用该插件。 | | `ctx.headlessStartup` | `core` | [`headless`](../packages/bundle/headless) | - | [`headless`](../packages/bundle/headless) | - | 将解析后的任务和运行选项提供给无界面运行器及其 sandbox-policy 配置。 | | `ctx.cotTranslation` | `service` | [`experimental-client-ui-cot-translation`](../packages/experimental/client-ui-cot-translation) | - | - | - | 为展开的思考内容提供有界的 Session 翻译请求,不改变模型可见历史。 | | `ctx.translator` | `service` | [`experimental-translator`](../packages/experimental/translator) | - | [`experimental-client-ui-cot-translation`](../packages/experimental/client-ui-cot-translation) | - | 解析 Google 或 Bing 请求,并可选保留统一的实验性 Session 记录。 | diff --git a/docs/config-catalog.md b/docs/config-catalog.md index 58536f99b4..35fd5dc088 100644 --- a/docs/config-catalog.md +++ b/docs/config-catalog.md @@ -33,6 +33,21 @@ export interface AcpConfig { ``` + + + +## `@deepseek-ai/dsh-action-cards` + +- `inject`: `tools` · `agents` · `sessionProjections` · `remoteAccess` +- `refs`: `z` (`zod`) +- `source`: [`packages/experimental/action-cards/src/index.ts:23`](../packages/experimental/action-cards/src/index.ts) + +```ts config-catalog +/** Validated card limits after schema defaults. */ +export type Config = z.infer +``` + + @@ -4689,6 +4704,7 @@ These load from a `cordis.yml` entry with no `config:` block; they declare no co | `@deepseek-ai/dsh-client-locale` | — | [`packages/client/locale/src/index.ts`](../packages/client/locale/src/index.ts) | | `@deepseek-ai/dsh-client-modules` | `loader` | [`packages/client/modules/src/index.ts`](../packages/client/modules/src/index.ts) | | `@deepseek-ai/dsh-client-resources` | — | [`packages/client/resources/src/index.ts`](../packages/client/resources/src/index.ts) | +| `@deepseek-ai/dsh-client-ui-action-cards` | — | [`packages/experimental/client-ui-action-cards/src/index.ts`](../packages/experimental/client-ui-action-cards/src/index.ts) | | `@deepseek-ai/dsh-client-ui-agent-preset` | — | [`packages/client/ui-agent-preset/src/index.ts`](../packages/client/ui-agent-preset/src/index.ts) | | `@deepseek-ai/dsh-client-ui-approval` | — | [`packages/client/ui-approval/src/index.ts`](../packages/client/ui-approval/src/index.ts) | | `@deepseek-ai/dsh-client-ui-attachment` | — | [`packages/client/ui-attachment/src/index.ts`](../packages/client/ui-attachment/src/index.ts) | @@ -4697,6 +4713,7 @@ These load from a `cordis.yml` entry with no `config:` block; they declare no co | `@deepseek-ai/dsh-client-ui-commands` | — | [`packages/client/ui-commands/src/index.ts`](../packages/client/ui-commands/src/index.ts) | | `@deepseek-ai/dsh-client-ui-conversation` | — | [`packages/client/ui-conversation/src/index.ts`](../packages/client/ui-conversation/src/index.ts) | | `@deepseek-ai/dsh-client-ui-cordis` | — | [`packages/extensions/ui-cordis/src/index.ts`](../packages/extensions/ui-cordis/src/index.ts) | +| `@deepseek-ai/dsh-client-ui-creation-recommendations` | — | [`packages/experimental/client-ui-creation-recommendations/src/index.ts`](../packages/experimental/client-ui-creation-recommendations/src/index.ts) | | `@deepseek-ai/dsh-client-ui-deepseek-model-label-1017` | — | [`packages/client/ui-deepseek-model-label-1017/src/index.ts`](../packages/client/ui-deepseek-model-label-1017/src/index.ts) | | `@deepseek-ai/dsh-client-ui-deliverables` | `connection` · `sessionQuery` · `sessionController` · `workspaceFiles` · `fs` · `sandboxPolicy` · `workspaceChanges` | [`packages/client/ui-deliverables/src/index.ts`](../packages/client/ui-deliverables/src/index.ts) | | `@deepseek-ai/dsh-client-ui-directory-picker-browse` | — | [`packages/client/ui-directory-picker-browse/src/index.ts`](../packages/client/ui-directory-picker-browse/src/index.ts) | @@ -4713,6 +4730,7 @@ These load from a `cordis.yml` entry with no `config:` block; they declare no co | `@deepseek-ai/dsh-client-ui-permission-presets` | — | [`packages/client/ui-permission-presets/src/index.ts`](../packages/client/ui-permission-presets/src/index.ts) | | `@deepseek-ai/dsh-client-ui-plan` | — | [`packages/client/ui-plan/src/index.ts`](../packages/client/ui-plan/src/index.ts) | | `@deepseek-ai/dsh-client-ui-plugin-market` | — | [`packages/client/ui-plugin-market/src/index.ts`](../packages/client/ui-plugin-market/src/index.ts) | +| `@deepseek-ai/dsh-client-ui-plugin-recommendations` | — | [`packages/experimental/client-ui-plugin-recommendations/src/index.ts`](../packages/experimental/client-ui-plugin-recommendations/src/index.ts) | | `@deepseek-ai/dsh-client-ui-reference` | — | [`packages/client/ui-reference/src/index.ts`](../packages/client/ui-reference/src/index.ts) | | `@deepseek-ai/dsh-client-ui-renderer` | — | [`packages/client/ui-renderer/src/index.ts`](../packages/client/ui-renderer/src/index.ts) | | `@deepseek-ai/dsh-client-ui-schedule` | — | [`packages/client/ui-schedule/src/index.ts`](../packages/client/ui-schedule/src/index.ts) | @@ -4748,6 +4766,7 @@ These load from a `cordis.yml` entry with no `config:` block; they declare no co | `@deepseek-ai/dsh-computer-use` | — | [`packages/computer-use/computer-use/src/index.ts`](../packages/computer-use/computer-use/src/index.ts) | | `@deepseek-ai/dsh-config-editor` | `loader` · `profileContext` | [`packages/boot/config-editor/src/index.ts`](../packages/boot/config-editor/src/index.ts) | | `@deepseek-ai/dsh-cordis-client-runner` | — | [`packages/extensions/cordis-client-runner/src/index.ts`](../packages/extensions/cordis-client-runner/src/index.ts) | +| `@deepseek-ai/dsh-creation-recommendations` | `tools` · `pluginManager` · `agents` · `subagents` · `profileContext` · `skills` · `actionCards` | [`packages/experimental/creation-recommendations/src/index.ts`](../packages/experimental/creation-recommendations/src/index.ts) | | `@deepseek-ai/dsh-cua` | — | [`packages/experimental/cua/src/index.ts`](../packages/experimental/cua/src/index.ts) | | `@deepseek-ai/dsh-deepseek-llm-api-extensions` | — | [`packages/llm/deepseek-llm-api-extensions/src/index.ts`](../packages/llm/deepseek-llm-api-extensions/src/index.ts) | | `@deepseek-ai/dsh-deliverable-file-references` | `systemPrompt` | [`packages/deliverables/deliverable-file-references/src/index.ts`](../packages/deliverables/deliverable-file-references/src/index.ts) | @@ -4770,6 +4789,7 @@ These load from a `cordis.yml` entry with no `config:` block; they declare no co | `@deepseek-ai/dsh-mcp-resources` | `tools` | [`packages/mcp/mcp-resources/src/index.ts`](../packages/mcp/mcp-resources/src/index.ts) | | `@deepseek-ai/dsh-message-quotes` | — | [`packages/session/message-quotes/src/index.ts`](../packages/session/message-quotes/src/index.ts) | | `@deepseek-ai/dsh-otel` | — | [`packages/telemetry/otel/src/index.ts`](../packages/telemetry/otel/src/index.ts) | +| `@deepseek-ai/dsh-plugin-recommendations` | `pluginManager` · `agents` · `skills` · `actionCards` | [`packages/experimental/plugin-recommendations/src/index.ts`](../packages/experimental/plugin-recommendations/src/index.ts) | | `@deepseek-ai/dsh-sandbox-ssh` | `ssh` | [`packages/ssh/sandbox-ssh/src/index.ts`](../packages/ssh/sandbox-ssh/src/index.ts) | | `@deepseek-ai/dsh-session` | — | [`packages/core/session/src/index.ts`](../packages/core/session/src/index.ts) | | `@deepseek-ai/dsh-session-checkpoint-policy` | `llm` · `sessionPersistence` · `sessions` · `tools` | [`packages/session/session-checkpoint-policy/src/index.ts`](../packages/session/session-checkpoint-policy/src/index.ts) | diff --git a/docs/config-catalog.zh.md b/docs/config-catalog.zh.md index c7cc4b27f9..a5161531c2 100644 --- a/docs/config-catalog.zh.md +++ b/docs/config-catalog.zh.md @@ -35,6 +35,21 @@ export interface AcpConfig { ``` + + + +## `@deepseek-ai/dsh-action-cards` + +- `inject`: `tools` · `agents` · `sessionProjections` · `remoteAccess` +- `refs`: `z` (`zod`) +- `source`: [`packages/experimental/action-cards/src/index.ts:23`](../packages/experimental/action-cards/src/index.ts) + +```ts config-catalog +/** Validated card limits after schema defaults. */ +export type Config = z.infer +``` + + @@ -4691,6 +4706,7 @@ export interface Config { | `@deepseek-ai/dsh-client-locale` | — | [`packages/client/locale/src/index.ts`](../packages/client/locale/src/index.ts) | | `@deepseek-ai/dsh-client-modules` | `loader` | [`packages/client/modules/src/index.ts`](../packages/client/modules/src/index.ts) | | `@deepseek-ai/dsh-client-resources` | — | [`packages/client/resources/src/index.ts`](../packages/client/resources/src/index.ts) | +| `@deepseek-ai/dsh-client-ui-action-cards` | — | [`packages/experimental/client-ui-action-cards/src/index.ts`](../packages/experimental/client-ui-action-cards/src/index.ts) | | `@deepseek-ai/dsh-client-ui-agent-preset` | — | [`packages/client/ui-agent-preset/src/index.ts`](../packages/client/ui-agent-preset/src/index.ts) | | `@deepseek-ai/dsh-client-ui-approval` | — | [`packages/client/ui-approval/src/index.ts`](../packages/client/ui-approval/src/index.ts) | | `@deepseek-ai/dsh-client-ui-attachment` | — | [`packages/client/ui-attachment/src/index.ts`](../packages/client/ui-attachment/src/index.ts) | @@ -4699,6 +4715,7 @@ export interface Config { | `@deepseek-ai/dsh-client-ui-commands` | — | [`packages/client/ui-commands/src/index.ts`](../packages/client/ui-commands/src/index.ts) | | `@deepseek-ai/dsh-client-ui-conversation` | — | [`packages/client/ui-conversation/src/index.ts`](../packages/client/ui-conversation/src/index.ts) | | `@deepseek-ai/dsh-client-ui-cordis` | — | [`packages/extensions/ui-cordis/src/index.ts`](../packages/extensions/ui-cordis/src/index.ts) | +| `@deepseek-ai/dsh-client-ui-creation-recommendations` | — | [`packages/experimental/client-ui-creation-recommendations/src/index.ts`](../packages/experimental/client-ui-creation-recommendations/src/index.ts) | | `@deepseek-ai/dsh-client-ui-deepseek-model-label-1017` | — | [`packages/client/ui-deepseek-model-label-1017/src/index.ts`](../packages/client/ui-deepseek-model-label-1017/src/index.ts) | | `@deepseek-ai/dsh-client-ui-deliverables` | `connection` · `sessionQuery` · `sessionController` · `workspaceFiles` · `fs` · `sandboxPolicy` · `workspaceChanges` | [`packages/client/ui-deliverables/src/index.ts`](../packages/client/ui-deliverables/src/index.ts) | | `@deepseek-ai/dsh-client-ui-directory-picker-browse` | — | [`packages/client/ui-directory-picker-browse/src/index.ts`](../packages/client/ui-directory-picker-browse/src/index.ts) | @@ -4715,6 +4732,7 @@ export interface Config { | `@deepseek-ai/dsh-client-ui-permission-presets` | — | [`packages/client/ui-permission-presets/src/index.ts`](../packages/client/ui-permission-presets/src/index.ts) | | `@deepseek-ai/dsh-client-ui-plan` | — | [`packages/client/ui-plan/src/index.ts`](../packages/client/ui-plan/src/index.ts) | | `@deepseek-ai/dsh-client-ui-plugin-market` | — | [`packages/client/ui-plugin-market/src/index.ts`](../packages/client/ui-plugin-market/src/index.ts) | +| `@deepseek-ai/dsh-client-ui-plugin-recommendations` | — | [`packages/experimental/client-ui-plugin-recommendations/src/index.ts`](../packages/experimental/client-ui-plugin-recommendations/src/index.ts) | | `@deepseek-ai/dsh-client-ui-reference` | — | [`packages/client/ui-reference/src/index.ts`](../packages/client/ui-reference/src/index.ts) | | `@deepseek-ai/dsh-client-ui-renderer` | — | [`packages/client/ui-renderer/src/index.ts`](../packages/client/ui-renderer/src/index.ts) | | `@deepseek-ai/dsh-client-ui-schedule` | — | [`packages/client/ui-schedule/src/index.ts`](../packages/client/ui-schedule/src/index.ts) | @@ -4750,6 +4768,7 @@ export interface Config { | `@deepseek-ai/dsh-computer-use` | — | [`packages/computer-use/computer-use/src/index.ts`](../packages/computer-use/computer-use/src/index.ts) | | `@deepseek-ai/dsh-config-editor` | `loader` · `profileContext` | [`packages/boot/config-editor/src/index.ts`](../packages/boot/config-editor/src/index.ts) | | `@deepseek-ai/dsh-cordis-client-runner` | — | [`packages/extensions/cordis-client-runner/src/index.ts`](../packages/extensions/cordis-client-runner/src/index.ts) | +| `@deepseek-ai/dsh-creation-recommendations` | `tools` · `pluginManager` · `agents` · `subagents` · `profileContext` · `skills` · `actionCards` | [`packages/experimental/creation-recommendations/src/index.ts`](../packages/experimental/creation-recommendations/src/index.ts) | | `@deepseek-ai/dsh-cua` | — | [`packages/experimental/cua/src/index.ts`](../packages/experimental/cua/src/index.ts) | | `@deepseek-ai/dsh-deepseek-llm-api-extensions` | — | [`packages/llm/deepseek-llm-api-extensions/src/index.ts`](../packages/llm/deepseek-llm-api-extensions/src/index.ts) | | `@deepseek-ai/dsh-deliverable-file-references` | `systemPrompt` | [`packages/deliverables/deliverable-file-references/src/index.ts`](../packages/deliverables/deliverable-file-references/src/index.ts) | @@ -4772,6 +4791,7 @@ export interface Config { | `@deepseek-ai/dsh-mcp-resources` | `tools` | [`packages/mcp/mcp-resources/src/index.ts`](../packages/mcp/mcp-resources/src/index.ts) | | `@deepseek-ai/dsh-message-quotes` | — | [`packages/session/message-quotes/src/index.ts`](../packages/session/message-quotes/src/index.ts) | | `@deepseek-ai/dsh-otel` | — | [`packages/telemetry/otel/src/index.ts`](../packages/telemetry/otel/src/index.ts) | +| `@deepseek-ai/dsh-plugin-recommendations` | `pluginManager` · `agents` · `skills` · `actionCards` | [`packages/experimental/plugin-recommendations/src/index.ts`](../packages/experimental/plugin-recommendations/src/index.ts) | | `@deepseek-ai/dsh-sandbox-ssh` | `ssh` | [`packages/ssh/sandbox-ssh/src/index.ts`](../packages/ssh/sandbox-ssh/src/index.ts) | | `@deepseek-ai/dsh-session` | — | [`packages/core/session/src/index.ts`](../packages/core/session/src/index.ts) | | `@deepseek-ai/dsh-session-checkpoint-policy` | `llm` · `sessionPersistence` · `sessions` · `tools` | [`packages/session/session-checkpoint-policy/src/index.ts`](../packages/session/session-checkpoint-policy/src/index.ts) | diff --git a/docs/event-producer-consumer.md b/docs/event-producer-consumer.md index df88f674a9..47056513f2 100644 --- a/docs/event-producer-consumer.md +++ b/docs/event-producer-consumer.md @@ -11,13 +11,13 @@ This matrix shows which packages dispatch each harness-owned event and which pac | `agent-loop/config-start-failed` | `emit` | [`packages/core/agent-loop/src/index.ts:199`](../packages/core/agent-loop/src/index.ts) | [`agent-loop`](../packages/core/agent-loop) (`events.dispatch`) | - | | `agent-preset/selected` | `emit` | [`packages/preset/agent-preset-registry/src/types.ts:66`](../packages/preset/agent-preset-registry/src/types.ts) | [`agent-preset-registry`](../packages/preset/agent-preset-registry) (`emit`) | [`cua`](../packages/experimental/cua), `remotes` | | `agent/assistant-stream` | `emit` | [`packages/core/agent/src/runtime-types.ts:399`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`headless`](../packages/bundle/headless), `session-controller` | -| `agent/created` | `serial` | [`packages/core/agent/src/runtime-types.ts:290`](../packages/core/agent/src/runtime-types.ts) | [`agent`](../packages/core/agent) (`serial`) | `agent-team`, `browser-use-runtime`, `claude-code-mods`, [`cua`](../packages/experimental/cua), [`file-reference-local`](../packages/context/file-reference-local), [`goal`](../packages/goal/goal), [`goal-round-driver`](../packages/goal/goal-round-driver), [`hooks-claude-code`](../packages/experimental/hooks-claude-code), [`hooks-codex`](../packages/experimental/hooks-codex), [`loader-smoke`](../packages/test-support/loader-smoke), [`permission-presets`](../packages/interaction/permission-presets), `session-controller`, `tool-agent-team`, [`tool-subagent`](../packages/subagent/tool-subagent) | -| `agent/disposed` | `emit` | [`packages/core/agent/src/runtime-types.ts:299`](../packages/core/agent/src/runtime-types.ts) | [`agent`](../packages/core/agent) (`events.dispatch`) | [`agent-loop`](../packages/core/agent-loop), `claude-code-mods`, [`file-reference-local`](../packages/context/file-reference-local), [`goal-round-driver`](../packages/goal/goal-round-driver), `session-controller`, [`subagent`](../packages/subagent/subagent), `tool-agent-team`, [`tool-subagent`](../packages/subagent/tool-subagent) | +| `agent/created` | `serial` | [`packages/core/agent/src/runtime-types.ts:290`](../packages/core/agent/src/runtime-types.ts) | [`agent`](../packages/core/agent) (`serial`) | `agent-team`, `browser-use-runtime`, `claude-code-mods`, [`creation-recommendations`](../packages/experimental/creation-recommendations), [`cua`](../packages/experimental/cua), [`file-reference-local`](../packages/context/file-reference-local), [`goal`](../packages/goal/goal), [`goal-round-driver`](../packages/goal/goal-round-driver), [`hooks-claude-code`](../packages/experimental/hooks-claude-code), [`hooks-codex`](../packages/experimental/hooks-codex), [`loader-smoke`](../packages/test-support/loader-smoke), [`permission-presets`](../packages/interaction/permission-presets), `session-controller`, `tool-agent-team`, [`tool-subagent`](../packages/subagent/tool-subagent) | +| `agent/disposed` | `emit` | [`packages/core/agent/src/runtime-types.ts:299`](../packages/core/agent/src/runtime-types.ts) | [`agent`](../packages/core/agent) (`events.dispatch`) | [`action-cards`](../packages/experimental/action-cards), [`agent-loop`](../packages/core/agent-loop), `claude-code-mods`, [`creation-recommendations`](../packages/experimental/creation-recommendations), [`file-reference-local`](../packages/context/file-reference-local), [`goal-round-driver`](../packages/goal/goal-round-driver), `session-controller`, [`subagent`](../packages/subagent/subagent), `tool-agent-team`, [`tool-subagent`](../packages/subagent/tool-subagent) | | `agent/error` | `emit` | [`packages/core/agent/src/runtime-types.ts:429`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`acp`](../packages/acp/acp), [`deepseek-account`](../packages/credentials/deepseek-account), [`goal-round-driver`](../packages/goal/goal-round-driver), [`headless`](../packages/bundle/headless), `server`, `session-controller`, [`session-telemetry`](../packages/session/session-telemetry), [`subagent`](../packages/subagent/subagent) | | `agent/inbox/claimed` | `emit` | [`packages/core/agent/src/runtime-types.ts:328`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`acp`](../packages/acp/acp), [`goal-round-driver`](../packages/goal/goal-round-driver), [`subagent`](../packages/subagent/subagent), [`tool-jobs`](../packages/jobs/tool-jobs), [`user-questions`](../packages/interaction/user-questions) | | `agent/inbox/discarded` | `emit` | [`packages/core/agent/src/runtime-types.ts:336`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`goal-round-driver`](../packages/goal/goal-round-driver), [`subagent`](../packages/subagent/subagent), [`user-questions`](../packages/interaction/user-questions) | | `agent/inbox/inserted` | `emit` | [`packages/core/agent/src/runtime-types.ts:317`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`goal-round-driver`](../packages/goal/goal-round-driver), [`subagent`](../packages/subagent/subagent), [`tool-jobs`](../packages/jobs/tool-jobs) | -| `agent/pre-step` | `waterfall` | [`packages/core/agent/src/runtime-types.ts:350`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`waterfall`) | [`agent`](../packages/core/agent), [`agent-instructions`](../packages/context/agent-instructions), `claude-code-mods`, [`compaction-basic`](../packages/compaction/compaction-basic), [`goal-round-driver`](../packages/goal/goal-round-driver), [`hooks-claude-code`](../packages/experimental/hooks-claude-code), [`hooks-codex`](../packages/experimental/hooks-codex), [`plan-mode`](../packages/plan/plan-mode), [`repeat-tool-reminder`](../packages/guard/repeat-tool-reminder), [`session-checkpoint-policy`](../packages/session/session-checkpoint-policy), `session-controller`, [`session-reference`](../packages/context/session-reference), [`time-context`](../packages/context/time-context), [`tmux-context`](../packages/context/tmux-context), [`tool-skill`](../packages/skill/tool-skill) | +| `agent/pre-step` | `waterfall` | [`packages/core/agent/src/runtime-types.ts:350`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`waterfall`) | [`action-cards`](../packages/experimental/action-cards), [`agent`](../packages/core/agent), [`agent-instructions`](../packages/context/agent-instructions), `claude-code-mods`, [`compaction-basic`](../packages/compaction/compaction-basic), [`goal-round-driver`](../packages/goal/goal-round-driver), [`hooks-claude-code`](../packages/experimental/hooks-claude-code), [`hooks-codex`](../packages/experimental/hooks-codex), [`plan-mode`](../packages/plan/plan-mode), [`repeat-tool-reminder`](../packages/guard/repeat-tool-reminder), [`session-checkpoint-policy`](../packages/session/session-checkpoint-policy), `session-controller`, [`session-reference`](../packages/context/session-reference), [`time-context`](../packages/context/time-context), [`tmux-context`](../packages/context/tmux-context), [`tool-skill`](../packages/skill/tool-skill) | | `agent/request-error` | `waterfall` | [`packages/core/agent/src/runtime-types.ts:389`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`waterfall`) | [`compaction-basic`](../packages/compaction/compaction-basic), [`compaction-image-offload`](../packages/compaction/compaction-image-offload), [`llm-retry`](../packages/llm/llm-retry) | | `agent/status` | `emit` | [`packages/core/agent/src/runtime-types.ts:309`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`acp`](../packages/acp/acp), `agent-team`, [`compaction-basic`](../packages/compaction/compaction-basic), [`goal-round-driver`](../packages/goal/goal-round-driver), `server`, `session-controller` | | `agent/turn-stopping` | `serial` | [`packages/core/agent/src/runtime-types.ts:417`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`serial`) | [`hooks-claude-code`](../packages/experimental/hooks-claude-code), [`hooks-codex`](../packages/experimental/hooks-codex), [`workspace-changes`](../packages/deliverables/workspace-changes) | @@ -80,7 +80,7 @@ This matrix shows which packages dispatch each harness-owned event and which pac | `tools/post-execute` | `waterfall` | [`packages/core/tools/src/events.ts:41`](../packages/core/tools/src/events.ts) | [`tools`](../packages/core/tools) (`waterfall`) | `claude-code-mods`, [`hooks-claude-code`](../packages/experimental/hooks-claude-code), [`hooks-codex`](../packages/experimental/hooks-codex), [`repeat-tool-reminder`](../packages/guard/repeat-tool-reminder), [`spill-policy`](../packages/spill/spill-policy), [`tool-fs-search`](../packages/fs/tool-fs-search) | | `tools/pre-execute` | `waterfall` | [`packages/core/tools/src/events.ts:18`](../packages/core/tools/src/events.ts) | [`tools`](../packages/core/tools) (`waterfall`) | `auto-review`, [`hooks-claude-code`](../packages/experimental/hooks-claude-code), [`hooks-codex`](../packages/experimental/hooks-codex), [`tool-jobs`](../packages/jobs/tool-jobs), [`workspace-changes`](../packages/deliverables/workspace-changes) | | `tools/ptc-dispatch-log` | `waterfall` | [`packages/core/tool-executor-ptc/src/runtime-types.ts:46`](../packages/core/tool-executor-ptc/src/runtime-types.ts) | [`tool-executor-ptc`](../packages/core/tool-executor-ptc) (`waterfall`) | [`spill-policy`](../packages/spill/spill-policy) | -| `tools/result` | `emit` | [`packages/core/tools/src/events.ts:49`](../packages/core/tools/src/events.ts) | [`tools`](../packages/core/tools) (`events.dispatch`) | [`agent-instructions`](../packages/context/agent-instructions), `claude-code-mods`, [`subagent`](../packages/subagent/subagent), [`tool-present`](../packages/deliverables/tool-present) | +| `tools/result` | `emit` | [`packages/core/tools/src/events.ts:49`](../packages/core/tools/src/events.ts) | [`tools`](../packages/core/tools) (`events.dispatch`) | [`action-cards`](../packages/experimental/action-cards), [`agent-instructions`](../packages/context/agent-instructions), `claude-code-mods`, [`subagent`](../packages/subagent/subagent), [`tool-present`](../packages/deliverables/tool-present) | | `user-questions/request` | `waterfall` | [`packages/interaction/user-questions/src/types.ts:174`](../packages/interaction/user-questions/src/types.ts) | [`user-questions`](../packages/interaction/user-questions) (`waterfall`) | `remotes`, `server` | | `webserver/index-inject` | `emit` | [`packages/host/webserver/src/index.ts:44`](../packages/host/webserver/src/index.ts) | `webserver` (`emit`) | `connection`, `inspector`, `modules`, `remote-access`, `shortcuts`, `ui-sidebar-documentpreview`, `ui-theme` | | `workflow/agent-end` | `emit` | [`packages/workflow/workflow/src/index.ts:79`](../packages/workflow/workflow/src/index.ts) | [`workflow`](../packages/workflow/workflow) (`events.dispatch`) | [`tool-workflow`](../packages/workflow/tool-workflow) | diff --git a/docs/event-producer-consumer.zh.md b/docs/event-producer-consumer.zh.md index 598196a5ce..dc555041ff 100644 --- a/docs/event-producer-consumer.zh.md +++ b/docs/event-producer-consumer.zh.md @@ -13,13 +13,13 @@ | `agent-loop/config-start-failed` | `emit` | [`packages/core/agent-loop/src/index.ts:199`](../packages/core/agent-loop/src/index.ts) | [`agent-loop`](../packages/core/agent-loop) (`events.dispatch`) | - | | `agent-preset/selected` | `emit` | [`packages/preset/agent-preset-registry/src/types.ts:66`](../packages/preset/agent-preset-registry/src/types.ts) | [`agent-preset-registry`](../packages/preset/agent-preset-registry) (`emit`) | [`cua`](../packages/experimental/cua), `remotes` | | `agent/assistant-stream` | `emit` | [`packages/core/agent/src/runtime-types.ts:399`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`headless`](../packages/bundle/headless), `session-controller` | -| `agent/created` | `serial` | [`packages/core/agent/src/runtime-types.ts:290`](../packages/core/agent/src/runtime-types.ts) | [`agent`](../packages/core/agent) (`serial`) | `agent-team`, `browser-use-runtime`, `claude-code-mods`, [`cua`](../packages/experimental/cua), [`file-reference-local`](../packages/context/file-reference-local), [`goal`](../packages/goal/goal), [`goal-round-driver`](../packages/goal/goal-round-driver), [`hooks-claude-code`](../packages/experimental/hooks-claude-code), [`hooks-codex`](../packages/experimental/hooks-codex), [`loader-smoke`](../packages/test-support/loader-smoke), [`permission-presets`](../packages/interaction/permission-presets), `session-controller`, `tool-agent-team`, [`tool-subagent`](../packages/subagent/tool-subagent) | -| `agent/disposed` | `emit` | [`packages/core/agent/src/runtime-types.ts:299`](../packages/core/agent/src/runtime-types.ts) | [`agent`](../packages/core/agent) (`events.dispatch`) | [`agent-loop`](../packages/core/agent-loop), `claude-code-mods`, [`file-reference-local`](../packages/context/file-reference-local), [`goal-round-driver`](../packages/goal/goal-round-driver), `session-controller`, [`subagent`](../packages/subagent/subagent), `tool-agent-team`, [`tool-subagent`](../packages/subagent/tool-subagent) | +| `agent/created` | `serial` | [`packages/core/agent/src/runtime-types.ts:290`](../packages/core/agent/src/runtime-types.ts) | [`agent`](../packages/core/agent) (`serial`) | `agent-team`, `browser-use-runtime`, `claude-code-mods`, [`creation-recommendations`](../packages/experimental/creation-recommendations), [`cua`](../packages/experimental/cua), [`file-reference-local`](../packages/context/file-reference-local), [`goal`](../packages/goal/goal), [`goal-round-driver`](../packages/goal/goal-round-driver), [`hooks-claude-code`](../packages/experimental/hooks-claude-code), [`hooks-codex`](../packages/experimental/hooks-codex), [`loader-smoke`](../packages/test-support/loader-smoke), [`permission-presets`](../packages/interaction/permission-presets), `session-controller`, `tool-agent-team`, [`tool-subagent`](../packages/subagent/tool-subagent) | +| `agent/disposed` | `emit` | [`packages/core/agent/src/runtime-types.ts:299`](../packages/core/agent/src/runtime-types.ts) | [`agent`](../packages/core/agent) (`events.dispatch`) | [`action-cards`](../packages/experimental/action-cards), [`agent-loop`](../packages/core/agent-loop), `claude-code-mods`, [`creation-recommendations`](../packages/experimental/creation-recommendations), [`file-reference-local`](../packages/context/file-reference-local), [`goal-round-driver`](../packages/goal/goal-round-driver), `session-controller`, [`subagent`](../packages/subagent/subagent), `tool-agent-team`, [`tool-subagent`](../packages/subagent/tool-subagent) | | `agent/error` | `emit` | [`packages/core/agent/src/runtime-types.ts:429`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`acp`](../packages/acp/acp), [`deepseek-account`](../packages/credentials/deepseek-account), [`goal-round-driver`](../packages/goal/goal-round-driver), [`headless`](../packages/bundle/headless), `server`, `session-controller`, [`session-telemetry`](../packages/session/session-telemetry), [`subagent`](../packages/subagent/subagent) | | `agent/inbox/claimed` | `emit` | [`packages/core/agent/src/runtime-types.ts:328`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`acp`](../packages/acp/acp), [`goal-round-driver`](../packages/goal/goal-round-driver), [`subagent`](../packages/subagent/subagent), [`tool-jobs`](../packages/jobs/tool-jobs), [`user-questions`](../packages/interaction/user-questions) | | `agent/inbox/discarded` | `emit` | [`packages/core/agent/src/runtime-types.ts:336`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`goal-round-driver`](../packages/goal/goal-round-driver), [`subagent`](../packages/subagent/subagent), [`user-questions`](../packages/interaction/user-questions) | | `agent/inbox/inserted` | `emit` | [`packages/core/agent/src/runtime-types.ts:317`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`goal-round-driver`](../packages/goal/goal-round-driver), [`subagent`](../packages/subagent/subagent), [`tool-jobs`](../packages/jobs/tool-jobs) | -| `agent/pre-step` | `waterfall` | [`packages/core/agent/src/runtime-types.ts:350`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`waterfall`) | [`agent`](../packages/core/agent), [`agent-instructions`](../packages/context/agent-instructions), `claude-code-mods`, [`compaction-basic`](../packages/compaction/compaction-basic), [`goal-round-driver`](../packages/goal/goal-round-driver), [`hooks-claude-code`](../packages/experimental/hooks-claude-code), [`hooks-codex`](../packages/experimental/hooks-codex), [`plan-mode`](../packages/plan/plan-mode), [`repeat-tool-reminder`](../packages/guard/repeat-tool-reminder), [`session-checkpoint-policy`](../packages/session/session-checkpoint-policy), `session-controller`, [`session-reference`](../packages/context/session-reference), [`time-context`](../packages/context/time-context), [`tmux-context`](../packages/context/tmux-context), [`tool-skill`](../packages/skill/tool-skill) | +| `agent/pre-step` | `waterfall` | [`packages/core/agent/src/runtime-types.ts:350`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`waterfall`) | [`action-cards`](../packages/experimental/action-cards), [`agent`](../packages/core/agent), [`agent-instructions`](../packages/context/agent-instructions), `claude-code-mods`, [`compaction-basic`](../packages/compaction/compaction-basic), [`goal-round-driver`](../packages/goal/goal-round-driver), [`hooks-claude-code`](../packages/experimental/hooks-claude-code), [`hooks-codex`](../packages/experimental/hooks-codex), [`plan-mode`](../packages/plan/plan-mode), [`repeat-tool-reminder`](../packages/guard/repeat-tool-reminder), [`session-checkpoint-policy`](../packages/session/session-checkpoint-policy), `session-controller`, [`session-reference`](../packages/context/session-reference), [`time-context`](../packages/context/time-context), [`tmux-context`](../packages/context/tmux-context), [`tool-skill`](../packages/skill/tool-skill) | | `agent/request-error` | `waterfall` | [`packages/core/agent/src/runtime-types.ts:389`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`waterfall`) | [`compaction-basic`](../packages/compaction/compaction-basic), [`compaction-image-offload`](../packages/compaction/compaction-image-offload), [`llm-retry`](../packages/llm/llm-retry) | | `agent/status` | `emit` | [`packages/core/agent/src/runtime-types.ts:309`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`acp`](../packages/acp/acp), `agent-team`, [`compaction-basic`](../packages/compaction/compaction-basic), [`goal-round-driver`](../packages/goal/goal-round-driver), `server`, `session-controller` | | `agent/turn-stopping` | `serial` | [`packages/core/agent/src/runtime-types.ts:417`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`serial`) | [`hooks-claude-code`](../packages/experimental/hooks-claude-code), [`hooks-codex`](../packages/experimental/hooks-codex), [`workspace-changes`](../packages/deliverables/workspace-changes) | @@ -82,7 +82,7 @@ | `tools/post-execute` | `waterfall` | [`packages/core/tools/src/events.ts:41`](../packages/core/tools/src/events.ts) | [`tools`](../packages/core/tools) (`waterfall`) | `claude-code-mods`, [`hooks-claude-code`](../packages/experimental/hooks-claude-code), [`hooks-codex`](../packages/experimental/hooks-codex), [`repeat-tool-reminder`](../packages/guard/repeat-tool-reminder), [`spill-policy`](../packages/spill/spill-policy), [`tool-fs-search`](../packages/fs/tool-fs-search) | | `tools/pre-execute` | `waterfall` | [`packages/core/tools/src/events.ts:18`](../packages/core/tools/src/events.ts) | [`tools`](../packages/core/tools) (`waterfall`) | `auto-review`, [`hooks-claude-code`](../packages/experimental/hooks-claude-code), [`hooks-codex`](../packages/experimental/hooks-codex), [`tool-jobs`](../packages/jobs/tool-jobs), [`workspace-changes`](../packages/deliverables/workspace-changes) | | `tools/ptc-dispatch-log` | `waterfall` | [`packages/core/tool-executor-ptc/src/runtime-types.ts:46`](../packages/core/tool-executor-ptc/src/runtime-types.ts) | [`tool-executor-ptc`](../packages/core/tool-executor-ptc) (`waterfall`) | [`spill-policy`](../packages/spill/spill-policy) | -| `tools/result` | `emit` | [`packages/core/tools/src/events.ts:49`](../packages/core/tools/src/events.ts) | [`tools`](../packages/core/tools) (`events.dispatch`) | [`agent-instructions`](../packages/context/agent-instructions), `claude-code-mods`, [`subagent`](../packages/subagent/subagent), [`tool-present`](../packages/deliverables/tool-present) | +| `tools/result` | `emit` | [`packages/core/tools/src/events.ts:49`](../packages/core/tools/src/events.ts) | [`tools`](../packages/core/tools) (`events.dispatch`) | [`action-cards`](../packages/experimental/action-cards), [`agent-instructions`](../packages/context/agent-instructions), `claude-code-mods`, [`subagent`](../packages/subagent/subagent), [`tool-present`](../packages/deliverables/tool-present) | | `user-questions/request` | `waterfall` | [`packages/interaction/user-questions/src/types.ts:174`](../packages/interaction/user-questions/src/types.ts) | [`user-questions`](../packages/interaction/user-questions) (`waterfall`) | `remotes`, `server` | | `webserver/index-inject` | `emit` | [`packages/host/webserver/src/index.ts:44`](../packages/host/webserver/src/index.ts) | `webserver` (`emit`) | `connection`, `inspector`, `modules`, `remote-access`, `shortcuts`, `ui-sidebar-documentpreview`, `ui-theme` | | `workflow/agent-end` | `emit` | [`packages/workflow/workflow/src/index.ts:79`](../packages/workflow/workflow/src/index.ts) | [`workflow`](../packages/workflow/workflow) (`events.dispatch`) | [`tool-workflow`](../packages/workflow/tool-workflow) | diff --git a/docs/experimental-persistence-catalog.i18n.yaml b/docs/experimental-persistence-catalog.i18n.yaml index a65825ab71..7dd24c2aa4 100644 --- a/docs/experimental-persistence-catalog.i18n.yaml +++ b/docs/experimental-persistence-catalog.i18n.yaml @@ -6,5 +6,5 @@ en: a11f106978c9dce7 zh: a11f106978c9dce7 /experimental-plugin-record-catalog: - en: 84dbc9c06ab92877 - zh: b82ef2a46292ab0e + en: cb931db3b4bcbe33 + zh: aca9cfecfdee34c0 diff --git a/docs/experimental-persistence-catalog.md b/docs/experimental-persistence-catalog.md index 0868849c51..b6d3a73de4 100644 --- a/docs/experimental-persistence-catalog.md +++ b/docs/experimental-persistence-catalog.md @@ -13,5 +13,6 @@ Shared Session event declarations and resolved schemas appear in the [Session pe | Record | Owner | Purpose | Payload and source | |---|---|---|---| +| `plugin:action-cards/decision` | `@deepseek-ai/dsh-action-cards` | Dismissals and kind-defined progress of action cards. | ActionCardDecision · [`packages/experimental/action-cards/src/types.ts`](../packages/experimental/action-cards/src/types.ts) | | `plugin:translator/request` | `@deepseek-ai/dsh-experimental-translator` | Provider-independent translation input, retained before dispatch. | TranslationRequestRecord · [`packages/experimental/translator/src/types.ts`](../packages/experimental/translator/src/types.ts) | | `plugin:translator/result` | `@deepseek-ai/dsh-experimental-translator` | Successful translation, retained before it is returned to the consumer. | TranslationResultRecord · [`packages/experimental/translator/src/types.ts`](../packages/experimental/translator/src/types.ts) | diff --git a/docs/experimental-persistence-catalog.zh.md b/docs/experimental-persistence-catalog.zh.md index 32cb1d0533..c32cdcabd0 100644 --- a/docs/experimental-persistence-catalog.zh.md +++ b/docs/experimental-persistence-catalog.zh.md @@ -13,5 +13,6 @@ | 记录 | 所属包 | 用途 | 载荷与源码 | |---|---|---|---| +| `plugin:action-cards/decision` | `@deepseek-ai/dsh-action-cards` | Dismissals and kind-defined progress of action cards. | ActionCardDecision · [`packages/experimental/action-cards/src/types.ts`](../packages/experimental/action-cards/src/types.ts) | | `plugin:translator/request` | `@deepseek-ai/dsh-experimental-translator` | Provider-independent translation input, retained before dispatch. | TranslationRequestRecord · [`packages/experimental/translator/src/types.ts`](../packages/experimental/translator/src/types.ts) | | `plugin:translator/result` | `@deepseek-ai/dsh-experimental-translator` | Successful translation, retained before it is returned to the consumer. | TranslationResultRecord · [`packages/experimental/translator/src/types.ts`](../packages/experimental/translator/src/types.ts) | diff --git a/docs/module-graph.md b/docs/module-graph.md index 492f4ed711..fe8ed351f5 100644 --- a/docs/module-graph.md +++ b/docs/module-graph.md @@ -254,6 +254,11 @@ flowchart TD pkg_office_to_pdf["office-to-pdf"] end subgraph group_experimental["packages/experimental"] + pkg_action_cards["action-cards"] + pkg_client_ui_action_cards["client-ui-action-cards"] + pkg_client_ui_creation_recommendations["client-ui-creation-recommendations"] + pkg_client_ui_plugin_recommendations["client-ui-plugin-recommendations"] + pkg_creation_recommendations["creation-recommendations"] pkg_cua["cua"] pkg_cua_bundle["cua-bundle"] pkg_experimental_agent_team["experimental-agent-team"] @@ -297,6 +302,7 @@ flowchart TD pkg_hook_protocol["hook-protocol"] pkg_hooks_claude_code["hooks-claude-code"] pkg_hooks_codex["hooks-codex"] + pkg_plugin_recommendations["plugin-recommendations"] pkg_session_title_all_prompts_llm["session-title-all-prompts-llm"] pkg_skill_badge["skill-badge"] pkg_tool_ralph["tool-ralph"] @@ -515,6 +521,9 @@ flowchart TD pkg_experimental_webworker_runtime --> pkg_client_connection pkg_experimental_webworker_runtime --> pkg_client_modules pkg_experimental_webworker_runtime --> pkg_host_webserver + pkg_plugin_recommendations --> pkg_llm + pkg_plugin_recommendations --> pkg_skill + pkg_plugin_recommendations --> pkg_typert_protocol pkg_skill_badge --> pkg_skill pkg_host_directory_picker_auto --> pkg_client_connection pkg_host_directory_picker_auto --> pkg_client_ui_directory_picker_browse @@ -924,6 +933,14 @@ flowchart TD pkg_workspace_changes --> pkg_session pkg_workspace_changes --> pkg_subprocess pkg_workspace_changes --> pkg_tools + pkg_action_cards --> pkg_llm + pkg_action_cards --> pkg_session + pkg_action_cards --> pkg_tools + pkg_action_cards --> pkg_typert_protocol + pkg_creation_recommendations --> pkg_llm + pkg_creation_recommendations --> pkg_session + pkg_creation_recommendations --> pkg_skill + pkg_creation_recommendations --> pkg_tools pkg_experimental_browser_use_chrome_devtools_mcp --> pkg_agent pkg_experimental_browser_use_chrome_devtools_mcp --> pkg_browser_use pkg_experimental_browser_use_chrome_devtools_mcp --> pkg_system_prompt @@ -1560,6 +1577,9 @@ flowchart TD | [`client-web`](../packages/client/web) | `client` | — | | [`credentials`](../packages/credentials/credentials) | `credentials` | — | | [`office-to-pdf`](../packages/document/office-to-pdf) | `document` | — | +| [`client-ui-action-cards`](../packages/experimental/client-ui-action-cards) | `experimental` | — | +| [`client-ui-creation-recommendations`](../packages/experimental/client-ui-creation-recommendations) | `experimental` | — | +| [`client-ui-plugin-recommendations`](../packages/experimental/client-ui-plugin-recommendations) | `experimental` | — | | [`cua-bundle`](../packages/experimental/cua-bundle) | `experimental` | — | | [`experimental-agent-team-profile`](../packages/experimental/agent-team-profile) | `experimental` | — | | [`experimental-badge-skill-bundle`](../packages/experimental/badge-skill-bundle) | `experimental` | — | @@ -1628,6 +1648,7 @@ flowchart TD | [`deliverable-file-references`](../packages/deliverables/deliverable-file-references) | `deliverables` | [`system-prompt`](../packages/core/system-prompt) | | [`experimental-computer-use-cua-driver-mcp`](../packages/experimental/computer-use-cua-driver-mcp) | `experimental` | [`computer-use`](../packages/computer-use/computer-use) | | [`experimental-webworker-runtime`](../packages/experimental/webworker-runtime) | `experimental` | [`client-connection`](../packages/client/connection), [`client-modules`](../packages/client/modules), [`host-webserver`](../packages/host/webserver) | +| [`plugin-recommendations`](../packages/experimental/plugin-recommendations) | `experimental` | [`llm`](../packages/llm/llm), [`skill`](../packages/skill/skill), [`typert-protocol`](../packages/typert/protocol) | | [`skill-badge`](../packages/experimental/skill-badge) | `experimental` | [`skill`](../packages/skill/skill) | | [`host-directory-picker-auto`](../packages/host/directory-picker-auto) | `host` | [`client-connection`](../packages/client/connection), [`client-ui-directory-picker-browse`](../packages/client/ui-directory-picker-browse), [`client-ui-directory-picker-native`](../packages/client/ui-directory-picker-native), [`host-directory-picker-browse`](../packages/host/directory-picker-browse), [`host-directory-picker-native`](../packages/host/directory-picker-native), [`host-webserver`](../packages/host/webserver) | | [`host-frontend-static`](../packages/host/frontend-static) | `host` | [`client-connection`](../packages/client/connection), [`host-webserver`](../packages/host/webserver) | @@ -1730,6 +1751,8 @@ flowchart TD | [`file-reference-local`](../packages/context/file-reference-local) | `context` | [`agent`](../packages/core/agent), [`file-reference`](../packages/context/file-reference), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools), [`working-directory`](../packages/session/working-directory) | | [`tool-present`](../packages/deliverables/tool-present) | `deliverables` | [`agent`](../packages/core/agent), [`fs`](../packages/fs/fs), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`tools`](../packages/core/tools), [`working-directory`](../packages/session/working-directory) | | [`workspace-changes`](../packages/deliverables/workspace-changes) | `deliverables` | [`agent`](../packages/core/agent), [`fs`](../packages/fs/fs), [`session`](../packages/core/session), [`subprocess`](../packages/subprocess/subprocess), [`tools`](../packages/core/tools) | +| [`action-cards`](../packages/experimental/action-cards) | `experimental` | [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`tools`](../packages/core/tools), [`typert-protocol`](../packages/typert/protocol) | +| [`creation-recommendations`](../packages/experimental/creation-recommendations) | `experimental` | [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`skill`](../packages/skill/skill), [`tools`](../packages/core/tools) | | [`experimental-browser-use-chrome-devtools-mcp`](../packages/experimental/browser-use-chrome-devtools-mcp) | `experimental` | [`agent`](../packages/core/agent), [`browser-use`](../packages/browser-use/browser-use), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools), [`working-directory`](../packages/session/working-directory) | | [`experimental-browser-use-playwright-mcp`](../packages/experimental/browser-use-playwright-mcp) | `experimental` | [`agent`](../packages/core/agent), [`browser-use`](../packages/browser-use/browser-use), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools), [`working-directory`](../packages/session/working-directory) | | [`experimental-browser-use-stagehand-native`](../packages/experimental/browser-use-stagehand-native) | `experimental` | [`agent`](../packages/core/agent), [`browser-use`](../packages/browser-use/browser-use), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools) | diff --git a/docs/module-graph.zh.md b/docs/module-graph.zh.md index 9c086c9ac7..95943ec2d8 100644 --- a/docs/module-graph.zh.md +++ b/docs/module-graph.zh.md @@ -256,6 +256,11 @@ flowchart TD pkg_office_to_pdf["office-to-pdf"] end subgraph group_experimental["packages/experimental"] + pkg_action_cards["action-cards"] + pkg_client_ui_action_cards["client-ui-action-cards"] + pkg_client_ui_creation_recommendations["client-ui-creation-recommendations"] + pkg_client_ui_plugin_recommendations["client-ui-plugin-recommendations"] + pkg_creation_recommendations["creation-recommendations"] pkg_cua["cua"] pkg_cua_bundle["cua-bundle"] pkg_experimental_agent_team["experimental-agent-team"] @@ -299,6 +304,7 @@ flowchart TD pkg_hook_protocol["hook-protocol"] pkg_hooks_claude_code["hooks-claude-code"] pkg_hooks_codex["hooks-codex"] + pkg_plugin_recommendations["plugin-recommendations"] pkg_session_title_all_prompts_llm["session-title-all-prompts-llm"] pkg_skill_badge["skill-badge"] pkg_tool_ralph["tool-ralph"] @@ -517,6 +523,9 @@ flowchart TD pkg_experimental_webworker_runtime --> pkg_client_connection pkg_experimental_webworker_runtime --> pkg_client_modules pkg_experimental_webworker_runtime --> pkg_host_webserver + pkg_plugin_recommendations --> pkg_llm + pkg_plugin_recommendations --> pkg_skill + pkg_plugin_recommendations --> pkg_typert_protocol pkg_skill_badge --> pkg_skill pkg_host_directory_picker_auto --> pkg_client_connection pkg_host_directory_picker_auto --> pkg_client_ui_directory_picker_browse @@ -926,6 +935,14 @@ flowchart TD pkg_workspace_changes --> pkg_session pkg_workspace_changes --> pkg_subprocess pkg_workspace_changes --> pkg_tools + pkg_action_cards --> pkg_llm + pkg_action_cards --> pkg_session + pkg_action_cards --> pkg_tools + pkg_action_cards --> pkg_typert_protocol + pkg_creation_recommendations --> pkg_llm + pkg_creation_recommendations --> pkg_session + pkg_creation_recommendations --> pkg_skill + pkg_creation_recommendations --> pkg_tools pkg_experimental_browser_use_chrome_devtools_mcp --> pkg_agent pkg_experimental_browser_use_chrome_devtools_mcp --> pkg_browser_use pkg_experimental_browser_use_chrome_devtools_mcp --> pkg_system_prompt @@ -1562,6 +1579,9 @@ flowchart TD | [`client-web`](../packages/client/web) | `client` | — | | [`credentials`](../packages/credentials/credentials) | `credentials` | — | | [`office-to-pdf`](../packages/document/office-to-pdf) | `document` | — | +| [`client-ui-action-cards`](../packages/experimental/client-ui-action-cards) | `experimental` | — | +| [`client-ui-creation-recommendations`](../packages/experimental/client-ui-creation-recommendations) | `experimental` | — | +| [`client-ui-plugin-recommendations`](../packages/experimental/client-ui-plugin-recommendations) | `experimental` | — | | [`cua-bundle`](../packages/experimental/cua-bundle) | `experimental` | — | | [`experimental-agent-team-profile`](../packages/experimental/agent-team-profile) | `experimental` | — | | [`experimental-badge-skill-bundle`](../packages/experimental/badge-skill-bundle) | `experimental` | — | @@ -1630,6 +1650,7 @@ flowchart TD | [`deliverable-file-references`](../packages/deliverables/deliverable-file-references) | `deliverables` | [`system-prompt`](../packages/core/system-prompt) | | [`experimental-computer-use-cua-driver-mcp`](../packages/experimental/computer-use-cua-driver-mcp) | `experimental` | [`computer-use`](../packages/computer-use/computer-use) | | [`experimental-webworker-runtime`](../packages/experimental/webworker-runtime) | `experimental` | [`client-connection`](../packages/client/connection), [`client-modules`](../packages/client/modules), [`host-webserver`](../packages/host/webserver) | +| [`plugin-recommendations`](../packages/experimental/plugin-recommendations) | `experimental` | [`llm`](../packages/llm/llm), [`skill`](../packages/skill/skill), [`typert-protocol`](../packages/typert/protocol) | | [`skill-badge`](../packages/experimental/skill-badge) | `experimental` | [`skill`](../packages/skill/skill) | | [`host-directory-picker-auto`](../packages/host/directory-picker-auto) | `host` | [`client-connection`](../packages/client/connection), [`client-ui-directory-picker-browse`](../packages/client/ui-directory-picker-browse), [`client-ui-directory-picker-native`](../packages/client/ui-directory-picker-native), [`host-directory-picker-browse`](../packages/host/directory-picker-browse), [`host-directory-picker-native`](../packages/host/directory-picker-native), [`host-webserver`](../packages/host/webserver) | | [`host-frontend-static`](../packages/host/frontend-static) | `host` | [`client-connection`](../packages/client/connection), [`host-webserver`](../packages/host/webserver) | @@ -1732,6 +1753,8 @@ flowchart TD | [`file-reference-local`](../packages/context/file-reference-local) | `context` | [`agent`](../packages/core/agent), [`file-reference`](../packages/context/file-reference), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools), [`working-directory`](../packages/session/working-directory) | | [`tool-present`](../packages/deliverables/tool-present) | `deliverables` | [`agent`](../packages/core/agent), [`fs`](../packages/fs/fs), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`tools`](../packages/core/tools), [`working-directory`](../packages/session/working-directory) | | [`workspace-changes`](../packages/deliverables/workspace-changes) | `deliverables` | [`agent`](../packages/core/agent), [`fs`](../packages/fs/fs), [`session`](../packages/core/session), [`subprocess`](../packages/subprocess/subprocess), [`tools`](../packages/core/tools) | +| [`action-cards`](../packages/experimental/action-cards) | `experimental` | [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`tools`](../packages/core/tools), [`typert-protocol`](../packages/typert/protocol) | +| [`creation-recommendations`](../packages/experimental/creation-recommendations) | `experimental` | [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`skill`](../packages/skill/skill), [`tools`](../packages/core/tools) | | [`experimental-browser-use-chrome-devtools-mcp`](../packages/experimental/browser-use-chrome-devtools-mcp) | `experimental` | [`agent`](../packages/core/agent), [`browser-use`](../packages/browser-use/browser-use), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools), [`working-directory`](../packages/session/working-directory) | | [`experimental-browser-use-playwright-mcp`](../packages/experimental/browser-use-playwright-mcp) | `experimental` | [`agent`](../packages/core/agent), [`browser-use`](../packages/browser-use/browser-use), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools), [`working-directory`](../packages/session/working-directory) | | [`experimental-browser-use-stagehand-native`](../packages/experimental/browser-use-stagehand-native) | `experimental` | [`agent`](../packages/core/agent), [`browser-use`](../packages/browser-use/browser-use), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools) | diff --git a/docs/persistence-catalog.i18n.yaml b/docs/persistence-catalog.i18n.yaml index c345f7e1b7..f8d952b38c 100644 --- a/docs/persistence-catalog.i18n.yaml +++ b/docs/persistence-catalog.i18n.yaml @@ -9,8 +9,8 @@ en: 1387c4505dde2faf zh: 48570fa1c13d6910 /session-persistence-event-catalog/persistence-type-fingerprints: - en: bcff48893d904af8 - zh: f82e75c91c39adb1 + en: 3e24343c4745793d + zh: 90f808fd6b79c5a3 /session-persistence-event-catalog/event-envelope: en: 981db6b1928eda50 zh: d77bc66cf678099d @@ -309,8 +309,11 @@ en: 3493ecf0993e5397 zh: ec95688f593a4ed4 /session-persistence-event-catalog/resolved-persistence-types/aborted: - en: d716fb9ee9baa564 - zh: d716fb9ee9baa564 + en: 40049fd36471e4b2 + zh: 40049fd36471e4b2 +/session-persistence-event-catalog/resolved-persistence-types/action-cards: + en: 629f821b50a38bc6 + zh: 629f821b50a38bc6 /session-persistence-event-catalog/resolved-persistence-types/active: en: 6b343e390ae7d7a7 zh: 6b343e390ae7d7a7 @@ -1005,11 +1008,11 @@ en: a6a2e38fdd6d127a zh: 1fe82356d89f5769 /session-persistence-event-catalog/resolved-persistence-types/deepseeksearchllmrequest: - en: 128c0ecc1897eb68 - zh: e4c67c92752bcc2a + en: 0e286bae74e78505 + zh: b1c9da5aa56d5a81 /session-persistence-event-catalog/resolved-persistence-types/developermessage: - en: 997b5db27df00446 - zh: 07e5197ddd7bd59b + en: 3d8a393be23efcde + zh: f95f337d816dbbe7 /session-persistence-event-catalog/resolved-persistence-types/epochheader: en: c804cb9e2e31c5ec zh: 3ed7ac59cd1441b7 @@ -1119,11 +1122,11 @@ en: 6dbe099c62f7c226 zh: 6fd788f63c038c91 /session-persistence-event-catalog/resolved-persistence-types/llmretrystartedeventdata: - en: c2d9edd8d9f492b7 - zh: f59c687ac691f7ce + en: 3d2f7c88259b6446 + zh: 58bee68c335d6d4f /session-persistence-event-catalog/resolved-persistence-types/message: - en: 594707e4894819a8 - zh: 268c4bfd5c4c28f2 + en: 76d13d39fe22f7bd + zh: b1e19cfe90a6d6b6 /session-persistence-event-catalog/resolved-persistence-types/messagefeedbackdelete: en: c582953270d4ca44 zh: 966ccfe2e7660a2a @@ -1134,14 +1137,14 @@ en: f58575a1fc96df42 zh: 9f0c6ed9033e5c9d /session-persistence-event-catalog/resolved-persistence-types/messagefeedbackrating: - en: 674296d54c1914d0 - zh: 3d71ab319514be25 + en: 887549d6401dd116 + zh: 30198672b67dac6b /session-persistence-event-catalog/resolved-persistence-types/messagesource: - en: 3fab179fe8d58c31 - zh: 5328c9161ff908a9 + en: 2f21414632f8d184 + zh: d85f66e001fd814c /session-persistence-event-catalog/resolved-persistence-types/message~2: - en: 8bd12543fcdcda5f - zh: d4c6b17083948856 + en: 87e95476b4985e5e + zh: 0e0322e005b3e995 /session-persistence-event-catalog/resolved-persistence-types/modelmessagesource: en: 5f9154ca34235ab9 zh: a833105910ebfa82 @@ -1203,11 +1206,11 @@ en: 9a54c4478ad3b620 zh: 02c4c1fd7e252770 /session-persistence-event-catalog/resolved-persistence-types/sessiontitleeventdata: - en: bf7325029c9338d6 - zh: e5f98271511e5318 + en: c0104327b481ae54 + zh: b4624d112bee37f1 /session-persistence-event-catalog/resolved-persistence-types/sessiontitlellmrequesteventdata: - en: 89dcb9a9b957c126 - zh: 132aeb7dfd435290 + en: 2e40681d575da503 + zh: 57cbccb16548e2da /session-persistence-event-catalog/resolved-persistence-types/sessiontitlemodelidentity: en: 9531468264499498 zh: 9da23c0ad2776563 @@ -1314,14 +1317,14 @@ en: 4dc89c072fe5d0d1 zh: 4ed2c9e9e8b4f7e2 /session-persistence-event-catalog/resolved-persistence-types/turnendreason: - en: 344a7207a4199a32 - zh: d500af39e83289cf + en: e39f2f99ea109af9 + zh: f95ec41c72738f3e /session-persistence-event-catalog/resolved-persistence-types/usermessage: - en: bd3aedd570ded4d2 - zh: f020d0816d3a6c1a + en: 79b3ac6c731859aa + zh: 81058fe7edf61bdb /session-persistence-event-catalog/resolved-persistence-types/usermessage~2: - en: b99d66ffe277f0bc - zh: eacc9c1f62ae66c1 + en: 8e699c0217638bbe + zh: a2047db24c25217b /session-persistence-event-catalog/resolved-persistence-types/workflowagentoutcome: en: d0a3f9aab18c3de5 zh: 76ac69a835ca4e29 @@ -1500,11 +1503,11 @@ en: 51af619ed8532a43 zh: a5ad878c63d0e046 /session-persistence-event-catalog/resolved-persistence-types/header-reason-startsseries: - en: 0b1ca5c765d716d1 - zh: dd939095c250c4eb + en: 8c523b44a081ffe0 + zh: d94ac2402fe80645 /session-persistence-event-catalog/resolved-persistence-types/headerseq-message-step-turn: - en: 3d16abb7b9581429 - zh: dc959157864261d8 + en: 174b2c8332e397b2 + zh: 31b94e9a33b9bdac /session-persistence-event-catalog/resolved-persistence-types/height-width: en: a5df50710083ac25 zh: aad3ccf6f6de70e4 @@ -1521,17 +1524,38 @@ en: 41f73c58a77dfa28 zh: af77f4aee3cb4a40 /session-persistence-event-catalog/resolved-persistence-types/inherited: - en: 6ce6ddac6d815805 - zh: f7196176e902c965 + en: bbffa2689f69314f + zh: c691059ad7b99d67 /session-persistence-event-catalog/resolved-persistence-types/inserted-outcome-removedcount-start: - en: 346d884a7a74e48d - zh: 646ac1021f9ca920 + en: 1750502af87d698c + zh: 382a3356f0a61546 /session-persistence-event-catalog/resolved-persistence-types/interrupted-message-step-stream: en: 89fb3f919d181c86 zh: 4380624236500687 /session-persistence-event-catalog/resolved-persistence-types/kind-aborted-reason: - en: 48424cbed3fbc269 - zh: 2ce88c04ad002104 + en: 8bdd31bfea14a015 + zh: 203d7ebf5ea4d352 +/session-persistence-event-catalog/resolved-persistence-types/kind-action-cards: + en: 8d057f0bd0b88fc6 + zh: 500d1e3d54e6d7c4 +/session-persistence-event-catalog/resolved-persistence-types/kind-action-cards-form-catalog: + en: 14e9dd01c49f91e0 + zh: 4a7e4e4aeeb15982 +/session-persistence-event-catalog/resolved-persistence-types/kind-action-cards-form-instructions: + en: f59ec0c37dc8f9b6 + zh: debc915ed98581fb +/session-persistence-event-catalog/resolved-persistence-types/kind-action-cards-form-notice: + en: ef0a456530698cca + zh: 23f2ace5345eb847 +/session-persistence-event-catalog/resolved-persistence-types/kind-action-cards-form-recall: + en: 0b607f9577e3a3c5 + zh: 237ed72b7a4154df +/session-persistence-event-catalog/resolved-persistence-types/kind-action-cards-form-relay: + en: 712bc9ad7c51a8fe + zh: f230dc9c09bdbf3c +/session-persistence-event-catalog/resolved-persistence-types/kind-action-cards-form-snapshot: + en: 4e030b06ef900819 + zh: 27532bbabe118206 /session-persistence-event-catalog/resolved-persistence-types/kind-blocked: en: 2fad818b19823c48 zh: a6b33dee21c3fdc9 @@ -1920,11 +1944,11 @@ en: ea32b4165dbfb182 zh: 57f48af06cfa201c /session-persistence-event-catalog/resolved-persistence-types/type-agent-preset-selected: - en: c8c731ee23bed85a - zh: f4ffc80f7828701d + en: 6a59f1cd472759e9 + zh: 1db67f4f97207efc /session-persistence-event-catalog/resolved-persistence-types/type-agent-inbox-spliced: - en: 62dfe9447efaceb1 - zh: 076454de19b5d7d7 + en: 6c276128e62a5432 + zh: 4ff5e93d255caece /session-persistence-event-catalog/resolved-persistence-types/type-approval-asked: en: 9c77f18b0dad22f0 zh: 8894cf2878fe90db @@ -1968,11 +1992,11 @@ en: ba73fb10b7e2ffc9 zh: 8d5c97e63e5f16da /session-persistence-event-catalog/resolved-persistence-types/type-deliverables-presented: - en: a4d9eab2928e9b30 - zh: b36702c866518273 + en: 10d95ce64c285e64 + zh: 0ddb26f1fa16629c /session-persistence-event-catalog/resolved-persistence-types/type-developer-message: - en: a9e6e24b98390923 - zh: 65addae898d8aaec + en: 78d421c83506ebe9 + zh: 2e9399ba2d360627 /session-persistence-event-catalog/resolved-persistence-types/type-disabled: en: 585627a50ece4e5f zh: 157837aee3da0ac3 @@ -2046,11 +2070,11 @@ en: acf7e21e4d21ebff zh: fb83a853634d204a /session-persistence-event-catalog/resolved-persistence-types/type-session-title: - en: b99fbacda0db1abd - zh: 9e75ce6b1d174d48 + en: 26d99fabe31f7fa3 + zh: 0a44b5c8f7f6488c /session-persistence-event-catalog/resolved-persistence-types/type-session-title-llm-request: - en: 7ba30b98d8e3f653 - zh: ca68b01ab4b6ba3d + en: bbbb345651600abf + zh: e3b29e05fecb136a /session-persistence-event-catalog/resolved-persistence-types/type-step-end: en: 7fc233c4a569dae2 zh: bc2b3fed4a397c00 @@ -2127,11 +2151,11 @@ en: 1b045de96172be4b zh: 8a15c6f35b576b37 /session-persistence-event-catalog/resolved-persistence-types/type-usage: - en: 8eec3bb6eee5085b - zh: 04bb3281fd7bd95c + en: ebb801c4444061fb + zh: f2d6531de88ee16e /session-persistence-event-catalog/resolved-persistence-types/type-user-message: - en: d6d9401f19a66ee8 - zh: 569fdddf887660a2 + en: 536f875df13987d0 + zh: 857a381433a4217f /session-persistence-event-catalog/resolved-persistence-types/type-web-deepseek-search-llm-request: en: 99c8837344178a47 zh: bf746f5678346596 diff --git a/docs/persistence-catalog.md b/docs/persistence-catalog.md index 7edaa463de..220437ca16 100644 --- a/docs/persistence-catalog.md +++ b/docs/persistence-catalog.md @@ -23,7 +23,7 @@ The [format references](persistence-changes/historical-formats/README.md) cover | `JsonlHeaderLine` | header | `18ee62b8900a4c3d046700f05d7a4d49d6cab2a660a020c87481d1603dd8bd4f` | [`HeaderLine`](#persistence-type-sha256-18ee62b8900a4c3d046700f05d7a4d49d6cab2a660a020c87481d1603dd8bd4f) | | `SessionEventEnvelope` | envelope | `5776e5553ff2dfe3f5bc202dbb1e7c9f93e35a531aebb7764c23b2b6153b2ccc` | [`union (3 variants)`](#persistence-type-sha256-5776e5553ff2dfe3f5bc202dbb1e7c9f93e35a531aebb7764c23b2b6153b2ccc) | | `event:agent-preset/selected` | event | `a10c17474eaf2ddab7095a099e0fe3d046fc18e56c3e344fc8894c05ff9ef97b` | [`{ type: "agent-preset/selected" }`](#persistence-type-sha256-a10c17474eaf2ddab7095a099e0fe3d046fc18e56c3e344fc8894c05ff9ef97b) | -| `event:agent/inbox/spliced` | event | `0755b7d1128319ba2e389619cbbf2c961ad648882b4e91da7f3e9582cc8a2034` | [`{ type: "agent/inbox/spliced" }`](#persistence-type-sha256-0755b7d1128319ba2e389619cbbf2c961ad648882b4e91da7f3e9582cc8a2034) | +| `event:agent/inbox/spliced` | event | `b36fd842cd004b532ce96fd5d6f5be28e017eda043131aa3cf5e69edc436108e` | [`{ type: "agent/inbox/spliced" }`](#persistence-type-sha256-b36fd842cd004b532ce96fd5d6f5be28e017eda043131aa3cf5e69edc436108e) | | `event:approval/asked` | event | `3bfeb47b58606f4661904bc723da612782214c463d01e6d61cd6d6193d7374e1` | [`{ type: "approval/asked" }`](#persistence-type-sha256-3bfeb47b58606f4661904bc723da612782214c463d01e6d61cd6d6193d7374e1) | | `event:approval/decided` | event | `bb1ab3d08f49a9f3b265f844cd78d5c49813062a7b34b54904b426f85d0ff6e3` | [`{ type: "approval/decided" }`](#persistence-type-sha256-bb1ab3d08f49a9f3b265f844cd78d5c49813062a7b34b54904b426f85d0ff6e3) | | `event:approval/policy` | event | `26718e15e7e395bce9642dba5bbe09b3b1a4ce2213d20d566cd9207d7fc5fb78` | [`{ type: "approval/policy" }`](#persistence-type-sha256-26718e15e7e395bce9642dba5bbe09b3b1a4ce2213d20d566cd9207d7fc5fb78) | @@ -36,7 +36,7 @@ The [format references](persistence-changes/historical-formats/README.md) cover | `event:compaction/start` | event | `db874d463b0fdec77e9da1c4568f37cb70bd6596781eb93800db44fb8a116965` | [`{ type: "compaction/start" }`](#persistence-type-sha256-db874d463b0fdec77e9da1c4568f37cb70bd6596781eb93800db44fb8a116965) | | `event:compaction/summary` | event | `e2f9a41e0989f54ed8cee80f8db2bcf9d60a5c810dc9d45b83fa050b9dce7602` | [`{ type: "compaction/summary" }`](#persistence-type-sha256-e2f9a41e0989f54ed8cee80f8db2bcf9d60a5c810dc9d45b83fa050b9dce7602) | | `event:deliverables/presented` | event | `13d3d180f977bf78081d487ffa0ecb75857349bcab29a5a3fb48189fca2a6176` | [`{ type: "deliverables/presented" }`](#persistence-type-sha256-13d3d180f977bf78081d487ffa0ecb75857349bcab29a5a3fb48189fca2a6176) | -| `event:developer/message` | event | `97e0162f983c27456ec71269fb4976b1851588b8c1aeac5d071d43e11f95e03c` | [`{ type: "developer/message" }`](#persistence-type-sha256-97e0162f983c27456ec71269fb4976b1851588b8c1aeac5d071d43e11f95e03c) | +| `event:developer/message` | event | `5ef58f2a1bdc576c1f12fa1f4da1920ece020b13c0f991a578cddcbeb28bc5be` | [`{ type: "developer/message" }`](#persistence-type-sha256-5ef58f2a1bdc576c1f12fa1f4da1920ece020b13c0f991a578cddcbeb28bc5be) | | `event:feedback/message-delete` | event | `3ee93b06f3a125850337602bcdf155d2538c43a5c944ec55b1b3c365152d6796` | [`{ type: "feedback/message-delete" }`](#persistence-type-sha256-3ee93b06f3a125850337602bcdf155d2538c43a5c944ec55b1b3c365152d6796) | | `event:feedback/message-put` | event | `b5086d249e8502e9ead1d39156bb8d559bde7951cac0f14ce150345b4e42a2bf` | [`{ type: "feedback/message-put" }`](#persistence-type-sha256-b5086d249e8502e9ead1d39156bb8d559bde7951cac0f14ce150345b4e42a2bf) | | `event:feedback/record` | event | `b54940ff095c17e874c5be03815f4c2145a256cf3a1d34dae4ab2f7769dfffe8` | [`{ type: "feedback/record" }`](#persistence-type-sha256-b54940ff095c17e874c5be03815f4c2145a256cf3a1d34dae4ab2f7769dfffe8) | @@ -58,7 +58,7 @@ The [format references](persistence-changes/historical-formats/README.md) cover | `event:session-log-deepseek/delivery-accepted` | event | `d63b8b8ffad9c02fd80c43a17df4f240c1fe8118ecca9de34f9d5871838ab5b9` | [`{ type: "session-log-deepseek/delivery-accepted" }`](#persistence-type-sha256-d63b8b8ffad9c02fd80c43a17df4f240c1fe8118ecca9de34f9d5871838ab5b9) | | `event:session/end-seed` | event | `5e6db6e24948d4a853c71cb9fabd252ad051ce93d4672c1266cf837c1c17b84e` | [`{ type: "session/end-seed" }`](#persistence-type-sha256-5e6db6e24948d4a853c71cb9fabd252ad051ce93d4672c1266cf837c1c17b84e) | | `event:session/title` | event | `1b912703e2d64f91c99c675b8f805b01076c8325b905c1218ad81ef0b24909d5` | [`{ type: "session/title" }`](#persistence-type-sha256-1b912703e2d64f91c99c675b8f805b01076c8325b905c1218ad81ef0b24909d5) | -| `event:session/title-llm-request` | event | `79a4c87f8bfb4d6d05cd9952b99210d6a7db3b478c4a01d94b4c50602308c056` | [`{ type: "session/title-llm-request" }`](#persistence-type-sha256-79a4c87f8bfb4d6d05cd9952b99210d6a7db3b478c4a01d94b4c50602308c056) | +| `event:session/title-llm-request` | event | `efa1a091017ce533e38b0ef3e14d11a5c102ff1793229b18dad719a4268d9adc` | [`{ type: "session/title-llm-request" }`](#persistence-type-sha256-efa1a091017ce533e38b0ef3e14d11a5c102ff1793229b18dad719a4268d9adc) | | `event:step/end` | event | `e0a787e6ec76c7c94fecbc501b489164ab0293db05bc947914077ad01e674f05` | [`{ type: "step/end" }`](#persistence-type-sha256-e0a787e6ec76c7c94fecbc501b489164ab0293db05bc947914077ad01e674f05) | | `event:step/start` | event | `4513e088d43e6c68425be30451b9f961cc264fe7318ca62681c41d4d78615986` | [`{ type: "step/start" }`](#persistence-type-sha256-4513e088d43e6c68425be30451b9f961cc264fe7318ca62681c41d4d78615986) | | `event:subagent/catalog` | event | `53750b9abf61efb7992b0aadc3e247ec927449c63266df28230fd1139f5590ac` | [`{ type: "subagent/catalog" }`](#persistence-type-sha256-53750b9abf61efb7992b0aadc3e247ec927449c63266df28230fd1139f5590ac) | @@ -80,7 +80,7 @@ The [format references](persistence-changes/historical-formats/README.md) cover | `event:tool/result` | event | `7c9f44e90a0058f4cc532ae20dad0c10afa6eba22e70a6c79fc79490bad64397` | [`{ type: "tool/result" }`](#persistence-type-sha256-7c9f44e90a0058f4cc532ae20dad0c10afa6eba22e70a6c79fc79490bad64397) | | `event:turn/end` | event | `0f8512903d94f57a4748fa1a2092e64342856796684e6b8343db685b192745ce` | [`{ type: "turn/end" }`](#persistence-type-sha256-0f8512903d94f57a4748fa1a2092e64342856796684e6b8343db685b192745ce) | | `event:turn/start` | event | `aa0957eca50aeb28bcd2e6930b95809926edacb550c8c340ba526ba6b861b3d8` | [`{ type: "turn/start" }`](#persistence-type-sha256-aa0957eca50aeb28bcd2e6930b95809926edacb550c8c340ba526ba6b861b3d8) | -| `event:user/message` | event | `2ae8594a998c9862a597129e782277bcc518266712a703b5c3161bdf552d13b8` | [`{ type: "user/message" }`](#persistence-type-sha256-2ae8594a998c9862a597129e782277bcc518266712a703b5c3161bdf552d13b8) | +| `event:user/message` | event | `58b122df513aeaf7ddc5783dbb1fd844e2979bb273964e30ae351a8db6510019` | [`{ type: "user/message" }`](#persistence-type-sha256-58b122df513aeaf7ddc5783dbb1fd844e2979bb273964e30ae351a8db6510019) | | `event:web/deepseek-search-llm-request` | event | `0c556c9b9f2a96238a6b9bbb99e748206cbff2ed6203041c9571f04ae5802f52` | [`{ type: "web/deepseek-search-llm-request" }`](#persistence-type-sha256-0c556c9b9f2a96238a6b9bbb99e748206cbff2ed6203041c9571f04ae5802f52) | | `event:working-directory/change` | event | `124aa62078ca3311353b39cc3bf6c75da281338be6bbf672f8b361d828cddca3` | [`{ type: "working-directory/change" }`](#persistence-type-sha256-124aa62078ca3311353b39cc3bf6c75da281338be6bbf672f8b361d828cddca3) | | `event:workspace/changes` | event | `e308ccf867a5398e316e0af8cb6ce238a8d33a63b9b384c8250a686786285f72` | [`{ type: "workspace/changes" }`](#persistence-type-sha256-e308ccf867a5398e316e0af8cb6ce238a8d33a63b9b384c8250a686786285f72) | @@ -1347,6 +1347,14 @@ SHA-256: `1e0b0b66b6f0c53fc72e52289d06b4708d776f1ca9df4c86a22bff621d403cf0` `"aborted"` + + +### `"action-cards"` + +SHA-256: `05205074b24d8340cfd7995f6cb64779c7f1241490954700115601e264178877` + +`"action-cards"` + ### `"active"` @@ -3365,7 +3373,7 @@ Sources: [`packages/web/web-search-deepseek/src/provider.ts:55`](../packages/web | `body` | required | [`{ max_tokens, messages, model, thinking?, … }`](#persistence-type-sha256-9c001ecb5e1d3c3894410db2a1e383842e5f8454fb52182338686158abc1b3ee) | | `endpoint` | required | `string` | - + @@ -3373,7 +3381,7 @@ Sources: [`packages/web/web-search-deepseek/src/provider.ts:55`](../packages/web ### `DeveloperMessage` -SHA-256: `0f6265188dae7ba6faa26830a180bb4deb0760561f97ee0c9d399ccf6c9cf1f7` +SHA-256: `7580e7d96dc8475fffe813a42af499665de3d4517fcdb3bc2337d0340e98d616` Sources: [`packages/llm/llm/src/message.ts:157`](../packages/llm/llm/src/message.ts) @@ -3382,11 +3390,11 @@ Sources: [`packages/llm/llm/src/message.ts:157`](../packages/llm/llm/src/message | `content` | required | [`ContentBlock[]`](#persistence-type-sha256-b52f215a6cd691058a3413c12f161fa3b563a2f4895adb3df6f744ec4b0a6ef1) | | `id` | required | `string` | | `role` | required | `"developer"` | -| `source` | required | [`MessageSource`](#persistence-type-sha256-5bc2d7f02503fc6b3e1bb7e0b7fa22c0b9e4b9841fa3175b2ad8e607ae3525a8) | +| `source` | required | [`MessageSource`](#persistence-type-sha256-7a5b68bd1a5118c827df9e2466acc5e4d16b83e3f97a2304d992958647592690) | Source compatibility: `source` — `session-source-attribution` v1; `session.developer-message.source`; `kind`; `preserve`. -Attribution-only additions: `tmux-context`, `user-question-reply`, `working-directory`. +Attribution-only additions: `action-cards`, `tmux-context`, `user-question-reply`, `working-directory`. @@ -4081,7 +4089,7 @@ Sources: [`packages/llm/llm-retry/src/types.ts:43`](../packages/llm/llm-retry/sr | `step` | required | `number` | | `turn` | required | `number` | - + @@ -4089,17 +4097,17 @@ Sources: [`packages/llm/llm-retry/src/types.ts:43`](../packages/llm/llm-retry/sr ### `Message` -SHA-256: `040cd65423bd64264d225504f1c572d239eeb1793d0c2856ad8e42bb6e1e148b` +SHA-256: `cda79b839d8cc0c7a7bd09dd067de04925f6e9037080d878b65aaa5a383f117c` Sources: [`packages/llm/llm/src/message.ts:196`](../packages/llm/llm/src/message.ts) One of: - [`ToolResultMessage`](#persistence-type-sha256-4b259f7f8f0a44ad5e90b70a73553ced1df27012d126921d397ff403b6c86731) -- [`DeveloperMessage`](#persistence-type-sha256-0f6265188dae7ba6faa26830a180bb4deb0760561f97ee0c9d399ccf6c9cf1f7) -- [`UserMessage`](#persistence-type-sha256-2cf075b013d363ad743b5a4982900678d108499c721bc11255137402003db711) -- [`AssistantMessage`](#persistence-type-sha256-fa87537f4a9c1e25602990a8bf686b383e9e00352ec8946e16ae24ba7c1625be) +- [`DeveloperMessage`](#persistence-type-sha256-7580e7d96dc8475fffe813a42af499665de3d4517fcdb3bc2337d0340e98d616) +- [`UserMessage`](#persistence-type-sha256-b782058cef83caf3eb44a16adeef5657e4d5cdff2d50655aba418c4490670a69) - [`SystemMessage`](#persistence-type-sha256-09f3f8dc417f278257202714132f98ba7b52957d7b558c4a04456603890eb363) +- [`AssistantMessage`](#persistence-type-sha256-fa87537f4a9c1e25602990a8bf686b383e9e00352ec8946e16ae24ba7c1625be) @@ -4174,7 +4182,7 @@ One of: - `"negative"` - `"positive"` - + @@ -4182,12 +4190,19 @@ One of: ### `MessageSource` -SHA-256: `5bc2d7f02503fc6b3e1bb7e0b7fa22c0b9e4b9841fa3175b2ad8e607ae3525a8` +SHA-256: `7a5b68bd1a5118c827df9e2466acc5e4d16b83e3f97a2304d992958647592690` Sources: [`packages/llm/llm/src/message.ts:136`](../packages/llm/llm/src/message.ts) | kind | Form property | Other required fields | Full definition | |---|---|---|---| +| `action-cards` | `"catalog"` | `through`: `number` | [`{ kind: "action-cards", form: "catalog" }`](#persistence-type-sha256-dd00cdccd0ba487b92362ef67db58cd264f48c1bfd368291424a1da9a899489a) | +| `action-cards` | `"instructions"` | `through`: `number` | [`{ kind: "action-cards", form: "instructions" }`](#persistence-type-sha256-ba2a1a8f194b1bb21f270713398f6643e206e8ff5f7f26f6d36a88e3f0f0ab10) | +| `action-cards` | `"notice"` | `summary`: `string`; `through`: `number` | [`{ kind: "action-cards", form: "notice" }`](#persistence-type-sha256-e7f4cbb3a57f4cbdb61ea41e95bcba626ad1fff6bb9b1e063b1d50a32da6e864) | +| `action-cards` | `"recall"` | `through`: `number` | [`{ kind: "action-cards", form: "recall" }`](#persistence-type-sha256-b613f83572fe2fbf7259c72308cafd008f4fceea897749128a48bcd158f290aa) | +| `action-cards` | `"relay"` | `through`: `number` | [`{ kind: "action-cards", form: "relay" }`](#persistence-type-sha256-45f005e6bbb7645248be792b715a74bc7fdf5a67f5381655d3a75dcc7aa2112e) | +| `action-cards` | `"snapshot"` | `sections`: [`ContextSnapshotSection[]`](#persistence-type-sha256-c748fb42c80a120038a8c587af80ec39924803c401364db11c8974d5bf6d0262); `through`: `number` | [`{ kind: "action-cards", form: "snapshot" }`](#persistence-type-sha256-3247e566f4340029325b70e4366b56fa9719977f1e39033b769a2ec221ab22a9) | +| `action-cards` | not declared | `through`: `number` | [`{ kind: "action-cards" }`](#persistence-type-sha256-dde0cdf9d426768b649b5a9783d2144e7f900c35ef65229cd89036dbf411d1b0) | | `agent-instructions` | `"instructions"` | `changes`: [`AgentInstructionChange[]`](#persistence-type-sha256-2ab12a0e345658f9342816d773fe610f049982bcad82631f43b9205abe43e5ee) | [`AgentInstructionSource`](#persistence-type-sha256-6f22a9d02b9a1f1157f3b0e7bdccd7e7b12b085c4efdb709dfc74a465cd4e590) | | `agent-message` | `"relay"` | `senderSessionId`: `string` | [`AgentMessageSource`](#persistence-type-sha256-4a9bef9f27a942520cc2279ca7dd3082556e4bff543f66b5c0333e1bbed1cd86) | | `compact-checkpoint` | not declared | `compactionId`: `string` | [`CompactionCheckpointSource`](#persistence-type-sha256-31907de9563751822af6d9be02bafaf26faa098bbd1cb412524134d577cc1648) | @@ -4300,13 +4315,13 @@ Sources: [`packages/llm/llm/src/message.ts:136`](../packages/llm/llm/src/message | `webhook` | `"notice"` | `deliveryId`: `string`; `provider`: `string`; `ruleId`: `string`; `source`: `string`; `summary`: `string` | [`{ kind: "webhook", form: "notice" }`](#persistence-type-sha256-9edc162949abf93bd5ff049191b345454ba104f583937ce5580fb50f067d2b94) | | `working-directory` | not declared | none | [`{ kind: "working-directory" }`](#persistence-type-sha256-2235d957bf4b7c6cf959231d480f0ba1f3a6d72d82981493fb6c7c963021d3d5) | - + ### `Message[]` -SHA-256: `a927d444c928ac0efd9dd8b2e7d7d04d711c264cb71578d81e0e2fd143ff669f` +SHA-256: `78b9204976ab9faaf133c3488b73d1dfb6f1a4bb6c0903e3473cd41a5e1e39e1` -Array of [`Message`](#persistence-type-sha256-040cd65423bd64264d225504f1c572d239eeb1793d0c2856ad8e42bb6e1e148b). +Array of [`Message`](#persistence-type-sha256-cda79b839d8cc0c7a7bd09dd067de04925f6e9037080d878b65aaa5a383f117c). @@ -4690,7 +4705,7 @@ Sources: [`packages/session/session-title/src/types.ts:41`](../packages/session/ | `source` | required | [`SessionTitleSource`](#persistence-type-sha256-30d5a537b296685770b1241c4b4bbde19d5a31ff81aa23df73fab5e90b8fc65f) | | `title` | required | `string` | - + @@ -4698,7 +4713,7 @@ Sources: [`packages/session/session-title/src/types.ts:41`](../packages/session/ ### `SessionTitleLlmRequestEventData` -SHA-256: `00c054d3018c198d92f459563331edd1385e58345c677bb499791df2da0ade9f` +SHA-256: `a3e299483348f27a4787a36bb3f29cd86024c65336abc10bb3d687468302c4cf` Sources: [`packages/session/session-title-llm/src/index.ts:37`](../packages/session/session-title-llm/src/index.ts) @@ -4706,7 +4721,7 @@ Sources: [`packages/session/session-title-llm/src/index.ts:37`](../packages/sess |---|---|---| | `maxTokens` | required | `number` | | `messageSeqs` | required | [`number[]`](#persistence-type-sha256-5d03ba38734809bcbd2a55221bd938b3b3fc34b49bb686c6113e9e4931e3aa78) | -| `messages` | required | [`Message[]`](#persistence-type-sha256-a927d444c928ac0efd9dd8b2e7d7d04d711c264cb71578d81e0e2fd143ff669f) | +| `messages` | required | [`Message[]`](#persistence-type-sha256-78b9204976ab9faaf133c3488b73d1dfb6f1a4bb6c0903e3473cd41a5e1e39e1) | | `reasoningEffort` | optional | `string` | | `route` | required | [`SessionTitleModelIdentity`](#persistence-type-sha256-07e1a58c58b593f507b8e7f25723bb0aa42baae6e00f5ecfddf28cb1e64d3235) | | `system` | required | `string` | @@ -5362,7 +5377,7 @@ Sources: [`packages/core/session/src/types.ts:234`](../packages/core/session/src | `interrupted` | not declared | none | [`{ kind: "interrupted" }`](#persistence-type-sha256-56b7c047694875a843ba020dfea3c8380d54ae4ff88ab349e72004dc21e07c23) | | `max-tokens` | not declared | none | [`{ kind: "max-tokens" }`](#persistence-type-sha256-ab7ec5f9eb2c6dfacca5da4c37cf7ba85d04172053ee6b1ebf1709ffcd99c1a7) | - + @@ -5370,7 +5385,7 @@ Sources: [`packages/core/session/src/types.ts:234`](../packages/core/session/src ### `UserMessage` -SHA-256: `2cf075b013d363ad743b5a4982900678d108499c721bc11255137402003db711` +SHA-256: `b782058cef83caf3eb44a16adeef5657e4d5cdff2d50655aba418c4490670a69` Sources: [`packages/llm/llm/src/message.ts:162`](../packages/llm/llm/src/message.ts) @@ -5379,19 +5394,19 @@ Sources: [`packages/llm/llm/src/message.ts:162`](../packages/llm/llm/src/message | `content` | required | [`ContentBlock[]`](#persistence-type-sha256-b52f215a6cd691058a3413c12f161fa3b563a2f4895adb3df6f744ec4b0a6ef1) | | `id` | required | `string` | | `role` | required | `"user"` | -| `source` | required | [`MessageSource`](#persistence-type-sha256-5bc2d7f02503fc6b3e1bb7e0b7fa22c0b9e4b9841fa3175b2ad8e607ae3525a8) | +| `source` | required | [`MessageSource`](#persistence-type-sha256-7a5b68bd1a5118c827df9e2466acc5e4d16b83e3f97a2304d992958647592690) | Source compatibility: `source` — `session-source-attribution` v1; `session.user-message.source`; `kind`; `preserve`. -Attribution-only additions: `tmux-context`, `user-question-reply`, `working-directory`. +Attribution-only additions: `action-cards`, `tmux-context`, `user-question-reply`, `working-directory`. - + ### `UserMessage[]` -SHA-256: `454a0fa879e38076bdcdbd87ef561cd67d7e43e58a287cf7dbaa7b6cf7847020` +SHA-256: `5d00abf938272f22eb47199e9d40801e8d8ec4c88e2b138b15aa2a5a5bf87520` -Array of [`UserMessage`](#persistence-type-sha256-2cf075b013d363ad743b5a4982900678d108499c721bc11255137402003db711). +Array of [`UserMessage`](#persistence-type-sha256-b782058cef83caf3eb44a16adeef5657e4d5cdff2d50655aba418c4490670a69). @@ -6276,18 +6291,18 @@ Sources: [`packages/core/session/src/types.ts:392`](../packages/core/session/src | `reason` | required | [`RequestHeaderReason`](#persistence-type-sha256-530da6c0267813bb4511af72883cc518834e624e3b3220a7062955fd57a788a6) | | `startsSeries` | optional | `true` | - + ### `{ headerSeq?, message, step, turn }` -SHA-256: `f4ed4717b4c259e933b550a8dcd90dc93628cda8a3c7affcaa53b9c13c1d53eb` +SHA-256: `7404c6123fdaa591474a37979a5a8ae70d1387af7c9f09bfe568b218935a974d` Sources: [`packages/core/session/src/types.ts:313`](../packages/core/session/src/types.ts) | Property | Presence | Type | |---|---|---| | `headerSeq` | optional | `number` | -| `message` | required | [`DeveloperMessage`](#persistence-type-sha256-0f6265188dae7ba6faa26830a180bb4deb0760561f97ee0c9d399ccf6c9cf1f7) | +| `message` | required | [`DeveloperMessage`](#persistence-type-sha256-7580e7d96dc8475fffe813a42af499665de3d4517fcdb3bc2337d0340e98d616) | | `step` | required | `number` | | `turn` | required | `number` | @@ -6371,17 +6386,17 @@ Sources: [`packages/core/session/src/types.ts:429`](../packages/core/session/src |---|---|---| | `inherited` | optional | `true` | - + ### `{ inserted, outcome?, removedCount?, start, … }` -SHA-256: `fa106c5b33b657e8ef16e8cbf39f3b7d44e5cbcdf4f9303d9db6410f28951e42` +SHA-256: `b2a948a5340a9341fa3bab99507c7090c5b0456de72ec28b5b157b8df3f1d640` Sources: [`packages/core/agent/src/types.ts:110`](../packages/core/agent/src/types.ts) | Property | Presence | Type | |---|---|---| -| `inserted` | required | [`UserMessage[]`](#persistence-type-sha256-454a0fa879e38076bdcdbd87ef561cd67d7e43e58a287cf7dbaa7b6cf7847020) | +| `inserted` | required | [`UserMessage[]`](#persistence-type-sha256-5d00abf938272f22eb47199e9d40801e8d8ec4c88e2b138b15aa2a5a5bf87520) | | `outcome` | optional | `"canceled"` | | `removedCount` | optional | `number` | | `start` | required | `number` | @@ -6417,6 +6432,91 @@ Sources: [`packages/core/session/src/types.ts:205`](../packages/core/session/src | `kind` | required | `"aborted"` | | `reason` | required | [`TurnEndCancelCause`](#persistence-type-sha256-1f8c126cfab3df812cd115cfc23e3256f0fa2383400f4ce91ecad313577e59b0) | + + +### `{ kind: "action-cards" }` + +SHA-256: `dde0cdf9d426768b649b5a9783d2144e7f900c35ef65229cd89036dbf411d1b0` + +| Property | Presence | Type | +|---|---|---| +| `kind` | required | `"action-cards"` | +| `through` | required | `number` | + + + +### `{ kind: "action-cards", form: "catalog" }` + +SHA-256: `dd00cdccd0ba487b92362ef67db58cd264f48c1bfd368291424a1da9a899489a` + +| Property | Presence | Type | +|---|---|---| +| `form` | required | `"catalog"` | +| `kind` | required | `"action-cards"` | +| `through` | required | `number` | + + + +### `{ kind: "action-cards", form: "instructions" }` + +SHA-256: `ba2a1a8f194b1bb21f270713398f6643e206e8ff5f7f26f6d36a88e3f0f0ab10` + +| Property | Presence | Type | +|---|---|---| +| `form` | required | `"instructions"` | +| `kind` | required | `"action-cards"` | +| `through` | required | `number` | + + + +### `{ kind: "action-cards", form: "notice" }` + +SHA-256: `e7f4cbb3a57f4cbdb61ea41e95bcba626ad1fff6bb9b1e063b1d50a32da6e864` + +| Property | Presence | Type | +|---|---|---| +| `form` | required | `"notice"` | +| `kind` | required | `"action-cards"` | +| `summary` | required | `string` | +| `through` | required | `number` | + + + +### `{ kind: "action-cards", form: "recall" }` + +SHA-256: `b613f83572fe2fbf7259c72308cafd008f4fceea897749128a48bcd158f290aa` + +| Property | Presence | Type | +|---|---|---| +| `form` | required | `"recall"` | +| `kind` | required | `"action-cards"` | +| `through` | required | `number` | + + + +### `{ kind: "action-cards", form: "relay" }` + +SHA-256: `45f005e6bbb7645248be792b715a74bc7fdf5a67f5381655d3a75dcc7aa2112e` + +| Property | Presence | Type | +|---|---|---| +| `form` | required | `"relay"` | +| `kind` | required | `"action-cards"` | +| `through` | required | `number` | + + + +### `{ kind: "action-cards", form: "snapshot" }` + +SHA-256: `3247e566f4340029325b70e4366b56fa9719977f1e39033b769a2ec221ab22a9` + +| Property | Presence | Type | +|---|---|---| +| `form` | required | `"snapshot"` | +| `kind` | required | `"action-cards"` | +| `sections` | required | [`ContextSnapshotSection[]`](#persistence-type-sha256-c748fb42c80a120038a8c587af80ec39924803c401364db11c8974d5bf6d0262) | +| `through` | required | `number` | + ### `{ kind: "blocked" }` @@ -7943,17 +8043,17 @@ SHA-256: `a10c17474eaf2ddab7095a099e0fe3d046fc18e56c3e344fc8894c05ff9ef97b` | `time` | required | `number` | | `type` | required | `"agent-preset/selected"` | - + ### `{ type: "agent/inbox/spliced" }` -SHA-256: `0755b7d1128319ba2e389619cbbf2c961ad648882b4e91da7f3e9582cc8a2034` +SHA-256: `b36fd842cd004b532ce96fd5d6f5be28e017eda043131aa3cf5e69edc436108e` | Property | Presence | Type | |---|---|---| -| `data` | required | [`{ inserted, outcome?, removedCount?, start, … }`](#persistence-type-sha256-fa106c5b33b657e8ef16e8cbf39f3b7d44e5cbcdf4f9303d9db6410f28951e42) | +| `data` | required | [`{ inserted, outcome?, removedCount?, start, … }`](#persistence-type-sha256-b2a948a5340a9341fa3bab99507c7090c5b0456de72ec28b5b157b8df3f1d640) | | `ignorable` | optional | `true` | | `seq` | required | `number` | | `time` | required | `number` | @@ -8194,17 +8294,17 @@ SHA-256: `13d3d180f977bf78081d487ffa0ecb75857349bcab29a5a3fb48189fca2a6176` | `time` | required | `number` | | `type` | required | `"deliverables/presented"` | - + ### `{ type: "developer/message" }` -SHA-256: `97e0162f983c27456ec71269fb4976b1851588b8c1aeac5d071d43e11f95e03c` +SHA-256: `5ef58f2a1bdc576c1f12fa1f4da1920ece020b13c0f991a578cddcbeb28bc5be` | Property | Presence | Type | |---|---|---| -| `data` | required | [`{ headerSeq?, message, step, turn }`](#persistence-type-sha256-f4ed4717b4c259e933b550a8dcd90dc93628cda8a3c7affcaa53b9c13c1d53eb) | +| `data` | required | [`{ headerSeq?, message, step, turn }`](#persistence-type-sha256-7404c6123fdaa591474a37979a5a8ae70d1387af7c9f09bfe568b218935a974d) | | `ignorable` | optional | `true` | | `seq` | required | `number` | | `sourceEventSeqs` | optional | [`number[]`](#persistence-type-sha256-5d03ba38734809bcbd2a55221bd938b3b3fc34b49bb686c6113e9e4931e3aa78) | @@ -8604,17 +8704,17 @@ SHA-256: `1b912703e2d64f91c99c675b8f805b01076c8325b905c1218ad81ef0b24909d5` | `time` | required | `number` | | `type` | required | `"session/title"` | - + ### `{ type: "session/title-llm-request" }` -SHA-256: `79a4c87f8bfb4d6d05cd9952b99210d6a7db3b478c4a01d94b4c50602308c056` +SHA-256: `efa1a091017ce533e38b0ef3e14d11a5c102ff1793229b18dad719a4268d9adc` | Property | Presence | Type | |---|---|---| -| `data` | required | [`SessionTitleLlmRequestEventData`](#persistence-type-sha256-00c054d3018c198d92f459563331edd1385e58345c677bb499791df2da0ade9f) | +| `data` | required | [`SessionTitleLlmRequestEventData`](#persistence-type-sha256-a3e299483348f27a4787a36bb3f29cd86024c65336abc10bb3d687468302c4cf) | | `ignorable` | optional | `true` | | `seq` | required | `number` | | `time` | required | `number` | @@ -9037,17 +9137,17 @@ Sources: [`packages/llm/llm/src/types.ts:476`](../packages/llm/llm/src/types.ts) | `type` | required | `"usage"` | | `usage` | required | [`TokenUsage`](#persistence-type-sha256-fec5442b9ee4122e10514d81d5a43648003fe8bfb7ee2ba50c0cdbdd383ed394) | - + ### `{ type: "user/message" }` -SHA-256: `2ae8594a998c9862a597129e782277bcc518266712a703b5c3161bdf552d13b8` +SHA-256: `58b122df513aeaf7ddc5783dbb1fd844e2979bb273964e30ae351a8db6510019` | Property | Presence | Type | |---|---|---| -| `data` | required | [`UserMessage`](#persistence-type-sha256-2cf075b013d363ad743b5a4982900678d108499c721bc11255137402003db711) | +| `data` | required | [`UserMessage`](#persistence-type-sha256-b782058cef83caf3eb44a16adeef5657e4d5cdff2d50655aba418c4490670a69) | | `ignorable` | optional | `true` | | `seq` | required | `number` | | `sourceEventSeqs` | optional | [`number[]`](#persistence-type-sha256-5d03ba38734809bcbd2a55221bd938b3b3fc34b49bb686c6113e9e4931e3aa78) | diff --git a/docs/persistence-catalog.zh.md b/docs/persistence-catalog.zh.md index 1d0d3e0bea..5ce100585a 100644 --- a/docs/persistence-catalog.zh.md +++ b/docs/persistence-catalog.zh.md @@ -25,7 +25,7 @@ | `JsonlHeaderLine` | header | `18ee62b8900a4c3d046700f05d7a4d49d6cab2a660a020c87481d1603dd8bd4f` | [`HeaderLine`](#persistence-type-sha256-18ee62b8900a4c3d046700f05d7a4d49d6cab2a660a020c87481d1603dd8bd4f) | | `SessionEventEnvelope` | envelope | `5776e5553ff2dfe3f5bc202dbb1e7c9f93e35a531aebb7764c23b2b6153b2ccc` | [`union (3 variants)`](#persistence-type-sha256-5776e5553ff2dfe3f5bc202dbb1e7c9f93e35a531aebb7764c23b2b6153b2ccc) | | `event:agent-preset/selected` | event | `a10c17474eaf2ddab7095a099e0fe3d046fc18e56c3e344fc8894c05ff9ef97b` | [`{ type: "agent-preset/selected" }`](#persistence-type-sha256-a10c17474eaf2ddab7095a099e0fe3d046fc18e56c3e344fc8894c05ff9ef97b) | -| `event:agent/inbox/spliced` | event | `0755b7d1128319ba2e389619cbbf2c961ad648882b4e91da7f3e9582cc8a2034` | [`{ type: "agent/inbox/spliced" }`](#persistence-type-sha256-0755b7d1128319ba2e389619cbbf2c961ad648882b4e91da7f3e9582cc8a2034) | +| `event:agent/inbox/spliced` | event | `b36fd842cd004b532ce96fd5d6f5be28e017eda043131aa3cf5e69edc436108e` | [`{ type: "agent/inbox/spliced" }`](#persistence-type-sha256-b36fd842cd004b532ce96fd5d6f5be28e017eda043131aa3cf5e69edc436108e) | | `event:approval/asked` | event | `3bfeb47b58606f4661904bc723da612782214c463d01e6d61cd6d6193d7374e1` | [`{ type: "approval/asked" }`](#persistence-type-sha256-3bfeb47b58606f4661904bc723da612782214c463d01e6d61cd6d6193d7374e1) | | `event:approval/decided` | event | `bb1ab3d08f49a9f3b265f844cd78d5c49813062a7b34b54904b426f85d0ff6e3` | [`{ type: "approval/decided" }`](#persistence-type-sha256-bb1ab3d08f49a9f3b265f844cd78d5c49813062a7b34b54904b426f85d0ff6e3) | | `event:approval/policy` | event | `26718e15e7e395bce9642dba5bbe09b3b1a4ce2213d20d566cd9207d7fc5fb78` | [`{ type: "approval/policy" }`](#persistence-type-sha256-26718e15e7e395bce9642dba5bbe09b3b1a4ce2213d20d566cd9207d7fc5fb78) | @@ -38,7 +38,7 @@ | `event:compaction/start` | event | `db874d463b0fdec77e9da1c4568f37cb70bd6596781eb93800db44fb8a116965` | [`{ type: "compaction/start" }`](#persistence-type-sha256-db874d463b0fdec77e9da1c4568f37cb70bd6596781eb93800db44fb8a116965) | | `event:compaction/summary` | event | `e2f9a41e0989f54ed8cee80f8db2bcf9d60a5c810dc9d45b83fa050b9dce7602` | [`{ type: "compaction/summary" }`](#persistence-type-sha256-e2f9a41e0989f54ed8cee80f8db2bcf9d60a5c810dc9d45b83fa050b9dce7602) | | `event:deliverables/presented` | event | `13d3d180f977bf78081d487ffa0ecb75857349bcab29a5a3fb48189fca2a6176` | [`{ type: "deliverables/presented" }`](#persistence-type-sha256-13d3d180f977bf78081d487ffa0ecb75857349bcab29a5a3fb48189fca2a6176) | -| `event:developer/message` | event | `97e0162f983c27456ec71269fb4976b1851588b8c1aeac5d071d43e11f95e03c` | [`{ type: "developer/message" }`](#persistence-type-sha256-97e0162f983c27456ec71269fb4976b1851588b8c1aeac5d071d43e11f95e03c) | +| `event:developer/message` | event | `5ef58f2a1bdc576c1f12fa1f4da1920ece020b13c0f991a578cddcbeb28bc5be` | [`{ type: "developer/message" }`](#persistence-type-sha256-5ef58f2a1bdc576c1f12fa1f4da1920ece020b13c0f991a578cddcbeb28bc5be) | | `event:feedback/message-delete` | event | `3ee93b06f3a125850337602bcdf155d2538c43a5c944ec55b1b3c365152d6796` | [`{ type: "feedback/message-delete" }`](#persistence-type-sha256-3ee93b06f3a125850337602bcdf155d2538c43a5c944ec55b1b3c365152d6796) | | `event:feedback/message-put` | event | `b5086d249e8502e9ead1d39156bb8d559bde7951cac0f14ce150345b4e42a2bf` | [`{ type: "feedback/message-put" }`](#persistence-type-sha256-b5086d249e8502e9ead1d39156bb8d559bde7951cac0f14ce150345b4e42a2bf) | | `event:feedback/record` | event | `b54940ff095c17e874c5be03815f4c2145a256cf3a1d34dae4ab2f7769dfffe8` | [`{ type: "feedback/record" }`](#persistence-type-sha256-b54940ff095c17e874c5be03815f4c2145a256cf3a1d34dae4ab2f7769dfffe8) | @@ -60,7 +60,7 @@ | `event:session-log-deepseek/delivery-accepted` | event | `d63b8b8ffad9c02fd80c43a17df4f240c1fe8118ecca9de34f9d5871838ab5b9` | [`{ type: "session-log-deepseek/delivery-accepted" }`](#persistence-type-sha256-d63b8b8ffad9c02fd80c43a17df4f240c1fe8118ecca9de34f9d5871838ab5b9) | | `event:session/end-seed` | event | `5e6db6e24948d4a853c71cb9fabd252ad051ce93d4672c1266cf837c1c17b84e` | [`{ type: "session/end-seed" }`](#persistence-type-sha256-5e6db6e24948d4a853c71cb9fabd252ad051ce93d4672c1266cf837c1c17b84e) | | `event:session/title` | event | `1b912703e2d64f91c99c675b8f805b01076c8325b905c1218ad81ef0b24909d5` | [`{ type: "session/title" }`](#persistence-type-sha256-1b912703e2d64f91c99c675b8f805b01076c8325b905c1218ad81ef0b24909d5) | -| `event:session/title-llm-request` | event | `79a4c87f8bfb4d6d05cd9952b99210d6a7db3b478c4a01d94b4c50602308c056` | [`{ type: "session/title-llm-request" }`](#persistence-type-sha256-79a4c87f8bfb4d6d05cd9952b99210d6a7db3b478c4a01d94b4c50602308c056) | +| `event:session/title-llm-request` | event | `efa1a091017ce533e38b0ef3e14d11a5c102ff1793229b18dad719a4268d9adc` | [`{ type: "session/title-llm-request" }`](#persistence-type-sha256-efa1a091017ce533e38b0ef3e14d11a5c102ff1793229b18dad719a4268d9adc) | | `event:step/end` | event | `e0a787e6ec76c7c94fecbc501b489164ab0293db05bc947914077ad01e674f05` | [`{ type: "step/end" }`](#persistence-type-sha256-e0a787e6ec76c7c94fecbc501b489164ab0293db05bc947914077ad01e674f05) | | `event:step/start` | event | `4513e088d43e6c68425be30451b9f961cc264fe7318ca62681c41d4d78615986` | [`{ type: "step/start" }`](#persistence-type-sha256-4513e088d43e6c68425be30451b9f961cc264fe7318ca62681c41d4d78615986) | | `event:subagent/catalog` | event | `53750b9abf61efb7992b0aadc3e247ec927449c63266df28230fd1139f5590ac` | [`{ type: "subagent/catalog" }`](#persistence-type-sha256-53750b9abf61efb7992b0aadc3e247ec927449c63266df28230fd1139f5590ac) | @@ -82,7 +82,7 @@ | `event:tool/result` | event | `7c9f44e90a0058f4cc532ae20dad0c10afa6eba22e70a6c79fc79490bad64397` | [`{ type: "tool/result" }`](#persistence-type-sha256-7c9f44e90a0058f4cc532ae20dad0c10afa6eba22e70a6c79fc79490bad64397) | | `event:turn/end` | event | `0f8512903d94f57a4748fa1a2092e64342856796684e6b8343db685b192745ce` | [`{ type: "turn/end" }`](#persistence-type-sha256-0f8512903d94f57a4748fa1a2092e64342856796684e6b8343db685b192745ce) | | `event:turn/start` | event | `aa0957eca50aeb28bcd2e6930b95809926edacb550c8c340ba526ba6b861b3d8` | [`{ type: "turn/start" }`](#persistence-type-sha256-aa0957eca50aeb28bcd2e6930b95809926edacb550c8c340ba526ba6b861b3d8) | -| `event:user/message` | event | `2ae8594a998c9862a597129e782277bcc518266712a703b5c3161bdf552d13b8` | [`{ type: "user/message" }`](#persistence-type-sha256-2ae8594a998c9862a597129e782277bcc518266712a703b5c3161bdf552d13b8) | +| `event:user/message` | event | `58b122df513aeaf7ddc5783dbb1fd844e2979bb273964e30ae351a8db6510019` | [`{ type: "user/message" }`](#persistence-type-sha256-58b122df513aeaf7ddc5783dbb1fd844e2979bb273964e30ae351a8db6510019) | | `event:web/deepseek-search-llm-request` | event | `0c556c9b9f2a96238a6b9bbb99e748206cbff2ed6203041c9571f04ae5802f52` | [`{ type: "web/deepseek-search-llm-request" }`](#persistence-type-sha256-0c556c9b9f2a96238a6b9bbb99e748206cbff2ed6203041c9571f04ae5802f52) | | `event:working-directory/change` | event | `124aa62078ca3311353b39cc3bf6c75da281338be6bbf672f8b361d828cddca3` | [`{ type: "working-directory/change" }`](#persistence-type-sha256-124aa62078ca3311353b39cc3bf6c75da281338be6bbf672f8b361d828cddca3) | | `event:workspace/changes` | event | `e308ccf867a5398e316e0af8cb6ce238a8d33a63b9b384c8250a686786285f72` | [`{ type: "workspace/changes" }`](#persistence-type-sha256-e308ccf867a5398e316e0af8cb6ce238a8d33a63b9b384c8250a686786285f72) | @@ -1349,6 +1349,14 @@ SHA-256: `1e0b0b66b6f0c53fc72e52289d06b4708d776f1ca9df4c86a22bff621d403cf0` `"aborted"` + + +### `"action-cards"` + +SHA-256: `05205074b24d8340cfd7995f6cb64779c7f1241490954700115601e264178877` + +`"action-cards"` + ### `"active"` @@ -3367,7 +3375,7 @@ SHA-256: `abd6ca62a5ab166553848324a23fef90eed7f59c8021bd63e7ea5bc6fd666b0c` | `body` | 必需 | [`{ max_tokens, messages, model, thinking?, … }`](#persistence-type-sha256-9c001ecb5e1d3c3894410db2a1e383842e5f8454fb52182338686158abc1b3ee) | | `endpoint` | 必需 | `string` | - + @@ -3375,7 +3383,7 @@ SHA-256: `abd6ca62a5ab166553848324a23fef90eed7f59c8021bd63e7ea5bc6fd666b0c` ### `DeveloperMessage` -SHA-256: `0f6265188dae7ba6faa26830a180bb4deb0760561f97ee0c9d399ccf6c9cf1f7` +SHA-256: `7580e7d96dc8475fffe813a42af499665de3d4517fcdb3bc2337d0340e98d616` 来源:[`packages/llm/llm/src/message.ts:157`](../packages/llm/llm/src/message.ts) @@ -3384,11 +3392,11 @@ SHA-256: `0f6265188dae7ba6faa26830a180bb4deb0760561f97ee0c9d399ccf6c9cf1f7` | `content` | 必需 | [`ContentBlock[]`](#persistence-type-sha256-b52f215a6cd691058a3413c12f161fa3b563a2f4895adb3df6f744ec4b0a6ef1) | | `id` | 必需 | `string` | | `role` | 必需 | `"developer"` | -| `source` | 必需 | [`MessageSource`](#persistence-type-sha256-5bc2d7f02503fc6b3e1bb7e0b7fa22c0b9e4b9841fa3175b2ad8e607ae3525a8) | +| `source` | 必需 | [`MessageSource`](#persistence-type-sha256-7a5b68bd1a5118c827df9e2466acc5e4d16b83e3f97a2304d992958647592690) | 消息源兼容策略:`source` — `session-source-attribution` v1; `session.developer-message.source`; `kind`; `preserve`. -仅表示归属的新增 kind:`tmux-context`, `user-question-reply`, `working-directory`. +仅表示归属的新增 kind:`action-cards`, `tmux-context`, `user-question-reply`, `working-directory`. @@ -4083,7 +4091,7 @@ SHA-256: `6f72ece8f317cd080c4f20a93f1bf709965f5b81ac4669528f5a023191cfcf20` | `step` | 必需 | `number` | | `turn` | 必需 | `number` | - + @@ -4091,17 +4099,17 @@ SHA-256: `6f72ece8f317cd080c4f20a93f1bf709965f5b81ac4669528f5a023191cfcf20` ### `Message` -SHA-256: `040cd65423bd64264d225504f1c572d239eeb1793d0c2856ad8e42bb6e1e148b` +SHA-256: `cda79b839d8cc0c7a7bd09dd067de04925f6e9037080d878b65aaa5a383f117c` 来源:[`packages/llm/llm/src/message.ts:196`](../packages/llm/llm/src/message.ts) 以下类型之一: - [`ToolResultMessage`](#persistence-type-sha256-4b259f7f8f0a44ad5e90b70a73553ced1df27012d126921d397ff403b6c86731) -- [`DeveloperMessage`](#persistence-type-sha256-0f6265188dae7ba6faa26830a180bb4deb0760561f97ee0c9d399ccf6c9cf1f7) -- [`UserMessage`](#persistence-type-sha256-2cf075b013d363ad743b5a4982900678d108499c721bc11255137402003db711) -- [`AssistantMessage`](#persistence-type-sha256-fa87537f4a9c1e25602990a8bf686b383e9e00352ec8946e16ae24ba7c1625be) +- [`DeveloperMessage`](#persistence-type-sha256-7580e7d96dc8475fffe813a42af499665de3d4517fcdb3bc2337d0340e98d616) +- [`UserMessage`](#persistence-type-sha256-b782058cef83caf3eb44a16adeef5657e4d5cdff2d50655aba418c4490670a69) - [`SystemMessage`](#persistence-type-sha256-09f3f8dc417f278257202714132f98ba7b52957d7b558c4a04456603890eb363) +- [`AssistantMessage`](#persistence-type-sha256-fa87537f4a9c1e25602990a8bf686b383e9e00352ec8946e16ae24ba7c1625be) @@ -4176,7 +4184,7 @@ SHA-256: `390eed1028aba02774f14408647acec10ec5516e97d4f15ecde2b9989d5a69f2` - `"negative"` - `"positive"` - + @@ -4184,12 +4192,19 @@ SHA-256: `390eed1028aba02774f14408647acec10ec5516e97d4f15ecde2b9989d5a69f2` ### `MessageSource` -SHA-256: `5bc2d7f02503fc6b3e1bb7e0b7fa22c0b9e4b9841fa3175b2ad8e607ae3525a8` +SHA-256: `7a5b68bd1a5118c827df9e2466acc5e4d16b83e3f97a2304d992958647592690` 来源:[`packages/llm/llm/src/message.ts:136`](../packages/llm/llm/src/message.ts) | kind | form 属性 | 其他必需字段 | 完整定义 | |---|---|---|---| +| `action-cards` | `"catalog"` | `through`: `number` | [`{ kind: "action-cards", form: "catalog" }`](#persistence-type-sha256-dd00cdccd0ba487b92362ef67db58cd264f48c1bfd368291424a1da9a899489a) | +| `action-cards` | `"instructions"` | `through`: `number` | [`{ kind: "action-cards", form: "instructions" }`](#persistence-type-sha256-ba2a1a8f194b1bb21f270713398f6643e206e8ff5f7f26f6d36a88e3f0f0ab10) | +| `action-cards` | `"notice"` | `summary`: `string`; `through`: `number` | [`{ kind: "action-cards", form: "notice" }`](#persistence-type-sha256-e7f4cbb3a57f4cbdb61ea41e95bcba626ad1fff6bb9b1e063b1d50a32da6e864) | +| `action-cards` | `"recall"` | `through`: `number` | [`{ kind: "action-cards", form: "recall" }`](#persistence-type-sha256-b613f83572fe2fbf7259c72308cafd008f4fceea897749128a48bcd158f290aa) | +| `action-cards` | `"relay"` | `through`: `number` | [`{ kind: "action-cards", form: "relay" }`](#persistence-type-sha256-45f005e6bbb7645248be792b715a74bc7fdf5a67f5381655d3a75dcc7aa2112e) | +| `action-cards` | `"snapshot"` | `sections`: [`ContextSnapshotSection[]`](#persistence-type-sha256-c748fb42c80a120038a8c587af80ec39924803c401364db11c8974d5bf6d0262); `through`: `number` | [`{ kind: "action-cards", form: "snapshot" }`](#persistence-type-sha256-3247e566f4340029325b70e4366b56fa9719977f1e39033b769a2ec221ab22a9) | +| `action-cards` | 未声明 | `through`: `number` | [`{ kind: "action-cards" }`](#persistence-type-sha256-dde0cdf9d426768b649b5a9783d2144e7f900c35ef65229cd89036dbf411d1b0) | | `agent-instructions` | `"instructions"` | `changes`: [`AgentInstructionChange[]`](#persistence-type-sha256-2ab12a0e345658f9342816d773fe610f049982bcad82631f43b9205abe43e5ee) | [`AgentInstructionSource`](#persistence-type-sha256-6f22a9d02b9a1f1157f3b0e7bdccd7e7b12b085c4efdb709dfc74a465cd4e590) | | `agent-message` | `"relay"` | `senderSessionId`: `string` | [`AgentMessageSource`](#persistence-type-sha256-4a9bef9f27a942520cc2279ca7dd3082556e4bff543f66b5c0333e1bbed1cd86) | | `compact-checkpoint` | 未声明 | `compactionId`: `string` | [`CompactionCheckpointSource`](#persistence-type-sha256-31907de9563751822af6d9be02bafaf26faa098bbd1cb412524134d577cc1648) | @@ -4302,13 +4317,13 @@ SHA-256: `5bc2d7f02503fc6b3e1bb7e0b7fa22c0b9e4b9841fa3175b2ad8e607ae3525a8` | `webhook` | `"notice"` | `deliveryId`: `string`; `provider`: `string`; `ruleId`: `string`; `source`: `string`; `summary`: `string` | [`{ kind: "webhook", form: "notice" }`](#persistence-type-sha256-9edc162949abf93bd5ff049191b345454ba104f583937ce5580fb50f067d2b94) | | `working-directory` | 未声明 | 无 | [`{ kind: "working-directory" }`](#persistence-type-sha256-2235d957bf4b7c6cf959231d480f0ba1f3a6d72d82981493fb6c7c963021d3d5) | - + ### `Message[]` -SHA-256: `a927d444c928ac0efd9dd8b2e7d7d04d711c264cb71578d81e0e2fd143ff669f` +SHA-256: `78b9204976ab9faaf133c3488b73d1dfb6f1a4bb6c0903e3473cd41a5e1e39e1` -[`Message`](#persistence-type-sha256-040cd65423bd64264d225504f1c572d239eeb1793d0c2856ad8e42bb6e1e148b) 的数组。 +[`Message`](#persistence-type-sha256-cda79b839d8cc0c7a7bd09dd067de04925f6e9037080d878b65aaa5a383f117c) 的数组。 @@ -4692,7 +4707,7 @@ SHA-256: `b56f6a885da3dc394c69a3e3cc5c0cfc4da15601b63fda4989eee8f40efc5be4` | `source` | 必需 | [`SessionTitleSource`](#persistence-type-sha256-30d5a537b296685770b1241c4b4bbde19d5a31ff81aa23df73fab5e90b8fc65f) | | `title` | 必需 | `string` | - + @@ -4700,7 +4715,7 @@ SHA-256: `b56f6a885da3dc394c69a3e3cc5c0cfc4da15601b63fda4989eee8f40efc5be4` ### `SessionTitleLlmRequestEventData` -SHA-256: `00c054d3018c198d92f459563331edd1385e58345c677bb499791df2da0ade9f` +SHA-256: `a3e299483348f27a4787a36bb3f29cd86024c65336abc10bb3d687468302c4cf` 来源:[`packages/session/session-title-llm/src/index.ts:37`](../packages/session/session-title-llm/src/index.ts) @@ -4708,7 +4723,7 @@ SHA-256: `00c054d3018c198d92f459563331edd1385e58345c677bb499791df2da0ade9f` |---|---|---| | `maxTokens` | 必需 | `number` | | `messageSeqs` | 必需 | [`number[]`](#persistence-type-sha256-5d03ba38734809bcbd2a55221bd938b3b3fc34b49bb686c6113e9e4931e3aa78) | -| `messages` | 必需 | [`Message[]`](#persistence-type-sha256-a927d444c928ac0efd9dd8b2e7d7d04d711c264cb71578d81e0e2fd143ff669f) | +| `messages` | 必需 | [`Message[]`](#persistence-type-sha256-78b9204976ab9faaf133c3488b73d1dfb6f1a4bb6c0903e3473cd41a5e1e39e1) | | `reasoningEffort` | 可选 | `string` | | `route` | 必需 | [`SessionTitleModelIdentity`](#persistence-type-sha256-07e1a58c58b593f507b8e7f25723bb0aa42baae6e00f5ecfddf28cb1e64d3235) | | `system` | 必需 | `string` | @@ -5364,7 +5379,7 @@ SHA-256: `f1687f982fa3d41ef29e159feed154ecd13af4dd209acaee16038f223f2e9402` | `interrupted` | 未声明 | 无 | [`{ kind: "interrupted" }`](#persistence-type-sha256-56b7c047694875a843ba020dfea3c8380d54ae4ff88ab349e72004dc21e07c23) | | `max-tokens` | 未声明 | 无 | [`{ kind: "max-tokens" }`](#persistence-type-sha256-ab7ec5f9eb2c6dfacca5da4c37cf7ba85d04172053ee6b1ebf1709ffcd99c1a7) | - + @@ -5372,7 +5387,7 @@ SHA-256: `f1687f982fa3d41ef29e159feed154ecd13af4dd209acaee16038f223f2e9402` ### `UserMessage` -SHA-256: `2cf075b013d363ad743b5a4982900678d108499c721bc11255137402003db711` +SHA-256: `b782058cef83caf3eb44a16adeef5657e4d5cdff2d50655aba418c4490670a69` 来源:[`packages/llm/llm/src/message.ts:162`](../packages/llm/llm/src/message.ts) @@ -5381,19 +5396,19 @@ SHA-256: `2cf075b013d363ad743b5a4982900678d108499c721bc11255137402003db711` | `content` | 必需 | [`ContentBlock[]`](#persistence-type-sha256-b52f215a6cd691058a3413c12f161fa3b563a2f4895adb3df6f744ec4b0a6ef1) | | `id` | 必需 | `string` | | `role` | 必需 | `"user"` | -| `source` | 必需 | [`MessageSource`](#persistence-type-sha256-5bc2d7f02503fc6b3e1bb7e0b7fa22c0b9e4b9841fa3175b2ad8e607ae3525a8) | +| `source` | 必需 | [`MessageSource`](#persistence-type-sha256-7a5b68bd1a5118c827df9e2466acc5e4d16b83e3f97a2304d992958647592690) | 消息源兼容策略:`source` — `session-source-attribution` v1; `session.user-message.source`; `kind`; `preserve`. -仅表示归属的新增 kind:`tmux-context`, `user-question-reply`, `working-directory`. +仅表示归属的新增 kind:`action-cards`, `tmux-context`, `user-question-reply`, `working-directory`. - + ### `UserMessage[]` -SHA-256: `454a0fa879e38076bdcdbd87ef561cd67d7e43e58a287cf7dbaa7b6cf7847020` +SHA-256: `5d00abf938272f22eb47199e9d40801e8d8ec4c88e2b138b15aa2a5a5bf87520` -[`UserMessage`](#persistence-type-sha256-2cf075b013d363ad743b5a4982900678d108499c721bc11255137402003db711) 的数组。 +[`UserMessage`](#persistence-type-sha256-b782058cef83caf3eb44a16adeef5657e4d5cdff2d50655aba418c4490670a69) 的数组。 @@ -6278,18 +6293,18 @@ SHA-256: `ad0c38a0adb82a94c925456df41586d9b5f9202fca18593eae2213d4445b4585` | `reason` | 必需 | [`RequestHeaderReason`](#persistence-type-sha256-530da6c0267813bb4511af72883cc518834e624e3b3220a7062955fd57a788a6) | | `startsSeries` | 可选 | `true` | - + ### `{ headerSeq?, message, step, turn }` -SHA-256: `f4ed4717b4c259e933b550a8dcd90dc93628cda8a3c7affcaa53b9c13c1d53eb` +SHA-256: `7404c6123fdaa591474a37979a5a8ae70d1387af7c9f09bfe568b218935a974d` 来源:[`packages/core/session/src/types.ts:313`](../packages/core/session/src/types.ts) | 属性 | 存在性 | 类型 | |---|---|---| | `headerSeq` | 可选 | `number` | -| `message` | 必需 | [`DeveloperMessage`](#persistence-type-sha256-0f6265188dae7ba6faa26830a180bb4deb0760561f97ee0c9d399ccf6c9cf1f7) | +| `message` | 必需 | [`DeveloperMessage`](#persistence-type-sha256-7580e7d96dc8475fffe813a42af499665de3d4517fcdb3bc2337d0340e98d616) | | `step` | 必需 | `number` | | `turn` | 必需 | `number` | @@ -6373,17 +6388,17 @@ SHA-256: `17d1afb770d9941936130996da00dc86782cfef731d8d6526162c301256a4ac3` |---|---|---| | `inherited` | 可选 | `true` | - + ### `{ inserted, outcome?, removedCount?, start, … }` -SHA-256: `fa106c5b33b657e8ef16e8cbf39f3b7d44e5cbcdf4f9303d9db6410f28951e42` +SHA-256: `b2a948a5340a9341fa3bab99507c7090c5b0456de72ec28b5b157b8df3f1d640` 来源:[`packages/core/agent/src/types.ts:110`](../packages/core/agent/src/types.ts) | 属性 | 存在性 | 类型 | |---|---|---| -| `inserted` | 必需 | [`UserMessage[]`](#persistence-type-sha256-454a0fa879e38076bdcdbd87ef561cd67d7e43e58a287cf7dbaa7b6cf7847020) | +| `inserted` | 必需 | [`UserMessage[]`](#persistence-type-sha256-5d00abf938272f22eb47199e9d40801e8d8ec4c88e2b138b15aa2a5a5bf87520) | | `outcome` | 可选 | `"canceled"` | | `removedCount` | 可选 | `number` | | `start` | 必需 | `number` | @@ -6419,6 +6434,91 @@ SHA-256: `0a30c3be336cdd4f8f34e66543a3d46d611c43986edd9b3ce6cbb9b721d3b5d3` | `kind` | 必需 | `"aborted"` | | `reason` | 必需 | [`TurnEndCancelCause`](#persistence-type-sha256-1f8c126cfab3df812cd115cfc23e3256f0fa2383400f4ce91ecad313577e59b0) | + + +### `{ kind: "action-cards" }` + +SHA-256: `dde0cdf9d426768b649b5a9783d2144e7f900c35ef65229cd89036dbf411d1b0` + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `kind` | 必需 | `"action-cards"` | +| `through` | 必需 | `number` | + + + +### `{ kind: "action-cards", form: "catalog" }` + +SHA-256: `dd00cdccd0ba487b92362ef67db58cd264f48c1bfd368291424a1da9a899489a` + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `form` | 必需 | `"catalog"` | +| `kind` | 必需 | `"action-cards"` | +| `through` | 必需 | `number` | + + + +### `{ kind: "action-cards", form: "instructions" }` + +SHA-256: `ba2a1a8f194b1bb21f270713398f6643e206e8ff5f7f26f6d36a88e3f0f0ab10` + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `form` | 必需 | `"instructions"` | +| `kind` | 必需 | `"action-cards"` | +| `through` | 必需 | `number` | + + + +### `{ kind: "action-cards", form: "notice" }` + +SHA-256: `e7f4cbb3a57f4cbdb61ea41e95bcba626ad1fff6bb9b1e063b1d50a32da6e864` + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `form` | 必需 | `"notice"` | +| `kind` | 必需 | `"action-cards"` | +| `summary` | 必需 | `string` | +| `through` | 必需 | `number` | + + + +### `{ kind: "action-cards", form: "recall" }` + +SHA-256: `b613f83572fe2fbf7259c72308cafd008f4fceea897749128a48bcd158f290aa` + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `form` | 必需 | `"recall"` | +| `kind` | 必需 | `"action-cards"` | +| `through` | 必需 | `number` | + + + +### `{ kind: "action-cards", form: "relay" }` + +SHA-256: `45f005e6bbb7645248be792b715a74bc7fdf5a67f5381655d3a75dcc7aa2112e` + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `form` | 必需 | `"relay"` | +| `kind` | 必需 | `"action-cards"` | +| `through` | 必需 | `number` | + + + +### `{ kind: "action-cards", form: "snapshot" }` + +SHA-256: `3247e566f4340029325b70e4366b56fa9719977f1e39033b769a2ec221ab22a9` + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `form` | 必需 | `"snapshot"` | +| `kind` | 必需 | `"action-cards"` | +| `sections` | 必需 | [`ContextSnapshotSection[]`](#persistence-type-sha256-c748fb42c80a120038a8c587af80ec39924803c401364db11c8974d5bf6d0262) | +| `through` | 必需 | `number` | + ### `{ kind: "blocked" }` @@ -7945,17 +8045,17 @@ SHA-256: `a10c17474eaf2ddab7095a099e0fe3d046fc18e56c3e344fc8894c05ff9ef97b` | `time` | 必需 | `number` | | `type` | 必需 | `"agent-preset/selected"` | - + ### `{ type: "agent/inbox/spliced" }` -SHA-256: `0755b7d1128319ba2e389619cbbf2c961ad648882b4e91da7f3e9582cc8a2034` +SHA-256: `b36fd842cd004b532ce96fd5d6f5be28e017eda043131aa3cf5e69edc436108e` | 属性 | 存在性 | 类型 | |---|---|---| -| `data` | 必需 | [`{ inserted, outcome?, removedCount?, start, … }`](#persistence-type-sha256-fa106c5b33b657e8ef16e8cbf39f3b7d44e5cbcdf4f9303d9db6410f28951e42) | +| `data` | 必需 | [`{ inserted, outcome?, removedCount?, start, … }`](#persistence-type-sha256-b2a948a5340a9341fa3bab99507c7090c5b0456de72ec28b5b157b8df3f1d640) | | `ignorable` | 可选 | `true` | | `seq` | 必需 | `number` | | `time` | 必需 | `number` | @@ -8196,17 +8296,17 @@ SHA-256: `13d3d180f977bf78081d487ffa0ecb75857349bcab29a5a3fb48189fca2a6176` | `time` | 必需 | `number` | | `type` | 必需 | `"deliverables/presented"` | - + ### `{ type: "developer/message" }` -SHA-256: `97e0162f983c27456ec71269fb4976b1851588b8c1aeac5d071d43e11f95e03c` +SHA-256: `5ef58f2a1bdc576c1f12fa1f4da1920ece020b13c0f991a578cddcbeb28bc5be` | 属性 | 存在性 | 类型 | |---|---|---| -| `data` | 必需 | [`{ headerSeq?, message, step, turn }`](#persistence-type-sha256-f4ed4717b4c259e933b550a8dcd90dc93628cda8a3c7affcaa53b9c13c1d53eb) | +| `data` | 必需 | [`{ headerSeq?, message, step, turn }`](#persistence-type-sha256-7404c6123fdaa591474a37979a5a8ae70d1387af7c9f09bfe568b218935a974d) | | `ignorable` | 可选 | `true` | | `seq` | 必需 | `number` | | `sourceEventSeqs` | 可选 | [`number[]`](#persistence-type-sha256-5d03ba38734809bcbd2a55221bd938b3b3fc34b49bb686c6113e9e4931e3aa78) | @@ -8606,17 +8706,17 @@ SHA-256: `1b912703e2d64f91c99c675b8f805b01076c8325b905c1218ad81ef0b24909d5` | `time` | 必需 | `number` | | `type` | 必需 | `"session/title"` | - + ### `{ type: "session/title-llm-request" }` -SHA-256: `79a4c87f8bfb4d6d05cd9952b99210d6a7db3b478c4a01d94b4c50602308c056` +SHA-256: `efa1a091017ce533e38b0ef3e14d11a5c102ff1793229b18dad719a4268d9adc` | 属性 | 存在性 | 类型 | |---|---|---| -| `data` | 必需 | [`SessionTitleLlmRequestEventData`](#persistence-type-sha256-00c054d3018c198d92f459563331edd1385e58345c677bb499791df2da0ade9f) | +| `data` | 必需 | [`SessionTitleLlmRequestEventData`](#persistence-type-sha256-a3e299483348f27a4787a36bb3f29cd86024c65336abc10bb3d687468302c4cf) | | `ignorable` | 可选 | `true` | | `seq` | 必需 | `number` | | `time` | 必需 | `number` | @@ -9039,17 +9139,17 @@ SHA-256: `f1a96afe7503ac1817de769cd0ba13d9b80111c7034c50106b3344ecc14cc515` | `type` | 必需 | `"usage"` | | `usage` | 必需 | [`TokenUsage`](#persistence-type-sha256-fec5442b9ee4122e10514d81d5a43648003fe8bfb7ee2ba50c0cdbdd383ed394) | - + ### `{ type: "user/message" }` -SHA-256: `2ae8594a998c9862a597129e782277bcc518266712a703b5c3161bdf552d13b8` +SHA-256: `58b122df513aeaf7ddc5783dbb1fd844e2979bb273964e30ae351a8db6510019` | 属性 | 存在性 | 类型 | |---|---|---| -| `data` | 必需 | [`UserMessage`](#persistence-type-sha256-2cf075b013d363ad743b5a4982900678d108499c721bc11255137402003db711) | +| `data` | 必需 | [`UserMessage`](#persistence-type-sha256-b782058cef83caf3eb44a16adeef5657e4d5cdff2d50655aba418c4490670a69) | | `ignorable` | 可选 | `true` | | `seq` | 必需 | `number` | | `sourceEventSeqs` | 可选 | [`number[]`](#persistence-type-sha256-5d03ba38734809bcbd2a55221bd938b3b3fc34b49bb686c6113e9e4931e3aa78) | diff --git a/docs/persistence-changes/2026-10-22-action-card-status.i18n.yaml b/docs/persistence-changes/2026-10-22-action-card-status.i18n.yaml new file mode 100644 index 0000000000..80b7c81132 --- /dev/null +++ b/docs/persistence-changes/2026-10-22-action-card-status.i18n.yaml @@ -0,0 +1,28 @@ +# Bilingual-pair consistency record for 2026-10-22-action-card-status.md (docs/i18n/README.md): per heading +# section, a hash of its English and Chinese blocks outside code blocks and generated regions. +# After editing either side, bring the other along and re-record with: +# pnpm run verify-translation-pairing --write docs/persistence-changes/2026-10-22-action-card-status.md +/: + en: 3d5a424ae7d7e46b + zh: c22a8e4a62d8cc1a +/2026-10-22-action-card-status: + en: f580a2fb64e941db + zh: 0e34217155210da6 +/2026-10-22-action-card-status/summary: + en: 3da2594bf2891e0e + zh: 5bc7d279d99400aa +/2026-10-22-action-card-status/table-of-contents: + en: 6cf314a069ceb451 + zh: 9adff678e9ecab3b +/2026-10-22-action-card-status/declaration: + en: 6907136f28e48c90 + zh: 02f8298485f3d8e7 +/2026-10-22-action-card-status/compatibility: + en: fbf4d1ada55f35fe + zh: bd5ce9a23c55fac6 +/2026-10-22-action-card-status/verification: + en: 91043c4f0b19e7bc + zh: ee1c541133475bb8 +/2026-10-22-action-card-status/dev-note: + en: b75c8458928b72b9 + zh: 003ba4e17b4b5269 diff --git a/docs/persistence-changes/2026-10-22-action-card-status.md b/docs/persistence-changes/2026-10-22-action-card-status.md new file mode 100644 index 0000000000..4c62efb5ce --- /dev/null +++ b/docs/persistence-changes/2026-10-22-action-card-status.md @@ -0,0 +1,60 @@ +--- +description: "Records a persistence type transition and its compatibility acknowledgement." +kind: persistence-change +--- + +# 2026-10-22-action-card-status + +English | [中文](2026-10-22-action-card-status.zh.md) + +## Summary + +Adds action-card status attribution to logged model context. + +## Table of Contents + +- [Declaration](#declaration) +- [Compatibility](#compatibility) +- [Verification](#verification) +- [Dev Note](#dev-note) + + +## Declaration + +```yaml persistence-change +schemaVersion: 1 +id: 2026-10-22-action-card-status +baseline: false +changes: + - root: "event:agent/inbox/spliced" + previous: "2026-10-13-user-rpc-principal" + after: "b36fd842cd004b532ce96fd5d6f5be28e017eda043131aa3cf5e69edc436108e" + decision: same-version + - root: "event:developer/message" + previous: "2026-10-13-user-rpc-principal" + after: "5ef58f2a1bdc576c1f12fa1f4da1920ece020b13c0f991a578cddcbeb28bc5be" + decision: same-version + - root: "event:session/title-llm-request" + previous: "2026-10-13-user-rpc-principal" + after: "efa1a091017ce533e38b0ef3e14d11a5c102ff1793229b18dad719a4268d9adc" + decision: same-version + - root: "event:user/message" + previous: "2026-10-13-user-rpc-principal" + after: "58b122df513aeaf7ddc5783dbb1fd844e2979bb273964e30ae351a8db6510019" + decision: same-version +``` + + +## Compatibility + +Existing messages and records are unchanged. Readers without the action-card plugin preserve the new action-cards source kind and its JSON metadata and derive the recorded text as ordinary context. Only the producer uses the through sequence, to acknowledge delivered decisions; it grants no authority and does not change replay interpretation. + + +## Verification + +pnpm exec vitest run on the action-cards, creation-recommendations, client-ui-action-cards and client-ui-creation-recommendations tests: 120 tests passed with 100% coverage of their sources. The Web creation-recommendations replay logs the status notice of a dismissed card before the next answer. + + +## Dev Note + +None. diff --git a/docs/persistence-changes/2026-10-22-action-card-status.schema.json b/docs/persistence-changes/2026-10-22-action-card-status.schema.json new file mode 100644 index 0000000000..9aa642699a --- /dev/null +++ b/docs/persistence-changes/2026-10-22-action-card-status.schema.json @@ -0,0 +1,249 @@ +{ + "formatVersion": 2, + "roots": [ + {"key":"event:agent/inbox/spliced","kind":"event","event":"agent/inbox/spliced","surface":false,"digest":"b36fd842cd004b532ce96fd5d6f5be28e017eda043131aa3cf5e69edc436108e"}, + {"key":"event:developer/message","kind":"event","event":"developer/message","surface":true,"digest":"5ef58f2a1bdc576c1f12fa1f4da1920ece020b13c0f991a578cddcbeb28bc5be"}, + {"key":"event:session/title-llm-request","kind":"event","event":"session/title-llm-request","surface":false,"digest":"efa1a091017ce533e38b0ef3e14d11a5c102ff1793229b18dad719a4268d9adc"}, + {"key":"event:user/message","kind":"event","event":"user/message","surface":true,"digest":"58b122df513aeaf7ddc5783dbb1fd844e2979bb273964e30ae351a8db6510019"} + ], + "types": [], + "nodes": { + "0203243b72fc867a": {"kind":"object","properties":[{"name":"kind","type":"24b10f7cdae64928","optional":false}],"indices":[]}, + "022623b9ef98ec47": {"kind":"object","properties":[{"name":"form","type":"383cf73ae4748e5b","optional":false},{"name":"kind","type":"f68c335306535a00","optional":false}],"indices":[]}, + "02b2c23f2d94b028": {"kind":"object","properties":[{"name":"kind","type":"c5687b1313206a6f","optional":false}],"indices":[]}, + "05205074b24d8340": {"kind":"literal","value":"action-cards"}, + "0657d3ca5ac4e7bf": {"kind":"primitive","type":"number"}, + "07e1a58c58b593f5": {"kind":"object","properties":[{"name":"model","type":"4cd48f3e5108bd6e","optional":false},{"name":"provider","type":"4cd48f3e5108bd6e","optional":false}],"indices":[]}, + "09655b34d7f3cf1e": {"kind":"literal","value":"system-prompt"}, + "09f3f8dc417f2782": {"kind":"object","properties":[{"name":"content","type":"b52f215a6cd69105","optional":false},{"name":"id","type":"4cd48f3e5108bd6e","optional":false},{"name":"role","type":"8af841fc9b265ade","optional":false},{"name":"source","type":"65a8400e451e667e","optional":false}],"indices":[]}, + "0ed8d1108fe7a5e1": {"kind":"object","properties":[{"name":"kind","type":"a9059a81430b3207","optional":false}],"indices":[]}, + "0eeb17e1df89b1eb": {"kind":"object","properties":[{"name":"form","type":"fee1caf8ab6a65c1","optional":false},{"name":"kind","type":"24b10f7cdae64928","optional":false}],"indices":[]}, + "0f46153645c29784": {"kind":"object","properties":[{"name":"height","type":"0657d3ca5ac4e7bf","optional":false},{"name":"width","type":"0657d3ca5ac4e7bf","optional":false}],"indices":[]}, + "0fe52387f6505270": {"kind":"literal","value":"plan-mode"}, + "11638dc58a31496f": {"kind":"object","properties":[{"name":"form","type":"6f9e00383836275e","optional":false},{"name":"kind","type":"2f3f201c8846e19c","optional":false},{"name":"sections","type":"c748fb42c80a1200","optional":false}],"indices":[]}, + "12d5808fac1996b9": {"kind":"object","properties":[{"name":"attachment","type":"b0b0eb1b16490f6b","optional":false},{"name":"offloaded","type":"dacf3d5f3e4ff619","optional":true},{"name":"type","type":"fb8b6a2e53cb9cf4","optional":false}],"indices":[]}, + "1379f0b72275994d": {"kind":"literal","value":"developer/message"}, + "16d0507cb94b4cfc": {"kind":"literal","value":"relay"}, + "1922cd79a70806c1": {"kind":"literal","value":"skill-invocation"}, + "1938c8c20f1cba7c": {"kind":"object","properties":[{"name":"form","type":"fee1caf8ab6a65c1","optional":false},{"name":"kind","type":"d9edf1862e4471d6","optional":false}],"indices":[]}, + "1e709346005826eb": {"kind":"object","properties":[{"name":"form","type":"aec150797b655c5f","optional":false},{"name":"kind","type":"d9edf1862e4471d6","optional":false}],"indices":[]}, + "1f0133217eeeffd8": {"kind":"literal","value":"tool-call"}, + "1f1385dbc3e875c0": {"kind":"literal","value":"repeat-tool-reminder"}, + "1fdad624334d3548": {"kind":"object","properties":[{"name":"form","type":"16d0507cb94b4cfc","optional":false},{"name":"kind","type":"bb94f3217aa56c88","optional":false}],"indices":[]}, + "2152caf1a71e588e": {"kind":"object","properties":[{"name":"kind","type":"2e4debca12f2c449","optional":false},{"name":"messageId","type":"4cd48f3e5108bd6e","optional":false},{"name":"senderId","type":"4cd48f3e5108bd6e","optional":false},{"name":"senderName","type":"4cd48f3e5108bd6e","optional":false},{"name":"teamId","type":"4cd48f3e5108bd6e","optional":false}],"indices":[]}, + "2235d957bf4b7c6c": {"kind":"object","properties":[{"name":"kind","type":"7b1ce60193fa8b69","optional":false}],"indices":[]}, + "224aa11d4d5f88c4": {"kind":"object","properties":[{"name":"form","type":"fee1caf8ab6a65c1","optional":false},{"name":"kind","type":"23af3759678a30de","optional":false}],"indices":[]}, + "23af3759678a30de": {"kind":"literal","value":"dsh-session-title-llm"}, + "24b10f7cdae64928": {"kind":"literal","value":"runtime-context"}, + "24ce31aa528272e5": {"kind":"object","properties":[{"name":"kind","type":"99e7e7d7fed55ae6","optional":false}],"indices":[]}, + "260523b412c19ad8": {"kind":"object","properties":[{"name":"form","type":"aec150797b655c5f","optional":false},{"name":"kind","type":"2f3f201c8846e19c","optional":false}],"indices":[]}, + "2765e9119edf5816": {"kind":"literal","value":"model"}, + "277b61f9c6f81704": {"kind":"literal","value":"file"}, + "28936c614bd4e7e2": {"kind":"object","properties":[{"name":"form","type":"6f9e00383836275e","optional":false},{"name":"kind","type":"d9edf1862e4471d6","optional":false},{"name":"sections","type":"c748fb42c80a1200","optional":false}],"indices":[]}, + "290f32bfd81ff85a": {"kind":"object","properties":[{"name":"kind","type":"23af3759678a30de","optional":false}],"indices":[]}, + "297fbd8067d7815a": {"kind":"object","properties":[{"name":"form","type":"aec150797b655c5f","optional":false},{"name":"kind","type":"a9059a81430b3207","optional":false}],"indices":[]}, + "2aad1179f9576314": {"kind":"object","properties":[{"name":"form","type":"6f9e00383836275e","optional":false},{"name":"kind","type":"24b10f7cdae64928","optional":false},{"name":"sections","type":"c748fb42c80a1200","optional":false}],"indices":[]}, + "2ab12a0e345658f9": {"kind":"array","element":"83da72c6857601c7"}, + "2e4debca12f2c449": {"kind":"literal","value":"team-message"}, + "2f1e6a7ebace43d7": {"kind":"union","types":["3f74ba74acd10644","58f94390b982f8a5","de30b1fa12c75f81","ebb768d85ec87fb6"]}, + "2f3f201c8846e19c": {"kind":"literal","value":"tool-goal"}, + "312aee2f0c42aabb": {"kind":"object","properties":[{"name":"form","type":"e8de0f457fed5b9f","optional":false},{"name":"kind","type":"d9edf1862e4471d6","optional":false},{"name":"summary","type":"4cd48f3e5108bd6e","optional":false}],"indices":[]}, + "31907de956375182": {"kind":"object","properties":[{"name":"compactionId","type":"4cd48f3e5108bd6e","optional":false},{"name":"kind","type":"6b23ed9ce1b397e6","optional":false},{"name":"sourceCommandId","type":"4cd48f3e5108bd6e","optional":true}],"indices":[]}, + "3247e566f4340029": {"kind":"object","properties":[{"name":"form","type":"6f9e00383836275e","optional":false},{"name":"kind","type":"05205074b24d8340","optional":false},{"name":"sections","type":"c748fb42c80a1200","optional":false},{"name":"through","type":"0657d3ca5ac4e7bf","optional":false}],"indices":[]}, + "335e242de1fcc17b": {"kind":"union","types":["bcf0caf62d964b2f","e438a9d4fb883b3e"]}, + "383cf73ae4748e5b": {"kind":"literal","value":"recall"}, + "3b04790e08b35241": {"kind":"object","properties":[{"name":"form","type":"aec150797b655c5f","optional":false},{"name":"kind","type":"0fe52387f6505270","optional":false}],"indices":[]}, + "3b4f2c9ef2c1217e": {"kind":"object","properties":[{"name":"form","type":"383cf73ae4748e5b","optional":false},{"name":"kind","type":"bb94f3217aa56c88","optional":false}],"indices":[]}, + "3bd652ebfa8726b3": {"kind":"union","types":["0657d3ca5ac4e7bf","938f7c7357bc403e"]}, + "3bdb8dc6989655b0": {"kind":"object","properties":[{"name":"form","type":"6f9e00383836275e","optional":false},{"name":"kind","type":"a9059a81430b3207","optional":false},{"name":"sections","type":"c748fb42c80a1200","optional":false}],"indices":[]}, + "3c2535ca0a2062f1": {"kind":"literal","value":"reasoning"}, + "3e2ece4967cfebe9": {"kind":"literal","value":"next-step"}, + "3f74ba74acd10644": {"kind":"literal","value":"image/jpeg"}, + "408927267422a7b2": {"kind":"object","properties":[{"name":"form","type":"fee1caf8ab6a65c1","optional":false},{"name":"kind","type":"0fe52387f6505270","optional":false}],"indices":[]}, + "43e311235aa537df": {"kind":"object","properties":[{"name":"toolName","type":"4cd48f3e5108bd6e","optional":false},{"name":"type","type":"83d6916021eede8f","optional":false}],"indices":[]}, + "45f005e6bbb76452": {"kind":"object","properties":[{"name":"form","type":"16d0507cb94b4cfc","optional":false},{"name":"kind","type":"05205074b24d8340","optional":false},{"name":"through","type":"0657d3ca5ac4e7bf","optional":false}],"indices":[]}, + "4991dfc415c9efeb": {"kind":"object","properties":[{"name":"form","type":"16d0507cb94b4cfc","optional":false},{"name":"kind","type":"1f1385dbc3e875c0","optional":false}],"indices":[]}, + "49dab019d00e8c26": {"kind":"object","properties":[{"name":"form","type":"6f9e00383836275e","optional":false},{"name":"kind","type":"78a9be340090b735","optional":false},{"name":"sections","type":"c748fb42c80a1200","optional":false}],"indices":[]}, + "4a9bef9f27a94252": {"kind":"object","properties":[{"name":"form","type":"16d0507cb94b4cfc","optional":false},{"name":"kind","type":"82470020b5bfcb1a","optional":false},{"name":"senderSessionId","type":"4cd48f3e5108bd6e","optional":false}],"indices":[]}, + "4b259f7f8f0a44ad": {"kind":"object","properties":[{"name":"content","type":"b52f215a6cd69105","optional":false},{"name":"id","type":"4cd48f3e5108bd6e","optional":false},{"name":"isError","type":"aa93010d0e521ad7","optional":true},{"name":"role","type":"a13d8f379222ece8","optional":false},{"name":"source","type":"d9c462b9f62fdd3e","optional":false},{"name":"toolCallId","type":"4cd48f3e5108bd6e","optional":false}],"indices":[]}, + "4cd48f3e5108bd6e": {"kind":"primitive","type":"string"}, + "4e44c9118152f682": {"kind":"literal","value":"text"}, + "4e88776858652856": {"kind":"object","properties":[{"name":"text","type":"4cd48f3e5108bd6e","optional":false},{"name":"type","type":"4e44c9118152f682","optional":false}],"indices":[]}, + "4e97c3d85c0fc817": {"kind":"object","properties":[{"name":"form","type":"e8de0f457fed5b9f","optional":false},{"name":"kind","type":"bf16d2e98a7f1389","optional":false},{"name":"senderSessionId","type":"4cd48f3e5108bd6e","optional":false},{"name":"summary","type":"4cd48f3e5108bd6e","optional":false}],"indices":[]}, + "4edec1b770e8ec4c": {"kind":"literal","value":"ptc-mode"}, + "4f91f187baeabef5": {"kind":"object","properties":[{"name":"capturedFormatVersion","type":"0657d3ca5ac4e7bf","optional":true},{"name":"capturedThroughSeq","type":"3bd652ebfa8726b3","optional":false},{"name":"compacted","type":"aa93010d0e521ad7","optional":false},{"name":"inputIndex","type":"0657d3ca5ac4e7bf","optional":false},{"name":"label","type":"4cd48f3e5108bd6e","optional":false},{"name":"omittedBytes","type":"0657d3ca5ac4e7bf","optional":false},{"name":"omittedMessages","type":"0657d3ca5ac4e7bf","optional":false},{"name":"originalMessages","type":"0657d3ca5ac4e7bf","optional":false},{"name":"retainedMessages","type":"0657d3ca5ac4e7bf","optional":false},{"name":"sessionId","type":"4cd48f3e5108bd6e","optional":false},{"name":"truncated","type":"aa93010d0e521ad7","optional":false}],"indices":[]}, + "52c6113cb0e53f34": {"kind":"object","properties":[{"name":"form","type":"6f9e00383836275e","optional":false},{"name":"kind","type":"9ac52f0f84fec2eb","optional":false},{"name":"sections","type":"c748fb42c80a1200","optional":false}],"indices":[]}, + "53cf1704ae0709c7": {"kind":"object","properties":[{"name":"form","type":"fee1caf8ab6a65c1","optional":false},{"name":"kind","type":"99e7e7d7fed55ae6","optional":false}],"indices":[]}, + "5654685d7557ec74": {"kind":"object","properties":[{"name":"form","type":"6f9e00383836275e","optional":false},{"name":"kind","type":"f68c335306535a00","optional":false},{"name":"sections","type":"c748fb42c80a1200","optional":false}],"indices":[]}, + "58015da1c1950a66": {"kind":"object","properties":[{"name":"form","type":"aec150797b655c5f","optional":false},{"name":"kind","type":"24b10f7cdae64928","optional":false}],"indices":[]}, + "587bb11da730e06a": {"kind":"literal","value":"session/title-llm-request"}, + "589d0d91239681f8": {"kind":"literal","value":"developer"}, + "58b122df513aeaf7": {"kind":"object","properties":[{"name":"data","type":"b782058cef83caf3","optional":false},{"name":"ignorable","type":"dacf3d5f3e4ff619","optional":true},{"name":"seq","type":"0657d3ca5ac4e7bf","optional":false},{"name":"sourceEventSeqs","type":"5d03ba38734809bc","optional":true},{"name":"surfaceOp","type":"335e242de1fcc17b","optional":false},{"name":"time","type":"0657d3ca5ac4e7bf","optional":false},{"name":"type","type":"de25352d2dc41963","optional":false}],"indices":[]}, + "58f94390b982f8a5": {"kind":"literal","value":"image/png"}, + "5bb69b88517f6214": {"kind":"object","properties":[{"name":"form","type":"e8de0f457fed5b9f","optional":false},{"name":"kind","type":"99e7e7d7fed55ae6","optional":false},{"name":"summary","type":"4cd48f3e5108bd6e","optional":false}],"indices":[]}, + "5bc66269cfaf614c": {"kind":"object","properties":[{"name":"form","type":"16d0507cb94b4cfc","optional":false},{"name":"kind","type":"d9edf1862e4471d6","optional":false}],"indices":[]}, + "5cdbf342b7748d15": {"kind":"object","properties":[{"name":"form","type":"16d0507cb94b4cfc","optional":false},{"name":"kind","type":"f68c335306535a00","optional":false}],"indices":[]}, + "5d00abf938272f22": {"kind":"array","element":"b782058cef83caf3"}, + "5d03ba38734809bc": {"kind":"array","element":"0657d3ca5ac4e7bf"}, + "5d7a64ed6aac5551": {"kind":"object","properties":[{"name":"form","type":"16d0507cb94b4cfc","optional":false},{"name":"kind","type":"8067b0d232c350f1","optional":false}],"indices":[]}, + "5ef58f2a1bdc576c": {"kind":"object","properties":[{"name":"data","type":"7404c6123fdaa591","optional":false},{"name":"ignorable","type":"dacf3d5f3e4ff619","optional":true},{"name":"seq","type":"0657d3ca5ac4e7bf","optional":false},{"name":"sourceEventSeqs","type":"5d03ba38734809bc","optional":true},{"name":"surfaceOp","type":"335e242de1fcc17b","optional":false},{"name":"time","type":"0657d3ca5ac4e7bf","optional":false},{"name":"type","type":"1379f0b72275994d","optional":false}],"indices":[]}, + "6085488d223aeffd": {"kind":"object","properties":[{"name":"form","type":"fee1caf8ab6a65c1","optional":false},{"name":"kind","type":"2f3f201c8846e19c","optional":false}],"indices":[]}, + "6292bcbedaa97fea": {"kind":"object","properties":[{"name":"kind","type":"f68c335306535a00","optional":false}],"indices":[]}, + "62ecb62dc0fe1b5e": {"kind":"literal","value":"next-turn"}, + "65a8400e451e667e": {"kind":"object","properties":[{"name":"kind","type":"09655b34d7f3cf1e","optional":false}],"indices":[]}, + "672c4aa2a9b736e3": {"kind":"object","properties":[{"name":"form","type":"fee1caf8ab6a65c1","optional":false},{"name":"kind","type":"1922cd79a70806c1","optional":false},{"name":"name","type":"4cd48f3e5108bd6e","optional":false}],"indices":[]}, + "68f44f59e91c4139": {"kind":"literal","value":"user"}, + "6b23ed9ce1b397e6": {"kind":"literal","value":"compact-checkpoint"}, + "6ccb6bc423fb3952": {"kind":"object","properties":[{"name":"form","type":"e8de0f457fed5b9f","optional":false},{"name":"kind","type":"f68c335306535a00","optional":false},{"name":"summary","type":"4cd48f3e5108bd6e","optional":false}],"indices":[]}, + "6dacb1584f2bfbe2": {"kind":"object","properties":[{"name":"clientTimeZone","type":"4cd48f3e5108bd6e","optional":true},{"name":"kind","type":"68f44f59e91c4139","optional":false},{"name":"principal","type":"deebfc7c62177ea8","optional":true},{"name":"rpcId","type":"4cd48f3e5108bd6e","optional":false}],"indices":[]}, + "6ea608bdc8a7a63d": {"kind":"object","properties":[{"name":"form","type":"16d0507cb94b4cfc","optional":false},{"name":"kind","type":"a9059a81430b3207","optional":false}],"indices":[]}, + "6ebcc2dbec9f2dab": {"kind":"literal","value":"skill-catalog"}, + "6f22a9d02b9a1f11": {"kind":"object","properties":[{"name":"baseline","type":"dacf3d5f3e4ff619","optional":true},{"name":"baselineIdentity","type":"4cd48f3e5108bd6e","optional":true},{"name":"changes","type":"2ab12a0e345658f9","optional":false},{"name":"form","type":"fee1caf8ab6a65c1","optional":false},{"name":"kind","type":"fbd2c74b346a1595","optional":false}],"indices":[]}, + "6f44f32be9bcd9a8": {"kind":"object","properties":[{"name":"form","type":"fee1caf8ab6a65c1","optional":false},{"name":"kind","type":"1f1385dbc3e875c0","optional":false}],"indices":[]}, + "6f9e00383836275e": {"kind":"literal","value":"snapshot"}, + "719c51f67074f970": {"kind":"object","properties":[{"name":"kind","type":"2765e9119edf5816","optional":false},{"name":"model","type":"4cd48f3e5108bd6e","optional":false},{"name":"provider","type":"4cd48f3e5108bd6e","optional":false},{"name":"replayState","type":"bbc5696e3f13c758","optional":true}],"indices":[]}, + "7382c4fc95fe8c5d": {"kind":"literal","value":"remove"}, + "7404c6123fdaa591": {"kind":"object","properties":[{"name":"headerSeq","type":"0657d3ca5ac4e7bf","optional":true},{"name":"message","type":"7580e7d96dc8475f","optional":false},{"name":"step","type":"0657d3ca5ac4e7bf","optional":false},{"name":"turn","type":"0657d3ca5ac4e7bf","optional":false}],"indices":[]}, + "7554565be416b034": {"kind":"object","properties":[{"name":"form","type":"383cf73ae4748e5b","optional":false},{"name":"kind","type":"2f3f201c8846e19c","optional":false}],"indices":[]}, + "756b5e23a5cb2c7f": {"kind":"object","properties":[{"name":"form","type":"383cf73ae4748e5b","optional":false},{"name":"kind","type":"8067b0d232c350f1","optional":false}],"indices":[]}, + "7580e7d96dc8475f": {"kind":"object","properties":[{"name":"content","type":"b52f215a6cd69105","optional":false},{"name":"id","type":"4cd48f3e5108bd6e","optional":false},{"name":"role","type":"589d0d91239681f8","optional":false},{"name":"source","type":"7a5b68bd1a5118c8","optional":false,"compatibility":{"version":1,"policy":"session-source-attribution","binding":"session.developer-message.source","discriminator":"kind","unknownKinds":"preserve","attributionKinds":["action-cards","tmux-context","user-question-reply","working-directory"]}}],"indices":[]}, + "78a9be340090b735": {"kind":"literal","value":"tmux-context"}, + "78b9204976ab9faa": {"kind":"array","element":"cda79b839d8cc0c7"}, + "7a5b68bd1a5118c8": {"kind":"union","types":["0203243b72fc867a","022623b9ef98ec47","02b2c23f2d94b028","0ed8d1108fe7a5e1","0eeb17e1df89b1eb","11638dc58a31496f","1938c8c20f1cba7c","1e709346005826eb","1fdad624334d3548","2152caf1a71e588e","2235d957bf4b7c6c","224aa11d4d5f88c4","24ce31aa528272e5","260523b412c19ad8","28936c614bd4e7e2","290f32bfd81ff85a","297fbd8067d7815a","2aad1179f9576314","312aee2f0c42aabb","31907de956375182","3247e566f4340029","3b04790e08b35241","3b4f2c9ef2c1217e","3bdb8dc6989655b0","408927267422a7b2","45f005e6bbb76452","4991dfc415c9efeb","49dab019d00e8c26","4a9bef9f27a94252","4e97c3d85c0fc817","52c6113cb0e53f34","53cf1704ae0709c7","5654685d7557ec74","58015da1c1950a66","5bb69b88517f6214","5bc66269cfaf614c","5cdbf342b7748d15","5d7a64ed6aac5551","6085488d223aeffd","6292bcbedaa97fea","65a8400e451e667e","672c4aa2a9b736e3","6ccb6bc423fb3952","6dacb1584f2bfbe2","6ea608bdc8a7a63d","6f22a9d02b9a1f11","6f44f32be9bcd9a8","719c51f67074f970","7554565be416b034","756b5e23a5cb2c7f","7df62978a2b7de85","7eaef7a763ba5b94","7f959241c24128b4","80677c8c8b804189","825ca23416b1ea9d","8338ee303e2bd448","854db3efdfb0cd39","85fe96bf15d5c755","8620189c8e335827","87b1a33564b9a4d6","88841202942c7683","8a046b52331959aa","8c1c4f72755a78ea","8c627d7a45fa406c","8f324cea3916c051","8fcb0b44a575e998","96bfb916a727cd44","99135d8a39379779","99f0e98184709ad1","9edc162949abf93b","9f65c43e340bba31","a15a19ef63cf5d1b","a16e567c8cd2c55c","a2066bd52f61218b","a3c6f1ea38e9a61d","aa2bf9e82b9fdeb9","aae343bc3d663c83","ada310bf0bdb8fed","ade057f4fdc387ae","b062078e899958f9","b2a5c8c008a31e3d","b3681c026a0b98e9","b4ec0af39fd52ed9","b613f83572fe2fbf","b6f94c17dd9bbd7c","b70f3e81fe11b7a5","b79641a9d25e7566","ba2a1a8f194b1bb2","ba4c40b4381e0dcd","baccc97ec34be6c4","c5228b7a55498d84","c55827fba7018198","cb1c8959e1726f82","cda2345a9be9d139","d85590a5675c6e2f","d9c462b9f62fdd3e","db06ef35b27a3842","dba54c09c6e7aafc","dd00cdccd0ba487b","dd5ee8762bd8a14b","dd6cc2bb2a8f2824","dde0cdf9d426768b","deaae5cd30987d4f","e7f4cbb3a57f4cbd","edbbfff1128099fd","ee577a0cc18664c9","f230c0b583e23b25","f29d63336e8266d8","f4a66f654996c093","f56c5b59ca016131","f6329fb30c7ea801","f6605d6c57933aea","f6afe41e3926f422","f7283223aacdc39b","f97f47b0eb87905e","fc2ae6962f9f8801","fcf81d3c58c13593","fe1a7acdeed4d0f5"]}, + "7b1ce60193fa8b69": {"kind":"literal","value":"working-directory"}, + "7df62978a2b7de85": {"kind":"object","properties":[{"name":"form","type":"aec150797b655c5f","optional":false},{"name":"kind","type":"78a9be340090b735","optional":false}],"indices":[]}, + "7eaef7a763ba5b94": {"kind":"object","properties":[{"name":"form","type":"16d0507cb94b4cfc","optional":false},{"name":"kind","type":"23af3759678a30de","optional":false}],"indices":[]}, + "7f13860ee89a2d35": {"kind":"literal","value":"session-reference"}, + "7f959241c24128b4": {"kind":"object","properties":[{"name":"goalId","type":"4cd48f3e5108bd6e","optional":false},{"name":"kind","type":"b815d7b91cdd9fc6","optional":false},{"name":"revision","type":"0657d3ca5ac4e7bf","optional":false},{"name":"round","type":"0657d3ca5ac4e7bf","optional":false}],"indices":[]}, + "8064c03c87c357fd": {"kind":"union","types":["12d5808fac1996b9","43e311235aa537df","4e88776858652856","814d9434e1ddf907","8e611bf46821e500","c1f0f17b6493b85d","f2da0f27c1e6add5"]}, + "80677c8c8b804189": {"kind":"object","properties":[{"name":"form","type":"aec150797b655c5f","optional":false},{"name":"kind","type":"23af3759678a30de","optional":false}],"indices":[]}, + "8067b0d232c350f1": {"kind":"literal","value":"time-context"}, + "814d9434e1ddf907": {"kind":"object","properties":[{"name":"text","type":"4cd48f3e5108bd6e","optional":false},{"name":"type","type":"3c2535ca0a2062f1","optional":false}],"indices":[]}, + "82470020b5bfcb1a": {"kind":"literal","value":"agent-message"}, + "825ca23416b1ea9d": {"kind":"object","properties":[{"name":"form","type":"aec150797b655c5f","optional":false},{"name":"kind","type":"f68c335306535a00","optional":false}],"indices":[]}, + "829a2fb3be6efd4b": {"kind":"literal","value":"agent/inbox/spliced"}, + "83079c8a3f733ac3": {"kind":"object","properties":[{"name":"description","type":"4cd48f3e5108bd6e","optional":false},{"name":"name","type":"4cd48f3e5108bd6e","optional":false}],"indices":[]}, + "8338ee303e2bd448": {"kind":"object","properties":[{"name":"form","type":"e8de0f457fed5b9f","optional":false},{"name":"kind","type":"23af3759678a30de","optional":false},{"name":"summary","type":"4cd48f3e5108bd6e","optional":false}],"indices":[]}, + "83d6916021eede8f": {"kind":"literal","value":"tool-removal"}, + "83da72c6857601c7": {"kind":"object","properties":[{"name":"action","type":"904bf499d98be4a3","optional":false},{"name":"digest","type":"4cd48f3e5108bd6e","optional":true},{"name":"path","type":"4cd48f3e5108bd6e","optional":false},{"name":"scope","type":"4cd48f3e5108bd6e","optional":false}],"indices":[]}, + "854db3efdfb0cd39": {"kind":"object","properties":[{"name":"form","type":"e8de0f457fed5b9f","optional":false},{"name":"kind","type":"bb94f3217aa56c88","optional":false},{"name":"summary","type":"4cd48f3e5108bd6e","optional":false}],"indices":[]}, + "85d6d96ff8842974": {"kind":"literal","value":"answered"}, + "85f9b505b7bdff1f": {"kind":"literal","value":"tool-addition"}, + "85fe96bf15d5c755": {"kind":"object","properties":[{"name":"form","type":"aec150797b655c5f","optional":false},{"name":"kind","type":"8067b0d232c350f1","optional":false}],"indices":[]}, + "8620189c8e335827": {"kind":"object","properties":[{"name":"form","type":"383cf73ae4748e5b","optional":false},{"name":"kind","type":"1f1385dbc3e875c0","optional":false}],"indices":[]}, + "86e1e384495ee09a": {"kind":"object","properties":[{"name":"name","type":"4cd48f3e5108bd6e","optional":false},{"name":"text","type":"4cd48f3e5108bd6e","optional":false}],"indices":[]}, + "87b1a33564b9a4d6": {"kind":"object","properties":[{"name":"form","type":"383cf73ae4748e5b","optional":false},{"name":"kind","type":"a9059a81430b3207","optional":false}],"indices":[]}, + "88841202942c7683": {"kind":"object","properties":[{"name":"form","type":"6f9e00383836275e","optional":false},{"name":"kind","type":"bb94f3217aa56c88","optional":false},{"name":"sections","type":"c748fb42c80a1200","optional":false}],"indices":[]}, + "8a046b52331959aa": {"kind":"object","properties":[{"name":"kind","type":"78a9be340090b735","optional":false}],"indices":[]}, + "8af841fc9b265ade": {"kind":"literal","value":"system"}, + "8c1c4f72755a78ea": {"kind":"object","properties":[{"name":"form","type":"6f9e00383836275e","optional":false},{"name":"kind","type":"0fe52387f6505270","optional":false},{"name":"sections","type":"c748fb42c80a1200","optional":false}],"indices":[]}, + "8c627d7a45fa406c": {"kind":"object","properties":[{"name":"form","type":"e8de0f457fed5b9f","optional":false},{"name":"kind","type":"2f3f201c8846e19c","optional":false},{"name":"summary","type":"4cd48f3e5108bd6e","optional":false}],"indices":[]}, + "8e611bf46821e500": {"kind":"object","properties":[{"name":"tool","type":"fcf427290c6213c2","optional":true},{"name":"toolName","type":"4cd48f3e5108bd6e","optional":false},{"name":"type","type":"85f9b505b7bdff1f","optional":false}],"indices":[]}, + "8f324cea3916c051": {"kind":"object","properties":[{"name":"form","type":"16d0507cb94b4cfc","optional":false},{"name":"kind","type":"2f3f201c8846e19c","optional":false}],"indices":[]}, + "8fcb0b44a575e998": {"kind":"object","properties":[{"name":"kind","type":"a720757f704a2512","optional":false}],"indices":[]}, + "904bf499d98be4a3": {"kind":"union","types":["7382c4fc95fe8c5d","b765ee31033cea46","f6cb462f808309e3"]}, + "938f7c7357bc403e": {"kind":"primitive","type":"null"}, + "95a3ffccbf3c8afc": {"kind":"literal","value":"owner"}, + "95c0e88fcf0bafb2": {"kind":"literal","value":"assistant"}, + "9645cf93769a6dfb": {"kind":"literal","value":"user-question-reply"}, + "96bfb916a727cd44": {"kind":"object","properties":[{"name":"form","type":"383cf73ae4748e5b","optional":false},{"name":"kind","type":"24b10f7cdae64928","optional":false}],"indices":[]}, + "96f4a9c81fc0f940": {"kind":"union","types":["3e2ece4967cfebe9","62ecb62dc0fe1b5e"]}, + "99135d8a39379779": {"kind":"object","properties":[{"name":"form","type":"aec150797b655c5f","optional":false},{"name":"kind","type":"9ac52f0f84fec2eb","optional":false}],"indices":[]}, + "99e7e7d7fed55ae6": {"kind":"literal","value":"tool-jobs"}, + "99f0e98184709ad1": {"kind":"object","properties":[{"name":"form","type":"aec150797b655c5f","optional":false},{"name":"kind","type":"bb94f3217aa56c88","optional":false}],"indices":[]}, + "9ac52f0f84fec2eb": {"kind":"literal","value":"model-selection"}, + "9edc162949abf93b": {"kind":"object","properties":[{"name":"deliveryId","type":"4cd48f3e5108bd6e","optional":false},{"name":"form","type":"e8de0f457fed5b9f","optional":false},{"name":"kind","type":"db6c5246911d47a7","optional":false},{"name":"provider","type":"4cd48f3e5108bd6e","optional":false},{"name":"ruleId","type":"4cd48f3e5108bd6e","optional":false},{"name":"source","type":"4cd48f3e5108bd6e","optional":false},{"name":"summary","type":"4cd48f3e5108bd6e","optional":false}],"indices":[]}, + "9f65c43e340bba31": {"kind":"object","properties":[{"name":"form","type":"fee1caf8ab6a65c1","optional":false},{"name":"kind","type":"78a9be340090b735","optional":false}],"indices":[]}, + "a13d8f379222ece8": {"kind":"literal","value":"tool"}, + "a15a19ef63cf5d1b": {"kind":"object","properties":[{"name":"form","type":"16d0507cb94b4cfc","optional":false},{"name":"kind","type":"9ac52f0f84fec2eb","optional":false}],"indices":[]}, + "a16e567c8cd2c55c": {"kind":"object","properties":[{"name":"form","type":"383cf73ae4748e5b","optional":false},{"name":"kind","type":"d9edf1862e4471d6","optional":false}],"indices":[]}, + "a2066bd52f61218b": {"kind":"object","properties":[{"name":"kind","type":"2f3f201c8846e19c","optional":false}],"indices":[]}, + "a3c6f1ea38e9a61d": {"kind":"object","properties":[{"name":"form","type":"e8de0f457fed5b9f","optional":false},{"name":"kind","type":"9ac52f0f84fec2eb","optional":false},{"name":"summary","type":"4cd48f3e5108bd6e","optional":false}],"indices":[]}, + "a3e299483348f27a": {"kind":"object","properties":[{"name":"maxTokens","type":"0657d3ca5ac4e7bf","optional":false},{"name":"messageSeqs","type":"5d03ba38734809bc","optional":false},{"name":"messages","type":"78b9204976ab9faa","optional":false},{"name":"reasoningEffort","type":"4cd48f3e5108bd6e","optional":true},{"name":"route","type":"07e1a58c58b593f5","optional":false},{"name":"system","type":"4cd48f3e5108bd6e","optional":false},{"name":"titleProvider","type":"4cd48f3e5108bd6e","optional":false}],"indices":[]}, + "a720757f704a2512": {"kind":"literal","value":"tool-registry"}, + "a9059a81430b3207": {"kind":"literal","value":"hooks-claude-code"}, + "aa2bf9e82b9fdeb9": {"kind":"object","properties":[{"name":"form","type":"fee1caf8ab6a65c1","optional":false},{"name":"kind","type":"f68c335306535a00","optional":false}],"indices":[]}, + "aa93010d0e521ad7": {"kind":"primitive","type":"boolean"}, + "aae343bc3d663c83": {"kind":"object","properties":[{"name":"form","type":"383cf73ae4748e5b","optional":false},{"name":"kind","type":"9ac52f0f84fec2eb","optional":false}],"indices":[]}, + "aba175fb20ffc711": {"kind":"array","element":"83079c8a3f733ac3"}, + "ada310bf0bdb8fed": {"kind":"object","properties":[{"name":"kind","type":"68f44f59e91c4139","optional":false}],"indices":[]}, + "ade057f4fdc387ae": {"kind":"object","properties":[{"name":"form","type":"16d0507cb94b4cfc","optional":false},{"name":"kind","type":"99e7e7d7fed55ae6","optional":false}],"indices":[]}, + "aec150797b655c5f": {"kind":"literal","value":"catalog"}, + "b062078e899958f9": {"kind":"object","properties":[{"name":"kind","type":"1f1385dbc3e875c0","optional":false}],"indices":[]}, + "b0b0eb1b16490f6b": {"kind":"object","properties":[{"name":"attachmentId","type":"4cd48f3e5108bd6e","optional":false},{"name":"bytes","type":"0657d3ca5ac4e7bf","optional":false},{"name":"height","type":"0657d3ca5ac4e7bf","optional":false},{"name":"mediaType","type":"2f1e6a7ebace43d7","optional":false},{"name":"name","type":"4cd48f3e5108bd6e","optional":true},{"name":"originalDimensions","type":"0f46153645c29784","optional":true},{"name":"width","type":"0657d3ca5ac4e7bf","optional":false}],"indices":[]}, + "b2a5c8c008a31e3d": {"kind":"object","properties":[{"name":"form","type":"383cf73ae4748e5b","optional":false},{"name":"kind","type":"23af3759678a30de","optional":false}],"indices":[]}, + "b2a948a5340a9341": {"kind":"object","properties":[{"name":"inserted","type":"5d00abf938272f22","optional":false},{"name":"outcome","type":"d4e4d575abeb4b72","optional":true},{"name":"removedCount","type":"0657d3ca5ac4e7bf","optional":true},{"name":"start","type":"0657d3ca5ac4e7bf","optional":false},{"name":"target","type":"96f4a9c81fc0f940","optional":false}],"indices":[]}, + "b3681c026a0b98e9": {"kind":"object","properties":[{"name":"form","type":"e8de0f457fed5b9f","optional":false},{"name":"kind","type":"78a9be340090b735","optional":false},{"name":"summary","type":"4cd48f3e5108bd6e","optional":false}],"indices":[]}, + "b36fd842cd004b53": {"kind":"object","properties":[{"name":"data","type":"b2a948a5340a9341","optional":false},{"name":"ignorable","type":"dacf3d5f3e4ff619","optional":true},{"name":"seq","type":"0657d3ca5ac4e7bf","optional":false},{"name":"time","type":"0657d3ca5ac4e7bf","optional":false},{"name":"type","type":"829a2fb3be6efd4b","optional":false}],"indices":[]}, + "b4ec0af39fd52ed9": {"kind":"object","properties":[{"name":"kind","type":"bb94f3217aa56c88","optional":false}],"indices":[]}, + "b52f215a6cd69105": {"kind":"array","element":"8064c03c87c357fd"}, + "b613f83572fe2fbf": {"kind":"object","properties":[{"name":"form","type":"383cf73ae4748e5b","optional":false},{"name":"kind","type":"05205074b24d8340","optional":false},{"name":"through","type":"0657d3ca5ac4e7bf","optional":false}],"indices":[]}, + "b6f94c17dd9bbd7c": {"kind":"object","properties":[{"name":"form","type":"fee1caf8ab6a65c1","optional":false},{"name":"kind","type":"bb94f3217aa56c88","optional":false}],"indices":[]}, + "b70f3e81fe11b7a5": {"kind":"object","properties":[{"name":"form","type":"fee1caf8ab6a65c1","optional":false},{"name":"kind","type":"a9059a81430b3207","optional":false}],"indices":[]}, + "b765ee31033cea46": {"kind":"literal","value":"set"}, + "b782058cef83caf3": {"kind":"object","properties":[{"name":"content","type":"b52f215a6cd69105","optional":false},{"name":"id","type":"4cd48f3e5108bd6e","optional":false},{"name":"role","type":"68f44f59e91c4139","optional":false},{"name":"source","type":"7a5b68bd1a5118c8","optional":false,"compatibility":{"version":1,"policy":"session-source-attribution","binding":"session.user-message.source","discriminator":"kind","unknownKinds":"preserve","attributionKinds":["action-cards","tmux-context","user-question-reply","working-directory"]}}],"indices":[]}, + "b79641a9d25e7566": {"kind":"object","properties":[{"name":"kind","type":"8067b0d232c350f1","optional":false}],"indices":[]}, + "b815d7b91cdd9fc6": {"kind":"literal","value":"goal"}, + "ba2a1a8f194b1bb2": {"kind":"object","properties":[{"name":"form","type":"fee1caf8ab6a65c1","optional":false},{"name":"kind","type":"05205074b24d8340","optional":false},{"name":"through","type":"0657d3ca5ac4e7bf","optional":false}],"indices":[]}, + "ba4c40b4381e0dcd": {"kind":"object","properties":[{"name":"form","type":"6f9e00383836275e","optional":false},{"name":"kind","type":"23af3759678a30de","optional":false},{"name":"sections","type":"c748fb42c80a1200","optional":false}],"indices":[]}, + "baccc97ec34be6c4": {"kind":"object","properties":[{"name":"form","type":"e8de0f457fed5b9f","optional":false},{"name":"kind","type":"0fe52387f6505270","optional":false},{"name":"summary","type":"4cd48f3e5108bd6e","optional":false}],"indices":[]}, + "bb94f3217aa56c88": {"kind":"literal","value":"hooks-codex"}, + "bbc5696e3f13c758": {"kind":"opaque","reason":"unknown"}, + "bcf0caf62d964b2f": {"kind":"object","properties":[{"name":"endSeq","type":"0657d3ca5ac4e7bf","optional":false},{"name":"op","type":"f6cb462f808309e3","optional":false},{"name":"startSeq","type":"0657d3ca5ac4e7bf","optional":false}],"indices":[]}, + "bf16d2e98a7f1389": {"kind":"literal","value":"subagent-settled"}, + "c1f0f17b6493b85d": {"kind":"object","properties":[{"name":"arguments","type":"4cd48f3e5108bd6e","optional":false},{"name":"id","type":"4cd48f3e5108bd6e","optional":false},{"name":"name","type":"4cd48f3e5108bd6e","optional":false},{"name":"type","type":"1f0133217eeeffd8","optional":false}],"indices":[]}, + "c5228b7a55498d84": {"kind":"object","properties":[{"name":"form","type":"6f9e00383836275e","optional":false},{"name":"kind","type":"99e7e7d7fed55ae6","optional":false},{"name":"sections","type":"c748fb42c80a1200","optional":false}],"indices":[]}, + "c55827fba7018198": {"kind":"object","properties":[{"name":"callId","type":"4cd48f3e5108bd6e","optional":false},{"name":"kind","type":"9645cf93769a6dfb","optional":false},{"name":"outcome","type":"85d6d96ff8842974","optional":false}],"indices":[]}, + "c5687b1313206a6f": {"kind":"literal","value":"cordis-host-runner"}, + "c748fb42c80a1200": {"kind":"array","element":"86e1e384495ee09a"}, + "cb1c8959e1726f82": {"kind":"object","properties":[{"name":"form","type":"16d0507cb94b4cfc","optional":false},{"name":"kind","type":"24b10f7cdae64928","optional":false}],"indices":[]}, + "cda2345a9be9d139": {"kind":"object","properties":[{"name":"form","type":"e8de0f457fed5b9f","optional":false},{"name":"kind","type":"24b10f7cdae64928","optional":false},{"name":"summary","type":"4cd48f3e5108bd6e","optional":false}],"indices":[]}, + "cda79b839d8cc0c7": {"kind":"union","types":["09f3f8dc417f2782","4b259f7f8f0a44ad","7580e7d96dc8475f","b782058cef83caf3","fa87537f4a9c1e25"]}, + "d4e4d575abeb4b72": {"kind":"literal","value":"canceled"}, + "d85590a5675c6e2f": {"kind":"object","properties":[{"name":"form","type":"aec150797b655c5f","optional":false},{"name":"kind","type":"1f1385dbc3e875c0","optional":false}],"indices":[]}, + "d9c462b9f62fdd3e": {"kind":"object","properties":[{"name":"callId","type":"4cd48f3e5108bd6e","optional":false},{"name":"kind","type":"a13d8f379222ece8","optional":false}],"indices":[]}, + "d9edf1862e4471d6": {"kind":"literal","value":"schedule"}, + "dacf3d5f3e4ff619": {"kind":"literal","value":true}, + "db06ef35b27a3842": {"kind":"object","properties":[{"name":"form","type":"6f9e00383836275e","optional":false},{"name":"kind","type":"1f1385dbc3e875c0","optional":false},{"name":"sections","type":"c748fb42c80a1200","optional":false}],"indices":[]}, + "db6c5246911d47a7": {"kind":"literal","value":"webhook"}, + "dba54c09c6e7aafc": {"kind":"object","properties":[{"name":"kind","type":"4edec1b770e8ec4c","optional":false}],"indices":[]}, + "dd00cdccd0ba487b": {"kind":"object","properties":[{"name":"form","type":"aec150797b655c5f","optional":false},{"name":"kind","type":"05205074b24d8340","optional":false},{"name":"through","type":"0657d3ca5ac4e7bf","optional":false}],"indices":[]}, + "dd5ee8762bd8a14b": {"kind":"object","properties":[{"name":"form","type":"aec150797b655c5f","optional":false},{"name":"kind","type":"99e7e7d7fed55ae6","optional":false}],"indices":[]}, + "dd6cc2bb2a8f2824": {"kind":"object","properties":[{"name":"form","type":"383cf73ae4748e5b","optional":false},{"name":"kind","type":"99e7e7d7fed55ae6","optional":false}],"indices":[]}, + "dde0cdf9d426768b": {"kind":"object","properties":[{"name":"kind","type":"05205074b24d8340","optional":false},{"name":"through","type":"0657d3ca5ac4e7bf","optional":false}],"indices":[]}, + "de25352d2dc41963": {"kind":"literal","value":"user/message"}, + "de30b1fa12c75f81": {"kind":"literal","value":"image/gif"}, + "deaae5cd30987d4f": {"kind":"object","properties":[{"name":"form","type":"16d0507cb94b4cfc","optional":false},{"name":"kind","type":"0fe52387f6505270","optional":false}],"indices":[]}, + "deebfc7c62177ea8": {"kind":"object","properties":[{"name":"kind","type":"95a3ffccbf3c8afc","optional":false}],"indices":[]}, + "df843efd87c78cf8": {"kind":"array","element":"4f91f187baeabef5"}, + "e438a9d4fb883b3e": {"kind":"literal","value":"append"}, + "e7f4cbb3a57f4cbd": {"kind":"object","properties":[{"name":"form","type":"e8de0f457fed5b9f","optional":false},{"name":"kind","type":"05205074b24d8340","optional":false},{"name":"summary","type":"4cd48f3e5108bd6e","optional":false},{"name":"through","type":"0657d3ca5ac4e7bf","optional":false}],"indices":[]}, + "e8de0f457fed5b9f": {"kind":"literal","value":"notice"}, + "ebb768d85ec87fb6": {"kind":"literal","value":"image/webp"}, + "edbbfff1128099fd": {"kind":"object","properties":[{"name":"form","type":"e8de0f457fed5b9f","optional":false},{"name":"kind","type":"a9059a81430b3207","optional":false},{"name":"summary","type":"4cd48f3e5108bd6e","optional":false}],"indices":[]}, + "ee577a0cc18664c9": {"kind":"object","properties":[{"name":"form","type":"e8de0f457fed5b9f","optional":false},{"name":"kind","type":"8067b0d232c350f1","optional":false},{"name":"summary","type":"4cd48f3e5108bd6e","optional":false}],"indices":[]}, + "efa1a091017ce533": {"kind":"object","properties":[{"name":"data","type":"a3e299483348f27a","optional":false},{"name":"ignorable","type":"dacf3d5f3e4ff619","optional":true},{"name":"seq","type":"0657d3ca5ac4e7bf","optional":false},{"name":"time","type":"0657d3ca5ac4e7bf","optional":false},{"name":"type","type":"587bb11da730e06a","optional":false}],"indices":[]}, + "f19bee84bf3d2090": {"kind":"object","properties":[{"name":"attachmentId","type":"4cd48f3e5108bd6e","optional":false},{"name":"bytes","type":"0657d3ca5ac4e7bf","optional":false},{"name":"name","type":"4cd48f3e5108bd6e","optional":false}],"indices":[]}, + "f230c0b583e23b25": {"kind":"object","properties":[{"name":"form","type":"6f9e00383836275e","optional":false},{"name":"kind","type":"8067b0d232c350f1","optional":false},{"name":"sections","type":"c748fb42c80a1200","optional":false}],"indices":[]}, + "f29d63336e8266d8": {"kind":"object","properties":[{"name":"kind","type":"d9edf1862e4471d6","optional":false}],"indices":[]}, + "f2da0f27c1e6add5": {"kind":"object","properties":[{"name":"attachment","type":"f19bee84bf3d2090","optional":false},{"name":"type","type":"277b61f9c6f81704","optional":false}],"indices":[]}, + "f4a66f654996c093": {"kind":"object","properties":[{"name":"kind","type":"0fe52387f6505270","optional":false}],"indices":[]}, + "f56c5b59ca016131": {"kind":"object","properties":[{"name":"form","type":"383cf73ae4748e5b","optional":false},{"name":"kind","type":"7f13860ee89a2d35","optional":false},{"name":"references","type":"df843efd87c78cf8","optional":false},{"name":"version","type":"f88f1b7b6bb733f6","optional":false}],"indices":[]}, + "f6329fb30c7ea801": {"kind":"object","properties":[{"name":"form","type":"383cf73ae4748e5b","optional":false},{"name":"kind","type":"0fe52387f6505270","optional":false}],"indices":[]}, + "f6605d6c57933aea": {"kind":"object","properties":[{"name":"form","type":"fee1caf8ab6a65c1","optional":false},{"name":"kind","type":"9ac52f0f84fec2eb","optional":false}],"indices":[]}, + "f68c335306535a00": {"kind":"literal","value":"user-approval"}, + "f6afe41e3926f422": {"kind":"object","properties":[{"name":"form","type":"fee1caf8ab6a65c1","optional":false},{"name":"kind","type":"8067b0d232c350f1","optional":false}],"indices":[]}, + "f6cb462f808309e3": {"kind":"literal","value":"replace"}, + "f7283223aacdc39b": {"kind":"object","properties":[{"name":"kind","type":"9ac52f0f84fec2eb","optional":false}],"indices":[]}, + "f88f1b7b6bb733f6": {"kind":"literal","value":1}, + "f97f47b0eb87905e": {"kind":"object","properties":[{"name":"form","type":"16d0507cb94b4cfc","optional":false},{"name":"kind","type":"78a9be340090b735","optional":false}],"indices":[]}, + "fa87537f4a9c1e25": {"kind":"object","properties":[{"name":"content","type":"b52f215a6cd69105","optional":false},{"name":"id","type":"4cd48f3e5108bd6e","optional":false},{"name":"role","type":"95c0e88fcf0bafb2","optional":false},{"name":"source","type":"719c51f67074f970","optional":false}],"indices":[]}, + "fb8b6a2e53cb9cf4": {"kind":"literal","value":"image"}, + "fbd2c74b346a1595": {"kind":"literal","value":"agent-instructions"}, + "fc2ae6962f9f8801": {"kind":"object","properties":[{"name":"entries","type":"aba175fb20ffc711","optional":false},{"name":"form","type":"aec150797b655c5f","optional":false},{"name":"kind","type":"6ebcc2dbec9f2dab","optional":false},{"name":"update","type":"dacf3d5f3e4ff619","optional":true}],"indices":[]}, + "fcf427290c6213c2": {"kind":"primitive","type":"never"}, + "fcf81d3c58c13593": {"kind":"object","properties":[{"name":"form","type":"383cf73ae4748e5b","optional":false},{"name":"kind","type":"78a9be340090b735","optional":false}],"indices":[]}, + "fe1a7acdeed4d0f5": {"kind":"object","properties":[{"name":"form","type":"e8de0f457fed5b9f","optional":false},{"name":"kind","type":"1f1385dbc3e875c0","optional":false},{"name":"summary","type":"4cd48f3e5108bd6e","optional":false}],"indices":[]}, + "fee1caf8ab6a65c1": {"kind":"literal","value":"instructions"} + } +} diff --git a/docs/persistence-changes/2026-10-22-action-card-status.zh.md b/docs/persistence-changes/2026-10-22-action-card-status.zh.md new file mode 100644 index 0000000000..4257655568 --- /dev/null +++ b/docs/persistence-changes/2026-10-22-action-card-status.zh.md @@ -0,0 +1,60 @@ +--- +description: "记录持久化类型更改及其兼容性确认。" +kind: persistence-change +--- + +# 2026-10-22-action-card-status + +[English](2026-10-22-action-card-status.md) | 中文 + +## 概述 + +为写入日志的模型上下文新增操作卡片状态的来源标识。 + +## 目录 + +- [声明](#declaration) +- [兼容性](#compatibility) +- [验证](#verification) +- [开发备注](#dev-note) + + +## 声明 + +```yaml persistence-change +schemaVersion: 1 +id: 2026-10-22-action-card-status +baseline: false +changes: + - root: "event:agent/inbox/spliced" + previous: "2026-10-13-user-rpc-principal" + after: "b36fd842cd004b532ce96fd5d6f5be28e017eda043131aa3cf5e69edc436108e" + decision: same-version + - root: "event:developer/message" + previous: "2026-10-13-user-rpc-principal" + after: "5ef58f2a1bdc576c1f12fa1f4da1920ece020b13c0f991a578cddcbeb28bc5be" + decision: same-version + - root: "event:session/title-llm-request" + previous: "2026-10-13-user-rpc-principal" + after: "efa1a091017ce533e38b0ef3e14d11a5c102ff1793229b18dad719a4268d9adc" + decision: same-version + - root: "event:user/message" + previous: "2026-10-13-user-rpc-principal" + after: "58b122df513aeaf7ddc5783dbb1fd844e2979bb273964e30ae351a8db6510019" + decision: same-version +``` + + +## 兼容性 + +已有消息和记录不变。没有操作卡片插件的读取方保留新的 action-cards 来源类型及其 JSON 元数据,并把记录的文本当作普通上下文。只有写入方使用 through 序号确认已送达的决定;它不授予任何权限,也不改变回放的解释。 + + +## 验证 + +对 action-cards、creation-recommendations、client-ui-action-cards 和 client-ui-creation-recommendations 的测试运行 pnpm exec vitest run:120 个测试通过,源码覆盖率 100%。Web creation-recommendations 回放在下一次回答之前记录了已忽略卡片的状态通知。 + + +## 开发备注 + +无。 diff --git a/docs/persistence-changes/historical-formats/README.i18n.yaml b/docs/persistence-changes/historical-formats/README.i18n.yaml index 5339de2d2b..40d842e063 100644 --- a/docs/persistence-changes/historical-formats/README.i18n.yaml +++ b/docs/persistence-changes/historical-formats/README.i18n.yaml @@ -15,8 +15,8 @@ en: 14c57f30e676f6e3 zh: b1a0ccf26fab3727 /session-persistence-formats/format-references: - en: 17ef09dfc5a89073 - zh: 59b8d60acb66c2dc + en: e3511a4ac3bc4457 + zh: 828f3c39f80d5370 /session-persistence-formats/scope-and-evidence: en: b220062139ddac1e zh: 7c4c0efe05dafa9c diff --git a/docs/persistence-changes/historical-formats/README.md b/docs/persistence-changes/historical-formats/README.md index 174c007423..74c575b300 100644 --- a/docs/persistence-changes/historical-formats/README.md +++ b/docs/persistence-changes/historical-formats/README.md @@ -32,7 +32,7 @@ The index is generated from validated snapshots and the current writer constant. | 1 | PR #3349 | [V1](v1.md) | [JSON](v1.schema.json) | 54 / 415 | | 2 | `dsh-v0.1.3-alpha.2` | [V2](v2.md) | [JSON](v2.schema.json) | 56 / 435 | | 3 | PR #4320 | [V3](v3.md) | [JSON](v3.schema.json) | 60 / 467 | -| 4 | Current checkout | [Current catalog](../../persistence-catalog.md) | [JSON](../../persistence-schema.json) | 65 / 614 | +| 4 | Current checkout | [Current catalog](../../persistence-catalog.md) | [JSON](../../persistence-schema.json) | 65 / 622 | diff --git a/docs/persistence-changes/historical-formats/README.zh.md b/docs/persistence-changes/historical-formats/README.zh.md index 055e07f954..12eb600771 100644 --- a/docs/persistence-changes/historical-formats/README.zh.md +++ b/docs/persistence-changes/historical-formats/README.zh.md @@ -32,7 +32,7 @@ description: "查找从 V0 到工作区写入器版本的每个 Session 格式 | 1 | PR #3349 | [V1](v1.zh.md) | [JSON](v1.schema.json) | 54 / 415 | | 2 | `dsh-v0.1.3-alpha.2` | [V2](v2.zh.md) | [JSON](v2.schema.json) | 56 / 435 | | 3 | PR #4320 | [V3](v3.zh.md) | [JSON](v3.schema.json) | 60 / 467 | -| 4 | 当前工作树 | [当前目录](../../persistence-catalog.zh.md) | [JSON](../../persistence-schema.json) | 65 / 614 | +| 4 | 当前工作树 | [当前目录](../../persistence-catalog.zh.md) | [JSON](../../persistence-schema.json) | 65 / 622 | diff --git a/docs/persistence-schema.json b/docs/persistence-schema.json index a9625525ff..4f3099bcec 100644 --- a/docs/persistence-schema.json +++ b/docs/persistence-schema.json @@ -5,7 +5,7 @@ {"key":"JsonlHeaderLine","kind":"header","digest":"18ee62b8900a4c3d046700f05d7a4d49d6cab2a660a020c87481d1603dd8bd4f"}, {"key":"SessionEventEnvelope","kind":"envelope","digest":"5776e5553ff2dfe3f5bc202dbb1e7c9f93e35a531aebb7764c23b2b6153b2ccc"}, {"key":"event:agent-preset/selected","kind":"event","event":"agent-preset/selected","surface":false,"digest":"a10c17474eaf2ddab7095a099e0fe3d046fc18e56c3e344fc8894c05ff9ef97b"}, - {"key":"event:agent/inbox/spliced","kind":"event","event":"agent/inbox/spliced","surface":false,"digest":"0755b7d1128319ba2e389619cbbf2c961ad648882b4e91da7f3e9582cc8a2034"}, + {"key":"event:agent/inbox/spliced","kind":"event","event":"agent/inbox/spliced","surface":false,"digest":"b36fd842cd004b532ce96fd5d6f5be28e017eda043131aa3cf5e69edc436108e"}, {"key":"event:approval/asked","kind":"event","event":"approval/asked","surface":false,"digest":"3bfeb47b58606f4661904bc723da612782214c463d01e6d61cd6d6193d7374e1"}, {"key":"event:approval/decided","kind":"event","event":"approval/decided","surface":false,"digest":"bb1ab3d08f49a9f3b265f844cd78d5c49813062a7b34b54904b426f85d0ff6e3"}, {"key":"event:approval/policy","kind":"event","event":"approval/policy","surface":false,"digest":"26718e15e7e395bce9642dba5bbe09b3b1a4ce2213d20d566cd9207d7fc5fb78"}, @@ -18,7 +18,7 @@ {"key":"event:compaction/start","kind":"event","event":"compaction/start","surface":false,"digest":"db874d463b0fdec77e9da1c4568f37cb70bd6596781eb93800db44fb8a116965"}, {"key":"event:compaction/summary","kind":"event","event":"compaction/summary","surface":false,"digest":"e2f9a41e0989f54ed8cee80f8db2bcf9d60a5c810dc9d45b83fa050b9dce7602"}, {"key":"event:deliverables/presented","kind":"event","event":"deliverables/presented","surface":false,"digest":"13d3d180f977bf78081d487ffa0ecb75857349bcab29a5a3fb48189fca2a6176"}, - {"key":"event:developer/message","kind":"event","event":"developer/message","surface":true,"digest":"97e0162f983c27456ec71269fb4976b1851588b8c1aeac5d071d43e11f95e03c"}, + {"key":"event:developer/message","kind":"event","event":"developer/message","surface":true,"digest":"5ef58f2a1bdc576c1f12fa1f4da1920ece020b13c0f991a578cddcbeb28bc5be"}, {"key":"event:feedback/message-delete","kind":"event","event":"feedback/message-delete","surface":false,"digest":"3ee93b06f3a125850337602bcdf155d2538c43a5c944ec55b1b3c365152d6796"}, {"key":"event:feedback/message-put","kind":"event","event":"feedback/message-put","surface":false,"digest":"b5086d249e8502e9ead1d39156bb8d559bde7951cac0f14ce150345b4e42a2bf"}, {"key":"event:feedback/record","kind":"event","event":"feedback/record","surface":false,"digest":"b54940ff095c17e874c5be03815f4c2145a256cf3a1d34dae4ab2f7769dfffe8"}, @@ -40,7 +40,7 @@ {"key":"event:session-log-deepseek/delivery-accepted","kind":"event","event":"session-log-deepseek/delivery-accepted","surface":false,"digest":"d63b8b8ffad9c02fd80c43a17df4f240c1fe8118ecca9de34f9d5871838ab5b9"}, {"key":"event:session/end-seed","kind":"event","event":"session/end-seed","surface":false,"digest":"5e6db6e24948d4a853c71cb9fabd252ad051ce93d4672c1266cf837c1c17b84e"}, {"key":"event:session/title","kind":"event","event":"session/title","surface":false,"digest":"1b912703e2d64f91c99c675b8f805b01076c8325b905c1218ad81ef0b24909d5"}, - {"key":"event:session/title-llm-request","kind":"event","event":"session/title-llm-request","surface":false,"digest":"79a4c87f8bfb4d6d05cd9952b99210d6a7db3b478c4a01d94b4c50602308c056"}, + {"key":"event:session/title-llm-request","kind":"event","event":"session/title-llm-request","surface":false,"digest":"efa1a091017ce533e38b0ef3e14d11a5c102ff1793229b18dad719a4268d9adc"}, {"key":"event:step/end","kind":"event","event":"step/end","surface":false,"digest":"e0a787e6ec76c7c94fecbc501b489164ab0293db05bc947914077ad01e674f05"}, {"key":"event:step/start","kind":"event","event":"step/start","surface":false,"digest":"4513e088d43e6c68425be30451b9f961cc264fe7318ca62681c41d4d78615986"}, {"key":"event:subagent/catalog","kind":"event","event":"subagent/catalog","surface":false,"digest":"53750b9abf61efb7992b0aadc3e247ec927449c63266df28230fd1139f5590ac"}, @@ -62,24 +62,22 @@ {"key":"event:tool/result","kind":"event","event":"tool/result","surface":true,"digest":"7c9f44e90a0058f4cc532ae20dad0c10afa6eba22e70a6c79fc79490bad64397"}, {"key":"event:turn/end","kind":"event","event":"turn/end","surface":false,"digest":"0f8512903d94f57a4748fa1a2092e64342856796684e6b8343db685b192745ce"}, {"key":"event:turn/start","kind":"event","event":"turn/start","surface":false,"digest":"aa0957eca50aeb28bcd2e6930b95809926edacb550c8c340ba526ba6b861b3d8"}, - {"key":"event:user/message","kind":"event","event":"user/message","surface":true,"digest":"2ae8594a998c9862a597129e782277bcc518266712a703b5c3161bdf552d13b8"}, + {"key":"event:user/message","kind":"event","event":"user/message","surface":true,"digest":"58b122df513aeaf7ddc5783dbb1fd844e2979bb273964e30ae351a8db6510019"}, {"key":"event:web/deepseek-search-llm-request","kind":"event","event":"web/deepseek-search-llm-request","surface":false,"digest":"0c556c9b9f2a96238a6b9bbb99e748206cbff2ed6203041c9571f04ae5802f52"}, {"key":"event:working-directory/change","kind":"event","event":"working-directory/change","surface":false,"digest":"124aa62078ca3311353b39cc3bf6c75da281338be6bbf672f8b361d828cddca3"}, {"key":"event:workspace/changes","kind":"event","event":"workspace/changes","surface":false,"digest":"e308ccf867a5398e316e0af8cb6ce238a8d33a63b9b384c8250a686786285f72"} ], "types": [ - {"digest":"00c054d3018c198d92f459563331edd1385e58345c677bb499791df2da0ade9f","names":["packages/session/session-title-llm/src/index.ts#SessionTitleLlmRequestEventData"],"sources":["packages/session/session-title-llm/src/index.ts:37"]}, {"digest":"0116b1f89c05221d56b0ba35c83d302bf4857bc7277d247e9bbaf74b27d2c66e","names":[],"sources":[]}, {"digest":"0203243b72fc867afc81aff5f4cf2167e564ed342534eb612fc92f295cf07789","names":[],"sources":[]}, {"digest":"022623b9ef98ec47ec3c3bae0c51027c70d2601141c5e92e26a0d83d1b4cef06","names":[],"sources":[]}, {"digest":"023a355b2be0d688fb6e4df8388dadb942761d46cfe96d0f499aae9631a159d4","names":[],"sources":["packages/core/session/src/types.ts:207"]}, {"digest":"02b2c23f2d94b028c9588a8faace3fce1217405db346da3389368b495457bf79","names":[],"sources":["packages/extensions/cordis-host-runner/src/index.ts:14"]}, {"digest":"0341d4c3dfcd53253cdc1e789d9130d3cd848b6f8b0706bf92f313a68b39ff23","names":[],"sources":[]}, - {"digest":"040cd65423bd64264d225504f1c572d239eeb1793d0c2856ad8e42bb6e1e148b","names":["packages/llm/llm/src/message.ts#Message"],"sources":["packages/llm/llm/src/message.ts:196"]}, + {"digest":"05205074b24d8340cfd7995f6cb64779c7f1241490954700115601e264178877","names":[],"sources":[]}, {"digest":"05a2796621b1ee6c03ef7abe560b25116bb4da4d592312117242a78415a32b04","names":[],"sources":[]}, {"digest":"05d2ae1e0c7f8770f83f9a09047afb39a335d8ac986df3193659aa1e581472cf","names":["packages/interaction/user-approval/src/index.ts#ApprovalPolicy"],"sources":["packages/interaction/user-approval/src/index.ts:68"]}, {"digest":"0657d3ca5ac4e7bf79b00bcf860b1c7159d01a9da137497221ec2f1776e365f7","names":["packages/core/session/src/types.ts#SessionSeq"],"sources":["packages/core/session/src/types.ts:33"]}, - {"digest":"0755b7d1128319ba2e389619cbbf2c961ad648882b4e91da7f3e9582cc8a2034","names":[],"sources":[]}, {"digest":"07e1a58c58b593f507b8e7f25723bb0aa42baae6e00f5ecfddf28cb1e64d3235","names":["packages/session/session-title/src/types.ts#SessionTitleModelIdentity","packages/subagent/tool-subagent/src/model-selection.ts#AllowedModelRoute"],"sources":["packages/session/session-title/src/types.ts:20","packages/subagent/tool-subagent/src/model-selection.ts:9"]}, {"digest":"07eccc9ea2f9b5439d76cc77d5e062936677d43f07a23da749819b0a22ee36b7","names":[],"sources":[]}, {"digest":"09655b34d7f3cf1e9005974de2357de2d4a60deea165d16dac1eadaa6ec539a8","names":[],"sources":[]}, @@ -101,7 +99,6 @@ {"digest":"0ed8d1108fe7a5e1c3078ef1faea12199a56b6b1216ccaceb0f6103b0c9a7691","names":[],"sources":[]}, {"digest":"0eeb17e1df89b1ebc2902116fe7145e0d321ef0db26e6bdebc802cb8513a01c1","names":[],"sources":[]}, {"digest":"0f46153645c297846a3fad3911636659ca4717a5e7d71c9f50bffacd312115ef","names":[],"sources":["packages/attachment/attachment/src/types.ts:28"]}, - {"digest":"0f6265188dae7ba6faa26830a180bb4deb0760561f97ee0c9d399ccf6c9cf1f7","names":["packages/llm/llm/src/message.ts#DeveloperMessage"],"sources":["packages/llm/llm/src/message.ts:157"]}, {"digest":"0f8512903d94f57a4748fa1a2092e64342856796684e6b8343db685b192745ce","names":[],"sources":[]}, {"digest":"0fe52387f65052702ed9d7cd59db826468936595d1b3eec207e7a823adc719db","names":[],"sources":[]}, {"digest":"1033093edd0db80ff410e00830b523405e00bb0c7684948e531ff65095799625","names":[],"sources":[]}, @@ -178,11 +175,9 @@ {"digest":"2aad1179f957631477b582e57cd8f538cb82618fc3fb816bde1171154235b065","names":[],"sources":[]}, {"digest":"2ab12a0e345658f9342816d773fe610f049982bcad82631f43b9205abe43e5ee","names":[],"sources":[]}, {"digest":"2abb711460e6238d851c30f3e4bdd87a2aa1887ee70575b3d3b7b25610673095","names":[],"sources":["packages/llm/llm/src/types.ts:473"]}, - {"digest":"2ae8594a998c9862a597129e782277bcc518266712a703b5c3161bdf552d13b8","names":[],"sources":[]}, {"digest":"2aed07493b1c65b6d00de5586da1e743cf501684b9ad43a1765fb4007d4ea456","names":[],"sources":[]}, {"digest":"2c4c21f19f426a58f2dac914e34313aa43f8408972de857a486d1f086d5adb6c","names":[],"sources":[]}, {"digest":"2c64d4bc7264fd3f8bed47e96ab8a593bce05a1e4e1c436dcd244a3b7e0c2a9d","names":[],"sources":[]}, - {"digest":"2cf075b013d363ad743b5a4982900678d108499c721bc11255137402003db711","names":["packages/llm/llm/src/message.ts#UserMessage"],"sources":["packages/llm/llm/src/message.ts:162"]}, {"digest":"2d11ca7b0d4493e244b74eba093227866b33249841e59a1e9bf56afed38604c3","names":[],"sources":["packages/web/web-search-deepseek/src/provider.ts:71"]}, {"digest":"2da725e554cd89301d829ed9dc45a572c3cd1b93b5e91d0d71f007976d8a0863","names":[],"sources":[]}, {"digest":"2e3523541e942ef6cc9fbc5b91f242e0ca4c6ada479b79b96025b797f6e41f3f","names":["packages/schedule/schedule/src/types.ts#ScheduleChange"],"sources":["packages/schedule/schedule/src/types.ts:256"]}, @@ -194,6 +189,7 @@ {"digest":"30d5a537b296685770b1241c4b4bbde19d5a31ff81aa23df73fab5e90b8fc65f","names":["packages/session/session-title/src/types.ts#SessionTitleSource"],"sources":["packages/session/session-title/src/types.ts:28"]}, {"digest":"312aee2f0c42aabbae6cc60d92d596d8453c0aeb061c5868d1755a69ef49e69a","names":[],"sources":[]}, {"digest":"31907de9563751822af6d9be02bafaf26faa098bbd1cb412524134d577cc1648","names":["packages/compaction/compaction/src/checkpoint.ts#CompactionCheckpointSource"],"sources":["packages/compaction/compaction/src/checkpoint.ts:22"]}, + {"digest":"3247e566f4340029325b70e4366b56fa9719977f1e39033b769a2ec221ab22a9","names":[],"sources":[]}, {"digest":"326a9bfda27dda5882fb3cbe6e715df41dae2298fdae3d66437457268a665693","names":["packages/llm/llm/src/call-config.ts#LlmCallConfigAdapterDefaults"],"sources":["packages/llm/llm/src/call-config.ts:54"]}, {"digest":"3305a4bb6b6c29b482ba91d59c60b47e8f98282d3dacc8c29e4a4c2712209bdd","names":[],"sources":[]}, {"digest":"33341aac09312300de5a178e6cc6f6544a4876f7f710cfea55eda473c8d329ef","names":["packages/llm/llm/src/assistant-stream.ts#AssistantStreamRecord"],"sources":["packages/llm/llm/src/assistant-stream.ts:20"]}, @@ -236,7 +232,7 @@ {"digest":"44818000e56acb97afdb5045ce235eb55e3fb1ccabc9fc7d0750bb38f36a7f8e","names":[],"sources":[]}, {"digest":"44c8c77aa9aa100b0d74d2deee85474b29faa6eee0ac0f662f7abe9931ddd7f2","names":[],"sources":[]}, {"digest":"4513e088d43e6c68425be30451b9f961cc264fe7318ca62681c41d4d78615986","names":[],"sources":[]}, - {"digest":"454a0fa879e38076bdcdbd87ef561cd67d7e43e58a287cf7dbaa7b6cf7847020","names":[],"sources":[]}, + {"digest":"45f005e6bbb7645248be792b715a74bc7fdf5a67f5381655d3a75dcc7aa2112e","names":[],"sources":[]}, {"digest":"45f2a07ea94573123b6ae14db787a40062dec3834f36d8be92c523d40ba28448","names":[],"sources":["packages/interaction/user-approval/src/types.ts:44"]}, {"digest":"477614bca595a9e7adb5a1870be446b9a8684f3ba8950259d6289f2d3b74f028","names":[],"sources":["packages/compaction/compaction-image-offload/src/projection.ts:25"]}, {"digest":"47e5d84f1a93450a78c7f3954b43990d0f629af7c034a56313e5deb63e772c1b","names":[],"sources":[]}, @@ -285,17 +281,18 @@ {"digest":"5808e36c95355f0e745201b5db1b6bcec4ebf5adb75d3e82d7d59cc89635ab80","names":["packages/llm/llm/src/types.ts#FinishReason"],"sources":["packages/llm/llm/src/types.ts:165"]}, {"digest":"587bb11da730e06aa52b62c087d217d6f8f22459b25f31cb9e1e1baf17944e5e","names":[],"sources":[]}, {"digest":"589d0d91239681f89ee485459dc8c2f73ea47183b5534289d966637024a2c35d","names":[],"sources":[]}, + {"digest":"58b122df513aeaf7ddc5783dbb1fd844e2979bb273964e30ae351a8db6510019","names":[],"sources":[]}, {"digest":"58f94390b982f8a5ac1f4f235ba612557f4a1dc4a995d8b1cec63ce7639decb0","names":[],"sources":[]}, {"digest":"5abab3a27fd2736660ecddd94b28d6bbf114986dd894880153bd1be3ccacb563","names":[],"sources":[]}, {"digest":"5b232a9b9bfcc0fc6794dab7865bcf203a65785c5de5aa647a51228bff628413","names":[],"sources":[]}, {"digest":"5b6fb1f226ff56402db08a6aae82af76100b14056271875f69ec526a1ae05d51","names":[],"sources":["packages/interaction/commands/src/types.ts:112"]}, {"digest":"5bb69b88517f6214f2af4d1a6d2b771e271ad3ad695cc9c2c272069ae698c9bc","names":[],"sources":[]}, - {"digest":"5bc2d7f02503fc6b3e1bb7e0b7fa22c0b9e4b9841fa3175b2ad8e607ae3525a8","names":["packages/llm/llm/src/message.ts#MessageSource"],"sources":["packages/llm/llm/src/message.ts:136"]}, {"digest":"5bc66269cfaf614c0ecda8ecb469f23101a7609793fc2b693ac23c4aecdb575e","names":[],"sources":[]}, {"digest":"5bd310104cfe448e530beb11422d2f791e2e32e8e95fdc528a15f9ca693dae75","names":[],"sources":[]}, {"digest":"5c45bf4c544a7211dcd8ba6ba7e5f1bc39b49e7a9df9d5cbdc8e87c22771b37b","names":[],"sources":[]}, {"digest":"5cb63db9fe57dc09c3a1c2b84c3edf73ff5b62960155d8436a2377a5c43ce32c","names":[],"sources":[]}, {"digest":"5cdbf342b7748d152bc987b68f58e565e5bba4b227e076e2d2120d87662e45cd","names":[],"sources":[]}, + {"digest":"5d00abf938272f22eb47199e9d40801e8d8ec4c88e2b138b15aa2a5a5bf87520","names":[],"sources":[]}, {"digest":"5d03ba38734809bcbd2a55221bd938b3b3fc34b49bb686c6113e9e4931e3aa78","names":[],"sources":[]}, {"digest":"5d17f27a1ef9f7026c0232714adf7573961536d75f3303ca97973975f5af170e","names":[],"sources":[]}, {"digest":"5d542ac7bdf13310c46e1a884dec9fb18ad8c6e153cff207e639cd7bf9d7e694","names":[],"sources":[]}, @@ -303,6 +300,7 @@ {"digest":"5dd766ccd1702a1142ad8b4bd44f2242f8e8a6752669ad3de0a5d224329b1438","names":[],"sources":["packages/compaction/compaction/src/types.ts:82"]}, {"digest":"5e6db6e24948d4a853c71cb9fabd252ad051ce93d4672c1266cf837c1c17b84e","names":[],"sources":[]}, {"digest":"5ebce1317eacd2c148456627a09668e927ee15c204f0643f5ea76b46b54a2647","names":[],"sources":[]}, + {"digest":"5ef58f2a1bdc576c1f12fa1f4da1920ece020b13c0f991a578cddcbeb28bc5be","names":[],"sources":[]}, {"digest":"5f2117d1856a5ab43c28371faa978ebfaa48f8c7d734ea3fc94842210df83dca","names":["packages/llm/llm/src/types.ts#LlmFailure"],"sources":["packages/llm/llm/src/types.ts:41"]}, {"digest":"5f26a6c20b37632f8f57729d171c671def4683d994ac6257a8dffcd855101627","names":[],"sources":[]}, {"digest":"5f758f3de5f00c18968a7c6d347083aac5c9ae13f6a79c78f6611b23c4973564","names":["packages/subagent/subagent/src/descriptor.ts#OneShotSubagentDescriptorData"],"sources":["packages/subagent/subagent/src/descriptor.ts:61"]}, @@ -351,16 +349,19 @@ {"digest":"71bb3254ec8f1e491ce63b762a7b5f18709de23aa4e8abcc8b886f4b6ce051dd","names":[],"sources":["packages/llm/llm/src/assistant-stream.ts:28"]}, {"digest":"72ec79127a9c0d0241b1106a74cc2b24c81ce467170f1c3c93f7b71a9496d227","names":[],"sources":[]}, {"digest":"7382c4fc95fe8c5db65ca4d9da572b115d0dbb818b7f32c22ab040318ffbb4bc","names":[],"sources":[]}, + {"digest":"7404c6123fdaa591474a37979a5a8ae70d1387af7c9f09bfe568b218935a974d","names":[],"sources":["packages/core/session/src/types.ts:313"]}, {"digest":"750a2b129d67704df675c06823c20c80b65cbae89d5aaa914fd5a72e3df395b5","names":["packages/jobs/jobs/src/types.ts#JobSettleCause"],"sources":["packages/jobs/jobs/src/types.ts:247"]}, {"digest":"7554565be416b0342053ee6a46256c9dab07c487349b4a5f10bab1edc151c619","names":[],"sources":[]}, {"digest":"756b5e23a5cb2c7f486f366a828f7461c88902af21125bd6810881f4ccbff6cc","names":[],"sources":[]}, + {"digest":"7580e7d96dc8475fffe813a42af499665de3d4517fcdb3bc2337d0340e98d616","names":["packages/llm/llm/src/message.ts#DeveloperMessage"],"sources":["packages/llm/llm/src/message.ts:157"]}, {"digest":"7624ca28c61ca0eaa4f015140f3cea6e0c16cce2f2960fab5f531203d253903e","names":[],"sources":[]}, {"digest":"763c8a20af487263a0080548274f7437ef4a86e0ba8769127d1ced10a72c664d","names":[],"sources":[]}, {"digest":"76632533f67879dcb346fcae640540dbc89b2472b0bce88c59f7e48fc5c0e668","names":[],"sources":[]}, {"digest":"78a9be340090b735a39980e7222ce730c972fbcf1de7c9e0aef2125884b7caee","names":[],"sources":[]}, + {"digest":"78b9204976ab9faaf133c3488b73d1dfb6f1a4bb6c0903e3473cd41a5e1e39e1","names":[],"sources":[]}, {"digest":"79650cfa10aa3c52440328d85e8a25f0ffadc8d7efd784e3c0819d8bb74a1985","names":[],"sources":["packages/core/session/src/types.ts:214"]}, - {"digest":"79a4c87f8bfb4d6d05cd9952b99210d6a7db3b478c4a01d94b4c50602308c056","names":[],"sources":[]}, {"digest":"79ddc907312f97cf9553d4841e8b3fc937097bd8d97900a790efad3c05255e99","names":[],"sources":["packages/todo/tool-todo/src/types.ts:33"]}, + {"digest":"7a5b68bd1a5118c827df9e2466acc5e4d16b83e3f97a2304d992958647592690","names":["packages/llm/llm/src/message.ts#MessageSource"],"sources":["packages/llm/llm/src/message.ts:136"]}, {"digest":"7a651f3b672c52fb615f22c0ce9cb3cc5b22aad92e6bbeefb0808fd7b9233a50","names":[],"sources":[]}, {"digest":"7af85bf70d4eafce63e739adae38ea85501e68df3a0e22f97f3a0be90fd9e4cb","names":[],"sources":[]}, {"digest":"7b1ce60193fa8b69292e5843858d9cee7dc565dcb40d33c136e5d7e8b8d4296a","names":[],"sources":[]}, @@ -431,7 +432,6 @@ {"digest":"9645cf93769a6dfb227bd400bd6cb9fa7bab9140f4a851e4ae92514ade6e3114","names":[],"sources":[]}, {"digest":"96bfb916a727cd4445a594df264c523fc039749a31bfed924b8cc7bb308a5bb0","names":[],"sources":[]}, {"digest":"96f4a9c81fc0f940ef71528a8cc66731ae4cf1f40d79680c5fe3c39f96d7723c","names":["packages/core/agent/src/types.ts#InboxTarget"],"sources":["packages/core/agent/src/types.ts:40"]}, - {"digest":"97e0162f983c27456ec71269fb4976b1851588b8c1aeac5d071d43e11f95e03c","names":[],"sources":[]}, {"digest":"9890113e97dd3541787bfe6704f2b223d3a890b5325ca0acae2b0398909b413e","names":[],"sources":["packages/sandbox/sandbox-policy/src/session-mode.ts:33"]}, {"digest":"99135d8a3937977955eaac5f8f04f930dcd1562f8450a54a51cde9b921dd59a6","names":[],"sources":[]}, {"digest":"9947aa5938fe19a895298da412917acbf9ffb5093a73ab8baa257e6ae9e63a07","names":[],"sources":[]}, @@ -464,6 +464,7 @@ {"digest":"a2be0f0cb59f0d4a06557d45847d1938aced238baa77a6da416e590b8b958553","names":[],"sources":[]}, {"digest":"a3a259a1aa5401af786ce08c4a8419681d3000d73467991b5fc563389bc4ebd2","names":[],"sources":[]}, {"digest":"a3c6f1ea38e9a61d7a9706057ba65aabe02dfe6d9dd90d4736aa62325d874531","names":[],"sources":[]}, + {"digest":"a3e299483348f27a4787a36bb3f29cd86024c65336abc10bb3d687468302c4cf","names":["packages/session/session-title-llm/src/index.ts#SessionTitleLlmRequestEventData"],"sources":["packages/session/session-title-llm/src/index.ts:37"]}, {"digest":"a3edd1efd1fef21b72c55e213c8a8b24196b091abde2b89db52be0b2aaae3cfa","names":[],"sources":[]}, {"digest":"a40d12070f6f4a124f32fb3cc86e7857554702e32f0eda3080a25c4b9ac9b18c","names":[],"sources":["packages/experimental/agent-team/src/types.ts:232"]}, {"digest":"a6567ccb2e530606b775371eb4fa31468d72084339968e8b0440a516a23b39dc","names":[],"sources":[]}, @@ -476,7 +477,6 @@ {"digest":"a8a2b9011fc067862df7ce40748868467e5778283d7ac193c13f77684a914c57","names":[],"sources":[]}, {"digest":"a9059a81430b320792bbb7c2ee1f8f6c77ff71e40ee44c18aec72e157c522694","names":[],"sources":[]}, {"digest":"a926dc71814edd01a4f041eb5fc766d153db3407ef4704e98f4c1b6527309ebb","names":["packages/schedule/schedule/src/types.ts#LegacyEveryScheduleRecord"],"sources":["packages/schedule/schedule/src/types.ts:173"]}, - {"digest":"a927d444c928ac0efd9dd8b2e7d7d04d711c264cb71578d81e0e2fd143ff669f","names":[],"sources":[]}, {"digest":"a99ec0993c62c058e50af9f68922202a3b54f5e259bc2abf1915a73381d2316d","names":[],"sources":[]}, {"digest":"a99f76fd149051c363f960bfeeb5b9509587ecaf7591a42797e4bfe810d8987a","names":["packages/todo/tool-todo/src/types.ts#TodoItem"],"sources":["packages/todo/tool-todo/src/types.ts:23"]}, {"digest":"aa0957eca50aeb28bcd2e6930b95809926edacb550c8c340ba526ba6b861b3d8","names":[],"sources":[]}, @@ -509,8 +509,10 @@ {"digest":"b222069eea2d768065161c1147b1f2c78c1b54328c84b3586ae5c8f91b8ed35e","names":[],"sources":[]}, {"digest":"b2245a906e13aa94f2d3cc6cf7b04cfa0267f7b7f4d468de1b00b1a314e54626","names":[],"sources":[]}, {"digest":"b2a5c8c008a31e3dc6c3013502f2356aca13894b31c8e70601fa92f2d40d1dba","names":[],"sources":[]}, + {"digest":"b2a948a5340a9341fa3bab99507c7090c5b0456de72ec28b5b157b8df3f1d640","names":[],"sources":["packages/core/agent/src/types.ts:110"]}, {"digest":"b2d2a01e51341a3bfec809198952c8c830a7899f4cb6ecd3c1dc0b5d9a2af943","names":["packages/experimental/agent-team/src/types.ts#TeamTaskStatus"],"sources":["packages/experimental/agent-team/src/types.ts:72"]}, {"digest":"b3681c026a0b98e98b103f057dfd217fbe904129c090efd668253ec9aea7fece","names":[],"sources":[]}, + {"digest":"b36fd842cd004b532ce96fd5d6f5be28e017eda043131aa3cf5e69edc436108e","names":[],"sources":[]}, {"digest":"b38f206d59bad891fa58c89f97a0036987b3defe7685acbd318fa55eb85f9751","names":[],"sources":[]}, {"digest":"b4ec0af39fd52ed971990e42f04e5be3204882e555c409eb330f8238eec21569","names":[],"sources":[]}, {"digest":"b5086d249e8502e9ead1d39156bb8d559bde7951cac0f14ce150345b4e42a2bf","names":[],"sources":[]}, @@ -519,12 +521,14 @@ {"digest":"b56f6a885da3dc394c69a3e3cc5c0cfc4da15601b63fda4989eee8f40efc5be4","names":["packages/session/session-title/src/types.ts#SessionTitleEventData"],"sources":["packages/session/session-title/src/types.ts:41"]}, {"digest":"b57a2889408de36e4516807194c73bdecd51a92f633ccd05db81bf049200f420","names":[],"sources":[]}, {"digest":"b57ae1b37e52bee0a21cbdace168dc6a15582bcda8c306dad962c5dcc8a8e28b","names":[],"sources":[]}, + {"digest":"b613f83572fe2fbf7259c72308cafd008f4fceea897749128a48bcd158f290aa","names":[],"sources":[]}, {"digest":"b63b089a00b3b9357675b3e0c3d3e9aa4fdbda1fba9c91a6b0c59ffdbb89f505","names":["packages/schedule/schedule/src/types.ts#LegacyAtScheduleRecord"],"sources":["packages/schedule/schedule/src/types.ts:167"]}, {"digest":"b6896938660a1dfe4ddd100fb3164d9f8fa57feeabfe37441a80106c9942697a","names":[],"sources":[]}, {"digest":"b6ef9c6210be8350cd465f485cf520031fffa6bf3809c61a1e713e834c5982d0","names":[],"sources":["packages/core/session/src/types.ts:203"]}, {"digest":"b6f94c17dd9bbd7cd57015198b7c7c88407f0e3b89faab176ca3c11c3d864d3e","names":[],"sources":[]}, {"digest":"b70f3e81fe11b7a5f2504fb47a4502fb8d9c8f0731fdfe8ddcaf9b41f5a6e196","names":[],"sources":[]}, {"digest":"b765ee31033cea463863faf3fc6a0a1b85c47c6a2f13fe1ce833abe56105114b","names":[],"sources":[]}, + {"digest":"b782058cef83caf3eb44a16adeef5657e4d5cdff2d50655aba418c4490670a69","names":["packages/llm/llm/src/message.ts#UserMessage"],"sources":["packages/llm/llm/src/message.ts:162"]}, {"digest":"b79641a9d25e756636dc0c706fdf3c4583ccb7d4920e009e6e86c0759cf4de4c","names":[],"sources":[]}, {"digest":"b79ada42962cad0190a9d465805260567621fa3a4abd757eb31e6016b52d5ab5","names":[],"sources":[]}, {"digest":"b815d7b91cdd9fc6ccc7dcba05cfc5ce1d55fab5fc0c0801803cd3630cedb602","names":[],"sources":[]}, @@ -534,6 +538,7 @@ {"digest":"b9305f0ea75ccd6a6061c27fde19f2ab85534f9f3c1a717b573b077ebae0f611","names":[],"sources":[]}, {"digest":"b978cff734e62143eb56c9125423ec275405eda969802d42aaf73ecb987d3b26","names":[],"sources":[]}, {"digest":"b993441f8ce7d27b80e619e113e232ae8e62d3d5a8340f6bfe12d8c4c018e62f","names":[],"sources":[]}, + {"digest":"ba2a1a8f194b1bb21f270713398f6643e206e8ff5f7f26f6d36a88e3f0f0ab10","names":[],"sources":[]}, {"digest":"ba4c40b4381e0dcdfab8fc9fc75b5c9b3e8f09ef56deeb1bec8e703b0ef8cd6d","names":[],"sources":[]}, {"digest":"ba77b58995e36f14bb13886b0493d0a1a63d5db10b1a6cd9f6ac65a72cf832ec","names":[],"sources":[]}, {"digest":"bab68657940075483f086fffb711682a45bcc99c43d659fbe1c36af21ac05dd7","names":[],"sources":[]}, @@ -568,6 +573,7 @@ {"digest":"cbca49bde16b656d5b8fbe4a8a507769f7b594c6287782b809c03814941bc5e3","names":[],"sources":[]}, {"digest":"cd186c309e2bef305705e1d324139240f5e681f4f6a957d6a25b0225f65560df","names":[],"sources":[]}, {"digest":"cda2345a9be9d139df49ccbd16dcba716f3440178bf20ff8b801fda1f5eb77dc","names":[],"sources":[]}, + {"digest":"cda79b839d8cc0c7a7bd09dd067de04925f6e9037080d878b65aaa5a383f117c","names":["packages/llm/llm/src/message.ts#Message"],"sources":["packages/llm/llm/src/message.ts:196"]}, {"digest":"cdca80b8dad24d4e7b1d5a288a8475ba8d8d46be385bd2da85f122ee836f1670","names":[],"sources":[]}, {"digest":"cdee10e87dbed8440d8b6935888624884878efc7af95402e94171b29f173dced","names":[],"sources":["packages/llm/llm-retry/src/types.ts:17"]}, {"digest":"cef171adf021a1867d244618a3dbbaafc22beb17c7f3df3ee203393271c8c6f6","names":["packages/core/tool-executor-ptc/src/types.ts#PtcDispatchEventData"],"sources":["packages/core/tool-executor-ptc/src/types.ts:21"]}, @@ -592,9 +598,11 @@ {"digest":"db874d463b0fdec77e9da1c4568f37cb70bd6596781eb93800db44fb8a116965","names":[],"sources":[]}, {"digest":"dba54c09c6e7aafc9acbed7792ecc920d99f92d0784aeeed10af55f7fe0bc8cd","names":[],"sources":["packages/core/tool-executor-ptc/src/types.ts:68"]}, {"digest":"dbdafceb9a900183cccc80596498d386f3bf7bddead2b8cfde68453aa4739edc","names":[],"sources":[]}, + {"digest":"dd00cdccd0ba487b92362ef67db58cd264f48c1bfd368291424a1da9a899489a","names":[],"sources":[]}, {"digest":"dd2780e7e07684332f7a9ab047237798d260e7eaffb1321cb38eca695d184c3d","names":[],"sources":[]}, {"digest":"dd5ee8762bd8a14b6c6bffedd1753e4e78505bb0f9ea1621018d03a54eef59d5","names":[],"sources":[]}, {"digest":"dd6cc2bb2a8f28248347021ad93565360b196a042360b4cd16b569d3a2a91f2d","names":[],"sources":[]}, + {"digest":"dde0cdf9d426768b649b5a9783d2144e7f900c35ef65229cd89036dbf411d1b0","names":[],"sources":[]}, {"digest":"de25352d2dc4196309bd84b5bac6319f6d5c898ea0e82bb5ae66c5738d0d426f","names":[],"sources":[]}, {"digest":"de30b1fa12c75f81df6aa4096e1595296d2d04e2be14a7c29849cf8028b79048","names":[],"sources":[]}, {"digest":"deaae5cd30987d4f49398e8f9d994c125907e7f928300745fb1dbf15f145d0e7","names":[],"sources":[]}, @@ -624,6 +632,7 @@ {"digest":"e7ce49f12953583fad217bf80fb9f1463acff2c3e9471a823782661675fb124c","names":[],"sources":[]}, {"digest":"e7e154d47d5fbc5366330fb4dade1f108c6dfe4335044d21ded14b7bf1dce084","names":[],"sources":[]}, {"digest":"e7e8f1318170b8284f16ab45a557afd4675c96071c0f2a456636481dab20b427","names":[],"sources":[]}, + {"digest":"e7f4cbb3a57f4cbdb61ea41e95bcba626ad1fff6bb9b1e063b1d50a32da6e864","names":[],"sources":[]}, {"digest":"e8de0f457fed5b9ff94dc4950b59ef75fd8ba8af665cc7899485924a62653367","names":[],"sources":[]}, {"digest":"e95f957154b2b1b58692af53d4131180b6dd19e2264869f876dbaa959991494e","names":[],"sources":[]}, {"digest":"ea08ec1df0c33108fd03345b9af199cf04dea450aa045d3c683f216311403403","names":[],"sources":["packages/experimental/agent-team/src/types.ts:234"]}, @@ -639,6 +648,7 @@ {"digest":"ef11a3b7fa2033ae4279545ff8a8a2ee4e301b9b73e5b91654c48e6d08283545","names":[],"sources":["packages/core/session/src/types.ts:301","packages/core/session/src/types.ts:303"]}, {"digest":"ef226a90ddac6cd8e631a3a337a3a3e27f9494be92befb7c718b404681d75e21","names":[],"sources":[]}, {"digest":"ef4f00923b9edbae15c7ef05320280bf61147ca17146cf59690359bfd5e5da45","names":[],"sources":[]}, + {"digest":"efa1a091017ce533e38b0ef3e14d11a5c102ff1793229b18dad719a4268d9adc","names":[],"sources":[]}, {"digest":"f05fb85e715af4ab4de0b6ba111166dd0435bd8bb74a0fa0f32b9d174113fc7c","names":[],"sources":[]}, {"digest":"f09e5e7acf5ff5ccd193ff2eeec015fa2ff7c29d98bc465f8c3302058d47e29b","names":["packages/core/agent/src/types.ts#ModelSelection"],"sources":["packages/core/agent/src/types.ts:79"]}, {"digest":"f1687f982fa3d41ef29e159feed154ecd13af4dd209acaee16038f223f2e9402","names":["packages/core/session/src/types.ts#TurnEndReason"],"sources":["packages/core/session/src/types.ts:234"]}, @@ -650,7 +660,6 @@ {"digest":"f2da0f27c1e6add59982c37b15e4a4912c5b33466859cce3b13d503600e24aef","names":["packages/llm/llm/src/types.ts#FileBlock"],"sources":["packages/llm/llm/src/types.ts:98"]}, {"digest":"f311671e07bc7fa645a0b6597de9966b97147956565296ba76a36543801b66c5","names":["packages/llm/llm/src/types.ts#SystemPromptUpdate"],"sources":["packages/llm/llm/src/types.ts:414"]}, {"digest":"f4a66f654996c093aae700ea4c434ff2ee3569fea9c0e5813ccf6afdccabd163","names":[],"sources":[]}, - {"digest":"f4ed4717b4c259e933b550a8dcd90dc93628cda8a3c7affcaa53b9c13c1d53eb","names":[],"sources":["packages/core/session/src/types.ts:313"]}, {"digest":"f5520c1d261ca86ea03735799c311a0a9ede8babb588cf0a65422576d2ee0ad1","names":[],"sources":[]}, {"digest":"f56c5b59ca0161310c338d31dd16cf14287ccd602ff4181058df178c1c286d7f","names":["packages/context/session-reference/src/types.ts#SessionReferenceSource"],"sources":["packages/context/session-reference/src/types.ts:13"]}, {"digest":"f6329fb30c7ea8013e8975ca5e78284fb7685cc6bd620e8aa4e9a99bc81d45eb","names":[],"sources":[]}, @@ -666,7 +675,6 @@ {"digest":"f90eb4ab2d3897bc20c521a038e3119d581f791ec974b1a0004f63a8ab9e232d","names":[],"sources":["packages/core/session/src/types.ts:290","packages/deliverables/workspace-changes/src/types.ts:106"]}, {"digest":"f97f47b0eb87905ed77cc27c5e4ed75ef805cb372c98905253709de1aab9b63c","names":[],"sources":[]}, {"digest":"f9cfc89e8fe085b4fcc9cd32b74b6f103608c54d7d8cd97b47c01de34f252ba3","names":[],"sources":[]}, - {"digest":"fa106c5b33b657e8ef16e8cbf39f3b7d44e5cbcdf4f9303d9db6410f28951e42","names":[],"sources":["packages/core/agent/src/types.ts:110"]}, {"digest":"fa87537f4a9c1e25602990a8bf686b383e9e00352ec8946e16ae24ba7c1625be","names":["packages/llm/llm/src/message.ts#AssistantMessage"],"sources":["packages/llm/llm/src/message.ts:167"]}, {"digest":"fab2bb09bc5e3b9db0685e8f5500b029af1a2b33e70bc703c1d5d0f596fbc1fd","names":["packages/sandbox/sandbox/src/index.ts#SandboxMode"],"sources":["packages/sandbox/sandbox/src/index.ts:30"]}, {"digest":"fab35c4520883c0d69e42d079c9f543fc5ac7a2d15b537b5572fcd99059c7018","names":[],"sources":[]}, @@ -684,18 +692,16 @@ {"digest":"fee1caf8ab6a65c1bc981f5a1730164490f22a2a89ffa3d0b76f1f432c44c8b5","names":[],"sources":[]} ], "nodes": { - "00c054d3018c198d": {"kind":"object","properties":[{"name":"maxTokens","type":"0657d3ca5ac4e7bf","optional":false},{"name":"messageSeqs","type":"5d03ba38734809bc","optional":false},{"name":"messages","type":"a927d444c928ac0e","optional":false},{"name":"reasoningEffort","type":"4cd48f3e5108bd6e","optional":true},{"name":"route","type":"07e1a58c58b593f5","optional":false},{"name":"system","type":"4cd48f3e5108bd6e","optional":false},{"name":"titleProvider","type":"4cd48f3e5108bd6e","optional":false}],"indices":[]}, "0116b1f89c05221d": {"kind":"literal","value":"web_search_20250305"}, "0203243b72fc867a": {"kind":"object","properties":[{"name":"kind","type":"24b10f7cdae64928","optional":false}],"indices":[]}, "022623b9ef98ec47": {"kind":"object","properties":[{"name":"form","type":"383cf73ae4748e5b","optional":false},{"name":"kind","type":"f68c335306535a00","optional":false}],"indices":[]}, "023a355b2be0d688": {"kind":"object","properties":[{"name":"kind","type":"254dce209e735f9c","optional":false}],"indices":[]}, "02b2c23f2d94b028": {"kind":"object","properties":[{"name":"kind","type":"c5687b1313206a6f","optional":false}],"indices":[]}, "0341d4c3dfcd5325": {"kind":"literal","value":"product-interaction"}, - "040cd65423bd6426": {"kind":"union","types":["09f3f8dc417f2782","0f6265188dae7ba6","2cf075b013d363ad","4b259f7f8f0a44ad","fa87537f4a9c1e25"]}, + "05205074b24d8340": {"kind":"literal","value":"action-cards"}, "05a2796621b1ee6c": {"kind":"literal","value":"unknown"}, "05d2ae1e0c7f8770": {"kind":"union","types":["1571525adc95a24e","9e07693fd7cd9bab"]}, "0657d3ca5ac4e7bf": {"kind":"primitive","type":"number"}, - "0755b7d1128319ba": {"kind":"object","properties":[{"name":"data","type":"fa106c5b33b657e8","optional":false},{"name":"ignorable","type":"dacf3d5f3e4ff619","optional":true},{"name":"seq","type":"0657d3ca5ac4e7bf","optional":false},{"name":"time","type":"0657d3ca5ac4e7bf","optional":false},{"name":"type","type":"829a2fb3be6efd4b","optional":false}],"indices":[]}, "07e1a58c58b593f5": {"kind":"object","properties":[{"name":"model","type":"4cd48f3e5108bd6e","optional":false},{"name":"provider","type":"4cd48f3e5108bd6e","optional":false}],"indices":[]}, "07eccc9ea2f9b543": {"kind":"literal","value":"security-privacy-permission"}, "09655b34d7f3cf1e": {"kind":"literal","value":"system-prompt"}, @@ -717,7 +723,6 @@ "0ed8d1108fe7a5e1": {"kind":"object","properties":[{"name":"kind","type":"a9059a81430b3207","optional":false}],"indices":[]}, "0eeb17e1df89b1eb": {"kind":"object","properties":[{"name":"form","type":"fee1caf8ab6a65c1","optional":false},{"name":"kind","type":"24b10f7cdae64928","optional":false}],"indices":[]}, "0f46153645c29784": {"kind":"object","properties":[{"name":"height","type":"0657d3ca5ac4e7bf","optional":false},{"name":"width","type":"0657d3ca5ac4e7bf","optional":false}],"indices":[]}, - "0f6265188dae7ba6": {"kind":"object","properties":[{"name":"content","type":"b52f215a6cd69105","optional":false},{"name":"id","type":"4cd48f3e5108bd6e","optional":false},{"name":"role","type":"589d0d91239681f8","optional":false},{"name":"source","type":"5bc2d7f02503fc6b","optional":false,"compatibility":{"version":1,"policy":"session-source-attribution","binding":"session.developer-message.source","discriminator":"kind","unknownKinds":"preserve","attributionKinds":["tmux-context","user-question-reply","working-directory"]}}],"indices":[]}, "0f8512903d94f57a": {"kind":"object","properties":[{"name":"data","type":"e481c46af3813e00","optional":false},{"name":"ignorable","type":"dacf3d5f3e4ff619","optional":true},{"name":"seq","type":"0657d3ca5ac4e7bf","optional":false},{"name":"time","type":"0657d3ca5ac4e7bf","optional":false},{"name":"type","type":"4312134fc6f9cad5","optional":false}],"indices":[]}, "0fe52387f6505270": {"kind":"literal","value":"plan-mode"}, "1033093edd0db80f": {"kind":"object","properties":[{"name":"data","type":"1ccb56061942f5cc","optional":false},{"name":"ignorable","type":"dacf3d5f3e4ff619","optional":true},{"name":"seq","type":"0657d3ca5ac4e7bf","optional":false},{"name":"surfaceOp","type":"335e242de1fcc17b","optional":false},{"name":"time","type":"0657d3ca5ac4e7bf","optional":false},{"name":"type","type":"6ab45ac76839722d","optional":false}],"indices":[]}, @@ -794,11 +799,9 @@ "2aad1179f9576314": {"kind":"object","properties":[{"name":"form","type":"6f9e00383836275e","optional":false},{"name":"kind","type":"24b10f7cdae64928","optional":false},{"name":"sections","type":"c748fb42c80a1200","optional":false}],"indices":[]}, "2ab12a0e345658f9": {"kind":"array","element":"83da72c6857601c7"}, "2abb711460e6238d": {"kind":"object","properties":[{"name":"index","type":"0657d3ca5ac4e7bf","optional":false},{"name":"text","type":"4cd48f3e5108bd6e","optional":false},{"name":"type","type":"6f091fe32ab24407","optional":false}],"indices":[]}, - "2ae8594a998c9862": {"kind":"object","properties":[{"name":"data","type":"2cf075b013d363ad","optional":false},{"name":"ignorable","type":"dacf3d5f3e4ff619","optional":true},{"name":"seq","type":"0657d3ca5ac4e7bf","optional":false},{"name":"sourceEventSeqs","type":"5d03ba38734809bc","optional":true},{"name":"surfaceOp","type":"335e242de1fcc17b","optional":false},{"name":"time","type":"0657d3ca5ac4e7bf","optional":false},{"name":"type","type":"de25352d2dc41963","optional":false}],"indices":[]}, "2aed07493b1c65b6": {"kind":"literal","value":"tool/ptc-dispatch-start"}, "2c4c21f19f426a58": {"kind":"literal","value":"unavailable"}, "2c64d4bc7264fd3f": {"kind":"literal","value":"step/start"}, - "2cf075b013d363ad": {"kind":"object","properties":[{"name":"content","type":"b52f215a6cd69105","optional":false},{"name":"id","type":"4cd48f3e5108bd6e","optional":false},{"name":"role","type":"68f44f59e91c4139","optional":false},{"name":"source","type":"5bc2d7f02503fc6b","optional":false,"compatibility":{"version":1,"policy":"session-source-attribution","binding":"session.user-message.source","discriminator":"kind","unknownKinds":"preserve","attributionKinds":["tmux-context","user-question-reply","working-directory"]}}],"indices":[]}, "2d11ca7b0d4493e2": {"kind":"object","properties":[{"name":"max_uses","type":"0657d3ca5ac4e7bf","optional":false},{"name":"name","type":"935c8d486292b13c","optional":false},{"name":"type","type":"0116b1f89c05221d","optional":false}],"indices":[]}, "2da725e554cd8930": {"kind":"literal","value":"fork"}, "2e3523541e942ef6": {"kind":"union","types":["18dc21f12bbac8ad","8e8e91646a5cae3f","e6063d0451dc0202","eea687d437880bd5"]}, @@ -810,6 +813,7 @@ "30d5a537b2966857": {"kind":"union","types":["1b5649238876e288","ada310bf0bdb8fed","b81e9de81a481b24"]}, "312aee2f0c42aabb": {"kind":"object","properties":[{"name":"form","type":"e8de0f457fed5b9f","optional":false},{"name":"kind","type":"d9edf1862e4471d6","optional":false},{"name":"summary","type":"4cd48f3e5108bd6e","optional":false}],"indices":[]}, "31907de956375182": {"kind":"object","properties":[{"name":"compactionId","type":"4cd48f3e5108bd6e","optional":false},{"name":"kind","type":"6b23ed9ce1b397e6","optional":false},{"name":"sourceCommandId","type":"4cd48f3e5108bd6e","optional":true}],"indices":[]}, + "3247e566f4340029": {"kind":"object","properties":[{"name":"form","type":"6f9e00383836275e","optional":false},{"name":"kind","type":"05205074b24d8340","optional":false},{"name":"sections","type":"c748fb42c80a1200","optional":false},{"name":"through","type":"0657d3ca5ac4e7bf","optional":false}],"indices":[]}, "326a9bfda27dda58": {"kind":"object","properties":[{"name":"maxTokens","type":"dacf3d5f3e4ff619","optional":true},{"name":"reasoningEffort","type":"dacf3d5f3e4ff619","optional":true}],"indices":[]}, "3305a4bb6b6c29b4": {"kind":"union","types":["0341d4c3dfcd5325","07eccc9ea2f9b543","5bd310104cfe448e","7180844e1066230c","9d80129e13edfb6f","e4cac694f8b6e1d5","f05fb85e715af4ab"]}, "33341aac09312300": {"kind":"union","types":["3860b4757d512a28","487b65c37bfedb8b","71bb3254ec8f1e49","e72269f931559e90"]}, @@ -852,7 +856,7 @@ "44818000e56acb97": {"kind":"literal","value":"feedback/message-delete"}, "44c8c77aa9aa100b": {"kind":"literal","value":"subagent/catalog"}, "4513e088d43e6c68": {"kind":"object","properties":[{"name":"data","type":"ef11a3b7fa2033ae","optional":false},{"name":"ignorable","type":"dacf3d5f3e4ff619","optional":true},{"name":"seq","type":"0657d3ca5ac4e7bf","optional":false},{"name":"time","type":"0657d3ca5ac4e7bf","optional":false},{"name":"type","type":"2c64d4bc7264fd3f","optional":false}],"indices":[]}, - "454a0fa879e38076": {"kind":"array","element":"2cf075b013d363ad"}, + "45f005e6bbb76452": {"kind":"object","properties":[{"name":"form","type":"16d0507cb94b4cfc","optional":false},{"name":"kind","type":"05205074b24d8340","optional":false},{"name":"through","type":"0657d3ca5ac4e7bf","optional":false}],"indices":[]}, "45f2a07ea9457312": {"kind":"object","properties":[{"name":"callId","type":"4cd48f3e5108bd6e","optional":true},{"name":"id","type":"4cd48f3e5108bd6e","optional":false},{"name":"reason","type":"4cd48f3e5108bd6e","optional":true},{"name":"toolName","type":"4cd48f3e5108bd6e","optional":false}],"indices":[]}, "477614bca595a9e7": {"kind":"object","properties":[{"name":"targets","type":"9e2c06d8653585b3","optional":false}],"indices":[]}, "47e5d84f1a93450a": {"kind":"literal","value":"session-log-deepseek/delivery-accepted"}, @@ -901,17 +905,18 @@ "5808e36c95355f0e": {"kind":"union","types":["1d76406cbb2ddadb","287211846beea1ec","693f4952fb5565da","8e31bb958e9b015e","ab7ec5f9eb2c6dfa"]}, "587bb11da730e06a": {"kind":"literal","value":"session/title-llm-request"}, "589d0d91239681f8": {"kind":"literal","value":"developer"}, + "58b122df513aeaf7": {"kind":"object","properties":[{"name":"data","type":"b782058cef83caf3","optional":false},{"name":"ignorable","type":"dacf3d5f3e4ff619","optional":true},{"name":"seq","type":"0657d3ca5ac4e7bf","optional":false},{"name":"sourceEventSeqs","type":"5d03ba38734809bc","optional":true},{"name":"surfaceOp","type":"335e242de1fcc17b","optional":false},{"name":"time","type":"0657d3ca5ac4e7bf","optional":false},{"name":"type","type":"de25352d2dc41963","optional":false}],"indices":[]}, "58f94390b982f8a5": {"kind":"literal","value":"image/png"}, "5abab3a27fd27366": {"kind":"literal","value":"text-delta"}, "5b232a9b9bfcc0fc": {"kind":"literal","value":"after"}, "5b6fb1f226ff5640": {"kind":"object","properties":[{"name":"commandId","type":"4cd48f3e5108bd6e","optional":false},{"name":"kind","type":"1ab2ef1296f157ba","optional":false},{"name":"sourceEventSeq","type":"0657d3ca5ac4e7bf","optional":true},{"name":"text","type":"4cd48f3e5108bd6e","optional":true}],"indices":[]}, "5bb69b88517f6214": {"kind":"object","properties":[{"name":"form","type":"e8de0f457fed5b9f","optional":false},{"name":"kind","type":"99e7e7d7fed55ae6","optional":false},{"name":"summary","type":"4cd48f3e5108bd6e","optional":false}],"indices":[]}, - "5bc2d7f02503fc6b": {"kind":"union","types":["0203243b72fc867a","022623b9ef98ec47","02b2c23f2d94b028","0ed8d1108fe7a5e1","0eeb17e1df89b1eb","11638dc58a31496f","1938c8c20f1cba7c","1e709346005826eb","1fdad624334d3548","2152caf1a71e588e","2235d957bf4b7c6c","224aa11d4d5f88c4","24ce31aa528272e5","260523b412c19ad8","28936c614bd4e7e2","290f32bfd81ff85a","297fbd8067d7815a","2aad1179f9576314","312aee2f0c42aabb","31907de956375182","3b04790e08b35241","3b4f2c9ef2c1217e","3bdb8dc6989655b0","408927267422a7b2","4991dfc415c9efeb","49dab019d00e8c26","4a9bef9f27a94252","4e97c3d85c0fc817","52c6113cb0e53f34","53cf1704ae0709c7","5654685d7557ec74","58015da1c1950a66","5bb69b88517f6214","5bc66269cfaf614c","5cdbf342b7748d15","5d7a64ed6aac5551","6085488d223aeffd","6292bcbedaa97fea","65a8400e451e667e","672c4aa2a9b736e3","6ccb6bc423fb3952","6dacb1584f2bfbe2","6ea608bdc8a7a63d","6f22a9d02b9a1f11","6f44f32be9bcd9a8","719c51f67074f970","7554565be416b034","756b5e23a5cb2c7f","7df62978a2b7de85","7eaef7a763ba5b94","7f959241c24128b4","80677c8c8b804189","825ca23416b1ea9d","8338ee303e2bd448","854db3efdfb0cd39","85fe96bf15d5c755","8620189c8e335827","87b1a33564b9a4d6","88841202942c7683","8a046b52331959aa","8c1c4f72755a78ea","8c627d7a45fa406c","8f324cea3916c051","8fcb0b44a575e998","96bfb916a727cd44","99135d8a39379779","99f0e98184709ad1","9edc162949abf93b","9f65c43e340bba31","a15a19ef63cf5d1b","a16e567c8cd2c55c","a2066bd52f61218b","a3c6f1ea38e9a61d","aa2bf9e82b9fdeb9","aae343bc3d663c83","ada310bf0bdb8fed","ade057f4fdc387ae","b062078e899958f9","b2a5c8c008a31e3d","b3681c026a0b98e9","b4ec0af39fd52ed9","b6f94c17dd9bbd7c","b70f3e81fe11b7a5","b79641a9d25e7566","ba4c40b4381e0dcd","baccc97ec34be6c4","c5228b7a55498d84","c55827fba7018198","cb1c8959e1726f82","cda2345a9be9d139","d85590a5675c6e2f","d9c462b9f62fdd3e","db06ef35b27a3842","dba54c09c6e7aafc","dd5ee8762bd8a14b","dd6cc2bb2a8f2824","deaae5cd30987d4f","edbbfff1128099fd","ee577a0cc18664c9","f230c0b583e23b25","f29d63336e8266d8","f4a66f654996c093","f56c5b59ca016131","f6329fb30c7ea801","f6605d6c57933aea","f6afe41e3926f422","f7283223aacdc39b","f97f47b0eb87905e","fc2ae6962f9f8801","fcf81d3c58c13593","fe1a7acdeed4d0f5"]}, "5bc66269cfaf614c": {"kind":"object","properties":[{"name":"form","type":"16d0507cb94b4cfc","optional":false},{"name":"kind","type":"d9edf1862e4471d6","optional":false}],"indices":[]}, "5bd310104cfe448e": {"kind":"literal","value":"resource-cost"}, "5c45bf4c544a7211": {"kind":"object","properties":[{"name":"data","type":"8a9c990773f798ea","optional":false},{"name":"ignorable","type":"dacf3d5f3e4ff619","optional":true},{"name":"seq","type":"0657d3ca5ac4e7bf","optional":false},{"name":"time","type":"0657d3ca5ac4e7bf","optional":false},{"name":"type","type":"ae63d9d1080ed894","optional":false}],"indices":[]}, "5cb63db9fe57dc09": {"kind":"literal","value":"teardown"}, "5cdbf342b7748d15": {"kind":"object","properties":[{"name":"form","type":"16d0507cb94b4cfc","optional":false},{"name":"kind","type":"f68c335306535a00","optional":false}],"indices":[]}, + "5d00abf938272f22": {"kind":"array","element":"b782058cef83caf3"}, "5d03ba38734809bc": {"kind":"array","element":"0657d3ca5ac4e7bf"}, "5d17f27a1ef9f702": {"kind":"literal","value":"tool-workflow/agent-end"}, "5d542ac7bdf13310": {"kind":"object","properties":[{"name":"childCreatedAt","type":"0657d3ca5ac4e7bf","optional":false},{"name":"childId","type":"4cd48f3e5108bd6e","optional":false},{"name":"label","type":"4cd48f3e5108bd6e","optional":true},{"name":"mode","type":"d28765f87d6b6ab4","optional":false},{"name":"version","type":"93d38436347f67c9","optional":false}],"indices":[]}, @@ -919,6 +924,7 @@ "5dd766ccd1702a11": {"kind":"object","properties":[{"name":"shadowedRange","type":"9e41386b3a0c9572","optional":false},{"name":"shadowedSeqs","type":"5d03ba38734809bc","optional":false},{"name":"shadowedTokenCount","type":"0657d3ca5ac4e7bf","optional":false}],"indices":[]}, "5e6db6e24948d4a8": {"kind":"object","properties":[{"name":"data","type":"17d1afb770d99419","optional":false},{"name":"ignorable","type":"dacf3d5f3e4ff619","optional":true},{"name":"seq","type":"0657d3ca5ac4e7bf","optional":false},{"name":"time","type":"0657d3ca5ac4e7bf","optional":false},{"name":"type","type":"1c2686c0682e7bc5","optional":false}],"indices":[]}, "5ebce1317eacd2c1": {"kind":"literal","value":"subagent/descriptor"}, + "5ef58f2a1bdc576c": {"kind":"object","properties":[{"name":"data","type":"7404c6123fdaa591","optional":false},{"name":"ignorable","type":"dacf3d5f3e4ff619","optional":true},{"name":"seq","type":"0657d3ca5ac4e7bf","optional":false},{"name":"sourceEventSeqs","type":"5d03ba38734809bc","optional":true},{"name":"surfaceOp","type":"335e242de1fcc17b","optional":false},{"name":"time","type":"0657d3ca5ac4e7bf","optional":false},{"name":"type","type":"1379f0b72275994d","optional":false}],"indices":[]}, "5f2117d1856a5ab4": {"kind":"object","properties":[{"name":"code","type":"4cd48f3e5108bd6e","optional":false},{"name":"message","type":"4cd48f3e5108bd6e","optional":false},{"name":"offloadImages","type":"0657d3ca5ac4e7bf","optional":true},{"name":"providerRetryAfterMs","type":"0657d3ca5ac4e7bf","optional":true},{"name":"requestId","type":"4cd48f3e5108bd6e","optional":true},{"name":"status","type":"0657d3ca5ac4e7bf","optional":true}],"indices":[]}, "5f26a6c20b37632f": {"kind":"object","properties":[{"name":"data","type":"9a410ddf56792b2b","optional":false},{"name":"ignorable","type":"dacf3d5f3e4ff619","optional":true},{"name":"seq","type":"0657d3ca5ac4e7bf","optional":false},{"name":"time","type":"0657d3ca5ac4e7bf","optional":false},{"name":"type","type":"ad92f473a03ae725","optional":false}],"indices":[]}, "5f758f3de5f00c18": {"kind":"object","properties":[{"name":"label","type":"4cd48f3e5108bd6e","optional":true},{"name":"mode","type":"d28765f87d6b6ab4","optional":false},{"name":"provider","type":"4cd48f3e5108bd6e","optional":false},{"name":"version","type":"0657d3ca5ac4e7bf","optional":false}],"indices":[]}, @@ -967,16 +973,19 @@ "71bb3254ec8f1e49": {"kind":"object","properties":[{"name":"dt","type":"5d03ba38734809bc","optional":false},{"name":"index","type":"0657d3ca5ac4e7bf","optional":false},{"name":"texts","type":"93c33d9687613293","optional":false},{"name":"time0","type":"0657d3ca5ac4e7bf","optional":false},{"name":"type","type":"688e853dc149f0d3","optional":false}],"indices":[]}, "72ec79127a9c0d02": {"kind":"array","element":"c592ce75aab73fca"}, "7382c4fc95fe8c5d": {"kind":"literal","value":"remove"}, + "7404c6123fdaa591": {"kind":"object","properties":[{"name":"headerSeq","type":"0657d3ca5ac4e7bf","optional":true},{"name":"message","type":"7580e7d96dc8475f","optional":false},{"name":"step","type":"0657d3ca5ac4e7bf","optional":false},{"name":"turn","type":"0657d3ca5ac4e7bf","optional":false}],"indices":[]}, "750a2b129d67704d": {"kind":"union","types":["5cb63db9fe57dc09","6cf082d0fb6034de","bab6865794007548"]}, "7554565be416b034": {"kind":"object","properties":[{"name":"form","type":"383cf73ae4748e5b","optional":false},{"name":"kind","type":"2f3f201c8846e19c","optional":false}],"indices":[]}, "756b5e23a5cb2c7f": {"kind":"object","properties":[{"name":"form","type":"383cf73ae4748e5b","optional":false},{"name":"kind","type":"8067b0d232c350f1","optional":false}],"indices":[]}, + "7580e7d96dc8475f": {"kind":"object","properties":[{"name":"content","type":"b52f215a6cd69105","optional":false},{"name":"id","type":"4cd48f3e5108bd6e","optional":false},{"name":"role","type":"589d0d91239681f8","optional":false},{"name":"source","type":"7a5b68bd1a5118c8","optional":false,"compatibility":{"version":1,"policy":"session-source-attribution","binding":"session.developer-message.source","discriminator":"kind","unknownKinds":"preserve","attributionKinds":["action-cards","tmux-context","user-question-reply","working-directory"]}}],"indices":[]}, "7624ca28c61ca0ea": {"kind":"literal","value":"create"}, "763c8a20af487263": {"kind":"object","properties":[{"name":"data","type":"7d226b9fd95b4ba2","optional":false},{"name":"ignorable","type":"dacf3d5f3e4ff619","optional":true},{"name":"seq","type":"0657d3ca5ac4e7bf","optional":false},{"name":"time","type":"0657d3ca5ac4e7bf","optional":false},{"name":"type","type":"237c004c73b6b288","optional":false}],"indices":[]}, "76632533f67879dc": {"kind":"literal","value":"delegation"}, "78a9be340090b735": {"kind":"literal","value":"tmux-context"}, + "78b9204976ab9faa": {"kind":"array","element":"cda79b839d8cc0c7"}, "79650cfa10aa3c52": {"kind":"object","properties":[{"name":"error","type":"5f2117d1856a5ab4","optional":false},{"name":"kind","type":"a2be0f0cb59f0d4a","optional":false}],"indices":[]}, - "79a4c87f8bfb4d6d": {"kind":"object","properties":[{"name":"data","type":"00c054d3018c198d","optional":false},{"name":"ignorable","type":"dacf3d5f3e4ff619","optional":true},{"name":"seq","type":"0657d3ca5ac4e7bf","optional":false},{"name":"time","type":"0657d3ca5ac4e7bf","optional":false},{"name":"type","type":"587bb11da730e06a","optional":false}],"indices":[]}, "79ddc907312f97cf": {"kind":"object","properties":[{"name":"todos","type":"4b7ddaf3710b88ff","optional":false}],"indices":[]}, + "7a5b68bd1a5118c8": {"kind":"union","types":["0203243b72fc867a","022623b9ef98ec47","02b2c23f2d94b028","0ed8d1108fe7a5e1","0eeb17e1df89b1eb","11638dc58a31496f","1938c8c20f1cba7c","1e709346005826eb","1fdad624334d3548","2152caf1a71e588e","2235d957bf4b7c6c","224aa11d4d5f88c4","24ce31aa528272e5","260523b412c19ad8","28936c614bd4e7e2","290f32bfd81ff85a","297fbd8067d7815a","2aad1179f9576314","312aee2f0c42aabb","31907de956375182","3247e566f4340029","3b04790e08b35241","3b4f2c9ef2c1217e","3bdb8dc6989655b0","408927267422a7b2","45f005e6bbb76452","4991dfc415c9efeb","49dab019d00e8c26","4a9bef9f27a94252","4e97c3d85c0fc817","52c6113cb0e53f34","53cf1704ae0709c7","5654685d7557ec74","58015da1c1950a66","5bb69b88517f6214","5bc66269cfaf614c","5cdbf342b7748d15","5d7a64ed6aac5551","6085488d223aeffd","6292bcbedaa97fea","65a8400e451e667e","672c4aa2a9b736e3","6ccb6bc423fb3952","6dacb1584f2bfbe2","6ea608bdc8a7a63d","6f22a9d02b9a1f11","6f44f32be9bcd9a8","719c51f67074f970","7554565be416b034","756b5e23a5cb2c7f","7df62978a2b7de85","7eaef7a763ba5b94","7f959241c24128b4","80677c8c8b804189","825ca23416b1ea9d","8338ee303e2bd448","854db3efdfb0cd39","85fe96bf15d5c755","8620189c8e335827","87b1a33564b9a4d6","88841202942c7683","8a046b52331959aa","8c1c4f72755a78ea","8c627d7a45fa406c","8f324cea3916c051","8fcb0b44a575e998","96bfb916a727cd44","99135d8a39379779","99f0e98184709ad1","9edc162949abf93b","9f65c43e340bba31","a15a19ef63cf5d1b","a16e567c8cd2c55c","a2066bd52f61218b","a3c6f1ea38e9a61d","aa2bf9e82b9fdeb9","aae343bc3d663c83","ada310bf0bdb8fed","ade057f4fdc387ae","b062078e899958f9","b2a5c8c008a31e3d","b3681c026a0b98e9","b4ec0af39fd52ed9","b613f83572fe2fbf","b6f94c17dd9bbd7c","b70f3e81fe11b7a5","b79641a9d25e7566","ba2a1a8f194b1bb2","ba4c40b4381e0dcd","baccc97ec34be6c4","c5228b7a55498d84","c55827fba7018198","cb1c8959e1726f82","cda2345a9be9d139","d85590a5675c6e2f","d9c462b9f62fdd3e","db06ef35b27a3842","dba54c09c6e7aafc","dd00cdccd0ba487b","dd5ee8762bd8a14b","dd6cc2bb2a8f2824","dde0cdf9d426768b","deaae5cd30987d4f","e7f4cbb3a57f4cbd","edbbfff1128099fd","ee577a0cc18664c9","f230c0b583e23b25","f29d63336e8266d8","f4a66f654996c093","f56c5b59ca016131","f6329fb30c7ea801","f6605d6c57933aea","f6afe41e3926f422","f7283223aacdc39b","f97f47b0eb87905e","fc2ae6962f9f8801","fcf81d3c58c13593","fe1a7acdeed4d0f5"]}, "7a651f3b672c52fb": {"kind":"literal","value":"session/title"}, "7af85bf70d4eafce": {"kind":"object","properties":[{"name":"ignorable","type":"dacf3d5f3e4ff619","optional":true},{"name":"seq","type":"0657d3ca5ac4e7bf","optional":false},{"name":"surfaceOp","type":"335e242de1fcc17b","optional":false},{"name":"time","type":"0657d3ca5ac4e7bf","optional":false},{"name":"type","type":"4cd48f3e5108bd6e","optional":false}],"indices":[]}, "7b1ce60193fa8b69": {"kind":"literal","value":"working-directory"}, @@ -1047,7 +1056,6 @@ "9645cf93769a6dfb": {"kind":"literal","value":"user-question-reply"}, "96bfb916a727cd44": {"kind":"object","properties":[{"name":"form","type":"383cf73ae4748e5b","optional":false},{"name":"kind","type":"24b10f7cdae64928","optional":false}],"indices":[]}, "96f4a9c81fc0f940": {"kind":"union","types":["3e2ece4967cfebe9","62ecb62dc0fe1b5e"]}, - "97e0162f983c2745": {"kind":"object","properties":[{"name":"data","type":"f4ed4717b4c259e9","optional":false},{"name":"ignorable","type":"dacf3d5f3e4ff619","optional":true},{"name":"seq","type":"0657d3ca5ac4e7bf","optional":false},{"name":"sourceEventSeqs","type":"5d03ba38734809bc","optional":true},{"name":"surfaceOp","type":"335e242de1fcc17b","optional":false},{"name":"time","type":"0657d3ca5ac4e7bf","optional":false},{"name":"type","type":"1379f0b72275994d","optional":false}],"indices":[]}, "9890113e97dd3541": {"kind":"object","properties":[{"name":"mode","type":"fab2bb09bc5e3b9d","optional":false},{"name":"source","type":"76632533f67879dc","optional":true}],"indices":[]}, "99135d8a39379779": {"kind":"object","properties":[{"name":"form","type":"aec150797b655c5f","optional":false},{"name":"kind","type":"9ac52f0f84fec2eb","optional":false}],"indices":[]}, "9947aa5938fe19a8": {"kind":"union","types":["576a2de8355eb098","a99ec0993c62c058"]}, @@ -1080,6 +1088,7 @@ "a2be0f0cb59f0d4a": {"kind":"literal","value":"error"}, "a3a259a1aa5401af": {"kind":"literal","value":"hook/result"}, "a3c6f1ea38e9a61d": {"kind":"object","properties":[{"name":"form","type":"e8de0f457fed5b9f","optional":false},{"name":"kind","type":"9ac52f0f84fec2eb","optional":false},{"name":"summary","type":"4cd48f3e5108bd6e","optional":false}],"indices":[]}, + "a3e299483348f27a": {"kind":"object","properties":[{"name":"maxTokens","type":"0657d3ca5ac4e7bf","optional":false},{"name":"messageSeqs","type":"5d03ba38734809bc","optional":false},{"name":"messages","type":"78b9204976ab9faa","optional":false},{"name":"reasoningEffort","type":"4cd48f3e5108bd6e","optional":true},{"name":"route","type":"07e1a58c58b593f5","optional":false},{"name":"system","type":"4cd48f3e5108bd6e","optional":false},{"name":"titleProvider","type":"4cd48f3e5108bd6e","optional":false}],"indices":[]}, "a3edd1efd1fef21b": {"kind":"literal","value":"workspace-write"}, "a40d12070f6f4a12": {"kind":"object","properties":[{"name":"task","type":"51edec100659da3a","optional":false},{"name":"teamId","type":"4cd48f3e5108bd6e","optional":false},{"name":"version","type":"a0f906856fc06f1c","optional":false}],"indices":[]}, "a6567ccb2e530606": {"kind":"object","properties":[{"name":"data","type":"0d47a8f3d847243c","optional":false},{"name":"ignorable","type":"dacf3d5f3e4ff619","optional":true},{"name":"seq","type":"0657d3ca5ac4e7bf","optional":false},{"name":"time","type":"0657d3ca5ac4e7bf","optional":false},{"name":"type","type":"0a8a062bc9c3c0d9","optional":false}],"indices":[]}, @@ -1092,7 +1101,6 @@ "a8a2b9011fc06786": {"kind":"literal","value":"team/message/delivered"}, "a9059a81430b3207": {"kind":"literal","value":"hooks-claude-code"}, "a926dc71814edd01": {"kind":"object","properties":[{"name":"everySeconds","type":"0657d3ca5ac4e7bf","optional":false},{"name":"id","type":"4cd48f3e5108bd6e","optional":false},{"name":"kind","type":"e42b0f40b5d0b628","optional":false},{"name":"prompt","type":"4cd48f3e5108bd6e","optional":false},{"name":"scheduledAt","type":"4cd48f3e5108bd6e","optional":false},{"name":"title","type":"4cd48f3e5108bd6e","optional":true}],"indices":[]}, - "a927d444c928ac0e": {"kind":"array","element":"040cd65423bd6426"}, "a99ec0993c62c058": {"kind":"object","properties":[{"name":"compactionId","type":"4cd48f3e5108bd6e","optional":false},{"name":"llmStreamCall","type":"dacf3d5f3e4ff619","optional":false},{"name":"maxTokens","type":"0657d3ca5ac4e7bf","optional":true},{"name":"model","type":"4cd48f3e5108bd6e","optional":false},{"name":"provider","type":"4cd48f3e5108bd6e","optional":false},{"name":"rawOutput","type":"b52f215a6cd69105","optional":false},{"name":"shadowedRange","type":"9e41386b3a0c9572","optional":false},{"name":"shadowedSeqs","type":"5d03ba38734809bc","optional":false},{"name":"shadowedTokenCount","type":"0657d3ca5ac4e7bf","optional":false},{"name":"sourceCommandId","type":"4cd48f3e5108bd6e","optional":true},{"name":"summary","type":"b52f215a6cd69105","optional":false},{"name":"usage","type":"fec5442b9ee4122e","optional":true}],"indices":[]}, "a99f76fd149051c3": {"kind":"object","properties":[{"name":"content","type":"4cd48f3e5108bd6e","optional":false},{"name":"status","type":"fd0d91a1f9dd2efb","optional":false}],"indices":[]}, "aa0957eca50aeb28": {"kind":"object","properties":[{"name":"data","type":"f90eb4ab2d3897bc","optional":false},{"name":"ignorable","type":"dacf3d5f3e4ff619","optional":true},{"name":"seq","type":"0657d3ca5ac4e7bf","optional":false},{"name":"time","type":"0657d3ca5ac4e7bf","optional":false},{"name":"type","type":"0af3ecfb8c60d416","optional":false}],"indices":[]}, @@ -1125,8 +1133,10 @@ "b222069eea2d7680": {"kind":"object","properties":[{"name":"data","type":"477614bca595a9e7","optional":false},{"name":"ignorable","type":"dacf3d5f3e4ff619","optional":true},{"name":"seq","type":"0657d3ca5ac4e7bf","optional":false},{"name":"time","type":"0657d3ca5ac4e7bf","optional":false},{"name":"type","type":"9acbe713f3f64192","optional":false}],"indices":[]}, "b2245a906e13aa94": {"kind":"literal","value":"forked"}, "b2a5c8c008a31e3d": {"kind":"object","properties":[{"name":"form","type":"383cf73ae4748e5b","optional":false},{"name":"kind","type":"23af3759678a30de","optional":false}],"indices":[]}, + "b2a948a5340a9341": {"kind":"object","properties":[{"name":"inserted","type":"5d00abf938272f22","optional":false},{"name":"outcome","type":"d4e4d575abeb4b72","optional":true},{"name":"removedCount","type":"0657d3ca5ac4e7bf","optional":true},{"name":"start","type":"0657d3ca5ac4e7bf","optional":false},{"name":"target","type":"96f4a9c81fc0f940","optional":false}],"indices":[]}, "b2d2a01e51341a3b": {"kind":"union","types":["1ebc6f9ff3aa9e3a","234142ee4f996f38","94c56aeb1564ad73","e7e8f1318170b828"]}, "b3681c026a0b98e9": {"kind":"object","properties":[{"name":"form","type":"e8de0f457fed5b9f","optional":false},{"name":"kind","type":"78a9be340090b735","optional":false},{"name":"summary","type":"4cd48f3e5108bd6e","optional":false}],"indices":[]}, + "b36fd842cd004b53": {"kind":"object","properties":[{"name":"data","type":"b2a948a5340a9341","optional":false},{"name":"ignorable","type":"dacf3d5f3e4ff619","optional":true},{"name":"seq","type":"0657d3ca5ac4e7bf","optional":false},{"name":"time","type":"0657d3ca5ac4e7bf","optional":false},{"name":"type","type":"829a2fb3be6efd4b","optional":false}],"indices":[]}, "b38f206d59bad891": {"kind":"literal","value":"disposed"}, "b4ec0af39fd52ed9": {"kind":"object","properties":[{"name":"kind","type":"bb94f3217aa56c88","optional":false}],"indices":[]}, "b5086d249e8502e9": {"kind":"object","properties":[{"name":"data","type":"aa7bc2817014ca6c","optional":false},{"name":"ignorable","type":"dacf3d5f3e4ff619","optional":true},{"name":"seq","type":"0657d3ca5ac4e7bf","optional":false},{"name":"time","type":"0657d3ca5ac4e7bf","optional":false},{"name":"type","type":"6cda0d6d3b4336d8","optional":false}],"indices":[]}, @@ -1135,12 +1145,14 @@ "b56f6a885da3dc39": {"kind":"object","properties":[{"name":"messageSeqs","type":"5d03ba38734809bc","optional":false},{"name":"source","type":"30d5a537b2966857","optional":false},{"name":"title","type":"4cd48f3e5108bd6e","optional":false}],"indices":[]}, "b57a2889408de36e": {"kind":"literal","value":"failed"}, "b57ae1b37e52bee0": {"kind":"literal","value":"team/task"}, + "b613f83572fe2fbf": {"kind":"object","properties":[{"name":"form","type":"383cf73ae4748e5b","optional":false},{"name":"kind","type":"05205074b24d8340","optional":false},{"name":"through","type":"0657d3ca5ac4e7bf","optional":false}],"indices":[]}, "b63b089a00b3b935": {"kind":"object","properties":[{"name":"id","type":"4cd48f3e5108bd6e","optional":false},{"name":"kind","type":"17aae616ef020b8b","optional":false},{"name":"prompt","type":"4cd48f3e5108bd6e","optional":false},{"name":"scheduledAt","type":"4cd48f3e5108bd6e","optional":false},{"name":"title","type":"4cd48f3e5108bd6e","optional":true}],"indices":[]}, "b6896938660a1dfe": {"kind":"object","properties":[{"name":"childCreatedAt","type":"0657d3ca5ac4e7bf","optional":false},{"name":"childId","type":"4cd48f3e5108bd6e","optional":false},{"name":"label","type":"4cd48f3e5108bd6e","optional":true},{"name":"mode","type":"d28765f87d6b6ab4","optional":false},{"name":"version","type":"f88f1b7b6bb733f6","optional":false}],"indices":[]}, "b6ef9c6210be8350": {"kind":"object","properties":[{"name":"kind","type":"1ebc6f9ff3aa9e3a","optional":false}],"indices":[]}, "b6f94c17dd9bbd7c": {"kind":"object","properties":[{"name":"form","type":"fee1caf8ab6a65c1","optional":false},{"name":"kind","type":"bb94f3217aa56c88","optional":false}],"indices":[]}, "b70f3e81fe11b7a5": {"kind":"object","properties":[{"name":"form","type":"fee1caf8ab6a65c1","optional":false},{"name":"kind","type":"a9059a81430b3207","optional":false}],"indices":[]}, "b765ee31033cea46": {"kind":"literal","value":"set"}, + "b782058cef83caf3": {"kind":"object","properties":[{"name":"content","type":"b52f215a6cd69105","optional":false},{"name":"id","type":"4cd48f3e5108bd6e","optional":false},{"name":"role","type":"68f44f59e91c4139","optional":false},{"name":"source","type":"7a5b68bd1a5118c8","optional":false,"compatibility":{"version":1,"policy":"session-source-attribution","binding":"session.user-message.source","discriminator":"kind","unknownKinds":"preserve","attributionKinds":["action-cards","tmux-context","user-question-reply","working-directory"]}}],"indices":[]}, "b79641a9d25e7566": {"kind":"object","properties":[{"name":"kind","type":"8067b0d232c350f1","optional":false}],"indices":[]}, "b79ada42962cad01": {"kind":"object","properties":[{"name":"data","type":"86d1d0e9cfebb240","optional":false},{"name":"ignorable","type":"dacf3d5f3e4ff619","optional":true},{"name":"seq","type":"0657d3ca5ac4e7bf","optional":false},{"name":"time","type":"0657d3ca5ac4e7bf","optional":false},{"name":"type","type":"5ebce1317eacd2c1","optional":false}],"indices":[]}, "b815d7b91cdd9fc6": {"kind":"literal","value":"goal"}, @@ -1150,6 +1162,7 @@ "b9305f0ea75ccd6a": {"kind":"literal","value":"hook/invoked"}, "b978cff734e62143": {"kind":"object","properties":[{"name":"data","type":"79ddc907312f97cf","optional":false},{"name":"ignorable","type":"dacf3d5f3e4ff619","optional":true},{"name":"seq","type":"0657d3ca5ac4e7bf","optional":false},{"name":"time","type":"0657d3ca5ac4e7bf","optional":false},{"name":"type","type":"af0750d32d97d47b","optional":false}],"indices":[]}, "b993441f8ce7d27b": {"kind":"tuple","elements":[{"type":"9a2a9029f8d7ede0","optional":false,"rest":false}]}, + "ba2a1a8f194b1bb2": {"kind":"object","properties":[{"name":"form","type":"fee1caf8ab6a65c1","optional":false},{"name":"kind","type":"05205074b24d8340","optional":false},{"name":"through","type":"0657d3ca5ac4e7bf","optional":false}],"indices":[]}, "ba4c40b4381e0dcd": {"kind":"object","properties":[{"name":"form","type":"6f9e00383836275e","optional":false},{"name":"kind","type":"23af3759678a30de","optional":false},{"name":"sections","type":"c748fb42c80a1200","optional":false}],"indices":[]}, "ba77b58995e36f14": {"kind":"array","element":"07e1a58c58b593f5"}, "bab6865794007548": {"kind":"literal","value":"kill"}, @@ -1184,6 +1197,7 @@ "cbca49bde16b656d": {"kind":"literal","value":"block-start"}, "cd186c309e2bef30": {"kind":"literal","value":"approval/asked"}, "cda2345a9be9d139": {"kind":"object","properties":[{"name":"form","type":"e8de0f457fed5b9f","optional":false},{"name":"kind","type":"24b10f7cdae64928","optional":false},{"name":"summary","type":"4cd48f3e5108bd6e","optional":false}],"indices":[]}, + "cda79b839d8cc0c7": {"kind":"union","types":["09f3f8dc417f2782","4b259f7f8f0a44ad","7580e7d96dc8475f","b782058cef83caf3","fa87537f4a9c1e25"]}, "cdca80b8dad24d4e": {"kind":"literal","value":"tool-workflow/run-start"}, "cdee10e87dbed844": {"kind":"object","properties":[{"name":"delayMs","type":"0657d3ca5ac4e7bf","optional":false},{"name":"failure","type":"5f2117d1856a5ab4","optional":false},{"name":"maxRetries","type":"0657d3ca5ac4e7bf","optional":false},{"name":"mode","type":"3bc287e765d58dc3","optional":false},{"name":"policyKey","type":"4cd48f3e5108bd6e","optional":false},{"name":"provider","type":"4cd48f3e5108bd6e","optional":false},{"name":"retry","type":"0657d3ca5ac4e7bf","optional":false},{"name":"retryId","type":"4cd48f3e5108bd6e","optional":false},{"name":"step","type":"0657d3ca5ac4e7bf","optional":false},{"name":"turn","type":"0657d3ca5ac4e7bf","optional":false}],"indices":[]}, "cef171adf021a186": {"kind":"object","properties":[{"name":"arguments","type":"bbc5696e3f13c758","optional":false},{"name":"content","type":"b52f215a6cd69105","optional":false},{"name":"error","type":"a81059ca06999da1","optional":true},{"name":"isError","type":"aa93010d0e521ad7","optional":false},{"name":"meta","type":"c592ce75aab73fca","optional":true},{"name":"name","type":"4cd48f3e5108bd6e","optional":false},{"name":"parentCallId","type":"4cd48f3e5108bd6e","optional":false},{"name":"rootCallId","type":"4cd48f3e5108bd6e","optional":false},{"name":"subCallId","type":"4cd48f3e5108bd6e","optional":false}],"indices":[]}, @@ -1208,9 +1222,11 @@ "db874d463b0fdec7": {"kind":"object","properties":[{"name":"data","type":"3e4e7683b0192c9f","optional":false},{"name":"ignorable","type":"dacf3d5f3e4ff619","optional":true},{"name":"seq","type":"0657d3ca5ac4e7bf","optional":false},{"name":"time","type":"0657d3ca5ac4e7bf","optional":false},{"name":"type","type":"e2d0367e5716bc18","optional":false}],"indices":[]}, "dba54c09c6e7aafc": {"kind":"object","properties":[{"name":"kind","type":"4edec1b770e8ec4c","optional":false}],"indices":[]}, "dbdafceb9a900183": {"kind":"literal","value":"hook"}, + "dd00cdccd0ba487b": {"kind":"object","properties":[{"name":"form","type":"aec150797b655c5f","optional":false},{"name":"kind","type":"05205074b24d8340","optional":false},{"name":"through","type":"0657d3ca5ac4e7bf","optional":false}],"indices":[]}, "dd2780e7e0768433": {"kind":"literal","value":"fresh"}, "dd5ee8762bd8a14b": {"kind":"object","properties":[{"name":"form","type":"aec150797b655c5f","optional":false},{"name":"kind","type":"99e7e7d7fed55ae6","optional":false}],"indices":[]}, "dd6cc2bb2a8f2824": {"kind":"object","properties":[{"name":"form","type":"383cf73ae4748e5b","optional":false},{"name":"kind","type":"99e7e7d7fed55ae6","optional":false}],"indices":[]}, + "dde0cdf9d426768b": {"kind":"object","properties":[{"name":"kind","type":"05205074b24d8340","optional":false},{"name":"through","type":"0657d3ca5ac4e7bf","optional":false}],"indices":[]}, "de25352d2dc41963": {"kind":"literal","value":"user/message"}, "de30b1fa12c75f81": {"kind":"literal","value":"image/gif"}, "deaae5cd30987d4f": {"kind":"object","properties":[{"name":"form","type":"16d0507cb94b4cfc","optional":false},{"name":"kind","type":"0fe52387f6505270","optional":false}],"indices":[]}, @@ -1240,6 +1256,7 @@ "e7ce49f12953583f": {"kind":"literal","value":"initial"}, "e7e154d47d5fbc53": {"kind":"literal","value":"clear"}, "e7e8f1318170b828": {"kind":"literal","value":"deleted"}, + "e7f4cbb3a57f4cbd": {"kind":"object","properties":[{"name":"form","type":"e8de0f457fed5b9f","optional":false},{"name":"kind","type":"05205074b24d8340","optional":false},{"name":"summary","type":"4cd48f3e5108bd6e","optional":false},{"name":"through","type":"0657d3ca5ac4e7bf","optional":false}],"indices":[]}, "e8de0f457fed5b9f": {"kind":"literal","value":"notice"}, "e95f957154b2b1b5": {"kind":"literal","value":"assistant/attempt"}, "ea08ec1df0c33108": {"kind":"object","properties":[{"name":"message","type":"2eafefdc6f79a492","optional":false},{"name":"teamId","type":"4cd48f3e5108bd6e","optional":false},{"name":"version","type":"a0f906856fc06f1c","optional":false}],"indices":[]}, @@ -1255,6 +1272,7 @@ "ef11a3b7fa2033ae": {"kind":"object","properties":[{"name":"step","type":"0657d3ca5ac4e7bf","optional":false},{"name":"turn","type":"0657d3ca5ac4e7bf","optional":false}],"indices":[]}, "ef226a90ddac6cd8": {"kind":"literal","value":"killed"}, "ef4f00923b9edbae": {"kind":"literal","value":"compaction/summary"}, + "efa1a091017ce533": {"kind":"object","properties":[{"name":"data","type":"a3e299483348f27a","optional":false},{"name":"ignorable","type":"dacf3d5f3e4ff619","optional":true},{"name":"seq","type":"0657d3ca5ac4e7bf","optional":false},{"name":"time","type":"0657d3ca5ac4e7bf","optional":false},{"name":"type","type":"587bb11da730e06a","optional":false}],"indices":[]}, "f05fb85e715af4ab": {"kind":"literal","value":"task-result"}, "f09e5e7acf5ff5cc": {"kind":"object","properties":[{"name":"model","type":"4cd48f3e5108bd6e","optional":false},{"name":"provider","type":"4cd48f3e5108bd6e","optional":false},{"name":"reasoningEffort","type":"4cd48f3e5108bd6e","optional":true}],"indices":[]}, "f1687f982fa3d41e": {"kind":"union","types":["023a355b2be0d688","0a30c3be336cdd4f","56b7c047694875a8","79650cfa10aa3c52","ab7ec5f9eb2c6dfa","b6ef9c6210be8350","ec8b5ff7c77ca689"]}, @@ -1266,7 +1284,6 @@ "f2da0f27c1e6add5": {"kind":"object","properties":[{"name":"attachment","type":"f19bee84bf3d2090","optional":false},{"name":"type","type":"277b61f9c6f81704","optional":false}],"indices":[]}, "f311671e07bc7fa6": {"kind":"literal","value":"in-history"}, "f4a66f654996c093": {"kind":"object","properties":[{"name":"kind","type":"0fe52387f6505270","optional":false}],"indices":[]}, - "f4ed4717b4c259e9": {"kind":"object","properties":[{"name":"headerSeq","type":"0657d3ca5ac4e7bf","optional":true},{"name":"message","type":"0f6265188dae7ba6","optional":false},{"name":"step","type":"0657d3ca5ac4e7bf","optional":false},{"name":"turn","type":"0657d3ca5ac4e7bf","optional":false}],"indices":[]}, "f5520c1d261ca86e": {"kind":"object","properties":[{"name":"data","type":"e3056e0dda3a379e","optional":false},{"name":"ignorable","type":"dacf3d5f3e4ff619","optional":true},{"name":"seq","type":"0657d3ca5ac4e7bf","optional":false},{"name":"time","type":"0657d3ca5ac4e7bf","optional":false},{"name":"type","type":"24a8188c4d61b70a","optional":false}],"indices":[]}, "f56c5b59ca016131": {"kind":"object","properties":[{"name":"form","type":"383cf73ae4748e5b","optional":false},{"name":"kind","type":"7f13860ee89a2d35","optional":false},{"name":"references","type":"df843efd87c78cf8","optional":false},{"name":"version","type":"f88f1b7b6bb733f6","optional":false}],"indices":[]}, "f6329fb30c7ea801": {"kind":"object","properties":[{"name":"form","type":"383cf73ae4748e5b","optional":false},{"name":"kind","type":"0fe52387f6505270","optional":false}],"indices":[]}, @@ -1282,7 +1299,6 @@ "f90eb4ab2d3897bc": {"kind":"object","properties":[{"name":"turn","type":"0657d3ca5ac4e7bf","optional":false}],"indices":[]}, "f97f47b0eb87905e": {"kind":"object","properties":[{"name":"form","type":"16d0507cb94b4cfc","optional":false},{"name":"kind","type":"78a9be340090b735","optional":false}],"indices":[]}, "f9cfc89e8fe085b4": {"kind":"literal","value":"text-chunks"}, - "fa106c5b33b657e8": {"kind":"object","properties":[{"name":"inserted","type":"454a0fa879e38076","optional":false},{"name":"outcome","type":"d4e4d575abeb4b72","optional":true},{"name":"removedCount","type":"0657d3ca5ac4e7bf","optional":true},{"name":"start","type":"0657d3ca5ac4e7bf","optional":false},{"name":"target","type":"96f4a9c81fc0f940","optional":false}],"indices":[]}, "fa87537f4a9c1e25": {"kind":"object","properties":[{"name":"content","type":"b52f215a6cd69105","optional":false},{"name":"id","type":"4cd48f3e5108bd6e","optional":false},{"name":"role","type":"95c0e88fcf0bafb2","optional":false},{"name":"source","type":"719c51f67074f970","optional":false}],"indices":[]}, "fab2bb09bc5e3b9d": {"kind":"union","types":["12a0cfa619ac4a5f","18fce9540bb20897","a3edd1efd1fef21b"]}, "fab35c4520883c0d": {"kind":"literal","value":"rejected"}, diff --git a/docs/subsystems/boot.md b/docs/subsystems/boot.md index ed9b5553c7..0cef9a67e4 100644 --- a/docs/subsystems/boot.md +++ b/docs/subsystems/boot.md @@ -69,6 +69,53 @@ interface PendingModule { Generated from source by `scripts/gen-cordis-catalog.ts` (verified fresh by `pnpm run verify-cordis-catalog` in doc-sync; regenerate with `pnpm run gen-cordis-catalog`) — the language sides differ only in locale-specific paired document paths. Signature blocks use a `ts cordis-catalog` fence and keep the original source JSDoc; dispatch modes are defined in the [primer](../cordis-primer.md#dispatch-modes), and the framework-inherited `ctx` API lives in [cordis-api/inherited.md](../cordis-api/inherited.md). + + +### `ctx.actionCards` — `ActionCards` + +Card tool, kind registry, Session projection, user actions and status notices. + +```ts cordis-catalog +/** + * Register one action kind for the caller's lifetime. + * @param kind - the kind's skill, details schema, verbs and handler. + * @returns disposer removing the kind; actions on its cards are then unavailable. + * @throws when another kind already registered the same action. + */ +register
(kind: ActionCardKind
): () => void + +/** + * Record a card decision in the Session log; the next model request reports it. + * @param agent - agent whose Session owns the card. + * @param decision - the card's new state. + * @throws when no kind ever registered the decision's action with this service, or its phase is one the projection derives. + */ +record(agent: Agent, decision: ActionCardDecision): void + +/** + * Read the cards of one Session. + * @param session - a Session whose projections are prepared. + * @returns proposals and decisions by call id. + */ +view(session: Session): ActionCardView + +/** + * Apply a user action to a logged card. Shared checks run first, then the kind reconciles the card; `dismiss` is + * recorded here and every other verb goes to the card's kind. `dismiss` also works on an unstarted card whose kind + * is no longer registered. Concurrent actions on one card share one operation. + * @param agent - live root resolved from the displayed Session. + * @param callId - the card's `propose_action` call id. + * @param verb - `dismiss` or one of the kind's verbs. + * @param payload - kind-defined fields such as a chosen location. + * @returns acceptance of background work, the decision in effect, or a refusal. + */ +@Remote act(agent: Agent, callId: ToolCallId, verb: string, payload?: Record): Promise +``` + +Types: [Agent](core.md) · [Session](session.md) · [ToolCallId](core.md) + +Source: [`packages/experimental/action-cards/src/index.ts`](../../packages/experimental/action-cards/src/index.ts) + ### `ctx.configEditor` — `ConfigEditor` @@ -355,6 +402,26 @@ async entry(url: string, name: string): Promise Source: [`packages/boot/plugin-market/src/index.ts`](../../packages/boot/plugin-market/src/index.ts) + + +### `ctx.pluginRecommendations` — `PluginRecommendations` + +Catalog lookups for card text and the enabling behind the `enable_official_plugin` card. + +```ts cordis-catalog +/** + * Read the current catalog text for a logged card. + * @param agent - Exact live root resolved by the Gateway from the Session id. + * @param callId - The card's `propose_action` call id, never a package supplied by the browser. + * @returns the plugin's title, description and selection, or a refusal when the card names no available official plugin. + */ +@Remote async describe(agent: Agent, callId: ToolCallId): Promise +``` + +Types: [Agent](core.md) · [ToolCallId](core.md) + +Source: [`packages/experimental/plugin-recommendations/src/index.ts`](../../packages/experimental/plugin-recommendations/src/index.ts) + ### `ctx.pluginRegistryProbe` — `PluginRegistryProbe` diff --git a/docs/subsystems/boot.zh.md b/docs/subsystems/boot.zh.md index ee14d22335..fe8014ab82 100644 --- a/docs/subsystems/boot.zh.md +++ b/docs/subsystems/boot.zh.md @@ -69,6 +69,53 @@ interface PendingModule { Generated from source by `scripts/gen-cordis-catalog.ts` (verified fresh by `pnpm run verify-cordis-catalog` in doc-sync; regenerate with `pnpm run gen-cordis-catalog`) — the language sides differ only in locale-specific paired document paths. Signature blocks use a `ts cordis-catalog` fence and keep the original source JSDoc; dispatch modes are defined in the [primer](../cordis-primer.zh.md#dispatch-modes), and the framework-inherited `ctx` API lives in [cordis-api/inherited.md](../cordis-api/inherited.md). + + +### `ctx.actionCards` — `ActionCards` + +Card tool, kind registry, Session projection, user actions and status notices. + +```ts cordis-catalog +/** + * Register one action kind for the caller's lifetime. + * @param kind - the kind's skill, details schema, verbs and handler. + * @returns disposer removing the kind; actions on its cards are then unavailable. + * @throws when another kind already registered the same action. + */ +register
(kind: ActionCardKind
): () => void + +/** + * Record a card decision in the Session log; the next model request reports it. + * @param agent - agent whose Session owns the card. + * @param decision - the card's new state. + * @throws when no kind ever registered the decision's action with this service, or its phase is one the projection derives. + */ +record(agent: Agent, decision: ActionCardDecision): void + +/** + * Read the cards of one Session. + * @param session - a Session whose projections are prepared. + * @returns proposals and decisions by call id. + */ +view(session: Session): ActionCardView + +/** + * Apply a user action to a logged card. Shared checks run first, then the kind reconciles the card; `dismiss` is + * recorded here and every other verb goes to the card's kind. `dismiss` also works on an unstarted card whose kind + * is no longer registered. Concurrent actions on one card share one operation. + * @param agent - live root resolved from the displayed Session. + * @param callId - the card's `propose_action` call id. + * @param verb - `dismiss` or one of the kind's verbs. + * @param payload - kind-defined fields such as a chosen location. + * @returns acceptance of background work, the decision in effect, or a refusal. + */ +@Remote act(agent: Agent, callId: ToolCallId, verb: string, payload?: Record): Promise +``` + +Types: [Agent](core.zh.md) · [Session](session.zh.md) · [ToolCallId](core.zh.md) + +Source: [`packages/experimental/action-cards/src/index.ts`](../../packages/experimental/action-cards/src/index.ts) + ### `ctx.configEditor` — `ConfigEditor` @@ -355,6 +402,26 @@ async entry(url: string, name: string): Promise Source: [`packages/boot/plugin-market/src/index.ts`](../../packages/boot/plugin-market/src/index.ts) + + +### `ctx.pluginRecommendations` — `PluginRecommendations` + +Catalog lookups for card text and the enabling behind the `enable_official_plugin` card. + +```ts cordis-catalog +/** + * Read the current catalog text for a logged card. + * @param agent - Exact live root resolved by the Gateway from the Session id. + * @param callId - The card's `propose_action` call id, never a package supplied by the browser. + * @returns the plugin's title, description and selection, or a refusal when the card names no available official plugin. + */ +@Remote async describe(agent: Agent, callId: ToolCallId): Promise +``` + +Types: [Agent](core.zh.md) · [ToolCallId](core.zh.md) + +Source: [`packages/experimental/plugin-recommendations/src/index.ts`](../../packages/experimental/plugin-recommendations/src/index.ts) + ### `ctx.pluginRegistryProbe` — `PluginRegistryProbe` diff --git a/docs/tool-catalog.i18n.yaml b/docs/tool-catalog.i18n.yaml index 8265143029..fbb5e2a7a5 100644 --- a/docs/tool-catalog.i18n.yaml +++ b/docs/tool-catalog.i18n.yaml @@ -9,8 +9,8 @@ en: 6cc2cd9ced70bb09 zh: 1c0e6cb4ba2ed637 /tool-schema-catalog/tool-package-map: - en: 10fe940a3d17cde7 - zh: ef1e8863c28e3c05 + en: c089eeb9ebe832b1 + zh: 97f6ac552abb55e3 /tool-schema-catalog/deepseek-ai-dsh-plugin-manager: en: e0143916bb634762 zh: e0143916bb634762 @@ -54,8 +54,14 @@ en: 4910ab327694ee97 zh: 17e79e4366cb636d /tool-schema-catalog/deepseek-ai-dsh-experimental-browser-use-stagehand-native/stagehand-tabs: - en: 095b18f9376e073d - zh: abd8785f8acaa555 + en: a0d5b9b891358120 + zh: 25793a62709770b5 +/tool-schema-catalog/deepseek-ai-dsh-action-cards: + en: 60c9df6ffda98a1d + zh: 60c9df6ffda98a1d +/tool-schema-catalog/deepseek-ai-dsh-action-cards/propose-action: + en: c289411d447b015a + zh: 3a770388e90df4df /tool-schema-catalog/deepseek-ai-dsh-tool-ask-user: en: e713a4a11a15d13c zh: e713a4a11a15d13c diff --git a/docs/tool-catalog.md b/docs/tool-catalog.md index 24101e126f..7a4da8d81a 100644 --- a/docs/tool-catalog.md +++ b/docs/tool-catalog.md @@ -19,6 +19,7 @@ This table connects model-visible tool names to the plugin package and service s | `@deepseek-ai/dsh-plugin-market` | `plugin_market` | `ctx.tools`, `ctx.pluginMarket` | `tool/call`, `tool/result` | - | - | | `@deepseek-ai/dsh-mcp-resources` | `list_mcp_resource_templates`, `list_mcp_resources`, `read_mcp_resource` | `ctx.tools`, `ctx.mcpResources` | `tool/call`, `tool/result` | - | - | | `@deepseek-ai/dsh-experimental-browser-use-stagehand-native` | `stagehand_act`, `stagehand_extract`, `stagehand_navigate`, `stagehand_observe`, `stagehand_screenshot`, `stagehand_tabs` | `ctx.browserUse`, `ctx.agents`, `ctx.tools`, `ctx.systemPrompt` | `tool/call`, `tool/result` | - | - | +| `@deepseek-ai/dsh-action-cards` | `propose_action` | `ctx.tools`, `ctx.agents`, `ctx.sessionProjections`, `ctx.remoteAccess` | `tool/call`, `tool/result`, `plugin:action-cards/decision`, `user/message` | - | Declared only to an Agent that loaded the skill of a registered card kind; the kind packages register their actions, and the Personalized pages and Plugin recommendations bundles supply the shipped kinds. | | `@deepseek-ai/dsh-tool-ask-user` | `ask_user_question` | `ctx.tools`, `ctx.userQuestions` | `tool/call`, `tool/result after an answer or timeout`, `late user/message` | - | ask_user_question keeps the original blocking behavior by default; set `mode: timed` to opt into a foreground timeout and pending result while the question remains answerable. In timed mode, `timeout: -1` keeps that call blocking indefinitely. | | `@deepseek-ai/dsh-tool-executor-ptc` | `run_code` | `ctx.workingDirectory (Agent executions)`, `ctx.tools`, `ctx.ptcRuntime`, `ctx.systemPrompt` | `tool/call`, `one tool/ptc-dispatch-start + tool/ptc-dispatch pair per bridged sub-call`, `tool/result` | - | The PTC executor exposes only run_code to the model and declares other visible capabilities in a generated SDK in the runtime’s language. Program bindings enter the shared guarded tool pipeline in submission order; concurrency-safe bodies overlap up to maxParallelSubCalls, and nested results remain linked to the outer program. | | `@deepseek-ai/dsh-plan-mode` | `exit_plan_mode` | `ctx.tools`, `ctx.systemPrompt`, `ctx.agents`, `ctx.userQuestions (execution time, opportunistic)` | `tool/call`, `plan/mode inactive on an approved review`, `tool/result` | - | exit_plan_mode stays in the model-facing schema while planning is inactive so transitions add no tool-catalog churn on top of the plan-policy change. Its execute path rejects calls outside plan mode; in plan mode it presents the plan over the user-questions seam (approve / keep planning with feedback), and approval logs plan mode inactive at the step boundary. | @@ -497,6 +498,49 @@ List, create, select, or close a Stagehand browser tab. Source: [`packages/experimental/browser-use-stagehand-native/src/index.ts`](../packages/experimental/browser-use-stagehand-native/src/index.ts) + + +## `@deepseek-ai/dsh-action-cards` + +### `propose_action` + +Show the user one optional action card under this answer. Call only as the skill that names the action instructs. + +```json +{ + "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" + ] +} +``` + +Source: [`packages/experimental/action-cards/src/index.ts`](../packages/experimental/action-cards/src/index.ts) + +Declared only to an Agent that loaded the skill of a registered card kind; the kind packages register their actions, and the Personalized pages and Plugin recommendations bundles supply the shipped kinds. + ## `@deepseek-ai/dsh-tool-ask-user` diff --git a/docs/tool-catalog.zh.md b/docs/tool-catalog.zh.md index 27670f9964..50948844ae 100644 --- a/docs/tool-catalog.zh.md +++ b/docs/tool-catalog.zh.md @@ -23,6 +23,7 @@ | `@deepseek-ai/dsh-plugin-market` | `plugin_market` | `ctx.tools`, `ctx.pluginMarket` | `tool/call`, `tool/result` | - | - | | `@deepseek-ai/dsh-mcp-resources` | `list_mcp_resource_templates`, `list_mcp_resources`, `read_mcp_resource` | `ctx.tools`, `ctx.mcpResources` | `tool/call`, `tool/result` | - | - | | `@deepseek-ai/dsh-experimental-browser-use-stagehand-native` | `stagehand_act`、`stagehand_extract`、`stagehand_navigate`、`stagehand_observe`、`stagehand_screenshot`、`stagehand_tabs` | `ctx.browserUse`、`ctx.agents`、`ctx.tools`、`ctx.systemPrompt` | `tool/call`、`tool/result` | - | - | +| `@deepseek-ai/dsh-action-cards` | `propose_action` | `ctx.tools`、`ctx.agents`、`ctx.sessionProjections`、`ctx.remoteAccess` | `tool/call`、`tool/result`、`plugin:action-cards/decision`、`user/message` | - | 只向加载过已注册卡片种类 Skill 的 Agent 声明;各种类的包注册自己的动作,个性化页面和插件推荐两个 Bundle 提供随产品发布的种类。 | | `@deepseek-ai/dsh-tool-ask-user` | `ask_user_question` | `ctx.tools`、`ctx.userQuestions` | `tool/call`、`tool/result after an answer or timeout`、`late user/message` | - | ask_user_question 默认保持原有阻塞行为;设置 `mode: timed` 后才启用前台超时与 pending 结果,同时问题仍可回答;timed 模式内 `timeout: -1` 让本次调用无限期阻塞。 | | `@deepseek-ai/dsh-tool-executor-ptc` | `run_code` | `ctx.workingDirectory (Agent executions)`、`ctx.tools`、`ctx.ptcRuntime`、`ctx.systemPrompt` | `tool/call`、`one tool/ptc-dispatch-start + tool/ptc-dispatch pair per bridged sub-call`、`tool/result` | - | PTC 执行器只向模型公开 run_code,并在运行时语言生成的 SDK 中声明其他可见能力。程序绑定按提交顺序进入共享的受守卫工具流水线;并行安全的函数体最多重叠 maxParallelSubCalls 个,嵌套结果保持与外层程序关联。 | | `@deepseek-ai/dsh-plan-mode` | `exit_plan_mode` | `ctx.tools`、`ctx.systemPrompt`、`ctx.agents`、`ctx.userQuestions (execution time, opportunistic)` | `tool/call`、`plan/mode inactive on an approved review`、`tool/result` | - | 规划未激活时,exit_plan_mode 仍保留在面向模型的 schema 中,这样状态转换不会在规划策略变更之外额外造成工具目录变动。其执行路径会拒绝规划模式之外的调用;在规划模式下,它通过用户交互 seam 提交计划(批准/根据反馈继续规划),批准后会在步骤边界记录规划模式已停用。 | @@ -501,6 +502,49 @@ 来源:[`packages/experimental/browser-use-stagehand-native/src/index.ts`](../packages/experimental/browser-use-stagehand-native/src/index.ts) + + +## `@deepseek-ai/dsh-action-cards` + +### `propose_action` + +在本次回答下方向用户展示一张可选操作卡片。只按指定该操作的 Skill 的要求调用。 + +```json +{ + "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" + ] +} +``` + +来源:[`packages/experimental/action-cards/src/index.ts`](../packages/experimental/action-cards/src/index.ts) + +只向加载过已注册卡片种类 Skill 的 Agent 声明;各种类的包注册自己的动作,个性化页面和插件推荐两个 Bundle 提供随产品发布的种类。 + ## `@deepseek-ai/dsh-tool-ask-user` diff --git a/package.json b/package.json index 5bc1973359..f3eb2e4a6d 100644 --- a/package.json +++ b/package.json @@ -300,6 +300,7 @@ "@deepseek-ai/dsh-cua-bundle": "workspace:*", "@deepseek-ai/dsh-experimental-subagent-oar": "workspace:*", "@deepseek-ai/dsh-experimental-tool-subagent-oar": "workspace:*", - "@deepseek-ai/dsh-experimental-cua-macos": "workspace:*" + "@deepseek-ai/dsh-experimental-cua-macos": "workspace:*", + "@deepseek-ai/dsh-action-cards": "workspace:*" } } diff --git a/packages/client/ui-plugin-manager/tests/components.client.spec.tsx b/packages/client/ui-plugin-manager/tests/components.client.spec.tsx index b2b196bfe1..c92cb76e92 100644 --- a/packages/client/ui-plugin-manager/tests/components.client.spec.tsx +++ b/packages/client/ui-plugin-manager/tests/components.client.spec.tsx @@ -1237,8 +1237,11 @@ describe('PluginManagerPage', () => { expect(document.querySelector(`[data-plugin-package="${name}"]`)).toBeTruthy() }) - it('returns from package details to the secondary page and retains it across React remounts', () => { - const name = '@deepseek-ai/dsh-experimental-session-titles-bundle' + it.each([ + '@deepseek-ai/dsh-experimental-session-titles-bundle', + '@deepseek-ai/dsh-creation-recommendations', + '@deepseek-ai/dsh-plugin-recommendations', + ])('returns %s details to the secondary page and retains it across React remounts', (name) => { const b = renderTab({ packages: [pkg({ name, official: true, enabled: false })] }) fireEvent.click(screen.getByRole('button', { name: en.moreAction })) expect(screen.getByRole('heading', { name: en.moreTitle, level: 1 })).toBeTruthy() @@ -1253,9 +1256,12 @@ describe('PluginManagerPage', () => { expect(document.querySelector(`[data-plugin-package="${name}"]`)).toBeNull() }) - it('pins a bundle the profile picks onto the main list, in its full-list place', () => { + it.each([ + '@deepseek-ai/dsh-experimental-tool-worktree', + '@deepseek-ai/dsh-creation-recommendations', + '@deepseek-ai/dsh-plugin-recommendations', + ])('pins enabled %s onto the main list and removes it when disabled', (first) => { const team = '@deepseek-ai/dsh-experimental-agent-team-profile' - const first = '@deepseek-ai/dsh-experimental-tool-worktree' const second = '@deepseek-ai/dsh-experimental-cot-translation-bundle' const entry = (name: string, enabled: boolean): PackageView => pkg({ name, official: true, enabled }) const b = renderTab({ packages: [entry(second, true), entry(team, true), entry(first, false)] }) diff --git a/packages/core/tools/tests/gen-tool-catalog.spec.ts b/packages/core/tools/tests/gen-tool-catalog.spec.ts index d571a19a80..4948fdb66f 100644 --- a/packages/core/tools/tests/gen-tool-catalog.spec.ts +++ b/packages/core/tools/tests/gen-tool-catalog.spec.ts @@ -31,7 +31,7 @@ describe('gen-tool-catalog collectToolCatalog', () => { 'create_goal', 'create_worktree', 'edit', 'exit_plan_mode', 'get_goal', 'glob', 'grep', 'interrupt_agent', 'interrupt_agent', 'job_kill', 'job_list', 'job_output', 'list_agents', 'list_agents', 'list_mcp_resource_templates', 'list_mcp_resources', - 'list_subagent_models', 'load_workspace_dependencies', 'lsp', 'plugin_manager', 'plugin_market', 'present', 'pwsh', 'pwsh', 'ralph', + 'list_subagent_models', 'load_workspace_dependencies', 'lsp', 'plugin_manager', 'plugin_market', 'present', 'propose_action', 'pwsh', 'pwsh', 'ralph', 'read', 'read_image', 'read_mcp_resource', 'run_code', 'schedule_create', 'schedule_delete', 'schedule_list', 'schedule_update', 'send_message', 'send_message', 'session_event_read', 'session_event_search', 'session_event_trace', 'session_search', 'session_trace', 'skill', 'spawn_teammate', diff --git a/packages/experimental/README.i18n.yaml b/packages/experimental/README.i18n.yaml index 7345f148c8..78807da546 100644 --- a/packages/experimental/README.i18n.yaml +++ b/packages/experimental/README.i18n.yaml @@ -18,8 +18,8 @@ en: b55aeb927737b90d zh: 0b215fcb5c2c7908 /packages-experimental/packages: - en: 9fd1c2ac5ede5d47 - zh: 9e8fc6c374b6f5e9 + en: 4d32c0a6c009a2ca + zh: 47bf9acd64100263 /packages-experimental/related-documentation: en: 1cb9d3448d44e48d zh: cd326d70a746b896 diff --git a/packages/experimental/README.md b/packages/experimental/README.md index 58d3351b40..3aedf87a9f 100644 --- a/packages/experimental/README.md +++ b/packages/experimental/README.md @@ -36,6 +36,12 @@ Promotion requires a named product role, documented public behavior and limitati | Package | Role | ctx key | |---|---|---| +| [`action-cards`](action-cards/README.md) | Optional action cards below answers: the proposal tool, card state, user actions and status notices | `ctx.actionCards` | +| [`client-ui-action-cards`](client-ui-action-cards/README.md) | Card frame below completed answers, kind bodies and the proposal process row | — | +| [`creation-recommendations`](creation-recommendations/README.md) | Explicit-need creation of persistent removable interface plugins | — | +| [`client-ui-creation-recommendations`](client-ui-creation-recommendations/README.md) | Personalized page card body with location selection and undo | — | +| [`plugin-recommendations`](plugin-recommendations/README.md) | Explicit-need official plugin recommendations in conversations | `ctx.pluginRecommendations` | +| [`client-ui-plugin-recommendations`](client-ui-plugin-recommendations/README.md) | Official plugin card body with catalog text and enabling | — | | [`tool-session-query`](tool-session-query/README.md) | Workspace-authorized model session search, trace, and event reads | — | | [`session-search`](session-search/README.md) | Optional experimental global session search tools | — | | [`ralph-bundle`](ralph-bundle/README.md) | Optional repeated delegation with an isolated workflow engine | — | diff --git a/packages/experimental/README.zh.md b/packages/experimental/README.zh.md index 3cd0f05350..7a7bbe01a9 100644 --- a/packages/experimental/README.zh.md +++ b/packages/experimental/README.zh.md @@ -36,6 +36,12 @@ kind: "package-group" | 包 | 职责 | ctx 键 | |---|---|---| +| [`action-cards`](action-cards/README.zh.md) | 回答下方的可选操作卡片:提议工具、卡片状态、用户操作与状态通知 | `ctx.actionCards` | +| [`client-ui-action-cards`](client-ui-action-cards/README.zh.md) | 已完成回答下方的卡片框架、种类正文与提议过程行 | — | +| [`creation-recommendations`](creation-recommendations/README.zh.md) | 针对明确需求创建持续可用、可移除的界面插件 | — | +| [`client-ui-creation-recommendations`](client-ui-creation-recommendations/README.zh.md) | 个性化页面卡片正文、位置选择和撤销 | — | +| [`plugin-recommendations`](plugin-recommendations/README.zh.md) | 在对话中针对明确需求推荐官方插件 | `ctx.pluginRecommendations` | +| [`client-ui-plugin-recommendations`](client-ui-plugin-recommendations/README.zh.md) | 官方插件卡片正文:目录文本与启用 | — | | [`tool-session-query`](tool-session-query/README.zh.md) | 按工作区授权的模型会话搜索、追踪与事件读取 | — | | [`session-search`](session-search/README.zh.md) | 可选实验性全局会话搜索工具 | — | | [`ralph-bundle`](ralph-bundle/README.zh.md) | 带隔离 workflow 引擎的可选重复委派 | — | diff --git a/packages/experimental/action-cards/README.i18n.yaml b/packages/experimental/action-cards/README.i18n.yaml new file mode 100644 index 0000000000..32c336d386 --- /dev/null +++ b/packages/experimental/action-cards/README.i18n.yaml @@ -0,0 +1,67 @@ +# Bilingual-pair consistency record for README.md (docs/i18n/README.md): per heading +# section, a hash of its English and Chinese blocks outside code blocks and generated regions. +# After editing either side, bring the other along and re-record with: +# pnpm run verify-translation-pairing --write packages/experimental/action-cards/README.md +/: + en: 1a68a7a733a2676d + zh: 01f72eb61a0626b7 +/deepseek-ai-dsh-action-cards: + en: 63bf9077e164f6fe + zh: 8b696bb8c3d6fcd4 +/deepseek-ai-dsh-action-cards/summary: + en: 7621088773d073f4 + zh: 57674e6dcee15997 +/deepseek-ai-dsh-action-cards/table-of-contents: + en: d152484eb41ac6b4 + zh: b00dddf4c88f0a45 +/deepseek-ai-dsh-action-cards/use-this-package: + en: 4e3a256d5aa6d29b + zh: b89ffa23ebb49e7e +/deepseek-ai-dsh-action-cards/use-this-package/register-a-card-kind: + en: 8432c02ef18eda11 + zh: 6d43a0a32edd7e30 +/deepseek-ai-dsh-action-cards/use-this-package/card-rules: + en: 2f5b3ccb91f3b30e + zh: 9bd877d9d92615da +/deepseek-ai-dsh-action-cards/understand-the-implementation: + en: d92243d71d2106c1 + zh: 47f0840fa723bb31 +/deepseek-ai-dsh-action-cards/further-exploration: + en: 1966ab8d7063c450 + zh: 033c2c0d258a257f +/deepseek-ai-dsh-action-cards/model-experience: + en: 215e7ba838619b7b + zh: a311da3843709f90 +/deepseek-ai-dsh-action-cards/model-experience/card-tool: + en: 4925bc5f7153825a + zh: 3a7bfb2210d9c6aa +/deepseek-ai-dsh-action-cards/model-experience/card-tool/what-the-model-sees: + en: 58521d3992ce42ec + zh: 45681f7b43e67613 +/deepseek-ai-dsh-action-cards/model-experience/card-tool/token-effect: + en: 7efaaad09be98a07 + zh: 86c7fa13d60c3588 +/deepseek-ai-dsh-action-cards/model-experience/card-tool/kv-cache-effect: + en: d4192e35d47b5fa3 + zh: c786828e123de657 +/deepseek-ai-dsh-action-cards/model-experience/card-status-notice: + en: 407ebca5eb94992e + zh: 779819ea83974103 +/deepseek-ai-dsh-action-cards/model-experience/card-status-notice/what-the-model-sees: + en: a14013ca3ddb5ee3 + zh: 18cd7ffc62fd6590 +/deepseek-ai-dsh-action-cards/model-experience/card-status-notice/what-the-model-sees/notice-preamble: + en: 04dd9469bfaff052 + zh: 35110b231b8e3928 +/deepseek-ai-dsh-action-cards/model-experience/card-status-notice/token-effect: + en: 4e73a2cff60b5e88 + zh: 4887212815e7425d +/deepseek-ai-dsh-action-cards/model-experience/card-status-notice/kv-cache-effect: + en: 58785543e2428dba + zh: 4b082f77b3b2f482 +/deepseek-ai-dsh-action-cards/known-limitations-and-deferred-work: + en: 163aa0c2a6b7e3f8 + zh: 26cdcae33579114a +/deepseek-ai-dsh-action-cards/known-limitations-and-deferred-work/dev-note: + en: e67f633dc6fe7773 + zh: bd8a8936229e035e diff --git a/packages/experimental/action-cards/README.md b/packages/experimental/action-cards/README.md new file mode 100644 index 0000000000..3dc8610b57 --- /dev/null +++ b/packages/experimental/action-cards/README.md @@ -0,0 +1,138 @@ +--- +description: "Shared Host service of optional action cards: the propose_action tool, card state, user actions and status notices that experiment packages register card kinds into." +kind: "package-reference" +--- + +# @deepseek-ai/dsh-action-cards + +English | [中文](README.zh.md) + +## Summary + +An experiment can offer one optional action below an answer, such as creating a page, without adding a tool of its own. The model loads the experiment's skill, then calls `propose_action`; the card appears after the turn ends, and nothing runs until the user acts on it. Each choice is recorded and reported to the model on its next request. The tool costs nothing until a card skill loads in the conversation. + +## Table of Contents + +- [Use this package](#use-this-package) +- [Understand the implementation](#understand-the-implementation) +- [Further Exploration](#further-exploration) +- [Model Experience](#model-experience) +- [Known Limitations and Deferred Work](#known-limitations-and-deferred-work) +- [Dev Note](#dev-note) + +----- + + +## Use this package + +The [Personalized pages](../creation-recommendations/README.md) and [Plugin recommendations](../plugin-recommendations/README.md) bundles each insert this service and its [browser half](../client-ui-action-cards/README.md); enabling either bundle enables both. The service has one configuration field: + +| Field | Default | Meaning | +|---|---|---| +| `queuedMessageLimit` | `2` | Queued next-turn user messages at or above which `propose_action` refuses the call, so no card is offered. | + +The generated [configuration catalog](../../../docs/config-catalog.md#deepseek-aidsh-action-cards) is the exhaustive source for accepted fields. + +### Register a card kind + +A kind package injects `actionCards` and calls `ctx.actionCards.register(kind)` from its own plugin; unloading that plugin removes the kind, and actions on its cards are then refused as unavailable, except that Not now still dismisses an unstarted card. The kind names its action, the skill that proposes it, the closing invitation guidance that an accepted call's result gives the model, a zod schema for the card's `details`, the user actions it accepts and the shared checks each needs (`global` Remote access, `writes` refused in a read-only Session), the recorded phases that still offer dismissal, optional status guidance, the decision data fields the status notice reports, an optional `reconcile` that updates the card's recorded decision from state outside the Session before every action, and the handler for every action except `dismiss`. The handler records each new decision with `ctx.actionCards.record()`, which refuses an action no kind registered and the derived `replaced` phase, and returns `accepted`, `settled` with the decision in effect, `refused` or `unconfirmed`. + +### Card rules + +- The first accepted `propose_action` call of a turn becomes the card. The tool refuses a second call in the same turn, an unregistered action, a call whose kind skill was not loaded in the current turn, a blank title or brief, details that fail the kind schema, calls dispatched from code, calls from a subagent, and any call while `queuedMessageLimit` or more user messages wait for later turns. +- A newer card of any kind replaces every unstarted earlier card. Replacement is derived from the log and never recorded. +- Events a forked Session inherits create no cards. +- Before every action, including Not now, the kind's `reconcile` updates the card from state outside the Session. Not now is then recorded for an unstarted card and for the phases the kind lists; dismissed and replaced cards accept no further action. Without its registered kind, Not now still dismisses an unstarted card, and every other action is refused as unavailable. +- Concurrent actions on one card share one operation. Actions require the card's live root Agent. + +----- + + +## Understand the implementation + +
+Implementation internals — click to expand + +The package default-exports the `actionCards` Remote service. It registers `propose_action` once, globally, with a `when` condition: a live root Agent becomes eligible when its own direct `skill` call loads the skill of a registered kind, while loads by subagents or from code are ignored. The flag lives in memory until the Agent is disposed. The call's `execute` checks the live root, the queued messages the projection counts against `queuedMessageLimit` and the current turn's skill loads through the turn boundary, and keeps an in-memory mark of the turn's accepted call; the logged `tool/call` and its successful `tool/result` are the durable proposal. + +The `actionCards` Session projection (state version 2) pairs each `propose_action` call with its successful result, records decisions from `plugin:action-cards/decision`, counts queued next-turn messages from inbox splices for the tool's limit, and keeps unreported decisions until a status notice whose `through` covers them enters the log. Its Client view `{ proposals, decisions }` is excluded from Session summaries. + +The Remote `act(sessionId, callId, verb, payload?)` checks the owner, the live root and the card; Not now continues without a registered kind, while other verbs need the kind, the verb and Remote access. It then shares one operation per card. The kind's `reconcile` runs first and the live root is checked again; a dismissed or replaced card then settles unchanged, Not now is recorded, a writing verb is refused in a read-only Session, and every other verb goes to the kind handler under the Agent's initiator. `record()` also accepts an action whose kind has unregistered, because Cordis disposes a plugin's effects concurrently and an unloading kind still records the outcome of work it started. An `agent/pre-step` listener adds the status notice after later listeners prepared the step. + +| Source | Contents | +|---|---| +| [`src/index.ts`](src/index.ts) | Service, tool, kind registry, Remote action and status notice | +| [`src/projection.ts`](src/projection.ts) | Session projection | +| [`src/types.ts`](src/types.ts) | Proposal, decision, view and result types | +| [`tests/`](tests/) | Activation, validation, projection, action and notice tests | + +
+ +----- + + +## Further Exploration + +- [Action card display](../client-ui-action-cards/README.md) — the browser frame, kind bodies and the process row. +- [Personalized pages](../creation-recommendations/README.md) and [Plugin recommendations](../plugin-recommendations/README.md) — the shipped card kinds. +- [Tools](../../core/tools/README.md) — conditional registration and step admission. +- [Session projection](../../session/session-projection/README.md) — the card state the browser reads. + + +## Model Experience + +### Card tool + +#### What the model sees + +The [`propose_action` declaration](../../../docs/tool-catalog.md#deepseek-aidsh-action-cards) appears only to a live root Agent whose own direct call loaded the skill of a registered kind in this Session, from the next step on, for that Agent's lifetime. Subagents never see it, and neither do PTC presets, whose skill loads are dispatched from code, so they show no cards. A kind's skill names the action and the fields of `details`. An accepted call returns `Card shown below your answer. Do not restate it. `, where `` is the kind's one- or two-sentence guidance on inviting the user to the card in the user's language, so the model writes an invitation only after a card exists. A refused call carries no invitation and returns its reason, for example `Load skill first, then call propose_action.`, `One card per turn; the earlier card stands.`, `The user has queued more messages; show no card and answer briefly.`, `Cards are offered in the main conversation only.`, `Unknown action "". Registered actions: .`, where the list reads `none` once the last kind unregistered, or the failing `details` fields. + +#### Token effect + +Before a card skill loads, the tool adds no tokens. Afterwards each request of that Agent declares it: 574 characters of JSON, about 144 tokens at the token meter's four characters per token. A card costs one extra model round trip, the call before the final answer, and its tool result: a 49-character prefix plus the kind's invitation. The Personalized pages result is 305 characters, about 77 tokens at the same rate. + +#### KV Cache effect + +Activation is append-only on routes that declare tool updates, such as DeepSeek-V4.1-Flash: when the condition turns true the loop logs a header change and a `tool-addition` developer message, and those routes receive the definition as a deferred declaration referenced at that position, so the cached prefix is kept. A route without tool updates receives the tool in its declaration list from that step on, which changes the request prefix once. Disposing the Agent drops the activation; a reopened Session declares the tool again only after a card skill loads again, logging a removal and a later addition. + +### Card status notice + +#### What the model sees + +After a decision is recorded, the next request carries one `action-cards` notice: the preamble below, the guidance sentences of each involved kind, `Continue with the user’s current request.`, and a JSON array of the changed cards with `action`, `callId`, `title`, `phase` and the data fields their kind reports. + +##### Notice preamble + +```markdown +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. +``` + +#### Token effect + +One notice per request that follows new decisions, sized by the changed cards; unchanged cards add nothing, and several decisions of one card collapse to its latest. + +#### KV Cache effect + +The notice is appended after the request's other new context and never rewrites earlier messages. The notice neither starts a turn nor wakes an idle conversation. + +## Known Limitations and Deferred Work + + + +- Activation is per live Agent and kept in memory, so after a Session reopens the tool appears again only after a card skill loads again. +- PTC presets show no cards: skill loads and calls dispatched from code never activate or reach the tool, because the log records them as program dispatches rather than tool calls. +- Cards are offered in the main conversation only. A subagent's skill load, including a Creator child's, activates nothing and its calls are refused, because only a live root can act on a card. +- Only one card is offered per turn across all kinds. A later card of any kind replaces every unstarted earlier card, so a second kind's card replaces an unstarted card of the first. +- Refusal reasons of user actions are plain strings by design: the service defines `closing`, `not-live-root`, `unknown`, `unavailable`, `access-denied` and `read-only`, each kind adds its own, and a browser body names the reasons it knows and treats others as unavailable. +- Each kind validates `details` on both sides, with its Host schema when the model proposes and before each action, and with its browser body's own schema, so the two schemas change together. +- The Personalized pages and Plugin recommendations bundles both insert the rows of this service and its browser half, and with both enabled the Loader keeps one entry per row id. Two optional bundles cannot yet share a dependency row addressably: while both are enabled, the Plugins page cannot address the two shared rows (`action-cards`, `client-ui-action-cards`), so they cannot be switched or configured individually. Use the bundle switches; `queuedMessageLimit` can then be changed only in the profile's `cordis.patch.yml`. + + +### Dev Note + +
+Working context for maintainers — click to expand + +None. + +
diff --git a/packages/experimental/action-cards/README.zh.md b/packages/experimental/action-cards/README.zh.md new file mode 100644 index 0000000000..79607601d9 --- /dev/null +++ b/packages/experimental/action-cards/README.zh.md @@ -0,0 +1,138 @@ +--- +description: "可选操作卡片的共享 Host 服务:propose_action 工具、卡片状态、用户操作和状态通知,各实验包把自己的卡片种类注册进来。" +kind: "package-reference" +--- + +# @deepseek-ai/dsh-action-cards + +[English](README.md) | 中文 + +## 概述 + +实验可以在回答下方提供一个可选操作,例如创建页面,而不必新增自己的工具。模型先加载该实验的 Skill,再调用 `propose_action`;本轮结束后卡片出现在回答下方,用户操作之前不会执行任何事。用户的每次选择都会被记录,并在模型的下一次请求中告知模型。对话中加载卡片 Skill 之前,这个工具不占用任何 token。 + +## 目录 + +- [使用本包](#use-this-package) +- [实现原理](#understand-the-implementation) +- [延伸阅读](#further-exploration) +- [模型体验](#model-experience) +- [已知限制与后续工作](#known-limitations-and-deferred-work) +- [开发备注](#dev-note) + +----- + + +## 使用本包 + +[个性化页面](../creation-recommendations/README.zh.md)和[插件推荐](../plugin-recommendations/README.zh.md)两个 Bundle 都会插入本服务及其[浏览器端](../client-ui-action-cards/README.zh.md);启用其中任一个即同时启用两者。本服务只有一个配置字段: + +| 字段 | 默认值 | 含义 | +|---|---|---| +| `queuedMessageLimit` | `2` | 排队到后续轮次的用户消息达到该数量时,`propose_action` 拒绝调用,因此不会展示卡片。 | + +生成的[配置目录](../../../docs/config-catalog.zh.md#deepseek-aidsh-action-cards)是可接受字段的完整来源。 + +### 注册卡片种类 + +种类所在的包注入 `actionCards`,并在自己的插件中调用 `ctx.actionCards.register(kind)`;该插件卸载时种类随之移除,此后对其卡片的操作会以 unavailable 拒绝,只有“暂不需要”仍可忽略尚未开始的卡片。种类需要给出:动作名、提议该动作的 Skill、被接受的调用结果交给模型的结尾邀请指引、卡片 `details` 的 zod 校验、接受的用户操作及各自需要的共享检查(`global` 表示需要全局 Remote 权限,`writes` 表示只读会话中拒绝)、仍可忽略卡片的已记录阶段、可选的状态说明、状态通知要报告的决定数据字段、可选的 `reconcile`(每次操作前根据会话之外的状态更新卡片的已记录决定),以及处理 `dismiss` 以外所有操作的处理器。处理器用 `ctx.actionCards.record()` 记录每个新决定(该方法拒绝没有种类注册过的动作和推导得出的 `replaced` 阶段),并返回 `accepted`、带当前决定的 `settled`、`refused` 或 `unconfirmed`。 + +### 卡片规则 + +- 一轮中第一个被接受的 `propose_action` 调用成为卡片。工具会拒绝同一轮的第二次调用、未注册的动作、本轮没有加载对应种类 Skill 的调用、空白的标题或说明、未通过种类校验的 details、从代码中发起的调用、来自 subagent 的调用,以及排队到后续轮次的用户消息达到 `queuedMessageLimit` 条时的任何调用。 +- 任何种类的新卡片都会替换此前所有尚未开始的卡片。替换从日志推导,从不单独记录。 +- fork 出的会话从父会话继承的事件不会产生卡片。 +- 每次操作(包括“暂不需要”)之前,种类的 `reconcile` 先根据会话之外的状态更新卡片。之后,对尚未开始的卡片,以及处于种类列出阶段的卡片,“暂不需要”会被记录;已忽略、已替换的卡片不再接受任何操作。卡片的种类未注册时,“暂不需要”仍可忽略尚未开始的卡片,其他操作一律以 unavailable 拒绝。 +- 同一张卡片上的并发操作共用一次处理。操作要求卡片所在会话的根 Agent 仍然在线。 + +----- + + +## 实现原理 + +
+实现细节——点击展开 + +本包默认导出 `actionCards` Remote 服务。它在全局注册一次 `propose_action`,并附带 `when` 条件:在线的根 Agent 自己直接调用 `skill` 加载了已注册种类的 Skill 后,该 Agent 才满足条件,subagent 或代码中发起的加载一律忽略;这个标记保存在内存中,直到 Agent 被释放。调用的 `execute` 检查在线根 Agent、投影累计的排队消息是否达到 `queuedMessageLimit`,并通过轮次边界检查本轮加载过的 Skill,同时在内存中标记本轮已接受的调用;日志中的 `tool/call` 及其成功的 `tool/result` 就是持久的提议。 + +`actionCards` 会话投影(状态版本 2)把每个 `propose_action` 调用与其成功结果配对,从 `plugin:action-cards/decision` 读取决定,从收件箱拼接事件累计排队到下一轮的消息供工具上限使用,并保留尚未报告的决定,直到 `through` 覆盖它们的状态通知写入日志。投影的客户端视图 `{ proposals, decisions }` 不进入会话摘要。 + +Remote `act(sessionId, callId, verb, payload?)` 先检查服务所有者、在线根 Agent 和卡片;“暂不需要”在种类未注册时也继续处理,其他操作还需要种类、操作和 Remote 权限。然后同一张卡片共用一次处理。种类的 `reconcile` 先运行,之后再次检查在线根 Agent;已忽略或已替换的卡片原样返回,“暂不需要”被记录,写入类操作在只读会话中被拒绝,其余操作在该 Agent 的发起方作用域内交给种类处理器。`record()` 也接受种类已注销的动作,因为 Cordis 并发释放插件的各个 effect,卸载中的种类仍要记录已开始工作的结果。一个 `agent/pre-step` 监听器在后续监听器准备好本步之后追加状态通知。 + +| 源码 | 内容 | +|---|---| +| [`src/index.ts`](src/index.ts) | 服务、工具、种类注册表、Remote 操作与状态通知 | +| [`src/projection.ts`](src/projection.ts) | 会话投影 | +| [`src/types.ts`](src/types.ts) | 提议、决定、视图与结果类型 | +| [`tests/`](tests/) | 激活、校验、投影、操作与通知测试 | + +
+ +----- + + +## 延伸阅读 + +- [操作卡片显示](../client-ui-action-cards/README.zh.md) — 浏览器端卡片框架、种类正文与过程行。 +- [个性化页面](../creation-recommendations/README.zh.md)和[插件推荐](../plugin-recommendations/README.zh.md) — 随产品提供的卡片种类。 +- [工具](../../core/tools/README.zh.md) — 条件注册与每步准入。 +- [会话投影](../../session/session-projection/README.zh.md) — 浏览器读取的卡片状态。 + + +## 模型体验 + +### 卡片工具 + +#### 模型看到什么 + +[`propose_action` 声明](../../../docs/tool-catalog.zh.md#deepseek-aidsh-action-cards)只对在本会话中自己直接调用加载过已注册种类 Skill 的在线根 Agent 出现,从下一步开始,持续到该 Agent 被释放。subagent 看不到它;PTC 预设的 Skill 加载由代码发起,也看不到它,因此不会展示卡片。种类的 Skill 给出动作名和 `details` 的字段。被接受的调用返回 `Card shown below your answer. Do not restate it. `,其中 `` 是种类给出的一两句指引,说明如何用用户的语言邀请用户使用卡片,因此模型只会在卡片已经存在之后写邀请。被拒绝的调用不带邀请,只返回原因,例如 `Load skill first, then call propose_action.`、`One card per turn; the earlier card stands.`、`The user has queued more messages; show no card and answer briefly.`、`Cards are offered in the main conversation only.`、`Unknown action "". Registered actions: .`(最后一个种类注销后列表为 `none`),或未通过校验的 `details` 字段。 + +#### Token 影响 + +加载卡片 Skill 之前,工具不增加任何 token。加载之后,该 Agent 的每次请求都会声明它:574 个字符的 JSON,按 token 计量器每 4 个字符计 1 个 token 约 144 个 token。出一张卡片多一次模型往返(最终回答前的这次调用)以及一条工具结果:49 个字符的前缀加上种类的邀请。个性化页面的结果为 305 个字符,按同样的比例约 77 个 token。 + +#### KV Cache 影响 + +在声明支持工具更新的路由上(例如 DeepSeek-V4.1-Flash),激活只追加内容:条件变为真时,循环记录一次请求头变化和一条 `tool-addition` 开发者消息,这些路由以延迟加载声明的形式在该位置引用新定义,因此已缓存的前缀保持不变。不支持工具更新的路由从这一步起在声明列表中收到该工具,请求前缀因此改变一次。Agent 被释放后激活随之失效;重新打开的会话要再次加载卡片 Skill 才会再次声明该工具,并依次记录一次移除和一次添加。 + +### 卡片状态通知 + +#### 模型看到什么 + +记录决定之后,下一次请求会带一条 `action-cards` 通知:下方的开头段落、所涉及各种类的说明句、`Continue with the user’s current request.`,以及一个 JSON 数组,列出发生变化的卡片的 `action`、`callId`、`title`、`phase` 和种类要报告的数据字段。 + +##### 通知开头 + +```markdown +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. +``` + +#### Token 影响 + +有新决定之后的每次请求带一条通知,大小取决于发生变化的卡片;没有变化的卡片不增加内容,同一张卡片的多次决定只报告最新一次。 + +#### KV Cache 影响 + +通知追加在本次请求其他新上下文之后,从不改写之前的消息。通知既不会开始新轮次,也不会唤醒空闲的对话。 + +## 已知限制与后续工作 + + + +- 激活按在线 Agent 记在内存中,因此会话重新打开后,要再次加载卡片 Skill 工具才会再次出现。 +- PTC 预设不会展示卡片:从代码中发起的 Skill 加载和调用既不会激活工具,也到达不了工具,因为日志把它们记为程序派发而不是工具调用。 +- 卡片只在主对话中提供。subagent(包括 Creator 子 Agent)加载 Skill 不会激活任何东西,其调用也会被拒绝,因为只有在线的根 Agent 才能对卡片进行操作。 +- 所有种类合计每轮只展示一张卡片。任何种类的新卡片都会替换此前所有尚未开始的卡片,因此第二个种类的卡片会替换第一个种类尚未开始的卡片。 +- 用户操作的拒绝原因有意设计为普通字符串:服务定义 `closing`、`not-live-root`、`unknown`、`unavailable`、`access-denied` 和 `read-only`,各种类可以添加自己的原因,浏览器端正文为已知原因显示文案,其余按 unavailable 处理。 +- 每个种类在两端各自校验 `details`:Host 端的 schema 在模型提议时和每次操作前校验,浏览器端正文用自己的 schema 校验,因此两个 schema 要一起修改。 +- 个性化页面和插件推荐两个 Bundle 都会插入本服务及其浏览器端的行,两个都启用时 Loader 对每个行 id 只保留一个条目。平台目前不支持两个可选 Bundle 以可定位的方式共享同一依赖行:两个都启用时,插件页无法定位这两条共享行(`action-cards`、`client-ui-action-cards`),不能单独开关或配置它们。请使用 Bundle 开关;此时 `queuedMessageLimit` 只能在 profile 的 `cordis.patch.yml` 中修改。 + + +### 开发备注 + +
+维护者工作背景——点击展开 + +无。 + +
diff --git a/packages/experimental/action-cards/locale/de.json b/packages/experimental/action-cards/locale/de.json new file mode 100644 index 0000000000..1ff66c5a5a --- /dev/null +++ b/packages/experimental/action-cards/locale/de.json @@ -0,0 +1,6 @@ +{ + "meta": { + "title": "Aktionskarten", + "description": "Zeigt für aktivierte Experimente optionale Aktionskarten unter Antworten an und meldet die Entscheidungen des Benutzers an das Gespräch zurück." + } +} diff --git a/packages/experimental/action-cards/locale/en.json b/packages/experimental/action-cards/locale/en.json new file mode 100644 index 0000000000..9b5b1fdc95 --- /dev/null +++ b/packages/experimental/action-cards/locale/en.json @@ -0,0 +1,6 @@ +{ + "meta": { + "title": "Action cards", + "description": "Show optional action cards below answers for enabled experiments, and report the user's choices back to the conversation." + } +} diff --git a/packages/experimental/action-cards/locale/es.json b/packages/experimental/action-cards/locale/es.json new file mode 100644 index 0000000000..71c4adb3ad --- /dev/null +++ b/packages/experimental/action-cards/locale/es.json @@ -0,0 +1,6 @@ +{ + "meta": { + "title": "Tarjetas de acción", + "description": "Muestra tarjetas de acción opcionales debajo de las respuestas para los experimentos habilitados e informa a la conversación de las elecciones del usuario." + } +} diff --git a/packages/experimental/action-cards/locale/fr.json b/packages/experimental/action-cards/locale/fr.json new file mode 100644 index 0000000000..5b8fef7ac1 --- /dev/null +++ b/packages/experimental/action-cards/locale/fr.json @@ -0,0 +1,6 @@ +{ + "meta": { + "title": "Cartes d'action", + "description": "Affiche des cartes d'action facultatives sous les réponses pour les expériences activées et transmet les choix de l'utilisateur à la conversation." + } +} diff --git a/packages/experimental/action-cards/locale/ja.json b/packages/experimental/action-cards/locale/ja.json new file mode 100644 index 0000000000..5882383b52 --- /dev/null +++ b/packages/experimental/action-cards/locale/ja.json @@ -0,0 +1,6 @@ +{ + "meta": { + "title": "アクションカード", + "description": "有効な実験のために回答の下に任意のアクションカードを表示し、ユーザーの選択を会話に伝えます。" + } +} diff --git a/packages/experimental/action-cards/locale/pt.json b/packages/experimental/action-cards/locale/pt.json new file mode 100644 index 0000000000..384e7146fb --- /dev/null +++ b/packages/experimental/action-cards/locale/pt.json @@ -0,0 +1,6 @@ +{ + "meta": { + "title": "Cartões de ação", + "description": "Mostra cartões de ação opcionais abaixo das respostas para experimentos ativados e informa as escolhas do usuário à conversa." + } +} diff --git a/packages/experimental/action-cards/locale/ru.json b/packages/experimental/action-cards/locale/ru.json new file mode 100644 index 0000000000..28d428e532 --- /dev/null +++ b/packages/experimental/action-cards/locale/ru.json @@ -0,0 +1,6 @@ +{ + "meta": { + "title": "Карточки действий", + "description": "Показывает необязательные карточки действий под ответами для включенных экспериментов и передает выбор пользователя в разговор." + } +} diff --git a/packages/experimental/action-cards/locale/zh.json b/packages/experimental/action-cards/locale/zh.json new file mode 100644 index 0000000000..2ddbdd8262 --- /dev/null +++ b/packages/experimental/action-cards/locale/zh.json @@ -0,0 +1,6 @@ +{ + "meta": { + "title": "操作卡片", + "description": "为已启用的实验在回答下方显示可选操作卡片,并把用户的选择告知对话。" + } +} diff --git a/packages/experimental/action-cards/package.json b/packages/experimental/action-cards/package.json new file mode 100644 index 0000000000..160e8156a0 --- /dev/null +++ b/packages/experimental/action-cards/package.json @@ -0,0 +1,75 @@ +{ + "name": "@deepseek-ai/dsh-action-cards", + "description": "Optional action cards below answers: one shared proposal tool, card state, user actions and status notices", + "version": "0.2.1-rc.2", + "publishConfig": { + "access": "public" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/deepseek-ai/deepseek-harness.git", + "directory": "packages/experimental/action-cards" + }, + "type": "module", + "main": "lib/index.js", + "types": "lib/types/index.d.ts", + "exports": { + ".": { + "types": "./lib/types/index.d.ts", + "default": "./lib/index.js" + }, + "./src/*": "./src/*", + "./package.json": "./package.json", + "./locale/*.json": "./locale/*.json", + "./types": { + "types": "./lib/types/types.d.ts", + "default": "./lib/types/types.js" + }, + "./typert": { + "types": "./lib/typert.host.d.ts", + "default": "./lib/typert.host.js" + }, + "./remote": { + "types": "./lib/typert.remote-client.d.ts", + "default": "./lib/typert.remote-client.js" + } + }, + "license": "MIT", + "peerDependencies": { + "@deepseek-ai/cordis": "workspace:~", + "@deepseek-ai/dsh-typert-protocol": "workspace:*", + "@deepseek-ai/dsh-llm": "workspace:*", + "@deepseek-ai/dsh-tools": "workspace:*", + "@deepseek-ai/dsh-session": "workspace:*" + }, + "devDependencies": { + "@deepseek-ai/cordis": "workspace:~", + "@deepseek-ai/dsh-typert-protocol": "workspace:*", + "@deepseek-ai/dsh-llm": "workspace:*", + "@deepseek-ai/dsh-tools": "workspace:*", + "@deepseek-ai/dsh-agent": "workspace:*", + "@deepseek-ai/dsh-agent-loop": "workspace:*", + "@deepseek-ai/dsh-agent-loop-testkit": "workspace:*", + "@deepseek-ai/dsh-session": "workspace:*", + "@deepseek-ai/dsh-session-projection": "workspace:*", + "@deepseek-ai/dsh-api-remote-access": "workspace:*", + "@deepseek-ai/dsh-sandbox-policy": "workspace:*", + "@deepseek-ai/dsh-skill": "workspace:*", + "@deepseek-ai/dsh-tool-skill": "workspace:*", + "@deepseek-ai/dsh-tool-testkit": "workspace:*", + "@deepseek-ai/dsh-util-values": "workspace:*" + }, + "dependencies": { + "zod": "4.4.3" + }, + "files": [ + "locale/*.json", + "lib/index.js", + "lib/types/**/*.js", + "lib/types/**/*.d.ts", + "lib/typert.host.js", + "lib/typert.host.d.ts", + "lib/typert.remote-client.js", + "lib/typert.remote-client.d.ts" + ] +} diff --git a/packages/experimental/action-cards/src/index.ts b/packages/experimental/action-cards/src/index.ts new file mode 100644 index 0000000000..1a5e75a4fc --- /dev/null +++ b/packages/experimental/action-cards/src/index.ts @@ -0,0 +1,451 @@ +/** Optional action cards below an answer: the `propose_action` tool, card state, user actions and status notices for registered kinds. */ +import { FiberState, Service, type Context } from '@deepseek-ai/cordis' +import { z, type ZodError, type ZodType } from 'zod' +import { Remote, TypertRemoteService } from '@deepseek-ai/dsh-typert-protocol' +import { createUserMessage, HarnessError, type ToolCallId } from '@deepseek-ai/dsh-llm' +import { defineTool, type ToolRunContext } from '@deepseek-ai/dsh-tools' +import { appendPluginRecord, SessionSeq, type Session } from '@deepseek-ai/dsh-session' +import type { Agent } from '@deepseek-ai/dsh-agent' +import type { ProjectionHandle } from '@deepseek-ai/dsh-session-projection' +import type { JsonValue } from '@deepseek-ai/dsh-util-values' +import type {} from '@deepseek-ai/dsh-api-remote-access' +import type {} from '@deepseek-ai/dsh-sandbox-policy' +import { actionCardProjection, PROPOSE_ACTION, type ActionCardState } from './projection.ts' +import type { ActionCardDecision, ActionCardFields, ActionCardProposal, ActionCardResult, ActionCardView } from './types.ts' +export type * from './types.ts' + +/** Deployment-tunable limits for offering cards. */ +export const Config = z.object({ + /** Queued next-turn user messages at or above which `propose_action` refuses, so no card is offered. */ + queuedMessageLimit: z.number().int().min(1).default(2), +}).prefault({}) +/** Validated card limits after schema defaults. */ +export type Config = z.infer + +declare module '@deepseek-ai/cordis' { + interface Context { actionCards: ActionCards } +} + +/** The user action every card offers; the shared service records it. */ +const DISMISS = 'dismiss' +/** Phases the projection derives from the log; no decision may record them. */ +const DERIVED_PHASES: readonly string[] = ['replaced'] +/** Phases after which no user action changes the card. */ +const FINAL_PHASES: readonly string[] = ['dismissed', ...DERIVED_PHASES] +/** Tool whose successful loads activate `propose_action`; its `name` argument names the skill. */ +const SKILL_TOOL = 'skill' +const skillArguments = z.object({ name: z.string() }) + +const STATUS_PREAMBLE = '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.' +const STATUS_CLOSING = 'Continue with the user’s current request.' + +/** The fiber fields the stopping check reads. */ +export interface PluginFiber { + readonly uid: number | null + readonly state: FiberState + readonly parent: { readonly fiber: PluginFiber } +} + +/** + * Whether a plugin must refuse new work: its fiber or an ancestor is disposed or unloading, since a parent's disposal + * begins before the plugin's own effects run, or the plugin's own drain has begun. + * @param owner - the plugin's fiber. + * @param draining - whether the plugin's drain effect has started. + * @returns whether the plugin is stopping. + */ +export function isPluginStopping(owner: PluginFiber, draining: boolean): boolean { + for (let fiber = owner; ; fiber = fiber.parent.fiber) { + if (fiber.uid === null || fiber.state === FiberState.UNLOADING) return true + if (fiber.parent.fiber === fiber) return draining + } +} + +/** A refused `propose_action` call; the message tells the model what to do instead. */ +export class ActionCardError extends HarnessError {} + +/** Shared checks one user action of a kind requires before the kind handles it. */ +export interface ActionCardVerb { + /** The connection must hold global Remote access, as for profile changes. */ + readonly global?: true + /** Refused while the Session sandbox mode is read-only. */ + readonly writes?: true +} + +/** A proposal whose details passed its kind's schema. */ +export type ActionCardProposalOf
= Omit & { readonly details: Details } + +/** Host half of one action kind: the skill that proposes it, its fields and its user actions. */ +export interface ActionCardKind
{ + /** Value of the `action` argument. */ + readonly action: string + /** Skill whose instructions propose this action; a load in the current turn admits the call. */ + readonly skill: string + /** + * Model-facing closing guidance that the result of an accepted call gives: one or two sentences on how to invite + * the user to the card, in the user's language. Refused calls never carry it, so the model invites the user only to a + * card that exists. + */ + readonly invitation: string + /** Validates `details` when the model proposes and before each user action. */ + readonly details: ZodType
+ /** User actions besides `dismiss`, with the shared checks each requires; other verbs are refused as unavailable. */ + readonly verbs: Readonly> + /** Recorded phases that still offer the action; dismissal is recorded for these and for an unstarted card. */ + readonly openPhases?: readonly string[] + /** Sentences the status notice adds when it reports this kind's decisions. */ + readonly statusGuidance?: string + /** Decision data fields the status notice reports; omitted reports every field. */ + readonly statusFields?: readonly string[] + /** + * Fields recorded with a dismissal. + * @param proposal - the dismissed card. + * @param payload - fields the user's client sent. + * @returns decision data, or undefined to record none. + */ + dismissal?(proposal: ActionCardProposalOf
, payload: ActionCardFields | undefined): ActionCardFields | undefined + /** + * Bring the card's recorded decision up to date with state outside the Session, such as an installation that + * finished while the Session was closed, recording each change with {@link ActionCards.record}. The service awaits it + * before every user action, including `dismiss`, then reads the card's decision again, so a card that is no longer + * open settles instead of being dismissed. + * @param agent - live root of the card's Session. + * @param proposal - the card with validated details. + * @throws when that state cannot be read; the action is then refused as unavailable. + */ + reconcile?(agent: Agent, proposal: ActionCardProposalOf
): Promise + /** + * Handle one user action other than `dismiss` after the shared checks passed. The handler records each new + * decision with {@link ActionCards.record}. + * @param agent - live root of the card's Session. + * @param proposal - the card with validated details. + * @param verb - one of {@link verbs}. + * @param payload - fields the user's client sent; the kind validates them. + * @returns the action's result. + */ + act(agent: Agent, proposal: ActionCardProposalOf
, verb: string, payload: ActionCardFields | undefined): Promise +} + +/** A registered kind with its details type erased behind validating closures. */ +interface RegisteredKind { + readonly skill: string + readonly invitation: string + readonly verbs: Readonly> + readonly openPhases: readonly string[] + readonly statusGuidance: string | undefined + readonly statusFields: readonly string[] | undefined + /** Describe why details fail the kind's schema; undefined when they pass. */ + readonly detailsIssues: (details: ActionCardFields) => string | undefined + readonly dismissal: (proposal: ActionCardProposal, payload: ActionCardFields | undefined) => ActionCardFields | undefined + /** Absent when the kind keeps no state outside the Session. */ + readonly reconcile: ((agent: Agent, proposal: ActionCardProposal) => Promise) | undefined + readonly act: ( + agent: Agent, proposal: ActionCardProposal, verb: string, payload: ActionCardFields | undefined, + ) => Promise +} + +/** Own entries only: browser-supplied ids such as `toString` never reach inherited object members. */ +function ownEntry(record: Readonly>, key: string): T | undefined { + return Object.hasOwn(record, key) ? record[key] : undefined +} + +function refused(reason: string): ActionCardResult { + return { status: 'refused', reason } +} + +function issuesOf(error: ZodError): string { + return 'Invalid details: ' + error.issues.map(issue => `${['details', ...issue.path.map(String)].join('.')}: ${issue.message}`).join('; ') +} + +/** Card tool, kind registry, Session projection, user actions and status notices. */ +export class ActionCards extends TypertRemoteService { + static inject = ['tools', 'agents', 'sessionProjections', 'remoteAccess'] + static Config = Config + private projection!: ProjectionHandle + private readonly kinds = new Map() + /** + * Actions whose kind unregistered. Cordis disposes a plugin's effects concurrently, so a kind's unload drain can + * still record the outcome of its in-flight work after its registration is gone. + */ + private readonly retired = new Set() + /** Skills of registered kinds each live agent has loaded; `propose_action` is declared while one is still registered. */ + private readonly loaded = new Map>() + /** Kind skills each agent loaded in its latest turn. */ + private readonly turnLoads = new Map }>() + /** The call accepted in each agent's latest turn, until its result fails. */ + private readonly accepted = new Map() + private readonly actions = new Map>() + private closing = false + private readonly owner: Context['fiber'] + + /** + * @param ctx - Owning plugin context. + * @param config - Validated card limits; direct composition without a config gets the schema defaults. + */ + constructor(ctx: Context, private readonly config: Config = Config.parse({})) { + super(ctx, 'actionCards') + this.owner = ctx.fiber + } + + protected async [Service.init](): Promise { + this.projection = await this.ctx.sessionProjections.register(actionCardProjection) + this.ctx.tools.register(defineTool({ + 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: { + action: { type: 'string', required: true, description: 'Action name from the skill.' }, + title: { type: 'string', required: true, description: 'Card title.' }, + brief: { type: 'string', required: true, description: 'What the action does, in the user\'s language.' }, + details: { type: 'object', additionalProperties: true, required: true, description: 'Fields the skill lists for this action.' }, + }, + output: { + schema: { + type: 'object', additionalProperties: false, + properties: { + action: { type: 'string', required: true }, title: { type: 'string', required: true }, invitation: { type: 'string', required: true }, + }, + }, + render: (_args, value) => [{ type: 'text', text: `Card shown below your answer. Do not restate it. ${value.invitation}` }], + }, + execute: (args, exec) => Promise.resolve(this.propose(args, exec)), + }), { when: ({ agent }) => this.declares(agent) }) + this.ctx.on('tools/result', (exec, result) => { + const agent = exec.agent + if (agent === undefined) return + if (exec.name === PROPOSE_ACTION) { + if (result.isError && this.accepted.get(agent)?.callId === exec.callId) this.accepted.delete(agent) + return + } + // Loads by subagents or from code activate nothing: `propose_action` refuses both callers. + if (exec.parent !== undefined || !this.ctx.agents.roots().includes(agent)) return + const skill = exec.name === SKILL_TOOL && !result.isError ? skillArguments.safeParse(exec.arguments) : undefined + if (skill?.success !== true || ![...this.kinds.values()].some(kind => kind.skill === skill.data.name)) return + const turn = this.ctx.agents.turnBoundaryOf(agent.session).lastTurn + const loads = this.turnLoads.get(agent) + if (loads?.turn === turn) loads.skills.add(skill.data.name) + else this.turnLoads.set(agent, { turn, skills: new Set([skill.data.name]) }) + this.loaded.set(agent, new Set([...this.loaded.get(agent) ?? [], skill.data.name])) + }) + this.ctx.on('agent/disposed', ({ agent }) => { + this.loaded.delete(agent) + this.turnLoads.delete(agent) + this.accepted.delete(agent) + }) + this.ctx.on('agent/pre-step', async ({ agent, signal }, next) => { + const decision = await next() + if (decision.kind === 'reject' || signal.aborted || this.isStopping()) return decision + const state = this.projection.stateOf(agent.session) + const pending = Object.values(state.pendingStatus) + if (pending.length === 0) return decision + const receipts = Object.values(state.view.decisions).filter(receipt => Object.hasOwn(state.pendingStatus, receipt.callId)) + const guidance = new Set(receipts.flatMap(receipt => this.kinds.get(receipt.action)?.statusGuidance ?? [])) + const updates = receipts.map((receipt) => { + const fields = this.kinds.get(receipt.action)?.statusFields + const data = receipt.data === undefined || fields === undefined ? receipt.data + : Object.fromEntries(Object.entries(receipt.data).filter(([key]) => fields.includes(key))) + return { action: receipt.action, callId: receipt.callId, title: ownEntry(state.view.proposals, receipt.callId)?.title, + phase: receipt.phase, ...data === undefined ? {} : { data } } + }) + return { ...decision, messages: [...decision.messages, createUserMessage({ + source: { kind: 'action-cards', through: SessionSeq(Math.max(...pending)), form: 'notice', summary: 'Action card status updated' }, + content: [{ type: 'text', text: [STATUS_PREAMBLE, ...guidance, STATUS_CLOSING].join(' ') + '\n' + JSON.stringify(updates) }], + })] } + }, { prepend: true }) + this.ctx.effect(() => async () => { + this.closing = true + await Promise.allSettled([...this.actions.values()]) + }, 'action-cards: drain user actions') + } + + /** + * Register one action kind for the caller's lifetime. + * @param kind - the kind's skill, details schema, verbs and handler. + * @returns disposer removing the kind; actions on its cards are then unavailable. + * @throws when another kind already registered the same action. + */ + register
(kind: ActionCardKind
): () => void { + const typed = (proposal: ActionCardProposal): ActionCardProposalOf
| undefined => { + const details = kind.details.safeParse(proposal.details) + return details.success ? { ...proposal, details: details.data } : undefined + } + const reconcile = kind.reconcile?.bind(kind) + const entry: RegisteredKind = { + skill: kind.skill, invitation: kind.invitation, verbs: kind.verbs, openPhases: kind.openPhases ?? [], + statusGuidance: kind.statusGuidance, statusFields: kind.statusFields, + detailsIssues: (details) => { + const parsed = kind.details.safeParse(details) + return parsed.success ? undefined : issuesOf(parsed.error) + }, + dismissal: (proposal, payload) => { + const card = typed(proposal) + return card === undefined ? undefined : kind.dismissal?.(card, payload) + }, + // A card whose details no longer validate never reached the kind, so the kind has nothing to reconcile. + reconcile: reconcile === undefined ? undefined : async (agent, proposal) => { + const card = typed(proposal) + if (card !== undefined) await reconcile(agent, card) + }, + act: (agent, proposal, verb, payload) => { + const card = typed(proposal) + return card === undefined ? Promise.resolve(refused('unavailable')) : kind.act(agent, card, verb, payload) + }, + } + const dispose = this.ctx.effect(() => { + if (this.kinds.has(kind.action)) throw new Error(`action-cards: action "${kind.action}" is already registered`) + this.kinds.set(kind.action, entry) + return () => { + this.kinds.delete(kind.action) + this.retired.add(kind.action) + } + }, `action-cards: kind ${kind.action}`) + return () => { void dispose() } + } + + /** + * Record a card decision in the Session log; the next model request reports it. + * @param agent - agent whose Session owns the card. + * @param decision - the card's new state. + * @throws when no kind ever registered the decision's action with this service, or its phase is one the projection derives. + */ + record(agent: Agent, decision: ActionCardDecision): void { + if (!this.kinds.has(decision.action) && !this.retired.has(decision.action)) { + throw new Error(`action-cards: cannot record a decision for unregistered action "${decision.action}"`) + } + if (DERIVED_PHASES.includes(decision.phase)) throw new Error(`action-cards: phase "${decision.phase}" is derived from the log and cannot be recorded`) + this.append(agent, decision) + } + + /** + * Read the cards of one Session. + * @param session - a Session whose projections are prepared. + * @returns proposals and decisions by call id. + */ + view(session: Session): ActionCardView { + return this.projection.stateOf(session).view + } + + /** + * Apply a user action to a logged card. Shared checks run first, then the kind reconciles the card; `dismiss` is + * recorded here and every other verb goes to the card's kind. `dismiss` also works on an unstarted card whose kind + * is no longer registered. Concurrent actions on one card share one operation. + * @param agent - live root resolved from the displayed Session. + * @param callId - the card's `propose_action` call id. + * @param verb - `dismiss` or one of the kind's verbs. + * @param payload - kind-defined fields such as a chosen location. + * @returns acceptance of background work, the decision in effect, or a refusal. + */ + @Remote + act(agent: Agent, callId: ToolCallId, verb: string, payload?: Record): Promise { + if (this.isStopping()) return Promise.resolve(refused('closing')) + if (!this.ctx.agents.roots().includes(agent)) return Promise.resolve(refused('not-live-root')) + const proposal = ownEntry(this.projection.stateOf(agent.session).view.proposals, callId) + if (proposal === undefined) return Promise.resolve(refused('unknown')) + const kind = this.kinds.get(proposal.action) + if (verb === DISMISS) return this.coalesce(agent, callId, () => this.dismiss(agent, kind, proposal, payload)) + const policy = kind === undefined ? undefined : ownEntry(kind.verbs, verb) + if (kind === undefined || policy === undefined) return Promise.resolve(refused('unavailable')) + if (policy.global === true && !this.ctx.remoteAccess.allows({ kind: 'global' })) return Promise.resolve(refused('access-denied')) + return this.coalesce(agent, callId, () => this.perform(agent, kind, proposal, verb, policy, payload)) + } + + /** Share one operation per card, run under the Agent's initiator, until it settles. */ + private coalesce(agent: Agent, callId: ToolCallId, run: () => Promise): Promise { + const key = JSON.stringify([agent.id, callId]) + const existing = this.actions.get(key) + if (existing !== undefined) return existing + const operation = this.ctx.agents.withInitiator(agent, run).finally(() => { this.actions.delete(key) }) + this.actions.set(key, operation) + return operation + } + + /** + * Await the kind's reconciliation. A kind that unloads meanwhile still handles the action and refuses it itself. + * @returns a refusal when reconciliation failed or the root closed meanwhile; undefined to continue. + */ + private async reconciled( + agent: Agent, kind: RegisteredKind, proposal: ActionCardProposal, verb: string, + ): Promise { + if (kind.reconcile === undefined) return undefined + try { await kind.reconcile(agent, proposal) } + catch (error) { + this.ctx.logger.warn('Cannot reconcile action card %s before %s: %s', proposal.callId, verb, String(error)) + return refused('unavailable') + } + return this.ctx.agents.roots().includes(agent) ? undefined : refused('not-live-root') + } + + private async dismiss( + agent: Agent, kind: RegisteredKind | undefined, proposal: ActionCardProposal, payload: ActionCardFields | undefined, + ): Promise { + const refusal = kind === undefined ? undefined : await this.reconciled(agent, kind, proposal, DISMISS) + if (refusal !== undefined) return refusal + const decision = ownEntry(this.projection.stateOf(agent.session).view.decisions, proposal.callId) + // Without its kind no recorded phase is known to be open, and the kind may return to act on it. + if (decision !== undefined && (FINAL_PHASES.includes(decision.phase) || kind?.openPhases.includes(decision.phase) !== true)) { + return { status: 'settled', decision } + } + const data = kind?.dismissal(proposal, payload) + const dismissed: ActionCardDecision = { callId: proposal.callId, action: proposal.action, phase: 'dismissed', ...data === undefined ? {} : { data } } + this.append(agent, dismissed) + return { status: 'settled', decision: dismissed } + } + + private async perform( + agent: Agent, kind: RegisteredKind, proposal: ActionCardProposal, verb: string, policy: ActionCardVerb, + payload: ActionCardFields | undefined, + ): Promise { + const refusal = await this.reconciled(agent, kind, proposal, verb) + if (refusal !== undefined) return refusal + const decision = ownEntry(this.projection.stateOf(agent.session).view.decisions, proposal.callId) + if (decision !== undefined && FINAL_PHASES.includes(decision.phase)) return { status: 'settled', decision } + if (policy.writes === true && this.ctx.get('sandboxPolicy')?.resolve({ session: agent.session }).mode === 'read-only') return refused('read-only') + return await kind.act(agent, proposal, verb, payload) + } + + private append(agent: Agent, decision: ActionCardDecision): void { + appendPluginRecord(agent.session, 'plugin:action-cards/decision', decision) + } + + /** Whether `propose_action` is declared for this agent's next step. */ + private declares(agent: Agent): boolean { + const skills = this.loaded.get(agent) + return skills !== undefined && [...this.kinds.values()].some(kind => skills.has(kind.skill)) + } + + private propose( + args: { readonly action: string; readonly title: string; readonly brief: string; readonly details: ActionCardFields }, + exec: ToolRunContext, + ): { action: string; title: string; invitation: string } { + const agent = exec.agent + // Step admission declares the tool only to an agent; calls dispatched from code are not logged as tool calls, + // so no card could follow them. + if (agent === undefined || exec.parent !== undefined) throw new ActionCardError('Call propose_action directly, not from code.', 'ACTION_CARD_NESTED') + // Only a live root can act on a card, so a subagent's card could never be used. + if (!this.ctx.agents.roots().includes(agent)) throw new ActionCardError('Cards are offered in the main conversation only.', 'ACTION_CARD_ROOT') + if (this.projection.stateOf(agent.session).queued >= this.config.queuedMessageLimit) { + throw new ActionCardError('The user has queued more messages; show no card and answer briefly.', 'ACTION_CARD_QUEUED') + } + const kind = this.kinds.get(args.action) + // Admission is decided when the step starts, so the last kind can unregister before this call runs. + if (kind === undefined) throw new ActionCardError(`Unknown action "${args.action}". Registered actions: ${[...this.kinds.keys()].join(', ') || 'none'}.`, 'ACTION_CARD_UNKNOWN') + const turn = this.ctx.agents.turnBoundaryOf(agent.session).lastTurn + const loads = this.turnLoads.get(agent) + if (loads?.turn !== turn || !loads.skills.has(kind.skill)) { + throw new ActionCardError(`Load skill ${kind.skill} first, then call propose_action.`, 'ACTION_CARD_SKILL') + } + if (args.title.trim() === '' || args.brief.trim() === '') throw new ActionCardError('Write a title and a brief.', 'ACTION_CARD_BLANK') + const issues = kind.detailsIssues(args.details) + if (issues !== undefined) throw new ActionCardError(issues, 'ACTION_CARD_DETAILS') + if (this.accepted.get(agent)?.turn === turn || Object.values(this.view(agent.session).proposals).some(card => card.turn === turn)) { + throw new ActionCardError('One card per turn; the earlier card stands.', 'ACTION_CARD_LIMIT') + } + this.accepted.set(agent, { turn, callId: exec.callId }) + return { action: args.action, title: args.title.trim(), invitation: kind.invitation } + } + + private isStopping(): boolean { + return isPluginStopping(this.owner, this.closing) + } +} + +export default ActionCards diff --git a/packages/experimental/action-cards/src/projection.ts b/packages/experimental/action-cards/src/projection.ts new file mode 100644 index 0000000000..3d1be18a97 --- /dev/null +++ b/packages/experimental/action-cards/src/projection.ts @@ -0,0 +1,124 @@ +/** Recover card proposals from logged `propose_action` calls and decisions from plugin records. */ +import { z } from 'zod' +import { ToolCallId } from '@deepseek-ai/dsh-llm' +import { pluginRecordOf, SessionLogOffset } from '@deepseek-ai/dsh-session' +import type { ProjectionDefinition } from '@deepseek-ai/dsh-session-projection' + +/** Model-facing name of the card tool. */ +export const PROPOSE_ACTION = 'propose_action' + +/** Kind-defined JSON object of a proposal or decision. */ +export const fieldsSchema = z.record(z.string(), z.json()) + +/** Arguments of a `propose_action` call; blank titles and briefs create no card. */ +export const argumentsSchema = z.object({ + action: z.string().min(1), + title: z.string().trim().min(1), + brief: z.string().trim().min(1), + details: fieldsSchema, +}) + +const proposalSchema = z.object({ + callId: z.string().min(1).transform(ToolCallId), + turn: z.number().int().nonnegative(), + action: z.string().min(1), + title: z.string().min(1), + brief: z.string().min(1), + details: fieldsSchema, +}).strict() + +/** Recorded decision of one card. */ +export const decisionSchema = z.object({ + callId: z.string().min(1).transform(ToolCallId), + action: z.string().min(1), + phase: z.string().min(1), + data: fieldsSchema.optional(), +}).strict() + +const viewSchema = z.object({ + proposals: z.record(z.string(), proposalSchema), + decisions: z.record(z.string(), decisionSchema), +}) + +const stateSchema = z.object({ + /** Fork-inherited prefix length; inherited events create no proposals or decisions. */ + inheritedEventCount: z.number().int().nonnegative().max(Number.MAX_SAFE_INTEGER).transform(SessionLogOffset), + view: viewSchema, + /** `propose_action` calls of the open turn still waiting for their result. */ + calls: z.record(z.string(), proposalSchema.omit({ callId: true })), + /** Turn of the latest admitted proposal; later calls of that turn create no card. */ + admittedTurn: z.number().int().nullable(), + /** Decisions not yet reported to the model, by call id, with the seq of their record. */ + pendingStatus: z.record(z.string(), z.number()), + /** User messages still queued for later turns, folded from inbox splices; the card tool compares it with its limit. */ + queued: z.number().int().nonnegative(), +}) + +/** Fold state; `view` is also the Client wire value. */ +export type ActionCardState = z.infer + +/** Type of {@link actionCardProjection}: the fold plus its Client view wire. */ +export type ActionCardProjectionDefinition = Omit, 'wire'> & { + readonly wire: NonNullable['wire']> +} + +/** + * Full-Session fold. A successful `propose_action` result admits the call as a card, at most one per turn; + * the first admitted call of a turn wins. A newer card of any kind replaces every unstarted earlier card. + * Events inherited by a forked Session create no proposals or decisions. + */ +export const actionCardProjection: ActionCardProjectionDefinition = { + key: 'actionCards', stateVersion: 2, stateSchema, + init: (_header, inheritedEventCount) => ({ + inheritedEventCount, view: { proposals: {}, decisions: {} }, calls: {}, admittedTurn: null, pendingStatus: {}, queued: 0, + }), + apply: (state, event) => { + if (event.seq < state.inheritedEventCount) return state + switch (event.type) { + case 'turn/start': + // A turn's calls settle inside it; a call left without a result never becomes a card. + return Object.keys(state.calls).length === 0 ? state : { ...state, calls: {} } + case 'agent/inbox/spliced': + if (event.data.target !== 'next-turn') return state + return { ...state, queued: Math.max(0, state.queued - (event.data.removedCount ?? 0) + event.data.inserted.length) } + case 'user/message': { + if (event.data.source.kind !== 'action-cards') return state + const through = event.data.source.through + return { ...state, pendingStatus: Object.fromEntries(Object.entries(state.pendingStatus).filter(([, seq]) => seq > through)) } + } + case 'tool/call': { + if (event.data.name !== PROPOSE_ACTION) return state + let raw: unknown + try { raw = JSON.parse(event.data.arguments) } + catch { return state } // Malformed model JSON failed in the tool too and creates no card. + const parsed = argumentsSchema.safeParse(raw) + if (!parsed.success) return state + return { ...state, calls: { ...state.calls, [event.data.callId]: { turn: event.data.turn, ...parsed.data } } } + } + case 'tool/result': { + const callId = event.data.message.toolCallId + const call = Object.hasOwn(state.calls, callId) ? state.calls[callId] : undefined + if (call === undefined) return state + const calls = Object.fromEntries(Object.entries(state.calls).filter(([id]) => id !== callId)) + if (event.data.message.isError === true || state.admittedTurn === call.turn) return { ...state, calls } + const decisions = { ...state.view.decisions } + // A newer card supersedes every unstarted card; the model made that choice, so no status is reported. + for (const earlier of Object.values(state.view.proposals)) { + if (decisions[earlier.callId] === undefined) decisions[earlier.callId] = { callId: earlier.callId, action: earlier.action, phase: 'replaced' } + } + return { ...state, calls, admittedTurn: call.turn, + view: { proposals: { ...state.view.proposals, [callId]: { callId, ...call } }, decisions } } + } + default: { + const record = pluginRecordOf(event) + if (record?.type !== 'plugin:action-cards/decision') return state + // A malformed or newer record is skipped; throwing here would fail every later read of this Session. + const decision = decisionSchema.safeParse(record.data) + if (!decision.success) return state + return { ...state, view: { ...state.view, decisions: { ...state.view.decisions, [decision.data.callId]: decision.data } }, + pendingStatus: { ...state.pendingStatus, [decision.data.callId]: event.seq } } + } + } + }, + wire: { summary: false, viewSchema, view: (state: ActionCardState) => state.view }, +} diff --git a/packages/experimental/action-cards/src/types.ts b/packages/experimental/action-cards/src/types.ts new file mode 100644 index 0000000000..d240f47126 --- /dev/null +++ b/packages/experimental/action-cards/src/types.ts @@ -0,0 +1,79 @@ +/** Card proposals, recorded decisions and user-action results shared by every action kind. */ +import type { ContextFormed } from '@deepseek-ai/dsh-llm' +import type { ToolCallId } from '@deepseek-ai/dsh-llm/brand' +import type { SessionSeq } from '@deepseek-ai/dsh-session/types' +import type { SessionProjectionSummaryExcluded } from '@deepseek-ai/dsh-session-projection/types' +import type { JsonValue } from '@deepseek-ai/dsh-util-values' + +/** Kind-defined JSON fields of a proposal or decision; each kind validates its own fields. */ +export type ActionCardFields = Readonly> + +/** One card admitted from a successful `propose_action` call. */ +export interface ActionCardProposal { + /** Id of the logged tool call. */ + readonly callId: ToolCallId + /** Turn whose answer the card follows. */ + readonly turn: number + /** Registered action kind named by the call. */ + readonly action: string + readonly title: string + /** What the action does, in the user's language. */ + readonly brief: string + /** Fields the kind's skill asks for. */ + readonly details: ActionCardFields +} + +/** + * Latest state of one card. Kinds define their own phases; `dismissed` is shared. + * `replaced` (a later proposal superseded an unstarted card) is derived from the + * log and never recorded. + */ +export interface ActionCardDecision { + readonly callId: ToolCallId + readonly action: string + readonly phase: string + /** Kind-defined fields such as a chosen location or a created package. */ + readonly data?: ActionCardFields | undefined +} + +/** Open-Session proposals and decisions by call id, excluded from Session summaries. */ +export interface ActionCardView { + readonly proposals: Readonly> + readonly decisions: Readonly> +} + +/** + * Result of a user action. `accepted` starts background work that records its + * own progress; `settled` returns the decision now in effect; `refused` changed + * nothing; `unconfirmed` lost its outcome to an unloading owner, which the + * kind reconciles when the Session reopens. + */ +export type ActionCardResult = + | { readonly status: 'accepted' } + | { readonly status: 'settled'; readonly decision: ActionCardDecision } + | { readonly status: 'refused'; readonly reason: string } + | { readonly status: 'unconfirmed'; readonly reason: string } + +declare module '@deepseek-ai/dsh-llm' { + interface MessageSourceMap { + /** Card status notice; other readers preserve the recorded text without this plugin. + * The producer uses through only to acknowledge delivered decisions. + * @persistenceAttribution + */ + 'action-cards': { kind: 'action-cards'; through: SessionSeq } & ContextFormed + } +} + +declare module '@deepseek-ai/dsh-session/types' { + interface PluginRecordMap { + /** Dismissals and kind-defined progress of action cards. */ + 'plugin:action-cards/decision': ActionCardDecision + } +} + +declare module '@deepseek-ai/dsh-session-projection/types' { + interface SessionProjectionMap { + /** Action card proposals and decisions for the currently open Session. */ + actionCards: SessionProjectionSummaryExcluded + } +} diff --git a/packages/experimental/action-cards/tests/__snapshots__/status.spec.ts.snap b/packages/experimental/action-cards/tests/__snapshots__/status.spec.ts.snap new file mode 100644 index 0000000000..4b6482d24f --- /dev/null +++ b/packages/experimental/action-cards/tests/__snapshots__/status.spec.ts.snap @@ -0,0 +1,11 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`reports recorded decisions once on the next request, with each kind guidance once, without waking the conversation > status notice 1`] = ` +[ + { + "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. Failed runs can be retried from their card. Continue with the user’s current request. +[{"action":"test_action","callId":"call-card-1","title":"Counter","phase":"failed","data":{"reason":"timeout"}},{"action":"test_action","callId":"call-card-2","title":"Second","phase":"dismissed"},{"action":"retired_action","callId":"call-orphan","phase":"done"}]", + "type": "text", + }, +] +`; diff --git a/packages/experimental/action-cards/tests/__snapshots__/tool.spec.ts.snap b/packages/experimental/action-cards/tests/__snapshots__/tool.spec.ts.snap new file mode 100644 index 0000000000..2eec86e6ec --- /dev/null +++ b/packages/experimental/action-cards/tests/__snapshots__/tool.spec.ts.snap @@ -0,0 +1,36 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`declares propose_action from the step after a card skill loads, and logs the addition > tool declaration 1`] = ` +{ + "description": "Show the user one optional action card under this answer. Call only as the skill that names the action instructs.", + "name": "propose_action", + "parameters": { + "properties": { + "action": { + "description": "Action name from the skill.", + "type": "string", + }, + "brief": { + "description": "What the action does, in the user's language.", + "type": "string", + }, + "details": { + "additionalProperties": true, + "description": "Fields the skill lists for this action.", + "type": "object", + }, + "title": { + "description": "Card title.", + "type": "string", + }, + }, + "required": [ + "action", + "title", + "brief", + "details", + ], + "type": "object", + }, +} +`; diff --git a/packages/experimental/action-cards/tests/act.spec.ts b/packages/experimental/action-cards/tests/act.spec.ts new file mode 100644 index 0000000000..0b6e580943 --- /dev/null +++ b/packages/experimental/action-cards/tests/act.spec.ts @@ -0,0 +1,167 @@ +import { expect, it, onTestFinished, vi } from 'vitest' +import { z } from 'zod' +import { ToolCallId } from '@deepseek-ai/dsh-llm' +import type { Agent } from '@deepseek-ai/dsh-agent' +import type { ActionCardProposalOf, ActionCardResult } from '../src/index.ts' +import { ACTION, callId, fixture, proposal, type TestDetails } from './fixture.ts' + +const dismissed = (data?: object) => ({ status: 'settled', decision: { callId, action: ACTION, phase: 'dismissed', ...data === undefined ? {} : { data } } }) + +it('refuses unknown cards, closed owners, verbs of removed kinds and undeclared verbs before any handler runs', async () => { + const f = await fixture() + expect(await f.ctx.actionCards.act(f.agent, callId, 'start')).toEqual({ status: 'refused', reason: 'unknown' }) + f.propose() + for (const id of ['toString', 'constructor']) { + expect(await f.ctx.actionCards.act(f.agent, ToolCallId(id), 'dismiss')).toEqual({ status: 'refused', reason: 'unknown' }) + } + for (const verb of ['toString', 'missing']) { + expect(await f.ctx.actionCards.act(f.agent, callId, verb)).toEqual({ status: 'refused', reason: 'unavailable' }) + } + const roots = vi.spyOn(f.ctx.agents, 'roots').mockReturnValue([]) + expect(await f.ctx.actionCards.act(f.agent, callId, 'start')).toEqual({ status: 'refused', reason: 'not-live-root' }) + roots.mockRestore() + await f.kindFiber.dispose() + expect(await f.ctx.actionCards.act(f.agent, callId, 'start')).toEqual({ status: 'refused', reason: 'unavailable' }) + const service = f.ctx.actionCards + await f.cards.dispose() + expect(await service.act(f.agent, callId, 'dismiss')).toEqual({ status: 'refused', reason: 'closing' }) + expect(f.act).not.toHaveBeenCalled() +}) + +it('applies the shared access and read-only checks the kind declares, then hands typed details to the kind', async () => { + const f = await fixture() + f.propose() + const access = vi.spyOn(f.ctx.remoteAccess, 'allows').mockReturnValue(false) + expect(await f.ctx.actionCards.act(f.agent, callId, 'publish')).toEqual({ status: 'refused', reason: 'access-denied' }) + expect(await f.ctx.actionCards.act(f.agent, callId, 'start', { note: 'x' })).toEqual({ status: 'settled', decision: { callId, action: ACTION, phase: 'start' } }) + access.mockRestore() + expect(f.act).toHaveBeenCalledWith(f.agent, { callId, turn: 1, ...proposal }, 'start', { note: 'x' }) + const resolve = vi.fn(() => ({ mode: 'read-only', workspaceRoot: '/' })) + f.ctx.provide('sandboxPolicy', { resolve }) + expect(await f.ctx.actionCards.act(f.agent, callId, 'write')).toEqual({ status: 'refused', reason: 'read-only' }) + expect(resolve).toHaveBeenCalledWith({ session: f.agent.session }) + expect((await f.ctx.actionCards.act(f.agent, callId, 'publish')).status).toBe('settled') + resolve.mockReturnValue({ mode: 'workspace-write', workspaceRoot: '/' }) + expect((await f.ctx.actionCards.act(f.agent, callId, 'write')).status).toBe('settled') + f.propose(ToolCallId('call-invalid'), { ...proposal, details: { value: 3 } }, f.agent, 2) + expect(await f.ctx.actionCards.act(f.agent, ToolCallId('call-invalid'), 'start')).toEqual({ status: 'refused', reason: 'unavailable' }) + expect(f.act).toHaveBeenCalledTimes(3) +}) + +it('records dismissal of an unstarted or open card with the kind data, and settles started or final cards', async () => { + const f = await fixture({ + openPhases: ['failed'], + dismissal: (card, payload) => ({ value: card.details.value, ...z.object({ choice: z.string() }).safeParse(payload).data }), + }) + f.propose() + expect(await f.ctx.actionCards.act(f.agent, callId, 'dismiss', { choice: 'later' })).toEqual(dismissed({ value: 'a', choice: 'later' })) + expect(f.view().decisions[callId]?.phase).toBe('dismissed') + for (const verb of ['dismiss', 'start']) expect(await f.ctx.actionCards.act(f.agent, callId, verb)).toEqual(dismissed({ value: 'a', choice: 'later' })) + f.ctx.actionCards.record(f.agent, { callId, action: ACTION, phase: 'failed' }) + expect(await f.ctx.actionCards.act(f.agent, callId, 'dismiss')).toEqual(dismissed({ value: 'a' })) + f.ctx.actionCards.record(f.agent, { callId, action: ACTION, phase: 'running', data: { step: 1 } }) + expect(await f.ctx.actionCards.act(f.agent, callId, 'dismiss')).toEqual({ status: 'settled', + decision: { callId, action: ACTION, phase: 'running', data: { step: 1 } } }) + f.propose(ToolCallId('call-invalid'), { ...proposal, details: { value: 3 } }, f.agent, 2) + expect(await f.ctx.actionCards.act(f.agent, ToolCallId('call-invalid'), 'dismiss')).toEqual({ status: 'settled', + decision: { callId: 'call-invalid', action: ACTION, phase: 'dismissed' } }) + expect(f.act).not.toHaveBeenCalled() +}) + +it('dismisses without data when the kind records none', async () => { + const f = await fixture() + f.propose() + expect(await f.ctx.actionCards.act(f.agent, callId, 'dismiss', { choice: 'later' })).toEqual(dismissed()) +}) + +it('shares one operation between concurrent actions on a card and drains it on unload', async () => { + const pending = Promise.withResolvers() + onTestFinished(() => { pending.resolve({ status: 'accepted' }) }) + const f = await fixture({ act: vi.fn(() => pending.promise) }) + f.propose() + const first = f.ctx.actionCards.act(f.agent, callId, 'start') + expect(f.ctx.actionCards.act(f.agent, callId, 'dismiss')).toBe(first) + const disposing = f.cards.dispose() + pending.resolve({ status: 'accepted' }) + expect(await first).toEqual({ status: 'accepted' }) + await disposing +}) + +it('rejects a second kind for the same action and releases a kind through its disposer', async () => { + const f = await fixture() + const kind = (action: string) => ({ action, skill: 'other-skill', invitation: 'Invite the user.', details: z.object({}), verbs: { start: {} }, + act: () => Promise.resolve({ status: 'accepted' }) }) + expect(() => f.ctx.actionCards.register(kind(ACTION))).toThrow('action-cards: action "test_action" is already registered') + const unregister = f.ctx.actionCards.register(kind('other_action')) + f.propose(ToolCallId('call-other'), { action: 'other_action', title: 'Other', brief: 'Other card.', details: {} }) + expect(await f.ctx.actionCards.act(f.agent, ToolCallId('call-other'), 'start')).toEqual({ status: 'accepted' }) + unregister() + expect(await f.ctx.actionCards.act(f.agent, ToolCallId('call-other'), 'start')).toEqual({ status: 'refused', reason: 'unavailable' }) +}) + +it('lets the kind reconcile before every action, so a card that is no longer open settles instead of being dismissed', async () => { + let finished = false + const reconcile = vi.fn(async (agent: Agent, card: ActionCardProposalOf) => { + if (finished) f.ctx.actionCards.record(agent, { callId: card.callId, action: ACTION, phase: 'installed' }) + }) + const f = await fixture({ openPhases: ['unconfirmed'], reconcile }) + f.propose() + f.ctx.actionCards.record(f.agent, { callId, action: ACTION, phase: 'unconfirmed' }) + finished = true + const installed = { status: 'settled', decision: { callId, action: ACTION, phase: 'installed' } } + expect(await f.ctx.actionCards.act(f.agent, callId, 'dismiss')).toEqual(installed) + expect(reconcile).toHaveBeenCalledWith(f.agent, { callId, turn: 1, ...proposal }) + expect(f.view().decisions[callId]?.phase).toBe('installed') + expect(await f.ctx.actionCards.act(f.agent, callId, 'start')).toEqual({ status: 'settled', decision: { callId, action: ACTION, phase: 'start' } }) + expect(reconcile).toHaveBeenCalledTimes(2) + f.propose(ToolCallId('call-invalid'), { ...proposal, details: { value: 3 } }, f.agent, 2) + expect((await f.ctx.actionCards.act(f.agent, ToolCallId('call-invalid'), 'dismiss')).status).toBe('settled') + expect(reconcile).toHaveBeenCalledTimes(2) +}) + +it('refuses the action when reconciliation fails or the root closes meanwhile', async () => { + const reconcile = vi.fn<(agent: Agent) => Promise>(async () => { throw new Error('profile read failed') }) + const f = await fixture({ reconcile }) + f.propose() + const warn = vi.spyOn(f.ctx.logger, 'warn').mockImplementation(() => {}) + expect(await f.ctx.actionCards.act(f.agent, callId, 'dismiss')).toEqual({ status: 'refused', reason: 'unavailable' }) + expect(warn).toHaveBeenCalledWith('Cannot reconcile action card %s before %s: %s', callId, 'dismiss', 'Error: profile read failed') + expect(await f.ctx.actionCards.act(f.agent, callId, 'start')).toEqual({ status: 'refused', reason: 'unavailable' }) + expect(warn).toHaveBeenLastCalledWith('Cannot reconcile action card %s before %s: %s', callId, 'start', 'Error: profile read failed') + expect(f.view().decisions).toEqual({}) + reconcile.mockImplementationOnce(async () => { await f.agentHandle.dispose() }) + expect(await f.ctx.actionCards.act(f.agent, callId, 'dismiss')).toEqual({ status: 'refused', reason: 'not-live-root' }) + expect(f.act).not.toHaveBeenCalled() +}) + +it('refuses to record decisions of actions no kind registered or of phases the projection derives', async () => { + const f = await fixture() + f.propose() + expect(() => { f.ctx.actionCards.record(f.agent, { callId, action: 'other_action', phase: 'done' }) }) + .toThrow('action-cards: cannot record a decision for unregistered action "other_action"') + expect(() => { f.ctx.actionCards.record(f.agent, { callId, action: ACTION, phase: 'replaced' }) }) + .toThrow('action-cards: phase "replaced" is derived from the log and cannot be recorded') + expect(f.view().decisions).toEqual({}) + // An unloading kind still records the outcome of work it had started. + await f.kindFiber.dispose() + f.ctx.actionCards.record(f.agent, { callId, action: ACTION, phase: 'interrupted' }) + expect(f.view().decisions[callId]?.phase).toBe('interrupted') +}) + +it('dismisses an unstarted card whose kind is no longer registered, without data, and settles recorded ones', async () => { + const f = await fixture({ openPhases: ['failed'], dismissal: () => ({ value: 'kind data' }) }) + f.propose(ToolCallId('call-failed'), proposal, f.agent, 1) + f.ctx.actionCards.record(f.agent, { callId: ToolCallId('call-failed'), action: ACTION, phase: 'failed' }) + f.propose(ToolCallId('call-done'), proposal, f.agent, 2) + f.ctx.actionCards.record(f.agent, { callId: ToolCallId('call-done'), action: ACTION, phase: 'dismissed' }) + f.propose(callId, proposal, f.agent, 3) + await f.kindFiber.dispose() + expect(await f.ctx.actionCards.act(f.agent, callId, 'dismiss', { choice: 'later' })).toEqual(dismissed()) + expect(f.view().decisions[callId]).toEqual({ callId, action: ACTION, phase: 'dismissed' }) + // The kind may return and act on its recorded phases, so they stay as they are. + expect(await f.ctx.actionCards.act(f.agent, ToolCallId('call-failed'), 'dismiss')).toEqual({ status: 'settled', + decision: { callId: 'call-failed', action: ACTION, phase: 'failed' } }) + expect(await f.ctx.actionCards.act(f.agent, ToolCallId('call-done'), 'dismiss')).toEqual({ status: 'settled', + decision: { callId: 'call-done', action: ACTION, phase: 'dismissed' } }) + expect(f.act).not.toHaveBeenCalled() +}) diff --git a/packages/experimental/action-cards/tests/fixture.ts b/packages/experimental/action-cards/tests/fixture.ts new file mode 100644 index 0000000000..5110f34676 --- /dev/null +++ b/packages/experimental/action-cards/tests/fixture.ts @@ -0,0 +1,73 @@ +/** Real Agent, Tools, skills and projections with the card service and one scripted test kind. */ +import { Context } from '@deepseek-ai/cordis' +import { onTestFinished, vi } from 'vitest' +import { z } from 'zod' +import { createToolResultMessage, ToolCallId } from '@deepseek-ai/dsh-llm' +import { SessionId, type SessionEvent } from '@deepseek-ai/dsh-session' +import { mountAgentLoopTestDependencies, mountAgentLoopTestHarness } from '@deepseek-ai/dsh-agent-loop-testkit' +import Skills, { BUNDLED_SKILL_RANK } from '@deepseek-ai/dsh-skill' +import * as SkillTool from '@deepseek-ai/dsh-tool-skill' +import RemoteAccess from '@deepseek-ai/dsh-api-remote-access' +import type { Agent } from '@deepseek-ai/dsh-agent' +import ActionCards from '../src/index.ts' +import type { ActionCardFields, ActionCardKind, ActionCardProposalOf, ActionCardResult } from '../src/index.ts' + +export const ACTION = 'test_action' +export const SKILL = 'test-skill' +export const INVITATION = 'Invite the user to try the card in one sentence.' +export const callId = ToolCallId('call-card-1') +export const proposal = { action: ACTION, title: 'Counter', brief: 'Keep a counter visible.', details: { value: 'a' } } + +/** Details of the test kind. */ +export interface TestDetails { readonly value: string } + +/** Provide one model-invocable skill per name. */ +export function provideSkills(ctx: Context, ...names: string[]): void { + ctx.skills.registerProvider(() => ({ + name: 'card-skills', + list: () => Promise.resolve(names.map(name => ({ + name, description: `Use ${name}.`, provider: 'card-skills', source: 'bundled' as const, rank: BUNDLED_SKILL_RANK, locator: null, + invocation: { modelInvocable: true, userInvocable: false }, + }))), + get: candidate => Promise.resolve({ name: candidate.name, description: candidate.description, provider: 'card-skills', source: 'bundled' as const, + invocation: candidate.invocation, content: `Call propose_action for ${candidate.name}.` }), + })) +} + +/** Mount the card service with one registered kind whose handler is a spy. */ +export async function fixture(kind: Partial> = {}) { + const ctx = new Context() + onTestFinished(async () => { await ctx.fiber.dispose() }) + await mountAgentLoopTestDependencies(ctx, { workingDirectory: true }) + const harness = await mountAgentLoopTestHarness(ctx) + await ctx.plugin(RemoteAccess) + await ctx.plugin(Skills) + await ctx.plugin(SkillTool) + provideSkills(ctx, SKILL, 'other-skill') + const cards = await ctx.plugin(ActionCards) + type Handler = ( + agent: Agent, card: ActionCardProposalOf, verb: string, payload: ActionCardFields | undefined, + ) => Promise + const act = vi.fn(async (_agent, card, verb) => + ({ status: 'settled', decision: { callId: card.callId, action: ACTION, phase: verb } })) + const kindFiber = await ctx.plugin({ name: 'test-kind', inject: ['actionCards'], apply: (inner: Context) => { + inner.actionCards.register({ + action: ACTION, skill: SKILL, invitation: INVITATION, details: z.object({ value: z.string() }), + verbs: { start: {}, publish: { global: true }, write: { writes: true } }, + act, ...kind, + }) + } }) + const agentHandle = await ctx.agents.create({ sessionId: SessionId('cards-root') }) + const agent = agentHandle.agent + const events: SessionEvent[] = [] + ctx.on('session/event', (session, event) => { if (session === agent.session) events.push(event) }) + let step = 0 + /** Log a successful `propose_action` call, as the loop does after the tool accepts it. */ + const propose = (id: ToolCallId = callId, args: object = proposal, caller = agent, turn = 1) => { + caller.session.append('tool/call', { turn, step: ++step, callId: id, name: 'propose_action', arguments: JSON.stringify(args) }) + caller.session.append('tool/result', { turn, step, message: createToolResultMessage({ callId: id, content: [], isError: false }) }, { surfaceOp: 'append' }) + return id + } + const view = (caller = agent) => ctx.actionCards.view(caller.session) + return { ctx, harness, cards, kindFiber, agent, agentHandle, events, act, propose, view } +} diff --git a/packages/experimental/action-cards/tests/projection.spec.ts b/packages/experimental/action-cards/tests/projection.spec.ts new file mode 100644 index 0000000000..b734b1bfdd --- /dev/null +++ b/packages/experimental/action-cards/tests/projection.spec.ts @@ -0,0 +1,130 @@ +import { expect, it } from 'vitest' +import { createToolResultMessage, createUserMessage, ToolCallId } from '@deepseek-ai/dsh-llm' +import { + appendPluginRecord, Session, SESSION_FORMAT_VERSION, SessionId, SessionLogOffset, SessionSeq, type SessionEvent, type SessionHeader, +} from '@deepseek-ai/dsh-session' +import { actionCardProjection, type ActionCardState } from '../src/projection.ts' +import type { ActionCardDecision } from '../src/types.ts' + +const projection = actionCardProjection +const args = { action: 'test_action', title: 'Counter', brief: 'Keep count visible', details: { value: 'a' } } +let seq = 0 + +function init(inherited = 0): ActionCardState { + const header: SessionHeader = { version: SESSION_FORMAT_VERSION, id: SessionId('cards'), createdAt: 0, isSeeded: inherited > 0 } + return projection.init(header, SessionLogOffset(inherited)) +} + +function turn(value: number): SessionEvent<'turn/start'> { + return { type: 'turn/start', seq: SessionSeq(seq++), time: 0, data: { turn: value } } +} + +function call(callId: string, value: object | string = args, turnNumber = 1, name = 'propose_action'): SessionEvent<'tool/call'> { + return { type: 'tool/call', seq: SessionSeq(seq++), time: 0, + data: { turn: turnNumber, step: 1, callId: ToolCallId(callId), name, arguments: typeof value === 'string' ? value : JSON.stringify(value) } } +} + +function result(callId: string, isError = false, turnNumber = 1): SessionEvent<'tool/result'> { + return { type: 'tool/result', seq: SessionSeq(seq++), time: 0, surfaceOp: 'append', + data: { turn: turnNumber, step: 1, message: createToolResultMessage({ callId: ToolCallId(callId), content: [], isError }) } } +} + +function spliced(inserted: number, removedCount?: number, target: 'next-turn' | 'next-step' = 'next-turn'): SessionEvent<'agent/inbox/spliced'> { + return { type: 'agent/inbox/spliced', seq: SessionSeq(seq++), time: 0, data: { target, start: 0, ...removedCount === undefined ? {} : { removedCount }, + inserted: Array.from({ length: inserted }, () => createUserMessage({ content: [{ type: 'text', text: 'next' }], source: { kind: 'user' } })) } } +} + +function records(...data: ActionCardDecision[]): readonly SessionEvent[] { + const session = Session.create(SessionId('records')) + for (const value of data) appendPluginRecord(session, 'plugin:action-cards/decision', value) + return session.snapshotEvents() +} + +const fold = (events: readonly SessionEvent[], state = init()) => events.reduce(projection.apply, state) + +it('admits a successful propose_action call as a card in its turn and ignores failed, malformed and other calls', () => { + const state = fold([turn(1), call('c1'), result('c1')]) + expect(state.view.proposals).toEqual({ c1: { callId: 'c1', turn: 1, ...args } }) + expect(state.calls).toEqual({}) + for (const events of [ + [call('c2'), result('c2', true)], + [call('c3', '{broken')], + [call('c4', { ...args, title: ' ' })], + [call('c5', { ...args, details: 'text' })], + [call('c6', args, 1, 'other_tool'), result('c6')], + [result('unrelated')], + ]) { + expect(fold(events).view.proposals).toEqual({}) + } + const trimmed = fold([call('c7', { ...args, title: ' Counter ' }), result('c7')]) + expect(trimmed.view.proposals.c7?.title).toBe('Counter') + const pending = fold([call('c8')]) + expect(Object.keys(pending.calls)).toEqual(['c8']) + expect(projection.apply(pending, turn(2)).calls).toEqual({}) + const idle = fold([turn(3)]) + expect(projection.apply(idle, turn(4))).toBe(idle) +}) + +it('keeps the first admitted card of a turn and replaces only unstarted earlier cards', () => { + const first = fold([turn(1), call('a'), result('a')]) + const late = fold([call('b'), result('b')], first) + expect(Object.keys(late.view.proposals)).toEqual(['a']) + const next = fold([turn(2), call('c', args, 2), result('c', false, 2)], first) + expect(next.view.decisions).toEqual({ a: { callId: 'a', action: 'test_action', phase: 'replaced' } }) + const started = fold(records({ callId: ToolCallId('a'), action: 'test_action', phase: 'creating' }), first) + const afterStart = fold([turn(2), call('c', args, 2), result('c', false, 2)], started) + expect(afterStart.view.decisions.a?.phase).toBe('creating') + expect(afterStart.view.decisions.c).toBeUndefined() + // Replacement crosses kinds: a card of another action replaces the unstarted one too. + const other = fold([turn(2), call('d', { ...args, action: 'other_action' }, 2), result('d', false, 2)], first) + expect(other.view.decisions).toEqual({ a: { callId: 'a', action: 'test_action', phase: 'replaced' } }) +}) + +it('counts user messages queued for later turns for the card tool and leaves cards to it', () => { + const queued = fold([turn(1), spliced(1, undefined, 'next-step'), spliced(2), call('a'), result('a')]) + expect(queued.queued).toBe(2) + expect(Object.keys(queued.view.proposals)).toEqual(['a']) + expect(queued.view.decisions).toEqual({}) + expect(fold([spliced(0, 2)], queued).queued).toBe(0) + expect(fold([spliced(0, 5)]).queued).toBe(0) +}) + +it('ignores the events a forked Session inherits from its parent', () => { + const parent = [turn(1), call('a'), result('a')] + // Every event created so far precedes the fork point. + const inherited = init(seq) + expect(fold(parent, inherited)).toBe(inherited) + const own = fold([turn(2), call('b', args, 2), result('b', false, 2)], inherited) + expect(Object.keys(own.view.proposals)).toEqual(['b']) + expect(own.view.decisions).toEqual({}) +}) + +it('records valid decisions as pending status until a status notice acknowledges them', () => { + const [created, malformed, dismissed] = records( + { callId: ToolCallId('a'), action: 'test_action', phase: 'creating', data: { value: 1 } }, + { callId: ToolCallId('a'), action: 'test_action', phase: '' }, + { callId: ToolCallId('b'), action: 'test_action', phase: 'dismissed' }, + ) + const state = fold([created!, malformed!, dismissed!]) + expect(state.view.decisions).toEqual({ + a: { callId: 'a', action: 'test_action', phase: 'creating', data: { value: 1 } }, + b: { callId: 'b', action: 'test_action', phase: 'dismissed' }, + }) + expect(state.pendingStatus).toEqual({ a: created!.seq, b: dismissed!.seq }) + const notice = (through: number): SessionEvent<'user/message'> => ({ type: 'user/message', seq: SessionSeq(99), time: 0, surfaceOp: 'append', + data: createUserMessage({ content: [{ type: 'text', text: 'status' }], + source: { kind: 'action-cards', through: SessionSeq(through), form: 'notice', summary: 'status' } }) }) + expect(projection.apply(state, notice(created!.seq)).pendingStatus).toEqual({ b: dismissed!.seq }) + const user: SessionEvent<'user/message'> = { type: 'user/message', seq: SessionSeq(100), time: 0, surfaceOp: 'append', + data: createUserMessage({ content: [{ type: 'text', text: 'hi' }], source: { kind: 'user' } }) } + expect(projection.apply(state, user)).toBe(state) +}) + +it('publishes proposals and decisions as one view reused across internal state changes', () => { + const state = fold([turn(1), call('a'), result('a')]) + const view = projection.wire.view(state) + expect(view).toBe(state.view) + expect(projection.wire.viewSchema.parse(view)).toEqual(view) + expect(projection.wire.view(fold([spliced(1), call('pending')], state))).toBe(view) + expect(projection.stateSchema.parse(state)).toEqual(state) +}) diff --git a/packages/experimental/action-cards/tests/status.spec.ts b/packages/experimental/action-cards/tests/status.spec.ts new file mode 100644 index 0000000000..76a6727a95 --- /dev/null +++ b/packages/experimental/action-cards/tests/status.spec.ts @@ -0,0 +1,71 @@ +import { expect, it, vi } from 'vitest' +import { agentEvents, type Agent, type PreStepDecision } from '@deepseek-ai/dsh-agent' +import { ToolCallId } from '@deepseek-ai/dsh-llm' +import { appendPluginRecord } from '@deepseek-ai/dsh-session' +import ActionCards from '../src/index.ts' +import { ACTION, callId, fixture } from './fixture.ts' + +type Fixture = Awaited> + +async function prepare(f: Fixture, agent: Agent = f.agent, signal = new AbortController().signal, admitted?: () => void | Promise) { + const decision = await agentEvents(f.ctx, agent).waterfall('agent/pre-step', { messages: [], turn: 2, step: 1, signal, modelSelection: { provider: 'mock', model: 'mock' } }, + async (): Promise => { await admitted?.(); return { kind: 'enter', messages: [] } }) + return decision.kind === 'reject' ? decision : { ...decision, messages: decision.messages.filter(message => message.source.kind === 'action-cards') } +} + +function enter(agent: Agent, decision: PreStepDecision): void { + if (decision.kind === 'enter') for (const message of decision.messages) agent.session.append('user/message', message, { surfaceOp: 'append' }) +} + +it('reports recorded decisions once on the next request, with each kind guidance once, without waking the conversation', async () => { + const f = await fixture({ statusGuidance: 'Failed runs can be retried from their card.', statusFields: ['reason'] }) + const followup = vi.spyOn(f.agent, 'followup') + expect(await prepare(f)).toEqual({ kind: 'enter', messages: [] }) + f.propose() + f.propose(ToolCallId('call-card-2'), { action: ACTION, title: 'Second', brief: 'Another card.', details: { value: 'b' } }, f.agent, 2) + expect(await prepare(f)).toEqual({ kind: 'enter', messages: [] }) + f.ctx.actionCards.record(f.agent, { callId, action: ACTION, phase: 'failed', data: { reason: 'timeout', internal: 'x' } }) + f.ctx.actionCards.record(f.agent, { callId: ToolCallId('call-card-2'), action: ACTION, phase: 'dismissed' }) + // A decision logged by a kind that is no longer registered is still reported. + appendPluginRecord(f.agent.session, 'plugin:action-cards/decision', { callId: ToolCallId('call-orphan'), action: 'retired_action', phase: 'done' }) + const decision = await prepare(f) + if (decision.kind !== 'enter') throw new Error('request was rejected') + expect(decision.messages).toHaveLength(1) + expect(decision.messages[0]!.source).toMatchObject({ kind: 'action-cards', form: 'notice', summary: 'Action card status updated' }) + expect(decision.messages[0]!.content).toMatchSnapshot('status notice') + enter(f.agent, decision) + expect(await prepare(f)).toEqual({ kind: 'enter', messages: [] }) + expect(followup).not.toHaveBeenCalled() +}) + +it('keeps undelivered status through rejection, cancellation and plugin reload', async () => { + const f = await fixture() + f.propose() + await f.ctx.actionCards.act(f.agent, callId, 'dismiss') + const cancelled = new AbortController() + await expect(prepare(f, f.agent, cancelled.signal, () => { cancelled.abort() })).rejects.toThrow() + const rejected = f.ctx.on('agent/pre-step', async (): Promise => ({ kind: 'reject' })) + expect((await prepare(f)).kind).toBe('reject') + rejected() + expect(await prepare(f, f.agent, undefined, async () => { await f.cards.dispose() })).toEqual({ kind: 'enter', messages: [] }) + await f.ctx.plugin(ActionCards, { queuedMessageLimit: 3 }) + const decision = await prepare(f) + if (decision.kind !== 'enter') throw new Error('request was rejected') + expect(JSON.stringify(decision.messages[0]!.content)).toContain('dismissed') +}) + +it('adds nothing when cancellation arrives after downstream preparation', async () => { + const f = await fixture() + f.propose() + await f.ctx.actionCards.act(f.agent, callId, 'dismiss') + await f.cards.dispose() + const controller = new AbortController() + // Prepended before the card service returns, so it runs right after the service's own listener. + f.ctx.on('agent/pre-step', async (_event, next) => { + const decision = await next() + controller.abort() + return decision + }, { prepend: true }) + await f.ctx.plugin(ActionCards) + expect(await prepare(f, f.agent, controller.signal)).toEqual({ kind: 'enter', messages: [] }) +}) diff --git a/packages/experimental/action-cards/tests/tool.spec.ts b/packages/experimental/action-cards/tests/tool.spec.ts new file mode 100644 index 0000000000..2bf978cd69 --- /dev/null +++ b/packages/experimental/action-cards/tests/tool.spec.ts @@ -0,0 +1,198 @@ +import { expect, it, vi } from 'vitest' +import { createUserMessage, ToolCallId } from '@deepseek-ai/dsh-llm' +import { SessionId } from '@deepseek-ai/dsh-session' +import type { ToolExecutionToken } from '@deepseek-ai/dsh-tools' +import { executeModelToolFixture, executeToolFixture } from '@deepseek-ai/dsh-tool-testkit' +import type { Agent } from '@deepseek-ai/dsh-agent' +import { MockAdapter, textResponse, toolCallResponse } from '../../../core/agent-loop/tests/mock-adapter.ts' +import { z } from 'zod' +import type { ActionCardKind, ActionCardResult } from '../src/index.ts' +import { ACTION, fixture, INVITATION, proposal, SKILL, type TestDetails } from './fixture.ts' + +type Fixture = Awaited> +const signal = new AbortController().signal +const modelSelection = { provider: 'mock', model: 'mock' } + +/** Run one call through the executor with the step admission the loop would resolve. */ +async function call(f: Fixture, name: string, args: object, id: string, agent: Agent = f.agent, parent?: ToolExecutionToken) { + const toolSet = await f.ctx.tools.resolve({ agent, modelSelection, signal }) + try { + return parent === undefined + ? await executeModelToolFixture(f.ctx, { callId: ToolCallId(id), name, arguments: args, agent, signal, toolSet }) + : await executeToolFixture(f.ctx, { callId: ToolCallId(id), name, arguments: args, agent, signal, toolSet, parent }) + } finally { toolSet.dispose() } +} + +async function declared(f: Fixture, agent: Agent = f.agent): Promise { + const toolSet = await f.ctx.tools.resolve({ agent, modelSelection, signal }) + const visible = toolSet.view(agent).has('propose_action') + toolSet.dispose() + return visible +} + +function text(result: { content: readonly { type: string; text?: string }[] }): string { + return result.content.map(block => block.text ?? '').join('') +} + +it('declares propose_action from the step after a card skill loads, and logs the addition', async () => { + const f = await fixture() + const adapter = new MockAdapter([ + toolCallResponse('call-skill', 'skill', { name: SKILL }), + toolCallResponse('call-card-1', 'propose_action', proposal), + textResponse('The card is below.'), + ]) + f.ctx.llm.registerAdapter(['mock'], adapter) + const agent = await f.harness.create(SessionId('loop'), modelSelection) + expect(await declared(f, agent)).toBe(false) + agent.followup(createUserMessage({ content: [{ type: 'text', text: 'I want a counter.' }], source: { kind: 'user' } })) + await agent.whenIdle() + expect(adapter.requests.map(request => (request.tools ?? []).some(tool => tool.name === 'propose_action'))).toEqual([false, true, true]) + const tool = adapter.requests[1]!.tools!.find(candidate => candidate.name === 'propose_action') + expect(tool).toMatchSnapshot('tool declaration') + expect(agent.session.snapshotEvents().flatMap(event => event.type === 'developer/message' ? event.data.message.content : [])) + .toEqual([{ type: 'tool-addition', toolName: 'propose_action' }]) + expect(await declared(f, agent)).toBe(true) + const result = agent.session.snapshotEvents().find(event => event.type === 'tool/result' && event.data.message.toolCallId === 'call-card-1') + expect(result?.type === 'tool/result' && result.data.message.content).toEqual([ + { type: 'text', text: `Card shown below your answer. Do not restate it. ${INVITATION}` }, + ]) + expect(f.view(agent).proposals['call-card-1']).toEqual({ callId: 'call-card-1', turn: 1, ...proposal }) +}) + +it('declares nothing for other skills and stops declaring when the kind unregisters', async () => { + const f = await fixture() + f.agent.session.append('turn/start', { turn: 1 }) + expect((await call(f, 'skill', { name: 'other-skill' }, 'other')).isError).toBe(false) + expect((await call(f, 'skill', { name: 'missing-skill' }, 'missing')).isError).toBe(true) + expect(await declared(f)).toBe(false) + expect((await call(f, 'propose_action', proposal, 'early')).isError).toBe(true) + expect((await call(f, 'skill', { name: SKILL }, 'load')).isError).toBe(false) + expect((await call(f, 'skill', { name: SKILL }, 'reload')).isError).toBe(false) + expect((await executeToolFixture(f.ctx, { callId: ToolCallId('detached'), name: 'skill', arguments: { name: SKILL }, signal })).isError).toBe(false) + expect(await declared(f)).toBe(true) + await f.kindFiber.dispose() + expect(await declared(f)).toBe(false) + await f.cards.dispose() + expect(f.ctx.tools.get('propose_action')).toBeUndefined() +}) + +it('refuses unknown actions, a skill not loaded in this turn, blank fields, invalid details and nested calls', async () => { + const f = await fixture() + f.agent.session.append('turn/start', { turn: 1 }) + await call(f, 'skill', { name: SKILL }, 'load') + expect(text(await call(f, 'propose_action', { ...proposal, action: 'nope' }, 'unknown'))).toBe('Error: Unknown action "nope". Registered actions: test_action.') + expect(text(await call(f, 'propose_action', { ...proposal, title: ' ' }, 'blank'))).toBe('Error: Write a title and a brief.') + expect(text(await call(f, 'propose_action', { ...proposal, brief: '' }, 'blank-brief'))).toBe('Error: Write a title and a brief.') + expect(text(await call(f, 'propose_action', { ...proposal, details: { value: 1 } }, 'invalid'))) + .toBe('Error: Invalid details: details.value: Invalid input: expected string, received number') + expect(text(await call(f, 'propose_action', proposal, 'nested', f.agent, Symbol('outer') as ToolExecutionToken))) + .toBe('Error: Call propose_action directly, not from code.') + f.agent.session.append('turn/start', { turn: 2 }) + expect(text(await call(f, 'propose_action', proposal, 'stale'))).toBe('Error: Load skill test-skill first, then call propose_action.') + await f.kindFiber.dispose() + await call(f, 'skill', { name: SKILL }, 'load-again') + expect(await declared(f)).toBe(false) + expect(Object.keys(f.view().proposals)).toEqual([]) +}) + +it('accepts one card per turn, admits a retry after a failed result and names the registered actions', async () => { + const f = await fixture() + f.agent.session.append('turn/start', { turn: 1 }) + await call(f, 'skill', { name: SKILL }, 'load') + const blocked = f.ctx.on('tools/post-execute', async (exec, _result, next) => exec.callId === 'blocked' + ? { kind: 'block', feedback: [{ type: 'text', text: 'blocked' }] } : await next()) + expect((await call(f, 'propose_action', proposal, 'blocked')).isError).toBe(true) + blocked() + const accepted = await call(f, 'propose_action', { ...proposal, title: ' Counter ' }, 'accepted') + expect(accepted).toMatchObject({ isError: false, value: { action: ACTION, title: 'Counter', invitation: INVITATION } }) + expect(text(accepted)).toBe(`Card shown below your answer. Do not restate it. ${INVITATION}`) + const second = text(await call(f, 'propose_action', proposal, 'second')) + // Only an accepted call tells the model how to invite the user to its card. + expect(second).toBe('Error: One card per turn; the earlier card stands.') + expect(second).not.toContain(INVITATION) + f.agent.session.append('turn/start', { turn: 2 }) + await call(f, 'skill', { name: SKILL }, 'load-2') + f.propose(ToolCallId('logged'), proposal, f.agent, 2) + expect(text(await call(f, 'propose_action', proposal, 'after-log'))).toBe('Error: One card per turn; the earlier card stands.') + // Disposing the agent releases its activation. + expect(await declared(f)).toBe(true) + await f.agentHandle.dispose() + expect(await declared(f)).toBe(false) +}) + +it('activates nothing for a skill load dispatched from code, so a PTC preset never declares the tool', async () => { + const f = await fixture() + f.agent.session.append('turn/start', { turn: 1 }) + const outer = Symbol('outer') as ToolExecutionToken + expect((await call(f, 'skill', { name: SKILL }, 'nested-load', f.agent, outer)).isError).toBe(false) + expect(await declared(f)).toBe(false) + expect((await call(f, 'propose_action', proposal, 'unadmitted')).isError).toBe(true) + expect((await call(f, 'skill', { name: SKILL }, 'direct-load')).isError).toBe(false) + expect(await declared(f)).toBe(true) +}) + +it('offers cards in the main conversation only: subagent skill loads activate nothing and non-root calls are refused', async () => { + const f = await fixture() + const child = (await f.ctx.agents.create({ sessionId: SessionId('cards-child'), parentAgent: f.agent, + meta: { parentSession: f.agent.id, origin: 'subagent' } })).agent + child.session.append('turn/start', { turn: 1 }) + expect((await call(f, 'skill', { name: SKILL }, 'child-load', child)).isError).toBe(false) + expect(await declared(f, child)).toBe(false) + expect((await call(f, 'propose_action', proposal, 'child-card', child)).isError).toBe(true) + f.agent.session.append('turn/start', { turn: 1 }) + await call(f, 'skill', { name: SKILL }, 'load') + // Admission is fixed when the step starts; the call still checks that its agent is a live root. + const toolSet = await f.ctx.tools.resolve({ agent: f.agent, modelSelection, signal }) + vi.spyOn(f.ctx.agents, 'roots').mockReturnValue([]) + const refused = await executeModelToolFixture(f.ctx, { callId: ToolCallId('detached-root'), name: 'propose_action', arguments: proposal, + agent: f.agent, signal, toolSet }) + toolSet.dispose() + expect(text(refused)).toBe('Error: Cards are offered in the main conversation only.') +}) + +it('names no actions when the last kind unregisters after the step admitted the tool', async () => { + const f = await fixture() + f.agent.session.append('turn/start', { turn: 1 }) + await call(f, 'skill', { name: SKILL }, 'load') + const toolSet = await f.ctx.tools.resolve({ agent: f.agent, modelSelection, signal }) + await f.kindFiber.dispose() + const result = await executeModelToolFixture(f.ctx, { callId: ToolCallId('late'), name: 'propose_action', arguments: proposal, + agent: f.agent, signal, toolSet }) + toolSet.dispose() + expect(text(result)).toBe('Error: Unknown action "test_action". Registered actions: none.') +}) + +it('refuses a card while the user has queued messages for later turns, so the log holds no card', async () => { + const f = await fixture() + const queued = () => createUserMessage({ content: [{ type: 'text', text: 'Next question.' }], source: { kind: 'user' } }) + const live: { agent?: Agent } = {} + const adapter = new MockAdapter([ + toolCallResponse('call-skill', 'skill', { name: SKILL }), + () => { + // The user queues two more messages while the model writes its answer. + live.agent!.inbox.append('next-turn', queued()) + live.agent!.inbox.append('next-turn', queued()) + return toolCallResponse('call-card-1', 'propose_action', proposal) + }, + textResponse('Short answer.'), + textResponse('Next answer.'), + textResponse('Last answer.'), + ]) + f.ctx.llm.registerAdapter(['mock'], adapter) + const agent = await f.harness.create(SessionId('queued'), modelSelection) + live.agent = agent + agent.followup(createUserMessage({ content: [{ type: 'text', text: 'I want a counter.' }], source: { kind: 'user' } })) + await agent.whenIdle() + const result = agent.session.snapshotEvents().find(event => event.type === 'tool/result' && event.data.message.toolCallId === 'call-card-1') + expect(result?.type === 'tool/result' && result.data.message).toMatchObject({ isError: true, + content: [{ type: 'text', text: 'Error: The user has queued more messages; show no card and answer briefly.' }] }) + expect(f.view(agent)).toEqual({ proposals: {}, decisions: {} }) +}) + +it('requires every kind to give the invitation its accepted calls return', () => { + const kind = { action: 'other_action', skill: 'other-skill', details: z.object({ value: z.string() }), verbs: {}, + act: () => Promise.resolve({ status: 'accepted' }) } + // @ts-expect-error: a kind without its invitation does not compile. + const incomplete: ActionCardKind = kind + expect(incomplete).toBe(kind) +}) diff --git a/packages/experimental/action-cards/tsconfig.json b/packages/experimental/action-cards/tsconfig.json new file mode 100644 index 0000000000..c5e45d6303 --- /dev/null +++ b/packages/experimental/action-cards/tsconfig.json @@ -0,0 +1,24 @@ +{ + "extends": "../../../tsconfig.base.json", + "compilerOptions": { + "rootDir": "src", + "outDir": "lib/types" + }, + "references": [ + { "path": "../../../vendor/cordis" }, + { "path": "../../typert/protocol" }, + { "path": "../../llm/llm" }, + { "path": "../../core/tools" }, + { "path": "../../core/agent" }, + { "path": "../../core/session" }, + { "path": "../../session/session-projection" }, + { "path": "../../api/remote-access/tsconfig.host.json" }, + { "path": "../../sandbox/sandbox-policy" }, + { "path": "../../util/values" } + ], + "files": [ + "src/index.ts", + "src/types.ts", + "src/projection.ts" + ] +} diff --git a/packages/experimental/action-cards/tsdown.config.ts b/packages/experimental/action-cards/tsdown.config.ts new file mode 100644 index 0000000000..46d896c05a --- /dev/null +++ b/packages/experimental/action-cards/tsdown.config.ts @@ -0,0 +1,15 @@ +import { defineConfig } from 'tsdown' + +/** Build the action card plugin. */ +export default defineConfig([ + { + entry: ['lib/types/index.js'], + outDir: 'lib', + format: ['esm'], + platform: 'node', + target: 'es2024', + fixedExtension: false, + dts: false, + clean: false, + }, +]) diff --git a/packages/experimental/client-ui-action-cards/README.i18n.yaml b/packages/experimental/client-ui-action-cards/README.i18n.yaml new file mode 100644 index 0000000000..50aa80b675 --- /dev/null +++ b/packages/experimental/client-ui-action-cards/README.i18n.yaml @@ -0,0 +1,40 @@ +# Bilingual-pair consistency record for README.md (docs/i18n/README.md): per heading +# section, a hash of its English and Chinese blocks outside code blocks and generated regions. +# After editing either side, bring the other along and re-record with: +# pnpm run verify-translation-pairing --write packages/experimental/client-ui-action-cards/README.md +/: + en: eec43de9002f4ab1 + zh: 0d2fddca5a1f993a +/deepseek-ai-dsh-client-ui-action-cards: + en: c36a554629f96f79 + zh: 0ea837da87938e52 +/deepseek-ai-dsh-client-ui-action-cards/summary: + en: 89ea57734c42a481 + zh: c7a4f5f0b59b78ad +/deepseek-ai-dsh-client-ui-action-cards/table-of-contents: + en: d152484eb41ac6b4 + zh: b00dddf4c88f0a45 +/deepseek-ai-dsh-client-ui-action-cards/use-this-package: + en: 163d385e91a5df38 + zh: 95bdde9e844cca51 +/deepseek-ai-dsh-client-ui-action-cards/use-this-package/register-a-card-body: + en: 9ec9f9f8f3165d3e + zh: 2d8154ec2fe0c9ab +/deepseek-ai-dsh-client-ui-action-cards/understand-the-implementation: + en: 168ae39a83d6f4eb + zh: 312cc12876bca834 +/deepseek-ai-dsh-client-ui-action-cards/further-exploration: + en: 55e4d74a33cdeeb3 + zh: f601d5a4dcf63ec5 +/deepseek-ai-dsh-client-ui-action-cards/model-experience: + en: f80a4b656c108f22 + zh: a3e30f46fa7136ac +/deepseek-ai-dsh-client-ui-action-cards/model-experience/kv-cache-effect: + en: 9cb7131bde146341 + zh: 1c7d1af00f58004d +/deepseek-ai-dsh-client-ui-action-cards/known-limitations-and-deferred-work: + en: df94821940e4e2b8 + zh: 490441ab06b153c9 +/deepseek-ai-dsh-client-ui-action-cards/known-limitations-and-deferred-work/dev-note: + en: e67f633dc6fe7773 + zh: bd8a8936229e035e diff --git a/packages/experimental/client-ui-action-cards/README.md b/packages/experimental/client-ui-action-cards/README.md new file mode 100644 index 0000000000..911beb6afa --- /dev/null +++ b/packages/experimental/client-ui-action-cards/README.md @@ -0,0 +1,89 @@ +--- +description: "Browser half of optional action cards: the card frame below completed answers, the body registry that experiment packages fill, and the one-line process row of propose_action calls." +kind: "package-reference" +--- + +# @deepseek-ai/dsh-client-ui-action-cards + +English | [中文](README.zh.md) + +## Summary + +Users see an optional action card below a completed answer: its title, a brief that folds when long, and the controls of the experiment that proposed it. In the process area, the proposal shows as one row with the card title and whether the call was accepted; a refused call expands to its reason. A card whose experiment provides no controls in this browser offers only Not now. Showing, expanding and folding cards make no model request. + +## Table of Contents + +- [Use this package](#use-this-package) +- [Understand the implementation](#understand-the-implementation) +- [Further Exploration](#further-exploration) +- [Model Experience](#model-experience) +- [Known Limitations and Deferred Work](#known-limitations-and-deferred-work) +- [Dev Note](#dev-note) + +----- + + +## Use this package + +Enable the [Personalized pages](../creation-recommendations/README.md) or [Plugin recommendations](../plugin-recommendations/README.md) bundle; each inserts this package beside its [Host service](../action-cards/README.md). This browser contribution has no configuration fields. + +### Register a card body + +A kind's browser plugin injects `actionCards` and registers its body inside its own `ctx.effect`: `ctx.actionCards.register({ action, locale, Body })` returns the disposer. The frame renders `Body` for cards of that action with the card, its recorded or derived decision, the Session id, the frame's title and folded brief as `summary`, the frame's fold component as `Folded`, an `act(verb, payload?)` bound to the card, and a translator `t` for the body's own locale namespace. The body places `summary` in its own layout and owns its header, controls, status and footer. A second body for the same action is a registration error. + +----- + + +## Understand the implementation + +
+Implementation internals — click to expand + +One `conversation.chat.turnTail` entry reads the `actionCards` Session projection and shows the card admitted in its Turn after that Turn has ended with a final answer; a Turn that ends without answer text shows no card, and should several cards share a Turn, the lowest call id is shown. The frame sets `data-action-card` to the call id and names its region by the card title. Folding counts a wide grapheme, such as a Chinese character, as two Latin-letter widths and folds after about 260; it never measures layout or alters the full text. + +A `tool.call.toolview` entry keyed `propose_action` renders the process row from the recorded call arguments and result. The plugin provides the body registry at its top level, then mounts the generated `actionCards` Remote and registers its dictionaries and slot entries for the same lifetime. + +| Source | Contents | +|---|---| +| [`src/client/mount.ts`](src/client/mount.ts) | Registry, Remote, dictionaries and slot entries | +| [`src/client/Card.tsx`](src/client/Card.tsx) | Turn tail, frame, fold and fallback body | +| [`src/client/ProposeRow.tsx`](src/client/ProposeRow.tsx) | Process row | +| [`src/client/registry.ts`](src/client/registry.ts) | Body registry and body props | +| [`tests/`](tests/) | Placement, frame, fallback, row and registration tests | + +
+ +----- + + +## Further Exploration + +- [Action cards](../action-cards/README.md) — the tool, card state and user actions behind the frame. +- [Personalized page cards](../client-ui-creation-recommendations/README.md) and [recommendation cards](../client-ui-plugin-recommendations/README.md) — the shipped card bodies. +- [Slots](../../client/ui-slots/README.md) — Turn tail and tool row registration. + + +## Model Experience + +Indirectly, through the Host card service: card actions reach it as Remote calls, and it owns the logged tool, decisions and status notice. + +#### KV Cache effect + +Rendering cards and process rows does not change any model request. + +## Known Limitations and Deferred Work + + + +- A card whose action has no registered body offers only Not now, so its experiment's other actions need that experiment's browser plugin. Not now still works after the experiment's Host plugin is turned off. +- The Turn tail shows one card per Turn, matching the Host rule of one card per turn. + + +### Dev Note + +
+Working context for maintainers — click to expand + +None. + +
diff --git a/packages/experimental/client-ui-action-cards/README.zh.md b/packages/experimental/client-ui-action-cards/README.zh.md new file mode 100644 index 0000000000..d0a0181da0 --- /dev/null +++ b/packages/experimental/client-ui-action-cards/README.zh.md @@ -0,0 +1,89 @@ +--- +description: "可选操作卡片的浏览器端:已完成回答下方的卡片框架、供各实验包填入的正文注册表,以及 propose_action 调用的单行过程行。" +kind: "package-reference" +--- + +# @deepseek-ai/dsh-client-ui-action-cards + +[English](README.md) | 中文 + +## 概述 + +用户在已完成的回答下方看到一张可选操作卡片:标题、过长时会折叠的说明,以及提议该卡片的实验所提供的控件。在过程区中,提议显示为一行,带卡片标题和调用是否被接受;被拒绝的调用可以展开查看原因。如果实验在当前浏览器中没有提供控件,卡片只提供“暂不需要”。展示、展开和折叠卡片都不会发起模型请求。 + +## 目录 + +- [使用本包](#use-this-package) +- [实现原理](#understand-the-implementation) +- [延伸阅读](#further-exploration) +- [模型体验](#model-experience) +- [已知限制与后续工作](#known-limitations-and-deferred-work) +- [开发备注](#dev-note) + +----- + + +## 使用本包 + +启用[个性化页面](../creation-recommendations/README.zh.md)或[插件推荐](../plugin-recommendations/README.zh.md) Bundle,它们都会在本包旁插入对应的 [Host 服务](../action-cards/README.zh.md)。本浏览器组件没有配置字段。 + +### 注册卡片正文 + +种类的浏览器插件注入 `actionCards`,并在自己的 `ctx.effect` 中注册正文:`ctx.actionCards.register({ action, locale, Body })` 返回注销函数。框架为该动作的卡片渲染 `Body`,并传入:卡片、已记录或推导出的决定、会话 id、框架渲染的标题与折叠说明 `summary`、框架的折叠组件 `Folded`、绑定到该卡片的 `act(verb, payload?)`,以及正文自己语言命名空间的翻译函数 `t`。正文把 `summary` 放进自己的布局,并负责自己的标题栏、控件、状态和底部操作。为同一动作注册第二个正文属于注册错误。 + +----- + + +## 实现原理 + +
+实现细节——点击展开 + +一个 `conversation.chat.turnTail` 条目读取 `actionCards` 会话投影,在某一轮以最终回答结束后展示该轮接受的卡片;没有回答文本就结束的轮次不显示卡片,若同一轮有多张卡片,显示调用 id 最小的一张。框架把 `data-action-card` 设为调用 id,并以卡片标题命名所在区域。折叠时,宽字素(如汉字)按两个拉丁字母宽度计算,约 260 个单位后折叠;折叠不测量布局,也不改动完整文本。 + +一个以 `propose_action` 为键的 `tool.call.toolview` 条目,根据记录的调用参数和结果渲染过程行。插件在顶层提供正文注册表,然后挂载生成的 `actionCards` Remote,并在同一生命周期内注册词典和插槽条目。 + +| 源码 | 内容 | +|---|---| +| [`src/client/mount.ts`](src/client/mount.ts) | 注册表、Remote、词典与插槽条目 | +| [`src/client/Card.tsx`](src/client/Card.tsx) | 轮次尾部、框架、折叠与兜底正文 | +| [`src/client/ProposeRow.tsx`](src/client/ProposeRow.tsx) | 过程行 | +| [`src/client/registry.ts`](src/client/registry.ts) | 正文注册表与正文属性 | +| [`tests/`](tests/) | 位置、框架、兜底、过程行与注册测试 | + +
+ +----- + + +## 延伸阅读 + +- [操作卡片](../action-cards/README.zh.md) — 框架背后的工具、卡片状态与用户操作。 +- [个性化页面卡片](../client-ui-creation-recommendations/README.zh.md)和[推荐卡片](../client-ui-plugin-recommendations/README.zh.md) — 随产品提供的卡片正文。 +- [插槽](../../client/ui-slots/README.zh.md) — 轮次尾部与工具行的注册。 + + +## 模型体验 + +间接影响来自 Host 卡片服务:卡片操作以 Remote 调用到达该服务,由它负责记录在日志中的工具、决定和状态通知。 + +#### KV Cache 影响 + +渲染卡片和过程行不会改变任何模型请求。 + +## 已知限制与后续工作 + + + +- 动作没有注册正文的卡片只提供“暂不需要”,该实验的其他操作需要它的浏览器插件。即使该实验的 Host 插件已关闭,“暂不需要”仍然可用。 +- 轮次尾部每轮只显示一张卡片,与 Host 每轮一张卡片的规则一致。 + + +### 开发备注 + +
+维护者工作背景——点击展开 + +无。 + +
diff --git a/packages/experimental/client-ui-action-cards/locale/de.json b/packages/experimental/client-ui-action-cards/locale/de.json new file mode 100644 index 0000000000..5a480a2067 --- /dev/null +++ b/packages/experimental/client-ui-action-cards/locale/de.json @@ -0,0 +1,6 @@ +{ + "meta": { + "title": "Anzeige von Aktionskarten", + "description": "Zeigt optionale Aktionskarten unter Antworten mit den eigenen Bedienelementen jedes Experiments an." + } +} diff --git a/packages/experimental/client-ui-action-cards/locale/en.json b/packages/experimental/client-ui-action-cards/locale/en.json new file mode 100644 index 0000000000..d11a646578 --- /dev/null +++ b/packages/experimental/client-ui-action-cards/locale/en.json @@ -0,0 +1,6 @@ +{ + "meta": { + "title": "Action card display", + "description": "Show optional action cards below answers, with each experiment's own controls." + } +} diff --git a/packages/experimental/client-ui-action-cards/locale/es.json b/packages/experimental/client-ui-action-cards/locale/es.json new file mode 100644 index 0000000000..7b5f75daa3 --- /dev/null +++ b/packages/experimental/client-ui-action-cards/locale/es.json @@ -0,0 +1,6 @@ +{ + "meta": { + "title": "Visualización de tarjetas de acción", + "description": "Muestra tarjetas de acción opcionales debajo de las respuestas, con los controles propios de cada experimento." + } +} diff --git a/packages/experimental/client-ui-action-cards/locale/fr.json b/packages/experimental/client-ui-action-cards/locale/fr.json new file mode 100644 index 0000000000..283dd32f1d --- /dev/null +++ b/packages/experimental/client-ui-action-cards/locale/fr.json @@ -0,0 +1,6 @@ +{ + "meta": { + "title": "Affichage des cartes d'action", + "description": "Affiche des cartes d'action facultatives sous les réponses, avec les commandes propres à chaque expérience." + } +} diff --git a/packages/experimental/client-ui-action-cards/locale/ja.json b/packages/experimental/client-ui-action-cards/locale/ja.json new file mode 100644 index 0000000000..01484f8e00 --- /dev/null +++ b/packages/experimental/client-ui-action-cards/locale/ja.json @@ -0,0 +1,6 @@ +{ + "meta": { + "title": "アクションカード表示", + "description": "回答の下に任意のアクションカードを表示します。カードの内容は各実験が提供します。" + } +} diff --git a/packages/experimental/client-ui-action-cards/locale/pt.json b/packages/experimental/client-ui-action-cards/locale/pt.json new file mode 100644 index 0000000000..22534af903 --- /dev/null +++ b/packages/experimental/client-ui-action-cards/locale/pt.json @@ -0,0 +1,6 @@ +{ + "meta": { + "title": "Exibição de cartões de ação", + "description": "Mostra cartões de ação opcionais abaixo das respostas, com os controles próprios de cada experimento." + } +} diff --git a/packages/experimental/client-ui-action-cards/locale/ru.json b/packages/experimental/client-ui-action-cards/locale/ru.json new file mode 100644 index 0000000000..76cac2e24b --- /dev/null +++ b/packages/experimental/client-ui-action-cards/locale/ru.json @@ -0,0 +1,6 @@ +{ + "meta": { + "title": "Отображение карточек действий", + "description": "Показывает необязательные карточки действий под ответами с элементами управления каждого эксперимента." + } +} diff --git a/packages/experimental/client-ui-action-cards/locale/zh.json b/packages/experimental/client-ui-action-cards/locale/zh.json new file mode 100644 index 0000000000..1792abcaf9 --- /dev/null +++ b/packages/experimental/client-ui-action-cards/locale/zh.json @@ -0,0 +1,6 @@ +{ + "meta": { + "title": "操作卡片显示", + "description": "在回答下方显示可选操作卡片,卡片内容由各实验提供。" + } +} diff --git a/packages/experimental/client-ui-action-cards/package.json b/packages/experimental/client-ui-action-cards/package.json new file mode 100644 index 0000000000..2430ec1b50 --- /dev/null +++ b/packages/experimental/client-ui-action-cards/package.json @@ -0,0 +1,71 @@ +{ + "version": "0.2.1-rc.2", + "publishConfig": { + "access": "public" + }, + "type": "module", + "main": "lib/index.js", + "types": "lib/types/index.d.ts", + "license": "MIT", + "name": "@deepseek-ai/dsh-client-ui-action-cards", + "description": "Action cards below completed answers: the shared card frame, kind bodies and the compact tool row", + "repository": { + "type": "git", + "url": "git+https://github.com/deepseek-ai/deepseek-harness.git", + "directory": "packages/experimental/client-ui-action-cards" + }, + "exports": { + ".": { + "types": "./lib/types/index.d.ts", + "default": "./lib/index.js" + }, + "./src/*": "./src/*", + "./package.json": "./package.json", + "./client": { + "types": "./lib/types/client/index.d.ts", + "default": "./lib/client.js" + }, + "./locale/*.json": "./locale/*.json" + }, + "dsh": { + "client": { + "platform": "web", + "inject": [ + "@deepseek-ai/dsh-api-remotes", + "@deepseek-ai/dsh-client-locale", + "@deepseek-ai/dsh-client-ui-conversation", + "@deepseek-ai/dsh-client-ui-chat", + "@deepseek-ai/dsh-client-ui-session", + "@deepseek-ai/dsh-client-ui-tool" + ] + } + }, + "peerDependencies": { + "@deepseek-ai/cordis": "workspace:~" + }, + "devDependencies": { + "@deepseek-ai/cordis": "workspace:~", + "@deepseek-ai/dsh-llm": "workspace:*", + "@deepseek-ai/dsh-session": "workspace:*", + "@deepseek-ai/dsh-client-ui-renderer": "workspace:*", + "@deepseek-ai/dsh-api-remotes": "workspace:*", + "@deepseek-ai/dsh-client-locale": "workspace:*", + "@deepseek-ai/dsh-client-ui-conversation": "workspace:*", + "@deepseek-ai/dsh-client-ui-chat": "workspace:*", + "@deepseek-ai/dsh-client-ui-session": "workspace:*", + "@deepseek-ai/dsh-client-ui-slots": "workspace:*", + "@deepseek-ai/dsh-client-ui-primitives": "workspace:*", + "@deepseek-ai/dsh-client-ui-tool": "workspace:*", + "react": "^18.2.0", + "@deepseek-ai/dsh-action-cards": "workspace:*", + "@deepseek-ai/dsh-typert-protocol": "workspace:*", + "@deepseek-ai/dsh-util-values": "workspace:*", + "@deepseek-ai/dsh-client-test-runtime": "workspace:*" + }, + "files": [ + "locale/*.json", + "lib/index.js", + "lib/client.js", + "lib/types/**/*.d.ts" + ] +} diff --git a/packages/experimental/client-ui-action-cards/src/client/Card.module.css b/packages/experimental/client-ui-action-cards/src/client/Card.module.css new file mode 100644 index 0000000000..cb8fd63989 --- /dev/null +++ b/packages/experimental/client-ui-action-cards/src/client/Card.module.css @@ -0,0 +1,7 @@ +.card { margin: 16px 0; border: 0.5px solid var(--dsw-alias-border-l2); border-radius: var(--dsw-radius-lg); overflow: hidden; background: var(--dsw-alias-bg-layer-1); font-family: var(--dsw-font-family); color: var(--dsw-alias-label-primary); } +.body { padding: 12px 20px 20px; } +.title { display: block; font-size: 16px; font-weight: 500; line-height: 1.5; overflow-wrap: anywhere; } +.text { margin: 8px 0 0; font-size: 13px; line-height: 1.7; white-space: pre-wrap; overflow-wrap: anywhere; color: var(--dsw-alias-label-secondary); } +.toggle { margin: 8px 0 0; padding-inline: 0; height: auto; min-height: 24px; font-size: 12px; } +.status { margin: 12px 0 0; font-size: 12px; line-height: 1.6; color: var(--dsw-alias-label-secondary); } +.footer { display: flex; align-items: center; justify-content: flex-end; gap: 12px; margin-top: 12px; } diff --git a/packages/experimental/client-ui-action-cards/src/client/Card.tsx b/packages/experimental/client-ui-action-cards/src/client/Card.tsx new file mode 100644 index 0000000000..20e6f75700 --- /dev/null +++ b/packages/experimental/client-ui-action-cards/src/client/Card.tsx @@ -0,0 +1,150 @@ +/** The shared card frame below a completed answer: title, folded brief, and the kind's body or a dismiss-only fallback. */ +import { useCallback, useId, useMemo, useState, type ReactNode } from 'react' +import { Button } from '@deepseek-ai/dsh-client-ui-primitives' +import type { HostObservable, InjectFace, PropsLocale, PropsRuntime, Translate } from '@deepseek-ai/dsh-client-ui-slots' +import type { SessionId } from '@deepseek-ai/dsh-session/types' +import type { ToolCallId } from '@deepseek-ai/dsh-llm/brand' +import type { + ActionCardDecision, ActionCardFields, ActionCardProposal, ActionCardResult, ActionCardView, +} from '@deepseek-ai/dsh-action-cards/types' +import type {} from '@deepseek-ai/dsh-client-ui-chat/client' +import type { ActionCardKey } from './locales.ts' +import type { FoldedTextProps, RegisteredBody } from './registry.ts' +import css from './Card.module.css' + +/** Session-bound actions and registered bodies, without a service in the component. */ +export interface TailInjected { + /** + * Send one user action for a card of the displayed Session. + * @param callId - the card's call id. + * @param verb - `dismiss` or one of the kind's verbs. + * @param payload - kind-defined fields. + * @returns the Host result, or undefined when the reply was lost. + */ + act: (callId: ToolCallId, verb: string, payload?: ActionCardFields) => Promise + /** + * Bind a body's own locale namespace. + * @param locale - namespace the body declared. + * @returns its translator. + */ + bind: (locale: string) => Translate + hooks: { bodies: HostObservable> } + sessionId: SessionId +} + +type TailProps = Pick, 'turn' | 'useProjection'> + & PropsLocale<'actionCards'> & InjectFace + +/** Graphemes drawn about twice as wide as a Latin letter: CJK scripts, CJK punctuation and fullwidth forms. */ +const WIDE_GRAPHEME = /^[\p{Script=Han}\p{Script=Hiragana}\p{Script=Katakana}\p{Script=Hangul} -〿＀-⦆¢-₩]/u +/** Preview budget in Latin-letter widths, about two card lines at desktop width. */ +const FOLD_WIDTH = 260 + +/** + * Fold omitted text without measuring layout; a wide grapheme counts twice so Chinese and English fold near the + * same line. The full text is never altered. + * @param props - text and its localized toggle labels. + * @returns the paragraph and, when text was omitted, its toggle. + */ +export function FoldedText({ text, expandLabel, collapseLabel }: FoldedTextProps) { + const id = useId() + const [expanded, setExpanded] = useState(false) + const preview = useMemo(() => { + const compact = text.replace(/\s+/g, ' ').trim() + const characters = Array.from(new Intl.Segmenter(undefined, { granularity: 'grapheme' }).segment(compact), part => part.segment) + let width = 0 + for (const [index, character] of characters.entries()) { + width += WIDE_GRAPHEME.test(character) ? 2 : 1 + if (width > FOLD_WIDTH) return characters.slice(0, index).join('').trimEnd() + '…' + } + return undefined + }, [text]) + return <> +

{expanded || preview === undefined ? text : preview}

+ {preview !== undefined && } + +} + +/** The projection admits at most one card per turn; should several exist, the lowest call id is shown. */ +function proposalOf(view: ActionCardView | undefined, turn: number): ActionCardProposal | undefined { + let found: ActionCardProposal | undefined + for (const proposal of Object.values(view?.proposals ?? {})) { + if (proposal.turn === turn && (found === undefined || proposal.callId < found.callId)) found = proposal + } + return found +} + +function decisionOf(view: ActionCardView | undefined, callId: string): ActionCardDecision | undefined { + return view !== undefined && Object.hasOwn(view.decisions, callId) ? view.decisions[callId] : undefined +} + +/** + * Place the card admitted in a turn after that turn's final answer, once the turn has ended with one. + * @param props - Turn location, projection reader, registered bodies and Session-bound actions. + * @returns the card, or null while the turn is open or when it ended without answer text. + */ +export function ActionCardTail({ turn, useProjection, ...props }: TailProps) { + const view = useProjection('actionCards') + const proposal = proposalOf(view, turn.turn) + return proposal === undefined || turn.end === undefined || turn.data.get('turn-tail')?.closing == null + ? null : +} + +/** + * Render the frame around one card. + * @param props - the card, its state, registered bodies and actions. + * @returns the card section. + */ +export function ActionCard({ proposal, decision, sessionId, act, bind, useBodies, t }: { + proposal: ActionCardProposal + decision: ActionCardDecision | undefined +} & InjectFace & PropsLocale<'actionCards'>) { + const body = useBodies(bodies => bodies.get(proposal.action)) + const send = useCallback((verb: string, payload?: ActionCardFields) => act(proposal.callId, verb, payload), [act, proposal.callId]) + const summary = <> + {proposal.title} + + + // The frame re-renders on a locale change and hands the body a new summary, so the body re-renders with it. + const content = body === undefined + ? + : body.render({ proposal, decision, sessionId, summary, Folded: FoldedText, act: send }, bind(body.locale)) + return
+ {content} +
+} + +/** Phases the fallback can name; other kind phases leave only the summary. */ +const NAMED_PHASES: Readonly> = { dismissed: 'dismissed', replaced: 'replaced' } + +/** Title, brief and Not now for a card whose kind registered no body in this browser. */ +function FallbackBody({ summary, decision, act, t }: { + summary: ReactNode + decision: ActionCardDecision | undefined + act: (verb: string) => Promise +} & PropsLocale<'actionCards'>) { + const [pending, setPending] = useState(false) + const [error, setError] = useState() + const phase = decision === undefined || !Object.hasOwn(NAMED_PHASES, decision.phase) ? undefined : NAMED_PHASES[decision.phase] + const dismiss = async () => { + setPending(true) + setError(undefined) + try { + const result = await act('dismiss') + if (result === undefined) setError('transport') + else if (result.status === 'refused' || result.status === 'unconfirmed') setError('unavailable') + } catch { + // A lost reply does not prove failure; replay supplies the durable decision. + setError('transport') + } finally { setPending(false) } + } + return
+ {summary} + {phase !== undefined &&

{t(phase)}

} + {error !== undefined &&

{t(error)}

} + {decision === undefined &&
+ +
} +
+} diff --git a/packages/experimental/client-ui-action-cards/src/client/ProposeRow.module.css b/packages/experimental/client-ui-action-cards/src/client/ProposeRow.module.css new file mode 100644 index 0000000000..22915510e1 --- /dev/null +++ b/packages/experimental/client-ui-action-cards/src/client/ProposeRow.module.css @@ -0,0 +1,6 @@ +/** The card title stays readable beside the call state. */ +.summary { margin-left: 8px; display: flex; align-items: center; gap: 8px; min-width: 0; color: inherit; font-size: 12px; } +.summary > :first-child { flex-shrink: 0; } +.title { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } +.reason { margin: 8px 0; padding: 12px; border-radius: var(--dsw-radius-lg); background: var(--dsw-alias-bg-layer-1); color: var(--dsw-alias-label-secondary); white-space: pre-wrap; overflow-wrap: anywhere; font-size: 12px; } +.inspect { align-self: flex-start; border: none; padding: 4px 0; background: transparent; color: var(--dsw-alias-link); font: inherit; font-size: 12px; cursor: pointer; } diff --git a/packages/experimental/client-ui-action-cards/src/client/ProposeRow.tsx b/packages/experimental/client-ui-action-cards/src/client/ProposeRow.tsx new file mode 100644 index 0000000000..acd3b1b972 --- /dev/null +++ b/packages/experimental/client-ui-action-cards/src/client/ProposeRow.tsx @@ -0,0 +1,43 @@ +/** One-line process row for a `propose_action` call: the card title and whether the card was shown. */ +import { useState } from 'react' +import { DisclosureRow, IconAppsAddOutlineRegular } from '@deepseek-ai/dsh-client-ui-primitives' +import type { ToolCallViewProps } from '@deepseek-ai/dsh-client-ui-tool/client' +import type { PropsLocale } from '@deepseek-ai/dsh-client-ui-slots' +import css from './ProposeRow.module.css' + +type ProposeRowProps = ToolCallViewProps & PropsLocale<'actionCards'> + +/** Call states with row copy; a preparing call has no complete arguments yet. */ +type RowState = 'preparing' | 'running' | 'ok' | 'error' | 'stopped' + +function stateOf(props: ProposeRowProps): RowState { + if (props.phase === 'preparing') return 'preparing' + if (props.phase === 'start') return 'running' + if (props.block.error?.code === 'interrupted') return 'stopped' + return props.block.isError ? 'error' : 'ok' +} + +/** + * Render a `propose_action` call from its recorded arguments and result. Only a refused call expands, to show + * why no card followed the answer. + * @param props - tool call and localized status copy. + * @returns the status row. + */ +export function ProposeRow(props: ProposeRowProps) { + const { block, inspect, t } = props + const [expanded, setExpanded] = useState(false) + const state = stateOf(props) + const title = block.args.text('title') ?? '' + const reason = props.phase === 'result' && props.block.isError + ? props.block.content.flatMap(item => item.type === 'text' ? [item.text] : []).join('\n') + : '' + return
+ } running={state === 'preparing' || state === 'running'} + open={expanded && reason !== ''} expandable={reason !== ''} expandOnRowClick keepContentWhenOpen + onToggle={() => { setExpanded(value => !value) }} + collapsedContent={{t(`row.${state}`)}{title}}> +
{reason}
+ {inspect && } +
+
+} diff --git a/packages/experimental/client-ui-action-cards/src/client/index.ts b/packages/experimental/client-ui-action-cards/src/client/index.ts new file mode 100644 index 0000000000..d8c3a96173 --- /dev/null +++ b/packages/experimental/client-ui-action-cards/src/client/index.ts @@ -0,0 +1,15 @@ +/** Bind generated Remote descriptors to the source-tested browser lifecycle. */ +import type { Context } from '@deepseek-ai/cordis' +import remote from '@deepseek-ai/dsh-action-cards/remote' +import { mountActionCards } from './mount.ts' +export { inject } from './mount.ts' +export type { ActionCardBodyProps, ActionCardDescriptor, ActionCardRegistry, FoldedTextProps } from './registry.ts' + +/** + * Mount action cards and their Remote. + * @param ctx - Client plugin context. + * @returns disposer for every contribution. + */ +export async function apply(ctx: Context): Promise<() => Promise> { + return await mountActionCards(ctx, remote) +} diff --git a/packages/experimental/client-ui-action-cards/src/client/locales.ts b/packages/experimental/client-ui-action-cards/src/client/locales.ts new file mode 100644 index 0000000000..0c98b67158 --- /dev/null +++ b/packages/experimental/client-ui-action-cards/src/client/locales.ts @@ -0,0 +1,82 @@ +/** English card frame, fallback and tool-row copy. */ +export const en = { + expandBrief: 'Show more', collapseBrief: 'Show less', + dismiss: 'Not now', dismissed: 'Dismissed', replaced: 'Replaced', + unavailable: 'This action is unavailable. Check the plugin in Plugins.', + transport: 'Could not confirm the result. Reconnect and check the card before retrying.', + 'row.title': 'Action card', 'row.preparing': 'Preparing a card', 'row.running': 'Proposing', + 'row.ok': 'Proposed', 'row.error': 'Not shown', 'row.stopped': 'Interrupted', 'row.inspect': 'Inspect call', +} + +/** Keys shared by every dictionary. */ +export type ActionCardKey = keyof typeof en + +/** Chinese card frame, fallback and tool-row copy. */ +export const zh: Record = { + expandBrief: '展开', collapseBrief: '收起', + dismiss: '暂不需要', dismissed: '已忽略', replaced: '已替换', + unavailable: '暂时无法执行,请到插件页面查看', + transport: '暂时无法确认结果,请重新连接并查看卡片状态后再重试', + 'row.title': '操作卡片', 'row.preparing': '正在准备卡片', 'row.running': '正在提议', + 'row.ok': '已提议', 'row.error': '未显示', 'row.stopped': '已中断', 'row.inspect': '查看调用', +} + +/** Japanese card frame, fallback and tool-row copy. */ +export const ja: Record = { + expandBrief: 'すべて表示', collapseBrief: '折りたたむ', + dismiss: '今はしない', dismissed: '却下済み', replaced: '置き換え済み', + unavailable: 'この操作は利用できません。プラグイン画面でプラグインを確認してください。', + transport: '結果を確認できませんでした。再接続してカードを確認してから再試行してください。', + 'row.title': 'アクションカード', 'row.preparing': 'カードを準備中', 'row.running': '提案中', + 'row.ok': '提案済み', 'row.error': '表示されませんでした', 'row.stopped': '中断されました', 'row.inspect': '呼び出しを確認', +} + +/** Spanish card frame, fallback and tool-row copy. */ +export const es: Record = { + expandBrief: 'Mostrar más', collapseBrief: 'Mostrar menos', + dismiss: 'Ahora no', dismissed: 'Descartado', replaced: 'Reemplazado', + unavailable: 'Esta acción no está disponible. Revisa el complemento en Complementos.', + transport: 'No se pudo confirmar el resultado. Vuelve a conectarte y revisa la tarjeta antes de reintentar.', + 'row.title': 'Tarjeta de acción', 'row.preparing': 'Preparando una tarjeta', 'row.running': 'Proponiendo', + 'row.ok': 'Propuesto', 'row.error': 'No se mostró', 'row.stopped': 'Interrumpido', 'row.inspect': 'Inspeccionar llamada', +} + +/** French card frame, fallback and tool-row copy. */ +export const fr: Record = { + expandBrief: 'Afficher plus', collapseBrief: 'Afficher moins', + dismiss: 'Pas maintenant', dismissed: 'Ignoré', replaced: 'Remplacé', + unavailable: "Cette action n'est pas disponible. Vérifiez le plugin dans Plugins.", + transport: 'Impossible de confirmer le résultat. Reconnectez-vous et vérifiez la carte avant de réessayer.', + 'row.title': "Carte d'action", 'row.preparing': "Préparation d'une carte", 'row.running': 'Proposition en cours', + 'row.ok': 'Proposé', 'row.error': 'Non affiché', 'row.stopped': 'Interrompu', 'row.inspect': "Inspecter l'appel", +} + +/** German card frame, fallback and tool-row copy. */ +export const de: Record = { + expandBrief: 'Mehr anzeigen', collapseBrief: 'Weniger anzeigen', + dismiss: 'Nicht jetzt', dismissed: 'Verworfen', replaced: 'Ersetzt', + unavailable: 'Diese Aktion ist nicht verfügbar. Prüfen Sie das Plugin unter Plugins.', + transport: 'Das Ergebnis konnte nicht bestätigt werden. Verbinden Sie sich erneut und prüfen Sie die Karte, bevor Sie es erneut versuchen.', + 'row.title': 'Aktionskarte', 'row.preparing': 'Karte wird vorbereitet', 'row.running': 'Wird vorgeschlagen', + 'row.ok': 'Vorgeschlagen', 'row.error': 'Nicht angezeigt', 'row.stopped': 'Unterbrochen', 'row.inspect': 'Aufruf prüfen', +} + +/** Russian card frame, fallback and tool-row copy. */ +export const ru: Record = { + expandBrief: 'Показать больше', collapseBrief: 'Показать меньше', + dismiss: 'Не сейчас', dismissed: 'Отклонено', replaced: 'Заменено', + unavailable: 'Это действие недоступно. Проверьте плагин в разделе «Плагины».', + transport: 'Не удалось подтвердить результат. Переподключитесь и проверьте карточку перед повторной попыткой.', + 'row.title': 'Карточка действия', 'row.preparing': 'Подготовка карточки', 'row.running': 'Предлагается', + 'row.ok': 'Предложено', 'row.error': 'Не показано', 'row.stopped': 'Прервано', 'row.inspect': 'Проверить вызов', +} + +/** Portuguese card frame, fallback and tool-row copy. */ +export const pt: Record = { + expandBrief: 'Mostrar mais', collapseBrief: 'Mostrar menos', + dismiss: 'Agora não', dismissed: 'Dispensado', replaced: 'Substituído', + unavailable: 'Esta ação não está disponível. Verifique o plug-in em Plug-ins.', + transport: 'Não foi possível confirmar o resultado. Reconecte-se e verifique o cartão antes de tentar novamente.', + 'row.title': 'Cartão de ação', 'row.preparing': 'Preparando um cartão', 'row.running': 'Propondo', + 'row.ok': 'Proposto', 'row.error': 'Não exibido', 'row.stopped': 'Interrompido', 'row.inspect': 'Inspecionar chamada', +} diff --git a/packages/experimental/client-ui-action-cards/src/client/mount.ts b/packages/experimental/client-ui-action-cards/src/client/mount.ts new file mode 100644 index 0000000000..9ffc9b4b17 --- /dev/null +++ b/packages/experimental/client-ui-action-cards/src/client/mount.ts @@ -0,0 +1,63 @@ +/** Register the card frame, the tool row, their copy and the body registry. */ +import type { Context } from '@deepseek-ai/cordis' +import type { SessionId } from '@deepseek-ai/dsh-session/types' +import type {} from '@deepseek-ai/dsh-api-remotes/client' +import type {} from '@deepseek-ai/dsh-client-locale/client' +import type {} from '@deepseek-ai/dsh-client-ui-session/client' +import type {} from '@deepseek-ai/dsh-client-ui-renderer/client' +import type {} from '@deepseek-ai/dsh-client-ui-tool/client' +import type {} from '@deepseek-ai/dsh-action-cards/remote' +import type { TypertRemoteContribution } from '@deepseek-ai/dsh-typert-protocol' +import { ActionCardTail, type TailInjected } from './Card.tsx' +import { ProposeRow } from './ProposeRow.tsx' +import { ActionCardRegistry } from './registry.ts' +import { de, en, es, fr, ja, pt, ru, zh, type ActionCardKey } from './locales.ts' + +declare module '@deepseek-ai/dsh-client-ui-slots' { + interface LocaleNamespaceMap { actionCards: ActionCardKey } +} + +declare module '@deepseek-ai/cordis' { + interface Context { + /** Card bodies by action kind; kinds register theirs inside their own `ctx.effect`. */ + actionCards: ActionCardRegistry + } +} + +/** Registries available before this plugin mounts its Remote. */ +export const inject = ['slots', 'locale', 'remote'] + +/** + * Provide the body registry, then mount the Remote and the UI for the same plugin lifetime. + * @param ctx - Client plugin context. + * @param contribution - Generated Remote descriptors supplied by the built entry. + * @returns disposer removing the UI before the Remote contribution. + */ +export async function mountActionCards(ctx: Context, contribution: TypertRemoteContribution): Promise<() => Promise> { + // Built at the plugin's top level: kinds register into it from their own apply. + const registry = new ActionCardRegistry(ctx) + ctx.reflect.provide('actionCards', registry) + const unmount = await ctx.remote.$mount(contribution) + const ui = ctx.inject(['remote.actionCards'], (inner) => { registerUi(inner, registry) }) + try { await ui } catch (error) { await ui.dispose(); await unmount(); throw error } + return async () => { await ui.dispose(); await unmount() } +} + +function registerUi(ctx: Context, registry: ActionCardRegistry): void { + ctx.effect(() => ctx.locale.register('actionCards', { en, zh, ja, es, fr, de, ru, pt }), 'action-cards: locale') + ctx.slots.inject('conversation.chat.turnTail', () => ctx.slots.register({ + name: 'conversation.chat.turnTail', id: '@deepseek-ai/dsh-client-ui-action-cards', locale: 'actionCards', + inject: (sessionId: SessionId): TailInjected => ({ + sessionId, + hooks: { bodies: registry.source }, + bind: locale => ctx.locale.bind(locale), + act: async (callId, verb, payload) => { + const result = await ctx.remote.actionCards.act(sessionId, callId, verb, payload) + return result.ok ? result.value : undefined + }, + }), + }, ActionCardTail)) + ctx.slots.inject('tool.call.toolview', () => ctx.slots.register( + { name: 'tool.call.toolview', key: 'propose_action', locale: 'actionCards' }, ProposeRow, + )) +} diff --git a/packages/experimental/client-ui-action-cards/src/client/registry.ts b/packages/experimental/client-ui-action-cards/src/client/registry.ts new file mode 100644 index 0000000000..4933ee8dbb --- /dev/null +++ b/packages/experimental/client-ui-action-cards/src/client/registry.ts @@ -0,0 +1,106 @@ +/** Card bodies registered by action kind for the shared card frame. */ +import { createElement, type ComponentType, type ReactNode } from 'react' +import type { Context } from '@deepseek-ai/cordis' +import type { HostObservable, LocaleNamespaceMap, Translate, TranslateNS } from '@deepseek-ai/dsh-client-ui-slots' +import type { SessionId } from '@deepseek-ai/dsh-session/types' +import type { + ActionCardDecision, ActionCardFields, ActionCardProposal, ActionCardResult, +} from '@deepseek-ai/dsh-action-cards/types' + +/** Props of the frame's folded text, which bodies reuse for their own long fields. */ +export interface FoldedTextProps { + readonly text: string + readonly expandLabel: string + readonly collapseLabel: string +} + +/** Everything a kind's body receives from the shared frame. */ +export interface ActionCardBodyProps> { + /** The card as admitted; the body validates `details` with its own schema. */ + readonly proposal: ActionCardProposal + /** Recorded or derived card state; undefined while the card is unstarted. */ + readonly decision: ActionCardDecision | undefined + readonly sessionId: SessionId + /** Title and folded brief rendered by the frame; the body places them in its layout. */ + readonly summary: ReactNode + /** The frame's width-weighted text fold. */ + readonly Folded: ComponentType + /** + * Send one user action for this card. + * @param verb - `dismiss` or one of the kind's Host verbs. + * @param payload - fields the kind's Host handler validates. + * @returns the Host result, or undefined when the reply was lost. + */ + readonly act: (verb: string, payload?: ActionCardFields) => Promise + /** Translate the body's own locale namespace. */ + readonly t: TranslateNS +} + +/** Browser half of one action kind. */ +export interface ActionCardDescriptor> { + /** Value of the card's `action`. */ + readonly action: string + /** Locale namespace the body's copy belongs to. */ + readonly locale: N + /** Kind-owned header, controls, status and footer. */ + readonly Body: ComponentType> +} + +/** Frame-supplied body props before the kind's translator is bound. */ +export type ActionCardFrameProps = Omit, 't'> + +/** A registered body with its namespace type erased. */ +export interface RegisteredBody { + readonly locale: string + /** + * Render the body. + * @param props - frame-supplied props. + * @param t - translator bound to {@link locale}. + * @returns the body element. + */ + readonly render: (props: ActionCardFrameProps, t: Translate) => ReactNode +} + +/** Card bodies by action; the frame renders a dismiss-only fallback for an action without one. */ +export class ActionCardRegistry { + private bodies: ReadonlyMap = new Map() + private readonly listeners = new Set<() => void>() + /** Current bodies by action; the snapshot changes on every registration change. */ + readonly source: HostObservable> = { + getSnapshot: () => this.bodies, + subscribe: (listener) => { + this.listeners.add(listener) + return () => { this.listeners.delete(listener) } + }, + } + + /** @param ctx - Context whose effects own registered bodies. */ + constructor(private readonly ctx: Context) {} + + /** + * Register one kind's body. The caller holds the returned disposer inside its own `ctx.effect`, so the body + * lives exactly as long as the plugin that contributed it. + * @param descriptor - the kind's action, locale namespace and body. + * @returns idempotent disposer. + * @throws when another body already serves the action. + */ + register>(descriptor: ActionCardDescriptor): () => void { + const { action, locale, Body } = descriptor + const body: RegisteredBody = { locale, render: (props, t) => createElement(Body, { ...props, t }) } + const dispose = this.ctx.effect(() => { + if (this.bodies.has(action)) throw new Error(`action-cards: a body for "${action}" is already registered`) + this.publish(new Map([...this.bodies, [action, body]])) + return () => { + const next = new Map(this.bodies) + next.delete(action) + this.publish(next) + } + }, `action-cards: body ${action}`) + return () => { void dispose() } + } + + private publish(bodies: ReadonlyMap): void { + this.bodies = bodies + for (const listener of [...this.listeners]) listener() + } +} diff --git a/packages/experimental/client-ui-action-cards/src/css-modules.d.ts b/packages/experimental/client-ui-action-cards/src/css-modules.d.ts new file mode 100644 index 0000000000..24a27bda3f --- /dev/null +++ b/packages/experimental/client-ui-action-cards/src/css-modules.d.ts @@ -0,0 +1,4 @@ +declare module '*.module.css' { + const classes: Readonly> + export default classes +} diff --git a/packages/experimental/client-ui-action-cards/src/index.ts b/packages/experimental/client-ui-action-cards/src/index.ts new file mode 100644 index 0000000000..8524d1b9ae --- /dev/null +++ b/packages/experimental/client-ui-action-cards/src/index.ts @@ -0,0 +1,4 @@ +/** Browser registration is discovered through the package client declaration. */ + +/** Register the browser-only contribution without Host state. */ +export function apply(): void {} diff --git a/packages/experimental/client-ui-action-cards/tests/browser-plugin.client.spec.ts b/packages/experimental/client-ui-action-cards/tests/browser-plugin.client.spec.ts new file mode 100644 index 0000000000..87f32a57c3 --- /dev/null +++ b/packages/experimental/client-ui-action-cards/tests/browser-plugin.client.spec.ts @@ -0,0 +1,74 @@ +/** Real registries bind actions to the displayed session and remove every contribution on unload. */ +import { isValidElement } from 'react' +import { Context } from '@deepseek-ai/cordis' +import { SlotRegistry } from '@deepseek-ai/dsh-client-ui-renderer/client' +import { LocaleRuntime } from '@deepseek-ai/dsh-client-locale/client' +import { SessionId } from '@deepseek-ai/dsh-session/types' +import { ToolCallId } from '@deepseek-ai/dsh-llm/brand' +import { expect, it, onTestFinished, vi } from 'vitest' +import { mountActionCards, inject } from '../src/client/mount.ts' +import { apply as hostApply } from '../src/index.ts' +import type { TailInjected } from '../src/client/Card.tsx' + +const apply = (ctx: Context) => mountActionCards(ctx, { package: '@deepseek-ai/dsh-action-cards', descriptors: [] }) +const Body = () => null + +it('binds actions to the displayed session, serves registered bodies and removes registrations on unload', async () => { + hostApply() + const ctx = new Context() + onTestFinished(() => ctx.fiber.dispose()) + await ctx.plugin(SlotRegistry) + const slots = ctx.slots + slots.register({ name: 'root', children: { + 'conversation.chat.turnTail': { kind: 'list', scope: 'session' }, + 'tool.call.toolview': { kind: 'keyed', scope: 'session' }, + } }, (_props: { renderSlot?: unknown }) => null) + const act = vi.fn().mockResolvedValue({ ok: true, value: { status: 'accepted' } }) + const unmount = vi.fn(async () => {}) + ctx.provide('remote', { actionCards: { act }, $mount: vi.fn(async () => unmount) }) + ctx.provide('remote.actionCards', { act }) + const locale = new LocaleRuntime(ctx) + ctx.provide('locale', locale) + const fiber = await ctx.plugin({ inject, apply }) + const entry = slots.entries('conversation.chat.turnTail')[0]! + expect(slots.entries('tool.call.toolview').map(item => item.options.key)).toEqual(['propose_action']) + const resolve = entry.inject as NonNullable & ((sessionId: SessionId) => TailInjected) + const bound = resolve(SessionId('session-one')) + expect(bound.sessionId).toBe('session-one') + expect(await bound.act(ToolCallId('card'), 'create', { slot: 'page' })).toEqual({ status: 'accepted' }) + expect(act).toHaveBeenCalledWith('session-one', 'card', 'create', { slot: 'page' }) + act.mockResolvedValueOnce({ ok: false }) + expect(await bound.act(ToolCallId('card'), 'dismiss')).toBeUndefined() + expect(bound.bind('actionCards')('dismiss')).toBe('Not now') + const changes = vi.fn() + const unsubscribe = bound.hooks.bodies.subscribe(changes) + const kind = await ctx.plugin({ inject: ['actionCards'], apply: (inner: Context) => { + inner.effect(() => inner.actionCards.register({ action: 'test_action', locale: 'actionCards', Body })) + } }) + expect([...bound.hooks.bodies.getSnapshot().keys()]).toEqual(['test_action']) + const translate = (key: string) => key + const element = bound.hooks.bodies.getSnapshot().get('test_action')!.render({ proposal: { callId: ToolCallId('card'), turn: 1, + action: 'test_action', title: 'Counter', brief: 'Keep count.', details: {} }, decision: undefined, + sessionId: SessionId('session-one'), summary: null, Folded: Body, act: vi.fn() }, translate) + expect(isValidElement(element) && element.type === Body && (element.props as { t: unknown }).t === translate).toBe(true) + expect(() => ctx.actionCards.register({ action: 'test_action', locale: 'actionCards', Body })).toThrow('a body for "test_action" is already registered') + await kind.dispose() + expect(bound.hooks.bodies.getSnapshot().size).toBe(0) + expect(changes).toHaveBeenCalledTimes(2) + unsubscribe() + const release = ctx.actionCards.register({ action: 'other_action', locale: 'actionCards', Body }) + expect(bound.hooks.bodies.getSnapshot().has('other_action')).toBe(true) + release() + expect(bound.hooks.bodies.getSnapshot().has('other_action')).toBe(false) + expect(changes).toHaveBeenCalledTimes(2) + await fiber.dispose() + expect(unmount).toHaveBeenCalledOnce() + expect(slots.entries('conversation.chat.turnTail')).toHaveLength(0) + expect(slots.entries('tool.call.toolview')).toHaveLength(0) + expect(ctx.get('actionCards')).toBeUndefined() + vi.spyOn(locale, 'register').mockImplementation(() => { throw new Error('registration failed') }) + const failed = ctx.plugin({ inject, apply }) + await expect(failed.await()).rejects.toThrow('registration failed') + await failed.dispose() + expect(unmount).toHaveBeenCalledTimes(2) +}) diff --git a/packages/experimental/client-ui-action-cards/tests/card.client.spec.tsx b/packages/experimental/client-ui-action-cards/tests/card.client.spec.tsx new file mode 100644 index 0000000000..c286a52937 --- /dev/null +++ b/packages/experimental/client-ui-action-cards/tests/card.client.spec.tsx @@ -0,0 +1,141 @@ +// @vitest-environment jsdom +import { memo, type ComponentProps } from 'react' +import { act, cleanup, fireEvent, render, screen } from '@testing-library/react' +import { afterEach, expect, it, vi } from 'vitest' +import { ToolCallId } from '@deepseek-ai/dsh-llm/brand' +import { SessionId, SessionSeq } from '@deepseek-ai/dsh-session/types' +import type { ActionCardDecision, ActionCardResult, ActionCardView } from '@deepseek-ai/dsh-action-cards/types' +import { ActionCard, ActionCardTail, FoldedText } from '../src/client/Card.tsx' +import { makeTranslate } from '@deepseek-ai/dsh-client-test-runtime' +import { en, zh } from '../src/client/locales.ts' +import type { ActionCardBodyProps, RegisteredBody } from '../src/client/registry.ts' + +afterEach(() => { cleanup(); vi.restoreAllMocks(); vi.unstubAllGlobals() }) + +type CardProps = ComponentProps +const callId = ToolCallId('card-1') +const proposal = { callId, turn: 3, action: 'test_action', title: 'Counter', brief: 'Keep my task count visible.', details: { value: 'a' } } + +function props(bodies: ReadonlyMap = new Map(), decision?: ActionCardDecision, chinese = false): CardProps { + return { proposal, decision, sessionId: SessionId('session-1'), + act: vi.fn(async (): Promise => ({ status: 'settled', decision: { callId, action: 'test_action', phase: 'dismissed' } })), + bind: vi.fn(() => (key: string) => `body:${key}`), + useBodies: selector => selector(bodies), + t: makeTranslate(chinese ? zh : en) } +} + +it('places the card admitted in its turn once the turn has ended with an answer, and leaves other turns empty', () => { + type Tail = ComponentProps + const value = props() + let view: ActionCardView | undefined + const turnData = vi.fn().mockReturnValue({ closing: {} }) + const ended = { type: 'turn/end', seq: SessionSeq(1), time: 0, data: { turn: 3, reason: { kind: 'max-tokens' } } } as const + const tail = (end: typeof ended | undefined): Tail => ({ ...value, useProjection: () => view, + turn: { turn: 3, start: undefined, end, status: end === undefined ? 'open' : 'closed', steps: [], + data: { get: turnData, source: () => ({ getSnapshot: () => undefined, subscribe: () => () => {} }) } } }) + const card = (id: string, turn: number) => ({ ...proposal, callId: ToolCallId(id), turn }) + const { rerender } = render() + expect(screen.queryByRole('region')).toBeNull() + view = { decisions: {}, proposals: { earlier: card('earlier', 2) } } + rerender() + expect(screen.queryByRole('region')).toBeNull() + view = { decisions: {}, proposals: { ...view.proposals, b: card('b', 3), a: card('a', 3), c: card('c', 3) } } + rerender() + expect(screen.getAllByRole('region').map(region => region.getAttribute('data-action-card'))).toEqual(['a']) + expect(screen.getByRole('region', { name: 'Counter' }).getAttribute('data-action')).toBe('test_action') + expect(turnData).toHaveBeenCalledWith('turn-tail') + rerender() + expect(screen.queryByRole('region')).toBeNull() + turnData.mockReturnValueOnce({ closing: null }) + rerender() + expect(screen.queryByRole('region')).toBeNull() + view = { ...view, decisions: { a: { callId: ToolCallId('a'), action: 'test_action', phase: 'dismissed' } } } + rerender() + expect(screen.getByRole('status').textContent).toBe(en.dismissed) + // A call id naming an inherited object member reads no decision. + view = { decisions: {}, proposals: { toString: card('toString', 3) } } + rerender() + expect(screen.getByRole('region').getAttribute('data-action-card')).toBe('toString') + expect(screen.getByRole('button', { name: en.dismiss })).toBeTruthy() +}) + +it('renders a registered body with the frame summary, the fold, its own translator and an action bound to the card', async () => { + const seen: ActionCardBodyProps[] = [] + const Body = memo((body: ActionCardBodyProps) => { + seen.push(body) + return
{body.summary} +
+ }) + const bodies = new Map([['test_action', { locale: 'testCard', render: (frame, t) => }]]) + const value = props(bodies, { callId, action: 'test_action', phase: 'running' }) + render() + expect(value.bind).toHaveBeenCalledWith('testCard') + expect(screen.getByRole('region', { name: 'Counter' }).textContent).toContain('Keep my task count visible.') + expect(screen.getByText('Long details')).toBeTruthy() + expect(seen.at(-1)).toMatchObject({ proposal, decision: { phase: 'running' }, sessionId: 'session-1', Folded: FoldedText }) + fireEvent.click(screen.getByRole('button', { name: 'body:start' })) + expect(value.act).toHaveBeenCalledWith(callId, 'start', { value: 'x' }) +}) + +it('offers only Not now for a card whose kind registered no body, and reports refusals and lost replies', async () => { + const value = props(new Map(), undefined, true) + const pending = Promise.withResolvers() + value.act = vi.fn().mockReturnValueOnce(pending.promise).mockResolvedValueOnce(undefined) + .mockRejectedValueOnce(new Error('disconnected')).mockResolvedValueOnce({ status: 'unconfirmed', reason: 'closing' }) + .mockResolvedValueOnce({ status: 'accepted' }) + render() + expect(screen.getByRole('region', { name: 'Counter' }).textContent).toContain('Keep my task count visible.') + const dismiss = screen.getByRole('button', { name: zh.dismiss }) + fireEvent.click(dismiss) + expect(dismiss.hasAttribute('disabled')).toBe(true) + expect(value.act).toHaveBeenCalledWith(callId, 'dismiss', undefined) + await act(async () => { pending.resolve({ status: 'refused', reason: 'unknown' }) }) + expect(screen.getByRole('alert').textContent).toBe(zh.unavailable) + for (const text of [zh.transport, zh.transport, zh.unavailable]) { + fireEvent.click(screen.getByRole('button', { name: zh.dismiss })) + await vi.waitFor(() => { expect(screen.getByRole('alert').textContent).toBe(text) }) + } + fireEvent.click(screen.getByRole('button', { name: zh.dismiss })) + await vi.waitFor(() => { expect(screen.queryByRole('alert')).toBeNull() }) +}) + +it('dismisses a card through the fallback Not now when the Host records the dismissal without the card kind', async () => { + const value = props() + render() + const dismiss = screen.getByRole('button', { name: en.dismiss }) + fireEvent.click(dismiss) + expect(value.act).toHaveBeenCalledWith(callId, 'dismiss', undefined) + await vi.waitFor(() => { expect(dismiss.hasAttribute('disabled')).toBe(false) }) + expect(screen.queryByRole('alert')).toBeNull() +}) + +it.each([['dismissed', en.dismissed], ['replaced', en.replaced], ['finished', undefined], ['toString', undefined]] as const)( + 'shows the fallback status of a %s card without actions', (phase, text) => { + render() + expect(screen.queryByRole('status')?.textContent).toBe(text) + expect(screen.queryByRole('button', { name: en.dismiss })).toBeNull() + }) + +it('only offers expansion for omitted text and preserves the full brief without layout measurement', () => { + vi.stubGlobal('ResizeObserver', undefined) + const short = 'Keep my task count visible.\n\nPersist across conversations.\nVerify removal.' + const view = render() + expect(screen.queryByRole('button', { name: zh.expandBrief })).toBeNull() + expect(screen.getByText(/Keep my task count visible/).textContent).toBe(short) + // 129 wide graphemes (258 units) plus one narrow emoji fill the 260-unit budget; the next grapheme folds. + const prefix = '计'.repeat(129) + '👩🏽‍💻' + const long = prefix + '\n\n保留完整方案,点击创建后交给 Creator。' + view.rerender() + const expand = screen.getByRole('button', { name: zh.expandBrief }) + const paragraph = document.getElementById(expand.getAttribute('aria-controls')!)! + expect(paragraph.textContent).toBe(prefix + '…') + expect(expand.getAttribute('aria-expanded')).toBe('false') + fireEvent.click(expand) + expect(screen.getByRole('button', { name: zh.collapseBrief }).getAttribute('aria-expanded')).toBe('true') + expect(paragraph.textContent).toBe(long) + fireEvent.click(screen.getByRole('button', { name: zh.collapseBrief })) + expect(paragraph.textContent).toBe(prefix + '…') + view.rerender() + expect(screen.queryByRole('button', { name: zh.expandBrief })).toBeNull() + expect(paragraph.textContent).toBe(prefix) +}) diff --git a/packages/experimental/client-ui-action-cards/tests/row.client.spec.tsx b/packages/experimental/client-ui-action-cards/tests/row.client.spec.tsx new file mode 100644 index 0000000000..ed197519ae --- /dev/null +++ b/packages/experimental/client-ui-action-cards/tests/row.client.spec.tsx @@ -0,0 +1,55 @@ +// @vitest-environment jsdom +/** The process row reads the card title from the recorded arguments and expands only a refused call. */ +import { cleanup, fireEvent, render } from '@testing-library/react' +import { afterEach, expect, it, vi } from 'vitest' +import type { StartedToolCall, ToolResultNode } from '@deepseek-ai/dsh-client-ui-chat/client' +import { makeTranslate } from '@deepseek-ai/dsh-client-test-runtime' +import { PartialArguments } from '@deepseek-ai/dsh-util-values' +import { ProposeRow } from '../src/client/ProposeRow.tsx' +import { en } from '../src/client/locales.ts' + +afterEach(cleanup) +type Props = Parameters[0] +const argsRaw = JSON.stringify({ action: 'test_action', title: 'Counter', brief: 'Keep count.', details: {} }) +const running: StartedToolCall = { phase: 'start', args: PartialArguments.fromText(argsRaw), callId: 'c', name: 'propose_action', argsRaw, turn: 1, step: 1, time: 1, subCalls: [] } +const settled: ToolResultNode = { kind: 'tool-result', seq: 2, time: 2, callId: 'c', name: 'propose_action', args: PartialArguments.fromText(argsRaw), + call: { name: 'propose_action', argsRaw }, callTime: 1, content: [{ type: 'text', text: 'Card shown below your answer. Do not restate it. Invite the user.' }], isError: false, subCalls: [] } +function props(block: Props['block'], inspect?: () => void): Props { + return { ...('kind' in block ? { phase: 'result' as const, block } : { phase: block.phase, block }), + callId: 'c', toolName: 'propose_action', openFile: vi.fn(), inspect, t: makeTranslate(en) } as Props +} + +it('shows a proposed card on one line without exposing the model-facing result', () => { + const view = render() + expect(view.container.querySelector('[data-tool="propose_action"]')?.getAttribute('data-state')).toBe('ok') + expect(view.getByText('Proposed')).toBeTruthy() + expect(view.getByText('Counter')).toBeTruthy() + expect(view.queryByRole('button')).toBeNull() + expect(view.container.textContent).not.toContain('Card shown below your answer. Do not restate it. Invite the user.') +}) + +it('explains a refused call and offers call inspection', () => { + const inspect = vi.fn() + const view = render() + expect(view.getByText('Not shown')).toBeTruthy() + const row = view.getByRole('button') + fireEvent.click(row) + expect(view.container.querySelector('pre')?.textContent).toBe('Error: Load skill test-skill first, then call propose_action.') + fireEvent.click(view.getByRole('button', { name: 'Inspect call' })) + expect(inspect).toHaveBeenCalledOnce() + fireEvent.click(row) + expect(row.getAttribute('aria-expanded')).toBe('false') +}) + +it.each([ + ['preparing', { phase: 'preparing', args: PartialArguments.EMPTY, callId: 'c', name: 'propose_action', turn: 1, step: 1, time: 1, subCalls: [] }, 'Preparing a card'], + ['running', running, 'Proposing'], + ['stopped', { ...settled, isError: true, content: [], error: { name: 'Interrupted', code: 'interrupted' } }, 'Interrupted'], +] as const)('renders the %s call stage without claiming a card', (state, block, label) => { + const view = render() + expect(view.container.querySelector('[data-tool="propose_action"]')?.getAttribute('data-state')).toBe(state) + expect(view.getAllByText(label).length).toBeGreaterThan(0) + expect(view.queryByText('Proposed')).toBeNull() + expect(view.queryByRole('button')).toBeNull() +}) diff --git a/packages/experimental/client-ui-action-cards/tsconfig.json b/packages/experimental/client-ui-action-cards/tsconfig.json new file mode 100644 index 0000000000..fec07b193a --- /dev/null +++ b/packages/experimental/client-ui-action-cards/tsconfig.json @@ -0,0 +1,27 @@ +{ + "extends": "../../../tsconfig.base.client.json", + "compilerOptions": { + "rootDir": "src", + "outDir": "lib/types" + }, + "include": [ + "src" + ], + "references": [ + { "path": "../../client/ui-renderer" }, + { "path": "../../../vendor/cordis" }, + { "path": "../../api/remotes/tsconfig.client.json" }, + { "path": "../../client/locale/tsconfig.client.json" }, + { "path": "../../client/ui-conversation/tsconfig.client.json" }, + { "path": "../../client/ui-chat/tsconfig.client.json" }, + { "path": "../../client/ui-session" }, + { "path": "../../client/ui-slots" }, + { "path": "../../client/ui-primitives" }, + { "path": "../../client/ui-tool" }, + { "path": "../../llm/llm" }, + { "path": "../../core/session" }, + { "path": "../action-cards" }, + { "path": "../../typert/protocol" }, + { "path": "../../util/values" } + ] +} diff --git a/packages/experimental/client-ui-action-cards/tsdown.config.ts b/packages/experimental/client-ui-action-cards/tsdown.config.ts new file mode 100644 index 0000000000..71d5bd77f8 --- /dev/null +++ b/packages/experimental/client-ui-action-cards/tsdown.config.ts @@ -0,0 +1,3 @@ +import { clientBundle } from '../../client/tsdown.client.ts' + +export default clientBundle(import.meta.dirname, ['lib/types/index.js']) diff --git a/packages/experimental/client-ui-creation-recommendations/README.i18n.yaml b/packages/experimental/client-ui-creation-recommendations/README.i18n.yaml new file mode 100644 index 0000000000..7abb687876 --- /dev/null +++ b/packages/experimental/client-ui-creation-recommendations/README.i18n.yaml @@ -0,0 +1,40 @@ +# Bilingual-pair consistency record for README.md (docs/i18n/README.md): per heading +# section, a hash of its English and Chinese blocks outside code blocks and generated regions. +# After editing either side, bring the other along and re-record with: +# pnpm run verify-translation-pairing --write packages/experimental/client-ui-creation-recommendations/README.md +/: + en: e332dc3625021ad9 + zh: a6637d7d8696ba5a +/deepseek-ai-dsh-client-ui-creation-recommendations: + en: b4af39c5d085f16e + zh: 19fd311580db7e2b +/deepseek-ai-dsh-client-ui-creation-recommendations/summary: + en: c2b93e3c50f80ecd + zh: da8aa68e686abddd +/deepseek-ai-dsh-client-ui-creation-recommendations/table-of-contents: + en: d152484eb41ac6b4 + zh: b00dddf4c88f0a45 +/deepseek-ai-dsh-client-ui-creation-recommendations/use-this-package: + en: 5f3f7e9eea09ae0c + zh: 378acca8f793cf34 +/deepseek-ai-dsh-client-ui-creation-recommendations/use-this-package/outcomes-and-recovery: + en: 5a3df9cd58832f91 + zh: 2068dca0a62394fe +/deepseek-ai-dsh-client-ui-creation-recommendations/understand-the-implementation: + en: 26b049c5e3f38846 + zh: 15768ae54aec3511 +/deepseek-ai-dsh-client-ui-creation-recommendations/further-exploration: + en: 6543c44016da2cdf + zh: fb0d365a5e411b04 +/deepseek-ai-dsh-client-ui-creation-recommendations/model-experience: + en: 176de8a02931b042 + zh: b1f1228f011ab056 +/deepseek-ai-dsh-client-ui-creation-recommendations/model-experience/kv-cache-effect: + en: 6a708541b49367ae + zh: 5722b25a6a8e6724 +/deepseek-ai-dsh-client-ui-creation-recommendations/known-limitations-and-deferred-work: + en: 93594f90162451d5 + zh: ca27b35469b537be +/deepseek-ai-dsh-client-ui-creation-recommendations/known-limitations-and-deferred-work/dev-note: + en: e67f633dc6fe7773 + zh: 8ec3e73528ddb1a6 diff --git a/packages/experimental/client-ui-creation-recommendations/README.md b/packages/experimental/client-ui-creation-recommendations/README.md new file mode 100644 index 0000000000..a5dd0e2f33 --- /dev/null +++ b/packages/experimental/client-ui-creation-recommendations/README.md @@ -0,0 +1,88 @@ +--- +description: "Show completed-answer creation cards with location choice, Keep and Undo." +kind: "package-reference" +--- + +# @deepseek-ai/dsh-client-ui-creation-recommendations + +English | [中文](README.zh.md) + +## Summary + +Users see one creation proposal below the completed answer. Short plans appear in full; longer ones fold after about two lines and expand on request. A dropdown offers Right sidebar, Session tab, Global page, or Write your own. The cost notice opens until dismissed; this browser remembers the choice across sessions. The information button reopens it. Only Create starts background work; chatting can continue. Progress appears in the header. Keep confirms retention; Undo removes the plugin, and Restore reinstalls it. + +## Table of Contents + +- [Use this package](#use-this-package) +- [Understand the implementation](#understand-the-implementation) +- [Further Exploration](#further-exploration) +- [Model Experience](#model-experience) +- [Known Limitations and Deferred Work](#known-limitations-and-deferred-work) +- [Dev Note](#dev-note) + +----- + + +## Use this package + +Enable the [Personalized pages bundle](../creation-recommendations/README.md). This browser contribution has no configuration fields; it fills the shared [action card frame](../client-ui-action-cards/README.md) for `create_personalized_page` cards. + +### Outcomes and recovery + +An unstarted card stays available until a newer proposal in the same conversation replaces it. The replaced card keeps its history with a Replaced label and no creation or location controls. Supplied initial content appears below the plan as folded data, so the user sees what the Creator will receive. Started tasks retain their progress and retry actions. The shared card projection owns this state, so reload does not reactivate a card. + +Creation, installation, retention and removal receipts come from the Session projection. The card restores them after reconnecting. A lost action reply shows an actionable error; retry uses the Host’s recorded state. Pending requests disable repeated actions. During generation, Cancel stops the child and waits for cleanup before retry. Accepted installations finish without cancellation. The information disclosure contains the source directory and explains that Undo keeps those files. The removed card offers Restore; when the retained files are gone, it asks for a new proposal. + +Write your own reveals a placement field, limited to 1,000 characters; blank input cannot start creation. Choosing a preset ignores that custom text. Accepted requests persist it with the receipt for reconnect and retry, and Creator receives it with the full brief. Editing the draft, opening the menu and reading the cost notice make no model request. Dismissing the notice or choosing Create/Not now acknowledges it in browser-local storage; a new browser or cleared/unavailable storage can show it again. Historical settled cards keep the notice collapsed. + +Plan disclosure depends on omitted text, not rendered height. Long previews collapse whitespace and preserve grapheme boundaries, including combined emoji; expanding restores the original brief. Short plans remain complete at every viewport width. Lines wrap naturally, with no fixed three-line limit, DOM measurement, or ResizeObserver. + +----- + + +## Understand the implementation + +
+Implementation internals — click to expand + +The plugin registers its body with the shared card frame, which places the card after its Turn and supplies the title, the folded brief, the fold component and card-bound actions. The body validates the card's details and recorded data, then renders the header with the cost notice toggle, the location picker, written placement, folded initial content, progress, Keep, Undo and Restore. The cost notice preference is a browser-local store owned by the plugin. The shared Menu handles portal positioning, keyboard selection and dismissal. The body registration and its dictionaries share one plugin lifetime. + +- [Source](src/client/Card.tsx) +- [Tests](tests/) + +
+ +----- + + +## Further Exploration + +- [Creation service](../creation-recommendations/README.md) — creation ownership and persistence. +- [Action card display](../client-ui-action-cards/README.md) — the frame, fold and actions this body uses. +- [Slots](../../client/ui-slots/README.md) — completed-Turn presentation. + + +## Model Experience + +Indirectly, through an accepted Create action: the Host starts Creator with the full brief and selected or human-written placement, without adding browser tools or resuming the parent model. + +#### KV Cache effect + +Rendering and changing the location do not affect the model cache. + +## Known Limitations and Deferred Work + + + +- The displayed brief is model-authored. Necessary existing data travels separately in the proposal’s optional `initialContent`. +- Historical receipts do not continuously track later manual changes on the Plugins page. + + +### Dev Note + +
+Working context for maintainers — click to expand + +None. + +
diff --git a/packages/experimental/client-ui-creation-recommendations/README.zh.md b/packages/experimental/client-ui-creation-recommendations/README.zh.md new file mode 100644 index 0000000000..8a562dc213 --- /dev/null +++ b/packages/experimental/client-ui-creation-recommendations/README.zh.md @@ -0,0 +1,88 @@ +--- +description: "在完整回答末尾显示支持位置选择、保留和撤销的创建卡片。" +kind: "package-reference" +--- + +# @deepseek-ai/dsh-client-ui-creation-recommendations + +[English](README.md) | 中文 + +## 概述 + +用户在完整回答下方看到一条创建建议。短方案完整显示,长方案约两行后折叠,可展开全文。下拉菜单提供右侧栏、会话 Tab、全局页面和自行撰写四项。消耗提示在用户收起前默认展开,本浏览器跨会话记住选择,信息按钮可重新打开。只有点击创建才启动后台任务,主对话可继续。进度显示在顶部,保留用于确认继续使用,撤销则移除插件,恢复会重新安装它。 + +## 目录 + +- [使用本包](#use-this-package) +- [实现原理](#understand-the-implementation) +- [延伸阅读](#further-exploration) +- [模型体验](#model-experience) +- [已知限制与后续工作](#known-limitations-and-deferred-work) +- [开发备注](#dev-note) + +----- + + +## 使用本包 + +启用[个性化页面 Bundle](../creation-recommendations/README.zh.md)。本浏览器组件没有配置字段;它为 `create_personalized_page` 卡片填充共享的[操作卡片框架](../client-ui-action-cards/README.zh.md)。 + +### 操作结果与恢复 + +尚未创建的卡片一直可用,直到同一会话里更新的方案替换它。被替换的卡片保留历史内容和“已替换”标签,没有创建和位置控件。提供的预填内容以折叠形式显示在方案下方,用户能看到交给 Creator 的内容。已开始的任务保留进度和重试操作。共享卡片投影拥有这个状态,所以刷新不会重新激活卡片。 + +创建、安装、保留和移除记录来自 Session 投影,重新连接后会在卡片中恢复。操作回复丢失时显示可采取行动的错误信息;重试以 Host 已记录的状态为准。请求处理中禁用重复操作。生成阶段可取消,等待子任务清理后才能重试;已接纳的安装不再取消。信息说明中包含源码目录,并明确撤销会保留这些文件。已移除的卡片提供“恢复”;保留的文件已不在时,提示需要重新提案。 + +自行撰写会显示位置输入框,最多 1,000 字,空白内容不能启动创建;改选预设位置后不提交自定义文字。已接受的请求将文字存入操作记录,供重连和重试恢复,并连同完整方案交给 Creator。编辑草稿、打开菜单或查看消耗说明都不会调用模型。收起提示或点击创建、暂不需要后,浏览器本地存储记住已读;更换浏览器、清除或无法使用存储时可能再次显示。历史已结束的卡片默认收起提示。 + +是否展开只取决于有没有省略内容,不取决于渲染高度。长方案预览合并空白,并保留组合 emoji 等字素边界;展开后恢复原始全文。短方案在任何窗口宽度都完整显示。文字自然换行,不固定三行,不读取 DOM 尺寸或使用 ResizeObserver。 + +----- + + +## 实现原理 + +
+实现细节 — 点击展开 + +插件把正文注册到共享卡片框架,由框架把卡片放在所属回合之后,并提供标题、折叠后的说明、折叠组件和绑定到卡片的操作。正文先校验卡片的 details 和已记录的数据,再渲染带消耗说明开关的标题栏、位置选择、手写位置、折叠的预填内容、进度,以及保留、撤销和恢复。消耗说明的偏好是插件自有的浏览器本地存储。共享 Menu 处理浮层定位、键盘选择和关闭。正文注册和语言字典使用同一插件生命周期。 + +- [源码](src/client/Card.tsx) +- [测试](tests/) + +
+ +----- + + +## 延伸阅读 + +- [创建服务](../creation-recommendations/README.zh.md) — 创建任务与持久化。 +- [操作卡片显示](../client-ui-action-cards/README.zh.md) — 本正文使用的框架、折叠和操作。 +- [Slots](../../client/ui-slots/README.zh.md) — 回合末尾呈现. + + +## 模型体验 + +间接影响来自用户接受的创建操作:Host 启动 Creator,传入完整方案和选定或手写的位置,不增加浏览器侧模型工具,也不恢复父模型执行。 + +#### KV Cache effect + +渲染卡片和修改位置不会影响模型缓存。 + +## 已知限制与后续工作 + + + +- 展示方案由模型编写,所需已有数据通过方案的可选 `initialContent` 单独交接。 +- 历史记录不会持续跟踪之后在插件页手动进行的修改。 + + +### 开发备注 + +
+维护者工作上下文 — 点击展开 + +无。 + +
diff --git a/packages/experimental/client-ui-creation-recommendations/locale/de.json b/packages/experimental/client-ui-creation-recommendations/locale/de.json new file mode 100644 index 0000000000..e9a5d99164 --- /dev/null +++ b/packages/experimental/client-ui-creation-recommendations/locale/de.json @@ -0,0 +1,6 @@ +{ + "meta": { + "title": "Karten für personalisierte Seiten", + "description": "Zeigt die Vorschlagskarte nach einer Antwort mit Ortswahl, Erstellungsfortschritt, Behalten und Rückgängigmachen an." + } +} diff --git a/packages/experimental/client-ui-creation-recommendations/locale/en.json b/packages/experimental/client-ui-creation-recommendations/locale/en.json new file mode 100644 index 0000000000..05dbfd4e43 --- /dev/null +++ b/packages/experimental/client-ui-creation-recommendations/locale/en.json @@ -0,0 +1,6 @@ +{ + "meta": { + "title": "Personalized page cards", + "description": "Show the proposal card after an answer with location choice, creation progress, retention and undo." + } +} diff --git a/packages/experimental/client-ui-creation-recommendations/locale/es.json b/packages/experimental/client-ui-creation-recommendations/locale/es.json new file mode 100644 index 0000000000..8f7e016149 --- /dev/null +++ b/packages/experimental/client-ui-creation-recommendations/locale/es.json @@ -0,0 +1,6 @@ +{ + "meta": { + "title": "Tarjetas de páginas personalizadas", + "description": "Muestra la tarjeta de propuesta después de una respuesta, con elección de ubicación, progreso de creación, conservación y deshacer." + } +} diff --git a/packages/experimental/client-ui-creation-recommendations/locale/fr.json b/packages/experimental/client-ui-creation-recommendations/locale/fr.json new file mode 100644 index 0000000000..1890ba69ee --- /dev/null +++ b/packages/experimental/client-ui-creation-recommendations/locale/fr.json @@ -0,0 +1,6 @@ +{ + "meta": { + "title": "Cartes de pages personnalisées", + "description": "Affiche la carte de proposition après une réponse, avec le choix de l'emplacement, la progression de la création, la conservation et l'annulation." + } +} diff --git a/packages/experimental/client-ui-creation-recommendations/locale/ja.json b/packages/experimental/client-ui-creation-recommendations/locale/ja.json new file mode 100644 index 0000000000..4e776bc2de --- /dev/null +++ b/packages/experimental/client-ui-creation-recommendations/locale/ja.json @@ -0,0 +1,6 @@ +{ + "meta": { + "title": "パーソナルページのカード", + "description": "回答の末尾に提案カードを表示し、配置の選択、作成の進行状況、保持と取り消しを扱います。" + } +} diff --git a/packages/experimental/client-ui-creation-recommendations/locale/pt.json b/packages/experimental/client-ui-creation-recommendations/locale/pt.json new file mode 100644 index 0000000000..37bd61d6a4 --- /dev/null +++ b/packages/experimental/client-ui-creation-recommendations/locale/pt.json @@ -0,0 +1,6 @@ +{ + "meta": { + "title": "Cartões de páginas personalizadas", + "description": "Mostra o cartão de proposta após uma resposta, com escolha de local, progresso da criação, manutenção e desfazer." + } +} diff --git a/packages/experimental/client-ui-creation-recommendations/locale/ru.json b/packages/experimental/client-ui-creation-recommendations/locale/ru.json new file mode 100644 index 0000000000..d9c778829a --- /dev/null +++ b/packages/experimental/client-ui-creation-recommendations/locale/ru.json @@ -0,0 +1,6 @@ +{ + "meta": { + "title": "Карточки персональных страниц", + "description": "Показывает карточку предложения после ответа с выбором места, ходом создания, сохранением и отменой." + } +} diff --git a/packages/experimental/client-ui-creation-recommendations/locale/zh.json b/packages/experimental/client-ui-creation-recommendations/locale/zh.json new file mode 100644 index 0000000000..debb5f1b11 --- /dev/null +++ b/packages/experimental/client-ui-creation-recommendations/locale/zh.json @@ -0,0 +1,6 @@ +{ + "meta": { + "title": "个性化页面卡片", + "description": "在回答末尾显示方案卡片,含位置选择、创建进度、保留和撤销。" + } +} diff --git a/packages/experimental/client-ui-creation-recommendations/package.json b/packages/experimental/client-ui-creation-recommendations/package.json new file mode 100644 index 0000000000..4d50999cda --- /dev/null +++ b/packages/experimental/client-ui-creation-recommendations/package.json @@ -0,0 +1,64 @@ +{ + "version": "0.2.1-rc.2", + "publishConfig": { + "access": "public" + }, + "type": "module", + "main": "lib/index.js", + "types": "lib/types/index.d.ts", + "license": "MIT", + "name": "@deepseek-ai/dsh-client-ui-creation-recommendations", + "description": "Body of the personalized page card: location choice, creation progress, Keep, Undo and Restore", + "repository": { + "type": "git", + "url": "git+https://github.com/deepseek-ai/deepseek-harness.git", + "directory": "packages/experimental/client-ui-creation-recommendations" + }, + "exports": { + ".": { + "types": "./lib/types/index.d.ts", + "default": "./lib/index.js" + }, + "./src/*": "./src/*", + "./package.json": "./package.json", + "./client": { + "types": "./lib/types/client/index.d.ts", + "default": "./lib/client.js" + }, + "./locale/*.json": "./locale/*.json" + }, + "dsh": { + "client": { + "platform": "web", + "inject": [ + "@deepseek-ai/dsh-client-locale", + "@deepseek-ai/dsh-client-ui-action-cards" + ] + } + }, + "peerDependencies": { + "@deepseek-ai/cordis": "workspace:~" + }, + "devDependencies": { + "@deepseek-ai/cordis": "workspace:~", + "@deepseek-ai/dsh-client-ui-renderer": "workspace:*", + "@deepseek-ai/dsh-client-locale": "workspace:*", + "@deepseek-ai/dsh-client-ui-slots": "workspace:*", + "@deepseek-ai/dsh-client-ui-primitives": "workspace:*", + "react": "^18.2.0", + "@deepseek-ai/dsh-creation-recommendations": "workspace:*", + "@deepseek-ai/dsh-client-store": "workspace:*", + "@deepseek-ai/dsh-client-ui-action-cards": "workspace:*", + "@deepseek-ai/dsh-action-cards": "workspace:*", + "zod": "4.4.3", + "@deepseek-ai/dsh-client-test-runtime": "workspace:*", + "@deepseek-ai/dsh-llm": "workspace:*", + "@deepseek-ai/dsh-session": "workspace:*" + }, + "files": [ + "locale/*.json", + "lib/index.js", + "lib/client.js", + "lib/types/**/*.d.ts" + ] +} diff --git a/packages/experimental/client-ui-creation-recommendations/src/client/Card.module.css b/packages/experimental/client-ui-creation-recommendations/src/client/Card.module.css new file mode 100644 index 0000000000..b1cc0590ec --- /dev/null +++ b/packages/experimental/client-ui-creation-recommendations/src/client/Card.module.css @@ -0,0 +1,28 @@ +.header { display: flex; align-items: center; justify-content: space-between; gap: 8px 12px; padding: 12px 20px 0; font-size: 12px; line-height: 1.5; color: var(--dsw-alias-label-secondary); flex-wrap: wrap; } +.heading { display: inline-flex; align-items: center; gap: 4px; } +.infoButton { width: 28px; padding: 0; color: var(--dsw-alias-label-secondary); } +.info { margin: 12px 20px 0; padding: 12px; border-radius: var(--dsw-radius-md); background: var(--dsw-alias-bg-module-platform); font-size: 13px; line-height: 1.7; overflow-wrap: anywhere; } +.info p { margin: 0; } +.info p + p { margin-top: 4px; } +.info strong { font-weight: 500; } +.phase { color: var(--dsw-alias-label-primary); } +.body { padding: 12px 20px 20px; } +.content { margin-top: 12px; } +.contentLabel { display: block; font-size: 12px; line-height: 1.6; color: var(--dsw-alias-label-secondary); } +.description { margin: 16px 0 0; padding-top: 12px; border-top: 0.5px solid var(--dsw-alias-border-l1); font-size: 12px; line-height: 1.6; color: var(--dsw-alias-label-secondary); overflow-wrap: anywhere; } +.footer { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-top: 12px; flex-wrap: wrap; } +.scope { font-size: 12px; line-height: 1.5; color: var(--dsw-alias-label-secondary); } +.actions { display: flex; align-items: center; gap: 8px; margin-left: auto; } +.status { margin: 12px 0 0; font-size: 12px; line-height: 1.6; color: var(--dsw-alias-label-secondary); } +.controls { display: flex; align-items: center; flex-wrap: wrap; gap: 12px; margin-top: 16px; padding-top: 16px; border-top: 0.5px solid var(--dsw-alias-border-l1); } +.controls .footer { margin: 0 0 0 auto; } +.locationRow { display: flex; align-items: center; gap: 8px; min-width: 0; } +.locationLabel { font-size: 12px; line-height: 1.6; color: var(--dsw-alias-label-secondary); } +.locationButton { gap: 8px; font-size: 13px; } +.locationMenu { width: 264px; max-width: calc(100vw - 24px); } +.locationOption { display: flex; flex-direction: column; gap: 2px; white-space: normal; line-height: 1.5; } +.locationOption > span + span { color: var(--dsw-alias-label-secondary); font-size: 12px; } +.customLocation { display: flex; flex-direction: column; gap: 6px; flex: 1 0 100%; min-width: 0; } +.customLocation textarea { box-sizing: border-box; width: 100%; min-height: 72px; resize: vertical; padding: 8px 10px; border: 0.5px solid var(--dsw-alias-border-l4); border-radius: var(--dsw-radius-md); background: var(--dsw-alias-bg-layer-1); color: var(--dsw-alias-label-primary); font: inherit; font-size: 13px; line-height: 1.7; } +.customLocation textarea:focus-visible { outline: 1px solid var(--dsw-alias-state-business-primary); outline-offset: 1px; } +.customLocation textarea::placeholder { color: var(--dsw-alias-label-secondary); } diff --git a/packages/experimental/client-ui-creation-recommendations/src/client/Card.tsx b/packages/experimental/client-ui-creation-recommendations/src/client/Card.tsx new file mode 100644 index 0000000000..5fffa80e65 --- /dev/null +++ b/packages/experimental/client-ui-creation-recommendations/src/client/Card.tsx @@ -0,0 +1,169 @@ +/** Body of a `create_personalized_page` card: location choice, creation progress, retention, undo and restore. */ +import { useCallback, useId, useState, useSyncExternalStore } from 'react' +import { z } from 'zod' +import { Button, IconChevronDownOutlineRegular, IconWarningTriangleOutlineRegular, Menu, Tooltip } from '@deepseek-ai/dsh-client-ui-primitives' +import type { PropsLocale } from '@deepseek-ai/dsh-client-ui-slots' +import type { SnapshotStore } from '@deepseek-ai/dsh-client-store' +import type { ActionCardBodyProps } from '@deepseek-ai/dsh-client-ui-action-cards/client' +import type { CreationSlot, PageData, PageDetails, PageVerb } from '@deepseek-ai/dsh-creation-recommendations/types' +import type { RecommendationKey } from './locales.ts' +import css from './Card.module.css' + +/** Body props: the frame's card props plus this browser's cost-notice preference. */ +export type PageCardProps = ActionCardBodyProps<'creationRecommendations'> & { readonly infoDismissed: SnapshotStore } + +type LocationChoice = CreationSlot | 'custom' +type CardVerb = PageVerb | 'dismiss' + +const slotSchema = z.enum(['sidebar-right', 'page', 'sidebar-left']) +const detailsSchema = z.object({ slot: slotSchema, initialContent: z.string().optional() }) satisfies z.ZodType +const dataSchema = z.object({ + slot: slotSchema, customLocation: z.string().optional(), bundleName: z.string().optional(), + application: z.enum(['applied', 'restart-required', 'overridden', 'failed', 'cancelled']).optional(), +}) satisfies z.ZodType + +/** Phases with their own status copy. */ +const PHASE_KEYS: readonly RecommendationKey[] = [ + 'dismissed', 'replaced', 'creating', 'installing', 'cancelled', 'installed', 'kept', 'removed', 'failed', 'interrupted', 'unconfirmed', +] +/** Refusal reasons with their own copy; any other reason reads as unavailable. */ +const REASON_KEYS: readonly RecommendationKey[] = [ + 'unknown', 'access-denied', 'not-live-root', 'closing', 'unavailable', 'invalid-location', 'read-only', 'source-missing', +] + +/** Application outcomes have their own copy; the phase copy of failed and cancelled describes creation instead. */ +const APPLICATION_KEYS: Readonly, 'applied'>, RecommendationKey>> = { + 'restart-required': 'restart-required', overridden: 'overridden', failed: 'applicationFailed', cancelled: 'applicationCancelled', +} + +function keyOf(keys: readonly RecommendationKey[], value: string): RecommendationKey | undefined { + return keys.find(key => key === value) +} + +function LocationPicker({ value, disabled, onChange, t }: { + value: LocationChoice + disabled: boolean + onChange: (value: LocationChoice) => void +} & PropsLocale<'creationRecommendations'>) { + const [open, setOpen] = useState(false) + return { setOpen(false) }} + onSelect={(id) => { onChange(id as LocationChoice); setOpen(false) }} + items={(['sidebar-right', 'page', 'sidebar-left', 'custom'] as const).map(id => ({ + id, label: {t(id)}{t(`${id}Hint`)}, + }))} + anchor={} /> +} + +/** + * Display creation, persistent installation, reversible removal and restoration below the frame's summary. + * @param props - card, state, actions, folded-text component and notice preference. + * @returns the card body. + */ +export function PageCardBody({ proposal, decision, summary, Folded, act, infoDismissed, t }: PageCardProps) { + const details = detailsSchema.safeParse(proposal.details) + const parsed = decision === undefined ? undefined : dataSchema.safeParse(decision.data) + const data = parsed?.success === true ? parsed.data : undefined + const phase = decision === undefined ? undefined : keyOf(PHASE_KEYS, decision.phase) + const [selected, setSelected] = useState() + const [custom, setCustom] = useState() + const [info, setInfo] = useState() + const subscribe = useCallback((listener: () => void) => infoDismissed.subscribe(listener), [infoDismissed]) + const noticeDismissed = useSyncExternalStore(subscribe, () => infoDismissed.getSnapshot()) + const infoId = useId() + const customId = useId() + const [pending, setPending] = useState() + const [error, setError] = useState() + if (!details.success) return
{summary}
+ const busy = pending !== undefined + const slot = selected !== undefined && selected !== 'custom' ? selected : data?.slot ?? details.data.slot + const location = selected ?? (data?.customLocation === undefined ? slot : 'custom') + const customLocation = custom ?? data?.customLocation ?? '' + const offer = decision === undefined || phase === 'cancelled' || phase === 'failed' || phase === 'interrupted' || phase === 'unconfirmed' + const infoOpen = info ?? (offer && !noticeDismissed) + const installed = phase === 'installed' || phase === 'kept' + const run = async (verb: CardVerb) => { + setPending(verb) + setError(undefined) + if (verb === 'create' || verb === 'dismiss') { infoDismissed.set(true); setInfo(false) } + const placement = { slot, ...location === 'custom' ? { customLocation: customLocation.trim() } : {} } + try { + const result = await act(verb, verb === 'create' || verb === 'dismiss' ? placement : undefined) + if (result === undefined) setError('transport') + else if (result.status === 'refused' || result.status === 'unconfirmed') setError(keyOf(REASON_KEYS, result.reason) ?? 'unavailable') + } catch { + // A lost reply does not prove failure; replay supplies the durable decision. + setError('transport') + } finally { setPending(undefined) } + } + return <> +
{t('heading')} + + + + + {phase !== undefined && {t(phase)}} +
+ {infoOpen &&
+

{t('creationMethod')}

+

{t('creationCost')}

+

{t('creationBackground')}

+ {data?.bundleName !== undefined &&

{t('sourceRetained')}

} +
} +
+ {summary} + {details.data.initialContent !== undefined &&
+ {t('initialContent')} + +
} + {data?.application !== undefined && data.application !== 'applied' && phase !== 'failed' + &&

{t(APPLICATION_KEYS[data.application])}

} + {error !== undefined &&

{t(error)}

} + {offer ?
+
+ {t('location')} + +
+ {location === 'custom' &&
+ +