@gajae-code/coding-agent 0.9.5 → 0.10.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/CHANGELOG.md +54 -0
- package/dist/types/cli/classify-install-target.d.ts +0 -10
- package/dist/types/cli/daemon-cli.d.ts +2 -0
- package/dist/types/cli/initial-message.d.ts +1 -1
- package/dist/types/cli/malloc-env-guard.d.ts +65 -0
- package/dist/types/cli/malloc-env-guard.test.d.ts +1 -0
- package/dist/types/cli/notify-cli.d.ts +3 -1
- package/dist/types/cli/session-picker.d.ts +4 -3
- package/dist/types/cli/update-cli.d.ts +22 -3
- package/dist/types/cli.d.ts +4 -0
- package/dist/types/commands/daemon.d.ts +6 -2
- package/dist/types/commands/launch.d.ts +1 -0
- package/dist/types/commands/notify.d.ts +6 -0
- package/dist/types/commit/analysis/conventional.d.ts +1 -1
- package/dist/types/commit/analysis/summary.d.ts +1 -1
- package/dist/types/commit/changelog/generate.d.ts +1 -1
- package/dist/types/commit/changelog/index.d.ts +2 -2
- package/dist/types/commit/map-reduce/map-phase.d.ts +1 -1
- package/dist/types/commit/map-reduce/reduce-phase.d.ts +1 -1
- package/dist/types/config/keybindings.d.ts +5 -0
- package/dist/types/config/model-profile-activation.d.ts +1 -0
- package/dist/types/config/settings-schema.d.ts +47 -7
- package/dist/types/coordinator-mcp/model-preset.d.ts +45 -0
- package/dist/types/coordinator-mcp/server.d.ts +67 -6
- package/dist/types/daemon/control-types.d.ts +10 -0
- package/dist/types/daemon/operator-contract.d.ts +47 -0
- package/dist/types/defaults/gjc/extensions/grok-cli-vendor/src/models/catalog.d.ts +4 -0
- package/dist/types/discovery/helpers.d.ts +1 -1
- package/dist/types/discovery/substitute-plugin-root.d.ts +0 -4
- package/dist/types/gjc-runtime/launch-tmux.d.ts +26 -7
- package/dist/types/gjc-runtime/session-state-sidecar.d.ts +34 -3
- package/dist/types/gjc-runtime/tmux-common.d.ts +6 -0
- package/dist/types/gjc-runtime/tmux-owner-isolation-cli.d.ts +5 -0
- package/dist/types/gjc-runtime/tmux-owner-isolation.d.ts +309 -0
- package/dist/types/gjc-runtime/tmux-sessions.d.ts +49 -17
- package/dist/types/internal-urls/local-protocol.d.ts +13 -9
- package/dist/types/main.d.ts +66 -2
- package/dist/types/modes/acp/acp-agent.d.ts +19 -2
- package/dist/types/modes/acp/acp-mode.d.ts +12 -0
- package/dist/types/modes/components/assistant-message.d.ts +1 -1
- package/dist/types/modes/components/custom-editor.d.ts +2 -0
- package/dist/types/modes/components/gajae-pet-widget.d.ts +59 -0
- package/dist/types/modes/components/hook-selector.d.ts +5 -0
- package/dist/types/modes/components/irc-sidebar.d.ts +32 -0
- package/dist/types/modes/components/pet-selector.d.ts +11 -0
- package/dist/types/modes/components/session-selector.d.ts +22 -9
- package/dist/types/modes/components/settings-selector.d.ts +2 -0
- package/dist/types/modes/components/tool-execution.d.ts +1 -0
- package/dist/types/modes/components/user-message.d.ts +2 -1
- package/dist/types/modes/controllers/event-controller.d.ts +4 -0
- package/dist/types/modes/controllers/selector-controller.d.ts +1 -0
- package/dist/types/modes/interactive-mode.d.ts +24 -2
- package/dist/types/modes/irc-observation-ledger.d.ts +22 -0
- package/dist/types/modes/shared/agent-wire/deep-interview-gate.d.ts +8 -0
- package/dist/types/modes/shared/agent-wire/unattended-run-controller.d.ts +5 -0
- package/dist/types/modes/shared/agent-wire/unattended-session.d.ts +45 -0
- package/dist/types/modes/shared/agent-wire/workflow-gate-broker.d.ts +42 -2
- package/dist/types/modes/theme/defaults/index.d.ts +568 -568
- package/dist/types/modes/types.d.ts +28 -2
- package/dist/types/modes/utils/abort-message.d.ts +1 -1
- package/dist/types/modes/utils/irc-message.d.ts +38 -0
- package/dist/types/modes/utils/ui-helpers.d.ts +13 -4
- package/dist/types/notifications/config.d.ts +34 -0
- package/dist/types/notifications/daemon-paths.d.ts +7 -0
- package/dist/types/notifications/index.d.ts +27 -0
- package/dist/types/notifications/lifecycle-control-runtime.d.ts +12 -4
- package/dist/types/notifications/notification-service.d.ts +144 -0
- package/dist/types/notifications/rate-limit-pool.d.ts +27 -9
- package/dist/types/notifications/telegram-daemon-control.d.ts +6 -0
- package/dist/types/notifications/telegram-daemon.d.ts +130 -3
- package/dist/types/notifications/telegram-reference.d.ts +19 -2
- package/dist/types/notifications/topic-registry.d.ts +24 -4
- package/dist/types/registry/agent-registry.d.ts +2 -0
- package/dist/types/rlm/index.d.ts +1 -1
- package/dist/types/sdk.d.ts +2 -0
- package/dist/types/secrets/obfuscator.d.ts +1 -1
- package/dist/types/session/agent-session.d.ts +26 -2
- package/dist/types/session/cache-economics.d.ts +79 -0
- package/dist/types/session/provider-safety-stop.d.ts +5 -0
- package/dist/types/session/session-manager.d.ts +124 -4
- package/dist/types/session/session-storage.d.ts +155 -13
- package/dist/types/setup/credential-auto-import.d.ts +2 -2
- package/dist/types/task/executor.d.ts +3 -0
- package/dist/types/task/receipt.d.ts +3 -1
- package/dist/types/task/types.d.ts +8 -0
- package/dist/types/tools/ask-answer-registry.d.ts +5 -0
- package/dist/types/tools/ask.d.ts +25 -1
- package/dist/types/tools/bash.d.ts +1 -1
- package/dist/types/tools/browser/tab-supervisor.d.ts +6 -0
- package/dist/types/tools/index.d.ts +58 -6
- package/dist/types/tools/telegram-send.d.ts +7 -0
- package/dist/types/utils/external-editor.d.ts +2 -0
- package/package.json +9 -9
- package/scripts/compile-args.ts +8 -2
- package/scripts/g004-tmux-smoke.ts +196 -36
- package/src/cli/daemon-cli.ts +19 -29
- package/src/cli/malloc-env-guard.test.ts +104 -0
- package/src/cli/malloc-env-guard.ts +149 -0
- package/src/cli/notify-cli.ts +78 -14
- package/src/cli/session-picker.ts +23 -40
- package/src/cli/update-cli.ts +56 -37
- package/src/cli.ts +50 -10
- package/src/commands/daemon.ts +19 -5
- package/src/commands/harness.ts +515 -47
- package/src/commands/launch.ts +11 -3
- package/src/commands/notify.ts +5 -1
- package/src/commands/session.ts +6 -1
- package/src/config/keybindings.ts +5 -0
- package/src/config/model-profile-activation.ts +1 -1
- package/src/config/model-profiles.ts +23 -23
- package/src/config/settings-schema.ts +41 -1
- package/src/coordinator-mcp/model-preset.ts +100 -0
- package/src/coordinator-mcp/server.ts +2653 -425
- package/src/daemon/control-types.ts +11 -0
- package/src/daemon/operator-contract.ts +121 -0
- package/src/deep-interview/render-middleware.ts +67 -2
- package/src/defaults/gjc/extensions/grok-cli-vendor/src/models/catalog.ts +57 -17
- package/src/defaults/gjc/extensions/grok-cli-vendor/src/payload/sanitize.ts +15 -2
- package/src/defaults/gjc/extensions/grok-cli-vendor/src/provider/register.ts +5 -1
- package/src/gjc-runtime/launch-tmux.ts +702 -135
- package/src/gjc-runtime/session-state-sidecar.ts +756 -135
- package/src/gjc-runtime/team-runtime.ts +6 -0
- package/src/gjc-runtime/tmux-common.ts +26 -7
- package/src/gjc-runtime/tmux-gc.ts +52 -9
- package/src/gjc-runtime/tmux-owner-isolation-cli.ts +185 -0
- package/src/gjc-runtime/tmux-owner-isolation.ts +2060 -0
- package/src/gjc-runtime/tmux-sessions.ts +871 -68
- package/src/internal-urls/docs-index.generated.ts +9 -8
- package/src/internal-urls/local-protocol.ts +29 -10
- package/src/main.ts +298 -75
- package/src/modes/acp/acp-agent.ts +728 -98
- package/src/modes/acp/acp-mode.ts +29 -2
- package/src/modes/components/assistant-message.ts +32 -3
- package/src/modes/components/bash-execution.ts +43 -13
- package/src/modes/components/custom-editor.ts +5 -0
- package/src/modes/components/gajae-pet-widget.ts +360 -0
- package/src/modes/components/hook-selector.ts +12 -0
- package/src/modes/components/irc-sidebar.ts +206 -0
- package/src/modes/components/pet-selector.ts +55 -0
- package/src/modes/components/session-selector.ts +222 -221
- package/src/modes/components/settings-selector.ts +10 -0
- package/src/modes/components/tool-execution.ts +47 -6
- package/src/modes/components/user-message.ts +26 -9
- package/src/modes/controllers/command-controller.ts +28 -2
- package/src/modes/controllers/event-controller.ts +159 -36
- package/src/modes/controllers/extension-ui-controller.ts +36 -17
- package/src/modes/controllers/input-controller.ts +8 -2
- package/src/modes/controllers/selector-controller.ts +62 -15
- package/src/modes/interactive-mode.ts +183 -7
- package/src/modes/irc-observation-ledger.ts +127 -0
- package/src/modes/shared/agent-wire/deep-interview-gate.ts +45 -2
- package/src/modes/shared/agent-wire/unattended-session.ts +288 -1
- package/src/modes/shared/agent-wire/workflow-gate-broker.ts +130 -20
- package/src/modes/types.ts +34 -2
- package/src/modes/utils/hotkeys-markdown.ts +1 -0
- package/src/modes/utils/irc-message.ts +138 -0
- package/src/modes/utils/ui-helpers.ts +216 -50
- package/src/notifications/config.ts +44 -0
- package/src/notifications/daemon-paths.ts +8 -0
- package/src/notifications/index.ts +535 -75
- package/src/notifications/lifecycle-control-runtime.ts +930 -77
- package/src/notifications/notification-service.ts +710 -0
- package/src/notifications/rate-limit-pool.ts +46 -12
- package/src/notifications/telegram-daemon-cli.ts +2 -0
- package/src/notifications/telegram-daemon-control.ts +27 -2
- package/src/notifications/telegram-daemon.ts +635 -94
- package/src/notifications/telegram-reference.ts +73 -13
- package/src/notifications/topic-registry.ts +86 -9
- package/src/prompts/system/irc-peer-roster.md +1 -0
- package/src/prompts/system/subagent-system-prompt.md +1 -0
- package/src/prompts/system/system-prompt.md +1 -0
- package/src/prompts/tools/irc.md +1 -1
- package/src/registry/agent-registry.ts +3 -0
- package/src/sdk.ts +52 -1
- package/src/secrets/obfuscator.ts +4 -2
- package/src/session/agent-session.ts +396 -145
- package/src/session/cache-economics.ts +275 -0
- package/src/session/provider-safety-stop.ts +9 -0
- package/src/session/session-dump-format.ts +6 -0
- package/src/session/session-manager.ts +809 -39
- package/src/session/session-storage.ts +582 -38
- package/src/slash-commands/builtin-registry.ts +143 -9
- package/src/task/executor.ts +12 -3
- package/src/task/index.ts +8 -0
- package/src/task/receipt.ts +7 -2
- package/src/task/types.ts +26 -0
- package/src/tools/ask-answer-registry.ts +25 -0
- package/src/tools/ask.ts +355 -33
- package/src/tools/browser/tab-supervisor.ts +63 -6
- package/src/tools/browser.ts +8 -3
- package/src/tools/index.ts +81 -6
- package/src/tools/sqlite-reader.ts +8 -4
- package/src/tui/output-block.ts +18 -6
- package/src/utils/external-editor.ts +6 -1
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
Image,
|
|
9
9
|
ImageProtocol,
|
|
10
10
|
imageFallback,
|
|
11
|
+
isTerminalGraphicsFallbackActive,
|
|
11
12
|
registerAnimationCallback,
|
|
12
13
|
Spacer,
|
|
13
14
|
TERMINAL,
|
|
@@ -33,7 +34,7 @@ import {
|
|
|
33
34
|
import { formatExpandHint, replaceTabs, resolveImageOptions, truncateToWidth } from "../../tools/render-utils";
|
|
34
35
|
import { toolRenderers } from "../../tools/renderers";
|
|
35
36
|
import { renderStatusLine } from "../../tui";
|
|
36
|
-
import { sanitizeWithOptionalSixelPassthrough } from "../../utils/sixel";
|
|
37
|
+
import { containsSixelSequence, getSixelLineMask, sanitizeWithOptionalSixelPassthrough } from "../../utils/sixel";
|
|
37
38
|
import { renderDiff } from "./diff";
|
|
38
39
|
|
|
39
40
|
function ensureInvalidate(component: unknown): Component {
|
|
@@ -44,6 +45,23 @@ function ensureInvalidate(component: unknown): Component {
|
|
|
44
45
|
return c as Component;
|
|
45
46
|
}
|
|
46
47
|
|
|
48
|
+
const SIXEL_FALLBACK_PLACEHOLDER = "[SIXEL image hidden while IRC sidebar is visible]";
|
|
49
|
+
|
|
50
|
+
function replaceSixelOutputForGraphicsFallback(text: string): string {
|
|
51
|
+
const lines = text.split("\n");
|
|
52
|
+
const sixelLineMask = getSixelLineMask(lines);
|
|
53
|
+
if (!sixelLineMask.some(Boolean)) return text;
|
|
54
|
+
|
|
55
|
+
return lines
|
|
56
|
+
.flatMap((line, index) => {
|
|
57
|
+
if (!sixelLineMask[index]) return [line];
|
|
58
|
+
return index === 0 || !sixelLineMask[index - 1] || containsSixelSequence(line)
|
|
59
|
+
? [SIXEL_FALLBACK_PLACEHOLDER]
|
|
60
|
+
: [];
|
|
61
|
+
})
|
|
62
|
+
.join("\n");
|
|
63
|
+
}
|
|
64
|
+
|
|
47
65
|
function cloneToolArgs<T>(args: T): T {
|
|
48
66
|
if (args === null || args === undefined) return args;
|
|
49
67
|
try {
|
|
@@ -169,7 +187,14 @@ export class ToolExecutionComponent extends Container {
|
|
|
169
187
|
isError?: boolean;
|
|
170
188
|
details?: any;
|
|
171
189
|
};
|
|
172
|
-
#textOutputCache?: {
|
|
190
|
+
#textOutputCache?: {
|
|
191
|
+
content: unknown;
|
|
192
|
+
showImages: boolean;
|
|
193
|
+
terminalImageProtocol: unknown;
|
|
194
|
+
graphicsFallbackActive: boolean;
|
|
195
|
+
output: string;
|
|
196
|
+
};
|
|
197
|
+
#displayBuiltWithGraphicsFallback: boolean | undefined;
|
|
173
198
|
// Edit preview state
|
|
174
199
|
#editMode?: EditMode;
|
|
175
200
|
#editDiffPreview?: PerFileDiffPreview[];
|
|
@@ -441,6 +466,13 @@ export class ToolExecutionComponent extends Container {
|
|
|
441
466
|
this.#updateDisplay();
|
|
442
467
|
}
|
|
443
468
|
|
|
469
|
+
override render(width: number): string[] {
|
|
470
|
+
if (this.#displayBuiltWithGraphicsFallback !== isTerminalGraphicsFallbackActive()) {
|
|
471
|
+
this.#updateDisplay();
|
|
472
|
+
}
|
|
473
|
+
return super.render(width);
|
|
474
|
+
}
|
|
475
|
+
|
|
444
476
|
#updateDisplay(): void {
|
|
445
477
|
// Set background based on state
|
|
446
478
|
const bgFn = this.#isPartial
|
|
@@ -685,6 +717,7 @@ export class ToolExecutionComponent extends Container {
|
|
|
685
717
|
}
|
|
686
718
|
}
|
|
687
719
|
}
|
|
720
|
+
this.#displayBuiltWithGraphicsFallback = isTerminalGraphicsFallbackActive();
|
|
688
721
|
}
|
|
689
722
|
|
|
690
723
|
#getCallArgsForRender(): any {
|
|
@@ -761,11 +794,13 @@ export class ToolExecutionComponent extends Container {
|
|
|
761
794
|
|
|
762
795
|
const content = this.#result.content;
|
|
763
796
|
const terminalImageProtocol = TERMINAL.imageProtocol;
|
|
797
|
+
const graphicsFallbackActive = isTerminalGraphicsFallbackActive();
|
|
764
798
|
const cached = this.#textOutputCache;
|
|
765
799
|
if (
|
|
766
800
|
cached?.content === content &&
|
|
767
801
|
cached.showImages === this.#showImages &&
|
|
768
|
-
cached.terminalImageProtocol === terminalImageProtocol
|
|
802
|
+
cached.terminalImageProtocol === terminalImageProtocol &&
|
|
803
|
+
cached.graphicsFallbackActive === graphicsFallbackActive
|
|
769
804
|
) {
|
|
770
805
|
return cached.output;
|
|
771
806
|
}
|
|
@@ -773,8 +808,8 @@ export class ToolExecutionComponent extends Container {
|
|
|
773
808
|
const textParts: string[] = [];
|
|
774
809
|
for (const block of content ?? []) {
|
|
775
810
|
if (block.type !== "text") continue;
|
|
776
|
-
const text = block.text || "";
|
|
777
|
-
textParts.push(
|
|
811
|
+
const text = sanitizeWithOptionalSixelPassthrough(block.text || "", sanitizeText);
|
|
812
|
+
textParts.push(graphicsFallbackActive ? replaceSixelOutputForGraphicsFallback(text) : text);
|
|
778
813
|
}
|
|
779
814
|
let output = textParts.join("\n");
|
|
780
815
|
|
|
@@ -789,7 +824,13 @@ export class ToolExecutionComponent extends Container {
|
|
|
789
824
|
output = output ? `${output}\n${imageIndicators}` : imageIndicators;
|
|
790
825
|
}
|
|
791
826
|
|
|
792
|
-
this.#textOutputCache = {
|
|
827
|
+
this.#textOutputCache = {
|
|
828
|
+
content,
|
|
829
|
+
showImages: this.#showImages,
|
|
830
|
+
terminalImageProtocol,
|
|
831
|
+
graphicsFallbackActive,
|
|
832
|
+
output,
|
|
833
|
+
};
|
|
793
834
|
return output;
|
|
794
835
|
}
|
|
795
836
|
|
|
@@ -1,4 +1,12 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
type Component,
|
|
3
|
+
Container,
|
|
4
|
+
Markdown,
|
|
5
|
+
Spacer,
|
|
6
|
+
Text,
|
|
7
|
+
type ViewportAnchorRender,
|
|
8
|
+
type ViewportAnchorSource,
|
|
9
|
+
} from "@gajae-code/tui";
|
|
2
10
|
import { getMarkdownTheme, theme } from "../../modes/theme/theme";
|
|
3
11
|
|
|
4
12
|
// OSC 133 shell integration: marks prompt zones for terminal multiplexers
|
|
@@ -10,8 +18,11 @@ const OSC133_ZONE_FINAL = "\x1b]133;C\x07";
|
|
|
10
18
|
* Component that renders a user message
|
|
11
19
|
*/
|
|
12
20
|
export class UserMessageComponent extends Container {
|
|
13
|
-
|
|
21
|
+
#viewportAnchorSource?: ViewportAnchorSource;
|
|
22
|
+
|
|
23
|
+
constructor(text: string, synthetic = false, viewportAnchorId?: string) {
|
|
14
24
|
super();
|
|
25
|
+
if (viewportAnchorId) this.#viewportAnchorSource = { id: viewportAnchorId };
|
|
15
26
|
const bgColor = (value: string) => theme.bg("userMessageBg", value);
|
|
16
27
|
const color = synthetic
|
|
17
28
|
? (value: string) => theme.fg("dim", value)
|
|
@@ -19,7 +30,9 @@ export class UserMessageComponent extends Container {
|
|
|
19
30
|
this.addChild(new Spacer(1));
|
|
20
31
|
const label = synthetic ? "replay" : "user";
|
|
21
32
|
this.addChild(new Text(theme.bold(theme.fg("accent", label)), 1, 0));
|
|
22
|
-
|
|
33
|
+
const prompt = new PromptZoneMarkdown(text, bgColor, color);
|
|
34
|
+
this.addChild(prompt);
|
|
35
|
+
if (this.#viewportAnchorSource) this.setViewportAnchorSource(prompt, this.#viewportAnchorSource);
|
|
23
36
|
}
|
|
24
37
|
}
|
|
25
38
|
|
|
@@ -37,15 +50,19 @@ class PromptZoneMarkdown implements Component {
|
|
|
37
50
|
this.#markdown.invalidate();
|
|
38
51
|
}
|
|
39
52
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
if (lines.length === 0) {
|
|
43
|
-
return lines;
|
|
44
|
-
}
|
|
45
|
-
|
|
53
|
+
#withPromptZone(lines: string[]): string[] {
|
|
54
|
+
if (lines.length === 0) return lines;
|
|
46
55
|
const zoned = [...lines];
|
|
47
56
|
zoned[0] = OSC133_ZONE_START + zoned[0];
|
|
48
57
|
zoned[zoned.length - 1] = `${zoned[zoned.length - 1]}${OSC133_ZONE_END}${OSC133_ZONE_FINAL}`;
|
|
49
58
|
return zoned;
|
|
50
59
|
}
|
|
60
|
+
|
|
61
|
+
renderWithViewportAnchorSource(width: number, source: ViewportAnchorSource): ViewportAnchorRender {
|
|
62
|
+
const rendered = this.#markdown.renderWithViewportAnchorSource(width, source);
|
|
63
|
+
return { lines: this.#withPromptZone(rendered.lines), anchors: rendered.anchors };
|
|
64
|
+
}
|
|
65
|
+
render(width: number): string[] {
|
|
66
|
+
return this.#withPromptZone(this.#markdown.render(width));
|
|
67
|
+
}
|
|
51
68
|
}
|
|
@@ -40,6 +40,7 @@ import { buildHotkeysMarkdown } from "../../modes/utils/hotkeys-markdown";
|
|
|
40
40
|
import { buildToolsMarkdown } from "../../modes/utils/tools-markdown";
|
|
41
41
|
import type { AsyncJobSnapshotItem } from "../../session/agent-session";
|
|
42
42
|
import type { AuthStorage } from "../../session/auth-storage";
|
|
43
|
+
import { computeCacheMissCostSummary, formatCacheMissSummaryLines } from "../../session/cache-economics";
|
|
43
44
|
import type { NewSessionOptions } from "../../session/session-manager";
|
|
44
45
|
import { outputMeta } from "../../tools/output-meta";
|
|
45
46
|
import { resolveToCwd, stripOuterDoubleQuotes } from "../../tools/path-utils";
|
|
@@ -48,6 +49,7 @@ import { getDisplayChangelogEntries } from "../../utils/changelog";
|
|
|
48
49
|
import { copyToClipboard } from "../../utils/clipboard";
|
|
49
50
|
import { openPath } from "../../utils/open";
|
|
50
51
|
import { setSessionTerminalTitle } from "../../utils/title-generator";
|
|
52
|
+
import { prepareTranscriptRebuild } from "../utils/ui-helpers";
|
|
51
53
|
|
|
52
54
|
function showMarkdownPanel(ctx: InteractiveModeContext, title: string, markdown: string): void {
|
|
53
55
|
ctx.chatContainer.addChild(new Spacer(1));
|
|
@@ -426,6 +428,24 @@ export class CommandController {
|
|
|
426
428
|
info += `${theme.fg("dim", "Premium Requests:")} ${normalizedPremiumRequests.toLocaleString()}\n`;
|
|
427
429
|
}
|
|
428
430
|
}
|
|
431
|
+
const cacheMissSummary = stats.costBreakdown
|
|
432
|
+
? computeCacheMissCostSummary(stats.tokens, {
|
|
433
|
+
kind: "persisted-aggregate",
|
|
434
|
+
costBreakdown: stats.costBreakdown,
|
|
435
|
+
})
|
|
436
|
+
: undefined;
|
|
437
|
+
if (cacheMissSummary) {
|
|
438
|
+
info += `\n${theme.bold("Cache Miss Cost")}`;
|
|
439
|
+
for (const line of formatCacheMissSummaryLines(cacheMissSummary)) {
|
|
440
|
+
const separator = line.indexOf(":");
|
|
441
|
+
if (separator === -1) {
|
|
442
|
+
info += `\n${line}`;
|
|
443
|
+
} else {
|
|
444
|
+
info += `\n${theme.fg("dim", `${line.slice(0, separator)}:`)}${line.slice(separator + 1)}`;
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
info += `\n`;
|
|
448
|
+
}
|
|
429
449
|
|
|
430
450
|
if (this.ctx.lspServers && this.ctx.lspServers.length > 0) {
|
|
431
451
|
info += `\n${theme.bold("LSP Servers")}\n`;
|
|
@@ -911,6 +931,8 @@ export class CommandController {
|
|
|
911
931
|
}
|
|
912
932
|
}
|
|
913
933
|
if (!(await this.ctx.session.newSession(options))) return;
|
|
934
|
+
this.ctx.resetIrcSidebarSession();
|
|
935
|
+
|
|
914
936
|
this.ctx.resetObserverRegistry();
|
|
915
937
|
setSessionTerminalTitle(this.ctx.sessionManager.getSessionName(), this.ctx.sessionManager.getCwd());
|
|
916
938
|
|
|
@@ -920,6 +942,7 @@ export class CommandController {
|
|
|
920
942
|
this.ctx.updateEditorBorderColor();
|
|
921
943
|
this.ctx.ui.requestRender();
|
|
922
944
|
|
|
945
|
+
prepareTranscriptRebuild(this.ctx.ui, "replace-identity");
|
|
923
946
|
this.ctx.chatContainer.clear();
|
|
924
947
|
this.ctx.pendingMessagesContainer.clear();
|
|
925
948
|
this.ctx.compactionQueuedMessages = [];
|
|
@@ -958,6 +981,7 @@ export class CommandController {
|
|
|
958
981
|
this.ctx.updateEditorBorderColor();
|
|
959
982
|
this.ctx.ui.requestRender();
|
|
960
983
|
|
|
984
|
+
prepareTranscriptRebuild(this.ctx.ui, "replace-identity");
|
|
961
985
|
this.ctx.chatContainer.clear();
|
|
962
986
|
this.ctx.pendingMessagesContainer.clear();
|
|
963
987
|
this.ctx.compactionQueuedMessages = [];
|
|
@@ -996,6 +1020,7 @@ export class CommandController {
|
|
|
996
1020
|
this.ctx.showError("Fork failed (session not persisted or cancelled)");
|
|
997
1021
|
return;
|
|
998
1022
|
}
|
|
1023
|
+
this.ctx.resetIrcSidebarSession();
|
|
999
1024
|
|
|
1000
1025
|
this.ctx.statusLine.invalidate();
|
|
1001
1026
|
this.ctx.updateEditorTopBorder();
|
|
@@ -1219,7 +1244,7 @@ export class CommandController {
|
|
|
1219
1244
|
: undefined;
|
|
1220
1245
|
await this.ctx.session.compact(instructions, options);
|
|
1221
1246
|
|
|
1222
|
-
this.ctx.rebuildChatFromMessages();
|
|
1247
|
+
this.ctx.rebuildChatFromMessages("reconcile-same-transcript");
|
|
1223
1248
|
|
|
1224
1249
|
this.ctx.statusLine.invalidate();
|
|
1225
1250
|
this.ctx.updateEditorTopBorder();
|
|
@@ -1279,9 +1304,10 @@ export class CommandController {
|
|
|
1279
1304
|
this.ctx.showError("Handoff cancelled");
|
|
1280
1305
|
return;
|
|
1281
1306
|
}
|
|
1307
|
+
this.ctx.resetIrcSidebarSession();
|
|
1282
1308
|
|
|
1283
1309
|
// Rebuild chat from the new session (which now contains the handoff document)
|
|
1284
|
-
this.ctx.rebuildChatFromMessages();
|
|
1310
|
+
this.ctx.rebuildChatFromMessages("replace-identity");
|
|
1285
1311
|
|
|
1286
1312
|
this.ctx.statusLine.invalidate();
|
|
1287
1313
|
this.ctx.updateEditorTopBorder();
|
|
@@ -20,18 +20,20 @@ import { completionNotifyDisabledByEnv } from "../../notifications/config";
|
|
|
20
20
|
import { summaryFromMessage } from "../../notifications/helpers";
|
|
21
21
|
import type { PlanApprovalDetails } from "../../plan-mode/approved-plan";
|
|
22
22
|
import type { AgentSessionEvent } from "../../session/agent-session";
|
|
23
|
-
import { isSilentAbort, readPendingDisplayTag } from "../../session/messages";
|
|
23
|
+
import { type CustomMessage, isSilentAbort, readPendingDisplayTag } from "../../session/messages";
|
|
24
|
+
import { transferSessionMessageIdentity } from "../../session/session-manager";
|
|
24
25
|
import type { ResolveToolDetails } from "../../tools/resolve";
|
|
26
|
+
import type { IrcObservationRecord } from "../irc-observation-ledger";
|
|
25
27
|
import { interruptHint } from "../shared";
|
|
26
28
|
import { buildAbortDisplayMessage } from "../utils/abort-message";
|
|
27
29
|
import { consumeInjectedOptimisticSignature } from "../utils/injected-user-submission";
|
|
30
|
+
import { parseIrcMessage } from "../utils/irc-message";
|
|
28
31
|
import { ringTerminalBell } from "../utils/terminal-bell";
|
|
32
|
+
|
|
29
33
|
import { addChatChild, argsWithPartialJson } from "../utils/ui-helpers";
|
|
30
34
|
|
|
31
35
|
type AgentSessionEventKind = AgentSessionEvent["type"];
|
|
32
36
|
|
|
33
|
-
const IRC_MESSAGE_VISIBLE_TTL_MS = 10_000;
|
|
34
|
-
|
|
35
37
|
/** Test-only performance counters for advisory baseline tests. */
|
|
36
38
|
export const __eventControllerPerfCounters = {
|
|
37
39
|
messageUpdateContentVisits: 0,
|
|
@@ -112,6 +114,7 @@ export class EventController {
|
|
|
112
114
|
#lastAssistantComponent: AssistantMessageComponent | undefined = undefined;
|
|
113
115
|
#idleCompactionTimer?: NodeJS.Timeout;
|
|
114
116
|
#ircExpiryTimers = new Map<string, NodeJS.Timeout>();
|
|
117
|
+
#renderedIrcComponents = new Map<string, readonly Component[]>();
|
|
115
118
|
#toolIntentCache = new Map<string, { args: unknown; intent: string | undefined }>();
|
|
116
119
|
#thinkingContentIndices = new Set<number>();
|
|
117
120
|
#handlers: AgentSessionEventHandlers;
|
|
@@ -157,10 +160,7 @@ export class EventController {
|
|
|
157
160
|
this.ctx.retryLoader.stop();
|
|
158
161
|
this.ctx.retryLoader = undefined;
|
|
159
162
|
}
|
|
160
|
-
|
|
161
|
-
clearTimeout(timer);
|
|
162
|
-
}
|
|
163
|
-
this.#ircExpiryTimers.clear();
|
|
163
|
+
this.clearIrcExpiryTimers();
|
|
164
164
|
}
|
|
165
165
|
|
|
166
166
|
#resetReadGroup(): void {
|
|
@@ -263,8 +263,61 @@ export class EventController {
|
|
|
263
263
|
this.ctx.ui.requestRender();
|
|
264
264
|
}
|
|
265
265
|
|
|
266
|
+
#observeIrcMessage(message: CustomMessage): void {
|
|
267
|
+
const parsed = parseIrcMessage(message);
|
|
268
|
+
if (!parsed) return;
|
|
269
|
+
const arrival = this.ctx.captureIrcArrivalSnapshot();
|
|
270
|
+
const record = this.ctx.ircLedger.observe(parsed, arrival.panelVisible);
|
|
271
|
+
this.#cleanupEvictedIrcObservations();
|
|
272
|
+
if (!record) return;
|
|
273
|
+
const signature = `irc:${record.observationId}`;
|
|
274
|
+
if (this.#renderedCustomMessages.has(signature)) return;
|
|
275
|
+
this.#renderedCustomMessages.add(signature);
|
|
276
|
+
this.#resetReadGroup();
|
|
277
|
+
const components = this.ctx.addLiveIrcObservationToChat(record, arrival);
|
|
278
|
+
this.#renderedIrcComponents.set(record.observationId, components);
|
|
279
|
+
this.#scheduleIrcExpiry(record, components);
|
|
280
|
+
this.ctx.ui.requestRender();
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
#cleanupIrcObservationIds(observationIds: readonly string[]): void {
|
|
284
|
+
const removedComponents = new Set<Component>();
|
|
285
|
+
for (const observationId of observationIds) {
|
|
286
|
+
const timer = this.#ircExpiryTimers.get(observationId);
|
|
287
|
+
if (timer) clearTimeout(timer);
|
|
288
|
+
this.#ircExpiryTimers.delete(observationId);
|
|
289
|
+
for (const component of this.#renderedIrcComponents.get(observationId) ?? []) {
|
|
290
|
+
removedComponents.add(component);
|
|
291
|
+
}
|
|
292
|
+
this.#renderedIrcComponents.delete(observationId);
|
|
293
|
+
for (const component of this.ctx.removeRenderedIrcInlineComponents(observationId) ?? []) {
|
|
294
|
+
removedComponents.add(component);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
this.#renderedCustomMessages.delete(`irc:${observationId}`);
|
|
298
|
+
}
|
|
299
|
+
for (const component of removedComponents) this.ctx.chatContainer.removeChild(component);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
#cleanupEvictedIrcObservations(): void {
|
|
303
|
+
this.#cleanupIrcObservationIds(this.ctx.ircLedger.drainEvictedObservationIds());
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
#cleanupExpiredIrcInlineComponents(observationId: string): void {
|
|
307
|
+
const removedComponents = new Set<Component>(this.#renderedIrcComponents.get(observationId));
|
|
308
|
+
this.#renderedIrcComponents.delete(observationId);
|
|
309
|
+
for (const component of this.ctx.removeRenderedIrcInlineComponents(observationId) ?? []) {
|
|
310
|
+
removedComponents.add(component);
|
|
311
|
+
}
|
|
312
|
+
for (const component of removedComponents) this.ctx.chatContainer.removeChild(component);
|
|
313
|
+
}
|
|
314
|
+
|
|
266
315
|
async #handleMessageStart(event: Extract<AgentSessionEvent, { type: "message_start" }>): Promise<void> {
|
|
267
316
|
if (event.message.role === "hookMessage" || event.message.role === "custom") {
|
|
317
|
+
if (event.message.role === "custom" && parseIrcMessage(event.message)) {
|
|
318
|
+
this.#observeIrcMessage(event.message);
|
|
319
|
+
return;
|
|
320
|
+
}
|
|
268
321
|
const signature = `${event.message.role}:${event.message.customType}:${event.message.timestamp}`;
|
|
269
322
|
if (this.#renderedCustomMessages.has(signature)) {
|
|
270
323
|
return;
|
|
@@ -328,8 +381,11 @@ export class EventController {
|
|
|
328
381
|
this.#resetReadGroup();
|
|
329
382
|
this.#toolIntentCache.clear();
|
|
330
383
|
this.#thinkingContentIndices.clear();
|
|
331
|
-
this.ctx.streamingComponent = new AssistantMessageComponent(
|
|
332
|
-
|
|
384
|
+
this.ctx.streamingComponent = new AssistantMessageComponent(
|
|
385
|
+
undefined,
|
|
386
|
+
this.ctx.hideThinkingBlock,
|
|
387
|
+
() => this.ctx.ui.requestRender(),
|
|
388
|
+
this.ctx.getAssistantViewportAnchorId?.(event.message),
|
|
333
389
|
);
|
|
334
390
|
this.ctx.streamingMessage = event.message;
|
|
335
391
|
addChatChild(this.ctx, this.ctx.streamingComponent);
|
|
@@ -339,15 +395,89 @@ export class EventController {
|
|
|
339
395
|
}
|
|
340
396
|
|
|
341
397
|
async #handleIrcMessage(event: Extract<AgentSessionEvent, { type: "irc_message" }>): Promise<void> {
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
398
|
+
this.#observeIrcMessage(event.message);
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
clearIrcExpiryTimers(): void {
|
|
402
|
+
for (const timer of this.#ircExpiryTimers.values()) {
|
|
403
|
+
clearTimeout(timer);
|
|
404
|
+
}
|
|
405
|
+
this.#ircExpiryTimers.clear();
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
resetIrcObservations(): void {
|
|
409
|
+
this.clearIrcExpiryTimers();
|
|
410
|
+
const observationIds = new Set<string>(this.#renderedIrcComponents.keys());
|
|
411
|
+
for (const observationId of this.ctx.ircLedger.drainEvictedObservationIds()) observationIds.add(observationId);
|
|
412
|
+
this.#cleanupIrcObservationIds([...observationIds]);
|
|
413
|
+
for (const components of this.ctx.resetRenderedIrcInlineComponents()) {
|
|
414
|
+
for (const component of components) this.ctx.chatContainer.removeChild(component);
|
|
415
|
+
}
|
|
416
|
+
this.#renderedIrcComponents.clear();
|
|
417
|
+
for (const signature of this.#renderedCustomMessages) {
|
|
418
|
+
if (signature.startsWith("irc:")) this.#renderedCustomMessages.delete(signature);
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
reconcileIrcExpiryTimers(componentsByObservationId: Map<string, readonly Component[]>): void {
|
|
423
|
+
this.clearIrcExpiryTimers();
|
|
424
|
+
this.#renderedIrcComponents.clear();
|
|
425
|
+
const now = Date.now();
|
|
426
|
+
const inlineProjection = this.ctx.ircLedger.getInlineProjection(now);
|
|
427
|
+
|
|
428
|
+
this.#cleanupEvictedIrcObservations();
|
|
429
|
+
const projectedObservationIds = new Set(inlineProjection.map(record => record.observationId));
|
|
430
|
+
for (const [observationId, components] of componentsByObservationId) {
|
|
431
|
+
const record = this.ctx.ircLedger.getRecord(observationId);
|
|
432
|
+
if (
|
|
433
|
+
!projectedObservationIds.has(observationId) ||
|
|
434
|
+
(record?.mode === "ephemeral" && record.expiresAt! - now <= 0)
|
|
435
|
+
) {
|
|
436
|
+
for (const component of components) {
|
|
437
|
+
this.ctx.chatContainer.removeChild(component);
|
|
438
|
+
}
|
|
439
|
+
this.#renderedIrcComponents.delete(observationId);
|
|
440
|
+
componentsByObservationId.delete(observationId);
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
for (const record of inlineProjection) {
|
|
444
|
+
const components = componentsByObservationId.get(record.observationId);
|
|
445
|
+
if (!components) continue;
|
|
446
|
+
this.#renderedIrcComponents.set(record.observationId, components);
|
|
447
|
+
// #scheduleIrcExpiry owns the single expiry clock read: a record that
|
|
448
|
+
// crossed its deadline while projection/reconciliation ran above is
|
|
449
|
+
// cleaned up there (old timers were already cleared, so silently
|
|
450
|
+
// skipping the timer would leave the row inline indefinitely).
|
|
451
|
+
if (!this.#scheduleIrcExpiry(record, components)) {
|
|
452
|
+
componentsByObservationId.delete(record.observationId);
|
|
453
|
+
}
|
|
345
454
|
}
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
/**
|
|
458
|
+
* Arms the removal timer for an ephemeral record using a single clock read.
|
|
459
|
+
* Returns false when the record's deadline has already elapsed — in that
|
|
460
|
+
* case the rendered components are removed from the chat container and
|
|
461
|
+
* {@link #renderedIrcComponents} here, and the caller must drop its own
|
|
462
|
+
* bookkeeping entry. Persistent records always return true (no timer).
|
|
463
|
+
*/
|
|
464
|
+
#scheduleIrcExpiry(record: IrcObservationRecord, components: readonly Component[]): boolean {
|
|
465
|
+
if (record.mode !== "ephemeral" || components.length === 0 || this.#ircExpiryTimers.has(record.observationId)) {
|
|
466
|
+
return true;
|
|
467
|
+
}
|
|
468
|
+
const remainingMs = record.expiresAt! - Date.now();
|
|
469
|
+
if (remainingMs <= 0) {
|
|
470
|
+
this.#cleanupExpiredIrcInlineComponents(record.observationId);
|
|
471
|
+
return false;
|
|
472
|
+
}
|
|
473
|
+
const timer = setTimeout(() => {
|
|
474
|
+
this.#ircExpiryTimers.delete(record.observationId);
|
|
475
|
+
this.#cleanupExpiredIrcInlineComponents(record.observationId);
|
|
476
|
+
this.ctx.ui.requestRender();
|
|
477
|
+
}, remainingMs);
|
|
478
|
+
timer.unref?.();
|
|
479
|
+
this.#ircExpiryTimers.set(record.observationId, timer);
|
|
480
|
+
return true;
|
|
351
481
|
}
|
|
352
482
|
|
|
353
483
|
async #handleSubagentSteerMessage(
|
|
@@ -360,28 +490,13 @@ export class EventController {
|
|
|
360
490
|
const signature = obsId
|
|
361
491
|
? `steer:${obsId}`
|
|
362
492
|
: `${event.message.role}:${event.message.customType}:${event.message.timestamp}:${details?.from}:${details?.to}:${details?.state}:${details?.body}`;
|
|
363
|
-
if (this.#renderedCustomMessages.has(signature))
|
|
364
|
-
return;
|
|
365
|
-
}
|
|
493
|
+
if (this.#renderedCustomMessages.has(signature)) return;
|
|
366
494
|
this.#renderedCustomMessages.add(signature);
|
|
367
495
|
this.#resetReadGroup();
|
|
368
496
|
this.ctx.addMessageToChat(event.message);
|
|
369
497
|
this.ctx.ui.requestRender();
|
|
370
498
|
}
|
|
371
499
|
|
|
372
|
-
#scheduleIrcExpiry(signature: string, components: Component[]): void {
|
|
373
|
-
if (components.length === 0 || this.#ircExpiryTimers.has(signature)) return;
|
|
374
|
-
const timer = setTimeout(() => {
|
|
375
|
-
this.#ircExpiryTimers.delete(signature);
|
|
376
|
-
for (const component of components) {
|
|
377
|
-
this.ctx.chatContainer.removeChild(component);
|
|
378
|
-
}
|
|
379
|
-
this.ctx.ui.requestRender();
|
|
380
|
-
}, IRC_MESSAGE_VISIBLE_TTL_MS);
|
|
381
|
-
timer.unref?.();
|
|
382
|
-
this.#ircExpiryTimers.set(signature, timer);
|
|
383
|
-
}
|
|
384
|
-
|
|
385
500
|
async #handleNotice(event: Extract<AgentSessionEvent, { type: "notice" }>): Promise<void> {
|
|
386
501
|
const message = event.source ? `${event.source}: ${event.message}` : event.message;
|
|
387
502
|
if (event.level === "error") {
|
|
@@ -395,6 +510,9 @@ export class EventController {
|
|
|
395
510
|
|
|
396
511
|
async #handleMessageUpdate(event: Extract<AgentSessionEvent, { type: "message_update" }>): Promise<void> {
|
|
397
512
|
if (this.ctx.streamingComponent && event.message.role === "assistant") {
|
|
513
|
+
if (this.ctx.streamingMessage?.role === "assistant") {
|
|
514
|
+
transferSessionMessageIdentity([this.ctx.streamingMessage], [event.message]);
|
|
515
|
+
}
|
|
398
516
|
this.ctx.streamingMessage = event.message;
|
|
399
517
|
this.ctx.streamingComponent.updateContent(this.ctx.streamingMessage, { streaming: true });
|
|
400
518
|
const contentIndex = event.assistantMessageEvent?.contentIndex;
|
|
@@ -498,6 +616,9 @@ export class EventController {
|
|
|
498
616
|
async #handleMessageEnd(event: Extract<AgentSessionEvent, { type: "message_end" }>): Promise<void> {
|
|
499
617
|
if (event.message.role === "user") return;
|
|
500
618
|
if (this.ctx.streamingComponent && event.message.role === "assistant") {
|
|
619
|
+
if (this.ctx.streamingMessage?.role === "assistant") {
|
|
620
|
+
transferSessionMessageIdentity([this.ctx.streamingMessage], [event.message]);
|
|
621
|
+
}
|
|
501
622
|
this.ctx.streamingMessage = event.message;
|
|
502
623
|
let errorMessage: string | undefined;
|
|
503
624
|
const aborted = this.ctx.streamingMessage.stopReason === "aborted";
|
|
@@ -750,7 +871,7 @@ export class EventController {
|
|
|
750
871
|
if (event.aborted) {
|
|
751
872
|
this.ctx.showStatus(isHandoffAction ? "Auto-handoff cancelled" : "Auto context-full maintenance cancelled");
|
|
752
873
|
} else if (event.result) {
|
|
753
|
-
this.ctx.rebuildChatFromMessages();
|
|
874
|
+
this.ctx.rebuildChatFromMessages("reconcile-same-transcript");
|
|
754
875
|
this.ctx.statusLine.invalidate();
|
|
755
876
|
this.ctx.updateEditorTopBorder();
|
|
756
877
|
if (continuationDisabled && !isHandoffAction) {
|
|
@@ -761,8 +882,10 @@ export class EventController {
|
|
|
761
882
|
} else if (event.errorMessage) {
|
|
762
883
|
this.ctx.showWarning(event.errorMessage);
|
|
763
884
|
} else if (isHandoffAction) {
|
|
764
|
-
|
|
765
|
-
|
|
885
|
+
// Reset BEFORE rebuild so the new session's transcript is not replayed
|
|
886
|
+
// from the old ledger and then cleared out from under its timers.
|
|
887
|
+
this.ctx.resetIrcSidebarSession();
|
|
888
|
+
this.ctx.rebuildChatFromMessages("replace-identity");
|
|
766
889
|
this.ctx.statusLine.invalidate();
|
|
767
890
|
this.ctx.updateEditorTopBorder();
|
|
768
891
|
await this.ctx.reloadTodos();
|