@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
|
@@ -26,6 +26,7 @@ import {
|
|
|
26
26
|
parseToolActivityToggleCommand,
|
|
27
27
|
} from "./config-commands";
|
|
28
28
|
import { agentDirDigest, daemonPaths, HEARTBEAT_TTL_MS } from "./daemon-paths";
|
|
29
|
+
import { type DoctorDaemonOccupancy, doctorDaemonOccupancySettled } from "./doctor-daemon-restart";
|
|
29
30
|
import {
|
|
30
31
|
acquireDaemonTransitionLock,
|
|
31
32
|
type DaemonTransitionLock,
|
|
@@ -160,6 +161,33 @@ export type EnsureTelegramDaemonDetailedResult = "spawned" | "reloaded" | "attac
|
|
|
160
161
|
|
|
161
162
|
export type TelegramDaemonOwnershipPhase = "provisional" | "ready" | "retired";
|
|
162
163
|
|
|
164
|
+
/** Closed vocabulary for the durable reason an owner stopped serving. */
|
|
165
|
+
export type TelegramDaemonStopCause =
|
|
166
|
+
| "stop"
|
|
167
|
+
| "reload"
|
|
168
|
+
| "signal"
|
|
169
|
+
| "idle_timeout"
|
|
170
|
+
| "ownership_loss"
|
|
171
|
+
| "startup_failed"
|
|
172
|
+
| "unexpected_exception"
|
|
173
|
+
| "postmortem";
|
|
174
|
+
|
|
175
|
+
export function isTelegramDaemonStopCause(value: unknown): value is TelegramDaemonStopCause {
|
|
176
|
+
switch (value) {
|
|
177
|
+
case "stop":
|
|
178
|
+
case "reload":
|
|
179
|
+
case "signal":
|
|
180
|
+
case "idle_timeout":
|
|
181
|
+
case "ownership_loss":
|
|
182
|
+
case "startup_failed":
|
|
183
|
+
case "unexpected_exception":
|
|
184
|
+
case "postmortem":
|
|
185
|
+
return true;
|
|
186
|
+
default:
|
|
187
|
+
return false;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
163
191
|
export interface DaemonState {
|
|
164
192
|
pid: number;
|
|
165
193
|
/** OS process-start provenance; mandatory for PID-authorized ownership actions. */
|
|
@@ -189,6 +217,8 @@ export interface DaemonState {
|
|
|
189
217
|
/** Lifecycle-serving compatibility epoch; absent pre-epoch records are epoch 1. */
|
|
190
218
|
servingEpoch?: number;
|
|
191
219
|
stoppedAt?: number;
|
|
220
|
+
/** Bounded, secret-free explanation for a stopped owner; absent on legacy/unknown records. */
|
|
221
|
+
stopCause?: TelegramDaemonStopCause;
|
|
192
222
|
}
|
|
193
223
|
interface ExactFileStat {
|
|
194
224
|
dev: bigint;
|
|
@@ -1640,6 +1670,7 @@ export function hasSafeDaemonStateShape(state: unknown): state is DaemonState {
|
|
|
1640
1670
|
(candidate.generation === undefined ||
|
|
1641
1671
|
(Number.isSafeInteger(candidate.generation) && (candidate.generation as number) > 0)) &&
|
|
1642
1672
|
(candidate.stoppedAt === undefined || Number.isSafeInteger(candidate.stoppedAt)) &&
|
|
1673
|
+
(candidate.stopCause === undefined || isTelegramDaemonStopCause(candidate.stopCause)) &&
|
|
1643
1674
|
(candidate.servingEpoch === undefined ||
|
|
1644
1675
|
(Number.isSafeInteger(candidate.servingEpoch) && (candidate.servingEpoch as number) > 0)),
|
|
1645
1676
|
);
|
|
@@ -2471,6 +2502,7 @@ export async function renewDaemonHeartbeat(input: {
|
|
|
2471
2502
|
...boundState,
|
|
2472
2503
|
ownershipPhase: "retired",
|
|
2473
2504
|
stoppedAt: (input.now ?? Date.now)(),
|
|
2505
|
+
stopCause: boundState.stopCause ?? "startup_failed",
|
|
2474
2506
|
}).catch(() => undefined);
|
|
2475
2507
|
if (await transitionLockIsHeldByCaller({ fs: fsImpl, path: paths.steal, lock: transition }))
|
|
2476
2508
|
await unlinkOwnershipLockExactly(fsImpl, paths.lock, lock);
|
|
@@ -2609,10 +2641,14 @@ export async function retireProvisionalDaemonOwnership(input: {
|
|
|
2609
2641
|
if (!(await transitionLockIsHeldByCaller({ fs: fsImpl, path: paths.steal, lock: transition }))) return false;
|
|
2610
2642
|
const lock = await readOwnershipLock(fsImpl, paths.lock);
|
|
2611
2643
|
if (!ownershipLockMatchesState(lock, state)) return false;
|
|
2644
|
+
if (!(await transitionLockIsHeldByCaller({ fs: fsImpl, path: paths.steal, lock: transition }))) return false;
|
|
2645
|
+
const stoppedAt = state.stoppedAt ?? (input.now ?? Date.now)();
|
|
2646
|
+
const stopCause = state.stopCause ?? (state.stoppedAt === undefined ? "startup_failed" : undefined);
|
|
2612
2647
|
await writeJsonAtomic(fsImpl, paths.state, {
|
|
2613
2648
|
...state,
|
|
2614
2649
|
ownershipPhase: "retired",
|
|
2615
|
-
stoppedAt
|
|
2650
|
+
stoppedAt,
|
|
2651
|
+
...(stopCause === undefined ? {} : { stopCause }),
|
|
2616
2652
|
});
|
|
2617
2653
|
if (!(await transitionLockIsHeldByCaller({ fs: fsImpl, path: paths.steal, lock: transition }))) return false;
|
|
2618
2654
|
return await unlinkOwnershipLockExactly(fsImpl, paths.lock, lock);
|
|
@@ -2859,6 +2895,7 @@ export async function releaseDaemonOwnership(input: {
|
|
|
2859
2895
|
pidIncarnation?: (pid: number) => string | undefined;
|
|
2860
2896
|
fs?: TelegramDaemonFs;
|
|
2861
2897
|
now?: () => number;
|
|
2898
|
+
stopCause?: TelegramDaemonStopCause;
|
|
2862
2899
|
}): Promise<void> {
|
|
2863
2900
|
const fsImpl = input.fs ?? nodeFs;
|
|
2864
2901
|
const paths = daemonPaths(input.settings.getAgentDir());
|
|
@@ -2890,7 +2927,15 @@ export async function releaseDaemonOwnership(input: {
|
|
|
2890
2927
|
if (!(await transitionLockIsHeldByCaller({ fs: fsImpl, path: paths.steal, lock: transition }))) return;
|
|
2891
2928
|
const lock = await readOwnershipLock(fsImpl, paths.lock);
|
|
2892
2929
|
if (!ownershipLockMatchesState(lock, state)) return;
|
|
2893
|
-
|
|
2930
|
+
const stoppedAt = state.stoppedAt ?? (input.now ?? Date.now)();
|
|
2931
|
+
const stopCause =
|
|
2932
|
+
state.stopCause ??
|
|
2933
|
+
(state.stoppedAt === undefined && isTelegramDaemonStopCause(input.stopCause) ? input.stopCause : undefined);
|
|
2934
|
+
await writeJsonAtomic(fsImpl, paths.state, {
|
|
2935
|
+
...state,
|
|
2936
|
+
stoppedAt,
|
|
2937
|
+
...(stopCause === undefined ? {} : { stopCause }),
|
|
2938
|
+
});
|
|
2894
2939
|
try {
|
|
2895
2940
|
const { removeTelegramOwnerMarker } = await import("./telegram-daemon-owner-registry");
|
|
2896
2941
|
await removeTelegramOwnerMarker(fsImpl, input.settings.getAgentDir(), state.acquisitionId ?? state.ownerId);
|
|
@@ -2912,9 +2957,9 @@ export async function releaseDaemonOwnership(input: {
|
|
|
2912
2957
|
* `ownershipPhase: "ready"` and a fresh-looking lock behind forever, so later
|
|
2913
2958
|
* readers attached to an owner that had not existed for hours.
|
|
2914
2959
|
*
|
|
2915
|
-
* This writes `stoppedAt` and
|
|
2916
|
-
* existing reclaim path to adjudicate, because a process on
|
|
2917
|
-
* least qualified party to decide who owns what next.
|
|
2960
|
+
* This writes `stoppedAt` and a closed-vocabulary `stopCause`. The lock is left
|
|
2961
|
+
* in place for the existing reclaim path to adjudicate, because a process on
|
|
2962
|
+
* its way out is the least qualified party to decide who owns what next.
|
|
2918
2963
|
*
|
|
2919
2964
|
* Fenced on full owner identity: if the state no longer names this exact
|
|
2920
2965
|
* owner, acquisition, pid and incarnation, a successor already took over and
|
|
@@ -2950,7 +2995,12 @@ export async function markDaemonOwnerStopped(input: {
|
|
|
2950
2995
|
return false;
|
|
2951
2996
|
const lock = await readOwnershipLock(fsImpl, paths.lock);
|
|
2952
2997
|
if (!ownershipLockMatchesState(lock, state)) return false;
|
|
2953
|
-
|
|
2998
|
+
const stopCause = state.stopCause ?? "postmortem";
|
|
2999
|
+
await writeJsonAtomic(fsImpl, paths.state, {
|
|
3000
|
+
...state,
|
|
3001
|
+
stoppedAt: (input.now ?? Date.now)(),
|
|
3002
|
+
stopCause,
|
|
3003
|
+
});
|
|
2954
3004
|
try {
|
|
2955
3005
|
const { removeTelegramOwnerMarker } = await import("./telegram-daemon-owner-registry");
|
|
2956
3006
|
await removeTelegramOwnerMarker(fsImpl, input.settings.getAgentDir(), state.acquisitionId ?? state.ownerId);
|
|
@@ -3992,6 +4042,12 @@ export class TelegramEventDispatchState {
|
|
|
3992
4042
|
export interface DaemonControlHooks {
|
|
3993
4043
|
/** Returns true when a stop/reload has been requested for this owner. */
|
|
3994
4044
|
shouldStop(ownerId: string): Promise<boolean>;
|
|
4045
|
+
/**
|
|
4046
|
+
* Returns the owner-fenced action behind `shouldStop`, when the control
|
|
4047
|
+
* plane can provide one. This is a diagnostic projection, not an alternate
|
|
4048
|
+
* stop authority: callers must still gate it behind `shouldStop`.
|
|
4049
|
+
*/
|
|
4050
|
+
requestedAction?(ownerId: string): Promise<"reload" | "stop" | undefined>;
|
|
3995
4051
|
/** Clear a consumed control request (best-effort). */
|
|
3996
4052
|
clear?(ownerId: string): Promise<void>;
|
|
3997
4053
|
}
|
|
@@ -4405,10 +4461,18 @@ class TelegramEffectSupervisor {
|
|
|
4405
4461
|
return this.#stopping;
|
|
4406
4462
|
}
|
|
4407
4463
|
|
|
4464
|
+
get admitting(): boolean {
|
|
4465
|
+
return this.#admitting;
|
|
4466
|
+
}
|
|
4467
|
+
|
|
4408
4468
|
closeAdmission(): void {
|
|
4409
4469
|
this.#admitting = false;
|
|
4410
4470
|
}
|
|
4411
4471
|
|
|
4472
|
+
openAdmission(): void {
|
|
4473
|
+
if (!this.#stopping) this.#admitting = true;
|
|
4474
|
+
}
|
|
4475
|
+
|
|
4412
4476
|
beginShutdown(): void {
|
|
4413
4477
|
this.closeAdmission();
|
|
4414
4478
|
this.#stopping = true;
|
|
@@ -4496,6 +4560,10 @@ export class TelegramNotificationDaemon {
|
|
|
4496
4560
|
private running = false;
|
|
4497
4561
|
/** Once set, a concurrent startup await can never restore a running daemon. */
|
|
4498
4562
|
private stopRequested = false;
|
|
4563
|
+
/** First observed stop cause wins, including an explicit stop. */
|
|
4564
|
+
#stopCause: TelegramDaemonStopCause | undefined;
|
|
4565
|
+
/** Signal cause resolution is asynchronous because control requests are file-backed. */
|
|
4566
|
+
#stopCauseResolution: Promise<void> | undefined;
|
|
4499
4567
|
|
|
4500
4568
|
private readonly fsImpl: TelegramDaemonFs;
|
|
4501
4569
|
readonly #deliveryAbort = new AbortController();
|
|
@@ -4652,12 +4720,47 @@ export class TelegramNotificationDaemon {
|
|
|
4652
4720
|
}
|
|
4653
4721
|
}
|
|
4654
4722
|
|
|
4723
|
+
#resolveControlStopCause(fallback: "signal" | "stop"): void {
|
|
4724
|
+
if (this.#stopCause !== undefined || this.#stopCauseResolution !== undefined) return;
|
|
4725
|
+
const requestedAction = this.opts.control?.requestedAction;
|
|
4726
|
+
if (!requestedAction) {
|
|
4727
|
+
this.#stopCause = fallback;
|
|
4728
|
+
return;
|
|
4729
|
+
}
|
|
4730
|
+
// Diagnostics must never hold ownership cleanup behind a stalled file read.
|
|
4731
|
+
const deadline = Promise.withResolvers<void>();
|
|
4732
|
+
const timer = setTimeout(() => {
|
|
4733
|
+
this.#stopCause ??= fallback;
|
|
4734
|
+
deadline.resolve();
|
|
4735
|
+
}, 250);
|
|
4736
|
+
const lookup = Promise.resolve()
|
|
4737
|
+
.then(() => requestedAction.call(this.opts.control, this.opts.ownerId))
|
|
4738
|
+
.then(
|
|
4739
|
+
action => {
|
|
4740
|
+
this.#stopCause ??= action === "reload" || action === "stop" ? action : fallback;
|
|
4741
|
+
},
|
|
4742
|
+
() => {
|
|
4743
|
+
this.#stopCause ??= fallback;
|
|
4744
|
+
},
|
|
4745
|
+
);
|
|
4746
|
+
this.#stopCauseResolution = Promise.race([lookup, deadline.promise]).finally(() => clearTimeout(timer));
|
|
4747
|
+
}
|
|
4748
|
+
|
|
4655
4749
|
/**
|
|
4656
4750
|
* Cooperatively stop the daemon: set the stop flag and abort the in-flight
|
|
4657
4751
|
* long poll so the run loop wakes immediately instead of waiting out the
|
|
4658
|
-
* ~25s getUpdates timeout. Safe to call from a signal handler.
|
|
4752
|
+
* ~25s getUpdates timeout. Safe to call from a signal handler. The
|
|
4753
|
+
* no-argument form is the generic finally fallback and never overwrites a
|
|
4754
|
+
* previously observed specific cause.
|
|
4659
4755
|
*/
|
|
4660
|
-
requestStop(
|
|
4756
|
+
requestStop(reason?: TelegramDaemonStopCause): void {
|
|
4757
|
+
if (reason === "signal") {
|
|
4758
|
+
this.#resolveControlStopCause("signal");
|
|
4759
|
+
} else if (reason !== undefined && this.#stopCauseResolution === undefined) {
|
|
4760
|
+
this.#stopCause ??= reason;
|
|
4761
|
+
} else if (this.#stopCauseResolution === undefined) {
|
|
4762
|
+
this.#stopCause ??= "stop";
|
|
4763
|
+
}
|
|
4661
4764
|
this.stopRequested = true;
|
|
4662
4765
|
this.effects.closeAdmission();
|
|
4663
4766
|
this.#deliveryAbort.abort();
|
|
@@ -4705,6 +4808,45 @@ export class TelegramNotificationDaemon {
|
|
|
4705
4808
|
this.running = false;
|
|
4706
4809
|
}
|
|
4707
4810
|
|
|
4811
|
+
/** Doctor reservation hook: stop admitting new provider work without killing the owner. Synchronous fence; returns a real acknowledgement. */
|
|
4812
|
+
prepareDoctorRestart(): boolean {
|
|
4813
|
+
this.effects.closeAdmission();
|
|
4814
|
+
return !this.effects.admitting;
|
|
4815
|
+
}
|
|
4816
|
+
|
|
4817
|
+
/**
|
|
4818
|
+
* Real occupancy read from the live runtime structures the daemon actually
|
|
4819
|
+
* uses, not synthetic zeros. `inflight` is turns currently executing;
|
|
4820
|
+
* `inbound` is inbound work accepted but not yet resolved (pending /btw
|
|
4821
|
+
* turns, topic-adoption session_create dispatches in flight, and inbound
|
|
4822
|
+
* reaction acks not yet terminal); `outbound` is the real rate-limit pool
|
|
4823
|
+
* backlog plus terminal /btw deliveries still in flight; `cleanup` counts
|
|
4824
|
+
* only genuinely outstanding cleanup — non-completed cleanup receipts plus
|
|
4825
|
+
* live archive/compensation fences — never a completed receipt or an
|
|
4826
|
+
* unrelated rejected-topic notice timer.
|
|
4827
|
+
*/
|
|
4828
|
+
doctorOccupancy(): DoctorDaemonOccupancy {
|
|
4829
|
+
const outstandingCleanupReceipts = [...this.#cleanupReceipts.values()].filter(
|
|
4830
|
+
receipt => receipt.state !== "completed",
|
|
4831
|
+
).length;
|
|
4832
|
+
return {
|
|
4833
|
+
attached: this.sessions.size,
|
|
4834
|
+
inflight: this.busy.size,
|
|
4835
|
+
inbound:
|
|
4836
|
+
this.#pendingBtwTurns.size + this.#adoptionStartingTopics.size + this.dispatchState.inboundReactions.size,
|
|
4837
|
+
outbound: this.pool.pending + this.#btwTerminalDeliveries.size,
|
|
4838
|
+
cleanup: outstandingCleanupReceipts + this.archiveFlights.size + this.compensationFenceRetries.size,
|
|
4839
|
+
};
|
|
4840
|
+
}
|
|
4841
|
+
|
|
4842
|
+
doctorRestartReady(): boolean {
|
|
4843
|
+
return doctorDaemonOccupancySettled(this.doctorOccupancy());
|
|
4844
|
+
}
|
|
4845
|
+
|
|
4846
|
+
cancelDoctorRestart(): void {
|
|
4847
|
+
if (!this.stopRequested) this.effects.openAdmission();
|
|
4848
|
+
}
|
|
4849
|
+
|
|
4708
4850
|
#lifecycleActor(): SessionLifecycleActor {
|
|
4709
4851
|
return { id: String(this.opts.chatId), namespace: `telegram:${tokenFingerprint(this.opts.botToken)}` };
|
|
4710
4852
|
}
|
|
@@ -8884,7 +9026,7 @@ export class TelegramNotificationDaemon {
|
|
|
8884
9026
|
);
|
|
8885
9027
|
}
|
|
8886
9028
|
private submitPool(item: Parameters<RateLimitPool<TelegramQueuePayload>["submit"]>[0]): boolean {
|
|
8887
|
-
if (this.stopRequested || this.effects.stopping) return false;
|
|
9029
|
+
if (this.stopRequested || this.effects.stopping || !this.effects.admitting) return false;
|
|
8888
9030
|
this.pool.submit(item);
|
|
8889
9031
|
return true;
|
|
8890
9032
|
}
|
|
@@ -9832,7 +9974,7 @@ export class TelegramNotificationDaemon {
|
|
|
9832
9974
|
// and the next interval republishes (#4200). Only a proven
|
|
9833
9975
|
// ownership loss stops the owner.
|
|
9834
9976
|
if ((await this.renewOwnershipHeartbeat()) === "not_owner") {
|
|
9835
|
-
this.
|
|
9977
|
+
this.requestStop("ownership_loss");
|
|
9836
9978
|
return;
|
|
9837
9979
|
}
|
|
9838
9980
|
await this.renewActiveTopicLeases();
|
|
@@ -11949,6 +12091,16 @@ export class TelegramNotificationDaemon {
|
|
|
11949
12091
|
if (this.validationMode() || !this.#topicRegistryMutationAllowed()) return;
|
|
11950
12092
|
if ((await this.handleForumTopicCreatedUpdate(update)) !== "not-topic") return;
|
|
11951
12093
|
if ((await this.handleForumTopicEdited(update)) !== "not-topic") return;
|
|
12094
|
+
// Doctor admission fence: forum-topic bookkeeping above is structural
|
|
12095
|
+
// registry housekeeping for topics that already exist, not new user-facing
|
|
12096
|
+
// work, so it stays live. Everything below this line admits genuinely NEW
|
|
12097
|
+
// inbound work — adoption/session_create, /session_* lifecycle commands,
|
|
12098
|
+
// /btw turns, model-picker taps, and inline session control dispatch — and
|
|
12099
|
+
// MUST be refused while the owner has closed admission for a doctor
|
|
12100
|
+
// restart. Already-active turns and the outbound queue are untouched here:
|
|
12101
|
+
// they drain through their own existing paths (RateLimitPool.drain,
|
|
12102
|
+
// in-flight session turns), never invalidated by this refusal.
|
|
12103
|
+
if (!this.effects.admitting) return;
|
|
11952
12104
|
// A raw path is accepted only after the explicit direct-entry choice. The exact
|
|
11953
12105
|
// `/session_create path <dir>` form remains available in any pending topic.
|
|
11954
12106
|
// Both must precede the general lifecycle handler, which would otherwise create
|
|
@@ -12803,7 +12955,10 @@ export class TelegramNotificationDaemon {
|
|
|
12803
12955
|
`notifications: ownership heartbeat renewal threw; continuing: ${sanitizeDiagnostic(String(error))}`,
|
|
12804
12956
|
);
|
|
12805
12957
|
}
|
|
12806
|
-
if (!ownerHeld)
|
|
12958
|
+
if (!ownerHeld) {
|
|
12959
|
+
this.requestStop("ownership_loss");
|
|
12960
|
+
break;
|
|
12961
|
+
}
|
|
12807
12962
|
if (await this.controlStopRequested()) break;
|
|
12808
12963
|
const idleElapsed = this.runtime.now() - idleSince >= (this.opts.idleTimeoutMs ?? 60_000);
|
|
12809
12964
|
if (this.sessions.size > 0) {
|
|
@@ -12811,6 +12966,7 @@ export class TelegramNotificationDaemon {
|
|
|
12811
12966
|
} else if (idleElapsed) {
|
|
12812
12967
|
// Zero sessions past the idle window: exit so the owner does not run
|
|
12813
12968
|
// forever. An active session resets the idle window above.
|
|
12969
|
+
this.requestStop("idle_timeout");
|
|
12814
12970
|
break;
|
|
12815
12971
|
}
|
|
12816
12972
|
// Poll getUpdates whenever the daemon owns the token — even with zero
|
|
@@ -12836,8 +12992,12 @@ export class TelegramNotificationDaemon {
|
|
|
12836
12992
|
if (await this.controlStopRequested()) break;
|
|
12837
12993
|
await this.runtime.sleep(10);
|
|
12838
12994
|
}
|
|
12995
|
+
} catch (error) {
|
|
12996
|
+
this.requestStop("unexpected_exception");
|
|
12997
|
+
throw error;
|
|
12839
12998
|
} finally {
|
|
12840
|
-
this.requestStop(
|
|
12999
|
+
this.requestStop();
|
|
13000
|
+
await this.#stopCauseResolution;
|
|
12841
13001
|
this.running = false;
|
|
12842
13002
|
await this.#attachmentRouter
|
|
12843
13003
|
.stop()
|
|
@@ -12911,6 +13071,7 @@ export class TelegramNotificationDaemon {
|
|
|
12911
13071
|
pidIncarnation: this.opts.pidIncarnation,
|
|
12912
13072
|
fs: this.fsImpl,
|
|
12913
13073
|
now: this.opts.now,
|
|
13074
|
+
stopCause: this.#stopCause,
|
|
12914
13075
|
});
|
|
12915
13076
|
}
|
|
12916
13077
|
}
|
|
@@ -12922,7 +13083,11 @@ export class TelegramNotificationDaemon {
|
|
|
12922
13083
|
if (this.runtime.stopRequested) return true;
|
|
12923
13084
|
if (!this.opts.control) return false;
|
|
12924
13085
|
try {
|
|
12925
|
-
|
|
13086
|
+
const requested = await this.opts.control.shouldStop(this.opts.ownerId);
|
|
13087
|
+
if (!requested) return false;
|
|
13088
|
+
this.#resolveControlStopCause("stop");
|
|
13089
|
+
this.requestStop();
|
|
13090
|
+
return true;
|
|
12926
13091
|
} catch {
|
|
12927
13092
|
return false;
|
|
12928
13093
|
}
|
|
@@ -86,6 +86,7 @@ export interface SdkSessionCliArgs {
|
|
|
86
86
|
strict?: boolean;
|
|
87
87
|
untilIdle?: boolean;
|
|
88
88
|
allEvents?: boolean;
|
|
89
|
+
page?: boolean;
|
|
89
90
|
repo?: string;
|
|
90
91
|
scope?: string;
|
|
91
92
|
limit?: number;
|
|
@@ -95,7 +96,7 @@ export interface SdkSessionCliArgs {
|
|
|
95
96
|
}
|
|
96
97
|
|
|
97
98
|
type JsonRecord = Record<string, unknown>;
|
|
98
|
-
type LifecycleMutationOperation = Exclude<SessionLifecycleOperation, "session.list">;
|
|
99
|
+
type LifecycleMutationOperation = Exclude<SessionLifecycleOperation, "session.lookup" | "session.list">;
|
|
99
100
|
type TailExitReason = "idle" | "close";
|
|
100
101
|
export interface RetainedTranscriptTailReader {
|
|
101
102
|
readonly size: number;
|
|
@@ -278,15 +279,27 @@ function reportRouterCleanupFailure(error: unknown): void {
|
|
|
278
279
|
process.stderr.write(`SDK session Router cleanup failed: ${message}\n`);
|
|
279
280
|
}
|
|
280
281
|
|
|
282
|
+
/**
|
|
283
|
+
* Sessions a CLI-opened Router is allowed to attach to. Required, and required
|
|
284
|
+
* to be exhaustive: attaching is not free and is not invisible, because every
|
|
285
|
+
* attachment registers this process as a live client on that host and renews
|
|
286
|
+
* the host's abandonment window. An omitted scope used to mean "the whole
|
|
287
|
+
* fleet", so a single-session command silently kept every unrelated host alive.
|
|
288
|
+
*
|
|
289
|
+
* `BROKER_ONLY` is the empty scope, for work the Broker answers over its own
|
|
290
|
+
* client (`session.list`) and which therefore needs no session transport at all.
|
|
291
|
+
*/
|
|
292
|
+
const BROKER_ONLY: readonly string[] = [];
|
|
293
|
+
|
|
281
294
|
async function withRouter<T>(
|
|
282
295
|
agentDir: string,
|
|
296
|
+
sessionIds: readonly string[],
|
|
283
297
|
action: (router: SessionRouter) => Promise<T>,
|
|
284
298
|
onFrame?: (attachment: SessionAttachment, frame: SessionRouterFrame) => void,
|
|
285
|
-
sessionIds?: readonly string[],
|
|
286
299
|
): Promise<T> {
|
|
287
300
|
const router = new SessionRouter({
|
|
288
301
|
agentDir,
|
|
289
|
-
|
|
302
|
+
sessionIds,
|
|
290
303
|
...(onFrame === undefined ? {} : { deps: { onFrame } }),
|
|
291
304
|
});
|
|
292
305
|
let result!: T;
|
|
@@ -370,6 +383,36 @@ async function paginatedSessionList(
|
|
|
370
383
|
}
|
|
371
384
|
}
|
|
372
385
|
|
|
386
|
+
async function singleSessionListPage(
|
|
387
|
+
router: SessionRouter,
|
|
388
|
+
input: JsonRecord = {},
|
|
389
|
+
requestKey = `${SDK_SESSION_CLI_LIFECYCLE_ACTOR.namespace}:session.list:${randomBytes(12).toString("hex")}`,
|
|
390
|
+
): Promise<unknown> {
|
|
391
|
+
try {
|
|
392
|
+
const cursor = typeof input.cursor === "string" ? input.cursor : "initial";
|
|
393
|
+
const response = object(await router.listBrokerSessions(input, `${requestKey}:${cursor}`));
|
|
394
|
+
if (response?.ok === false) {
|
|
395
|
+
const failure = object(response.error);
|
|
396
|
+
throw new SdkClientError(
|
|
397
|
+
typeof failure?.code === "string" ? failure.code : "broker_error",
|
|
398
|
+
typeof failure?.message === "string" ? failure.message : "session.list failed",
|
|
399
|
+
);
|
|
400
|
+
}
|
|
401
|
+
const page = sessionListPageFromResponse(response);
|
|
402
|
+
const continuationCursor = page?.continuationCursor;
|
|
403
|
+
if (
|
|
404
|
+
!page ||
|
|
405
|
+
(continuationCursor !== undefined &&
|
|
406
|
+
(typeof continuationCursor !== "string" || continuationCursor.length === 0))
|
|
407
|
+
)
|
|
408
|
+
throw new SessionListTraversalError("malformed_page");
|
|
409
|
+
return response;
|
|
410
|
+
} catch (error) {
|
|
411
|
+
if (error instanceof SessionListTraversalError) throw new SdkClientError("protocol_error", error.message);
|
|
412
|
+
throw error;
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
|
|
373
416
|
type SessionRows = {
|
|
374
417
|
indexSeq?: number;
|
|
375
418
|
warnings: unknown[];
|
|
@@ -378,7 +421,12 @@ type SessionRows = {
|
|
|
378
421
|
|
|
379
422
|
async function sessionRows(agentDir: string, input: JsonRecord = {}): Promise<SessionRows> {
|
|
380
423
|
await ensureBroker({ agentDir });
|
|
381
|
-
|
|
424
|
+
// `session.list` is answered by the Broker over its own client, so resolving a
|
|
425
|
+
// row needs no session transport. `runInspect` and `runTail` resolve exactly
|
|
426
|
+
// one session through here, and an unscoped lookup attached every live
|
|
427
|
+
// session in the directory before the scoped operation that follows it ever
|
|
428
|
+
// ran — renewing the abandonment window of hosts the caller never named.
|
|
429
|
+
return await withRouter(agentDir, BROKER_ONLY, async router => {
|
|
382
430
|
const response = await paginatedSessionList(router, input);
|
|
383
431
|
const result = resultObject(response) ?? {};
|
|
384
432
|
let sessions: SdkSessionRowV1[];
|
|
@@ -433,9 +481,8 @@ async function probeSearchRows(agentDir: string, result: SdkSearchResultV1): Pro
|
|
|
433
481
|
try {
|
|
434
482
|
const probes = await withRouter(
|
|
435
483
|
agentDir,
|
|
436
|
-
async router => await Promise.all(rows.map(row => searchProbe(row, router))),
|
|
437
|
-
undefined,
|
|
438
484
|
rows.map(row => row.id),
|
|
485
|
+
async router => await Promise.all(rows.map(row => searchProbe(row, router))),
|
|
439
486
|
);
|
|
440
487
|
return { ...result, rows: mergeProbedSearchRows(result.rows, probes) };
|
|
441
488
|
} catch {
|
|
@@ -913,7 +960,7 @@ async function runSend(agentDir: string, sessionId: string, args: SdkSessionCliA
|
|
|
913
960
|
if (invalid) throw new SdkSessionCliError(invalid.code, invalid.message, 2);
|
|
914
961
|
await ensureBroker({ agentDir });
|
|
915
962
|
|
|
916
|
-
return await withRouter(agentDir, async router => {
|
|
963
|
+
return await withRouter(agentDir, [sessionId], async router => {
|
|
917
964
|
const response = await requestControl(router, sessionId, "turn.prompt", promptInput, args);
|
|
918
965
|
const result: JsonRecord = {
|
|
919
966
|
version: SESSION_ROWS_VERSION,
|
|
@@ -945,7 +992,7 @@ async function runStatus(
|
|
|
945
992
|
): Promise<unknown> {
|
|
946
993
|
assertClientRef(opRef);
|
|
947
994
|
await ensureBroker({ agentDir });
|
|
948
|
-
return await withRouter(agentDir, async router => {
|
|
995
|
+
return await withRouter(agentDir, [sessionId], async router => {
|
|
949
996
|
const response = await requestQuery(router, sessionId, "turn.result", { kind: "prompt", clientRef: opRef }, args);
|
|
950
997
|
const status = resultObject(response) ?? {};
|
|
951
998
|
const raw = typeof status.status === "string" ? status.status : "unknown";
|
|
@@ -1508,6 +1555,7 @@ async function runLiveTail(
|
|
|
1508
1555
|
|
|
1509
1556
|
return await withRouter(
|
|
1510
1557
|
agentDir,
|
|
1558
|
+
[sessionId],
|
|
1511
1559
|
async router => {
|
|
1512
1560
|
const attachment = attachmentFor(router, sessionId);
|
|
1513
1561
|
const checkpointResponse = await router.request(
|
|
@@ -1695,7 +1743,11 @@ async function runRawControl(
|
|
|
1695
1743
|
await ensureBroker({ agentDir });
|
|
1696
1744
|
const operatorRequest = operatorAbortBrokerRequest(sessionId, operation, input, args);
|
|
1697
1745
|
if (operatorRequest) return await requestBrokerOperatorAbort(agentDir, operatorRequest, args);
|
|
1698
|
-
return await withRouter(
|
|
1746
|
+
return await withRouter(
|
|
1747
|
+
agentDir,
|
|
1748
|
+
[sessionId],
|
|
1749
|
+
async router => await requestControl(router, sessionId, operation, input, args),
|
|
1750
|
+
);
|
|
1699
1751
|
}
|
|
1700
1752
|
|
|
1701
1753
|
async function runRawQuery(
|
|
@@ -1706,7 +1758,11 @@ async function runRawQuery(
|
|
|
1706
1758
|
args: SdkSessionCliArgs,
|
|
1707
1759
|
): Promise<unknown> {
|
|
1708
1760
|
await ensureBroker({ agentDir });
|
|
1709
|
-
return await withRouter(
|
|
1761
|
+
return await withRouter(
|
|
1762
|
+
agentDir,
|
|
1763
|
+
[sessionId],
|
|
1764
|
+
async router => await requestQuery(router, sessionId, operation, input, args),
|
|
1765
|
+
);
|
|
1710
1766
|
}
|
|
1711
1767
|
|
|
1712
1768
|
function lifecycleMutationRequest(
|
|
@@ -1753,9 +1809,39 @@ async function runRawGlobal(
|
|
|
1753
1809
|
input: JsonRecord,
|
|
1754
1810
|
args: SdkSessionCliArgs,
|
|
1755
1811
|
): Promise<unknown> {
|
|
1812
|
+
if (args.page && operation !== "session.list")
|
|
1813
|
+
throw new SdkSessionCliError("usage", "--page is only available for raw global session.list.", 2);
|
|
1756
1814
|
if (operation === "session.list") {
|
|
1757
1815
|
await ensureBroker({ agentDir });
|
|
1758
|
-
|
|
1816
|
+
const pageInput = { ...input };
|
|
1817
|
+
if (args.cursor !== undefined) {
|
|
1818
|
+
if (pageInput.cursor !== undefined && pageInput.cursor !== args.cursor)
|
|
1819
|
+
throw new SdkSessionCliError("usage", "--cursor must match the cursor in --json-input.", 2);
|
|
1820
|
+
pageInput.cursor = args.cursor;
|
|
1821
|
+
}
|
|
1822
|
+
if (args.limit !== undefined) {
|
|
1823
|
+
if (pageInput.limit !== undefined && pageInput.limit !== args.limit)
|
|
1824
|
+
throw new SdkSessionCliError("usage", "--limit must match the limit in --json-input.", 2);
|
|
1825
|
+
pageInput.limit = args.limit;
|
|
1826
|
+
}
|
|
1827
|
+
return await withRouter(
|
|
1828
|
+
agentDir,
|
|
1829
|
+
BROKER_ONLY,
|
|
1830
|
+
async router =>
|
|
1831
|
+
await (args.page ? singleSessionListPage(router, pageInput) : paginatedSessionList(router, pageInput)),
|
|
1832
|
+
);
|
|
1833
|
+
}
|
|
1834
|
+
if (operation === "session.lookup") {
|
|
1835
|
+
if (!args.idempotencyKey)
|
|
1836
|
+
throw new SdkSessionCliError("invalid_input", "--idempotency-key is required for session lookup.", 2);
|
|
1837
|
+
const lifecycle = createBrokerSessionLifecycleService(agentDir);
|
|
1838
|
+
return await lifecycle.lookup({
|
|
1839
|
+
actor: SDK_SESSION_CLI_LIFECYCLE_ACTOR,
|
|
1840
|
+
capability: "session.lookup",
|
|
1841
|
+
operation: "session.create",
|
|
1842
|
+
requestKey: args.idempotencyKey,
|
|
1843
|
+
target: input,
|
|
1844
|
+
});
|
|
1759
1845
|
}
|
|
1760
1846
|
if (!isLifecycleOperation(operation))
|
|
1761
1847
|
throw new SdkSessionCliError("unknown_operation", `Unknown global operation: ${operation}`, 1);
|
|
@@ -1884,7 +1970,9 @@ export async function runSdkSessionCli(
|
|
|
1884
1970
|
const secretError = validateAdapterSecretFields(operation, input);
|
|
1885
1971
|
if (secretError) throw new SdkSessionCliError(secretError.code, secretError.message, 2);
|
|
1886
1972
|
if (kind === "global") {
|
|
1887
|
-
|
|
1973
|
+
const result = await runRawGlobal(agentDir, operation, input, args);
|
|
1974
|
+
writeOutput(stripSecretFields(result));
|
|
1975
|
+
if (isRecord(result) && result.ok === false) setExitCode(1);
|
|
1888
1976
|
return;
|
|
1889
1977
|
}
|
|
1890
1978
|
const sessionId = requireValue(args.sessionId, "<sessionId>");
|
package/src/sdk/host/host.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { logger } from "@gajae-code/utils";
|
|
2
2
|
import { AUTOROUTING_INACTIVE_WARNING } from "../../config/autorouting-contract";
|
|
3
|
-
import {
|
|
3
|
+
import { isFileLockAcquireTimeout } from "../../config/file-lock";
|
|
4
4
|
import { redactBrokerRuntimeCapabilities, redactObservedRequestContent } from "./control/runtime-gate";
|
|
5
5
|
import { type EventFrame, SessionEventStream } from "./events";
|
|
6
6
|
import { isAutoroutingInactive } from "./internal-autorouting-state";
|
|
@@ -395,12 +395,7 @@ export class SessionSdkHost {
|
|
|
395
395
|
// the durable row in place is fail-closed because peers still fence
|
|
396
396
|
// authority on process liveness and incarnation, while propagating the
|
|
397
397
|
// contention error turns ordinary shutdown into an uncaught failure.
|
|
398
|
-
if (
|
|
399
|
-
!options.allowLockContention ||
|
|
400
|
-
!(error instanceof FileLockAcquireError) ||
|
|
401
|
-
error.code !== "acquire_timeout"
|
|
402
|
-
)
|
|
403
|
-
throw error;
|
|
398
|
+
if (!options.allowLockContention || !isFileLockAcquireTimeout(error)) throw error;
|
|
404
399
|
logger.warn("sdk broker unregister deferred because the session index is busy", {
|
|
405
400
|
holder: error.holder,
|
|
406
401
|
lockPath: error.lockPath,
|