lody 0.67.3 → 0.68.0

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
@@ -4188,7 +4188,7 @@ let __tla = Promise.all([
4188
4188
  }
4189
4189
  }
4190
4190
  const name$1 = "lody";
4191
- const version$3 = "0.67.3";
4191
+ const version$3 = "0.68.0";
4192
4192
  const description$1 = "Lody Agent CLI tool for managing remote command execution";
4193
4193
  const type$2 = "module";
4194
4194
  const main$4 = "dist/index.js";
@@ -4534,6 +4534,274 @@ let __tla = Promise.all([
4534
4534
  return void 0;
4535
4535
  };
4536
4536
  const isBuiltinAgentType = (agentType) => agentType === "claude" || agentType === "codex";
4537
+ const CODEX_STATIC_MODES = [
4538
+ {
4539
+ id: "read-only",
4540
+ name: "Read-only",
4541
+ description: "Requires approval to edit files and run commands."
4542
+ },
4543
+ {
4544
+ id: "agent",
4545
+ name: "Agent",
4546
+ description: "Read and edit files, and run commands."
4547
+ },
4548
+ {
4549
+ id: "agent-full-access",
4550
+ name: "Agent (full access)",
4551
+ description: "Codex can edit files outside this workspace and run commands with network access. Exercise caution when using."
4552
+ }
4553
+ ];
4554
+ const CODEX_STATIC_MODELS = [
4555
+ {
4556
+ modelId: "gpt-5.5",
4557
+ name: "gpt-5.5",
4558
+ description: "Latest frontier Codex model"
4559
+ },
4560
+ {
4561
+ modelId: "gpt-5.4",
4562
+ name: "gpt-5.4",
4563
+ description: "Frontier Codex model"
4564
+ },
4565
+ {
4566
+ modelId: "gpt-5.4-mini",
4567
+ name: "gpt-5.4-mini",
4568
+ description: "Smaller, faster Codex model"
4569
+ }
4570
+ ];
4571
+ const CODEX_REASONING_OPTIONS = [
4572
+ {
4573
+ value: "low",
4574
+ name: "low",
4575
+ description: "Fastest responses"
4576
+ },
4577
+ {
4578
+ value: "medium",
4579
+ name: "medium",
4580
+ description: "Balanced reasoning"
4581
+ },
4582
+ {
4583
+ value: "high",
4584
+ name: "high",
4585
+ description: "More reasoning for difficult tasks"
4586
+ },
4587
+ {
4588
+ value: "xhigh",
4589
+ name: "xhigh",
4590
+ description: "Extra reasoning for complex tasks"
4591
+ }
4592
+ ];
4593
+ const CODEX_STATIC_CONFIG_OPTIONS = [
4594
+ {
4595
+ id: "mode",
4596
+ name: "Mode",
4597
+ description: "Approval and sandboxing preset for the session",
4598
+ category: "mode",
4599
+ type: "select",
4600
+ currentValue: "agent",
4601
+ options: CODEX_STATIC_MODES.map((mode2) => ({
4602
+ value: mode2.id,
4603
+ name: mode2.name,
4604
+ description: mode2.description ?? void 0
4605
+ }))
4606
+ },
4607
+ {
4608
+ id: "model",
4609
+ name: "Model",
4610
+ description: "Model Codex uses for the session",
4611
+ category: "model",
4612
+ type: "select",
4613
+ currentValue: "gpt-5.5",
4614
+ options: CODEX_STATIC_MODELS.map((model) => ({
4615
+ value: model.modelId,
4616
+ name: model.name,
4617
+ description: model.description ?? void 0
4618
+ }))
4619
+ },
4620
+ {
4621
+ id: "reasoning_effort",
4622
+ name: "Reasoning effort",
4623
+ description: "How much reasoning effort the model should use",
4624
+ category: "thought_level",
4625
+ type: "select",
4626
+ currentValue: "medium",
4627
+ options: CODEX_REASONING_OPTIONS
4628
+ },
4629
+ {
4630
+ id: "fast-mode",
4631
+ name: "Fast mode",
4632
+ description: "1.5x speed, increased usage",
4633
+ category: "fast-mode",
4634
+ type: "select",
4635
+ currentValue: "off",
4636
+ options: [
4637
+ {
4638
+ value: "off",
4639
+ name: "Off",
4640
+ description: "Default speed, normal usage"
4641
+ },
4642
+ {
4643
+ value: "on",
4644
+ name: "On",
4645
+ description: "1.5x speed, increased usage"
4646
+ }
4647
+ ]
4648
+ },
4649
+ {
4650
+ id: "plan-mode",
4651
+ name: "Plan mode",
4652
+ description: "Plan without modifying files; switch off to implement the approved plan",
4653
+ category: "plan-mode",
4654
+ type: "select",
4655
+ currentValue: "off",
4656
+ options: [
4657
+ {
4658
+ value: "off",
4659
+ name: "Off",
4660
+ description: "Implement changes normally"
4661
+ },
4662
+ {
4663
+ value: "on",
4664
+ name: "On",
4665
+ description: "Plan without modifying files; switch off to implement the approved plan"
4666
+ }
4667
+ ]
4668
+ }
4669
+ ];
4670
+ const CLAUDE_STATIC_MODES = [
4671
+ {
4672
+ id: "auto",
4673
+ name: "Auto",
4674
+ description: "Use a model classifier to approve/deny permission prompts"
4675
+ },
4676
+ {
4677
+ id: "default",
4678
+ name: "Default",
4679
+ description: "Standard behavior, prompts for dangerous operations"
4680
+ },
4681
+ {
4682
+ id: "acceptEdits",
4683
+ name: "Accept Edits",
4684
+ description: "Auto-accept file edit operations"
4685
+ },
4686
+ {
4687
+ id: "plan",
4688
+ name: "Plan Mode",
4689
+ description: "Planning mode, no actual tool execution"
4690
+ },
4691
+ {
4692
+ id: "dontAsk",
4693
+ name: "Don't Ask",
4694
+ description: "Don't prompt for permissions, deny if not pre-approved"
4695
+ }
4696
+ ];
4697
+ const CLAUDE_STATIC_MODELS = [
4698
+ {
4699
+ modelId: "default",
4700
+ name: "Default",
4701
+ description: "Claude Code default model"
4702
+ },
4703
+ {
4704
+ modelId: "opus",
4705
+ name: "Opus",
4706
+ description: "Claude Opus"
4707
+ },
4708
+ {
4709
+ modelId: "sonnet",
4710
+ name: "Sonnet",
4711
+ description: "Claude Sonnet"
4712
+ },
4713
+ {
4714
+ modelId: "haiku",
4715
+ name: "Haiku",
4716
+ description: "Claude Haiku"
4717
+ }
4718
+ ];
4719
+ const CLAUDE_STATIC_CONFIG_OPTIONS = [
4720
+ {
4721
+ id: "mode",
4722
+ name: "Mode",
4723
+ description: "Session permission mode",
4724
+ category: "mode",
4725
+ type: "select",
4726
+ currentValue: "default",
4727
+ options: CLAUDE_STATIC_MODES.map((mode2) => ({
4728
+ value: mode2.id,
4729
+ name: mode2.name,
4730
+ description: mode2.description ?? void 0
4731
+ }))
4732
+ },
4733
+ {
4734
+ id: "model",
4735
+ name: "Model",
4736
+ description: "AI model to use",
4737
+ category: "model",
4738
+ type: "select",
4739
+ currentValue: "default",
4740
+ options: CLAUDE_STATIC_MODELS.map((model) => ({
4741
+ value: model.modelId,
4742
+ name: model.name,
4743
+ description: model.description ?? void 0
4744
+ }))
4745
+ },
4746
+ {
4747
+ id: "effort",
4748
+ name: "Effort",
4749
+ description: "Available effort levels for this model",
4750
+ category: "thought_level",
4751
+ type: "select",
4752
+ currentValue: "default",
4753
+ options: [
4754
+ {
4755
+ value: "default",
4756
+ name: "Default"
4757
+ },
4758
+ {
4759
+ value: "low",
4760
+ name: "Low"
4761
+ },
4762
+ {
4763
+ value: "medium",
4764
+ name: "Medium"
4765
+ },
4766
+ {
4767
+ value: "high",
4768
+ name: "High"
4769
+ }
4770
+ ]
4771
+ }
4772
+ ];
4773
+ const cloneConfigOption = (option2) => ({
4774
+ ...option2,
4775
+ options: option2.options.map((value) => ({
4776
+ ...value
4777
+ }))
4778
+ });
4779
+ const cloneStaticCapabilities = (capabilities) => ({
4780
+ modes: capabilities.modes.map((mode2) => ({
4781
+ ...mode2
4782
+ })),
4783
+ models: capabilities.models.map((model) => ({
4784
+ ...model
4785
+ })),
4786
+ configOptions: capabilities.configOptions.map(cloneConfigOption)
4787
+ });
4788
+ const getStaticBuiltinAcpCapabilities = (cliType, agentType, runtimeOverrides) => {
4789
+ if (cliType !== "builtin" || !agentType || !isBuiltinAgentType(agentType)) {
4790
+ return void 0;
4791
+ }
4792
+ if (hasBuiltinRuntimeOverrideValues(runtimeOverrides)) {
4793
+ return void 0;
4794
+ }
4795
+ return cloneStaticCapabilities(agentType === "claude" ? {
4796
+ modes: CLAUDE_STATIC_MODES,
4797
+ models: CLAUDE_STATIC_MODELS,
4798
+ configOptions: CLAUDE_STATIC_CONFIG_OPTIONS
4799
+ } : {
4800
+ modes: CODEX_STATIC_MODES,
4801
+ models: CODEX_STATIC_MODELS,
4802
+ configOptions: CODEX_STATIC_CONFIG_OPTIONS
4803
+ });
4804
+ };
4537
4805
  const leastPermissionModeRank = (value, name2) => {
4538
4806
  const normalized = `${value} ${name2}`.toLowerCase().replace(/[\s_-]+/g, "-");
4539
4807
  if (normalized.includes("read-only") || normalized.includes("readonly")) return 0;
@@ -78955,6 +79223,252 @@ ${this.stack.split("\n").slice(1).join("\n")}` : this.toString();
78955
79223
  }
78956
79224
  }
78957
79225
  }
79226
+ const isRecoverableMachineFlockRoomStatus = (status) => status === "disconnected" || status === "error";
79227
+ class MachineFlockSyncCoordinator {
79228
+ repo;
79229
+ workspaceId;
79230
+ logger;
79231
+ random;
79232
+ retryBaseDelayMs;
79233
+ retryMaxDelayMs;
79234
+ states = /* @__PURE__ */ new Map();
79235
+ cleanedUp = false;
79236
+ constructor(options) {
79237
+ this.repo = options.repo;
79238
+ this.workspaceId = options.workspaceId;
79239
+ this.logger = options.logger;
79240
+ this.random = options.random ?? Math.random;
79241
+ this.retryBaseDelayMs = options.retryBaseDelayMs;
79242
+ this.retryMaxDelayMs = options.retryMaxDelayMs;
79243
+ }
79244
+ ensureJoined(machineId, options = {}) {
79245
+ const state2 = this.getState(machineId);
79246
+ return this.ensureStateJoined(state2, options.reason ?? "ensure-joined");
79247
+ }
79248
+ markDirty(machineId, options = {}) {
79249
+ if (this.cleanedUp) {
79250
+ return;
79251
+ }
79252
+ const state2 = this.getState(machineId);
79253
+ state2.dirty = true;
79254
+ state2.dirtyVersion += 1;
79255
+ if (options.resetBackoff) {
79256
+ state2.retryAttempt = 0;
79257
+ }
79258
+ const reason = options.reason ?? "dirty";
79259
+ void this.ensureStateJoined(state2, `dirty:${reason}`).catch((error2) => {
79260
+ this.logger.debug(`[${this.workspaceId}] Machine Flock room join failed before background sync (machine=${machineId} reason=${reason}): ${formatErrorMessage(error2)}`);
79261
+ });
79262
+ void this.syncNow(machineId, {
79263
+ ...options,
79264
+ reason,
79265
+ scheduleRetry: options.scheduleRetry ?? true
79266
+ });
79267
+ }
79268
+ async syncNow(machineId, options = {}) {
79269
+ if (this.cleanedUp) {
79270
+ return false;
79271
+ }
79272
+ const state2 = this.getState(machineId);
79273
+ if (state2.activeSync) {
79274
+ return await state2.activeSync;
79275
+ }
79276
+ state2.activeSync = this.syncStateNow(state2, options).finally(() => {
79277
+ state2.activeSync = null;
79278
+ });
79279
+ return await state2.activeSync;
79280
+ }
79281
+ retryDirtyNow(reason) {
79282
+ if (this.cleanedUp) {
79283
+ return;
79284
+ }
79285
+ for (const state2 of this.states.values()) {
79286
+ if (!state2.dirty) {
79287
+ continue;
79288
+ }
79289
+ this.clearRetryTimer(state2);
79290
+ state2.retryAttempt = 0;
79291
+ void this.syncNow(state2.machineId, {
79292
+ reason,
79293
+ scheduleRetry: true,
79294
+ resetBackoff: true
79295
+ });
79296
+ }
79297
+ }
79298
+ async cleanUp() {
79299
+ this.cleanedUp = true;
79300
+ const pendingOperations = [];
79301
+ for (const state2 of this.states.values()) {
79302
+ this.clearRetryTimer(state2);
79303
+ if (state2.activeSync) {
79304
+ pendingOperations.push(state2.activeSync);
79305
+ }
79306
+ if (state2.joinPromise) {
79307
+ pendingOperations.push(state2.joinPromise);
79308
+ }
79309
+ this.releaseRoomSubscription(state2);
79310
+ }
79311
+ await Promise.allSettled(pendingOperations);
79312
+ this.states.clear();
79313
+ }
79314
+ async syncStateNow(state2, options) {
79315
+ const reason = options.reason ?? "sync-now";
79316
+ if (options.resetBackoff) {
79317
+ state2.retryAttempt = 0;
79318
+ }
79319
+ this.clearRetryTimer(state2);
79320
+ try {
79321
+ await this.ensureStateJoined(state2, `sync:${reason}`);
79322
+ } catch (error2) {
79323
+ this.logger.debug(`[${this.workspaceId}] Machine Flock room join failed before sync (machine=${state2.machineId} reason=${reason}): ${formatErrorMessage(error2)}`);
79324
+ }
79325
+ const syncVersion = state2.dirtyVersion;
79326
+ const timeoutMs = options.timeoutMs ?? readTimeoutEnv("LODY_LORO_SYNC_MACHINE_FLOCK_TIMEOUT_MS", 8e3);
79327
+ const timeoutMessage = `Timeout waiting for machine Flock doc sync (doc=${state2.docId})`;
79328
+ try {
79329
+ const handle = await this.repo.openFlockDoc(state2.docId);
79330
+ await withTimeout$3(handle.syncOnce(), timeoutMs, timeoutMessage);
79331
+ if (state2.dirtyVersion === syncVersion) {
79332
+ state2.dirty = false;
79333
+ state2.retryAttempt = 0;
79334
+ } else if (state2.dirty) {
79335
+ this.scheduleRetry(state2, `${reason}:new-writes`, true);
79336
+ }
79337
+ this.logger.debug(`[${this.workspaceId}] Machine Flock doc synced (machine=${state2.machineId} reason=${reason})`);
79338
+ return true;
79339
+ } catch (error2) {
79340
+ this.logger.debug(`[${this.workspaceId}] Machine Flock doc sync was not confirmed before continuing (machine=${state2.machineId} reason=${reason}): ${formatErrorMessage(error2)}`);
79341
+ if (options.scheduleRetry ?? true) {
79342
+ state2.dirty = true;
79343
+ this.scheduleRetry(state2, reason, false);
79344
+ }
79345
+ return false;
79346
+ }
79347
+ }
79348
+ async ensureStateJoined(state2, reason) {
79349
+ if (this.cleanedUp) {
79350
+ return;
79351
+ }
79352
+ if (state2.roomSub) {
79353
+ if (!isRecoverableMachineFlockRoomStatus(state2.roomSub.status)) {
79354
+ return;
79355
+ }
79356
+ this.releaseRoomSubscription(state2, state2.roomSub);
79357
+ }
79358
+ if (state2.joinPromise) {
79359
+ return await state2.joinPromise;
79360
+ }
79361
+ state2.joinPromise = (async () => {
79362
+ const handle = await this.repo.openFlockDoc(state2.docId);
79363
+ const sub = await handle.joinRoom();
79364
+ if (this.cleanedUp) {
79365
+ sub.unsubscribe();
79366
+ return;
79367
+ }
79368
+ state2.roomSub = sub;
79369
+ state2.detachRoomStatusListener = sub.onStatusChange((status) => {
79370
+ this.handleRoomStatusChange(state2, sub, status);
79371
+ });
79372
+ this.handleRoomStatusChange(state2, sub, sub.status);
79373
+ if (state2.roomSub !== sub) {
79374
+ return;
79375
+ }
79376
+ void sub.firstSyncedWithRemote.then(() => {
79377
+ if (this.cleanedUp || state2.roomSub !== sub) {
79378
+ return;
79379
+ }
79380
+ this.logger.debug(`[${this.workspaceId}] Machine Flock room first sync completed (machine=${state2.machineId} reason=${reason})`);
79381
+ }, (error2) => {
79382
+ if (this.cleanedUp || state2.roomSub !== sub) {
79383
+ return;
79384
+ }
79385
+ this.logger.debug(`[${this.workspaceId}] Machine Flock room first sync failed (machine=${state2.machineId} reason=${reason}): ${formatErrorMessage(error2)}`);
79386
+ });
79387
+ })();
79388
+ try {
79389
+ await state2.joinPromise;
79390
+ } finally {
79391
+ state2.joinPromise = null;
79392
+ }
79393
+ }
79394
+ handleRoomStatusChange(state2, sub, status) {
79395
+ if (this.cleanedUp || state2.roomSub !== sub) {
79396
+ return;
79397
+ }
79398
+ if (!isRecoverableMachineFlockRoomStatus(status)) {
79399
+ return;
79400
+ }
79401
+ this.logger.debug(`[${this.workspaceId}] Machine Flock room became ${status}; will rejoin before the next sync (machine=${state2.machineId})`);
79402
+ this.releaseRoomSubscription(state2, sub);
79403
+ if (state2.dirty) {
79404
+ this.scheduleRetry(state2, `room-${status}`, false);
79405
+ }
79406
+ }
79407
+ releaseRoomSubscription(state2, sub = state2.roomSub) {
79408
+ if (!sub || state2.roomSub !== sub) {
79409
+ return;
79410
+ }
79411
+ state2.detachRoomStatusListener?.();
79412
+ state2.detachRoomStatusListener = null;
79413
+ state2.roomSub = null;
79414
+ sub.unsubscribe();
79415
+ }
79416
+ scheduleRetry(state2, reason, resetBackoff) {
79417
+ if (this.cleanedUp || !state2.dirty) {
79418
+ return;
79419
+ }
79420
+ if (resetBackoff) {
79421
+ state2.retryAttempt = 0;
79422
+ }
79423
+ if (state2.retryTimer) {
79424
+ return;
79425
+ }
79426
+ const delayMs = computeLoroReconnectDelayMs(state2.retryAttempt, {
79427
+ baseDelayMs: this.retryBaseDelayMs ?? readTimeoutEnv("LODY_LORO_MACHINE_FLOCK_RETRY_BASE_DELAY_MS", 1e3),
79428
+ maxDelayMs: this.retryMaxDelayMs ?? readTimeoutEnv("LODY_LORO_MACHINE_FLOCK_RETRY_MAX_DELAY_MS", 6e4),
79429
+ random: this.random
79430
+ });
79431
+ state2.retryAttempt += 1;
79432
+ state2.retryTimer = setTimeout(() => {
79433
+ state2.retryTimer = null;
79434
+ if (this.cleanedUp || !state2.dirty) {
79435
+ return;
79436
+ }
79437
+ void this.syncNow(state2.machineId, {
79438
+ reason: `retry:${reason}`,
79439
+ scheduleRetry: true
79440
+ });
79441
+ }, delayMs);
79442
+ this.logger.debug(`[${this.workspaceId}] Scheduled Machine Flock doc sync retry in ${delayMs}ms (machine=${state2.machineId} reason=${reason})`);
79443
+ }
79444
+ clearRetryTimer(state2) {
79445
+ if (!state2.retryTimer) {
79446
+ return;
79447
+ }
79448
+ clearTimeout(state2.retryTimer);
79449
+ state2.retryTimer = null;
79450
+ }
79451
+ getState(machineId) {
79452
+ const existing = this.states.get(machineId);
79453
+ if (existing) {
79454
+ return existing;
79455
+ }
79456
+ const state2 = {
79457
+ machineId,
79458
+ docId: getMachineFlockDocId(this.workspaceId, machineId),
79459
+ dirty: false,
79460
+ dirtyVersion: 0,
79461
+ retryAttempt: 0,
79462
+ retryTimer: null,
79463
+ activeSync: null,
79464
+ joinPromise: null,
79465
+ roomSub: null,
79466
+ detachRoomStatusListener: null
79467
+ };
79468
+ this.states.set(machineId, state2);
79469
+ return state2;
79470
+ }
79471
+ }
78958
79472
  const SENSITIVE_QUERY_KEYS = /* @__PURE__ */ new Set([
78959
79473
  "token",
78960
79474
  "access_token",
@@ -98517,7 +99031,7 @@ ${value}`;
98517
99031
  ]);
98518
99032
  return mergeAgentConfigs(loroRepoMetaConfigs, machineFlockConfigs);
98519
99033
  }
98520
- async function upsertMachineAgentConfig(repo, workspaceId, config2) {
99034
+ async function upsertMachineAgentConfig(repo, workspaceId, config2, options = {}) {
98521
99035
  const handle = await repo.openFlockDoc(getMachineFlockDocId(workspaceId, config2.machineId));
98522
99036
  const changed = writeMachineFlockRowToFlock(handle.flock, {
98523
99037
  key: machineFlockKeys.agentConfig(config2.id),
@@ -98528,10 +99042,16 @@ ${value}`;
98528
99042
  return;
98529
99043
  }
98530
99044
  await repo.flush();
98531
- await handle.syncOnce().catch(() => void 0);
99045
+ if (options.sync) {
99046
+ options.sync.markMachineFlockDocDirty(config2.machineId, {
99047
+ reason: options.reason ?? "agent-config-upsert"
99048
+ });
99049
+ } else {
99050
+ await handle.syncOnce().catch(() => void 0);
99051
+ }
98532
99052
  await deleteLoroRepoMetaAgentConfigIfPresent(repo, config2.id);
98533
99053
  }
98534
- async function deleteMachineAgentConfig(repo, workspaceId, config2) {
99054
+ async function deleteMachineAgentConfig(repo, workspaceId, config2, options = {}) {
98535
99055
  const handle = await repo.openFlockDoc(getMachineFlockDocId(workspaceId, config2.machineId));
98536
99056
  const changed = deleteMachineFlockRowFromFlock(handle.flock, machineFlockKeys.agentConfig(config2.id));
98537
99057
  if (!changed) {
@@ -98539,7 +99059,13 @@ ${value}`;
98539
99059
  return;
98540
99060
  }
98541
99061
  await repo.flush();
98542
- await handle.syncOnce().catch(() => void 0);
99062
+ if (options.sync) {
99063
+ options.sync.markMachineFlockDocDirty(config2.machineId, {
99064
+ reason: options.reason ?? "agent-config-delete"
99065
+ });
99066
+ } else {
99067
+ await handle.syncOnce().catch(() => void 0);
99068
+ }
98543
99069
  await deleteLoroRepoMetaAgentConfigIfPresent(repo, config2.id);
98544
99070
  }
98545
99071
  async function listMachineAgentConfigs(repo, workspaceId, machineIds) {
@@ -98735,11 +99261,21 @@ ${value}`;
98735
99261
  this.initialMetaSyncCompleted = true;
98736
99262
  }
98737
99263
  });
99264
+ this.machineFlockSync = new MachineFlockSyncCoordinator({
99265
+ repo,
99266
+ workspaceId,
99267
+ logger: logger2
99268
+ });
99269
+ this.detachMachineFlockMetaRoomSyncedListener = this.connectionRecovery.onMetaRoomSynced((reason) => {
99270
+ this.machineFlockSync.retryDirtyNow(`meta-room-synced:${reason}`);
99271
+ });
98738
99272
  }
98739
99273
  sessions = /* @__PURE__ */ new Map();
98740
99274
  machine = null;
98741
99275
  machineExistenceWatcher = null;
98742
99276
  connectionRecovery;
99277
+ machineFlockSync;
99278
+ detachMachineFlockMetaRoomSyncedListener = null;
98743
99279
  initialMetaSyncCompleted = false;
98744
99280
  initialMetaSyncPromise;
98745
99281
  presenceRuntime;
@@ -98926,6 +99462,70 @@ ${value}`;
98926
99462
  async waitUntilMetaSynced(options = {}) {
98927
99463
  return await this.connectionRecovery.waitUntilMetaSynced(options);
98928
99464
  }
99465
+ async syncMetaOrThrow(options = {}) {
99466
+ const reason = options.reason ?? "explicit-sync";
99467
+ const timeoutMs = options.timeoutMs ?? readTimeoutEnv("LODY_LORO_SYNC_META_TIMEOUT_MS", 2e4);
99468
+ const timeoutMessage = `Timeout waiting for workspace metadata sync (workspace=${this.workspaceId})`;
99469
+ try {
99470
+ await withTimeout$3(this.repo.sync({
99471
+ scope: "meta"
99472
+ }), timeoutMs, timeoutMessage);
99473
+ this.initialMetaSyncCompleted = true;
99474
+ } catch (error2) {
99475
+ throw new Error(`Workspace metadata sync failed (${reason}): ${formatErrorMessage(error2)}`, {
99476
+ cause: error2
99477
+ });
99478
+ }
99479
+ }
99480
+ async syncDocOrThrow(docId, options = {}) {
99481
+ const reason = options.reason ?? "explicit-sync";
99482
+ const timeoutMs = options.timeoutMs ?? readTimeoutEnv("LODY_LORO_SYNC_DOC_TIMEOUT_MS", 8e3);
99483
+ const timeoutMessage = `Timeout waiting for document sync (doc=${docId})`;
99484
+ try {
99485
+ await withTimeout$3(this.repo.sync({
99486
+ scope: "doc",
99487
+ docIds: [
99488
+ docId
99489
+ ]
99490
+ }), timeoutMs, timeoutMessage);
99491
+ } catch (error2) {
99492
+ throw new Error(`Document sync failed for ${docId} (${reason}): ${formatErrorMessage(error2)}`, {
99493
+ cause: error2
99494
+ });
99495
+ }
99496
+ }
99497
+ async syncFlockDocOrThrow(flockDocId, options = {}) {
99498
+ const reason = options.reason ?? "explicit-sync";
99499
+ const timeoutMs = options.timeoutMs ?? readTimeoutEnv("LODY_LORO_SYNC_MACHINE_FLOCK_TIMEOUT_MS", 8e3);
99500
+ const timeoutMessage = `Timeout waiting for Flock document sync (doc=${flockDocId})`;
99501
+ try {
99502
+ await withTimeout$3(this.repo.sync({
99503
+ scope: "doc",
99504
+ flockDocIds: [
99505
+ flockDocId
99506
+ ]
99507
+ }), timeoutMs, timeoutMessage);
99508
+ } catch (error2) {
99509
+ throw new Error(`Flock document sync failed for ${flockDocId} (${reason}): ${formatErrorMessage(error2)}`, {
99510
+ cause: error2
99511
+ });
99512
+ }
99513
+ }
99514
+ async syncMachineFlockDoc(machineId, options = {}) {
99515
+ return await this.machineFlockSync.syncNow(machineId, {
99516
+ ...options,
99517
+ reason: options.reason ?? "explicit-sync",
99518
+ scheduleRetry: options.scheduleRetry ?? true
99519
+ });
99520
+ }
99521
+ markMachineFlockDocDirty(machineId, options = {}) {
99522
+ this.machineFlockSync.markDirty(machineId, options);
99523
+ }
99524
+ ensureMachineFlockDocJoined(machineId, options = {}) {
99525
+ void this.machineFlockSync.ensureJoined(machineId, options).catch((error2) => {
99526
+ this.logger.debug(`[${this.workspaceId}] Failed to join Machine Flock room (machine=${machineId} reason=${options.reason ?? "ensure-joined"}): ${formatErrorMessage(error2)}`);
99527
+ });
99528
+ }
98929
99529
  async destroyRepo(options) {
98930
99530
  const repoDestroyPromise = this.repo.destroy();
98931
99531
  if (!options.fast) {
@@ -99038,6 +99638,9 @@ ${value}`;
99038
99638
  cliType,
99039
99639
  agentType,
99040
99640
  env: {}
99641
+ }, {
99642
+ sync: this,
99643
+ reason: "agent-config-upsert"
99041
99644
  });
99042
99645
  return agentConfigId;
99043
99646
  }
@@ -99045,7 +99648,7 @@ ${value}`;
99045
99648
  }
99046
99649
  async registerMachine(machineId, machine) {
99047
99650
  if (!this.machine) {
99048
- this.machine = new MachineDocument(this.repo, this.workspaceId, machineId);
99651
+ this.machine = this.createMachineDocument(machineId);
99049
99652
  await this.machine.init();
99050
99653
  }
99051
99654
  await this.machine.setMetaState({
@@ -99056,25 +99659,32 @@ ${value}`;
99056
99659
  }
99057
99660
  async updateRateLimits(machineId, cliType, limits) {
99058
99661
  if (!this.machine) {
99059
- this.machine = new MachineDocument(this.repo, this.workspaceId, machineId);
99662
+ this.machine = this.createMachineDocument(machineId);
99060
99663
  await this.machine.init();
99061
99664
  }
99062
99665
  await this.machine.updateRateLimits(cliType, limits);
99063
99666
  }
99064
99667
  async updateAcpCapabilities(machineId, cliType, agentType, modes, models, configOptions, availableCommands, sourceVersion) {
99065
99668
  if (!this.machine) {
99066
- this.machine = new MachineDocument(this.repo, this.workspaceId, machineId);
99669
+ this.machine = this.createMachineDocument(machineId);
99067
99670
  await this.machine.init();
99068
99671
  }
99069
99672
  await this.machine.updateAcpCapabilities(cliType, agentType, modes, models, configOptions, availableCommands, sourceVersion);
99070
99673
  }
99071
99674
  async getAcpCapabilities(machineId, cliType, agentType) {
99072
99675
  if (!this.machine) {
99073
- this.machine = new MachineDocument(this.repo, this.workspaceId, machineId);
99676
+ this.machine = this.createMachineDocument(machineId);
99074
99677
  await this.machine.init();
99075
99678
  }
99076
99679
  return this.machine.getAcpCapabilities(cliType, agentType);
99077
99680
  }
99681
+ createMachineDocument(machineId) {
99682
+ return new MachineDocument(this.repo, this.workspaceId, machineId, (reason) => {
99683
+ this.markMachineFlockDocDirty(machineId, {
99684
+ reason
99685
+ });
99686
+ });
99687
+ }
99078
99688
  async restoreMachineDocument(machineId) {
99079
99689
  const machineRoomId = getMachineRoomId(machineId);
99080
99690
  await this.repo.restoreDoc(machineRoomId);
@@ -99104,6 +99714,9 @@ ${value}`;
99104
99714
  } catch (error2) {
99105
99715
  this.logger.debug(`[${this.workspaceId}] Failed to stop Loro presence runtime: ${formatErrorMessage(error2)}`);
99106
99716
  }
99717
+ this.detachMachineFlockMetaRoomSyncedListener?.();
99718
+ this.detachMachineFlockMetaRoomSyncedListener = null;
99719
+ await this.machineFlockSync.cleanUp();
99107
99720
  await this.connectionRecovery.cleanUp();
99108
99721
  for (const [sessionId, pending2] of this.pendingSessionDocs) {
99109
99722
  try {
@@ -99988,10 +100601,11 @@ ${value}`;
99988
100601
  return meta.meta;
99989
100602
  };
99990
100603
  class MachineDocument {
99991
- constructor(repo, workspaceId, machineId) {
100604
+ constructor(repo, workspaceId, machineId, markMachineFlockDirty) {
99992
100605
  this.repo = repo;
99993
100606
  this.workspaceId = workspaceId;
99994
100607
  this.machineId = machineId;
100608
+ this.markMachineFlockDirty = markMachineFlockDirty;
99995
100609
  this.roomId = getMachineRoomId(this.machineId);
99996
100610
  }
99997
100611
  roomId;
@@ -100031,7 +100645,11 @@ ${value}`;
100031
100645
  });
100032
100646
  if (changed) {
100033
100647
  await this.repo.flush();
100034
- await handle.syncOnce().catch(() => void 0);
100648
+ if (this.markMachineFlockDirty) {
100649
+ this.markMachineFlockDirty("rate-limit-update");
100650
+ } else {
100651
+ await handle.syncOnce().catch(() => void 0);
100652
+ }
100035
100653
  }
100036
100654
  });
100037
100655
  }
@@ -100064,7 +100682,11 @@ ${value}`;
100064
100682
  });
100065
100683
  if (changed) {
100066
100684
  await this.repo.flush();
100067
- await handle.syncOnce().catch(() => void 0);
100685
+ if (this.markMachineFlockDirty) {
100686
+ this.markMachineFlockDirty("acp-capability-update");
100687
+ } else {
100688
+ await handle.syncOnce().catch(() => void 0);
100689
+ }
100068
100690
  }
100069
100691
  }
100070
100692
  async getAcpCapabilities(cliType, agentType) {
@@ -102156,7 +102778,9 @@ The file is not available yet and could not be downloaded; ask the user to resen
102156
102778
  return remoteName;
102157
102779
  }
102158
102780
  async function probeGitHubRemoteAtRootPath(rootPath) {
102159
- await assertGitRepository(rootPath);
102781
+ if (!await isGitRepository(rootPath)) {
102782
+ return null;
102783
+ }
102160
102784
  const remotes = await listGitRemotes(rootPath);
102161
102785
  if (remotes.length === 0) return null;
102162
102786
  const currentBranchRemote = await resolveCurrentBranchRemote(rootPath, remotes);
@@ -117765,18 +118389,6 @@ ${fallbackStderrTail}` : errorMessage;
117765
118389
  function shouldScrubClaudeAuthEnv(cliType, agentType) {
117766
118390
  return cliType === "builtin" && agentType === "claude" || cliType === "registry" && agentType === "claude-p";
117767
118391
  }
117768
- async function getStaticBuiltinAcpCapabilities(agentType) {
117769
- if (!isBuiltinAgentType(agentType)) {
117770
- return void 0;
117771
- }
117772
- const baseline = agentType === "claude" ? (await import("./chunks/baseline-config-CcYq1mUH.js")).getClaudeBaselineConfig() : (await import("./chunks/baseline-config-BPa_f3S0.js")).getCodexBaselineConfig();
117773
- const configOptions = normalizeConfigOptions(baseline.configOptions);
117774
- return {
117775
- modes: baseline.modes,
117776
- models: baseline.models,
117777
- configOptions
117778
- };
117779
- }
117780
118392
  function isSelectGroup$1(item) {
117781
118393
  return typeof item === "object" && item !== null && "group" in item;
117782
118394
  }
@@ -117854,7 +118466,7 @@ ${fallbackStderrTail}` : errorMessage;
117854
118466
  async function fetchAcpCapabilities(cliType, agentType, logger2, env2, customAcp, runtimeOverrides, options = {}) {
117855
118467
  const allowStaticBuiltinCapabilities = options.allowStaticBuiltinCapabilities ?? true;
117856
118468
  if (allowStaticBuiltinCapabilities && cliType === "builtin" && !hasBuiltinRuntimeOverrideValues(runtimeOverrides)) {
117857
- const staticCapabilities = await getStaticBuiltinAcpCapabilities(agentType);
118469
+ const staticCapabilities = getStaticBuiltinAcpCapabilities(cliType, agentType, runtimeOverrides);
117858
118470
  if (staticCapabilities) {
117859
118471
  logger2.debug(`[acp-capabilities] Using static builtin capabilities (agentType=${agentType})`);
117860
118472
  return staticCapabilities;
@@ -121377,7 +121989,7 @@ The postId is ${normalizedFeedbackPostId}. Use the feedback-progress-reporter sk
121377
121989
  ...flockLocalProjects
121378
121990
  };
121379
121991
  }
121380
- async function upsertMachineLocalProject(repo, workspaceId, machineId, project, nowMs2 = getServerNow()) {
121992
+ async function upsertMachineLocalProject(repo, workspaceId, machineId, project, nowMs2 = getServerNow(), options = {}) {
121381
121993
  const handle = await repo.openFlockDoc(getMachineFlockDocId(workspaceId, machineId));
121382
121994
  const changed = writeMachineFlockRowToFlock(handle.flock, {
121383
121995
  key: machineFlockKeys.localProject(project.id),
@@ -121387,14 +121999,26 @@ The postId is ${normalizedFeedbackPostId}. Use the feedback-progress-reporter sk
121387
121999
  return;
121388
122000
  }
121389
122001
  await repo.flush();
121390
- await handle.syncOnce();
122002
+ if (options.sync) {
122003
+ options.sync.markMachineFlockDocDirty(machineId, {
122004
+ reason: options.reason ?? "local-project-upsert"
122005
+ });
122006
+ } else {
122007
+ await handle.syncOnce().catch(() => void 0);
122008
+ }
121391
122009
  }
121392
- async function removeMachineLocalProject(repo, workspaceId, machineId, localProjectId, nowMs2 = getServerNow()) {
122010
+ async function removeMachineLocalProject(repo, workspaceId, machineId, localProjectId, nowMs2 = getServerNow(), options = {}) {
121393
122011
  const handle = await repo.openFlockDoc(getMachineFlockDocId(workspaceId, machineId));
121394
122012
  const changed = deleteMachineFlockRowFromFlock(handle.flock, machineFlockKeys.localProject(localProjectId), nowMs2);
121395
122013
  if (changed) {
121396
122014
  await repo.flush();
121397
- await handle.syncOnce().catch(() => void 0);
122015
+ if (options.sync) {
122016
+ options.sync.markMachineFlockDocDirty(machineId, {
122017
+ reason: options.reason ?? "local-project-remove"
122018
+ });
122019
+ } else {
122020
+ await handle.syncOnce().catch(() => void 0);
122021
+ }
121398
122022
  }
121399
122023
  const machineRoomId = getMachineRoomId(machineId);
121400
122024
  const current2 = await repo.getDocMeta(machineRoomId);
@@ -133814,7 +134438,10 @@ ${escapeHtmlScriptContent(VISUAL_ANNOTATION_INSPECTOR_BROWSER_SCRIPT)}
133814
134438
  ]))
133815
134439
  }
133816
134440
  }
133817
- }, lastListedAt);
134441
+ }, lastListedAt, {
134442
+ sync: this.manager,
134443
+ reason: "local-project-history-sync"
134444
+ });
133818
134445
  });
133819
134446
  return catalog;
133820
134447
  }
@@ -139197,7 +139824,10 @@ ${escapeHtmlScriptContent(VISUAL_ANNOTATION_INSPECTOR_BROWSER_SCRIPT)}
139197
139824
  rootPath: entry.rootPath,
139198
139825
  createdAtMs: previous?.createdAtMs ?? nowMs2,
139199
139826
  lastOpenedAtMs: nowMs2
139200
- }, nowMs2);
139827
+ }, nowMs2, {
139828
+ sync: this.workspaceDocument,
139829
+ reason: "local-project-add"
139830
+ });
139201
139831
  }
139202
139832
  async authorizeLocalProjectRoot(args2) {
139203
139833
  const access = await canUseMachineForCliToken({
@@ -148234,6 +148864,8 @@ export PATH=${toSingleQuotedShellString(ghShimBinDir)}:"$PATH"
148234
148864
  await next2;
148235
148865
  }
148236
148866
  }
148867
+ const BUILTIN_AGENT_CONFIG_INITIAL_RETRY_DELAY_MS = 1e4;
148868
+ const BUILTIN_AGENT_CONFIG_MAX_RETRY_DELAY_MS = 5 * 6e4;
148237
148869
  class Lody {
148238
148870
  constructor(options, documentManager) {
148239
148871
  this.options = options;
@@ -148274,6 +148906,9 @@ export PATH=${toSingleQuotedShellString(ghShimBinDir)}:"$PATH"
148274
148906
  runtime;
148275
148907
  supportRegistryAgentTypes;
148276
148908
  cleanedUp = false;
148909
+ builtinAgentConfigRetryTimer;
148910
+ pendingBuiltinAgentConfigRetryCliTypes = /* @__PURE__ */ new Set();
148911
+ builtinAgentConfigRetryAttempt = 0;
148277
148912
  static async create(options) {
148278
148913
  const manager = await LoroDocumentManager.create(options.workspaceId, options.userId, () => options.token, options.logger);
148279
148914
  return new Lody(options, manager);
@@ -148281,25 +148916,49 @@ export PATH=${toSingleQuotedShellString(ghShimBinDir)}:"$PATH"
148281
148916
  async start() {
148282
148917
  void getLoginShellEnv();
148283
148918
  await this.runtime.initialize();
148919
+ this.documentManager.ensureMachineFlockDocJoined(this.machineId, {
148920
+ reason: "lody-start"
148921
+ });
148284
148922
  }
148285
148923
  async registerAgent(cliTypes) {
148286
148924
  await this.runtime.initialize();
148287
148925
  if (this.documentManager.hasCompletedInitialMetaSync()) {
148288
- await this.ensureBuiltinAgentConfigs(cliTypes);
148926
+ await this.ensureBuiltinAgentConfigsOrRetry(cliTypes);
148289
148927
  } else {
148290
148928
  this.logger.debug(`[agent-config] Initial meta sync is not complete for workspace ${this.workspaceId}; deferring builtin agent registration`);
148291
148929
  void this.documentManager.waitForInitialMetaSync().then(async (completed) => {
148292
148930
  if (!completed || this.cleanedUp) {
148293
148931
  return;
148294
148932
  }
148295
- await this.ensureBuiltinAgentConfigs(cliTypes);
148933
+ await this.ensureBuiltinAgentConfigsOrRetry(cliTypes);
148296
148934
  }).catch((error2) => {
148297
148935
  this.logger.debug(`[agent-config] Deferred builtin agent registration failed: ${formatErrorMessage(error2)}`);
148298
148936
  });
148299
148937
  }
148300
148938
  void this.refreshBuiltinCapabilities(cliTypes);
148301
148939
  }
148940
+ async ensureBuiltinAgentConfigsOrRetry(cliTypes) {
148941
+ if (this.cleanedUp) {
148942
+ return;
148943
+ }
148944
+ const completed = await this.ensureBuiltinAgentConfigs(cliTypes);
148945
+ if (!completed) {
148946
+ this.scheduleBuiltinAgentConfigRetry(cliTypes);
148947
+ return;
148948
+ }
148949
+ this.builtinAgentConfigRetryAttempt = 0;
148950
+ }
148302
148951
  async ensureBuiltinAgentConfigs(cliTypes) {
148952
+ if (cliTypes.length === 0) {
148953
+ return true;
148954
+ }
148955
+ const syncedMachineFlock = await this.documentManager.syncMachineFlockDoc(this.machineId, {
148956
+ reason: "builtin-agent-registration"
148957
+ });
148958
+ if (!syncedMachineFlock) {
148959
+ this.logger.debug(`[agent-config] Machine Flock sync is not complete for workspace ${this.workspaceId} machine ${this.machineId}; skipping builtin agent registration for this attempt`);
148960
+ return false;
148961
+ }
148303
148962
  const builtinDisplayName = {
148304
148963
  claude: "Claude Code",
148305
148964
  codex: "Codex"
@@ -148310,6 +148969,40 @@ export PATH=${toSingleQuotedShellString(ghShimBinDir)}:"$PATH"
148310
148969
  await this.documentManager.createAgentConfig("builtin", cliType, this.machineId, builtinDisplayName[cliType]);
148311
148970
  }
148312
148971
  }
148972
+ return true;
148973
+ }
148974
+ scheduleBuiltinAgentConfigRetry(cliTypes) {
148975
+ if (cliTypes.length === 0 || this.cleanedUp) {
148976
+ return;
148977
+ }
148978
+ for (const cliType of cliTypes) {
148979
+ this.pendingBuiltinAgentConfigRetryCliTypes.add(cliType);
148980
+ }
148981
+ if (this.builtinAgentConfigRetryTimer) {
148982
+ return;
148983
+ }
148984
+ const delayMs = this.nextBuiltinAgentConfigRetryDelayMs();
148985
+ this.logger.debug(`[agent-config] Scheduling builtin agent registration retry in ${delayMs}ms for workspace ${this.workspaceId} machine ${this.machineId}`);
148986
+ this.builtinAgentConfigRetryTimer = setTimeout(() => {
148987
+ this.builtinAgentConfigRetryTimer = void 0;
148988
+ if (this.cleanedUp) {
148989
+ this.pendingBuiltinAgentConfigRetryCliTypes.clear();
148990
+ return;
148991
+ }
148992
+ const retryCliTypes = [
148993
+ ...this.pendingBuiltinAgentConfigRetryCliTypes
148994
+ ];
148995
+ this.pendingBuiltinAgentConfigRetryCliTypes.clear();
148996
+ void this.ensureBuiltinAgentConfigsOrRetry(retryCliTypes).catch((error2) => {
148997
+ this.logger.debug(`[agent-config] Retried builtin agent registration failed: ${formatErrorMessage(error2)}`);
148998
+ });
148999
+ }, delayMs);
149000
+ this.builtinAgentConfigRetryTimer.unref?.();
149001
+ }
149002
+ nextBuiltinAgentConfigRetryDelayMs() {
149003
+ const multiplier = 2 ** Math.min(this.builtinAgentConfigRetryAttempt, 5);
149004
+ this.builtinAgentConfigRetryAttempt += 1;
149005
+ return Math.min(BUILTIN_AGENT_CONFIG_INITIAL_RETRY_DELAY_MS * multiplier, BUILTIN_AGENT_CONFIG_MAX_RETRY_DELAY_MS);
148313
149006
  }
148314
149007
  async refreshBuiltinCapabilities(cliTypes) {
148315
149008
  for (const cliType of cliTypes) {
@@ -148340,6 +149033,12 @@ export PATH=${toSingleQuotedShellString(ghShimBinDir)}:"$PATH"
148340
149033
  }
148341
149034
  cleanup = async () => {
148342
149035
  this.cleanedUp = true;
149036
+ if (this.builtinAgentConfigRetryTimer) {
149037
+ clearTimeout(this.builtinAgentConfigRetryTimer);
149038
+ this.builtinAgentConfigRetryTimer = void 0;
149039
+ }
149040
+ this.pendingBuiltinAgentConfigRetryCliTypes.clear();
149041
+ this.builtinAgentConfigRetryAttempt = 0;
148343
149042
  return await this.runtime.cleanup();
148344
149043
  };
148345
149044
  async dispatchLocalControl(message) {
@@ -150549,10 +151248,16 @@ export PATH=${toSingleQuotedShellString(ghShimBinDir)}:"$PATH"
150549
151248
  rootPath: entry.rootPath,
150550
151249
  createdAtMs: previous?.createdAtMs ?? nowMs2,
150551
151250
  lastOpenedAtMs: nowMs2
150552
- }, nowMs2);
151251
+ }, nowMs2, {
151252
+ sync: runtime.lody.documentManager,
151253
+ reason: "local-project-add"
151254
+ });
150553
151255
  }
150554
151256
  async removeProjectMetaInWorkspace(runtime, localProjectId) {
150555
- await removeMachineLocalProject(runtime.lody.documentManager.repo, runtime.workspace.id, this.machineId, localProjectId);
151257
+ await removeMachineLocalProject(runtime.lody.documentManager.repo, runtime.workspace.id, this.machineId, localProjectId, void 0, {
151258
+ sync: runtime.lody.documentManager,
151259
+ reason: "local-project-delete"
151260
+ });
150556
151261
  }
150557
151262
  async listProjectsByWorkspace() {
150558
151263
  const groups = [];
@@ -177422,6 +178127,29 @@ ${page}${helpTipBottom}${choiceDescription}${ansiEscapes.cursorHide}`;
177422
178127
  throw new Error(`Workspace metadata changes were not confirmed by Loro Streams (${reason}). Retry the command after checking network connectivity.`);
177423
178128
  }
177424
178129
  }
178130
+ function buildOfflineHint(error2) {
178131
+ return new Error(`${formatErrorMessage(error2)} Use --offline to read the local cache without syncing.`, {
178132
+ cause: error2
178133
+ });
178134
+ }
178135
+ async function syncWorkspaceMetaForRead(manager, reason) {
178136
+ try {
178137
+ await manager.syncMetaOrThrow({
178138
+ reason
178139
+ });
178140
+ } catch (error2) {
178141
+ throw buildOfflineHint(error2);
178142
+ }
178143
+ }
178144
+ async function syncDocForRead(manager, docId, reason) {
178145
+ try {
178146
+ await manager.syncDocOrThrow(docId, {
178147
+ reason
178148
+ });
178149
+ } catch (error2) {
178150
+ throw buildOfflineHint(error2);
178151
+ }
178152
+ }
177425
178153
  async function listAliveRoomIds(manager, predicate) {
177426
178154
  const scanner = manager.repo.getMeta();
177427
178155
  if (!scanner) {
@@ -177600,16 +178328,19 @@ ${page}${helpTipBottom}${choiceDescription}${ansiEscapes.cursorHide}`;
177600
178328
  await action();
177601
178329
  await exitOneShotCommand(0);
177602
178330
  } catch (error2) {
178331
+ const commandError = error2 && typeof error2 === "object" ? error2 : void 0;
177603
178332
  const message = formatErrorMessage(error2);
177604
- if (options.json || options.jsonl) {
177605
- printJson({
177606
- ok: false,
177607
- error: message
177608
- });
177609
- } else {
177610
- getLogger(loggerName).error(message);
178333
+ if (commandError?.suppressCommandErrorOutput !== true) {
178334
+ if (options.json || options.jsonl) {
178335
+ printJson({
178336
+ ok: false,
178337
+ error: message
178338
+ });
178339
+ } else {
178340
+ getLogger(loggerName).error(message);
178341
+ }
177611
178342
  }
177612
- await exitOneShotCommand(1);
178343
+ await exitOneShotCommand(commandError?.exitCode ?? 1);
177613
178344
  }
177614
178345
  }
177615
178346
  var debug_1;
@@ -180339,16 +181070,28 @@ ${page}${helpTipBottom}${choiceDescription}${ansiEscapes.cursorHide}`;
180339
181070
  configOptionValues
180340
181071
  };
180341
181072
  }
180342
- const agentConfigListCommand = new Command("list").description("List agent configs in a workspace").option("--workspace <idOrSlug>", "Target workspace id or slug").option("--json", "Print JSON output").option("--debug", "Enable debug output").action(async (options) => {
181073
+ const agentConfigListCommand = new Command("list").description("List agent configs in a workspace").option("--workspace <idOrSlug>", "Target workspace id or slug").option("--machine <idOrName>", "Only include configs for one machine").option("--json", "Print JSON output").option("--debug", "Enable debug output").action(async (options) => {
180343
181074
  await runOneShotCommand("agent-config", options, async () => {
180344
181075
  const auth = getAuthContextOrThrow$1("agent-config");
180345
181076
  const workspace = await resolveWorkspaceOrThrow$1(auth, options.workspace);
180346
181077
  await withWorkspaceManager$1(auth, workspace, "agent-config", async (manager) => {
180347
- const configs = await listAgentConfigsForWorkspace$1(manager, workspace.id);
181078
+ const machineSelector = normalizeCliValue(options.machine);
181079
+ let machineId;
181080
+ if (machineSelector) {
181081
+ const machine = resolveMachineOrThrow(await listMachineMetasForWorkspace(manager), {
181082
+ selector: machineSelector,
181083
+ authMachineId: auth.machineId
181084
+ });
181085
+ machineId = machine.id;
181086
+ }
181087
+ const configs = (await listAgentConfigsForWorkspace$1(manager, workspace.id)).filter((config2) => machineId === void 0 || config2.machineId === machineId);
180348
181088
  if (options.json) {
180349
181089
  printJson({
180350
181090
  ok: true,
180351
181091
  workspaceId: workspace.id,
181092
+ ...machineId ? {
181093
+ machineId
181094
+ } : {},
180352
181095
  agentConfigs: configs.map(toAgentConfigOutput)
180353
181096
  });
180354
181097
  return;
@@ -180805,13 +181548,21 @@ ${page}${helpTipBottom}${choiceDescription}${ansiEscapes.cursorHide}`;
180805
181548
  }
180806
181549
  throw new Error(`Agent config not found: ${normalizedSelector}. Candidates: ${formatAgentConfigCandidates(configs)}`);
180807
181550
  }
181551
+ function resolveCreateAgentSelector(options) {
181552
+ const agent2 = normalizeCliValue(options.agent);
181553
+ const agentConfig = normalizeCliValue(options.agentConfig);
181554
+ if (agent2 && agentConfig && agent2 !== agentConfig) {
181555
+ throw new Error("Pass either --agent or --agent-config, not both.");
181556
+ }
181557
+ return agentConfig ?? agent2;
181558
+ }
180808
181559
  function buildAgentPrompt(prompt2, agentPrompt = "") {
180809
181560
  return [
180810
181561
  agentPrompt,
180811
181562
  prompt2
180812
181563
  ].filter((part) => part?.trim()).join("\n\n");
180813
181564
  }
180814
- function parsePositiveIntOption(value) {
181565
+ function parsePositiveIntOption$1(value) {
180815
181566
  const parsed = Number.parseInt(value, 10);
180816
181567
  if (!Number.isFinite(parsed) || parsed <= 0) {
180817
181568
  throw new Error(`Invalid numeric value: ${value}`);
@@ -180898,6 +181649,9 @@ ${page}${helpTipBottom}${choiceDescription}${ansiEscapes.cursorHide}`;
180898
181649
  return entries.map((entry) => `[${entry.role}] ${entry.timestamp} ${entry.id}
180899
181650
  ${entry.text}`).join("\n\n");
180900
181651
  }
181652
+ function renderAssistantTurnCompletion(content) {
181653
+ return extractTranscriptText(content, "assistant") ?? "No visible assistant reply found.";
181654
+ }
180901
181655
  async function readStdinText() {
180902
181656
  const chunks = [];
180903
181657
  return await new Promise((resolve2, reject) => {
@@ -181198,15 +181952,22 @@ ${entry.text}`).join("\n\n");
181198
181952
  const effectiveSelector = normalizeCliValue(selector) ?? normalizeCliValue(process.env.LODY_WORKSPACE_ID);
181199
181953
  return selectWorkspaceSummary(workspaces, effectiveSelector);
181200
181954
  }
181201
- async function resolveWorkspaceForSessionOrThrow(auth, sessionId, selector) {
181955
+ async function resolveWorkspaceForSessionOrThrow(auth, sessionId, options) {
181202
181956
  const workspaces = await listWorkspacesForToken(auth.token);
181957
+ const selector = typeof options === "string" ? options : options?.workspace;
181958
+ const shouldSync = typeof options === "object" && options.offline !== true;
181959
+ const syncReason = typeof options === "object" ? options.reason : void 0;
181203
181960
  const effectiveSelector = normalizeCliValue(selector) ?? normalizeCliValue(process.env.LODY_WORKSPACE_ID);
181961
+ const sessionExistsInWorkspace = async (workspace) => await withWorkspaceManager(auth, workspace, async (manager) => {
181962
+ if (shouldSync) {
181963
+ await syncWorkspaceMetaForRead(manager, syncReason ?? `session.resolve:${sessionId}:${workspace.id}`);
181964
+ }
181965
+ const raw2 = await manager.repo.getDocMeta(getSessionRoomId(sessionId));
181966
+ return !!raw2?.meta && !isLoroRepoDocDeleted(raw2);
181967
+ });
181204
181968
  if (effectiveSelector) {
181205
181969
  const workspace = selectWorkspaceSummary(workspaces, effectiveSelector);
181206
- const exists = await withWorkspaceManager(auth, workspace, async (manager) => {
181207
- const raw2 = await manager.repo.getDocMeta(getSessionRoomId(sessionId));
181208
- return !!raw2?.meta && !isLoroRepoDocDeleted(raw2);
181209
- });
181970
+ const exists = await sessionExistsInWorkspace(workspace);
181210
181971
  if (!exists) {
181211
181972
  throw new Error(`Session not found in workspace ${workspace.id}: ${sessionId}`);
181212
181973
  }
@@ -181214,10 +181975,7 @@ ${entry.text}`).join("\n\n");
181214
181975
  }
181215
181976
  if (workspaces.length === 1) {
181216
181977
  const workspace = workspaces[0];
181217
- const exists = await withWorkspaceManager(auth, workspace, async (manager) => {
181218
- const raw2 = await manager.repo.getDocMeta(getSessionRoomId(sessionId));
181219
- return !!raw2?.meta && !isLoroRepoDocDeleted(raw2);
181220
- });
181978
+ const exists = await sessionExistsInWorkspace(workspace);
181221
181979
  if (!exists) {
181222
181980
  throw new Error(`Session not found: ${sessionId}`);
181223
181981
  }
@@ -181225,10 +181983,7 @@ ${entry.text}`).join("\n\n");
181225
181983
  }
181226
181984
  const matches = [];
181227
181985
  for (const workspace of workspaces) {
181228
- const exists = await withWorkspaceManager(auth, workspace, async (manager) => {
181229
- const raw2 = await manager.repo.getDocMeta(getSessionRoomId(sessionId));
181230
- return !!raw2?.meta && !isLoroRepoDocDeleted(raw2);
181231
- });
181986
+ const exists = await sessionExistsInWorkspace(workspace);
181232
181987
  if (exists) {
181233
181988
  matches.push(workspace);
181234
181989
  }
@@ -181241,6 +181996,13 @@ ${entry.text}`).join("\n\n");
181241
181996
  }
181242
181997
  return matches[0];
181243
181998
  }
181999
+ async function syncSessionReadData(manager, sessionId, offline, reason) {
182000
+ if (offline === true) {
182001
+ return;
182002
+ }
182003
+ await syncWorkspaceMetaForRead(manager, `${reason}:meta`);
182004
+ await syncDocForRead(manager, getSessionRoomId(sessionId), `${reason}:doc`);
182005
+ }
181244
182006
  async function resolveSessionMetaOrThrow(manager, sessionId) {
181245
182007
  const raw2 = await manager.repo.getDocMeta(getSessionRoomId(sessionId));
181246
182008
  if (!raw2?.meta || isLoroRepoDocDeleted(raw2)) {
@@ -181575,6 +182337,37 @@ ${entry.text}`).join("\n\n");
181575
182337
  messageQueueCount: docState?.mq?.length ?? 0
181576
182338
  };
181577
182339
  }
182340
+ async function buildSessionStatusResult(workspace, manager, sessionId) {
182341
+ const session = await resolveSessionMetaOrThrow(manager, sessionId);
182342
+ const sessionDoc = await manager.getOrCreateSessionDoc(sessionId);
182343
+ const history = await sessionDoc.getHistory();
182344
+ const assistantTurnId = resolveActiveAssistantTurnId(history);
182345
+ return {
182346
+ workspace,
182347
+ sessionId,
182348
+ status: session.status,
182349
+ machineId: session.machineId,
182350
+ agent: {
182351
+ cliType: session.cliType,
182352
+ agentType: session.agentType,
182353
+ ...session.agentConfigId ? {
182354
+ agentConfigId: session.agentConfigId
182355
+ } : {}
182356
+ },
182357
+ archived: session.isArchived === true,
182358
+ ...assistantTurnId ? {
182359
+ activeTurn: {
182360
+ assistantTurnId,
182361
+ ...session.processingUserMsgId ? {
182362
+ processingUserMsgId: session.processingUserMsgId
182363
+ } : {},
182364
+ ...session.latestUserMsgId ? {
182365
+ latestUserMsgId: session.latestUserMsgId
182366
+ } : {}
182367
+ }
182368
+ } : {}
182369
+ };
182370
+ }
181578
182371
  function printHumanSessionList(sessions) {
181579
182372
  if (sessions.length === 0) {
181580
182373
  console.log("No sessions found.");
@@ -181622,6 +182415,16 @@ ${entry.text}`).join("\n\n");
181622
182415
  console.log(`createdAt: ${session.createdAt}`);
181623
182416
  console.log(`lastHistoryAt: ${result.latestHistoryAt ?? "-"}`);
181624
182417
  }
182418
+ function printHumanSessionStatus(result) {
182419
+ console.log(`id: ${result.sessionId}`);
182420
+ console.log(`workspace: ${result.workspace.slug ?? result.workspace.id}`);
182421
+ console.log(`machine: ${result.machineId}`);
182422
+ console.log(`status: ${result.status?.type ?? "unknown"}`);
182423
+ console.log(`archived: ${result.archived ? "yes" : "no"}`);
182424
+ console.log(`agent: ${result.agent.cliType}/${result.agent.agentType}`);
182425
+ console.log(`agentConfigId: ${result.agent.agentConfigId ?? "-"}`);
182426
+ console.log(`activeTurn: ${result.activeTurn?.assistantTurnId ?? "-"}`);
182427
+ }
181625
182428
  async function runSessionCommand(options, action) {
181626
182429
  if (options.debug) {
181627
182430
  rootLogger.setDebug(true);
@@ -181680,7 +182483,7 @@ ${entry.text}`).join("\n\n");
181680
182483
  process.exit(code2);
181681
182484
  }
181682
182485
  }
181683
- const sessionCreateCommand = new Command("create").description("Create a new session on the current machine").option("--workspace <idOrSlug>", "Target workspace id or slug").option("--agent-config <idOrName>", "Agent config id or name").option("--title <title>", "Session title").option("--repo <owner/repo>", "GitHub repository to attach").option("--local-project <id|name|path>", "Local project id, name, or root path").option("--worktree", "Create an isolated git worktree for --local-project").option("--branch <name>", "Git branch to use for GitHub repos or local git projects").option("--mode <modeId>", "ACP mode override").option("--model <modelId>", "ACP model override").option("--env <keyValue>", "Deprecated per-session env override; configure env on the agent config instead", collectListOption, []).option("--prompt <text>", "Prompt text").option("--prompt-file <path>", "Read prompt text from file, or - for stdin").option("--json", "Print JSON output").option("--jsonl", "Print JSON Lines output").option("--timeout <seconds>", "Wait timeout in seconds for structured output", parsePositiveIntOption).option("--debug", "Enable debug output").argument("[prompt]", "Prompt text").action(async (promptArg, options) => {
182486
+ const sessionCreateCommand = new Command("create").description("Create a new session on the current machine").option("--workspace <idOrSlug>", "Target workspace id or slug").option("--agent <idOrName>", "Agent config id or name").option("--agent-config <idOrName>", "Agent config id or name").option("--title <title>", "Session title").option("--repo <owner/repo>", "GitHub repository to attach").option("--local-project <id|name|path>", "Local project id, name, or root path").option("--worktree", "Create an isolated git worktree for --local-project").option("--branch <name>", "Git branch to use for GitHub repos or local git projects").option("--mode <modeId>", "ACP mode override").option("--model <modelId>", "ACP model override").option("--env <keyValue>", "Deprecated per-session env override; configure env on the agent config instead", collectListOption, []).option("--prompt <text>", "Prompt text").option("--prompt-file <path>", "Read prompt text from file, or - for stdin").option("--json", "Print JSON output").option("--jsonl", "Print JSON Lines output").option("--wait", "Wait for the assistant turn to complete before exiting").option("--timeout <seconds>", "Wait timeout in seconds for --wait, --json, and --jsonl", parsePositiveIntOption$1).option("--debug", "Enable debug output").argument("[prompt]", "Prompt text").action(async (promptArg, options) => {
181684
182487
  await runSessionCommand(options, async () => {
181685
182488
  const outputMode = resolveStructuredOutputMode(options);
181686
182489
  const createStartMs = Date.now();
@@ -181693,12 +182496,16 @@ ${entry.text}`).join("\n\n");
181693
182496
  const prompt2 = await readPromptText(options, promptArg);
181694
182497
  await ensureLocalRuntimeAvailable(auth.machineId, workspace.id);
181695
182498
  await withWorkspaceManager(auth, workspace, async (manager) => {
181696
- const agentConfig = await resolveAgentConfigOrThrow(manager, workspace.id, options.agentConfig);
182499
+ const agentSelector = resolveCreateAgentSelector(options);
182500
+ const agentConfig = await resolveAgentConfigOrThrow(manager, workspace.id, agentSelector);
181697
182501
  const dispatchConfig = resolveTurnDispatchConfig({
181698
182502
  mode: options.mode,
181699
182503
  model: options.model
181700
182504
  });
181701
- const result = await createSessionResult(auth, workspace, manager, prompt2, options, agentConfig, dispatchConfig, outputMode === "human" ? void 0 : {
182505
+ const result = await createSessionResult(auth, workspace, manager, prompt2, options, agentConfig, dispatchConfig, outputMode === "human" ? options.wait === true ? {
182506
+ outputMode: "json",
182507
+ timeoutMs: resolveStructuredOutputTimeoutMs(options.timeout)
182508
+ } : void 0 : {
181702
182509
  outputMode,
181703
182510
  timeoutMs: resolveStructuredOutputTimeoutMs(options.timeout),
181704
182511
  onEvent: outputMode === "jsonl" ? (event) => printJson(event) : void 0
@@ -181749,13 +182556,29 @@ ${entry.text}`).join("\n\n");
181749
182556
  }
181750
182557
  return;
181751
182558
  }
182559
+ console.log(result.sessionId);
182560
+ if (options.wait === true) {
182561
+ const completionPromise = result.completionPromise;
182562
+ if (!completionPromise) {
182563
+ throw new Error("Missing completion promise for session create --wait output.");
182564
+ }
182565
+ const completedTurn = await completionPromise;
182566
+ captureSessionCommandEvent("session_create_succeeded", {
182567
+ output_mode: outputMode,
182568
+ turn_duration_ms: completedTurn.durationMs
182569
+ }, {
182570
+ distinctId: auth.machineId
182571
+ });
182572
+ console.log("");
182573
+ console.log(renderAssistantTurnCompletion(completedTurn.content));
182574
+ return;
182575
+ }
181752
182576
  captureSessionCommandEvent("session_create_succeeded", {
181753
182577
  output_mode: outputMode,
181754
182578
  turn_duration_ms: Date.now() - createStartMs
181755
182579
  }, {
181756
182580
  distinctId: auth.machineId
181757
182581
  });
181758
- console.log(result.sessionId);
181759
182582
  });
181760
182583
  } catch (error2) {
181761
182584
  captureSessionCommandEvent("session_create_failed", {
@@ -181766,7 +182589,7 @@ ${entry.text}`).join("\n\n");
181766
182589
  }
181767
182590
  });
181768
182591
  });
181769
- const sessionChatCommand = new Command("chat").description("Send a new user prompt to an existing session on the current machine").option("--workspace <idOrSlug>", "Target workspace id or slug").option("--mode <modeId>", "ACP mode override").option("--model <modelId>", "ACP model override").option("--prompt <text>", "Prompt text").option("--prompt-file <path>", "Read prompt text from file, or - for stdin").option("--json", "Print JSON output").option("--jsonl", "Print JSON Lines output").option("--timeout <seconds>", "Wait timeout in seconds for structured output", parsePositiveIntOption).option("--debug", "Enable debug output").argument("[sessionId]", "Session ID; falls back to LODY_SESSION_ID").argument("[prompt]", "Prompt text").action(async (sessionIdArg, promptArg, options) => {
182592
+ const sessionChatCommand = new Command("chat").description("Send a new user prompt to an existing session on the current machine").option("--workspace <idOrSlug>", "Target workspace id or slug").option("--mode <modeId>", "ACP mode override").option("--model <modelId>", "ACP model override").option("--prompt <text>", "Prompt text").option("--prompt-file <path>", "Read prompt text from file, or - for stdin").option("--json", "Print JSON output").option("--jsonl", "Print JSON Lines output").option("--wait", "Wait for the assistant turn to complete before exiting").option("--timeout <seconds>", "Wait timeout in seconds for --wait, --json, and --jsonl", parsePositiveIntOption$1).option("--debug", "Enable debug output").argument("[sessionId]", "Session ID; falls back to LODY_SESSION_ID").argument("[prompt]", "Prompt text").action(async (sessionIdArg, promptArg, options) => {
181770
182593
  await runSessionCommand(options, async () => {
181771
182594
  const outputMode = resolveStructuredOutputMode(options);
181772
182595
  const auth = getAuthContextOrThrow();
@@ -181813,15 +182636,16 @@ ${entry.text}`).join("\n\n");
181813
182636
  configOptionValues: dispatchConfig.configOptionValues,
181814
182637
  resume: session.acpSessionId ?? void 0
181815
182638
  }));
181816
- const completionAbortController = outputMode === "human" ? void 0 : new AbortController();
181817
- const completionPromise = outputMode === "human" ? void 0 : waitForTurnCompletion({
182639
+ const shouldWaitForCompletion = outputMode !== "human" || options.wait === true;
182640
+ const completionAbortController = shouldWaitForCompletion ? new AbortController() : void 0;
182641
+ const completionPromise = shouldWaitForCompletion ? waitForTurnCompletion({
181818
182642
  sessionDoc,
181819
182643
  userTurnId,
181820
- outputMode,
182644
+ outputMode: outputMode === "human" ? "json" : outputMode,
181821
182645
  timeoutMs: resolveStructuredOutputTimeoutMs(options.timeout),
181822
182646
  signal: completionAbortController?.signal,
181823
182647
  onEvent: outputMode === "jsonl" ? (event) => printJson(event) : void 0
181824
- });
182648
+ }) : void 0;
181825
182649
  try {
181826
182650
  await updateSessionActivityTimestampsBestEffort(manager, sessionId);
181827
182651
  await ensureSessionDocSynced(sessionDoc, `session.chat:${sessionId}:${userTurnId}`);
@@ -181862,6 +182686,18 @@ ${entry.text}`).join("\n\n");
181862
182686
  }
181863
182687
  return;
181864
182688
  }
182689
+ if (options.wait === true) {
182690
+ try {
182691
+ if (!completionPromise) {
182692
+ throw new Error("Missing completion promise for session chat --wait output.");
182693
+ }
182694
+ const completedTurn = await completionPromise;
182695
+ console.log(renderAssistantTurnCompletion(completedTurn.content));
182696
+ } catch (error2) {
182697
+ throw buildStructuredWaitError("json", sessionId, userTurnId, error2);
182698
+ }
182699
+ return;
182700
+ }
181865
182701
  console.log(userTurnId);
181866
182702
  });
181867
182703
  });
@@ -181903,11 +182739,14 @@ ${entry.text}`).join("\n\n");
181903
182739
  });
181904
182740
  });
181905
182741
  });
181906
- const sessionListCommand = new Command("list").description("List sessions in a workspace").option("--workspace <idOrSlug>", "Target workspace id or slug").option("--archived", "Only include archived sessions").option("--all", "Include active and archived sessions").option("--limit <count>", "Maximum number of sessions to print", parsePositiveIntOption).option("--json", "Print JSON output").option("--debug", "Enable debug output").action(async (options) => {
182742
+ const sessionListCommand = new Command("list").description("List sessions in a workspace").option("--workspace <idOrSlug>", "Target workspace id or slug").option("--archived", "Only include archived sessions").option("--all", "Include active and archived sessions").option("--limit <count>", "Maximum number of sessions to print", parsePositiveIntOption$1).option("--offline", "Read the local cache without syncing first").option("--json", "Print JSON output").option("--debug", "Enable debug output").action(async (options) => {
181907
182743
  await runSessionCommand(options, async () => {
181908
182744
  const auth = getAuthContextOrThrow();
181909
182745
  const workspace = await resolveWorkspaceOrThrow(auth, options.workspace);
181910
182746
  await withWorkspaceManager(auth, workspace, async (manager) => {
182747
+ if (options.offline !== true) {
182748
+ await syncWorkspaceMetaForRead(manager, `session.list:${workspace.id}`);
182749
+ }
181911
182750
  const sessions = sortSessionMetas(filterSessionMetas(await listSessionMetasForWorkspace(manager), {
181912
182751
  archivedOnly: options.archived,
181913
182752
  includeAll: options.all
@@ -181925,7 +182764,7 @@ ${entry.text}`).join("\n\n");
181925
182764
  });
181926
182765
  });
181927
182766
  });
181928
- const sessionHistoryCommand = new Command("history").description("Read visible session transcript history").option("--workspace <idOrSlug>", "Target workspace id or slug").option("--limit <count>", "Maximum number of transcript turns to print", parsePositiveIntOption).option("--all", "Include all transcript turns").option("--reverse", "Print newest transcript turns first").option("--json", "Print JSON output").option("--jsonl", "Print JSON Lines output").option("--debug", "Enable debug output").argument("[sessionId]", "Session ID; falls back to LODY_SESSION_ID").action(async (sessionIdArg, options) => {
182767
+ const sessionHistoryCommand = new Command("history").description("Read visible session transcript history").option("--workspace <idOrSlug>", "Target workspace id or slug").option("--limit <count>", "Maximum number of transcript turns to print", parsePositiveIntOption$1).option("--all", "Include all transcript turns").option("--reverse", "Print newest transcript turns first").option("--offline", "Read the local cache without syncing first").option("--json", "Print JSON output").option("--jsonl", "Print JSON Lines output").option("--debug", "Enable debug output").argument("[sessionId]", "Session ID; falls back to LODY_SESSION_ID").action(async (sessionIdArg, options) => {
181929
182768
  await runSessionCommand(options, async () => {
181930
182769
  const outputMode = resolveStructuredOutputMode(options);
181931
182770
  if (options.all && typeof options.limit === "number") {
@@ -181936,8 +182775,13 @@ ${entry.text}`).join("\n\n");
181936
182775
  if (!sessionId) {
181937
182776
  throw new Error("Missing session ID. Pass one explicitly or set LODY_SESSION_ID.");
181938
182777
  }
181939
- const workspace = await resolveWorkspaceForSessionOrThrow(auth, sessionId, options.workspace);
182778
+ const workspace = await resolveWorkspaceForSessionOrThrow(auth, sessionId, {
182779
+ workspace: options.workspace,
182780
+ offline: options.offline,
182781
+ reason: `session.history.resolve:${sessionId}`
182782
+ });
181940
182783
  await withWorkspaceManager(auth, workspace, async (manager) => {
182784
+ await syncSessionReadData(manager, sessionId, options.offline, `session.history:${sessionId}`);
181941
182785
  await resolveSessionMetaOrThrow(manager, sessionId);
181942
182786
  const sessionDoc = await manager.getOrCreateSessionDoc(sessionId);
181943
182787
  const transcript = toSessionTranscriptEntries(await sessionDoc.getHistory());
@@ -181971,15 +182815,20 @@ ${entry.text}`).join("\n\n");
181971
182815
  });
181972
182816
  });
181973
182817
  });
181974
- const sessionShowCommand = new Command("show").description("Show session metadata").option("--workspace <idOrSlug>", "Target workspace id or slug").option("--json", "Print JSON output").option("--debug", "Enable debug output").argument("[sessionId]", "Session ID; falls back to LODY_SESSION_ID").action(async (sessionIdArg, options) => {
182818
+ const sessionShowCommand = new Command("show").description("Show session metadata").option("--workspace <idOrSlug>", "Target workspace id or slug").option("--offline", "Read the local cache without syncing first").option("--json", "Print JSON output").option("--debug", "Enable debug output").argument("[sessionId]", "Session ID; falls back to LODY_SESSION_ID").action(async (sessionIdArg, options) => {
181975
182819
  await runSessionCommand(options, async () => {
181976
182820
  const auth = getAuthContextOrThrow();
181977
182821
  const sessionId = normalizeCliValue(sessionIdArg) ?? normalizeCliValue(process.env.LODY_SESSION_ID);
181978
182822
  if (!sessionId) {
181979
182823
  throw new Error("Missing session ID. Pass one explicitly or set LODY_SESSION_ID.");
181980
182824
  }
181981
- const workspace = await resolveWorkspaceForSessionOrThrow(auth, sessionId, options.workspace);
182825
+ const workspace = await resolveWorkspaceForSessionOrThrow(auth, sessionId, {
182826
+ workspace: options.workspace,
182827
+ offline: options.offline,
182828
+ reason: `session.show.resolve:${sessionId}`
182829
+ });
181982
182830
  await withWorkspaceManager(auth, workspace, async (manager) => {
182831
+ await syncSessionReadData(manager, sessionId, options.offline, `session.show:${sessionId}`);
181983
182832
  const result = await buildSessionShowResult(workspace, manager, sessionId);
181984
182833
  if (options.json) {
181985
182834
  printJson({
@@ -181992,6 +182841,32 @@ ${entry.text}`).join("\n\n");
181992
182841
  });
181993
182842
  });
181994
182843
  });
182844
+ const sessionStatusCommand = new Command("status").description("Show current session status").option("--workspace <idOrSlug>", "Target workspace id or slug").option("--offline", "Read the local cache without syncing first").option("--json", "Print JSON output").option("--debug", "Enable debug output").argument("[sessionId]", "Session ID; falls back to LODY_SESSION_ID").action(async (sessionIdArg, options) => {
182845
+ await runSessionCommand(options, async () => {
182846
+ const auth = getAuthContextOrThrow();
182847
+ const sessionId = normalizeCliValue(sessionIdArg) ?? normalizeCliValue(process.env.LODY_SESSION_ID);
182848
+ if (!sessionId) {
182849
+ throw new Error("Missing session ID. Pass one explicitly or set LODY_SESSION_ID.");
182850
+ }
182851
+ const workspace = await resolveWorkspaceForSessionOrThrow(auth, sessionId, {
182852
+ workspace: options.workspace,
182853
+ offline: options.offline,
182854
+ reason: `session.status.resolve:${sessionId}`
182855
+ });
182856
+ await withWorkspaceManager(auth, workspace, async (manager) => {
182857
+ await syncSessionReadData(manager, sessionId, options.offline, `session.status:${sessionId}`);
182858
+ const result = await buildSessionStatusResult(workspace, manager, sessionId);
182859
+ if (options.json) {
182860
+ printJson({
182861
+ ok: true,
182862
+ ...result
182863
+ });
182864
+ return;
182865
+ }
182866
+ printHumanSessionStatus(result);
182867
+ });
182868
+ });
182869
+ });
181995
182870
  const sessionRenameCommand = new Command("rename").description("Rename a session").option("--workspace <idOrSlug>", "Target workspace id or slug").option("--title <title>", "New session title").option("--json", "Print JSON output").option("--debug", "Enable debug output").argument("[sessionId]", "Session ID; falls back to LODY_SESSION_ID").argument("[title]", "New session title").action(async (sessionIdArg, titleArg, options) => {
181996
182871
  await runSessionCommand(options, async () => {
181997
182872
  const auth = getAuthContextOrThrow();
@@ -182200,7 +183075,270 @@ ${entry.text}`).join("\n\n");
182200
183075
  });
182201
183076
  });
182202
183077
  });
182203
- const sessionCommand = new Command("session").description("Manage sessions without the web UI").addCommand(sessionCreateCommand).addCommand(sessionChatCommand).addCommand(sessionCancelCommand).addCommand(sessionListCommand).addCommand(sessionHistoryCommand).addCommand(sessionShowCommand).addCommand(sessionRenameCommand).addCommand(sessionArchiveCommand).addCommand(sessionRestoreCommand).addCommand(sessionDeleteCommand);
183078
+ const sessionCommand = new Command("session").description("Manage sessions without the web UI").addCommand(sessionCreateCommand).addCommand(sessionChatCommand).addCommand(sessionCancelCommand).addCommand(sessionListCommand).addCommand(sessionHistoryCommand).addCommand(sessionShowCommand).addCommand(sessionStatusCommand).addCommand(sessionRenameCommand).addCommand(sessionArchiveCommand).addCommand(sessionRestoreCommand).addCommand(sessionDeleteCommand);
183079
+ async function mapWithConcurrency(items2, concurrency, worker) {
183080
+ if (items2.length === 0) {
183081
+ return [];
183082
+ }
183083
+ const limit2 = Math.max(1, Math.floor(concurrency));
183084
+ const results = new Array(items2.length);
183085
+ let nextIndex = 0;
183086
+ const runWorker = async () => {
183087
+ while (nextIndex < items2.length) {
183088
+ const currentIndex = nextIndex;
183089
+ nextIndex += 1;
183090
+ results[currentIndex] = await worker(items2[currentIndex], currentIndex);
183091
+ }
183092
+ };
183093
+ await Promise.all(Array.from({
183094
+ length: Math.min(limit2, items2.length)
183095
+ }, () => runWorker()));
183096
+ return results;
183097
+ }
183098
+ const DEFAULT_SYNC_CONCURRENCY = 4;
183099
+ function parsePositiveIntOption(value) {
183100
+ const parsed = Number.parseInt(value, 10);
183101
+ if (!Number.isFinite(parsed) || parsed <= 0) {
183102
+ throw new Error(`Invalid numeric value: ${value}`);
183103
+ }
183104
+ return parsed;
183105
+ }
183106
+ function createWorkspaceSummary(workspaceId) {
183107
+ return {
183108
+ workspaceId,
183109
+ totals: {
183110
+ meta: 0,
183111
+ doc: 0,
183112
+ flock: 0
183113
+ },
183114
+ completed: {
183115
+ meta: 0,
183116
+ doc: 0,
183117
+ flock: 0
183118
+ },
183119
+ failed: {
183120
+ meta: 0,
183121
+ doc: 0,
183122
+ flock: 0
183123
+ },
183124
+ failures: []
183125
+ };
183126
+ }
183127
+ function mergeSummaries(workspaces) {
183128
+ const failures2 = workspaces.flatMap((workspace) => workspace.failures);
183129
+ let total = 0;
183130
+ let completed = 0;
183131
+ let failed = 0;
183132
+ for (const workspace of workspaces) {
183133
+ for (const kind of [
183134
+ "meta",
183135
+ "doc",
183136
+ "flock"
183137
+ ]) {
183138
+ total += workspace.totals[kind];
183139
+ completed += workspace.completed[kind];
183140
+ failed += workspace.failed[kind];
183141
+ }
183142
+ }
183143
+ return {
183144
+ ok: failures2.length === 0,
183145
+ workspaces,
183146
+ total,
183147
+ completed,
183148
+ failed,
183149
+ failures: failures2
183150
+ };
183151
+ }
183152
+ function buildProgressEvent(input2) {
183153
+ return {
183154
+ type: "progress",
183155
+ workspaceId: input2.workspaceId,
183156
+ kind: input2.kind,
183157
+ id: input2.id,
183158
+ total: input2.total,
183159
+ completed: input2.completed,
183160
+ failed: input2.failed,
183161
+ remaining: Math.max(0, input2.total - input2.completed - input2.failed),
183162
+ ok: input2.ok,
183163
+ ...input2.error ? {
183164
+ error: input2.error
183165
+ } : {}
183166
+ };
183167
+ }
183168
+ function printHumanProgress(event) {
183169
+ const status = event.ok ? "synced" : "failed";
183170
+ const failedText = event.failed > 0 ? `, failed ${event.failed}` : "";
183171
+ console.log(`[${event.workspaceId}] ${event.kind} ${event.id}: ${status} (${event.completed}/${event.total}${failedText})`);
183172
+ if (event.error) {
183173
+ console.log(` ${event.error}`);
183174
+ }
183175
+ }
183176
+ function emitProgress(event, outputMode) {
183177
+ if (outputMode === "jsonl") {
183178
+ printJson(event);
183179
+ return;
183180
+ }
183181
+ if (outputMode === "human") {
183182
+ printHumanProgress(event);
183183
+ }
183184
+ }
183185
+ function recordSyncResult(args2) {
183186
+ if (args2.ok) {
183187
+ args2.summary.completed[args2.kind] += 1;
183188
+ } else {
183189
+ args2.summary.failed[args2.kind] += 1;
183190
+ args2.summary.failures.push({
183191
+ workspaceId: args2.summary.workspaceId,
183192
+ kind: args2.kind,
183193
+ id: args2.id,
183194
+ error: args2.error ?? "Sync failed."
183195
+ });
183196
+ }
183197
+ emitProgress(buildProgressEvent({
183198
+ workspaceId: args2.summary.workspaceId,
183199
+ kind: args2.kind,
183200
+ id: args2.id,
183201
+ total: args2.summary.totals[args2.kind],
183202
+ completed: args2.summary.completed[args2.kind],
183203
+ failed: args2.summary.failed[args2.kind],
183204
+ ok: args2.ok,
183205
+ error: args2.error
183206
+ }), args2.outputMode);
183207
+ }
183208
+ async function syncItems(input2) {
183209
+ input2.summary.totals[input2.kind] = input2.ids.length;
183210
+ await mapWithConcurrency(input2.ids, input2.concurrency, async (id2) => {
183211
+ try {
183212
+ await input2.syncOne(id2);
183213
+ recordSyncResult({
183214
+ summary: input2.summary,
183215
+ kind: input2.kind,
183216
+ id: id2,
183217
+ ok: true,
183218
+ outputMode: input2.outputMode
183219
+ });
183220
+ } catch (error2) {
183221
+ recordSyncResult({
183222
+ summary: input2.summary,
183223
+ kind: input2.kind,
183224
+ id: id2,
183225
+ ok: false,
183226
+ error: formatErrorMessage(error2),
183227
+ outputMode: input2.outputMode
183228
+ });
183229
+ }
183230
+ });
183231
+ }
183232
+ async function listMachineFlockDocIds(manager, workspaceId) {
183233
+ const machines = await listAliveDocMetas(manager, isMachineDocRoomId);
183234
+ return machines.map((entry) => getMachineFlockDocId(workspaceId, entry.meta.id)).sort((left2, right2) => left2.localeCompare(right2));
183235
+ }
183236
+ async function syncWorkspace(input2) {
183237
+ const workspaceId = input2.workspace.id;
183238
+ const summary2 = createWorkspaceSummary(input2.workspace.id);
183239
+ await withWorkspaceManager$1(input2.auth, input2.workspace, "sync", async (manager) => {
183240
+ summary2.totals.meta = 1;
183241
+ try {
183242
+ await manager.syncMetaOrThrow({
183243
+ reason: `sync:${workspaceId}:meta`
183244
+ });
183245
+ recordSyncResult({
183246
+ summary: summary2,
183247
+ kind: "meta",
183248
+ id: "meta",
183249
+ ok: true,
183250
+ outputMode: input2.outputMode
183251
+ });
183252
+ } catch (error2) {
183253
+ recordSyncResult({
183254
+ summary: summary2,
183255
+ kind: "meta",
183256
+ id: "meta",
183257
+ ok: false,
183258
+ error: formatErrorMessage(error2),
183259
+ outputMode: input2.outputMode
183260
+ });
183261
+ return;
183262
+ }
183263
+ const docIds = (await listAliveRoomIds(manager, () => true)).sort((left2, right2) => left2.localeCompare(right2));
183264
+ const flockDocIds = await listMachineFlockDocIds(manager, workspaceId);
183265
+ await syncItems({
183266
+ summary: summary2,
183267
+ kind: "doc",
183268
+ ids: docIds,
183269
+ concurrency: input2.concurrency,
183270
+ outputMode: input2.outputMode,
183271
+ syncOne: async (id2) => {
183272
+ await manager.syncDocOrThrow(id2, {
183273
+ reason: `sync:${workspaceId}:doc:${id2}`
183274
+ });
183275
+ }
183276
+ });
183277
+ await syncItems({
183278
+ summary: summary2,
183279
+ kind: "flock",
183280
+ ids: flockDocIds,
183281
+ concurrency: input2.concurrency,
183282
+ outputMode: input2.outputMode,
183283
+ syncOne: async (id2) => {
183284
+ await manager.syncFlockDocOrThrow(id2, {
183285
+ reason: `sync:${workspaceId}:flock:${id2}`
183286
+ });
183287
+ }
183288
+ });
183289
+ });
183290
+ return summary2;
183291
+ }
183292
+ function printHumanSummary(summary2) {
183293
+ console.log(`Finished sync: ${summary2.completed}/${summary2.total} item(s) synced, ${summary2.failed} failed.`);
183294
+ if (summary2.failures.length > 0) {
183295
+ console.log("Failures:");
183296
+ for (const failure of summary2.failures) {
183297
+ console.log(`- [${failure.workspaceId}] ${failure.kind} ${failure.id}: ${failure.error}`);
183298
+ }
183299
+ }
183300
+ }
183301
+ function createAlreadyPrintedError() {
183302
+ return Object.assign(new Error("Sync completed with failures."), {
183303
+ suppressCommandErrorOutput: true,
183304
+ exitCode: 1
183305
+ });
183306
+ }
183307
+ const syncCommand = new Command("sync").description("Sync workspace Loro data to the local cache").option("--workspace <idOrSlug>", "Target workspace id or slug").option("--all-workspace", "Sync all accessible workspaces").option("--concurrency <count>", "Maximum number of documents to sync concurrently", parsePositiveIntOption, DEFAULT_SYNC_CONCURRENCY).option("--json", "Print final JSON summary").option("--jsonl", "Print JSON Lines progress events and final summary").option("--debug", "Enable debug output").action(async (options) => {
183308
+ await runOneShotCommand("sync", options, async () => {
183309
+ const outputMode = resolveStructuredOutputMode$1(options);
183310
+ const auth = getAuthContextOrThrow$1("sync");
183311
+ if (options.allWorkspace && options.workspace) {
183312
+ throw new Error("Pass either --workspace or --all-workspace, not both.");
183313
+ }
183314
+ const workspaces = options.allWorkspace ? await listWorkspacesForToken(auth.token) : [
183315
+ await resolveWorkspaceOrThrow$1(auth, options.workspace)
183316
+ ];
183317
+ const workspaceSummaries = [];
183318
+ for (const workspace of workspaces) {
183319
+ workspaceSummaries.push(await syncWorkspace({
183320
+ auth,
183321
+ workspace,
183322
+ concurrency: options.concurrency ?? DEFAULT_SYNC_CONCURRENCY,
183323
+ outputMode
183324
+ }));
183325
+ }
183326
+ const summary2 = mergeSummaries(workspaceSummaries);
183327
+ if (outputMode === "jsonl") {
183328
+ printJson({
183329
+ type: "summary",
183330
+ ...summary2
183331
+ });
183332
+ } else if (outputMode === "json") {
183333
+ printJson(summary2);
183334
+ } else {
183335
+ printHumanSummary(summary2);
183336
+ }
183337
+ if (!summary2.ok) {
183338
+ throw createAlreadyPrintedError();
183339
+ }
183340
+ });
183341
+ });
182204
183342
  function sortWorkspaceSummaries(workspaces) {
182205
183343
  return [
182206
183344
  ...workspaces
@@ -182342,19 +183480,53 @@ ${entry.text}`).join("\n\n");
182342
183480
  } : {}
182343
183481
  };
182344
183482
  }
182345
- function toMachineJsonEntry(machine, includeAcpCapabilities) {
182346
- if (includeAcpCapabilities) {
182347
- return machine;
183483
+ function toMachineJsonEntry(machine, options) {
183484
+ const withoutOptional = {
183485
+ ...machine
183486
+ };
183487
+ if (!options.includeAcpCapabilities) {
183488
+ delete withoutOptional.acpCapabilities;
182348
183489
  }
182349
- const { acpCapabilities: _acpCapabilities, ...rest } = machine;
182350
- return rest;
183490
+ if (!options.includeAgents) {
183491
+ delete withoutOptional.agentConfigs;
183492
+ }
183493
+ return withoutOptional;
183494
+ }
183495
+ function formatMachineAgents(machine) {
183496
+ const configs = machine.agentConfigs ?? [];
183497
+ if (configs.length === 0) {
183498
+ return "-";
183499
+ }
183500
+ return configs.map((config2) => `${config2.name} (${config2.agentType})`).join(",");
182351
183501
  }
182352
- function printHumanMachineList(machines, currentMachineId) {
183502
+ async function attachAgentConfigsToMachines(repo, workspaceId, machines) {
183503
+ const configs = await listMergedAgentConfigs(repo, workspaceId, machines.map((machine) => machine.id));
183504
+ const configsByMachine = /* @__PURE__ */ new Map();
183505
+ for (const config2 of configs) {
183506
+ const current2 = configsByMachine.get(config2.machineId) ?? [];
183507
+ current2.push(config2);
183508
+ configsByMachine.set(config2.machineId, current2);
183509
+ }
183510
+ for (const values of configsByMachine.values()) {
183511
+ values.sort((left2, right2) => {
183512
+ const nameCompare = left2.name.localeCompare(right2.name);
183513
+ if (nameCompare !== 0) {
183514
+ return nameCompare;
183515
+ }
183516
+ return left2.id.localeCompare(right2.id);
183517
+ });
183518
+ }
183519
+ return machines.map((machine) => ({
183520
+ ...machine,
183521
+ agentConfigs: configsByMachine.get(machine.id) ?? []
183522
+ }));
183523
+ }
183524
+ function printHumanMachineList(machines, currentMachineId, includeAgents) {
182353
183525
  if (machines.length === 0) {
182354
183526
  console.log("No machines found.");
182355
183527
  return;
182356
183528
  }
182357
- console.log(renderTerminalTable([
183529
+ const columns = [
182358
183530
  {
182359
183531
  header: "ID"
182360
183532
  },
@@ -182367,30 +183539,49 @@ ${entry.text}`).join("\n\n");
182367
183539
  {
182368
183540
  header: "CLI"
182369
183541
  }
182370
- ], machines.map((machine) => [
182371
- machine.id,
182372
- machine.id === currentMachineId ? `${machine.name} (current)` : machine.name,
182373
- machine.online ? "online" : "offline",
182374
- formatMachineCli(machine)
182375
- ])));
183542
+ ];
183543
+ if (includeAgents) {
183544
+ columns.push({
183545
+ header: "Agents"
183546
+ });
183547
+ }
183548
+ console.log(renderTerminalTable(columns, machines.map((machine) => {
183549
+ const row = [
183550
+ machine.id,
183551
+ machine.id === currentMachineId ? `${machine.name} (current)` : machine.name,
183552
+ machine.online ? "online" : "offline",
183553
+ formatMachineCli(machine)
183554
+ ];
183555
+ if (includeAgents) {
183556
+ row.push(formatMachineAgents(machine));
183557
+ }
183558
+ return row;
183559
+ })));
182376
183560
  }
182377
- const machineCommand = new Command("machine").description("Inspect registered machines").addCommand(new Command("list").description("List machines in a workspace").option("--workspace <idOrSlug>", "Target workspace id or slug").option("--online-only", "Only include machines with a recent heartbeat").option("--json", "Print JSON output").option("--include-acp-capabilities", "Include acpCapabilities in JSON output").option("--debug", "Enable debug output").action(async (options) => {
183561
+ const machineCommand = new Command("machine").description("Inspect registered machines").addCommand(new Command("list").description("List machines in a workspace").option("--workspace <idOrSlug>", "Target workspace id or slug").option("--online-only", "Only include machines with a recent heartbeat").option("--json", "Print JSON output").option("--include-acp-capabilities", "Include acpCapabilities in JSON output").option("--include-agents", "Include agent config summaries per machine").option("--debug", "Enable debug output").action(async (options) => {
182378
183562
  await runOneShotCommand("machine", options, async () => {
182379
183563
  const auth = getAuthContextOrThrow$1("machine");
182380
183564
  const workspace = await resolveWorkspaceOrThrow$1(auth, options.workspace);
182381
183565
  await withWorkspaceManager$1(auth, workspace, "machine", async (manager) => {
182382
- const machines = sortMachineMetas((await listAliveDocMetas(manager, isMachineDocRoomId)).map((entry) => entry.meta), auth.machineId).map(toMachineListEntry).filter((machine) => !options.onlineOnly || machine.online);
183566
+ let machines = sortMachineMetas((await listAliveDocMetas(manager, isMachineDocRoomId)).map((entry) => entry.meta), auth.machineId).map(toMachineListEntry).filter((machine) => !options.onlineOnly || machine.online);
183567
+ if (options.includeAgents === true) {
183568
+ machines = await attachAgentConfigsToMachines(manager.repo, workspace.id, machines);
183569
+ }
182383
183570
  if (options.json) {
182384
183571
  const includeAcpCapabilities = options.includeAcpCapabilities === true;
183572
+ const includeAgents = options.includeAgents === true;
182385
183573
  const jsonMachines = await Promise.all(machines.map((machine) => mergeMachineFlockJsonState(manager.repo, workspace.id, machine, includeAcpCapabilities)));
182386
183574
  printJson({
182387
183575
  ok: true,
182388
183576
  workspaceId: workspace.id,
182389
- machines: jsonMachines.map((machine) => toMachineJsonEntry(machine, includeAcpCapabilities))
183577
+ machines: jsonMachines.map((machine) => toMachineJsonEntry(machine, {
183578
+ includeAcpCapabilities,
183579
+ includeAgents
183580
+ }))
182390
183581
  });
182391
183582
  return;
182392
183583
  }
182393
- printHumanMachineList(machines, auth.machineId);
183584
+ printHumanMachineList(machines, auth.machineId, options.includeAgents === true);
182394
183585
  });
182395
183586
  });
182396
183587
  }));
@@ -182572,25 +183763,6 @@ ${entry.text}`).join("\n\n");
182572
183763
  ].sort((left2, right2) => left2.imageId.localeCompare(right2.imageId))
182573
183764
  };
182574
183765
  }
182575
- async function mapWithConcurrency(items2, concurrency, worker) {
182576
- if (items2.length === 0) {
182577
- return [];
182578
- }
182579
- const limit2 = Math.max(1, Math.floor(concurrency));
182580
- const results = new Array(items2.length);
182581
- let nextIndex = 0;
182582
- const runWorker = async () => {
182583
- while (nextIndex < items2.length) {
182584
- const currentIndex = nextIndex;
182585
- nextIndex += 1;
182586
- results[currentIndex] = await worker(items2[currentIndex], currentIndex);
182587
- }
182588
- };
182589
- await Promise.all(Array.from({
182590
- length: Math.min(limit2, items2.length)
182591
- }, () => runWorker()));
182592
- return results;
182593
- }
182594
183766
  const MIME_EXTENSION_MAP = {
182595
183767
  "image/png": ".png",
182596
183768
  "image/jpeg": ".jpg",
@@ -183011,6 +184183,7 @@ ${entry.text}`).join("\n\n");
183011
184183
  warnings
183012
184184
  };
183013
184185
  }
184186
+ const EXPORT_SYNC_CONCURRENCY = 4;
183014
184187
  function buildDefaultOutputDir() {
183015
184188
  const timestamp2 = (/* @__PURE__ */ new Date()).toISOString().replaceAll(":", "-");
183016
184189
  return path__default$1.resolve(process.cwd(), `lody-export-${timestamp2}`);
@@ -183019,7 +184192,14 @@ ${entry.text}`).join("\n\n");
183019
184192
  const candidate = (workspace.slug?.trim() || workspace.id).trim();
183020
184193
  return candidate.replace(/[\\/]/g, "_");
183021
184194
  }
183022
- const exportCommand = new Command("export").description("Export user-facing workspace session data").option("--workspace <idOrSlug>", "Target workspace id or slug").option("--all-workspace", "Export all accessible workspaces").option("--no-images", "Skip downloading image binaries").option("--debug", "Enable debug output").argument("[outputDir]", "Output directory for export files").action(async (outputDirArg, options) => {
184195
+ async function syncWorkspaceSessionsForExport(manager, workspace) {
184196
+ await syncWorkspaceMetaForRead(manager, `export:${workspace.id}:meta`);
184197
+ const sessions = await listAliveDocMetas(manager, isSessionDocRoomId);
184198
+ await mapWithConcurrency(sessions, EXPORT_SYNC_CONCURRENCY, async (entry) => {
184199
+ await syncDocForRead(manager, getSessionRoomId(entry.meta.id), `export:${workspace.id}:${entry.meta.id}`);
184200
+ });
184201
+ }
184202
+ const exportCommand = new Command("export").description("Export user-facing workspace session data").option("--workspace <idOrSlug>", "Target workspace id or slug").option("--all-workspace", "Export all accessible workspaces").option("--no-images", "Skip downloading image binaries").option("--offline", "Read the local cache without syncing first").option("--debug", "Enable debug output").argument("[outputDir]", "Output directory for export files").action(async (outputDirArg, options) => {
183023
184203
  await runOneShotCommand("export", options, async () => {
183024
184204
  const auth = getAuthContextOrThrow$1("export");
183025
184205
  const outputDir = path__default$1.resolve(outputDirArg ?? buildDefaultOutputDir());
@@ -183034,6 +184214,9 @@ ${entry.text}`).join("\n\n");
183034
184214
  for (const workspace of workspaces) {
183035
184215
  const workspaceOutputDir = path__default$1.join(outputDir, toWorkspaceDirName(workspace));
183036
184216
  const result = await withWorkspaceManager$1(auth, workspace, "export", async (manager) => {
184217
+ if (options.offline !== true) {
184218
+ await syncWorkspaceSessionsForExport(manager, workspace);
184219
+ }
183037
184220
  return await exportWorkspaceData({
183038
184221
  manager,
183039
184222
  workspace,
@@ -183157,7 +184340,7 @@ ${entry.text}`).join("\n\n");
183157
184340
  data = createReviewBundleSnapshot(bundle);
183158
184341
  }
183159
184342
  const { injectReviewSnapshot } = await import("./chunks/index-VoI6Ds2-.js");
183160
- const { resolveReviewViewerTemplate } = await import("./chunks/review-viewer-DoxiB4Vb.js");
184343
+ const { resolveReviewViewerTemplate } = await import("./chunks/review-viewer--K5LcsS4.js");
183161
184344
  const template = await resolveReviewViewerTemplate();
183162
184345
  const html = injectReviewSnapshot(template, data);
183163
184346
  const outputPath = options.output ? path__default$1.resolve(options.output) : defaultHtmlOutputPath(inputPath);
@@ -200486,6 +201669,7 @@ ${lines2.join("\n")}` : ""}${suffix}`);
200486
201669
  program.addCommand(startCommand);
200487
201670
  program.addCommand(projectCommand);
200488
201671
  program.addCommand(sessionCommand);
201672
+ program.addCommand(syncCommand);
200489
201673
  program.addCommand(workspaceCommand);
200490
201674
  program.addCommand(agentConfigCommand);
200491
201675
  program.addCommand(machineCommand);