@gajae-code/coding-agent 0.9.6 → 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 +45 -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/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 +14 -14
- 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
|
@@ -81,9 +81,11 @@ import type { BashExecutionComponent } from "./components/bash-execution";
|
|
|
81
81
|
import { CustomEditor } from "./components/custom-editor";
|
|
82
82
|
import { DynamicBorder } from "./components/dynamic-border";
|
|
83
83
|
import type { EvalExecutionComponent } from "./components/eval-execution";
|
|
84
|
+
import { GajaePetWidget, type PetMode } from "./components/gajae-pet-widget";
|
|
84
85
|
import type { HookEditorComponent } from "./components/hook-editor";
|
|
85
86
|
import type { HookInputComponent } from "./components/hook-input";
|
|
86
87
|
import type { HookSelectorComponent } from "./components/hook-selector";
|
|
88
|
+
import { IrcSplitViewComponent } from "./components/irc-sidebar";
|
|
87
89
|
import { StatusLineComponent } from "./components/status-line";
|
|
88
90
|
import type { ToolExecutionHandle } from "./components/tool-execution";
|
|
89
91
|
import {
|
|
@@ -99,6 +101,7 @@ import { InputController } from "./controllers/input-controller";
|
|
|
99
101
|
import { SelectorController } from "./controllers/selector-controller";
|
|
100
102
|
import { SSHCommandController } from "./controllers/ssh-command-controller";
|
|
101
103
|
import { TodoCommandController } from "./controllers/todo-command-controller";
|
|
104
|
+
import { IrcObservationLedger } from "./irc-observation-ledger";
|
|
102
105
|
import { JobsObserver } from "./jobs-observer";
|
|
103
106
|
import { OAuthManualInputManager } from "./oauth-manual-input";
|
|
104
107
|
import { SessionObserverRegistry } from "./session-observer-registry";
|
|
@@ -114,13 +117,42 @@ import {
|
|
|
114
117
|
onThemeChange,
|
|
115
118
|
theme,
|
|
116
119
|
} from "./theme/theme";
|
|
117
|
-
import type {
|
|
118
|
-
|
|
120
|
+
import type {
|
|
121
|
+
CompactionQueuedMessage,
|
|
122
|
+
InteractiveModeContext,
|
|
123
|
+
IrcArrivalSnapshot,
|
|
124
|
+
SubmittedUserInput,
|
|
125
|
+
TodoItem,
|
|
126
|
+
TodoPhase,
|
|
127
|
+
TranscriptRebuildPolicy,
|
|
128
|
+
} from "./types";
|
|
129
|
+
import type { ParsedIrcMessage } from "./utils/irc-message";
|
|
130
|
+
import { addChatChild, prepareTranscriptRebuild, UiHelpers } from "./utils/ui-helpers";
|
|
119
131
|
|
|
120
132
|
const INTERACTIVE_ABORT_CLEANUP_TIMEOUT_MS = 5_000;
|
|
121
133
|
const COMPOSER_NEWLINE_HINT = process.platform === "win32" ? "Alt+Enter/Ctrl+J" : "Shift+Enter/Ctrl+J";
|
|
122
134
|
export const DEFAULT_COMPOSER_PLACEHOLDER = `Type your message... ${COMPOSER_NEWLINE_HINT}: New line · Ctrl+C: Clear · Ctrl+R: Search history · Shift+Tab: Reasoning`;
|
|
123
135
|
const WELCOME_RESERVED_CONTAINER_CHILD_LIMIT = 8;
|
|
136
|
+
|
|
137
|
+
const IRC_SIDEBAR_TOGGLE_SHADOWING_ACTIONS: readonly AppKeybinding[] = [
|
|
138
|
+
"app.plan.toggle",
|
|
139
|
+
"app.session.new",
|
|
140
|
+
"app.session.tree",
|
|
141
|
+
"app.session.fork",
|
|
142
|
+
"app.session.resume",
|
|
143
|
+
"app.message.followUp",
|
|
144
|
+
"app.stt.toggle",
|
|
145
|
+
"app.clipboard.copyLine",
|
|
146
|
+
"app.session.observe",
|
|
147
|
+
"app.jobs.open",
|
|
148
|
+
"app.tool.backgroundFold",
|
|
149
|
+
];
|
|
150
|
+
|
|
151
|
+
export function getWelcomeTranscriptReservedRows(chatContainer: Container, width: number): number {
|
|
152
|
+
return chatContainer.children.length === 0 || chatContainer.children.length > WELCOME_RESERVED_CONTAINER_CHILD_LIMIT
|
|
153
|
+
? 0
|
|
154
|
+
: chatContainer.render(width).length;
|
|
155
|
+
}
|
|
124
156
|
const FRIENDLY_KEY_PARTS: Record<string, string> = {
|
|
125
157
|
alt: "Alt",
|
|
126
158
|
cmd: "Cmd",
|
|
@@ -280,6 +312,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
280
312
|
keybindings: KeybindingsManager;
|
|
281
313
|
agent: Agent;
|
|
282
314
|
historyStorage?: HistoryStorage;
|
|
315
|
+
readonly ircLedger = new IrcObservationLedger();
|
|
283
316
|
|
|
284
317
|
ui: TUI;
|
|
285
318
|
chatContainer: Container;
|
|
@@ -291,6 +324,8 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
291
324
|
editorContainer: Container;
|
|
292
325
|
hookWidgetContainerAbove: Container;
|
|
293
326
|
hookWidgetContainerBelow: Container;
|
|
327
|
+
petFloorContainer: Container = new Container();
|
|
328
|
+
petWidget: GajaePetWidget | undefined;
|
|
294
329
|
statusLine: StatusLineComponent;
|
|
295
330
|
|
|
296
331
|
isInitialized = false;
|
|
@@ -387,6 +422,9 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
387
422
|
#eventBus?: EventBus;
|
|
388
423
|
#eventBusUnsubscribers: Array<() => void> = [];
|
|
389
424
|
#welcomeComponent?: WelcomeComponent;
|
|
425
|
+
#ircSplitView: IrcSplitViewComponent;
|
|
426
|
+
#ircSidebarAvailable = false;
|
|
427
|
+
#ircSidebarRequestedVisible = false;
|
|
390
428
|
|
|
391
429
|
constructor(
|
|
392
430
|
session: AgentSession,
|
|
@@ -423,6 +461,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
423
461
|
this.ui = new TUI(new ProcessTerminal(), settings.get("showHardwareCursor"));
|
|
424
462
|
this.ui.setClearOnShrink(settings.get("clearOnShrink"));
|
|
425
463
|
this.chatContainer = new Container();
|
|
464
|
+
this.#ircSplitView = new IrcSplitViewComponent(this.chatContainer, this.ircLedger, () => theme);
|
|
426
465
|
this.pendingMessagesContainer = new Container();
|
|
427
466
|
this.statusContainer = new Container();
|
|
428
467
|
this.todoContainer = new Container();
|
|
@@ -442,6 +481,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
442
481
|
this.#syncEditorMaxHeight();
|
|
443
482
|
this.updateEditorChrome();
|
|
444
483
|
this.editor.invalidate();
|
|
484
|
+
this.#invalidateIrcSidebarRender();
|
|
445
485
|
this.ui.requestResizeRender();
|
|
446
486
|
};
|
|
447
487
|
process.stdout.on("resize", this.#resizeHandler);
|
|
@@ -561,7 +601,9 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
561
601
|
this.#welcomeComponent.playIntro(() => this.ui.requestRender());
|
|
562
602
|
}
|
|
563
603
|
|
|
564
|
-
this.ui.addChild(this
|
|
604
|
+
this.ui.addChild(this.#ircSplitView);
|
|
605
|
+
this.ui.setViewportAnchorComponent(this.#ircSplitView);
|
|
606
|
+
|
|
565
607
|
this.ui.addChild(this.pendingMessagesContainer);
|
|
566
608
|
this.ui.addChild(this.statusContainer);
|
|
567
609
|
this.ui.addChild(this.todoContainer);
|
|
@@ -569,9 +611,12 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
569
611
|
this.ui.addChild(this.statusLine); // Main status rail + hook statuses; composer chrome is rendered by the editor.
|
|
570
612
|
this.ui.addChild(this.hookWidgetContainerAbove);
|
|
571
613
|
this.ui.addChild(this.editorContainer);
|
|
614
|
+
this.ui.addChild(this.petFloorContainer);
|
|
572
615
|
this.ui.addChild(this.hookWidgetContainerBelow);
|
|
573
616
|
this.ui.setBottomPinnedComponent(this.statusLine);
|
|
574
617
|
this.ui.setFocus(this.editor);
|
|
618
|
+
this.petWidget = this.#createPetWidget(this.editor);
|
|
619
|
+
this.petWidget.setMode(settings.get("pet.mode"));
|
|
575
620
|
|
|
576
621
|
this.#inputController.setupKeyHandlers();
|
|
577
622
|
this.#inputController.setupEditorSubmitHandler();
|
|
@@ -609,6 +654,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
609
654
|
this.updateEditorChrome();
|
|
610
655
|
this.#syncEditorMaxHeight();
|
|
611
656
|
this.isInitialized = true;
|
|
657
|
+
this.#syncIrcSidebarAvailabilityFromSettings();
|
|
612
658
|
this.ui.requestRender(true);
|
|
613
659
|
|
|
614
660
|
// GitHub star reminder (interactive-only). Register the decline-driven
|
|
@@ -874,7 +920,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
874
920
|
}
|
|
875
921
|
if (!submission.customType) {
|
|
876
922
|
this.pendingImages = submission.images ? [...submission.images] : [];
|
|
877
|
-
this.rebuildChatFromMessages();
|
|
923
|
+
this.rebuildChatFromMessages("reconcile-same-transcript");
|
|
878
924
|
this.editor.setText(submission.text);
|
|
879
925
|
}
|
|
880
926
|
this.updateEditorChrome();
|
|
@@ -950,8 +996,9 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
950
996
|
}
|
|
951
997
|
|
|
952
998
|
#getWelcomeReservedRows(width: number): number {
|
|
999
|
+
const transcriptRows = getWelcomeTranscriptReservedRows(this.chatContainer, width);
|
|
1000
|
+
|
|
953
1001
|
const transientRows = [
|
|
954
|
-
this.chatContainer,
|
|
955
1002
|
this.pendingMessagesContainer,
|
|
956
1003
|
this.statusContainer,
|
|
957
1004
|
this.todoContainer,
|
|
@@ -965,7 +1012,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
965
1012
|
this.hookWidgetContainerBelow,
|
|
966
1013
|
].reduce((rows, component) => rows + component.render(width).length, 0);
|
|
967
1014
|
|
|
968
|
-
return transientRows + pinnedRows;
|
|
1015
|
+
return transcriptRows + transientRows + pinnedRows;
|
|
969
1016
|
}
|
|
970
1017
|
|
|
971
1018
|
#renderShortContainerRowsForWelcomeReservation(width: number, container: Container): number {
|
|
@@ -1017,7 +1064,42 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
1017
1064
|
this.editor.setTopBorder(undefined);
|
|
1018
1065
|
}
|
|
1019
1066
|
|
|
1020
|
-
|
|
1067
|
+
setPetMode(mode: PetMode): void {
|
|
1068
|
+
this.petWidget?.setMode(mode);
|
|
1069
|
+
settings.set("pet.mode", mode);
|
|
1070
|
+
this.ui.requestRender();
|
|
1071
|
+
}
|
|
1072
|
+
|
|
1073
|
+
previewPetMode(mode: PetMode): void {
|
|
1074
|
+
this.petWidget?.previewMode(mode);
|
|
1075
|
+
this.ui.requestRender();
|
|
1076
|
+
}
|
|
1077
|
+
|
|
1078
|
+
restoreComposer(): void {
|
|
1079
|
+
if (this.petWidget) {
|
|
1080
|
+
this.petWidget.remountComposer();
|
|
1081
|
+
} else {
|
|
1082
|
+
this.editorContainer.clear();
|
|
1083
|
+
this.editorContainer.addChild(this.editor);
|
|
1084
|
+
}
|
|
1085
|
+
this.ui.setFocus(this.editor);
|
|
1086
|
+
}
|
|
1087
|
+
|
|
1088
|
+
#createPetWidget(editor: CustomEditor): GajaePetWidget {
|
|
1089
|
+
return new GajaePetWidget({
|
|
1090
|
+
ui: this.ui,
|
|
1091
|
+
editor,
|
|
1092
|
+
editorContainer: this.editorContainer,
|
|
1093
|
+
floorContainer: this.petFloorContainer,
|
|
1094
|
+
isWorking: () => this.loadingAnimation !== undefined,
|
|
1095
|
+
getComposerBottomOffset: () =>
|
|
1096
|
+
this.petFloorContainer.render(this.ui.terminal.columns).length +
|
|
1097
|
+
this.hookWidgetContainerBelow.render(this.ui.terminal.columns).length,
|
|
1098
|
+
});
|
|
1099
|
+
}
|
|
1100
|
+
|
|
1101
|
+
rebuildChatFromMessages(policy: TranscriptRebuildPolicy): void {
|
|
1102
|
+
prepareTranscriptRebuild(this.ui, policy);
|
|
1021
1103
|
this.chatContainer.clear();
|
|
1022
1104
|
const context = this.session.buildDisplaySessionContext();
|
|
1023
1105
|
this.renderSessionContext(context);
|
|
@@ -1992,6 +2074,8 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
1992
2074
|
}
|
|
1993
2075
|
|
|
1994
2076
|
stop(): void {
|
|
2077
|
+
this.petWidget?.dispose();
|
|
2078
|
+
this.petWidget = undefined;
|
|
1995
2079
|
if (this.loadingAnimation) {
|
|
1996
2080
|
this.loadingAnimation.stop();
|
|
1997
2081
|
this.loadingAnimation = undefined;
|
|
@@ -2123,11 +2207,17 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
2123
2207
|
nextEditor.setText(previousText);
|
|
2124
2208
|
previousEditor.dispose();
|
|
2125
2209
|
|
|
2210
|
+
const petMode = this.petWidget?.mode ?? settings.get("pet.mode");
|
|
2211
|
+
this.petWidget?.dispose();
|
|
2212
|
+
|
|
2126
2213
|
this.editorContainer.clear();
|
|
2127
2214
|
this.editor = nextEditor;
|
|
2128
2215
|
this.editorContainer.addChild(nextEditor);
|
|
2129
2216
|
this.ui.setFocus(nextEditor);
|
|
2130
2217
|
|
|
2218
|
+
this.petWidget = this.#createPetWidget(nextEditor);
|
|
2219
|
+
this.petWidget.setMode(petMode);
|
|
2220
|
+
|
|
2131
2221
|
this.#inputController.setupKeyHandlers();
|
|
2132
2222
|
this.#inputController.setupEditorSubmitHandler();
|
|
2133
2223
|
|
|
@@ -2284,13 +2374,32 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
2284
2374
|
return this.#uiHelpers.addMessageToChat(message, options);
|
|
2285
2375
|
}
|
|
2286
2376
|
|
|
2377
|
+
addLiveIrcObservationToChat(message: ParsedIrcMessage, arrival: IrcArrivalSnapshot): Component[] {
|
|
2378
|
+
return this.#uiHelpers.addLiveIrcObservationToChat(message, arrival);
|
|
2379
|
+
}
|
|
2380
|
+
removeRenderedIrcInlineComponents(observationId: string): readonly Component[] | undefined {
|
|
2381
|
+
return this.#uiHelpers.removeRenderedIrcInlineComponents(observationId);
|
|
2382
|
+
}
|
|
2383
|
+
resetRenderedIrcInlineComponents(): readonly (readonly Component[])[] {
|
|
2384
|
+
return this.#uiHelpers.resetRenderedIrcInlineComponents();
|
|
2385
|
+
}
|
|
2386
|
+
|
|
2287
2387
|
renderSessionContext(
|
|
2288
2388
|
sessionContext: SessionContext,
|
|
2289
2389
|
options?: { updateFooter?: boolean; populateHistory?: boolean },
|
|
2290
2390
|
): void {
|
|
2291
2391
|
this.#uiHelpers.renderSessionContext(sessionContext, options);
|
|
2392
|
+
this.#eventController.reconcileIrcExpiryTimers(this.#uiHelpers.getRenderedIrcInlineComponents());
|
|
2292
2393
|
}
|
|
2293
2394
|
|
|
2395
|
+
rebuildInitialMessages(
|
|
2396
|
+
policy: TranscriptRebuildPolicy,
|
|
2397
|
+
prebuiltContext?: SessionContext,
|
|
2398
|
+
options?: { preserveExistingChat?: boolean },
|
|
2399
|
+
): void {
|
|
2400
|
+
prepareTranscriptRebuild(this.ui, policy);
|
|
2401
|
+
this.#uiHelpers.renderInitialMessages(prebuiltContext, options);
|
|
2402
|
+
}
|
|
2294
2403
|
renderInitialMessages(prebuiltContext?: SessionContext, options?: { preserveExistingChat?: boolean }): void {
|
|
2295
2404
|
this.#uiHelpers.renderInitialMessages(prebuiltContext, options);
|
|
2296
2405
|
}
|
|
@@ -2303,6 +2412,10 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
2303
2412
|
return this.#uiHelpers.findLastAssistantMessage();
|
|
2304
2413
|
}
|
|
2305
2414
|
|
|
2415
|
+
getAssistantViewportAnchorId(message: AssistantMessage): string {
|
|
2416
|
+
return this.#uiHelpers.assistantViewportAnchorId(message);
|
|
2417
|
+
}
|
|
2418
|
+
|
|
2306
2419
|
extractAssistantText(message: AssistantMessage): string {
|
|
2307
2420
|
return this.#uiHelpers.extractAssistantText(message);
|
|
2308
2421
|
}
|
|
@@ -2553,6 +2666,10 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
2553
2666
|
this.#selectorController.showThemeSelector();
|
|
2554
2667
|
}
|
|
2555
2668
|
|
|
2669
|
+
showPetSelector(): void {
|
|
2670
|
+
this.#selectorController.showPetSelector();
|
|
2671
|
+
}
|
|
2672
|
+
|
|
2556
2673
|
showHistorySearch(): void {
|
|
2557
2674
|
this.#selectorController.showHistorySearch();
|
|
2558
2675
|
}
|
|
@@ -2668,6 +2785,65 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
2668
2785
|
this.#inputController.setToolsExpanded(expanded);
|
|
2669
2786
|
}
|
|
2670
2787
|
|
|
2788
|
+
#resolveEffectiveIrcSidebarToggleKey(): string | null {
|
|
2789
|
+
for (const key of this.keybindings.getKeys("app.irc.sidebar.toggle")) {
|
|
2790
|
+
if (this.editor.hasActionKey(key)) continue;
|
|
2791
|
+
const shadowed = IRC_SIDEBAR_TOGGLE_SHADOWING_ACTIONS.some(action =>
|
|
2792
|
+
this.keybindings.getKeys(action).includes(key),
|
|
2793
|
+
);
|
|
2794
|
+
if (!shadowed) return key;
|
|
2795
|
+
}
|
|
2796
|
+
return null;
|
|
2797
|
+
}
|
|
2798
|
+
|
|
2799
|
+
captureIrcArrivalSnapshot(): IrcArrivalSnapshot {
|
|
2800
|
+
return {
|
|
2801
|
+
panelVisible: this.#ircSplitView.effectiveSidebarVisible(this.ui.terminal.columns),
|
|
2802
|
+
panelRequestedVisible: this.#ircSidebarRequestedVisible,
|
|
2803
|
+
sidebarAvailable: this.#ircSidebarAvailable,
|
|
2804
|
+
resolvedToggleKey: this.#resolveEffectiveIrcSidebarToggleKey(),
|
|
2805
|
+
};
|
|
2806
|
+
}
|
|
2807
|
+
toggleIrcSidebar(): void {
|
|
2808
|
+
if (
|
|
2809
|
+
!this.#ircSidebarAvailable ||
|
|
2810
|
+
this.settings.get("irc.enabled") !== true ||
|
|
2811
|
+
this.settings.get("irc.sidebar.enabled") !== true
|
|
2812
|
+
)
|
|
2813
|
+
return;
|
|
2814
|
+
this.#ircSidebarRequestedVisible = !this.#ircSidebarRequestedVisible;
|
|
2815
|
+
this.#ircSplitView.setVisible(this.#ircSidebarRequestedVisible);
|
|
2816
|
+
this.#invalidateIrcSidebarRender();
|
|
2817
|
+
this.ui.requestRender();
|
|
2818
|
+
}
|
|
2819
|
+
|
|
2820
|
+
applyIrcSidebarAvailability(enabled: boolean): void {
|
|
2821
|
+
this.#ircSidebarAvailable = enabled;
|
|
2822
|
+
this.#ircSplitView.setVisible(enabled && this.#ircSidebarRequestedVisible);
|
|
2823
|
+
this.#invalidateIrcSidebarRender();
|
|
2824
|
+
this.ui.requestRender();
|
|
2825
|
+
}
|
|
2826
|
+
|
|
2827
|
+
#syncIrcSidebarAvailabilityFromSettings(): void {
|
|
2828
|
+
this.applyIrcSidebarAvailability(
|
|
2829
|
+
this.settings.get("irc.enabled") === true && this.settings.get("irc.sidebar.enabled") === true,
|
|
2830
|
+
);
|
|
2831
|
+
}
|
|
2832
|
+
|
|
2833
|
+
resetIrcSidebarSession(): void {
|
|
2834
|
+
this.ircLedger.reset();
|
|
2835
|
+
this.#eventController.resetIrcObservations();
|
|
2836
|
+
this.#ircSidebarRequestedVisible = false;
|
|
2837
|
+
this.#ircSplitView.setVisible(false);
|
|
2838
|
+
this.#uiHelpers.resetIrcSidebarHint();
|
|
2839
|
+
this.#syncIrcSidebarAvailabilityFromSettings();
|
|
2840
|
+
}
|
|
2841
|
+
|
|
2842
|
+
#invalidateIrcSidebarRender(): void {
|
|
2843
|
+
clearRenderCache();
|
|
2844
|
+
this.#ircSplitView.invalidate();
|
|
2845
|
+
}
|
|
2846
|
+
|
|
2671
2847
|
toggleThinkingBlockVisibility(): void {
|
|
2672
2848
|
this.#inputController.toggleThinkingBlockVisibility();
|
|
2673
2849
|
}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import * as crypto from "node:crypto";
|
|
2
|
+
|
|
3
|
+
import type { ParsedIrcMessage } from "./utils/irc-message";
|
|
4
|
+
|
|
5
|
+
type InlineMode = "persistent" | "ephemeral";
|
|
6
|
+
|
|
7
|
+
const MAX_RECORDS = 10_000;
|
|
8
|
+
const MAX_RETAINED_UTF8_BYTES = 16 * 1024 * 1024;
|
|
9
|
+
const MAX_SEEN_IDENTITIES = 100_000;
|
|
10
|
+
|
|
11
|
+
export type IrcObservationRecord = Readonly<
|
|
12
|
+
ParsedIrcMessage & {
|
|
13
|
+
mode: InlineMode;
|
|
14
|
+
observedAt: number;
|
|
15
|
+
sequence: number;
|
|
16
|
+
expiresAt?: number;
|
|
17
|
+
retainedUtf8Bytes: number;
|
|
18
|
+
}
|
|
19
|
+
>;
|
|
20
|
+
|
|
21
|
+
function measureRetainedUtf8Bytes(message: ParsedIrcMessage): number {
|
|
22
|
+
let bytes = 4;
|
|
23
|
+
for (const value of [message.observationId, message.from, message.to, message.text, message.kind]) {
|
|
24
|
+
bytes += Buffer.byteLength(value, "utf8");
|
|
25
|
+
if (bytes > MAX_RETAINED_UTF8_BYTES) return bytes;
|
|
26
|
+
}
|
|
27
|
+
return bytes;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function tombstoneIdentity(observationId: string): string {
|
|
31
|
+
const hash = crypto.createHash("sha256");
|
|
32
|
+
hash.update("gjc:irc:observation-id:utf16le:v1\0");
|
|
33
|
+
for (let offset = 0; offset < observationId.length; offset += 4_096) {
|
|
34
|
+
hash.update(Buffer.from(observationId.slice(offset, offset + 4_096), "utf16le"));
|
|
35
|
+
}
|
|
36
|
+
return hash.digest("hex");
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** Runtime-only IRC observations. This intentionally has no persistence layer. */
|
|
40
|
+
export class IrcObservationLedger {
|
|
41
|
+
#records = new Map<string, IrcObservationRecord>();
|
|
42
|
+
#retainedUtf8Bytes = 0;
|
|
43
|
+
#nextSequence = 0;
|
|
44
|
+
#seenObservationIdentities = new Set<string>();
|
|
45
|
+
#identityCapacityExhausted = false;
|
|
46
|
+
#evictedObservationIds = new Set<string>();
|
|
47
|
+
|
|
48
|
+
#rememberObservationIdentity(observationId: string): boolean {
|
|
49
|
+
const identity = tombstoneIdentity(observationId);
|
|
50
|
+
if (this.#seenObservationIdentities.has(identity) || this.#identityCapacityExhausted) return false;
|
|
51
|
+
if (this.#seenObservationIdentities.size >= MAX_SEEN_IDENTITIES) {
|
|
52
|
+
this.#identityCapacityExhausted = true;
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
this.#seenObservationIdentities.add(identity);
|
|
56
|
+
return true;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
#evict(observationId: string): void {
|
|
60
|
+
const record = this.#records.get(observationId);
|
|
61
|
+
if (!record) return;
|
|
62
|
+
this.#records.delete(observationId);
|
|
63
|
+
this.#retainedUtf8Bytes -= record.retainedUtf8Bytes;
|
|
64
|
+
this.#evictedObservationIds.add(observationId);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
#enforceBounds(): void {
|
|
68
|
+
while (this.#records.size > MAX_RECORDS || this.#retainedUtf8Bytes > MAX_RETAINED_UTF8_BYTES) {
|
|
69
|
+
const oldestObservationId = this.#records.keys().next().value;
|
|
70
|
+
if (oldestObservationId === undefined) return;
|
|
71
|
+
this.#evict(oldestObservationId);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** Inline observations expire after 10 seconds; closed-panel observations persist inline. */
|
|
76
|
+
observe(message: ParsedIrcMessage, panelVisibleAtObservation: boolean): IrcObservationRecord | undefined {
|
|
77
|
+
const existing = this.#records.get(message.observationId);
|
|
78
|
+
if (existing) return existing;
|
|
79
|
+
if (!this.#rememberObservationIdentity(message.observationId)) return undefined;
|
|
80
|
+
|
|
81
|
+
const estimatedBytes = measureRetainedUtf8Bytes(message);
|
|
82
|
+
if (estimatedBytes > MAX_RETAINED_UTF8_BYTES) return undefined;
|
|
83
|
+
|
|
84
|
+
const observedAt = Date.now();
|
|
85
|
+
const mode: InlineMode = panelVisibleAtObservation ? "ephemeral" : "persistent";
|
|
86
|
+
const record: IrcObservationRecord = Object.freeze({
|
|
87
|
+
...message,
|
|
88
|
+
mode,
|
|
89
|
+
observedAt,
|
|
90
|
+
sequence: this.#nextSequence++,
|
|
91
|
+
...(mode === "ephemeral" ? { expiresAt: observedAt + 10_000 } : {}),
|
|
92
|
+
retainedUtf8Bytes: estimatedBytes,
|
|
93
|
+
});
|
|
94
|
+
this.#records.set(record.observationId, record);
|
|
95
|
+
this.#retainedUtf8Bytes += estimatedBytes;
|
|
96
|
+
this.#enforceBounds();
|
|
97
|
+
return this.#records.get(record.observationId);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
getRecord(observationId: string): IrcObservationRecord | undefined {
|
|
101
|
+
return this.#records.get(observationId);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
getSidebarRecords(): readonly IrcObservationRecord[] {
|
|
105
|
+
return [...this.#records.values()];
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
getInlineProjection(now: number): readonly IrcObservationRecord[] {
|
|
109
|
+
return [...this.#records.values()].filter(record => record.mode === "persistent" || now < record.expiresAt!);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/** Returns and clears IDs whose retained payload was released. */
|
|
113
|
+
drainEvictedObservationIds(): readonly string[] {
|
|
114
|
+
const observationIds = [...this.#evictedObservationIds];
|
|
115
|
+
this.#evictedObservationIds.clear();
|
|
116
|
+
return observationIds;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
reset(): void {
|
|
120
|
+
for (const observationId of this.#records.keys()) {
|
|
121
|
+
this.#evictedObservationIds.add(observationId);
|
|
122
|
+
}
|
|
123
|
+
this.#records.clear();
|
|
124
|
+
this.#retainedUtf8Bytes = 0;
|
|
125
|
+
this.#nextSequence = 0;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
@@ -44,6 +44,8 @@ export interface AskGateQuestion {
|
|
|
44
44
|
deepInterview?: AskGateDeepInterviewState;
|
|
45
45
|
/** Override the emitted workflow gate address for non-deep-interview ask prompts. */
|
|
46
46
|
workflowGate?: AskGateWorkflowGateMeta;
|
|
47
|
+
allowEmpty?: boolean;
|
|
48
|
+
navigationLabel?: "Next" | "Done";
|
|
47
49
|
}
|
|
48
50
|
|
|
49
51
|
export interface AskGateResult {
|
|
@@ -119,7 +121,7 @@ function questionAnswerSchema(question: AskGateQuestion, labels: string[]): RpcJ
|
|
|
119
121
|
const selectedBase: RpcJsonSchema = { type: "array", items: selectedItems, uniqueItems: true };
|
|
120
122
|
const selectedOnly: RpcJsonSchema = {
|
|
121
123
|
...selectedBase,
|
|
122
|
-
minItems: 1,
|
|
124
|
+
minItems: question.allowEmpty ? 0 : 1,
|
|
123
125
|
...(multi ? {} : { maxItems: 1 }),
|
|
124
126
|
};
|
|
125
127
|
const selectedWithOther: RpcJsonSchema = {
|
|
@@ -216,6 +218,8 @@ export function questionToGate(question: AskGateQuestion): OpenGateInput {
|
|
|
216
218
|
options: labels,
|
|
217
219
|
other_option: GATE_OTHER_OPTION,
|
|
218
220
|
clarification_action: "clarify",
|
|
221
|
+
allow_empty: question.allowEmpty === true,
|
|
222
|
+
navigation_label: question.navigationLabel,
|
|
219
223
|
...(question.deepInterview
|
|
220
224
|
? structuredDeepInterviewState(question.deepInterview)
|
|
221
225
|
: deepInterviewQuestionState(question.question)),
|
|
@@ -280,12 +284,21 @@ export function gateAnswerToResult(question: AskGateQuestion, answer: unknown):
|
|
|
280
284
|
}
|
|
281
285
|
const other = answer.other === true;
|
|
282
286
|
const totalPicks = deduped.length + (other ? 1 : 0);
|
|
283
|
-
if (totalPicks === 0) {
|
|
287
|
+
if (totalPicks === 0 && !question.allowEmpty) {
|
|
284
288
|
throw new DeepInterviewGateError(
|
|
285
289
|
"empty_selection",
|
|
286
290
|
"at least one option (or the free-text other) must be selected",
|
|
287
291
|
);
|
|
288
292
|
}
|
|
293
|
+
if (totalPicks === 0) {
|
|
294
|
+
return {
|
|
295
|
+
id: question.id,
|
|
296
|
+
question: question.question,
|
|
297
|
+
options: labels,
|
|
298
|
+
multi,
|
|
299
|
+
selectedOptions: [],
|
|
300
|
+
};
|
|
301
|
+
}
|
|
289
302
|
if (!multi && totalPicks > 1) {
|
|
290
303
|
throw new DeepInterviewGateError("multi_not_allowed", "this question accepts a single selection");
|
|
291
304
|
}
|
|
@@ -302,6 +315,36 @@ export function gateAnswerToResult(question: AskGateQuestion, answer: unknown):
|
|
|
302
315
|
};
|
|
303
316
|
}
|
|
304
317
|
|
|
318
|
+
/**
|
|
319
|
+
* Classify an accepted ask-domain gate answer independently of its transport.
|
|
320
|
+
* Schema-invalid and semantically invalid answers throw, so callers leave the
|
|
321
|
+
* durable gate pending rather than treating them as a non-committing answer.
|
|
322
|
+
*/
|
|
323
|
+
export function classifyAskGateDisposition(
|
|
324
|
+
gate: Pick<OpenGateInput, "context" | "options">,
|
|
325
|
+
answer: unknown,
|
|
326
|
+
): "commit" | "resolve_without_commit" {
|
|
327
|
+
const state = gate.context?.stage_state;
|
|
328
|
+
if (typeof state !== "object" || state === null) return "commit";
|
|
329
|
+
const questionId = (state as Record<string, unknown>).question_id;
|
|
330
|
+
if (typeof questionId !== "string") return "commit";
|
|
331
|
+
const question: AskGateQuestion = {
|
|
332
|
+
id: questionId,
|
|
333
|
+
question: typeof gate.context?.prompt === "string" ? gate.context.prompt : questionId,
|
|
334
|
+
options: (gate.options ?? []).map(option => ({
|
|
335
|
+
label: typeof option.value === "string" ? option.value : option.label,
|
|
336
|
+
})),
|
|
337
|
+
multi: (state as Record<string, unknown>).multi === true,
|
|
338
|
+
allowEmpty: (state as Record<string, unknown>).allow_empty === true,
|
|
339
|
+
};
|
|
340
|
+
if (typeof answer === "object" && answer !== null && (answer as Record<string, unknown>).action === "clarify") {
|
|
341
|
+
gateAnswerToResult(question, answer);
|
|
342
|
+
return "resolve_without_commit";
|
|
343
|
+
}
|
|
344
|
+
const result = gateAnswerToResult(question, answer);
|
|
345
|
+
return result.selectedOptions.length === 0 && result.customInput === undefined ? "resolve_without_commit" : "commit";
|
|
346
|
+
}
|
|
347
|
+
|
|
305
348
|
/** Convenience: map a batch of ask questions to gate open-inputs. */
|
|
306
349
|
export function questionsToGates(questions: AskGateQuestion[]): OpenGateInput[] {
|
|
307
350
|
return questions.map(questionToGate);
|