@gajae-code/coding-agent 0.11.6 → 0.11.8
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 +92 -0
- package/dist/types/capability/index.d.ts +10 -20
- package/dist/types/capability/types.d.ts +3 -0
- package/dist/types/cli/args.d.ts +0 -2
- package/dist/types/commands/daemon.d.ts +2 -2
- package/dist/types/commands/sdk.d.ts +20 -2
- package/dist/types/config/keybindings.d.ts +34 -4
- package/dist/types/config/model-profile-activation.d.ts +4 -3
- package/dist/types/config/model-profile-contract.d.ts +38 -0
- package/dist/types/config/settings-schema.d.ts +70 -7
- package/dist/types/edit/modes/patch.d.ts +13 -0
- package/dist/types/edit/path-mutation-lock.d.ts +15 -0
- package/dist/types/eval/py/executor.d.ts +1 -1
- package/dist/types/eval/py/runner-artifact.d.ts +8 -0
- package/dist/types/eval/py/tool-bridge.d.ts +1 -2
- package/dist/types/gjc-runtime/repository-binding.d.ts +63 -0
- package/dist/types/gjc-runtime/ultragoal-guard.d.ts +6 -5
- package/dist/types/gjc-runtime/ultragoal-receipt-freshness.d.ts +20 -0
- package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +43 -5
- package/dist/types/main.d.ts +32 -1
- package/dist/types/modes/components/btw-panel.d.ts +1 -0
- package/dist/types/modes/components/model-selector.d.ts +2 -0
- package/dist/types/modes/components/runtime-mcp-add-wizard.d.ts +2 -1
- package/dist/types/modes/components/tool-status-header.d.ts +4 -2
- package/dist/types/modes/components/welcome.d.ts +2 -0
- package/dist/types/modes/controllers/btw-controller.d.ts +5 -0
- package/dist/types/modes/controllers/command-controller.d.ts +2 -0
- package/dist/types/modes/controllers/selector-controller.d.ts +2 -1
- package/dist/types/modes/interactive-mode.d.ts +7 -2
- package/dist/types/modes/prompt-action-autocomplete.d.ts +6 -1
- package/dist/types/modes/prompt-suggestion-controller.d.ts +35 -0
- package/dist/types/modes/shared/agent-wire/workflow-gate-broker.d.ts +2 -2
- package/dist/types/modes/types.d.ts +4 -0
- package/dist/types/modes/utils/hotkeys-markdown.d.ts +2 -1
- package/dist/types/runtime-mcp/content-limits.d.ts +7 -0
- package/dist/types/runtime-mcp/manager.d.ts +1 -1
- package/dist/types/runtime-mcp/oauth-flow.d.ts +0 -5
- package/dist/types/runtime-mcp/plugin-network-boundary.d.ts +14 -0
- package/dist/types/sdk/broker/broker.d.ts +2 -0
- package/dist/types/sdk/broker/lifecycle.d.ts +6 -0
- package/dist/types/sdk/bus/chat-daemon-control.d.ts +6 -1
- package/dist/types/sdk/bus/config-commands.d.ts +2 -2
- package/dist/types/sdk/bus/config.d.ts +2 -0
- package/dist/types/sdk/bus/conversation-store.d.ts +1 -0
- package/dist/types/sdk/bus/index.d.ts +26 -1
- package/dist/types/sdk/bus/lifecycle-commands.d.ts +5 -4
- package/dist/types/sdk/bus/notification-orchestration.d.ts +1 -0
- package/dist/types/sdk/bus/notification-service.d.ts +34 -0
- package/dist/types/sdk/bus/prompt-reconciliation.d.ts +105 -0
- package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +18 -3
- package/dist/types/sdk/bus/telegram-daemon-control.d.ts +5 -1
- package/dist/types/sdk/bus/telegram-daemon.d.ts +189 -4
- package/dist/types/sdk/bus/telegram-reference.d.ts +2 -0
- package/dist/types/sdk/bus/topic-registry.d.ts +88 -2
- package/dist/types/sdk/client/discovery.d.ts +2 -0
- package/dist/types/sdk/client/liveness.d.ts +7 -0
- package/dist/types/sdk/host/control/operations.d.ts +1 -1
- package/dist/types/sdk/host/host.d.ts +1 -1
- package/dist/types/sdk/host/query/handlers.d.ts +9 -0
- package/dist/types/sdk/index.d.ts +2 -0
- package/dist/types/sdk/prompt-status.d.ts +88 -0
- package/dist/types/sdk/session-directory.d.ts +1 -1
- package/dist/types/sdk/startup-capability.d.ts +3 -0
- package/dist/types/sdk/transport/auth-preface.d.ts +9 -0
- package/dist/types/sdk/transport/index.d.ts +12 -0
- package/dist/types/sdk/transport/relay.d.ts +43 -0
- package/dist/types/sdk/transport/serve-cli.d.ts +4 -0
- package/dist/types/sdk/transport/socket.d.ts +7 -0
- package/dist/types/sdk/transport/stdio.d.ts +3 -0
- package/dist/types/session/agent-session.d.ts +30 -5
- package/dist/types/session/btw-contract.d.ts +16 -0
- package/dist/types/session/internal/managed-session-scope.d.ts +32 -9
- package/dist/types/session/internal/managed-session-storage.d.ts +27 -10
- package/dist/types/session/internal/native-publish-outcome.d.ts +48 -0
- package/dist/types/session/session-manager.d.ts +25 -4
- package/dist/types/session/session-storage.d.ts +18 -0
- package/dist/types/setup/credential-import.d.ts +5 -1
- package/dist/types/setup/provider-onboarding.d.ts +2 -0
- package/dist/types/ssh/utils.d.ts +1 -0
- package/dist/types/task/discovery.d.ts +3 -1
- package/dist/types/task/index.d.ts +3 -1
- package/dist/types/task/receipt.d.ts +2 -0
- package/dist/types/task/types.d.ts +94 -0
- package/dist/types/tools/ask.d.ts +454 -174
- package/dist/types/tools/fetch.d.ts +5 -8
- package/dist/types/tools/image-gen.d.ts +63 -1
- package/dist/types/tools/index.d.ts +4 -0
- package/dist/types/tools/skill-discovery.d.ts +7 -0
- package/dist/types/tools/sqlite-reader.d.ts +7 -0
- package/dist/types/utils/prompt-suggestion.d.ts +33 -0
- package/dist/types/utils/shell-snapshot.d.ts +12 -0
- package/dist/types/web/insane/bridge.d.ts +1 -0
- package/dist/types/web/insane/url-guard.d.ts +17 -0
- package/package.json +8 -8
- package/scripts/capture-platform-shortcut-labels-showcase.ts +342 -0
- package/scripts/dogfood-repository-binding.ts +209 -0
- package/scripts/generate-sdk-operation-inventory.ts +4 -0
- package/scripts/run-test-manifest.ts +4 -4
- package/src/capability/index.ts +50 -42
- package/src/capability/types.ts +3 -0
- package/src/cli/args.ts +2 -92
- package/src/cli/notify-cli.ts +5 -38
- package/src/cli/setup-cli.ts +10 -4
- package/src/cli.ts +52 -9
- package/src/commands/daemon.ts +22 -5
- package/src/commands/sdk.ts +44 -11
- package/src/config/keybindings.ts +158 -25
- package/src/config/model-profile-activation.ts +7 -18
- package/src/config/model-profile-contract.ts +161 -0
- package/src/config/model-profiles.ts +18 -0
- package/src/config/model-registry.ts +11 -1
- package/src/config/settings-schema.ts +73 -7
- package/src/coordinator-mcp/model-preset.ts +21 -74
- package/src/defaults/gjc/skills/deep-interview/SKILL.md +2 -2
- package/src/defaults/gjc/skills/ralplan/SKILL.md +3 -1
- package/src/defaults/gjc/skills/ultragoal/SKILL.md +42 -1
- package/src/discovery/ssh.ts +12 -1
- package/src/edit/index.ts +3 -6
- package/src/edit/modes/patch.ts +38 -1
- package/src/edit/modes/replace.ts +42 -0
- package/src/edit/path-mutation-lock.ts +112 -0
- package/src/eval/py/executor.ts +30 -11
- package/src/eval/py/kernel.ts +1 -21
- package/src/eval/py/prelude.py +2 -2
- package/src/eval/py/runner-artifact.ts +84 -0
- package/src/eval/py/tool-bridge.ts +34 -18
- package/src/extensibility/gjc-plugins/runtime-adapters.ts +2 -1
- package/src/extensibility/plugins/marketplace/fetcher.ts +191 -7
- package/src/gjc-runtime/launch-tmux.ts +1 -1
- package/src/gjc-runtime/managed-owner-supervisor.ts +83 -4
- package/src/gjc-runtime/ralplan-runtime.ts +75 -3
- package/src/gjc-runtime/repository-binding.ts +267 -0
- package/src/gjc-runtime/session-state-sidecar.ts +25 -9
- package/src/gjc-runtime/tmux-owner-isolation.ts +2 -1
- package/src/gjc-runtime/tmux-sessions.ts +45 -6
- package/src/gjc-runtime/ultragoal-guard.ts +93 -15
- package/src/gjc-runtime/ultragoal-receipt-freshness.ts +103 -0
- package/src/gjc-runtime/ultragoal-runtime.ts +456 -31
- package/src/gjc-runtime/workflow-manifest.generated.json +58 -0
- package/src/gjc-runtime/workflow-manifest.ts +23 -0
- package/src/internal-urls/docs-index.generated.ts +12 -10
- package/src/internal-urls/local-protocol.ts +64 -21
- package/src/internal-urls/mcp-protocol.ts +73 -11
- package/src/main.ts +111 -37
- package/src/modes/DESIGN.md +5 -0
- package/src/modes/acp/acp-agent.ts +50 -16
- package/src/modes/action-registry.ts +1 -1
- package/src/modes/components/btw-panel.ts +82 -16
- package/src/modes/components/model-selector.ts +73 -11
- package/src/modes/components/plan-preview-overlay.ts +1 -1
- package/src/modes/components/runtime-mcp-add-wizard.ts +5 -0
- package/src/modes/components/tool-status-header.ts +25 -4
- package/src/modes/components/welcome.ts +22 -18
- package/src/modes/controllers/btw-controller.ts +119 -36
- package/src/modes/controllers/command-controller.ts +97 -54
- package/src/modes/controllers/event-controller.ts +10 -0
- package/src/modes/controllers/input-controller.ts +38 -2
- package/src/modes/controllers/runtime-mcp-command-controller.ts +29 -6
- package/src/modes/controllers/selector-controller.ts +149 -63
- package/src/modes/interactive-mode.ts +57 -34
- package/src/modes/prompt-action-autocomplete.ts +32 -13
- package/src/modes/prompt-suggestion-controller.ts +96 -0
- package/src/modes/shared/agent-wire/workflow-gate-broker.ts +2 -2
- package/src/modes/types.ts +4 -0
- package/src/modes/utils/hotkeys-markdown.ts +64 -36
- package/src/modes/utils/ui-helpers.ts +5 -3
- package/src/prompts/agents/architect.md +0 -1
- package/src/prompts/agents/critic.md +0 -1
- package/src/prompts/system/btw-user.md +3 -8
- package/src/prompts/system/prompt-suggestion-system.md +32 -0
- package/src/prompts/system/system-prompt.md +6 -4
- package/src/prompts/tools/cron.md +3 -1
- package/src/prompts/tools/skill-discovery.md +1 -0
- package/src/runtime-mcp/client.ts +41 -55
- package/src/runtime-mcp/content-limits.ts +71 -0
- package/src/runtime-mcp/json-rpc.ts +23 -9
- package/src/runtime-mcp/manager.ts +51 -53
- package/src/runtime-mcp/oauth-flow.ts +0 -72
- package/src/runtime-mcp/plugin-network-boundary.ts +101 -0
- package/src/runtime-mcp/transports/http.ts +63 -22
- package/src/sdk/broker/broker.ts +34 -20
- package/src/sdk/broker/lifecycle.ts +185 -36
- package/src/sdk/bus/chat-daemon-control.ts +19 -5
- package/src/sdk/bus/config-commands.ts +2 -2
- package/src/sdk/bus/config.ts +9 -1
- package/src/sdk/bus/conversation-store.ts +40 -6
- package/src/sdk/bus/index.ts +244 -70
- package/src/sdk/bus/lifecycle-commands.ts +63 -10
- package/src/sdk/bus/lifecycle-control-runtime.ts +11 -2
- package/src/sdk/bus/lifecycle-orchestrator.ts +32 -1
- package/src/sdk/bus/notification-orchestration.ts +12 -0
- package/src/sdk/bus/notification-service.ts +68 -20
- package/src/sdk/bus/prompt-reconciliation.ts +239 -0
- package/src/sdk/bus/recent-activity.ts +90 -26
- package/src/sdk/bus/telegram-daemon-contract.ts +18 -3
- package/src/sdk/bus/telegram-daemon-control.ts +147 -35
- package/src/sdk/bus/telegram-daemon.ts +2463 -450
- package/src/sdk/bus/telegram-reference.ts +20 -2
- package/src/sdk/bus/topic-registry.ts +451 -12
- package/src/sdk/cli/session-cli.ts +15 -2
- package/src/sdk/client/discovery.ts +19 -3
- package/src/sdk/client/liveness.ts +49 -0
- package/src/sdk/host/control/dispatch.ts +1 -1
- package/src/sdk/host/control/operations.ts +1 -1
- package/src/sdk/host/host.ts +2 -6
- package/src/sdk/host/query/handlers.ts +70 -5
- package/src/sdk/index.ts +12 -0
- package/src/sdk/prompt-status.ts +97 -0
- package/src/sdk/protocol/operation-inventory.generated.json +58 -0
- package/src/sdk/protocol/operation-registry.ts +18 -4
- package/src/sdk/session-directory.ts +5 -1
- package/src/sdk/session.ts +31 -5
- package/src/sdk/startup-capability.ts +9 -1
- package/src/sdk/transport/auth-preface.ts +82 -0
- package/src/sdk/transport/index.ts +18 -0
- package/src/sdk/transport/relay.ts +252 -0
- package/src/sdk/transport/serve-cli.ts +105 -0
- package/src/sdk/transport/socket.ts +197 -0
- package/src/sdk/transport/stdio.ts +17 -0
- package/src/session/agent-session.ts +292 -154
- package/src/session/blob-store.ts +25 -7
- package/src/session/btw-contract.ts +59 -0
- package/src/session/internal/managed-session-scope.ts +1163 -137
- package/src/session/internal/managed-session-storage.ts +397 -234
- package/src/session/internal/native-publish-outcome.ts +266 -0
- package/src/session/session-manager.ts +620 -403
- package/src/session/session-storage.ts +128 -11
- package/src/setup/credential-import.ts +28 -3
- package/src/setup/provider-onboarding.ts +30 -1
- package/src/setup/provider-presets.json +2 -1
- package/src/slash-commands/builtin-registry.ts +1 -1
- package/src/ssh/utils.ts +14 -0
- package/src/task/discovery.ts +8 -2
- package/src/task/gjc-command.ts +2 -2
- package/src/task/index.ts +121 -15
- package/src/task/receipt.ts +3 -0
- package/src/task/types.ts +34 -3
- package/src/tools/ask.ts +199 -89
- package/src/tools/ast-edit.ts +2 -2
- package/src/tools/bash-pty-selection.ts +2 -2
- package/src/tools/browser/screenshot-format.ts +6 -1
- package/src/tools/cron.ts +1 -1
- package/src/tools/fetch.ts +13 -131
- package/src/tools/image-gen.ts +476 -23
- package/src/tools/index.ts +4 -0
- package/src/tools/read.ts +2 -0
- package/src/tools/render-utils.ts +7 -1
- package/src/tools/skill-discovery.ts +39 -3
- package/src/tools/sqlite-reader.ts +63 -5
- package/src/utils/edit-mode.ts +2 -2
- package/src/utils/pasted-image-path.ts +15 -6
- package/src/utils/prompt-suggestion.ts +265 -0
- package/src/utils/shell-snapshot.ts +173 -63
- package/src/utils/sixel.ts +3 -3
- package/src/web/insane/bridge.ts +5 -1
- package/src/web/insane/url-guard.ts +140 -27
- package/src/web/scrapers/docs-rs.ts +30 -5
- package/src/web/scrapers/types.ts +19 -49
- package/src/web/scrapers/utils.ts +12 -26
package/src/sdk/bus/index.ts
CHANGED
|
@@ -30,7 +30,8 @@ import { promisify } from "node:util";
|
|
|
30
30
|
import { ThinkingLevel } from "@gajae-code/agent-core";
|
|
31
31
|
import type { ImageContent, TextContent, Tool } from "@gajae-code/ai";
|
|
32
32
|
import { NotificationServer, nativeBuildInfo } from "@gajae-code/natives";
|
|
33
|
-
import { logger, postmortem,
|
|
33
|
+
import { logger, postmortem, VERSION } from "@gajae-code/utils";
|
|
34
|
+
import { projectModelProfileCatalog } from "../../config/model-profile-contract";
|
|
34
35
|
import { Settings } from "../../config/settings";
|
|
35
36
|
import type { ExtensionAPI, ExtensionCommandContext, ExtensionContext } from "../../extensibility/extensions";
|
|
36
37
|
import { toAgentWireEventPayload } from "../../modes/shared/agent-wire/event-envelope";
|
|
@@ -40,7 +41,6 @@ import {
|
|
|
40
41
|
type WorkflowGateTerminalController,
|
|
41
42
|
type WorkflowGateTerminalProof,
|
|
42
43
|
} from "../../modes/shared/agent-wire/workflow-gate-broker";
|
|
43
|
-
import btwUserPrompt from "../../prompts/system/btw-user.md" with { type: "text" };
|
|
44
44
|
import type { AgentSessionEvent } from "../../session/agent-session";
|
|
45
45
|
import { parseThinkingLevel } from "../../thinking";
|
|
46
46
|
import type {
|
|
@@ -60,13 +60,13 @@ import { SessionSdkHost, shouldHostSdk } from "../host";
|
|
|
60
60
|
import { type ControlSurface, dispatchControl } from "../host/control";
|
|
61
61
|
import { CursorRegistry, QueryHandlers, RevisionStore, type SessionSurface } from "../host/query";
|
|
62
62
|
import { projectQ10Models } from "../models.js";
|
|
63
|
+
import { PROMPT_CLIENT_REF_MAX_LENGTH } from "../prompt-status";
|
|
63
64
|
import { OPERATIONS } from "../protocol/operation-registry";
|
|
64
65
|
import {
|
|
65
66
|
lifecycleStartupCapabilityForApi,
|
|
66
67
|
normalizeSdkStartupFailure,
|
|
67
68
|
type SdkStartupFailure,
|
|
68
69
|
} from "../startup-capability";
|
|
69
|
-
|
|
70
70
|
import { registerTelegramFileSink } from "./attachment-registry";
|
|
71
71
|
import { ensureDiscordDaemon, ensureSlackDaemon } from "./chat-daemon-control";
|
|
72
72
|
import {
|
|
@@ -82,6 +82,7 @@ import {
|
|
|
82
82
|
import { telegramControlCommandUsage } from "./config-commands";
|
|
83
83
|
import { imageAttachmentsFromMessage, notificationActionPayload, summaryFromMessage, truncate } from "./helpers";
|
|
84
84
|
import { assertNativeRuntimeCompatibility } from "./native-runtime-compatibility";
|
|
85
|
+
import { createPromptReconciliation, sanitizePromptFailure } from "./prompt-reconciliation";
|
|
85
86
|
import { NotificationSessionController, type NotificationSessionRuntime } from "./session-control";
|
|
86
87
|
import {
|
|
87
88
|
ASK_SELECTED_ACK_CAPABILITY,
|
|
@@ -402,6 +403,7 @@ interface UnattendedGatePresentation {
|
|
|
402
403
|
question: string;
|
|
403
404
|
options: string[];
|
|
404
405
|
controls: readonly AskRemoteControl[];
|
|
406
|
+
recommendedIndex?: number;
|
|
405
407
|
multi: boolean;
|
|
406
408
|
allowEmpty: boolean;
|
|
407
409
|
navigationLabel?: "Next" | "Done";
|
|
@@ -410,6 +412,14 @@ interface UnattendedGatePresentation {
|
|
|
410
412
|
onActivated?: (actionId: string, lease: { actionId: string; registrationEpoch: number }) => void;
|
|
411
413
|
onClosed?: () => void;
|
|
412
414
|
}
|
|
415
|
+
function recommendedIndexFromGateOptions(options: readonly unknown[]): number | undefined {
|
|
416
|
+
const descriptions = options.map(option => (option as { description?: unknown }).description);
|
|
417
|
+
const recommended = descriptions.filter(description => description === "recommended");
|
|
418
|
+
return recommended.length === 1 &&
|
|
419
|
+
descriptions.every(description => description === undefined || description === "recommended")
|
|
420
|
+
? descriptions.indexOf("recommended")
|
|
421
|
+
: undefined;
|
|
422
|
+
}
|
|
413
423
|
|
|
414
424
|
type RetireStatus = "retired" | "already_terminal" | "claimed" | "stale";
|
|
415
425
|
type DirectControlOutcome = "accepted" | "rejected" | "unknown";
|
|
@@ -740,6 +750,9 @@ export class PresentationArbiter {
|
|
|
740
750
|
? `(${presentation.selectedOptions.length} selected) ${presentation.question}`
|
|
741
751
|
: presentation.question,
|
|
742
752
|
options: presentation.options,
|
|
753
|
+
...(presentation.recommendedIndex === undefined
|
|
754
|
+
? {}
|
|
755
|
+
: { recommendedIndex: presentation.recommendedIndex }),
|
|
743
756
|
controls: presentation.multi
|
|
744
757
|
? [
|
|
745
758
|
{
|
|
@@ -907,6 +920,13 @@ interface SessionRuntime {
|
|
|
907
920
|
/** Records a correlated prompt terminal boundary after agent unwind. */
|
|
908
921
|
/** Atomically claims a correlated prompt terminal boundary after agent unwind. */
|
|
909
922
|
recordPromptTerminal: (correlation: { commandId: string; turnId: string } | undefined) => boolean;
|
|
923
|
+
/** Transitions the authoritative reconciliation record at lifecycle ingress; terminal outcomes settle once. */
|
|
924
|
+
notePromptReconciliation: (
|
|
925
|
+
correlation: { commandId: string; turnId: string } | undefined,
|
|
926
|
+
frame: { type: "agent_start" | "agent_end" } | { type: "agent_failed"; error: unknown },
|
|
927
|
+
) => void;
|
|
928
|
+
/** Settles and emits one sanitized correlated prompt failure. */
|
|
929
|
+
emitPromptFailure: (correlation: { commandId: string; turnId: string }, error: unknown) => void;
|
|
910
930
|
/** Records correlated lifecycle frames for replay and delivers them only to the accepted requester after acknowledgement. */
|
|
911
931
|
emitPromptLifecycle: (
|
|
912
932
|
correlation: { commandId: string; turnId: string } | undefined,
|
|
@@ -940,6 +960,12 @@ interface SessionRuntime {
|
|
|
940
960
|
turnClosed?: boolean;
|
|
941
961
|
/** Finalized while provisional policy was held; flush exactly once on stable activation. */
|
|
942
962
|
pendingFinal?: { text?: string; messageRef?: string };
|
|
963
|
+
/**
|
|
964
|
+
* Lean-mode deferred final answer: latest assistant text observed at `turn_end`,
|
|
965
|
+
* emitted once at `agent_end` so intermediate tool-turn narration does not flood
|
|
966
|
+
* remote clients. Cleared after flush or when replaced by a newer turn.
|
|
967
|
+
*/
|
|
968
|
+
pendingSettled?: { text: string; messageRef?: string };
|
|
943
969
|
/** Durable gates emitted while ownership is provisional; presented only after stable activation. */
|
|
944
970
|
deferredGatePresentations: Array<() => void>;
|
|
945
971
|
/** SDK control frames received during provisional ownership; replayed only after stable activation. */
|
|
@@ -955,7 +981,6 @@ interface SessionRuntime {
|
|
|
955
981
|
const SENSITIVE_MODEL_LABEL =
|
|
956
982
|
/(?:\b(?:https?|wss?):\/\/|\b[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,}\b|\b(?:api[-_ ]?key|access[-_ ]?token|bearer|secret|password|account(?:\s*id)?|email|exception|stack trace)\b|\b(?:sk|pk|rk)-[A-Za-z0-9_-]{12,}\b)/i;
|
|
957
983
|
const TOOL_SUMMARY_MAX = 280;
|
|
958
|
-
const EMPTY_CAPABILITIES: ReadonlySet<string> = new Set();
|
|
959
984
|
|
|
960
985
|
/** Stable projection of the tool-owned safe-display seam (never the full Tool surface). */
|
|
961
986
|
type SafeSummaryTool = Pick<Tool, "safeSummary" | "safeSummaryFields">;
|
|
@@ -1020,6 +1045,14 @@ function pushSessionFrame(
|
|
|
1020
1045
|
runtime.server.pushFrame(JSON.stringify(frame));
|
|
1021
1046
|
}
|
|
1022
1047
|
|
|
1048
|
+
async function pushTerminalSessionFrame(
|
|
1049
|
+
runtime: Pick<SessionRuntime, "server" | "host">,
|
|
1050
|
+
frame: { type: "session_closed"; sessionId: string },
|
|
1051
|
+
): Promise<boolean> {
|
|
1052
|
+
runtime.host.emitEvent({ kind: frame.type, payload: frame });
|
|
1053
|
+
return await runtime.server.pushFrameAndWait(JSON.stringify(frame), 1_000);
|
|
1054
|
+
}
|
|
1055
|
+
|
|
1023
1056
|
function pushFileAttachment(
|
|
1024
1057
|
runtime: Pick<SessionRuntime, "server" | "host">,
|
|
1025
1058
|
frame: { type: "file_attachment"; sessionId: string; name: string; mime?: string; caption?: string },
|
|
@@ -1594,6 +1627,7 @@ function registerInteractiveAnswerSource(
|
|
|
1594
1627
|
question: request.question,
|
|
1595
1628
|
options: request.options,
|
|
1596
1629
|
controls: request.controls,
|
|
1630
|
+
recommendedIndex: request.recommendedIndex,
|
|
1597
1631
|
multi: false,
|
|
1598
1632
|
allowEmpty: false,
|
|
1599
1633
|
selectedOptions: [],
|
|
@@ -1756,6 +1790,7 @@ function sdkQuerySurface(
|
|
|
1756
1790
|
followupQueueDepth: 0,
|
|
1757
1791
|
}),
|
|
1758
1792
|
configOverrides: ReadonlyMap<string, unknown> = new Map(),
|
|
1793
|
+
promptStatusLookup: (selector: { commandId?: string; turnId?: string; clientRef?: string }) => unknown,
|
|
1759
1794
|
): SessionSurface {
|
|
1760
1795
|
const metadata = () => ({
|
|
1761
1796
|
sessionId: id,
|
|
@@ -1823,6 +1858,8 @@ function sdkQuerySurface(
|
|
|
1823
1858
|
const currentThinkingLevel = api.getThinkingLevel();
|
|
1824
1859
|
return projectQ10Models({ models, currentModel, currentThinkingLevel });
|
|
1825
1860
|
},
|
|
1861
|
+
getModelProfiles: () =>
|
|
1862
|
+
projectModelProfileCatalog(ctx.modelRegistry.getModelProfiles(), ctx.modelRegistry.getError()),
|
|
1826
1863
|
getSkillState: () => ctx.getSkillState(),
|
|
1827
1864
|
getGates: () => {
|
|
1828
1865
|
const workflowGate = ctx.workflowGate;
|
|
@@ -1862,6 +1899,8 @@ function sdkQuerySurface(
|
|
|
1862
1899
|
getExtensions: () => ctx.getExtensions(),
|
|
1863
1900
|
getArtifactRange: (id, offset, length) => ctx.getArtifactRange?.(id, offset, length),
|
|
1864
1901
|
getJobs: () => ctx.getJobs(),
|
|
1902
|
+
getPromptStatus: (selector: { commandId?: string; turnId?: string; clientRef?: string }) =>
|
|
1903
|
+
promptStatusLookup(selector),
|
|
1865
1904
|
installedQueries: installedOperations(ctx, "query"),
|
|
1866
1905
|
};
|
|
1867
1906
|
}
|
|
@@ -1887,10 +1926,14 @@ function sdkControlSurface(
|
|
|
1887
1926
|
onPromptAccepted: (
|
|
1888
1927
|
correlation: { commandId: string; turnId: string },
|
|
1889
1928
|
requesterConnectionId?: string,
|
|
1929
|
+
clientRef?: string,
|
|
1930
|
+
trackReconciliation?: boolean,
|
|
1890
1931
|
) => void = () => {},
|
|
1891
1932
|
onPromptFailed: (correlation: { commandId: string; turnId: string }, error: unknown) => void = () => {},
|
|
1892
1933
|
acceptGateResolution: () => boolean,
|
|
1893
1934
|
trackGateResolution: <T>(resolution: Promise<T>) => Promise<T>,
|
|
1935
|
+
admitPrompt: (clientRef?: string) => void,
|
|
1936
|
+
releasePromptAdmission: (clientRef?: string) => void,
|
|
1894
1937
|
settings?: Settings,
|
|
1895
1938
|
configOverrides: Map<string, unknown> = new Map(),
|
|
1896
1939
|
configRevision: { current: number } = { current: 0 },
|
|
@@ -1967,19 +2010,35 @@ function sdkControlSurface(
|
|
|
1967
2010
|
deliverAs?: "steer" | "followUp",
|
|
1968
2011
|
rejectWhenBusy = false,
|
|
1969
2012
|
requesterConnectionId?: string,
|
|
2013
|
+
clientRef?: string,
|
|
2014
|
+
trackReconciliation = false,
|
|
1970
2015
|
) => {
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
2016
|
+
const trimmedClientRef = typeof clientRef === "string" ? clientRef.trim() : undefined;
|
|
2017
|
+
if (clientRef !== undefined && (!trimmedClientRef || trimmedClientRef.length > PROMPT_CLIENT_REF_MAX_LENGTH))
|
|
2018
|
+
throw Object.assign(new Error("clientRef must be a non-empty string of at most 128 characters."), {
|
|
2019
|
+
code: "invalid_input",
|
|
1974
2020
|
});
|
|
2021
|
+
if (trackReconciliation) admitPrompt(trimmedClientRef);
|
|
2022
|
+
try {
|
|
2023
|
+
if (forceFresh && isSessionBusy()) {
|
|
2024
|
+
throw Object.assign(
|
|
2025
|
+
new Error("Previous turn did not finish aborting before replacement prompt submission."),
|
|
2026
|
+
{
|
|
2027
|
+
code: "busy",
|
|
2028
|
+
},
|
|
2029
|
+
);
|
|
2030
|
+
}
|
|
2031
|
+
if (rejectWhenBusy && isSessionBusy())
|
|
2032
|
+
throw Object.assign(
|
|
2033
|
+
new Error("turn.prompt is unavailable while the agent is busy; use turn.steer explicitly."),
|
|
2034
|
+
{
|
|
2035
|
+
code: "busy",
|
|
2036
|
+
},
|
|
2037
|
+
);
|
|
2038
|
+
} catch (error) {
|
|
2039
|
+
if (trackReconciliation) releasePromptAdmission(trimmedClientRef);
|
|
2040
|
+
throw error;
|
|
1975
2041
|
}
|
|
1976
|
-
if (rejectWhenBusy && isSessionBusy())
|
|
1977
|
-
throw Object.assign(
|
|
1978
|
-
new Error("turn.prompt is unavailable while the agent is busy; use turn.steer explicitly."),
|
|
1979
|
-
{
|
|
1980
|
-
code: "busy",
|
|
1981
|
-
},
|
|
1982
|
-
);
|
|
1983
2042
|
const promptImages = Array.isArray(images) ? (images as { data: string; mimeType?: string }[]) : [];
|
|
1984
2043
|
const content: string | (TextContent | ImageContent)[] =
|
|
1985
2044
|
promptImages.length > 0
|
|
@@ -2011,7 +2070,7 @@ function sdkControlSurface(
|
|
|
2011
2070
|
const onPreflightAccepted = () => {
|
|
2012
2071
|
if (preflightSettled) return;
|
|
2013
2072
|
accepted = true;
|
|
2014
|
-
onPromptAccepted(correlation, requesterConnectionId);
|
|
2073
|
+
onPromptAccepted(correlation, requesterConnectionId, trimmedClientRef, trackReconciliation);
|
|
2015
2074
|
settlePreflight({ status: "accepted" });
|
|
2016
2075
|
};
|
|
2017
2076
|
// Do not acknowledge the prompt until AgentSession's async preflight
|
|
@@ -2048,13 +2107,17 @@ function sdkControlSurface(
|
|
|
2048
2107
|
try {
|
|
2049
2108
|
const result = await preflight.promise;
|
|
2050
2109
|
if (result.status === "rejected") throw result.error;
|
|
2051
|
-
return { commandId, turnId, accepted: true };
|
|
2110
|
+
return { commandId, turnId, accepted: true, ...(trimmedClientRef ? { clientRef: trimmedClientRef } : {}) };
|
|
2111
|
+
} catch (error) {
|
|
2112
|
+
if (trackReconciliation) releasePromptAdmission(trimmedClientRef);
|
|
2113
|
+
throw error;
|
|
2052
2114
|
} finally {
|
|
2053
2115
|
pendingPreflightCancellations.delete(cancelPreflight);
|
|
2054
2116
|
}
|
|
2055
2117
|
};
|
|
2056
2118
|
const surface: ControlSurface & { cancelPendingPreflights(): void } = {
|
|
2057
|
-
prompt: (text, images) =>
|
|
2119
|
+
prompt: (text, images, clientRef) =>
|
|
2120
|
+
submitPrompt(text, images, false, undefined, true, controlRequesterContext.getStore(), clientRef, true),
|
|
2058
2121
|
steer: text => sendSteer(text),
|
|
2059
2122
|
followUp: text => submitPrompt(text, undefined, false, "followUp", false, controlRequesterContext.getStore()),
|
|
2060
2123
|
abort: () => {
|
|
@@ -2778,7 +2841,7 @@ export function createNotificationsExtension(
|
|
|
2778
2841
|
sdkHostModeSupported?: boolean;
|
|
2779
2842
|
|
|
2780
2843
|
onSdkRequest?: (kind: "control" | "query", connectionId: string, frame: Record<string, unknown>) => void;
|
|
2781
|
-
|
|
2844
|
+
runBtwTurn?: (question: string, signal: AbortSignal) => Promise<{ replyText: string }>;
|
|
2782
2845
|
/** Observes settlement of optional session-branch startup after reconciliation completes. */
|
|
2783
2846
|
onBranchStartupSettled?: (receipt: { sessionId: string; status: SessionStartResult["status"] }) => void;
|
|
2784
2847
|
readNotificationFile?: (path: string) => Promise<Buffer>;
|
|
@@ -2840,7 +2903,6 @@ export function createNotificationsExtension(
|
|
|
2840
2903
|
"session.branch",
|
|
2841
2904
|
]);
|
|
2842
2905
|
const sessionId = (ctx: ExtensionContext): string => ctx.sessionManager.getSessionId();
|
|
2843
|
-
const sleep = (ms: number): Promise<void> => new Promise(resolve => setTimeout(resolve, ms));
|
|
2844
2906
|
|
|
2845
2907
|
async function stopSession(
|
|
2846
2908
|
id: string,
|
|
@@ -2915,6 +2977,12 @@ export function createNotificationsExtension(
|
|
|
2915
2977
|
try {
|
|
2916
2978
|
rt.workflowGate?.setRuntimeTurnProvider?.(null);
|
|
2917
2979
|
} catch {}
|
|
2980
|
+
try {
|
|
2981
|
+
const delivered = await pushTerminalSessionFrame(rt, { type: "session_closed", sessionId: id });
|
|
2982
|
+
if (!delivered) logger.warn("notifications: session_closed socket delivery was not acknowledged");
|
|
2983
|
+
} catch (e) {
|
|
2984
|
+
logger.warn(`notifications: session_closed failed: ${String(e)}`);
|
|
2985
|
+
}
|
|
2918
2986
|
await rt.waitForGateResolutionQuiescence();
|
|
2919
2987
|
try {
|
|
2920
2988
|
rt.disposeAckRecoveryParticipant();
|
|
@@ -2956,14 +3024,6 @@ export function createNotificationsExtension(
|
|
|
2956
3024
|
ownerReleaseFailures.push(e);
|
|
2957
3025
|
logger.warn(`sdk query snapshots: close failed: ${String(e)}`);
|
|
2958
3026
|
}
|
|
2959
|
-
let closeFrameSent = false;
|
|
2960
|
-
try {
|
|
2961
|
-
pushSessionFrame(rt, { type: "session_closed", sessionId: id });
|
|
2962
|
-
closeFrameSent = true;
|
|
2963
|
-
} catch (e) {
|
|
2964
|
-
logger.warn(`notifications: session_closed failed: ${String(e)}`);
|
|
2965
|
-
}
|
|
2966
|
-
if (closeFrameSent) await sleep(100);
|
|
2967
3027
|
let serverStopped = rt.serverStopped;
|
|
2968
3028
|
if (!serverStopped) {
|
|
2969
3029
|
try {
|
|
@@ -3143,11 +3203,20 @@ export function createNotificationsExtension(
|
|
|
3143
3203
|
failed: boolean;
|
|
3144
3204
|
error: unknown;
|
|
3145
3205
|
terminal: boolean;
|
|
3206
|
+
retainCorrelation: boolean;
|
|
3146
3207
|
createdAt: number;
|
|
3147
3208
|
bufferedFrames: Array<PromptLifecycleFrame | Record<string, unknown>>;
|
|
3148
3209
|
};
|
|
3149
3210
|
const promptSubmissions = new Map<string, PromptSubmission>();
|
|
3150
3211
|
const promptTerminalTombstones = new Map<string, number>();
|
|
3212
|
+
// Authoritative bounded reconciliation state for Q26 turn.prompt_status
|
|
3213
|
+
// (contract documented in ./prompt-reconciliation and ../prompt-status).
|
|
3214
|
+
// Active records never age into terminal; documented TTL/capacity
|
|
3215
|
+
// eviction is the only removal, after which lookups report `unknown`.
|
|
3216
|
+
const reconciliation = createPromptReconciliation();
|
|
3217
|
+
const admitPromptSubmission = reconciliation.admit;
|
|
3218
|
+
const notePromptReconciliationAccepted = reconciliation.noteAccepted;
|
|
3219
|
+
const lookupPromptStatus = reconciliation.lookup;
|
|
3151
3220
|
const removePendingPromptCorrelation = (correlation: { commandId: string; turnId: string }) => {
|
|
3152
3221
|
const pendingIndex = pendingPromptCorrelations.findIndex(
|
|
3153
3222
|
candidate => candidate.commandId === correlation.commandId && candidate.turnId === correlation.turnId,
|
|
@@ -3165,14 +3234,20 @@ export function createNotificationsExtension(
|
|
|
3165
3234
|
removePendingPromptCorrelation(correlation);
|
|
3166
3235
|
addTerminalTombstone(key);
|
|
3167
3236
|
};
|
|
3237
|
+
const expirePromptDelivery = (key: string, submission: PromptSubmission) => {
|
|
3238
|
+
promptSubmissions.delete(key);
|
|
3239
|
+
if (!submission.terminal && submission.retainCorrelation) return;
|
|
3240
|
+
const [commandId, turnId] = key.split(":", 2);
|
|
3241
|
+
if (!commandId || !turnId) return;
|
|
3242
|
+
removePendingPromptCorrelation({ commandId, turnId });
|
|
3243
|
+
addTerminalTombstone(key);
|
|
3244
|
+
};
|
|
3168
3245
|
const cleanupPromptRecords = (now = Date.now()) => {
|
|
3246
|
+
reconciliation.cleanup();
|
|
3169
3247
|
for (const [key, expiresAt] of promptTerminalTombstones)
|
|
3170
3248
|
if (expiresAt <= now) promptTerminalTombstones.delete(key);
|
|
3171
3249
|
for (const [key, submission] of promptSubmissions)
|
|
3172
|
-
if (submission.createdAt + PROMPT_SUBMISSION_TTL_MS <= now)
|
|
3173
|
-
const [commandId, turnId] = key.split(":", 2);
|
|
3174
|
-
if (commandId && turnId) finalizePrompt(key, { commandId, turnId });
|
|
3175
|
-
}
|
|
3250
|
+
if (submission.createdAt + PROMPT_SUBMISSION_TTL_MS <= now) expirePromptDelivery(key, submission);
|
|
3176
3251
|
};
|
|
3177
3252
|
const abandonPrompt = (submission: PromptSubmission) => {
|
|
3178
3253
|
submission.abandoned = true;
|
|
@@ -3183,6 +3258,7 @@ export function createNotificationsExtension(
|
|
|
3183
3258
|
frame: PromptLifecycleFrame,
|
|
3184
3259
|
) => {
|
|
3185
3260
|
cleanupPromptRecords();
|
|
3261
|
+
|
|
3186
3262
|
if (!correlation || !runtime) {
|
|
3187
3263
|
emitAgentLifecycle(runtime!, frame as Extract<PromptLifecycleFrame, { type: "agent_start" | "agent_end" }>);
|
|
3188
3264
|
return;
|
|
@@ -3248,15 +3324,21 @@ export function createNotificationsExtension(
|
|
|
3248
3324
|
const recordPromptAccepted = (
|
|
3249
3325
|
correlation: { commandId: string; turnId: string },
|
|
3250
3326
|
requesterConnectionId?: string,
|
|
3327
|
+
clientRef?: string,
|
|
3328
|
+
trackReconciliation = false,
|
|
3251
3329
|
) => {
|
|
3252
|
-
if (!requesterConnectionId)
|
|
3330
|
+
if (!requesterConnectionId) {
|
|
3331
|
+
// No delivery owner: tracked prompts cannot be reconciled. Release
|
|
3332
|
+
// their admission reservation instead of leaking the active slot.
|
|
3333
|
+
if (trackReconciliation) reconciliation.releaseAdmission(clientRef);
|
|
3334
|
+
return;
|
|
3335
|
+
}
|
|
3336
|
+
if (trackReconciliation) notePromptReconciliationAccepted(correlation, clientRef);
|
|
3253
3337
|
cleanupPromptRecords();
|
|
3254
3338
|
while (promptSubmissions.size >= PROMPT_SUBMISSION_CAPACITY) {
|
|
3255
3339
|
const oldest = promptSubmissions.entries().next().value as [string, PromptSubmission] | undefined;
|
|
3256
3340
|
if (!oldest) break;
|
|
3257
|
-
|
|
3258
|
-
const [commandId, turnId] = key.split(":", 2);
|
|
3259
|
-
if (commandId && turnId) finalizePrompt(key, { commandId, turnId });
|
|
3341
|
+
expirePromptDelivery(oldest[0], oldest[1]);
|
|
3260
3342
|
}
|
|
3261
3343
|
pendingPromptCorrelations.push(correlation);
|
|
3262
3344
|
promptSubmissions.set(promptSubmissionKey(correlation), {
|
|
@@ -3266,6 +3348,7 @@ export function createNotificationsExtension(
|
|
|
3266
3348
|
failed: false,
|
|
3267
3349
|
error: undefined,
|
|
3268
3350
|
terminal: false,
|
|
3351
|
+
retainCorrelation: trackReconciliation,
|
|
3269
3352
|
createdAt: Date.now(),
|
|
3270
3353
|
bufferedFrames: [],
|
|
3271
3354
|
});
|
|
@@ -3281,17 +3364,15 @@ export function createNotificationsExtension(
|
|
|
3281
3364
|
return true;
|
|
3282
3365
|
};
|
|
3283
3366
|
const emitPromptFailure = (correlation: { commandId: string; turnId: string }, error: unknown) => {
|
|
3367
|
+
const sanitized = sanitizePromptFailure(error);
|
|
3368
|
+
reconciliation.noteTransition(correlation, { type: "agent_failed", error: sanitized });
|
|
3284
3369
|
const submission = promptSubmissions.get(promptSubmissionKey(correlation));
|
|
3285
3370
|
if (!submission || !runtime || !recordPromptTerminal(correlation)) return;
|
|
3286
|
-
const candidate = error as { code?: unknown; message?: unknown };
|
|
3287
3371
|
emitPromptLifecycle(correlation, {
|
|
3288
3372
|
type: "agent_failed",
|
|
3289
3373
|
sessionId: runtime.id,
|
|
3290
3374
|
...correlation,
|
|
3291
|
-
error:
|
|
3292
|
-
code: typeof candidate.code === "string" ? candidate.code : "internal",
|
|
3293
|
-
message: typeof candidate.message === "string" ? candidate.message : "Prompt submission failed.",
|
|
3294
|
-
},
|
|
3375
|
+
error: sanitized,
|
|
3295
3376
|
});
|
|
3296
3377
|
};
|
|
3297
3378
|
const recordPromptFailure = (correlation: { commandId: string; turnId: string }, error: unknown) => {
|
|
@@ -3334,6 +3415,7 @@ export function createNotificationsExtension(
|
|
|
3334
3415
|
};
|
|
3335
3416
|
},
|
|
3336
3417
|
configOverrides,
|
|
3418
|
+
lookupPromptStatus,
|
|
3337
3419
|
),
|
|
3338
3420
|
id,
|
|
3339
3421
|
revisions,
|
|
@@ -3349,6 +3431,8 @@ export function createNotificationsExtension(
|
|
|
3349
3431
|
recordPromptFailure,
|
|
3350
3432
|
() => runtime?.stopping !== true,
|
|
3351
3433
|
trackGateResolution,
|
|
3434
|
+
admitPromptSubmission,
|
|
3435
|
+
reconciliation.releaseAdmission,
|
|
3352
3436
|
settings,
|
|
3353
3437
|
configOverrides,
|
|
3354
3438
|
configRevision,
|
|
@@ -3407,7 +3491,7 @@ export function createNotificationsExtension(
|
|
|
3407
3491
|
stateRoot,
|
|
3408
3492
|
token,
|
|
3409
3493
|
sendFrame: (connectionId, frame) => sendSdkFrame(connectionId, frame),
|
|
3410
|
-
connectionCapabilities: connectionId => hostCapCache.get(connectionId)
|
|
3494
|
+
connectionCapabilities: connectionId => hostCapCache.get(connectionId),
|
|
3411
3495
|
installProviderDefinitions,
|
|
3412
3496
|
onProviderDefinitionsRemoved: removeProviderDefinitions,
|
|
3413
3497
|
onFrame: handler => {
|
|
@@ -3479,6 +3563,7 @@ export function createNotificationsExtension(
|
|
|
3479
3563
|
},
|
|
3480
3564
|
),
|
|
3481
3565
|
);
|
|
3566
|
+
|
|
3482
3567
|
if (rotatesIdentity && response.ok !== true) {
|
|
3483
3568
|
identityControlInFlight = false;
|
|
3484
3569
|
deferredIdentityRotation = undefined;
|
|
@@ -3548,6 +3633,8 @@ export function createNotificationsExtension(
|
|
|
3548
3633
|
pendingPromptCorrelations,
|
|
3549
3634
|
activePromptCorrelation: undefined,
|
|
3550
3635
|
recordPromptTerminal,
|
|
3636
|
+
notePromptReconciliation: reconciliation.noteTransition,
|
|
3637
|
+
emitPromptFailure,
|
|
3551
3638
|
emitPromptLifecycle,
|
|
3552
3639
|
emitPromptEvent,
|
|
3553
3640
|
pendingInbound: new Set<number>(),
|
|
@@ -3587,10 +3674,10 @@ export function createNotificationsExtension(
|
|
|
3587
3674
|
};
|
|
3588
3675
|
|
|
3589
3676
|
const ephemeralTurns = new EphemeralTurnHost(sendSdkFrame, async (question, signal) => {
|
|
3590
|
-
if (!options.
|
|
3677
|
+
if (!options.runBtwTurn) throw new Error("Ephemeral turns are unavailable.");
|
|
3591
3678
|
const generation = initializedRuntime.policyGeneration;
|
|
3592
3679
|
if (initializedRuntime.policySuspended) throw new Error("Notification policy is provisional.");
|
|
3593
|
-
const result = await options.
|
|
3680
|
+
const result = await options.runBtwTurn(question, signal);
|
|
3594
3681
|
if (
|
|
3595
3682
|
initializedRuntime.policySuspended ||
|
|
3596
3683
|
initializedRuntime.policyGeneration !== generation ||
|
|
@@ -3609,6 +3696,13 @@ export function createNotificationsExtension(
|
|
|
3609
3696
|
if (!frame || typeof frame !== "object") return;
|
|
3610
3697
|
const typedFrame = frame as Record<string, unknown>;
|
|
3611
3698
|
if (typedFrame.type === "ephemeral_turn" || typedFrame.type === "ephemeral_turn_cancel") return;
|
|
3699
|
+
if (typedFrame.type === "event_replay") {
|
|
3700
|
+
const capabilities = Array.isArray(typedFrame.capabilities) ? typedFrame.capabilities : [];
|
|
3701
|
+
hostCapCache.set(
|
|
3702
|
+
inbound.connectionId,
|
|
3703
|
+
new Set(capabilities.filter((capability): capability is string => typeof capability === "string")),
|
|
3704
|
+
);
|
|
3705
|
+
}
|
|
3612
3706
|
inboundSdkFrame?.(inbound.connectionId, typedFrame);
|
|
3613
3707
|
} catch {}
|
|
3614
3708
|
});
|
|
@@ -3749,7 +3843,7 @@ export function createNotificationsExtension(
|
|
|
3749
3843
|
}
|
|
3750
3844
|
const gate = runtime?.workflowGate;
|
|
3751
3845
|
const workflowGateActive =
|
|
3752
|
-
gate?.
|
|
3846
|
+
gate?.supportsRemoteGateAnswers?.() === true &&
|
|
3753
3847
|
typeof gate.onGateEmitted === "function" &&
|
|
3754
3848
|
typeof gate.resolveGateFromNotification === "function";
|
|
3755
3849
|
const gateId = gatePresentations.routeFor(reply.id);
|
|
@@ -4202,7 +4296,9 @@ export function createNotificationsExtension(
|
|
|
4202
4296
|
? Gate
|
|
4203
4297
|
: never,
|
|
4204
4298
|
): void => {
|
|
4205
|
-
const
|
|
4299
|
+
const rawGateOptions = g.options ?? [];
|
|
4300
|
+
const options = rawGateOptions.map(o => String((o as { label?: unknown }).label ?? ""));
|
|
4301
|
+
const recommendedIndex = recommendedIndexFromGateOptions(rawGateOptions);
|
|
4206
4302
|
gateOptions.set(g.gate_id, options);
|
|
4207
4303
|
const promptCtx = g.context as { prompt?: unknown; title?: unknown } | undefined;
|
|
4208
4304
|
const question =
|
|
@@ -4219,6 +4315,7 @@ export function createNotificationsExtension(
|
|
|
4219
4315
|
sessionId: id,
|
|
4220
4316
|
question,
|
|
4221
4317
|
options,
|
|
4318
|
+
...(recommendedIndex === undefined ? {} : { recommendedIndex }),
|
|
4222
4319
|
controls: [],
|
|
4223
4320
|
multi: stageState.multi === true,
|
|
4224
4321
|
allowEmpty: stageState.allow_empty === true,
|
|
@@ -4577,22 +4674,45 @@ export function createNotificationsExtension(
|
|
|
4577
4674
|
if (!pending) return;
|
|
4578
4675
|
rt.pendingFinal = undefined;
|
|
4579
4676
|
if (pending.text && rt.notificationsActive && !rt.redact) {
|
|
4580
|
-
|
|
4581
|
-
|
|
4582
|
-
|
|
4583
|
-
|
|
4584
|
-
phase: "finalized",
|
|
4585
|
-
finalAnswer: true,
|
|
4677
|
+
// Under lean, hold intermediate finals until agent_end when the agent is still
|
|
4678
|
+
// running so provisional-policy activation cannot reintroduce per-turn spam.
|
|
4679
|
+
if (rt.verbosity === "lean" && rt.busy) {
|
|
4680
|
+
rt.pendingSettled = {
|
|
4586
4681
|
text: pending.text,
|
|
4587
4682
|
...(pending.messageRef ? { messageRef: pending.messageRef } : {}),
|
|
4588
|
-
}
|
|
4589
|
-
}
|
|
4590
|
-
|
|
4683
|
+
};
|
|
4684
|
+
} else {
|
|
4685
|
+
try {
|
|
4686
|
+
pushSessionFrame(rt, {
|
|
4687
|
+
type: "turn_stream",
|
|
4688
|
+
sessionId: id,
|
|
4689
|
+
phase: "finalized",
|
|
4690
|
+
finalAnswer: true,
|
|
4691
|
+
text: pending.text,
|
|
4692
|
+
...(pending.messageRef ? { messageRef: pending.messageRef } : {}),
|
|
4693
|
+
});
|
|
4694
|
+
} catch (error) {
|
|
4695
|
+
logger.warn(`notifications: pushFrame (pending turn) failed: ${String(error)}`);
|
|
4696
|
+
}
|
|
4591
4697
|
}
|
|
4592
4698
|
}
|
|
4593
4699
|
resetTurnStreamState(rt);
|
|
4594
4700
|
};
|
|
4595
4701
|
|
|
4702
|
+
/** Emit the deferred lean settled answer exactly once (agent_end / idle). */
|
|
4703
|
+
const flushPendingSettled = (rt: SessionRuntime, id: string): void => {
|
|
4704
|
+
const settled = rt.pendingSettled;
|
|
4705
|
+
rt.pendingSettled = undefined;
|
|
4706
|
+
if (!settled?.text || !rt.notificationsActive || rt.redact || rt.policySuspended) return;
|
|
4707
|
+
const previousLiveRef = rt.liveRef;
|
|
4708
|
+
if (settled.messageRef) rt.liveRef = settled.messageRef;
|
|
4709
|
+
try {
|
|
4710
|
+
flushTurnText(rt, id, settled.text, true);
|
|
4711
|
+
} finally {
|
|
4712
|
+
if (!settled.messageRef) rt.liveRef = previousLiveRef;
|
|
4713
|
+
}
|
|
4714
|
+
};
|
|
4715
|
+
|
|
4596
4716
|
// Drive the live typing indicator: mark busy when the agent loop starts so
|
|
4597
4717
|
// the daemon shows "typing…" in the thread while the agent is thinking,
|
|
4598
4718
|
// before any turn output exists. Cleared on `agent_end` below.
|
|
@@ -4605,6 +4725,7 @@ export function createNotificationsExtension(
|
|
|
4605
4725
|
rt.busy = true;
|
|
4606
4726
|
const correlation = rt.pendingPromptCorrelations.shift();
|
|
4607
4727
|
rt.activePromptCorrelation = correlation;
|
|
4728
|
+
rt.notePromptReconciliation(correlation, { type: "agent_start" });
|
|
4608
4729
|
rt.emitPromptLifecycle(correlation, { type: "agent_start", sessionId: id, ...correlation });
|
|
4609
4730
|
try {
|
|
4610
4731
|
// `activity` is the native live-host lifecycle surface. The separately
|
|
@@ -4649,8 +4770,35 @@ export function createNotificationsExtension(
|
|
|
4649
4770
|
rt.busy = false;
|
|
4650
4771
|
const correlation = rt.activePromptCorrelation;
|
|
4651
4772
|
if (correlation) {
|
|
4652
|
-
|
|
4653
|
-
|
|
4773
|
+
const terminalAssistant = (Array.isArray(event.messages) ? [...event.messages].reverse() : []).find(
|
|
4774
|
+
message =>
|
|
4775
|
+
message &&
|
|
4776
|
+
typeof message === "object" &&
|
|
4777
|
+
(message as { role?: unknown }).role === "assistant" &&
|
|
4778
|
+
((message as { stopReason?: unknown }).stopReason === "error" ||
|
|
4779
|
+
(message as { stopReason?: unknown }).stopReason === "aborted"),
|
|
4780
|
+
) as { stopReason?: "error" | "aborted"; errorMessage?: unknown } | undefined;
|
|
4781
|
+
const failureCode =
|
|
4782
|
+
event.stopReason === "cancelled"
|
|
4783
|
+
? "cancelled"
|
|
4784
|
+
: terminalAssistant?.stopReason === "error"
|
|
4785
|
+
? "agent_error"
|
|
4786
|
+
: terminalAssistant?.stopReason === "aborted"
|
|
4787
|
+
? "aborted"
|
|
4788
|
+
: undefined;
|
|
4789
|
+
if (failureCode)
|
|
4790
|
+
rt.emitPromptFailure(correlation, {
|
|
4791
|
+
code: failureCode,
|
|
4792
|
+
message:
|
|
4793
|
+
typeof terminalAssistant?.errorMessage === "string"
|
|
4794
|
+
? terminalAssistant.errorMessage
|
|
4795
|
+
: "Prompt submission failed.",
|
|
4796
|
+
});
|
|
4797
|
+
else {
|
|
4798
|
+
rt.notePromptReconciliation(correlation, { type: "agent_end" });
|
|
4799
|
+
if (rt.recordPromptTerminal(correlation))
|
|
4800
|
+
rt.emitPromptLifecycle(correlation, { type: "agent_end", sessionId: id, ...correlation });
|
|
4801
|
+
}
|
|
4654
4802
|
} else {
|
|
4655
4803
|
rt.emitPromptLifecycle(undefined, { type: "agent_end", sessionId: id });
|
|
4656
4804
|
}
|
|
@@ -4694,9 +4842,14 @@ export function createNotificationsExtension(
|
|
|
4694
4842
|
logger.warn(`notifications: noteIdle failed: ${String(e)}`);
|
|
4695
4843
|
}
|
|
4696
4844
|
|
|
4845
|
+
// Lean: emit the latest deferred assistant answer exactly once at idle.
|
|
4846
|
+
// Intermediate tool-turn narration was held on turn_end; ask lead-ins were
|
|
4847
|
+
// already flushed immediately and deduped via preAskFlushedText.
|
|
4848
|
+
flushPendingSettled(rt, id);
|
|
4849
|
+
|
|
4697
4850
|
// On idle, stream a context update with metadata (token/model usage +
|
|
4698
|
-
// working-tree diff) unless redaction is on.
|
|
4699
|
-
//
|
|
4851
|
+
// working-tree diff) unless redaction is on. Under verbose the agent's last
|
|
4852
|
+
// message is already streamed per turn_end; lean flushes it just above.
|
|
4700
4853
|
if (!rt.redact && rt.verbosity === "verbose") {
|
|
4701
4854
|
const usage = (
|
|
4702
4855
|
ctx as { getContextUsage?: () => { tokens: number | null; contextWindow: number } | undefined }
|
|
@@ -4725,17 +4878,20 @@ export function createNotificationsExtension(
|
|
|
4725
4878
|
}
|
|
4726
4879
|
});
|
|
4727
4880
|
|
|
4728
|
-
// Stream viable agent output
|
|
4729
|
-
//
|
|
4730
|
-
//
|
|
4731
|
-
//
|
|
4732
|
-
//
|
|
4733
|
-
// rate-limit pool
|
|
4881
|
+
// Stream viable agent output. Verbose mirrors each turn that produced assistant
|
|
4882
|
+
// text. Lean defers the latest answer until agent_end (idle) so tool-heavy runs
|
|
4883
|
+
// do not flood remote clients with intermediate narration; ask lead-ins still
|
|
4884
|
+
// flush immediately. Tool-only turns yield no text and are skipped. Redaction
|
|
4885
|
+
// suppresses streamed content (only the one-time identity header survives).
|
|
4886
|
+
// The daemon coalesces/throttles these via its shared rate-limit pool.
|
|
4734
4887
|
// Push the in-flight turn's assistant text as a finalized turn_stream, deduped
|
|
4735
4888
|
// against what was already flushed for this turn (the pre-ask lead-in).
|
|
4736
4889
|
const flushTurnText = (rt: SessionRuntime, id: string, text: string | undefined, finalAnswer: boolean): void => {
|
|
4737
4890
|
if (!text || text === rt.preAskFlushedText || !rt.notificationsActive || rt.policySuspended) return;
|
|
4738
4891
|
rt.preAskFlushedText = text;
|
|
4892
|
+
// Ask lead-ins supersede any deferred lean settled answer from earlier turns
|
|
4893
|
+
// so agent_end does not re-emit stale intermediate narration (#2863 review).
|
|
4894
|
+
if (!finalAnswer) rt.pendingSettled = undefined;
|
|
4739
4895
|
// Decision A: a stream-enabled turn must finalize as an in-place edit of ONE
|
|
4740
4896
|
// live message, never a fresh (rich-promotable) send. If live frames were
|
|
4741
4897
|
// async-queued and none landed before this flush, reuse the per-turn ref
|
|
@@ -4876,19 +5032,37 @@ export function createNotificationsExtension(
|
|
|
4876
5032
|
rt.turnClosed = true;
|
|
4877
5033
|
return;
|
|
4878
5034
|
}
|
|
4879
|
-
if (text)
|
|
5035
|
+
if (text) {
|
|
5036
|
+
if (rt.verbosity === "verbose") {
|
|
5037
|
+
// Verbose: one finalized turn_stream per turn with assistant text.
|
|
5038
|
+
flushTurnText(rt, id, text, true);
|
|
5039
|
+
} else if (text !== rt.preAskFlushedText) {
|
|
5040
|
+
// Lean: hold the latest answer until agent_end. Skip when this turn
|
|
5041
|
+
// already flushed the same text as an ask lead-in (no duplicate at idle).
|
|
5042
|
+
rt.pendingSettled = {
|
|
5043
|
+
text,
|
|
5044
|
+
...(rt.liveRef ? { messageRef: rt.liveRef } : {}),
|
|
5045
|
+
};
|
|
5046
|
+
} else {
|
|
5047
|
+
// Lead-in already flushed: drop any older deferred settled text so idle
|
|
5048
|
+
// does not re-emit intermediate narration after the ask prompt (#2863).
|
|
5049
|
+
rt.pendingSettled = undefined;
|
|
5050
|
+
}
|
|
5051
|
+
}
|
|
4880
5052
|
resetTurnStreamState(rt);
|
|
4881
5053
|
});
|
|
4882
5054
|
|
|
4883
|
-
// Live streaming (opt-in): push throttled in-progress assistant
|
|
4884
|
-
// non-finalized turn_stream frames so remote clients edit one message
|
|
4885
|
-
// turn streams.
|
|
4886
|
-
//
|
|
5055
|
+
// Live streaming (opt-in + verbose only): push throttled in-progress assistant
|
|
5056
|
+
// text as non-finalized turn_stream frames so remote clients edit one message
|
|
5057
|
+
// as the turn streams. Lean keeps settled-answer-only delivery; live frames
|
|
5058
|
+
// are suppressed even when the stream preference is on. The finalized frame
|
|
5059
|
+
// (turn_end / agent_end) carries the same messageRef and lands the authoritative
|
|
5060
|
+
// text. Suppressed under redaction.
|
|
4887
5061
|
api.on("message_update", (event, ctx) => {
|
|
4888
5062
|
const id = sessionId(ctx);
|
|
4889
5063
|
const rt = runtimes.get(id);
|
|
4890
5064
|
rt?.emitPromptEvent(event);
|
|
4891
|
-
if (!rt?.notificationsActive || !rt.stream || rt.redact || rt.turnClosed) return;
|
|
5065
|
+
if (!rt?.notificationsActive || !rt.stream || rt.redact || rt.turnClosed || rt.verbosity !== "verbose") return;
|
|
4892
5066
|
if ((event.message as { role?: unknown }).role !== "assistant") return;
|
|
4893
5067
|
if (rt.liveRef === undefined && rt.turnSeq !== undefined) {
|
|
4894
5068
|
rt.liveRef = String(rt.turnSeq);
|