lody 0.50.2 → 0.51.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.
Files changed (2) hide show
  1. package/dist/index.js +1385 -181
  2. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import require$$3$5, { randomUUID, randomBytes, createHash as createHash$1 } from "crypto";
2
+ import require$$3$5, { randomUUID, createHash as createHash$1, randomBytes } from "crypto";
3
3
  import require$$0$5, { inspect as inspect$1, types as types$6 } from "util";
4
4
  import require$$2$6 from "url";
5
5
  import * as path$2 from "path";
@@ -56,6 +56,7 @@ import fsPromises, { stat, open } from "node:fs/promises";
56
56
  import { fileURLToPath } from "node:url";
57
57
  import require$$2$7 from "assert";
58
58
  import { performance as performance$1 } from "perf_hooks";
59
+ import { createRequire as createRequire$1 } from "node:module";
59
60
  import { Buffer as Buffer$1 } from "node:buffer";
60
61
  import { AsyncLocalStorage, AsyncResource } from "node:async_hooks";
61
62
  let program;
@@ -36821,7 +36822,7 @@ Mongoose Error Code: ${error2.code}` : ""}`
36821
36822
  return client;
36822
36823
  }
36823
36824
  const name = "lody";
36824
- const version$4 = "0.50.2";
36825
+ const version$4 = "0.51.0";
36825
36826
  const description$1 = "Lody Agent CLI tool for managing remote command execution";
36826
36827
  const type$2 = "module";
36827
36828
  const main$3 = "dist/index.js";
@@ -36865,7 +36866,7 @@ Mongoose Error Code: ${error2.code}` : ""}`
36865
36866
  };
36866
36867
  const optionalDependencies = {
36867
36868
  "acp-extension-claude": "0.31.1",
36868
- "acp-extension-codex": "0.14.2"
36869
+ "acp-extension-codex": "0.14.3"
36869
36870
  };
36870
36871
  const devDependencies = {
36871
36872
  "@agentclientprotocol/sdk": "catalog:",
@@ -66971,14 +66972,14 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
66971
66972
  email: string$2(),
66972
66973
  name: string$2().nullable().optional()
66973
66974
  }).passthrough();
66974
- function isRecord$5(value) {
66975
+ function isRecord$6(value) {
66975
66976
  return typeof value === "object" && value !== null && !Array.isArray(value);
66976
66977
  }
66977
66978
  function asRecord(value) {
66978
- return isRecord$5(value) ? value : null;
66979
+ return isRecord$6(value) ? value : null;
66979
66980
  }
66980
66981
  function readSessionUserFromResponse(response) {
66981
- if (!isRecord$5(response)) {
66982
+ if (!isRecord$6(response)) {
66982
66983
  return null;
66983
66984
  }
66984
66985
  if ("data" in response) {
@@ -78661,6 +78662,27 @@ Task description:
78661
78662
  localProjectId: LocalProjectIdSchema,
78662
78663
  branchName: string$2()
78663
78664
  }).strict();
78665
+ const LocalProjectHistoryProviderSchema = _enum$1([
78666
+ "codex",
78667
+ "claude"
78668
+ ]);
78669
+ const LocalProjectSyncHistoryRequestSchema = object$1({
78670
+ type: literal("local-project/sync-history"),
78671
+ machineId: MachineIdSchema,
78672
+ workspaceId: WorkspaceIdSchema,
78673
+ localProjectId: LocalProjectIdSchema,
78674
+ provider: LocalProjectHistoryProviderSchema,
78675
+ requestedByUserId: string$2().trim().min(1).optional()
78676
+ }).strict();
78677
+ const LocalProjectImportHistoryRequestSchema = object$1({
78678
+ type: literal("local-project/import-history"),
78679
+ machineId: MachineIdSchema,
78680
+ workspaceId: WorkspaceIdSchema,
78681
+ localProjectId: LocalProjectIdSchema,
78682
+ provider: LocalProjectHistoryProviderSchema,
78683
+ acpSessionIds: array$3(ACPSessionIdSchema),
78684
+ requestedByUserId: string$2().trim().min(1).optional()
78685
+ }).strict();
78664
78686
  const WorktreeListFilesRequestSchema = object$1({
78665
78687
  type: literal("worktree/list-files"),
78666
78688
  machineId: MachineIdSchema,
@@ -78684,6 +78706,8 @@ Task description:
78684
78706
  LocalProjectListFilesRequestSchema,
78685
78707
  LocalProjectReadFileRequestSchema,
78686
78708
  LocalProjectCheckoutBranchRequestSchema,
78709
+ LocalProjectSyncHistoryRequestSchema,
78710
+ LocalProjectImportHistoryRequestSchema,
78687
78711
  WorktreeListFilesRequestSchema,
78688
78712
  WorktreeReadFileRequestSchema
78689
78713
  ]);
@@ -78725,12 +78749,45 @@ Task description:
78725
78749
  error: string$2()
78726
78750
  }).strict()
78727
78751
  ]);
78752
+ const LocalProjectHistorySyncSummarySchema = object$1({
78753
+ listed: number$3().int().nonnegative(),
78754
+ imported: number$3().int().nonnegative(),
78755
+ refreshed: number$3().int().nonnegative(),
78756
+ skipped: number$3().int().nonnegative(),
78757
+ conflicted: number$3().int().nonnegative(),
78758
+ failed: number$3().int().nonnegative(),
78759
+ failures: array$3(object$1({
78760
+ acpSessionId: string$2(),
78761
+ message: string$2()
78762
+ }).strict())
78763
+ }).strict();
78764
+ const LocalProjectHistoryCatalogItemSchema = object$1({
78765
+ acpSessionId: string$2(),
78766
+ title: string$2(),
78767
+ updatedAt: string$2().optional(),
78768
+ importedSessionId: string$2().optional(),
78769
+ status: _enum$1([
78770
+ "available",
78771
+ "imported",
78772
+ "sync_conflict"
78773
+ ]).optional()
78774
+ }).strict();
78775
+ const LocalProjectHistoryCatalogResultSchema = object$1({
78776
+ listed: number$3().int().nonnegative(),
78777
+ lastListedAt: number$3().int().nonnegative(),
78778
+ sessions: array$3(LocalProjectHistoryCatalogItemSchema)
78779
+ }).strict();
78780
+ const LocalProjectHistoryImportResultSchema = object$1({
78781
+ summary: LocalProjectHistorySyncSummarySchema,
78782
+ catalog: LocalProjectHistoryCatalogResultSchema
78783
+ }).strict();
78728
78784
  const LocalProjectControlErrorCodeSchema = _enum$1([
78729
78785
  "invalid_request",
78730
78786
  "machine_mismatch",
78731
78787
  "workspace_required",
78732
78788
  "workspace_not_found",
78733
78789
  "daemon_unavailable",
78790
+ "access_denied",
78734
78791
  "local_project_not_found",
78735
78792
  "path_invalid",
78736
78793
  "execution_failed",
@@ -78746,6 +78803,8 @@ Task description:
78746
78803
  "local-project/list-files",
78747
78804
  "local-project/read-file",
78748
78805
  "local-project/checkout-branch",
78806
+ "local-project/sync-history",
78807
+ "local-project/import-history",
78749
78808
  "worktree/list-files",
78750
78809
  "worktree/read-file"
78751
78810
  ]),
@@ -78809,6 +78868,16 @@ Task description:
78809
78868
  type: literal("local-project/checkout-branch"),
78810
78869
  result: LocalProjectCheckoutBranchResultSchema
78811
78870
  }).strict(),
78871
+ object$1({
78872
+ ok: literal(true),
78873
+ type: literal("local-project/sync-history"),
78874
+ result: LocalProjectHistoryCatalogResultSchema
78875
+ }).strict(),
78876
+ object$1({
78877
+ ok: literal(true),
78878
+ type: literal("local-project/import-history"),
78879
+ result: LocalProjectHistoryImportResultSchema
78880
+ }).strict(),
78812
78881
  object$1({
78813
78882
  ok: literal(true),
78814
78883
  type: literal("worktree/list-files"),
@@ -79127,7 +79196,7 @@ Task description:
79127
79196
  "claude",
79128
79197
  "codex"
79129
79198
  ]);
79130
- function isRecord$4(value) {
79199
+ function isRecord$5(value) {
79131
79200
  return typeof value === "object" && value !== null;
79132
79201
  }
79133
79202
  function getTrimmedString(value) {
@@ -79144,7 +79213,7 @@ Task description:
79144
79213
  return value === "builtin" || value === "registry";
79145
79214
  }
79146
79215
  function normalizeLegacyProjectRef(value) {
79147
- if (!isRecord$4(value)) {
79216
+ if (!isRecord$5(value)) {
79148
79217
  return value;
79149
79218
  }
79150
79219
  const kind = value.kind;
@@ -79160,13 +79229,13 @@ Task description:
79160
79229
  normalized.branch = existingBranch;
79161
79230
  } else {
79162
79231
  const legacyBranchFromString = getTrimmedString(legacyProject);
79163
- const legacyBranchFromObject = isRecord$4(legacyProject) ? getTrimmedString(legacyProject.branch) ?? getTrimmedString(legacyProject.project) : void 0;
79232
+ const legacyBranchFromObject = isRecord$5(legacyProject) ? getTrimmedString(legacyProject.branch) ?? getTrimmedString(legacyProject.project) : void 0;
79164
79233
  const resolvedBranch = legacyBranchFromString ?? legacyBranchFromObject;
79165
79234
  if (resolvedBranch) {
79166
79235
  normalized.branch = resolvedBranch;
79167
79236
  }
79168
79237
  }
79169
- if (isRecord$4(legacyProject)) {
79238
+ if (isRecord$5(legacyProject)) {
79170
79239
  if (kind === "github" && !getTrimmedString(normalized.repoFullName)) {
79171
79240
  const repoFullName = getTrimmedString(legacyProject.repoFullName);
79172
79241
  if (repoFullName) {
@@ -79203,7 +79272,7 @@ Task description:
79203
79272
  };
79204
79273
  normalized.project = normalizeLegacyProjectRef(normalized.project);
79205
79274
  const currentProject = normalized.project;
79206
- const projectRecord = isRecord$4(currentProject) ? currentProject : void 0;
79275
+ const projectRecord = isRecord$5(currentProject) ? currentProject : void 0;
79207
79276
  const explicitBranch = getTrimmedString(normalized.branch) ?? getTrimmedString(currentProject) ?? (projectRecord ? getTrimmedString(projectRecord.branch) ?? getTrimmedString(projectRecord.project) : void 0);
79208
79277
  const repoFullName = (projectRecord ? getTrimmedString(projectRecord.repoFullName) : void 0) ?? getTrimmedString(normalized.repoFullName) ?? getTrimmedString(normalized.githubRepo);
79209
79278
  const localProjectId = (projectRecord ? getTrimmedString(projectRecord.localProjectId) : void 0) ?? getTrimmedString(normalized.localProjectId);
@@ -79246,7 +79315,7 @@ Task description:
79246
79315
  return normalized;
79247
79316
  }
79248
79317
  function normalizeLegacyAcpSessionConfig(value) {
79249
- if (!isRecord$4(value)) {
79318
+ if (!isRecord$5(value)) {
79250
79319
  return value;
79251
79320
  }
79252
79321
  const normalized = {
@@ -79281,13 +79350,13 @@ Task description:
79281
79350
  return normalized;
79282
79351
  }
79283
79352
  function normalizeLegacySessionMessage(parsed) {
79284
- if (!isRecord$4(parsed)) {
79353
+ if (!isRecord$5(parsed)) {
79285
79354
  return parsed;
79286
79355
  }
79287
79356
  const messageType = parsed.type;
79288
79357
  if (messageType === "session/create" || messageType === "session/chat") {
79289
79358
  const normalized = normalizeLegacySessionProject(parsed);
79290
- if (!isRecord$4(normalized)) {
79359
+ if (!isRecord$5(normalized)) {
79291
79360
  return normalized;
79292
79361
  }
79293
79362
  normalized.acpSessionConfig = normalizeLegacyAcpSessionConfig(normalized.acpSessionConfig);
@@ -81512,7 +81581,7 @@ Task description:
81512
81581
  "read"
81513
81582
  ]);
81514
81583
  const MAX_STORED_TERMINAL_OUTPUT_CHARS = 1024;
81515
- const defaultCreateId = () => {
81584
+ const defaultCreateId$1 = () => {
81516
81585
  const maybeCrypto = globalThis.crypto;
81517
81586
  if (typeof maybeCrypto?.randomUUID === "function") {
81518
81587
  return maybeCrypto.randomUUID();
@@ -82193,7 +82262,7 @@ Task description:
82193
82262
  constructor(history, options, model) {
82194
82263
  this.model = model;
82195
82264
  this.history = history;
82196
- this.createId = options.createId ?? defaultCreateId;
82265
+ this.createId = options.createId ?? defaultCreateId$1;
82197
82266
  this.now = options.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
82198
82267
  this.parsedItemsByEntryIndex = Array.from({
82199
82268
  length: history.length
@@ -82445,28 +82514,149 @@ Task description:
82445
82514
  const applyNotificationOnHistory = (history, notifications, model, options = {}) => {
82446
82515
  return new NotificationOnHistoryApplier(history, options, model).apply(notifications);
82447
82516
  };
82448
- const isRecord$3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
82517
+ const defaultNow = () => (/* @__PURE__ */ new Date()).toISOString();
82518
+ const defaultCreateId = () => {
82519
+ const maybeCrypto = globalThis.crypto;
82520
+ if (typeof maybeCrypto?.randomUUID === "function") {
82521
+ return maybeCrypto.randomUUID();
82522
+ }
82523
+ return `history-replay-${Date.now()}-${Math.random().toString(16).slice(2)}`;
82524
+ };
82525
+ function textFromContentBlock(content) {
82526
+ if (!content || content.type !== "text") {
82527
+ return null;
82528
+ }
82529
+ return content.text;
82530
+ }
82531
+ function appendUserText(entry2, text) {
82532
+ const items2 = Array.isArray(entry2.items) ? [
82533
+ ...entry2.items
82534
+ ] : [];
82535
+ const last2 = items2[items2.length - 1];
82536
+ if (last2?.type === "text") {
82537
+ items2[items2.length - 1] = {
82538
+ ...last2,
82539
+ text: `${last2.text}${text}`
82540
+ };
82541
+ } else {
82542
+ items2.push({
82543
+ type: "text",
82544
+ text
82545
+ });
82546
+ }
82547
+ return {
82548
+ ...entry2,
82549
+ items: items2,
82550
+ inputConfig: entry2.inputConfig ? {
82551
+ ...entry2.inputConfig,
82552
+ prompt: `${entry2.inputConfig.prompt ?? ""}${text}`
82553
+ } : entry2.inputConfig
82554
+ };
82555
+ }
82556
+ function createUserEntry(args2) {
82557
+ const inputConfig = {
82558
+ prompt: args2.text,
82559
+ cliType: "builtin",
82560
+ agentType: args2.agentType,
82561
+ ...{}
82562
+ };
82563
+ return {
82564
+ id: args2.id,
82565
+ role: "user",
82566
+ items: [
82567
+ {
82568
+ type: "text",
82569
+ text: args2.text
82570
+ }
82571
+ ],
82572
+ timestamp: args2.timestamp,
82573
+ status: args2.mode === "resumable" ? "seen" : "handled",
82574
+ read: true,
82575
+ userId: args2.userId,
82576
+ finished: true,
82577
+ fileDiff: [],
82578
+ inputConfig
82579
+ };
82580
+ }
82581
+ function buildHistoryReplayImport(notifications, options) {
82582
+ const now2 = options.now ?? defaultNow;
82583
+ const createId = options.createId ?? defaultCreateId;
82584
+ const mode2 = options.mode;
82585
+ const agentType = options.agentType ?? "codex";
82586
+ let history = [];
82587
+ let lastWasUserChunk = false;
82588
+ let droppedNotifications = 0;
82589
+ for (const notification of notifications) {
82590
+ if (notification.update.sessionUpdate === "user_message_chunk") {
82591
+ const text = textFromContentBlock(notification.update.content);
82592
+ if (text === null) {
82593
+ droppedNotifications += 1;
82594
+ lastWasUserChunk = false;
82595
+ continue;
82596
+ }
82597
+ const lastIndex = history.length - 1;
82598
+ const last2 = lastIndex >= 0 ? history[lastIndex] : void 0;
82599
+ if (lastWasUserChunk && last2?.role === "user") {
82600
+ history[lastIndex] = appendUserText(last2, text);
82601
+ } else {
82602
+ history.push(createUserEntry({
82603
+ id: createId(),
82604
+ text,
82605
+ timestamp: now2(),
82606
+ userId: options.userId,
82607
+ agentType,
82608
+ mode: mode2
82609
+ }));
82610
+ }
82611
+ lastWasUserChunk = true;
82612
+ continue;
82613
+ }
82614
+ const beforeLength = history.length;
82615
+ const beforeSnapshot = JSON.stringify(history);
82616
+ history = applyNotificationOnHistory(history, [
82617
+ notification
82618
+ ], void 0, {
82619
+ createId,
82620
+ now: now2
82621
+ });
82622
+ if (history.length === beforeLength && JSON.stringify(history) === beforeSnapshot) {
82623
+ const updateType = notification.update.sessionUpdate;
82624
+ if (updateType !== "session_info_update" && updateType !== "current_mode_update" && updateType !== "config_option_update" && updateType !== "usage_update" && updateType !== "available_commands_update") {
82625
+ droppedNotifications += 1;
82626
+ }
82627
+ }
82628
+ lastWasUserChunk = false;
82629
+ }
82630
+ return {
82631
+ history: history.map((entry2) => entry2.role === "assistant" ? {
82632
+ ...entry2,
82633
+ finished: entry2.finished ?? true
82634
+ } : entry2),
82635
+ droppedNotifications
82636
+ };
82637
+ }
82638
+ const isRecord$4 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
82449
82639
  const getClaudeCodeMeta = (meta) => {
82450
- if (!isRecord$3(meta)) return null;
82640
+ if (!isRecord$4(meta)) return null;
82451
82641
  const claudeCode = meta.claudeCode;
82452
- return isRecord$3(claudeCode) ? claudeCode : null;
82642
+ return isRecord$4(claudeCode) ? claudeCode : null;
82453
82643
  };
82454
82644
  function parseAskUserQuestionPermissionMeta(meta) {
82455
82645
  const claudeCode = getClaudeCodeMeta(meta);
82456
82646
  if (!claudeCode) return null;
82457
82647
  const raw = claudeCode.askUserQuestion;
82458
- if (!isRecord$3(raw)) return null;
82648
+ if (!isRecord$4(raw)) return null;
82459
82649
  const rawQuestions = raw.questions;
82460
82650
  if (!Array.isArray(rawQuestions) || rawQuestions.length === 0) return null;
82461
82651
  const questions = [];
82462
82652
  for (const rawQuestion of rawQuestions) {
82463
- if (!isRecord$3(rawQuestion)) return null;
82653
+ if (!isRecord$4(rawQuestion)) return null;
82464
82654
  if (typeof rawQuestion.question !== "string") return null;
82465
82655
  if (typeof rawQuestion.header !== "string") return null;
82466
82656
  if (!Array.isArray(rawQuestion.options)) return null;
82467
82657
  const options = [];
82468
82658
  for (const rawOption of rawQuestion.options) {
82469
- if (!isRecord$3(rawOption)) return null;
82659
+ if (!isRecord$4(rawOption)) return null;
82470
82660
  if (typeof rawOption.label !== "string") return null;
82471
82661
  options.push({
82472
82662
  label: rawOption.label,
@@ -85171,7 +85361,7 @@ ${tailedOutput}` : null;
85171
85361
  ];
85172
85362
  const buildPreviewTunnelRefreshPath = (tunnelId) => `${PREVIEW_TUNNELS_API_PATH}/${encodeURIComponent(tunnelId)}/refresh`;
85173
85363
  const buildPreviewTunnelRevokePath = (tunnelId) => `${PREVIEW_TUNNELS_API_PATH}/${encodeURIComponent(tunnelId)}/revoke`;
85174
- const isRecord$2 = (value) => typeof value === "object" && value !== null;
85364
+ const isRecord$3 = (value) => typeof value === "object" && value !== null;
85175
85365
  const isString$2 = (value) => typeof value === "string";
85176
85366
  const isOptionalNumber = (value) => value === void 0 || typeof value === "number";
85177
85367
  const isOptionalBoolean = (value) => value === void 0 || typeof value === "boolean";
@@ -85179,11 +85369,11 @@ ${tailedOutput}` : null;
85179
85369
  const isStringArray$1 = (value) => Array.isArray(value) && value.every((item) => typeof item === "string");
85180
85370
  const isHeaderEntries = (value) => Array.isArray(value) && value.every((entry2) => Array.isArray(entry2) && entry2.length === 2 && typeof entry2[0] === "string" && typeof entry2[1] === "string");
85181
85371
  const isPreviewTunnelBinaryPayloadStream = (value) => value === "request-body" || value === "response-body" || value === "websocket-frame";
85182
- const isPreviewResourceLimits = (value) => isRecord$2(value) && isPositiveInteger(value.maxRequestBodyBytes) && isPositiveInteger(value.maxResponseBodyBytes) && isPositiveInteger(value.maxRequestDurationMs);
85372
+ const isPreviewResourceLimits = (value) => isRecord$3(value) && isPositiveInteger(value.maxRequestBodyBytes) && isPositiveInteger(value.maxResponseBodyBytes) && isPositiveInteger(value.maxRequestDurationMs);
85183
85373
  const parseJsonRecord = (raw) => {
85184
85374
  try {
85185
85375
  const parsed = JSON.parse(raw);
85186
- return isRecord$2(parsed) ? parsed : null;
85376
+ return isRecord$3(parsed) ? parsed : null;
85187
85377
  } catch {
85188
85378
  return null;
85189
85379
  }
@@ -85222,8 +85412,8 @@ ${tailedOutput}` : null;
85222
85412
  const parsed = parseJsonRecord(raw);
85223
85413
  return parsed && isPreviewTunnelServerMessage(parsed) ? parsed : null;
85224
85414
  };
85225
- const isPreviewTunnelCreateResponse = (value) => isRecord$2(value) && isString$2(value.tunnelId) && isString$2(value.publicUrl) && isString$2(value.websocketUrl) && isString$2(value.sessionToken) && typeof value.expiresAt === "number" && (value.resourceLimits === void 0 || isPreviewResourceLimits(value.resourceLimits));
85226
- const isPreviewTunnelRefreshResponse = (value) => isRecord$2(value) && isString$2(value.websocketUrl) && isString$2(value.sessionToken) && typeof value.expiresAt === "number";
85415
+ const isPreviewTunnelCreateResponse = (value) => isRecord$3(value) && isString$2(value.tunnelId) && isString$2(value.publicUrl) && isString$2(value.websocketUrl) && isString$2(value.sessionToken) && typeof value.expiresAt === "number" && (value.resourceLimits === void 0 || isPreviewResourceLimits(value.resourceLimits));
85416
+ const isPreviewTunnelRefreshResponse = (value) => isRecord$3(value) && isString$2(value.websocketUrl) && isString$2(value.sessionToken) && typeof value.expiresAt === "number";
85227
85417
  const LOCAL_PROBE_PORT$1 = 17789;
85228
85418
  const LOCAL_SESSION_CONTROL_PORT = 17790;
85229
85419
  const IMAGE_UPLOAD_PATH = "/image-upload";
@@ -90150,18 +90340,18 @@ ${val.stack}`;
90150
90340
  }
90151
90341
  return next;
90152
90342
  }
90153
- function isRecord$1(value) {
90343
+ function isRecord$2(value) {
90154
90344
  return typeof value === "object" && value !== null;
90155
90345
  }
90156
90346
  function normalizeRecoveryReport(raw) {
90157
- if (!isRecord$1(raw) || !Array.isArray(raw.skipped)) {
90347
+ if (!isRecord$2(raw) || !Array.isArray(raw.skipped)) {
90158
90348
  return {
90159
90349
  skipped: []
90160
90350
  };
90161
90351
  }
90162
90352
  return {
90163
90353
  skipped: raw.skipped.flatMap((entry2) => {
90164
- if (!isRecord$1(entry2)) {
90354
+ if (!isRecord$2(entry2)) {
90165
90355
  return [];
90166
90356
  }
90167
90357
  const key2 = Array.isArray(entry2.key) ? cloneJson(entry2.key) : void 0;
@@ -99013,7 +99203,7 @@ stream:${scope2.streamId}`;
99013
99203
  }
99014
99204
  return parsed;
99015
99205
  };
99016
- const withTimeout$2 = async (promise, timeoutMs, message) => {
99206
+ const withTimeout$3 = async (promise, timeoutMs, message) => {
99017
99207
  if (timeoutMs <= 0) {
99018
99208
  return promise;
99019
99209
  }
@@ -105853,7 +106043,7 @@ stream:${scope2.streamId}`;
105853
106043
  const timeoutMs = options.timeoutMs ?? readTimeoutEnv("LODY_LORO_WAIT_CODE_SESSION_SYNC_TIMEOUT_MS", readTimeoutEnv("LODY_LORO_WAIT_DOC_SYNC_TIMEOUT_MS", 4e3));
105854
106044
  const timeoutMessage = `Timeout waiting for code session pending writes (session=${this.sessionId})`;
105855
106045
  try {
105856
- await withTimeout$2(this.subscription.waitUntilSynced(), timeoutMs, timeoutMessage);
106046
+ await withTimeout$3(this.subscription.waitUntilSynced(), timeoutMs, timeoutMessage);
105857
106047
  return true;
105858
106048
  } catch {
105859
106049
  return false;
@@ -115733,7 +115923,7 @@ ${this.stack.split("\n").slice(1).join("\n")}` : this.toString();
115733
115923
  this.logger.debug(`[${this.workspaceId}] Triggering Loro streams reconnect (reason=${reason}, transport=${this.transportStatus}, metaRoom=${this.metaRoomStatus ?? "unknown"})`);
115734
115924
  }
115735
115925
  await this.ensureMetaRoomJoined(reason);
115736
- await withTimeout$2(this.repo.reconnect({
115926
+ await withTimeout$3(this.repo.reconnect({
115737
115927
  resetBackoff: true,
115738
115928
  timeout: timeoutMs
115739
115929
  }), timeoutMs, `Timeout waiting for Loro streams reconnect (workspace=${this.workspaceId})`);
@@ -115755,7 +115945,7 @@ ${this.stack.split("\n").slice(1).join("\n")}` : this.toString();
115755
115945
  const joinMetaTimeoutMs = readTimeoutEnv("LODY_LORO_JOIN_META_TIMEOUT_MS", 3e4);
115756
115946
  const startedAt = Date.now();
115757
115947
  this.logger.debug(`[${this.workspaceId}] Joining Loro meta room (reason=${reason})`);
115758
- const metaSub = await withTimeout$2(this.repo.joinMetaRoom(), joinMetaTimeoutMs, `Timeout waiting for repo.joinMetaRoom during reconnect (workspace=${this.workspaceId})`);
115948
+ const metaSub = await withTimeout$3(this.repo.joinMetaRoom(), joinMetaTimeoutMs, `Timeout waiting for repo.joinMetaRoom during reconnect (workspace=${this.workspaceId})`);
115759
115949
  this.attachMetaRoomStatusLogger(metaSub);
115760
115950
  this.logger.debug(`[${this.workspaceId}] Meta room join returned in ${Date.now() - startedAt}ms (reason=${reason})`);
115761
115951
  }
@@ -115767,7 +115957,7 @@ ${this.stack.split("\n").slice(1).join("\n")}` : this.toString();
115767
115957
  const startedAt = Date.now();
115768
115958
  try {
115769
115959
  const syncPromise = this.initialMetaSyncCompleted ? metaSub.waitUntilSynced() : metaSub.firstSyncedWithRemote;
115770
- await withTimeout$2(syncPromise, syncMetaTimeoutMs, `Timeout waiting for Loro meta room sync (workspace=${this.workspaceId})`);
115960
+ await withTimeout$3(syncPromise, syncMetaTimeoutMs, `Timeout waiting for Loro meta room sync (workspace=${this.workspaceId})`);
115771
115961
  if (this.metaSub !== metaSub || this.isCleanedUp) {
115772
115962
  return;
115773
115963
  }
@@ -116689,7 +116879,7 @@ ${this.stack.split("\n").slice(1).join("\n")}` : this.toString();
116689
116879
  });
116690
116880
  try {
116691
116881
  const createRepoStartMs = Date.now();
116692
- repo = await withTimeout$2(LoroRepo.create({
116882
+ repo = await withTimeout$3(LoroRepo.create({
116693
116883
  storageAdapter: new FileSystemStorageAdaptor({
116694
116884
  baseDir: storageBaseDir
116695
116885
  }),
@@ -116707,7 +116897,7 @@ ${this.stack.split("\n").slice(1).join("\n")}` : this.toString();
116707
116897
  }
116708
116898
  try {
116709
116899
  const joinMetaStartMs = Date.now();
116710
- metaSub = await withTimeout$2(repo.joinMetaRoom(), joinMetaTimeoutMs, "Timeout waiting for repo.joinMetaRoom");
116900
+ metaSub = await withTimeout$3(repo.joinMetaRoom(), joinMetaTimeoutMs, "Timeout waiting for repo.joinMetaRoom");
116711
116901
  logger2.debug(`[${workspaceId}] Meta room join returned in ${Date.now() - joinMetaStartMs}ms`);
116712
116902
  } catch (error2) {
116713
116903
  logger2.debug(`[${workspaceId}] Failed to join Loro meta room; continuing without initial meta join: ${formatErrorMessage(error2)}`);
@@ -116724,7 +116914,7 @@ ${this.stack.split("\n").slice(1).join("\n")}` : this.toString();
116724
116914
  });
116725
116915
  try {
116726
116916
  const syncStartMs = Date.now();
116727
- await withTimeout$2(metaSub.firstSyncedWithRemote, syncMetaTimeoutMs, initialMetaSyncTimeoutMessage);
116917
+ await withTimeout$3(metaSub.firstSyncedWithRemote, syncMetaTimeoutMs, initialMetaSyncTimeoutMessage);
116728
116918
  initialMetaSyncCompleted = true;
116729
116919
  logger2.debug(`[${workspaceId}] Meta room synced in ${Date.now() - syncStartMs}ms`);
116730
116920
  } catch (error2) {
@@ -116772,7 +116962,7 @@ ${this.stack.split("\n").slice(1).join("\n")}` : this.toString();
116772
116962
  }
116773
116963
  const timeoutMessage = `Timeout waiting for initial meta sync (workspace=${this.workspaceId})`;
116774
116964
  try {
116775
- return await withTimeout$2(this.initialMetaSyncPromise, timeoutMs, timeoutMessage);
116965
+ return await withTimeout$3(this.initialMetaSyncPromise, timeoutMs, timeoutMessage);
116776
116966
  } catch (error2) {
116777
116967
  if (error2 instanceof Error && error2.message === timeoutMessage) {
116778
116968
  return false;
@@ -116795,7 +116985,7 @@ ${this.stack.split("\n").slice(1).join("\n")}` : this.toString();
116795
116985
  const destroyTimeoutMs = readTimeoutEnv("LODY_LORO_DESTROY_TIMEOUT_MS", 1e3);
116796
116986
  const timeoutMessage = `Timeout waiting for repo.destroy (workspace=${this.workspaceId})`;
116797
116987
  try {
116798
- await withTimeout$2(repoDestroyPromise, destroyTimeoutMs, timeoutMessage);
116988
+ await withTimeout$3(repoDestroyPromise, destroyTimeoutMs, timeoutMessage);
116799
116989
  } catch (error2) {
116800
116990
  if (!(error2 instanceof Error) || error2.message !== timeoutMessage) {
116801
116991
  throw error2;
@@ -117271,7 +117461,7 @@ ${this.stack.split("\n").slice(1).join("\n")}` : this.toString();
117271
117461
  this.docSub = joinedSub;
117272
117462
  const syncDocTimeoutMs = readTimeoutEnv("LODY_LORO_SYNC_DOC_TIMEOUT_MS", 8e3);
117273
117463
  const timeoutMessage = `Timeout waiting for session doc initial sync (room=${this.roomId})`;
117274
- await withTimeout$2(joinedSub.firstSyncedWithRemote, syncDocTimeoutMs, timeoutMessage);
117464
+ await withTimeout$3(joinedSub.firstSyncedWithRemote, syncDocTimeoutMs, timeoutMessage);
117275
117465
  return;
117276
117466
  } catch (error2) {
117277
117467
  const errMsg = formatErrorMessage(error2);
@@ -117333,7 +117523,7 @@ ${this.stack.split("\n").slice(1).join("\n")}` : this.toString();
117333
117523
  const timeoutMs = options.timeoutMs ?? readTimeoutEnv("LODY_LORO_WAIT_DOC_SYNC_TIMEOUT_MS", 4e3);
117334
117524
  const timeoutMessage = `Timeout waiting for session doc pending writes (room=${this.roomId})`;
117335
117525
  try {
117336
- await withTimeout$2(sub.waitUntilSynced(), timeoutMs, timeoutMessage);
117526
+ await withTimeout$3(sub.waitUntilSynced(), timeoutMs, timeoutMessage);
117337
117527
  return true;
117338
117528
  } catch (error2) {
117339
117529
  this.logger.debug(`[${this.sessionId}] Session doc pending writes were not confirmed before continuing: ${formatErrorMessage(error2)}`);
@@ -119481,7 +119671,8 @@ ${this.stack.split("\n").slice(1).join("\n")}` : this.toString();
119481
119671
  "machine/status",
119482
119672
  "machine/acp-capabilities-refresh",
119483
119673
  "session/preview-create",
119484
- "session/preview-revoke"
119674
+ "session/preview-revoke",
119675
+ "local-project/control"
119485
119676
  ]);
119486
119677
  const LoroStreamsRpcErrorSchema = object$1({
119487
119678
  code: string$2().trim().min(1),
@@ -119527,11 +119718,18 @@ ${this.stack.split("\n").slice(1).join("\n")}` : this.toString();
119527
119718
  reason: string$2().trim().min(1).optional()
119528
119719
  }).strict()
119529
119720
  }).strict();
119721
+ const LoroLocalProjectControlRpcRequestSchema = BaseRpcRequestSchema.extend({
119722
+ method: literal("local-project/control"),
119723
+ params: object$1({
119724
+ request: LocalProjectControlRequestSchema
119725
+ }).strict()
119726
+ }).strict();
119530
119727
  const LoroStreamsRpcRequestSchema = discriminatedUnion("method", [
119531
119728
  LoroMachineStatusRpcRequestSchema,
119532
119729
  LoroMachineAcpCapabilitiesRefreshRpcRequestSchema,
119533
119730
  LoroSessionPreviewCreateRpcRequestSchema,
119534
- LoroSessionPreviewRevokeRpcRequestSchema
119731
+ LoroSessionPreviewRevokeRpcRequestSchema,
119732
+ LoroLocalProjectControlRpcRequestSchema
119535
119733
  ]);
119536
119734
  object$1({
119537
119735
  jsonrpc: literal(JSON_RPC_VERSION$1),
@@ -119870,6 +120068,18 @@ ${this.stack.split("\n").slice(1).join("\n")}` : this.toString();
119870
120068
  await this.appendResultResponse(request.replyTo, request.id, request.method, response);
119871
120069
  return;
119872
120070
  }
120071
+ case "local-project/control": {
120072
+ if (!this.deps.dispatchLocalProjectControl) {
120073
+ await this.appendErrorResponse(request.replyTo, request.id, request.method, {
120074
+ code: "method_unavailable",
120075
+ message: "Local project control is not available on this machine."
120076
+ });
120077
+ return;
120078
+ }
120079
+ const response = await this.deps.dispatchLocalProjectControl(request.params.request);
120080
+ await this.appendResultResponse(request.replyTo, request.id, request.method, response);
120081
+ return;
120082
+ }
119873
120083
  }
119874
120084
  } catch (error2) {
119875
120085
  const message = error2 instanceof Error ? error2.message : String(error2);
@@ -121455,7 +121665,7 @@ ${this.stack.split("\n").slice(1).join("\n")}` : this.toString();
121455
121665
  this.name = "AcpTimeoutError";
121456
121666
  }
121457
121667
  }
121458
- function withTimeout$1(promise, logger2, operationName, sessionId, timeoutMs, warningIntervalMs = 1e4) {
121668
+ function withTimeout$2(promise, logger2, operationName, sessionId, timeoutMs, warningIntervalMs = 1e4) {
121459
121669
  let completed = false;
121460
121670
  let elapsedMs = 0;
121461
121671
  let timeoutHandle;
@@ -121849,7 +122059,7 @@ ${this.stack.split("\n").slice(1).join("\n")}` : this.toString();
121849
122059
  type: "initialize_start"
121850
122060
  });
121851
122061
  const ACP_INIT_TIMEOUT_MS = Math.max(0, timeoutOptions.initTimeoutMs ?? 12e4);
121852
- const initResponse = await withTimeout$1(withAbort(connection.initialize({
122062
+ const initResponse = await withTimeout$2(withAbort(connection.initialize({
121853
122063
  protocolVersion: PROTOCOL_VERSION,
121854
122064
  clientCapabilities: {
121855
122065
  terminal: this.terminalEnabled,
@@ -121957,7 +122167,7 @@ ${this.stack.split("\n").slice(1).join("\n")}` : this.toString();
121957
122167
  } else {
121958
122168
  this.logger.debug(`[${this.options.sessionId}] Calling connection.newSession (cwd=${workdir})`);
121959
122169
  const ACP_NEW_SESSION_TIMEOUT_MS = Math.max(0, timeoutOptions.newSessionTimeoutMs ?? 12e4);
121960
- sessionResponse = await withTimeout$1(withAbort(connection.newSession({
122170
+ sessionResponse = await withTimeout$2(withAbort(connection.newSession({
121961
122171
  cwd: workdir,
121962
122172
  mcpServers
121963
122173
  }), startupAbort), this.logger, "connection.newSession", this.options.sessionId, ACP_NEW_SESSION_TIMEOUT_MS);
@@ -122070,7 +122280,7 @@ ${this.stack.split("\n").slice(1).join("\n")}` : this.toString();
122070
122280
  return false;
122071
122281
  }
122072
122282
  this.logger.debug(`[${this.options.sessionId}] Closing ACP session (acpSessionId=${sessionId} timeoutMs=${timeoutMs})`);
122073
- await withTimeout$1(closeSession2.call(this.connection, {
122283
+ await withTimeout$2(closeSession2.call(this.connection, {
122074
122284
  sessionId
122075
122285
  }), this.logger, "connection.closeSession", this.options.sessionId, timeoutMs, Math.min(timeoutMs, 1e3));
122076
122286
  this.logger.debug(`[${this.options.sessionId}] ACP session close finished (acpSessionId=${sessionId})`);
@@ -122208,7 +122418,7 @@ ${this.stack.split("\n").slice(1).join("\n")}` : this.toString();
122208
122418
  },
122209
122419
  codex: {
122210
122420
  packageName: "acp-extension-codex",
122211
- version: "0.14.2",
122421
+ version: "0.14.3",
122212
122422
  binName: "acp-extension-codex",
122213
122423
  args: [
122214
122424
  "-c",
@@ -122228,12 +122438,50 @@ ${this.stack.split("\n").slice(1).join("\n")}` : this.toString();
122228
122438
  "claude",
122229
122439
  "codex"
122230
122440
  ]);
122441
+ const moduleRequire = createRequire$1(import.meta.url);
122442
+ function isRecord$1(value) {
122443
+ return typeof value === "object" && value !== null;
122444
+ }
122445
+ function resolveInstalledPackageBin(setting) {
122446
+ try {
122447
+ const packageJsonPath = moduleRequire.resolve(`${setting.packageName}/package.json`);
122448
+ const packageJson = JSON.parse(readFileSync$1(packageJsonPath, "utf8"));
122449
+ if (!isRecord$1(packageJson) || packageJson.version !== setting.version) {
122450
+ return void 0;
122451
+ }
122452
+ const bin2 = packageJson.bin;
122453
+ const namedBin = isRecord$1(bin2) ? bin2[setting.binName] : void 0;
122454
+ const relativeBin = typeof bin2 === "string" ? bin2 : typeof namedBin === "string" ? namedBin : void 0;
122455
+ if (!relativeBin) {
122456
+ return void 0;
122457
+ }
122458
+ const binPath = resolve$2(dirname$1(packageJsonPath), relativeBin);
122459
+ return existsSync(binPath) ? binPath : void 0;
122460
+ } catch {
122461
+ return void 0;
122462
+ }
122463
+ }
122231
122464
  function resolveBuiltinACPSetting(agentType) {
122232
122465
  if (!builtinTypeSet.has(agentType)) {
122233
122466
  throw new Error(`Unsupported builtin ACP type: ${agentType}`);
122234
122467
  }
122235
122468
  const builtinType = agentType;
122236
122469
  const setting = BuiltinACPSetting[builtinType];
122470
+ if (builtinType === "claude") {
122471
+ const localClaudeAcpPath = resolveInstalledPackageBin(setting);
122472
+ if (localClaudeAcpPath) {
122473
+ return {
122474
+ status: {
122475
+ agent: `local ${setting.packageName}@${setting.version}`,
122476
+ command: localClaudeAcpPath
122477
+ },
122478
+ exec: {
122479
+ command: localClaudeAcpPath,
122480
+ args: setting.args ?? []
122481
+ }
122482
+ };
122483
+ }
122484
+ }
122237
122485
  const agent = `${setting.packageName}@${setting.version}`;
122238
122486
  const packageSpec = `${setting.packageName}@${setting.version}`;
122239
122487
  const args2 = [
@@ -122270,11 +122518,7 @@ ${this.stack.split("\n").slice(1).join("\n")}` : this.toString();
122270
122518
  }
122271
122519
  return `${agent.id}@${agent.version}`;
122272
122520
  }
122273
- function resolveRegistryACPSetting(agentType) {
122274
- const agent = registryAgentsById[agentType];
122275
- if (!agent) {
122276
- throw new Error(`Unknown registry ACP type: ${agentType}`);
122277
- }
122521
+ function resolveRegistryAgentACPSetting(agent) {
122278
122522
  if (agent.distribution.local?.command) {
122279
122523
  const isNpx = agent.distribution.local.command === "npx";
122280
122524
  const args2 = [
@@ -122331,7 +122575,14 @@ ${this.stack.split("\n").slice(1).join("\n")}` : this.toString();
122331
122575
  }
122332
122576
  };
122333
122577
  }
122334
- throw new Error(`Registry ACP ${agentType} has no supported launcher`);
122578
+ throw new Error(`Registry ACP ${agent.id} has no supported launcher`);
122579
+ }
122580
+ function resolveRegistryACPSetting(agentType) {
122581
+ const agent = registryAgentsById[agentType];
122582
+ if (!agent) {
122583
+ throw new Error(`Unknown registry ACP type: ${agentType}`);
122584
+ }
122585
+ return resolveRegistryAgentACPSetting(agent);
122335
122586
  }
122336
122587
  function resolveACPSetting(input2) {
122337
122588
  if (input2.cliType === "builtin") {
@@ -122542,7 +122793,7 @@ ${this.stack.split("\n").slice(1).join("\n")}` : this.toString();
122542
122793
  sessionResponse
122543
122794
  };
122544
122795
  };
122545
- function waitForChildProcessExit(child, timeoutMs) {
122796
+ function waitForChildProcessExit$1(child, timeoutMs) {
122546
122797
  if (child.exitCode !== null) {
122547
122798
  return Promise.resolve(true);
122548
122799
  }
@@ -122563,32 +122814,32 @@ ${this.stack.split("\n").slice(1).join("\n")}` : this.toString();
122563
122814
  child.once("exit", onExit2);
122564
122815
  });
122565
122816
  }
122566
- function signalChildProcess(child, signal) {
122817
+ function signalChildProcess$1(child, signal) {
122567
122818
  if (process.platform !== "win32" && typeof child.pid === "number" && child.pid > 0) {
122568
122819
  process.kill(-child.pid, signal);
122569
122820
  return;
122570
122821
  }
122571
122822
  child.kill(signal);
122572
122823
  }
122573
- async function terminateChildProcess(child, logger2, sessionLabel, exitTimeoutMs) {
122824
+ async function terminateChildProcess$1(child, logger2, sessionLabel, exitTimeoutMs) {
122574
122825
  if (child.exitCode !== null) {
122575
122826
  return;
122576
122827
  }
122577
122828
  try {
122578
- signalChildProcess(child, "SIGTERM");
122829
+ signalChildProcess$1(child, "SIGTERM");
122579
122830
  } catch {
122580
122831
  return;
122581
122832
  }
122582
- if (await waitForChildProcessExit(child, exitTimeoutMs)) {
122833
+ if (await waitForChildProcessExit$1(child, exitTimeoutMs)) {
122583
122834
  return;
122584
122835
  }
122585
122836
  logger2.debug(`[${sessionLabel}] ACP agent process did not exit within ${exitTimeoutMs}ms of SIGTERM; escalating to SIGKILL`);
122586
122837
  try {
122587
- signalChildProcess(child, "SIGKILL");
122838
+ signalChildProcess$1(child, "SIGKILL");
122588
122839
  } catch {
122589
122840
  return;
122590
122841
  }
122591
- await waitForChildProcessExit(child, exitTimeoutMs);
122842
+ await waitForChildProcessExit$1(child, exitTimeoutMs);
122592
122843
  }
122593
122844
  const spawnAcpProcess = (options) => {
122594
122845
  const setting = resolveACPSetting({
@@ -122735,7 +122986,7 @@ ${this.stack.split("\n").slice(1).join("\n")}` : this.toString();
122735
122986
  sessionResponse: started.sessionResponse
122736
122987
  };
122737
122988
  } catch (error2) {
122738
- await terminateChildProcess(agentProcess, options.logger, "acp-startup", 3e3);
122989
+ await terminateChildProcess$1(agentProcess, options.logger, "acp-startup", 3e3);
122739
122990
  throw error2;
122740
122991
  } finally {
122741
122992
  startupMonitor.dispose();
@@ -122751,7 +123002,7 @@ ${this.stack.split("\n").slice(1).join("\n")}` : this.toString();
122751
123002
  options.logger.debug(`[${options.sessionLabel}] ACP session close failed during local agent shutdown: ${error2 instanceof Error ? error2.message : String(error2)}`);
122752
123003
  }
122753
123004
  }
122754
- await terminateChildProcess(options.agentProcess, options.logger, options.sessionLabel, exitTimeoutMs);
123005
+ await terminateChildProcess$1(options.agentProcess, options.logger, options.sessionLabel, exitTimeoutMs);
122755
123006
  }
122756
123007
  const isRecord = (value) => typeof value === "object" && value !== null;
122757
123008
  const getStringField = (obj, key2) => {
@@ -125761,6 +126012,154 @@ $mem | ConvertTo-Json -Compress
125761
126012
  return null;
125762
126013
  }
125763
126014
  }
126015
+ function getImportedAcpSourceAcpSessionId(meta) {
126016
+ const externalHistory = meta.externalHistory;
126017
+ const provider2 = externalHistory?.provider;
126018
+ if (!externalHistory || provider2 !== "codex" && provider2 !== "claude") {
126019
+ return void 0;
126020
+ }
126021
+ return externalHistory.sourceAcpSessionId;
126022
+ }
126023
+ function isImportedAcpReplayUserTurn(entry2, meta) {
126024
+ const provider2 = meta.externalHistory?.provider;
126025
+ const sourceAcpSessionId = getImportedAcpSourceAcpSessionId(meta);
126026
+ return !!provider2 && !!sourceAcpSessionId && entry2.id.startsWith(`${provider2}:${sourceAcpSessionId}:turn:`);
126027
+ }
126028
+ function resolveResumableAcpSessionId(meta) {
126029
+ const acpSessionId = meta?.acpSessionId;
126030
+ if (!meta || !acpSessionId) {
126031
+ return void 0;
126032
+ }
126033
+ if (!meta.externalHistory) {
126034
+ return acpSessionId;
126035
+ }
126036
+ const sourceAcpSessionId = meta.externalHistory.sourceAcpSessionId;
126037
+ if (!sourceAcpSessionId) {
126038
+ return void 0;
126039
+ }
126040
+ return acpSessionId === sourceAcpSessionId ? void 0 : acpSessionId;
126041
+ }
126042
+ function resolveDispatchAcpSessionId(meta) {
126043
+ const liveSessionId = resolveResumableAcpSessionId(meta);
126044
+ if (liveSessionId || !meta?.externalHistory || meta.externalHistory.status === "sync_conflict") {
126045
+ return liveSessionId;
126046
+ }
126047
+ return meta.externalHistory.sourceAcpSessionId;
126048
+ }
126049
+ function resolveSessionDispatchAction(snapshot, machineId) {
126050
+ const { meta, history, hasActiveTurn, hasBlockingPendingCreate, hasReusableSession } = snapshot;
126051
+ if (!meta || meta.machineId !== machineId) {
126052
+ return {
126053
+ type: "noop",
126054
+ reason: "not-owned"
126055
+ };
126056
+ }
126057
+ if (meta.isArchived) {
126058
+ return {
126059
+ type: "noop",
126060
+ reason: "archived"
126061
+ };
126062
+ }
126063
+ if (hasBlockingPendingCreate) {
126064
+ return {
126065
+ type: "noop",
126066
+ reason: "pending-create"
126067
+ };
126068
+ }
126069
+ const statusType = meta.status?.type;
126070
+ if (statusType === "running" || statusType === "requestPermission" || statusType === "initializing") {
126071
+ if (hasActiveTurn) {
126072
+ return {
126073
+ type: "noop",
126074
+ reason: "active-session"
126075
+ };
126076
+ }
126077
+ return {
126078
+ type: "reset-stale-status",
126079
+ statusType
126080
+ };
126081
+ }
126082
+ const turn = findNextDispatchableUserTurn(history, meta);
126083
+ if (!turn) {
126084
+ return {
126085
+ type: "no-dispatchable-turn"
126086
+ };
126087
+ }
126088
+ const mode2 = hasReusableSession || resolveDispatchAcpSessionId(meta) ? "continue" : "create";
126089
+ return {
126090
+ type: "dispatch",
126091
+ mode: mode2,
126092
+ turn
126093
+ };
126094
+ }
126095
+ function resolveSessionCancelAction(meta, lastSeenCancelTurn, machineId) {
126096
+ if (!meta || meta.machineId !== machineId) {
126097
+ return {
126098
+ type: "noop",
126099
+ reason: "not-owned"
126100
+ };
126101
+ }
126102
+ if (meta.isArchived) {
126103
+ return {
126104
+ type: "noop",
126105
+ reason: "archived"
126106
+ };
126107
+ }
126108
+ const lastCanceledTurn = meta.lastCanceledTurn;
126109
+ if (typeof lastCanceledTurn !== "string" || !lastCanceledTurn) {
126110
+ return {
126111
+ type: "noop",
126112
+ reason: "no-cancel-turn"
126113
+ };
126114
+ }
126115
+ if (lastCanceledTurn === lastSeenCancelTurn) {
126116
+ return {
126117
+ type: "noop",
126118
+ reason: "already-seen"
126119
+ };
126120
+ }
126121
+ return {
126122
+ type: "cancel",
126123
+ turnId: lastCanceledTurn
126124
+ };
126125
+ }
126126
+ function findNextDispatchableUserTurn(history, meta) {
126127
+ for (const entry2 of history) {
126128
+ if (entry2.role !== "user") {
126129
+ continue;
126130
+ }
126131
+ if (isImportedAcpReplayUserTurn(entry2, meta)) {
126132
+ continue;
126133
+ }
126134
+ if (typeof entry2.status === "string") {
126135
+ if (entry2.status === "pending" || entry2.status === "seen" || entry2.status === "processing") {
126136
+ return entry2;
126137
+ }
126138
+ continue;
126139
+ }
126140
+ if (entry2.read === false) {
126141
+ return entry2;
126142
+ }
126143
+ if (entry2.id === meta.processingUserMsgId) {
126144
+ return entry2;
126145
+ }
126146
+ if (entry2.id === meta.latestUserMsgId && entry2.id !== meta.lastHandledUserMsgId) {
126147
+ return entry2;
126148
+ }
126149
+ }
126150
+ return null;
126151
+ }
126152
+ function resolveDispatchTurnInput(entry2) {
126153
+ const historyBlocks = historyItemsToInputBlocks(entry2.items);
126154
+ const configuredBlocks = normalizeSessionInputBlocks(entry2.inputConfig?.inputBlocks, "");
126155
+ const fallbackBlocks = normalizeSessionInputBlocks(void 0, entry2.inputConfig?.prompt ?? "");
126156
+ const inputBlocks = configuredBlocks.length > 0 ? configuredBlocks : historyBlocks.length > 0 ? historyBlocks : fallbackBlocks;
126157
+ const prompt2 = entry2.inputConfig?.prompt ?? extractPromptPreviewFromInputBlocks(inputBlocks.length > 0 ? inputBlocks : historyBlocks);
126158
+ return {
126159
+ inputBlocks,
126160
+ prompt: prompt2
126161
+ };
126162
+ }
125764
126163
  class SessionTurnCancelled extends TaggedError("SessionTurnCancelled") {
125765
126164
  }
125766
126165
  class SessionTurnHalted extends TaggedError("SessionTurnHalted") {
@@ -126753,7 +127152,7 @@ $mem | ConvertTo-Json -Compress
126753
127152
  const agentConfigEnv = agentConfigEnvResolution.env ?? void 0;
126754
127153
  self2.deps.logger.debug(`[${sessionId}] Resume env resolved (agentConfigId=${meta?.agentConfigId ?? "none"} reason=${agentConfigEnvResolution.reason} keys=${agentConfigEnv ? Object.keys(agentConfigEnv).length : 0})`);
126755
127154
  const requestedResumeSessionId = acpSessionConfig.resume;
126756
- const storedResumeSessionId = meta?.acpSessionId;
127155
+ const storedResumeSessionId = resolveResumableAcpSessionId(meta);
126757
127156
  const resumeSessionId = requestedResumeSessionId ?? storedResumeSessionId;
126758
127157
  const resumeSource = requestedResumeSessionId ? "request" : storedResumeSessionId ? "meta" : "none";
126759
127158
  self2.deps.logger.debug(`[${sessionId}] Session not found in memory; restoring (project=${project?.kind === "github" ? project.repoFullName : project?.kind === "local" ? `local:${project.localProjectId}` : "none"} resume=${resumeSessionId ? "yes" : "no"} resumeSource=${resumeSource} resumeSessionId=${resumeSessionId ?? "none"})`);
@@ -127470,117 +127869,6 @@ $mem | ConvertTo-Json -Compress
127470
127869
  };
127471
127870
  }
127472
127871
  }
127473
- function resolveSessionDispatchAction(snapshot, machineId) {
127474
- const { meta, history, hasActiveTurn, hasBlockingPendingCreate, hasReusableSession } = snapshot;
127475
- if (!meta || meta.machineId !== machineId) {
127476
- return {
127477
- type: "noop",
127478
- reason: "not-owned"
127479
- };
127480
- }
127481
- if (meta.isArchived) {
127482
- return {
127483
- type: "noop",
127484
- reason: "archived"
127485
- };
127486
- }
127487
- if (hasBlockingPendingCreate) {
127488
- return {
127489
- type: "noop",
127490
- reason: "pending-create"
127491
- };
127492
- }
127493
- const statusType = meta.status?.type;
127494
- if (statusType === "running" || statusType === "requestPermission" || statusType === "initializing") {
127495
- if (hasActiveTurn) {
127496
- return {
127497
- type: "noop",
127498
- reason: "active-session"
127499
- };
127500
- }
127501
- return {
127502
- type: "reset-stale-status",
127503
- statusType
127504
- };
127505
- }
127506
- const turn = findNextDispatchableUserTurn(history, meta);
127507
- if (!turn) {
127508
- return {
127509
- type: "no-dispatchable-turn"
127510
- };
127511
- }
127512
- const mode2 = hasReusableSession || meta.acpSessionId ? "continue" : "create";
127513
- return {
127514
- type: "dispatch",
127515
- mode: mode2,
127516
- turn
127517
- };
127518
- }
127519
- function resolveSessionCancelAction(meta, lastSeenCancelTurn, machineId) {
127520
- if (!meta || meta.machineId !== machineId) {
127521
- return {
127522
- type: "noop",
127523
- reason: "not-owned"
127524
- };
127525
- }
127526
- if (meta.isArchived) {
127527
- return {
127528
- type: "noop",
127529
- reason: "archived"
127530
- };
127531
- }
127532
- const lastCanceledTurn = meta.lastCanceledTurn;
127533
- if (typeof lastCanceledTurn !== "string" || !lastCanceledTurn) {
127534
- return {
127535
- type: "noop",
127536
- reason: "no-cancel-turn"
127537
- };
127538
- }
127539
- if (lastCanceledTurn === lastSeenCancelTurn) {
127540
- return {
127541
- type: "noop",
127542
- reason: "already-seen"
127543
- };
127544
- }
127545
- return {
127546
- type: "cancel",
127547
- turnId: lastCanceledTurn
127548
- };
127549
- }
127550
- function findNextDispatchableUserTurn(history, meta) {
127551
- for (const entry2 of history) {
127552
- if (entry2.role !== "user") {
127553
- continue;
127554
- }
127555
- if (typeof entry2.status === "string") {
127556
- if (entry2.status === "pending" || entry2.status === "seen" || entry2.status === "processing") {
127557
- return entry2;
127558
- }
127559
- continue;
127560
- }
127561
- if (entry2.read === false) {
127562
- return entry2;
127563
- }
127564
- if (entry2.id === meta.processingUserMsgId) {
127565
- return entry2;
127566
- }
127567
- if (entry2.id === meta.latestUserMsgId && entry2.id !== meta.lastHandledUserMsgId) {
127568
- return entry2;
127569
- }
127570
- }
127571
- return null;
127572
- }
127573
- function resolveDispatchTurnInput(entry2) {
127574
- const historyBlocks = historyItemsToInputBlocks(entry2.items);
127575
- const configuredBlocks = normalizeSessionInputBlocks(entry2.inputConfig?.inputBlocks, "");
127576
- const fallbackBlocks = normalizeSessionInputBlocks(void 0, entry2.inputConfig?.prompt ?? "");
127577
- const inputBlocks = configuredBlocks.length > 0 ? configuredBlocks : historyBlocks.length > 0 ? historyBlocks : fallbackBlocks;
127578
- const prompt2 = entry2.inputConfig?.prompt ?? extractPromptPreviewFromInputBlocks(inputBlocks.length > 0 ? inputBlocks : historyBlocks);
127579
- return {
127580
- inputBlocks,
127581
- prompt: prompt2
127582
- };
127583
- }
127584
127872
  const isConfigOptionValueRecord = (value) => {
127585
127873
  if (!value || typeof value !== "object" || Array.isArray(value)) {
127586
127874
  return false;
@@ -127955,7 +128243,7 @@ $mem | ConvertTo-Json -Compress
127955
128243
  modelId: entry2.inputConfig?.modelId,
127956
128244
  configOptionValues: entry2.inputConfig?.configOptionValues,
127957
128245
  issuePRMentions: entry2.inputConfig?.issuePRMentions,
127958
- resume: entry2.inputConfig?.resume ?? meta.acpSessionId ?? void 0
128246
+ resume: entry2.inputConfig?.resume ?? resolveDispatchAcpSessionId(meta)
127959
128247
  },
127960
128248
  userTurnId: entry2.id,
127961
128249
  userId: entry2.userId ?? meta.userId,
@@ -128017,7 +128305,7 @@ $mem | ConvertTo-Json -Compress
128017
128305
  modelId: queuedItem.acpSessionConfig?.modelId,
128018
128306
  configOptionValues: isConfigOptionValueRecord(queuedItem.acpSessionConfig?.configOptionValues) ? queuedItem.acpSessionConfig.configOptionValues : void 0,
128019
128307
  issuePRMentions: queuedItem.acpSessionConfig?.issuePRMentions,
128020
- resume: meta.acpSessionId ?? void 0
128308
+ resume: resolveResumableAcpSessionId(meta)
128021
128309
  });
128022
128310
  const pendingEntry = buildPendingUserHistoryEntry({
128023
128311
  userId: queuedItem.userId ?? meta.userId,
@@ -129053,7 +129341,7 @@ $mem | ConvertTo-Json -Compress
129053
129341
  }
129054
129342
  })();
129055
129343
  try {
129056
- await withTimeout(readyPromise, TUNNEL_READY_TIMEOUT_MS, "Timed out waiting for preview tunnel control connection to become ready");
129344
+ await withTimeout$1(readyPromise, TUNNEL_READY_TIMEOUT_MS, "Timed out waiting for preview tunnel control connection to become ready");
129057
129345
  } catch (error2) {
129058
129346
  await close2("Preview tunnel failed to become ready");
129059
129347
  throw error2;
@@ -129876,7 +130164,7 @@ ${escapeHtmlScriptContent(VISUAL_ANNOTATION_INSPECTOR_BROWSER_SCRIPT)}
129876
130164
  function asError(error2) {
129877
130165
  return error2 instanceof Error ? error2 : new Error(formatError(error2));
129878
130166
  }
129879
- async function withTimeout(promise, timeoutMs, message) {
130167
+ async function withTimeout$1(promise, timeoutMs, message) {
129880
130168
  let timeoutHandle;
129881
130169
  const timeoutPromise = new Promise((_2, reject) => {
129882
130170
  timeoutHandle = setTimeout(() => reject(new Error(message)), timeoutMs);
@@ -130738,6 +131026,794 @@ ${escapeHtmlScriptContent(VISUAL_ANNOTATION_INSPECTOR_BROWSER_SCRIPT)}
130738
131026
  return Math.round(this.deps.now?.() ?? getServerNow());
130739
131027
  }
130740
131028
  }
131029
+ const ACP_OPERATION_TIMEOUT_MS = 12e4;
131030
+ const ACP_PROCESS_EXIT_TIMEOUT_MS = 3e3;
131031
+ function waitForChildProcessExit(child, timeoutMs) {
131032
+ if (child.exitCode !== null) {
131033
+ return Promise.resolve(true);
131034
+ }
131035
+ return new Promise((resolve2) => {
131036
+ const timeout2 = setTimeout(() => {
131037
+ child.off("exit", onExit2);
131038
+ resolve2(child.exitCode !== null);
131039
+ }, timeoutMs);
131040
+ const onExit2 = () => {
131041
+ clearTimeout(timeout2);
131042
+ resolve2(true);
131043
+ };
131044
+ child.once("exit", onExit2);
131045
+ });
131046
+ }
131047
+ function signalChildProcess(child, signal) {
131048
+ if (process.platform !== "win32" && typeof child.pid === "number" && child.pid > 0) {
131049
+ process.kill(-child.pid, signal);
131050
+ return;
131051
+ }
131052
+ child.kill(signal);
131053
+ }
131054
+ async function terminateChildProcess(child) {
131055
+ if (child.exitCode !== null) {
131056
+ return;
131057
+ }
131058
+ try {
131059
+ signalChildProcess(child, "SIGTERM");
131060
+ } catch {
131061
+ return;
131062
+ }
131063
+ if (await waitForChildProcessExit(child, ACP_PROCESS_EXIT_TIMEOUT_MS)) {
131064
+ return;
131065
+ }
131066
+ try {
131067
+ signalChildProcess(child, "SIGKILL");
131068
+ } catch {
131069
+ return;
131070
+ }
131071
+ await waitForChildProcessExit(child, ACP_PROCESS_EXIT_TIMEOUT_MS);
131072
+ }
131073
+ async function withTimeout(promise, label2) {
131074
+ let timeout2 = null;
131075
+ try {
131076
+ return await Promise.race([
131077
+ promise,
131078
+ new Promise((_resolve, reject) => {
131079
+ timeout2 = setTimeout(() => reject(new Error(`${label2} timed out after ${ACP_OPERATION_TIMEOUT_MS}ms`)), ACP_OPERATION_TIMEOUT_MS);
131080
+ })
131081
+ ]);
131082
+ } finally {
131083
+ if (timeout2) {
131084
+ clearTimeout(timeout2);
131085
+ }
131086
+ }
131087
+ }
131088
+ class AcpReplayCollectorClient {
131089
+ notifications = [];
131090
+ async requestPermission() {
131091
+ return {
131092
+ outcome: {
131093
+ outcome: "cancelled"
131094
+ }
131095
+ };
131096
+ }
131097
+ async sessionUpdate(params) {
131098
+ this.notifications.push(parseSessionNotification(params));
131099
+ }
131100
+ }
131101
+ function getProviderLabel$1(provider2) {
131102
+ return provider2 === "claude" ? "Claude" : "Codex";
131103
+ }
131104
+ async function createHistoryAcpConnection(args2) {
131105
+ const setting = resolveACPSetting({
131106
+ cliType: "builtin",
131107
+ agentType: args2.provider
131108
+ });
131109
+ const env2 = setting.exec.env ? {
131110
+ ...process.env,
131111
+ ...setting.exec.env
131112
+ } : process.env;
131113
+ const agentProcess = spawnAcpProcess({
131114
+ cliType: "builtin",
131115
+ agentType: args2.provider,
131116
+ workdir: args2.workdir,
131117
+ env: env2
131118
+ });
131119
+ agentProcess.stderr?.setEncoding("utf8");
131120
+ agentProcess.stderr?.on("data", (chunk) => {
131121
+ if (!chunk) return;
131122
+ args2.logger.debug(`[${args2.provider}-history-sync] ACP stderr: ${chunk.slice(0, 1200)}`);
131123
+ });
131124
+ if (!agentProcess.stdout || !agentProcess.stdin) {
131125
+ await terminateChildProcess(agentProcess);
131126
+ throw new Error(`${getProviderLabel$1(args2.provider)} ACP process did not expose stdio streams`);
131127
+ }
131128
+ const output = createStdoutReadableStream(agentProcess.stdout);
131129
+ const input2 = createStdinWritableStream(agentProcess.stdin);
131130
+ const stream2 = ndJsonStream(input2, output);
131131
+ const collector = new AcpReplayCollectorClient();
131132
+ const connection = new ClientSideConnection(() => collector, stream2);
131133
+ try {
131134
+ const initResponse = await withTimeout(connection.initialize({
131135
+ protocolVersion: PROTOCOL_VERSION,
131136
+ clientCapabilities: {
131137
+ terminal: false,
131138
+ fs: {
131139
+ readTextFile: false,
131140
+ writeTextFile: false
131141
+ }
131142
+ }
131143
+ }), `${getProviderLabel$1(args2.provider)} ACP initialize`);
131144
+ return {
131145
+ agentProcess,
131146
+ connection,
131147
+ collector,
131148
+ initResponse
131149
+ };
131150
+ } catch (error2) {
131151
+ await terminateChildProcess(agentProcess);
131152
+ throw error2;
131153
+ }
131154
+ }
131155
+ async function resolveCatalogQueryPaths(rootPath) {
131156
+ const resolved = path__default.resolve(rootPath);
131157
+ let real = null;
131158
+ try {
131159
+ real = await fs$5.realpath(resolved);
131160
+ } catch {
131161
+ real = null;
131162
+ }
131163
+ const paths = [
131164
+ resolved
131165
+ ];
131166
+ if (real && real !== resolved) {
131167
+ paths.push(real);
131168
+ }
131169
+ return paths;
131170
+ }
131171
+ async function listPaginatedHistorySessions(cwd, listPage) {
131172
+ const sessions = [];
131173
+ let cursor;
131174
+ do {
131175
+ const response = await listPage({
131176
+ cwd,
131177
+ cursor
131178
+ });
131179
+ sessions.push(...response.sessions);
131180
+ cursor = response.nextCursor;
131181
+ } while (cursor);
131182
+ return sessions;
131183
+ }
131184
+ async function listHistorySessionsForLocalProject(args2) {
131185
+ const queryPaths = await resolveCatalogQueryPaths(args2.rootPath);
131186
+ const bySessionId = /* @__PURE__ */ new Map();
131187
+ for (const cwd of queryPaths) {
131188
+ const { agentProcess, connection, initResponse } = await createHistoryAcpConnection({
131189
+ provider: args2.provider,
131190
+ workdir: cwd,
131191
+ logger: args2.logger
131192
+ });
131193
+ try {
131194
+ if (!initResponse.agentCapabilities?.sessionCapabilities?.list) {
131195
+ throw new Error(`${getProviderLabel$1(args2.provider)} ACP agent does not advertise sessionCapabilities.list`);
131196
+ }
131197
+ const sessions = await listPaginatedHistorySessions(cwd, async ({ cursor }) => withTimeout(connection.listSessions({
131198
+ cwd,
131199
+ cursor
131200
+ }), `${getProviderLabel$1(args2.provider)} ACP listSessions (${cwd})`));
131201
+ for (const session of sessions) {
131202
+ bySessionId.set(session.sessionId, session);
131203
+ }
131204
+ } finally {
131205
+ await terminateChildProcess(agentProcess);
131206
+ }
131207
+ }
131208
+ return {
131209
+ sessions: [
131210
+ ...bySessionId.values()
131211
+ ],
131212
+ queryPaths
131213
+ };
131214
+ }
131215
+ async function loadHistorySessionReplay(args2) {
131216
+ const cwd = path__default.resolve(args2.rootPath);
131217
+ const { agentProcess, connection, collector, initResponse } = await createHistoryAcpConnection({
131218
+ provider: args2.provider,
131219
+ workdir: cwd,
131220
+ logger: args2.logger
131221
+ });
131222
+ try {
131223
+ if (!initResponse.agentCapabilities?.loadSession) {
131224
+ throw new Error(`${getProviderLabel$1(args2.provider)} ACP agent does not advertise loadSession`);
131225
+ }
131226
+ await withTimeout(connection.loadSession({
131227
+ sessionId: args2.acpSessionId,
131228
+ cwd,
131229
+ mcpServers: []
131230
+ }), `${getProviderLabel$1(args2.provider)} ACP loadSession (${args2.acpSessionId})`);
131231
+ return collector.notifications;
131232
+ } catch (error2) {
131233
+ throw new Error(`Failed to load ${getProviderLabel$1(args2.provider)} session ${args2.acpSessionId}: ${formatErrorMessage(error2)}`, {
131234
+ cause: error2
131235
+ });
131236
+ } finally {
131237
+ await terminateChildProcess(agentProcess);
131238
+ }
131239
+ }
131240
+ const syncLeases = /* @__PURE__ */ new Set();
131241
+ const machineCatalogWriteChains = /* @__PURE__ */ new Map();
131242
+ async function withMachineCatalogWriteLock(machineRoomId, fn) {
131243
+ const prev = machineCatalogWriteChains.get(machineRoomId);
131244
+ const current2 = (async () => {
131245
+ if (prev) {
131246
+ await prev.catch(() => {
131247
+ });
131248
+ }
131249
+ return fn();
131250
+ })();
131251
+ machineCatalogWriteChains.set(machineRoomId, current2);
131252
+ try {
131253
+ return await current2;
131254
+ } finally {
131255
+ if (machineCatalogWriteChains.get(machineRoomId) === current2) {
131256
+ machineCatalogWriteChains.delete(machineRoomId);
131257
+ }
131258
+ }
131259
+ }
131260
+ function emptySummary() {
131261
+ return {
131262
+ listed: 0,
131263
+ imported: 0,
131264
+ refreshed: 0,
131265
+ skipped: 0,
131266
+ conflicted: 0,
131267
+ failed: 0,
131268
+ failures: []
131269
+ };
131270
+ }
131271
+ function stableJson(value) {
131272
+ if (value === null || typeof value !== "object") {
131273
+ return JSON.stringify(value);
131274
+ }
131275
+ if (Array.isArray(value)) {
131276
+ return `[${value.map((item) => stableJson(item)).join(",")}]`;
131277
+ }
131278
+ const record2 = value;
131279
+ const entries = Object.keys(record2).filter((key2) => record2[key2] !== void 0).sort().map((key2) => `${JSON.stringify(key2)}:${stableJson(record2[key2])}`);
131280
+ return `{${entries.join(",")}}`;
131281
+ }
131282
+ function hashText(value) {
131283
+ return createHash$1("sha256").update(value).digest("hex");
131284
+ }
131285
+ function normalizeHistoryEntryForHash(entry2) {
131286
+ return {
131287
+ role: entry2.role,
131288
+ items: entry2.items ?? [],
131289
+ plan: entry2.plan ?? []
131290
+ };
131291
+ }
131292
+ function hashHistoryEntry(entry2) {
131293
+ return hashText(stableJson(normalizeHistoryEntryForHash(entry2)));
131294
+ }
131295
+ function materializeReplay(args2) {
131296
+ let tempId = 0;
131297
+ const nowIso = new Date(getServerNow()).toISOString();
131298
+ const replay = buildHistoryReplayImport(args2.replayNotifications, {
131299
+ agentType: args2.provider,
131300
+ userId: args2.userId,
131301
+ now: () => nowIso,
131302
+ createId: () => `${args2.provider}:${args2.acpSessionId}:tmp:${tempId++}`,
131303
+ mode: "imported_snapshot"
131304
+ });
131305
+ const turnHashes = replay.history.map(hashHistoryEntry);
131306
+ const history = replay.history.map((entry2, index2) => ({
131307
+ ...entry2,
131308
+ id: `${args2.provider}:${args2.acpSessionId}:turn:${index2}:${turnHashes[index2].slice(0, 16)}`
131309
+ }));
131310
+ return {
131311
+ history,
131312
+ turnHashes,
131313
+ replayDigest: hashText(turnHashes.join("\n"))
131314
+ };
131315
+ }
131316
+ function isPrefix(prefix, value) {
131317
+ if (prefix.length > value.length) {
131318
+ return false;
131319
+ }
131320
+ for (let index2 = 0; index2 < prefix.length; index2 += 1) {
131321
+ if (prefix[index2] !== value[index2]) {
131322
+ return false;
131323
+ }
131324
+ }
131325
+ return true;
131326
+ }
131327
+ function decideHistoryRefresh(args2) {
131328
+ if (args2.replayDigest === args2.externalHistory.replayDigest) {
131329
+ return {
131330
+ status: "skipped",
131331
+ reason: "digest_match"
131332
+ };
131333
+ }
131334
+ if (!isPrefix(args2.externalHistory.importedTurnHashes, args2.turnHashes)) {
131335
+ return {
131336
+ status: "conflicted",
131337
+ reason: "prefix_mismatch"
131338
+ };
131339
+ }
131340
+ if (args2.currentHistoryLength !== void 0 && args2.currentHistoryLength !== args2.externalHistory.importedTurnCount) {
131341
+ return {
131342
+ status: "conflicted",
131343
+ reason: "local_history_has_untracked_suffix"
131344
+ };
131345
+ }
131346
+ const appendFromIndex = args2.externalHistory.importedTurnCount;
131347
+ return args2.turnHashes.length > appendFromIndex ? {
131348
+ status: "refreshed",
131349
+ reason: "prefix_append",
131350
+ appendFromIndex
131351
+ } : {
131352
+ status: "skipped",
131353
+ reason: "empty_suffix",
131354
+ appendFromIndex
131355
+ };
131356
+ }
131357
+ async function listWorkspaceSessionMetas(manager) {
131358
+ const scanner = manager.repo.getMeta();
131359
+ if (!scanner) {
131360
+ return [];
131361
+ }
131362
+ const roomIds = /* @__PURE__ */ new Set();
131363
+ for (const row of await scanner.scan({
131364
+ prefix: [
131365
+ "m"
131366
+ ]
131367
+ })) {
131368
+ const key2 = row.key;
131369
+ if (!Array.isArray(key2) || key2.length < 2) {
131370
+ continue;
131371
+ }
131372
+ const roomId = key2[1];
131373
+ if (typeof roomId === "string" && isSessionDocRoomId(roomId)) {
131374
+ roomIds.add(roomId);
131375
+ }
131376
+ }
131377
+ const metas = await Promise.all([
131378
+ ...roomIds
131379
+ ].map(async (roomId) => {
131380
+ const record2 = await manager.repo.getDocMeta(roomId);
131381
+ if (!record2?.meta || isLoroRepoDocDeleted(record2)) {
131382
+ return null;
131383
+ }
131384
+ const sessionId = roomId.slice("session-".length);
131385
+ return {
131386
+ sessionId,
131387
+ meta: record2.meta
131388
+ };
131389
+ }));
131390
+ return metas.filter((meta) => meta !== null);
131391
+ }
131392
+ function buildExistingHistorySessionIndex(metas, machineId, provider2) {
131393
+ const index2 = /* @__PURE__ */ new Map();
131394
+ for (const entry2 of metas) {
131395
+ if (entry2.meta.machineId !== machineId) continue;
131396
+ if (entry2.meta.cliType !== "builtin") continue;
131397
+ if (entry2.meta.agentType !== provider2) continue;
131398
+ const acpSessionIds = /* @__PURE__ */ new Set();
131399
+ if (entry2.meta.externalHistory?.provider === provider2) {
131400
+ const sourceAcpSessionId = entry2.meta.externalHistory.sourceAcpSessionId;
131401
+ if (sourceAcpSessionId) {
131402
+ acpSessionIds.add(sourceAcpSessionId);
131403
+ }
131404
+ if (entry2.meta.acpSessionId && entry2.meta.acpSessionId !== sourceAcpSessionId) {
131405
+ acpSessionIds.add(entry2.meta.acpSessionId);
131406
+ }
131407
+ } else if (entry2.meta.acpSessionId) {
131408
+ acpSessionIds.add(entry2.meta.acpSessionId);
131409
+ }
131410
+ for (const acpSessionId of acpSessionIds) {
131411
+ index2.set(acpSessionId, entry2);
131412
+ }
131413
+ }
131414
+ return index2;
131415
+ }
131416
+ function getProviderLabel(provider2) {
131417
+ return provider2 === "claude" ? "Claude" : "Codex";
131418
+ }
131419
+ function resolveSessionTitle(info, provider2) {
131420
+ const title2 = info.title?.trim();
131421
+ return title2 || `${getProviderLabel(provider2)} session`;
131422
+ }
131423
+ function parseUpdatedAtMs(updatedAt) {
131424
+ if (!updatedAt) return 0;
131425
+ const parsed = Date.parse(updatedAt);
131426
+ return Number.isFinite(parsed) ? parsed : 0;
131427
+ }
131428
+ function compareCatalogItems(left2, right2) {
131429
+ const leftUpdatedAt = parseUpdatedAtMs(left2.updatedAt);
131430
+ const rightUpdatedAt = parseUpdatedAtMs(right2.updatedAt);
131431
+ if (leftUpdatedAt !== rightUpdatedAt) {
131432
+ return rightUpdatedAt - leftUpdatedAt;
131433
+ }
131434
+ return left2.title.localeCompare(right2.title);
131435
+ }
131436
+ function getHistoryCatalogStatus(existing) {
131437
+ if (!existing) return "available";
131438
+ return existing.meta.externalHistory?.status === "sync_conflict" ? "sync_conflict" : "imported";
131439
+ }
131440
+ function buildCatalogItem(provider2, info, existing) {
131441
+ const acpSessionId = info.sessionId;
131442
+ return {
131443
+ acpSessionId,
131444
+ title: resolveSessionTitle(info, provider2),
131445
+ updatedAt: info.updatedAt ?? void 0,
131446
+ importedSessionId: existing?.sessionId,
131447
+ status: getHistoryCatalogStatus(existing)
131448
+ };
131449
+ }
131450
+ function shouldSkipBySourceUpdatedAt(info, externalHistory) {
131451
+ if (externalHistory.status === "metadata_only") {
131452
+ return false;
131453
+ }
131454
+ if (!info.updatedAt || !externalHistory.sourceUpdatedAt) {
131455
+ return false;
131456
+ }
131457
+ const next = Date.parse(info.updatedAt);
131458
+ const current2 = Date.parse(externalHistory.sourceUpdatedAt);
131459
+ return Number.isFinite(next) && Number.isFinite(current2) && next <= current2;
131460
+ }
131461
+ function resolveSourceUpdatedAtMs(info, fallback2) {
131462
+ if (!info.updatedAt) {
131463
+ return fallback2;
131464
+ }
131465
+ const parsed = Date.parse(info.updatedAt);
131466
+ return Number.isFinite(parsed) ? parsed : fallback2;
131467
+ }
131468
+ function buildExternalHistoryMeta(args2) {
131469
+ return {
131470
+ provider: args2.provider,
131471
+ source: args2.provider === "claude" ? "local-claude-home" : "local-codex-home",
131472
+ sourceAcpSessionId: args2.sourceAcpSessionId,
131473
+ sourceUpdatedAt: args2.sourceUpdatedAt ?? void 0,
131474
+ replayDigest: args2.materialized.replayDigest,
131475
+ importedTurnCount: args2.materialized.turnHashes.length,
131476
+ importedTurnHashes: args2.materialized.turnHashes,
131477
+ lastSyncAt: getServerNow(),
131478
+ status: args2.status ?? "synced",
131479
+ conflictReason: args2.conflictReason
131480
+ };
131481
+ }
131482
+ function buildMetadataOnlyExternalHistoryMeta(args2) {
131483
+ return {
131484
+ provider: args2.provider,
131485
+ source: args2.provider === "claude" ? "local-claude-home" : "local-codex-home",
131486
+ sourceAcpSessionId: args2.sourceAcpSessionId,
131487
+ sourceUpdatedAt: args2.sourceUpdatedAt ?? void 0,
131488
+ importedTurnCount: 0,
131489
+ importedTurnHashes: [],
131490
+ lastSyncAt: getServerNow(),
131491
+ status: "metadata_only"
131492
+ };
131493
+ }
131494
+ class LocalProjectHistorySyncService {
131495
+ constructor(manager, logger2, context2, provider2 = "codex") {
131496
+ this.manager = manager;
131497
+ this.logger = logger2;
131498
+ this.context = context2;
131499
+ this.provider = provider2;
131500
+ }
131501
+ provider;
131502
+ async syncLocalProject(args2) {
131503
+ const leaseKey = `${this.provider}:${this.context.workspaceId}:${this.context.machineId}:${args2.localProjectId}`;
131504
+ if (syncLeases.has(leaseKey)) {
131505
+ throw new Error(`${getProviderLabel(this.provider)} history sync is already running for this local project`);
131506
+ }
131507
+ syncLeases.add(leaseKey);
131508
+ try {
131509
+ return await this.syncLocalProjectInner(args2);
131510
+ } finally {
131511
+ syncLeases.delete(leaseKey);
131512
+ }
131513
+ }
131514
+ async syncLocalProjectInner(args2) {
131515
+ const snapshot = await this.listCatalogSnapshot(args2.rootPath);
131516
+ return await this.writeCatalogResult({
131517
+ localProjectId: args2.localProjectId,
131518
+ sessions: snapshot.sessions,
131519
+ existingByAcpSessionId: snapshot.existingByAcpSessionId
131520
+ });
131521
+ }
131522
+ async importLocalProjectSessions(args2) {
131523
+ const leaseKey = `${this.provider}:${this.context.workspaceId}:${this.context.machineId}:${args2.localProjectId}`;
131524
+ if (syncLeases.has(leaseKey)) {
131525
+ throw new Error(`${getProviderLabel(this.provider)} history sync is already running for this local project`);
131526
+ }
131527
+ syncLeases.add(leaseKey);
131528
+ try {
131529
+ return await this.importLocalProjectSessionsInner(args2);
131530
+ } finally {
131531
+ syncLeases.delete(leaseKey);
131532
+ }
131533
+ }
131534
+ async importLocalProjectSessionsInner(args2) {
131535
+ const summary2 = emptySummary();
131536
+ const selectedIds = [
131537
+ ...new Set(args2.acpSessionIds)
131538
+ ];
131539
+ summary2.listed = selectedIds.length;
131540
+ const snapshot = await this.listCatalogSnapshot(args2.rootPath);
131541
+ const infoByAcpSessionId = new Map(snapshot.sessions.map((info) => [
131542
+ info.sessionId,
131543
+ info
131544
+ ]));
131545
+ const project = {
131546
+ kind: "local",
131547
+ localProjectId: args2.localProjectId
131548
+ };
131549
+ for (const selectedId of selectedIds) {
131550
+ const acpSessionId = selectedId;
131551
+ const info = infoByAcpSessionId.get(selectedId);
131552
+ try {
131553
+ if (!info) {
131554
+ throw new Error(`${getProviderLabel(this.provider)} session was not found in the local project catalog`);
131555
+ }
131556
+ const existing = snapshot.existingByAcpSessionId.get(selectedId);
131557
+ if (!existing) {
131558
+ const importedSession = await this.importNewSession({
131559
+ info,
131560
+ acpSessionId,
131561
+ project
131562
+ });
131563
+ snapshot.existingByAcpSessionId.set(selectedId, importedSession);
131564
+ summary2.imported += 1;
131565
+ continue;
131566
+ }
131567
+ const status = await this.refreshExistingSession({
131568
+ existing,
131569
+ info,
131570
+ acpSessionId,
131571
+ rootPath: args2.rootPath
131572
+ });
131573
+ summary2[status] += 1;
131574
+ } catch (error2) {
131575
+ summary2.failed += 1;
131576
+ summary2.failures.push({
131577
+ acpSessionId,
131578
+ message: formatErrorMessage(error2)
131579
+ });
131580
+ this.logger.debug(`[${this.provider}-history-sync] Failed to import ${getProviderLabel(this.provider)} session ${acpSessionId}: ${formatErrorMessage(error2)}`);
131581
+ }
131582
+ }
131583
+ const catalog = await this.writeCatalogResult({
131584
+ localProjectId: args2.localProjectId,
131585
+ sessions: snapshot.sessions,
131586
+ existingByAcpSessionId: snapshot.existingByAcpSessionId
131587
+ });
131588
+ return {
131589
+ summary: summary2,
131590
+ catalog
131591
+ };
131592
+ }
131593
+ async listCatalogSnapshot(rootPath) {
131594
+ const catalog = await listHistorySessionsForLocalProject({
131595
+ provider: this.provider,
131596
+ rootPath,
131597
+ logger: this.logger
131598
+ });
131599
+ const sessionMetas = await listWorkspaceSessionMetas(this.manager);
131600
+ const existingByAcpSessionId = buildExistingHistorySessionIndex(sessionMetas, this.context.machineId, this.provider);
131601
+ return {
131602
+ sessions: catalog.sessions,
131603
+ existingByAcpSessionId
131604
+ };
131605
+ }
131606
+ async writeCatalogResult(args2) {
131607
+ const lastListedAt = Math.round(getServerNow());
131608
+ const sessions = args2.sessions.map((info) => buildCatalogItem(this.provider, info, args2.existingByAcpSessionId.get(info.sessionId))).sort(compareCatalogItems);
131609
+ const catalog = {
131610
+ listed: sessions.length,
131611
+ lastListedAt,
131612
+ sessions
131613
+ };
131614
+ const machineRoomId = getMachineRoomId(this.context.machineId);
131615
+ await withMachineCatalogWriteLock(machineRoomId, async () => {
131616
+ const current2 = await this.manager.repo.getDocMeta(machineRoomId);
131617
+ const rawExisting = current2?.meta?.localProjects;
131618
+ const existing = rawExisting && typeof rawExisting === "object" ? rawExisting : {};
131619
+ const previous = existing[args2.localProjectId];
131620
+ if (!previous) {
131621
+ return;
131622
+ }
131623
+ await this.manager.repo.upsertDocMeta(machineRoomId, {
131624
+ localProjects: {
131625
+ ...existing,
131626
+ [args2.localProjectId]: {
131627
+ ...previous,
131628
+ history: {
131629
+ ...previous.history ?? {},
131630
+ [this.provider]: {
131631
+ lastListedAt,
131632
+ sessions: Object.fromEntries(sessions.map((item) => [
131633
+ item.acpSessionId,
131634
+ item
131635
+ ]))
131636
+ }
131637
+ }
131638
+ }
131639
+ }
131640
+ });
131641
+ });
131642
+ return catalog;
131643
+ }
131644
+ async importNewSession(args2) {
131645
+ const sessionId = v4();
131646
+ const roomId = getSessionRoomId(sessionId);
131647
+ const nowMs = getServerNow();
131648
+ const lastMessageAt = resolveSourceUpdatedAtMs(args2.info, nowMs);
131649
+ const meta = {
131650
+ id: sessionId,
131651
+ machineId: this.context.machineId,
131652
+ createdAt: new Date(nowMs).toISOString(),
131653
+ userId: this.context.userId,
131654
+ status: SessionStatusFactory.idle(),
131655
+ isArchived: false,
131656
+ origin: this.provider,
131657
+ cliType: "builtin",
131658
+ agentType: this.provider,
131659
+ project: args2.project,
131660
+ title: resolveSessionTitle(args2.info, this.provider),
131661
+ lastMessageAt,
131662
+ externalHistory: buildMetadataOnlyExternalHistoryMeta({
131663
+ provider: this.provider,
131664
+ sourceAcpSessionId: args2.acpSessionId,
131665
+ sourceUpdatedAt: args2.info.updatedAt
131666
+ })
131667
+ };
131668
+ await this.manager.repo.upsertDocMeta(roomId, meta);
131669
+ return {
131670
+ sessionId,
131671
+ meta
131672
+ };
131673
+ }
131674
+ async refreshExistingSession(args2) {
131675
+ const externalHistory = args2.existing.meta.externalHistory;
131676
+ if (externalHistory?.provider !== this.provider) {
131677
+ return "skipped";
131678
+ }
131679
+ if (shouldSkipBySourceUpdatedAt(args2.info, externalHistory)) {
131680
+ return "skipped";
131681
+ }
131682
+ const replayNotifications = await loadHistorySessionReplay({
131683
+ provider: this.provider,
131684
+ rootPath: args2.rootPath,
131685
+ acpSessionId: args2.acpSessionId,
131686
+ logger: this.logger
131687
+ });
131688
+ const materialized = materializeReplay({
131689
+ provider: this.provider,
131690
+ acpSessionId: args2.acpSessionId,
131691
+ replayNotifications,
131692
+ userId: this.context.userId
131693
+ });
131694
+ const replayDecision = decideHistoryRefresh({
131695
+ externalHistory,
131696
+ replayDigest: materialized.replayDigest,
131697
+ turnHashes: materialized.turnHashes
131698
+ });
131699
+ if (replayDecision.reason === "digest_match") {
131700
+ await this.manager.repo.upsertDocMeta(getSessionRoomId(args2.existing.sessionId), {
131701
+ origin: this.provider,
131702
+ externalHistory: buildExternalHistoryMeta({
131703
+ provider: this.provider,
131704
+ sourceAcpSessionId: args2.acpSessionId,
131705
+ sourceUpdatedAt: args2.info.updatedAt,
131706
+ materialized
131707
+ })
131708
+ });
131709
+ return "skipped";
131710
+ }
131711
+ if (replayDecision.status === "conflicted") {
131712
+ await this.markConflict(args2.existing.sessionId, args2.info, materialized, replayDecision.reason);
131713
+ return "conflicted";
131714
+ }
131715
+ const sessionDoc = await this.manager.getOrCreateSessionDoc(args2.existing.sessionId);
131716
+ const currentHistory = await sessionDoc.getHistory();
131717
+ const appendDecision = decideHistoryRefresh({
131718
+ externalHistory,
131719
+ replayDigest: materialized.replayDigest,
131720
+ turnHashes: materialized.turnHashes,
131721
+ currentHistoryLength: currentHistory.length
131722
+ });
131723
+ if (appendDecision.status === "conflicted") {
131724
+ await this.markConflict(args2.existing.sessionId, args2.info, materialized, appendDecision.reason);
131725
+ const synced2 = await sessionDoc.waitUntilSynced();
131726
+ if (!synced2) {
131727
+ this.logger.debug(`[${this.provider}-history-sync] Conflict marker for ${args2.existing.sessionId} did not confirm sync before unload; clients may see the previous state until next sync.`);
131728
+ }
131729
+ await this.manager.cleanSessionDoc(args2.existing.sessionId, {
131730
+ preserveStatus: true
131731
+ });
131732
+ return "conflicted";
131733
+ }
131734
+ const suffix = materialized.history.slice(appendDecision.appendFromIndex);
131735
+ await sessionDoc.updateHistory((history) => [
131736
+ ...history,
131737
+ ...suffix
131738
+ ]);
131739
+ await this.manager.repo.upsertDocMeta(getSessionRoomId(args2.existing.sessionId), {
131740
+ origin: this.provider,
131741
+ lastMessageAt: resolveSourceUpdatedAtMs(args2.info, getServerNow()),
131742
+ externalHistory: buildExternalHistoryMeta({
131743
+ provider: this.provider,
131744
+ sourceAcpSessionId: args2.acpSessionId,
131745
+ sourceUpdatedAt: args2.info.updatedAt,
131746
+ materialized
131747
+ })
131748
+ });
131749
+ const synced = await sessionDoc.waitUntilSynced();
131750
+ if (!synced) {
131751
+ this.logger.debug(`[${this.provider}-history-sync] Appended history for ${args2.existing.sessionId} did not confirm sync before unload; other clients may see the previous state until next sync.`);
131752
+ }
131753
+ await this.manager.cleanSessionDoc(args2.existing.sessionId, {
131754
+ preserveStatus: true
131755
+ });
131756
+ return suffix.length > 0 ? "refreshed" : "skipped";
131757
+ }
131758
+ async markConflict(sessionId, info, materialized, reason) {
131759
+ await this.manager.repo.upsertDocMeta(getSessionRoomId(sessionId), {
131760
+ origin: this.provider,
131761
+ externalHistory: buildExternalHistoryMeta({
131762
+ provider: this.provider,
131763
+ sourceAcpSessionId: info.sessionId,
131764
+ sourceUpdatedAt: info.updatedAt,
131765
+ materialized,
131766
+ status: "sync_conflict",
131767
+ conflictReason: reason
131768
+ })
131769
+ });
131770
+ }
131771
+ }
131772
+ const HISTORY_REQUEST_TYPES = /* @__PURE__ */ new Set([
131773
+ "local-project/sync-history",
131774
+ "local-project/import-history"
131775
+ ]);
131776
+ function isHistoryRequestType(value) {
131777
+ return HISTORY_REQUEST_TYPES.has(value);
131778
+ }
131779
+ function precheckLocalProjectHistoryRequest(args2) {
131780
+ const { request, expectedMachineId, expectedWorkspaceId } = args2;
131781
+ if (request.machineId !== expectedMachineId) {
131782
+ return {
131783
+ ok: false,
131784
+ error: "machine_mismatch",
131785
+ message: `Machine mismatch: expected ${expectedMachineId}`
131786
+ };
131787
+ }
131788
+ if (!isHistoryRequestType(request.type)) {
131789
+ return {
131790
+ ok: false,
131791
+ error: "invalid_request",
131792
+ message: `Unsupported remote local project control request: ${request.type}`
131793
+ };
131794
+ }
131795
+ const historyRequest = request;
131796
+ const requesterUserId = historyRequest.requestedByUserId?.trim();
131797
+ if (!requesterUserId) {
131798
+ return {
131799
+ ok: false,
131800
+ error: "invalid_request",
131801
+ message: "Local project history requests require requestedByUserId"
131802
+ };
131803
+ }
131804
+ if (historyRequest.workspaceId !== expectedWorkspaceId) {
131805
+ return {
131806
+ ok: false,
131807
+ error: "workspace_not_found",
131808
+ message: `Workspace mismatch: expected ${expectedWorkspaceId}`
131809
+ };
131810
+ }
131811
+ return {
131812
+ ok: true,
131813
+ request: historyRequest,
131814
+ requesterUserId
131815
+ };
131816
+ }
130741
131817
  const SESSION_IMAGE_MIME_TYPE_BY_EXTENSION = {
130742
131818
  png: "image/png",
130743
131819
  jpg: "image/jpeg",
@@ -130904,6 +131980,7 @@ ${escapeHtmlScriptContent(VISUAL_ANNOTATION_INSPECTOR_BROWSER_SCRIPT)}
130904
131980
  requestedByUserId,
130905
131981
  reason
130906
131982
  }),
131983
+ dispatchLocalProjectControl: async (request) => await this.dispatchLocalProjectControlViaRpc(request),
130907
131984
  onFatalAuthFailure: (error2) => this.onFatalAuthFailure?.(error2)
130908
131985
  });
130909
131986
  } else {
@@ -131789,6 +132866,7 @@ ${escapeHtmlScriptContent(VISUAL_ANNOTATION_INSPECTOR_BROWSER_SCRIPT)}
131789
132866
  cliVersion: this.cliVersion,
131790
132867
  os: process.platform,
131791
132868
  rpcVersion: supportsStreamsRpc ? LORO_STREAMS_RPC_VERSION : void 0,
132869
+ supportsLocalProjectHistoryRpc: supportsStreamsRpc,
131792
132870
  supportRegistryAgentTypes: this.supportRegistryAgentTypes,
131793
132871
  sessions: [],
131794
132872
  needToArchiveSessions: {},
@@ -132626,6 +133704,7 @@ ${escapeHtmlScriptContent(VISUAL_ANNOTATION_INSPECTOR_BROWSER_SCRIPT)}
132626
133704
  cliVersion: this.cliVersion,
132627
133705
  os: process.platform,
132628
133706
  rpcVersion: supportsStreamsRpc ? LORO_STREAMS_RPC_VERSION : machineMeta?.rpcVersion,
133707
+ supportsLocalProjectHistoryRpc: supportsStreamsRpc,
132629
133708
  supportRegistryAgentTypes: this.supportRegistryAgentTypes,
132630
133709
  acpCapabilities: machineMeta?.acpCapabilities,
132631
133710
  sessions: machineMeta?.sessions ?? [],
@@ -133266,9 +134345,75 @@ ${escapeHtmlScriptContent(VISUAL_ANNOTATION_INSPECTOR_BROWSER_SCRIPT)}
133266
134345
  }
133267
134346
  await this.workspaceDocument.registerMachine(this.machineId, {
133268
134347
  ...existingMeta,
133269
- rpcVersion: LORO_STREAMS_RPC_VERSION
134348
+ rpcVersion: LORO_STREAMS_RPC_VERSION,
134349
+ supportsLocalProjectHistoryRpc: true
133270
134350
  });
133271
134351
  }
134352
+ toLocalProjectControlError(type2, error2, message, data) {
134353
+ return {
134354
+ ok: false,
134355
+ type: type2,
134356
+ error: error2,
134357
+ message,
134358
+ data
134359
+ };
134360
+ }
134361
+ async dispatchLocalProjectControlViaRpc(message) {
134362
+ const requestType = message.type;
134363
+ const precheck = precheckLocalProjectHistoryRequest({
134364
+ request: message,
134365
+ expectedMachineId: this.machineId,
134366
+ expectedWorkspaceId: this.workspaceId
134367
+ });
134368
+ if (!precheck.ok) {
134369
+ return this.toLocalProjectControlError(requestType, precheck.error, precheck.message);
134370
+ }
134371
+ const { requesterUserId, request } = precheck;
134372
+ try {
134373
+ const access = await canUseMachineForCliToken({
134374
+ token: this.token,
134375
+ workspaceId: this.workspaceId,
134376
+ machineId: this.machineId,
134377
+ requesterUserId,
134378
+ localProjectId: request.localProjectId
134379
+ });
134380
+ if (!access.allowed) {
134381
+ return this.toLocalProjectControlError(requestType, "access_denied", `Machine access denied: ${access.reason}`);
134382
+ }
134383
+ const rootPath = await resolveWorkspaceLocalProjectRootPath(this.workspaceDocument.repo, this.machineId, request.localProjectId);
134384
+ if (!rootPath) {
134385
+ return this.toLocalProjectControlError(requestType, "local_project_not_found", `Local project not found in workspace: ${request.localProjectId}`);
134386
+ }
134387
+ const service = new LocalProjectHistorySyncService(this.workspaceDocument, this.logger, {
134388
+ workspaceId: this.workspaceId,
134389
+ machineId: this.machineId,
134390
+ userId: this.userId
134391
+ }, request.provider);
134392
+ if (request.type === "local-project/sync-history") {
134393
+ const result2 = await service.syncLocalProject({
134394
+ localProjectId: request.localProjectId,
134395
+ rootPath
134396
+ });
134397
+ return {
134398
+ ok: true,
134399
+ type: "local-project/sync-history",
134400
+ result: result2
134401
+ };
134402
+ }
134403
+ const result = await service.importLocalProjectSessions({
134404
+ localProjectId: request.localProjectId,
134405
+ rootPath,
134406
+ acpSessionIds: request.acpSessionIds
134407
+ });
134408
+ return {
134409
+ ok: true,
134410
+ type: "local-project/import-history",
134411
+ result
134412
+ };
134413
+ } catch (error2) {
134414
+ return this.toLocalProjectControlError(requestType, "execution_failed", formatErrorMessage(error2));
134415
+ }
134416
+ }
133272
134417
  cancelPendingPermissionRequests() {
133273
134418
  this.permissionRequestStartTimes.clear();
133274
134419
  }
@@ -142115,6 +143260,18 @@ export PATH=${toSingleQuotedShellString(ghShimBinDir)}:"$PATH"
142115
143260
  }
142116
143261
  return typeof value.error === "string";
142117
143262
  }
143263
+ function isLocalProjectHistorySyncSummary(value) {
143264
+ return isObjectRecord(value) && typeof value.listed === "number" && Number.isInteger(value.listed) && value.listed >= 0 && typeof value.imported === "number" && Number.isInteger(value.imported) && value.imported >= 0 && typeof value.refreshed === "number" && Number.isInteger(value.refreshed) && value.refreshed >= 0 && typeof value.skipped === "number" && Number.isInteger(value.skipped) && value.skipped >= 0 && typeof value.conflicted === "number" && Number.isInteger(value.conflicted) && value.conflicted >= 0 && typeof value.failed === "number" && Number.isInteger(value.failed) && value.failed >= 0 && Array.isArray(value.failures) && value.failures.every((failure) => isObjectRecord(failure) && typeof failure.acpSessionId === "string" && typeof failure.message === "string");
143265
+ }
143266
+ function isLocalProjectHistoryCatalogItem(value) {
143267
+ return isObjectRecord(value) && typeof value.acpSessionId === "string" && typeof value.title === "string" && (typeof value.updatedAt === "undefined" || typeof value.updatedAt === "string") && (typeof value.importedSessionId === "undefined" || typeof value.importedSessionId === "string") && (typeof value.status === "undefined" || value.status === "available" || value.status === "imported" || value.status === "sync_conflict");
143268
+ }
143269
+ function isLocalProjectHistoryCatalogResult(value) {
143270
+ return isObjectRecord(value) && typeof value.listed === "number" && Number.isInteger(value.listed) && value.listed >= 0 && typeof value.lastListedAt === "number" && Number.isInteger(value.lastListedAt) && value.lastListedAt >= 0 && Array.isArray(value.sessions) && value.sessions.every(isLocalProjectHistoryCatalogItem);
143271
+ }
143272
+ function isLocalProjectHistoryImportResult(value) {
143273
+ return isObjectRecord(value) && isLocalProjectHistorySyncSummary(value.summary) && isLocalProjectHistoryCatalogResult(value.catalog);
143274
+ }
142118
143275
  function isWorkspaceIds(value) {
142119
143276
  return isStringArray(value);
142120
143277
  }
@@ -142146,6 +143303,12 @@ export PATH=${toSingleQuotedShellString(ghShimBinDir)}:"$PATH"
142146
143303
  if (value.type === "local-project/checkout-branch") {
142147
143304
  return isLocalProjectCheckoutBranchResult(value.result);
142148
143305
  }
143306
+ if (value.type === "local-project/sync-history") {
143307
+ return isLocalProjectHistoryCatalogResult(value.result);
143308
+ }
143309
+ if (value.type === "local-project/import-history") {
143310
+ return isLocalProjectHistoryImportResult(value.result);
143311
+ }
142149
143312
  return false;
142150
143313
  }
142151
143314
  const SESSION_CONTROL_PATH$2 = "/session-control";
@@ -142160,6 +143323,8 @@ export PATH=${toSingleQuotedShellString(ghShimBinDir)}:"$PATH"
142160
143323
  "local-project/list-files",
142161
143324
  "local-project/read-file",
142162
143325
  "local-project/checkout-branch",
143326
+ "local-project/sync-history",
143327
+ "local-project/import-history",
142163
143328
  "worktree/list-files",
142164
143329
  "worktree/read-file"
142165
143330
  ];
@@ -143437,6 +144602,7 @@ export PATH=${toSingleQuotedShellString(ghShimBinDir)}:"$PATH"
143437
144602
  localProjects: {
143438
144603
  ...existing,
143439
144604
  [entry2.localProjectId]: {
144605
+ ...previous ?? {},
143440
144606
  id: entry2.localProjectId,
143441
144607
  name: entry2.name,
143442
144608
  rootPath: entry2.rootPath,
@@ -143618,6 +144784,43 @@ export PATH=${toSingleQuotedShellString(ghShimBinDir)}:"$PATH"
143618
144784
  result: this.localProjectControlService.checkoutProjectBranch(rootPath, message.branchName)
143619
144785
  };
143620
144786
  }
144787
+ if (message.type === "local-project/sync-history") {
144788
+ const runtime = await this.resolveWorkspaceRuntime(message.workspaceId);
144789
+ const rootPath = await this.resolveWorkspaceProjectRootPath(runtime, message.localProjectId);
144790
+ const service = new LocalProjectHistorySyncService(runtime.lody.documentManager, this.logger, {
144791
+ workspaceId: message.workspaceId,
144792
+ machineId: this.machineId,
144793
+ userId: this.userId
144794
+ }, message.provider);
144795
+ const result = await service.syncLocalProject({
144796
+ localProjectId: message.localProjectId,
144797
+ rootPath
144798
+ });
144799
+ return {
144800
+ ok: true,
144801
+ type: "local-project/sync-history",
144802
+ result
144803
+ };
144804
+ }
144805
+ if (message.type === "local-project/import-history") {
144806
+ const runtime = await this.resolveWorkspaceRuntime(message.workspaceId);
144807
+ const rootPath = await this.resolveWorkspaceProjectRootPath(runtime, message.localProjectId);
144808
+ const service = new LocalProjectHistorySyncService(runtime.lody.documentManager, this.logger, {
144809
+ workspaceId: message.workspaceId,
144810
+ machineId: this.machineId,
144811
+ userId: this.userId
144812
+ }, message.provider);
144813
+ const result = await service.importLocalProjectSessions({
144814
+ localProjectId: message.localProjectId,
144815
+ rootPath,
144816
+ acpSessionIds: message.acpSessionIds
144817
+ });
144818
+ return {
144819
+ ok: true,
144820
+ type: "local-project/import-history",
144821
+ result
144822
+ };
144823
+ }
143621
144824
  if (message.type === "worktree/list-files") {
143622
144825
  return {
143623
144826
  ok: true,
@@ -175829,6 +177032,7 @@ ${result.stderr}`;
175829
177032
  this.lastExitCode = result.code;
175830
177033
  this.lastExitAtMs = Date.now();
175831
177034
  this.publishState();
177035
+ if (!this.triggered) return;
175832
177036
  if (this.restartPending) return;
175833
177037
  if (isAlreadyRunningOutcome(result)) {
175834
177038
  if (this.alreadyRunningIsFatal) {