@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
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
* RPC server can route `negotiate_unattended` + `workflow_gate_response` here.
|
|
16
16
|
*/
|
|
17
17
|
import type { Model } from "@gajae-code/ai";
|
|
18
|
+
import type { AskSelectedAckOutcome } from "../../../tools";
|
|
18
19
|
import type {
|
|
19
20
|
RpcCommand,
|
|
20
21
|
RpcUnattendedAccepted,
|
|
@@ -30,7 +31,53 @@ import {
|
|
|
30
31
|
type UnattendedAuditEvent,
|
|
31
32
|
UnattendedRunController,
|
|
32
33
|
} from "./unattended-run-controller";
|
|
33
|
-
import {
|
|
34
|
+
import {
|
|
35
|
+
type GateStore,
|
|
36
|
+
MemoryGateStore,
|
|
37
|
+
type OpenGateInput,
|
|
38
|
+
type PersistedAckPolicy,
|
|
39
|
+
type PersistedGate,
|
|
40
|
+
type PersistedSemanticDisposition,
|
|
41
|
+
WorkflowGateBroker,
|
|
42
|
+
} from "./workflow-gate-broker";
|
|
43
|
+
|
|
44
|
+
export interface WorkflowGateTerminalController {
|
|
45
|
+
completeGateInteractions(gateId: string): void | Promise<void>;
|
|
46
|
+
cancelGateInteractions(gateId: string, reason: string): void | Promise<void>;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export interface WorkflowGateTerminalEvent {
|
|
50
|
+
gateId: string;
|
|
51
|
+
kind: "accepted" | "cancelled";
|
|
52
|
+
origin?: "rpc" | "bridge" | "other" | "telegram_notification";
|
|
53
|
+
reason?: string;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export interface NotificationGateResolutionOptions {
|
|
57
|
+
interactionActionId: string;
|
|
58
|
+
replyReceiptId: string;
|
|
59
|
+
answerJson: string;
|
|
60
|
+
idempotencyKey?: string;
|
|
61
|
+
requestSelectedAck(input: {
|
|
62
|
+
replyReceiptId: string;
|
|
63
|
+
actionId: string;
|
|
64
|
+
commitKey: string;
|
|
65
|
+
daemonDeadlineAt: number;
|
|
66
|
+
hostTimeoutMs: number;
|
|
67
|
+
}): Promise<AskSelectedAckOutcome>;
|
|
68
|
+
resolveClaim(): void;
|
|
69
|
+
closeClaimInvalid(reason: string): void;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export interface AskSelectedAckRecoveryParticipant {
|
|
73
|
+
requestRecoveredAskSelectedAck(input: {
|
|
74
|
+
sessionId: string;
|
|
75
|
+
actionId: string;
|
|
76
|
+
commitKey: string;
|
|
77
|
+
deadlineAt: number;
|
|
78
|
+
hostTimeoutMs: number;
|
|
79
|
+
}): Promise<AskSelectedAckOutcome>;
|
|
80
|
+
}
|
|
34
81
|
|
|
35
82
|
/**
|
|
36
83
|
* RPC commands that perform agent/tool work and therefore consume one unit of the
|
|
@@ -66,7 +113,13 @@ export interface WorkflowGateEmitter {
|
|
|
66
113
|
*/
|
|
67
114
|
onGateEmitted?(listener: (gate: RpcWorkflowGate) => void): () => void;
|
|
68
115
|
resolveGate?(response: RpcWorkflowGateResponse): Promise<RpcWorkflowGateResolution>;
|
|
116
|
+
resolveGateFromNotification?(
|
|
117
|
+
response: RpcWorkflowGateResponse,
|
|
118
|
+
options: NotificationGateResolutionOptions,
|
|
119
|
+
): Promise<RpcWorkflowGateResolution>;
|
|
120
|
+
registerGateTerminalController?(controller: WorkflowGateTerminalController): () => void;
|
|
69
121
|
listPendingGates?(): RpcWorkflowGate[];
|
|
122
|
+
setAckRecoveryParticipant?(participant: AskSelectedAckRecoveryParticipant | null): void;
|
|
70
123
|
}
|
|
71
124
|
|
|
72
125
|
export interface UnattendedSessionOptions {
|
|
@@ -91,6 +144,12 @@ export class UnattendedSessionControlPlane implements RpcUnattendedControlPlane,
|
|
|
91
144
|
readonly #pending = new Map<string, { resolve: (answer: unknown) => void; reject: (err: Error) => void }>();
|
|
92
145
|
readonly #earlyAnswers = new Map<string, unknown>();
|
|
93
146
|
readonly #gateListeners = new Set<(gate: RpcWorkflowGate) => void>();
|
|
147
|
+
#terminalController: WorkflowGateTerminalController | undefined;
|
|
148
|
+
#brokerReady = false;
|
|
149
|
+
#recoveryPromise: Promise<void> | undefined;
|
|
150
|
+
#disposed = false;
|
|
151
|
+
#recoveryParticipant: AskSelectedAckRecoveryParticipant | undefined;
|
|
152
|
+
#participantReady = Promise.withResolvers<void>();
|
|
94
153
|
|
|
95
154
|
constructor(private readonly opts: UnattendedSessionOptions) {}
|
|
96
155
|
|
|
@@ -108,6 +167,38 @@ export class UnattendedSessionControlPlane implements RpcUnattendedControlPlane,
|
|
|
108
167
|
return this.#controller;
|
|
109
168
|
}
|
|
110
169
|
|
|
170
|
+
registerGateTerminalController(controller: WorkflowGateTerminalController): () => void {
|
|
171
|
+
if (this.#terminalController && this.#terminalController !== controller) {
|
|
172
|
+
throw new Error("a workflow gate terminal controller is already registered");
|
|
173
|
+
}
|
|
174
|
+
this.#terminalController = controller;
|
|
175
|
+
return () => {
|
|
176
|
+
if (this.#terminalController === controller) this.#terminalController = undefined;
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
setAckRecoveryParticipant(participant: AskSelectedAckRecoveryParticipant | null): void {
|
|
181
|
+
this.#recoveryParticipant = participant ?? undefined;
|
|
182
|
+
if (participant) this.#participantReady.resolve();
|
|
183
|
+
void this.startRecoveryOnce();
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
async #completeGateInteractions(gateId: string): Promise<void> {
|
|
187
|
+
try {
|
|
188
|
+
await this.#terminalController?.completeGateInteractions(gateId);
|
|
189
|
+
} catch (error) {
|
|
190
|
+
this.opts.audit?.({ event: "gate_interaction_completion_failed", gate_id: gateId, error: String(error) });
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
async #cancelGateInteractions(gateId: string, reason: string): Promise<void> {
|
|
195
|
+
try {
|
|
196
|
+
await this.#terminalController?.cancelGateInteractions(gateId, reason);
|
|
197
|
+
} catch (error) {
|
|
198
|
+
this.opts.audit?.({ event: "gate_interaction_cancellation_failed", gate_id: gateId, error: String(error) });
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
111
202
|
negotiate(declaration: RpcUnattendedDeclaration): RpcUnattendedAccepted {
|
|
112
203
|
const controller = UnattendedRunController.negotiate(declaration, {
|
|
113
204
|
runId: this.opts.runId,
|
|
@@ -140,7 +231,11 @@ export class UnattendedSessionControlPlane implements RpcUnattendedControlPlane,
|
|
|
140
231
|
// openGate returns and the caller starts awaiting.
|
|
141
232
|
this.#earlyAnswers.set(gate.gate_id, answer);
|
|
142
233
|
},
|
|
234
|
+
finalizeAccepted: record => this.#finalizeAcceptedGate(record),
|
|
143
235
|
});
|
|
236
|
+
this.#brokerReady = true;
|
|
237
|
+
void this.startRecoveryOnce();
|
|
238
|
+
|
|
144
239
|
return {
|
|
145
240
|
run_id: this.opts.runId,
|
|
146
241
|
actor: controller.actor,
|
|
@@ -190,12 +285,203 @@ export class UnattendedSessionControlPlane implements RpcUnattendedControlPlane,
|
|
|
190
285
|
}
|
|
191
286
|
|
|
192
287
|
resolveGate(response: RpcWorkflowGateResponse): Promise<RpcWorkflowGateResolution> {
|
|
288
|
+
if (this.#disposed) return Promise.reject(new Error("unattended session is disposed"));
|
|
193
289
|
if (!this.#broker) {
|
|
194
290
|
return Promise.reject(new Error("workflow gates are not available until unattended mode is negotiated"));
|
|
195
291
|
}
|
|
196
292
|
return this.#broker.resolve(response);
|
|
197
293
|
}
|
|
198
294
|
|
|
295
|
+
async resolveGateFromNotification(
|
|
296
|
+
response: RpcWorkflowGateResponse,
|
|
297
|
+
options: NotificationGateResolutionOptions,
|
|
298
|
+
): Promise<RpcWorkflowGateResolution> {
|
|
299
|
+
let claimSettled = false;
|
|
300
|
+
const resolveClaim = () => {
|
|
301
|
+
if (claimSettled) return;
|
|
302
|
+
claimSettled = true;
|
|
303
|
+
try {
|
|
304
|
+
options.resolveClaim();
|
|
305
|
+
} catch (error) {
|
|
306
|
+
this.opts.audit?.({
|
|
307
|
+
event: "notification_claim_resolution_failed",
|
|
308
|
+
gate_id: response.gate_id,
|
|
309
|
+
error: String(error),
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
};
|
|
313
|
+
const closeClaimInvalid = (reason: string) => {
|
|
314
|
+
if (claimSettled) return;
|
|
315
|
+
claimSettled = true;
|
|
316
|
+
try {
|
|
317
|
+
options.closeClaimInvalid(reason);
|
|
318
|
+
} catch (error) {
|
|
319
|
+
this.opts.audit?.({
|
|
320
|
+
event: "notification_claim_close_failed",
|
|
321
|
+
gate_id: response.gate_id,
|
|
322
|
+
error: String(error),
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
};
|
|
326
|
+
if (this.#disposed) {
|
|
327
|
+
closeClaimInvalid("unattended_session_disposed");
|
|
328
|
+
throw new Error("unattended session is disposed");
|
|
329
|
+
}
|
|
330
|
+
if (!this.#broker) {
|
|
331
|
+
closeClaimInvalid("workflow_gate_unavailable");
|
|
332
|
+
throw new Error("workflow gates are not available until unattended mode is negotiated");
|
|
333
|
+
}
|
|
334
|
+
try {
|
|
335
|
+
JSON.parse(options.answerJson);
|
|
336
|
+
} catch {
|
|
337
|
+
closeClaimInvalid("invalid_structured_answer");
|
|
338
|
+
return {
|
|
339
|
+
gate_id: response.gate_id,
|
|
340
|
+
status: "rejected",
|
|
341
|
+
answer_hash: "",
|
|
342
|
+
resolved_at: new Date().toISOString(),
|
|
343
|
+
error: this.#broker.validationError(response.gate_id, "json_parse", "invalid structured answer"),
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
let disposition: PersistedSemanticDisposition;
|
|
347
|
+
try {
|
|
348
|
+
disposition = this.#broker.classifyDisposition(response.gate_id, response.answer);
|
|
349
|
+
} catch (error) {
|
|
350
|
+
closeClaimInvalid(error instanceof Error ? error.message : "invalid_answer");
|
|
351
|
+
return {
|
|
352
|
+
gate_id: response.gate_id,
|
|
353
|
+
status: "rejected",
|
|
354
|
+
answer_hash: "",
|
|
355
|
+
resolved_at: new Date().toISOString(),
|
|
356
|
+
error: this.#broker.validationError(
|
|
357
|
+
response.gate_id,
|
|
358
|
+
"semantic_disposition",
|
|
359
|
+
error instanceof Error ? error.message : "invalid answer",
|
|
360
|
+
),
|
|
361
|
+
};
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
const commitKey = `${response.gate_id}:${options.idempotencyKey ?? options.replyReceiptId}`;
|
|
365
|
+
const ackPolicy: PersistedAckPolicy =
|
|
366
|
+
disposition === "commit"
|
|
367
|
+
? {
|
|
368
|
+
kind: "telegram_selected_v1",
|
|
369
|
+
commitKey,
|
|
370
|
+
actionId: options.interactionActionId,
|
|
371
|
+
state: "pending",
|
|
372
|
+
updatedAt: new Date().toISOString(),
|
|
373
|
+
}
|
|
374
|
+
: { kind: "none", reason: "semantic_noncommit" };
|
|
375
|
+
let resolution: RpcWorkflowGateResolution;
|
|
376
|
+
try {
|
|
377
|
+
resolution = await this.#broker.resolve(response, {
|
|
378
|
+
resolutionOrigin: { kind: "telegram_notification", interactionActionId: options.interactionActionId },
|
|
379
|
+
ackPolicy,
|
|
380
|
+
semanticDisposition: disposition,
|
|
381
|
+
|
|
382
|
+
beforeAdvance: async () => {
|
|
383
|
+
if (ackPolicy.kind === "telegram_selected_v1") {
|
|
384
|
+
this.#broker?.updateAckPolicy(response.gate_id, {
|
|
385
|
+
...ackPolicy,
|
|
386
|
+
state: "attempt_started",
|
|
387
|
+
updatedAt: new Date().toISOString(),
|
|
388
|
+
});
|
|
389
|
+
let outcome: AskSelectedAckOutcome;
|
|
390
|
+
try {
|
|
391
|
+
outcome = await options.requestSelectedAck({
|
|
392
|
+
replyReceiptId: options.replyReceiptId,
|
|
393
|
+
actionId: options.interactionActionId,
|
|
394
|
+
commitKey,
|
|
395
|
+
daemonDeadlineAt: Date.now() + 8_000,
|
|
396
|
+
hostTimeoutMs: 10_000,
|
|
397
|
+
});
|
|
398
|
+
} catch {
|
|
399
|
+
outcome = { status: "unknown", reason: "host_timeout" };
|
|
400
|
+
}
|
|
401
|
+
this.#broker?.updateAckPolicy(response.gate_id, {
|
|
402
|
+
...ackPolicy,
|
|
403
|
+
state: outcome.status,
|
|
404
|
+
outcome,
|
|
405
|
+
updatedAt: new Date().toISOString(),
|
|
406
|
+
});
|
|
407
|
+
}
|
|
408
|
+
resolveClaim();
|
|
409
|
+
},
|
|
410
|
+
});
|
|
411
|
+
} catch (error) {
|
|
412
|
+
closeClaimInvalid(error instanceof Error ? error.message : "invalid_answer");
|
|
413
|
+
throw error;
|
|
414
|
+
}
|
|
415
|
+
if (resolution.status === "accepted") resolveClaim();
|
|
416
|
+
else closeClaimInvalid(resolution.error?.code ?? "invalid_answer");
|
|
417
|
+
return resolution;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
async startRecoveryOnce(options: { participantGraceMs?: number } = {}): Promise<void> {
|
|
421
|
+
if (this.#disposed || !this.#brokerReady) return;
|
|
422
|
+
if (this.#recoveryPromise) return this.#recoveryPromise;
|
|
423
|
+
this.#recoveryPromise = (async () => {
|
|
424
|
+
if (!this.#recoveryParticipant) {
|
|
425
|
+
const grace = options.participantGraceMs ?? 2_000;
|
|
426
|
+
await Promise.race([
|
|
427
|
+
this.#participantReady.promise,
|
|
428
|
+
new Promise<void>(resolve => setTimeout(resolve, grace)),
|
|
429
|
+
]);
|
|
430
|
+
}
|
|
431
|
+
if (!this.#disposed) await this.#broker?.recover();
|
|
432
|
+
})();
|
|
433
|
+
return this.#recoveryPromise;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
async #finalizeAcceptedGate(record: PersistedGate): Promise<void> {
|
|
437
|
+
const policy = record.ackPolicy;
|
|
438
|
+
if (policy?.kind === "telegram_selected_v1" && policy.state === "pending") {
|
|
439
|
+
let outcome: AskSelectedAckOutcome;
|
|
440
|
+
const participant = this.#recoveryParticipant;
|
|
441
|
+
if (!participant || !this.opts.sessionId) {
|
|
442
|
+
outcome = { status: "failed", reason: "no_participant" };
|
|
443
|
+
} else {
|
|
444
|
+
this.#broker?.updateAckPolicy(record.gate.gate_id, {
|
|
445
|
+
...policy,
|
|
446
|
+
state: "attempt_started",
|
|
447
|
+
updatedAt: new Date().toISOString(),
|
|
448
|
+
});
|
|
449
|
+
try {
|
|
450
|
+
outcome = await participant.requestRecoveredAskSelectedAck({
|
|
451
|
+
sessionId: this.opts.sessionId,
|
|
452
|
+
actionId: policy.actionId,
|
|
453
|
+
commitKey: policy.commitKey,
|
|
454
|
+
deadlineAt: Date.now() + 8_000,
|
|
455
|
+
hostTimeoutMs: 10_000,
|
|
456
|
+
});
|
|
457
|
+
} catch {
|
|
458
|
+
outcome = { status: "unknown", reason: "host_timeout" };
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
this.#broker?.updateAckPolicy(record.gate.gate_id, {
|
|
462
|
+
...policy,
|
|
463
|
+
state: outcome.status,
|
|
464
|
+
outcome,
|
|
465
|
+
updatedAt: new Date().toISOString(),
|
|
466
|
+
});
|
|
467
|
+
}
|
|
468
|
+
if (policy?.kind === "telegram_selected_v1" && policy.state === "attempt_started") {
|
|
469
|
+
this.#broker?.updateAckPolicy(record.gate.gate_id, {
|
|
470
|
+
...policy,
|
|
471
|
+
state: "unknown",
|
|
472
|
+
outcome: { status: "unknown", reason: "shutdown" },
|
|
473
|
+
updatedAt: new Date().toISOString(),
|
|
474
|
+
});
|
|
475
|
+
}
|
|
476
|
+
await this.#completeGateInteractions(record.gate.gate_id);
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
dispose(reason = "session_shutdown"): void {
|
|
480
|
+
this.#disposed = true;
|
|
481
|
+
for (const gateId of this.#pending.keys()) void this.#cancelGateInteractions(gateId, reason);
|
|
482
|
+
this.#rejectAllPending(new Error(`unattended session disposed: ${reason}`));
|
|
483
|
+
}
|
|
484
|
+
|
|
199
485
|
listPendingGates(): import("../../rpc/rpc-types").RpcWorkflowGate[] {
|
|
200
486
|
const store = this.opts.store;
|
|
201
487
|
if (!store) return [];
|
|
@@ -206,6 +492,7 @@ export class UnattendedSessionControlPlane implements RpcUnattendedControlPlane,
|
|
|
206
492
|
}
|
|
207
493
|
|
|
208
494
|
emitGate(input: OpenGateInput): Promise<unknown> {
|
|
495
|
+
if (this.#disposed) return Promise.reject(new Error("unattended session is disposed"));
|
|
209
496
|
if (!this.#broker) {
|
|
210
497
|
return Promise.reject(new Error("cannot emit a workflow gate before unattended mode is negotiated"));
|
|
211
498
|
}
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
*/
|
|
14
14
|
import { closeSync, fsyncSync, mkdirSync, openSync, readFileSync, renameSync, writeFileSync } from "node:fs";
|
|
15
15
|
import * as path from "node:path";
|
|
16
|
+
import type { AskSelectedAckOutcome } from "../../../tools";
|
|
16
17
|
import type {
|
|
17
18
|
RpcWorkflowGate,
|
|
18
19
|
RpcWorkflowGateContext,
|
|
@@ -20,11 +21,36 @@ import type {
|
|
|
20
21
|
RpcWorkflowGateOption,
|
|
21
22
|
RpcWorkflowGateResolution,
|
|
22
23
|
RpcWorkflowGateResponse,
|
|
24
|
+
RpcWorkflowGateValidationError,
|
|
23
25
|
RpcWorkflowStage,
|
|
24
26
|
} from "../../rpc/rpc-types";
|
|
25
27
|
import { RESERVED_WORKFLOW_STAGES } from "../../rpc/rpc-types";
|
|
28
|
+
import { classifyAskGateDisposition } from "./deep-interview-gate";
|
|
26
29
|
import { answerHashOf, canonicalJson, compileGateSchema, schemaHash, validateGateAnswer } from "./workflow-gate-schema";
|
|
27
30
|
|
|
31
|
+
export type PersistedSemanticDisposition = "commit" | "resolve_without_commit";
|
|
32
|
+
export type PersistedResolutionOrigin =
|
|
33
|
+
| { kind: "generic"; channel: "rpc" | "bridge" | "other" }
|
|
34
|
+
| { kind: "telegram_notification"; interactionActionId: string }
|
|
35
|
+
| { kind: "legacy_unknown" };
|
|
36
|
+
export type PersistedAckPolicy =
|
|
37
|
+
| { kind: "none"; reason: "non_telegram" | "semantic_noncommit" | "legacy_unproven" }
|
|
38
|
+
| {
|
|
39
|
+
kind: "telegram_selected_v1";
|
|
40
|
+
commitKey: string;
|
|
41
|
+
actionId: string;
|
|
42
|
+
state: "pending" | "attempt_started" | "delivered" | "failed" | "unknown";
|
|
43
|
+
outcome?: AskSelectedAckOutcome;
|
|
44
|
+
updatedAt: string;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
export interface GateResolutionOptions {
|
|
48
|
+
semanticDisposition?: PersistedSemanticDisposition;
|
|
49
|
+
resolutionOrigin?: PersistedResolutionOrigin;
|
|
50
|
+
ackPolicy?: PersistedAckPolicy;
|
|
51
|
+
beforeAdvance?: () => Promise<void>;
|
|
52
|
+
}
|
|
53
|
+
|
|
28
54
|
const V1_STAGES: readonly RpcWorkflowStage[] = ["deep-interview", "ralplan", "ultragoal"];
|
|
29
55
|
|
|
30
56
|
export interface PersistedGate {
|
|
@@ -36,6 +62,9 @@ export interface PersistedGate {
|
|
|
36
62
|
answer?: unknown;
|
|
37
63
|
resolution?: RpcWorkflowGateResolution;
|
|
38
64
|
advanced: boolean;
|
|
65
|
+
semanticDisposition?: PersistedSemanticDisposition;
|
|
66
|
+
resolutionOrigin?: PersistedResolutionOrigin;
|
|
67
|
+
ackPolicy?: PersistedAckPolicy;
|
|
39
68
|
}
|
|
40
69
|
|
|
41
70
|
export interface GateStore {
|
|
@@ -153,6 +182,9 @@ export interface BrokerHooks {
|
|
|
153
182
|
* it for any gate left `accepted` but not `advanced` by a crash.
|
|
154
183
|
*/
|
|
155
184
|
advance?(gate: RpcWorkflowGate, answer: unknown): void | Promise<void>;
|
|
185
|
+
/** Runs after durable acceptance and before advance, including crash recovery. */
|
|
186
|
+
finalizeAccepted?(record: PersistedGate): Promise<void>;
|
|
187
|
+
|
|
156
188
|
/** Append-only audit sink. */
|
|
157
189
|
audit?(event: GateAuditEvent): void;
|
|
158
190
|
}
|
|
@@ -176,12 +208,51 @@ export class WorkflowGateBrokerError extends Error {
|
|
|
176
208
|
}
|
|
177
209
|
|
|
178
210
|
export class WorkflowGateBroker {
|
|
211
|
+
private readonly recovering = new Set<string>();
|
|
212
|
+
private readonly gateLocks = new Map<string, Promise<void>>();
|
|
213
|
+
|
|
214
|
+
private async withGateLock<T>(gateId: string, operation: () => Promise<T>): Promise<T> {
|
|
215
|
+
const previous = this.gateLocks.get(gateId) ?? Promise.resolve();
|
|
216
|
+
const release = Promise.withResolvers<void>();
|
|
217
|
+
const current = previous.then(() => release.promise);
|
|
218
|
+
this.gateLocks.set(gateId, current);
|
|
219
|
+
await previous;
|
|
220
|
+
try {
|
|
221
|
+
return await operation();
|
|
222
|
+
} finally {
|
|
223
|
+
release.resolve();
|
|
224
|
+
if (this.gateLocks.get(gateId) === current) this.gateLocks.delete(gateId);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
179
228
|
constructor(
|
|
180
229
|
private readonly runId: string,
|
|
181
230
|
private readonly store: GateStore,
|
|
182
231
|
private readonly hooks: BrokerHooks = {},
|
|
183
232
|
) {}
|
|
184
233
|
|
|
234
|
+
classifyDisposition(gateId: string, answer: unknown): PersistedSemanticDisposition {
|
|
235
|
+
const record = this.store.get(gateId);
|
|
236
|
+
if (!record) throw new WorkflowGateBrokerError("unknown_gate", `no pending gate ${gateId}`);
|
|
237
|
+
return classifyAskGateDisposition(record.gate, answer);
|
|
238
|
+
}
|
|
239
|
+
validationError(gateId: string, keyword: string, message: string): RpcWorkflowGateValidationError {
|
|
240
|
+
const record = this.store.get(gateId);
|
|
241
|
+
return {
|
|
242
|
+
code: "invalid_workflow_gate_answer",
|
|
243
|
+
gate_id: gateId,
|
|
244
|
+
schema_hash: record ? schemaHash(record.gate.schema) : "",
|
|
245
|
+
errors: [{ path: "$", keyword, message }],
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
updateAckPolicy(gateId: string, ackPolicy: PersistedAckPolicy): void {
|
|
250
|
+
const record = this.store.get(gateId);
|
|
251
|
+
if (record?.status !== "accepted")
|
|
252
|
+
throw new WorkflowGateBrokerError("unknown_gate", `no accepted gate ${gateId}`);
|
|
253
|
+
this.store.put({ ...record, ackPolicy });
|
|
254
|
+
}
|
|
255
|
+
|
|
185
256
|
private runShort(): string {
|
|
186
257
|
return this.runId.replace(/[^a-zA-Z0-9]/g, "").slice(-8) || "run";
|
|
187
258
|
}
|
|
@@ -222,7 +293,17 @@ export class WorkflowGateBroker {
|
|
|
222
293
|
* On success the resolution is persisted BEFORE `advance` is invoked exactly
|
|
223
294
|
* once. Invalid answers leave the gate pending (per #315 acceptance).
|
|
224
295
|
*/
|
|
225
|
-
async resolve(
|
|
296
|
+
async resolve(
|
|
297
|
+
response: RpcWorkflowGateResponse,
|
|
298
|
+
options: GateResolutionOptions = {},
|
|
299
|
+
): Promise<RpcWorkflowGateResolution> {
|
|
300
|
+
return this.withGateLock(response.gate_id, () => this.resolveUnlocked(response, options));
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
private async resolveUnlocked(
|
|
304
|
+
response: RpcWorkflowGateResponse,
|
|
305
|
+
options: GateResolutionOptions = {},
|
|
306
|
+
): Promise<RpcWorkflowGateResolution> {
|
|
226
307
|
const record = this.store.get(response.gate_id);
|
|
227
308
|
if (!record) {
|
|
228
309
|
this.hooks.audit?.({ event: "gate_response_unknown_gate", gate_id: response.gate_id });
|
|
@@ -263,6 +344,13 @@ export class WorkflowGateBroker {
|
|
|
263
344
|
};
|
|
264
345
|
}
|
|
265
346
|
|
|
347
|
+
const semanticDisposition =
|
|
348
|
+
options.semanticDisposition ?? classifyAskGateDisposition(record.gate, response.answer);
|
|
349
|
+
const resolutionOrigin = options.resolutionOrigin ?? { kind: "generic", channel: "rpc" as const };
|
|
350
|
+
const ackPolicy =
|
|
351
|
+
options.ackPolicy ??
|
|
352
|
+
({ kind: "none", reason: semanticDisposition === "commit" ? "non_telegram" : "semantic_noncommit" } as const);
|
|
353
|
+
|
|
266
354
|
const resolution: RpcWorkflowGateResolution = {
|
|
267
355
|
gate_id: response.gate_id,
|
|
268
356
|
status: "accepted",
|
|
@@ -280,18 +368,30 @@ export class WorkflowGateBroker {
|
|
|
280
368
|
answer: response.answer,
|
|
281
369
|
resolution,
|
|
282
370
|
advanced: false,
|
|
371
|
+
semanticDisposition,
|
|
372
|
+
resolutionOrigin,
|
|
373
|
+
ackPolicy,
|
|
283
374
|
});
|
|
284
375
|
this.hooks.audit?.({ event: "gate_response_accepted", gate_id: response.gate_id, answer_hash: answerHash });
|
|
285
|
-
await
|
|
286
|
-
this.store.
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
376
|
+
await options.beforeAdvance?.();
|
|
377
|
+
await this.hooks.finalizeAccepted?.(this.store.get(response.gate_id) as PersistedGate);
|
|
378
|
+
const finalized = this.store.get(response.gate_id);
|
|
379
|
+
if (finalized?.status !== "accepted") {
|
|
380
|
+
throw new WorkflowGateBrokerError(
|
|
381
|
+
"unknown_gate",
|
|
382
|
+
`accepted gate ${response.gate_id} disappeared before advance`,
|
|
383
|
+
);
|
|
384
|
+
}
|
|
385
|
+
if (finalized.advanced) return finalized.resolution ?? resolution;
|
|
386
|
+
await this.hooks.advance?.(finalized.gate, finalized.answer);
|
|
387
|
+
const latest = this.store.get(response.gate_id);
|
|
388
|
+
if (latest?.status !== "accepted") {
|
|
389
|
+
throw new WorkflowGateBrokerError(
|
|
390
|
+
"unknown_gate",
|
|
391
|
+
`accepted gate ${response.gate_id} disappeared after advance`,
|
|
392
|
+
);
|
|
393
|
+
}
|
|
394
|
+
this.store.put({ ...latest, advanced: true });
|
|
295
395
|
return resolution;
|
|
296
396
|
}
|
|
297
397
|
|
|
@@ -304,15 +404,25 @@ export class WorkflowGateBroker {
|
|
|
304
404
|
async recover(): Promise<string[]> {
|
|
305
405
|
const recovered: string[] = [];
|
|
306
406
|
for (const listed of this.store.list()) {
|
|
307
|
-
if (listed.status !== "accepted" || listed.advanced) continue;
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
407
|
+
if (listed.status !== "accepted" || listed.advanced || this.recovering.has(listed.gate.gate_id)) continue;
|
|
408
|
+
this.recovering.add(listed.gate.gate_id);
|
|
409
|
+
try {
|
|
410
|
+
await this.withGateLock(listed.gate.gate_id, async () => {
|
|
411
|
+
const rec = this.store.get(listed.gate.gate_id);
|
|
412
|
+
if (rec?.status !== "accepted" || rec.advanced) return;
|
|
413
|
+
await this.hooks.finalizeAccepted?.(rec);
|
|
414
|
+
const finalized = this.store.get(listed.gate.gate_id);
|
|
415
|
+
if (finalized?.status !== "accepted" || finalized.advanced) return;
|
|
416
|
+
await this.hooks.advance?.(finalized.gate, finalized.answer);
|
|
417
|
+
const latest = this.store.get(listed.gate.gate_id);
|
|
418
|
+
if (latest?.status !== "accepted" || latest.advanced) return;
|
|
419
|
+
this.store.put({ ...latest, advanced: true });
|
|
420
|
+
this.hooks.audit?.({ event: "gate_advance_recovered", gate_id: latest.gate.gate_id });
|
|
421
|
+
recovered.push(latest.gate.gate_id);
|
|
422
|
+
});
|
|
423
|
+
} finally {
|
|
424
|
+
this.recovering.delete(listed.gate.gate_id);
|
|
425
|
+
}
|
|
316
426
|
}
|
|
317
427
|
return recovered;
|
|
318
428
|
}
|
package/src/modes/types.ts
CHANGED
|
@@ -23,14 +23,18 @@ import type { AssistantMessageComponent } from "./components/assistant-message";
|
|
|
23
23
|
import type { BashExecutionComponent } from "./components/bash-execution";
|
|
24
24
|
import type { CustomEditor } from "./components/custom-editor";
|
|
25
25
|
import type { EvalExecutionComponent } from "./components/eval-execution";
|
|
26
|
+
import type { PetMode } from "./components/gajae-pet-widget";
|
|
26
27
|
import type { HookEditorComponent } from "./components/hook-editor";
|
|
27
28
|
import type { HookInputComponent } from "./components/hook-input";
|
|
28
29
|
import type { HookSelectorComponent } from "./components/hook-selector";
|
|
29
30
|
import type { StatusLineComponent } from "./components/status-line";
|
|
30
31
|
import type { ToolExecutionHandle } from "./components/tool-execution";
|
|
32
|
+
import type { IrcObservationLedger } from "./irc-observation-ledger";
|
|
31
33
|
import type { OAuthManualInputManager } from "./oauth-manual-input";
|
|
32
34
|
import type { Theme } from "./theme/theme";
|
|
35
|
+
import type { ParsedIrcMessage } from "./utils/irc-message";
|
|
33
36
|
|
|
37
|
+
export type TranscriptRebuildPolicy = "replace-identity" | "reconcile-same-transcript";
|
|
34
38
|
export type CompactionQueuedMessage = {
|
|
35
39
|
text: string;
|
|
36
40
|
mode: "steer" | "followUp";
|
|
@@ -60,6 +64,14 @@ export type TodoPhase = {
|
|
|
60
64
|
tasks: TodoItem[];
|
|
61
65
|
};
|
|
62
66
|
|
|
67
|
+
export type IrcArrivalSnapshot = Readonly<{
|
|
68
|
+
panelVisible: boolean;
|
|
69
|
+
/** User-requested open state; may be true while the panel yields at narrow widths. */
|
|
70
|
+
panelRequestedVisible: boolean;
|
|
71
|
+
sidebarAvailable: boolean;
|
|
72
|
+
resolvedToggleKey: string | null;
|
|
73
|
+
}>;
|
|
74
|
+
|
|
63
75
|
export interface InteractiveModeContext {
|
|
64
76
|
// UI access
|
|
65
77
|
ui: TUI;
|
|
@@ -84,6 +96,7 @@ export interface InteractiveModeContext {
|
|
|
84
96
|
mcpManager?: MCPManager;
|
|
85
97
|
lspServers?: LspStartupServerInfo[];
|
|
86
98
|
|
|
99
|
+
readonly ircLedger: IrcObservationLedger;
|
|
87
100
|
// State
|
|
88
101
|
isInitialized: boolean;
|
|
89
102
|
isBackgrounded: boolean;
|
|
@@ -163,6 +176,11 @@ export interface InteractiveModeContext {
|
|
|
163
176
|
setWorkingMessage(message?: string): void;
|
|
164
177
|
applyPendingWorkingMessage(): void;
|
|
165
178
|
ensureLoadingAnimation(): void;
|
|
179
|
+
setPetMode(mode: PetMode): void;
|
|
180
|
+
/** Live-preview a pet skin during a selector without persisting. */
|
|
181
|
+
previewPetMode(mode: PetMode): void;
|
|
182
|
+
/** Re-mount the composer (pet-aware) after an overlay/selector closes. */
|
|
183
|
+
restoreComposer(): void;
|
|
166
184
|
startPendingSubmission(input: {
|
|
167
185
|
text: string;
|
|
168
186
|
images?: ImageContent[];
|
|
@@ -187,21 +205,34 @@ export interface InteractiveModeContext {
|
|
|
187
205
|
withLocalSubmission<T>(text: string, fn: () => Promise<T>, options?: { imageCount?: number }): Promise<T>;
|
|
188
206
|
isKnownSlashCommand(text: string): boolean;
|
|
189
207
|
addMessageToChat(message: AgentMessage, options?: { populateHistory?: boolean }): Component[];
|
|
208
|
+
addLiveIrcObservationToChat(message: ParsedIrcMessage, arrival: IrcArrivalSnapshot): Component[];
|
|
209
|
+
removeRenderedIrcInlineComponents(observationId: string): readonly Component[] | undefined;
|
|
210
|
+
resetRenderedIrcInlineComponents(): readonly (readonly Component[])[];
|
|
190
211
|
renderSessionContext(
|
|
191
212
|
sessionContext: SessionContext,
|
|
192
213
|
options?: { updateFooter?: boolean; populateHistory?: boolean },
|
|
193
214
|
): void;
|
|
194
|
-
|
|
215
|
+
rebuildInitialMessages(
|
|
216
|
+
policy: TranscriptRebuildPolicy,
|
|
217
|
+
prebuiltContext?: SessionContext,
|
|
218
|
+
options?: { preserveExistingChat?: boolean },
|
|
219
|
+
): void;
|
|
195
220
|
getUserMessageText(message: Message): string;
|
|
221
|
+
getAssistantViewportAnchorId?(message: AssistantMessage): string;
|
|
196
222
|
findLastAssistantMessage(): AssistantMessage | undefined;
|
|
197
223
|
extractAssistantText(message: AssistantMessage): string;
|
|
198
224
|
updateEditorTopBorder(): void;
|
|
199
225
|
updateEditorBorderColor(): void;
|
|
200
|
-
rebuildChatFromMessages(): void;
|
|
226
|
+
rebuildChatFromMessages(policy: TranscriptRebuildPolicy): void;
|
|
201
227
|
setTodos(todos: TodoItem[] | TodoPhase[]): void;
|
|
202
228
|
reloadTodos(): Promise<void>;
|
|
203
229
|
toggleTodoExpansion(): void;
|
|
204
230
|
|
|
231
|
+
// IRC sidebar
|
|
232
|
+
toggleIrcSidebar(): void;
|
|
233
|
+
captureIrcArrivalSnapshot(): IrcArrivalSnapshot;
|
|
234
|
+
applyIrcSidebarAvailability(enabled: boolean): void;
|
|
235
|
+
resetIrcSidebarSession(): void;
|
|
205
236
|
// Command handling
|
|
206
237
|
handleExportCommand(text: string): Promise<void>;
|
|
207
238
|
handleShareCommand(): Promise<void>;
|
|
@@ -242,6 +273,7 @@ export interface InteractiveModeContext {
|
|
|
242
273
|
// Selector handling
|
|
243
274
|
showSettingsSelector(): void;
|
|
244
275
|
showThemeSelector(): void;
|
|
276
|
+
showPetSelector(): void;
|
|
245
277
|
showHistorySearch(): void;
|
|
246
278
|
showExtensionsDashboard(): void;
|
|
247
279
|
showAgentsDashboard(): void;
|
|
@@ -54,6 +54,7 @@ export function buildHotkeysMarkdown(bindings: HotkeysMarkdownBindings): string
|
|
|
54
54
|
`| \`${appKey(bindings, "app.editor.external")}\` | Edit message in external editor |`,
|
|
55
55
|
`| \`${appKey(bindings, "app.clipboard.pasteImage")}\` | Paste image from clipboard |`,
|
|
56
56
|
`| \`${appKey(bindings, "app.stt.toggle")}\` | Toggle speech-to-text recording |`,
|
|
57
|
+
`| \`${appKey(bindings, "app.irc.sidebar.toggle")}\` | Toggle IRC sidebar |`,
|
|
57
58
|
"| `?` | Show help / shortcuts |",
|
|
58
59
|
"| `#` | Prompt actions (command-palette style actions) |",
|
|
59
60
|
"| `/` | Slash commands (try `/help` or `/new`) |",
|