@dharma-ai-labs/agent-fabric 0.2.119 → 0.2.120
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/AGENT_FABRIC_ONBOARDING.md +2 -0
- package/README.md +23 -0
- package/dist/demoEnrollment.d.ts.map +1 -1
- package/dist/demoEnrollment.js +81 -9
- package/dist/demoEnrollment.js.map +1 -1
- package/dist/demoEnrollment.test.js +185 -0
- package/dist/demoEnrollment.test.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +541 -372
- package/dist/index.js.map +1 -1
- package/dist/index.test.js +2 -2
- package/dist/index.test.js.map +1 -1
- package/dist/legacyInstallerRecovery.d.ts +30 -0
- package/dist/legacyInstallerRecovery.d.ts.map +1 -0
- package/dist/legacyInstallerRecovery.js +107 -0
- package/dist/legacyInstallerRecovery.js.map +1 -0
- package/dist/legacyInstallerRecovery.test.d.ts +2 -0
- package/dist/legacyInstallerRecovery.test.d.ts.map +1 -0
- package/dist/legacyInstallerRecovery.test.js +126 -0
- package/dist/legacyInstallerRecovery.test.js.map +1 -0
- package/dist/relayAutostart.d.ts +1 -0
- package/dist/relayAutostart.d.ts.map +1 -1
- package/dist/relayAutostart.js +12 -3
- package/dist/relayAutostart.js.map +1 -1
- package/dist/relayAutostart.test.js +37 -0
- package/dist/relayAutostart.test.js.map +1 -1
- package/dist/relayRuntimeUpgrade.test.js +13 -2
- package/dist/relayRuntimeUpgrade.test.js.map +1 -1
- package/dist/repositoryInstallerFiles.d.ts +2 -1
- package/dist/repositoryInstallerFiles.d.ts.map +1 -1
- package/dist/repositoryInstallerFiles.js +6 -3
- package/dist/repositoryInstallerFiles.js.map +1 -1
- package/dist/repositoryOnboardingCaller.test.js +24 -0
- package/dist/repositoryOnboardingCaller.test.js.map +1 -1
- package/dist/repositoryOnboardingFlow.test.js +4 -3
- package/dist/repositoryOnboardingFlow.test.js.map +1 -1
- package/dist/repositoryRelaySupervisor.d.ts +39 -0
- package/dist/repositoryRelaySupervisor.d.ts.map +1 -0
- package/dist/repositoryRelaySupervisor.js +145 -0
- package/dist/repositoryRelaySupervisor.js.map +1 -0
- package/dist/repositoryRelaySupervisor.test.d.ts +2 -0
- package/dist/repositoryRelaySupervisor.test.d.ts.map +1 -0
- package/dist/repositoryRelaySupervisor.test.js +189 -0
- package/dist/repositoryRelaySupervisor.test.js.map +1 -0
- package/dist/skillPreparationCache.test.js +8 -4
- package/dist/skillPreparationCache.test.js.map +1 -1
- package/dist/skillPreparationCaller.test.js +4 -2
- package/dist/skillPreparationCaller.test.js.map +1 -1
- 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/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -17,6 +17,7 @@ import { contentHash, getActiveSkillBundleAuthorization, getExpiredSkillBundleAu
|
|
|
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 { superviseRelay } from './relaySupervisor.js';
|
|
20
|
+
import { repositoryRelayObservationReady, runRegisteredRepositoryRelays, selectRepositoryRelayRegistrations, serializeRelayWork, waitForRelayRefresh } from './repositoryRelaySupervisor.js';
|
|
20
21
|
import { disableRelayAutostart, enableRelayAutostart, inspectOwnedRelayAutostart, relayAutostartStatus, startRelayAutostart, stopRelayAutostart } from './relayAutostart.js';
|
|
21
22
|
import { relayRuntimeObservationReady, upgradeRelayRuntime } from './relayRuntimeUpgrade.js';
|
|
22
23
|
import { assertStudyExactPoll, studyTaskIdFromFlags } from './studyExactTaskSelector.js';
|
|
@@ -25,6 +26,7 @@ import { inventoryRepositoryPackage, readRepositoryPackageSnapshot, readReposito
|
|
|
25
26
|
import { validateRepositorySourceAuthorization } from './repositorySourceAuthorization.js';
|
|
26
27
|
import { advanceRepositorySourceBaseline, BlockedRepositorySourceRetry, fetchRepositorySourceAuthorization, recoverPublishedLocalSourceBaseline, RepositorySourceWatcher, scanRepositorySourceChanges, seedRepositorySourceWatcher } from './repositorySourceSync.js';
|
|
27
28
|
import { assertRepositoryInstallerOwnership, writeRepositoryInstallerFile } from './repositoryInstallerFiles.js';
|
|
29
|
+
import { recoverLegacyRepositoryInstaller, selectLegacyInstallerRecoveryWorkspace } from './legacyInstallerRecovery.js';
|
|
28
30
|
import { onboardingResumeCommand, selectDeviceWorkspace, workspaceIdForDevice } from './onboardingWorkspace.js';
|
|
29
31
|
import { receiveRepositoryPackageDelivery } from './repositoryPackageDelivery.js';
|
|
30
32
|
import { selectInstalledRepositoryKnowledge } from './repositoryInstalledKnowledge.js';
|
|
@@ -56,7 +58,7 @@ import { openCodexAppServerTransport } from '@dharma-ai-labs/agent-fabric-provid
|
|
|
56
58
|
import { createNamedSessionTrust, isNamedSessionOwnerReceipt, renewNamedSessionLifetime } from './namedSessionTrust.js';
|
|
57
59
|
import { startNamedCodexThread } from './namedCodexThread.js';
|
|
58
60
|
export { openCooperativeInboxSession } from './cooperativeInboxSession.js';
|
|
59
|
-
const VERSION = '0.2.
|
|
61
|
+
const VERSION = '0.2.120';
|
|
60
62
|
const USAGE = CLI_USAGE;
|
|
61
63
|
const execFileAsync = promisify(execFile);
|
|
62
64
|
const LOCAL_PROVIDER_IDS = ['codex', 'claude', 'agy', 'hermes'];
|
|
@@ -1303,12 +1305,24 @@ async function registry() {
|
|
|
1303
1305
|
}
|
|
1304
1306
|
async function saveRegistry(items) {
|
|
1305
1307
|
await mkdir(resolve(dharmaHome(), 'registry'), { recursive: true, mode: 0o700 });
|
|
1306
|
-
await
|
|
1308
|
+
await writeJsonAtomic(workspaceRegistryPath(), items);
|
|
1307
1309
|
}
|
|
1308
1310
|
async function saveWorkspaceRecord(entry) {
|
|
1309
|
-
const
|
|
1310
|
-
|
|
1311
|
-
|
|
1311
|
+
const release = await acquirePidLock(`${workspaceRegistryPath()}.mutation.lock`, 10_000, 'Workspace registry is busy; preserve it for recovery.');
|
|
1312
|
+
try {
|
|
1313
|
+
const value = await readFile(workspaceRegistryPath(), 'utf8').catch(error => {
|
|
1314
|
+
if (error.code === 'ENOENT')
|
|
1315
|
+
return '[]';
|
|
1316
|
+
throw error;
|
|
1317
|
+
});
|
|
1318
|
+
const items = JSON.parse(value);
|
|
1319
|
+
if (!Array.isArray(items))
|
|
1320
|
+
throw new Error('Workspace registry is invalid; preserve it for recovery.');
|
|
1321
|
+
await saveRegistry([...items.filter(item => item.workspaceId !== entry.workspaceId), entry]);
|
|
1322
|
+
}
|
|
1323
|
+
finally {
|
|
1324
|
+
await release();
|
|
1325
|
+
}
|
|
1312
1326
|
}
|
|
1313
1327
|
async function gitValue(workspace, argv) {
|
|
1314
1328
|
try {
|
|
@@ -1656,6 +1670,15 @@ export async function waitForRelayReadiness(options = {}) {
|
|
|
1656
1670
|
throw new Error(`The outbound relay did not become ready after bootstrap (${reason}).`);
|
|
1657
1671
|
}
|
|
1658
1672
|
async function startRelayDaemon(policyPath) {
|
|
1673
|
+
const config = await readDeviceConfig();
|
|
1674
|
+
if (!config)
|
|
1675
|
+
throw new Error('Relay startup requires current device enrollment.');
|
|
1676
|
+
const policy = await loadOrganizationPolicy(policyPath);
|
|
1677
|
+
const workspaceId = policy.serverAuthorization?.workspaceId;
|
|
1678
|
+
const selected = (await registry()).find(row => row.workspaceId === workspaceId
|
|
1679
|
+
&& row.organizationId === config.organizationId && resolve(row.path, '.dharma', 'approved-policy.json') === policyPath);
|
|
1680
|
+
if (!selected)
|
|
1681
|
+
throw new Error('Relay policy must belong to this enrolled repository.');
|
|
1659
1682
|
const alreadyRunning = await relayProcessState() === 'running';
|
|
1660
1683
|
const supervisorState = await relaySupervisorProcessState();
|
|
1661
1684
|
if (supervisorState === 'unknown')
|
|
@@ -1677,8 +1700,11 @@ async function startRelayDaemon(policyPath) {
|
|
|
1677
1700
|
const binding = await readFile(resolve(dharmaHome(), 'relay', 'supervisor-workspace.json'), 'utf8')
|
|
1678
1701
|
.then(value => JSON.parse(value)).catch(() => null);
|
|
1679
1702
|
if (binding?.pid === pid) {
|
|
1680
|
-
if (binding.
|
|
1681
|
-
throw new Error('
|
|
1703
|
+
if (binding.version !== VERSION || !binding.standardRepositories) {
|
|
1704
|
+
throw new Error('relay_runtime_upgrade_required: safely stop and upgrade the existing startup-anchor relay; do not retarget a working receiver.');
|
|
1705
|
+
}
|
|
1706
|
+
if (binding.organizationId !== config.organizationId || binding.deviceId !== config.deviceId) {
|
|
1707
|
+
throw new Error('relay_workspace_conflict: the active supervisor belongs to another enrollment.');
|
|
1682
1708
|
}
|
|
1683
1709
|
supervisorReady = true;
|
|
1684
1710
|
break;
|
|
@@ -1689,7 +1715,14 @@ async function startRelayDaemon(policyPath) {
|
|
|
1689
1715
|
}
|
|
1690
1716
|
if (!supervisorReady)
|
|
1691
1717
|
throw new Error('The relay supervisor did not become ready after bootstrap.');
|
|
1692
|
-
const readiness = await waitForRelayReadiness({ expectedVersion: VERSION
|
|
1718
|
+
const readiness = await waitForRelayReadiness({ expectedVersion: VERSION, attempts: 120, delayMs: 1000, processState: async () => {
|
|
1719
|
+
if (await relayProcessState() !== 'running')
|
|
1720
|
+
return 'stopped';
|
|
1721
|
+
const pid = Number((await readFile(resolve(dharmaHome(), 'relay', 'relay.pid'), 'utf8')).trim());
|
|
1722
|
+
const poll = await readFile(resolve(dharmaHome(), 'relay', 'repositories', selected.workspaceId, 'last-successful-poll.json'), 'utf8').then(value => JSON.parse(value)).catch(() => null);
|
|
1723
|
+
return repositoryRelayObservationReady({ observation: poll, workspaceId: selected.workspaceId,
|
|
1724
|
+
version: VERSION, pid }) ? 'running' : 'stopped';
|
|
1725
|
+
} });
|
|
1693
1726
|
return { started: !alreadyRunning, supervisor: 'running', ...readiness };
|
|
1694
1727
|
}
|
|
1695
1728
|
async function supervisedDemoCycle(registration, signal) {
|
|
@@ -1752,7 +1785,8 @@ async function relaySupervise(flags) {
|
|
|
1752
1785
|
const health = createDemoWatchHealthRecorder({ home: dharmaHome(), pid: process.pid, version: VERSION });
|
|
1753
1786
|
try {
|
|
1754
1787
|
await writeJsonAtomic(bindingPath, { pid: process.pid, workspaceId: selectedWorkspace?.workspaceId ?? null,
|
|
1755
|
-
policyPath, demoWatches: true, version: VERSION
|
|
1788
|
+
policyPath, demoWatches: true, version: VERSION, standardRepositories: Boolean(policyPath),
|
|
1789
|
+
organizationId: config?.organizationId ?? null, deviceId: config?.deviceId ?? null });
|
|
1756
1790
|
const demo = runDemoSupervisor({ signal: controller.signal,
|
|
1757
1791
|
list: () => listDemoWatchRegistrations(dharmaHome()), cycle: (registration, signal) => {
|
|
1758
1792
|
if (!health.available(demoWatchRegistrationKey(registration))) {
|
|
@@ -2123,11 +2157,36 @@ async function bootstrap(flags) {
|
|
|
2123
2157
|
const launcher = await installStableRepositoryLauncher(workspace);
|
|
2124
2158
|
const autostart = flags.has('no-relay-daemon')
|
|
2125
2159
|
? { state: 'disabled', backend: null }
|
|
2126
|
-
: await withOnboardingStage('autostart', String(onboarded.workspaceId || ''), `dharma bootstrap --resume --complete --portal-url ${hqUrl} --organization-id ${organizationId} --workspace . --policy-revision ${policyRevision}`, () => withRelayStartupMutation(() =>
|
|
2127
|
-
home
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2160
|
+
: await withOnboardingStage('autostart', String(onboarded.workspaceId || ''), `dharma bootstrap --resume --complete --portal-url ${hqUrl} --organization-id ${organizationId} --workspace . --policy-revision ${policyRevision}`, () => withRelayStartupMutation(async () => {
|
|
2161
|
+
const home = dharmaHome();
|
|
2162
|
+
const existing = await relayAutostartStatus({ home });
|
|
2163
|
+
if (existing.backend !== null || existing.state !== 'disabled') {
|
|
2164
|
+
const anchor = await inspectOwnedRelayAutostart({ home });
|
|
2165
|
+
if (anchor.policy !== null) {
|
|
2166
|
+
const current = await readDeviceConfig();
|
|
2167
|
+
if (!current || current.organizationId !== organizationId || current.deviceId !== config.deviceId) {
|
|
2168
|
+
throw new Error('relay_workspace_conflict: startup enrollment changed.');
|
|
2169
|
+
}
|
|
2170
|
+
const selected = selectDeviceWorkspace(await registry(), { organizationId,
|
|
2171
|
+
deviceId: config.deviceId, path: anchor.workspace });
|
|
2172
|
+
if (!selected)
|
|
2173
|
+
throw new Error('relay_workspace_conflict: startup anchor is not registered to this device.');
|
|
2174
|
+
if (anchor.policy !== resolve(anchor.workspace, '.dharma', 'approved-policy.json')) {
|
|
2175
|
+
throw new Error('relay_workspace_conflict: startup policy is not canonical.');
|
|
2176
|
+
}
|
|
2177
|
+
const authorized = await loadOrganizationPolicy(anchor.policy);
|
|
2178
|
+
const canonical = (await registry()).filter(row => row.workspaceId === authorized.serverAuthorization?.workspaceId
|
|
2179
|
+
&& row.organizationId === organizationId && row.path === anchor.workspace
|
|
2180
|
+
&& row.routeHash === selected.routeHash && row.repositoryRemoteHash === selected.repositoryRemoteHash);
|
|
2181
|
+
if (canonical.length !== 1)
|
|
2182
|
+
throw new Error('relay_workspace_conflict: startup policy route changed.');
|
|
2183
|
+
await loadVerifiedWorkspacePolicy(anchor.policy, authorized.serverAuthorization?.workspaceId ?? '');
|
|
2184
|
+
}
|
|
2185
|
+
}
|
|
2186
|
+
return enableRelayAutostart({ home, workspace, policy: resolve(workspace, '.dharma', 'approved-policy.json'),
|
|
2187
|
+
launcher: resolve(workspace, process.platform === 'win32' ? launcher.windows : launcher.shell),
|
|
2188
|
+
version: VERSION, preserveStandardAnchor: true });
|
|
2189
|
+
}));
|
|
2131
2190
|
let sharedRepositoryReady = onboarded.sharedRepositoryReady === true;
|
|
2132
2191
|
const completionRequested = flags.has('complete');
|
|
2133
2192
|
if (!completionRequested) {
|
|
@@ -3885,6 +3944,38 @@ The CLI enrolls this device through browser-confirmed Clerk organization consent
|
|
|
3885
3944
|
knowledge,
|
|
3886
3945
|
};
|
|
3887
3946
|
}
|
|
3947
|
+
async function repositoryInstallerRecoveryCommand(flags) {
|
|
3948
|
+
if (flags.has('apply') === flags.has('dry-run'))
|
|
3949
|
+
throw new Error('Choose exactly one of --apply or --dry-run.');
|
|
3950
|
+
const fromWorkspaceId = required(flags, 'from-workspace-id');
|
|
3951
|
+
const workspaceId = required(flags, 'workspace-id');
|
|
3952
|
+
const workspace = await realpath(String(flags.get('workspace') || '.'));
|
|
3953
|
+
const organizationId = required(flags, 'organization-id');
|
|
3954
|
+
const config = await readDeviceConfig();
|
|
3955
|
+
if (!config || config.organizationId !== organizationId)
|
|
3956
|
+
throw new Error('Recovery requires current organization enrollment.');
|
|
3957
|
+
await loadDeviceEnrollmentAnchor({ config });
|
|
3958
|
+
const identity = await preflightBootstrapWorkspaceIdentity(workspace);
|
|
3959
|
+
const registered = selectLegacyInstallerRecoveryWorkspace(await registry(), { organizationId, deviceId: config.deviceId,
|
|
3960
|
+
path: workspace, repositoryRemoteHash: identity.fingerprint, workspaceId });
|
|
3961
|
+
const repositoryAgentKey = `repo:${identity.fingerprint.slice('sha256:'.length, 'sha256:'.length + 24)}`;
|
|
3962
|
+
if (!registered?.repositoryBindingId || !registered.repositoryAgentId
|
|
3963
|
+
|| registered.repositoryAgentKey !== repositoryAgentKey) {
|
|
3964
|
+
throw new Error('Recovery requires the enrolled device canonical repository binding.');
|
|
3965
|
+
}
|
|
3966
|
+
const policy = await loadVerifiedWorkspacePolicy(resolve(workspace, '.dharma', 'approved-policy.json'), registered.workspaceId);
|
|
3967
|
+
if (policy.organizationId !== organizationId || policy.serverAuthorization?.workspaceId !== registered.workspaceId) {
|
|
3968
|
+
throw new Error('Recovery requires a current signed workspace policy.');
|
|
3969
|
+
}
|
|
3970
|
+
await assertWorkspaceAuthorizationCurrent(registered.workspaceId, policy.serverAuthorization);
|
|
3971
|
+
const scope = { organizationId, workspaceId: registered.workspaceId,
|
|
3972
|
+
repositoryBindingId: registered.repositoryBindingId, repositoryAgentId: registered.repositoryAgentId };
|
|
3973
|
+
validateRepositorySourceAuthorization(await fetchRepositorySourceAuthorization(await client(), scope), scope, new Date());
|
|
3974
|
+
const recovery = await recoverLegacyRepositoryInstaller({ workspace, fromWorkspaceId,
|
|
3975
|
+
workspaceId: registered.workspaceId, repositoryAgentKey, apply: flags.has('apply') });
|
|
3976
|
+
return { ok: true, dryRun: !recovery.applied, serverMutation: false, recovery,
|
|
3977
|
+
nextAction: 'Resume pinned onboarding; recovery is not signed skill installation or activation.' };
|
|
3978
|
+
}
|
|
3888
3979
|
async function repositorySnapshotCommand(flags, outputs) {
|
|
3889
3980
|
if (flags.has('apply') && flags.has('dry-run'))
|
|
3890
3981
|
throw new Error('Choose --apply or --dry-run, not both.');
|
|
@@ -5972,6 +6063,52 @@ async function takeCachedSkillUpdate(input) {
|
|
|
5972
6063
|
}
|
|
5973
6064
|
}
|
|
5974
6065
|
async function relayStart(flags) {
|
|
6066
|
+
const anchor = resolve(required(flags, 'policy'));
|
|
6067
|
+
const config = await readDeviceConfig();
|
|
6068
|
+
if (!config)
|
|
6069
|
+
throw new Error('Relay requires current enrollment.');
|
|
6070
|
+
const declared = await loadOrganizationPolicy(anchor);
|
|
6071
|
+
const rows = await registry();
|
|
6072
|
+
const selected = rows.find(row => row.workspaceId === declared.serverAuthorization?.workspaceId
|
|
6073
|
+
&& row.organizationId === config.organizationId && resolve(row.path, '.dharma', 'approved-policy.json') === anchor);
|
|
6074
|
+
if (!selected)
|
|
6075
|
+
throw new Error('Relay policy must be the canonical policy of one registered workspace.');
|
|
6076
|
+
const authorized = await selectRepositoryRelayRegistrations(rows, config, loadOrganizationPolicy);
|
|
6077
|
+
if (!authorized.some(row => row.workspaceId === selected.workspaceId && row.policyPath === anchor)) {
|
|
6078
|
+
throw new Error('Relay policy is not registered to this enrolled device.');
|
|
6079
|
+
}
|
|
6080
|
+
await loadVerifiedWorkspacePolicy(anchor, selected.workspaceId);
|
|
6081
|
+
const release = await acquireRelayProcessLease();
|
|
6082
|
+
const controller = new AbortController();
|
|
6083
|
+
const stop = () => controller.abort();
|
|
6084
|
+
process.once('SIGINT', stop);
|
|
6085
|
+
process.once('SIGTERM', stop);
|
|
6086
|
+
const serialized = serializeRelayWork();
|
|
6087
|
+
try {
|
|
6088
|
+
if (flags.has('once'))
|
|
6089
|
+
return await relayWorkspaceLoop(flags, controller.signal, serialized);
|
|
6090
|
+
await runRegisteredRepositoryRelays({ signal: controller.signal, list: async () => {
|
|
6091
|
+
const currentConfig = await readDeviceConfig();
|
|
6092
|
+
if (currentConfig?.organizationId !== config.organizationId || currentConfig.deviceId !== config.deviceId) {
|
|
6093
|
+
throw new Error('Relay enrollment changed.');
|
|
6094
|
+
}
|
|
6095
|
+
const records = JSON.parse(await readFile(workspaceRegistryPath(), 'utf8'));
|
|
6096
|
+
if (!Array.isArray(records))
|
|
6097
|
+
throw new Error('Invalid workspace registry.');
|
|
6098
|
+
return selectRepositoryRelayRegistrations(records, config, loadOrganizationPolicy);
|
|
6099
|
+
}, run: (row, signal) => relayWorkspaceLoop(new Map([...flags, ['policy', row.policyPath]]), signal, serialized),
|
|
6100
|
+
observe: event => process.stderr.write(`${JSON.stringify({ event: 'repository_relay', ...event })}\n`) });
|
|
6101
|
+
return { ok: true, stopped: true };
|
|
6102
|
+
}
|
|
6103
|
+
finally {
|
|
6104
|
+
controller.abort();
|
|
6105
|
+
process.removeListener('SIGINT', stop);
|
|
6106
|
+
process.removeListener('SIGTERM', stop);
|
|
6107
|
+
await release();
|
|
6108
|
+
}
|
|
6109
|
+
}
|
|
6110
|
+
async function relayWorkspaceLoop(flags, signal, serialized) {
|
|
6111
|
+
signal.throwIfAborted();
|
|
5975
6112
|
const policyPath = resolve(required(flags, 'policy'));
|
|
5976
6113
|
const declaredPolicy = await loadOrganizationPolicy(policyPath);
|
|
5977
6114
|
const declaredWorkspaceId = declaredPolicy.serverAuthorization?.workspaceId;
|
|
@@ -5986,400 +6123,411 @@ async function relayStart(flags) {
|
|
|
5986
6123
|
}
|
|
5987
6124
|
let canonicalWorkspace = selectedWorkspace;
|
|
5988
6125
|
let policy = await loadVerifiedWorkspacePolicy(policyPath, canonicalWorkspace.workspaceId);
|
|
6126
|
+
signal.throwIfAborted();
|
|
5989
6127
|
const fabric = await client();
|
|
5990
6128
|
const leaseSeconds = Number(flags.get('lease-seconds') || 120);
|
|
5991
6129
|
const pollMs = Math.min(Math.max(Number(flags.get('poll-seconds') || 3), 1), 60) * 1_000;
|
|
5992
|
-
|
|
5993
|
-
|
|
5994
|
-
|
|
5995
|
-
|
|
5996
|
-
|
|
5997
|
-
|
|
5998
|
-
|
|
5999
|
-
|
|
6000
|
-
|
|
6001
|
-
|
|
6002
|
-
|
|
6003
|
-
|
|
6004
|
-
|
|
6005
|
-
|
|
6006
|
-
|
|
6007
|
-
|
|
6008
|
-
|
|
6009
|
-
|
|
6010
|
-
|
|
6011
|
-
|
|
6012
|
-
|
|
6013
|
-
|
|
6014
|
-
|
|
6015
|
-
|
|
6016
|
-
|
|
6017
|
-
|
|
6018
|
-
|
|
6019
|
-
|
|
6020
|
-
|
|
6021
|
-
|
|
6022
|
-
|
|
6023
|
-
|
|
6024
|
-
...canonicalWorkspace.repositoryPackage, localBaselineSnapshotHash: recovered,
|
|
6025
|
-
publishedLocalSnapshotHash: recovered,
|
|
6026
|
-
} };
|
|
6027
|
-
await saveWorkspaceRecord(canonicalWorkspace);
|
|
6028
|
-
}
|
|
6029
|
-
}
|
|
6030
|
-
catch {
|
|
6031
|
-
repositorySourceBaselineAvailable = false;
|
|
6130
|
+
let stopping = signal.aborted;
|
|
6131
|
+
let stopPreparation;
|
|
6132
|
+
const stop = () => { stopping = true; stopPreparation?.(); };
|
|
6133
|
+
let tasksCompleted = 0;
|
|
6134
|
+
let lastProviderFailureCategory = null;
|
|
6135
|
+
let taskTrajectoriesRecovered = 0;
|
|
6136
|
+
let evidenceResponsesCompleted = 0;
|
|
6137
|
+
let trajectorySyncsCompleted = 0;
|
|
6138
|
+
let nextPolicyRefreshAt = 0;
|
|
6139
|
+
let lastPollReceiptAt = 0;
|
|
6140
|
+
let evidencePolicyFresh = false;
|
|
6141
|
+
const repositorySourceWatcher = new RepositorySourceWatcher();
|
|
6142
|
+
const blockedRepositorySourceRetry = new BlockedRepositorySourceRetry();
|
|
6143
|
+
let repositorySourceBaselineAvailable = true;
|
|
6144
|
+
let publishedLocalSnapshotHash = canonicalWorkspace.repositoryPackage?.publishedLocalSnapshotHash
|
|
6145
|
+
|| (canonicalWorkspace.repositoryPackage?.state === 'published'
|
|
6146
|
+
&& canonicalWorkspace.repositoryPackage.snapshotHash === canonicalWorkspace.repositoryPackage.localBaselineSnapshotHash
|
|
6147
|
+
? canonicalWorkspace.repositoryPackage.snapshotHash : null);
|
|
6148
|
+
if (canonicalWorkspace.repositoryPackage && canonicalWorkspace.repositoryBindingId && canonicalWorkspace.repositoryAgentId) {
|
|
6149
|
+
try {
|
|
6150
|
+
const recovered = await recoverPublishedLocalSourceBaseline({
|
|
6151
|
+
workspace: canonicalWorkspace.path, organizationId: canonicalWorkspace.organizationId,
|
|
6152
|
+
workspaceId: canonicalWorkspace.workspaceId, repositoryBindingId: canonicalWorkspace.repositoryBindingId,
|
|
6153
|
+
repositoryAgentId: canonicalWorkspace.repositoryAgentId, record: canonicalWorkspace.repositoryPackage,
|
|
6154
|
+
});
|
|
6155
|
+
if (recovered && recovered !== publishedLocalSnapshotHash) {
|
|
6156
|
+
publishedLocalSnapshotHash = recovered;
|
|
6157
|
+
canonicalWorkspace = { ...canonicalWorkspace, repositoryPackage: {
|
|
6158
|
+
...canonicalWorkspace.repositoryPackage, localBaselineSnapshotHash: recovered,
|
|
6159
|
+
publishedLocalSnapshotHash: recovered,
|
|
6160
|
+
} };
|
|
6161
|
+
await saveWorkspaceRecord(canonicalWorkspace);
|
|
6032
6162
|
}
|
|
6033
6163
|
}
|
|
6034
|
-
|
|
6035
|
-
|
|
6036
|
-
|
|
6037
|
-
|
|
6038
|
-
|
|
6039
|
-
|
|
6040
|
-
|
|
6041
|
-
|
|
6042
|
-
|
|
6043
|
-
|
|
6044
|
-
|
|
6045
|
-
|
|
6046
|
-
|
|
6047
|
-
|
|
6048
|
-
|
|
6049
|
-
|
|
6050
|
-
|
|
6164
|
+
catch {
|
|
6165
|
+
repositorySourceBaselineAvailable = false;
|
|
6166
|
+
}
|
|
6167
|
+
}
|
|
6168
|
+
if (publishedLocalSnapshotHash) {
|
|
6169
|
+
try {
|
|
6170
|
+
await readRepositoryPackageSnapshot(canonicalWorkspace.path, publishedLocalSnapshotHash);
|
|
6171
|
+
const localHash = canonicalWorkspace.repositoryPackage?.localBaselineSnapshotHash;
|
|
6172
|
+
if (localHash && (localHash === publishedLocalSnapshotHash
|
|
6173
|
+
|| canonicalWorkspace.repositoryPackage?.pendingLocalSnapshotHash === localHash)) {
|
|
6174
|
+
await seedRepositorySourceWatcher({ workspace: canonicalWorkspace.path,
|
|
6175
|
+
organizationId: canonicalWorkspace.organizationId, workspaceId: canonicalWorkspace.workspaceId,
|
|
6176
|
+
repositoryBindingId: canonicalWorkspace.repositoryBindingId,
|
|
6177
|
+
repositoryAgentId: canonicalWorkspace.repositoryAgentId,
|
|
6178
|
+
publishedHash: publishedLocalSnapshotHash,
|
|
6179
|
+
localHash,
|
|
6180
|
+
watcher: repositorySourceWatcher });
|
|
6051
6181
|
}
|
|
6052
6182
|
}
|
|
6053
|
-
|
|
6054
|
-
|
|
6055
|
-
|
|
6056
|
-
|
|
6057
|
-
|
|
6058
|
-
|
|
6059
|
-
|
|
6060
|
-
|
|
6061
|
-
|
|
6062
|
-
|
|
6063
|
-
|
|
6064
|
-
|
|
6065
|
-
|
|
6066
|
-
|
|
6067
|
-
|
|
6068
|
-
|
|
6069
|
-
|
|
6070
|
-
|
|
6071
|
-
|
|
6072
|
-
|
|
6073
|
-
|
|
6074
|
-
|
|
6075
|
-
|
|
6076
|
-
|
|
6077
|
-
|
|
6183
|
+
catch {
|
|
6184
|
+
repositorySourceBaselineAvailable = false;
|
|
6185
|
+
}
|
|
6186
|
+
}
|
|
6187
|
+
let nextRepositorySourceScanAt = 0;
|
|
6188
|
+
let repositorySourceCandidates = 0;
|
|
6189
|
+
let repositorySourceFailures = 0;
|
|
6190
|
+
let repositorySourceState = 'not_scanned';
|
|
6191
|
+
let sourceScanFlight = null;
|
|
6192
|
+
let sourceScanReady = null;
|
|
6193
|
+
const { LocalVault, loadOrCreateVaultMasterKey } = await loadVaultModule();
|
|
6194
|
+
const vault = await LocalVault.open({
|
|
6195
|
+
root: resolve(dharmaHome(), 'vault'),
|
|
6196
|
+
masterKey: await loadOrCreateVaultMasterKey(),
|
|
6197
|
+
rawLocalDays: rawLocalRetentionDays(policy),
|
|
6198
|
+
});
|
|
6199
|
+
if (signal.aborted)
|
|
6200
|
+
vault.close();
|
|
6201
|
+
signal.throwIfAborted();
|
|
6202
|
+
signal.addEventListener('abort', stop, { once: true });
|
|
6203
|
+
if (signal.aborted)
|
|
6204
|
+
stop();
|
|
6205
|
+
let skillPreparationsCompleted = 0;
|
|
6206
|
+
let skillPreparationFailures = 0;
|
|
6207
|
+
let skillActivationsCompleted = 0;
|
|
6208
|
+
let skillActivationFailures = 0;
|
|
6209
|
+
const skillActivationFailuresByProvider = {};
|
|
6210
|
+
let nextSkillActivationAt = 0;
|
|
6211
|
+
const skillPreparationPump = startSkillPreparationPump({
|
|
6212
|
+
prepare: async (assertRunning) => {
|
|
6213
|
+
await prepareProvidersIndependently(providerAdapters, assertRunning, async (adapter) => {
|
|
6214
|
+
assertRunning();
|
|
6215
|
+
const capability = await adapter.capability();
|
|
6216
|
+
assertRunning();
|
|
6217
|
+
if (capability.skillInstall === 'unavailable' || !isLocalProviderId(adapter.providerId))
|
|
6218
|
+
return;
|
|
6219
|
+
const provider = adapter.providerId;
|
|
6220
|
+
await withSkillPreparationTransaction({ home: dharmaHome(), workspaceId: canonicalWorkspace.workspaceId,
|
|
6221
|
+
provider, assertCurrent: assertRunning }, async () => {
|
|
6222
|
+
const prepared = await prepareSkillUpdate({ workspaceId: canonicalWorkspace.workspaceId,
|
|
6223
|
+
provider, policyPath, fabric, automatic: true, assertRunning });
|
|
6224
|
+
if (!prepared) {
|
|
6225
|
+
assertRunning();
|
|
6078
6226
|
return;
|
|
6079
|
-
|
|
6080
|
-
|
|
6081
|
-
|
|
6082
|
-
|
|
6083
|
-
|
|
6084
|
-
if (!
|
|
6085
|
-
|
|
6086
|
-
|
|
6087
|
-
|
|
6088
|
-
|
|
6089
|
-
|
|
6090
|
-
prepared.
|
|
6091
|
-
|
|
6092
|
-
|
|
6093
|
-
|
|
6094
|
-
|
|
6095
|
-
|
|
6096
|
-
|
|
6227
|
+
}
|
|
6228
|
+
let retained = false;
|
|
6229
|
+
try {
|
|
6230
|
+
prepared.assertCurrent();
|
|
6231
|
+
const repositoryAgentId = prepared.workspace.repositoryAgentId;
|
|
6232
|
+
if (!repositoryAgentId)
|
|
6233
|
+
throw new Error('Preparation cache requires an enrolled repository agent.');
|
|
6234
|
+
const pendingRecord = await serializeSkillPreparationRecord({
|
|
6235
|
+
schema: 'dharma.skill-preparation/v1', organizationId: prepared.config.organizationId,
|
|
6236
|
+
deviceId: prepared.config.deviceId, workspaceId: prepared.workspace.workspaceId,
|
|
6237
|
+
repositoryAgentId,
|
|
6238
|
+
repositoryBindingId: prepared.workspace.repositoryBindingId ?? null, provider,
|
|
6239
|
+
policyHash: sha256(canonicalize(prepared.policy)), rolloutId: prepared.rollout.id,
|
|
6240
|
+
bundle: prepared.bundle, repositoryPackage: prepared.repositoryDelivery
|
|
6241
|
+
? { envelope: prepared.repositoryDelivery.envelope, index: prepared.repositoryDelivery.index } : null,
|
|
6242
|
+
preparedAt: new Date().toISOString(), activationAuthorized: false,
|
|
6243
|
+
});
|
|
6244
|
+
prepared.assertCurrent();
|
|
6245
|
+
await writeFile(resolve(prepared.sourceRoot, 'PREPARED.json'), pendingRecord, { mode: 0o600, flag: 'wx' });
|
|
6246
|
+
prepared.assertCurrent();
|
|
6247
|
+
await publishSkillPreparationCache({ home: dharmaHome(), workspaceId: canonicalWorkspace.workspaceId,
|
|
6248
|
+
provider, sourceRoot: prepared.sourceRoot, recordBytes: pendingRecord, assertCurrent: prepared.assertCurrent,
|
|
6249
|
+
expected: { organizationId: prepared.config.organizationId, deviceId: prepared.config.deviceId,
|
|
6097
6250
|
repositoryAgentId,
|
|
6098
|
-
repositoryBindingId: prepared.workspace.repositoryBindingId ?? null,
|
|
6251
|
+
repositoryBindingId: prepared.workspace.repositoryBindingId ?? null,
|
|
6099
6252
|
policyHash: sha256(canonicalize(prepared.policy)), rolloutId: prepared.rollout.id,
|
|
6100
|
-
|
|
6101
|
-
|
|
6102
|
-
|
|
6103
|
-
|
|
6104
|
-
|
|
6105
|
-
|
|
6106
|
-
prepared.
|
|
6107
|
-
|
|
6108
|
-
|
|
6109
|
-
|
|
6110
|
-
|
|
6111
|
-
|
|
6112
|
-
|
|
6113
|
-
|
|
6114
|
-
|
|
6115
|
-
|
|
6116
|
-
|
|
6117
|
-
|
|
6118
|
-
|
|
6119
|
-
|
|
6120
|
-
|
|
6121
|
-
|
|
6122
|
-
|
|
6123
|
-
|
|
6124
|
-
|
|
6125
|
-
|
|
6126
|
-
|
|
6127
|
-
|
|
6128
|
-
|
|
6129
|
-
|
|
6130
|
-
|
|
6131
|
-
|
|
6132
|
-
|
|
6133
|
-
|
|
6134
|
-
|
|
6135
|
-
|
|
6136
|
-
|
|
6137
|
-
|
|
6138
|
-
|
|
6139
|
-
|
|
6140
|
-
|
|
6141
|
-
|
|
6142
|
-
|
|
6143
|
-
|
|
6144
|
-
|
|
6145
|
-
|
|
6146
|
-
|
|
6147
|
-
|
|
6148
|
-
|
|
6149
|
-
|
|
6150
|
-
|
|
6151
|
-
|
|
6152
|
-
|
|
6153
|
-
|
|
6253
|
+
bundleId: prepared.bundle.bundleId, bundleHash: prepared.bundle.bundleHash },
|
|
6254
|
+
onCommitted: () => { retained = true; } });
|
|
6255
|
+
skillPreparationsCompleted += 1;
|
|
6256
|
+
}
|
|
6257
|
+
finally {
|
|
6258
|
+
if (!retained)
|
|
6259
|
+
await rm(prepared.sourceRoot, { recursive: true, force: true });
|
|
6260
|
+
}
|
|
6261
|
+
});
|
|
6262
|
+
}, () => { skillPreparationFailures += 1; });
|
|
6263
|
+
},
|
|
6264
|
+
onError: () => { skillPreparationFailures += 1; },
|
|
6265
|
+
});
|
|
6266
|
+
stopPreparation = skillPreparationPump.requestStop;
|
|
6267
|
+
if (stopping)
|
|
6268
|
+
stopPreparation();
|
|
6269
|
+
const applyReadySourceScan = async () => {
|
|
6270
|
+
if (!sourceScanReady)
|
|
6271
|
+
return;
|
|
6272
|
+
const ready = sourceScanReady;
|
|
6273
|
+
sourceScanReady = null;
|
|
6274
|
+
sourceScanFlight = null;
|
|
6275
|
+
if ('error' in ready) {
|
|
6276
|
+
repositorySourceWatcher.invalidate();
|
|
6277
|
+
repositorySourceFailures += 1;
|
|
6278
|
+
repositorySourceState = 'blocked';
|
|
6279
|
+
nextRepositorySourceScanAt = performance.now() + 60_000;
|
|
6280
|
+
return;
|
|
6281
|
+
}
|
|
6282
|
+
if ('state' in ready) {
|
|
6283
|
+
repositorySourceState = ready.state;
|
|
6284
|
+
nextRepositorySourceScanAt = performance.now() + 15_000;
|
|
6285
|
+
return;
|
|
6286
|
+
}
|
|
6287
|
+
const sourceCycle = ready.cycle;
|
|
6288
|
+
repositorySourceState = sourceCycle.state;
|
|
6289
|
+
if (sourceCycle.localMutation) {
|
|
6290
|
+
repositorySourceCandidates += 1;
|
|
6291
|
+
if (canonicalWorkspace.repositoryPackage && sourceCycle.persisted) {
|
|
6292
|
+
canonicalWorkspace.repositoryPackage.localBaselineSnapshotHash = sourceCycle.persisted.snapshotHash;
|
|
6293
|
+
if (sourceCycle.state === 'source_already_published') {
|
|
6294
|
+
canonicalWorkspace.repositoryPackage.publishedLocalSnapshotHash = sourceCycle.persisted.snapshotHash;
|
|
6295
|
+
canonicalWorkspace.repositoryPackage.pendingLocalSnapshotHash = null;
|
|
6296
|
+
canonicalWorkspace.repositoryPackage.pendingLocalOperationId = null;
|
|
6297
|
+
publishedLocalSnapshotHash = sourceCycle.persisted.snapshotHash;
|
|
6298
|
+
}
|
|
6299
|
+
else if (sourceCycle.candidate) {
|
|
6300
|
+
const receipt = sourceCycle.candidate;
|
|
6301
|
+
if (typeof receipt.candidateId === 'string' && typeof receipt.operationId === 'string'
|
|
6302
|
+
&& typeof receipt.snapshotHash === 'string' && sourceCycle.submittedManifestHash) {
|
|
6303
|
+
Object.assign(canonicalWorkspace.repositoryPackage, {
|
|
6304
|
+
state: receipt.state, candidateId: receipt.candidateId, operationId: receipt.operationId,
|
|
6305
|
+
snapshotHash: receipt.snapshotHash, sourceManifestHash: sourceCycle.submittedManifestHash,
|
|
6306
|
+
releaseId: typeof receipt.releaseId === 'string' ? receipt.releaseId : null,
|
|
6307
|
+
consolidationMode: 'repository_update',
|
|
6308
|
+
});
|
|
6309
|
+
}
|
|
6310
|
+
if (receipt.state === 'published') {
|
|
6154
6311
|
canonicalWorkspace.repositoryPackage.publishedLocalSnapshotHash = sourceCycle.persisted.snapshotHash;
|
|
6155
6312
|
canonicalWorkspace.repositoryPackage.pendingLocalSnapshotHash = null;
|
|
6156
6313
|
canonicalWorkspace.repositoryPackage.pendingLocalOperationId = null;
|
|
6157
6314
|
publishedLocalSnapshotHash = sourceCycle.persisted.snapshotHash;
|
|
6158
6315
|
}
|
|
6159
|
-
else if (
|
|
6160
|
-
|
|
6161
|
-
|
|
6162
|
-
|
|
6163
|
-
|
|
6164
|
-
|
|
6165
|
-
|
|
6166
|
-
|
|
6167
|
-
|
|
6168
|
-
|
|
6169
|
-
|
|
6170
|
-
|
|
6171
|
-
|
|
6172
|
-
|
|
6173
|
-
|
|
6174
|
-
|
|
6175
|
-
|
|
6176
|
-
|
|
6177
|
-
|
|
6178
|
-
|
|
6179
|
-
|
|
6316
|
+
else if ((receipt.state === 'accepted' || receipt.state === 'processing')
|
|
6317
|
+
&& typeof receipt.operationId === 'string') {
|
|
6318
|
+
canonicalWorkspace.repositoryPackage.pendingLocalSnapshotHash = sourceCycle.persisted.snapshotHash;
|
|
6319
|
+
canonicalWorkspace.repositoryPackage.pendingLocalOperationId = receipt.operationId;
|
|
6320
|
+
}
|
|
6321
|
+
else if (receipt.state === 'blocked') {
|
|
6322
|
+
canonicalWorkspace.repositoryPackage.localBaselineSnapshotHash = publishedLocalSnapshotHash;
|
|
6323
|
+
repositorySourceWatcher.invalidate();
|
|
6324
|
+
}
|
|
6325
|
+
}
|
|
6326
|
+
await saveWorkspaceRecord(canonicalWorkspace);
|
|
6327
|
+
}
|
|
6328
|
+
}
|
|
6329
|
+
nextRepositorySourceScanAt = performance.now() + (sourceCycle.state === 'debouncing' ? 15_000 : 60_000);
|
|
6330
|
+
};
|
|
6331
|
+
const consumeSourceScan = async () => {
|
|
6332
|
+
try {
|
|
6333
|
+
await applyReadySourceScan();
|
|
6334
|
+
}
|
|
6335
|
+
catch {
|
|
6336
|
+
repositorySourceWatcher.invalidate();
|
|
6337
|
+
repositorySourceFailures += 1;
|
|
6338
|
+
repositorySourceState = 'blocked';
|
|
6339
|
+
nextRepositorySourceScanAt = performance.now() + 60_000;
|
|
6340
|
+
}
|
|
6341
|
+
};
|
|
6342
|
+
try {
|
|
6343
|
+
signal.throwIfAborted();
|
|
6344
|
+
const recoveredTaskTrajectories = await deferUnavailableRelayRetention(async () => finalizeRecoveredSignedTaskTrajectories(fabric, policy));
|
|
6345
|
+
if (recoveredTaskTrajectories.state === 'completed') {
|
|
6346
|
+
taskTrajectoriesRecovered += recoveredTaskTrajectories.value.length;
|
|
6347
|
+
}
|
|
6348
|
+
do {
|
|
6349
|
+
if (stopping)
|
|
6350
|
+
break;
|
|
6351
|
+
await consumeSourceScan();
|
|
6352
|
+
if (Date.now() >= nextPolicyRefreshAt) {
|
|
6353
|
+
try {
|
|
6354
|
+
await syncWorkspacePolicy(fabric, canonicalWorkspace, policy.revision, true);
|
|
6355
|
+
policy = await loadVerifiedWorkspacePolicy(policyPath, canonicalWorkspace.workspaceId);
|
|
6356
|
+
evidencePolicyFresh = true;
|
|
6357
|
+
}
|
|
6358
|
+
catch {
|
|
6359
|
+
evidencePolicyFresh = false;
|
|
6360
|
+
}
|
|
6361
|
+
nextPolicyRefreshAt = Date.now() + 60_000;
|
|
6362
|
+
}
|
|
6363
|
+
if (!sourceScanFlight && canonicalWorkspace.repositoryBindingId && canonicalWorkspace.repositoryAgentId) {
|
|
6364
|
+
try {
|
|
6365
|
+
const current = await pollRepositoryCandidate({ transport: fabric,
|
|
6366
|
+
outboxRoot: resolve(dharmaHome(), 'relay', 'repository-candidates'),
|
|
6367
|
+
scope: { organizationId: canonicalWorkspace.organizationId, workspaceId: canonicalWorkspace.workspaceId,
|
|
6368
|
+
repositoryBindingId: canonicalWorkspace.repositoryBindingId,
|
|
6369
|
+
repositoryAgentId: canonicalWorkspace.repositoryAgentId } });
|
|
6370
|
+
if (current && canonicalWorkspace.repositoryPackage) {
|
|
6371
|
+
if (current.candidateId !== canonicalWorkspace.repositoryPackage.candidateId
|
|
6372
|
+
|| current.operationId !== canonicalWorkspace.repositoryPackage.operationId) {
|
|
6373
|
+
throw new Error('Repository candidate status changed identity while polling.');
|
|
6180
6374
|
}
|
|
6181
|
-
|
|
6182
|
-
|
|
6375
|
+
const baseline = advanceRepositorySourceBaseline(canonicalWorkspace.repositoryPackage, current);
|
|
6376
|
+
canonicalWorkspace = { ...canonicalWorkspace, repositoryPackage: { ...canonicalWorkspace.repositoryPackage,
|
|
6377
|
+
state: current.state, candidateId: current.candidateId, operationId: current.operationId,
|
|
6378
|
+
snapshotHash: current.snapshotHash, releaseId: current.releaseId,
|
|
6379
|
+
...baseline,
|
|
6380
|
+
generation: current.state === 'published' ? Math.max(1, canonicalWorkspace.repositoryPackage.generation) : canonicalWorkspace.repositoryPackage.generation } };
|
|
6381
|
+
publishedLocalSnapshotHash = baseline.publishedLocalSnapshotHash;
|
|
6382
|
+
if (current.state === 'blocked')
|
|
6183
6383
|
repositorySourceWatcher.invalidate();
|
|
6184
|
-
|
|
6384
|
+
await saveWorkspaceRecord(canonicalWorkspace);
|
|
6185
6385
|
}
|
|
6186
|
-
|
|
6386
|
+
}
|
|
6387
|
+
catch {
|
|
6388
|
+
repositorySourceFailures += 1;
|
|
6187
6389
|
}
|
|
6188
6390
|
}
|
|
6189
|
-
|
|
6190
|
-
|
|
6191
|
-
|
|
6192
|
-
|
|
6193
|
-
|
|
6194
|
-
|
|
6195
|
-
|
|
6196
|
-
|
|
6197
|
-
|
|
6198
|
-
|
|
6199
|
-
|
|
6391
|
+
let evidenceRequestId;
|
|
6392
|
+
if (evidencePolicyFresh && !stopping) {
|
|
6393
|
+
const evidenceCycle = await deferUnavailableRelayRetention(async () => {
|
|
6394
|
+
const synced = await syncPendingRetentionCapsules(vault, fabric, policy, canonicalWorkspace.workspaceId);
|
|
6395
|
+
const evidence = await processEvidenceRequest(fabric, policy, canonicalWorkspace.workspaceId);
|
|
6396
|
+
return {
|
|
6397
|
+
synced,
|
|
6398
|
+
evidenceRequestId: typeof evidence.requestId === 'string' ? evidence.requestId : undefined,
|
|
6399
|
+
};
|
|
6400
|
+
});
|
|
6401
|
+
if (evidenceCycle.state === 'deferred') {
|
|
6402
|
+
evidencePolicyFresh = false;
|
|
6403
|
+
}
|
|
6404
|
+
else {
|
|
6405
|
+
trajectorySyncsCompleted += evidenceCycle.value.synced;
|
|
6406
|
+
evidenceRequestId = evidenceCycle.value.evidenceRequestId;
|
|
6407
|
+
if (evidenceRequestId)
|
|
6408
|
+
evidenceResponsesCompleted += 1;
|
|
6409
|
+
}
|
|
6200
6410
|
}
|
|
6201
|
-
|
|
6202
|
-
|
|
6203
|
-
|
|
6204
|
-
|
|
6205
|
-
|
|
6411
|
+
const result = await serialized(async () => {
|
|
6412
|
+
if (stopping)
|
|
6413
|
+
return { taskId: null };
|
|
6414
|
+
return executeOneTask(fabric, leaseSeconds);
|
|
6415
|
+
});
|
|
6416
|
+
if (!stopping && Date.now() - lastPollReceiptAt >= 30_000) {
|
|
6417
|
+
const observation = {
|
|
6418
|
+
at: new Date().toISOString(), workspaceId: canonicalWorkspace.workspaceId, version: VERSION, pid: process.pid,
|
|
6419
|
+
};
|
|
6420
|
+
await writeJsonAtomic(resolve(dharmaHome(), 'relay', 'repositories', canonicalWorkspace.workspaceId, 'last-successful-poll.json'), observation);
|
|
6421
|
+
const binding = await readFile(resolve(dharmaHome(), 'relay', 'supervisor-workspace.json'), 'utf8')
|
|
6422
|
+
.then(value => JSON.parse(value)).catch(() => null);
|
|
6423
|
+
if (!binding || binding.workspaceId === canonicalWorkspace.workspaceId) {
|
|
6424
|
+
await writeJsonAtomic(resolve(dharmaHome(), 'relay', 'last-successful-poll.json'), observation);
|
|
6425
|
+
}
|
|
6426
|
+
lastPollReceiptAt = Date.now();
|
|
6206
6427
|
}
|
|
6207
|
-
|
|
6208
|
-
|
|
6209
|
-
|
|
6428
|
+
if (result.taskId)
|
|
6429
|
+
tasksCompleted += 1;
|
|
6430
|
+
if (typeof result.failureCategory === 'string')
|
|
6431
|
+
lastProviderFailureCategory = result.failureCategory;
|
|
6432
|
+
if (!stopping && !result.taskId && performance.now() >= nextSkillActivationAt) {
|
|
6433
|
+
for (const adapter of providerAdapters) {
|
|
6210
6434
|
try {
|
|
6211
|
-
|
|
6212
|
-
|
|
6213
|
-
|
|
6435
|
+
const capability = await adapter.capability();
|
|
6436
|
+
if (capability.skillInstall === 'unavailable' || !isLocalProviderId(adapter.providerId))
|
|
6437
|
+
continue;
|
|
6438
|
+
const provider = adapter.providerId;
|
|
6439
|
+
await withWorkspaceSkillActivationLock(canonicalWorkspace.workspaceId, provider, async () => {
|
|
6440
|
+
await withSkillPreparationTransaction({ home: dharmaHome(), workspaceId: canonicalWorkspace.workspaceId,
|
|
6441
|
+
provider, assertCurrent: () => { if (stopping)
|
|
6442
|
+
throw new Error('Relay is stopping.'); } }, async () => {
|
|
6443
|
+
const cached = await takeCachedSkillUpdate({ workspaceId: canonicalWorkspace.workspaceId,
|
|
6444
|
+
provider, policyPath, fabric });
|
|
6445
|
+
const prepared = cached || await prepareSkillUpdate({ workspaceId: canonicalWorkspace.workspaceId,
|
|
6446
|
+
provider, policyPath, fabric, automatic: true });
|
|
6447
|
+
if (!prepared)
|
|
6448
|
+
return;
|
|
6449
|
+
if (!cached)
|
|
6450
|
+
skillPreparationsCompleted += 1;
|
|
6451
|
+
await activatePreparedSkillUpdate({ prepared, workspaceId: canonicalWorkspace.workspaceId,
|
|
6452
|
+
provider, policyPath, flags: new Map() });
|
|
6453
|
+
skillActivationsCompleted += 1;
|
|
6454
|
+
});
|
|
6455
|
+
});
|
|
6214
6456
|
}
|
|
6215
6457
|
catch {
|
|
6216
|
-
|
|
6458
|
+
skillActivationFailures += 1;
|
|
6459
|
+
skillActivationFailuresByProvider[adapter.providerId] = (skillActivationFailuresByProvider[adapter.providerId] || 0) + 1;
|
|
6217
6460
|
}
|
|
6218
|
-
nextPolicyRefreshAt = Date.now() + 60_000;
|
|
6219
6461
|
}
|
|
6220
|
-
|
|
6462
|
+
nextSkillActivationAt = performance.now() + 60_000;
|
|
6463
|
+
}
|
|
6464
|
+
if (!stopping && !sourceScanFlight && performance.now() >= nextRepositorySourceScanAt) {
|
|
6465
|
+
const sourceWorkspace = canonicalWorkspace;
|
|
6466
|
+
const sourceBaselineHash = publishedLocalSnapshotHash;
|
|
6467
|
+
sourceScanFlight = (async () => {
|
|
6221
6468
|
try {
|
|
6222
|
-
|
|
6223
|
-
|
|
6224
|
-
scope: { organizationId: canonicalWorkspace.organizationId, workspaceId: canonicalWorkspace.workspaceId,
|
|
6225
|
-
repositoryBindingId: canonicalWorkspace.repositoryBindingId,
|
|
6226
|
-
repositoryAgentId: canonicalWorkspace.repositoryAgentId } });
|
|
6227
|
-
if (current && canonicalWorkspace.repositoryPackage) {
|
|
6228
|
-
if (current.candidateId !== canonicalWorkspace.repositoryPackage.candidateId
|
|
6229
|
-
|| current.operationId !== canonicalWorkspace.repositoryPackage.operationId) {
|
|
6230
|
-
throw new Error('Repository candidate status changed identity while polling.');
|
|
6231
|
-
}
|
|
6232
|
-
const baseline = advanceRepositorySourceBaseline(canonicalWorkspace.repositoryPackage, current);
|
|
6233
|
-
canonicalWorkspace = { ...canonicalWorkspace, repositoryPackage: { ...canonicalWorkspace.repositoryPackage,
|
|
6234
|
-
state: current.state, candidateId: current.candidateId, operationId: current.operationId,
|
|
6235
|
-
snapshotHash: current.snapshotHash, releaseId: current.releaseId,
|
|
6236
|
-
...baseline,
|
|
6237
|
-
generation: current.state === 'published' ? Math.max(1, canonicalWorkspace.repositoryPackage.generation) : canonicalWorkspace.repositoryPackage.generation } };
|
|
6238
|
-
publishedLocalSnapshotHash = baseline.publishedLocalSnapshotHash;
|
|
6239
|
-
if (current.state === 'blocked')
|
|
6240
|
-
repositorySourceWatcher.invalidate();
|
|
6241
|
-
await saveWorkspaceRecord(canonicalWorkspace);
|
|
6469
|
+
if (!repositorySourceBaselineAvailable) {
|
|
6470
|
+
throw new Error('Repository source baseline is unavailable; refusing an unanchored update.');
|
|
6242
6471
|
}
|
|
6243
|
-
|
|
6244
|
-
|
|
6245
|
-
repositorySourceFailures += 1;
|
|
6246
|
-
}
|
|
6247
|
-
}
|
|
6248
|
-
let evidenceRequestId;
|
|
6249
|
-
if (evidencePolicyFresh) {
|
|
6250
|
-
const evidenceCycle = await deferUnavailableRelayRetention(async () => {
|
|
6251
|
-
const synced = await syncPendingRetentionCapsules(vault, fabric, policy, canonicalWorkspace.workspaceId);
|
|
6252
|
-
const evidence = await processEvidenceRequest(fabric, policy, canonicalWorkspace.workspaceId);
|
|
6253
|
-
return {
|
|
6254
|
-
synced,
|
|
6255
|
-
evidenceRequestId: typeof evidence.requestId === 'string' ? evidence.requestId : undefined,
|
|
6256
|
-
};
|
|
6257
|
-
});
|
|
6258
|
-
if (evidenceCycle.state === 'deferred') {
|
|
6259
|
-
evidencePolicyFresh = false;
|
|
6260
|
-
}
|
|
6261
|
-
else {
|
|
6262
|
-
trajectorySyncsCompleted += evidenceCycle.value.synced;
|
|
6263
|
-
evidenceRequestId = evidenceCycle.value.evidenceRequestId;
|
|
6264
|
-
if (evidenceRequestId)
|
|
6265
|
-
evidenceResponsesCompleted += 1;
|
|
6266
|
-
}
|
|
6267
|
-
}
|
|
6268
|
-
const result = await executeOneTask(fabric, leaseSeconds);
|
|
6269
|
-
if (Date.now() - lastPollReceiptAt >= 30_000) {
|
|
6270
|
-
await writeJsonAtomic(resolve(dharmaHome(), 'relay', 'last-successful-poll.json'), {
|
|
6271
|
-
at: new Date().toISOString(), workspaceId: canonicalWorkspace.workspaceId, version: VERSION, pid: process.pid,
|
|
6272
|
-
});
|
|
6273
|
-
lastPollReceiptAt = Date.now();
|
|
6274
|
-
}
|
|
6275
|
-
if (result.taskId)
|
|
6276
|
-
tasksCompleted += 1;
|
|
6277
|
-
if (typeof result.failureCategory === 'string')
|
|
6278
|
-
lastProviderFailureCategory = result.failureCategory;
|
|
6279
|
-
if (!result.taskId && performance.now() >= nextSkillActivationAt) {
|
|
6280
|
-
for (const adapter of providerAdapters) {
|
|
6281
|
-
try {
|
|
6282
|
-
const capability = await adapter.capability();
|
|
6283
|
-
if (capability.skillInstall === 'unavailable' || !isLocalProviderId(adapter.providerId))
|
|
6284
|
-
continue;
|
|
6285
|
-
const provider = adapter.providerId;
|
|
6286
|
-
await withWorkspaceSkillActivationLock(canonicalWorkspace.workspaceId, provider, async () => {
|
|
6287
|
-
await withSkillPreparationTransaction({ home: dharmaHome(), workspaceId: canonicalWorkspace.workspaceId,
|
|
6288
|
-
provider, assertCurrent: () => { if (stopping)
|
|
6289
|
-
throw new Error('Relay is stopping.'); } }, async () => {
|
|
6290
|
-
const cached = await takeCachedSkillUpdate({ workspaceId: canonicalWorkspace.workspaceId,
|
|
6291
|
-
provider, policyPath, fabric });
|
|
6292
|
-
const prepared = cached || await prepareSkillUpdate({ workspaceId: canonicalWorkspace.workspaceId,
|
|
6293
|
-
provider, policyPath, fabric, automatic: true });
|
|
6294
|
-
if (!prepared)
|
|
6295
|
-
return;
|
|
6296
|
-
if (!cached)
|
|
6297
|
-
skillPreparationsCompleted += 1;
|
|
6298
|
-
await activatePreparedSkillUpdate({ prepared, workspaceId: canonicalWorkspace.workspaceId,
|
|
6299
|
-
provider, policyPath, flags: new Map() });
|
|
6300
|
-
skillActivationsCompleted += 1;
|
|
6301
|
-
});
|
|
6302
|
-
});
|
|
6472
|
+
if (!sourceWorkspace.repositoryBindingId || !sourceWorkspace.repositoryAgentId) {
|
|
6473
|
+
throw new Error('Repository source synchronization requires a complete repository binding.');
|
|
6303
6474
|
}
|
|
6304
|
-
|
|
6305
|
-
|
|
6306
|
-
|
|
6475
|
+
const installedKnowledge = await installedRepositoryKnowledge(sourceWorkspace);
|
|
6476
|
+
if (!installedKnowledge && (sourceWorkspace.repositoryPackage?.generation ?? 0) > 0) {
|
|
6477
|
+
sourceScanReady = { state: 'awaiting_signed_baseline' };
|
|
6307
6478
|
}
|
|
6308
|
-
|
|
6309
|
-
|
|
6310
|
-
|
|
6311
|
-
|
|
6312
|
-
|
|
6313
|
-
const sourceBaselineHash = publishedLocalSnapshotHash;
|
|
6314
|
-
sourceScanFlight = (async () => {
|
|
6315
|
-
try {
|
|
6316
|
-
if (!repositorySourceBaselineAvailable) {
|
|
6317
|
-
throw new Error('Repository source baseline is unavailable; refusing an unanchored update.');
|
|
6318
|
-
}
|
|
6319
|
-
if (!sourceWorkspace.repositoryBindingId || !sourceWorkspace.repositoryAgentId) {
|
|
6320
|
-
throw new Error('Repository source synchronization requires a complete repository binding.');
|
|
6479
|
+
else {
|
|
6480
|
+
if (sourceWorkspace.repositoryPackage?.state === 'blocked' && sourceWorkspace.repositoryPackage.snapshotHash) {
|
|
6481
|
+
const blocked = await readRepositorySourceBaselineSnapshot(sourceWorkspace.path, sourceWorkspace.repositoryPackage.snapshotHash);
|
|
6482
|
+
if (blockedRepositorySourceRetry.consider(sourceWorkspace.repositoryPackage.candidateId ?? '', blocked.manifest.knowledge, installedKnowledge))
|
|
6483
|
+
repositorySourceWatcher.invalidate();
|
|
6321
6484
|
}
|
|
6322
|
-
const
|
|
6323
|
-
|
|
6324
|
-
|
|
6325
|
-
|
|
6326
|
-
|
|
6327
|
-
|
|
6328
|
-
|
|
6329
|
-
|
|
6330
|
-
|
|
6331
|
-
|
|
6332
|
-
|
|
6333
|
-
|
|
6334
|
-
|
|
6335
|
-
|
|
6336
|
-
|
|
6337
|
-
|
|
6338
|
-
? readRepositoryPackageSnapshot(sourceWorkspace.path, sourceBaselineHash) : Promise.resolve(null),
|
|
6339
|
-
submitCandidate: (snapshot, expectedLatestSourceFingerprint) => synchronizeRepositoryCandidate({ transport: fabric,
|
|
6340
|
-
outboxRoot: resolve(dharmaHome(), 'relay', 'repository-candidates'),
|
|
6341
|
-
scope: { organizationId: sourceWorkspace.organizationId,
|
|
6342
|
-
workspaceId: sourceWorkspace.workspaceId,
|
|
6343
|
-
repositoryBindingId: sourceWorkspace.repositoryBindingId,
|
|
6344
|
-
repositoryAgentId: sourceWorkspace.repositoryAgentId },
|
|
6345
|
-
snapshot, initialRepository: false, expectedLatestSourceFingerprint }),
|
|
6346
|
-
});
|
|
6347
|
-
sourceScanReady = { cycle: sourceCycle };
|
|
6348
|
-
}
|
|
6349
|
-
}
|
|
6350
|
-
catch (error) {
|
|
6351
|
-
sourceScanReady = { error };
|
|
6485
|
+
const sourceCycle = await scanRepositorySourceChanges({
|
|
6486
|
+
workspace: sourceWorkspace.path, organizationId: sourceWorkspace.organizationId,
|
|
6487
|
+
workspaceId: sourceWorkspace.workspaceId, repositoryBindingId: sourceWorkspace.repositoryBindingId,
|
|
6488
|
+
repositoryAgentId: sourceWorkspace.repositoryAgentId, transport: fabric, watcher: repositorySourceWatcher,
|
|
6489
|
+
loadRetainedKnowledge: () => installedRepositoryKnowledge(sourceWorkspace),
|
|
6490
|
+
loadPublishedLocalBaseline: () => sourceBaselineHash
|
|
6491
|
+
? readRepositoryPackageSnapshot(sourceWorkspace.path, sourceBaselineHash) : Promise.resolve(null),
|
|
6492
|
+
submitCandidate: (snapshot, expectedLatestSourceFingerprint) => synchronizeRepositoryCandidate({ transport: fabric,
|
|
6493
|
+
outboxRoot: resolve(dharmaHome(), 'relay', 'repository-candidates'),
|
|
6494
|
+
scope: { organizationId: sourceWorkspace.organizationId,
|
|
6495
|
+
workspaceId: sourceWorkspace.workspaceId,
|
|
6496
|
+
repositoryBindingId: sourceWorkspace.repositoryBindingId,
|
|
6497
|
+
repositoryAgentId: sourceWorkspace.repositoryAgentId },
|
|
6498
|
+
snapshot, initialRepository: false, expectedLatestSourceFingerprint }),
|
|
6499
|
+
});
|
|
6500
|
+
sourceScanReady = { cycle: sourceCycle };
|
|
6352
6501
|
}
|
|
6353
|
-
}
|
|
6354
|
-
|
|
6355
|
-
|
|
6356
|
-
|
|
6357
|
-
|
|
6358
|
-
|
|
6359
|
-
|
|
6360
|
-
|
|
6361
|
-
|
|
6362
|
-
|
|
6363
|
-
|
|
6364
|
-
await consumeSourceScan();
|
|
6365
|
-
await skillPreparationPump.stop();
|
|
6366
|
-
vault.close();
|
|
6367
|
-
process.removeListener('SIGINT', stop);
|
|
6368
|
-
process.removeListener('SIGTERM', stop);
|
|
6369
|
-
}
|
|
6370
|
-
return {
|
|
6371
|
-
ok: true, stopped: true, tasksCompleted, taskTrajectoriesRecovered,
|
|
6372
|
-
lastProviderFailureCategory,
|
|
6373
|
-
evidenceResponsesCompleted, trajectorySyncsCompleted,
|
|
6374
|
-
repositorySourceCandidates, repositorySourceFailures, repositorySourceState,
|
|
6375
|
-
skillPreparationsCompleted, skillPreparationFailures, skillActivationsCompleted, skillActivationFailures,
|
|
6376
|
-
skillActivationFailuresByProvider,
|
|
6377
|
-
sharedRepositoryReady: await repositorySharedReady(canonicalWorkspace),
|
|
6378
|
-
};
|
|
6502
|
+
}
|
|
6503
|
+
catch (error) {
|
|
6504
|
+
sourceScanReady = { error };
|
|
6505
|
+
}
|
|
6506
|
+
})();
|
|
6507
|
+
}
|
|
6508
|
+
if (flags.has('once'))
|
|
6509
|
+
break;
|
|
6510
|
+
if (!result.taskId && !evidenceRequestId)
|
|
6511
|
+
await waitForRelayRefresh(pollMs, signal);
|
|
6512
|
+
} while (!stopping);
|
|
6379
6513
|
}
|
|
6380
6514
|
finally {
|
|
6381
|
-
|
|
6515
|
+
if (sourceScanFlight)
|
|
6516
|
+
await sourceScanFlight;
|
|
6517
|
+
await consumeSourceScan();
|
|
6518
|
+
await skillPreparationPump.stop();
|
|
6519
|
+
vault.close();
|
|
6520
|
+
signal.removeEventListener('abort', stop);
|
|
6382
6521
|
}
|
|
6522
|
+
return {
|
|
6523
|
+
ok: true, stopped: true, tasksCompleted, taskTrajectoriesRecovered,
|
|
6524
|
+
lastProviderFailureCategory,
|
|
6525
|
+
evidenceResponsesCompleted, trajectorySyncsCompleted,
|
|
6526
|
+
repositorySourceCandidates, repositorySourceFailures, repositorySourceState,
|
|
6527
|
+
skillPreparationsCompleted, skillPreparationFailures, skillActivationsCompleted, skillActivationFailures,
|
|
6528
|
+
skillActivationFailuresByProvider,
|
|
6529
|
+
sharedRepositoryReady: await repositorySharedReady(canonicalWorkspace),
|
|
6530
|
+
};
|
|
6383
6531
|
}
|
|
6384
6532
|
export async function run(argv) {
|
|
6385
6533
|
const { positional, flags, repeated } = parseCliOptions(argv);
|
|
@@ -6571,6 +6719,8 @@ export async function run(argv) {
|
|
|
6571
6719
|
}
|
|
6572
6720
|
if (command === 'repositories' && subcommand === 'list')
|
|
6573
6721
|
return repositoriesList(flags);
|
|
6722
|
+
if (command === 'repositories' && subcommand === 'recover-installer')
|
|
6723
|
+
return repositoryInstallerRecoveryCommand(flags);
|
|
6574
6724
|
if (command === 'repositories' && subcommand === 'snapshot') {
|
|
6575
6725
|
return repositorySnapshotCommand(flags, repeated.get('approved-output') || []);
|
|
6576
6726
|
}
|
|
@@ -6624,6 +6774,25 @@ export async function run(argv) {
|
|
|
6624
6774
|
try {
|
|
6625
6775
|
const config = JSON.parse(await readFile(configPath(), 'utf8'));
|
|
6626
6776
|
const status = { version: VERSION, enrolled: true, relay, supervisor, autostart, reconnect };
|
|
6777
|
+
if (flags.has('verbose') || flags.has('diagnostic')) {
|
|
6778
|
+
const relayPid = await readFile(resolve(dharmaHome(), 'relay', 'relay.pid'), 'utf8')
|
|
6779
|
+
.then(value => Number(value.trim())).catch(() => 0);
|
|
6780
|
+
try {
|
|
6781
|
+
const registered = await selectRepositoryRelayRegistrations(await registry(), config, loadOrganizationPolicy);
|
|
6782
|
+
const observations = [];
|
|
6783
|
+
for (const row of registered) {
|
|
6784
|
+
const observation = await readFile(resolve(dharmaHome(), 'relay', 'repositories', row.workspaceId, 'last-successful-poll.json'), 'utf8').then(value => JSON.parse(value)).catch(() => null);
|
|
6785
|
+
observations.push({ workspaceId: row.workspaceId,
|
|
6786
|
+
state: relay === 'running' && repositoryRelayObservationReady({ observation,
|
|
6787
|
+
workspaceId: row.workspaceId, version: VERSION, pid: relayPid }) ? 'acknowledged_recently' : 'pending',
|
|
6788
|
+
lastSuccessfulPollAt: typeof observation?.at === 'string' ? observation.at : null });
|
|
6789
|
+
}
|
|
6790
|
+
status.repositoryRelays = { state: 'observed', repositories: observations };
|
|
6791
|
+
}
|
|
6792
|
+
catch {
|
|
6793
|
+
status.repositoryRelays = { state: 'unavailable', repositories: [] };
|
|
6794
|
+
}
|
|
6795
|
+
}
|
|
6627
6796
|
if (flags.has('verbose') || flags.has('diagnostic')) {
|
|
6628
6797
|
Object.assign(status, { home: dharmaHome(), organizationId: config.organizationId, deviceId: config.deviceId });
|
|
6629
6798
|
}
|