@dharma-ai-labs/agent-fabric 0.2.190 → 0.2.191

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 (55) hide show
  1. package/AGENT_FABRIC_ONBOARDING.md +4 -2
  2. package/dist/cliVersionContract.test.d.ts +2 -0
  3. package/dist/cliVersionContract.test.d.ts.map +1 -0
  4. package/dist/cliVersionContract.test.js +17 -0
  5. package/dist/cliVersionContract.test.js.map +1 -0
  6. package/dist/codexSetupLaunchReservation.d.ts +12 -0
  7. package/dist/codexSetupLaunchReservation.d.ts.map +1 -0
  8. package/dist/codexSetupLaunchReservation.js +76 -0
  9. package/dist/codexSetupLaunchReservation.js.map +1 -0
  10. package/dist/codexSetupLaunchReservation.test.d.ts +2 -0
  11. package/dist/codexSetupLaunchReservation.test.d.ts.map +1 -0
  12. package/dist/codexSetupLaunchReservation.test.js +168 -0
  13. package/dist/codexSetupLaunchReservation.test.js.map +1 -0
  14. package/dist/codexSetupOwnedConnection.d.ts +4 -1
  15. package/dist/codexSetupOwnedConnection.d.ts.map +1 -1
  16. package/dist/codexSetupOwnedConnection.js +16 -3
  17. package/dist/codexSetupOwnedConnection.js.map +1 -1
  18. package/dist/codexSetupOwnedConnection.test.js +14 -0
  19. package/dist/codexSetupOwnedConnection.test.js.map +1 -1
  20. package/dist/codexSetupPortalIngress.test.d.ts +2 -0
  21. package/dist/codexSetupPortalIngress.test.d.ts.map +1 -0
  22. package/dist/codexSetupPortalIngress.test.js +355 -0
  23. package/dist/codexSetupPortalIngress.test.js.map +1 -0
  24. package/dist/codexSetupPortalPreparation.d.ts +28 -0
  25. package/dist/codexSetupPortalPreparation.d.ts.map +1 -0
  26. package/dist/codexSetupPortalPreparation.js +101 -0
  27. package/dist/codexSetupPortalPreparation.js.map +1 -0
  28. package/dist/codexSetupPortalPreparation.test.d.ts +2 -0
  29. package/dist/codexSetupPortalPreparation.test.d.ts.map +1 -0
  30. package/dist/codexSetupPortalPreparation.test.js +173 -0
  31. package/dist/codexSetupPortalPreparation.test.js.map +1 -0
  32. package/dist/codexSetupPublicStdin.d.ts +6 -0
  33. package/dist/codexSetupPublicStdin.d.ts.map +1 -0
  34. package/dist/codexSetupPublicStdin.js +36 -0
  35. package/dist/codexSetupPublicStdin.js.map +1 -0
  36. package/dist/codexSetupPublicStdin.test.d.ts +2 -0
  37. package/dist/codexSetupPublicStdin.test.d.ts.map +1 -0
  38. package/dist/codexSetupPublicStdin.test.js +40 -0
  39. package/dist/codexSetupPublicStdin.test.js.map +1 -0
  40. package/dist/codexSetupUserPrompt.d.ts +2 -0
  41. package/dist/codexSetupUserPrompt.d.ts.map +1 -1
  42. package/dist/codexSetupUserPrompt.js +13 -7
  43. package/dist/codexSetupUserPrompt.js.map +1 -1
  44. package/dist/index.d.ts +15 -0
  45. package/dist/index.d.ts.map +1 -1
  46. package/dist/index.js +187 -4
  47. package/dist/index.js.map +1 -1
  48. package/dist/onboardingContract.test.js +9 -0
  49. package/dist/onboardingContract.test.js.map +1 -1
  50. package/dist/schemas/setup-claim-request.schema.json +10 -0
  51. package/dist/schemas/setup-host-preparation.schema.json +51 -0
  52. package/dist/schemas/setup-launch-reservation.schema.json +29 -0
  53. package/dist/usage.js +1 -1
  54. package/dist/usage.js.map +1 -1
  55. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -93,6 +93,9 @@ import { assertBootstrapHostSource, captureBootstrapHostChild, currentBootstrapH
93
93
  import { startCodexSetupNativeHost } from './codexSetupNativeHost.js';
94
94
  import { openCodexSetupOwnedConnection } from './codexSetupOwnedConnection.js';
95
95
  import { captureCodexSetupUserPrompt, captureCodexSetupPromptBinding } from './codexSetupUserPrompt.js';
96
+ import { requestCodexSetupHostPreparation } from './codexSetupPortalPreparation.js';
97
+ import { readCodexSetupPublicStdin } from './codexSetupPublicStdin.js';
98
+ import { reserveCodexSetupLaunch } from './codexSetupLaunchReservation.js';
96
99
  export { startCodexSetupNativeHost } from './codexSetupNativeHost.js';
97
100
  export { openCodexSetupVaultJournal } from './codexSetupVaultJournal.js';
98
101
  import { createCodexSetupReadinessOwner } from './codexSetupReadiness.js';
@@ -101,7 +104,7 @@ import { assertCodexSetupExecutionLease } from './codexSetupAdmission.js';
101
104
  import { parseLocalCodexSetupReadiness } from '@dharma-ai-labs/agent-fabric-local-vault/setup-readiness';
102
105
  import { writeBootstrapHostJson, writeBootstrapHostText } from './bootstrapHostFiles.js';
103
106
  export { openCooperativeInboxSession } from './cooperativeInboxSession.js';
104
- const VERSION = '0.2.190';
107
+ const VERSION = '0.2.191';
105
108
  const USAGE = CLI_USAGE;
106
109
  const execFileAsync = promisify(execFile);
107
110
  const LOCAL_PROVIDER_IDS = ['codex', 'claude', 'agy', 'hermes'];
@@ -159,13 +162,17 @@ function print(value) {
159
162
  }
160
163
  export function commandExitCode(argv, value) {
161
164
  const { positional, flags } = parseCliOptions(argv);
162
- if (positional[0] !== 'bootstrap' || flags.has('help') || flags.has('version'))
165
+ const setupIngress = positional[0] === 'setup' && positional[1] === 'codex';
166
+ if ((!setupIngress && positional[0] !== 'bootstrap') || flags.has('help') || flags.has('version'))
163
167
  return 0;
164
168
  if (!value || typeof value !== 'object' || Array.isArray(value))
165
169
  return 1;
166
170
  const receipt = value;
167
171
  if (receipt.ok !== true)
168
172
  return 1;
173
+ if (setupIngress)
174
+ return flags.has('dry-run') && receipt.stage === 'setup_host_plan' && receipt.effects === false
175
+ ? 0 : receipt.stage === 'complete' && receipt.sharedRepositoryReady === true ? 0 : 1;
169
176
  if (flags.has('setup-reference') && flags.has('dry-run') && receipt.stage === 'plan' && receipt.effects === false)
170
177
  return 0;
171
178
  return flags.has('complete') && (receipt.stage !== 'complete' || receipt.sharedRepositoryReady !== true) ? 1 : 0;
@@ -2907,6 +2914,7 @@ export async function openCodexBootstrapNativeHost(input) {
2907
2914
  void settled.catch(() => { });
2908
2915
  return Object.freeze({ threadId: host.threadId, turnId: host.turnId, close, settled,
2909
2916
  initialUserPromptSha256: host.initialUserPromptSha256,
2917
+ get verifiedCompletion() { return ownedHost.verifiedCompletion; },
2910
2918
  get diagnostics() { return ownedHost.diagnostics; } });
2911
2919
  }
2912
2920
  catch {
@@ -3003,6 +3011,7 @@ export async function startCodexBootstrapNativeHost(input) {
3003
3011
  const root = resolve(dharmaHome(), 'vault');
3004
3012
  let completion;
3005
3013
  let ownerScope;
3014
+ let verifiedCompletion;
3006
3015
  const failures = [];
3007
3016
  const observeFailure = failure => { if (failures.length < 16)
3008
3017
  failures.push(failure); };
@@ -3040,11 +3049,19 @@ export async function startCodexBootstrapNativeHost(input) {
3040
3049
  return completion.execute(lease);
3041
3050
  },
3042
3051
  verifyReadiness: async (receiptId, intent, digest) => {
3052
+ verifiedCompletion = undefined;
3043
3053
  if (!completion)
3044
3054
  return false;
3045
- return completion.verify(receiptId, intent, digest);
3055
+ const verified = await completion.verify(receiptId, intent, digest);
3056
+ // This callback runs after the original admission's journal readback. A
3057
+ // completed native turn or another process's device config cannot set it.
3058
+ if (verified)
3059
+ verifiedCompletion = Object.freeze({ operationId: intent.operationId,
3060
+ setupReference: intent.setupReference, readinessReceiptId: receiptId });
3061
+ return verified;
3046
3062
  }, });
3047
- return Object.freeze({ ...host, get diagnostics() { return Object.freeze([...failures]); } });
3063
+ return Object.freeze({ ...host, get verifiedCompletion() { return verifiedCompletion; },
3064
+ get diagnostics() { return Object.freeze([...failures]); } });
3048
3065
  }
3049
3066
  async function assertBootstrapStartupAnchor(organizationId, deviceId, step) {
3050
3067
  const home = dharmaHome();
@@ -3080,6 +3097,166 @@ async function assertBootstrapStartupAnchor(organizationId, deviceId, step) {
3080
3097
  throw new Error('relay_runtime_upgrade_required: upgrade the existing startup anchor before connecting another repository.');
3081
3098
  }
3082
3099
  }
3100
+ /** Official public ingress. Only this local client, never the website or model,
3101
+ * owns the resulting native connection and its original setup execution lease. */
3102
+ async function codexPortalSetup(flags) {
3103
+ const allowed = new Set(['portal-url', 'organization-id', 'setup-reference', 'workspace',
3104
+ 'maximum-provider-cost-cents', 'dry-run', 'stdin']);
3105
+ if ([...flags.keys()].some(key => !allowed.has(key)) || flags.get('stdin') !== true
3106
+ || flags.has('dry-run') && flags.get('dry-run') !== true
3107
+ || flags.has('maximum-provider-cost-cents') && typeof flags.get('maximum-provider-cost-cents') !== 'string') {
3108
+ throw new Error('codex_setup_ingress_options_invalid');
3109
+ }
3110
+ const controller = new AbortController(), stop = () => controller.abort();
3111
+ for (const signal of ['SIGINT', 'SIGTERM'])
3112
+ process.once(signal, stop);
3113
+ let host;
3114
+ try {
3115
+ const origin = normalizeHqUrl(required(flags, 'portal-url'));
3116
+ const organizationId = required(flags, 'organization-id'), setupReference = required(flags, 'setup-reference');
3117
+ const prompt = await readCodexSetupPublicStdin(process.stdin, controller.signal);
3118
+ const prepared = await requestCodexSetupHostPreparation({ origin, organizationId, setupReference,
3119
+ cliVersion: VERSION, initialUserPrompt: prompt, signal: controller.signal });
3120
+ if (flags.has('dry-run'))
3121
+ return { ok: true, stage: 'setup_host_plan', effects: false,
3122
+ enrollmentChanged: false, requiresHostExecutionAdmission: true, requiresRecipientApproval: true,
3123
+ promptSha256: prepared.promptSha256, initialUserPromptSha256: prepared.initialUserPromptSha256,
3124
+ portalContextSha256: prepared.portalContextSha256,
3125
+ organizationId, setupReference, recipientMembershipId: prepared.intent.recipientMembershipId,
3126
+ repositoryFingerprint: prepared.intent.repositoryFingerprint, expiresAt: prepared.intent.expiresAt };
3127
+ if (process.platform !== 'linux' || !process.getuid?.() || !process.env.DHARMA_HOME || !process.env.CODEX_HOME) {
3128
+ throw new Error('codex_setup_ingress_context_unqualified');
3129
+ }
3130
+ const workspace = await realpath(String(flags.get('workspace') || '.'));
3131
+ const home = dharmaHome(), providerHome = process.env.CODEX_HOME, uid = process.getuid();
3132
+ const environment = { home: process.env.DHARMA_HOME, provider: providerHome };
3133
+ const currentDirectory = process.cwd(), file = fileURLToPath(import.meta.url), runtime = await lstat(file, { bigint: true });
3134
+ const cents = boundedInteger(flags.get('maximum-provider-cost-cents'), 25, 1, 25, '--maximum-provider-cost-cents');
3135
+ const privateDirectory = async (path, privateMode) => {
3136
+ if (!isAbsolute(path) || resolve(path) !== path || !path.startsWith(resolve(homedir()) + '/'))
3137
+ return false;
3138
+ const info = await lstat(path);
3139
+ return info.isDirectory() && !info.isSymbolicLink() && info.uid === uid
3140
+ && (info.mode & (privateMode ? 0o077 : 0o022)) === 0 && await realpath(path) === path;
3141
+ };
3142
+ const current = async () => {
3143
+ if (controller.signal.aborted || Date.now() >= Date.parse(prepared.intent.expiresAt)
3144
+ || process.getuid?.() !== uid || process.cwd() !== currentDirectory
3145
+ || process.env.DHARMA_HOME !== environment.home || process.env.CODEX_HOME !== environment.provider)
3146
+ return false;
3147
+ try {
3148
+ const now = await lstat(file, { bigint: true });
3149
+ return now.isFile() && !now.isSymbolicLink() && now.dev === runtime.dev && now.ino === runtime.ino
3150
+ && now.size === runtime.size && now.mtimeNs === runtime.mtimeNs && now.ctimeNs === runtime.ctimeNs
3151
+ && await privateDirectory(home, true) && await privateDirectory(providerHome, false)
3152
+ && (await preflightBootstrapWorkspaceIdentity(workspace)).fingerprint === prepared.intent.repositoryFingerprint;
3153
+ }
3154
+ catch {
3155
+ return false;
3156
+ }
3157
+ };
3158
+ if (!await current() || await detectBootstrapProvider(workspace) !== 'codex') {
3159
+ throw new Error('codex_setup_ingress_context_unqualified');
3160
+ }
3161
+ // Read only public account identity through the official owned provider.
3162
+ // It is not the portal recipient, a credential copy or a nested login shell.
3163
+ const account = await openCodexSetupOwnedConnection({ workspace, deviceHome: home,
3164
+ accountSelection: 'current_authenticated', signal: controller.signal, current });
3165
+ const expectedAccountEmail = account.accountEmail;
3166
+ await account.close();
3167
+ if (!await current())
3168
+ throw new Error('codex_setup_ingress_context_unqualified');
3169
+ const portalArgs = ['bootstrap', '--portal-url', origin, '--organization-id', organizationId,
3170
+ '--setup-reference', setupReference, '--setup-recipient-membership-id', prepared.intent.recipientMembershipId,
3171
+ '--setup-scope-digest', prepared.intent.scopeDigest, '--setup-contract-digest', prepared.intent.contractDigest,
3172
+ '--workspace', workspace, '--repository-url-base64url',
3173
+ Buffer.from(prepared.binding.repositoryRemoteUrl).toString('base64url'),
3174
+ '--policy-revision', prepared.intent.policyRevision, '--complete'];
3175
+ host = await openCodexBootstrapFromPortalCommand({ workspace, name: 'setup-' + setupReference.slice(0, 8),
3176
+ intent: prepared.intent, portalArgs, initialUserPrompt: prepared.initialUserPrompt,
3177
+ initialUserPromptBinding: prepared.binding, expectedAccountEmail, signal: controller.signal,
3178
+ current, maximumProviderCostCents: cents,
3179
+ reserve: async (operationId, requested) => {
3180
+ if (operationId !== prepared.intent.operationId || requested !== cents || !await current())
3181
+ return false;
3182
+ // Recheck the unchanged source consent immediately before the native
3183
+ // turn reservation. This is still not browser approval or a policy lock.
3184
+ const rechecked = await requestCodexSetupHostPreparation({ origin, organizationId, setupReference,
3185
+ cliVersion: VERSION, initialUserPrompt: prompt, signal: controller.signal });
3186
+ if (rechecked.promptSha256 !== prepared.promptSha256
3187
+ || canonicalize(rechecked.binding) !== canonicalize(prepared.binding)
3188
+ || canonicalize({ ...rechecked.intent, operationId: prepared.intent.operationId,
3189
+ hostContextId: prepared.intent.hostContextId, issuedAt: prepared.intent.issuedAt }) !== canonicalize(prepared.intent))
3190
+ return false;
3191
+ return reserveCodexSetupLaunch({ home, organizationId, setupReference, operationId,
3192
+ promptSha256: prepared.promptSha256, cents, current });
3193
+ } });
3194
+ await host.settled;
3195
+ await host.close();
3196
+ if (!await current())
3197
+ throw new Error('codex_setup_ingress_context_unqualified');
3198
+ const completed = host.verifiedCompletion;
3199
+ if (!completed || completed.operationId !== prepared.intent.operationId
3200
+ || completed.setupReference !== setupReference || !UUID_PATTERN.test(completed.readinessReceiptId)) {
3201
+ return { ok: false, stage: 'setup_host_unconfirmed', code: 'codex_setup_ingress_readiness_unconfirmed',
3202
+ nativeThreadId: host.threadId, nativeTurnId: host.turnId };
3203
+ }
3204
+ const config = await readDeviceConfig();
3205
+ if (!config || config.organizationId !== organizationId || normalizeHqUrl(config.hqUrl) !== origin
3206
+ || config.setupClaimReference !== setupReference
3207
+ || config.setupClaimRepositoryFingerprint !== prepared.intent.repositoryFingerprint) {
3208
+ return { ok: false, stage: 'setup_host_unconfirmed', effects: 'unconfirmed',
3209
+ code: 'codex_setup_ingress_readiness_unconfirmed', setupReference,
3210
+ nativeThreadId: host.threadId, nativeTurnId: host.turnId };
3211
+ }
3212
+ // One supported grant-free reconciliation, only for this consumed reference.
3213
+ // No replacement identity, grant replay or automatic provider-turn retry.
3214
+ const resume = parseCliOptions(portalArgs).flags;
3215
+ for (const key of ['setup-reference', 'setup-recipient-membership-id', 'setup-scope-digest', 'setup-contract-digest'])
3216
+ resume.delete(key);
3217
+ resume.set('resume', true);
3218
+ if (!await current())
3219
+ throw new Error('codex_setup_ingress_context_unqualified');
3220
+ // The owned native host is closed. This is now ordinary enrolled-device
3221
+ // reconciliation: restore Node's normal termination instead of suppressing
3222
+ // SIGINT/SIGTERM with a controller that bootstrap does not consume.
3223
+ for (const signal of ['SIGINT', 'SIGTERM'])
3224
+ process.removeListener(signal, stop);
3225
+ if (controller.signal.aborted)
3226
+ throw new Error('codex_setup_ingress_context_unqualified');
3227
+ const result = await bootstrap(resume);
3228
+ if (!result || typeof result !== 'object' || Array.isArray(result)) {
3229
+ return { ok: false, stage: 'setup_host_unconfirmed', code: 'codex_setup_ingress_readiness_unconfirmed' };
3230
+ }
3231
+ return { ...result, portalIngress: { promptSha256: prepared.promptSha256,
3232
+ portalContextSha256: prepared.portalContextSha256,
3233
+ initialUserPromptSha256: prepared.initialUserPromptSha256, operationId: prepared.intent.operationId,
3234
+ readinessReceiptId: completed.readinessReceiptId,
3235
+ nativeThreadId: host.threadId, nativeTurnId: host.turnId,
3236
+ reservedProviderCostCents: cents, actualUsd: null, liability: 'UNSETTLED_NOT_RELEASED',
3237
+ hardInvoiceCapProven: false } };
3238
+ }
3239
+ catch (error) {
3240
+ const known = new Set(['codex_setup_ingress_options_invalid', 'codex_setup_public_input_unavailable',
3241
+ 'codex_setup_public_prompt_invalid', 'codex_setup_portal_preparation_unavailable',
3242
+ 'codex_setup_ingress_context_unqualified', 'codex_setup_owned_start_unqualified',
3243
+ 'codex_setup_owned_close_unconfirmed', 'codex_setup_owned_completion_unconfirmed']);
3244
+ return { ok: false, stage: 'setup_host_unconfirmed',
3245
+ code: error instanceof Error && known.has(error.message) ? error.message : 'codex_setup_ingress_unconfirmed',
3246
+ requiresSupportedRecovery: true };
3247
+ }
3248
+ finally {
3249
+ controller.abort();
3250
+ for (const signal of ['SIGINT', 'SIGTERM'])
3251
+ process.removeListener(signal, stop);
3252
+ try {
3253
+ await host?.close();
3254
+ }
3255
+ catch {
3256
+ return { ok: false, stage: 'setup_host_unconfirmed', code: 'codex_setup_owned_close_unconfirmed' };
3257
+ }
3258
+ }
3259
+ }
3083
3260
  async function bootstrap(flags, hostScope) {
3084
3261
  const step = (operation) => hostScope ? hostScope.step(operation) : operation();
3085
3262
  await hostScope?.assert();
@@ -8795,6 +8972,12 @@ export async function run(argv) {
8795
8972
  await originalCodexSetupSessionSender(setupScope);
8796
8973
  return setupScope.step(() => bootstrap(flags, setupScope));
8797
8974
  }
8975
+ if (command === 'setup' && subcommand === 'codex') {
8976
+ if (positional.length !== 2 || [...repeated.values()].some(values => values.length !== 1)) {
8977
+ throw new Error('codex_setup_ingress_options_invalid');
8978
+ }
8979
+ return codexPortalSetup(flags);
8980
+ }
8798
8981
  if (command === 'relay' && subcommand === 'container-entrypoint') {
8799
8982
  if (positional.length === 2 && flags.size === 1 && flags.get('dry-run') === true) {
8800
8983
  return { ok: true, stage: 'container_entrypoint_plan', started: false,