@dharma-ai-labs/agent-fabric 0.2.49 → 0.2.50
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 +88 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +826 -175
- package/dist/index.js.map +1 -1
- package/dist/index.test.js +2 -2
- package/dist/repositoryCandidateSync.d.ts +55 -0
- package/dist/repositoryCandidateSync.d.ts.map +1 -0
- package/dist/repositoryCandidateSync.js +177 -0
- package/dist/repositoryCandidateSync.js.map +1 -0
- package/dist/repositoryCandidateSync.test.d.ts +2 -0
- package/dist/repositoryCandidateSync.test.d.ts.map +1 -0
- package/dist/repositoryCandidateSync.test.js +141 -0
- package/dist/repositoryCandidateSync.test.js.map +1 -0
- package/dist/repositoryInstalledKnowledge.d.ts +13 -0
- package/dist/repositoryInstalledKnowledge.d.ts.map +1 -0
- package/dist/repositoryInstalledKnowledge.js +25 -0
- package/dist/repositoryInstalledKnowledge.js.map +1 -0
- package/dist/repositoryInstallerFiles.d.ts +6 -0
- package/dist/repositoryInstallerFiles.d.ts.map +1 -0
- package/dist/repositoryInstallerFiles.js +113 -0
- package/dist/repositoryInstallerFiles.js.map +1 -0
- package/dist/repositoryKnowledge.d.ts +119 -0
- package/dist/repositoryKnowledge.d.ts.map +1 -0
- package/dist/repositoryKnowledge.js +462 -0
- package/dist/repositoryKnowledge.js.map +1 -0
- package/dist/repositoryKnowledge.test.d.ts +2 -0
- package/dist/repositoryKnowledge.test.d.ts.map +1 -0
- package/dist/repositoryKnowledge.test.js +245 -0
- package/dist/repositoryKnowledge.test.js.map +1 -0
- package/dist/repositoryKnowledgeRetention.test.d.ts +2 -0
- package/dist/repositoryKnowledgeRetention.test.d.ts.map +1 -0
- package/dist/repositoryKnowledgeRetention.test.js +386 -0
- package/dist/repositoryKnowledgeRetention.test.js.map +1 -0
- package/dist/repositoryOnboardingCaller.test.d.ts +2 -0
- package/dist/repositoryOnboardingCaller.test.d.ts.map +1 -0
- package/dist/repositoryOnboardingCaller.test.js +197 -0
- package/dist/repositoryOnboardingCaller.test.js.map +1 -0
- package/dist/repositoryOnboardingFlow.test.d.ts +2 -0
- package/dist/repositoryOnboardingFlow.test.d.ts.map +1 -0
- package/dist/repositoryOnboardingFlow.test.js +47 -0
- package/dist/repositoryOnboardingFlow.test.js.map +1 -0
- package/dist/repositoryPackage.d.ts +115 -0
- package/dist/repositoryPackage.d.ts.map +1 -0
- package/dist/repositoryPackage.js +1095 -0
- package/dist/repositoryPackage.js.map +1 -0
- package/dist/repositoryPackage.test.d.ts +2 -0
- package/dist/repositoryPackage.test.d.ts.map +1 -0
- package/dist/repositoryPackage.test.js +709 -0
- package/dist/repositoryPackage.test.js.map +1 -0
- package/dist/repositoryPackageDelivery.d.ts +29 -0
- package/dist/repositoryPackageDelivery.d.ts.map +1 -0
- package/dist/repositoryPackageDelivery.js +140 -0
- package/dist/repositoryPackageDelivery.js.map +1 -0
- package/dist/repositoryPackageDelivery.test.d.ts +2 -0
- package/dist/repositoryPackageDelivery.test.d.ts.map +1 -0
- package/dist/repositoryPackageDelivery.test.js +787 -0
- package/dist/repositoryPackageDelivery.test.js.map +1 -0
- package/dist/repositoryPackageDeliveryCaller.test.d.ts +2 -0
- package/dist/repositoryPackageDeliveryCaller.test.d.ts.map +1 -0
- package/dist/repositoryPackageDeliveryCaller.test.js +18 -0
- package/dist/repositoryPackageDeliveryCaller.test.js.map +1 -0
- package/dist/repositoryPackageDeliveryPreflight.test.d.ts +2 -0
- package/dist/repositoryPackageDeliveryPreflight.test.d.ts.map +1 -0
- package/dist/repositoryPackageDeliveryPreflight.test.js +29 -0
- package/dist/repositoryPackageDeliveryPreflight.test.js.map +1 -0
- package/dist/repositoryPackageTransfer.d.ts +85 -0
- package/dist/repositoryPackageTransfer.d.ts.map +1 -0
- package/dist/repositoryPackageTransfer.js +233 -0
- package/dist/repositoryPackageTransfer.js.map +1 -0
- package/dist/repositoryPackageTransfer.test.d.ts +2 -0
- package/dist/repositoryPackageTransfer.test.d.ts.map +1 -0
- package/dist/repositoryPackageTransfer.test.js +173 -0
- package/dist/repositoryPackageTransfer.test.js.map +1 -0
- package/dist/repositoryPackageTransferV2.test.d.ts +2 -0
- package/dist/repositoryPackageTransferV2.test.d.ts.map +1 -0
- package/dist/repositoryPackageTransferV2.test.js +549 -0
- package/dist/repositoryPackageTransferV2.test.js.map +1 -0
- package/dist/repositoryPackageTransferV2Schema.test.d.ts +2 -0
- package/dist/repositoryPackageTransferV2Schema.test.d.ts.map +1 -0
- package/dist/repositoryPackageTransferV2Schema.test.js +48 -0
- package/dist/repositoryPackageTransferV2Schema.test.js.map +1 -0
- package/dist/repositoryReleaseMetadata.d.ts +37 -0
- package/dist/repositoryReleaseMetadata.d.ts.map +1 -0
- package/dist/repositoryReleaseMetadata.js +251 -0
- package/dist/repositoryReleaseMetadata.js.map +1 -0
- package/dist/repositoryReleaseMetadata.test.d.ts +2 -0
- package/dist/repositoryReleaseMetadata.test.d.ts.map +1 -0
- package/dist/repositoryReleaseMetadata.test.js +297 -0
- package/dist/repositoryReleaseMetadata.test.js.map +1 -0
- package/dist/repositoryRoleDerivation.d.ts +11 -0
- package/dist/repositoryRoleDerivation.d.ts.map +1 -0
- package/dist/repositoryRoleDerivation.js +59 -0
- package/dist/repositoryRoleDerivation.js.map +1 -0
- package/dist/repositoryRoleDerivation.test.d.ts +2 -0
- package/dist/repositoryRoleDerivation.test.d.ts.map +1 -0
- package/dist/repositoryRoleDerivation.test.js +33 -0
- package/dist/repositoryRoleDerivation.test.js.map +1 -0
- package/dist/repositoryRoleMetadata.d.ts +63 -0
- package/dist/repositoryRoleMetadata.d.ts.map +1 -0
- package/dist/repositoryRoleMetadata.js +203 -0
- package/dist/repositoryRoleMetadata.js.map +1 -0
- package/dist/repositoryRoleMetadata.test.d.ts +2 -0
- package/dist/repositoryRoleMetadata.test.d.ts.map +1 -0
- package/dist/repositoryRoleMetadata.test.js +296 -0
- package/dist/repositoryRoleMetadata.test.js.map +1 -0
- package/dist/repositoryRoleQuestion.d.ts +36 -0
- package/dist/repositoryRoleQuestion.d.ts.map +1 -0
- package/dist/repositoryRoleQuestion.js +68 -0
- package/dist/repositoryRoleQuestion.js.map +1 -0
- package/dist/repositoryRoleQuestion.test.d.ts +2 -0
- package/dist/repositoryRoleQuestion.test.d.ts.map +1 -0
- package/dist/repositoryRoleQuestion.test.js +66 -0
- package/dist/repositoryRoleQuestion.test.js.map +1 -0
- package/dist/repositorySourceAuthorization.d.ts +43 -0
- package/dist/repositorySourceAuthorization.d.ts.map +1 -0
- package/dist/repositorySourceAuthorization.js +164 -0
- package/dist/repositorySourceAuthorization.js.map +1 -0
- package/dist/repositorySourceCapture.test.d.ts +2 -0
- package/dist/repositorySourceCapture.test.d.ts.map +1 -0
- package/dist/repositorySourceCapture.test.js +303 -0
- package/dist/repositorySourceCapture.test.js.map +1 -0
- package/dist/repositorySourceSync.d.ts +54 -0
- package/dist/repositorySourceSync.d.ts.map +1 -0
- package/dist/repositorySourceSync.js +88 -0
- package/dist/repositorySourceSync.js.map +1 -0
- package/dist/repositorySourceSync.test.d.ts +2 -0
- package/dist/repositorySourceSync.test.d.ts.map +1 -0
- package/dist/repositorySourceSync.test.js +218 -0
- package/dist/repositorySourceSync.test.js.map +1 -0
- package/dist/schemas/repository-knowledge-retention-v1.schema.json +26 -0
- package/dist/schemas/repository-knowledge-v2.schema.json +74 -0
- package/dist/schemas/repository-knowledge.schema.json +63 -0
- package/dist/schemas/repository-package-chunk-v2.schema.json +15 -0
- package/dist/schemas/repository-package-chunk.schema.json +18 -0
- package/dist/schemas/repository-package-delivery-v1.schema.json +51 -0
- package/dist/schemas/repository-package-envelope-v1.schema.json +16 -0
- package/dist/schemas/repository-package-transfer-v2.schema.json +36 -0
- package/dist/schemas/repository-package-transfer.schema.json +34 -0
- package/dist/schemas/repository-package-v2.schema.json +491 -0
- package/dist/schemas/repository-package-v3.schema.json +36 -0
- package/dist/schemas/repository-package.schema.json +94 -0
- package/dist/schemas/repository-release-manifest.schema.json +34 -0
- package/dist/schemas/repository-role-metadata.schema.json +82 -0
- package/dist/schemas/skill-preparation-pointer-v1.schema.json +28 -0
- package/dist/schemas/skill-preparation-v1.schema.json +32 -0
- package/dist/skillPollResponse.test.d.ts +2 -0
- package/dist/skillPollResponse.test.d.ts.map +1 -0
- package/dist/skillPollResponse.test.js +37 -0
- package/dist/skillPollResponse.test.js.map +1 -0
- package/dist/skillPreparation.test.d.ts +2 -0
- package/dist/skillPreparation.test.d.ts.map +1 -0
- package/dist/skillPreparation.test.js +163 -0
- package/dist/skillPreparation.test.js.map +1 -0
- package/dist/skillPreparationCache.d.ts +34 -0
- package/dist/skillPreparationCache.d.ts.map +1 -0
- package/dist/skillPreparationCache.js +371 -0
- package/dist/skillPreparationCache.js.map +1 -0
- package/dist/skillPreparationCache.test.d.ts +2 -0
- package/dist/skillPreparationCache.test.d.ts.map +1 -0
- package/dist/skillPreparationCache.test.js +316 -0
- package/dist/skillPreparationCache.test.js.map +1 -0
- package/dist/skillPreparationCaller.test.d.ts +2 -0
- package/dist/skillPreparationCaller.test.d.ts.map +1 -0
- package/dist/skillPreparationCaller.test.js +32 -0
- package/dist/skillPreparationCaller.test.js.map +1 -0
- package/dist/skillPreparationPump.d.ts +11 -0
- package/dist/skillPreparationPump.d.ts.map +1 -0
- package/dist/skillPreparationPump.js +41 -0
- package/dist/skillPreparationPump.js.map +1 -0
- package/dist/skillPreparationPump.test.d.ts +2 -0
- package/dist/skillPreparationPump.test.d.ts.map +1 -0
- package/dist/skillPreparationPump.test.js +87 -0
- package/dist/skillPreparationPump.test.js.map +1 -0
- package/dist/skillPreparationRecord.d.ts +2 -0
- package/dist/skillPreparationRecord.d.ts.map +1 -0
- package/dist/skillPreparationRecord.js +36 -0
- package/dist/skillPreparationRecord.js.map +1 -0
- package/dist/skillPreparationTransaction.d.ts +13 -0
- package/dist/skillPreparationTransaction.d.ts.map +1 -0
- package/dist/skillPreparationTransaction.js +236 -0
- package/dist/skillPreparationTransaction.js.map +1 -0
- package/dist/skillPreparationTransaction.test.d.ts +2 -0
- package/dist/skillPreparationTransaction.test.d.ts.map +1 -0
- package/dist/skillPreparationTransaction.test.js +181 -0
- package/dist/skillPreparationTransaction.test.js.map +1 -0
- package/dist/skillSyncPolicy.test.d.ts +2 -0
- package/dist/skillSyncPolicy.test.d.ts.map +1 -0
- package/dist/skillSyncPolicy.test.js +156 -0
- package/dist/skillSyncPolicy.test.js.map +1 -0
- package/dist/usage.d.ts +1 -1
- package/dist/usage.d.ts.map +1 -1
- package/dist/usage.js +1 -1
- package/dist/usage.js.map +1 -1
- package/dist/windowsProcessPortability.test.d.ts +2 -0
- package/dist/windowsProcessPortability.test.d.ts.map +1 -0
- package/dist/windowsProcessPortability.test.js +14 -0
- package/dist/windowsProcessPortability.test.js.map +1 -0
- package/package.json +7 -4
package/dist/index.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { execFile, spawn } from 'node:child_process';
|
|
3
3
|
import { createHash, createPrivateKey, createPublicKey, randomUUID } from 'node:crypto';
|
|
4
4
|
import { realpathSync } from 'node:fs';
|
|
5
|
-
import { access, chmod, link, mkdir, open, readFile, readdir, realpath, rename, rm, unlink, writeFile } from 'node:fs/promises';
|
|
5
|
+
import { access, chmod, link, mkdir, mkdtemp, open, readFile, readdir, realpath, rename, rm, unlink, writeFile } from 'node:fs/promises';
|
|
6
6
|
import { homedir } from 'node:os';
|
|
7
7
|
import { basename, dirname, isAbsolute, relative, resolve } from 'node:path';
|
|
8
8
|
import { fileURLToPath } from 'node:url';
|
|
@@ -13,10 +13,25 @@ import { assertPolicy, loadOrganizationPolicy, verifyServerAuthorizedPolicy } fr
|
|
|
13
13
|
import { agyAdapter, claudeAdapter, codexAdapter, hermesAdapter, providerAdapters, providerExecutionRecords, providerProcessEnvironment } from '@dharma-ai-labs/agent-fabric-provider-adapters';
|
|
14
14
|
import { AgentFabricClient, beginEnrollment, loadOrCreateDeviceIdentity, normalizeHqUrl, pollEnrollment, deleteActiveSkillAuthorizationAnchor, loadActiveSkillAuthorizationAnchor, loadDeviceEnrollmentAnchor, saveActiveSkillAuthorizationAnchor, isDefinitiveAgentFabricRejection, recoverDeviceEnrollmentConsistency, loadOrganizationApiToken, redeemBootstrapGrant, saveDeviceConfig, saveDeviceEnrollmentAnchor, saveOrganizationApiToken, } from '@dharma-ai-labs/agent-fabric-relay-client';
|
|
15
15
|
import { AgentFabricClient as AgentFabricApiClient, } from '@dharma-ai-labs/agent-fabric-sdk';
|
|
16
|
-
import { getActiveSkillBundleAuthorization, getExpiredSkillBundleAuthorizationForReplacement, getLegacySkillBundleIdForUpgrade, installSkillBundle, rollbackUnconfirmedSkillBundle, verifySkillBundle } from '@dharma-ai-labs/agent-fabric-skill-manager';
|
|
16
|
+
import { contentHash, getActiveSkillBundleAuthorization, getExpiredSkillBundleAuthorizationForReplacement, getLegacySkillBundleIdForUpgrade, installSkillBundle, readVerifiedRepositoryKnowledge, rollbackUnconfirmedSkillBundle, verifySkillBundle } from '@dharma-ai-labs/agent-fabric-skill-manager';
|
|
17
17
|
import { executeTask, FileActionExecutionJournal, FileTaskReceiptStore, providerInstructionsForTask, } from '@dharma-ai-labs/agent-fabric-task-runner';
|
|
18
18
|
import { CLI_USAGE } from './usage.js';
|
|
19
|
-
|
|
19
|
+
import { initializeRepositoryKnowledge } from './repositoryKnowledge.js';
|
|
20
|
+
import { inventoryRepositoryPackage, writeRepositoryPackageSnapshot } from './repositoryPackage.js';
|
|
21
|
+
import { validateRepositorySourceAuthorization } from './repositorySourceAuthorization.js';
|
|
22
|
+
import { fetchRepositorySourceAuthorization, RepositorySourceWatcher, scanRepositorySourceChanges } from './repositorySourceSync.js';
|
|
23
|
+
import { assertRepositoryInstallerOwnership, writeRepositoryInstallerFile } from './repositoryInstallerFiles.js';
|
|
24
|
+
import { receiveRepositoryPackageDelivery } from './repositoryPackageDelivery.js';
|
|
25
|
+
import { selectInstalledRepositoryKnowledge } from './repositoryInstalledKnowledge.js';
|
|
26
|
+
import { prepareProvidersIndependently, startSkillPreparationPump } from './skillPreparationPump.js';
|
|
27
|
+
import { serializeSkillPreparationRecord } from './skillPreparationRecord.js';
|
|
28
|
+
import { skillPreparationScopeRoot, withSkillPreparationTransaction } from './skillPreparationTransaction.js';
|
|
29
|
+
import { publishSkillPreparationCache, takeSkillPreparationCache } from './skillPreparationCache.js';
|
|
30
|
+
import { adoptRepositoryCandidate, pollRepositoryCandidate, synchronizeRepositoryCandidate } from './repositoryCandidateSync.js';
|
|
31
|
+
import { registerRepositoryRoleMetadata, discoverRepositoryRoleMetadata } from './repositoryRoleMetadata.js';
|
|
32
|
+
import { askRepositoryRoleQuestion, readRepositoryRoleReply } from './repositoryRoleQuestion.js';
|
|
33
|
+
import { deriveRepositoryRole } from './repositoryRoleDerivation.js';
|
|
34
|
+
const VERSION = '0.2.50';
|
|
20
35
|
const USAGE = CLI_USAGE;
|
|
21
36
|
const execFileAsync = promisify(execFile);
|
|
22
37
|
const LOCAL_PROVIDER_IDS = ['codex', 'claude', 'agy', 'hermes'];
|
|
@@ -754,7 +769,15 @@ async function assertWorkspaceAuthorizationCurrent(workspaceId, authorization, r
|
|
|
754
769
|
const statePath = workspaceAuthorizationStatePath(workspaceId);
|
|
755
770
|
let previous = null;
|
|
756
771
|
try {
|
|
757
|
-
|
|
772
|
+
const parsed = JSON.parse(await readFile(statePath, 'utf8'));
|
|
773
|
+
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)
|
|
774
|
+
|| typeof parsed.issuedAt !== 'string'
|
|
775
|
+
|| !Number.isFinite(Date.parse(parsed.issuedAt))
|
|
776
|
+
|| typeof parsed.signature !== 'string'
|
|
777
|
+
|| !parsed.signature) {
|
|
778
|
+
throw new Error('Workspace authorization replay state is missing or invalid; apply a fresh server policy.');
|
|
779
|
+
}
|
|
780
|
+
previous = parsed;
|
|
758
781
|
}
|
|
759
782
|
catch (error) {
|
|
760
783
|
if (error.code === 'ENOENT' && !requireExisting)
|
|
@@ -1464,12 +1487,32 @@ async function bootstrap(flags) {
|
|
|
1464
1487
|
onboardFlags.set('policy-revision', policyRevision);
|
|
1465
1488
|
onboardFlags.set('provider', provider);
|
|
1466
1489
|
const onboarded = await retryBootstrapOnboarding(async () => await onboard(onboardFlags));
|
|
1490
|
+
if (onboarded.ok !== true || onboarded.stage === 'approve_device') {
|
|
1491
|
+
return {
|
|
1492
|
+
ok: onboarded.ok === true,
|
|
1493
|
+
stage: onboarded.stage,
|
|
1494
|
+
code: onboarded.code,
|
|
1495
|
+
sharedRepositoryReady: false,
|
|
1496
|
+
enrollment: {
|
|
1497
|
+
organizationId,
|
|
1498
|
+
deviceId: config.deviceId,
|
|
1499
|
+
status: 'approved',
|
|
1500
|
+
organizationApiTokenStored: true,
|
|
1501
|
+
scopes: redeemed.organizationApiTokenScopes,
|
|
1502
|
+
rebind,
|
|
1503
|
+
},
|
|
1504
|
+
repository: onboarded,
|
|
1505
|
+
};
|
|
1506
|
+
}
|
|
1467
1507
|
const launcher = await installStableRepositoryLauncher(workspace);
|
|
1508
|
+
const sharedRepositoryReady = onboarded.sharedRepositoryReady === true;
|
|
1468
1509
|
const completionRequested = flags.has('complete');
|
|
1469
1510
|
if (!completionRequested) {
|
|
1470
1511
|
return {
|
|
1471
1512
|
ok: true,
|
|
1472
|
-
stage: onboarded.stage,
|
|
1513
|
+
stage: sharedRepositoryReady ? onboarded.stage : 'shared_repository_pending',
|
|
1514
|
+
localStage: onboarded.localStage ?? onboarded.stage,
|
|
1515
|
+
sharedRepositoryReady,
|
|
1473
1516
|
enrollment: {
|
|
1474
1517
|
organizationId,
|
|
1475
1518
|
deviceId: config.deviceId,
|
|
@@ -1524,7 +1567,9 @@ async function bootstrap(flags) {
|
|
|
1524
1567
|
});
|
|
1525
1568
|
return {
|
|
1526
1569
|
ok: true,
|
|
1527
|
-
stage: 'complete',
|
|
1570
|
+
stage: sharedRepositoryReady ? 'complete' : 'shared_repository_pending',
|
|
1571
|
+
localStage: 'complete',
|
|
1572
|
+
sharedRepositoryReady,
|
|
1528
1573
|
enrollment: {
|
|
1529
1574
|
organizationId,
|
|
1530
1575
|
deviceId: config.deviceId,
|
|
@@ -2366,7 +2411,22 @@ async function repositoriesConnect(flags, positional, repeatedRepos, repeatedKey
|
|
|
2366
2411
|
if (result?.stage === 'approve_device')
|
|
2367
2412
|
break;
|
|
2368
2413
|
}
|
|
2369
|
-
|
|
2414
|
+
const states = results.map(result => result);
|
|
2415
|
+
const blocked = states.filter(result => result.ok !== true).length;
|
|
2416
|
+
const connected = states.filter(result => result.ok === true && result.sharedRepositoryReady === true).length;
|
|
2417
|
+
const locallyReady = states.filter(result => result.ok === true && (['ready', 'ready_with_provider_actions', 'complete'].includes(String(result.localStage))
|
|
2418
|
+
|| ['ready', 'ready_with_provider_actions', 'complete'].includes(String(result.stage)))).length;
|
|
2419
|
+
return {
|
|
2420
|
+
ok: blocked === 0,
|
|
2421
|
+
requested: unique.length,
|
|
2422
|
+
attempted: results.length,
|
|
2423
|
+
locallyReady,
|
|
2424
|
+
connected,
|
|
2425
|
+
blocked,
|
|
2426
|
+
pending: states.length - blocked - connected,
|
|
2427
|
+
sharedRepositoryReady: connected === unique.length,
|
|
2428
|
+
repositories: results,
|
|
2429
|
+
};
|
|
2370
2430
|
}
|
|
2371
2431
|
export function parseSelectedProviderIds(values) {
|
|
2372
2432
|
const selected = [...new Set(values
|
|
@@ -2445,22 +2505,72 @@ export async function receiptAwareProviderCapabilities(providers, now = new Date
|
|
|
2445
2505
|
}
|
|
2446
2506
|
async function repositoriesList(flags) {
|
|
2447
2507
|
const verbose = flags.has('verbose') || flags.has('diagnostic');
|
|
2508
|
+
const items = await registry();
|
|
2448
2509
|
return {
|
|
2449
2510
|
ok: true,
|
|
2450
|
-
repositories:
|
|
2511
|
+
repositories: await Promise.all(items.map(async (item) => ({
|
|
2451
2512
|
workspaceId: item.workspaceId,
|
|
2452
2513
|
name: item.name,
|
|
2453
2514
|
repositoryAgentId: item.repositoryAgentId || null,
|
|
2454
2515
|
repositoryAgentKey: item.repositoryAgentKey || null,
|
|
2455
2516
|
controlBranch: item.controlBranch || null,
|
|
2456
2517
|
connected: Boolean(item.repositoryAgentId && item.controlBranch),
|
|
2518
|
+
repositoryPackageState: item.repositoryPackage?.state || 'unknown',
|
|
2519
|
+
sharedRepositoryReady: await repositorySharedReady(item),
|
|
2457
2520
|
...(verbose ? { localPath: item.path, sourceFingerprint: item.repositoryRemoteHash } : {}),
|
|
2458
|
-
})),
|
|
2521
|
+
}))),
|
|
2459
2522
|
};
|
|
2460
2523
|
}
|
|
2524
|
+
async function repositorySharedReady(item) {
|
|
2525
|
+
if (item.repositoryPackage?.state !== 'published' || !item.repositoryPackage.releaseId)
|
|
2526
|
+
return false;
|
|
2527
|
+
try {
|
|
2528
|
+
return Boolean(await installedRepositoryKnowledge(item));
|
|
2529
|
+
}
|
|
2530
|
+
catch {
|
|
2531
|
+
return false;
|
|
2532
|
+
}
|
|
2533
|
+
}
|
|
2534
|
+
export function canonicalRepositoryPackage(value) {
|
|
2535
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
2536
|
+
throw new Error('Dharma HQ did not return canonical repository package state.');
|
|
2537
|
+
}
|
|
2538
|
+
const row = value;
|
|
2539
|
+
const keys = ['state', 'candidateId', 'operationId', 'snapshotHash', 'sourceManifestHash', 'releaseId', 'generation', 'consolidationMode'];
|
|
2540
|
+
if (!keys.every(key => Object.hasOwn(row, key)) || Object.keys(row).some(key => !keys.includes(key))
|
|
2541
|
+
|| !['absent', 'accepted', 'processing', 'published', 'blocked'].includes(String(row.state))
|
|
2542
|
+
|| !Number.isSafeInteger(row.generation) || Number(row.generation) < 0) {
|
|
2543
|
+
throw new Error('Dharma HQ returned invalid canonical repository package state.');
|
|
2544
|
+
}
|
|
2545
|
+
const nullableUuid = (candidate) => candidate === null || typeof candidate === 'string' && UUID_PATTERN.test(candidate);
|
|
2546
|
+
const nullableHash = (candidate) => candidate === null || typeof candidate === 'string' && /^sha256:[a-f0-9]{64}$/.test(candidate);
|
|
2547
|
+
const absent = row.state === 'absent';
|
|
2548
|
+
if (![row.candidateId, row.releaseId].every(nullableUuid)
|
|
2549
|
+
|| ![row.operationId, row.snapshotHash, row.sourceManifestHash].every(nullableHash)
|
|
2550
|
+
|| ![null, 'initial_repository', 'repository_update'].includes(row.consolidationMode)
|
|
2551
|
+
|| absent !== (row.candidateId === null && row.operationId === null && row.snapshotHash === null
|
|
2552
|
+
&& row.sourceManifestHash === null && row.releaseId === null && row.consolidationMode === null && row.generation === 0)
|
|
2553
|
+
|| (row.state === 'published') !== (row.releaseId !== null)
|
|
2554
|
+
|| (!absent && (row.candidateId === null || row.operationId === null || row.snapshotHash === null
|
|
2555
|
+
|| row.sourceManifestHash === null || row.consolidationMode === null))) {
|
|
2556
|
+
throw new Error('Dharma HQ returned inconsistent canonical repository package state.');
|
|
2557
|
+
}
|
|
2558
|
+
return row;
|
|
2559
|
+
}
|
|
2560
|
+
export function canonicalEndpointRole(value) {
|
|
2561
|
+
if (value === null)
|
|
2562
|
+
return null;
|
|
2563
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
2564
|
+
throw new Error('Dharma HQ returned invalid endpoint role state.');
|
|
2565
|
+
const row = value;
|
|
2566
|
+
if (Object.keys(row).sort().join(',') !== 'profileHash,revision'
|
|
2567
|
+
|| !Number.isSafeInteger(row.revision) || Number(row.revision) < 1
|
|
2568
|
+
|| typeof row.profileHash !== 'string' || !/^sha256:[a-f0-9]{64}$/.test(row.profileHash)) {
|
|
2569
|
+
throw new Error('Dharma HQ returned invalid endpoint role state.');
|
|
2570
|
+
}
|
|
2571
|
+
return { revision: Number(row.revision), profileHash: row.profileHash };
|
|
2572
|
+
}
|
|
2461
2573
|
async function bindRepositoryAgent(fabric, item) {
|
|
2462
|
-
if (item.repositoryAgentId && item.repositoryBindingId && item.repositoryAgentKey && item.controlBranch)
|
|
2463
|
-
return item;
|
|
2464
2574
|
const remote = await gitValue(item.path, ['config', '--get', 'remote.origin.url']);
|
|
2465
2575
|
const currentIdentity = item.repositoryIdentityVersion === 'normalized-v1'
|
|
2466
2576
|
? item.repositoryRemoteHash
|
|
@@ -2481,17 +2591,25 @@ async function bindRepositoryAgent(fabric, item) {
|
|
|
2481
2591
|
const branch = repositoryAgent?.branch && typeof repositoryAgent.branch === 'object'
|
|
2482
2592
|
? repositoryAgent.branch
|
|
2483
2593
|
: null;
|
|
2594
|
+
const endpoint = response.endpoint && typeof response.endpoint === 'object' && !Array.isArray(response.endpoint)
|
|
2595
|
+
? response.endpoint
|
|
2596
|
+
: null;
|
|
2484
2597
|
const updated = {
|
|
2485
2598
|
...item,
|
|
2486
2599
|
repositoryRemoteHash: currentIdentity,
|
|
2487
2600
|
repositoryIdentityVersion: 'normalized-v1',
|
|
2488
2601
|
repositoryAgentId: String(repositoryAgent?.organization_agent_id || ''),
|
|
2489
2602
|
repositoryBindingId: String(repositoryAgent?.id || ''),
|
|
2603
|
+
endpointId: typeof response.endpointId === 'string' ? response.endpointId
|
|
2604
|
+
: typeof endpoint?.id === 'string' ? endpoint.id : item.endpointId || null,
|
|
2605
|
+
repositoryPackage: canonicalRepositoryPackage(response.repositoryPackage),
|
|
2606
|
+
repositoryRole: canonicalEndpointRole(response.endpointRole),
|
|
2490
2607
|
repositoryAgentKey: String(repositoryAgent?.agent_key || ''),
|
|
2491
2608
|
controlBranch: String(repositoryAgent?.control_branch || branch?.branch || ''),
|
|
2492
2609
|
};
|
|
2493
2610
|
if (!/^[0-9a-f-]{36}$/i.test(updated.repositoryAgentId || '')
|
|
2494
2611
|
|| !/^[0-9a-f-]{36}$/i.test(updated.repositoryBindingId || '')
|
|
2612
|
+
|| !UUID_PATTERN.test(updated.endpointId || '')
|
|
2495
2613
|
|| !/^repo:[a-f0-9]{24}$/.test(updated.repositoryAgentKey || '')
|
|
2496
2614
|
|| !/^agents\/[a-z0-9][a-z0-9._-]*-[a-f0-9]{8}$/.test(updated.controlBranch || '')) {
|
|
2497
2615
|
throw new Error('Dharma HQ returned an invalid repository-agent binding.');
|
|
@@ -2499,6 +2617,35 @@ async function bindRepositoryAgent(fabric, item) {
|
|
|
2499
2617
|
await saveWorkspaceRecord(updated);
|
|
2500
2618
|
return updated;
|
|
2501
2619
|
}
|
|
2620
|
+
function repositoryRoleScope(item) {
|
|
2621
|
+
if (!item.repositoryAgentId || !item.repositoryBindingId || !item.endpointId || !item.repositoryRemoteHash) {
|
|
2622
|
+
throw new Error('Repository role operations require a complete signed endpoint and repository binding.');
|
|
2623
|
+
}
|
|
2624
|
+
return { organizationId: item.organizationId, workspaceId: item.workspaceId,
|
|
2625
|
+
repositoryBindingId: item.repositoryBindingId, repositoryAgentId: item.repositoryAgentId,
|
|
2626
|
+
endpointId: item.endpointId, sourceFingerprint: item.repositoryRemoteHash };
|
|
2627
|
+
}
|
|
2628
|
+
async function repositoryRoleCommand(action, flags) {
|
|
2629
|
+
const workspaceId = required(flags, 'workspace-id');
|
|
2630
|
+
const item = (await registry()).find(candidate => candidate.workspaceId === workspaceId);
|
|
2631
|
+
if (!item)
|
|
2632
|
+
throw new Error('Repository role workspace is not registered locally.');
|
|
2633
|
+
const scope = repositoryRoleScope(item), transport = await client();
|
|
2634
|
+
if (action === 'register') {
|
|
2635
|
+
const categories = required(flags, 'question-categories').split(',').map(value => value.trim()).filter(Boolean);
|
|
2636
|
+
return registerRepositoryRoleMetadata(transport, scope, { expectedRevision: Number(flags.get('expected-revision') || 0),
|
|
2637
|
+
roleName: required(flags, 'role-name'), questionCategories: categories,
|
|
2638
|
+
description: required(flags, 'role-description') });
|
|
2639
|
+
}
|
|
2640
|
+
if (action === 'discover') {
|
|
2641
|
+
const category = typeof flags.get('category') === 'string' ? String(flags.get('category')) : undefined;
|
|
2642
|
+
return discoverRepositoryRoleMetadata(transport, scope, category);
|
|
2643
|
+
}
|
|
2644
|
+
if (action === 'ask')
|
|
2645
|
+
return askRepositoryRoleQuestion({ transport, scope,
|
|
2646
|
+
category: required(flags, 'category'), question: required(flags, 'question') });
|
|
2647
|
+
return readRepositoryRoleReply({ transport, scope, questionId: required(flags, 'question-id') });
|
|
2648
|
+
}
|
|
2502
2649
|
async function workspaceSync(flags, positional) {
|
|
2503
2650
|
const workspaceId = positional[0] || required(flags, 'workspace-id');
|
|
2504
2651
|
const policyRevision = required(flags, 'policy-revision');
|
|
@@ -2662,23 +2809,13 @@ async function loadVerifiedWorkspacePolicy(path, workspaceId) {
|
|
|
2662
2809
|
return verified;
|
|
2663
2810
|
}
|
|
2664
2811
|
export async function installRepositoryAgentFabricSkill(input) {
|
|
2812
|
+
if (input.sourceAuthorization !== undefined)
|
|
2813
|
+
input = { ...input,
|
|
2814
|
+
sourceAuthorization: validateRepositorySourceAuthorization(input.sourceAuthorization, input, new Date()) };
|
|
2815
|
+
await assertRepositoryInstallerOwnership(input.workspace, input.workspaceId);
|
|
2816
|
+
input = { ...input, workspace: await realpath(input.workspace) };
|
|
2817
|
+
await assertRepositoryInstallerOwnership(input.workspace, input.workspaceId);
|
|
2665
2818
|
const skillRoot = resolve(input.workspace, '.agents', 'skills', 'dharma-agent-fabric');
|
|
2666
|
-
const marker = resolve(skillRoot, '.dharma-agent-fabric.json');
|
|
2667
|
-
let skillRootExists = true;
|
|
2668
|
-
try {
|
|
2669
|
-
await access(skillRoot);
|
|
2670
|
-
}
|
|
2671
|
-
catch {
|
|
2672
|
-
skillRootExists = false;
|
|
2673
|
-
}
|
|
2674
|
-
if (skillRootExists) {
|
|
2675
|
-
try {
|
|
2676
|
-
await access(marker);
|
|
2677
|
-
}
|
|
2678
|
-
catch {
|
|
2679
|
-
throw new Error('Refusing to replace an unmanaged repository skill at .agents/skills/dharma-agent-fabric.');
|
|
2680
|
-
}
|
|
2681
|
-
}
|
|
2682
2819
|
await mkdir(resolve(skillRoot, 'references'), { recursive: true, mode: 0o700 });
|
|
2683
2820
|
await mkdir(resolve(input.workspace, '.dharma'), { recursive: true, mode: 0o700 });
|
|
2684
2821
|
const skill = `---
|
|
@@ -2741,17 +2878,47 @@ The CLI enrolls this device through browser-confirmed Clerk organization consent
|
|
|
2741
2878
|
controlBranch: input.controlBranch || null,
|
|
2742
2879
|
workspaceId: input.workspaceId,
|
|
2743
2880
|
};
|
|
2744
|
-
await
|
|
2745
|
-
await
|
|
2746
|
-
await
|
|
2747
|
-
|
|
2748
|
-
|
|
2881
|
+
await writeRepositoryInstallerFile(input.workspace, '.agents/skills/dharma-agent-fabric/SKILL.md', skill);
|
|
2882
|
+
await writeRepositoryInstallerFile(input.workspace, '.agents/skills/dharma-agent-fabric/references/organization.md', reference);
|
|
2883
|
+
await writeRepositoryInstallerFile(input.workspace, '.agents/skills/dharma-agent-fabric/.dharma-agent-fabric.json', `${JSON.stringify({ managedBy: 'dharma-agent-fabric', workspaceId: input.workspaceId }, null, 2)}\n`);
|
|
2884
|
+
const knowledge = input.repositoryAgentId ? await initializeRepositoryKnowledge({
|
|
2885
|
+
workspace: input.workspace, organizationId: input.organizationId, repositoryAgentId: input.repositoryAgentId,
|
|
2886
|
+
}) : null;
|
|
2887
|
+
const repositoryPackage = await writeRepositoryPackageSnapshot({
|
|
2888
|
+
workspace: input.workspace, snapshot: await inventoryRepositoryPackage(input),
|
|
2889
|
+
});
|
|
2890
|
+
await writeRepositoryInstallerFile(input.workspace, '.dharma/agent-fabric.json', `${JSON.stringify(connection, null, 2)}\n`);
|
|
2891
|
+
await writeRepositoryInstallerFile(input.workspace, '.dharma/repository-agent.json', `${JSON.stringify(repositoryAgent, null, 2)}\n`);
|
|
2749
2892
|
return {
|
|
2750
2893
|
skillPath: '.agents/skills/dharma-agent-fabric/SKILL.md',
|
|
2751
2894
|
connectionPath: '.dharma/agent-fabric.json',
|
|
2752
2895
|
repositoryAgentPath: '.dharma/repository-agent.json',
|
|
2896
|
+
repositoryPackage,
|
|
2897
|
+
knowledge,
|
|
2753
2898
|
};
|
|
2754
2899
|
}
|
|
2900
|
+
async function repositorySnapshotCommand(flags, outputs) {
|
|
2901
|
+
if (flags.has('apply') && flags.has('dry-run'))
|
|
2902
|
+
throw new Error('Choose --apply or --dry-run, not both.');
|
|
2903
|
+
if (outputs.some(value => typeof value !== 'string'))
|
|
2904
|
+
throw new Error('--approved-output requires a workspace-relative file path.');
|
|
2905
|
+
const workspace = String(flags.get('workspace') || '.');
|
|
2906
|
+
const apply = flags.get('apply') === true;
|
|
2907
|
+
if (apply) {
|
|
2908
|
+
await access(resolve(workspace, '.agents/skills/dharma-agent-fabric/.dharma-agent-fabric.json')).catch(() => {
|
|
2909
|
+
throw new Error('Snapshot --apply requires an installed managed repository skill; use --dry-run before onboarding.');
|
|
2910
|
+
});
|
|
2911
|
+
}
|
|
2912
|
+
const snapshot = await inventoryRepositoryPackage({
|
|
2913
|
+
workspace, organizationId: required(flags, 'organization-id'), workspaceId: required(flags, 'workspace-id'),
|
|
2914
|
+
repositoryAgentId: (await registry()).find(record => record.path === resolve(workspace)
|
|
2915
|
+
&& record.organizationId === flags.get('organization-id') && record.workspaceId === flags.get('workspace-id'))?.repositoryAgentId,
|
|
2916
|
+
approvedOutputs: outputs,
|
|
2917
|
+
});
|
|
2918
|
+
const persisted = apply ? await writeRepositoryPackageSnapshot({ workspace, snapshot }) : null;
|
|
2919
|
+
return { ok: true, dryRun: !apply, localMutation: apply, serverMutation: false,
|
|
2920
|
+
authority: 'local_inventory_not_signed', snapshot, persisted };
|
|
2921
|
+
}
|
|
2755
2922
|
async function onboard(flags) {
|
|
2756
2923
|
const workspace = await realpath(String(flags.get('workspace') || flags.get('path') || '.'));
|
|
2757
2924
|
const organizationId = required(flags, 'organization-id');
|
|
@@ -2809,17 +2976,98 @@ async function onboard(flags) {
|
|
|
2809
2976
|
const localPolicy = synced.localPolicy;
|
|
2810
2977
|
const authoritativeRevision = String(localPolicy?.revision || policyRevision);
|
|
2811
2978
|
const generatedPolicy = await loadVerifiedWorkspacePolicy(resolve(workspace, '.dharma', 'approved-policy.json'), registered.workspaceId);
|
|
2979
|
+
if (!registered.repositoryBindingId || !registered.repositoryAgentId) {
|
|
2980
|
+
throw new Error('Repository onboarding requires the canonical repository binding and logical agent.');
|
|
2981
|
+
}
|
|
2982
|
+
let sourceAuthorization;
|
|
2983
|
+
try {
|
|
2984
|
+
sourceAuthorization = await fetchRepositorySourceAuthorization(fabric, {
|
|
2985
|
+
organizationId, workspaceId: registered.workspaceId,
|
|
2986
|
+
repositoryBindingId: registered.repositoryBindingId, repositoryAgentId: registered.repositoryAgentId,
|
|
2987
|
+
});
|
|
2988
|
+
}
|
|
2989
|
+
catch {
|
|
2990
|
+
return { ok: false, stage: 'repository_source_authorization_required', organizationId,
|
|
2991
|
+
workspaceId: registered.workspaceId, code: 'repository_source_policy_unavailable',
|
|
2992
|
+
sharedRepositoryReady: false, localInventoryFallback: false };
|
|
2993
|
+
}
|
|
2812
2994
|
const installed = await installRepositoryAgentFabricSkill({
|
|
2813
2995
|
workspace,
|
|
2814
2996
|
hqUrl,
|
|
2815
2997
|
organizationId,
|
|
2816
2998
|
workspaceId: registered.workspaceId,
|
|
2817
2999
|
repositoryAgentId: registered.repositoryAgentId,
|
|
3000
|
+
repositoryBindingId: registered.repositoryBindingId,
|
|
3001
|
+
sourceAuthorization,
|
|
2818
3002
|
repositoryAgentKey: registered.repositoryAgentKey,
|
|
2819
3003
|
controlBranch: registered.controlBranch,
|
|
2820
3004
|
policyRevision: authoritativeRevision,
|
|
2821
3005
|
});
|
|
3006
|
+
const onboardingProvider = providerIds?.[0] || 'codex';
|
|
3007
|
+
const onboardingEvidenceFlags = new Map([
|
|
3008
|
+
['workspace', workspace], ['provider', onboardingProvider],
|
|
3009
|
+
['policy', resolve(workspace, '.dharma', 'approved-policy.json')], ['maximum-sessions', '20'],
|
|
3010
|
+
]);
|
|
3011
|
+
const onboardingEvidencePreview = await evidencePreview(onboardingEvidenceFlags);
|
|
3012
|
+
let onboardingEvidence = { state: 'synchronized', captured: 0, synced: 0 };
|
|
3013
|
+
if (Number(onboardingEvidencePreview.trajectoryCount || 0) > 0
|
|
3014
|
+
&& onboardingEvidencePreview.automaticDisclosure?.ready === true) {
|
|
3015
|
+
onboardingEvidenceFlags.set('sync', true);
|
|
3016
|
+
onboardingEvidence = await synchronizeBootstrapEvidence(async () => await retryBootstrapOnboarding(async () => await capture(onboardingEvidenceFlags, true)));
|
|
3017
|
+
requireCompletedBootstrapEvidence(onboardingEvidence, Number(onboardingEvidencePreview.trajectoryCount || 0));
|
|
3018
|
+
}
|
|
3019
|
+
const initialSnapshot = await inventoryRepositoryPackage({ workspace, organizationId,
|
|
3020
|
+
workspaceId: registered.workspaceId, repositoryAgentId: registered.repositoryAgentId,
|
|
3021
|
+
repositoryBindingId: registered.repositoryBindingId, sourceAuthorization });
|
|
3022
|
+
const candidateScope = { organizationId, workspaceId: registered.workspaceId,
|
|
3023
|
+
repositoryBindingId: registered.repositoryBindingId, repositoryAgentId: registered.repositoryAgentId };
|
|
3024
|
+
const outboxRoot = resolve(dharmaHome(), 'relay', 'repository-candidates');
|
|
3025
|
+
const canonicalPackage = registered.repositoryPackage;
|
|
3026
|
+
const candidate = canonicalPackage.state === 'absent'
|
|
3027
|
+
? await synchronizeRepositoryCandidate({ transport: fabric, outboxRoot, scope: candidateScope,
|
|
3028
|
+
snapshot: initialSnapshot, initialRepository: true })
|
|
3029
|
+
: await adoptRepositoryCandidate({ outboxRoot, scope: candidateScope,
|
|
3030
|
+
sourceManifestHash: canonicalPackage.sourceManifestHash,
|
|
3031
|
+
consolidationMode: canonicalPackage.consolidationMode, candidate: {
|
|
3032
|
+
candidateId: canonicalPackage.candidateId, operationId: canonicalPackage.operationId,
|
|
3033
|
+
snapshotHash: canonicalPackage.snapshotHash, state: canonicalPackage.state,
|
|
3034
|
+
releaseId: canonicalPackage.releaseId,
|
|
3035
|
+
} });
|
|
3036
|
+
registered = { ...registered, repositoryPackage: {
|
|
3037
|
+
state: candidate.state, candidateId: candidate.candidateId, operationId: candidate.operationId,
|
|
3038
|
+
snapshotHash: candidate.snapshotHash,
|
|
3039
|
+
sourceManifestHash: canonicalPackage.state === 'absent'
|
|
3040
|
+
? sha256(canonicalize(initialSnapshot.manifest)) : canonicalPackage.sourceManifestHash,
|
|
3041
|
+
releaseId: candidate.releaseId,
|
|
3042
|
+
generation: candidate.state === 'published' ? Math.max(1, canonicalPackage.generation) : canonicalPackage.generation,
|
|
3043
|
+
consolidationMode: canonicalPackage.state === 'absent' ? 'initial_repository' : canonicalPackage.consolidationMode,
|
|
3044
|
+
} };
|
|
3045
|
+
await saveWorkspaceRecord(registered);
|
|
2822
3046
|
const providers = await receiptAwareProviderCapabilities(await Promise.all(selectedProviderAdapters(providerIds).map((adapter) => adapter.capability())));
|
|
3047
|
+
const roleRequested = ['role-name', 'question-categories', 'role-description'].some(key => flags.has(key));
|
|
3048
|
+
if (roleRequested && !['role-name', 'question-categories', 'role-description'].every(key => flags.has(key))) {
|
|
3049
|
+
throw new Error('Repository role overrides require --role-name, --question-categories, and --role-description together.');
|
|
3050
|
+
}
|
|
3051
|
+
const derivedRole = deriveRepositoryRole({ snapshot: initialSnapshot,
|
|
3052
|
+
providers: providers.map(provider => provider.provider).filter(isLocalProviderId) });
|
|
3053
|
+
const roleInput = roleRequested ? {
|
|
3054
|
+
roleName: required(flags, 'role-name'),
|
|
3055
|
+
questionCategories: required(flags, 'question-categories').split(',').map(value => value.trim()).filter(Boolean),
|
|
3056
|
+
description: required(flags, 'role-description'),
|
|
3057
|
+
} : derivedRole;
|
|
3058
|
+
const profileHash = sha256(canonicalize(roleInput));
|
|
3059
|
+
let role = registered.repositoryRole?.profileHash === profileHash
|
|
3060
|
+
? { stage: 'repository_role_observed', reused: true, role: { ...roleInput, revision: registered.repositoryRole.revision } }
|
|
3061
|
+
: await registerRepositoryRoleMetadata(fabric, repositoryRoleScope(registered), {
|
|
3062
|
+
expectedRevision: registered.repositoryRole?.revision || Number(flags.get('role-revision') || 0), ...roleInput
|
|
3063
|
+
});
|
|
3064
|
+
if (registered.repositoryRole?.profileHash !== profileHash) {
|
|
3065
|
+
const revision = Number(role.role?.revision);
|
|
3066
|
+
if (!Number.isSafeInteger(revision) || revision < 1)
|
|
3067
|
+
throw new Error('Repository role registration returned no durable revision.');
|
|
3068
|
+
registered = { ...registered, repositoryRole: { revision, profileHash } };
|
|
3069
|
+
await saveWorkspaceRecord(registered);
|
|
3070
|
+
}
|
|
2823
3071
|
const nativeSkillResult = await installAvailableNativeAgentFabricBootstraps({
|
|
2824
3072
|
providers,
|
|
2825
3073
|
workspace,
|
|
@@ -2828,9 +3076,17 @@ async function onboard(flags) {
|
|
|
2828
3076
|
hqUrl,
|
|
2829
3077
|
});
|
|
2830
3078
|
const primaryProvider = providers[0]?.provider || 'codex';
|
|
3079
|
+
const relay = flags.has('no-relay-daemon')
|
|
3080
|
+
? { started: false, state: 'not_started' }
|
|
3081
|
+
: await startRelayDaemon(resolve(workspace, '.dharma', 'approved-policy.json'));
|
|
3082
|
+
const sharedRepositoryReady = await repositorySharedReady(registered);
|
|
2831
3083
|
return {
|
|
2832
3084
|
ok: true,
|
|
2833
|
-
stage:
|
|
3085
|
+
stage: sharedRepositoryReady ? 'ready' : 'shared_repository_pending',
|
|
3086
|
+
localStage: nativeSkillResult.failures.length ? 'ready_with_provider_actions' : 'ready',
|
|
3087
|
+
sharedRepositoryReady,
|
|
3088
|
+
sharedRepository: { authority: sharedRepositoryReady ? 'signed_repository_release' : 'local_inventory_not_signed',
|
|
3089
|
+
publication: candidate.state, activation: sharedRepositoryReady },
|
|
2834
3090
|
organizationId,
|
|
2835
3091
|
workspaceId: registered.workspaceId,
|
|
2836
3092
|
repositoryAgent: {
|
|
@@ -2849,6 +3105,14 @@ async function onboard(flags) {
|
|
|
2849
3105
|
writePaths: generatedPolicy.tasks.writePaths,
|
|
2850
3106
|
},
|
|
2851
3107
|
repositorySkill: installed,
|
|
3108
|
+
repositoryCandidate: candidate,
|
|
3109
|
+
firstLearningEvidence: {
|
|
3110
|
+
discovered: Number(onboardingEvidencePreview.trajectoryCount || 0),
|
|
3111
|
+
disclosureReady: onboardingEvidencePreview.automaticDisclosure?.ready === true,
|
|
3112
|
+
...onboardingEvidence,
|
|
3113
|
+
},
|
|
3114
|
+
repositoryRole: role,
|
|
3115
|
+
relay,
|
|
2852
3116
|
nativeSkills: nativeSkillResult.installed,
|
|
2853
3117
|
nativeSkillFailures: nativeSkillResult.failures,
|
|
2854
3118
|
workspaceSync: synced,
|
|
@@ -4042,7 +4306,8 @@ function containedInlinePath(root, value) {
|
|
|
4042
4306
|
}
|
|
4043
4307
|
return candidate;
|
|
4044
4308
|
}
|
|
4045
|
-
export async function materializeInlineSkillFiles(bundle, sourceRoot) {
|
|
4309
|
+
export async function materializeInlineSkillFiles(bundle, sourceRoot, assertCurrent = () => { }) {
|
|
4310
|
+
assertCurrent();
|
|
4046
4311
|
if (bundle.operation === 'clear')
|
|
4047
4312
|
return false;
|
|
4048
4313
|
const inline = bundle.skills.map((skill) => skill.files);
|
|
@@ -4073,8 +4338,11 @@ export async function materializeInlineSkillFiles(bundle, sourceRoot) {
|
|
|
4073
4338
|
if (digest !== file.sha256)
|
|
4074
4339
|
throw new Error(`Inline skill file hash mismatch: ${file.path}`);
|
|
4075
4340
|
const destination = containedInlinePath(skillRoot, file.path);
|
|
4341
|
+
assertCurrent();
|
|
4076
4342
|
await mkdir(dirname(destination), { recursive: true, mode: 0o700 });
|
|
4343
|
+
assertCurrent();
|
|
4077
4344
|
await writeFile(destination, content, { mode: 0o600 });
|
|
4345
|
+
assertCurrent();
|
|
4078
4346
|
}
|
|
4079
4347
|
}
|
|
4080
4348
|
return true;
|
|
@@ -4110,22 +4378,68 @@ export async function recoverLegacySkillBundleIdAfterAuthorizationFailure(input)
|
|
|
4110
4378
|
export async function installedBundleIdForSkillPollAfterAuthorizationFailure(input) {
|
|
4111
4379
|
return recoverLegacySkillBundleIdAfterAuthorizationFailure(input);
|
|
4112
4380
|
}
|
|
4113
|
-
async function
|
|
4114
|
-
const
|
|
4115
|
-
|
|
4116
|
-
|
|
4117
|
-
|
|
4118
|
-
|
|
4119
|
-
|
|
4120
|
-
|
|
4121
|
-
|
|
4122
|
-
|
|
4123
|
-
|
|
4124
|
-
|
|
4125
|
-
|
|
4126
|
-
|
|
4127
|
-
|
|
4128
|
-
|
|
4381
|
+
export async function loadSkillSynchronizationPolicy(policyPath, workspaceId, secureStore) {
|
|
4382
|
+
const workspace = (await registry()).find((item) => item.workspaceId === workspaceId);
|
|
4383
|
+
if (!workspace)
|
|
4384
|
+
throw new Error('Skill workspace is not registered locally.');
|
|
4385
|
+
const [providedPath, canonicalPath] = await Promise.all([
|
|
4386
|
+
canonicalFilesystemPath(resolve(policyPath)),
|
|
4387
|
+
canonicalFilesystemPath(resolve(workspace.path, '.dharma', 'approved-policy.json')),
|
|
4388
|
+
]);
|
|
4389
|
+
if (providedPath !== canonicalPath) {
|
|
4390
|
+
throw new Error('Skill synchronization requires the canonical registered workspace policy path.');
|
|
4391
|
+
}
|
|
4392
|
+
const policy = await loadOrganizationPolicy(policyPath);
|
|
4393
|
+
const config = await readDeviceConfig();
|
|
4394
|
+
if (!config || config.organizationId !== workspace.organizationId || policy.organizationId !== workspace.organizationId
|
|
4395
|
+
|| workspace.status !== 'active') {
|
|
4396
|
+
throw new Error('Skill policy does not match the enrolled organization and workspace.');
|
|
4397
|
+
}
|
|
4398
|
+
verifyServerAuthorizedPolicy({ policy, publicKeyEd25519: config.serverPublicKeyEd25519,
|
|
4399
|
+
organizationId: config.organizationId, workspaceId });
|
|
4400
|
+
await assertWorkspaceAuthorizationCurrent(workspaceId, policy.serverAuthorization);
|
|
4401
|
+
const enrollment = await loadDeviceEnrollmentAnchor({ config, store: secureStore });
|
|
4402
|
+
await assertWorkspaceAuthorizationCurrent(workspaceId, policy.serverAuthorization);
|
|
4403
|
+
verifyServerAuthorizedPolicy({ policy, publicKeyEd25519: enrollment.serverPublicKeyEd25519,
|
|
4404
|
+
organizationId: config.organizationId, workspaceId });
|
|
4405
|
+
return { workspace, policy, config, enrollment };
|
|
4406
|
+
}
|
|
4407
|
+
export function parseSkillRolloutResponse(response, organizationId) {
|
|
4408
|
+
if (!response || typeof response !== 'object' || Array.isArray(response))
|
|
4409
|
+
throw new Error('Skill rollout response is invalid.');
|
|
4410
|
+
const value = response;
|
|
4411
|
+
if (value.ok !== true || value.organizationId !== organizationId || !Object.hasOwn(value, 'rollout')) {
|
|
4412
|
+
throw new Error('Skill rollout response does not match the authenticated organization contract.');
|
|
4413
|
+
}
|
|
4414
|
+
const rollout = value.rollout;
|
|
4415
|
+
if (rollout === null)
|
|
4416
|
+
return null;
|
|
4417
|
+
if (!rollout || typeof rollout !== 'object' || Array.isArray(rollout))
|
|
4418
|
+
throw new Error('Skill rollout response is invalid.');
|
|
4419
|
+
const candidate = rollout;
|
|
4420
|
+
if (typeof candidate.id !== 'string' || !candidate.id.trim() || !candidate.bundle
|
|
4421
|
+
|| typeof candidate.bundle !== 'object' || Array.isArray(candidate.bundle))
|
|
4422
|
+
throw new Error('Skill rollout response is invalid.');
|
|
4423
|
+
return rollout;
|
|
4424
|
+
}
|
|
4425
|
+
export async function prepareSkillUpdate(input) {
|
|
4426
|
+
const { workspaceId, provider, policyPath } = input;
|
|
4427
|
+
const assertRunning = input.assertRunning || (() => { });
|
|
4428
|
+
assertRunning();
|
|
4429
|
+
const context = await loadSkillSynchronizationPolicy(policyPath, workspaceId, input.store);
|
|
4430
|
+
const { workspace, policy, config } = context;
|
|
4431
|
+
assertRunning();
|
|
4432
|
+
const destination = nativeSkillDirectory(provider);
|
|
4433
|
+
const fabric = input.fabric || await client();
|
|
4434
|
+
assertRunning();
|
|
4435
|
+
let activeBundleId;
|
|
4436
|
+
let legacyBaselineMigrationRequested = false;
|
|
4437
|
+
if (input.automatic) {
|
|
4438
|
+
activeBundleId = (await loadActiveSkillAuthorizationAnchor({ config, workspaceId,
|
|
4439
|
+
organizationAgentId: String(workspace.repositoryAgentId || ''), provider, store: input.store, fresh: true }))?.bundleId ?? null;
|
|
4440
|
+
assertRunning();
|
|
4441
|
+
}
|
|
4442
|
+
else {
|
|
4129
4443
|
try {
|
|
4130
4444
|
activeBundleId = (await activeSkillAuthorization(provider, workspaceId, String(workspace.repositoryAgentId || ''), config))?.bundleId ?? null;
|
|
4131
4445
|
}
|
|
@@ -4142,152 +4456,330 @@ async function skillSync(flags) {
|
|
|
4142
4456
|
legacyBaselineMigrationRequested = true;
|
|
4143
4457
|
}
|
|
4144
4458
|
}
|
|
4145
|
-
|
|
4146
|
-
|
|
4147
|
-
|
|
4148
|
-
|
|
4149
|
-
|
|
4459
|
+
}
|
|
4460
|
+
const pollRequest = {
|
|
4461
|
+
workspaceId,
|
|
4462
|
+
provider,
|
|
4463
|
+
installedBundleId: activeBundleId,
|
|
4464
|
+
legacyBaselineMigrationRequested,
|
|
4465
|
+
repositoryPackageProtocol: 'dharma.repository-package-envelope/v1',
|
|
4466
|
+
};
|
|
4467
|
+
assertRunning();
|
|
4468
|
+
const response = await fabric.pollSkill(pollRequest);
|
|
4469
|
+
assertRunning();
|
|
4470
|
+
const rollout = parseSkillRolloutResponse(response, config.organizationId);
|
|
4471
|
+
if (!rollout)
|
|
4472
|
+
return null;
|
|
4473
|
+
let bundle = rollout.bundle;
|
|
4474
|
+
if (bundle.organizationId !== policy.organizationId || !Array.isArray(bundle.skills)
|
|
4475
|
+
|| (bundle.operation === 'install' && bundle.skills.length === 0)
|
|
4476
|
+
|| (bundle.operation === 'clear' && bundle.skills.length !== 0)) {
|
|
4477
|
+
throw new Error('Skill bundle does not match local organization policy.');
|
|
4478
|
+
}
|
|
4479
|
+
const sources = validateSkillBundleSources(bundle);
|
|
4480
|
+
const commits = [...new Set(bundle.skills.map((skill) => skill.commit))];
|
|
4481
|
+
const repositories = [...new Set(bundle.skills.map((skill) => skill.repository))];
|
|
4482
|
+
const serverPublicKey = createPublicKey({ key: { kty: 'OKP', crv: 'Ed25519', x: config.serverPublicKeyEd25519 }, format: 'jwk' });
|
|
4483
|
+
verifySkillBundle(bundle, serverPublicKey);
|
|
4484
|
+
for (const [selectors, value] of [[bundle.targetSelectors.organizationAgentIds, String(workspace.repositoryAgentId || '')],
|
|
4485
|
+
[bundle.targetSelectors.deviceIds, config.deviceId], [bundle.targetSelectors.workspaceIds, workspaceId],
|
|
4486
|
+
[bundle.targetSelectors.providers, provider]]) {
|
|
4487
|
+
if (selectors.length && !selectors.includes(value))
|
|
4488
|
+
throw new Error('Skill bundle does not target this enrolled endpoint.');
|
|
4489
|
+
}
|
|
4490
|
+
let repositoryDelivery;
|
|
4491
|
+
if (rollout.repositoryPackage !== undefined) {
|
|
4492
|
+
if (workspace.organizationId !== config.organizationId || policy.organizationId !== config.organizationId
|
|
4493
|
+
|| !workspace.repositoryBindingId || !workspace.repositoryAgentId) {
|
|
4494
|
+
throw new Error('Repository delivery requires the enrolled organization and complete repository binding.');
|
|
4495
|
+
}
|
|
4496
|
+
const query = new URLSearchParams({ rolloutId: rollout.id, workspaceId, provider });
|
|
4497
|
+
const packageRoute = `/agent-fabric/skills/${encodeURIComponent(bundle.bundleId)}/repository-package`;
|
|
4498
|
+
const readPackagePart = async (route, field) => {
|
|
4499
|
+
assertRunning();
|
|
4500
|
+
const part = await fabric.signedGet(route);
|
|
4501
|
+
assertRunning();
|
|
4502
|
+
if (part.ok !== true || part.organizationId !== config.organizationId || !Object.hasOwn(part, field)) {
|
|
4503
|
+
throw new Error('Repository delivery response does not match the authenticated route contract.');
|
|
4504
|
+
}
|
|
4505
|
+
return part[field];
|
|
4506
|
+
};
|
|
4507
|
+
repositoryDelivery = await receiveRepositoryPackageDelivery({
|
|
4508
|
+
envelope: rollout.repositoryPackage, bundle, serverPublicKey,
|
|
4509
|
+
scope: { organizationId: config.organizationId, repositoryBindingId: workspace.repositoryBindingId,
|
|
4510
|
+
repositoryAgentId: workspace.repositoryAgentId, deviceId: config.deviceId, workspaceId, provider },
|
|
4511
|
+
fetchIndex: () => readPackagePart(`${packageRoute}/index?${query}`, 'index'),
|
|
4512
|
+
fetchChunk: (fileIndex, chunkIndex) => readPackagePart(`${packageRoute}/chunks?${query}&fileIndex=${fileIndex}&chunkIndex=${chunkIndex}`, 'chunk'),
|
|
4150
4513
|
});
|
|
4151
|
-
|
|
4152
|
-
|
|
4153
|
-
|
|
4154
|
-
|
|
4155
|
-
|
|
4156
|
-
|
|
4157
|
-
|
|
4158
|
-
|
|
4159
|
-
|
|
4160
|
-
|
|
4514
|
+
assertRunning();
|
|
4515
|
+
bundle = repositoryDelivery.bundle;
|
|
4516
|
+
}
|
|
4517
|
+
const assertCurrent = () => {
|
|
4518
|
+
assertRunning();
|
|
4519
|
+
verifyServerAuthorizedPolicy({ policy, publicKeyEd25519: config.serverPublicKeyEd25519,
|
|
4520
|
+
organizationId: config.organizationId, workspaceId });
|
|
4521
|
+
verifySkillBundle(bundle, serverPublicKey);
|
|
4522
|
+
repositoryDelivery?.assertCurrent();
|
|
4523
|
+
};
|
|
4524
|
+
assertCurrent();
|
|
4525
|
+
const sourceParent = input.automatic ? skillPreparationScopeRoot(dharmaHome(), workspaceId, provider)
|
|
4526
|
+
: resolve(dharmaHome(), 'relay', 'skill-sources');
|
|
4527
|
+
await mkdir(sourceParent, { recursive: true, mode: 0o700 });
|
|
4528
|
+
assertCurrent();
|
|
4529
|
+
const sourceRoot = await mkdtemp(resolve(sourceParent, 'attempt-'));
|
|
4530
|
+
try {
|
|
4531
|
+
assertCurrent();
|
|
4532
|
+
if (repositoryDelivery) {
|
|
4533
|
+
for (const file of repositoryDelivery.files) {
|
|
4534
|
+
assertCurrent();
|
|
4535
|
+
const target = containedInlinePath(sourceRoot, file.path);
|
|
4536
|
+
await mkdir(dirname(target), { recursive: true, mode: 0o700 });
|
|
4537
|
+
assertCurrent();
|
|
4538
|
+
await writeFile(target, Buffer.from(file.contentBase64, 'base64'), { mode: 0o600, flag: 'wx' });
|
|
4539
|
+
assertCurrent();
|
|
4540
|
+
}
|
|
4161
4541
|
}
|
|
4162
|
-
const
|
|
4163
|
-
|
|
4164
|
-
|
|
4165
|
-
const sourceRoot = resolve(dharmaHome(), 'relay', 'skill-sources', bundle.bundleId);
|
|
4166
|
-
verifySkillBundle(bundle, createPublicKey({ key: { kty: 'OKP', crv: 'Ed25519', x: config.serverPublicKeyEd25519 }, format: 'jwk' }));
|
|
4167
|
-
await mkdir(resolve(dharmaHome(), 'relay', 'skill-sources'), { recursive: true, mode: 0o700 });
|
|
4168
|
-
await rm(sourceRoot, { recursive: true, force: true });
|
|
4169
|
-
await mkdir(sourceRoot, { recursive: true, mode: 0o700 });
|
|
4170
|
-
const materializedInline = await materializeInlineSkillFiles(bundle, sourceRoot);
|
|
4171
|
-
if (bundle.operation === 'install' && !materializedInline) {
|
|
4542
|
+
const materializedInline = repositoryDelivery ? false : await materializeInlineSkillFiles(bundle, sourceRoot, assertCurrent);
|
|
4543
|
+
assertCurrent();
|
|
4544
|
+
if (bundle.operation === 'install' && !materializedInline && !repositoryDelivery) {
|
|
4172
4545
|
if (!sources.singleSource)
|
|
4173
4546
|
throw new Error('Multi-source skill bundles require signed inline files for every skill.');
|
|
4547
|
+
if (input.automatic)
|
|
4548
|
+
throw new Error('Automatic preparation requires bounded inline files or an authenticated complete repository package.');
|
|
4174
4549
|
await rm(sourceRoot, { recursive: true, force: true });
|
|
4550
|
+
assertCurrent();
|
|
4175
4551
|
await execFileAsync('git', ['clone', '--filter=blob:none', '--no-checkout', repositories[0], sourceRoot], { timeout: 120_000 });
|
|
4552
|
+
assertCurrent();
|
|
4176
4553
|
await execFileAsync('git', ['-C', sourceRoot, 'fetch', '--no-tags', '--depth=1', 'origin', commits[0]], { timeout: 120_000 });
|
|
4554
|
+
assertCurrent();
|
|
4177
4555
|
await execFileAsync('git', ['-C', sourceRoot, 'checkout', '--detach', commits[0]], { timeout: 30_000 });
|
|
4556
|
+
assertCurrent();
|
|
4178
4557
|
}
|
|
4179
|
-
|
|
4180
|
-
const
|
|
4181
|
-
|
|
4182
|
-
|
|
4183
|
-
|
|
4184
|
-
|
|
4185
|
-
|
|
4186
|
-
|
|
4187
|
-
|
|
4188
|
-
|
|
4189
|
-
|
|
4190
|
-
|
|
4191
|
-
|
|
4192
|
-
|
|
4193
|
-
|
|
4194
|
-
|
|
4195
|
-
|
|
4196
|
-
|
|
4197
|
-
|
|
4198
|
-
|
|
4199
|
-
|
|
4200
|
-
|
|
4201
|
-
|
|
4202
|
-
|
|
4203
|
-
|
|
4204
|
-
|
|
4205
|
-
|
|
4206
|
-
|
|
4207
|
-
|
|
4208
|
-
|
|
4209
|
-
|
|
4210
|
-
|
|
4211
|
-
|
|
4212
|
-
|
|
4213
|
-
|
|
4214
|
-
|
|
4215
|
-
|
|
4216
|
-
|
|
4217
|
-
|
|
4218
|
-
|
|
4219
|
-
|
|
4220
|
-
|
|
4221
|
-
|
|
4222
|
-
|
|
4223
|
-
|
|
4224
|
-
|
|
4225
|
-
|
|
4226
|
-
|
|
4227
|
-
|
|
4228
|
-
|
|
4229
|
-
|
|
4230
|
-
|
|
4231
|
-
|
|
4232
|
-
|
|
4233
|
-
|
|
4234
|
-
|
|
4235
|
-
|
|
4236
|
-
|
|
4237
|
-
|
|
4238
|
-
|
|
4239
|
-
}
|
|
4240
|
-
|
|
4241
|
-
|
|
4242
|
-
|
|
4243
|
-
|
|
4244
|
-
|
|
4245
|
-
|
|
4246
|
-
|
|
4247
|
-
|
|
4248
|
-
|
|
4249
|
-
|
|
4250
|
-
|
|
4251
|
-
|
|
4252
|
-
|
|
4253
|
-
|
|
4558
|
+
for (const skill of bundle.skills) {
|
|
4559
|
+
const actual = await contentHash(containedInlinePath(sourceRoot, skill.path));
|
|
4560
|
+
assertCurrent();
|
|
4561
|
+
if (actual !== skill.contentHash)
|
|
4562
|
+
throw new Error('Prepared skill tree does not match the signed content hash.');
|
|
4563
|
+
}
|
|
4564
|
+
const current = await loadSkillSynchronizationPolicy(policyPath, workspaceId, input.store);
|
|
4565
|
+
assertCurrent();
|
|
4566
|
+
if (canonicalize(current) !== canonicalize(context))
|
|
4567
|
+
throw new Error('Skill preparation scope or policy changed; retry with current authorization.');
|
|
4568
|
+
return { workspace, policy, config, destination, fabric, rollout, bundle, sourceRoot, repositoryDelivery, assertCurrent };
|
|
4569
|
+
}
|
|
4570
|
+
catch (error) {
|
|
4571
|
+
await rm(sourceRoot, { recursive: true, force: true });
|
|
4572
|
+
throw error;
|
|
4573
|
+
}
|
|
4574
|
+
}
|
|
4575
|
+
async function activatePreparedSkillUpdate(input) {
|
|
4576
|
+
const { prepared, workspaceId, provider, policyPath, flags } = input;
|
|
4577
|
+
const { workspace, policy, config, destination, fabric, rollout, bundle, sourceRoot, repositoryDelivery } = prepared;
|
|
4578
|
+
try {
|
|
4579
|
+
const previousAnchor = await loadActiveSkillAuthorizationAnchor({
|
|
4580
|
+
config,
|
|
4581
|
+
workspaceId,
|
|
4582
|
+
organizationAgentId: String(workspace.repositoryAgentId || ''),
|
|
4583
|
+
provider,
|
|
4584
|
+
});
|
|
4585
|
+
const identity = await loadOrCreateDeviceIdentity({ hqUrl: config.hqUrl, organizationId: config.organizationId });
|
|
4586
|
+
repositoryDelivery?.assertCurrent();
|
|
4587
|
+
const current = await loadSkillSynchronizationPolicy(policyPath, workspaceId);
|
|
4588
|
+
if (canonicalize(current.policy) !== canonicalize(policy)
|
|
4589
|
+
|| canonicalize(current.workspace) !== canonicalize(workspace)
|
|
4590
|
+
|| canonicalize(current.config) !== canonicalize(config)) {
|
|
4591
|
+
throw new Error('Skill synchronization scope or policy changed before activation; retry with current authorization.');
|
|
4592
|
+
}
|
|
4593
|
+
repositoryDelivery?.assertCurrent();
|
|
4594
|
+
const receipt = await installSkillBundle({
|
|
4595
|
+
bundle,
|
|
4596
|
+
sourceDirectory: sourceRoot,
|
|
4597
|
+
nativeSkillDirectory: destination,
|
|
4598
|
+
policy,
|
|
4599
|
+
serverPublicKey: createPublicKey({ key: { kty: 'OKP', crv: 'Ed25519', x: config.serverPublicKeyEd25519 }, format: 'jwk' }),
|
|
4600
|
+
devicePrivateKey: createPrivateKey({ key: identity.privateJwk, format: 'jwk' }),
|
|
4601
|
+
deviceId: config.deviceId,
|
|
4602
|
+
organizationAgentId: String(workspace.repositoryAgentId || ''),
|
|
4603
|
+
workspaceId,
|
|
4604
|
+
provider,
|
|
4605
|
+
smokeCommandId: typeof flags.get('smoke-command') === 'string' ? String(flags.get('smoke-command')) : undefined,
|
|
4606
|
+
organizationApprovalId: typeof flags.get('approval-id') === 'string' ? String(flags.get('approval-id')) : undefined,
|
|
4607
|
+
...(provider === 'agy' ? {
|
|
4608
|
+
providerActivationCheck: () => attestAgySkillBundleActivation({
|
|
4609
|
+
bundle,
|
|
4610
|
+
nativeSkillDirectory: destination,
|
|
4611
|
+
workspace: workspace.path,
|
|
4612
|
+
}),
|
|
4613
|
+
} : provider === 'hermes' ? {
|
|
4614
|
+
providerActivationCheck: () => attestHermesSkillBundleActivation({
|
|
4615
|
+
bundle,
|
|
4616
|
+
workspace: workspace.path,
|
|
4617
|
+
nativeSkillDirectory: destination,
|
|
4618
|
+
}),
|
|
4619
|
+
} : {}),
|
|
4620
|
+
});
|
|
4621
|
+
const restoreAnchor = async () => {
|
|
4622
|
+
if (previousAnchor) {
|
|
4623
|
+
await saveActiveSkillAuthorizationAnchor({
|
|
4624
|
+
config,
|
|
4625
|
+
workspaceId,
|
|
4626
|
+
organizationAgentId: previousAnchor.organizationAgentId,
|
|
4627
|
+
provider,
|
|
4628
|
+
bundleId: previousAnchor.bundleId,
|
|
4629
|
+
receiptHash: previousAnchor.receiptHash,
|
|
4630
|
+
activatedAt: previousAnchor.activatedAt,
|
|
4631
|
+
expiresAt: previousAnchor.expiresAt,
|
|
4632
|
+
});
|
|
4633
|
+
}
|
|
4634
|
+
else {
|
|
4635
|
+
await deleteActiveSkillAuthorizationAnchor({ config, workspaceId, provider });
|
|
4636
|
+
}
|
|
4637
|
+
};
|
|
4638
|
+
const recoverLocalInstallation = async (error) => {
|
|
4639
|
+
const recoveryErrors = [];
|
|
4254
4640
|
try {
|
|
4255
|
-
|
|
4256
|
-
|
|
4257
|
-
|
|
4258
|
-
|
|
4259
|
-
|
|
4260
|
-
provider,
|
|
4261
|
-
bundleId: receipt.bundleId,
|
|
4262
|
-
receiptHash: receipt.receiptHash,
|
|
4263
|
-
activatedAt: receipt.completedAt,
|
|
4264
|
-
expiresAt: bundle.expiresAt ?? null,
|
|
4265
|
-
});
|
|
4266
|
-
}
|
|
4641
|
+
await rollbackUnconfirmedSkillBundle({
|
|
4642
|
+
nativeSkillDirectory: destination,
|
|
4643
|
+
workspaceId,
|
|
4644
|
+
receipt,
|
|
4645
|
+
});
|
|
4267
4646
|
}
|
|
4268
|
-
catch (
|
|
4269
|
-
|
|
4647
|
+
catch (rollbackError) {
|
|
4648
|
+
recoveryErrors.push(rollbackError);
|
|
4270
4649
|
}
|
|
4271
4650
|
try {
|
|
4272
|
-
await
|
|
4651
|
+
await restoreAnchor();
|
|
4273
4652
|
}
|
|
4274
|
-
catch (
|
|
4275
|
-
|
|
4276
|
-
|
|
4277
|
-
|
|
4653
|
+
catch (anchorError) {
|
|
4654
|
+
recoveryErrors.push(anchorError);
|
|
4655
|
+
}
|
|
4656
|
+
if (recoveryErrors.length) {
|
|
4657
|
+
throw new AggregateError([error, ...recoveryErrors], 'Skill installation failed and local recovery was incomplete.');
|
|
4658
|
+
}
|
|
4659
|
+
throw error;
|
|
4660
|
+
};
|
|
4661
|
+
try {
|
|
4662
|
+
if (receipt.status === 'active') {
|
|
4663
|
+
await saveActiveSkillAuthorizationAnchor({
|
|
4664
|
+
config,
|
|
4665
|
+
workspaceId,
|
|
4666
|
+
organizationAgentId: String(workspace.repositoryAgentId || ''),
|
|
4667
|
+
provider,
|
|
4668
|
+
bundleId: receipt.bundleId,
|
|
4669
|
+
receiptHash: receipt.receiptHash,
|
|
4670
|
+
activatedAt: receipt.completedAt,
|
|
4671
|
+
expiresAt: bundle.expiresAt ?? null,
|
|
4672
|
+
});
|
|
4278
4673
|
}
|
|
4279
|
-
return { ok: true, rolloutId: rollout.id, bundleId: bundle.bundleId, status: receipt.status, changed: true };
|
|
4280
4674
|
}
|
|
4281
|
-
|
|
4282
|
-
await
|
|
4675
|
+
catch (error) {
|
|
4676
|
+
await recoverLocalInstallation(error);
|
|
4677
|
+
}
|
|
4678
|
+
try {
|
|
4679
|
+
await fabric.postInstallReceipt(bundle.bundleId, rollout.id, receipt);
|
|
4680
|
+
}
|
|
4681
|
+
catch (error) {
|
|
4682
|
+
if (isDefinitiveAgentFabricRejection(error))
|
|
4683
|
+
await recoverLocalInstallation(error);
|
|
4684
|
+
throw error;
|
|
4283
4685
|
}
|
|
4686
|
+
return { ok: true, rolloutId: rollout.id, bundleId: bundle.bundleId, status: receipt.status, changed: true };
|
|
4687
|
+
}
|
|
4688
|
+
finally {
|
|
4689
|
+
await rm(sourceRoot, { recursive: true, force: true });
|
|
4690
|
+
}
|
|
4691
|
+
}
|
|
4692
|
+
async function skillSync(flags) {
|
|
4693
|
+
const workspaceId = required(flags, 'workspace-id');
|
|
4694
|
+
const providerValue = required(flags, 'provider');
|
|
4695
|
+
if (!isLocalProviderId(providerValue))
|
|
4696
|
+
throw new Error('Skill provider must be codex, claude, agy, or hermes.');
|
|
4697
|
+
const provider = providerValue;
|
|
4698
|
+
return withWorkspaceSkillActivationLock(workspaceId, provider, async () => {
|
|
4699
|
+
const policyPath = required(flags, 'policy');
|
|
4700
|
+
const prepared = await prepareSkillUpdate({ workspaceId, provider, policyPath });
|
|
4701
|
+
if (!prepared)
|
|
4702
|
+
return { ok: true, rollout: null, changed: false };
|
|
4703
|
+
return activatePreparedSkillUpdate({ prepared, workspaceId, provider, policyPath, flags });
|
|
4284
4704
|
});
|
|
4285
4705
|
}
|
|
4706
|
+
async function installedRepositoryKnowledge(workspace) {
|
|
4707
|
+
const config = await readDeviceConfig();
|
|
4708
|
+
if (!config || config.organizationId !== workspace.organizationId || !workspace.repositoryBindingId || !workspace.repositoryAgentId) {
|
|
4709
|
+
throw new Error('Installed repository knowledge requires current enrolled workspace scope.');
|
|
4710
|
+
}
|
|
4711
|
+
const identity = await loadOrCreateDeviceIdentity({ hqUrl: config.hqUrl, organizationId: config.organizationId });
|
|
4712
|
+
const enrollment = await loadDeviceEnrollmentAnchor({ config });
|
|
4713
|
+
if (identity.publicKeyEd25519 !== enrollment.devicePublicKeyEd25519)
|
|
4714
|
+
throw new Error('Installed knowledge device identity mismatch.');
|
|
4715
|
+
const organizationAgentId = workspace.repositoryAgentId;
|
|
4716
|
+
return selectInstalledRepositoryKnowledge({ organizationId: config.organizationId,
|
|
4717
|
+
repositoryBindingId: workspace.repositoryBindingId, repositoryAgentId: organizationAgentId,
|
|
4718
|
+
loadProvider: async (provider) => {
|
|
4719
|
+
const root = nativeSkillDirectory(provider);
|
|
4720
|
+
if (!await pathExistsOrThrow(resolve(root, '.dharma-managed/workspaces', workspace.workspaceId, 'ACTIVE_BUNDLE')))
|
|
4721
|
+
return null;
|
|
4722
|
+
const anchorInput = { config, workspaceId: workspace.workspaceId, organizationAgentId, provider };
|
|
4723
|
+
const active = await loadActiveSkillAuthorizationAnchor(anchorInput);
|
|
4724
|
+
if (!active)
|
|
4725
|
+
throw new Error('Installed knowledge release lacks a protected authorization anchor.');
|
|
4726
|
+
const read = (expectedReceiptHash) => readVerifiedRepositoryKnowledge({
|
|
4727
|
+
nativeSkillDirectory: root, workspaceId: workspace.workspaceId, provider,
|
|
4728
|
+
organizationId: config.organizationId, organizationAgentId, deviceId: config.deviceId,
|
|
4729
|
+
serverPublicKey: createPublicKey({ key: { kty: 'OKP', crv: 'Ed25519', x: enrollment.serverPublicKeyEd25519 }, format: 'jwk' }),
|
|
4730
|
+
devicePublicKey: createPublicKey({ key: { kty: 'OKP', crv: 'Ed25519', x: identity.publicKeyEd25519 }, format: 'jwk' }), expectedReceiptHash,
|
|
4731
|
+
});
|
|
4732
|
+
try {
|
|
4733
|
+
return await read(active.receiptHash);
|
|
4734
|
+
}
|
|
4735
|
+
catch (error) {
|
|
4736
|
+
if (!(error instanceof Error) || error.message !== 'Active bundle receipt is not the current protected authorization.')
|
|
4737
|
+
throw error;
|
|
4738
|
+
const refreshed = await loadActiveSkillAuthorizationAnchor({ ...anchorInput, fresh: true });
|
|
4739
|
+
if (!refreshed)
|
|
4740
|
+
throw error;
|
|
4741
|
+
return read(refreshed.receiptHash);
|
|
4742
|
+
}
|
|
4743
|
+
} });
|
|
4744
|
+
}
|
|
4745
|
+
async function takeCachedSkillUpdate(input) {
|
|
4746
|
+
const context = await loadSkillSynchronizationPolicy(input.policyPath, input.workspaceId);
|
|
4747
|
+
const { workspace, policy, config } = context;
|
|
4748
|
+
if (!workspace.repositoryAgentId)
|
|
4749
|
+
return null;
|
|
4750
|
+
const cached = await takeSkillPreparationCache({ home: dharmaHome(), workspaceId: input.workspaceId,
|
|
4751
|
+
provider: input.provider, organizationId: config.organizationId, deviceId: config.deviceId,
|
|
4752
|
+
repositoryAgentId: workspace.repositoryAgentId, repositoryBindingId: workspace.repositoryBindingId ?? null,
|
|
4753
|
+
policyHash: sha256(canonicalize(policy)), assertCurrent: () => {
|
|
4754
|
+
verifyServerAuthorizedPolicy({ policy, publicKeyEd25519: config.serverPublicKeyEd25519,
|
|
4755
|
+
organizationId: config.organizationId, workspaceId: input.workspaceId });
|
|
4756
|
+
} });
|
|
4757
|
+
if (!cached)
|
|
4758
|
+
return null;
|
|
4759
|
+
try {
|
|
4760
|
+
const bundle = cached.record.bundle;
|
|
4761
|
+
const serverPublicKey = createPublicKey({ key: { kty: 'OKP', crv: 'Ed25519', x: config.serverPublicKeyEd25519 }, format: 'jwk' });
|
|
4762
|
+
verifySkillBundle(bundle, serverPublicKey);
|
|
4763
|
+
for (const skill of bundle.skills) {
|
|
4764
|
+
if (await contentHash(containedInlinePath(cached.sourceRoot, skill.path)) !== skill.contentHash) {
|
|
4765
|
+
throw new Error('Cached skill tree does not match the signed content hash.');
|
|
4766
|
+
}
|
|
4767
|
+
}
|
|
4768
|
+
return { workspace, policy, config, destination: nativeSkillDirectory(input.provider), fabric: input.fabric,
|
|
4769
|
+
rollout: { id: String(cached.record.rolloutId), bundle }, bundle, sourceRoot: cached.sourceRoot,
|
|
4770
|
+
repositoryDelivery: undefined, assertCurrent: () => verifySkillBundle(bundle, serverPublicKey) };
|
|
4771
|
+
}
|
|
4772
|
+
catch (error) {
|
|
4773
|
+
await rm(cached.sourceRoot, { recursive: true, force: true });
|
|
4774
|
+
throw error;
|
|
4775
|
+
}
|
|
4776
|
+
}
|
|
4286
4777
|
async function relayStart(flags) {
|
|
4287
4778
|
const policyPath = resolve(required(flags, 'policy'));
|
|
4288
|
-
const
|
|
4289
|
-
if (!
|
|
4779
|
+
const selectedWorkspace = (await registry()).find((item) => resolve(item.path, '.dharma', 'approved-policy.json') === policyPath);
|
|
4780
|
+
if (!selectedWorkspace)
|
|
4290
4781
|
throw new Error('Relay policy must be the canonical policy of one registered workspace.');
|
|
4782
|
+
let canonicalWorkspace = selectedWorkspace;
|
|
4291
4783
|
let policy = await loadVerifiedWorkspacePolicy(policyPath, canonicalWorkspace.workspaceId);
|
|
4292
4784
|
const fabric = await client();
|
|
4293
4785
|
const leaseSeconds = Number(flags.get('lease-seconds') || 120);
|
|
@@ -4296,7 +4788,8 @@ async function relayStart(flags) {
|
|
|
4296
4788
|
await mkdir(resolve(dharmaHome(), 'relay'), { recursive: true, mode: 0o700 });
|
|
4297
4789
|
await writeFile(pidPath, `${process.pid}\n`, { mode: 0o600 });
|
|
4298
4790
|
let stopping = false;
|
|
4299
|
-
|
|
4791
|
+
let stopPreparation;
|
|
4792
|
+
const stop = () => { stopping = true; stopPreparation?.(); };
|
|
4300
4793
|
process.once('SIGINT', stop);
|
|
4301
4794
|
process.once('SIGTERM', stop);
|
|
4302
4795
|
let tasksCompleted = 0;
|
|
@@ -4305,12 +4798,80 @@ async function relayStart(flags) {
|
|
|
4305
4798
|
let trajectorySyncsCompleted = 0;
|
|
4306
4799
|
let nextPolicyRefreshAt = 0;
|
|
4307
4800
|
let evidencePolicyFresh = false;
|
|
4801
|
+
const repositorySourceWatcher = new RepositorySourceWatcher();
|
|
4802
|
+
let nextRepositorySourceScanAt = 0;
|
|
4803
|
+
let repositorySourceCandidates = 0;
|
|
4804
|
+
let repositorySourceFailures = 0;
|
|
4805
|
+
let repositorySourceState = 'not_scanned';
|
|
4308
4806
|
const { LocalVault, loadOrCreateVaultMasterKey } = await loadVaultModule();
|
|
4309
4807
|
const vault = await LocalVault.open({
|
|
4310
4808
|
root: resolve(dharmaHome(), 'vault'),
|
|
4311
4809
|
masterKey: await loadOrCreateVaultMasterKey(),
|
|
4312
4810
|
rawLocalDays: rawLocalRetentionDays(policy),
|
|
4313
4811
|
});
|
|
4812
|
+
let skillPreparationsCompleted = 0;
|
|
4813
|
+
let skillPreparationFailures = 0;
|
|
4814
|
+
let skillActivationsCompleted = 0;
|
|
4815
|
+
let skillActivationFailures = 0;
|
|
4816
|
+
let nextSkillActivationAt = 0;
|
|
4817
|
+
const skillPreparationPump = startSkillPreparationPump({
|
|
4818
|
+
prepare: async (assertRunning) => {
|
|
4819
|
+
await prepareProvidersIndependently(providerAdapters, assertRunning, async (adapter) => {
|
|
4820
|
+
assertRunning();
|
|
4821
|
+
const capability = await adapter.capability();
|
|
4822
|
+
assertRunning();
|
|
4823
|
+
if (capability.skillInstall === 'unavailable' || !isLocalProviderId(adapter.providerId))
|
|
4824
|
+
return;
|
|
4825
|
+
const provider = adapter.providerId;
|
|
4826
|
+
await withSkillPreparationTransaction({ home: dharmaHome(), workspaceId: canonicalWorkspace.workspaceId,
|
|
4827
|
+
provider, assertCurrent: assertRunning }, async () => {
|
|
4828
|
+
const prepared = await prepareSkillUpdate({ workspaceId: canonicalWorkspace.workspaceId,
|
|
4829
|
+
provider, policyPath, fabric, automatic: true, assertRunning });
|
|
4830
|
+
if (!prepared) {
|
|
4831
|
+
assertRunning();
|
|
4832
|
+
return;
|
|
4833
|
+
}
|
|
4834
|
+
let retained = false;
|
|
4835
|
+
try {
|
|
4836
|
+
prepared.assertCurrent();
|
|
4837
|
+
const repositoryAgentId = prepared.workspace.repositoryAgentId;
|
|
4838
|
+
if (!repositoryAgentId)
|
|
4839
|
+
throw new Error('Preparation cache requires an enrolled repository agent.');
|
|
4840
|
+
const pendingRecord = await serializeSkillPreparationRecord({
|
|
4841
|
+
schema: 'dharma.skill-preparation/v1', organizationId: prepared.config.organizationId,
|
|
4842
|
+
deviceId: prepared.config.deviceId, workspaceId: prepared.workspace.workspaceId,
|
|
4843
|
+
repositoryAgentId,
|
|
4844
|
+
repositoryBindingId: prepared.workspace.repositoryBindingId ?? null, provider,
|
|
4845
|
+
policyHash: sha256(canonicalize(prepared.policy)), rolloutId: prepared.rollout.id,
|
|
4846
|
+
bundle: prepared.bundle, repositoryPackage: prepared.repositoryDelivery
|
|
4847
|
+
? { envelope: prepared.repositoryDelivery.envelope, index: prepared.repositoryDelivery.index } : null,
|
|
4848
|
+
preparedAt: new Date().toISOString(), activationAuthorized: false,
|
|
4849
|
+
});
|
|
4850
|
+
prepared.assertCurrent();
|
|
4851
|
+
await writeFile(resolve(prepared.sourceRoot, 'PREPARED.json'), pendingRecord, { mode: 0o600, flag: 'wx' });
|
|
4852
|
+
prepared.assertCurrent();
|
|
4853
|
+
await publishSkillPreparationCache({ home: dharmaHome(), workspaceId: canonicalWorkspace.workspaceId,
|
|
4854
|
+
provider, sourceRoot: prepared.sourceRoot, recordBytes: pendingRecord, assertCurrent: prepared.assertCurrent,
|
|
4855
|
+
expected: { organizationId: prepared.config.organizationId, deviceId: prepared.config.deviceId,
|
|
4856
|
+
repositoryAgentId,
|
|
4857
|
+
repositoryBindingId: prepared.workspace.repositoryBindingId ?? null,
|
|
4858
|
+
policyHash: sha256(canonicalize(prepared.policy)), rolloutId: prepared.rollout.id,
|
|
4859
|
+
bundleId: prepared.bundle.bundleId, bundleHash: prepared.bundle.bundleHash },
|
|
4860
|
+
onCommitted: () => { retained = true; } });
|
|
4861
|
+
skillPreparationsCompleted += 1;
|
|
4862
|
+
}
|
|
4863
|
+
finally {
|
|
4864
|
+
if (!retained)
|
|
4865
|
+
await rm(prepared.sourceRoot, { recursive: true, force: true });
|
|
4866
|
+
}
|
|
4867
|
+
});
|
|
4868
|
+
}, () => { skillPreparationFailures += 1; });
|
|
4869
|
+
},
|
|
4870
|
+
onError: () => { skillPreparationFailures += 1; },
|
|
4871
|
+
});
|
|
4872
|
+
stopPreparation = skillPreparationPump.requestStop;
|
|
4873
|
+
if (stopping)
|
|
4874
|
+
stopPreparation();
|
|
4314
4875
|
try {
|
|
4315
4876
|
const recoveredTaskTrajectories = await deferUnavailableRelayRetention(async () => finalizeRecoveredSignedTaskTrajectories(fabric, policy));
|
|
4316
4877
|
if (recoveredTaskTrajectories.state === 'completed') {
|
|
@@ -4328,6 +4889,54 @@ async function relayStart(flags) {
|
|
|
4328
4889
|
}
|
|
4329
4890
|
nextPolicyRefreshAt = Date.now() + 60_000;
|
|
4330
4891
|
}
|
|
4892
|
+
if (canonicalWorkspace.repositoryBindingId && canonicalWorkspace.repositoryAgentId) {
|
|
4893
|
+
try {
|
|
4894
|
+
const current = await pollRepositoryCandidate({ transport: fabric,
|
|
4895
|
+
outboxRoot: resolve(dharmaHome(), 'relay', 'repository-candidates'),
|
|
4896
|
+
scope: { organizationId: canonicalWorkspace.organizationId, workspaceId: canonicalWorkspace.workspaceId,
|
|
4897
|
+
repositoryBindingId: canonicalWorkspace.repositoryBindingId,
|
|
4898
|
+
repositoryAgentId: canonicalWorkspace.repositoryAgentId } });
|
|
4899
|
+
if (current && canonicalWorkspace.repositoryPackage) {
|
|
4900
|
+
canonicalWorkspace = { ...canonicalWorkspace, repositoryPackage: { ...canonicalWorkspace.repositoryPackage,
|
|
4901
|
+
state: current.state, candidateId: current.candidateId, operationId: current.operationId,
|
|
4902
|
+
snapshotHash: current.snapshotHash, releaseId: current.releaseId,
|
|
4903
|
+
generation: current.state === 'published' ? Math.max(1, canonicalWorkspace.repositoryPackage.generation) : canonicalWorkspace.repositoryPackage.generation } };
|
|
4904
|
+
await saveWorkspaceRecord(canonicalWorkspace);
|
|
4905
|
+
}
|
|
4906
|
+
}
|
|
4907
|
+
catch {
|
|
4908
|
+
repositorySourceFailures += 1;
|
|
4909
|
+
}
|
|
4910
|
+
}
|
|
4911
|
+
if (performance.now() >= nextRepositorySourceScanAt) {
|
|
4912
|
+
try {
|
|
4913
|
+
if (!canonicalWorkspace.repositoryBindingId || !canonicalWorkspace.repositoryAgentId) {
|
|
4914
|
+
throw new Error('Repository source synchronization requires a complete repository binding.');
|
|
4915
|
+
}
|
|
4916
|
+
const sourceCycle = await scanRepositorySourceChanges({
|
|
4917
|
+
workspace: canonicalWorkspace.path, organizationId: canonicalWorkspace.organizationId,
|
|
4918
|
+
workspaceId: canonicalWorkspace.workspaceId, repositoryBindingId: canonicalWorkspace.repositoryBindingId,
|
|
4919
|
+
repositoryAgentId: canonicalWorkspace.repositoryAgentId, transport: fabric, watcher: repositorySourceWatcher,
|
|
4920
|
+
loadRetainedKnowledge: () => installedRepositoryKnowledge(canonicalWorkspace),
|
|
4921
|
+
submitCandidate: snapshot => synchronizeRepositoryCandidate({ transport: fabric,
|
|
4922
|
+
outboxRoot: resolve(dharmaHome(), 'relay', 'repository-candidates'),
|
|
4923
|
+
scope: { organizationId: canonicalWorkspace.organizationId,
|
|
4924
|
+
workspaceId: canonicalWorkspace.workspaceId,
|
|
4925
|
+
repositoryBindingId: canonicalWorkspace.repositoryBindingId,
|
|
4926
|
+
repositoryAgentId: canonicalWorkspace.repositoryAgentId },
|
|
4927
|
+
snapshot, initialRepository: false }),
|
|
4928
|
+
});
|
|
4929
|
+
repositorySourceState = sourceCycle.state;
|
|
4930
|
+
if (sourceCycle.localMutation)
|
|
4931
|
+
repositorySourceCandidates += 1;
|
|
4932
|
+
}
|
|
4933
|
+
catch {
|
|
4934
|
+
repositorySourceWatcher.invalidate();
|
|
4935
|
+
repositorySourceFailures += 1;
|
|
4936
|
+
repositorySourceState = 'blocked';
|
|
4937
|
+
}
|
|
4938
|
+
nextRepositorySourceScanAt = performance.now() + 60_000;
|
|
4939
|
+
}
|
|
4331
4940
|
let evidenceRequestId;
|
|
4332
4941
|
if (evidencePolicyFresh) {
|
|
4333
4942
|
const evidenceCycle = await deferUnavailableRelayRetention(async () => {
|
|
@@ -4351,6 +4960,37 @@ async function relayStart(flags) {
|
|
|
4351
4960
|
const result = await executeOneTask(fabric, leaseSeconds);
|
|
4352
4961
|
if (result.taskId)
|
|
4353
4962
|
tasksCompleted += 1;
|
|
4963
|
+
if (!result.taskId && performance.now() >= nextSkillActivationAt) {
|
|
4964
|
+
for (const adapter of providerAdapters) {
|
|
4965
|
+
try {
|
|
4966
|
+
const capability = await adapter.capability();
|
|
4967
|
+
if (capability.skillInstall === 'unavailable' || !isLocalProviderId(adapter.providerId))
|
|
4968
|
+
continue;
|
|
4969
|
+
const provider = adapter.providerId;
|
|
4970
|
+
await withWorkspaceSkillActivationLock(canonicalWorkspace.workspaceId, provider, async () => {
|
|
4971
|
+
await withSkillPreparationTransaction({ home: dharmaHome(), workspaceId: canonicalWorkspace.workspaceId,
|
|
4972
|
+
provider, assertCurrent: () => { if (stopping)
|
|
4973
|
+
throw new Error('Relay is stopping.'); } }, async () => {
|
|
4974
|
+
const cached = await takeCachedSkillUpdate({ workspaceId: canonicalWorkspace.workspaceId,
|
|
4975
|
+
provider, policyPath, fabric });
|
|
4976
|
+
const prepared = cached || await prepareSkillUpdate({ workspaceId: canonicalWorkspace.workspaceId,
|
|
4977
|
+
provider, policyPath, fabric, automatic: true });
|
|
4978
|
+
if (!prepared)
|
|
4979
|
+
return;
|
|
4980
|
+
if (!cached)
|
|
4981
|
+
skillPreparationsCompleted += 1;
|
|
4982
|
+
await activatePreparedSkillUpdate({ prepared, workspaceId: canonicalWorkspace.workspaceId,
|
|
4983
|
+
provider, policyPath, flags: new Map() });
|
|
4984
|
+
skillActivationsCompleted += 1;
|
|
4985
|
+
});
|
|
4986
|
+
});
|
|
4987
|
+
}
|
|
4988
|
+
catch {
|
|
4989
|
+
skillActivationFailures += 1;
|
|
4990
|
+
}
|
|
4991
|
+
}
|
|
4992
|
+
nextSkillActivationAt = performance.now() + 60_000;
|
|
4993
|
+
}
|
|
4354
4994
|
if (flags.has('once'))
|
|
4355
4995
|
break;
|
|
4356
4996
|
if (!result.taskId && !evidenceRequestId)
|
|
@@ -4358,6 +4998,7 @@ async function relayStart(flags) {
|
|
|
4358
4998
|
} while (!stopping);
|
|
4359
4999
|
}
|
|
4360
5000
|
finally {
|
|
5001
|
+
await skillPreparationPump.stop();
|
|
4361
5002
|
vault.close();
|
|
4362
5003
|
process.removeListener('SIGINT', stop);
|
|
4363
5004
|
process.removeListener('SIGTERM', stop);
|
|
@@ -4366,6 +5007,9 @@ async function relayStart(flags) {
|
|
|
4366
5007
|
return {
|
|
4367
5008
|
ok: true, stopped: true, tasksCompleted, taskTrajectoriesRecovered,
|
|
4368
5009
|
evidenceResponsesCompleted, trajectorySyncsCompleted,
|
|
5010
|
+
repositorySourceCandidates, repositorySourceFailures, repositorySourceState,
|
|
5011
|
+
skillPreparationsCompleted, skillPreparationFailures, skillActivationsCompleted, skillActivationFailures,
|
|
5012
|
+
sharedRepositoryReady: await repositorySharedReady(canonicalWorkspace),
|
|
4369
5013
|
};
|
|
4370
5014
|
}
|
|
4371
5015
|
export async function run(argv) {
|
|
@@ -4393,6 +5037,13 @@ export async function run(argv) {
|
|
|
4393
5037
|
}
|
|
4394
5038
|
if (command === 'repositories' && subcommand === 'list')
|
|
4395
5039
|
return repositoriesList(flags);
|
|
5040
|
+
if (command === 'repositories' && subcommand === 'snapshot') {
|
|
5041
|
+
return repositorySnapshotCommand(flags, repeated.get('approved-output') || []);
|
|
5042
|
+
}
|
|
5043
|
+
if (command === 'repositories' && ['role-register', 'role-discover', 'ask', 'reply'].includes(String(subcommand))) {
|
|
5044
|
+
const action = subcommand === 'role-register' ? 'register' : subcommand === 'role-discover' ? 'discover' : subcommand;
|
|
5045
|
+
return repositoryRoleCommand(action, flags);
|
|
5046
|
+
}
|
|
4396
5047
|
if (command === 'repositories' && subcommand === 'status') {
|
|
4397
5048
|
const listed = await repositoriesList(flags);
|
|
4398
5049
|
return {
|