@aiwayds/dsh-tui-pi 2.6.0 → 2.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -2
- package/README.zh-CN.md +1 -2
- package/lib/history-fork.d.ts +83 -0
- package/lib/history-fork.js +182 -0
- package/lib/history-fork.js.map +1 -0
- package/lib/history-turns.d.ts +10 -0
- package/lib/history-turns.js +16 -0
- package/lib/history-turns.js.map +1 -1
- package/lib/history.d.ts +29 -0
- package/lib/history.js +50 -2
- package/lib/history.js.map +1 -1
- package/lib/hotkeys.d.ts +1 -1
- package/lib/hotkeys.js +2 -3
- package/lib/hotkeys.js.map +1 -1
- package/lib/index.js +182 -50
- package/lib/index.js.map +1 -1
- package/lib/keymap.d.ts +0 -5
- package/lib/keymap.js +3 -8
- package/lib/keymap.js.map +1 -1
- package/lib/preset-dialog.d.ts +152 -0
- package/lib/preset-dialog.js +254 -0
- package/lib/preset-dialog.js.map +1 -0
- package/lib/preset.d.ts +16 -9
- package/lib/preset.js +18 -11
- package/lib/preset.js.map +1 -1
- package/lib/session.d.ts +41 -0
- package/lib/session.js +82 -25
- package/lib/session.js.map +1 -1
- package/lib/tui.js +1 -1
- package/lib/tui.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The preset-switch confirmation dialog + the switch orchestration.
|
|
3
|
+
*
|
|
4
|
+
* Switching presets is an EXPLICIT action with a visible cost: the preset is
|
|
5
|
+
* baked into a session at creation time, so applying a new selection means
|
|
6
|
+
* starting a NEW session — the current one is detached (and stays resumable
|
|
7
|
+
* via /resume). Whenever a live session exists, the switch therefore goes
|
|
8
|
+
* through this confirmation gate first (mirroring the /resume repair
|
|
9
|
+
* dialog's pure-reducer split, src/repair-dialog.ts, so the decision matrix
|
|
10
|
+
* stays unit-testable without a terminal); on a fresh TUI without a session
|
|
11
|
+
* there is nothing to leave behind and the selection applies directly.
|
|
12
|
+
*
|
|
13
|
+
* The dialog offers exactly three ways out: FORK the current conversation
|
|
14
|
+
* into the new session on the preset (context weight included), start FRESH
|
|
15
|
+
* (an empty session on the preset), or CANCEL. Title/body wording
|
|
16
|
+
* distinguishes a switch to a different preset from a restart on the
|
|
17
|
+
* current one. ↑↓ moves the selection, `1`/`2`/`3` select directly, Enter
|
|
18
|
+
* confirms, Esc = cancel. Framing/focus follow the shared overlay contract:
|
|
19
|
+
* PanelHost framing, close re-focuses the CURRENT editor instance through
|
|
20
|
+
* `restoreFocus`.
|
|
21
|
+
*/
|
|
22
|
+
import { getKeybindings } from '@earendil-works/pi-tui';
|
|
23
|
+
import { PanelHost, panelThemeFns } from "./panels.js";
|
|
24
|
+
import { BOLD, RESET, ansiFg } from "./theme/index.js";
|
|
25
|
+
import { clipToWidth, wrapText } from "./text.js";
|
|
26
|
+
/** The three choices in display order; index 0 is preselected. */
|
|
27
|
+
export const PRESET_CONFIRM_OPTION_IDS = ['fork', 'fresh', 'cancel'];
|
|
28
|
+
/** The dialog wording for `name`: `restart` marks a re-selection of the current preset. */
|
|
29
|
+
export function presetConfirmWording(name, restart) {
|
|
30
|
+
return restart
|
|
31
|
+
? {
|
|
32
|
+
title: `● Restart session on ${name}?`,
|
|
33
|
+
fork: `Fork & restart — new session on ${name}, carrying this conversation`,
|
|
34
|
+
fresh: `Restart now — new empty session on ${name}`,
|
|
35
|
+
firstPoint: `Restarting starts a NEW session on ${name}.`,
|
|
36
|
+
}
|
|
37
|
+
: {
|
|
38
|
+
title: `● Switch preset to ${name}?`,
|
|
39
|
+
fork: `Fork & switch — new session on ${name}, carrying this conversation`,
|
|
40
|
+
fresh: `Fresh start — new empty session on ${name}`,
|
|
41
|
+
firstPoint: `Switching starts a NEW session on ${name}.`,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
/** The fixed option rows for one wording. */
|
|
45
|
+
export function presetConfirmOptions(wording) {
|
|
46
|
+
return [
|
|
47
|
+
{ id: 'fork', text: wording.fork },
|
|
48
|
+
{ id: 'fresh', text: wording.fresh },
|
|
49
|
+
{ id: 'cancel', text: 'Cancel — stay on the current session' },
|
|
50
|
+
];
|
|
51
|
+
}
|
|
52
|
+
/** Accent-BOLD dialog title. */
|
|
53
|
+
export function presetConfirmTitle(wording) {
|
|
54
|
+
return wording.title;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* The fixed body points, one entry per bullet — each wrapped to the terminal
|
|
58
|
+
* width by the panel before painting.
|
|
59
|
+
*/
|
|
60
|
+
export function presetConfirmBody(wording) {
|
|
61
|
+
return [
|
|
62
|
+
wording.firstPoint,
|
|
63
|
+
'Fork carries this conversation into the new session (compacted context included) — fresh starts empty.',
|
|
64
|
+
];
|
|
65
|
+
}
|
|
66
|
+
/** Footer hint — hardcoded like every other panel footer (English-only). */
|
|
67
|
+
export const PRESET_CONFIRM_FOOTER = '↑↓ select · 1/2/3 pick · Enter confirm · Esc cancel';
|
|
68
|
+
export function initialPresetConfirmState() {
|
|
69
|
+
return { selected: 0 };
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Apply one raw key sequence to the dialog state. Unknown keys are no-ops;
|
|
73
|
+
* anything after a settle is ignored (single terminal outcome guard).
|
|
74
|
+
*/
|
|
75
|
+
export function updatePresetConfirm(state, data) {
|
|
76
|
+
if (state.settled !== undefined)
|
|
77
|
+
return state;
|
|
78
|
+
const kb = getKeybindings();
|
|
79
|
+
if (kb.matches(data, 'tui.select.cancel'))
|
|
80
|
+
return { ...state, settled: 'cancel' };
|
|
81
|
+
if (kb.matches(data, 'tui.input.submit'))
|
|
82
|
+
return { ...state, settled: 'confirm' };
|
|
83
|
+
if (kb.matches(data, 'tui.select.up')) {
|
|
84
|
+
return { ...state, selected: Math.max(0, state.selected - 1) };
|
|
85
|
+
}
|
|
86
|
+
if (kb.matches(data, 'tui.select.down')) {
|
|
87
|
+
return { ...state, selected: Math.min(PRESET_CONFIRM_OPTION_IDS.length - 1, state.selected + 1) };
|
|
88
|
+
}
|
|
89
|
+
// Digit direct-select (1-based): selects the row, Enter still confirms —
|
|
90
|
+
// same select-then-confirm split as the routing dialog.
|
|
91
|
+
const digit = /^([1-9])$/.exec(data);
|
|
92
|
+
if (digit !== null) {
|
|
93
|
+
const index = Number(digit[1]) - 1;
|
|
94
|
+
if (index < PRESET_CONFIRM_OPTION_IDS.length)
|
|
95
|
+
return { ...state, selected: index };
|
|
96
|
+
}
|
|
97
|
+
return state;
|
|
98
|
+
}
|
|
99
|
+
/** Resolved dialog outcome: the chosen action, or undefined on cancel. */
|
|
100
|
+
export function presetConfirmOutcome(state) {
|
|
101
|
+
if (state.settled !== 'confirm')
|
|
102
|
+
return undefined;
|
|
103
|
+
return PRESET_CONFIRM_OPTION_IDS[state.selected];
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* The framed overlay component. Renders the title, the wrapped body points
|
|
107
|
+
* and the three option rows; every key goes through
|
|
108
|
+
* {@link updatePresetConfirm}, and the first terminal key fires `onFinish`
|
|
109
|
+
* exactly once.
|
|
110
|
+
*/
|
|
111
|
+
export class PresetConfirmPanel {
|
|
112
|
+
theme;
|
|
113
|
+
wording;
|
|
114
|
+
onFinish;
|
|
115
|
+
requestRenderFn;
|
|
116
|
+
state = initialPresetConfirmState();
|
|
117
|
+
constructor(theme, wording, onFinish, requestRender) {
|
|
118
|
+
this.theme = theme;
|
|
119
|
+
this.wording = wording;
|
|
120
|
+
this.onFinish = onFinish;
|
|
121
|
+
this.requestRenderFn = requestRender;
|
|
122
|
+
}
|
|
123
|
+
invalidate() { }
|
|
124
|
+
render(width) {
|
|
125
|
+
const fns = panelThemeFns(this.theme);
|
|
126
|
+
const wrap = Math.max(2, width - 2);
|
|
127
|
+
// Word-wrap the body FIRST, then paint (iron rule: width math runs on
|
|
128
|
+
// plain text, ANSI goes on after clipping).
|
|
129
|
+
const lines = [
|
|
130
|
+
fns.accent(BOLD + clipToWidth(presetConfirmTitle(this.wording), wrap) + RESET),
|
|
131
|
+
...presetConfirmBody(this.wording).flatMap(point => wrapText(point, wrap).map(segment => fns.muted(clipToWidth(segment, wrap)))),
|
|
132
|
+
'',
|
|
133
|
+
];
|
|
134
|
+
const options = presetConfirmOptions(this.wording);
|
|
135
|
+
for (let i = 0; i < options.length; i++) {
|
|
136
|
+
const option = options[i];
|
|
137
|
+
const marker = i === this.state.selected ? '▸' : ' ';
|
|
138
|
+
const row = clipToWidth(`${marker} ${i + 1}. ${option.text}`, wrap);
|
|
139
|
+
lines.push(i === this.state.selected
|
|
140
|
+
? ansiFg(this.theme.palette.accent) + BOLD + row + RESET
|
|
141
|
+
: fns.muted(row));
|
|
142
|
+
}
|
|
143
|
+
lines.push('');
|
|
144
|
+
lines.push(fns.subtle(clipToWidth(PRESET_CONFIRM_FOOTER, wrap)));
|
|
145
|
+
return lines;
|
|
146
|
+
}
|
|
147
|
+
handleInput(data) {
|
|
148
|
+
const previous = this.state;
|
|
149
|
+
this.state = updatePresetConfirm(this.state, data);
|
|
150
|
+
if (this.state === previous)
|
|
151
|
+
return;
|
|
152
|
+
if (this.state.settled === undefined) {
|
|
153
|
+
this.requestRenderFn();
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
this.onFinish(presetConfirmOutcome(this.state));
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Open the preset-switch confirmation dialog for one target preset.
|
|
161
|
+
* Resolves the chosen action (`'fork'` / `'fresh'`) or `'cancelled'` (Esc,
|
|
162
|
+
* or an overlay that failed to mount — treated as cancel so a half-mounted
|
|
163
|
+
* dialog can never imply consent). Closing always hands focus back through
|
|
164
|
+
* `restoreFocus` before the promise settles.
|
|
165
|
+
*/
|
|
166
|
+
export function openPresetConfirmDialog(tui, theme, presetName, restart, restoreFocus) {
|
|
167
|
+
return new Promise(resolve => {
|
|
168
|
+
let settled = false;
|
|
169
|
+
const settle = (outcome) => {
|
|
170
|
+
if (settled)
|
|
171
|
+
return;
|
|
172
|
+
settled = true;
|
|
173
|
+
resolve(outcome);
|
|
174
|
+
};
|
|
175
|
+
// A half-mounted overlay must not strand the keyboard: PanelHost's
|
|
176
|
+
// onError closes + calls restoreFocus, then we settle as cancelled.
|
|
177
|
+
const host = new PanelHost(tui, theme, () => {
|
|
178
|
+
restoreFocus();
|
|
179
|
+
settle('cancelled');
|
|
180
|
+
});
|
|
181
|
+
const finish = (outcome) => {
|
|
182
|
+
host.close();
|
|
183
|
+
restoreFocus();
|
|
184
|
+
settle(outcome === 'fork' || outcome === 'fresh' ? outcome : 'cancelled');
|
|
185
|
+
};
|
|
186
|
+
const panel = new PresetConfirmPanel(theme, presetConfirmWording(presetName, restart), outcome => finish(outcome), () => tui.requestRender());
|
|
187
|
+
// maxHeight is a hard slice in pi-tui: title + 2 wrapped body points +
|
|
188
|
+
// 3 options + footer ≈ 9 content rows + 4 frame rows; 75% of the 24-row
|
|
189
|
+
// e2e floor is 18 (the /resume picker's headroom).
|
|
190
|
+
host.open(panel, '70%', '75%');
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* One preset switch, from any entry path (picker Enter, `/preset <name>`,
|
|
195
|
+
* `/preset next`): with a live session the confirmation dialog gates the
|
|
196
|
+
* switch — cancelling changes NOTHING (selection index untouched, no
|
|
197
|
+
* detach); `fresh` commits an empty new session (the /new path); `fork`
|
|
198
|
+
* commits a new session seeded with the current conversation. Without a
|
|
199
|
+
* live session the selection applies directly (the first submit already
|
|
200
|
+
* creates the session on it). Pure with respect to `state` apart from the
|
|
201
|
+
* commit-index write, and fully injectable.
|
|
202
|
+
*/
|
|
203
|
+
export async function performPresetSwitch(state, target, deps) {
|
|
204
|
+
if (deps.hasLiveSession()) {
|
|
205
|
+
const restart = state.roster[state.index]?.id === target.id;
|
|
206
|
+
const action = await deps.confirmSwitch(target.name, restart);
|
|
207
|
+
if (action === 'cancel') {
|
|
208
|
+
return { switched: false, message: 'Preset unchanged — still on the current session.' };
|
|
209
|
+
}
|
|
210
|
+
// Snapshot BEFORE the index write: a commit that throws must not leave
|
|
211
|
+
// the selection (and with it the footer) advertising a preset the live
|
|
212
|
+
// session is not on.
|
|
213
|
+
const previousIndex = state.index;
|
|
214
|
+
const index = state.roster.findIndex(preset => preset.id === target.id);
|
|
215
|
+
try {
|
|
216
|
+
if (index >= 0)
|
|
217
|
+
state.index = index;
|
|
218
|
+
if (action === 'fork') {
|
|
219
|
+
await deps.forkCommit(target.id);
|
|
220
|
+
return { switched: true, message: `Preset → ${target.name} — new session forked from this conversation.` };
|
|
221
|
+
}
|
|
222
|
+
await deps.commit(target.id);
|
|
223
|
+
return { switched: true, message: `Preset → ${target.name} — new empty session started on it.` };
|
|
224
|
+
}
|
|
225
|
+
catch (error) {
|
|
226
|
+
state.index = previousIndex;
|
|
227
|
+
throw error;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
const index = state.roster.findIndex(preset => preset.id === target.id);
|
|
231
|
+
if (index >= 0)
|
|
232
|
+
state.index = index;
|
|
233
|
+
await deps.commit(target.id);
|
|
234
|
+
return { switched: true, message: `Preset → ${target.name} — new session started on it.` };
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* The balanced completed-turn prefix of a session snapshot: every event up
|
|
238
|
+
* to and including the last `turn/end` — the in-flight turn is unbalanced
|
|
239
|
+
* and cannot replay as a valid seed. Contiguous from seq 0 (live sequence
|
|
240
|
+
* numbers equal array indexes) and empty before any completed turn, in which
|
|
241
|
+
* case a fork degrades to a fresh session. The same slice the host's fork
|
|
242
|
+
* subagent backend (`dsh-subagent-fork-in-process`) takes.
|
|
243
|
+
*/
|
|
244
|
+
export function completedTurnSeed(events) {
|
|
245
|
+
let lastEnd = -1;
|
|
246
|
+
for (let i = events.length - 1; i >= 0; i--) {
|
|
247
|
+
if (events[i].type === 'turn/end') {
|
|
248
|
+
lastEnd = i;
|
|
249
|
+
break;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
return lastEnd < 0 ? [] : events.slice(0, lastEnd + 1);
|
|
253
|
+
}
|
|
254
|
+
//# sourceMappingURL=preset-dialog.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"preset-dialog.js","sourceRoot":"","sources":["../src/preset-dialog.ts"],"names":[],"mappings":"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"}
|
package/lib/preset.d.ts
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Agent-preset state manager — scans the filesystem for preset directories
|
|
3
3
|
* (same discovery logic as @deepseek-ai/dsh-agent-presets), tracks the user's
|
|
4
|
-
*
|
|
4
|
+
* /preset selection, and formats the footer label.
|
|
5
5
|
*
|
|
6
6
|
* Presets are a dsh deployment concept: each preset composes a session's agent
|
|
7
7
|
* from a different set of plugins/tools. The TUI scans the preset roots once
|
|
8
|
-
* at startup and lets the user
|
|
9
|
-
*
|
|
8
|
+
* at startup and lets the user switch through /preset; a switch is an explicit,
|
|
9
|
+
* confirmed action that starts a NEW session on the chosen preset (a fresh TUI
|
|
10
|
+
* without a live session applies the selection directly).
|
|
10
11
|
*
|
|
11
|
-
* The roster is O(1) to read (an in-memory array)
|
|
12
|
-
*
|
|
13
|
-
* scans the filesystem.
|
|
12
|
+
* The roster is O(1) to read (an in-memory array). The module is pure except
|
|
13
|
+
* for the async `fetchPresetRoster` which scans the filesystem.
|
|
14
14
|
*/
|
|
15
15
|
/** One preset entry from the deployment roster. */
|
|
16
16
|
export interface PresetEntry {
|
|
@@ -55,8 +55,15 @@ export declare function resolvePresetRoots(): PresetRoot[];
|
|
|
55
55
|
* presets are found.
|
|
56
56
|
*/
|
|
57
57
|
export declare function fetchPresetRoster(): Promise<PresetEntry[]>;
|
|
58
|
-
/**
|
|
59
|
-
|
|
58
|
+
/**
|
|
59
|
+
* The entry `/preset next` would switch to — the roster item after the
|
|
60
|
+
* current selection, wrapping around. PURE (no index mutation): the switch
|
|
61
|
+
* only commits through the confirmation flow, so `next` must be able to
|
|
62
|
+
* preview its target first. With 0 or 1 entries there is nothing to switch
|
|
63
|
+
* to (the caller short-circuits); with ≥2 the result always differs from
|
|
64
|
+
* the current selection.
|
|
65
|
+
*/
|
|
66
|
+
export declare function peekNextPreset(state: PresetState): PresetEntry | undefined;
|
|
60
67
|
/** Return the currently selected preset, or undefined when the roster is empty. */
|
|
61
68
|
export declare function currentPreset(state: PresetState): PresetEntry | undefined;
|
|
62
69
|
/**
|
|
@@ -67,7 +74,7 @@ export declare function currentPreset(state: PresetState): PresetEntry | undefin
|
|
|
67
74
|
export declare function findPresetByName(state: PresetState, name: string): PresetEntry | undefined;
|
|
68
75
|
/** The preset id selected out of the box when the roster supplies it. It
|
|
69
76
|
* mirrors the dsh shipped default, but there is no mechanical binding:
|
|
70
|
-
* until the user
|
|
77
|
+
* until the user switches via /preset, the server-side
|
|
71
78
|
* `agent-presets.default` setting still governs session creation. */
|
|
72
79
|
export declare const DEFAULT_PRESET_ID = "standard";
|
|
73
80
|
/**
|
package/lib/preset.js
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Agent-preset state manager — scans the filesystem for preset directories
|
|
3
3
|
* (same discovery logic as @deepseek-ai/dsh-agent-presets), tracks the user's
|
|
4
|
-
*
|
|
4
|
+
* /preset selection, and formats the footer label.
|
|
5
5
|
*
|
|
6
6
|
* Presets are a dsh deployment concept: each preset composes a session's agent
|
|
7
7
|
* from a different set of plugins/tools. The TUI scans the preset roots once
|
|
8
|
-
* at startup and lets the user
|
|
9
|
-
*
|
|
8
|
+
* at startup and lets the user switch through /preset; a switch is an explicit,
|
|
9
|
+
* confirmed action that starts a NEW session on the chosen preset (a fresh TUI
|
|
10
|
+
* without a live session applies the selection directly).
|
|
10
11
|
*
|
|
11
|
-
* The roster is O(1) to read (an in-memory array)
|
|
12
|
-
*
|
|
13
|
-
* scans the filesystem.
|
|
12
|
+
* The roster is O(1) to read (an in-memory array). The module is pure except
|
|
13
|
+
* for the async `fetchPresetRoster` which scans the filesystem.
|
|
14
14
|
*/
|
|
15
15
|
import { access, readdir, readFile } from 'node:fs/promises';
|
|
16
16
|
import { join, resolve } from 'node:path';
|
|
@@ -151,11 +151,18 @@ export async function fetchPresetRoster() {
|
|
|
151
151
|
}
|
|
152
152
|
return roster;
|
|
153
153
|
}
|
|
154
|
-
/**
|
|
155
|
-
|
|
154
|
+
/**
|
|
155
|
+
* The entry `/preset next` would switch to — the roster item after the
|
|
156
|
+
* current selection, wrapping around. PURE (no index mutation): the switch
|
|
157
|
+
* only commits through the confirmation flow, so `next` must be able to
|
|
158
|
+
* preview its target first. With 0 or 1 entries there is nothing to switch
|
|
159
|
+
* to (the caller short-circuits); with ≥2 the result always differs from
|
|
160
|
+
* the current selection.
|
|
161
|
+
*/
|
|
162
|
+
export function peekNextPreset(state) {
|
|
156
163
|
if (state.roster.length <= 1)
|
|
157
|
-
return;
|
|
158
|
-
state.
|
|
164
|
+
return undefined;
|
|
165
|
+
return state.roster[(state.index + 1) % state.roster.length];
|
|
159
166
|
}
|
|
160
167
|
/** Return the currently selected preset, or undefined when the roster is empty. */
|
|
161
168
|
export function currentPreset(state) {
|
|
@@ -174,7 +181,7 @@ export function findPresetByName(state, name) {
|
|
|
174
181
|
}
|
|
175
182
|
/** The preset id selected out of the box when the roster supplies it. It
|
|
176
183
|
* mirrors the dsh shipped default, but there is no mechanical binding:
|
|
177
|
-
* until the user
|
|
184
|
+
* until the user switches via /preset, the server-side
|
|
178
185
|
* `agent-presets.default` setting still governs session creation. */
|
|
179
186
|
export const DEFAULT_PRESET_ID = 'standard';
|
|
180
187
|
/**
|
package/lib/preset.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"preset.js","sourceRoot":"","sources":["../src/preset.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAA;AAC5D,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AACzC,OAAO,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAA;AA0B5C,sEAAsE;AACtE,IAAI,kBAA4C,CAAA;AAChD,MAAM,UAAU,uBAAuB,CAAC,KAA+B;IACrE,kBAAkB,GAAG,KAAK,CAAA;AAC5B,CAAC;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,kBAAkB;IAChC,MAAM,KAAK,GAAiB,EAAE,CAAA;IAC9B,oEAAoE;IACpE,kEAAkE;IAClE,2EAA2E;IAC3E,4CAA4C;IAC5C,MAAM,YAAY,GAAG;QACnB,qGAAqG;QACrG,kGAAkG;QAClG,uEAAuE;QACvE,oEAAoE;KACrE,CAAA;IACD,KAAK,MAAM,CAAC,IAAI,YAAY,EAAE,CAAC;QAC7B,KAAK,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC,CAAA;IAC1C,CAAC;IACD,uCAAuC;IACvC,MAAM,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,OAAO,EAAE,EAAE,gBAAgB,CAAC,CAAC,CAAA;IAC3D,KAAK,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,CAAA;IAC7C,OAAO,KAAK,CAAA;AACd,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,oBAAoB,GAAqC;IAC7D,QAAQ,EAAE,UAAU;IACpB,OAAO,EAAE,SAAS;IAClB,MAAM,EAAE,QAAQ;IAChB,GAAG,EAAE,KAAK;CACX,CAAA;AAED;;;;;GAKG;AACH,KAAK,UAAU,QAAQ,CAAC,IAAgB;IACtC,IAAI,OAAmC,CAAA;IACvC,IAAI,CAAC;QACH,OAAO,GAAG,MAAM,OAAO,CAAC,IAAI,CAAC,IAAI,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAA;IAC7D,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAA,CAAC,2BAA2B;IACvC,CAAC;IACD,MAAM,OAAO,GAAkB,EAAE,CAAA;IACjC,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;QAC5B,IAAI,CAAC,KAAK,CAAC,WAAW,EAAE;YAAE,SAAQ;QAClC,kFAAkF;QAClF,IAAI,CAAC,0BAA0B,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC;YAAE,SAAQ;QAC1D,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,CAAC,CAAA;QACvC,MAAM,eAAe,GAAG,IAAI,CAAC,GAAG,EAAE,kBAAkB,CAAC,CAAA;QACrD,IAAI,CAAC;YACH,MAAM,MAAM,CAAC,eAAe,CAAC,CAAA;QAC/B,CAAC;QAAC,MAAM,CAAC;YACP,SAAQ,CAAC,6BAA6B;QACxC,CAAC;QACD,gEAAgE;QAChE,uEAAuE;QACvE,sCAAsC;QACtC,IAAI,IAAI,GAAG,KAAK,CAAC,IAAI,CAAA;QACrB,IAAI,WAA+B,CAAA;QACnC,IAAI,IAAY,CAAA;QAChB,IAAI,CAAC;YACH,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,CAAC,GAAG,EAAE,YAAY,CAAC,EAAE,MAAM,CAAC,CAAA;QACxD,CAAC;QAAC,MAAM,CAAC;YACP,IAAI,GAAG,EAAE,CAAA;QACX,CAAC;QACD,IAAI,IAAI,KAAK,EAAE,EAAE,CAAC;YAChB,MAAM,SAAS,GAAG,IAAI,CAAC,KAAK,CAAC,iBAAiB,CAAC,CAAA;YAC/C,IAAI,SAAS;gBAAE,IAAI,GAAG,SAAS,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAA;YACzC,MAAM,SAAS,GAAG,IAAI,CAAC,KAAK,CAAC,wBAAwB,CAAC,CAAA;YACtD,IAAI,SAAS;gBAAE,WAAW,GAAG,SAAS,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAA;QAClD,CAAC;QACD,wEAAwE;QACxE,yCAAyC;QACzC,MAAM,YAAY,GAAG,IAAI,KAAK,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAA;QAC3D,OAAO,CAAC,IAAI,CAAC;YACX,EAAE,EAAE,KAAK,CAAC,IAAI;YACd,IAAI,EAAE,oBAAoB,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,IAAI;YAC9C,GAAG,CAAC,YAAY,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,YAAY,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YACvD,WAAW;YACX,KAAK,EAAE,IAAI,CAAC,KAAK;YACjB,SAAS,EAAE,KAAK,EAAE,kCAAkC;SACrD,CAAC,CAAA;IACJ,CAAC;IACD,OAAO,OAAO,CAAA;AAChB,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB;IACrC,MAAM,KAAK,GAAG,kBAAkB,IAAI,kBAAkB,EAAE,CAAA;IACxD,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAA;IAC9B,MAAM,MAAM,GAAkB,EAAE,CAAA;IAChC,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,KAAK,MAAM,MAAM,IAAI,MAAM,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;YAC1C,IAAI,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC;gBAAE,SAAQ;YACjC,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,CAAA;YACnB,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAA;QACrB,CAAC;IACH,CAAC;IACD,OAAO,MAAM,CAAA;AACf,CAAC;AAED
|
|
1
|
+
{"version":3,"file":"preset.js","sourceRoot":"","sources":["../src/preset.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAA;AAC5D,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AACzC,OAAO,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAA;AA0B5C,sEAAsE;AACtE,IAAI,kBAA4C,CAAA;AAChD,MAAM,UAAU,uBAAuB,CAAC,KAA+B;IACrE,kBAAkB,GAAG,KAAK,CAAA;AAC5B,CAAC;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,kBAAkB;IAChC,MAAM,KAAK,GAAiB,EAAE,CAAA;IAC9B,oEAAoE;IACpE,kEAAkE;IAClE,2EAA2E;IAC3E,4CAA4C;IAC5C,MAAM,YAAY,GAAG;QACnB,qGAAqG;QACrG,kGAAkG;QAClG,uEAAuE;QACvE,oEAAoE;KACrE,CAAA;IACD,KAAK,MAAM,CAAC,IAAI,YAAY,EAAE,CAAC;QAC7B,KAAK,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC,CAAA;IAC1C,CAAC;IACD,uCAAuC;IACvC,MAAM,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,OAAO,EAAE,EAAE,gBAAgB,CAAC,CAAC,CAAA;IAC3D,KAAK,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,CAAA;IAC7C,OAAO,KAAK,CAAA;AACd,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,oBAAoB,GAAqC;IAC7D,QAAQ,EAAE,UAAU;IACpB,OAAO,EAAE,SAAS;IAClB,MAAM,EAAE,QAAQ;IAChB,GAAG,EAAE,KAAK;CACX,CAAA;AAED;;;;;GAKG;AACH,KAAK,UAAU,QAAQ,CAAC,IAAgB;IACtC,IAAI,OAAmC,CAAA;IACvC,IAAI,CAAC;QACH,OAAO,GAAG,MAAM,OAAO,CAAC,IAAI,CAAC,IAAI,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAA;IAC7D,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAA,CAAC,2BAA2B;IACvC,CAAC;IACD,MAAM,OAAO,GAAkB,EAAE,CAAA;IACjC,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;QAC5B,IAAI,CAAC,KAAK,CAAC,WAAW,EAAE;YAAE,SAAQ;QAClC,kFAAkF;QAClF,IAAI,CAAC,0BAA0B,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC;YAAE,SAAQ;QAC1D,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,CAAC,CAAA;QACvC,MAAM,eAAe,GAAG,IAAI,CAAC,GAAG,EAAE,kBAAkB,CAAC,CAAA;QACrD,IAAI,CAAC;YACH,MAAM,MAAM,CAAC,eAAe,CAAC,CAAA;QAC/B,CAAC;QAAC,MAAM,CAAC;YACP,SAAQ,CAAC,6BAA6B;QACxC,CAAC;QACD,gEAAgE;QAChE,uEAAuE;QACvE,sCAAsC;QACtC,IAAI,IAAI,GAAG,KAAK,CAAC,IAAI,CAAA;QACrB,IAAI,WAA+B,CAAA;QACnC,IAAI,IAAY,CAAA;QAChB,IAAI,CAAC;YACH,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,CAAC,GAAG,EAAE,YAAY,CAAC,EAAE,MAAM,CAAC,CAAA;QACxD,CAAC;QAAC,MAAM,CAAC;YACP,IAAI,GAAG,EAAE,CAAA;QACX,CAAC;QACD,IAAI,IAAI,KAAK,EAAE,EAAE,CAAC;YAChB,MAAM,SAAS,GAAG,IAAI,CAAC,KAAK,CAAC,iBAAiB,CAAC,CAAA;YAC/C,IAAI,SAAS;gBAAE,IAAI,GAAG,SAAS,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAA;YACzC,MAAM,SAAS,GAAG,IAAI,CAAC,KAAK,CAAC,wBAAwB,CAAC,CAAA;YACtD,IAAI,SAAS;gBAAE,WAAW,GAAG,SAAS,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAA;QAClD,CAAC;QACD,wEAAwE;QACxE,yCAAyC;QACzC,MAAM,YAAY,GAAG,IAAI,KAAK,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAA;QAC3D,OAAO,CAAC,IAAI,CAAC;YACX,EAAE,EAAE,KAAK,CAAC,IAAI;YACd,IAAI,EAAE,oBAAoB,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,IAAI;YAC9C,GAAG,CAAC,YAAY,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,YAAY,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YACvD,WAAW;YACX,KAAK,EAAE,IAAI,CAAC,KAAK;YACjB,SAAS,EAAE,KAAK,EAAE,kCAAkC;SACrD,CAAC,CAAA;IACJ,CAAC;IACD,OAAO,OAAO,CAAA;AAChB,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB;IACrC,MAAM,KAAK,GAAG,kBAAkB,IAAI,kBAAkB,EAAE,CAAA;IACxD,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAA;IAC9B,MAAM,MAAM,GAAkB,EAAE,CAAA;IAChC,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,KAAK,MAAM,MAAM,IAAI,MAAM,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;YAC1C,IAAI,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC;gBAAE,SAAQ;YACjC,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,CAAA;YACnB,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAA;QACrB,CAAC;IACH,CAAC;IACD,OAAO,MAAM,CAAA;AACf,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,cAAc,CAAC,KAAkB;IAC/C,IAAI,KAAK,CAAC,MAAM,CAAC,MAAM,IAAI,CAAC;QAAE,OAAO,SAAS,CAAA;IAC9C,OAAO,KAAK,CAAC,MAAM,CAAC,CAAC,KAAK,CAAC,KAAK,GAAG,CAAC,CAAC,GAAG,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAA;AAC9D,CAAC;AAED,mFAAmF;AACnF,MAAM,UAAU,aAAa,CAAC,KAAkB;IAC9C,OAAO,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,CAAA;AAClC,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,gBAAgB,CAAC,KAAkB,EAAE,IAAY;IAC/D,MAAM,KAAK,GAAG,IAAI,CAAC,WAAW,EAAE,CAAA;IAChC,OAAO,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAC3B,CAAC,CAAC,EAAE,CAAC,WAAW,EAAE,KAAK,KAAK;WACzB,CAAC,CAAC,IAAI,CAAC,WAAW,EAAE,KAAK,KAAK;WAC9B,CAAC,CAAC,CAAC,YAAY,KAAK,SAAS,IAAI,CAAC,CAAC,YAAY,CAAC,WAAW,EAAE,KAAK,KAAK,CAAC,CAAC,CAAA;AAChF,CAAC;AAED;;;sEAGsE;AACtE,MAAM,CAAC,MAAM,iBAAiB,GAAG,UAAU,CAAA;AAE3C;;;;;GAKG;AACH,MAAM,UAAU,kBAAkB,CAAC,MAA8B;IAC/D,MAAM,CAAC,GAAG,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,iBAAiB,CAAC,CAAA;IAC3D,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAA;AACtB,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,iBAAiB,CAAC,KAA8B;IAC9D,OAAO,KAAK,EAAE,IAAI,IAAI,EAAE,CAAA;AAC1B,CAAC"}
|
package/lib/session.d.ts
CHANGED
|
@@ -334,6 +334,37 @@ export declare class DshSessionBridge {
|
|
|
334
334
|
* point; switching away or exiting releases what we established.
|
|
335
335
|
*/
|
|
336
336
|
private ownWithLock;
|
|
337
|
+
/**
|
|
338
|
+
* Tear down the current live binding — the shared prelude of every session
|
|
339
|
+
* switch (resume, fork): stop the remote watch, dispose the handle, clear
|
|
340
|
+
* the subagent tracker and re-seed the model selection (a live /model or
|
|
341
|
+
* /think choice survives the switch; otherwise the composed default
|
|
342
|
+
* applies). The renderer keeps its live snapshot across the teardown (it
|
|
343
|
+
* must survive theme-switch rebuilds), so an empty `onLive` emit is
|
|
344
|
+
* required to drop the previous session's agents block before the next
|
|
345
|
+
* session's events rebuild it.
|
|
346
|
+
*/
|
|
347
|
+
private teardownLiveBinding;
|
|
348
|
+
/**
|
|
349
|
+
* Fork-and-switch: create a NEW session seeded with a balanced
|
|
350
|
+
* completed-turn prefix of the current session's log, then rebind this
|
|
351
|
+
* bridge to it — the resume lifecycle (teardown, tracker clear, rebind)
|
|
352
|
+
* over `agents.create` instead of `agents.resume`. The new session carries
|
|
353
|
+
* durable fork lineage (`parentSession` + `isSeeded` + the inherited
|
|
354
|
+
* prefix length) and composes under `presetId` (`meta.agentPreset`) — a
|
|
355
|
+
* seeded USER session, deliberately NOT a subagent child (no
|
|
356
|
+
* `origin`/delegationDepth, so /resume keeps it and the tracker ignores
|
|
357
|
+
* it). The seed must be sliced from the live session BEFORE calling this —
|
|
358
|
+
* the in-memory snapshot is the stable source. The teardown runs FIRST:
|
|
359
|
+
* `markTuiSurface` provides a cordis service on the agent scope and two
|
|
360
|
+
* live agents cannot coexist on it (the /new cycle only works because
|
|
361
|
+
* dispose reclaims the service before the next create). A failed create
|
|
362
|
+
* therefore leaves the old session DETACHED but fully resumable.
|
|
363
|
+
* @param seed contiguous-from-0 events ending at a `turn/end` (non-empty).
|
|
364
|
+
* @param parentSessionId the session the seed came from (durable lineage).
|
|
365
|
+
* @param presetId the preset id the new session composes under.
|
|
366
|
+
*/
|
|
367
|
+
forkCurrentSession(seed: readonly SessionEvent[], parentSessionId: SessionId, presetId: string | undefined): Promise<AgentHandle>;
|
|
337
368
|
/**
|
|
338
369
|
* Resume a persisted session: tear down the live agent (disposers are kept —
|
|
339
370
|
* the session-id filter re-binds to the resumed id) and load the persisted
|
|
@@ -397,6 +428,16 @@ export declare class DshSessionBridge {
|
|
|
397
428
|
/** The composed global default (`agent-default-model`) as a plain selection. */
|
|
398
429
|
private composedDefaultSelection;
|
|
399
430
|
/** Create the agent with the composed default model selection. */
|
|
431
|
+
/**
|
|
432
|
+
* Create the agent with the composed default model selection. `fork`
|
|
433
|
+
* seeds the new session with a balanced completed-turn prefix of another
|
|
434
|
+
* session's log (the preset-switch "Fork & switch" path): durable lineage
|
|
435
|
+
* (`parentSession` + `isSeeded` + the inherited prefix length) plus the
|
|
436
|
+
* fork's own `agentPreset` — recording the preset is what keeps the
|
|
437
|
+
* session's history reconstructable (a cold read that dropped it would
|
|
438
|
+
* rebuild turns under the wrong composition, the same rule
|
|
439
|
+
* `childSessionMeta` documents for subagent children).
|
|
440
|
+
*/
|
|
400
441
|
private createSession;
|
|
401
442
|
/** The model selection shown in the footer (live value after `/model`). */
|
|
402
443
|
getSelection(): ModelSelection | undefined;
|
package/lib/session.js
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
import { installModelSelection } from '@deepseek-ai/dsh-agent';
|
|
13
13
|
import { createUserMessage } from '@deepseek-ai/dsh-llm';
|
|
14
14
|
import { SettingsConflictError } from '@deepseek-ai/dsh-settings';
|
|
15
|
-
import { SessionId } from '@deepseek-ai/dsh-session';
|
|
15
|
+
import { SessionId, SessionLogOffset } from '@deepseek-ai/dsh-session';
|
|
16
16
|
import { readAppendSystem } from "./append-system.js";
|
|
17
17
|
import { join } from 'node:path';
|
|
18
18
|
import { isAgentEnd, isAgentStart, isDcpCompactionNotice, isLlmRetry, isSubagentDescriptor, isTuiPluginInjection } from "./dsh-events.js";
|
|
@@ -880,6 +880,60 @@ export class DshSessionBridge {
|
|
|
880
880
|
};
|
|
881
881
|
return handle;
|
|
882
882
|
}
|
|
883
|
+
/**
|
|
884
|
+
* Tear down the current live binding — the shared prelude of every session
|
|
885
|
+
* switch (resume, fork): stop the remote watch, dispose the handle, clear
|
|
886
|
+
* the subagent tracker and re-seed the model selection (a live /model or
|
|
887
|
+
* /think choice survives the switch; otherwise the composed default
|
|
888
|
+
* applies). The renderer keeps its live snapshot across the teardown (it
|
|
889
|
+
* must survive theme-switch rebuilds), so an empty `onLive` emit is
|
|
890
|
+
* required to drop the previous session's agents block before the next
|
|
891
|
+
* session's events rebuild it.
|
|
892
|
+
*/
|
|
893
|
+
async teardownLiveBinding() {
|
|
894
|
+
await this.stopRemoteWatch();
|
|
895
|
+
const handle = this.handle;
|
|
896
|
+
this.handle = undefined;
|
|
897
|
+
this.sessionId = undefined;
|
|
898
|
+
this.running = false;
|
|
899
|
+
this.agentViews.clear();
|
|
900
|
+
this.childSessions.clear();
|
|
901
|
+
this.trackedSessions.clear();
|
|
902
|
+
this.runSeqToChild.clear();
|
|
903
|
+
this.emitLive();
|
|
904
|
+
if (handle !== undefined)
|
|
905
|
+
await handle.dispose();
|
|
906
|
+
this.seedSelectionFromDefault();
|
|
907
|
+
}
|
|
908
|
+
/**
|
|
909
|
+
* Fork-and-switch: create a NEW session seeded with a balanced
|
|
910
|
+
* completed-turn prefix of the current session's log, then rebind this
|
|
911
|
+
* bridge to it — the resume lifecycle (teardown, tracker clear, rebind)
|
|
912
|
+
* over `agents.create` instead of `agents.resume`. The new session carries
|
|
913
|
+
* durable fork lineage (`parentSession` + `isSeeded` + the inherited
|
|
914
|
+
* prefix length) and composes under `presetId` (`meta.agentPreset`) — a
|
|
915
|
+
* seeded USER session, deliberately NOT a subagent child (no
|
|
916
|
+
* `origin`/delegationDepth, so /resume keeps it and the tracker ignores
|
|
917
|
+
* it). The seed must be sliced from the live session BEFORE calling this —
|
|
918
|
+
* the in-memory snapshot is the stable source. The teardown runs FIRST:
|
|
919
|
+
* `markTuiSurface` provides a cordis service on the agent scope and two
|
|
920
|
+
* live agents cannot coexist on it (the /new cycle only works because
|
|
921
|
+
* dispose reclaims the service before the next create). A failed create
|
|
922
|
+
* therefore leaves the old session DETACHED but fully resumable.
|
|
923
|
+
* @param seed contiguous-from-0 events ending at a `turn/end` (non-empty).
|
|
924
|
+
* @param parentSessionId the session the seed came from (durable lineage).
|
|
925
|
+
* @param presetId the preset id the new session composes under.
|
|
926
|
+
*/
|
|
927
|
+
async forkCurrentSession(seed, parentSessionId, presetId) {
|
|
928
|
+
// Teardown FIRST (see docblock): the old agent's scope holds the TUI
|
|
929
|
+
// surface service the new agent's setup must provide.
|
|
930
|
+
await this.teardownLiveBinding();
|
|
931
|
+
const handle = await this.createSession({ seed, parentSessionId, presetId });
|
|
932
|
+
this.handle = handle;
|
|
933
|
+
this.sessionId = handle.agent.session.id;
|
|
934
|
+
this.trackedSessions.add(String(this.sessionId));
|
|
935
|
+
return handle;
|
|
936
|
+
}
|
|
883
937
|
/**
|
|
884
938
|
* Resume a persisted session: tear down the live agent (disposers are kept —
|
|
885
939
|
* the session-id filter re-binds to the resumed id) and load the persisted
|
|
@@ -900,25 +954,7 @@ export class DshSessionBridge {
|
|
|
900
954
|
// touching this field, so it always names the id actually loading.
|
|
901
955
|
this.resumeTargetId = sessionId;
|
|
902
956
|
const task = (async () => {
|
|
903
|
-
await this.
|
|
904
|
-
const handle = this.handle;
|
|
905
|
-
this.handle = undefined;
|
|
906
|
-
this.sessionId = undefined;
|
|
907
|
-
this.running = false;
|
|
908
|
-
this.agentViews.clear();
|
|
909
|
-
this.childSessions.clear();
|
|
910
|
-
this.trackedSessions.clear();
|
|
911
|
-
this.runSeqToChild.clear();
|
|
912
|
-
// The renderer keeps its live snapshot across the resume teardown (it
|
|
913
|
-
// must survive theme-switch rebuilds), so an empty emit is required to
|
|
914
|
-
// drop the previous session's agents block before the resumed log's
|
|
915
|
-
// workflow events rebuild it.
|
|
916
|
-
this.emitLive();
|
|
917
|
-
if (handle !== undefined)
|
|
918
|
-
await handle.dispose();
|
|
919
|
-
// Same seeding rule as createSession: a live /model or /think choice
|
|
920
|
-
// survives the resume; otherwise the composed default applies.
|
|
921
|
-
this.seedSelectionFromDefault();
|
|
957
|
+
await this.teardownLiveBinding();
|
|
922
958
|
// Attach arm: the session may already be LIVE in this process — e.g.
|
|
923
959
|
// created by the feishu bot's /new or resumed by it. agents.resume
|
|
924
960
|
// refuses live sessions ("cannot prepare session while it is live"),
|
|
@@ -1478,21 +1514,42 @@ export class DshSessionBridge {
|
|
|
1478
1514
|
};
|
|
1479
1515
|
}
|
|
1480
1516
|
/** Create the agent with the composed default model selection. */
|
|
1481
|
-
|
|
1517
|
+
/**
|
|
1518
|
+
* Create the agent with the composed default model selection. `fork`
|
|
1519
|
+
* seeds the new session with a balanced completed-turn prefix of another
|
|
1520
|
+
* session's log (the preset-switch "Fork & switch" path): durable lineage
|
|
1521
|
+
* (`parentSession` + `isSeeded` + the inherited prefix length) plus the
|
|
1522
|
+
* fork's own `agentPreset` — recording the preset is what keeps the
|
|
1523
|
+
* session's history reconstructable (a cold read that dropped it would
|
|
1524
|
+
* rebuild turns under the wrong composition, the same rule
|
|
1525
|
+
* `childSessionMeta` documents for subagent children).
|
|
1526
|
+
*/
|
|
1527
|
+
async createSession(fork) {
|
|
1482
1528
|
this.seedSelectionFromDefault();
|
|
1483
1529
|
// Mint the id HERE so the pre-create lock covers exactly the directory
|
|
1484
1530
|
// agents.create is about to persist into (same cold-arm guard as resume).
|
|
1485
1531
|
const sessionId = crypto.randomUUID();
|
|
1486
1532
|
const cwd = process.cwd();
|
|
1487
1533
|
const lock = await this.acquireSessionLock(sessionId, cwd);
|
|
1534
|
+
const meta = {
|
|
1535
|
+
cwd,
|
|
1536
|
+
...this.agentPreset !== undefined ? { agentPreset: this.agentPreset } : {},
|
|
1537
|
+
};
|
|
1538
|
+
if (fork !== undefined) {
|
|
1539
|
+
if (fork.presetId !== undefined)
|
|
1540
|
+
meta.agentPreset = fork.presetId;
|
|
1541
|
+
meta.parentSession = fork.parentSessionId;
|
|
1542
|
+
meta.isSeeded = true;
|
|
1543
|
+
}
|
|
1488
1544
|
let handle;
|
|
1489
1545
|
try {
|
|
1490
1546
|
handle = await this.ctx.agents.create({
|
|
1491
1547
|
sessionId: SessionId(sessionId),
|
|
1492
|
-
meta
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1548
|
+
meta,
|
|
1549
|
+
...(fork === undefined ? {} : {
|
|
1550
|
+
seed: fork.seed,
|
|
1551
|
+
inheritedEventCount: SessionLogOffset(fork.seed.length),
|
|
1552
|
+
}),
|
|
1496
1553
|
agentOptions: this.selection ?? {},
|
|
1497
1554
|
// Install the mutable selection so `/model` can live-switch the route,
|
|
1498
1555
|
// the APPEND_SYSTEM.md section on this agent ONLY (never its
|