feat(experimental): replace unstarted cards, limit queued turns, show content, tighten routing
A newer proposal in the same conversation replaces an unstarted card; the next user message no longer expires it, so one unstarted card exists per conversation (projection state version 7, phase replaced). queuedMessageLimit (default 2) refuses a card while the user has that many messages queued for later turns. The card shows supplied initialContent as folded data below the plan. The personalized-pages skill is rewritten around the chosen scenarios with a shorter catalog entry that loads before any file reading. Paired sampling, five runs per input on the same build: 63/90 to 81/90 cards on the regression set with zero false cards; the log-tab inputs went from one to four of five to five of five with two tool calls each. Regenerated the config, cordis and plugin package catalogs, re-recorded README pairing, and refreshed the Web replay golden.
This commit is contained in:
1 parent
3a8c1daf66
commit
b51e33d5ca
27 files changed
+224
-174
No files matched your search
+14
-1
@@ -1036,6 +1036,20 @@ export interface Config {
|
||||
```
|
||||
<!-- END GENERATED config-catalog:@deepseek-ai/dsh-experimental-computer-use-cua-driver-mcp -->
|
||||
|
||||
<!-- BEGIN GENERATED config-catalog:@deepseek-ai/dsh-experimental-creation-recommendations -->
|
||||
<a id="deepseek-aidsh-experimental-creation-recommendations"></a>
|
||||
|
||||
## `@deepseek-ai/dsh-experimental-creation-recommendations`
|
||||
|
||||
- `refs`: `z` (`zod`)
|
||||
- `source`: [`packages/experimental/creation-recommendations/src/index.ts:34`](../packages/experimental/creation-recommendations/src/index.ts)
|
||||
|
||||
```ts config-catalog
|
||||
/** Validated card limits after schema defaults. */
|
||||
export type Config = z.infer<typeof Config>
|
||||
```
|
||||
<!-- END GENERATED config-catalog:@deepseek-ai/dsh-experimental-creation-recommendations -->
|
||||
|
||||
<!-- BEGIN GENERATED config-catalog:@deepseek-ai/dsh-experimental-cua-macos -->
|
||||
<a id="deepseek-aidsh-experimental-cua-macos"></a>
|
||||
|
||||
@@ -4756,7 +4770,6 @@ These load from a `cordis.yml` entry with no `config:` block; they declare no co
|
||||
| `@deepseek-ai/dsh-experimental-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-experimental-client-ui-voice-input` | — | [`packages/experimental/client-ui-voice-input/src/index.ts`](../packages/experimental/client-ui-voice-input/src/index.ts) |
|
||||
| `@deepseek-ai/dsh-experimental-computer-use-cua-driver-native` | `computerUse` · `tools` · `systemPrompt` | [`packages/experimental/computer-use-cua-driver-native/src/index.ts`](../packages/experimental/computer-use-cua-driver-native/src/index.ts) |
|
||||
| `@deepseek-ai/dsh-experimental-creation-recommendations` | — | [`packages/experimental/creation-recommendations/src/index.ts`](../packages/experimental/creation-recommendations/src/index.ts) |
|
||||
| `@deepseek-ai/dsh-experimental-session-inspector` | — | [`packages/experimental/session-inspector/src/index.ts`](../packages/experimental/session-inspector/src/index.ts) |
|
||||
| `@deepseek-ai/dsh-experimental-tool-worktree` | `tools` · `worktrees` | [`packages/experimental/tool-worktree/src/index.ts`](../packages/experimental/tool-worktree/src/index.ts) |
|
||||
| `@deepseek-ai/dsh-fs-observation-policy` | — | [`packages/fs/fs-observation-policy/src/index.ts`](../packages/fs/fs-observation-policy/src/index.ts) |
|
||||
|
||||
@@ -1038,6 +1038,20 @@ export interface Config {
|
||||
```
|
||||
<!-- END GENERATED config-catalog:@deepseek-ai/dsh-experimental-computer-use-cua-driver-mcp -->
|
||||
|
||||
<!-- BEGIN GENERATED config-catalog:@deepseek-ai/dsh-experimental-creation-recommendations -->
|
||||
<a id="deepseek-aidsh-experimental-creation-recommendations"></a>
|
||||
|
||||
## `@deepseek-ai/dsh-experimental-creation-recommendations`
|
||||
|
||||
- `refs`: `z` (`zod`)
|
||||
- `source`: [`packages/experimental/creation-recommendations/src/index.ts:34`](../packages/experimental/creation-recommendations/src/index.ts)
|
||||
|
||||
```ts config-catalog
|
||||
/** Validated card limits after schema defaults. */
|
||||
export type Config = z.infer<typeof Config>
|
||||
```
|
||||
<!-- END GENERATED config-catalog:@deepseek-ai/dsh-experimental-creation-recommendations -->
|
||||
|
||||
<!-- BEGIN GENERATED config-catalog:@deepseek-ai/dsh-experimental-cua-macos -->
|
||||
<a id="deepseek-aidsh-experimental-cua-macos"></a>
|
||||
|
||||
@@ -4758,7 +4772,6 @@ export interface Config {
|
||||
| `@deepseek-ai/dsh-experimental-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-experimental-client-ui-voice-input` | — | [`packages/experimental/client-ui-voice-input/src/index.ts`](../packages/experimental/client-ui-voice-input/src/index.ts) |
|
||||
| `@deepseek-ai/dsh-experimental-computer-use-cua-driver-native` | `computerUse` · `tools` · `systemPrompt` | [`packages/experimental/computer-use-cua-driver-native/src/index.ts`](../packages/experimental/computer-use-cua-driver-native/src/index.ts) |
|
||||
| `@deepseek-ai/dsh-experimental-creation-recommendations` | — | [`packages/experimental/creation-recommendations/src/index.ts`](../packages/experimental/creation-recommendations/src/index.ts) |
|
||||
| `@deepseek-ai/dsh-experimental-session-inspector` | — | [`packages/experimental/session-inspector/src/index.ts`](../packages/experimental/session-inspector/src/index.ts) |
|
||||
| `@deepseek-ai/dsh-experimental-tool-worktree` | `tools` · `worktrees` | [`packages/experimental/tool-worktree/src/index.ts`](../packages/experimental/tool-worktree/src/index.ts) |
|
||||
| `@deepseek-ai/dsh-fs-observation-policy` | — | [`packages/fs/fs-observation-policy/src/index.ts`](../packages/fs/fs-observation-policy/src/index.ts) |
|
||||
|
||||
@@ -18,8 +18,8 @@
|
||||
en: 1483f3c6b4dc31ef
|
||||
zh: 042566fbead0d9a3
|
||||
/deepseek-ai-dsh-experimental-client-ui-creation-recommendations/use-this-package/outcomes-and-recovery:
|
||||
en: bca286792467af78
|
||||
zh: 51ee275d9306dace
|
||||
en: fa3e5923560422d3
|
||||
zh: df82b4f84b0a7c15
|
||||
/deepseek-ai-dsh-experimental-client-ui-creation-recommendations/understand-the-implementation:
|
||||
en: d6040e6455c911b4
|
||||
zh: d4b967dc0c6dbb6e
|
||||
|
||||
@@ -29,7 +29,7 @@ Enable the [Personalized pages bundle](../creation-recommendations/README.md). T
|
||||
|
||||
### Outcomes and recovery
|
||||
|
||||
Sending another user message expires an unstarted card. It stays in history with an Expired label and no creation or location controls. Started tasks retain their progress and retry actions. The Host projection owns this state, so reload does not reactivate a card.
|
||||
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 Host 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.
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ kind: "package-reference"
|
||||
|
||||
### 操作结果与恢复
|
||||
|
||||
发送下一条人工消息后,尚未创建的卡片失效,保留历史内容和“已失效”标记,移除创建与选位操作。已开始的任务保留进度和重试入口。该状态由 Host 投影负责,刷新不会使卡片重新可用。
|
||||
尚未创建的卡片一直可用,直到同一会话里更新的方案替换它。被替换的卡片保留历史内容和“已替换”标签,没有创建和位置控件。提供的预填内容以折叠形式显示在方案下方,用户能看到交给 Creator 的内容。已开始的任务保留进度和重试操作。Host 投影拥有这个状态,所以刷新不会重新激活卡片。
|
||||
|
||||
创建、安装、保留和移除记录来自 Session 投影,重新连接后会在卡片中恢复。操作回复丢失时显示可采取行动的错误信息;重试以 Host 已记录的状态为准。请求处理中禁用重复操作。生成阶段可取消,等待子任务清理后才能重试;已接纳的安装不再取消。信息说明中包含源码目录,并明确撤销会保留这些文件。
|
||||
|
||||
|
||||
@@ -10,6 +10,8 @@
|
||||
.body { padding: 12px 20px 20px; }
|
||||
.title { display: block; font-size: 16px; font-weight: 500; line-height: 1.5; overflow-wrap: anywhere; }
|
||||
.reason { margin: 8px 0 0; font-size: 13px; line-height: 1.7; white-space: pre-wrap; overflow-wrap: anywhere; color: var(--dsw-alias-label-secondary); }
|
||||
.content { margin-top: 12px; }
|
||||
.contentLabel { display: block; font-size: 12px; line-height: 1.6; color: var(--dsw-alias-label-secondary); }
|
||||
.planToggle { margin: 8px 0 0; padding-inline: 0; height: auto; min-height: 24px; font-size: 12px; }
|
||||
.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; }
|
||||
|
||||
@@ -46,19 +46,19 @@ function LocationPicker({ value, disabled, onChange, t }: {
|
||||
</Button>} />
|
||||
}
|
||||
|
||||
/** Fold omitted content, without measuring layout or changing Creator's brief. */
|
||||
function CreationPlan({ brief, t }: { brief: string } & PropsLocale<'creationRecommendations'>) {
|
||||
/** Fold omitted text without measuring layout; the full text is never altered. */
|
||||
function FoldedText({ text, expandLabel, collapseLabel }: { text: string; expandLabel: string; collapseLabel: string }) {
|
||||
const id = useId()
|
||||
const [expanded, setExpanded] = useState(false)
|
||||
const preview = useMemo(() => {
|
||||
const text = brief.replace(/\s+/g, ' ').trim()
|
||||
const characters = Array.from(new Intl.Segmenter(undefined, { granularity: 'grapheme' }).segment(text), part => part.segment)
|
||||
const compact = text.replace(/\s+/g, ' ').trim()
|
||||
const characters = Array.from(new Intl.Segmenter(undefined, { granularity: 'grapheme' }).segment(compact), part => part.segment)
|
||||
return characters.length > 80 ? characters.slice(0, 80).join('').trimEnd() + '…' : undefined
|
||||
}, [brief])
|
||||
}, [text])
|
||||
return <>
|
||||
<p id={id} className={css.reason}>{expanded || preview === undefined ? brief : preview}</p>
|
||||
<p id={id} className={css.reason}>{expanded || preview === undefined ? text : preview}</p>
|
||||
{preview !== undefined && <Button variant="ghost" size="sm" className={css.planToggle} aria-expanded={expanded} aria-controls={id}
|
||||
onClick={() => { setExpanded(value => !value) }}>{t(expanded ? 'collapsePlan' : 'expandPlan')}</Button>}
|
||||
onClick={() => { setExpanded(value => !value) }}>{expanded ? collapseLabel : expandLabel}</Button>}
|
||||
</>
|
||||
}
|
||||
|
||||
@@ -135,7 +135,11 @@ export function RecommendationCard({ recommendation, useProjection, act, useInfo
|
||||
</div>}
|
||||
<div className={css.body}>
|
||||
<strong className={css.title}>{recommendation.title}</strong>
|
||||
<CreationPlan brief={recommendation.brief} t={t} />
|
||||
<FoldedText text={recommendation.brief} expandLabel={t('expandPlan')} collapseLabel={t('collapsePlan')} />
|
||||
{recommendation.initialContent !== undefined && <div className={css.content}>
|
||||
<span className={css.contentLabel}>{t('initialContent')}</span>
|
||||
<FoldedText text={recommendation.initialContent} expandLabel={t('expandContent')} collapseLabel={t('collapseContent')} />
|
||||
</div>}
|
||||
{decision?.application !== undefined && decision.application !== 'applied'
|
||||
&& <p role="status" className={css.status}>{t(decision.application)}</p>}
|
||||
{error !== undefined && <p role="alert" className={css.status}>{t(error)}</p>}
|
||||
|
||||
@@ -25,8 +25,10 @@ export function recommendationOf(event: { readonly type: string; readonly data:
|
||||
const value = record(card) && card.action === 'create_personalized_page' ? card.data : result.meta.creationRecommendation
|
||||
if (!record(value) || typeof value.callId !== 'string' || value.callId === ''
|
||||
|| typeof value.title !== 'string' || value.title === '' || typeof value.brief !== 'string' || value.brief === ''
|
||||
|| value.slot !== 'sidebar-right' && value.slot !== 'page' && value.slot !== 'sidebar-left') return undefined
|
||||
return { callId: value.callId as ToolCallId, title: value.title, brief: value.brief, slot: value.slot }
|
||||
|| value.slot !== 'sidebar-right' && value.slot !== 'page' && value.slot !== 'sidebar-left'
|
||||
|| value.initialContent !== undefined && typeof value.initialContent !== 'string') return undefined
|
||||
return { callId: value.callId as ToolCallId, title: value.title, brief: value.brief, slot: value.slot,
|
||||
...value.initialContent === undefined ? {} : { initialContent: value.initialContent } }
|
||||
}
|
||||
|
||||
/** Retain proposal data without rendering inside the tool sequence. */
|
||||
|
||||
@@ -6,6 +6,7 @@ export const en = {
|
||||
creationCost: 'Creation may take a while and use a large number of tokens.',
|
||||
creationBackground: 'Click Create to start in the background. You can keep chatting here.',
|
||||
expandPlan: 'View full plan', collapsePlan: 'Collapse plan',
|
||||
initialContent: 'Starts with', expandContent: 'View all content', collapseContent: 'Collapse content',
|
||||
'sidebar-right': 'Right sidebar', page: 'Session tab', 'sidebar-left': 'Global page', custom: 'Write your own',
|
||||
'sidebar-rightHint': 'View alongside this conversation',
|
||||
pageHint: 'Open beside Chat and Trajectory',
|
||||
@@ -15,7 +16,7 @@ export const en = {
|
||||
customPlaceholder: 'For example, a left sidebar entry that opens a full-page calendar',
|
||||
create: 'Create', starting: 'Starting…', dismiss: 'Not now', retry: 'Create again', keep: 'Keep', undo: 'Undo',
|
||||
scope: 'You can remove it at any time',
|
||||
dismissed: 'Dismissed', expired: 'Expired', creating: 'Creating in the background',
|
||||
dismissed: 'Dismissed', replaced: 'Replaced', creating: 'Creating in the background',
|
||||
cancel: 'Cancel creation', cancelling: 'Cancelling…', installing: 'Installing…',
|
||||
sourceRetained: 'Undo keeps the source files in the original workspace’s .dsh-created-plugins/ folder.',
|
||||
installed: 'Installed', kept: 'Kept', removed: 'Removed',
|
||||
@@ -44,6 +45,7 @@ export const zh: Record<RecommendationKey, string> = {
|
||||
creationCost: '创建可能耗时较长,并消耗大量 token',
|
||||
creationBackground: '点击「创建」后在后台运行,你可以继续聊天',
|
||||
expandPlan: '展开方案', collapsePlan: '收起方案',
|
||||
initialContent: '预填内容', expandContent: '展开内容', collapseContent: '收起内容',
|
||||
'sidebar-right': '右侧栏', page: '会话 Tab', 'sidebar-left': '全局页面', custom: '自行撰写',
|
||||
'sidebar-rightHint': '在当前会话旁查看',
|
||||
pageHint: '与「对话」「轨迹」并列',
|
||||
@@ -53,7 +55,7 @@ export const zh: Record<RecommendationKey, string> = {
|
||||
customPlaceholder: '例如:在左侧加一个入口,打开全屏日历',
|
||||
create: '创建', starting: '开始创建…', dismiss: '暂不需要', retry: '重新创建', keep: '保留', undo: '撤销',
|
||||
scope: '可随时移除',
|
||||
dismissed: '已忽略', expired: '已失效', creating: '正在后台创建',
|
||||
dismissed: '已忽略', replaced: '已替换', creating: '正在后台创建',
|
||||
cancel: '取消创建', cancelling: '正在取消…', installing: '正在安装…',
|
||||
sourceRetained: '撤销后,源码仍保留在原工作区的 .dsh-created-plugins/ 文件夹中',
|
||||
installed: '已安装', kept: '已保留', removed: '已移除',
|
||||
@@ -77,6 +79,7 @@ export const ja: Record<RecommendationKey, string> = {
|
||||
creationCost: '作成には時間がかかり、大量のトークンを消費することがあります。',
|
||||
creationBackground: '「作成」をクリックするとバックグラウンドで開始します。ここで会話を続けられます。',
|
||||
expandPlan: 'プランをすべて表示', collapsePlan: 'プランを折りたたむ',
|
||||
initialContent: '初期内容', expandContent: '内容をすべて表示', collapseContent: '内容を折りたたむ',
|
||||
'sidebar-right': '右サイドバー', page: 'セッションタブ', 'sidebar-left': 'グローバルページ', custom: '自分で指定',
|
||||
'sidebar-rightHint': 'この会話の横に表示',
|
||||
pageHint: 'チャットと軌跡の隣に開く',
|
||||
@@ -86,7 +89,7 @@ export const ja: Record<RecommendationKey, string> = {
|
||||
customPlaceholder: '例:左サイドバーに全画面カレンダーを開く項目を追加',
|
||||
create: '作成', starting: '開始しています…', dismiss: '今はしない', retry: '再作成', keep: '保持', undo: '元に戻す',
|
||||
scope: 'いつでも削除できます',
|
||||
dismissed: '却下済み', expired: '期限切れ', creating: 'バックグラウンドで作成中',
|
||||
dismissed: '却下済み', replaced: '置き換え済み', creating: 'バックグラウンドで作成中',
|
||||
cancel: '作成をキャンセル', cancelling: 'キャンセルしています…', installing: 'インストール中…',
|
||||
sourceRetained: '元に戻しても、ソースファイルは元のワークスペースの .dsh-created-plugins/ フォルダーに残ります。',
|
||||
installed: 'インストール済み', kept: '保持済み', removed: '削除済み',
|
||||
|
||||
+19
-1
@@ -120,7 +120,7 @@ it('disables pending decisions and supports dismissal without starting a child',
|
||||
await act(async () => { pending.resolve({ status: 'settled', decision: { callId, phase: 'dismissed', slot: 'sidebar-right' } }) })
|
||||
})
|
||||
|
||||
it.each(['installing', 'dismissed', 'removed', 'expired'] as const)('restores %s without repeat actions', (phase) => {
|
||||
it.each(['installing', 'dismissed', 'removed', 'replaced'] as const)('restores %s without repeat actions', (phase) => {
|
||||
render(<RecommendationCard {...props({ callId, phase, slot: 'sidebar-left' }, false)} />)
|
||||
expect(screen.getByRole('status').textContent).toBe(en[phase])
|
||||
expect(screen.getAllByRole('button')).toEqual([screen.getByRole('button', { name: en.creationInfo })])
|
||||
@@ -233,3 +233,21 @@ it('only offers expansion for omitted content and preserves the full plan withou
|
||||
expect(screen.queryByRole('button', { name: '展开方案' })).toBeNull()
|
||||
expect(paragraph.textContent).toBe(prefix)
|
||||
})
|
||||
|
||||
it('shows supplied initial content as folded data below the plan and omits it otherwise', () => {
|
||||
const value = props()
|
||||
const view = render(<RecommendationCard {...value} />)
|
||||
expect(screen.queryByText('预填内容')).toBeNull()
|
||||
const items = '1. 导出成功\n2. 断网提示清楚'
|
||||
value.recommendation = { ...value.recommendation, initialContent: items }
|
||||
view.rerender(<RecommendationCard {...value} />)
|
||||
expect(screen.getByText('预填内容')).toBeTruthy()
|
||||
expect(screen.getByText(/导出成功/).textContent).toBe(items)
|
||||
expect(screen.queryByRole('button', { name: '展开内容' })).toBeNull()
|
||||
value.recommendation = { ...value.recommendation, initialContent: '条'.repeat(81) }
|
||||
view.rerender(<RecommendationCard {...value} />)
|
||||
const expand = screen.getByRole('button', { name: '展开内容' })
|
||||
fireEvent.click(expand)
|
||||
expect(screen.getByRole('button', { name: '收起内容' }).getAttribute('aria-expanded')).toBe('true')
|
||||
expect(value.act).not.toHaveBeenCalled()
|
||||
})
|
||||
+3
-1
@@ -14,6 +14,8 @@ it('reads identical proposal facts from native and nested PTC results', () => {
|
||||
for (const event of [native, ptc]) {
|
||||
expect(recommendationOf({ ...event, data: { ...event.data, meta: { actionCard: { action: 'create_personalized_page', callId: value.callId, data: value } } } })).toEqual(value)
|
||||
expect(recommendationOf({ ...event, data: { ...event.data, meta: { actionCard: { action: 'another_action', callId: value.callId, data: value } } } })).toBeUndefined()
|
||||
const withContent = { ...value, initialContent: '1. a\n2. b' }
|
||||
expect(recommendationOf({ ...event, data: { ...event.data, meta: { actionCard: { action: 'create_personalized_page', callId: value.callId, data: withContent } } } })).toEqual(withContent)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -24,7 +26,7 @@ it('falls back for unrelated, failed, missing, and malformed data', () => {
|
||||
{ type: 'tool/result', data: { message: { isError: true } } },
|
||||
{ type: 'tool/result', data: { message: {}, meta: [] } },
|
||||
{ type: 'tool/result', data: { message: {}, meta: {} } },
|
||||
...[null, [], {}, { ...value, brief: 3 }, { ...value, callId: '' }, { ...value, title: '' }, { ...value, brief: '' }, { ...value, slot: 'other' }].map(data => ({
|
||||
...[null, [], {}, { ...value, brief: 3 }, { ...value, callId: '' }, { ...value, title: '' }, { ...value, brief: '' }, { ...value, slot: 'other' }, { ...value, initialContent: 3 }].map(data => ({
|
||||
type: 'tool/result', data: { message: {}, meta: { creationRecommendation: data } },
|
||||
})),
|
||||
]) expect(recommendationOf(event)).toBeUndefined()
|
||||
|
||||
@@ -15,14 +15,14 @@
|
||||
en: d152484eb41ac6b4
|
||||
zh: b00dddf4c88f0a45
|
||||
/deepseek-ai-dsh-experimental-creation-recommendations/use-this-package:
|
||||
en: 5f0b97617b171881
|
||||
zh: c01ad497a1e935ae
|
||||
en: ccf2d721cdc97d72
|
||||
zh: e2df54f636088be4
|
||||
/deepseek-ai-dsh-experimental-creation-recommendations/use-this-package/outcomes-and-recovery:
|
||||
en: 2fdafe328be6698b
|
||||
zh: b0795841ae683bfd
|
||||
en: 58a58f545ad72b48
|
||||
zh: 996549efff61b613
|
||||
/deepseek-ai-dsh-experimental-creation-recommendations/understand-the-implementation:
|
||||
en: 3d0449589366c9ca
|
||||
zh: 6360df1aa4a11f3c
|
||||
en: bcccf97c846f8270
|
||||
zh: fd71762c88480175
|
||||
/deepseek-ai-dsh-experimental-creation-recommendations/further-exploration:
|
||||
en: 764927dcbad0e080
|
||||
zh: 9d14cb335a2fb54c
|
||||
|
||||
@@ -25,7 +25,7 @@ For an explicit interface request, a clear implied viewing or editing need, or a
|
||||
<a id="use-this-package"></a>
|
||||
## Use this package
|
||||
|
||||
Open Plugins → More → Experimental plugins and enable Personalized pages. Shipped profiles leave it disabled. Enabling pins it to the main Plugins list; disabling returns it to the secondary page only. It has no configuration fields. An explicit user location takes precedence; otherwise the Agent recommends a location by scope and interaction:
|
||||
Open Plugins → More → Experimental plugins and enable Personalized pages. Shipped profiles leave it disabled. Enabling pins it to the main Plugins list; disabling returns it to the secondary page only. Its one configuration field, `queuedMessageLimit`, is the number of queued next-turn messages at or above which the current turn offers no card; the default is 2. An explicit user location takes precedence; otherwise the Agent recommends a location by scope and interaction:
|
||||
|
||||
| Location | Use |
|
||||
|:--|:--|
|
||||
@@ -45,7 +45,7 @@ For multiple independent needs, guidance selects one proposal across creation an
|
||||
|
||||
### Outcomes and recovery
|
||||
|
||||
An unstarted proposal expires as soon as the next human message is accepted, including a follow-up about its plan. The card remains visible as Expired without creation controls; the Host also refuses stale clicks. Refreshing, switching views and background notices do not expire it. Already-started work keeps its progress and retry controls. Expiration is derived from existing user-message events, not a new persisted decision record.
|
||||
An unstarted proposal stays available across later human messages, including follow-ups about its plan. The next proposal in the same conversation replaces it: the old card shows Replaced without creation controls, and the Host refuses its stale clicks, so one unstarted card exists per conversation. Refreshing, switching views and background notices do not change a card. Already-started work keeps its progress and retry controls. Replacement is derived from the later proposal, not a new persisted decision record. While the user has queued `queuedMessageLimit` or more messages for later turns, the tool refuses to offer a card in the current turn.
|
||||
|
||||
Creation requires a live root conversation and permission to modify the profile. Cancel is available during generation; retry waits for the child’s cleanup. Once installation starts, the card shows Installing and lets the accepted operation finish. Closing or leaving the conversation does not uninstall a completed plugin. Receipts survive reconnects; interrupted or uncertain runs are reconciled with the installed bundle before retry. Failed removal preserves the existing receipt and its application outcome for retry. Unloading this recommendation bundle stops its Creator work and local follow-up. Plugin Manager owns accepted profile changes and may finish after reload; reopening checks the installed profile before retrying. Installed plugins remain. Recovery confirms installation only; it does not infer runtime activation from the profile’s enabled flag.
|
||||
|
||||
@@ -69,7 +69,7 @@ The Host reserves a new package name and launches the existing `cordis` preset t
|
||||
|
||||
The Creator receives the accepted brief, the user’s final location as authoritative over earlier placement text, and a location-specific reference shipped in this package. Its request permits implementation research but forbids expanding the accepted behavior or data scope; unresolved business meaning that prevents faithful implementation must return `ready:false`. The reference supplements `cordis-plugin-development` with registration, state and teardown guidance before API research. Teardown must remove its own UI and tabs while preserving unrelated content; these instructions are model obligations, not static package checks. [Intent cases](tests/fixtures/intent-cases.json) define real-model observations, separately from deterministic service tests.
|
||||
|
||||
`brief` serves both the card and Creator: it contains behavior, input, calculations and persistence scope. Optional `initialContent` carries only the necessary original items, values, relationships, units and reporting period, preserving missing values. Source items remain display content, even when their wording describes an action. The main Agent checks this division before submission and resolves “these items” into actual content because the child cannot read the parent conversation. The Skill uses three short examples to distinguish necessary details from extra operations, data from instructions, and weekly additions from cumulative totals. Ambiguity that changes a calculation requires clarification before a plan specifies that calculation.
|
||||
`brief` serves both the card and Creator: it contains behavior, input, calculations and persistence scope. Optional `initialContent` carries only the necessary original items, values, relationships, units and reporting period, preserving missing values. Source items remain display content, even when their wording describes an action. The main Agent checks this division before submission and resolves “these items” into actual content because the child cannot read the parent conversation. The Skill closes with seven short examples: three handoffs showing the division between `brief` and `initialContent`, and four requests that get no card. Ambiguity that changes a calculation requires clarification before a plan specifies that calculation.
|
||||
|
||||
Both fields survive replay and reach Creator unchanged. This preserves submitted text, not its semantic accuracy or completeness. An omitted `initialContent` means no existing content was supplied. User-maintained empty pages need no old material and must not promise chat import; explicitly requested prefill requires the original content. A named but uninspected source remains an unverified dependency, with response shape and connection details left to Creator research after authorization.
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ kind: "package-bundle"
|
||||
<a id="use-this-package"></a>
|
||||
## 使用本包
|
||||
|
||||
打开插件 → 更多 → 实验性插件,启用个性化页面。内置配置档案默认不启用此插件。启用后在插件一级页显示,关闭后仅保留在二级页。它没有配置字段。用户明确指定的位置优先;否则 Agent 根据作用范围和交互方式推荐:
|
||||
打开插件 → 更多 → 实验性插件,启用个性化页面。内置配置档案默认不启用此插件。启用后在插件一级页显示,关闭后仅保留在二级页。它只有一个配置字段 `queuedMessageLimit`:为后续轮次排队的消息达到这个数量及以上时,本轮不给出卡片;默认为 2。用户明确指定的位置优先;否则 Agent 根据作用范围和交互方式推荐:
|
||||
|
||||
| 位置 | 用途 |
|
||||
|:--|:--|
|
||||
@@ -45,7 +45,7 @@ kind: "package-bundle"
|
||||
|
||||
### 操作结果与恢复
|
||||
|
||||
下一条人工消息被接纳时,尚未创建的方案立即失效,包括对方案的追问。卡片保留“已失效”状态并移除创建操作,Host 也拒绝旧卡点击。刷新、切换页面和后台通知不触发失效。已经开始的任务保留进度和重试入口。失效状态由已有用户消息事件派生,不新增持久化操作记录。
|
||||
尚未创建的方案在后续人工消息之后仍然可用,包括对方案的追问。同一会话里的下一个方案会替换它:旧卡片显示“已替换”,没有创建控件,Host 也会拒绝对它的过时点击,因此每个会话只有一张未开始的卡片。刷新、切换视图和后台通知不会改变卡片。已开始的任务保留进度和重试控件。替换由后来的方案推导得出,不是新的持久化决定记录。当用户为后续轮次排队的消息达到 `queuedMessageLimit` 条及以上时,本轮工具拒绝给出卡片。
|
||||
|
||||
创建要求当前会话是活动的根会话,并且连接有权修改配置档案。生成阶段可取消,子任务清理完毕后才能重试。安装开始后显示“正在安装”,等待已接纳的操作结束。关闭或离开会话不会卸载已完成的插件。重新连接后会恢复操作记录;重试之前会对照已安装插件检查中断或结果不明的创建。移除失败时保留已有记录和实际生效结果,以便重试。卸载推荐插件会停止它负责的 Creator 任务和本地后续处理。已接纳的配置变更由插件管理器负责,可能在重载后完成;重新打开会话时先检查安装状态再允许重试。已安装插件仍然保留。恢复只确认安装,不根据配置中的开启标记推断运行时已生效。
|
||||
|
||||
@@ -69,7 +69,7 @@ Host 预留新包名,通过 `subagents` 启动已有的 `cordis` 预设,传
|
||||
|
||||
Creator 收到已接受的完整方案、优先于早先位置描述的用户最终选位,以及本插件随包提供的位置指南。执行请求允许调研实现细节,但不得扩大已接受的行为和数据范围;业务含义不明导致无法忠实实现时,应返回 `ready:false`。位置指南补充 `cordis-plugin-development`,在 API 调研前提供注册、状态和卸载指引。卸载时应移除自己的界面和页签,保留其他内容;这些是模型执行要求,不是静态包校验。[意图用例](tests/fixtures/intent-cases.json)用于真实模型观察,与确定性的服务测试分开。
|
||||
|
||||
`brief` 同时用于卡片和 Creator,承载行为、输入、计算方法与持久化范围。可选的 `initialContent` 只承载所需的原始条目、数值、对应关系、单位和统计周期,缺失值仍保持缺失。原始条目即使描述了动作,也仍是待展示内容。主 Agent 在提交前检查这项分工,并将“这些条目”等指代补成实际内容,因为子 Agent 无法读取主对话。Skill 用三个短示例区分必要细节与额外操作、数据与要求、当周新增与累计总量。会改变计算结果的口径歧义,需要在方案确定计算方法前澄清。
|
||||
`brief` 同时用于卡片和 Creator,承载行为、输入、计算方法与持久化范围。可选的 `initialContent` 只承载所需的原始条目、数值、对应关系、单位和统计周期,缺失值仍保持缺失。原始条目即使描述了动作,也仍是待展示内容。主 Agent 在提交前检查这项分工,并将“这些条目”等指代补成实际内容,因为子 Agent 无法读取主对话。Skill 末尾有七个短示例:三个交接示例展示 `brief` 与 `initialContent` 的分工,四个不出卡的请求。会改变计算结果的口径歧义,需要在方案确定计算方法前澄清。
|
||||
|
||||
两个字段均可从日志恢复并原样传给 Creator;这保证文本不丢失,不保证语义准确或完整。省略 `initialContent` 表示未提供已有内容。用户自行维护的空白页面无需旧资料,也不得承诺自动导入聊天;明确要求预填时则需要原始内容。已命名但未检查的数据源仍是未验证的依赖,响应格式和连接方式留待授权后的 Creator 调研。
|
||||
|
||||
|
||||
@@ -1,67 +1,59 @@
|
||||
# Personalized pages
|
||||
|
||||
Use this workflow in the main conversation to plan an optional new page or small tool for any task, not just DSH. You decide whether a page helps, check for duplicates, and supply its scope, content and location. `suggest_action` saves the proposal and displays a card; it does not plan or execute it. After the user clicks Create, the background Creator researches implementation details and implements the accepted scope.
|
||||
Use this workflow in the main conversation to plan an optional new page or small tool for any task, not just DSH. You decide whether a page helps, check existing capabilities, and hand the Creator a short plan. `suggest_action` only saves the proposal and shows a card; nothing is built until the user clicks Create. Plan from the conversation and the catalog at the end of this skill. Do not read files, configuration or source to validate a page idea; name an uninspected source as an unverified dependency and leave its details to the Creator.
|
||||
|
||||
Plan from the known conversation and the included catalog. Do not inspect files, configuration, APIs or other sources merely to validate a page idea or catalog match. Name an uninspected source as an unverified dependency; leave its response shape and connection details to Creator. Wanting a page is not an instruction to develop the application shell, even when its checkout is the workspace.
|
||||
## When to propose
|
||||
|
||||
## Choose the smallest useful response
|
||||
Propose a page in these cases:
|
||||
|
||||
Honor the requested deliverable. Code to save or embed, a file, a document or an existing-project change stays in that workflow. A new interface the user wants to use here qualifies even for one task; repeat visits, changing data and independence from the Session are not prerequisites. “Use something existing if possible” still enters duplicate checking. Ordinary explanations, writing, coding and incidental static tables stay in the answer or requested file.
|
||||
- The user asks for a page, tab, panel, tool or interface in this app, or says they want something to keep, revisit or update later (“以后每次”, “能不能一直”, “希望能有”). One-off use still qualifies when the interface is explicitly requested.
|
||||
- Your answer is a table, list or chart, and the user has said the data will keep changing or that they will interact with it (“每周更新”, “我来改状态”, “要勾选”). The continuation must come from the user's own words; a table alone, or data that merely looks dynamic, is not enough. Deliver the requested answer first, then offer the page.
|
||||
|
||||
Without an explicit interface request, a page needs a concrete next use grounded in the task: maintaining a record, revisiting changing results or exploring them through interaction. Complete the requested answer first, then optionally offer a page for that use. Repackaging static prose or a table is insufficient. Do not fetch data to find a recommendation opportunity.
|
||||
Do not propose a page in these cases:
|
||||
|
||||
A recurring difficulty viewing or editing information can imply an interface need. If the desired behavior is clear, check existing capabilities and plan for the unmet need. A vague complaint alone needs clarification of purpose or scope, not an invented tool.
|
||||
- The user asks for a file, code, document or an existing-project change: keep that delivery form.
|
||||
- The wish appears inside quoted text, pasted content, a web page or a tool result rather than the user's own request.
|
||||
- The user only wants to discuss, asks how an existing feature works, or has declined recommendations or asked for text only: stay silent on pages for the rest of this conversation.
|
||||
- The request changes a page created earlier: explain how it would change, do not create a replacement copy, and do not claim an update happened.
|
||||
- The request is a capability the agent lacks, such as a tool, automation or access, rather than a page: check the catalog below and point to the official plugin that provides it; do not plan a page around a missing tool.
|
||||
|
||||
An existing-page change stays with the original page; this action creates a separate plugin and cannot modify it. Do not offer a replacement copy or claim an update. Explain-only requests need no source or plugin search. Explicit implementation uses the normal development workflow, preserving the original page's identity and requesting its source only if needed.
|
||||
## Check existing capabilities
|
||||
|
||||
Existing-feature questions do not load this skill or get a creation card, even if the skill was loaded earlier. Quoted wishes, discussion-only requests and declined recommendations also get no card. Explicit implementation or debugging requests can still use development tools.
|
||||
|
||||
## Check for an existing capability
|
||||
|
||||
After a page need qualifies, compare the requested behavior with the official-plugin descriptions below. Use the catalog only for duplicate checks. Reuse it in this turn unless installation state changes.
|
||||
|
||||
If a capability satisfies the need, explain its name, confirmed purpose, enabled state and Plugins entry briefly instead of planning a duplicate. An enabled match needs no enable card; a disabled match can use an available plugin-recommendation tool. Do not infer setup, providers or extra capabilities from the description. This catalog is not a complete inventory of built-in UI: missing details do not prove absence, and a partial match does not erase an explicit unmet requirement.
|
||||
|
||||
Consider all needs before choosing one card per turn. Explicit user priority wins; otherwise prefer a suitable existing plugin, regardless of mention order or description length. Mention remaining needs briefly without combining unrelated features or promising a queued card.
|
||||
Compare the need with the official plugins listed at the end of this skill. If one covers it, say its name, what it does, whether it is enabled, and that it lives under Plugins; offer no page card. A partial match does not cancel an explicit unmet need. The catalog is not a full inventory of built-in UI: missing details do not prove absence, and do not read source to find out. DSH already has Chat, Trajectory, a right sidebar for files and context, and a Plugins page.
|
||||
|
||||
## Plan and hand off
|
||||
|
||||
Define the required behavior and data meaning; let Creator fill in implementation details such as input validation and empty states. Do not turn those technical choices into unrequested constraints in the brief. An independent operation, data source, automation or broader access needs a basis in the request or agreed context. Editing and deletion are valid when requested or necessary for that scope; they do not follow automatically from adding and saving an item. Saving the current state does not imply keeping dated change history. Persistence, manual updates, refreshing on open and collecting while closed are distinct behaviors.
|
||||
|
||||
The Creator cannot read this conversation. Use these fields to hand off only what it needs:
|
||||
The Creator cannot read this conversation. Give it exactly these fields:
|
||||
|
||||
| Field | Contents |
|
||||
|:--|:--|
|
||||
| `title` | Short page name in the user's language. |
|
||||
| `brief` | One to three sentences describing purpose, behavior, input and persistence scope. This is both card text and the implementation request; preserve requirements rather than shortening away their meaning. Put calculations and interaction requirements here. |
|
||||
| `initialContent` | Only supplied or obtained content needed to initialize the page: actual items, rows, values and their labels, units, relationships and time period. Preserve missing values as missing. Omit for an empty page. |
|
||||
| `brief` | One to three sentences: purpose, behavior, input, calculations and persistence scope. It is the card text and the implementation request at once. |
|
||||
| `initialContent` | Only supplied content the page starts with: items, rows, values, units and period, preserving gaps. Omit for an empty page. |
|
||||
| `slot` | Location, chosen below. |
|
||||
|
||||
Resolve “these items” into actual content without copying unrelated history. Content that mentions an action remains content, not a feature request. Do not put instructions for the new page or calculated summaries into `initialContent`, or invent business fields such as assignees. Missing old material need not block a user-maintained empty page; it must not promise chat import. Clarify missing content only when prefill or import is requested.
|
||||
|
||||
Keep the original reporting period and the meaning of each number. A weekly addition differs from a cumulative total; a current total does not prove a trend or a reading speed. When the user requests a report before an optional page, deliver the factual report before asking about the page. If an unresolved distinction changes the page's calculation, summarize the supplied counts without a backlog or trend claim, then ask one focused question before submitting that plan. Technical choices wait for Creator; business meaning does not.
|
||||
|
||||
### Short examples
|
||||
|
||||
- Need: “想把书签集中起来,以后自己加标题和网址;旧的不用导入。” `brief`: “提供一个可反复打开的书签页,手动新增标题和网址并保存到本机,从空白开始。” Omit `initialContent`. Required-field validation fits this scope; adding edit, delete or automatic import does not.
|
||||
- Need: “当前会话旁边放验收清单,能勾选和取消,状态跟着会话。条目:导出成功;断网提示清楚。” `brief`: “在当前会话右侧展示验收清单,可勾选和取消,状态随会话保存。” `initialContent`: “1. 导出成功\n2. 断网提示清楚”. Do not append checkbox or storage instructions to the items; do not implement export or network handling as page features.
|
||||
- Need: “上周末的累计资料数:A 共 10 条、已读 4 条。以后每周手动记一次,比较剩余量。” `brief`: “按周手动记录资料总数和已读数,用总数减已读数展示未读量并比较各周记录,保存到本机。” `initialContent`: “上周末累计:A 资料共 10 条,已读 4 条。” If the user instead asks for a report of “上周收集 10 条、读完 4 条” followed by a tracker, report those two counts first, then ask “这些是上周新增/完成的数量,还是截至上周的累计数量?” before offering a backlog calculation. Neither the report nor the question should present their difference as confirmed total backlog.
|
||||
|
||||
Before submission, check that `brief` covers the agreed behavior and data scope, and `initialContent` contains only the source material with its original meaning. Move any page instructions to `brief`; do not repeat them in both fields.
|
||||
Keep the plan to what was asked or agreed. Editing, deletion, history, extra fields, data sources and automation need a basis in the request. Resolve “these items” into the actual content. Content that mentions an action stays content. Keep each number's meaning and period; a weekly addition is not a cumulative total. If an unresolved distinction changes a calculation, give the factual answer, ask one question, and propose later.
|
||||
|
||||
## Choose the location
|
||||
|
||||
Honor a location explicitly requested by the user. Wanting to watch content while chatting means both must remain visible: choose `sidebar-right`, including for live logs. Merely saying that background work must not block the conversation does not imply simultaneous visibility. Otherwise:
|
||||
Honor a location the user named. Otherwise:
|
||||
|
||||
- `sidebar-left`: global feature, with a left entry opening a standalone main-area page without Chat/Trajectory tabs; includes global streaming tools.
|
||||
- `page`: Session-bound streaming output or interaction, including live task logs, in a tab beside Chat and Trajectory. Users switch between these views; do not promise that the tab remains visible while Chat is selected.
|
||||
- `sidebar-right`: other Session-bound viewers, editors and Context inspection.
|
||||
- `sidebar-right`: content tied to this Session or task, especially anything to watch while chatting, including live logs.
|
||||
- `sidebar-left`: a global feature or an ongoing personal record used across Sessions, opened from a left entry as a standalone page.
|
||||
- `page`: a Session-bound streaming view or interaction in a tab beside Chat and Trajectory, only when the user does not need it visible while chatting.
|
||||
|
||||
An ongoing personal record defaults to `sidebar-left` unless tied to a task or Session. Supplying data in this conversation alone does not make it Session-bound. Task- or Session-local viewers default to `sidebar-right`. Ask only when missing scope or interaction changes the outcome.
|
||||
Supplying data in this conversation does not make a record Session-bound.
|
||||
|
||||
## Offer without starting creation
|
||||
## Offer without starting
|
||||
|
||||
Plan-only or do-not-start requests permit a proposal, including read-only mode. “先让我确认方案” asks for this reviewable card, not a separate approval question; ask questions only for missing requirements, never to approve creation instead of the card. Only clicking Create starts the background Creator, which builds and hot-installs a new removable plugin while the conversation remains available. The card shows progress, cancellation, retry, removal and a cost/time notice. Do not wait, poll, require a mode switch or refresh, ask for a report-back, or claim installation from a proposal.
|
||||
Call `suggest_action` with `request.action: "create_personalized_page"` and the fields above. One proposal per turn, and one unstarted card per conversation: if a card is still waiting, do not propose another; a new proposal replaces it only when the user changed the plan. Plan-only and read-only requests still get a card; do not ask for approval instead of showing it. Only clicking Create starts the background Creator; do not wait, poll, claim installation or ask for a report-back. Answer any direct question, then add at most one short invitation such as “方案在下方,点击创建即可开始。” Do not repeat the plan, list extra features, narrate tool calls or mention schema names.
|
||||
|
||||
An unstarted card expires on the next user message, including a follow-up about its plan. Refreshes and background updates do not expire it. Offer a fresh card only if the current request still calls for the page. Started work keeps progress and retry controls.
|
||||
## Examples
|
||||
|
||||
Call `suggest_action` with `request.action: "create_personalized_page"`, the fields above and `request.slot`. A success saves the proposal; a rejection creates no card. Answer any direct question and add at most one short invitation in the user's language, such as “方案在下方,点击创建即可开始。” The card already shows the plan and location. Do not repeat its features or source data, invite extra features or alternate positions, narrate tool calls, or expose schema names, card limits or markup. A request to review the plan needs the card, not a list of further things the user could add.
|
||||
- “想有个地方放书签,以后自己加标题和网址;旧的不用导入。” → `brief`: “提供一个可反复打开的书签页,手动新增标题和网址并保存到本机,从空白开始。” No `initialContent`. `sidebar-left`.
|
||||
- “当前会话旁放验收清单,能勾选和取消,状态跟着会话。条目:导出成功;断网提示清楚。” → `brief`: “在当前会话右侧展示验收清单,可勾选和取消,状态随会话保存。” `initialContent`: “1. 导出成功\n2. 断网提示清楚”. `sidebar-right`.
|
||||
- “这三周 bug 数 12、9、15,说说趋势。以后每周我把新数字贴给你,想自己更新着看。” → answer the trend first, then `brief`: “按周手动录入 bug 数并展示各周变化,保存到本机。” `initialContent`: “第 1 周 12,第 2 周 9,第 3 周 15”. `sidebar-left`.
|
||||
- “我想用语音说话代替打字。” → the catalog lists 语音输入; explain it and where to enable it; no card.
|
||||
- “把我之前做的读书页评分改成五星。” → explain the change to that page; no card and no copy.
|
||||
- “帮我把这句话翻成英文:我想要一个所有会话都能打开的读书页。” → translate only; no card.
|
||||
- “把三个方案排成表格比较一下,看完就行。” → the table only; no card.
|
||||
@@ -25,6 +25,14 @@ export type * from './types.ts'
|
||||
|
||||
export const name = 'creation-recommendations'
|
||||
|
||||
/** Deployment-tunable limits for offering cards. */
|
||||
export const Config = z.object({
|
||||
/** Queued next-turn messages at or above which the current turn offers no card. */
|
||||
queuedMessageLimit: z.number().int().min(1).default(2),
|
||||
}).prefault({})
|
||||
/** Validated card limits after schema defaults. */
|
||||
export type Config = z.infer<typeof Config>
|
||||
|
||||
declare module '@deepseek-ai/cordis' {
|
||||
interface Context { creationRecommendations: CreationRecommendations }
|
||||
}
|
||||
@@ -51,7 +59,7 @@ export class CreationRecommendations extends TypertRemoteService {
|
||||
private closing = false
|
||||
private readonly owner: Context['fiber']
|
||||
|
||||
constructor(ctx: Context) {
|
||||
constructor(ctx: Context, private readonly config: Config) {
|
||||
super(ctx, 'creationRecommendations')
|
||||
this.owner = ctx.fiber
|
||||
}
|
||||
@@ -75,7 +83,6 @@ export class CreationRecommendations extends TypertRemoteService {
|
||||
content: [{ type: 'text', text: 'Latest personalized page status from card actions and background creation. '
|
||||
+ 'These are status facts, not a request to start or retry work. Use these receipts instead of the earlier proposal when describing progress. '
|
||||
+ 'Cancelled means a started background creation was stopped before installation. Cancelled, failed or interrupted runs can be retried from their existing card; no new proposal is needed. '
|
||||
+ 'Expired means an unstarted proposal became inactive when the user sent another message. Its old card cannot create anything. Offer a fresh card only if the current request still calls for a page plan. '
|
||||
+ 'Continue with the user’s current request.\n' + JSON.stringify(updates) }],
|
||||
})] }
|
||||
}, { prepend: true })
|
||||
@@ -102,6 +109,9 @@ export class CreationRecommendations extends TypertRemoteService {
|
||||
return 'Generate the new bundle files only. The caller installs them after validation.'
|
||||
}
|
||||
if (exec.name !== 'suggest_action' && exec.name !== 'suggest_plugin') return undefined
|
||||
if (exec.agent.inbox.nextTurn.length >= this.config.queuedMessageLimit) {
|
||||
return 'The user has already queued more messages. This call created no card; finish the current answer without a recommendation.'
|
||||
}
|
||||
const state = this.projection.stateOf(exec.agent.session)
|
||||
const reserved = this.reservations.get(exec.agent.id)
|
||||
const first = state.firstCall ?? (reserved?.turn === state.turn ? reserved.callId : undefined)
|
||||
@@ -237,7 +247,7 @@ export class CreationRecommendations extends TypertRemoteService {
|
||||
this.record(agent, decision)
|
||||
return { status: 'settled', decision }
|
||||
}
|
||||
if (previous?.phase === 'dismissed' || previous?.phase === 'removed' || previous?.phase === 'expired'
|
||||
if (previous?.phase === 'dismissed' || previous?.phase === 'removed' || previous?.phase === 'replaced'
|
||||
|| action === 'cancel' && previous !== undefined) return { status: 'settled', decision: previous }
|
||||
if (action === 'dismiss') {
|
||||
const decision: CreationDecision = { callId, phase: 'dismissed', slot: slotSchema.safeParse(slot).success ? slot : proposal.slot,
|
||||
@@ -345,7 +355,8 @@ export class CreationRecommendations extends TypertRemoteService {
|
||||
/**
|
||||
* Assemble the feature-owned Remote; disposing the feature also disposes its effects.
|
||||
* @param ctx - Owning feature context.
|
||||
* @param config - Validated card limits; the Loader supplies them, and direct composition gets the schema defaults.
|
||||
*/
|
||||
export async function apply(ctx: Context): Promise<void> {
|
||||
await ctx.plugin(CreationRecommendations)
|
||||
export async function apply(ctx: Context, config: Config = Config.parse({})): Promise<void> {
|
||||
await ctx.plugin(CreationRecommendations, config)
|
||||
}
|
||||
@@ -22,7 +22,7 @@ const decisionSchema = z.object({
|
||||
application: z.enum(['applied', 'restart-required', 'overridden', 'failed', 'cancelled']).optional(),
|
||||
})
|
||||
const viewSchema = z.object({ decisions: z.record(z.string(), z.union([
|
||||
decisionSchema, decisionSchema.extend({ phase: z.literal('expired') }),
|
||||
decisionSchema, decisionSchema.extend({ phase: z.literal('replaced') }),
|
||||
])) })
|
||||
const stateSchema = z.object({
|
||||
proposals: z.record(z.string(), proposalSchema), view: viewSchema,
|
||||
@@ -54,26 +54,12 @@ function settlement(event: SessionEvent) {
|
||||
return result?.isError ? undefined : result
|
||||
}
|
||||
|
||||
/** Full-session fold; later explicit needs may offer another card in another turn. */
|
||||
/** Full-session fold; a later proposal replaces an unstarted card, so one unstarted card exists per conversation. */
|
||||
export const creationProjection = {
|
||||
key: 'creationRecommendations', stateVersion: 6, stateSchema,
|
||||
key: 'creationRecommendations', stateVersion: 7, stateSchema,
|
||||
init: (): CreationState => ({ proposals: {}, view: { decisions: {} }, turn: 0, firstCall: null, pendingStatus: {} }),
|
||||
apply: (state, event) => {
|
||||
if (event.type === 'turn/start') return { ...state, turn: event.data.turn, firstCall: null }
|
||||
const humanMessage = event.type === 'agent/inbox/spliced'
|
||||
? event.data.inserted.some(message => message.source.kind === 'user')
|
||||
: event.type === 'user/message' && event.data.source.kind === 'user'
|
||||
if (humanMessage) {
|
||||
const pending = Object.values(state.proposals).filter(proposal => state.view.decisions[proposal.callId] === undefined)
|
||||
if (pending.length === 0) return state
|
||||
const decisions = { ...state.view.decisions }
|
||||
const pendingStatus = { ...state.pendingStatus }
|
||||
for (const proposal of pending) {
|
||||
decisions[proposal.callId] = { callId: proposal.callId, phase: 'expired', slot: proposal.slot }
|
||||
pendingStatus[proposal.callId] = event.seq
|
||||
}
|
||||
return { ...state, view: { decisions }, pendingStatus }
|
||||
}
|
||||
if (event.type === 'user/message' && event.data.source.kind === 'personalized-pages') {
|
||||
const through = event.data.source.through
|
||||
return { ...state, pendingStatus: Object.fromEntries(Object.entries(state.pendingStatus).filter(([, seq]) => seq > through)) }
|
||||
@@ -90,8 +76,15 @@ export const creationProjection = {
|
||||
z.object({ actionCard: z.object({ action: z.string(), callId: z.string().min(1) }) }).transform(value => value.actionCard.callId),
|
||||
]).safeParse(settlement(event)?.meta)
|
||||
const firstCall = state.firstCall ?? proposal?.callId ?? (foreign.success ? foreign.data : null)
|
||||
return proposal === undefined ? firstCall === state.firstCall ? state : { ...state, firstCall }
|
||||
: { ...state, firstCall, proposals: { ...state.proposals, [proposal.callId]: proposal } }
|
||||
if (proposal === undefined) return firstCall === state.firstCall ? state : { ...state, firstCall }
|
||||
// A newer proposal supersedes every unstarted card; the model owns that choice, so no status notice is queued.
|
||||
const decisions = { ...state.view.decisions }
|
||||
for (const earlier of Object.values(state.proposals)) {
|
||||
if (earlier.callId !== proposal.callId && decisions[earlier.callId] === undefined) {
|
||||
decisions[earlier.callId] = { callId: earlier.callId, phase: 'replaced', slot: earlier.slot }
|
||||
}
|
||||
}
|
||||
return { ...state, firstCall, proposals: { ...state.proposals, [proposal.callId]: proposal }, view: { decisions } }
|
||||
},
|
||||
wire: { summary: false, viewSchema, view: (state: CreationState) => state.view },
|
||||
} satisfies ProjectionDefinition<'creationRecommendations', CreationState>
|
||||
@@ -9,7 +9,7 @@ import { BUNDLED_SKILL_RANK, type SkillCandidate } from '@deepseek-ai/dsh-skill'
|
||||
export function registerCreationSkill(ctx: Context): void {
|
||||
const candidate: SkillCandidate = {
|
||||
name: 'personalized-pages',
|
||||
description: 'Plan optional new pages or tools for explicit UI wishes (even one-off use), clear viewing/editing needs, or results worth revisiting. Load before suggest_action; use known context and the catalog, not source/API research. Existing-page changes stay on the original: explain-only requests need no source search or creation card. Requested files/code, ordinary answers, static tables alone, existing-feature questions, quoted wishes and declined recommendations do not qualify.',
|
||||
description: 'Plan an optional new page or small tool when the user asks for a page, tab, panel, tool or interface in this app (even for one-off use), wants something to keep, revisit or update later, or will keep changing data shown in your answer. Load this before reading any files or source. Not for requested files or code, ordinary answers, existing-feature questions, quoted wishes or declined recommendations.',
|
||||
provider: 'dsh-personalized-pages', source: 'bundled', rank: BUNDLED_SKILL_RANK, locator: null,
|
||||
invocation: { modelInvocable: true, userInvocable: false },
|
||||
}
|
||||
|
||||
@@ -29,8 +29,8 @@ export interface CreationDecision {
|
||||
readonly application?: ChangeResult['application'] | undefined
|
||||
}
|
||||
|
||||
/** Expiration is derived from user messages, never written as a plugin decision. */
|
||||
export type CreationCardState = CreationDecision | (Omit<CreationDecision, 'phase'> & { readonly phase: 'expired' })
|
||||
/** Replacement is derived from a later proposal in the same conversation, never written as a plugin decision. */
|
||||
export type CreationCardState = CreationDecision | (Omit<CreationDecision, 'phase'> & { readonly phase: 'replaced' })
|
||||
|
||||
/** Open-session receipts, excluded from session-list summaries. */
|
||||
export interface CreationView {
|
||||
|
||||
+34
-42
@@ -55,7 +55,7 @@ exports[`interface creation > loads page planning with a live duplicate-check ca
|
||||
exports[`interface creation > loads page planning with a live duplicate-check catalog and withdraws it on unload > skill discovery 1`] = `
|
||||
[
|
||||
{
|
||||
"description": "Plan optional new pages or tools for explicit UI wishes (even one-off use), clear viewing/editing needs, or results worth revisiting. Load before suggest_action; use known context and the catalog, not source/API research. Existing-page changes stay on the original: explain-only requests need no source search or creation card. Requested files/code, ordinary answers, static tables alone, existing-feature questions, quoted wishes and declined recommendations do not qualify.",
|
||||
"description": "Plan an optional new page or small tool when the user asks for a page, tab, panel, tool or interface in this app (even for one-off use), wants something to keep, revisit or update later, or will keep changing data shown in your answer. Load this before reading any files or source. Not for requested files or code, ordinary answers, existing-feature questions, quoted wishes or declined recommendations.",
|
||||
"invocation": {
|
||||
"modelInvocable": true,
|
||||
"userInvocable": false,
|
||||
@@ -71,71 +71,63 @@ exports[`interface creation > loads page planning with a live duplicate-check ca
|
||||
{
|
||||
"content": "# Personalized pages
|
||||
|
||||
Use this workflow in the main conversation to plan an optional new page or small tool for any task, not just DSH. You decide whether a page helps, check for duplicates, and supply its scope, content and location. \`suggest_action\` saves the proposal and displays a card; it does not plan or execute it. After the user clicks Create, the background Creator researches implementation details and implements the accepted scope.
|
||||
Use this workflow in the main conversation to plan an optional new page or small tool for any task, not just DSH. You decide whether a page helps, check existing capabilities, and hand the Creator a short plan. \`suggest_action\` only saves the proposal and shows a card; nothing is built until the user clicks Create. Plan from the conversation and the catalog at the end of this skill. Do not read files, configuration or source to validate a page idea; name an uninspected source as an unverified dependency and leave its details to the Creator.
|
||||
|
||||
Plan from the known conversation and the included catalog. Do not inspect files, configuration, APIs or other sources merely to validate a page idea or catalog match. Name an uninspected source as an unverified dependency; leave its response shape and connection details to Creator. Wanting a page is not an instruction to develop the application shell, even when its checkout is the workspace.
|
||||
## When to propose
|
||||
|
||||
## Choose the smallest useful response
|
||||
Propose a page in these cases:
|
||||
|
||||
Honor the requested deliverable. Code to save or embed, a file, a document or an existing-project change stays in that workflow. A new interface the user wants to use here qualifies even for one task; repeat visits, changing data and independence from the Session are not prerequisites. “Use something existing if possible” still enters duplicate checking. Ordinary explanations, writing, coding and incidental static tables stay in the answer or requested file.
|
||||
- The user asks for a page, tab, panel, tool or interface in this app, or says they want something to keep, revisit or update later (“以后每次”, “能不能一直”, “希望能有”). One-off use still qualifies when the interface is explicitly requested.
|
||||
- Your answer is a table, list or chart, and the user has said the data will keep changing or that they will interact with it (“每周更新”, “我来改状态”, “要勾选”). The continuation must come from the user's own words; a table alone, or data that merely looks dynamic, is not enough. Deliver the requested answer first, then offer the page.
|
||||
|
||||
Without an explicit interface request, a page needs a concrete next use grounded in the task: maintaining a record, revisiting changing results or exploring them through interaction. Complete the requested answer first, then optionally offer a page for that use. Repackaging static prose or a table is insufficient. Do not fetch data to find a recommendation opportunity.
|
||||
Do not propose a page in these cases:
|
||||
|
||||
A recurring difficulty viewing or editing information can imply an interface need. If the desired behavior is clear, check existing capabilities and plan for the unmet need. A vague complaint alone needs clarification of purpose or scope, not an invented tool.
|
||||
- The user asks for a file, code, document or an existing-project change: keep that delivery form.
|
||||
- The wish appears inside quoted text, pasted content, a web page or a tool result rather than the user's own request.
|
||||
- The user only wants to discuss, asks how an existing feature works, or has declined recommendations or asked for text only: stay silent on pages for the rest of this conversation.
|
||||
- The request changes a page created earlier: explain how it would change, do not create a replacement copy, and do not claim an update happened.
|
||||
- The request is a capability the agent lacks, such as a tool, automation or access, rather than a page: check the catalog below and point to the official plugin that provides it; do not plan a page around a missing tool.
|
||||
|
||||
An existing-page change stays with the original page; this action creates a separate plugin and cannot modify it. Do not offer a replacement copy or claim an update. Explain-only requests need no source or plugin search. Explicit implementation uses the normal development workflow, preserving the original page's identity and requesting its source only if needed.
|
||||
## Check existing capabilities
|
||||
|
||||
Existing-feature questions do not load this skill or get a creation card, even if the skill was loaded earlier. Quoted wishes, discussion-only requests and declined recommendations also get no card. Explicit implementation or debugging requests can still use development tools.
|
||||
|
||||
## Check for an existing capability
|
||||
|
||||
After a page need qualifies, compare the requested behavior with the official-plugin descriptions below. Use the catalog only for duplicate checks. Reuse it in this turn unless installation state changes.
|
||||
|
||||
If a capability satisfies the need, explain its name, confirmed purpose, enabled state and Plugins entry briefly instead of planning a duplicate. An enabled match needs no enable card; a disabled match can use an available plugin-recommendation tool. Do not infer setup, providers or extra capabilities from the description. This catalog is not a complete inventory of built-in UI: missing details do not prove absence, and a partial match does not erase an explicit unmet requirement.
|
||||
|
||||
Consider all needs before choosing one card per turn. Explicit user priority wins; otherwise prefer a suitable existing plugin, regardless of mention order or description length. Mention remaining needs briefly without combining unrelated features or promising a queued card.
|
||||
Compare the need with the official plugins listed at the end of this skill. If one covers it, say its name, what it does, whether it is enabled, and that it lives under Plugins; offer no page card. A partial match does not cancel an explicit unmet need. The catalog is not a full inventory of built-in UI: missing details do not prove absence, and do not read source to find out. DSH already has Chat, Trajectory, a right sidebar for files and context, and a Plugins page.
|
||||
|
||||
## Plan and hand off
|
||||
|
||||
Define the required behavior and data meaning; let Creator fill in implementation details such as input validation and empty states. Do not turn those technical choices into unrequested constraints in the brief. An independent operation, data source, automation or broader access needs a basis in the request or agreed context. Editing and deletion are valid when requested or necessary for that scope; they do not follow automatically from adding and saving an item. Saving the current state does not imply keeping dated change history. Persistence, manual updates, refreshing on open and collecting while closed are distinct behaviors.
|
||||
|
||||
The Creator cannot read this conversation. Use these fields to hand off only what it needs:
|
||||
The Creator cannot read this conversation. Give it exactly these fields:
|
||||
|
||||
| Field | Contents |
|
||||
|:--|:--|
|
||||
| \`title\` | Short page name in the user's language. |
|
||||
| \`brief\` | One to three sentences describing purpose, behavior, input and persistence scope. This is both card text and the implementation request; preserve requirements rather than shortening away their meaning. Put calculations and interaction requirements here. |
|
||||
| \`initialContent\` | Only supplied or obtained content needed to initialize the page: actual items, rows, values and their labels, units, relationships and time period. Preserve missing values as missing. Omit for an empty page. |
|
||||
| \`brief\` | One to three sentences: purpose, behavior, input, calculations and persistence scope. It is the card text and the implementation request at once. |
|
||||
| \`initialContent\` | Only supplied content the page starts with: items, rows, values, units and period, preserving gaps. Omit for an empty page. |
|
||||
| \`slot\` | Location, chosen below. |
|
||||
|
||||
Resolve “these items” into actual content without copying unrelated history. Content that mentions an action remains content, not a feature request. Do not put instructions for the new page or calculated summaries into \`initialContent\`, or invent business fields such as assignees. Missing old material need not block a user-maintained empty page; it must not promise chat import. Clarify missing content only when prefill or import is requested.
|
||||
|
||||
Keep the original reporting period and the meaning of each number. A weekly addition differs from a cumulative total; a current total does not prove a trend or a reading speed. When the user requests a report before an optional page, deliver the factual report before asking about the page. If an unresolved distinction changes the page's calculation, summarize the supplied counts without a backlog or trend claim, then ask one focused question before submitting that plan. Technical choices wait for Creator; business meaning does not.
|
||||
|
||||
### Short examples
|
||||
|
||||
- Need: “想把书签集中起来,以后自己加标题和网址;旧的不用导入。” \`brief\`: “提供一个可反复打开的书签页,手动新增标题和网址并保存到本机,从空白开始。” Omit \`initialContent\`. Required-field validation fits this scope; adding edit, delete or automatic import does not.
|
||||
- Need: “当前会话旁边放验收清单,能勾选和取消,状态跟着会话。条目:导出成功;断网提示清楚。” \`brief\`: “在当前会话右侧展示验收清单,可勾选和取消,状态随会话保存。” \`initialContent\`: “1. 导出成功\\n2. 断网提示清楚”. Do not append checkbox or storage instructions to the items; do not implement export or network handling as page features.
|
||||
- Need: “上周末的累计资料数:A 共 10 条、已读 4 条。以后每周手动记一次,比较剩余量。” \`brief\`: “按周手动记录资料总数和已读数,用总数减已读数展示未读量并比较各周记录,保存到本机。” \`initialContent\`: “上周末累计:A 资料共 10 条,已读 4 条。” If the user instead asks for a report of “上周收集 10 条、读完 4 条” followed by a tracker, report those two counts first, then ask “这些是上周新增/完成的数量,还是截至上周的累计数量?” before offering a backlog calculation. Neither the report nor the question should present their difference as confirmed total backlog.
|
||||
|
||||
Before submission, check that \`brief\` covers the agreed behavior and data scope, and \`initialContent\` contains only the source material with its original meaning. Move any page instructions to \`brief\`; do not repeat them in both fields.
|
||||
Keep the plan to what was asked or agreed. Editing, deletion, history, extra fields, data sources and automation need a basis in the request. Resolve “these items” into the actual content. Content that mentions an action stays content. Keep each number's meaning and period; a weekly addition is not a cumulative total. If an unresolved distinction changes a calculation, give the factual answer, ask one question, and propose later.
|
||||
|
||||
## Choose the location
|
||||
|
||||
Honor a location explicitly requested by the user. Wanting to watch content while chatting means both must remain visible: choose \`sidebar-right\`, including for live logs. Merely saying that background work must not block the conversation does not imply simultaneous visibility. Otherwise:
|
||||
Honor a location the user named. Otherwise:
|
||||
|
||||
- \`sidebar-left\`: global feature, with a left entry opening a standalone main-area page without Chat/Trajectory tabs; includes global streaming tools.
|
||||
- \`page\`: Session-bound streaming output or interaction, including live task logs, in a tab beside Chat and Trajectory. Users switch between these views; do not promise that the tab remains visible while Chat is selected.
|
||||
- \`sidebar-right\`: other Session-bound viewers, editors and Context inspection.
|
||||
- \`sidebar-right\`: content tied to this Session or task, especially anything to watch while chatting, including live logs.
|
||||
- \`sidebar-left\`: a global feature or an ongoing personal record used across Sessions, opened from a left entry as a standalone page.
|
||||
- \`page\`: a Session-bound streaming view or interaction in a tab beside Chat and Trajectory, only when the user does not need it visible while chatting.
|
||||
|
||||
An ongoing personal record defaults to \`sidebar-left\` unless tied to a task or Session. Supplying data in this conversation alone does not make it Session-bound. Task- or Session-local viewers default to \`sidebar-right\`. Ask only when missing scope or interaction changes the outcome.
|
||||
Supplying data in this conversation does not make a record Session-bound.
|
||||
|
||||
## Offer without starting creation
|
||||
## Offer without starting
|
||||
|
||||
Plan-only or do-not-start requests permit a proposal, including read-only mode. “先让我确认方案” asks for this reviewable card, not a separate approval question; ask questions only for missing requirements, never to approve creation instead of the card. Only clicking Create starts the background Creator, which builds and hot-installs a new removable plugin while the conversation remains available. The card shows progress, cancellation, retry, removal and a cost/time notice. Do not wait, poll, require a mode switch or refresh, ask for a report-back, or claim installation from a proposal.
|
||||
Call \`suggest_action\` with \`request.action: "create_personalized_page"\` and the fields above. One proposal per turn, and one unstarted card per conversation: if a card is still waiting, do not propose another; a new proposal replaces it only when the user changed the plan. Plan-only and read-only requests still get a card; do not ask for approval instead of showing it. Only clicking Create starts the background Creator; do not wait, poll, claim installation or ask for a report-back. Answer any direct question, then add at most one short invitation such as “方案在下方,点击创建即可开始。” Do not repeat the plan, list extra features, narrate tool calls or mention schema names.
|
||||
|
||||
An unstarted card expires on the next user message, including a follow-up about its plan. Refreshes and background updates do not expire it. Offer a fresh card only if the current request still calls for the page. Started work keeps progress and retry controls.
|
||||
## Examples
|
||||
|
||||
Call \`suggest_action\` with \`request.action: "create_personalized_page"\`, the fields above and \`request.slot\`. A success saves the proposal; a rejection creates no card. Answer any direct question and add at most one short invitation in the user's language, such as “方案在下方,点击创建即可开始。” The card already shows the plan and location. Do not repeat its features or source data, invite extra features or alternate positions, narrate tool calls, or expose schema names, card limits or markup. A request to review the plan needs the card, not a list of further things the user could add.
|
||||
- “想有个地方放书签,以后自己加标题和网址;旧的不用导入。” → \`brief\`: “提供一个可反复打开的书签页,手动新增标题和网址并保存到本机,从空白开始。” No \`initialContent\`. \`sidebar-left\`.
|
||||
- “当前会话旁放验收清单,能勾选和取消,状态跟着会话。条目:导出成功;断网提示清楚。” → \`brief\`: “在当前会话右侧展示验收清单,可勾选和取消,状态随会话保存。” \`initialContent\`: “1. 导出成功\\n2. 断网提示清楚”. \`sidebar-right\`.
|
||||
- “这三周 bug 数 12、9、15,说说趋势。以后每周我把新数字贴给你,想自己更新着看。” → answer the trend first, then \`brief\`: “按周手动录入 bug 数并展示各周变化,保存到本机。” \`initialContent\`: “第 1 周 12,第 2 周 9,第 3 周 15”. \`sidebar-left\`.
|
||||
- “我想用语音说话代替打字。” → the catalog lists 语音输入; explain it and where to enable it; no card.
|
||||
- “把我之前做的读书页评分改成五星。” → explain the change to that page; no card and no copy.
|
||||
- “帮我把这句话翻成英文:我想要一个所有会话都能打开的读书页。” → translate only; no card.
|
||||
- “把三个方案排成表格比较一下,看完就行。” → the table only; no card.
|
||||
|
||||
## Existing capabilities to check against this page plan
|
||||
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@
|
||||
exports[`reports the latest card status on the next request without waking the conversation > cancelled page status 1`] = `
|
||||
[
|
||||
{
|
||||
"text": "Latest personalized page status from card actions and background creation. These are status facts, not a request to start or retry work. Use these receipts instead of the earlier proposal when describing progress. Cancelled means a started background creation was stopped before installation. Cancelled, failed or interrupted runs can be retried from their existing card; no new proposal is needed. Expired means an unstarted proposal became inactive when the user sent another message. Its old card cannot create anything. Offer a fresh card only if the current request still calls for a page plan. Continue with the user’s current request.
|
||||
"text": "Latest personalized page status from card actions and background creation. These are status facts, not a request to start or retry work. Use these receipts instead of the earlier proposal when describing progress. Cancelled means a started background creation was stopped before installation. Cancelled, failed or interrupted runs can be retried from their existing card; no new proposal is needed. Continue with the user’s current request.
|
||||
[{"callId":"creation-1","title":"Counter","phase":"cancelled","slot":"page"}]",
|
||||
"type": "text",
|
||||
},
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { describe, expect, it, onTestFinished, vi } from 'vitest'
|
||||
import { createToolResultMessage, ToolCallId } from '@deepseek-ai/dsh-llm'
|
||||
import { createToolResultMessage, createUserMessage, ToolCallId } from '@deepseek-ai/dsh-llm'
|
||||
import { appendPluginRecord, SessionId } from '@deepseek-ai/dsh-session'
|
||||
import { defineTool } from '@deepseek-ai/dsh-tools'
|
||||
import { readFile, rm } from 'node:fs/promises'
|
||||
@@ -585,3 +585,16 @@ describe('interface creation', () => {
|
||||
expect(f.current()?.phase).toBe('installing')
|
||||
})
|
||||
})
|
||||
|
||||
it('offers no card while the user has queued more messages for later turns', async () => {
|
||||
const f = await fixture()
|
||||
const queued = () => createUserMessage({ content: [{ type: 'text', text: 'Next question.' }], source: { kind: 'user' } })
|
||||
f.agent.inbox.append('next-turn', queued())
|
||||
f.agent.inbox.append('next-turn', queued())
|
||||
const refused = await f.execute()
|
||||
expect(refused.isError).toBe(true)
|
||||
expect(JSON.stringify(refused.content)).toContain('queued more messages')
|
||||
f.agent.inbox.clear()
|
||||
f.agent.inbox.append('next-turn', queued())
|
||||
expect((await f.execute()).isError).toBe(false)
|
||||
})
|
||||
+3
-3
@@ -253,10 +253,10 @@
|
||||
"expected": "Recognize implicit recurring need; one global manual-entry record, no invented workout data, automation or extra required fields."
|
||||
},
|
||||
{
|
||||
"id": "unstarted_card_expiry",
|
||||
"setup": "Offer a page but do not click Create. Send a follow-up user message.",
|
||||
"id": "unstarted_card_replacement",
|
||||
"setup": "Offer a page but do not click Create. Send a follow-up user message, then ask for a changed plan in a later turn.",
|
||||
"prompt": "刚才的方案改成只在当前会话使用。",
|
||||
"expected": "Expire the unstarted old card, refuse stale Create calls and offer a fresh plan when needed. Refresh or background context alone never expires a card; started/cancelled runs retain retry."
|
||||
"expected": "Keep the unstarted card available across follow-up messages. The next proposal replaces it: the old card shows Replaced and refuses stale Create calls. Refresh or background context never changes a card; started or cancelled runs retain retry."
|
||||
},
|
||||
{
|
||||
"id": "artifact_html_file",
|
||||
|
||||
@@ -2,7 +2,7 @@ import { expect, it, vi } from 'vitest'
|
||||
import { agentEvents, type Agent, type PreStepDecision } from '@deepseek-ai/dsh-agent'
|
||||
import { createUserMessage, ToolCallId } from '@deepseek-ai/dsh-llm'
|
||||
import { appendPluginRecord } from '@deepseek-ai/dsh-session'
|
||||
import { fixture, callId } from './fixture.ts'
|
||||
import { fixture, callId, proposal } from './fixture.ts'
|
||||
import * as CreationRecommendations from '../src/index.ts'
|
||||
|
||||
async function prepare(
|
||||
@@ -134,34 +134,26 @@ it('reports installed and removed outcomes, including application errors, withou
|
||||
expect(f.startActivation).toHaveBeenCalledOnce()
|
||||
})
|
||||
|
||||
it.each(['inbox', 'message'] as const)('expires unstarted proposals at the next human %s and refuses stale clicks after replay', async (path) => {
|
||||
it('keeps an unstarted proposal across later human messages and lets the next proposal replace it', async () => {
|
||||
const f = await fixture()
|
||||
await f.propose()
|
||||
const notice = createUserMessage({ content: [{ type: 'text', text: 'Context updated' }],
|
||||
source: { kind: 'personalized-pages', through: f.events.at(-1)!.seq, form: 'notice', summary: 'Status' } })
|
||||
f.agent.session.append('agent/inbox/spliced', { target: 'next-turn', start: 0, inserted: [notice] })
|
||||
f.agent.session.append('user/message', notice, { surfaceOp: 'append' })
|
||||
f.agent.session.append('agent/inbox/spliced', { target: 'next-turn', start: 0, removedCount: 1, inserted: [] })
|
||||
expect(f.current()).toBeUndefined()
|
||||
await f.fiber.dispose()
|
||||
await f.ctx.plugin(CreationRecommendations)
|
||||
expect(f.current()).toBeUndefined()
|
||||
const human = createUserMessage({ content: [{ type: 'text', text: 'Let us discuss something else.' }], source: { kind: 'user' } })
|
||||
if (path === 'inbox') f.agent.session.append('agent/inbox/spliced', { target: 'next-turn', start: 0, inserted: [human] })
|
||||
else f.agent.session.append('user/message', human, { surfaceOp: 'append' })
|
||||
expect(f.current()).toEqual({ callId, phase: 'expired', slot: 'sidebar-right' })
|
||||
const status = await prepare(f)
|
||||
if (status.kind !== 'enter') throw new Error('request was rejected')
|
||||
const text = status.messages.flatMap(message => message.content).filter(block => block.type === 'text').map(block => block.text).join('\n')
|
||||
expect(text).toContain('"phase":"expired"')
|
||||
enter(f.agent, status)
|
||||
// The same accepted input later entering model context does not re-queue expiry.
|
||||
f.agent.session.append('agent/inbox/spliced', { target: 'next-turn', start: 0, inserted: [human] })
|
||||
f.agent.session.append('user/message', human, { surfaceOp: 'append' })
|
||||
expect(f.current()).toBeUndefined()
|
||||
expect(await prepare(f)).toEqual({ kind: 'enter', messages: [] })
|
||||
f.agent.session.append('turn/start', { turn: 2 })
|
||||
await f.propose(ToolCallId('creation-2'), { ...proposal, title: 'Checklist' })
|
||||
expect(f.current()).toEqual({ callId, phase: 'replaced', slot: 'sidebar-right' })
|
||||
// Replacement is the model's own choice, so no status notice is queued for it.
|
||||
expect(await prepare(f)).toEqual({ kind: 'enter', messages: [] })
|
||||
expect(await f.ctx.creationRecommendations.act(f.agent, callId, 'create', 'page')).toEqual({
|
||||
status: 'settled', decision: { callId, phase: 'replaced', slot: 'sidebar-right' },
|
||||
})
|
||||
await f.agentHandle.dispose()
|
||||
const reopened = await f.ctx.agents.create({ sessionId: f.agent.id, seed: [...f.events] })
|
||||
expect(await f.ctx.creationRecommendations.act(reopened.agent, callId, 'create', 'page')).toEqual({
|
||||
status: 'settled', decision: { callId, phase: 'expired', slot: 'sidebar-right' },
|
||||
status: 'settled', decision: { callId, phase: 'replaced', slot: 'sidebar-right' },
|
||||
})
|
||||
expect(f.startActivation).not.toHaveBeenCalled()
|
||||
expect(f.manager.installBundle).not.toHaveBeenCalled()
|
||||
|
||||
@@ -5458,7 +5458,7 @@ export const TYPE_API: readonly TypeApiEntry[] = [
|
||||
},
|
||||
{
|
||||
name: 'CreationCardState',
|
||||
declaration: 'export type CreationCardState = CreationDecision | (Omit<CreationDecision, \'phase\'> & {\n readonly phase: \'expired\';\n});',
|
||||
declaration: 'export type CreationCardState = CreationDecision | (Omit<CreationDecision, \'phase\'> & {\n readonly phase: \'replaced\';\n});',
|
||||
},
|
||||
{
|
||||
name: 'CreationDecision',
|
||||
|
||||
+1
-1
@@ -203,7 +203,7 @@ Every package below exports a Cordis plugin that a bundle patch can name in a Lo
|
||||
| `@deepseek-ai/dsh-experimental-client-ui-voice-input` | no | Record speech and insert editable text into the conversation draft |
|
||||
| `@deepseek-ai/dsh-experimental-computer-use-cua-driver-mcp` | yes | Experimental computer use through an installed Cua Driver MCP executable |
|
||||
| `@deepseek-ai/dsh-experimental-computer-use-cua-driver-native` | no | Experimental computer-use provider embedding the Cua Driver native npm SDK |
|
||||
| `@deepseek-ai/dsh-experimental-creation-recommendations` | no | Create persistent removable interface plugins from explicit conversation needs |
|
||||
| `@deepseek-ai/dsh-experimental-creation-recommendations` | yes | Create persistent removable interface plugins from explicit conversation needs |
|
||||
| `@deepseek-ai/dsh-experimental-cua-macos` | yes | macOS native provider for shared Computer Use application and window objects |
|
||||
| `@deepseek-ai/dsh-experimental-inspector` | yes | Experimental cross-realm CDP hub for Host debugging and Client Runtime inspection |
|
||||
| `@deepseek-ai/dsh-experimental-ptc-runtime-python` | yes | CPython subprocess implementation of the DeepSeek Harness PTC execution seam |
|
||||
|
||||
@@ -9,14 +9,14 @@
|
||||
{"type":"system/message","data":{"turn":1,"step":1,"message":{"role":"system","content":[{"type":"text","text":"{{system}}"}],"source":{"kind":"system-prompt"},"id":"{{message:2}}"}},"surfaceOp":"append"}
|
||||
{"type":"user/message","data":{"content":[{"type":"text","text":"I wish I had a small counter that stays visible between conversations."}],"source":{"kind":"user","rpcId":"{{rpc:1}}","clientTimeZone":"{{clientTimeZone}}"},"role":"user","id":"{{message:1}}"},"surfaceOp":"append"}
|
||||
{"type":"user/message","data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent working directory: \"{{cwd}}\".\n\nCurrent DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable.\n\nApproval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed.\n\nCurrent DeepSeek Harness implementation checkout: \"{{sourceRoot}}\".\n\nCurrent DeepSeek Harness Web GUI URL: \"{{webUrl}}\"."}],"source":{"kind":"runtime-context","form":"snapshot","sections":[{"name":"working-directory:current","text":"Current working directory: \"{{cwd}}\"."},{"name":"sandbox:policy","text":"Current DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable."},{"name":"approval:policy","text":"Approval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."},{"name":"harness:source","text":"Current DeepSeek Harness implementation checkout: \"{{sourceRoot}}\"."},{"name":"app:web-surface","text":"Current DeepSeek Harness Web GUI URL: \"{{webUrl}}\"."}]},"role":"user","id":"{{message:3}}"},"surfaceOp":"append"}
|
||||
{"type":"user/message","data":{"content":[{"type":"text","text":"<system-reminder>\nA skill is a reusable set of task-specific instructions. The following skills are available in this session:\n\n<available_skills>\n- `personalized-pages`: Plan optional new pages or tools for explicit UI wishes (even one-off use), clear viewing/editing needs, or results worth revisiting. Load before suggest_action; use known context and the catalog, not source/API research. Existing-page changes stay on the original: explain-only requests need no source search or creation card. Requested files/code, ordinary answers, static tables alone, existing-feature questions, quoted wishes and declined recommendations do not qualify.\n</available_skills>\n\nIf the user names a skill, or the task clearly matches a skill's description, call the `skill` tool with the exact skill name before taking task actions. Load all applicable skills, then follow their full instructions. This catalog contains summaries only; do not infer or follow a skill's instructions until it has been loaded.\nA user may also invoke a skill directly; its <skill_content> block then appears in this conversation. Follow it, and do not call the `skill` tool again for that skill.\n</system-reminder>"}],"source":{"kind":"skill-catalog","form":"catalog","entries":[{"name":"personalized-pages","description":"Plan optional new pages or tools for explicit UI wishes (even one-off use), clear viewing/editing needs, or results worth revisiting. Load before suggest_action; use known context and the catalog, not source/API research. Existing-page changes stay on the original: explain-only requests need no source search or creation card. Requested files/code, ordinary answers, static tables alone, existing-feature questions, quoted wishes and declined recommendations do not qualify."}]},"role":"user","id":"{{message:4}}"},"surfaceOp":"append"}
|
||||
{"type":"user/message","data":{"content":[{"type":"text","text":"<system-reminder>\nA skill is a reusable set of task-specific instructions. The following skills are available in this session:\n\n<available_skills>\n- `personalized-pages`: Plan an optional new page or small tool when the user asks for a page, tab, panel, tool or interface in this app (even for one-off use), wants something to keep, revisit or update later, or will keep changing data shown in your answer. Load this before reading any files or source. Not for requested files or code, ordinary answers, existing-feature questions, quoted wishes or declined recommendations.\n</available_skills>\n\nIf the user names a skill, or the task clearly matches a skill's description, call the `skill` tool with the exact skill name before taking task actions. Load all applicable skills, then follow their full instructions. This catalog contains summaries only; do not infer or follow a skill's instructions until it has been loaded.\nA user may also invoke a skill directly; its <skill_content> block then appears in this conversation. Follow it, and do not call the `skill` tool again for that skill.\n</system-reminder>"}],"source":{"kind":"skill-catalog","form":"catalog","entries":[{"name":"personalized-pages","description":"Plan an optional new page or small tool when the user asks for a page, tab, panel, tool or interface in this app (even for one-off use), wants something to keep, revisit or update later, or will keep changing data shown in your answer. Load this before reading any files or source. Not for requested files or code, ordinary answers, existing-feature questions, quoted wishes or declined recommendations."}]},"role":"user","id":"{{message:4}}"},"surfaceOp":"append"}
|
||||
{"type":"user/message","data":{"content":[{"type":"text","text":"Time sampled while preparing turn 1, step 1: {{timeContextTimestamp}}\nBrowser time zone for this request: {{clientTimeZone}}. Interpret otherwise-unqualified dates and times in this zone.\nElapsed since the preceding model-visible message: {{elapsed}}"}],"source":{"kind":"time-context","form":"snapshot","sections":[{"name":"time-context","text":"Time sampled while preparing turn 1, step 1: {{timeContextTimestamp}}\nBrowser time zone for this request: {{clientTimeZone}}. Interpret otherwise-unqualified dates and times in this zone.\nElapsed since the preceding model-visible message: {{elapsed}}"}]},"role":"user","id":"{{message:5}}"},"surfaceOp":"append"}
|
||||
{"type":"request/header","data":{"header":{"config":{"provider":"deepseek-official","model":"deepseek-v4-flash"},"tools":"{{tools}}"},"reason":"initial"}}
|
||||
{"type":"request/context","data":{"provider":"deepseek-official","model":"deepseek-v4-flash","contextWindow":128000}}
|
||||
{"type":"session/title","data":{"title":"I wish I had a","messageSeqs":[8],"source":{"kind":"fallback"}}}
|
||||
{"type":"assistant/message","data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"tool-call","id":"call_creation_skill","name":"skill","arguments":"{\"name\":\"personalized-pages\"}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{message:6}}"},"stream":[{"type":"chunk","time":1792505538988,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}},{"type":"chunk","time":1792505538988,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"call_creation_skill","name":"skill","arguments":"{\"name\":\"personalized-pages\"}"}}},{"type":"chunk","time":1792505538988,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}]},"surfaceOp":"append"}
|
||||
{"type":"tool/call","data":{"turn":1,"step":1,"callId":"call_creation_skill","name":"skill","arguments":"{\"name\":\"personalized-pages\"}"}}
|
||||
{"type":"tool/result","data":{"turn":1,"step":1,"message":{"role":"tool","source":{"kind":"tool","callId":"call_creation_skill"},"toolCallId":"call_creation_skill","content":[{"type":"text","text":"<skill_content name=\"personalized-pages\">\n<skill_resources>\nResources for this skill are managed by provider \"dsh-personalized-pages\".\nLoad referenced resources only as needed.\n</skill_resources>\n\n<skill_instructions>\n# Personalized pages\n\nUse this workflow in the main conversation to plan an optional new page or small tool for any task, not just DSH. You decide whether a page helps, check for duplicates, and supply its scope, content and location. `suggest_action` saves the proposal and displays a card; it does not plan or execute it. After the user clicks Create, the background Creator researches implementation details and implements the accepted scope.\n\nPlan from the known conversation and the included catalog. Do not inspect files, configuration, APIs or other sources merely to validate a page idea or catalog match. Name an uninspected source as an unverified dependency; leave its response shape and connection details to Creator. Wanting a page is not an instruction to develop the application shell, even when its checkout is the workspace.\n\n## Choose the smallest useful response\n\nHonor the requested deliverable. Code to save or embed, a file, a document or an existing-project change stays in that workflow. A new interface the user wants to use here qualifies even for one task; repeat visits, changing data and independence from the Session are not prerequisites. “Use something existing if possible” still enters duplicate checking. Ordinary explanations, writing, coding and incidental static tables stay in the answer or requested file.\n\nWithout an explicit interface request, a page needs a concrete next use grounded in the task: maintaining a record, revisiting changing results or exploring them through interaction. Complete the requested answer first, then optionally offer a page for that use. Repackaging static prose or a table is insufficient. Do not fetch data to find a recommendation opportunity.\n\nA recurring difficulty viewing or editing information can imply an interface need. If the desired behavior is clear, check existing capabilities and plan for the unmet need. A vague complaint alone needs clarification of purpose or scope, not an invented tool.\n\nAn existing-page change stays with the original page; this action creates a separate plugin and cannot modify it. Do not offer a replacement copy or claim an update. Explain-only requests need no source or plugin search. Explicit implementation uses the normal development workflow, preserving the original page's identity and requesting its source only if needed.\n\nExisting-feature questions do not load this skill or get a creation card, even if the skill was loaded earlier. Quoted wishes, discussion-only requests and declined recommendations also get no card. Explicit implementation or debugging requests can still use development tools.\n\n## Check for an existing capability\n\nAfter a page need qualifies, compare the requested behavior with the official-plugin descriptions below. Use the catalog only for duplicate checks. Reuse it in this turn unless installation state changes.\n\nIf a capability satisfies the need, explain its name, confirmed purpose, enabled state and Plugins entry briefly instead of planning a duplicate. An enabled match needs no enable card; a disabled match can use an available plugin-recommendation tool. Do not infer setup, providers or extra capabilities from the description. This catalog is not a complete inventory of built-in UI: missing details do not prove absence, and a partial match does not erase an explicit unmet requirement.\n\nConsider all needs before choosing one card per turn. Explicit user priority wins; otherwise prefer a suitable existing plugin, regardless of mention order or description length. Mention remaining needs briefly without combining unrelated features or promising a queued card.\n\n## Plan and hand off\n\nDefine the required behavior and data meaning; let Creator fill in implementation details such as input validation and empty states. Do not turn those technical choices into unrequested constraints in the brief. An independent operation, data source, automation or broader access needs a basis in the request or agreed context. Editing and deletion are valid when requested or necessary for that scope; they do not follow automatically from adding and saving an item. Saving the current state does not imply keeping dated change history. Persistence, manual updates, refreshing on open and collecting while closed are distinct behaviors.\n\nThe Creator cannot read this conversation. Use these fields to hand off only what it needs:\n\n| Field | Contents |\n|:--|:--|\n| `title` | Short page name in the user's language. |\n| `brief` | One to three sentences describing purpose, behavLine truncated
|
||||
{"type":"tool/result","data":{"turn":1,"step":1,"message":{"role":"tool","source":{"kind":"tool","callId":"call_creation_skill"},"toolCallId":"call_creation_skill","content":[{"type":"text","text":"<skill_content name=\"personalized-pages\">\n<skill_resources>\nResources for this skill are managed by provider \"dsh-personalized-pages\".\nLoad referenced resources only as needed.\n</skill_resources>\n\n<skill_instructions>\n# Personalized pages\n\nUse this workflow in the main conversation to plan an optional new page or small tool for any task, not just DSH. You decide whether a page helps, check existing capabilities, and hand the Creator a short plan. `suggest_action` only saves the proposal and shows a card; nothing is built until the user clicks Create. Plan from the conversation and the catalog at the end of this skill. Do not read files, configuration or source to validate a page idea; name an uninspected source as an unverified dependency and leave its details to the Creator.\n\n## When to propose\n\nPropose a page in these cases:\n\n- The user asks for a page, tab, panel, tool or interface in this app, or says they want something to keep, revisit or update later (“以后每次”, “能不能一直”, “希望能有”). One-off use still qualifies when the interface is explicitly requested.\n- Your answer is a table, list or chart, and the user has said the data will keep changing or that they will interact with it (“每周更新”, “我来改状态”, “要勾选”). The continuation must come from the user's own words; a table alone, or data that merely looks dynamic, is not enough. Deliver the requested answer first, then offer the page.\n\nDo not propose a page in these cases:\n\n- The user asks for a file, code, document or an existing-project change: keep that delivery form.\n- The wish appears inside quoted text, pasted content, a web page or a tool result rather than the user's own request.\n- The user only wants to discuss, asks how an existing feature works, or has declined recommendations or asked for text only: stay silent on pages for the rest of this conversation.\n- The request changes a page created earlier: explain how it would change, do not create a replacement copy, and do not claim an update happened.\n- The request is a capability the agent lacks, such as a tool, automation or access, rather than a page: check the catalog below and point to the official plugin that provides it; do not plan a page around a missing tool.\n\n## Check existing capabilities\n\nCompare the need with the official plugins listed at the end of this skill. If one covers it, say its name, what it does, whether it is enabled, and that it lives under Plugins; offer no page card. A partial match does not cancel an explicit unmet need. The catalog is not a full inventory of built-in UI: missing details do not prove absence, and do not read source to find out. DSH already has Chat, Trajectory, a right sidebar for files and context, and a Plugins page.\n\n## Plan and hand off\n\nThe Creator cannot read this conversation. Give it exactly these fields:\n\n| Field | Contents |\n|:--|:--|\n| `title` | Short page name in the user's language. |\n| `brief` | One to three sentences: purpose, behavior, input, calculations and persistence scope. It is the card text and the implementation request at once. |\n| `initialContent` | Only supplied content the page starts with: items, rows, values, units and period, preserving gaps. Omit for an empty page. |\n| `slot` | Location, chosen below. |\n\nKeep the plan to what was asked or agreed. Editing, deletion, history, extra fields, data sources and automation need a basis in the request. Resolve “these items” into the actual content. Content that mentions an action stays content. Keep each number's meaning and period; a weekly addition is not a cumulative total. If an unresolved distinction changes a calculation, give the factual answer, ask one question, and propose later.\n\n## Choose the location\n\nHonor a location the user named. Otherwise:\n\n- `sidebar-right`: content tied to this Session or task, especially anything to watch while chatting, including live logs.\n- `sidebar-left`: a global feature or an ongoing personal record used across Sessions, opened from a left entry as a standalone page.\n- `page`: a Session-bound streaming view or interaction in a tab beside Chat and Trajectory, only when the user does not need it visible while chatting.\n\nSupplying data in this conversation does not make a record Session-bound.\n\n## Offer without starting\n\nCall `suggest_action` with `request.action: \"create_personalized_page\"` and the fields above. One proposal per turn, and one unstarted card per conversation: if a card is still waiting, do not propose another; a new proposal replaces it only when the user changed the plan. Plan-only and read-only requests still get a card; do not ask for approval instead of showing it. Only clicking Create starts the bLine truncated
|
||||
{"type":"step/end","data":{"turn":1,"step":1}}
|
||||
{"type":"step/start","data":{"turn":1,"step":2}}
|
||||
{"type":"assistant/message","data":{"turn":1,"step":2,"message":{"role":"assistant","content":[{"type":"tool-call","id":"call_creation","name":"suggest_action","arguments":"{\"request\":{\"action\":\"create_personalized_page\",\"title\":\"Counter\",\"brief\":\"Need: Keep a small counter visible across conversations.\\nLocation: A card titled Today’s achievements in the right sidebar.\\nControls: Increment and reset buttons beside the current value.\\nStorage: Save changes in browser storage across navigation and refresh.\\nAppearance: Follow the existing sidebar theme in light and dark mode.\\nCompletion: Both controls work and reopening restores the saved value.\",\"slot\":\"sidebar-right\"}}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{message:8}}"},"stream":[{"type":"chunk","time":1792505539242,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}},{"type":"chunk","time":1792505539242,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"call_creation","name":"suggest_action","arguments":"{\"request\":{\"action\":\"create_personalized_page\",\"title\":\"Counter\",\"brief\":\"Need: Keep a small counter visible across conversations.\\nLocation: A card titled Today’s achievements in the right sidebar.\\nControls: Increment and reset buttons beside the current value.\\nStorage: Save changes in browser storage across navigation and refresh.\\nAppearance: Follow the existing sidebar theme in light and dark mode.\\nCompletion: Both controls work and reopening restores the saved value.\",\"slot\":\"sidebar-right\"}}"}}},{"type":"chunk","time":1792505539242,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}]},"surfaceOp":"append"}
|
||||
@@ -33,7 +33,7 @@
|
||||
{"type":"agent/inbox/spliced","data":{"target":"next-turn","start":0,"removedCount":1,"inserted":[]}}
|
||||
{"type":"step/start","data":{"turn":2,"step":1}}
|
||||
{"type":"user/message","data":{"content":[{"type":"text","text":"What is the counter status now?"}],"source":{"kind":"user","rpcId":"{{rpc:2}}","clientTimeZone":"{{clientTimeZone}}"},"role":"user","id":"{{message:11}}"},"surfaceOp":"append"}
|
||||
{"type":"user/message","data":{"source":{"kind":"personalized-pages","through":28,"form":"notice","summary":"Personalized page status updated"},"content":[{"type":"text","text":"Latest personalized page status from card actions and background creation. These are status facts, not a request to start or retry work. Use these receipts instead of the earlier proposal when describing progress. Cancelled means a started background creation was stopped before installation. Cancelled, failed or interrupted runs can be retried from their existing card; no new proposal is needed. Expired means an unstarted proposal became inactive when the user sent another message. Its old card cannot create anything. Offer a fresh card only if the current request still calls for a page plan. Continue with the user’s current request.\n[{\"callId\":\"call_creation\",\"title\":\"Counter\",\"phase\":\"dismissed\",\"slot\":\"page\"}]"}],"role":"user","id":"{{message:12}}"},"surfaceOp":"append"}
|
||||
{"type":"user/message","data":{"source":{"kind":"personalized-pages","through":28,"form":"notice","summary":"Personalized page status updated"},"content":[{"type":"text","text":"Latest personalized page status from card actions and background creation. These are status facts, not a request to start or retry work. Use these receipts instead of the earlier proposal when describing progress. Cancelled means a started background creation was stopped before installation. Cancelled, failed or interrupted runs can be retried from their existing card; no new proposal is needed. Continue with the user’s current request.\n[{\"callId\":\"call_creation\",\"title\":\"Counter\",\"phase\":\"dismissed\",\"slot\":\"page\"}]"}],"role":"user","id":"{{message:12}}"},"surfaceOp":"append"}
|
||||
{"type":"assistant/message","data":{"turn":2,"step":1,"message":{"role":"assistant","content":[{"type":"text","text":"The counter proposal was dismissed. Creation never started."}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{message:13}}"},"stream":[{"type":"chunk","time":1792505539296,"chunk":{"type":"block-start","index":0,"blockType":"text"}},{"type":"text-chunks","time0":1792505539297,"index":0,"dt":[],"texts":["The counter proposal was dismissed. Creation never started."]},{"type":"chunk","time":1792505539297,"chunk":{"type":"block-end","index":0,"block":{"type":"text","text":"The counter proposal was dismissed. Creation never started."}}},{"type":"chunk","time":1792505539297,"chunk":{"type":"finish","reason":{"kind":"stop"}}}]},"surfaceOp":"append"}
|
||||
{"type":"step/end","data":{"turn":2,"step":1}}
|
||||
{"type":"turn/end","data":{"turn":2,"reason":{"kind":"completed"}}}
|
||||
Reference in new issue
Block a user