@dharma-ai-labs/agent-fabric 0.2.97 → 0.2.98
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/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +349 -315
- package/dist/index.js.map +1 -1
- package/dist/index.test.js +36 -3
- package/dist/index.test.js.map +1 -1
- package/dist/skillPreparationCache.test.js +1 -0
- package/dist/skillPreparationCache.test.js.map +1 -1
- package/package.json +5 -5
package/dist/index.js
CHANGED
|
@@ -37,7 +37,7 @@ import { connectDemoDevice, verifyDemoDevice } from './demoEnrollment.js';
|
|
|
37
37
|
import { performDemoPeerAction, withDemoDeviceLock } from './demoPeer.js';
|
|
38
38
|
import { demoRepositoryPackage } from './demoPackage.js';
|
|
39
39
|
import { runDemoWatch } from './demoWatch.js';
|
|
40
|
-
const VERSION = '0.2.
|
|
40
|
+
const VERSION = '0.2.98';
|
|
41
41
|
const USAGE = CLI_USAGE;
|
|
42
42
|
const execFileAsync = promisify(execFile);
|
|
43
43
|
const LOCAL_PROVIDER_IDS = ['codex', 'claude', 'agy', 'hermes'];
|
|
@@ -752,6 +752,30 @@ export async function relayProcessState(home = dharmaHome()) {
|
|
|
752
752
|
return code === 'EPERM' ? 'unknown' : 'stopped';
|
|
753
753
|
}
|
|
754
754
|
}
|
|
755
|
+
export async function acquireRelayProcessLease(home = dharmaHome()) {
|
|
756
|
+
const pidPath = resolve(home, 'relay', 'relay.pid');
|
|
757
|
+
const releaseLock = await acquirePidLock(`${pidPath}.lock`, 250, 'Another relay process is already running for this device.');
|
|
758
|
+
try {
|
|
759
|
+
const state = await relayProcessState(home);
|
|
760
|
+
if (state !== 'stopped')
|
|
761
|
+
throw new Error(`Cannot start a second relay while process state is ${state}.`);
|
|
762
|
+
await writeFile(pidPath, `${process.pid}\n`, { mode: 0o600 });
|
|
763
|
+
}
|
|
764
|
+
catch (error) {
|
|
765
|
+
await releaseLock();
|
|
766
|
+
throw error;
|
|
767
|
+
}
|
|
768
|
+
return async () => {
|
|
769
|
+
try {
|
|
770
|
+
if ((await readFile(pidPath, 'utf8').catch(() => '')).trim() === String(process.pid)) {
|
|
771
|
+
await rm(pidPath, { force: true });
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
finally {
|
|
775
|
+
await releaseLock();
|
|
776
|
+
}
|
|
777
|
+
};
|
|
778
|
+
}
|
|
755
779
|
export async function materializeWorkspacePolicy(input) {
|
|
756
780
|
const allowedCommands = {};
|
|
757
781
|
try {
|
|
@@ -1396,9 +1420,17 @@ export async function waitForRelayReadiness(options = {}) {
|
|
|
1396
1420
|
lastState = await processState();
|
|
1397
1421
|
if (lastState === 'running') {
|
|
1398
1422
|
try {
|
|
1399
|
-
|
|
1423
|
+
const acknowledged = await probe();
|
|
1424
|
+
if (!acknowledged.connected)
|
|
1425
|
+
throw new Error('Relay session is not connected.');
|
|
1426
|
+
if (options.expectedVersion && acknowledged.relayVersion !== options.expectedVersion) {
|
|
1427
|
+
throw new Error(`relay_version_mismatch:${acknowledged.relayVersion}:${options.expectedVersion}`);
|
|
1428
|
+
}
|
|
1429
|
+
return { state: 'running', probe: acknowledged };
|
|
1400
1430
|
}
|
|
1401
|
-
catch {
|
|
1431
|
+
catch (error) {
|
|
1432
|
+
if (String(error.message).startsWith('relay_version_mismatch:'))
|
|
1433
|
+
throw error;
|
|
1402
1434
|
acknowledgementPending = true;
|
|
1403
1435
|
}
|
|
1404
1436
|
}
|
|
@@ -1419,7 +1451,7 @@ async function startRelayDaemon(policyPath) {
|
|
|
1419
1451
|
});
|
|
1420
1452
|
child.unref();
|
|
1421
1453
|
}
|
|
1422
|
-
const readiness = await waitForRelayReadiness();
|
|
1454
|
+
const readiness = await waitForRelayReadiness({ expectedVersion: VERSION });
|
|
1423
1455
|
return { started: !alreadyRunning, ...readiness };
|
|
1424
1456
|
}
|
|
1425
1457
|
export function providerHintFromEnvironment(env = process.env) {
|
|
@@ -5185,350 +5217,352 @@ async function relayStart(flags) {
|
|
|
5185
5217
|
const fabric = await client();
|
|
5186
5218
|
const leaseSeconds = Number(flags.get('lease-seconds') || 120);
|
|
5187
5219
|
const pollMs = Math.min(Math.max(Number(flags.get('poll-seconds') || 3), 1), 60) * 1_000;
|
|
5188
|
-
const
|
|
5189
|
-
|
|
5190
|
-
|
|
5191
|
-
|
|
5192
|
-
|
|
5193
|
-
|
|
5194
|
-
|
|
5195
|
-
|
|
5196
|
-
|
|
5197
|
-
|
|
5198
|
-
|
|
5199
|
-
|
|
5200
|
-
|
|
5201
|
-
|
|
5202
|
-
|
|
5203
|
-
|
|
5204
|
-
|
|
5205
|
-
|
|
5206
|
-
|
|
5207
|
-
|
|
5208
|
-
|
|
5209
|
-
|
|
5210
|
-
|
|
5211
|
-
|
|
5212
|
-
|
|
5213
|
-
|
|
5214
|
-
|
|
5215
|
-
|
|
5216
|
-
|
|
5217
|
-
|
|
5218
|
-
|
|
5219
|
-
|
|
5220
|
-
|
|
5221
|
-
|
|
5222
|
-
|
|
5223
|
-
|
|
5220
|
+
const releaseRelayLease = await acquireRelayProcessLease();
|
|
5221
|
+
try {
|
|
5222
|
+
let stopping = false;
|
|
5223
|
+
let stopPreparation;
|
|
5224
|
+
const stop = () => { stopping = true; stopPreparation?.(); };
|
|
5225
|
+
process.once('SIGINT', stop);
|
|
5226
|
+
process.once('SIGTERM', stop);
|
|
5227
|
+
let tasksCompleted = 0;
|
|
5228
|
+
let lastProviderFailureCategory = null;
|
|
5229
|
+
let taskTrajectoriesRecovered = 0;
|
|
5230
|
+
let evidenceResponsesCompleted = 0;
|
|
5231
|
+
let trajectorySyncsCompleted = 0;
|
|
5232
|
+
let nextPolicyRefreshAt = 0;
|
|
5233
|
+
let evidencePolicyFresh = false;
|
|
5234
|
+
const repositorySourceWatcher = new RepositorySourceWatcher();
|
|
5235
|
+
const blockedRepositorySourceRetry = new BlockedRepositorySourceRetry();
|
|
5236
|
+
let repositorySourceBaselineAvailable = true;
|
|
5237
|
+
let publishedLocalSnapshotHash = canonicalWorkspace.repositoryPackage?.publishedLocalSnapshotHash
|
|
5238
|
+
|| (canonicalWorkspace.repositoryPackage?.state === 'published'
|
|
5239
|
+
&& canonicalWorkspace.repositoryPackage.snapshotHash === canonicalWorkspace.repositoryPackage.localBaselineSnapshotHash
|
|
5240
|
+
? canonicalWorkspace.repositoryPackage.snapshotHash : null);
|
|
5241
|
+
if (canonicalWorkspace.repositoryPackage && canonicalWorkspace.repositoryBindingId && canonicalWorkspace.repositoryAgentId) {
|
|
5242
|
+
try {
|
|
5243
|
+
const recovered = await recoverPublishedLocalSourceBaseline({
|
|
5244
|
+
workspace: canonicalWorkspace.path, organizationId: canonicalWorkspace.organizationId,
|
|
5245
|
+
workspaceId: canonicalWorkspace.workspaceId, repositoryBindingId: canonicalWorkspace.repositoryBindingId,
|
|
5246
|
+
repositoryAgentId: canonicalWorkspace.repositoryAgentId, record: canonicalWorkspace.repositoryPackage,
|
|
5247
|
+
});
|
|
5248
|
+
if (recovered && recovered !== publishedLocalSnapshotHash) {
|
|
5249
|
+
publishedLocalSnapshotHash = recovered;
|
|
5250
|
+
canonicalWorkspace = { ...canonicalWorkspace, repositoryPackage: {
|
|
5251
|
+
...canonicalWorkspace.repositoryPackage, localBaselineSnapshotHash: recovered,
|
|
5252
|
+
publishedLocalSnapshotHash: recovered,
|
|
5253
|
+
} };
|
|
5254
|
+
await saveWorkspaceRecord(canonicalWorkspace);
|
|
5255
|
+
}
|
|
5224
5256
|
}
|
|
5225
|
-
|
|
5226
|
-
|
|
5227
|
-
repositorySourceBaselineAvailable = false;
|
|
5228
|
-
}
|
|
5229
|
-
}
|
|
5230
|
-
if (publishedLocalSnapshotHash) {
|
|
5231
|
-
try {
|
|
5232
|
-
await readRepositoryPackageSnapshot(canonicalWorkspace.path, publishedLocalSnapshotHash);
|
|
5233
|
-
const localHash = canonicalWorkspace.repositoryPackage?.localBaselineSnapshotHash;
|
|
5234
|
-
if (localHash && (localHash === publishedLocalSnapshotHash
|
|
5235
|
-
|| canonicalWorkspace.repositoryPackage?.pendingLocalSnapshotHash === localHash)) {
|
|
5236
|
-
await seedRepositorySourceWatcher({ workspace: canonicalWorkspace.path,
|
|
5237
|
-
organizationId: canonicalWorkspace.organizationId, workspaceId: canonicalWorkspace.workspaceId,
|
|
5238
|
-
repositoryBindingId: canonicalWorkspace.repositoryBindingId,
|
|
5239
|
-
repositoryAgentId: canonicalWorkspace.repositoryAgentId,
|
|
5240
|
-
publishedHash: publishedLocalSnapshotHash,
|
|
5241
|
-
localHash,
|
|
5242
|
-
watcher: repositorySourceWatcher });
|
|
5257
|
+
catch {
|
|
5258
|
+
repositorySourceBaselineAvailable = false;
|
|
5243
5259
|
}
|
|
5244
5260
|
}
|
|
5245
|
-
|
|
5246
|
-
|
|
5261
|
+
if (publishedLocalSnapshotHash) {
|
|
5262
|
+
try {
|
|
5263
|
+
await readRepositoryPackageSnapshot(canonicalWorkspace.path, publishedLocalSnapshotHash);
|
|
5264
|
+
const localHash = canonicalWorkspace.repositoryPackage?.localBaselineSnapshotHash;
|
|
5265
|
+
if (localHash && (localHash === publishedLocalSnapshotHash
|
|
5266
|
+
|| canonicalWorkspace.repositoryPackage?.pendingLocalSnapshotHash === localHash)) {
|
|
5267
|
+
await seedRepositorySourceWatcher({ workspace: canonicalWorkspace.path,
|
|
5268
|
+
organizationId: canonicalWorkspace.organizationId, workspaceId: canonicalWorkspace.workspaceId,
|
|
5269
|
+
repositoryBindingId: canonicalWorkspace.repositoryBindingId,
|
|
5270
|
+
repositoryAgentId: canonicalWorkspace.repositoryAgentId,
|
|
5271
|
+
publishedHash: publishedLocalSnapshotHash,
|
|
5272
|
+
localHash,
|
|
5273
|
+
watcher: repositorySourceWatcher });
|
|
5274
|
+
}
|
|
5275
|
+
}
|
|
5276
|
+
catch {
|
|
5277
|
+
repositorySourceBaselineAvailable = false;
|
|
5278
|
+
}
|
|
5247
5279
|
}
|
|
5248
|
-
|
|
5249
|
-
|
|
5250
|
-
|
|
5251
|
-
|
|
5252
|
-
|
|
5253
|
-
|
|
5254
|
-
|
|
5255
|
-
|
|
5256
|
-
|
|
5257
|
-
|
|
5258
|
-
|
|
5259
|
-
|
|
5260
|
-
|
|
5261
|
-
|
|
5262
|
-
|
|
5263
|
-
|
|
5264
|
-
|
|
5265
|
-
|
|
5266
|
-
|
|
5267
|
-
|
|
5268
|
-
|
|
5269
|
-
|
|
5270
|
-
|
|
5271
|
-
if (capability.skillInstall === 'unavailable' || !isLocalProviderId(adapter.providerId))
|
|
5272
|
-
return;
|
|
5273
|
-
const provider = adapter.providerId;
|
|
5274
|
-
await withSkillPreparationTransaction({ home: dharmaHome(), workspaceId: canonicalWorkspace.workspaceId,
|
|
5275
|
-
provider, assertCurrent: assertRunning }, async () => {
|
|
5276
|
-
const prepared = await prepareSkillUpdate({ workspaceId: canonicalWorkspace.workspaceId,
|
|
5277
|
-
provider, policyPath, fabric, automatic: true, assertRunning });
|
|
5278
|
-
if (!prepared) {
|
|
5279
|
-
assertRunning();
|
|
5280
|
+
let nextRepositorySourceScanAt = 0;
|
|
5281
|
+
let repositorySourceCandidates = 0;
|
|
5282
|
+
let repositorySourceFailures = 0;
|
|
5283
|
+
let repositorySourceState = 'not_scanned';
|
|
5284
|
+
const { LocalVault, loadOrCreateVaultMasterKey } = await loadVaultModule();
|
|
5285
|
+
const vault = await LocalVault.open({
|
|
5286
|
+
root: resolve(dharmaHome(), 'vault'),
|
|
5287
|
+
masterKey: await loadOrCreateVaultMasterKey(),
|
|
5288
|
+
rawLocalDays: rawLocalRetentionDays(policy),
|
|
5289
|
+
});
|
|
5290
|
+
let skillPreparationsCompleted = 0;
|
|
5291
|
+
let skillPreparationFailures = 0;
|
|
5292
|
+
let skillActivationsCompleted = 0;
|
|
5293
|
+
let skillActivationFailures = 0;
|
|
5294
|
+
const skillActivationFailuresByProvider = {};
|
|
5295
|
+
let nextSkillActivationAt = 0;
|
|
5296
|
+
const skillPreparationPump = startSkillPreparationPump({
|
|
5297
|
+
prepare: async (assertRunning) => {
|
|
5298
|
+
await prepareProvidersIndependently(providerAdapters, assertRunning, async (adapter) => {
|
|
5299
|
+
assertRunning();
|
|
5300
|
+
const capability = await adapter.capability();
|
|
5301
|
+
assertRunning();
|
|
5302
|
+
if (capability.skillInstall === 'unavailable' || !isLocalProviderId(adapter.providerId))
|
|
5280
5303
|
return;
|
|
5281
|
-
|
|
5282
|
-
|
|
5283
|
-
|
|
5284
|
-
prepared.
|
|
5285
|
-
|
|
5286
|
-
if (!
|
|
5287
|
-
|
|
5288
|
-
|
|
5289
|
-
|
|
5290
|
-
|
|
5291
|
-
|
|
5292
|
-
|
|
5293
|
-
|
|
5294
|
-
|
|
5295
|
-
|
|
5296
|
-
|
|
5297
|
-
|
|
5298
|
-
|
|
5299
|
-
await writeFile(resolve(prepared.sourceRoot, 'PREPARED.json'), pendingRecord, { mode: 0o600, flag: 'wx' });
|
|
5300
|
-
prepared.assertCurrent();
|
|
5301
|
-
await publishSkillPreparationCache({ home: dharmaHome(), workspaceId: canonicalWorkspace.workspaceId,
|
|
5302
|
-
provider, sourceRoot: prepared.sourceRoot, recordBytes: pendingRecord, assertCurrent: prepared.assertCurrent,
|
|
5303
|
-
expected: { organizationId: prepared.config.organizationId, deviceId: prepared.config.deviceId,
|
|
5304
|
+
const provider = adapter.providerId;
|
|
5305
|
+
await withSkillPreparationTransaction({ home: dharmaHome(), workspaceId: canonicalWorkspace.workspaceId,
|
|
5306
|
+
provider, assertCurrent: assertRunning }, async () => {
|
|
5307
|
+
const prepared = await prepareSkillUpdate({ workspaceId: canonicalWorkspace.workspaceId,
|
|
5308
|
+
provider, policyPath, fabric, automatic: true, assertRunning });
|
|
5309
|
+
if (!prepared) {
|
|
5310
|
+
assertRunning();
|
|
5311
|
+
return;
|
|
5312
|
+
}
|
|
5313
|
+
let retained = false;
|
|
5314
|
+
try {
|
|
5315
|
+
prepared.assertCurrent();
|
|
5316
|
+
const repositoryAgentId = prepared.workspace.repositoryAgentId;
|
|
5317
|
+
if (!repositoryAgentId)
|
|
5318
|
+
throw new Error('Preparation cache requires an enrolled repository agent.');
|
|
5319
|
+
const pendingRecord = await serializeSkillPreparationRecord({
|
|
5320
|
+
schema: 'dharma.skill-preparation/v1', organizationId: prepared.config.organizationId,
|
|
5321
|
+
deviceId: prepared.config.deviceId, workspaceId: prepared.workspace.workspaceId,
|
|
5304
5322
|
repositoryAgentId,
|
|
5305
|
-
repositoryBindingId: prepared.workspace.repositoryBindingId ?? null,
|
|
5323
|
+
repositoryBindingId: prepared.workspace.repositoryBindingId ?? null, provider,
|
|
5306
5324
|
policyHash: sha256(canonicalize(prepared.policy)), rolloutId: prepared.rollout.id,
|
|
5307
|
-
|
|
5308
|
-
|
|
5309
|
-
|
|
5310
|
-
|
|
5311
|
-
|
|
5312
|
-
|
|
5313
|
-
|
|
5314
|
-
|
|
5315
|
-
|
|
5316
|
-
|
|
5317
|
-
|
|
5318
|
-
|
|
5319
|
-
|
|
5320
|
-
|
|
5321
|
-
|
|
5322
|
-
|
|
5323
|
-
try {
|
|
5324
|
-
const recoveredTaskTrajectories = await deferUnavailableRelayRetention(async () => finalizeRecoveredSignedTaskTrajectories(fabric, policy));
|
|
5325
|
-
if (recoveredTaskTrajectories.state === 'completed') {
|
|
5326
|
-
taskTrajectoriesRecovered += recoveredTaskTrajectories.value.length;
|
|
5327
|
-
}
|
|
5328
|
-
do {
|
|
5329
|
-
if (Date.now() >= nextPolicyRefreshAt) {
|
|
5330
|
-
try {
|
|
5331
|
-
await syncWorkspacePolicy(fabric, canonicalWorkspace, policy.revision, true);
|
|
5332
|
-
policy = await loadVerifiedWorkspacePolicy(policyPath, canonicalWorkspace.workspaceId);
|
|
5333
|
-
evidencePolicyFresh = true;
|
|
5334
|
-
}
|
|
5335
|
-
catch {
|
|
5336
|
-
evidencePolicyFresh = false;
|
|
5337
|
-
}
|
|
5338
|
-
nextPolicyRefreshAt = Date.now() + 60_000;
|
|
5339
|
-
}
|
|
5340
|
-
if (canonicalWorkspace.repositoryBindingId && canonicalWorkspace.repositoryAgentId) {
|
|
5341
|
-
try {
|
|
5342
|
-
const current = await pollRepositoryCandidate({ transport: fabric,
|
|
5343
|
-
outboxRoot: resolve(dharmaHome(), 'relay', 'repository-candidates'),
|
|
5344
|
-
scope: { organizationId: canonicalWorkspace.organizationId, workspaceId: canonicalWorkspace.workspaceId,
|
|
5345
|
-
repositoryBindingId: canonicalWorkspace.repositoryBindingId,
|
|
5346
|
-
repositoryAgentId: canonicalWorkspace.repositoryAgentId } });
|
|
5347
|
-
if (current && canonicalWorkspace.repositoryPackage) {
|
|
5348
|
-
if (current.candidateId !== canonicalWorkspace.repositoryPackage.candidateId
|
|
5349
|
-
|| current.operationId !== canonicalWorkspace.repositoryPackage.operationId) {
|
|
5350
|
-
throw new Error('Repository candidate status changed identity while polling.');
|
|
5325
|
+
bundle: prepared.bundle, repositoryPackage: prepared.repositoryDelivery
|
|
5326
|
+
? { envelope: prepared.repositoryDelivery.envelope, index: prepared.repositoryDelivery.index } : null,
|
|
5327
|
+
preparedAt: new Date().toISOString(), activationAuthorized: false,
|
|
5328
|
+
});
|
|
5329
|
+
prepared.assertCurrent();
|
|
5330
|
+
await writeFile(resolve(prepared.sourceRoot, 'PREPARED.json'), pendingRecord, { mode: 0o600, flag: 'wx' });
|
|
5331
|
+
prepared.assertCurrent();
|
|
5332
|
+
await publishSkillPreparationCache({ home: dharmaHome(), workspaceId: canonicalWorkspace.workspaceId,
|
|
5333
|
+
provider, sourceRoot: prepared.sourceRoot, recordBytes: pendingRecord, assertCurrent: prepared.assertCurrent,
|
|
5334
|
+
expected: { organizationId: prepared.config.organizationId, deviceId: prepared.config.deviceId,
|
|
5335
|
+
repositoryAgentId,
|
|
5336
|
+
repositoryBindingId: prepared.workspace.repositoryBindingId ?? null,
|
|
5337
|
+
policyHash: sha256(canonicalize(prepared.policy)), rolloutId: prepared.rollout.id,
|
|
5338
|
+
bundleId: prepared.bundle.bundleId, bundleHash: prepared.bundle.bundleHash },
|
|
5339
|
+
onCommitted: () => { retained = true; } });
|
|
5340
|
+
skillPreparationsCompleted += 1;
|
|
5351
5341
|
}
|
|
5352
|
-
|
|
5353
|
-
|
|
5354
|
-
|
|
5355
|
-
|
|
5356
|
-
|
|
5357
|
-
|
|
5358
|
-
|
|
5359
|
-
|
|
5360
|
-
|
|
5361
|
-
|
|
5362
|
-
|
|
5363
|
-
|
|
5364
|
-
|
|
5365
|
-
|
|
5366
|
-
|
|
5367
|
-
|
|
5368
|
-
let evidenceRequestId;
|
|
5369
|
-
if (evidencePolicyFresh) {
|
|
5370
|
-
const evidenceCycle = await deferUnavailableRelayRetention(async () => {
|
|
5371
|
-
const synced = await syncPendingRetentionCapsules(vault, fabric, policy, canonicalWorkspace.workspaceId);
|
|
5372
|
-
const evidence = await processEvidenceRequest(fabric, policy, canonicalWorkspace.workspaceId);
|
|
5373
|
-
return {
|
|
5374
|
-
synced,
|
|
5375
|
-
evidenceRequestId: typeof evidence.requestId === 'string' ? evidence.requestId : undefined,
|
|
5376
|
-
};
|
|
5377
|
-
});
|
|
5378
|
-
if (evidenceCycle.state === 'deferred') {
|
|
5379
|
-
evidencePolicyFresh = false;
|
|
5380
|
-
}
|
|
5381
|
-
else {
|
|
5382
|
-
trajectorySyncsCompleted += evidenceCycle.value.synced;
|
|
5383
|
-
evidenceRequestId = evidenceCycle.value.evidenceRequestId;
|
|
5384
|
-
if (evidenceRequestId)
|
|
5385
|
-
evidenceResponsesCompleted += 1;
|
|
5386
|
-
}
|
|
5342
|
+
finally {
|
|
5343
|
+
if (!retained)
|
|
5344
|
+
await rm(prepared.sourceRoot, { recursive: true, force: true });
|
|
5345
|
+
}
|
|
5346
|
+
});
|
|
5347
|
+
}, () => { skillPreparationFailures += 1; });
|
|
5348
|
+
},
|
|
5349
|
+
onError: () => { skillPreparationFailures += 1; },
|
|
5350
|
+
});
|
|
5351
|
+
stopPreparation = skillPreparationPump.requestStop;
|
|
5352
|
+
if (stopping)
|
|
5353
|
+
stopPreparation();
|
|
5354
|
+
try {
|
|
5355
|
+
const recoveredTaskTrajectories = await deferUnavailableRelayRetention(async () => finalizeRecoveredSignedTaskTrajectories(fabric, policy));
|
|
5356
|
+
if (recoveredTaskTrajectories.state === 'completed') {
|
|
5357
|
+
taskTrajectoriesRecovered += recoveredTaskTrajectories.value.length;
|
|
5387
5358
|
}
|
|
5388
|
-
|
|
5389
|
-
|
|
5390
|
-
tasksCompleted += 1;
|
|
5391
|
-
if (typeof result.failureCategory === 'string')
|
|
5392
|
-
lastProviderFailureCategory = result.failureCategory;
|
|
5393
|
-
if (!result.taskId && performance.now() >= nextSkillActivationAt) {
|
|
5394
|
-
for (const adapter of providerAdapters) {
|
|
5359
|
+
do {
|
|
5360
|
+
if (Date.now() >= nextPolicyRefreshAt) {
|
|
5395
5361
|
try {
|
|
5396
|
-
|
|
5397
|
-
|
|
5398
|
-
|
|
5399
|
-
const provider = adapter.providerId;
|
|
5400
|
-
await withWorkspaceSkillActivationLock(canonicalWorkspace.workspaceId, provider, async () => {
|
|
5401
|
-
await withSkillPreparationTransaction({ home: dharmaHome(), workspaceId: canonicalWorkspace.workspaceId,
|
|
5402
|
-
provider, assertCurrent: () => { if (stopping)
|
|
5403
|
-
throw new Error('Relay is stopping.'); } }, async () => {
|
|
5404
|
-
const cached = await takeCachedSkillUpdate({ workspaceId: canonicalWorkspace.workspaceId,
|
|
5405
|
-
provider, policyPath, fabric });
|
|
5406
|
-
const prepared = cached || await prepareSkillUpdate({ workspaceId: canonicalWorkspace.workspaceId,
|
|
5407
|
-
provider, policyPath, fabric, automatic: true });
|
|
5408
|
-
if (!prepared)
|
|
5409
|
-
return;
|
|
5410
|
-
if (!cached)
|
|
5411
|
-
skillPreparationsCompleted += 1;
|
|
5412
|
-
await activatePreparedSkillUpdate({ prepared, workspaceId: canonicalWorkspace.workspaceId,
|
|
5413
|
-
provider, policyPath, flags: new Map() });
|
|
5414
|
-
skillActivationsCompleted += 1;
|
|
5415
|
-
});
|
|
5416
|
-
});
|
|
5362
|
+
await syncWorkspacePolicy(fabric, canonicalWorkspace, policy.revision, true);
|
|
5363
|
+
policy = await loadVerifiedWorkspacePolicy(policyPath, canonicalWorkspace.workspaceId);
|
|
5364
|
+
evidencePolicyFresh = true;
|
|
5417
5365
|
}
|
|
5418
5366
|
catch {
|
|
5419
|
-
|
|
5420
|
-
skillActivationFailuresByProvider[adapter.providerId] = (skillActivationFailuresByProvider[adapter.providerId] || 0) + 1;
|
|
5367
|
+
evidencePolicyFresh = false;
|
|
5421
5368
|
}
|
|
5369
|
+
nextPolicyRefreshAt = Date.now() + 60_000;
|
|
5422
5370
|
}
|
|
5423
|
-
|
|
5424
|
-
|
|
5425
|
-
|
|
5426
|
-
|
|
5427
|
-
|
|
5428
|
-
|
|
5371
|
+
if (canonicalWorkspace.repositoryBindingId && canonicalWorkspace.repositoryAgentId) {
|
|
5372
|
+
try {
|
|
5373
|
+
const current = await pollRepositoryCandidate({ transport: fabric,
|
|
5374
|
+
outboxRoot: resolve(dharmaHome(), 'relay', 'repository-candidates'),
|
|
5375
|
+
scope: { organizationId: canonicalWorkspace.organizationId, workspaceId: canonicalWorkspace.workspaceId,
|
|
5376
|
+
repositoryBindingId: canonicalWorkspace.repositoryBindingId,
|
|
5377
|
+
repositoryAgentId: canonicalWorkspace.repositoryAgentId } });
|
|
5378
|
+
if (current && canonicalWorkspace.repositoryPackage) {
|
|
5379
|
+
if (current.candidateId !== canonicalWorkspace.repositoryPackage.candidateId
|
|
5380
|
+
|| current.operationId !== canonicalWorkspace.repositoryPackage.operationId) {
|
|
5381
|
+
throw new Error('Repository candidate status changed identity while polling.');
|
|
5382
|
+
}
|
|
5383
|
+
const baseline = advanceRepositorySourceBaseline(canonicalWorkspace.repositoryPackage, current);
|
|
5384
|
+
canonicalWorkspace = { ...canonicalWorkspace, repositoryPackage: { ...canonicalWorkspace.repositoryPackage,
|
|
5385
|
+
state: current.state, candidateId: current.candidateId, operationId: current.operationId,
|
|
5386
|
+
snapshotHash: current.snapshotHash, releaseId: current.releaseId,
|
|
5387
|
+
...baseline,
|
|
5388
|
+
generation: current.state === 'published' ? Math.max(1, canonicalWorkspace.repositoryPackage.generation) : canonicalWorkspace.repositoryPackage.generation } };
|
|
5389
|
+
publishedLocalSnapshotHash = baseline.publishedLocalSnapshotHash;
|
|
5390
|
+
if (current.state === 'blocked')
|
|
5391
|
+
repositorySourceWatcher.invalidate();
|
|
5392
|
+
await saveWorkspaceRecord(canonicalWorkspace);
|
|
5393
|
+
}
|
|
5429
5394
|
}
|
|
5430
|
-
|
|
5431
|
-
|
|
5395
|
+
catch {
|
|
5396
|
+
repositorySourceFailures += 1;
|
|
5432
5397
|
}
|
|
5433
|
-
|
|
5434
|
-
|
|
5435
|
-
|
|
5436
|
-
|
|
5398
|
+
}
|
|
5399
|
+
let evidenceRequestId;
|
|
5400
|
+
if (evidencePolicyFresh) {
|
|
5401
|
+
const evidenceCycle = await deferUnavailableRelayRetention(async () => {
|
|
5402
|
+
const synced = await syncPendingRetentionCapsules(vault, fabric, policy, canonicalWorkspace.workspaceId);
|
|
5403
|
+
const evidence = await processEvidenceRequest(fabric, policy, canonicalWorkspace.workspaceId);
|
|
5404
|
+
return {
|
|
5405
|
+
synced,
|
|
5406
|
+
evidenceRequestId: typeof evidence.requestId === 'string' ? evidence.requestId : undefined,
|
|
5407
|
+
};
|
|
5408
|
+
});
|
|
5409
|
+
if (evidenceCycle.state === 'deferred') {
|
|
5410
|
+
evidencePolicyFresh = false;
|
|
5437
5411
|
}
|
|
5438
5412
|
else {
|
|
5439
|
-
|
|
5440
|
-
|
|
5441
|
-
|
|
5442
|
-
|
|
5413
|
+
trajectorySyncsCompleted += evidenceCycle.value.synced;
|
|
5414
|
+
evidenceRequestId = evidenceCycle.value.evidenceRequestId;
|
|
5415
|
+
if (evidenceRequestId)
|
|
5416
|
+
evidenceResponsesCompleted += 1;
|
|
5417
|
+
}
|
|
5418
|
+
}
|
|
5419
|
+
const result = await executeOneTask(fabric, leaseSeconds);
|
|
5420
|
+
if (result.taskId)
|
|
5421
|
+
tasksCompleted += 1;
|
|
5422
|
+
if (typeof result.failureCategory === 'string')
|
|
5423
|
+
lastProviderFailureCategory = result.failureCategory;
|
|
5424
|
+
if (!result.taskId && performance.now() >= nextSkillActivationAt) {
|
|
5425
|
+
for (const adapter of providerAdapters) {
|
|
5426
|
+
try {
|
|
5427
|
+
const capability = await adapter.capability();
|
|
5428
|
+
if (capability.skillInstall === 'unavailable' || !isLocalProviderId(adapter.providerId))
|
|
5429
|
+
continue;
|
|
5430
|
+
const provider = adapter.providerId;
|
|
5431
|
+
await withWorkspaceSkillActivationLock(canonicalWorkspace.workspaceId, provider, async () => {
|
|
5432
|
+
await withSkillPreparationTransaction({ home: dharmaHome(), workspaceId: canonicalWorkspace.workspaceId,
|
|
5433
|
+
provider, assertCurrent: () => { if (stopping)
|
|
5434
|
+
throw new Error('Relay is stopping.'); } }, async () => {
|
|
5435
|
+
const cached = await takeCachedSkillUpdate({ workspaceId: canonicalWorkspace.workspaceId,
|
|
5436
|
+
provider, policyPath, fabric });
|
|
5437
|
+
const prepared = cached || await prepareSkillUpdate({ workspaceId: canonicalWorkspace.workspaceId,
|
|
5438
|
+
provider, policyPath, fabric, automatic: true });
|
|
5439
|
+
if (!prepared)
|
|
5440
|
+
return;
|
|
5441
|
+
if (!cached)
|
|
5442
|
+
skillPreparationsCompleted += 1;
|
|
5443
|
+
await activatePreparedSkillUpdate({ prepared, workspaceId: canonicalWorkspace.workspaceId,
|
|
5444
|
+
provider, policyPath, flags: new Map() });
|
|
5445
|
+
skillActivationsCompleted += 1;
|
|
5446
|
+
});
|
|
5447
|
+
});
|
|
5443
5448
|
}
|
|
5444
|
-
|
|
5445
|
-
|
|
5446
|
-
|
|
5447
|
-
|
|
5448
|
-
|
|
5449
|
-
|
|
5450
|
-
|
|
5451
|
-
|
|
5452
|
-
|
|
5453
|
-
|
|
5454
|
-
|
|
5455
|
-
|
|
5456
|
-
|
|
5457
|
-
|
|
5458
|
-
}
|
|
5459
|
-
|
|
5460
|
-
if (
|
|
5461
|
-
|
|
5462
|
-
|
|
5463
|
-
|
|
5464
|
-
|
|
5465
|
-
|
|
5466
|
-
|
|
5467
|
-
|
|
5468
|
-
|
|
5469
|
-
|
|
5470
|
-
|
|
5471
|
-
|
|
5472
|
-
|
|
5473
|
-
|
|
5474
|
-
|
|
5475
|
-
|
|
5476
|
-
|
|
5477
|
-
|
|
5478
|
-
|
|
5479
|
-
|
|
5480
|
-
|
|
5481
|
-
|
|
5449
|
+
catch {
|
|
5450
|
+
skillActivationFailures += 1;
|
|
5451
|
+
skillActivationFailuresByProvider[adapter.providerId] = (skillActivationFailuresByProvider[adapter.providerId] || 0) + 1;
|
|
5452
|
+
}
|
|
5453
|
+
}
|
|
5454
|
+
nextSkillActivationAt = performance.now() + 60_000;
|
|
5455
|
+
}
|
|
5456
|
+
if (performance.now() >= nextRepositorySourceScanAt) {
|
|
5457
|
+
try {
|
|
5458
|
+
if (!repositorySourceBaselineAvailable) {
|
|
5459
|
+
throw new Error('Repository source baseline is unavailable; refusing an unanchored update.');
|
|
5460
|
+
}
|
|
5461
|
+
if (!canonicalWorkspace.repositoryBindingId || !canonicalWorkspace.repositoryAgentId) {
|
|
5462
|
+
throw new Error('Repository source synchronization requires a complete repository binding.');
|
|
5463
|
+
}
|
|
5464
|
+
const installedKnowledge = await installedRepositoryKnowledge(canonicalWorkspace);
|
|
5465
|
+
if (!installedKnowledge && (canonicalWorkspace.repositoryPackage?.generation ?? 0) > 0) {
|
|
5466
|
+
repositorySourceState = 'awaiting_signed_baseline';
|
|
5467
|
+
nextRepositorySourceScanAt = performance.now() + 15_000;
|
|
5468
|
+
}
|
|
5469
|
+
else {
|
|
5470
|
+
if (canonicalWorkspace.repositoryPackage?.state === 'blocked' && canonicalWorkspace.repositoryPackage.snapshotHash) {
|
|
5471
|
+
const blocked = await readRepositorySourceBaselineSnapshot(canonicalWorkspace.path, canonicalWorkspace.repositoryPackage.snapshotHash);
|
|
5472
|
+
if (blockedRepositorySourceRetry.consider(canonicalWorkspace.repositoryPackage.candidateId ?? '', blocked.manifest.knowledge, installedKnowledge))
|
|
5473
|
+
repositorySourceWatcher.invalidate();
|
|
5474
|
+
}
|
|
5475
|
+
const sourceCycle = await scanRepositorySourceChanges({
|
|
5476
|
+
workspace: canonicalWorkspace.path, organizationId: canonicalWorkspace.organizationId,
|
|
5477
|
+
workspaceId: canonicalWorkspace.workspaceId, repositoryBindingId: canonicalWorkspace.repositoryBindingId,
|
|
5478
|
+
repositoryAgentId: canonicalWorkspace.repositoryAgentId, transport: fabric, watcher: repositorySourceWatcher,
|
|
5479
|
+
loadRetainedKnowledge: () => installedRepositoryKnowledge(canonicalWorkspace),
|
|
5480
|
+
loadPublishedLocalBaseline: () => publishedLocalSnapshotHash
|
|
5481
|
+
? readRepositoryPackageSnapshot(canonicalWorkspace.path, publishedLocalSnapshotHash) : Promise.resolve(null),
|
|
5482
|
+
submitCandidate: (snapshot, expectedLatestSourceFingerprint) => synchronizeRepositoryCandidate({ transport: fabric,
|
|
5483
|
+
outboxRoot: resolve(dharmaHome(), 'relay', 'repository-candidates'),
|
|
5484
|
+
scope: { organizationId: canonicalWorkspace.organizationId,
|
|
5485
|
+
workspaceId: canonicalWorkspace.workspaceId,
|
|
5486
|
+
repositoryBindingId: canonicalWorkspace.repositoryBindingId,
|
|
5487
|
+
repositoryAgentId: canonicalWorkspace.repositoryAgentId },
|
|
5488
|
+
snapshot, initialRepository: false, expectedLatestSourceFingerprint }),
|
|
5489
|
+
});
|
|
5490
|
+
repositorySourceState = sourceCycle.state;
|
|
5491
|
+
if (sourceCycle.localMutation) {
|
|
5492
|
+
repositorySourceCandidates += 1;
|
|
5493
|
+
if (canonicalWorkspace.repositoryPackage && sourceCycle.persisted) {
|
|
5494
|
+
canonicalWorkspace.repositoryPackage.localBaselineSnapshotHash = sourceCycle.persisted.snapshotHash;
|
|
5495
|
+
if (sourceCycle.state === 'source_already_published') {
|
|
5482
5496
|
canonicalWorkspace.repositoryPackage.publishedLocalSnapshotHash = sourceCycle.persisted.snapshotHash;
|
|
5483
5497
|
canonicalWorkspace.repositoryPackage.pendingLocalSnapshotHash = null;
|
|
5484
5498
|
canonicalWorkspace.repositoryPackage.pendingLocalOperationId = null;
|
|
5485
5499
|
publishedLocalSnapshotHash = sourceCycle.persisted.snapshotHash;
|
|
5486
5500
|
}
|
|
5487
|
-
else if (
|
|
5488
|
-
|
|
5489
|
-
|
|
5490
|
-
|
|
5491
|
-
|
|
5492
|
-
|
|
5493
|
-
|
|
5494
|
-
|
|
5501
|
+
else if (sourceCycle.candidate) {
|
|
5502
|
+
const receipt = sourceCycle.candidate;
|
|
5503
|
+
if (typeof receipt.candidateId === 'string' && typeof receipt.operationId === 'string'
|
|
5504
|
+
&& typeof receipt.snapshotHash === 'string' && sourceCycle.submittedManifestHash) {
|
|
5505
|
+
Object.assign(canonicalWorkspace.repositoryPackage, {
|
|
5506
|
+
state: receipt.state, candidateId: receipt.candidateId, operationId: receipt.operationId,
|
|
5507
|
+
snapshotHash: receipt.snapshotHash, sourceManifestHash: sourceCycle.submittedManifestHash,
|
|
5508
|
+
releaseId: typeof receipt.releaseId === 'string' ? receipt.releaseId : null,
|
|
5509
|
+
consolidationMode: 'repository_update',
|
|
5510
|
+
});
|
|
5511
|
+
}
|
|
5512
|
+
if (receipt.state === 'published') {
|
|
5513
|
+
canonicalWorkspace.repositoryPackage.publishedLocalSnapshotHash = sourceCycle.persisted.snapshotHash;
|
|
5514
|
+
canonicalWorkspace.repositoryPackage.pendingLocalSnapshotHash = null;
|
|
5515
|
+
canonicalWorkspace.repositoryPackage.pendingLocalOperationId = null;
|
|
5516
|
+
publishedLocalSnapshotHash = sourceCycle.persisted.snapshotHash;
|
|
5517
|
+
}
|
|
5518
|
+
else if ((receipt.state === 'accepted' || receipt.state === 'processing')
|
|
5519
|
+
&& typeof receipt.operationId === 'string') {
|
|
5520
|
+
canonicalWorkspace.repositoryPackage.pendingLocalSnapshotHash = sourceCycle.persisted.snapshotHash;
|
|
5521
|
+
canonicalWorkspace.repositoryPackage.pendingLocalOperationId = receipt.operationId;
|
|
5522
|
+
}
|
|
5523
|
+
else if (receipt.state === 'blocked') {
|
|
5524
|
+
canonicalWorkspace.repositoryPackage.localBaselineSnapshotHash = publishedLocalSnapshotHash;
|
|
5525
|
+
repositorySourceWatcher.invalidate();
|
|
5526
|
+
}
|
|
5495
5527
|
}
|
|
5528
|
+
await saveWorkspaceRecord(canonicalWorkspace);
|
|
5496
5529
|
}
|
|
5497
|
-
await saveWorkspaceRecord(canonicalWorkspace);
|
|
5498
5530
|
}
|
|
5531
|
+
nextRepositorySourceScanAt = performance.now() + (sourceCycle.state === 'debouncing' ? 15_000 : 60_000);
|
|
5499
5532
|
}
|
|
5500
|
-
|
|
5533
|
+
}
|
|
5534
|
+
catch {
|
|
5535
|
+
repositorySourceWatcher.invalidate();
|
|
5536
|
+
repositorySourceFailures += 1;
|
|
5537
|
+
repositorySourceState = 'blocked';
|
|
5538
|
+
nextRepositorySourceScanAt = performance.now() + 60_000;
|
|
5501
5539
|
}
|
|
5502
5540
|
}
|
|
5503
|
-
|
|
5504
|
-
|
|
5505
|
-
|
|
5506
|
-
|
|
5507
|
-
|
|
5508
|
-
|
|
5509
|
-
|
|
5510
|
-
|
|
5511
|
-
|
|
5512
|
-
|
|
5513
|
-
|
|
5514
|
-
}
|
|
5541
|
+
if (flags.has('once'))
|
|
5542
|
+
break;
|
|
5543
|
+
if (!result.taskId && !evidenceRequestId)
|
|
5544
|
+
await new Promise((accept) => setTimeout(accept, pollMs));
|
|
5545
|
+
} while (!stopping);
|
|
5546
|
+
}
|
|
5547
|
+
finally {
|
|
5548
|
+
await skillPreparationPump.stop();
|
|
5549
|
+
vault.close();
|
|
5550
|
+
process.removeListener('SIGINT', stop);
|
|
5551
|
+
process.removeListener('SIGTERM', stop);
|
|
5552
|
+
}
|
|
5553
|
+
return {
|
|
5554
|
+
ok: true, stopped: true, tasksCompleted, taskTrajectoriesRecovered,
|
|
5555
|
+
lastProviderFailureCategory,
|
|
5556
|
+
evidenceResponsesCompleted, trajectorySyncsCompleted,
|
|
5557
|
+
repositorySourceCandidates, repositorySourceFailures, repositorySourceState,
|
|
5558
|
+
skillPreparationsCompleted, skillPreparationFailures, skillActivationsCompleted, skillActivationFailures,
|
|
5559
|
+
skillActivationFailuresByProvider,
|
|
5560
|
+
sharedRepositoryReady: await repositorySharedReady(canonicalWorkspace),
|
|
5561
|
+
};
|
|
5515
5562
|
}
|
|
5516
5563
|
finally {
|
|
5517
|
-
await
|
|
5518
|
-
vault.close();
|
|
5519
|
-
process.removeListener('SIGINT', stop);
|
|
5520
|
-
process.removeListener('SIGTERM', stop);
|
|
5521
|
-
await rm(pidPath, { force: true });
|
|
5564
|
+
await releaseRelayLease();
|
|
5522
5565
|
}
|
|
5523
|
-
return {
|
|
5524
|
-
ok: true, stopped: true, tasksCompleted, taskTrajectoriesRecovered,
|
|
5525
|
-
lastProviderFailureCategory,
|
|
5526
|
-
evidenceResponsesCompleted, trajectorySyncsCompleted,
|
|
5527
|
-
repositorySourceCandidates, repositorySourceFailures, repositorySourceState,
|
|
5528
|
-
skillPreparationsCompleted, skillPreparationFailures, skillActivationsCompleted, skillActivationFailures,
|
|
5529
|
-
skillActivationFailuresByProvider,
|
|
5530
|
-
sharedRepositoryReady: await repositorySharedReady(canonicalWorkspace),
|
|
5531
|
-
};
|
|
5532
5566
|
}
|
|
5533
5567
|
export async function run(argv) {
|
|
5534
5568
|
const { positional, flags, repeated } = parseCliOptions(argv);
|