@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
|
@@ -5,7 +5,13 @@ import { createHash, randomUUID } from "node:crypto";
|
|
|
5
5
|
* Preserves Q26 admit/first-terminal/capacity semantics; indexes and caps are per-kind.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
failedPromptOutcome,
|
|
10
|
+
failureEvidence,
|
|
11
|
+
type PromptFailureEvidence,
|
|
12
|
+
rephaseFailedOutcome,
|
|
13
|
+
sanitizePromptFailure,
|
|
14
|
+
} from "../prompt-failure";
|
|
9
15
|
import type {
|
|
10
16
|
PromptReconciliationStatus,
|
|
11
17
|
SdkPromptFailureCode,
|
|
@@ -55,7 +61,10 @@ const STOP_REASONS: readonly SdkPromptStopReason[] = [
|
|
|
55
61
|
];
|
|
56
62
|
const FAILURE_CODES: readonly SdkPromptFailureCode[] = ["prompt_failed", "prompt_deadline_exceeded"];
|
|
57
63
|
|
|
58
|
-
const normalizeTerminalOutcome = (
|
|
64
|
+
const normalizeTerminalOutcome = (
|
|
65
|
+
outcome: unknown,
|
|
66
|
+
evidence: PromptFailureEvidence = {},
|
|
67
|
+
): SdkPromptTerminalOutcome | undefined => {
|
|
59
68
|
if (outcome === null || typeof outcome !== "object") return undefined;
|
|
60
69
|
const candidate = outcome as Record<string, unknown>;
|
|
61
70
|
if (
|
|
@@ -73,23 +82,32 @@ const normalizeTerminalOutcome = (outcome: unknown): SdkPromptTerminalOutcome |
|
|
|
73
82
|
FAILURE_CODES.includes(candidate.code as SdkPromptFailureCode) &&
|
|
74
83
|
typeof candidate.message === "string" &&
|
|
75
84
|
["agent_failed", "deadline"].includes(candidate.provenance as string)
|
|
76
|
-
)
|
|
77
|
-
return {
|
|
78
|
-
kind: "failed",
|
|
85
|
+
) {
|
|
86
|
+
return failedPromptOutcome({
|
|
79
87
|
code: candidate.code as SdkPromptFailureCode,
|
|
80
|
-
message:
|
|
81
|
-
candidate.code === "prompt_deadline_exceeded" ? "Prompt deadline exceeded." : "Prompt submission failed.",
|
|
82
88
|
provenance: candidate.provenance as "agent_failed" | "deadline",
|
|
83
|
-
|
|
89
|
+
...(typeof candidate.providerCode === "string" ? { providerCode: candidate.providerCode } : {}),
|
|
90
|
+
evidence,
|
|
91
|
+
});
|
|
92
|
+
}
|
|
84
93
|
return undefined;
|
|
85
94
|
};
|
|
86
95
|
|
|
87
|
-
const failedOutcomeForError = (
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
96
|
+
const failedOutcomeForError = (
|
|
97
|
+
error: { code: string; message: string },
|
|
98
|
+
evidence: PromptFailureEvidence = {},
|
|
99
|
+
providerCodeOverride?: string,
|
|
100
|
+
): SdkPromptTerminalOutcome =>
|
|
101
|
+
failedPromptOutcome({
|
|
102
|
+
code: "prompt_failed",
|
|
103
|
+
provenance: "agent_failed",
|
|
104
|
+
...(providerCodeOverride !== undefined
|
|
105
|
+
? { providerCode: providerCodeOverride }
|
|
106
|
+
: error.code !== "prompt_failed"
|
|
107
|
+
? { providerCode: error.code }
|
|
108
|
+
: {}),
|
|
109
|
+
evidence,
|
|
110
|
+
});
|
|
93
111
|
|
|
94
112
|
const isDeadlineOutcome = (outcome: SdkPromptTerminalOutcome | undefined): boolean =>
|
|
95
113
|
outcome?.kind === "failed" && outcome.provenance === "deadline";
|
|
@@ -440,7 +458,10 @@ export function createKindAwareReconciliation(
|
|
|
440
458
|
record.receiptState,
|
|
441
459
|
reportableTurnResultContent(content) ? "present" : undefined,
|
|
442
460
|
);
|
|
443
|
-
const incomingOutcome = normalizeTerminalOutcome(
|
|
461
|
+
const incomingOutcome = normalizeTerminalOutcome(
|
|
462
|
+
frame.outcome,
|
|
463
|
+
failureEvidence(record, frame.hasActivity),
|
|
464
|
+
);
|
|
444
465
|
const providerErrorRecord = record.error;
|
|
445
466
|
const providerError =
|
|
446
467
|
providerErrorRecord !== undefined && providerErrorRecord.code !== "prompt_deadline_exceeded";
|
|
@@ -460,7 +481,7 @@ export function createKindAwareReconciliation(
|
|
|
460
481
|
? providerErrorRecord
|
|
461
482
|
: { code: providerOutcome.code, message: providerOutcome.message };
|
|
462
483
|
} else if (providerError) {
|
|
463
|
-
outcome = failedOutcomeForError(providerErrorRecord);
|
|
484
|
+
outcome = failedOutcomeForError(providerErrorRecord, failureEvidence(record, frame.hasActivity));
|
|
464
485
|
error = providerErrorRecord;
|
|
465
486
|
} else if (
|
|
466
487
|
incomingOutcome?.kind === "stopped" ||
|
|
@@ -479,7 +500,7 @@ export function createKindAwareReconciliation(
|
|
|
479
500
|
? providerErrorRecord
|
|
480
501
|
: { code: providerOutcome.code, message: providerOutcome.message };
|
|
481
502
|
} else if (record.status === "failed" && providerError) {
|
|
482
|
-
outcome = failedOutcomeForError(providerErrorRecord);
|
|
503
|
+
outcome = failedOutcomeForError(providerErrorRecord, failureEvidence(record, frame.hasActivity));
|
|
483
504
|
error = providerErrorRecord;
|
|
484
505
|
} else if (
|
|
485
506
|
record.status === "terminal_ok" &&
|
|
@@ -523,7 +544,7 @@ export function createKindAwareReconciliation(
|
|
|
523
544
|
const failure = sanitizePromptFailure(frame.error);
|
|
524
545
|
if (record.error?.code === "prompt_deadline_exceeded") {
|
|
525
546
|
record.error = failure;
|
|
526
|
-
record.outcome = failedOutcomeForError(failure);
|
|
547
|
+
record.outcome = failedOutcomeForError(failure, failureEvidence(record, frame.hasActivity));
|
|
527
548
|
record.status = "failed";
|
|
528
549
|
return { value: undefined, changed: true };
|
|
529
550
|
}
|
|
@@ -557,7 +578,10 @@ export function createKindAwareReconciliation(
|
|
|
557
578
|
return { value: undefined, changed: true };
|
|
558
579
|
}
|
|
559
580
|
const pendingOutcome = record.pendingOutcome;
|
|
560
|
-
const frameOutcome = normalizeTerminalOutcome(
|
|
581
|
+
const frameOutcome = normalizeTerminalOutcome(
|
|
582
|
+
frame.type === "agent_end" ? frame.outcome : undefined,
|
|
583
|
+
failureEvidence(record, frame.hasActivity),
|
|
584
|
+
);
|
|
561
585
|
delete record.deadlineRecoveryPending;
|
|
562
586
|
delete record.deadlineMaxAt;
|
|
563
587
|
record.terminalAt = now();
|
|
@@ -572,7 +596,7 @@ export function createKindAwareReconciliation(
|
|
|
572
596
|
if (frameOutcome?.kind === "failed" && frameOutcome.provenance === "agent_failed")
|
|
573
597
|
terminalOutcome = frameOutcome;
|
|
574
598
|
else if (providerError && (pendingOutcome.kind !== "failed" || isDeadlineOutcome(pendingOutcome)))
|
|
575
|
-
terminalOutcome = failedOutcomeForError(providerErrorRecord);
|
|
599
|
+
terminalOutcome = failedOutcomeForError(providerErrorRecord, failureEvidence(record, frame.hasActivity));
|
|
576
600
|
else if (isDeadlineOutcome(pendingOutcome) && frameOutcome?.kind === "stopped")
|
|
577
601
|
terminalOutcome = frameOutcome;
|
|
578
602
|
record.outcome = terminalOutcome;
|
|
@@ -598,7 +622,7 @@ export function createKindAwareReconciliation(
|
|
|
598
622
|
? frameOutcome
|
|
599
623
|
: providerError &&
|
|
600
624
|
(frameOutcome === undefined || isDeadlineOutcome(frameOutcome) || frameOutcome.kind === "stopped")
|
|
601
|
-
? failedOutcomeForError(providerErrorRecord)
|
|
625
|
+
? failedOutcomeForError(providerErrorRecord, failureEvidence(record, frame.hasActivity))
|
|
602
626
|
: frameOutcome;
|
|
603
627
|
if (terminalOutcome !== undefined) {
|
|
604
628
|
record.outcome = terminalOutcome;
|
|
@@ -618,10 +642,10 @@ export function createKindAwareReconciliation(
|
|
|
618
642
|
) {
|
|
619
643
|
record.status = "failed";
|
|
620
644
|
record.error = EMPTY_PROMPT_FAILURE;
|
|
621
|
-
record.outcome = failedOutcomeForError(EMPTY_PROMPT_FAILURE);
|
|
645
|
+
record.outcome = failedOutcomeForError(EMPTY_PROMPT_FAILURE, failureEvidence(record, frame.hasActivity));
|
|
622
646
|
} else if (record.error !== undefined) {
|
|
623
647
|
record.status = "failed";
|
|
624
|
-
record.outcome = failedOutcomeForError(record.error);
|
|
648
|
+
record.outcome = failedOutcomeForError(record.error, failureEvidence(record, frame.hasActivity));
|
|
625
649
|
} else record.status = "terminal_ok";
|
|
626
650
|
record.receiptState = reduceReceiptState(
|
|
627
651
|
record.receiptState,
|
|
@@ -645,9 +669,10 @@ export function createKindAwareReconciliation(
|
|
|
645
669
|
if (!record || record.terminalAt !== undefined || record.kind !== kind)
|
|
646
670
|
return { value: normalized, changed: false };
|
|
647
671
|
if (record.pendingOutcome !== undefined) return { value: record.pendingOutcome, changed: false };
|
|
648
|
-
|
|
672
|
+
const decorated = rephaseFailedOutcome(normalized, failureEvidence(record));
|
|
673
|
+
record.pendingOutcome = decorated;
|
|
649
674
|
record.pendingReceiptState = receiptState;
|
|
650
|
-
return { value:
|
|
675
|
+
return { value: decorated, changed: true };
|
|
651
676
|
});
|
|
652
677
|
|
|
653
678
|
const finalizeOutcome = async (
|
|
@@ -673,25 +698,28 @@ export function createKindAwareReconciliation(
|
|
|
673
698
|
const record = candidate.get(keyOf(kind, correlation));
|
|
674
699
|
if (!record || record.terminalAt !== undefined || record.kind !== kind)
|
|
675
700
|
return { value: undefined, changed: false };
|
|
676
|
-
const requestedOutcome = normalizeTerminalOutcome(outcome) ?? record.pendingOutcome;
|
|
701
|
+
const requestedOutcome = normalizeTerminalOutcome(outcome, failureEvidence(record)) ?? record.pendingOutcome;
|
|
677
702
|
const providerErrorRecord = record.error;
|
|
678
703
|
const providerError =
|
|
679
704
|
providerErrorRecord !== undefined && providerErrorRecord.code !== "prompt_deadline_exceeded";
|
|
705
|
+
const requestedProviderCode = requestedOutcome?.kind === "failed" ? requestedOutcome.providerCode : undefined;
|
|
680
706
|
const finalOutcome = providerError
|
|
681
|
-
? failedOutcomeForError(providerErrorRecord)
|
|
707
|
+
? failedOutcomeForError(providerErrorRecord, failureEvidence(record), requestedProviderCode)
|
|
682
708
|
: requestedOutcome?.kind === "stopped"
|
|
683
709
|
? requestedOutcome
|
|
684
710
|
: providerError &&
|
|
685
711
|
(requestedOutcome === undefined ||
|
|
686
712
|
(requestedOutcome.kind === "failed" && isDeadlineOutcome(requestedOutcome)))
|
|
687
|
-
? failedOutcomeForError(providerErrorRecord)
|
|
713
|
+
? failedOutcomeForError(providerErrorRecord, failureEvidence(record), requestedProviderCode)
|
|
688
714
|
: requestedOutcome;
|
|
689
715
|
record.terminalAt = now();
|
|
690
716
|
if (finalOutcome?.kind === "failed") {
|
|
691
717
|
record.status = "failed";
|
|
692
718
|
if (recordError !== undefined && !(providerError && recordError.code === "prompt_deadline_exceeded"))
|
|
693
|
-
record.error = recordError;
|
|
694
|
-
else if (
|
|
719
|
+
record.error = { code: recordError.code, message: finalOutcome.message };
|
|
720
|
+
else if (providerError && providerErrorRecord !== undefined)
|
|
721
|
+
record.error = { code: providerErrorRecord.code, message: finalOutcome.message };
|
|
722
|
+
else record.error = { code: finalOutcome.code, message: finalOutcome.message };
|
|
695
723
|
} else if (
|
|
696
724
|
kind === "prompt" &&
|
|
697
725
|
((finalOutcome === undefined && !sanitizedContent?.text.trim()) ||
|
|
@@ -699,7 +727,7 @@ export function createKindAwareReconciliation(
|
|
|
699
727
|
) {
|
|
700
728
|
record.status = "failed";
|
|
701
729
|
record.error = EMPTY_PROMPT_FAILURE;
|
|
702
|
-
record.outcome = failedOutcomeForError(EMPTY_PROMPT_FAILURE);
|
|
730
|
+
record.outcome = failedOutcomeForError(EMPTY_PROMPT_FAILURE, failureEvidence(record));
|
|
703
731
|
} else if (finalOutcome?.kind === "stopped") {
|
|
704
732
|
// A stopped terminal is authoritative only when no provider failure
|
|
705
733
|
// was recorded before finalization.
|
|
@@ -837,16 +865,23 @@ export function createKindAwareReconciliation(
|
|
|
837
865
|
loaded.map(record => {
|
|
838
866
|
const safeOutcome = (
|
|
839
867
|
outcome: SdkPromptTerminalOutcome | undefined,
|
|
840
|
-
): SdkPromptTerminalOutcome | undefined =>
|
|
841
|
-
outcome?.kind
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
868
|
+
): SdkPromptTerminalOutcome | undefined => {
|
|
869
|
+
if (outcome?.kind !== "failed") return outcome;
|
|
870
|
+
const rephased = rephaseFailedOutcome(
|
|
871
|
+
outcome,
|
|
872
|
+
record.startedAt !== undefined ? { startedAt: record.startedAt } : {},
|
|
873
|
+
);
|
|
874
|
+
// Legacy durable rows carry a generic message and no phase/category;
|
|
875
|
+
// rewriting them to the current contract is a durable-state sanitization.
|
|
876
|
+
if (
|
|
877
|
+
rephased.kind === "failed" &&
|
|
878
|
+
(outcome.phase !== rephased.phase ||
|
|
879
|
+
outcome.category !== rephased.category ||
|
|
880
|
+
outcome.message !== rephased.message)
|
|
881
|
+
)
|
|
882
|
+
sanitizedDurableRecord = true;
|
|
883
|
+
return rephased;
|
|
884
|
+
};
|
|
850
885
|
let hydrated: DurableReconciliationRecord;
|
|
851
886
|
if (record.kind === "steer") {
|
|
852
887
|
const safeError = record.error === undefined ? undefined : sanitizePromptFailure(record.error);
|
|
@@ -52,7 +52,13 @@ import { DiscordLiveProvider } from "./discord-live-provider";
|
|
|
52
52
|
import type { DiscordDiagnosticProvider } from "./discord-provider";
|
|
53
53
|
import { SlackLiveProvider } from "./slack-live-provider";
|
|
54
54
|
import type { SlackDiagnosticProvider } from "./slack-provider";
|
|
55
|
-
import {
|
|
55
|
+
import {
|
|
56
|
+
isTelegramDaemonStopCause,
|
|
57
|
+
type OwnerFreshnessSnapshot,
|
|
58
|
+
readOwnerFreshnessSnapshot,
|
|
59
|
+
type TelegramDaemonFs,
|
|
60
|
+
type TelegramDaemonStopCause,
|
|
61
|
+
} from "./telegram-daemon";
|
|
56
62
|
import { DAEMON_GENERATION } from "./telegram-daemon-contract";
|
|
57
63
|
|
|
58
64
|
const DEFAULT_API_BASE = "https://api.telegram.org";
|
|
@@ -547,6 +553,7 @@ interface NormalizedDaemonState {
|
|
|
547
553
|
startedAt: number | undefined;
|
|
548
554
|
heartbeatAt: number | undefined;
|
|
549
555
|
stoppedAt: number | undefined;
|
|
556
|
+
stopCause: TelegramDaemonStopCause | undefined;
|
|
550
557
|
roots: string[] | undefined;
|
|
551
558
|
generation: number | undefined;
|
|
552
559
|
generationStatus: "missing" | "valid" | "invalid";
|
|
@@ -594,6 +601,7 @@ function parseDaemonState(raw: string): NormalizedDaemonState | undefined {
|
|
|
594
601
|
startedAt: finiteNonNegativeNumber(rec.startedAt),
|
|
595
602
|
heartbeatAt: finiteNonNegativeNumber(rec.heartbeatAt),
|
|
596
603
|
stoppedAt: finiteNonNegativeNumber(rec.stoppedAt),
|
|
604
|
+
stopCause: isTelegramDaemonStopCause(rec.stopCause) ? rec.stopCause : undefined,
|
|
597
605
|
roots: stringArray(rec.roots),
|
|
598
606
|
generation,
|
|
599
607
|
generationStatus,
|
|
@@ -677,6 +685,8 @@ export interface DaemonHealth {
|
|
|
677
685
|
stopped: boolean;
|
|
678
686
|
heartbeatAt: number | undefined;
|
|
679
687
|
heartbeatAgeMs: number | undefined;
|
|
688
|
+
/** Bounded persisted stop cause; absent means this record predates diagnostics or is unknown. */
|
|
689
|
+
stopCause?: TelegramDaemonStopCause;
|
|
680
690
|
|
|
681
691
|
/**
|
|
682
692
|
* Session endpoints the live owner reported an OPEN socket to in its latest
|
|
@@ -928,6 +938,7 @@ export async function checkNotificationHealth(opts: HealthOptions): Promise<Noti
|
|
|
928
938
|
stopped: state?.stoppedAt !== undefined,
|
|
929
939
|
heartbeatAt,
|
|
930
940
|
heartbeatAgeMs: heartbeatAt === undefined ? undefined : displayHeartbeatAgeMs(now, heartbeatAt),
|
|
941
|
+
...(state?.stopCause === undefined ? {} : { stopCause: state.stopCause }),
|
|
931
942
|
attachedEndpoints: snapshot.attachedEndpoints,
|
|
932
943
|
generation: state?.generation,
|
|
933
944
|
currentGeneration: DAEMON_GENERATION,
|
|
@@ -948,19 +959,26 @@ export async function checkNotificationHealth(opts: HealthOptions): Promise<Noti
|
|
|
948
959
|
} catch {
|
|
949
960
|
lockPresent = false;
|
|
950
961
|
}
|
|
962
|
+
const stopCause = daemon.stopCause ?? "unknown";
|
|
951
963
|
if (lockPresent) {
|
|
952
964
|
checks.push({
|
|
953
965
|
name: "daemon",
|
|
954
966
|
level: "warn",
|
|
955
|
-
detail: `daemon owner pid ${daemon.pid} is not alive; run recovery to clear the stale lock`,
|
|
967
|
+
detail: `daemon owner pid ${daemon.pid} is not alive; run recovery to clear the stale lock (stop cause: ${stopCause})`,
|
|
956
968
|
});
|
|
957
969
|
} else {
|
|
958
970
|
checks.push({
|
|
959
971
|
name: "daemon",
|
|
960
972
|
level: "warn",
|
|
961
|
-
detail: `daemon owner pid ${daemon.pid} is not alive; no lock present (
|
|
973
|
+
detail: `daemon owner pid ${daemon.pid} is not alive; no lock present (stop cause: ${stopCause})`,
|
|
962
974
|
});
|
|
963
975
|
}
|
|
976
|
+
} else if (daemon.stopped) {
|
|
977
|
+
checks.push({
|
|
978
|
+
name: "daemon",
|
|
979
|
+
level: "warn",
|
|
980
|
+
detail: `daemon is stopped (stop cause: ${daemon.stopCause ?? "unknown"})`,
|
|
981
|
+
});
|
|
964
982
|
} else if (!daemon.heartbeatFresh) {
|
|
965
983
|
checks.push({ name: "daemon", level: "warn", detail: `daemon pid ${daemon.pid} heartbeat is stale` });
|
|
966
984
|
} else if (telegramConfigured && !daemon.identityMatches) {
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
import { createHash, randomUUID } from "node:crypto";
|
|
13
13
|
import * as fs from "node:fs/promises";
|
|
14
14
|
import * as path from "node:path";
|
|
15
|
+
import { failedPromptOutcome } from "../prompt-failure";
|
|
15
16
|
import type { PromptReconciliationStatus, SdkPromptTerminalOutcome } from "../prompt-status";
|
|
16
17
|
import type { ReceiptState } from "../receipt-state";
|
|
17
18
|
import { reportableTurnResultContent, TURN_RESULT_CONTENT_MAX_BYTES, type TurnResultContent } from "../turn-result";
|
|
@@ -559,12 +560,16 @@ export function settleProcessRestart(
|
|
|
559
560
|
): DurableReconciliationRecord[] {
|
|
560
561
|
const isDeadlineOutcome = (outcome: SdkPromptTerminalOutcome | undefined): boolean =>
|
|
561
562
|
outcome?.kind === "failed" && outcome.provenance === "deadline";
|
|
562
|
-
const providerFailureOutcome = (
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
563
|
+
const providerFailureOutcome = (
|
|
564
|
+
error: { code: string; message: string },
|
|
565
|
+
record: { startedAt?: number },
|
|
566
|
+
): SdkPromptTerminalOutcome =>
|
|
567
|
+
failedPromptOutcome({
|
|
568
|
+
code: "prompt_failed",
|
|
569
|
+
provenance: "agent_failed",
|
|
570
|
+
...(error.code !== "prompt_failed" ? { providerCode: error.code } : {}),
|
|
571
|
+
evidence: record.startedAt !== undefined ? { startedAt: record.startedAt } : {},
|
|
572
|
+
});
|
|
568
573
|
return records.map(record => {
|
|
569
574
|
if (record.kind === "steer") {
|
|
570
575
|
if (record.status !== "dispatching") return record;
|
|
@@ -592,13 +597,14 @@ export function settleProcessRestart(
|
|
|
592
597
|
const outcome: SdkPromptTerminalOutcome =
|
|
593
598
|
providerError &&
|
|
594
599
|
(pendingOutcome === undefined || isDeadlineOutcome(pendingOutcome) || pendingOutcome.kind === "stopped")
|
|
595
|
-
? providerFailureOutcome(providerErrorRecord)
|
|
596
|
-
: (pendingOutcome ??
|
|
597
|
-
|
|
600
|
+
? providerFailureOutcome(providerErrorRecord, record)
|
|
601
|
+
: (pendingOutcome ??
|
|
602
|
+
failedPromptOutcome({
|
|
598
603
|
code: "prompt_failed",
|
|
599
|
-
message: "Prompt did not complete before process restart.",
|
|
600
604
|
provenance: "agent_failed",
|
|
601
|
-
|
|
605
|
+
providerCode: "process_restart_uncertain",
|
|
606
|
+
evidence: record.startedAt !== undefined ? { startedAt: record.startedAt } : {},
|
|
607
|
+
}));
|
|
602
608
|
const preservedError =
|
|
603
609
|
providerError && preserveProviderError
|
|
604
610
|
? providerErrorRecord
|
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
parseNotificationSettingsSnapshot,
|
|
14
14
|
} from "./config";
|
|
15
15
|
import { daemonPaths, HEARTBEAT_TTL_MS } from "./daemon-paths";
|
|
16
|
+
import { doctorDaemonIdentityMatches, doctorDaemonOccupancySettled } from "./doctor-daemon-restart";
|
|
16
17
|
import { type NotificationDebrisSweepReport, sweepNotificationDebris } from "./notification-service";
|
|
17
18
|
import {
|
|
18
19
|
type DaemonState,
|
|
@@ -26,11 +27,26 @@ import {
|
|
|
26
27
|
type TelegramDaemonOptions,
|
|
27
28
|
TelegramNotificationDaemon,
|
|
28
29
|
} from "./telegram-daemon";
|
|
29
|
-
import {
|
|
30
|
+
import {
|
|
31
|
+
clearTelegramControlRequest,
|
|
32
|
+
clearTelegramDoctorControlRequest,
|
|
33
|
+
readTelegramControlRequest,
|
|
34
|
+
readTelegramDoctorControlRequest,
|
|
35
|
+
} from "./telegram-daemon-control";
|
|
30
36
|
|
|
31
37
|
type TelegramDaemonRunner = {
|
|
32
38
|
run(): Promise<void>;
|
|
33
39
|
requestStop(reason?: "reload" | "signal" | "stop"): void;
|
|
40
|
+
prepareDoctorRestart?(): void;
|
|
41
|
+
cancelDoctorRestart?(): void;
|
|
42
|
+
doctorOccupancy?(): {
|
|
43
|
+
attached: number;
|
|
44
|
+
inflight: number;
|
|
45
|
+
inbound: number;
|
|
46
|
+
outbound: number;
|
|
47
|
+
cleanup: number;
|
|
48
|
+
};
|
|
49
|
+
doctorRestartReady?(): boolean;
|
|
34
50
|
};
|
|
35
51
|
|
|
36
52
|
type TelegramDaemonConstructor = new (opts: TelegramDaemonOptions) => TelegramDaemonRunner;
|
|
@@ -254,16 +270,23 @@ function ownerProcessIsAlive(ownerId: string, deps: RunDaemonInternalDeps): bool
|
|
|
254
270
|
|
|
255
271
|
/** Creates owner-fenced daemon control hooks for the CLI lifecycle boundary. */
|
|
256
272
|
export function createDaemonControlHooks(settings: Settings) {
|
|
273
|
+
const readOwnedRequest = async (owner: string) => {
|
|
274
|
+
const req = await readTelegramControlRequest(settings);
|
|
275
|
+
return req && (!req.ownerId || req.ownerId === owner) ? req : undefined;
|
|
276
|
+
};
|
|
257
277
|
return {
|
|
258
278
|
shouldStop: async (owner: string) => {
|
|
259
|
-
|
|
260
|
-
|
|
279
|
+
return (await readOwnedRequest(owner)) !== undefined;
|
|
280
|
+
},
|
|
281
|
+
requestedAction: async (owner: string): Promise<"reload" | "stop" | undefined> => {
|
|
282
|
+
const action = (await readOwnedRequest(owner))?.action;
|
|
283
|
+
return action === "reload" || action === "stop" ? action : undefined;
|
|
261
284
|
},
|
|
262
285
|
clear: async (owner: string) => {
|
|
263
|
-
const req = await
|
|
286
|
+
const req = await readOwnedRequest(owner);
|
|
264
287
|
// Only clear a request that targets this daemon owner, so an exiting
|
|
265
288
|
// daemon never erases a newer request meant for a different owner.
|
|
266
|
-
if (req
|
|
289
|
+
if (req) await clearTelegramControlRequest(settings, req.requestId);
|
|
267
290
|
},
|
|
268
291
|
};
|
|
269
292
|
}
|
|
@@ -409,6 +432,22 @@ export async function runDaemonInternal(argv: string[], deps: RunDaemonInternalD
|
|
|
409
432
|
requireTelegramTopicEligibility: true,
|
|
410
433
|
orphanReap,
|
|
411
434
|
});
|
|
435
|
+
const doctorOwnerIdentity = async (): Promise<
|
|
436
|
+
| {
|
|
437
|
+
owner: "telegram";
|
|
438
|
+
ownerId: string;
|
|
439
|
+
generation: number;
|
|
440
|
+
incarnation: string;
|
|
441
|
+
}
|
|
442
|
+
| undefined
|
|
443
|
+
> => {
|
|
444
|
+
const current = await readState(settings as Settings);
|
|
445
|
+
if (!current || !hasSafeDaemonStateShape(current) || current.ownerId !== ownerId) return undefined;
|
|
446
|
+
const expectedIncarnation = (deps.pidIncarnation ?? processIncarnation)(deps.processPid ?? process.pid);
|
|
447
|
+
if (!expectedIncarnation || current.incarnation !== expectedIncarnation || current.generation === undefined)
|
|
448
|
+
return undefined;
|
|
449
|
+
return { owner: "telegram", ownerId, generation: current.generation, incarnation: current.incarnation };
|
|
450
|
+
};
|
|
412
451
|
// Signals are a process concern: install them at the daemon-internal boundary,
|
|
413
452
|
// not inside the embeddable daemon class. SIGTERM is the reload wakeup path.
|
|
414
453
|
const onSignal = (): void => daemon.requestStop("signal");
|
|
@@ -418,6 +457,8 @@ export async function runDaemonInternal(argv: string[], deps: RunDaemonInternalD
|
|
|
418
457
|
let watchdogActive = true;
|
|
419
458
|
let watchdogTickInFlight = false;
|
|
420
459
|
let stopRequested = false;
|
|
460
|
+
let doctorPreparedRequestId: string | undefined;
|
|
461
|
+
let doctorPoll: Timer | undefined;
|
|
421
462
|
let lastHeartbeatAt: number | undefined;
|
|
422
463
|
let stalledSince: number | undefined;
|
|
423
464
|
const watchdogTick = async (): Promise<void> => {
|
|
@@ -526,8 +567,53 @@ export async function runDaemonInternal(argv: string[], deps: RunDaemonInternalD
|
|
|
526
567
|
await recordOwnerStopped();
|
|
527
568
|
});
|
|
528
569
|
try {
|
|
570
|
+
const doctorTick = async (): Promise<void> => {
|
|
571
|
+
const request = await readTelegramDoctorControlRequest(settings as Settings);
|
|
572
|
+
if (!request) return;
|
|
573
|
+
const identity = await doctorOwnerIdentity();
|
|
574
|
+
// The shared predicate is the single definition of "this request addresses
|
|
575
|
+
// exactly me"; spelling the four fields out here would let this owner and the
|
|
576
|
+
// doctor client drift apart silently.
|
|
577
|
+
if (
|
|
578
|
+
!identity ||
|
|
579
|
+
!doctorDaemonIdentityMatches(request, identity) ||
|
|
580
|
+
(request.action !== "prepare" &&
|
|
581
|
+
request.action !== "commit" &&
|
|
582
|
+
request.action !== "cancel" &&
|
|
583
|
+
request.action !== "status")
|
|
584
|
+
)
|
|
585
|
+
return;
|
|
586
|
+
if (request.leaseExpiresAt !== undefined && request.leaseExpiresAt <= (deps.now ?? Date.now)()) {
|
|
587
|
+
// Expiry never reopens admission implicitly; an authorized
|
|
588
|
+
// coordinator must issue cancel after recovering the lease.
|
|
589
|
+
return;
|
|
590
|
+
}
|
|
591
|
+
if (request.action === "prepare") {
|
|
592
|
+
daemon.prepareDoctorRestart?.();
|
|
593
|
+
doctorPreparedRequestId = request.requestId;
|
|
594
|
+
return;
|
|
595
|
+
}
|
|
596
|
+
if (request.action === "cancel") {
|
|
597
|
+
if (doctorPreparedRequestId === request.requestId) daemon.cancelDoctorRestart?.();
|
|
598
|
+
await clearTelegramDoctorControlRequest(settings as Settings, request.requestId);
|
|
599
|
+
doctorPreparedRequestId = undefined;
|
|
600
|
+
return;
|
|
601
|
+
}
|
|
602
|
+
if (request.action === "commit" && doctorPreparedRequestId === request.requestId) {
|
|
603
|
+
const occupancy = daemon.doctorOccupancy?.();
|
|
604
|
+
if (!occupancy || !doctorDaemonOccupancySettled(occupancy)) return;
|
|
605
|
+
if (!daemon.doctorRestartReady?.()) return;
|
|
606
|
+
// A committed intent is durable across this owner's exit. The
|
|
607
|
+
// successor/doctor clears it only after proving old death, cleanup,
|
|
608
|
+
// and successor publication.
|
|
609
|
+
daemon.requestStop("reload");
|
|
610
|
+
doctorPreparedRequestId = undefined;
|
|
611
|
+
}
|
|
612
|
+
};
|
|
613
|
+
doctorPoll = schedule(() => void doctorTick(), 50);
|
|
529
614
|
await daemon.run();
|
|
530
615
|
} finally {
|
|
616
|
+
if (doctorPoll !== undefined) unschedule(doctorPoll);
|
|
531
617
|
watchdogActive = false;
|
|
532
618
|
unschedule(watchdog);
|
|
533
619
|
process.off("SIGTERM", onSignal);
|
|
@@ -291,7 +291,7 @@ export const SDK_LIFECYCLE_ROUTER_PROTOCOL_VERSION = 1;
|
|
|
291
291
|
* killing a session's mirroring after one transient rejection and must be
|
|
292
292
|
* replaced across this upgrade.
|
|
293
293
|
*/
|
|
294
|
-
export const DAEMON_GENERATION =
|
|
294
|
+
export const DAEMON_GENERATION = 187;
|
|
295
295
|
|
|
296
296
|
/**
|
|
297
297
|
* Serving-compatibility boundary for daemon lifecycle requests. Epoch 7
|
|
@@ -27,6 +27,7 @@ import { resolveGjcRuntimeSpawnInfo } from "../../daemon/runtime";
|
|
|
27
27
|
import { isProcessIncarnation } from "../broker/process-incarnation";
|
|
28
28
|
|
|
29
29
|
import { getNotificationConfig, isTelegramComplete, tokenFingerprint } from "./config";
|
|
30
|
+
import { type DoctorDaemonControlRequest, isDoctorDaemonControlRequest } from "./doctor-daemon-restart";
|
|
30
31
|
import {
|
|
31
32
|
type AttestedLegacyDaemonOwner,
|
|
32
33
|
confirmTelegramDaemonSpawn,
|
|
@@ -36,6 +37,7 @@ import {
|
|
|
36
37
|
isCurrentCompatibleOwner,
|
|
37
38
|
isFreshLiveOwner,
|
|
38
39
|
isSignalableMatchingOwner,
|
|
40
|
+
isTelegramDaemonStopCause,
|
|
39
41
|
readAttestedLegacyDaemonOwner,
|
|
40
42
|
readDaemonState,
|
|
41
43
|
readOwnerFreshnessSnapshot,
|
|
@@ -65,6 +67,48 @@ export function telegramControlRequestPath(agentDir: string): string {
|
|
|
65
67
|
return path.join(daemonPaths(agentDir).dir, "telegram-daemon.control.json");
|
|
66
68
|
}
|
|
67
69
|
|
|
70
|
+
export function telegramDoctorControlRequestPath(agentDir: string): string {
|
|
71
|
+
return path.join(daemonPaths(agentDir).dir, "doctor-restart.control.json");
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export async function readTelegramDoctorControlRequest(
|
|
75
|
+
settings: Settings,
|
|
76
|
+
fsImpl: TelegramDaemonFs = nodeFs,
|
|
77
|
+
): Promise<DoctorDaemonControlRequest | undefined> {
|
|
78
|
+
try {
|
|
79
|
+
const parsed = JSON.parse(
|
|
80
|
+
await fsImpl.readFile(telegramDoctorControlRequestPath(settings.getAgentDir()), "utf8"),
|
|
81
|
+
) as unknown;
|
|
82
|
+
return isDoctorDaemonControlRequest(parsed) && parsed.owner === "telegram" ? parsed : undefined;
|
|
83
|
+
} catch {
|
|
84
|
+
return undefined;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export async function writeTelegramDoctorControlRequest(
|
|
89
|
+
settings: Settings,
|
|
90
|
+
request: DoctorDaemonControlRequest,
|
|
91
|
+
fsImpl: TelegramDaemonFs = nodeFs,
|
|
92
|
+
): Promise<void> {
|
|
93
|
+
if (request.owner !== "telegram") throw new Error("telegram doctor request owner mismatch");
|
|
94
|
+
const dir = daemonPaths(settings.getAgentDir()).dir;
|
|
95
|
+
await fsImpl.mkdir(dir, { recursive: true, mode: 0o700 });
|
|
96
|
+
const file = telegramDoctorControlRequestPath(settings.getAgentDir());
|
|
97
|
+
const tmp = `${file}.${process.pid}.${Date.now()}.tmp`;
|
|
98
|
+
await fsImpl.writeFile(tmp, `${JSON.stringify(request)}\n`, { mode: 0o600 });
|
|
99
|
+
await fsImpl.rename(tmp, file);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export async function clearTelegramDoctorControlRequest(
|
|
103
|
+
settings: Settings,
|
|
104
|
+
requestId?: string,
|
|
105
|
+
fsImpl: TelegramDaemonFs = nodeFs,
|
|
106
|
+
): Promise<void> {
|
|
107
|
+
const file = telegramDoctorControlRequestPath(settings.getAgentDir());
|
|
108
|
+
if (requestId && (await readTelegramDoctorControlRequest(settings, fsImpl))?.requestId !== requestId) return;
|
|
109
|
+
await fsImpl.unlink(file).catch(() => undefined);
|
|
110
|
+
}
|
|
111
|
+
|
|
68
112
|
export async function readTelegramControlRequest(
|
|
69
113
|
settings: Settings,
|
|
70
114
|
fsImpl: TelegramDaemonFs = nodeFs,
|
|
@@ -236,6 +280,8 @@ export class TelegramDaemonController implements BuiltInDaemonController {
|
|
|
236
280
|
})
|
|
237
281
|
? "running"
|
|
238
282
|
: "stale";
|
|
283
|
+
const stopCause = isTelegramDaemonStopCause(state?.stopCause) ? state.stopCause : undefined;
|
|
284
|
+
const detail = health === "stopped" && state ? `stop cause: ${stopCause ?? "unknown"}` : undefined;
|
|
239
285
|
return {
|
|
240
286
|
kind: this.kind,
|
|
241
287
|
configured: true,
|
|
@@ -244,6 +290,7 @@ export class TelegramDaemonController implements BuiltInDaemonController {
|
|
|
244
290
|
ownerId: state?.ownerId,
|
|
245
291
|
startedAt: state?.startedAt,
|
|
246
292
|
heartbeatAt: snapshot.effectiveHeartbeatAt,
|
|
293
|
+
...(detail === undefined ? {} : { detail }),
|
|
247
294
|
runtime,
|
|
248
295
|
};
|
|
249
296
|
}
|
|
@@ -15,7 +15,7 @@ export interface TelegramOwnerMarker {
|
|
|
15
15
|
startedAt: number;
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
-
function validMarker(value: unknown): value is TelegramOwnerMarker {
|
|
18
|
+
export function validMarker(value: unknown): value is TelegramOwnerMarker {
|
|
19
19
|
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
|
|
20
20
|
const v = value as Record<string, unknown>;
|
|
21
21
|
return (
|