lody 0.50.2 → 0.52.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 +1416 -186
  2. package/package.json +6 -6
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.52.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";
@@ -36864,8 +36865,8 @@ Mongoose Error Code: ${error2.code}` : ""}`
36864
36865
  "node": ">=18.0.0"
36865
36866
  };
36866
36867
  const optionalDependencies = {
36867
- "acp-extension-claude": "0.31.1",
36868
- "acp-extension-codex": "0.14.2"
36868
+ "acp-extension-claude": "0.34.1",
36869
+ "acp-extension-codex": "0.14.3"
36869
36870
  };
36870
36871
  const devDependencies = {
36871
36872
  "@agentclientprotocol/sdk": "catalog:",
@@ -37075,7 +37076,7 @@ Mongoose Error Code: ${error2.code}` : ""}`
37075
37076
  const runtimeEnv = getRuntimeEnv();
37076
37077
  const environment$1 = "production";
37077
37078
  const dsn = "https://080f9de535ff335a1a0440d0e385f796@o4510491299086336.ingest.us.sentry.io/4510559045681152";
37078
- const postHogHost = process.env.LODY_POSTHOG_HOST ?? "https://us.i.posthog.com";
37079
+ const postHogHost = process.env.LODY_POSTHOG_HOST ?? "https://m.lody.ai";
37079
37080
  const postHogKey = process.env.LODY_POSTHOG_KEY ?? "phc_LFS5i5WIwg4irAhrG5oJR04iYPhReVZ3DdFZOKqCkjG";
37080
37081
  const tracesSampleRate = Number(process.env.SENTRY_TRACES_SAMPLE_RATE) || 0.2;
37081
37082
  const profilesSampleRate = Number(process.env.SENTRY_PROFILES_SAMPLE_RATE) || 0.1;
@@ -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) {
@@ -77780,6 +77781,9 @@ Task description:
77780
77781
  }
77781
77782
  return void 0;
77782
77783
  };
77784
+ function getLocalProjectHistoryProviderKey(provider2) {
77785
+ return `${provider2.cliType}:${provider2.agentType}`;
77786
+ }
77783
77787
  const DEFAULT_BASE_BRANCH = "main";
77784
77788
  function getTrimmedBranch(value) {
77785
77789
  if (typeof value !== "string") {
@@ -78661,6 +78665,27 @@ Task description:
78661
78665
  localProjectId: LocalProjectIdSchema,
78662
78666
  branchName: string$2()
78663
78667
  }).strict();
78668
+ const LocalProjectHistoryProviderSchema = object$1({
78669
+ cliType: AgentConfigCliTypeSchema,
78670
+ agentType: string$2().trim().min(1)
78671
+ }).strict();
78672
+ const LocalProjectSyncHistoryRequestSchema = object$1({
78673
+ type: literal("local-project/sync-history"),
78674
+ machineId: MachineIdSchema,
78675
+ workspaceId: WorkspaceIdSchema,
78676
+ localProjectId: LocalProjectIdSchema,
78677
+ provider: LocalProjectHistoryProviderSchema,
78678
+ requestedByUserId: string$2().trim().min(1).optional()
78679
+ }).strict();
78680
+ const LocalProjectImportHistoryRequestSchema = object$1({
78681
+ type: literal("local-project/import-history"),
78682
+ machineId: MachineIdSchema,
78683
+ workspaceId: WorkspaceIdSchema,
78684
+ localProjectId: LocalProjectIdSchema,
78685
+ provider: LocalProjectHistoryProviderSchema,
78686
+ acpSessionIds: array$3(ACPSessionIdSchema),
78687
+ requestedByUserId: string$2().trim().min(1).optional()
78688
+ }).strict();
78664
78689
  const WorktreeListFilesRequestSchema = object$1({
78665
78690
  type: literal("worktree/list-files"),
78666
78691
  machineId: MachineIdSchema,
@@ -78684,6 +78709,8 @@ Task description:
78684
78709
  LocalProjectListFilesRequestSchema,
78685
78710
  LocalProjectReadFileRequestSchema,
78686
78711
  LocalProjectCheckoutBranchRequestSchema,
78712
+ LocalProjectSyncHistoryRequestSchema,
78713
+ LocalProjectImportHistoryRequestSchema,
78687
78714
  WorktreeListFilesRequestSchema,
78688
78715
  WorktreeReadFileRequestSchema
78689
78716
  ]);
@@ -78725,12 +78752,45 @@ Task description:
78725
78752
  error: string$2()
78726
78753
  }).strict()
78727
78754
  ]);
78755
+ const LocalProjectHistorySyncSummarySchema = object$1({
78756
+ listed: number$3().int().nonnegative(),
78757
+ imported: number$3().int().nonnegative(),
78758
+ refreshed: number$3().int().nonnegative(),
78759
+ skipped: number$3().int().nonnegative(),
78760
+ conflicted: number$3().int().nonnegative(),
78761
+ failed: number$3().int().nonnegative(),
78762
+ failures: array$3(object$1({
78763
+ acpSessionId: string$2(),
78764
+ message: string$2()
78765
+ }).strict())
78766
+ }).strict();
78767
+ const LocalProjectHistoryCatalogItemSchema = object$1({
78768
+ acpSessionId: string$2(),
78769
+ title: string$2(),
78770
+ updatedAt: string$2().optional(),
78771
+ importedSessionId: string$2().optional(),
78772
+ status: _enum$1([
78773
+ "available",
78774
+ "imported",
78775
+ "sync_conflict"
78776
+ ]).optional()
78777
+ }).strict();
78778
+ const LocalProjectHistoryCatalogResultSchema = object$1({
78779
+ listed: number$3().int().nonnegative(),
78780
+ lastListedAt: number$3().int().nonnegative(),
78781
+ sessions: array$3(LocalProjectHistoryCatalogItemSchema)
78782
+ }).strict();
78783
+ const LocalProjectHistoryImportResultSchema = object$1({
78784
+ summary: LocalProjectHistorySyncSummarySchema,
78785
+ catalog: LocalProjectHistoryCatalogResultSchema
78786
+ }).strict();
78728
78787
  const LocalProjectControlErrorCodeSchema = _enum$1([
78729
78788
  "invalid_request",
78730
78789
  "machine_mismatch",
78731
78790
  "workspace_required",
78732
78791
  "workspace_not_found",
78733
78792
  "daemon_unavailable",
78793
+ "access_denied",
78734
78794
  "local_project_not_found",
78735
78795
  "path_invalid",
78736
78796
  "execution_failed",
@@ -78746,6 +78806,8 @@ Task description:
78746
78806
  "local-project/list-files",
78747
78807
  "local-project/read-file",
78748
78808
  "local-project/checkout-branch",
78809
+ "local-project/sync-history",
78810
+ "local-project/import-history",
78749
78811
  "worktree/list-files",
78750
78812
  "worktree/read-file"
78751
78813
  ]),
@@ -78809,6 +78871,16 @@ Task description:
78809
78871
  type: literal("local-project/checkout-branch"),
78810
78872
  result: LocalProjectCheckoutBranchResultSchema
78811
78873
  }).strict(),
78874
+ object$1({
78875
+ ok: literal(true),
78876
+ type: literal("local-project/sync-history"),
78877
+ result: LocalProjectHistoryCatalogResultSchema
78878
+ }).strict(),
78879
+ object$1({
78880
+ ok: literal(true),
78881
+ type: literal("local-project/import-history"),
78882
+ result: LocalProjectHistoryImportResultSchema
78883
+ }).strict(),
78812
78884
  object$1({
78813
78885
  ok: literal(true),
78814
78886
  type: literal("worktree/list-files"),
@@ -79127,7 +79199,7 @@ Task description:
79127
79199
  "claude",
79128
79200
  "codex"
79129
79201
  ]);
79130
- function isRecord$4(value) {
79202
+ function isRecord$5(value) {
79131
79203
  return typeof value === "object" && value !== null;
79132
79204
  }
79133
79205
  function getTrimmedString(value) {
@@ -79144,7 +79216,7 @@ Task description:
79144
79216
  return value === "builtin" || value === "registry";
79145
79217
  }
79146
79218
  function normalizeLegacyProjectRef(value) {
79147
- if (!isRecord$4(value)) {
79219
+ if (!isRecord$5(value)) {
79148
79220
  return value;
79149
79221
  }
79150
79222
  const kind = value.kind;
@@ -79160,13 +79232,13 @@ Task description:
79160
79232
  normalized.branch = existingBranch;
79161
79233
  } else {
79162
79234
  const legacyBranchFromString = getTrimmedString(legacyProject);
79163
- const legacyBranchFromObject = isRecord$4(legacyProject) ? getTrimmedString(legacyProject.branch) ?? getTrimmedString(legacyProject.project) : void 0;
79235
+ const legacyBranchFromObject = isRecord$5(legacyProject) ? getTrimmedString(legacyProject.branch) ?? getTrimmedString(legacyProject.project) : void 0;
79164
79236
  const resolvedBranch = legacyBranchFromString ?? legacyBranchFromObject;
79165
79237
  if (resolvedBranch) {
79166
79238
  normalized.branch = resolvedBranch;
79167
79239
  }
79168
79240
  }
79169
- if (isRecord$4(legacyProject)) {
79241
+ if (isRecord$5(legacyProject)) {
79170
79242
  if (kind === "github" && !getTrimmedString(normalized.repoFullName)) {
79171
79243
  const repoFullName = getTrimmedString(legacyProject.repoFullName);
79172
79244
  if (repoFullName) {
@@ -79203,7 +79275,7 @@ Task description:
79203
79275
  };
79204
79276
  normalized.project = normalizeLegacyProjectRef(normalized.project);
79205
79277
  const currentProject = normalized.project;
79206
- const projectRecord = isRecord$4(currentProject) ? currentProject : void 0;
79278
+ const projectRecord = isRecord$5(currentProject) ? currentProject : void 0;
79207
79279
  const explicitBranch = getTrimmedString(normalized.branch) ?? getTrimmedString(currentProject) ?? (projectRecord ? getTrimmedString(projectRecord.branch) ?? getTrimmedString(projectRecord.project) : void 0);
79208
79280
  const repoFullName = (projectRecord ? getTrimmedString(projectRecord.repoFullName) : void 0) ?? getTrimmedString(normalized.repoFullName) ?? getTrimmedString(normalized.githubRepo);
79209
79281
  const localProjectId = (projectRecord ? getTrimmedString(projectRecord.localProjectId) : void 0) ?? getTrimmedString(normalized.localProjectId);
@@ -79246,7 +79318,7 @@ Task description:
79246
79318
  return normalized;
79247
79319
  }
79248
79320
  function normalizeLegacyAcpSessionConfig(value) {
79249
- if (!isRecord$4(value)) {
79321
+ if (!isRecord$5(value)) {
79250
79322
  return value;
79251
79323
  }
79252
79324
  const normalized = {
@@ -79281,13 +79353,13 @@ Task description:
79281
79353
  return normalized;
79282
79354
  }
79283
79355
  function normalizeLegacySessionMessage(parsed) {
79284
- if (!isRecord$4(parsed)) {
79356
+ if (!isRecord$5(parsed)) {
79285
79357
  return parsed;
79286
79358
  }
79287
79359
  const messageType = parsed.type;
79288
79360
  if (messageType === "session/create" || messageType === "session/chat") {
79289
79361
  const normalized = normalizeLegacySessionProject(parsed);
79290
- if (!isRecord$4(normalized)) {
79362
+ if (!isRecord$5(normalized)) {
79291
79363
  return normalized;
79292
79364
  }
79293
79365
  normalized.acpSessionConfig = normalizeLegacyAcpSessionConfig(normalized.acpSessionConfig);
@@ -81512,7 +81584,7 @@ Task description:
81512
81584
  "read"
81513
81585
  ]);
81514
81586
  const MAX_STORED_TERMINAL_OUTPUT_CHARS = 1024;
81515
- const defaultCreateId = () => {
81587
+ const defaultCreateId$1 = () => {
81516
81588
  const maybeCrypto = globalThis.crypto;
81517
81589
  if (typeof maybeCrypto?.randomUUID === "function") {
81518
81590
  return maybeCrypto.randomUUID();
@@ -82193,7 +82265,7 @@ Task description:
82193
82265
  constructor(history, options, model) {
82194
82266
  this.model = model;
82195
82267
  this.history = history;
82196
- this.createId = options.createId ?? defaultCreateId;
82268
+ this.createId = options.createId ?? defaultCreateId$1;
82197
82269
  this.now = options.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
82198
82270
  this.parsedItemsByEntryIndex = Array.from({
82199
82271
  length: history.length
@@ -82445,28 +82517,149 @@ Task description:
82445
82517
  const applyNotificationOnHistory = (history, notifications, model, options = {}) => {
82446
82518
  return new NotificationOnHistoryApplier(history, options, model).apply(notifications);
82447
82519
  };
82448
- const isRecord$3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
82520
+ const defaultNow = () => (/* @__PURE__ */ new Date()).toISOString();
82521
+ const defaultCreateId = () => {
82522
+ const maybeCrypto = globalThis.crypto;
82523
+ if (typeof maybeCrypto?.randomUUID === "function") {
82524
+ return maybeCrypto.randomUUID();
82525
+ }
82526
+ return `history-replay-${Date.now()}-${Math.random().toString(16).slice(2)}`;
82527
+ };
82528
+ function textFromContentBlock(content) {
82529
+ if (!content || content.type !== "text") {
82530
+ return null;
82531
+ }
82532
+ return content.text;
82533
+ }
82534
+ function appendUserText(entry2, text) {
82535
+ const items2 = Array.isArray(entry2.items) ? [
82536
+ ...entry2.items
82537
+ ] : [];
82538
+ const last2 = items2[items2.length - 1];
82539
+ if (last2?.type === "text") {
82540
+ items2[items2.length - 1] = {
82541
+ ...last2,
82542
+ text: `${last2.text}${text}`
82543
+ };
82544
+ } else {
82545
+ items2.push({
82546
+ type: "text",
82547
+ text
82548
+ });
82549
+ }
82550
+ return {
82551
+ ...entry2,
82552
+ items: items2,
82553
+ inputConfig: entry2.inputConfig ? {
82554
+ ...entry2.inputConfig,
82555
+ prompt: `${entry2.inputConfig.prompt ?? ""}${text}`
82556
+ } : entry2.inputConfig
82557
+ };
82558
+ }
82559
+ function createUserEntry(args2) {
82560
+ const inputConfig = {
82561
+ prompt: args2.text,
82562
+ cliType: args2.provider.cliType,
82563
+ agentType: args2.provider.agentType,
82564
+ ...{}
82565
+ };
82566
+ return {
82567
+ id: args2.id,
82568
+ role: "user",
82569
+ items: [
82570
+ {
82571
+ type: "text",
82572
+ text: args2.text
82573
+ }
82574
+ ],
82575
+ timestamp: args2.timestamp,
82576
+ status: args2.mode === "resumable" ? "seen" : "handled",
82577
+ read: true,
82578
+ userId: args2.userId,
82579
+ finished: true,
82580
+ fileDiff: [],
82581
+ inputConfig
82582
+ };
82583
+ }
82584
+ function buildHistoryReplayImport(notifications, options) {
82585
+ const now2 = options.now ?? defaultNow;
82586
+ const createId = options.createId ?? defaultCreateId;
82587
+ const mode2 = options.mode;
82588
+ const provider2 = options.provider;
82589
+ let history = [];
82590
+ let lastWasUserChunk = false;
82591
+ let droppedNotifications = 0;
82592
+ for (const notification of notifications) {
82593
+ if (notification.update.sessionUpdate === "user_message_chunk") {
82594
+ const text = textFromContentBlock(notification.update.content);
82595
+ if (text === null) {
82596
+ droppedNotifications += 1;
82597
+ lastWasUserChunk = false;
82598
+ continue;
82599
+ }
82600
+ const lastIndex = history.length - 1;
82601
+ const last2 = lastIndex >= 0 ? history[lastIndex] : void 0;
82602
+ if (lastWasUserChunk && last2?.role === "user") {
82603
+ history[lastIndex] = appendUserText(last2, text);
82604
+ } else {
82605
+ history.push(createUserEntry({
82606
+ id: createId(),
82607
+ text,
82608
+ timestamp: now2(),
82609
+ userId: options.userId,
82610
+ provider: provider2,
82611
+ mode: mode2
82612
+ }));
82613
+ }
82614
+ lastWasUserChunk = true;
82615
+ continue;
82616
+ }
82617
+ const beforeLength = history.length;
82618
+ const beforeSnapshot = JSON.stringify(history);
82619
+ history = applyNotificationOnHistory(history, [
82620
+ notification
82621
+ ], void 0, {
82622
+ createId,
82623
+ now: now2
82624
+ });
82625
+ if (history.length === beforeLength && JSON.stringify(history) === beforeSnapshot) {
82626
+ const updateType = notification.update.sessionUpdate;
82627
+ if (updateType !== "session_info_update" && updateType !== "current_mode_update" && updateType !== "config_option_update" && updateType !== "usage_update" && updateType !== "available_commands_update") {
82628
+ droppedNotifications += 1;
82629
+ }
82630
+ }
82631
+ lastWasUserChunk = false;
82632
+ }
82633
+ return {
82634
+ history: history.map((entry2) => entry2.role === "assistant" ? {
82635
+ ...entry2,
82636
+ finished: entry2.finished ?? true
82637
+ } : entry2),
82638
+ droppedNotifications
82639
+ };
82640
+ }
82641
+ const isRecord$4 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
82449
82642
  const getClaudeCodeMeta = (meta) => {
82450
- if (!isRecord$3(meta)) return null;
82643
+ if (!isRecord$4(meta)) return null;
82451
82644
  const claudeCode = meta.claudeCode;
82452
- return isRecord$3(claudeCode) ? claudeCode : null;
82645
+ return isRecord$4(claudeCode) ? claudeCode : null;
82453
82646
  };
82454
82647
  function parseAskUserQuestionPermissionMeta(meta) {
82455
82648
  const claudeCode = getClaudeCodeMeta(meta);
82456
82649
  if (!claudeCode) return null;
82457
82650
  const raw = claudeCode.askUserQuestion;
82458
- if (!isRecord$3(raw)) return null;
82651
+ if (!isRecord$4(raw)) return null;
82459
82652
  const rawQuestions = raw.questions;
82460
82653
  if (!Array.isArray(rawQuestions) || rawQuestions.length === 0) return null;
82461
82654
  const questions = [];
82462
82655
  for (const rawQuestion of rawQuestions) {
82463
- if (!isRecord$3(rawQuestion)) return null;
82656
+ if (!isRecord$4(rawQuestion)) return null;
82464
82657
  if (typeof rawQuestion.question !== "string") return null;
82465
82658
  if (typeof rawQuestion.header !== "string") return null;
82466
82659
  if (!Array.isArray(rawQuestion.options)) return null;
82467
82660
  const options = [];
82468
82661
  for (const rawOption of rawQuestion.options) {
82469
- if (!isRecord$3(rawOption)) return null;
82662
+ if (!isRecord$4(rawOption)) return null;
82470
82663
  if (typeof rawOption.label !== "string") return null;
82471
82664
  options.push({
82472
82665
  label: rawOption.label,
@@ -85171,7 +85364,7 @@ ${tailedOutput}` : null;
85171
85364
  ];
85172
85365
  const buildPreviewTunnelRefreshPath = (tunnelId) => `${PREVIEW_TUNNELS_API_PATH}/${encodeURIComponent(tunnelId)}/refresh`;
85173
85366
  const buildPreviewTunnelRevokePath = (tunnelId) => `${PREVIEW_TUNNELS_API_PATH}/${encodeURIComponent(tunnelId)}/revoke`;
85174
- const isRecord$2 = (value) => typeof value === "object" && value !== null;
85367
+ const isRecord$3 = (value) => typeof value === "object" && value !== null;
85175
85368
  const isString$2 = (value) => typeof value === "string";
85176
85369
  const isOptionalNumber = (value) => value === void 0 || typeof value === "number";
85177
85370
  const isOptionalBoolean = (value) => value === void 0 || typeof value === "boolean";
@@ -85179,11 +85372,11 @@ ${tailedOutput}` : null;
85179
85372
  const isStringArray$1 = (value) => Array.isArray(value) && value.every((item) => typeof item === "string");
85180
85373
  const isHeaderEntries = (value) => Array.isArray(value) && value.every((entry2) => Array.isArray(entry2) && entry2.length === 2 && typeof entry2[0] === "string" && typeof entry2[1] === "string");
85181
85374
  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);
85375
+ const isPreviewResourceLimits = (value) => isRecord$3(value) && isPositiveInteger(value.maxRequestBodyBytes) && isPositiveInteger(value.maxResponseBodyBytes) && isPositiveInteger(value.maxRequestDurationMs);
85183
85376
  const parseJsonRecord = (raw) => {
85184
85377
  try {
85185
85378
  const parsed = JSON.parse(raw);
85186
- return isRecord$2(parsed) ? parsed : null;
85379
+ return isRecord$3(parsed) ? parsed : null;
85187
85380
  } catch {
85188
85381
  return null;
85189
85382
  }
@@ -85222,8 +85415,8 @@ ${tailedOutput}` : null;
85222
85415
  const parsed = parseJsonRecord(raw);
85223
85416
  return parsed && isPreviewTunnelServerMessage(parsed) ? parsed : null;
85224
85417
  };
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";
85418
+ 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));
85419
+ const isPreviewTunnelRefreshResponse = (value) => isRecord$3(value) && isString$2(value.websocketUrl) && isString$2(value.sessionToken) && typeof value.expiresAt === "number";
85227
85420
  const LOCAL_PROBE_PORT$1 = 17789;
85228
85421
  const LOCAL_SESSION_CONTROL_PORT = 17790;
85229
85422
  const IMAGE_UPLOAD_PATH = "/image-upload";
@@ -90150,18 +90343,18 @@ ${val.stack}`;
90150
90343
  }
90151
90344
  return next;
90152
90345
  }
90153
- function isRecord$1(value) {
90346
+ function isRecord$2(value) {
90154
90347
  return typeof value === "object" && value !== null;
90155
90348
  }
90156
90349
  function normalizeRecoveryReport(raw) {
90157
- if (!isRecord$1(raw) || !Array.isArray(raw.skipped)) {
90350
+ if (!isRecord$2(raw) || !Array.isArray(raw.skipped)) {
90158
90351
  return {
90159
90352
  skipped: []
90160
90353
  };
90161
90354
  }
90162
90355
  return {
90163
90356
  skipped: raw.skipped.flatMap((entry2) => {
90164
- if (!isRecord$1(entry2)) {
90357
+ if (!isRecord$2(entry2)) {
90165
90358
  return [];
90166
90359
  }
90167
90360
  const key2 = Array.isArray(entry2.key) ? cloneJson(entry2.key) : void 0;
@@ -99013,7 +99206,7 @@ stream:${scope2.streamId}`;
99013
99206
  }
99014
99207
  return parsed;
99015
99208
  };
99016
- const withTimeout$2 = async (promise, timeoutMs, message) => {
99209
+ const withTimeout$3 = async (promise, timeoutMs, message) => {
99017
99210
  if (timeoutMs <= 0) {
99018
99211
  return promise;
99019
99212
  }
@@ -105853,7 +106046,7 @@ stream:${scope2.streamId}`;
105853
106046
  const timeoutMs = options.timeoutMs ?? readTimeoutEnv("LODY_LORO_WAIT_CODE_SESSION_SYNC_TIMEOUT_MS", readTimeoutEnv("LODY_LORO_WAIT_DOC_SYNC_TIMEOUT_MS", 4e3));
105854
106047
  const timeoutMessage = `Timeout waiting for code session pending writes (session=${this.sessionId})`;
105855
106048
  try {
105856
- await withTimeout$2(this.subscription.waitUntilSynced(), timeoutMs, timeoutMessage);
106049
+ await withTimeout$3(this.subscription.waitUntilSynced(), timeoutMs, timeoutMessage);
105857
106050
  return true;
105858
106051
  } catch {
105859
106052
  return false;
@@ -115733,7 +115926,7 @@ ${this.stack.split("\n").slice(1).join("\n")}` : this.toString();
115733
115926
  this.logger.debug(`[${this.workspaceId}] Triggering Loro streams reconnect (reason=${reason}, transport=${this.transportStatus}, metaRoom=${this.metaRoomStatus ?? "unknown"})`);
115734
115927
  }
115735
115928
  await this.ensureMetaRoomJoined(reason);
115736
- await withTimeout$2(this.repo.reconnect({
115929
+ await withTimeout$3(this.repo.reconnect({
115737
115930
  resetBackoff: true,
115738
115931
  timeout: timeoutMs
115739
115932
  }), timeoutMs, `Timeout waiting for Loro streams reconnect (workspace=${this.workspaceId})`);
@@ -115755,7 +115948,7 @@ ${this.stack.split("\n").slice(1).join("\n")}` : this.toString();
115755
115948
  const joinMetaTimeoutMs = readTimeoutEnv("LODY_LORO_JOIN_META_TIMEOUT_MS", 3e4);
115756
115949
  const startedAt = Date.now();
115757
115950
  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})`);
115951
+ const metaSub = await withTimeout$3(this.repo.joinMetaRoom(), joinMetaTimeoutMs, `Timeout waiting for repo.joinMetaRoom during reconnect (workspace=${this.workspaceId})`);
115759
115952
  this.attachMetaRoomStatusLogger(metaSub);
115760
115953
  this.logger.debug(`[${this.workspaceId}] Meta room join returned in ${Date.now() - startedAt}ms (reason=${reason})`);
115761
115954
  }
@@ -115767,7 +115960,7 @@ ${this.stack.split("\n").slice(1).join("\n")}` : this.toString();
115767
115960
  const startedAt = Date.now();
115768
115961
  try {
115769
115962
  const syncPromise = this.initialMetaSyncCompleted ? metaSub.waitUntilSynced() : metaSub.firstSyncedWithRemote;
115770
- await withTimeout$2(syncPromise, syncMetaTimeoutMs, `Timeout waiting for Loro meta room sync (workspace=${this.workspaceId})`);
115963
+ await withTimeout$3(syncPromise, syncMetaTimeoutMs, `Timeout waiting for Loro meta room sync (workspace=${this.workspaceId})`);
115771
115964
  if (this.metaSub !== metaSub || this.isCleanedUp) {
115772
115965
  return;
115773
115966
  }
@@ -116689,7 +116882,7 @@ ${this.stack.split("\n").slice(1).join("\n")}` : this.toString();
116689
116882
  });
116690
116883
  try {
116691
116884
  const createRepoStartMs = Date.now();
116692
- repo = await withTimeout$2(LoroRepo.create({
116885
+ repo = await withTimeout$3(LoroRepo.create({
116693
116886
  storageAdapter: new FileSystemStorageAdaptor({
116694
116887
  baseDir: storageBaseDir
116695
116888
  }),
@@ -116707,7 +116900,7 @@ ${this.stack.split("\n").slice(1).join("\n")}` : this.toString();
116707
116900
  }
116708
116901
  try {
116709
116902
  const joinMetaStartMs = Date.now();
116710
- metaSub = await withTimeout$2(repo.joinMetaRoom(), joinMetaTimeoutMs, "Timeout waiting for repo.joinMetaRoom");
116903
+ metaSub = await withTimeout$3(repo.joinMetaRoom(), joinMetaTimeoutMs, "Timeout waiting for repo.joinMetaRoom");
116711
116904
  logger2.debug(`[${workspaceId}] Meta room join returned in ${Date.now() - joinMetaStartMs}ms`);
116712
116905
  } catch (error2) {
116713
116906
  logger2.debug(`[${workspaceId}] Failed to join Loro meta room; continuing without initial meta join: ${formatErrorMessage(error2)}`);
@@ -116724,7 +116917,7 @@ ${this.stack.split("\n").slice(1).join("\n")}` : this.toString();
116724
116917
  });
116725
116918
  try {
116726
116919
  const syncStartMs = Date.now();
116727
- await withTimeout$2(metaSub.firstSyncedWithRemote, syncMetaTimeoutMs, initialMetaSyncTimeoutMessage);
116920
+ await withTimeout$3(metaSub.firstSyncedWithRemote, syncMetaTimeoutMs, initialMetaSyncTimeoutMessage);
116728
116921
  initialMetaSyncCompleted = true;
116729
116922
  logger2.debug(`[${workspaceId}] Meta room synced in ${Date.now() - syncStartMs}ms`);
116730
116923
  } catch (error2) {
@@ -116772,7 +116965,7 @@ ${this.stack.split("\n").slice(1).join("\n")}` : this.toString();
116772
116965
  }
116773
116966
  const timeoutMessage = `Timeout waiting for initial meta sync (workspace=${this.workspaceId})`;
116774
116967
  try {
116775
- return await withTimeout$2(this.initialMetaSyncPromise, timeoutMs, timeoutMessage);
116968
+ return await withTimeout$3(this.initialMetaSyncPromise, timeoutMs, timeoutMessage);
116776
116969
  } catch (error2) {
116777
116970
  if (error2 instanceof Error && error2.message === timeoutMessage) {
116778
116971
  return false;
@@ -116795,7 +116988,7 @@ ${this.stack.split("\n").slice(1).join("\n")}` : this.toString();
116795
116988
  const destroyTimeoutMs = readTimeoutEnv("LODY_LORO_DESTROY_TIMEOUT_MS", 1e3);
116796
116989
  const timeoutMessage = `Timeout waiting for repo.destroy (workspace=${this.workspaceId})`;
116797
116990
  try {
116798
- await withTimeout$2(repoDestroyPromise, destroyTimeoutMs, timeoutMessage);
116991
+ await withTimeout$3(repoDestroyPromise, destroyTimeoutMs, timeoutMessage);
116799
116992
  } catch (error2) {
116800
116993
  if (!(error2 instanceof Error) || error2.message !== timeoutMessage) {
116801
116994
  throw error2;
@@ -117271,7 +117464,7 @@ ${this.stack.split("\n").slice(1).join("\n")}` : this.toString();
117271
117464
  this.docSub = joinedSub;
117272
117465
  const syncDocTimeoutMs = readTimeoutEnv("LODY_LORO_SYNC_DOC_TIMEOUT_MS", 8e3);
117273
117466
  const timeoutMessage = `Timeout waiting for session doc initial sync (room=${this.roomId})`;
117274
- await withTimeout$2(joinedSub.firstSyncedWithRemote, syncDocTimeoutMs, timeoutMessage);
117467
+ await withTimeout$3(joinedSub.firstSyncedWithRemote, syncDocTimeoutMs, timeoutMessage);
117275
117468
  return;
117276
117469
  } catch (error2) {
117277
117470
  const errMsg = formatErrorMessage(error2);
@@ -117333,7 +117526,7 @@ ${this.stack.split("\n").slice(1).join("\n")}` : this.toString();
117333
117526
  const timeoutMs = options.timeoutMs ?? readTimeoutEnv("LODY_LORO_WAIT_DOC_SYNC_TIMEOUT_MS", 4e3);
117334
117527
  const timeoutMessage = `Timeout waiting for session doc pending writes (room=${this.roomId})`;
117335
117528
  try {
117336
- await withTimeout$2(sub.waitUntilSynced(), timeoutMs, timeoutMessage);
117529
+ await withTimeout$3(sub.waitUntilSynced(), timeoutMs, timeoutMessage);
117337
117530
  return true;
117338
117531
  } catch (error2) {
117339
117532
  this.logger.debug(`[${this.sessionId}] Session doc pending writes were not confirmed before continuing: ${formatErrorMessage(error2)}`);
@@ -119481,7 +119674,8 @@ ${this.stack.split("\n").slice(1).join("\n")}` : this.toString();
119481
119674
  "machine/status",
119482
119675
  "machine/acp-capabilities-refresh",
119483
119676
  "session/preview-create",
119484
- "session/preview-revoke"
119677
+ "session/preview-revoke",
119678
+ "local-project/control"
119485
119679
  ]);
119486
119680
  const LoroStreamsRpcErrorSchema = object$1({
119487
119681
  code: string$2().trim().min(1),
@@ -119527,11 +119721,18 @@ ${this.stack.split("\n").slice(1).join("\n")}` : this.toString();
119527
119721
  reason: string$2().trim().min(1).optional()
119528
119722
  }).strict()
119529
119723
  }).strict();
119724
+ const LoroLocalProjectControlRpcRequestSchema = BaseRpcRequestSchema.extend({
119725
+ method: literal("local-project/control"),
119726
+ params: object$1({
119727
+ request: LocalProjectControlRequestSchema
119728
+ }).strict()
119729
+ }).strict();
119530
119730
  const LoroStreamsRpcRequestSchema = discriminatedUnion("method", [
119531
119731
  LoroMachineStatusRpcRequestSchema,
119532
119732
  LoroMachineAcpCapabilitiesRefreshRpcRequestSchema,
119533
119733
  LoroSessionPreviewCreateRpcRequestSchema,
119534
- LoroSessionPreviewRevokeRpcRequestSchema
119734
+ LoroSessionPreviewRevokeRpcRequestSchema,
119735
+ LoroLocalProjectControlRpcRequestSchema
119535
119736
  ]);
119536
119737
  object$1({
119537
119738
  jsonrpc: literal(JSON_RPC_VERSION$1),
@@ -119870,6 +120071,18 @@ ${this.stack.split("\n").slice(1).join("\n")}` : this.toString();
119870
120071
  await this.appendResultResponse(request.replyTo, request.id, request.method, response);
119871
120072
  return;
119872
120073
  }
120074
+ case "local-project/control": {
120075
+ if (!this.deps.dispatchLocalProjectControl) {
120076
+ await this.appendErrorResponse(request.replyTo, request.id, request.method, {
120077
+ code: "method_unavailable",
120078
+ message: "Local project control is not available on this machine."
120079
+ });
120080
+ return;
120081
+ }
120082
+ const response = await this.deps.dispatchLocalProjectControl(request.params.request);
120083
+ await this.appendResultResponse(request.replyTo, request.id, request.method, response);
120084
+ return;
120085
+ }
119873
120086
  }
119874
120087
  } catch (error2) {
119875
120088
  const message = error2 instanceof Error ? error2.message : String(error2);
@@ -120904,6 +121117,9 @@ ${this.stack.split("\n").slice(1).join("\n")}` : this.toString();
120904
121117
  nes_reject: "nes/reject",
120905
121118
  nes_start: "nes/start",
120906
121119
  nes_suggest: "nes/suggest",
121120
+ providers_disable: "providers/disable",
121121
+ providers_list: "providers/list",
121122
+ providers_set: "providers/set",
120907
121123
  session_cancel: "session/cancel",
120908
121124
  session_close: "session/close",
120909
121125
  session_fork: "session/fork",
@@ -121105,6 +121321,15 @@ ${this.stack.split("\n").slice(1).join("\n")}` : this.toString();
121105
121321
  async authenticate(params) {
121106
121322
  return await this.connection.sendRequest(AGENT_METHODS.authenticate, params) ?? {};
121107
121323
  }
121324
+ async unstable_listProviders(params) {
121325
+ return await this.connection.sendRequest(AGENT_METHODS.providers_list, params);
121326
+ }
121327
+ async unstable_setProvider(params) {
121328
+ return await this.connection.sendRequest(AGENT_METHODS.providers_set, params) ?? {};
121329
+ }
121330
+ async unstable_disableProvider(params) {
121331
+ return await this.connection.sendRequest(AGENT_METHODS.providers_disable, params) ?? {};
121332
+ }
121108
121333
  async unstable_logout(params) {
121109
121334
  return await this.connection.sendRequest(AGENT_METHODS.logout, params) ?? {};
121110
121335
  }
@@ -121455,7 +121680,7 @@ ${this.stack.split("\n").slice(1).join("\n")}` : this.toString();
121455
121680
  this.name = "AcpTimeoutError";
121456
121681
  }
121457
121682
  }
121458
- function withTimeout$1(promise, logger2, operationName, sessionId, timeoutMs, warningIntervalMs = 1e4) {
121683
+ function withTimeout$2(promise, logger2, operationName, sessionId, timeoutMs, warningIntervalMs = 1e4) {
121459
121684
  let completed = false;
121460
121685
  let elapsedMs = 0;
121461
121686
  let timeoutHandle;
@@ -121849,7 +122074,7 @@ ${this.stack.split("\n").slice(1).join("\n")}` : this.toString();
121849
122074
  type: "initialize_start"
121850
122075
  });
121851
122076
  const ACP_INIT_TIMEOUT_MS = Math.max(0, timeoutOptions.initTimeoutMs ?? 12e4);
121852
- const initResponse = await withTimeout$1(withAbort(connection.initialize({
122077
+ const initResponse = await withTimeout$2(withAbort(connection.initialize({
121853
122078
  protocolVersion: PROTOCOL_VERSION,
121854
122079
  clientCapabilities: {
121855
122080
  terminal: this.terminalEnabled,
@@ -121957,7 +122182,7 @@ ${this.stack.split("\n").slice(1).join("\n")}` : this.toString();
121957
122182
  } else {
121958
122183
  this.logger.debug(`[${this.options.sessionId}] Calling connection.newSession (cwd=${workdir})`);
121959
122184
  const ACP_NEW_SESSION_TIMEOUT_MS = Math.max(0, timeoutOptions.newSessionTimeoutMs ?? 12e4);
121960
- sessionResponse = await withTimeout$1(withAbort(connection.newSession({
122185
+ sessionResponse = await withTimeout$2(withAbort(connection.newSession({
121961
122186
  cwd: workdir,
121962
122187
  mcpServers
121963
122188
  }), startupAbort), this.logger, "connection.newSession", this.options.sessionId, ACP_NEW_SESSION_TIMEOUT_MS);
@@ -122070,7 +122295,7 @@ ${this.stack.split("\n").slice(1).join("\n")}` : this.toString();
122070
122295
  return false;
122071
122296
  }
122072
122297
  this.logger.debug(`[${this.options.sessionId}] Closing ACP session (acpSessionId=${sessionId} timeoutMs=${timeoutMs})`);
122073
- await withTimeout$1(closeSession2.call(this.connection, {
122298
+ await withTimeout$2(closeSession2.call(this.connection, {
122074
122299
  sessionId
122075
122300
  }), this.logger, "connection.closeSession", this.options.sessionId, timeoutMs, Math.min(timeoutMs, 1e3));
122076
122301
  this.logger.debug(`[${this.options.sessionId}] ACP session close finished (acpSessionId=${sessionId})`);
@@ -122203,12 +122428,12 @@ ${this.stack.split("\n").slice(1).join("\n")}` : this.toString();
122203
122428
  const BuiltinACPSetting = {
122204
122429
  claude: {
122205
122430
  packageName: "acp-extension-claude",
122206
- version: "0.31.1",
122431
+ version: "0.34.1",
122207
122432
  binName: "acp-extension-claude"
122208
122433
  },
122209
122434
  codex: {
122210
122435
  packageName: "acp-extension-codex",
122211
- version: "0.14.2",
122436
+ version: "0.14.3",
122212
122437
  binName: "acp-extension-codex",
122213
122438
  args: [
122214
122439
  "-c",
@@ -122228,12 +122453,50 @@ ${this.stack.split("\n").slice(1).join("\n")}` : this.toString();
122228
122453
  "claude",
122229
122454
  "codex"
122230
122455
  ]);
122456
+ const moduleRequire = createRequire$1(import.meta.url);
122457
+ function isRecord$1(value) {
122458
+ return typeof value === "object" && value !== null;
122459
+ }
122460
+ function resolveInstalledPackageBin(setting) {
122461
+ try {
122462
+ const packageJsonPath = moduleRequire.resolve(`${setting.packageName}/package.json`);
122463
+ const packageJson = JSON.parse(readFileSync$1(packageJsonPath, "utf8"));
122464
+ if (!isRecord$1(packageJson) || packageJson.version !== setting.version) {
122465
+ return void 0;
122466
+ }
122467
+ const bin2 = packageJson.bin;
122468
+ const namedBin = isRecord$1(bin2) ? bin2[setting.binName] : void 0;
122469
+ const relativeBin = typeof bin2 === "string" ? bin2 : typeof namedBin === "string" ? namedBin : void 0;
122470
+ if (!relativeBin) {
122471
+ return void 0;
122472
+ }
122473
+ const binPath = resolve$2(dirname$1(packageJsonPath), relativeBin);
122474
+ return existsSync(binPath) ? binPath : void 0;
122475
+ } catch {
122476
+ return void 0;
122477
+ }
122478
+ }
122231
122479
  function resolveBuiltinACPSetting(agentType) {
122232
122480
  if (!builtinTypeSet.has(agentType)) {
122233
122481
  throw new Error(`Unsupported builtin ACP type: ${agentType}`);
122234
122482
  }
122235
122483
  const builtinType = agentType;
122236
122484
  const setting = BuiltinACPSetting[builtinType];
122485
+ if (builtinType === "claude") {
122486
+ const localClaudeAcpPath = resolveInstalledPackageBin(setting);
122487
+ if (localClaudeAcpPath) {
122488
+ return {
122489
+ status: {
122490
+ agent: `local ${setting.packageName}@${setting.version}`,
122491
+ command: localClaudeAcpPath
122492
+ },
122493
+ exec: {
122494
+ command: localClaudeAcpPath,
122495
+ args: setting.args ?? []
122496
+ }
122497
+ };
122498
+ }
122499
+ }
122237
122500
  const agent = `${setting.packageName}@${setting.version}`;
122238
122501
  const packageSpec = `${setting.packageName}@${setting.version}`;
122239
122502
  const args2 = [
@@ -122270,11 +122533,7 @@ ${this.stack.split("\n").slice(1).join("\n")}` : this.toString();
122270
122533
  }
122271
122534
  return `${agent.id}@${agent.version}`;
122272
122535
  }
122273
- function resolveRegistryACPSetting(agentType) {
122274
- const agent = registryAgentsById[agentType];
122275
- if (!agent) {
122276
- throw new Error(`Unknown registry ACP type: ${agentType}`);
122277
- }
122536
+ function resolveRegistryAgentACPSetting(agent) {
122278
122537
  if (agent.distribution.local?.command) {
122279
122538
  const isNpx = agent.distribution.local.command === "npx";
122280
122539
  const args2 = [
@@ -122331,7 +122590,14 @@ ${this.stack.split("\n").slice(1).join("\n")}` : this.toString();
122331
122590
  }
122332
122591
  };
122333
122592
  }
122334
- throw new Error(`Registry ACP ${agentType} has no supported launcher`);
122593
+ throw new Error(`Registry ACP ${agent.id} has no supported launcher`);
122594
+ }
122595
+ function resolveRegistryACPSetting(agentType) {
122596
+ const agent = registryAgentsById[agentType];
122597
+ if (!agent) {
122598
+ throw new Error(`Unknown registry ACP type: ${agentType}`);
122599
+ }
122600
+ return resolveRegistryAgentACPSetting(agent);
122335
122601
  }
122336
122602
  function resolveACPSetting(input2) {
122337
122603
  if (input2.cliType === "builtin") {
@@ -122542,7 +122808,7 @@ ${this.stack.split("\n").slice(1).join("\n")}` : this.toString();
122542
122808
  sessionResponse
122543
122809
  };
122544
122810
  };
122545
- function waitForChildProcessExit(child, timeoutMs) {
122811
+ function waitForChildProcessExit$1(child, timeoutMs) {
122546
122812
  if (child.exitCode !== null) {
122547
122813
  return Promise.resolve(true);
122548
122814
  }
@@ -122563,32 +122829,32 @@ ${this.stack.split("\n").slice(1).join("\n")}` : this.toString();
122563
122829
  child.once("exit", onExit2);
122564
122830
  });
122565
122831
  }
122566
- function signalChildProcess(child, signal) {
122832
+ function signalChildProcess$1(child, signal) {
122567
122833
  if (process.platform !== "win32" && typeof child.pid === "number" && child.pid > 0) {
122568
122834
  process.kill(-child.pid, signal);
122569
122835
  return;
122570
122836
  }
122571
122837
  child.kill(signal);
122572
122838
  }
122573
- async function terminateChildProcess(child, logger2, sessionLabel, exitTimeoutMs) {
122839
+ async function terminateChildProcess$1(child, logger2, sessionLabel, exitTimeoutMs) {
122574
122840
  if (child.exitCode !== null) {
122575
122841
  return;
122576
122842
  }
122577
122843
  try {
122578
- signalChildProcess(child, "SIGTERM");
122844
+ signalChildProcess$1(child, "SIGTERM");
122579
122845
  } catch {
122580
122846
  return;
122581
122847
  }
122582
- if (await waitForChildProcessExit(child, exitTimeoutMs)) {
122848
+ if (await waitForChildProcessExit$1(child, exitTimeoutMs)) {
122583
122849
  return;
122584
122850
  }
122585
122851
  logger2.debug(`[${sessionLabel}] ACP agent process did not exit within ${exitTimeoutMs}ms of SIGTERM; escalating to SIGKILL`);
122586
122852
  try {
122587
- signalChildProcess(child, "SIGKILL");
122853
+ signalChildProcess$1(child, "SIGKILL");
122588
122854
  } catch {
122589
122855
  return;
122590
122856
  }
122591
- await waitForChildProcessExit(child, exitTimeoutMs);
122857
+ await waitForChildProcessExit$1(child, exitTimeoutMs);
122592
122858
  }
122593
122859
  const spawnAcpProcess = (options) => {
122594
122860
  const setting = resolveACPSetting({
@@ -122735,7 +123001,7 @@ ${this.stack.split("\n").slice(1).join("\n")}` : this.toString();
122735
123001
  sessionResponse: started.sessionResponse
122736
123002
  };
122737
123003
  } catch (error2) {
122738
- await terminateChildProcess(agentProcess, options.logger, "acp-startup", 3e3);
123004
+ await terminateChildProcess$1(agentProcess, options.logger, "acp-startup", 3e3);
122739
123005
  throw error2;
122740
123006
  } finally {
122741
123007
  startupMonitor.dispose();
@@ -122751,7 +123017,7 @@ ${this.stack.split("\n").slice(1).join("\n")}` : this.toString();
122751
123017
  options.logger.debug(`[${options.sessionLabel}] ACP session close failed during local agent shutdown: ${error2 instanceof Error ? error2.message : String(error2)}`);
122752
123018
  }
122753
123019
  }
122754
- await terminateChildProcess(options.agentProcess, options.logger, options.sessionLabel, exitTimeoutMs);
123020
+ await terminateChildProcess$1(options.agentProcess, options.logger, options.sessionLabel, exitTimeoutMs);
122755
123021
  }
122756
123022
  const isRecord = (value) => typeof value === "object" && value !== null;
122757
123023
  const getStringField = (obj, key2) => {
@@ -125761,6 +126027,150 @@ $mem | ConvertTo-Json -Compress
125761
126027
  return null;
125762
126028
  }
125763
126029
  }
126030
+ function getImportedAcpSourceAcpSessionId(meta) {
126031
+ const externalHistory = meta.externalHistory;
126032
+ return externalHistory?.sourceAcpSessionId;
126033
+ }
126034
+ function isImportedAcpReplayUserTurn(entry2, meta) {
126035
+ const provider2 = meta.externalHistory ? getLocalProjectHistoryProviderKey(meta.externalHistory.provider) : null;
126036
+ const sourceAcpSessionId = getImportedAcpSourceAcpSessionId(meta);
126037
+ return !!provider2 && !!sourceAcpSessionId && entry2.id.startsWith(`${provider2}:${sourceAcpSessionId}:turn:`);
126038
+ }
126039
+ function resolveResumableAcpSessionId(meta) {
126040
+ const acpSessionId = meta?.acpSessionId;
126041
+ if (!meta || !acpSessionId) {
126042
+ return void 0;
126043
+ }
126044
+ if (!meta.externalHistory) {
126045
+ return acpSessionId;
126046
+ }
126047
+ const sourceAcpSessionId = meta.externalHistory.sourceAcpSessionId;
126048
+ if (!sourceAcpSessionId) {
126049
+ return void 0;
126050
+ }
126051
+ return acpSessionId === sourceAcpSessionId ? void 0 : acpSessionId;
126052
+ }
126053
+ function resolveDispatchAcpSessionId(meta) {
126054
+ const liveSessionId = resolveResumableAcpSessionId(meta);
126055
+ if (liveSessionId || !meta?.externalHistory || meta.externalHistory.status === "sync_conflict") {
126056
+ return liveSessionId;
126057
+ }
126058
+ return meta.externalHistory.sourceAcpSessionId;
126059
+ }
126060
+ function resolveSessionDispatchAction(snapshot, machineId) {
126061
+ const { meta, history, hasActiveTurn, hasBlockingPendingCreate, hasReusableSession } = snapshot;
126062
+ if (!meta || meta.machineId !== machineId) {
126063
+ return {
126064
+ type: "noop",
126065
+ reason: "not-owned"
126066
+ };
126067
+ }
126068
+ if (meta.isArchived) {
126069
+ return {
126070
+ type: "noop",
126071
+ reason: "archived"
126072
+ };
126073
+ }
126074
+ if (hasBlockingPendingCreate) {
126075
+ return {
126076
+ type: "noop",
126077
+ reason: "pending-create"
126078
+ };
126079
+ }
126080
+ const statusType = meta.status?.type;
126081
+ if (statusType === "running" || statusType === "requestPermission" || statusType === "initializing") {
126082
+ if (hasActiveTurn) {
126083
+ return {
126084
+ type: "noop",
126085
+ reason: "active-session"
126086
+ };
126087
+ }
126088
+ return {
126089
+ type: "reset-stale-status",
126090
+ statusType
126091
+ };
126092
+ }
126093
+ const turn = findNextDispatchableUserTurn(history, meta);
126094
+ if (!turn) {
126095
+ return {
126096
+ type: "no-dispatchable-turn"
126097
+ };
126098
+ }
126099
+ const mode2 = hasReusableSession || resolveDispatchAcpSessionId(meta) ? "continue" : "create";
126100
+ return {
126101
+ type: "dispatch",
126102
+ mode: mode2,
126103
+ turn
126104
+ };
126105
+ }
126106
+ function resolveSessionCancelAction(meta, lastSeenCancelTurn, machineId) {
126107
+ if (!meta || meta.machineId !== machineId) {
126108
+ return {
126109
+ type: "noop",
126110
+ reason: "not-owned"
126111
+ };
126112
+ }
126113
+ if (meta.isArchived) {
126114
+ return {
126115
+ type: "noop",
126116
+ reason: "archived"
126117
+ };
126118
+ }
126119
+ const lastCanceledTurn = meta.lastCanceledTurn;
126120
+ if (typeof lastCanceledTurn !== "string" || !lastCanceledTurn) {
126121
+ return {
126122
+ type: "noop",
126123
+ reason: "no-cancel-turn"
126124
+ };
126125
+ }
126126
+ if (lastCanceledTurn === lastSeenCancelTurn) {
126127
+ return {
126128
+ type: "noop",
126129
+ reason: "already-seen"
126130
+ };
126131
+ }
126132
+ return {
126133
+ type: "cancel",
126134
+ turnId: lastCanceledTurn
126135
+ };
126136
+ }
126137
+ function findNextDispatchableUserTurn(history, meta) {
126138
+ for (const entry2 of history) {
126139
+ if (entry2.role !== "user") {
126140
+ continue;
126141
+ }
126142
+ if (isImportedAcpReplayUserTurn(entry2, meta)) {
126143
+ continue;
126144
+ }
126145
+ if (typeof entry2.status === "string") {
126146
+ if (entry2.status === "pending" || entry2.status === "seen" || entry2.status === "processing") {
126147
+ return entry2;
126148
+ }
126149
+ continue;
126150
+ }
126151
+ if (entry2.read === false) {
126152
+ return entry2;
126153
+ }
126154
+ if (entry2.id === meta.processingUserMsgId) {
126155
+ return entry2;
126156
+ }
126157
+ if (entry2.id === meta.latestUserMsgId && entry2.id !== meta.lastHandledUserMsgId) {
126158
+ return entry2;
126159
+ }
126160
+ }
126161
+ return null;
126162
+ }
126163
+ function resolveDispatchTurnInput(entry2) {
126164
+ const historyBlocks = historyItemsToInputBlocks(entry2.items);
126165
+ const configuredBlocks = normalizeSessionInputBlocks(entry2.inputConfig?.inputBlocks, "");
126166
+ const fallbackBlocks = normalizeSessionInputBlocks(void 0, entry2.inputConfig?.prompt ?? "");
126167
+ const inputBlocks = configuredBlocks.length > 0 ? configuredBlocks : historyBlocks.length > 0 ? historyBlocks : fallbackBlocks;
126168
+ const prompt2 = entry2.inputConfig?.prompt ?? extractPromptPreviewFromInputBlocks(inputBlocks.length > 0 ? inputBlocks : historyBlocks);
126169
+ return {
126170
+ inputBlocks,
126171
+ prompt: prompt2
126172
+ };
126173
+ }
125764
126174
  class SessionTurnCancelled extends TaggedError("SessionTurnCancelled") {
125765
126175
  }
125766
126176
  class SessionTurnHalted extends TaggedError("SessionTurnHalted") {
@@ -126753,7 +127163,7 @@ $mem | ConvertTo-Json -Compress
126753
127163
  const agentConfigEnv = agentConfigEnvResolution.env ?? void 0;
126754
127164
  self2.deps.logger.debug(`[${sessionId}] Resume env resolved (agentConfigId=${meta?.agentConfigId ?? "none"} reason=${agentConfigEnvResolution.reason} keys=${agentConfigEnv ? Object.keys(agentConfigEnv).length : 0})`);
126755
127165
  const requestedResumeSessionId = acpSessionConfig.resume;
126756
- const storedResumeSessionId = meta?.acpSessionId;
127166
+ const storedResumeSessionId = resolveResumableAcpSessionId(meta);
126757
127167
  const resumeSessionId = requestedResumeSessionId ?? storedResumeSessionId;
126758
127168
  const resumeSource = requestedResumeSessionId ? "request" : storedResumeSessionId ? "meta" : "none";
126759
127169
  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 +127880,6 @@ $mem | ConvertTo-Json -Compress
127470
127880
  };
127471
127881
  }
127472
127882
  }
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
127883
  const isConfigOptionValueRecord = (value) => {
127585
127884
  if (!value || typeof value !== "object" || Array.isArray(value)) {
127586
127885
  return false;
@@ -127955,7 +128254,7 @@ $mem | ConvertTo-Json -Compress
127955
128254
  modelId: entry2.inputConfig?.modelId,
127956
128255
  configOptionValues: entry2.inputConfig?.configOptionValues,
127957
128256
  issuePRMentions: entry2.inputConfig?.issuePRMentions,
127958
- resume: entry2.inputConfig?.resume ?? meta.acpSessionId ?? void 0
128257
+ resume: entry2.inputConfig?.resume ?? resolveDispatchAcpSessionId(meta)
127959
128258
  },
127960
128259
  userTurnId: entry2.id,
127961
128260
  userId: entry2.userId ?? meta.userId,
@@ -128017,7 +128316,7 @@ $mem | ConvertTo-Json -Compress
128017
128316
  modelId: queuedItem.acpSessionConfig?.modelId,
128018
128317
  configOptionValues: isConfigOptionValueRecord(queuedItem.acpSessionConfig?.configOptionValues) ? queuedItem.acpSessionConfig.configOptionValues : void 0,
128019
128318
  issuePRMentions: queuedItem.acpSessionConfig?.issuePRMentions,
128020
- resume: meta.acpSessionId ?? void 0
128319
+ resume: resolveResumableAcpSessionId(meta)
128021
128320
  });
128022
128321
  const pendingEntry = buildPendingUserHistoryEntry({
128023
128322
  userId: queuedItem.userId ?? meta.userId,
@@ -129053,7 +129352,7 @@ $mem | ConvertTo-Json -Compress
129053
129352
  }
129054
129353
  })();
129055
129354
  try {
129056
- await withTimeout(readyPromise, TUNNEL_READY_TIMEOUT_MS, "Timed out waiting for preview tunnel control connection to become ready");
129355
+ await withTimeout$1(readyPromise, TUNNEL_READY_TIMEOUT_MS, "Timed out waiting for preview tunnel control connection to become ready");
129057
129356
  } catch (error2) {
129058
129357
  await close2("Preview tunnel failed to become ready");
129059
129358
  throw error2;
@@ -129876,7 +130175,7 @@ ${escapeHtmlScriptContent(VISUAL_ANNOTATION_INSPECTOR_BROWSER_SCRIPT)}
129876
130175
  function asError(error2) {
129877
130176
  return error2 instanceof Error ? error2 : new Error(formatError(error2));
129878
130177
  }
129879
- async function withTimeout(promise, timeoutMs, message) {
130178
+ async function withTimeout$1(promise, timeoutMs, message) {
129880
130179
  let timeoutHandle;
129881
130180
  const timeoutPromise = new Promise((_2, reject) => {
129882
130181
  timeoutHandle = setTimeout(() => reject(new Error(message)), timeoutMs);
@@ -130738,6 +131037,796 @@ ${escapeHtmlScriptContent(VISUAL_ANNOTATION_INSPECTOR_BROWSER_SCRIPT)}
130738
131037
  return Math.round(this.deps.now?.() ?? getServerNow());
130739
131038
  }
130740
131039
  }
131040
+ const ACP_OPERATION_TIMEOUT_MS = 12e4;
131041
+ const ACP_PROCESS_EXIT_TIMEOUT_MS = 3e3;
131042
+ function waitForChildProcessExit(child, timeoutMs) {
131043
+ if (child.exitCode !== null) {
131044
+ return Promise.resolve(true);
131045
+ }
131046
+ return new Promise((resolve2) => {
131047
+ const timeout2 = setTimeout(() => {
131048
+ child.off("exit", onExit2);
131049
+ resolve2(child.exitCode !== null);
131050
+ }, timeoutMs);
131051
+ const onExit2 = () => {
131052
+ clearTimeout(timeout2);
131053
+ resolve2(true);
131054
+ };
131055
+ child.once("exit", onExit2);
131056
+ });
131057
+ }
131058
+ function signalChildProcess(child, signal) {
131059
+ if (process.platform !== "win32" && typeof child.pid === "number" && child.pid > 0) {
131060
+ process.kill(-child.pid, signal);
131061
+ return;
131062
+ }
131063
+ child.kill(signal);
131064
+ }
131065
+ async function terminateChildProcess(child) {
131066
+ if (child.exitCode !== null) {
131067
+ return;
131068
+ }
131069
+ try {
131070
+ signalChildProcess(child, "SIGTERM");
131071
+ } catch {
131072
+ return;
131073
+ }
131074
+ if (await waitForChildProcessExit(child, ACP_PROCESS_EXIT_TIMEOUT_MS)) {
131075
+ return;
131076
+ }
131077
+ try {
131078
+ signalChildProcess(child, "SIGKILL");
131079
+ } catch {
131080
+ return;
131081
+ }
131082
+ await waitForChildProcessExit(child, ACP_PROCESS_EXIT_TIMEOUT_MS);
131083
+ }
131084
+ async function withTimeout(promise, label2) {
131085
+ let timeout2 = null;
131086
+ try {
131087
+ return await Promise.race([
131088
+ promise,
131089
+ new Promise((_resolve, reject) => {
131090
+ timeout2 = setTimeout(() => reject(new Error(`${label2} timed out after ${ACP_OPERATION_TIMEOUT_MS}ms`)), ACP_OPERATION_TIMEOUT_MS);
131091
+ })
131092
+ ]);
131093
+ } finally {
131094
+ if (timeout2) {
131095
+ clearTimeout(timeout2);
131096
+ }
131097
+ }
131098
+ }
131099
+ class AcpReplayCollectorClient {
131100
+ notifications = [];
131101
+ async requestPermission() {
131102
+ return {
131103
+ outcome: {
131104
+ outcome: "cancelled"
131105
+ }
131106
+ };
131107
+ }
131108
+ async sessionUpdate(params) {
131109
+ this.notifications.push(parseSessionNotification(params));
131110
+ }
131111
+ }
131112
+ function getProviderLabel$1(provider2) {
131113
+ return getLocalProjectHistoryProviderKey(provider2);
131114
+ }
131115
+ async function createHistoryAcpConnection(args2) {
131116
+ const setting = resolveACPSetting(args2.provider);
131117
+ const env2 = setting.exec.env ? {
131118
+ ...process.env,
131119
+ ...setting.exec.env
131120
+ } : process.env;
131121
+ const agentProcess = spawnAcpProcess({
131122
+ cliType: args2.provider.cliType,
131123
+ agentType: args2.provider.agentType,
131124
+ workdir: args2.workdir,
131125
+ env: env2
131126
+ });
131127
+ agentProcess.stderr?.setEncoding("utf8");
131128
+ agentProcess.stderr?.on("data", (chunk) => {
131129
+ if (!chunk) return;
131130
+ args2.logger.debug(`[${getProviderLabel$1(args2.provider)}-history-sync] ACP stderr: ${chunk.slice(0, 1200)}`);
131131
+ });
131132
+ if (!agentProcess.stdout || !agentProcess.stdin) {
131133
+ await terminateChildProcess(agentProcess);
131134
+ throw new Error(`${getProviderLabel$1(args2.provider)} ACP process did not expose stdio streams`);
131135
+ }
131136
+ const output = createStdoutReadableStream(agentProcess.stdout);
131137
+ const input2 = createStdinWritableStream(agentProcess.stdin);
131138
+ const stream2 = ndJsonStream(input2, output);
131139
+ const collector = new AcpReplayCollectorClient();
131140
+ const connection = new ClientSideConnection(() => collector, stream2);
131141
+ try {
131142
+ const initResponse = await withTimeout(connection.initialize({
131143
+ protocolVersion: PROTOCOL_VERSION,
131144
+ clientCapabilities: {
131145
+ terminal: false,
131146
+ fs: {
131147
+ readTextFile: false,
131148
+ writeTextFile: false
131149
+ }
131150
+ }
131151
+ }), `${getProviderLabel$1(args2.provider)} ACP initialize`);
131152
+ return {
131153
+ agentProcess,
131154
+ connection,
131155
+ collector,
131156
+ initResponse
131157
+ };
131158
+ } catch (error2) {
131159
+ await terminateChildProcess(agentProcess);
131160
+ throw error2;
131161
+ }
131162
+ }
131163
+ async function resolveCatalogQueryPaths(rootPath) {
131164
+ const resolved = path__default.resolve(rootPath);
131165
+ let real = null;
131166
+ try {
131167
+ real = await fs$5.realpath(resolved);
131168
+ } catch {
131169
+ real = null;
131170
+ }
131171
+ const paths = [
131172
+ resolved
131173
+ ];
131174
+ if (real && real !== resolved) {
131175
+ paths.push(real);
131176
+ }
131177
+ return paths;
131178
+ }
131179
+ async function listPaginatedHistorySessions(cwd, listPage) {
131180
+ const sessions = [];
131181
+ let cursor;
131182
+ do {
131183
+ const response = await listPage({
131184
+ cwd,
131185
+ cursor
131186
+ });
131187
+ sessions.push(...response.sessions);
131188
+ cursor = response.nextCursor;
131189
+ } while (cursor);
131190
+ return sessions;
131191
+ }
131192
+ async function listHistorySessionsForLocalProject(args2) {
131193
+ const queryPaths = await resolveCatalogQueryPaths(args2.rootPath);
131194
+ const bySessionId = /* @__PURE__ */ new Map();
131195
+ for (const cwd of queryPaths) {
131196
+ const { agentProcess, connection, initResponse } = await createHistoryAcpConnection({
131197
+ provider: args2.provider,
131198
+ workdir: cwd,
131199
+ logger: args2.logger
131200
+ });
131201
+ try {
131202
+ if (!initResponse.agentCapabilities?.sessionCapabilities?.list) {
131203
+ throw new Error(`${getProviderLabel$1(args2.provider)} ACP agent does not advertise sessionCapabilities.list`);
131204
+ }
131205
+ const sessions = await listPaginatedHistorySessions(cwd, async ({ cursor }) => withTimeout(connection.listSessions({
131206
+ cwd,
131207
+ cursor
131208
+ }), `${getProviderLabel$1(args2.provider)} ACP listSessions (${cwd})`));
131209
+ for (const session of sessions) {
131210
+ bySessionId.set(session.sessionId, session);
131211
+ }
131212
+ } finally {
131213
+ await terminateChildProcess(agentProcess);
131214
+ }
131215
+ }
131216
+ return {
131217
+ sessions: [
131218
+ ...bySessionId.values()
131219
+ ],
131220
+ queryPaths
131221
+ };
131222
+ }
131223
+ async function loadHistorySessionReplay(args2) {
131224
+ const cwd = path__default.resolve(args2.rootPath);
131225
+ const { agentProcess, connection, collector, initResponse } = await createHistoryAcpConnection({
131226
+ provider: args2.provider,
131227
+ workdir: cwd,
131228
+ logger: args2.logger
131229
+ });
131230
+ try {
131231
+ if (!initResponse.agentCapabilities?.loadSession) {
131232
+ throw new Error(`${getProviderLabel$1(args2.provider)} ACP agent does not advertise loadSession`);
131233
+ }
131234
+ await withTimeout(connection.loadSession({
131235
+ sessionId: args2.acpSessionId,
131236
+ cwd,
131237
+ mcpServers: []
131238
+ }), `${getProviderLabel$1(args2.provider)} ACP loadSession (${args2.acpSessionId})`);
131239
+ return collector.notifications;
131240
+ } catch (error2) {
131241
+ const message = `Failed to load ${getProviderLabel$1(args2.provider)} session ${args2.acpSessionId}: ${formatErrorMessage(error2)}`;
131242
+ throw new Error(message, {
131243
+ cause: error2
131244
+ });
131245
+ } finally {
131246
+ await terminateChildProcess(agentProcess);
131247
+ }
131248
+ }
131249
+ const syncLeases = /* @__PURE__ */ new Set();
131250
+ const machineCatalogWriteChains = /* @__PURE__ */ new Map();
131251
+ async function withMachineCatalogWriteLock(machineRoomId, fn) {
131252
+ const prev = machineCatalogWriteChains.get(machineRoomId);
131253
+ const current2 = (async () => {
131254
+ if (prev) {
131255
+ await prev.catch(() => {
131256
+ });
131257
+ }
131258
+ return fn();
131259
+ })();
131260
+ machineCatalogWriteChains.set(machineRoomId, current2);
131261
+ try {
131262
+ return await current2;
131263
+ } finally {
131264
+ if (machineCatalogWriteChains.get(machineRoomId) === current2) {
131265
+ machineCatalogWriteChains.delete(machineRoomId);
131266
+ }
131267
+ }
131268
+ }
131269
+ function emptySummary() {
131270
+ return {
131271
+ listed: 0,
131272
+ imported: 0,
131273
+ refreshed: 0,
131274
+ skipped: 0,
131275
+ conflicted: 0,
131276
+ failed: 0,
131277
+ failures: []
131278
+ };
131279
+ }
131280
+ function stableJson(value) {
131281
+ if (value === null || typeof value !== "object") {
131282
+ return JSON.stringify(value);
131283
+ }
131284
+ if (Array.isArray(value)) {
131285
+ return `[${value.map((item) => stableJson(item)).join(",")}]`;
131286
+ }
131287
+ const record2 = value;
131288
+ const entries = Object.keys(record2).filter((key2) => record2[key2] !== void 0).sort().map((key2) => `${JSON.stringify(key2)}:${stableJson(record2[key2])}`);
131289
+ return `{${entries.join(",")}}`;
131290
+ }
131291
+ function hashText(value) {
131292
+ return createHash$1("sha256").update(value).digest("hex");
131293
+ }
131294
+ function normalizeHistoryEntryForHash(entry2) {
131295
+ return {
131296
+ role: entry2.role,
131297
+ items: entry2.items ?? [],
131298
+ plan: entry2.plan ?? []
131299
+ };
131300
+ }
131301
+ function hashHistoryEntry(entry2) {
131302
+ return hashText(stableJson(normalizeHistoryEntryForHash(entry2)));
131303
+ }
131304
+ function materializeReplay(args2) {
131305
+ let tempId = 0;
131306
+ const nowIso = new Date(getServerNow()).toISOString();
131307
+ const providerKey = getLocalProjectHistoryProviderKey(args2.provider);
131308
+ const replay = buildHistoryReplayImport(args2.replayNotifications, {
131309
+ provider: args2.provider,
131310
+ userId: args2.userId,
131311
+ now: () => nowIso,
131312
+ createId: () => `${providerKey}:${args2.acpSessionId}:tmp:${tempId++}`,
131313
+ mode: "imported_snapshot"
131314
+ });
131315
+ const turnHashes = replay.history.map(hashHistoryEntry);
131316
+ const history = replay.history.map((entry2, index2) => ({
131317
+ ...entry2,
131318
+ id: `${providerKey}:${args2.acpSessionId}:turn:${index2}:${turnHashes[index2].slice(0, 16)}`
131319
+ }));
131320
+ return {
131321
+ history,
131322
+ turnHashes,
131323
+ replayDigest: hashText(turnHashes.join("\n"))
131324
+ };
131325
+ }
131326
+ function isPrefix(prefix, value) {
131327
+ if (prefix.length > value.length) {
131328
+ return false;
131329
+ }
131330
+ for (let index2 = 0; index2 < prefix.length; index2 += 1) {
131331
+ if (prefix[index2] !== value[index2]) {
131332
+ return false;
131333
+ }
131334
+ }
131335
+ return true;
131336
+ }
131337
+ function decideHistoryRefresh(args2) {
131338
+ if (args2.replayDigest === args2.externalHistory.replayDigest) {
131339
+ return {
131340
+ status: "skipped",
131341
+ reason: "digest_match"
131342
+ };
131343
+ }
131344
+ if (!isPrefix(args2.externalHistory.importedTurnHashes, args2.turnHashes)) {
131345
+ return {
131346
+ status: "conflicted",
131347
+ reason: "prefix_mismatch"
131348
+ };
131349
+ }
131350
+ if (args2.currentHistoryLength !== void 0 && args2.currentHistoryLength !== args2.externalHistory.importedTurnCount) {
131351
+ return {
131352
+ status: "conflicted",
131353
+ reason: "local_history_has_untracked_suffix"
131354
+ };
131355
+ }
131356
+ const appendFromIndex = args2.externalHistory.importedTurnCount;
131357
+ return args2.turnHashes.length > appendFromIndex ? {
131358
+ status: "refreshed",
131359
+ reason: "prefix_append",
131360
+ appendFromIndex
131361
+ } : {
131362
+ status: "skipped",
131363
+ reason: "empty_suffix",
131364
+ appendFromIndex
131365
+ };
131366
+ }
131367
+ async function listWorkspaceSessionMetas(manager) {
131368
+ const scanner = manager.repo.getMeta();
131369
+ if (!scanner) {
131370
+ return [];
131371
+ }
131372
+ const roomIds = /* @__PURE__ */ new Set();
131373
+ for (const row of await scanner.scan({
131374
+ prefix: [
131375
+ "m"
131376
+ ]
131377
+ })) {
131378
+ const key2 = row.key;
131379
+ if (!Array.isArray(key2) || key2.length < 2) {
131380
+ continue;
131381
+ }
131382
+ const roomId = key2[1];
131383
+ if (typeof roomId === "string" && isSessionDocRoomId(roomId)) {
131384
+ roomIds.add(roomId);
131385
+ }
131386
+ }
131387
+ const metas = await Promise.all([
131388
+ ...roomIds
131389
+ ].map(async (roomId) => {
131390
+ const record2 = await manager.repo.getDocMeta(roomId);
131391
+ if (!record2?.meta || isLoroRepoDocDeleted(record2)) {
131392
+ return null;
131393
+ }
131394
+ const sessionId = roomId.slice("session-".length);
131395
+ return {
131396
+ sessionId,
131397
+ meta: record2.meta
131398
+ };
131399
+ }));
131400
+ return metas.filter((meta) => meta !== null);
131401
+ }
131402
+ function buildExistingHistorySessionIndex(metas, machineId, provider2) {
131403
+ const index2 = /* @__PURE__ */ new Map();
131404
+ const providerKey = getLocalProjectHistoryProviderKey(provider2);
131405
+ for (const entry2 of metas) {
131406
+ if (entry2.meta.machineId !== machineId) continue;
131407
+ if (entry2.meta.cliType !== provider2.cliType) continue;
131408
+ if (entry2.meta.agentType !== provider2.agentType) continue;
131409
+ const acpSessionIds = /* @__PURE__ */ new Set();
131410
+ if (entry2.meta.externalHistory && getLocalProjectHistoryProviderKey(entry2.meta.externalHistory.provider) === providerKey) {
131411
+ const sourceAcpSessionId = entry2.meta.externalHistory.sourceAcpSessionId;
131412
+ if (sourceAcpSessionId) {
131413
+ acpSessionIds.add(sourceAcpSessionId);
131414
+ }
131415
+ if (entry2.meta.acpSessionId && entry2.meta.acpSessionId !== sourceAcpSessionId) {
131416
+ acpSessionIds.add(entry2.meta.acpSessionId);
131417
+ }
131418
+ } else if (entry2.meta.acpSessionId) {
131419
+ acpSessionIds.add(entry2.meta.acpSessionId);
131420
+ }
131421
+ for (const acpSessionId of acpSessionIds) {
131422
+ index2.set(acpSessionId, entry2);
131423
+ }
131424
+ }
131425
+ return index2;
131426
+ }
131427
+ function getProviderLabel(provider2) {
131428
+ return getLocalProjectHistoryProviderKey(provider2);
131429
+ }
131430
+ function resolveSessionTitle(info, provider2) {
131431
+ const title2 = info.title?.trim();
131432
+ return title2 || `${getProviderLabel(provider2)} session`;
131433
+ }
131434
+ function parseUpdatedAtMs(updatedAt) {
131435
+ if (!updatedAt) return 0;
131436
+ const parsed = Date.parse(updatedAt);
131437
+ return Number.isFinite(parsed) ? parsed : 0;
131438
+ }
131439
+ function compareCatalogItems(left2, right2) {
131440
+ const leftUpdatedAt = parseUpdatedAtMs(left2.updatedAt);
131441
+ const rightUpdatedAt = parseUpdatedAtMs(right2.updatedAt);
131442
+ if (leftUpdatedAt !== rightUpdatedAt) {
131443
+ return rightUpdatedAt - leftUpdatedAt;
131444
+ }
131445
+ return left2.title.localeCompare(right2.title);
131446
+ }
131447
+ function getHistoryCatalogStatus(existing) {
131448
+ if (!existing) return "available";
131449
+ return existing.meta.externalHistory?.status === "sync_conflict" ? "sync_conflict" : "imported";
131450
+ }
131451
+ function buildCatalogItem(provider2, info, existing) {
131452
+ const acpSessionId = info.sessionId;
131453
+ return {
131454
+ acpSessionId,
131455
+ title: resolveSessionTitle(info, provider2),
131456
+ updatedAt: info.updatedAt ?? void 0,
131457
+ importedSessionId: existing?.sessionId,
131458
+ status: getHistoryCatalogStatus(existing)
131459
+ };
131460
+ }
131461
+ function shouldSkipBySourceUpdatedAt(info, externalHistory) {
131462
+ if (externalHistory.status === "metadata_only") {
131463
+ return false;
131464
+ }
131465
+ if (!info.updatedAt || !externalHistory.sourceUpdatedAt) {
131466
+ return false;
131467
+ }
131468
+ const next = Date.parse(info.updatedAt);
131469
+ const current2 = Date.parse(externalHistory.sourceUpdatedAt);
131470
+ return Number.isFinite(next) && Number.isFinite(current2) && next <= current2;
131471
+ }
131472
+ function resolveSourceUpdatedAtMs(info, fallback2) {
131473
+ if (!info.updatedAt) {
131474
+ return fallback2;
131475
+ }
131476
+ const parsed = Date.parse(info.updatedAt);
131477
+ return Number.isFinite(parsed) ? parsed : fallback2;
131478
+ }
131479
+ function buildExternalHistoryMeta(args2) {
131480
+ return {
131481
+ provider: args2.provider,
131482
+ source: "local-acp-history",
131483
+ sourceAcpSessionId: args2.sourceAcpSessionId,
131484
+ sourceUpdatedAt: args2.sourceUpdatedAt ?? void 0,
131485
+ replayDigest: args2.materialized.replayDigest,
131486
+ importedTurnCount: args2.materialized.turnHashes.length,
131487
+ importedTurnHashes: args2.materialized.turnHashes,
131488
+ lastSyncAt: getServerNow(),
131489
+ status: args2.status ?? "synced",
131490
+ conflictReason: args2.conflictReason
131491
+ };
131492
+ }
131493
+ function buildMetadataOnlyExternalHistoryMeta(args2) {
131494
+ return {
131495
+ provider: args2.provider,
131496
+ source: "local-acp-history",
131497
+ sourceAcpSessionId: args2.sourceAcpSessionId,
131498
+ sourceUpdatedAt: args2.sourceUpdatedAt ?? void 0,
131499
+ importedTurnCount: 0,
131500
+ importedTurnHashes: [],
131501
+ lastSyncAt: getServerNow(),
131502
+ status: "metadata_only"
131503
+ };
131504
+ }
131505
+ class LocalProjectHistorySyncService {
131506
+ constructor(manager, logger2, context2, provider2) {
131507
+ this.manager = manager;
131508
+ this.logger = logger2;
131509
+ this.context = context2;
131510
+ this.provider = provider2;
131511
+ this.providerKey = getLocalProjectHistoryProviderKey(provider2);
131512
+ }
131513
+ provider;
131514
+ providerKey;
131515
+ async syncLocalProject(args2) {
131516
+ const leaseKey = `${this.providerKey}:${this.context.workspaceId}:${this.context.machineId}:${args2.localProjectId}`;
131517
+ if (syncLeases.has(leaseKey)) {
131518
+ throw new Error(`${getProviderLabel(this.provider)} history sync is already running for this local project`);
131519
+ }
131520
+ syncLeases.add(leaseKey);
131521
+ try {
131522
+ return await this.syncLocalProjectInner(args2);
131523
+ } finally {
131524
+ syncLeases.delete(leaseKey);
131525
+ }
131526
+ }
131527
+ async syncLocalProjectInner(args2) {
131528
+ const snapshot = await this.listCatalogSnapshot(args2.rootPath);
131529
+ return await this.writeCatalogResult({
131530
+ localProjectId: args2.localProjectId,
131531
+ sessions: snapshot.sessions,
131532
+ existingByAcpSessionId: snapshot.existingByAcpSessionId
131533
+ });
131534
+ }
131535
+ async importLocalProjectSessions(args2) {
131536
+ const leaseKey = `${this.providerKey}:${this.context.workspaceId}:${this.context.machineId}:${args2.localProjectId}`;
131537
+ if (syncLeases.has(leaseKey)) {
131538
+ throw new Error(`${getProviderLabel(this.provider)} history sync is already running for this local project`);
131539
+ }
131540
+ syncLeases.add(leaseKey);
131541
+ try {
131542
+ return await this.importLocalProjectSessionsInner(args2);
131543
+ } finally {
131544
+ syncLeases.delete(leaseKey);
131545
+ }
131546
+ }
131547
+ async importLocalProjectSessionsInner(args2) {
131548
+ const summary2 = emptySummary();
131549
+ const selectedIds = [
131550
+ ...new Set(args2.acpSessionIds)
131551
+ ];
131552
+ summary2.listed = selectedIds.length;
131553
+ const snapshot = await this.listCatalogSnapshot(args2.rootPath);
131554
+ const infoByAcpSessionId = new Map(snapshot.sessions.map((info) => [
131555
+ info.sessionId,
131556
+ info
131557
+ ]));
131558
+ const project = {
131559
+ kind: "local",
131560
+ localProjectId: args2.localProjectId
131561
+ };
131562
+ for (const selectedId of selectedIds) {
131563
+ const acpSessionId = selectedId;
131564
+ const info = infoByAcpSessionId.get(selectedId);
131565
+ try {
131566
+ if (!info) {
131567
+ throw new Error(`${getProviderLabel(this.provider)} session was not found in the local project catalog`);
131568
+ }
131569
+ const existing = snapshot.existingByAcpSessionId.get(selectedId);
131570
+ if (!existing) {
131571
+ const importedSession = await this.importNewSession({
131572
+ info,
131573
+ acpSessionId,
131574
+ project
131575
+ });
131576
+ snapshot.existingByAcpSessionId.set(selectedId, importedSession);
131577
+ summary2.imported += 1;
131578
+ continue;
131579
+ }
131580
+ const status = await this.refreshExistingSession({
131581
+ existing,
131582
+ info,
131583
+ acpSessionId,
131584
+ rootPath: args2.rootPath
131585
+ });
131586
+ summary2[status] += 1;
131587
+ } catch (error2) {
131588
+ summary2.failed += 1;
131589
+ summary2.failures.push({
131590
+ acpSessionId,
131591
+ message: formatErrorMessage(error2)
131592
+ });
131593
+ this.logger.debug(`[${this.providerKey}-history-sync] Failed to import ${getProviderLabel(this.provider)} session ${acpSessionId}: ${formatErrorMessage(error2)}`);
131594
+ }
131595
+ }
131596
+ const catalog = await this.writeCatalogResult({
131597
+ localProjectId: args2.localProjectId,
131598
+ sessions: snapshot.sessions,
131599
+ existingByAcpSessionId: snapshot.existingByAcpSessionId
131600
+ });
131601
+ return {
131602
+ summary: summary2,
131603
+ catalog
131604
+ };
131605
+ }
131606
+ async listCatalogSnapshot(rootPath) {
131607
+ const catalog = await listHistorySessionsForLocalProject({
131608
+ provider: this.provider,
131609
+ rootPath,
131610
+ logger: this.logger
131611
+ });
131612
+ const sessionMetas = await listWorkspaceSessionMetas(this.manager);
131613
+ const existingByAcpSessionId = buildExistingHistorySessionIndex(sessionMetas, this.context.machineId, this.provider);
131614
+ return {
131615
+ sessions: catalog.sessions,
131616
+ existingByAcpSessionId
131617
+ };
131618
+ }
131619
+ async writeCatalogResult(args2) {
131620
+ const lastListedAt = Math.round(getServerNow());
131621
+ const sessions = args2.sessions.map((info) => buildCatalogItem(this.provider, info, args2.existingByAcpSessionId.get(info.sessionId))).sort(compareCatalogItems);
131622
+ const catalog = {
131623
+ listed: sessions.length,
131624
+ lastListedAt,
131625
+ sessions
131626
+ };
131627
+ const machineRoomId = getMachineRoomId(this.context.machineId);
131628
+ await withMachineCatalogWriteLock(machineRoomId, async () => {
131629
+ const current2 = await this.manager.repo.getDocMeta(machineRoomId);
131630
+ const rawExisting = current2?.meta?.localProjects;
131631
+ const existing = rawExisting && typeof rawExisting === "object" ? rawExisting : {};
131632
+ const previous = existing[args2.localProjectId];
131633
+ if (!previous) {
131634
+ return;
131635
+ }
131636
+ await this.manager.repo.upsertDocMeta(machineRoomId, {
131637
+ localProjects: {
131638
+ ...existing,
131639
+ [args2.localProjectId]: {
131640
+ ...previous,
131641
+ history: {
131642
+ ...previous.history ?? {},
131643
+ [this.providerKey]: {
131644
+ lastListedAt,
131645
+ sessions: Object.fromEntries(sessions.map((item) => [
131646
+ item.acpSessionId,
131647
+ item
131648
+ ]))
131649
+ }
131650
+ }
131651
+ }
131652
+ }
131653
+ });
131654
+ });
131655
+ return catalog;
131656
+ }
131657
+ async importNewSession(args2) {
131658
+ const sessionId = v4();
131659
+ const roomId = getSessionRoomId(sessionId);
131660
+ const nowMs = getServerNow();
131661
+ const lastMessageAt = resolveSourceUpdatedAtMs(args2.info, nowMs);
131662
+ const meta = {
131663
+ id: sessionId,
131664
+ machineId: this.context.machineId,
131665
+ createdAt: new Date(nowMs).toISOString(),
131666
+ userId: this.context.userId,
131667
+ status: SessionStatusFactory.idle(),
131668
+ isArchived: false,
131669
+ origin: "external-acp",
131670
+ cliType: this.provider.cliType,
131671
+ agentType: this.provider.agentType,
131672
+ project: args2.project,
131673
+ title: resolveSessionTitle(args2.info, this.provider),
131674
+ lastMessageAt,
131675
+ externalHistory: buildMetadataOnlyExternalHistoryMeta({
131676
+ provider: this.provider,
131677
+ sourceAcpSessionId: args2.acpSessionId,
131678
+ sourceUpdatedAt: args2.info.updatedAt
131679
+ })
131680
+ };
131681
+ await this.manager.repo.upsertDocMeta(roomId, meta);
131682
+ return {
131683
+ sessionId,
131684
+ meta
131685
+ };
131686
+ }
131687
+ async refreshExistingSession(args2) {
131688
+ const externalHistory = args2.existing.meta.externalHistory;
131689
+ if (!externalHistory || getLocalProjectHistoryProviderKey(externalHistory.provider) !== this.providerKey) {
131690
+ return "skipped";
131691
+ }
131692
+ if (shouldSkipBySourceUpdatedAt(args2.info, externalHistory)) {
131693
+ return "skipped";
131694
+ }
131695
+ const replayNotifications = await loadHistorySessionReplay({
131696
+ provider: this.provider,
131697
+ rootPath: args2.rootPath,
131698
+ acpSessionId: args2.acpSessionId,
131699
+ logger: this.logger
131700
+ });
131701
+ const materialized = materializeReplay({
131702
+ provider: this.provider,
131703
+ acpSessionId: args2.acpSessionId,
131704
+ replayNotifications,
131705
+ userId: this.context.userId
131706
+ });
131707
+ const replayDecision = decideHistoryRefresh({
131708
+ externalHistory,
131709
+ replayDigest: materialized.replayDigest,
131710
+ turnHashes: materialized.turnHashes
131711
+ });
131712
+ if (replayDecision.reason === "digest_match") {
131713
+ await this.manager.repo.upsertDocMeta(getSessionRoomId(args2.existing.sessionId), {
131714
+ origin: "external-acp",
131715
+ externalHistory: buildExternalHistoryMeta({
131716
+ provider: this.provider,
131717
+ sourceAcpSessionId: args2.acpSessionId,
131718
+ sourceUpdatedAt: args2.info.updatedAt,
131719
+ materialized
131720
+ })
131721
+ });
131722
+ return "skipped";
131723
+ }
131724
+ if (replayDecision.status === "conflicted") {
131725
+ await this.markConflict(args2.existing.sessionId, args2.info, materialized, replayDecision.reason);
131726
+ return "conflicted";
131727
+ }
131728
+ const sessionDoc = await this.manager.getOrCreateSessionDoc(args2.existing.sessionId);
131729
+ const currentHistory = await sessionDoc.getHistory();
131730
+ const appendDecision = decideHistoryRefresh({
131731
+ externalHistory,
131732
+ replayDigest: materialized.replayDigest,
131733
+ turnHashes: materialized.turnHashes,
131734
+ currentHistoryLength: currentHistory.length
131735
+ });
131736
+ if (appendDecision.status === "conflicted") {
131737
+ await this.markConflict(args2.existing.sessionId, args2.info, materialized, appendDecision.reason);
131738
+ const synced2 = await sessionDoc.waitUntilSynced();
131739
+ if (!synced2) {
131740
+ this.logger.debug(`[${this.providerKey}-history-sync] Conflict marker for ${args2.existing.sessionId} did not confirm sync before unload; clients may see the previous state until next sync.`);
131741
+ }
131742
+ await this.manager.cleanSessionDoc(args2.existing.sessionId, {
131743
+ preserveStatus: true
131744
+ });
131745
+ return "conflicted";
131746
+ }
131747
+ const suffix = materialized.history.slice(appendDecision.appendFromIndex);
131748
+ await sessionDoc.updateHistory((history) => [
131749
+ ...history,
131750
+ ...suffix
131751
+ ]);
131752
+ await this.manager.repo.upsertDocMeta(getSessionRoomId(args2.existing.sessionId), {
131753
+ origin: "external-acp",
131754
+ lastMessageAt: resolveSourceUpdatedAtMs(args2.info, getServerNow()),
131755
+ externalHistory: buildExternalHistoryMeta({
131756
+ provider: this.provider,
131757
+ sourceAcpSessionId: args2.acpSessionId,
131758
+ sourceUpdatedAt: args2.info.updatedAt,
131759
+ materialized
131760
+ })
131761
+ });
131762
+ const synced = await sessionDoc.waitUntilSynced();
131763
+ if (!synced) {
131764
+ this.logger.debug(`[${this.providerKey}-history-sync] Appended history for ${args2.existing.sessionId} did not confirm sync before unload; other clients may see the previous state until next sync.`);
131765
+ }
131766
+ await this.manager.cleanSessionDoc(args2.existing.sessionId, {
131767
+ preserveStatus: true
131768
+ });
131769
+ return suffix.length > 0 ? "refreshed" : "skipped";
131770
+ }
131771
+ async markConflict(sessionId, info, materialized, reason) {
131772
+ await this.manager.repo.upsertDocMeta(getSessionRoomId(sessionId), {
131773
+ origin: "external-acp",
131774
+ externalHistory: buildExternalHistoryMeta({
131775
+ provider: this.provider,
131776
+ sourceAcpSessionId: info.sessionId,
131777
+ sourceUpdatedAt: info.updatedAt,
131778
+ materialized,
131779
+ status: "sync_conflict",
131780
+ conflictReason: reason
131781
+ })
131782
+ });
131783
+ }
131784
+ }
131785
+ const HISTORY_REQUEST_TYPES = /* @__PURE__ */ new Set([
131786
+ "local-project/sync-history",
131787
+ "local-project/import-history"
131788
+ ]);
131789
+ function isHistoryRequestType(value) {
131790
+ return HISTORY_REQUEST_TYPES.has(value);
131791
+ }
131792
+ function precheckLocalProjectHistoryRequest(args2) {
131793
+ const { request, expectedMachineId, expectedWorkspaceId } = args2;
131794
+ if (request.machineId !== expectedMachineId) {
131795
+ return {
131796
+ ok: false,
131797
+ error: "machine_mismatch",
131798
+ message: `Machine mismatch: expected ${expectedMachineId}`
131799
+ };
131800
+ }
131801
+ if (!isHistoryRequestType(request.type)) {
131802
+ return {
131803
+ ok: false,
131804
+ error: "invalid_request",
131805
+ message: `Unsupported remote local project control request: ${request.type}`
131806
+ };
131807
+ }
131808
+ const historyRequest = request;
131809
+ const requesterUserId = historyRequest.requestedByUserId?.trim();
131810
+ if (!requesterUserId) {
131811
+ return {
131812
+ ok: false,
131813
+ error: "invalid_request",
131814
+ message: "Local project history requests require requestedByUserId"
131815
+ };
131816
+ }
131817
+ if (historyRequest.workspaceId !== expectedWorkspaceId) {
131818
+ return {
131819
+ ok: false,
131820
+ error: "workspace_not_found",
131821
+ message: `Workspace mismatch: expected ${expectedWorkspaceId}`
131822
+ };
131823
+ }
131824
+ return {
131825
+ ok: true,
131826
+ request: historyRequest,
131827
+ requesterUserId
131828
+ };
131829
+ }
130741
131830
  const SESSION_IMAGE_MIME_TYPE_BY_EXTENSION = {
130742
131831
  png: "image/png",
130743
131832
  jpg: "image/jpeg",
@@ -130904,6 +131993,7 @@ ${escapeHtmlScriptContent(VISUAL_ANNOTATION_INSPECTOR_BROWSER_SCRIPT)}
130904
131993
  requestedByUserId,
130905
131994
  reason
130906
131995
  }),
131996
+ dispatchLocalProjectControl: async (request) => await this.dispatchLocalProjectControlViaRpc(request),
130907
131997
  onFatalAuthFailure: (error2) => this.onFatalAuthFailure?.(error2)
130908
131998
  });
130909
131999
  } else {
@@ -131789,6 +132879,7 @@ ${escapeHtmlScriptContent(VISUAL_ANNOTATION_INSPECTOR_BROWSER_SCRIPT)}
131789
132879
  cliVersion: this.cliVersion,
131790
132880
  os: process.platform,
131791
132881
  rpcVersion: supportsStreamsRpc ? LORO_STREAMS_RPC_VERSION : void 0,
132882
+ supportsLocalProjectHistoryRpc: supportsStreamsRpc,
131792
132883
  supportRegistryAgentTypes: this.supportRegistryAgentTypes,
131793
132884
  sessions: [],
131794
132885
  needToArchiveSessions: {},
@@ -132626,6 +133717,7 @@ ${escapeHtmlScriptContent(VISUAL_ANNOTATION_INSPECTOR_BROWSER_SCRIPT)}
132626
133717
  cliVersion: this.cliVersion,
132627
133718
  os: process.platform,
132628
133719
  rpcVersion: supportsStreamsRpc ? LORO_STREAMS_RPC_VERSION : machineMeta?.rpcVersion,
133720
+ supportsLocalProjectHistoryRpc: supportsStreamsRpc,
132629
133721
  supportRegistryAgentTypes: this.supportRegistryAgentTypes,
132630
133722
  acpCapabilities: machineMeta?.acpCapabilities,
132631
133723
  sessions: machineMeta?.sessions ?? [],
@@ -133266,8 +134358,74 @@ ${escapeHtmlScriptContent(VISUAL_ANNOTATION_INSPECTOR_BROWSER_SCRIPT)}
133266
134358
  }
133267
134359
  await this.workspaceDocument.registerMachine(this.machineId, {
133268
134360
  ...existingMeta,
133269
- rpcVersion: LORO_STREAMS_RPC_VERSION
134361
+ rpcVersion: LORO_STREAMS_RPC_VERSION,
134362
+ supportsLocalProjectHistoryRpc: true
134363
+ });
134364
+ }
134365
+ toLocalProjectControlError(type2, error2, message, data) {
134366
+ return {
134367
+ ok: false,
134368
+ type: type2,
134369
+ error: error2,
134370
+ message,
134371
+ data
134372
+ };
134373
+ }
134374
+ async dispatchLocalProjectControlViaRpc(message) {
134375
+ const requestType = message.type;
134376
+ const precheck = precheckLocalProjectHistoryRequest({
134377
+ request: message,
134378
+ expectedMachineId: this.machineId,
134379
+ expectedWorkspaceId: this.workspaceId
133270
134380
  });
134381
+ if (!precheck.ok) {
134382
+ return this.toLocalProjectControlError(requestType, precheck.error, precheck.message);
134383
+ }
134384
+ const { requesterUserId, request } = precheck;
134385
+ try {
134386
+ const access = await canUseMachineForCliToken({
134387
+ token: this.token,
134388
+ workspaceId: this.workspaceId,
134389
+ machineId: this.machineId,
134390
+ requesterUserId,
134391
+ localProjectId: request.localProjectId
134392
+ });
134393
+ if (!access.allowed) {
134394
+ return this.toLocalProjectControlError(requestType, "access_denied", `Machine access denied: ${access.reason}`);
134395
+ }
134396
+ const rootPath = await resolveWorkspaceLocalProjectRootPath(this.workspaceDocument.repo, this.machineId, request.localProjectId);
134397
+ if (!rootPath) {
134398
+ return this.toLocalProjectControlError(requestType, "local_project_not_found", `Local project not found in workspace: ${request.localProjectId}`);
134399
+ }
134400
+ const service = new LocalProjectHistorySyncService(this.workspaceDocument, this.logger, {
134401
+ workspaceId: this.workspaceId,
134402
+ machineId: this.machineId,
134403
+ userId: this.userId
134404
+ }, request.provider);
134405
+ if (request.type === "local-project/sync-history") {
134406
+ const result2 = await service.syncLocalProject({
134407
+ localProjectId: request.localProjectId,
134408
+ rootPath
134409
+ });
134410
+ return {
134411
+ ok: true,
134412
+ type: "local-project/sync-history",
134413
+ result: result2
134414
+ };
134415
+ }
134416
+ const result = await service.importLocalProjectSessions({
134417
+ localProjectId: request.localProjectId,
134418
+ rootPath,
134419
+ acpSessionIds: request.acpSessionIds
134420
+ });
134421
+ return {
134422
+ ok: true,
134423
+ type: "local-project/import-history",
134424
+ result
134425
+ };
134426
+ } catch (error2) {
134427
+ return this.toLocalProjectControlError(requestType, "execution_failed", formatErrorMessage(error2));
134428
+ }
133271
134429
  }
133272
134430
  cancelPendingPermissionRequests() {
133273
134431
  this.permissionRequestStartTimes.clear();
@@ -133400,8 +134558,15 @@ ${escapeHtmlScriptContent(VISUAL_ANNOTATION_INSPECTOR_BROWSER_SCRIPT)}
133400
134558
  if (meta.processingUserMsgId) {
133401
134559
  return true;
133402
134560
  }
134561
+ if (meta.lastGoalCommand) {
134562
+ return true;
134563
+ }
133403
134564
  return Boolean(meta.latestUserMsgId && meta.latestUserMsgId !== meta.lastHandledUserMsgId);
133404
134565
  }
134566
+ async hasActiveGoal(sessionId) {
134567
+ const meta = (await this.workspaceDocument.repo.getDocMeta(getSessionRoomId(sessionId)))?.meta;
134568
+ return isSessionGoalWorking(meta?.latestGoal);
134569
+ }
133405
134570
  isArchiveInFlight(sessionId) {
133406
134571
  return this.archiveInFlight.has(sessionId);
133407
134572
  }
@@ -133979,6 +135144,9 @@ ${escapeHtmlScriptContent(VISUAL_ANNOTATION_INSPECTOR_BROWSER_SCRIPT)}
133979
135144
  if (this.deps.hasActiveTurn(sessionId)) {
133980
135145
  return false;
133981
135146
  }
135147
+ if (await this.deps.hasActiveGoal(sessionId)) {
135148
+ return false;
135149
+ }
133982
135150
  if (this.deps.hasPendingUpdates(sessionId)) {
133983
135151
  return false;
133984
135152
  }
@@ -134118,6 +135286,7 @@ ${escapeHtmlScriptContent(VISUAL_ANNOTATION_INSPECTOR_BROWSER_SCRIPT)}
134118
135286
  this.gcManager = new SessionGCManager(gcConfig, {
134119
135287
  getSessionLastActivity: (sessionId) => handler.getLastActivity(sessionId),
134120
135288
  hasActiveTurn: (sessionId) => handler.hasActiveTurn(sessionId),
135289
+ hasActiveGoal: async (sessionId) => await handler.hasActiveGoal(sessionId),
134121
135290
  hasPendingUpdates: (sessionId) => handler.hasPendingUpdates(sessionId),
134122
135291
  hasPendingUserWork: async (sessionId) => await handler.hasPendingUserWork(sessionId),
134123
135292
  isArchiveInFlight: (sessionId) => handler.isArchiveInFlight(sessionId),
@@ -142115,6 +143284,18 @@ export PATH=${toSingleQuotedShellString(ghShimBinDir)}:"$PATH"
142115
143284
  }
142116
143285
  return typeof value.error === "string";
142117
143286
  }
143287
+ function isLocalProjectHistorySyncSummary(value) {
143288
+ 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");
143289
+ }
143290
+ function isLocalProjectHistoryCatalogItem(value) {
143291
+ 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");
143292
+ }
143293
+ function isLocalProjectHistoryCatalogResult(value) {
143294
+ 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);
143295
+ }
143296
+ function isLocalProjectHistoryImportResult(value) {
143297
+ return isObjectRecord(value) && isLocalProjectHistorySyncSummary(value.summary) && isLocalProjectHistoryCatalogResult(value.catalog);
143298
+ }
142118
143299
  function isWorkspaceIds(value) {
142119
143300
  return isStringArray(value);
142120
143301
  }
@@ -142146,6 +143327,12 @@ export PATH=${toSingleQuotedShellString(ghShimBinDir)}:"$PATH"
142146
143327
  if (value.type === "local-project/checkout-branch") {
142147
143328
  return isLocalProjectCheckoutBranchResult(value.result);
142148
143329
  }
143330
+ if (value.type === "local-project/sync-history") {
143331
+ return isLocalProjectHistoryCatalogResult(value.result);
143332
+ }
143333
+ if (value.type === "local-project/import-history") {
143334
+ return isLocalProjectHistoryImportResult(value.result);
143335
+ }
142149
143336
  return false;
142150
143337
  }
142151
143338
  const SESSION_CONTROL_PATH$2 = "/session-control";
@@ -142160,6 +143347,8 @@ export PATH=${toSingleQuotedShellString(ghShimBinDir)}:"$PATH"
142160
143347
  "local-project/list-files",
142161
143348
  "local-project/read-file",
142162
143349
  "local-project/checkout-branch",
143350
+ "local-project/sync-history",
143351
+ "local-project/import-history",
142163
143352
  "worktree/list-files",
142164
143353
  "worktree/read-file"
142165
143354
  ];
@@ -143433,10 +144622,12 @@ export PATH=${toSingleQuotedShellString(ghShimBinDir)}:"$PATH"
143433
144622
  const existing = rawExisting && typeof rawExisting === "object" ? rawExisting : {};
143434
144623
  const previous = existing[entry2.localProjectId];
143435
144624
  const nowMs = getServerNow();
143436
- await runtime.lody.documentManager.repo.upsertDocMeta(machineRoomId, {
144625
+ const repo = runtime.lody.documentManager.repo;
144626
+ await repo.upsertDocMeta(machineRoomId, {
143437
144627
  localProjects: {
143438
144628
  ...existing,
143439
144629
  [entry2.localProjectId]: {
144630
+ ...previous ?? {},
143440
144631
  id: entry2.localProjectId,
143441
144632
  name: entry2.name,
143442
144633
  rootPath: entry2.rootPath,
@@ -143458,7 +144649,8 @@ export PATH=${toSingleQuotedShellString(ghShimBinDir)}:"$PATH"
143458
144649
  ...existing
143459
144650
  };
143460
144651
  delete next[localProjectId];
143461
- await runtime.lody.documentManager.repo.upsertDocMeta(machineRoomId, {
144652
+ const repo = runtime.lody.documentManager.repo;
144653
+ await repo.upsertDocMeta(machineRoomId, {
143462
144654
  localProjects: next
143463
144655
  });
143464
144656
  }
@@ -143618,6 +144810,43 @@ export PATH=${toSingleQuotedShellString(ghShimBinDir)}:"$PATH"
143618
144810
  result: this.localProjectControlService.checkoutProjectBranch(rootPath, message.branchName)
143619
144811
  };
143620
144812
  }
144813
+ if (message.type === "local-project/sync-history") {
144814
+ const runtime = await this.resolveWorkspaceRuntime(message.workspaceId);
144815
+ const rootPath = await this.resolveWorkspaceProjectRootPath(runtime, message.localProjectId);
144816
+ const service = new LocalProjectHistorySyncService(runtime.lody.documentManager, this.logger, {
144817
+ workspaceId: message.workspaceId,
144818
+ machineId: this.machineId,
144819
+ userId: this.userId
144820
+ }, message.provider);
144821
+ const result = await service.syncLocalProject({
144822
+ localProjectId: message.localProjectId,
144823
+ rootPath
144824
+ });
144825
+ return {
144826
+ ok: true,
144827
+ type: "local-project/sync-history",
144828
+ result
144829
+ };
144830
+ }
144831
+ if (message.type === "local-project/import-history") {
144832
+ const runtime = await this.resolveWorkspaceRuntime(message.workspaceId);
144833
+ const rootPath = await this.resolveWorkspaceProjectRootPath(runtime, message.localProjectId);
144834
+ const service = new LocalProjectHistorySyncService(runtime.lody.documentManager, this.logger, {
144835
+ workspaceId: message.workspaceId,
144836
+ machineId: this.machineId,
144837
+ userId: this.userId
144838
+ }, message.provider);
144839
+ const result = await service.importLocalProjectSessions({
144840
+ localProjectId: message.localProjectId,
144841
+ rootPath,
144842
+ acpSessionIds: message.acpSessionIds
144843
+ });
144844
+ return {
144845
+ ok: true,
144846
+ type: "local-project/import-history",
144847
+ result
144848
+ };
144849
+ }
143621
144850
  if (message.type === "worktree/list-files") {
143622
144851
  return {
143623
144852
  ok: true,
@@ -175829,6 +177058,7 @@ ${result.stderr}`;
175829
177058
  this.lastExitCode = result.code;
175830
177059
  this.lastExitAtMs = Date.now();
175831
177060
  this.publishState();
177061
+ if (!this.triggered) return;
175832
177062
  if (this.restartPending) return;
175833
177063
  if (isAlreadyRunningOutcome(result)) {
175834
177064
  if (this.alreadyRunningIsFatal) {