@deepseek-harness-tui/dsh-tui 0.8.1 → 0.8.2
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 +22 -13
- package/dsh-ecosystem-spec/protocols/profile-definitions.d.ts +13 -0
- package/dsh-ecosystem-spec/protocols/profile-definitions.js +59 -0
- package/dsh-ecosystem-spec/protocols/tui-channel-http.d.ts +15 -0
- package/dsh-ecosystem-spec/protocols/tui-channel-http.js +32 -0
- package/dsh-ecosystem-spec/protocols/tui-channel.d.ts +59 -0
- package/dsh-ecosystem-spec/protocols/tui-channel.js +166 -0
- package/dsh-ecosystem-spec/protocols/tui-contributions.d.ts +42 -0
- package/dsh-ecosystem-spec/protocols/tui-contributions.js +143 -0
- package/dsh-ecosystem-spec/registry/README.md +14 -0
- package/dsh-ecosystem-spec/registry/contracts/scene-v1alpha1.json +13 -0
- package/dsh-ecosystem-spec/registry/contracts/settings-section-v1alpha1.json +13 -0
- package/dsh-ecosystem-spec/registry/contracts/tui-channel-v1alpha1.json +30 -0
- package/dsh-ecosystem-spec/registry/contracts/workspace-provider-v1alpha1.json +6 -0
- package/{ecosystem-spec → dsh-ecosystem-spec}/registry/permissions-0.1.json +1 -1
- package/dsh-ecosystem-spec/registry/registry-0.15.json +149 -0
- package/lib/types/command-trees.d.ts +2 -1
- package/lib/types/command-trees.d.ts.map +1 -1
- package/lib/types/command-trees.js +1 -1
- package/lib/types/components/LoadedContextPanel.d.ts +10 -5
- package/lib/types/components/LoadedContextPanel.d.ts.map +1 -1
- package/lib/types/components/LoadedContextPanel.js +18 -7
- package/lib/types/components/LogoV2.d.ts.map +1 -1
- package/lib/types/components/LogoV2.js +16 -7
- package/lib/types/components/Markdown.d.ts +13 -1
- package/lib/types/components/Markdown.d.ts.map +1 -1
- package/lib/types/components/Markdown.js +15 -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 +90 -9
- package/lib/types/components/PromptInput.d.ts.map +1 -1
- package/lib/types/components/PromptInput.js +26 -12
- package/lib/types/components/StreamingMarkdown.d.ts +2 -8
- package/lib/types/components/StreamingMarkdown.d.ts.map +1 -1
- package/lib/types/components/StreamingMarkdown.js +51 -9
- package/lib/types/components/activityFrames.d.ts +1 -1
- package/lib/types/components/activityFrames.d.ts.map +1 -1
- package/lib/types/components/activityFrames.js +4 -1
- package/lib/types/components/messages/AssistantThinkingMessage.d.ts +5 -1
- package/lib/types/components/messages/AssistantThinkingMessage.d.ts.map +1 -1
- package/lib/types/components/messages/AssistantThinkingMessage.js +26 -3
- package/lib/types/components/messages/AssistantToolUseMessage.d.ts.map +1 -1
- package/lib/types/components/messages/AssistantToolUseMessage.js +12 -1
- package/lib/types/dsh-adapter/channel.d.ts +31 -4
- package/lib/types/dsh-adapter/channel.d.ts.map +1 -1
- package/lib/types/dsh-adapter/channel.js +267 -102
- package/lib/types/dsh-adapter/childStderr.d.ts.map +1 -1
- package/lib/types/dsh-adapter/childStderr.js +10 -0
- package/lib/types/dsh-adapter/command-attribution.d.ts.map +1 -1
- package/lib/types/dsh-adapter/command-attribution.js +2 -2
- package/lib/types/dsh-adapter/command-trees.d.ts +7 -1
- package/lib/types/dsh-adapter/command-trees.d.ts.map +1 -1
- package/lib/types/dsh-adapter/command-trees.js +70 -20
- package/lib/types/dsh-adapter/component-identity.d.ts +5 -0
- package/lib/types/dsh-adapter/component-identity.d.ts.map +1 -1
- package/lib/types/dsh-adapter/component-identity.js +14 -14
- package/lib/types/dsh-adapter/decision-guard.d.ts.map +1 -1
- package/lib/types/dsh-adapter/decision-guard.js +10 -16
- package/lib/types/dsh-adapter/dialogs.d.ts +2 -3
- package/lib/types/dsh-adapter/dialogs.d.ts.map +1 -1
- package/lib/types/dsh-adapter/dialogs.js +114 -60
- package/lib/types/dsh-adapter/effect-ledger.d.ts +0 -8
- package/lib/types/dsh-adapter/effect-ledger.d.ts.map +1 -1
- package/lib/types/dsh-adapter/effect-ledger.js +73 -53
- package/lib/types/dsh-adapter/extension-events.d.ts.map +1 -1
- package/lib/types/dsh-adapter/extension-events.js +39 -1
- package/lib/types/dsh-adapter/grants.d.ts.map +1 -1
- package/lib/types/dsh-adapter/grants.js +43 -14
- package/lib/types/dsh-adapter/host-access.d.ts +37 -0
- package/lib/types/dsh-adapter/host-access.d.ts.map +1 -0
- package/lib/types/dsh-adapter/host-access.js +652 -0
- package/lib/types/dsh-adapter/host-descriptor.d.ts +2 -2
- package/lib/types/dsh-adapter/host-descriptor.d.ts.map +1 -1
- package/lib/types/dsh-adapter/host-descriptor.js +31 -8
- 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/message-observer.d.ts +10 -15
- package/lib/types/dsh-adapter/message-observer.d.ts.map +1 -1
- package/lib/types/dsh-adapter/message-observer.js +114 -70
- package/lib/types/dsh-adapter/plugin-host.d.ts +39 -6
- package/lib/types/dsh-adapter/plugin-host.d.ts.map +1 -1
- package/lib/types/dsh-adapter/plugin-host.js +139 -44
- package/lib/types/dsh-adapter/plugin-storage.d.ts +0 -5
- package/lib/types/dsh-adapter/plugin-storage.d.ts.map +1 -1
- package/lib/types/dsh-adapter/plugin-storage.js +48 -36
- package/lib/types/dsh-adapter/plugin.d.ts.map +1 -1
- package/lib/types/dsh-adapter/plugin.js +80 -16
- package/lib/types/dsh-adapter/promptDebug.d.ts +0 -6
- package/lib/types/dsh-adapter/promptDebug.d.ts.map +1 -1
- package/lib/types/dsh-adapter/promptDebug.js +13 -4
- package/lib/types/dsh-adapter/renderers.d.ts +6 -14
- package/lib/types/dsh-adapter/renderers.d.ts.map +1 -1
- package/lib/types/dsh-adapter/renderers.js +81 -46
- package/lib/types/dsh-adapter/scenes.d.ts +8 -4
- package/lib/types/dsh-adapter/scenes.d.ts.map +1 -1
- package/lib/types/dsh-adapter/scenes.js +136 -37
- package/lib/types/dsh-adapter/settings-sections.d.ts +7 -3
- package/lib/types/dsh-adapter/settings-sections.d.ts.map +1 -1
- package/lib/types/dsh-adapter/settings-sections.js +106 -26
- package/lib/types/dsh-adapter/shortcuts.d.ts +8 -10
- package/lib/types/dsh-adapter/shortcuts.d.ts.map +1 -1
- package/lib/types/dsh-adapter/shortcuts.js +108 -36
- package/lib/types/dsh-adapter/status.d.ts +6 -7
- package/lib/types/dsh-adapter/status.d.ts.map +1 -1
- package/lib/types/dsh-adapter/status.js +88 -22
- package/lib/types/dsh-adapter/workspaces.d.ts +16 -5
- package/lib/types/dsh-adapter/workspaces.d.ts.map +1 -1
- package/lib/types/dsh-adapter/workspaces.js +287 -170
- package/lib/types/extensions.d.ts +10 -5
- package/lib/types/extensions.d.ts.map +1 -1
- package/lib/types/extensions.js +11 -5
- package/lib/types/force-production-react.d.ts +1 -0
- package/lib/types/force-production-react.d.ts.map +1 -0
- package/lib/types/force-production-react.js +12 -0
- package/lib/types/i18n.d.ts +14 -6
- package/lib/types/i18n.d.ts.map +1 -1
- package/lib/types/i18n.js +4 -2
- package/lib/types/index.d.ts +1 -0
- package/lib/types/index.d.ts.map +1 -1
- package/lib/types/index.js +1 -0
- package/lib/types/ink/components/App.d.ts +2 -0
- package/lib/types/ink/components/App.d.ts.map +1 -1
- package/lib/types/ink/components/App.js +4 -0
- package/lib/types/ink/ink.d.ts +20 -3
- package/lib/types/ink/ink.d.ts.map +1 -1
- package/lib/types/ink/ink.js +57 -5
- package/lib/types/ink/log-update.d.ts.map +1 -1
- package/lib/types/ink/log-update.js +128 -9
- package/lib/types/ink/output.d.ts +28 -2
- package/lib/types/ink/output.d.ts.map +1 -1
- package/lib/types/ink/output.js +158 -12
- package/lib/types/ink/screen.d.ts +39 -0
- package/lib/types/ink/screen.d.ts.map +1 -1
- package/lib/types/ink/screen.js +117 -0
- package/lib/types/ink/terminal.d.ts.map +1 -1
- package/lib/types/ink/terminal.js +10 -2
- package/lib/types/ink/wrapAnsi.d.ts +1 -1
- package/lib/types/ink/wrapAnsi.d.ts.map +1 -1
- package/lib/types/ink/wrapAnsi.js +29 -2
- package/lib/types/plugin-host.d.ts +13 -11
- package/lib/types/plugin-host.d.ts.map +1 -1
- package/lib/types/plugin-host.js +16 -16
- package/lib/types/plugin-spec/registry.d.ts +2 -2
- package/lib/types/plugin-spec/registry.d.ts.map +1 -1
- package/lib/types/plugin-spec/registry.js +113 -18
- package/lib/types/plugin-spec/tui-extension.d.ts.map +1 -1
- package/lib/types/plugin-spec/tui-extension.js +2 -0
- package/lib/types/plugin-spec/validate.d.ts.map +1 -1
- package/lib/types/plugin-spec/validate.js +20 -1
- package/lib/types/scenes.d.ts +2 -1
- package/lib/types/scenes.d.ts.map +1 -1
- package/lib/types/scenes.js +1 -1
- package/lib/types/screens/Chat.d.ts +3 -3
- package/lib/types/screens/Chat.d.ts.map +1 -1
- package/lib/types/screens/Chat.js +38 -17
- package/lib/types/screens/SessionBrowser.d.ts.map +1 -1
- package/lib/types/screens/SessionBrowser.js +10 -3
- package/lib/types/sessions/view.d.ts +3 -2
- package/lib/types/sessions/view.d.ts.map +1 -1
- package/lib/types/sessions/view.js +18 -0
- package/lib/types/settings-sections.d.ts +2 -1
- package/lib/types/settings-sections.d.ts.map +1 -1
- package/lib/types/settings-sections.js +1 -1
- package/lib/types/utils/execFileNoThrow.d.ts.map +1 -1
- package/lib/types/utils/execFileNoThrow.js +3 -0
- package/lib/types/workspaces.d.ts +2 -1
- package/lib/types/workspaces.d.ts.map +1 -1
- package/lib/types/workspaces.js +1 -1
- package/node_modules/@dsh-std/command/CHANGELOG.md +8 -0
- package/node_modules/@dsh-std/command/package.json +1 -1
- package/node_modules/@dsh-std/connection/CHANGELOG.md +8 -0
- package/node_modules/@dsh-std/connection/lib/memory.d.ts +1 -1
- package/node_modules/@dsh-std/connection/lib/memory.d.ts.map +1 -1
- package/node_modules/@dsh-std/connection/lib/memory.js +1 -0
- package/node_modules/@dsh-std/connection/lib/memory.js.map +1 -1
- package/node_modules/@dsh-std/connection/package.json +1 -1
- package/node_modules/@dsh-std/core/CHANGELOG.md +8 -0
- package/node_modules/@dsh-std/core/package.json +1 -1
- package/node_modules/@dsh-std/manifest/CHANGELOG.md +9 -0
- package/node_modules/@dsh-std/manifest/package.json +1 -1
- package/node_modules/@dsh-std/messages/CHANGELOG.md +8 -0
- package/node_modules/@dsh-std/messages/package.json +1 -1
- package/node_modules/@dsh-std/presentation/CHANGELOG.md +9 -0
- package/node_modules/@dsh-std/presentation/README.md +2 -1
- package/node_modules/@dsh-std/presentation/README.zh.md +2 -1
- package/node_modules/@dsh-std/presentation/lib/callback.d.ts +2 -0
- package/node_modules/@dsh-std/presentation/lib/callback.js +179 -0
- package/node_modules/@dsh-std/presentation/lib/callback.js.map +1 -0
- package/node_modules/@dsh-std/presentation/lib/index.d.ts +44 -1
- package/node_modules/@dsh-std/presentation/lib/index.d.ts.map +1 -1
- package/node_modules/@dsh-std/presentation/lib/index.js +6 -3
- package/node_modules/@dsh-std/presentation/lib/index.js.map +1 -1
- package/node_modules/@dsh-std/presentation/package.json +7 -3
- package/node_modules/@dsh-std/storage/CHANGELOG.md +8 -0
- package/node_modules/@dsh-std/storage/package.json +1 -1
- package/package.json +10 -5
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/connection/CHANGELOG.md +8 -0
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/connection/lib/memory.d.ts +1 -1
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/connection/lib/memory.d.ts.map +1 -1
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/connection/lib/memory.js +1 -0
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/connection/lib/memory.js.map +1 -1
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/connection/package.json +1 -1
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/core/CHANGELOG.md +8 -0
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/core/package.json +1 -1
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/manifest/CHANGELOG.md +9 -0
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/manifest/package.json +1 -1
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/presentation/CHANGELOG.md +9 -0
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/presentation/README.md +2 -1
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/presentation/README.zh.md +2 -1
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/presentation/lib/callback.d.ts +2 -0
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/presentation/lib/callback.js +179 -0
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/presentation/lib/callback.js.map +1 -0
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/presentation/lib/index.d.ts +44 -1
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/presentation/lib/index.d.ts.map +1 -1
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/presentation/lib/index.js +6 -3
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/presentation/lib/index.js.map +1 -1
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/presentation/package.json +7 -3
- package/vendor/dsh-std/packages/connection/node_modules/@dsh-std/core/CHANGELOG.md +8 -0
- package/vendor/dsh-std/packages/connection/node_modules/@dsh-std/core/package.json +1 -1
- package/vendor/dsh-std/packages/manifest/node_modules/@dsh-std/core/CHANGELOG.md +8 -0
- package/vendor/dsh-std/packages/manifest/node_modules/@dsh-std/core/package.json +1 -1
- package/vendor/dsh-std/packages/messages/node_modules/@dsh-std/core/CHANGELOG.md +8 -0
- package/vendor/dsh-std/packages/messages/node_modules/@dsh-std/core/package.json +1 -1
- package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/connection/CHANGELOG.md +8 -0
- package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/connection/lib/memory.d.ts +1 -1
- package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/connection/lib/memory.d.ts.map +1 -1
- package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/connection/lib/memory.js +1 -0
- package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/connection/lib/memory.js.map +1 -1
- package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/connection/package.json +1 -1
- package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/core/CHANGELOG.md +8 -0
- package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/core/package.json +1 -1
- package/vendor/dsh-std/packages/storage/node_modules/@dsh-std/core/CHANGELOG.md +8 -0
- package/vendor/dsh-std/packages/storage/node_modules/@dsh-std/core/package.json +1 -1
- package/ecosystem-spec/registry/registry-0.15.json +0 -57
- /package/{ecosystem-spec → dsh-ecosystem-spec}/registry/contracts/decision-events-v1alpha1.json +0 -0
- /package/{ecosystem-spec → dsh-ecosystem-spec}/registry/host-descriptor.tui.example.json +0 -0
- /package/{ecosystem-spec → dsh-ecosystem-spec}/schemas/conformance-claim.schema.json +0 -0
- /package/{ecosystem-spec → dsh-ecosystem-spec}/schemas/effect-ledger-record.schema.json +0 -0
- /package/{ecosystem-spec → dsh-ecosystem-spec}/schemas/host-descriptor.schema.json +0 -0
|
@@ -26,7 +26,12 @@ import { t } from '../i18n.js';
|
|
|
26
26
|
import { modeDisplayName, resolveSessionModes } from '../sessionModes.js';
|
|
27
27
|
import { ActivityTracker } from 'dsh-working-activity/status';
|
|
28
28
|
import { attachSessionToWorkspace } from './workspace.js';
|
|
29
|
-
import { createLocalWorkspaceRuntime } from './workspaces.js';
|
|
29
|
+
import { createLocalWorkspaceRuntime, getHostWorkspaceRuntime } from './workspaces.js';
|
|
30
|
+
import { getHostCommandTrees } from './command-trees.js';
|
|
31
|
+
import { getHostSettingsSections } from './settings-sections.js';
|
|
32
|
+
import { getHostSceneRuntime } from './scenes.js';
|
|
33
|
+
import { getHostRenderers } from './renderers.js';
|
|
34
|
+
import { getHostMessageObserver } from './message-observer.js';
|
|
30
35
|
import { dispatchTuiDecision, dispatchTuiNotification, normalizeCancelDecision } from './extension-events.js';
|
|
31
36
|
import { installDecisionGuard } from './decision-guard.js';
|
|
32
37
|
import { commandOwner } from './command-attribution.js';
|
|
@@ -155,12 +160,25 @@ function preview(text, limit) {
|
|
|
155
160
|
* a loadOlder() restore is not instantly undone. Returns the number of rows
|
|
156
161
|
* folded.
|
|
157
162
|
*/
|
|
158
|
-
function foldRows(rows, cap) {
|
|
163
|
+
function foldRows(rows, cap, cursor) {
|
|
159
164
|
const excess = rows.length - cap;
|
|
160
|
-
if (excess <= 0)
|
|
165
|
+
if (excess <= 0) {
|
|
166
|
+
if (cursor !== undefined)
|
|
167
|
+
cursor.index = 0;
|
|
168
|
+
return 0;
|
|
169
|
+
}
|
|
170
|
+
// Incremental pass: rows only ever append past the fold line and the
|
|
171
|
+
// folded/restored exemptions are permanent, so everything below a cursor
|
|
172
|
+
// over the SAME array identity needs no re-inspection. emit/emitStream
|
|
173
|
+
// fold on every frame during streaming — a full rescan of a long window
|
|
174
|
+
// there was the O(rows) per-frame term of long-session streaming.
|
|
175
|
+
const from = cursor === undefined ? 0 : cursor.rows === rows ? cursor.index : 0;
|
|
176
|
+
if (cursor !== undefined)
|
|
177
|
+
cursor.rows = rows;
|
|
178
|
+
if (excess <= from)
|
|
161
179
|
return 0;
|
|
162
180
|
let folded = 0;
|
|
163
|
-
for (const row of rows.slice(
|
|
181
|
+
for (const row of rows.slice(from, excess)) {
|
|
164
182
|
if (row.folded || row.restored)
|
|
165
183
|
continue;
|
|
166
184
|
if (row.kind !== 'user' && row.kind !== 'assistant' && row.kind !== 'reasoning' && row.kind !== 'tool')
|
|
@@ -182,6 +200,8 @@ function foldRows(rows, cap) {
|
|
|
182
200
|
row.text = preview(row.text, 200);
|
|
183
201
|
}
|
|
184
202
|
}
|
|
203
|
+
if (cursor !== undefined)
|
|
204
|
+
cursor.index = excess;
|
|
185
205
|
return folded;
|
|
186
206
|
}
|
|
187
207
|
/**
|
|
@@ -310,46 +330,44 @@ function restoreToolResult(row, event) {
|
|
|
310
330
|
row.tool.resultFull = result || undefined;
|
|
311
331
|
}
|
|
312
332
|
/**
|
|
313
|
-
*
|
|
314
|
-
*
|
|
315
|
-
* events
|
|
316
|
-
*
|
|
317
|
-
*
|
|
318
|
-
*
|
|
319
|
-
*
|
|
320
|
-
*
|
|
333
|
+
* Prepare durable events for REPLAY (resume / rewind / model-switch fork):
|
|
334
|
+
* drop settled `assistant/chunk` stream deltas — the sealed
|
|
335
|
+
* `assistant/message` events carry the full text and reasoning blocks, so
|
|
336
|
+
* per-token chunks add nothing to the replayed transcript while costing a
|
|
337
|
+
* per-chunk renderEvent pass (a real 4.5MB session logs ~19k chunks against
|
|
338
|
+
* ~30 messages). The trailing chunk run AFTER the last message belongs to an
|
|
339
|
+
* unfinished step (crash-orphaned turn) and is kept, so a resumed session
|
|
340
|
+
* still shows its partial content. Storage-level packed rows
|
|
341
|
+
* (`text-chunks`/`reasoning-chunks`/`tool-call-chunks`) are dropped the
|
|
342
|
+
* same way — defensive: the jsonl reader expands them, but a future
|
|
343
|
+
* direct-pass path must not resurrect them. Replay-side tps sampling is
|
|
344
|
+
* lost with the chunks (a live metric; lastUsage comes from the message's
|
|
345
|
+
* own usage). Live events never go through this.
|
|
321
346
|
*/
|
|
322
|
-
function
|
|
323
|
-
|
|
324
|
-
let run = null;
|
|
325
|
-
const flush = () => {
|
|
326
|
-
if (run === null)
|
|
327
|
-
return;
|
|
328
|
-
const chunk = run.event.data.chunk;
|
|
329
|
-
out.push({
|
|
330
|
-
...run.event,
|
|
331
|
-
data: { ...run.event.data, chunk: { ...chunk, text: run.parts.join('') } },
|
|
332
|
-
});
|
|
333
|
-
run = null;
|
|
334
|
-
};
|
|
347
|
+
function prepareReplayEvents(events) {
|
|
348
|
+
let lastMessageSeq = -1;
|
|
335
349
|
for (const event of events) {
|
|
336
|
-
if (event.type === 'assistant/
|
|
337
|
-
|
|
338
|
-
if (run !== null && run.type === event.data.chunk.type) {
|
|
339
|
-
// oxlint-disable-next-line typescript/no-unnecessary-condition -- durable replay data may lack text
|
|
340
|
-
run.parts.push(event.data.chunk.text ?? '');
|
|
341
|
-
continue;
|
|
342
|
-
}
|
|
343
|
-
flush();
|
|
344
|
-
// oxlint-disable-next-line typescript/no-unnecessary-condition -- durable replay data may lack text
|
|
345
|
-
run = { event, type: event.data.chunk.type, parts: [event.data.chunk.text ?? ''] };
|
|
346
|
-
continue;
|
|
347
|
-
}
|
|
348
|
-
flush();
|
|
349
|
-
out.push(event);
|
|
350
|
+
if (event.type === 'assistant/message')
|
|
351
|
+
lastMessageSeq = event.seq;
|
|
350
352
|
}
|
|
351
|
-
|
|
352
|
-
|
|
353
|
+
return events.filter(event => {
|
|
354
|
+
if (event.type === 'assistant/message')
|
|
355
|
+
return true;
|
|
356
|
+
if (event.type === 'assistant/chunk') {
|
|
357
|
+
// Keep only the in-flight tail (no message sealed after it).
|
|
358
|
+
return lastMessageSeq < 0 || event.seq > lastMessageSeq;
|
|
359
|
+
}
|
|
360
|
+
// Storage-level packed rows: not in the SessionEvent union (they exist
|
|
361
|
+
// only in the durable JSON), so compare through a widened view — the
|
|
362
|
+
// defensive drop is exactly for data the static type doesn't know.
|
|
363
|
+
const packedType = event.type;
|
|
364
|
+
if (packedType === 'text-chunks' ||
|
|
365
|
+
packedType === 'reasoning-chunks' ||
|
|
366
|
+
packedType === 'tool-call-chunks') {
|
|
367
|
+
return false;
|
|
368
|
+
}
|
|
369
|
+
return true;
|
|
370
|
+
});
|
|
353
371
|
}
|
|
354
372
|
/** Buffer below the context window at which CC warns (autoCompact.ts). */
|
|
355
373
|
const CONTEXT_WARNING_BUFFER_TOKENS = 20_000;
|
|
@@ -406,13 +424,13 @@ export function createChannel(ctx, initialAgent, options) {
|
|
|
406
424
|
// messages.observe broker (optional service, C-042): mounted by the
|
|
407
425
|
// dsh-tui-plugin-host row; absent the row, publish is a no-op and nothing
|
|
408
426
|
// else changes (soft degradation, #183).
|
|
409
|
-
const messageObserver = ctx.get('tuiMessageObserver');
|
|
427
|
+
const messageObserver = getHostMessageObserver(ctx.get('tuiMessageObserver'));
|
|
410
428
|
// Workspace registry runtime (optional service, issue #183): mounted by
|
|
411
429
|
// the bundle patch's dsh-tui-workspaces row; absent the row (stale patch
|
|
412
430
|
// or a bare embedder), degrade to the local-only runtime. plugin.ts owns
|
|
413
431
|
// the degraded-boot warning for profile launches.
|
|
414
|
-
const workspaceService = ctx.get('tuiWorkspaces') ?? createLocalWorkspaceRuntime();
|
|
415
|
-
const commandTrees = ctx.get('tuiCommandTrees');
|
|
432
|
+
const workspaceService = getHostWorkspaceRuntime(ctx.get('tuiWorkspaces')) ?? createLocalWorkspaceRuntime();
|
|
433
|
+
const commandTrees = getHostCommandTrees(ctx.get('tuiCommandTrees'));
|
|
416
434
|
// The `/settings` screen reads its host on EVERY render, so the host must
|
|
417
435
|
// be a stable object: a fresh literal per call would re-fire the screen's
|
|
418
436
|
// host-keyed effects endlessly (render → new host → effect → state →
|
|
@@ -423,11 +441,12 @@ export function createChannel(ctx, initialAgent, options) {
|
|
|
423
441
|
// Plugin scene runtime (optional service, same degradation rule as
|
|
424
442
|
// tuiWorkspaces/tuiCommandTrees): mounted by the bundle patch's
|
|
425
443
|
// dsh-tui-scenes row; absent the row, `pluginScene` simply stays undefined.
|
|
426
|
-
const sceneRuntime = ctx.get('tuiScenes');
|
|
444
|
+
const sceneRuntime = getHostSceneRuntime(ctx.get('tuiScenes'));
|
|
445
|
+
const settingsSectionsRuntime = getHostSettingsSections(ctx.get('tuiSettingsSections'));
|
|
427
446
|
// Custom-entry text renderers (optional service, dsh-tui-extensions row):
|
|
428
447
|
// absent the row, unknown plugin event types stay invisible in the
|
|
429
448
|
// transcript, exactly as before the seam existed.
|
|
430
|
-
const rendererRuntime = ctx.get('tuiRenderers');
|
|
449
|
+
const rendererRuntime = getHostRenderers(ctx.get('tuiRenderers'));
|
|
431
450
|
// Shift+Tab session-mode cycle: cordis.yml `modes` wins; absent/empty/
|
|
432
451
|
// atom-less → the built-in default/plan/full cycle (sessionModes.ts).
|
|
433
452
|
const { modes: sessionModes, dropped: droppedModeIds } = resolveSessionModes(options.modes);
|
|
@@ -437,6 +456,9 @@ export function createChannel(ctx, initialAgent, options) {
|
|
|
437
456
|
const listeners = new Set();
|
|
438
457
|
/** True while a frame-aligned stream notification is pending (emitStream). */
|
|
439
458
|
let streamNotifyScheduled = false;
|
|
459
|
+
// foldRows incremental cursor (see foldRows): rows only append past the
|
|
460
|
+
// fold line, so each pass touches only newly-eligible rows.
|
|
461
|
+
const foldCursor = { rows: null, index: 0 };
|
|
440
462
|
let nextNotificationId = 1;
|
|
441
463
|
/** One-shot context-low warning per session (CC's TokenWarning). */
|
|
442
464
|
let contextWarned = false;
|
|
@@ -945,6 +967,7 @@ export function createChannel(ctx, initialAgent, options) {
|
|
|
945
967
|
workingActivity: undefined,
|
|
946
968
|
activityFrames: options.activityFrames,
|
|
947
969
|
diffLayout: options.diffLayout ?? 'auto',
|
|
970
|
+
thinkingFold: options.thinkingFold ?? 'preview',
|
|
948
971
|
activityEnabled: options.activity !== false,
|
|
949
972
|
contextBarEnabled: options.contextBar !== false,
|
|
950
973
|
agentPreset: options.agentPreset,
|
|
@@ -993,7 +1016,7 @@ export function createChannel(ctx, initialAgent, options) {
|
|
|
993
1016
|
};
|
|
994
1017
|
},
|
|
995
1018
|
emit() {
|
|
996
|
-
foldRows(state.rows, MAX_ROWS);
|
|
1019
|
+
foldRows(state.rows, MAX_ROWS, foldCursor);
|
|
997
1020
|
state.version += 1;
|
|
998
1021
|
for (const listener of listeners)
|
|
999
1022
|
listener();
|
|
@@ -1011,7 +1034,7 @@ export function createChannel(ctx, initialAgent, options) {
|
|
|
1011
1034
|
streamNotifyScheduled = true;
|
|
1012
1035
|
const timer = setTimeout(() => {
|
|
1013
1036
|
streamNotifyScheduled = false;
|
|
1014
|
-
foldRows(state.rows, MAX_ROWS);
|
|
1037
|
+
foldRows(state.rows, MAX_ROWS, foldCursor);
|
|
1015
1038
|
for (const listener of listeners)
|
|
1016
1039
|
listener();
|
|
1017
1040
|
}, 16);
|
|
@@ -1280,6 +1303,10 @@ export function createChannel(ctx, initialAgent, options) {
|
|
|
1280
1303
|
// counters land back at the rewind point, matching the fork).
|
|
1281
1304
|
streaming = undefined;
|
|
1282
1305
|
reasoning = undefined;
|
|
1306
|
+
// Stale sealed/thinking bookkeeping belongs to the OLD agent's rows;
|
|
1307
|
+
// keep it out of the next turn's settle logs and revive cache.
|
|
1308
|
+
sealedReasoning.length = 0;
|
|
1309
|
+
lastReasoningRow = undefined;
|
|
1283
1310
|
toolCards.clear();
|
|
1284
1311
|
nextRowId = 0;
|
|
1285
1312
|
state.rows.length = 0;
|
|
@@ -1313,8 +1340,13 @@ export function createChannel(ctx, initialAgent, options) {
|
|
|
1313
1340
|
thinking: 0,
|
|
1314
1341
|
tools: 0,
|
|
1315
1342
|
};
|
|
1316
|
-
|
|
1317
|
-
|
|
1343
|
+
replayEvents(seed);
|
|
1344
|
+
settleStreaming();
|
|
1345
|
+
// A seed ending mid-turn replays a turn/start that set working=true;
|
|
1346
|
+
// the boot path resets this after replay — mirror it here so an idle
|
|
1347
|
+
// rewound agent doesn't sit with a live spinner (a still-running
|
|
1348
|
+
// agent re-asserts on its next event).
|
|
1349
|
+
state.working = handle.agent.status === 'running';
|
|
1318
1350
|
// Rebind subscriptions to the new agent, then free the old one.
|
|
1319
1351
|
const oldHandle = currentHandle;
|
|
1320
1352
|
const sourceSessionId = String(agent.session.id);
|
|
@@ -1373,18 +1405,18 @@ export function createChannel(ctx, initialAgent, options) {
|
|
|
1373
1405
|
// resolving through DSH_TUI_RESUME_SESSION at boot).
|
|
1374
1406
|
if (state.working) {
|
|
1375
1407
|
state.notify(t('resume-while-working'), { color: 'warning' });
|
|
1376
|
-
return false;
|
|
1408
|
+
return { ok: false, reason: 'working' };
|
|
1377
1409
|
}
|
|
1378
1410
|
const agents = ctx.get('agents');
|
|
1379
1411
|
if (!agents) {
|
|
1380
1412
|
state.notify(t('resume-unavailable'), { color: 'error' });
|
|
1381
|
-
return false;
|
|
1413
|
+
return { ok: false, reason: 'unavailable' };
|
|
1382
1414
|
}
|
|
1383
1415
|
// Plugin veto point (tui/session-switch): before any read of the
|
|
1384
1416
|
// target — a veto leaves the live session and its transcript
|
|
1385
1417
|
// untouched.
|
|
1386
1418
|
if (await sessionSwitchVetoed('resume', sessionId))
|
|
1387
|
-
return false;
|
|
1419
|
+
return { ok: false, reason: 'cancelled' };
|
|
1388
1420
|
let handle;
|
|
1389
1421
|
// Compat boundary: register vouched-for legacy event types (e.g.
|
|
1390
1422
|
// activity/status from pre-#143 logs) in every reachable dsh-session
|
|
@@ -1415,7 +1447,7 @@ export function createChannel(ctx, initialAgent, options) {
|
|
|
1415
1447
|
catch (error) {
|
|
1416
1448
|
const message = error instanceof Error ? error.message : String(error);
|
|
1417
1449
|
state.notify(t('resume-failed', { err: message }), { color: 'error', timeoutMs: 8000 });
|
|
1418
|
-
return false;
|
|
1450
|
+
return { ok: false, reason: 'failed', error: message };
|
|
1419
1451
|
}
|
|
1420
1452
|
try {
|
|
1421
1453
|
// `/resume` is an explicit adoption of this persisted conversation.
|
|
@@ -1430,6 +1462,10 @@ export function createChannel(ctx, initialAgent, options) {
|
|
|
1430
1462
|
// rewindTo, plus the context window which the replay re-derives).
|
|
1431
1463
|
streaming = undefined;
|
|
1432
1464
|
reasoning = undefined;
|
|
1465
|
+
// Stale sealed/thinking bookkeeping belongs to the OLD agent's rows;
|
|
1466
|
+
// keep it out of the next turn's settle logs and revive cache.
|
|
1467
|
+
sealedReasoning.length = 0;
|
|
1468
|
+
lastReasoningRow = undefined;
|
|
1433
1469
|
toolCards.clear();
|
|
1434
1470
|
nextRowId = 0;
|
|
1435
1471
|
state.rows.length = 0;
|
|
@@ -1482,9 +1518,12 @@ export function createChannel(ctx, initialAgent, options) {
|
|
|
1482
1518
|
thinking: 0,
|
|
1483
1519
|
tools: 0,
|
|
1484
1520
|
};
|
|
1485
|
-
|
|
1486
|
-
renderEvent(event);
|
|
1521
|
+
replayEvents(handle.agent.session.events);
|
|
1487
1522
|
settleStreaming();
|
|
1523
|
+
// A log ending mid-turn replays a turn/start that set working=true;
|
|
1524
|
+
// mirror the boot path's post-replay reset (a still-running agent
|
|
1525
|
+
// re-asserts on its next event).
|
|
1526
|
+
state.working = handle.agent.status === 'running';
|
|
1488
1527
|
// Rebind subscriptions to the resumed agent, then free the old one.
|
|
1489
1528
|
const oldHandle = currentHandle;
|
|
1490
1529
|
const previousSessionId = String(agent.session.id);
|
|
@@ -1502,7 +1541,7 @@ export function createChannel(ctx, initialAgent, options) {
|
|
|
1502
1541
|
void oldHandle?.dispose().catch(() => { });
|
|
1503
1542
|
clearStagedImages();
|
|
1504
1543
|
notifySessionSwitched('resume', sessionId, previousSessionId);
|
|
1505
|
-
return true;
|
|
1544
|
+
return { ok: true };
|
|
1506
1545
|
},
|
|
1507
1546
|
async newSession() {
|
|
1508
1547
|
// `/new` — start a fresh conversation: brand-new agent + session, the
|
|
@@ -1579,6 +1618,10 @@ export function createChannel(ctx, initialAgent, options) {
|
|
|
1579
1618
|
}
|
|
1580
1619
|
streaming = undefined;
|
|
1581
1620
|
reasoning = undefined;
|
|
1621
|
+
// Stale sealed/thinking bookkeeping belongs to the OLD agent's rows;
|
|
1622
|
+
// keep it out of the next turn's settle logs and revive cache.
|
|
1623
|
+
sealedReasoning.length = 0;
|
|
1624
|
+
lastReasoningRow = undefined;
|
|
1582
1625
|
toolCards.clear();
|
|
1583
1626
|
nextRowId = 0;
|
|
1584
1627
|
state.rows.length = 0;
|
|
@@ -1752,6 +1795,10 @@ export function createChannel(ctx, initialAgent, options) {
|
|
|
1752
1795
|
}
|
|
1753
1796
|
streaming = undefined;
|
|
1754
1797
|
reasoning = undefined;
|
|
1798
|
+
// Stale sealed/thinking bookkeeping belongs to the OLD agent's rows;
|
|
1799
|
+
// keep it out of the next turn's settle logs and revive cache.
|
|
1800
|
+
sealedReasoning.length = 0;
|
|
1801
|
+
lastReasoningRow = undefined;
|
|
1755
1802
|
toolCards.clear();
|
|
1756
1803
|
nextRowId = 0;
|
|
1757
1804
|
state.rows.length = 0;
|
|
@@ -1788,9 +1835,10 @@ export function createChannel(ctx, initialAgent, options) {
|
|
|
1788
1835
|
thinking: 0,
|
|
1789
1836
|
tools: 0,
|
|
1790
1837
|
};
|
|
1791
|
-
|
|
1792
|
-
renderEvent(event);
|
|
1838
|
+
replayEvents(seed);
|
|
1793
1839
|
settleStreaming();
|
|
1840
|
+
// Same mid-turn-seed spinner reset as resume above.
|
|
1841
|
+
state.working = handle.agent.status === 'running';
|
|
1794
1842
|
const oldHandle = currentHandle;
|
|
1795
1843
|
agent = handle.agent;
|
|
1796
1844
|
currentHandle = handle;
|
|
@@ -1865,6 +1913,12 @@ export function createChannel(ctx, initialAgent, options) {
|
|
|
1865
1913
|
state.diffLayout = layout;
|
|
1866
1914
|
state.emit();
|
|
1867
1915
|
},
|
|
1916
|
+
setThinkingFold(mode) {
|
|
1917
|
+
if (mode === state.thinkingFold)
|
|
1918
|
+
return;
|
|
1919
|
+
state.thinkingFold = mode;
|
|
1920
|
+
state.emit();
|
|
1921
|
+
},
|
|
1868
1922
|
setActivityFrames(name) {
|
|
1869
1923
|
if (!isPresetName(name)) {
|
|
1870
1924
|
state.notify(t('unknown-activity-preset', { name }), { color: 'error' });
|
|
@@ -2045,12 +2099,10 @@ export function createChannel(ctx, initialAgent, options) {
|
|
|
2045
2099
|
return settingsHostCache;
|
|
2046
2100
|
},
|
|
2047
2101
|
settingsSections() {
|
|
2048
|
-
|
|
2049
|
-
return sections?.list() ?? [];
|
|
2102
|
+
return settingsSectionsRuntime?.list() ?? [];
|
|
2050
2103
|
},
|
|
2051
2104
|
subscribeSettingsSections(listener) {
|
|
2052
|
-
|
|
2053
|
-
return sections?.subscribe(listener) ?? (() => { });
|
|
2105
|
+
return settingsSectionsRuntime?.subscribe(listener) ?? (() => { });
|
|
2054
2106
|
},
|
|
2055
2107
|
providerSetup() {
|
|
2056
2108
|
// The `/provider` wizard's runtime surface, over the dsh-base seams:
|
|
@@ -2995,16 +3047,60 @@ ${output}
|
|
|
2995
3047
|
}
|
|
2996
3048
|
return streaming;
|
|
2997
3049
|
};
|
|
2998
|
-
|
|
3050
|
+
/** Latest reasoning row keyed by its (turn, step) — lets a resumed
|
|
3051
|
+
* mid-step stream REVIVE the row the replay sealed (crash-orphan tail:
|
|
3052
|
+
* replay folds the partial row, live continuation chunks would
|
|
3053
|
+
* otherwise open a SECOND row for the same step, splitting one
|
|
3054
|
+
* thinking block in two). */
|
|
3055
|
+
let lastReasoningRow;
|
|
3056
|
+
const ensureReasoning = (seq, turn, step) => {
|
|
2999
3057
|
if (reasoning === undefined) {
|
|
3058
|
+
// Same-step revive: the sealed row is this step's thinking — continue
|
|
3059
|
+
// it (durationMs carried over via reasoningStart back-dating).
|
|
3060
|
+
if (lastReasoningRow !== undefined &&
|
|
3061
|
+
turn !== undefined &&
|
|
3062
|
+
lastReasoningRow.turn === turn &&
|
|
3063
|
+
lastReasoningRow.step === step) {
|
|
3064
|
+
reasoning = lastReasoningRow.row;
|
|
3065
|
+
reasoning.streaming = true;
|
|
3066
|
+
const sealedIdx = sealedReasoning.indexOf(reasoning);
|
|
3067
|
+
if (sealedIdx !== -1)
|
|
3068
|
+
sealedReasoning.splice(sealedIdx, 1);
|
|
3069
|
+
reasoningStart = Date.now() - (reasoning.durationMs ?? 0);
|
|
3070
|
+
logForDebugging('thinking: revived sealed reasoning row for same step');
|
|
3071
|
+
return reasoning;
|
|
3072
|
+
}
|
|
3000
3073
|
reasoningStart = Date.now();
|
|
3001
3074
|
reasoning = { id: nextRowId, kind: 'reasoning', text: '', streaming: true, ...seq !== undefined ? { seq } : {} };
|
|
3002
3075
|
nextRowId += 1;
|
|
3003
3076
|
state.rows.push(reasoning);
|
|
3004
3077
|
logForDebugging('thinking: reasoning row open (expanded)');
|
|
3005
3078
|
}
|
|
3079
|
+
if (turn !== undefined && step !== undefined) {
|
|
3080
|
+
lastReasoningRow = { row: reasoning, turn, step };
|
|
3081
|
+
}
|
|
3006
3082
|
return reasoning;
|
|
3007
3083
|
};
|
|
3084
|
+
/** Fold the live reasoning preview the moment the model moves PAST
|
|
3085
|
+
* thinking — the answer's first text token or a tool call — not at
|
|
3086
|
+
* `assistant/message` (end of step). A long reply pushes the thinking
|
|
3087
|
+
* block into terminal scrollback long before the message seals, and
|
|
3088
|
+
* scrollback rows cannot be repainted (the cursor cannot reach them),
|
|
3089
|
+
* so a late fold leaves a stale unfolded preview frozen above the
|
|
3090
|
+
* window — the user scrolls up and the thinking looks "not folded".
|
|
3091
|
+
* Folding while the block still sits in the live window keeps the
|
|
3092
|
+
* shrink inside the diff engine's reachable region. Preview mode only
|
|
3093
|
+
* (`full` holds every block open until turn settle by design). */
|
|
3094
|
+
const foldLiveReasoning = (where) => {
|
|
3095
|
+
if (reasoning === undefined || state.thinkingFold !== 'preview')
|
|
3096
|
+
return;
|
|
3097
|
+
const duration = Math.max(0, Date.now() - reasoningStart);
|
|
3098
|
+
reasoning.durationMs = duration;
|
|
3099
|
+
reasoning.streaming = false;
|
|
3100
|
+
sealedReasoning.push(reasoning);
|
|
3101
|
+
reasoning = undefined;
|
|
3102
|
+
logForDebugging(`thinking: folded at ${where} (${duration}ms)`);
|
|
3103
|
+
};
|
|
3008
3104
|
const settleStreaming = () => {
|
|
3009
3105
|
if (streaming !== undefined)
|
|
3010
3106
|
streaming.streaming = false;
|
|
@@ -3070,6 +3166,24 @@ ${output}
|
|
|
3070
3166
|
};
|
|
3071
3167
|
}
|
|
3072
3168
|
};
|
|
3169
|
+
/** True while the durable transcript is being replayed (boot /resume /
|
|
3170
|
+
* rewind / model-switch fork). The assistant/message reasoning-rebuild
|
|
3171
|
+
* branch below must run ONLY on this path: in a live stream the chunks
|
|
3172
|
+
* already created the reasoning row, and foldLiveReasoning clears the
|
|
3173
|
+
* `reasoning` handle before assistant/message arrives — so
|
|
3174
|
+
* `reasoning === undefined` alone cannot tell replay from live, and
|
|
3175
|
+
* using it would rebuild a second thinking block per step. */
|
|
3176
|
+
let replaying = false;
|
|
3177
|
+
const replayEvents = (events) => {
|
|
3178
|
+
replaying = true;
|
|
3179
|
+
try {
|
|
3180
|
+
for (const event of prepareReplayEvents(events))
|
|
3181
|
+
renderEvent(event);
|
|
3182
|
+
}
|
|
3183
|
+
finally {
|
|
3184
|
+
replaying = false;
|
|
3185
|
+
}
|
|
3186
|
+
};
|
|
3073
3187
|
const renderEvent = (event) => {
|
|
3074
3188
|
switch (event.type) {
|
|
3075
3189
|
case 'user/message': {
|
|
@@ -3151,13 +3265,18 @@ ${output}
|
|
|
3151
3265
|
const chunk = event.data.chunk;
|
|
3152
3266
|
if (chunk.type === 'text-delta') {
|
|
3153
3267
|
if (chunk.text) {
|
|
3268
|
+
// Fold the thinking preview while it is still in the live
|
|
3269
|
+
// window (see foldLiveReasoning) — before this text grows the
|
|
3270
|
+
// transcript and pushes the block into scrollback.
|
|
3271
|
+
foldLiveReasoning('first text token');
|
|
3154
3272
|
ensureStreaming(event.seq).text += chunk.text;
|
|
3155
3273
|
state.responseChars += chunk.text.length;
|
|
3156
3274
|
}
|
|
3157
3275
|
}
|
|
3158
3276
|
else if (chunk.type === 'reasoning-delta') {
|
|
3159
|
-
if (chunk.text)
|
|
3160
|
-
ensureReasoning(event.seq).text += chunk.text;
|
|
3277
|
+
if (chunk.text) {
|
|
3278
|
+
ensureReasoning(event.seq, event.data.turn, event.data.step).text += chunk.text;
|
|
3279
|
+
}
|
|
3161
3280
|
}
|
|
3162
3281
|
const step = tpsStep;
|
|
3163
3282
|
if (step !== undefined &&
|
|
@@ -3178,6 +3297,30 @@ ${output}
|
|
|
3178
3297
|
}
|
|
3179
3298
|
case 'assistant/message': {
|
|
3180
3299
|
const text = textOf(event.data.message.content);
|
|
3300
|
+
// Replay without chunk deltas (prepareReplayEvents drops settled
|
|
3301
|
+
// ones): rebuild the reasoning row from the sealed message's
|
|
3302
|
+
// reasoning blocks. Replay-only — gated on the `replaying` flag,
|
|
3303
|
+
// not on `reasoning === undefined`: a live stream's chunks already
|
|
3304
|
+
// created the row, and foldLiveReasoning has cleared the `reasoning`
|
|
3305
|
+
// handle by the time this event lands, so the undefined check alone
|
|
3306
|
+
// would rebuild a duplicate thinking block per step. Pushed BEFORE
|
|
3307
|
+
// the assistant row so the transcript order matches the live
|
|
3308
|
+
// stream; settled (folded) immediately, durationMs unknown without
|
|
3309
|
+
// a live clock.
|
|
3310
|
+
if (replaying && reasoning === undefined) {
|
|
3311
|
+
const reasoningText = event.data.message.content
|
|
3312
|
+
.map(block => (block.type === 'reasoning' ? block.text : ''))
|
|
3313
|
+
.join('');
|
|
3314
|
+
if (reasoningText !== '') {
|
|
3315
|
+
state.rows.push({
|
|
3316
|
+
id: nextRowId,
|
|
3317
|
+
kind: 'reasoning',
|
|
3318
|
+
text: reasoningText,
|
|
3319
|
+
seq: event.seq,
|
|
3320
|
+
});
|
|
3321
|
+
nextRowId += 1;
|
|
3322
|
+
}
|
|
3323
|
+
}
|
|
3181
3324
|
// Reasoning/tool-only steps emit no text: creating an assistant row
|
|
3182
3325
|
// anyway leaves an empty `●` bullet in the transcript. A pre-existing
|
|
3183
3326
|
// streaming row always has text (ensureStreaming is only reached on
|
|
@@ -3191,10 +3334,16 @@ ${output}
|
|
|
3191
3334
|
}
|
|
3192
3335
|
streaming = undefined;
|
|
3193
3336
|
if (reasoning !== undefined) {
|
|
3194
|
-
//
|
|
3195
|
-
//
|
|
3196
|
-
//
|
|
3337
|
+
// Backstop fold: reasoning whose step ended with no text token
|
|
3338
|
+
// and no tool call (foldLiveReasoning handles those earlier —
|
|
3339
|
+
// while the block is still in the repaintable live window;
|
|
3340
|
+
// here a long reply may already have pushed it into scrollback,
|
|
3341
|
+
// where the shrink cannot be repainted). `full` mode
|
|
3342
|
+
// (/settings opt-in) keeps the block expanded until turn settle
|
|
3343
|
+
// — settleStreaming folds the sealed rows then.
|
|
3197
3344
|
reasoning.durationMs = Math.max(0, Date.now() - reasoningStart);
|
|
3345
|
+
if (state.thinkingFold === 'preview')
|
|
3346
|
+
reasoning.streaming = false;
|
|
3198
3347
|
sealedReasoning.push(reasoning);
|
|
3199
3348
|
logForDebugging(`thinking: step sealed (${reasoning.durationMs}ms), expanded until turn/end`);
|
|
3200
3349
|
}
|
|
@@ -3263,6 +3412,10 @@ ${output}
|
|
|
3263
3412
|
// no card is a no-op below.
|
|
3264
3413
|
if (event.data.name === 'ask_user_question')
|
|
3265
3414
|
break;
|
|
3415
|
+
// Reasoning that led to a tool call is done thinking — fold the
|
|
3416
|
+
// preview now, before the tool card grows the transcript past it
|
|
3417
|
+
// (see foldLiveReasoning).
|
|
3418
|
+
foldLiveReasoning('tool call');
|
|
3266
3419
|
const card = {
|
|
3267
3420
|
id: nextRowId,
|
|
3268
3421
|
kind: 'tool',
|
|
@@ -3459,8 +3612,7 @@ ${output}
|
|
|
3459
3612
|
}
|
|
3460
3613
|
};
|
|
3461
3614
|
// Replay the durable transcript first, then follow live events.
|
|
3462
|
-
|
|
3463
|
-
renderEvent(event);
|
|
3615
|
+
replayEvents(agent.session.events);
|
|
3464
3616
|
settleStreaming();
|
|
3465
3617
|
// Attached to an idle agent: any replayed turn/start belongs to a previous
|
|
3466
3618
|
// session run, so the spinner must not come up on boot.
|
|
@@ -3756,47 +3908,60 @@ function usageOutputTokens(usage) {
|
|
|
3756
3908
|
: undefined;
|
|
3757
3909
|
}
|
|
3758
3910
|
/**
|
|
3759
|
-
*
|
|
3760
|
-
*
|
|
3761
|
-
*
|
|
3762
|
-
*
|
|
3763
|
-
*
|
|
3911
|
+
* `@` file listing through the leaf's fs service (dsh-fs-local): skips
|
|
3912
|
+
* VCS/dependency/build dirs and rotates across directory listings so one
|
|
3913
|
+
* large subtree cannot consume the global MAX_FILES budget. That budget also
|
|
3914
|
+
* bounds directory reads without imposing an arbitrary source-tree depth.
|
|
3915
|
+
* Relative directories retain the trailing `/` that FileSuggestions expects.
|
|
3916
|
+
* Unreadable subtrees are skipped, not fatal.
|
|
3764
3917
|
*/
|
|
3765
3918
|
async function listFilesDeep(fs, root) {
|
|
3766
3919
|
if (!fs)
|
|
3767
3920
|
return [];
|
|
3768
3921
|
const out = [];
|
|
3769
|
-
const SKIP = new Set(['node_modules', '.git', '.hg', '.svn', '.DS_Store', 'dist'
|
|
3770
|
-
const
|
|
3922
|
+
const SKIP = new Set(['node_modules', '.git', '.hg', '.svn', '.DS_Store', 'dist']);
|
|
3923
|
+
const BUILD_DIR = /^(?:build(?:[-_].*)?|cmake-build(?:[-_].*)?)$/i;
|
|
3771
3924
|
const MAX_FILES = 100;
|
|
3772
|
-
const
|
|
3773
|
-
|
|
3774
|
-
|
|
3775
|
-
|
|
3776
|
-
|
|
3777
|
-
|
|
3778
|
-
|
|
3779
|
-
|
|
3780
|
-
|
|
3781
|
-
|
|
3925
|
+
const directories = [{ dir: root, prefix: '', index: 0 }];
|
|
3926
|
+
while (directories.length > 0 && out.length < MAX_FILES) {
|
|
3927
|
+
const current = directories.shift();
|
|
3928
|
+
if (!current)
|
|
3929
|
+
continue;
|
|
3930
|
+
if (!current.entries) {
|
|
3931
|
+
try {
|
|
3932
|
+
const target = await fs.resolve(current.dir);
|
|
3933
|
+
current.entries = await fs.listDir(target);
|
|
3934
|
+
}
|
|
3935
|
+
catch {
|
|
3936
|
+
continue; // unreadable subtree — skip
|
|
3937
|
+
}
|
|
3782
3938
|
}
|
|
3783
|
-
|
|
3784
|
-
|
|
3785
|
-
|
|
3786
|
-
if (SKIP.has(
|
|
3939
|
+
let entry;
|
|
3940
|
+
while (current.index < current.entries.length) {
|
|
3941
|
+
const candidate = current.entries[current.index++];
|
|
3942
|
+
if (SKIP.has(candidate.name) || BUILD_DIR.test(candidate.name))
|
|
3787
3943
|
continue;
|
|
3788
|
-
|
|
3789
|
-
|
|
3790
|
-
|
|
3944
|
+
entry = candidate;
|
|
3945
|
+
break;
|
|
3946
|
+
}
|
|
3947
|
+
if (!entry)
|
|
3948
|
+
continue;
|
|
3949
|
+
if (current.index < current.entries.length)
|
|
3950
|
+
directories.push(current);
|
|
3951
|
+
const rel = current.prefix ? `${current.prefix}/${entry.name}` : entry.name;
|
|
3952
|
+
if (entry.type === 'directory') {
|
|
3953
|
+
out.push(`${rel}/`);
|
|
3954
|
+
directories.push({
|
|
3791
3955
|
// oxlint-disable-next-line typescript/no-unnecessary-condition -- runtime guard: symlink targets optional
|
|
3792
|
-
|
|
3793
|
-
|
|
3794
|
-
|
|
3795
|
-
|
|
3796
|
-
}
|
|
3956
|
+
dir: entry.target?.displayPath ?? join(current.dir, entry.name),
|
|
3957
|
+
prefix: rel,
|
|
3958
|
+
index: 0,
|
|
3959
|
+
});
|
|
3797
3960
|
}
|
|
3798
|
-
|
|
3799
|
-
|
|
3961
|
+
else if (entry.type === 'file') {
|
|
3962
|
+
out.push(rel);
|
|
3963
|
+
}
|
|
3964
|
+
}
|
|
3800
3965
|
return out;
|
|
3801
3966
|
}
|
|
3802
3967
|
/** One attached file's contribution is capped so an absent-minded `@` of a
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"childStderr.d.ts","sourceRoot":"","sources":["../../../src/dsh-adapter/childStderr.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAsDH;;;;;GAKG;AACH,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,GAAG,MAAM,IAAI,CA8BhF;AAED,0EAA0E;AAC1E,MAAM,WAAW,iBAAiB;IAChC,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,OAAO,GAAG,SAAS,GAAG,SAAS,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAA;CAChG;AAED,MAAM,WAAW,0BAA0B;IACzC,qFAAqF;IACrF,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,8EAA8E;IAC9E,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,yFAAyF;IACzF,aAAa,CAAC,EAAE,MAAM,CAAA;CACvB;AAED,MAAM,WAAW,mBAAmB;IAClC,gCAAgC;IAChC,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,iDAAiD;IACjD,OAAO,IAAI,IAAI,CAAA;CAChB;AAED;;;;;GAKG;AACH,wBAAgB,yBAAyB,CACvC,MAAM,EAAE,iBAAiB,EACzB,OAAO,GAAE,0BAA+B,GACvC,mBAAmB,
|
|
1
|
+
{"version":3,"file":"childStderr.d.ts","sourceRoot":"","sources":["../../../src/dsh-adapter/childStderr.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAsDH;;;;;GAKG;AACH,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,GAAG,MAAM,IAAI,CA8BhF;AAED,0EAA0E;AAC1E,MAAM,WAAW,iBAAiB;IAChC,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,OAAO,GAAG,SAAS,GAAG,SAAS,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAA;CAChG;AAED,MAAM,WAAW,0BAA0B;IACzC,qFAAqF;IACrF,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,8EAA8E;IAC9E,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,yFAAyF;IACzF,aAAa,CAAC,EAAE,MAAM,CAAA;CACvB;AAED,MAAM,WAAW,mBAAmB;IAClC,gCAAgC;IAChC,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,iDAAiD;IACjD,OAAO,IAAI,IAAI,CAAA;CAChB;AAED;;;;;GAKG;AACH,wBAAgB,yBAAyB,CACvC,MAAM,EAAE,iBAAiB,EACzB,OAAO,GAAE,0BAA+B,GACvC,mBAAmB,CAqDrB"}
|
|
@@ -145,6 +145,16 @@ export function createChildStderrReporter(notify, options = {}) {
|
|
|
145
145
|
// A burst inside the cooldown window is counted but stays silent.
|
|
146
146
|
if ((mutedUntil.get(line) ?? 0) > Date.now())
|
|
147
147
|
return;
|
|
148
|
+
// Prune expired cooldowns on the way in: entries are child-stderr
|
|
149
|
+
// lines and a long-lived MCP reconnect loop would otherwise grow the
|
|
150
|
+
// dedup table without bound.
|
|
151
|
+
if (mutedUntil.size > 64) {
|
|
152
|
+
const now = Date.now();
|
|
153
|
+
for (const [key, until] of mutedUntil) {
|
|
154
|
+
if (until <= now)
|
|
155
|
+
mutedUntil.delete(key);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
148
158
|
mutedUntil.set(line, Date.now() + cooldownMs);
|
|
149
159
|
const text = group.count > 1
|
|
150
160
|
? t('child-stderr-line-repeat', { line, count: group.count })
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"command-attribution.d.ts","sourceRoot":"","sources":["../../../src/dsh-adapter/command-attribution.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAClD,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,yBAAyB,CAAA;
|
|
1
|
+
{"version":3,"file":"command-attribution.d.ts","sourceRoot":"","sources":["../../../src/dsh-adapter/command-attribution.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAClD,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,yBAAyB,CAAA;AAGxE,MAAM,WAAW,YAAY;IAC3B,WAAW,EAAE,MAAM,CAAA;IACnB,YAAY,EAAE,MAAM,CAAA;IACpB,SAAS,EAAE,MAAM,CAAA;CAClB;AAQD;;4DAE4D;AAC5D,wBAAgB,WAAW,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,CAQhD;AAYD,oFAAoF;AACpF,wBAAgB,iBAAiB,CAC/B,GAAG,EAAE,OAAO,EACZ,UAAU,EAAE,OAAO,EACnB,QAAQ,EAAE,yBAAyB,EACnC,SAAS,EAAE,MAAM,GAChB,IAAI,CAUN;AAED,yEAAyE;AACzE,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,OAAO,EAAE,UAAU,EAAE,OAAO,EAAE,YAAY,EAAE,MAAM,GAAG,IAAI,CAMjG;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,OAAO,EAAE,UAAU,EAAE,OAAO,GAAG,YAAY,GAAG,SAAS,CAKxF"}
|