@dharma-ai-labs/agent-fabric 0.2.49 → 0.2.51
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 +840 -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 +216 -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.51';
|
|
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)
|
|
@@ -1417,6 +1440,10 @@ async function bootstrap(flags) {
|
|
|
1417
1440
|
const name = String(flags.get('device-name') || `${process.env.USER || process.env.USERNAME || 'developer'} device`);
|
|
1418
1441
|
const devicePlatform = await platform();
|
|
1419
1442
|
const identity = await loadOrCreateDeviceIdentity({ hqUrl, organizationId });
|
|
1443
|
+
let recipientApproval = {
|
|
1444
|
+
required: false,
|
|
1445
|
+
browserOpened: false,
|
|
1446
|
+
};
|
|
1420
1447
|
const redeemed = await redeemBootstrapGrant({
|
|
1421
1448
|
hqUrl,
|
|
1422
1449
|
organizationId,
|
|
@@ -1424,6 +1451,13 @@ async function bootstrap(flags) {
|
|
|
1424
1451
|
name,
|
|
1425
1452
|
platform: devicePlatform,
|
|
1426
1453
|
publicKeyEd25519: identity.publicKeyEd25519,
|
|
1454
|
+
onRecipientApprovalRequired: async (approval) => {
|
|
1455
|
+
recipientApproval.required = true;
|
|
1456
|
+
recipientApproval.browserOpened = flags.has('no-browser')
|
|
1457
|
+
? false
|
|
1458
|
+
: await openVerificationUri(approval.url);
|
|
1459
|
+
process.stderr.write(`Confirm this exact device in the authenticated Dharma portal before ${approval.expiresAt}: ${approval.url}\n`);
|
|
1460
|
+
},
|
|
1427
1461
|
});
|
|
1428
1462
|
const rebind = enrollmentMismatch && existing
|
|
1429
1463
|
? await archiveEnrollmentForAuthorizedRebind(existing)
|
|
@@ -1464,18 +1498,40 @@ async function bootstrap(flags) {
|
|
|
1464
1498
|
onboardFlags.set('policy-revision', policyRevision);
|
|
1465
1499
|
onboardFlags.set('provider', provider);
|
|
1466
1500
|
const onboarded = await retryBootstrapOnboarding(async () => await onboard(onboardFlags));
|
|
1501
|
+
if (onboarded.ok !== true || onboarded.stage === 'approve_device') {
|
|
1502
|
+
return {
|
|
1503
|
+
ok: onboarded.ok === true,
|
|
1504
|
+
stage: onboarded.stage,
|
|
1505
|
+
code: onboarded.code,
|
|
1506
|
+
sharedRepositoryReady: false,
|
|
1507
|
+
enrollment: {
|
|
1508
|
+
organizationId,
|
|
1509
|
+
deviceId: config.deviceId,
|
|
1510
|
+
status: 'approved',
|
|
1511
|
+
organizationApiTokenStored: true,
|
|
1512
|
+
scopes: redeemed.organizationApiTokenScopes,
|
|
1513
|
+
recipientApproval,
|
|
1514
|
+
rebind,
|
|
1515
|
+
},
|
|
1516
|
+
repository: onboarded,
|
|
1517
|
+
};
|
|
1518
|
+
}
|
|
1467
1519
|
const launcher = await installStableRepositoryLauncher(workspace);
|
|
1520
|
+
const sharedRepositoryReady = onboarded.sharedRepositoryReady === true;
|
|
1468
1521
|
const completionRequested = flags.has('complete');
|
|
1469
1522
|
if (!completionRequested) {
|
|
1470
1523
|
return {
|
|
1471
1524
|
ok: true,
|
|
1472
|
-
stage: onboarded.stage,
|
|
1525
|
+
stage: sharedRepositoryReady ? onboarded.stage : 'shared_repository_pending',
|
|
1526
|
+
localStage: onboarded.localStage ?? onboarded.stage,
|
|
1527
|
+
sharedRepositoryReady,
|
|
1473
1528
|
enrollment: {
|
|
1474
1529
|
organizationId,
|
|
1475
1530
|
deviceId: config.deviceId,
|
|
1476
1531
|
status: 'approved',
|
|
1477
1532
|
organizationApiTokenStored: true,
|
|
1478
1533
|
scopes: redeemed.organizationApiTokenScopes,
|
|
1534
|
+
recipientApproval,
|
|
1479
1535
|
rebind,
|
|
1480
1536
|
},
|
|
1481
1537
|
repository: onboarded,
|
|
@@ -1524,13 +1580,16 @@ async function bootstrap(flags) {
|
|
|
1524
1580
|
});
|
|
1525
1581
|
return {
|
|
1526
1582
|
ok: true,
|
|
1527
|
-
stage: 'complete',
|
|
1583
|
+
stage: sharedRepositoryReady ? 'complete' : 'shared_repository_pending',
|
|
1584
|
+
localStage: 'complete',
|
|
1585
|
+
sharedRepositoryReady,
|
|
1528
1586
|
enrollment: {
|
|
1529
1587
|
organizationId,
|
|
1530
1588
|
deviceId: config.deviceId,
|
|
1531
1589
|
status: 'approved',
|
|
1532
1590
|
organizationApiTokenStored: true,
|
|
1533
1591
|
scopes: redeemed.organizationApiTokenScopes,
|
|
1592
|
+
recipientApproval,
|
|
1534
1593
|
rebind,
|
|
1535
1594
|
},
|
|
1536
1595
|
repository: onboarded,
|
|
@@ -2366,7 +2425,22 @@ async function repositoriesConnect(flags, positional, repeatedRepos, repeatedKey
|
|
|
2366
2425
|
if (result?.stage === 'approve_device')
|
|
2367
2426
|
break;
|
|
2368
2427
|
}
|
|
2369
|
-
|
|
2428
|
+
const states = results.map(result => result);
|
|
2429
|
+
const blocked = states.filter(result => result.ok !== true).length;
|
|
2430
|
+
const connected = states.filter(result => result.ok === true && result.sharedRepositoryReady === true).length;
|
|
2431
|
+
const locallyReady = states.filter(result => result.ok === true && (['ready', 'ready_with_provider_actions', 'complete'].includes(String(result.localStage))
|
|
2432
|
+
|| ['ready', 'ready_with_provider_actions', 'complete'].includes(String(result.stage)))).length;
|
|
2433
|
+
return {
|
|
2434
|
+
ok: blocked === 0,
|
|
2435
|
+
requested: unique.length,
|
|
2436
|
+
attempted: results.length,
|
|
2437
|
+
locallyReady,
|
|
2438
|
+
connected,
|
|
2439
|
+
blocked,
|
|
2440
|
+
pending: states.length - blocked - connected,
|
|
2441
|
+
sharedRepositoryReady: connected === unique.length,
|
|
2442
|
+
repositories: results,
|
|
2443
|
+
};
|
|
2370
2444
|
}
|
|
2371
2445
|
export function parseSelectedProviderIds(values) {
|
|
2372
2446
|
const selected = [...new Set(values
|
|
@@ -2445,22 +2519,72 @@ export async function receiptAwareProviderCapabilities(providers, now = new Date
|
|
|
2445
2519
|
}
|
|
2446
2520
|
async function repositoriesList(flags) {
|
|
2447
2521
|
const verbose = flags.has('verbose') || flags.has('diagnostic');
|
|
2522
|
+
const items = await registry();
|
|
2448
2523
|
return {
|
|
2449
2524
|
ok: true,
|
|
2450
|
-
repositories:
|
|
2525
|
+
repositories: await Promise.all(items.map(async (item) => ({
|
|
2451
2526
|
workspaceId: item.workspaceId,
|
|
2452
2527
|
name: item.name,
|
|
2453
2528
|
repositoryAgentId: item.repositoryAgentId || null,
|
|
2454
2529
|
repositoryAgentKey: item.repositoryAgentKey || null,
|
|
2455
2530
|
controlBranch: item.controlBranch || null,
|
|
2456
2531
|
connected: Boolean(item.repositoryAgentId && item.controlBranch),
|
|
2532
|
+
repositoryPackageState: item.repositoryPackage?.state || 'unknown',
|
|
2533
|
+
sharedRepositoryReady: await repositorySharedReady(item),
|
|
2457
2534
|
...(verbose ? { localPath: item.path, sourceFingerprint: item.repositoryRemoteHash } : {}),
|
|
2458
|
-
})),
|
|
2535
|
+
}))),
|
|
2459
2536
|
};
|
|
2460
2537
|
}
|
|
2538
|
+
async function repositorySharedReady(item) {
|
|
2539
|
+
if (item.repositoryPackage?.state !== 'published' || !item.repositoryPackage.releaseId)
|
|
2540
|
+
return false;
|
|
2541
|
+
try {
|
|
2542
|
+
return Boolean(await installedRepositoryKnowledge(item));
|
|
2543
|
+
}
|
|
2544
|
+
catch {
|
|
2545
|
+
return false;
|
|
2546
|
+
}
|
|
2547
|
+
}
|
|
2548
|
+
export function canonicalRepositoryPackage(value) {
|
|
2549
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
2550
|
+
throw new Error('Dharma HQ did not return canonical repository package state.');
|
|
2551
|
+
}
|
|
2552
|
+
const row = value;
|
|
2553
|
+
const keys = ['state', 'candidateId', 'operationId', 'snapshotHash', 'sourceManifestHash', 'releaseId', 'generation', 'consolidationMode'];
|
|
2554
|
+
if (!keys.every(key => Object.hasOwn(row, key)) || Object.keys(row).some(key => !keys.includes(key))
|
|
2555
|
+
|| !['absent', 'accepted', 'processing', 'published', 'blocked'].includes(String(row.state))
|
|
2556
|
+
|| !Number.isSafeInteger(row.generation) || Number(row.generation) < 0) {
|
|
2557
|
+
throw new Error('Dharma HQ returned invalid canonical repository package state.');
|
|
2558
|
+
}
|
|
2559
|
+
const nullableUuid = (candidate) => candidate === null || typeof candidate === 'string' && UUID_PATTERN.test(candidate);
|
|
2560
|
+
const nullableHash = (candidate) => candidate === null || typeof candidate === 'string' && /^sha256:[a-f0-9]{64}$/.test(candidate);
|
|
2561
|
+
const absent = row.state === 'absent';
|
|
2562
|
+
if (![row.candidateId, row.releaseId].every(nullableUuid)
|
|
2563
|
+
|| ![row.operationId, row.snapshotHash, row.sourceManifestHash].every(nullableHash)
|
|
2564
|
+
|| ![null, 'initial_repository', 'repository_update'].includes(row.consolidationMode)
|
|
2565
|
+
|| absent !== (row.candidateId === null && row.operationId === null && row.snapshotHash === null
|
|
2566
|
+
&& row.sourceManifestHash === null && row.releaseId === null && row.consolidationMode === null && row.generation === 0)
|
|
2567
|
+
|| (row.state === 'published') !== (row.releaseId !== null)
|
|
2568
|
+
|| (!absent && (row.candidateId === null || row.operationId === null || row.snapshotHash === null
|
|
2569
|
+
|| row.sourceManifestHash === null || row.consolidationMode === null))) {
|
|
2570
|
+
throw new Error('Dharma HQ returned inconsistent canonical repository package state.');
|
|
2571
|
+
}
|
|
2572
|
+
return row;
|
|
2573
|
+
}
|
|
2574
|
+
export function canonicalEndpointRole(value) {
|
|
2575
|
+
if (value === null)
|
|
2576
|
+
return null;
|
|
2577
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
2578
|
+
throw new Error('Dharma HQ returned invalid endpoint role state.');
|
|
2579
|
+
const row = value;
|
|
2580
|
+
if (Object.keys(row).sort().join(',') !== 'profileHash,revision'
|
|
2581
|
+
|| !Number.isSafeInteger(row.revision) || Number(row.revision) < 1
|
|
2582
|
+
|| typeof row.profileHash !== 'string' || !/^sha256:[a-f0-9]{64}$/.test(row.profileHash)) {
|
|
2583
|
+
throw new Error('Dharma HQ returned invalid endpoint role state.');
|
|
2584
|
+
}
|
|
2585
|
+
return { revision: Number(row.revision), profileHash: row.profileHash };
|
|
2586
|
+
}
|
|
2461
2587
|
async function bindRepositoryAgent(fabric, item) {
|
|
2462
|
-
if (item.repositoryAgentId && item.repositoryBindingId && item.repositoryAgentKey && item.controlBranch)
|
|
2463
|
-
return item;
|
|
2464
2588
|
const remote = await gitValue(item.path, ['config', '--get', 'remote.origin.url']);
|
|
2465
2589
|
const currentIdentity = item.repositoryIdentityVersion === 'normalized-v1'
|
|
2466
2590
|
? item.repositoryRemoteHash
|
|
@@ -2481,17 +2605,25 @@ async function bindRepositoryAgent(fabric, item) {
|
|
|
2481
2605
|
const branch = repositoryAgent?.branch && typeof repositoryAgent.branch === 'object'
|
|
2482
2606
|
? repositoryAgent.branch
|
|
2483
2607
|
: null;
|
|
2608
|
+
const endpoint = response.endpoint && typeof response.endpoint === 'object' && !Array.isArray(response.endpoint)
|
|
2609
|
+
? response.endpoint
|
|
2610
|
+
: null;
|
|
2484
2611
|
const updated = {
|
|
2485
2612
|
...item,
|
|
2486
2613
|
repositoryRemoteHash: currentIdentity,
|
|
2487
2614
|
repositoryIdentityVersion: 'normalized-v1',
|
|
2488
2615
|
repositoryAgentId: String(repositoryAgent?.organization_agent_id || ''),
|
|
2489
2616
|
repositoryBindingId: String(repositoryAgent?.id || ''),
|
|
2617
|
+
endpointId: typeof response.endpointId === 'string' ? response.endpointId
|
|
2618
|
+
: typeof endpoint?.id === 'string' ? endpoint.id : item.endpointId || null,
|
|
2619
|
+
repositoryPackage: canonicalRepositoryPackage(response.repositoryPackage),
|
|
2620
|
+
repositoryRole: canonicalEndpointRole(response.endpointRole),
|
|
2490
2621
|
repositoryAgentKey: String(repositoryAgent?.agent_key || ''),
|
|
2491
2622
|
controlBranch: String(repositoryAgent?.control_branch || branch?.branch || ''),
|
|
2492
2623
|
};
|
|
2493
2624
|
if (!/^[0-9a-f-]{36}$/i.test(updated.repositoryAgentId || '')
|
|
2494
2625
|
|| !/^[0-9a-f-]{36}$/i.test(updated.repositoryBindingId || '')
|
|
2626
|
+
|| !UUID_PATTERN.test(updated.endpointId || '')
|
|
2495
2627
|
|| !/^repo:[a-f0-9]{24}$/.test(updated.repositoryAgentKey || '')
|
|
2496
2628
|
|| !/^agents\/[a-z0-9][a-z0-9._-]*-[a-f0-9]{8}$/.test(updated.controlBranch || '')) {
|
|
2497
2629
|
throw new Error('Dharma HQ returned an invalid repository-agent binding.');
|
|
@@ -2499,6 +2631,35 @@ async function bindRepositoryAgent(fabric, item) {
|
|
|
2499
2631
|
await saveWorkspaceRecord(updated);
|
|
2500
2632
|
return updated;
|
|
2501
2633
|
}
|
|
2634
|
+
function repositoryRoleScope(item) {
|
|
2635
|
+
if (!item.repositoryAgentId || !item.repositoryBindingId || !item.endpointId || !item.repositoryRemoteHash) {
|
|
2636
|
+
throw new Error('Repository role operations require a complete signed endpoint and repository binding.');
|
|
2637
|
+
}
|
|
2638
|
+
return { organizationId: item.organizationId, workspaceId: item.workspaceId,
|
|
2639
|
+
repositoryBindingId: item.repositoryBindingId, repositoryAgentId: item.repositoryAgentId,
|
|
2640
|
+
endpointId: item.endpointId, sourceFingerprint: item.repositoryRemoteHash };
|
|
2641
|
+
}
|
|
2642
|
+
async function repositoryRoleCommand(action, flags) {
|
|
2643
|
+
const workspaceId = required(flags, 'workspace-id');
|
|
2644
|
+
const item = (await registry()).find(candidate => candidate.workspaceId === workspaceId);
|
|
2645
|
+
if (!item)
|
|
2646
|
+
throw new Error('Repository role workspace is not registered locally.');
|
|
2647
|
+
const scope = repositoryRoleScope(item), transport = await client();
|
|
2648
|
+
if (action === 'register') {
|
|
2649
|
+
const categories = required(flags, 'question-categories').split(',').map(value => value.trim()).filter(Boolean);
|
|
2650
|
+
return registerRepositoryRoleMetadata(transport, scope, { expectedRevision: Number(flags.get('expected-revision') || 0),
|
|
2651
|
+
roleName: required(flags, 'role-name'), questionCategories: categories,
|
|
2652
|
+
description: required(flags, 'role-description') });
|
|
2653
|
+
}
|
|
2654
|
+
if (action === 'discover') {
|
|
2655
|
+
const category = typeof flags.get('category') === 'string' ? String(flags.get('category')) : undefined;
|
|
2656
|
+
return discoverRepositoryRoleMetadata(transport, scope, category);
|
|
2657
|
+
}
|
|
2658
|
+
if (action === 'ask')
|
|
2659
|
+
return askRepositoryRoleQuestion({ transport, scope,
|
|
2660
|
+
category: required(flags, 'category'), question: required(flags, 'question') });
|
|
2661
|
+
return readRepositoryRoleReply({ transport, scope, questionId: required(flags, 'question-id') });
|
|
2662
|
+
}
|
|
2502
2663
|
async function workspaceSync(flags, positional) {
|
|
2503
2664
|
const workspaceId = positional[0] || required(flags, 'workspace-id');
|
|
2504
2665
|
const policyRevision = required(flags, 'policy-revision');
|
|
@@ -2662,23 +2823,13 @@ async function loadVerifiedWorkspacePolicy(path, workspaceId) {
|
|
|
2662
2823
|
return verified;
|
|
2663
2824
|
}
|
|
2664
2825
|
export async function installRepositoryAgentFabricSkill(input) {
|
|
2826
|
+
if (input.sourceAuthorization !== undefined)
|
|
2827
|
+
input = { ...input,
|
|
2828
|
+
sourceAuthorization: validateRepositorySourceAuthorization(input.sourceAuthorization, input, new Date()) };
|
|
2829
|
+
await assertRepositoryInstallerOwnership(input.workspace, input.workspaceId);
|
|
2830
|
+
input = { ...input, workspace: await realpath(input.workspace) };
|
|
2831
|
+
await assertRepositoryInstallerOwnership(input.workspace, input.workspaceId);
|
|
2665
2832
|
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
2833
|
await mkdir(resolve(skillRoot, 'references'), { recursive: true, mode: 0o700 });
|
|
2683
2834
|
await mkdir(resolve(input.workspace, '.dharma'), { recursive: true, mode: 0o700 });
|
|
2684
2835
|
const skill = `---
|
|
@@ -2741,17 +2892,47 @@ The CLI enrolls this device through browser-confirmed Clerk organization consent
|
|
|
2741
2892
|
controlBranch: input.controlBranch || null,
|
|
2742
2893
|
workspaceId: input.workspaceId,
|
|
2743
2894
|
};
|
|
2744
|
-
await
|
|
2745
|
-
await
|
|
2746
|
-
await
|
|
2747
|
-
|
|
2748
|
-
|
|
2895
|
+
await writeRepositoryInstallerFile(input.workspace, '.agents/skills/dharma-agent-fabric/SKILL.md', skill);
|
|
2896
|
+
await writeRepositoryInstallerFile(input.workspace, '.agents/skills/dharma-agent-fabric/references/organization.md', reference);
|
|
2897
|
+
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`);
|
|
2898
|
+
const knowledge = input.repositoryAgentId ? await initializeRepositoryKnowledge({
|
|
2899
|
+
workspace: input.workspace, organizationId: input.organizationId, repositoryAgentId: input.repositoryAgentId,
|
|
2900
|
+
}) : null;
|
|
2901
|
+
const repositoryPackage = await writeRepositoryPackageSnapshot({
|
|
2902
|
+
workspace: input.workspace, snapshot: await inventoryRepositoryPackage(input),
|
|
2903
|
+
});
|
|
2904
|
+
await writeRepositoryInstallerFile(input.workspace, '.dharma/agent-fabric.json', `${JSON.stringify(connection, null, 2)}\n`);
|
|
2905
|
+
await writeRepositoryInstallerFile(input.workspace, '.dharma/repository-agent.json', `${JSON.stringify(repositoryAgent, null, 2)}\n`);
|
|
2749
2906
|
return {
|
|
2750
2907
|
skillPath: '.agents/skills/dharma-agent-fabric/SKILL.md',
|
|
2751
2908
|
connectionPath: '.dharma/agent-fabric.json',
|
|
2752
2909
|
repositoryAgentPath: '.dharma/repository-agent.json',
|
|
2910
|
+
repositoryPackage,
|
|
2911
|
+
knowledge,
|
|
2753
2912
|
};
|
|
2754
2913
|
}
|
|
2914
|
+
async function repositorySnapshotCommand(flags, outputs) {
|
|
2915
|
+
if (flags.has('apply') && flags.has('dry-run'))
|
|
2916
|
+
throw new Error('Choose --apply or --dry-run, not both.');
|
|
2917
|
+
if (outputs.some(value => typeof value !== 'string'))
|
|
2918
|
+
throw new Error('--approved-output requires a workspace-relative file path.');
|
|
2919
|
+
const workspace = String(flags.get('workspace') || '.');
|
|
2920
|
+
const apply = flags.get('apply') === true;
|
|
2921
|
+
if (apply) {
|
|
2922
|
+
await access(resolve(workspace, '.agents/skills/dharma-agent-fabric/.dharma-agent-fabric.json')).catch(() => {
|
|
2923
|
+
throw new Error('Snapshot --apply requires an installed managed repository skill; use --dry-run before onboarding.');
|
|
2924
|
+
});
|
|
2925
|
+
}
|
|
2926
|
+
const snapshot = await inventoryRepositoryPackage({
|
|
2927
|
+
workspace, organizationId: required(flags, 'organization-id'), workspaceId: required(flags, 'workspace-id'),
|
|
2928
|
+
repositoryAgentId: (await registry()).find(record => record.path === resolve(workspace)
|
|
2929
|
+
&& record.organizationId === flags.get('organization-id') && record.workspaceId === flags.get('workspace-id'))?.repositoryAgentId,
|
|
2930
|
+
approvedOutputs: outputs,
|
|
2931
|
+
});
|
|
2932
|
+
const persisted = apply ? await writeRepositoryPackageSnapshot({ workspace, snapshot }) : null;
|
|
2933
|
+
return { ok: true, dryRun: !apply, localMutation: apply, serverMutation: false,
|
|
2934
|
+
authority: 'local_inventory_not_signed', snapshot, persisted };
|
|
2935
|
+
}
|
|
2755
2936
|
async function onboard(flags) {
|
|
2756
2937
|
const workspace = await realpath(String(flags.get('workspace') || flags.get('path') || '.'));
|
|
2757
2938
|
const organizationId = required(flags, 'organization-id');
|
|
@@ -2809,17 +2990,98 @@ async function onboard(flags) {
|
|
|
2809
2990
|
const localPolicy = synced.localPolicy;
|
|
2810
2991
|
const authoritativeRevision = String(localPolicy?.revision || policyRevision);
|
|
2811
2992
|
const generatedPolicy = await loadVerifiedWorkspacePolicy(resolve(workspace, '.dharma', 'approved-policy.json'), registered.workspaceId);
|
|
2993
|
+
if (!registered.repositoryBindingId || !registered.repositoryAgentId) {
|
|
2994
|
+
throw new Error('Repository onboarding requires the canonical repository binding and logical agent.');
|
|
2995
|
+
}
|
|
2996
|
+
let sourceAuthorization;
|
|
2997
|
+
try {
|
|
2998
|
+
sourceAuthorization = await fetchRepositorySourceAuthorization(fabric, {
|
|
2999
|
+
organizationId, workspaceId: registered.workspaceId,
|
|
3000
|
+
repositoryBindingId: registered.repositoryBindingId, repositoryAgentId: registered.repositoryAgentId,
|
|
3001
|
+
});
|
|
3002
|
+
}
|
|
3003
|
+
catch {
|
|
3004
|
+
return { ok: false, stage: 'repository_source_authorization_required', organizationId,
|
|
3005
|
+
workspaceId: registered.workspaceId, code: 'repository_source_policy_unavailable',
|
|
3006
|
+
sharedRepositoryReady: false, localInventoryFallback: false };
|
|
3007
|
+
}
|
|
2812
3008
|
const installed = await installRepositoryAgentFabricSkill({
|
|
2813
3009
|
workspace,
|
|
2814
3010
|
hqUrl,
|
|
2815
3011
|
organizationId,
|
|
2816
3012
|
workspaceId: registered.workspaceId,
|
|
2817
3013
|
repositoryAgentId: registered.repositoryAgentId,
|
|
3014
|
+
repositoryBindingId: registered.repositoryBindingId,
|
|
3015
|
+
sourceAuthorization,
|
|
2818
3016
|
repositoryAgentKey: registered.repositoryAgentKey,
|
|
2819
3017
|
controlBranch: registered.controlBranch,
|
|
2820
3018
|
policyRevision: authoritativeRevision,
|
|
2821
3019
|
});
|
|
3020
|
+
const onboardingProvider = providerIds?.[0] || 'codex';
|
|
3021
|
+
const onboardingEvidenceFlags = new Map([
|
|
3022
|
+
['workspace', workspace], ['provider', onboardingProvider],
|
|
3023
|
+
['policy', resolve(workspace, '.dharma', 'approved-policy.json')], ['maximum-sessions', '20'],
|
|
3024
|
+
]);
|
|
3025
|
+
const onboardingEvidencePreview = await evidencePreview(onboardingEvidenceFlags);
|
|
3026
|
+
let onboardingEvidence = { state: 'synchronized', captured: 0, synced: 0 };
|
|
3027
|
+
if (Number(onboardingEvidencePreview.trajectoryCount || 0) > 0
|
|
3028
|
+
&& onboardingEvidencePreview.automaticDisclosure?.ready === true) {
|
|
3029
|
+
onboardingEvidenceFlags.set('sync', true);
|
|
3030
|
+
onboardingEvidence = await synchronizeBootstrapEvidence(async () => await retryBootstrapOnboarding(async () => await capture(onboardingEvidenceFlags, true)));
|
|
3031
|
+
requireCompletedBootstrapEvidence(onboardingEvidence, Number(onboardingEvidencePreview.trajectoryCount || 0));
|
|
3032
|
+
}
|
|
3033
|
+
const initialSnapshot = await inventoryRepositoryPackage({ workspace, organizationId,
|
|
3034
|
+
workspaceId: registered.workspaceId, repositoryAgentId: registered.repositoryAgentId,
|
|
3035
|
+
repositoryBindingId: registered.repositoryBindingId, sourceAuthorization });
|
|
3036
|
+
const candidateScope = { organizationId, workspaceId: registered.workspaceId,
|
|
3037
|
+
repositoryBindingId: registered.repositoryBindingId, repositoryAgentId: registered.repositoryAgentId };
|
|
3038
|
+
const outboxRoot = resolve(dharmaHome(), 'relay', 'repository-candidates');
|
|
3039
|
+
const canonicalPackage = registered.repositoryPackage;
|
|
3040
|
+
const candidate = canonicalPackage.state === 'absent'
|
|
3041
|
+
? await synchronizeRepositoryCandidate({ transport: fabric, outboxRoot, scope: candidateScope,
|
|
3042
|
+
snapshot: initialSnapshot, initialRepository: true })
|
|
3043
|
+
: await adoptRepositoryCandidate({ outboxRoot, scope: candidateScope,
|
|
3044
|
+
sourceManifestHash: canonicalPackage.sourceManifestHash,
|
|
3045
|
+
consolidationMode: canonicalPackage.consolidationMode, candidate: {
|
|
3046
|
+
candidateId: canonicalPackage.candidateId, operationId: canonicalPackage.operationId,
|
|
3047
|
+
snapshotHash: canonicalPackage.snapshotHash, state: canonicalPackage.state,
|
|
3048
|
+
releaseId: canonicalPackage.releaseId,
|
|
3049
|
+
} });
|
|
3050
|
+
registered = { ...registered, repositoryPackage: {
|
|
3051
|
+
state: candidate.state, candidateId: candidate.candidateId, operationId: candidate.operationId,
|
|
3052
|
+
snapshotHash: candidate.snapshotHash,
|
|
3053
|
+
sourceManifestHash: canonicalPackage.state === 'absent'
|
|
3054
|
+
? sha256(canonicalize(initialSnapshot.manifest)) : canonicalPackage.sourceManifestHash,
|
|
3055
|
+
releaseId: candidate.releaseId,
|
|
3056
|
+
generation: candidate.state === 'published' ? Math.max(1, canonicalPackage.generation) : canonicalPackage.generation,
|
|
3057
|
+
consolidationMode: canonicalPackage.state === 'absent' ? 'initial_repository' : canonicalPackage.consolidationMode,
|
|
3058
|
+
} };
|
|
3059
|
+
await saveWorkspaceRecord(registered);
|
|
2822
3060
|
const providers = await receiptAwareProviderCapabilities(await Promise.all(selectedProviderAdapters(providerIds).map((adapter) => adapter.capability())));
|
|
3061
|
+
const roleRequested = ['role-name', 'question-categories', 'role-description'].some(key => flags.has(key));
|
|
3062
|
+
if (roleRequested && !['role-name', 'question-categories', 'role-description'].every(key => flags.has(key))) {
|
|
3063
|
+
throw new Error('Repository role overrides require --role-name, --question-categories, and --role-description together.');
|
|
3064
|
+
}
|
|
3065
|
+
const derivedRole = deriveRepositoryRole({ snapshot: initialSnapshot,
|
|
3066
|
+
providers: providers.map(provider => provider.provider).filter(isLocalProviderId) });
|
|
3067
|
+
const roleInput = roleRequested ? {
|
|
3068
|
+
roleName: required(flags, 'role-name'),
|
|
3069
|
+
questionCategories: required(flags, 'question-categories').split(',').map(value => value.trim()).filter(Boolean),
|
|
3070
|
+
description: required(flags, 'role-description'),
|
|
3071
|
+
} : derivedRole;
|
|
3072
|
+
const profileHash = sha256(canonicalize(roleInput));
|
|
3073
|
+
let role = registered.repositoryRole?.profileHash === profileHash
|
|
3074
|
+
? { stage: 'repository_role_observed', reused: true, role: { ...roleInput, revision: registered.repositoryRole.revision } }
|
|
3075
|
+
: await registerRepositoryRoleMetadata(fabric, repositoryRoleScope(registered), {
|
|
3076
|
+
expectedRevision: registered.repositoryRole?.revision || Number(flags.get('role-revision') || 0), ...roleInput
|
|
3077
|
+
});
|
|
3078
|
+
if (registered.repositoryRole?.profileHash !== profileHash) {
|
|
3079
|
+
const revision = Number(role.role?.revision);
|
|
3080
|
+
if (!Number.isSafeInteger(revision) || revision < 1)
|
|
3081
|
+
throw new Error('Repository role registration returned no durable revision.');
|
|
3082
|
+
registered = { ...registered, repositoryRole: { revision, profileHash } };
|
|
3083
|
+
await saveWorkspaceRecord(registered);
|
|
3084
|
+
}
|
|
2823
3085
|
const nativeSkillResult = await installAvailableNativeAgentFabricBootstraps({
|
|
2824
3086
|
providers,
|
|
2825
3087
|
workspace,
|
|
@@ -2828,9 +3090,17 @@ async function onboard(flags) {
|
|
|
2828
3090
|
hqUrl,
|
|
2829
3091
|
});
|
|
2830
3092
|
const primaryProvider = providers[0]?.provider || 'codex';
|
|
3093
|
+
const relay = flags.has('no-relay-daemon')
|
|
3094
|
+
? { started: false, state: 'not_started' }
|
|
3095
|
+
: await startRelayDaemon(resolve(workspace, '.dharma', 'approved-policy.json'));
|
|
3096
|
+
const sharedRepositoryReady = await repositorySharedReady(registered);
|
|
2831
3097
|
return {
|
|
2832
3098
|
ok: true,
|
|
2833
|
-
stage:
|
|
3099
|
+
stage: sharedRepositoryReady ? 'ready' : 'shared_repository_pending',
|
|
3100
|
+
localStage: nativeSkillResult.failures.length ? 'ready_with_provider_actions' : 'ready',
|
|
3101
|
+
sharedRepositoryReady,
|
|
3102
|
+
sharedRepository: { authority: sharedRepositoryReady ? 'signed_repository_release' : 'local_inventory_not_signed',
|
|
3103
|
+
publication: candidate.state, activation: sharedRepositoryReady },
|
|
2834
3104
|
organizationId,
|
|
2835
3105
|
workspaceId: registered.workspaceId,
|
|
2836
3106
|
repositoryAgent: {
|
|
@@ -2849,6 +3119,14 @@ async function onboard(flags) {
|
|
|
2849
3119
|
writePaths: generatedPolicy.tasks.writePaths,
|
|
2850
3120
|
},
|
|
2851
3121
|
repositorySkill: installed,
|
|
3122
|
+
repositoryCandidate: candidate,
|
|
3123
|
+
firstLearningEvidence: {
|
|
3124
|
+
discovered: Number(onboardingEvidencePreview.trajectoryCount || 0),
|
|
3125
|
+
disclosureReady: onboardingEvidencePreview.automaticDisclosure?.ready === true,
|
|
3126
|
+
...onboardingEvidence,
|
|
3127
|
+
},
|
|
3128
|
+
repositoryRole: role,
|
|
3129
|
+
relay,
|
|
2852
3130
|
nativeSkills: nativeSkillResult.installed,
|
|
2853
3131
|
nativeSkillFailures: nativeSkillResult.failures,
|
|
2854
3132
|
workspaceSync: synced,
|
|
@@ -4042,7 +4320,8 @@ function containedInlinePath(root, value) {
|
|
|
4042
4320
|
}
|
|
4043
4321
|
return candidate;
|
|
4044
4322
|
}
|
|
4045
|
-
export async function materializeInlineSkillFiles(bundle, sourceRoot) {
|
|
4323
|
+
export async function materializeInlineSkillFiles(bundle, sourceRoot, assertCurrent = () => { }) {
|
|
4324
|
+
assertCurrent();
|
|
4046
4325
|
if (bundle.operation === 'clear')
|
|
4047
4326
|
return false;
|
|
4048
4327
|
const inline = bundle.skills.map((skill) => skill.files);
|
|
@@ -4073,8 +4352,11 @@ export async function materializeInlineSkillFiles(bundle, sourceRoot) {
|
|
|
4073
4352
|
if (digest !== file.sha256)
|
|
4074
4353
|
throw new Error(`Inline skill file hash mismatch: ${file.path}`);
|
|
4075
4354
|
const destination = containedInlinePath(skillRoot, file.path);
|
|
4355
|
+
assertCurrent();
|
|
4076
4356
|
await mkdir(dirname(destination), { recursive: true, mode: 0o700 });
|
|
4357
|
+
assertCurrent();
|
|
4077
4358
|
await writeFile(destination, content, { mode: 0o600 });
|
|
4359
|
+
assertCurrent();
|
|
4078
4360
|
}
|
|
4079
4361
|
}
|
|
4080
4362
|
return true;
|
|
@@ -4110,22 +4392,68 @@ export async function recoverLegacySkillBundleIdAfterAuthorizationFailure(input)
|
|
|
4110
4392
|
export async function installedBundleIdForSkillPollAfterAuthorizationFailure(input) {
|
|
4111
4393
|
return recoverLegacySkillBundleIdAfterAuthorizationFailure(input);
|
|
4112
4394
|
}
|
|
4113
|
-
async function
|
|
4114
|
-
const
|
|
4115
|
-
|
|
4116
|
-
|
|
4117
|
-
|
|
4118
|
-
|
|
4119
|
-
|
|
4120
|
-
|
|
4121
|
-
|
|
4122
|
-
|
|
4123
|
-
|
|
4124
|
-
|
|
4125
|
-
|
|
4126
|
-
|
|
4127
|
-
|
|
4128
|
-
|
|
4395
|
+
export async function loadSkillSynchronizationPolicy(policyPath, workspaceId, secureStore) {
|
|
4396
|
+
const workspace = (await registry()).find((item) => item.workspaceId === workspaceId);
|
|
4397
|
+
if (!workspace)
|
|
4398
|
+
throw new Error('Skill workspace is not registered locally.');
|
|
4399
|
+
const [providedPath, canonicalPath] = await Promise.all([
|
|
4400
|
+
canonicalFilesystemPath(resolve(policyPath)),
|
|
4401
|
+
canonicalFilesystemPath(resolve(workspace.path, '.dharma', 'approved-policy.json')),
|
|
4402
|
+
]);
|
|
4403
|
+
if (providedPath !== canonicalPath) {
|
|
4404
|
+
throw new Error('Skill synchronization requires the canonical registered workspace policy path.');
|
|
4405
|
+
}
|
|
4406
|
+
const policy = await loadOrganizationPolicy(policyPath);
|
|
4407
|
+
const config = await readDeviceConfig();
|
|
4408
|
+
if (!config || config.organizationId !== workspace.organizationId || policy.organizationId !== workspace.organizationId
|
|
4409
|
+
|| workspace.status !== 'active') {
|
|
4410
|
+
throw new Error('Skill policy does not match the enrolled organization and workspace.');
|
|
4411
|
+
}
|
|
4412
|
+
verifyServerAuthorizedPolicy({ policy, publicKeyEd25519: config.serverPublicKeyEd25519,
|
|
4413
|
+
organizationId: config.organizationId, workspaceId });
|
|
4414
|
+
await assertWorkspaceAuthorizationCurrent(workspaceId, policy.serverAuthorization);
|
|
4415
|
+
const enrollment = await loadDeviceEnrollmentAnchor({ config, store: secureStore });
|
|
4416
|
+
await assertWorkspaceAuthorizationCurrent(workspaceId, policy.serverAuthorization);
|
|
4417
|
+
verifyServerAuthorizedPolicy({ policy, publicKeyEd25519: enrollment.serverPublicKeyEd25519,
|
|
4418
|
+
organizationId: config.organizationId, workspaceId });
|
|
4419
|
+
return { workspace, policy, config, enrollment };
|
|
4420
|
+
}
|
|
4421
|
+
export function parseSkillRolloutResponse(response, organizationId) {
|
|
4422
|
+
if (!response || typeof response !== 'object' || Array.isArray(response))
|
|
4423
|
+
throw new Error('Skill rollout response is invalid.');
|
|
4424
|
+
const value = response;
|
|
4425
|
+
if (value.ok !== true || value.organizationId !== organizationId || !Object.hasOwn(value, 'rollout')) {
|
|
4426
|
+
throw new Error('Skill rollout response does not match the authenticated organization contract.');
|
|
4427
|
+
}
|
|
4428
|
+
const rollout = value.rollout;
|
|
4429
|
+
if (rollout === null)
|
|
4430
|
+
return null;
|
|
4431
|
+
if (!rollout || typeof rollout !== 'object' || Array.isArray(rollout))
|
|
4432
|
+
throw new Error('Skill rollout response is invalid.');
|
|
4433
|
+
const candidate = rollout;
|
|
4434
|
+
if (typeof candidate.id !== 'string' || !candidate.id.trim() || !candidate.bundle
|
|
4435
|
+
|| typeof candidate.bundle !== 'object' || Array.isArray(candidate.bundle))
|
|
4436
|
+
throw new Error('Skill rollout response is invalid.');
|
|
4437
|
+
return rollout;
|
|
4438
|
+
}
|
|
4439
|
+
export async function prepareSkillUpdate(input) {
|
|
4440
|
+
const { workspaceId, provider, policyPath } = input;
|
|
4441
|
+
const assertRunning = input.assertRunning || (() => { });
|
|
4442
|
+
assertRunning();
|
|
4443
|
+
const context = await loadSkillSynchronizationPolicy(policyPath, workspaceId, input.store);
|
|
4444
|
+
const { workspace, policy, config } = context;
|
|
4445
|
+
assertRunning();
|
|
4446
|
+
const destination = nativeSkillDirectory(provider);
|
|
4447
|
+
const fabric = input.fabric || await client();
|
|
4448
|
+
assertRunning();
|
|
4449
|
+
let activeBundleId;
|
|
4450
|
+
let legacyBaselineMigrationRequested = false;
|
|
4451
|
+
if (input.automatic) {
|
|
4452
|
+
activeBundleId = (await loadActiveSkillAuthorizationAnchor({ config, workspaceId,
|
|
4453
|
+
organizationAgentId: String(workspace.repositoryAgentId || ''), provider, store: input.store, fresh: true }))?.bundleId ?? null;
|
|
4454
|
+
assertRunning();
|
|
4455
|
+
}
|
|
4456
|
+
else {
|
|
4129
4457
|
try {
|
|
4130
4458
|
activeBundleId = (await activeSkillAuthorization(provider, workspaceId, String(workspace.repositoryAgentId || ''), config))?.bundleId ?? null;
|
|
4131
4459
|
}
|
|
@@ -4142,152 +4470,330 @@ async function skillSync(flags) {
|
|
|
4142
4470
|
legacyBaselineMigrationRequested = true;
|
|
4143
4471
|
}
|
|
4144
4472
|
}
|
|
4145
|
-
|
|
4146
|
-
|
|
4147
|
-
|
|
4148
|
-
|
|
4149
|
-
|
|
4473
|
+
}
|
|
4474
|
+
const pollRequest = {
|
|
4475
|
+
workspaceId,
|
|
4476
|
+
provider,
|
|
4477
|
+
installedBundleId: activeBundleId,
|
|
4478
|
+
legacyBaselineMigrationRequested,
|
|
4479
|
+
repositoryPackageProtocol: 'dharma.repository-package-envelope/v1',
|
|
4480
|
+
};
|
|
4481
|
+
assertRunning();
|
|
4482
|
+
const response = await fabric.pollSkill(pollRequest);
|
|
4483
|
+
assertRunning();
|
|
4484
|
+
const rollout = parseSkillRolloutResponse(response, config.organizationId);
|
|
4485
|
+
if (!rollout)
|
|
4486
|
+
return null;
|
|
4487
|
+
let bundle = rollout.bundle;
|
|
4488
|
+
if (bundle.organizationId !== policy.organizationId || !Array.isArray(bundle.skills)
|
|
4489
|
+
|| (bundle.operation === 'install' && bundle.skills.length === 0)
|
|
4490
|
+
|| (bundle.operation === 'clear' && bundle.skills.length !== 0)) {
|
|
4491
|
+
throw new Error('Skill bundle does not match local organization policy.');
|
|
4492
|
+
}
|
|
4493
|
+
const sources = validateSkillBundleSources(bundle);
|
|
4494
|
+
const commits = [...new Set(bundle.skills.map((skill) => skill.commit))];
|
|
4495
|
+
const repositories = [...new Set(bundle.skills.map((skill) => skill.repository))];
|
|
4496
|
+
const serverPublicKey = createPublicKey({ key: { kty: 'OKP', crv: 'Ed25519', x: config.serverPublicKeyEd25519 }, format: 'jwk' });
|
|
4497
|
+
verifySkillBundle(bundle, serverPublicKey);
|
|
4498
|
+
for (const [selectors, value] of [[bundle.targetSelectors.organizationAgentIds, String(workspace.repositoryAgentId || '')],
|
|
4499
|
+
[bundle.targetSelectors.deviceIds, config.deviceId], [bundle.targetSelectors.workspaceIds, workspaceId],
|
|
4500
|
+
[bundle.targetSelectors.providers, provider]]) {
|
|
4501
|
+
if (selectors.length && !selectors.includes(value))
|
|
4502
|
+
throw new Error('Skill bundle does not target this enrolled endpoint.');
|
|
4503
|
+
}
|
|
4504
|
+
let repositoryDelivery;
|
|
4505
|
+
if (rollout.repositoryPackage !== undefined) {
|
|
4506
|
+
if (workspace.organizationId !== config.organizationId || policy.organizationId !== config.organizationId
|
|
4507
|
+
|| !workspace.repositoryBindingId || !workspace.repositoryAgentId) {
|
|
4508
|
+
throw new Error('Repository delivery requires the enrolled organization and complete repository binding.');
|
|
4509
|
+
}
|
|
4510
|
+
const query = new URLSearchParams({ rolloutId: rollout.id, workspaceId, provider });
|
|
4511
|
+
const packageRoute = `/agent-fabric/skills/${encodeURIComponent(bundle.bundleId)}/repository-package`;
|
|
4512
|
+
const readPackagePart = async (route, field) => {
|
|
4513
|
+
assertRunning();
|
|
4514
|
+
const part = await fabric.signedGet(route);
|
|
4515
|
+
assertRunning();
|
|
4516
|
+
if (part.ok !== true || part.organizationId !== config.organizationId || !Object.hasOwn(part, field)) {
|
|
4517
|
+
throw new Error('Repository delivery response does not match the authenticated route contract.');
|
|
4518
|
+
}
|
|
4519
|
+
return part[field];
|
|
4520
|
+
};
|
|
4521
|
+
repositoryDelivery = await receiveRepositoryPackageDelivery({
|
|
4522
|
+
envelope: rollout.repositoryPackage, bundle, serverPublicKey,
|
|
4523
|
+
scope: { organizationId: config.organizationId, repositoryBindingId: workspace.repositoryBindingId,
|
|
4524
|
+
repositoryAgentId: workspace.repositoryAgentId, deviceId: config.deviceId, workspaceId, provider },
|
|
4525
|
+
fetchIndex: () => readPackagePart(`${packageRoute}/index?${query}`, 'index'),
|
|
4526
|
+
fetchChunk: (fileIndex, chunkIndex) => readPackagePart(`${packageRoute}/chunks?${query}&fileIndex=${fileIndex}&chunkIndex=${chunkIndex}`, 'chunk'),
|
|
4150
4527
|
});
|
|
4151
|
-
|
|
4152
|
-
|
|
4153
|
-
|
|
4154
|
-
|
|
4155
|
-
|
|
4156
|
-
|
|
4157
|
-
|
|
4158
|
-
|
|
4159
|
-
|
|
4160
|
-
|
|
4528
|
+
assertRunning();
|
|
4529
|
+
bundle = repositoryDelivery.bundle;
|
|
4530
|
+
}
|
|
4531
|
+
const assertCurrent = () => {
|
|
4532
|
+
assertRunning();
|
|
4533
|
+
verifyServerAuthorizedPolicy({ policy, publicKeyEd25519: config.serverPublicKeyEd25519,
|
|
4534
|
+
organizationId: config.organizationId, workspaceId });
|
|
4535
|
+
verifySkillBundle(bundle, serverPublicKey);
|
|
4536
|
+
repositoryDelivery?.assertCurrent();
|
|
4537
|
+
};
|
|
4538
|
+
assertCurrent();
|
|
4539
|
+
const sourceParent = input.automatic ? skillPreparationScopeRoot(dharmaHome(), workspaceId, provider)
|
|
4540
|
+
: resolve(dharmaHome(), 'relay', 'skill-sources');
|
|
4541
|
+
await mkdir(sourceParent, { recursive: true, mode: 0o700 });
|
|
4542
|
+
assertCurrent();
|
|
4543
|
+
const sourceRoot = await mkdtemp(resolve(sourceParent, 'attempt-'));
|
|
4544
|
+
try {
|
|
4545
|
+
assertCurrent();
|
|
4546
|
+
if (repositoryDelivery) {
|
|
4547
|
+
for (const file of repositoryDelivery.files) {
|
|
4548
|
+
assertCurrent();
|
|
4549
|
+
const target = containedInlinePath(sourceRoot, file.path);
|
|
4550
|
+
await mkdir(dirname(target), { recursive: true, mode: 0o700 });
|
|
4551
|
+
assertCurrent();
|
|
4552
|
+
await writeFile(target, Buffer.from(file.contentBase64, 'base64'), { mode: 0o600, flag: 'wx' });
|
|
4553
|
+
assertCurrent();
|
|
4554
|
+
}
|
|
4161
4555
|
}
|
|
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) {
|
|
4556
|
+
const materializedInline = repositoryDelivery ? false : await materializeInlineSkillFiles(bundle, sourceRoot, assertCurrent);
|
|
4557
|
+
assertCurrent();
|
|
4558
|
+
if (bundle.operation === 'install' && !materializedInline && !repositoryDelivery) {
|
|
4172
4559
|
if (!sources.singleSource)
|
|
4173
4560
|
throw new Error('Multi-source skill bundles require signed inline files for every skill.');
|
|
4561
|
+
if (input.automatic)
|
|
4562
|
+
throw new Error('Automatic preparation requires bounded inline files or an authenticated complete repository package.');
|
|
4174
4563
|
await rm(sourceRoot, { recursive: true, force: true });
|
|
4564
|
+
assertCurrent();
|
|
4175
4565
|
await execFileAsync('git', ['clone', '--filter=blob:none', '--no-checkout', repositories[0], sourceRoot], { timeout: 120_000 });
|
|
4566
|
+
assertCurrent();
|
|
4176
4567
|
await execFileAsync('git', ['-C', sourceRoot, 'fetch', '--no-tags', '--depth=1', 'origin', commits[0]], { timeout: 120_000 });
|
|
4568
|
+
assertCurrent();
|
|
4177
4569
|
await execFileAsync('git', ['-C', sourceRoot, 'checkout', '--detach', commits[0]], { timeout: 30_000 });
|
|
4570
|
+
assertCurrent();
|
|
4178
4571
|
}
|
|
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
|
-
|
|
4572
|
+
for (const skill of bundle.skills) {
|
|
4573
|
+
const actual = await contentHash(containedInlinePath(sourceRoot, skill.path));
|
|
4574
|
+
assertCurrent();
|
|
4575
|
+
if (actual !== skill.contentHash)
|
|
4576
|
+
throw new Error('Prepared skill tree does not match the signed content hash.');
|
|
4577
|
+
}
|
|
4578
|
+
const current = await loadSkillSynchronizationPolicy(policyPath, workspaceId, input.store);
|
|
4579
|
+
assertCurrent();
|
|
4580
|
+
if (canonicalize(current) !== canonicalize(context))
|
|
4581
|
+
throw new Error('Skill preparation scope or policy changed; retry with current authorization.');
|
|
4582
|
+
return { workspace, policy, config, destination, fabric, rollout, bundle, sourceRoot, repositoryDelivery, assertCurrent };
|
|
4583
|
+
}
|
|
4584
|
+
catch (error) {
|
|
4585
|
+
await rm(sourceRoot, { recursive: true, force: true });
|
|
4586
|
+
throw error;
|
|
4587
|
+
}
|
|
4588
|
+
}
|
|
4589
|
+
async function activatePreparedSkillUpdate(input) {
|
|
4590
|
+
const { prepared, workspaceId, provider, policyPath, flags } = input;
|
|
4591
|
+
const { workspace, policy, config, destination, fabric, rollout, bundle, sourceRoot, repositoryDelivery } = prepared;
|
|
4592
|
+
try {
|
|
4593
|
+
const previousAnchor = await loadActiveSkillAuthorizationAnchor({
|
|
4594
|
+
config,
|
|
4595
|
+
workspaceId,
|
|
4596
|
+
organizationAgentId: String(workspace.repositoryAgentId || ''),
|
|
4597
|
+
provider,
|
|
4598
|
+
});
|
|
4599
|
+
const identity = await loadOrCreateDeviceIdentity({ hqUrl: config.hqUrl, organizationId: config.organizationId });
|
|
4600
|
+
repositoryDelivery?.assertCurrent();
|
|
4601
|
+
const current = await loadSkillSynchronizationPolicy(policyPath, workspaceId);
|
|
4602
|
+
if (canonicalize(current.policy) !== canonicalize(policy)
|
|
4603
|
+
|| canonicalize(current.workspace) !== canonicalize(workspace)
|
|
4604
|
+
|| canonicalize(current.config) !== canonicalize(config)) {
|
|
4605
|
+
throw new Error('Skill synchronization scope or policy changed before activation; retry with current authorization.');
|
|
4606
|
+
}
|
|
4607
|
+
repositoryDelivery?.assertCurrent();
|
|
4608
|
+
const receipt = await installSkillBundle({
|
|
4609
|
+
bundle,
|
|
4610
|
+
sourceDirectory: sourceRoot,
|
|
4611
|
+
nativeSkillDirectory: destination,
|
|
4612
|
+
policy,
|
|
4613
|
+
serverPublicKey: createPublicKey({ key: { kty: 'OKP', crv: 'Ed25519', x: config.serverPublicKeyEd25519 }, format: 'jwk' }),
|
|
4614
|
+
devicePrivateKey: createPrivateKey({ key: identity.privateJwk, format: 'jwk' }),
|
|
4615
|
+
deviceId: config.deviceId,
|
|
4616
|
+
organizationAgentId: String(workspace.repositoryAgentId || ''),
|
|
4617
|
+
workspaceId,
|
|
4618
|
+
provider,
|
|
4619
|
+
smokeCommandId: typeof flags.get('smoke-command') === 'string' ? String(flags.get('smoke-command')) : undefined,
|
|
4620
|
+
organizationApprovalId: typeof flags.get('approval-id') === 'string' ? String(flags.get('approval-id')) : undefined,
|
|
4621
|
+
...(provider === 'agy' ? {
|
|
4622
|
+
providerActivationCheck: () => attestAgySkillBundleActivation({
|
|
4623
|
+
bundle,
|
|
4624
|
+
nativeSkillDirectory: destination,
|
|
4625
|
+
workspace: workspace.path,
|
|
4626
|
+
}),
|
|
4627
|
+
} : provider === 'hermes' ? {
|
|
4628
|
+
providerActivationCheck: () => attestHermesSkillBundleActivation({
|
|
4629
|
+
bundle,
|
|
4630
|
+
workspace: workspace.path,
|
|
4631
|
+
nativeSkillDirectory: destination,
|
|
4632
|
+
}),
|
|
4633
|
+
} : {}),
|
|
4634
|
+
});
|
|
4635
|
+
const restoreAnchor = async () => {
|
|
4636
|
+
if (previousAnchor) {
|
|
4637
|
+
await saveActiveSkillAuthorizationAnchor({
|
|
4638
|
+
config,
|
|
4639
|
+
workspaceId,
|
|
4640
|
+
organizationAgentId: previousAnchor.organizationAgentId,
|
|
4641
|
+
provider,
|
|
4642
|
+
bundleId: previousAnchor.bundleId,
|
|
4643
|
+
receiptHash: previousAnchor.receiptHash,
|
|
4644
|
+
activatedAt: previousAnchor.activatedAt,
|
|
4645
|
+
expiresAt: previousAnchor.expiresAt,
|
|
4646
|
+
});
|
|
4647
|
+
}
|
|
4648
|
+
else {
|
|
4649
|
+
await deleteActiveSkillAuthorizationAnchor({ config, workspaceId, provider });
|
|
4650
|
+
}
|
|
4651
|
+
};
|
|
4652
|
+
const recoverLocalInstallation = async (error) => {
|
|
4653
|
+
const recoveryErrors = [];
|
|
4254
4654
|
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
|
-
}
|
|
4655
|
+
await rollbackUnconfirmedSkillBundle({
|
|
4656
|
+
nativeSkillDirectory: destination,
|
|
4657
|
+
workspaceId,
|
|
4658
|
+
receipt,
|
|
4659
|
+
});
|
|
4267
4660
|
}
|
|
4268
|
-
catch (
|
|
4269
|
-
|
|
4661
|
+
catch (rollbackError) {
|
|
4662
|
+
recoveryErrors.push(rollbackError);
|
|
4270
4663
|
}
|
|
4271
4664
|
try {
|
|
4272
|
-
await
|
|
4665
|
+
await restoreAnchor();
|
|
4273
4666
|
}
|
|
4274
|
-
catch (
|
|
4275
|
-
|
|
4276
|
-
|
|
4277
|
-
|
|
4667
|
+
catch (anchorError) {
|
|
4668
|
+
recoveryErrors.push(anchorError);
|
|
4669
|
+
}
|
|
4670
|
+
if (recoveryErrors.length) {
|
|
4671
|
+
throw new AggregateError([error, ...recoveryErrors], 'Skill installation failed and local recovery was incomplete.');
|
|
4672
|
+
}
|
|
4673
|
+
throw error;
|
|
4674
|
+
};
|
|
4675
|
+
try {
|
|
4676
|
+
if (receipt.status === 'active') {
|
|
4677
|
+
await saveActiveSkillAuthorizationAnchor({
|
|
4678
|
+
config,
|
|
4679
|
+
workspaceId,
|
|
4680
|
+
organizationAgentId: String(workspace.repositoryAgentId || ''),
|
|
4681
|
+
provider,
|
|
4682
|
+
bundleId: receipt.bundleId,
|
|
4683
|
+
receiptHash: receipt.receiptHash,
|
|
4684
|
+
activatedAt: receipt.completedAt,
|
|
4685
|
+
expiresAt: bundle.expiresAt ?? null,
|
|
4686
|
+
});
|
|
4278
4687
|
}
|
|
4279
|
-
return { ok: true, rolloutId: rollout.id, bundleId: bundle.bundleId, status: receipt.status, changed: true };
|
|
4280
4688
|
}
|
|
4281
|
-
|
|
4282
|
-
await
|
|
4689
|
+
catch (error) {
|
|
4690
|
+
await recoverLocalInstallation(error);
|
|
4691
|
+
}
|
|
4692
|
+
try {
|
|
4693
|
+
await fabric.postInstallReceipt(bundle.bundleId, rollout.id, receipt);
|
|
4283
4694
|
}
|
|
4695
|
+
catch (error) {
|
|
4696
|
+
if (isDefinitiveAgentFabricRejection(error))
|
|
4697
|
+
await recoverLocalInstallation(error);
|
|
4698
|
+
throw error;
|
|
4699
|
+
}
|
|
4700
|
+
return { ok: true, rolloutId: rollout.id, bundleId: bundle.bundleId, status: receipt.status, changed: true };
|
|
4701
|
+
}
|
|
4702
|
+
finally {
|
|
4703
|
+
await rm(sourceRoot, { recursive: true, force: true });
|
|
4704
|
+
}
|
|
4705
|
+
}
|
|
4706
|
+
async function skillSync(flags) {
|
|
4707
|
+
const workspaceId = required(flags, 'workspace-id');
|
|
4708
|
+
const providerValue = required(flags, 'provider');
|
|
4709
|
+
if (!isLocalProviderId(providerValue))
|
|
4710
|
+
throw new Error('Skill provider must be codex, claude, agy, or hermes.');
|
|
4711
|
+
const provider = providerValue;
|
|
4712
|
+
return withWorkspaceSkillActivationLock(workspaceId, provider, async () => {
|
|
4713
|
+
const policyPath = required(flags, 'policy');
|
|
4714
|
+
const prepared = await prepareSkillUpdate({ workspaceId, provider, policyPath });
|
|
4715
|
+
if (!prepared)
|
|
4716
|
+
return { ok: true, rollout: null, changed: false };
|
|
4717
|
+
return activatePreparedSkillUpdate({ prepared, workspaceId, provider, policyPath, flags });
|
|
4284
4718
|
});
|
|
4285
4719
|
}
|
|
4720
|
+
async function installedRepositoryKnowledge(workspace) {
|
|
4721
|
+
const config = await readDeviceConfig();
|
|
4722
|
+
if (!config || config.organizationId !== workspace.organizationId || !workspace.repositoryBindingId || !workspace.repositoryAgentId) {
|
|
4723
|
+
throw new Error('Installed repository knowledge requires current enrolled workspace scope.');
|
|
4724
|
+
}
|
|
4725
|
+
const identity = await loadOrCreateDeviceIdentity({ hqUrl: config.hqUrl, organizationId: config.organizationId });
|
|
4726
|
+
const enrollment = await loadDeviceEnrollmentAnchor({ config });
|
|
4727
|
+
if (identity.publicKeyEd25519 !== enrollment.devicePublicKeyEd25519)
|
|
4728
|
+
throw new Error('Installed knowledge device identity mismatch.');
|
|
4729
|
+
const organizationAgentId = workspace.repositoryAgentId;
|
|
4730
|
+
return selectInstalledRepositoryKnowledge({ organizationId: config.organizationId,
|
|
4731
|
+
repositoryBindingId: workspace.repositoryBindingId, repositoryAgentId: organizationAgentId,
|
|
4732
|
+
loadProvider: async (provider) => {
|
|
4733
|
+
const root = nativeSkillDirectory(provider);
|
|
4734
|
+
if (!await pathExistsOrThrow(resolve(root, '.dharma-managed/workspaces', workspace.workspaceId, 'ACTIVE_BUNDLE')))
|
|
4735
|
+
return null;
|
|
4736
|
+
const anchorInput = { config, workspaceId: workspace.workspaceId, organizationAgentId, provider };
|
|
4737
|
+
const active = await loadActiveSkillAuthorizationAnchor(anchorInput);
|
|
4738
|
+
if (!active)
|
|
4739
|
+
throw new Error('Installed knowledge release lacks a protected authorization anchor.');
|
|
4740
|
+
const read = (expectedReceiptHash) => readVerifiedRepositoryKnowledge({
|
|
4741
|
+
nativeSkillDirectory: root, workspaceId: workspace.workspaceId, provider,
|
|
4742
|
+
organizationId: config.organizationId, organizationAgentId, deviceId: config.deviceId,
|
|
4743
|
+
serverPublicKey: createPublicKey({ key: { kty: 'OKP', crv: 'Ed25519', x: enrollment.serverPublicKeyEd25519 }, format: 'jwk' }),
|
|
4744
|
+
devicePublicKey: createPublicKey({ key: { kty: 'OKP', crv: 'Ed25519', x: identity.publicKeyEd25519 }, format: 'jwk' }), expectedReceiptHash,
|
|
4745
|
+
});
|
|
4746
|
+
try {
|
|
4747
|
+
return await read(active.receiptHash);
|
|
4748
|
+
}
|
|
4749
|
+
catch (error) {
|
|
4750
|
+
if (!(error instanceof Error) || error.message !== 'Active bundle receipt is not the current protected authorization.')
|
|
4751
|
+
throw error;
|
|
4752
|
+
const refreshed = await loadActiveSkillAuthorizationAnchor({ ...anchorInput, fresh: true });
|
|
4753
|
+
if (!refreshed)
|
|
4754
|
+
throw error;
|
|
4755
|
+
return read(refreshed.receiptHash);
|
|
4756
|
+
}
|
|
4757
|
+
} });
|
|
4758
|
+
}
|
|
4759
|
+
async function takeCachedSkillUpdate(input) {
|
|
4760
|
+
const context = await loadSkillSynchronizationPolicy(input.policyPath, input.workspaceId);
|
|
4761
|
+
const { workspace, policy, config } = context;
|
|
4762
|
+
if (!workspace.repositoryAgentId)
|
|
4763
|
+
return null;
|
|
4764
|
+
const cached = await takeSkillPreparationCache({ home: dharmaHome(), workspaceId: input.workspaceId,
|
|
4765
|
+
provider: input.provider, organizationId: config.organizationId, deviceId: config.deviceId,
|
|
4766
|
+
repositoryAgentId: workspace.repositoryAgentId, repositoryBindingId: workspace.repositoryBindingId ?? null,
|
|
4767
|
+
policyHash: sha256(canonicalize(policy)), assertCurrent: () => {
|
|
4768
|
+
verifyServerAuthorizedPolicy({ policy, publicKeyEd25519: config.serverPublicKeyEd25519,
|
|
4769
|
+
organizationId: config.organizationId, workspaceId: input.workspaceId });
|
|
4770
|
+
} });
|
|
4771
|
+
if (!cached)
|
|
4772
|
+
return null;
|
|
4773
|
+
try {
|
|
4774
|
+
const bundle = cached.record.bundle;
|
|
4775
|
+
const serverPublicKey = createPublicKey({ key: { kty: 'OKP', crv: 'Ed25519', x: config.serverPublicKeyEd25519 }, format: 'jwk' });
|
|
4776
|
+
verifySkillBundle(bundle, serverPublicKey);
|
|
4777
|
+
for (const skill of bundle.skills) {
|
|
4778
|
+
if (await contentHash(containedInlinePath(cached.sourceRoot, skill.path)) !== skill.contentHash) {
|
|
4779
|
+
throw new Error('Cached skill tree does not match the signed content hash.');
|
|
4780
|
+
}
|
|
4781
|
+
}
|
|
4782
|
+
return { workspace, policy, config, destination: nativeSkillDirectory(input.provider), fabric: input.fabric,
|
|
4783
|
+
rollout: { id: String(cached.record.rolloutId), bundle }, bundle, sourceRoot: cached.sourceRoot,
|
|
4784
|
+
repositoryDelivery: undefined, assertCurrent: () => verifySkillBundle(bundle, serverPublicKey) };
|
|
4785
|
+
}
|
|
4786
|
+
catch (error) {
|
|
4787
|
+
await rm(cached.sourceRoot, { recursive: true, force: true });
|
|
4788
|
+
throw error;
|
|
4789
|
+
}
|
|
4790
|
+
}
|
|
4286
4791
|
async function relayStart(flags) {
|
|
4287
4792
|
const policyPath = resolve(required(flags, 'policy'));
|
|
4288
|
-
const
|
|
4289
|
-
if (!
|
|
4793
|
+
const selectedWorkspace = (await registry()).find((item) => resolve(item.path, '.dharma', 'approved-policy.json') === policyPath);
|
|
4794
|
+
if (!selectedWorkspace)
|
|
4290
4795
|
throw new Error('Relay policy must be the canonical policy of one registered workspace.');
|
|
4796
|
+
let canonicalWorkspace = selectedWorkspace;
|
|
4291
4797
|
let policy = await loadVerifiedWorkspacePolicy(policyPath, canonicalWorkspace.workspaceId);
|
|
4292
4798
|
const fabric = await client();
|
|
4293
4799
|
const leaseSeconds = Number(flags.get('lease-seconds') || 120);
|
|
@@ -4296,7 +4802,8 @@ async function relayStart(flags) {
|
|
|
4296
4802
|
await mkdir(resolve(dharmaHome(), 'relay'), { recursive: true, mode: 0o700 });
|
|
4297
4803
|
await writeFile(pidPath, `${process.pid}\n`, { mode: 0o600 });
|
|
4298
4804
|
let stopping = false;
|
|
4299
|
-
|
|
4805
|
+
let stopPreparation;
|
|
4806
|
+
const stop = () => { stopping = true; stopPreparation?.(); };
|
|
4300
4807
|
process.once('SIGINT', stop);
|
|
4301
4808
|
process.once('SIGTERM', stop);
|
|
4302
4809
|
let tasksCompleted = 0;
|
|
@@ -4305,12 +4812,80 @@ async function relayStart(flags) {
|
|
|
4305
4812
|
let trajectorySyncsCompleted = 0;
|
|
4306
4813
|
let nextPolicyRefreshAt = 0;
|
|
4307
4814
|
let evidencePolicyFresh = false;
|
|
4815
|
+
const repositorySourceWatcher = new RepositorySourceWatcher();
|
|
4816
|
+
let nextRepositorySourceScanAt = 0;
|
|
4817
|
+
let repositorySourceCandidates = 0;
|
|
4818
|
+
let repositorySourceFailures = 0;
|
|
4819
|
+
let repositorySourceState = 'not_scanned';
|
|
4308
4820
|
const { LocalVault, loadOrCreateVaultMasterKey } = await loadVaultModule();
|
|
4309
4821
|
const vault = await LocalVault.open({
|
|
4310
4822
|
root: resolve(dharmaHome(), 'vault'),
|
|
4311
4823
|
masterKey: await loadOrCreateVaultMasterKey(),
|
|
4312
4824
|
rawLocalDays: rawLocalRetentionDays(policy),
|
|
4313
4825
|
});
|
|
4826
|
+
let skillPreparationsCompleted = 0;
|
|
4827
|
+
let skillPreparationFailures = 0;
|
|
4828
|
+
let skillActivationsCompleted = 0;
|
|
4829
|
+
let skillActivationFailures = 0;
|
|
4830
|
+
let nextSkillActivationAt = 0;
|
|
4831
|
+
const skillPreparationPump = startSkillPreparationPump({
|
|
4832
|
+
prepare: async (assertRunning) => {
|
|
4833
|
+
await prepareProvidersIndependently(providerAdapters, assertRunning, async (adapter) => {
|
|
4834
|
+
assertRunning();
|
|
4835
|
+
const capability = await adapter.capability();
|
|
4836
|
+
assertRunning();
|
|
4837
|
+
if (capability.skillInstall === 'unavailable' || !isLocalProviderId(adapter.providerId))
|
|
4838
|
+
return;
|
|
4839
|
+
const provider = adapter.providerId;
|
|
4840
|
+
await withSkillPreparationTransaction({ home: dharmaHome(), workspaceId: canonicalWorkspace.workspaceId,
|
|
4841
|
+
provider, assertCurrent: assertRunning }, async () => {
|
|
4842
|
+
const prepared = await prepareSkillUpdate({ workspaceId: canonicalWorkspace.workspaceId,
|
|
4843
|
+
provider, policyPath, fabric, automatic: true, assertRunning });
|
|
4844
|
+
if (!prepared) {
|
|
4845
|
+
assertRunning();
|
|
4846
|
+
return;
|
|
4847
|
+
}
|
|
4848
|
+
let retained = false;
|
|
4849
|
+
try {
|
|
4850
|
+
prepared.assertCurrent();
|
|
4851
|
+
const repositoryAgentId = prepared.workspace.repositoryAgentId;
|
|
4852
|
+
if (!repositoryAgentId)
|
|
4853
|
+
throw new Error('Preparation cache requires an enrolled repository agent.');
|
|
4854
|
+
const pendingRecord = await serializeSkillPreparationRecord({
|
|
4855
|
+
schema: 'dharma.skill-preparation/v1', organizationId: prepared.config.organizationId,
|
|
4856
|
+
deviceId: prepared.config.deviceId, workspaceId: prepared.workspace.workspaceId,
|
|
4857
|
+
repositoryAgentId,
|
|
4858
|
+
repositoryBindingId: prepared.workspace.repositoryBindingId ?? null, provider,
|
|
4859
|
+
policyHash: sha256(canonicalize(prepared.policy)), rolloutId: prepared.rollout.id,
|
|
4860
|
+
bundle: prepared.bundle, repositoryPackage: prepared.repositoryDelivery
|
|
4861
|
+
? { envelope: prepared.repositoryDelivery.envelope, index: prepared.repositoryDelivery.index } : null,
|
|
4862
|
+
preparedAt: new Date().toISOString(), activationAuthorized: false,
|
|
4863
|
+
});
|
|
4864
|
+
prepared.assertCurrent();
|
|
4865
|
+
await writeFile(resolve(prepared.sourceRoot, 'PREPARED.json'), pendingRecord, { mode: 0o600, flag: 'wx' });
|
|
4866
|
+
prepared.assertCurrent();
|
|
4867
|
+
await publishSkillPreparationCache({ home: dharmaHome(), workspaceId: canonicalWorkspace.workspaceId,
|
|
4868
|
+
provider, sourceRoot: prepared.sourceRoot, recordBytes: pendingRecord, assertCurrent: prepared.assertCurrent,
|
|
4869
|
+
expected: { organizationId: prepared.config.organizationId, deviceId: prepared.config.deviceId,
|
|
4870
|
+
repositoryAgentId,
|
|
4871
|
+
repositoryBindingId: prepared.workspace.repositoryBindingId ?? null,
|
|
4872
|
+
policyHash: sha256(canonicalize(prepared.policy)), rolloutId: prepared.rollout.id,
|
|
4873
|
+
bundleId: prepared.bundle.bundleId, bundleHash: prepared.bundle.bundleHash },
|
|
4874
|
+
onCommitted: () => { retained = true; } });
|
|
4875
|
+
skillPreparationsCompleted += 1;
|
|
4876
|
+
}
|
|
4877
|
+
finally {
|
|
4878
|
+
if (!retained)
|
|
4879
|
+
await rm(prepared.sourceRoot, { recursive: true, force: true });
|
|
4880
|
+
}
|
|
4881
|
+
});
|
|
4882
|
+
}, () => { skillPreparationFailures += 1; });
|
|
4883
|
+
},
|
|
4884
|
+
onError: () => { skillPreparationFailures += 1; },
|
|
4885
|
+
});
|
|
4886
|
+
stopPreparation = skillPreparationPump.requestStop;
|
|
4887
|
+
if (stopping)
|
|
4888
|
+
stopPreparation();
|
|
4314
4889
|
try {
|
|
4315
4890
|
const recoveredTaskTrajectories = await deferUnavailableRelayRetention(async () => finalizeRecoveredSignedTaskTrajectories(fabric, policy));
|
|
4316
4891
|
if (recoveredTaskTrajectories.state === 'completed') {
|
|
@@ -4328,6 +4903,54 @@ async function relayStart(flags) {
|
|
|
4328
4903
|
}
|
|
4329
4904
|
nextPolicyRefreshAt = Date.now() + 60_000;
|
|
4330
4905
|
}
|
|
4906
|
+
if (canonicalWorkspace.repositoryBindingId && canonicalWorkspace.repositoryAgentId) {
|
|
4907
|
+
try {
|
|
4908
|
+
const current = await pollRepositoryCandidate({ transport: fabric,
|
|
4909
|
+
outboxRoot: resolve(dharmaHome(), 'relay', 'repository-candidates'),
|
|
4910
|
+
scope: { organizationId: canonicalWorkspace.organizationId, workspaceId: canonicalWorkspace.workspaceId,
|
|
4911
|
+
repositoryBindingId: canonicalWorkspace.repositoryBindingId,
|
|
4912
|
+
repositoryAgentId: canonicalWorkspace.repositoryAgentId } });
|
|
4913
|
+
if (current && canonicalWorkspace.repositoryPackage) {
|
|
4914
|
+
canonicalWorkspace = { ...canonicalWorkspace, repositoryPackage: { ...canonicalWorkspace.repositoryPackage,
|
|
4915
|
+
state: current.state, candidateId: current.candidateId, operationId: current.operationId,
|
|
4916
|
+
snapshotHash: current.snapshotHash, releaseId: current.releaseId,
|
|
4917
|
+
generation: current.state === 'published' ? Math.max(1, canonicalWorkspace.repositoryPackage.generation) : canonicalWorkspace.repositoryPackage.generation } };
|
|
4918
|
+
await saveWorkspaceRecord(canonicalWorkspace);
|
|
4919
|
+
}
|
|
4920
|
+
}
|
|
4921
|
+
catch {
|
|
4922
|
+
repositorySourceFailures += 1;
|
|
4923
|
+
}
|
|
4924
|
+
}
|
|
4925
|
+
if (performance.now() >= nextRepositorySourceScanAt) {
|
|
4926
|
+
try {
|
|
4927
|
+
if (!canonicalWorkspace.repositoryBindingId || !canonicalWorkspace.repositoryAgentId) {
|
|
4928
|
+
throw new Error('Repository source synchronization requires a complete repository binding.');
|
|
4929
|
+
}
|
|
4930
|
+
const sourceCycle = await scanRepositorySourceChanges({
|
|
4931
|
+
workspace: canonicalWorkspace.path, organizationId: canonicalWorkspace.organizationId,
|
|
4932
|
+
workspaceId: canonicalWorkspace.workspaceId, repositoryBindingId: canonicalWorkspace.repositoryBindingId,
|
|
4933
|
+
repositoryAgentId: canonicalWorkspace.repositoryAgentId, transport: fabric, watcher: repositorySourceWatcher,
|
|
4934
|
+
loadRetainedKnowledge: () => installedRepositoryKnowledge(canonicalWorkspace),
|
|
4935
|
+
submitCandidate: snapshot => synchronizeRepositoryCandidate({ transport: fabric,
|
|
4936
|
+
outboxRoot: resolve(dharmaHome(), 'relay', 'repository-candidates'),
|
|
4937
|
+
scope: { organizationId: canonicalWorkspace.organizationId,
|
|
4938
|
+
workspaceId: canonicalWorkspace.workspaceId,
|
|
4939
|
+
repositoryBindingId: canonicalWorkspace.repositoryBindingId,
|
|
4940
|
+
repositoryAgentId: canonicalWorkspace.repositoryAgentId },
|
|
4941
|
+
snapshot, initialRepository: false }),
|
|
4942
|
+
});
|
|
4943
|
+
repositorySourceState = sourceCycle.state;
|
|
4944
|
+
if (sourceCycle.localMutation)
|
|
4945
|
+
repositorySourceCandidates += 1;
|
|
4946
|
+
}
|
|
4947
|
+
catch {
|
|
4948
|
+
repositorySourceWatcher.invalidate();
|
|
4949
|
+
repositorySourceFailures += 1;
|
|
4950
|
+
repositorySourceState = 'blocked';
|
|
4951
|
+
}
|
|
4952
|
+
nextRepositorySourceScanAt = performance.now() + 60_000;
|
|
4953
|
+
}
|
|
4331
4954
|
let evidenceRequestId;
|
|
4332
4955
|
if (evidencePolicyFresh) {
|
|
4333
4956
|
const evidenceCycle = await deferUnavailableRelayRetention(async () => {
|
|
@@ -4351,6 +4974,37 @@ async function relayStart(flags) {
|
|
|
4351
4974
|
const result = await executeOneTask(fabric, leaseSeconds);
|
|
4352
4975
|
if (result.taskId)
|
|
4353
4976
|
tasksCompleted += 1;
|
|
4977
|
+
if (!result.taskId && performance.now() >= nextSkillActivationAt) {
|
|
4978
|
+
for (const adapter of providerAdapters) {
|
|
4979
|
+
try {
|
|
4980
|
+
const capability = await adapter.capability();
|
|
4981
|
+
if (capability.skillInstall === 'unavailable' || !isLocalProviderId(adapter.providerId))
|
|
4982
|
+
continue;
|
|
4983
|
+
const provider = adapter.providerId;
|
|
4984
|
+
await withWorkspaceSkillActivationLock(canonicalWorkspace.workspaceId, provider, async () => {
|
|
4985
|
+
await withSkillPreparationTransaction({ home: dharmaHome(), workspaceId: canonicalWorkspace.workspaceId,
|
|
4986
|
+
provider, assertCurrent: () => { if (stopping)
|
|
4987
|
+
throw new Error('Relay is stopping.'); } }, async () => {
|
|
4988
|
+
const cached = await takeCachedSkillUpdate({ workspaceId: canonicalWorkspace.workspaceId,
|
|
4989
|
+
provider, policyPath, fabric });
|
|
4990
|
+
const prepared = cached || await prepareSkillUpdate({ workspaceId: canonicalWorkspace.workspaceId,
|
|
4991
|
+
provider, policyPath, fabric, automatic: true });
|
|
4992
|
+
if (!prepared)
|
|
4993
|
+
return;
|
|
4994
|
+
if (!cached)
|
|
4995
|
+
skillPreparationsCompleted += 1;
|
|
4996
|
+
await activatePreparedSkillUpdate({ prepared, workspaceId: canonicalWorkspace.workspaceId,
|
|
4997
|
+
provider, policyPath, flags: new Map() });
|
|
4998
|
+
skillActivationsCompleted += 1;
|
|
4999
|
+
});
|
|
5000
|
+
});
|
|
5001
|
+
}
|
|
5002
|
+
catch {
|
|
5003
|
+
skillActivationFailures += 1;
|
|
5004
|
+
}
|
|
5005
|
+
}
|
|
5006
|
+
nextSkillActivationAt = performance.now() + 60_000;
|
|
5007
|
+
}
|
|
4354
5008
|
if (flags.has('once'))
|
|
4355
5009
|
break;
|
|
4356
5010
|
if (!result.taskId && !evidenceRequestId)
|
|
@@ -4358,6 +5012,7 @@ async function relayStart(flags) {
|
|
|
4358
5012
|
} while (!stopping);
|
|
4359
5013
|
}
|
|
4360
5014
|
finally {
|
|
5015
|
+
await skillPreparationPump.stop();
|
|
4361
5016
|
vault.close();
|
|
4362
5017
|
process.removeListener('SIGINT', stop);
|
|
4363
5018
|
process.removeListener('SIGTERM', stop);
|
|
@@ -4366,6 +5021,9 @@ async function relayStart(flags) {
|
|
|
4366
5021
|
return {
|
|
4367
5022
|
ok: true, stopped: true, tasksCompleted, taskTrajectoriesRecovered,
|
|
4368
5023
|
evidenceResponsesCompleted, trajectorySyncsCompleted,
|
|
5024
|
+
repositorySourceCandidates, repositorySourceFailures, repositorySourceState,
|
|
5025
|
+
skillPreparationsCompleted, skillPreparationFailures, skillActivationsCompleted, skillActivationFailures,
|
|
5026
|
+
sharedRepositoryReady: await repositorySharedReady(canonicalWorkspace),
|
|
4369
5027
|
};
|
|
4370
5028
|
}
|
|
4371
5029
|
export async function run(argv) {
|
|
@@ -4393,6 +5051,13 @@ export async function run(argv) {
|
|
|
4393
5051
|
}
|
|
4394
5052
|
if (command === 'repositories' && subcommand === 'list')
|
|
4395
5053
|
return repositoriesList(flags);
|
|
5054
|
+
if (command === 'repositories' && subcommand === 'snapshot') {
|
|
5055
|
+
return repositorySnapshotCommand(flags, repeated.get('approved-output') || []);
|
|
5056
|
+
}
|
|
5057
|
+
if (command === 'repositories' && ['role-register', 'role-discover', 'ask', 'reply'].includes(String(subcommand))) {
|
|
5058
|
+
const action = subcommand === 'role-register' ? 'register' : subcommand === 'role-discover' ? 'discover' : subcommand;
|
|
5059
|
+
return repositoryRoleCommand(action, flags);
|
|
5060
|
+
}
|
|
4396
5061
|
if (command === 'repositories' && subcommand === 'status') {
|
|
4397
5062
|
const listed = await repositoriesList(flags);
|
|
4398
5063
|
return {
|