@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
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { NativeBrokerRestartIntent, NativeBrokerRestartIntentIdentity } from "@gajae-code/natives";
|
|
1
2
|
import { processIncarnation } from "./process-incarnation";
|
|
2
3
|
import { SDK_STATE_VERSION } from "./state-version";
|
|
3
4
|
export type BrokerPublicationObservation = "owned" | "absent" | "replaced" | "ambiguous";
|
|
@@ -6,12 +7,30 @@ export interface RetainedBrokerDiscovery {
|
|
|
6
7
|
observeAsync(): Promise<BrokerPublicationObservation>;
|
|
7
8
|
heartbeat(heartbeatAt: number): Promise<boolean>;
|
|
8
9
|
close(): void;
|
|
10
|
+
/** Owner-side `prepared`: exclusively creates the durable restart-intent slot. */
|
|
11
|
+
prepareRestartIntent(intent: NativeBrokerRestartIntent): Promise<boolean>;
|
|
12
|
+
/** Owner-side `prepared` -> `committed`: rewrites the exact slot prepare created. */
|
|
13
|
+
commitRestartIntent(intent: NativeBrokerRestartIntent): Promise<boolean>;
|
|
14
|
+
/** Owner-side cancel of this process's own prepared/committed slot. */
|
|
15
|
+
cancelRestartIntent(): Promise<boolean>;
|
|
16
|
+
/** Successor-side clear of a predecessor's slot this process never prepared. */
|
|
17
|
+
clearForeignRestartIntent(identity: NativeBrokerRestartIntentIdentity): Promise<boolean>;
|
|
9
18
|
}
|
|
10
19
|
export declare const BROKER_HEARTBEAT_TTL_MS = 15000;
|
|
11
20
|
export interface BrokerDiscovery {
|
|
12
21
|
version: typeof SDK_STATE_VERSION;
|
|
13
22
|
protocolVersion: 3;
|
|
14
23
|
packageGeneration: string;
|
|
24
|
+
/**
|
|
25
|
+
* The runtime image this broker launches internal SDK processes with. Present
|
|
26
|
+
* whenever the publisher could classify its own runtime, including when that
|
|
27
|
+
* image was already gone at publication time -- absence is the reader's
|
|
28
|
+
* verdict to reach, not a reason to publish nothing. Absent only for records
|
|
29
|
+
* written before this field existed and for a publisher whose runtime
|
|
30
|
+
* evidence could not be classified at all; readers then reuse the broker as
|
|
31
|
+
* before rather than retiring it on unknown evidence.
|
|
32
|
+
*/
|
|
33
|
+
runtime?: string;
|
|
15
34
|
ownerId: string;
|
|
16
35
|
pid: number;
|
|
17
36
|
incarnation: string;
|
|
@@ -21,6 +40,14 @@ export interface BrokerDiscovery {
|
|
|
21
40
|
token: string;
|
|
22
41
|
startedAt: number;
|
|
23
42
|
heartbeatAt: number;
|
|
43
|
+
restartRequestId?: string;
|
|
44
|
+
rootDigest?: string;
|
|
45
|
+
}
|
|
46
|
+
export interface BrokerRestartIntent {
|
|
47
|
+
requestId: string;
|
|
48
|
+
lease: string;
|
|
49
|
+
expiresAt: number;
|
|
50
|
+
phase?: "prepared" | "committed" | "cancelled";
|
|
24
51
|
}
|
|
25
52
|
export type RedactedBrokerDiscovery = Omit<BrokerDiscovery, "token"> & {
|
|
26
53
|
token: "[redacted]";
|
|
@@ -38,4 +65,15 @@ export declare function publishBrokerDiscovery(agentDir: string, discovery: Brok
|
|
|
38
65
|
/** Heartbeat through retained authority, or the explicit Windows compatibility path. */
|
|
39
66
|
export declare function heartbeatBrokerDiscoveryRetained(publication: RetainedBrokerDiscovery, heartbeatAt: number): Promise<boolean>;
|
|
40
67
|
export declare function readBrokerDiscovery(agentDir: string, ttlMs?: number): Promise<BrokerDiscovery | null>;
|
|
68
|
+
/**
|
|
69
|
+
* The restart-intent slot lives directly beneath `sdk/`, independent of
|
|
70
|
+
* `sdk/broker.lock/` -- so it durably survives a stale-lock reclaim that
|
|
71
|
+
* renames the incumbent's lock directory aside (`Broker#reclaimStaleLock`).
|
|
72
|
+
*/
|
|
73
|
+
export declare const brokerRestartIntentPath: (agentDir: string) => string;
|
|
74
|
+
export interface BrokerRestartIntentWithIdentity extends BrokerRestartIntent {
|
|
75
|
+
/** Existing platform file identity, for a successor's exact-identity clear. */
|
|
76
|
+
identity: NativeBrokerRestartIntentIdentity;
|
|
77
|
+
}
|
|
78
|
+
export declare function readBrokerRestartIntent(agentDir: string): Promise<BrokerRestartIntentWithIdentity | null>;
|
|
41
79
|
export declare function redactBrokerDiscovery(discovery: BrokerDiscovery): RedactedBrokerDiscovery;
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { SdkClient } from "../client/client";
|
|
2
|
+
import type { BrokerRestartOwnerIdentity, BrokerRestartPrepareResult, BrokerRestartResult } from "./broker";
|
|
3
|
+
import { type BrokerDiscovery } from "./discovery";
|
|
4
|
+
export interface DoctorBrokerRestartOptions {
|
|
5
|
+
agentDir: string;
|
|
6
|
+
deadlineMs?: number;
|
|
7
|
+
drain?: boolean;
|
|
8
|
+
requestId?: string;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Concrete typed before/after-effect outcome of a full D8 broker restart
|
|
12
|
+
* attempt. Every refusal names the exact stage and, where the broker itself
|
|
13
|
+
* refused, the broker's own typed error code -- never an opaque thrown
|
|
14
|
+
* message standing in for a decision this adapter could name.
|
|
15
|
+
*/
|
|
16
|
+
export type DoctorBrokerRestartOutcome = {
|
|
17
|
+
kind: "restarted";
|
|
18
|
+
requestId: string;
|
|
19
|
+
oldOwner: BrokerRestartOwnerIdentity;
|
|
20
|
+
successor: BrokerDiscovery;
|
|
21
|
+
/** True when an already-committed successor for this exact request was found and reused, not spawned. */
|
|
22
|
+
adopted: boolean;
|
|
23
|
+
} | {
|
|
24
|
+
kind: "owner_unavailable";
|
|
25
|
+
reason: "no_discovery" | "owner_not_confirmed_live";
|
|
26
|
+
} | {
|
|
27
|
+
kind: "prepare_refused";
|
|
28
|
+
code: string;
|
|
29
|
+
message: string;
|
|
30
|
+
} | {
|
|
31
|
+
kind: "commit_refused";
|
|
32
|
+
code: string;
|
|
33
|
+
message: string;
|
|
34
|
+
} | {
|
|
35
|
+
kind: "old_owner_exit_timeout";
|
|
36
|
+
requestId: string;
|
|
37
|
+
oldOwner: BrokerRestartOwnerIdentity;
|
|
38
|
+
} | {
|
|
39
|
+
kind: "successor_refused";
|
|
40
|
+
requestId: string;
|
|
41
|
+
oldOwner: BrokerRestartOwnerIdentity;
|
|
42
|
+
reason: "deadline_elapsed" | "intent_not_committed" | "spawn_failed" | "spawn_exited_before_publication" | "publication_timeout";
|
|
43
|
+
detail?: string;
|
|
44
|
+
};
|
|
45
|
+
export declare function prepareDoctorBrokerRestart(client: SdkClient, discovery: BrokerDiscovery, options: DoctorBrokerRestartOptions & {
|
|
46
|
+
requestId: string;
|
|
47
|
+
}): Promise<BrokerRestartResult>;
|
|
48
|
+
export declare function commitDoctorBrokerRestart(client: SdkClient, discovery: BrokerDiscovery, prepared: BrokerRestartPrepareResult, requestId: string): Promise<BrokerRestartResult>;
|
|
49
|
+
export declare function cancelDoctorBrokerRestart(client: SdkClient, requestId: string): Promise<BrokerRestartResult>;
|
|
50
|
+
/**
|
|
51
|
+
* Owner-side prepare/commit/stop, positive-exit-confirmed cleanup, then
|
|
52
|
+
* exactly one successor launch or adoption. The exact sequence:
|
|
53
|
+
*
|
|
54
|
+
* 1. Read the CURRENT discovery snapshot (never a stale caller-supplied one)
|
|
55
|
+
* and prove its owner is live via a real connected client, not an
|
|
56
|
+
* incarnation string comparison alone.
|
|
57
|
+
* 2. `prepare_restart` against that exact owner tuple/generation; on any
|
|
58
|
+
* prepare refusal, cancel the reservation and return the broker's own
|
|
59
|
+
* typed refusal rather than throwing.
|
|
60
|
+
* 3. `commit_restart` with the exact lease/occupancyEpoch prepare returned.
|
|
61
|
+
* Once committed, the owner is durably retiring: cancellation is no longer
|
|
62
|
+
* safe and this function must never call cancel past this point.
|
|
63
|
+
* 4. Close the client (every path below runs with the client closed).
|
|
64
|
+
* 5. Wait, bounded by the request deadline, for `observeProcessIncarnation`
|
|
65
|
+
* to positively confirm the exact old pid is OS-absent -- never inferred
|
|
66
|
+
* from a changed/missing incarnation read, and never from discovery
|
|
67
|
+
* disappearing (a crashed writer can leave a stale discovery file behind
|
|
68
|
+
* just as easily as a clean exit can remove it).
|
|
69
|
+
* 6. Launch (or adopt an already-published) exactly one successor for this
|
|
70
|
+
* requestId, holding the shared startup exclusion only around the launch
|
|
71
|
+
* decision itself -- never across the successor's own publication wait,
|
|
72
|
+
* which would deadlock this process against its own spawned child.
|
|
73
|
+
*
|
|
74
|
+
* The successor clears the durable committed intent itself, from inside its
|
|
75
|
+
* own `Broker#start()`, only after its own publication has independently
|
|
76
|
+
* proven it owns the root -- never here, and never before that proof exists.
|
|
77
|
+
*/
|
|
78
|
+
export declare function restartBrokerForDoctor(options: DoctorBrokerRestartOptions): Promise<DoctorBrokerRestartOutcome>;
|
|
79
|
+
export type { BrokerRestartResult };
|
|
@@ -13,6 +13,13 @@ export type IndexedEndpointAuthority = {
|
|
|
13
13
|
endpointMtimeMs?: number;
|
|
14
14
|
endpointFileId?: string;
|
|
15
15
|
};
|
|
16
|
+
/** Derive the broker-compatible opaque authority for one indexed endpoint generation. */
|
|
17
|
+
export declare function endpointIncarnation(record: {
|
|
18
|
+
endpointGeneration: number;
|
|
19
|
+
endpointMtimeMs?: number;
|
|
20
|
+
pid: number;
|
|
21
|
+
endpointFileId?: string;
|
|
22
|
+
}, sessionId: string): string | undefined;
|
|
16
23
|
/**
|
|
17
24
|
* Reads one endpoint from the descriptor opened for its pathname. The opened
|
|
18
25
|
* object must remain a small, singly-linked regular file with stable identity
|
|
@@ -4,8 +4,22 @@ import { type FileLockOptions } from "../../config/file-lock";
|
|
|
4
4
|
import { type BrokerDiscovery } from "./discovery";
|
|
5
5
|
import { type SdkInternalSpawnCommand } from "./runtime";
|
|
6
6
|
export declare function isBrokerGenerationCompatible(discovery: BrokerDiscovery | null): boolean;
|
|
7
|
+
/**
|
|
8
|
+
* A live incumbent stops being reusable once the runtime image it launches
|
|
9
|
+
* internal processes with is provably gone. A broker outlives its own executable
|
|
10
|
+
* (a package manager replacing the interpreter it was started from, an install
|
|
11
|
+
* directory swapped underneath it): it keeps heartbeating and answering
|
|
12
|
+
* requests, but every `session.create` it accepts is refused at spawn time.
|
|
13
|
+
* Retiring it here repairs the endpoint before a caller reaches session
|
|
14
|
+
* creation. Absent runtime evidence -- a record predating the field, or a
|
|
15
|
+
* publisher that could not classify its own runtime at all -- and evidence this
|
|
16
|
+
* caller merely cannot inspect both keep the incumbent reusable.
|
|
17
|
+
*/
|
|
18
|
+
export declare function isBrokerReusable(discovery: BrokerDiscovery | null): Promise<boolean>;
|
|
7
19
|
export interface EnsureBrokerSettings {
|
|
8
20
|
agentDir: string;
|
|
21
|
+
/** Only an authorized doctor successor may consume a restart-intent publication. */
|
|
22
|
+
restartRequestId?: string;
|
|
9
23
|
heartbeatTtlMs?: number;
|
|
10
24
|
/**
|
|
11
25
|
* Environment for the spawned detached broker. Defaults to `process.env`; tests
|
|
@@ -233,6 +233,8 @@ export declare function probePublishedReadyAuthorityForTest(root: string, id: st
|
|
|
233
233
|
export declare function removeOwnedLifecycleArtifactsForTest(root: string, id: string, expected: EffectMarker): Promise<boolean>;
|
|
234
234
|
/** Test seam for the worktree occupancy boundary. */
|
|
235
235
|
export declare function worktreeOccupantForTest(sessions: IndexedSession[], worktreePath: string, observe?: (pid: number, expectedIncarnation: string | undefined) => ProcessObservation): string | null;
|
|
236
|
+
/** Test seam for the forced stale-worktree release boundary. */
|
|
237
|
+
export declare function releaseForcedStaleWorktreeForTest(broker: Broker, id: string, expected: IndexedSession): Promise<void>;
|
|
236
238
|
export declare function canonicalDeleteLocatorPath(pathname: string): string;
|
|
237
239
|
export interface LifecycleExecutionOutcome {
|
|
238
240
|
response: BrokerResponse;
|
|
@@ -11,6 +11,56 @@ export interface ProcessIncarnationOptions {
|
|
|
11
11
|
export declare function parseDarwinProcessIncarnation(info: Uint8Array): string | undefined;
|
|
12
12
|
/** Whether `value` is a canonical process-incarnation string (`linux:`/`darwin:`/`windows:`). */
|
|
13
13
|
export declare function isProcessIncarnation(value: unknown): value is string;
|
|
14
|
+
/**
|
|
15
|
+
* Read-only, non-mutating observation of whether a pid currently names a
|
|
16
|
+
* verifiable process incarnation.
|
|
17
|
+
*
|
|
18
|
+
* - `status: "present"` carries the exact kernel-reported identity evidence
|
|
19
|
+
* (the same value `processIncarnation` returns).
|
|
20
|
+
* - `status: "absent"` is positive OS-confirmed death proof (POSIX
|
|
21
|
+
* `kill(pid, 0)` returning `ESRCH`, or Windows `OpenProcess` failing with
|
|
22
|
+
* the exact code the kernel reports for an unresolved client id) — never a
|
|
23
|
+
* permission failure or an ambiguous disappearance.
|
|
24
|
+
* - `status: "unknown"` covers every inconclusive case (invalid pid, missing
|
|
25
|
+
* or unavailable native addon, permission denial, platform limitation).
|
|
26
|
+
* Callers MUST NOT treat this as death proof.
|
|
27
|
+
*/
|
|
28
|
+
export type ProcessIncarnationObservation = {
|
|
29
|
+
status: "present";
|
|
30
|
+
incarnation: string;
|
|
31
|
+
} | {
|
|
32
|
+
status: "absent";
|
|
33
|
+
} | {
|
|
34
|
+
status: "unknown";
|
|
35
|
+
reasonCode: string;
|
|
36
|
+
};
|
|
37
|
+
/** Raw shape returned by the native `Process.observe` binding. */
|
|
38
|
+
export type NativeProcessObserveResult = {
|
|
39
|
+
status?: unknown;
|
|
40
|
+
incarnation?: unknown;
|
|
41
|
+
reasonCode?: unknown;
|
|
42
|
+
};
|
|
43
|
+
export interface ObserveProcessIncarnationOptions {
|
|
44
|
+
/**
|
|
45
|
+
* Injectable native observe function, used by tests to exercise error
|
|
46
|
+
* classification (malformed shapes, unavailable addon) without a global
|
|
47
|
+
* mock. Defaults to the real `Process.observe` binding.
|
|
48
|
+
*/
|
|
49
|
+
observe?: (pid: number) => NativeProcessObserveResult;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Read-only process-incarnation observation for `pid`, distinguishing a
|
|
53
|
+
* proved-live incarnation from positive OS-confirmed absence from every
|
|
54
|
+
* inconclusive outcome. Never signals, kills, reaps, waits on, or spawns any
|
|
55
|
+
* process — it only reads existing native process-table state via
|
|
56
|
+
* `Process.observe`.
|
|
57
|
+
*
|
|
58
|
+
* `pid` must be a positive safe integer; any other value (including `0`,
|
|
59
|
+
* negative, `NaN`, or non-integer) is reported as `unknown` rather than
|
|
60
|
+
* risking an ambiguous platform-specific interpretation (`0`/negative PIDs
|
|
61
|
+
* address process groups on POSIX, not a single process).
|
|
62
|
+
*/
|
|
63
|
+
export declare function observeProcessIncarnation(pid: number, options?: ObserveProcessIncarnationOptions): ProcessIncarnationObservation;
|
|
14
64
|
/** A PID is reusable; bind it to the strongest OS-provided process start incarnation available. */
|
|
15
65
|
export declare function processIncarnation(pid: number, options?: ProcessIncarnationOptions): string | undefined;
|
|
16
66
|
export {};
|
|
@@ -26,8 +26,34 @@ export interface SdkInternalRuntimeDescriptorTestOptions {
|
|
|
26
26
|
configPath?: string;
|
|
27
27
|
bunAvailable?: boolean;
|
|
28
28
|
}
|
|
29
|
+
/**
|
|
30
|
+
* The on-disk image internal SDK spawns from this process execute, or
|
|
31
|
+
* `undefined` only when runtime evidence cannot be classified at all. It comes
|
|
32
|
+
* from the same marker authority a spawn uses, but deliberately not from the
|
|
33
|
+
* source-tree assets a spawn also proves (CLI entrypoint, isolated Bun config,
|
|
34
|
+
* package identity): a failure to read one of those says nothing about the
|
|
35
|
+
* runtime image, and publishing `undefined` for it would blind every reader.
|
|
36
|
+
*
|
|
37
|
+
* An image the classification names but this process can no longer prove is
|
|
38
|
+
* still published. `undefined` means only "no evidence" -- readers keep such a
|
|
39
|
+
* broker reusable, so a broker whose own image vanished before it published
|
|
40
|
+
* must not hide behind it.
|
|
41
|
+
*/
|
|
42
|
+
export declare function sdkInternalRuntimeImage(): string | undefined;
|
|
43
|
+
/**
|
|
44
|
+
* Whether a published runtime image is still a regular file on disk. A resident broker keeps
|
|
45
|
+
* answering requests after the executable it was started from is deleted (a
|
|
46
|
+
* Homebrew Bun upgrade removing the Cellar path, an install directory swapped
|
|
47
|
+
* underneath it), yet every internal spawn it then attempts is refused. Callers
|
|
48
|
+
* use this to retire such an incumbent before a session launch reaches it, so it
|
|
49
|
+
* answers `true` on every inconclusive outcome, including a probe that outlives
|
|
50
|
+
* its bound: a false retirement kills a healthy broker.
|
|
51
|
+
*/
|
|
52
|
+
export declare function isSdkInternalRuntimeImagePresent(file: string): Promise<boolean>;
|
|
29
53
|
/** Resolve the production descriptor from the statically imported marker and current Bun runtime evidence. */
|
|
30
54
|
export declare function resolveSdkInternalSpawnCommand(action: SdkInternalAction): SdkInternalSpawnCommand;
|
|
31
55
|
/** Test hook: injects runtime evidence without weakening the production marker authority. */
|
|
32
56
|
export declare function resolveSdkInternalSpawnCommandForTest(action: SdkInternalAction, options: SdkInternalRuntimeDescriptorTestOptions): SdkInternalSpawnCommand;
|
|
57
|
+
/** Test hook: the publication decision under injected evidence, same authority as production. */
|
|
58
|
+
export declare function sdkInternalRuntimeImageForTest(options: SdkInternalRuntimeDescriptorTestOptions): string | undefined;
|
|
33
59
|
export {};
|
|
@@ -77,6 +77,13 @@ export interface SessionIndexEvent {
|
|
|
77
77
|
endpointFileId?: string;
|
|
78
78
|
lifecycleRequestId?: string;
|
|
79
79
|
terminalUncertain?: boolean;
|
|
80
|
+
/**
|
|
81
|
+
* Distinguishes the terminal-uncertain claim written by a forced stop of a
|
|
82
|
+
* stale-endpoint session from the one written by the fail-closed tail of an
|
|
83
|
+
* ordinary teardown. Only the forced claim releases the session's worktree;
|
|
84
|
+
* see `worktreeOccupant`.
|
|
85
|
+
*/
|
|
86
|
+
forcedStaleRelease?: boolean;
|
|
80
87
|
/** OS process incarnation (C1); absent on legacy v1/v2 events. */
|
|
81
88
|
hostIncarnation?: string;
|
|
82
89
|
/** Present on host_heartbeat checkpoints (C2). */
|
|
@@ -99,6 +106,8 @@ export interface IndexedSession {
|
|
|
99
106
|
indexSeq: number;
|
|
100
107
|
lifecycleRequestId?: string;
|
|
101
108
|
terminalUncertain?: boolean;
|
|
109
|
+
/** True only for a terminal-uncertain claim written by a forced stale-worktree release. */
|
|
110
|
+
forcedStaleRelease?: boolean;
|
|
102
111
|
hostIncarnation?: string;
|
|
103
112
|
identityProvenance: SessionIdentityProvenance;
|
|
104
113
|
activity?: SessionActivity;
|
|
@@ -192,7 +201,7 @@ export declare class SessionIndex {
|
|
|
192
201
|
refresh(): Promise<void>;
|
|
193
202
|
get indexSeq(): number;
|
|
194
203
|
append(input: Omit<SessionIndexEvent, "version" | "indexSeq" | "checksum" | "ts"> & Partial<Pick<SessionIndexEvent, "ts">>): Promise<SessionIndexEvent>;
|
|
195
|
-
unregisterIfCurrent(expected:
|
|
204
|
+
unregisterIfCurrent(expected: Pick<SessionIndexEvent, "sessionId" | "locator" | "endpointGeneration" | "pid" | "indexSeq" | "endpointMtimeMs" | "endpointFileId" | "lifecycleRequestId" | "processIncarnation" | "hostIncarnation">): Promise<boolean>;
|
|
196
205
|
/** Hold the canonical index lock across an authority-sensitive operation. */
|
|
197
206
|
withLocked<T>(callback: () => Promise<T>): Promise<T>;
|
|
198
207
|
snapshot(): Promise<void>;
|
|
@@ -229,12 +238,12 @@ export declare class SessionIndex {
|
|
|
229
238
|
lifecycleRequestId?: string;
|
|
230
239
|
}): IndexedSession | undefined;
|
|
231
240
|
/** Returns the latest identity-bound terminal event, if retained. */
|
|
232
|
-
findSessionTerminalEvidence(expected: Pick<IndexedSession, "sessionId" | "locator" | "endpointGeneration" | "pid" | "processIncarnation" | "hostIncarnation" | "endpointMtimeMs" | "lifecycleRequestId">): {
|
|
241
|
+
findSessionTerminalEvidence(expected: Pick<IndexedSession, "sessionId" | "locator" | "endpointGeneration" | "pid" | "processIncarnation" | "hostIncarnation" | "endpointMtimeMs" | "endpointFileId" | "lifecycleRequestId">): {
|
|
233
242
|
type: "host_unregistered" | "session_closed" | "session_deleted";
|
|
234
243
|
indexSeq: number;
|
|
235
244
|
} | undefined;
|
|
236
245
|
/** Returns the latest identity-bound session_closed evidence, even after a tombstone. */
|
|
237
|
-
findSessionClosedEvidence(expected: Pick<IndexedSession, "sessionId" | "locator" | "endpointGeneration" | "pid" | "processIncarnation" | "hostIncarnation" | "endpointMtimeMs" | "lifecycleRequestId">): number | undefined;
|
|
246
|
+
findSessionClosedEvidence(expected: Pick<IndexedSession, "sessionId" | "locator" | "endpointGeneration" | "pid" | "processIncarnation" | "hostIncarnation" | "endpointMtimeMs" | "endpointFileId" | "lifecycleRequestId">): number | undefined;
|
|
238
247
|
/**
|
|
239
248
|
* Production coalesced heartbeat checkpoint pass (C2): appends one
|
|
240
249
|
* `host_heartbeat` per session at most once per {@link SESSION_HEARTBEAT_INTERVAL_MS}.
|
|
@@ -247,7 +256,7 @@ export declare class SessionIndex {
|
|
|
247
256
|
* unknown/not-live (never fresh forever). Returns the number of checkpoints written.
|
|
248
257
|
*/
|
|
249
258
|
checkpointLiveHeartbeats(now?: number): Promise<number>;
|
|
250
|
-
hostUnregisteredAfter(registration: Pick<IndexedSession, "sessionId" | "locator" | "endpointGeneration" | "pid" | "indexSeq" | "lifecycleRequestId" | "hostIncarnation" | "processIncarnation">): {
|
|
259
|
+
hostUnregisteredAfter(registration: Pick<IndexedSession, "sessionId" | "locator" | "endpointGeneration" | "pid" | "indexSeq" | "lifecycleRequestId" | "hostIncarnation" | "processIncarnation" | "endpointFileId">): {
|
|
251
260
|
indexSeq: number;
|
|
252
261
|
lifecycleRequestId?: string;
|
|
253
262
|
} | undefined;
|
|
@@ -24,5 +24,19 @@ export declare function observeProcess(pid: number, expectedIncarnation: string
|
|
|
24
24
|
* `uncertain` counts as occupied: refusing a launch is recoverable by picking
|
|
25
25
|
* another worktree name, whereas two live sessions sharing a checkout corrupts
|
|
26
26
|
* work already done.
|
|
27
|
+
*
|
|
28
|
+
* A row carrying BOTH `terminalUncertain` and `forcedStaleRelease` is the one
|
|
29
|
+
* exception. That pair is written only by the forced stop of a stale-endpoint
|
|
30
|
+
* session: teardown was requested explicitly, so parking its worktree until an
|
|
31
|
+
* OS probe happens to prove exit would leave the checkout locked indefinitely
|
|
32
|
+
* (#5581) exactly when the probe returns `uncertain`. Such a row is released
|
|
33
|
+
* regardless of the process observation.
|
|
34
|
+
*
|
|
35
|
+
* `terminalUncertain` on its own is not enough. The fail-closed tail of a
|
|
36
|
+
* signal-escalated teardown sets it after SIGKILL without ever proving the child
|
|
37
|
+
* gone, so that process may still be running in the checkout; releasing on the
|
|
38
|
+
* flag alone would let a second session into the same worktree. Those rows stay
|
|
39
|
+
* under the strict "only definitive exit releases" rule like every other
|
|
40
|
+
* ordinary retained row.
|
|
27
41
|
*/
|
|
28
42
|
export declare function worktreeOccupant(sessions: readonly IndexedSession[], worktreePath: string, observe?: (pid: number, expectedIncarnation: string | undefined) => ProcessObservation): string | null;
|
|
@@ -7,6 +7,22 @@ export interface ChatDaemonRuntimeHandle {
|
|
|
7
7
|
transportHealthy?(): boolean;
|
|
8
8
|
/** Executes operator commands that must run inside the owning daemon. */
|
|
9
9
|
bindExistingRoot?(request: ChatDaemonCommandBindInput): Promise<ChatDaemonCommandOutcome>;
|
|
10
|
+
prepareDoctorRestart?(): void;
|
|
11
|
+
cancelDoctorRestart?(): void;
|
|
12
|
+
doctorOccupancy?(): {
|
|
13
|
+
attached: number;
|
|
14
|
+
inflight: number;
|
|
15
|
+
inbound: number;
|
|
16
|
+
outbound: number;
|
|
17
|
+
cleanup: number;
|
|
18
|
+
};
|
|
19
|
+
doctorRestartReady?(): boolean;
|
|
20
|
+
doctorRestartStatus?(identity: {
|
|
21
|
+
owner: "discord" | "slack";
|
|
22
|
+
ownerId: string;
|
|
23
|
+
generation: number;
|
|
24
|
+
incarnation: string;
|
|
25
|
+
}, requestId: string, leaseExpiresAt: number): unknown;
|
|
10
26
|
}
|
|
11
27
|
export interface RunChatDaemonInternalDeps {
|
|
12
28
|
processPid?: number;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { Settings } from "../../config/settings";
|
|
2
2
|
import type { BuiltInDaemonController, DaemonOperationOptions, DaemonOperationResult, DaemonRuntimeInfo, DaemonStatus } from "../../daemon/control-types";
|
|
3
|
+
import { type DoctorDaemonControlRequest } from "./doctor-daemon-restart";
|
|
3
4
|
export type ChatDaemonKind = "discord" | "slack";
|
|
4
5
|
export type ChatDaemonAction = "stop" | "reload";
|
|
5
6
|
/**
|
|
@@ -122,6 +123,14 @@ export interface ChatDaemonState {
|
|
|
122
123
|
transportHealthy: boolean;
|
|
123
124
|
generation: number;
|
|
124
125
|
stoppedAt?: number;
|
|
126
|
+
/**
|
|
127
|
+
* Digest of the canonical (symlink-resolved) agent root this record was
|
|
128
|
+
* published for. Legacy records omit it and remain non-authorizing for the
|
|
129
|
+
* D9 cross-bind check: a state record without a matching rootDigest can
|
|
130
|
+
* never be treated as this root's live owner for that check, even though it
|
|
131
|
+
* is still valid for every other pre-existing owner check in this module.
|
|
132
|
+
*/
|
|
133
|
+
rootDigest?: string;
|
|
125
134
|
}
|
|
126
135
|
/**
|
|
127
136
|
* State files are untrusted persisted input. A record must be completely valid
|
|
@@ -161,12 +170,51 @@ export interface ChatDaemonControlDeps {
|
|
|
161
170
|
sleep?: (ms: number) => Promise<void>;
|
|
162
171
|
spawnReadyTimeoutMs?: number;
|
|
163
172
|
}
|
|
173
|
+
/**
|
|
174
|
+
* `version`/`ownerId`/`rootDigest` are the D9 cross-bind receipt delta: a
|
|
175
|
+
* legacy lock predating this generation omits them. Legacy records remain
|
|
176
|
+
* valid, signalable owner locks for every existing check in this module —
|
|
177
|
+
* they are simply non-authorizing for a D9 cross-bind check that requires a
|
|
178
|
+
* matching `ownerId`/`rootDigest` against the sibling state record.
|
|
179
|
+
*/
|
|
180
|
+
export interface ChatDaemonOwnerLock {
|
|
181
|
+
version?: 1;
|
|
182
|
+
pid: number;
|
|
183
|
+
incarnation: string;
|
|
184
|
+
createdAt: number;
|
|
185
|
+
ownerId?: string;
|
|
186
|
+
rootDigest?: string;
|
|
187
|
+
}
|
|
188
|
+
export interface ChatDaemonOwnerLockLease {
|
|
189
|
+
content: string;
|
|
190
|
+
dev: bigint;
|
|
191
|
+
ino: bigint;
|
|
192
|
+
size: bigint;
|
|
193
|
+
mtimeNs: bigint;
|
|
194
|
+
nlink: bigint;
|
|
195
|
+
parentDev: bigint;
|
|
196
|
+
parentIno: bigint;
|
|
197
|
+
sha256: string;
|
|
198
|
+
}
|
|
199
|
+
/** Result of a bounded, no-follow owner-lock lease capture. Only `"absent"` (ENOENT) means the lock currently does not exist; every other failure is `"unreadable"` and must never be treated as absence. */
|
|
200
|
+
export type ChatDaemonOwnerLockLeaseResult = {
|
|
201
|
+
status: "present";
|
|
202
|
+
lease: ChatDaemonOwnerLockLease;
|
|
203
|
+
} | {
|
|
204
|
+
status: "absent";
|
|
205
|
+
} | {
|
|
206
|
+
status: "unreadable";
|
|
207
|
+
};
|
|
164
208
|
export declare function chatDaemonPaths(agentDir: string, kind: ChatDaemonKind): {
|
|
165
209
|
dir: string;
|
|
166
210
|
lock: string;
|
|
167
211
|
state: string;
|
|
168
212
|
control: string;
|
|
169
213
|
};
|
|
214
|
+
export declare function chatDoctorControlRequestPath(agentDir: string, kind: ChatDaemonKind): string;
|
|
215
|
+
export declare function readChatDoctorControlRequest(agentDir: string, kind: ChatDaemonKind): Promise<DoctorDaemonControlRequest | undefined>;
|
|
216
|
+
export declare function writeChatDoctorControlRequest(agentDir: string, kind: ChatDaemonKind, request: DoctorDaemonControlRequest): Promise<void>;
|
|
217
|
+
export declare function clearChatDoctorControlRequest(agentDir: string, kind: ChatDaemonKind, requestId?: string): Promise<void>;
|
|
170
218
|
/**
|
|
171
219
|
* Configuration fingerprint that identifies which settings a daemon owner was
|
|
172
220
|
* started for. `undefined` means the current settings cannot configure that
|
|
@@ -223,7 +271,7 @@ export declare class ChatDaemonController implements BuiltInDaemonController {
|
|
|
223
271
|
export type EnsureChatDaemonResult = "disabled" | "owner_spawned" | "attached";
|
|
224
272
|
export declare function ensureDiscordDaemon(settings: Settings, deps?: ChatDaemonControlDeps): Promise<EnsureChatDaemonResult>;
|
|
225
273
|
export declare function ensureSlackDaemon(settings: Settings, deps?: ChatDaemonControlDeps): Promise<EnsureChatDaemonResult>;
|
|
226
|
-
export
|
|
274
|
+
export interface AcquireChatDaemonOwnershipInput {
|
|
227
275
|
agentDir: string;
|
|
228
276
|
kind: ChatDaemonKind;
|
|
229
277
|
ownerId: string;
|
|
@@ -232,8 +280,40 @@ export declare function acquireChatDaemonOwnership(input: {
|
|
|
232
280
|
incarnation?: string;
|
|
233
281
|
pidAlive?: (pid: number) => boolean;
|
|
234
282
|
pidIncarnation?: (pid: number) => string | undefined;
|
|
235
|
-
}
|
|
236
|
-
|
|
283
|
+
}
|
|
284
|
+
/**
|
|
285
|
+
* Publishes this process as the `(agentDir, kind)` chat daemon owner
|
|
286
|
+
* (owner.lock + state.json). Runs behind the shared cross-process startup
|
|
287
|
+
* exclusion so it can never race a doctor startup/maintenance repair that
|
|
288
|
+
* holds the same guard (e.g. detaching a stale owner-lock quarantine) — the
|
|
289
|
+
* guard is acquired here and released before returning; the actual
|
|
290
|
+
* lock-creation/state-publication logic lives in the unlocked
|
|
291
|
+
* {@link acquireChatDaemonOwnershipLocked} so the guard is never held across
|
|
292
|
+
* a nested reacquisition of itself.
|
|
293
|
+
*/
|
|
294
|
+
export declare function acquireChatDaemonOwnership(input: AcquireChatDaemonOwnershipInput): Promise<boolean>;
|
|
295
|
+
/**
|
|
296
|
+
* Bounded, no-follow, non-blocking owner-lock lease capture. Opens with the
|
|
297
|
+
* same `O_NOFOLLOW|O_NONBLOCK` posture as the canonical lock-info reader in
|
|
298
|
+
* config/file-lock.ts, reads at most {@link CHAT_DAEMON_OWNER_LOCK_LEASE_MAX_BYTES}
|
|
299
|
+
* bytes through the open descriptor (never an unbounded `readFile`), and
|
|
300
|
+
* revalidates identity/parent-directory metadata before and after the read so a
|
|
301
|
+
* TOCTOU replacement during the read is detected rather than silently trusted.
|
|
302
|
+
*
|
|
303
|
+
* Only `ENOENT` on the initial open means the lock is currently absent.
|
|
304
|
+
* SYMLINK, FIFO/special-file, oversize, and any other I/O failure are all
|
|
305
|
+
* `"unreadable"` — distinct from absence, and callers must not treat them as
|
|
306
|
+
* "no lock exists".
|
|
307
|
+
*/
|
|
308
|
+
export declare function captureChatDaemonOwnerLockLease(lock: string): Promise<ChatDaemonOwnerLockLeaseResult>;
|
|
309
|
+
/**
|
|
310
|
+
* Strict: `pid`/`incarnation`/`createdAt` are always required. `version`,
|
|
311
|
+
* `ownerId`, and `rootDigest` are individually optional (legacy locks omit
|
|
312
|
+
* them), but any one that IS present must be well-formed — a malformed
|
|
313
|
+
* optional field is never silently ignored.
|
|
314
|
+
*/
|
|
315
|
+
export declare function isChatDaemonOwnerLock(value: unknown): value is ChatDaemonOwnerLock;
|
|
316
|
+
export interface RenewChatDaemonHeartbeatInput {
|
|
237
317
|
agentDir: string;
|
|
238
318
|
kind: ChatDaemonKind;
|
|
239
319
|
ownerId: string;
|
|
@@ -242,8 +322,16 @@ export declare function renewChatDaemonHeartbeat(input: {
|
|
|
242
322
|
transportHealthy: boolean;
|
|
243
323
|
pidAlive?: (pid: number) => boolean;
|
|
244
324
|
pidIncarnation?: (pid: number) => string | undefined;
|
|
245
|
-
}
|
|
246
|
-
|
|
325
|
+
}
|
|
326
|
+
/**
|
|
327
|
+
* Renews the exact-owner heartbeat for `(agentDir, kind)`. Runs behind the
|
|
328
|
+
* shared cross-process startup exclusion so a heartbeat can never publish in
|
|
329
|
+
* the middle of a doctor startup/maintenance repair holding the same guard;
|
|
330
|
+
* the actual re-check-and-write logic lives in the unlocked
|
|
331
|
+
* {@link renewChatDaemonHeartbeatLocked}.
|
|
332
|
+
*/
|
|
333
|
+
export declare function renewChatDaemonHeartbeat(input: RenewChatDaemonHeartbeatInput): Promise<boolean>;
|
|
334
|
+
export interface ReleaseChatDaemonOwnershipInput {
|
|
247
335
|
agentDir: string;
|
|
248
336
|
kind: ChatDaemonKind;
|
|
249
337
|
ownerId: string;
|
|
@@ -251,4 +339,11 @@ export declare function releaseChatDaemonOwnership(input: {
|
|
|
251
339
|
incarnation: string;
|
|
252
340
|
pidAlive?: (pid: number) => boolean;
|
|
253
341
|
pidIncarnation?: (pid: number) => string | undefined;
|
|
254
|
-
}
|
|
342
|
+
}
|
|
343
|
+
/**
|
|
344
|
+
* Releases this process's `(agentDir, kind)` chat daemon ownership. Runs
|
|
345
|
+
* behind the shared cross-process startup exclusion for the same reason as
|
|
346
|
+
* {@link acquireChatDaemonOwnership}; the actual re-check-and-unpublish logic
|
|
347
|
+
* lives in the unlocked {@link releaseChatDaemonOwnershipLocked}.
|
|
348
|
+
*/
|
|
349
|
+
export declare function releaseChatDaemonOwnership(input: ReleaseChatDaemonOwnershipInput): Promise<void>;
|
|
@@ -2,6 +2,7 @@ import type { SessionRouterDeps } from "../router";
|
|
|
2
2
|
import type { ChatDaemonCommandBindInput, ChatDaemonCommandOutcome } from "./chat-daemon-command-channel";
|
|
3
3
|
import type { ChatDaemonKind } from "./chat-daemon-control";
|
|
4
4
|
import type { DiscordProvider } from "./discord-provider";
|
|
5
|
+
import { type DoctorDaemonIdentity, type DoctorDaemonOccupancy, type DoctorDaemonStatus } from "./doctor-daemon-restart";
|
|
5
6
|
import { type SlackProviderClient } from "./slack-provider";
|
|
6
7
|
export interface ChatDaemonRuntimeConfig {
|
|
7
8
|
identity: string;
|
|
@@ -63,6 +64,21 @@ export declare class ChatDaemonRuntime {
|
|
|
63
64
|
}, deps?: ChatDaemonRuntimeDeps);
|
|
64
65
|
start(): Promise<void>;
|
|
65
66
|
transportHealthy(): boolean;
|
|
67
|
+
/** Doctor reservation hook: stop admitting new `/sdk` command dispatch. Synchronous fence. */
|
|
68
|
+
prepareDoctorRestart(): boolean;
|
|
69
|
+
cancelDoctorRestart(): void;
|
|
70
|
+
/**
|
|
71
|
+
* Real runtime occupancy, not synthetic placeholders. `attached` is live
|
|
72
|
+
* session attachments; `inflight` is attachment (re)publication barriers
|
|
73
|
+
* currently resolving; `inbound` is admitted `/sdk` command dispatches
|
|
74
|
+
* (including operator `bindExistingRoot` binds) actually executing;
|
|
75
|
+
* `outbound` is real Discord/Slack notify/resume provider deliveries in
|
|
76
|
+
* flight; `cleanup` is provider close/retire work that has not yet
|
|
77
|
+
* settled.
|
|
78
|
+
*/
|
|
79
|
+
doctorOccupancy(): DoctorDaemonOccupancy;
|
|
80
|
+
doctorRestartStatus(identity: DoctorDaemonIdentity, requestId: string, leaseExpiresAt: number): DoctorDaemonStatus;
|
|
81
|
+
doctorRestartReady(): boolean;
|
|
66
82
|
/** Reconcile indexed attachments and optionally wait for their replay tails. */
|
|
67
83
|
reconcile(options?: {
|
|
68
84
|
waitForReplay?: boolean;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
export type DaemonStartupExclusionOwner = "telegram" | "discord" | "slack";
|
|
2
|
+
export interface DaemonStartupExclusionOptions {
|
|
3
|
+
/** Abort acquisition early; never releases a lease this call did not itself acquire. */
|
|
4
|
+
signal?: AbortSignal;
|
|
5
|
+
/** Upper bound on total wait while contended. Defaults to {@link DAEMON_STARTUP_EXCLUSION_DEFAULT_TIMEOUT_MS}. */
|
|
6
|
+
timeoutMs?: number;
|
|
7
|
+
}
|
|
8
|
+
/** Matches the production wall-time bound already established for other durable daemon locks. */
|
|
9
|
+
export declare const DAEMON_STARTUP_EXCLUSION_DEFAULT_TIMEOUT_MS = 5000;
|
|
10
|
+
/**
|
|
11
|
+
* The canonical per-service guard path. Lives inside the SAME per-service
|
|
12
|
+
* directory as the service's ordinary owner/state artifacts (so it inherits
|
|
13
|
+
* that directory's existing parent-permission posture) but under a filename
|
|
14
|
+
* reserved exclusively for this guard — never a name any ownership,
|
|
15
|
+
* publication, or transition-marker code path reads or writes.
|
|
16
|
+
*
|
|
17
|
+
* This function only computes a path; it performs no filesystem I/O and
|
|
18
|
+
* creates nothing. (`withFileLock` itself creates the missing parent
|
|
19
|
+
* directory chain, mode 0o700, purely as lock-acquisition scaffolding — an
|
|
20
|
+
* empty directory carries no service state.)
|
|
21
|
+
*/
|
|
22
|
+
export declare function daemonStartupExclusionPath(agentDir: string, owner: DaemonStartupExclusionOwner): string;
|
|
23
|
+
/**
|
|
24
|
+
* Run `operation` while holding the cross-process startup/maintenance
|
|
25
|
+
* exclusion for `(agentDir, owner)`. Bounded: acquisition retries back off
|
|
26
|
+
* for at most `timeoutMs` (default {@link DAEMON_STARTUP_EXCLUSION_DEFAULT_TIMEOUT_MS})
|
|
27
|
+
* and additionally honors `signal` if supplied — whichever bound is hit first
|
|
28
|
+
* rejects the acquisition. A rejected/timed-out/aborted acquisition never held
|
|
29
|
+
* a lease and therefore never releases one; it cannot touch a foreign owner's
|
|
30
|
+
* lock. On success, the lease this call acquired is released exactly once,
|
|
31
|
+
* through the same identity-bound release primitive `withFileLock` uses for
|
|
32
|
+
* every other durable lock in this codebase — never a recursive directory
|
|
33
|
+
* removal, and never a process signal.
|
|
34
|
+
*
|
|
35
|
+
* This primitive itself never reads, writes, or removes any owner/state/lock
|
|
36
|
+
* file belonging to the service; it only fences concurrent callers of this
|
|
37
|
+
* same function (and, by construction, is never consulted by ordinary
|
|
38
|
+
* ownership-acquisition or publication code, which uses its own distinct
|
|
39
|
+
* lock file).
|
|
40
|
+
*/
|
|
41
|
+
export declare function withDaemonStartupExclusion<T>(agentDir: string, owner: DaemonStartupExclusionOwner, operation: () => Promise<T>, options?: DaemonStartupExclusionOptions): Promise<T>;
|
|
42
|
+
/** Retain the same exclusion across an existing acquire/release lifecycle. */
|
|
43
|
+
export declare function acquireDaemonStartupExclusion(agentDir: string, owner: DaemonStartupExclusionOwner, options?: DaemonStartupExclusionOptions): Promise<() => Promise<void>>;
|