@xp266/dshtui 0.1.0 → 0.1.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/bin/dshtui.js +9 -5
- package/cordis.patch.yml +9 -19
- package/lib/index.mjs +29 -16
- package/package.json +25 -24
- package/src/chat/bridge.ts +27 -7
- package/src/chat/interactions.ts +7 -8
- package/src/chat/session-list.ts +4 -4
- package/src/contract/upstream.ts +1 -1
- package/src/index.tsx +6 -5
- package/src/ui/hooks/use-chat-events.ts +1 -0
package/bin/dshtui.js
CHANGED
|
@@ -29,7 +29,11 @@ function fail(message) {
|
|
|
29
29
|
process.exit(1)
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
-
|
|
32
|
+
// Windows resolves `dsh` through a .cmd shim, which only spawns with a
|
|
33
|
+
// shell; the command line is built as one quoted string there because
|
|
34
|
+
// passing an args array alongside shell:true is deprecated (DEP0190).
|
|
35
|
+
const windows = process.platform === 'win32'
|
|
36
|
+
const dshProbe = spawnSync('dsh', ['--version'], { stdio: 'ignore', shell: windows })
|
|
33
37
|
if (dshProbe.error !== undefined) {
|
|
34
38
|
fail('the dsh CLI was not found on PATH; install it with: npm install -g @deepseek-ai/dsh')
|
|
35
39
|
}
|
|
@@ -47,11 +51,11 @@ if (!existsSync(profileDir)) {
|
|
|
47
51
|
}
|
|
48
52
|
|
|
49
53
|
const argv = process.argv.slice(2)
|
|
50
|
-
const
|
|
54
|
+
const quote = arg => /^[A-Za-z0-9_@%+=:,./-]+$/.test(arg) ? arg : `"${arg.replaceAll('"', '\\"')}"`
|
|
55
|
+
const command = ['dsh', '--profile', profile, ...argv].map(quote).join(' ')
|
|
56
|
+
const child = spawn(windows ? command : 'dsh', windows ? [] : ['--profile', profile, ...argv], {
|
|
51
57
|
stdio: 'inherit',
|
|
52
|
-
...(
|
|
53
|
-
? { shell: true, windowsVerbatimArguments: true }
|
|
54
|
-
: {}),
|
|
58
|
+
...(windows ? { shell: true } : {}),
|
|
55
59
|
})
|
|
56
60
|
child.on('error', () => {
|
|
57
61
|
fail('the dsh CLI was not found on PATH; install it with: npm install -g @deepseek-ai/dsh')
|
package/cordis.patch.yml
CHANGED
|
@@ -1,29 +1,19 @@
|
|
|
1
1
|
- insert:
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
backend: json
|
|
12
|
-
# Persisted title/blank hints so the session picker lists cold sessions
|
|
13
|
-
# without reading their logs.
|
|
14
|
-
- id: session-projection-cache
|
|
15
|
-
name: '@deepseek-ai/dsh-session-projection-cache'
|
|
16
|
-
config:
|
|
17
|
-
writeEveryEvents: 200
|
|
18
|
-
writeIntervalMs: 5000
|
|
2
|
+
# The durable storage stack (storage, storage-json, storage-domain,
|
|
3
|
+
# session-projection-cache) is inserted by the dsh-base bundle since
|
|
4
|
+
# 0.1.2-rc.1 with the same configuration this file used to carry —
|
|
5
|
+
# re-inserting it here would collide with duplicate loader entry ids.
|
|
6
|
+
# The standard preset's subagent tool enables modelSelectionSettings,
|
|
7
|
+
# which requires this settings service in the host scope (the web bundle
|
|
8
|
+
# mounts the same row for its own roster).
|
|
9
|
+
- id: subagent-model-selection-settings
|
|
10
|
+
name: '@deepseek-ai/dsh-tool-subagent/model-selection-settings'
|
|
19
11
|
- id: workspace
|
|
20
12
|
name: '@deepseek-ai/dsh-workspace'
|
|
21
13
|
- id: agent-presets
|
|
22
14
|
name: '@deepseek-ai/dsh-agent-presets'
|
|
23
15
|
config:
|
|
24
16
|
default: standard
|
|
25
|
-
- id: cordis-host-runner
|
|
26
|
-
name: '@deepseek-ai/dsh-cordis-host-runner'
|
|
27
17
|
# Durable image storage comes from the dsh base bundle's attachment-local
|
|
28
18
|
# row; the TUI resolves ctx.attachments at send time and degrades to a
|
|
29
19
|
# plain text message when the service is absent.
|
package/lib/index.mjs
CHANGED
|
@@ -14,7 +14,6 @@ import Prism from "prismjs";
|
|
|
14
14
|
import { marked } from "marked";
|
|
15
15
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
16
16
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
17
|
-
import { resolveSessionPreset } from "@deepseek-ai/dsh-agent-presets";
|
|
18
17
|
import { installModelSelection } from "@deepseek-ai/dsh-agent";
|
|
19
18
|
import { ReasoningEffortId, createUserMessage } from "@deepseek-ai/dsh-llm";
|
|
20
19
|
import { SessionId } from "@deepseek-ai/dsh-session";
|
|
@@ -6201,7 +6200,7 @@ function MessageList({ messages, height, width, scrollTop, onScroll, interactive
|
|
|
6201
6200
|
}
|
|
6202
6201
|
//#endregion
|
|
6203
6202
|
//#region src/contract/upstream.ts
|
|
6204
|
-
const UPSTREAM_FLOOR_VERSION = "0.1.
|
|
6203
|
+
const UPSTREAM_FLOOR_VERSION = "0.1.2-rc.1";
|
|
6205
6204
|
const UPSTREAM_CEILING_VERSION = "0.2.0";
|
|
6206
6205
|
const UPSTREAM_FRAMEWORK_MAJORS = {
|
|
6207
6206
|
"@deepseek-ai/cordis": 4,
|
|
@@ -7204,9 +7203,9 @@ async function computeSessionList(ctx) {
|
|
|
7204
7203
|
attached.add(id);
|
|
7205
7204
|
if (session.header.origin === "subagent") continue;
|
|
7206
7205
|
if (archived.has(id)) continue;
|
|
7207
|
-
if (isBlankSession(session.
|
|
7208
|
-
const title = titleService?.get?.(session)?.title ?? firstUserText(session.
|
|
7209
|
-
summaries.push(toSummary(id, title, session.header.cwd, session.header.createdAt, lastPromptAt(session.
|
|
7206
|
+
if (isBlankSession(session.snapshotEvents())) continue;
|
|
7207
|
+
const title = titleService?.get?.(session)?.title ?? firstUserText(session.snapshotEvents());
|
|
7208
|
+
summaries.push(toSummary(id, title, session.header.cwd, session.header.createdAt, lastPromptAt(session.snapshotEvents()), workspacePaths));
|
|
7210
7209
|
}
|
|
7211
7210
|
const persistence = ctx.get("sessionPersistence");
|
|
7212
7211
|
await mergeColdSummaries(ctx, persistence, await listColdHeaders(ctx, persistence, attached), archived, workspacePaths, summaries);
|
|
@@ -9650,12 +9649,12 @@ function registerInteractionChannels(ctx, store) {
|
|
|
9650
9649
|
const offApproval = ctx.on("approval/request", async (req) => {
|
|
9651
9650
|
return store.pushApproval(req.toolName, req.callId, req.reason, req.signal);
|
|
9652
9651
|
});
|
|
9653
|
-
const
|
|
9652
|
+
const offQuestions = ctx.on("user-questions/request", async (request) => {
|
|
9654
9653
|
return store.pushQuestion(request, request.signal);
|
|
9655
|
-
}
|
|
9654
|
+
});
|
|
9656
9655
|
return () => {
|
|
9657
9656
|
offApproval();
|
|
9658
|
-
|
|
9657
|
+
offQuestions();
|
|
9659
9658
|
};
|
|
9660
9659
|
}
|
|
9661
9660
|
//#endregion
|
|
@@ -10173,6 +10172,17 @@ async function createChatBridge(ctx) {
|
|
|
10173
10172
|
image: info.inputModalities?.includes("image") ?? false
|
|
10174
10173
|
};
|
|
10175
10174
|
}
|
|
10175
|
+
function resolveSessionPreset(session) {
|
|
10176
|
+
const events = session.snapshotEvents();
|
|
10177
|
+
for (let index = events.length - 1; index >= 0; index -= 1) {
|
|
10178
|
+
const event = events[index];
|
|
10179
|
+
if (event?.type === "agent-preset/selected") {
|
|
10180
|
+
const data = event.data;
|
|
10181
|
+
if (data?.agentPreset !== void 0) return data.agentPreset;
|
|
10182
|
+
}
|
|
10183
|
+
}
|
|
10184
|
+
return session.header?.agentPreset;
|
|
10185
|
+
}
|
|
10176
10186
|
async function createAgent(cwd, presetId) {
|
|
10177
10187
|
const resolved = presetId ?? (await presets?.resolve())?.id;
|
|
10178
10188
|
const meta = {
|
|
@@ -10245,21 +10255,21 @@ async function createChatBridge(ctx) {
|
|
|
10245
10255
|
async function openSession(id) {
|
|
10246
10256
|
const sessionId = SessionId(id);
|
|
10247
10257
|
if (activeAgent.id === sessionId) {
|
|
10248
|
-
for (const event of activeAgent.session.
|
|
10258
|
+
for (const event of activeAgent.session.snapshotEvents()) emit(event);
|
|
10249
10259
|
return;
|
|
10250
10260
|
}
|
|
10251
10261
|
const existing = ctx.agents.get(sessionId);
|
|
10252
10262
|
if (existing !== void 0) {
|
|
10253
10263
|
await activateAgent(void 0, existing);
|
|
10254
10264
|
syncCwd();
|
|
10255
|
-
for (const event of activeAgent.session.
|
|
10265
|
+
for (const event of activeAgent.session.snapshotEvents()) emit(event);
|
|
10256
10266
|
refreshAgentState();
|
|
10257
10267
|
return;
|
|
10258
10268
|
}
|
|
10259
10269
|
const handle = await resumeAgent(sessionId);
|
|
10260
10270
|
await activateAgent(handle, handle.agent);
|
|
10261
10271
|
syncCwd();
|
|
10262
|
-
for (const event of activeAgent.session.
|
|
10272
|
+
for (const event of activeAgent.session.snapshotEvents()) emit(event);
|
|
10263
10273
|
refreshAgentState();
|
|
10264
10274
|
}
|
|
10265
10275
|
function syncCwd() {
|
|
@@ -10312,7 +10322,7 @@ async function createChatBridge(ctx) {
|
|
|
10312
10322
|
if (presets === void 0) throw new Error("agent presets are not configured");
|
|
10313
10323
|
if (currentPreset() === id) return;
|
|
10314
10324
|
const session = activeAgent.session;
|
|
10315
|
-
if (!isBlankSession(session.
|
|
10325
|
+
if (!isBlankSession(session.snapshotEvents())) throw new Error("the preset is fixed once the session has started; use /new to start a new session");
|
|
10316
10326
|
const preset = await presets.recompose(activeAgent.ctx, id);
|
|
10317
10327
|
session.append("agent-preset/selected", { agentPreset: preset.id });
|
|
10318
10328
|
}
|
|
@@ -10487,12 +10497,12 @@ async function createChatBridge(ctx) {
|
|
|
10487
10497
|
currentEffort,
|
|
10488
10498
|
effortName,
|
|
10489
10499
|
selectEffort,
|
|
10490
|
-
permissionMode: () => permission()?.current(activeAgent.session.
|
|
10500
|
+
permissionMode: () => permission()?.current(activeAgent.session.snapshotEvents()) ?? PERMISSION_PRESETS[0],
|
|
10491
10501
|
cyclePermission: () => {
|
|
10492
10502
|
const service = permission();
|
|
10493
10503
|
if (service === void 0) return;
|
|
10494
10504
|
try {
|
|
10495
|
-
const current = service.current(activeAgent.session.
|
|
10505
|
+
const current = service.current(activeAgent.session.snapshotEvents());
|
|
10496
10506
|
const names = service.names.length > 0 ? service.names : PERMISSION_PRESETS;
|
|
10497
10507
|
const next = names[(names.indexOf(current) + 1) % names.length];
|
|
10498
10508
|
service.set(activeAgent.session, next);
|
|
@@ -11294,7 +11304,11 @@ function apply(ctx, config = Config(DEFAULT_CONFIG)) {
|
|
|
11294
11304
|
const restorePerformance = startPerformanceGuard();
|
|
11295
11305
|
const disposeCrashHandlers = installCrashHandlers({ exit: env.crashExit });
|
|
11296
11306
|
const drift = upstreamDriftSummary();
|
|
11297
|
-
if (drift !== void 0)
|
|
11307
|
+
if (drift !== void 0) {
|
|
11308
|
+
const message = `dshtui requires dsh harness packages in ${UPSTREAM_SUPPORTED_RANGE} (found ${drift.kind}: ${drift.versions.join(", ")}); upgrade the dsh CLI with: npm install -g @deepseek-ai/dsh@latest`;
|
|
11309
|
+
error("boot", message);
|
|
11310
|
+
throw new Error(message);
|
|
11311
|
+
}
|
|
11298
11312
|
const capture = createScreenCapture();
|
|
11299
11313
|
let bridge;
|
|
11300
11314
|
let app;
|
|
@@ -11362,7 +11376,6 @@ function apply(ctx, config = Config(DEFAULT_CONFIG)) {
|
|
|
11362
11376
|
};
|
|
11363
11377
|
const themeScope = registerThemeSettings(ctx);
|
|
11364
11378
|
openBootLog();
|
|
11365
|
-
if (drift !== void 0) emitBootLine(`warning: upstream version drift (${drift.kind})`);
|
|
11366
11379
|
emitBootLine("terminal: probing color support");
|
|
11367
11380
|
(async () => {
|
|
11368
11381
|
const probed = await probeColorLevel();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xp266/dshtui",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "ink-based terminal UI plugin for DeepSeek Harness",
|
|
6
6
|
"engines": {
|
|
@@ -38,17 +38,17 @@
|
|
|
38
38
|
],
|
|
39
39
|
"license": "MIT",
|
|
40
40
|
"peerDependencies": {
|
|
41
|
-
"@deepseek-ai/cordis": "^4.0.
|
|
42
|
-
"@deepseek-ai/dsh-agent": "^0.1.
|
|
43
|
-
"@deepseek-ai/dsh-agent-default-model": "^0.1.
|
|
44
|
-
"@deepseek-ai/dsh-agent-loop": "^0.1.
|
|
45
|
-
"@deepseek-ai/dsh-agent-presets": "^0.1.
|
|
46
|
-
"@deepseek-ai/dsh-attachment": "^0.1.
|
|
47
|
-
"@deepseek-ai/dsh-credentials": "^0.1.
|
|
48
|
-
"@deepseek-ai/dsh-llm": "^0.1.
|
|
49
|
-
"@deepseek-ai/dsh-session": "^0.1.
|
|
50
|
-
"@deepseek-ai/dsh-settings": "^0.1.
|
|
51
|
-
"@deepseek-ai/schemastery": "^3.18.
|
|
41
|
+
"@deepseek-ai/cordis": "^4.0.2",
|
|
42
|
+
"@deepseek-ai/dsh-agent": "^0.1.2-rc.1",
|
|
43
|
+
"@deepseek-ai/dsh-agent-default-model": "^0.1.2-rc.1",
|
|
44
|
+
"@deepseek-ai/dsh-agent-loop": "^0.1.2-rc.1",
|
|
45
|
+
"@deepseek-ai/dsh-agent-presets": "^0.1.2-rc.1",
|
|
46
|
+
"@deepseek-ai/dsh-attachment": "^0.1.2-rc.1",
|
|
47
|
+
"@deepseek-ai/dsh-credentials": "^0.1.2-rc.1",
|
|
48
|
+
"@deepseek-ai/dsh-llm": "^0.1.2-rc.1",
|
|
49
|
+
"@deepseek-ai/dsh-session": "^0.1.2-rc.1",
|
|
50
|
+
"@deepseek-ai/dsh-settings": "^0.1.2-rc.1",
|
|
51
|
+
"@deepseek-ai/schemastery": "^3.18.2"
|
|
52
52
|
},
|
|
53
53
|
"peerDependenciesMeta": {
|
|
54
54
|
"@deepseek-ai/cordis": {
|
|
@@ -94,23 +94,24 @@
|
|
|
94
94
|
"string-width": "^8.2.2"
|
|
95
95
|
},
|
|
96
96
|
"devDependencies": {
|
|
97
|
-
"@deepseek-ai/cordis": "^4.0.
|
|
98
|
-
"@deepseek-ai/dsh-agent": "0.1.
|
|
99
|
-
"@deepseek-ai/dsh-agent-default-model": "0.1.
|
|
100
|
-
"@deepseek-ai/dsh-agent-loop": "0.1.
|
|
101
|
-
"@deepseek-ai/dsh-agent-presets": "0.1.
|
|
102
|
-
"@deepseek-ai/dsh-attachment": "0.1.
|
|
103
|
-
"@deepseek-ai/dsh-credentials": "0.1.
|
|
104
|
-
"@deepseek-ai/dsh-llm": "0.1.
|
|
105
|
-
"@deepseek-ai/dsh-session": "0.1.
|
|
106
|
-
"@deepseek-ai/dsh-settings": "0.1.
|
|
107
|
-
"@deepseek-ai/schemastery": "^3.18.
|
|
97
|
+
"@deepseek-ai/cordis": "^4.0.2",
|
|
98
|
+
"@deepseek-ai/dsh-agent": "0.1.2-rc.1",
|
|
99
|
+
"@deepseek-ai/dsh-agent-default-model": "0.1.2-rc.1",
|
|
100
|
+
"@deepseek-ai/dsh-agent-loop": "0.1.2-rc.1",
|
|
101
|
+
"@deepseek-ai/dsh-agent-presets": "0.1.2-rc.1",
|
|
102
|
+
"@deepseek-ai/dsh-attachment": "0.1.2-rc.1",
|
|
103
|
+
"@deepseek-ai/dsh-credentials": "0.1.2-rc.1",
|
|
104
|
+
"@deepseek-ai/dsh-llm": "0.1.2-rc.1",
|
|
105
|
+
"@deepseek-ai/dsh-session": "0.1.2-rc.1",
|
|
106
|
+
"@deepseek-ai/dsh-settings": "0.1.2-rc.1",
|
|
107
|
+
"@deepseek-ai/schemastery": "^3.18.2",
|
|
108
108
|
"@types/node": "^24.0.0",
|
|
109
109
|
"@types/prismjs": "^1.26.6",
|
|
110
110
|
"@types/react": "^19.2.0",
|
|
111
111
|
"tsdown": "^0.22.14",
|
|
112
112
|
"tsx": "^4.0.0",
|
|
113
|
-
"typescript": "^5.9.0"
|
|
113
|
+
"typescript": "^5.9.0",
|
|
114
|
+
"@deepseek-ai/dsh-tool-todo": "0.1.2-rc.1"
|
|
114
115
|
},
|
|
115
116
|
"dsh": {
|
|
116
117
|
"bundle": {
|
package/src/chat/bridge.ts
CHANGED
|
@@ -5,7 +5,6 @@ import type {} from '@deepseek-ai/dsh-credentials'
|
|
|
5
5
|
import type {} from '@deepseek-ai/dsh-settings'
|
|
6
6
|
import type {} from '@deepseek-ai/dsh-agent-default-model'
|
|
7
7
|
import type {} from '@deepseek-ai/dsh-agent-presets'
|
|
8
|
-
import { resolveSessionPreset } from '@deepseek-ai/dsh-agent-presets'
|
|
9
8
|
import { installModelSelection } from '@deepseek-ai/dsh-agent'
|
|
10
9
|
import type { Agent, AgentHandle, AgentOptions, ModelSelection, ModelSelectionRef } from '@deepseek-ai/dsh-agent'
|
|
11
10
|
import type { ContentBlock, LlmDiscoveredModel } from '@deepseek-ai/dsh-llm'
|
|
@@ -136,6 +135,11 @@ async function sendContent(
|
|
|
136
135
|
agent.followup(createUserMessage({ content, source: { kind: 'user' } }))
|
|
137
136
|
}
|
|
138
137
|
|
|
138
|
+
interface PresetSessionLike {
|
|
139
|
+
snapshotEvents(): ReadonlyArray<{ type?: string; data?: unknown }>
|
|
140
|
+
header?: { agentPreset?: string }
|
|
141
|
+
}
|
|
142
|
+
|
|
139
143
|
export const PERMISSION_PRESETS = ['workspace-write', 'danger-full-access', 'read-only'] as const
|
|
140
144
|
|
|
141
145
|
interface PermissionPresetsLike {
|
|
@@ -663,6 +667,22 @@ export async function createChatBridge(ctx: Context): Promise<ChatBridge> {
|
|
|
663
667
|
}
|
|
664
668
|
}
|
|
665
669
|
|
|
670
|
+
// The 0.1.2 presets package dropped its resolveSessionPreset helper, so the
|
|
671
|
+
// session log is read directly: the newest selection event wins over the
|
|
672
|
+
// creation-time header value, matching the upstream semantics on every
|
|
673
|
+
// host version the peer range admits.
|
|
674
|
+
function resolveSessionPreset(session: PresetSessionLike): string | undefined {
|
|
675
|
+
const events = session.snapshotEvents()
|
|
676
|
+
for (let index = events.length - 1; index >= 0; index -= 1) {
|
|
677
|
+
const event = events[index]
|
|
678
|
+
if (event?.type === 'agent-preset/selected') {
|
|
679
|
+
const data = event.data as { agentPreset?: string } | undefined
|
|
680
|
+
if (data?.agentPreset !== undefined) return data.agentPreset
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
return session.header?.agentPreset
|
|
684
|
+
}
|
|
685
|
+
|
|
666
686
|
async function createAgent(cwd: string, presetId?: string): Promise<AgentHandle> {
|
|
667
687
|
const resolved = presetId ?? (await presets?.resolve())?.id
|
|
668
688
|
const meta = { cwd, ...(resolved === undefined ? {} : { agentPreset: resolved }) }
|
|
@@ -742,21 +762,21 @@ export async function createChatBridge(ctx: Context): Promise<ChatBridge> {
|
|
|
742
762
|
async function openSession(id: string): Promise<void> {
|
|
743
763
|
const sessionId = SessionId(id)
|
|
744
764
|
if (activeAgent.id === sessionId) {
|
|
745
|
-
for (const event of activeAgent.session.
|
|
765
|
+
for (const event of activeAgent.session.snapshotEvents()) emit(event)
|
|
746
766
|
return
|
|
747
767
|
}
|
|
748
768
|
const existing = ctx.agents.get(sessionId)
|
|
749
769
|
if (existing !== undefined) {
|
|
750
770
|
await activateAgent(undefined, existing)
|
|
751
771
|
syncCwd()
|
|
752
|
-
for (const event of activeAgent.session.
|
|
772
|
+
for (const event of activeAgent.session.snapshotEvents()) emit(event)
|
|
753
773
|
refreshAgentState()
|
|
754
774
|
return
|
|
755
775
|
}
|
|
756
776
|
const handle = await resumeAgent(sessionId)
|
|
757
777
|
await activateAgent(handle, handle.agent)
|
|
758
778
|
syncCwd()
|
|
759
|
-
for (const event of activeAgent.session.
|
|
779
|
+
for (const event of activeAgent.session.snapshotEvents()) emit(event)
|
|
760
780
|
refreshAgentState()
|
|
761
781
|
}
|
|
762
782
|
|
|
@@ -817,7 +837,7 @@ export async function createChatBridge(ctx: Context): Promise<ChatBridge> {
|
|
|
817
837
|
if (presets === undefined) throw new Error('agent presets are not configured')
|
|
818
838
|
if (currentPreset() === id) return
|
|
819
839
|
const session = activeAgent.session
|
|
820
|
-
if (!isBlankSession(session.
|
|
840
|
+
if (!isBlankSession(session.snapshotEvents())) {
|
|
821
841
|
throw new Error('the preset is fixed once the session has started; use /new to start a new session')
|
|
822
842
|
}
|
|
823
843
|
const preset = await presets.recompose(activeAgent.ctx, id)
|
|
@@ -1020,12 +1040,12 @@ export async function createChatBridge(ctx: Context): Promise<ChatBridge> {
|
|
|
1020
1040
|
currentEffort,
|
|
1021
1041
|
effortName,
|
|
1022
1042
|
selectEffort,
|
|
1023
|
-
permissionMode: () => permission()?.current(activeAgent.session.
|
|
1043
|
+
permissionMode: () => permission()?.current(activeAgent.session.snapshotEvents()) ?? PERMISSION_PRESETS[0],
|
|
1024
1044
|
cyclePermission: () => {
|
|
1025
1045
|
const service = permission()
|
|
1026
1046
|
if (service === undefined) return
|
|
1027
1047
|
try {
|
|
1028
|
-
const current = service.current(activeAgent.session.
|
|
1048
|
+
const current = service.current(activeAgent.session.snapshotEvents())
|
|
1029
1049
|
const names = service.names.length > 0 ? service.names : PERMISSION_PRESETS
|
|
1030
1050
|
const index = names.indexOf(current)
|
|
1031
1051
|
const next = names[(index + 1) % names.length]
|
package/src/chat/interactions.ts
CHANGED
|
@@ -308,22 +308,21 @@ type ApprovalRequestLike = {
|
|
|
308
308
|
|
|
309
309
|
type CtxLike = {
|
|
310
310
|
on(event: 'approval/request', handler: (req: ApprovalRequestLike) => Promise<ApprovalOutcome>): () => void
|
|
311
|
-
|
|
312
|
-
registerProvider(provider: { ask(request: AskUserQuestionRequestLike): Promise<AskUserQuestionAnswerLike> }): () => void
|
|
313
|
-
} | undefined
|
|
311
|
+
on(event: 'user-questions/request', handler: (req: AskUserQuestionRequestLike) => Promise<AskUserQuestionAnswerLike>): () => void
|
|
314
312
|
}
|
|
315
313
|
|
|
316
314
|
export function registerInteractionChannels(ctx: CtxLike, store: InteractionStore): () => void {
|
|
317
315
|
const offApproval = ctx.on('approval/request', async req => {
|
|
318
316
|
return store.pushApproval(req.toolName, req.callId, req.reason, req.signal)
|
|
319
317
|
})
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
318
|
+
// The 0.1.2 user-questions service routes asks through the scoped
|
|
319
|
+
// `user-questions/request` waterfall; answering without calling `next`
|
|
320
|
+
// claims the request for this terminal.
|
|
321
|
+
const offQuestions = ctx.on('user-questions/request', async request => {
|
|
322
|
+
return store.pushQuestion(request, request.signal)
|
|
324
323
|
})
|
|
325
324
|
return () => {
|
|
326
325
|
offApproval()
|
|
327
|
-
|
|
326
|
+
offQuestions()
|
|
328
327
|
}
|
|
329
328
|
}
|
package/src/chat/session-list.ts
CHANGED
|
@@ -32,16 +32,16 @@ export async function computeSessionList(ctx: Context): Promise<SessionSummary[]
|
|
|
32
32
|
const summaries: SessionSummary[] = []
|
|
33
33
|
const attached = new Set<string>()
|
|
34
34
|
const titleService = ctx.get('sessionTitle') as
|
|
35
|
-
| { get?(session: { id: string;
|
|
35
|
+
| { get?(session: { id: string; snapshotEvents(): readonly SessionEvent[] }): { title?: string } | undefined }
|
|
36
36
|
| undefined
|
|
37
37
|
for (const session of ctx.sessions.list()) {
|
|
38
38
|
const id = String(session.id)
|
|
39
39
|
attached.add(id)
|
|
40
40
|
if (session.header.origin === 'subagent') continue
|
|
41
41
|
if (archived.has(id)) continue
|
|
42
|
-
if (isBlankSession(session.
|
|
43
|
-
const title = titleService?.get?.(session)?.title ?? firstUserText(session.
|
|
44
|
-
summaries.push(toSummary(id, title, session.header.cwd, session.header.createdAt, lastPromptAt(session.
|
|
42
|
+
if (isBlankSession(session.snapshotEvents())) continue
|
|
43
|
+
const title = titleService?.get?.(session)?.title ?? firstUserText(session.snapshotEvents())
|
|
44
|
+
summaries.push(toSummary(id, title, session.header.cwd, session.header.createdAt, lastPromptAt(session.snapshotEvents()), workspacePaths))
|
|
45
45
|
}
|
|
46
46
|
const persistence = ctx.get('sessionPersistence') as PersistenceLike | undefined
|
|
47
47
|
const cold = await listColdHeaders(ctx, persistence, attached)
|
package/src/contract/upstream.ts
CHANGED
package/src/index.tsx
CHANGED
|
@@ -23,7 +23,7 @@ import { warmRenderPipeline } from './ui/message/warmup.ts'
|
|
|
23
23
|
import { createTuiExtensionPoint, exposeRuntimeFaces } from './ui/extension-point.ts'
|
|
24
24
|
import { registerBuiltinToolViews } from './chat/builtin-tool-views.ts'
|
|
25
25
|
import { closeBootLog, emitBootLine, openBootLog } from './boot-log.ts'
|
|
26
|
-
import { configureLogs, error as logError, installCrashHandlers
|
|
26
|
+
import { configureLogs, error, error as logError, installCrashHandlers } from './log.ts'
|
|
27
27
|
import { env } from './env.ts'
|
|
28
28
|
import { upstreamDriftSummary, UPSTREAM_SUPPORTED_RANGE } from './contract/upstream.ts'
|
|
29
29
|
|
|
@@ -67,9 +67,13 @@ export function apply(ctx: Context, config: Config = Config(DEFAULT_CONFIG)) {
|
|
|
67
67
|
configureLogs({ stderr: env.debug, file: env.logFile, dir: env.logDir, level: env.logLevel, maxFileBytes: env.logMaxBytes })
|
|
68
68
|
const restorePerformance = startPerformanceGuard()
|
|
69
69
|
const disposeCrashHandlers = installCrashHandlers({ exit: env.crashExit })
|
|
70
|
+
// Fail before any surface mounts: an out-of-range host breaks the bridge
|
|
71
|
+
// in ways that render as a dead interface instead of an actionable error.
|
|
70
72
|
const drift = upstreamDriftSummary()
|
|
71
73
|
if (drift !== undefined) {
|
|
72
|
-
|
|
74
|
+
const message = `dshtui requires dsh harness packages in ${UPSTREAM_SUPPORTED_RANGE} (found ${drift.kind}: ${drift.versions.join(', ')}); upgrade the dsh CLI with: npm install -g @deepseek-ai/dsh@latest`
|
|
75
|
+
error('boot', message)
|
|
76
|
+
throw new Error(message)
|
|
73
77
|
}
|
|
74
78
|
const capture = createScreenCapture()
|
|
75
79
|
let bridge: ChatBridge | undefined
|
|
@@ -138,9 +142,6 @@ export function apply(ctx: Context, config: Config = Config(DEFAULT_CONFIG)) {
|
|
|
138
142
|
}
|
|
139
143
|
const themeScope = registerThemeSettings(ctx)
|
|
140
144
|
openBootLog()
|
|
141
|
-
if (drift !== undefined) {
|
|
142
|
-
emitBootLine(`warning: upstream version drift (${drift.kind})`)
|
|
143
|
-
}
|
|
144
145
|
emitBootLine('terminal: probing color support')
|
|
145
146
|
void (async () => {
|
|
146
147
|
const probed = await probeColorLevel()
|