@dharma-ai-labs/agent-fabric 0.2.111 → 0.2.113

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 (88) 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/demoPackage.d.ts +32 -0
  21. package/dist/demoPackage.d.ts.map +1 -1
  22. package/dist/demoPackage.js +24 -4
  23. package/dist/demoPackage.js.map +1 -1
  24. package/dist/demoPackage.test.js +19 -1
  25. package/dist/demoPackage.test.js.map +1 -1
  26. package/dist/index.d.ts.map +1 -1
  27. package/dist/index.js +275 -8
  28. package/dist/index.js.map +1 -1
  29. package/dist/namedCodexThread.d.ts +3 -0
  30. package/dist/namedCodexThread.d.ts.map +1 -0
  31. package/dist/namedCodexThread.js +22 -0
  32. package/dist/namedCodexThread.js.map +1 -0
  33. package/dist/namedCodexThread.test.d.ts +2 -0
  34. package/dist/namedCodexThread.test.d.ts.map +1 -0
  35. package/dist/namedCodexThread.test.js +63 -0
  36. package/dist/namedCodexThread.test.js.map +1 -0
  37. package/dist/namedSessionBudget.d.ts +21 -0
  38. package/dist/namedSessionBudget.d.ts.map +1 -0
  39. package/dist/namedSessionBudget.js +70 -0
  40. package/dist/namedSessionBudget.js.map +1 -0
  41. package/dist/namedSessionBudget.test.d.ts +2 -0
  42. package/dist/namedSessionBudget.test.d.ts.map +1 -0
  43. package/dist/namedSessionBudget.test.js +40 -0
  44. package/dist/namedSessionBudget.test.js.map +1 -0
  45. package/dist/namedSessionService.d.ts +39 -0
  46. package/dist/namedSessionService.d.ts.map +1 -0
  47. package/dist/namedSessionService.js +269 -0
  48. package/dist/namedSessionService.js.map +1 -0
  49. package/dist/namedSessionService.test.d.ts +2 -0
  50. package/dist/namedSessionService.test.d.ts.map +1 -0
  51. package/dist/namedSessionService.test.js +158 -0
  52. package/dist/namedSessionService.test.js.map +1 -0
  53. package/dist/namedSessionTrust.d.ts +21 -0
  54. package/dist/namedSessionTrust.d.ts.map +1 -0
  55. package/dist/namedSessionTrust.js +64 -0
  56. package/dist/namedSessionTrust.js.map +1 -0
  57. package/dist/namedSessionTrust.test.d.ts +2 -0
  58. package/dist/namedSessionTrust.test.d.ts.map +1 -0
  59. package/dist/namedSessionTrust.test.js +178 -0
  60. package/dist/namedSessionTrust.test.js.map +1 -0
  61. package/dist/onboardingStage.d.ts +1 -1
  62. package/dist/onboardingStage.d.ts.map +1 -1
  63. package/dist/onboardingStage.js.map +1 -1
  64. package/dist/providerSessionChannel.d.ts +1 -0
  65. package/dist/providerSessionChannel.d.ts.map +1 -1
  66. package/dist/providerSessionChannel.js +5 -1
  67. package/dist/providerSessionChannel.js.map +1 -1
  68. package/dist/providerSessionChannel.test.js +10 -3
  69. package/dist/providerSessionChannel.test.js.map +1 -1
  70. package/dist/repositoryOnboardingCaller.test.js +19 -0
  71. package/dist/repositoryOnboardingCaller.test.js.map +1 -1
  72. package/dist/studyExactTaskSelector.d.ts +3 -0
  73. package/dist/studyExactTaskSelector.d.ts.map +1 -0
  74. package/dist/studyExactTaskSelector.js +27 -0
  75. package/dist/studyExactTaskSelector.js.map +1 -0
  76. package/dist/studyExactTaskSelector.test.d.ts +2 -0
  77. package/dist/studyExactTaskSelector.test.d.ts.map +1 -0
  78. package/dist/studyExactTaskSelector.test.js +25 -0
  79. package/dist/studyExactTaskSelector.test.js.map +1 -0
  80. package/dist/usage.d.ts +1 -1
  81. package/dist/usage.d.ts.map +1 -1
  82. package/dist/usage.js +1 -1
  83. package/dist/usage.js.map +1 -1
  84. package/dist/usage.test.d.ts +2 -0
  85. package/dist/usage.test.d.ts.map +1 -0
  86. package/dist/usage.test.js +18 -0
  87. package/dist/usage.test.js.map +1 -0
  88. package/package.json +5 -5
package/dist/index.js CHANGED
@@ -18,6 +18,7 @@ import { executeTask, FileActionExecutionJournal, FileTaskReceiptStore, provider
18
18
  import { CLI_USAGE } from './usage.js';
19
19
  import { superviseRelay } from './relaySupervisor.js';
20
20
  import { disableRelayAutostart, enableRelayAutostart, relayAutostartStatus, startRelayAutostart } from './relayAutostart.js';
21
+ import { assertStudyExactPoll, studyTaskIdFromFlags } from './studyExactTaskSelector.js';
21
22
  import { initializeRepositoryKnowledge, readRepositoryKnowledgeSource } from './repositoryKnowledge.js';
22
23
  import { inventoryRepositoryPackage, readRepositoryPackageSnapshot, readRepositorySourceBaselineSnapshot, writeRepositoryPackageSnapshot } from './repositoryPackage.js';
23
24
  import { validateRepositorySourceAuthorization } from './repositorySourceAuthorization.js';
@@ -47,8 +48,12 @@ import { runDemoSupervisor } from './demoSupervisor.js';
47
48
  import { demoWatchRegistrationKey, listDemoWatchRegistrations } from './demoWatchRegistry.js';
48
49
  import { createDemoWatchHealthRecorder } from './demoWatchHealth.js';
49
50
  import { demoWatchStatus, disableDemoWatch, enableDemoWatch, inspectDemoWatchSupervisor } from './demoWatchControl.js';
51
+ import { namedSessionPaths, namedSessionRequest, readNamedSession, saveNamedSession, runNamedSessionService } from './namedSessionService.js';
52
+ import { openCodexAppServerTransport } from '@dharma-ai-labs/agent-fabric-provider-adapters/experimental/codex-transport';
53
+ import { createNamedSessionTrust, isNamedSessionOwnerReceipt, renewNamedSessionLifetime } from './namedSessionTrust.js';
54
+ import { startNamedCodexThread } from './namedCodexThread.js';
50
55
  export { openCooperativeInboxSession } from './cooperativeInboxSession.js';
51
- const VERSION = '0.2.111';
56
+ const VERSION = '0.2.113';
52
57
  const USAGE = CLI_USAGE;
53
58
  const execFileAsync = promisify(execFile);
54
59
  const LOCAL_PROVIDER_IDS = ['codex', 'claude', 'agy', 'hermes'];
@@ -1667,7 +1672,9 @@ async function relaySupervise(flags) {
1667
1672
  }),
1668
1673
  }) : Promise.resolve({ restarts: 0 });
1669
1674
  services.push(standard);
1670
- const [demoResult, standardResult] = await Promise.all([demo, standard]);
1675
+ const sessions = policyPath ? superviseNamedSessions(controller.signal) : Promise.resolve();
1676
+ services.push(sessions);
1677
+ const [demoResult, standardResult] = await Promise.all([demo, standard, sessions]);
1671
1678
  return { ok: true, stopped: true, ...standardResult, demo: demoResult };
1672
1679
  }
1673
1680
  finally {
@@ -1683,6 +1690,42 @@ async function relaySupervise(flags) {
1683
1690
  await releaseLease();
1684
1691
  }
1685
1692
  }
1693
+ async function superviseNamedSessions(signal) {
1694
+ if (process.platform !== 'linux')
1695
+ return;
1696
+ while (!signal.aborted) {
1697
+ const root = resolve(dharmaHome(), 'sessions');
1698
+ const entries = await readdir(root, { withFileTypes: true }).catch(error => {
1699
+ if (error.code === 'ENOENT')
1700
+ return [];
1701
+ throw error;
1702
+ });
1703
+ for (const entry of entries.slice(0, 50)) {
1704
+ if (signal.aborted)
1705
+ break;
1706
+ if (!entry.isDirectory() || !/^[a-z][a-z0-9-]{0,47}$/.test(entry.name))
1707
+ continue;
1708
+ try {
1709
+ const registration = await readNamedSession(dharmaHome(), entry.name);
1710
+ if (!registration?.enabled)
1711
+ continue;
1712
+ await namedSessionCommand('start', new Map([
1713
+ ['name', registration.name], ['workspace-id', registration.identity.workspaceId], ['apply', true],
1714
+ ]));
1715
+ }
1716
+ catch {
1717
+ process.stderr.write(`${JSON.stringify({ event: 'named_session_reconnect_pending', name: entry.name })}\n`);
1718
+ }
1719
+ }
1720
+ await new Promise(resolveWait => {
1721
+ const finish = () => { clearTimeout(timer); signal.removeEventListener('abort', finish); resolveWait(); };
1722
+ const timer = setTimeout(finish, 10_000);
1723
+ signal.addEventListener('abort', finish, { once: true });
1724
+ if (signal.aborted)
1725
+ finish();
1726
+ });
1727
+ }
1728
+ }
1686
1729
  async function relayStop() {
1687
1730
  const home = dharmaHome();
1688
1731
  const supervisor = await relaySupervisorProcessState(home);
@@ -2035,12 +2078,22 @@ async function bootstrap(flags) {
2035
2078
  usage,
2036
2079
  });
2037
2080
  const operatingContract = await loadAgentFabricOnboardingContract();
2081
+ const namedSession = provider === 'codex' && process.platform === 'linux' && sharedRepositoryReady
2082
+ ? 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([
2083
+ ['name', String(flags.get('session-name') || `codex-${String(onboarded.workspaceId).slice(0, 8)}`)],
2084
+ ['workspace-id', String(onboarded.workspaceId)], ['apply', true],
2085
+ ...(typeof flags.get('session-budget-cents') === 'string'
2086
+ ? [['session-budget-cents', String(flags.get('session-budget-cents'))]] : []),
2087
+ ])))
2088
+ : null;
2038
2089
  const repositoryReceipt = onboarded;
2090
+ const namedSessionReady = provider !== 'codex' || process.platform !== 'linux'
2091
+ || Boolean(namedSession?.ok === true && ['running', 'executing'].includes(String(namedSession.state)));
2039
2092
  const firstLearning = repositoryReceipt.firstLearningEvidence;
2040
2093
  const role = repositoryReceipt.repositoryRole;
2041
2094
  return {
2042
- ok: repositoryReadiness?.outcome !== 'blocked',
2043
- stage: sharedRepositoryReady ? 'complete'
2095
+ ok: repositoryReadiness?.outcome !== 'blocked' && namedSessionReady,
2096
+ stage: sharedRepositoryReady ? namedSessionReady ? 'complete' : 'named_session_pending'
2044
2097
  : repositoryReadiness?.outcome === 'blocked' ? 'shared_repository_blocked' : 'shared_repository_pending',
2045
2098
  localStage: 'complete',
2046
2099
  sharedRepositoryReady,
@@ -2065,6 +2118,7 @@ async function bootstrap(flags) {
2065
2118
  },
2066
2119
  relayProbe: relay.probe,
2067
2120
  relay,
2121
+ namedSession,
2068
2122
  repositoryReadiness,
2069
2123
  organizationApi,
2070
2124
  operatingContract: {
@@ -2079,6 +2133,8 @@ async function bootstrap(flags) {
2079
2133
  ? Number(firstLearning.discovered || 0) > 0 ? 'synchronized' : 'no_eligible_history'
2080
2134
  : String(firstLearning?.state || 'pending'),
2081
2135
  synchronization: relay.state === 'running' ? 'running' : 'pending',
2136
+ namedSession: namedSession && namedSessionReady ? 'ready'
2137
+ : provider === 'codex' && process.platform === 'linux' ? 'pending' : 'not_supported',
2082
2138
  },
2083
2139
  };
2084
2140
  }
@@ -3161,6 +3217,17 @@ async function repositoryRoleCommand(action, flags) {
3161
3217
  if (!item)
3162
3218
  throw new Error('Repository role workspace is not registered locally.');
3163
3219
  const scope = repositoryRoleScope(item), transport = await client();
3220
+ if (['ask', 'reply'].includes(action) && flags.has('session-name')) {
3221
+ const name = required(flags, 'session-name');
3222
+ const registered = await readNamedSession(dharmaHome(), name);
3223
+ if (!registered || registered.identity.workspaceId !== workspaceId)
3224
+ throw new Error('named_session_scope_mismatch');
3225
+ return namedSessionRequest(dharmaHome(), name, action === 'ask' ? {
3226
+ action: 'ask', targetBindingId: required(flags, 'target-session-binding-id'),
3227
+ taskId: required(flags, 'task-id'), category: required(flags, 'category'), question: required(flags, 'question'),
3228
+ } : { action: 'read', questionId: required(flags, 'question-id'),
3229
+ taskId: required(flags, 'task-id'), targetBindingId: required(flags, 'target-session-binding-id') });
3230
+ }
3164
3231
  if (action === 'register') {
3165
3232
  const categories = required(flags, 'question-categories').split(',').map(value => value.trim()).filter(Boolean);
3166
3233
  return registerRepositoryRoleMetadata(transport, scope, { expectedRevision: Number(flags.get('expected-revision') || 0),
@@ -3177,6 +3244,201 @@ async function repositoryRoleCommand(action, flags) {
3177
3244
  targetEndpointId: typeof flags.get('target-endpoint-id') === 'string' ? String(flags.get('target-endpoint-id')) : undefined });
3178
3245
  return readRepositoryRoleReply({ transport, scope, questionId: required(flags, 'question-id') });
3179
3246
  }
3247
+ async function namedSessionCommand(action, flags) {
3248
+ const name = String(flags.get('name') || 'codex');
3249
+ const paths = namedSessionPaths(dharmaHome(), name);
3250
+ if (process.platform !== 'linux')
3251
+ throw new Error('codex_session_sandbox_unqualified');
3252
+ const workspaceId = required(flags, 'workspace-id');
3253
+ const item = (await registry()).find(row => row.workspaceId === workspaceId);
3254
+ const config = await readDeviceConfig();
3255
+ if (!item || !config || item.organizationId !== config.organizationId)
3256
+ throw new Error('named_session_scope_mismatch');
3257
+ const scope = repositoryRoleScope(item);
3258
+ const existing = await readNamedSession(dharmaHome(), name);
3259
+ if (existing && (existing.identity.workspaceId !== item.workspaceId
3260
+ || existing.identity.organizationId !== config.organizationId || existing.identity.deviceId !== config.deviceId
3261
+ || existing.identity.endpointId !== scope.endpointId || existing.identity.repositoryBindingId !== scope.repositoryBindingId)) {
3262
+ throw new Error('named_session_scope_mismatch');
3263
+ }
3264
+ if (action === 'status') {
3265
+ try {
3266
+ return await namedSessionRequest(dharmaHome(), name, { action: 'status' });
3267
+ }
3268
+ catch {
3269
+ return { ok: true, name, state: 'stopped', registered: Boolean(existing), enabled: existing?.enabled ?? false,
3270
+ bindingId: existing?.bindingId ?? null };
3271
+ }
3272
+ }
3273
+ if (action === 'work')
3274
+ return namedSessionRequest(dharmaHome(), name, { action: 'work',
3275
+ workId: String(flags.get('work-id') || randomUUID()), prompt: required(flags, 'prompt') });
3276
+ if (action === 'stop') {
3277
+ if (!flags.has('apply'))
3278
+ return { ok: true, planned: true, name, action: 'stop' };
3279
+ if (existing)
3280
+ await saveNamedSession(dharmaHome(), { ...existing, enabled: false });
3281
+ try {
3282
+ return await namedSessionRequest(dharmaHome(), name, { action: 'stop' });
3283
+ }
3284
+ catch {
3285
+ return { ok: true, name, state: 'stopped', enabled: false };
3286
+ }
3287
+ }
3288
+ if (!['start', 'serve'].includes(action))
3289
+ throw new Error('named_session_action_invalid');
3290
+ if (!flags.has('apply'))
3291
+ return { ok: true, planned: true, name, workspaceId,
3292
+ provider: 'codex', localWork: 'workspace_write', peerQuestions: 'read_only', network: 'deny' };
3293
+ if (!await repositorySharedReady(item))
3294
+ throw new Error('named_session_repository_package_pending');
3295
+ if (action === 'start') {
3296
+ if (existing && !existing.enabled)
3297
+ await saveNamedSession(dharmaHome(), { ...existing, enabled: true });
3298
+ try {
3299
+ return await namedSessionRequest(dharmaHome(), name, { action: 'status' });
3300
+ }
3301
+ catch { /* Start an owned worker below. */ }
3302
+ const args = [fileURLToPath(import.meta.url), 'sessions', 'serve', '--name', name,
3303
+ '--workspace-id', workspaceId, '--apply'];
3304
+ for (const key of ['session-budget-cents', 'turn-budget-cents']) {
3305
+ if (flags.has(key))
3306
+ args.push(`--${key}`, required(flags, key));
3307
+ }
3308
+ const child = spawn(process.execPath, args, { cwd: item.path, detached: true, stdio: 'ignore', env: process.env });
3309
+ child.on('error', () => { });
3310
+ child.unref();
3311
+ for (let attempt = 0; attempt < 80; attempt++) {
3312
+ await new Promise(resolveWait => setTimeout(resolveWait, 250));
3313
+ try {
3314
+ return await namedSessionRequest(dharmaHome(), name, { action: 'status' });
3315
+ }
3316
+ catch { /* Await service initialization. */ }
3317
+ }
3318
+ throw new Error('named_session_startup_failed');
3319
+ }
3320
+ await mkdir(paths.root, { recursive: true, mode: 0o700 });
3321
+ const releaseLock = await acquirePidLock(resolve(paths.root, 'service.lock'), 250, 'named_session_already_running');
3322
+ const { LocalVault, loadOrCreateVaultMasterKey } = await loadVaultModule();
3323
+ const vault = await LocalVault.open({ root: resolve(dharmaHome(), 'vault'), masterKey: await loadOrCreateVaultMasterKey() });
3324
+ const controller = new AbortController(), stop = () => controller.abort();
3325
+ process.once('SIGTERM', stop);
3326
+ process.once('SIGINT', stop);
3327
+ let transport;
3328
+ try {
3329
+ const fabric = await AgentFabricClient.open({ configPath: configPath(), statePath: resolve(paths.root, 'protocol-state.json') });
3330
+ if (fabric.config.organizationId !== config.organizationId || fabric.config.deviceId !== config.deviceId
3331
+ || fabric.config.publicKeyEd25519 !== config.publicKeyEd25519)
3332
+ throw new Error('named_session_trust_scope_mismatch');
3333
+ const trust = createNamedSessionTrust({ configPath: configPath(), identity: fabric.config });
3334
+ await trust.refresh();
3335
+ await fabric.openSession(VERSION);
3336
+ const policyPath = resolve(item.path, '.dharma', 'approved-policy.json');
3337
+ const policy = await refreshVerifiedWorkspacePolicyForTransmission(policyPath, workspaceId, fabric);
3338
+ const writeRoots = policy.tasks.writePaths.filter(path => /^[a-zA-Z0-9_-]+\/\*\*$/.test(path))
3339
+ .map(path => path.slice(0, -3));
3340
+ if (!writeRoots.length)
3341
+ throw new Error('named_session_workspace_write_not_authorized');
3342
+ const workRoots = ['"."="read"', ...writeRoots.map(root => `${JSON.stringify(root)}="write"`)].join(',');
3343
+ transport = await openCodexAppServerTransport({ command: 'codex', cwd: item.path,
3344
+ environment: providerProcessEnvironment(process.env), experimentalApi: true,
3345
+ argv: ['-c', 'default_permissions="dharma_bridge"',
3346
+ '-c', 'permissions.dharma_bridge.filesystem={":minimal"="read",":workspace_roots"={"."="read"}}',
3347
+ '-c', 'permissions.dharma_bridge.network={enabled=false}',
3348
+ '-c', `permissions.dharma_work.filesystem={":minimal"="read",":workspace_roots"={${workRoots}}}`,
3349
+ '-c', 'permissions.dharma_work.network={enabled=false}', 'app-server'] });
3350
+ const account = await transport.request('account/read', { refreshToken: false });
3351
+ if (!account.account)
3352
+ throw new Error('named_session_provider_authentication_required');
3353
+ let registration = existing;
3354
+ if (!registration) {
3355
+ const maximumCostCents = boundedInteger(flags.get('session-budget-cents'), 1000, 1, 10000, '--session-budget-cents');
3356
+ const maximumTurnCostCents = boundedInteger(flags.get('turn-budget-cents'), 25, 1, maximumCostCents, '--turn-budget-cents');
3357
+ const threadId = await startNamedCodexThread(transport, item.path, name);
3358
+ const bindingId = randomUUID();
3359
+ const response = await fabric.signedPost('/agent-fabric/provider-sessions', {
3360
+ schema: 'dharma.provider-session-registration/v1', action: 'attach', provider: 'codex', mode: 'bridge_owned',
3361
+ workspaceId, endpointId: scope.endpointId, repositoryBindingId: scope.repositoryBindingId,
3362
+ bindingId, expectedRevision: 0, leaseSeconds: 60,
3363
+ });
3364
+ const remote = response.registration;
3365
+ if (response.ok !== true || response.organizationId !== config.organizationId || !remote
3366
+ || remote.bindingId !== bindingId || remote.workspaceId !== workspaceId || remote.endpointId !== scope.endpointId
3367
+ || remote.repositoryBindingId !== scope.repositoryBindingId || remote.deviceId !== config.deviceId
3368
+ || !UUID_PATTERN.test(String(remote.membershipId)) || remote.provider !== 'codex'
3369
+ || remote.mode !== 'bridge_owned' || remote.state !== 'attached' || remote.revision !== 1) {
3370
+ throw new Error('named_session_registration_invalid');
3371
+ }
3372
+ const identity = { organizationId: config.organizationId, repositoryBindingId: scope.repositoryBindingId,
3373
+ workspaceId, endpointId: scope.endpointId, membershipId: String(remote.membershipId), deviceId: config.deviceId,
3374
+ provider: 'codex' };
3375
+ vault.saveProviderSessionBinding({ schema: 'dharma.local-provider-session-binding/v1', ...identity,
3376
+ bindingId, owner: 'dharma_bridge', sessionId: threadId, workspaceRoot: item.path,
3377
+ createdAt: new Date().toISOString(), expiresAt: new Date(Date.now() + 30 * 86400000).toISOString(),
3378
+ maximumProviderCostCents: maximumTurnCostCents });
3379
+ registration = { schema: 'dharma.named-session/v1', name, bindingId, identity,
3380
+ maximumCostCents, maximumTurnCostCents, enabled: true };
3381
+ await saveNamedSession(dharmaHome(), registration);
3382
+ }
3383
+ const authorizeLifetime = async () => {
3384
+ const current = await refreshVerifiedWorkspacePolicyForTransmission(policyPath, workspaceId, fabric);
3385
+ if (!writeRoots.every(root => current.tasks.writePaths.includes(`${root}/**`)))
3386
+ return false;
3387
+ const response = await fabric.signedPost('/agent-fabric/provider-sessions', {
3388
+ schema: 'dharma.provider-session-registration/v1', action: 'inspect', provider: 'codex', mode: 'bridge_owned',
3389
+ workspaceId, endpointId: scope.endpointId, repositoryBindingId: scope.repositoryBindingId,
3390
+ bindingId: registration.bindingId, expectedRevision: 0, leaseSeconds: 60,
3391
+ });
3392
+ return isNamedSessionOwnerReceipt(response, registration.bindingId, registration.identity);
3393
+ };
3394
+ const refreshLifetime = () => renewNamedSessionLifetime({ vault, bindingId: registration.bindingId,
3395
+ identity: registration.identity, trust, authorize: authorizeLifetime });
3396
+ await refreshLifetime();
3397
+ const channelTransport = { async signedPost(route, body) {
3398
+ await trust.refresh();
3399
+ const response = await fabric.signedPost(route, body);
3400
+ await trust.refresh();
3401
+ return response;
3402
+ } };
3403
+ // The durable reservation already suppresses duplicate provider execution.
3404
+ const consumed = new Set();
3405
+ return await runNamedSessionService({ home: dharmaHome(), registration, vault, signal: controller.signal,
3406
+ localWriteRoots: writeRoots,
3407
+ withActivationBoundary: operation => withWorkspaceSkillActivationLock(workspaceId, 'codex', async () => {
3408
+ await refreshLifetime();
3409
+ const skill = await verifyAgentFabricSkillInstallation({ provider: 'codex', workspace: item.path });
3410
+ if (!skill.ready || !await repositorySharedReady(item))
3411
+ throw new Error('named_session_repository_package_pending');
3412
+ return operation();
3413
+ }),
3414
+ authorizeLocalWork: async () => {
3415
+ const current = await refreshVerifiedWorkspacePolicyForTransmission(policyPath, workspaceId, fabric);
3416
+ return writeRoots.every(root => current.tasks.writePaths.includes(`${root}/**`));
3417
+ },
3418
+ openTransport: async () => transport, channelTransport,
3419
+ verifier: { resolvePublicKey: trust.resolvePublicKey, consume: async (id) => { if (consumed.has(id))
3420
+ return false; consumed.add(id); return true; } },
3421
+ authorizeContent: async (content) => {
3422
+ const current = await refreshVerifiedWorkspacePolicyForTransmission(policyPath, workspaceId, fabric);
3423
+ return current.evidence.automaticDisclosure?.mode === 'customer_authorized_content'
3424
+ && !containsDisallowedLocalPath(content)
3425
+ && canonicalize(redactValue(content, { classes: new Set(), redactedValues: 0,
3426
+ excludedPaths: 0, inputBytes: 0, outputBytes: 0 })) === canonicalize(content);
3427
+ } });
3428
+ }
3429
+ finally {
3430
+ try {
3431
+ if (transport)
3432
+ await transport.close();
3433
+ }
3434
+ finally {
3435
+ vault.close();
3436
+ await releaseLock();
3437
+ process.removeListener('SIGTERM', stop);
3438
+ process.removeListener('SIGINT', stop);
3439
+ }
3440
+ }
3441
+ }
3180
3442
  async function workspaceSync(flags, positional) {
3181
3443
  const workspaceId = positional[0] || required(flags, 'workspace-id');
3182
3444
  const policyRevision = required(flags, 'policy-revision');
@@ -4246,8 +4508,10 @@ async function finalizeRecoveredSignedTaskTrajectories(fabric, vaultPolicy, only
4246
4508
  }
4247
4509
  return finalized;
4248
4510
  }
4249
- async function executeOneTask(fabric, leaseSeconds) {
4250
- const polled = await fabric.pollTask(leaseSeconds);
4511
+ async function executeOneTask(fabric, leaseSeconds, exactStudyTask) {
4512
+ const polled = await fabric.pollTask(leaseSeconds, exactStudyTask?.taskId);
4513
+ if (exactStudyTask)
4514
+ assertStudyExactPoll(polled, exactStudyTask.taskId, exactStudyTask.workspaceId);
4251
4515
  const taskRow = polled.task;
4252
4516
  if (!taskRow?.envelope)
4253
4517
  return { ok: true, task: null };
@@ -4384,10 +4648,11 @@ async function runOneTask(flags) {
4384
4648
  && (!requestedWorkspaceId || item.workspaceId === requestedWorkspaceId)));
4385
4649
  if (!selectedWorkspace)
4386
4650
  throw new Error('Task policy must be the canonical policy of one registered workspace.');
4651
+ const studyTaskId = studyTaskIdFromFlags(flags, selectedWorkspace.workspaceId);
4387
4652
  const policy = await loadVerifiedWorkspacePolicy(policyPath, selectedWorkspace.workspaceId);
4388
4653
  const fabric = await client();
4389
- const recoveredTaskTrajectories = await finalizeRecoveredSignedTaskTrajectories(fabric, policy);
4390
- const result = await executeOneTask(fabric, Number(flags.get('lease-seconds') || 120));
4654
+ const recoveredTaskTrajectories = studyTaskId ? [] : await finalizeRecoveredSignedTaskTrajectories(fabric, policy);
4655
+ const result = await executeOneTask(fabric, Number(flags.get('lease-seconds') || 120), studyTaskId ? { taskId: studyTaskId, workspaceId: selectedWorkspace.workspaceId } : undefined);
4391
4656
  return recoveredTaskTrajectories.length ? { ...result, recoveredTaskTrajectories } : result;
4392
4657
  }
4393
4658
  export function nativeSkillDirectory(provider, env = process.env, home = homedir()) {
@@ -6064,6 +6329,8 @@ export async function run(argv) {
6064
6329
  }
6065
6330
  if (command === 'bootstrap')
6066
6331
  return bootstrap(flags);
6332
+ if (command === 'sessions')
6333
+ return namedSessionCommand(String(subcommand), flags);
6067
6334
  if (command === 'onboard')
6068
6335
  return onboard(flags);
6069
6336
  if (command === 'login')