@gajae-code/coding-agent 0.9.6 → 0.10.1
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 +58 -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 +5 -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/config/settings.d.ts +2 -0
- package/dist/types/coordinator/contract.d.ts +1 -1
- package/dist/types/coordinator-mcp/model-preset.d.ts +45 -0
- package/dist/types/coordinator-mcp/policy.d.ts +3 -0
- package/dist/types/coordinator-mcp/server.d.ts +79 -6
- package/dist/types/coordinator-mcp/session-reaper.d.ts +53 -0
- 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/extensibility/extensions/types.d.ts +3 -1
- 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/status-line/types.d.ts +1 -1
- package/dist/types/modes/components/status-line.d.ts +0 -10
- 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/print-mode.d.ts +3 -4
- package/dist/types/modes/rpc/rpc-client.d.ts +7 -1
- package/dist/types/modes/rpc/rpc-mode.d.ts +2 -1
- package/dist/types/modes/rpc/rpc-types.d.ts +17 -1
- 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/context-usage.d.ts +4 -19
- 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 +40 -3
- package/dist/types/session/cache-economics.d.ts +79 -0
- package/dist/types/session/context-estimation.d.ts +31 -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 +51 -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/config/settings.ts +96 -45
- package/src/coordinator/contract.ts +1 -0
- package/src/coordinator-mcp/model-preset.ts +100 -0
- package/src/coordinator-mcp/policy.ts +30 -0
- package/src/coordinator-mcp/server.ts +2881 -425
- package/src/coordinator-mcp/session-reaper.ts +129 -0
- 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/extensibility/extensions/runner.ts +1 -1
- package/src/extensibility/extensions/types.ts +3 -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 +12 -11
- package/src/internal-urls/local-protocol.ts +29 -10
- package/src/main.ts +301 -80
- package/src/modes/acp/acp-agent.ts +730 -100
- 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/footer.ts +3 -3
- 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/status-line/segments.ts +13 -6
- package/src/modes/components/status-line/types.ts +1 -1
- package/src/modes/components/status-line.ts +4 -171
- 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 +37 -18
- package/src/modes/controllers/input-controller.ts +8 -2
- package/src/modes/controllers/selector-controller.ts +62 -15
- package/src/modes/interactive-mode.ts +199 -7
- package/src/modes/irc-observation-ledger.ts +127 -0
- package/src/modes/print-mode.ts +228 -92
- package/src/modes/rpc/rpc-client.ts +58 -1
- package/src/modes/rpc/rpc-mode.ts +286 -85
- package/src/modes/rpc/rpc-types.ts +15 -1
- package/src/modes/shared/agent-wire/command-dispatch.ts +33 -1
- package/src/modes/shared/agent-wire/command-validation.ts +10 -0
- package/src/modes/shared/agent-wire/deep-interview-gate.ts +45 -2
- package/src/modes/shared/agent-wire/scopes.ts +1 -0
- 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/context-usage.ts +56 -92
- 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 +670 -217
- package/src/session/cache-economics.ts +275 -0
- package/src/session/context-estimation.ts +91 -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 +153 -9
- package/src/slash-commands/helpers/context-report.ts +26 -4
- 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
|
@@ -21,6 +21,7 @@ import {
|
|
|
21
21
|
getRenderCacheRetainedBytes,
|
|
22
22
|
Loader,
|
|
23
23
|
Markdown,
|
|
24
|
+
onImageProtocolChanged,
|
|
24
25
|
ProcessTerminal,
|
|
25
26
|
Spacer,
|
|
26
27
|
Text,
|
|
@@ -81,9 +82,11 @@ import type { BashExecutionComponent } from "./components/bash-execution";
|
|
|
81
82
|
import { CustomEditor } from "./components/custom-editor";
|
|
82
83
|
import { DynamicBorder } from "./components/dynamic-border";
|
|
83
84
|
import type { EvalExecutionComponent } from "./components/eval-execution";
|
|
85
|
+
import { GajaePetWidget, type PetMode } from "./components/gajae-pet-widget";
|
|
84
86
|
import type { HookEditorComponent } from "./components/hook-editor";
|
|
85
87
|
import type { HookInputComponent } from "./components/hook-input";
|
|
86
88
|
import type { HookSelectorComponent } from "./components/hook-selector";
|
|
89
|
+
import { IrcSplitViewComponent } from "./components/irc-sidebar";
|
|
87
90
|
import { StatusLineComponent } from "./components/status-line";
|
|
88
91
|
import type { ToolExecutionHandle } from "./components/tool-execution";
|
|
89
92
|
import {
|
|
@@ -99,6 +102,7 @@ import { InputController } from "./controllers/input-controller";
|
|
|
99
102
|
import { SelectorController } from "./controllers/selector-controller";
|
|
100
103
|
import { SSHCommandController } from "./controllers/ssh-command-controller";
|
|
101
104
|
import { TodoCommandController } from "./controllers/todo-command-controller";
|
|
105
|
+
import { IrcObservationLedger } from "./irc-observation-ledger";
|
|
102
106
|
import { JobsObserver } from "./jobs-observer";
|
|
103
107
|
import { OAuthManualInputManager } from "./oauth-manual-input";
|
|
104
108
|
import { SessionObserverRegistry } from "./session-observer-registry";
|
|
@@ -114,13 +118,42 @@ import {
|
|
|
114
118
|
onThemeChange,
|
|
115
119
|
theme,
|
|
116
120
|
} from "./theme/theme";
|
|
117
|
-
import type {
|
|
118
|
-
|
|
121
|
+
import type {
|
|
122
|
+
CompactionQueuedMessage,
|
|
123
|
+
InteractiveModeContext,
|
|
124
|
+
IrcArrivalSnapshot,
|
|
125
|
+
SubmittedUserInput,
|
|
126
|
+
TodoItem,
|
|
127
|
+
TodoPhase,
|
|
128
|
+
TranscriptRebuildPolicy,
|
|
129
|
+
} from "./types";
|
|
130
|
+
import type { ParsedIrcMessage } from "./utils/irc-message";
|
|
131
|
+
import { addChatChild, prepareTranscriptRebuild, UiHelpers } from "./utils/ui-helpers";
|
|
119
132
|
|
|
120
133
|
const INTERACTIVE_ABORT_CLEANUP_TIMEOUT_MS = 5_000;
|
|
121
134
|
const COMPOSER_NEWLINE_HINT = process.platform === "win32" ? "Alt+Enter/Ctrl+J" : "Shift+Enter/Ctrl+J";
|
|
122
135
|
export const DEFAULT_COMPOSER_PLACEHOLDER = `Type your message... ${COMPOSER_NEWLINE_HINT}: New line · Ctrl+C: Clear · Ctrl+R: Search history · Shift+Tab: Reasoning`;
|
|
123
136
|
const WELCOME_RESERVED_CONTAINER_CHILD_LIMIT = 8;
|
|
137
|
+
|
|
138
|
+
const IRC_SIDEBAR_TOGGLE_SHADOWING_ACTIONS: readonly AppKeybinding[] = [
|
|
139
|
+
"app.plan.toggle",
|
|
140
|
+
"app.session.new",
|
|
141
|
+
"app.session.tree",
|
|
142
|
+
"app.session.fork",
|
|
143
|
+
"app.session.resume",
|
|
144
|
+
"app.message.followUp",
|
|
145
|
+
"app.stt.toggle",
|
|
146
|
+
"app.clipboard.copyLine",
|
|
147
|
+
"app.session.observe",
|
|
148
|
+
"app.jobs.open",
|
|
149
|
+
"app.tool.backgroundFold",
|
|
150
|
+
];
|
|
151
|
+
|
|
152
|
+
export function getWelcomeTranscriptReservedRows(chatContainer: Container, width: number): number {
|
|
153
|
+
return chatContainer.children.length === 0 || chatContainer.children.length > WELCOME_RESERVED_CONTAINER_CHILD_LIMIT
|
|
154
|
+
? 0
|
|
155
|
+
: chatContainer.render(width).length;
|
|
156
|
+
}
|
|
124
157
|
const FRIENDLY_KEY_PARTS: Record<string, string> = {
|
|
125
158
|
alt: "Alt",
|
|
126
159
|
cmd: "Cmd",
|
|
@@ -280,6 +313,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
280
313
|
keybindings: KeybindingsManager;
|
|
281
314
|
agent: Agent;
|
|
282
315
|
historyStorage?: HistoryStorage;
|
|
316
|
+
readonly ircLedger = new IrcObservationLedger();
|
|
283
317
|
|
|
284
318
|
ui: TUI;
|
|
285
319
|
chatContainer: Container;
|
|
@@ -291,6 +325,8 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
291
325
|
editorContainer: Container;
|
|
292
326
|
hookWidgetContainerAbove: Container;
|
|
293
327
|
hookWidgetContainerBelow: Container;
|
|
328
|
+
petFloorContainer: Container = new Container();
|
|
329
|
+
petWidget: GajaePetWidget | undefined;
|
|
294
330
|
statusLine: StatusLineComponent;
|
|
295
331
|
|
|
296
332
|
isInitialized = false;
|
|
@@ -352,6 +388,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
352
388
|
#baseReservedSlashCommandNames: Set<string> = new Set();
|
|
353
389
|
#cleanupUnsubscribe?: () => void;
|
|
354
390
|
#subprocessTeardownUnsubscribe?: () => void;
|
|
391
|
+
#petProtocolUnsubscribe?: () => void;
|
|
355
392
|
readonly #version: string;
|
|
356
393
|
readonly #changelogMarkdown: string | undefined;
|
|
357
394
|
#planModePreviousTools: string[] | undefined;
|
|
@@ -387,6 +424,9 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
387
424
|
#eventBus?: EventBus;
|
|
388
425
|
#eventBusUnsubscribers: Array<() => void> = [];
|
|
389
426
|
#welcomeComponent?: WelcomeComponent;
|
|
427
|
+
#ircSplitView: IrcSplitViewComponent;
|
|
428
|
+
#ircSidebarAvailable = false;
|
|
429
|
+
#ircSidebarRequestedVisible = false;
|
|
390
430
|
|
|
391
431
|
constructor(
|
|
392
432
|
session: AgentSession,
|
|
@@ -423,6 +463,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
423
463
|
this.ui = new TUI(new ProcessTerminal(), settings.get("showHardwareCursor"));
|
|
424
464
|
this.ui.setClearOnShrink(settings.get("clearOnShrink"));
|
|
425
465
|
this.chatContainer = new Container();
|
|
466
|
+
this.#ircSplitView = new IrcSplitViewComponent(this.chatContainer, this.ircLedger, () => theme);
|
|
426
467
|
this.pendingMessagesContainer = new Container();
|
|
427
468
|
this.statusContainer = new Container();
|
|
428
469
|
this.todoContainer = new Container();
|
|
@@ -442,6 +483,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
442
483
|
this.#syncEditorMaxHeight();
|
|
443
484
|
this.updateEditorChrome();
|
|
444
485
|
this.editor.invalidate();
|
|
486
|
+
this.#invalidateIrcSidebarRender();
|
|
445
487
|
this.ui.requestResizeRender();
|
|
446
488
|
};
|
|
447
489
|
process.stdout.on("resize", this.#resizeHandler);
|
|
@@ -561,7 +603,9 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
561
603
|
this.#welcomeComponent.playIntro(() => this.ui.requestRender());
|
|
562
604
|
}
|
|
563
605
|
|
|
564
|
-
this.ui.addChild(this
|
|
606
|
+
this.ui.addChild(this.#ircSplitView);
|
|
607
|
+
this.ui.setViewportAnchorComponent(this.#ircSplitView);
|
|
608
|
+
|
|
565
609
|
this.ui.addChild(this.pendingMessagesContainer);
|
|
566
610
|
this.ui.addChild(this.statusContainer);
|
|
567
611
|
this.ui.addChild(this.todoContainer);
|
|
@@ -569,9 +613,24 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
569
613
|
this.ui.addChild(this.statusLine); // Main status rail + hook statuses; composer chrome is rendered by the editor.
|
|
570
614
|
this.ui.addChild(this.hookWidgetContainerAbove);
|
|
571
615
|
this.ui.addChild(this.editorContainer);
|
|
616
|
+
this.ui.addChild(this.petFloorContainer);
|
|
572
617
|
this.ui.addChild(this.hookWidgetContainerBelow);
|
|
573
618
|
this.ui.setBottomPinnedComponent(this.statusLine);
|
|
574
619
|
this.ui.setFocus(this.editor);
|
|
620
|
+
this.petWidget = this.#createPetWidget(this.editor);
|
|
621
|
+
this.petWidget.setMode(settings.get("pet.mode"));
|
|
622
|
+
// The async sixel capability probe can enable graphics after the saved
|
|
623
|
+
// pet mode was applied and dropped (no protocol yet at startup).
|
|
624
|
+
// Re-apply the configured mode when capability arrives so the pet
|
|
625
|
+
// appears without the user re-running /pet.
|
|
626
|
+
this.#petProtocolUnsubscribe?.();
|
|
627
|
+
this.#petProtocolUnsubscribe = onImageProtocolChanged(protocol => {
|
|
628
|
+
if (!protocol) return;
|
|
629
|
+
const saved = settings.get("pet.mode");
|
|
630
|
+
if (saved !== "off" && this.petWidget && this.petWidget.mode === "off") {
|
|
631
|
+
this.petWidget.setMode(saved);
|
|
632
|
+
}
|
|
633
|
+
});
|
|
575
634
|
|
|
576
635
|
this.#inputController.setupKeyHandlers();
|
|
577
636
|
this.#inputController.setupEditorSubmitHandler();
|
|
@@ -609,6 +668,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
609
668
|
this.updateEditorChrome();
|
|
610
669
|
this.#syncEditorMaxHeight();
|
|
611
670
|
this.isInitialized = true;
|
|
671
|
+
this.#syncIrcSidebarAvailabilityFromSettings();
|
|
612
672
|
this.ui.requestRender(true);
|
|
613
673
|
|
|
614
674
|
// GitHub star reminder (interactive-only). Register the decline-driven
|
|
@@ -874,7 +934,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
874
934
|
}
|
|
875
935
|
if (!submission.customType) {
|
|
876
936
|
this.pendingImages = submission.images ? [...submission.images] : [];
|
|
877
|
-
this.rebuildChatFromMessages();
|
|
937
|
+
this.rebuildChatFromMessages("reconcile-same-transcript");
|
|
878
938
|
this.editor.setText(submission.text);
|
|
879
939
|
}
|
|
880
940
|
this.updateEditorChrome();
|
|
@@ -950,8 +1010,9 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
950
1010
|
}
|
|
951
1011
|
|
|
952
1012
|
#getWelcomeReservedRows(width: number): number {
|
|
1013
|
+
const transcriptRows = getWelcomeTranscriptReservedRows(this.chatContainer, width);
|
|
1014
|
+
|
|
953
1015
|
const transientRows = [
|
|
954
|
-
this.chatContainer,
|
|
955
1016
|
this.pendingMessagesContainer,
|
|
956
1017
|
this.statusContainer,
|
|
957
1018
|
this.todoContainer,
|
|
@@ -965,7 +1026,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
965
1026
|
this.hookWidgetContainerBelow,
|
|
966
1027
|
].reduce((rows, component) => rows + component.render(width).length, 0);
|
|
967
1028
|
|
|
968
|
-
return transientRows + pinnedRows;
|
|
1029
|
+
return transcriptRows + transientRows + pinnedRows;
|
|
969
1030
|
}
|
|
970
1031
|
|
|
971
1032
|
#renderShortContainerRowsForWelcomeReservation(width: number, container: Container): number {
|
|
@@ -1017,7 +1078,42 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
1017
1078
|
this.editor.setTopBorder(undefined);
|
|
1018
1079
|
}
|
|
1019
1080
|
|
|
1020
|
-
|
|
1081
|
+
setPetMode(mode: PetMode): void {
|
|
1082
|
+
this.petWidget?.setMode(mode);
|
|
1083
|
+
settings.set("pet.mode", mode);
|
|
1084
|
+
this.ui.requestRender();
|
|
1085
|
+
}
|
|
1086
|
+
|
|
1087
|
+
previewPetMode(mode: PetMode): void {
|
|
1088
|
+
this.petWidget?.previewMode(mode);
|
|
1089
|
+
this.ui.requestRender();
|
|
1090
|
+
}
|
|
1091
|
+
|
|
1092
|
+
restoreComposer(): void {
|
|
1093
|
+
if (this.petWidget) {
|
|
1094
|
+
this.petWidget.remountComposer();
|
|
1095
|
+
} else {
|
|
1096
|
+
this.editorContainer.clear();
|
|
1097
|
+
this.editorContainer.addChild(this.editor);
|
|
1098
|
+
}
|
|
1099
|
+
this.ui.setFocus(this.editor);
|
|
1100
|
+
}
|
|
1101
|
+
|
|
1102
|
+
#createPetWidget(editor: CustomEditor): GajaePetWidget {
|
|
1103
|
+
return new GajaePetWidget({
|
|
1104
|
+
ui: this.ui,
|
|
1105
|
+
editor,
|
|
1106
|
+
editorContainer: this.editorContainer,
|
|
1107
|
+
floorContainer: this.petFloorContainer,
|
|
1108
|
+
isWorking: () => this.loadingAnimation !== undefined,
|
|
1109
|
+
getComposerBottomOffset: () =>
|
|
1110
|
+
this.petFloorContainer.render(this.ui.terminal.columns).length +
|
|
1111
|
+
this.hookWidgetContainerBelow.render(this.ui.terminal.columns).length,
|
|
1112
|
+
});
|
|
1113
|
+
}
|
|
1114
|
+
|
|
1115
|
+
rebuildChatFromMessages(policy: TranscriptRebuildPolicy): void {
|
|
1116
|
+
prepareTranscriptRebuild(this.ui, policy);
|
|
1021
1117
|
this.chatContainer.clear();
|
|
1022
1118
|
const context = this.session.buildDisplaySessionContext();
|
|
1023
1119
|
this.renderSessionContext(context);
|
|
@@ -1992,6 +2088,10 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
1992
2088
|
}
|
|
1993
2089
|
|
|
1994
2090
|
stop(): void {
|
|
2091
|
+
this.#petProtocolUnsubscribe?.();
|
|
2092
|
+
this.#petProtocolUnsubscribe = undefined;
|
|
2093
|
+
this.petWidget?.dispose();
|
|
2094
|
+
this.petWidget = undefined;
|
|
1995
2095
|
if (this.loadingAnimation) {
|
|
1996
2096
|
this.loadingAnimation.stop();
|
|
1997
2097
|
this.loadingAnimation = undefined;
|
|
@@ -2123,11 +2223,17 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
2123
2223
|
nextEditor.setText(previousText);
|
|
2124
2224
|
previousEditor.dispose();
|
|
2125
2225
|
|
|
2226
|
+
const petMode = this.petWidget?.mode ?? settings.get("pet.mode");
|
|
2227
|
+
this.petWidget?.dispose();
|
|
2228
|
+
|
|
2126
2229
|
this.editorContainer.clear();
|
|
2127
2230
|
this.editor = nextEditor;
|
|
2128
2231
|
this.editorContainer.addChild(nextEditor);
|
|
2129
2232
|
this.ui.setFocus(nextEditor);
|
|
2130
2233
|
|
|
2234
|
+
this.petWidget = this.#createPetWidget(nextEditor);
|
|
2235
|
+
this.petWidget.setMode(petMode);
|
|
2236
|
+
|
|
2131
2237
|
this.#inputController.setupKeyHandlers();
|
|
2132
2238
|
this.#inputController.setupEditorSubmitHandler();
|
|
2133
2239
|
|
|
@@ -2284,13 +2390,32 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
2284
2390
|
return this.#uiHelpers.addMessageToChat(message, options);
|
|
2285
2391
|
}
|
|
2286
2392
|
|
|
2393
|
+
addLiveIrcObservationToChat(message: ParsedIrcMessage, arrival: IrcArrivalSnapshot): Component[] {
|
|
2394
|
+
return this.#uiHelpers.addLiveIrcObservationToChat(message, arrival);
|
|
2395
|
+
}
|
|
2396
|
+
removeRenderedIrcInlineComponents(observationId: string): readonly Component[] | undefined {
|
|
2397
|
+
return this.#uiHelpers.removeRenderedIrcInlineComponents(observationId);
|
|
2398
|
+
}
|
|
2399
|
+
resetRenderedIrcInlineComponents(): readonly (readonly Component[])[] {
|
|
2400
|
+
return this.#uiHelpers.resetRenderedIrcInlineComponents();
|
|
2401
|
+
}
|
|
2402
|
+
|
|
2287
2403
|
renderSessionContext(
|
|
2288
2404
|
sessionContext: SessionContext,
|
|
2289
2405
|
options?: { updateFooter?: boolean; populateHistory?: boolean },
|
|
2290
2406
|
): void {
|
|
2291
2407
|
this.#uiHelpers.renderSessionContext(sessionContext, options);
|
|
2408
|
+
this.#eventController.reconcileIrcExpiryTimers(this.#uiHelpers.getRenderedIrcInlineComponents());
|
|
2292
2409
|
}
|
|
2293
2410
|
|
|
2411
|
+
rebuildInitialMessages(
|
|
2412
|
+
policy: TranscriptRebuildPolicy,
|
|
2413
|
+
prebuiltContext?: SessionContext,
|
|
2414
|
+
options?: { preserveExistingChat?: boolean },
|
|
2415
|
+
): void {
|
|
2416
|
+
prepareTranscriptRebuild(this.ui, policy);
|
|
2417
|
+
this.#uiHelpers.renderInitialMessages(prebuiltContext, options);
|
|
2418
|
+
}
|
|
2294
2419
|
renderInitialMessages(prebuiltContext?: SessionContext, options?: { preserveExistingChat?: boolean }): void {
|
|
2295
2420
|
this.#uiHelpers.renderInitialMessages(prebuiltContext, options);
|
|
2296
2421
|
}
|
|
@@ -2303,6 +2428,10 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
2303
2428
|
return this.#uiHelpers.findLastAssistantMessage();
|
|
2304
2429
|
}
|
|
2305
2430
|
|
|
2431
|
+
getAssistantViewportAnchorId(message: AssistantMessage): string {
|
|
2432
|
+
return this.#uiHelpers.assistantViewportAnchorId(message);
|
|
2433
|
+
}
|
|
2434
|
+
|
|
2306
2435
|
extractAssistantText(message: AssistantMessage): string {
|
|
2307
2436
|
return this.#uiHelpers.extractAssistantText(message);
|
|
2308
2437
|
}
|
|
@@ -2553,6 +2682,10 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
2553
2682
|
this.#selectorController.showThemeSelector();
|
|
2554
2683
|
}
|
|
2555
2684
|
|
|
2685
|
+
showPetSelector(): void {
|
|
2686
|
+
this.#selectorController.showPetSelector();
|
|
2687
|
+
}
|
|
2688
|
+
|
|
2556
2689
|
showHistorySearch(): void {
|
|
2557
2690
|
this.#selectorController.showHistorySearch();
|
|
2558
2691
|
}
|
|
@@ -2668,6 +2801,65 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
2668
2801
|
this.#inputController.setToolsExpanded(expanded);
|
|
2669
2802
|
}
|
|
2670
2803
|
|
|
2804
|
+
#resolveEffectiveIrcSidebarToggleKey(): string | null {
|
|
2805
|
+
for (const key of this.keybindings.getKeys("app.irc.sidebar.toggle")) {
|
|
2806
|
+
if (this.editor.hasActionKey(key)) continue;
|
|
2807
|
+
const shadowed = IRC_SIDEBAR_TOGGLE_SHADOWING_ACTIONS.some(action =>
|
|
2808
|
+
this.keybindings.getKeys(action).includes(key),
|
|
2809
|
+
);
|
|
2810
|
+
if (!shadowed) return key;
|
|
2811
|
+
}
|
|
2812
|
+
return null;
|
|
2813
|
+
}
|
|
2814
|
+
|
|
2815
|
+
captureIrcArrivalSnapshot(): IrcArrivalSnapshot {
|
|
2816
|
+
return {
|
|
2817
|
+
panelVisible: this.#ircSplitView.effectiveSidebarVisible(this.ui.terminal.columns),
|
|
2818
|
+
panelRequestedVisible: this.#ircSidebarRequestedVisible,
|
|
2819
|
+
sidebarAvailable: this.#ircSidebarAvailable,
|
|
2820
|
+
resolvedToggleKey: this.#resolveEffectiveIrcSidebarToggleKey(),
|
|
2821
|
+
};
|
|
2822
|
+
}
|
|
2823
|
+
toggleIrcSidebar(): void {
|
|
2824
|
+
if (
|
|
2825
|
+
!this.#ircSidebarAvailable ||
|
|
2826
|
+
this.settings.get("irc.enabled") !== true ||
|
|
2827
|
+
this.settings.get("irc.sidebar.enabled") !== true
|
|
2828
|
+
)
|
|
2829
|
+
return;
|
|
2830
|
+
this.#ircSidebarRequestedVisible = !this.#ircSidebarRequestedVisible;
|
|
2831
|
+
this.#ircSplitView.setVisible(this.#ircSidebarRequestedVisible);
|
|
2832
|
+
this.#invalidateIrcSidebarRender();
|
|
2833
|
+
this.ui.requestRender();
|
|
2834
|
+
}
|
|
2835
|
+
|
|
2836
|
+
applyIrcSidebarAvailability(enabled: boolean): void {
|
|
2837
|
+
this.#ircSidebarAvailable = enabled;
|
|
2838
|
+
this.#ircSplitView.setVisible(enabled && this.#ircSidebarRequestedVisible);
|
|
2839
|
+
this.#invalidateIrcSidebarRender();
|
|
2840
|
+
this.ui.requestRender();
|
|
2841
|
+
}
|
|
2842
|
+
|
|
2843
|
+
#syncIrcSidebarAvailabilityFromSettings(): void {
|
|
2844
|
+
this.applyIrcSidebarAvailability(
|
|
2845
|
+
this.settings.get("irc.enabled") === true && this.settings.get("irc.sidebar.enabled") === true,
|
|
2846
|
+
);
|
|
2847
|
+
}
|
|
2848
|
+
|
|
2849
|
+
resetIrcSidebarSession(): void {
|
|
2850
|
+
this.ircLedger.reset();
|
|
2851
|
+
this.#eventController.resetIrcObservations();
|
|
2852
|
+
this.#ircSidebarRequestedVisible = false;
|
|
2853
|
+
this.#ircSplitView.setVisible(false);
|
|
2854
|
+
this.#uiHelpers.resetIrcSidebarHint();
|
|
2855
|
+
this.#syncIrcSidebarAvailabilityFromSettings();
|
|
2856
|
+
}
|
|
2857
|
+
|
|
2858
|
+
#invalidateIrcSidebarRender(): void {
|
|
2859
|
+
clearRenderCache();
|
|
2860
|
+
this.#ircSplitView.invalidate();
|
|
2861
|
+
}
|
|
2862
|
+
|
|
2671
2863
|
toggleThinkingBlockVisibility(): void {
|
|
2672
2864
|
this.#inputController.toggleThinkingBlockVisibility();
|
|
2673
2865
|
}
|
|
@@ -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
|
+
}
|