@gajae-code/coding-agent 0.9.2 → 0.9.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 +19 -0
- package/dist/types/build-metadata.d.ts +7 -0
- package/dist/types/capability/mcp.d.ts +3 -1
- package/dist/types/commands/mcp.d.ts +1 -1
- package/dist/types/config/keybindings.d.ts +2 -2
- package/dist/types/modes/components/hook-selector.d.ts +1 -0
- package/dist/types/modes/components/status-line/types.d.ts +6 -0
- package/dist/types/modes/components/status-line.d.ts +1 -0
- package/dist/types/modes/components/welcome.d.ts +1 -0
- package/dist/types/modes/rpc/rpc-client.d.ts +7 -2
- package/dist/types/modes/utils/ui-helpers.d.ts +12 -0
- package/dist/types/notifications/telegram-daemon.d.ts +2 -0
- package/dist/types/runtime-mcp/config-writer.d.ts +4 -3
- package/dist/types/runtime-mcp/types.d.ts +5 -4
- package/dist/types/session/agent-session.d.ts +16 -0
- package/dist/types/session/session-manager.d.ts +1 -0
- package/dist/types/tools/skill.d.ts +9 -8
- package/dist/types/utils/linkedom.d.ts +4 -0
- package/dist/types/utils/pasted-image-path.d.ts +5 -3
- package/package.json +11 -7
- package/scripts/build-binary.ts +2 -40
- package/scripts/compile-args.ts +96 -0
- package/scripts/generate-docs-index.ts +1 -0
- package/src/build-metadata.ts +104 -0
- package/src/capability/mcp.ts +3 -1
- package/src/cli/mcp-cli.ts +108 -26
- package/src/cli.ts +21 -0
- package/src/commands/mcp.ts +8 -8
- package/src/commands/stats.ts +6 -2
- package/src/config/keybindings.ts +2 -2
- package/src/config/mcp-schema.json +21 -17
- package/src/coordinator-mcp/server.ts +7 -4
- package/src/discovery/builtin.ts +14 -0
- package/src/discovery/mcp-json.ts +14 -0
- package/src/eval/js/executor.ts +7 -1
- package/src/gjc-runtime/state-runtime.ts +105 -12
- package/src/gjc-runtime/workflow-manifest.generated.json +4 -28
- package/src/gjc-runtime/workflow-manifest.ts +1 -7
- package/src/internal-urls/docs-index.generated.ts +3 -2
- package/src/internal-urls/gjc-protocol.ts +14 -1
- package/src/modes/components/assistant-message.ts +1 -1
- package/src/modes/components/hook-selector.ts +4 -0
- package/src/modes/components/session-selector.ts +2 -2
- package/src/modes/components/status-line/presets.ts +5 -7
- package/src/modes/components/status-line/segments.ts +14 -0
- package/src/modes/components/status-line/types.ts +6 -0
- package/src/modes/components/status-line.ts +13 -6
- package/src/modes/components/tool-execution.ts +1 -1
- package/src/modes/components/welcome.ts +4 -1
- package/src/modes/controllers/command-controller.ts +1 -1
- package/src/modes/controllers/event-controller.ts +9 -9
- package/src/modes/controllers/input-controller.ts +8 -2
- package/src/modes/controllers/selector-controller.ts +7 -3
- package/src/modes/interactive-mode.ts +12 -4
- package/src/modes/rpc/rpc-client.ts +30 -6
- package/src/modes/utils/ui-helpers.ts +154 -32
- package/src/notifications/telegram-daemon.ts +28 -2
- package/src/prompts/tools/skill.md +2 -2
- package/src/runtime-mcp/config-writer.ts +4 -3
- package/src/runtime-mcp/config.ts +1 -0
- package/src/runtime-mcp/types.ts +5 -4
- package/src/session/agent-session.ts +117 -3
- package/src/session/session-manager.ts +68 -40
- package/src/slash-commands/builtin-registry.ts +9 -4
- package/src/tools/bash-interactive.ts +9 -4
- package/src/tools/browser/readable.ts +2 -2
- package/src/tools/fetch.ts +5 -5
- package/src/tools/skill.ts +14 -11
- package/src/utils/linkedom.ts +14 -0
- package/src/utils/pasted-image-path.ts +23 -9
- package/src/web/scrapers/arxiv.ts +2 -2
- package/src/web/scrapers/go-pkg.ts +2 -2
- package/src/web/scrapers/iacr.ts +2 -2
- package/src/web/scrapers/readthedocs.ts +2 -2
- package/src/web/scrapers/twitter.ts +3 -2
- package/src/web/scrapers/wikipedia.ts +2 -2
- package/src/export/html/template.css +0 -1060
- package/src/export/html/template.html +0 -47
- package/src/export/html/template.js +0 -2314
- package/vendor/insane-search/engine/tests/test_hardening.py +0 -57
- package/vendor/insane-search/engine/tests/test_smoke.py +0 -152
- package/vendor/insane-search/engine/tests/test_u1.py +0 -200
- package/vendor/insane-search/engine/tests/test_u4.py +0 -131
- package/vendor/insane-search/engine/tests/test_u5.py +0 -163
- package/vendor/insane-search/engine/tests/test_u7.py +0 -124
- package/vendor/insane-search/engine/tests/test_u8.py +0 -216
|
@@ -29,7 +29,7 @@ import { calculateTokensPerSecond } from "./status-line/token-rate";
|
|
|
29
29
|
import type { SeparatorDef } from "./status-line/types";
|
|
30
30
|
|
|
31
31
|
export interface StatusLineSegmentOptions {
|
|
32
|
-
model?: { showThinkingLevel?: boolean };
|
|
32
|
+
model?: { showThinkingLevel?: boolean; showContextPercent?: boolean };
|
|
33
33
|
path?: { abbreviate?: boolean; maxLength?: number; stripWorkPrefix?: boolean };
|
|
34
34
|
git?: { showBranch?: boolean; showStaged?: boolean; showUnstaged?: boolean; showUntracked?: boolean };
|
|
35
35
|
time?: { format?: "12h" | "24h"; showSeconds?: boolean };
|
|
@@ -700,6 +700,11 @@ export class StatusLineComponent implements Component {
|
|
|
700
700
|
const contextTokens = breakdown.usedTokens;
|
|
701
701
|
const contextWindow = breakdown.contextWindow || state.model?.contextWindow || 0;
|
|
702
702
|
const contextPercent = contextWindow > 0 ? (contextTokens / contextWindow) * 100 : 0;
|
|
703
|
+
// Suppress the inline model percentage when a standalone context_pct
|
|
704
|
+
// segment is also rendered, so the value is not shown twice.
|
|
705
|
+
const contextPctSegmentActive =
|
|
706
|
+
effectiveSettings.leftSegments.includes("context_pct") ||
|
|
707
|
+
effectiveSettings.rightSegments.includes("context_pct");
|
|
703
708
|
|
|
704
709
|
return {
|
|
705
710
|
session: this.session,
|
|
@@ -710,6 +715,7 @@ export class StatusLineComponent implements Component {
|
|
|
710
715
|
usageStats,
|
|
711
716
|
contextPercent,
|
|
712
717
|
contextWindow,
|
|
718
|
+
contextPctSegmentActive,
|
|
713
719
|
autoCompactEnabled: this.#autoCompactEnabled,
|
|
714
720
|
subagentCount: this.#subagentCount,
|
|
715
721
|
jobs: this.#jobs,
|
|
@@ -923,11 +929,8 @@ export class StatusLineComponent implements Component {
|
|
|
923
929
|
const totalWidth = () => leftWidth + rightWidth + (left.length > 0 && right.length > 0 ? 1 : 0);
|
|
924
930
|
|
|
925
931
|
if (topFillWidth > 0) {
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
rightWidth = this.#groupWidth(right, rightCapWidth, rightSepWidth);
|
|
929
|
-
}
|
|
930
|
-
// Shrink path before dropping left segments — path is the only elastic segment
|
|
932
|
+
// Shrink path before dropping right-side telemetry — path is the only elastic segment,
|
|
933
|
+
// and presets such as default-usage should not hide usage just because cwd is long.
|
|
931
934
|
const pathIdx = leftIds.indexOf("path");
|
|
932
935
|
if (pathIdx >= 0 && totalWidth() > topFillWidth) {
|
|
933
936
|
const overflow = totalWidth() - topFillWidth;
|
|
@@ -937,6 +940,10 @@ export class StatusLineComponent implements Component {
|
|
|
937
940
|
leftWidth = this.#groupWidth(left, leftCapWidth, leftSepWidth);
|
|
938
941
|
}
|
|
939
942
|
}
|
|
943
|
+
while (totalWidth() > topFillWidth && right.length > 0) {
|
|
944
|
+
right.pop();
|
|
945
|
+
rightWidth = this.#groupWidth(right, rightCapWidth, rightSepWidth);
|
|
946
|
+
}
|
|
940
947
|
while (totalWidth() > topFillWidth && left.length > 0) {
|
|
941
948
|
left.pop();
|
|
942
949
|
leftIds.pop();
|
|
@@ -678,7 +678,7 @@ export class ToolExecutionComponent extends Container {
|
|
|
678
678
|
imageData,
|
|
679
679
|
imageMimeType,
|
|
680
680
|
{ fallbackColor: (s: string) => theme.fg("toolOutput", s) },
|
|
681
|
-
resolveImageOptions(),
|
|
681
|
+
{ ...resolveImageOptions(), refetch: () => imageData },
|
|
682
682
|
);
|
|
683
683
|
this.#imageComponents.push(imageComponent);
|
|
684
684
|
this.addChild(imageComponent);
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type Component, padding, TERMINAL, truncateToWidth, visibleWidth } from "@gajae-code/tui";
|
|
2
2
|
import { APP_NAME } from "@gajae-code/utils";
|
|
3
|
+
import { formatBuildLabel } from "../../build-metadata";
|
|
3
4
|
import { type ThemeColor, theme } from "../../modes/theme/theme";
|
|
4
5
|
|
|
5
6
|
export interface RecentSession {
|
|
@@ -19,6 +20,7 @@ export interface WelcomeComponentOptions {
|
|
|
19
20
|
getReservedBottomRows?: (termWidth: number) => number;
|
|
20
21
|
changelogMarkdown?: string;
|
|
21
22
|
collapseChangelog?: boolean;
|
|
23
|
+
buildLabel?: string;
|
|
22
24
|
}
|
|
23
25
|
|
|
24
26
|
const WELCOME_STATIC_RIGHT_ROWS_EXCLUDING_DYNAMIC_SECTIONS = 9;
|
|
@@ -227,7 +229,8 @@ export class WelcomeComponent implements Component {
|
|
|
227
229
|
const br = theme.fg("dim", theme.boxRound.bottomRight);
|
|
228
230
|
|
|
229
231
|
const lines: string[] = [];
|
|
230
|
-
const
|
|
232
|
+
const buildLabel = this.options.buildLabel ?? formatBuildLabel();
|
|
233
|
+
const title = ` ${APP_NAME} v${this.version} · ${buildLabel} · GJC Forge `;
|
|
231
234
|
const titlePrefixRaw = hChar.repeat(3);
|
|
232
235
|
const titleStyled = theme.fg("dim", titlePrefixRaw) + theme.fg("muted", title);
|
|
233
236
|
const titleVisLen = visibleWidth(titlePrefixRaw) + visibleWidth(title);
|
|
@@ -567,7 +567,7 @@ export class CommandController {
|
|
|
567
567
|
`| Start a fresh session | \`${sessionNewKey}\` or \`/new\` |`,
|
|
568
568
|
"| Resume another session | `/resume` |",
|
|
569
569
|
"| Show session details | `/session info` |",
|
|
570
|
-
"| Delete current session | `/session delete` |",
|
|
570
|
+
"| Delete current session transcript/artifacts | `/session delete` |",
|
|
571
571
|
"| Select a model | `/model` or `Ctrl+L` |",
|
|
572
572
|
"| Show all shortcuts | `?` on an empty prompt or `/hotkeys` |",
|
|
573
573
|
"",
|
|
@@ -26,7 +26,7 @@ import { interruptHint } from "../shared";
|
|
|
26
26
|
import { buildAbortDisplayMessage } from "../utils/abort-message";
|
|
27
27
|
import { consumeInjectedOptimisticSignature } from "../utils/injected-user-submission";
|
|
28
28
|
import { ringTerminalBell } from "../utils/terminal-bell";
|
|
29
|
-
import { argsWithPartialJson } from "../utils/ui-helpers";
|
|
29
|
+
import { addChatChild, argsWithPartialJson } from "../utils/ui-helpers";
|
|
30
30
|
|
|
31
31
|
type AgentSessionEventKind = AgentSessionEvent["type"];
|
|
32
32
|
|
|
@@ -169,12 +169,12 @@ export class EventController {
|
|
|
169
169
|
|
|
170
170
|
#getReadGroup(): ReadToolGroupComponent {
|
|
171
171
|
if (!this.#lastReadGroup) {
|
|
172
|
-
this.ctx
|
|
172
|
+
addChatChild(this.ctx, new Text("", 0, 0));
|
|
173
173
|
const group = new ReadToolGroupComponent({
|
|
174
174
|
showContentPreview: this.ctx.settings.get("read.toolResultPreview"),
|
|
175
175
|
});
|
|
176
176
|
group.setExpanded(this.ctx.toolOutputExpanded);
|
|
177
|
-
this.ctx
|
|
177
|
+
addChatChild(this.ctx, group);
|
|
178
178
|
this.#lastReadGroup = group;
|
|
179
179
|
}
|
|
180
180
|
return this.#lastReadGroup;
|
|
@@ -332,7 +332,7 @@ export class EventController {
|
|
|
332
332
|
this.ctx.ui.requestRender(),
|
|
333
333
|
);
|
|
334
334
|
this.ctx.streamingMessage = event.message;
|
|
335
|
-
this.ctx
|
|
335
|
+
addChatChild(this.ctx, this.ctx.streamingComponent);
|
|
336
336
|
this.ctx.streamingComponent.updateContent(this.ctx.streamingMessage, { streaming: true });
|
|
337
337
|
this.ctx.ui.requestRender();
|
|
338
338
|
}
|
|
@@ -445,7 +445,7 @@ export class EventController {
|
|
|
445
445
|
);
|
|
446
446
|
if (!this.ctx.pendingTools.has(content.id)) {
|
|
447
447
|
this.#resetReadGroup();
|
|
448
|
-
this.ctx
|
|
448
|
+
addChatChild(this.ctx, new Text("", 0, 0));
|
|
449
449
|
const tool = this.ctx.session.getToolByName(content.name);
|
|
450
450
|
const component = new ToolExecutionComponent(
|
|
451
451
|
content.name,
|
|
@@ -462,8 +462,8 @@ export class EventController {
|
|
|
462
462
|
content.id,
|
|
463
463
|
);
|
|
464
464
|
component.setExpanded(this.ctx.toolOutputExpanded);
|
|
465
|
-
this.ctx.chatContainer.addChild(component);
|
|
466
465
|
this.ctx.pendingTools.set(content.id, component);
|
|
466
|
+
addChatChild(this.ctx, component);
|
|
467
467
|
} else {
|
|
468
468
|
this.ctx.pendingTools.get(content.id)?.updateArgs(renderArgs, content.id);
|
|
469
469
|
}
|
|
@@ -575,8 +575,8 @@ export class EventController {
|
|
|
575
575
|
event.toolCallId,
|
|
576
576
|
);
|
|
577
577
|
component.setExpanded(this.ctx.toolOutputExpanded);
|
|
578
|
-
this.ctx.chatContainer.addChild(component);
|
|
579
578
|
this.ctx.pendingTools.set(event.toolCallId, component);
|
|
579
|
+
addChatChild(this.ctx, component);
|
|
580
580
|
this.ctx.ui.requestRender();
|
|
581
581
|
}
|
|
582
582
|
}
|
|
@@ -869,13 +869,13 @@ export class EventController {
|
|
|
869
869
|
async #handleTtsrTriggered(event: Extract<AgentSessionEvent, { type: "ttsr_triggered" }>): Promise<void> {
|
|
870
870
|
const component = new TtsrNotificationComponent(event.rules);
|
|
871
871
|
component.setExpanded(this.ctx.toolOutputExpanded);
|
|
872
|
-
this.ctx
|
|
872
|
+
addChatChild(this.ctx, component);
|
|
873
873
|
this.ctx.ui.requestRender();
|
|
874
874
|
}
|
|
875
875
|
|
|
876
876
|
async #handleTodoReminder(event: Extract<AgentSessionEvent, { type: "todo_reminder" }>): Promise<void> {
|
|
877
877
|
const component = new TodoReminderComponent(event.todos, event.attempt, event.maxAttempts);
|
|
878
|
-
this.ctx
|
|
878
|
+
addChatChild(this.ctx, component);
|
|
879
879
|
this.ctx.ui.requestRender();
|
|
880
880
|
}
|
|
881
881
|
|
|
@@ -18,7 +18,7 @@ import { copyToClipboard, readImageFromClipboard } from "../../utils/clipboard";
|
|
|
18
18
|
import { getEditorCommand, openInEditor } from "../../utils/external-editor";
|
|
19
19
|
import { ensureSupportedImageInput, ImageInputTooLargeError, loadImageInput } from "../../utils/image-loading";
|
|
20
20
|
import { resizeImage } from "../../utils/image-resize";
|
|
21
|
-
import { resolvePastedImagePath } from "../../utils/pasted-image-path";
|
|
21
|
+
import { formatPastedImageReference, resolvePastedImagePath } from "../../utils/pasted-image-path";
|
|
22
22
|
import { generateSessionTitle, setSessionTerminalTitle } from "../../utils/title-generator";
|
|
23
23
|
import { type QueuedMessageMoveDirection, QueuedMessageSelectorComponent } from "../components/queued-message-selector";
|
|
24
24
|
|
|
@@ -149,6 +149,12 @@ export class InputController {
|
|
|
149
149
|
return { consume: true };
|
|
150
150
|
}
|
|
151
151
|
const hookDialogActive = this.#hasHookDialog();
|
|
152
|
+
if (this.ctx.hookSelector?.hasActiveInlineInput?.() === true) {
|
|
153
|
+
// Inline ask/custom-input editors use Esc to return to the option list.
|
|
154
|
+
// Let the focused selector see the key instead of converting a typo
|
|
155
|
+
// into a full workflow/session abort while the agent is streaming.
|
|
156
|
+
return undefined;
|
|
157
|
+
}
|
|
152
158
|
if (
|
|
153
159
|
this.#handleCancellableWorkEscape({
|
|
154
160
|
loading: hookDialogActive,
|
|
@@ -1096,7 +1102,7 @@ export class InputController {
|
|
|
1096
1102
|
data: image.data,
|
|
1097
1103
|
mimeType: image.mimeType,
|
|
1098
1104
|
});
|
|
1099
|
-
this.ctx.editor.insertText(`${this.#nextImagePlaceholder()} `);
|
|
1105
|
+
this.ctx.editor.insertText(`${formatPastedImageReference(this.#nextImagePlaceholder(), image.resolvedPath)} `);
|
|
1100
1106
|
this.ctx.showStatus(`Attached image: ${path.basename(image.resolvedPath)}`, { dim: true });
|
|
1101
1107
|
this.ctx.ui.requestRender();
|
|
1102
1108
|
return true;
|
|
@@ -1431,8 +1431,12 @@ export class SelectorController {
|
|
|
1431
1431
|
}
|
|
1432
1432
|
|
|
1433
1433
|
const confirmed = await this.ctx.showHookConfirm(
|
|
1434
|
-
"Delete
|
|
1435
|
-
|
|
1434
|
+
"Delete current session transcript and artifacts?",
|
|
1435
|
+
[
|
|
1436
|
+
"This permanently deletes only the current session transcript file and its artifacts directory.",
|
|
1437
|
+
"Other sessions and topic/history metadata are not deleted.",
|
|
1438
|
+
"You will be moved to a fresh session and returned to the session selector.",
|
|
1439
|
+
].join("\n"),
|
|
1436
1440
|
);
|
|
1437
1441
|
|
|
1438
1442
|
if (!confirmed) {
|
|
@@ -1449,7 +1453,7 @@ export class SelectorController {
|
|
|
1449
1453
|
await storage.deleteSessionWithArtifacts(sessionFile);
|
|
1450
1454
|
|
|
1451
1455
|
// Show session selector
|
|
1452
|
-
this.ctx.showStatus("
|
|
1456
|
+
this.ctx.showStatus("Current session transcript and artifacts deleted");
|
|
1453
1457
|
await this.showSessionSelector();
|
|
1454
1458
|
}
|
|
1455
1459
|
|
|
@@ -18,6 +18,7 @@ import type { Component, EditorTheme, SlashCommand } from "@gajae-code/tui";
|
|
|
18
18
|
import {
|
|
19
19
|
Container,
|
|
20
20
|
clearRenderCache,
|
|
21
|
+
getRenderCacheRetainedBytes,
|
|
21
22
|
Loader,
|
|
22
23
|
Markdown,
|
|
23
24
|
ProcessTerminal,
|
|
@@ -114,7 +115,7 @@ import {
|
|
|
114
115
|
theme,
|
|
115
116
|
} from "./theme/theme";
|
|
116
117
|
import type { CompactionQueuedMessage, InteractiveModeContext, SubmittedUserInput, TodoItem, TodoPhase } from "./types";
|
|
117
|
-
import { UiHelpers } from "./utils/ui-helpers";
|
|
118
|
+
import { addChatChild, UiHelpers } from "./utils/ui-helpers";
|
|
118
119
|
|
|
119
120
|
const INTERACTIVE_ABORT_CLEANUP_TIMEOUT_MS = 5_000;
|
|
120
121
|
const COMPOSER_NEWLINE_HINT = process.platform === "win32" ? "Alt+Enter/Ctrl+J" : "Shift+Enter/Ctrl+J";
|
|
@@ -398,6 +399,10 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
398
399
|
) {
|
|
399
400
|
this.session = session;
|
|
400
401
|
this.sessionManager = session.sessionManager;
|
|
402
|
+
this.session.setRetainedMemorySampler(() => ({
|
|
403
|
+
tuiChatChildren: this.chatContainer.children.length,
|
|
404
|
+
tuiCachedRenderBytes: getRenderCacheRetainedBytes(),
|
|
405
|
+
}));
|
|
401
406
|
this.settings = session.settings;
|
|
402
407
|
this.keybindings = KeybindingsManager.inMemory();
|
|
403
408
|
this.agent = session.agent;
|
|
@@ -1514,8 +1519,11 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
1514
1519
|
|
|
1515
1520
|
#renderPlanPreview(planContent: string, options?: { append?: boolean }): void {
|
|
1516
1521
|
const existingContainer = this.#planReviewContainer;
|
|
1517
|
-
|
|
1518
|
-
|
|
1522
|
+
// Only reuse the existing preview when it is still attached to the chat container;
|
|
1523
|
+
// a collapsed/disposed container must be re-added, not mutated in place.
|
|
1524
|
+
const stillAttached = existingContainer !== undefined && this.chatContainer.children.includes(existingContainer);
|
|
1525
|
+
const replaceExisting = options?.append !== true && stillAttached;
|
|
1526
|
+
const planReviewContainer = replaceExisting && existingContainer ? existingContainer : new Container();
|
|
1519
1527
|
planReviewContainer.clear();
|
|
1520
1528
|
planReviewContainer.addChild(new Spacer(1));
|
|
1521
1529
|
planReviewContainer.addChild(new DynamicBorder());
|
|
@@ -1524,7 +1532,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
1524
1532
|
planReviewContainer.addChild(new Markdown(planContent, 1, 1, getMarkdownTheme()));
|
|
1525
1533
|
planReviewContainer.addChild(new DynamicBorder());
|
|
1526
1534
|
if (!replaceExisting) {
|
|
1527
|
-
this
|
|
1535
|
+
addChatChild(this, planReviewContainer);
|
|
1528
1536
|
}
|
|
1529
1537
|
this.#planReviewContainer = planReviewContainer;
|
|
1530
1538
|
this.ui.requestRender();
|
|
@@ -8,7 +8,8 @@ import type { CompactionResult } from "@gajae-code/agent-core/compaction";
|
|
|
8
8
|
import type { ImageContent, Model } from "@gajae-code/ai";
|
|
9
9
|
import { isRecord, ptree, readJsonl } from "@gajae-code/utils";
|
|
10
10
|
import type { BashResult } from "../../exec/bash-executor";
|
|
11
|
-
import type { SessionStats } from "../../session/agent-session";
|
|
11
|
+
import type { AgentSessionEvent, SessionStats } from "../../session/agent-session";
|
|
12
|
+
import { AGENT_WIRE_EVENT_TYPES, type AgentWireEventType } from "../shared/agent-wire/event-contract";
|
|
12
13
|
import type {
|
|
13
14
|
RpcCommand,
|
|
14
15
|
RpcExtensionUIRequest,
|
|
@@ -64,6 +65,7 @@ export interface RpcClientOptions {
|
|
|
64
65
|
export type ModelInfo = Pick<Model, "provider" | "id" | "contextWindow" | "reasoning" | "thinking">;
|
|
65
66
|
|
|
66
67
|
export type RpcEventListener = (event: AgentEvent) => void;
|
|
68
|
+
export type RpcSessionEventListener = (event: AgentSessionEvent) => void;
|
|
67
69
|
|
|
68
70
|
export interface RpcClientToolContext<TDetails = unknown> {
|
|
69
71
|
toolCallId: string;
|
|
@@ -91,7 +93,8 @@ export function defineRpcClientTool<
|
|
|
91
93
|
return tool;
|
|
92
94
|
}
|
|
93
95
|
|
|
94
|
-
const
|
|
96
|
+
const agentWireEventTypes = new Set<AgentWireEventType>(AGENT_WIRE_EVENT_TYPES);
|
|
97
|
+
const coreAgentEventTypes = new Set<AgentEvent["type"]>([
|
|
95
98
|
"agent_start",
|
|
96
99
|
"agent_end",
|
|
97
100
|
"turn_start",
|
|
@@ -116,13 +119,16 @@ function isRpcResponse(value: unknown): value is RpcResponse {
|
|
|
116
119
|
return true;
|
|
117
120
|
}
|
|
118
121
|
|
|
119
|
-
function
|
|
122
|
+
function isAgentSessionEvent(value: unknown): value is AgentSessionEvent {
|
|
120
123
|
if (!isRecord(value)) return false;
|
|
121
124
|
const type = value.type;
|
|
122
125
|
if (typeof type !== "string") return false;
|
|
123
|
-
return
|
|
126
|
+
return agentWireEventTypes.has(type as AgentWireEventType);
|
|
124
127
|
}
|
|
125
128
|
|
|
129
|
+
function isCoreAgentEvent(value: AgentSessionEvent): value is AgentEvent {
|
|
130
|
+
return coreAgentEventTypes.has(value.type as AgentEvent["type"]);
|
|
131
|
+
}
|
|
126
132
|
function isRpcHostToolCallRequest(value: unknown): value is RpcHostToolCallRequest {
|
|
127
133
|
if (!isRecord(value)) return false;
|
|
128
134
|
return (
|
|
@@ -378,6 +384,7 @@ export class RpcClient {
|
|
|
378
384
|
#transport: RpcTransport | null = null;
|
|
379
385
|
#transportClosed = false;
|
|
380
386
|
#eventListeners: RpcEventListener[] = [];
|
|
387
|
+
#sessionEventListeners: RpcSessionEventListener[] = [];
|
|
381
388
|
#pendingRequests: Map<string, { resolve: (response: RpcResponse) => void; reject: (error: Error) => void }> =
|
|
382
389
|
new Map();
|
|
383
390
|
#customTools: RpcClientCustomTool[] = [];
|
|
@@ -442,7 +449,7 @@ export class RpcClient {
|
|
|
442
449
|
}
|
|
443
450
|
|
|
444
451
|
/**
|
|
445
|
-
* Subscribe to
|
|
452
|
+
* Subscribe to the legacy core AgentEvent subset.
|
|
446
453
|
*/
|
|
447
454
|
onEvent(listener: RpcEventListener): () => void {
|
|
448
455
|
this.#eventListeners.push(listener);
|
|
@@ -454,6 +461,19 @@ export class RpcClient {
|
|
|
454
461
|
};
|
|
455
462
|
}
|
|
456
463
|
|
|
464
|
+
/**
|
|
465
|
+
* Subscribe to all renderer-facing agent session events.
|
|
466
|
+
*/
|
|
467
|
+
onSessionEvent(listener: RpcSessionEventListener): () => void {
|
|
468
|
+
this.#sessionEventListeners.push(listener);
|
|
469
|
+
return () => {
|
|
470
|
+
const index = this.#sessionEventListeners.indexOf(listener);
|
|
471
|
+
if (index !== -1) {
|
|
472
|
+
this.#sessionEventListeners.splice(index, 1);
|
|
473
|
+
}
|
|
474
|
+
};
|
|
475
|
+
}
|
|
476
|
+
|
|
457
477
|
/**
|
|
458
478
|
* Subscribe to workflow lifecycle gates emitted by RPC mode.
|
|
459
479
|
*/
|
|
@@ -928,8 +948,12 @@ export class RpcClient {
|
|
|
928
948
|
|
|
929
949
|
// Canonical agent-wire event frame: { type:"event", payload:{ event_type, event } }.
|
|
930
950
|
const event = unwrapAgentWireEventFrame(data);
|
|
931
|
-
if (!
|
|
951
|
+
if (!isAgentSessionEvent(event)) return;
|
|
932
952
|
|
|
953
|
+
for (const listener of this.#sessionEventListeners) {
|
|
954
|
+
listener(event);
|
|
955
|
+
}
|
|
956
|
+
if (!isCoreAgentEvent(event)) return;
|
|
933
957
|
for (const listener of this.#eventListeners) {
|
|
934
958
|
listener(event);
|
|
935
959
|
}
|