@gajae-code/coding-agent 0.12.1 → 0.12.4
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/CHANGELOG.md +39 -0
- package/dist/types/config/settings-schema.d.ts +2 -2
- package/dist/types/gjc-runtime/ralplan-review-conflicts.d.ts +114 -0
- package/dist/types/gjc-runtime/ralplan-runtime.d.ts +5 -3
- package/dist/types/gjc-runtime/ultragoal-evidence.d.ts +5 -1
- package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +8 -2
- package/dist/types/modes/components/irc-sidebar.d.ts +16 -0
- package/dist/types/modes/controllers/command-controller.d.ts +1 -1
- package/dist/types/modes/controllers/event-controller.d.ts +1 -1
- package/dist/types/modes/controllers/extension-ui-controller.d.ts +2 -1
- package/dist/types/modes/controllers/selector-controller.d.ts +1 -1
- package/dist/types/modes/interactive-mode.d.ts +9 -0
- package/dist/types/modes/theme/theme.d.ts +3 -1
- package/dist/types/modes/types.d.ts +32 -0
- package/dist/types/sdk/bus/chat-daemon-control.d.ts +2 -1
- package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +3 -2
- package/dist/types/session/agent-session.d.ts +2 -0
- package/dist/types/session/internal/managed-session-storage.d.ts +22 -3
- package/dist/types/task/index.d.ts +4 -0
- package/dist/types/task/output-manager.d.ts +1 -0
- package/dist/types/task/receipt.d.ts +2 -0
- package/dist/types/tools/index.d.ts +2 -0
- package/package.json +8 -8
- package/scripts/dogfood-ralplan-review-conflicts.ts +272 -0
- package/scripts/generate-sdk-operation-inventory.ts +2 -0
- package/scripts/run-acp-conformance.ts +5 -0
- package/src/async/job-manager.ts +22 -20
- package/src/config/provider-ranking.ts +1 -0
- package/src/config/settings-schema.ts +2 -2
- package/src/debug/index.ts +42 -17
- package/src/defaults/gjc/skills/ralplan/SKILL.md +9 -7
- package/src/defaults/gjc/skills/ultragoal/SKILL.md +22 -5
- package/src/defaults/gjc/skills/ultragoal/validation-batch-contracts.md +2 -1
- package/src/gjc-runtime/launch-worktree.ts +103 -1
- package/src/gjc-runtime/ledger-event-renderer.ts +1 -0
- package/src/gjc-runtime/ralplan-review-conflicts.ts +472 -0
- package/src/gjc-runtime/ralplan-runtime.ts +80 -10
- package/src/gjc-runtime/ultragoal-evidence.ts +110 -49
- package/src/gjc-runtime/ultragoal-runtime.ts +467 -22
- package/src/gjc-runtime/workflow-manifest.generated.json +29 -0
- package/src/gjc-runtime/workflow-manifest.ts +17 -2
- package/src/internal-urls/docs-index.generated.ts +2 -2
- package/src/modes/DESIGN.md +53 -0
- package/src/modes/components/irc-sidebar.ts +30 -1
- package/src/modes/components/tool-status-header.ts +0 -1
- package/src/modes/controllers/command-controller.ts +38 -34
- package/src/modes/controllers/event-controller.ts +27 -16
- package/src/modes/controllers/extension-ui-controller.ts +186 -34
- package/src/modes/controllers/selector-controller.ts +115 -22
- package/src/modes/interactive-mode.ts +189 -62
- package/src/modes/jobs-observer.ts +1 -1
- package/src/modes/theme/theme.ts +11 -9
- package/src/modes/types.ts +88 -0
- package/src/sdk/broker/lifecycle.ts +18 -13
- package/src/sdk/bus/chat-daemon-control.ts +4 -3
- package/src/sdk/bus/telegram-daemon-contract.ts +3 -2
- package/src/sdk/protocol/operation-inventory.generated.json +11 -0
- package/src/sdk/session.ts +1 -0
- package/src/session/agent-session.ts +22 -1
- package/src/session/internal/managed-session-scope.ts +158 -107
- package/src/session/internal/managed-session-storage.ts +445 -57
- package/src/session/session-storage.ts +29 -44
- package/src/task/index.ts +257 -31
- package/src/task/output-manager.ts +24 -14
- package/src/task/receipt.ts +11 -1
- package/src/tools/index.ts +2 -0
package/src/modes/DESIGN.md
CHANGED
|
@@ -291,6 +291,59 @@ textual state without SGR. Korean, Japanese, Chinese, and mixed CJK/Latin prose
|
|
|
291
291
|
must wrap at semantic phrase boundaries, never through an action/status label or
|
|
292
292
|
short identifier; a semantic CJK break is a visual-QA failure.
|
|
293
293
|
|
|
294
|
+
### Direct-root anatomy, IRC lane, and pin boundary
|
|
295
|
+
|
|
296
|
+
The production root has one ordered, direct-child anatomy. Do not wrap, reorder,
|
|
297
|
+
or independently pin these regions:
|
|
298
|
+
|
|
299
|
+
1. `ircSplitView` is the viewport anchor and owns the transcript (and the IRC
|
|
300
|
+
sidebar when effective);
|
|
301
|
+
2. `pendingMessagesContainer`, `statusContainer`, `todoContainer`, and
|
|
302
|
+
`btwContainer` follow the anchor **before** the pin boundary;
|
|
303
|
+
3. `statusLine` is the pin boundary; and
|
|
304
|
+
4. `hookWidgetContainerAbove`, `editorContainer`, `petFloorContainer`, and
|
|
305
|
+
`hookWidgetContainerBelow` are the later direct composer children in that
|
|
306
|
+
order.
|
|
307
|
+
|
|
308
|
+
`statusContainer` is transient operation/status content in the pre-boundary
|
|
309
|
+
application flow; it is not the persistent `statusLine` telemetry rail. The
|
|
310
|
+
status line begins the fixed suffix. The focused editor, its cursor, and every
|
|
311
|
+
later direct composer child remain below that boundary during manual history,
|
|
312
|
+
streaming, and reflow. Pending messages, todo, and BTW therefore remain
|
|
313
|
+
transcript-adjacent rather than becoming accidental fixed chrome.
|
|
314
|
+
|
|
315
|
+
IRC has one shared work-lane geometry. At **64 cells or narrower** the IRC lane
|
|
316
|
+
is ineffective and transcript/todo use the full width. At **65 cells** it is
|
|
317
|
+
exactly **32 / 3 / 30** cells (left work lane / separator / IRC lane). At wider
|
|
318
|
+
sizes the same split calculation applies; todo uses the left work lane whenever
|
|
319
|
+
IRC is effective, including empty, streaming, and long IRC histories. Todo
|
|
320
|
+
does not create a second sidebar, a separately rounded width, or a different
|
|
321
|
+
collapse rule.
|
|
322
|
+
|
|
323
|
+
Todo is absent when empty. Populated todos support long text, multiple phases,
|
|
324
|
+
and collapsed/expanded views without crossing the separator or overflowing
|
|
325
|
+
their work lane. The active phase is retained in collapsed view; expanded view
|
|
326
|
+
keeps phase/task order. IRC empty, streaming, and long states preserve the
|
|
327
|
+
same root order and pin boundary.
|
|
328
|
+
|
|
329
|
+
Row reservation is content-priority based: reserve the focused composer and
|
|
330
|
+
status line first, then normal hooks; when height is constrained, drop the
|
|
331
|
+
manual-output notice first, then the decorative pet, then low-priority hooks.
|
|
332
|
+
A zero-row transcript capacity is valid. Neither reservation nor degradation
|
|
333
|
+
may hide focus/cursor geometry, move an anchor into pinned chrome, or turn a
|
|
334
|
+
resize into a follow/manual state change. Manual and follow paths, streaming
|
|
335
|
+
updates, width growth/shrink, and height growth/shrink preserve the anchor
|
|
336
|
+
semantics; resize must recompute the shared IRC/todo lane before rendering.
|
|
337
|
+
|
|
338
|
+
All lane measurement, clipping, and wrapping is ANSI-aware terminal-cell
|
|
339
|
+
measurement. Terminal graphics may be shown only where the effective layout
|
|
340
|
+
permits them and must degrade to the textual graphics fallback without changing
|
|
341
|
+
row ownership. ANSI/color output preserves visible cursor/focus state; ASCII
|
|
342
|
+
and no-color output retains textual selection/status affordances. CJK and mixed
|
|
343
|
+
CJK/Latin todo, IRC, status, and BTW text break at semantic phrase boundaries,
|
|
344
|
+
not inside a phase/action/status label, short identifier, or grapheme cluster.
|
|
345
|
+
Those defects, width overflow, overlap, hidden focus/cursor, and lost anchors
|
|
346
|
+
are blocking visual-QA failures.
|
|
294
347
|
### Sticky viewport deterministic visual QA
|
|
295
348
|
|
|
296
349
|
`test/fixtures/tui/sticky-viewport-showcase.ts` is a fixed-clock, no-network,
|
|
@@ -82,6 +82,35 @@ export function computeIrcSplitWidths(width: number): {
|
|
|
82
82
|
rightWidth: sidebarWidth,
|
|
83
83
|
};
|
|
84
84
|
}
|
|
85
|
+
/**
|
|
86
|
+
* Resolves the single shared work-lane geometry. The left lane is used by both
|
|
87
|
+
* transcript and todo content whenever the IRC lane is effective.
|
|
88
|
+
*/
|
|
89
|
+
export function computeIrcWorkLaneWidths(
|
|
90
|
+
width: number,
|
|
91
|
+
sidebarVisible: boolean,
|
|
92
|
+
): { leftWidth: number; separatorWidth: number; rightWidth: number } {
|
|
93
|
+
const fullWidth = Math.max(0, Math.floor(width));
|
|
94
|
+
return sidebarVisible
|
|
95
|
+
? computeIrcSplitWidths(fullWidth)
|
|
96
|
+
: { leftWidth: fullWidth, separatorWidth: 0, rightWidth: 0 };
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export class IrcLeftLaneComponent implements Component {
|
|
100
|
+
constructor(
|
|
101
|
+
private readonly content: Component,
|
|
102
|
+
private readonly isSidebarVisible: (width: number) => boolean,
|
|
103
|
+
) {}
|
|
104
|
+
|
|
105
|
+
render(width: number): string[] {
|
|
106
|
+
const layout = computeIrcWorkLaneWidths(width, this.isSidebarVisible(width));
|
|
107
|
+
return this.content.render(layout.leftWidth);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
invalidate(): void {
|
|
111
|
+
this.content.invalidate?.();
|
|
112
|
+
}
|
|
113
|
+
}
|
|
85
114
|
|
|
86
115
|
type SemanticLine = Readonly<{
|
|
87
116
|
kind: "header" | "body" | "elision" | "blank";
|
|
@@ -288,7 +317,7 @@ export class IrcSplitViewComponent implements ViewportAnchorProvider {
|
|
|
288
317
|
renderWithViewportAnchors(width: number): ViewportAnchorRender {
|
|
289
318
|
if (!this.#visible) return renderComponentWithViewportAnchors(this.leftPane, width);
|
|
290
319
|
const componentTheme = typeof this.componentTheme === "function" ? this.componentTheme() : this.componentTheme;
|
|
291
|
-
const { leftWidth, separatorWidth, rightWidth } =
|
|
320
|
+
const { leftWidth, separatorWidth, rightWidth } = computeIrcWorkLaneWidths(width, this.#visible);
|
|
292
321
|
if (rightWidth === 0) return renderComponentWithViewportAnchors(this.leftPane, width);
|
|
293
322
|
const separator = separatorWidth > 0 ? componentTheme.fg("dim", ` ${componentTheme.boxSharp.vertical} `) : "";
|
|
294
323
|
const leftRender = withTerminalGraphicsFallback(
|
|
@@ -206,7 +206,6 @@ export class StatusLineComponent implements Component {
|
|
|
206
206
|
separator: settings.get("statusLine.separator"),
|
|
207
207
|
showHookStatus: settings.get("statusLine.showHookStatus"),
|
|
208
208
|
showSkillHud: settings.get("statusLine.showSkillHud"),
|
|
209
|
-
showActionHints: settings.get("statusLine.showActionHints"),
|
|
210
209
|
segmentOptions: settings.getGroup("statusLine").segmentOptions,
|
|
211
210
|
sessionAccent: settings.get("statusLine.sessionAccent"),
|
|
212
211
|
maxRows: settings.get("statusLine.maxRows"),
|
|
@@ -34,7 +34,12 @@ import { BorderedLoader } from "../../modes/components/bordered-loader";
|
|
|
34
34
|
import { DynamicBorder } from "../../modes/components/dynamic-border";
|
|
35
35
|
import { EvalExecutionComponent } from "../../modes/components/eval-execution";
|
|
36
36
|
import { getMarkdownTheme, getSymbolTheme, theme } from "../../modes/theme/theme";
|
|
37
|
-
import
|
|
37
|
+
import {
|
|
38
|
+
clearInteractiveActivityLoaders,
|
|
39
|
+
type InteractiveModeContext,
|
|
40
|
+
stopInteractiveActivityIndicator,
|
|
41
|
+
suspendInteractiveActivityIndicator,
|
|
42
|
+
} from "../../modes/types";
|
|
38
43
|
import { computeContextBreakdown, renderContextUsage } from "../../modes/utils/context-usage";
|
|
39
44
|
import { buildHotkeysMarkdown, formatHotkeyMarkdownCode } from "../../modes/utils/hotkeys-markdown";
|
|
40
45
|
import { buildToolsMarkdown } from "../../modes/utils/tools-markdown";
|
|
@@ -965,12 +970,10 @@ export class CommandController {
|
|
|
965
970
|
|
|
966
971
|
async #runNewSessionFlow(options?: NewSessionOptions, label: string = "New session started"): Promise<boolean> {
|
|
967
972
|
if (!(await this.ctx.session.newSession(options))) return false;
|
|
973
|
+
if (this.ctx.isStopped?.()) return false;
|
|
974
|
+
clearInteractiveActivityLoaders(this.ctx);
|
|
968
975
|
|
|
969
|
-
|
|
970
|
-
this.ctx.loadingAnimation.stop();
|
|
971
|
-
this.ctx.loadingAnimation = undefined;
|
|
972
|
-
}
|
|
973
|
-
this.ctx.statusContainer.clear();
|
|
976
|
+
stopInteractiveActivityIndicator(this.ctx);
|
|
974
977
|
this.ctx.resetIrcSidebarSession();
|
|
975
978
|
this.ctx.resetObserverRegistry();
|
|
976
979
|
setSessionTerminalTitle(this.ctx.sessionManager.getSessionName(), this.ctx.sessionManager.getCwd());
|
|
@@ -991,6 +994,7 @@ export class CommandController {
|
|
|
991
994
|
|
|
992
995
|
this.ctx.chatContainer.addChild(new Text(`${theme.fg("accent", `${theme.status.success} ${label}`)}`, 1, 0));
|
|
993
996
|
await this.ctx.reloadTodos();
|
|
997
|
+
if (this.ctx.isStopped?.()) return false;
|
|
994
998
|
this.ctx.ui.requestRender();
|
|
995
999
|
return true;
|
|
996
1000
|
}
|
|
@@ -1000,19 +1004,18 @@ export class CommandController {
|
|
|
1000
1004
|
}
|
|
1001
1005
|
|
|
1002
1006
|
async handleContextClearCommand(): Promise<void> {
|
|
1003
|
-
if (this.ctx.
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
}
|
|
1007
|
-
this.ctx.statusContainer.clear();
|
|
1008
|
-
|
|
1007
|
+
if (this.ctx.isStopped?.()) return;
|
|
1008
|
+
clearInteractiveActivityLoaders(this.ctx);
|
|
1009
|
+
stopInteractiveActivityIndicator(this.ctx);
|
|
1009
1010
|
if (this.ctx.session.isCompacting) {
|
|
1010
1011
|
this.ctx.session.abortCompaction();
|
|
1011
1012
|
while (this.ctx.session.isCompacting) {
|
|
1012
1013
|
await Bun.sleep(10);
|
|
1013
1014
|
}
|
|
1014
1015
|
}
|
|
1016
|
+
if (this.ctx.isStopped?.()) return;
|
|
1015
1017
|
if (!(await this.ctx.session.clearContext())) return;
|
|
1018
|
+
if (this.ctx.isStopped?.()) return;
|
|
1016
1019
|
setSessionTerminalTitle(this.ctx.sessionManager.getSessionName(), this.ctx.sessionManager.getCwd());
|
|
1017
1020
|
|
|
1018
1021
|
this.ctx.statusLine.invalidate();
|
|
@@ -1033,6 +1036,7 @@ export class CommandController {
|
|
|
1033
1036
|
new Text(`${theme.fg("accent", `${theme.status.success} Context cleared`)}`, 1, 0),
|
|
1034
1037
|
);
|
|
1035
1038
|
await this.ctx.reloadTodos();
|
|
1039
|
+
if (this.ctx.isStopped?.()) return;
|
|
1036
1040
|
this.ctx.ui.requestRender();
|
|
1037
1041
|
}
|
|
1038
1042
|
|
|
@@ -1049,17 +1053,15 @@ export class CommandController {
|
|
|
1049
1053
|
this.ctx.showWarning("Wait for the current response to finish or abort it before forking.");
|
|
1050
1054
|
return;
|
|
1051
1055
|
}
|
|
1052
|
-
if (this.ctx.loadingAnimation) {
|
|
1053
|
-
this.ctx.loadingAnimation.stop();
|
|
1054
|
-
this.ctx.loadingAnimation = undefined;
|
|
1055
|
-
}
|
|
1056
|
-
this.ctx.statusContainer.clear();
|
|
1057
1056
|
|
|
1058
1057
|
const success = await this.ctx.session.fork();
|
|
1058
|
+
if (this.ctx.isStopped?.()) return;
|
|
1059
1059
|
if (!success) {
|
|
1060
1060
|
this.ctx.showError("Fork failed (session not persisted or cancelled)");
|
|
1061
1061
|
return;
|
|
1062
1062
|
}
|
|
1063
|
+
clearInteractiveActivityLoaders(this.ctx);
|
|
1064
|
+
stopInteractiveActivityIndicator(this.ctx);
|
|
1063
1065
|
this.ctx.resetIrcSidebarSession();
|
|
1064
1066
|
|
|
1065
1067
|
this.ctx.statusLine.invalidate();
|
|
@@ -1256,11 +1258,7 @@ export class CommandController {
|
|
|
1256
1258
|
customInstructionsOrOptions?: string | CompactOptions,
|
|
1257
1259
|
isAuto = false,
|
|
1258
1260
|
): Promise<CompactionOutcome> {
|
|
1259
|
-
|
|
1260
|
-
this.ctx.loadingAnimation.stop();
|
|
1261
|
-
this.ctx.loadingAnimation = undefined;
|
|
1262
|
-
}
|
|
1263
|
-
this.ctx.statusContainer.clear();
|
|
1261
|
+
const releaseActivityIndicator = suspendInteractiveActivityIndicator(this.ctx);
|
|
1264
1262
|
|
|
1265
1263
|
const originalOnEscape = this.ctx.editor.onEscape;
|
|
1266
1264
|
this.ctx.editor.onEscape = () => {
|
|
@@ -1287,24 +1285,28 @@ export class CommandController {
|
|
|
1287
1285
|
? customInstructionsOrOptions
|
|
1288
1286
|
: undefined;
|
|
1289
1287
|
await this.ctx.session.compact(instructions, options);
|
|
1288
|
+
if (this.ctx.isStopped?.()) return outcome;
|
|
1290
1289
|
|
|
1291
1290
|
this.ctx.rebuildChatFromMessages("reconcile-same-transcript");
|
|
1292
1291
|
|
|
1293
1292
|
this.ctx.statusLine.invalidate();
|
|
1294
1293
|
this.ctx.updateEditorTopBorder();
|
|
1295
1294
|
} catch (error) {
|
|
1296
|
-
|
|
1297
|
-
|
|
1295
|
+
outcome = error instanceof CompactionCancelledError ? "cancelled" : "failed";
|
|
1296
|
+
if (this.ctx.isStopped?.()) return outcome;
|
|
1297
|
+
if (outcome === "cancelled") {
|
|
1298
1298
|
this.ctx.showError("Compaction cancelled");
|
|
1299
1299
|
} else {
|
|
1300
|
-
outcome = "failed";
|
|
1301
1300
|
const message = error instanceof Error ? error.message : String(error);
|
|
1302
1301
|
this.ctx.showError(`Compaction failed: ${message}`);
|
|
1303
1302
|
}
|
|
1304
1303
|
} finally {
|
|
1305
1304
|
compactingLoader.stop();
|
|
1306
|
-
this.ctx.
|
|
1307
|
-
|
|
1305
|
+
if (!this.ctx.isStopped?.()) {
|
|
1306
|
+
this.ctx.statusContainer.clear();
|
|
1307
|
+
this.ctx.editor.onEscape = originalOnEscape;
|
|
1308
|
+
}
|
|
1309
|
+
releaseActivityIndicator();
|
|
1308
1310
|
}
|
|
1309
1311
|
await this.ctx.flushCompactionQueue({ willRetry: false });
|
|
1310
1312
|
return outcome;
|
|
@@ -1323,11 +1325,7 @@ export class CommandController {
|
|
|
1323
1325
|
return;
|
|
1324
1326
|
}
|
|
1325
1327
|
|
|
1326
|
-
|
|
1327
|
-
this.ctx.loadingAnimation.stop();
|
|
1328
|
-
this.ctx.loadingAnimation = undefined;
|
|
1329
|
-
}
|
|
1330
|
-
this.ctx.statusContainer.clear();
|
|
1328
|
+
const releaseActivityIndicator = suspendInteractiveActivityIndicator(this.ctx);
|
|
1331
1329
|
|
|
1332
1330
|
const originalOnEscape = this.ctx.editor.onEscape;
|
|
1333
1331
|
this.ctx.editor.onEscape = () => {
|
|
@@ -1347,6 +1345,7 @@ export class CommandController {
|
|
|
1347
1345
|
try {
|
|
1348
1346
|
// Handoff generation runs as a oneshot request; the new session is shown after it completes.
|
|
1349
1347
|
const result = await this.ctx.session.handoff(customInstructions);
|
|
1348
|
+
if (this.ctx.isStopped?.()) return;
|
|
1350
1349
|
|
|
1351
1350
|
if (!result) {
|
|
1352
1351
|
this.ctx.showError("Handoff cancelled");
|
|
@@ -1361,6 +1360,7 @@ export class CommandController {
|
|
|
1361
1360
|
this.ctx.updateEditorTopBorder();
|
|
1362
1361
|
this.ctx.updateEditorBorderColor();
|
|
1363
1362
|
await this.ctx.reloadTodos();
|
|
1363
|
+
if (this.ctx.isStopped?.()) return;
|
|
1364
1364
|
|
|
1365
1365
|
this.ctx.chatContainer.addChild(new Spacer(1));
|
|
1366
1366
|
this.ctx.chatContainer.addChild(
|
|
@@ -1370,6 +1370,7 @@ export class CommandController {
|
|
|
1370
1370
|
this.ctx.showStatus(`Handoff document saved to: ${result.savedPath}`);
|
|
1371
1371
|
}
|
|
1372
1372
|
} catch (error) {
|
|
1373
|
+
if (this.ctx.isStopped?.()) return;
|
|
1373
1374
|
const message = error instanceof Error ? error.message : String(error);
|
|
1374
1375
|
if (message === "Handoff cancelled" || (error instanceof Error && error.name === "AbortError")) {
|
|
1375
1376
|
this.ctx.showError("Handoff cancelled");
|
|
@@ -1389,8 +1390,11 @@ export class CommandController {
|
|
|
1389
1390
|
}
|
|
1390
1391
|
} finally {
|
|
1391
1392
|
handoffLoader.stop();
|
|
1392
|
-
this.ctx.
|
|
1393
|
-
|
|
1393
|
+
if (!this.ctx.isStopped?.()) {
|
|
1394
|
+
this.ctx.statusContainer.clear();
|
|
1395
|
+
this.ctx.editor.onEscape = originalOnEscape;
|
|
1396
|
+
}
|
|
1397
|
+
releaseActivityIndicator();
|
|
1394
1398
|
}
|
|
1395
1399
|
}
|
|
1396
1400
|
|
|
@@ -15,7 +15,12 @@ import { TodoReminderComponent } from "../../modes/components/todo-reminder";
|
|
|
15
15
|
import { ToolExecutionComponent, type ToolExecutionHandle } from "../../modes/components/tool-execution";
|
|
16
16
|
import { TtsrNotificationComponent } from "../../modes/components/ttsr-notification";
|
|
17
17
|
import { getSymbolTheme, theme } from "../../modes/theme/theme";
|
|
18
|
-
import
|
|
18
|
+
import {
|
|
19
|
+
type InteractiveModeContext,
|
|
20
|
+
stopInteractiveActivityIndicator,
|
|
21
|
+
syncInteractiveActivityIndicator,
|
|
22
|
+
type TodoPhase,
|
|
23
|
+
} from "../../modes/types";
|
|
19
24
|
import type { PlanApprovalDetails } from "../../plan-mode/approved-plan";
|
|
20
25
|
import { completionNotifyDisabledByEnv } from "../../sdk/bus/config";
|
|
21
26
|
import { summaryFromMessage } from "../../sdk/bus/helpers";
|
|
@@ -166,6 +171,14 @@ export class EventController {
|
|
|
166
171
|
this.ctx.retryEscapeHandler = undefined;
|
|
167
172
|
}
|
|
168
173
|
this.ctx.retryEscapePrimed = false;
|
|
174
|
+
if (this.ctx.autoCompactionEscapeHandler) {
|
|
175
|
+
this.ctx.editor.onEscape = this.ctx.autoCompactionEscapeHandler;
|
|
176
|
+
this.ctx.autoCompactionEscapeHandler = undefined;
|
|
177
|
+
}
|
|
178
|
+
if (this.ctx.autoCompactionLoader) {
|
|
179
|
+
this.ctx.autoCompactionLoader.stop();
|
|
180
|
+
this.ctx.autoCompactionLoader = undefined;
|
|
181
|
+
}
|
|
169
182
|
if (this.ctx.retryLoader) {
|
|
170
183
|
this.ctx.retryLoader.stop();
|
|
171
184
|
this.ctx.retryLoader = undefined;
|
|
@@ -266,7 +279,9 @@ export class EventController {
|
|
|
266
279
|
}
|
|
267
280
|
|
|
268
281
|
async #dispatchEvent(event: AgentSessionEvent): Promise<void> {
|
|
282
|
+
if (this.ctx.isStopped?.()) return;
|
|
269
283
|
if (!this.ctx.isInitialized) await this.ctx.init();
|
|
284
|
+
if (this.ctx.isStopped?.()) return;
|
|
270
285
|
this.#visibleTranscriptChanged = false;
|
|
271
286
|
this.#handlingEvent = true;
|
|
272
287
|
try {
|
|
@@ -274,6 +289,7 @@ export class EventController {
|
|
|
274
289
|
this.ctx.updateEditorTopBorder();
|
|
275
290
|
const run = this.#handlers[event.type] as (e: AgentSessionEvent) => Promise<void>;
|
|
276
291
|
await run(event);
|
|
292
|
+
if (this.ctx.isStopped?.()) return;
|
|
277
293
|
if (this.#visibleTranscriptChanged) this.ctx.recordVisibleTranscriptMutation?.();
|
|
278
294
|
if (this.ctx.isTranscriptViewerOpen?.()) this.ctx.refreshTranscriptViewer?.();
|
|
279
295
|
} finally {
|
|
@@ -878,23 +894,22 @@ export class EventController {
|
|
|
878
894
|
const planDetails = details.sourceResultDetails as PlanApprovalDetails | undefined;
|
|
879
895
|
if (planDetails) {
|
|
880
896
|
await this.ctx.planModeController.handleApproval(planDetails);
|
|
897
|
+
if (this.ctx.isStopped?.()) return;
|
|
881
898
|
}
|
|
882
899
|
}
|
|
883
900
|
}
|
|
884
901
|
}
|
|
885
902
|
|
|
886
903
|
async #handleAgentEnd(_event: Extract<AgentSessionEvent, { type: "agent_end" }>): Promise<void> {
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
this.ctx.loadingAnimation = undefined;
|
|
890
|
-
this.ctx.statusContainer.clear();
|
|
891
|
-
}
|
|
904
|
+
this.ctx.setWorkingMessage(undefined);
|
|
905
|
+
stopInteractiveActivityIndicator(this.ctx);
|
|
892
906
|
if (this.ctx.streamingComponent) {
|
|
893
907
|
this.ctx.chatContainer.removeChild(this.ctx.streamingComponent);
|
|
894
908
|
this.ctx.streamingComponent = undefined;
|
|
895
909
|
this.ctx.streamingMessage = undefined;
|
|
896
910
|
}
|
|
897
911
|
await this.ctx.planModeController.flushPendingModelSwitch();
|
|
912
|
+
if (this.ctx.isStopped?.()) return;
|
|
898
913
|
for (const toolCallId of Array.from(this.ctx.pendingTools.keys())) {
|
|
899
914
|
if (!this.#backgroundToolCallIds.has(toolCallId)) {
|
|
900
915
|
this.ctx.pendingTools.delete(toolCallId);
|
|
@@ -922,11 +937,7 @@ export class EventController {
|
|
|
922
937
|
this.ctx.editor.onEscape = () => {
|
|
923
938
|
this.ctx.session.abortCompaction();
|
|
924
939
|
};
|
|
925
|
-
|
|
926
|
-
this.ctx.loadingAnimation.stop();
|
|
927
|
-
this.ctx.loadingAnimation = undefined;
|
|
928
|
-
}
|
|
929
|
-
this.ctx.statusContainer.clear();
|
|
940
|
+
stopInteractiveActivityIndicator(this.ctx, { restoreBackground: false });
|
|
930
941
|
const reasonText =
|
|
931
942
|
event.reason === "overflow" ? "Context overflow detected, " : event.reason === "idle" ? "Idle " : "";
|
|
932
943
|
const actionLabel = event.action === "handoff" ? "Auto-handoff" : "Auto context-full maintenance";
|
|
@@ -982,6 +993,7 @@ export class EventController {
|
|
|
982
993
|
this.ctx.statusLine.invalidate();
|
|
983
994
|
this.ctx.updateEditorTopBorder();
|
|
984
995
|
await this.ctx.reloadTodos();
|
|
996
|
+
if (this.ctx.isStopped?.()) return;
|
|
985
997
|
this.ctx.showStatus("Auto-handoff completed");
|
|
986
998
|
} else if (event.skipped) {
|
|
987
999
|
// Benign skip: no model selected, no candidate models available, or nothing
|
|
@@ -995,6 +1007,8 @@ export class EventController {
|
|
|
995
1007
|
this.ctx.showWarning("Auto context-full maintenance failed; continuing without maintenance");
|
|
996
1008
|
}
|
|
997
1009
|
await this.ctx.flushCompactionQueue({ willRetry: event.willRetry });
|
|
1010
|
+
if (this.ctx.isStopped?.()) return;
|
|
1011
|
+
syncInteractiveActivityIndicator(this.ctx);
|
|
998
1012
|
this.ctx.ui.requestRender();
|
|
999
1013
|
}
|
|
1000
1014
|
|
|
@@ -1024,11 +1038,7 @@ export class EventController {
|
|
|
1024
1038
|
this.ctx.session.abortRetry();
|
|
1025
1039
|
}
|
|
1026
1040
|
};
|
|
1027
|
-
|
|
1028
|
-
this.ctx.loadingAnimation.stop();
|
|
1029
|
-
this.ctx.loadingAnimation = undefined;
|
|
1030
|
-
}
|
|
1031
|
-
this.ctx.statusContainer.clear();
|
|
1041
|
+
stopInteractiveActivityIndicator(this.ctx, { restoreBackground: false });
|
|
1032
1042
|
// Stop any prior retry loader/timer before installing a new one.
|
|
1033
1043
|
this.ctx.retryLoader?.stop();
|
|
1034
1044
|
this.#clearRetryCountdown();
|
|
@@ -1071,6 +1081,7 @@ export class EventController {
|
|
|
1071
1081
|
if (!event.success) {
|
|
1072
1082
|
this.ctx.showError(`Retry failed after ${event.attempt} attempts: ${event.finalError || "Unknown error"}`);
|
|
1073
1083
|
}
|
|
1084
|
+
syncInteractiveActivityIndicator(this.ctx);
|
|
1074
1085
|
this.ctx.ui.requestRender();
|
|
1075
1086
|
}
|
|
1076
1087
|
|