@dharma-ai-labs/agent-fabric 0.2.110 → 0.2.112

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 (124) hide show
  1. package/AGENT_FABRIC_ONBOARDING.md +13 -0
  2. package/dist/codexBoundSession.d.ts +30 -5
  3. package/dist/codexBoundSession.d.ts.map +1 -1
  4. package/dist/codexBoundSession.js +49 -2
  5. package/dist/codexBoundSession.js.map +1 -1
  6. package/dist/codexBoundSession.test.js +17 -0
  7. package/dist/codexBoundSession.test.js.map +1 -1
  8. package/dist/codexInboxSession.d.ts +50 -0
  9. package/dist/codexInboxSession.d.ts.map +1 -1
  10. package/dist/codexInboxSession.js +32 -3
  11. package/dist/codexInboxSession.js.map +1 -1
  12. package/dist/codexPeerTools.d.ts +62 -0
  13. package/dist/codexPeerTools.d.ts.map +1 -0
  14. package/dist/codexPeerTools.js +53 -0
  15. package/dist/codexPeerTools.js.map +1 -0
  16. package/dist/codexPeerTools.test.d.ts +2 -0
  17. package/dist/codexPeerTools.test.d.ts.map +1 -0
  18. package/dist/codexPeerTools.test.js +77 -0
  19. package/dist/codexPeerTools.test.js.map +1 -0
  20. package/dist/demoDeviceTransport.d.ts +33 -0
  21. package/dist/demoDeviceTransport.d.ts.map +1 -0
  22. package/dist/demoDeviceTransport.js +52 -0
  23. package/dist/demoDeviceTransport.js.map +1 -0
  24. package/dist/demoDeviceTransport.test.d.ts +2 -0
  25. package/dist/demoDeviceTransport.test.d.ts.map +1 -0
  26. package/dist/demoDeviceTransport.test.js +178 -0
  27. package/dist/demoDeviceTransport.test.js.map +1 -0
  28. package/dist/demoTransportCommand.test.d.ts +2 -0
  29. package/dist/demoTransportCommand.test.d.ts.map +1 -0
  30. package/dist/demoTransportCommand.test.js +28 -0
  31. package/dist/demoTransportCommand.test.js.map +1 -0
  32. package/dist/demoTransportContinuity.d.ts +39 -0
  33. package/dist/demoTransportContinuity.d.ts.map +1 -0
  34. package/dist/demoTransportContinuity.js +82 -0
  35. package/dist/demoTransportContinuity.js.map +1 -0
  36. package/dist/demoTransportContinuity.test.d.ts +2 -0
  37. package/dist/demoTransportContinuity.test.d.ts.map +1 -0
  38. package/dist/demoTransportContinuity.test.js +145 -0
  39. package/dist/demoTransportContinuity.test.js.map +1 -0
  40. package/dist/demoTransportFetch.d.ts +12 -0
  41. package/dist/demoTransportFetch.d.ts.map +1 -0
  42. package/dist/demoTransportFetch.js +109 -0
  43. package/dist/demoTransportFetch.js.map +1 -0
  44. package/dist/demoTransportFetch.test.d.ts +2 -0
  45. package/dist/demoTransportFetch.test.d.ts.map +1 -0
  46. package/dist/demoTransportFetch.test.js +150 -0
  47. package/dist/demoTransportFetch.test.js.map +1 -0
  48. package/dist/demoTransportRequest.d.ts +35 -0
  49. package/dist/demoTransportRequest.d.ts.map +1 -0
  50. package/dist/demoTransportRequest.js +193 -0
  51. package/dist/demoTransportRequest.js.map +1 -0
  52. package/dist/demoTransportRequest.test.d.ts +2 -0
  53. package/dist/demoTransportRequest.test.d.ts.map +1 -0
  54. package/dist/demoTransportRequest.test.js +203 -0
  55. package/dist/demoTransportRequest.test.js.map +1 -0
  56. package/dist/demoTransportState.d.ts +31 -0
  57. package/dist/demoTransportState.d.ts.map +1 -0
  58. package/dist/demoTransportState.js +204 -0
  59. package/dist/demoTransportState.js.map +1 -0
  60. package/dist/demoTransportState.test.d.ts +2 -0
  61. package/dist/demoTransportState.test.d.ts.map +1 -0
  62. package/dist/demoTransportState.test.js +192 -0
  63. package/dist/demoTransportState.test.js.map +1 -0
  64. package/dist/index.d.ts.map +1 -1
  65. package/dist/index.js +299 -17
  66. package/dist/index.js.map +1 -1
  67. package/dist/namedCodexThread.d.ts +3 -0
  68. package/dist/namedCodexThread.d.ts.map +1 -0
  69. package/dist/namedCodexThread.js +22 -0
  70. package/dist/namedCodexThread.js.map +1 -0
  71. package/dist/namedCodexThread.test.d.ts +2 -0
  72. package/dist/namedCodexThread.test.d.ts.map +1 -0
  73. package/dist/namedCodexThread.test.js +63 -0
  74. package/dist/namedCodexThread.test.js.map +1 -0
  75. package/dist/namedSessionBudget.d.ts +21 -0
  76. package/dist/namedSessionBudget.d.ts.map +1 -0
  77. package/dist/namedSessionBudget.js +70 -0
  78. package/dist/namedSessionBudget.js.map +1 -0
  79. package/dist/namedSessionBudget.test.d.ts +2 -0
  80. package/dist/namedSessionBudget.test.d.ts.map +1 -0
  81. package/dist/namedSessionBudget.test.js +40 -0
  82. package/dist/namedSessionBudget.test.js.map +1 -0
  83. package/dist/namedSessionService.d.ts +39 -0
  84. package/dist/namedSessionService.d.ts.map +1 -0
  85. package/dist/namedSessionService.js +269 -0
  86. package/dist/namedSessionService.js.map +1 -0
  87. package/dist/namedSessionService.test.d.ts +2 -0
  88. package/dist/namedSessionService.test.d.ts.map +1 -0
  89. package/dist/namedSessionService.test.js +158 -0
  90. package/dist/namedSessionService.test.js.map +1 -0
  91. package/dist/namedSessionTrust.d.ts +21 -0
  92. package/dist/namedSessionTrust.d.ts.map +1 -0
  93. package/dist/namedSessionTrust.js +64 -0
  94. package/dist/namedSessionTrust.js.map +1 -0
  95. package/dist/namedSessionTrust.test.d.ts +2 -0
  96. package/dist/namedSessionTrust.test.d.ts.map +1 -0
  97. package/dist/namedSessionTrust.test.js +178 -0
  98. package/dist/namedSessionTrust.test.js.map +1 -0
  99. package/dist/onboardingStage.d.ts +1 -1
  100. package/dist/onboardingStage.d.ts.map +1 -1
  101. package/dist/onboardingStage.js.map +1 -1
  102. package/dist/providerSessionChannel.d.ts +1 -0
  103. package/dist/providerSessionChannel.d.ts.map +1 -1
  104. package/dist/providerSessionChannel.js +5 -1
  105. package/dist/providerSessionChannel.js.map +1 -1
  106. package/dist/providerSessionChannel.test.js +10 -3
  107. package/dist/providerSessionChannel.test.js.map +1 -1
  108. package/dist/relaySupervisor.test.js +3 -1
  109. package/dist/relaySupervisor.test.js.map +1 -1
  110. package/dist/repositoryOnboardingCaller.test.js +19 -0
  111. package/dist/repositoryOnboardingCaller.test.js.map +1 -1
  112. package/dist/schemas/demo-transport-continuity.schema.json +27 -0
  113. package/dist/schemas/demo-transport-journal.schema.json +13 -0
  114. package/dist/schemas/demo-transport-request.schema.json +27 -0
  115. package/dist/schemas/demo-transport-state.schema.json +13 -0
  116. package/dist/usage.d.ts +1 -1
  117. package/dist/usage.d.ts.map +1 -1
  118. package/dist/usage.js +1 -1
  119. package/dist/usage.js.map +1 -1
  120. package/dist/usage.test.d.ts +2 -0
  121. package/dist/usage.test.d.ts.map +1 -0
  122. package/dist/usage.test.js +18 -0
  123. package/dist/usage.test.js.map +1 -0
  124. package/package.json +5 -5
package/dist/index.js CHANGED
@@ -41,13 +41,18 @@ import { demoRepositoryPackage } from './demoPackage.js';
41
41
  import { demoDeviceAndPackageStatus } from './demoStatus.js';
42
42
  import { prepareDemoSigningUpgradeProof, readDemoSigningUpgradeContext } from './demoSigningUpgradeProof.js';
43
43
  import { submitDemoSigningUpgradeProof } from './demoSigningUpgradeSubmission.js';
44
+ import { createDemoDeviceTransport } from './demoDeviceTransport.js';
44
45
  import { runDemoWatch } from './demoWatch.js';
45
46
  import { runDemoSupervisor } from './demoSupervisor.js';
46
47
  import { demoWatchRegistrationKey, listDemoWatchRegistrations } from './demoWatchRegistry.js';
47
48
  import { createDemoWatchHealthRecorder } from './demoWatchHealth.js';
48
49
  import { demoWatchStatus, disableDemoWatch, enableDemoWatch, inspectDemoWatchSupervisor } from './demoWatchControl.js';
50
+ import { namedSessionPaths, namedSessionRequest, readNamedSession, saveNamedSession, runNamedSessionService } from './namedSessionService.js';
51
+ import { openCodexAppServerTransport } from '@dharma-ai-labs/agent-fabric-provider-adapters/experimental/codex-transport';
52
+ import { createNamedSessionTrust, isNamedSessionOwnerReceipt, renewNamedSessionLifetime } from './namedSessionTrust.js';
53
+ import { startNamedCodexThread } from './namedCodexThread.js';
49
54
  export { openCooperativeInboxSession } from './cooperativeInboxSession.js';
50
- const VERSION = '0.2.110';
55
+ const VERSION = '0.2.112';
51
56
  const USAGE = CLI_USAGE;
52
57
  const execFileAsync = promisify(execFile);
53
58
  const LOCAL_PROVIDER_IDS = ['codex', 'claude', 'agy', 'hermes'];
@@ -1606,14 +1611,15 @@ async function supervisedDemoCycle(registration, signal) {
1606
1611
  const nativeDirectory = registration.provider === 'codex' ? resolve(workspace, '.agents', 'skills')
1607
1612
  : registration.provider === 'claude' ? resolve(workspace, '.claude', 'skills')
1608
1613
  : nativeSkillDirectory(registration.provider);
1609
- return withDemoDeviceLock(scope, () => {
1614
+ return withDemoDeviceLock(scope, async () => {
1610
1615
  signal.throwIfAborted();
1611
- return demoRepositoryPackage({ scope, workspace, provider: registration.provider,
1612
- nativeSkillDirectory: nativeDirectory }, { fetcher: (url, options) => {
1616
+ const deps = await createDemoDeviceTransport(scope, { fetcher: (url, options) => {
1613
1617
  signal.throwIfAborted();
1614
1618
  return fetch(url, { ...options,
1615
1619
  signal: options?.signal ? AbortSignal.any([options.signal, signal]) : signal });
1616
1620
  } });
1621
+ return demoRepositoryPackage({ scope, workspace, provider: registration.provider,
1622
+ nativeSkillDirectory: nativeDirectory }, deps);
1617
1623
  });
1618
1624
  }
1619
1625
  async function relaySupervise(flags) {
@@ -1665,7 +1671,9 @@ async function relaySupervise(flags) {
1665
1671
  }),
1666
1672
  }) : Promise.resolve({ restarts: 0 });
1667
1673
  services.push(standard);
1668
- const [demoResult, standardResult] = await Promise.all([demo, standard]);
1674
+ const sessions = policyPath ? superviseNamedSessions(controller.signal) : Promise.resolve();
1675
+ services.push(sessions);
1676
+ const [demoResult, standardResult] = await Promise.all([demo, standard, sessions]);
1669
1677
  return { ok: true, stopped: true, ...standardResult, demo: demoResult };
1670
1678
  }
1671
1679
  finally {
@@ -1681,6 +1689,42 @@ async function relaySupervise(flags) {
1681
1689
  await releaseLease();
1682
1690
  }
1683
1691
  }
1692
+ async function superviseNamedSessions(signal) {
1693
+ if (process.platform !== 'linux')
1694
+ return;
1695
+ while (!signal.aborted) {
1696
+ const root = resolve(dharmaHome(), 'sessions');
1697
+ const entries = await readdir(root, { withFileTypes: true }).catch(error => {
1698
+ if (error.code === 'ENOENT')
1699
+ return [];
1700
+ throw error;
1701
+ });
1702
+ for (const entry of entries.slice(0, 50)) {
1703
+ if (signal.aborted)
1704
+ break;
1705
+ if (!entry.isDirectory() || !/^[a-z][a-z0-9-]{0,47}$/.test(entry.name))
1706
+ continue;
1707
+ try {
1708
+ const registration = await readNamedSession(dharmaHome(), entry.name);
1709
+ if (!registration?.enabled)
1710
+ continue;
1711
+ await namedSessionCommand('start', new Map([
1712
+ ['name', registration.name], ['workspace-id', registration.identity.workspaceId], ['apply', true],
1713
+ ]));
1714
+ }
1715
+ catch {
1716
+ process.stderr.write(`${JSON.stringify({ event: 'named_session_reconnect_pending', name: entry.name })}\n`);
1717
+ }
1718
+ }
1719
+ await new Promise(resolveWait => {
1720
+ const finish = () => { clearTimeout(timer); signal.removeEventListener('abort', finish); resolveWait(); };
1721
+ const timer = setTimeout(finish, 10_000);
1722
+ signal.addEventListener('abort', finish, { once: true });
1723
+ if (signal.aborted)
1724
+ finish();
1725
+ });
1726
+ }
1727
+ }
1684
1728
  async function relayStop() {
1685
1729
  const home = dharmaHome();
1686
1730
  const supervisor = await relaySupervisorProcessState(home);
@@ -2033,12 +2077,22 @@ async function bootstrap(flags) {
2033
2077
  usage,
2034
2078
  });
2035
2079
  const operatingContract = await loadAgentFabricOnboardingContract();
2080
+ const namedSession = provider === 'codex' && process.platform === 'linux' && sharedRepositoryReady
2081
+ ? await withOnboardingStage('named_session', String(onboarded.workspaceId), `dharma bootstrap --resume --complete --portal-url ${hqUrl} --organization-id ${organizationId} --workspace . --policy-revision ${policyRevision}`, () => namedSessionCommand('start', new Map([
2082
+ ['name', String(flags.get('session-name') || `codex-${String(onboarded.workspaceId).slice(0, 8)}`)],
2083
+ ['workspace-id', String(onboarded.workspaceId)], ['apply', true],
2084
+ ...(typeof flags.get('session-budget-cents') === 'string'
2085
+ ? [['session-budget-cents', String(flags.get('session-budget-cents'))]] : []),
2086
+ ])))
2087
+ : null;
2036
2088
  const repositoryReceipt = onboarded;
2089
+ const namedSessionReady = provider !== 'codex' || process.platform !== 'linux'
2090
+ || Boolean(namedSession?.ok === true && ['running', 'executing'].includes(String(namedSession.state)));
2037
2091
  const firstLearning = repositoryReceipt.firstLearningEvidence;
2038
2092
  const role = repositoryReceipt.repositoryRole;
2039
2093
  return {
2040
- ok: repositoryReadiness?.outcome !== 'blocked',
2041
- stage: sharedRepositoryReady ? 'complete'
2094
+ ok: repositoryReadiness?.outcome !== 'blocked' && namedSessionReady,
2095
+ stage: sharedRepositoryReady ? namedSessionReady ? 'complete' : 'named_session_pending'
2042
2096
  : repositoryReadiness?.outcome === 'blocked' ? 'shared_repository_blocked' : 'shared_repository_pending',
2043
2097
  localStage: 'complete',
2044
2098
  sharedRepositoryReady,
@@ -2063,6 +2117,7 @@ async function bootstrap(flags) {
2063
2117
  },
2064
2118
  relayProbe: relay.probe,
2065
2119
  relay,
2120
+ namedSession,
2066
2121
  repositoryReadiness,
2067
2122
  organizationApi,
2068
2123
  operatingContract: {
@@ -2077,6 +2132,8 @@ async function bootstrap(flags) {
2077
2132
  ? Number(firstLearning.discovered || 0) > 0 ? 'synchronized' : 'no_eligible_history'
2078
2133
  : String(firstLearning?.state || 'pending'),
2079
2134
  synchronization: relay.state === 'running' ? 'running' : 'pending',
2135
+ namedSession: namedSession && namedSessionReady ? 'ready'
2136
+ : provider === 'codex' && process.platform === 'linux' ? 'pending' : 'not_supported',
2080
2137
  },
2081
2138
  };
2082
2139
  }
@@ -3159,6 +3216,17 @@ async function repositoryRoleCommand(action, flags) {
3159
3216
  if (!item)
3160
3217
  throw new Error('Repository role workspace is not registered locally.');
3161
3218
  const scope = repositoryRoleScope(item), transport = await client();
3219
+ if (['ask', 'reply'].includes(action) && flags.has('session-name')) {
3220
+ const name = required(flags, 'session-name');
3221
+ const registered = await readNamedSession(dharmaHome(), name);
3222
+ if (!registered || registered.identity.workspaceId !== workspaceId)
3223
+ throw new Error('named_session_scope_mismatch');
3224
+ return namedSessionRequest(dharmaHome(), name, action === 'ask' ? {
3225
+ action: 'ask', targetBindingId: required(flags, 'target-session-binding-id'),
3226
+ taskId: required(flags, 'task-id'), category: required(flags, 'category'), question: required(flags, 'question'),
3227
+ } : { action: 'read', questionId: required(flags, 'question-id'),
3228
+ taskId: required(flags, 'task-id'), targetBindingId: required(flags, 'target-session-binding-id') });
3229
+ }
3162
3230
  if (action === 'register') {
3163
3231
  const categories = required(flags, 'question-categories').split(',').map(value => value.trim()).filter(Boolean);
3164
3232
  return registerRepositoryRoleMetadata(transport, scope, { expectedRevision: Number(flags.get('expected-revision') || 0),
@@ -3175,6 +3243,201 @@ async function repositoryRoleCommand(action, flags) {
3175
3243
  targetEndpointId: typeof flags.get('target-endpoint-id') === 'string' ? String(flags.get('target-endpoint-id')) : undefined });
3176
3244
  return readRepositoryRoleReply({ transport, scope, questionId: required(flags, 'question-id') });
3177
3245
  }
3246
+ async function namedSessionCommand(action, flags) {
3247
+ const name = String(flags.get('name') || 'codex');
3248
+ const paths = namedSessionPaths(dharmaHome(), name);
3249
+ if (process.platform !== 'linux')
3250
+ throw new Error('codex_session_sandbox_unqualified');
3251
+ const workspaceId = required(flags, 'workspace-id');
3252
+ const item = (await registry()).find(row => row.workspaceId === workspaceId);
3253
+ const config = await readDeviceConfig();
3254
+ if (!item || !config || item.organizationId !== config.organizationId)
3255
+ throw new Error('named_session_scope_mismatch');
3256
+ const scope = repositoryRoleScope(item);
3257
+ const existing = await readNamedSession(dharmaHome(), name);
3258
+ if (existing && (existing.identity.workspaceId !== item.workspaceId
3259
+ || existing.identity.organizationId !== config.organizationId || existing.identity.deviceId !== config.deviceId
3260
+ || existing.identity.endpointId !== scope.endpointId || existing.identity.repositoryBindingId !== scope.repositoryBindingId)) {
3261
+ throw new Error('named_session_scope_mismatch');
3262
+ }
3263
+ if (action === 'status') {
3264
+ try {
3265
+ return await namedSessionRequest(dharmaHome(), name, { action: 'status' });
3266
+ }
3267
+ catch {
3268
+ return { ok: true, name, state: 'stopped', registered: Boolean(existing), enabled: existing?.enabled ?? false,
3269
+ bindingId: existing?.bindingId ?? null };
3270
+ }
3271
+ }
3272
+ if (action === 'work')
3273
+ return namedSessionRequest(dharmaHome(), name, { action: 'work',
3274
+ workId: String(flags.get('work-id') || randomUUID()), prompt: required(flags, 'prompt') });
3275
+ if (action === 'stop') {
3276
+ if (!flags.has('apply'))
3277
+ return { ok: true, planned: true, name, action: 'stop' };
3278
+ if (existing)
3279
+ await saveNamedSession(dharmaHome(), { ...existing, enabled: false });
3280
+ try {
3281
+ return await namedSessionRequest(dharmaHome(), name, { action: 'stop' });
3282
+ }
3283
+ catch {
3284
+ return { ok: true, name, state: 'stopped', enabled: false };
3285
+ }
3286
+ }
3287
+ if (!['start', 'serve'].includes(action))
3288
+ throw new Error('named_session_action_invalid');
3289
+ if (!flags.has('apply'))
3290
+ return { ok: true, planned: true, name, workspaceId,
3291
+ provider: 'codex', localWork: 'workspace_write', peerQuestions: 'read_only', network: 'deny' };
3292
+ if (!await repositorySharedReady(item))
3293
+ throw new Error('named_session_repository_package_pending');
3294
+ if (action === 'start') {
3295
+ if (existing && !existing.enabled)
3296
+ await saveNamedSession(dharmaHome(), { ...existing, enabled: true });
3297
+ try {
3298
+ return await namedSessionRequest(dharmaHome(), name, { action: 'status' });
3299
+ }
3300
+ catch { /* Start an owned worker below. */ }
3301
+ const args = [fileURLToPath(import.meta.url), 'sessions', 'serve', '--name', name,
3302
+ '--workspace-id', workspaceId, '--apply'];
3303
+ for (const key of ['session-budget-cents', 'turn-budget-cents']) {
3304
+ if (flags.has(key))
3305
+ args.push(`--${key}`, required(flags, key));
3306
+ }
3307
+ const child = spawn(process.execPath, args, { cwd: item.path, detached: true, stdio: 'ignore', env: process.env });
3308
+ child.on('error', () => { });
3309
+ child.unref();
3310
+ for (let attempt = 0; attempt < 80; attempt++) {
3311
+ await new Promise(resolveWait => setTimeout(resolveWait, 250));
3312
+ try {
3313
+ return await namedSessionRequest(dharmaHome(), name, { action: 'status' });
3314
+ }
3315
+ catch { /* Await service initialization. */ }
3316
+ }
3317
+ throw new Error('named_session_startup_failed');
3318
+ }
3319
+ await mkdir(paths.root, { recursive: true, mode: 0o700 });
3320
+ const releaseLock = await acquirePidLock(resolve(paths.root, 'service.lock'), 250, 'named_session_already_running');
3321
+ const { LocalVault, loadOrCreateVaultMasterKey } = await loadVaultModule();
3322
+ const vault = await LocalVault.open({ root: resolve(dharmaHome(), 'vault'), masterKey: await loadOrCreateVaultMasterKey() });
3323
+ const controller = new AbortController(), stop = () => controller.abort();
3324
+ process.once('SIGTERM', stop);
3325
+ process.once('SIGINT', stop);
3326
+ let transport;
3327
+ try {
3328
+ const fabric = await AgentFabricClient.open({ configPath: configPath(), statePath: resolve(paths.root, 'protocol-state.json') });
3329
+ if (fabric.config.organizationId !== config.organizationId || fabric.config.deviceId !== config.deviceId
3330
+ || fabric.config.publicKeyEd25519 !== config.publicKeyEd25519)
3331
+ throw new Error('named_session_trust_scope_mismatch');
3332
+ const trust = createNamedSessionTrust({ configPath: configPath(), identity: fabric.config });
3333
+ await trust.refresh();
3334
+ await fabric.openSession(VERSION);
3335
+ const policyPath = resolve(item.path, '.dharma', 'approved-policy.json');
3336
+ const policy = await refreshVerifiedWorkspacePolicyForTransmission(policyPath, workspaceId, fabric);
3337
+ const writeRoots = policy.tasks.writePaths.filter(path => /^[a-zA-Z0-9_-]+\/\*\*$/.test(path))
3338
+ .map(path => path.slice(0, -3));
3339
+ if (!writeRoots.length)
3340
+ throw new Error('named_session_workspace_write_not_authorized');
3341
+ const workRoots = ['"."="read"', ...writeRoots.map(root => `${JSON.stringify(root)}="write"`)].join(',');
3342
+ transport = await openCodexAppServerTransport({ command: 'codex', cwd: item.path,
3343
+ environment: providerProcessEnvironment(process.env), experimentalApi: true,
3344
+ argv: ['-c', 'default_permissions="dharma_bridge"',
3345
+ '-c', 'permissions.dharma_bridge.filesystem={":minimal"="read",":workspace_roots"={"."="read"}}',
3346
+ '-c', 'permissions.dharma_bridge.network={enabled=false}',
3347
+ '-c', `permissions.dharma_work.filesystem={":minimal"="read",":workspace_roots"={${workRoots}}}`,
3348
+ '-c', 'permissions.dharma_work.network={enabled=false}', 'app-server'] });
3349
+ const account = await transport.request('account/read', { refreshToken: false });
3350
+ if (!account.account)
3351
+ throw new Error('named_session_provider_authentication_required');
3352
+ let registration = existing;
3353
+ if (!registration) {
3354
+ const maximumCostCents = boundedInteger(flags.get('session-budget-cents'), 1000, 1, 10000, '--session-budget-cents');
3355
+ const maximumTurnCostCents = boundedInteger(flags.get('turn-budget-cents'), 25, 1, maximumCostCents, '--turn-budget-cents');
3356
+ const threadId = await startNamedCodexThread(transport, item.path, name);
3357
+ const bindingId = randomUUID();
3358
+ const response = await fabric.signedPost('/agent-fabric/provider-sessions', {
3359
+ schema: 'dharma.provider-session-registration/v1', action: 'attach', provider: 'codex', mode: 'bridge_owned',
3360
+ workspaceId, endpointId: scope.endpointId, repositoryBindingId: scope.repositoryBindingId,
3361
+ bindingId, expectedRevision: 0, leaseSeconds: 60,
3362
+ });
3363
+ const remote = response.registration;
3364
+ if (response.ok !== true || response.organizationId !== config.organizationId || !remote
3365
+ || remote.bindingId !== bindingId || remote.workspaceId !== workspaceId || remote.endpointId !== scope.endpointId
3366
+ || remote.repositoryBindingId !== scope.repositoryBindingId || remote.deviceId !== config.deviceId
3367
+ || !UUID_PATTERN.test(String(remote.membershipId)) || remote.provider !== 'codex'
3368
+ || remote.mode !== 'bridge_owned' || remote.state !== 'attached' || remote.revision !== 1) {
3369
+ throw new Error('named_session_registration_invalid');
3370
+ }
3371
+ const identity = { organizationId: config.organizationId, repositoryBindingId: scope.repositoryBindingId,
3372
+ workspaceId, endpointId: scope.endpointId, membershipId: String(remote.membershipId), deviceId: config.deviceId,
3373
+ provider: 'codex' };
3374
+ vault.saveProviderSessionBinding({ schema: 'dharma.local-provider-session-binding/v1', ...identity,
3375
+ bindingId, owner: 'dharma_bridge', sessionId: threadId, workspaceRoot: item.path,
3376
+ createdAt: new Date().toISOString(), expiresAt: new Date(Date.now() + 30 * 86400000).toISOString(),
3377
+ maximumProviderCostCents: maximumTurnCostCents });
3378
+ registration = { schema: 'dharma.named-session/v1', name, bindingId, identity,
3379
+ maximumCostCents, maximumTurnCostCents, enabled: true };
3380
+ await saveNamedSession(dharmaHome(), registration);
3381
+ }
3382
+ const authorizeLifetime = async () => {
3383
+ const current = await refreshVerifiedWorkspacePolicyForTransmission(policyPath, workspaceId, fabric);
3384
+ if (!writeRoots.every(root => current.tasks.writePaths.includes(`${root}/**`)))
3385
+ return false;
3386
+ const response = await fabric.signedPost('/agent-fabric/provider-sessions', {
3387
+ schema: 'dharma.provider-session-registration/v1', action: 'inspect', provider: 'codex', mode: 'bridge_owned',
3388
+ workspaceId, endpointId: scope.endpointId, repositoryBindingId: scope.repositoryBindingId,
3389
+ bindingId: registration.bindingId, expectedRevision: 0, leaseSeconds: 60,
3390
+ });
3391
+ return isNamedSessionOwnerReceipt(response, registration.bindingId, registration.identity);
3392
+ };
3393
+ const refreshLifetime = () => renewNamedSessionLifetime({ vault, bindingId: registration.bindingId,
3394
+ identity: registration.identity, trust, authorize: authorizeLifetime });
3395
+ await refreshLifetime();
3396
+ const channelTransport = { async signedPost(route, body) {
3397
+ await trust.refresh();
3398
+ const response = await fabric.signedPost(route, body);
3399
+ await trust.refresh();
3400
+ return response;
3401
+ } };
3402
+ // The durable reservation already suppresses duplicate provider execution.
3403
+ const consumed = new Set();
3404
+ return await runNamedSessionService({ home: dharmaHome(), registration, vault, signal: controller.signal,
3405
+ localWriteRoots: writeRoots,
3406
+ withActivationBoundary: operation => withWorkspaceSkillActivationLock(workspaceId, 'codex', async () => {
3407
+ await refreshLifetime();
3408
+ const skill = await verifyAgentFabricSkillInstallation({ provider: 'codex', workspace: item.path });
3409
+ if (!skill.ready || !await repositorySharedReady(item))
3410
+ throw new Error('named_session_repository_package_pending');
3411
+ return operation();
3412
+ }),
3413
+ authorizeLocalWork: async () => {
3414
+ const current = await refreshVerifiedWorkspacePolicyForTransmission(policyPath, workspaceId, fabric);
3415
+ return writeRoots.every(root => current.tasks.writePaths.includes(`${root}/**`));
3416
+ },
3417
+ openTransport: async () => transport, channelTransport,
3418
+ verifier: { resolvePublicKey: trust.resolvePublicKey, consume: async (id) => { if (consumed.has(id))
3419
+ return false; consumed.add(id); return true; } },
3420
+ authorizeContent: async (content) => {
3421
+ const current = await refreshVerifiedWorkspacePolicyForTransmission(policyPath, workspaceId, fabric);
3422
+ return current.evidence.automaticDisclosure?.mode === 'customer_authorized_content'
3423
+ && !containsDisallowedLocalPath(content)
3424
+ && canonicalize(redactValue(content, { classes: new Set(), redactedValues: 0,
3425
+ excludedPaths: 0, inputBytes: 0, outputBytes: 0 })) === canonicalize(content);
3426
+ } });
3427
+ }
3428
+ finally {
3429
+ try {
3430
+ if (transport)
3431
+ await transport.close();
3432
+ }
3433
+ finally {
3434
+ vault.close();
3435
+ await releaseLock();
3436
+ process.removeListener('SIGTERM', stop);
3437
+ process.removeListener('SIGINT', stop);
3438
+ }
3439
+ }
3440
+ }
3178
3441
  async function workspaceSync(flags, positional) {
3179
3442
  const workspaceId = positional[0] || required(flags, 'workspace-id');
3180
3443
  const policyRevision = required(flags, 'policy-revision');
@@ -5899,7 +6162,7 @@ export async function run(argv) {
5899
6162
  return { version: VERSION };
5900
6163
  if (command === 'demo' && ['connect', 'status', 'role', 'peers', 'ask', 'reply', 'inbox', 'ack', 'resume',
5901
6164
  'package', 'package-status', 'watch', 'watch-enable', 'watch-status', 'watch-disable',
5902
- 'signing-client-proof', 'signing-owner-proof']
6165
+ 'signing-client-proof', 'signing-owner-proof', 'transport-connect']
5903
6166
  .includes(String(subcommand))) {
5904
6167
  const hqUrl = normalizeHqUrl(portalUrl(flags));
5905
6168
  const organizationId = required(flags, 'organization-id');
@@ -5921,11 +6184,23 @@ export async function run(argv) {
5921
6184
  if (watchControl && await realpath(root) !== workspace) {
5922
6185
  throw new Error('Demo watch setup must select the exact Git repository root.');
5923
6186
  }
6187
+ let transportOrigin = null;
6188
+ if (subcommand === 'transport-connect') {
6189
+ transportOrigin = required(flags, 'transport-origin');
6190
+ try {
6191
+ const target = new URL(transportOrigin);
6192
+ if (transportOrigin.length > 2048 || target.protocol !== 'https:' || target.origin !== transportOrigin || transportOrigin === hqUrl)
6193
+ throw new Error();
6194
+ }
6195
+ catch {
6196
+ throw new Error('Demo transport recovery target_invalid. Use the exact reviewed HTTPS origin.');
6197
+ }
6198
+ }
5924
6199
  if (flags.has('dry-run'))
5925
- return { ok: true, stage: signingProof ? 'demo_signing_proof_plan'
6200
+ return { ok: true, stage: transportOrigin ? 'demo_transport_plan' : signingProof ? 'demo_signing_proof_plan'
5926
6201
  : watchControl ? 'demo_watch_plan' : 'demo_device_plan',
5927
6202
  organizationId, repositoryId, normalizedRepository, workspaceVerified: true,
5928
- repositoryPackageState: 'not_connected' };
6203
+ repositoryPackageState: 'not_connected', ...(transportOrigin ? { transportOrigin } : {}) };
5929
6204
  const grant = subcommand === 'connect' ? required(flags, 'grant') : null;
5930
6205
  const scope = {
5931
6206
  hqUrl, organizationId, repositoryId, normalizedRepository,
@@ -5934,7 +6209,7 @@ export async function run(argv) {
5934
6209
  if (signingProof) {
5935
6210
  const context = await readDemoSigningUpgradeContext(resolve(required(flags, 'review-context')));
5936
6211
  if (flags.has('submit'))
5937
- return withDemoDeviceLock(scope, () => submitDemoSigningUpgradeProof(scope, context));
6212
+ return withDemoDeviceLock(scope, async () => submitDemoSigningUpgradeProof(scope, context, await createDemoDeviceTransport(scope)));
5938
6213
  return withDemoDeviceLock(scope, () => prepareDemoSigningUpgradeProof(scope, context, subcommand === 'signing-owner-proof' ? 'owner' : 'client'));
5939
6214
  }
5940
6215
  if (watchControl) {
@@ -5949,7 +6224,9 @@ export async function run(argv) {
5949
6224
  hqUrl, organizationId, repositoryId, normalizedRepository, provider, workspace };
5950
6225
  const input = { home: dharmaHome(), registration, version: VERSION };
5951
6226
  const deps = {
5952
- verify: async () => { await withDemoDeviceLock(scope, () => verifyDemoDevice(scope)); },
6227
+ verify: async () => {
6228
+ await withDemoDeviceLock(scope, async () => verifyDemoDevice(scope, await createDemoDeviceTransport(scope)));
6229
+ },
5953
6230
  exclusive: operation => withRelayStartupMutation(operation),
5954
6231
  prepareStartup: async () => {
5955
6232
  const launcher = await installStableRepositoryLauncher(workspace);
@@ -5977,9 +6254,9 @@ export async function run(argv) {
5977
6254
  const demoNativeSkillDirectory = provider === 'codex' ? resolve(workspace, '.agents', 'skills')
5978
6255
  : provider === 'claude' ? resolve(workspace, '.claude', 'skills')
5979
6256
  : nativeSkillDirectory(provider);
5980
- const cycle = () => withDemoDeviceLock(scope, () => demoRepositoryPackage({ scope, workspace,
6257
+ const cycle = () => withDemoDeviceLock(scope, async () => demoRepositoryPackage({ scope, workspace,
5981
6258
  statusOnly: subcommand === 'package-status', provider,
5982
- nativeSkillDirectory: demoNativeSkillDirectory }));
6259
+ nativeSkillDirectory: demoNativeSkillDirectory }, await createDemoDeviceTransport(scope)));
5983
6260
  if (subcommand !== 'watch')
5984
6261
  return cycle();
5985
6262
  const rawInterval = flags.get('interval-ms');
@@ -6002,8 +6279,6 @@ export async function run(argv) {
6002
6279
  }
6003
6280
  }
6004
6281
  return withDemoDeviceLock(scope, async () => {
6005
- if (subcommand === 'status')
6006
- return demoDeviceAndPackageStatus(scope, workspace);
6007
6282
  if (subcommand === 'connect') {
6008
6283
  const connected = await connectDemoDevice({
6009
6284
  ...scope, grant: grant,
@@ -6017,6 +6292,11 @@ export async function run(argv) {
6017
6292
  const { configPath: _configPath, ...receipt } = connected;
6018
6293
  return receipt;
6019
6294
  }
6295
+ const deps = await createDemoDeviceTransport(scope);
6296
+ if (transportOrigin)
6297
+ return deps.connectTransport(transportOrigin);
6298
+ if (subcommand === 'status')
6299
+ return demoDeviceAndPackageStatus(scope, workspace, deps);
6020
6300
  let action = null;
6021
6301
  if (subcommand === 'role') {
6022
6302
  const categories = repeated.get('category') || [];
@@ -6040,11 +6320,13 @@ export async function run(argv) {
6040
6320
  action = { kind: 'inbox' };
6041
6321
  if (subcommand === 'ack')
6042
6322
  action = { kind: 'ack', messageId: required(flags, 'message-id') };
6043
- return performDemoPeerAction(scope, action);
6323
+ return performDemoPeerAction(scope, action, deps);
6044
6324
  });
6045
6325
  }
6046
6326
  if (command === 'bootstrap')
6047
6327
  return bootstrap(flags);
6328
+ if (command === 'sessions')
6329
+ return namedSessionCommand(String(subcommand), flags);
6048
6330
  if (command === 'onboard')
6049
6331
  return onboard(flags);
6050
6332
  if (command === 'login')