@agi-fans/dsh-tui 0.17.0 → 0.19.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/lib/CHANGELOG.md +67 -1
- package/lib/chrome/box.d.ts +2 -0
- package/lib/chrome/box.js +5 -1
- package/lib/chrome/box.js.map +1 -1
- package/lib/chrome/main-screen-renderer.d.ts +61 -11
- package/lib/chrome/main-screen-renderer.js +1674 -71
- package/lib/chrome/main-screen-renderer.js.map +1 -1
- package/lib/chrome/markdown.js +21 -13
- package/lib/chrome/markdown.js.map +1 -1
- package/lib/chrome/renderer.d.ts +75 -0
- package/lib/chrome/renderer.js.map +1 -1
- package/lib/chrome/status-config.d.ts +4 -1
- package/lib/chrome/status-config.js.map +1 -1
- package/lib/chrome/theme.d.ts +18 -0
- package/lib/chrome/theme.js +18 -0
- package/lib/chrome/theme.js.map +1 -1
- package/lib/chrome/tool-args.d.ts +24 -1
- package/lib/chrome/tool-args.js +40 -1
- package/lib/chrome/tool-args.js.map +1 -1
- package/lib/chrome/tool-renderers.d.ts +11 -0
- package/lib/chrome/tool-renderers.js +55 -7
- package/lib/chrome/tool-renderers.js.map +1 -1
- package/lib/chrome/welcome-tips.js +1 -1
- package/lib/chrome/welcome-tips.js.map +1 -1
- package/lib/commands/auth.js +4 -2
- package/lib/commands/auth.js.map +1 -1
- package/lib/commands/jobs.d.ts +2 -2
- package/lib/commands/jobs.js +2 -2
- package/lib/commands/jobs.js.map +1 -1
- package/lib/definition.d.ts +62 -7
- package/lib/definition.js.map +1 -1
- package/lib/index.d.ts +1 -1
- package/lib/index.js +1 -1
- package/lib/index.js.map +1 -1
- package/lib/input/keybindings-config.d.ts +1 -1
- package/lib/input/keybindings-config.js +2 -1
- package/lib/input/keybindings-config.js.map +1 -1
- package/lib/input/keys.d.ts +7 -0
- package/lib/input/keys.js +11 -4
- package/lib/input/keys.js.map +1 -1
- package/lib/runtime/provider-local.d.ts +63 -11
- package/lib/runtime/provider-local.js +506 -91
- package/lib/runtime/provider-local.js.map +1 -1
- package/lib/runtime/session-runtime.d.ts +9 -1
- package/lib/runtime/session-runtime.js +2 -2
- package/lib/runtime/session-runtime.js.map +1 -1
- package/lib/runtime/startup-notices.js +6 -9
- package/lib/runtime/startup-notices.js.map +1 -1
- package/lib/runtime/tool-presentation.js +44 -31
- package/lib/runtime/tool-presentation.js.map +1 -1
- package/lib/session/feature-toggles.d.ts +100 -0
- package/lib/session/feature-toggles.js +170 -0
- package/lib/session/feature-toggles.js.map +1 -0
- package/lib/session/fold-policy.d.ts +21 -0
- package/lib/session/fold-policy.js +15 -0
- package/lib/session/fold-policy.js.map +1 -0
- package/lib/session/job-notice.d.ts +6 -2
- package/lib/session/job-notice.js +44 -3
- package/lib/session/job-notice.js.map +1 -1
- package/lib/session/session-configuration.d.ts +0 -3
- package/lib/session/session-configuration.js +0 -4
- package/lib/session/session-configuration.js.map +1 -1
- package/lib/session/session-controller.d.ts +6 -4
- package/lib/session/session-controller.js +147 -55
- package/lib/session/session-controller.js.map +1 -1
- package/lib/session/session-library.d.ts +20 -0
- package/lib/session/session-library.js +52 -0
- package/lib/session/session-library.js.map +1 -1
- package/lib/session/subagent-roster.js +3 -3
- package/lib/session/subagent-roster.js.map +1 -1
- package/lib/session/subagent-tracker.d.ts +2 -9
- package/lib/session/subagent-tracker.js +3 -4
- package/lib/session/subagent-tracker.js.map +1 -1
- package/lib/session/tui-settings.d.ts +46 -18
- package/lib/session/tui-settings.js +59 -39
- package/lib/session/tui-settings.js.map +1 -1
- package/lib/views/at-complete.d.ts +2 -0
- package/lib/views/at-complete.js +2 -2
- package/lib/views/at-complete.js.map +1 -1
- package/lib/views/event-views.d.ts +26 -2
- package/lib/views/event-views.js +162 -48
- package/lib/views/event-views.js.map +1 -1
- package/lib/views/hotkeys.d.ts +2 -0
- package/lib/views/hotkeys.js +7 -3
- package/lib/views/hotkeys.js.map +1 -1
- package/lib/views/settings-list.d.ts +12 -6
- package/lib/views/settings-list.js +41 -18
- package/lib/views/settings-list.js.map +1 -1
- package/lib/views/transcript-export.js +0 -2
- package/lib/views/transcript-export.js.map +1 -1
- package/lib/views/transcript-render.d.ts +133 -18
- package/lib/views/transcript-render.js +1179 -112
- package/lib/views/transcript-render.js.map +1 -1
- package/lib/views/transcript-types.d.ts +79 -1
- package/lib/views/transcript-types.js +11 -0
- package/lib/views/transcript-types.js.map +1 -1
- package/lib/views/workspace-changes.d.ts +27 -0
- package/lib/views/workspace-changes.js +128 -0
- package/lib/views/workspace-changes.js.map +1 -0
- package/package.json +40 -38
|
@@ -7,34 +7,36 @@
|
|
|
7
7
|
* @module @agi-fans/dsh-tui/session-controller
|
|
8
8
|
*/
|
|
9
9
|
import { randomUUID } from 'node:crypto';
|
|
10
|
+
import { join } from 'node:path';
|
|
10
11
|
import { installModelSelection, } from '@deepseek-ai/dsh-agent';
|
|
11
12
|
import { createUserMessage, } from '@deepseek-ai/dsh-llm';
|
|
12
13
|
import { isUserInvocable } from '@deepseek-ai/dsh-skill';
|
|
14
|
+
import { SessionAlreadyOwnedError } from '@deepseek-ai/dsh-session-persistence';
|
|
13
15
|
import { readColdSessionLog } from '@deepseek-ai/dsh-session-query';
|
|
14
|
-
import { SessionId, SessionLogOffset } from '@deepseek-ai/dsh-session';
|
|
16
|
+
import { SESSION_FORMAT_VERSION, SessionId, SessionLogOffset } from '@deepseek-ai/dsh-session';
|
|
15
17
|
import { queueHostSubagentPrompt } from '@deepseek-ai/dsh-subagent/internal';
|
|
16
18
|
import { firstVisibleStreamTime } from "../views/stream-time.js";
|
|
17
19
|
import { LiveAttemptTracker } from "./live-attempt-tracker.js";
|
|
18
20
|
import { blocksText } from "./content-text.js";
|
|
19
21
|
import { jobNoticeFor } from "./job-notice.js";
|
|
20
22
|
import { descendantDepth, isSteerableSubagent } from "./subagent-roster.js";
|
|
23
|
+
import { readRecentRows, writeRecentRows } from "./session-library.js";
|
|
24
|
+
import { readJsonFile, writeJsonAtomic } from "./json-file.js";
|
|
21
25
|
import { SubagentTracker } from "./subagent-tracker.js";
|
|
22
26
|
import { commandPermission } from "../commands/permission.js";
|
|
23
|
-
import { isBlankSession,
|
|
27
|
+
import { isBlankSession, } from "./session-configuration.js";
|
|
24
28
|
import { stripComposerImageMarkers } from "../input/image-paste.js";
|
|
25
29
|
async function setupAgentContext(agentCtx, agent, selection) {
|
|
26
30
|
installModelSelection(agentCtx, selection);
|
|
27
31
|
const agentPresets = agentCtx.get('agentPresets');
|
|
28
32
|
const sessionProjections = agentCtx.get('sessionProjections');
|
|
29
|
-
|
|
30
|
-
if (agentPresets === undefined || sessionProjections === undefined || tools === undefined) {
|
|
33
|
+
if (agentPresets === undefined || sessionProjections === undefined) {
|
|
31
34
|
throw new Error('agent configuration services are unavailable');
|
|
32
35
|
}
|
|
33
36
|
const agentPreset = sessionProjections.stateOf(agent.session, 'agentPreset') ?? agentPresets.defaultId;
|
|
34
37
|
const mounted = await agentPresets.mount(agentCtx, agentPreset);
|
|
35
|
-
const disposeToolPresentation = tools.presentAs(toolPresentationForPreset(mounted.id));
|
|
36
38
|
await agentCtx.plugin(commandPermission(agent));
|
|
37
|
-
return { agentPreset: mounted.id
|
|
39
|
+
return { agentPreset: mounted.id };
|
|
38
40
|
}
|
|
39
41
|
function parseControl(line) {
|
|
40
42
|
const match = /^\/([a-z][a-z0-9_-]*(?::[a-z0-9][a-z0-9_-]*)?)(?:\s+(.*))?$/su.exec(line.trim());
|
|
@@ -393,14 +395,19 @@ export class SessionRuntime {
|
|
|
393
395
|
#tracker;
|
|
394
396
|
#streamAttempts = new LiveAttemptTracker();
|
|
395
397
|
#recentCache = new Map();
|
|
398
|
+
#recentRowsPath;
|
|
399
|
+
#upgradeMarkerPath;
|
|
400
|
+
#recentRowsLoaded = false;
|
|
396
401
|
#refreshInFlight = null;
|
|
397
402
|
#refreshDirty = false;
|
|
398
403
|
#inspectEpoch = 0;
|
|
399
404
|
#activationEpoch = 0;
|
|
400
405
|
#inspectedId;
|
|
401
|
-
constructor(ctx, tui) {
|
|
406
|
+
constructor(ctx, tui, options = {}) {
|
|
402
407
|
this.#ctx = ctx;
|
|
403
408
|
this.#tui = tui;
|
|
409
|
+
this.#recentRowsPath = options.stateDir === undefined ? undefined : join(options.stateDir, 'recent-sessions.json');
|
|
410
|
+
this.#upgradeMarkerPath = options.stateDir === undefined ? undefined : join(options.stateDir, 'sessions-upgraded.json');
|
|
404
411
|
this.#tracker = new SubagentTracker({
|
|
405
412
|
rootId: () => this.#active?.handle.agent.id,
|
|
406
413
|
depth: (session) => {
|
|
@@ -446,10 +453,23 @@ export class SessionRuntime {
|
|
|
446
453
|
}
|
|
447
454
|
const jobs = this.#ctx.get('jobs');
|
|
448
455
|
if (jobs !== undefined) {
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
456
|
+
// Settlements speak session ids, so the owning Agent is resolved here;
|
|
457
|
+
// a job whose owner is gone has no notice to show.
|
|
458
|
+
this.#off.push(jobs.events.subscribe({ owners: 'scope' }, (event) => {
|
|
459
|
+
if (event.type !== 'settled')
|
|
460
|
+
return;
|
|
461
|
+
const owner = event.job.owner === undefined ? undefined : this.#ctx.get('agents')?.get(event.job.owner);
|
|
462
|
+
const notice = jobNoticeFor(event.job, owner, this.#active?.handle.agent);
|
|
463
|
+
if (notice !== undefined) {
|
|
464
|
+
const job = event.job;
|
|
465
|
+
tui.notice(notice, {
|
|
466
|
+
process: true, processSource: `job:${job.id}`,
|
|
467
|
+
...(/^(bash|pwsh)$/u.test(job.kind) && (job.status === 'completed' || job.status === 'failed' || job.status === 'killed')
|
|
468
|
+
? { job: { id: job.id, label: job.label, status: job.status, startedAt: job.startedAt, ...(job.detail === undefined ? {} : { detail: job.detail }) } }
|
|
469
|
+
: {}),
|
|
470
|
+
...(job.status === 'failed' || job.status === 'killed' ? { level: 'error' } : {}),
|
|
471
|
+
});
|
|
472
|
+
}
|
|
453
473
|
}));
|
|
454
474
|
}
|
|
455
475
|
this.#off.push(ctx.on('agent/status', (payload) => {
|
|
@@ -482,6 +502,8 @@ export class SessionRuntime {
|
|
|
482
502
|
if (this.#inspectedId === undefined) {
|
|
483
503
|
tui.event(event, ctx.get('tuiToolPresentation')?.event(active.handle.agent, event));
|
|
484
504
|
}
|
|
505
|
+
if (event.type === 'workspace/changes')
|
|
506
|
+
this.#serveWorkspaceSummary(ctx, tui, session, event);
|
|
485
507
|
if (event.type === 'subagent/catalog')
|
|
486
508
|
this.#tracker.observeCatalog(session, [event]);
|
|
487
509
|
if (shouldRefreshSessionInfoAfter(event))
|
|
@@ -535,6 +557,13 @@ export class SessionRuntime {
|
|
|
535
557
|
this.#pushSessionInfo();
|
|
536
558
|
}));
|
|
537
559
|
}
|
|
560
|
+
// One-time per session format, off the first render: stored logs an earlier
|
|
561
|
+
// release wrote get their current-format generation here rather than on the
|
|
562
|
+
// next listing or search that has to decode them.
|
|
563
|
+
ctx.inject(['sessionPersistence'], (persistenceCtx) => {
|
|
564
|
+
void this.#upgradeStoredSessions(persistenceCtx.sessionPersistence)
|
|
565
|
+
.catch((error) => { persistenceCtx.logger.warn(error); });
|
|
566
|
+
});
|
|
538
567
|
}
|
|
539
568
|
get agent() {
|
|
540
569
|
return this.#active?.handle.agent;
|
|
@@ -560,7 +589,7 @@ export class SessionRuntime {
|
|
|
560
589
|
if (defaults === undefined)
|
|
561
590
|
throw new Error('agent default model is unavailable');
|
|
562
591
|
await this.refreshRecent();
|
|
563
|
-
await this.#activate(await this.#create(defaults));
|
|
592
|
+
await this.#activate(await this.#create(defaults), 'initial');
|
|
564
593
|
}
|
|
565
594
|
/** Submit one human composer value; active turns retain it as a later follow-up. */
|
|
566
595
|
async send(input, agent = this.#requiredAgent()) {
|
|
@@ -621,9 +650,9 @@ export class SessionRuntime {
|
|
|
621
650
|
this.#tui.restoreInput({ text: line, images });
|
|
622
651
|
if (result.text !== undefined) {
|
|
623
652
|
if (result.kind === 'error')
|
|
624
|
-
this.#tui.notice(result.text, { level: 'error' });
|
|
653
|
+
this.#tui.notice(result.text, { level: 'error', focus: true });
|
|
625
654
|
else
|
|
626
|
-
this.#tui.commandOutput(parsed.name, result.text);
|
|
655
|
+
this.#tui.commandOutput(parsed.name, result.text, { focus: true });
|
|
627
656
|
}
|
|
628
657
|
return true;
|
|
629
658
|
}
|
|
@@ -662,7 +691,7 @@ export class SessionRuntime {
|
|
|
662
691
|
/** Start a new top-level session with the current model selection. */
|
|
663
692
|
async newSession(agent) {
|
|
664
693
|
this.assertActive(agent);
|
|
665
|
-
await this.#activate(await this.#create(this.selection(agent)));
|
|
694
|
+
await this.#activate(await this.#create(this.selection(agent)), 'new');
|
|
666
695
|
await this.refreshRecent();
|
|
667
696
|
}
|
|
668
697
|
/** Replace the active top-level session with one durable session. */
|
|
@@ -688,7 +717,6 @@ export class SessionRuntime {
|
|
|
688
717
|
contextWindow: undefined,
|
|
689
718
|
reasoningEffort: undefined,
|
|
690
719
|
agentPreset: configuration.agentPreset,
|
|
691
|
-
disposeToolPresentation: configuration.disposeToolPresentation,
|
|
692
720
|
});
|
|
693
721
|
await this.refreshRecent();
|
|
694
722
|
}
|
|
@@ -817,7 +845,6 @@ export class SessionRuntime {
|
|
|
817
845
|
contextWindow: undefined,
|
|
818
846
|
reasoningEffort: undefined,
|
|
819
847
|
agentPreset: configuration.agentPreset,
|
|
820
|
-
disposeToolPresentation: configuration.disposeToolPresentation,
|
|
821
848
|
});
|
|
822
849
|
this.#tui.restoreInput({ text, images });
|
|
823
850
|
this.#tui.notice(`Rewound to before turn ${selected.turn}. The original session remains available in /resume.`);
|
|
@@ -864,15 +891,7 @@ export class SessionRuntime {
|
|
|
864
891
|
const active = this.#requiredActive();
|
|
865
892
|
if (active.agentPreset === id)
|
|
866
893
|
return active.agentPreset;
|
|
867
|
-
const previousPreset = active.agentPreset;
|
|
868
894
|
const preset = await this.#ctx.agentPresets.recompose(agent.ctx, id);
|
|
869
|
-
try {
|
|
870
|
-
this.#replaceToolPresentation(active, toolPresentationForPreset(preset.id));
|
|
871
|
-
}
|
|
872
|
-
catch (error) {
|
|
873
|
-
await this.#ctx.agentPresets.recompose(agent.ctx, previousPreset);
|
|
874
|
-
throw error;
|
|
875
|
-
}
|
|
876
895
|
active.agentPreset = preset.id;
|
|
877
896
|
agent.session.append('agent-preset/selected', { agentPreset: preset.id });
|
|
878
897
|
this.#pushTools();
|
|
@@ -892,6 +911,78 @@ export class SessionRuntime {
|
|
|
892
911
|
this.#pushSessionInfo();
|
|
893
912
|
return outcome;
|
|
894
913
|
}
|
|
914
|
+
/**
|
|
915
|
+
* Resolve and attach the changed-file summary a `workspace/changes` event
|
|
916
|
+
* announces.
|
|
917
|
+
*
|
|
918
|
+
* The Harness appends the event and only then files the summary under its
|
|
919
|
+
* sequence, so a listener running inside that append always reads
|
|
920
|
+
* `undefined`. The lookup is therefore deferred by one microtask, which runs
|
|
921
|
+
* after the recorder's own synchronous store, and enriches the turn-only
|
|
922
|
+
* block the transcript already rendered. A session without the plugin, or a
|
|
923
|
+
* recorder that has already been disposed, simply keeps the fallback.
|
|
924
|
+
*
|
|
925
|
+
* @param ctx - host context carrying the optional service.
|
|
926
|
+
* @param tui - the mounted presentation service.
|
|
927
|
+
* @param session - the Session that appended the event.
|
|
928
|
+
* @param event - the `workspace/changes` event.
|
|
929
|
+
*/
|
|
930
|
+
#serveWorkspaceSummary(ctx, tui, session, event) {
|
|
931
|
+
const service = ctx.get('workspaceChanges');
|
|
932
|
+
if (service === undefined)
|
|
933
|
+
return;
|
|
934
|
+
const { id } = session;
|
|
935
|
+
const { seq, data } = event;
|
|
936
|
+
queueMicrotask(() => {
|
|
937
|
+
const summary = service.summary(id, seq);
|
|
938
|
+
if (summary !== undefined)
|
|
939
|
+
tui.setWorkspaceSummary(data.turn, summary);
|
|
940
|
+
});
|
|
941
|
+
}
|
|
942
|
+
/**
|
|
943
|
+
* Publish a current-format generation for every stored session an earlier
|
|
944
|
+
* release wrote. A read open never upgrades a log, so without this pass
|
|
945
|
+
* every listing and search keeps decoding the historical corpus — the pass
|
|
946
|
+
* runs once per session format, in the background, and skips a session
|
|
947
|
+
* another live process owns.
|
|
948
|
+
*/
|
|
949
|
+
async #upgradeStoredSessions(persistence) {
|
|
950
|
+
const markerPath = this.#upgradeMarkerPath;
|
|
951
|
+
if (markerPath === undefined)
|
|
952
|
+
return;
|
|
953
|
+
const marker = readJsonFile(markerPath);
|
|
954
|
+
if (typeof marker?.sessionFormat === 'number' && marker.sessionFormat >= SESSION_FORMAT_VERSION)
|
|
955
|
+
return;
|
|
956
|
+
const before = await persistence.list();
|
|
957
|
+
const revisions = new Map(before.map(snapshot => [String(snapshot.header.id), snapshot.revision]));
|
|
958
|
+
const active = this.#active?.handle.agent.id;
|
|
959
|
+
for (const snapshot of before) {
|
|
960
|
+
if (this.#disposed)
|
|
961
|
+
return;
|
|
962
|
+
if (String(snapshot.header.id) === active)
|
|
963
|
+
continue;
|
|
964
|
+
try {
|
|
965
|
+
const handle = await persistence.open(snapshot.header.id, 'write');
|
|
966
|
+
await handle.close();
|
|
967
|
+
}
|
|
968
|
+
catch (error) {
|
|
969
|
+
// Another process owns it, or the log is unreadable: leave it alone.
|
|
970
|
+
if (!(error instanceof SessionAlreadyOwnedError))
|
|
971
|
+
this.#ctx.logger.debug(error);
|
|
972
|
+
}
|
|
973
|
+
}
|
|
974
|
+
if (this.#disposed)
|
|
975
|
+
return;
|
|
976
|
+
let upgraded = 0;
|
|
977
|
+
for (const snapshot of await persistence.list()) {
|
|
978
|
+
if (revisions.get(String(snapshot.header.id)) !== snapshot.revision)
|
|
979
|
+
upgraded += 1;
|
|
980
|
+
}
|
|
981
|
+
writeJsonAtomic(markerPath, { sessionFormat: SESSION_FORMAT_VERSION });
|
|
982
|
+
if (upgraded > 0) {
|
|
983
|
+
this.#tui.notice(`Upgraded ${upgraded} stored session${upgraded === 1 ? '' : 's'} to the current format.`);
|
|
984
|
+
}
|
|
985
|
+
}
|
|
895
986
|
async refreshRecent() {
|
|
896
987
|
// Coalesce concurrent callers: the recent list is one snapshot, and a
|
|
897
988
|
// burst of title/status events must not queue a read of every stored log.
|
|
@@ -921,24 +1012,40 @@ export class SessionRuntime {
|
|
|
921
1012
|
}
|
|
922
1013
|
const snapshots = (await persistence.list()).filter(snapshot => snapshot.header.origin !== 'subagent')
|
|
923
1014
|
.sort((left, right) => right.header.createdAt - left.header.createdAt);
|
|
1015
|
+
// A stored row stays valid while its revision holds, so a cold process
|
|
1016
|
+
// renders labels for unchanged sessions without reading their logs; only a
|
|
1017
|
+
// new or changed session pays a read.
|
|
1018
|
+
if (!this.#recentRowsLoaded && this.#recentRowsPath !== undefined) {
|
|
1019
|
+
this.#recentRowsLoaded = true;
|
|
1020
|
+
for (const [id, memo] of readRecentRows(this.#recentRowsPath))
|
|
1021
|
+
this.#recentCache.set(id, memo);
|
|
1022
|
+
}
|
|
924
1023
|
const rows = [];
|
|
1024
|
+
let memoChanged = false;
|
|
925
1025
|
const liveIds = new Set(snapshots.map(snapshot => snapshot.header.id));
|
|
926
|
-
for (const id of this.#recentCache.keys())
|
|
927
|
-
if (
|
|
928
|
-
|
|
1026
|
+
for (const id of this.#recentCache.keys()) {
|
|
1027
|
+
if (liveIds.has(id))
|
|
1028
|
+
continue;
|
|
1029
|
+
this.#recentCache.delete(id);
|
|
1030
|
+
memoChanged = true;
|
|
1031
|
+
}
|
|
929
1032
|
for (const snapshot of snapshots) {
|
|
930
1033
|
const header = snapshot.header;
|
|
931
1034
|
const cached = this.#recentCache.get(header.id);
|
|
932
1035
|
if (cached?.revision === snapshot.revision) {
|
|
933
|
-
|
|
1036
|
+
if (cached.row !== null)
|
|
1037
|
+
rows.push(cached.row);
|
|
934
1038
|
continue;
|
|
935
1039
|
}
|
|
936
1040
|
try {
|
|
937
1041
|
const log = await readColdSessionLog(persistence, header.id);
|
|
938
1042
|
const status = recentSessionStatus(log.events);
|
|
939
1043
|
const content = recentSessionContent(log.events);
|
|
940
|
-
if (content === undefined)
|
|
1044
|
+
if (content === undefined) {
|
|
1045
|
+
this.#recentCache.set(header.id, { revision: snapshot.revision, row: null });
|
|
1046
|
+
memoChanged = true;
|
|
941
1047
|
continue;
|
|
1048
|
+
}
|
|
942
1049
|
const row = {
|
|
943
1050
|
id: header.id,
|
|
944
1051
|
...content,
|
|
@@ -948,12 +1055,15 @@ export class SessionRuntime {
|
|
|
948
1055
|
...(status === undefined ? {} : { status }),
|
|
949
1056
|
};
|
|
950
1057
|
this.#recentCache.set(header.id, { revision: snapshot.revision, row });
|
|
1058
|
+
memoChanged = true;
|
|
951
1059
|
rows.push(row);
|
|
952
1060
|
}
|
|
953
1061
|
catch {
|
|
954
1062
|
rows.push({ id: header.id, title: '(unavailable session)', createdAt: header.createdAt });
|
|
955
1063
|
}
|
|
956
1064
|
}
|
|
1065
|
+
if (memoChanged && this.#recentRowsPath !== undefined)
|
|
1066
|
+
writeRecentRows(this.#recentRowsPath, this.#recentCache);
|
|
957
1067
|
this.#recent = rows;
|
|
958
1068
|
this.#pushSessionInfo();
|
|
959
1069
|
}
|
|
@@ -1002,7 +1112,6 @@ export class SessionRuntime {
|
|
|
1002
1112
|
contextWindow: undefined,
|
|
1003
1113
|
reasoningEffort: undefined,
|
|
1004
1114
|
agentPreset: configuration.agentPreset,
|
|
1005
|
-
disposeToolPresentation: configuration.disposeToolPresentation,
|
|
1006
1115
|
};
|
|
1007
1116
|
}
|
|
1008
1117
|
/**
|
|
@@ -1035,7 +1144,7 @@ export class SessionRuntime {
|
|
|
1035
1144
|
return undefined;
|
|
1036
1145
|
}
|
|
1037
1146
|
}
|
|
1038
|
-
async #activate(next) {
|
|
1147
|
+
async #activate(next, reason = 'open') {
|
|
1039
1148
|
// Reject a late create before it publishes UI on a disposed tree.
|
|
1040
1149
|
if (this.#disposed) {
|
|
1041
1150
|
this.#releaseHandle(next.handle);
|
|
@@ -1046,7 +1155,7 @@ export class SessionRuntime {
|
|
|
1046
1155
|
this.#active = next;
|
|
1047
1156
|
void this.#claimWorkspace(next);
|
|
1048
1157
|
try {
|
|
1049
|
-
this.#presentAgent(next);
|
|
1158
|
+
this.#presentAgent(next, reason);
|
|
1050
1159
|
const selected = this.selection(next.handle.agent);
|
|
1051
1160
|
const info = await this.#resolveModelInfo(selected);
|
|
1052
1161
|
if (this.#disposed || this.#activationEpoch !== epoch) {
|
|
@@ -1107,7 +1216,7 @@ export class SessionRuntime {
|
|
|
1107
1216
|
this.#releaseHandle(previous.handle);
|
|
1108
1217
|
}
|
|
1109
1218
|
/** Show one active session on the terminal: transcript, tools, controls, model. */
|
|
1110
|
-
#presentAgent(active) {
|
|
1219
|
+
#presentAgent(active, reason = 'open') {
|
|
1111
1220
|
const agent = active.handle.agent;
|
|
1112
1221
|
this.#inspectedId = undefined;
|
|
1113
1222
|
this.#tui.setInspectedSubagent(undefined);
|
|
@@ -1116,7 +1225,7 @@ export class SessionRuntime {
|
|
|
1116
1225
|
// Seed welcome metadata before replaceSession commits the startup header to
|
|
1117
1226
|
// native scrollback; later updates cannot rewrite that frozen first frame.
|
|
1118
1227
|
this.#pushSessionInfo();
|
|
1119
|
-
this.#replaceTranscript(agent);
|
|
1228
|
+
this.#replaceTranscript(agent, reason);
|
|
1120
1229
|
this.#pushTools();
|
|
1121
1230
|
this.#pushCommands();
|
|
1122
1231
|
const selected = this.selection(agent);
|
|
@@ -1238,27 +1347,10 @@ export class SessionRuntime {
|
|
|
1238
1347
|
controls: this.#sessionControls(active, projection),
|
|
1239
1348
|
});
|
|
1240
1349
|
}
|
|
1241
|
-
#
|
|
1242
|
-
const previousMode = toolPresentationForPreset(active.agentPreset);
|
|
1243
|
-
active.disposeToolPresentation();
|
|
1244
|
-
try {
|
|
1245
|
-
const tools = active.handle.agent.ctx.get('tools');
|
|
1246
|
-
if (tools === undefined)
|
|
1247
|
-
throw new Error('tool registry is unavailable');
|
|
1248
|
-
active.disposeToolPresentation = tools.presentAs(mode);
|
|
1249
|
-
}
|
|
1250
|
-
catch (error) {
|
|
1251
|
-
const tools = active.handle.agent.ctx.get('tools');
|
|
1252
|
-
if (tools === undefined)
|
|
1253
|
-
throw error;
|
|
1254
|
-
active.disposeToolPresentation = tools.presentAs(previousMode);
|
|
1255
|
-
throw error;
|
|
1256
|
-
}
|
|
1257
|
-
}
|
|
1258
|
-
#replaceTranscript(agent) {
|
|
1350
|
+
#replaceTranscript(agent, reason = 'refresh') {
|
|
1259
1351
|
const events = agent.session.snapshotEvents();
|
|
1260
1352
|
this.#tracker.observeCatalog(agent.session, events);
|
|
1261
|
-
this.#tui.replaceSession(events, this.#ctx.get('tuiToolPresentation')?.session(agent, events), agent.status);
|
|
1353
|
+
this.#tui.replaceSession(events, this.#ctx.get('tuiToolPresentation')?.session(agent, events), agent.status, reason);
|
|
1262
1354
|
this.#replayLivePrefix(agent.session.id);
|
|
1263
1355
|
}
|
|
1264
1356
|
#replaceVisibleTranscript() {
|
|
@@ -1313,7 +1405,7 @@ export class SessionRuntime {
|
|
|
1313
1405
|
return;
|
|
1314
1406
|
this.#inspectedId = id;
|
|
1315
1407
|
const child = this.#ctx.get('agents')?.get(SessionId(id));
|
|
1316
|
-
this.#tui.replaceSession(events, child === undefined ? undefined : this.#ctx.get('tuiToolPresentation')?.session(child, events), child?.status ?? 'idle');
|
|
1408
|
+
this.#tui.replaceSession(events, child === undefined ? undefined : this.#ctx.get('tuiToolPresentation')?.session(child, events), child?.status ?? 'idle', 'refresh');
|
|
1317
1409
|
this.#replayLivePrefix(id);
|
|
1318
1410
|
this.#tui.setInspectedSubagent(this.#inspectView(id, child?.status === 'running' ? 'running' : 'waiting'));
|
|
1319
1411
|
this.#tui.setStatus(child?.status ?? 'idle');
|