@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.
Files changed (69) hide show
  1. package/README.md +95 -0
  2. package/dist/adapters/claude/process-client.d.ts +24 -0
  3. package/dist/adapters/codex/codex-adapter.d.ts +2 -0
  4. package/dist/adapters/codex/process-runner.d.ts +62 -18
  5. package/dist/adapters/detect-outcome.d.ts +18 -0
  6. package/dist/adapters/index.d.ts +1 -1
  7. package/dist/adapters/index.js +546 -76
  8. package/dist/adapters/index.js.map +1 -1
  9. package/dist/adapters/pi/events.d.ts +1 -1
  10. package/dist/adapters/pi/rpc-client.d.ts +47 -1
  11. package/dist/adapters/pi/subagents-policy-extension.js +1 -1
  12. package/dist/adapters/pi/subagents-policy-extension.js.map +1 -1
  13. package/dist/adapters/pi/team-interaction-extension.d.ts +24 -0
  14. package/dist/adapters/pi/team-interaction-extension.js +81 -0
  15. package/dist/adapters/pi/team-interaction-extension.js.map +1 -0
  16. package/dist/adapters/process-tree.d.ts +74 -4
  17. package/dist/adapters/provider-credential-environment.d.ts +1 -1
  18. package/dist/adapters/win32-job-object.d.ts +101 -0
  19. package/dist/bin/byok-agent-memory-mcp.js +29 -0
  20. package/dist/bin/byok-agent-memory-mcp.js.map +1 -1
  21. package/dist/bin/byok-agent-message-mcp.js +29 -0
  22. package/dist/bin/byok-agent-message-mcp.js.map +1 -1
  23. package/dist/bin/byok-agent-team-mcp.js +29 -0
  24. package/dist/bin/byok-agent-team-mcp.js.map +1 -1
  25. package/dist/bin/byok-agent.js +20274 -18057
  26. package/dist/bin/byok-agent.js.map +1 -1
  27. package/dist/bin/byok-approval-mcp.js +29 -0
  28. package/dist/bin/byok-approval-mcp.js.map +1 -1
  29. package/dist/bin/byok-mcp-env.d.ts +2 -0
  30. package/dist/bin/byok-mcp-env.js +36 -0
  31. package/dist/bin/byok-mcp-env.js.map +1 -0
  32. package/dist/bin/commands/doctor.d.ts +3 -0
  33. package/dist/bin/commands/team-pi-relay.d.ts +24 -0
  34. package/dist/bin/commands/team-relay.d.ts +11 -0
  35. package/dist/bin/mcp-env-launcher.d.ts +5 -0
  36. package/dist/bin/runtime-probe.d.ts +6 -7
  37. package/dist/bin/team-codex-relay.d.ts +32 -0
  38. package/dist/bin/team-notification-relay.d.ts +40 -0
  39. package/dist/bin/team-pi-session.d.ts +59 -0
  40. package/dist/daemon/admission-wait.d.ts +2 -0
  41. package/dist/daemon/agent-egress-policy.d.ts +8 -0
  42. package/dist/daemon/agent-egress-spool.d.ts +1 -0
  43. package/dist/daemon/agent-message-outbox.d.ts +15 -3
  44. package/dist/daemon/artifact-read.d.ts +7 -0
  45. package/dist/daemon/connection-manager.d.ts +23 -176
  46. package/dist/daemon/control-protocol.d.ts +2 -0
  47. package/dist/daemon/create-daemon.d.ts +43 -1
  48. package/dist/daemon/event-spill.d.ts +90 -0
  49. package/dist/daemon/journal/journal.d.ts +15 -3
  50. package/dist/daemon/journal/sqlite-journal.d.ts +7 -2
  51. package/dist/daemon/long-poll-transport.d.ts +2 -50
  52. package/dist/daemon/runtime-start.d.ts +3 -0
  53. package/dist/daemon/store.d.ts +2 -0
  54. package/dist/daemon/task-runner.d.ts +36 -0
  55. package/dist/daemon/team-workspace.d.ts +9 -0
  56. package/dist/daemon/terminal-commit-queue.d.ts +21 -0
  57. package/dist/daemon/terminal-identity.d.ts +6 -0
  58. package/dist/diagnostics/device-doctor.d.ts +46 -0
  59. package/dist/diagnostics/diagnostics.d.ts +3 -80
  60. package/dist/diagnostics/types.d.ts +82 -0
  61. package/dist/index.d.ts +3 -1
  62. package/dist/index.js +2824 -649
  63. package/dist/index.js.map +1 -1
  64. package/dist/runtime-detection.d.ts +3 -0
  65. package/dist/runtime-failure.d.ts +6 -0
  66. package/dist/sdk-reserved-helper-host.d.ts +1 -1
  67. package/dist/types.d.ts +20 -10
  68. package/dist/util/durable-jsonl.d.ts +12 -0
  69. package/package.json +8 -5
@@ -0,0 +1,36 @@
1
+ #!/usr/bin/env node
2
+ import { spawn } from 'child_process';
3
+
4
+ async function runMcpEnvLauncher() {
5
+ const key = process.env.BYOK_MCP_ENV_KEY;
6
+ if (!key || !/^BYOK_MCP_PAYLOAD_[A-F0-9]{32}$/.test(key)) throw new Error("invalid MCP environment binding");
7
+ const encoded = process.env[key];
8
+ if (!encoded) throw new Error("missing sealed MCP environment");
9
+ let config;
10
+ try {
11
+ config = JSON.parse(encoded);
12
+ if (!config || typeof config.command !== "string" || !config.command || config.args !== void 0 && (!Array.isArray(config.args) || config.args.some((value) => typeof value !== "string")) || config.env !== void 0 && (typeof config.env !== "object" || config.env === null || Array.isArray(config.env) || Object.values(config.env).some((value) => typeof value !== "string"))) throw new Error();
13
+ } catch {
14
+ throw new Error("invalid sealed MCP environment");
15
+ }
16
+ const env = { ...process.env };
17
+ delete env.BYOK_MCP_ENV_KEY;
18
+ for (const name of Object.keys(env)) if (name.startsWith("BYOK_MCP_PAYLOAD_")) delete env[name];
19
+ Object.assign(env, config.env);
20
+ await new Promise((resolve, reject) => {
21
+ const child = spawn(config.command, config.args ?? [], { env, stdio: "inherit", windowsHide: true });
22
+ child.once("error", () => reject(new Error("MCP child could not be spawned")));
23
+ child.once("close", (code) => {
24
+ if (code === 0) resolve();
25
+ else reject(new Error("MCP child exited unsuccessfully"));
26
+ });
27
+ });
28
+ }
29
+
30
+ // src/bin/byok-mcp-env.ts
31
+ runMcpEnvLauncher().catch(() => {
32
+ process.stderr.write("byok-mcp-env: MCP launch failed\n");
33
+ process.exitCode = 1;
34
+ });
35
+ //# sourceMappingURL=byok-mcp-env.js.map
36
+ //# sourceMappingURL=byok-mcp-env.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/bin/mcp-env-launcher.ts","../../src/bin/byok-mcp-env.ts"],"names":[],"mappings":";;;AAMA,eAAsB,iBAAA,GAAmC;AACvD,EAAA,MAAM,GAAA,GAAM,QAAQ,GAAA,CAAI,gBAAA;AACxB,EAAA,IAAI,CAAC,GAAA,IAAO,CAAC,iCAAA,CAAkC,IAAA,CAAK,GAAG,CAAA,EAAG,MAAM,IAAI,KAAA,CAAM,iCAAiC,CAAA;AAC3G,EAAA,MAAM,OAAA,GAAU,OAAA,CAAQ,GAAA,CAAI,GAAG,CAAA;AAC/B,EAAA,IAAI,CAAC,OAAA,EAAS,MAAM,IAAI,MAAM,gCAAgC,CAAA;AAC9D,EAAA,IAAI,MAAA;AACJ,EAAA,IAAI;AACF,IAAA,MAAA,GAAS,IAAA,CAAK,MAAM,OAAO,CAAA;AAC3B,IAAA,IAAI,CAAC,MAAA,IAAU,OAAO,MAAA,CAAO,OAAA,KAAY,YAAY,CAAC,MAAA,CAAO,OAAA,IACvD,MAAA,CAAO,IAAA,KAAS,KAAA,CAAA,KAAc,CAAC,KAAA,CAAM,OAAA,CAAQ,MAAA,CAAO,IAAI,CAAA,IAAK,MAAA,CAAO,IAAA,CAAK,IAAA,CAAK,CAAA,KAAA,KAAS,OAAO,KAAA,KAAU,QAAQ,CAAA,CAAA,IAChH,MAAA,CAAO,QAAQ,KAAA,CAAA,KAAc,OAAO,MAAA,CAAO,GAAA,KAAQ,QAAA,IAAY,MAAA,CAAO,QAAQ,IAAA,IAAQ,KAAA,CAAM,OAAA,CAAQ,MAAA,CAAO,GAAG,CAAA,IAC7G,OAAO,MAAA,CAAO,MAAA,CAAO,GAAG,CAAA,CAAE,IAAA,CAAK,CAAA,KAAA,KAAS,OAAO,KAAA,KAAU,QAAQ,CAAA,CAAA,EAAK,MAAM,IAAI,KAAA,EAAM;AAAA,EAC/F,CAAA,CAAA,MAAQ;AACN,IAAA,MAAM,IAAI,MAAM,gCAAgC,CAAA;AAAA,EAClD;AACA,EAAA,MAAM,GAAA,GAAM,EAAE,GAAG,OAAA,CAAQ,GAAA,EAAI;AAC7B,EAAA,OAAO,GAAA,CAAI,gBAAA;AACX,EAAA,KAAA,MAAW,IAAA,IAAQ,MAAA,CAAO,IAAA,CAAK,GAAG,CAAA,EAAG,IAAI,IAAA,CAAK,UAAA,CAAW,mBAAmB,CAAA,EAAG,OAAO,GAAA,CAAI,IAAI,CAAA;AAC9F,EAAA,MAAA,CAAO,MAAA,CAAO,GAAA,EAAK,MAAA,CAAO,GAAG,CAAA;AAC7B,EAAA,MAAM,IAAI,OAAA,CAAc,CAAC,OAAA,EAAS,MAAA,KAAW;AAC3C,IAAA,MAAM,KAAA,GAAQ,KAAA,CAAM,MAAA,CAAO,OAAA,EAAS,OAAO,IAAA,IAAQ,EAAC,EAAG,EAAE,GAAA,EAAK,KAAA,EAAO,SAAA,EAAW,WAAA,EAAa,MAAM,CAAA;AACnG,IAAA,KAAA,CAAM,IAAA,CAAK,SAAS,MAAM,MAAA,CAAO,IAAI,KAAA,CAAM,gCAAgC,CAAC,CAAC,CAAA;AAC7E,IAAA,KAAA,CAAM,IAAA,CAAK,SAAS,CAAA,IAAA,KAAQ;AAC1B,MAAA,IAAI,IAAA,KAAS,GAAG,OAAA,EAAQ;AAAA,WACnB,MAAA,CAAO,IAAI,KAAA,CAAM,iCAAiC,CAAC,CAAA;AAAA,IAC1D,CAAC,CAAA;AAAA,EACH,CAAC,CAAA;AACH;;;AC9BA,iBAAA,EAAkB,CAAE,MAAM,MAAM;AAC9B,EAAA,OAAA,CAAQ,MAAA,CAAO,MAAM,mCAAmC,CAAA;AACxD,EAAA,OAAA,CAAQ,QAAA,GAAW,CAAA;AACrB,CAAC,CAAA","file":"byok-mcp-env.js","sourcesContent":["import { spawn } from 'node:child_process';\n\n/** Project one sealed server's env through Codex's documented env_vars channel.\n * No secret or original server argument is carried in the launcher argv.\n * The child remains in Codex's owned process group / Windows Job Object.\n */\nexport async function runMcpEnvLauncher(): Promise<void> {\n const key = process.env.BYOK_MCP_ENV_KEY;\n if (!key || !/^BYOK_MCP_PAYLOAD_[A-F0-9]{32}$/.test(key)) throw new Error('invalid MCP environment binding');\n const encoded = process.env[key];\n if (!encoded) throw new Error('missing sealed MCP environment');\n let config: { command: string; args?: string[]; env?: Record<string, string> };\n try {\n config = JSON.parse(encoded);\n if (!config || typeof config.command !== 'string' || !config.command\n || (config.args !== undefined && (!Array.isArray(config.args) || config.args.some(value => typeof value !== 'string')))\n || (config.env !== undefined && (typeof config.env !== 'object' || config.env === null || Array.isArray(config.env)\n || Object.values(config.env).some(value => typeof value !== 'string')))) throw new Error();\n } catch {\n throw new Error('invalid sealed MCP environment');\n }\n const env = { ...process.env };\n delete env.BYOK_MCP_ENV_KEY;\n for (const name of Object.keys(env)) if (name.startsWith('BYOK_MCP_PAYLOAD_')) delete env[name];\n Object.assign(env, config.env);\n await new Promise<void>((resolve, reject) => {\n const child = spawn(config.command, config.args ?? [], { env, stdio: 'inherit', windowsHide: true });\n child.once('error', () => reject(new Error('MCP child could not be spawned')));\n child.once('close', code => {\n if (code === 0) resolve();\n else reject(new Error('MCP child exited unsuccessfully'));\n });\n });\n}\n","#!/usr/bin/env node\nimport { runMcpEnvLauncher } from './mcp-env-launcher';\n\nrunMcpEnvLauncher().catch(() => {\n process.stderr.write('byok-mcp-env: MCP launch failed\\n');\n process.exitCode = 1;\n});\n"]}
@@ -3,6 +3,9 @@ import { type CollectDiagnosticsOptions } from '../../diagnostics/diagnostics';
3
3
  export interface DoctorOptions extends CollectDiagnosticsOptions {
4
4
  json?: boolean;
5
5
  fix?: boolean;
6
+ repair?: string;
7
+ expectedDeviceId?: string;
8
+ expectedTenantId?: string;
6
9
  confirmed?: boolean;
7
10
  log?: (line: string) => void;
8
11
  }
@@ -0,0 +1,24 @@
1
+ import type { DaemonConfig } from '../../daemon/create-daemon';
2
+ import { type CodexTeamBinding } from '../team-codex-relay';
3
+ export declare function parsePiRelayBindings(value: unknown, workspaceId: string): {
4
+ codex: CodexTeamBinding;
5
+ pi: {
6
+ context: string;
7
+ lease: import("../..").TeamMemberLease;
8
+ afterSeq: number;
9
+ cwd: string;
10
+ sessionDir: string;
11
+ provider: string;
12
+ model: string;
13
+ systemPrompt: string;
14
+ extensionPaths: string[];
15
+ };
16
+ };
17
+ export declare function runTeamPiRelayCommand(input: {
18
+ config: DaemonConfig;
19
+ workspaceId: string;
20
+ bindingsFile: string;
21
+ codexBin: string;
22
+ maxNotifications: number;
23
+ signal: AbortSignal;
24
+ }): Promise<void>;
@@ -0,0 +1,11 @@
1
+ import type { DaemonConfig } from '../../daemon/create-daemon';
2
+ /** One foreground owner per room. Stale locks are never guessed away or stolen. */
3
+ export declare function acquireTeamRelayLock(storeDir: string, workspaceId: string): Promise<() => Promise<void>>;
4
+ export declare function runTeamRelayCommand(input: {
5
+ config: DaemonConfig;
6
+ workspaceId: string;
7
+ bindingsFile: string;
8
+ codexBin: string;
9
+ maxNotifications: number;
10
+ signal: AbortSignal;
11
+ }): Promise<void>;
@@ -0,0 +1,5 @@
1
+ /** Project one sealed server's env through Codex's documented env_vars channel.
2
+ * No secret or original server argument is carried in the launcher argv.
3
+ * The child remains in Codex's owned process group / Windows Job Object.
4
+ */
5
+ export declare function runMcpEnvLauncher(): Promise<void>;
@@ -1,3 +1,4 @@
1
+ import type { RuntimeDetectResult } from '../types';
1
2
  import { type RuntimeAdapter } from '../index';
2
3
  export declare const RUNTIME_PROBE_TIMEOUT_MS = 5000;
3
4
  /** The bundled adapter set `byok-agent status`/`byok-agent runtimes` probe by default — same unset-vs-set allowlist contract as `createDaemon` itself. */
@@ -13,7 +14,9 @@ export declare function defaultRuntimeAdapters(runtimeAllowlist: string[] | unde
13
14
  */
14
15
  export interface ProbedRuntime {
15
16
  id: string;
17
+ /** Deterministic projection of outcome, never adapter-authored. */
16
18
  present: boolean;
19
+ outcome: RuntimeDetectResult['kind'];
17
20
  version?: string;
18
21
  authPresent?: boolean;
19
22
  steer: boolean;
@@ -21,13 +24,9 @@ export interface ProbedRuntime {
21
24
  permissionModes: string[];
22
25
  }
23
26
  /**
24
- * Runs `detect()` and reads each adapter's frozen descriptor, in parallel. Every
25
- * bundled adapter's own `detect()` already catches its own failures (e.g.
26
- * `pi-adapter.ts`'s `detect()` wraps its version probe in try/catch and
27
- * resolves `{present: false}` rather than rejecting) — the catch here is a
28
- * defensive backstop for a `RuntimeAdapter` that doesn't hold that
29
- * convention, not a workaround for an observed failure in the bundled
30
- * three.
27
+ * Fresh, parallel local observations. A custom adapter timeout is an observation
28
+ * deadline only: detect() has no cancellation contract. Never expose arbitrary
29
+ * adapter errors or interpret an old/malformed result as available.
31
30
  */
32
31
  export declare function probeRuntimes(adapters: readonly RuntimeAdapter[], options?: {
33
32
  timeoutMs?: number;
@@ -0,0 +1,32 @@
1
+ import { type TeamMemberLease } from '../daemon/team-workspace';
2
+ export interface CodexTeamBinding {
3
+ readonly context: string;
4
+ readonly lease: TeamMemberLease;
5
+ readonly threadId: string;
6
+ readonly endpoint: string;
7
+ readonly afterSeq: number;
8
+ }
9
+ export interface TeamNotificationSnapshot {
10
+ workspaceId: string;
11
+ memberId: string;
12
+ registryRevision: string;
13
+ expiresAt: string;
14
+ acknowledgedThroughSeq: number;
15
+ latestPeerSeq: number | null;
16
+ }
17
+ /** Local endpoints only. Never select a default daemon or infer a thread from its name. */
18
+ export declare function validateCodexRelayEndpoint(value: unknown): asserts value is string;
19
+ export declare function parseCodexTeamBinding(binding: unknown, workspaceId: string): CodexTeamBinding;
20
+ export declare function parseCodexTeamBindings(value: unknown, workspaceId: string): readonly CodexTeamBinding[];
21
+ export declare function loadPrivateTeamDocument(file: string): Promise<unknown>;
22
+ export declare function loadCodexTeamBindings(file: string, workspaceId: string): Promise<readonly CodexTeamBinding[]>;
23
+ export declare function codexTeamNotification(workspaceId: string, throughSeq: number): string;
24
+ /** The native queue receipt contract is qualified against this CLI version. */
25
+ export declare function preflightCodexRelay(codexBin: string, signal: AbortSignal): Promise<string>;
26
+ /** Only a confirmed exact-thread queue receipt advances this epoch's notification watermark. */
27
+ export declare function queueCodexTeamNotification(input: {
28
+ codexBin: string;
29
+ binding: CodexTeamBinding;
30
+ throughSeq: number;
31
+ signal: AbortSignal;
32
+ }): Promise<string>;
@@ -0,0 +1,40 @@
1
+ import type { TeamMemberLease } from '../daemon/team-workspace';
2
+ export interface TeamRelayBinding {
3
+ readonly context: string;
4
+ readonly lease: TeamMemberLease;
5
+ readonly afterSeq: number;
6
+ }
7
+ export type TeamRelayState = 'running' | 'paused' | 'stopped' | 'budget_exhausted' | 'failed';
8
+ export declare class TeamNotificationRelay<T extends TeamRelayBinding> {
9
+ private readonly options;
10
+ private state;
11
+ private attempts;
12
+ private error;
13
+ private readonly watermarks;
14
+ private pending;
15
+ private readonly abort;
16
+ constructor(options: {
17
+ bindings: readonly T[];
18
+ maxNotifications: number;
19
+ snapshot: (binding: T, afterSeq: number) => Promise<unknown>;
20
+ describe: (binding: T) => Record<string, string>;
21
+ ready?: (binding: T) => Promise<boolean>;
22
+ enqueue: (binding: T, throughSeq: number, signal: AbortSignal) => Promise<string>;
23
+ });
24
+ status(): {
25
+ state: TeamRelayState;
26
+ attempts: number;
27
+ maxNotifications: number;
28
+ error?: "queue_delivery_unknown" | "snapshot_failed" | undefined;
29
+ bindings: {
30
+ workspaceId: string;
31
+ memberId: string;
32
+ notifiedThroughSeq: number | undefined;
33
+ }[];
34
+ };
35
+ pause(): void;
36
+ resume(): void;
37
+ stop(): void;
38
+ tick(): Promise<void>;
39
+ private performTick;
40
+ }
@@ -0,0 +1,59 @@
1
+ export interface PiInteractionResponse {
2
+ sessionId: string;
3
+ requestId: string;
4
+ response: {
5
+ cancelled: true;
6
+ } | {
7
+ confirmed: boolean;
8
+ } | {
9
+ value: string;
10
+ };
11
+ }
12
+ export interface PiTeamSessionOptions {
13
+ workspaceId: string;
14
+ cwd: string;
15
+ sessionDir: string;
16
+ provider: string;
17
+ model: string;
18
+ systemPrompt: string;
19
+ mcpConfig: Record<string, unknown>;
20
+ extensionPaths?: readonly string[];
21
+ onEvent: (event: Record<string, unknown>) => void;
22
+ }
23
+ /** One owned RPC child; GUI replies never share a model-controlled tool channel. */
24
+ export declare class PiTeamSession {
25
+ private readonly options;
26
+ private client;
27
+ private sessionId;
28
+ private revision;
29
+ private phase;
30
+ private readonly interactions;
31
+ private readonly replying;
32
+ private stopping;
33
+ private active;
34
+ private pendingInputs;
35
+ private readonly privateFiles;
36
+ private constructor();
37
+ static start(options: PiTeamSessionOptions): Promise<PiTeamSession>;
38
+ status(): {
39
+ sessionId: string | undefined;
40
+ phase: "closed" | "failed" | "open" | "starting" | "waiting";
41
+ revision: number;
42
+ pendingUi: {
43
+ id: string | undefined;
44
+ method: unknown;
45
+ responding: boolean;
46
+ }[];
47
+ };
48
+ private fail;
49
+ private onFrame;
50
+ private onInteraction;
51
+ private request;
52
+ private state;
53
+ ready(): Promise<boolean>;
54
+ notify(throughSeq: number, signal: AbortSignal): Promise<string>;
55
+ sendInput(message: string): Promise<string>;
56
+ respond(input: PiInteractionResponse): Promise<void>;
57
+ drain(signal: AbortSignal): Promise<void>;
58
+ stop(): Promise<void>;
59
+ }
@@ -0,0 +1,2 @@
1
+ /** Only for side-effect-free admission. Never abandon an owned lease/session. */
2
+ export declare function awaitAdmission<T>(operation: () => Promise<T>, signal: AbortSignal): Promise<T>;
@@ -31,6 +31,14 @@ export declare function resolveAgentEgressPolicy(policy: AgentEgressPolicy | und
31
31
  * Default activity projection. Every retained string is SDK-authored; no
32
32
  * runtime trajectory, tool, prompt, environment, argv, path, or credential
33
33
  * value survives this transformation.
34
+ *
35
+ * Each case CONSTRUCTS a fresh event from SDK-authored literals rather than
36
+ * editing the incoming one, which is what makes the guarantee total rather
37
+ * than a list of fields someone remembered to strip. `spill` on
38
+ * `tool_use`/`tool_result` is covered by exactly that: a `BlobRef` is a
39
+ * readable locator for the omitted tool payload — content, not metadata — so
40
+ * it never survives a metadata-status projection, and neither do the byte
41
+ * counts that would leak the payload's size.
34
42
  */
35
43
  export declare function metadataStatusEvent(event: AgentEvent): AgentEvent;
36
44
  export declare function eventBytes(event: AgentEvent): number;
@@ -70,6 +70,7 @@ export declare class AgentReliableSpool {
70
70
  readonly homeDir: string;
71
71
  readonly spoolPath: string;
72
72
  private readonly pending;
73
+ private readonly file;
73
74
  private nextCursor;
74
75
  private logEntries;
75
76
  private writeTail;
@@ -20,12 +20,14 @@ export interface AgentMessageOutboxRecord {
20
20
  export declare class AgentMessageOutboxError extends Error {
21
21
  constructor(message: string);
22
22
  }
23
- /** Agent-local, append-before-send outbox. Only exact accepted disposition retires bytes. */
23
+ /** Agent-local append-before-send authority. Only acceptance or explicit terminal archival retires bytes. */
24
24
  export declare class AgentMessageOutbox {
25
25
  readonly homeDir: string;
26
26
  readonly outboxPath: string;
27
27
  private readonly pendingByTask;
28
+ private readonly revokedTasks;
28
29
  private readonly dispositionByTask;
30
+ private readonly file;
29
31
  private nextCursor;
30
32
  private logEntries;
31
33
  private writeTail;
@@ -34,7 +36,7 @@ export declare class AgentMessageOutbox {
34
36
  /** Re-open every existing Agent-local message outbox without following Agent-home symlinks. */
35
37
  static recover(agentsRoot: string, tenantId: string): Promise<readonly AgentMessageOutbox[]>;
36
38
  records(): readonly AgentMessageOutboxRecord[];
37
- /** Activated records with no exact disposition yet; only these may be transport-replayed. */
39
+ /** Records without disposition or local revoke; transport replay additionally requires session binding. */
38
40
  retryableRecords(): readonly AgentMessageOutboxRecord[];
39
41
  get(taskId: string): AgentMessageOutboxRecord | undefined;
40
42
  appendDraft(input: {
@@ -48,11 +50,21 @@ export declare class AgentMessageOutbox {
48
50
  readonly maxPendingEvents: number;
49
51
  readonly maxPendingBytes: number;
50
52
  }): Promise<AgentMessageOutboxRecord>;
51
- activate(taskId: string, sessionRef: string): Promise<AgentMessageOutboxRecord | undefined>;
53
+ activate(taskId: string, sessionRef: string, isCurrent?: () => boolean): Promise<AgentMessageOutboxRecord | undefined>;
54
+ /** Local lifecycle cancellation, never a synthesized consumer disposition. */
55
+ revoke(taskId: string): Promise<void>;
56
+ private isTerminalEvidence;
52
57
  publishPayload(record: AgentMessageOutboxRecord): AgentMessagePublishPayload;
53
58
  applyDisposition(taskId: string, input: unknown): Promise<'accepted' | 'held' | 'refused' | 'mismatch' | 'unknown'>;
54
59
  private load;
55
60
  private appendEntry;
61
+ /**
62
+ * Explicit operator maintenance under the Agent-home single-writer lease.
63
+ * Copy complete terminal evidence durably before removing it from the live log.
64
+ * Archives are audit artifacts, never another replay input. held remains live.
65
+ */
66
+ archiveTerminalRecords(archiveDirectory: string): Promise<string | undefined>;
67
+ private snapshotEntries;
56
68
  private compact;
57
69
  private exclusive;
58
70
  }
@@ -0,0 +1,7 @@
1
+ import type { FileHandle } from 'node:fs/promises';
2
+ export declare const DEFAULT_ARTIFACT_LIMITS: Readonly<{
3
+ maxFileBytes: number;
4
+ maxTaskBytes: number;
5
+ }>;
6
+ /** The caller retains fd ownership; no pathname is reopened. */
7
+ export declare function readArtifactBytes(handle: FileHandle, maxBytes: number, signal: AbortSignal, account: (bytes: number) => void): Promise<Buffer>;
@@ -1,3 +1,4 @@
1
+ import type { HarnessInfo } from '@byok-sdk/protocol';
1
2
  import { type CapabilityFlag, type Envelope, type RuntimeInfo, type ToolsetId } from '@byok-sdk/protocol';
2
3
  import { AuthManager } from './auth-manager';
3
4
  import type { CursorStore } from './cursor-store';
@@ -14,6 +15,7 @@ export interface ConnectionManagerOptions {
14
15
  /** U4a Local Agent release version, sent unchanged in `conn.hello`. */
15
16
  clientVersion?: string;
16
17
  runtimes: RuntimeInfo[];
18
+ harnesses?: HarnessInfo[];
17
19
  /** Reads current sorted logical IDs from the validated local registry for every `conn.hello`. */
18
20
  getConfiguredToolsets?: () => readonly ToolsetId[];
19
21
  auth: AuthManager;
@@ -25,6 +27,11 @@ export interface ConnectionManagerOptions {
25
27
  */
26
28
  onEnvelope: (envelope: Envelope) => void | Promise<void>;
27
29
  onStateChange?: (state: ConnectionState) => void;
30
+ /** Await durable disposition before retiring the exact accepted/rejected bytes. */
31
+ onOutboundAccepted?: (envelopes: readonly Envelope[]) => Promise<void>;
32
+ onOutboundRejected?: (envelope: Envelope) => Promise<void>;
33
+ onOutboundQueued?: (envelope: Envelope) => void;
34
+ beforeOutboundPost?: (envelopes: readonly Envelope[]) => void;
28
35
  /** Backoff between failed long-poll HTTP attempts. Default 2s. */
29
36
  longPollRetryDelayMs?: number;
30
37
  /** Minimum delay before the next long-poll request after an empty (no-events) response. Default 250ms. */
@@ -68,25 +75,11 @@ export declare class ConnectionManager {
68
75
  * guarantees in docs/protocol.md §9.
69
76
  */
70
77
  private stalledAtSeq;
71
- /**
72
- * Design A (Wave 2, F3-on-long-poll): the second, in-memory watermark
73
- * alongside the durable `cursor`. `cursor` only ever advances AFTER a
74
- * `task.*` handler's side effects resolve successfully, and is persisted
75
- * (see `advanceCursor`) — that semantics is unchanged. `deliveredSeq`
76
- * advances eagerly, the instant a `task.*` envelope is admitted past
77
- * dedup (see `deliver`/`noteDelivered`), independent of whether its
78
- * handler has even started, let alone succeeded. It exists so a repeated
79
- * read at the durable cursor does not re-dispatch an envelope already in
80
- * flight — `handleOffer` must not start a second adapter session while a
81
- * first attempt is still running. On WS this same field is written the
82
- * same way, but since a live WS connection only ever pushes a given `seq`
83
- * once, it never has an observable effect there beyond mirroring
84
- * `cursor` (see `dedupWatermark`'s doc comment for why redelivery
85
- * correctness doesn't depend on resetting it anywhere).
86
- */
87
- private deliveredSeq;
88
- /** Finding F3: serializes `onEnvelope` calls into a per-connection FIFO — one envelope's handler always fully settles before the next one starts. */
78
+ /** Drain barrier for independently running handlers; not an admission lock. */
89
79
  private processingChain;
80
+ private cursorInitialization;
81
+ private completionTail;
82
+ private readonly controlTails;
90
83
  /**
91
84
  * Design B (finding N4): the ONE outbound queue holds `Envelope` OBJECTS,
92
85
  * never re-encoded/rebuilt strings, so a
@@ -112,38 +105,13 @@ export declare class ConnectionManager {
112
105
  private terminalError;
113
106
  private settledWaiters;
114
107
  private pendingCursorSave;
115
- /**
116
- * Finding P2 (Fix 2b): seqs currently admitted into `processingChain` but
117
- * not yet settled — added in `deliver()` the moment a `task.*` envelope is
118
- * accepted past the ordinary watermark check, removed in `process()`'s
119
- * `finally` once that specific attempt resolves (success OR failure).
120
- * While stalled, `dedupWatermark()` deliberately stays frozen below
121
- * already-delivered seqs (see its own doc comment) so the failed seq's own
122
- * redelivery can get through — but that same frozen watermark also means
123
- * every OTHER seq above it rides along on every re-poll too. Without this,
124
- * a seq already mid-flight (e.g. a `task.offer` whose prepared operation start()
125
- * hasn't resolved yet) would be re-enqueued into `processingChain` on
126
- * every such re-poll, piling up duplicate copies that — once the first
127
- * finally resolves and the chain unwinds through them — run its handler
128
- * again; for `task.offer` specifically, a second adapter session
129
- * orphaning the first (`TaskRunner`'s own `this.tasks.has` guard, finding
130
- * P2c, is the second, independent layer against exactly that).
131
- */
108
+ /** Admission dedup while an individual handler is running. */
132
109
  private readonly inFlightSeqs;
133
- /**
134
- * Finding P2 (Fix 2b): seqs whose handler has already resolved
135
- * successfully at least once this session, tracked only while a stall is
136
- * in effect — cleared the moment `stalledAtSeq` itself clears (see
137
- * `process()`), since once unstalled the ordinary watermark check via
138
- * `deliveredSeq` already covers everything delivered so far, making this
139
- * redundant. Needed because the stall-gap-prevention rule in `process()`
140
- * deliberately does NOT advance `cursor` past a seq above the
141
- * still-unresolved `stalledAtSeq`, even once that seq's own handler
142
- * succeeds — so `dedupWatermark()` alone can't distinguish "already
143
- * succeeded, don't re-run" from "never yet attempted" for anything in
144
- * that gap.
145
- */
110
+ /** Successful side effects retained until their durable acknowledgement. */
146
111
  private readonly processedSeqs;
112
+ private readonly failedEnvelopes;
113
+ /** Received work lacking a successful handler receipt, including malformed frames. */
114
+ private readonly unresolvedSeqs;
147
115
  /**
148
116
  * Finding P3: the pending `drainOutbox` long-poll retry backoff, if any —
149
117
  * cancellable so `enterRevoked()` can unblock it immediately instead of
@@ -298,142 +266,21 @@ export declare class ConnectionManager {
298
266
  * - F3 (at-most-once): the old code persisted the cursor advance BEFORE
299
267
  * `onEnvelope` even ran (fire-and-forget) — a handler that then failed
300
268
  * left a redelivery-proof envelope permanently marked processed. Inbound
301
- * envelopes are now serialized through `processingChain` (one handler
302
- * fully settles before the next starts) and the cursor only advances
269
+ * handlers may run independently; their receipt commits are serialized.
270
+ * The cursor only advances
303
271
  * AFTER the handler resolves successfully; a rejection leaves the
304
272
  * cursor where it was (see `stalledAtSeq`), so a future reconnect's
305
273
  * redelivery re-attempts it — safe because every server->daemon type is
306
274
  * documented idempotent (protocol §9).
307
275
  */
308
276
  private deliver;
309
- /**
310
- * The local watermark `deliver()` dedupes inbound `task.*` envelopes
311
- * against. It is deliberately NOT the long-poll query cursor: that query
312
- * is the kernel acknowledgement and uses only the successfully processed
313
- * `cursor` (see the constructor). Normally this local watermark is
314
- * `deliveredSeq` — which is always >= `cursor` (every envelope that
315
- * reaches `advanceCursor` already passed through `noteDelivered` first,
316
- * see `deliver`) — so this is the literal `max(cursor, deliveredSeq)` the
317
- * design calls for, just expressed via that invariant rather than an
318
- * explicit `Math.max`.
319
- *
320
- * While `stalledAtSeq` is set, this collapses to the durable `cursor`
321
- * alone, deliberately ignoring however far `deliveredSeq` had already run
322
- * ahead before the failure was known: that's what lets the stalled
323
- * envelope's own redelivery (and everything after it, right up to a
324
- * fresh success) get past this same dedup check instead of being
325
- * self-deduped by the client's own earlier eager tracking of envelopes
326
- * whose outcome wasn't known yet. No separate "reset deliveredSeq on
327
- * reconnect" step is needed for this to be correct — collapsing to
328
- * `cursor` exactly while stalled already produces the right answer on
329
- * every long-poll retry path. NOT resetting it unconditionally on every
330
- * retry lets `deliveredSeq` keep doing its job of not re-dispatching
331
- * something already in flight while a handler is still running.
332
- */
277
+ /** A committed terminal can settle a failed offer receipt even if cloud
278
+ * cancellation has since filtered that offer from mailbox replay. */
279
+ retryTaskReceipts(taskId: string): void;
280
+ /** Only a durable acknowledgement can deduplicate a whole prefix. */
333
281
  private dedupWatermark;
334
- /** Design A: eagerly advance the in-memory delivery watermark — called for every `task.*` envelope `deliver()` admits past dedup, regardless of transport or of whether its handler has even started yet. */
335
- private noteDelivered;
336
282
  private process;
337
- /**
338
- * M4 Phase 4 (version-negotiation drill fix): `LongPollClient` calls this
339
- * for a batch entry it could not parse into a known `Envelope` at all (an
340
- * unrecognized message type (see `long-poll-transport.ts`'s own doc
341
- * comment on `parseLooseEventsPollResponse`) but which still carried a numeric,
342
- * task-class envelope-level `seq` (the caller only invokes this for a
343
- * `task.`-prefixed type — see `long-poll-transport.ts`'s own
344
- * `extractSkippableSeq`; `conn.*`-shaped or type-less entries never reach
345
- * here at all, mirroring F2's "conn.* is never cursor-tracked" rule).
346
- * There is no real `Envelope` to hand to a handler — a genuinely
347
- * unrecognized type has nothing this build could ever act on.
348
- *
349
- * GATEKEEPER-CAUGHT REGRESSION (fixed here): this used to call
350
- * `advanceCursor(seq)` DIRECTLY, synchronously, the instant a skip was
351
- * detected in `LongPollClient.loop()`'s per-entry for-loop. That is NOT
352
- * "instantaneous and race-free" the way the previous version of this
353
- * comment claimed — the hazard was never the skip racing against itself,
354
- * it was the skip racing AHEAD of an EARLIER real envelope in the SAME
355
- * batch that is still in flight on `processingChain` (`deliver()`, above,
356
- * only ever CHAINS `process()` onto that promise chain — it never awaits
357
- * it before returning). Concretely, batch `[real seq1, unknown seq2]`:
358
- * `deliver(seq1)` chains `process(seq1)` but returns immediately without
359
- * running it; the for-loop then reaches `seq2` and (pre-fix) called
360
- * `advanceCursor(2)` synchronously, BEFORE `process(seq1)` had even
361
- * started, let alone failed. If `seq1`'s handler then failed,
362
- * `stalledAtSeq` became 1 — but the durable cursor was already 2, so
363
- * `dedupWatermark()` returned 2, and every future redelivery of seq1 was
364
- * dedup-dropped as "already past the cursor" forever: permanent envelope
365
- * loss, exactly the F3 bug class the whole `stalledAtSeq`/frozen-watermark
366
- * mechanism exists to prevent.
367
- *
368
- * Fix: the cursor-advancing half is now CHAINED onto `processingChain`
369
- * too, exactly like `process()`'s own post-handler bookkeeping — so it
370
- * only ever runs once every earlier envelope already queued ahead of it
371
- * has fully settled (success or failure), and can observe `stalledAtSeq`'s
372
- * REAL, up-to-date value rather than whatever it happened to be at the
373
- * instant the skip was first noticed. The guard mirrors `process()`'s own
374
- * success-path guard exactly: never advance past a still-unresolved
375
- * earlier failure, unless (degenerate, cannot really happen for a skip)
376
- * this exact seq IS the stalled one.
377
- *
378
- * `noteDelivered` (the eager, in-memory watermark) stays UNCHAINED —
379
- * called immediately, unconditionally, regardless of `stalledAtSeq` —
380
- * matching `deliver()`'s own eager, unconditional call for a real
381
- * envelope: its only job is "don't re-dispatch something already handed off,"
382
- * independent of outcome, and that property does not depend on FIFO
383
- * ordering the way the DURABLE cursor does.
384
- *
385
- * Deliberately NO top-level `dedupWatermark() <= seq` early-return before
386
- * queuing the chained callback (an earlier draft of this fix had one, and
387
- * it was itself subtly wrong): `deliveredSeq` can already reflect a seq
388
- * from the FIRST time it was ever seen, while the DURABLE cursor is still
389
- * behind it because a stall intervened before that seq's chained
390
- * advancement ran — a pre-check keyed on `deliveredSeq` would then
391
- * wrongly treat a LATER redelivery of the same seq (arriving once the
392
- * stall has since cleared) as "already accounted for" and never queue
393
- * another attempt, permanently stranding the cursor one seq short. Always
394
- * queuing is safe and cheap: `advanceCursor`'s own `seq <= this.cursor`
395
- * guard already makes a genuinely-redundant call a no-op, so there is no
396
- * correctness reason to short-circuit earlier, only a (here, unnecessary)
397
- * micro-optimization one.
398
- */
399
- private noteSkippedSeq;
400
- /**
401
- * Finding R1 (cross-model re-review — was NOT-CLOSED against F1):
402
- * `LongPollClient` calls this for a batch entry whose `type` it
403
- * recognized but whose payload failed schema validation
404
- * ({@link EnvelopeValidationError}) — a genuine delivery failure at that
405
- * seq, unlike `noteSkippedSeq`'s forward-compat case. Deliberately mirrors
406
- * `process()`'s own catch block (`if (tracked && this.stalledAtSeq ===
407
- * undefined) this.stalledAtSeq = envelope.seq;`) as closely as possible:
408
- * the SAME "only the lowest unresolved failure holds the stall" rule, the
409
- * SAME resulting freeze of `dedupWatermark()` at the durable cursor
410
- * (protocol §9 keeps this seq alive), and — because it's the SAME
411
- * `stalledAtSeq` field `process()`'s own post-success guard already
412
- * checks — anything ELSE delivered after this seq (same batch or a later
413
- * one) is automatically held back from advancing the cursor too, with
414
- * zero changes needed to `process()` itself.
415
- *
416
- * Chained onto `processingChain` for exactly the reason `noteSkippedSeq`
417
- * documents for its own identical chaining (see that method's sibling
418
- * doc comment on `LongPollClient`, "GATEKEEPER-CAUGHT REGRESSION"): an
419
- * EARLIER real envelope in the SAME batch may still be in flight on that
420
- * FIFO chain when this is called (`deliver()` only ever chains
421
- * `process()` onto it, never awaits before returning) — mutating
422
- * `stalledAtSeq` synchronously here could race ahead of that still-
423
- * unresolved earlier envelope. Chaining instead guarantees this only
424
- * takes effect once every earlier-queued envelope has already settled,
425
- * and reads `stalledAtSeq`'s real, up-to-date value rather than whatever
426
- * it happened to be the instant the failure was first noticed.
427
- *
428
- * No `noteDelivered` call here (contrast `noteSkippedSeq`, which does
429
- * call it): a validation-failed entry never becomes a real `Envelope` and
430
- * never reaches `deliver()`, so it was never "delivered" in the eager
431
- * in-memory-watermark sense that field tracks — there is nothing for it
432
- * to eagerly mark. Once a corrected redelivery of this exact seq DOES
433
- * arrive as a real envelope, it flows through the ordinary `deliver()`
434
- * path (which calls `noteDelivered` itself) and, on success, clears the
435
- * stall via `process()`'s own existing logic — no special-casing needed.
436
- */
283
+ /** Recorded synchronously at receive, before a later successful receipt can commit. */
437
284
  private noteValidationFailure;
438
285
  private advanceCursor;
439
286
  private quarantineRejectedOutbound;
@@ -465,5 +465,7 @@ export declare function parseTeamWorkspaceJoinParams(value: unknown): TeamWorksp
465
465
  export declare function parseTeamContextParams(value: unknown): TeamContextParams | undefined;
466
466
  export declare function parseTeamMessagePostParams(value: unknown): TeamMessagePostParams | undefined;
467
467
  export declare function parseTeamMessageReadParams(value: unknown): TeamMessageReadParams | undefined;
468
+ /** Exact local operator RPC; the shared read-parameter shape has no model identity fields. */
469
+ export declare function parseTeamNotificationSnapshotParams(value: unknown): TeamMessageReadParams | undefined;
468
470
  export declare function parseTeamMessageAckParams(value: unknown): TeamMessageAckParams | undefined;
469
471
  export declare function parseTeamMessageInspectParams(value: unknown): TeamMessageInspectParams | undefined;