@byok-sdk/client 0.6.0 → 0.7.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 +94 -1
- package/dist/adapters/index.js +183 -34
- package/dist/adapters/index.js.map +1 -1
- package/dist/adapters/pi/events.d.ts +1 -1
- package/dist/adapters/pi/mcp-config.d.ts +1 -0
- package/dist/adapters/pi/mcp-extension.js +25 -0
- package/dist/adapters/pi/mcp-extension.js.map +1 -0
- package/dist/adapters/pi/permission-mapping.d.ts +1 -1
- package/dist/adapters/pi/pi-adapter.d.ts +5 -0
- package/dist/adapters/pi/resolve-extensions.d.ts +11 -0
- package/dist/agent-home.d.ts +106 -0
- package/dist/bin/byok-agent.js +12926 -9436
- package/dist/bin/byok-agent.js.map +1 -1
- package/dist/bin/byok-approval-mcp.js.map +1 -1
- package/dist/bin/commands/toolsets.d.ts +8 -0
- package/dist/bin/format.d.ts +3 -0
- package/dist/daemon/agent-content-audit-store.d.ts +35 -0
- package/dist/daemon/agent-content-read.d.ts +169 -0
- package/dist/daemon/agent-egress-controller.d.ts +79 -0
- package/dist/daemon/agent-egress-policy.d.ts +36 -0
- package/dist/daemon/agent-egress-sanitizer.d.ts +38 -0
- package/dist/daemon/agent-egress-spool.d.ts +116 -0
- package/dist/daemon/agent-session-handoff-store.d.ts +82 -0
- package/dist/daemon/blob-client.d.ts +6 -2
- package/dist/daemon/connection-manager.d.ts +2 -2
- package/dist/daemon/control-protocol.d.ts +9 -0
- package/dist/daemon/create-daemon.d.ts +76 -10
- package/dist/daemon/long-poll-transport.d.ts +60 -0
- package/dist/daemon/presence-publisher.d.ts +2 -2
- package/dist/daemon/store.d.ts +9 -0
- package/dist/daemon/task-runner.d.ts +53 -4
- package/dist/daemon/toolset-registry.d.ts +30 -0
- package/dist/daemon/url.d.ts +21 -0
- package/dist/daemon/ws-transport.d.ts +30 -5
- package/dist/index.d.ts +12 -2
- package/dist/index.js +8550 -5105
- package/dist/index.js.map +1 -1
- package/dist/types.d.ts +44 -0
- package/package.json +7 -5
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import type { AgentEvent } from '@byok-sdk/protocol';
|
|
2
|
+
import type { AgentRef } from '../agent-home';
|
|
3
|
+
import { type AgentEgressDropReason, type AgentEgressDropReceipt, type AgentEgressPolicy, type AgentEgressStatus } from './agent-egress-policy';
|
|
4
|
+
import { type AgentReliableAck, type AgentContentReceiptWithoutReliableIdentity, type AgentReliableEgressRecord } from './agent-egress-spool';
|
|
5
|
+
import { type AgentEgressSanitizer } from './agent-egress-sanitizer';
|
|
6
|
+
export interface AgentEgressControllerOptions {
|
|
7
|
+
readonly policy: Readonly<AgentEgressPolicy>;
|
|
8
|
+
/** Authenticated tenant identity, never accepted from an egress event. */
|
|
9
|
+
readonly tenantId?: string;
|
|
10
|
+
readonly sanitizer?: AgentEgressSanitizer;
|
|
11
|
+
}
|
|
12
|
+
export interface AgentEgressProgressInput {
|
|
13
|
+
readonly agentRef?: AgentRef;
|
|
14
|
+
readonly taskId: string;
|
|
15
|
+
readonly events: readonly AgentEvent[];
|
|
16
|
+
readonly serverCapabilities: readonly string[];
|
|
17
|
+
}
|
|
18
|
+
export interface AgentEgressReliableInput {
|
|
19
|
+
readonly homeDir: string;
|
|
20
|
+
readonly agentRef: AgentRef;
|
|
21
|
+
readonly payload: unknown;
|
|
22
|
+
readonly sessionRef: string;
|
|
23
|
+
readonly taskId?: string;
|
|
24
|
+
readonly eventId?: string;
|
|
25
|
+
}
|
|
26
|
+
export interface AgentEgressContentReceiptInput {
|
|
27
|
+
readonly homeDir: string;
|
|
28
|
+
readonly agentRef: AgentRef;
|
|
29
|
+
/** The content-free payload before the spool supplies stable event/cursor identity. */
|
|
30
|
+
readonly payload: AgentContentReceiptWithoutReliableIdentity;
|
|
31
|
+
readonly taskId?: string;
|
|
32
|
+
}
|
|
33
|
+
export type AgentEgressReliableAppendResult = Readonly<{
|
|
34
|
+
ok: true;
|
|
35
|
+
record: AgentReliableEgressRecord;
|
|
36
|
+
}> | Readonly<{
|
|
37
|
+
ok: false;
|
|
38
|
+
reason: AgentEgressDropReason;
|
|
39
|
+
}>;
|
|
40
|
+
/**
|
|
41
|
+
* The one daemon-owned policy consumer. Reliable and latest-value retain
|
|
42
|
+
* distinct types and stores; retries are sends, and only exact acknowledgments
|
|
43
|
+
* retire durable records.
|
|
44
|
+
*/
|
|
45
|
+
export declare class AgentEgressController {
|
|
46
|
+
private readonly options;
|
|
47
|
+
private readonly latest;
|
|
48
|
+
private readonly spools;
|
|
49
|
+
private readonly latestStatus;
|
|
50
|
+
private readonly reliableStatus;
|
|
51
|
+
private readonly drops;
|
|
52
|
+
private active;
|
|
53
|
+
constructor(options: AgentEgressControllerOptions);
|
|
54
|
+
get policy(): Readonly<AgentEgressPolicy>;
|
|
55
|
+
/** Permanently fail closed after its authenticated enrollment is replaced. */
|
|
56
|
+
deactivate(): void;
|
|
57
|
+
status(): AgentEgressStatus;
|
|
58
|
+
dropReceipts(): readonly AgentEgressDropReceipt[];
|
|
59
|
+
noteTransportDrop(reason: AgentEgressDropReason, agentRef?: AgentRef): void;
|
|
60
|
+
/** Project before TaskRunner builds a `task.progress` envelope. */
|
|
61
|
+
projectLatestValue(input: AgentEgressProgressInput): readonly AgentEvent[];
|
|
62
|
+
appendReliable(input: AgentEgressReliableInput): Promise<AgentEgressReliableAppendResult>;
|
|
63
|
+
/**
|
|
64
|
+
* Content decisions are reliable facts too. They never enter the generic
|
|
65
|
+
* egress payload authority: the spool persists their exact protocol payload
|
|
66
|
+
* with `wireType: agent.content.receipt` before any transport attempt.
|
|
67
|
+
*/
|
|
68
|
+
appendContentReceipt(input: AgentEgressContentReceiptInput): Promise<AgentEgressReliableAppendResult>;
|
|
69
|
+
/** Retires only the record whose full Agent/tenant/revision/id/cursor tuple matches. */
|
|
70
|
+
acknowledge(ack: AgentReliableAck): Promise<boolean>;
|
|
71
|
+
/** Re-open every existing Agent-local spool before retrying stable records after restart. */
|
|
72
|
+
recover(agentsRoot: string): Promise<void>;
|
|
73
|
+
reliableRecords(): readonly AgentReliableEgressRecord[];
|
|
74
|
+
/** Missing ack capability holds records in their reliable lane; it never makes them lossy. */
|
|
75
|
+
retryableReliableRecords(serverCapabilities: readonly string[]): readonly AgentReliableEgressRecord[];
|
|
76
|
+
private spoolFor;
|
|
77
|
+
private tenantPendingBytes;
|
|
78
|
+
private noteDrop;
|
|
79
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { type AgentEgressDropReason, type AgentEgressPolicy, type AgentEvent } from '@byok-sdk/protocol';
|
|
2
|
+
export type { AgentEgressDropReason, AgentEgressPolicy } from '@byok-sdk/protocol';
|
|
3
|
+
export interface AgentEgressDropReceipt {
|
|
4
|
+
lane: 'latest-value' | 'reliable';
|
|
5
|
+
reason: AgentEgressDropReason;
|
|
6
|
+
agentId?: string;
|
|
7
|
+
tenantId?: string;
|
|
8
|
+
eventId?: string;
|
|
9
|
+
occurredAt: string;
|
|
10
|
+
}
|
|
11
|
+
export interface AgentEgressLaneStatus {
|
|
12
|
+
pendingEvents: number;
|
|
13
|
+
pendingBytes: number;
|
|
14
|
+
replaced: number;
|
|
15
|
+
dropped: number;
|
|
16
|
+
lastDropReason?: AgentEgressDropReason;
|
|
17
|
+
}
|
|
18
|
+
export interface AgentEgressStatus {
|
|
19
|
+
policyRevision: string;
|
|
20
|
+
latestValue: AgentEgressLaneStatus;
|
|
21
|
+
reliable: AgentEgressLaneStatus;
|
|
22
|
+
}
|
|
23
|
+
/** Safe policy selected only when the host has not opted into content. */
|
|
24
|
+
export declare const DEFAULT_AGENT_EGRESS_POLICY: Readonly<AgentEgressPolicy>;
|
|
25
|
+
export declare class AgentEgressPolicyError extends Error {
|
|
26
|
+
constructor(message: string);
|
|
27
|
+
}
|
|
28
|
+
/** Resolve/validate once at construction; unknown policy shapes never become an implicit default. */
|
|
29
|
+
export declare function resolveAgentEgressPolicy(policy: AgentEgressPolicy | undefined): Readonly<AgentEgressPolicy>;
|
|
30
|
+
/**
|
|
31
|
+
* Default activity projection. Every retained string is SDK-authored; no
|
|
32
|
+
* runtime trajectory, tool, prompt, environment, argv, path, or credential
|
|
33
|
+
* value survives this transformation.
|
|
34
|
+
*/
|
|
35
|
+
export declare function metadataStatusEvent(event: AgentEvent): AgentEvent;
|
|
36
|
+
export declare function eventBytes(event: AgentEvent): number;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { type Envelope } from '@byok-sdk/protocol';
|
|
2
|
+
import { type AgentEgressDropReason, type AgentEgressPolicy } from './agent-egress-policy';
|
|
3
|
+
export interface AgentEgressSanitizerContext {
|
|
4
|
+
readonly lane: 'latest-value' | 'reliable';
|
|
5
|
+
readonly policyRevision: string;
|
|
6
|
+
readonly envelopeType?: string;
|
|
7
|
+
readonly agentId?: string;
|
|
8
|
+
readonly tenantId?: string;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Optional named host redaction hook for an explicitly contentful policy.
|
|
12
|
+
* It receives the SDK-projected value, never a second raw wire
|
|
13
|
+
* representation. Throwing/refusing drops the event; callers never receive
|
|
14
|
+
* an original-payload fallback.
|
|
15
|
+
*/
|
|
16
|
+
export type AgentEgressSanitizer = (value: unknown, context: AgentEgressSanitizerContext) => unknown;
|
|
17
|
+
export declare class AgentEgressSanitizationError extends Error {
|
|
18
|
+
readonly reason: AgentEgressDropReason;
|
|
19
|
+
constructor(message: string, reason?: AgentEgressDropReason);
|
|
20
|
+
}
|
|
21
|
+
export type SanitizedEnvelope = Readonly<{
|
|
22
|
+
ok: true;
|
|
23
|
+
envelope: Envelope;
|
|
24
|
+
}> | Readonly<{
|
|
25
|
+
ok: false;
|
|
26
|
+
reason: AgentEgressDropReason;
|
|
27
|
+
}>;
|
|
28
|
+
/**
|
|
29
|
+
* The one envelope-boundary sanitizer used before either transport sees an
|
|
30
|
+
* envelope. It parses the final value through the frozen protocol so a
|
|
31
|
+
* broken custom sanitizer also fails locally, before WS bytes or long-poll
|
|
32
|
+
* JSON can be created.
|
|
33
|
+
*/
|
|
34
|
+
export declare function sanitizeEgressEnvelope(envelope: Envelope, policy: Readonly<AgentEgressPolicy>, sanitizer: AgentEgressSanitizer | undefined, context?: Omit<AgentEgressSanitizerContext, 'lane' | 'policyRevision' | 'envelopeType'> & {
|
|
35
|
+
lane?: 'latest-value' | 'reliable';
|
|
36
|
+
}): SanitizedEnvelope;
|
|
37
|
+
/** Sanitizes a reliable payload before it is hashed/appended, never after. */
|
|
38
|
+
export declare function sanitizeReliablePayload(payload: unknown, policy: Readonly<AgentEgressPolicy>, sanitizer: AgentEgressSanitizer | undefined, context?: Omit<AgentEgressSanitizerContext, 'lane' | 'policyRevision'>): unknown;
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { type AgentContentReceiptPayload } from '@byok-sdk/protocol';
|
|
2
|
+
import type { AgentRef } from '../agent-home';
|
|
3
|
+
import { type AgentEgressPolicy, type AgentEgressDropReason } from './agent-egress-policy';
|
|
4
|
+
export declare const AGENT_EGRESS_DIRECTORY: string;
|
|
5
|
+
export declare const AGENT_RELIABLE_SPOOL_FILENAME = "reliable-v1.jsonl";
|
|
6
|
+
/** A spool row's intended envelope type, never inferred from its payload. */
|
|
7
|
+
export declare const AGENT_RELIABLE_WIRE_TYPES: readonly ['agent.egress.reliable', 'agent.content.receipt'];
|
|
8
|
+
export type AgentReliableWireType = (typeof AGENT_RELIABLE_WIRE_TYPES)[number];
|
|
9
|
+
type OmitReliableIdentity<T> = T extends unknown ? Omit<T, 'eventId' | 'cursor'> : never;
|
|
10
|
+
export type AgentContentReceiptWithoutReliableIdentity = OmitReliableIdentity<AgentContentReceiptPayload>;
|
|
11
|
+
export interface AgentReliableEgressRecord {
|
|
12
|
+
readonly schema: 1;
|
|
13
|
+
readonly wireType: AgentReliableWireType;
|
|
14
|
+
readonly agentRef: AgentRef;
|
|
15
|
+
readonly tenantId: string;
|
|
16
|
+
readonly policyRevision: string;
|
|
17
|
+
readonly eventId: string;
|
|
18
|
+
readonly cursor: number;
|
|
19
|
+
readonly payload: unknown;
|
|
20
|
+
readonly payloadHash: string;
|
|
21
|
+
readonly byteCount: number;
|
|
22
|
+
readonly createdAt: string;
|
|
23
|
+
readonly sessionRef?: string;
|
|
24
|
+
readonly taskId?: string;
|
|
25
|
+
}
|
|
26
|
+
export interface AgentReliableAppendInput {
|
|
27
|
+
readonly agentRef: AgentRef;
|
|
28
|
+
readonly tenantId: string;
|
|
29
|
+
readonly policyRevision: string;
|
|
30
|
+
readonly payload: unknown;
|
|
31
|
+
readonly sessionRef?: string;
|
|
32
|
+
readonly taskId?: string;
|
|
33
|
+
readonly eventId?: string;
|
|
34
|
+
readonly createdAt?: string;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Content receipts use the existing durable spool but retain their own wire
|
|
38
|
+
* type and validated receipt payload. The request id is the stable event id:
|
|
39
|
+
* a retried local read cannot mint a competing receipt identity.
|
|
40
|
+
*/
|
|
41
|
+
export interface AgentContentReceiptAppendInput {
|
|
42
|
+
readonly agentRef: AgentRef;
|
|
43
|
+
readonly tenantId: string;
|
|
44
|
+
readonly policyRevision: string;
|
|
45
|
+
readonly sessionRef: string;
|
|
46
|
+
readonly payload: AgentContentReceiptWithoutReliableIdentity;
|
|
47
|
+
readonly taskId?: string;
|
|
48
|
+
}
|
|
49
|
+
export interface AgentReliableAck {
|
|
50
|
+
readonly agentRef: AgentRef;
|
|
51
|
+
readonly tenantId: string;
|
|
52
|
+
readonly sessionRef: string;
|
|
53
|
+
readonly policyRevision: string;
|
|
54
|
+
readonly eventId: string;
|
|
55
|
+
readonly cursor: number;
|
|
56
|
+
}
|
|
57
|
+
export declare class AgentReliableSpoolError extends Error {
|
|
58
|
+
constructor(message: string);
|
|
59
|
+
}
|
|
60
|
+
export declare class AgentReliableQuotaError extends AgentReliableSpoolError {
|
|
61
|
+
readonly reason: Extract<AgentEgressDropReason, 'quota_exceeded' | 'backpressure'>;
|
|
62
|
+
constructor(reason: Extract<AgentEgressDropReason, 'quota_exceeded' | 'backpressure'>, message: string);
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Per-Agent durable append-before-send log. It is deliberately separate from
|
|
66
|
+
* the inbound task journal: its only truth is outbound reliable egress
|
|
67
|
+
* pending/exact-ack state.
|
|
68
|
+
*/
|
|
69
|
+
export declare class AgentReliableSpool {
|
|
70
|
+
readonly homeDir: string;
|
|
71
|
+
readonly spoolPath: string;
|
|
72
|
+
private readonly pending;
|
|
73
|
+
private nextCursor;
|
|
74
|
+
private logEntries;
|
|
75
|
+
private writeTail;
|
|
76
|
+
private constructor();
|
|
77
|
+
static open(homeDir: string): Promise<AgentReliableSpool>;
|
|
78
|
+
get pendingEvents(): number;
|
|
79
|
+
get pendingBytes(): number;
|
|
80
|
+
records(): readonly AgentReliableEgressRecord[];
|
|
81
|
+
append(input: AgentReliableAppendInput, policy: Readonly<AgentEgressPolicy>, tenantPendingBytes: number): Promise<AgentReliableEgressRecord>;
|
|
82
|
+
/**
|
|
83
|
+
* Append one complete, protocol-validated content receipt before its first
|
|
84
|
+
* send. `eventId` is fixed to `requestId`; the durable spool alone allocates
|
|
85
|
+
* the positive cursor, then validates the final payload before fsync.
|
|
86
|
+
*/
|
|
87
|
+
appendContentReceipt(input: AgentContentReceiptAppendInput, policy: Readonly<AgentEgressPolicy>, tenantPendingBytes: number): Promise<AgentReliableEgressRecord>;
|
|
88
|
+
/** Exact matching ack is the only transition which retires a record. */
|
|
89
|
+
acknowledge(ack: AgentReliableAck): Promise<boolean>;
|
|
90
|
+
private load;
|
|
91
|
+
private appendEntry;
|
|
92
|
+
private compact;
|
|
93
|
+
private exclusive;
|
|
94
|
+
}
|
|
95
|
+
export interface LatestValueRecord {
|
|
96
|
+
readonly agentRef: AgentRef;
|
|
97
|
+
readonly tenantId: string;
|
|
98
|
+
readonly event: import('@byok-sdk/protocol').AgentEvent;
|
|
99
|
+
readonly byteCount: number;
|
|
100
|
+
readonly updatedAt: string;
|
|
101
|
+
}
|
|
102
|
+
/** In-memory latest-value state; it is never replayed as durable history. */
|
|
103
|
+
export declare class AgentLatestValueState {
|
|
104
|
+
private readonly recordsByAgent;
|
|
105
|
+
offer(record: Omit<LatestValueRecord, 'byteCount' | 'updatedAt'>, policy: Readonly<AgentEgressPolicy>): Readonly<{
|
|
106
|
+
accepted: true;
|
|
107
|
+
replaced: boolean;
|
|
108
|
+
record: LatestValueRecord;
|
|
109
|
+
}> | Readonly<{
|
|
110
|
+
accepted: false;
|
|
111
|
+
reason: Extract<AgentEgressDropReason, 'quota_exceeded' | 'backpressure'>;
|
|
112
|
+
}>;
|
|
113
|
+
get pendingEvents(): number;
|
|
114
|
+
get pendingBytes(): number;
|
|
115
|
+
}
|
|
116
|
+
export {};
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { type AgentRef } from '../agent-home';
|
|
2
|
+
export type AgentTerminalCause = 'complete' | 'failed' | 'cancelled';
|
|
3
|
+
export interface AgentSessionHandoff {
|
|
4
|
+
readonly agentRef: AgentRef;
|
|
5
|
+
/** Task that created the native runtime session. */
|
|
6
|
+
readonly taskId: string;
|
|
7
|
+
readonly sessionRef: string;
|
|
8
|
+
readonly runtimeId: string;
|
|
9
|
+
/** Canonical Agent home and runtime cwd; these are intentionally one value. */
|
|
10
|
+
readonly cwd: string;
|
|
11
|
+
readonly leaseId: string;
|
|
12
|
+
readonly terminalCause?: AgentTerminalCause;
|
|
13
|
+
readonly terminalReason?: string;
|
|
14
|
+
readonly updatedAt: string;
|
|
15
|
+
}
|
|
16
|
+
export interface AgentSessionHandoffMatch {
|
|
17
|
+
readonly agentRef: AgentRef;
|
|
18
|
+
readonly sessionRef: string;
|
|
19
|
+
readonly runtimeId: string;
|
|
20
|
+
readonly cwd: string;
|
|
21
|
+
}
|
|
22
|
+
export interface AgentTaskTerminalEvidence {
|
|
23
|
+
readonly agentRef: AgentRef;
|
|
24
|
+
readonly taskId: string;
|
|
25
|
+
readonly runtimeId: string;
|
|
26
|
+
/** Canonical Agent home and sealed runtime cwd. */
|
|
27
|
+
readonly cwd: string;
|
|
28
|
+
readonly leaseId: string;
|
|
29
|
+
/** Present when adapter start succeeded but handoff persistence failed. */
|
|
30
|
+
readonly sessionRef?: string;
|
|
31
|
+
readonly terminalCause: 'failed';
|
|
32
|
+
readonly terminalReason: string;
|
|
33
|
+
readonly updatedAt: string;
|
|
34
|
+
}
|
|
35
|
+
export interface AgentTaskTerminalMatch {
|
|
36
|
+
readonly agentRef: AgentRef;
|
|
37
|
+
readonly taskId: string;
|
|
38
|
+
readonly runtimeId: string;
|
|
39
|
+
readonly cwd: string;
|
|
40
|
+
}
|
|
41
|
+
export declare class AgentSessionHandoffStoreError extends Error {
|
|
42
|
+
constructor(message: string);
|
|
43
|
+
}
|
|
44
|
+
export declare class AgentSessionHandoffCorruptError extends AgentSessionHandoffStoreError {
|
|
45
|
+
constructor(message: string);
|
|
46
|
+
}
|
|
47
|
+
export declare class AgentSessionHandoffMismatchError extends AgentSessionHandoffStoreError {
|
|
48
|
+
constructor(message: string);
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Durable, fail-closed session evidence stored inside the canonical Agent
|
|
52
|
+
* home. Each session gets one hash-addressed append-only JSONL ledger under
|
|
53
|
+
* `.byok/runtime-sessions/`; session text never becomes a pathname. Unlike
|
|
54
|
+
* the legacy SessionWorkspaceStore, corrupt bytes are never interpreted as a
|
|
55
|
+
* missing mapping.
|
|
56
|
+
*/
|
|
57
|
+
export declare class AgentSessionHandoffStore {
|
|
58
|
+
private readonly queues;
|
|
59
|
+
get(expected: AgentSessionHandoffMatch): Promise<AgentSessionHandoff | undefined>;
|
|
60
|
+
/** Append-only readback for audit/recovery; every historical terminal remains visible. */
|
|
61
|
+
history(expected: AgentSessionHandoffMatch): Promise<readonly AgentSessionHandoff[]>;
|
|
62
|
+
/** Exact identity check used before a strict Agent resume is admitted. */
|
|
63
|
+
requireMatch(expected: AgentSessionHandoffMatch): Promise<AgentSessionHandoff>;
|
|
64
|
+
/** Append-only, fsynced write. The caller awaits this before task.started. */
|
|
65
|
+
record(input: Omit<AgentSessionHandoff, 'updatedAt' | 'terminalCause' | 'terminalReason'>): Promise<AgentSessionHandoff>;
|
|
66
|
+
/** Records the first terminal cause without changing the exact handoff identity. */
|
|
67
|
+
recordTerminal(expected: AgentSessionHandoffMatch, cause: AgentTerminalCause, reason?: string): Promise<AgentSessionHandoff>;
|
|
68
|
+
/**
|
|
69
|
+
* Persists a claimed Agent task failure that happened before an active
|
|
70
|
+
* session handoff existed. Callers await the fsync before sending
|
|
71
|
+
* `task.fail`, so cloud state can never outrun the Agent-local evidence.
|
|
72
|
+
*/
|
|
73
|
+
recordTaskTerminal(input: Omit<AgentTaskTerminalEvidence, 'updatedAt' | 'terminalCause'>): Promise<AgentTaskTerminalEvidence>;
|
|
74
|
+
getTaskTerminal(expectedInput: AgentTaskTerminalMatch): Promise<AgentTaskTerminalEvidence | undefined>;
|
|
75
|
+
private filePath;
|
|
76
|
+
private taskTerminalFilePath;
|
|
77
|
+
private enqueue;
|
|
78
|
+
private load;
|
|
79
|
+
private loadAll;
|
|
80
|
+
private loadTaskTerminal;
|
|
81
|
+
private append;
|
|
82
|
+
}
|
|
@@ -3,7 +3,9 @@ import type { AuthManager } from './auth-manager';
|
|
|
3
3
|
/** Seam `TaskRunner` depends on, so tests can substitute a fake without spinning up real HTTP endpoints. */
|
|
4
4
|
export interface BlobResolver {
|
|
5
5
|
resolveInstruction(blobRef: BlobRef): Promise<string>;
|
|
6
|
-
uploadArtifact(content: string | Uint8Array, contentType: string
|
|
6
|
+
uploadArtifact(content: string | Uint8Array, contentType: string, options?: {
|
|
7
|
+
readonly idempotencyKey?: string;
|
|
8
|
+
}): Promise<BlobRef>;
|
|
7
9
|
}
|
|
8
10
|
/**
|
|
9
11
|
* HTTP-side blob transfer (protocol §7): resolving an instruction `blobRef`
|
|
@@ -18,5 +20,7 @@ export declare class BlobClient implements BlobResolver {
|
|
|
18
20
|
/** `blobRef` -> `GET /byok/blobs/:id/url` -> fetch the presigned download URL -> text content. Always resolves fresh rather than trusting any inlined `BlobRef.url`, per docs/protocol.md §7. */
|
|
19
21
|
resolveInstruction(blobRef: BlobRef): Promise<string>;
|
|
20
22
|
/** `POST /byok/blobs` (declares size/contentType/contentHash) -> PUT the bytes to the presigned upload URL -> a `BlobRef` for `task.artifact.blobRef`. */
|
|
21
|
-
uploadArtifact(content: string | Uint8Array, contentType: string
|
|
23
|
+
uploadArtifact(content: string | Uint8Array, contentType: string, options?: {
|
|
24
|
+
readonly idempotencyKey?: string;
|
|
25
|
+
}): Promise<BlobRef>;
|
|
22
26
|
}
|
|
@@ -12,8 +12,8 @@ export interface ConnectionManagerOptions {
|
|
|
12
12
|
/** U4a Local Agent release version; passed unchanged to both transports. */
|
|
13
13
|
clientVersion?: string;
|
|
14
14
|
runtimes: RuntimeInfo[];
|
|
15
|
-
/**
|
|
16
|
-
|
|
15
|
+
/** Reads current sorted logical IDs from the validated local registry for every WS hello. */
|
|
16
|
+
getConfiguredToolsets?: () => readonly ToolsetId[];
|
|
17
17
|
auth: AuthManager;
|
|
18
18
|
cursorStore: CursorStore;
|
|
19
19
|
/**
|
|
@@ -4,6 +4,7 @@ import type { StorageCategory } from './journal/journal';
|
|
|
4
4
|
import type { StoragePressureState } from './journal/storage-policy';
|
|
5
5
|
import type { OperationalHealthSnapshot } from './operational-health';
|
|
6
6
|
import type { LocalAgentReleaseIdentity } from '../release-identity';
|
|
7
|
+
import type { McpToolsetConfig, McpToolsetRegistryStatus } from '../types';
|
|
7
8
|
/**
|
|
8
9
|
* M4 Phase 2: shared local-IPC contract between the daemon's control server
|
|
9
10
|
* (`control-server.ts`) and the CLI's control client (`bin/control-client.ts`)
|
|
@@ -273,7 +274,15 @@ export interface ControlStatusResult {
|
|
|
273
274
|
storage?: ControlStorageStatus;
|
|
274
275
|
/** Local lifecycle/retry budget. This is not the transport state above. */
|
|
275
276
|
operationalHealth: OperationalHealthSnapshot;
|
|
277
|
+
/** Redacted content-addressed status from the daemon's single local registry. */
|
|
278
|
+
toolsets: McpToolsetRegistryStatus;
|
|
276
279
|
}
|
|
280
|
+
export interface ToolsetsReloadParams {
|
|
281
|
+
expectedRevision: string;
|
|
282
|
+
mcpToolsets: Record<string, McpToolsetConfig>;
|
|
283
|
+
}
|
|
284
|
+
/** Shape-only parser; executable definition validation remains registry-owned. */
|
|
285
|
+
export declare function parseToolsetsReloadParams(value: unknown): ToolsetsReloadParams | undefined;
|
|
277
286
|
export interface ApprovalsListResult {
|
|
278
287
|
approvals: PendingApproval[];
|
|
279
288
|
}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
import type { RuntimeId } from '@byok-sdk/protocol';
|
|
1
|
+
import type { AgentEgressPolicy, RuntimeId } from '@byok-sdk/protocol';
|
|
2
2
|
import type { PermissionPolicy } from '@byok-sdk/protocol';
|
|
3
|
-
import type { RuntimeAdapter, GitWorkspaceConfig, McpToolsetConfig } from '../types';
|
|
3
|
+
import type { RuntimeAdapter, GitWorkspaceConfig, McpToolsetConfig, McpToolsetObservation, McpToolsetRegistryStatus, McpToolsetReloadReceipt } from '../types';
|
|
4
|
+
import { type AgentHomeProjection } from '../agent-home';
|
|
5
|
+
import type { AgentRef } from '../agent-home';
|
|
4
6
|
import { type LocalAgentReleaseIdentity } from '../release-identity';
|
|
5
7
|
import type { BackoffOptions, LivenessOptions } from './ws-transport';
|
|
6
8
|
import { type OperationalHealthSnapshot } from './operational-health';
|
|
@@ -13,6 +15,10 @@ import { type JournalOpenFaultSeam } from './journal/sqlite-support';
|
|
|
13
15
|
import { LocalStoragePressureEngine, type LocalStoragePolicyInput } from './journal/storage-policy';
|
|
14
16
|
import { type ResultDocumentExtractor } from './task-runner';
|
|
15
17
|
import { type ProgressBatcherOptions } from './progress-batcher';
|
|
18
|
+
import { type AgentEgressReliableAppendResult } from './agent-egress-controller';
|
|
19
|
+
import { type AgentEgressStatus } from './agent-egress-policy';
|
|
20
|
+
import { type AgentEgressSanitizer } from './agent-egress-sanitizer';
|
|
21
|
+
import { type AgentContentReadRoot } from './agent-content-read';
|
|
16
22
|
/**
|
|
17
23
|
* Optional white-label product display info — purely opaque passthrough
|
|
18
24
|
* (never interpreted, validated, or rendered by the daemon itself). Carried
|
|
@@ -56,13 +62,6 @@ export interface HostedJournalConfig {
|
|
|
56
62
|
* to a closed set instead of a re-interpretation of an existing config.
|
|
57
63
|
*/
|
|
58
64
|
mode: 'sqlite';
|
|
59
|
-
/**
|
|
60
|
-
* The tenant every journal row on this device is scoped to (§12.7.2's
|
|
61
|
-
* minimum fact set opens with tenant/product/device). Required: a hosted
|
|
62
|
-
* daemon that cannot say which tenant its durable evidence belongs to has
|
|
63
|
-
* evidence nobody can act on.
|
|
64
|
-
*/
|
|
65
|
-
tenantId: string;
|
|
66
65
|
/** Bound on waiting for the journal's write lock, ms. Defaults to the journal's own bound. */
|
|
67
66
|
busyTimeoutMs?: number;
|
|
68
67
|
/** Per-record byte bound. Defaults to the journal's own bound; oversized records are refused, never truncated. */
|
|
@@ -91,7 +90,27 @@ export interface DaemonConfig {
|
|
|
91
90
|
serverUrl: string;
|
|
92
91
|
deviceName?: string;
|
|
93
92
|
workspaceRoot: string;
|
|
94
|
-
/**
|
|
93
|
+
/**
|
|
94
|
+
* Strict Agent execution boundary. The host selects one absolute branded
|
|
95
|
+
* storage root; the SDK alone composes `agents/<agentId>`, initializes the
|
|
96
|
+
* durable home, and binds it as runtime cwd. `projection` may write opaque,
|
|
97
|
+
* redacted host content into the canonical home supplied by the SDK.
|
|
98
|
+
*/
|
|
99
|
+
agentHome?: {
|
|
100
|
+
hostStorageRoot: string;
|
|
101
|
+
projection?: AgentHomeProjection;
|
|
102
|
+
};
|
|
103
|
+
/**
|
|
104
|
+
* Explicit Agent-local/cloud egress selection. Omission still enforces the
|
|
105
|
+
* SDK metadata/status projection, but does not advertise or admit the new
|
|
106
|
+
* policy/reliable protocol surface.
|
|
107
|
+
*/
|
|
108
|
+
agentEgress?: AgentEgressConfig;
|
|
109
|
+
/**
|
|
110
|
+
* Disabled by default. Enables local-only Git checkpoint repositories for
|
|
111
|
+
* legacy task workspaces. Mutually exclusive with `agentHome`: strict Agent
|
|
112
|
+
* execution has one canonical workspace authority.
|
|
113
|
+
*/
|
|
95
114
|
gitWorkspace?: GitWorkspaceConfig;
|
|
96
115
|
/** Disabled by default. Enables the durable local task journal — see {@link HostedJournalConfig}. */
|
|
97
116
|
hostedJournal?: HostedJournalConfig;
|
|
@@ -320,6 +339,43 @@ export interface DaemonConfig {
|
|
|
320
339
|
*/
|
|
321
340
|
deviceAssertion?: DeviceAssertionConfig;
|
|
322
341
|
}
|
|
342
|
+
export interface AgentEgressConfig {
|
|
343
|
+
/** Exact policy the daemon is willing to consume from an Agent offer. */
|
|
344
|
+
policy: AgentEgressPolicy;
|
|
345
|
+
/** Named redaction hook for explicit contentful trajectory only. */
|
|
346
|
+
sanitizer?: AgentEgressSanitizer;
|
|
347
|
+
/**
|
|
348
|
+
* Device-local additions required to make one server-selected transfer
|
|
349
|
+
* policy executable. These values only supplement `policy.transfers`: a
|
|
350
|
+
* locally configured surface never enables a server-disabled transfer, and
|
|
351
|
+
* cannot widen its maxBytes or MIME authority.
|
|
352
|
+
*/
|
|
353
|
+
contentRead?: AgentContentReadConfig;
|
|
354
|
+
}
|
|
355
|
+
/** Local root and text handling authority for one independently-gated surface. */
|
|
356
|
+
export interface AgentContentReadSurfaceConfig {
|
|
357
|
+
readonly root: AgentContentReadRoot;
|
|
358
|
+
readonly maxTextBytes: number;
|
|
359
|
+
readonly textMimeTypes: readonly string[];
|
|
360
|
+
readonly sensitiveNames?: readonly string[];
|
|
361
|
+
}
|
|
362
|
+
/**
|
|
363
|
+
* Host-local portions of the content-read contract. The audit ledger has no
|
|
364
|
+
* host path option: SDK composition fixes it per Agent home.
|
|
365
|
+
*/
|
|
366
|
+
export interface AgentContentReadConfig {
|
|
367
|
+
readonly workspace?: AgentContentReadSurfaceConfig;
|
|
368
|
+
readonly transcript?: AgentContentReadSurfaceConfig;
|
|
369
|
+
readonly artifact?: AgentContentReadSurfaceConfig;
|
|
370
|
+
readonly runtimeAllowlistedRoots?: readonly string[];
|
|
371
|
+
}
|
|
372
|
+
export interface AgentReliableEgressInput {
|
|
373
|
+
agentRef: AgentRef;
|
|
374
|
+
sessionRef: string;
|
|
375
|
+
payload: unknown;
|
|
376
|
+
taskId?: string;
|
|
377
|
+
eventId?: string;
|
|
378
|
+
}
|
|
323
379
|
/**
|
|
324
380
|
* Plan `device-assertion-broker`. Two fields, both about what this daemon will
|
|
325
381
|
* refuse.
|
|
@@ -383,12 +439,22 @@ export interface DaemonStatus {
|
|
|
383
439
|
branding?: DaemonBranding;
|
|
384
440
|
/** Local lifecycle/retry budget, separate from transport fallback state. */
|
|
385
441
|
operationalHealth: OperationalHealthSnapshot;
|
|
442
|
+
/** Redacted, content-addressed device-local MCP registry status. */
|
|
443
|
+
toolsets: McpToolsetRegistryStatus;
|
|
444
|
+
/** Content-free egress lane watermarks and typed last-drop facts. */
|
|
445
|
+
egress: AgentEgressStatus;
|
|
386
446
|
}
|
|
387
447
|
export interface Daemon {
|
|
388
448
|
pair(pairingCode: string): Promise<DeviceRecord>;
|
|
389
449
|
start(): Promise<void>;
|
|
390
450
|
stop(): Promise<void>;
|
|
391
451
|
status(): DaemonStatus;
|
|
452
|
+
/** Append one sanitized reliable record before its first transport attempt. */
|
|
453
|
+
publishReliableAgentEgress?(input: AgentReliableEgressInput): Promise<AgentEgressReliableAppendResult>;
|
|
454
|
+
/** Atomically replace the local registry when its current revision matches. */
|
|
455
|
+
reloadMcpToolsets(mcpToolsets: Record<string, McpToolsetConfig> | undefined, expectedRevision: string): McpToolsetReloadReceipt;
|
|
456
|
+
/** Record one explicit host-owned lifecycle observation for a configured toolset. */
|
|
457
|
+
reportMcpToolsetObservation(toolsetId: string, expectedDefinitionRevision: string, observation: McpToolsetObservation): void;
|
|
392
458
|
/**
|
|
393
459
|
* M3-2a: local observability — subscribe to live `DaemonEvent`s (task
|
|
394
460
|
* feed, connection/pairing state changes, runtime-detection results) as
|
|
@@ -1,5 +1,33 @@
|
|
|
1
1
|
import { type Envelope } from '@byok-sdk/protocol';
|
|
2
2
|
import { AuthManager } from './auth-manager';
|
|
3
|
+
import { type TransportEndpoint } from './url';
|
|
4
|
+
/**
|
|
5
|
+
* A long-poll request failed in a way that today told the caller only
|
|
6
|
+
* `false`/"retry in 2s" — this names WHICH of the transport's two routes it
|
|
7
|
+
* was and what the server said, so a stuck fallback loop is diagnosable
|
|
8
|
+
* without a packet capture.
|
|
9
|
+
*
|
|
10
|
+
* Scope (review finding — honest attribution): this type represents ONLY an
|
|
11
|
+
* actual route request/response cycle failing. Anything that happens BEFORE
|
|
12
|
+
* the request exists — in practice credential acquisition
|
|
13
|
+
* (`AuthManager.getValidAccessToken`) — is not a route failure and is never
|
|
14
|
+
* reported as one; neither is {@link DeviceRevokedError}, which is a device
|
|
15
|
+
* lifecycle fact rather than something the route did. See
|
|
16
|
+
* `LongPollClient.loop`/`postBatch` for where that boundary is drawn.
|
|
17
|
+
*
|
|
18
|
+
* `status` is the HTTP status of the response the route produced, INCLUDING
|
|
19
|
+
* the case where the response arrived intact and its body then failed to read
|
|
20
|
+
* or parse (a 200 whose payload is malformed is still a 200 — the parse error
|
|
21
|
+
* rides in `cause`). `undefined` means no response was ever produced: the
|
|
22
|
+
* `fetch` itself rejected (DNS/TLS/connection failure, abort). The underlying
|
|
23
|
+
* error is kept in `cause` rather than flattened into the message, so nothing
|
|
24
|
+
* about the original failure is lost.
|
|
25
|
+
*/
|
|
26
|
+
export declare class LongPollRouteError extends Error {
|
|
27
|
+
readonly endpoint: TransportEndpoint;
|
|
28
|
+
readonly status: number | undefined;
|
|
29
|
+
constructor(endpoint: TransportEndpoint, status: number | undefined, cause: unknown);
|
|
30
|
+
}
|
|
3
31
|
export interface LongPollClientOptions {
|
|
4
32
|
serverUrl: string;
|
|
5
33
|
auth: AuthManager;
|
|
@@ -131,7 +159,39 @@ export declare class LongPollClient {
|
|
|
131
159
|
* eviction bookkeeping for a case this unlikely.
|
|
132
160
|
*/
|
|
133
161
|
private readonly warnedValidationFailureSeqs;
|
|
162
|
+
/**
|
|
163
|
+
* `path:status` keys this loop has already warned about — same one-warn-per-key
|
|
164
|
+
* discipline (and same rare-path soft-cap reset) as
|
|
165
|
+
* {@link warnedValidationFailureSeqs}, and for the same reason: an
|
|
166
|
+
* unreachable or misconfigured route fails again every `retryDelayMs` (2s
|
|
167
|
+
* by default) for as long as the fallback is engaged, so an unguarded warn
|
|
168
|
+
* would bury every other line in the log within a minute. Keyed by route
|
|
169
|
+
* AND status so a route that starts failing differently (503 -> 401) still
|
|
170
|
+
* warns once for the new condition.
|
|
171
|
+
*/
|
|
172
|
+
private readonly warnedRouteFailures;
|
|
173
|
+
/**
|
|
174
|
+
* Both routes this transport can fail against, built once (see
|
|
175
|
+
* {@link describeEndpoint} for why constructing them in one place is what
|
|
176
|
+
* keeps credentials out of every diagnostic derived from them).
|
|
177
|
+
*/
|
|
178
|
+
private readonly eventsEndpoint;
|
|
179
|
+
private readonly messagesEndpoint;
|
|
134
180
|
constructor(opts: LongPollClientOptions);
|
|
181
|
+
/**
|
|
182
|
+
* One warn per `path:status`, carrying the typed {@link LongPollRouteError}
|
|
183
|
+
* as the second argument so a caller inspecting the log (or a test) reads
|
|
184
|
+
* the route off the error rather than re-parsing the message.
|
|
185
|
+
*/
|
|
186
|
+
private warnRouteFailure;
|
|
187
|
+
/**
|
|
188
|
+
* {@link DeviceRevokedError} is a device lifecycle fact, not a route
|
|
189
|
+
* failure: it stops this loop outright (retrying cannot help) and is
|
|
190
|
+
* deliberately reported through `onRevoked` ONLY — never additionally as a
|
|
191
|
+
* {@link LongPollRouteError}. Returns whether the error was that case, so
|
|
192
|
+
* each call site can skip its route-failure warn for it.
|
|
193
|
+
*/
|
|
194
|
+
private noteRevoked;
|
|
135
195
|
start(): void;
|
|
136
196
|
stop(): void;
|
|
137
197
|
/**
|
|
@@ -57,8 +57,8 @@ export declare function assertPresenceHeartbeatCadence(cadence: {
|
|
|
57
57
|
export interface PresencePublisherOptions {
|
|
58
58
|
serverUrl: string;
|
|
59
59
|
auth: AuthManager;
|
|
60
|
-
/**
|
|
61
|
-
|
|
60
|
+
/** Reads current sorted logical IDs only; executable definitions and credentials remain device-local. */
|
|
61
|
+
getConfiguredToolsets?: () => readonly ToolsetId[];
|
|
62
62
|
/** U4a Local Agent release version; never inferred from the host package. */
|
|
63
63
|
clientVersion?: string;
|
|
64
64
|
/** The same runtime/auth snapshot sent in `conn.hello`. */
|
package/dist/daemon/store.d.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { type EnsureSecureDirOptions } from '../util/secure-dir';
|
|
2
2
|
export interface DeviceRecord {
|
|
3
3
|
deviceId: string;
|
|
4
|
+
/** Opaque tenant binding returned by the authenticated pairing response. */
|
|
5
|
+
tenantId: string;
|
|
4
6
|
/** Current access token (JWT), renewed via challenge/token without re-pairing (protocol §6.2). */
|
|
5
7
|
accessToken: string;
|
|
6
8
|
/** ISO-8601 expiry for `accessToken` (our best knowledge of it — see auth-manager.ts for how this is derived after `/byok/pair`, which reports no explicit expiry itself). */
|
|
@@ -10,6 +12,13 @@ export interface DeviceRecord {
|
|
|
10
12
|
/** Ed25519 public key, base64url — re-sent verbatim on a post-revocation re-pair (protocol §6.3). */
|
|
11
13
|
devicePublicKey: string;
|
|
12
14
|
}
|
|
15
|
+
/**
|
|
16
|
+
* A durable enrollment record cannot be used by any steady-state path. Only
|
|
17
|
+
* the explicit pair operation may replace it with a fresh authenticated row.
|
|
18
|
+
*/
|
|
19
|
+
export declare class DeviceRecordRePairRequiredError extends Error {
|
|
20
|
+
constructor();
|
|
21
|
+
}
|
|
13
22
|
/**
|
|
14
23
|
* Persists the device identity issued by `pair()` — deviceId, current
|
|
15
24
|
* access token + its expiry, and the device's own Ed25519 keypair. This is
|