@gajae-code/coding-agent 0.14.2 → 0.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +120 -1
- package/dist/types/async/job-manager.d.ts +4 -1
- package/dist/types/autoresearch/dashboard.d.ts +29 -4
- package/dist/types/autoresearch/data-context.d.ts +25 -0
- package/dist/types/autoresearch/git.d.ts +65 -7
- package/dist/types/autoresearch/harness.d.ts +58 -0
- package/dist/types/autoresearch/prompts.d.ts +51 -0
- package/dist/types/autoresearch/runs.d.ts +173 -0
- package/dist/types/autoresearch/types.d.ts +9 -139
- package/dist/types/capability/types.d.ts +20 -0
- package/dist/types/cli/accounts-cli.d.ts +1 -0
- package/dist/types/commands/{rlm.d.ts → autoresearch.d.ts} +2 -4
- package/dist/types/config/autorouting-contract.d.ts +146 -0
- package/dist/types/config/autorouting-generator.d.ts +40 -0
- package/dist/types/config/autorouting-tier-map.d.ts +16844 -0
- package/dist/types/config/autorouting.d.ts +35 -0
- package/dist/types/config/model-discovery-manager.d.ts +8 -1
- package/dist/types/config/model-registry.d.ts +30 -1
- package/dist/types/config/provider-selection-policy.d.ts +23 -0
- package/dist/types/config/settings-schema.d.ts +61 -3
- package/dist/types/config/settings.d.ts +3 -0
- package/dist/types/coordinator/contract.d.ts +1 -1
- package/dist/types/coordinator-mcp/durability.d.ts +29 -0
- package/dist/types/coordinator-mcp/event-webhook.d.ts +50 -0
- package/dist/types/coordinator-mcp/question-state.d.ts +1 -1
- package/dist/types/coordinator-mcp/server.d.ts +14 -0
- package/dist/types/crash/index-store.d.ts +3 -0
- package/dist/types/crash/record-loader.d.ts +2 -0
- package/dist/types/crash/upstream/relay.d.ts +5 -3
- package/dist/types/cursor.d.ts +12 -0
- package/dist/types/defaults/gjc-defaults.d.ts +35 -1
- package/dist/types/defaults/gjc-skills.generated.d.ts +1 -1
- package/dist/types/discovery/helpers.d.ts +1 -1
- package/dist/types/edit/index.d.ts +2 -1
- package/dist/types/eval/py/executor.d.ts +2 -0
- package/dist/types/extensibility/extensions/loader.d.ts +32 -0
- package/dist/types/extensibility/gjc-plugins/types.d.ts +1 -1
- package/dist/types/extensibility/skills.d.ts +2 -0
- package/dist/types/gjc-runtime/autoresearch-runtime.d.ts +194 -0
- package/dist/types/gjc-runtime/gc-runtime.d.ts +1 -1
- package/dist/types/gjc-runtime/psmux-detect.d.ts +1 -1
- package/dist/types/gjc-runtime/python-transcript.d.ts +19 -0
- package/dist/types/gjc-runtime/ralplan-runtime.d.ts +2 -8
- package/dist/types/gjc-runtime/session-layout.d.ts +6 -3
- package/dist/types/gjc-runtime/state-renderer.d.ts +0 -22
- package/dist/types/gjc-runtime/state-schema.d.ts +7 -7
- package/dist/types/gjc-runtime/tmux-common.d.ts +2 -2
- package/dist/types/gjc-runtime/ultragoal-change-set.d.ts +38 -1
- package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +2 -24
- package/dist/types/gjc-runtime/ultragoal-validation-policy.d.ts +73 -0
- package/dist/types/gjc-runtime/workflow-recovery-projection.d.ts +100 -0
- package/dist/types/hooks/skill-keywords.d.ts +1 -1
- package/dist/types/main.d.ts +0 -10
- package/dist/types/modes/acp/acp-agent.d.ts +13 -6
- package/dist/types/modes/components/model-selector.d.ts +14 -0
- package/dist/types/modes/components/skill-hud/render.d.ts +6 -1
- package/dist/types/modes/components/smart-routing-panel.d.ts +82 -0
- package/dist/types/modes/controllers/selector-controller.d.ts +14 -0
- package/dist/types/modes/interactive-mode.d.ts +3 -0
- package/dist/types/modes/types.d.ts +9 -0
- package/dist/types/rlm/artifacts.d.ts +4 -4
- package/dist/types/runtime/optional-runtime-services.d.ts +2 -1
- package/dist/types/runtime/workspace-tree-service.d.ts +5 -1
- package/dist/types/runtime-mcp/config.d.ts +6 -0
- package/dist/types/runtime-mcp/manager.d.ts +10 -0
- package/dist/types/sdk/acp/adapter.d.ts +0 -4
- package/dist/types/sdk/broker/broker.d.ts +1 -1
- package/dist/types/sdk/broker/lifecycle.d.ts +57 -2
- package/dist/types/sdk/broker/session-index.d.ts +15 -0
- package/dist/types/sdk/bus/chat-daemon-control.d.ts +6 -0
- package/dist/types/sdk/bus/chat-daemon-runtime.d.ts +4 -0
- package/dist/types/sdk/bus/index.d.ts +8 -0
- package/dist/types/sdk/bus/kind-aware-reconciliation.d.ts +2 -0
- package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +12 -2
- package/dist/types/sdk/client/client.d.ts +39 -4
- package/dist/types/sdk/host/host.d.ts +1 -0
- package/dist/types/sdk/host/internal-autorouting-state.d.ts +3 -0
- package/dist/types/sdk/host/model-pin.d.ts +29 -0
- package/dist/types/sdk/host/session-runtime.d.ts +2 -1
- package/dist/types/sdk/lifecycle-session.d.ts +13 -0
- package/dist/types/sdk/router/session-router.d.ts +27 -1
- package/dist/types/sdk/session.d.ts +11 -1
- package/dist/types/session/agent-session.d.ts +36 -6
- package/dist/types/session/artifacts.d.ts +16 -1
- package/dist/types/session/internal/managed-session-storage.d.ts +7 -0
- package/dist/types/session/session-manager.d.ts +84 -2
- package/dist/types/session/session-staging-paths.d.ts +3 -0
- package/dist/types/skill-state/canonical-skills.d.ts +1 -1
- package/dist/types/skill-state/workflow-hud.d.ts +12 -21
- package/dist/types/skill-state/workflow-mutation-guard.d.ts +1 -0
- package/dist/types/slash-commands/helpers/autorouting-status.d.ts +8 -0
- package/dist/types/system-prompt.d.ts +23 -0
- package/dist/types/task/executor.d.ts +35 -4
- package/dist/types/task/index.d.ts +17 -3
- package/dist/types/task/receipt.d.ts +3 -1
- package/dist/types/task/types.d.ts +144 -0
- package/dist/types/tools/atomic-file-write.d.ts +72 -0
- package/dist/types/tools/descriptors.d.ts +1 -0
- package/dist/types/tools/index.d.ts +36 -1
- package/dist/types/tools/move-session.d.ts +40 -0
- package/dist/types/tools/python.d.ts +12 -0
- package/dist/types/tools/resource-gc.d.ts +0 -3
- package/dist/types/tools/search.d.ts +8 -1
- package/dist/types/tools/skill.d.ts +9 -4
- package/dist/types/tools/subagent.d.ts +33 -3
- package/dist/types/utils/xml-escape.d.ts +2 -0
- package/dist/types/workflow/workflow-intent-diff.d.ts +2 -2
- package/dist/types/workflow/workflow-intent-report-consumer.d.ts +1 -1
- package/package.json +11 -12
- package/scripts/build-sdk-package-smoke.ts +7 -5
- package/scripts/check-autorouting-tier-map.ts +183 -0
- package/scripts/generate-sdk-operation-inventory.ts +12 -0
- package/scripts/generate-tool-catalog.ts +40 -5
- package/src/async/job-manager.ts +5 -2
- package/src/autoresearch/dashboard.ts +134 -314
- package/src/autoresearch/data-context.ts +35 -0
- package/src/autoresearch/git.ts +186 -26
- package/src/autoresearch/{helpers.ts → harness.ts} +65 -40
- package/src/autoresearch/{prompt-setup.md → prompts/prompt-setup.md} +7 -7
- package/src/autoresearch/prompts/prompt.md +103 -0
- package/src/autoresearch/prompts.ts +126 -0
- package/src/autoresearch/runs.ts +708 -0
- package/src/autoresearch/types.ts +9 -150
- package/src/capability/index.ts +4 -4
- package/src/capability/types.ts +20 -0
- package/src/cli/accounts-cli.ts +21 -5
- package/src/cli/config-cli.ts +25 -1
- package/src/cli/fast-help.ts +1 -2
- package/src/cli/list-models.ts +5 -8
- package/src/cli/setup-cli.ts +8 -0
- package/src/cli.ts +2 -21
- package/src/commands/autoresearch.ts +33 -0
- package/src/commands/coordinator.ts +15 -0
- package/src/commands/launch.ts +2 -4
- package/src/commands/quick-lane.ts +1 -1
- package/src/commands/sdk.ts +24 -0
- package/src/commands/state.ts +1 -1
- package/src/config/autorouting-contract.ts +474 -0
- package/src/config/autorouting-generator.ts +238 -0
- package/src/config/autorouting-tier-map.ts +6108 -0
- package/src/config/autorouting.ts +120 -0
- package/src/config/model-discovery-manager.ts +42 -11
- package/src/config/model-registry.ts +348 -68
- package/src/config/provider-selection-policy.ts +60 -9
- package/src/config/settings-schema.ts +195 -6
- package/src/config/settings.ts +62 -1
- package/src/config.ts +2 -3
- package/src/coordinator/contract.ts +0 -1
- package/src/coordinator-mcp/codex-handoff.ts +68 -42
- package/src/coordinator-mcp/durability.ts +200 -0
- package/src/coordinator-mcp/event-webhook.ts +278 -0
- package/src/coordinator-mcp/question-state.ts +109 -46
- package/src/coordinator-mcp/server.ts +932 -504
- package/src/crash/index-store.ts +126 -22
- package/src/crash/record-loader.ts +7 -0
- package/src/crash/upstream/relay.ts +82 -34
- package/src/cursor.ts +84 -1
- package/src/debug/index.ts +26 -0
- package/src/defaults/gjc/skills/autoresearch/SKILL.md +116 -0
- package/src/defaults/gjc/skills/autoresearch/auto-critic.md +46 -0
- package/src/defaults/gjc/skills/autoresearch/auto-iterate.md +42 -0
- package/src/defaults/gjc/skills/deep-interview/SKILL.md +17 -37
- package/src/defaults/gjc/skills/ralplan/SKILL.md +21 -18
- package/src/defaults/gjc/skills/ultragoal/SKILL.md +6 -17
- package/src/defaults/gjc/skills/ultragoal/ai-slop-cleaner.md +2 -2
- package/src/defaults/gjc-defaults.ts +89 -2
- package/src/defaults/gjc-skills.generated.ts +24 -17
- package/src/discovery/builtin.ts +18 -5
- package/src/discovery/helpers.ts +6 -6
- package/src/edit/index.ts +5 -2
- package/src/eval/py/executor.ts +8 -0
- package/src/extensibility/extensions/loader.ts +135 -13
- package/src/extensibility/extensions/wrapper.ts +1 -2
- package/src/extensibility/gjc-plugins/schema.ts +1 -1
- package/src/extensibility/runtime-skill-discovery.ts +2 -2
- package/src/extensibility/skill-management.ts +2 -3
- package/src/extensibility/skills.ts +16 -8
- package/src/gjc-runtime/autoresearch-runtime.ts +1509 -0
- package/src/gjc-runtime/gc-render.ts +0 -1
- package/src/gjc-runtime/gc-runtime.ts +1 -11
- package/src/gjc-runtime/launch-tmux.ts +3 -3
- package/src/gjc-runtime/psmux-detect.ts +2 -2
- package/src/gjc-runtime/python-transcript.ts +58 -0
- package/src/gjc-runtime/ralplan-runtime.ts +6 -18
- package/src/gjc-runtime/session-layout.ts +18 -7
- package/src/gjc-runtime/session-state-sidecar.ts +58 -9
- package/src/gjc-runtime/state-renderer.ts +0 -38
- package/src/gjc-runtime/state-runtime.ts +43 -22
- package/src/gjc-runtime/state-schema.ts +1 -1
- package/src/gjc-runtime/tmux-common.ts +7 -7
- package/src/gjc-runtime/ultragoal-change-set.ts +232 -11
- package/src/gjc-runtime/ultragoal-runtime.ts +309 -114
- package/src/gjc-runtime/ultragoal-validation-policy.ts +247 -0
- package/src/gjc-runtime/workflow-command-ref.ts +15 -11
- package/src/gjc-runtime/workflow-manifest.generated.json +413 -532
- package/src/gjc-runtime/workflow-manifest.ts +43 -83
- package/src/gjc-runtime/workflow-recovery-projection.ts +616 -0
- package/src/hooks/skill-keywords.ts +3 -3
- package/src/hooks/skill-state.ts +2 -2
- package/src/internal-urls/docs-index.generated.ts +26 -24
- package/src/lsp/index.ts +25 -5
- package/src/main.ts +2 -27
- package/src/modes/acp/acp-agent.ts +200 -75
- package/src/modes/components/model-selector.ts +216 -13
- package/src/modes/components/oauth-selector.ts +5 -5
- package/src/modes/components/skill-hud/render.ts +173 -35
- package/src/modes/components/smart-routing-panel.ts +531 -0
- package/src/modes/components/tool-status-header.ts +6 -3
- package/src/modes/controllers/command-controller.ts +17 -7
- package/src/modes/controllers/input-controller.ts +44 -9
- package/src/modes/controllers/runtime-mcp-command-controller.ts +4 -0
- package/src/modes/controllers/selector-controller.ts +298 -65
- package/src/modes/interactive-mode.ts +8 -1
- package/src/modes/shared/agent-wire/workflow-gate-types.ts +1 -1
- package/src/modes/types.ts +9 -1
- package/src/prompts/agents/planner.md +1 -1
- package/src/prompts/system/system-prompt.md +1 -1
- package/src/prompts/system/volatile-project-context.md +2 -1
- package/src/prompts/tools/move-session.md +15 -0
- package/src/prompts/tools/python.md +12 -0
- package/src/prompts/tools/skill-discovery.md +1 -1
- package/src/prompts/tools/skill.md +2 -2
- package/src/prompts/tools/task-summary.md +1 -0
- package/src/prompts/tools/task.md +6 -0
- package/src/rlm/artifacts.ts +12 -10
- package/src/runtime/optional-runtime-services.ts +3 -2
- package/src/runtime/workspace-tree-service.ts +11 -3
- package/src/runtime-mcp/config.ts +8 -1
- package/src/runtime-mcp/manager.ts +33 -0
- package/src/sdk/acp/adapter.ts +48 -1
- package/src/sdk/broker/broker.ts +30 -2
- package/src/sdk/broker/discovery.ts +217 -2
- package/src/sdk/broker/lifecycle.ts +385 -59
- package/src/sdk/broker/session-index.ts +243 -48
- package/src/sdk/broker/transport.ts +1 -0
- package/src/sdk/bus/chat-daemon-control.ts +8 -2
- package/src/sdk/bus/chat-daemon-runtime.ts +5 -0
- package/src/sdk/bus/index.ts +208 -11
- package/src/sdk/bus/kind-aware-reconciliation.ts +16 -0
- package/src/sdk/bus/reconciliation-store.ts +27 -5
- package/src/sdk/bus/telegram-daemon-contract.ts +12 -2
- package/src/sdk/client/client.ts +204 -22
- package/src/sdk/host/host.ts +16 -0
- package/src/sdk/host/internal-autorouting-state.ts +13 -0
- package/src/sdk/host/model-pin.ts +101 -0
- package/src/sdk/host/reverse-leases.ts +2 -1
- package/src/sdk/host/session-runtime.test.ts +224 -1
- package/src/sdk/host/session-runtime.ts +125 -37
- package/src/sdk/lifecycle/service.ts +3 -1
- package/src/sdk/lifecycle-session.ts +42 -1
- package/src/sdk/protocol/operation-inventory.generated.json +66 -0
- package/src/sdk/router/session-router.ts +375 -36
- package/src/sdk/session.ts +547 -47
- package/src/session/account-inventory.ts +1 -1
- package/src/session/agent-session.ts +765 -189
- package/src/session/artifacts.ts +234 -4
- package/src/session/internal/managed-session-storage.ts +91 -4
- package/src/session/provider-safety-stop-hint.ts +3 -2
- package/src/session/session-manager.ts +775 -16
- package/src/session/session-staging-paths.ts +8 -0
- package/src/setup/hermes/templates/operator-instructions.v1.md +0 -1
- package/src/setup/host-plugin-setup.ts +2 -2
- package/src/skill-state/active-state.ts +2 -2
- package/src/skill-state/canonical-skills.ts +1 -1
- package/src/skill-state/initial-phase.ts +1 -1
- package/src/skill-state/workflow-hud.ts +41 -41
- package/src/skill-state/workflow-mutation-guard.ts +26 -7
- package/src/slash-commands/builtin-registry.ts +77 -0
- package/src/slash-commands/helpers/autorouting-status.ts +55 -0
- package/src/system-prompt.ts +102 -5
- package/src/task/discovery.ts +2 -3
- package/src/task/executor.ts +617 -43
- package/src/task/index.ts +262 -15
- package/src/task/receipt.ts +30 -0
- package/src/task/render.ts +11 -1
- package/src/task/types.ts +316 -0
- package/src/tools/atomic-file-write.ts +594 -0
- package/src/tools/bisect.ts +3 -2
- package/src/tools/descriptors.test.ts +3 -0
- package/src/tools/descriptors.ts +24 -0
- package/src/tools/image-gen.ts +5 -1
- package/src/tools/index.ts +62 -5
- package/src/tools/move-session.ts +107 -0
- package/src/tools/python.ts +205 -0
- package/src/tools/read.ts +88 -15
- package/src/tools/renderers.ts +2 -0
- package/src/tools/resource-gc.ts +8 -177
- package/src/tools/search.ts +30 -6
- package/src/tools/skill-discovery.ts +1 -0
- package/src/tools/skill.ts +37 -7
- package/src/tools/subagent-render.ts +119 -13
- package/src/tools/subagent.ts +91 -74
- package/src/tools/tool-catalog.generated.ts +64 -2
- package/src/tools/write.ts +16 -12
- package/src/utils/xml-escape.ts +14 -0
- package/src/web/search/providers/codex.ts +2 -2
- package/src/workflow/workflow-intent-diff.ts +13 -12
- package/src/workflow/workflow-intent-report-consumer.ts +6 -6
- package/dist/types/autoresearch/helpers.d.ts +0 -24
- package/dist/types/autoresearch/index.d.ts +0 -2
- package/dist/types/autoresearch/state.d.ts +0 -17
- package/dist/types/autoresearch/storage.d.ts +0 -142
- package/dist/types/autoresearch/tools/init-experiment.d.ts +0 -31
- package/dist/types/autoresearch/tools/log-experiment.d.ts +0 -23
- package/dist/types/autoresearch/tools/run-experiment.d.ts +0 -8
- package/dist/types/autoresearch/tools/update-notes.d.ts +0 -12
- package/dist/types/commands/team.d.ts +0 -24
- package/dist/types/gjc-runtime/team-gc.d.ts +0 -7
- package/dist/types/gjc-runtime/team-launch.d.ts +0 -61
- package/dist/types/gjc-runtime/team-notify.d.ts +0 -45
- package/dist/types/gjc-runtime/team-runtime.d.ts +0 -336
- package/dist/types/gjc-runtime/team-store.d.ts +0 -168
- package/dist/types/gjc-runtime/team-worker-heartbeat.d.ts +0 -43
- package/dist/types/gjc-runtime/team-worker-memory-guard.d.ts +0 -92
- package/dist/types/gjc-runtime/team-workers.d.ts +0 -83
- package/dist/types/rlm/index.d.ts +0 -47
- package/dist/types/rlm/preset.d.ts +0 -23
- package/dist/types/rlm/python-tool.d.ts +0 -16
- package/src/autoresearch/command-resume.md +0 -14
- package/src/autoresearch/index.ts +0 -536
- package/src/autoresearch/prompt.md +0 -103
- package/src/autoresearch/resume-message.md +0 -10
- package/src/autoresearch/state.ts +0 -273
- package/src/autoresearch/storage.ts +0 -699
- package/src/autoresearch/tools/init-experiment.ts +0 -272
- package/src/autoresearch/tools/log-experiment.ts +0 -524
- package/src/autoresearch/tools/run-experiment.ts +0 -475
- package/src/autoresearch/tools/update-notes.ts +0 -109
- package/src/commands/rlm.ts +0 -19
- package/src/commands/team.ts +0 -238
- package/src/defaults/gjc/skills/deep-interview/auto-research-greenfield.md +0 -42
- package/src/defaults/gjc/skills/team/SKILL.md +0 -467
- package/src/gjc-runtime/team-gc.ts +0 -49
- package/src/gjc-runtime/team-launch.ts +0 -404
- package/src/gjc-runtime/team-notify.ts +0 -326
- package/src/gjc-runtime/team-runtime.ts +0 -5923
- package/src/gjc-runtime/team-store.ts +0 -878
- package/src/gjc-runtime/team-worker-heartbeat.ts +0 -149
- package/src/gjc-runtime/team-worker-memory-guard.ts +0 -363
- package/src/gjc-runtime/team-workers.ts +0 -1123
- package/src/prompts/system/rlm-report-command.md +0 -1
- package/src/prompts/system/rlm-research.md +0 -23
- package/src/rlm/index.ts +0 -365
- package/src/rlm/preset.ts +0 -76
- package/src/rlm/python-tool.ts +0 -68
package/src/sdk/bus/index.ts
CHANGED
|
@@ -48,6 +48,7 @@ function sdkBusNatives(): NativeSdkBusBindings {
|
|
|
48
48
|
type NotificationServer = NativeNotificationServer;
|
|
49
49
|
|
|
50
50
|
import { $credentialEnv, logger, postmortem, VERSION } from "@gajae-code/utils";
|
|
51
|
+
|
|
51
52
|
import { AsyncJobManager } from "../../async";
|
|
52
53
|
import { Settings, validateSettingPatch } from "../../config/settings";
|
|
53
54
|
import type { ExtensionAPI, ExtensionCommandContext, ExtensionContext } from "../../extensibility/extensions";
|
|
@@ -100,6 +101,7 @@ import {
|
|
|
100
101
|
} from "../host";
|
|
101
102
|
import { type AbortScope, type ControlSurface, dispatchControl, TypedControlError } from "../host/control";
|
|
102
103
|
import { BROKER_RUNTIME_CLOSE_CAPABILITY_FIELD } from "../host/control/runtime-gate";
|
|
104
|
+
import { isAutoroutingInactive, markAutoroutingInactive } from "../host/internal-autorouting-state";
|
|
103
105
|
import { CursorRegistry, QueryHandlers, RevisionStore, type SessionSurface } from "../host/query";
|
|
104
106
|
import type { SdkFrame } from "../host/types";
|
|
105
107
|
import {
|
|
@@ -209,6 +211,39 @@ const PROMPT_SETTLEMENT_DIAGNOSTIC_MAX_AGE_MS = 86_400_000;
|
|
|
209
211
|
* provider error cannot flood the log file.
|
|
210
212
|
*/
|
|
211
213
|
const PROMPT_TERMINAL_FAILURE_REASON_LOG_MAX = 512;
|
|
214
|
+
/**
|
|
215
|
+
* #4743: bounded wait for durable reconciliation quiescence during session
|
|
216
|
+
* teardown. Expiry is OBSERVABLE (owner-release failure), never silently
|
|
217
|
+
* treated as drained. Read per drain so the env override is effective for
|
|
218
|
+
* deterministic tests.
|
|
219
|
+
*/
|
|
220
|
+
const sdkReconciliationDrainTimeoutMs = (): number => {
|
|
221
|
+
const override = Number(process.env.GJC_SDK_RECONCILIATION_DRAIN_TIMEOUT_MS);
|
|
222
|
+
return override > 0 ? override : 5_000;
|
|
223
|
+
};
|
|
224
|
+
/**
|
|
225
|
+
* #4743: the two reconciliation failure codes that mean durable state may be
|
|
226
|
+
* lost. Both must reach the teardown owner; every other owner-release failure
|
|
227
|
+
* stays retryable through the retained `cleanupRetries` entry.
|
|
228
|
+
*/
|
|
229
|
+
const RECONCILIATION_DURABILITY_FAILURE_CODES: ReadonlySet<string> = new Set([
|
|
230
|
+
"reconciliation_persist_failed",
|
|
231
|
+
"reconciliation_drain_timeout",
|
|
232
|
+
]);
|
|
233
|
+
function reconciliationFailureCode(value: unknown): string | undefined {
|
|
234
|
+
if (typeof value !== "object" || value === null) return undefined;
|
|
235
|
+
const code = (value as { code?: unknown }).code;
|
|
236
|
+
return typeof code === "string" && RECONCILIATION_DURABILITY_FAILURE_CODES.has(code) ? code : undefined;
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* Flatten an owner-release failure to its durability-failure members. Aggregates
|
|
240
|
+
* nest (the store batches a drained window's failures, and owner release batches
|
|
241
|
+
* every release failure), so recursion is required to reach the coded leaves.
|
|
242
|
+
*/
|
|
243
|
+
function reconciliationDurabilityFailures(error: unknown): unknown[] {
|
|
244
|
+
if (error instanceof AggregateError) return error.errors.flatMap(member => reconciliationDurabilityFailures(member));
|
|
245
|
+
return reconciliationFailureCode(error) === undefined ? [] : [error];
|
|
246
|
+
}
|
|
212
247
|
type PromptTerminalDiagnostic = {
|
|
213
248
|
reason?: unknown;
|
|
214
249
|
loopStopReason?: string;
|
|
@@ -1186,6 +1221,11 @@ interface SessionRuntime {
|
|
|
1186
1221
|
/** Aborts and fences side turns while notification delivery is disabled. */
|
|
1187
1222
|
disableEphemeralTurns: () => void;
|
|
1188
1223
|
waitForGateResolutionQuiescence: () => Promise<void>;
|
|
1224
|
+
/** Awaits durable quiescence of every reconciliation transaction this runtime
|
|
1225
|
+
* admitted, joining their producers first. Never swallows evidence: producer
|
|
1226
|
+
* and store rejections are returned in `failures`, and a bounded-deadline
|
|
1227
|
+
* expiry is returned as `timedOut` (#4743). */
|
|
1228
|
+
drainDurableReconciliation: () => Promise<{ timedOut: boolean; failures: unknown[] }>;
|
|
1189
1229
|
trackGateResolution: <T>(resolution: Promise<T>) => Promise<T>;
|
|
1190
1230
|
workflowGate?: WorkflowGateEmitter;
|
|
1191
1231
|
gatePresentations?: PresentationArbiter;
|
|
@@ -2476,6 +2516,10 @@ function sdkControlSurface(
|
|
|
2476
2516
|
discardTerminalAbortSteeringSnapshot?: (token: number) => void;
|
|
2477
2517
|
rebindTerminalAbortSteeringSnapshot?: (token: number) => void;
|
|
2478
2518
|
},
|
|
2519
|
+
// #4743: registers fire-and-forget reconciliation producers (accepted
|
|
2520
|
+
// executions and post-response publications) so session teardown can join
|
|
2521
|
+
// them before reporting durable quiescence.
|
|
2522
|
+
trackReconciliationProducer?: (producer: Promise<unknown>) => void,
|
|
2479
2523
|
): ControlSurface & {
|
|
2480
2524
|
cancelPendingPreflights(): Promise<void>;
|
|
2481
2525
|
cancelPendingPreflightsForConnection(connectionId: string): Promise<void>;
|
|
@@ -2750,6 +2794,10 @@ function sdkControlSurface(
|
|
|
2750
2794
|
}
|
|
2751
2795
|
accepting = false;
|
|
2752
2796
|
accepted = true;
|
|
2797
|
+
// #4743: an accepted run owns a durable terminal publication when it
|
|
2798
|
+
// settles; teardown joins it via this latch (never-accepted preflights
|
|
2799
|
+
// produce no durable write and stay unjoined by design).
|
|
2800
|
+
trackReconciliationProducer?.(submissionSettled.promise);
|
|
2753
2801
|
pendingPreflightCancellations.delete(key);
|
|
2754
2802
|
settlePreflight({ status: "accepted" });
|
|
2755
2803
|
if (preflightController.signal.aborted) throw cancellationError;
|
|
@@ -2775,10 +2823,14 @@ function sdkControlSurface(
|
|
|
2775
2823
|
);
|
|
2776
2824
|
} catch (error) {
|
|
2777
2825
|
submissionSettled.resolve();
|
|
2778
|
-
if (accepted && !preflightController.signal.aborted)
|
|
2826
|
+
if (accepted && !preflightController.signal.aborted)
|
|
2827
|
+
trackReconciliationProducer?.(Promise.resolve(onPromptFailed(correlation, error)));
|
|
2779
2828
|
else settlePreflight({ status: "rejected", error });
|
|
2780
2829
|
}
|
|
2781
2830
|
if (submission) {
|
|
2831
|
+
// #4743: the continuation may settle long after teardown (a cancelled
|
|
2832
|
+
// preflight stays pending forever by design), so the EXECUTION is never
|
|
2833
|
+
// joined — only its reconciliation publications are tracked below.
|
|
2782
2834
|
void submission.then(
|
|
2783
2835
|
() => {
|
|
2784
2836
|
if (!accepted)
|
|
@@ -2791,7 +2843,8 @@ function sdkControlSurface(
|
|
|
2791
2843
|
submissionSettled.resolve();
|
|
2792
2844
|
},
|
|
2793
2845
|
error => {
|
|
2794
|
-
if (accepted && !preflightController.signal.aborted)
|
|
2846
|
+
if (accepted && !preflightController.signal.aborted)
|
|
2847
|
+
trackReconciliationProducer?.(Promise.resolve(onPromptFailed(correlation, error)));
|
|
2795
2848
|
else settlePreflight({ status: "rejected", error });
|
|
2796
2849
|
submissionSettled.resolve();
|
|
2797
2850
|
},
|
|
@@ -3287,6 +3340,10 @@ function sdkControlSurface(
|
|
|
3287
3340
|
if (skillRecon) {
|
|
3288
3341
|
await skillRecon.noteAccepted(correlation, trimmedClientRef, { skillName: meta.name });
|
|
3289
3342
|
durableSkillAccepted = true;
|
|
3343
|
+
// #4743: a durably accepted skill owns a terminal
|
|
3344
|
+
// publication; teardown joins this latch (bounded by the
|
|
3345
|
+
// drain deadline).
|
|
3346
|
+
trackReconciliationProducer?.(executionSettled.promise);
|
|
3290
3347
|
}
|
|
3291
3348
|
} catch (error) {
|
|
3292
3349
|
onPromptAcceptFailed(correlation);
|
|
@@ -3305,6 +3362,11 @@ function sdkControlSurface(
|
|
|
3305
3362
|
settleReject(error);
|
|
3306
3363
|
run = Promise.reject(error);
|
|
3307
3364
|
}
|
|
3365
|
+
// #4743: the skill execution itself may outlive teardown (a cancelled
|
|
3366
|
+
// preflight never settles by design), so only its reconciliation
|
|
3367
|
+
// PUBLICATIONS are tracked — created synchronously the moment the
|
|
3368
|
+
// execution settles, so a publication fired during teardown is always
|
|
3369
|
+
// joined before the drain reports quiescence.
|
|
3308
3370
|
void run.then(
|
|
3309
3371
|
result => {
|
|
3310
3372
|
if (phase === "pending") {
|
|
@@ -3317,12 +3379,14 @@ function sdkControlSurface(
|
|
|
3317
3379
|
...(trimmedClientRef ? { clientRef: trimmedClientRef } : {}),
|
|
3318
3380
|
});
|
|
3319
3381
|
} else if (durableSkillAccepted && skillRecon) {
|
|
3320
|
-
|
|
3321
|
-
|
|
3322
|
-
|
|
3323
|
-
|
|
3324
|
-
|
|
3325
|
-
|
|
3382
|
+
trackReconciliationProducer?.(
|
|
3383
|
+
skillRecon.noteTransition(correlation, {
|
|
3384
|
+
type: "agent_end",
|
|
3385
|
+
...(typeof result === "string"
|
|
3386
|
+
? { content: { version: 1, type: "text", text: result, byteLength: 0, truncated: false } }
|
|
3387
|
+
: {}),
|
|
3388
|
+
}),
|
|
3389
|
+
);
|
|
3326
3390
|
}
|
|
3327
3391
|
executionSettled.resolve();
|
|
3328
3392
|
},
|
|
@@ -3331,10 +3395,12 @@ function sdkControlSurface(
|
|
|
3331
3395
|
releaseAdmission();
|
|
3332
3396
|
settleReject(error);
|
|
3333
3397
|
} else if (phase === "accepted" && promptOwned && !preflightController.signal.aborted) {
|
|
3334
|
-
|
|
3398
|
+
trackReconciliationProducer?.(Promise.resolve(onPromptFailed(correlation, error)));
|
|
3335
3399
|
}
|
|
3336
3400
|
if (durableSkillAccepted && skillRecon && !preflightController.signal.aborted)
|
|
3337
|
-
|
|
3401
|
+
trackReconciliationProducer?.(
|
|
3402
|
+
skillRecon.noteTransition(correlation, { type: "agent_failed", error }),
|
|
3403
|
+
);
|
|
3338
3404
|
executionSettled.resolve();
|
|
3339
3405
|
},
|
|
3340
3406
|
);
|
|
@@ -3953,7 +4019,6 @@ export function createNotificationsExtension(
|
|
|
3953
4019
|
controller?: NotificationSessionController;
|
|
3954
4020
|
/** Whether this host mode can own the root SDK endpoint. Default: true. */
|
|
3955
4021
|
sdkHostModeSupported?: boolean;
|
|
3956
|
-
|
|
3957
4022
|
onSdkRequest?: (kind: "control" | "query", connectionId: string, frame: Record<string, unknown>) => void;
|
|
3958
4023
|
runBtwTurn?: (question: string, signal: AbortSignal) => Promise<{ replyText: string }>;
|
|
3959
4024
|
/** Observes settlement of optional session-branch startup after reconciliation completes. */
|
|
@@ -4343,6 +4408,34 @@ export function createNotificationsExtension(
|
|
|
4343
4408
|
logger.warn(`notifications: stop failed: ${String(e)}`);
|
|
4344
4409
|
}
|
|
4345
4410
|
}
|
|
4411
|
+
// #4743: terminal release must not be reported while a reconciliation
|
|
4412
|
+
// publication this runtime admitted — or the execution that will produce it —
|
|
4413
|
+
// is still in flight. Observe durable quiescence AFTER the endpoint stopped
|
|
4414
|
+
// serving (no new admissions) and BEFORE the terminal release is recorded.
|
|
4415
|
+
// Both failure shapes stay observable as owner-release failures: a drained
|
|
4416
|
+
// window containing a failed write (reconciliation_persist_failed evidence
|
|
4417
|
+
// preserved through the store drain) and a deadline expiry (non-quiescent,
|
|
4418
|
+
// never silently treated as drained).
|
|
4419
|
+
try {
|
|
4420
|
+
const drained = await rt.drainDurableReconciliation();
|
|
4421
|
+
for (const failure of drained.failures) {
|
|
4422
|
+
ownerReleaseFailures.push(failure);
|
|
4423
|
+
logger.warn(`sdk reconciliation publication failed during teardown: ${String(failure)}`);
|
|
4424
|
+
}
|
|
4425
|
+
if (drained.timedOut) {
|
|
4426
|
+
const timeoutError = Object.assign(
|
|
4427
|
+
new Error(
|
|
4428
|
+
`SDK reconciliation drain timed out after ${sdkReconciliationDrainTimeoutMs()}ms; reconciliation state may be non-quiescent.`,
|
|
4429
|
+
),
|
|
4430
|
+
{ code: "reconciliation_drain_timeout" },
|
|
4431
|
+
);
|
|
4432
|
+
ownerReleaseFailures.push(timeoutError);
|
|
4433
|
+
logger.warn("sdk reconciliation drain timed out; proceeding with non-quiescent state");
|
|
4434
|
+
}
|
|
4435
|
+
} catch (e) {
|
|
4436
|
+
ownerReleaseFailures.push(e);
|
|
4437
|
+
logger.warn(`sdk reconciliation drain failed: ${String(e)}`);
|
|
4438
|
+
}
|
|
4346
4439
|
lifecycleStartupCapability?.rollback?.recordStop(rt.host.generation, {
|
|
4347
4440
|
runtimeRemoved: true,
|
|
4348
4441
|
hostStopped: rt.hostStopped && rt.serverStopped,
|
|
@@ -4687,6 +4780,26 @@ export function createNotificationsExtension(
|
|
|
4687
4780
|
? createReconciliationStore({ sessionFile, sessionId: String(reconciliationSessionId) })
|
|
4688
4781
|
: null;
|
|
4689
4782
|
const kindReconciliation = createKindAwareReconciliation({ store: durableStore });
|
|
4783
|
+
// #4743: reconciliation publications and the executions that produce them
|
|
4784
|
+
// are fire-and-forget at their call sites; without joining the PRODUCERS a
|
|
4785
|
+
// drain can observe an empty queue and return just before a still-running
|
|
4786
|
+
// skill enqueues its terminal publication — the teardown race again, by
|
|
4787
|
+
// another route. Every void-ed producer registers here and the teardown
|
|
4788
|
+
// drain joins the set before awaiting store quiescence.
|
|
4789
|
+
const reconciliationProducers = new Set<Promise<unknown>>();
|
|
4790
|
+
const trackReconciliationProducer = (producer: Promise<unknown>): void => {
|
|
4791
|
+
reconciliationProducers.add(producer);
|
|
4792
|
+
// Deliberately a two-arm handler, NOT `finally`: `finally` returns a derived
|
|
4793
|
+
// promise that re-rejects when the producer rejects, and nothing observes
|
|
4794
|
+
// that derived promise — a failed durable write would become an unhandled
|
|
4795
|
+
// rejection and kill the resident process. The two-arm form always resolves,
|
|
4796
|
+
// while the ORIGINAL producer keeps its rejection for the teardown drain to
|
|
4797
|
+
// inspect (#4743).
|
|
4798
|
+
const forget = (): void => {
|
|
4799
|
+
reconciliationProducers.delete(producer);
|
|
4800
|
+
};
|
|
4801
|
+
void producer.then(forget, forget);
|
|
4802
|
+
};
|
|
4690
4803
|
// Restart recovery must commit before any prompt is admitted; otherwise a new
|
|
4691
4804
|
// admission can race the hydrated full-state replacement.
|
|
4692
4805
|
let reconciliationReady: Promise<void> = durableStore ? kindReconciliation.hydrateFromStore() : Promise.resolve();
|
|
@@ -6316,6 +6429,8 @@ export function createNotificationsExtension(
|
|
|
6316
6429
|
settleSteer: kindReconciliation.settleSteer,
|
|
6317
6430
|
},
|
|
6318
6431
|
terminalAbortSeams,
|
|
6432
|
+
// #4743: join fire-and-forget reconciliation producers at teardown.
|
|
6433
|
+
trackReconciliationProducer,
|
|
6319
6434
|
);
|
|
6320
6435
|
cancelPreflightsForConnection = controlSurface.cancelPendingPreflightsForConnection;
|
|
6321
6436
|
const abandonPromptResponse = (connectionId: string, frame: Record<string, unknown>) => {
|
|
@@ -6712,6 +6827,7 @@ export function createNotificationsExtension(
|
|
|
6712
6827
|
return { type: "query_response", ...response };
|
|
6713
6828
|
},
|
|
6714
6829
|
});
|
|
6830
|
+
if (isAutoroutingInactive(api)) markAutoroutingInactive(sdkRuntime.host);
|
|
6715
6831
|
host = sdkRuntime.host;
|
|
6716
6832
|
|
|
6717
6833
|
// Install the runtime before either transport can expose the host. session_start
|
|
@@ -6742,6 +6858,66 @@ export function createNotificationsExtension(
|
|
|
6742
6858
|
enableNotifications: () => {},
|
|
6743
6859
|
disposeGateTerminalController: () => {},
|
|
6744
6860
|
disposeAckRecoveryParticipant: () => {},
|
|
6861
|
+
// #4743: session teardown must observe durable quiescence of every
|
|
6862
|
+
// reconciliation transaction this runtime admitted, INCLUDING the ones
|
|
6863
|
+
// whose producers (accepted skill/prompt executions and their post-
|
|
6864
|
+
// response publications) are still running when teardown starts — an
|
|
6865
|
+
// empty queue proves nothing about work that has not been enqueued yet.
|
|
6866
|
+
// Order: join producers → join kind-aware mutations → join the store
|
|
6867
|
+
// chain (which also surfaces persistence-failure evidence). All under a
|
|
6868
|
+
// finite deadline whose expiry is reported as a NON-QUIESCENT result so
|
|
6869
|
+
// the caller records an owner-release failure instead of silently
|
|
6870
|
+
// treating a hung write as drained.
|
|
6871
|
+
drainDurableReconciliation: async (): Promise<{ timedOut: boolean; failures: unknown[] }> => {
|
|
6872
|
+
// One failed write surfaces twice by design — once as the producer's
|
|
6873
|
+
// rejection and once through the store's failure window — and both carry
|
|
6874
|
+
// the same coded error object, so identity dedupe keeps the reported
|
|
6875
|
+
// evidence at one entry per real failure without dropping distinct ones.
|
|
6876
|
+
const seen = new Set<unknown>();
|
|
6877
|
+
const failures: unknown[] = [];
|
|
6878
|
+
const record = (failure: unknown): void => {
|
|
6879
|
+
if (seen.has(failure)) return;
|
|
6880
|
+
seen.add(failure);
|
|
6881
|
+
failures.push(failure);
|
|
6882
|
+
};
|
|
6883
|
+
const quiescent = (async () => {
|
|
6884
|
+
while (true) {
|
|
6885
|
+
const producers = [...reconciliationProducers];
|
|
6886
|
+
// `allSettled` joins ALL producers even when one rejects, but its
|
|
6887
|
+
// results are INSPECTED: a rejected terminal publication is evidence
|
|
6888
|
+
// the caller must see, not something the join consumes before the
|
|
6889
|
+
// store's own failure window opens (#4743).
|
|
6890
|
+
for (const settled of await Promise.allSettled(producers))
|
|
6891
|
+
if (settled.status === "rejected") record(settled.reason);
|
|
6892
|
+
// Both drains below surface store-recorded persistence failures by
|
|
6893
|
+
// rejecting; collect and keep draining so one failed write cannot
|
|
6894
|
+
// hide a later producer's work from the join.
|
|
6895
|
+
try {
|
|
6896
|
+
await kindReconciliation.drain();
|
|
6897
|
+
} catch (e) {
|
|
6898
|
+
record(e);
|
|
6899
|
+
}
|
|
6900
|
+
// Direct store writes (terminal-scope response-state advance) bypass
|
|
6901
|
+
// the kind-aware chain; join them too.
|
|
6902
|
+
try {
|
|
6903
|
+
await durableStore?.drain?.();
|
|
6904
|
+
} catch (e) {
|
|
6905
|
+
record(e);
|
|
6906
|
+
}
|
|
6907
|
+
await Bun.sleep(0);
|
|
6908
|
+
if ([...reconciliationProducers].every(producer => producers.includes(producer))) {
|
|
6909
|
+
return { timedOut: false, failures };
|
|
6910
|
+
}
|
|
6911
|
+
}
|
|
6912
|
+
})();
|
|
6913
|
+
return await Promise.race([
|
|
6914
|
+
quiescent,
|
|
6915
|
+
// A deadline expiry reports the failures observed SO FAR alongside the
|
|
6916
|
+
// timeout: evidence already collected must not be dropped because the
|
|
6917
|
+
// remaining wait ran out.
|
|
6918
|
+
Bun.sleep(sdkReconciliationDrainTimeoutMs()).then(() => ({ timedOut: true, failures })),
|
|
6919
|
+
]);
|
|
6920
|
+
},
|
|
6745
6921
|
disposeGateEmitterListener: () => {},
|
|
6746
6922
|
trackGateResolution,
|
|
6747
6923
|
waitForGateResolutionQuiescence: async () => {
|
|
@@ -8889,6 +9065,11 @@ export function createNotificationsExtension(
|
|
|
8889
9065
|
return false;
|
|
8890
9066
|
});
|
|
8891
9067
|
if (startupWasPending) void settledControllerStop;
|
|
9068
|
+
// #4743: a durability failure has no later retry cycle at terminal quit, so it
|
|
9069
|
+
// must reach the teardown owner instead of being logged into silence. Held
|
|
9070
|
+
// until the controller queue below is joined so the failure never truncates
|
|
9071
|
+
// the rest of teardown.
|
|
9072
|
+
let reconciliationTeardownFailure: Error | undefined;
|
|
8892
9073
|
try {
|
|
8893
9074
|
await stopSession(id);
|
|
8894
9075
|
} catch (error) {
|
|
@@ -8898,11 +9079,27 @@ export function createNotificationsExtension(
|
|
|
8898
9079
|
// shutdown. On terminal quit there is no later retry cycle, so log at
|
|
8899
9080
|
// error severity (matching the postmortem cleanup precedent).
|
|
8900
9081
|
logger.error(`notifications: SDK notification runtime cleanup failed: ${String(error)}`);
|
|
9082
|
+
// Endpoint-release failures (host/server stop) stay swallowed: they are
|
|
9083
|
+
// retryable through the retained cleanupRetries entry and observable in
|
|
9084
|
+
// lifecycle rollback proof. Reconciliation durability failures are not —
|
|
9085
|
+
// a committed publication that never reached disk, or a drain whose
|
|
9086
|
+
// deadline expired, is state loss the owner must be told about.
|
|
9087
|
+
const durable = reconciliationDurabilityFailures(error);
|
|
9088
|
+
if (durable.length > 0)
|
|
9089
|
+
reconciliationTeardownFailure = Object.assign(
|
|
9090
|
+
new Error(
|
|
9091
|
+
`SDK reconciliation teardown failed for session ${id}: ${durable
|
|
9092
|
+
.map(failure => reconciliationFailureCode(failure) ?? "unknown")
|
|
9093
|
+
.join(", ")}.`,
|
|
9094
|
+
),
|
|
9095
|
+
{ code: "sdk_reconciliation_teardown_failed", failures: durable },
|
|
9096
|
+
);
|
|
8901
9097
|
}
|
|
8902
9098
|
// Keep shutdown nonblocking only while native startup is genuinely
|
|
8903
9099
|
// pending (the `/notify on` path); otherwise await the controller queue so
|
|
8904
9100
|
// completed-start reconciliation and its replacement-token cleanup are
|
|
8905
9101
|
// joined before lifecycle shutdown returns.
|
|
8906
9102
|
if (!startupWasPending) await settledControllerStop;
|
|
9103
|
+
if (reconciliationTeardownFailure) throw reconciliationTeardownFailure;
|
|
8907
9104
|
});
|
|
8908
9105
|
}
|
|
@@ -89,6 +89,8 @@ export interface KindAwareReconciliation {
|
|
|
89
89
|
activeCount(kind: ReconciliationKind): number;
|
|
90
90
|
/** Hydrate from durable store (call once at session host start). */
|
|
91
91
|
hydrateFromStore(): Promise<void>;
|
|
92
|
+
/** Await quiescence of every admitted mutation and the durable store (#4743). */
|
|
93
|
+
drain(): Promise<void>;
|
|
92
94
|
}
|
|
93
95
|
|
|
94
96
|
export function createKindAwareReconciliation(
|
|
@@ -534,6 +536,19 @@ export function createKindAwareReconciliation(
|
|
|
534
536
|
);
|
|
535
537
|
await pending;
|
|
536
538
|
};
|
|
539
|
+
const drain = async (): Promise<void> => {
|
|
540
|
+
while (true) {
|
|
541
|
+
const observed = mutationChain;
|
|
542
|
+
await observed;
|
|
543
|
+
// A noteTransition/noteAccepted admitted in a later microtask must still
|
|
544
|
+
// be joined before teardown reports durable quiescence (#4743).
|
|
545
|
+
await Bun.sleep(0);
|
|
546
|
+
if (mutationChain === observed) {
|
|
547
|
+
await store?.drain?.();
|
|
548
|
+
return;
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
};
|
|
537
552
|
|
|
538
553
|
return {
|
|
539
554
|
admit,
|
|
@@ -551,6 +566,7 @@ export function createKindAwareReconciliation(
|
|
|
551
566
|
reserveSteer,
|
|
552
567
|
settleSteer,
|
|
553
568
|
lookupSteer,
|
|
569
|
+
drain,
|
|
554
570
|
};
|
|
555
571
|
}
|
|
556
572
|
|
|
@@ -625,13 +625,21 @@ export function createReconciliationStore(options: {
|
|
|
625
625
|
let terminalMemory: DurableTerminalScopeRecord[] = [];
|
|
626
626
|
let terminalKeyMemory: EvictedTerminalKeyEntry[] = [];
|
|
627
627
|
let chain: Promise<void> = Promise.resolve();
|
|
628
|
+
// #4743: transaction tails neutralize rejection on the serialization chain and
|
|
629
|
+
// publications are enqueued fire-and-forget, so a failed write has no observer
|
|
630
|
+
// at all. Retain every persistence failure until a drain reports it, then clear
|
|
631
|
+
// it: surface-once means no failure is lost (even one that landed before
|
|
632
|
+
// teardown began) and no later drain is permanently poisoned by a failure an
|
|
633
|
+
// owner has already been told about.
|
|
634
|
+
let unreportedPersistFailures: unknown[] = [];
|
|
628
635
|
|
|
629
636
|
const writeAtomic = async (document: ReconciliationStoreDocument): Promise<void> => {
|
|
630
637
|
if (!filePath) return;
|
|
631
638
|
const directory = path.dirname(filePath);
|
|
632
|
-
|
|
633
|
-
const temporary = `${filePath}.${process.pid}.${Date.now()}.${randomUUID()}.tmp`;
|
|
639
|
+
let temporary: string | undefined;
|
|
634
640
|
try {
|
|
641
|
+
await fileFs.mkdir(directory, { recursive: true, mode: 0o700 });
|
|
642
|
+
temporary = `${filePath}.${process.pid}.${Date.now()}.${randomUUID()}.tmp`;
|
|
635
643
|
await fileFs.writeFile(temporary, `${JSON.stringify(document)}\n`, { mode: 0o600 });
|
|
636
644
|
try {
|
|
637
645
|
const handle = await fileFs.open(temporary, "r+");
|
|
@@ -645,10 +653,14 @@ export function createReconciliationStore(options: {
|
|
|
645
653
|
}
|
|
646
654
|
await fileFs.rename(temporary, filePath);
|
|
647
655
|
} catch (error) {
|
|
648
|
-
|
|
649
|
-
|
|
656
|
+
// Every persistence-path failure (mkdir included) is evidence a drained
|
|
657
|
+
// window must surface, never silently treat as quiescent (#4743).
|
|
658
|
+
if (temporary !== undefined) await fileFs.unlink(temporary).catch(() => {});
|
|
659
|
+
const coded = Object.assign(error instanceof Error ? error : new Error("reconciliation persist failed"), {
|
|
650
660
|
code: "reconciliation_persist_failed",
|
|
651
661
|
});
|
|
662
|
+
unreportedPersistFailures.push(coded);
|
|
663
|
+
throw coded;
|
|
652
664
|
}
|
|
653
665
|
};
|
|
654
666
|
|
|
@@ -834,7 +846,17 @@ export function createReconciliationStore(options: {
|
|
|
834
846
|
// a later microtask. Yield once and require the queue tail to remain
|
|
835
847
|
// stable before reporting durable quiescence.
|
|
836
848
|
await Bun.sleep(0);
|
|
837
|
-
if (chain === observed)
|
|
849
|
+
if (chain === observed) {
|
|
850
|
+
// Transaction tails neutralize rejections on `chain`, so a failed
|
|
851
|
+
// publication would otherwise be indistinguishable from a completed
|
|
852
|
+
// one. Claim the retained evidence (clearing it so exactly one drain
|
|
853
|
+
// reports each failure) and reject instead of claiming quiescence.
|
|
854
|
+
if (unreportedPersistFailures.length === 0) return;
|
|
855
|
+
const claimed = unreportedPersistFailures;
|
|
856
|
+
unreportedPersistFailures = [];
|
|
857
|
+
if (claimed.length === 1) throw claimed[0];
|
|
858
|
+
throw new AggregateError(claimed, "Reconciliation persistence failed during the drained window.");
|
|
859
|
+
}
|
|
838
860
|
}
|
|
839
861
|
},
|
|
840
862
|
loadTerminalScopes: async () => {
|
|
@@ -273,8 +273,15 @@ export const SDK_LIFECYCLE_ROUTER_PROTOCOL_VERSION = 1;
|
|
|
273
273
|
* reaction settlement for Telegram notification delivery (#4528).
|
|
274
274
|
* Generation 169 delivers every ring-positioned session event live through the
|
|
275
275
|
* bounded, capability-gated directed subscriber leg used by replay.
|
|
276
|
+
* Generation 172 fences the SessionRouter idle-poll/change-stamp rollout (#4689).
|
|
277
|
+
* The daemon constructs a SessionRouter, and the router's idle tick no longer
|
|
278
|
+
* re-acquires the machine-global session-index lock on every 2s pass: unchanged
|
|
279
|
+
* polls are stat-only and staleness retirement moved to a 30s sweep. A daemon
|
|
280
|
+
* still running the pre-#4689 router keeps the old hot polling loop and its
|
|
281
|
+
* lock contention, so an already-running owner must be replaced rather than
|
|
282
|
+
* retained across this upgrade.
|
|
276
283
|
*/
|
|
277
|
-
export const DAEMON_GENERATION =
|
|
284
|
+
export const DAEMON_GENERATION = 172;
|
|
278
285
|
|
|
279
286
|
/**
|
|
280
287
|
* Serving-compatibility boundary for daemon lifecycle requests. Epoch 7
|
|
@@ -360,5 +367,8 @@ export const DAEMON_GENERATION = 171;
|
|
|
360
367
|
* Epoch 85 removes direct relay attachment and prohibits lifecycle controls on every adapter.
|
|
361
368
|
* Epoch 86 removes public discovery/client exports and retires cross-process raw transports.
|
|
362
369
|
* Epoch 87 preserves accepted Telegram delivery ambiguity and advances poisoned poll cursors.
|
|
370
|
+
* Epoch 88 requires the #4689 SessionRouter idle-poll contract: generation alone
|
|
371
|
+
* does not force replacement, so the serving boundary advances to guarantee a
|
|
372
|
+
* pre-#4689 daemon cannot keep serving with the old per-tick locked rescan.
|
|
363
373
|
*/
|
|
364
|
-
export const SERVING_EPOCH =
|
|
374
|
+
export const SERVING_EPOCH = 88;
|