@gajae-code/coding-agent 0.12.18 → 0.12.20
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 +26 -9
- package/dist/types/commands/gc.d.ts +4 -0
- package/dist/types/commands/sdk.d.ts +66 -0
- package/dist/types/config/settings-schema.d.ts +34 -0
- package/dist/types/gjc-runtime/gc-runtime.d.ts +97 -1
- package/dist/types/modes/acp/acp-agent.d.ts +33 -1
- package/dist/types/modes/components/bash-execution.d.ts +7 -0
- package/dist/types/modes/components/eval-execution.d.ts +7 -0
- package/dist/types/modes/utils/ui-helpers.d.ts +10 -1
- package/dist/types/sdk/acp/adapter.d.ts +9 -17
- package/dist/types/sdk/acp/index.d.ts +1 -1
- package/dist/types/sdk/broker/broker.d.ts +93 -1
- package/dist/types/sdk/broker/ensure.d.ts +17 -0
- package/dist/types/sdk/broker/index.d.ts +1 -0
- package/dist/types/sdk/broker/lifecycle-ledger.d.ts +10 -1
- package/dist/types/sdk/broker/lifecycle.d.ts +81 -2
- package/dist/types/sdk/broker/startup-budget.d.ts +44 -0
- package/dist/types/sdk/bus/chat-daemon-control.d.ts +7 -1
- package/dist/types/sdk/bus/chat-daemon-runtime.d.ts +11 -1
- package/dist/types/sdk/bus/discord-daemon.d.ts +1 -0
- package/dist/types/sdk/bus/index.d.ts +20 -9
- package/dist/types/sdk/bus/prompt-reconciliation.d.ts +2 -6
- package/dist/types/sdk/bus/slack-daemon.d.ts +2 -2
- package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +4 -2
- package/dist/types/sdk/host/query/revision-store.d.ts +5 -0
- package/dist/types/sdk/host/session-runtime.d.ts +4 -0
- package/dist/types/sdk/lifecycle-session.d.ts +13 -2
- package/dist/types/sdk/prompt-failure.d.ts +13 -0
- package/dist/types/sdk/prompt-watchdog.d.ts +82 -0
- package/dist/types/sdk/session-reconnect.d.ts +20 -0
- package/dist/types/sdk/startup-capability.d.ts +1 -1
- package/dist/types/session/agent-session.d.ts +14 -4
- package/dist/types/session/blob-store.d.ts +32 -0
- package/dist/types/session/internal/managed-session-storage.d.ts +40 -2
- package/dist/types/session/internal/session-memory-sidecar.d.ts +881 -0
- package/dist/types/session/session-manager.d.ts +301 -27
- package/dist/types/session/session-storage.d.ts +170 -0
- package/dist/types/task/executor.d.ts +2 -1
- package/dist/types/tools/bash.d.ts +1 -0
- package/dist/types/tui/output-block.d.ts +6 -0
- package/package.json +8 -8
- package/src/cli/args.ts +75 -34
- package/src/commands/acp.ts +7 -1
- package/src/commands/gc.ts +31 -2
- package/src/commands/sdk.ts +141 -7
- package/src/config/settings-schema.ts +43 -0
- package/src/coordinator-mcp/server.ts +6 -1
- package/src/export/html/index.ts +83 -39
- package/src/gjc-runtime/gc-runtime.ts +1283 -4
- package/src/internal-urls/docs-index.generated.ts +1 -1
- package/src/internal-urls/local-protocol.ts +14 -0
- package/src/main.ts +40 -9
- package/src/modes/acp/acp-agent.ts +564 -37
- package/src/modes/acp/terminal-auth.ts +2 -2
- package/src/modes/components/bash-execution.ts +14 -0
- package/src/modes/components/eval-execution.ts +14 -0
- package/src/modes/controllers/command-controller.ts +63 -8
- package/src/modes/utils/ui-helpers.ts +81 -11
- package/src/sdk/acp/adapter.ts +46 -45
- package/src/sdk/acp/index.ts +1 -1
- package/src/sdk/broker/broker.ts +392 -11
- package/src/sdk/broker/ensure.ts +131 -61
- package/src/sdk/broker/index.ts +1 -0
- package/src/sdk/broker/lifecycle-ledger.ts +59 -5
- package/src/sdk/broker/lifecycle.ts +278 -63
- package/src/sdk/broker/startup-budget.ts +103 -0
- package/src/sdk/bus/chat-daemon-control.ts +9 -3
- package/src/sdk/bus/chat-daemon-runtime.ts +565 -29
- package/src/sdk/bus/conversation-store.ts +1 -1
- package/src/sdk/bus/discord-daemon.ts +2 -1
- package/src/sdk/bus/discord-live-provider.ts +34 -6
- package/src/sdk/bus/index.ts +118 -20
- package/src/sdk/bus/kind-aware-reconciliation.ts +2 -1
- package/src/sdk/bus/prompt-reconciliation.ts +4 -9
- package/src/sdk/bus/slack-daemon.ts +91 -18
- package/src/sdk/bus/telegram-daemon-contract.ts +4 -2
- package/src/sdk/bus/topic-registry.ts +7 -1
- package/src/sdk/cli/session-cli.ts +8 -1
- package/src/sdk/host/query/handlers.ts +1 -1
- package/src/sdk/host/query/revision-store.ts +48 -12
- package/src/sdk/host/session-runtime.test.ts +50 -0
- package/src/sdk/host/session-runtime.ts +12 -2
- package/src/sdk/lifecycle-session.ts +26 -3
- package/src/sdk/mcp/server.ts +6 -1
- package/src/sdk/prompt-failure.ts +42 -0
- package/src/sdk/prompt-watchdog.ts +162 -0
- package/src/sdk/session-reconnect.ts +43 -0
- package/src/sdk/startup-capability.ts +13 -4
- package/src/session/agent-session.ts +525 -222
- package/src/session/blob-store.ts +89 -0
- package/src/session/internal/managed-session-scope.ts +36 -26
- package/src/session/internal/managed-session-storage.ts +303 -32
- package/src/session/internal/session-memory-sidecar.ts +2160 -0
- package/src/session/session-manager.ts +7027 -394
- package/src/session/session-storage.ts +1134 -11
- package/src/task/executor.ts +17 -4
- package/src/tools/bash.ts +109 -7
- package/src/tools/find.ts +55 -26
- package/src/tools/todo-contract.ts +40 -8
- package/src/tui/output-block.ts +11 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,12 +2,37 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [0.12.20] - 2026-08-09
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
- Parallel task children now use their durable parent/task ownership tuple as a stable provider-facing continuity identity, preventing nested managed children from contending for one websocket owner while preserving logical session headers, artifact hierarchy, detached-resume identity, and explicit provider-session overrides.
|
|
9
|
+
- Session contexts no longer retain materialized resident text after the backing cache disappears; subsequent reads rematerialize the existing public-safe missing-blob placeholder. Headless `--export` coverage now expects the established error for a nonexistent source file.
|
|
10
|
+
- SDK prompt reconciliation now preserves bounded safe provider failure codes after `agent_failed`, while retaining the fixed redacted wire/persisted message and recording a bounded local diagnostic.
|
|
11
|
+
|
|
12
|
+
- Shadow-mode cold-session builds now compare the actual sidecar-derived provider context against the authoritative eager context and expose check/mismatch counters in `/session`; corrupt provider metadata produces a counted mismatch without weakening transcript authority. Cold branch activation now resolves compacted 10k-entry branches through one bounded ordinal-index scan and one bounded transcript range read instead of scanning a dictionary partition for every ancestor, and cold dictionary lookup skips unrelated JSON decoding while retaining full partition-digest verification. Bounded first-open, exact-reopen, and lazy cold-entry paths apply the same stale OpenAI Responses replay-metadata sanitation as eager resume.
|
|
13
|
+
- ACP startup now rejects unrecognized command flags and incompatible terminal-auth modes through the normal CLI usage-error path, while preserving separate dash-leading system-prompt text.
|
|
14
|
+
|
|
15
|
+
- `gjc --mode acp` no longer aborts on flags it advertises in `--help`. `parseArgs` never recognized `--extension`/`-e`, `--hook`, `--no-extensions`, `--no-skills`, or `--skills`, so every one of them landed in `unknownFlags`; once unknown flags became a hard ACP failure, launching ACP with `--no-extensions` crashed at startup with `Unsupported under SDK-backed ACP: extension flags`. All five are parsed again, so `--no-extensions` starts normally while `--extension`, `--hook`, `--no-skills`, and `--skills` are rejected under ACP by their own names — the rejection list in `resolveAcpStartupOptions` had been dead code because nothing ever populated the fields it tested. A genuinely unknown flag is now reported as `unknown flags: --typo` instead of being mislabeled an extension flag.
|
|
16
|
+
|
|
17
|
+
### Added
|
|
18
|
+
|
|
19
|
+
- Bounded-memory cold-session disk offloading is rollout-ready behind the `sessionMemory` settings namespace. `sessionMemory.mode` (default `"shadow"`) selects `off`/`shadow`/`enabled`: shadow measures without changing observable session behavior, and canary/default-on remain release-channel decisions rather than user-facing enum values. Fixed implementation budgets preserve the ≤64 MiB steady-state guarantee; transcript v5 stays authoritative and derived `.spill.*` sidecars remain disposable. Context materialization uses the typed `SessionContextTooLargeError` preflight, while `sessionMemory.contextOverflowRecovery` independently controls the async compact-once recovery path.
|
|
20
|
+
- Enabled explicit-path resumes validate descriptor-bound commit metadata, base/tail hashes, terminal proof, and hot-record digests before loading only the authenticated post-compaction tail. Fresh-process gates cover 120k compaction cycles, direct/captured forks, and one-million-record first build plus exact lazy reopen. Managed retained-authority startup remains on the eager compatibility path pending retained-authority range support.
|
|
21
|
+
- Enabled canary sessions fail closed to shadow/eager behavior after repeated sidecar build failures or failed adoption, expose the live auto-disable reason through `/session`, and clear the session-local latch through the existing `off` rollback mode. Live `enabled` → `shadow`/`off` rollback keeps existing cold entries lazily readable for the current process; the next `off`-mode start ignores derived state and restores eager compatibility.
|
|
22
|
+
|
|
23
|
+
## [0.12.19] - 2026-08-08
|
|
24
|
+
|
|
25
|
+
### Fixed
|
|
26
|
+
|
|
27
|
+
- Telegram topic-registry archive transitions remain parser-valid across durable-fence load promotion and failed archive-publication rollback. The initial generation-57 fix cleared `disconnectGraceExpiresAt` on direct archive transitions, but loading a `disconnect_grace` record superseded by a newer durable fence could still promote it to `archive_pending` while retaining the grace-only field, and rollback could restore `disconnect_grace` without restoring its required deadline. Both paths now preserve the state/field invariant with parser-roundtrip coverage. This entry also corrects the hotfix attribution omitted from the published 0.12.18 release notes. `DAEMON_GENERATION` bumped to 58.
|
|
28
|
+
|
|
5
29
|
## [0.12.18] - 2026-08-08
|
|
6
30
|
|
|
7
31
|
### Fixed
|
|
32
|
+
- Repeated provider idle-stream stalls now stop after `retry.maxRetries` instead of inheriting the unbounded transient-error retry path. A one-off stall still retries normally, while a persistently silent stream surfaces its error rather than resubmitting the same billable context indefinitely.
|
|
8
33
|
|
|
9
34
|
- SDK snapshot spill writes now retry once with a fresh temporary file when Bun reports `EBADF` during write or `fsync` under heavily contended descriptor teardown, and no longer fail when `close()` reports `EBADF` after a successful write and sync. The atomic writer removes the abandoned attempt before retrying, preserves primary I/O failures, accepts only the proven already-closed close case, and keeps every non-`EBADF` error or repeated descriptor failure fatal.
|
|
10
|
-
-
|
|
35
|
+
- Discord Gateway ingestion now decodes text and binary WebSocket payloads, resolves a thread's missing parent-channel metadata through Discord's REST API, and stops reconnecting after terminal authentication, sharding, version, or intent close codes.
|
|
11
36
|
|
|
12
37
|
## [0.12.17] - 2026-08-08
|
|
13
38
|
|
|
@@ -88,14 +113,6 @@
|
|
|
88
113
|
- GJC no longer advertises `mcpCapabilities.sse`. The legacy MCP HTTP+SSE transport (spec 2024-11-05, deprecated) is not implemented: an `sse` config is routed to the Streamable HTTP transport, which never performs the required `endpoint`-event handshake, so a genuine legacy-SSE server offered by an ACP client could not connect. Locally configured `sse` entries still resolve through `createTransport` and keep working.
|
|
89
114
|
- Reverse ACP responses (`fs/read_text_file`, terminal output) are now measured as the full serialized frame rather than only the inner `result`. A near-limit result passed the 256 KiB check and then tripped the WebSocket total-frame ceiling, closing the whole session instead of raising the typed `payload_too_large`.
|
|
90
115
|
- `todo_write` now accepts `complete` and `completed` as aliases for the `done` operation. The status this operation sets is spelled `completed`, so models repeatedly emitted `op: "complete"` and hit a hard mid-turn tool failure; the operation vocabulary is otherwise unchanged and a completion still requires a task or phase target.
|
|
91
|
-
||||||| parent of d5f44548e (fix(todo): accept the operation and field synonyms models actually emit)
|
|
92
|
-
- ACP `session/prompt` no longer hangs forever when the agent continues mid-prompt. Any continuation (todo reminder, TTSR resume, auto-continue) re-enters the agent loop and emits a second `agent_start`, which shifted the already-empty pending queue and overwrote the live prompt correlation with `undefined`. The prompt's `agent_end` then carried no correlation, `terminalizePrompt` never ran, and every ACP client (Paseo, Zed, JetBrains Air) waited until the 30-minute `sdk.promptDeadlineMs` before failing with `prompt_deadline_exceeded` — despite the answer having already streamed. `agent_start` now claims a pending correlation only when the session has no active one, and the todo-completion continuation holds the predecessor `agent_end` like every other continuation call site.
|
|
93
|
-
- ACP clients can now render the user's own message and its image attachments. A live prompt turn emitted no `user_message_chunk` at all — that update only existed on the `session/load` replay path — so an attached image reached the model but never appeared in the client transcript. `session/prompt` now echoes the prompt's text and image content blocks as `user_message_chunk` before dispatching the turn.
|
|
94
|
-
- An oversize ACP prompt now fails with a typed, actionable error instead of killing the session. The SDK WebSocket server caps a request frame at 256 KiB (`REQUEST_FRAME_BYTES`) and answers an oversize frame with `CloseCode::Size`, which surfaced to clients as an opaque `-32603 connection_closed` mid-turn; a 244 KiB PNG reproduced it while a 59 KiB PNG succeeded. The prompt is now measured inside its real `control_request` envelope and rejected up front with `-32602` naming the actual and permitted size.
|
|
95
|
-
- ACP `session/cancel` and `session/close` now settle the pending `session/prompt` with `stopReason: "cancelled"` instead of surfacing a transport error. A cancel that landed during prompt preflight rejected with `busy`, and `session/close` rejected with `connection_closed`, both of which clients display as spurious errors for the user's own action. The spec requires agents to "catch these errors and return the semantically meaningful `cancelled` stop reason"; involuntary teardown still rejects.
|
|
96
|
-
- GJC no longer advertises `mcpCapabilities.sse`. The legacy MCP HTTP+SSE transport (spec 2024-11-05, deprecated) is not implemented: an `sse` config is routed to the Streamable HTTP transport, which never performs the required `endpoint`-event handshake, so a genuine legacy-SSE server offered by an ACP client could not connect. Locally configured `sse` entries still resolve through `createTransport` and keep working.
|
|
97
|
-
- Reverse ACP responses (`fs/read_text_file`, terminal output) are now measured as the full serialized frame rather than only the inner `result`. A near-limit result passed the 256 KiB check and then tripped the WebSocket total-frame ceiling, closing the whole session instead of raising the typed `payload_too_large`.
|
|
98
|
-
- `todo_write` now accepts `complete` and `completed` as aliases for the `done` operation. The status this operation sets is spelled `completed`, so models repeatedly emitted `op: "complete"` and hit a hard mid-turn tool failure; the operation vocabulary is otherwise unchanged and a completion still requires a task or phase target.
|
|
99
116
|
|
|
100
117
|
- `todo_write` now rejects malformed raw arguments with bounded, authority-controlled correction codes instead of a generic rejection: unknown root keys, unknown operation-entry keys, done/drop entries without a task or phase target, and unknown init list-entry keys each surface a fixed message naming the accepted shape without echoing the offending input, while recoverable payloads keep the passthrough/coercion path and the existing ask-tool codes are untouched (#3916).
|
|
101
118
|
- The Alibaba Token Plan onboarding preset and `alibaba-token-plan-qwen-deepseek` profile now reference the provider-supported `qwen3.8-max` model id instead of `qwen-3.8-max`, preventing the built-in profile from selecting an HTTP 400 unsupported model (#3909).
|
|
@@ -20,6 +20,10 @@ export default class Gc extends Command {
|
|
|
20
20
|
description: string;
|
|
21
21
|
default: boolean;
|
|
22
22
|
};
|
|
23
|
+
disk: import("@gajae-code/utils/cli").FlagDescriptor<"boolean"> & {
|
|
24
|
+
description: string;
|
|
25
|
+
default: boolean;
|
|
26
|
+
};
|
|
23
27
|
"repair-session-index": import("@gajae-code/utils/cli").FlagDescriptor<"boolean"> & {
|
|
24
28
|
description: string;
|
|
25
29
|
default: boolean;
|
|
@@ -26,11 +26,77 @@ export declare function watchSessionHostBrokerLiveness(deps: {
|
|
|
26
26
|
graceMs?: number;
|
|
27
27
|
pollMs?: number;
|
|
28
28
|
}): Promise<void>;
|
|
29
|
+
/**
|
|
30
|
+
* How long a session host that has served at least one client tolerates having
|
|
31
|
+
* no client attached before treating itself as abandoned.
|
|
32
|
+
*
|
|
33
|
+
* This cannot fire during healthy work. "Attached" is the host's own live
|
|
34
|
+
* socket-subscription count, so a client that is merely idle — an editor
|
|
35
|
+
* sitting on an open ACP session, a long agent turn with nobody typing — still
|
|
36
|
+
* holds a socket and resets the window on every poll. Only a client that is
|
|
37
|
+
* actually gone opens it, and 30 minutes is far longer than any client
|
|
38
|
+
* reconnect budget (ACP's is seconds), so a crashed-and-restarted client
|
|
39
|
+
* reattaches long before the window closes.
|
|
40
|
+
*/
|
|
41
|
+
export declare const SESSION_HOST_DETACHED_IDLE_GRACE_MS: number;
|
|
42
|
+
/**
|
|
43
|
+
* How long a freshly spawned session host waits for its very first client
|
|
44
|
+
* before treating itself as abandoned.
|
|
45
|
+
*
|
|
46
|
+
* A host is ready before its client has finished dialing, so a host that has
|
|
47
|
+
* never seen an attachment must not be judged by the detached window above. One
|
|
48
|
+
* hour is orders of magnitude beyond the slowest observed cold start (worktree
|
|
49
|
+
* preparation, MCP server launch, model-profile application) and beyond any
|
|
50
|
+
* lifecycle readiness deadline the broker will wait on, so it can only elapse
|
|
51
|
+
* for a host nobody ever came for.
|
|
52
|
+
*/
|
|
53
|
+
export declare const SESSION_HOST_FIRST_ATTACH_GRACE_MS: number;
|
|
54
|
+
/**
|
|
55
|
+
* Resolves once this host is provably abandoned: either it has been detached
|
|
56
|
+
* from every client for the full idle grace, or nobody has come for it at all
|
|
57
|
+
* for the full first-attach grace.
|
|
58
|
+
*
|
|
59
|
+
* `readAttachedClients` reports the host's own live client/socket subscription
|
|
60
|
+
* count; `undefined` means the SDK endpoint publishes no such evidence yet and
|
|
61
|
+
* is deliberately *not* treated as detachment, so a host can never be reaped on
|
|
62
|
+
* missing evidence — only on observed absence of clients.
|
|
63
|
+
*
|
|
64
|
+
* `readWorkInFlight` reports whether the host is running agent work right now.
|
|
65
|
+
* Work is positive proof that a client did come for this host: a prompt can
|
|
66
|
+
* only arrive over the endpoint a client dialed. So for a host that has never
|
|
67
|
+
* been *observed* attached — a client that connected, prompted and dropped
|
|
68
|
+
* between two 30s polls, or a client count that is momentarily unreadable — the
|
|
69
|
+
* first-attach window is measured from the last moment work was in flight
|
|
70
|
+
* rather than from process start. That still bounds a host whose SDK runtime
|
|
71
|
+
* never came up: with no transport it can never receive a prompt, so it never
|
|
72
|
+
* reports work and its window keeps running from start. And any real turn ends,
|
|
73
|
+
* after which the window resumes; live work defers the bound, never removes it.
|
|
74
|
+
*/
|
|
75
|
+
export declare function watchSessionHostClientAttachment(deps: {
|
|
76
|
+
readAttachedClients: () => number | undefined;
|
|
77
|
+
readWorkInFlight?: () => boolean;
|
|
78
|
+
now?: () => number;
|
|
79
|
+
sleep?: (ms: number) => Promise<void>;
|
|
80
|
+
idleGraceMs?: number;
|
|
81
|
+
firstAttachGraceMs?: number;
|
|
82
|
+
pollMs?: number;
|
|
83
|
+
}): Promise<void>;
|
|
29
84
|
/** Opens lifecycle-authorized history without letting replacement content reach readiness. */
|
|
30
85
|
export declare function openLifecycleSessionManager(request: SessionLifecycleLaunchRequest, cwd: string, agentDir: string): Promise<{
|
|
31
86
|
parsed: ParsedArgs;
|
|
32
87
|
sessionManager: SessionManager | undefined;
|
|
33
88
|
}>;
|
|
89
|
+
/**
|
|
90
|
+
* Starts the memory backend a lifecycle session deferred past its readiness
|
|
91
|
+
* window.
|
|
92
|
+
*
|
|
93
|
+
* Deliberately not awaited: the local backend summarises every queued rollout
|
|
94
|
+
* through the model, so its duration scales with the backlog and would eat the
|
|
95
|
+
* broker's readiness budget. The session is already published as ready here, so
|
|
96
|
+
* a failure is a degraded-memory condition, not a startup failure — it is
|
|
97
|
+
* logged and swallowed instead of surfacing as an unhandled rejection.
|
|
98
|
+
*/
|
|
99
|
+
export declare function startMemoryBackendAfterReadiness(start: () => Promise<void>): void;
|
|
34
100
|
/** Runs the same persisted AgentSession bootstrap used by the production CLI. */
|
|
35
101
|
export declare function runSessionHost(timing?: {
|
|
36
102
|
now?: () => number;
|
|
@@ -2500,6 +2500,15 @@ export declare const SETTINGS_SCHEMA: {
|
|
|
2500
2500
|
}];
|
|
2501
2501
|
};
|
|
2502
2502
|
};
|
|
2503
|
+
readonly "sessionMemory.mode": {
|
|
2504
|
+
readonly type: "enum";
|
|
2505
|
+
readonly values: readonly ["off", "shadow", "enabled"];
|
|
2506
|
+
readonly default: "shadow";
|
|
2507
|
+
};
|
|
2508
|
+
readonly "sessionMemory.contextOverflowRecovery": {
|
|
2509
|
+
readonly type: "boolean";
|
|
2510
|
+
readonly default: true;
|
|
2511
|
+
};
|
|
2503
2512
|
readonly "edit.mode": {
|
|
2504
2513
|
readonly type: "enum";
|
|
2505
2514
|
readonly values: import("../edit").EditMode[];
|
|
@@ -3273,6 +3282,26 @@ export declare const SETTINGS_SCHEMA: {
|
|
|
3273
3282
|
readonly description: "Parent-process RSS (MB) above which idle tabs are opportunistically evicted LRU.";
|
|
3274
3283
|
};
|
|
3275
3284
|
};
|
|
3285
|
+
readonly "gc.sessions.maxAgeDays": {
|
|
3286
|
+
readonly type: "number";
|
|
3287
|
+
readonly default: 60;
|
|
3288
|
+
readonly validate: (value: number) => boolean;
|
|
3289
|
+
};
|
|
3290
|
+
readonly "gc.sessions.maxTotalBytes": {
|
|
3291
|
+
readonly type: "number";
|
|
3292
|
+
readonly default: 0;
|
|
3293
|
+
readonly validate: (value: number) => boolean;
|
|
3294
|
+
};
|
|
3295
|
+
readonly "gc.natives.keepVersions": {
|
|
3296
|
+
readonly type: "number";
|
|
3297
|
+
readonly default: 2;
|
|
3298
|
+
readonly validate: (value: number) => boolean;
|
|
3299
|
+
};
|
|
3300
|
+
readonly "gc.backups.maxAgeDays": {
|
|
3301
|
+
readonly type: "number";
|
|
3302
|
+
readonly default: 30;
|
|
3303
|
+
readonly validate: (value: number) => boolean;
|
|
3304
|
+
};
|
|
3276
3305
|
readonly "resourceGc.sweepIntervalMs": {
|
|
3277
3306
|
readonly type: "number";
|
|
3278
3307
|
readonly default: 30000;
|
|
@@ -4465,6 +4494,10 @@ export interface ShellMinimizerSettings {
|
|
|
4465
4494
|
except: string[];
|
|
4466
4495
|
maxCaptureBytes: number;
|
|
4467
4496
|
}
|
|
4497
|
+
export interface SessionMemorySettings {
|
|
4498
|
+
mode: "off" | "shadow" | "enabled";
|
|
4499
|
+
contextOverflowRecovery: boolean;
|
|
4500
|
+
}
|
|
4468
4501
|
export interface MemoryGuardSettings {
|
|
4469
4502
|
enabled: boolean;
|
|
4470
4503
|
checkIntervalMs: number;
|
|
@@ -4538,6 +4571,7 @@ export interface GroupTypeMap {
|
|
|
4538
4571
|
thinkingBudgets: ThinkingBudgetsSettings;
|
|
4539
4572
|
stt: SttSettings;
|
|
4540
4573
|
memoryGuard: MemoryGuardSettings;
|
|
4574
|
+
sessionMemory: SessionMemorySettings;
|
|
4541
4575
|
modelRoles: Record<string, ModelSelectorValue>;
|
|
4542
4576
|
modelTags: ModelTagsSettings;
|
|
4543
4577
|
cycleOrder: string[];
|
|
@@ -122,6 +122,11 @@ export interface GcReport {
|
|
|
122
122
|
session_index?: GcSessionIndexHealth;
|
|
123
123
|
/** Managed-scope capacity, reported only when it is near or past the budget. */
|
|
124
124
|
session_scope?: GcSessionScopeUsage;
|
|
125
|
+
/**
|
|
126
|
+
* Disk-retention findings. Present only when `--disk` was passed; the
|
|
127
|
+
* PID-liveness axis above is unchanged by its absence or presence.
|
|
128
|
+
*/
|
|
129
|
+
disk?: GcDiskReport;
|
|
125
130
|
}
|
|
126
131
|
export interface GcRunResult {
|
|
127
132
|
stdout: string;
|
|
@@ -149,9 +154,100 @@ export declare function collectGcReport(adapters: GcStoreAdapter[], ctx: GcConte
|
|
|
149
154
|
* - prune mode with a failed intended removal => 1
|
|
150
155
|
* - warnings alone never fail the run
|
|
151
156
|
* - otherwise => 0
|
|
157
|
+
*
|
|
158
|
+
* The disk axis reuses the same policy against its own errors/failures, and is
|
|
159
|
+
* inert when `--disk` was not passed (`report.disk` is then undefined). A
|
|
160
|
+
* fail-closed KEEP is never a failure — only a hard scan error or a reclaim
|
|
161
|
+
* that was attempted and threw.
|
|
152
162
|
*/
|
|
153
163
|
export declare function computeExitCode(report: GcReport): number;
|
|
154
|
-
export declare function runGjcGcCommand(argv: string[], cwd?: string, env?: NodeJS.ProcessEnv, adapters?: GcStoreAdapter[]): Promise<GcRunResult>;
|
|
164
|
+
export declare function runGjcGcCommand(argv: string[], cwd?: string, env?: NodeJS.ProcessEnv, adapters?: GcStoreAdapter[], diskPolicy?: Partial<GcDiskPolicy>): Promise<GcRunResult>;
|
|
155
165
|
export declare function gcHelpText(): string;
|
|
156
166
|
/** Lazily assemble the real store adapters (kept lazy to avoid import cycles). */
|
|
157
167
|
export declare function defaultGcAdapters(): Promise<GcStoreAdapter[]>;
|
|
168
|
+
/** On-disk surfaces the retention axis can reclaim. */
|
|
169
|
+
export type GcDiskSurface = "sessions" | "blobs" | "natives" | "backups";
|
|
170
|
+
export declare const GC_DISK_SURFACES: readonly GcDiskSurface[];
|
|
171
|
+
export type GcDiskAction = "keep" | "would_reclaim" | "reclaimed" | "reclaim_failed";
|
|
172
|
+
export interface GcDiskRecord {
|
|
173
|
+
surface: GcDiskSurface;
|
|
174
|
+
/** Session id, blob hash, natives version, or backup entry name. */
|
|
175
|
+
id: string;
|
|
176
|
+
path: string;
|
|
177
|
+
bytes: number;
|
|
178
|
+
age_days: number;
|
|
179
|
+
action: GcDiskAction;
|
|
180
|
+
reason: string;
|
|
181
|
+
error?: string;
|
|
182
|
+
/** Set when `bytes` is a floor because a walk was capped or partially unreadable. */
|
|
183
|
+
partial?: true;
|
|
184
|
+
/** Set when this entry was a reclaim candidate that its surface withheld on incomplete evidence. */
|
|
185
|
+
withheld?: true;
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Why a surface refused to reclaim anything, and how much it withheld to stay
|
|
189
|
+
* fail-closed. Present only when the surface declined.
|
|
190
|
+
*/
|
|
191
|
+
export interface GcDiskDeclined {
|
|
192
|
+
reason: string;
|
|
193
|
+
withheld: number;
|
|
194
|
+
withheld_bytes: number;
|
|
195
|
+
}
|
|
196
|
+
export interface GcDiskSurfaceReport {
|
|
197
|
+
surface: GcDiskSurface;
|
|
198
|
+
root: string;
|
|
199
|
+
scanned: number;
|
|
200
|
+
scanned_bytes: number;
|
|
201
|
+
reclaimable: number;
|
|
202
|
+
reclaimable_bytes: number;
|
|
203
|
+
reclaimed: number;
|
|
204
|
+
reclaimed_bytes: number;
|
|
205
|
+
kept: number;
|
|
206
|
+
kept_bytes: number;
|
|
207
|
+
failed: number;
|
|
208
|
+
/** Set when the surface declined to reclaim anything because its evidence was incomplete. */
|
|
209
|
+
declined?: GcDiskDeclined;
|
|
210
|
+
records: GcDiskRecord[];
|
|
211
|
+
}
|
|
212
|
+
export interface GcDiskError {
|
|
213
|
+
surface: GcDiskSurface;
|
|
214
|
+
scope: string;
|
|
215
|
+
message: string;
|
|
216
|
+
}
|
|
217
|
+
/** Retention policy, mirroring the `gc.*` settings one-for-one. */
|
|
218
|
+
export interface GcDiskPolicy {
|
|
219
|
+
sessions_max_age_days: number;
|
|
220
|
+
/** 0 disables the size axis; only the age axis retires transcripts then. */
|
|
221
|
+
sessions_max_total_bytes: number;
|
|
222
|
+
natives_keep_versions: number;
|
|
223
|
+
backups_max_age_days: number;
|
|
224
|
+
}
|
|
225
|
+
export interface GcDiskReport {
|
|
226
|
+
dry_run: boolean;
|
|
227
|
+
policy: GcDiskPolicy;
|
|
228
|
+
surfaces: Record<GcDiskSurface, GcDiskSurfaceReport>;
|
|
229
|
+
totals: {
|
|
230
|
+
scanned_bytes: number;
|
|
231
|
+
reclaimable_bytes: number;
|
|
232
|
+
reclaimed_bytes: number;
|
|
233
|
+
kept_bytes: number;
|
|
234
|
+
failed: number;
|
|
235
|
+
};
|
|
236
|
+
errors: GcDiskError[];
|
|
237
|
+
}
|
|
238
|
+
/** Schema-backed defaults, so the CLI and the settings surface cannot drift. */
|
|
239
|
+
export declare const GC_DISK_POLICY_DEFAULTS: GcDiskPolicy;
|
|
240
|
+
export declare function resolveGcDiskPolicy(overrides?: Partial<GcDiskPolicy>): GcDiskPolicy;
|
|
241
|
+
/**
|
|
242
|
+
* Run the disk-retention axis. Nothing is mutated unless `prune` is true; the
|
|
243
|
+
* dry-run report projects exactly the same decisions a prune would make.
|
|
244
|
+
*/
|
|
245
|
+
export declare function collectGcDiskReport(input: {
|
|
246
|
+
agentDir: string;
|
|
247
|
+
env: NodeJS.ProcessEnv;
|
|
248
|
+
policy: GcDiskPolicy;
|
|
249
|
+
prune: boolean;
|
|
250
|
+
now?: number;
|
|
251
|
+
runningVersion?: string;
|
|
252
|
+
}): Promise<GcDiskReport>;
|
|
253
|
+
export declare function buildGcDiskReportText(disk: GcDiskReport): string;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type Agent, type AgentSideConnection, type AuthenticateRequest, type AuthenticateResponse, type AvailableCommand, type ClientCapabilities, type CloseSessionRequest, type CloseSessionResponse, type DeleteSessionRequest, type DeleteSessionResponse, type ForkSessionRequest, type ForkSessionResponse, type InitializeRequest, type InitializeResponse, type ListSessionsRequest, type ListSessionsResponse, type LoadSessionRequest, type LoadSessionResponse, type NewSessionRequest, type NewSessionResponse, type PromptRequest, type PromptResponse, type ResumeSessionRequest, type ResumeSessionResponse, type SetSessionConfigOptionRequest, type SetSessionConfigOptionResponse, type SetSessionModeRequest, type SetSessionModeResponse } from "@agentclientprotocol/sdk";
|
|
2
2
|
import { type AcpProviderRegistration, type AcpReverseConnection, AcpSdkAdapter } from "../../sdk/acp";
|
|
3
|
+
import { type PromptWatchdogClock } from "../../sdk/prompt-watchdog";
|
|
3
4
|
import type { AcpStartupOptions } from "./startup-options";
|
|
4
5
|
type JsonObject = Record<string, unknown>;
|
|
5
6
|
/** Applies ACP's offset cursor after narrowing the broker listing to the requested cwd. */
|
|
@@ -30,7 +31,37 @@ export interface TranscriptReplayContent {
|
|
|
30
31
|
reason: "historical_transcript_images_unavailable";
|
|
31
32
|
};
|
|
32
33
|
}
|
|
33
|
-
|
|
34
|
+
/** Machine-readable reason replay could not restore a transcript entry. */
|
|
35
|
+
export type TranscriptReplaySkipReason = "transcript_body_unavailable" | "transcript_tool_call_unavailable";
|
|
36
|
+
/**
|
|
37
|
+
* Replay decides per entry. An entry whose production body is missing is not
|
|
38
|
+
* replayable, and the caller reports that boundary instead of failing the whole
|
|
39
|
+
* load; fabricating an empty body would replay a message that never existed.
|
|
40
|
+
*/
|
|
41
|
+
export type TranscriptReplayEntry = {
|
|
42
|
+
replayable: true;
|
|
43
|
+
content: TranscriptReplayContent;
|
|
44
|
+
} | {
|
|
45
|
+
replayable: false;
|
|
46
|
+
reason: TranscriptReplaySkipReason;
|
|
47
|
+
};
|
|
48
|
+
export declare function transcriptReplayContent(entry: unknown): TranscriptReplayEntry;
|
|
49
|
+
/**
|
|
50
|
+
* `transcript.list` answers an entry larger than one page with a body-less row
|
|
51
|
+
* `{ id, error: { code: "item_too_large" }, continuations }`. Each continuation is a
|
|
52
|
+
* `Q23` (`resource.body`) descriptor for one indexed string field of that entry, so
|
|
53
|
+
* the row is a pointer to the largest message in the session rather than a broken
|
|
54
|
+
* entry. Replay follows it instead of dropping the message.
|
|
55
|
+
*/
|
|
56
|
+
export interface TranscriptContinuation {
|
|
57
|
+
query: string;
|
|
58
|
+
resourceKind: string;
|
|
59
|
+
resourceId: string;
|
|
60
|
+
revision: string;
|
|
61
|
+
itemId: string;
|
|
62
|
+
field: string;
|
|
63
|
+
}
|
|
64
|
+
export declare function transcriptContinuations(entry: unknown): TranscriptContinuation[];
|
|
34
65
|
/**
|
|
35
66
|
* Collect every page of `providers.list/active` (Q29, GJC >= 0.12.8) and
|
|
36
67
|
* return the providers with usable stored credentials or credentialless
|
|
@@ -250,6 +281,7 @@ export declare class AcpAgent implements Agent {
|
|
|
250
281
|
agentDir?: string;
|
|
251
282
|
startupOptions?: AcpStartupOptions;
|
|
252
283
|
cancelSettlementGraceMs?: number;
|
|
284
|
+
promptWatchdogClock?: PromptWatchdogClock;
|
|
253
285
|
} | unknown);
|
|
254
286
|
initialize(params: InitializeRequest): Promise<InitializeResponse>;
|
|
255
287
|
authenticate(params: AuthenticateRequest): Promise<AuthenticateResponse>;
|
|
@@ -26,4 +26,11 @@ export declare class BashExecutionComponent extends Container {
|
|
|
26
26
|
* Get the command that was executed.
|
|
27
27
|
*/
|
|
28
28
|
getCommand(): string;
|
|
29
|
+
/**
|
|
30
|
+
* Record that this execution's message reached session state, so a transcript
|
|
31
|
+
* rebuild renders it from the session instead of keeping this live block parked.
|
|
32
|
+
*/
|
|
33
|
+
markResultPersisted(): void;
|
|
34
|
+
/** Whether the session already holds this execution's message. */
|
|
35
|
+
hasPersistedResult(): boolean;
|
|
29
36
|
}
|
|
@@ -20,4 +20,11 @@ export declare class EvalExecutionComponent extends Container {
|
|
|
20
20
|
}): void;
|
|
21
21
|
getOutput(): string;
|
|
22
22
|
getCode(): string;
|
|
23
|
+
/**
|
|
24
|
+
* Record that this execution's message reached session state, so a transcript
|
|
25
|
+
* rebuild renders it from the session instead of keeping this live block parked.
|
|
26
|
+
*/
|
|
27
|
+
markResultPersisted(): void;
|
|
28
|
+
/** Whether the session already holds this execution's message. */
|
|
29
|
+
hasPersistedResult(): boolean;
|
|
23
30
|
}
|
|
@@ -37,6 +37,15 @@ export declare class CollapsedChatHistoryComponent implements Component {
|
|
|
37
37
|
invalidate(): void;
|
|
38
38
|
}
|
|
39
39
|
export declare function addChatChild(ctx: InteractiveModeContext, component: Component): void;
|
|
40
|
+
/**
|
|
41
|
+
* Parked `!`/`$` execution components are listed in `pendingBashComponents` /
|
|
42
|
+
* `pendingPythonComponents`, but `pendingMessagesContainer` is the only
|
|
43
|
+
* authority on parentage: `/clear`, `/context-clear`, extension redraws and the
|
|
44
|
+
* selector all call `pendingMessagesContainer.clear()`, which disposes and
|
|
45
|
+
* evicts parked components without touching those arrays. Drop every entry the
|
|
46
|
+
* container no longer holds so nothing downstream can move a dead component.
|
|
47
|
+
*/
|
|
48
|
+
export declare function syncPendingExecutionComponents(ctx: InteractiveModeContext): void;
|
|
40
49
|
export declare function trimChatChildren(ctx: InteractiveModeContext): void;
|
|
41
50
|
export declare class UiHelpers {
|
|
42
51
|
#private;
|
|
@@ -84,7 +93,7 @@ export declare class UiHelpers {
|
|
|
84
93
|
flushCompactionQueue(options?: {
|
|
85
94
|
willRetry?: boolean;
|
|
86
95
|
}): Promise<void>;
|
|
87
|
-
/** Move pending bash components from pending area to chat */
|
|
96
|
+
/** Move pending bash/python components from the pending area to chat */
|
|
88
97
|
flushPendingBashComponents(): void;
|
|
89
98
|
findLastAssistantMessage(): AssistantMessage | undefined;
|
|
90
99
|
extractAssistantText(message: AssistantMessage): string;
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import { SdkClient, SdkClientError, type
|
|
1
|
+
import { SdkClient, SdkClientError, type SdkFrame } from "../client";
|
|
2
|
+
import { ACP_SESSION_RECONNECT } from "../session-reconnect";
|
|
3
|
+
import type { SessionLifecycleMcpServer } from "./mcp";
|
|
2
4
|
type JsonObject = Record<string, unknown>;
|
|
3
5
|
/** The small agent-side ACP surface used for reverse requests. */
|
|
4
6
|
export interface AcpReverseConnection {
|
|
@@ -26,23 +28,14 @@ export declare class AcpSdkAdapterError extends Error {
|
|
|
26
28
|
readonly code: string;
|
|
27
29
|
constructor(code: string, message?: string);
|
|
28
30
|
}
|
|
29
|
-
export type AcpReconnectFailedHandler = (error: SdkClientError) => void;
|
|
30
|
-
export type AcpFrameHandler = (frame: SdkFrame) => void;
|
|
31
|
-
type AcpSessionReconnectOptions = Required<Pick<SdkClientOptions, "reconnectAttempts" | "reconnectBackoffMs" | "reconnectMaxBackoffMs">>;
|
|
32
31
|
/**
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
* Invariant: the client's total reconnect budget must outlive the host heartbeat
|
|
36
|
-
* TTL ({@link HEARTBEAT_TTL_MS}). The SDK host drops a session whose client has
|
|
37
|
-
* not ponged within that TTL, so a client that gives up reconnecting sooner turns
|
|
38
|
-
* every stall the host reaps into a permanently lost session ("SDK WebSocket
|
|
39
|
-
* reconnect attempts exhausted"). The transport defaults (3 attempts at 25ms base
|
|
40
|
-
* = 175ms total) are correct only for one-shot request clients.
|
|
41
|
-
*
|
|
42
|
-
* Backoff ramps 250 -> 500 -> 1000 and then holds at the 2s cap, so individual
|
|
43
|
-
* sleeps stay short and recovery is prompt once the host answers again.
|
|
32
|
+
* The error an ACP session launch must throw once a lifecycle request that carried MCP
|
|
33
|
+
* servers has failed.
|
|
44
34
|
*/
|
|
45
|
-
export declare
|
|
35
|
+
export declare function acpMcpLaunchFailure(error: unknown, mcpServers: SessionLifecycleMcpServer[]): unknown;
|
|
36
|
+
export type AcpReconnectFailedHandler = (error: SdkClientError) => void;
|
|
37
|
+
export type AcpFrameHandler = (frame: SdkFrame) => void;
|
|
38
|
+
export { ACP_SESSION_RECONNECT };
|
|
46
39
|
/**
|
|
47
40
|
* Pure ACP-to-SDK adapter. It deliberately owns neither an AgentSession nor an
|
|
48
41
|
* ACP bridge: all session work is performed through authenticated v3 frames.
|
|
@@ -81,4 +74,3 @@ export declare class AcpSdkAdapter {
|
|
|
81
74
|
handle(method: string, params?: JsonObject): Promise<unknown>;
|
|
82
75
|
registerProvider(provider: AcpProviderRegistration): Promise<void>;
|
|
83
76
|
}
|
|
84
|
-
export {};
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
export type { AcpProviderRegistration, AcpReverseConnection, AcpSdkAdapterOptions } from "./adapter";
|
|
2
|
-
export { ACP_SESSION_RECONNECT, AcpSdkAdapter, AcpSdkAdapterError } from "./adapter";
|
|
2
|
+
export { ACP_SESSION_RECONNECT, AcpSdkAdapter, AcpSdkAdapterError, acpMcpLaunchFailure } from "./adapter";
|
|
@@ -19,7 +19,7 @@ type ResolvedBrokerSettings = {
|
|
|
19
19
|
heartbeatTtlMs: number;
|
|
20
20
|
resolveDirectoryMigration: (_cwd: string) => Promise<DirectoryMigrationPolicy>;
|
|
21
21
|
};
|
|
22
|
-
export type BrokerErrorCode = "idempotency_conflict" | "terminal_uncertain" | "broker_restarting" | "unavailable" | "endpoint_stale" | "resource_gone" | "invalid_input" | "spawn_failed" | "readiness_timeout" | "close_refused" | "not_found" | "live_session" | "cleanup_pending" | (string & {});
|
|
22
|
+
export type BrokerErrorCode = "idempotency_conflict" | "terminal_uncertain" | "broker_restarting" | "unavailable" | "endpoint_stale" | "resource_gone" | "invalid_input" | "spawn_failed" | "startup_admission_timeout" | "startup_admission_refused" | "readiness_timeout" | "close_refused" | "not_found" | "live_session" | "cleanup_pending" | (string & {});
|
|
23
23
|
export type BrokerCleanupIdentity = {
|
|
24
24
|
dev: string;
|
|
25
25
|
ino: string;
|
|
@@ -120,6 +120,77 @@ export type BrokerResponse = {
|
|
|
120
120
|
durableEffects?: LifecycleDurableEffectsReceipt;
|
|
121
121
|
startupFailure?: LifecycleStartupFailureReceipt;
|
|
122
122
|
};
|
|
123
|
+
/** Tombstone prefix used by {@link Broker.reclaimStaleLock} when a dead owner's lock is renamed aside. */
|
|
124
|
+
export declare const BROKER_LOCK_TOMBSTONE_PREFIX = ".broker.lock.stale-";
|
|
125
|
+
/**
|
|
126
|
+
* Recovery directories left beside the lock by manual and older automated broker
|
|
127
|
+
* restarts. Nothing writes them today, but installs that ever recovered by hand
|
|
128
|
+
* still carry them, so the reaper owns them alongside its own tombstones.
|
|
129
|
+
*/
|
|
130
|
+
export declare const BROKER_LOCK_BACKUP_PREFIXES: readonly ["broker-restart-backup-", "broker-stale-backup-"];
|
|
131
|
+
/**
|
|
132
|
+
* Age bound before a reclaimed lock artifact may be removed. Generous enough
|
|
133
|
+
* that a broker still settling after a reclaim can never have its own successor
|
|
134
|
+
* state deleted underneath it.
|
|
135
|
+
*/
|
|
136
|
+
export declare const BROKER_LOCK_ARTIFACT_GRACE_MS: number;
|
|
137
|
+
/** Why a candidate lock artifact survived a reap pass. */
|
|
138
|
+
export type BrokerLockArtifactRetentionReason = "within-grace" | "owner-alive" | "owner-record-unreadable" | "owner-record-missing" | "not-a-directory" | "removal-failed";
|
|
139
|
+
export interface BrokerLockArtifactRetention {
|
|
140
|
+
path: string;
|
|
141
|
+
reason: BrokerLockArtifactRetentionReason;
|
|
142
|
+
}
|
|
143
|
+
export interface BrokerLockArtifactReapResult {
|
|
144
|
+
removed: string[];
|
|
145
|
+
retained: BrokerLockArtifactRetention[];
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Remove reclaimed broker lock tombstones and legacy restart backups older than
|
|
149
|
+
* the grace window.
|
|
150
|
+
*
|
|
151
|
+
* `#reclaimStaleLock` renames a dead owner's lock to a tombstone named by a hash
|
|
152
|
+
* of the lock's dev+ino, so a machine accrues one directory per dead owner and
|
|
153
|
+
* nothing ever removed them (54 on the install in #3963). Reaping is
|
|
154
|
+
* best-effort and fail-closed: anything live, unreadable, permission-denied, or
|
|
155
|
+
* otherwise ambiguous is kept and the reason is logged.
|
|
156
|
+
*/
|
|
157
|
+
export declare function reapStaleBrokerLockArtifacts(input: {
|
|
158
|
+
agentDir: string;
|
|
159
|
+
now?: number;
|
|
160
|
+
graceMs?: number;
|
|
161
|
+
pidAlive?: (pid: number) => boolean;
|
|
162
|
+
}): Promise<BrokerLockArtifactReapResult>;
|
|
163
|
+
export interface StartupAdmissionTiming {
|
|
164
|
+
now(): number;
|
|
165
|
+
sleep(ms: number, signal?: AbortSignal): Promise<void>;
|
|
166
|
+
}
|
|
167
|
+
export type StartupAdmissionResult<T> = {
|
|
168
|
+
status: "completed";
|
|
169
|
+
admittedAt: number;
|
|
170
|
+
value: T;
|
|
171
|
+
} | {
|
|
172
|
+
status: "admission_timeout";
|
|
173
|
+
reason: "admission_timeout";
|
|
174
|
+
} | {
|
|
175
|
+
status: "admission_refused";
|
|
176
|
+
reason: "admission_refused";
|
|
177
|
+
};
|
|
178
|
+
export declare function sdkHostStartupConcurrency(availableParallelism?: number): number;
|
|
179
|
+
export declare class StartupAdmissionQueue {
|
|
180
|
+
#private;
|
|
181
|
+
readonly limit: number;
|
|
182
|
+
constructor(limit: number);
|
|
183
|
+
run<T>(queueWaitMs: number, timing: StartupAdmissionTiming, task: (admittedAt: number) => Promise<T>): Promise<StartupAdmissionResult<T>>;
|
|
184
|
+
/**
|
|
185
|
+
* Refuse every queued startup and every later one. A broker that can no longer
|
|
186
|
+
* prove it owns the published root must not spawn children through slots that
|
|
187
|
+
* free up while it is fenced. The epoch also invalidates a waiter that was
|
|
188
|
+
* granted but has not crossed the task execution boundary yet.
|
|
189
|
+
*/
|
|
190
|
+
close(): void;
|
|
191
|
+
/** Accept later startups after fresh publication ownership has been proven. */
|
|
192
|
+
reopen(): void;
|
|
193
|
+
}
|
|
123
194
|
export declare class Broker {
|
|
124
195
|
#private;
|
|
125
196
|
readonly settings: ResolvedBrokerSettings;
|
|
@@ -127,11 +198,30 @@ export declare class Broker {
|
|
|
127
198
|
readonly ledger: LifecycleLedger;
|
|
128
199
|
discovery: BrokerDiscovery | null;
|
|
129
200
|
constructor(settings: BrokerSettings);
|
|
201
|
+
runStartup<T>(queueWaitMs: number, timing: StartupAdmissionTiming, task: (admittedAt: number) => Promise<T>): Promise<StartupAdmissionResult<T>>;
|
|
130
202
|
start(): Promise<BrokerDiscovery>;
|
|
131
203
|
get ownsDiscovery(): boolean;
|
|
132
204
|
get completion(): Promise<void>;
|
|
133
205
|
status(): RedactedBrokerDiscovery | null;
|
|
134
206
|
heartbeat(): Promise<void>;
|
|
207
|
+
/**
|
|
208
|
+
* Revalidate retained publication ownership and begin one synchronous effect in
|
|
209
|
+
* the same stack. The callback is the authority boundary: callers must perform
|
|
210
|
+
* the authorized effect inside it, so no awaited work can separate proof from
|
|
211
|
+
* the effect it authorizes.
|
|
212
|
+
*/
|
|
213
|
+
runSynchronousEffectWithFreshPublicationAuthority<T>(effect: () => T, ..._synchronousOnly: T extends PromiseLike<unknown> ? [never] : []): {
|
|
214
|
+
authorized: true;
|
|
215
|
+
value: T;
|
|
216
|
+
} | {
|
|
217
|
+
authorized: false;
|
|
218
|
+
};
|
|
219
|
+
/**
|
|
220
|
+
* A stop may take the owning path only while it can prove it still owns the root.
|
|
221
|
+
* Claiming ownership it cannot prove keeps the admission queue open, so a startup
|
|
222
|
+
* queued behind this broker is granted a slot that frees after completion and
|
|
223
|
+
* spawns a child the broker has no authority over.
|
|
224
|
+
*/
|
|
135
225
|
stop(): Promise<void>;
|
|
136
226
|
handleRequest(operation: string, input: Record<string, unknown>, idempotencyKey?: string): Promise<BrokerResponse>;
|
|
137
227
|
}
|
|
@@ -139,6 +229,8 @@ export declare class Broker {
|
|
|
139
229
|
export declare function setTerminalPersistenceHookForTest(broker: Broker, hook: (() => void) | undefined): void;
|
|
140
230
|
/** Test-only hook for shortening the bounded ambiguity deadline. */
|
|
141
231
|
export declare function setAmbiguityGraceForTest(broker: Broker, graceMs: number | undefined): void;
|
|
232
|
+
/** Test-only hook for shortening the startup lock-artifact reap bound. */
|
|
233
|
+
export declare function setLockArtifactGraceForTest(broker: Broker, graceMs: number | undefined): void;
|
|
142
234
|
/** Test-only hook for forcing the observation the publication watchdog sees. */
|
|
143
235
|
export declare function setPublicationObservationForTest(broker: Broker, observation: BrokerPublicationObservation | undefined): void;
|
|
144
236
|
export {};
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type ChildProcess } from "node:child_process";
|
|
2
|
+
import type { FileHandle } from "node:fs/promises";
|
|
2
3
|
import { type BrokerDiscovery } from "./discovery";
|
|
3
4
|
import { type SdkInternalSpawnCommand } from "./runtime";
|
|
4
5
|
export interface EnsureBrokerSettings {
|
|
@@ -11,6 +12,22 @@ export interface EnsureBrokerSettings {
|
|
|
11
12
|
*/
|
|
12
13
|
env?: NodeJS.ProcessEnv;
|
|
13
14
|
}
|
|
15
|
+
/**
|
|
16
|
+
* Tail of the detached broker's stderr folded into a discovery failure.
|
|
17
|
+
*
|
|
18
|
+
* The broker used to spawn with `stdio: "ignore"`, so a broker that exited
|
|
19
|
+
* cleanly told the caller nothing beyond `code=0` (#3963). Its stderr goes to a
|
|
20
|
+
* file instead of a pipe because the child is detached and outlives this
|
|
21
|
+
* process: a pipe would break under it the moment the parent exits.
|
|
22
|
+
*/
|
|
23
|
+
export declare const BROKER_SPAWN_LOG_TAIL_BYTES = 4096;
|
|
24
|
+
export interface BrokerSpawnLog {
|
|
25
|
+
path: string;
|
|
26
|
+
handle: FileHandle;
|
|
27
|
+
}
|
|
28
|
+
/** Opens an isolated, bounded-lifetime diagnostic sink for one broker spawn. */
|
|
29
|
+
export declare function openBrokerSpawnLog(agentDir: string): Promise<BrokerSpawnLog | undefined>;
|
|
30
|
+
export declare function readBrokerSpawnLogTail(spawnLogPath: string): Promise<string>;
|
|
14
31
|
export interface FixtureBrokerLease {
|
|
15
32
|
/** Backward-compatible fixture cleanup alias for exact child termination. */
|
|
16
33
|
close(): Promise<void>;
|