@byok-sdk/client 0.13.0 → 0.15.0
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/README.md +95 -0
- package/dist/adapters/claude/process-client.d.ts +24 -0
- package/dist/adapters/codex/codex-adapter.d.ts +2 -0
- package/dist/adapters/codex/process-runner.d.ts +62 -18
- package/dist/adapters/detect-outcome.d.ts +18 -0
- package/dist/adapters/index.d.ts +1 -1
- package/dist/adapters/index.js +546 -76
- package/dist/adapters/index.js.map +1 -1
- package/dist/adapters/pi/events.d.ts +1 -1
- package/dist/adapters/pi/rpc-client.d.ts +47 -1
- package/dist/adapters/pi/subagents-policy-extension.js +1 -1
- package/dist/adapters/pi/subagents-policy-extension.js.map +1 -1
- package/dist/adapters/pi/team-interaction-extension.d.ts +24 -0
- package/dist/adapters/pi/team-interaction-extension.js +81 -0
- package/dist/adapters/pi/team-interaction-extension.js.map +1 -0
- package/dist/adapters/process-tree.d.ts +74 -4
- package/dist/adapters/provider-credential-environment.d.ts +1 -1
- package/dist/adapters/win32-job-object.d.ts +101 -0
- package/dist/bin/byok-agent-memory-mcp.js +29 -0
- package/dist/bin/byok-agent-memory-mcp.js.map +1 -1
- package/dist/bin/byok-agent-message-mcp.js +29 -0
- package/dist/bin/byok-agent-message-mcp.js.map +1 -1
- package/dist/bin/byok-agent-team-mcp.js +29 -0
- package/dist/bin/byok-agent-team-mcp.js.map +1 -1
- package/dist/bin/byok-agent.js +20274 -18057
- package/dist/bin/byok-agent.js.map +1 -1
- package/dist/bin/byok-approval-mcp.js +29 -0
- package/dist/bin/byok-approval-mcp.js.map +1 -1
- package/dist/bin/byok-mcp-env.d.ts +2 -0
- package/dist/bin/byok-mcp-env.js +36 -0
- package/dist/bin/byok-mcp-env.js.map +1 -0
- package/dist/bin/commands/doctor.d.ts +3 -0
- package/dist/bin/commands/team-pi-relay.d.ts +24 -0
- package/dist/bin/commands/team-relay.d.ts +11 -0
- package/dist/bin/mcp-env-launcher.d.ts +5 -0
- package/dist/bin/runtime-probe.d.ts +6 -7
- package/dist/bin/team-codex-relay.d.ts +32 -0
- package/dist/bin/team-notification-relay.d.ts +40 -0
- package/dist/bin/team-pi-session.d.ts +59 -0
- package/dist/daemon/admission-wait.d.ts +2 -0
- package/dist/daemon/agent-egress-policy.d.ts +8 -0
- package/dist/daemon/agent-egress-spool.d.ts +1 -0
- package/dist/daemon/agent-message-outbox.d.ts +15 -3
- package/dist/daemon/artifact-read.d.ts +7 -0
- package/dist/daemon/connection-manager.d.ts +23 -176
- package/dist/daemon/control-protocol.d.ts +2 -0
- package/dist/daemon/create-daemon.d.ts +43 -1
- package/dist/daemon/event-spill.d.ts +90 -0
- package/dist/daemon/journal/journal.d.ts +15 -3
- package/dist/daemon/journal/sqlite-journal.d.ts +7 -2
- package/dist/daemon/long-poll-transport.d.ts +2 -50
- package/dist/daemon/runtime-start.d.ts +3 -0
- package/dist/daemon/store.d.ts +2 -0
- package/dist/daemon/task-runner.d.ts +36 -0
- package/dist/daemon/team-workspace.d.ts +9 -0
- package/dist/daemon/terminal-commit-queue.d.ts +21 -0
- package/dist/daemon/terminal-identity.d.ts +6 -0
- package/dist/diagnostics/device-doctor.d.ts +46 -0
- package/dist/diagnostics/diagnostics.d.ts +3 -80
- package/dist/diagnostics/types.d.ts +82 -0
- package/dist/index.d.ts +3 -1
- package/dist/index.js +2824 -649
- package/dist/index.js.map +1 -1
- package/dist/runtime-detection.d.ts +3 -0
- package/dist/runtime-failure.d.ts +6 -0
- package/dist/sdk-reserved-helper-host.d.ts +1 -1
- package/dist/types.d.ts +20 -10
- package/dist/util/durable-jsonl.d.ts +12 -0
- package/package.json +8 -5
|
@@ -307,6 +307,43 @@ export interface DaemonConfig {
|
|
|
307
307
|
* explicitly instead to opt out of enforcement altogether.
|
|
308
308
|
*/
|
|
309
309
|
maxTaskOutputBytes?: number;
|
|
310
|
+
/** Legacy artifact bytes only: default 16 MiB/file and 64 MiB/task, independent of event output limits. */
|
|
311
|
+
artifactLimits?: {
|
|
312
|
+
maxFileBytes: number;
|
|
313
|
+
maxTaskBytes: number;
|
|
314
|
+
};
|
|
315
|
+
/** Admission and startup deadline, including pure detect/prepare waits; unresolved process owners remain quarantined. Default 30 seconds. */
|
|
316
|
+
startupTimeoutMs?: number;
|
|
317
|
+
/**
|
|
318
|
+
* Per-EVENT inline ceiling (default {@link DEFAULT_MAX_INLINE_EVENT_BYTES},
|
|
319
|
+
* 64 KiB) for the two `AgentEvent` fields a runtime authors freely:
|
|
320
|
+
* `tool_use.input` and `tool_result.output`. An event whose serialization
|
|
321
|
+
* exceeds this leaves `TaskRunner.pump` with that field replaced by a
|
|
322
|
+
* UTF-8-safe head/tail preview (`{ preview: { head, tail } }`) and an
|
|
323
|
+
* additive `spill` descriptor; the full JSON serialization is uploaded to
|
|
324
|
+
* the blob plane under an idempotent, content-addressed key, and
|
|
325
|
+
* `spill.blob` is where a consumer reads it back. If the upload fails the
|
|
326
|
+
* preview still ships, carrying `spill.unstoredReason` instead — omission
|
|
327
|
+
* is always described, never silent.
|
|
328
|
+
*
|
|
329
|
+
* This is a per-event bound, orthogonal to `maxTaskOutputBytes` (a
|
|
330
|
+
* whole-task total, counted AFTER spilling) and to
|
|
331
|
+
* `progressBatch.maxBatchBytes` (a per-batch wire budget).
|
|
332
|
+
*
|
|
333
|
+
* **Consumer contract:** `spill`'s presence is the only signal that the
|
|
334
|
+
* inline field is a preview. A consumer that renders `tool_result.output`
|
|
335
|
+
* without checking `spill` renders a truncation as the whole result.
|
|
336
|
+
*
|
|
337
|
+
* Must be a positive safe integer of at least
|
|
338
|
+
* {@link MIN_MAX_INLINE_EVENT_BYTES} (4096) — below that a legitimate
|
|
339
|
+
* `spill` descriptor no longer fits inside the cap it exists to enforce.
|
|
340
|
+
* Anything else (0, negative, non-integer, `NaN`,
|
|
341
|
+
* `Number.POSITIVE_INFINITY`) is a config validation error thrown
|
|
342
|
+
* synchronously from `createDaemonWithAdapters`/`createDaemon`; there is
|
|
343
|
+
* no opt-out, because "unbounded event" is exactly the state this exists
|
|
344
|
+
* to prevent.
|
|
345
|
+
*/
|
|
346
|
+
maxInlineEventBytes?: number;
|
|
310
347
|
/**
|
|
311
348
|
* Host-owned batching policy for normalized `task.progress` events.
|
|
312
349
|
* `maxBatchBytes`, when set, measures exactly the UTF-8 bytes of
|
|
@@ -498,6 +535,8 @@ export interface DaemonStatus {
|
|
|
498
535
|
revoked: boolean;
|
|
499
536
|
deviceId?: string;
|
|
500
537
|
activeTaskCount: number;
|
|
538
|
+
/** Exact terminal results retained for journal retry; nonzero requires recovery. */
|
|
539
|
+
pendingTerminalCommits: number;
|
|
501
540
|
/** Passthrough of `DaemonConfig.branding` — `undefined` when the product configured none. See `DaemonBranding`. */
|
|
502
541
|
branding?: DaemonBranding;
|
|
503
542
|
/** Local lifecycle/retry budget, separate from transport fallback state. */
|
|
@@ -557,8 +596,11 @@ export interface Daemon {
|
|
|
557
596
|
/** M3-2a: same as {@link approve} but rejects — see that method's doc comment. */
|
|
558
597
|
reject(taskId: string, reason?: string): Promise<void>;
|
|
559
598
|
}
|
|
560
|
-
/**
|
|
599
|
+
/** Custom adapter composition. Available custom descriptors are published through
|
|
600
|
+
* the capability-gated harness inventory; built-in RuntimeId remains closed. */
|
|
561
601
|
export interface DaemonOverrides {
|
|
602
|
+
/** Test-only synchronous kill points; never supplied by production configuration. */
|
|
603
|
+
executionRecoveryFault?: (step: 'terminal:before-send' | 'terminal:queued' | 'outbound:before-post' | 'outbound:after-ack') => void;
|
|
562
604
|
/** M4 Phase 3: overrides `TaskRunner`'s default out-of-band approval wait (`DEFAULT_APPROVAL_TIMEOUT_MS`, 10 minutes) before an unanswered `requestApproval` force-resolves as a fail-closed rejection. */
|
|
563
605
|
approvalTimeoutMs?: number;
|
|
564
606
|
/** Finding F5: overrides for the control-socket shutdown path's own bounded waits — see `TaskRunner.shutdownTask`'s and `ConnectionManager.stop`'s own doc comments. Both default to 5s; neither affects an ordinary (non-shutdown-RPC) `daemon.stop()` call. */
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import type { AgentEvent } from '@byok-sdk/protocol';
|
|
2
|
+
import type { BlobResolver } from './blob-client';
|
|
3
|
+
/**
|
|
4
|
+
* Default per-event inline ceiling (64 KiB) for the two `AgentEvent` variants
|
|
5
|
+
* that carry runtime-authored payloads (`tool_use.input`,
|
|
6
|
+
* `tool_result.output`) — see `DaemonConfig.maxInlineEventBytes`
|
|
7
|
+
* (`create-daemon.ts`) for the host-facing contract.
|
|
8
|
+
*
|
|
9
|
+
* 64 KiB is the same threshold `sendArtifact` already uses to decide inline
|
|
10
|
+
* vs. blob for an artifact (`MAX_INLINE_ARTIFACT_BYTES`): one number for "a
|
|
11
|
+
* payload this daemon is willing to put on the activity wire".
|
|
12
|
+
*/
|
|
13
|
+
export declare const DEFAULT_MAX_INLINE_EVENT_BYTES: number;
|
|
14
|
+
/**
|
|
15
|
+
* Smallest accepted `maxInlineEventBytes`. Below this a legitimate spill
|
|
16
|
+
* descriptor (`field` + byte counts + a `BlobRef` whose `blobId` is chosen by
|
|
17
|
+
* the server, plus the event's own `type`/`tool`/`toolCallId`) stops fitting
|
|
18
|
+
* inside the cap it is supposed to keep the event under, which would turn a
|
|
19
|
+
* host's configuration mistake into a per-event runtime invariant failure.
|
|
20
|
+
* Enforced up front at `DaemonConfig` validation, never here.
|
|
21
|
+
*/
|
|
22
|
+
export declare const MIN_MAX_INLINE_EVENT_BYTES = 4096;
|
|
23
|
+
/**
|
|
24
|
+
* Worst-case JSON cost of the `,"spill":{…}` fragment when the descriptor
|
|
25
|
+
* carries an `unstoredReason` rather than a server-chosen `BlobRef`.
|
|
26
|
+
*
|
|
27
|
+
* Every part is bounded by construction, which a `BlobRef` is not — its
|
|
28
|
+
* `blobId` is an arbitrary-length server-chosen string:
|
|
29
|
+
*
|
|
30
|
+
* ```
|
|
31
|
+
* ,"spill": 9
|
|
32
|
+
* { 1
|
|
33
|
+
* "field":"output", 17 ("output" is the longer of the two)
|
|
34
|
+
* "totalBytes":<=16 digits>, 30
|
|
35
|
+
* "omittedBytes":<=16 digits>, 32 (never exceeds totalBytes)
|
|
36
|
+
* "contentType":"application/json", 33 (a module constant)
|
|
37
|
+
* "unstoredReason": 17
|
|
38
|
+
* "<=512 bytes of escaped reason>" 514 (MAX_UNSTORED_REASON_BYTES + 2 quotes)
|
|
39
|
+
* } 1
|
|
40
|
+
* ----
|
|
41
|
+
* 654
|
|
42
|
+
* ```
|
|
43
|
+
*
|
|
44
|
+
* Rounded up to 768 so digit-count growth cannot invalidate it. Because the
|
|
45
|
+
* event is spread first and `spill` written last, a bounded event is EXACTLY
|
|
46
|
+
* the empty-preview skeleton plus this fragment — so refusing to spill unless
|
|
47
|
+
* `skeleton + MAX_SPILL_DESCRIPTOR_BYTES <= maxInlineBytes` is what makes the
|
|
48
|
+
* final cap check unreachable rather than merely unlikely. `event-spill.test.ts`
|
|
49
|
+
* asserts this against a maximally escaping reason instead of trusting the
|
|
50
|
+
* comment.
|
|
51
|
+
*/
|
|
52
|
+
export declare const MAX_SPILL_DESCRIPTOR_BYTES = 768;
|
|
53
|
+
export interface EventSpillDeps {
|
|
54
|
+
/** Effective inline ceiling for this daemon; already validated at the `DaemonConfig` layer. */
|
|
55
|
+
maxInlineBytes: number;
|
|
56
|
+
/** Only the upload half of `BlobResolver` is needed, so a test double stays minimal. */
|
|
57
|
+
blobClient: Pick<BlobResolver, 'uploadArtifact'>;
|
|
58
|
+
/** Scopes the upload's idempotency key to the task that produced the event. */
|
|
59
|
+
taskId: string;
|
|
60
|
+
/** Task lifecycle authority — aborting it stops the spill upload with the rest of the task's blob I/O. */
|
|
61
|
+
signal?: AbortSignal;
|
|
62
|
+
/** Diagnostic seam. Called only on a path that loses information (upload failure, or an event this policy cannot bound). */
|
|
63
|
+
log?: (message: string) => void;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Bound one normalized `AgentEvent` at the daemon's ingestion boundary
|
|
67
|
+
* (`TaskRunner.pump`).
|
|
68
|
+
*
|
|
69
|
+
* An event whose serialized form already fits `maxInlineBytes` is returned
|
|
70
|
+
* **as the same object reference** — the overwhelming majority of events pay
|
|
71
|
+
* exactly one `JSON.stringify` and nothing else, and no downstream identity
|
|
72
|
+
* comparison changes meaning.
|
|
73
|
+
*
|
|
74
|
+
* An oversized `tool_use` / `tool_result` has its runtime-authored field
|
|
75
|
+
* (`input` / `output`) uploaded to the blob plane in full and REPLACED inline
|
|
76
|
+
* by `{ preview: { head, tail } }`, with an additive `spill` descriptor
|
|
77
|
+
* carrying either the resulting `BlobRef` or a bounded `unstoredReason`. The
|
|
78
|
+
* replacement is *measured* against the cap, never assumed to fit: the
|
|
79
|
+
* preview budget is whatever is left after the rest of the event and the
|
|
80
|
+
* real descriptor, and it is shrunk until `JSON.stringify(result)` actually
|
|
81
|
+
* fits (JSON escaping can cost several bytes per source character, so the
|
|
82
|
+
* byte budget alone is not a bound).
|
|
83
|
+
*
|
|
84
|
+
* Storage failure is never silent and never fatal: the preview still ships,
|
|
85
|
+
* `unstoredReason` says why the omitted bytes are unreadable, and `log` is
|
|
86
|
+
* called. The runtime's own transcript still holds the content, so failing
|
|
87
|
+
* the task over a telemetry upload would trade a real result for an
|
|
88
|
+
* observability problem.
|
|
89
|
+
*/
|
|
90
|
+
export declare function spillOversizedEvent(event: AgentEvent, deps: EventSpillDeps): Promise<AgentEvent>;
|
|
@@ -124,18 +124,21 @@ export interface LocalTransitionRecord {
|
|
|
124
124
|
/** Whether the cloud has confirmed the terminal this daemon produced (§12.7.3's "terminal 生成后、truth 写入前" window). */
|
|
125
125
|
export type TerminalTruthState = 'pending' | 'confirmed' | 'failed';
|
|
126
126
|
/**
|
|
127
|
-
*
|
|
128
|
-
*
|
|
127
|
+
* One immutable canonical terminal and its delivery projection. Bytes are the
|
|
128
|
+
* replay authority; the hash is checked against them, never used as a substitute.
|
|
129
129
|
*/
|
|
130
130
|
export interface LocalTerminalRecord {
|
|
131
131
|
readonly taskId: string;
|
|
132
|
-
readonly terminalType: 'complete' | 'failed' | 'cancelled';
|
|
132
|
+
readonly terminalType: 'complete' | 'failed' | 'cancelled' | 'declined';
|
|
133
|
+
readonly bytes: string;
|
|
133
134
|
readonly payloadHash: string;
|
|
134
135
|
readonly truthState: TerminalTruthState;
|
|
135
136
|
/** How many times delivery to the cloud has been attempted. */
|
|
136
137
|
readonly attempt: number;
|
|
137
138
|
readonly lastError?: string;
|
|
138
139
|
readonly recordedAt: string;
|
|
140
|
+
/** Committed atomically with the original interruption report. */
|
|
141
|
+
readonly recovery?: RecoveryOutcome;
|
|
139
142
|
}
|
|
140
143
|
/** A task the journal knows about that has no terminal and no recovery marker — i.e. one this daemon was in the middle of when it stopped. */
|
|
141
144
|
export interface RecoverableTask {
|
|
@@ -147,6 +150,7 @@ export interface RecoverableTask {
|
|
|
147
150
|
readonly claimedRuntime?: string;
|
|
148
151
|
readonly workspaceRef?: string;
|
|
149
152
|
readonly updatedAt: string;
|
|
153
|
+
readonly envelopeBytes: string;
|
|
150
154
|
}
|
|
151
155
|
/**
|
|
152
156
|
* What recovery decided about a task. `interrupted` is the honest default for
|
|
@@ -262,6 +266,14 @@ export interface LocalTaskJournal {
|
|
|
262
266
|
recordTransition(record: LocalTransitionRecord): Promise<void>;
|
|
263
267
|
/** Record (or update the retry state of) a task's terminal. Idempotent by task id: a replay with the same payload hash is a no-op beyond retry bookkeeping. */
|
|
264
268
|
recordTerminal(record: LocalTerminalRecord): Promise<void>;
|
|
269
|
+
/** Exact original terminal bytes, including rejected records, until acknowledged. */
|
|
270
|
+
listPendingTerminals(identity: JournalIdentity): Promise<LocalTerminalRecord[]>;
|
|
271
|
+
/** A successful authenticated transport disposition, bound to the original bytes. */
|
|
272
|
+
confirmTerminal(taskId: string, payloadHash: string): Promise<void>;
|
|
273
|
+
rejectTerminal(taskId: string, payloadHash: string, reason: string): Promise<void>;
|
|
274
|
+
/** Includes old interruption markers without reports; they must not hide pending work. */
|
|
275
|
+
listRecoveryTasks(identity: JournalIdentity): Promise<RecoverableTask[]>;
|
|
276
|
+
readTask(taskId: string, identity: JournalIdentity): Promise<RecoverableTask | undefined>;
|
|
265
277
|
/** Tasks with no terminal and no recovery marker — what this daemon was in the middle of when it last stopped. */
|
|
266
278
|
listRecoverable(): Promise<RecoverableTask[]>;
|
|
267
279
|
/** Close out one recoverable task by writing its recovery marker. Never deletes; a marked row is on §12.7.2.1's never-auto-delete list. */
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type AdmissionRecord, type CategoryUsage, type CleanableCategory, type CleanupCandidate, type CleanupResult, type CompactOptions, type CompactResult, type JournalReceipt, type LocalStorageUsage, type LocalTaskJournal, type LocalTerminalRecord, type LocalTransitionRecord, type RecoverableTask, type RecoveryOutcome, type ReceivedEnvelopeRecord, type StorageCategory } from './journal';
|
|
1
|
+
import { type JournalIdentity, type AdmissionRecord, type CategoryUsage, type CleanableCategory, type CleanupCandidate, type CleanupResult, type CompactOptions, type CompactResult, type JournalReceipt, type LocalStorageUsage, type LocalTaskJournal, type LocalTerminalRecord, type LocalTransitionRecord, type RecoverableTask, type RecoveryOutcome, type ReceivedEnvelopeRecord, type StorageCategory } from './journal';
|
|
2
2
|
import { JournalHandleCleanupError, type JournalOpenFaultSeam } from './sqlite-support';
|
|
3
3
|
export { JournalHandleCleanupError };
|
|
4
4
|
/** The single database file, per §12.7.2's "建议单库 `<storeDir>/daemon.db`". */
|
|
@@ -24,7 +24,7 @@ export declare const DEFAULT_JOURNAL_BUSY_TIMEOUT_MS = 5000;
|
|
|
24
24
|
* (`util/secure-dir.ts`): a seam the production path never supplies, exercised
|
|
25
25
|
* from any host.
|
|
26
26
|
*/
|
|
27
|
-
export type JournalFaultStep = 'append:before-begin' | 'append:after-envelope' | 'append:after-task' | 'append:after-receipt' | 'append:before-commit' | 'admission:before-commit' | 'transition:before-commit' | 'terminal:before-commit' | 'recovery:before-commit' | 'cleanup:before-commit' | 'prune:before-commit';
|
|
27
|
+
export type JournalFaultStep = 'append:before-begin' | 'append:after-envelope' | 'append:after-task' | 'append:after-receipt' | 'append:before-commit' | 'append:after-commit' | 'admission:before-commit' | 'transition:before-commit' | 'terminal:before-commit' | 'terminal:after-commit' | 'recovery:after-commit' | 'confirm:before-commit' | 'confirm:after-commit' | 'recovery:before-commit' | 'cleanup:before-commit' | 'prune:before-commit';
|
|
28
28
|
export interface JournalFaultSeam {
|
|
29
29
|
/** Throw to simulate a crash or IO error at exactly this step. Return normally to proceed. */
|
|
30
30
|
onStep?(step: JournalFaultStep): void;
|
|
@@ -79,6 +79,11 @@ export declare class SqliteLocalTaskJournal implements LocalTaskJournal {
|
|
|
79
79
|
* first fact stands.
|
|
80
80
|
*/
|
|
81
81
|
recordTerminal(record: LocalTerminalRecord): Promise<void>;
|
|
82
|
+
readTask(taskId: string, identity: JournalIdentity): Promise<RecoverableTask | undefined>;
|
|
83
|
+
listRecoveryTasks(identity: JournalIdentity): Promise<RecoverableTask[]>;
|
|
84
|
+
listPendingTerminals(identity: JournalIdentity): Promise<LocalTerminalRecord[]>;
|
|
85
|
+
confirmTerminal(taskId: string, payloadHash: string): Promise<void>;
|
|
86
|
+
rejectTerminal(taskId: string, payloadHash: string, reason: string): Promise<void>;
|
|
82
87
|
/**
|
|
83
88
|
* What this daemon was in the middle of: a task whose offer envelope is
|
|
84
89
|
* durable, that has no terminal, that was not declined, and that recovery
|
|
@@ -49,56 +49,8 @@ export interface LongPollClientOptions {
|
|
|
49
49
|
onRevoked?: () => void;
|
|
50
50
|
/** Called when the server cannot replay the durable cursor supplied to this poll. */
|
|
51
51
|
onReplayCursorTooOld?: (error: ReplayCursorTooOldError) => void;
|
|
52
|
-
/**
|
|
53
|
-
*
|
|
54
|
-
* called ONLY for a batch entry that failed to parse because its `type`
|
|
55
|
-
* is entirely unrecognized (`parseMessage` throwing
|
|
56
|
-
* {@link UnknownMessageTypeError}) and which still carries a
|
|
57
|
-
* numeric envelope-level `seq` AND a recognizably task-class `type` (a
|
|
58
|
-
* `task.` prefix — see `extractSkippableSeq`'s own doc comment for why a
|
|
59
|
-
* `conn.*`-shaped or type-less entry is deliberately excluded, mirroring
|
|
60
|
-
* F2's "conn.* is never cursor-tracked" rule), so the caller can advance
|
|
61
|
-
* its cursor/watermark past it even though there is no real `Envelope` to
|
|
62
|
-
* hand to `onEnvelope`. Without this, a persistently-redelivered
|
|
63
|
-
* unrecognized-type entry (the real server retains and redelivers an
|
|
64
|
-
* un-acked envelope, protocol §9) would keep reappearing at the same
|
|
65
|
-
* cursor position forever.
|
|
66
|
-
*
|
|
67
|
-
* Finding F1: a RECOGNIZED type that fails schema validation
|
|
68
|
-
* ({@link EnvelopeValidationError} — e.g. a `task.offer` whose
|
|
69
|
-
* `PermissionPolicy` rejects an unknown constraint) is deliberately NOT
|
|
70
|
-
* reported here. That failure is a genuinely malformed control message,
|
|
71
|
-
* not forward-compat tolerance — forwarding its `seq` here would
|
|
72
|
-
* permanently ack a message the daemon never actually understood (the
|
|
73
|
-
* server would stop redelivering it, silently stranding whatever it was
|
|
74
|
-
* offering). This callback being scoped to `UnknownMessageTypeError` only
|
|
75
|
-
* preserves the no-silent-permanent-ack property. Optional
|
|
76
|
-
* only for constructor/test convenience — `ConnectionManager` always
|
|
77
|
-
* supplies it.
|
|
78
|
-
*/
|
|
79
|
-
onSkippedSeq?: (seq: number) => void;
|
|
80
|
-
/**
|
|
81
|
-
* Finding R1 (cross-model re-review — the F1 fix alone was NOT-CLOSED):
|
|
82
|
-
* called for a batch entry whose `type` WAS recognized but whose payload
|
|
83
|
-
* failed schema validation ({@link EnvelopeValidationError}) — a genuine
|
|
84
|
-
* delivery failure at that specific seq, not forward-compat tolerance
|
|
85
|
-
* (contrast {@link onSkippedSeq}, which is scoped to the opposite case,
|
|
86
|
-
* an entirely unrecognized type). F1's own fix — simply not forwarding
|
|
87
|
-
* this seq to `onSkippedSeq` — turned out to be insufficient on its own:
|
|
88
|
-
* a LATER valid envelope in the same or a later batch would still
|
|
89
|
-
* silently advance the durable cursor PAST this seq once its own handler
|
|
90
|
-
* succeeded, since nothing had told `ConnectionManager` this seq needed
|
|
91
|
-
* the same stall treatment a thrown handler failure already gets — an
|
|
92
|
-
* INDIRECT permanent ack, one hop removed from the exact bug F1 set out
|
|
93
|
-
* to fix. `ConnectionManager` (`noteValidationFailure`) engages
|
|
94
|
-
* `stalledAtSeq` for this seq the same way `process()`'s own catch block
|
|
95
|
-
* does for a real thrown handler — freezing `dedupWatermark()` at the
|
|
96
|
-
* durable cursor (so the server's retain-and-redeliver semantics,
|
|
97
|
-
* protocol §9, keep this seq alive) and, via that SAME existing
|
|
98
|
-
* machinery, holding back the cursor for anything else delivered after it
|
|
99
|
-
* in the same batch too, exactly as a real handler failure already would.
|
|
100
|
-
* Optional only for constructor/test convenience — `ConnectionManager`
|
|
101
|
-
* always supplies it.
|
|
52
|
+
/** Unknown or malformed executable messages have no durable disposition.
|
|
53
|
+
* Freeze their sequence; a later valid message must not acknowledge them.
|
|
102
54
|
*/
|
|
103
55
|
onValidationFailedSeq?: (seq: number) => void;
|
|
104
56
|
/**
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { RuntimeOperationStartInput, Session } from '../types';
|
|
2
|
+
/** A deadline withdraws admission; it never invents a quiescence receipt. */
|
|
3
|
+
export declare function startOwnedRuntime(start: (input: RuntimeOperationStartInput) => Promise<Session>, input: RuntimeOperationStartInput, signal: AbortSignal, timeoutMs: number): Promise<Session>;
|
package/dist/daemon/store.d.ts
CHANGED
|
@@ -72,6 +72,8 @@ export declare class DeviceStore {
|
|
|
72
72
|
* identifiable until the synchronous pathname check and unlink complete.
|
|
73
73
|
*/
|
|
74
74
|
remove(): Promise<DeviceMetadata | undefined>;
|
|
75
|
+
/** Rebuild only the non-secret projection, while the caller owns the store lease. */
|
|
76
|
+
reconcileMetadata(authority: DeviceMetadata): Promise<boolean>;
|
|
75
77
|
save(record: DeviceMetadata): Promise<void>;
|
|
76
78
|
private openBounded;
|
|
77
79
|
}
|
|
@@ -228,6 +228,9 @@ export interface TaskRunnerDeps {
|
|
|
228
228
|
agentSessionHandoffs?: AgentSessionHandoffStore;
|
|
229
229
|
deviceId: string;
|
|
230
230
|
send: (envelope: Envelope) => void;
|
|
231
|
+
awaitTerminalCommit?: (taskId: string) => Promise<void>;
|
|
232
|
+
/** Fsync the execution commitment before claim/runtime side effects. */
|
|
233
|
+
beforeClaim?: (taskId: string, runtime: string) => Promise<void>;
|
|
231
234
|
blobClient: BlobResolver;
|
|
232
235
|
batcherOptions?: ProgressBatcherOptions;
|
|
233
236
|
/**
|
|
@@ -321,6 +324,7 @@ export interface TaskRunnerDeps {
|
|
|
321
324
|
onApprovalDispatched?: (taskId: string, approvalId: string) => void;
|
|
322
325
|
/** Overrides the bounded soft-interrupt window before authoritative `Session.close()` disposal begins. */
|
|
323
326
|
shutdownInterruptTimeoutMs?: number;
|
|
327
|
+
startupTimeoutMs?: number;
|
|
324
328
|
/**
|
|
325
329
|
* M5 batch-3 (workstream 2): overrides {@link DEFAULT_MAX_TASK_OUTPUT_BYTES}
|
|
326
330
|
* — see that constant's own doc comment and `DaemonConfig.maxTaskOutputBytes`
|
|
@@ -330,6 +334,19 @@ export interface TaskRunnerDeps {
|
|
|
330
334
|
* interface (`shutdownInterruptTimeoutMs`, `approvalTimeoutMs`).
|
|
331
335
|
*/
|
|
332
336
|
maxTaskOutputBytes?: number;
|
|
337
|
+
artifactLimits?: {
|
|
338
|
+
maxFileBytes: number;
|
|
339
|
+
maxTaskBytes: number;
|
|
340
|
+
};
|
|
341
|
+
/**
|
|
342
|
+
* Per-event inline ceiling for `tool_use.input` / `tool_result.output` —
|
|
343
|
+
* see `DaemonConfig.maxInlineEventBytes` (`create-daemon.ts`) for the full
|
|
344
|
+
* contract and {@link DEFAULT_MAX_INLINE_EVENT_BYTES} for the default.
|
|
345
|
+
* Validated (positive safe integer, at least
|
|
346
|
+
* `MIN_MAX_INLINE_EVENT_BYTES`) at the `DaemonConfig` layer, not here —
|
|
347
|
+
* this seam trusts its caller, same as `maxTaskOutputBytes` above.
|
|
348
|
+
*/
|
|
349
|
+
maxInlineEventBytes?: number;
|
|
333
350
|
/**
|
|
334
351
|
* M4 (additive-minor, `task.approval_resolved`): the capabilities advertised
|
|
335
352
|
* by the CURRENT transport's server (`conn.ack` on WS, the latest successful
|
|
@@ -569,6 +586,8 @@ export declare class TaskRunner {
|
|
|
569
586
|
usesAgentEgress(taskId: string): boolean;
|
|
570
587
|
/** M5 batch-3 (workstream 2): effective `maxTaskOutputBytes` cap for this daemon — see {@link DEFAULT_MAX_TASK_OUTPUT_BYTES}'s own doc comment. */
|
|
571
588
|
private get maxTaskOutputBytes();
|
|
589
|
+
/** Effective per-event inline ceiling for this daemon — see `DaemonConfig.maxInlineEventBytes`. */
|
|
590
|
+
private get maxInlineEventBytes();
|
|
572
591
|
/** WP0: effective per-canonical-Agent-home Attempt cap — see {@link DEFAULT_MAX_CONCURRENT_MUTABLE_SESSIONS_PER_AGENT_HOME}. */
|
|
573
592
|
private get maxConcurrentMutableSessionsPerAgentHome();
|
|
574
593
|
/**
|
|
@@ -614,8 +633,13 @@ export declare class TaskRunner {
|
|
|
614
633
|
/** Restore activated, unaccepted message drafts before transport admission on daemon restart. */
|
|
615
634
|
recoverAgentMessageOutboxes(agentsRoot: string): Promise<void>;
|
|
616
635
|
private agentMessageOutbox;
|
|
636
|
+
/** A recovery terminal must not close first-message admission before this durable draft has a disposition. */
|
|
637
|
+
hasPendingRecoveredAgentMessage(taskId: string): boolean;
|
|
617
638
|
/** Retry stable recovered records after a transport handshake/re-handshake. */
|
|
618
639
|
retryRecoveredAgentMessages(): void;
|
|
640
|
+
private canPublishAgentMessage;
|
|
641
|
+
/** All authors and startup use the same queue-time and post-I/O authority check. */
|
|
642
|
+
private activateAgentMessage;
|
|
619
643
|
private sendAgentMessageRecord;
|
|
620
644
|
private handleAgentMessageDisposition;
|
|
621
645
|
/** M4 Phase 2: stop claiming any FUTURE `task.offer` — see `stoppingOffers`'s own doc comment. Idempotent. */
|
|
@@ -657,6 +681,14 @@ export declare class TaskRunner {
|
|
|
657
681
|
* ahead of this method — see `daemon-control-socket.test.ts`'s dedicated
|
|
658
682
|
* regression test for the exact scenario.
|
|
659
683
|
*/
|
|
684
|
+
/** Startup resources have an owner even before a Session can become active. */
|
|
685
|
+
private startupRetryTimer;
|
|
686
|
+
private readonly claimedHarnesses;
|
|
687
|
+
private readonly homeReservations;
|
|
688
|
+
private readonly startupOwners;
|
|
689
|
+
private readonly startupDisposals;
|
|
690
|
+
private disposeStartupOwner;
|
|
691
|
+
private disposeStartupOwnerOnce;
|
|
660
692
|
shutdownActiveTasks(reason: string): Promise<void>;
|
|
661
693
|
/**
|
|
662
694
|
* M5 batch-3 (workstream 2): the ONE shared per-task teardown sequence —
|
|
@@ -688,6 +720,7 @@ export declare class TaskRunner {
|
|
|
688
720
|
* a genuine protocol bug, not a benign race — mirrors `pump()`'s own
|
|
689
721
|
* identity-check guard for the same class of race.
|
|
690
722
|
*/
|
|
723
|
+
private interruptBounded;
|
|
691
724
|
private teardownActiveTask;
|
|
692
725
|
/** Graceful-shutdown caller of {@link teardownActiveTask} — see `shutdownActiveTasks`'s own doc comment. `retryable: true`: nothing about the task/policy itself was ever at fault, only this device's own availability right now. */
|
|
693
726
|
private shutdownTask;
|
|
@@ -1138,7 +1171,10 @@ export declare class TaskRunner {
|
|
|
1138
1171
|
private persistAgentTerminalEvidence;
|
|
1139
1172
|
private retryAgentTerminalEvidence;
|
|
1140
1173
|
private reportAgentTerminalEvidenceFailure;
|
|
1174
|
+
retryTerminalFinalization(taskId: string): Promise<void>;
|
|
1175
|
+
private readonly finalizationAttempts;
|
|
1141
1176
|
private finish;
|
|
1177
|
+
private finishOnce;
|
|
1142
1178
|
private reserveSemanticTerminal;
|
|
1143
1179
|
/** M3-B: bounded insert for `finishedTaskIds` — see its class-level doc comment and `MAX_TRACKED_TASK_IDS`. Evicts the oldest (first-inserted) entry once over cap, same idiom as `ConnectionHub.checkAndRecordDuplicate` (packages/server/src/hub.ts). */
|
|
1144
1180
|
private addFinishedTaskId;
|
|
@@ -191,6 +191,15 @@ export declare class LocalTeamWorkspace {
|
|
|
191
191
|
expiresAt: string;
|
|
192
192
|
}>>;
|
|
193
193
|
postMessage(input: TeamPostMessageInput): Promise<TeamMessageAcceptedReceipt>;
|
|
194
|
+
/** Metadata-only operator notification view. Never advances delivery or acknowledgement. */
|
|
195
|
+
notificationSnapshot(input: TeamReadMessagesInput): Promise<{
|
|
196
|
+
workspaceId: string;
|
|
197
|
+
memberId: string;
|
|
198
|
+
registryRevision: TeamWorkspaceRevision;
|
|
199
|
+
expiresAt: string;
|
|
200
|
+
acknowledgedThroughSeq: number;
|
|
201
|
+
latestPeerSeq: number | null;
|
|
202
|
+
}>;
|
|
194
203
|
readMessages(input: TeamReadMessagesInput): Promise<TeamReadMessagesResult>;
|
|
195
204
|
ackMessages(input: TeamAckMessagesInput): Promise<TeamAckReceipt>;
|
|
196
205
|
private resolveLease;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/** Volatile ownership of exact terminal bytes until the journal accepts them.
|
|
2
|
+
* The journal remains the sole durable authority. Failed receipts stay rejected;
|
|
3
|
+
* only the internal scheduling tail recovers so another task can make progress.
|
|
4
|
+
*/
|
|
5
|
+
export declare class TerminalCommitQueue {
|
|
6
|
+
private readonly committed;
|
|
7
|
+
private readonly pending;
|
|
8
|
+
private tail;
|
|
9
|
+
private timer;
|
|
10
|
+
private stopped;
|
|
11
|
+
constructor(committed: (taskId: string) => void);
|
|
12
|
+
get pendingCount(): number;
|
|
13
|
+
hasPending(taskId: string): boolean;
|
|
14
|
+
enqueue(taskId: string, commit: () => Promise<void>): void;
|
|
15
|
+
receipt(taskId: string): Promise<void>;
|
|
16
|
+
retry(taskId: string): Promise<void>;
|
|
17
|
+
resume(): void;
|
|
18
|
+
stop(): Promise<void>;
|
|
19
|
+
private attempt;
|
|
20
|
+
private armRetry;
|
|
21
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { type AgentRef } from '@byok-sdk/protocol';
|
|
2
|
+
/** Project sealed execution facts, never the offer's requested harness. */
|
|
3
|
+
export declare function terminalIdentity(runtimeId: string | undefined, agentRef?: AgentRef): {
|
|
4
|
+
harnessId?: string;
|
|
5
|
+
agentRef?: AgentRef;
|
|
6
|
+
};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { DaemonConfig } from '../daemon/create-daemon';
|
|
2
|
+
import type { RuntimeAdapter } from '../types';
|
|
3
|
+
import type { DiagnosticsSnapshot } from './types';
|
|
4
|
+
export type { DiagnosticsSnapshot, DiagnosticCheck, DiagnosticStatus } from './types';
|
|
5
|
+
export interface DiagnoseDeviceOptions {
|
|
6
|
+
/** The same adapters used by an embedded host; omitted uses bundled adapters. */
|
|
7
|
+
adapters?: RuntimeAdapter[];
|
|
8
|
+
runtimeProbeTimeoutMs?: number;
|
|
9
|
+
}
|
|
10
|
+
/** Read-only device observation; does not read OS credentials or prove Agent readiness. */
|
|
11
|
+
export declare function diagnoseDevice(config: DaemonConfig, options?: DiagnoseDeviceOptions): Promise<DiagnosticsSnapshot>;
|
|
12
|
+
export interface RepairDeviceEnrollmentMetadataInput {
|
|
13
|
+
confirmed: true;
|
|
14
|
+
/** Obtain both from the host's authorized enrollment target, never from a guessed default. */
|
|
15
|
+
expectedDeviceId: string;
|
|
16
|
+
expectedTenantId: string;
|
|
17
|
+
}
|
|
18
|
+
export interface DeviceMetadataRepairResult {
|
|
19
|
+
action: 'restore-enrollment-metadata';
|
|
20
|
+
scope: 'device';
|
|
21
|
+
/** Metadata readback only; does not imply renewed credentials or a running daemon. */
|
|
22
|
+
status: 'repaired' | 'not-needed';
|
|
23
|
+
}
|
|
24
|
+
declare const MESSAGES: {
|
|
25
|
+
readonly 'confirmation-required': 'Explicit confirmation is required for enrollment metadata repair.';
|
|
26
|
+
readonly 'invalid-target': 'An explicit expected tenant and device are required.';
|
|
27
|
+
readonly 'daemon-running': 'Stop the daemon before enrollment metadata repair.';
|
|
28
|
+
readonly 'store-busy': 'The store is owned by another operation; enrollment metadata repair refused.';
|
|
29
|
+
readonly 'authority-unavailable': 'The OS enrollment authority could not be read.';
|
|
30
|
+
readonly 'authority-missing': 'No complete OS enrollment exists; use explicit authenticated pairing.';
|
|
31
|
+
readonly 'target-mismatch': 'The OS enrollment does not match the expected tenant and device.';
|
|
32
|
+
readonly 'projection-unavailable': 'The enrollment metadata could not be read safely; repair refused.';
|
|
33
|
+
readonly 'repair-failed': 'Enrollment metadata repair did not complete; inspect the state before retrying.';
|
|
34
|
+
};
|
|
35
|
+
export type DeviceMetadataRepairErrorCode = keyof typeof MESSAGES;
|
|
36
|
+
/** Closed diagnostics only: no OS stderr, local paths, authority bytes or nested cause. */
|
|
37
|
+
export declare class DeviceMetadataRepairError extends Error {
|
|
38
|
+
readonly code: DeviceMetadataRepairErrorCode;
|
|
39
|
+
constructor(code: DeviceMetadataRepairErrorCode);
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Explicitly restore missing/valid-stale device.json from its existing OS authority.
|
|
43
|
+
* Does not instantiate AuthManager, renew credentials, pair, or start a runtime.
|
|
44
|
+
* Ordinary doctor remains credential-blind; only this confirmed action opens the OS store.
|
|
45
|
+
*/
|
|
46
|
+
export declare function repairDeviceEnrollmentMetadata(config: DaemonConfig, input: RepairDeviceEnrollmentMetadataInput): Promise<DeviceMetadataRepairResult>;
|
|
@@ -1,90 +1,13 @@
|
|
|
1
|
+
import type { DiagnosticsSnapshot } from './types';
|
|
1
2
|
import type { DaemonConfig, RuntimeAdapter } from '../index';
|
|
2
3
|
import { connectControlClient } from '../bin/control-client';
|
|
3
|
-
import
|
|
4
|
-
import { type OperationalHealthFileInspection, OPERATIONAL_HEALTH_FILENAME } from '../daemon/operational-health';
|
|
4
|
+
import { OPERATIONAL_HEALTH_FILENAME } from '../daemon/operational-health';
|
|
5
5
|
export declare const MAX_QUARANTINE_ENTRIES = 100;
|
|
6
6
|
export declare const MAX_QUARANTINE_SCAN_ENTRIES: number;
|
|
7
7
|
export declare const MAX_QUARANTINE_READ_BYTES: number;
|
|
8
8
|
export declare const MAX_DEVICE_RECORD_BYTES: number;
|
|
9
9
|
export declare const MAX_JOURNAL_COPY_BYTES: number;
|
|
10
|
-
export type DiagnosticStatus
|
|
11
|
-
export interface DiagnosticCheck {
|
|
12
|
-
id: 'config' | 'device' | 'runtimes' | 'control' | 'health' | 'journal' | 'workspace' | 'quarantine';
|
|
13
|
-
status: DiagnosticStatus;
|
|
14
|
-
summary: string;
|
|
15
|
-
}
|
|
16
|
-
export interface DiagnosticsSnapshot {
|
|
17
|
-
version: 1;
|
|
18
|
-
generatedAt: string;
|
|
19
|
-
product: {
|
|
20
|
-
nameHash: string;
|
|
21
|
-
idHash: string;
|
|
22
|
-
};
|
|
23
|
-
system: {
|
|
24
|
-
node: string;
|
|
25
|
-
platform: NodeJS.Platform;
|
|
26
|
-
arch: string;
|
|
27
|
-
sqliteAvailable: boolean;
|
|
28
|
-
};
|
|
29
|
-
config: {
|
|
30
|
-
serverProtocol: 'http' | 'https' | 'ws' | 'wss' | 'invalid' | 'unsupported';
|
|
31
|
-
customStoreDir: boolean;
|
|
32
|
-
hostedJournal: boolean;
|
|
33
|
-
runtimeAllowlistCount?: number;
|
|
34
|
-
};
|
|
35
|
-
device: {
|
|
36
|
-
status: 'paired' | 'unpaired' | 'unavailable';
|
|
37
|
-
deviceIdHash?: string;
|
|
38
|
-
};
|
|
39
|
-
runtimes: Array<{
|
|
40
|
-
idHash: string;
|
|
41
|
-
present: boolean;
|
|
42
|
-
versionPresent: boolean;
|
|
43
|
-
authPresent?: boolean;
|
|
44
|
-
steer: boolean;
|
|
45
|
-
resume: boolean;
|
|
46
|
-
permissionModeCount: number;
|
|
47
|
-
}>;
|
|
48
|
-
control: {
|
|
49
|
-
status: 'offline';
|
|
50
|
-
reason: string;
|
|
51
|
-
} | {
|
|
52
|
-
status: 'online';
|
|
53
|
-
pid: number;
|
|
54
|
-
uptimeMs: number;
|
|
55
|
-
transport: string;
|
|
56
|
-
activeTaskCount: number;
|
|
57
|
-
pendingApprovalCount: number;
|
|
58
|
-
operationalHealth: ControlStatusResult['operationalHealth'];
|
|
59
|
-
storage?: ControlStatusResult['storage'];
|
|
60
|
-
};
|
|
61
|
-
health: OperationalHealthFileInspection;
|
|
62
|
-
journal: {
|
|
63
|
-
status: 'missing' | 'present' | 'corrupt' | 'unavailable';
|
|
64
|
-
sizeBytes?: number;
|
|
65
|
-
walBytes?: number;
|
|
66
|
-
integrity?: 'ok' | 'not-checked';
|
|
67
|
-
reason?: string;
|
|
68
|
-
};
|
|
69
|
-
workspace: {
|
|
70
|
-
status: 'available' | 'missing' | 'unavailable';
|
|
71
|
-
writable?: boolean;
|
|
72
|
-
reason?: string;
|
|
73
|
-
};
|
|
74
|
-
quarantine: {
|
|
75
|
-
status: 'available' | 'missing' | 'unavailable';
|
|
76
|
-
count: number;
|
|
77
|
-
scannedCount: number;
|
|
78
|
-
truncated: boolean;
|
|
79
|
-
entries: Array<{
|
|
80
|
-
nameHash: string;
|
|
81
|
-
sizeBytes: number;
|
|
82
|
-
modifiedAt: string;
|
|
83
|
-
}>;
|
|
84
|
-
reason?: string;
|
|
85
|
-
};
|
|
86
|
-
checks: DiagnosticCheck[];
|
|
87
|
-
}
|
|
10
|
+
export type { DiagnosticStatus, DiagnosticCheck, DiagnosticsSnapshot } from './types';
|
|
88
11
|
export interface CollectDiagnosticsOptions {
|
|
89
12
|
clock?: () => Date;
|
|
90
13
|
adapters?: RuntimeAdapter[];
|