@dharma-ai-labs/agent-fabric 0.2.79 → 0.2.81

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -33,7 +33,8 @@ import { askRepositoryRoleQuestion, readRepositoryRoleReply } from './repository
33
33
  import { deriveRepositoryRole } from './repositoryRoleDerivation.js';
34
34
  import { withOnboardingStage } from './onboardingStage.js';
35
35
  import { waitForRepositoryReadiness } from './repositoryReadinessWait.js';
36
- const VERSION = '0.2.79';
36
+ import { connectDemoDevice, verifyDemoDevice } from './demoEnrollment.js';
37
+ const VERSION = '0.2.81';
37
38
  const USAGE = CLI_USAGE;
38
39
  const execFileAsync = promisify(execFile);
39
40
  const LOCAL_PROVIDER_IDS = ['codex', 'claude', 'agy', 'hermes'];
@@ -252,12 +253,13 @@ export function rawLocalRetentionDays(policy) {
252
253
  }
253
254
  return Number(value);
254
255
  }
255
- async function syncPendingRetentionCapsules(vault, fabric, policy, workspaceId) {
256
+ export async function syncPendingRetentionCapsules(vault, fabric, policy, workspaceId) {
256
257
  // The caller must obtain this policy from an authoritative control-plane
257
258
  // refresh. An expired policy pauses the queue instead of mutating it.
258
259
  assertPolicy(policy);
259
260
  let synced = 0;
260
261
  let offset = 0;
262
+ let warnedPreviousDevice = false;
261
263
  for (;;) {
262
264
  const pending = await vault.listPendingCapsuleSyncs(1_000, offset);
263
265
  if (pending.length === 0)
@@ -274,6 +276,15 @@ async function syncPendingRetentionCapsules(vault, fabric, policy, workspaceId)
274
276
  vault.discardPendingCapsuleSync(item.trajectoryId, item.revision, 'capsule_integrity_failed');
275
277
  continue;
276
278
  }
279
+ if (item.capsule.organizationId === fabric.config.organizationId
280
+ && item.capsule.deviceId !== fabric.config.deviceId) {
281
+ vault.discardPendingCapsuleSync(item.trajectoryId, item.revision, 'device_binding_changed');
282
+ if (!warnedPreviousDevice) {
283
+ process.stderr.write('Queued trajectory capsules from a previous device were preserved locally and excluded from upload. Recapture eligible provider sessions under the current device.\n');
284
+ warnedPreviousDevice = true;
285
+ }
286
+ continue;
287
+ }
277
288
  // Policy expiry and signature failure pause the queue. Only a capsule
278
289
  // mismatch under a still-current policy is an authoritative discard.
279
290
  assertPolicy(policy);
@@ -2204,6 +2215,10 @@ async function capture(flags, batch = false) {
2204
2215
  const fabric = flags.has('sync')
2205
2216
  ? await client()
2206
2217
  : null;
2218
+ if (fabric && (fabric.config.organizationId !== device.organizationId
2219
+ || fabric.config.deviceId !== device.deviceId)) {
2220
+ throw new Error('The enrolled device changed during evidence capture. Retry from the current device home.');
2221
+ }
2207
2222
  if (fabric)
2208
2223
  policy = await refreshVerifiedWorkspacePolicyForTransmission(policyPath, registered.workspaceId, fabric);
2209
2224
  const vault = await LocalVault.open({
@@ -5361,6 +5376,42 @@ export async function run(argv) {
5361
5376
  return USAGE;
5362
5377
  if (flags.has('version') || command === 'version')
5363
5378
  return { version: VERSION };
5379
+ if (command === 'demo' && ['connect', 'status'].includes(String(subcommand))) {
5380
+ const hqUrl = normalizeHqUrl(portalUrl(flags));
5381
+ const organizationId = required(flags, 'organization-id');
5382
+ const repositoryId = required(flags, 'repository-id');
5383
+ const normalizedRepository = required(flags, 'normalized-repository');
5384
+ const workspace = await realpath(String(flags.get('workspace') || '.'));
5385
+ const root = await gitValue(workspace, ['rev-parse', '--show-toplevel']);
5386
+ if (!root)
5387
+ throw new Error('Demo device setup must run inside the exact Git repository.');
5388
+ const remote = await gitValue(workspace, ['config', '--get', 'remote.origin.url']);
5389
+ if (!remote || normalizeGitRemoteIdentity(remote) !== normalizedRepository) {
5390
+ throw new Error('Local Git remote does not match the private Demo repository binding.');
5391
+ }
5392
+ if (flags.has('dry-run'))
5393
+ return { ok: true, stage: 'demo_device_plan',
5394
+ organizationId, repositoryId, normalizedRepository, workspaceVerified: true,
5395
+ repositoryPackageState: 'not_connected' };
5396
+ const grant = subcommand === 'connect' ? required(flags, 'grant') : null;
5397
+ const scope = {
5398
+ hqUrl, organizationId, repositoryId, normalizedRepository,
5399
+ installationId: await loadOrCreateInstallationId(), stateRoot: dharmaHome(),
5400
+ };
5401
+ const connected = subcommand === 'status'
5402
+ ? await verifyDemoDevice(scope)
5403
+ : await connectDemoDevice({
5404
+ ...scope, grant: grant,
5405
+ deviceName: String(flags.get('device-name') || `${process.env.USER || process.env.USERNAME || 'developer'} device`),
5406
+ platform: await platform(),
5407
+ }, { onApprovalRequired: async (url) => {
5408
+ if (!flags.has('no-browser'))
5409
+ await openVerificationUri(url);
5410
+ process.stderr.write(`Approve this exact Demo device in your signed-in Dharma session: ${url}\n`);
5411
+ } });
5412
+ const { configPath: _configPath, ...receipt } = connected;
5413
+ return receipt;
5414
+ }
5364
5415
  if (command === 'bootstrap')
5365
5416
  return bootstrap(flags);
5366
5417
  if (command === 'onboard')