@deepseek-harness-tui/dsh-tui 0.7.1 → 0.7.3
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 +7 -5
- package/bin/dsh-tui.js +42 -1
- package/cordis.patch.yml +10 -0
- package/lib/types/commands.js +1 -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/LoadedContextPanel.d.ts.map +1 -1
- package/lib/types/components/LoadedContextPanel.js +1 -1
- package/lib/types/components/MessageList.d.ts.map +1 -1
- package/lib/types/components/MessageList.js +5 -1
- 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/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/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 +9 -3
- package/lib/types/components/design-system/ThemedText.d.ts.map +1 -1
- package/lib/types/components/design-system/ThemedText.js +3 -5
- 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/components/sessions/SessionListRow.d.ts +26 -0
- package/lib/types/components/sessions/SessionListRow.d.ts.map +1 -0
- package/lib/types/components/sessions/SessionListRow.js +35 -0
- package/lib/types/components/sessions/SessionPreview.d.ts +25 -0
- package/lib/types/components/sessions/SessionPreview.d.ts.map +1 -0
- package/lib/types/components/sessions/SessionPreview.js +60 -0
- package/lib/types/dsh-adapter/channel.d.ts +19 -4
- package/lib/types/dsh-adapter/channel.d.ts.map +1 -1
- package/lib/types/dsh-adapter/channel.js +76 -82
- package/lib/types/dsh-adapter/compat/index.d.ts +1 -1
- package/lib/types/dsh-adapter/compat/index.d.ts.map +1 -1
- package/lib/types/dsh-adapter/compat/index.js +1 -1
- package/lib/types/dsh-adapter/compat/sessionLog.d.ts +8 -0
- package/lib/types/dsh-adapter/compat/sessionLog.d.ts.map +1 -1
- package/lib/types/dsh-adapter/compat/sessionLog.js +1 -1
- package/lib/types/dsh-adapter/index.d.ts.map +1 -1
- package/lib/types/dsh-adapter/index.js +10 -1
- package/lib/types/dsh-adapter/packaged-presets.d.ts +22 -0
- package/lib/types/dsh-adapter/packaged-presets.d.ts.map +1 -0
- package/lib/types/dsh-adapter/packaged-presets.js +93 -0
- package/lib/types/dsh-adapter/plugin.d.ts.map +1 -1
- package/lib/types/dsh-adapter/plugin.js +53 -7
- package/lib/types/dsh-adapter/presets.d.ts +16 -0
- package/lib/types/dsh-adapter/presets.d.ts.map +1 -1
- package/lib/types/dsh-adapter/presets.js +23 -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/sessions/digest.d.ts +29 -0
- package/lib/types/dsh-adapter/sessions/digest.d.ts.map +1 -0
- package/lib/types/dsh-adapter/sessions/digest.js +224 -0
- package/lib/types/dsh-adapter/sessions/frames.d.ts +100 -0
- package/lib/types/dsh-adapter/sessions/frames.d.ts.map +1 -0
- package/lib/types/dsh-adapter/sessions/frames.js +246 -0
- package/lib/types/dsh-adapter/sessions/header.d.ts +50 -0
- package/lib/types/dsh-adapter/sessions/header.d.ts.map +1 -0
- package/lib/types/dsh-adapter/sessions/header.js +62 -0
- package/lib/types/dsh-adapter/sessions/index.d.ts +17 -0
- package/lib/types/dsh-adapter/sessions/index.d.ts.map +1 -0
- package/lib/types/dsh-adapter/sessions/index.js +15 -0
- package/lib/types/dsh-adapter/sessions/list.d.ts +41 -0
- package/lib/types/dsh-adapter/sessions/list.d.ts.map +1 -0
- package/lib/types/dsh-adapter/sessions/list.js +213 -0
- package/lib/types/dsh-adapter/sessions/store.d.ts +48 -0
- package/lib/types/dsh-adapter/sessions/store.d.ts.map +1 -0
- package/lib/types/dsh-adapter/sessions/store.js +164 -0
- package/lib/types/dsh-adapter/sessions/types.d.ts +142 -0
- package/lib/types/dsh-adapter/sessions/types.d.ts.map +1 -0
- package/lib/types/dsh-adapter/sessions/types.js +14 -0
- package/lib/types/i18n.d.ts +150 -22
- package/lib/types/i18n.d.ts.map +1 -1
- package/lib/types/i18n.js +45 -9
- 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/screens/Chat.d.ts +3 -2
- package/lib/types/screens/Chat.d.ts.map +1 -1
- package/lib/types/screens/Chat.js +104 -160
- package/lib/types/screens/SessionBrowser.d.ts +37 -0
- package/lib/types/screens/SessionBrowser.d.ts.map +1 -0
- package/lib/types/screens/SessionBrowser.js +452 -0
- package/lib/types/sessionHistory.d.ts +0 -8
- package/lib/types/sessionHistory.d.ts.map +1 -1
- package/lib/types/sessions/format.d.ts +117 -0
- package/lib/types/sessions/format.d.ts.map +1 -0
- package/lib/types/sessions/format.js +244 -0
- package/lib/types/sessions/view.d.ts +125 -0
- package/lib/types/sessions/view.d.ts.map +1 -0
- package/lib/types/sessions/view.js +234 -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 +37 -9
- package/presets/liangshen/.dsh-tui-managed.json +5 -0
- package/presets/liangshen/agent.cordis.yml +389 -0
- package/presets/liangshen/compaction-epoch.mjs +81 -0
- package/presets/liangshen/custom-bash.mjs +126 -0
- package/presets/liangshen/instruction-hint.mjs +181 -0
- package/presets/liangshen/preset.yml +3 -0
- package/presets/liangshen/skill-search.mjs +142 -0
- package/presets/liangshen/tool-bootstrap.mjs +300 -0
- package/lib/types/components/ResumePicker.d.ts +0 -26
- package/lib/types/components/ResumePicker.d.ts.map +0 -1
- package/lib/types/components/ResumePicker.js +0 -50
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import { cpSync, existsSync, mkdirSync, readFileSync, readdirSync, renameSync, rmSync, } from 'node:fs';
|
|
3
|
+
import { homedir } from 'node:os';
|
|
4
|
+
import { dirname, join } from 'node:path';
|
|
5
|
+
import { fileURLToPath } from 'node:url';
|
|
6
|
+
const OWNER = '@deepseek-harness-tui/dsh-tui';
|
|
7
|
+
const MARKER = '.dsh-tui-managed.json';
|
|
8
|
+
function readMarker(directory) {
|
|
9
|
+
try {
|
|
10
|
+
const value = JSON.parse(readFileSync(join(directory, MARKER), 'utf8'));
|
|
11
|
+
if (value.owner === OWNER
|
|
12
|
+
&& typeof value.preset === 'string'
|
|
13
|
+
&& typeof value.revision === 'string'
|
|
14
|
+
&& value.revision.length > 0) {
|
|
15
|
+
return value;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
// An absent or user-authored marker means this directory is not ours.
|
|
20
|
+
}
|
|
21
|
+
return undefined;
|
|
22
|
+
}
|
|
23
|
+
/** Resolve the package asset in both src/tsx and compiled npm layouts. */
|
|
24
|
+
export function packagedPresetRoot(moduleUrl = import.meta.url) {
|
|
25
|
+
const directory = dirname(fileURLToPath(moduleUrl));
|
|
26
|
+
const candidates = [join(directory, '../../presets'), join(directory, '../../../presets')];
|
|
27
|
+
const found = candidates.find(candidate => existsSync(candidate));
|
|
28
|
+
if (found === undefined) {
|
|
29
|
+
throw new Error(`dsh-tui: packaged preset root is missing (checked ${candidates.join(', ')})`);
|
|
30
|
+
}
|
|
31
|
+
return found;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Materialize presets shipped by dsh-tui into the Harness user preset root.
|
|
35
|
+
*
|
|
36
|
+
* The official launcher replaces the roster's configured roots with its own
|
|
37
|
+
* shipped root at the end of profile composition, so a bundle patch cannot
|
|
38
|
+
* add a second system root. The user root is the roster's supported extension
|
|
39
|
+
* seam and is discovered on every list/resolve call. Existing unmarked
|
|
40
|
+
* directories are never overwritten.
|
|
41
|
+
*/
|
|
42
|
+
export function ensurePackagedPresets(options = {}) {
|
|
43
|
+
const sourceRoot = options.sourceRoot ?? packagedPresetRoot(options.moduleUrl);
|
|
44
|
+
const dshHome = options.dshHome ?? process.env.DSH_HOME ?? join(homedir(), '.dsh');
|
|
45
|
+
const targetRoot = join(dshHome, '.agent-presets');
|
|
46
|
+
const results = [];
|
|
47
|
+
for (const entry of readdirSync(sourceRoot, { withFileTypes: true })) {
|
|
48
|
+
if (!entry.isDirectory())
|
|
49
|
+
continue;
|
|
50
|
+
const id = entry.name;
|
|
51
|
+
const source = join(sourceRoot, id);
|
|
52
|
+
const sourceMarker = readMarker(source);
|
|
53
|
+
if (sourceMarker === undefined || sourceMarker.preset !== id) {
|
|
54
|
+
throw new Error(`dsh-tui: packaged preset ${id} has no valid ${MARKER}`);
|
|
55
|
+
}
|
|
56
|
+
const target = join(targetRoot, id);
|
|
57
|
+
if (!existsSync(target)) {
|
|
58
|
+
mkdirSync(targetRoot, { recursive: true });
|
|
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 });
|
|
63
|
+
results.push({ id, status: 'installed' });
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
const targetMarker = readMarker(target);
|
|
67
|
+
if (targetMarker === undefined || targetMarker.preset !== id) {
|
|
68
|
+
results.push({ id, status: 'conflict' });
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
if (targetMarker.revision === sourceMarker.revision) {
|
|
72
|
+
results.push({ id, status: 'current' });
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
const suffix = `${process.pid}-${randomUUID()}`;
|
|
76
|
+
const staged = join(targetRoot, `.${id}.staged-${suffix}`);
|
|
77
|
+
const backup = join(targetRoot, `.${id}.backup-${suffix}`);
|
|
78
|
+
cpSync(source, staged, { recursive: true, force: false, errorOnExist: true, filter: () => true });
|
|
79
|
+
try {
|
|
80
|
+
renameSync(target, backup);
|
|
81
|
+
renameSync(staged, target);
|
|
82
|
+
rmSync(backup, { recursive: true, force: true });
|
|
83
|
+
}
|
|
84
|
+
catch (error) {
|
|
85
|
+
if (!existsSync(target) && existsSync(backup))
|
|
86
|
+
renameSync(backup, target);
|
|
87
|
+
rmSync(staged, { recursive: true, force: true });
|
|
88
|
+
throw error;
|
|
89
|
+
}
|
|
90
|
+
results.push({ id, status: 'updated' });
|
|
91
|
+
}
|
|
92
|
+
return results;
|
|
93
|
+
}
|
|
@@ -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;AAClD,OAAO,EAAE,MAAM,EAAE,MAAM,YAAY,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;AAClD,OAAO,EAAE,MAAM,EAAE,MAAM,YAAY,CAAA;AA6BnC;;;;;;;;GAQG;AACH,wBAAsB,KAAK,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CA8bvE;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"}
|
|
@@ -13,7 +13,8 @@ import { readActivityFrames } from '../activityPrefs.js';
|
|
|
13
13
|
import { readModelPref } from '../modelPrefs.js';
|
|
14
14
|
import { explicitModelRoute, recordedModelRoute, resolveModelRoute, validateModelRoute } from '../modelRoute.js';
|
|
15
15
|
import { readPresetPref } from '../presetPrefs.js';
|
|
16
|
-
import { composePreset, resolvePersistedPreset, runningPresetOf } from './presets.js';
|
|
16
|
+
import { composePreset, filterMinimalPresetTools, resolvePersistedPreset, runningPresetOf } from './presets.js';
|
|
17
|
+
import { ensurePackagedPresets } from './packaged-presets.js';
|
|
17
18
|
import { ensureLegacySessionEventTypes } from './compat/index.js';
|
|
18
19
|
import { clearResumeTarget, writeResumeTarget } from '../sessionHistory.js';
|
|
19
20
|
import { resolveSessionCwd } from '../utils/workspaceRoot.js';
|
|
@@ -22,6 +23,7 @@ import { isLang, resolveStartupLang, setLang, t } from '../i18n.js';
|
|
|
22
23
|
import { detectLegacyEnv, migrateLegacyDataDir, RENAMED_ENV } from '../utils/paths.js';
|
|
23
24
|
import { Chat } from '../screens/Chat.js';
|
|
24
25
|
import { attachSessionToWorkspace } from './workspace.js';
|
|
26
|
+
import { createLocalWorkspaceRuntime } from './workspaces.js';
|
|
25
27
|
import { render, ThemeProvider, AlternateScreen } from '../ui.js';
|
|
26
28
|
import instances from '../ink/instances.js';
|
|
27
29
|
import { cursorMove, DISABLE_KITTY_KEYBOARD, DISABLE_MODIFY_OTHER_KEYS, DISABLE_WIN32_INPUT_MODE } from '../ink/termio/csi.js';
|
|
@@ -40,6 +42,22 @@ export async function apply(ctx, config) {
|
|
|
40
42
|
if (!process.stdout.isTTY) {
|
|
41
43
|
throw new Error('dsh-tui requires an interactive terminal (stdout must be a TTY).');
|
|
42
44
|
}
|
|
45
|
+
// The official profile launcher owns the system preset root and replaces
|
|
46
|
+
// any bundle-supplied roots at boot. Install dsh-tui's bundled presets via
|
|
47
|
+
// the roster's supported user-root seam before resolving the first agent.
|
|
48
|
+
// Never overwrite an existing directory unless it carries our marker.
|
|
49
|
+
try {
|
|
50
|
+
for (const result of ensurePackagedPresets()) {
|
|
51
|
+
if (result.status === 'conflict') {
|
|
52
|
+
ctx.logger.warn(`dsh-tui: packaged preset "${result.id}" was not installed because an unmanaged preset already uses that id`);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
catch (error) {
|
|
57
|
+
// A read-only home must not make the whole terminal unusable; the other
|
|
58
|
+
// official and user presets remain available.
|
|
59
|
+
ctx.logger.warn(`dsh-tui: unable to install packaged presets (${error instanceof Error ? error.message : String(error)})`);
|
|
60
|
+
}
|
|
43
61
|
// Data-directory rename (~/.dsh-cc → ~/.dsh-tui, issue #120): copy the
|
|
44
62
|
// legacy directory before ANY preference read below (resolveStartupLang
|
|
45
63
|
// already touches lang.json). Copy, not move — old launchers keep working
|
|
@@ -98,6 +116,16 @@ export async function apply(ctx, config) {
|
|
|
98
116
|
// the inject proxy.
|
|
99
117
|
const userQuestions = ctx.get('userQuestions') ?? new UserQuestionService(ctx);
|
|
100
118
|
ctx.plugin(toolAskUser);
|
|
119
|
+
// The host-level tool mount above is intentional for the TUI and for user
|
|
120
|
+
// presets, but the official Minimal preset is a strict two-tool trajectory
|
|
121
|
+
// (persistent bash + str_replace_editor). Filter only that preset at the
|
|
122
|
+
// final assembly boundary. Reading the session on every assembly also makes
|
|
123
|
+
// blank-session /preset switches and resumed sessions behave correctly.
|
|
124
|
+
ctx.on('system-prompt/assemble', async (_assembly, context, next) => {
|
|
125
|
+
const assembled = await next();
|
|
126
|
+
const presetId = context.agent === undefined ? undefined : runningPresetOf(context.agent.session);
|
|
127
|
+
return filterMinimalPresetTools(assembled, presetId);
|
|
128
|
+
});
|
|
101
129
|
const questionStore = new QuestionStore();
|
|
102
130
|
// Packaged skills (/audit, /bug, …): contribute them through the host's
|
|
103
131
|
// skill registry so they resolve with zero manual copying.
|
|
@@ -170,9 +198,22 @@ export async function apply(ctx, config) {
|
|
|
170
198
|
// see the repository, not an arbitrary launch subdirectory. Resolved ONCE
|
|
171
199
|
// here — the agent meta and the channel must agree.
|
|
172
200
|
const requestedWorkspace = config.workspace ?? process.env.DSH_TUI_WORKSPACE_TARGET;
|
|
201
|
+
// Degraded boot (issue #183): a stale bundle patch without the
|
|
202
|
+
// dsh-tui-workspaces row leaves the service unmounted; resolve startup
|
|
203
|
+
// targets through the local-only runtime (provider URIs then fail loud
|
|
204
|
+
// below instead of crashing on an undefined service). A profile launch
|
|
205
|
+
// without the service means the patch came from an older dsh-tui copy
|
|
206
|
+
// than the running code — warn once so the skew is diagnosable. Bare
|
|
207
|
+
// embedders (no --profile) take the same fallback by design, silently.
|
|
208
|
+
const mountedWorkspaceService = ctx.get('tuiWorkspaces');
|
|
209
|
+
if (mountedWorkspaceService === undefined && resolveDshProfileName() !== undefined) {
|
|
210
|
+
ctx.logger.warn('dsh-tui: tuiWorkspaces service is not mounted; /workspace runs with the local-only fallback. ' +
|
|
211
|
+
'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
|
+
}
|
|
213
|
+
const workspaceService = mountedWorkspaceService ?? createLocalWorkspaceRuntime();
|
|
173
214
|
const initialWorkspace = requestedWorkspace === undefined
|
|
174
215
|
? undefined
|
|
175
|
-
: await
|
|
216
|
+
: await workspaceService.resolve(requestedWorkspace);
|
|
176
217
|
if (requestedWorkspace !== undefined && initialWorkspace === undefined) {
|
|
177
218
|
throw new Error(`dsh-tui: unsupported or unavailable workspace target: ${requestedWorkspace}`);
|
|
178
219
|
}
|
|
@@ -268,6 +309,7 @@ export async function apply(ctx, config) {
|
|
|
268
309
|
let instance;
|
|
269
310
|
let exited = false;
|
|
270
311
|
let updateRequested = false;
|
|
312
|
+
let updateTargetVersion;
|
|
271
313
|
// The profile this process was booted with (`dsh --profile <name>`); dsh
|
|
272
314
|
// exposes it nowhere else, and /update must update the installation the
|
|
273
315
|
// user is actually running, not a hard-coded one.
|
|
@@ -295,7 +337,7 @@ export async function apply(ctx, config) {
|
|
|
295
337
|
catch {
|
|
296
338
|
// Resume persistence is best effort and must never block an update.
|
|
297
339
|
}
|
|
298
|
-
void finishExit(ctx, instance, config.fullscreen === true, 'Updating @deepseek-harness-tui/dsh-tui and restarting…', undefined, () => runUpdate(ctx, profile, channel.agentId));
|
|
340
|
+
void finishExit(ctx, instance, config.fullscreen === true, 'Updating @deepseek-harness-tui/dsh-tui and restarting…', undefined, () => runUpdate(ctx, profile, channel.agentId, updateTargetVersion));
|
|
299
341
|
return;
|
|
300
342
|
}
|
|
301
343
|
// Judge against the live session behind the channel (channel.agentId),
|
|
@@ -327,8 +369,9 @@ export async function apply(ctx, config) {
|
|
|
327
369
|
channel,
|
|
328
370
|
questionStore,
|
|
329
371
|
approvalStore,
|
|
330
|
-
//
|
|
331
|
-
//
|
|
372
|
+
// Full-screen surfaces inside Chat — the trajectory scene and the session
|
|
373
|
+
// browser — enter the alt screen themselves in inline mode; in fullscreen
|
|
374
|
+
// the tree is already wrapped below, so they must not nest.
|
|
332
375
|
fullscreen: config.fullscreen === true,
|
|
333
376
|
onExit: () => handleExit(),
|
|
334
377
|
// Only a `dsh --profile <name>` launch has a profile installation for
|
|
@@ -350,6 +393,9 @@ export async function apply(ctx, config) {
|
|
|
350
393
|
if (target.kind === 'unknown') {
|
|
351
394
|
channel.notify(t('update-check-failed'));
|
|
352
395
|
}
|
|
396
|
+
else {
|
|
397
|
+
updateTargetVersion = target.latest;
|
|
398
|
+
}
|
|
353
399
|
channel.notify(t('update-starting'));
|
|
354
400
|
updateRequested = true;
|
|
355
401
|
handleExit();
|
|
@@ -623,13 +669,13 @@ function writeStream(stream, data) {
|
|
|
623
669
|
}
|
|
624
670
|
});
|
|
625
671
|
}
|
|
626
|
-
function runUpdate(ctx, profile, sessionId) {
|
|
672
|
+
function runUpdate(ctx, profile, sessionId, targetVersion) {
|
|
627
673
|
disposeRootAndThen(ctx, () => {
|
|
628
674
|
if (profile === undefined) {
|
|
629
675
|
process.stderr.write(`\n${t('update-aborted-no-profile')}\n`);
|
|
630
676
|
process.exit(1);
|
|
631
677
|
}
|
|
632
|
-
void updateTuiAndRestart(sessionId, profile).then(({ updateCode, restartCode }) => {
|
|
678
|
+
void updateTuiAndRestart(sessionId, profile, targetVersion).then(({ updateCode, restartCode }) => {
|
|
633
679
|
if (updateCode !== 0) {
|
|
634
680
|
process.stderr.write(`\ndsh-tui update failed (exit ${updateCode}). Your session is preserved — resume with:\n` +
|
|
635
681
|
`${resumeCommand(profile, sessionId)}\n\n`);
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
import type { Context } from '@deepseek-ai/cordis';
|
|
19
19
|
import type { AgentSetup } from '@deepseek-ai/dsh-agent';
|
|
20
20
|
import type { SessionId } from '@deepseek-ai/dsh-session';
|
|
21
|
+
import type { PromptAssembly } from '@deepseek-ai/dsh-system-prompt';
|
|
21
22
|
/** One roster entry, as returned by `agentPresets.list()`/`resolve()`. */
|
|
22
23
|
export interface AgentPresetInfo {
|
|
23
24
|
readonly id: string;
|
|
@@ -93,6 +94,21 @@ export declare function runningPresetOf(session: {
|
|
|
93
94
|
data: unknown;
|
|
94
95
|
}[];
|
|
95
96
|
}): string | undefined;
|
|
97
|
+
/**
|
|
98
|
+
* Keep the official Minimal preset's model-facing contract at exactly two
|
|
99
|
+
* tools. The TUI mounts ask_user_question at the host layer so every other
|
|
100
|
+
* preset (including user presets) can use its questionnaire UI; host-layer
|
|
101
|
+
* tools otherwise merge into Minimal's scoped catalog as a third tool.
|
|
102
|
+
*
|
|
103
|
+
* This is a per-assembly filter rather than a startup-time decision because
|
|
104
|
+
* one TUI process can resume, create, or recompose sessions under different
|
|
105
|
+
* presets. The session log remains the source of truth for the active preset.
|
|
106
|
+
*
|
|
107
|
+
* @param assembly - Fully assembled prompt inputs for one model request.
|
|
108
|
+
* @param presetId - Preset recorded for the requesting session.
|
|
109
|
+
* @returns The original assembly, except ask_user_question is absent in Minimal.
|
|
110
|
+
*/
|
|
111
|
+
export declare function filterMinimalPresetTools(assembly: PromptAssembly, presetId: string | undefined): PromptAssembly;
|
|
96
112
|
/**
|
|
97
113
|
* Read a service the way a joined agent sees it: through the preset scope
|
|
98
114
|
* chain when a roster is mounted (preset realms hide e.g. `compaction` from
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"presets.d.ts","sourceRoot":"","sources":["../../../src/dsh-adapter/presets.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAClD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAA;AACxD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAA;
|
|
1
|
+
{"version":3,"file":"presets.d.ts","sourceRoot":"","sources":["../../../src/dsh-adapter/presets.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAClD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAA;AACxD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAA;AACzD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gCAAgC,CAAA;AAKpE,0EAA0E;AAC1E,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;IACnB,QAAQ,CAAC,KAAK,EAAE,QAAQ,GAAG,MAAM,CAAA;IACjC,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAA;IAC7B,yEAAyE;IACzE,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CACzB;AAED,+DAA+D;AAC/D,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;IAC1B,IAAI,IAAI,OAAO,CAAC,SAAS,eAAe,EAAE,CAAC,CAAA;IAC3C,OAAO,CAAC,EAAE,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,CAAC,CAAA;IAC9C,KAAK,CAAC,QAAQ,EAAE,OAAO,EAAE,EAAE,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,CAAC,CAAA;IAC/D,SAAS,CAAC,QAAQ,EAAE,OAAO,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,CAAC,CAAA;IAClE,yEAAyE;IACzE,UAAU,CAAC,CAAC,KAAK,EAAE;QAAE,GAAG,EAAE,OAAO,CAAA;KAAE,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;CAC3D;AAED;;;GAGG;AACH,wBAAgB,QAAQ,CAAC,GAAG,EAAE,OAAO,GAAG,gBAAgB,GAAG,SAAS,CAEnE;AAED,kEAAkE;AAClE,MAAM,WAAW,iBAAiB;IAChC,kFAAkF;IAClF,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAA;IAC7B,yEAAyE;IACzE,QAAQ,CAAC,KAAK,CAAC,EAAE,UAAU,CAAA;CAC5B;AAED;;;;;;;;;;;;GAYG;AACH,wBAAsB,aAAa,CAAC,GAAG,EAAE,OAAO,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAmBhG;AAED;;;;;;;;;GASG;AACH,wBAAsB,sBAAsB,CAAC,GAAG,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAqB5G;AAED;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE;IACvC,MAAM,EAAE;QAAE,WAAW,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;IAChC,MAAM,EAAE,SAAS;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,OAAO,CAAA;KAAE,EAAE,CAAA;CACnD,GAAG,MAAM,GAAG,SAAS,CAErB;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,wBAAwB,CAAC,QAAQ,EAAE,cAAc,EAAE,QAAQ,EAAE,MAAM,GAAG,SAAS,GAAG,cAAc,CAM/G;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,eAAe,CAAC,CAAC,EAAE,GAAG,EAAE,OAAO,EAAE,KAAK,EAAE;IAAE,GAAG,EAAE,OAAO,CAAA;CAAE,EAAE,GAAG,EAAE,MAAM,GAAG,CAAC,GAAG,SAAS,CAKpG"}
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
* before presets existed.
|
|
17
17
|
*/
|
|
18
18
|
import { resolveSessionPreset } from '@deepseek-ai/dsh-agent-presets';
|
|
19
|
+
const ASK_USER_TOOL = 'ask_user_question';
|
|
19
20
|
/**
|
|
20
21
|
* The mounted preset roster, or undefined when the composition has no
|
|
21
22
|
* `agent-presets` row (bare boots) — optional-service access via `ctx.get`.
|
|
@@ -94,6 +95,28 @@ export async function resolvePersistedPreset(ctx, sessionId) {
|
|
|
94
95
|
export function runningPresetOf(session) {
|
|
95
96
|
return resolveSessionPreset(session);
|
|
96
97
|
}
|
|
98
|
+
/**
|
|
99
|
+
* Keep the official Minimal preset's model-facing contract at exactly two
|
|
100
|
+
* tools. The TUI mounts ask_user_question at the host layer so every other
|
|
101
|
+
* preset (including user presets) can use its questionnaire UI; host-layer
|
|
102
|
+
* tools otherwise merge into Minimal's scoped catalog as a third tool.
|
|
103
|
+
*
|
|
104
|
+
* This is a per-assembly filter rather than a startup-time decision because
|
|
105
|
+
* one TUI process can resume, create, or recompose sessions under different
|
|
106
|
+
* presets. The session log remains the source of truth for the active preset.
|
|
107
|
+
*
|
|
108
|
+
* @param assembly - Fully assembled prompt inputs for one model request.
|
|
109
|
+
* @param presetId - Preset recorded for the requesting session.
|
|
110
|
+
* @returns The original assembly, except ask_user_question is absent in Minimal.
|
|
111
|
+
*/
|
|
112
|
+
export function filterMinimalPresetTools(assembly, presetId) {
|
|
113
|
+
if (presetId !== 'minimal' || !assembly.tools.some(tool => tool.name === ASK_USER_TOOL))
|
|
114
|
+
return assembly;
|
|
115
|
+
return {
|
|
116
|
+
...assembly,
|
|
117
|
+
tools: assembly.tools.filter(tool => tool.name !== ASK_USER_TOOL),
|
|
118
|
+
};
|
|
119
|
+
}
|
|
97
120
|
/**
|
|
98
121
|
* Read a service the way a joined agent sees it: through the preset scope
|
|
99
122
|
* chain when a roster is mounted (preset realms hide e.g. `compaction` from
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"providerWizard.d.ts","sourceRoot":"","sources":["../../../src/dsh-adapter/providerWizard.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAGH,OAAO,EAEL,KAAK,qBAAqB,EAE1B,KAAK,sBAAsB,EAC5B,MAAM,iCAAiC,CAAA;AACxC,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAA;AAE9D,iFAAiF;AACjF,eAAO,MAAM,iBAAiB,QAAoC,CAAA;AAElE,2EAA2E;AAC3E,eAAO,MAAM,kBAAkB,2EAIrB,CAAA;AAEV;;;GAGG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAElD;AAED,mEAAmE;AACnE,MAAM,WAAW,wBAAwB;IACvC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAA;CAC7B;AAED;;;;;GAKG;AACH,MAAM,WAAW,iBAAiB;IAChC,uEAAuE;IACvE,oBAAoB,IAAI,SAAS,wBAAwB,EAAE,CAAA;IAC3D,kEAAkE;IAClE,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAA;IACnC,sEAAsE;IACtE,cAAc,CAAC,OAAO,EAAE;QACtB,QAAQ,CAAC,EAAE,MAAM,CAAA;QACjB,OAAO,CAAC,EAAE,MAAM,CAAA;QAChB,GAAG,CAAC,EAAE,MAAM,CAAA;QACZ,MAAM,CAAC,EAAE,MAAM,CAAA;KAChB,GAAG,OAAO,CAAC,SAAS,kBAAkB,EAAE,CAAC,CAAA;IAC1C,0EAA0E;IAC1E,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;IAChC;;;;OAIG;IACH,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAAA;IACxD,2EAA2E;IAC3E,eAAe,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IACjE,yDAAyD;IACzD,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IACnD;;;OAGG;IACH,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CAC7E;AAED,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,IAAI,EAAE,iBAAiB,CAAA;IAChC,QAAQ,CAAC,GAAG,EAAE,CACZ,OAAO,EAAE,sBAAsB,EAC/B,OAAO,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,OAAO,CAAA;KAAE,KAC3B,OAAO,CAAC,qBAAqB,CAAC,CAAA;IACnC,QAAQ,CAAC,MAAM,EAAE,CACf,IAAI,EAAE,MAAM,EACZ,OAAO,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,OAAO,GAAG,SAAS,GAAG,SAAS,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,KACtE,IAAI,CAAA;IACT,QAAQ,CAAC,SAAS,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,MAAM,EAAE,KAAK,IAAI,CAAA;IACrE,2EAA2E;IAC3E,QAAQ,CAAC,OAAO,EAAE,MAAM,OAAO,CAAA;IAC/B,QAAQ,CAAC,WAAW,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAA;CAC5E;AAED,MAAM,MAAM,qBAAqB,GAAG,OAAO,GAAG,WAAW,GAAG,QAAQ,CAAA;AAgDpE;;;;GAIG;AACH,wBAAsB,iBAAiB,CACrC,IAAI,EAAE,kBAAkB,GACvB,OAAO,CAAC,qBAAqB,CAAC,
|
|
1
|
+
{"version":3,"file":"providerWizard.d.ts","sourceRoot":"","sources":["../../../src/dsh-adapter/providerWizard.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAGH,OAAO,EAEL,KAAK,qBAAqB,EAE1B,KAAK,sBAAsB,EAC5B,MAAM,iCAAiC,CAAA;AACxC,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAA;AAE9D,iFAAiF;AACjF,eAAO,MAAM,iBAAiB,QAAoC,CAAA;AAElE,2EAA2E;AAC3E,eAAO,MAAM,kBAAkB,2EAIrB,CAAA;AAEV;;;GAGG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAElD;AAED,mEAAmE;AACnE,MAAM,WAAW,wBAAwB;IACvC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAA;CAC7B;AAED;;;;;GAKG;AACH,MAAM,WAAW,iBAAiB;IAChC,uEAAuE;IACvE,oBAAoB,IAAI,SAAS,wBAAwB,EAAE,CAAA;IAC3D,kEAAkE;IAClE,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAA;IACnC,sEAAsE;IACtE,cAAc,CAAC,OAAO,EAAE;QACtB,QAAQ,CAAC,EAAE,MAAM,CAAA;QACjB,OAAO,CAAC,EAAE,MAAM,CAAA;QAChB,GAAG,CAAC,EAAE,MAAM,CAAA;QACZ,MAAM,CAAC,EAAE,MAAM,CAAA;KAChB,GAAG,OAAO,CAAC,SAAS,kBAAkB,EAAE,CAAC,CAAA;IAC1C,0EAA0E;IAC1E,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;IAChC;;;;OAIG;IACH,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAAA;IACxD,2EAA2E;IAC3E,eAAe,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IACjE,yDAAyD;IACzD,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IACnD;;;OAGG;IACH,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CAC7E;AAED,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,IAAI,EAAE,iBAAiB,CAAA;IAChC,QAAQ,CAAC,GAAG,EAAE,CACZ,OAAO,EAAE,sBAAsB,EAC/B,OAAO,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,OAAO,CAAA;KAAE,KAC3B,OAAO,CAAC,qBAAqB,CAAC,CAAA;IACnC,QAAQ,CAAC,MAAM,EAAE,CACf,IAAI,EAAE,MAAM,EACZ,OAAO,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,OAAO,GAAG,SAAS,GAAG,SAAS,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,KACtE,IAAI,CAAA;IACT,QAAQ,CAAC,SAAS,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,MAAM,EAAE,KAAK,IAAI,CAAA;IACrE,2EAA2E;IAC3E,QAAQ,CAAC,OAAO,EAAE,MAAM,OAAO,CAAA;IAC/B,QAAQ,CAAC,WAAW,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAA;CAC5E;AAED,MAAM,MAAM,qBAAqB,GAAG,OAAO,GAAG,WAAW,GAAG,QAAQ,CAAA;AAgDpE;;;;GAIG;AACH,wBAAsB,iBAAiB,CACrC,IAAI,EAAE,kBAAkB,GACvB,OAAO,CAAC,qBAAqB,CAAC,CA8MhC"}
|
|
@@ -86,7 +86,9 @@ export async function runProviderWizard(deps) {
|
|
|
86
86
|
questions: [optionQuestion('catalog', t('provider-q-catalog'), [
|
|
87
87
|
...candidates.map(candidate => ({
|
|
88
88
|
label: candidate.provider,
|
|
89
|
-
description: candidate.displayName
|
|
89
|
+
description: candidate.displayName === candidate.provider
|
|
90
|
+
? undefined
|
|
91
|
+
: candidate.displayName,
|
|
90
92
|
})),
|
|
91
93
|
{ label: otherLabel, description: t('provider-opt-other-route-desc') },
|
|
92
94
|
], { hideCustomInput: true })],
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { PreviewEntry, SessionDigest } from './types.js';
|
|
2
|
+
/** Head window budget. Eight times the measured worst-case prompt offset. */
|
|
3
|
+
export declare const HEAD_WINDOW_BYTES: number;
|
|
4
|
+
/** Head frame ceiling — a cost bound independent of how the bytes compress. */
|
|
5
|
+
export declare const HEAD_MAX_FRAMES = 128;
|
|
6
|
+
/** Tail window budget. Wider than the head: trailing frames carry payloads. */
|
|
7
|
+
export declare const TAIL_WINDOW_BYTES: number;
|
|
8
|
+
/**
|
|
9
|
+
* Read both windows of one session log.
|
|
10
|
+
*
|
|
11
|
+
* @param path - Absolute artifact path.
|
|
12
|
+
* @param cwd - Working directory, for the last-resort title.
|
|
13
|
+
* @returns The digest. An unreadable log still yields a usable record: the
|
|
14
|
+
* title falls back to the directory basename and says so through its source.
|
|
15
|
+
*/
|
|
16
|
+
export declare function digestSession(path: string, cwd: string): SessionDigest;
|
|
17
|
+
/**
|
|
18
|
+
* The last exchanges of a session, for the browser's preview pane.
|
|
19
|
+
*
|
|
20
|
+
* Bounded like everything else here: the preview shows the end of the
|
|
21
|
+
* conversation because that is what the tail window holds, and because the end
|
|
22
|
+
* is what tells you whether this is the session you meant.
|
|
23
|
+
*
|
|
24
|
+
* @param path - Absolute artifact path.
|
|
25
|
+
* @param limit - How many entries to keep, newest last.
|
|
26
|
+
* @returns Entries in log order.
|
|
27
|
+
*/
|
|
28
|
+
export declare function previewSession(path: string, limit: number): PreviewEntry[];
|
|
29
|
+
//# sourceMappingURL=digest.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"digest.d.ts","sourceRoot":"","sources":["../../../../src/dsh-adapter/sessions/digest.ts"],"names":[],"mappings":"AAuBA,OAAO,KAAK,EAAE,YAAY,EAAE,aAAa,EAAgB,MAAM,YAAY,CAAA;AAE3E,6EAA6E;AAC7E,eAAO,MAAM,iBAAiB,QAAY,CAAA;AAC1C,+EAA+E;AAC/E,eAAO,MAAM,eAAe,MAAM,CAAA;AAClC,+EAA+E;AAC/E,eAAO,MAAM,iBAAiB,QAAa,CAAA;AA+F3C;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,aAAa,CA8CtE;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,YAAY,EAAE,CAyB1E"}
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a bounded read can learn about a session log.
|
|
3
|
+
*
|
|
4
|
+
* Two windows, one at each end, and never anything in between:
|
|
5
|
+
*
|
|
6
|
+
* - The HEAD holds the session envelope, the boot policy events, and the
|
|
7
|
+
* opening prompt. Measured across a real corpus, the first user prompt lands
|
|
8
|
+
* within 8,107 bytes of the start (524 in its `agent/inbox/spliced` form),
|
|
9
|
+
* so a 64 KB window carries an eightfold margin.
|
|
10
|
+
* - The TAIL holds whatever was appended most recently: the current title
|
|
11
|
+
* (titles are re-emitted, and the last one wins), the model of the last
|
|
12
|
+
* request, and the last exchanges for the preview.
|
|
13
|
+
*
|
|
14
|
+
* Titles carry their own provenance, so this module does not have to guess.
|
|
15
|
+
* A title written by a provider records `source.kind: 'provider'`; the TUI's
|
|
16
|
+
* own rename paths append `{ title }` with no source at all. That difference
|
|
17
|
+
* is the evidence behind {@link SessionTitle.source}, which is why the picker
|
|
18
|
+
* can dim a fallback and explain a name instead of merely displaying one.
|
|
19
|
+
*
|
|
20
|
+
* @module @deepseek-harness-tui/dsh-tui/sessions/digest
|
|
21
|
+
*/
|
|
22
|
+
import { basename } from 'node:path';
|
|
23
|
+
import { decodeFrames, decodeTail, readWindow, walkFrames } from './frames.js';
|
|
24
|
+
/** Head window budget. Eight times the measured worst-case prompt offset. */
|
|
25
|
+
export const HEAD_WINDOW_BYTES = 64 * 1024;
|
|
26
|
+
/** Head frame ceiling — a cost bound independent of how the bytes compress. */
|
|
27
|
+
export const HEAD_MAX_FRAMES = 128;
|
|
28
|
+
/** Tail window budget. Wider than the head: trailing frames carry payloads. */
|
|
29
|
+
export const TAIL_WINDOW_BYTES = 128 * 1024;
|
|
30
|
+
/** Longest preview excerpt kept per message, in characters. */
|
|
31
|
+
const PREVIEW_CHARS = 400;
|
|
32
|
+
/** The first text block of a message `content` payload. */
|
|
33
|
+
function textOfContent(content) {
|
|
34
|
+
if (typeof content === 'string')
|
|
35
|
+
return content.trim() || undefined;
|
|
36
|
+
if (!Array.isArray(content))
|
|
37
|
+
return undefined;
|
|
38
|
+
for (const block of content) {
|
|
39
|
+
if (block === null || typeof block !== 'object')
|
|
40
|
+
continue;
|
|
41
|
+
const record = block;
|
|
42
|
+
if (record['type'] !== 'text')
|
|
43
|
+
continue;
|
|
44
|
+
const value = record['text'];
|
|
45
|
+
if (typeof value === 'string' && value.trim().length > 0)
|
|
46
|
+
return value.trim();
|
|
47
|
+
}
|
|
48
|
+
return undefined;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Whether a message's `source` marks it as typed by the person at the
|
|
52
|
+
* keyboard. Plugin injections, instruction snapshots, skill catalogues and
|
|
53
|
+
* sub-agent reports all arrive as user-role messages too, and counting them
|
|
54
|
+
* would report a conversation where none happened.
|
|
55
|
+
*/
|
|
56
|
+
function isHumanSource(source) {
|
|
57
|
+
if (source === undefined || source === null)
|
|
58
|
+
return true;
|
|
59
|
+
if (typeof source !== 'object')
|
|
60
|
+
return false;
|
|
61
|
+
return source['kind'] === 'user';
|
|
62
|
+
}
|
|
63
|
+
/** The human prompt carried by one log line, in either of its two forms. */
|
|
64
|
+
function humanPrompt(line) {
|
|
65
|
+
const data = line['data'];
|
|
66
|
+
if (data === null || typeof data !== 'object')
|
|
67
|
+
return undefined;
|
|
68
|
+
const record = data;
|
|
69
|
+
if (line['type'] === 'user/message') {
|
|
70
|
+
return isHumanSource(record['source']) ? textOfContent(record['content']) : undefined;
|
|
71
|
+
}
|
|
72
|
+
// The inbox splice precedes the durable user/message and reaches the log
|
|
73
|
+
// several frames earlier, which is what keeps the head window small.
|
|
74
|
+
if (line['type'] === 'agent/inbox/spliced') {
|
|
75
|
+
const inserted = record['inserted'];
|
|
76
|
+
if (!Array.isArray(inserted))
|
|
77
|
+
return undefined;
|
|
78
|
+
for (const message of inserted) {
|
|
79
|
+
if (message === null || typeof message !== 'object')
|
|
80
|
+
continue;
|
|
81
|
+
const entry = message;
|
|
82
|
+
if (entry['role'] !== 'user' || !isHumanSource(entry['source']))
|
|
83
|
+
continue;
|
|
84
|
+
const text = textOfContent(entry['content']);
|
|
85
|
+
if (text !== undefined)
|
|
86
|
+
return text;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return undefined;
|
|
90
|
+
}
|
|
91
|
+
/** A `session/title` payload, with the provenance that classifies it. */
|
|
92
|
+
function titleOf(line) {
|
|
93
|
+
if (line['type'] !== 'session/title')
|
|
94
|
+
return undefined;
|
|
95
|
+
const data = line['data'];
|
|
96
|
+
if (data === null || typeof data !== 'object')
|
|
97
|
+
return undefined;
|
|
98
|
+
const record = data;
|
|
99
|
+
const text = record['title'];
|
|
100
|
+
if (typeof text !== 'string' || text.trim().length === 0)
|
|
101
|
+
return undefined;
|
|
102
|
+
const source = record['source'];
|
|
103
|
+
const byProvider = source !== null &&
|
|
104
|
+
typeof source === 'object' &&
|
|
105
|
+
source['kind'] === 'provider';
|
|
106
|
+
return { text: text.trim(), source: byProvider ? 'auto' : 'renamed' };
|
|
107
|
+
}
|
|
108
|
+
/** The route recorded by a `request/context` event. */
|
|
109
|
+
function modelOf(line) {
|
|
110
|
+
if (line['type'] !== 'request/context')
|
|
111
|
+
return undefined;
|
|
112
|
+
const data = line['data'];
|
|
113
|
+
if (data === null || typeof data !== 'object')
|
|
114
|
+
return undefined;
|
|
115
|
+
const model = data['model'];
|
|
116
|
+
return typeof model === 'string' && model.length > 0 ? model : undefined;
|
|
117
|
+
}
|
|
118
|
+
/** The label a delegated run was started under. */
|
|
119
|
+
function labelOf(line) {
|
|
120
|
+
if (line['type'] !== 'subagent/descriptor')
|
|
121
|
+
return undefined;
|
|
122
|
+
const data = line['data'];
|
|
123
|
+
if (data === null || typeof data !== 'object')
|
|
124
|
+
return undefined;
|
|
125
|
+
const label = data['label'];
|
|
126
|
+
return typeof label === 'string' && label.trim().length > 0 ? label.trim() : undefined;
|
|
127
|
+
}
|
|
128
|
+
/** Epoch-ms of a log line, when it carries one. */
|
|
129
|
+
function timeOf(line) {
|
|
130
|
+
const time = line['time'];
|
|
131
|
+
return typeof time === 'number' && Number.isFinite(time) ? time : undefined;
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Read both windows of one session log.
|
|
135
|
+
*
|
|
136
|
+
* @param path - Absolute artifact path.
|
|
137
|
+
* @param cwd - Working directory, for the last-resort title.
|
|
138
|
+
* @returns The digest. An unreadable log still yields a usable record: the
|
|
139
|
+
* title falls back to the directory basename and says so through its source.
|
|
140
|
+
*/
|
|
141
|
+
export function digestSession(path, cwd) {
|
|
142
|
+
const head = readWindow(path, HEAD_WINDOW_BYTES);
|
|
143
|
+
if (head === undefined) {
|
|
144
|
+
return { title: undefined, hasPrompt: false, model: undefined, label: undefined };
|
|
145
|
+
}
|
|
146
|
+
const headLines = decodeFrames(head.buffer, walkFrames(head.buffer, 0, HEAD_MAX_FRAMES));
|
|
147
|
+
let prompt;
|
|
148
|
+
let headTitle;
|
|
149
|
+
let label;
|
|
150
|
+
for (const line of headLines) {
|
|
151
|
+
prompt ??= humanPrompt(line);
|
|
152
|
+
headTitle ??= titleOf(line);
|
|
153
|
+
label ??= labelOf(line);
|
|
154
|
+
}
|
|
155
|
+
// Absence of a prompt only means "empty" when the window actually saw the
|
|
156
|
+
// whole log. A log too large for the window has a conversation in it by
|
|
157
|
+
// construction, and erring toward listing it is the safe direction: hiding
|
|
158
|
+
// a real session is a defect, showing a boot artifact is a nuisance.
|
|
159
|
+
const hasPrompt = prompt !== undefined || !head.whole;
|
|
160
|
+
// A head window that already covered the whole log IS the tail.
|
|
161
|
+
const tail = head.whole ? undefined : readWindow(path, TAIL_WINDOW_BYTES, true);
|
|
162
|
+
const tailLines = tail === undefined ? headLines : decodeTail(tail);
|
|
163
|
+
let tailTitle;
|
|
164
|
+
let model;
|
|
165
|
+
for (const line of tailLines) {
|
|
166
|
+
const title = titleOf(line);
|
|
167
|
+
if (title !== undefined)
|
|
168
|
+
tailTitle = title;
|
|
169
|
+
const route = modelOf(line);
|
|
170
|
+
if (route !== undefined)
|
|
171
|
+
model = route;
|
|
172
|
+
}
|
|
173
|
+
const resolved = tailTitle ??
|
|
174
|
+
headTitle ??
|
|
175
|
+
(prompt === undefined ? undefined : { text: prompt, source: 'prompt' });
|
|
176
|
+
return {
|
|
177
|
+
title: resolved ?? { text: basename(cwd), source: 'fallback' },
|
|
178
|
+
hasPrompt,
|
|
179
|
+
model,
|
|
180
|
+
label,
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* The last exchanges of a session, for the browser's preview pane.
|
|
185
|
+
*
|
|
186
|
+
* Bounded like everything else here: the preview shows the end of the
|
|
187
|
+
* conversation because that is what the tail window holds, and because the end
|
|
188
|
+
* is what tells you whether this is the session you meant.
|
|
189
|
+
*
|
|
190
|
+
* @param path - Absolute artifact path.
|
|
191
|
+
* @param limit - How many entries to keep, newest last.
|
|
192
|
+
* @returns Entries in log order.
|
|
193
|
+
*/
|
|
194
|
+
export function previewSession(path, limit) {
|
|
195
|
+
const window = readWindow(path, TAIL_WINDOW_BYTES, true);
|
|
196
|
+
if (window === undefined)
|
|
197
|
+
return [];
|
|
198
|
+
const lines = decodeTail(window);
|
|
199
|
+
const entries = [];
|
|
200
|
+
for (const line of lines) {
|
|
201
|
+
const data = line['data'];
|
|
202
|
+
if (data === null || typeof data !== 'object')
|
|
203
|
+
continue;
|
|
204
|
+
const record = data;
|
|
205
|
+
if (line['type'] === 'user/message') {
|
|
206
|
+
if (!isHumanSource(record['source']))
|
|
207
|
+
continue;
|
|
208
|
+
const text = textOfContent(record['content']);
|
|
209
|
+
if (text !== undefined)
|
|
210
|
+
entries.push({ role: 'user', text: text.slice(0, PREVIEW_CHARS), at: timeOf(line) });
|
|
211
|
+
continue;
|
|
212
|
+
}
|
|
213
|
+
if (line['type'] === 'assistant/message') {
|
|
214
|
+
const message = record['message'];
|
|
215
|
+
if (message === null || typeof message !== 'object')
|
|
216
|
+
continue;
|
|
217
|
+
const text = textOfContent(message['content']);
|
|
218
|
+
if (text !== undefined) {
|
|
219
|
+
entries.push({ role: 'assistant', text: text.slice(0, PREVIEW_CHARS), at: timeOf(line) });
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
return entries.slice(-limit);
|
|
224
|
+
}
|