@dharma-ai-labs/agent-fabric 0.2.97 → 0.2.99
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 +388 -316
- 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 +57 -8
- 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.99';
|
|
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,390 @@ 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
|
-
assertRunning()
|
|
5269
|
-
|
|
5270
|
-
|
|
5271
|
-
|
|
5272
|
-
|
|
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
|
+
let sourceScanFlight = null;
|
|
5285
|
+
let sourceScanReady = null;
|
|
5286
|
+
const { LocalVault, loadOrCreateVaultMasterKey } = await loadVaultModule();
|
|
5287
|
+
const vault = await LocalVault.open({
|
|
5288
|
+
root: resolve(dharmaHome(), 'vault'),
|
|
5289
|
+
masterKey: await loadOrCreateVaultMasterKey(),
|
|
5290
|
+
rawLocalDays: rawLocalRetentionDays(policy),
|
|
5291
|
+
});
|
|
5292
|
+
let skillPreparationsCompleted = 0;
|
|
5293
|
+
let skillPreparationFailures = 0;
|
|
5294
|
+
let skillActivationsCompleted = 0;
|
|
5295
|
+
let skillActivationFailures = 0;
|
|
5296
|
+
const skillActivationFailuresByProvider = {};
|
|
5297
|
+
let nextSkillActivationAt = 0;
|
|
5298
|
+
const skillPreparationPump = startSkillPreparationPump({
|
|
5299
|
+
prepare: async (assertRunning) => {
|
|
5300
|
+
await prepareProvidersIndependently(providerAdapters, assertRunning, async (adapter) => {
|
|
5301
|
+
assertRunning();
|
|
5302
|
+
const capability = await adapter.capability();
|
|
5303
|
+
assertRunning();
|
|
5304
|
+
if (capability.skillInstall === 'unavailable' || !isLocalProviderId(adapter.providerId))
|
|
5280
5305
|
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,
|
|
5306
|
+
const provider = adapter.providerId;
|
|
5307
|
+
await withSkillPreparationTransaction({ home: dharmaHome(), workspaceId: canonicalWorkspace.workspaceId,
|
|
5308
|
+
provider, assertCurrent: assertRunning }, async () => {
|
|
5309
|
+
const prepared = await prepareSkillUpdate({ workspaceId: canonicalWorkspace.workspaceId,
|
|
5310
|
+
provider, policyPath, fabric, automatic: true, assertRunning });
|
|
5311
|
+
if (!prepared) {
|
|
5312
|
+
assertRunning();
|
|
5313
|
+
return;
|
|
5314
|
+
}
|
|
5315
|
+
let retained = false;
|
|
5316
|
+
try {
|
|
5317
|
+
prepared.assertCurrent();
|
|
5318
|
+
const repositoryAgentId = prepared.workspace.repositoryAgentId;
|
|
5319
|
+
if (!repositoryAgentId)
|
|
5320
|
+
throw new Error('Preparation cache requires an enrolled repository agent.');
|
|
5321
|
+
const pendingRecord = await serializeSkillPreparationRecord({
|
|
5322
|
+
schema: 'dharma.skill-preparation/v1', organizationId: prepared.config.organizationId,
|
|
5323
|
+
deviceId: prepared.config.deviceId, workspaceId: prepared.workspace.workspaceId,
|
|
5304
5324
|
repositoryAgentId,
|
|
5305
|
-
repositoryBindingId: prepared.workspace.repositoryBindingId ?? null,
|
|
5325
|
+
repositoryBindingId: prepared.workspace.repositoryBindingId ?? null, provider,
|
|
5306
5326
|
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
|
-
|
|
5324
|
-
|
|
5325
|
-
|
|
5326
|
-
|
|
5327
|
-
|
|
5328
|
-
|
|
5329
|
-
|
|
5330
|
-
|
|
5331
|
-
|
|
5332
|
-
|
|
5333
|
-
|
|
5334
|
-
|
|
5335
|
-
|
|
5336
|
-
|
|
5337
|
-
|
|
5338
|
-
|
|
5327
|
+
bundle: prepared.bundle, repositoryPackage: prepared.repositoryDelivery
|
|
5328
|
+
? { envelope: prepared.repositoryDelivery.envelope, index: prepared.repositoryDelivery.index } : null,
|
|
5329
|
+
preparedAt: new Date().toISOString(), activationAuthorized: false,
|
|
5330
|
+
});
|
|
5331
|
+
prepared.assertCurrent();
|
|
5332
|
+
await writeFile(resolve(prepared.sourceRoot, 'PREPARED.json'), pendingRecord, { mode: 0o600, flag: 'wx' });
|
|
5333
|
+
prepared.assertCurrent();
|
|
5334
|
+
await publishSkillPreparationCache({ home: dharmaHome(), workspaceId: canonicalWorkspace.workspaceId,
|
|
5335
|
+
provider, sourceRoot: prepared.sourceRoot, recordBytes: pendingRecord, assertCurrent: prepared.assertCurrent,
|
|
5336
|
+
expected: { organizationId: prepared.config.organizationId, deviceId: prepared.config.deviceId,
|
|
5337
|
+
repositoryAgentId,
|
|
5338
|
+
repositoryBindingId: prepared.workspace.repositoryBindingId ?? null,
|
|
5339
|
+
policyHash: sha256(canonicalize(prepared.policy)), rolloutId: prepared.rollout.id,
|
|
5340
|
+
bundleId: prepared.bundle.bundleId, bundleHash: prepared.bundle.bundleHash },
|
|
5341
|
+
onCommitted: () => { retained = true; } });
|
|
5342
|
+
skillPreparationsCompleted += 1;
|
|
5343
|
+
}
|
|
5344
|
+
finally {
|
|
5345
|
+
if (!retained)
|
|
5346
|
+
await rm(prepared.sourceRoot, { recursive: true, force: true });
|
|
5347
|
+
}
|
|
5348
|
+
});
|
|
5349
|
+
}, () => { skillPreparationFailures += 1; });
|
|
5350
|
+
},
|
|
5351
|
+
onError: () => { skillPreparationFailures += 1; },
|
|
5352
|
+
});
|
|
5353
|
+
stopPreparation = skillPreparationPump.requestStop;
|
|
5354
|
+
if (stopping)
|
|
5355
|
+
stopPreparation();
|
|
5356
|
+
const applyReadySourceScan = async () => {
|
|
5357
|
+
if (!sourceScanReady)
|
|
5358
|
+
return;
|
|
5359
|
+
const ready = sourceScanReady;
|
|
5360
|
+
sourceScanReady = null;
|
|
5361
|
+
sourceScanFlight = null;
|
|
5362
|
+
if ('error' in ready) {
|
|
5363
|
+
repositorySourceWatcher.invalidate();
|
|
5364
|
+
repositorySourceFailures += 1;
|
|
5365
|
+
repositorySourceState = 'blocked';
|
|
5366
|
+
nextRepositorySourceScanAt = performance.now() + 60_000;
|
|
5367
|
+
return;
|
|
5339
5368
|
}
|
|
5340
|
-
if (
|
|
5341
|
-
|
|
5342
|
-
|
|
5343
|
-
|
|
5344
|
-
|
|
5345
|
-
|
|
5346
|
-
|
|
5347
|
-
|
|
5348
|
-
|
|
5349
|
-
|
|
5350
|
-
|
|
5369
|
+
if ('state' in ready) {
|
|
5370
|
+
repositorySourceState = ready.state;
|
|
5371
|
+
nextRepositorySourceScanAt = performance.now() + 15_000;
|
|
5372
|
+
return;
|
|
5373
|
+
}
|
|
5374
|
+
const sourceCycle = ready.cycle;
|
|
5375
|
+
repositorySourceState = sourceCycle.state;
|
|
5376
|
+
if (sourceCycle.localMutation) {
|
|
5377
|
+
repositorySourceCandidates += 1;
|
|
5378
|
+
if (canonicalWorkspace.repositoryPackage && sourceCycle.persisted) {
|
|
5379
|
+
canonicalWorkspace.repositoryPackage.localBaselineSnapshotHash = sourceCycle.persisted.snapshotHash;
|
|
5380
|
+
if (sourceCycle.state === 'source_already_published') {
|
|
5381
|
+
canonicalWorkspace.repositoryPackage.publishedLocalSnapshotHash = sourceCycle.persisted.snapshotHash;
|
|
5382
|
+
canonicalWorkspace.repositoryPackage.pendingLocalSnapshotHash = null;
|
|
5383
|
+
canonicalWorkspace.repositoryPackage.pendingLocalOperationId = null;
|
|
5384
|
+
publishedLocalSnapshotHash = sourceCycle.persisted.snapshotHash;
|
|
5385
|
+
}
|
|
5386
|
+
else if (sourceCycle.candidate) {
|
|
5387
|
+
const receipt = sourceCycle.candidate;
|
|
5388
|
+
if (typeof receipt.candidateId === 'string' && typeof receipt.operationId === 'string'
|
|
5389
|
+
&& typeof receipt.snapshotHash === 'string' && sourceCycle.submittedManifestHash) {
|
|
5390
|
+
Object.assign(canonicalWorkspace.repositoryPackage, {
|
|
5391
|
+
state: receipt.state, candidateId: receipt.candidateId, operationId: receipt.operationId,
|
|
5392
|
+
snapshotHash: receipt.snapshotHash, sourceManifestHash: sourceCycle.submittedManifestHash,
|
|
5393
|
+
releaseId: typeof receipt.releaseId === 'string' ? receipt.releaseId : null,
|
|
5394
|
+
consolidationMode: 'repository_update',
|
|
5395
|
+
});
|
|
5396
|
+
}
|
|
5397
|
+
if (receipt.state === 'published') {
|
|
5398
|
+
canonicalWorkspace.repositoryPackage.publishedLocalSnapshotHash = sourceCycle.persisted.snapshotHash;
|
|
5399
|
+
canonicalWorkspace.repositoryPackage.pendingLocalSnapshotHash = null;
|
|
5400
|
+
canonicalWorkspace.repositoryPackage.pendingLocalOperationId = null;
|
|
5401
|
+
publishedLocalSnapshotHash = sourceCycle.persisted.snapshotHash;
|
|
5402
|
+
}
|
|
5403
|
+
else if ((receipt.state === 'accepted' || receipt.state === 'processing')
|
|
5404
|
+
&& typeof receipt.operationId === 'string') {
|
|
5405
|
+
canonicalWorkspace.repositoryPackage.pendingLocalSnapshotHash = sourceCycle.persisted.snapshotHash;
|
|
5406
|
+
canonicalWorkspace.repositoryPackage.pendingLocalOperationId = receipt.operationId;
|
|
5351
5407
|
}
|
|
5352
|
-
|
|
5353
|
-
|
|
5354
|
-
state: current.state, candidateId: current.candidateId, operationId: current.operationId,
|
|
5355
|
-
snapshotHash: current.snapshotHash, releaseId: current.releaseId,
|
|
5356
|
-
...baseline,
|
|
5357
|
-
generation: current.state === 'published' ? Math.max(1, canonicalWorkspace.repositoryPackage.generation) : canonicalWorkspace.repositoryPackage.generation } };
|
|
5358
|
-
publishedLocalSnapshotHash = baseline.publishedLocalSnapshotHash;
|
|
5359
|
-
if (current.state === 'blocked')
|
|
5408
|
+
else if (receipt.state === 'blocked') {
|
|
5409
|
+
canonicalWorkspace.repositoryPackage.localBaselineSnapshotHash = publishedLocalSnapshotHash;
|
|
5360
5410
|
repositorySourceWatcher.invalidate();
|
|
5361
|
-
|
|
5411
|
+
}
|
|
5362
5412
|
}
|
|
5363
|
-
|
|
5364
|
-
catch {
|
|
5365
|
-
repositorySourceFailures += 1;
|
|
5413
|
+
await saveWorkspaceRecord(canonicalWorkspace);
|
|
5366
5414
|
}
|
|
5367
5415
|
}
|
|
5368
|
-
|
|
5369
|
-
|
|
5370
|
-
|
|
5371
|
-
|
|
5372
|
-
|
|
5373
|
-
|
|
5374
|
-
|
|
5375
|
-
|
|
5376
|
-
|
|
5377
|
-
|
|
5378
|
-
|
|
5379
|
-
|
|
5380
|
-
|
|
5381
|
-
|
|
5382
|
-
|
|
5383
|
-
|
|
5384
|
-
|
|
5385
|
-
evidenceResponsesCompleted += 1;
|
|
5386
|
-
}
|
|
5416
|
+
nextRepositorySourceScanAt = performance.now() + (sourceCycle.state === 'debouncing' ? 15_000 : 60_000);
|
|
5417
|
+
};
|
|
5418
|
+
const consumeSourceScan = async () => {
|
|
5419
|
+
try {
|
|
5420
|
+
await applyReadySourceScan();
|
|
5421
|
+
}
|
|
5422
|
+
catch {
|
|
5423
|
+
repositorySourceWatcher.invalidate();
|
|
5424
|
+
repositorySourceFailures += 1;
|
|
5425
|
+
repositorySourceState = 'blocked';
|
|
5426
|
+
nextRepositorySourceScanAt = performance.now() + 60_000;
|
|
5427
|
+
}
|
|
5428
|
+
};
|
|
5429
|
+
try {
|
|
5430
|
+
const recoveredTaskTrajectories = await deferUnavailableRelayRetention(async () => finalizeRecoveredSignedTaskTrajectories(fabric, policy));
|
|
5431
|
+
if (recoveredTaskTrajectories.state === 'completed') {
|
|
5432
|
+
taskTrajectoriesRecovered += recoveredTaskTrajectories.value.length;
|
|
5387
5433
|
}
|
|
5388
|
-
|
|
5389
|
-
|
|
5390
|
-
|
|
5391
|
-
if (typeof result.failureCategory === 'string')
|
|
5392
|
-
lastProviderFailureCategory = result.failureCategory;
|
|
5393
|
-
if (!result.taskId && performance.now() >= nextSkillActivationAt) {
|
|
5394
|
-
for (const adapter of providerAdapters) {
|
|
5434
|
+
do {
|
|
5435
|
+
await consumeSourceScan();
|
|
5436
|
+
if (Date.now() >= nextPolicyRefreshAt) {
|
|
5395
5437
|
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
|
-
});
|
|
5438
|
+
await syncWorkspacePolicy(fabric, canonicalWorkspace, policy.revision, true);
|
|
5439
|
+
policy = await loadVerifiedWorkspacePolicy(policyPath, canonicalWorkspace.workspaceId);
|
|
5440
|
+
evidencePolicyFresh = true;
|
|
5417
5441
|
}
|
|
5418
5442
|
catch {
|
|
5419
|
-
|
|
5420
|
-
skillActivationFailuresByProvider[adapter.providerId] = (skillActivationFailuresByProvider[adapter.providerId] || 0) + 1;
|
|
5443
|
+
evidencePolicyFresh = false;
|
|
5421
5444
|
}
|
|
5445
|
+
nextPolicyRefreshAt = Date.now() + 60_000;
|
|
5422
5446
|
}
|
|
5423
|
-
|
|
5424
|
-
|
|
5425
|
-
|
|
5426
|
-
|
|
5427
|
-
|
|
5428
|
-
|
|
5447
|
+
if (!sourceScanFlight && canonicalWorkspace.repositoryBindingId && canonicalWorkspace.repositoryAgentId) {
|
|
5448
|
+
try {
|
|
5449
|
+
const current = await pollRepositoryCandidate({ transport: fabric,
|
|
5450
|
+
outboxRoot: resolve(dharmaHome(), 'relay', 'repository-candidates'),
|
|
5451
|
+
scope: { organizationId: canonicalWorkspace.organizationId, workspaceId: canonicalWorkspace.workspaceId,
|
|
5452
|
+
repositoryBindingId: canonicalWorkspace.repositoryBindingId,
|
|
5453
|
+
repositoryAgentId: canonicalWorkspace.repositoryAgentId } });
|
|
5454
|
+
if (current && canonicalWorkspace.repositoryPackage) {
|
|
5455
|
+
if (current.candidateId !== canonicalWorkspace.repositoryPackage.candidateId
|
|
5456
|
+
|| current.operationId !== canonicalWorkspace.repositoryPackage.operationId) {
|
|
5457
|
+
throw new Error('Repository candidate status changed identity while polling.');
|
|
5458
|
+
}
|
|
5459
|
+
const baseline = advanceRepositorySourceBaseline(canonicalWorkspace.repositoryPackage, current);
|
|
5460
|
+
canonicalWorkspace = { ...canonicalWorkspace, repositoryPackage: { ...canonicalWorkspace.repositoryPackage,
|
|
5461
|
+
state: current.state, candidateId: current.candidateId, operationId: current.operationId,
|
|
5462
|
+
snapshotHash: current.snapshotHash, releaseId: current.releaseId,
|
|
5463
|
+
...baseline,
|
|
5464
|
+
generation: current.state === 'published' ? Math.max(1, canonicalWorkspace.repositoryPackage.generation) : canonicalWorkspace.repositoryPackage.generation } };
|
|
5465
|
+
publishedLocalSnapshotHash = baseline.publishedLocalSnapshotHash;
|
|
5466
|
+
if (current.state === 'blocked')
|
|
5467
|
+
repositorySourceWatcher.invalidate();
|
|
5468
|
+
await saveWorkspaceRecord(canonicalWorkspace);
|
|
5469
|
+
}
|
|
5429
5470
|
}
|
|
5430
|
-
|
|
5431
|
-
|
|
5471
|
+
catch {
|
|
5472
|
+
repositorySourceFailures += 1;
|
|
5432
5473
|
}
|
|
5433
|
-
|
|
5434
|
-
|
|
5435
|
-
|
|
5436
|
-
|
|
5474
|
+
}
|
|
5475
|
+
let evidenceRequestId;
|
|
5476
|
+
if (evidencePolicyFresh) {
|
|
5477
|
+
const evidenceCycle = await deferUnavailableRelayRetention(async () => {
|
|
5478
|
+
const synced = await syncPendingRetentionCapsules(vault, fabric, policy, canonicalWorkspace.workspaceId);
|
|
5479
|
+
const evidence = await processEvidenceRequest(fabric, policy, canonicalWorkspace.workspaceId);
|
|
5480
|
+
return {
|
|
5481
|
+
synced,
|
|
5482
|
+
evidenceRequestId: typeof evidence.requestId === 'string' ? evidence.requestId : undefined,
|
|
5483
|
+
};
|
|
5484
|
+
});
|
|
5485
|
+
if (evidenceCycle.state === 'deferred') {
|
|
5486
|
+
evidencePolicyFresh = false;
|
|
5437
5487
|
}
|
|
5438
5488
|
else {
|
|
5439
|
-
|
|
5440
|
-
|
|
5441
|
-
|
|
5442
|
-
|
|
5489
|
+
trajectorySyncsCompleted += evidenceCycle.value.synced;
|
|
5490
|
+
evidenceRequestId = evidenceCycle.value.evidenceRequestId;
|
|
5491
|
+
if (evidenceRequestId)
|
|
5492
|
+
evidenceResponsesCompleted += 1;
|
|
5493
|
+
}
|
|
5494
|
+
}
|
|
5495
|
+
const result = await executeOneTask(fabric, leaseSeconds);
|
|
5496
|
+
if (result.taskId)
|
|
5497
|
+
tasksCompleted += 1;
|
|
5498
|
+
if (typeof result.failureCategory === 'string')
|
|
5499
|
+
lastProviderFailureCategory = result.failureCategory;
|
|
5500
|
+
if (!result.taskId && performance.now() >= nextSkillActivationAt) {
|
|
5501
|
+
for (const adapter of providerAdapters) {
|
|
5502
|
+
try {
|
|
5503
|
+
const capability = await adapter.capability();
|
|
5504
|
+
if (capability.skillInstall === 'unavailable' || !isLocalProviderId(adapter.providerId))
|
|
5505
|
+
continue;
|
|
5506
|
+
const provider = adapter.providerId;
|
|
5507
|
+
await withWorkspaceSkillActivationLock(canonicalWorkspace.workspaceId, provider, async () => {
|
|
5508
|
+
await withSkillPreparationTransaction({ home: dharmaHome(), workspaceId: canonicalWorkspace.workspaceId,
|
|
5509
|
+
provider, assertCurrent: () => { if (stopping)
|
|
5510
|
+
throw new Error('Relay is stopping.'); } }, async () => {
|
|
5511
|
+
const cached = await takeCachedSkillUpdate({ workspaceId: canonicalWorkspace.workspaceId,
|
|
5512
|
+
provider, policyPath, fabric });
|
|
5513
|
+
const prepared = cached || await prepareSkillUpdate({ workspaceId: canonicalWorkspace.workspaceId,
|
|
5514
|
+
provider, policyPath, fabric, automatic: true });
|
|
5515
|
+
if (!prepared)
|
|
5516
|
+
return;
|
|
5517
|
+
if (!cached)
|
|
5518
|
+
skillPreparationsCompleted += 1;
|
|
5519
|
+
await activatePreparedSkillUpdate({ prepared, workspaceId: canonicalWorkspace.workspaceId,
|
|
5520
|
+
provider, policyPath, flags: new Map() });
|
|
5521
|
+
skillActivationsCompleted += 1;
|
|
5522
|
+
});
|
|
5523
|
+
});
|
|
5443
5524
|
}
|
|
5444
|
-
|
|
5445
|
-
|
|
5446
|
-
|
|
5447
|
-
|
|
5448
|
-
|
|
5449
|
-
|
|
5450
|
-
|
|
5451
|
-
|
|
5452
|
-
|
|
5453
|
-
|
|
5454
|
-
|
|
5455
|
-
|
|
5456
|
-
|
|
5457
|
-
|
|
5458
|
-
|
|
5459
|
-
|
|
5460
|
-
|
|
5461
|
-
|
|
5462
|
-
|
|
5463
|
-
|
|
5464
|
-
|
|
5465
|
-
|
|
5466
|
-
|
|
5467
|
-
|
|
5468
|
-
|
|
5469
|
-
|
|
5470
|
-
else if (sourceCycle.candidate) {
|
|
5471
|
-
const receipt = sourceCycle.candidate;
|
|
5472
|
-
if (typeof receipt.candidateId === 'string' && typeof receipt.operationId === 'string'
|
|
5473
|
-
&& typeof receipt.snapshotHash === 'string' && sourceCycle.submittedManifestHash) {
|
|
5474
|
-
Object.assign(canonicalWorkspace.repositoryPackage, {
|
|
5475
|
-
state: receipt.state, candidateId: receipt.candidateId, operationId: receipt.operationId,
|
|
5476
|
-
snapshotHash: receipt.snapshotHash, sourceManifestHash: sourceCycle.submittedManifestHash,
|
|
5477
|
-
releaseId: typeof receipt.releaseId === 'string' ? receipt.releaseId : null,
|
|
5478
|
-
consolidationMode: 'repository_update',
|
|
5479
|
-
});
|
|
5480
|
-
}
|
|
5481
|
-
if (receipt.state === 'published') {
|
|
5482
|
-
canonicalWorkspace.repositoryPackage.publishedLocalSnapshotHash = sourceCycle.persisted.snapshotHash;
|
|
5483
|
-
canonicalWorkspace.repositoryPackage.pendingLocalSnapshotHash = null;
|
|
5484
|
-
canonicalWorkspace.repositoryPackage.pendingLocalOperationId = null;
|
|
5485
|
-
publishedLocalSnapshotHash = sourceCycle.persisted.snapshotHash;
|
|
5486
|
-
}
|
|
5487
|
-
else if ((receipt.state === 'accepted' || receipt.state === 'processing')
|
|
5488
|
-
&& typeof receipt.operationId === 'string') {
|
|
5489
|
-
canonicalWorkspace.repositoryPackage.pendingLocalSnapshotHash = sourceCycle.persisted.snapshotHash;
|
|
5490
|
-
canonicalWorkspace.repositoryPackage.pendingLocalOperationId = receipt.operationId;
|
|
5491
|
-
}
|
|
5492
|
-
else if (receipt.state === 'blocked') {
|
|
5493
|
-
canonicalWorkspace.repositoryPackage.localBaselineSnapshotHash = publishedLocalSnapshotHash;
|
|
5525
|
+
catch {
|
|
5526
|
+
skillActivationFailures += 1;
|
|
5527
|
+
skillActivationFailuresByProvider[adapter.providerId] = (skillActivationFailuresByProvider[adapter.providerId] || 0) + 1;
|
|
5528
|
+
}
|
|
5529
|
+
}
|
|
5530
|
+
nextSkillActivationAt = performance.now() + 60_000;
|
|
5531
|
+
}
|
|
5532
|
+
if (!sourceScanFlight && performance.now() >= nextRepositorySourceScanAt) {
|
|
5533
|
+
const sourceWorkspace = canonicalWorkspace;
|
|
5534
|
+
const sourceBaselineHash = publishedLocalSnapshotHash;
|
|
5535
|
+
sourceScanFlight = (async () => {
|
|
5536
|
+
try {
|
|
5537
|
+
if (!repositorySourceBaselineAvailable) {
|
|
5538
|
+
throw new Error('Repository source baseline is unavailable; refusing an unanchored update.');
|
|
5539
|
+
}
|
|
5540
|
+
if (!sourceWorkspace.repositoryBindingId || !sourceWorkspace.repositoryAgentId) {
|
|
5541
|
+
throw new Error('Repository source synchronization requires a complete repository binding.');
|
|
5542
|
+
}
|
|
5543
|
+
const installedKnowledge = await installedRepositoryKnowledge(sourceWorkspace);
|
|
5544
|
+
if (!installedKnowledge && (sourceWorkspace.repositoryPackage?.generation ?? 0) > 0) {
|
|
5545
|
+
sourceScanReady = { state: 'awaiting_signed_baseline' };
|
|
5546
|
+
}
|
|
5547
|
+
else {
|
|
5548
|
+
if (sourceWorkspace.repositoryPackage?.state === 'blocked' && sourceWorkspace.repositoryPackage.snapshotHash) {
|
|
5549
|
+
const blocked = await readRepositorySourceBaselineSnapshot(sourceWorkspace.path, sourceWorkspace.repositoryPackage.snapshotHash);
|
|
5550
|
+
if (blockedRepositorySourceRetry.consider(sourceWorkspace.repositoryPackage.candidateId ?? '', blocked.manifest.knowledge, installedKnowledge))
|
|
5494
5551
|
repositorySourceWatcher.invalidate();
|
|
5495
|
-
}
|
|
5496
5552
|
}
|
|
5497
|
-
await
|
|
5553
|
+
const sourceCycle = await scanRepositorySourceChanges({
|
|
5554
|
+
workspace: sourceWorkspace.path, organizationId: sourceWorkspace.organizationId,
|
|
5555
|
+
workspaceId: sourceWorkspace.workspaceId, repositoryBindingId: sourceWorkspace.repositoryBindingId,
|
|
5556
|
+
repositoryAgentId: sourceWorkspace.repositoryAgentId, transport: fabric, watcher: repositorySourceWatcher,
|
|
5557
|
+
loadRetainedKnowledge: () => installedRepositoryKnowledge(sourceWorkspace),
|
|
5558
|
+
loadPublishedLocalBaseline: () => sourceBaselineHash
|
|
5559
|
+
? readRepositoryPackageSnapshot(sourceWorkspace.path, sourceBaselineHash) : Promise.resolve(null),
|
|
5560
|
+
submitCandidate: (snapshot, expectedLatestSourceFingerprint) => synchronizeRepositoryCandidate({ transport: fabric,
|
|
5561
|
+
outboxRoot: resolve(dharmaHome(), 'relay', 'repository-candidates'),
|
|
5562
|
+
scope: { organizationId: sourceWorkspace.organizationId,
|
|
5563
|
+
workspaceId: sourceWorkspace.workspaceId,
|
|
5564
|
+
repositoryBindingId: sourceWorkspace.repositoryBindingId,
|
|
5565
|
+
repositoryAgentId: sourceWorkspace.repositoryAgentId },
|
|
5566
|
+
snapshot, initialRepository: false, expectedLatestSourceFingerprint }),
|
|
5567
|
+
});
|
|
5568
|
+
sourceScanReady = { cycle: sourceCycle };
|
|
5498
5569
|
}
|
|
5499
5570
|
}
|
|
5500
|
-
|
|
5501
|
-
|
|
5502
|
-
|
|
5503
|
-
|
|
5504
|
-
repositorySourceWatcher.invalidate();
|
|
5505
|
-
repositorySourceFailures += 1;
|
|
5506
|
-
repositorySourceState = 'blocked';
|
|
5507
|
-
nextRepositorySourceScanAt = performance.now() + 60_000;
|
|
5571
|
+
catch (error) {
|
|
5572
|
+
sourceScanReady = { error };
|
|
5573
|
+
}
|
|
5574
|
+
})();
|
|
5508
5575
|
}
|
|
5509
|
-
|
|
5510
|
-
|
|
5511
|
-
|
|
5512
|
-
|
|
5513
|
-
|
|
5514
|
-
}
|
|
5576
|
+
if (flags.has('once'))
|
|
5577
|
+
break;
|
|
5578
|
+
if (!result.taskId && !evidenceRequestId)
|
|
5579
|
+
await new Promise((accept) => setTimeout(accept, pollMs));
|
|
5580
|
+
} while (!stopping);
|
|
5581
|
+
}
|
|
5582
|
+
finally {
|
|
5583
|
+
if (sourceScanFlight)
|
|
5584
|
+
await sourceScanFlight;
|
|
5585
|
+
await consumeSourceScan();
|
|
5586
|
+
await skillPreparationPump.stop();
|
|
5587
|
+
vault.close();
|
|
5588
|
+
process.removeListener('SIGINT', stop);
|
|
5589
|
+
process.removeListener('SIGTERM', stop);
|
|
5590
|
+
}
|
|
5591
|
+
return {
|
|
5592
|
+
ok: true, stopped: true, tasksCompleted, taskTrajectoriesRecovered,
|
|
5593
|
+
lastProviderFailureCategory,
|
|
5594
|
+
evidenceResponsesCompleted, trajectorySyncsCompleted,
|
|
5595
|
+
repositorySourceCandidates, repositorySourceFailures, repositorySourceState,
|
|
5596
|
+
skillPreparationsCompleted, skillPreparationFailures, skillActivationsCompleted, skillActivationFailures,
|
|
5597
|
+
skillActivationFailuresByProvider,
|
|
5598
|
+
sharedRepositoryReady: await repositorySharedReady(canonicalWorkspace),
|
|
5599
|
+
};
|
|
5515
5600
|
}
|
|
5516
5601
|
finally {
|
|
5517
|
-
await
|
|
5518
|
-
vault.close();
|
|
5519
|
-
process.removeListener('SIGINT', stop);
|
|
5520
|
-
process.removeListener('SIGTERM', stop);
|
|
5521
|
-
await rm(pidPath, { force: true });
|
|
5602
|
+
await releaseRelayLease();
|
|
5522
5603
|
}
|
|
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
5604
|
}
|
|
5533
5605
|
export async function run(argv) {
|
|
5534
5606
|
const { positional, flags, repeated } = parseCliOptions(argv);
|