@gajae-code/coding-agent 0.16.7 → 0.17.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 +180 -1
- package/dist/types/capability/fs.d.ts +37 -4
- package/dist/types/capability/index.d.ts +19 -0
- package/dist/types/capability/types.d.ts +9 -0
- package/dist/types/cli/doctor/args.d.ts +36 -0
- package/dist/types/cli/doctor/checks.d.ts +18 -0
- package/dist/types/cli/doctor/config-repairs.d.ts +38 -0
- package/dist/types/cli/doctor/context.d.ts +89 -0
- package/dist/types/cli/doctor/files.d.ts +27 -0
- package/dist/types/cli/doctor/ids.d.ts +20 -0
- package/dist/types/cli/doctor/install-repairs.d.ts +94 -0
- package/dist/types/cli/doctor/journal.d.ts +43 -0
- package/dist/types/cli/doctor/managed-link.d.ts +39 -0
- package/dist/types/cli/doctor/native-probe.d.ts +2 -0
- package/dist/types/cli/doctor/permission-repairs.d.ts +25 -0
- package/dist/types/cli/doctor/plugin-quarantine.d.ts +77 -0
- package/dist/types/cli/doctor/plugin-restore.d.ts +40 -0
- package/dist/types/cli/doctor/plugin-targets.d.ts +11 -0
- package/dist/types/cli/doctor/probe-client.d.ts +9 -0
- package/dist/types/cli/doctor/probe-types.d.ts +18 -0
- package/dist/types/cli/doctor/probe.d.ts +3 -0
- package/dist/types/cli/doctor/projection-probe.d.ts +2 -0
- package/dist/types/cli/doctor/repairs.d.ts +28 -0
- package/dist/types/cli/doctor/report.d.ts +27 -0
- package/dist/types/cli/doctor/runner.d.ts +4 -0
- package/dist/types/cli/doctor/safety.d.ts +10 -0
- package/dist/types/cli/doctor/service-repairs.d.ts +8 -0
- package/dist/types/cli/doctor/service-targets.d.ts +41 -0
- package/dist/types/cli/doctor/types.d.ts +145 -0
- package/dist/types/cli/doctor-cli.d.ts +7 -0
- package/dist/types/cli/doctor-supervisor.d.ts +121 -0
- package/dist/types/cli/doctor-worker.d.ts +29 -0
- package/dist/types/cli/install-activation.d.ts +158 -0
- package/dist/types/cli/macos-community-app.d.ts +41 -0
- package/dist/types/cli/mcp-cli.d.ts +0 -11
- package/dist/types/cli/update-cli.d.ts +41 -4
- package/dist/types/cli-main.d.ts +174 -0
- package/dist/types/cli.d.ts +1 -171
- package/dist/types/commands/doctor.d.ts +7 -0
- package/dist/types/commands/sdk.d.ts +17 -8
- package/dist/types/config/autorouting-tier-map.d.ts +19 -0
- package/dist/types/config/file-lock.d.ts +27 -3
- package/dist/types/config/model-preset-registry.d.ts +1 -0
- package/dist/types/config/model-registry.d.ts +12 -5
- package/dist/types/config/model-resolver.d.ts +16 -3
- package/dist/types/config/models-config-schema.d.ts +2 -0
- package/dist/types/config/settings-schema.d.ts +1 -1
- package/dist/types/coordinator-mcp/question-gate-codec.d.ts +1 -1
- package/dist/types/coordinator-mcp/session-reaper.d.ts +15 -5
- package/dist/types/cursor.d.ts +9 -9
- package/dist/types/discovery/agents-md.d.ts +6 -1
- package/dist/types/discovery/helpers.d.ts +36 -7
- package/dist/types/discovery/index.d.ts +1 -1
- package/dist/types/discovery/mcp-json.d.ts +2 -0
- package/dist/types/extensibility/extensions/loader.d.ts +22 -1
- package/dist/types/extensibility/gjc-plugins/lifecycle.d.ts +14 -1
- package/dist/types/extensibility/gjc-plugins/paths.d.ts +2 -1
- package/dist/types/extensibility/gjc-plugins/types.d.ts +25 -0
- package/dist/types/extensibility/plugins/manager.d.ts +33 -4
- package/dist/types/extensibility/plugins/marketplace/cache.d.ts +6 -0
- package/dist/types/extensibility/plugins/marketplace/manager.d.ts +186 -0
- package/dist/types/extensibility/plugins/marketplace/registry.d.ts +27 -0
- package/dist/types/extensibility/plugins/marketplace/source-resolver.d.ts +16 -1
- package/dist/types/extensibility/runtime-skill-discovery.d.ts +16 -3
- package/dist/types/gjc-runtime/autoresearch-runtime.d.ts +6 -0
- package/dist/types/gjc-runtime/launch-worktree.d.ts +53 -0
- package/dist/types/gjc-runtime/session-layout.d.ts +8 -0
- package/dist/types/gjc-runtime/session-state-lock.d.ts +15 -0
- package/dist/types/gjc-runtime/session-state-sidecar.d.ts +13 -0
- package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +6 -0
- package/dist/types/main.d.ts +19 -1
- package/dist/types/modes/acp/acp-agent.d.ts +40 -0
- package/dist/types/modes/components/agent-dashboard.d.ts +1 -0
- package/dist/types/modes/components/model-selector.d.ts +2 -0
- package/dist/types/modes/interactive-mode.d.ts +1 -1
- package/dist/types/runtime-mcp/config-writer.d.ts +47 -0
- package/dist/types/runtime-mcp/policy-json-edit.d.ts +60 -0
- package/dist/types/runtime-mcp/startup-policy.d.ts +4 -0
- package/dist/types/runtime-mcp/types.d.ts +2 -2
- package/dist/types/sdk/acp/adapter.d.ts +5 -1
- package/dist/types/sdk/broker/broker.d.ts +51 -0
- package/dist/types/sdk/broker/daemon-entry.d.ts +39 -0
- package/dist/types/sdk/broker/discovery.d.ts +38 -0
- package/dist/types/sdk/broker/doctor-restart.d.ts +79 -0
- package/dist/types/sdk/broker/endpoint-authority.d.ts +7 -0
- package/dist/types/sdk/broker/ensure.d.ts +14 -0
- package/dist/types/sdk/broker/lifecycle.d.ts +2 -0
- package/dist/types/sdk/broker/process-incarnation.d.ts +50 -0
- package/dist/types/sdk/broker/runtime.d.ts +26 -0
- package/dist/types/sdk/broker/session-index.d.ts +13 -4
- package/dist/types/sdk/broker/worktree-occupancy.d.ts +14 -0
- package/dist/types/sdk/bus/chat-daemon-cli.d.ts +16 -0
- package/dist/types/sdk/bus/chat-daemon-control.d.ts +101 -6
- package/dist/types/sdk/bus/chat-daemon-runtime.d.ts +16 -0
- package/dist/types/sdk/bus/daemon-startup-exclusion.d.ts +43 -0
- package/dist/types/sdk/bus/doctor-daemon-restart-client.d.ts +51 -0
- package/dist/types/sdk/bus/doctor-daemon-restart.d.ts +39 -0
- package/dist/types/sdk/bus/index.d.ts +2 -0
- package/dist/types/sdk/bus/notification-service.d.ts +3 -0
- package/dist/types/sdk/bus/telegram-daemon-cli.d.ts +11 -0
- package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +1 -1
- package/dist/types/sdk/bus/telegram-daemon-control.d.ts +5 -0
- package/dist/types/sdk/bus/telegram-daemon-owner-registry.d.ts +1 -0
- package/dist/types/sdk/bus/telegram-daemon.d.ts +36 -5
- package/dist/types/sdk/cli/session-cli.d.ts +1 -0
- package/dist/types/sdk/host/session-runtime.d.ts +4 -0
- package/dist/types/sdk/host/surface-policy.d.ts +3 -1
- package/dist/types/sdk/lifecycle/service.d.ts +44 -10
- package/dist/types/sdk/prompt-deadline-lease.d.ts +6 -3
- package/dist/types/sdk/prompt-deadline-manager.d.ts +2 -2
- package/dist/types/sdk/prompt-failure.d.ts +47 -0
- package/dist/types/sdk/prompt-status.d.ts +19 -0
- package/dist/types/sdk/router/session-router.d.ts +3 -4
- package/dist/types/sdk/service-artifact-paths.d.ts +16 -0
- package/dist/types/sdk/session-authority.d.ts +9 -0
- package/dist/types/sdk/session.d.ts +3 -1
- package/dist/types/session/agent-session.d.ts +1 -1
- package/dist/types/session/blob-store.d.ts +1 -1
- package/dist/types/session/fallback-chain-controller.d.ts +3 -6
- package/dist/types/session/internal/managed-session-scope.d.ts +44 -0
- package/dist/types/session/primary-control-surface.d.ts +4 -0
- package/dist/types/session/session-manager.d.ts +6 -0
- package/dist/types/system-prompt.d.ts +6 -0
- package/dist/types/tools/ask.d.ts +25 -0
- package/dist/types/tools/image-gen.d.ts +0 -4
- package/dist/types/tools/skill-discovery.d.ts +7 -5
- package/dist/types/tools/ultragoal-ask-guard.d.ts +7 -4
- package/dist/types/utils/changelog.d.ts +0 -9
- package/dist/types/utils/mupdf-wasm.d.ts +1 -0
- package/package.json +9 -8
- package/scripts/compile-args.ts +8 -4
- package/src/capability/fs.ts +395 -17
- package/src/capability/index.ts +273 -10
- package/src/capability/types.ts +9 -0
- package/src/cli/customize-doctor.ts +18 -12
- package/src/cli/doctor/args.ts +236 -0
- package/src/cli/doctor/checks.ts +431 -0
- package/src/cli/doctor/config-repairs.ts +517 -0
- package/src/cli/doctor/context.ts +149 -0
- package/src/cli/doctor/files.ts +149 -0
- package/src/cli/doctor/ids.ts +115 -0
- package/src/cli/doctor/install-repairs.ts +335 -0
- package/src/cli/doctor/journal.ts +423 -0
- package/src/cli/doctor/managed-link.ts +514 -0
- package/src/cli/doctor/native-probe.ts +13 -0
- package/src/cli/doctor/permission-repairs.ts +242 -0
- package/src/cli/doctor/plugin-quarantine.ts +377 -0
- package/src/cli/doctor/plugin-restore.ts +59 -0
- package/src/cli/doctor/plugin-targets.ts +373 -0
- package/src/cli/doctor/probe-client.ts +195 -0
- package/src/cli/doctor/probe-types.ts +21 -0
- package/src/cli/doctor/probe.ts +42 -0
- package/src/cli/doctor/projection-probe.ts +249 -0
- package/src/cli/doctor/repairs.ts +1157 -0
- package/src/cli/doctor/report.ts +176 -0
- package/src/cli/doctor/runner.ts +342 -0
- package/src/cli/doctor/safety.ts +40 -0
- package/src/cli/doctor/service-repairs.ts +452 -0
- package/src/cli/doctor/service-targets.ts +274 -0
- package/src/cli/doctor/types.ts +164 -0
- package/src/cli/doctor-cli.ts +62 -0
- package/src/cli/doctor-supervisor.ts +571 -0
- package/src/cli/doctor-worker.ts +181 -0
- package/src/cli/install-activation.ts +666 -0
- package/src/cli/macos-community-app.ts +1449 -0
- package/src/cli/mcp-cli.ts +11 -21
- package/src/cli/nofile-limit.ts +5 -1
- package/src/cli/plugin-cli.ts +6 -1
- package/src/cli/update-cli.ts +362 -264
- package/src/cli-main.ts +517 -0
- package/src/cli.ts +64 -433
- package/src/commands/autoresearch.ts +11 -5
- package/src/commands/completion.ts +1 -1
- package/src/commands/doctor.ts +13 -0
- package/src/commands/sdk.ts +58 -10
- package/src/commands/ultragoal.ts +11 -5
- package/src/config/autorouting-tier-map.ts +7 -0
- package/src/config/file-lock.ts +736 -71
- package/src/config/mcp-schema.json +3 -3
- package/src/config/model-preset-registry.ts +1 -0
- package/src/config/model-registry.ts +80 -27
- package/src/config/model-resolver.ts +33 -4
- package/src/config/models-config-schema.ts +2 -0
- package/src/config/settings-schema.ts +6 -2
- package/src/coordinator-mcp/question-gate-codec.ts +1 -0
- package/src/coordinator-mcp/server.ts +534 -51
- package/src/coordinator-mcp/session-reaper.ts +64 -8
- package/src/cursor.ts +223 -53
- package/src/discovery/agents-md.ts +74 -8
- package/src/discovery/agents.ts +32 -7
- package/src/discovery/builtin.ts +184 -75
- package/src/discovery/claude-plugins.ts +85 -17
- package/src/discovery/cline.ts +38 -10
- package/src/discovery/cursor.ts +19 -6
- package/src/discovery/gemini.ts +62 -22
- package/src/discovery/github.ts +13 -3
- package/src/discovery/helpers.ts +483 -89
- package/src/discovery/index.ts +1 -0
- package/src/discovery/mcp-json.ts +36 -10
- package/src/discovery/opencode.ts +36 -19
- package/src/discovery/ssh.ts +11 -8
- package/src/discovery/windsurf.ts +12 -3
- package/src/exec/bash-executor.ts +10 -3
- package/src/exec/isolated-shell.ts +15 -2
- package/src/extensibility/extensions/loader.ts +88 -26
- package/src/extensibility/gjc-plugins/lifecycle.ts +189 -0
- package/src/extensibility/gjc-plugins/paths.ts +4 -1
- package/src/extensibility/gjc-plugins/types.ts +27 -0
- package/src/extensibility/plugins/manager.ts +200 -22
- package/src/extensibility/plugins/marketplace/cache.ts +55 -2
- package/src/extensibility/plugins/marketplace/manager.ts +1262 -162
- package/src/extensibility/plugins/marketplace/registry.ts +153 -0
- package/src/extensibility/plugins/marketplace/source-resolver.ts +116 -10
- package/src/extensibility/runtime-skill-discovery.ts +61 -9
- package/src/extensibility/skill-management.ts +7 -1
- package/src/gjc-runtime/autoresearch-runtime.ts +10 -1
- package/src/gjc-runtime/launch-worktree.test.ts +248 -0
- package/src/gjc-runtime/launch-worktree.ts +275 -38
- package/src/gjc-runtime/repository-binding.ts +26 -1
- package/src/gjc-runtime/session-layout.ts +10 -0
- package/src/gjc-runtime/session-state-lock.ts +330 -23
- package/src/gjc-runtime/session-state-sidecar.ts +328 -80
- package/src/gjc-runtime/ultragoal-runtime.ts +10 -1
- package/src/harness-control-plane/session-lease.ts +2 -2
- package/src/internal-urls/docs-index.generated.ts +28 -26
- package/src/main.ts +150 -29
- package/src/memories/index.ts +24 -2
- package/src/modes/acp/acp-agent.ts +522 -68
- package/src/modes/components/agent-dashboard.ts +3 -1
- package/src/modes/components/model-selector.ts +28 -19
- package/src/modes/components/plugin-settings.ts +2 -1
- package/src/modes/controllers/selector-controller.ts +15 -9
- package/src/modes/interactive-mode.ts +21 -3
- package/src/prompts/system/system-prompt.md +5 -1
- package/src/prompts/tools/browser.md +1 -1
- package/src/prompts/tools/move-session.md +4 -0
- package/src/prompts/tools/read.md +2 -1
- package/src/prompts/tools/skill-discovery.md +6 -5
- package/src/prompts/tools/task.md +1 -0
- package/src/runtime-mcp/config-writer.ts +228 -0
- package/src/runtime-mcp/oauth-discovery.ts +114 -14
- package/src/runtime-mcp/policy-json-edit.ts +129 -0
- package/src/runtime-mcp/startup-policy.ts +14 -0
- package/src/runtime-mcp/types.ts +2 -2
- package/src/sdk/acp/adapter.ts +27 -12
- package/src/sdk/broker/broker.ts +273 -44
- package/src/sdk/broker/daemon-entry.ts +119 -0
- package/src/sdk/broker/discovery.ts +173 -20
- package/src/sdk/broker/doctor-restart.ts +206 -0
- package/src/sdk/broker/endpoint-authority.ts +36 -0
- package/src/sdk/broker/ensure.ts +49 -14
- package/src/sdk/broker/lifecycle.ts +174 -42
- package/src/sdk/broker/process-incarnation.ts +86 -2
- package/src/sdk/broker/runtime.ts +145 -21
- package/src/sdk/broker/session-index.ts +136 -22
- package/src/sdk/broker/spawn-substrate.ts +4 -1
- package/src/sdk/broker/transport.ts +5 -0
- package/src/sdk/broker/worktree-occupancy.ts +21 -1
- package/src/sdk/bus/chat-daemon-cli.ts +63 -0
- package/src/sdk/bus/chat-daemon-control.ts +260 -64
- package/src/sdk/bus/chat-daemon-runtime.ts +141 -25
- package/src/sdk/bus/daemon-startup-exclusion.ts +119 -0
- package/src/sdk/bus/doctor-daemon-restart-client.ts +291 -0
- package/src/sdk/bus/doctor-daemon-restart.ts +92 -0
- package/src/sdk/bus/index.ts +252 -51
- package/src/sdk/bus/kind-aware-reconciliation.ts +76 -41
- package/src/sdk/bus/notification-service.ts +21 -3
- package/src/sdk/bus/reconciliation-store.ts +17 -11
- package/src/sdk/bus/telegram-daemon-cli.ts +91 -5
- package/src/sdk/bus/telegram-daemon-contract.ts +1 -1
- package/src/sdk/bus/telegram-daemon-control.ts +47 -0
- package/src/sdk/bus/telegram-daemon-owner-registry.ts +1 -1
- package/src/sdk/bus/telegram-daemon.ts +178 -13
- package/src/sdk/cli/session-cli.ts +100 -12
- package/src/sdk/host/host.ts +2 -7
- package/src/sdk/host/session-runtime.test.ts +329 -8
- package/src/sdk/host/session-runtime.ts +202 -50
- package/src/sdk/host/surface-policy.ts +8 -1
- package/src/sdk/lifecycle/service.ts +199 -22
- package/src/sdk/mcp/server.ts +1 -1
- package/src/sdk/prompt-deadline-lease.ts +12 -4
- package/src/sdk/prompt-deadline-manager.ts +5 -7
- package/src/sdk/prompt-failure.ts +148 -0
- package/src/sdk/prompt-status.ts +30 -1
- package/src/sdk/prompt-watchdog.ts +11 -3
- package/src/sdk/protocol/operation-inventory.generated.json +18 -0
- package/src/sdk/protocol/operation-registry.ts +20 -8
- package/src/sdk/router/session-router.ts +8 -3
- package/src/sdk/service-artifact-paths.ts +26 -0
- package/src/sdk/session-authority.ts +10 -0
- package/src/sdk/session.ts +361 -194
- package/src/session/agent-session.ts +398 -93
- package/src/session/blob-store.ts +9 -9
- package/src/session/fallback-chain-controller.ts +3 -7
- package/src/session/internal/managed-session-scope.ts +445 -29
- package/src/session/internal/managed-session-storage.ts +38 -6
- package/src/session/primary-control-surface.ts +12 -0
- package/src/session/session-manager.ts +11 -3
- package/src/session/session-storage.ts +1 -1
- package/src/session-import/codex.ts +6 -1
- package/src/session-import/redact.ts +7 -0
- package/src/skill-state/workflow-mutation-guard.ts +645 -39
- package/src/slash-commands/builtin-registry.ts +7 -3
- package/src/system-prompt.ts +8 -0
- package/src/task/executor.ts +36 -3
- package/src/task/types.ts +27 -5
- package/src/tools/ask.ts +114 -34
- package/src/tools/browser/actions.ts +2 -2
- package/src/tools/browser/tab-worker.ts +11 -8
- package/src/tools/descriptors.ts +6 -1
- package/src/tools/fetch.ts +7 -2
- package/src/tools/image-gen.ts +33 -34
- package/src/tools/read.ts +78 -32
- package/src/tools/skill-discovery.ts +17 -7
- package/src/tools/tool-catalog.generated.ts +8 -8
- package/src/tools/ultragoal-ask-guard.ts +16 -26
- package/src/utils/changelog.ts +7 -1
- package/src/utils/git.ts +3 -1
- package/src/utils/markit.ts +24 -2
- package/src/utils/mupdf-wasm.ts +26 -0
- package/src/web/search/providers/openai-compatible.ts +191 -14
|
@@ -25,6 +25,7 @@ import {
|
|
|
25
25
|
import { listMcpDelegateHostContexts } from "../hooks/mcp-delegate-host-context";
|
|
26
26
|
import type { WorkflowGate, WorkflowGateQueryRecord } from "../modes/shared/agent-wire/workflow-gate-types";
|
|
27
27
|
import type { BrokerDiscovery } from "../sdk/broker/discovery";
|
|
28
|
+
import { endpointIncarnation } from "../sdk/broker/endpoint-authority";
|
|
28
29
|
import { type EnsureBrokerSettings, ensureBroker } from "../sdk/broker/ensure";
|
|
29
30
|
import { lifecycleRequestTimeoutMs } from "../sdk/broker/startup-budget";
|
|
30
31
|
import { UnsupportedStateVersionError } from "../sdk/broker/state-version";
|
|
@@ -139,6 +140,7 @@ import {
|
|
|
139
140
|
reconcileCreationRemoteVerifier,
|
|
140
141
|
recordCreationRetirementBrokerProof,
|
|
141
142
|
recordCreationRetirementIntent,
|
|
143
|
+
recordDeletionIntent,
|
|
142
144
|
recoverExpiredPublicDelivery,
|
|
143
145
|
releasePublicDeliveryClaim,
|
|
144
146
|
removeSessionTransaction,
|
|
@@ -542,7 +544,10 @@ function textResult(
|
|
|
542
544
|
};
|
|
543
545
|
}
|
|
544
546
|
|
|
545
|
-
function toolSchema(
|
|
547
|
+
function toolSchema(
|
|
548
|
+
name: CoordinatorToolName,
|
|
549
|
+
platform: NodeJS.Platform,
|
|
550
|
+
): {
|
|
546
551
|
name: CoordinatorToolName;
|
|
547
552
|
description: string;
|
|
548
553
|
inputSchema: Record<string, unknown>;
|
|
@@ -684,7 +689,7 @@ function toolSchema(name: CoordinatorToolName): {
|
|
|
684
689
|
return {
|
|
685
690
|
name,
|
|
686
691
|
description:
|
|
687
|
-
"Close and reap a coordinator
|
|
692
|
+
"Close and reap a coordinator-created (ephemeral) SDK session through broker lifecycle control. Non-ephemeral user-registered sessions require both force and the force-stop capability.",
|
|
688
693
|
inputSchema: {
|
|
689
694
|
type: "object",
|
|
690
695
|
properties: {
|
|
@@ -803,7 +808,9 @@ function toolSchema(name: CoordinatorToolName): {
|
|
|
803
808
|
if (name === "gjc_coordinator_read_artifact") {
|
|
804
809
|
return {
|
|
805
810
|
name,
|
|
806
|
-
|
|
811
|
+
// Controllers detect capability from tools/list (docs/hermes-mcp-bridge.md), so the
|
|
812
|
+
// advertisement must use the same resolved platform as the handler refusal.
|
|
813
|
+
description: coordinatorArtifactCapability(platform).available
|
|
807
814
|
? "Read one bounded artifact from configured safe roots."
|
|
808
815
|
: "Unavailable on this platform: artifact reads require Linux identity-bound handle authorization.",
|
|
809
816
|
inputSchema: { type: "object", properties: { path: pathField }, required: ["path"] },
|
|
@@ -1376,7 +1383,7 @@ function boundedPublicValue(value: unknown, budget: { remaining: number }, depth
|
|
|
1376
1383
|
const output: Record<string, unknown> = {};
|
|
1377
1384
|
for (const key of Object.keys(value as Record<string, unknown>).slice(0, 128)) {
|
|
1378
1385
|
const field = (value as Record<string, unknown>)[key];
|
|
1379
|
-
if (key === "error") {
|
|
1386
|
+
if (key === "error" && field !== null) {
|
|
1380
1387
|
const rawCode =
|
|
1381
1388
|
field && typeof field === "object" && !Array.isArray(field)
|
|
1382
1389
|
? (field as Record<string, unknown>).code
|
|
@@ -4438,6 +4445,8 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
|
|
|
4438
4445
|
eventId: string;
|
|
4439
4446
|
}> = [];
|
|
4440
4447
|
let q12Admitted = true;
|
|
4448
|
+
let liveInFlightAskAdmitted = false;
|
|
4449
|
+
let liveInFlightCandidate = false;
|
|
4441
4450
|
await withAdmittedSessionTransaction(
|
|
4442
4451
|
questionPaths,
|
|
4443
4452
|
sessionId,
|
|
@@ -4445,6 +4454,39 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
|
|
|
4445
4454
|
const liveState = await readSessionState(namespaceDir, sessionId);
|
|
4446
4455
|
const waitingToken = admissionToken;
|
|
4447
4456
|
const waitingTurn = waitingToken ? transaction.canonical.turns[waitingToken.coordinator_turn_id] : null;
|
|
4457
|
+
const liveTurn = admission.active_turn_id
|
|
4458
|
+
? transaction.canonical.turns[admission.active_turn_id]
|
|
4459
|
+
: undefined;
|
|
4460
|
+
// The Q12 query runs outside this transaction. Re-check the turn that
|
|
4461
|
+
// admission observed so cancellation or terminal reconciliation cannot
|
|
4462
|
+
// make an in-flight empty snapshot look like a healthy running result.
|
|
4463
|
+
if (
|
|
4464
|
+
admission.active_turn_id &&
|
|
4465
|
+
(!liveTurn || TERMINAL_TURN_STATUSES.has(liveTurn.status as TurnStatus))
|
|
4466
|
+
) {
|
|
4467
|
+
q12Admitted = false;
|
|
4468
|
+
return;
|
|
4469
|
+
}
|
|
4470
|
+
// A headless ask opens a durable Q12 gate before the runtime has a
|
|
4471
|
+
// distinct needs_user_input lifecycle marker. Admit that narrow,
|
|
4472
|
+
// observable in-flight state only when the sidecar, active turn, and
|
|
4473
|
+
// accepted runtime receipt all identify the same turn. This is waiting
|
|
4474
|
+
// evidence, not a terminal observation; all terminal paths remain
|
|
4475
|
+
// subject to the existing receipt and terminal-fence checks.
|
|
4476
|
+
liveInFlightCandidate = Boolean(
|
|
4477
|
+
liveState?.schema_version === 1 &&
|
|
4478
|
+
liveState.session_id === sessionId &&
|
|
4479
|
+
liveState?.source === "agent_session_event" &&
|
|
4480
|
+
liveState.live === true &&
|
|
4481
|
+
liveState.state === "running" &&
|
|
4482
|
+
liveState.current_turn_id !== null &&
|
|
4483
|
+
Number.isFinite(Date.parse(liveState.updated_at)) &&
|
|
4484
|
+
hasExactRuntimeWriterIdentity(liveState, transaction.canonical.session.broker) &&
|
|
4485
|
+
liveState.current_turn_id === admission.active_turn_id &&
|
|
4486
|
+
liveTurn &&
|
|
4487
|
+
ACTIVE_TURN_STATUSES.has(liveTurn.status as TurnStatus) &&
|
|
4488
|
+
hasAcceptedRuntimeReceipt(liveTurn),
|
|
4489
|
+
);
|
|
4448
4490
|
if (
|
|
4449
4491
|
waitingToken &&
|
|
4450
4492
|
(!liveState ||
|
|
@@ -4464,10 +4506,19 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
|
|
|
4464
4506
|
q12Admitted = false;
|
|
4465
4507
|
return;
|
|
4466
4508
|
}
|
|
4509
|
+
if (
|
|
4510
|
+
liveState?.source === "agent_session_event" &&
|
|
4511
|
+
(liveState.state === "completed" || liveState.state === "errored") &&
|
|
4512
|
+
!admission.terminal
|
|
4513
|
+
) {
|
|
4514
|
+
q12Admitted = false;
|
|
4515
|
+
return;
|
|
4516
|
+
}
|
|
4467
4517
|
if (
|
|
4468
4518
|
liveState?.source === "agent_session_event" &&
|
|
4469
4519
|
liveState.live === true &&
|
|
4470
|
-
(liveState.state !== "needs_user_input" || liveState.current_turn_id !== admission.active_turn_id)
|
|
4520
|
+
(liveState.state !== "needs_user_input" || liveState.current_turn_id !== admission.active_turn_id) &&
|
|
4521
|
+
!liveInFlightCandidate
|
|
4471
4522
|
) {
|
|
4472
4523
|
q12Admitted = false;
|
|
4473
4524
|
return;
|
|
@@ -4581,6 +4632,13 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
|
|
|
4581
4632
|
};
|
|
4582
4633
|
authority.outcome = { state: "deferred_link", first_seen_at: authority.first_seen_at };
|
|
4583
4634
|
authority.updated_at = observedAt;
|
|
4635
|
+
// The runtime already published this gate, but no coordinator turn owns
|
|
4636
|
+
// it yet, so the question stays unmaterialized. Reporting nothing here
|
|
4637
|
+
// made a blocked agent indistinguishable from an idle one: `list_questions`
|
|
4638
|
+
// answered a healthy empty snapshot for the whole deferral window while
|
|
4639
|
+
// the ask behind the gate was waiting for an answer no operator could see
|
|
4640
|
+
// (#5475). The deferral itself is unchanged — only the silence is.
|
|
4641
|
+
diagnostic("pending_registration", null, gate.gate_id);
|
|
4584
4642
|
continue;
|
|
4585
4643
|
}
|
|
4586
4644
|
authority.outcome =
|
|
@@ -4596,6 +4654,12 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
|
|
|
4596
4654
|
continue;
|
|
4597
4655
|
}
|
|
4598
4656
|
const turn = owners[0]!;
|
|
4657
|
+
if (liveInFlightCandidate && turn.turn_id !== admission.active_turn_id) {
|
|
4658
|
+
authority.outcome = { state: "ownership_conflict", reason: "ownership_conflict" };
|
|
4659
|
+
authority.updated_at = observedAt;
|
|
4660
|
+
diagnostic("ownership_conflict", turn.turn_id, gate.gate_id);
|
|
4661
|
+
continue;
|
|
4662
|
+
}
|
|
4599
4663
|
const provenance = runtimeProvenanceToken(sessionId, transaction, turn, gate);
|
|
4600
4664
|
if (!turn.runtime_provenance || canonicalJson(turn.runtime_provenance) !== canonicalJson(provenance))
|
|
4601
4665
|
turn.runtime_provenance = provenance;
|
|
@@ -4691,6 +4755,8 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
|
|
|
4691
4755
|
},
|
|
4692
4756
|
};
|
|
4693
4757
|
}
|
|
4758
|
+
if (liveInFlightCandidate && turn.turn_id === admission.active_turn_id)
|
|
4759
|
+
liveInFlightAskAdmitted = true;
|
|
4694
4760
|
}
|
|
4695
4761
|
if (complete)
|
|
4696
4762
|
for (const [authorityId, authority] of Object.entries(transaction.canonical.gate_authorities))
|
|
@@ -4712,7 +4778,11 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
|
|
|
4712
4778
|
},
|
|
4713
4779
|
options,
|
|
4714
4780
|
);
|
|
4715
|
-
|
|
4781
|
+
// A complete empty Q12 snapshot proves no pending gate at this revision,
|
|
4782
|
+
// even while an authenticated, acknowledged turn is still running.
|
|
4783
|
+
// Nonempty snapshots still require an admitted ask; all writer, waiting
|
|
4784
|
+
// token and terminal fences above remain authoritative.
|
|
4785
|
+
if (!q12Admitted || (liveInFlightCandidate && items.length > 0 && !liveInFlightAskAdmitted))
|
|
4716
4786
|
return {
|
|
4717
4787
|
ok: true,
|
|
4718
4788
|
schema_version: 1,
|
|
@@ -5633,9 +5703,19 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
|
|
|
5633
5703
|
endpointMtimeMs <= 0
|
|
5634
5704
|
)
|
|
5635
5705
|
return null;
|
|
5636
|
-
|
|
5637
|
-
|
|
5638
|
-
|
|
5706
|
+
// Single canonical producer lives in endpoint-authority.ts (#5376
|
|
5707
|
+
// follow-up): never reimplement the digest recipe here.
|
|
5708
|
+
return (
|
|
5709
|
+
endpointIncarnation(
|
|
5710
|
+
{
|
|
5711
|
+
endpointGeneration,
|
|
5712
|
+
endpointMtimeMs,
|
|
5713
|
+
pid,
|
|
5714
|
+
...(typeof session.endpointFileId === "string" ? { endpointFileId: session.endpointFileId } : {}),
|
|
5715
|
+
},
|
|
5716
|
+
sessionId,
|
|
5717
|
+
) ?? null
|
|
5718
|
+
);
|
|
5639
5719
|
}
|
|
5640
5720
|
|
|
5641
5721
|
type BrokerSessionAuthority = {
|
|
@@ -5701,13 +5781,31 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
|
|
|
5701
5781
|
|
|
5702
5782
|
async function listSessions(cwd?: string): Promise<Array<Record<string, unknown>>> {
|
|
5703
5783
|
const roots = cwd ? [cwd] : config.allowedRoots;
|
|
5784
|
+
const uniqueRoots = [
|
|
5785
|
+
...new Map(
|
|
5786
|
+
roots.map(root => {
|
|
5787
|
+
let canonical = path.resolve(root);
|
|
5788
|
+
try {
|
|
5789
|
+
canonical = nodeFs.realpathSync.native(canonical);
|
|
5790
|
+
} catch {
|
|
5791
|
+
// Preserve the configured path when the root is not materialized yet.
|
|
5792
|
+
}
|
|
5793
|
+
return [normalizePathForComparison(canonical, platform), root] as const;
|
|
5794
|
+
}),
|
|
5795
|
+
).values(),
|
|
5796
|
+
];
|
|
5704
5797
|
const listings = await Promise.all(
|
|
5705
|
-
|
|
5798
|
+
uniqueRoots.map(async root => {
|
|
5706
5799
|
const listing = await paginatedBrokerSessionList(root, { cwd: root });
|
|
5707
5800
|
return scopedBrokerSessions(Array.isArray(listing.sessions) ? listing.sessions : [], root, platform);
|
|
5708
5801
|
}),
|
|
5709
5802
|
);
|
|
5710
|
-
|
|
5803
|
+
const uniqueSessions = new Map<string, Record<string, unknown>>();
|
|
5804
|
+
for (const session of listings.flat()) {
|
|
5805
|
+
const id = brokerSessionId(session);
|
|
5806
|
+
if (id !== null) uniqueSessions.set(id, session);
|
|
5807
|
+
}
|
|
5808
|
+
return [...uniqueSessions.values()];
|
|
5711
5809
|
}
|
|
5712
5810
|
function sessionFile(sessionId: unknown): string {
|
|
5713
5811
|
return path.join(namespaceDir, "sessions", `${safeExternalId("session", sessionId)}.json`);
|
|
@@ -5848,6 +5946,41 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
|
|
|
5848
5946
|
);
|
|
5849
5947
|
return true;
|
|
5850
5948
|
}
|
|
5949
|
+
/**
|
|
5950
|
+
* True when the broker row for this session is the terminal-uncertain claim a
|
|
5951
|
+
* forced stale-worktree release recorded against exactly this authority. The
|
|
5952
|
+
* claim is only accepted as close proof while the row is still that identity:
|
|
5953
|
+
* a rotated successor, an ambiguous row, or an unreadable listing all fall
|
|
5954
|
+
* back to the ordinary close.
|
|
5955
|
+
*/
|
|
5956
|
+
async function forcedStaleReleaseProven(
|
|
5957
|
+
sessionId: string,
|
|
5958
|
+
workspace: string | null,
|
|
5959
|
+
endpointGeneration: number,
|
|
5960
|
+
endpointIncarnation: string,
|
|
5961
|
+
): Promise<boolean> {
|
|
5962
|
+
if (!workspace) return false;
|
|
5963
|
+
let matches: Array<Record<string, unknown>>;
|
|
5964
|
+
try {
|
|
5965
|
+
const canonicalWorkspace = await canonicalBrokerWorkspace(workspace);
|
|
5966
|
+
const listing = await paginatedBrokerSessionList(canonicalWorkspace, { cwd: canonicalWorkspace });
|
|
5967
|
+
matches = jsonRecords(Array.isArray(listing.sessions) ? listing.sessions : []).filter(
|
|
5968
|
+
row => brokerSessionId(row) === sessionId,
|
|
5969
|
+
);
|
|
5970
|
+
} catch {
|
|
5971
|
+
return false;
|
|
5972
|
+
}
|
|
5973
|
+
if (matches.length !== 1) return false;
|
|
5974
|
+
const row = matches[0]!;
|
|
5975
|
+
return (
|
|
5976
|
+
row.ambiguous !== true &&
|
|
5977
|
+
(row.terminalUncertain === true || row.terminal_uncertain === true) &&
|
|
5978
|
+
(row.forcedStaleRelease === true || row.forced_stale_release === true) &&
|
|
5979
|
+
brokerEndpointGeneration(row) === endpointGeneration &&
|
|
5980
|
+
brokerEndpointIncarnation(row, sessionId) === endpointIncarnation
|
|
5981
|
+
);
|
|
5982
|
+
}
|
|
5983
|
+
|
|
5851
5984
|
async function recoverIntentDeletion(entry: NamespaceDeletionEntryV1): Promise<void> {
|
|
5852
5985
|
const session = asRecord(await readJsonFile(sessionFile(entry.session_id)));
|
|
5853
5986
|
if (!session) throw new SdkClientError("state_corrupt", "Close intent has no session authority record.");
|
|
@@ -5860,19 +5993,33 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
|
|
|
5860
5993
|
: null;
|
|
5861
5994
|
if (!cwd || endpointGeneration === null)
|
|
5862
5995
|
throw new SdkClientError("state_corrupt", "Close intent authority is incomplete.");
|
|
5863
|
-
|
|
5864
|
-
|
|
5865
|
-
|
|
5866
|
-
|
|
5867
|
-
|
|
5868
|
-
|
|
5869
|
-
|
|
5870
|
-
|
|
5871
|
-
|
|
5872
|
-
|
|
5873
|
-
|
|
5874
|
-
|
|
5875
|
-
|
|
5996
|
+
// #5581: a forced stop that admitted this deletion and then lost its endpoint
|
|
5997
|
+
// already released the worktree by recording a terminal-uncertain claim bound
|
|
5998
|
+
// to this exact authority. The broker refuses an ordinary `session.close`
|
|
5999
|
+
// against such a row (`terminal_uncertain`), so retrying one here would park
|
|
6000
|
+
// the manifest at `intent` forever. The claim is itself the teardown proof:
|
|
6001
|
+
// advance on it instead, exactly as the success path does.
|
|
6002
|
+
if (
|
|
6003
|
+
!(await forcedStaleReleaseProven(
|
|
6004
|
+
entry.session_id,
|
|
6005
|
+
optionalString(session.broker_workspace),
|
|
6006
|
+
endpointGeneration,
|
|
6007
|
+
entry.endpoint_incarnation,
|
|
6008
|
+
))
|
|
6009
|
+
)
|
|
6010
|
+
strictBrokerSessionClose(
|
|
6011
|
+
await brokerSession(
|
|
6012
|
+
cwd,
|
|
6013
|
+
"session.close",
|
|
6014
|
+
{
|
|
6015
|
+
sessionId: entry.session_id,
|
|
6016
|
+
endpointGeneration,
|
|
6017
|
+
endpointIncarnation: entry.endpoint_incarnation,
|
|
6018
|
+
},
|
|
6019
|
+
`coordinator-reap:${entry.session_id}:${entry.endpoint_incarnation}`,
|
|
6020
|
+
),
|
|
6021
|
+
entry.session_id,
|
|
6022
|
+
);
|
|
5876
6023
|
await advanceDeletion(questionPaths, entry.deletion_id, "broker_closed", undefined, {
|
|
5877
6024
|
ok: true,
|
|
5878
6025
|
closed: true,
|
|
@@ -5969,22 +6116,49 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
|
|
|
5969
6116
|
const persistedIncarnation = optionalString(session.endpoint_incarnation);
|
|
5970
6117
|
if (!cwd || !persistedWorkspace || persistedGeneration === null || !persistedIncarnation)
|
|
5971
6118
|
return { ok: false, reason: "endpoint_stale", closed: false };
|
|
6119
|
+
// #5581: a forced stop of a session whose endpoint has gone stale (its
|
|
6120
|
+
// coordinator service vanished mid-run) cannot prove teardown, so it
|
|
6121
|
+
// returns endpoint_stale below. The worktree release must still happen
|
|
6122
|
+
// first: ask the broker to record a terminal-uncertain claim so the row
|
|
6123
|
+
// stops holding its checkout. Best-effort — the endpoint_stale signal the
|
|
6124
|
+
// caller depends on is returned regardless of whether release succeeds.
|
|
6125
|
+
// The persisted endpoint authority is passed so the broker acts on exactly
|
|
6126
|
+
// this stale identity: a live successor incarnation fails the close
|
|
6127
|
+
// authority check and is never touched, so a legitimately re-held worktree
|
|
6128
|
+
// stays occupied.
|
|
6129
|
+
const releaseStaleWorktreeOnForce = async (): Promise<void> => {
|
|
6130
|
+
if (opts.force !== true) return;
|
|
6131
|
+
try {
|
|
6132
|
+
await brokerSession(cwd, "session.close", {
|
|
6133
|
+
sessionId: id,
|
|
6134
|
+
endpointGeneration: persistedGeneration,
|
|
6135
|
+
endpointIncarnation: persistedIncarnation,
|
|
6136
|
+
forceReleaseStaleWorktree: true,
|
|
6137
|
+
});
|
|
6138
|
+
} catch {
|
|
6139
|
+
// Advisory release; a later launch simply re-observes the row and retries.
|
|
6140
|
+
}
|
|
6141
|
+
};
|
|
5972
6142
|
// Endpoint identity is the authority for any lifecycle mutation. Check it
|
|
5973
6143
|
// before consulting a possibly stale active-turn projection so a successor
|
|
5974
6144
|
// incarnation cannot be blocked by old local state.
|
|
5975
6145
|
let preflightWorkspace: string;
|
|
5976
6146
|
try {
|
|
5977
|
-
preflightWorkspace = await canonicalBrokerWorkspace(
|
|
6147
|
+
preflightWorkspace = await canonicalBrokerWorkspace(persistedWorkspace);
|
|
5978
6148
|
const authority = await exactBrokerSessionAuthority(id, preflightWorkspace);
|
|
5979
6149
|
if (
|
|
5980
6150
|
!sameCanonicalPath(authority.workspace, persistedWorkspace, platform) ||
|
|
5981
6151
|
authority.endpointGeneration !== persistedGeneration ||
|
|
5982
6152
|
authority.endpointIncarnation !== persistedIncarnation
|
|
5983
|
-
)
|
|
6153
|
+
) {
|
|
6154
|
+
await releaseStaleWorktreeOnForce();
|
|
5984
6155
|
return { ok: false, reason: "endpoint_stale", closed: false };
|
|
6156
|
+
}
|
|
5985
6157
|
} catch (error) {
|
|
5986
|
-
if (error instanceof SdkClientError && (error.code === "not_found" || error.code === "endpoint_stale"))
|
|
6158
|
+
if (error instanceof SdkClientError && (error.code === "not_found" || error.code === "endpoint_stale")) {
|
|
6159
|
+
await releaseStaleWorktreeOnForce();
|
|
5987
6160
|
return { ok: false, reason: "endpoint_stale", closed: false };
|
|
6161
|
+
}
|
|
5988
6162
|
throw error;
|
|
5989
6163
|
}
|
|
5990
6164
|
if (session.ephemeral !== true && opts.force !== true)
|
|
@@ -6078,7 +6252,7 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
|
|
|
6078
6252
|
const closeAlreadyProven = deletionPhase === "broker_closed" || deletionPhase === "cleanup_pending";
|
|
6079
6253
|
let workspace = "";
|
|
6080
6254
|
try {
|
|
6081
|
-
workspace = await canonicalBrokerWorkspace(
|
|
6255
|
+
workspace = await canonicalBrokerWorkspace(persistedWorkspace);
|
|
6082
6256
|
let authority: BrokerSessionAuthority | null = null;
|
|
6083
6257
|
if (!closeAlreadyProven) {
|
|
6084
6258
|
authority = await exactBrokerSessionAuthority(id, workspace);
|
|
@@ -6086,8 +6260,10 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
|
|
|
6086
6260
|
!sameCanonicalPath(authority.workspace, persistedWorkspace, platform) ||
|
|
6087
6261
|
authority.endpointGeneration !== persistedGeneration ||
|
|
6088
6262
|
authority.endpointIncarnation !== persistedIncarnation
|
|
6089
|
-
)
|
|
6263
|
+
) {
|
|
6264
|
+
await releaseStaleWorktreeOnForce();
|
|
6090
6265
|
return { ok: false, reason: "endpoint_stale", closed: false };
|
|
6266
|
+
}
|
|
6091
6267
|
}
|
|
6092
6268
|
await ensureQuestionStateReady();
|
|
6093
6269
|
await ensureQuestionTransaction(id);
|
|
@@ -6127,6 +6303,15 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
|
|
|
6127
6303
|
});
|
|
6128
6304
|
}
|
|
6129
6305
|
} catch (error) {
|
|
6306
|
+
// #5581: a forced stop can clear the second authority check yet lose the
|
|
6307
|
+
// endpoint before `session.close` reaches the broker, so the close throws
|
|
6308
|
+
// a stale/absent code. The deletion is already admitted as remote_started
|
|
6309
|
+
// but never proven closed, leaving the indexed row holding its worktree.
|
|
6310
|
+
// Record the same identity-bound terminal-uncertain claim before surfacing
|
|
6311
|
+
// the unproven close. Only stale/absent codes qualify: a live endpoint
|
|
6312
|
+
// failing transiently must keep its checkout.
|
|
6313
|
+
if (error instanceof SdkClientError && (error.code === "endpoint_stale" || error.code === "not_found"))
|
|
6314
|
+
await releaseStaleWorktreeOnForce();
|
|
6130
6315
|
return {
|
|
6131
6316
|
ok: false,
|
|
6132
6317
|
reason: "close_failed",
|
|
@@ -6211,6 +6396,164 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
|
|
|
6211
6396
|
});
|
|
6212
6397
|
}
|
|
6213
6398
|
|
|
6399
|
+
/**
|
|
6400
|
+
* Prove a stored endpoint identity (workspace + generation + incarnation) is no
|
|
6401
|
+
* longer current against the live broker index. Returns true only when the
|
|
6402
|
+
* broker row is absent (not_found) or its generation/incarnation/workspace has
|
|
6403
|
+
* rotated away from the stored triple. A transient authority-read failure
|
|
6404
|
+
* returns false: we cannot prove the endpoint recovered, but we also must not
|
|
6405
|
+
* force-evict on a flake — the normal reap path will retry.
|
|
6406
|
+
*/
|
|
6407
|
+
async function brokerEndpointIdentityStale(
|
|
6408
|
+
id: string,
|
|
6409
|
+
persistedWorkspace: string,
|
|
6410
|
+
persistedGeneration: number,
|
|
6411
|
+
persistedIncarnation: string,
|
|
6412
|
+
): Promise<boolean> {
|
|
6413
|
+
try {
|
|
6414
|
+
const workspace = await canonicalBrokerWorkspace(persistedWorkspace);
|
|
6415
|
+
const authority = await exactBrokerSessionAuthority(id, workspace);
|
|
6416
|
+
return (
|
|
6417
|
+
!sameCanonicalPath(authority.workspace, persistedWorkspace, platform) ||
|
|
6418
|
+
authority.endpointGeneration !== persistedGeneration ||
|
|
6419
|
+
authority.endpointIncarnation !== persistedIncarnation
|
|
6420
|
+
);
|
|
6421
|
+
} catch (error) {
|
|
6422
|
+
if (error instanceof SdkClientError && (error.code === "not_found" || error.code === "endpoint_stale"))
|
|
6423
|
+
return true;
|
|
6424
|
+
return false;
|
|
6425
|
+
}
|
|
6426
|
+
}
|
|
6427
|
+
|
|
6428
|
+
/**
|
|
6429
|
+
* True when the session's endpoint authority can no longer be proven current.
|
|
6430
|
+
* Mirrors the reap preflight so force eviction only fires against a genuinely
|
|
6431
|
+
* stale endpoint. When the projection carries a full endpoint identity we prove
|
|
6432
|
+
* staleness against it directly. When the projection identity is missing/corrupt
|
|
6433
|
+
* (or the projection is absent), an absent field is NOT itself proof of a stale
|
|
6434
|
+
* endpoint: a partial write can drop `endpoint_incarnation` while the broker
|
|
6435
|
+
* session is still live. We therefore recover the canonical WAL's own broker
|
|
6436
|
+
* identity and prove absence/mismatch against that authority instead. If neither
|
|
6437
|
+
* the projection nor the WAL yields a usable identity, we return false and leave
|
|
6438
|
+
* the state for the safe repair path rather than destructively evicting a
|
|
6439
|
+
* possibly-live session.
|
|
6440
|
+
*/
|
|
6441
|
+
async function isSessionEndpointStale(session: Record<string, unknown> | null, id: string): Promise<boolean> {
|
|
6442
|
+
const persistedWorkspace = optionalString(session?.broker_workspace);
|
|
6443
|
+
const persistedGeneration =
|
|
6444
|
+
typeof session?.endpoint_generation === "number" &&
|
|
6445
|
+
Number.isSafeInteger(session.endpoint_generation) &&
|
|
6446
|
+
session.endpoint_generation > 0
|
|
6447
|
+
? session.endpoint_generation
|
|
6448
|
+
: null;
|
|
6449
|
+
const persistedIncarnation = optionalString(session?.endpoint_incarnation);
|
|
6450
|
+
if (persistedWorkspace && persistedGeneration !== null && persistedIncarnation)
|
|
6451
|
+
return await brokerEndpointIdentityStale(id, persistedWorkspace, persistedGeneration, persistedIncarnation);
|
|
6452
|
+
// Projection identity is missing — fall back to the canonical WAL authority.
|
|
6453
|
+
const canonicalTransaction = await readSessionTransaction(questionPaths, id);
|
|
6454
|
+
const broker = canonicalTransaction?.canonical.session.broker;
|
|
6455
|
+
const walWorkspace = optionalString(broker?.workspace);
|
|
6456
|
+
const walGeneration =
|
|
6457
|
+
typeof broker?.endpoint_generation === "number" &&
|
|
6458
|
+
Number.isSafeInteger(broker.endpoint_generation) &&
|
|
6459
|
+
broker.endpoint_generation > 0
|
|
6460
|
+
? broker.endpoint_generation
|
|
6461
|
+
: null;
|
|
6462
|
+
const walIncarnation = optionalString(broker?.endpoint_incarnation);
|
|
6463
|
+
if (!walWorkspace || walGeneration === null || !walIncarnation) return false;
|
|
6464
|
+
return await brokerEndpointIdentityStale(id, walWorkspace, walGeneration, walIncarnation);
|
|
6465
|
+
}
|
|
6466
|
+
|
|
6467
|
+
/**
|
|
6468
|
+
* Confirm no turn is active for the session, reading the durable authority
|
|
6469
|
+
* (canonical WAL) and the legacy active-turn projection. `listSessions`
|
|
6470
|
+
* captures reap candidates before the per-session transition lock is held, so
|
|
6471
|
+
* a turn can begin in the gap before eviction — this must be re-checked under
|
|
6472
|
+
* the lock immediately before any irreversible removal.
|
|
6473
|
+
*/
|
|
6474
|
+
async function sessionHasActiveTurn(id: string): Promise<boolean> {
|
|
6475
|
+
const canonicalTransaction = await readSessionTransaction(questionPaths, id);
|
|
6476
|
+
const canonicalActiveTurn = canonicalTransaction
|
|
6477
|
+
? Object.values(canonicalTransaction.canonical.turns).some(turn =>
|
|
6478
|
+
ACTIVE_TURN_STATUSES.has(turn.status as TurnStatus),
|
|
6479
|
+
)
|
|
6480
|
+
: false;
|
|
6481
|
+
return canonicalActiveTurn || (await readActiveTurn(namespaceDir, id)) !== null;
|
|
6482
|
+
}
|
|
6483
|
+
|
|
6484
|
+
/**
|
|
6485
|
+
* Recovery-safe force eviction for a session whose endpoint is stale. A stale
|
|
6486
|
+
* endpoint has no live broker to close against, but the session's durable
|
|
6487
|
+
* footprint must not be orphaned: removing only the projection rows would leak
|
|
6488
|
+
* the canonical WAL, retained public-delivery claims, and the scheduler/index
|
|
6489
|
+
* registry hints. We instead write a durable deletion (retirement) record and
|
|
6490
|
+
* run the same cleanup the normal reap path uses — drain retained deliveries,
|
|
6491
|
+
* remove the WAL (which also deregisters roster/retained scheduler hints),
|
|
6492
|
+
* remove projections, and emit the reaped event. If any step cannot finish yet
|
|
6493
|
+
* the record stays in `cleanup_pending`, so a subsequent reapSession() for this
|
|
6494
|
+
* id (selected by session_id, not deletion_id) resumes and completes it.
|
|
6495
|
+
*/
|
|
6496
|
+
async function forceEvictStaleSession(sessionId: string): Promise<void> {
|
|
6497
|
+
await ensureQuestionStateReady();
|
|
6498
|
+
// Recover the session by id from the canonical WAL — not just the projection.
|
|
6499
|
+
// A crash/partial-write, or a malformed/legacy projection, can leave canonical
|
|
6500
|
+
// state committed while the projection has lost its endpoint_incarnation.
|
|
6501
|
+
// Keying force eviction off the projection alone would then retire only the
|
|
6502
|
+
// projection files and orphan the WAL, retained public-delivery claims,
|
|
6503
|
+
// registry (roster/retained) hints, and scheduler markers indefinitely: after
|
|
6504
|
+
// three stale sweeps the projection disappears and the normal
|
|
6505
|
+
// deletion-intent/completeDeletionCleanup path can no longer select the id.
|
|
6506
|
+
const canonicalTransaction = await readSessionTransaction(questionPaths, sessionId);
|
|
6507
|
+
if (!canonicalTransaction) {
|
|
6508
|
+
// Canonical state confirmed absent (this also covers the malformed/absent
|
|
6509
|
+
// projection case): the only durable footprint left is the projection, so
|
|
6510
|
+
// best-effort projection removal is all that remains.
|
|
6511
|
+
await removeReapedProjection(sessionId, [], []);
|
|
6512
|
+
return;
|
|
6513
|
+
}
|
|
6514
|
+
// Endpoint authority for the durable retirement comes from the canonical WAL,
|
|
6515
|
+
// never the projection: removeSessionTransaction fences the delete on
|
|
6516
|
+
// `canonicalTransaction.endpoint.incarnation`, so a present-but-stale
|
|
6517
|
+
// projection incarnation would make cleanup fail with endpoint_stale and
|
|
6518
|
+
// orphan the WAL, retained deliveries, and registry state. Using the WAL's
|
|
6519
|
+
// own incarnation guarantees the fence matches.
|
|
6520
|
+
const persistedIncarnation = optionalString(canonicalTransaction.endpoint?.incarnation);
|
|
6521
|
+
if (!persistedIncarnation) {
|
|
6522
|
+
// The WAL exists but never observed an endpoint incarnation, so there is
|
|
6523
|
+
// no authority to fence a durable retirement against. Leave the state for
|
|
6524
|
+
// the normal reap path rather than orphaning the WAL with a
|
|
6525
|
+
// projection-only delete.
|
|
6526
|
+
return;
|
|
6527
|
+
}
|
|
6528
|
+
const deletionId = `force-evict:${sessionId}:${persistedIncarnation}`;
|
|
6529
|
+
const deletionKey = createHash("sha256").update(deletionId).digest("hex");
|
|
6530
|
+
const now = new Date().toISOString();
|
|
6531
|
+
const entry: NamespaceDeletionEntryV1 = {
|
|
6532
|
+
deletion_id: deletionId,
|
|
6533
|
+
session_id: sessionId,
|
|
6534
|
+
endpoint_incarnation: persistedIncarnation,
|
|
6535
|
+
operation_id: deletionId,
|
|
6536
|
+
key_digest: deletionKey,
|
|
6537
|
+
request_digest: deletionKey,
|
|
6538
|
+
close_key: deletionId,
|
|
6539
|
+
phase: "cleanup_pending",
|
|
6540
|
+
cleanup: {
|
|
6541
|
+
wal: false,
|
|
6542
|
+
turns: false,
|
|
6543
|
+
reports: false,
|
|
6544
|
+
session: false,
|
|
6545
|
+
events: false,
|
|
6546
|
+
turn_ids: canonicalTransaction ? Object.keys(canonicalTransaction.canonical.turns) : [],
|
|
6547
|
+
report_ids: canonicalTransaction ? Object.keys(canonicalTransaction.canonical.reports) : [],
|
|
6548
|
+
},
|
|
6549
|
+
authority_digest: deletionKey,
|
|
6550
|
+
created_at: now,
|
|
6551
|
+
updated_at: now,
|
|
6552
|
+
};
|
|
6553
|
+
await recordDeletionIntent(questionPaths, entry);
|
|
6554
|
+
await completeDeletionCleanup(entry, "idle_reaper_force_evict", true);
|
|
6555
|
+
}
|
|
6556
|
+
|
|
6214
6557
|
const sessionReaper: SessionReaper = createSessionReaper(
|
|
6215
6558
|
{
|
|
6216
6559
|
listSessions: async (): Promise<ReapableSession[]> => {
|
|
@@ -6261,6 +6604,23 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
|
|
|
6261
6604
|
const result = await reapSession(sessionId, { reason: "idle_reaper" });
|
|
6262
6605
|
if (!result.ok) throw new Error(result.reason ?? "session_reap_failed");
|
|
6263
6606
|
},
|
|
6607
|
+
markSessionDead: async (sessionId: string): Promise<void> => {
|
|
6608
|
+
// Force-evict a session that cannot be reaped normally (endpoint_stale
|
|
6609
|
+
// repeated MAX_REAP_FAILURES times). The candidate was selected before
|
|
6610
|
+
// this transition acquired its per-session lock, so revalidate the
|
|
6611
|
+
// durable authority under the lock: a turn may have started, or the
|
|
6612
|
+
// endpoint may have recovered, since listSessions() ran.
|
|
6613
|
+
await withSessionTransition(sessionId, async () => {
|
|
6614
|
+
if (await sessionHasActiveTurn(sessionId)) return;
|
|
6615
|
+
const session = asRecord(await readJsonFile(sessionFile(sessionId)));
|
|
6616
|
+
// Prove endpoint staleness before any destructive eviction, even when the
|
|
6617
|
+
// projection is absent: isSessionEndpointStale falls back to the canonical
|
|
6618
|
+
// WAL authority to confirm broker absence/mismatch, so a missing or
|
|
6619
|
+
// partially-written projection can no longer force-evict a live broker.
|
|
6620
|
+
if (!(await isSessionEndpointStale(session, sessionId))) return;
|
|
6621
|
+
await forceEvictStaleSession(sessionId);
|
|
6622
|
+
});
|
|
6623
|
+
},
|
|
6264
6624
|
now: () => Date.now(),
|
|
6265
6625
|
},
|
|
6266
6626
|
{ idleTtlMs: config.sessionIdleTtlMs, sweepIntervalMs: config.sessionSweepIntervalMs },
|
|
@@ -7924,7 +8284,8 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
|
|
|
7924
8284
|
};
|
|
7925
8285
|
await reconcileSessionRuntime(canonicalSessionId, { observeQuestions: false });
|
|
7926
8286
|
try {
|
|
7927
|
-
|
|
8287
|
+
const brokerWorkspace = optionalString(session.broker_workspace) ?? cwd;
|
|
8288
|
+
let indexedSession = (await listSessions(brokerWorkspace)).find(
|
|
7928
8289
|
candidate => brokerSessionId(candidate) === canonicalSessionId,
|
|
7929
8290
|
);
|
|
7930
8291
|
// Windows broker locators may differ in drive-letter casing or separator
|
|
@@ -7932,7 +8293,7 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
|
|
|
7932
8293
|
// the coordinator path. The scoped listing request is still authoritative;
|
|
7933
8294
|
// only its local path filter is relaxed for the exact requested session.
|
|
7934
8295
|
if (!indexedSession && platform === "win32") {
|
|
7935
|
-
const listing = await paginatedBrokerSessionList(
|
|
8296
|
+
const listing = await paginatedBrokerSessionList(brokerWorkspace, { cwd: brokerWorkspace });
|
|
7936
8297
|
indexedSession = jsonRecords(Array.isArray(listing.sessions) ? listing.sessions : []).find(
|
|
7937
8298
|
candidate => brokerSessionId(candidate) === canonicalSessionId,
|
|
7938
8299
|
);
|
|
@@ -7987,8 +8348,14 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
|
|
|
7987
8348
|
}
|
|
7988
8349
|
if (name === "gjc_coordinator_list_questions") return await listQuestions(args);
|
|
7989
8350
|
if (name === "gjc_coordinator_list_artifacts") return { ok: true, roots: config.allowedRoots };
|
|
7990
|
-
if (name === "gjc_coordinator_read_artifact")
|
|
8351
|
+
if (name === "gjc_coordinator_read_artifact") {
|
|
8352
|
+
// Advertised capability (tools/list) and refusal must agree on the resolved
|
|
8353
|
+
// platform. This can only add a refusal: safeOpenCoordinatorArtifact keeps
|
|
8354
|
+
// the host-real capability and /proc/self/fd identity checks.
|
|
8355
|
+
if (!coordinatorArtifactCapability(platform).available)
|
|
8356
|
+
return publicError(new Error("artifact_unavailable"));
|
|
7991
8357
|
return await readCoordinatorArtifact(config, { path: args.path });
|
|
8358
|
+
}
|
|
7992
8359
|
if (name === "gjc_coordinator_read_coordination_status") {
|
|
7993
8360
|
const scopedSessionId = args.session_id == null ? null : safeExternalId("session", args.session_id);
|
|
7994
8361
|
if (scopedSessionId) await ensureQuestionTransaction(scopedSessionId);
|
|
@@ -9041,6 +9408,7 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
|
|
|
9041
9408
|
session = normalizeSession({
|
|
9042
9409
|
session_id: sessionId,
|
|
9043
9410
|
cwd: sessionCwd,
|
|
9411
|
+
ephemeral: true,
|
|
9044
9412
|
...(mpresetResolution.mpreset ? { mpreset: mpresetResolution.mpreset } : {}),
|
|
9045
9413
|
...(modelResolution.model ? { model: modelResolution.model } : {}),
|
|
9046
9414
|
broker_workspace: binding.workspace,
|
|
@@ -9758,10 +10126,9 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
|
|
|
9758
10126
|
|
|
9759
10127
|
const admission = await reconcileSessionRuntime(sessionId, { observeQuestions: true });
|
|
9760
10128
|
const admissionToken = admission.waiting_token;
|
|
9761
|
-
const
|
|
9762
|
-
admission.session_state?.source === "agent_session_event" &&
|
|
9763
|
-
|
|
9764
|
-
admission.session_state.state === "needs_user_input";
|
|
10129
|
+
const agentRuntimeAdmissionRequired =
|
|
10130
|
+
admission.session_state?.source === "agent_session_event" && admission.session_state.live === true;
|
|
10131
|
+
const liveAdmissionRequired = admission.session_state?.state === "needs_user_input";
|
|
9765
10132
|
if (liveAdmissionRequired && !admissionToken)
|
|
9766
10133
|
return {
|
|
9767
10134
|
ok: false,
|
|
@@ -9778,6 +10145,7 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
|
|
|
9778
10145
|
};
|
|
9779
10146
|
let translated: unknown;
|
|
9780
10147
|
let session: Record<string, unknown> | null = null;
|
|
10148
|
+
let liveInFlightAnswerAdmitted = false;
|
|
9781
10149
|
const claimed = await withAdmittedSessionTransaction(questionPaths, sessionId, async transaction => {
|
|
9782
10150
|
const liveState = await readSessionState(namespaceDir, sessionId);
|
|
9783
10151
|
const question = transaction.canonical.questions[questionId];
|
|
@@ -9858,10 +10226,30 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
|
|
|
9858
10226
|
authority?.observation.kind === "valid"
|
|
9859
10227
|
? authority.observation.first_provenance.runtime_turn_id
|
|
9860
10228
|
: null;
|
|
10229
|
+
const liveInFlightAskOwned = Boolean(
|
|
10230
|
+
liveState?.schema_version === 1 &&
|
|
10231
|
+
liveState.session_id === sessionId &&
|
|
10232
|
+
liveState?.source === "agent_session_event" &&
|
|
10233
|
+
liveState.live === true &&
|
|
10234
|
+
liveState.state === "running" &&
|
|
10235
|
+
liveState.current_turn_id === turnId &&
|
|
10236
|
+
Number.isFinite(Date.parse(liveState.updated_at)) &&
|
|
10237
|
+
hasExactRuntimeWriterIdentity(liveState, transaction.canonical.session.broker) &&
|
|
10238
|
+
canonicalTurn &&
|
|
10239
|
+
ACTIVE_TURN_STATUSES.has(canonicalTurn.status as TurnStatus) &&
|
|
10240
|
+
hasAcceptedRuntimeReceipt(canonicalTurn) &&
|
|
10241
|
+
authority?.outcome?.state === "pending" &&
|
|
10242
|
+
authority.outcome.turn_id === turnId &&
|
|
10243
|
+
authority.observation.kind === "valid" &&
|
|
10244
|
+
runtimeTurnId === canonicalTurn.delivery.runtime_turn_id,
|
|
10245
|
+
);
|
|
9861
10246
|
const liveWaitingOwned =
|
|
9862
|
-
liveState?.source !== "agent_session_event"
|
|
9863
|
-
|
|
9864
|
-
|
|
10247
|
+
liveState?.source !== "agent_session_event"
|
|
10248
|
+
? true
|
|
10249
|
+
: liveState.live === true &&
|
|
10250
|
+
((liveState.state === "needs_user_input" && liveState.current_turn_id === turnId) ||
|
|
10251
|
+
liveInFlightAskOwned);
|
|
10252
|
+
liveInFlightAnswerAdmitted = liveInFlightAskOwned;
|
|
9865
10253
|
const provenanceMatches =
|
|
9866
10254
|
admissionToken === null ||
|
|
9867
10255
|
(Boolean(canonicalTurn?.runtime_provenance) &&
|
|
@@ -9873,7 +10261,8 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
|
|
|
9873
10261
|
!liveWaitingOwned ||
|
|
9874
10262
|
(liveState?.source === "agent_session_event" &&
|
|
9875
10263
|
liveState.live === true &&
|
|
9876
|
-
canonicalTurn?.status !== "waiting_for_answer"
|
|
10264
|
+
canonicalTurn?.status !== "waiting_for_answer" &&
|
|
10265
|
+
!liveInFlightAskOwned) ||
|
|
9877
10266
|
!authority ||
|
|
9878
10267
|
authority.observation.kind !== "valid" ||
|
|
9879
10268
|
!canonicalTurn ||
|
|
@@ -9985,17 +10374,103 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
|
|
|
9985
10374
|
const latestAdmission = await reconcileSessionRuntime(sessionId, { observeQuestions: true });
|
|
9986
10375
|
const latestState = latestAdmission.session_state;
|
|
9987
10376
|
const latestToken = latestAdmission.waiting_token;
|
|
9988
|
-
const
|
|
9989
|
-
|
|
9990
|
-
|
|
9991
|
-
|
|
9992
|
-
if (
|
|
9993
|
-
|
|
9994
|
-
|
|
10377
|
+
const latestQuestions = await reconcileQuestions(sessionId);
|
|
10378
|
+
const latestSession = asRecord(await readJsonFile(sessionFile(sessionId)));
|
|
10379
|
+
const latestSnapshot = latestSession ? await readCompleteQ12Snapshot(latestSession) : null;
|
|
10380
|
+
let latestFreshGate: (WorkflowGateQueryRecord & WorkflowGate) | undefined;
|
|
10381
|
+
if (latestSnapshot?.complete === true) {
|
|
10382
|
+
latestFreshGate = (latestSnapshot.items as unknown[]).find(row => {
|
|
10383
|
+
if (!row || typeof row !== "object") return false;
|
|
10384
|
+
const record = row as Record<string, unknown>;
|
|
10385
|
+
return record.tag === "pending" && record.gate_id === gateId;
|
|
10386
|
+
}) as (WorkflowGateQueryRecord & WorkflowGate) | undefined;
|
|
10387
|
+
}
|
|
10388
|
+
const latestQ12Proof =
|
|
10389
|
+
latestQuestions.reconciliation.complete &&
|
|
10390
|
+
latestSnapshot?.complete === true &&
|
|
10391
|
+
latestFreshGate !== undefined &&
|
|
10392
|
+
(await withSessionTransaction(questionPaths, sessionId, async transaction => {
|
|
10393
|
+
const canonicalQuestion = transaction.canonical.questions[questionId];
|
|
10394
|
+
const canonicalTurn = transaction.canonical.turns[turnId];
|
|
10395
|
+
const authority = canonicalQuestion
|
|
10396
|
+
? transaction.canonical.gate_authorities[canonicalQuestion.authority_id]
|
|
10397
|
+
: undefined;
|
|
10398
|
+
if (!canonicalQuestion || !canonicalTurn || !authority || !latestFreshGate) return false;
|
|
10399
|
+
const freshProvenance = runtimeProvenanceToken(
|
|
10400
|
+
sessionId,
|
|
10401
|
+
transaction,
|
|
10402
|
+
canonicalTurn,
|
|
10403
|
+
latestFreshGate,
|
|
10404
|
+
);
|
|
10405
|
+
return Boolean(
|
|
10406
|
+
canonicalQuestion.session_id === sessionId &&
|
|
10407
|
+
canonicalQuestion.turn_id === turnId &&
|
|
10408
|
+
(canonicalQuestion.status === "pending" || canonicalQuestion.status === "resolving") &&
|
|
10409
|
+
ACTIVE_TURN_STATUSES.has(canonicalTurn.status as TurnStatus) &&
|
|
10410
|
+
hasAcceptedRuntimeReceipt(canonicalTurn) &&
|
|
10411
|
+
latestFreshGate.gate_id === authority.authority.gate_id &&
|
|
10412
|
+
latestFreshGate.runtime_turn_id === canonicalTurn.delivery.runtime_turn_id &&
|
|
10413
|
+
authority.outcome.state === "pending" &&
|
|
10414
|
+
authority.outcome.turn_id === turnId &&
|
|
10415
|
+
authority.outcome.question_id === questionId &&
|
|
10416
|
+
authority.observation.kind === "valid" &&
|
|
10417
|
+
canonicalTurn.runtime_provenance &&
|
|
10418
|
+
canonicalJson(authority.observation.first_provenance) ===
|
|
10419
|
+
canonicalJson(freshProvenance) &&
|
|
10420
|
+
canonicalJson(canonicalTurn.runtime_provenance) === canonicalJson(freshProvenance) &&
|
|
10421
|
+
!canonicalTurn.terminal_fence,
|
|
10422
|
+
);
|
|
10423
|
+
}));
|
|
10424
|
+
const latestLiveInFlightAsk =
|
|
10425
|
+
!latestAdmission.terminal &&
|
|
10426
|
+
latestAdmission.active_turn_id === turnId &&
|
|
10427
|
+
latestQ12Proof &&
|
|
10428
|
+
(await withSessionTransaction(questionPaths, sessionId, async transaction => {
|
|
10429
|
+
const runtimeState = await readSessionState(namespaceDir, sessionId);
|
|
10430
|
+
const canonicalQuestion = transaction.canonical.questions[questionId];
|
|
10431
|
+
const canonicalTurn = transaction.canonical.turns[turnId];
|
|
10432
|
+
const authority = canonicalQuestion
|
|
10433
|
+
? transaction.canonical.gate_authorities[canonicalQuestion.authority_id]
|
|
10434
|
+
: undefined;
|
|
10435
|
+
const authorityRuntimeTurnId =
|
|
10436
|
+
authority?.observation.kind === "valid"
|
|
10437
|
+
? authority.observation.first_provenance.runtime_turn_id
|
|
10438
|
+
: null;
|
|
10439
|
+
return Boolean(
|
|
10440
|
+
runtimeState?.schema_version === 1 &&
|
|
10441
|
+
runtimeState.session_id === sessionId &&
|
|
10442
|
+
runtimeState.source === "agent_session_event" &&
|
|
10443
|
+
runtimeState.live === true &&
|
|
10444
|
+
runtimeState.state === "running" &&
|
|
10445
|
+
runtimeState.current_turn_id === turnId &&
|
|
10446
|
+
Number.isFinite(Date.parse(runtimeState.updated_at)) &&
|
|
10447
|
+
hasExactRuntimeWriterIdentity(runtimeState, transaction.canonical.session.broker) &&
|
|
10448
|
+
canonicalTurn &&
|
|
10449
|
+
ACTIVE_TURN_STATUSES.has(canonicalTurn.status as TurnStatus) &&
|
|
10450
|
+
hasAcceptedRuntimeReceipt(canonicalTurn) &&
|
|
10451
|
+
canonicalQuestion &&
|
|
10452
|
+
(canonicalQuestion.status === "pending" || canonicalQuestion.status === "resolving") &&
|
|
10453
|
+
authority?.outcome.state === "pending" &&
|
|
10454
|
+
authority.outcome.turn_id === turnId &&
|
|
10455
|
+
authority.observation.kind === "valid" &&
|
|
10456
|
+
canonicalTurn.delivery.runtime_turn_id === authorityRuntimeTurnId &&
|
|
10457
|
+
canonicalTurn.runtime_provenance &&
|
|
10458
|
+
canonicalJson(canonicalTurn.runtime_provenance) ===
|
|
10459
|
+
canonicalJson(authority.observation.first_provenance) &&
|
|
10460
|
+
!canonicalTurn.terminal_fence,
|
|
10461
|
+
);
|
|
10462
|
+
}));
|
|
10463
|
+
const latestAgentRuntimeAdmitted =
|
|
10464
|
+
!agentRuntimeAdmissionRequired ||
|
|
9995
10465
|
(latestState?.source === "agent_session_event" &&
|
|
9996
10466
|
latestState.live === true &&
|
|
9997
|
-
|
|
9998
|
-
|
|
10467
|
+
latestState.current_turn_id === turnId &&
|
|
10468
|
+
((liveInFlightAnswerAdmitted && latestState.state === "running" && latestLiveInFlightAsk) ||
|
|
10469
|
+
(!liveInFlightAnswerAdmitted &&
|
|
10470
|
+
latestState.state === "needs_user_input" &&
|
|
10471
|
+
latestToken?.coordinator_turn_id === turnId &&
|
|
10472
|
+
latestQ12Proof)));
|
|
10473
|
+
if (latestAdmission.terminal || !latestQ12Proof || !latestAgentRuntimeAdmitted) {
|
|
9999
10474
|
// No remote call has occurred. Undo the durable dispatch claim so this
|
|
10000
10475
|
// outer request remains retryable rather than stranding the question in
|
|
10001
10476
|
// remote_started/resolving.
|
|
@@ -10510,7 +10985,11 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
|
|
|
10510
10985
|
return { jsonrpc: "2.0", id, result: {} };
|
|
10511
10986
|
}
|
|
10512
10987
|
if (request.method === "tools/list") {
|
|
10513
|
-
return {
|
|
10988
|
+
return {
|
|
10989
|
+
jsonrpc: "2.0",
|
|
10990
|
+
id,
|
|
10991
|
+
result: { tools: COORDINATOR_MCP_TOOL_NAMES.map(name => toolSchema(name, platform)) },
|
|
10992
|
+
};
|
|
10514
10993
|
}
|
|
10515
10994
|
if (request.method === "prompts/list") {
|
|
10516
10995
|
return { jsonrpc: "2.0", id, result: { prompts: [] } };
|
|
@@ -10564,7 +11043,11 @@ export async function handleCoordinatorMcpRequest(
|
|
|
10564
11043
|
};
|
|
10565
11044
|
}
|
|
10566
11045
|
if (request.method === "tools/list") {
|
|
10567
|
-
return {
|
|
11046
|
+
return {
|
|
11047
|
+
jsonrpc: "2.0",
|
|
11048
|
+
id: request.id ?? null,
|
|
11049
|
+
result: { tools: COORDINATOR_MCP_TOOL_NAMES.map(name => toolSchema(name, process.platform)) },
|
|
11050
|
+
};
|
|
10568
11051
|
}
|
|
10569
11052
|
if (request.method === "prompts/list") {
|
|
10570
11053
|
return { jsonrpc: "2.0", id: request.id ?? null, result: { prompts: [] } };
|