@sayknow-cli/coding-agent 0.3.12 → 0.3.15
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/dist/types/autoresearch/tools/init-experiment.d.ts +1 -1
- package/dist/types/autoresearch/tools/log-experiment.d.ts +2 -2
- 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/commands/web-search.d.ts +1 -1
- package/dist/types/commit/agentic/tools/propose-changelog.d.ts +4 -4
- package/dist/types/commit/agentic/tools/propose-commit.d.ts +9 -9
- package/dist/types/commit/agentic/tools/schemas.d.ts +9 -9
- package/dist/types/commit/agentic/tools/split-commit.d.ts +9 -9
- 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 +3 -3
- 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/commit/shared-llm.d.ts +9 -9
- package/dist/types/config/keybindings.d.ts +134 -129
- package/dist/types/config/model-profile-activation.d.ts +1 -0
- package/dist/types/config/model-registry.d.ts +35 -35
- package/dist/types/config/models-config-schema.d.ts +111 -111
- package/dist/types/config/settings-schema.d.ts +56 -16
- 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/skc/extensions/grok-cli-vendor/src/models/catalog.d.ts +4 -0
- package/dist/types/defaults/skc/extensions/grok-cli-vendor/src/shared/errors.d.ts +12 -12
- package/dist/types/discovery/helpers.d.ts +1 -1
- package/dist/types/discovery/substitute-plugin-root.d.ts +0 -4
- package/dist/types/edit/modes/patch.d.ts +2 -2
- package/dist/types/extensibility/extensions/types.d.ts +3 -1
- package/dist/types/goals/tools/goal-tool.d.ts +6 -6
- package/dist/types/i18n/messages/en.d.ts +8 -8
- package/dist/types/internal-urls/local-protocol.d.ts +13 -9
- package/dist/types/lsp/index.d.ts +9 -9
- package/dist/types/lsp/types.d.ts +9 -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/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/sayknow-pet-widget.d.ts +59 -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/skc-runtime/session-state-sidecar.d.ts +34 -3
- package/dist/types/skc-runtime/state-schema.d.ts +19 -19
- package/dist/types/skc-runtime/tmux-common.d.ts +6 -0
- package/dist/types/skc-runtime/tmux-owner-isolation-cli.d.ts +5 -0
- package/dist/types/skc-runtime/tmux-owner-isolation.d.ts +309 -0
- package/dist/types/skc-runtime/tmux-sessions.d.ts +49 -17
- package/dist/types/task/agents.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 +35 -27
- package/dist/types/tools/ask-answer-registry.d.ts +5 -0
- package/dist/types/tools/bash.d.ts +1 -1
- package/dist/types/tools/browser/tab-supervisor.d.ts +6 -0
- package/dist/types/tools/browser.d.ts +28 -28
- package/dist/types/tools/computer.d.ts +20 -20
- package/dist/types/tools/cron.d.ts +2 -2
- package/dist/types/tools/debug.d.ts +40 -40
- package/dist/types/tools/gh.d.ts +10 -10
- package/dist/types/tools/image-gen.d.ts +2 -2
- package/dist/types/tools/index.d.ts +58 -6
- package/dist/types/tools/monitor.d.ts +6 -6
- package/dist/types/tools/skill-discovery.d.ts +4 -4
- package/dist/types/tools/subagent.d.ts +12 -12
- package/dist/types/tools/telegram-send.d.ts +7 -0
- package/dist/types/tools/todo-write.d.ts +8 -8
- package/dist/types/utils/external-editor.d.ts +2 -0
- package/dist/types/web/insane/bridge.d.ts +9 -9
- package/dist/types/web/search/index.d.ts +4 -4
- package/package.json +8 -8
- 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 +2958 -430
- 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/defaults/skc/extensions/grok-cli-vendor/src/models/catalog.ts +57 -17
- package/src/defaults/skc/extensions/grok-cli-vendor/src/payload/sanitize.ts +15 -2
- package/src/defaults/skc/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/internal-urls/docs-index.generated.ts +10 -9
- 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/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/sayknow-pet-widget.ts +360 -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 +14 -7
- 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-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/skc-runtime/session-state-sidecar.ts +756 -135
- package/src/skc-runtime/tmux-common.ts +26 -7
- package/src/skc-runtime/tmux-gc.ts +52 -9
- package/src/skc-runtime/tmux-owner-isolation-cli.ts +185 -0
- package/src/skc-runtime/tmux-owner-isolation.ts +2060 -0
- package/src/skc-runtime/tmux-sessions.ts +871 -68
- 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/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
|
@@ -20,18 +20,20 @@ import { completionNotifyDisabledByEnv } from "../../notifications/config";
|
|
|
20
20
|
import { summaryFromMessage } from "../../notifications/helpers";
|
|
21
21
|
import type { PlanApprovalDetails } from "../../plan-mode/approved-plan";
|
|
22
22
|
import type { AgentSessionEvent } from "../../session/agent-session";
|
|
23
|
-
import { isSilentAbort, readPendingDisplayTag } from "../../session/messages";
|
|
23
|
+
import { type CustomMessage, isSilentAbort, readPendingDisplayTag } from "../../session/messages";
|
|
24
|
+
import { transferSessionMessageIdentity } from "../../session/session-manager";
|
|
24
25
|
import type { ResolveToolDetails } from "../../tools/resolve";
|
|
26
|
+
import type { IrcObservationRecord } from "../irc-observation-ledger";
|
|
25
27
|
import { interruptHint } from "../shared";
|
|
26
28
|
import { buildAbortDisplayMessage } from "../utils/abort-message";
|
|
27
29
|
import { consumeInjectedOptimisticSignature } from "../utils/injected-user-submission";
|
|
30
|
+
import { parseIrcMessage } from "../utils/irc-message";
|
|
28
31
|
import { ringTerminalBell } from "../utils/terminal-bell";
|
|
32
|
+
|
|
29
33
|
import { addChatChild, argsWithPartialJson } from "../utils/ui-helpers";
|
|
30
34
|
|
|
31
35
|
type AgentSessionEventKind = AgentSessionEvent["type"];
|
|
32
36
|
|
|
33
|
-
const IRC_MESSAGE_VISIBLE_TTL_MS = 10_000;
|
|
34
|
-
|
|
35
37
|
/** Test-only performance counters for advisory baseline tests. */
|
|
36
38
|
export const __eventControllerPerfCounters = {
|
|
37
39
|
messageUpdateContentVisits: 0,
|
|
@@ -112,6 +114,7 @@ export class EventController {
|
|
|
112
114
|
#lastAssistantComponent: AssistantMessageComponent | undefined = undefined;
|
|
113
115
|
#idleCompactionTimer?: NodeJS.Timeout;
|
|
114
116
|
#ircExpiryTimers = new Map<string, NodeJS.Timeout>();
|
|
117
|
+
#renderedIrcComponents = new Map<string, readonly Component[]>();
|
|
115
118
|
#toolIntentCache = new Map<string, { args: unknown; intent: string | undefined }>();
|
|
116
119
|
#thinkingContentIndices = new Set<number>();
|
|
117
120
|
#handlers: AgentSessionEventHandlers;
|
|
@@ -157,10 +160,7 @@ export class EventController {
|
|
|
157
160
|
this.ctx.retryLoader.stop();
|
|
158
161
|
this.ctx.retryLoader = undefined;
|
|
159
162
|
}
|
|
160
|
-
|
|
161
|
-
clearTimeout(timer);
|
|
162
|
-
}
|
|
163
|
-
this.#ircExpiryTimers.clear();
|
|
163
|
+
this.clearIrcExpiryTimers();
|
|
164
164
|
}
|
|
165
165
|
|
|
166
166
|
#resetReadGroup(): void {
|
|
@@ -263,8 +263,61 @@ export class EventController {
|
|
|
263
263
|
this.ctx.ui.requestRender();
|
|
264
264
|
}
|
|
265
265
|
|
|
266
|
+
#observeIrcMessage(message: CustomMessage): void {
|
|
267
|
+
const parsed = parseIrcMessage(message);
|
|
268
|
+
if (!parsed) return;
|
|
269
|
+
const arrival = this.ctx.captureIrcArrivalSnapshot();
|
|
270
|
+
const record = this.ctx.ircLedger.observe(parsed, arrival.panelVisible);
|
|
271
|
+
this.#cleanupEvictedIrcObservations();
|
|
272
|
+
if (!record) return;
|
|
273
|
+
const signature = `irc:${record.observationId}`;
|
|
274
|
+
if (this.#renderedCustomMessages.has(signature)) return;
|
|
275
|
+
this.#renderedCustomMessages.add(signature);
|
|
276
|
+
this.#resetReadGroup();
|
|
277
|
+
const components = this.ctx.addLiveIrcObservationToChat(record, arrival);
|
|
278
|
+
this.#renderedIrcComponents.set(record.observationId, components);
|
|
279
|
+
this.#scheduleIrcExpiry(record, components);
|
|
280
|
+
this.ctx.ui.requestRender();
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
#cleanupIrcObservationIds(observationIds: readonly string[]): void {
|
|
284
|
+
const removedComponents = new Set<Component>();
|
|
285
|
+
for (const observationId of observationIds) {
|
|
286
|
+
const timer = this.#ircExpiryTimers.get(observationId);
|
|
287
|
+
if (timer) clearTimeout(timer);
|
|
288
|
+
this.#ircExpiryTimers.delete(observationId);
|
|
289
|
+
for (const component of this.#renderedIrcComponents.get(observationId) ?? []) {
|
|
290
|
+
removedComponents.add(component);
|
|
291
|
+
}
|
|
292
|
+
this.#renderedIrcComponents.delete(observationId);
|
|
293
|
+
for (const component of this.ctx.removeRenderedIrcInlineComponents(observationId) ?? []) {
|
|
294
|
+
removedComponents.add(component);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
this.#renderedCustomMessages.delete(`irc:${observationId}`);
|
|
298
|
+
}
|
|
299
|
+
for (const component of removedComponents) this.ctx.chatContainer.removeChild(component);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
#cleanupEvictedIrcObservations(): void {
|
|
303
|
+
this.#cleanupIrcObservationIds(this.ctx.ircLedger.drainEvictedObservationIds());
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
#cleanupExpiredIrcInlineComponents(observationId: string): void {
|
|
307
|
+
const removedComponents = new Set<Component>(this.#renderedIrcComponents.get(observationId));
|
|
308
|
+
this.#renderedIrcComponents.delete(observationId);
|
|
309
|
+
for (const component of this.ctx.removeRenderedIrcInlineComponents(observationId) ?? []) {
|
|
310
|
+
removedComponents.add(component);
|
|
311
|
+
}
|
|
312
|
+
for (const component of removedComponents) this.ctx.chatContainer.removeChild(component);
|
|
313
|
+
}
|
|
314
|
+
|
|
266
315
|
async #handleMessageStart(event: Extract<AgentSessionEvent, { type: "message_start" }>): Promise<void> {
|
|
267
316
|
if (event.message.role === "hookMessage" || event.message.role === "custom") {
|
|
317
|
+
if (event.message.role === "custom" && parseIrcMessage(event.message)) {
|
|
318
|
+
this.#observeIrcMessage(event.message);
|
|
319
|
+
return;
|
|
320
|
+
}
|
|
268
321
|
const signature = `${event.message.role}:${event.message.customType}:${event.message.timestamp}`;
|
|
269
322
|
if (this.#renderedCustomMessages.has(signature)) {
|
|
270
323
|
return;
|
|
@@ -328,8 +381,11 @@ export class EventController {
|
|
|
328
381
|
this.#resetReadGroup();
|
|
329
382
|
this.#toolIntentCache.clear();
|
|
330
383
|
this.#thinkingContentIndices.clear();
|
|
331
|
-
this.ctx.streamingComponent = new AssistantMessageComponent(
|
|
332
|
-
|
|
384
|
+
this.ctx.streamingComponent = new AssistantMessageComponent(
|
|
385
|
+
undefined,
|
|
386
|
+
this.ctx.hideThinkingBlock,
|
|
387
|
+
() => this.ctx.ui.requestRender(),
|
|
388
|
+
this.ctx.getAssistantViewportAnchorId?.(event.message),
|
|
333
389
|
);
|
|
334
390
|
this.ctx.streamingMessage = event.message;
|
|
335
391
|
addChatChild(this.ctx, this.ctx.streamingComponent);
|
|
@@ -339,15 +395,89 @@ export class EventController {
|
|
|
339
395
|
}
|
|
340
396
|
|
|
341
397
|
async #handleIrcMessage(event: Extract<AgentSessionEvent, { type: "irc_message" }>): Promise<void> {
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
398
|
+
this.#observeIrcMessage(event.message);
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
clearIrcExpiryTimers(): void {
|
|
402
|
+
for (const timer of this.#ircExpiryTimers.values()) {
|
|
403
|
+
clearTimeout(timer);
|
|
404
|
+
}
|
|
405
|
+
this.#ircExpiryTimers.clear();
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
resetIrcObservations(): void {
|
|
409
|
+
this.clearIrcExpiryTimers();
|
|
410
|
+
const observationIds = new Set<string>(this.#renderedIrcComponents.keys());
|
|
411
|
+
for (const observationId of this.ctx.ircLedger.drainEvictedObservationIds()) observationIds.add(observationId);
|
|
412
|
+
this.#cleanupIrcObservationIds([...observationIds]);
|
|
413
|
+
for (const components of this.ctx.resetRenderedIrcInlineComponents()) {
|
|
414
|
+
for (const component of components) this.ctx.chatContainer.removeChild(component);
|
|
415
|
+
}
|
|
416
|
+
this.#renderedIrcComponents.clear();
|
|
417
|
+
for (const signature of this.#renderedCustomMessages) {
|
|
418
|
+
if (signature.startsWith("irc:")) this.#renderedCustomMessages.delete(signature);
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
reconcileIrcExpiryTimers(componentsByObservationId: Map<string, readonly Component[]>): void {
|
|
423
|
+
this.clearIrcExpiryTimers();
|
|
424
|
+
this.#renderedIrcComponents.clear();
|
|
425
|
+
const now = Date.now();
|
|
426
|
+
const inlineProjection = this.ctx.ircLedger.getInlineProjection(now);
|
|
427
|
+
|
|
428
|
+
this.#cleanupEvictedIrcObservations();
|
|
429
|
+
const projectedObservationIds = new Set(inlineProjection.map(record => record.observationId));
|
|
430
|
+
for (const [observationId, components] of componentsByObservationId) {
|
|
431
|
+
const record = this.ctx.ircLedger.getRecord(observationId);
|
|
432
|
+
if (
|
|
433
|
+
!projectedObservationIds.has(observationId) ||
|
|
434
|
+
(record?.mode === "ephemeral" && record.expiresAt! - now <= 0)
|
|
435
|
+
) {
|
|
436
|
+
for (const component of components) {
|
|
437
|
+
this.ctx.chatContainer.removeChild(component);
|
|
438
|
+
}
|
|
439
|
+
this.#renderedIrcComponents.delete(observationId);
|
|
440
|
+
componentsByObservationId.delete(observationId);
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
for (const record of inlineProjection) {
|
|
444
|
+
const components = componentsByObservationId.get(record.observationId);
|
|
445
|
+
if (!components) continue;
|
|
446
|
+
this.#renderedIrcComponents.set(record.observationId, components);
|
|
447
|
+
// #scheduleIrcExpiry owns the single expiry clock read: a record that
|
|
448
|
+
// crossed its deadline while projection/reconciliation ran above is
|
|
449
|
+
// cleaned up there (old timers were already cleared, so silently
|
|
450
|
+
// skipping the timer would leave the row inline indefinitely).
|
|
451
|
+
if (!this.#scheduleIrcExpiry(record, components)) {
|
|
452
|
+
componentsByObservationId.delete(record.observationId);
|
|
453
|
+
}
|
|
345
454
|
}
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
/**
|
|
458
|
+
* Arms the removal timer for an ephemeral record using a single clock read.
|
|
459
|
+
* Returns false when the record's deadline has already elapsed — in that
|
|
460
|
+
* case the rendered components are removed from the chat container and
|
|
461
|
+
* {@link #renderedIrcComponents} here, and the caller must drop its own
|
|
462
|
+
* bookkeeping entry. Persistent records always return true (no timer).
|
|
463
|
+
*/
|
|
464
|
+
#scheduleIrcExpiry(record: IrcObservationRecord, components: readonly Component[]): boolean {
|
|
465
|
+
if (record.mode !== "ephemeral" || components.length === 0 || this.#ircExpiryTimers.has(record.observationId)) {
|
|
466
|
+
return true;
|
|
467
|
+
}
|
|
468
|
+
const remainingMs = record.expiresAt! - Date.now();
|
|
469
|
+
if (remainingMs <= 0) {
|
|
470
|
+
this.#cleanupExpiredIrcInlineComponents(record.observationId);
|
|
471
|
+
return false;
|
|
472
|
+
}
|
|
473
|
+
const timer = setTimeout(() => {
|
|
474
|
+
this.#ircExpiryTimers.delete(record.observationId);
|
|
475
|
+
this.#cleanupExpiredIrcInlineComponents(record.observationId);
|
|
476
|
+
this.ctx.ui.requestRender();
|
|
477
|
+
}, remainingMs);
|
|
478
|
+
timer.unref?.();
|
|
479
|
+
this.#ircExpiryTimers.set(record.observationId, timer);
|
|
480
|
+
return true;
|
|
351
481
|
}
|
|
352
482
|
|
|
353
483
|
async #handleSubagentSteerMessage(
|
|
@@ -360,28 +490,13 @@ export class EventController {
|
|
|
360
490
|
const signature = obsId
|
|
361
491
|
? `steer:${obsId}`
|
|
362
492
|
: `${event.message.role}:${event.message.customType}:${event.message.timestamp}:${details?.from}:${details?.to}:${details?.state}:${details?.body}`;
|
|
363
|
-
if (this.#renderedCustomMessages.has(signature))
|
|
364
|
-
return;
|
|
365
|
-
}
|
|
493
|
+
if (this.#renderedCustomMessages.has(signature)) return;
|
|
366
494
|
this.#renderedCustomMessages.add(signature);
|
|
367
495
|
this.#resetReadGroup();
|
|
368
496
|
this.ctx.addMessageToChat(event.message);
|
|
369
497
|
this.ctx.ui.requestRender();
|
|
370
498
|
}
|
|
371
499
|
|
|
372
|
-
#scheduleIrcExpiry(signature: string, components: Component[]): void {
|
|
373
|
-
if (components.length === 0 || this.#ircExpiryTimers.has(signature)) return;
|
|
374
|
-
const timer = setTimeout(() => {
|
|
375
|
-
this.#ircExpiryTimers.delete(signature);
|
|
376
|
-
for (const component of components) {
|
|
377
|
-
this.ctx.chatContainer.removeChild(component);
|
|
378
|
-
}
|
|
379
|
-
this.ctx.ui.requestRender();
|
|
380
|
-
}, IRC_MESSAGE_VISIBLE_TTL_MS);
|
|
381
|
-
timer.unref?.();
|
|
382
|
-
this.#ircExpiryTimers.set(signature, timer);
|
|
383
|
-
}
|
|
384
|
-
|
|
385
500
|
async #handleNotice(event: Extract<AgentSessionEvent, { type: "notice" }>): Promise<void> {
|
|
386
501
|
const message = event.source ? `${event.source}: ${event.message}` : event.message;
|
|
387
502
|
if (event.level === "error") {
|
|
@@ -395,6 +510,9 @@ export class EventController {
|
|
|
395
510
|
|
|
396
511
|
async #handleMessageUpdate(event: Extract<AgentSessionEvent, { type: "message_update" }>): Promise<void> {
|
|
397
512
|
if (this.ctx.streamingComponent && event.message.role === "assistant") {
|
|
513
|
+
if (this.ctx.streamingMessage?.role === "assistant") {
|
|
514
|
+
transferSessionMessageIdentity([this.ctx.streamingMessage], [event.message]);
|
|
515
|
+
}
|
|
398
516
|
this.ctx.streamingMessage = event.message;
|
|
399
517
|
this.ctx.streamingComponent.updateContent(this.ctx.streamingMessage, { streaming: true });
|
|
400
518
|
const contentIndex = event.assistantMessageEvent?.contentIndex;
|
|
@@ -498,6 +616,9 @@ export class EventController {
|
|
|
498
616
|
async #handleMessageEnd(event: Extract<AgentSessionEvent, { type: "message_end" }>): Promise<void> {
|
|
499
617
|
if (event.message.role === "user") return;
|
|
500
618
|
if (this.ctx.streamingComponent && event.message.role === "assistant") {
|
|
619
|
+
if (this.ctx.streamingMessage?.role === "assistant") {
|
|
620
|
+
transferSessionMessageIdentity([this.ctx.streamingMessage], [event.message]);
|
|
621
|
+
}
|
|
501
622
|
this.ctx.streamingMessage = event.message;
|
|
502
623
|
let errorMessage: string | undefined;
|
|
503
624
|
const aborted = this.ctx.streamingMessage.stopReason === "aborted";
|
|
@@ -750,7 +871,7 @@ export class EventController {
|
|
|
750
871
|
if (event.aborted) {
|
|
751
872
|
this.ctx.showStatus(isHandoffAction ? "Auto-handoff cancelled" : "Auto context-full maintenance cancelled");
|
|
752
873
|
} else if (event.result) {
|
|
753
|
-
this.ctx.rebuildChatFromMessages();
|
|
874
|
+
this.ctx.rebuildChatFromMessages("reconcile-same-transcript");
|
|
754
875
|
this.ctx.statusLine.invalidate();
|
|
755
876
|
this.ctx.updateEditorTopBorder();
|
|
756
877
|
if (continuationDisabled && !isHandoffAction) {
|
|
@@ -761,8 +882,10 @@ export class EventController {
|
|
|
761
882
|
} else if (event.errorMessage) {
|
|
762
883
|
this.ctx.showWarning(event.errorMessage);
|
|
763
884
|
} else if (isHandoffAction) {
|
|
764
|
-
|
|
765
|
-
|
|
885
|
+
// Reset BEFORE rebuild so the new session's transcript is not replayed
|
|
886
|
+
// from the old ledger and then cleared out from under its timers.
|
|
887
|
+
this.ctx.resetIrcSidebarSession();
|
|
888
|
+
this.ctx.rebuildChatFromMessages("replace-identity");
|
|
766
889
|
this.ctx.statusLine.invalidate();
|
|
767
890
|
this.ctx.updateEditorTopBorder();
|
|
768
891
|
await this.ctx.reloadTodos();
|
|
@@ -26,6 +26,7 @@ import type { InteractiveModeContext } from "../../modes/types";
|
|
|
26
26
|
import { setSessionTerminalTitle, setTerminalTitle } from "../../utils/title-generator";
|
|
27
27
|
import { applyInjectedUserSubmission } from "../utils/injected-user-submission";
|
|
28
28
|
import { classifyHookSelectorBellEvent, ringTerminalBell } from "../utils/terminal-bell";
|
|
29
|
+
import { prepareTranscriptRebuild } from "../utils/ui-helpers";
|
|
29
30
|
|
|
30
31
|
const MAX_WIDGET_LINES = 10;
|
|
31
32
|
const HOOK_SELECTOR_CHROME_ROWS = 7;
|
|
@@ -151,9 +152,11 @@ export class ExtensionUiController {
|
|
|
151
152
|
getContextUsage: () => this.ctx.session.getContextUsage(),
|
|
152
153
|
waitForIdle: () => this.ctx.session.agent.waitForIdle(),
|
|
153
154
|
reload: async () => {
|
|
155
|
+
const previousSessionId = this.ctx.sessionManager.getSessionId();
|
|
154
156
|
await this.ctx.session.reload();
|
|
155
|
-
this.ctx.
|
|
156
|
-
this.ctx.
|
|
157
|
+
const sessionIdentityChanged = previousSessionId !== this.ctx.sessionManager.getSessionId();
|
|
158
|
+
if (sessionIdentityChanged) this.ctx.resetIrcSidebarSession();
|
|
159
|
+
this.ctx.rebuildInitialMessages(sessionIdentityChanged ? "replace-identity" : "reconcile-same-transcript");
|
|
157
160
|
await this.ctx.reloadTodos();
|
|
158
161
|
this.ctx.showStatus("Reloaded session");
|
|
159
162
|
},
|
|
@@ -172,6 +175,8 @@ export class ExtensionUiController {
|
|
|
172
175
|
if (!success) {
|
|
173
176
|
return { cancelled: true };
|
|
174
177
|
}
|
|
178
|
+
this.ctx.resetIrcSidebarSession();
|
|
179
|
+
|
|
175
180
|
setSessionTerminalTitle(this.ctx.sessionManager.getSessionName(), this.ctx.sessionManager.getCwd());
|
|
176
181
|
|
|
177
182
|
// Call setup callback if provided
|
|
@@ -186,6 +191,7 @@ export class ExtensionUiController {
|
|
|
186
191
|
this.ctx.ui.requestRender();
|
|
187
192
|
|
|
188
193
|
// Clear UI state
|
|
194
|
+
prepareTranscriptRebuild(this.ctx.ui, "replace-identity");
|
|
189
195
|
this.ctx.chatContainer.clear();
|
|
190
196
|
this.ctx.pendingMessagesContainer.clear();
|
|
191
197
|
this.ctx.compactionQueuedMessages = [];
|
|
@@ -206,10 +212,10 @@ export class ExtensionUiController {
|
|
|
206
212
|
if (result.cancelled) {
|
|
207
213
|
return { cancelled: true };
|
|
208
214
|
}
|
|
215
|
+
this.ctx.resetIrcSidebarSession();
|
|
209
216
|
|
|
210
217
|
// Update UI
|
|
211
|
-
this.ctx.
|
|
212
|
-
this.ctx.renderInitialMessages();
|
|
218
|
+
this.ctx.rebuildInitialMessages("replace-identity");
|
|
213
219
|
await this.ctx.reloadTodos();
|
|
214
220
|
this.ctx.editor.setText(result.selectedText);
|
|
215
221
|
this.ctx.showStatus("Branched to new session");
|
|
@@ -223,8 +229,7 @@ export class ExtensionUiController {
|
|
|
223
229
|
}
|
|
224
230
|
|
|
225
231
|
// Update UI
|
|
226
|
-
this.ctx.
|
|
227
|
-
this.ctx.renderInitialMessages();
|
|
232
|
+
this.ctx.rebuildInitialMessages("reconcile-same-transcript");
|
|
228
233
|
await this.ctx.reloadTodos();
|
|
229
234
|
if (result.editorText && !this.ctx.editor.getText().trim()) {
|
|
230
235
|
this.ctx.editor.setText(result.editorText);
|
|
@@ -235,14 +240,20 @@ export class ExtensionUiController {
|
|
|
235
240
|
},
|
|
236
241
|
compact: async instructionsOrOptions => this.#handleInteractiveCompact(instructionsOrOptions),
|
|
237
242
|
switchSession: async sessionPath => {
|
|
243
|
+
const previousSessionId = this.ctx.sessionManager.getSessionId();
|
|
244
|
+
|
|
238
245
|
this.clearHookWidgets();
|
|
239
246
|
const result = await this.ctx.session.switchSession(sessionPath);
|
|
240
247
|
if (!result) {
|
|
241
248
|
return { cancelled: true };
|
|
242
249
|
}
|
|
250
|
+
const switchingToDifferentSession = previousSessionId !== this.ctx.sessionManager.getSessionId();
|
|
251
|
+
if (switchingToDifferentSession) this.ctx.resetIrcSidebarSession();
|
|
252
|
+
|
|
243
253
|
setSessionTerminalTitle(this.ctx.sessionManager.getSessionName(), this.ctx.sessionManager.getCwd());
|
|
244
|
-
this.ctx.
|
|
245
|
-
|
|
254
|
+
this.ctx.rebuildInitialMessages(
|
|
255
|
+
switchingToDifferentSession ? "replace-identity" : "reconcile-same-transcript",
|
|
256
|
+
);
|
|
246
257
|
await this.ctx.reloadTodos();
|
|
247
258
|
return { cancelled: false };
|
|
248
259
|
},
|
|
@@ -393,9 +404,11 @@ export class ExtensionUiController {
|
|
|
393
404
|
if (this.ctx.isBackgrounded) {
|
|
394
405
|
return;
|
|
395
406
|
}
|
|
407
|
+
const previousSessionId = this.ctx.sessionManager.getSessionId();
|
|
396
408
|
await this.ctx.session.reload();
|
|
397
|
-
this.ctx.
|
|
398
|
-
this.ctx.
|
|
409
|
+
const sessionIdentityChanged = previousSessionId !== this.ctx.sessionManager.getSessionId();
|
|
410
|
+
if (sessionIdentityChanged) this.ctx.resetIrcSidebarSession();
|
|
411
|
+
this.ctx.rebuildInitialMessages(sessionIdentityChanged ? "replace-identity" : "reconcile-same-transcript");
|
|
399
412
|
await this.ctx.reloadTodos();
|
|
400
413
|
this.ctx.showStatus("Reloaded session");
|
|
401
414
|
},
|
|
@@ -417,6 +430,7 @@ export class ExtensionUiController {
|
|
|
417
430
|
if (!success) {
|
|
418
431
|
return { cancelled: true };
|
|
419
432
|
}
|
|
433
|
+
this.ctx.resetIrcSidebarSession();
|
|
420
434
|
|
|
421
435
|
// Call setup callback if provided
|
|
422
436
|
if (options?.setup) {
|
|
@@ -424,6 +438,7 @@ export class ExtensionUiController {
|
|
|
424
438
|
}
|
|
425
439
|
|
|
426
440
|
// Clear UI state
|
|
441
|
+
prepareTranscriptRebuild(this.ctx.ui, "replace-identity");
|
|
427
442
|
this.ctx.chatContainer.clear();
|
|
428
443
|
this.ctx.pendingMessagesContainer.clear();
|
|
429
444
|
this.ctx.compactionQueuedMessages = [];
|
|
@@ -447,10 +462,10 @@ export class ExtensionUiController {
|
|
|
447
462
|
if (result.cancelled) {
|
|
448
463
|
return { cancelled: true };
|
|
449
464
|
}
|
|
465
|
+
this.ctx.resetIrcSidebarSession();
|
|
450
466
|
|
|
451
467
|
// Update UI
|
|
452
|
-
this.ctx.
|
|
453
|
-
this.ctx.renderInitialMessages();
|
|
468
|
+
this.ctx.rebuildInitialMessages("replace-identity");
|
|
454
469
|
await this.ctx.reloadTodos();
|
|
455
470
|
this.ctx.editor.setText(result.selectedText);
|
|
456
471
|
this.ctx.showStatus("Branched to new session");
|
|
@@ -467,8 +482,7 @@ export class ExtensionUiController {
|
|
|
467
482
|
}
|
|
468
483
|
|
|
469
484
|
// Update UI
|
|
470
|
-
this.ctx.
|
|
471
|
-
this.ctx.renderInitialMessages();
|
|
485
|
+
this.ctx.rebuildInitialMessages("reconcile-same-transcript");
|
|
472
486
|
await this.ctx.reloadTodos();
|
|
473
487
|
if (result.editorText && !this.ctx.editor.getText().trim()) {
|
|
474
488
|
this.ctx.editor.setText(result.editorText);
|
|
@@ -482,13 +496,18 @@ export class ExtensionUiController {
|
|
|
482
496
|
if (this.ctx.isBackgrounded) {
|
|
483
497
|
return { cancelled: true };
|
|
484
498
|
}
|
|
499
|
+
const previousSessionId = this.ctx.sessionManager.getSessionId();
|
|
500
|
+
|
|
485
501
|
this.clearHookWidgets();
|
|
486
502
|
const result = await this.ctx.session.switchSession(sessionPath);
|
|
487
503
|
if (!result) {
|
|
488
504
|
return { cancelled: true };
|
|
489
505
|
}
|
|
490
|
-
this.ctx.
|
|
491
|
-
this.ctx.
|
|
506
|
+
const switchingToDifferentSession = previousSessionId !== this.ctx.sessionManager.getSessionId();
|
|
507
|
+
if (switchingToDifferentSession) this.ctx.resetIrcSidebarSession();
|
|
508
|
+
this.ctx.rebuildInitialMessages(
|
|
509
|
+
switchingToDifferentSession ? "replace-identity" : "reconcile-same-transcript",
|
|
510
|
+
);
|
|
492
511
|
await this.ctx.reloadTodos();
|
|
493
512
|
return { cancelled: false };
|
|
494
513
|
},
|
|
@@ -560,7 +579,7 @@ export class ExtensionUiController {
|
|
|
560
579
|
shutdown: () => {
|
|
561
580
|
// Signal shutdown request
|
|
562
581
|
},
|
|
563
|
-
getSystemPrompt: () => this.ctx.session.systemPrompt,
|
|
582
|
+
getSystemPrompt: () => [...this.ctx.session.systemPrompt],
|
|
564
583
|
});
|
|
565
584
|
} catch (err) {
|
|
566
585
|
this.showToolError(registeredTool.definition.name, err instanceof Error ? err.message : String(err));
|
|
@@ -970,7 +989,7 @@ export class ExtensionUiController {
|
|
|
970
989
|
// For non-streaming cases with display=true, update UI
|
|
971
990
|
// (streaming cases update via message_end event)
|
|
972
991
|
if (!this.ctx.isBackgrounded && !wasStreaming && shouldDisplay) {
|
|
973
|
-
this.ctx.rebuildChatFromMessages();
|
|
992
|
+
this.ctx.rebuildChatFromMessages("reconcile-same-transcript");
|
|
974
993
|
}
|
|
975
994
|
}
|
|
976
995
|
|
|
@@ -331,6 +331,13 @@ export class InputController {
|
|
|
331
331
|
// Wire up extension shortcuts
|
|
332
332
|
this.registerExtensionShortcuts();
|
|
333
333
|
|
|
334
|
+
for (const key of this.ctx.keybindings.getKeys("app.irc.sidebar.toggle")) {
|
|
335
|
+
this.ctx.editor.setCustomKeyHandler(key, () => {
|
|
336
|
+
this.ctx.toggleIrcSidebar();
|
|
337
|
+
return true;
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
|
|
334
341
|
const planModeKeys = this.ctx.keybindings.getKeys("app.plan.toggle");
|
|
335
342
|
for (const key of planModeKeys) {
|
|
336
343
|
this.ctx.editor.setCustomKeyHandler(key, () => void this.ctx.handlePlanModeCommand());
|
|
@@ -1344,8 +1351,7 @@ export class InputController {
|
|
|
1344
1351
|
if (this.ctx.streamingComponent) {
|
|
1345
1352
|
this.ctx.chatContainer.detachChild(this.ctx.streamingComponent);
|
|
1346
1353
|
}
|
|
1347
|
-
this.ctx.
|
|
1348
|
-
this.ctx.rebuildChatFromMessages();
|
|
1354
|
+
this.ctx.rebuildChatFromMessages("reconcile-same-transcript");
|
|
1349
1355
|
|
|
1350
1356
|
// If streaming, re-add the streaming component with updated visibility and re-render
|
|
1351
1357
|
if (this.ctx.streamingComponent && this.ctx.streamingMessage) {
|
|
@@ -2,7 +2,7 @@ import { ThinkingLevel } from "@sayknow-cli/agent-core";
|
|
|
2
2
|
import { getOAuthProviders } from "@sayknow-cli/ai/utils/oauth";
|
|
3
3
|
import type { OAuthProvider } from "@sayknow-cli/ai/utils/oauth/types";
|
|
4
4
|
import type { Component, OverlayHandle } from "@sayknow-cli/tui";
|
|
5
|
-
import { Input, Loader, Spacer, Text } from "@sayknow-cli/tui";
|
|
5
|
+
import { Input, isPetMode, Loader, Spacer, Text } from "@sayknow-cli/tui";
|
|
6
6
|
import { getAgentDbPath, getProjectDir } from "@sayknow-cli/utils";
|
|
7
7
|
import {
|
|
8
8
|
activateModelProfile,
|
|
@@ -71,11 +71,13 @@ import { HistorySearchComponent } from "../components/history-search";
|
|
|
71
71
|
import { JobsOverlayComponent } from "../components/jobs-overlay";
|
|
72
72
|
import { ModelSelectorComponent } from "../components/model-selector";
|
|
73
73
|
import { OAuthSelectorComponent } from "../components/oauth-selector";
|
|
74
|
+
import { PetSelectorComponent } from "../components/pet-selector";
|
|
74
75
|
import { PluginSelectorComponent } from "../components/plugin-selector";
|
|
75
76
|
import {
|
|
76
77
|
type ProviderOnboardingAction,
|
|
77
78
|
ProviderOnboardingSelectorComponent,
|
|
78
79
|
} from "../components/provider-onboarding-selector";
|
|
80
|
+
import type { PetMode } from "../components/sayknow-pet-widget";
|
|
79
81
|
import { SessionObserverOverlayComponent } from "../components/session-observer-overlay";
|
|
80
82
|
import { SessionSelectorComponent } from "../components/session-selector";
|
|
81
83
|
import { SettingsSelectorComponent } from "../components/settings-selector";
|
|
@@ -155,9 +157,15 @@ export class SelectorController {
|
|
|
155
157
|
*/
|
|
156
158
|
showSelector(create: (done: () => void) => { component: Component; focus: Component }): void {
|
|
157
159
|
const done = () => {
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
160
|
+
// Prefer the pet-aware composer restore (InteractiveMode.restoreComposer); fall back
|
|
161
|
+
// to a plain editor swap for contexts that predate it (e.g. lightweight test doubles).
|
|
162
|
+
if (typeof this.ctx.restoreComposer === "function") {
|
|
163
|
+
this.ctx.restoreComposer();
|
|
164
|
+
} else {
|
|
165
|
+
this.ctx.editorContainer.clear();
|
|
166
|
+
this.ctx.editorContainer.addChild(this.ctx.editor);
|
|
167
|
+
this.ctx.ui.setFocus(this.ctx.editor);
|
|
168
|
+
}
|
|
161
169
|
};
|
|
162
170
|
const { component, focus } = create(done);
|
|
163
171
|
this.ctx.editorContainer.clear();
|
|
@@ -400,6 +408,9 @@ export class SelectorController {
|
|
|
400
408
|
this.#refreshThemeUi();
|
|
401
409
|
});
|
|
402
410
|
},
|
|
411
|
+
onPetPreview: mode => {
|
|
412
|
+
this.ctx.previewPetMode(mode as PetMode);
|
|
413
|
+
},
|
|
403
414
|
onStatusLinePreview: previewSettings => {
|
|
404
415
|
// Update status line with preview settings
|
|
405
416
|
this.ctx.statusLine.updateSettings({
|
|
@@ -474,6 +485,29 @@ export class SelectorController {
|
|
|
474
485
|
});
|
|
475
486
|
}
|
|
476
487
|
|
|
488
|
+
showPetSelector(): void {
|
|
489
|
+
const stored = settings.get("pet.mode");
|
|
490
|
+
const initial: PetMode = isPetMode(stored) ? stored : "off";
|
|
491
|
+
this.showSelector(done => {
|
|
492
|
+
// Live-preview via previewMode (no editor re-mount, so the overlay stays);
|
|
493
|
+
// Enter commits + persists, Esc restores the initial skin.
|
|
494
|
+
const selector = new PetSelectorComponent(
|
|
495
|
+
initial,
|
|
496
|
+
mode => {
|
|
497
|
+
this.ctx.setPetMode(mode);
|
|
498
|
+
done();
|
|
499
|
+
},
|
|
500
|
+
() => {
|
|
501
|
+
this.ctx.previewPetMode(initial);
|
|
502
|
+
done();
|
|
503
|
+
},
|
|
504
|
+
mode => {
|
|
505
|
+
this.ctx.previewPetMode(mode);
|
|
506
|
+
},
|
|
507
|
+
);
|
|
508
|
+
return { component: selector, focus: selector.getSelectList() };
|
|
509
|
+
});
|
|
510
|
+
}
|
|
477
511
|
showHistorySearch(): void {
|
|
478
512
|
const historyStorage = this.ctx.historyStorage;
|
|
479
513
|
if (!historyStorage) return;
|
|
@@ -612,8 +646,7 @@ export class SelectorController {
|
|
|
612
646
|
child.setHideThinkingBlock(value as boolean);
|
|
613
647
|
}
|
|
614
648
|
}
|
|
615
|
-
this.ctx.
|
|
616
|
-
this.ctx.rebuildChatFromMessages();
|
|
649
|
+
this.ctx.rebuildChatFromMessages("reconcile-same-transcript");
|
|
617
650
|
break;
|
|
618
651
|
case "theme": {
|
|
619
652
|
setTheme(value as string, true).then(result => {
|
|
@@ -626,6 +659,12 @@ export class SelectorController {
|
|
|
626
659
|
});
|
|
627
660
|
break;
|
|
628
661
|
}
|
|
662
|
+
case "pet.mode":
|
|
663
|
+
// The settings submenu already persisted the value; apply it to the live
|
|
664
|
+
// widget via previewMode (the settings overlay is still open, so a full
|
|
665
|
+
// re-mount would tear it down — restoreComposer re-mounts on close).
|
|
666
|
+
this.ctx.previewPetMode(value as PetMode);
|
|
667
|
+
break;
|
|
629
668
|
case "symbolPreset": {
|
|
630
669
|
setSymbolPreset(value as "unicode" | "nerd" | "ascii").then(() => {
|
|
631
670
|
this.ctx.statusLine.invalidate();
|
|
@@ -697,6 +736,12 @@ export class SelectorController {
|
|
|
697
736
|
this.ctx.ui.requestRender();
|
|
698
737
|
break;
|
|
699
738
|
}
|
|
739
|
+
case "irc.enabled":
|
|
740
|
+
case "irc.sidebar.enabled":
|
|
741
|
+
this.ctx.applyIrcSidebarAvailability(
|
|
742
|
+
this.ctx.settings.get("irc.enabled") === true && this.ctx.settings.get("irc.sidebar.enabled") === true,
|
|
743
|
+
);
|
|
744
|
+
break;
|
|
700
745
|
|
|
701
746
|
// Provider settings - update runtime preferences
|
|
702
747
|
case "providers.webSearch":
|
|
@@ -1075,9 +1120,9 @@ export class SelectorController {
|
|
|
1075
1120
|
this.ctx.ui.requestRender();
|
|
1076
1121
|
return;
|
|
1077
1122
|
}
|
|
1123
|
+
this.ctx.resetIrcSidebarSession();
|
|
1078
1124
|
|
|
1079
|
-
this.ctx.
|
|
1080
|
-
this.ctx.renderInitialMessages();
|
|
1125
|
+
this.ctx.rebuildInitialMessages("replace-identity");
|
|
1081
1126
|
this.ctx.editor.setText(result.selectedText);
|
|
1082
1127
|
done();
|
|
1083
1128
|
this.ctx.showStatus("Branched to new session");
|
|
@@ -1187,8 +1232,7 @@ export class SelectorController {
|
|
|
1187
1232
|
}
|
|
1188
1233
|
|
|
1189
1234
|
// Update UI — pass the context built by navigateTree to skip a second O(N) walk.
|
|
1190
|
-
this.ctx.
|
|
1191
|
-
this.ctx.renderInitialMessages(result.sessionContext);
|
|
1235
|
+
this.ctx.rebuildInitialMessages("reconcile-same-transcript", result.sessionContext);
|
|
1192
1236
|
await this.ctx.reloadTodos();
|
|
1193
1237
|
if (result.editorText && !this.ctx.editor.getText().trim()) {
|
|
1194
1238
|
this.ctx.editor.setText(result.editorText);
|
|
@@ -1289,6 +1333,8 @@ export class SelectorController {
|
|
|
1289
1333
|
if (!detached) {
|
|
1290
1334
|
return false;
|
|
1291
1335
|
}
|
|
1336
|
+
this.ctx.resetIrcSidebarSession();
|
|
1337
|
+
|
|
1292
1338
|
this.#refreshSessionTerminalTitle();
|
|
1293
1339
|
|
|
1294
1340
|
this.#clearTransientSessionUi();
|
|
@@ -1296,23 +1342,24 @@ export class SelectorController {
|
|
|
1296
1342
|
this.ctx.statusLine.setSessionStartTime(Date.now());
|
|
1297
1343
|
this.ctx.updateEditorTopBorder();
|
|
1298
1344
|
this.ctx.updateEditorBorderColor();
|
|
1299
|
-
this.ctx.
|
|
1345
|
+
this.ctx.rebuildInitialMessages("replace-identity");
|
|
1300
1346
|
await this.ctx.reloadTodos();
|
|
1301
1347
|
this.ctx.ui.requestRender();
|
|
1302
1348
|
return true;
|
|
1303
1349
|
}
|
|
1304
1350
|
|
|
1305
1351
|
async handleResumeSession(sessionPath: string): Promise<void> {
|
|
1352
|
+
const previousSessionId = this.ctx.sessionManager.getSessionId();
|
|
1306
1353
|
this.#clearTransientSessionUi();
|
|
1307
1354
|
|
|
1308
1355
|
// Switch session via AgentSession (emits hook and tool session events)
|
|
1309
|
-
await this.ctx.session.switchSession(sessionPath);
|
|
1356
|
+
if (!(await this.ctx.session.switchSession(sessionPath))) return;
|
|
1357
|
+
const switchingToDifferentSession = previousSessionId !== this.ctx.sessionManager.getSessionId();
|
|
1358
|
+
if (switchingToDifferentSession) this.ctx.resetIrcSidebarSession();
|
|
1310
1359
|
this.#refreshSessionTerminalTitle();
|
|
1311
1360
|
this.ctx.updateEditorBorderColor();
|
|
1312
1361
|
|
|
1313
|
-
|
|
1314
|
-
this.ctx.chatContainer.clear();
|
|
1315
|
-
this.ctx.renderInitialMessages();
|
|
1362
|
+
this.ctx.rebuildInitialMessages(switchingToDifferentSession ? "replace-identity" : "reconcile-same-transcript");
|
|
1316
1363
|
await this.ctx.reloadTodos();
|
|
1317
1364
|
this.ctx.showStatus("Resumed session");
|
|
1318
1365
|
}
|