@dharma-ai-labs/agent-fabric 0.2.117 → 0.2.119

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.
Files changed (51) hide show
  1. package/AGENT_FABRIC_ONBOARDING.md +3 -0
  2. package/dist/demoPackage.d.ts +1 -1
  3. package/dist/demoPeer.test.js +8 -11
  4. package/dist/demoPeer.test.js.map +1 -1
  5. package/dist/demoTransportFetch.d.ts.map +1 -1
  6. package/dist/demoTransportFetch.js +3 -1
  7. package/dist/demoTransportFetch.js.map +1 -1
  8. package/dist/demoTransportFetch.test.js +62 -0
  9. package/dist/demoTransportFetch.test.js.map +1 -1
  10. package/dist/evidencePreviewDisclosureCaller.test.d.ts +2 -0
  11. package/dist/evidencePreviewDisclosureCaller.test.d.ts.map +1 -0
  12. package/dist/evidencePreviewDisclosureCaller.test.js +56 -0
  13. package/dist/evidencePreviewDisclosureCaller.test.js.map +1 -0
  14. package/dist/index.d.ts.map +1 -1
  15. package/dist/index.js +180 -11
  16. package/dist/index.js.map +1 -1
  17. package/dist/namedSessionEvidence.d.ts.map +1 -1
  18. package/dist/namedSessionEvidence.js +19 -2
  19. package/dist/namedSessionEvidence.js.map +1 -1
  20. package/dist/namedSessionEvidence.test.js +47 -1
  21. package/dist/namedSessionEvidence.test.js.map +1 -1
  22. package/dist/namedSessionService.d.ts.map +1 -1
  23. package/dist/namedSessionService.js +4 -1
  24. package/dist/namedSessionService.js.map +1 -1
  25. package/dist/namedSessionService.test.js +14 -2
  26. package/dist/namedSessionService.test.js.map +1 -1
  27. package/dist/relayAutostart.d.ts +8 -1
  28. package/dist/relayAutostart.d.ts.map +1 -1
  29. package/dist/relayAutostart.js +27 -0
  30. package/dist/relayAutostart.js.map +1 -1
  31. package/dist/relayRuntimeUpgrade.d.ts +58 -0
  32. package/dist/relayRuntimeUpgrade.d.ts.map +1 -0
  33. package/dist/relayRuntimeUpgrade.js +193 -0
  34. package/dist/relayRuntimeUpgrade.js.map +1 -0
  35. package/dist/relayRuntimeUpgrade.test.d.ts +2 -0
  36. package/dist/relayRuntimeUpgrade.test.d.ts.map +1 -0
  37. package/dist/relayRuntimeUpgrade.test.js +162 -0
  38. package/dist/relayRuntimeUpgrade.test.js.map +1 -0
  39. package/dist/relayRuntimeUpgradeCaller.test.d.ts +2 -0
  40. package/dist/relayRuntimeUpgradeCaller.test.d.ts.map +1 -0
  41. package/dist/relayRuntimeUpgradeCaller.test.js +116 -0
  42. package/dist/relayRuntimeUpgradeCaller.test.js.map +1 -0
  43. package/dist/repositoryOnboardingCaller.test.js +174 -1
  44. package/dist/repositoryOnboardingCaller.test.js.map +1 -1
  45. package/dist/schemas/codex-local-work-capture-v2.schema.json +64 -0
  46. package/dist/schemas/local-relay-upgrade.schema.json +32 -0
  47. package/dist/usage.d.ts +1 -1
  48. package/dist/usage.d.ts.map +1 -1
  49. package/dist/usage.js +1 -1
  50. package/dist/usage.js.map +1 -1
  51. package/package.json +7 -7
package/dist/index.js CHANGED
@@ -17,7 +17,8 @@ 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 { disableRelayAutostart, enableRelayAutostart, relayAutostartStatus, startRelayAutostart } from './relayAutostart.js';
20
+ import { disableRelayAutostart, enableRelayAutostart, inspectOwnedRelayAutostart, relayAutostartStatus, startRelayAutostart, stopRelayAutostart } from './relayAutostart.js';
21
+ import { relayRuntimeObservationReady, upgradeRelayRuntime } from './relayRuntimeUpgrade.js';
21
22
  import { assertStudyExactPoll, studyTaskIdFromFlags } from './studyExactTaskSelector.js';
22
23
  import { initializeRepositoryKnowledge, readRepositoryKnowledgeSource } from './repositoryKnowledge.js';
23
24
  import { inventoryRepositoryPackage, readRepositoryPackageSnapshot, readRepositorySourceBaselineSnapshot, writeRepositoryPackageSnapshot } from './repositoryPackage.js';
@@ -55,7 +56,7 @@ import { openCodexAppServerTransport } from '@dharma-ai-labs/agent-fabric-provid
55
56
  import { createNamedSessionTrust, isNamedSessionOwnerReceipt, renewNamedSessionLifetime } from './namedSessionTrust.js';
56
57
  import { startNamedCodexThread } from './namedCodexThread.js';
57
58
  export { openCooperativeInboxSession } from './cooperativeInboxSession.js';
58
- const VERSION = '0.2.117';
59
+ const VERSION = '0.2.119';
59
60
  const USAGE = CLI_USAGE;
60
61
  const execFileAsync = promisify(execFile);
61
62
  const LOCAL_PROVIDER_IDS = ['codex', 'claude', 'agy', 'hermes'];
@@ -1525,6 +1526,103 @@ async function installStableRepositoryLauncher(workspace) {
1525
1526
  await writeFile(cmdPath, contents.windows, { mode: 0o600 });
1526
1527
  return { shell: '.dharma/bin/dharma', windows: '.dharma/bin/dharma.cmd' };
1527
1528
  }
1529
+ async function relayUpgrade(flags) {
1530
+ const config = await readDeviceConfig();
1531
+ if (!config)
1532
+ throw new Error('relay_upgrade_enrollment_required');
1533
+ if (flags.has('dry-run') && flags.has('apply'))
1534
+ throw new Error('relay_upgrade_flags_conflict');
1535
+ const workspace = await realpath(String(flags.get('workspace') || '.'));
1536
+ const selected = selectDeviceWorkspace(await registry(), {
1537
+ organizationId: config.organizationId, deviceId: config.deviceId, path: workspace,
1538
+ });
1539
+ if (!selected)
1540
+ throw new Error('relay_upgrade_workspace_unregistered');
1541
+ const home = dharmaHome();
1542
+ const startup = await inspectOwnedRelayAutostart({ home });
1543
+ const expectedLauncher = resolve(workspace, '.dharma', 'bin', process.platform === 'win32' ? 'dharma.cmd' : 'dharma');
1544
+ if (startup.workspace !== workspace || startup.launcher !== expectedLauncher
1545
+ || startup.policy !== resolve(workspace, '.dharma', 'approved-policy.json')) {
1546
+ throw new Error('relay_upgrade_workspace_conflict');
1547
+ }
1548
+ const assertStopped = async () => {
1549
+ if (await relaySupervisorProcessState(home) !== 'stopped' || await relayProcessState(home) !== 'stopped') {
1550
+ throw new Error('relay_upgrade_runtime_busy: finish current work, then stop the owned relay before upgrading.');
1551
+ }
1552
+ const entries = await readdir(resolve(home, 'sessions'), { withFileTypes: true }).catch(error => {
1553
+ if (error.code === 'ENOENT')
1554
+ return [];
1555
+ throw error;
1556
+ });
1557
+ for (const entry of entries) {
1558
+ if (!entry.isDirectory() || !/^[a-z][a-z0-9-]{0,47}$/.test(entry.name))
1559
+ continue;
1560
+ if (await pidProcessState(resolve(home, 'sessions', entry.name, 'service.lock')) !== 'stopped') {
1561
+ throw new Error('relay_upgrade_session_busy: finish and stop the named session before upgrading.');
1562
+ }
1563
+ }
1564
+ };
1565
+ return withRelayStartupMutation(async () => {
1566
+ const receipt = await upgradeRelayRuntime({ home, workspace, version: VERSION,
1567
+ organizationId: config.organizationId, deviceId: config.deviceId, workspaceId: selected.workspaceId,
1568
+ dryRun: !flags.has('apply'), rollback: flags.has('rollback') }, {
1569
+ assertStopped, inspectStartup: async () => {
1570
+ const current = await inspectOwnedRelayAutostart({ home });
1571
+ if (current.launcher !== expectedLauncher || current.policy !== startup.policy
1572
+ || current.workspace !== workspace)
1573
+ throw new Error('relay_upgrade_workspace_conflict');
1574
+ return current;
1575
+ },
1576
+ launcherContents: version => stableRepositoryLauncherContents(version, { platform: process.platform, nodeDirectory: dirname(process.execPath) }),
1577
+ legacyLauncherContents: version => stableRepositoryLauncherContents(version),
1578
+ configureStartup: version => enableRelayAutostart({ home, workspace,
1579
+ launcher: startup.launcher, policy: startup.policy, version }),
1580
+ start: () => startRelayAutostart({ home }),
1581
+ stop: async () => {
1582
+ const activationLocks = resolve(home, 'registry', 'skill-activation-locks');
1583
+ const locks = await readdir(activationLocks).catch(error => {
1584
+ if (error.code === 'ENOENT')
1585
+ return [];
1586
+ throw error;
1587
+ });
1588
+ for (const lock of locks) {
1589
+ if (lock.endsWith('.lock') && await pidProcessState(resolve(activationLocks, lock)) !== 'stopped') {
1590
+ throw new Error('relay_upgrade_work_in_progress: recovery must wait for a safe task boundary.');
1591
+ }
1592
+ }
1593
+ const binding = await readFile(resolve(home, 'relay', 'supervisor-workspace.json'), 'utf8')
1594
+ .then(value => JSON.parse(value)).catch(() => null);
1595
+ if (await relaySupervisorProcessState(home) !== 'stopped'
1596
+ && binding?.workspaceId !== selected.workspaceId)
1597
+ throw new Error('relay_upgrade_process_unverified');
1598
+ await stopRelayAutostart({ home });
1599
+ const stopped = await relayStop();
1600
+ if (stopped.ok !== true)
1601
+ throw new Error('relay_upgrade_stop_unconfirmed');
1602
+ },
1603
+ verify: async (version, since) => {
1604
+ for (let attempt = 0; attempt < 90; attempt++) {
1605
+ const read = (name) => readFile(resolve(home, 'relay', name), 'utf8').catch(() => 'null');
1606
+ const supervisorPid = Number((await read('supervisor.pid')).trim());
1607
+ const relayPid = Number((await read('relay.pid')).trim());
1608
+ const supervisor = JSON.parse(await read('supervisor-workspace.json'));
1609
+ const lastPoll = JSON.parse(await read('last-successful-poll.json'));
1610
+ if (await relaySupervisorProcessState(home) === 'running' && await relayProcessState(home) === 'running'
1611
+ && relayRuntimeObservationReady({ version, workspaceId: selected.workspaceId, since,
1612
+ supervisorPid, relayPid, supervisor, lastPoll, allowLegacyPoll: version !== VERSION }))
1613
+ return;
1614
+ if (attempt < 89)
1615
+ await new Promise(resolveWait => setTimeout(resolveWait, 1000));
1616
+ }
1617
+ throw new Error('relay_upgrade_receiver_unconfirmed');
1618
+ },
1619
+ });
1620
+ const validation = await validateContract(resolve(fileURLToPath(new URL('.', import.meta.url)), 'schemas'), 'https://schemas.dharma-ai.io/local-relay-upgrade/v1', receipt);
1621
+ if (!validation.ok)
1622
+ throw new Error('relay_upgrade_receipt_invalid');
1623
+ return receipt;
1624
+ });
1625
+ }
1528
1626
  export async function waitForRelayReadiness(options = {}) {
1529
1627
  const processState = options.processState || relayProcessState;
1530
1628
  const probe = options.probe || probeRelayConnection;
@@ -1969,6 +2067,33 @@ async function bootstrap(flags) {
1969
2067
  });
1970
2068
  scopes = redeemed.organizationApiTokenScopes;
1971
2069
  }
2070
+ let organizationApiTokenStored = false;
2071
+ let credentialFailure = null;
2072
+ try {
2073
+ organizationApiTokenStored = Boolean(await loadOrganizationApiToken({
2074
+ hqUrl, organizationId, installationId: config.installationId,
2075
+ }));
2076
+ if (!organizationApiTokenStored && !String(process.env.DHARMA_ORG_API_TOKEN || '').trim()) {
2077
+ credentialFailure = 'organization_api_credentials_required';
2078
+ }
2079
+ }
2080
+ catch {
2081
+ // Secure-store diagnostics can contain secrets. Emit only the failed stage.
2082
+ credentialFailure = 'organization_api_credential_store_unavailable';
2083
+ }
2084
+ if (credentialFailure) {
2085
+ return {
2086
+ ok: false,
2087
+ stage: 'organization_api_credentials',
2088
+ code: credentialFailure,
2089
+ message: 'The enrolled device is preserved, but its organization API credential is unavailable. Contact Dharma support for device-bound credential recovery; do not repeat enrollment or use another member\'s token.',
2090
+ sharedRepositoryReady: false,
2091
+ enrollment: {
2092
+ organizationId, deviceId: config.deviceId, status: 'approved', organizationApiTokenStored,
2093
+ scopes, recipientApproval, rebind,
2094
+ },
2095
+ };
2096
+ }
1972
2097
  const onboardFlags = new Map(flags);
1973
2098
  onboardFlags.delete('grant');
1974
2099
  onboardFlags.set('portal-url', hqUrl);
@@ -1987,7 +2112,7 @@ async function bootstrap(flags) {
1987
2112
  organizationId,
1988
2113
  deviceId: config.deviceId,
1989
2114
  status: 'approved',
1990
- organizationApiTokenStored: true,
2115
+ organizationApiTokenStored,
1991
2116
  scopes,
1992
2117
  recipientApproval,
1993
2118
  rebind,
@@ -2015,7 +2140,7 @@ async function bootstrap(flags) {
2015
2140
  organizationId,
2016
2141
  deviceId: config.deviceId,
2017
2142
  status: 'approved',
2018
- organizationApiTokenStored: true,
2143
+ organizationApiTokenStored,
2019
2144
  scopes,
2020
2145
  recipientApproval,
2021
2146
  rebind,
@@ -2106,7 +2231,7 @@ async function bootstrap(flags) {
2106
2231
  organizationId,
2107
2232
  deviceId: config.deviceId,
2108
2233
  status: 'approved',
2109
- organizationApiTokenStored: true,
2234
+ organizationApiTokenStored,
2110
2235
  scopes,
2111
2236
  recipientApproval,
2112
2237
  rebind,
@@ -2791,8 +2916,18 @@ async function evidencePreview(flags) {
2791
2916
  captureProvenance: captureProvenance(session.endedAt),
2792
2917
  });
2793
2918
  });
2919
+ const disclosureFailures = capsules.map((capsule) => {
2920
+ try {
2921
+ return containsDisallowedLocalPath(capsule) ? 'local_path_disclosure_forbidden' : null;
2922
+ }
2923
+ catch {
2924
+ return 'local_path_inspection_depth_exceeded';
2925
+ }
2926
+ }).filter((reason) => reason !== null);
2794
2927
  automaticDisclosure = {
2795
- ready: true,
2928
+ ready: disclosureFailures.length === 0,
2929
+ blockedCapsuleCount: disclosureFailures.length,
2930
+ reasonCodes: [...new Set(disclosureFailures)].sort(),
2796
2931
  disclosureClass: 'automatic_capsule',
2797
2932
  disclosureMode: capsules[0]?.automaticDisclosureMode ?? policy.evidence.automaticDisclosure?.mode ?? 'local_analysis',
2798
2933
  consentReceiptId: policy.evidence.automaticDisclosure?.consentReceiptId ?? null,
@@ -3481,13 +3616,14 @@ async function syncWorkspacePolicy(fabric, item, policyRevision, apply = true, p
3481
3616
  repositoryAgentId: item.repositoryAgentId,
3482
3617
  policyRevision, providers,
3483
3618
  });
3619
+ const canonical = reconcileWorkspaceRegistration(await registry(), item, response);
3484
3620
  const generated = await materializeWorkspacePolicy({
3485
- workspace: item.path,
3486
- organizationId: item.organizationId,
3621
+ workspace: canonical.path,
3622
+ organizationId: canonical.organizationId,
3487
3623
  revision: String(response.workspace?.policyRevision || policyRevision),
3488
3624
  serverPolicyAuthorization: response.organizationPolicyAuthorization,
3489
3625
  serverPublicKeyEd25519: (await readDeviceConfig())?.serverPublicKeyEd25519,
3490
- workspaceId: item.workspaceId,
3626
+ workspaceId: canonical.workspaceId,
3491
3627
  dryRun: !apply,
3492
3628
  });
3493
3629
  return {
@@ -3500,12 +3636,43 @@ async function syncWorkspacePolicy(fabric, item, policyRevision, apply = true, p
3500
3636
  },
3501
3637
  };
3502
3638
  }
3639
+ function reconcileWorkspaceRegistration(records, requested, response) {
3640
+ const workspace = response.workspace && typeof response.workspace === 'object' && !Array.isArray(response.workspace)
3641
+ ? response.workspace : null;
3642
+ if (response.ok !== true || response.organizationId !== requested.organizationId
3643
+ || !workspace || typeof workspace.id !== 'string' || !UUID_PATTERN.test(workspace.id)
3644
+ || workspace.status !== 'active') {
3645
+ throw new Error('fabric_workspace_registration_scope_mismatch');
3646
+ }
3647
+ if (workspace.id === requested.workspaceId)
3648
+ return requested;
3649
+ // The authenticated server upsert preserves the original ID for this device's
3650
+ // route. Local records are hints only; signed policy verification still follows.
3651
+ const candidates = records.filter(record => record.workspaceId === workspace.id);
3652
+ if (candidates.length > 1)
3653
+ throw new Error('fabric_workspace_registration_ambiguous');
3654
+ const canonical = candidates[0];
3655
+ if (canonical) {
3656
+ if (canonical.organizationId !== requested.organizationId || canonical.path !== requested.path
3657
+ || canonical.routeHash !== requested.routeHash || canonical.status !== 'active'
3658
+ || canonical.repositoryRemoteHash !== requested.repositoryRemoteHash) {
3659
+ throw new Error('fabric_workspace_registration_scope_mismatch');
3660
+ }
3661
+ return canonical;
3662
+ }
3663
+ // A restored credential store may lack the old registry row. Do not carry
3664
+ // workspace-specific endpoint/package state into the server-returned identity.
3665
+ return { ...requested, workspaceId: workspace.id, repositoryAgentId: null,
3666
+ repositoryBindingId: null, endpointId: null, repositoryPackage: undefined,
3667
+ repositoryRole: null, repositoryAgentKey: null, controlBranch: null };
3668
+ }
3503
3669
  async function registerWorkspaceBeforeRepositoryBind(fabric, item, policyRevision, providerIds = null) {
3504
3670
  // Repository connect is fenced by a server-side workspace, provider capability,
3505
3671
  // and signed device-session record. Register those facts before requesting the
3506
3672
  // permanent repository binding and control branch.
3507
3673
  const synchronized = await syncWorkspacePolicy(fabric, item, policyRevision, true, providerIds);
3508
- const registered = await bindRepositoryAgent(fabric, item);
3674
+ const canonical = reconcileWorkspaceRegistration(await registry(), item, synchronized);
3675
+ const registered = await bindRepositoryAgent(fabric, canonical);
3509
3676
  return { registered, synchronized };
3510
3677
  }
3511
3678
  async function readDeviceConfig() {
@@ -6101,7 +6268,7 @@ async function relayStart(flags) {
6101
6268
  const result = await executeOneTask(fabric, leaseSeconds);
6102
6269
  if (Date.now() - lastPollReceiptAt >= 30_000) {
6103
6270
  await writeJsonAtomic(resolve(dharmaHome(), 'relay', 'last-successful-poll.json'), {
6104
- at: new Date().toISOString(), workspaceId: canonicalWorkspace.workspaceId, version: VERSION,
6271
+ at: new Date().toISOString(), workspaceId: canonicalWorkspace.workspaceId, version: VERSION, pid: process.pid,
6105
6272
  });
6106
6273
  lastPollReceiptAt = Date.now();
6107
6274
  }
@@ -6490,6 +6657,8 @@ export async function run(argv) {
6490
6657
  return probeRelayConnection();
6491
6658
  if (command === 'relay' && subcommand === 'stop')
6492
6659
  return relayStop();
6660
+ if (command === 'relay' && subcommand === 'upgrade')
6661
+ return relayUpgrade(flags);
6493
6662
  if (command === 'relay' && subcommand === 'autostart') {
6494
6663
  if (positional[2] === 'status')
6495
6664
  return relayAutostartStatus({ home: dharmaHome() });