@byok-sdk/client 0.6.1 → 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 +11 -8
- package/dist/bin/byok-agent.js +105 -34
- package/dist/bin/byok-agent.js.map +1 -1
- package/dist/daemon/agent-egress-controller.d.ts +3 -0
- package/dist/daemon/create-daemon.d.ts +0 -9
- package/dist/daemon/store.d.ts +9 -0
- package/dist/index.js +104 -33
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
|
@@ -49,8 +49,11 @@ export declare class AgentEgressController {
|
|
|
49
49
|
private readonly latestStatus;
|
|
50
50
|
private readonly reliableStatus;
|
|
51
51
|
private readonly drops;
|
|
52
|
+
private active;
|
|
52
53
|
constructor(options: AgentEgressControllerOptions);
|
|
53
54
|
get policy(): Readonly<AgentEgressPolicy>;
|
|
55
|
+
/** Permanently fail closed after its authenticated enrollment is replaced. */
|
|
56
|
+
deactivate(): void;
|
|
54
57
|
status(): AgentEgressStatus;
|
|
55
58
|
dropReceipts(): readonly AgentEgressDropReceipt[];
|
|
56
59
|
noteTransportDrop(reason: AgentEgressDropReason, agentRef?: AgentRef): void;
|
|
@@ -62,13 +62,6 @@ export interface HostedJournalConfig {
|
|
|
62
62
|
* to a closed set instead of a re-interpretation of an existing config.
|
|
63
63
|
*/
|
|
64
64
|
mode: 'sqlite';
|
|
65
|
-
/**
|
|
66
|
-
* The tenant every journal row on this device is scoped to (§12.7.2's
|
|
67
|
-
* minimum fact set opens with tenant/product/device). Required: a hosted
|
|
68
|
-
* daemon that cannot say which tenant its durable evidence belongs to has
|
|
69
|
-
* evidence nobody can act on.
|
|
70
|
-
*/
|
|
71
|
-
tenantId: string;
|
|
72
65
|
/** Bound on waiting for the journal's write lock, ms. Defaults to the journal's own bound. */
|
|
73
66
|
busyTimeoutMs?: number;
|
|
74
67
|
/** Per-record byte bound. Defaults to the journal's own bound; oversized records are refused, never truncated. */
|
|
@@ -347,8 +340,6 @@ export interface DaemonConfig {
|
|
|
347
340
|
deviceAssertion?: DeviceAssertionConfig;
|
|
348
341
|
}
|
|
349
342
|
export interface AgentEgressConfig {
|
|
350
|
-
/** Authenticated deployment tenant bound into every local reliable record. */
|
|
351
|
-
tenantId: string;
|
|
352
343
|
/** Exact policy the daemon is willing to consume from an Agent offer. */
|
|
353
344
|
policy: AgentEgressPolicy;
|
|
354
345
|
/** Named redaction hook for explicit contentful trajectory only. */
|
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
|
package/dist/index.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { randomUUID, createHash, sign, createPrivateKey, generateKeyPairSync, randomBytes, timingSafeEqual, createHmac } from 'crypto';
|
|
2
2
|
import { promises, mkdirSync, existsSync, renameSync, writeFileSync, chmodSync, statSync, readdirSync, linkSync, fstatSync, lstatSync, unlinkSync, constants, readFileSync, realpathSync } from 'fs';
|
|
3
3
|
import path, { join, isAbsolute } from 'path';
|
|
4
|
-
import { AGENT_CONTENT_ARTIFACT_READ_CAPABILITY, AGENT_CONTENT_TRANSCRIPT_READ_CAPABILITY, AGENT_CONTENT_WORKSPACE_READ_CAPABILITY, AgentRefSchema, BYOK_PAIR_PATH, BYOK_CHALLENGE_PATH, BYOK_TOKEN_PATH, byokBlobUrlPath, BYOK_BLOBS_PATH, byokBlobFinalizePath, partitionAgentEvents, BYOK_SKILL_PACKS_PATH, byokSkillPackFilePath, BYOK_RECORDS_PATH, byokRecordPath, TASK_TRANSITIONS, AgentEgressPolicySchema, CONFIGURED_TOOLSETS_MAX_ITEMS, ToolsetIdSchema, AgentContentReceiptPayloadSchema, encodeEnvelope, PROTOCOL_VERSION, createEnvelope, AGENT_EGRESS_RELIABLE_ACK_CAPABILITY, AGENT_EGRESS_POLICY_CAPABILITY, parseMessage, checkResultDocument, MAX_MESSAGES_PER_BATCH, BYOK_CAPABILITIES_PATH, BYOK_PRESENCE_PATH, RuntimeIdSchema, TERMINAL_INFERENCE_USAGE_MAX_DURATION_MS, TERMINAL_INFERENCE_USAGE_MAX_TOKENS, RESULT_DOCUMENT_MAX_BYTES, decodeEnvelope, BYOK_EVENTS_PATH, BYOK_MESSAGES_PATH, MessagesSendResponseSchema, UnknownMessageTypeError, BYOK_WS_PATH } from '@byok-sdk/protocol';
|
|
4
|
+
import { AGENT_CONTENT_ARTIFACT_READ_CAPABILITY, AGENT_CONTENT_TRANSCRIPT_READ_CAPABILITY, AGENT_CONTENT_WORKSPACE_READ_CAPABILITY, AgentRefSchema, BYOK_PAIR_PATH, PairResponseSchema, BYOK_CHALLENGE_PATH, BYOK_TOKEN_PATH, byokBlobUrlPath, BYOK_BLOBS_PATH, byokBlobFinalizePath, partitionAgentEvents, BYOK_SKILL_PACKS_PATH, byokSkillPackFilePath, BYOK_RECORDS_PATH, byokRecordPath, TASK_TRANSITIONS, AgentEgressPolicySchema, CONFIGURED_TOOLSETS_MAX_ITEMS, ToolsetIdSchema, AgentContentReceiptPayloadSchema, encodeEnvelope, PROTOCOL_VERSION, createEnvelope, AGENT_EGRESS_RELIABLE_ACK_CAPABILITY, AGENT_EGRESS_POLICY_CAPABILITY, parseMessage, checkResultDocument, MAX_MESSAGES_PER_BATCH, BYOK_CAPABILITIES_PATH, BYOK_PRESENCE_PATH, RuntimeIdSchema, TERMINAL_INFERENCE_USAGE_MAX_DURATION_MS, TERMINAL_INFERENCE_USAGE_MAX_TOKENS, RESULT_DOCUMENT_MAX_BYTES, decodeEnvelope, BYOK_EVENTS_PATH, BYOK_MESSAGES_PATH, MessagesSendResponseSchema, UnknownMessageTypeError, BYOK_WS_PATH } from '@byok-sdk/protocol';
|
|
5
5
|
import { execFile, spawn } from 'child_process';
|
|
6
6
|
import os6 from 'os';
|
|
7
|
-
import { parseDeviceAssertionEnvelope, tenantId, DeviceProofProtectedClaimsSchema, deviceProofSigningInput, DEVICE_PROOF_SCHEMA_ID, SKILL_PACK_MAX_BYTES, hasCapability, parseSkillPackManifest, checkSkillPackManifest, skillPackContentHashInput, checkSkillPackFileContent, SKILL_PACK_ENTRY_PATH, checkSkillPackEntry, isSkillPackPathSafe, DEVICE_PROOF_HEADER, contentHash as contentHash$1, TRUTH_RECORD_KINDS, nonceSigningBytes, DEVICE_ASSERTION_AUDIENCE_MAX_BYTES, DEVICE_ASSERTION_DEFAULT_TTL_MS, DEVICE_ASSERTION_MAX_TTL_MS, CONTENT_HASH_PATTERN, CapabilityDeclarationSchema, DeviceAssertionClaimsSchema, deviceAssertionSigningInput, DEVICE_ASSERTION_SCHEMA_ID } from '@byok-sdk/core';
|
|
7
|
+
import { parseDeviceAssertionEnvelope, tenantId, DeviceProofProtectedClaimsSchema, deviceProofSigningInput, DEVICE_PROOF_SCHEMA_ID, SKILL_PACK_MAX_BYTES, hasCapability, parseSkillPackManifest, checkSkillPackManifest, skillPackContentHashInput, checkSkillPackFileContent, SKILL_PACK_ENTRY_PATH, checkSkillPackEntry, isSkillPackPathSafe, DEVICE_PROOF_HEADER, contentHash as contentHash$1, TRUTH_RECORD_KINDS, nonceSigningBytes, DEVICE_ASSERTION_AUDIENCE_MAX_BYTES, DEVICE_ASSERTION_DEFAULT_TTL_MS, DEVICE_ASSERTION_MAX_TTL_MS, CONTENT_HASH_PATTERN, isTenantId, CapabilityDeclarationSchema, DeviceAssertionClaimsSchema, deviceAssertionSigningInput, DEVICE_ASSERTION_SCHEMA_ID } from '@byok-sdk/core';
|
|
8
8
|
import { promisify } from 'util';
|
|
9
9
|
import { fileURLToPath } from 'url';
|
|
10
10
|
import 'readline';
|
|
@@ -4683,6 +4683,13 @@ var ApprovalRegistry = class {
|
|
|
4683
4683
|
}
|
|
4684
4684
|
};
|
|
4685
4685
|
var MAX_DEVICE_RECORD_BYTES = 256 * 1024;
|
|
4686
|
+
var REPAIR_REQUIRED_MESSAGE = "device enrollment record is missing or has an invalid authenticated tenant binding; re-pair required";
|
|
4687
|
+
var DeviceRecordRePairRequiredError = class extends Error {
|
|
4688
|
+
constructor() {
|
|
4689
|
+
super(REPAIR_REQUIRED_MESSAGE);
|
|
4690
|
+
this.name = "DeviceRecordRePairRequiredError";
|
|
4691
|
+
}
|
|
4692
|
+
};
|
|
4686
4693
|
function sameInode(left, right) {
|
|
4687
4694
|
return left.dev === right.dev && left.ino === right.ino;
|
|
4688
4695
|
}
|
|
@@ -4692,18 +4699,32 @@ function sameFileState(left, right) {
|
|
|
4692
4699
|
function sameContentState(left, right) {
|
|
4693
4700
|
return sameInode(left, right) && left.size === right.size && left.mtimeNs === right.mtimeNs;
|
|
4694
4701
|
}
|
|
4702
|
+
function assertDeviceRecord(value) {
|
|
4703
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
4704
|
+
throw new DeviceRecordRePairRequiredError();
|
|
4705
|
+
}
|
|
4706
|
+
const parsed = value;
|
|
4707
|
+
if (typeof parsed.deviceId === "string" && isTenantId(parsed.tenantId) && typeof parsed.accessToken === "string" && typeof parsed.expiresAt === "string" && typeof parsed.devicePrivateKeyPem === "string" && typeof parsed.devicePublicKey === "string") {
|
|
4708
|
+
return;
|
|
4709
|
+
}
|
|
4710
|
+
throw new DeviceRecordRePairRequiredError();
|
|
4711
|
+
}
|
|
4695
4712
|
function parseDeviceRecord(raw) {
|
|
4696
|
-
|
|
4697
|
-
|
|
4698
|
-
|
|
4699
|
-
|
|
4700
|
-
|
|
4701
|
-
expiresAt: parsed.expiresAt,
|
|
4702
|
-
devicePrivateKeyPem: parsed.devicePrivateKeyPem,
|
|
4703
|
-
devicePublicKey: parsed.devicePublicKey
|
|
4704
|
-
};
|
|
4713
|
+
let parsed;
|
|
4714
|
+
try {
|
|
4715
|
+
parsed = JSON.parse(raw);
|
|
4716
|
+
} catch {
|
|
4717
|
+
throw new DeviceRecordRePairRequiredError();
|
|
4705
4718
|
}
|
|
4706
|
-
|
|
4719
|
+
assertDeviceRecord(parsed);
|
|
4720
|
+
return {
|
|
4721
|
+
deviceId: parsed.deviceId,
|
|
4722
|
+
tenantId: parsed.tenantId,
|
|
4723
|
+
accessToken: parsed.accessToken,
|
|
4724
|
+
expiresAt: parsed.expiresAt,
|
|
4725
|
+
devicePrivateKeyPem: parsed.devicePrivateKeyPem,
|
|
4726
|
+
devicePublicKey: parsed.devicePublicKey
|
|
4727
|
+
};
|
|
4707
4728
|
}
|
|
4708
4729
|
var DeviceStore = class _DeviceStore {
|
|
4709
4730
|
/**
|
|
@@ -4773,6 +4794,7 @@ var DeviceStore = class _DeviceStore {
|
|
|
4773
4794
|
}
|
|
4774
4795
|
}
|
|
4775
4796
|
async save(record) {
|
|
4797
|
+
assertDeviceRecord(record);
|
|
4776
4798
|
const storeDir = path.dirname(this.filePath);
|
|
4777
4799
|
await ensureSecureDir(storeDir, this.secureDirOptions);
|
|
4778
4800
|
await atomicWriteFile(this.filePath, JSON.stringify(record, null, 2), { mode: 384 });
|
|
@@ -4943,7 +4965,14 @@ var AuthManager = class {
|
|
|
4943
4965
|
this.proactiveTimer = void 0;
|
|
4944
4966
|
try {
|
|
4945
4967
|
return await this.runCredentialMutation(async () => {
|
|
4946
|
-
|
|
4968
|
+
let existing = this.record;
|
|
4969
|
+
if (!existing) {
|
|
4970
|
+
try {
|
|
4971
|
+
existing = await this.opts.store.load();
|
|
4972
|
+
} catch (error) {
|
|
4973
|
+
if (!(error instanceof DeviceRecordRePairRequiredError)) throw error;
|
|
4974
|
+
}
|
|
4975
|
+
}
|
|
4947
4976
|
const keyPair = existing ? { privateKey: importPrivateKeyPem(existing.devicePrivateKeyPem), publicKeyBase64Url: existing.devicePublicKey } : generateDeviceKeyPair();
|
|
4948
4977
|
const url = new URL(BYOK_PAIR_PATH, toHttpBase(this.opts.serverUrl));
|
|
4949
4978
|
const res = await fetch(url, {
|
|
@@ -4958,9 +4987,10 @@ var AuthManager = class {
|
|
|
4958
4987
|
if (!res.ok) {
|
|
4959
4988
|
throw new Error(`pairing failed: HTTP ${res.status} ${await safeErrorText(res)}`.trimEnd());
|
|
4960
4989
|
}
|
|
4961
|
-
const body = await res.json();
|
|
4990
|
+
const body = PairResponseSchema.parse(await res.json());
|
|
4962
4991
|
const record = {
|
|
4963
4992
|
deviceId: body.deviceId,
|
|
4993
|
+
tenantId: body.tenantId,
|
|
4964
4994
|
accessToken: body.accessToken,
|
|
4965
4995
|
expiresAt: resolvePairExpiry(body.refreshHint),
|
|
4966
4996
|
devicePrivateKeyPem: exportPrivateKeyPem(keyPair.privateKey),
|
|
@@ -12345,9 +12375,14 @@ var AgentEgressController = class {
|
|
|
12345
12375
|
latestStatus = emptyLane();
|
|
12346
12376
|
reliableStatus = emptyLane();
|
|
12347
12377
|
drops = [];
|
|
12378
|
+
active = true;
|
|
12348
12379
|
get policy() {
|
|
12349
12380
|
return this.options.policy;
|
|
12350
12381
|
}
|
|
12382
|
+
/** Permanently fail closed after its authenticated enrollment is replaced. */
|
|
12383
|
+
deactivate() {
|
|
12384
|
+
this.active = false;
|
|
12385
|
+
}
|
|
12351
12386
|
status() {
|
|
12352
12387
|
const reliable = this.reliableRecords();
|
|
12353
12388
|
return Object.freeze({
|
|
@@ -12365,6 +12400,10 @@ var AgentEgressController = class {
|
|
|
12365
12400
|
/** Project before TaskRunner builds a `task.progress` envelope. */
|
|
12366
12401
|
projectLatestValue(input) {
|
|
12367
12402
|
if (input.agentRef === void 0) return Object.freeze([...input.events]);
|
|
12403
|
+
if (!this.active) {
|
|
12404
|
+
this.noteDrop("latest-value", "policy_denied", input.agentRef);
|
|
12405
|
+
return [];
|
|
12406
|
+
}
|
|
12368
12407
|
if (this.options.policy.activity.mode === "contentful-trajectory" && !input.serverCapabilities.includes("agent-egress-policy")) {
|
|
12369
12408
|
this.noteDrop("latest-value", "capability_missing", input.agentRef);
|
|
12370
12409
|
return [];
|
|
@@ -12387,7 +12426,7 @@ var AgentEgressController = class {
|
|
|
12387
12426
|
return latest === void 0 ? [] : Object.freeze([latest]);
|
|
12388
12427
|
}
|
|
12389
12428
|
async appendReliable(input) {
|
|
12390
|
-
if (this.options.tenantId === void 0) {
|
|
12429
|
+
if (!this.active || this.options.tenantId === void 0) {
|
|
12391
12430
|
this.noteDrop("reliable", "policy_denied", input.agentRef);
|
|
12392
12431
|
return { ok: false, reason: "policy_denied" };
|
|
12393
12432
|
}
|
|
@@ -12425,7 +12464,7 @@ var AgentEgressController = class {
|
|
|
12425
12464
|
* with `wireType: agent.content.receipt` before any transport attempt.
|
|
12426
12465
|
*/
|
|
12427
12466
|
async appendContentReceipt(input) {
|
|
12428
|
-
if (this.options.tenantId === void 0) {
|
|
12467
|
+
if (!this.active || this.options.tenantId === void 0) {
|
|
12429
12468
|
this.noteDrop("reliable", "policy_denied", input.agentRef);
|
|
12430
12469
|
return { ok: false, reason: "policy_denied" };
|
|
12431
12470
|
}
|
|
@@ -12449,7 +12488,7 @@ var AgentEgressController = class {
|
|
|
12449
12488
|
/** Retires only the record whose full Agent/tenant/revision/id/cursor tuple matches. */
|
|
12450
12489
|
async acknowledge(ack) {
|
|
12451
12490
|
const spool = this.spools.get(agentKey(ack.agentRef));
|
|
12452
|
-
if (!spool || this.options.tenantId === void 0 || ack.tenantId !== this.options.tenantId) {
|
|
12491
|
+
if (!this.active || !spool || this.options.tenantId === void 0 || ack.tenantId !== this.options.tenantId) {
|
|
12453
12492
|
this.noteDrop("reliable", "ack_mismatch", ack.agentRef);
|
|
12454
12493
|
return false;
|
|
12455
12494
|
}
|
|
@@ -12460,6 +12499,7 @@ var AgentEgressController = class {
|
|
|
12460
12499
|
/** Re-open every existing Agent-local spool before retrying stable records after restart. */
|
|
12461
12500
|
async recover(agentsRoot) {
|
|
12462
12501
|
if (!path.isAbsolute(agentsRoot)) throw new Error("Agent egress recovery root must be absolute");
|
|
12502
|
+
if (!this.active) throw new Error("Agent egress recovery requires an active authenticated enrollment");
|
|
12463
12503
|
if (this.options.tenantId === void 0) {
|
|
12464
12504
|
throw new Error("Agent egress recovery requires one authenticated tenant authority");
|
|
12465
12505
|
}
|
|
@@ -13567,9 +13607,6 @@ function buildDaemonWithAdapters(config, adapters, overrides = {}, assertionProb
|
|
|
13567
13607
|
if (config.agentEgress !== void 0 && config.agentHome === void 0) {
|
|
13568
13608
|
throw new Error("DaemonConfig.agentEgress requires DaemonConfig.agentHome for the per-Agent local spool");
|
|
13569
13609
|
}
|
|
13570
|
-
if (config.agentEgress !== void 0 && (config.agentEgress.tenantId.length === 0 || config.agentEgress.tenantId.trim() !== config.agentEgress.tenantId)) {
|
|
13571
|
-
throw new Error("DaemonConfig.agentEgress.tenantId must be a non-empty canonical authenticated tenant id");
|
|
13572
|
-
}
|
|
13573
13610
|
const egressPolicy = resolveAgentEgressPolicy(config.agentEgress?.policy);
|
|
13574
13611
|
const egressBatcherOptions = egressPolicy.activity.mode === "contentful-trajectory" ? {
|
|
13575
13612
|
...config.progressBatch,
|
|
@@ -13600,9 +13637,8 @@ function buildDaemonWithAdapters(config, adapters, overrides = {}, assertionProb
|
|
|
13600
13637
|
});
|
|
13601
13638
|
const agentSessionHandoffs = config.agentHome === void 0 ? void 0 : new AgentSessionHandoffStore();
|
|
13602
13639
|
const agentContentReadPolicies = resolveContentReadPolicies(egressPolicy, config.agentEgress?.contentRead);
|
|
13603
|
-
|
|
13640
|
+
let agentEgress = new AgentEgressController({
|
|
13604
13641
|
policy: egressPolicy,
|
|
13605
|
-
...config.agentEgress?.tenantId === void 0 ? {} : { tenantId: config.agentEgress.tenantId },
|
|
13606
13642
|
...config.agentEgress?.sanitizer === void 0 ? {} : { sanitizer: config.agentEgress.sanitizer }
|
|
13607
13643
|
});
|
|
13608
13644
|
const gitWorkspaceManager = config.gitWorkspace ? overrides.gitWorkspace?.manager ?? new GitWorkspaceManager(config.workspaceRoot, { ownerId: stableGitWorkspaceOwnerId(storeDir, config.productId) }) : void 0;
|
|
@@ -13612,11 +13648,6 @@ function buildDaemonWithAdapters(config, adapters, overrides = {}, assertionProb
|
|
|
13612
13648
|
if (config.hostedJournal.mode !== "sqlite") {
|
|
13613
13649
|
throw new Error(`DaemonConfig.hostedJournal.mode must be "sqlite" \u2014 got ${JSON.stringify(config.hostedJournal.mode)}`);
|
|
13614
13650
|
}
|
|
13615
|
-
if (typeof config.hostedJournal.tenantId !== "string" || config.hostedJournal.tenantId.trim() === "") {
|
|
13616
|
-
throw new Error(
|
|
13617
|
-
"DaemonConfig.hostedJournal.tenantId must be a non-empty tenant id \u2014 a hosted journal row with no tenant is durable evidence nobody can act on"
|
|
13618
|
-
);
|
|
13619
|
-
}
|
|
13620
13651
|
if (config.hostedJournal.storagePolicy) {
|
|
13621
13652
|
resolvedStoragePolicy = resolveLocalStoragePolicy(config.hostedJournal.storagePolicy);
|
|
13622
13653
|
}
|
|
@@ -13685,6 +13716,8 @@ function buildDaemonWithAdapters(config, adapters, overrides = {}, assertionProb
|
|
|
13685
13716
|
const approvalRegistry = new ApprovalRegistry();
|
|
13686
13717
|
let connection;
|
|
13687
13718
|
let connectionState = "closed";
|
|
13719
|
+
let daemonStarted = false;
|
|
13720
|
+
let tenantRebinding = false;
|
|
13688
13721
|
let runner;
|
|
13689
13722
|
let controlServerHandle;
|
|
13690
13723
|
let daemonOwnerLease;
|
|
@@ -13737,11 +13770,31 @@ function buildDaemonWithAdapters(config, adapters, overrides = {}, assertionProb
|
|
|
13737
13770
|
return runLifecycleMutation(() => pairUnderLease(pairingCode));
|
|
13738
13771
|
}
|
|
13739
13772
|
async function pairUnderLease(pairingCode) {
|
|
13773
|
+
const wasRunning = daemonStarted;
|
|
13774
|
+
if (wasRunning) tenantRebinding = true;
|
|
13740
13775
|
const acquiredHere = daemonOwnerLease === void 0;
|
|
13741
13776
|
if (acquiredHere) daemonOwnerLease = await acquireDaemonOwner(storeDir, "daemon");
|
|
13777
|
+
let replacementPersisted = false;
|
|
13742
13778
|
try {
|
|
13743
|
-
|
|
13779
|
+
let previous;
|
|
13780
|
+
try {
|
|
13781
|
+
previous = await store.load();
|
|
13782
|
+
} catch (error) {
|
|
13783
|
+
if (!(error instanceof DeviceRecordRePairRequiredError)) throw error;
|
|
13784
|
+
}
|
|
13744
13785
|
const record = await auth.pair(pairingCode);
|
|
13786
|
+
replacementPersisted = true;
|
|
13787
|
+
if (config.agentEgress !== void 0) {
|
|
13788
|
+
agentEgress.deactivate();
|
|
13789
|
+
agentEgress = new AgentEgressController({
|
|
13790
|
+
policy: egressPolicy,
|
|
13791
|
+
...config.agentEgress.sanitizer === void 0 ? {} : { sanitizer: config.agentEgress.sanitizer }
|
|
13792
|
+
});
|
|
13793
|
+
}
|
|
13794
|
+
if (wasRunning) {
|
|
13795
|
+
await runShutdownSequence("re-pairing enrollment binding");
|
|
13796
|
+
tenantRebinding = false;
|
|
13797
|
+
}
|
|
13745
13798
|
if (previous && previous.deviceId !== record.deviceId) {
|
|
13746
13799
|
await cursorStore.clear(config.serverUrl, previous.deviceId);
|
|
13747
13800
|
}
|
|
@@ -13752,6 +13805,7 @@ function buildDaemonWithAdapters(config, adapters, overrides = {}, assertionProb
|
|
|
13752
13805
|
}
|
|
13753
13806
|
return record;
|
|
13754
13807
|
} catch (err) {
|
|
13808
|
+
if (wasRunning && !replacementPersisted) tenantRebinding = false;
|
|
13755
13809
|
if (acquiredHere) {
|
|
13756
13810
|
await daemonOwnerLease?.release();
|
|
13757
13811
|
daemonOwnerLease = void 0;
|
|
@@ -13774,6 +13828,13 @@ function buildDaemonWithAdapters(config, adapters, overrides = {}, assertionProb
|
|
|
13774
13828
|
if (!record) {
|
|
13775
13829
|
throw new Error("device is not paired yet; call pair(pairingCode) first");
|
|
13776
13830
|
}
|
|
13831
|
+
if (config.agentEgress !== void 0) {
|
|
13832
|
+
agentEgress = new AgentEgressController({
|
|
13833
|
+
policy: egressPolicy,
|
|
13834
|
+
tenantId: record.tenantId,
|
|
13835
|
+
...config.agentEgress.sanitizer === void 0 ? {} : { sanitizer: config.agentEgress.sanitizer }
|
|
13836
|
+
});
|
|
13837
|
+
}
|
|
13777
13838
|
await agentHomeManager?.preflight();
|
|
13778
13839
|
if (config.agentEgress !== void 0 && config.agentHome !== void 0) {
|
|
13779
13840
|
await agentEgress.recover(path.join(config.agentHome.hostStorageRoot, "agents"));
|
|
@@ -13834,7 +13895,7 @@ function buildDaemonWithAdapters(config, adapters, overrides = {}, assertionProb
|
|
|
13834
13895
|
config.agentEgress !== void 0,
|
|
13835
13896
|
agentContentReadPolicies
|
|
13836
13897
|
);
|
|
13837
|
-
const journalIdentity = config.hostedJournal ? { tenantId:
|
|
13898
|
+
const journalIdentity = config.hostedJournal ? { tenantId: record.tenantId, productId: config.productId, deviceId: record.deviceId } : void 0;
|
|
13838
13899
|
const sendSanitizedEnvelope = activeJournal && journalIdentity ? (envelope) => {
|
|
13839
13900
|
observer.handleOutboundEnvelope(envelope);
|
|
13840
13901
|
const terminalKind = terminalKindOf(envelope.type);
|
|
@@ -13972,9 +14033,8 @@ function buildDaemonWithAdapters(config, adapters, overrides = {}, assertionProb
|
|
|
13972
14033
|
runner = new TaskRunner(deps);
|
|
13973
14034
|
const handleAgentEgressEnvelope = async (envelope) => {
|
|
13974
14035
|
if (envelope.type !== "agent.egress.ack") return false;
|
|
13975
|
-
|
|
13976
|
-
|
|
13977
|
-
await agentEgress.acknowledge({ ...envelope.payload, tenantId: tenantId2 });
|
|
14036
|
+
if (config.agentEgress === void 0) return true;
|
|
14037
|
+
await agentEgress.acknowledge({ ...envelope.payload, tenantId: record.tenantId });
|
|
13978
14038
|
return true;
|
|
13979
14039
|
};
|
|
13980
14040
|
const handleAgentContentReadEnvelope = async (envelope) => {
|
|
@@ -14026,7 +14086,7 @@ function buildDaemonWithAdapters(config, adapters, overrides = {}, assertionProb
|
|
|
14026
14086
|
const result = await policyEngine.read({
|
|
14027
14087
|
requestId: payload.requestId,
|
|
14028
14088
|
actor: payload.actor,
|
|
14029
|
-
tenantId:
|
|
14089
|
+
tenantId: record.tenantId,
|
|
14030
14090
|
deviceId: record.deviceId,
|
|
14031
14091
|
agentRef: payload.agentRef,
|
|
14032
14092
|
surface: payload.surface,
|
|
@@ -14122,6 +14182,9 @@ function buildDaemonWithAdapters(config, adapters, overrides = {}, assertionProb
|
|
|
14122
14182
|
//
|
|
14123
14183
|
// The no-journal branch is the ORIGINAL closure, unchanged.
|
|
14124
14184
|
onEnvelope: activeJournal && journalIdentity ? async (envelope) => {
|
|
14185
|
+
if (tenantRebinding) {
|
|
14186
|
+
throw new Error("tenant enrollment is being re-paired; inbound work is blocked until restart");
|
|
14187
|
+
}
|
|
14125
14188
|
observer.handleInboundEnvelope(envelope);
|
|
14126
14189
|
if (await handleAgentEgressEnvelope(envelope)) return;
|
|
14127
14190
|
if (await handleAgentContentReadEnvelope(envelope)) return;
|
|
@@ -14129,6 +14192,9 @@ function buildDaemonWithAdapters(config, adapters, overrides = {}, assertionProb
|
|
|
14129
14192
|
await activeJournal.appendEnvelope(toJournalEnvelopeRecord(envelope, journalIdentity));
|
|
14130
14193
|
return runner?.handleEnvelope(envelope) ?? Promise.resolve();
|
|
14131
14194
|
} : (envelope) => {
|
|
14195
|
+
if (tenantRebinding) {
|
|
14196
|
+
return Promise.reject(new Error("tenant enrollment is being re-paired; inbound work is blocked until restart"));
|
|
14197
|
+
}
|
|
14132
14198
|
observer.handleInboundEnvelope(envelope);
|
|
14133
14199
|
if (envelope.type === "agent.egress.ack") return handleAgentEgressEnvelope(envelope).then(() => void 0);
|
|
14134
14200
|
if (envelope.type === "agent.content.read") return handleAgentContentReadEnvelope(envelope).then(() => void 0);
|
|
@@ -14160,6 +14226,7 @@ function buildDaemonWithAdapters(config, adapters, overrides = {}, assertionProb
|
|
|
14160
14226
|
dispatchReliableRecord(record2);
|
|
14161
14227
|
}
|
|
14162
14228
|
startPresenceProducer();
|
|
14229
|
+
daemonStarted = true;
|
|
14163
14230
|
} catch (err) {
|
|
14164
14231
|
try {
|
|
14165
14232
|
await runShutdownSequence("startup failed", { drainTimeoutMs: 0 });
|
|
@@ -14304,6 +14371,7 @@ function buildDaemonWithAdapters(config, adapters, overrides = {}, assertionProb
|
|
|
14304
14371
|
if (!mutationBarrierComplete) {
|
|
14305
14372
|
throw new Error("daemon shutdown mutation barrier is incomplete; ownership lease retained");
|
|
14306
14373
|
}
|
|
14374
|
+
daemonStarted = false;
|
|
14307
14375
|
}
|
|
14308
14376
|
async function stop(opts = {}) {
|
|
14309
14377
|
shuttingDown = true;
|
|
@@ -14625,6 +14693,9 @@ function buildDaemonWithAdapters(config, adapters, overrides = {}, assertionProb
|
|
|
14625
14693
|
if (config.agentEgress === void 0 || agentHomeManager === void 0) {
|
|
14626
14694
|
throw new Error("Agent reliable egress is not configured");
|
|
14627
14695
|
}
|
|
14696
|
+
if (tenantRebinding) {
|
|
14697
|
+
throw new Error("tenant enrollment is being re-paired; reliable egress is blocked until restart");
|
|
14698
|
+
}
|
|
14628
14699
|
const binding = await agentHomeManager.acquire(input.agentRef);
|
|
14629
14700
|
try {
|
|
14630
14701
|
await agentHomeManager.initialize(binding);
|