@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
package/README.md
CHANGED
|
@@ -122,17 +122,19 @@ contents.
|
|
|
122
122
|
|
|
123
123
|
## Agent egress and explicit content reads
|
|
124
124
|
|
|
125
|
-
`agentEgress` is consumed configuration, not a profile
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
125
|
+
`agentEgress` is consumed policy configuration, not a profile or tenant
|
|
126
|
+
projection. The host selects one exact policy revision. The daemon obtains its
|
|
127
|
+
tenant binding only from the authenticated pair response persisted in the
|
|
128
|
+
atomic local `DeviceRecord`; there is no `agentEgress.tenantId` setting and no
|
|
129
|
+
Profile/config, deviceId, or access-token fallback. Omitting contentful mode
|
|
130
|
+
keeps runtime activity metadata/status-only; enabling it is an explicit product
|
|
131
|
+
decision and requires the server capability. Reliable events are fsynced under
|
|
132
|
+
the canonical Agent home and retire only after an exact ack.
|
|
130
133
|
|
|
131
134
|
```ts
|
|
132
135
|
createDaemon({
|
|
133
136
|
// ...normal device, transport and agentHome configuration
|
|
134
137
|
agentEgress: {
|
|
135
|
-
tenantId: 'tenant-authority-from-salesko',
|
|
136
138
|
policy: {
|
|
137
139
|
policyRevision: 'salesko-agent-egress-r1',
|
|
138
140
|
activity: { mode: 'metadata-status', delivery: 'latest-value' },
|
|
@@ -163,8 +165,9 @@ its local supplement. The local supplement can only narrow root, text, MIME,
|
|
|
163
165
|
size and sensitive-name behavior; it cannot enable a wire-disabled surface.
|
|
164
166
|
The SDK derives `agents/<agentId>`, `.byok/egress`, runtime-session evidence and
|
|
165
167
|
the per-Agent content-read audit path. Salesko must not compose those paths.
|
|
166
|
-
Tenant/device identity comes from authenticated
|
|
167
|
-
override it. Transcript reads
|
|
168
|
+
Tenant/device identity comes from the persisted authenticated enrollment; a
|
|
169
|
+
request or editable host configuration cannot override it. Transcript reads
|
|
170
|
+
additionally require the exact persisted
|
|
168
171
|
AgentRef/session/runtime/cwd handoff. Allowed content is uploaded through the
|
|
169
172
|
authenticated blob channel. The content-free receipt is fsynced into the
|
|
170
173
|
Agent-local reliable spool with stable event/cursor identity before send and
|
package/dist/bin/byok-agent.js
CHANGED
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
import { randomUUID, createHash, randomBytes, timingSafeEqual, createHmac, createPrivateKey, generateKeyPairSync, sign } from 'crypto';
|
|
3
3
|
import { readFileSync, promises, linkSync, fstatSync, lstatSync, unlinkSync, constants, readSync, openSync, writeFileSync, fchmodSync, fsyncSync, closeSync, opendirSync, existsSync, realpathSync, mkdirSync, renameSync, chmodSync, statSync, readdirSync } from 'fs';
|
|
4
4
|
import path, { isAbsolute, join } from 'path';
|
|
5
|
-
import { AGENT_CONTENT_ARTIFACT_READ_CAPABILITY, AGENT_CONTENT_TRANSCRIPT_READ_CAPABILITY, AGENT_CONTENT_WORKSPACE_READ_CAPABILITY, TASK_STATES, AgentEgressPolicySchema, CONFIGURED_TOOLSETS_MAX_ITEMS, ToolsetIdSchema, AgentRefSchema, AgentContentReceiptPayloadSchema, BYOK_PAIR_PATH, BYOK_CHALLENGE_PATH, BYOK_TOKEN_PATH, partitionAgentEvents, TASK_TRANSITIONS, encodeEnvelope, PROTOCOL_VERSION, createEnvelope, AGENT_EGRESS_RELIABLE_ACK_CAPABILITY, byokBlobUrlPath, BYOK_BLOBS_PATH, byokBlobFinalizePath, 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
|
+
import { AGENT_CONTENT_ARTIFACT_READ_CAPABILITY, AGENT_CONTENT_TRANSCRIPT_READ_CAPABILITY, AGENT_CONTENT_WORKSPACE_READ_CAPABILITY, TASK_STATES, AgentEgressPolicySchema, CONFIGURED_TOOLSETS_MAX_ITEMS, ToolsetIdSchema, AgentRefSchema, AgentContentReceiptPayloadSchema, BYOK_PAIR_PATH, PairResponseSchema, BYOK_CHALLENGE_PATH, BYOK_TOKEN_PATH, partitionAgentEvents, TASK_TRANSITIONS, encodeEnvelope, PROTOCOL_VERSION, createEnvelope, AGENT_EGRESS_RELIABLE_ACK_CAPABILITY, byokBlobUrlPath, BYOK_BLOBS_PATH, byokBlobFinalizePath, 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';
|
|
6
6
|
import { execFile, spawn } from 'child_process';
|
|
7
7
|
import os from 'os';
|
|
8
|
-
import { DEVICE_ASSERTION_AUDIENCE_MAX_BYTES, DEVICE_ASSERTION_DEFAULT_TTL_MS, DEVICE_ASSERTION_MAX_TTL_MS, nonceSigningBytes, CapabilityDeclarationSchema, hasCapability, DeviceAssertionClaimsSchema, deviceAssertionSigningInput, DEVICE_ASSERTION_SCHEMA_ID } from '@byok-sdk/core';
|
|
8
|
+
import { isTenantId, DEVICE_ASSERTION_AUDIENCE_MAX_BYTES, DEVICE_ASSERTION_DEFAULT_TTL_MS, DEVICE_ASSERTION_MAX_TTL_MS, nonceSigningBytes, CapabilityDeclarationSchema, hasCapability, DeviceAssertionClaimsSchema, deviceAssertionSigningInput, DEVICE_ASSERTION_SCHEMA_ID } from '@byok-sdk/core';
|
|
9
9
|
import { promisify } from 'util';
|
|
10
10
|
import { fileURLToPath } from 'url';
|
|
11
11
|
import 'readline';
|
|
@@ -4732,6 +4732,13 @@ var ApprovalRegistry = class {
|
|
|
4732
4732
|
}
|
|
4733
4733
|
};
|
|
4734
4734
|
var MAX_DEVICE_RECORD_BYTES = 256 * 1024;
|
|
4735
|
+
var REPAIR_REQUIRED_MESSAGE = "device enrollment record is missing or has an invalid authenticated tenant binding; re-pair required";
|
|
4736
|
+
var DeviceRecordRePairRequiredError = class extends Error {
|
|
4737
|
+
constructor() {
|
|
4738
|
+
super(REPAIR_REQUIRED_MESSAGE);
|
|
4739
|
+
this.name = "DeviceRecordRePairRequiredError";
|
|
4740
|
+
}
|
|
4741
|
+
};
|
|
4735
4742
|
function sameInode(left, right) {
|
|
4736
4743
|
return left.dev === right.dev && left.ino === right.ino;
|
|
4737
4744
|
}
|
|
@@ -4741,18 +4748,32 @@ function sameFileState(left, right) {
|
|
|
4741
4748
|
function sameContentState(left, right) {
|
|
4742
4749
|
return sameInode(left, right) && left.size === right.size && left.mtimeNs === right.mtimeNs;
|
|
4743
4750
|
}
|
|
4751
|
+
function assertDeviceRecord(value) {
|
|
4752
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
4753
|
+
throw new DeviceRecordRePairRequiredError();
|
|
4754
|
+
}
|
|
4755
|
+
const parsed = value;
|
|
4756
|
+
if (typeof parsed.deviceId === "string" && isTenantId(parsed.tenantId) && typeof parsed.accessToken === "string" && typeof parsed.expiresAt === "string" && typeof parsed.devicePrivateKeyPem === "string" && typeof parsed.devicePublicKey === "string") {
|
|
4757
|
+
return;
|
|
4758
|
+
}
|
|
4759
|
+
throw new DeviceRecordRePairRequiredError();
|
|
4760
|
+
}
|
|
4744
4761
|
function parseDeviceRecord(raw) {
|
|
4745
|
-
|
|
4746
|
-
|
|
4747
|
-
|
|
4748
|
-
|
|
4749
|
-
|
|
4750
|
-
expiresAt: parsed.expiresAt,
|
|
4751
|
-
devicePrivateKeyPem: parsed.devicePrivateKeyPem,
|
|
4752
|
-
devicePublicKey: parsed.devicePublicKey
|
|
4753
|
-
};
|
|
4762
|
+
let parsed;
|
|
4763
|
+
try {
|
|
4764
|
+
parsed = JSON.parse(raw);
|
|
4765
|
+
} catch {
|
|
4766
|
+
throw new DeviceRecordRePairRequiredError();
|
|
4754
4767
|
}
|
|
4755
|
-
|
|
4768
|
+
assertDeviceRecord(parsed);
|
|
4769
|
+
return {
|
|
4770
|
+
deviceId: parsed.deviceId,
|
|
4771
|
+
tenantId: parsed.tenantId,
|
|
4772
|
+
accessToken: parsed.accessToken,
|
|
4773
|
+
expiresAt: parsed.expiresAt,
|
|
4774
|
+
devicePrivateKeyPem: parsed.devicePrivateKeyPem,
|
|
4775
|
+
devicePublicKey: parsed.devicePublicKey
|
|
4776
|
+
};
|
|
4756
4777
|
}
|
|
4757
4778
|
var DeviceStore = class _DeviceStore {
|
|
4758
4779
|
/**
|
|
@@ -4822,6 +4843,7 @@ var DeviceStore = class _DeviceStore {
|
|
|
4822
4843
|
}
|
|
4823
4844
|
}
|
|
4824
4845
|
async save(record) {
|
|
4846
|
+
assertDeviceRecord(record);
|
|
4825
4847
|
const storeDir = path.dirname(this.filePath);
|
|
4826
4848
|
await ensureSecureDir(storeDir, this.secureDirOptions);
|
|
4827
4849
|
await atomicWriteFile(this.filePath, JSON.stringify(record, null, 2), { mode: 384 });
|
|
@@ -4992,7 +5014,14 @@ var AuthManager = class {
|
|
|
4992
5014
|
this.proactiveTimer = void 0;
|
|
4993
5015
|
try {
|
|
4994
5016
|
return await this.runCredentialMutation(async () => {
|
|
4995
|
-
|
|
5017
|
+
let existing = this.record;
|
|
5018
|
+
if (!existing) {
|
|
5019
|
+
try {
|
|
5020
|
+
existing = await this.opts.store.load();
|
|
5021
|
+
} catch (error) {
|
|
5022
|
+
if (!(error instanceof DeviceRecordRePairRequiredError)) throw error;
|
|
5023
|
+
}
|
|
5024
|
+
}
|
|
4996
5025
|
const keyPair = existing ? { privateKey: importPrivateKeyPem(existing.devicePrivateKeyPem), publicKeyBase64Url: existing.devicePublicKey } : generateDeviceKeyPair();
|
|
4997
5026
|
const url = new URL(BYOK_PAIR_PATH, toHttpBase(this.opts.serverUrl));
|
|
4998
5027
|
const res = await fetch(url, {
|
|
@@ -5007,9 +5036,10 @@ var AuthManager = class {
|
|
|
5007
5036
|
if (!res.ok) {
|
|
5008
5037
|
throw new Error(`pairing failed: HTTP ${res.status} ${await safeErrorText(res)}`.trimEnd());
|
|
5009
5038
|
}
|
|
5010
|
-
const body = await res.json();
|
|
5039
|
+
const body = PairResponseSchema.parse(await res.json());
|
|
5011
5040
|
const record = {
|
|
5012
5041
|
deviceId: body.deviceId,
|
|
5042
|
+
tenantId: body.tenantId,
|
|
5013
5043
|
accessToken: body.accessToken,
|
|
5014
5044
|
expiresAt: resolvePairExpiry(body.refreshHint),
|
|
5015
5045
|
devicePrivateKeyPem: exportPrivateKeyPem(keyPair.privateKey),
|
|
@@ -12428,9 +12458,14 @@ var AgentEgressController = class {
|
|
|
12428
12458
|
latestStatus = emptyLane();
|
|
12429
12459
|
reliableStatus = emptyLane();
|
|
12430
12460
|
drops = [];
|
|
12461
|
+
active = true;
|
|
12431
12462
|
get policy() {
|
|
12432
12463
|
return this.options.policy;
|
|
12433
12464
|
}
|
|
12465
|
+
/** Permanently fail closed after its authenticated enrollment is replaced. */
|
|
12466
|
+
deactivate() {
|
|
12467
|
+
this.active = false;
|
|
12468
|
+
}
|
|
12434
12469
|
status() {
|
|
12435
12470
|
const reliable = this.reliableRecords();
|
|
12436
12471
|
return Object.freeze({
|
|
@@ -12448,6 +12483,10 @@ var AgentEgressController = class {
|
|
|
12448
12483
|
/** Project before TaskRunner builds a `task.progress` envelope. */
|
|
12449
12484
|
projectLatestValue(input) {
|
|
12450
12485
|
if (input.agentRef === void 0) return Object.freeze([...input.events]);
|
|
12486
|
+
if (!this.active) {
|
|
12487
|
+
this.noteDrop("latest-value", "policy_denied", input.agentRef);
|
|
12488
|
+
return [];
|
|
12489
|
+
}
|
|
12451
12490
|
if (this.options.policy.activity.mode === "contentful-trajectory" && !input.serverCapabilities.includes("agent-egress-policy")) {
|
|
12452
12491
|
this.noteDrop("latest-value", "capability_missing", input.agentRef);
|
|
12453
12492
|
return [];
|
|
@@ -12470,7 +12509,7 @@ var AgentEgressController = class {
|
|
|
12470
12509
|
return latest === void 0 ? [] : Object.freeze([latest]);
|
|
12471
12510
|
}
|
|
12472
12511
|
async appendReliable(input) {
|
|
12473
|
-
if (this.options.tenantId === void 0) {
|
|
12512
|
+
if (!this.active || this.options.tenantId === void 0) {
|
|
12474
12513
|
this.noteDrop("reliable", "policy_denied", input.agentRef);
|
|
12475
12514
|
return { ok: false, reason: "policy_denied" };
|
|
12476
12515
|
}
|
|
@@ -12508,7 +12547,7 @@ var AgentEgressController = class {
|
|
|
12508
12547
|
* with `wireType: agent.content.receipt` before any transport attempt.
|
|
12509
12548
|
*/
|
|
12510
12549
|
async appendContentReceipt(input) {
|
|
12511
|
-
if (this.options.tenantId === void 0) {
|
|
12550
|
+
if (!this.active || this.options.tenantId === void 0) {
|
|
12512
12551
|
this.noteDrop("reliable", "policy_denied", input.agentRef);
|
|
12513
12552
|
return { ok: false, reason: "policy_denied" };
|
|
12514
12553
|
}
|
|
@@ -12532,7 +12571,7 @@ var AgentEgressController = class {
|
|
|
12532
12571
|
/** Retires only the record whose full Agent/tenant/revision/id/cursor tuple matches. */
|
|
12533
12572
|
async acknowledge(ack) {
|
|
12534
12573
|
const spool = this.spools.get(agentKey(ack.agentRef));
|
|
12535
|
-
if (!spool || this.options.tenantId === void 0 || ack.tenantId !== this.options.tenantId) {
|
|
12574
|
+
if (!this.active || !spool || this.options.tenantId === void 0 || ack.tenantId !== this.options.tenantId) {
|
|
12536
12575
|
this.noteDrop("reliable", "ack_mismatch", ack.agentRef);
|
|
12537
12576
|
return false;
|
|
12538
12577
|
}
|
|
@@ -12543,6 +12582,7 @@ var AgentEgressController = class {
|
|
|
12543
12582
|
/** Re-open every existing Agent-local spool before retrying stable records after restart. */
|
|
12544
12583
|
async recover(agentsRoot) {
|
|
12545
12584
|
if (!path.isAbsolute(agentsRoot)) throw new Error("Agent egress recovery root must be absolute");
|
|
12585
|
+
if (!this.active) throw new Error("Agent egress recovery requires an active authenticated enrollment");
|
|
12546
12586
|
if (this.options.tenantId === void 0) {
|
|
12547
12587
|
throw new Error("Agent egress recovery requires one authenticated tenant authority");
|
|
12548
12588
|
}
|
|
@@ -13650,9 +13690,6 @@ function buildDaemonWithAdapters(config, adapters, overrides = {}, assertionProb
|
|
|
13650
13690
|
if (config.agentEgress !== void 0 && config.agentHome === void 0) {
|
|
13651
13691
|
throw new Error("DaemonConfig.agentEgress requires DaemonConfig.agentHome for the per-Agent local spool");
|
|
13652
13692
|
}
|
|
13653
|
-
if (config.agentEgress !== void 0 && (config.agentEgress.tenantId.length === 0 || config.agentEgress.tenantId.trim() !== config.agentEgress.tenantId)) {
|
|
13654
|
-
throw new Error("DaemonConfig.agentEgress.tenantId must be a non-empty canonical authenticated tenant id");
|
|
13655
|
-
}
|
|
13656
13693
|
const egressPolicy = resolveAgentEgressPolicy(config.agentEgress?.policy);
|
|
13657
13694
|
const egressBatcherOptions = egressPolicy.activity.mode === "contentful-trajectory" ? {
|
|
13658
13695
|
...config.progressBatch,
|
|
@@ -13683,9 +13720,8 @@ function buildDaemonWithAdapters(config, adapters, overrides = {}, assertionProb
|
|
|
13683
13720
|
});
|
|
13684
13721
|
const agentSessionHandoffs = config.agentHome === void 0 ? void 0 : new AgentSessionHandoffStore();
|
|
13685
13722
|
const agentContentReadPolicies = resolveContentReadPolicies(egressPolicy, config.agentEgress?.contentRead);
|
|
13686
|
-
|
|
13723
|
+
let agentEgress = new AgentEgressController({
|
|
13687
13724
|
policy: egressPolicy,
|
|
13688
|
-
...config.agentEgress?.tenantId === void 0 ? {} : { tenantId: config.agentEgress.tenantId },
|
|
13689
13725
|
...config.agentEgress?.sanitizer === void 0 ? {} : { sanitizer: config.agentEgress.sanitizer }
|
|
13690
13726
|
});
|
|
13691
13727
|
const gitWorkspaceManager = config.gitWorkspace ? overrides.gitWorkspace?.manager ?? new GitWorkspaceManager(config.workspaceRoot, { ownerId: stableGitWorkspaceOwnerId(storeDir, config.productId) }) : void 0;
|
|
@@ -13695,11 +13731,6 @@ function buildDaemonWithAdapters(config, adapters, overrides = {}, assertionProb
|
|
|
13695
13731
|
if (config.hostedJournal.mode !== "sqlite") {
|
|
13696
13732
|
throw new Error(`DaemonConfig.hostedJournal.mode must be "sqlite" \u2014 got ${JSON.stringify(config.hostedJournal.mode)}`);
|
|
13697
13733
|
}
|
|
13698
|
-
if (typeof config.hostedJournal.tenantId !== "string" || config.hostedJournal.tenantId.trim() === "") {
|
|
13699
|
-
throw new Error(
|
|
13700
|
-
"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"
|
|
13701
|
-
);
|
|
13702
|
-
}
|
|
13703
13734
|
if (config.hostedJournal.storagePolicy) {
|
|
13704
13735
|
resolvedStoragePolicy = resolveLocalStoragePolicy(config.hostedJournal.storagePolicy);
|
|
13705
13736
|
}
|
|
@@ -13768,6 +13799,8 @@ function buildDaemonWithAdapters(config, adapters, overrides = {}, assertionProb
|
|
|
13768
13799
|
const approvalRegistry = new ApprovalRegistry();
|
|
13769
13800
|
let connection;
|
|
13770
13801
|
let connectionState = "closed";
|
|
13802
|
+
let daemonStarted = false;
|
|
13803
|
+
let tenantRebinding = false;
|
|
13771
13804
|
let runner;
|
|
13772
13805
|
let controlServerHandle;
|
|
13773
13806
|
let daemonOwnerLease;
|
|
@@ -13820,11 +13853,31 @@ function buildDaemonWithAdapters(config, adapters, overrides = {}, assertionProb
|
|
|
13820
13853
|
return runLifecycleMutation(() => pairUnderLease(pairingCode));
|
|
13821
13854
|
}
|
|
13822
13855
|
async function pairUnderLease(pairingCode) {
|
|
13856
|
+
const wasRunning = daemonStarted;
|
|
13857
|
+
if (wasRunning) tenantRebinding = true;
|
|
13823
13858
|
const acquiredHere = daemonOwnerLease === void 0;
|
|
13824
13859
|
if (acquiredHere) daemonOwnerLease = await acquireDaemonOwner(storeDir, "daemon");
|
|
13860
|
+
let replacementPersisted = false;
|
|
13825
13861
|
try {
|
|
13826
|
-
|
|
13862
|
+
let previous;
|
|
13863
|
+
try {
|
|
13864
|
+
previous = await store.load();
|
|
13865
|
+
} catch (error) {
|
|
13866
|
+
if (!(error instanceof DeviceRecordRePairRequiredError)) throw error;
|
|
13867
|
+
}
|
|
13827
13868
|
const record = await auth.pair(pairingCode);
|
|
13869
|
+
replacementPersisted = true;
|
|
13870
|
+
if (config.agentEgress !== void 0) {
|
|
13871
|
+
agentEgress.deactivate();
|
|
13872
|
+
agentEgress = new AgentEgressController({
|
|
13873
|
+
policy: egressPolicy,
|
|
13874
|
+
...config.agentEgress.sanitizer === void 0 ? {} : { sanitizer: config.agentEgress.sanitizer }
|
|
13875
|
+
});
|
|
13876
|
+
}
|
|
13877
|
+
if (wasRunning) {
|
|
13878
|
+
await runShutdownSequence("re-pairing enrollment binding");
|
|
13879
|
+
tenantRebinding = false;
|
|
13880
|
+
}
|
|
13828
13881
|
if (previous && previous.deviceId !== record.deviceId) {
|
|
13829
13882
|
await cursorStore.clear(config.serverUrl, previous.deviceId);
|
|
13830
13883
|
}
|
|
@@ -13835,6 +13888,7 @@ function buildDaemonWithAdapters(config, adapters, overrides = {}, assertionProb
|
|
|
13835
13888
|
}
|
|
13836
13889
|
return record;
|
|
13837
13890
|
} catch (err) {
|
|
13891
|
+
if (wasRunning && !replacementPersisted) tenantRebinding = false;
|
|
13838
13892
|
if (acquiredHere) {
|
|
13839
13893
|
await daemonOwnerLease?.release();
|
|
13840
13894
|
daemonOwnerLease = void 0;
|
|
@@ -13857,6 +13911,13 @@ function buildDaemonWithAdapters(config, adapters, overrides = {}, assertionProb
|
|
|
13857
13911
|
if (!record) {
|
|
13858
13912
|
throw new Error("device is not paired yet; call pair(pairingCode) first");
|
|
13859
13913
|
}
|
|
13914
|
+
if (config.agentEgress !== void 0) {
|
|
13915
|
+
agentEgress = new AgentEgressController({
|
|
13916
|
+
policy: egressPolicy,
|
|
13917
|
+
tenantId: record.tenantId,
|
|
13918
|
+
...config.agentEgress.sanitizer === void 0 ? {} : { sanitizer: config.agentEgress.sanitizer }
|
|
13919
|
+
});
|
|
13920
|
+
}
|
|
13860
13921
|
await agentHomeManager?.preflight();
|
|
13861
13922
|
if (config.agentEgress !== void 0 && config.agentHome !== void 0) {
|
|
13862
13923
|
await agentEgress.recover(path.join(config.agentHome.hostStorageRoot, "agents"));
|
|
@@ -13917,7 +13978,7 @@ function buildDaemonWithAdapters(config, adapters, overrides = {}, assertionProb
|
|
|
13917
13978
|
config.agentEgress !== void 0,
|
|
13918
13979
|
agentContentReadPolicies
|
|
13919
13980
|
);
|
|
13920
|
-
const journalIdentity = config.hostedJournal ? { tenantId:
|
|
13981
|
+
const journalIdentity = config.hostedJournal ? { tenantId: record.tenantId, productId: config.productId, deviceId: record.deviceId } : void 0;
|
|
13921
13982
|
const sendSanitizedEnvelope = activeJournal && journalIdentity ? (envelope) => {
|
|
13922
13983
|
observer.handleOutboundEnvelope(envelope);
|
|
13923
13984
|
const terminalKind = terminalKindOf(envelope.type);
|
|
@@ -14055,9 +14116,8 @@ function buildDaemonWithAdapters(config, adapters, overrides = {}, assertionProb
|
|
|
14055
14116
|
runner = new TaskRunner(deps);
|
|
14056
14117
|
const handleAgentEgressEnvelope = async (envelope) => {
|
|
14057
14118
|
if (envelope.type !== "agent.egress.ack") return false;
|
|
14058
|
-
|
|
14059
|
-
|
|
14060
|
-
await agentEgress.acknowledge({ ...envelope.payload, tenantId });
|
|
14119
|
+
if (config.agentEgress === void 0) return true;
|
|
14120
|
+
await agentEgress.acknowledge({ ...envelope.payload, tenantId: record.tenantId });
|
|
14061
14121
|
return true;
|
|
14062
14122
|
};
|
|
14063
14123
|
const handleAgentContentReadEnvelope = async (envelope) => {
|
|
@@ -14109,7 +14169,7 @@ function buildDaemonWithAdapters(config, adapters, overrides = {}, assertionProb
|
|
|
14109
14169
|
const result = await policyEngine.read({
|
|
14110
14170
|
requestId: payload.requestId,
|
|
14111
14171
|
actor: payload.actor,
|
|
14112
|
-
tenantId:
|
|
14172
|
+
tenantId: record.tenantId,
|
|
14113
14173
|
deviceId: record.deviceId,
|
|
14114
14174
|
agentRef: payload.agentRef,
|
|
14115
14175
|
surface: payload.surface,
|
|
@@ -14205,6 +14265,9 @@ function buildDaemonWithAdapters(config, adapters, overrides = {}, assertionProb
|
|
|
14205
14265
|
//
|
|
14206
14266
|
// The no-journal branch is the ORIGINAL closure, unchanged.
|
|
14207
14267
|
onEnvelope: activeJournal && journalIdentity ? async (envelope) => {
|
|
14268
|
+
if (tenantRebinding) {
|
|
14269
|
+
throw new Error("tenant enrollment is being re-paired; inbound work is blocked until restart");
|
|
14270
|
+
}
|
|
14208
14271
|
observer.handleInboundEnvelope(envelope);
|
|
14209
14272
|
if (await handleAgentEgressEnvelope(envelope)) return;
|
|
14210
14273
|
if (await handleAgentContentReadEnvelope(envelope)) return;
|
|
@@ -14212,6 +14275,9 @@ function buildDaemonWithAdapters(config, adapters, overrides = {}, assertionProb
|
|
|
14212
14275
|
await activeJournal.appendEnvelope(toJournalEnvelopeRecord(envelope, journalIdentity));
|
|
14213
14276
|
return runner?.handleEnvelope(envelope) ?? Promise.resolve();
|
|
14214
14277
|
} : (envelope) => {
|
|
14278
|
+
if (tenantRebinding) {
|
|
14279
|
+
return Promise.reject(new Error("tenant enrollment is being re-paired; inbound work is blocked until restart"));
|
|
14280
|
+
}
|
|
14215
14281
|
observer.handleInboundEnvelope(envelope);
|
|
14216
14282
|
if (envelope.type === "agent.egress.ack") return handleAgentEgressEnvelope(envelope).then(() => void 0);
|
|
14217
14283
|
if (envelope.type === "agent.content.read") return handleAgentContentReadEnvelope(envelope).then(() => void 0);
|
|
@@ -14243,6 +14309,7 @@ function buildDaemonWithAdapters(config, adapters, overrides = {}, assertionProb
|
|
|
14243
14309
|
dispatchReliableRecord(record2);
|
|
14244
14310
|
}
|
|
14245
14311
|
startPresenceProducer();
|
|
14312
|
+
daemonStarted = true;
|
|
14246
14313
|
} catch (err) {
|
|
14247
14314
|
try {
|
|
14248
14315
|
await runShutdownSequence("startup failed", { drainTimeoutMs: 0 });
|
|
@@ -14387,6 +14454,7 @@ function buildDaemonWithAdapters(config, adapters, overrides = {}, assertionProb
|
|
|
14387
14454
|
if (!mutationBarrierComplete) {
|
|
14388
14455
|
throw new Error("daemon shutdown mutation barrier is incomplete; ownership lease retained");
|
|
14389
14456
|
}
|
|
14457
|
+
daemonStarted = false;
|
|
14390
14458
|
}
|
|
14391
14459
|
async function stop(opts = {}) {
|
|
14392
14460
|
shuttingDown = true;
|
|
@@ -14708,6 +14776,9 @@ function buildDaemonWithAdapters(config, adapters, overrides = {}, assertionProb
|
|
|
14708
14776
|
if (config.agentEgress === void 0 || agentHomeManager === void 0) {
|
|
14709
14777
|
throw new Error("Agent reliable egress is not configured");
|
|
14710
14778
|
}
|
|
14779
|
+
if (tenantRebinding) {
|
|
14780
|
+
throw new Error("tenant enrollment is being re-paired; reliable egress is blocked until restart");
|
|
14781
|
+
}
|
|
14711
14782
|
const binding = await agentHomeManager.acquire(input.agentRef);
|
|
14712
14783
|
try {
|
|
14713
14784
|
await agentHomeManager.initialize(binding);
|
|
@@ -15381,7 +15452,7 @@ function createServiceLifecycle(def, opts = {}) {
|
|
|
15381
15452
|
|
|
15382
15453
|
// src/bin/official-release.ts
|
|
15383
15454
|
var OFFICIAL_LOCAL_AGENT_RELEASE = resolveLocalAgentReleaseIdentity({
|
|
15384
|
-
version: "0.
|
|
15455
|
+
version: "0.7.0"
|
|
15385
15456
|
});
|
|
15386
15457
|
|
|
15387
15458
|
// src/bin/config.ts
|