lody 0.59.0 → 0.60.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -20,6 +20,7 @@ import require$$0$7, { errorMonitor, EventEmitter as EventEmitter$1 } from "node
20
20
  import * as diagch from "diagnostics_channel";
21
21
  import diagch__default from "diagnostics_channel";
22
22
  import * as net from "node:net";
23
+ import net__default from "node:net";
23
24
  import require$$1$4 from "async_hooks";
24
25
  import require$$1$5, { execFile, spawn as spawn$1, execFileSync } from "node:child_process";
25
26
  import fs$3, { readdir, readFile, createReadStream, existsSync, readFileSync as readFileSync$1, createWriteStream, rmSync, readdirSync, promises as promises$1 } from "node:fs";
@@ -47,7 +48,7 @@ import require$$1$6 from "string_decoder";
47
48
  import * as http$2 from "http";
48
49
  import http__default from "http";
49
50
  import require$$1$7 from "https";
50
- import { randomFillSync, randomUUID, createHash, randomBytes as randomBytes$1 } from "node:crypto";
51
+ import crypto$1, { randomFillSync, randomUUID, createHash, randomBytes as randomBytes$1 } from "node:crypto";
51
52
  import require$$0$a from "net";
52
53
  import require$$4$3 from "tls";
53
54
  import { i as imports, _ as __wbg_set_wasm$1, r as rawWasm, L as LoroDoc, E as EphemeralStoreWasm, U as UndoManager, c as callPendingEvents$3, a as LoroTree, b as LoroText, d as LoroMovableList, e as LoroList, f as LoroMap, g as __vite__initWasm, V as VersionVector, h as decodeImportBlobMeta, __tla as __tla_0 } from "./chunks/loro_wasm_bg-BV-n7JyC.js";
@@ -12304,7 +12305,7 @@ ${JSON.stringify(itemHeaders)}
12304
12305
  function createTransport(options, makeRequest, buffer2 = makePromiseBuffer(options.bufferSize || DEFAULT_TRANSPORT_BUFFER_SIZE)) {
12305
12306
  let rateLimits = {};
12306
12307
  const flush2 = (timeout2) => buffer2.drain(timeout2);
12307
- function send(envelope) {
12308
+ function send2(envelope) {
12308
12309
  const filteredEnvelopeItems = [];
12309
12310
  forEachEnvelopeItem(envelope, (item, type2) => {
12310
12311
  const dataCategory = envelopeItemTypeToDataCategory(type2);
@@ -12353,7 +12354,7 @@ ${JSON.stringify(itemHeaders)}
12353
12354
  });
12354
12355
  }
12355
12356
  return {
12356
- send,
12357
+ send: send2,
12357
12358
  flush: flush2
12358
12359
  };
12359
12360
  }
@@ -25954,7 +25955,7 @@ Event: ${getEventDescription(event)}`);
25954
25955
  const instrumentation2 = this;
25955
25956
  this._diag.debug("Patching fastify reply.send function");
25956
25957
  return function patchSend(original) {
25957
- return function send(...args2) {
25958
+ return function send2(...args2) {
25958
25959
  const maybeError = args2[0];
25959
25960
  if (!instrumentation2.isEnabled()) {
25960
25961
  return original.apply(this, args2);
@@ -27283,7 +27284,7 @@ Event: ${getEventDescription(event)}`);
27283
27284
  const transactionPromise = original.apply(this, args2);
27284
27285
  transactionPromise.then((transaction2) => {
27285
27286
  const originalSend = transaction2.send;
27286
- transaction2.send = function send(...args3) {
27287
+ transaction2.send = function send2(...args3) {
27287
27288
  return api_1.context.with(api_1.trace.setSpan(api_1.context.active(), transactionSpan), () => {
27288
27289
  const patched = instrumentation2._getSendPatch()(originalSend);
27289
27290
  return patched.apply(this, args3).catch((err2) => {
@@ -27369,7 +27370,7 @@ Event: ${getEventDescription(event)}`);
27369
27370
  _getSendPatch() {
27370
27371
  const instrumentation2 = this;
27371
27372
  return (original) => {
27372
- return function send(...args2) {
27373
+ return function send2(...args2) {
27373
27374
  const record2 = args2[0];
27374
27375
  const spans = record2.messages.map((message) => {
27375
27376
  return instrumentation2._startProducerSpan(record2.topic, message);
@@ -36827,7 +36828,7 @@ Mongoose Error Code: ${error2.code}` : ""}`
36827
36828
  return client;
36828
36829
  }
36829
36830
  const name$1 = "lody";
36830
- const version$4 = "0.59.0";
36831
+ const version$4 = "0.60.0";
36831
36832
  const description$1 = "Lody Agent CLI tool for managing remote command execution";
36832
36833
  const type$2 = "module";
36833
36834
  const main$4 = "dist/index.js";
@@ -36942,6 +36943,7 @@ Mongoose Error Code: ${error2.code}` : ""}`
36942
36943
  const dependencies$1 = {
36943
36944
  "better-sqlite3": "catalog:",
36944
36945
  "loro-crdt": "1.13.1",
36946
+ "node-pty": "1.0.0",
36945
36947
  "shell-env": "^4.0.3",
36946
36948
  "typescript": "^5.9.3"
36947
36949
  };
@@ -37137,6 +37139,30 @@ Task description:
37137
37139
  "image/webp",
37138
37140
  "image/gif"
37139
37141
  ];
37142
+ const IMAGE_MIME_TYPE_BY_EXTENSION = {
37143
+ png: "image/png",
37144
+ apng: "image/apng",
37145
+ jpg: "image/jpeg",
37146
+ jpeg: "image/jpeg",
37147
+ jfif: "image/jpeg",
37148
+ gif: "image/gif",
37149
+ webp: "image/webp",
37150
+ svg: "image/svg+xml",
37151
+ bmp: "image/bmp",
37152
+ ico: "image/x-icon",
37153
+ cur: "image/x-icon",
37154
+ avif: "image/avif"
37155
+ };
37156
+ function getImageMimeTypeForPath(pathOrName) {
37157
+ const lastDot = pathOrName.lastIndexOf(".");
37158
+ if (lastDot < 0 || lastDot === pathOrName.length - 1) return void 0;
37159
+ const extension2 = pathOrName.slice(lastDot + 1).trim().toLowerCase();
37160
+ return IMAGE_MIME_TYPE_BY_EXTENSION[extension2];
37161
+ }
37162
+ function isBinaryImagePath(pathOrName) {
37163
+ const mimeType = getImageMimeTypeForPath(pathOrName);
37164
+ return mimeType !== void 0 && mimeType !== "image/svg+xml";
37165
+ }
37140
37166
  const WORKSPACE_API_PATH_PREFIX = "/api/workspaces";
37141
37167
  const SESSION_IMAGE_UPLOAD_API_PATH = "/session-images/upload";
37142
37168
  const trimTrailingSlash = (url) => {
@@ -47263,7 +47289,11 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
47263
47289
  const LocalProjectFileReadResultSchema = object$1({
47264
47290
  path: string$1(),
47265
47291
  content: string$1(),
47266
- truncated: boolean()
47292
+ truncated: boolean(),
47293
+ encoding: _enum$1([
47294
+ "utf8",
47295
+ "base64"
47296
+ ]).optional()
47267
47297
  }).strict();
47268
47298
  const LocalProjectDirectoryListResultSchema = object$1({
47269
47299
  entries: array$2(object$1({
@@ -47777,6 +47807,8 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
47777
47807
  "acp_not_ready",
47778
47808
  "agent_disconnected",
47779
47809
  "turn_pre_prompt_failed",
47810
+ "message_delivery_failed",
47811
+ "machine_access_denied",
47780
47812
  "acp_auth_required",
47781
47813
  "acp_internal_error",
47782
47814
  "acp_upstream_api_error",
@@ -48123,7 +48155,35 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
48123
48155
  };
48124
48156
  }
48125
48157
  }
48126
- const REGISTRY_ACP_AGENTS = [
48158
+ const HARDCODED_REGISTRY_ACP_AGENTS = [
48159
+ {
48160
+ id: "claude-p",
48161
+ name: "Interactive Claude",
48162
+ version: "0.1.5",
48163
+ description: "Interactive Claude Code ACP runtime",
48164
+ distribution: {
48165
+ npx: {
48166
+ package: "acp-extension-claude-pty@0.1.5",
48167
+ registry: "https://registry.npmjs.org/",
48168
+ platformPackages: {
48169
+ darwin: {
48170
+ arm64: "acp-extension-claude-pty-darwin-arm64@0.1.5",
48171
+ x64: "acp-extension-claude-pty-darwin-x64@0.1.5"
48172
+ },
48173
+ linux: {
48174
+ arm64: "acp-extension-claude-pty-linux-arm64@0.1.5",
48175
+ x64: "acp-extension-claude-pty-linux-x64@0.1.5"
48176
+ },
48177
+ win32: {
48178
+ arm64: "acp-extension-claude-pty-win32-arm64@0.1.5",
48179
+ x64: "acp-extension-claude-pty-win32-x64@0.1.5"
48180
+ }
48181
+ }
48182
+ }
48183
+ }
48184
+ }
48185
+ ];
48186
+ const REMOTE_REGISTRY_ACP_AGENTS = [
48127
48187
  {
48128
48188
  id: "agoragentic-acp",
48129
48189
  name: "Agoragentic",
@@ -48946,6 +49006,10 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
48946
49006
  }
48947
49007
  }
48948
49008
  ];
49009
+ const REGISTRY_ACP_AGENTS = [
49010
+ ...HARDCODED_REGISTRY_ACP_AGENTS,
49011
+ ...REMOTE_REGISTRY_ACP_AGENTS
49012
+ ];
48949
49013
  const CodexShellCommandArraySchema = array$2(string$1()).min(1);
48950
49014
  const CodexToolRawInputSchema = object$1({
48951
49015
  command: union$7([
@@ -55617,6 +55681,7 @@ ${tailedOutput}` : null;
55617
55681
  if (brandId === "minimax") return "MM";
55618
55682
  if (session.cliType === "builtin" && session.agentType === "codex") return "CX";
55619
55683
  if (session.cliType === "builtin" && session.agentType === "claude") return "CC";
55684
+ if (session.cliType === "registry" && session.agentType === "claude-p") return "IC";
55620
55685
  const source = normalizeText(agentConfig?.name) || normalizeText(session.agentType) || normalizeText(session.cliType) || "ACP";
55621
55686
  const letters = source.replace(/[^a-zA-Z0-9]+/g, " ").split(" ").filter(Boolean).map((part) => part[0]).join("").slice(0, 2).toUpperCase();
55622
55687
  return letters || "ACP";
@@ -55631,6 +55696,7 @@ ${tailedOutput}` : null;
55631
55696
  if (brandId === "minimax") return "minimax";
55632
55697
  if (session.cliType === "builtin" && session.agentType === "codex") return "codex";
55633
55698
  if (session.cliType === "builtin" && session.agentType === "claude") return "claude";
55699
+ if (session.cliType === "registry" && session.agentType === "claude-p") return "claude";
55634
55700
  return "agent";
55635
55701
  }
55636
55702
  function buildLiveActivityConversationItems({ sessions, agentConfigs = [], currentUserId, defaultTitle, statusLabels, formatUpdatedAt, maxItems = DEFAULT_MAX_ITEMS }) {
@@ -55731,6 +55797,102 @@ ${tailedOutput}` : null;
55731
55797
  return normalizeBaseUrl$1(convexUrl);
55732
55798
  }
55733
55799
  }
55800
+ const TerminalDimensionsSchema = object$1({
55801
+ cols: number$3().int().positive(),
55802
+ rows: number$3().int().positive()
55803
+ });
55804
+ const TerminalSnapshotSchema = object$1({
55805
+ terminalId: string$1().min(1),
55806
+ title: string$1(),
55807
+ cwd: string$1().optional()
55808
+ });
55809
+ const TERMINAL_MAX_PER_SESSION = 8;
55810
+ const RequestIdSchema$1 = string$1().min(1).optional();
55811
+ const TerminalClientMessageSchema = discriminatedUnion("type", [
55812
+ object$1({
55813
+ type: literal$1("list"),
55814
+ requestId: RequestIdSchema$1,
55815
+ sessionId: string$1().min(1)
55816
+ }),
55817
+ object$1({
55818
+ type: literal$1("open"),
55819
+ requestId: RequestIdSchema$1,
55820
+ sessionId: string$1().min(1),
55821
+ cols: TerminalDimensionsSchema.shape.cols,
55822
+ rows: TerminalDimensionsSchema.shape.rows
55823
+ }),
55824
+ object$1({
55825
+ type: literal$1("attach"),
55826
+ requestId: RequestIdSchema$1,
55827
+ terminalId: string$1().min(1),
55828
+ cols: TerminalDimensionsSchema.shape.cols,
55829
+ rows: TerminalDimensionsSchema.shape.rows
55830
+ }),
55831
+ object$1({
55832
+ type: literal$1("input"),
55833
+ requestId: RequestIdSchema$1,
55834
+ terminalId: string$1().min(1),
55835
+ data: string$1()
55836
+ }),
55837
+ object$1({
55838
+ type: literal$1("resize"),
55839
+ requestId: RequestIdSchema$1,
55840
+ terminalId: string$1().min(1),
55841
+ cols: TerminalDimensionsSchema.shape.cols,
55842
+ rows: TerminalDimensionsSchema.shape.rows
55843
+ }),
55844
+ object$1({
55845
+ type: literal$1("close"),
55846
+ requestId: RequestIdSchema$1,
55847
+ terminalId: string$1().min(1)
55848
+ }),
55849
+ object$1({
55850
+ type: literal$1("close_session"),
55851
+ requestId: RequestIdSchema$1,
55852
+ sessionId: string$1().min(1)
55853
+ })
55854
+ ]);
55855
+ discriminatedUnion("type", [
55856
+ object$1({
55857
+ type: literal$1("terminals"),
55858
+ requestId: RequestIdSchema$1,
55859
+ sessionId: string$1().min(1),
55860
+ terminals: array$2(TerminalSnapshotSchema)
55861
+ }),
55862
+ object$1({
55863
+ type: literal$1("opened"),
55864
+ requestId: RequestIdSchema$1,
55865
+ terminalId: string$1().min(1),
55866
+ cwd: string$1().optional()
55867
+ }),
55868
+ object$1({
55869
+ type: literal$1("data"),
55870
+ requestId: RequestIdSchema$1,
55871
+ terminalId: string$1().min(1),
55872
+ data: string$1(),
55873
+ replay: boolean().optional()
55874
+ }),
55875
+ object$1({
55876
+ type: literal$1("title"),
55877
+ requestId: RequestIdSchema$1,
55878
+ terminalId: string$1().min(1),
55879
+ title: string$1()
55880
+ }),
55881
+ object$1({
55882
+ type: literal$1("exit"),
55883
+ requestId: RequestIdSchema$1,
55884
+ terminalId: string$1().min(1),
55885
+ exitCode: number$3().int(),
55886
+ signal: string$1().optional()
55887
+ }),
55888
+ object$1({
55889
+ type: literal$1("error"),
55890
+ requestId: RequestIdSchema$1,
55891
+ terminalId: string$1().min(1).optional(),
55892
+ code: string$1().min(1),
55893
+ message: string$1()
55894
+ })
55895
+ ]);
55734
55896
  const LOCAL_PROBE_PORT$1 = 17789;
55735
55897
  const LOCAL_SESSION_CONTROL_PORT = 17790;
55736
55898
  const IMAGE_UPLOAD_PATH = "/image-upload";
@@ -84135,17 +84297,17 @@ ${fromBody}`;
84135
84297
  return this.client.subscribeToConnectionState(cb);
84136
84298
  }
84137
84299
  }
84138
- const require$1 = createRequire(resolve$1("."));
84300
+ const require$2 = createRequire(resolve$1("."));
84139
84301
  var __create = Object.create;
84140
84302
  var __defProp = Object.defineProperty;
84141
84303
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
84142
84304
  var __getOwnPropNames = Object.getOwnPropertyNames;
84143
84305
  var __getProtoOf = Object.getPrototypeOf;
84144
84306
  var __hasOwnProp = Object.prototype.hasOwnProperty;
84145
- var __require = ((x) => typeof require$1 !== "undefined" ? require$1 : typeof Proxy !== "undefined" ? new Proxy(x, {
84146
- get: (a, b) => (typeof require$1 !== "undefined" ? require$1 : a)[b]
84307
+ var __require = ((x) => typeof require$2 !== "undefined" ? require$2 : typeof Proxy !== "undefined" ? new Proxy(x, {
84308
+ get: (a, b) => (typeof require$2 !== "undefined" ? require$2 : a)[b]
84147
84309
  }) : x)(function(x) {
84148
- if (typeof require$1 !== "undefined") return require$1.apply(this, arguments);
84310
+ if (typeof require$2 !== "undefined") return require$2.apply(this, arguments);
84149
84311
  throw Error('Dynamic require of "' + x + '" is not supported');
84150
84312
  });
84151
84313
  var __commonJS = (cb, mod2) => function __require2() {
@@ -110943,6 +111105,18 @@ ${this.stack.split("\n").slice(1).join("\n")}` : this.toString();
110943
111105
  }
110944
111106
  }
110945
111107
  };
111108
+ const DEFAULT_RECONNECT_BASE_DELAY_MS = 1e3;
111109
+ const DEFAULT_RECONNECT_MAX_DELAY_MS = 3e4;
111110
+ const RECONNECT_JITTER_FRACTION = 0.2;
111111
+ const computeLoroReconnectDelayMs = (attempt, options = {}) => {
111112
+ const safeAttempt = Number.isFinite(attempt) ? Math.max(0, Math.floor(attempt)) : 0;
111113
+ const baseDelayMs = Math.max(0, options.baseDelayMs ?? DEFAULT_RECONNECT_BASE_DELAY_MS);
111114
+ const maxDelayMs = Math.max(baseDelayMs, options.maxDelayMs ?? DEFAULT_RECONNECT_MAX_DELAY_MS);
111115
+ const exponentialDelay = Math.min(maxDelayMs, baseDelayMs * 2 ** safeAttempt);
111116
+ const random2 = options.random ?? Math.random;
111117
+ const jitter = exponentialDelay * RECONNECT_JITTER_FRACTION * (Math.min(1, Math.max(0, random2())) * 2 - 1);
111118
+ return Math.min(maxDelayMs, Math.max(0, Math.round(exponentialDelay + jitter)));
111119
+ };
110946
111120
  const isRecoverableMetaRoomStatus = (status) => status === "disconnected" || status === "error";
110947
111121
  class LoroConnectionRecoveryController {
110948
111122
  recoveryEvents = runSync(unbounded());
@@ -110962,6 +111136,7 @@ ${this.stack.split("\n").slice(1).join("\n")}` : this.toString();
110962
111136
  activeOperation = null;
110963
111137
  metaRoomReadyPending = 0;
110964
111138
  metaRoomReadySequence = 0;
111139
+ reconnectAttempt = 0;
110965
111140
  initialMetaSyncCompleted;
110966
111141
  isCleanedUp = false;
110967
111142
  constructor(options) {
@@ -110992,6 +111167,10 @@ ${this.stack.split("\n").slice(1).join("\n")}` : this.toString();
110992
111167
  }
110993
111168
  setTransportStatus(status) {
110994
111169
  this.transportStatus = status;
111170
+ if (this.isStreamsHealthy()) {
111171
+ this.resetReconnectBackoff("transport-connected");
111172
+ return;
111173
+ }
110995
111174
  if (status === "disconnected") {
110996
111175
  this.scheduleReconnect("transport-disconnected");
110997
111176
  }
@@ -111123,6 +111302,7 @@ ${this.stack.split("\n").slice(1).join("\n")}` : this.toString();
111123
111302
  this.markReconnectCompletionActive(event.completion);
111124
111303
  try {
111125
111304
  await this.runReconnect(event.reason, event.options);
111305
+ this.updateBackoffAfterReconnect(event.reason, event.options);
111126
111306
  this.resolveReconnectCompletion(event.completion);
111127
111307
  } catch (error2) {
111128
111308
  this.rejectReconnectCompletion(event.completion, error2);
@@ -111219,6 +111399,9 @@ ${this.stack.split("\n").slice(1).join("\n")}` : this.toString();
111219
111399
  this.metaRoomReadyPending = Math.max(0, this.metaRoomReadyPending - 1);
111220
111400
  }
111221
111401
  }
111402
+ if (this.isStreamsHealthy()) {
111403
+ this.resetReconnectBackoff("meta-room-joined");
111404
+ }
111222
111405
  }
111223
111406
  startAutoReconnectWatchdog() {
111224
111407
  const intervalMs = readTimeoutEnv("LODY_LORO_AUTO_RECONNECT_INTERVAL_MS", 3e4);
@@ -111241,8 +111424,13 @@ ${this.stack.split("\n").slice(1).join("\n")}` : this.toString();
111241
111424
  if (this.isCleanedUp || this.isStreamsHealthy() || this.reconnectTimer) {
111242
111425
  return;
111243
111426
  }
111244
- const delayMs = readTimeoutEnv("LODY_LORO_AUTO_RECONNECT_DELAY_MS", 1e3);
111245
- this.logger.debug(`[${this.workspaceId}] Scheduling Loro streams reconnect in ${delayMs}ms (reason=${reason}, transport=${this.transportStatus}, metaRoom=${this.metaRoomStatus ?? "unknown"})`);
111427
+ const baseDelayMs = readTimeoutEnv("LODY_LORO_AUTO_RECONNECT_DELAY_MS", DEFAULT_RECONNECT_BASE_DELAY_MS);
111428
+ const maxDelayMs = readTimeoutEnv("LODY_LORO_AUTO_RECONNECT_MAX_DELAY_MS", DEFAULT_RECONNECT_MAX_DELAY_MS);
111429
+ const delayMs = computeLoroReconnectDelayMs(this.reconnectAttempt, {
111430
+ baseDelayMs,
111431
+ maxDelayMs
111432
+ });
111433
+ this.logger.debug(`[${this.workspaceId}] Scheduling Loro streams reconnect in ${delayMs}ms (attempt=${this.reconnectAttempt + 1}, reason=${reason}, transport=${this.transportStatus}, metaRoom=${this.metaRoomStatus ?? "unknown"})`);
111246
111434
  this.reconnectTimer = setTimeout(() => {
111247
111435
  this.reconnectTimer = null;
111248
111436
  this.offer({
@@ -111258,6 +111446,28 @@ ${this.stack.split("\n").slice(1).join("\n")}` : this.toString();
111258
111446
  isStreamsHealthy() {
111259
111447
  return this.transportStatus === "connected" && this.metaRoomStatus === "joined";
111260
111448
  }
111449
+ resetReconnectBackoff(reason) {
111450
+ if (this.reconnectAttempt === 0) {
111451
+ return;
111452
+ }
111453
+ this.logger.debug(`[${this.workspaceId}] Resetting Loro streams reconnect backoff (reason=${reason}, attempts=${this.reconnectAttempt})`);
111454
+ this.reconnectAttempt = 0;
111455
+ }
111456
+ updateBackoffAfterReconnect(reason, options) {
111457
+ if (this.isCleanedUp) {
111458
+ return;
111459
+ }
111460
+ if (this.isStreamsHealthy()) {
111461
+ this.resetReconnectBackoff(`reconnect:${reason}`);
111462
+ return;
111463
+ }
111464
+ if (options.force) {
111465
+ this.reconnectAttempt = 0;
111466
+ return;
111467
+ }
111468
+ this.reconnectAttempt += 1;
111469
+ this.scheduleReconnect(`retry-after-${reason}`);
111470
+ }
111261
111471
  async runReconnect(reason, options) {
111262
111472
  if (this.isCleanedUp) {
111263
111473
  return;
@@ -111275,7 +111485,7 @@ ${this.stack.split("\n").slice(1).join("\n")}` : this.toString();
111275
111485
  }
111276
111486
  await this.ensureMetaRoomJoined(reason);
111277
111487
  await withTimeout$3(this.repo.reconnect({
111278
- resetBackoff: true,
111488
+ resetBackoff: options.force === true,
111279
111489
  timeout: timeoutMs
111280
111490
  }), timeoutMs, `Timeout waiting for Loro streams reconnect (workspace=${this.workspaceId})`);
111281
111491
  if (shouldLog) {
@@ -111314,6 +111524,7 @@ ${this.stack.split("\n").slice(1).join("\n")}` : this.toString();
111314
111524
  }
111315
111525
  this.initialMetaSyncCompleted = true;
111316
111526
  this.onMetaRoomReady();
111527
+ this.resetReconnectBackoff(`meta-room-ready:${reason}`);
111317
111528
  this.logger.debug(`[${this.workspaceId}] Loro meta room ready in ${Date.now() - startedAt}ms (reason=${reason})`);
111318
111529
  this.emitMetaRoomSynced(reason);
111319
111530
  } catch (error2) {
@@ -112784,16 +112995,6 @@ ${this.stack.split("\n").slice(1).join("\n")}` : this.toString();
112784
112995
  const key2 = getAcpCapabilityCacheKey(cliType, agentType);
112785
112996
  return meta?.acpCapabilities?.[key2];
112786
112997
  }
112787
- async sendMachineHeartbeat() {
112788
- if (!this.machine) {
112789
- return;
112790
- }
112791
- const startedAt = Date.now();
112792
- this.logger.debug(`[${this.workspaceId}] Machine heartbeat writing presence and meta`);
112793
- this.presenceRuntime?.writeMachineHeartbeat();
112794
- await withSlowOperationWarning(this.machine.sendHeartbeat(), this.logger, "machine.sendHeartbeat repo.upsertDocMeta(lastSeen)", this.workspaceId);
112795
- this.logger.debug(`[${this.workspaceId}] Machine heartbeat meta write completed (duration=${Date.now() - startedAt}ms)`);
112796
- }
112797
112998
  async restoreMachineDocument(machineId) {
112798
112999
  const machineRoomId = getMachineRoomId(machineId);
112799
113000
  await this.repo.restoreDoc(machineRoomId);
@@ -113337,9 +113538,6 @@ ${this.stack.split("\n").slice(1).join("\n")}` : this.toString();
113337
113538
  const current2 = await this.repo.getDocMeta(this.roomId);
113338
113539
  if (isLoroRepoDocDeleted(current2)) return;
113339
113540
  const currentMeta = current2?.meta;
113340
- await this.repo.upsertDocMeta(this.roomId, {
113341
- lastRunningSeen: getServerNow()
113342
- });
113343
113541
  this.presenceRuntime?.setSessionPresence({
113344
113542
  sessionId: this.sessionId,
113345
113543
  machineId: currentMeta?.machineId,
@@ -113760,11 +113958,6 @@ ${this.stack.split("\n").slice(1).join("\n")}` : this.toString();
113760
113958
  }
113761
113959
  });
113762
113960
  }
113763
- async sendHeartbeat() {
113764
- await this.repo.upsertDocMeta(this.roomId, {
113765
- lastSeen: getServerNow()
113766
- });
113767
- }
113768
113961
  }
113769
113962
  const DEFAULT_GATEWAY_BASE_URL = "https://streams-api-proxy.loro.dev";
113770
113963
  const JSON_RPC_VERSION$1 = "2.0";
@@ -127702,6 +127895,9 @@ ${error2.message}` : execaMessage;
127702
127895
  function resolveCodexPlatformPackage(platform2 = process.platform, arch = process.arch) {
127703
127896
  return CODEX_PLATFORM_PACKAGES[platform2]?.[arch];
127704
127897
  }
127898
+ function resolveRegistryNpxPackage(distribution, platform2 = process.platform, arch = process.arch) {
127899
+ return distribution.platformPackages?.[platform2]?.[arch] ?? distribution.package;
127900
+ }
127705
127901
  const DEFAULT_ACP_PATH_RELATIVE_DIRS = [
127706
127902
  ".local/bin",
127707
127903
  "bin",
@@ -127711,10 +127907,6 @@ ${error2.message}` : execaMessage;
127711
127907
  agent.id,
127712
127908
  agent
127713
127909
  ]));
127714
- const builtinTypeSet = /* @__PURE__ */ new Set([
127715
- "claude",
127716
- "codex"
127717
- ]);
127718
127910
  const moduleRequire = createRequire$1(import.meta.url);
127719
127911
  function isRecord$2(value) {
127720
127912
  return typeof value === "object" && value !== null;
@@ -127739,7 +127931,7 @@ ${error2.message}` : execaMessage;
127739
127931
  }
127740
127932
  }
127741
127933
  function resolveBuiltinACPSetting(agentType) {
127742
- if (!builtinTypeSet.has(agentType)) {
127934
+ if (!isBuiltinAgentType(agentType)) {
127743
127935
  throw new Error(`Unsupported builtin ACP type: ${agentType}`);
127744
127936
  }
127745
127937
  const builtinType = agentType;
@@ -127759,8 +127951,8 @@ ${error2.message}` : execaMessage;
127759
127951
  };
127760
127952
  }
127761
127953
  }
127762
- const codexPlatformPackage = builtinType === "codex" ? resolveCodexPlatformPackage() : void 0;
127763
- const packageName = codexPlatformPackage ?? setting.packageName;
127954
+ const platformPackage = builtinType === "codex" ? resolveCodexPlatformPackage() : void 0;
127955
+ const packageName = platformPackage ?? setting.packageName;
127764
127956
  const packageSpec = `${packageName}@${setting.version}`;
127765
127957
  const args2 = [
127766
127958
  ...builtinType === "codex" ? [
@@ -127787,7 +127979,7 @@ ${error2.message}` : execaMessage;
127787
127979
  return input2.customAcp ? `custom:${serializeCustomAcpLaunchSpec(input2.customAcp)}` : `custom:${input2.agentType}:unknown`;
127788
127980
  }
127789
127981
  if (input2.cliType === "builtin") {
127790
- if (builtinTypeSet.has(input2.agentType)) {
127982
+ if (isBuiltinAgentType(input2.agentType)) {
127791
127983
  const setting = BuiltinACPSetting[input2.agentType];
127792
127984
  return `${setting.packageName}@${setting.version}`;
127793
127985
  }
@@ -127821,11 +128013,16 @@ ${error2.message}` : execaMessage;
127821
128013
  };
127822
128014
  }
127823
128015
  if (agent.distribution.npx?.package) {
128016
+ const npxDistribution = agent.distribution.npx;
128017
+ const packageSpec = resolveRegistryNpxPackage(npxDistribution);
127824
128018
  const args2 = [
128019
+ ...npxDistribution.registry ? [
128020
+ `--registry=${npxDistribution.registry}`
128021
+ ] : [],
127825
128022
  NPX_CACHE_MODE_ARG,
127826
128023
  "-y",
127827
- agent.distribution.npx.package,
127828
- ...agent.distribution.npx.args ?? []
128024
+ packageSpec,
128025
+ ...npxDistribution.args ?? []
127829
128026
  ];
127830
128027
  return {
127831
128028
  status: {
@@ -127835,7 +128032,7 @@ ${error2.message}` : execaMessage;
127835
128032
  exec: {
127836
128033
  command: "npx",
127837
128034
  args: args2,
127838
- env: agent.distribution.npx.env
128035
+ env: npxDistribution.env
127839
128036
  }
127840
128037
  };
127841
128038
  }
@@ -131541,6 +131738,9 @@ ${exitStatus.output}` : `Worktree ${options.phase} failed at command "${stepComm
131541
131738
  }
131542
131739
  return out;
131543
131740
  }
131741
+ function shouldScrubClaudeAuthEnv(cliType, agentType) {
131742
+ return cliType === "builtin" && agentType === "claude" || cliType === "registry" && agentType === "claude-p";
131743
+ }
131544
131744
  function isSelectGroup(item) {
131545
131745
  return typeof item === "object" && item !== null && "group" in item;
131546
131746
  }
@@ -131596,7 +131796,7 @@ ${exitStatus.output}` : `Worktree ${options.phase} failed at command "${stepComm
131596
131796
  ...process.env,
131597
131797
  ...env2
131598
131798
  } : process.env;
131599
- const probeEnv = cliType === "builtin" && agentType === "claude" && env2 ? scrubInheritedClaudeAuthEnv(mergedProbeEnv, env2) : mergedProbeEnv;
131799
+ const probeEnv = shouldScrubClaudeAuthEnv(cliType, agentType) && env2 ? scrubInheritedClaudeAuthEnv(mergedProbeEnv, env2) : mergedProbeEnv;
131600
131800
  let capturedCommands;
131601
131801
  let commandsResolve;
131602
131802
  const commandsPromise = new Promise((resolve2) => {
@@ -132035,23 +132235,12 @@ The postId is ${normalizedFeedbackPostId}. Use the feedback-progress-reporter sk
132035
132235
  "diff",
132036
132236
  "--numstat",
132037
132237
  "--no-renames",
132038
- mergeBase
132238
+ mergeBase,
132239
+ "HEAD"
132039
132240
  ]);
132040
132241
  const baseDiff = parseGitNumstat(baseNumstat);
132041
- const trackedBasePaths = new Set(baseDiff.map((diff2) => diff2.filePath));
132042
- const untrackedPaths = (await listUntrackedFiles(runGit)).filter((filePath) => !trackedBasePaths.has(filePath));
132043
- const untrackedFileDiff = await buildUntrackedFileDiffs(untrackedPaths, options?.countWorkingTreeFileLines);
132044
- const baseDiffWithUntracked = [
132045
- ...baseDiff,
132046
- ...untrackedFileDiff
132047
- ];
132048
- const untrackedFileDiffByPath = new Map(untrackedFileDiff.map((diff2) => [
132049
- diff2.filePath,
132050
- diff2
132051
- ]));
132052
132242
  const baseDiffStats = {
132053
- allChange: sumLineChange(baseDiffWithUntracked),
132054
- files: baseDiffWithUntracked
132243
+ allChange: sumLineChange(baseDiff)
132055
132244
  };
132056
132245
  let commitFileDiff;
132057
132246
  if (options?.baseCommitHash) {
@@ -132075,6 +132264,12 @@ The postId is ${normalizedFeedbackPostId}. Use the feedback-progress-reporter sk
132075
132264
  });
132076
132265
  }
132077
132266
  const commitFileDiffPaths = new Set(commitFileDiff.map((diff2) => diff2.filePath));
132267
+ const untrackedPaths = await listUntrackedFiles(runGit);
132268
+ const untrackedFileDiff = await buildUntrackedFileDiffs(untrackedPaths, options?.countWorkingTreeFileLines);
132269
+ const untrackedFileDiffByPath = new Map(untrackedFileDiff.map((diff2) => [
132270
+ diff2.filePath,
132271
+ diff2
132272
+ ]));
132078
132273
  let turnUntrackedPaths = untrackedPaths.filter((filePath) => !commitFileDiffPaths.has(filePath));
132079
132274
  if (turnStartWorkingTreeDiff && turnUntrackedPaths.length > 0) {
132080
132275
  const currentHashes = await hashObjectPaths(runGit, turnUntrackedPaths);
@@ -133030,9 +133225,13 @@ $mem | ConvertTo-Json -Compress
133030
133225
  this.deps.logger.warn(`[${sessionId}] Code Collab host skipped: session has no workspace directory`);
133031
133226
  return _void;
133032
133227
  }
133033
- this.deps.logger.debug(`[${sessionId}] Code Collab host start requested by agent turn workspaceRoot=${workspaceRoot}`);
133228
+ const hostSessionId = session.getParentSessionId();
133229
+ this.deps.logger.debug(`[${sessionId}] Code Collab host start requested by agent turn workspaceRoot=${workspaceRoot}${hostSessionId === void 0 ? "" : ` host=${hostSessionId}`}`);
133034
133230
  return this.tryPromise(() => startCodeCollabHost({
133035
133231
  sessionId,
133232
+ ...hostSessionId === void 0 ? {} : {
133233
+ hostSessionId
133234
+ },
133036
133235
  workspaceRoot,
133037
133236
  project
133038
133237
  }));
@@ -133181,7 +133380,13 @@ $mem | ConvertTo-Json -Compress
133181
133380
  turnId: options.turnId,
133182
133381
  session: runtime?.session ?? options.session ?? null,
133183
133382
  baseCommitHash: runtime?.baseCommitHash ?? null,
133184
- fileDiff: markerResult?.fileDiff
133383
+ fileDiff: markerResult?.fileDiff,
133384
+ ...markerResult?.allChangesFileDiff === void 0 ? {} : {
133385
+ allChangesFileDiff: markerResult.allChangesFileDiff
133386
+ },
133387
+ ...runtime?.project === void 0 ? {} : {
133388
+ project: runtime.project
133389
+ }
133185
133390
  })));
133186
133391
  }).pipe(ensuring(sync(() => {
133187
133392
  self2.clearTurnCancellation(options.sessionId, options.turnId);
@@ -133268,7 +133473,13 @@ $mem | ConvertTo-Json -Compress
133268
133473
  turnId: options.runtime.turnId,
133269
133474
  session: options.runtime.session ?? null,
133270
133475
  baseCommitHash: options.runtime.baseCommitHash ?? null,
133271
- fileDiff: failedMarkerResult?.fileDiff
133476
+ fileDiff: failedMarkerResult?.fileDiff,
133477
+ ...failedMarkerResult?.allChangesFileDiff === void 0 ? {} : {
133478
+ allChangesFileDiff: failedMarkerResult.allChangesFileDiff
133479
+ },
133480
+ ...options.runtime.project === void 0 ? {} : {
133481
+ project: options.runtime.project
133482
+ }
133272
133483
  });
133273
133484
  } catch (persistError) {
133274
133485
  this.deps.logger.warn(`[${options.sessionId}] Failed to persist Code Collab fileDiff for failed turn: ${formatErrorMessage$1(persistError)}`);
@@ -133283,7 +133494,10 @@ $mem | ConvertTo-Json -Compress
133283
133494
  sessionId: options.sessionId,
133284
133495
  sessionDoc: options.sessionDoc,
133285
133496
  turnId: options.turnId,
133286
- fileDiff: markerResult?.fileDiff
133497
+ fileDiff: markerResult?.fileDiff,
133498
+ ...markerResult?.allChangesFileDiff === void 0 ? {} : {
133499
+ allChangesFileDiff: markerResult.allChangesFileDiff
133500
+ }
133287
133501
  });
133288
133502
  await this.handleTurnError(options.sessionId, options.sessionDoc);
133289
133503
  } catch (error2) {
@@ -133414,40 +133628,68 @@ $mem | ConvertTo-Json -Compress
133414
133628
  if (await stopIfTurnCancelled("ACP finalization")) {
133415
133629
  return;
133416
133630
  }
133417
- if (resolveProjectGitHubRepo(project)) {
133418
- const branchName = await this.deps.turnFinalization.syncSessionBranchName(sessionId, session);
133631
+ const githubProject = resolveProjectGitHubRepo(project);
133632
+ let branchName = null;
133633
+ let preferredStatsBaseBranch = project?.branch;
133634
+ if (githubProject) {
133635
+ branchName = await this.deps.turnFinalization.syncSessionBranchName(sessionId, session);
133419
133636
  if (await stopIfTurnCancelled("branch synchronization")) {
133420
133637
  return;
133421
133638
  }
133422
- await this.deps.turnFinalization.updateSessionDiffStats(sessionId, session, {
133423
- turnId,
133424
- baseCommitHash: baseCommitHash ?? void 0,
133425
- turnStartWorkingTreeDiff,
133426
- preferredBaseBranch: project?.branch
133427
- });
133428
- if (await stopIfTurnCancelled("diff recording")) {
133429
- return;
133430
- }
133431
133639
  try {
133432
- await this.deps.turnFinalization.detectAndAssociatePR({
133640
+ const detectedPr = await this.deps.turnFinalization.detectAndAssociatePR({
133433
133641
  sessionId,
133434
133642
  session,
133435
133643
  sessionDoc,
133436
133644
  project,
133437
133645
  branchName
133438
133646
  });
133647
+ preferredStatsBaseBranch = detectedPr?.baseBranch ?? preferredStatsBaseBranch;
133439
133648
  } catch (error2) {
133440
133649
  this.deps.logger.debug(`[${sessionId}] PR detection failed: ${formatErrorMessage$1(error2)}`);
133441
133650
  }
133442
133651
  if (await stopIfTurnCancelled("PR detection")) {
133443
133652
  return;
133444
133653
  }
133654
+ await this.deps.turnFinalization.updateSessionDiffStats(sessionId, session, {
133655
+ turnId,
133656
+ baseCommitHash: baseCommitHash ?? void 0,
133657
+ turnStartWorkingTreeDiff,
133658
+ preferredBaseBranch: preferredStatsBaseBranch
133659
+ });
133660
+ if (await stopIfTurnCancelled("diff recording")) {
133661
+ return;
133662
+ }
133663
+ }
133664
+ const codeCollabTurnMarkerResult = await runPromise(this.recordCodeCollabTurnMarkerEffect(sessionId, turnId, "turn:end", project, {
133665
+ baseCommitHash,
133666
+ turnStartWorkingTreeDiff
133667
+ }));
133668
+ await this.persistCodeCollabFileDiffForTurn({
133669
+ sessionId,
133670
+ sessionDoc,
133671
+ turnId,
133672
+ session,
133673
+ baseCommitHash,
133674
+ fileDiff: codeCollabTurnMarkerResult?.fileDiff,
133675
+ ...codeCollabTurnMarkerResult?.allChangesFileDiff === void 0 ? {} : {
133676
+ allChangesFileDiff: codeCollabTurnMarkerResult.allChangesFileDiff
133677
+ },
133678
+ ...project === void 0 ? {} : {
133679
+ project
133680
+ }
133681
+ });
133682
+ if (await stopIfTurnCancelled("Code Collab diff recording")) {
133683
+ return;
133684
+ }
133685
+ if (githubProject) {
133445
133686
  try {
133446
133687
  await this.deps.turnFinalization.autoCommitAndPushForPR({
133447
133688
  sessionId,
133448
133689
  session,
133449
133690
  sessionDoc,
133450
133691
  project,
133692
+ preferredBaseBranch: preferredStatsBaseBranch,
133451
133693
  userId,
133452
133694
  isTurnCancelled,
133453
133695
  abortSignal: ctx.abortSignal,
@@ -133466,18 +133708,6 @@ $mem | ConvertTo-Json -Compress
133466
133708
  this.captureStatusChanged(sessionId, "idle", void 0, "turn_completed");
133467
133709
  await this.captureTurnCompleted(sessionId, sessionDoc, turnId);
133468
133710
  this.deps.logger.info(`Session chat completed: ${sessionId}`);
133469
- const codeCollabTurnMarkerResult = await runPromise(this.recordCodeCollabTurnMarkerEffect(sessionId, turnId, "turn:end", project, {
133470
- baseCommitHash,
133471
- turnStartWorkingTreeDiff
133472
- }));
133473
- await this.persistCodeCollabFileDiffForTurn({
133474
- sessionId,
133475
- sessionDoc,
133476
- turnId,
133477
- session,
133478
- baseCommitHash,
133479
- fileDiff: codeCollabTurnMarkerResult?.fileDiff
133480
- });
133481
133711
  try {
133482
133712
  await sessionDoc.setLastMessageAt();
133483
133713
  } catch (error2) {
@@ -133505,18 +133735,14 @@ $mem | ConvertTo-Json -Compress
133505
133735
  }
133506
133736
  const currentMeta = current2?.meta;
133507
133737
  const currentDiffStats = currentMeta?.diffStats ?? (targetSessionId === sessionId ? meta?.diffStats : void 0);
133508
- const currentFiles = currentDiffStats?.files ?? [];
133509
133738
  const currentAllChange = currentDiffStats?.allChange;
133510
- const hasExistingStats = currentFiles.length > 0 || (currentAllChange?.add ?? 0) !== 0 || (currentAllChange?.del ?? 0) !== 0;
133739
+ const hasExistingStats = (currentAllChange?.add ?? 0) !== 0 || (currentAllChange?.del ?? 0) !== 0;
133511
133740
  if (hasExistingStats) {
133512
133741
  return;
133513
133742
  }
133514
133743
  await this.deps.workspaceDocument.repo.upsertDocMeta(targetRoomId, {
133515
133744
  diffStats: {
133516
- allChange: sumLineChange(fileDiff),
133517
- files: [
133518
- ...fileDiff
133519
- ]
133745
+ allChange: sumLineChange(fileDiff)
133520
133746
  }
133521
133747
  });
133522
133748
  } catch (error2) {
@@ -133551,14 +133777,38 @@ $mem | ConvertTo-Json -Compress
133551
133777
  }
133552
133778
  async persistCodeCollabFileDiffForTurn(options) {
133553
133779
  const fileDiff = options.fileDiff ?? [];
133554
- if (fileDiff.length === 0) {
133780
+ let normalizedCodeCollabFileDiff = [];
133781
+ if (fileDiff.length > 0) {
133782
+ normalizedCodeCollabFileDiff = options.session === void 0 || options.session === null ? [
133783
+ ...fileDiff
133784
+ ] : await this.normalizeCodeCollabFileDiffLineStats(options.session, options.baseCommitHash ?? null, fileDiff);
133785
+ options.sessionDoc.setLatestAssistantHistoryFileDiff(normalizedCodeCollabFileDiff, options.turnId);
133786
+ }
133787
+ if (options.allChangesFileDiff !== void 0 && !resolveProjectGitHubRepo(options.project)) {
133788
+ await this.upsertDiffStatsFromCodeCollabAllChanges(options.sessionId, options.sessionDoc, options.allChangesFileDiff);
133555
133789
  return;
133556
133790
  }
133557
- const normalizedCodeCollabFileDiff = options.session === void 0 || options.session === null ? [
133558
- ...fileDiff
133559
- ] : await this.normalizeCodeCollabFileDiffLineStats(options.session, options.baseCommitHash ?? null, fileDiff);
133560
- options.sessionDoc.setLatestAssistantHistoryFileDiff(normalizedCodeCollabFileDiff, options.turnId);
133561
- await this.upsertMissingDiffStatsFromCodeCollabFileDiff(options.sessionId, options.sessionDoc, normalizedCodeCollabFileDiff);
133791
+ if (normalizedCodeCollabFileDiff.length > 0) {
133792
+ await this.upsertMissingDiffStatsFromCodeCollabFileDiff(options.sessionId, options.sessionDoc, normalizedCodeCollabFileDiff);
133793
+ }
133794
+ }
133795
+ async upsertDiffStatsFromCodeCollabAllChanges(sessionId, sessionDoc, allChangesFileDiff) {
133796
+ try {
133797
+ const meta = await sessionDoc.getMetaState();
133798
+ const targetSessionId = meta?.parentSessionId ?? sessionId;
133799
+ const targetRoomId = getSessionRoomId(targetSessionId);
133800
+ const current2 = await this.deps.workspaceDocument.repo.getDocMeta(targetRoomId);
133801
+ if (isLoroRepoDocDeleted(current2)) {
133802
+ return;
133803
+ }
133804
+ await this.deps.workspaceDocument.repo.upsertDocMeta(targetRoomId, {
133805
+ diffStats: {
133806
+ allChange: sumLineChange(allChangesFileDiff)
133807
+ }
133808
+ });
133809
+ } catch (error2) {
133810
+ this.deps.logger.debug(`[${sessionId}] Failed to persist session diffStats from Code Collab All Changes: ${formatErrorMessage$1(error2)}`);
133811
+ }
133562
133812
  }
133563
133813
  async isFileMissingAtGitBase(session, options) {
133564
133814
  if (!options.baseCommitHash) {
@@ -133750,7 +134000,8 @@ $mem | ConvertTo-Json -Compress
133750
134000
  await this.setUserTurnStatus(sessionDoc, userTurnId, "processing");
133751
134001
  await this.upsertSessionMeta(sessionId, {
133752
134002
  latestUserMsgId: userTurnId,
133753
- processingUserMsgId: userTurnId
134003
+ processingUserMsgId: userTurnId,
134004
+ lastMissingHistoryUserMsgId: void 0
133754
134005
  });
133755
134006
  }
133756
134007
  async clearDispatchProcessing(sessionId) {
@@ -133814,7 +134065,8 @@ $mem | ConvertTo-Json -Compress
133814
134065
  await this.upsertSessionMeta(sessionId, {
133815
134066
  latestUserMsgId: this.resolveLatestUserMsgIdForTerminalTurn(existingMeta, userTurnId),
133816
134067
  lastHandledUserMsgId: userTurnId,
133817
- processingUserMsgId: void 0
134068
+ processingUserMsgId: void 0,
134069
+ lastMissingHistoryUserMsgId: void 0
133818
134070
  });
133819
134071
  }
133820
134072
  async markTurnFailed(sessionId, sessionDoc, userTurnId) {
@@ -133823,7 +134075,8 @@ $mem | ConvertTo-Json -Compress
133823
134075
  await this.upsertSessionMeta(sessionId, {
133824
134076
  latestUserMsgId: this.resolveLatestUserMsgIdForTerminalTurn(existingMeta, userTurnId),
133825
134077
  lastHandledUserMsgId: userTurnId,
133826
- processingUserMsgId: void 0
134078
+ processingUserMsgId: void 0,
134079
+ lastMissingHistoryUserMsgId: void 0
133827
134080
  });
133828
134081
  }
133829
134082
  resolveGitHubProjectBranch(meta, preferredBranch) {
@@ -135099,6 +135352,9 @@ $mem | ConvertTo-Json -Compress
135099
135352
  if (meta.lastGoalCommand?.id && meta.lastGoalCommand.id !== this.goalCommandSeenId.get(meta.id)) {
135100
135353
  return true;
135101
135354
  }
135355
+ if (meta.lastMissingHistoryUserMsgId && !meta.latestUserMsgId) {
135356
+ return false;
135357
+ }
135102
135358
  if (!meta.lastHandledUserMsgId) {
135103
135359
  return true;
135104
135360
  }
@@ -135126,7 +135382,7 @@ $mem | ConvertTo-Json -Compress
135126
135382
  const nextUserTurn = await this.findOrAwaitDispatchableTurn(sessionId, sessionDoc, meta);
135127
135383
  if (!nextUserTurn) {
135128
135384
  if (this.hasPendingUserTurnSignal(meta)) {
135129
- await this.markMissingUserTurnRecovery(sessionId, meta);
135385
+ await this.markMissingUserTurnRecovery(sessionId, sessionDoc, meta);
135130
135386
  } else {
135131
135387
  await this.markMessageQueueSignalChecked(sessionDoc, meta);
135132
135388
  }
@@ -135258,7 +135514,8 @@ $mem | ConvertTo-Json -Compress
135258
135514
  await this.deps.workspaceDocument.repo.upsertDocMeta?.(getSessionRoomId(sessionId), {
135259
135515
  latestUserMsgId: userTurnId,
135260
135516
  lastHandledUserMsgId: userTurnId,
135261
- processingUserMsgId: void 0
135517
+ processingUserMsgId: void 0,
135518
+ lastMissingHistoryUserMsgId: void 0
135262
135519
  });
135263
135520
  await sessionDoc.setStatus(SessionStatusFactory.idle());
135264
135521
  await this.deps.recordChatFailure?.(sessionDoc, "machine_access_denied", message);
@@ -135579,7 +135836,7 @@ $mem | ConvertTo-Json -Compress
135579
135836
  }
135580
135837
  });
135581
135838
  }
135582
- async markMissingUserTurnRecovery(sessionId, previousMeta) {
135839
+ async markMissingUserTurnRecovery(sessionId, sessionDoc, previousMeta) {
135583
135840
  const roomId = getSessionRoomId(sessionId);
135584
135841
  let meta = previousMeta;
135585
135842
  try {
@@ -135596,13 +135853,20 @@ $mem | ConvertTo-Json -Compress
135596
135853
  const pendingUserMsgId = meta.processingUserMsgId ?? meta.latestUserMsgId ?? meta.lastHandledUserMsgId;
135597
135854
  const recoveryPatch = {
135598
135855
  status: SessionStatusFactory.idle(),
135856
+ latestUserMsgId: void 0,
135599
135857
  processingUserMsgId: void 0
135600
135858
  };
135601
135859
  if (pendingUserMsgId) {
135602
- recoveryPatch.lastHandledUserMsgId = pendingUserMsgId;
135603
- recoveryPatch.latestUserMsgId = pendingUserMsgId;
135860
+ recoveryPatch.lastMissingHistoryUserMsgId = pendingUserMsgId;
135604
135861
  }
135605
135862
  await this.deps.workspaceDocument.repo.upsertDocMeta?.(roomId, recoveryPatch);
135863
+ if (this.deps.recordChatFailure) {
135864
+ try {
135865
+ await this.deps.recordChatFailure(sessionDoc, "message_delivery_failed", "The user message could not be delivered because its history payload did not sync to this machine. Please resend it after sync recovers.");
135866
+ } catch (error2) {
135867
+ this.deps.logger.debug(`[${sessionId}] Failed to record missing-history delivery failure: ${formatErrorMessage$1(error2)}`);
135868
+ }
135869
+ }
135606
135870
  const watched = this.watchedSessions.get(sessionId);
135607
135871
  watched?.unsubscribe();
135608
135872
  this.watchedSessions.delete(sessionId);
@@ -135729,7 +135993,8 @@ $mem | ConvertTo-Json -Compress
135729
135993
  "CLOSED",
135730
135994
  "MERGED"
135731
135995
  ]),
135732
- headRefName: string$1()
135996
+ headRefName: string$1(),
135997
+ baseRefName: string$1()
135733
135998
  });
135734
135999
  const GhPrListSchema = array$2(GhPrListItemSchema);
135735
136000
  async function detectPullRequestForBranch(options) {
@@ -135746,7 +136011,7 @@ $mem | ConvertTo-Json -Compress
135746
136011
  "--head",
135747
136012
  branchName,
135748
136013
  "--json",
135749
- "number,url,state,headRefName",
136014
+ "number,url,state,headRefName,baseRefName",
135750
136015
  "--limit",
135751
136016
  "5"
135752
136017
  ], workdir, false);
@@ -135772,6 +136037,7 @@ $mem | ConvertTo-Json -Compress
135772
136037
  prNumber: pr.number,
135773
136038
  prUrl: pr.url,
135774
136039
  branch: branchName,
136040
+ baseBranch: pr.baseRefName,
135775
136041
  status
135776
136042
  };
135777
136043
  } catch (error2) {
@@ -135838,8 +136104,7 @@ $mem | ConvertTo-Json -Compress
135838
136104
  allChange: {
135839
136105
  add: 0,
135840
136106
  del: 0
135841
- },
135842
- files: []
136107
+ }
135843
136108
  };
135844
136109
  try {
135845
136110
  const preferredBaseBranch = resolveBaseBranchPreference({
@@ -135889,11 +136154,7 @@ $mem | ConvertTo-Json -Compress
135889
136154
  const { sessionId, session, sessionDoc, project, branchName } = ctx;
135890
136155
  const githubRepo = resolveProjectGitHubRepo(project);
135891
136156
  if (!githubRepo) {
135892
- return;
135893
- }
135894
- const workspace = await this.resolveWorkspaceSessionContext(sessionId, sessionDoc);
135895
- if (workspace.pullRequests.some((pr) => pr.status === "open")) {
135896
- return;
136157
+ return null;
135897
136158
  }
135898
136159
  const workdir = session.getWorkdir();
135899
136160
  const detected = await detectPullRequestForBranch({
@@ -135903,8 +136164,15 @@ $mem | ConvertTo-Json -Compress
135903
136164
  branchName: branchName ?? void 0,
135904
136165
  logger: this.deps.logger
135905
136166
  });
135906
- if (!detected || !this.deps.authBaseUrl) {
135907
- return;
136167
+ if (!detected) {
136168
+ return null;
136169
+ }
136170
+ const workspace = await this.resolveWorkspaceSessionContext(sessionId, sessionDoc);
136171
+ if (workspace.pullRequests.some((pr) => pr.status === "open")) {
136172
+ return detected;
136173
+ }
136174
+ if (!this.deps.authBaseUrl) {
136175
+ return detected;
135908
136176
  }
135909
136177
  try {
135910
136178
  const actionUrl = new URL("/api/action", this.deps.authBaseUrl).toString();
@@ -135931,12 +136199,12 @@ $mem | ConvertTo-Json -Compress
135931
136199
  if (!res.ok) {
135932
136200
  const text = await res.text().catch(() => "");
135933
136201
  this.deps.logger.debug(`[${sessionId}] PR association backend call failed (${res.status}): ${text || "no body"}`);
135934
- return;
136202
+ return detected;
135935
136203
  }
135936
136204
  this.deps.logger.debug(`[${sessionId}] Associated PR #${detected.prNumber} with session`);
135937
136205
  } catch (error2) {
135938
136206
  this.deps.logger.debug(`[${sessionId}] PR association backend call failed: ${formatErrorMessage$1(error2)}`);
135939
- return;
136207
+ return detected;
135940
136208
  }
135941
136209
  const reportedAt = (/* @__PURE__ */ new Date()).toISOString();
135942
136210
  try {
@@ -135951,6 +136219,7 @@ $mem | ConvertTo-Json -Compress
135951
136219
  } catch (error2) {
135952
136220
  this.deps.logger.debug(`[${sessionId}] Failed to write PR to local doc: ${formatErrorMessage$1(error2)}`);
135953
136221
  }
136222
+ return detected;
135954
136223
  }
135955
136224
  async autoCommitAndPushForPR(ctx) {
135956
136225
  const { sessionId, session, sessionDoc, project } = ctx;
@@ -135969,6 +136238,7 @@ $mem | ConvertTo-Json -Compress
135969
136238
  if (workspace.pullRequests.length === 0) {
135970
136239
  return;
135971
136240
  }
136241
+ const preferredBaseBranch = ctx.preferredBaseBranch ?? project?.branch;
135972
136242
  const workdir = session.getWorkdir();
135973
136243
  const runGit = (args2) => session.exec("git", args2, workdir, false);
135974
136244
  for (let attempt = 0; attempt < 2; attempt++) {
@@ -136006,7 +136276,7 @@ $mem | ConvertTo-Json -Compress
136006
136276
  turnId: result.turnId,
136007
136277
  baseCommitHash: result.baseCommitHash ?? void 0,
136008
136278
  turnStartWorkingTreeDiff: result.turnStartWorkingTreeDiff,
136009
- preferredBaseBranch: project?.branch
136279
+ preferredBaseBranch
136010
136280
  });
136011
136281
  } catch (error2) {
136012
136282
  if (shouldStop("commit prompt failure")) {
@@ -136051,7 +136321,7 @@ $mem | ConvertTo-Json -Compress
136051
136321
  turnId: result.turnId,
136052
136322
  baseCommitHash: result.baseCommitHash ?? void 0,
136053
136323
  turnStartWorkingTreeDiff: result.turnStartWorkingTreeDiff,
136054
- preferredBaseBranch: project?.branch
136324
+ preferredBaseBranch
136055
136325
  });
136056
136326
  } catch (error2) {
136057
136327
  if (shouldStop("push prompt failure")) {
@@ -141278,7 +141548,6 @@ $mem | ConvertTo-Json -Compress
141278
141548
  return parseFileId(key2).fileId;
141279
141549
  }
141280
141550
  function normalizeCurrentChangeValue(value) {
141281
- if (value === true) return true;
141282
141551
  if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
141283
141552
  const record2 = value;
141284
141553
  const a = parseOptionalLineCount(record2.a);
@@ -141308,7 +141577,7 @@ $mem | ConvertTo-Json -Compress
141308
141577
  n: true
141309
141578
  } : {}
141310
141579
  };
141311
- return Object.keys(normalized).length === 0 ? true : normalized;
141580
+ return Object.keys(normalized).length === 0 ? void 0 : normalized;
141312
141581
  }
141313
141582
  function parseOptionalOpId(value) {
141314
141583
  if (typeof value !== "string" || !/^\d+:\d+$/u.test(value)) return void 0;
@@ -142402,7 +142671,8 @@ $mem | ConvertTo-Json -Compress
142402
142671
  "diff",
142403
142672
  "--numstat",
142404
142673
  "--no-renames",
142405
- stats.mergeBase
142674
+ stats.mergeBase,
142675
+ "HEAD"
142406
142676
  ]));
142407
142677
  const tracked = await Promise.all(trackedFileDiffs.map(async (fileDiff) => {
142408
142678
  const baseText = await readGitTextAtMergeBase(runGit, stats.mergeBase, fileDiff.filePath);
@@ -142447,17 +142717,7 @@ $mem | ConvertTo-Json -Compress
142447
142717
  }
142448
142718
  };
142449
142719
  }));
142450
- const trackedPaths = new Set(trackedFileDiffs.map((file2) => file2.filePath));
142451
- const untracked = (await listUntrackedFiles(runGit)).filter((filePath) => !trackedPaths.has(filePath)).map((filePath) => ({
142452
- path: filePath,
142453
- base: {
142454
- status: "missing"
142455
- }
142456
- }));
142457
- return [
142458
- ...tracked,
142459
- ...untracked
142460
- ];
142720
+ return tracked;
142461
142721
  }
142462
142722
  class TurnFileChangeTracker {
142463
142723
  #base = /* @__PURE__ */ new Map();
@@ -143607,7 +143867,7 @@ $mem | ConvertTo-Json -Compress
143607
143867
  await m.__tla;
143608
143868
  return m;
143609
143869
  }),
143610
- import("./chunks/index-dgDvn87c.js").then(async (m) => {
143870
+ import("./chunks/index-Dr2JkTB2.js").then(async (m) => {
143611
143871
  await m.__tla;
143612
143872
  return m;
143613
143873
  })
@@ -143634,6 +143894,7 @@ $mem | ConvertTo-Json -Compress
143634
143894
  host: input2.host
143635
143895
  }),
143636
143896
  computeHostTurnChanges: async (input2) => await codeCollab.computeHostTurnChanges(input2),
143897
+ computeHostTurnFileDiff: async (input2) => await codeCollab.computeHostTurnFileDiff(input2),
143637
143898
  publishHostCurrentChanges: async (input2) => await codeCollab.publishHostCurrentChanges(input2)
143638
143899
  };
143639
143900
  }
@@ -144376,16 +144637,41 @@ parentPort.on('message', (message) => {
144376
144637
  lastActivityAtMs = /* @__PURE__ */ new Map();
144377
144638
  idleTimers = /* @__PURE__ */ new Map();
144378
144639
  pendingWorkCounts = /* @__PURE__ */ new Map();
144640
+ pendingWorkWaiters = /* @__PURE__ */ new Map();
144379
144641
  hostStreamsTokenCache = /* @__PURE__ */ new Map();
144380
144642
  hostStreamsTokenInFlight = /* @__PURE__ */ new Map();
144381
144643
  bootstrapSecretCache = /* @__PURE__ */ new Map();
144382
144644
  disabledLogged = false;
144645
+ hostSessionAliases = /* @__PURE__ */ new Map();
144646
+ publishCurrentChangesTails = /* @__PURE__ */ new Map();
144383
144647
  codeCollabTurnTrackers = /* @__PURE__ */ new Map();
144384
144648
  activeCodeCollabTurnTrackers = /* @__PURE__ */ new Map();
144385
144649
  turnHistoryLineStatsWorkerPool;
144386
144650
  initialTextSnapshotWorkerPool;
144387
144651
  textUpdateWorkerPool;
144388
144652
  textFrontierCheckoutWorkerPool;
144653
+ resolveHostSessionId(sessionId) {
144654
+ return this.hostSessionAliases.get(sessionId) ?? sessionId;
144655
+ }
144656
+ registerHostSessionAlias(sessionId, hostSessionId) {
144657
+ if (hostSessionId === void 0 || hostSessionId === sessionId) {
144658
+ return this.resolveHostSessionId(sessionId);
144659
+ }
144660
+ if (this.hostSessionAliases.get(sessionId) !== hostSessionId) {
144661
+ this.hostSessionAliases.set(sessionId, hostSessionId);
144662
+ this.hostStates.delete(sessionId);
144663
+ }
144664
+ return hostSessionId;
144665
+ }
144666
+ sessionIdsOnHost(hostSessionId) {
144667
+ const ids2 = [
144668
+ hostSessionId
144669
+ ];
144670
+ for (const [sessionId, host] of this.hostSessionAliases) {
144671
+ if (host === hostSessionId) ids2.push(sessionId);
144672
+ }
144673
+ return ids2;
144674
+ }
144389
144675
  recordAgentFileWrite(sessionId, filePath, oldContent, newContent) {
144390
144676
  if (!this.config.cliConfig.enabled) return;
144391
144677
  const normalizedFilePath = this.normalizeSessionWritePath(sessionId, filePath);
@@ -144468,10 +144754,11 @@ parentPort.on('message', (message) => {
144468
144754
  }
144469
144755
  }
144470
144756
  sessionTracker(sessionId) {
144471
- let tracker = this.codeCollabTurnTrackers.get(sessionId);
144757
+ const hostSessionId = this.resolveHostSessionId(sessionId);
144758
+ let tracker = this.codeCollabTurnTrackers.get(hostSessionId);
144472
144759
  if (tracker === void 0) {
144473
144760
  tracker = new TurnFileChangeTracker();
144474
- this.codeCollabTurnTrackers.set(sessionId, tracker);
144761
+ this.codeCollabTurnTrackers.set(hostSessionId, tracker);
144475
144762
  }
144476
144763
  return tracker;
144477
144764
  }
@@ -144521,10 +144808,15 @@ parentPort.on('message', (message) => {
144521
144808
  if (!record2 || record2.type !== "file_changed") return;
144522
144809
  const filePath = normalizeWatchedTurnPath(record2.path);
144523
144810
  if (filePath === void 0) return;
144524
- const activeTurn = this.activeCodeCollabTurnTrackers.get(sessionId);
144525
- if (activeTurn === void 0) return;
144526
- const bases = this.turnSnapshotTextBases.get(turnSnapshotKey(sessionId, activeTurn.turnId));
144527
- const oldText = bases?.get(filePath);
144811
+ const hostSessionId = this.resolveHostSessionId(sessionId);
144812
+ let oldText;
144813
+ for (const candidateSessionId of this.sessionIdsOnHost(hostSessionId)) {
144814
+ const activeTurn = this.activeCodeCollabTurnTrackers.get(candidateSessionId);
144815
+ if (activeTurn === void 0) continue;
144816
+ const bases = this.turnSnapshotTextBases.get(turnSnapshotKey(candidateSessionId, activeTurn.turnId));
144817
+ oldText = bases?.get(filePath);
144818
+ if (oldText !== void 0) break;
144819
+ }
144528
144820
  if (oldText === void 0) return;
144529
144821
  const normalized = this.normalizeSessionWritePath(sessionId, filePath);
144530
144822
  if (normalized === void 0) return;
@@ -144543,6 +144835,15 @@ parentPort.on('message', (message) => {
144543
144835
  reason: this.config.cliConfig.reason
144544
144836
  };
144545
144837
  }
144838
+ if (input2.hostSessionId !== void 0 && input2.hostSessionId !== input2.sessionId) {
144839
+ this.registerHostSessionAlias(input2.sessionId, input2.hostSessionId);
144840
+ this.logCodeCollabDebug(`[${input2.sessionId}] Code Collab host start aliased to parent host=${input2.hostSessionId}`);
144841
+ const { hostSessionId, sessionId: _childSessionId, ...rest } = input2;
144842
+ return await this.startForSession({
144843
+ ...rest,
144844
+ sessionId: hostSessionId
144845
+ });
144846
+ }
144546
144847
  const existing = this.activeHosts.get(input2.sessionId);
144547
144848
  if (existing) {
144548
144849
  this.touchActivity(input2.sessionId);
@@ -144568,6 +144869,22 @@ parentPort.on('message', (message) => {
144568
144869
  return await start2;
144569
144870
  }
144570
144871
  stopSession(sessionId, reason = "session stopped") {
144872
+ const aliasedHostSessionId = this.hostSessionAliases.get(sessionId);
144873
+ if (aliasedHostSessionId !== void 0 && aliasedHostSessionId !== sessionId) {
144874
+ this.hostSessionAliases.delete(sessionId);
144875
+ this.activeCodeCollabTurnTrackers.delete(sessionId);
144876
+ this.clearTurnSnapshotsForSession(sessionId);
144877
+ this.hostStates.delete(sessionId);
144878
+ this.logCodeCollabDebug(`[${sessionId}] Code Collab child session detached from host=${aliasedHostSessionId} (${reason})`);
144879
+ return;
144880
+ }
144881
+ for (const childSessionId of this.sessionIdsOnHost(sessionId)) {
144882
+ if (childSessionId === sessionId) continue;
144883
+ this.hostSessionAliases.delete(childSessionId);
144884
+ this.activeCodeCollabTurnTrackers.delete(childSessionId);
144885
+ this.clearTurnSnapshotsForSession(childSessionId);
144886
+ this.hostStates.delete(childSessionId);
144887
+ }
144571
144888
  this.clearIdleTimer(sessionId);
144572
144889
  if (this.startInFlight.has(sessionId)) {
144573
144890
  this.stopRequestedWhileStarting.set(sessionId, reason);
@@ -144591,7 +144908,7 @@ parentPort.on('message', (message) => {
144591
144908
  this.activeCodeCollabTurnTrackers.delete(sessionId);
144592
144909
  this.clearTurnSnapshotsForSession(sessionId);
144593
144910
  this.lastActivityAtMs.delete(sessionId);
144594
- this.pendingWorkCounts.delete(sessionId);
144911
+ this.clearPendingWork(sessionId);
144595
144912
  this.disposeTurnHistoryLineStatsWorkerPoolIfIdle();
144596
144913
  try {
144597
144914
  host.running.stop();
@@ -144636,7 +144953,7 @@ parentPort.on('message', (message) => {
144636
144953
  ];
144637
144954
  }
144638
144955
  getSessionState(sessionId) {
144639
- const state2 = this.hostStates.get(sessionId);
144956
+ const state2 = this.hostStates.get(sessionId) ?? this.hostStates.get(this.resolveHostSessionId(sessionId));
144640
144957
  if (state2) {
144641
144958
  return state2;
144642
144959
  }
@@ -144651,6 +144968,17 @@ parentPort.on('message', (message) => {
144651
144968
  status: "idle"
144652
144969
  };
144653
144970
  }
144971
+ async waitForPendingWork(sessionId) {
144972
+ const hostSessionId = this.resolveHostSessionId(sessionId);
144973
+ if ((this.pendingWorkCounts.get(hostSessionId) ?? 0) === 0) {
144974
+ return;
144975
+ }
144976
+ await new Promise((resolve2) => {
144977
+ const waiters = this.pendingWorkWaiters.get(hostSessionId) ?? /* @__PURE__ */ new Set();
144978
+ waiters.add(resolve2);
144979
+ this.pendingWorkWaiters.set(hostSessionId, waiters);
144980
+ });
144981
+ }
144654
144982
  prefetchWorkspaceHostStreamsToken() {
144655
144983
  const cliConfig = this.config.cliConfig;
144656
144984
  if (!cliConfig.enabled) {
@@ -144677,7 +145005,7 @@ parentPort.on('message', (message) => {
144677
145005
  this.logDisabledOnce();
144678
145006
  throw new Error(`Code Collab host disabled: ${this.config.cliConfig.message}`);
144679
145007
  }
144680
- const host = this.activeHosts.get(input2.sessionId);
145008
+ const host = this.activeHosts.get(this.resolveHostSessionId(input2.sessionId));
144681
145009
  if (!host?.running) {
144682
145010
  throw new Error("Code Collab host runtime is not online for this session.");
144683
145011
  }
@@ -144714,9 +145042,10 @@ parentPort.on('message', (message) => {
144714
145042
  reason: this.config.cliConfig.reason
144715
145043
  };
144716
145044
  }
144717
- const host = this.activeHosts.get(input2.sessionId);
145045
+ const hostSessionId = this.registerHostSessionAlias(input2.sessionId, input2.hostSessionId);
145046
+ const host = this.activeHosts.get(hostSessionId);
144718
145047
  if (host?.running.runtime === void 0) {
144719
- this.logCodeCollabDebug(`[${input2.sessionId}] Code Collab turn marker skipped: no active host phase=${input2.phase} turn=${input2.turnId}`);
145048
+ this.logCodeCollabDebug(`[${input2.sessionId}] Code Collab turn marker skipped: no active host host=${hostSessionId} phase=${input2.phase} turn=${input2.turnId}`);
144720
145049
  return {
144721
145050
  status: "no-host"
144722
145051
  };
@@ -144726,7 +145055,7 @@ parentPort.on('message', (message) => {
144726
145055
  const runtime = await (this.config.runtimeLoader ?? loadLodyCodeCollabRuntime)();
144727
145056
  const backend = this.createBackend();
144728
145057
  const getHostToken = async () => await runPromise(backend.getStreamsToken({
144729
- sessionId: input2.sessionId,
145058
+ sessionId: hostSessionId,
144730
145059
  requestedRole: "host"
144731
145060
  }));
144732
145061
  const auth = async () => {
@@ -144749,6 +145078,7 @@ parentPort.on('message', (message) => {
144749
145078
  if (computeHostTurnChanges === void 0) {
144750
145079
  throw new Error(`[${input2.sessionId}] Code Collab runtime is missing computeHostTurnChanges; turn-history fallback has been removed`);
144751
145080
  }
145081
+ const computeHostTurnFileDiff = runtime.computeHostTurnFileDiff;
144752
145082
  if (input2.phase === "turn:start") {
144753
145083
  const snapshot = await runtime.snapshotHostTurnHistoryFiles({
144754
145084
  host: host.running
@@ -144779,27 +145109,52 @@ parentPort.on('message', (message) => {
144779
145109
  }
144780
145110
  }
144781
145111
  const capture2 = await this.buildTurnCapture(input2.phase, input2.turnId, input2.sessionId, input2.capture, input2.baseCommitHash, input2.turnStartWorkingTreeDiff);
145112
+ const isTerminalMarker = isTerminalLodyCodeCollabTurnMarkerPhase(input2.phase);
144782
145113
  let changedFilesResult;
145114
+ let currentChangesPublishDeferred = false;
144783
145115
  try {
144784
- changedFilesResult = await this.computeTurnChangesForSessionHistory({
144785
- computeHostTurnChanges,
144786
- host: host.running,
144787
- input: input2,
144788
- baseFiles,
144789
- capture: capture2,
144790
- auth,
144791
- blobAuth
144792
- });
145116
+ if (isTerminalMarker && computeHostTurnFileDiff !== void 0) {
145117
+ changedFilesResult = await this.computeTurnFileDiffForSessionHistory({
145118
+ computeHostTurnFileDiff,
145119
+ host: host.running,
145120
+ input: input2,
145121
+ baseFiles,
145122
+ capture: capture2,
145123
+ auth,
145124
+ blobAuth
145125
+ });
145126
+ currentChangesPublishDeferred = true;
145127
+ this.publishCodeCollabCurrentChangesInBackground({
145128
+ runtime,
145129
+ computeHostTurnChanges,
145130
+ host: host.running,
145131
+ input: input2,
145132
+ baseFiles,
145133
+ capture: capture2,
145134
+ auth,
145135
+ blobAuth
145136
+ });
145137
+ } else {
145138
+ changedFilesResult = await this.computeTurnChangesForSessionHistory({
145139
+ computeHostTurnChanges,
145140
+ host: host.running,
145141
+ input: input2,
145142
+ baseFiles,
145143
+ capture: capture2,
145144
+ auth,
145145
+ blobAuth
145146
+ });
145147
+ }
144793
145148
  } finally {
144794
145149
  if (snapshotKeyToClear !== void 0) {
144795
145150
  this.turnSnapshots.delete(snapshotKeyToClear);
144796
145151
  this.turnSnapshotTextBases.delete(snapshotKeyToClear);
144797
145152
  }
144798
- if (isTerminalLodyCodeCollabTurnMarkerPhase(input2.phase)) {
145153
+ if (isTerminalMarker) {
144799
145154
  this.clearActiveTurnTracker(input2.sessionId, input2.turnId);
144800
145155
  }
144801
145156
  }
144802
- if (isTerminalLodyCodeCollabTurnMarkerPhase(input2.phase)) {
145157
+ if (isTerminalMarker) {
144803
145158
  if (changedFilesResult.status === "recorded") {
144804
145159
  this.config.logger.debug(`[${input2.sessionId}] Code Collab changedFiles recorded turn=${input2.turnId}`);
144805
145160
  } else if (changedFilesResult.status === "failed") {
@@ -144809,21 +145164,24 @@ parentPort.on('message', (message) => {
144809
145164
  this.logCodeCollabDebug(`[${input2.sessionId}] Code Collab turn marker recorded phase=${input2.phase} turn=${input2.turnId}`);
144810
145165
  const turnChangedFiles = changedFilesResult.status === "recorded" ? changedFilesResult.result?.turnChangedFiles : void 0;
144811
145166
  const allChangedFiles = changedFilesResult.status === "recorded" ? changedFilesResult.result?.changedFiles : void 0;
144812
- if (isTerminalLodyCodeCollabTurnMarkerPhase(input2.phase) && runtime.publishHostCurrentChanges !== void 0) {
144813
- await runtime.publishHostCurrentChanges({
145167
+ if (isTerminalMarker && runtime.publishHostCurrentChanges !== void 0 && !currentChangesPublishDeferred) {
145168
+ await this.publishCodeCollabCurrentChanges({
145169
+ sessionId: input2.sessionId,
145170
+ runtime,
144814
145171
  host: host.running,
144815
- currentChanges: allChangedFiles === void 0 ? {} : currentChangesFromChangedFiles(allChangedFiles),
144816
- auth,
144817
- ...this.config.fetch === void 0 ? {} : {
144818
- fetch: this.config.fetch
144819
- }
145172
+ allChangedFiles,
145173
+ auth
144820
145174
  });
144821
145175
  }
144822
145176
  const fileDiff = turnChangedFiles === void 0 ? void 0 : fileDiffsFromChangedFiles(turnChangedFiles);
145177
+ const allChangesFileDiff = allChangedFiles === void 0 ? void 0 : fileDiffsFromChangedFiles(allChangedFiles);
144823
145178
  return {
144824
145179
  status: "recorded",
144825
145180
  ...fileDiff === void 0 ? {} : {
144826
145181
  fileDiff
145182
+ },
145183
+ ...allChangesFileDiff === void 0 ? {} : {
145184
+ allChangesFileDiff
144827
145185
  }
144828
145186
  };
144829
145187
  } catch (error2) {
@@ -144878,6 +145236,109 @@ parentPort.on('message', (message) => {
144878
145236
  };
144879
145237
  }
144880
145238
  }
145239
+ async computeTurnFileDiffForSessionHistory(input2) {
145240
+ if (!isTerminalLodyCodeCollabTurnMarkerPhase(input2.input.phase)) {
145241
+ return {
145242
+ status: "not_applicable"
145243
+ };
145244
+ }
145245
+ const capturedTurnBases = input2.capture?.capturedTurnBases ?? [];
145246
+ if (capturedTurnBases.length === 0) {
145247
+ return {
145248
+ status: "recorded",
145249
+ result: {
145250
+ turnChangedFiles: /* @__PURE__ */ new Map()
145251
+ }
145252
+ };
145253
+ }
145254
+ try {
145255
+ const result = await input2.computeHostTurnFileDiff({
145256
+ host: input2.host,
145257
+ turnId: input2.input.turnId,
145258
+ ...input2.baseFiles === void 0 ? {} : {
145259
+ baseFiles: input2.baseFiles
145260
+ },
145261
+ capturedTurnBases,
145262
+ auth: input2.auth,
145263
+ blobAuth: input2.blobAuth,
145264
+ ...this.config.fetch === void 0 ? {} : {
145265
+ fetch: this.config.fetch
145266
+ }
145267
+ });
145268
+ return {
145269
+ status: "recorded",
145270
+ result
145271
+ };
145272
+ } catch (error2) {
145273
+ return {
145274
+ status: "failed",
145275
+ error: formatErrorMessage$1(error2)
145276
+ };
145277
+ }
145278
+ }
145279
+ async publishCodeCollabCurrentChanges(input2) {
145280
+ const publishHostCurrentChanges = input2.runtime.publishHostCurrentChanges;
145281
+ if (publishHostCurrentChanges === void 0) {
145282
+ return;
145283
+ }
145284
+ const hostSessionId = this.resolveHostSessionId(input2.sessionId);
145285
+ const tail2 = this.publishCurrentChangesTails.get(hostSessionId) ?? Promise.resolve();
145286
+ const publish2 = tail2.catch(() => void 0).then(async () => {
145287
+ await publishHostCurrentChanges({
145288
+ host: input2.host,
145289
+ currentChanges: input2.allChangedFiles === void 0 ? {} : currentChangesFromChangedFiles(input2.allChangedFiles),
145290
+ auth: input2.auth,
145291
+ ...this.config.fetch === void 0 ? {} : {
145292
+ fetch: this.config.fetch
145293
+ }
145294
+ });
145295
+ });
145296
+ const tracked = publish2.catch(() => void 0).then(() => {
145297
+ if (this.publishCurrentChangesTails.get(hostSessionId) === tracked) {
145298
+ this.publishCurrentChangesTails.delete(hostSessionId);
145299
+ }
145300
+ });
145301
+ this.publishCurrentChangesTails.set(hostSessionId, tracked);
145302
+ await publish2;
145303
+ }
145304
+ publishCodeCollabCurrentChangesInBackground(input2) {
145305
+ if (!isTerminalLodyCodeCollabTurnMarkerPhase(input2.input.phase) || input2.runtime.publishHostCurrentChanges === void 0) {
145306
+ return;
145307
+ }
145308
+ this.beginPendingWork(input2.input.sessionId);
145309
+ const allChangesCapture = input2.capture === void 0 ? void 0 : {
145310
+ capturedBases: input2.capture.capturedBases
145311
+ };
145312
+ void (async () => {
145313
+ try {
145314
+ const changedFilesResult = await this.computeTurnChangesForSessionHistory({
145315
+ computeHostTurnChanges: input2.computeHostTurnChanges,
145316
+ host: input2.host,
145317
+ input: input2.input,
145318
+ baseFiles: input2.baseFiles,
145319
+ capture: allChangesCapture,
145320
+ auth: input2.auth,
145321
+ blobAuth: input2.blobAuth
145322
+ });
145323
+ if (changedFilesResult.status === "failed") {
145324
+ this.config.logger.warn(`[${input2.input.sessionId}] Failed to record Code Collab current changes turn=${input2.input.turnId}: ${changedFilesResult.error ?? "unknown error"}`);
145325
+ return;
145326
+ }
145327
+ const allChangedFiles = changedFilesResult.status === "recorded" ? changedFilesResult.result?.changedFiles : void 0;
145328
+ await this.publishCodeCollabCurrentChanges({
145329
+ sessionId: input2.input.sessionId,
145330
+ runtime: input2.runtime,
145331
+ host: input2.host,
145332
+ allChangedFiles,
145333
+ auth: input2.auth
145334
+ });
145335
+ } catch (error2) {
145336
+ this.config.logger.warn(`[${input2.input.sessionId}] Failed to publish Code Collab current changes turn=${input2.input.turnId}: ${formatErrorMessage$1(error2)}`);
145337
+ } finally {
145338
+ this.endPendingWork(input2.input.sessionId);
145339
+ }
145340
+ })();
145341
+ }
144881
145342
  async buildTurnCapture(phase, turnId, sessionId, captureConfig, baseCommitHash, turnStartWorkingTreeDiff) {
144882
145343
  if (!isTerminalLodyCodeCollabTurnMarkerPhase(phase)) return void 0;
144883
145344
  const mode2 = captureConfig?.mode ?? "acp-tracked";
@@ -144903,7 +145364,7 @@ parentPort.on('message', (message) => {
144903
145364
  }
144904
145365
  }
144905
145366
  } else {
144906
- const tracker = this.codeCollabTurnTrackers.get(sessionId);
145367
+ const tracker = this.codeCollabTurnTrackers.get(this.resolveHostSessionId(sessionId));
144907
145368
  capturedBases = tracker ? tracker.entries().map(([filePath, base]) => ({
144908
145369
  path: filePath,
144909
145370
  base: capturedBaseContentFromTracker(base)
@@ -145173,7 +145634,7 @@ parentPort.on('message', (message) => {
145173
145634
  this.activeHosts.delete(sessionId);
145174
145635
  this.clearIdleTimer(sessionId);
145175
145636
  this.lastActivityAtMs.delete(sessionId);
145176
- this.pendingWorkCounts.delete(sessionId);
145637
+ this.clearPendingWork(sessionId);
145177
145638
  this.disposeTurnHistoryLineStatsWorkerPoolIfIdle();
145178
145639
  this.setSessionState(sessionId, {
145179
145640
  status: "stopped",
@@ -145187,7 +145648,7 @@ parentPort.on('message', (message) => {
145187
145648
  this.activeHosts.delete(sessionId);
145188
145649
  this.clearIdleTimer(sessionId);
145189
145650
  this.lastActivityAtMs.delete(sessionId);
145190
- this.pendingWorkCounts.delete(sessionId);
145651
+ this.clearPendingWork(sessionId);
145191
145652
  this.disposeTurnHistoryLineStatsWorkerPoolIfIdle();
145192
145653
  this.setSessionState(sessionId, {
145193
145654
  status: "degraded",
@@ -145199,24 +145660,43 @@ parentPort.on('message', (message) => {
145199
145660
  });
145200
145661
  }
145201
145662
  beginPendingWork(sessionId) {
145202
- this.pendingWorkCounts.set(sessionId, (this.pendingWorkCounts.get(sessionId) ?? 0) + 1);
145203
- this.touchActivity(sessionId);
145663
+ const hostSessionId = this.resolveHostSessionId(sessionId);
145664
+ this.pendingWorkCounts.set(hostSessionId, (this.pendingWorkCounts.get(hostSessionId) ?? 0) + 1);
145665
+ this.touchActivity(hostSessionId);
145204
145666
  }
145205
145667
  endPendingWork(sessionId) {
145206
- const next2 = Math.max(0, (this.pendingWorkCounts.get(sessionId) ?? 0) - 1);
145668
+ const hostSessionId = this.resolveHostSessionId(sessionId);
145669
+ const next2 = Math.max(0, (this.pendingWorkCounts.get(hostSessionId) ?? 0) - 1);
145207
145670
  if (next2 === 0) {
145208
- this.pendingWorkCounts.delete(sessionId);
145671
+ this.pendingWorkCounts.delete(hostSessionId);
145672
+ this.resolvePendingWorkWaiters(hostSessionId);
145209
145673
  } else {
145210
- this.pendingWorkCounts.set(sessionId, next2);
145674
+ this.pendingWorkCounts.set(hostSessionId, next2);
145675
+ }
145676
+ this.touchActivity(hostSessionId);
145677
+ }
145678
+ clearPendingWork(sessionId) {
145679
+ const hostSessionId = this.resolveHostSessionId(sessionId);
145680
+ this.pendingWorkCounts.delete(hostSessionId);
145681
+ this.resolvePendingWorkWaiters(hostSessionId);
145682
+ }
145683
+ resolvePendingWorkWaiters(sessionId) {
145684
+ const waiters = this.pendingWorkWaiters.get(sessionId);
145685
+ if (waiters === void 0) {
145686
+ return;
145687
+ }
145688
+ this.pendingWorkWaiters.delete(sessionId);
145689
+ for (const resolve2 of waiters) {
145690
+ resolve2();
145211
145691
  }
145212
- this.touchActivity(sessionId);
145213
145692
  }
145214
145693
  touchActivity(sessionId, now2 = Date.now()) {
145215
- if (!this.activeHosts.has(sessionId)) {
145694
+ const hostSessionId = this.resolveHostSessionId(sessionId);
145695
+ if (!this.activeHosts.has(hostSessionId)) {
145216
145696
  return;
145217
145697
  }
145218
- this.lastActivityAtMs.set(sessionId, now2);
145219
- this.scheduleIdleCheck(sessionId, now2);
145698
+ this.lastActivityAtMs.set(hostSessionId, now2);
145699
+ this.scheduleIdleCheck(hostSessionId, now2);
145220
145700
  }
145221
145701
  scheduleIdleCheck(sessionId, now2 = Date.now()) {
145222
145702
  this.clearIdleTimer(sessionId);
@@ -145865,22 +146345,28 @@ parentPort.on('message', (message) => {
145865
146345
  function currentChangesFromChangedFiles(changedFiles) {
145866
146346
  const projected = projectChangedFiles(changedFiles);
145867
146347
  if (projected.length === 0) return {};
145868
- return Object.fromEntries(projected.map(({ record: record2, filePath }) => {
146348
+ const entries = [];
146349
+ for (const { record: record2, filePath } of projected) {
146350
+ const change = currentChangeFromChangedFileRecord(record2);
146351
+ if (change === void 0) continue;
145869
146352
  const currentPath = record2.current.path;
145870
146353
  const basePath = record2.base.path;
145871
146354
  const path2 = currentPath ?? basePath ?? filePath;
145872
146355
  const key2 = record2.current.state === "present" ? record2.fileId : `path:${path2}`;
145873
- return [
146356
+ entries.push([
145874
146357
  key2,
145875
- currentChangeFromChangedFileRecord(record2)
145876
- ];
145877
- }).sort(([left2], [right2]) => left2.localeCompare(right2)));
146358
+ change
146359
+ ]);
146360
+ }
146361
+ if (entries.length === 0) return {};
146362
+ return Object.fromEntries(entries.sort(([left2], [right2]) => left2.localeCompare(right2)));
145878
146363
  }
145879
146364
  function fileDiffsFromChangedFiles(changedFiles) {
145880
146365
  const projected = projectChangedFiles(changedFiles);
145881
146366
  if (projected.length === 0) return void 0;
145882
- return projected.map(({ record: record2, filePath, add: add2, del }) => {
146367
+ const fileDiffs = projected.flatMap(({ record: record2, filePath, add: add2, del }) => {
145883
146368
  const cc = codeCollabFileDiffCheckpointFromRecord(record2);
146369
+ if (cc === void 0 && currentChangeFromChangedFileRecord(record2) === void 0) return [];
145884
146370
  return {
145885
146371
  filePath,
145886
146372
  add: add2,
@@ -145890,6 +146376,7 @@ parentPort.on('message', (message) => {
145890
146376
  }
145891
146377
  };
145892
146378
  });
146379
+ return fileDiffs.length === 0 ? void 0 : fileDiffs;
145893
146380
  }
145894
146381
  function projectChangedFiles(changedFiles) {
145895
146382
  const statsByFileId = new Map(projectTurnHistoryFileDiffStats(changedFiles).map((diffStat) => [
@@ -145925,15 +146412,15 @@ parentPort.on('message', (message) => {
145925
146412
  } : {}
145926
146413
  };
145927
146414
  if (record2.lineStats?.kind !== "counted") {
145928
- const value = {
146415
+ const value2 = {
145929
146416
  ...record2.current.state === "deleted" ? {
145930
146417
  r: true
145931
146418
  } : {},
145932
146419
  ...checkpoint
145933
146420
  };
145934
- return Object.keys(value).length === 0 ? true : value;
146421
+ return Object.keys(value2).length === 0 ? void 0 : value2;
145935
146422
  }
145936
- return {
146423
+ const value = {
145937
146424
  ...record2.lineStats.add === 0 ? {} : {
145938
146425
  a: record2.lineStats.add
145939
146426
  },
@@ -145945,6 +146432,7 @@ parentPort.on('message', (message) => {
145945
146432
  } : {},
145946
146433
  ...checkpoint
145947
146434
  };
146435
+ return Object.keys(value).length === 0 ? void 0 : value;
145948
146436
  }
145949
146437
  function changedFileRecords(changedFiles) {
145950
146438
  if (Array.isArray(changedFiles)) {
@@ -146089,7 +146577,7 @@ parentPort.on('message', (message) => {
146089
146577
  const DEFAULT_LOCAL_PROJECT_BROWSE_DIR_LIMIT = 500;
146090
146578
  const HARD_LOCAL_PROJECT_BROWSE_DIR_LIMIT = 5e3;
146091
146579
  const DEFAULT_LOCAL_PROJECT_READ_MAX_BYTES = 64 * 1024;
146092
- const HARD_LOCAL_PROJECT_READ_MAX_BYTES = 1024 * 1024;
146580
+ const HARD_LOCAL_PROJECT_READ_MAX_BYTES = 5 * 1024 * 1024;
146093
146581
  const GIT_COMMAND_MAX_BUFFER_BYTES = 64 * 1024 * 1024;
146094
146582
  const LOCAL_PROJECT_GIT_COMMAND_TIMEOUT_MS = 5e3;
146095
146583
  const LOCAL_PROJECT_WALK_YIELD_EVERY_ENTRIES = 1e3;
@@ -146152,7 +146640,7 @@ parentPort.on('message', (message) => {
146152
146640
  }
146153
146641
  return absolutePath;
146154
146642
  }
146155
- function readLocalFileAtRoot(rootPath, relativePath, maxBytes) {
146643
+ function readLocalFileAtRoot(rootPath, relativePath, maxBytes, allowBinary = false) {
146156
146644
  const requestedRelativePath = normalizeProjectRelativePath(relativePath.trim());
146157
146645
  const safePath = resolveSafeProjectFilePath(rootPath, requestedRelativePath);
146158
146646
  if (!safePath) return null;
@@ -146189,8 +146677,17 @@ parentPort.on('message', (message) => {
146189
146677
  const bytesRead = fs$3.readSync(fd, buffer2, 0, maxBytes + 1, 0);
146190
146678
  const truncated = bytesRead > maxBytes;
146191
146679
  const safeBytes = truncated ? maxBytes : bytesRead;
146680
+ const resolvedRelativePath = toProjectRelativePath(rootRealPath, targetRealPath);
146681
+ if (allowBinary && isBinaryImagePath(resolvedRelativePath)) {
146682
+ return {
146683
+ path: resolvedRelativePath,
146684
+ content: buffer2.subarray(0, safeBytes).toString("base64"),
146685
+ truncated,
146686
+ encoding: "base64"
146687
+ };
146688
+ }
146192
146689
  return {
146193
- path: toProjectRelativePath(rootRealPath, targetRealPath),
146690
+ path: resolvedRelativePath,
146194
146691
  content: buffer2.subarray(0, safeBytes).toString("utf8"),
146195
146692
  truncated
146196
146693
  };
@@ -146740,7 +147237,7 @@ parentPort.on('message', (message) => {
146740
147237
  }
146741
147238
  readProjectFile(rootPath, relativePath, options) {
146742
147239
  const maxBytes = clampInteger(options?.maxBytes, 1, HARD_LOCAL_PROJECT_READ_MAX_BYTES, DEFAULT_LOCAL_PROJECT_READ_MAX_BYTES);
146743
- return readLocalFileAtRoot(rootPath, relativePath, maxBytes);
147240
+ return readLocalFileAtRoot(rootPath, relativePath, maxBytes, true);
146744
147241
  }
146745
147242
  async checkoutProjectBranch(rootPath, branchName) {
146746
147243
  try {
@@ -150001,6 +150498,7 @@ ${escapeHtmlScriptContent(VISUAL_ANNOTATION_INSPECTOR_BROWSER_SCRIPT)}
150001
150498
  this.cliVersion = config2.cliVersion;
150002
150499
  this.machineId = config2.machineId;
150003
150500
  this.supportRegistryAgentTypes = config2.supportRegistryAgentTypes ?? [];
150501
+ this.closeSessionTerminals = config2.closeSessionTerminals;
150004
150502
  this.onFatalAuthFailure = config2.onFatalAuthFailure;
150005
150503
  this.notificationService = createNotificationService(this.token, this.logger);
150006
150504
  this.usageTrackingService = createUsageTrackingService(this.token, this.logger);
@@ -150056,9 +150554,12 @@ ${escapeHtmlScriptContent(VISUAL_ANNOTATION_INSPECTOR_BROWSER_SCRIPT)}
150056
150554
  touchSession: (sessionId) => this.touchSession(sessionId),
150057
150555
  startMachineSessionHeartbeat: (sessionId) => this.startMachineSessionHeartbeat(sessionId),
150058
150556
  stopMachineSessionHeartbeat: (sessionId) => this.stopMachineSessionHeartbeat(sessionId),
150059
- startCodeCollabHost: async ({ sessionId, workspaceRoot, project }) => {
150557
+ startCodeCollabHost: async ({ sessionId, hostSessionId, workspaceRoot, project }) => {
150060
150558
  const result = await this.codeCollabHostManager.startForSession({
150061
150559
  sessionId,
150560
+ ...hostSessionId === void 0 ? {} : {
150561
+ hostSessionId
150562
+ },
150062
150563
  workspaceRoot,
150063
150564
  project
150064
150565
  });
@@ -150079,18 +150580,34 @@ ${escapeHtmlScriptContent(VISUAL_ANNOTATION_INSPECTOR_BROWSER_SCRIPT)}
150079
150580
  }
150080
150581
  },
150081
150582
  recordCodeCollabTurnMarker: async ({ sessionId, turnId, phase, project, baseCommitHash, turnStartWorkingTreeDiff }) => {
150583
+ const hostSessionId = await this.resolveCodeCollabHostSessionId(sessionId);
150082
150584
  const result = await this.codeCollabHostManager.recordTurnMarker({
150083
150585
  sessionId,
150586
+ ...hostSessionId === void 0 ? {} : {
150587
+ hostSessionId
150588
+ },
150084
150589
  turnId,
150085
150590
  phase,
150086
150591
  baseCommitHash,
150087
150592
  turnStartWorkingTreeDiff,
150088
150593
  capture: await this.resolveCodeCollabCaptureConfig(sessionId, project)
150089
150594
  });
150090
- const fileDiff = result.status === "recorded" ? result.fileDiff : void 0;
150091
- return fileDiff && fileDiff.length > 0 ? {
150092
- fileDiff
150093
- } : void 0;
150595
+ if (result.status !== "recorded") {
150596
+ return void 0;
150597
+ }
150598
+ const fileDiff = result.fileDiff;
150599
+ const allChangesFileDiff = result.allChangesFileDiff;
150600
+ if ((!fileDiff || fileDiff.length === 0) && allChangesFileDiff === void 0) {
150601
+ return void 0;
150602
+ }
150603
+ return {
150604
+ ...fileDiff && fileDiff.length > 0 ? {
150605
+ fileDiff
150606
+ } : {},
150607
+ ...allChangesFileDiff === void 0 ? {} : {
150608
+ allChangesFileDiff
150609
+ }
150610
+ };
150094
150611
  },
150095
150612
  beginACPReplaySuppression: (sessionId) => this.beginACPReplaySuppression(sessionId),
150096
150613
  endACPReplaySuppression: (sessionId) => this.endACPReplaySuppression(sessionId),
@@ -150289,6 +150806,7 @@ ${escapeHtmlScriptContent(VISUAL_ANNOTATION_INSPECTOR_BROWSER_SCRIPT)}
150289
150806
  machineName;
150290
150807
  cliVersion;
150291
150808
  supportRegistryAgentTypes;
150809
+ closeSessionTerminals;
150292
150810
  onFatalAuthFailure;
150293
150811
  store = new SessionTransientStore();
150294
150812
  titleGenerationInFlight = /* @__PURE__ */ new Set();
@@ -150304,10 +150822,6 @@ ${escapeHtmlScriptContent(VISUAL_ANNOTATION_INSPECTOR_BROWSER_SCRIPT)}
150304
150822
  static CODEX_PROPOSED_PLAN_UPDATE_BATCH_WINDOW_MS = 100;
150305
150823
  static CONTEXT_WINDOW_USAGE_THROTTLE_MS = 400;
150306
150824
  permissionRequestStartTimes = /* @__PURE__ */ new Map();
150307
- machineHeartbeatTimer = null;
150308
- machineHeartbeatInFlightStartedAt = null;
150309
- machineHeartbeatSeq = 0;
150310
- static MACHINE_HEARTBEAT_INTERVAL_MS = 2e4;
150311
150825
  static MACHINE_ACCESS_REGISTRATION_CACHE_TTL_MS = 20 * 6e4;
150312
150826
  machineAccessRegistrationInFlight = null;
150313
150827
  machineAccessRegistrationExpiresAtMs = 0;
@@ -150342,39 +150856,6 @@ ${escapeHtmlScriptContent(VISUAL_ANNOTATION_INSPECTOR_BROWSER_SCRIPT)}
150342
150856
  touchSession(sessionId) {
150343
150857
  this.store.get(sessionId).lastActivityMs = Date.now();
150344
150858
  }
150345
- startMachineHeartbeat() {
150346
- this.stopMachineHeartbeat();
150347
- this.logger.debug(`Machine heartbeat started (${Math.round(MessageHandler.MACHINE_HEARTBEAT_INTERVAL_MS / 1e3)}s interval)`);
150348
- this.sendMachineHeartbeatWithDiagnostics("initial");
150349
- this.machineHeartbeatTimer = setInterval(() => {
150350
- this.sendMachineHeartbeatWithDiagnostics("periodic");
150351
- }, MessageHandler.MACHINE_HEARTBEAT_INTERVAL_MS);
150352
- this.machineHeartbeatTimer.unref?.();
150353
- }
150354
- sendMachineHeartbeatWithDiagnostics(reason) {
150355
- const now2 = Date.now();
150356
- if (this.machineHeartbeatInFlightStartedAt !== null) {
150357
- this.logger.warn(`Machine heartbeat skipped (reason=${reason}); previous heartbeat still pending for ${now2 - this.machineHeartbeatInFlightStartedAt}ms`);
150358
- return;
150359
- }
150360
- const seq2 = ++this.machineHeartbeatSeq;
150361
- this.machineHeartbeatInFlightStartedAt = now2;
150362
- this.logger.debug(`Machine heartbeat tick started (reason=${reason} seq=${seq2})`);
150363
- void withSlowOperationWarning(this.workspaceDocument.sendMachineHeartbeat(), this.logger, `workspaceDocument.sendMachineHeartbeat(reason=${reason}, seq=${seq2})`, this.machineId).then(() => {
150364
- this.logger.debug(`Machine heartbeat sent (reason=${reason} seq=${seq2} duration=${Date.now() - now2}ms)`);
150365
- }).catch((error2) => {
150366
- this.logger.debug(`Machine heartbeat failed (reason=${reason} seq=${seq2} duration=${Date.now() - now2}ms): ${formatErrorMessage$1(error2)}`);
150367
- }).finally(() => {
150368
- this.machineHeartbeatInFlightStartedAt = null;
150369
- });
150370
- }
150371
- stopMachineHeartbeat() {
150372
- if (this.machineHeartbeatTimer) {
150373
- clearInterval(this.machineHeartbeatTimer);
150374
- this.machineHeartbeatTimer = null;
150375
- }
150376
- this.machineHeartbeatInFlightStartedAt = null;
150377
- }
150378
150859
  preferredBaseBranch = (process.env.LODY_BASE_BRANCH || "main").trim() || "main";
150379
150860
  resolveGitHubProjectBranch(meta, preferredBranch) {
150380
150861
  return resolveBaseBranchPreference({
@@ -150384,6 +150865,21 @@ ${escapeHtmlScriptContent(VISUAL_ANNOTATION_INSPECTOR_BROWSER_SCRIPT)}
150384
150865
  fallbackBranch: this.preferredBaseBranch
150385
150866
  });
150386
150867
  }
150868
+ async resolveCodeCollabHostSessionId(sessionId) {
150869
+ const activeParent = this.sessionManager.getSession(sessionId)?.getParentSessionId();
150870
+ if (activeParent !== void 0 && activeParent !== sessionId) {
150871
+ return activeParent;
150872
+ }
150873
+ try {
150874
+ const sessionDoc = await this.workspaceDocument.getOrCreateSessionDoc(sessionId);
150875
+ const meta = await sessionDoc.getMetaState();
150876
+ const parentSessionId = meta?.parentSessionId;
150877
+ return parentSessionId !== void 0 && parentSessionId !== sessionId ? parentSessionId : void 0;
150878
+ } catch (error2) {
150879
+ this.logger.debug(`[${sessionId}] Code Collab host session resolve failed: ${formatErrorMessage$1(error2)}`);
150880
+ return void 0;
150881
+ }
150882
+ }
150387
150883
  async resolveCodeCollabCaptureConfig(sessionId, projectHint) {
150388
150884
  try {
150389
150885
  const sessionDoc = await this.workspaceDocument.getOrCreateSessionDoc(sessionId);
@@ -151374,7 +151870,7 @@ ${escapeHtmlScriptContent(VISUAL_ANNOTATION_INSPECTOR_BROWSER_SCRIPT)}
151374
151870
  if (!meta) continue;
151375
151871
  if (meta.machineId !== this.machineId) continue;
151376
151872
  if (meta.status?.type === "idle") continue;
151377
- if (isSessionActiveWithHeartbeat(meta.status, meta.lastRunningSeen)) continue;
151873
+ if (this.store.has(sessionId) && this.store.get(sessionId).heartbeat !== null) continue;
151378
151874
  await sessionDoc.setStatus(SessionStatusFactory.idle());
151379
151875
  resetCount += 1;
151380
151876
  } catch (error2) {
@@ -151534,6 +152030,7 @@ ${escapeHtmlScriptContent(VISUAL_ANNOTATION_INSPECTOR_BROWSER_SCRIPT)}
151534
152030
  this.logger.debug(`[${sessionId}] Archiving session resources`);
151535
152031
  this.stopMachineSessionHeartbeat(sessionId);
151536
152032
  this.codeCollabHostManager.stopSession(sessionId, "session archived");
152033
+ this.closeSessionTerminals?.(sessionId);
151537
152034
  this.logger.debug(`[${sessionId}] Heartbeat stopped`);
151538
152035
  await this.finalizeACPState(sessionId);
151539
152036
  this.logger.debug(`[${sessionId}] ACP state finalized`);
@@ -151655,6 +152152,7 @@ ${escapeHtmlScriptContent(VISUAL_ANNOTATION_INSPECTOR_BROWSER_SCRIPT)}
151655
152152
  this.logger.debug(`[${parentSessionId}] Terminating ${childSessionIds.length} active child session(s) before workspace cleanup`);
151656
152153
  await Promise.all(childSessionIds.map(async (childSessionId) => {
151657
152154
  this.stopMachineSessionHeartbeat(childSessionId);
152155
+ this.closeSessionTerminals?.(childSessionId);
151658
152156
  await this.finalizeACPState(childSessionId);
151659
152157
  await this.previewService.closeSessionPreviewForCleanup(childSessionId, reason);
151660
152158
  await this.sessionManager.terminateSession(childSessionId, true);
@@ -151797,6 +152295,7 @@ ${escapeHtmlScriptContent(VISUAL_ANNOTATION_INSPECTOR_BROWSER_SCRIPT)}
151797
152295
  await this.terminateActiveChildSessions(sessionId, "Parent session deleted");
151798
152296
  this.stopMachineSessionHeartbeat(sessionId);
151799
152297
  this.codeCollabHostManager.stopSession(sessionId, "session deleted");
152298
+ this.closeSessionTerminals?.(sessionId);
151800
152299
  await this.finalizeACPState(sessionId);
151801
152300
  await this.previewService.closeSessionPreviewForCleanup(sessionId, "Session deleted");
151802
152301
  this.logger.debug(`[${sessionId}] Preview tunnel closed for deletion`);
@@ -152260,7 +152759,6 @@ ${escapeHtmlScriptContent(VISUAL_ANNOTATION_INSPECTOR_BROWSER_SCRIPT)}
152260
152759
  raceLimits: machineMeta?.raceLimits ?? {}
152261
152760
  });
152262
152761
  await this.registerMachineAccess();
152263
- this.startMachineHeartbeat();
152264
152762
  this.logger.debug(`Machine registered with name: ${registeredName}`);
152265
152763
  } catch (error2) {
152266
152764
  this.logger.error(`Failed to register machine: ${formatErrorMessage$1(error2)}`);
@@ -152310,12 +152808,16 @@ ${escapeHtmlScriptContent(VISUAL_ANNOTATION_INSPECTOR_BROWSER_SCRIPT)}
152310
152808
  }
152311
152809
  return null;
152312
152810
  };
152811
+ const hostSessionIdField = (parentSessionId) => parentSessionId === void 0 || parentSessionId === sessionId ? {} : {
152812
+ hostSessionId: parentSessionId
152813
+ };
152313
152814
  const activeSession = this.sessionManager.getSession(sessionId);
152314
152815
  if (activeSession) {
152315
152816
  return {
152316
152817
  ok: true,
152317
152818
  workspaceRoot: activeSession.getHostWorkdir() ?? activeSession.getWorkdir(),
152318
- source: "active-session"
152819
+ source: "active-session",
152820
+ ...hostSessionIdField(activeSession.getParentSessionId())
152319
152821
  };
152320
152822
  }
152321
152823
  const pendingSession = this.sessionManager.getPendingSession(sessionId);
@@ -152336,7 +152838,8 @@ ${escapeHtmlScriptContent(VISUAL_ANNOTATION_INSPECTOR_BROWSER_SCRIPT)}
152336
152838
  return {
152337
152839
  ok: true,
152338
152840
  workspaceRoot: session.getHostWorkdir() ?? session.getWorkdir(),
152339
- source: "pending-session"
152841
+ source: "pending-session",
152842
+ ...hostSessionIdField(session.getParentSessionId())
152340
152843
  };
152341
152844
  }
152342
152845
  const metaRecord = await this.workspaceDocument.repo.getDocMeta(getSessionRoomId(sessionId));
@@ -152356,6 +152859,7 @@ ${escapeHtmlScriptContent(VISUAL_ANNOTATION_INSPECTOR_BROWSER_SCRIPT)}
152356
152859
  };
152357
152860
  }
152358
152861
  const project = meta.project;
152862
+ const hostSessionId = meta.parentSessionId ?? sessionId;
152359
152863
  if (project?.kind === "local") {
152360
152864
  const workspaceRoot = await resolveWorkspaceLocalProjectRootPath(this.workspaceDocument.repo, this.machineId, project.localProjectId);
152361
152865
  if (!workspaceRoot) {
@@ -152368,17 +152872,18 @@ ${escapeHtmlScriptContent(VISUAL_ANNOTATION_INSPECTOR_BROWSER_SCRIPT)}
152368
152872
  return {
152369
152873
  ok: true,
152370
152874
  workspaceRoot,
152371
- source: `local-project:${project.localProjectId}`
152875
+ source: `local-project:${project.localProjectId}`,
152876
+ ...hostSessionIdField(hostSessionId)
152372
152877
  };
152373
152878
  }
152374
- const hostSessionId = meta.parentSessionId ?? sessionId;
152375
152879
  if (meta.parentSessionId) {
152376
152880
  const parentSession = this.sessionManager.getSession(meta.parentSessionId);
152377
152881
  if (parentSession) {
152378
152882
  return {
152379
152883
  ok: true,
152380
152884
  workspaceRoot: parentSession.getHostWorkdir() ?? parentSession.getWorkdir(),
152381
- source: `active-parent-session:${meta.parentSessionId}`
152885
+ source: `active-parent-session:${meta.parentSessionId}`,
152886
+ ...hostSessionIdField(hostSessionId)
152382
152887
  };
152383
152888
  }
152384
152889
  const pendingParentSession = this.sessionManager.getPendingSession(meta.parentSessionId);
@@ -152399,7 +152904,8 @@ ${escapeHtmlScriptContent(VISUAL_ANNOTATION_INSPECTOR_BROWSER_SCRIPT)}
152399
152904
  return {
152400
152905
  ok: true,
152401
152906
  workspaceRoot: session.getHostWorkdir() ?? session.getWorkdir(),
152402
- source: `pending-parent-session:${meta.parentSessionId}`
152907
+ source: `pending-parent-session:${meta.parentSessionId}`,
152908
+ ...hostSessionIdField(hostSessionId)
152403
152909
  };
152404
152910
  }
152405
152911
  }
@@ -152414,7 +152920,8 @@ ${escapeHtmlScriptContent(VISUAL_ANNOTATION_INSPECTOR_BROWSER_SCRIPT)}
152414
152920
  return {
152415
152921
  ok: true,
152416
152922
  workspaceRoot: worktreeManager.getWorktreeHostPath(hostSessionId),
152417
- source: `github-worktree-existing:${hostSessionId}`
152923
+ source: `github-worktree-existing:${hostSessionId}`,
152924
+ ...hostSessionIdField(hostSessionId)
152418
152925
  };
152419
152926
  }
152420
152927
  this.logCodeCollabDebug(`[${sessionId}] Code Collab host-start waiting for session workspace hostSessionId=${hostSessionId} repo=${repoFullName}`);
@@ -152425,13 +152932,17 @@ ${escapeHtmlScriptContent(VISUAL_ANNOTATION_INSPECTOR_BROWSER_SCRIPT)}
152425
152932
  timeoutMs: CODE_COLLAB_HOST_SESSION_WAIT_TIMEOUT_MS
152426
152933
  });
152427
152934
  if (waited) {
152428
- return waited;
152935
+ return waited.ok ? {
152936
+ ...waited,
152937
+ ...hostSessionIdField(hostSessionId)
152938
+ } : waited;
152429
152939
  }
152430
152940
  if (worktreeManager.hasWorktree(hostSessionId)) {
152431
152941
  return {
152432
152942
  ok: true,
152433
152943
  workspaceRoot: worktreeManager.getWorktreeHostPath(hostSessionId),
152434
- source: `github-worktree-existing-after-wait:${hostSessionId}`
152944
+ source: `github-worktree-existing-after-wait:${hostSessionId}`,
152945
+ ...hostSessionIdField(hostSessionId)
152435
152946
  };
152436
152947
  }
152437
152948
  return {
@@ -152510,7 +153021,10 @@ ${escapeHtmlScriptContent(VISUAL_ANNOTATION_INSPECTOR_BROWSER_SCRIPT)}
152510
153021
  this.logCodeCollabDebug(`[${sessionId}] Code Collab host-start resolved workspaceRoot=${resolved.workspaceRoot} source=${resolved.source}`);
152511
153022
  const result = await this.codeCollabHostManager.startForSession({
152512
153023
  sessionId,
152513
- workspaceRoot: resolved.workspaceRoot
153024
+ workspaceRoot: resolved.workspaceRoot,
153025
+ ...resolved.hostSessionId === void 0 ? {} : {
153026
+ hostSessionId: resolved.hostSessionId
153027
+ }
152514
153028
  });
152515
153029
  this.logCodeCollabDebug(`[${sessionId}] Code Collab host-start result status=${result.status}`);
152516
153030
  switch (result.status) {
@@ -153650,7 +154164,6 @@ ${escapeHtmlScriptContent(VISUAL_ANNOTATION_INSPECTOR_BROWSER_SCRIPT)}
153650
154164
  }
153651
154165
  async cleanup() {
153652
154166
  this.logger.debug("Cleaning up message handler resources");
153653
- this.stopMachineHeartbeat();
153654
154167
  this.cancelPendingPermissionRequests();
153655
154168
  this.machineRpcServer?.stop();
153656
154169
  this.codeCollabHostManager.stopAll("message handler cleanup");
@@ -153877,10 +154390,14 @@ ${escapeHtmlScriptContent(VISUAL_ANNOTATION_INSPECTOR_BROWSER_SCRIPT)}
153877
154390
  if (await this.hasActiveGoal(sessionId)) {
153878
154391
  return false;
153879
154392
  }
153880
- const meta = (await this.workspaceDocument.repo.getDocMeta(getSessionRoomId(sessionId)))?.meta;
153881
- if (meta && isSessionActiveWithHeartbeat(meta.status, meta.lastRunningSeen)) {
154393
+ if (this.store.has(sessionId) && this.store.get(sessionId).heartbeat !== null) {
153882
154394
  return false;
153883
154395
  }
154396
+ for (const childSessionId of this.sessionManager.getActiveChildSessionIds(sessionId)) {
154397
+ if (!await this.isCodeCollabHostSessionIdle(childSessionId)) {
154398
+ return false;
154399
+ }
154400
+ }
153884
154401
  return true;
153885
154402
  }
153886
154403
  async hasActiveGoal(sessionId) {
@@ -154532,6 +155049,7 @@ ${escapeHtmlScriptContent(VISUAL_ANNOTATION_INSPECTOR_BROWSER_SCRIPT)}
154532
155049
  messageProcessor;
154533
155050
  gcManager = null;
154534
155051
  initialized = false;
155052
+ sessionTerminatedHandlers = /* @__PURE__ */ new Set();
154535
155053
  async initialize() {
154536
155054
  if (this.initialized) {
154537
155055
  return {
@@ -154542,6 +155060,11 @@ ${escapeHtmlScriptContent(VISUAL_ANNOTATION_INSPECTOR_BROWSER_SCRIPT)}
154542
155060
  this.options.logger.debug("Initializing machine runtime");
154543
155061
  this.sessionManager = this.options.sessionManagerFactory();
154544
155062
  await this.sessionManager.initialize();
155063
+ this.sessionManager.on("terminated", (event) => {
155064
+ for (const handler of this.sessionTerminatedHandlers) {
155065
+ handler(event.sessionId);
155066
+ }
155067
+ });
154545
155068
  this.options.logger.debug("Session manager initialized");
154546
155069
  this.handler = new MessageHandler(this.sessionManager, this.options.workspaceDocument, this.options.logger, this.options.handlerConfig);
154547
155070
  this.initializeGCManager();
@@ -154563,6 +155086,21 @@ ${escapeHtmlScriptContent(VISUAL_ANNOTATION_INSPECTOR_BROWSER_SCRIPT)}
154563
155086
  getSessionManager() {
154564
155087
  return this.sessionManager;
154565
155088
  }
155089
+ async resolveSessionWorkdir(sessionId) {
155090
+ const sessionManager = this.requireSessionManager();
155091
+ const pendingSession = sessionManager.getPendingSession(sessionId);
155092
+ if (pendingSession) {
155093
+ const session = await pendingSession;
155094
+ return session.getWorkdir();
155095
+ }
155096
+ return sessionManager.getSession(sessionId)?.getWorkdir() ?? null;
155097
+ }
155098
+ onSessionTerminated(handler) {
155099
+ this.sessionTerminatedHandlers.add(handler);
155100
+ return () => {
155101
+ this.sessionTerminatedHandlers.delete(handler);
155102
+ };
155103
+ }
154566
155104
  getActiveSessionCount() {
154567
155105
  return this.handler?.getActiveTurnCount() ?? 0;
154568
155106
  }
@@ -161449,7 +161987,7 @@ export PATH=${toSingleQuotedShellString(ghShimBinDir)}:"$PATH"
161449
161987
  LODY_WORKSPACE_SESSION_ID: workspaceSessionId
161450
161988
  };
161451
161989
  const withLoginShell = mergeLoginShellEnv(merged, getCachedLoginShellEnvSync());
161452
- const agentEnv = this.config.agentCliType === "builtin" && this.config.agentType === "claude" ? scrubInheritedClaudeAuthEnv(withLoginShell, {
161990
+ const agentEnv = shouldScrubClaudeAuthEnv(this.config.agentCliType, this.config.agentType) ? scrubInheritedClaudeAuthEnv(withLoginShell, {
161453
161991
  ...configEnv,
161454
161992
  ...extraEnv
161455
161993
  }) : withLoginShell;
@@ -162348,6 +162886,7 @@ export PATH=${toSingleQuotedShellString(ghShimBinDir)}:"$PATH"
162348
162886
  machineName: this.machineName,
162349
162887
  cliVersion: pkg.version,
162350
162888
  supportRegistryAgentTypes: this.supportRegistryAgentTypes,
162889
+ closeSessionTerminals: options.closeSessionTerminals,
162351
162890
  onFatalAuthFailure: options.onFatalAuthFailure
162352
162891
  },
162353
162892
  logger: this.logger
@@ -162444,6 +162983,12 @@ export PATH=${toSingleQuotedShellString(ghShimBinDir)}:"$PATH"
162444
162983
  getActiveSessionCount() {
162445
162984
  return this.runtime.getActiveSessionCount();
162446
162985
  }
162986
+ async resolveSessionWorkdir(sessionId) {
162987
+ return await this.runtime.resolveSessionWorkdir(sessionId);
162988
+ }
162989
+ onSessionTerminated(handler) {
162990
+ return this.runtime.onSessionTerminated(handler);
162991
+ }
162447
162992
  getConnectedRoomCount() {
162448
162993
  return this.documentManager.getConnectedRoomCount();
162449
162994
  }
@@ -163166,6 +163711,679 @@ export PATH=${toSingleQuotedShellString(ghShimBinDir)}:"$PATH"
163166
163711
  server.close(() => resolve2());
163167
163712
  });
163168
163713
  }
163714
+ const LOCAL_TERMINAL_SOCKET_BASENAME = "lody-terminal";
163715
+ function getUserSocketSuffix() {
163716
+ if (typeof process.getuid === "function") {
163717
+ return String(process.getuid());
163718
+ }
163719
+ const userInfo2 = os__default$1.userInfo();
163720
+ return crypto$1.createHash("sha256").update(`${userInfo2.uid}:${userInfo2.username}:${os__default$1.homedir()}`).digest("hex").slice(0, 16);
163721
+ }
163722
+ function getLocalTerminalSocketPath() {
163723
+ const userId = getUserSocketSuffix();
163724
+ if (process.platform === "win32") {
163725
+ return `\\\\.\\pipe\\${LOCAL_TERMINAL_SOCKET_BASENAME}-${userId}`;
163726
+ }
163727
+ return path__default$1.join(os__default$1.tmpdir(), `${LOCAL_TERMINAL_SOCKET_BASENAME}-${userId}.sock`);
163728
+ }
163729
+ const MAX_BUFFER_BYTES = 1024 * 1024;
163730
+ let terminalServer = null;
163731
+ let activeSocketPath = null;
163732
+ let terminalServerStart = null;
163733
+ function encodeEvent(event) {
163734
+ return `${JSON.stringify(event)}
163735
+ `;
163736
+ }
163737
+ function send(socket, event) {
163738
+ if (socket.destroyed) return;
163739
+ socket.write(encodeEvent(event));
163740
+ }
163741
+ function getEventTerminalId(event) {
163742
+ return "terminalId" in event && typeof event.terminalId === "string" ? event.terminalId : null;
163743
+ }
163744
+ function createTerminalSocketState() {
163745
+ return {
163746
+ subscribedTerminalIds: /* @__PURE__ */ new Set(),
163747
+ replayingTerminalIds: /* @__PURE__ */ new Set(),
163748
+ replayBuffers: /* @__PURE__ */ new Map()
163749
+ };
163750
+ }
163751
+ function publishTerminalEvent(socket, state2, event) {
163752
+ const terminalId = getEventTerminalId(event);
163753
+ if (!terminalId) {
163754
+ send(socket, event);
163755
+ return;
163756
+ }
163757
+ if (!state2.subscribedTerminalIds.has(terminalId)) {
163758
+ return;
163759
+ }
163760
+ if (state2.replayingTerminalIds.has(terminalId)) {
163761
+ const buffer2 = state2.replayBuffers.get(terminalId) ?? [];
163762
+ buffer2.push(event);
163763
+ state2.replayBuffers.set(terminalId, buffer2);
163764
+ return;
163765
+ }
163766
+ send(socket, event);
163767
+ if (event.type === "exit") {
163768
+ state2.subscribedTerminalIds.delete(terminalId);
163769
+ }
163770
+ }
163771
+ function startTerminalReplay(state2, terminalId) {
163772
+ state2.subscribedTerminalIds.add(terminalId);
163773
+ state2.replayingTerminalIds.add(terminalId);
163774
+ state2.replayBuffers.set(terminalId, []);
163775
+ }
163776
+ function finishTerminalReplay(socket, state2, terminalId) {
163777
+ state2.replayingTerminalIds.delete(terminalId);
163778
+ const bufferedEvents = state2.replayBuffers.get(terminalId) ?? [];
163779
+ state2.replayBuffers.delete(terminalId);
163780
+ for (const event of bufferedEvents) {
163781
+ send(socket, event);
163782
+ if (event.type === "exit") {
163783
+ state2.subscribedTerminalIds.delete(terminalId);
163784
+ }
163785
+ }
163786
+ }
163787
+ function cancelTerminalReplay(state2, terminalId) {
163788
+ state2.replayingTerminalIds.delete(terminalId);
163789
+ state2.replayBuffers.delete(terminalId);
163790
+ state2.subscribedTerminalIds.delete(terminalId);
163791
+ }
163792
+ function classifyTerminalError(error2) {
163793
+ const message = formatErrorMessage$1(error2);
163794
+ if (message.startsWith("session_not_found:")) {
163795
+ return {
163796
+ code: "session_not_found",
163797
+ message
163798
+ };
163799
+ }
163800
+ if (message.startsWith("session_archived:")) {
163801
+ return {
163802
+ code: "session_archived",
163803
+ message
163804
+ };
163805
+ }
163806
+ if (message.startsWith("session_deleted:")) {
163807
+ return {
163808
+ code: "session_deleted",
163809
+ message
163810
+ };
163811
+ }
163812
+ if (message.startsWith("session_machine_mismatch:")) {
163813
+ return {
163814
+ code: "session_machine_mismatch",
163815
+ message
163816
+ };
163817
+ }
163818
+ if (message.startsWith("session_parent_cycle:")) {
163819
+ return {
163820
+ code: "session_parent_cycle",
163821
+ message
163822
+ };
163823
+ }
163824
+ if (message.startsWith("session_ambiguous:")) {
163825
+ return {
163826
+ code: "session_ambiguous",
163827
+ message
163828
+ };
163829
+ }
163830
+ if (message.startsWith("terminal_not_found:")) {
163831
+ return {
163832
+ code: "terminal_not_found",
163833
+ message
163834
+ };
163835
+ }
163836
+ if (message.startsWith("terminal_limit_exceeded:")) {
163837
+ return {
163838
+ code: "terminal_limit_exceeded",
163839
+ message
163840
+ };
163841
+ }
163842
+ if (message.includes("workdir") || message.includes("directory") || message.includes("ENOENT")) {
163843
+ return {
163844
+ code: "workdir_unavailable",
163845
+ message
163846
+ };
163847
+ }
163848
+ return {
163849
+ code: "terminal_error",
163850
+ message
163851
+ };
163852
+ }
163853
+ async function removeStaleUnixSocket(socketPath) {
163854
+ if (process.platform === "win32" || !fs$3.existsSync(socketPath)) {
163855
+ return;
163856
+ }
163857
+ await new Promise((resolve2, reject) => {
163858
+ const socket = net__default.createConnection(socketPath);
163859
+ socket.once("connect", () => {
163860
+ socket.destroy();
163861
+ reject(new Error(`local_terminal_socket_in_use:${socketPath}`));
163862
+ });
163863
+ socket.once("error", (error2) => {
163864
+ socket.destroy();
163865
+ if (error2.code === "ECONNREFUSED" || error2.code === "ENOENT") {
163866
+ fs$3.unlinkSync(socketPath);
163867
+ resolve2();
163868
+ return;
163869
+ }
163870
+ reject(error2);
163871
+ });
163872
+ });
163873
+ }
163874
+ async function handleMessage(config2, socket, state2, message) {
163875
+ try {
163876
+ switch (message.type) {
163877
+ case "list": {
163878
+ send(socket, {
163879
+ type: "terminals",
163880
+ requestId: message.requestId,
163881
+ sessionId: message.sessionId,
163882
+ terminals: config2.terminalPtyService.list(message.sessionId)
163883
+ });
163884
+ return;
163885
+ }
163886
+ case "open": {
163887
+ const result = await config2.terminalPtyService.open(message);
163888
+ send(socket, {
163889
+ type: "opened",
163890
+ requestId: message.requestId,
163891
+ terminalId: result.terminalId,
163892
+ ...result.cwd ? {
163893
+ cwd: result.cwd
163894
+ } : {}
163895
+ });
163896
+ return;
163897
+ }
163898
+ case "attach": {
163899
+ startTerminalReplay(state2, message.terminalId);
163900
+ try {
163901
+ const replay = config2.terminalPtyService.attach(message.terminalId, message.cols, message.rows);
163902
+ send(socket, {
163903
+ terminalId: message.terminalId,
163904
+ type: "title",
163905
+ title: replay.title
163906
+ });
163907
+ if (replay.scrollback) {
163908
+ send(socket, {
163909
+ type: "data",
163910
+ terminalId: message.terminalId,
163911
+ data: replay.scrollback,
163912
+ replay: true
163913
+ });
163914
+ }
163915
+ finishTerminalReplay(socket, state2, message.terminalId);
163916
+ } catch (error2) {
163917
+ cancelTerminalReplay(state2, message.terminalId);
163918
+ throw error2;
163919
+ }
163920
+ return;
163921
+ }
163922
+ case "input": {
163923
+ config2.terminalPtyService.input(message.terminalId, message.data);
163924
+ return;
163925
+ }
163926
+ case "resize": {
163927
+ config2.terminalPtyService.resize(message.terminalId, message.cols, message.rows);
163928
+ return;
163929
+ }
163930
+ case "close": {
163931
+ config2.terminalPtyService.close(message.terminalId);
163932
+ return;
163933
+ }
163934
+ case "close_session": {
163935
+ config2.terminalPtyService.closeSession(message.sessionId);
163936
+ return;
163937
+ }
163938
+ default: {
163939
+ throw new Error(`unsupported_terminal_message:${JSON.stringify(message)}`);
163940
+ }
163941
+ }
163942
+ } catch (error2) {
163943
+ const terminalError = classifyTerminalError(error2);
163944
+ send(socket, {
163945
+ type: "error",
163946
+ requestId: message.requestId,
163947
+ ..."terminalId" in message ? {
163948
+ terminalId: message.terminalId
163949
+ } : {},
163950
+ code: terminalError.code,
163951
+ message: terminalError.message
163952
+ });
163953
+ }
163954
+ }
163955
+ async function startLocalTerminalServer(config2) {
163956
+ if (terminalServer) {
163957
+ return;
163958
+ }
163959
+ if (terminalServerStart) {
163960
+ return await terminalServerStart;
163961
+ }
163962
+ terminalServerStart = startLocalTerminalServerInner(config2).finally(() => {
163963
+ terminalServerStart = null;
163964
+ });
163965
+ return await terminalServerStart;
163966
+ }
163967
+ async function startLocalTerminalServerInner(config2) {
163968
+ const socketPath = getLocalTerminalSocketPath();
163969
+ await removeStaleUnixSocket(socketPath);
163970
+ const server = net__default.createServer((socket) => {
163971
+ let buffer2 = "";
163972
+ const state2 = createTerminalSocketState();
163973
+ const unsubscribe2 = config2.terminalPtyService.onEvent((event) => {
163974
+ publishTerminalEvent(socket, state2, event);
163975
+ });
163976
+ socket.on("data", (chunk) => {
163977
+ buffer2 += chunk.toString("utf8");
163978
+ if (buffer2.length > MAX_BUFFER_BYTES) {
163979
+ send(socket, {
163980
+ type: "error",
163981
+ code: "payload_too_large",
163982
+ message: "Terminal socket payload exceeded buffer limit"
163983
+ });
163984
+ socket.destroy();
163985
+ return;
163986
+ }
163987
+ let newlineIndex = buffer2.indexOf("\n");
163988
+ while (newlineIndex >= 0) {
163989
+ const line3 = buffer2.slice(0, newlineIndex).trim();
163990
+ buffer2 = buffer2.slice(newlineIndex + 1);
163991
+ if (line3) {
163992
+ let raw2;
163993
+ try {
163994
+ raw2 = JSON.parse(line3);
163995
+ } catch (error2) {
163996
+ send(socket, {
163997
+ type: "error",
163998
+ code: "invalid_json",
163999
+ message: formatErrorMessage$1(error2)
164000
+ });
164001
+ newlineIndex = buffer2.indexOf("\n");
164002
+ continue;
164003
+ }
164004
+ const parsed = TerminalClientMessageSchema.safeParse(raw2);
164005
+ if (!parsed.success) {
164006
+ send(socket, {
164007
+ type: "error",
164008
+ code: "invalid_request",
164009
+ message: parsed.error.message
164010
+ });
164011
+ newlineIndex = buffer2.indexOf("\n");
164012
+ continue;
164013
+ }
164014
+ void handleMessage(config2, socket, state2, parsed.data);
164015
+ }
164016
+ newlineIndex = buffer2.indexOf("\n");
164017
+ }
164018
+ });
164019
+ socket.on("close", unsubscribe2);
164020
+ socket.on("error", (error2) => {
164021
+ config2.logger.debug(`[terminal] socket error: ${error2.message}`);
164022
+ });
164023
+ });
164024
+ await new Promise((resolve2, reject) => {
164025
+ server.once("error", reject);
164026
+ server.listen(socketPath, () => {
164027
+ server.off("error", reject);
164028
+ terminalServer = server;
164029
+ activeSocketPath = socketPath;
164030
+ if (process.platform !== "win32") {
164031
+ fs$3.chmodSync(socketPath, 384);
164032
+ }
164033
+ config2.logger.debug(`[terminal] local terminal socket listening at ${socketPath}`);
164034
+ resolve2();
164035
+ });
164036
+ });
164037
+ server.on("error", (error2) => {
164038
+ config2.logger.warn(`[terminal] local terminal server error: ${error2.message}`);
164039
+ if (terminalServer === server) {
164040
+ terminalServer = null;
164041
+ activeSocketPath = null;
164042
+ }
164043
+ try {
164044
+ server.close();
164045
+ } catch {
164046
+ }
164047
+ if (process.platform !== "win32" && fs$3.existsSync(socketPath)) {
164048
+ fs$3.unlinkSync(socketPath);
164049
+ }
164050
+ });
164051
+ }
164052
+ async function stopLocalTerminalServer() {
164053
+ if (!terminalServer) {
164054
+ return;
164055
+ }
164056
+ const server = terminalServer;
164057
+ const socketPath = activeSocketPath;
164058
+ terminalServer = null;
164059
+ activeSocketPath = null;
164060
+ await new Promise((resolve2) => {
164061
+ server.close(() => resolve2());
164062
+ });
164063
+ if (socketPath && process.platform !== "win32" && fs$3.existsSync(socketPath)) {
164064
+ fs$3.unlinkSync(socketPath);
164065
+ }
164066
+ }
164067
+ const SCROLLBACK_MAX_CHARS = 512 * 1024;
164068
+ const ESC = String.fromCharCode(27);
164069
+ const BEL = String.fromCharCode(7);
164070
+ const OSC_TITLE_REGEX = new RegExp(`${ESC}\\](?:0|2);([^${BEL}${ESC}]*)(?:${BEL}|${ESC}\\\\)`, "g");
164071
+ const TERMINAL_ENV_BLOCKLIST = /* @__PURE__ */ new Set([
164072
+ "LODY_CLI_TOKEN",
164073
+ "LODY_ELECTRON_SESSION_TOKEN",
164074
+ "LODY_GIT_CRED_BROKER_TOKEN",
164075
+ LODY_GIT_CRED_CONTEXT_TOKEN_ENV,
164076
+ LODY_MANAGED_GH_TOKEN_SHA256_ENV
164077
+ ]);
164078
+ const require$1 = createRequire$1(import.meta.url);
164079
+ const pty = require$1("node-pty");
164080
+ function basenameForTitle(cwd) {
164081
+ const name2 = path__default$1.basename(cwd);
164082
+ return name2 || cwd;
164083
+ }
164084
+ function resolveShellCommand() {
164085
+ if (process.platform === "win32") {
164086
+ return {
164087
+ file: process.env.ComSpec || "powershell.exe",
164088
+ args: []
164089
+ };
164090
+ }
164091
+ return {
164092
+ file: process.env.SHELL || "/bin/sh",
164093
+ args: [
164094
+ "-l"
164095
+ ]
164096
+ };
164097
+ }
164098
+ function buildTerminalEnv(sessionId) {
164099
+ const base = {
164100
+ ...process.env,
164101
+ TERM: "xterm-256color",
164102
+ COLORTERM: process.env.COLORTERM ?? "truecolor",
164103
+ FORCE_COLOR: "1",
164104
+ LODY_SESSION_ID: sessionId,
164105
+ LODY_WORKSPACE_SESSION_ID: sessionId
164106
+ };
164107
+ const merged = withDefaultAcpPathEntries(mergeLoginShellEnv(base, getCachedLoginShellEnvSync()));
164108
+ clearManagedGhTokenEnv(merged);
164109
+ for (const key2 of TERMINAL_ENV_BLOCKLIST) {
164110
+ delete merged[key2];
164111
+ }
164112
+ return merged;
164113
+ }
164114
+ function normalizeExitSignal(signal) {
164115
+ if (typeof signal === "undefined") return void 0;
164116
+ return String(signal);
164117
+ }
164118
+ function appendScrollback(current2, data) {
164119
+ const next2 = current2 + data;
164120
+ if (next2.length <= SCROLLBACK_MAX_CHARS) return next2;
164121
+ return next2.slice(next2.length - SCROLLBACK_MAX_CHARS);
164122
+ }
164123
+ function extractLatestTitle(record2, data) {
164124
+ record2.titleParseBuffer = (record2.titleParseBuffer + data).slice(-4096);
164125
+ let latest2 = null;
164126
+ for (const match5 of record2.titleParseBuffer.matchAll(OSC_TITLE_REGEX)) {
164127
+ const title2 = match5[1]?.trim();
164128
+ if (title2) {
164129
+ latest2 = title2;
164130
+ }
164131
+ }
164132
+ return latest2;
164133
+ }
164134
+ class TerminalPtyServiceImpl {
164135
+ logger;
164136
+ resolveSessionWorkdir;
164137
+ records = /* @__PURE__ */ new Map();
164138
+ sessionIndex = /* @__PURE__ */ new Map();
164139
+ pendingSessionOpens = /* @__PURE__ */ new Map();
164140
+ handlers = /* @__PURE__ */ new Set();
164141
+ constructor(options) {
164142
+ this.logger = options.logger;
164143
+ this.resolveSessionWorkdir = options.resolveSessionWorkdir;
164144
+ }
164145
+ list(sessionId) {
164146
+ const ids2 = this.sessionIndex.get(sessionId);
164147
+ if (!ids2) return [];
164148
+ return [
164149
+ ...ids2
164150
+ ].flatMap((terminalId) => {
164151
+ const record2 = this.records.get(terminalId);
164152
+ if (!record2) return [];
164153
+ return [
164154
+ {
164155
+ terminalId: record2.terminalId,
164156
+ title: record2.title,
164157
+ cwd: record2.cwd
164158
+ }
164159
+ ];
164160
+ });
164161
+ }
164162
+ async open(params) {
164163
+ const sessionId = params.sessionId;
164164
+ this.reserveSessionOpen(params.sessionId);
164165
+ try {
164166
+ const cwd = await this.resolveSessionWorkdir(sessionId);
164167
+ const terminalId = randomUUID();
164168
+ const shell = resolveShellCommand();
164169
+ const terminal2 = pty.spawn(shell.file, shell.args, {
164170
+ name: "xterm-256color",
164171
+ cols: params.cols,
164172
+ rows: params.rows,
164173
+ cwd,
164174
+ env: buildTerminalEnv(params.sessionId)
164175
+ });
164176
+ const record2 = {
164177
+ sessionId: params.sessionId,
164178
+ terminalId,
164179
+ cwd,
164180
+ title: basenameForTitle(cwd),
164181
+ pty: terminal2,
164182
+ scrollback: "",
164183
+ titleParseBuffer: ""
164184
+ };
164185
+ this.records.set(terminalId, record2);
164186
+ const sessionTerminals = this.sessionIndex.get(params.sessionId) ?? /* @__PURE__ */ new Set();
164187
+ sessionTerminals.add(terminalId);
164188
+ this.sessionIndex.set(params.sessionId, sessionTerminals);
164189
+ terminal2.onData((data) => {
164190
+ record2.scrollback = appendScrollback(record2.scrollback, data);
164191
+ this.emit({
164192
+ type: "data",
164193
+ terminalId,
164194
+ data
164195
+ });
164196
+ const title2 = extractLatestTitle(record2, data);
164197
+ if (title2 && title2 !== record2.title) {
164198
+ record2.title = title2;
164199
+ this.emit({
164200
+ type: "title",
164201
+ terminalId,
164202
+ title: title2
164203
+ });
164204
+ }
164205
+ });
164206
+ terminal2.onExit(({ exitCode, signal }) => {
164207
+ this.removeRecord(terminalId);
164208
+ this.emit({
164209
+ type: "exit",
164210
+ terminalId,
164211
+ exitCode,
164212
+ ...typeof signal === "undefined" ? {} : {
164213
+ signal: normalizeExitSignal(signal)
164214
+ }
164215
+ });
164216
+ });
164217
+ this.emit({
164218
+ type: "title",
164219
+ terminalId,
164220
+ title: record2.title
164221
+ });
164222
+ this.logger.debug(`[terminal] opened terminalId=${terminalId} sessionId=${params.sessionId} cwd=${cwd}`);
164223
+ return {
164224
+ terminalId,
164225
+ cwd
164226
+ };
164227
+ } finally {
164228
+ this.releaseSessionOpen(params.sessionId);
164229
+ }
164230
+ }
164231
+ attach(terminalId, cols, rows) {
164232
+ const record2 = this.requireRecord(terminalId);
164233
+ record2.pty.resize(cols, rows);
164234
+ return {
164235
+ title: record2.title,
164236
+ scrollback: record2.scrollback
164237
+ };
164238
+ }
164239
+ input(terminalId, data) {
164240
+ this.requireRecord(terminalId).pty.write(data);
164241
+ }
164242
+ resize(terminalId, cols, rows) {
164243
+ this.requireRecord(terminalId).pty.resize(cols, rows);
164244
+ }
164245
+ close(terminalId) {
164246
+ const record2 = this.requireRecord(terminalId);
164247
+ record2.pty.kill();
164248
+ }
164249
+ closeSession(sessionId) {
164250
+ const ids2 = [
164251
+ ...this.sessionIndex.get(sessionId) ?? []
164252
+ ];
164253
+ for (const terminalId of ids2) {
164254
+ this.closeIfPresent(terminalId);
164255
+ }
164256
+ }
164257
+ closeAll() {
164258
+ const ids2 = [
164259
+ ...this.records.keys()
164260
+ ];
164261
+ for (const terminalId of ids2) {
164262
+ this.closeIfPresent(terminalId);
164263
+ }
164264
+ }
164265
+ onEvent(handler) {
164266
+ this.handlers.add(handler);
164267
+ return () => {
164268
+ this.handlers.delete(handler);
164269
+ };
164270
+ }
164271
+ requireRecord(terminalId) {
164272
+ const record2 = this.records.get(terminalId);
164273
+ if (!record2) {
164274
+ throw new Error(`terminal_not_found:${terminalId}`);
164275
+ }
164276
+ return record2;
164277
+ }
164278
+ closeIfPresent(terminalId) {
164279
+ const record2 = this.records.get(terminalId);
164280
+ if (!record2) return;
164281
+ try {
164282
+ record2.pty.kill();
164283
+ } catch (error2) {
164284
+ this.logger.debug(`[terminal] failed to close terminalId=${terminalId}: ${formatErrorMessage$1(error2)}`);
164285
+ this.removeRecord(terminalId);
164286
+ }
164287
+ }
164288
+ removeRecord(terminalId) {
164289
+ const record2 = this.records.get(terminalId);
164290
+ if (!record2) return;
164291
+ this.records.delete(terminalId);
164292
+ const sessionTerminals = this.sessionIndex.get(record2.sessionId);
164293
+ sessionTerminals?.delete(terminalId);
164294
+ if (sessionTerminals && sessionTerminals.size === 0) {
164295
+ this.sessionIndex.delete(record2.sessionId);
164296
+ }
164297
+ }
164298
+ reserveSessionOpen(sessionId) {
164299
+ const currentCount = (this.sessionIndex.get(sessionId)?.size ?? 0) + (this.pendingSessionOpens.get(sessionId) ?? 0);
164300
+ if (currentCount >= TERMINAL_MAX_PER_SESSION) {
164301
+ throw new Error(`terminal_limit_exceeded:${sessionId}:${TERMINAL_MAX_PER_SESSION}`);
164302
+ }
164303
+ this.pendingSessionOpens.set(sessionId, (this.pendingSessionOpens.get(sessionId) ?? 0) + 1);
164304
+ }
164305
+ releaseSessionOpen(sessionId) {
164306
+ const current2 = this.pendingSessionOpens.get(sessionId) ?? 0;
164307
+ if (current2 <= 1) {
164308
+ this.pendingSessionOpens.delete(sessionId);
164309
+ return;
164310
+ }
164311
+ this.pendingSessionOpens.set(sessionId, current2 - 1);
164312
+ }
164313
+ emit(event) {
164314
+ for (const handler of this.handlers) {
164315
+ handler(event);
164316
+ }
164317
+ }
164318
+ }
164319
+ function makeTerminalPtyService(options) {
164320
+ return new TerminalPtyServiceImpl(options);
164321
+ }
164322
+ function defaultIsDirectory(filePath) {
164323
+ try {
164324
+ return fs$3.statSync(filePath).isDirectory();
164325
+ } catch {
164326
+ return false;
164327
+ }
164328
+ }
164329
+ function requireExistingDirectory(filePath, isDirectory2) {
164330
+ if (!isDirectory2(filePath)) {
164331
+ throw new Error(`workdir_unavailable:path_not_found:${filePath}`);
164332
+ }
164333
+ return filePath;
164334
+ }
164335
+ function resolveDefaultChatWorkdir(sessionId, homeDir, isDirectory2) {
164336
+ const workdir = path__default$1.join(homeDir, ".lody", "chats", sessionId);
164337
+ fs$3.mkdirSync(workdir, {
164338
+ recursive: true
164339
+ });
164340
+ return requireExistingDirectory(workdir, isDirectory2);
164341
+ }
164342
+ async function resolveTerminalWorkdirForSession(sessionId, options, seen) {
164343
+ if (seen.has(sessionId)) {
164344
+ throw new Error(`session_parent_cycle:${sessionId}`);
164345
+ }
164346
+ seen.add(sessionId);
164347
+ const lookup2 = await options.lookupSessionMeta(sessionId);
164348
+ if (lookup2.type === "missing") {
164349
+ throw new Error(`session_not_found:${sessionId}`);
164350
+ }
164351
+ if (lookup2.type === "deleted") {
164352
+ throw new Error(`session_deleted:${sessionId}`);
164353
+ }
164354
+ const meta = lookup2.meta;
164355
+ if (meta.isArchived) {
164356
+ throw new Error(`session_archived:${sessionId}`);
164357
+ }
164358
+ if (meta.machineId !== options.machineId) {
164359
+ throw new Error(`session_machine_mismatch:${sessionId}:${meta.machineId}`);
164360
+ }
164361
+ if (meta.parentSessionId) {
164362
+ return await resolveTerminalWorkdirForSession(meta.parentSessionId, options, seen);
164363
+ }
164364
+ const isDirectory2 = options.isDirectory ?? defaultIsDirectory;
164365
+ const homeDir = options.homeDir ?? os__default$1.homedir();
164366
+ const project = meta.project;
164367
+ if (project?.kind === "local") {
164368
+ const rootPath = await options.resolveLocalProjectRootPath(project.localProjectId);
164369
+ if (!rootPath) {
164370
+ throw new Error(`workdir_unavailable:local_project_not_found:${project.localProjectId}`);
164371
+ }
164372
+ if (meta.isWorktree || project.useWorktree === true) {
164373
+ const repoId = deriveRepoIdFromLocalProjectPath(rootPath);
164374
+ return requireExistingDirectory(getWorktreeHostPath(repoId, sessionId, homeDir), isDirectory2);
164375
+ }
164376
+ return requireExistingDirectory(rootPath, isDirectory2);
164377
+ }
164378
+ if (meta.repoFullName) {
164379
+ const repoId = deriveRepoIdFromGitHubRepo$1(meta.repoFullName);
164380
+ return requireExistingDirectory(getWorktreeHostPath(repoId, sessionId, homeDir), isDirectory2);
164381
+ }
164382
+ return resolveDefaultChatWorkdir(sessionId, homeDir, isDirectory2);
164383
+ }
164384
+ async function resolveTerminalWorkdirFromMetadata(options) {
164385
+ return await resolveTerminalWorkdirForSession(options.sessionId, options, /* @__PURE__ */ new Set());
164386
+ }
163169
164387
  const FLEET_RUNTIME_STATE_INTERVAL_MS = 2e3;
163170
164388
  class LodyFleet {
163171
164389
  logger;
@@ -163177,6 +164395,7 @@ export PATH=${toSingleQuotedShellString(ghShimBinDir)}:"$PATH"
163177
164395
  machineName;
163178
164396
  localProjectControlService;
163179
164397
  runtimeStateReporter;
164398
+ terminalPtyService;
163180
164399
  onFatalAuthFailure;
163181
164400
  runtimes = /* @__PURE__ */ new Map();
163182
164401
  startInFlight = /* @__PURE__ */ new Map();
@@ -163202,6 +164421,10 @@ export PATH=${toSingleQuotedShellString(ghShimBinDir)}:"$PATH"
163202
164421
  this.runtimeStateReporter = options.runtimeStateReporter;
163203
164422
  this.onFatalAuthFailure = options.onFatalAuthFailure;
163204
164423
  this.localProjectControlService = new LocalProjectControlService(this.logger);
164424
+ this.terminalPtyService = makeTerminalPtyService({
164425
+ logger: this.logger,
164426
+ resolveSessionWorkdir: async (sessionId) => await this.resolveTerminalSessionWorkdir(sessionId)
164427
+ });
163205
164428
  }
163206
164429
  async start() {
163207
164430
  if (!LODY_AUTH_URL) {
@@ -163250,6 +164473,10 @@ export PATH=${toSingleQuotedShellString(ghShimBinDir)}:"$PATH"
163250
164473
  dispatchSession: async (message) => await this.dispatchLocalSessionControl(message),
163251
164474
  dispatchProject: async (message) => await this.dispatchLocalProjectControl(message)
163252
164475
  });
164476
+ await startLocalTerminalServer({
164477
+ logger: this.logger,
164478
+ terminalPtyService: this.terminalPtyService
164479
+ });
163253
164480
  this.convex = new ConvexClient(LODY_AUTH_URL);
163254
164481
  await new Promise((resolve2, reject) => {
163255
164482
  let initialResolved = false;
@@ -163336,7 +164563,9 @@ export PATH=${toSingleQuotedShellString(ghShimBinDir)}:"$PATH"
163336
164563
  for (const runtime of runtimes) {
163337
164564
  try {
163338
164565
  await runtime.lody.cleanup();
164566
+ runtime.unsubscribeTerminalCleanup();
163339
164567
  } catch (error2) {
164568
+ runtime.unsubscribeTerminalCleanup();
163340
164569
  this.logger.debug(`[fleet] Failed to cleanup workspace runtime ${runtime.workspace.id}: ${formatErrorMessage$1(error2)}`);
163341
164570
  }
163342
164571
  }
@@ -163346,6 +164575,10 @@ export PATH=${toSingleQuotedShellString(ghShimBinDir)}:"$PATH"
163346
164575
  await stopLocalSessionControl().catch((error2) => {
163347
164576
  this.logger.debug(`[fleet] Failed to stop local session control: ${formatErrorMessage$1(error2)}`);
163348
164577
  });
164578
+ await stopLocalTerminalServer().catch((error2) => {
164579
+ this.logger.debug(`[fleet] Failed to stop local terminal server: ${formatErrorMessage$1(error2)}`);
164580
+ });
164581
+ this.terminalPtyService.closeAll();
163349
164582
  }
163350
164583
  async applyWorkspaceList(next2) {
163351
164584
  if (this.stopped) return;
@@ -163404,6 +164637,7 @@ export PATH=${toSingleQuotedShellString(ghShimBinDir)}:"$PATH"
163404
164637
  userId: this.userId,
163405
164638
  machineId: this.machineId,
163406
164639
  machineName: this.machineName,
164640
+ closeSessionTerminals: (sessionId) => this.terminalPtyService.closeSession(sessionId),
163407
164641
  onFatalAuthFailure: this.onFatalAuthFailure
163408
164642
  });
163409
164643
  if (!this.desiredWorkspaces.has(workspace.id) || this.stopped) {
@@ -163420,9 +164654,13 @@ export PATH=${toSingleQuotedShellString(ghShimBinDir)}:"$PATH"
163420
164654
  await lody.cleanup();
163421
164655
  return;
163422
164656
  }
164657
+ const unsubscribeTerminalCleanup = lody.onSessionTerminated((sessionId) => {
164658
+ this.terminalPtyService.closeSession(sessionId);
164659
+ });
163423
164660
  this.runtimes.set(workspace.id, {
163424
164661
  workspace,
163425
- lody
164662
+ lody,
164663
+ unsubscribeTerminalCleanup
163426
164664
  });
163427
164665
  this.logger.debug(`[fleet] Connected workspace: ${workspaceLabel} (${workspace.id})`);
163428
164666
  this.runtimeStateReporter.clearIssue(`workspace_start_failed:${workspace.id}`);
@@ -163480,7 +164718,9 @@ export PATH=${toSingleQuotedShellString(ghShimBinDir)}:"$PATH"
163480
164718
  this.runtimes.delete(workspaceId);
163481
164719
  try {
163482
164720
  await state2.lody.cleanup();
164721
+ state2.unsubscribeTerminalCleanup();
163483
164722
  } catch (error2) {
164723
+ state2.unsubscribeTerminalCleanup();
163484
164724
  this.logger.debug(`[fleet] Failed to cleanup workspace runtime ${workspaceId}: ${formatErrorMessage$1(error2)}`);
163485
164725
  }
163486
164726
  this.refreshRuntimeState();
@@ -163618,6 +164858,93 @@ export PATH=${toSingleQuotedShellString(ghShimBinDir)}:"$PATH"
163618
164858
  }
163619
164859
  return await runtime.lody.dispatchLocalControl(message);
163620
164860
  }
164861
+ async lookupTerminalSessionMeta(runtime, sessionId) {
164862
+ const record2 = await runtime.lody.documentManager.repo.getDocMeta(getSessionRoomId(sessionId));
164863
+ if (!record2?.meta) {
164864
+ return {
164865
+ type: "missing"
164866
+ };
164867
+ }
164868
+ if (isLoroRepoDocDeleted(record2)) {
164869
+ return {
164870
+ type: "deleted"
164871
+ };
164872
+ }
164873
+ return {
164874
+ type: "found",
164875
+ meta: record2.meta
164876
+ };
164877
+ }
164878
+ async assertTerminalSessionAllowed(sessionId) {
164879
+ for (const runtime of this.runtimes.values()) {
164880
+ const lookup2 = await this.lookupTerminalSessionMeta(runtime, sessionId);
164881
+ if (lookup2.type === "missing") {
164882
+ continue;
164883
+ }
164884
+ if (lookup2.type === "deleted") {
164885
+ throw new Error(`session_deleted:${sessionId}`);
164886
+ }
164887
+ if (lookup2.meta.isArchived) {
164888
+ throw new Error(`session_archived:${sessionId}`);
164889
+ }
164890
+ if (lookup2.meta.machineId !== this.machineId) {
164891
+ throw new Error(`session_machine_mismatch:${sessionId}:${lookup2.meta.machineId}`);
164892
+ }
164893
+ return;
164894
+ }
164895
+ }
164896
+ async resolveActiveTerminalSessionWorkdir(sessionId) {
164897
+ const matches = [];
164898
+ for (const runtime of this.runtimes.values()) {
164899
+ const workdir = await runtime.lody.resolveSessionWorkdir(sessionId);
164900
+ if (workdir) {
164901
+ matches.push(workdir);
164902
+ }
164903
+ }
164904
+ if (matches.length > 1) {
164905
+ throw new Error(`session_ambiguous:${sessionId}`);
164906
+ }
164907
+ return matches[0] ?? null;
164908
+ }
164909
+ async resolveTerminalSessionWorkdirFromMetadata(sessionId) {
164910
+ const matches = [];
164911
+ const errors2 = [];
164912
+ for (const runtime of this.runtimes.values()) {
164913
+ try {
164914
+ const workdir = await resolveTerminalWorkdirFromMetadata({
164915
+ sessionId,
164916
+ machineId: this.machineId,
164917
+ lookupSessionMeta: async (targetSessionId) => await this.lookupTerminalSessionMeta(runtime, targetSessionId),
164918
+ resolveLocalProjectRootPath: async (localProjectId) => await resolveWorkspaceLocalProjectRootPath(runtime.lody.documentManager.repo, this.machineId, localProjectId)
164919
+ });
164920
+ matches.push(workdir);
164921
+ } catch (error2) {
164922
+ const message = formatErrorMessage$1(error2);
164923
+ if (message.startsWith("session_not_found:")) {
164924
+ continue;
164925
+ }
164926
+ errors2.push(error2 instanceof Error ? error2 : new Error(message));
164927
+ }
164928
+ }
164929
+ if (matches.length > 1) {
164930
+ throw new Error(`session_ambiguous:${sessionId}`);
164931
+ }
164932
+ if (matches.length === 1) {
164933
+ return matches[0];
164934
+ }
164935
+ if (errors2[0]) {
164936
+ throw errors2[0];
164937
+ }
164938
+ throw new Error(`session_not_found:${sessionId}`);
164939
+ }
164940
+ async resolveTerminalSessionWorkdir(sessionId) {
164941
+ await this.assertTerminalSessionAllowed(sessionId);
164942
+ const activeWorkdir = await this.resolveActiveTerminalSessionWorkdir(sessionId);
164943
+ if (activeWorkdir) {
164944
+ return activeWorkdir;
164945
+ }
164946
+ return await this.resolveTerminalSessionWorkdirFromMetadata(sessionId);
164947
+ }
163621
164948
  toProjectControlError(type2, error2, message, data) {
163622
164949
  return {
163623
164950
  ok: false,
@@ -168165,9 +169492,9 @@ Received ${signal}, shutting down gracefully...` : "\nShutting down gracefully..
168165
169492
  (function(module2) {
168166
169493
  const ansiEscapes2 = module2.exports;
168167
169494
  module2.exports.default = ansiEscapes2;
168168
- const ESC = "\x1B[";
169495
+ const ESC2 = "\x1B[";
168169
169496
  const OSC = "\x1B]";
168170
- const BEL = "\x07";
169497
+ const BEL2 = "\x07";
168171
169498
  const SEP = ";";
168172
169499
  const isTerminalApp = process.env.TERM_PROGRAM === "Apple_Terminal";
168173
169500
  ansiEscapes2.cursorTo = (x, y) => {
@@ -168175,9 +169502,9 @@ Received ${signal}, shutting down gracefully...` : "\nShutting down gracefully..
168175
169502
  throw new TypeError("The `x` argument is required");
168176
169503
  }
168177
169504
  if (typeof y !== "number") {
168178
- return ESC + (x + 1) + "G";
169505
+ return ESC2 + (x + 1) + "G";
168179
169506
  }
168180
- return ESC + (y + 1) + ";" + (x + 1) + "H";
169507
+ return ESC2 + (y + 1) + ";" + (x + 1) + "H";
168181
169508
  };
168182
169509
  ansiEscapes2.cursorMove = (x, y) => {
168183
169510
  if (typeof x !== "number") {
@@ -168185,29 +169512,29 @@ Received ${signal}, shutting down gracefully...` : "\nShutting down gracefully..
168185
169512
  }
168186
169513
  let ret = "";
168187
169514
  if (x < 0) {
168188
- ret += ESC + -x + "D";
169515
+ ret += ESC2 + -x + "D";
168189
169516
  } else if (x > 0) {
168190
- ret += ESC + x + "C";
169517
+ ret += ESC2 + x + "C";
168191
169518
  }
168192
169519
  if (y < 0) {
168193
- ret += ESC + -y + "A";
169520
+ ret += ESC2 + -y + "A";
168194
169521
  } else if (y > 0) {
168195
- ret += ESC + y + "B";
169522
+ ret += ESC2 + y + "B";
168196
169523
  }
168197
169524
  return ret;
168198
169525
  };
168199
- ansiEscapes2.cursorUp = (count2 = 1) => ESC + count2 + "A";
168200
- ansiEscapes2.cursorDown = (count2 = 1) => ESC + count2 + "B";
168201
- ansiEscapes2.cursorForward = (count2 = 1) => ESC + count2 + "C";
168202
- ansiEscapes2.cursorBackward = (count2 = 1) => ESC + count2 + "D";
168203
- ansiEscapes2.cursorLeft = ESC + "G";
168204
- ansiEscapes2.cursorSavePosition = isTerminalApp ? "\x1B7" : ESC + "s";
168205
- ansiEscapes2.cursorRestorePosition = isTerminalApp ? "\x1B8" : ESC + "u";
168206
- ansiEscapes2.cursorGetPosition = ESC + "6n";
168207
- ansiEscapes2.cursorNextLine = ESC + "E";
168208
- ansiEscapes2.cursorPrevLine = ESC + "F";
168209
- ansiEscapes2.cursorHide = ESC + "?25l";
168210
- ansiEscapes2.cursorShow = ESC + "?25h";
169526
+ ansiEscapes2.cursorUp = (count2 = 1) => ESC2 + count2 + "A";
169527
+ ansiEscapes2.cursorDown = (count2 = 1) => ESC2 + count2 + "B";
169528
+ ansiEscapes2.cursorForward = (count2 = 1) => ESC2 + count2 + "C";
169529
+ ansiEscapes2.cursorBackward = (count2 = 1) => ESC2 + count2 + "D";
169530
+ ansiEscapes2.cursorLeft = ESC2 + "G";
169531
+ ansiEscapes2.cursorSavePosition = isTerminalApp ? "\x1B7" : ESC2 + "s";
169532
+ ansiEscapes2.cursorRestorePosition = isTerminalApp ? "\x1B8" : ESC2 + "u";
169533
+ ansiEscapes2.cursorGetPosition = ESC2 + "6n";
169534
+ ansiEscapes2.cursorNextLine = ESC2 + "E";
169535
+ ansiEscapes2.cursorPrevLine = ESC2 + "F";
169536
+ ansiEscapes2.cursorHide = ESC2 + "?25l";
169537
+ ansiEscapes2.cursorShow = ESC2 + "?25h";
168211
169538
  ansiEscapes2.eraseLines = (count2) => {
168212
169539
  let clear = "";
168213
169540
  for (let i2 = 0; i2 < count2; i2++) {
@@ -168218,17 +169545,17 @@ Received ${signal}, shutting down gracefully...` : "\nShutting down gracefully..
168218
169545
  }
168219
169546
  return clear;
168220
169547
  };
168221
- ansiEscapes2.eraseEndLine = ESC + "K";
168222
- ansiEscapes2.eraseStartLine = ESC + "1K";
168223
- ansiEscapes2.eraseLine = ESC + "2K";
168224
- ansiEscapes2.eraseDown = ESC + "J";
168225
- ansiEscapes2.eraseUp = ESC + "1J";
168226
- ansiEscapes2.eraseScreen = ESC + "2J";
168227
- ansiEscapes2.scrollUp = ESC + "S";
168228
- ansiEscapes2.scrollDown = ESC + "T";
169548
+ ansiEscapes2.eraseEndLine = ESC2 + "K";
169549
+ ansiEscapes2.eraseStartLine = ESC2 + "1K";
169550
+ ansiEscapes2.eraseLine = ESC2 + "2K";
169551
+ ansiEscapes2.eraseDown = ESC2 + "J";
169552
+ ansiEscapes2.eraseUp = ESC2 + "1J";
169553
+ ansiEscapes2.eraseScreen = ESC2 + "2J";
169554
+ ansiEscapes2.scrollUp = ESC2 + "S";
169555
+ ansiEscapes2.scrollDown = ESC2 + "T";
168229
169556
  ansiEscapes2.clearScreen = "\x1Bc";
168230
- ansiEscapes2.clearTerminal = process.platform === "win32" ? `${ansiEscapes2.eraseScreen}${ESC}0f` : `${ansiEscapes2.eraseScreen}${ESC}3J${ESC}H`;
168231
- ansiEscapes2.beep = BEL;
169557
+ ansiEscapes2.clearTerminal = process.platform === "win32" ? `${ansiEscapes2.eraseScreen}${ESC2}0f` : `${ansiEscapes2.eraseScreen}${ESC2}3J${ESC2}H`;
169558
+ ansiEscapes2.beep = BEL2;
168232
169559
  ansiEscapes2.link = (text, url) => {
168233
169560
  return [
168234
169561
  OSC,
@@ -168236,13 +169563,13 @@ Received ${signal}, shutting down gracefully...` : "\nShutting down gracefully..
168236
169563
  SEP,
168237
169564
  SEP,
168238
169565
  url,
168239
- BEL,
169566
+ BEL2,
168240
169567
  text,
168241
169568
  OSC,
168242
169569
  "8",
168243
169570
  SEP,
168244
169571
  SEP,
168245
- BEL
169572
+ BEL2
168246
169573
  ].join("");
168247
169574
  };
168248
169575
  ansiEscapes2.image = (buffer2, options = {}) => {
@@ -168256,10 +169583,10 @@ Received ${signal}, shutting down gracefully...` : "\nShutting down gracefully..
168256
169583
  if (options.preserveAspectRatio === false) {
168257
169584
  ret += ";preserveAspectRatio=0";
168258
169585
  }
168259
- return ret + ":" + buffer2.toString("base64") + BEL;
169586
+ return ret + ":" + buffer2.toString("base64") + BEL2;
168260
169587
  };
168261
169588
  ansiEscapes2.iTerm = {
168262
- setCwd: (cwd = process.cwd()) => `${OSC}50;CurrentDir=${cwd}${BEL}`,
169589
+ setCwd: (cwd = process.cwd()) => `${OSC}50;CurrentDir=${cwd}${BEL2}`,
168263
169590
  annotation: (message, options = {}) => {
168264
169591
  let ret = `${OSC}1337;`;
168265
169592
  const hasX = typeof options.x !== "undefined";
@@ -168282,7 +169609,7 @@ Received ${signal}, shutting down gracefully...` : "\nShutting down gracefully..
168282
169609
  } else {
168283
169610
  ret += message;
168284
169611
  }
168285
- return ret + BEL;
169612
+ return ret + BEL2;
168286
169613
  }
168287
169614
  };
168288
169615
  })(ansiEscapes$1);
@@ -188151,7 +189478,7 @@ ${page}${helpTipBottom}${choiceDescription}${ansiEscapes.cursorHide}`;
188151
189478
  throttled === null || throttled === void 0 ? void 0 : throttled.unsubscribe();
188152
189479
  throttled = null;
188153
189480
  if (trailing) {
188154
- send();
189481
+ send2();
188155
189482
  isComplete && subscriber.complete();
188156
189483
  }
188157
189484
  };
@@ -188162,7 +189489,7 @@ ${page}${helpTipBottom}${choiceDescription}${ansiEscapes.cursorHide}`;
188162
189489
  var startThrottle = function(value) {
188163
189490
  return throttled = innerFrom_1.innerFrom(durationSelector(value)).subscribe(OperatorSubscriber_1.createOperatorSubscriber(subscriber, endThrottling, cleanupThrottling));
188164
189491
  };
188165
- var send = function() {
189492
+ var send2 = function() {
188166
189493
  if (hasValue) {
188167
189494
  hasValue = false;
188168
189495
  var value = sendValue;
@@ -188174,7 +189501,7 @@ ${page}${helpTipBottom}${choiceDescription}${ansiEscapes.cursorHide}`;
188174
189501
  source.subscribe(OperatorSubscriber_1.createOperatorSubscriber(subscriber, function(value) {
188175
189502
  hasValue = true;
188176
189503
  sendValue = value;
188177
- !(throttled && !throttled.closed) && (leading ? send() : startThrottle(value));
189504
+ !(throttled && !throttled.closed) && (leading ? send2() : startThrottle(value));
188178
189505
  }, function() {
188179
189506
  isComplete = true;
188180
189507
  !(trailing && hasValue && throttled && !throttled.closed) && subscriber.complete();
@@ -191068,7 +192395,7 @@ ${page}${helpTipBottom}${choiceDescription}${ansiEscapes.cursorHide}`;
191068
192395
  }
191069
192396
  function hyperlink(url, text) {
191070
192397
  const OSC = "\x1B]";
191071
- const BEL = "\x07";
192398
+ const BEL2 = "\x07";
191072
192399
  const SEP = ";";
191073
192400
  return [
191074
192401
  OSC,
@@ -191076,13 +192403,13 @@ ${page}${helpTipBottom}${choiceDescription}${ansiEscapes.cursorHide}`;
191076
192403
  SEP,
191077
192404
  SEP,
191078
192405
  url || text,
191079
- BEL,
192406
+ BEL2,
191080
192407
  text,
191081
192408
  OSC,
191082
192409
  "8",
191083
192410
  SEP,
191084
192411
  SEP,
191085
- BEL
192412
+ BEL2
191086
192413
  ].join("");
191087
192414
  }
191088
192415
  utils$1 = {
@@ -193234,7 +194561,7 @@ ${page}${helpTipBottom}${choiceDescription}${ansiEscapes.cursorHide}`;
193234
194561
  }
193235
194562
  function inferAgentConfigCliType(agentType) {
193236
194563
  const normalized = normalizeCliValue(agentType)?.toLowerCase();
193237
- return normalized === "claude" || normalized === "codex" ? "builtin" : "registry";
194564
+ return normalized && isBuiltinAgentType(normalized) ? "builtin" : "registry";
193238
194565
  }
193239
194566
  function parseEnvAssignments(entries) {
193240
194567
  const parsed = {};
@@ -194247,7 +195574,7 @@ ${entry.text}`).join("\n\n");
194247
195574
  }
194248
195575
  throw new Error(`Multiple agent configs are available; pass --agent-config. Candidates: ${formatAgentConfigCandidates(configs)}`);
194249
195576
  }
194250
- async function resolveLocalProjectRefOrThrow(manager, machineId, selector, requestedBranch) {
195577
+ async function resolveLocalProjectRefOrThrow(manager, machineId, selector, requestedBranch, useWorktree) {
194251
195578
  const machineRoomId = getMachineRoomId(machineId);
194252
195579
  const raw2 = await manager.repo.getDocMeta(machineRoomId);
194253
195580
  const machineMeta = raw2?.meta;
@@ -194267,18 +195594,26 @@ ${entry.text}`).join("\n\n");
194267
195594
  throw new Error(`Local project selector is ambiguous: ${normalizedSelector}. Use a project id instead.`);
194268
195595
  }
194269
195596
  const project = matches[0];
194270
- const branch = await resolveLocalProjectBranchForCreate(project, requestedBranch);
195597
+ const branch = await resolveLocalProjectBranchForCreate(project, requestedBranch, {
195598
+ requireGit: useWorktree === true
195599
+ });
194271
195600
  return {
194272
195601
  kind: "local",
194273
195602
  localProjectId: project.id,
194274
195603
  ...branch ? {
194275
195604
  branch
195605
+ } : {},
195606
+ ...useWorktree === true ? {
195607
+ useWorktree: true
194276
195608
  } : {}
194277
195609
  };
194278
195610
  }
194279
- async function resolveLocalProjectBranchForCreate(project, requestedBranch) {
195611
+ async function resolveLocalProjectBranchForCreate(project, requestedBranch, options = {}) {
194280
195612
  const gitState = await getLocalProjectGitStateAtRootPath(project.rootPath);
194281
195613
  if (!gitState.git) {
195614
+ if (options.requireGit === true) {
195615
+ throw new Error("Cannot use --worktree with a local project that is not a git repository.");
195616
+ }
194282
195617
  if (requestedBranch) {
194283
195618
  throw new Error("Cannot use --branch with a local project that is not a git repository.");
194284
195619
  }
@@ -194634,6 +195969,10 @@ ${entry.text}`).join("\n\n");
194634
195969
  if (normalizedRepo && normalizedLocalProject) {
194635
195970
  throw new Error("Pass either --repo or --local-project, not both.");
194636
195971
  }
195972
+ const useLocalProjectWorktree = options.worktree === true;
195973
+ if (useLocalProjectWorktree && !normalizedLocalProject) {
195974
+ throw new Error("Pass --worktree together with --local-project.");
195975
+ }
194637
195976
  const requestedBranch = normalizeCliValue(options.branch);
194638
195977
  const envOverrides = parseEnvAssignments(options.env);
194639
195978
  let project;
@@ -194648,7 +195987,7 @@ ${entry.text}`).join("\n\n");
194648
195987
  branch
194649
195988
  };
194650
195989
  } else if (normalizedLocalProject) {
194651
- project = await resolveLocalProjectRefOrThrow(manager, auth.machineId, normalizedLocalProject, requestedBranch);
195990
+ project = await resolveLocalProjectRefOrThrow(manager, auth.machineId, normalizedLocalProject, requestedBranch, useLocalProjectWorktree);
194652
195991
  }
194653
195992
  const sessionId = v4();
194654
195993
  const sessionRoomId = getSessionRoomId(sessionId);
@@ -194850,7 +196189,7 @@ ${entry.text}`).join("\n\n");
194850
196189
  process.exit(code2);
194851
196190
  }
194852
196191
  }
194853
- const sessionCreateCommand = new Command("create").description("Create a new session on the current machine").option("--workspace <idOrSlug>", "Target workspace id or slug").option("--agent-config <idOrName>", "Agent config id or name").option("--title <title>", "Session title").option("--repo <owner/repo>", "GitHub repository to attach").option("--local-project <id|name|path>", "Local project id, name, or root path").option("--branch <name>", "Git branch to use for GitHub repos or local git projects").option("--mode <modeId>", "ACP mode override").option("--model <modelId>", "ACP model override").option("--env <keyValue>", "Extra environment variable in KEY=VALUE form; repeatable", collectListOption, []).option("--prompt <text>", "Prompt text").option("--prompt-file <path>", "Read prompt text from file, or - for stdin").option("--json", "Print JSON output").option("--jsonl", "Print JSON Lines output").option("--timeout <seconds>", "Wait timeout in seconds for structured output", parsePositiveIntOption).option("--debug", "Enable debug output").argument("[prompt]", "Prompt text").action(async (promptArg, options) => {
196192
+ const sessionCreateCommand = new Command("create").description("Create a new session on the current machine").option("--workspace <idOrSlug>", "Target workspace id or slug").option("--agent-config <idOrName>", "Agent config id or name").option("--title <title>", "Session title").option("--repo <owner/repo>", "GitHub repository to attach").option("--local-project <id|name|path>", "Local project id, name, or root path").option("--worktree", "Create an isolated git worktree for --local-project").option("--branch <name>", "Git branch to use for GitHub repos or local git projects").option("--mode <modeId>", "ACP mode override").option("--model <modelId>", "ACP model override").option("--env <keyValue>", "Extra environment variable in KEY=VALUE form; repeatable", collectListOption, []).option("--prompt <text>", "Prompt text").option("--prompt-file <path>", "Read prompt text from file, or - for stdin").option("--json", "Print JSON output").option("--jsonl", "Print JSON Lines output").option("--timeout <seconds>", "Wait timeout in seconds for structured output", parsePositiveIntOption).option("--debug", "Enable debug output").argument("[prompt]", "Prompt text").action(async (promptArg, options) => {
194854
196193
  await runSessionCommand(options, async () => {
194855
196194
  const outputMode = resolveStructuredOutputMode(options);
194856
196195
  const createStartMs = Date.now();