@gajae-code/coding-agent 0.11.11 → 0.12.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +131 -4
- package/dist/types/async/job-manager.d.ts +17 -3
- package/dist/types/cli/read-cli.d.ts +1 -0
- package/dist/types/commands/deep-interview.d.ts +9 -0
- package/dist/types/commands/harness.d.ts +22 -0
- package/dist/types/commands/read.d.ts +6 -0
- package/dist/types/config/model-registry.d.ts +4 -0
- package/dist/types/config/provider-auth-health.d.ts +14 -0
- package/dist/types/config/provider-ranking.d.ts +59 -0
- package/dist/types/config/settings-schema.d.ts +83 -1
- package/dist/types/config/settings.d.ts +2 -0
- package/dist/types/exa/mcp-client.d.ts +10 -1
- package/dist/types/exec/bash-executor.d.ts +45 -12
- package/dist/types/extensibility/extensions/types.d.ts +38 -4
- package/dist/types/extensibility/gjc-plugins/index.d.ts +12 -2
- package/dist/types/extensibility/gjc-plugins/installer.d.ts +63 -7
- package/dist/types/extensibility/gjc-plugins/lifecycle-reconciliation.d.ts +55 -0
- package/dist/types/extensibility/gjc-plugins/lifecycle.d.ts +64 -0
- package/dist/types/extensibility/gjc-plugins/runtime-quarantine.d.ts +67 -0
- package/dist/types/extensibility/gjc-plugins/session-validation.d.ts +3 -1
- package/dist/types/extensibility/gjc-plugins/types.d.ts +124 -0
- package/dist/types/extensibility/shared-events.d.ts +6 -0
- package/dist/types/gjc-runtime/deep-interview-ambiguity.d.ts +5 -12
- package/dist/types/gjc-runtime/deep-interview-recorder.d.ts +17 -32
- package/dist/types/gjc-runtime/deep-interview-stage.d.ts +37 -0
- package/dist/types/gjc-runtime/deep-interview-state.d.ts +4 -135
- package/dist/types/gjc-runtime/gc-runtime.d.ts +1 -1
- package/dist/types/gjc-runtime/launch-tmux.d.ts +1 -0
- package/dist/types/gjc-runtime/memory-guard-owner-claims.d.ts +47 -0
- package/dist/types/gjc-runtime/ralplan-runtime.d.ts +70 -1
- package/dist/types/gjc-runtime/state-writer.d.ts +5 -46
- package/dist/types/gjc-runtime/team-launch.d.ts +1 -1
- package/dist/types/gjc-runtime/team-runtime.d.ts +27 -1
- package/dist/types/gjc-runtime/team-store.d.ts +12 -0
- package/dist/types/gjc-runtime/team-worker-heartbeat.d.ts +43 -0
- package/dist/types/gjc-runtime/team-worker-memory-guard.d.ts +92 -0
- package/dist/types/gjc-runtime/tmux-owner-isolation.d.ts +17 -0
- package/dist/types/gjc-runtime/ultragoal-receipt-freshness.d.ts +8 -0
- package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +31 -80
- package/dist/types/gjc-runtime/workflow-manifest.d.ts +3 -5
- package/dist/types/internal-urls/local-root-gc.d.ts +2 -0
- package/dist/types/main.d.ts +1 -1
- package/dist/types/memories/index.d.ts +2 -0
- package/dist/types/modes/acp/acp-agent.d.ts +10 -19
- package/dist/types/modes/components/assistant-message.d.ts +3 -1
- package/dist/types/modes/components/gjc-bundle-settings.d.ts +34 -0
- package/dist/types/modes/components/irc-sidebar.d.ts +41 -0
- package/dist/types/modes/components/notifications-settings-editor.d.ts +1 -0
- package/dist/types/modes/components/read-tool-group.d.ts +4 -4
- package/dist/types/modes/components/session-observer-overlay.d.ts +22 -0
- package/dist/types/modes/components/settings-selector.d.ts +11 -0
- package/dist/types/modes/components/status-line/gh.d.ts +5 -0
- package/dist/types/modes/components/tool-execution.d.ts +8 -0
- package/dist/types/modes/components/transcript-viewer-overlay.d.ts +18 -1
- package/dist/types/modes/controllers/event-controller.d.ts +3 -0
- package/dist/types/modes/interactive-mode.d.ts +2 -0
- package/dist/types/modes/irc-observation-ledger.d.ts +17 -1
- package/dist/types/modes/types.d.ts +2 -0
- package/dist/types/modes/utils/ui-helpers.d.ts +16 -0
- package/dist/types/reminders/star-reminder.d.ts +3 -13
- package/dist/types/runtime-mcp/manager.d.ts +1 -0
- package/dist/types/runtime-mcp/smithery-auth.d.ts +5 -0
- package/dist/types/sdk/acp/adapter.d.ts +3 -1
- package/dist/types/sdk/broker/lifecycle.d.ts +5 -0
- package/dist/types/sdk/broker/transport.d.ts +2 -0
- package/dist/types/sdk/bus/attachment-registry.d.ts +1 -0
- package/dist/types/sdk/bus/chat-daemon-control.d.ts +4 -1
- package/dist/types/sdk/bus/config.d.ts +6 -2
- package/dist/types/sdk/bus/control-drain-lease.d.ts +52 -0
- package/dist/types/sdk/bus/index.d.ts +44 -1
- package/dist/types/sdk/bus/kind-aware-reconciliation.d.ts +44 -0
- package/dist/types/sdk/bus/lifecycle-control-runtime.d.ts +4 -0
- package/dist/types/sdk/bus/prompt-reconciliation.d.ts +2 -1
- package/dist/types/sdk/bus/reconciliation-store.d.ts +69 -0
- package/dist/types/sdk/bus/rich-draft.d.ts +1 -0
- package/dist/types/sdk/bus/rich-render.d.ts +2 -0
- package/dist/types/sdk/bus/telegram-adoption-intent.d.ts +165 -0
- package/dist/types/sdk/bus/telegram-cli.d.ts +12 -0
- package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +11 -4
- package/dist/types/sdk/bus/telegram-daemon.d.ts +25 -1
- package/dist/types/sdk/bus/telegram-reference.d.ts +11 -1
- package/dist/types/sdk/bus/threaded-render.d.ts +3 -2
- package/dist/types/sdk/bus/topic-registry.d.ts +11 -1
- package/dist/types/sdk/host/control/operations.d.ts +1 -1
- package/dist/types/sdk/host/host.d.ts +2 -0
- package/dist/types/sdk/host/query/handlers.d.ts +5 -0
- package/dist/types/sdk/host/query/revision-store.d.ts +2 -0
- package/dist/types/sdk/host/reverse-leases.d.ts +1 -1
- package/dist/types/sdk/prompt-status.d.ts +23 -3
- package/dist/types/sdk/session.d.ts +6 -0
- package/dist/types/session/agent-session.d.ts +82 -12
- package/dist/types/session/artifacts.d.ts +8 -0
- package/dist/types/session/blob-store.d.ts +57 -4
- package/dist/types/session/internal/managed-session-storage.d.ts +8 -1
- package/dist/types/session/memory-guard-checkpoint-participant.d.ts +69 -0
- package/dist/types/session/session-manager.d.ts +48 -19
- package/dist/types/session/session-storage.d.ts +4 -0
- package/dist/types/session/streaming-output.d.ts +96 -12
- package/dist/types/setup/credential-import.d.ts +12 -0
- package/dist/types/skill-state/active-state.d.ts +12 -4
- package/dist/types/skill-state/workflow-hud.d.ts +9 -0
- package/dist/types/task/executor.d.ts +3 -2
- package/dist/types/task/index.d.ts +3 -1
- package/dist/types/task/provider-retry-status.d.ts +8 -0
- package/dist/types/task/receipt.d.ts +7 -1
- package/dist/types/task/types.d.ts +23 -0
- package/dist/types/tools/ask-answer-registry.d.ts +2 -2
- package/dist/types/tools/bash-interactive.d.ts +4 -1
- package/dist/types/tools/bash.d.ts +3 -1
- package/dist/types/tools/browser/launch.d.ts +7 -0
- package/dist/types/tools/fetch.d.ts +17 -1
- package/dist/types/tools/image-gen.d.ts +4 -0
- package/dist/types/tools/index.d.ts +4 -0
- package/dist/types/tools/output-meta.d.ts +44 -3
- package/dist/types/tools/path-utils.d.ts +1 -0
- package/dist/types/tools/read-internals.d.ts +12 -0
- package/dist/types/tools/read.d.ts +45 -7
- package/dist/types/tools/resource-gc.d.ts +4 -0
- package/dist/types/tools/review.d.ts +5 -7
- package/dist/types/tools/subagent.d.ts +2 -0
- package/dist/types/tools/tool-result.d.ts +7 -1
- package/dist/types/utils/gh.d.ts +13 -0
- package/dist/types/utils/image-resize.d.ts +1 -0
- package/dist/types/web/search/providers/kimi.d.ts +2 -0
- package/dist/types/web/search/providers/searxng.d.ts +7 -0
- package/dist/types/web/search/providers/xai.d.ts +2 -0
- package/package.json +11 -13
- package/scripts/acp-conformance-agent.ts +218 -0
- package/scripts/benchmark-sticky-viewport-pr1.ts +239 -0
- package/scripts/capture-gjc-bundle-settings.ts +306 -0
- package/scripts/capture-sticky-viewport-showcase.ts +292 -0
- package/scripts/generate-sdk-operation-inventory.ts +5 -0
- package/scripts/resident-memory-bench.ts +1341 -0
- package/scripts/run-acp-conformance.ts +285 -0
- package/scripts/verify-gjc-sdk-canonicalization.ts +122 -18
- package/scripts/verify-sticky-viewport-showcase.ts +611 -0
- package/src/async/job-manager.ts +78 -20
- package/src/capability/index.ts +8 -0
- package/src/cli/fast-help.ts +1 -1
- package/src/cli/plugin-cli.ts +176 -18
- package/src/cli/read-cli.ts +2 -1
- package/src/cli.ts +1 -0
- package/src/commands/deep-interview.ts +29 -9
- package/src/commands/harness.ts +35 -12
- package/src/commands/read.ts +11 -2
- package/src/commands/team.ts +20 -2
- package/src/config/file-lock.ts +11 -2
- package/src/config/model-registry.ts +21 -2
- package/src/config/provider-auth-health.ts +42 -0
- package/src/config/provider-ranking.ts +119 -0
- package/src/config/settings-schema.ts +74 -1
- package/src/config/settings.ts +246 -108
- package/src/coordinator-mcp/question-gate-codec.ts +1 -1
- package/src/coordinator-mcp/server.ts +5 -12
- package/src/defaults/gjc/skills/deep-interview/SKILL.md +230 -82
- package/src/defaults/gjc/skills/ralplan/SKILL.md +77 -41
- package/src/defaults/gjc/skills/team/SKILL.md +3 -1
- package/src/defaults/gjc/skills/ultragoal/SKILL.md +62 -39
- package/src/defaults/gjc/skills/ultragoal/validation-batch-contracts.md +12 -0
- package/src/defaults/gjc-defaults.ts +4 -4
- package/src/eval/py/executor.ts +7 -2
- package/src/exa/mcp-client.ts +12 -3
- package/src/exec/bash-executor.ts +144 -27
- package/src/extensibility/extensions/runner.ts +15 -0
- package/src/extensibility/extensions/types.ts +46 -5
- package/src/extensibility/gjc-plugins/constrained-hooks.ts +8 -2
- package/src/extensibility/gjc-plugins/index.ts +19 -2
- package/src/extensibility/gjc-plugins/installer.ts +189 -50
- package/src/extensibility/gjc-plugins/lifecycle-reconciliation.ts +199 -0
- package/src/extensibility/gjc-plugins/lifecycle.ts +626 -0
- package/src/extensibility/gjc-plugins/observability.ts +3 -3
- package/src/extensibility/gjc-plugins/prompt-appendix.ts +5 -1
- package/src/extensibility/gjc-plugins/runtime-adapters.ts +12 -2
- package/src/extensibility/gjc-plugins/runtime-quarantine.ts +121 -0
- package/src/extensibility/gjc-plugins/schema.ts +84 -12
- package/src/extensibility/gjc-plugins/session-validation.ts +9 -3
- package/src/extensibility/gjc-plugins/types.ts +141 -0
- package/src/extensibility/gjc-plugins/validation.ts +9 -0
- package/src/extensibility/shared-events.ts +5 -0
- package/src/gjc-runtime/deep-interview-ambiguity.ts +47 -82
- package/src/gjc-runtime/deep-interview-recorder.ts +227 -310
- package/src/gjc-runtime/deep-interview-runtime.ts +109 -270
- package/src/gjc-runtime/deep-interview-stage.ts +1016 -0
- package/src/gjc-runtime/deep-interview-state.ts +12 -943
- package/src/gjc-runtime/gc-render.ts +1 -0
- package/src/gjc-runtime/gc-runtime.ts +11 -1
- package/src/gjc-runtime/launch-tmux.ts +25 -6
- package/src/gjc-runtime/managed-owner-admission.ts +1 -1
- package/src/gjc-runtime/memory-guard-owner-claims.ts +389 -0
- package/src/gjc-runtime/ralplan-runtime.ts +1149 -98
- package/src/gjc-runtime/state-runtime.ts +2 -1
- package/src/gjc-runtime/state-writer.ts +70 -222
- package/src/gjc-runtime/team-launch.ts +42 -5
- package/src/gjc-runtime/team-runtime.ts +1085 -40
- package/src/gjc-runtime/team-store.ts +32 -0
- package/src/gjc-runtime/team-worker-heartbeat.ts +149 -0
- package/src/gjc-runtime/team-worker-memory-guard.ts +363 -0
- package/src/gjc-runtime/team-workers.ts +1 -0
- package/src/gjc-runtime/tmux-owner-isolation.ts +28 -0
- package/src/gjc-runtime/tmux-sessions.ts +58 -13
- package/src/gjc-runtime/ultragoal-receipt-freshness.ts +20 -0
- package/src/gjc-runtime/ultragoal-runtime.ts +414 -618
- package/src/gjc-runtime/workflow-manifest.generated.json +105 -449
- package/src/gjc-runtime/workflow-manifest.ts +38 -247
- package/src/hooks/native-skill-hook.ts +17 -4
- package/src/hooks/skill-state.ts +1 -23
- package/src/internal-urls/docs-index.generated.ts +21 -22
- package/src/internal-urls/local-root-gc.ts +127 -0
- package/src/main.ts +9 -5
- package/src/memories/index.ts +10 -0
- package/src/modes/DESIGN.md +98 -0
- package/src/modes/acp/acp-agent.ts +339 -191
- package/src/modes/acp/acp-event-mapper.ts +14 -7
- package/src/modes/acp/acp-mode.ts +39 -3
- package/src/modes/components/assistant-message.ts +123 -22
- package/src/modes/components/gjc-bundle-settings.ts +508 -0
- package/src/modes/components/irc-sidebar.ts +176 -62
- package/src/modes/components/model-selector.ts +61 -4
- package/src/modes/components/notifications-settings-editor.ts +12 -1
- package/src/modes/components/oauth-selector.ts +96 -28
- package/src/modes/components/read-tool-group.ts +82 -14
- package/src/modes/components/session-observer-overlay.ts +457 -83
- package/src/modes/components/settings-selector.ts +116 -23
- package/src/modes/components/status-line/gh.ts +16 -0
- package/src/modes/components/tool-execution.ts +138 -27
- package/src/modes/components/tool-status-header.ts +3 -14
- package/src/modes/components/transcript-viewer-overlay.ts +99 -24
- package/src/modes/controllers/command-controller.ts +4 -0
- package/src/modes/controllers/event-controller.ts +109 -27
- package/src/modes/controllers/extension-ui-controller.ts +26 -2
- package/src/modes/controllers/input-controller.ts +18 -3
- package/src/modes/controllers/selector-controller.ts +127 -36
- package/src/modes/interactive-mode.ts +62 -3
- package/src/modes/irc-observation-ledger.ts +71 -15
- package/src/modes/runtime-init.ts +22 -2
- package/src/modes/types.ts +2 -0
- package/src/modes/utils/ui-helpers.ts +94 -1
- package/src/prompts/agent-fragments/ralplan-persistence.md +4 -3
- package/src/prompts/agents/architect.md +9 -1
- package/src/prompts/agents/critic.md +9 -0
- package/src/prompts/system/eager-todo.md +8 -4
- package/src/prompts/system/system-prompt.md +11 -5
- package/src/prompts/tools/bash.md +5 -5
- package/src/prompts/tools/read.md +4 -2
- package/src/prompts/tools/search-tool-bm25.md +5 -0
- package/src/prompts/tools/task.md +1 -1
- package/src/reminders/star-reminder.ts +4 -42
- package/src/runtime-mcp/manager.ts +19 -4
- package/src/runtime-mcp/smithery-auth.ts +17 -2
- package/src/runtime-mcp/smithery-connect.ts +11 -1
- package/src/sdk/acp/adapter.ts +22 -6
- package/src/sdk/broker/lifecycle.ts +25 -6
- package/src/sdk/broker/transport.ts +35 -3
- package/src/sdk/bus/attachment-registry.ts +5 -1
- package/src/sdk/bus/chat-daemon-cli.ts +4 -39
- package/src/sdk/bus/chat-daemon-control.ts +6 -3
- package/src/sdk/bus/config.ts +18 -2
- package/src/sdk/bus/control-drain-lease.ts +104 -0
- package/src/sdk/bus/index.ts +984 -146
- package/src/sdk/bus/kind-aware-reconciliation.ts +317 -0
- package/src/sdk/bus/lifecycle-commands.ts +4 -1
- package/src/sdk/bus/lifecycle-control-runtime.ts +403 -88
- package/src/sdk/bus/prompt-reconciliation.ts +2 -1
- package/src/sdk/bus/reconciliation-store.ts +303 -0
- package/src/sdk/bus/rich-draft.ts +1 -1
- package/src/sdk/bus/rich-render.ts +2 -2
- package/src/sdk/bus/telegram-adoption-intent.ts +574 -0
- package/src/sdk/bus/telegram-cli.ts +33 -8
- package/src/sdk/bus/telegram-daemon-cli.ts +1 -0
- package/src/sdk/bus/telegram-daemon-contract.ts +11 -4
- package/src/sdk/bus/telegram-daemon.ts +916 -23
- package/src/sdk/bus/telegram-reference.ts +40 -3
- package/src/sdk/bus/threaded-render.ts +48 -3
- package/src/sdk/bus/topic-registry.ts +20 -0
- package/src/sdk/host/control/dispatch.ts +5 -1
- package/src/sdk/host/control/operations.ts +1 -1
- package/src/sdk/host/host.ts +23 -1
- package/src/sdk/host/query/handlers.ts +55 -0
- package/src/sdk/host/query/revision-store.ts +36 -2
- package/src/sdk/host/reverse-leases.ts +44 -7
- package/src/sdk/mcp/server.ts +46 -18
- package/src/sdk/prompt-status.ts +16 -3
- package/src/sdk/protocol/operation-inventory.generated.json +51 -0
- package/src/sdk/protocol/operation-registry.ts +7 -2
- package/src/sdk/session.ts +101 -10
- package/src/session/agent-session.ts +1286 -220
- package/src/session/artifacts.ts +22 -0
- package/src/session/auth-broker-config.ts +10 -3
- package/src/session/blob-store.ts +915 -24
- package/src/session/internal/managed-session-scope.ts +44 -6
- package/src/session/internal/managed-session-storage.ts +48 -6
- package/src/session/memory-guard-checkpoint-participant.ts +131 -0
- package/src/session/session-manager.ts +2364 -438
- package/src/session/session-storage.ts +9 -1
- package/src/session/streaming-output.ts +500 -111
- package/src/setup/credential-import.ts +75 -13
- package/src/setup/provider-onboarding.ts +19 -4
- package/src/skill-state/active-state.ts +32 -10
- package/src/skill-state/workflow-hud.ts +42 -2
- package/src/skill-state/workflow-mutation-guard.ts +560 -8
- package/src/task/executor.ts +122 -39
- package/src/task/index.ts +23 -14
- package/src/task/provider-retry-status.ts +16 -0
- package/src/task/receipt.ts +23 -7
- package/src/task/render.ts +6 -2
- package/src/task/types.ts +108 -0
- package/src/tools/ask-answer-registry.ts +12 -6
- package/src/tools/ask.ts +169 -118
- package/src/tools/ast-edit.ts +1 -0
- package/src/tools/ast-grep.ts +1 -0
- package/src/tools/bash-interactive.ts +8 -3
- package/src/tools/bash.ts +503 -60
- package/src/tools/browser/launch.ts +41 -6
- package/src/tools/fetch.ts +114 -23
- package/src/tools/find.ts +1 -0
- package/src/tools/image-gen.ts +29 -5
- package/src/tools/index.ts +4 -0
- package/src/tools/output-meta.ts +281 -20
- package/src/tools/path-utils.ts +8 -3
- package/src/tools/read-internals.ts +44 -0
- package/src/tools/read.ts +1560 -310
- package/src/tools/resource-gc.ts +182 -18
- package/src/tools/review.ts +5 -7
- package/src/tools/search.ts +1 -0
- package/src/tools/subagent-render.ts +6 -2
- package/src/tools/subagent.ts +6 -0
- package/src/tools/telegram-send.ts +6 -3
- package/src/tools/tool-result.ts +14 -1
- package/src/utils/gh.ts +39 -0
- package/src/utils/image-resize.ts +3 -0
- package/src/web/search/providers/anthropic.ts +4 -4
- package/src/web/search/providers/codex.ts +1 -1
- package/src/web/search/providers/kimi.ts +15 -5
- package/src/web/search/providers/searxng.ts +47 -4
- package/src/web/search/providers/xai.ts +7 -2
- package/dist/types/gjc-runtime/deep-interview-draft.d.ts +0 -8
- package/dist/types/gjc-runtime/deep-interview-payload.d.ts +0 -41
- package/dist/types/gjc-runtime/deep-interview-repair.d.ts +0 -17
- package/dist/types/gjc-runtime/ultragoal-pipeline.d.ts +0 -23
- package/src/defaults/gjc/skills/ultragoal/pipeline-validation-contracts.md +0 -30
- package/src/gjc-runtime/deep-interview-draft.ts +0 -843
- package/src/gjc-runtime/deep-interview-payload.ts +0 -262
- package/src/gjc-runtime/deep-interview-repair.ts +0 -1674
- package/src/gjc-runtime/ultragoal-pipeline.ts +0 -306
|
@@ -209,7 +209,12 @@ export function buildActionMarkdown(action: {
|
|
|
209
209
|
if (action.kind === "idle") {
|
|
210
210
|
return action.summary ? `🟢 Agent idle\n${action.summary}` : "🟢 Agent idle";
|
|
211
211
|
}
|
|
212
|
-
const
|
|
212
|
+
const question = (action.question ?? "Question")
|
|
213
|
+
.split(/\r\n|\r|\n/)
|
|
214
|
+
.map(line => line.trimEnd())
|
|
215
|
+
.map(line => (line ? `**${line}**` : ""))
|
|
216
|
+
.join(" \n");
|
|
217
|
+
const heading = `❓ ${question}`;
|
|
213
218
|
const options = action.options ?? [];
|
|
214
219
|
const displayOptions = withRecommendedOptionLabel(options, action.recommendedIndex);
|
|
215
220
|
if (options.length === 0) return `${heading}\n\n(reply with text)`;
|
|
@@ -217,19 +222,43 @@ export function buildActionMarkdown(action: {
|
|
|
217
222
|
return `${heading}\n\n${list}`;
|
|
218
223
|
}
|
|
219
224
|
|
|
225
|
+
export type TelegramNotificationSound = "all" | "important" | "none";
|
|
226
|
+
|
|
227
|
+
export type TelegramDeliveryLane = "ask" | "idle" | "live" | "finalized";
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Resolve Telegram's optional silent-delivery flag. `finalChunk` silences
|
|
231
|
+
* non-final actionable chunks under the `important` policy; `undefined`
|
|
232
|
+
* intentionally omits the field so Telegram uses its normal audible delivery
|
|
233
|
+
* behavior.
|
|
234
|
+
*/
|
|
235
|
+
export function telegramDisableNotification(
|
|
236
|
+
sound: TelegramNotificationSound | undefined,
|
|
237
|
+
lane: TelegramDeliveryLane,
|
|
238
|
+
finalChunk = true,
|
|
239
|
+
): true | undefined {
|
|
240
|
+
if (sound === "none") return true;
|
|
241
|
+
if (sound !== "important") return undefined;
|
|
242
|
+
return (lane === "ask" || lane === "idle") && finalChunk ? undefined : true;
|
|
243
|
+
}
|
|
244
|
+
|
|
220
245
|
/** Send Telegram HTML text chunks sequentially so long messages preserve order. */
|
|
221
246
|
export async function sendTelegramHtmlChunks(
|
|
222
247
|
send: TelegramSend,
|
|
223
248
|
chatId: string,
|
|
224
249
|
text: string,
|
|
225
250
|
inlineKeyboard?: InlineButton[][],
|
|
251
|
+
sound?: TelegramNotificationSound,
|
|
252
|
+
lane: TelegramDeliveryLane = "ask",
|
|
226
253
|
): Promise<void> {
|
|
227
254
|
const chunks = splitTelegramHtml(text);
|
|
228
255
|
for (let i = 0; i < chunks.length; i++) {
|
|
256
|
+
const disableNotification = telegramDisableNotification(sound, lane, i === chunks.length - 1);
|
|
229
257
|
await send("sendMessage", {
|
|
230
258
|
chat_id: chatId,
|
|
231
259
|
text: chunks[i]!,
|
|
232
260
|
parse_mode: TELEGRAM_PARSE_MODE,
|
|
261
|
+
...(disableNotification === true ? { disable_notification: true } : {}),
|
|
233
262
|
...(i === chunks.length - 1 && inlineKeyboard ? { reply_markup: { inline_keyboard: inlineKeyboard } } : {}),
|
|
234
263
|
});
|
|
235
264
|
}
|
|
@@ -348,6 +377,7 @@ export interface TelegramReferenceOptions {
|
|
|
348
377
|
endpointFile: string;
|
|
349
378
|
apiBase?: string;
|
|
350
379
|
fetchImpl?: typeof fetch;
|
|
380
|
+
sound?: TelegramNotificationSound;
|
|
351
381
|
}
|
|
352
382
|
|
|
353
383
|
/**
|
|
@@ -395,7 +425,14 @@ export async function runTelegramReferenceClient(opts: TelegramReferenceOptions)
|
|
|
395
425
|
controls: msg.controls,
|
|
396
426
|
summary: msg.summary,
|
|
397
427
|
});
|
|
398
|
-
await sendTelegramHtmlChunks(
|
|
428
|
+
await sendTelegramHtmlChunks(
|
|
429
|
+
send,
|
|
430
|
+
opts.chatId,
|
|
431
|
+
rendered.text,
|
|
432
|
+
rendered.inline_keyboard,
|
|
433
|
+
opts.sound,
|
|
434
|
+
msg.kind ?? "ask",
|
|
435
|
+
);
|
|
399
436
|
} else if (msg.type === "action_unavailable") {
|
|
400
437
|
const requiredCapabilities = Array.isArray(msg.requiredCapabilities)
|
|
401
438
|
? msg.requiredCapabilities
|
|
@@ -416,7 +453,7 @@ export async function runTelegramReferenceClient(opts: TelegramReferenceOptions)
|
|
|
416
453
|
// session's forum topic; this reference shows the minimal handling.
|
|
417
454
|
const threaded = renderThreadedFrame(msg as never);
|
|
418
455
|
if (threaded?.text) {
|
|
419
|
-
await sendTelegramHtmlChunks(send, opts.chatId, threaded.text);
|
|
456
|
+
await sendTelegramHtmlChunks(send, opts.chatId, threaded.text, undefined, opts.sound, threaded.lane);
|
|
420
457
|
}
|
|
421
458
|
}
|
|
422
459
|
};
|
|
@@ -18,13 +18,13 @@ export interface ThreadedSend {
|
|
|
18
18
|
method: "sendMessage" | "sendPhoto" | "sendDocument";
|
|
19
19
|
/** Rate-limit lane for prioritisation/fairness. */
|
|
20
20
|
lane: RateLimitLane;
|
|
21
|
-
/** Message text (sendMessage) or
|
|
21
|
+
/** Message text (sendMessage) or media caption (sendPhoto/sendDocument). */
|
|
22
22
|
text?: string;
|
|
23
23
|
/** Base64 image bytes for sendPhoto. */
|
|
24
24
|
photoBase64?: string;
|
|
25
25
|
/** Base64 file bytes for sendDocument. */
|
|
26
26
|
documentBase64?: string;
|
|
27
|
-
/**
|
|
27
|
+
/** Uploaded media MIME type. */
|
|
28
28
|
mime?: string;
|
|
29
29
|
/** Suggested document filename. */
|
|
30
30
|
fileName?: string;
|
|
@@ -100,6 +100,40 @@ function str(v: unknown): string | undefined {
|
|
|
100
100
|
return typeof v === "string" && v.length > 0 ? v : undefined;
|
|
101
101
|
}
|
|
102
102
|
|
|
103
|
+
function baseMimeType(mime: string): string {
|
|
104
|
+
return mime.split(";", 1)[0]!.trim().toLowerCase();
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export function supportsTelegramPhotoUpload(mime: string | undefined): boolean {
|
|
108
|
+
if (mime === undefined) return true;
|
|
109
|
+
const normalized = baseMimeType(mime);
|
|
110
|
+
return normalized === "image/jpeg" || normalized === "image/png";
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function imageDocumentFileName(mime: string | undefined): string {
|
|
114
|
+
switch (mime === undefined ? "" : baseMimeType(mime)) {
|
|
115
|
+
case "image/webp":
|
|
116
|
+
return "image.webp";
|
|
117
|
+
case "image/gif":
|
|
118
|
+
return "image.gif";
|
|
119
|
+
case "image/svg+xml":
|
|
120
|
+
return "image.svg";
|
|
121
|
+
case "image/avif":
|
|
122
|
+
return "image.avif";
|
|
123
|
+
case "image/bmp":
|
|
124
|
+
case "image/x-ms-bmp":
|
|
125
|
+
return "image.bmp";
|
|
126
|
+
case "image/tiff":
|
|
127
|
+
return "image.tiff";
|
|
128
|
+
case "image/heic":
|
|
129
|
+
return "image.heic";
|
|
130
|
+
case "image/heif":
|
|
131
|
+
return "image.heif";
|
|
132
|
+
default:
|
|
133
|
+
return "image";
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
103
137
|
function isDotOnlyText(value: string): boolean {
|
|
104
138
|
return /^[.\s]+$/.test(value);
|
|
105
139
|
}
|
|
@@ -244,11 +278,22 @@ export function renderThreadedFrame(frame: ThreadedFrame): ThreadedSend | undefi
|
|
|
244
278
|
const data = str(frame.data);
|
|
245
279
|
if (!data) return undefined;
|
|
246
280
|
const caption = str(frame.caption);
|
|
281
|
+
const mime = str(frame.mime);
|
|
282
|
+
if (!supportsTelegramPhotoUpload(mime)) {
|
|
283
|
+
return {
|
|
284
|
+
method: "sendDocument",
|
|
285
|
+
lane: "finalized",
|
|
286
|
+
documentBase64: data,
|
|
287
|
+
mime,
|
|
288
|
+
fileName: imageDocumentFileName(mime),
|
|
289
|
+
text: finalizeTelegramHtml(caption === undefined ? undefined : escapeHtml(caption)),
|
|
290
|
+
};
|
|
291
|
+
}
|
|
247
292
|
return {
|
|
248
293
|
method: "sendPhoto",
|
|
249
294
|
lane: "finalized",
|
|
250
295
|
photoBase64: data,
|
|
251
|
-
mime
|
|
296
|
+
mime,
|
|
252
297
|
text: finalizeTelegramHtml(caption === undefined ? undefined : escapeHtml(caption)),
|
|
253
298
|
};
|
|
254
299
|
}
|
|
@@ -43,6 +43,8 @@ export interface TopicRecord {
|
|
|
43
43
|
creationLeaseEpoch?: number;
|
|
44
44
|
/** An uncertain delete fences future creation and inbound routing. */
|
|
45
45
|
authorityState?: "active" | "delete_pending";
|
|
46
|
+
/** Provenance for topics created by the user and adopted by GJC. Missing means daemon-created. */
|
|
47
|
+
topicOrigin?: "user_created";
|
|
46
48
|
/** Telegram chat and endpoint authority last proven to use this topic. */
|
|
47
49
|
chatId?: string;
|
|
48
50
|
/** Canonical endpoint tuple (URL + token) that currently holds the lease. */
|
|
@@ -234,6 +236,7 @@ export class TopicRegistry {
|
|
|
234
236
|
...(raw.authorityState === "delete_pending" || fenceSupersedesRecord
|
|
235
237
|
? { authorityState: "delete_pending" as const }
|
|
236
238
|
: {}),
|
|
239
|
+
...(raw.topicOrigin === "user_created" ? { topicOrigin: "user_created" as const } : {}),
|
|
237
240
|
...(isValidBindingString(raw.chatId) ? { chatId: raw.chatId } : {}),
|
|
238
241
|
...(isValidBindingString(raw.endpointKey) ? { endpointKey: raw.endpointKey } : {}),
|
|
239
242
|
...(isValidBindingString(raw.endpointDigest) ? { endpointDigest: raw.endpointDigest } : {}),
|
|
@@ -307,6 +310,21 @@ export class TopicRegistry {
|
|
|
307
310
|
const record = this.topics.get(sessionId);
|
|
308
311
|
return record?.authorityState !== "delete_pending" && this.byTopic.get(record?.topicId ?? "") === sessionId;
|
|
309
312
|
}
|
|
313
|
+
/**
|
|
314
|
+
* Pure read-only availability check for user-topic adoption. Rejects invalid
|
|
315
|
+
* ids and any id already committed, delete-pending/fenced, staged, or
|
|
316
|
+
* ambiguous, without mutating maps, epochs, or persistence. Used inside the
|
|
317
|
+
* {@link getOrCreateTopic} create callback to fail-closed before adopting a
|
|
318
|
+
* user-created topic id.
|
|
319
|
+
*/
|
|
320
|
+
isTopicIdAvailable(topicId: string): boolean {
|
|
321
|
+
if (!isValidTopicId(topicId)) return false;
|
|
322
|
+
if (this.#ambiguousTopicIds.has(topicId)) return false;
|
|
323
|
+
if (this.byTopic.has(topicId)) return false;
|
|
324
|
+
for (const record of this.topics.values()) if (record.topicId === topicId) return false;
|
|
325
|
+
for (const record of this.staged.values()) if (record.topicId === topicId) return false;
|
|
326
|
+
return true;
|
|
327
|
+
}
|
|
310
328
|
|
|
311
329
|
/**
|
|
312
330
|
* Resolve endpoint authority for identity-less replay. An endpoint may bootstrap
|
|
@@ -455,6 +473,7 @@ export class TopicRegistry {
|
|
|
455
473
|
binding?: TopicEndpointBinding,
|
|
456
474
|
commit?: () => Promise<void>,
|
|
457
475
|
transientClaimant?: object,
|
|
476
|
+
topicOrigin?: TopicRecord["topicOrigin"],
|
|
458
477
|
): Promise<TopicRecord> {
|
|
459
478
|
const existing = this.topics.get(sessionId);
|
|
460
479
|
if (existing?.authorityState === "delete_pending") throw new Error("topic authority is deletion-fenced");
|
|
@@ -491,6 +510,7 @@ export class TopicRegistry {
|
|
|
491
510
|
}
|
|
492
511
|
: {}),
|
|
493
512
|
...(revoked ? { authorityState: "delete_pending" as const } : {}),
|
|
513
|
+
...(topicOrigin === "user_created" ? { topicOrigin: "user_created" as const } : {}),
|
|
494
514
|
};
|
|
495
515
|
if (revoked) {
|
|
496
516
|
this.topics.set(sessionId, record);
|
|
@@ -158,7 +158,11 @@ function invoke(
|
|
|
158
158
|
case "workflow.plan_approve":
|
|
159
159
|
return surface.approvePlan(text(input, "id"), input.choice, input.expectedSessionId as string | undefined);
|
|
160
160
|
case "skill.invoke":
|
|
161
|
-
return surface.invokeSkill(
|
|
161
|
+
return surface.invokeSkill(
|
|
162
|
+
text(input, "name"),
|
|
163
|
+
input.args,
|
|
164
|
+
typeof input.clientRef === "string" ? input.clientRef : undefined,
|
|
165
|
+
);
|
|
162
166
|
case "mode.plan.set":
|
|
163
167
|
return surface.setPlanMode(input.on as boolean);
|
|
164
168
|
case "mode.goal.operate":
|
|
@@ -19,7 +19,7 @@ export interface ControlSurface {
|
|
|
19
19
|
idempotencyKey?: string,
|
|
20
20
|
): Promise<ControlValue> | ControlValue;
|
|
21
21
|
approvePlan(id: string, choice: ControlValue, expectedSessionId?: string): Promise<ControlValue> | ControlValue;
|
|
22
|
-
invokeSkill(name: string, args: ControlValue): Promise<ControlValue> | ControlValue;
|
|
22
|
+
invokeSkill(name: string, args: ControlValue, clientRef?: string): Promise<ControlValue> | ControlValue;
|
|
23
23
|
setPlanMode(on: boolean): Promise<ControlValue> | ControlValue;
|
|
24
24
|
operateGoal(op: string, objective?: string): Promise<ControlValue> | ControlValue;
|
|
25
25
|
replaceTodo(items: ControlValue): Promise<ControlValue> | ControlValue;
|
package/src/sdk/host/host.ts
CHANGED
|
@@ -9,6 +9,13 @@ export interface SessionSdkHostOptions extends HostEndpointAdapters {
|
|
|
9
9
|
query?: (connectionId: string, frame: SdkFrame) => unknown | Promise<unknown>;
|
|
10
10
|
/** Best-effort diagnostic observation of accepted control/query frames. */
|
|
11
11
|
onRequest?: SdkRequestObserver;
|
|
12
|
+
/** Runs before a control response is sent; identity transitions use sendTerminal. */
|
|
13
|
+
beforeControlResponse?: (
|
|
14
|
+
connectionId: string,
|
|
15
|
+
request: SdkFrame,
|
|
16
|
+
response: SdkFrame,
|
|
17
|
+
sendTerminal: () => Promise<void>,
|
|
18
|
+
) => void | Promise<void>;
|
|
12
19
|
/** Runs only after a successful control response has been sent to the client. */
|
|
13
20
|
afterControlResponse?: (connectionId: string, request: SdkFrame, response: SdkFrame) => void | Promise<void>;
|
|
14
21
|
installProviderDefinitions?: (capability: string, definitions: unknown) => void;
|
|
@@ -36,6 +43,14 @@ function errorFrame(connectionId: string, frame: SdkFrame, error: unknown): SdkF
|
|
|
36
43
|
? candidate.code
|
|
37
44
|
: "internal";
|
|
38
45
|
const message = typeof candidate?.message === "string" ? candidate.message : "SDK host operation failed.";
|
|
46
|
+
if (frame.type === "control_request") {
|
|
47
|
+
return {
|
|
48
|
+
type: "control_response",
|
|
49
|
+
id: typeof frame.id === "string" ? frame.id : "",
|
|
50
|
+
ok: false,
|
|
51
|
+
error: { code, message },
|
|
52
|
+
};
|
|
53
|
+
}
|
|
39
54
|
return {
|
|
40
55
|
type: "reverse_response",
|
|
41
56
|
id: typeof frame.id === "string" ? frame.id : "",
|
|
@@ -214,7 +229,14 @@ export class SessionSdkHost {
|
|
|
214
229
|
const result = await this.#options.control?.(connectionId, frame);
|
|
215
230
|
if (result !== undefined) {
|
|
216
231
|
const response = { type: "control_response", ...(result as SdkFrame) };
|
|
217
|
-
|
|
232
|
+
let terminalSent = false;
|
|
233
|
+
const sendTerminal = async (): Promise<void> => {
|
|
234
|
+
if (terminalSent) return;
|
|
235
|
+
terminalSent = true;
|
|
236
|
+
await this.#send(connectionId, response);
|
|
237
|
+
};
|
|
238
|
+
await this.#options.beforeControlResponse?.(connectionId, frame, response, sendTerminal);
|
|
239
|
+
await sendTerminal();
|
|
218
240
|
await this.#options.afterControlResponse?.(connectionId, frame, response);
|
|
219
241
|
}
|
|
220
242
|
break;
|
|
@@ -46,6 +46,11 @@ export interface SessionSurface {
|
|
|
46
46
|
getJobs(): unknown | Promise<unknown>;
|
|
47
47
|
/** Q26 keyed lookup of a submitted prompt's authoritative reconciliation status. */
|
|
48
48
|
getPromptStatus?(selector: { commandId?: string; turnId?: string; clientRef?: string }): unknown | Promise<unknown>;
|
|
49
|
+
getSkillInvokeStatus?(selector: {
|
|
50
|
+
commandId?: string;
|
|
51
|
+
turnId?: string;
|
|
52
|
+
clientRef?: string;
|
|
53
|
+
}): unknown | Promise<unknown>;
|
|
49
54
|
/** Q27 effective model-profile catalog from the live session registry. */
|
|
50
55
|
getModelProfiles?(): unknown[] | Promise<unknown[]>;
|
|
51
56
|
/** Query rows backed by the session's installed binding map. */
|
|
@@ -127,6 +132,7 @@ const names = [
|
|
|
127
132
|
"runtime.jobs.list",
|
|
128
133
|
"turn.prompt_status",
|
|
129
134
|
"models.profiles.list",
|
|
135
|
+
"skill.invoke_status",
|
|
130
136
|
];
|
|
131
137
|
|
|
132
138
|
export class QueryHandlers {
|
|
@@ -156,6 +162,7 @@ export class QueryHandlers {
|
|
|
156
162
|
if (query === "Q02") return await this.#transcriptBody(request);
|
|
157
163
|
if (query === "Q23") return await this.#resourceBody(request);
|
|
158
164
|
if (query === "Q26") return await this.#promptStatus(request);
|
|
165
|
+
if (query === "Q28") return await this.#skillInvokeStatus(request);
|
|
159
166
|
if (query === "Q24") return await this.#artifact(request);
|
|
160
167
|
if (query === "Q27" && request.input && Object.keys(request.input).length > 0)
|
|
161
168
|
return this.#error(request, "invalid_request", false, "models.profiles.list does not accept input fields.");
|
|
@@ -517,6 +524,54 @@ export class QueryHandlers {
|
|
|
517
524
|
return { id: request.id, ok: true, page };
|
|
518
525
|
}
|
|
519
526
|
|
|
527
|
+
async #skillInvokeStatus(request: QueryRequest): Promise<QueryResponse> {
|
|
528
|
+
if (request.cursor)
|
|
529
|
+
return this.#error(request, "invalid_request", false, "skill.invoke_status does not support cursors.");
|
|
530
|
+
const input = request.input ?? {};
|
|
531
|
+
for (const key of Object.keys(input))
|
|
532
|
+
if (key !== "commandId" && key !== "turnId" && key !== "clientRef")
|
|
533
|
+
return this.#error(
|
|
534
|
+
request,
|
|
535
|
+
"invalid_request",
|
|
536
|
+
false,
|
|
537
|
+
`skill.invoke_status does not accept selector field "${key}".`,
|
|
538
|
+
);
|
|
539
|
+
const commandId = typeof input.commandId === "string" && input.commandId ? input.commandId : undefined;
|
|
540
|
+
const turnId = typeof input.turnId === "string" && input.turnId ? input.turnId : undefined;
|
|
541
|
+
const rawClientRef = typeof input.clientRef === "string" ? input.clientRef : undefined;
|
|
542
|
+
const trimmedClientRef = rawClientRef?.trim();
|
|
543
|
+
if (rawClientRef !== undefined && (!trimmedClientRef || trimmedClientRef.length > PROMPT_CLIENT_REF_MAX_LENGTH))
|
|
544
|
+
return this.#error(
|
|
545
|
+
request,
|
|
546
|
+
"invalid_request",
|
|
547
|
+
false,
|
|
548
|
+
"clientRef must be a non-empty string of at most 128 characters.",
|
|
549
|
+
);
|
|
550
|
+
const clientRef = trimmedClientRef || undefined;
|
|
551
|
+
if ((commandId === undefined) !== (turnId === undefined))
|
|
552
|
+
return this.#error(request, "invalid_request", false, "commandId and turnId must be provided together.");
|
|
553
|
+
if (commandId !== undefined && clientRef !== undefined)
|
|
554
|
+
return this.#error(
|
|
555
|
+
request,
|
|
556
|
+
"invalid_request",
|
|
557
|
+
false,
|
|
558
|
+
"Provide exactly one selector: a commandId/turnId pair or a clientRef.",
|
|
559
|
+
);
|
|
560
|
+
if (commandId === undefined && clientRef === undefined)
|
|
561
|
+
return this.#error(
|
|
562
|
+
request,
|
|
563
|
+
"invalid_request",
|
|
564
|
+
false,
|
|
565
|
+
"skill.invoke_status requires a commandId/turnId pair or a clientRef.",
|
|
566
|
+
);
|
|
567
|
+
if (typeof this.surface.getSkillInvokeStatus !== "function")
|
|
568
|
+
return this.#error(request, "unavailable", false, "skill.invoke_status is unavailable for this session.");
|
|
569
|
+
const result = await this.surface.getSkillInvokeStatus(
|
|
570
|
+
clientRef !== undefined ? { clientRef } : { commandId: commandId!, turnId: turnId! },
|
|
571
|
+
);
|
|
572
|
+
return { id: request.id, ok: true, result };
|
|
573
|
+
}
|
|
574
|
+
|
|
520
575
|
async #promptStatus(request: QueryRequest): Promise<QueryResponse> {
|
|
521
576
|
if (request.cursor)
|
|
522
577
|
return this.#error(request, "invalid_request", false, "turn.prompt_status does not support cursors.");
|
|
@@ -2,6 +2,32 @@ import { createHash } from "node:crypto";
|
|
|
2
2
|
import { chmod, mkdir, mkdtemp, open, readFile, rename, rm, unlink } from "node:fs/promises";
|
|
3
3
|
import { tmpdir } from "node:os";
|
|
4
4
|
import { join } from "node:path";
|
|
5
|
+
import { postmortem } from "@gajae-code/utils";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Spill directories this process created under the system temp dir.
|
|
9
|
+
*
|
|
10
|
+
* `close()` removes a store's own directory, but an abnormal exit never reaches
|
|
11
|
+
* it, and these hold real spilled snapshot data rather than a marker. The two
|
|
12
|
+
* sibling temp-artifact caches (`shell-snapshot`, `python-runner-artifact`)
|
|
13
|
+
* already register a postmortem sweep; this does the same.
|
|
14
|
+
*
|
|
15
|
+
* Only `mkdtemp` directories are tracked. A caller-supplied `storageDir` is
|
|
16
|
+
* durable session storage and must never be swept here.
|
|
17
|
+
*/
|
|
18
|
+
const processSpillDirectories = new Set<string>();
|
|
19
|
+
|
|
20
|
+
/** Test seam: spill directories currently tracked for postmortem cleanup. */
|
|
21
|
+
export function trackedSpillDirectoriesForTest(): string[] {
|
|
22
|
+
return [...processSpillDirectories];
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
postmortem.register("sdk-revision-store:spill", async () => {
|
|
26
|
+
for (const dir of [...processSpillDirectories]) {
|
|
27
|
+
processSpillDirectories.delete(dir);
|
|
28
|
+
await rm(dir, { recursive: true, force: true }).catch(() => undefined);
|
|
29
|
+
}
|
|
30
|
+
});
|
|
5
31
|
|
|
6
32
|
export const MAX_REVISIONS_PER_RESOURCE = 8;
|
|
7
33
|
export const MAX_PINNED_REVISIONS = 128;
|
|
@@ -512,7 +538,10 @@ export class RevisionStore {
|
|
|
512
538
|
this.#memoryBytes = 0;
|
|
513
539
|
this.#chunkRefs.clear();
|
|
514
540
|
this.#manifestRefs.clear();
|
|
515
|
-
if (this.#directory)
|
|
541
|
+
if (this.#directory) {
|
|
542
|
+
processSpillDirectories.delete(this.#directory);
|
|
543
|
+
await rm(this.#directory, { recursive: true, force: true });
|
|
544
|
+
}
|
|
516
545
|
})();
|
|
517
546
|
return this.#closePromise;
|
|
518
547
|
}
|
|
@@ -1040,7 +1069,12 @@ export class RevisionStore {
|
|
|
1040
1069
|
}
|
|
1041
1070
|
|
|
1042
1071
|
async #spillDirectory(): Promise<string> {
|
|
1043
|
-
if (!this.#directory)
|
|
1072
|
+
if (!this.#directory) {
|
|
1073
|
+
this.#directory = await mkdtemp(join(tmpdir(), "gjc-sdk-snapshots-"));
|
|
1074
|
+
// Only temp spill directories are swept on abnormal exit; a caller-supplied
|
|
1075
|
+
// storageDir is durable and never enters this set.
|
|
1076
|
+
processSpillDirectories.add(this.#directory);
|
|
1077
|
+
}
|
|
1044
1078
|
await mkdir(join(this.#directory, "objects"), { recursive: true, mode: 0o700 });
|
|
1045
1079
|
await mkdir(join(this.#directory, "manifests"), { recursive: true, mode: 0o700 });
|
|
1046
1080
|
await chmod(this.#directory, 0o700);
|
|
@@ -41,6 +41,8 @@ interface Outstanding {
|
|
|
41
41
|
leaseId: string;
|
|
42
42
|
resolve: (value: unknown) => void;
|
|
43
43
|
reject: (reason: Error) => void;
|
|
44
|
+
signal?: AbortSignal;
|
|
45
|
+
onAbort?: () => void;
|
|
44
46
|
}
|
|
45
47
|
|
|
46
48
|
export interface ReverseLeaseOptions {
|
|
@@ -188,7 +190,7 @@ export class ReverseLeaseRuntime {
|
|
|
188
190
|
this.#cancelForConnection(connectionId, "provider_disconnected");
|
|
189
191
|
}
|
|
190
192
|
|
|
191
|
-
request(capability: string, method: string, payload: unknown): Promise<unknown> {
|
|
193
|
+
request(capability: string, method: string, payload: unknown, signal?: AbortSignal): Promise<unknown> {
|
|
192
194
|
if (this.#disposing) throw new Error("reverse runtime is disposing");
|
|
193
195
|
this.#assertPayload(payload);
|
|
194
196
|
const lease = this.#liveLease(capability);
|
|
@@ -198,16 +200,42 @@ export class ReverseLeaseRuntime {
|
|
|
198
200
|
throw new ReverseLeaseError("lease_unavailable");
|
|
199
201
|
throw new ReverseLeaseError("provider_required");
|
|
200
202
|
}
|
|
203
|
+
if (signal?.aborted)
|
|
204
|
+
return Promise.reject(Object.assign(new Error("request_cancelled"), { name: "request_cancelled" }));
|
|
201
205
|
if (this.#outstanding.size >= MAX_REVERSE_OUTSTANDING) throw new ReverseLeaseError("too_many_outstanding");
|
|
202
206
|
const id = randomUUID();
|
|
203
207
|
return new Promise((resolve, reject) => {
|
|
204
|
-
|
|
208
|
+
const outstanding: Outstanding = {
|
|
205
209
|
connectionId: lease.connectionId,
|
|
206
210
|
capability,
|
|
207
211
|
leaseId: lease.leaseId,
|
|
208
212
|
resolve,
|
|
209
213
|
reject,
|
|
210
|
-
|
|
214
|
+
...(signal ? { signal } : {}),
|
|
215
|
+
};
|
|
216
|
+
this.#outstanding.set(id, outstanding);
|
|
217
|
+
if (signal) {
|
|
218
|
+
outstanding.onAbort = () => {
|
|
219
|
+
if (this.#takeOutstanding(id) !== outstanding) return;
|
|
220
|
+
try {
|
|
221
|
+
const cancellation = this.#sendFrame(lease.connectionId, {
|
|
222
|
+
type: "reverse_cancel",
|
|
223
|
+
id,
|
|
224
|
+
connectionId: lease.connectionId,
|
|
225
|
+
leaseId: lease.leaseId,
|
|
226
|
+
});
|
|
227
|
+
void Promise.resolve(cancellation).catch(() => {});
|
|
228
|
+
} catch {
|
|
229
|
+
// Cancellation is best effort; the caller is already settled locally.
|
|
230
|
+
}
|
|
231
|
+
reject(Object.assign(new Error("request_cancelled"), { name: "request_cancelled" }));
|
|
232
|
+
};
|
|
233
|
+
signal.addEventListener("abort", outstanding.onAbort, { once: true });
|
|
234
|
+
if (signal.aborted) {
|
|
235
|
+
outstanding.onAbort();
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
211
239
|
let delivery: void | Promise<void>;
|
|
212
240
|
try {
|
|
213
241
|
delivery = this.#sendFrame(lease.connectionId, {
|
|
@@ -219,12 +247,12 @@ export class ReverseLeaseRuntime {
|
|
|
219
247
|
payload: { method, payload },
|
|
220
248
|
});
|
|
221
249
|
} catch (error) {
|
|
222
|
-
this.#
|
|
250
|
+
this.#takeOutstanding(id);
|
|
223
251
|
reject(error instanceof Error ? error : new Error(String(error)));
|
|
224
252
|
return;
|
|
225
253
|
}
|
|
226
254
|
Promise.resolve(delivery).catch(error => {
|
|
227
|
-
this.#
|
|
255
|
+
if (this.#takeOutstanding(id) !== outstanding) return;
|
|
228
256
|
reject(error instanceof Error ? error : new Error(String(error)));
|
|
229
257
|
});
|
|
230
258
|
});
|
|
@@ -242,7 +270,7 @@ export class ReverseLeaseRuntime {
|
|
|
242
270
|
if (!request) throw new ReverseLeaseError("unknown_request");
|
|
243
271
|
if (request.connectionId !== connectionId || request.leaseId !== leaseId)
|
|
244
272
|
throw new ReverseLeaseError("not_lease_owner");
|
|
245
|
-
this.#
|
|
273
|
+
this.#takeOutstanding(id);
|
|
246
274
|
if (error) {
|
|
247
275
|
const rejection = new Error(error.message);
|
|
248
276
|
rejection.name = error.code;
|
|
@@ -270,6 +298,8 @@ export class ReverseLeaseRuntime {
|
|
|
270
298
|
this.#installedCapabilities.clear();
|
|
271
299
|
this.#leases.clear();
|
|
272
300
|
this.#idempotency.clear();
|
|
301
|
+
for (const request of outstanding.map(([, request]) => request))
|
|
302
|
+
if (request.signal && request.onAbort) request.signal.removeEventListener("abort", request.onAbort);
|
|
273
303
|
for (const [id, request] of outstanding) {
|
|
274
304
|
request.reject(new Error("request_cancelled"));
|
|
275
305
|
try {
|
|
@@ -304,11 +334,18 @@ export class ReverseLeaseRuntime {
|
|
|
304
334
|
#cancelForConnection(connectionId: string, reason: "provider_disconnected" | "lease_released"): void {
|
|
305
335
|
for (const [id, request] of this.#outstanding)
|
|
306
336
|
if (request.connectionId === connectionId) {
|
|
307
|
-
this.#
|
|
337
|
+
this.#takeOutstanding(id);
|
|
308
338
|
request.reject(new Error("request_cancelled"));
|
|
309
339
|
this.#onCancel?.(id, reason);
|
|
310
340
|
}
|
|
311
341
|
}
|
|
342
|
+
#takeOutstanding(id: string): Outstanding | undefined {
|
|
343
|
+
const request = this.#outstanding.get(id);
|
|
344
|
+
if (!request) return undefined;
|
|
345
|
+
this.#outstanding.delete(id);
|
|
346
|
+
if (request.signal && request.onAbort) request.signal.removeEventListener("abort", request.onAbort);
|
|
347
|
+
return request;
|
|
348
|
+
}
|
|
312
349
|
#installDefinitionsFor(capability: string, definitions: unknown): void {
|
|
313
350
|
this.#installDefinitions?.(capability, definitions);
|
|
314
351
|
this.#installedCapabilities.add(capability);
|
package/src/sdk/mcp/server.ts
CHANGED
|
@@ -301,9 +301,16 @@ export function createSdkMcpServer(options: SdkMcpServerOptions = {}) {
|
|
|
301
301
|
export async function runSdkMcpStdio(options: SdkMcpServerOptions = {}): Promise<void> {
|
|
302
302
|
const server = createSdkMcpServer(options);
|
|
303
303
|
let buffer = "";
|
|
304
|
+
const inflight = new Set<Promise<void>>();
|
|
304
305
|
process.stdin.setEncoding("utf8");
|
|
305
|
-
|
|
306
|
-
|
|
306
|
+
|
|
307
|
+
const track = (work: Promise<void>): void => {
|
|
308
|
+
inflight.add(work);
|
|
309
|
+
void work.finally(() => inflight.delete(work));
|
|
310
|
+
};
|
|
311
|
+
|
|
312
|
+
await new Promise<void>((resolve, reject) => {
|
|
313
|
+
const onData = (chunk: string) => {
|
|
307
314
|
buffer += chunk;
|
|
308
315
|
let index = buffer.indexOf("\n");
|
|
309
316
|
while (index >= 0) {
|
|
@@ -311,22 +318,43 @@ export async function runSdkMcpStdio(options: SdkMcpServerOptions = {}): Promise
|
|
|
311
318
|
buffer = buffer.slice(index + 1);
|
|
312
319
|
index = buffer.indexOf("\n");
|
|
313
320
|
if (!line) continue;
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
321
|
+
track(
|
|
322
|
+
(async () => {
|
|
323
|
+
let request: JsonRpcRequest;
|
|
324
|
+
try {
|
|
325
|
+
request = JSON.parse(line) as JsonRpcRequest;
|
|
326
|
+
} catch {
|
|
327
|
+
process.stdout.write(
|
|
328
|
+
`${JSON.stringify({ jsonrpc: "2.0", id: null, error: { code: -32700, message: "parse_error" } })}\n`,
|
|
329
|
+
);
|
|
330
|
+
return;
|
|
331
|
+
}
|
|
332
|
+
const response = await server.handleJsonRpc(request);
|
|
333
|
+
// JSON-RPC notifications (no id) receive no response.
|
|
334
|
+
if (request.id !== undefined) process.stdout.write(`${JSON.stringify(response)}\n`);
|
|
335
|
+
})(),
|
|
336
|
+
);
|
|
328
337
|
}
|
|
329
|
-
}
|
|
330
|
-
|
|
338
|
+
};
|
|
339
|
+
const onEnd = () => {
|
|
340
|
+
process.stdin.off("data", onData);
|
|
341
|
+
process.stdin.off("end", onEnd);
|
|
342
|
+
process.stdin.off("error", onError);
|
|
343
|
+
resolve();
|
|
344
|
+
};
|
|
345
|
+
const onError = (error: Error) => {
|
|
346
|
+
process.stdin.off("data", onData);
|
|
347
|
+
process.stdin.off("end", onEnd);
|
|
348
|
+
process.stdin.off("error", onError);
|
|
349
|
+
reject(error);
|
|
350
|
+
};
|
|
351
|
+
process.stdin.on("data", onData);
|
|
352
|
+
process.stdin.on("end", onEnd);
|
|
353
|
+
process.stdin.on("error", onError);
|
|
331
354
|
});
|
|
355
|
+
|
|
356
|
+
// Stdin EOF must not drop in-flight tools/call handlers (WS connect/query).
|
|
357
|
+
// Awaiting them also prevents the process from exiting before responses flush,
|
|
358
|
+
// and lets clients close so the event loop can drain deterministically.
|
|
359
|
+
await Promise.allSettled([...inflight]);
|
|
332
360
|
}
|