@deepseek-harness-tui/dsh-tui 0.7.2 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -3
- package/bin/dsh-tui.js +24 -3
- package/cordis.patch.yml +24 -11
- package/cordis.yml +9 -1
- package/lib/types/commands.d.ts.map +1 -1
- package/lib/types/commands.js +2 -1
- package/lib/types/components/GoalTodoPanel.d.ts.map +1 -1
- package/lib/types/components/GoalTodoPanel.js +6 -1
- package/lib/types/components/HistorySearchDialog.d.ts.map +1 -1
- package/lib/types/components/HistorySearchDialog.js +5 -1
- package/lib/types/components/MessageList.d.ts +3 -1
- package/lib/types/components/MessageList.d.ts.map +1 -1
- package/lib/types/components/MessageList.js +9 -5
- package/lib/types/components/PluginSceneBoundary.d.ts +31 -0
- package/lib/types/components/PluginSceneBoundary.d.ts.map +1 -0
- package/lib/types/components/PluginSceneBoundary.js +29 -0
- package/lib/types/components/PromptInput.d.ts.map +1 -1
- package/lib/types/components/PromptInput.js +52 -74
- package/lib/types/components/SearchBox.d.ts +4 -0
- package/lib/types/components/SearchBox.d.ts.map +1 -1
- package/lib/types/components/SearchBox.js +101 -2
- package/lib/types/components/SkillsPicker.d.ts +18 -0
- package/lib/types/components/SkillsPicker.d.ts.map +1 -0
- package/lib/types/components/SkillsPicker.js +50 -0
- package/lib/types/components/Spinner/SpinnerAnimationRow.d.ts +4 -1
- package/lib/types/components/Spinner/SpinnerAnimationRow.d.ts.map +1 -1
- package/lib/types/components/Spinner/SpinnerAnimationRow.js +10 -5
- package/lib/types/components/SplitDiffView.d.ts +28 -0
- package/lib/types/components/SplitDiffView.d.ts.map +1 -0
- package/lib/types/components/SplitDiffView.js +417 -0
- package/lib/types/components/WorkingSpinner.d.ts +3 -1
- package/lib/types/components/WorkingSpinner.d.ts.map +1 -1
- package/lib/types/components/WorkingSpinner.js +2 -2
- package/lib/types/components/design-system/ThemedText.d.ts +7 -1
- package/lib/types/components/design-system/ThemedText.d.ts.map +1 -1
- package/lib/types/components/design-system/ThemedText.js +2 -2
- package/lib/types/components/messages/AssistantToolUseMessage.d.ts +3 -1
- package/lib/types/components/messages/AssistantToolUseMessage.d.ts.map +1 -1
- package/lib/types/components/messages/AssistantToolUseMessage.js +17 -7
- package/lib/types/components/questions/AskUserQuestionPanel.d.ts.map +1 -1
- package/lib/types/components/questions/AskUserQuestionPanel.js +52 -8
- package/lib/types/components/questions/PlanReviewPanel.d.ts.map +1 -1
- package/lib/types/components/questions/PlanReviewPanel.js +12 -3
- package/lib/types/dsh-adapter/channel.d.ts +78 -0
- package/lib/types/dsh-adapter/channel.d.ts.map +1 -1
- package/lib/types/dsh-adapter/channel.js +165 -14
- package/lib/types/dsh-adapter/contract.d.ts +1 -1
- package/lib/types/dsh-adapter/contract.d.ts.map +1 -1
- package/lib/types/dsh-adapter/contract.js +1 -0
- package/lib/types/dsh-adapter/index.d.ts +4 -0
- package/lib/types/dsh-adapter/index.d.ts.map +1 -1
- package/lib/types/dsh-adapter/index.js +1 -0
- package/lib/types/dsh-adapter/packaged-presets.d.ts.map +1 -1
- package/lib/types/dsh-adapter/packaged-presets.js +5 -2
- package/lib/types/dsh-adapter/plugin.d.ts.map +1 -1
- package/lib/types/dsh-adapter/plugin.js +66 -3
- package/lib/types/dsh-adapter/promptDebug.d.ts +10 -0
- package/lib/types/dsh-adapter/promptDebug.d.ts.map +1 -0
- package/lib/types/dsh-adapter/promptDebug.js +148 -0
- package/lib/types/dsh-adapter/providerWizard.d.ts.map +1 -1
- package/lib/types/dsh-adapter/providerWizard.js +3 -1
- package/lib/types/dsh-adapter/scenes.d.ts +75 -0
- package/lib/types/dsh-adapter/scenes.d.ts.map +1 -0
- package/lib/types/dsh-adapter/scenes.js +78 -0
- package/lib/types/dsh-adapter/settings-sections.d.ts +116 -0
- package/lib/types/dsh-adapter/settings-sections.d.ts.map +1 -0
- package/lib/types/dsh-adapter/settings-sections.js +63 -0
- package/lib/types/dsh-adapter/settingsEditor.d.ts +128 -0
- package/lib/types/dsh-adapter/settingsEditor.d.ts.map +1 -0
- package/lib/types/dsh-adapter/settingsEditor.js +244 -0
- package/lib/types/i18n.d.ts +169 -18
- package/lib/types/i18n.d.ts.map +1 -1
- package/lib/types/i18n.js +45 -6
- package/lib/types/ink/components/Text.d.ts +9 -1
- package/lib/types/ink/components/Text.d.ts.map +1 -1
- package/lib/types/ink/components/Text.js +7 -6
- package/lib/types/ink/ink.d.ts.map +1 -1
- package/lib/types/ink/ink.js +4 -2
- package/lib/types/ink/log-update.d.ts.map +1 -1
- package/lib/types/ink/log-update.js +76 -9
- package/lib/types/ink/parse-keypress.d.ts.map +1 -1
- package/lib/types/ink/parse-keypress.js +4 -1
- package/lib/types/ink/terminal.d.ts +40 -4
- package/lib/types/ink/terminal.d.ts.map +1 -1
- package/lib/types/ink/terminal.js +57 -5
- package/lib/types/ink/termio/tokenize.d.ts +6 -0
- package/lib/types/ink/termio/tokenize.d.ts.map +1 -1
- package/lib/types/ink/termio/tokenize.js +18 -4
- package/lib/types/jsx-runtime.d.ts +2 -0
- package/lib/types/jsx-runtime.d.ts.map +1 -0
- package/lib/types/jsx-runtime.js +13 -0
- package/lib/types/modelRoute.d.ts +10 -2
- package/lib/types/modelRoute.d.ts.map +1 -1
- package/lib/types/modelRoute.js +33 -4
- package/lib/types/scenes.d.ts +3 -0
- package/lib/types/scenes.d.ts.map +1 -0
- package/lib/types/scenes.js +4 -0
- package/lib/types/screens/Chat.d.ts.map +1 -1
- package/lib/types/screens/Chat.js +182 -26
- package/lib/types/screens/SessionBrowser.d.ts.map +1 -1
- package/lib/types/screens/SessionBrowser.js +30 -15
- package/lib/types/screens/Settings.d.ts +20 -0
- package/lib/types/screens/Settings.d.ts.map +1 -0
- package/lib/types/screens/Settings.js +333 -0
- package/lib/types/screens/StatusLine.js +1 -1
- package/lib/types/sessions/view.d.ts.map +1 -1
- package/lib/types/sessions/view.js +13 -1
- package/lib/types/settings-sections.d.ts +3 -0
- package/lib/types/settings-sections.d.ts.map +1 -0
- package/lib/types/settings-sections.js +4 -0
- package/lib/types/theme.d.ts +12 -0
- package/lib/types/theme.d.ts.map +1 -1
- package/lib/types/theme.js +36 -0
- package/lib/types/update.d.ts +21 -7
- package/lib/types/update.d.ts.map +1 -1
- package/lib/types/update.js +66 -16
- package/lib/types/utils/paths.d.ts.map +1 -1
- package/lib/types/utils/paths.js +4 -1
- package/package.json +49 -9
|
@@ -300,6 +300,17 @@ export function createChannel(ctx, initialAgent, options) {
|
|
|
300
300
|
// the degraded-boot warning for profile launches.
|
|
301
301
|
const workspaceService = ctx.get('tuiWorkspaces') ?? createLocalWorkspaceRuntime();
|
|
302
302
|
const commandTrees = ctx.get('tuiCommandTrees');
|
|
303
|
+
// The `/settings` screen reads its host on EVERY render, so the host must
|
|
304
|
+
// be a stable object: a fresh literal per call would re-fire the screen's
|
|
305
|
+
// host-keyed effects endlessly (render → new host → effect → state →
|
|
306
|
+
// render). The underlying services are fixed for the channel's lifetime,
|
|
307
|
+
// so compute once and cache.
|
|
308
|
+
let settingsHostCache;
|
|
309
|
+
let settingsHostResolved = false;
|
|
310
|
+
// Plugin scene runtime (optional service, same degradation rule as
|
|
311
|
+
// tuiWorkspaces/tuiCommandTrees): mounted by the bundle patch's
|
|
312
|
+
// dsh-tui-scenes row; absent the row, `pluginScene` simply stays undefined.
|
|
313
|
+
const sceneRuntime = ctx.get('tuiScenes');
|
|
303
314
|
// Shift+Tab session-mode cycle: cordis.yml `modes` wins; absent/empty/
|
|
304
315
|
// atom-less → the built-in default/plan/full cycle (sessionModes.ts).
|
|
305
316
|
const { modes: sessionModes, dropped: droppedModeIds } = resolveSessionModes(options.modes);
|
|
@@ -641,6 +652,7 @@ export function createChannel(ctx, initialAgent, options) {
|
|
|
641
652
|
modeIndex: 0,
|
|
642
653
|
workingActivity: undefined,
|
|
643
654
|
activityFrames: options.activityFrames,
|
|
655
|
+
diffLayout: options.diffLayout ?? 'auto',
|
|
644
656
|
activityEnabled: options.activity !== false,
|
|
645
657
|
contextBarEnabled: options.contextBar !== false,
|
|
646
658
|
agentPreset: options.agentPreset,
|
|
@@ -1452,6 +1464,12 @@ export function createChannel(ctx, initialAgent, options) {
|
|
|
1452
1464
|
}
|
|
1453
1465
|
}, item.timeoutMs);
|
|
1454
1466
|
},
|
|
1467
|
+
setDiffLayout(layout) {
|
|
1468
|
+
if (layout === state.diffLayout)
|
|
1469
|
+
return;
|
|
1470
|
+
state.diffLayout = layout;
|
|
1471
|
+
state.emit();
|
|
1472
|
+
},
|
|
1455
1473
|
setActivityFrames(name) {
|
|
1456
1474
|
if (!isPresetName(name)) {
|
|
1457
1475
|
state.notify(t('unknown-activity-preset', { name }), { color: 'error' });
|
|
@@ -1561,6 +1579,84 @@ export function createChannel(ctx, initialAgent, options) {
|
|
|
1561
1579
|
return Promise.all(providers.map(provider => llm.listModels(provider.id).catch(() => [])))
|
|
1562
1580
|
.then(lists => lists.flat());
|
|
1563
1581
|
},
|
|
1582
|
+
async listSkills() {
|
|
1583
|
+
// snapshot() over list(): only a COMPLETE observation is authoritative
|
|
1584
|
+
// (same contract as the skill-command merge above) — a partial catalog
|
|
1585
|
+
// must surface as "failed", not as a misleading near-empty picker.
|
|
1586
|
+
const registry = skillRegistryFor(agent);
|
|
1587
|
+
if (registry === undefined)
|
|
1588
|
+
return [];
|
|
1589
|
+
try {
|
|
1590
|
+
const observation = await registry.snapshot(skillViewOptions(agent));
|
|
1591
|
+
if (!observation.complete)
|
|
1592
|
+
return undefined;
|
|
1593
|
+
return observation.skills.map(skill => ({
|
|
1594
|
+
name: skill.name,
|
|
1595
|
+
description: skill.description,
|
|
1596
|
+
userInvocable: isUserInvocable(skill),
|
|
1597
|
+
source: skill.source,
|
|
1598
|
+
}));
|
|
1599
|
+
}
|
|
1600
|
+
catch {
|
|
1601
|
+
return undefined;
|
|
1602
|
+
}
|
|
1603
|
+
},
|
|
1604
|
+
async describeCredential(ref) {
|
|
1605
|
+
const credentials = ctx.get('credentials');
|
|
1606
|
+
if (!credentials)
|
|
1607
|
+
return undefined;
|
|
1608
|
+
return credentials.describe(ref);
|
|
1609
|
+
},
|
|
1610
|
+
settingsHost() {
|
|
1611
|
+
if (settingsHostResolved)
|
|
1612
|
+
return settingsHostCache;
|
|
1613
|
+
settingsHostResolved = true;
|
|
1614
|
+
// The `/settings` screen's runtime surface, over the same dsh-base
|
|
1615
|
+
// seams the `/provider` wizard uses: settings (namespace descriptors +
|
|
1616
|
+
// revision-fenced mutate) and credentials (secret writes). Structurally
|
|
1617
|
+
// typed like the other optional seams in this file.
|
|
1618
|
+
const settings = ctx.get('settings');
|
|
1619
|
+
const credentials = ctx.get('credentials');
|
|
1620
|
+
if (!settings)
|
|
1621
|
+
return undefined;
|
|
1622
|
+
settingsHostCache = {
|
|
1623
|
+
listNamespaces() {
|
|
1624
|
+
// redactSecrets: the screen never renders a secret literal — secret
|
|
1625
|
+
// fields are write-only controls over the credentials seam.
|
|
1626
|
+
return settings.describe({ redactSecrets: true }).map(descriptor => ({
|
|
1627
|
+
ns: descriptor.ns,
|
|
1628
|
+
revision: descriptor.revision,
|
|
1629
|
+
applies: descriptor.applies,
|
|
1630
|
+
value: descriptor.value,
|
|
1631
|
+
user: descriptor.user,
|
|
1632
|
+
}));
|
|
1633
|
+
},
|
|
1634
|
+
write(ns, ops, expectedRevision) {
|
|
1635
|
+
return settings.mutate(ns, ops, expectedRevision);
|
|
1636
|
+
},
|
|
1637
|
+
async credentialConfigured(ref) {
|
|
1638
|
+
// The environment shadows the store (providerSetup.envShadows), so
|
|
1639
|
+
// an env-provided key counts as configured.
|
|
1640
|
+
if (process.env[ref] !== undefined)
|
|
1641
|
+
return true;
|
|
1642
|
+
return credentials !== undefined && (await credentials.resolve(ref)) !== undefined;
|
|
1643
|
+
},
|
|
1644
|
+
async writeCredential(ref, value) {
|
|
1645
|
+
if (!credentials)
|
|
1646
|
+
throw new Error('credentials service unavailable');
|
|
1647
|
+
await credentials.set(ref, value);
|
|
1648
|
+
},
|
|
1649
|
+
};
|
|
1650
|
+
return settingsHostCache;
|
|
1651
|
+
},
|
|
1652
|
+
settingsSections() {
|
|
1653
|
+
const sections = ctx.get('tuiSettingsSections');
|
|
1654
|
+
return sections?.list() ?? [];
|
|
1655
|
+
},
|
|
1656
|
+
subscribeSettingsSections(listener) {
|
|
1657
|
+
const sections = ctx.get('tuiSettingsSections');
|
|
1658
|
+
return sections?.subscribe(listener) ?? (() => { });
|
|
1659
|
+
},
|
|
1564
1660
|
providerSetup() {
|
|
1565
1661
|
// The `/provider` wizard's runtime surface, over the dsh-base seams:
|
|
1566
1662
|
// settings (profile persistence), credentials (key storage) and the
|
|
@@ -1757,6 +1853,13 @@ export function createChannel(ctx, initialAgent, options) {
|
|
|
1757
1853
|
runExternalCommand(name, rawInput) {
|
|
1758
1854
|
return executeRegistryCommand(name, rawInput);
|
|
1759
1855
|
},
|
|
1856
|
+
pluginScene: sceneRuntime?.active,
|
|
1857
|
+
openPluginScene(id) {
|
|
1858
|
+
return sceneRuntime?.open(id) ?? false;
|
|
1859
|
+
},
|
|
1860
|
+
closePluginScene() {
|
|
1861
|
+
sceneRuntime?.close();
|
|
1862
|
+
},
|
|
1760
1863
|
pushLocal(title, lines) {
|
|
1761
1864
|
state.rows.push({ id: nextRowId++, kind: 'local', text: title });
|
|
1762
1865
|
for (const line of lines) {
|
|
@@ -1937,6 +2040,7 @@ export function createChannel(ctx, initialAgent, options) {
|
|
|
1937
2040
|
},
|
|
1938
2041
|
releaseContributions() {
|
|
1939
2042
|
releaseSkillCommands();
|
|
2043
|
+
unsubscribeScenes?.();
|
|
1940
2044
|
},
|
|
1941
2045
|
traceEvents() {
|
|
1942
2046
|
// Immutable per-append snapshot (dsh-session caches the frozen array);
|
|
@@ -2232,25 +2336,38 @@ export function createChannel(ctx, initialAgent, options) {
|
|
|
2232
2336
|
const dispose = commandService.register({
|
|
2233
2337
|
name,
|
|
2234
2338
|
description,
|
|
2235
|
-
// The
|
|
2236
|
-
//
|
|
2339
|
+
// The invocation line is re-submitted as a user message (kernel
|
|
2340
|
+
// path) or replaced by the injected body (fallback) — recording
|
|
2341
|
+
// the raw input here too would duplicate it in the session log.
|
|
2237
2342
|
recordInput: false,
|
|
2238
|
-
handler: async ({ agent: invoker, signal }) => {
|
|
2343
|
+
handler: async ({ agent: invoker, rawInput, signal }) => {
|
|
2344
|
+
// Kernel gesture path: the `skill` tool and dsh-tool-skill's
|
|
2345
|
+
// pre-step boundary mount together, so a visible `skill` tool
|
|
2346
|
+
// means the boundary scans this agent's user messages for the
|
|
2347
|
+
// `/name` gesture and injects the rendered body host-side —
|
|
2348
|
+
// the same architecture as the web client's ui-skill. Routing
|
|
2349
|
+
// through it keeps the user's args in the transcript message
|
|
2350
|
+
// (rawInput rides along instead of being swallowed by the
|
|
2351
|
+
// command layer) and matches the kernel's own adjudication.
|
|
2352
|
+
const tools = ctx.get('tools');
|
|
2353
|
+
if (tools?.get('skill', invoker) !== undefined) {
|
|
2354
|
+
deliverUserText(`/${name}${rawInput}`, 'followup');
|
|
2355
|
+
// Silent success: the submitted message is the feedback.
|
|
2356
|
+
return { kind: 'success' };
|
|
2357
|
+
}
|
|
2358
|
+
// Fallback for compositions without dsh-tool-skill (e.g. the
|
|
2359
|
+
// minimal preset): inject the rendered body directly, in the
|
|
2360
|
+
// official user-explicit invocation shape (dsh-skill's
|
|
2361
|
+
// SkillInvocationSource).
|
|
2239
2362
|
const view = { ...skillViewOptions(invoker), signal };
|
|
2240
2363
|
const skill = await skillRegistryFor(invoker)?.get(name, view);
|
|
2241
2364
|
if (skill === undefined || !isUserInvocable(skill)) {
|
|
2242
2365
|
return { kind: 'error', text: t('skill-unavailable', { name }) };
|
|
2243
2366
|
}
|
|
2244
|
-
// The official user-explicit invocation shape (dsh-skill's
|
|
2245
|
-
// SkillInvocationSource): the rendered body rides as instructions
|
|
2246
|
-
// the model follows, and transcript consumers present it from the
|
|
2247
|
-
// source metadata instead of re-parsing model-facing text.
|
|
2248
2367
|
invoker.followup(createUserMessage({
|
|
2249
2368
|
content: [{ type: 'text', text: renderSkillContent(skill) }],
|
|
2250
2369
|
source: { kind: 'skill-invocation', name, form: 'instructions' },
|
|
2251
2370
|
}));
|
|
2252
|
-
// Silent success: the agent visibly starts working on the skill,
|
|
2253
|
-
// which is the feedback (CC shows no banner either).
|
|
2254
2371
|
return { kind: 'success' };
|
|
2255
2372
|
},
|
|
2256
2373
|
});
|
|
@@ -2265,6 +2382,17 @@ export function createChannel(ctx, initialAgent, options) {
|
|
|
2265
2382
|
ctx.on('skills/change', () => {
|
|
2266
2383
|
void refreshSkillCommands();
|
|
2267
2384
|
});
|
|
2385
|
+
/**
|
|
2386
|
+
* Mirror the scene runtime's active scene into channel state, so screens
|
|
2387
|
+
* swap to it through the ordinary version-bump re-render instead of a
|
|
2388
|
+
* second subscription channel.
|
|
2389
|
+
*/
|
|
2390
|
+
const unsubscribeScenes = sceneRuntime?.subscribe(() => {
|
|
2391
|
+
if (state.pluginScene === sceneRuntime.active)
|
|
2392
|
+
return;
|
|
2393
|
+
state.pluginScene = sceneRuntime.active;
|
|
2394
|
+
state.emit();
|
|
2395
|
+
});
|
|
2268
2396
|
/** See {@link Channel.releaseContributions}. */
|
|
2269
2397
|
const releaseSkillCommands = () => {
|
|
2270
2398
|
if (skillCommandsRetry !== undefined)
|
|
@@ -2595,12 +2723,18 @@ ${output}
|
|
|
2595
2723
|
break;
|
|
2596
2724
|
}
|
|
2597
2725
|
case 'assistant/message': {
|
|
2598
|
-
const row = ensureStreaming(event.seq);
|
|
2599
|
-
row.time = event.time;
|
|
2600
2726
|
const text = textOf(event.data.message.content);
|
|
2601
|
-
|
|
2602
|
-
|
|
2603
|
-
|
|
2727
|
+
// Reasoning/tool-only steps emit no text: creating an assistant row
|
|
2728
|
+
// anyway leaves an empty `●` bullet in the transcript. A pre-existing
|
|
2729
|
+
// streaming row always has text (ensureStreaming is only reached on
|
|
2730
|
+
// non-empty text deltas), so only create one when text arrives.
|
|
2731
|
+
const row = streaming ?? (text ? ensureStreaming(event.seq) : undefined);
|
|
2732
|
+
if (row !== undefined) {
|
|
2733
|
+
row.time = event.time;
|
|
2734
|
+
if (text)
|
|
2735
|
+
row.text = text;
|
|
2736
|
+
row.streaming = false;
|
|
2737
|
+
}
|
|
2604
2738
|
streaming = undefined;
|
|
2605
2739
|
if (reasoning !== undefined) {
|
|
2606
2740
|
// Seal, don't fold: the per-step duration settles here, but the
|
|
@@ -2997,6 +3131,23 @@ ${output}
|
|
|
2997
3131
|
}),
|
|
2998
3132
|
];
|
|
2999
3133
|
};
|
|
3134
|
+
// Subagents inherit provider/model from AgentOptions, but resumed TUI
|
|
3135
|
+
// agents can legitimately carry their route only in persisted request
|
|
3136
|
+
// headers. Their child scopes do not share this channel's per-agent
|
|
3137
|
+
// ModelSelectionRef, so fill an otherwise incomplete first request from
|
|
3138
|
+
// the active route. Keep complete child-specific routes authoritative.
|
|
3139
|
+
ctx.on('agent/request', async (_payload, next) => {
|
|
3140
|
+
const resolved = await next();
|
|
3141
|
+
if (typeof resolved.provider === 'string' && resolved.provider.length > 0 &&
|
|
3142
|
+
typeof resolved.model === 'string' && resolved.model.length > 0) {
|
|
3143
|
+
return resolved;
|
|
3144
|
+
}
|
|
3145
|
+
return {
|
|
3146
|
+
...resolved,
|
|
3147
|
+
provider: state.provider,
|
|
3148
|
+
model: state.model,
|
|
3149
|
+
};
|
|
3150
|
+
});
|
|
3000
3151
|
bindAgent();
|
|
3001
3152
|
// Cordis owns the Channel lifetime. Rebinding handles the common case;
|
|
3002
3153
|
// this effect closes the final timer when the Channel's context unloads.
|
|
@@ -5,7 +5,7 @@ export declare const UPSTREAM_VALIDATED_VERSION = "0.1.0-rc.6";
|
|
|
5
5
|
*/
|
|
6
6
|
export declare const UPSTREAM_FRAMEWORK_MAJORS: Record<string, number>;
|
|
7
7
|
/** Official packages the adapter consumes at runtime or as types. */
|
|
8
|
-
export declare const UPSTREAM_BLESSED_PACKAGES: readonly ["@deepseek-ai/cordis", "@deepseek-ai/schemastery", "@deepseek-ai/dsh-invariants", "@deepseek-ai/dsh-agent", "@deepseek-ai/dsh-agent-instructions", "@deepseek-ai/dsh-agent-presets", "@deepseek-ai/dsh-commands", "@deepseek-ai/dsh-cordis-host-runner", "@deepseek-ai/dsh-llm", "@deepseek-ai/dsh-persona", "@deepseek-ai/dsh-session", "@deepseek-ai/dsh-skill", "@deepseek-ai/dsh-storage", "@deepseek-ai/dsh-storage-domain", "@deepseek-ai/dsh-storage-json", "@deepseek-ai/dsh-workspace", "@deepseek-ai/dsh-system-prompt", "@deepseek-ai/dsh-terminal", "@deepseek-ai/dsh-terminal-bash", "@deepseek-ai/dsh-tool-ask-user", "@deepseek-ai/dsh-tool-bash-persistent", "@deepseek-ai/dsh-tool-cordis", "@deepseek-ai/dsh-user-approval", "@deepseek-ai/dsh-user-questions"];
|
|
8
|
+
export declare const UPSTREAM_BLESSED_PACKAGES: readonly ["@deepseek-ai/cordis", "@deepseek-ai/schemastery", "@deepseek-ai/dsh-invariants", "@deepseek-ai/dsh-agent", "@deepseek-ai/dsh-agent-instructions", "@deepseek-ai/dsh-agent-presets", "@deepseek-ai/dsh-atomic-write", "@deepseek-ai/dsh-commands", "@deepseek-ai/dsh-cordis-host-runner", "@deepseek-ai/dsh-llm", "@deepseek-ai/dsh-persona", "@deepseek-ai/dsh-session", "@deepseek-ai/dsh-skill", "@deepseek-ai/dsh-storage", "@deepseek-ai/dsh-storage-domain", "@deepseek-ai/dsh-storage-json", "@deepseek-ai/dsh-workspace", "@deepseek-ai/dsh-system-prompt", "@deepseek-ai/dsh-terminal", "@deepseek-ai/dsh-terminal-bash", "@deepseek-ai/dsh-tool-ask-user", "@deepseek-ai/dsh-tool-bash-persistent", "@deepseek-ai/dsh-tool-cordis", "@deepseek-ai/dsh-user-approval", "@deepseek-ai/dsh-user-questions"];
|
|
9
9
|
export interface UpstreamDriftEntry {
|
|
10
10
|
package: string;
|
|
11
11
|
installed: string | undefined;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"contract.d.ts","sourceRoot":"","sources":["../../../src/dsh-adapter/contract.ts"],"names":[],"mappings":"AAcA,eAAO,MAAM,0BAA0B,eAAe,CAAA;AAEtD;;;GAGG;AACH,eAAO,MAAM,yBAAyB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAG5D,CAAA;AAED,qEAAqE;AACrE,eAAO,MAAM,yBAAyB,
|
|
1
|
+
{"version":3,"file":"contract.d.ts","sourceRoot":"","sources":["../../../src/dsh-adapter/contract.ts"],"names":[],"mappings":"AAcA,eAAO,MAAM,0BAA0B,eAAe,CAAA;AAEtD;;;GAGG;AACH,eAAO,MAAM,yBAAyB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAG5D,CAAA;AAED,qEAAqE;AACrE,eAAO,MAAM,yBAAyB,8xBA0B5B,CAAA;AAEV,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,MAAM,CAAA;IACf,SAAS,EAAE,MAAM,GAAG,SAAS,CAAA;IAC7B,SAAS,EAAE,MAAM,CAAA;CAClB;AAWD,wBAAgB,yBAAyB,IAAI,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAgB9E;AAOD;;;GAGG;AACH,wBAAgB,aAAa,IAAI,kBAAkB,EAAE,CAsBpD"}
|
|
@@ -28,6 +28,7 @@ export const UPSTREAM_BLESSED_PACKAGES = [
|
|
|
28
28
|
'@deepseek-ai/dsh-agent',
|
|
29
29
|
'@deepseek-ai/dsh-agent-instructions',
|
|
30
30
|
'@deepseek-ai/dsh-agent-presets',
|
|
31
|
+
'@deepseek-ai/dsh-atomic-write',
|
|
31
32
|
'@deepseek-ai/dsh-commands',
|
|
32
33
|
'@deepseek-ai/dsh-cordis-host-runner',
|
|
33
34
|
'@deepseek-ai/dsh-llm',
|
|
@@ -62,6 +62,10 @@ export interface Config {
|
|
|
62
62
|
* persisted in `~/.dsh-tui/agent-preset.json` wins, then the roster
|
|
63
63
|
* default (`standard`). */
|
|
64
64
|
preset?: string;
|
|
65
|
+
/** Edit/Write diff presentation: `auto` picks side-by-side on wide
|
|
66
|
+
* terminals (≥110 cols) and unified below; `split`/`unified` force one
|
|
67
|
+
* layout. Editable live from the `/settings` screen. */
|
|
68
|
+
diffLayout?: 'auto' | 'split' | 'unified';
|
|
65
69
|
/** Shift+Tab session-mode cycle (array order IS the cycle order; index 0
|
|
66
70
|
* is the unmarked base mode). Each entry bundles any subset of the
|
|
67
71
|
* `plan`/`sandbox`/`approval` atoms; absent → the built-in
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/dsh-adapter/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAClD,OAAO,MAAM,MAAM,0BAA0B,CAAA;AAC7C,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAA;AAEzD,eAAO,MAAM,IAAI,YAAY,CAAA;AAU7B,eAAO,MAAM,MAAM,UAAa,CAAA;AAEhC;;;GAGG;AACH,MAAM,WAAW,MAAM;IACrB,0EAA0E;IAC1E,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB;;;;;yEAKqE;IACrE,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB;gDAC4C;IAC5C,KAAK,CAAC,EAAE,MAAM,CAAA;IACd;;gFAE4E;IAC5E,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ;oCACgC;IAChC,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB;;;;sBAIkB;IAClB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,8EAA8E;IAC9E,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB;;;4BAGwB;IACxB,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB;;wDAEoD;IACpD,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,8EAA8E;IAC9E,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB;kFAC8E;IAC9E,IAAI,CAAC,EAAE,MAAM,CAAA;IACb;;;gCAG4B;IAC5B,MAAM,CAAC,EAAE,MAAM,CAAA;IACf;;;yDAGqD;IACrD,KAAK,CAAC,EAAE,eAAe,EAAE,CAAA;CAC1B;AAED,eAAO,MAAM,MAAM,EAAE,MAAM,CAAC,MAAM,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/dsh-adapter/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAClD,OAAO,MAAM,MAAM,0BAA0B,CAAA;AAC7C,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAA;AAEzD,eAAO,MAAM,IAAI,YAAY,CAAA;AAU7B,eAAO,MAAM,MAAM,UAAa,CAAA;AAEhC;;;GAGG;AACH,MAAM,WAAW,MAAM;IACrB,0EAA0E;IAC1E,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB;;;;;yEAKqE;IACrE,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB;gDAC4C;IAC5C,KAAK,CAAC,EAAE,MAAM,CAAA;IACd;;gFAE4E;IAC5E,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ;oCACgC;IAChC,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB;;;;sBAIkB;IAClB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,8EAA8E;IAC9E,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB;;;4BAGwB;IACxB,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB;;wDAEoD;IACpD,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,8EAA8E;IAC9E,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB;kFAC8E;IAC9E,IAAI,CAAC,EAAE,MAAM,CAAA;IACb;;;gCAG4B;IAC5B,MAAM,CAAC,EAAE,MAAM,CAAA;IACf;;6DAEyD;IACzD,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,GAAG,SAAS,CAAA;IACzC;;;yDAGqD;IACrD,KAAK,CAAC,EAAE,eAAe,EAAE,CAAA;CAC1B;AAED,eAAO,MAAM,MAAM,EAAE,MAAM,CAAC,MAAM,CA2BhC,CAAA;AAEF;;;;;;GAMG;AACH,wBAAsB,KAAK,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAWvE"}
|
|
@@ -27,6 +27,7 @@ export const Config = Schema.object({
|
|
|
27
27
|
fullscreen: Schema.boolean().default(false),
|
|
28
28
|
lang: Schema.string().required(false),
|
|
29
29
|
preset: Schema.string().required(false),
|
|
30
|
+
diffLayout: Schema.union(['auto', 'split', 'unified']).default('auto'),
|
|
30
31
|
modes: Schema.array(Schema.object({
|
|
31
32
|
id: Schema.string(),
|
|
32
33
|
label: Schema.string().required(false),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"packaged-presets.d.ts","sourceRoot":"","sources":["../../../src/dsh-adapter/packaged-presets.ts"],"names":[],"mappings":"AAuBA,MAAM,WAAW,oBAAoB;IACnC,EAAE,EAAE,MAAM,CAAA;IACV,MAAM,EAAE,WAAW,GAAG,SAAS,GAAG,SAAS,GAAG,UAAU,CAAA;CACzD;AAED,MAAM,WAAW,qBAAqB;IACpC,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAmBD,0EAA0E;AAC1E,wBAAgB,kBAAkB,CAAC,SAAS,GAAE,MAAwB,GAAG,MAAM,CAQ9E;AAED;;;;;;;;GAQG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,GAAE,qBAA0B,GAAG,oBAAoB,EAAE,
|
|
1
|
+
{"version":3,"file":"packaged-presets.d.ts","sourceRoot":"","sources":["../../../src/dsh-adapter/packaged-presets.ts"],"names":[],"mappings":"AAuBA,MAAM,WAAW,oBAAoB;IACnC,EAAE,EAAE,MAAM,CAAA;IACV,MAAM,EAAE,WAAW,GAAG,SAAS,GAAG,SAAS,GAAG,UAAU,CAAA;CACzD;AAED,MAAM,WAAW,qBAAqB;IACpC,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAmBD,0EAA0E;AAC1E,wBAAgB,kBAAkB,CAAC,SAAS,GAAE,MAAwB,GAAG,MAAM,CAQ9E;AAED;;;;;;;;GAQG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,GAAE,qBAA0B,GAAG,oBAAoB,EAAE,CAqDjG"}
|
|
@@ -56,7 +56,10 @@ export function ensurePackagedPresets(options = {}) {
|
|
|
56
56
|
const target = join(targetRoot, id);
|
|
57
57
|
if (!existsSync(target)) {
|
|
58
58
|
mkdirSync(targetRoot, { recursive: true });
|
|
59
|
-
|
|
59
|
+
// `filter` forces the JS copy path (see src/utils/paths.ts): the native
|
|
60
|
+
// cpSync fast path fails with EIO or crashes under a non-ASCII home
|
|
61
|
+
// directory such as `C:\Users\米`.
|
|
62
|
+
cpSync(source, target, { recursive: true, force: false, errorOnExist: true, filter: () => true });
|
|
60
63
|
results.push({ id, status: 'installed' });
|
|
61
64
|
continue;
|
|
62
65
|
}
|
|
@@ -72,7 +75,7 @@ export function ensurePackagedPresets(options = {}) {
|
|
|
72
75
|
const suffix = `${process.pid}-${randomUUID()}`;
|
|
73
76
|
const staged = join(targetRoot, `.${id}.staged-${suffix}`);
|
|
74
77
|
const backup = join(targetRoot, `.${id}.backup-${suffix}`);
|
|
75
|
-
cpSync(source, staged, { recursive: true, force: false, errorOnExist: true });
|
|
78
|
+
cpSync(source, staged, { recursive: true, force: false, errorOnExist: true, filter: () => true });
|
|
76
79
|
try {
|
|
77
80
|
renameSync(target, backup);
|
|
78
81
|
renameSync(staged, target);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../../../src/dsh-adapter/plugin.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,KAAK,EAAe,MAAM,wBAAwB,CAAA;AAGhE,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;
|
|
1
|
+
{"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../../../src/dsh-adapter/plugin.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,KAAK,EAAe,MAAM,wBAAwB,CAAA;AAGhE,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAGlD,OAAO,EAAE,MAAM,EAAE,MAAM,YAAY,CAAA;AA8BnC;;;;;;;;GAQG;AACH,wBAAsB,KAAK,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAkgBvE;AA6GD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE;IAAE,UAAU,EAAE,CAAC,KAAK,CAAC,EAAE,OAAO,KAAK,IAAI,CAAA;CAAE,GAAG;IACjF,UAAU,EAAE,CAAC,KAAK,CAAC,EAAE,OAAO,KAAK,IAAI,CAAA;IACrC,YAAY,EAAE,MAAM,IAAI,CAAA;CACzB,CAcA;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE;IACpC,YAAY,EAAE,MAAM,CAAA;IACpB,SAAS,EAAE,KAAK,GAAG,SAAS,CAAA;IAC5B,YAAY,EAAE,KAAK,CAAA;CACpB,GAAG,OAAO,CAQV"}
|
|
@@ -3,12 +3,15 @@ import React from 'react';
|
|
|
3
3
|
import { SessionId } from '@deepseek-ai/dsh-session';
|
|
4
4
|
import UserQuestionService from '@deepseek-ai/dsh-user-questions';
|
|
5
5
|
import * as toolAskUser from '@deepseek-ai/dsh-tool-ask-user';
|
|
6
|
+
import { settingsNamespace } from '@deepseek-ai/dsh-settings';
|
|
7
|
+
import Schema from '@deepseek-ai/schemastery';
|
|
6
8
|
import { createChannel } from './channel.js';
|
|
7
9
|
import { createChildStderrReporter, installChildStderrGuard } from './childStderr.js';
|
|
8
10
|
import { logForDebugging } from '../utils/debug.js';
|
|
9
11
|
import { QuestionStore } from './questions.js';
|
|
10
12
|
import { ApprovalStore } from './approvals.js';
|
|
11
13
|
import { registerPackagedSkills } from './packaged-skills.js';
|
|
14
|
+
import { registerPromptDebug } from './promptDebug.js';
|
|
12
15
|
import { readActivityFrames } from '../activityPrefs.js';
|
|
13
16
|
import { readModelPref } from '../modelPrefs.js';
|
|
14
17
|
import { explicitModelRoute, recordedModelRoute, resolveModelRoute, validateModelRoute } from '../modelRoute.js';
|
|
@@ -130,6 +133,9 @@ export async function apply(ctx, config) {
|
|
|
130
133
|
// Packaged skills (/audit, /bug, …): contribute them through the host's
|
|
131
134
|
// skill registry so they resolve with zero manual copying.
|
|
132
135
|
registerPackagedSkills(ctx);
|
|
136
|
+
// `/debug-prompt` snapshots the final provider-neutral request at the
|
|
137
|
+
// llm/stream boundary, after every prompt and tool contributor has run.
|
|
138
|
+
registerPromptDebug(ctx);
|
|
133
139
|
// Yield to an incumbent provider instead of crashing the whole plugin tree
|
|
134
140
|
// (issue #98): the harness allows exactly ONE user-questions provider per
|
|
135
141
|
// context, and stacking this TUI onto a profile that already carries
|
|
@@ -211,6 +217,14 @@ export async function apply(ctx, config) {
|
|
|
211
217
|
'The bundle patch is older than the installed dsh-tui package — update the globally installed dsh-tui launcher to match the profile (issue #183).');
|
|
212
218
|
}
|
|
213
219
|
const workspaceService = mountedWorkspaceService ?? createLocalWorkspaceRuntime();
|
|
220
|
+
// Same skew guard for the plugin-scene registry (dsh-tui-scenes row): the
|
|
221
|
+
// channel degrades to never opening scenes when the service is absent, so
|
|
222
|
+
// say why on profile launches — a plugin's open() otherwise fails with only
|
|
223
|
+
// its own warn to go on.
|
|
224
|
+
if (ctx.get('tuiScenes') === undefined && resolveDshProfileName() !== undefined) {
|
|
225
|
+
ctx.logger.warn('dsh-tui: tuiScenes service is not mounted; plugin scenes will never open. ' +
|
|
226
|
+
'The bundle patch is older than the installed dsh-tui package — update the globally installed dsh-tui launcher to match the profile (issue #183).');
|
|
227
|
+
}
|
|
214
228
|
const initialWorkspace = requestedWorkspace === undefined
|
|
215
229
|
? undefined
|
|
216
230
|
: await workspaceService.resolve(requestedWorkspace);
|
|
@@ -267,8 +281,53 @@ export async function apply(ctx, config) {
|
|
|
267
281
|
// Shift+Tab session-mode cycle (undefined → the built-in default/
|
|
268
282
|
// plan/full cycle in sessionModes.ts).
|
|
269
283
|
modes: config.modes,
|
|
284
|
+
// Edit/Write diff presentation (schema default 'auto'); the /settings
|
|
285
|
+
// screen edits this key live through the dsh-tui namespace.
|
|
286
|
+
diffLayout: config.diffLayout,
|
|
270
287
|
handle,
|
|
271
288
|
});
|
|
289
|
+
// Register the dsh-tui settings namespace so the /settings screen can
|
|
290
|
+
// edit it (the section below was '命名空间未注册' without this): the
|
|
291
|
+
// user layer in settings.yaml wins over cordis.yml's diffLayout, and
|
|
292
|
+
// watch() lands commits on the live channel — no recompose needed.
|
|
293
|
+
ctx.inject(['settings'], (settingsCtx) => {
|
|
294
|
+
const scope = settingsCtx.settings.register(settingsNamespace('dsh-tui'), Schema.object({
|
|
295
|
+
diffLayout: Schema.union(['auto', 'split', 'unified']).default('auto'),
|
|
296
|
+
}));
|
|
297
|
+
const applyLayout = (value) => {
|
|
298
|
+
channel.setDiffLayout(value.diffLayout ?? config.diffLayout ?? 'auto');
|
|
299
|
+
};
|
|
300
|
+
applyLayout(scope.get());
|
|
301
|
+
scope.watch(next => {
|
|
302
|
+
applyLayout(next);
|
|
303
|
+
});
|
|
304
|
+
});
|
|
305
|
+
// The /settings screen's own section: the dsh-tui namespace comes from
|
|
306
|
+
// this plugin's Config schema, and the declared select writes diffLayout
|
|
307
|
+
// back through the settings service's revision-fenced mutate.
|
|
308
|
+
const settingsSections = ctx.get('tuiSettingsSections');
|
|
309
|
+
if (settingsSections !== undefined) {
|
|
310
|
+
const unregister = settingsSections.register({
|
|
311
|
+
ns: 'dsh-tui',
|
|
312
|
+
title: 'dsh-tui',
|
|
313
|
+
fields: [
|
|
314
|
+
{
|
|
315
|
+
path: ['diffLayout'],
|
|
316
|
+
label: 'Diff layout',
|
|
317
|
+
descriptions: { zh: 'diff 布局' },
|
|
318
|
+
hint: 'Edit/Write tool cards: auto picks by terminal width, or force one layout.',
|
|
319
|
+
hintDescriptions: { zh: 'Edit/Write 工具卡的 diff 呈现:auto 按终端宽度选择,或强制一种布局。' },
|
|
320
|
+
kind: 'select',
|
|
321
|
+
options: [
|
|
322
|
+
{ value: 'auto', label: 'Auto (by width)', descriptions: { zh: '自动(按宽度)' } },
|
|
323
|
+
{ value: 'split', label: 'Side-by-side', descriptions: { zh: '双栏对照' } },
|
|
324
|
+
{ value: 'unified', label: 'Unified', descriptions: { zh: '统一式' } },
|
|
325
|
+
],
|
|
326
|
+
},
|
|
327
|
+
],
|
|
328
|
+
});
|
|
329
|
+
ctx.effect(() => unregister);
|
|
330
|
+
}
|
|
272
331
|
// DSH approval seam: the permission layer asks ApprovalService.request(),
|
|
273
332
|
// which dispatches an `approval/request` waterfall. With no answerer the
|
|
274
333
|
// chain falls through to the fail-closed 'unavailable', so register this
|
|
@@ -309,6 +368,7 @@ export async function apply(ctx, config) {
|
|
|
309
368
|
let instance;
|
|
310
369
|
let exited = false;
|
|
311
370
|
let updateRequested = false;
|
|
371
|
+
let updateTargetVersion;
|
|
312
372
|
// The profile this process was booted with (`dsh --profile <name>`); dsh
|
|
313
373
|
// exposes it nowhere else, and /update must update the installation the
|
|
314
374
|
// user is actually running, not a hard-coded one.
|
|
@@ -336,7 +396,7 @@ export async function apply(ctx, config) {
|
|
|
336
396
|
catch {
|
|
337
397
|
// Resume persistence is best effort and must never block an update.
|
|
338
398
|
}
|
|
339
|
-
void finishExit(ctx, instance, config.fullscreen === true, 'Updating @deepseek-harness-tui/dsh-tui and restarting…', undefined, () => runUpdate(ctx, profile, channel.agentId));
|
|
399
|
+
void finishExit(ctx, instance, config.fullscreen === true, 'Updating @deepseek-harness-tui/dsh-tui and restarting…', undefined, () => runUpdate(ctx, profile, channel.agentId, updateTargetVersion));
|
|
340
400
|
return;
|
|
341
401
|
}
|
|
342
402
|
// Judge against the live session behind the channel (channel.agentId),
|
|
@@ -392,6 +452,9 @@ export async function apply(ctx, config) {
|
|
|
392
452
|
if (target.kind === 'unknown') {
|
|
393
453
|
channel.notify(t('update-check-failed'));
|
|
394
454
|
}
|
|
455
|
+
else {
|
|
456
|
+
updateTargetVersion = target.latest;
|
|
457
|
+
}
|
|
395
458
|
channel.notify(t('update-starting'));
|
|
396
459
|
updateRequested = true;
|
|
397
460
|
handleExit();
|
|
@@ -665,13 +728,13 @@ function writeStream(stream, data) {
|
|
|
665
728
|
}
|
|
666
729
|
});
|
|
667
730
|
}
|
|
668
|
-
function runUpdate(ctx, profile, sessionId) {
|
|
731
|
+
function runUpdate(ctx, profile, sessionId, targetVersion) {
|
|
669
732
|
disposeRootAndThen(ctx, () => {
|
|
670
733
|
if (profile === undefined) {
|
|
671
734
|
process.stderr.write(`\n${t('update-aborted-no-profile')}\n`);
|
|
672
735
|
process.exit(1);
|
|
673
736
|
}
|
|
674
|
-
void updateTuiAndRestart(sessionId, profile).then(({ updateCode, restartCode }) => {
|
|
737
|
+
void updateTuiAndRestart(sessionId, profile, targetVersion).then(({ updateCode, restartCode }) => {
|
|
675
738
|
if (updateCode !== 0) {
|
|
676
739
|
process.stderr.write(`\ndsh-tui update failed (exit ${updateCode}). Your session is preserved — resume with:\n` +
|
|
677
740
|
`${resumeCommand(profile, sessionId)}\n\n`);
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
2
|
+
export declare const PROMPT_DEBUG_FILENAME = ".dsh-prompt-debug.json";
|
|
3
|
+
/**
|
|
4
|
+
* Register `/debug-prompt` and retain every final request observed for each
|
|
5
|
+
* live session. The command writes one private, atomic snapshot into the
|
|
6
|
+
* receiving session's workspace; it never records credentials, transport
|
|
7
|
+
* headers, or AbortSignals.
|
|
8
|
+
*/
|
|
9
|
+
export declare function registerPromptDebug(ctx: Context): void;
|
|
10
|
+
//# sourceMappingURL=promptDebug.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"promptDebug.d.ts","sourceRoot":"","sources":["../../../src/dsh-adapter/promptDebug.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAMlD,eAAO,MAAM,qBAAqB,2BAA2B,CAAA;AAmF7D;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,OAAO,GAAG,IAAI,CA4FtD"}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* On-demand capture of every final AgentLoop request context observed for a
|
|
3
|
+
* live session.
|
|
4
|
+
*
|
|
5
|
+
* The LLM waterfall is the last provider-neutral boundary before adapter
|
|
6
|
+
* dispatch. Capturing there records the fully assembled system prompt,
|
|
7
|
+
* messages, and tool schemas after prompt/tool plugins have contributed.
|
|
8
|
+
* @module @deepseek-harness-tui/dsh-tui/prompt-debug
|
|
9
|
+
*/
|
|
10
|
+
import { join, resolve } from 'node:path';
|
|
11
|
+
import { writeFileAtomic } from '@deepseek-ai/dsh-atomic-write';
|
|
12
|
+
import { isAgentLoopRequest } from '@deepseek-ai/dsh-llm';
|
|
13
|
+
export const PROMPT_DEBUG_FILENAME = '.dsh-prompt-debug.json';
|
|
14
|
+
function latestPosition(agent) {
|
|
15
|
+
for (let index = agent.session.events.length - 1; index >= 0; index -= 1) {
|
|
16
|
+
const event = agent.session.events[index];
|
|
17
|
+
if (event?.type === 'step/start')
|
|
18
|
+
return event.data;
|
|
19
|
+
}
|
|
20
|
+
return undefined;
|
|
21
|
+
}
|
|
22
|
+
function latestRequestHeaderReason(agent) {
|
|
23
|
+
for (let index = agent.session.events.length - 1; index >= 0; index -= 1) {
|
|
24
|
+
const event = agent.session.events[index];
|
|
25
|
+
if (event?.type === 'request/header')
|
|
26
|
+
return event.data.reason;
|
|
27
|
+
}
|
|
28
|
+
return undefined;
|
|
29
|
+
}
|
|
30
|
+
function turnCompleted(agent, turn) {
|
|
31
|
+
return agent.session.events.some(event => event.type === 'turn/end' && event.data.turn === turn);
|
|
32
|
+
}
|
|
33
|
+
function captureRequest(agent, options, requestIndex, position, attempt) {
|
|
34
|
+
const requestHeaderReason = latestRequestHeaderReason(agent);
|
|
35
|
+
return {
|
|
36
|
+
capturedAt: new Date().toISOString(),
|
|
37
|
+
requestIndex,
|
|
38
|
+
...position,
|
|
39
|
+
attempt,
|
|
40
|
+
...(requestHeaderReason === undefined ? {} : { requestHeaderReason }),
|
|
41
|
+
finalLlmContext: {
|
|
42
|
+
provider: options.provider,
|
|
43
|
+
model: options.model,
|
|
44
|
+
...(options.reasoningEffort === undefined ? {} : { reasoningEffort: options.reasoningEffort }),
|
|
45
|
+
...(options.system === undefined ? {} : { system: options.system }),
|
|
46
|
+
messages: options.messages,
|
|
47
|
+
...(options.tools === undefined ? {} : { tools: options.tools }),
|
|
48
|
+
...(options.temperature === undefined ? {} : { temperature: options.temperature }),
|
|
49
|
+
...(options.maxTokens === undefined ? {} : { maxTokens: options.maxTokens }),
|
|
50
|
+
...(options.stop === undefined ? {} : { stop: options.stop }),
|
|
51
|
+
},
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Register `/debug-prompt` and retain every final request observed for each
|
|
56
|
+
* live session. The command writes one private, atomic snapshot into the
|
|
57
|
+
* receiving session's workspace; it never records credentials, transport
|
|
58
|
+
* headers, or AbortSignals.
|
|
59
|
+
*/
|
|
60
|
+
export function registerPromptDebug(ctx) {
|
|
61
|
+
const commands = ctx.get('commands');
|
|
62
|
+
const agents = ctx.get('agents');
|
|
63
|
+
if (commands === undefined || agents === undefined || ctx.get('llm') === undefined)
|
|
64
|
+
return;
|
|
65
|
+
const captureStartedAt = new Date().toISOString();
|
|
66
|
+
const captures = new Map();
|
|
67
|
+
ctx.on('llm/stream', (options, next) => {
|
|
68
|
+
if (!isAgentLoopRequest(options) || options.sessionId === undefined)
|
|
69
|
+
return next();
|
|
70
|
+
const agent = agents.get(options.sessionId);
|
|
71
|
+
if (agent === undefined)
|
|
72
|
+
return next();
|
|
73
|
+
const position = latestPosition(agent);
|
|
74
|
+
if (position === undefined)
|
|
75
|
+
return next();
|
|
76
|
+
const sessionId = String(options.sessionId);
|
|
77
|
+
const capture = captures.get(sessionId) ?? { requests: [] };
|
|
78
|
+
if (!captures.has(sessionId))
|
|
79
|
+
captures.set(sessionId, capture);
|
|
80
|
+
const previous = capture.requests.findLast(request => request.turn === position.turn && request.step === position.step);
|
|
81
|
+
const attempt = (previous?.attempt ?? 0) + 1;
|
|
82
|
+
capture.requests.push(captureRequest(agent, options, capture.requests.length + 1, position, attempt));
|
|
83
|
+
return next();
|
|
84
|
+
});
|
|
85
|
+
// Replaced agents are no longer addressable by `/debug-prompt`; release
|
|
86
|
+
// their potentially large message/tool snapshots with the agent lifecycle.
|
|
87
|
+
ctx.on('agent/disposed', ({ agent }) => {
|
|
88
|
+
captures.delete(String(agent.session.id));
|
|
89
|
+
});
|
|
90
|
+
ctx.effect(() => commands.register({
|
|
91
|
+
name: 'debug-prompt',
|
|
92
|
+
description: 'Write every observed final LLM request context for this session to the workspace root',
|
|
93
|
+
recordInput: false,
|
|
94
|
+
handler: async ({ agent, rawInput }) => {
|
|
95
|
+
if (rawInput.trim().length > 0) {
|
|
96
|
+
return { kind: 'error', text: 'Usage: /debug-prompt' };
|
|
97
|
+
}
|
|
98
|
+
const sessionId = String(agent.session.id);
|
|
99
|
+
const capture = captures.get(sessionId);
|
|
100
|
+
if (capture === undefined || capture.requests.length === 0) {
|
|
101
|
+
return {
|
|
102
|
+
kind: 'error',
|
|
103
|
+
text: 'No final LLM request is available for this session. Send one prompt, wait for it to finish, then run /debug-prompt.',
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
const latest = capture.requests.at(-1);
|
|
107
|
+
if (!turnCompleted(agent, latest.turn)) {
|
|
108
|
+
return { kind: 'error', text: 'The latest model turn is still running. Wait for it to finish, then run /debug-prompt.' };
|
|
109
|
+
}
|
|
110
|
+
const root = resolve(agent.session.header.cwd ?? process.cwd());
|
|
111
|
+
const output = join(root, PROMPT_DEBUG_FILENAME);
|
|
112
|
+
const turns = new Map();
|
|
113
|
+
for (const request of capture.requests) {
|
|
114
|
+
const turn = turns.get(request.turn) ?? { turn: request.turn, requests: [] };
|
|
115
|
+
if (!turns.has(request.turn))
|
|
116
|
+
turns.set(request.turn, turn);
|
|
117
|
+
turn.requests.push(request);
|
|
118
|
+
}
|
|
119
|
+
const document = {
|
|
120
|
+
schemaVersion: 2,
|
|
121
|
+
generatedAt: new Date().toISOString(),
|
|
122
|
+
captureStartedAt,
|
|
123
|
+
captureScope: 'Requests observed by this dsh-tui process; requests from before a session resume cannot be reconstructed.',
|
|
124
|
+
warning: 'Sensitive debug data: contains system prompts, conversation messages, tool schemas, and tool results. It intentionally excludes credentials and transport headers.',
|
|
125
|
+
sessionId,
|
|
126
|
+
turnCount: turns.size,
|
|
127
|
+
requestCount: capture.requests.length,
|
|
128
|
+
turns: [...turns.values()].map(turn => ({
|
|
129
|
+
...turn,
|
|
130
|
+
requestCount: turn.requests.length,
|
|
131
|
+
})),
|
|
132
|
+
};
|
|
133
|
+
try {
|
|
134
|
+
await writeFileAtomic(output, `${JSON.stringify(document, null, 2)}\n`, { mode: 0o600 });
|
|
135
|
+
}
|
|
136
|
+
catch (error) {
|
|
137
|
+
return {
|
|
138
|
+
kind: 'error',
|
|
139
|
+
text: `Could not write prompt debug file: ${error instanceof Error ? error.message : String(error)}`,
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
return {
|
|
143
|
+
kind: 'success',
|
|
144
|
+
text: `Wrote ${capture.requests.length} final LLM request${capture.requests.length === 1 ? '' : 's'} to ${output}. The file contains sensitive conversation and prompt data.`,
|
|
145
|
+
};
|
|
146
|
+
},
|
|
147
|
+
}));
|
|
148
|
+
}
|