lody 0.52.3 → 0.53.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +1046 -201
  2. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -23,9 +23,9 @@ import require$$1$4 from "async_hooks";
23
23
  import require$$1$5, { execFile, spawn as spawn$1 } from "node:child_process";
24
24
  import fs$6, { readdir, readFile, createReadStream, existsSync, readFileSync as readFileSync$1, promises } from "node:fs";
25
25
  import * as os from "node:os";
26
- import os__default$1 from "node:os";
26
+ import os__default$1, { homedir } from "node:os";
27
27
  import * as path$3 from "node:path";
28
- import path__default$1, { join as join$2, dirname as dirname$1, posix, sep as sep$1, resolve as resolve$2, relative, isAbsolute } from "node:path";
28
+ import path__default$1, { join as join$2, dirname as dirname$1, posix, sep as sep$1, delimiter, normalize as normalize$2, resolve as resolve$2, relative, isAbsolute } from "node:path";
29
29
  import * as util$2 from "node:util";
30
30
  import util__default, { format as format$8, promisify, inspect as inspect$2 } from "node:util";
31
31
  import * as readline$1 from "node:readline";
@@ -5144,8 +5144,8 @@ let __tla = Promise.all([
5144
5144
  }
5145
5145
  return debug2;
5146
5146
  }
5147
- function extend2(namespace, delimiter) {
5148
- const newDebug = createDebug(this.namespace + (typeof delimiter === "undefined" ? ":" : delimiter) + namespace);
5147
+ function extend2(namespace, delimiter2) {
5148
+ const newDebug = createDebug(this.namespace + (typeof delimiter2 === "undefined" ? ":" : delimiter2) + namespace);
5149
5149
  newDebug.log = this.log;
5150
5150
  return newDebug;
5151
5151
  }
@@ -8644,7 +8644,7 @@ Error:`, e);
8644
8644
  }
8645
8645
  return newLine;
8646
8646
  }
8647
- function safeJoin(input2, delimiter) {
8647
+ function safeJoin(input2, delimiter2) {
8648
8648
  if (!Array.isArray(input2)) {
8649
8649
  return "";
8650
8650
  }
@@ -8661,7 +8661,7 @@ Error:`, e);
8661
8661
  output.push("[value cannot be serialized]");
8662
8662
  }
8663
8663
  }
8664
- return output.join(delimiter);
8664
+ return output.join(delimiter2);
8665
8665
  }
8666
8666
  function isMatchingPattern(value, pattern2, requireExactStringMatch = false) {
8667
8667
  if (!isString$4(value)) {
@@ -36822,7 +36822,7 @@ Mongoose Error Code: ${error2.code}` : ""}`
36822
36822
  return client;
36823
36823
  }
36824
36824
  const name = "lody";
36825
- const version$4 = "0.52.3";
36825
+ const version$4 = "0.53.0";
36826
36826
  const description$1 = "Lody Agent CLI tool for managing remote command execution";
36827
36827
  const type$2 = "module";
36828
36828
  const main$3 = "dist/index.js";
@@ -36866,7 +36866,7 @@ Mongoose Error Code: ${error2.code}` : ""}`
36866
36866
  };
36867
36867
  const optionalDependencies = {
36868
36868
  "acp-extension-claude": "0.34.3",
36869
- "acp-extension-codex": "0.14.3"
36869
+ "acp-extension-codex": "0.14.5"
36870
36870
  };
36871
36871
  const devDependencies = {
36872
36872
  "@agentclientprotocol/sdk": "catalog:",
@@ -48967,8 +48967,8 @@ ${originalIndentation}`;
48967
48967
  return "#" + Array(6 - color.length + 1).join("0") + color;
48968
48968
  };
48969
48969
  var hex = getDefaultExportFromCjs2(textHex);
48970
- function colorspace(namespace, delimiter) {
48971
- const split2 = namespace.split(delimiter || ":");
48970
+ function colorspace(namespace, delimiter2) {
48971
+ const split2 = namespace.split(delimiter2 || ":");
48972
48972
  let base = hex(split2[0]);
48973
48973
  if (!split2.length) return base;
48974
48974
  for (let i2 = 0, l = split2.length - 1; i2 < l; i2++) {
@@ -73599,13 +73599,14 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
73599
73599
  const getLoroPreviewCommentStreamId = (workspaceId, sessionId) => `${workspaceId}:${LORO_PREVIEW_COMMENT_STREAM_SEGMENT}:${sessionId}`;
73600
73600
  const LODY_FULL_ACCESS_MODE_ID = "lodyFullAccess";
73601
73601
  const isFullAccessModeId = (modeId) => modeId === LODY_FULL_ACCESS_MODE_ID || modeId === "bypassPermissions" || modeId === "full-access";
73602
- const ACP_CAPABILITY_CACHE_VERSION = 1;
73602
+ const ACP_CAPABILITY_CACHE_VERSION = 2;
73603
73603
  const getAcpCapabilityCacheKey = (cliType, agentType) => `${cliType}:${agentType}`;
73604
+ const isAcpCapabilityCacheEntryCurrent = (entry2) => entry2?.cacheVersion === ACP_CAPABILITY_CACHE_VERSION;
73604
73605
  const getAcpCapabilityCacheStaleReason = (entry2, expectedSourceVersion) => {
73605
73606
  if (!entry2) {
73606
73607
  return "missing";
73607
73608
  }
73608
- if (entry2.cacheVersion !== ACP_CAPABILITY_CACHE_VERSION) {
73609
+ if (!isAcpCapabilityCacheEntryCurrent(entry2)) {
73609
73610
  return "cache-version-mismatch";
73610
73611
  }
73611
73612
  if (entry2.sourceVersion !== expectedSourceVersion) {
@@ -77626,6 +77627,8 @@ Task description:
77626
77627
  return Array.isArray(v.images) && v.images.length > 0 ? true : "Missing images";
77627
77628
  case "plan":
77628
77629
  return Array.isArray(v.entries) ? true : "Missing entries";
77630
+ case "proposed_plan":
77631
+ return typeof v.turnId === "string" && typeof v.markdown === "string" && typeof v.status === "string" && typeof v.isLatest === "boolean" ? true : "Missing proposed plan metadata";
77629
77632
  case "goal":
77630
77633
  return typeof v.threadId === "string" && typeof v.objective === "string" && typeof v.status === "string" && typeof v.tokensUsed === "number" && typeof v.timeUsedSeconds === "number" && typeof v.createdAt === "number" && typeof v.updatedAt === "number" ? true : "Missing goal metadata";
77631
77634
  case "tool_call":
@@ -79151,6 +79154,17 @@ Task description:
79151
79154
  type: literal("plan"),
79152
79155
  entries: array$3(PlanEntrySchema)
79153
79156
  }),
79157
+ object$1({
79158
+ type: literal("proposed_plan"),
79159
+ turnId: string$2(),
79160
+ markdown: string$2(),
79161
+ status: _enum$1([
79162
+ "delta",
79163
+ "completed",
79164
+ "cleared"
79165
+ ]),
79166
+ isLatest: boolean()
79167
+ }),
79154
79168
  object$1({
79155
79169
  type: literal("goal"),
79156
79170
  threadId: string$2(),
@@ -79468,12 +79482,12 @@ Task description:
79468
79482
  {
79469
79483
  id: "auggie",
79470
79484
  name: "Auggie CLI",
79471
- version: "0.26.0",
79485
+ version: "0.27.2",
79472
79486
  description: "Augment Code's powerful software agent, backed by industry-leading context engine",
79473
79487
  icon: "https://cdn.agentclientprotocol.com/registry/v1/latest/auggie.svg",
79474
79488
  distribution: {
79475
79489
  npx: {
79476
- package: "@augmentcode/auggie@0.26.0",
79490
+ package: "@augmentcode/auggie@0.27.2",
79477
79491
  args: [
79478
79492
  "--acp"
79479
79493
  ],
@@ -79498,12 +79512,12 @@ Task description:
79498
79512
  {
79499
79513
  id: "cline",
79500
79514
  name: "Cline",
79501
- version: "3.0.0",
79515
+ version: "3.0.7",
79502
79516
  description: "Autonomous coding agent CLI - capable of creating/editing files, running commands, using the browser, and more",
79503
79517
  icon: "https://cdn.agentclientprotocol.com/registry/v1/latest/cline.svg",
79504
79518
  distribution: {
79505
79519
  npx: {
79506
- package: "cline@3.0.0",
79520
+ package: "cline@3.0.7",
79507
79521
  args: [
79508
79522
  "--acp"
79509
79523
  ]
@@ -79513,12 +79527,12 @@ Task description:
79513
79527
  {
79514
79528
  id: "codebuddy-code",
79515
79529
  name: "Codebuddy Code",
79516
- version: "2.97.0",
79530
+ version: "2.97.3",
79517
79531
  description: "Tencent Cloud's official intelligent coding tool",
79518
79532
  icon: "https://cdn.agentclientprotocol.com/registry/v1/latest/codebuddy-code.svg",
79519
79533
  distribution: {
79520
79534
  npx: {
79521
- package: "@tencent-ai/codebuddy-code@2.97.0",
79535
+ package: "@tencent-ai/codebuddy-code@2.97.3",
79522
79536
  args: [
79523
79537
  "--acp"
79524
79538
  ]
@@ -79573,12 +79587,12 @@ Task description:
79573
79587
  {
79574
79588
  id: "dirac",
79575
79589
  name: "Dirac",
79576
- version: "0.3.41",
79590
+ version: "0.3.44",
79577
79591
  description: "Reduces API costs by more than 50%, produces better and faster work. Uses Hash anchored parallel edits, AST manipulation and a whole lot of neat optimizations. Fully Open Source.",
79578
79592
  icon: "https://cdn.agentclientprotocol.com/registry/v1/latest/dirac.svg",
79579
79593
  distribution: {
79580
79594
  npx: {
79581
- package: "dirac-cli@0.3.41",
79595
+ package: "dirac-cli@0.3.44",
79582
79596
  args: [
79583
79597
  "--acp"
79584
79598
  ]
@@ -79588,16 +79602,16 @@ Task description:
79588
79602
  {
79589
79603
  id: "factory-droid",
79590
79604
  name: "Factory Droid",
79591
- version: "0.124.0",
79605
+ version: "0.129.0",
79592
79606
  description: "Factory Droid - AI coding agent powered by Factory AI",
79593
79607
  icon: "https://cdn.agentclientprotocol.com/registry/v1/latest/factory-droid.svg",
79594
79608
  distribution: {
79595
79609
  npx: {
79596
- package: "droid@0.124.0",
79610
+ package: "droid@0.129.0",
79597
79611
  args: [
79598
79612
  "exec",
79599
79613
  "--output-format",
79600
- "acp-daemon"
79614
+ "acp"
79601
79615
  ],
79602
79616
  env: {
79603
79617
  DROID_DISABLE_AUTO_UPDATE: "true",
@@ -79609,12 +79623,12 @@ Task description:
79609
79623
  {
79610
79624
  id: "fast-agent",
79611
79625
  name: "fast-agent",
79612
- version: "0.7.3",
79626
+ version: "0.7.6",
79613
79627
  description: "Code and build agents with comprehensive multi-provider support",
79614
79628
  icon: "https://cdn.agentclientprotocol.com/registry/v1/latest/fast-agent.svg",
79615
79629
  distribution: {
79616
79630
  uvx: {
79617
- package: "fast-agent-acp==0.7.3",
79631
+ package: "fast-agent-acp==0.7.6",
79618
79632
  args: [
79619
79633
  "-x"
79620
79634
  ]
@@ -79639,12 +79653,12 @@ Task description:
79639
79653
  {
79640
79654
  id: "github-copilot-cli",
79641
79655
  name: "GitHub Copilot",
79642
- version: "1.0.46",
79656
+ version: "1.0.50",
79643
79657
  description: "GitHub's AI pair programmer",
79644
79658
  icon: "https://cdn.agentclientprotocol.com/registry/v1/latest/github-copilot-cli.svg",
79645
79659
  distribution: {
79646
79660
  npx: {
79647
- package: "@github/copilot@1.0.46",
79661
+ package: "@github/copilot@1.0.50",
79648
79662
  args: [
79649
79663
  "--acp"
79650
79664
  ]
@@ -79654,19 +79668,19 @@ Task description:
79654
79668
  {
79655
79669
  id: "glm-acp-agent",
79656
79670
  name: "GLM Agent",
79657
- version: "1.1.3",
79671
+ version: "1.1.4",
79658
79672
  description: "ACP agent powered by Zhipu AI's GLM Coding Plan models (glm-5.1, glm-5-turbo, glm-4.7, glm-4.5-air). Supports streaming, tool calls, mid-session model switching, image input via Z.AI Coding Plan Vision MCP, and session load/fork/resume with on-disk persistence.",
79659
79673
  icon: "https://cdn.agentclientprotocol.com/registry/v1/latest/glm-acp-agent.svg",
79660
79674
  distribution: {
79661
79675
  npx: {
79662
- package: "glm-acp-agent@1.1.3"
79676
+ package: "glm-acp-agent@1.1.4"
79663
79677
  }
79664
79678
  }
79665
79679
  },
79666
79680
  {
79667
79681
  id: "goose",
79668
79682
  name: "goose",
79669
- version: "1.33.1",
79683
+ version: "1.34.1",
79670
79684
  description: "A local, extensible, open source AI agent that automates engineering tasks",
79671
79685
  icon: "https://cdn.agentclientprotocol.com/registry/v1/latest/goose.svg",
79672
79686
  distribution: {
@@ -79702,12 +79716,12 @@ Task description:
79702
79716
  {
79703
79717
  id: "kilo",
79704
79718
  name: "Kilo",
79705
- version: "7.2.52",
79719
+ version: "7.3.0",
79706
79720
  description: "The open source coding agent",
79707
79721
  icon: "https://cdn.agentclientprotocol.com/registry/v1/latest/kilo.svg",
79708
79722
  distribution: {
79709
79723
  npx: {
79710
- package: "@kilocode/cli@7.2.52",
79724
+ package: "@kilocode/cli@7.3.0",
79711
79725
  args: [
79712
79726
  "acp"
79713
79727
  ]
@@ -79717,7 +79731,7 @@ Task description:
79717
79731
  {
79718
79732
  id: "kimi",
79719
79733
  name: "Kimi CLI",
79720
- version: "1.43.0",
79734
+ version: "1.44.0",
79721
79735
  description: "Moonshot AI's coding assistant",
79722
79736
  icon: "https://cdn.agentclientprotocol.com/registry/v1/latest/kimi.svg",
79723
79737
  distribution: {
@@ -79769,12 +79783,12 @@ Task description:
79769
79783
  {
79770
79784
  id: "nova",
79771
79785
  name: "Nova",
79772
- version: "1.1.8",
79786
+ version: "1.1.9",
79773
79787
  description: "Nova by Compass AI - a fully-fledged software engineer at your command",
79774
79788
  icon: "https://cdn.agentclientprotocol.com/registry/v1/latest/nova.svg",
79775
79789
  distribution: {
79776
79790
  npx: {
79777
- package: "@compass-ai/nova@1.1.8",
79791
+ package: "@compass-ai/nova@1.1.9",
79778
79792
  args: [
79779
79793
  "acp"
79780
79794
  ]
@@ -79784,7 +79798,7 @@ Task description:
79784
79798
  {
79785
79799
  id: "opencode",
79786
79800
  name: "OpenCode",
79787
- version: "1.14.48",
79801
+ version: "1.15.5",
79788
79802
  description: "The open source coding agent",
79789
79803
  icon: "https://cdn.agentclientprotocol.com/registry/v1/latest/opencode.svg",
79790
79804
  distribution: {
@@ -79802,24 +79816,24 @@ Task description:
79802
79816
  {
79803
79817
  id: "pi-acp",
79804
79818
  name: "pi ACP",
79805
- version: "0.0.26",
79819
+ version: "0.0.27",
79806
79820
  description: "ACP adapter for pi coding agent",
79807
79821
  icon: "https://cdn.agentclientprotocol.com/registry/v1/latest/pi-acp.svg",
79808
79822
  distribution: {
79809
79823
  npx: {
79810
- package: "pi-acp@0.0.26"
79824
+ package: "pi-acp@0.0.27"
79811
79825
  }
79812
79826
  }
79813
79827
  },
79814
79828
  {
79815
79829
  id: "qoder",
79816
79830
  name: "Qoder CLI",
79817
- version: "0.2.13",
79831
+ version: "0.2.14",
79818
79832
  description: "AI coding assistant with agentic capabilities",
79819
79833
  icon: "https://cdn.agentclientprotocol.com/registry/v1/latest/qoder.svg",
79820
79834
  distribution: {
79821
79835
  npx: {
79822
- package: "@qoder-ai/qodercli@0.2.13",
79836
+ package: "@qoder-ai/qodercli@0.2.14",
79823
79837
  args: [
79824
79838
  "--acp"
79825
79839
  ]
@@ -79857,7 +79871,7 @@ Task description:
79857
79871
  {
79858
79872
  id: "stakpak",
79859
79873
  name: "Stakpak",
79860
- version: "0.3.80",
79874
+ version: "0.3.81",
79861
79875
  description: "Open-source DevOps agent in Rust with enterprise-grade security",
79862
79876
  icon: "https://cdn.agentclientprotocol.com/registry/v1/latest/stakpak.svg",
79863
79877
  distribution: {
@@ -82155,6 +82169,68 @@ Task description:
82155
82169
  }
82156
82170
  return result;
82157
82171
  };
82172
+ const parseCodexProposedPlanTags = (text, turnId) => {
82173
+ const planRegex = /(^|\r?\n)[ \t]*<proposed_plan>[ \t]*(?:\r?\n)((?:(?!<proposed_plan>)[\s\S])*?)(\r?\n)[ \t]*<\/proposed_plan>[ \t]*(?=\r?\n|$)/g;
82174
+ const result = [];
82175
+ let lastIndex = 0;
82176
+ let insertIndex;
82177
+ let markdown = "";
82178
+ let match5;
82179
+ while ((match5 = planRegex.exec(text)) !== null) {
82180
+ const leadingNewline = match5[1] ?? "";
82181
+ const textBeforeEnd = match5.index + leadingNewline.length;
82182
+ if (textBeforeEnd > lastIndex) {
82183
+ const textBefore = text.slice(lastIndex, textBeforeEnd);
82184
+ if (textBefore) {
82185
+ result.push({
82186
+ type: "text",
82187
+ text: textBefore
82188
+ });
82189
+ }
82190
+ }
82191
+ insertIndex ??= result.length;
82192
+ markdown += match5[2] ?? "";
82193
+ lastIndex = planRegex.lastIndex;
82194
+ }
82195
+ if (insertIndex === void 0) {
82196
+ return [
82197
+ {
82198
+ type: "text",
82199
+ text
82200
+ }
82201
+ ];
82202
+ }
82203
+ if (lastIndex < text.length) {
82204
+ const textAfter = text.slice(lastIndex);
82205
+ if (textAfter) {
82206
+ result.push({
82207
+ type: "text",
82208
+ text: textAfter
82209
+ });
82210
+ }
82211
+ }
82212
+ if (markdown.trim()) {
82213
+ result.splice(insertIndex, 0, {
82214
+ type: "proposed_plan",
82215
+ turnId,
82216
+ markdown,
82217
+ status: "completed",
82218
+ isLatest: true
82219
+ });
82220
+ }
82221
+ return result;
82222
+ };
82223
+ const parseAssistantTextTags = (text, turnId) => {
82224
+ const withThoughts = parseClaudeCodeThinkingTags(text);
82225
+ return withThoughts.flatMap((item) => {
82226
+ if (item.type !== "text") {
82227
+ return [
82228
+ item
82229
+ ];
82230
+ }
82231
+ return parseCodexProposedPlanTags(item.text, turnId);
82232
+ });
82233
+ };
82158
82234
  const buildMessageContentFromNotification = (message) => {
82159
82235
  const { update: update2 } = message;
82160
82236
  switch (update2.sessionUpdate) {
@@ -82412,6 +82488,11 @@ Task description:
82412
82488
  this.upsertSingletonItem(entryIndex, "available_commands", message);
82413
82489
  return;
82414
82490
  }
82491
+ case "proposed_plan": {
82492
+ const entryIndex = this.ensureActiveAssistantEntry();
82493
+ this.upsertProposedPlanItem(entryIndex, message);
82494
+ return;
82495
+ }
82415
82496
  case "tool_call": {
82416
82497
  const existingEntryIndex = this.resolveToolCallEntryIndex(message.toolCallId);
82417
82498
  if (existingEntryIndex !== void 0) {
@@ -82471,6 +82552,20 @@ Task description:
82471
82552
  entry2.items = compacted;
82472
82553
  this.parsedItemsByEntryIndex[entryIndex] = compacted;
82473
82554
  }
82555
+ upsertProposedPlanItem(entryIndex, next) {
82556
+ const entry2 = this.history[entryIndex];
82557
+ if (!entry2) return;
82558
+ const items2 = this.ensureEntryItems(entryIndex);
82559
+ const existingIndex = items2.findIndex((item) => item.type === "proposed_plan" && item.turnId === next.turnId);
82560
+ if (existingIndex >= 0) {
82561
+ items2[existingIndex] = next;
82562
+ return;
82563
+ }
82564
+ items2.push(next);
82565
+ const compacted = compactAdjacentTextAndThought(items2);
82566
+ entry2.items = compacted;
82567
+ this.parsedItemsByEntryIndex[entryIndex] = compacted;
82568
+ }
82474
82569
  upsertToolCall(entryIndex, incoming) {
82475
82570
  const entry2 = this.history[entryIndex];
82476
82571
  if (!entry2) return;
@@ -82502,12 +82597,12 @@ Task description:
82502
82597
  continue;
82503
82598
  }
82504
82599
  const text = item.text;
82505
- if (!text.includes("<thinking>")) {
82600
+ if (!text.includes("<thinking>") && !text.includes("<proposed_plan>")) {
82506
82601
  newItems.push(item);
82507
82602
  continue;
82508
82603
  }
82509
- const parsed = parseClaudeCodeThinkingTags(text);
82510
- if (parsed.length > 1 || parsed.length === 1 && parsed[0]?.type !== "text") {
82604
+ const parsed = parseAssistantTextTags(text, entry2.id);
82605
+ if (parsed.length !== 1 || parsed[0]?.type !== "text" || parsed[0]?.type === "text" && parsed[0].text !== text) {
82511
82606
  newItems.push(...parsed);
82512
82607
  modified = true;
82513
82608
  } else {
@@ -82525,6 +82620,216 @@ Task description:
82525
82620
  const applyNotificationOnHistory = (history, notifications, model, options = {}) => {
82526
82621
  return new NotificationOnHistoryApplier(history, options, model).apply(notifications);
82527
82622
  };
82623
+ const applyMessageContentsBatch = (history, messages, options = {}) => {
82624
+ if (messages.length === 0) {
82625
+ return history;
82626
+ }
82627
+ const createId = options.createId ?? defaultCreateId$1;
82628
+ const now2 = options.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
82629
+ const parseEntryItems = (entry2) => {
82630
+ const rawItems = entry2.items;
82631
+ return Array.isArray(rawItems) ? rawItems : [];
82632
+ };
82633
+ const writeEntryItems2 = (entry2, items2) => {
82634
+ return {
82635
+ ...entry2,
82636
+ items: items2
82637
+ };
82638
+ };
82639
+ const createAssistantEntryState = () => ({
82640
+ entry: {
82641
+ id: options.targetAssistantEntryId ?? createId(),
82642
+ role: "assistant",
82643
+ items: [],
82644
+ timestamp: now2(),
82645
+ userId: void 0,
82646
+ read: void 0,
82647
+ fileDiff: []
82648
+ },
82649
+ items: [],
82650
+ dirty: true
82651
+ });
82652
+ const entryStates = history.map((entry2) => {
82653
+ return {
82654
+ entry: entry2,
82655
+ items: parseEntryItems(entry2),
82656
+ dirty: false
82657
+ };
82658
+ });
82659
+ const ensureActiveAssistantEntry = () => {
82660
+ if (options.targetAssistantEntryId) {
82661
+ const targetIndex = entryStates.findIndex((state2) => state2.entry.role === "assistant" && state2.entry.id === options.targetAssistantEntryId);
82662
+ if (targetIndex >= 0) {
82663
+ return targetIndex;
82664
+ }
82665
+ entryStates.push(createAssistantEntryState());
82666
+ return entryStates.length - 1;
82667
+ }
82668
+ const lastIndex = entryStates.length - 1;
82669
+ const last2 = lastIndex >= 0 ? entryStates[lastIndex] : void 0;
82670
+ if (last2 && last2.entry.role === "assistant") {
82671
+ return lastIndex;
82672
+ }
82673
+ entryStates.push(createAssistantEntryState());
82674
+ return entryStates.length - 1;
82675
+ };
82676
+ const appendOrMergeAdjacentText = (entryIndex, kind, delta) => {
82677
+ if (!delta) return;
82678
+ const state2 = entryStates[entryIndex];
82679
+ if (!state2) return;
82680
+ const last2 = state2.items[state2.items.length - 1];
82681
+ if (last2 && last2.type === kind) {
82682
+ const existing = last2;
82683
+ const text = sanitizeLodyInternalInstructions(mergeStreamChunk(existing.text, delta));
82684
+ if (text) {
82685
+ state2.items[state2.items.length - 1] = {
82686
+ ...existing,
82687
+ text
82688
+ };
82689
+ } else {
82690
+ state2.items.pop();
82691
+ }
82692
+ } else {
82693
+ state2.items.push({
82694
+ type: kind,
82695
+ text: delta
82696
+ });
82697
+ }
82698
+ state2.dirty = true;
82699
+ };
82700
+ const upsertSingletonItem = (entryIndex, type2, next) => {
82701
+ const state2 = entryStates[entryIndex];
82702
+ if (!state2) return;
82703
+ const last2 = state2.items[state2.items.length - 1];
82704
+ if (last2 && last2.type === type2) {
82705
+ state2.items[state2.items.length - 1] = next;
82706
+ state2.dirty = true;
82707
+ return;
82708
+ }
82709
+ const withoutType = state2.items.filter((m) => m.type !== type2);
82710
+ withoutType.push(next);
82711
+ state2.items = compactAdjacentTextAndThought(withoutType);
82712
+ state2.dirty = true;
82713
+ };
82714
+ const upsertProposedPlanItem = (entryIndex, next) => {
82715
+ const state2 = entryStates[entryIndex];
82716
+ if (!state2) return;
82717
+ const existingIndex = state2.items.findIndex((item) => item.type === "proposed_plan" && item.turnId === next.turnId);
82718
+ if (existingIndex >= 0) {
82719
+ state2.items[existingIndex] = next;
82720
+ state2.dirty = true;
82721
+ return;
82722
+ }
82723
+ state2.items.push(next);
82724
+ state2.items = compactAdjacentTextAndThought(state2.items);
82725
+ state2.dirty = true;
82726
+ };
82727
+ const upsertToolCall = (entryIndex, incoming) => {
82728
+ const state2 = entryStates[entryIndex];
82729
+ if (!state2) return;
82730
+ const toolIndex = state2.items.findIndex((m) => m.type === "tool_call" && m.toolCallId === incoming.toolCallId);
82731
+ if (toolIndex >= 0) {
82732
+ const prevTool = state2.items[toolIndex];
82733
+ state2.items[toolIndex] = mergeToolCallMessage(prevTool, incoming);
82734
+ } else {
82735
+ state2.items.push({
82736
+ ...incoming,
82737
+ status: incoming.status || "pending",
82738
+ content: incoming.content ? compactToolCallContentForHistory(incoming.content, {
82739
+ kind: incoming.kind ?? void 0
82740
+ }) : void 0
82741
+ });
82742
+ }
82743
+ state2.dirty = true;
82744
+ };
82745
+ const toolCallEntryIndexById = /* @__PURE__ */ new Map();
82746
+ for (let i2 = 0; i2 < entryStates.length; i2++) {
82747
+ const state2 = entryStates[i2];
82748
+ if (!state2) continue;
82749
+ for (const content of state2.items) {
82750
+ if (content.type === "tool_call") {
82751
+ toolCallEntryIndexById.set(content.toolCallId, i2);
82752
+ }
82753
+ }
82754
+ }
82755
+ for (const message of messages) {
82756
+ switch (message.type) {
82757
+ case "text": {
82758
+ const text = sanitizeLodyInternalInstructions(message.text);
82759
+ if (!text) break;
82760
+ const entryIndex = ensureActiveAssistantEntry();
82761
+ appendOrMergeAdjacentText(entryIndex, "text", text);
82762
+ break;
82763
+ }
82764
+ case "thought": {
82765
+ const text = sanitizeLodyInternalInstructions(message.text);
82766
+ if (!text) break;
82767
+ const entryIndex = ensureActiveAssistantEntry();
82768
+ appendOrMergeAdjacentText(entryIndex, "thought", text);
82769
+ break;
82770
+ }
82771
+ case "plan": {
82772
+ const entryIndex = ensureActiveAssistantEntry();
82773
+ const state2 = entryStates[entryIndex];
82774
+ if (state2) {
82775
+ state2.entry.plan = message.entries;
82776
+ state2.dirty = true;
82777
+ }
82778
+ break;
82779
+ }
82780
+ case "available_commands": {
82781
+ const entryIndex = ensureActiveAssistantEntry();
82782
+ upsertSingletonItem(entryIndex, "available_commands", message);
82783
+ break;
82784
+ }
82785
+ case "proposed_plan": {
82786
+ const entryIndex = ensureActiveAssistantEntry();
82787
+ upsertProposedPlanItem(entryIndex, message);
82788
+ break;
82789
+ }
82790
+ case "tool_call": {
82791
+ const existingEntryIndex = toolCallEntryIndexById.get(message.toolCallId);
82792
+ if (existingEntryIndex !== void 0) {
82793
+ upsertToolCall(existingEntryIndex, message);
82794
+ } else {
82795
+ const entryIndex = ensureActiveAssistantEntry();
82796
+ upsertToolCall(entryIndex, message);
82797
+ toolCallEntryIndexById.set(message.toolCallId, entryIndex);
82798
+ }
82799
+ break;
82800
+ }
82801
+ }
82802
+ }
82803
+ for (const state2 of entryStates) {
82804
+ if (!state2.dirty || state2.entry.role !== "assistant") continue;
82805
+ let modified = false;
82806
+ const newItems = [];
82807
+ for (const item of state2.items) {
82808
+ if (item.type !== "text") {
82809
+ newItems.push(item);
82810
+ continue;
82811
+ }
82812
+ if (!item.text.includes("<thinking>") && !item.text.includes("<proposed_plan>")) {
82813
+ newItems.push(item);
82814
+ continue;
82815
+ }
82816
+ const parsed = parseAssistantTextTags(item.text, state2.entry.id);
82817
+ if (parsed.length !== 1 || parsed[0]?.type !== "text" || parsed[0]?.type === "text" && parsed[0].text !== item.text) {
82818
+ newItems.push(...parsed);
82819
+ modified = true;
82820
+ } else {
82821
+ newItems.push(item);
82822
+ }
82823
+ }
82824
+ if (modified) {
82825
+ state2.items = compactAdjacentTextAndThought(newItems);
82826
+ }
82827
+ }
82828
+ return entryStates.map((state2) => {
82829
+ if (!state2.dirty) return state2.entry;
82830
+ return writeEntryItems2(state2.entry, state2.items);
82831
+ });
82832
+ };
82528
82833
  const defaultNow = () => (/* @__PURE__ */ new Date()).toISOString();
82529
82834
  const defaultCreateId = () => {
82530
82835
  const maybeCrypto = globalThis.crypto;
@@ -82647,14 +82952,29 @@ Task description:
82647
82952
  };
82648
82953
  }
82649
82954
  const isRecord$4 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
82955
+ const getBooleanField = (value, camelCaseKey, snakeCaseKey) => value[camelCaseKey] === true || value[snakeCaseKey] === true;
82650
82956
  const getClaudeCodeMeta = (meta) => {
82651
82957
  if (!isRecord$4(meta)) return null;
82652
82958
  const claudeCode = meta.claudeCode;
82653
82959
  return isRecord$4(claudeCode) ? claudeCode : null;
82654
82960
  };
82961
+ const getCodexMeta = (meta) => {
82962
+ if (!isRecord$4(meta)) return null;
82963
+ const codex = meta.codex;
82964
+ return isRecord$4(codex) ? codex : null;
82965
+ };
82655
82966
  function parseAskUserQuestionPermissionMeta(meta) {
82656
82967
  const claudeCode = getClaudeCodeMeta(meta);
82657
- if (!claudeCode) return null;
82968
+ if (claudeCode) {
82969
+ return parseClaudeAskUserQuestionPermissionMeta(claudeCode);
82970
+ }
82971
+ const codex = getCodexMeta(meta);
82972
+ if (codex) {
82973
+ return parseCodexRequestUserInputPermissionMeta(codex);
82974
+ }
82975
+ return null;
82976
+ }
82977
+ function parseClaudeAskUserQuestionPermissionMeta(claudeCode) {
82658
82978
  const raw = claudeCode.askUserQuestion;
82659
82979
  if (!isRecord$4(raw)) return null;
82660
82980
  const rawQuestions = raw.questions;
@@ -82687,11 +83007,55 @@ Task description:
82687
83007
  });
82688
83008
  }
82689
83009
  return {
83010
+ source: "claude",
82690
83011
  version: typeof raw.version === "number" && Number.isFinite(raw.version) ? raw.version : 1,
82691
83012
  allowCustomAnswer: raw.allowCustomAnswer === true,
82692
83013
  questions
82693
83014
  };
82694
83015
  }
83016
+ function parseCodexRequestUserInputPermissionMeta(codex) {
83017
+ const raw = codex.requestUserInput;
83018
+ if (!isRecord$4(raw)) return null;
83019
+ const rawQuestions = raw.questions;
83020
+ if (!Array.isArray(rawQuestions) || rawQuestions.length === 0) return null;
83021
+ const questions = [];
83022
+ for (const rawQuestion of rawQuestions) {
83023
+ if (!isRecord$4(rawQuestion)) return null;
83024
+ if (typeof rawQuestion.id !== "string") return null;
83025
+ if (typeof rawQuestion.question !== "string") return null;
83026
+ if (typeof rawQuestion.header !== "string") return null;
83027
+ const rawOptions = rawQuestion.options;
83028
+ const options = [];
83029
+ if (rawOptions !== void 0) {
83030
+ if (!Array.isArray(rawOptions)) return null;
83031
+ for (const rawOption of rawOptions) {
83032
+ if (!isRecord$4(rawOption)) return null;
83033
+ if (typeof rawOption.label !== "string") return null;
83034
+ options.push({
83035
+ label: rawOption.label,
83036
+ ...typeof rawOption.description === "string" ? {
83037
+ description: rawOption.description
83038
+ } : {}
83039
+ });
83040
+ }
83041
+ }
83042
+ questions.push({
83043
+ id: rawQuestion.id,
83044
+ question: rawQuestion.question,
83045
+ header: rawQuestion.header,
83046
+ options,
83047
+ multiSelect: false,
83048
+ allowCustomAnswer: getBooleanField(rawQuestion, "isOther", "is_other"),
83049
+ isSecret: getBooleanField(rawQuestion, "isSecret", "is_secret")
83050
+ });
83051
+ }
83052
+ return {
83053
+ source: "codex",
83054
+ version: 1,
83055
+ allowCustomAnswer: questions.some((question) => question.allowCustomAnswer === true),
83056
+ questions
83057
+ };
83058
+ }
82695
83059
  function isAskUserQuestionPermissionMeta(meta) {
82696
83060
  return parseAskUserQuestionPermissionMeta(meta) !== null;
82697
83061
  }
@@ -85425,6 +85789,26 @@ ${tailedOutput}` : null;
85425
85789
  };
85426
85790
  const isPreviewTunnelCreateResponse = (value) => isRecord$3(value) && isString$2(value.tunnelId) && isString$2(value.publicUrl) && isString$2(value.websocketUrl) && isString$2(value.sessionToken) && typeof value.expiresAt === "number" && (value.resourceLimits === void 0 || isPreviewResourceLimits(value.resourceLimits));
85427
85791
  const isPreviewTunnelRefreshResponse = (value) => isRecord$3(value) && isString$2(value.websocketUrl) && isString$2(value.sessionToken) && typeof value.expiresAt === "number";
85792
+ class InFlightDedupe {
85793
+ inFlight = /* @__PURE__ */ new Map();
85794
+ async run(key2, factory) {
85795
+ const existing = this.inFlight.get(key2);
85796
+ if (existing) {
85797
+ return await existing;
85798
+ }
85799
+ let wrapped;
85800
+ wrapped = factory().finally(() => {
85801
+ if (this.inFlight.get(key2) === wrapped) {
85802
+ this.inFlight.delete(key2);
85803
+ }
85804
+ });
85805
+ this.inFlight.set(key2, wrapped);
85806
+ return await wrapped;
85807
+ }
85808
+ size() {
85809
+ return this.inFlight.size;
85810
+ }
85811
+ }
85428
85812
  const LOCAL_PROBE_PORT$1 = 17789;
85429
85813
  const LOCAL_SESSION_CONTROL_PORT = 17790;
85430
85814
  const IMAGE_UPLOAD_PATH = "/image-upload";
@@ -92876,18 +93260,18 @@ ${val.stack}`;
92876
93260
  };
92877
93261
  }
92878
93262
  function decodeMultipartMixed$1(boundary, data) {
92879
- const delimiter = new TextEncoder().encode(`--${boundary}`);
93263
+ const delimiter2 = new TextEncoder().encode(`--${boundary}`);
92880
93264
  const closeDelimiter = new TextEncoder().encode(`--${boundary}--`);
92881
93265
  const crlfCrlf = new TextEncoder().encode("\r\n\r\n");
92882
93266
  const parts2 = [];
92883
93267
  let pos = 0;
92884
- const firstDelimPos = indexOf$1(data, delimiter, pos);
93268
+ const firstDelimPos = indexOf$1(data, delimiter2, pos);
92885
93269
  if (firstDelimPos < 0) return parts2;
92886
- pos = firstDelimPos + delimiter.byteLength;
93270
+ pos = firstDelimPos + delimiter2.byteLength;
92887
93271
  if (pos < data.byteLength && data[pos] === 13) pos += 1;
92888
93272
  if (pos < data.byteLength && data[pos] === 10) pos += 1;
92889
93273
  while (pos < data.byteLength) {
92890
- if (startsWith$1(data.subarray(pos), closeDelimiter.subarray(delimiter.byteLength))) break;
93274
+ if (startsWith$1(data.subarray(pos), closeDelimiter.subarray(delimiter2.byteLength))) break;
92891
93275
  const headerEnd = indexOf$1(data, crlfCrlf, pos);
92892
93276
  if (headerEnd < 0) break;
92893
93277
  const headerBytes = data.subarray(pos, headerEnd);
@@ -92900,7 +93284,7 @@ ${val.stack}`;
92900
93284
  contentType = line3.slice(colon + 1).trim();
92901
93285
  }
92902
93286
  const bodyStart = headerEnd + crlfCrlf.byteLength;
92903
- const nextDelimiter = indexOf$1(data, delimiter, bodyStart);
93287
+ const nextDelimiter = indexOf$1(data, delimiter2, bodyStart);
92904
93288
  if (nextDelimiter < 0) {
92905
93289
  parts2.push({
92906
93290
  contentType,
@@ -92914,7 +93298,7 @@ ${val.stack}`;
92914
93298
  contentType,
92915
93299
  body: data.subarray(bodyStart, bodyEnd)
92916
93300
  });
92917
- pos = nextDelimiter + delimiter.byteLength;
93301
+ pos = nextDelimiter + delimiter2.byteLength;
92918
93302
  if (pos + 1 < data.byteLength && data[pos] === 45 && data[pos + 1] === 45) break;
92919
93303
  if (pos < data.byteLength && data[pos] === 13) pos += 1;
92920
93304
  if (pos < data.byteLength && data[pos] === 10) pos += 1;
@@ -111655,14 +112039,14 @@ ${this.stack.split("\n").slice(1).join("\n")}` : this.toString();
111655
112039
  const split2 = text.split(new RegExp(`\\s*${escape$1(delim)}\\s*`));
111656
112040
  return split2;
111657
112041
  };
111658
- const parsePrimitive = (text, path2, primitive, delimiter, split2) => {
112042
+ const parsePrimitive = (text, path2, primitive, delimiter2, split2) => {
111659
112043
  if (!split2) {
111660
112044
  return pipe$1(primitive.parse(text), mapBoth({
111661
112045
  onFailure: prefixed(path2),
111662
112046
  onSuccess: of$3
111663
112047
  }));
111664
112048
  }
111665
- return pipe$1(splitPathString(text, delimiter), forEachSequential((char) => primitive.parse(char.trim())), mapError(prefixed(path2)));
112049
+ return pipe$1(splitPathString(text, delimiter2), forEachSequential((char) => primitive.parse(char.trim())), mapError(prefixed(path2)));
111666
112050
  };
111667
112051
  const transpose = (array2) => {
111668
112052
  return Object.keys(array2[0]).map((column) => array2.map((row) => row[column]));
@@ -118481,18 +118865,18 @@ ${this.stack.split("\n").slice(1).join("\n")}` : this.toString();
118481
118865
  };
118482
118866
  }
118483
118867
  function decodeMultipartMixed(boundary, data) {
118484
- const delimiter = new TextEncoder().encode(`--${boundary}`);
118868
+ const delimiter2 = new TextEncoder().encode(`--${boundary}`);
118485
118869
  const closeDelimiter = new TextEncoder().encode(`--${boundary}--`);
118486
118870
  const crlfCrlf = new TextEncoder().encode("\r\n\r\n");
118487
118871
  const parts2 = [];
118488
118872
  let pos = 0;
118489
- const firstDelimPos = indexOf(data, delimiter, pos);
118873
+ const firstDelimPos = indexOf(data, delimiter2, pos);
118490
118874
  if (firstDelimPos < 0) return parts2;
118491
- pos = firstDelimPos + delimiter.byteLength;
118875
+ pos = firstDelimPos + delimiter2.byteLength;
118492
118876
  if (pos < data.byteLength && data[pos] === 13) pos += 1;
118493
118877
  if (pos < data.byteLength && data[pos] === 10) pos += 1;
118494
118878
  while (pos < data.byteLength) {
118495
- if (startsWith(data.subarray(pos), closeDelimiter.subarray(delimiter.byteLength))) break;
118879
+ if (startsWith(data.subarray(pos), closeDelimiter.subarray(delimiter2.byteLength))) break;
118496
118880
  const headerEnd = indexOf(data, crlfCrlf, pos);
118497
118881
  if (headerEnd < 0) break;
118498
118882
  const headerBytes = data.subarray(pos, headerEnd);
@@ -118505,7 +118889,7 @@ ${this.stack.split("\n").slice(1).join("\n")}` : this.toString();
118505
118889
  contentType = line3.slice(colon + 1).trim();
118506
118890
  }
118507
118891
  const bodyStart = headerEnd + crlfCrlf.byteLength;
118508
- const nextDelimiter = indexOf(data, delimiter, bodyStart);
118892
+ const nextDelimiter = indexOf(data, delimiter2, bodyStart);
118509
118893
  if (nextDelimiter < 0) {
118510
118894
  parts2.push({
118511
118895
  contentType,
@@ -118519,7 +118903,7 @@ ${this.stack.split("\n").slice(1).join("\n")}` : this.toString();
118519
118903
  contentType,
118520
118904
  body: data.subarray(bodyStart, bodyEnd)
118521
118905
  });
118522
- pos = nextDelimiter + delimiter.byteLength;
118906
+ pos = nextDelimiter + delimiter2.byteLength;
118523
118907
  if (pos + 1 < data.byteLength && data[pos] === 45 && data[pos + 1] === 45) break;
118524
118908
  if (pos < data.byteLength && data[pos] === 13) pos += 1;
118525
118909
  if (pos < data.byteLength && data[pos] === 10) pos += 1;
@@ -119728,6 +120112,11 @@ ${this.stack.split("\n").slice(1).join("\n")}` : this.toString();
119728
120112
  const JSON_RPC_VERSION$1 = "2.0";
119729
120113
  const LORO_STREAMS_RPC_VERSION = "1";
119730
120114
  const LORO_STREAMS_RPC_RETENTION_SECONDS = 86400;
120115
+ const LORO_STREAMS_RPC_ERROR_CODES = {
120116
+ rpcVersionMismatch: "rpc_version_mismatch",
120117
+ methodUnavailable: "method_unavailable",
120118
+ internalError: "internal_error"
120119
+ };
119731
120120
  const LORO_RPC_REQUEST_STREAM_SEGMENT = "rpc:req";
119732
120121
  const getLoroMachineRpcRequestStreamId = (workspaceId, machineId) => `${workspaceId}:${LORO_RPC_REQUEST_STREAM_SEGMENT}:${machineId}`;
119733
120122
  const normalizeLoroGatewayBaseUrl = (baseUrl) => {
@@ -119758,8 +120147,8 @@ ${this.stack.split("\n").slice(1).join("\n")}` : this.toString();
119758
120147
  machineId: string$2().trim().min(1),
119759
120148
  workspaceId: string$2().trim().min(1),
119760
120149
  replyTo: string$2().trim().min(1),
119761
- sentAt: number$3().int().nonnegative(),
119762
- expiresAt: number$3().int().positive()
120150
+ sentAt: number$3().finite().nonnegative(),
120151
+ expiresAt: number$3().finite().positive()
119763
120152
  }).strict();
119764
120153
  const LoroMachineStatusRpcRequestSchema = BaseRpcRequestSchema.extend({
119765
120154
  method: literal("machine/status"),
@@ -120052,19 +120441,11 @@ ${this.stack.split("\n").slice(1).join("\n")}` : this.toString();
120052
120441
  while (!this.stopped) {
120053
120442
  try {
120054
120443
  await (this.deps.streamClient.readJsonLive?.(this.requestStreamId, this.requestState, async (batch) => {
120055
- this.requestState.nextOffset = batch.nextOffset ?? this.requestState.nextOffset;
120056
- this.requestState.cursor = batch.cursor;
120057
- for (const raw of batch.messages) {
120058
- await this.handleRawRequest(raw);
120059
- }
120444
+ await this.handleRequestBatch(batch);
120060
120445
  }, {
120061
120446
  signal: this.stopController.signal
120062
120447
  }) ?? readJsonLiveViaLongPollFallback(this.deps.streamClient, this.requestStreamId, this.requestState, async (batch) => {
120063
- this.requestState.nextOffset = batch.nextOffset ?? this.requestState.nextOffset;
120064
- this.requestState.cursor = batch.cursor;
120065
- for (const raw of batch.messages) {
120066
- await this.handleRawRequest(raw);
120067
- }
120448
+ await this.handleRequestBatch(batch);
120068
120449
  }, {
120069
120450
  signal: this.stopController.signal
120070
120451
  }));
@@ -120098,6 +120479,13 @@ ${this.stack.split("\n").slice(1).join("\n")}` : this.toString();
120098
120479
  }
120099
120480
  }
120100
120481
  }
120482
+ async handleRequestBatch(batch) {
120483
+ this.requestState.nextOffset = batch.nextOffset ?? this.requestState.nextOffset;
120484
+ this.requestState.cursor = batch.cursor;
120485
+ for (const raw of batch.messages) {
120486
+ await this.handleRawRequest(raw);
120487
+ }
120488
+ }
120101
120489
  async handleRawRequest(raw) {
120102
120490
  const parsed = LoroStreamsRpcRequestSchema.safeParse(raw);
120103
120491
  if (!parsed.success) {
@@ -120114,7 +120502,7 @@ ${this.stack.split("\n").slice(1).join("\n")}` : this.toString();
120114
120502
  }
120115
120503
  if (request.rpcVersion !== (this.deps.rpcVersion ?? LORO_STREAMS_RPC_VERSION)) {
120116
120504
  await this.appendErrorResponse(request.replyTo, request.id, request.method, {
120117
- code: "rpc_version_mismatch",
120505
+ code: LORO_STREAMS_RPC_ERROR_CODES.rpcVersionMismatch,
120118
120506
  message: `Expected rpcVersion=${this.deps.rpcVersion ?? LORO_STREAMS_RPC_VERSION}, got ${request.rpcVersion}`
120119
120507
  });
120120
120508
  return;
@@ -120138,7 +120526,7 @@ ${this.stack.split("\n").slice(1).join("\n")}` : this.toString();
120138
120526
  case "session/preview-create": {
120139
120527
  if (!this.deps.createSessionPreview) {
120140
120528
  await this.appendErrorResponse(request.replyTo, request.id, request.method, {
120141
- code: "method_unavailable",
120529
+ code: LORO_STREAMS_RPC_ERROR_CODES.methodUnavailable,
120142
120530
  message: "Session preview creation is not available on this machine."
120143
120531
  });
120144
120532
  return;
@@ -120154,7 +120542,7 @@ ${this.stack.split("\n").slice(1).join("\n")}` : this.toString();
120154
120542
  case "session/preview-revoke": {
120155
120543
  if (!this.deps.revokeSessionPreview) {
120156
120544
  await this.appendErrorResponse(request.replyTo, request.id, request.method, {
120157
- code: "method_unavailable",
120545
+ code: LORO_STREAMS_RPC_ERROR_CODES.methodUnavailable,
120158
120546
  message: "Session preview revocation is not available on this machine."
120159
120547
  });
120160
120548
  return;
@@ -120170,7 +120558,7 @@ ${this.stack.split("\n").slice(1).join("\n")}` : this.toString();
120170
120558
  case "local-project/git-state": {
120171
120559
  if (!this.deps.getLocalProjectGitState) {
120172
120560
  await this.appendErrorResponse(request.replyTo, request.id, request.method, {
120173
- code: "method_unavailable",
120561
+ code: LORO_STREAMS_RPC_ERROR_CODES.methodUnavailable,
120174
120562
  message: "Local project Git state is not available on this machine."
120175
120563
  });
120176
120564
  return;
@@ -120185,7 +120573,7 @@ ${this.stack.split("\n").slice(1).join("\n")}` : this.toString();
120185
120573
  case "local-project/control": {
120186
120574
  if (!this.deps.dispatchLocalProjectControl) {
120187
120575
  await this.appendErrorResponse(request.replyTo, request.id, request.method, {
120188
- code: "method_unavailable",
120576
+ code: LORO_STREAMS_RPC_ERROR_CODES.methodUnavailable,
120189
120577
  message: "Local project control is not available on this machine."
120190
120578
  });
120191
120579
  return;
@@ -120198,7 +120586,7 @@ ${this.stack.split("\n").slice(1).join("\n")}` : this.toString();
120198
120586
  } catch (error2) {
120199
120587
  const message = error2 instanceof Error ? error2.message : String(error2);
120200
120588
  await this.appendErrorResponse(request.replyTo, request.id, request.method, {
120201
- code: "internal_error",
120589
+ code: LORO_STREAMS_RPC_ERROR_CODES.internalError,
120202
120590
  message
120203
120591
  });
120204
120592
  }
@@ -120255,6 +120643,8 @@ ${this.stack.split("\n").slice(1).join("\n")}` : this.toString();
120255
120643
  return raw;
120256
120644
  }
120257
120645
  };
120646
+ const DEFAULT_GIT_COMMAND_TIMEOUT_MS = 5e3;
120647
+ const GIT_CHECKOUT_TIMEOUT_MS = 3e4;
120258
120648
  function tryRealpath(inputPath) {
120259
120649
  try {
120260
120650
  if (typeof fs$6.realpathSync.native === "function") {
@@ -120297,7 +120687,8 @@ ${this.stack.split("\n").slice(1).join("\n")}` : this.toString();
120297
120687
  const hash2 = createHash("sha256").update(normalizedRootPath).digest("hex").slice(0, 24);
120298
120688
  return `local-project-${hash2}`;
120299
120689
  }
120300
- function runGitCommand$1(rootPath, args2) {
120690
+ function runGitCommand$1(rootPath, args2, options = {}) {
120691
+ const timeoutMs = options.timeoutMs ?? DEFAULT_GIT_COMMAND_TIMEOUT_MS;
120301
120692
  try {
120302
120693
  const result = spawn.sync("git", args2, {
120303
120694
  cwd: rootPath,
@@ -120306,13 +120697,30 @@ ${this.stack.split("\n").slice(1).join("\n")}` : this.toString();
120306
120697
  "ignore",
120307
120698
  "pipe",
120308
120699
  "pipe"
120309
- ]
120700
+ ],
120701
+ timeout: timeoutMs,
120702
+ killSignal: "SIGTERM",
120703
+ env: {
120704
+ ...process.env,
120705
+ GIT_TERMINAL_PROMPT: "0",
120706
+ GIT_OPTIONAL_LOCKS: "0"
120707
+ }
120310
120708
  });
120311
- return {
120709
+ if (result.error) {
120710
+ const message = result.error.message || `Git command timed out after ${timeoutMs}ms: git ${args2.join(" ")}`;
120711
+ const commandResult2 = {
120712
+ status: null,
120713
+ stdout: String(result.stdout ?? ""),
120714
+ stderr: message
120715
+ };
120716
+ return commandResult2;
120717
+ }
120718
+ const commandResult = {
120312
120719
  status: result.status ?? null,
120313
120720
  stdout: String(result.stdout ?? ""),
120314
120721
  stderr: String(result.stderr ?? "")
120315
120722
  };
120723
+ return commandResult;
120316
120724
  } catch (error2) {
120317
120725
  const message = error2 instanceof Error ? error2.message : String(error2);
120318
120726
  return {
@@ -120523,7 +120931,7 @@ ${this.stack.split("\n").slice(1).join("\n")}` : this.toString();
120523
120931
  const statusResult = runGitCommand$1(rootPath, [
120524
120932
  "status",
120525
120933
  "--porcelain=v1",
120526
- "--untracked-files=all"
120934
+ "--untracked-files=normal"
120527
120935
  ]);
120528
120936
  if (statusResult.status !== 0) {
120529
120937
  const reason = statusResult.stderr.trim() || statusResult.stdout.trim() || "unknown error";
@@ -120604,7 +121012,9 @@ ${this.stack.split("\n").slice(1).join("\n")}` : this.toString();
120604
121012
  const checkoutResult = runGitCommand$1(normalizedRootPath, [
120605
121013
  "checkout",
120606
121014
  normalizedBranchName
120607
- ]);
121015
+ ], {
121016
+ timeoutMs: GIT_CHECKOUT_TIMEOUT_MS
121017
+ });
120608
121018
  if (checkoutResult.status !== 0) {
120609
121019
  const reason = checkoutResult.stderr.trim() || checkoutResult.stdout.trim() || "unknown error";
120610
121020
  throw new Error(`Failed to checkout git branch: ${reason}`);
@@ -122253,6 +122663,18 @@ ${this.stack.split("\n").slice(1).join("\n")}` : this.toString();
122253
122663
  const ThreadGoalClearedParamsSchema = object$1({
122254
122664
  threadId: string$2()
122255
122665
  });
122666
+ const CodexProposedPlanParamsSchema = object$1({
122667
+ schemaVersion: literal(1),
122668
+ sessionId: string$2(),
122669
+ turnId: string$2(),
122670
+ markdown: string$2(),
122671
+ status: _enum$1([
122672
+ "delta",
122673
+ "completed",
122674
+ "cleared"
122675
+ ]),
122676
+ isLatest: boolean()
122677
+ });
122256
122678
  const CODEX_IMAGE_GENERATION_TOOL_TITLE = "Image generation";
122257
122679
  const CODEX_IMAGE_GENERATION_REVISED_PROMPT_PREFIX = "Revised prompt: ";
122258
122680
  function extractCodexImageGenerationFields(content) {
@@ -122547,7 +122969,28 @@ ${this.stack.split("\n").slice(1).join("\n")}` : this.toString();
122547
122969
  this.options.onThreadGoalCleared?.(result.data.threadId);
122548
122970
  break;
122549
122971
  }
122972
+ case "acp_ext:codex_proposed_plan": {
122973
+ this.tryHandleCodexProposedPlanExtension(resolvedMethod, params);
122974
+ break;
122975
+ }
122976
+ default:
122977
+ this.logger.debug(`[${this.options.sessionId}] Ignoring extension message ${resolvedMethod}`);
122978
+ }
122979
+ }
122980
+ tryHandleCodexProposedPlanExtension(method, params) {
122981
+ if (!this.options.onCodexProposedPlan) return;
122982
+ const result = CodexProposedPlanParamsSchema.safeParse(params);
122983
+ if (!result.success) {
122984
+ this.logger.debug(`[${this.options.sessionId}] Dropping invalid Codex proposed plan update from ${method}: ${result.error.message}`);
122985
+ return;
122550
122986
  }
122987
+ this.options.onCodexProposedPlan({
122988
+ type: "proposed_plan",
122989
+ turnId: result.data.turnId,
122990
+ markdown: result.data.markdown,
122991
+ status: result.data.status,
122992
+ isLatest: result.data.isLatest
122993
+ });
122551
122994
  }
122552
122995
  isCodexAgent() {
122553
122996
  return this.options.agentConfig?.agentType === "codex";
@@ -122575,6 +123018,9 @@ ${this.stack.split("\n").slice(1).join("\n")}` : this.toString();
122575
123018
  _meta: {
122576
123019
  claudeCode: {
122577
123020
  askUserQuestion: true
123021
+ },
123022
+ codex: {
123023
+ requestUserInput: true
122578
123024
  }
122579
123025
  }
122580
123026
  }
@@ -122909,7 +123355,7 @@ ${this.stack.split("\n").slice(1).join("\n")}` : this.toString();
122909
123355
  },
122910
123356
  codex: {
122911
123357
  packageName: "acp-extension-codex",
122912
- version: "0.14.3",
123358
+ version: "0.14.5",
122913
123359
  binName: "acp-extension-codex",
122914
123360
  args: [
122915
123361
  "-c",
@@ -122921,6 +123367,11 @@ ${this.stack.split("\n").slice(1).join("\n")}` : this.toString();
122921
123367
  };
122922
123368
  const OFFICIAL_NPM_REGISTRY = "https://registry.npmjs.org/";
122923
123369
  const NPX_CACHE_MODE_ARG = "--prefer-online";
123370
+ const DEFAULT_ACP_PATH_RELATIVE_DIRS = [
123371
+ ".local/bin",
123372
+ "bin",
123373
+ ".claude/local"
123374
+ ];
122924
123375
  const registryAgentsById = Object.fromEntries(REGISTRY_ACP_AGENTS.map((agent) => [
122925
123376
  agent.id,
122926
123377
  agent
@@ -123084,6 +123535,60 @@ ${this.stack.split("\n").slice(1).join("\n")}` : this.toString();
123084
123535
  }
123085
123536
  throw new Error(`Unsupported ACP cliType: ${input2.cliType}`);
123086
123537
  }
123538
+ function resolveACPProcessLaunch(input2) {
123539
+ const setting = resolveACPSetting(input2);
123540
+ return {
123541
+ command: setting.exec.command,
123542
+ args: [
123543
+ ...setting.exec.args,
123544
+ ...input2.extraArgs ?? []
123545
+ ],
123546
+ env: setting.exec.env
123547
+ };
123548
+ }
123549
+ function mergeACPProcessEnv(launch, baseEnv) {
123550
+ return launch.env ? {
123551
+ ...baseEnv,
123552
+ ...launch.env
123553
+ } : baseEnv;
123554
+ }
123555
+ function getPathEnvKey(env2) {
123556
+ if (process.platform !== "win32") {
123557
+ return "PATH";
123558
+ }
123559
+ return Object.keys(env2).find((key2) => key2.toLowerCase() === "path") ?? "Path";
123560
+ }
123561
+ function normalizePathEntry(entry2) {
123562
+ const normalized = normalize$2(entry2);
123563
+ return normalized.length > 1 ? normalized.replace(/[\\/]+$/, "") : normalized;
123564
+ }
123565
+ function getDefaultAcpPathEntries(homeDir = homedir()) {
123566
+ if (!homeDir) {
123567
+ return [];
123568
+ }
123569
+ return DEFAULT_ACP_PATH_RELATIVE_DIRS.map((relativeDir) => join$2(homeDir, relativeDir));
123570
+ }
123571
+ function withDefaultAcpPathEntries(env2) {
123572
+ const defaultEntries = getDefaultAcpPathEntries();
123573
+ if (defaultEntries.length === 0) {
123574
+ return env2;
123575
+ }
123576
+ const pathKey2 = getPathEnvKey(env2);
123577
+ const currentParts = (env2[pathKey2] ?? "").split(delimiter).filter(Boolean);
123578
+ const defaultEntrySet = new Set(defaultEntries.map(normalizePathEntry));
123579
+ const currentWithoutDefaults = currentParts.filter((entry2) => !defaultEntrySet.has(normalizePathEntry(entry2)));
123580
+ const nextPath = [
123581
+ ...defaultEntries,
123582
+ ...currentWithoutDefaults
123583
+ ].join(delimiter);
123584
+ if (env2[pathKey2] === nextPath) {
123585
+ return env2;
123586
+ }
123587
+ return {
123588
+ ...env2,
123589
+ [pathKey2]: nextPath
123590
+ };
123591
+ }
123087
123592
  function createStdinWritableStream(stdin) {
123088
123593
  stdin.on("error", (err2) => {
123089
123594
  if (err2.code !== "EPIPE") {
@@ -123272,6 +123777,7 @@ ${this.stack.split("\n").slice(1).join("\n")}` : this.toString();
123272
123777
  onRateLimitUpdate: options.onRateLimitUpdate,
123273
123778
  onThreadGoalUpdated: options.onThreadGoalUpdated,
123274
123779
  onThreadGoalCleared: options.onThreadGoalCleared,
123780
+ onCodexProposedPlan: options.onCodexProposedPlan,
123275
123781
  onCodexImageGenerationBegin: options.onCodexImageGenerationBegin,
123276
123782
  onCodexImageGenerationEnd: options.onCodexImageGenerationEnd
123277
123783
  });
@@ -123333,12 +123839,12 @@ ${this.stack.split("\n").slice(1).join("\n")}` : this.toString();
123333
123839
  await waitForChildProcessExit$1(child, exitTimeoutMs);
123334
123840
  }
123335
123841
  const spawnAcpProcess = (options) => {
123336
- const setting = resolveACPSetting({
123842
+ const launch = resolveACPProcessLaunch({
123337
123843
  cliType: options.cliType,
123338
123844
  agentType: options.agentType
123339
123845
  });
123340
- const command2 = options.command ?? setting.exec.command;
123341
- const args2 = options.args ?? setting.exec.args;
123846
+ const command2 = options.command ?? launch.command;
123847
+ const args2 = options.args ?? launch.args;
123342
123848
  const spawnFn = options.spawnImpl ?? spawn;
123343
123849
  return spawnFn(command2, args2, {
123344
123850
  cwd: options.workdir,
@@ -123352,24 +123858,18 @@ ${this.stack.split("\n").slice(1).join("\n")}` : this.toString();
123352
123858
  });
123353
123859
  };
123354
123860
  const startLocalAcpAgent = async (options) => {
123355
- const setting = resolveACPSetting({
123861
+ const launch = resolveACPProcessLaunch({
123356
123862
  cliType: options.cliType,
123357
- agentType: options.agentType
123863
+ agentType: options.agentType,
123864
+ extraArgs: options.extraArgs
123358
123865
  });
123359
- const args2 = [
123360
- ...setting.exec.args,
123361
- ...options.extraArgs ?? []
123362
- ];
123363
123866
  const baseEnv = options.env ?? process.env;
123364
123867
  const shouldUseWorkdirCodexHome = options.cliType === "builtin" && options.agentType === "codex" && !baseEnv.CODEX_HOME && (baseEnv.LODY_E2E === "1" || baseEnv.LODY_TITLE_AGENT === "1");
123365
123868
  const env2 = shouldUseWorkdirCodexHome ? {
123366
123869
  ...baseEnv,
123367
123870
  CODEX_HOME: path__default.join(options.workdir, ".codex")
123368
123871
  } : baseEnv;
123369
- const envWithAcpStartup = setting.exec.env ? {
123370
- ...env2,
123371
- ...setting.exec.env
123372
- } : env2;
123872
+ const envWithAcpStartup = withDefaultAcpPathEntries(mergeACPProcessEnv(launch, env2));
123373
123873
  const keepCodexHome = env2.LODY_KEEP_CODEX_HOME === "1";
123374
123874
  const defaultCodexHome = path__default.join(os__default.homedir(), ".codex");
123375
123875
  const defaultAuthPath = path__default.join(defaultCodexHome, "auth.json");
@@ -123392,7 +123892,8 @@ ${this.stack.split("\n").slice(1).join("\n")}` : this.toString();
123392
123892
  agentType: options.agentType,
123393
123893
  workdir: options.workdir,
123394
123894
  env: envWithAcpStartup,
123395
- args: args2,
123895
+ command: launch.command,
123896
+ args: launch.args,
123396
123897
  spawnImpl: options.spawnImpl
123397
123898
  });
123398
123899
  options.logger.debug(`[acp-startup] spawned ACP process (cliType=${options.cliType} agentType=${options.agentType} workdir=${options.workdir})`);
@@ -123425,8 +123926,8 @@ ${this.stack.split("\n").slice(1).join("\n")}` : this.toString();
123425
123926
  }
123426
123927
  }, {
123427
123928
  sessionId: "acp-startup",
123428
- command: setting.exec.command,
123429
- args: args2,
123929
+ command: launch.command,
123930
+ args: launch.args,
123430
123931
  getStderrTail: () => stderrTail
123431
123932
  });
123432
123933
  if (shouldUseWorkdirCodexHome && env2.CODEX_HOME && !keepCodexHome) {
@@ -124144,7 +124645,8 @@ const normalizeRepoPath = (rawPath) => {
124144
124645
 
124145
124646
  const main = async () => {
124146
124647
  const action = process.argv[2];
124147
- if (action && action !== 'get') {
124648
+ const isRejectAction = action === 'erase' || action === 'reject';
124649
+ if (action && action !== 'get' && !isRejectAction) {
124148
124650
  debug('skip', { reason: 'unsupported_action', action });
124149
124651
  return;
124150
124652
  }
@@ -124183,23 +124685,18 @@ const main = async () => {
124183
124685
  return;
124184
124686
  }
124185
124687
 
124186
- /**
124187
- * Attempt to fetch credentials from a broker URL.
124188
- * Returns { success: true, json } on success, { success: false, error } on connection error,
124189
- * or { success: false } on other failures.
124190
- */
124191
- const tryFetchFromBroker = async (baseUrl, token) => {
124688
+ const tryBrokerRequest = async (baseUrl, token, endpoint, body) => {
124192
124689
  const controller = new AbortController();
124193
124690
  const timeoutId = setTimeout(() => controller.abort(), 10000);
124194
124691
  try {
124195
- debug('fetch', { repoFullName, baseUrl });
124196
- const res = await fetchImpl(\`\${baseUrl}/git-credential\`, {
124692
+ debug('fetch', { repoFullName, baseUrl, endpoint });
124693
+ const res = await fetchImpl(\`\${baseUrl}\${endpoint}\`, {
124197
124694
  method: 'POST',
124198
124695
  headers: {
124199
124696
  'Content-Type': 'application/json',
124200
124697
  Authorization: \`Bearer \${token}\`,
124201
124698
  },
124202
- body: JSON.stringify({ repoFullName }),
124699
+ body: JSON.stringify(body),
124203
124700
  signal: controller.signal,
124204
124701
  });
124205
124702
 
@@ -124214,11 +124711,6 @@ const main = async () => {
124214
124711
  return { success: false };
124215
124712
  }
124216
124713
 
124217
- if (!json || typeof json.username !== 'string' || typeof json.password !== 'string') {
124218
- return { success: false };
124219
- }
124220
- if (!json.username || !json.password) return { success: false };
124221
-
124222
124714
  return { success: true, json };
124223
124715
  } catch (error) {
124224
124716
  debug('fetch_error', { repoFullName, error: error && error.message });
@@ -124228,8 +124720,36 @@ const main = async () => {
124228
124720
  }
124229
124721
  };
124230
124722
 
124723
+ /**
124724
+ * Attempt to fetch credentials from a broker URL.
124725
+ * Returns { success: true, json } on success, { success: false, error } on connection error,
124726
+ * or { success: false } on other failures.
124727
+ */
124728
+ const tryFetchFromBroker = async (baseUrl, token) => {
124729
+ const result = await tryBrokerRequest(baseUrl, token, '/git-credential', { repoFullName });
124730
+ if (!result.success) return result;
124731
+
124732
+ const json = result.json;
124733
+ if (!json || typeof json.username !== 'string' || typeof json.password !== 'string') {
124734
+ return { success: false };
124735
+ }
124736
+ if (!json.username || !json.password) return { success: false };
124737
+
124738
+ return result;
124739
+ };
124740
+
124741
+ const tryRejectFromBroker = async (baseUrl, token) => {
124742
+ const invalidatedToken = typeof req.password === 'string' ? req.password : undefined;
124743
+ return tryBrokerRequest(baseUrl, token, '/git-credential/reject', {
124744
+ repoFullName,
124745
+ ...(invalidatedToken ? { invalidatedToken } : {}),
124746
+ });
124747
+ };
124748
+
124231
124749
  const { url: baseUrl, token, source } = brokerConfig;
124232
- let result = await tryFetchFromBroker(baseUrl, token);
124750
+ let result = isRejectAction
124751
+ ? await tryRejectFromBroker(baseUrl, token)
124752
+ : await tryFetchFromBroker(baseUrl, token);
124233
124753
 
124234
124754
  // If we had a connection error and we were using env vars, try the file fallback
124235
124755
  // This handles the case where the broker restarted on a different port
@@ -124237,10 +124757,17 @@ const main = async () => {
124237
124757
  const fileConfig = getBrokerConfigFromFile();
124238
124758
  if (fileConfig && fileConfig.url !== baseUrl) {
124239
124759
  debug('fallback', { from: baseUrl, to: fileConfig.url });
124240
- result = await tryFetchFromBroker(fileConfig.url, fileConfig.token);
124760
+ result = isRejectAction
124761
+ ? await tryRejectFromBroker(fileConfig.url, fileConfig.token)
124762
+ : await tryFetchFromBroker(fileConfig.url, fileConfig.token);
124241
124763
  }
124242
124764
  }
124243
124765
 
124766
+ if (isRejectAction) {
124767
+ debug(result.success ? 'reject_success' : 'reject_failed', { repoFullName });
124768
+ return;
124769
+ }
124770
+
124244
124771
  if (!result.success || !result.json) {
124245
124772
  return;
124246
124773
  }
@@ -125624,6 +126151,9 @@ path=/${options.repoFullName}.git
125624
126151
  acpFlushInFlight: null,
125625
126152
  acpFlushTimer: null,
125626
126153
  acpFlushCountInTurn: 0,
126154
+ codexProposedPlanBuffer: /* @__PURE__ */ new Map(),
126155
+ codexProposedPlanFlushInFlight: null,
126156
+ codexProposedPlanFlushTimer: null,
125627
126157
  contextWindowUsageBuffer: null,
125628
126158
  contextWindowUsageTimer: null,
125629
126159
  pendingContextWindowHandlers: /* @__PURE__ */ new Set(),
@@ -125713,7 +126243,7 @@ path=/${options.repoFullName}.git
125713
126243
  hasPendingTurnWork(sessionId) {
125714
126244
  const state2 = this.sessions.get(sessionId);
125715
126245
  if (!state2) return false;
125716
- return state2.turn.phase !== "idle" || state2.acpUpdateBuffer.length > 0 || state2.acpFlushInFlight !== null || state2.acpFlushTimer !== null || state2.contextWindowUsageBuffer !== null || state2.contextWindowUsageTimer !== null || state2.pendingContextWindowHandlers.size > 0 || state2.pendingThreadGoalHandlers.size > 0 || state2.codexImageGenerationUploads.size > 0 || state2.pendingUnread;
126246
+ return state2.turn.phase !== "idle" || state2.acpUpdateBuffer.length > 0 || state2.acpFlushInFlight !== null || state2.acpFlushTimer !== null || state2.codexProposedPlanBuffer.size > 0 || state2.codexProposedPlanFlushInFlight !== null || state2.codexProposedPlanFlushTimer !== null || state2.contextWindowUsageBuffer !== null || state2.contextWindowUsageTimer !== null || state2.pendingContextWindowHandlers.size > 0 || state2.pendingThreadGoalHandlers.size > 0 || state2.codexImageGenerationUploads.size > 0 || state2.pendingUnread;
125717
126247
  }
125718
126248
  clearTurnState(sessionId) {
125719
126249
  const state2 = this.sessions.get(sessionId);
@@ -125729,6 +126259,11 @@ path=/${options.repoFullName}.git
125729
126259
  state2.acpFlushTimer = null;
125730
126260
  }
125731
126261
  state2.acpFlushCountInTurn = 0;
126262
+ state2.codexProposedPlanBuffer.clear();
126263
+ if (state2.codexProposedPlanFlushTimer) {
126264
+ clearTimeout(state2.codexProposedPlanFlushTimer);
126265
+ state2.codexProposedPlanFlushTimer = null;
126266
+ }
125732
126267
  if (state2.contextWindowUsageTimer) {
125733
126268
  clearTimeout(state2.contextWindowUsageTimer);
125734
126269
  state2.contextWindowUsageTimer = null;
@@ -125747,6 +126282,9 @@ path=/${options.repoFullName}.git
125747
126282
  if (state2.acpFlushTimer) {
125748
126283
  clearTimeout(state2.acpFlushTimer);
125749
126284
  }
126285
+ if (state2.codexProposedPlanFlushTimer) {
126286
+ clearTimeout(state2.codexProposedPlanFlushTimer);
126287
+ }
125750
126288
  this.sessions.delete(sessionId);
125751
126289
  }
125752
126290
  }
@@ -126711,6 +127249,7 @@ $mem | ConvertTo-Json -Compress
126711
127249
  canceledTurnBySession = /* @__PURE__ */ new Map();
126712
127250
  currentTurnBySession = /* @__PURE__ */ new Map();
126713
127251
  turnRuntimeBySession = /* @__PURE__ */ new Map();
127252
+ inFlightAcpRefresh = new InFlightDedupe();
126714
127253
  getExecutionSnapshot(sessionId) {
126715
127254
  const runtime = this.turnRuntimeBySession.get(sessionId);
126716
127255
  const currentTurnId = this.currentTurnBySession.get(sessionId);
@@ -128296,7 +128835,11 @@ $mem | ConvertTo-Json -Compress
128296
128835
  error: `Machine mismatch: expected ${this.deps.machineId}, got ${message.machineId}`
128297
128836
  };
128298
128837
  }
128838
+ const dedupeKey = computeAcpRefreshDedupeKey(message.cliType, message.agentType, message.env);
128299
128839
  this.deps.logger.debug(`[acp-capabilities] Refresh requested (cliType=${message.cliType} agentType=${message.agentType})`);
128840
+ return await this.inFlightAcpRefresh.run(dedupeKey, () => this.executeAcpRefresh(message));
128841
+ }
128842
+ async executeAcpRefresh(message) {
128300
128843
  try {
128301
128844
  const { modes, models, configOptions, availableCommands } = await this.deps.fetchAcpCapabilities(message.cliType, message.agentType, message.env);
128302
128845
  await this.deps.workspaceDocument.updateAcpCapabilities(this.deps.machineId, message.cliType, message.agentType, modes, models, configOptions, availableCommands, getAcpCapabilitySourceVersion({
@@ -128333,6 +128876,11 @@ $mem | ConvertTo-Json -Compress
128333
128876
  }
128334
128877
  }
128335
128878
  }
128879
+ const computeAcpRefreshDedupeKey = (cliType, agentType, env2) => {
128880
+ const sortedKeys = env2 ? Object.keys(env2).sort() : [];
128881
+ const envSerialized = sortedKeys.map((k) => `${k}=${env2[k]}`).join("");
128882
+ return `${cliType}\0${agentType}\0${envSerialized}`;
128883
+ };
128336
128884
  class SessionUserResolver {
128337
128885
  constructor(logger2, workspaceId) {
128338
128886
  this.logger = logger2;
@@ -131638,17 +132186,24 @@ ${escapeHtmlScriptContent(VISUAL_ANNOTATION_INSPECTOR_BROWSER_SCRIPT)}
131638
132186
  function getProviderLabel$1(provider2) {
131639
132187
  return getLocalProjectHistoryProviderKey(provider2);
131640
132188
  }
132189
+ function resolveHistoryACPProcessLaunch(args2) {
132190
+ const launch = resolveACPProcessLaunch(args2.provider);
132191
+ return {
132192
+ ...launch,
132193
+ env: mergeACPProcessEnv(launch, args2.env ?? process.env)
132194
+ };
132195
+ }
131641
132196
  async function createHistoryAcpConnection(args2) {
131642
- const setting = resolveACPSetting(args2.provider);
131643
- const env2 = setting.exec.env ? {
131644
- ...process.env,
131645
- ...setting.exec.env
131646
- } : process.env;
132197
+ const launch = resolveHistoryACPProcessLaunch({
132198
+ provider: args2.provider
132199
+ });
131647
132200
  const agentProcess = spawnAcpProcess({
131648
132201
  cliType: args2.provider.cliType,
131649
132202
  agentType: args2.provider.agentType,
131650
132203
  workdir: args2.workdir,
131651
- env: env2
132204
+ env: launch.env,
132205
+ command: launch.command,
132206
+ args: launch.args
131652
132207
  });
131653
132208
  agentProcess.stderr?.setEncoding("utf8");
131654
132209
  agentProcess.stderr?.on("data", (chunk) => {
@@ -132539,6 +133094,7 @@ ${escapeHtmlScriptContent(VISUAL_ANNOTATION_INSPECTOR_BROWSER_SCRIPT)}
132539
133094
  streamClient: jsonStreamClient,
132540
133095
  rpcVersion: LORO_STREAMS_RPC_VERSION,
132541
133096
  retentionSeconds: LORO_STREAMS_RPC_RETENTION_SECONDS,
133097
+ now: getServerNow,
132542
133098
  getMachineStatus: async () => await this.executionService.getMachineStatus({
132543
133099
  type: "machine/status",
132544
133100
  machineId: this.machineId,
@@ -132635,6 +133191,7 @@ ${escapeHtmlScriptContent(VISUAL_ANNOTATION_INSPECTOR_BROWSER_SCRIPT)}
132635
133191
  usageTrackingService;
132636
133192
  static ACP_INITIAL_UPDATE_BATCH_WINDOW_MS = 10;
132637
133193
  static ACP_SUBSEQUENT_UPDATE_BATCH_WINDOW_MS = 100;
133194
+ static CODEX_PROPOSED_PLAN_UPDATE_BATCH_WINDOW_MS = 100;
132638
133195
  static CONTEXT_WINDOW_USAGE_THROTTLE_MS = 400;
132639
133196
  permissionRequestStartTimes = /* @__PURE__ */ new Map();
132640
133197
  machineHeartbeatTimer = null;
@@ -133038,6 +133595,125 @@ ${escapeHtmlScriptContent(VISUAL_ANNOTATION_INSPECTOR_BROWSER_SCRIPT)}
133038
133595
  this.logger.debug(`[${sessionId}] Failed to persist thread goal clear: ${formatErrorMessage(error2)}`);
133039
133596
  }
133040
133597
  }
133598
+ enqueueCodexProposedPlanUpdate(sessionId, plan) {
133599
+ const state2 = this.store.get(sessionId);
133600
+ const targetEntryId = this.store.getTurnId(sessionId);
133601
+ const existing = state2.codexProposedPlanBuffer.get(plan.turnId);
133602
+ if (existing && existing.targetEntryId === targetEntryId && existing.plan.markdown === plan.markdown && existing.plan.status === plan.status && existing.plan.isLatest === plan.isLatest) {
133603
+ return;
133604
+ }
133605
+ state2.codexProposedPlanBuffer.set(plan.turnId, {
133606
+ plan,
133607
+ ...targetEntryId ? {
133608
+ targetEntryId
133609
+ } : {}
133610
+ });
133611
+ this.scheduleCodexProposedPlanFlush(sessionId);
133612
+ }
133613
+ clearScheduledCodexProposedPlanFlush(sessionId) {
133614
+ const state2 = this.store.get(sessionId);
133615
+ if (!state2.codexProposedPlanFlushTimer) {
133616
+ return;
133617
+ }
133618
+ clearTimeout(state2.codexProposedPlanFlushTimer);
133619
+ state2.codexProposedPlanFlushTimer = null;
133620
+ }
133621
+ scheduleCodexProposedPlanFlush(sessionId) {
133622
+ const state2 = this.store.get(sessionId);
133623
+ if (state2.codexProposedPlanFlushInFlight || state2.codexProposedPlanFlushTimer) {
133624
+ return;
133625
+ }
133626
+ const timer2 = setTimeout(() => {
133627
+ state2.codexProposedPlanFlushTimer = null;
133628
+ void this.startCodexProposedPlanFlush(sessionId);
133629
+ }, MessageHandler.CODEX_PROPOSED_PLAN_UPDATE_BATCH_WINDOW_MS);
133630
+ timer2.unref?.();
133631
+ state2.codexProposedPlanFlushTimer = timer2;
133632
+ }
133633
+ startCodexProposedPlanFlush(sessionId) {
133634
+ const state2 = this.store.get(sessionId);
133635
+ if (state2.codexProposedPlanFlushInFlight) {
133636
+ return state2.codexProposedPlanFlushInFlight;
133637
+ }
133638
+ const flushPromise = this.flushCodexProposedPlanUpdates(sessionId).catch((error2) => {
133639
+ this.logger.error(`[${sessionId}] Failed to flush Codex proposed plan updates: ${formatErrorMessage(error2, {
133640
+ includeStack: true
133641
+ })}`);
133642
+ }).finally(() => {
133643
+ state2.codexProposedPlanFlushInFlight = null;
133644
+ if (state2.codexProposedPlanBuffer.size > 0) {
133645
+ this.scheduleCodexProposedPlanFlush(sessionId);
133646
+ }
133647
+ });
133648
+ state2.codexProposedPlanFlushInFlight = flushPromise;
133649
+ return flushPromise;
133650
+ }
133651
+ async flushCodexProposedPlanUpdatesNow(sessionId) {
133652
+ const state2 = this.store.get(sessionId);
133653
+ this.clearScheduledCodexProposedPlanFlush(sessionId);
133654
+ while (true) {
133655
+ if (!state2.codexProposedPlanFlushInFlight) {
133656
+ if (state2.codexProposedPlanBuffer.size === 0) {
133657
+ return;
133658
+ }
133659
+ void this.startCodexProposedPlanFlush(sessionId);
133660
+ }
133661
+ const inFlight = state2.codexProposedPlanFlushInFlight;
133662
+ if (!inFlight) {
133663
+ return;
133664
+ }
133665
+ await inFlight;
133666
+ this.clearScheduledCodexProposedPlanFlush(sessionId);
133667
+ if (state2.codexProposedPlanBuffer.size === 0 && !state2.codexProposedPlanFlushInFlight) {
133668
+ return;
133669
+ }
133670
+ }
133671
+ }
133672
+ async flushCodexProposedPlanUpdates(sessionId) {
133673
+ const state2 = this.store.get(sessionId);
133674
+ this.clearScheduledCodexProposedPlanFlush(sessionId);
133675
+ if (state2.codexProposedPlanBuffer.size === 0) {
133676
+ return;
133677
+ }
133678
+ const snapshots = [
133679
+ ...state2.codexProposedPlanBuffer.values()
133680
+ ];
133681
+ state2.codexProposedPlanBuffer.clear();
133682
+ const groups = /* @__PURE__ */ new Map();
133683
+ for (const snapshot of snapshots) {
133684
+ const key2 = snapshot.targetEntryId ?? "";
133685
+ const group = groups.get(key2);
133686
+ if (group) {
133687
+ group.plans.push(snapshot.plan);
133688
+ } else {
133689
+ groups.set(key2, {
133690
+ targetEntryId: snapshot.targetEntryId,
133691
+ plans: [
133692
+ snapshot.plan
133693
+ ]
133694
+ });
133695
+ }
133696
+ }
133697
+ const sessionDoc = await this.workspaceDocument.getOrCreateSessionDoc(sessionId);
133698
+ await sessionDoc.updateHistory((history) => {
133699
+ let next = history;
133700
+ for (const group of groups.values()) {
133701
+ next = applyMessageContentsBatch(next, group.plans, {
133702
+ ...group.targetEntryId ? {
133703
+ targetAssistantEntryId: group.targetEntryId
133704
+ } : {},
133705
+ createId: () => group.targetEntryId ?? v4(),
133706
+ now: () => new Date(getServerNow()).toISOString()
133707
+ });
133708
+ }
133709
+ return next;
133710
+ });
133711
+ if (state2.turn.phase === "idle") {
133712
+ await sessionDoc.setLastMessageAt();
133713
+ } else {
133714
+ state2.pendingUnread = true;
133715
+ }
133716
+ }
133041
133717
  async persistContextWindowUsage(sessionId, usage) {
133042
133718
  try {
133043
133719
  const sessionDoc = await this.workspaceDocument.getOrCreateSessionDoc(sessionId);
@@ -133473,13 +134149,14 @@ ${escapeHtmlScriptContent(VISUAL_ANNOTATION_INSPECTOR_BROWSER_SCRIPT)}
133473
134149
  return failure("local_project_not_found", `Local project not found: ${args2.localProjectId}`);
133474
134150
  }
133475
134151
  try {
134152
+ const state2 = getLocalProjectGitStateAtRootPath(rootPath);
133476
134153
  return {
133477
134154
  type: "local-project/git-state_response",
133478
134155
  machineId: this.machineId,
133479
134156
  workspaceId: this.workspaceId,
133480
134157
  localProjectId: args2.localProjectId,
133481
134158
  success: true,
133482
- state: getLocalProjectGitStateAtRootPath(rootPath),
134159
+ state: state2,
133483
134160
  observedAtMs: getServerNow()
133484
134161
  };
133485
134162
  } catch (error2) {
@@ -133579,6 +134256,9 @@ ${escapeHtmlScriptContent(VISUAL_ANNOTATION_INSPECTOR_BROWSER_SCRIPT)}
133579
134256
  this.sessionManager.on("onThreadGoalCleared", (sessionId, threadId2) => {
133580
134257
  this.enqueueThreadGoalHistoryPersist(sessionId, () => this.persistThreadGoalClear(sessionId, threadId2));
133581
134258
  });
134259
+ this.sessionManager.on("onCodexProposedPlan", (sessionId, plan) => {
134260
+ this.enqueueCodexProposedPlanUpdate(sessionId, plan);
134261
+ });
133582
134262
  this.sessionManager.on("onCodexImageGenerationBegin", (sessionId, event) => {
133583
134263
  this.handleCodexImageGenerationBegin(sessionId, event);
133584
134264
  });
@@ -134254,6 +134934,7 @@ ${escapeHtmlScriptContent(VISUAL_ANNOTATION_INSPECTOR_BROWSER_SCRIPT)}
134254
134934
  await this.flushSessionContextWindowUsage(sessionId);
134255
134935
  await this.flushACPUpdatesNow(sessionId);
134256
134936
  await this.flushThreadGoalHistoryPersists(sessionId);
134937
+ await this.flushCodexProposedPlanUpdatesNow(sessionId);
134257
134938
  await this.flushCodexGeneratedImageUploads(sessionId);
134258
134939
  if (state2.pendingUnread) {
134259
134940
  state2.pendingUnread = false;
@@ -136089,12 +136770,17 @@ ${escapeHtmlScriptContent(VISUAL_ANNOTATION_INSPECTOR_BROWSER_SCRIPT)}
136089
136770
  this.stopAutoRefresh();
136090
136771
  this.states.clear();
136091
136772
  }
136092
- invalidate(repoFullName) {
136773
+ invalidate(repoFullName, options) {
136093
136774
  try {
136094
136775
  const repoKeyPrefix = `${normalizeGitHubRepo(repoFullName).toLowerCase()}:`;
136095
136776
  const states = Array.from(this.states.entries()).filter(([key2]) => key2.startsWith(repoKeyPrefix));
136096
136777
  for (const [repoKey, state2] of states) {
136097
136778
  this.logger.debug(`[github-token] Invalidating cached token for repo: ${repoKey}`);
136779
+ if (options?.invalidatedToken && state2.operation === "write") {
136780
+ state2.invalidatedPersonalToken = options.invalidatedToken;
136781
+ } else if (options?.markPersonalTokenInvalid && state2.operation === "write" && state2.tokenSource === "personal" && state2.token) {
136782
+ state2.invalidatedPersonalToken = state2.token;
136783
+ }
136098
136784
  state2.token = null;
136099
136785
  state2.expiresAtMs = null;
136100
136786
  state2.tokenSource = null;
@@ -136110,6 +136796,7 @@ ${escapeHtmlScriptContent(VISUAL_ANNOTATION_INSPECTOR_BROWSER_SCRIPT)}
136110
136796
  state2.token = null;
136111
136797
  state2.expiresAtMs = null;
136112
136798
  state2.tokenSource = null;
136799
+ state2.invalidatedPersonalToken = null;
136113
136800
  state2.inFlight = void 0;
136114
136801
  }
136115
136802
  }
@@ -136123,7 +136810,8 @@ ${escapeHtmlScriptContent(VISUAL_ANNOTATION_INSPECTOR_BROWSER_SCRIPT)}
136123
136810
  operation,
136124
136811
  token: null,
136125
136812
  expiresAtMs: null,
136126
- tokenSource: null
136813
+ tokenSource: null,
136814
+ invalidatedPersonalToken: null
136127
136815
  };
136128
136816
  this.states.set(repoKey, state2);
136129
136817
  return state2;
@@ -136144,7 +136832,7 @@ ${escapeHtmlScriptContent(VISUAL_ANNOTATION_INSPECTOR_BROWSER_SCRIPT)}
136144
136832
  this.applyTokenResult(state2, result);
136145
136833
  return;
136146
136834
  }
136147
- state2.inFlight = this.fetchToken(state2.repoFullName, state2.operation);
136835
+ state2.inFlight = this.fetchToken(state2.repoFullName, state2.operation, state2.invalidatedPersonalToken ?? void 0);
136148
136836
  try {
136149
136837
  const result = await state2.inFlight;
136150
136838
  this.applyTokenResult(state2, result);
@@ -136177,12 +136865,15 @@ ${escapeHtmlScriptContent(VISUAL_ANNOTATION_INSPECTOR_BROWSER_SCRIPT)}
136177
136865
  this.refreshAllInFlight = null;
136178
136866
  }
136179
136867
  }
136180
- async fetchToken(repoFullName, operation) {
136868
+ async fetchToken(repoFullName, operation, invalidatedPersonalToken) {
136181
136869
  const raw = await this.client.action(api.github.getOperationAccessTokenByRepoNameForCli, {
136182
136870
  repoFullName,
136183
136871
  cliToken: this.cliToken,
136184
136872
  workspaceId: this.workspaceId,
136185
- operation
136873
+ operation,
136874
+ ...operation === "write" && invalidatedPersonalToken ? {
136875
+ invalidatedPersonalToken
136876
+ } : {}
136186
136877
  });
136187
136878
  const result = GitHubTokenResponseSchema.parse(raw);
136188
136879
  if (!result.success) {
@@ -136195,9 +136886,14 @@ ${escapeHtmlScriptContent(VISUAL_ANNOTATION_INSPECTOR_BROWSER_SCRIPT)}
136195
136886
  };
136196
136887
  }
136197
136888
  applyTokenResult(state2, result) {
136889
+ const tokenSource = result.tokenSource ?? "app";
136890
+ if (state2.invalidatedPersonalToken && tokenSource === "personal" && result.token === state2.invalidatedPersonalToken) {
136891
+ throw new GitHubTokenFetchError("token_generation_failed", `Backend returned an invalidated personal GitHub token for repository "${state2.repoFullName}".`);
136892
+ }
136198
136893
  const parsedMs = result.expiresAt ? Date.parse(result.expiresAt) : NaN;
136199
136894
  state2.token = result.token;
136200
- state2.tokenSource = result.tokenSource ?? "app";
136895
+ state2.tokenSource = tokenSource;
136896
+ state2.invalidatedPersonalToken = null;
136201
136897
  if (Number.isFinite(parsedMs)) {
136202
136898
  state2.expiresAtMs = parsedMs;
136203
136899
  } else if (state2.tokenSource === "personal") {
@@ -136221,7 +136917,12 @@ ${escapeHtmlScriptContent(VISUAL_ANNOTATION_INSPECTOR_BROWSER_SCRIPT)}
136221
136917
  }));
136222
136918
  return;
136223
136919
  }
136224
- if (req.method !== "POST" || req.url !== "/git-credential" && req.url !== "/github-token") {
136920
+ if (req.method !== "POST" || ![
136921
+ "/git-credential",
136922
+ "/github-token",
136923
+ "/git-credential/reject",
136924
+ "/github-token/reject"
136925
+ ].includes(req.url ?? "")) {
136225
136926
  res.writeHead(404, {
136226
136927
  "Content-Type": "application/json"
136227
136928
  });
@@ -136255,8 +136956,25 @@ ${escapeHtmlScriptContent(VISUAL_ANNOTATION_INSPECTOR_BROWSER_SCRIPT)}
136255
136956
  }));
136256
136957
  return;
136257
136958
  }
136959
+ if (req.url === "/git-credential/reject" || req.url === "/github-token/reject") {
136960
+ const invalidatedToken = obj && typeof obj.invalidatedToken === "string" ? obj.invalidatedToken : void 0;
136961
+ options.tokenManager.invalidate(repoFullName, {
136962
+ ...invalidatedToken ? {
136963
+ invalidatedToken
136964
+ } : {
136965
+ markPersonalTokenInvalid: true
136966
+ }
136967
+ });
136968
+ res.writeHead(200, {
136969
+ "Content-Type": "application/json"
136970
+ });
136971
+ res.end(JSON.stringify({
136972
+ ok: true
136973
+ }));
136974
+ return;
136975
+ }
136258
136976
  const tokenValue = await options.tokenManager.getTokenForRepo(repoFullName, {
136259
- operation: "read"
136977
+ operation: "write"
136260
136978
  });
136261
136979
  if (!tokenValue) {
136262
136980
  res.writeHead(404, {
@@ -136589,11 +137307,11 @@ ${escapeHtmlScriptContent(VISUAL_ANNOTATION_INSPECTOR_BROWSER_SCRIPT)}
136589
137307
  return null;
136590
137308
  }
136591
137309
  if (!githubRepo) {
136592
- logger2.debug("[gh-token] No GitHub repo context \u2014 cannot fetch installation token");
137310
+ logger2.debug("[gh-token] No GitHub repo context \u2014 cannot fetch managed token");
136593
137311
  return null;
136594
137312
  }
136595
137313
  if (!tokenManager) {
136596
- logger2.debug("[gh-token] No token manager available \u2014 cannot fetch installation token");
137314
+ logger2.debug("[gh-token] No token manager available \u2014 cannot fetch managed token");
136597
137315
  return null;
136598
137316
  }
136599
137317
  if (managedRepoToken) {
@@ -136612,10 +137330,10 @@ ${escapeHtmlScriptContent(VISUAL_ANNOTATION_INSPECTOR_BROWSER_SCRIPT)}
136612
137330
  const token2 = await tokenManager.getTokenForRepo(githubRepo, {
136613
137331
  operation: "write"
136614
137332
  });
136615
- logger2.debug(`[gh-token] Fetched installation token for ${githubRepo}`);
137333
+ logger2.debug(`[gh-token] Fetched managed write-operation token for ${githubRepo}`);
136616
137334
  return token2;
136617
137335
  } catch (error2) {
136618
- logger2.debug(`[gh-token] Failed to fetch installation token for ${githubRepo}: ${formatErrorMessage(error2)}`);
137336
+ logger2.debug(`[gh-token] Failed to fetch managed write-operation token for ${githubRepo}: ${formatErrorMessage(error2)}`);
136619
137337
  return null;
136620
137338
  }
136621
137339
  }
@@ -136657,9 +137375,17 @@ ${escapeHtmlScriptContent(VISUAL_ANNOTATION_INSPECTOR_BROWSER_SCRIPT)}
136657
137375
  mode: mode2
136658
137376
  });
136659
137377
  };
136660
- const GH_SHIM_BASENAME = "gh";
137378
+ const GH_SHIM_POSIX_BASENAME = "gh";
137379
+ const GH_SHIM_WINDOWS_BASENAME = "gh.cmd";
136661
137380
  const REAL_GH_PATH_PLACEHOLDER = "__LODY_REAL_GH_PATH__";
137381
+ const NODE_EXEC_PATH_PLACEHOLDER = "__LODY_NODE_EXEC_PATH__";
136662
137382
  const MANAGED_TOKEN_MARKER_ENV = LODY_MANAGED_GH_TOKEN_SHA256_ENV;
137383
+ const getWindowsExecutableCandidateNames = (name2) => [
137384
+ ".exe",
137385
+ ".cmd",
137386
+ ".bat",
137387
+ ".com"
137388
+ ].map((ext2) => `${name2}${ext2}`).concat(name2);
136663
137389
  const normalizeComparablePath = (value) => {
136664
137390
  const resolved = path__default.resolve(value);
136665
137391
  return process.platform === "win32" ? resolved.toLowerCase() : resolved;
@@ -136676,6 +137402,9 @@ ${escapeHtmlScriptContent(VISUAL_ANNOTATION_INSPECTOR_BROWSER_SCRIPT)}
136676
137402
  if (!statSync(filePath).isFile()) {
136677
137403
  return false;
136678
137404
  }
137405
+ if (process.platform === "win32") {
137406
+ return true;
137407
+ }
136679
137408
  accessSync(filePath, constants$4.X_OK);
136680
137409
  return true;
136681
137410
  } catch {
@@ -136689,10 +137418,7 @@ ${escapeHtmlScriptContent(VISUAL_ANNOTATION_INSPECTOR_BROWSER_SCRIPT)}
136689
137418
  }
136690
137419
  const excludedComparablePath = toComparablePath(excludedPath);
136691
137420
  const excludedComparableDir = toComparablePath(path__default.dirname(excludedPath));
136692
- const candidateNames = process.platform === "win32" ? [
136693
- `${name2}.exe`,
136694
- name2
136695
- ] : [
137421
+ const candidateNames = process.platform === "win32" ? getWindowsExecutableCandidateNames(name2) : [
136696
137422
  name2
136697
137423
  ];
136698
137424
  for (const dir of pathEnv.split(path__default.delimiter)) {
@@ -136729,6 +137455,7 @@ const REAL_GH_PATH = '${REAL_GH_PATH_PLACEHOLDER}';
136729
137455
  const SHIM_PATH = __filename;
136730
137456
  const SHIM_DIR = path.dirname(SHIM_PATH);
136731
137457
  const MANAGED_TOKEN_MARKER_ENV = '${MANAGED_TOKEN_MARKER_ENV}';
137458
+ const WINDOWS_EXECUTABLE_CANDIDATE_NAMES = ['gh.exe', 'gh.cmd', 'gh.bat', 'gh.com', 'gh'];
136732
137459
 
136733
137460
  const normalizeComparablePath = (value) => {
136734
137461
  const resolved = path.resolve(String(value || ''));
@@ -136748,6 +137475,7 @@ const isExecutableFile = (filePath) => {
136748
137475
  if (!filePath) return false;
136749
137476
  try {
136750
137477
  if (!fs.statSync(filePath).isFile()) return false;
137478
+ if (process.platform === 'win32') return true;
136751
137479
  fs.accessSync(filePath, fs.constants.X_OK);
136752
137480
  return true;
136753
137481
  } catch {
@@ -136761,7 +137489,8 @@ const resolveGhFromPath = () => {
136761
137489
 
136762
137490
  const shimComparablePath = toComparablePath(SHIM_PATH);
136763
137491
  const shimComparableDir = toComparablePath(SHIM_DIR);
136764
- const candidateNames = process.platform === 'win32' ? ['gh.exe', 'gh'] : ['gh'];
137492
+ const candidateNames =
137493
+ process.platform === 'win32' ? WINDOWS_EXECUTABLE_CANDIDATE_NAMES : ['gh'];
136765
137494
 
136766
137495
  for (const dir of pathEnv.split(path.delimiter)) {
136767
137496
  if (!dir) continue;
@@ -136785,6 +137514,32 @@ const resolveRealGhCommand = () => {
136785
137514
  return resolveGhFromPath();
136786
137515
  };
136787
137516
 
137517
+ const quoteCmdArg = (arg) => {
137518
+ const value = String(arg || '');
137519
+ if (value === '') return '""';
137520
+ if (!/[\\s"]/u.test(value)) return value;
137521
+ return '"' + value.replace(/"/g, '""') + '"';
137522
+ };
137523
+
137524
+ const buildWindowsSpawnSpec = (command, args) => {
137525
+ const lower = String(command || '').toLowerCase();
137526
+ if (process.platform === 'win32' && (lower.endsWith('.cmd') || lower.endsWith('.bat'))) {
137527
+ const cmdline = [quoteCmdArg(command), ...args.map(quoteCmdArg)].join(' ');
137528
+ return { command: 'cmd.exe', args: ['/d', '/s', '/c', cmdline] };
137529
+ }
137530
+ return { command, args };
137531
+ };
137532
+
137533
+ const spawnGh = (command, args, options) => {
137534
+ const spec = buildWindowsSpawnSpec(command, args);
137535
+ return spawn(spec.command, spec.args, options);
137536
+ };
137537
+
137538
+ const spawnSyncGh = (command, args, options) => {
137539
+ const spec = buildWindowsSpawnSpec(command, args);
137540
+ return spawnSync(spec.command, spec.args, options);
137541
+ };
137542
+
136788
137543
  const fingerprintToken = (token) => crypto.createHash('sha256').update(token).digest('hex');
136789
137544
 
136790
137545
  const isManagedTokenValue = (token, marker) => {
@@ -136832,7 +137587,7 @@ const buildGhAuthEnv = (env) => {
136832
137587
  const isGhCliAuthed = (ghPath) => {
136833
137588
  if (!ghPath) return false;
136834
137589
  try {
136835
- const result = spawnSync(ghPath, ['auth', 'status'], {
137590
+ const result = spawnSyncGh(ghPath, ['auth', 'status'], {
136836
137591
  env: buildGhAuthEnv(process.env),
136837
137592
  stdio: ['ignore', 'ignore', 'ignore'],
136838
137593
  timeout: 5000,
@@ -136932,30 +137687,23 @@ const isConnectionError = (error) => {
136932
137687
  return message.includes('econnrefused') || message.includes('enotfound') || message.includes('fetch failed');
136933
137688
  };
136934
137689
 
136935
- const doFetchFromBroker = async (baseUrl, brokerToken, repoFullName) => {
137690
+ const doBrokerRequest = async (baseUrl, brokerToken, endpoint, body, timeoutMs) => {
136936
137691
  const fetchImpl = globalThis.fetch;
136937
- if (typeof fetchImpl !== 'function') return { result: null };
137692
+ if (typeof fetchImpl !== 'function') return { unavailable: true };
136938
137693
 
136939
137694
  const controller = new AbortController();
136940
- const timeoutId = setTimeout(() => controller.abort(), 10000);
137695
+ const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
136941
137696
  try {
136942
- const res = await fetchImpl(baseUrl + '/github-token', {
137697
+ const res = await fetchImpl(baseUrl + endpoint, {
136943
137698
  method: 'POST',
136944
137699
  headers: {
136945
137700
  'Content-Type': 'application/json',
136946
137701
  Authorization: 'Bearer ' + brokerToken,
136947
137702
  },
136948
- body: JSON.stringify({ repoFullName }),
137703
+ body: JSON.stringify(body),
136949
137704
  signal: controller.signal,
136950
137705
  });
136951
- if (!res || !res.ok) {
136952
- return { result: null };
136953
- }
136954
- const json = await res.json().catch(() => null);
136955
- if (!json || typeof json.token !== 'string' || !json.token) {
136956
- return { result: null };
136957
- }
136958
- return { result: { token: json.token } };
137706
+ return { res };
136959
137707
  } catch (error) {
136960
137708
  return { error };
136961
137709
  } finally {
@@ -136963,51 +137711,105 @@ const doFetchFromBroker = async (baseUrl, brokerToken, repoFullName) => {
136963
137711
  }
136964
137712
  };
136965
137713
 
136966
- const fetchTokenFromBroker = async (repoFullName) => {
137714
+ const doFetchFromBroker = async (baseUrl, brokerToken, repoFullName) => {
137715
+ const reply = await doBrokerRequest(
137716
+ baseUrl,
137717
+ brokerToken,
137718
+ '/github-token',
137719
+ { repoFullName },
137720
+ 10000
137721
+ );
137722
+ if (reply.unavailable) return { result: null };
137723
+ if (reply.error) return { error: reply.error };
137724
+ if (!reply.res.ok) return { result: null };
137725
+ const json = await reply.res.json().catch(() => null);
137726
+ if (!json || typeof json.token !== 'string' || !json.token) {
137727
+ return { result: null };
137728
+ }
137729
+ return { result: { token: json.token } };
137730
+ };
137731
+
137732
+ const doRejectToBroker = async (baseUrl, brokerToken, repoFullName, invalidatedToken) => {
137733
+ // Short timeout: the gh shim awaits this before exiting, so a slow broker would stall
137734
+ // the user. The next gh invocation will re-trigger reject if delivery here fails.
137735
+ const reply = await doBrokerRequest(
137736
+ baseUrl,
137737
+ brokerToken,
137738
+ '/github-token/reject',
137739
+ { repoFullName, ...(invalidatedToken ? { invalidatedToken } : {}) },
137740
+ 2000
137741
+ );
137742
+ if (reply.unavailable) return { result: false };
137743
+ if (reply.error) return { error: reply.error };
137744
+ return { result: reply.res.ok };
137745
+ };
137746
+
137747
+ const callBrokerWithFallback = async (action) => {
136967
137748
  const brokerConfig = getBrokerConfig();
136968
137749
  if (!brokerConfig) return null;
136969
137750
 
136970
137751
  const { url, token, source } = brokerConfig;
136971
- const first = await doFetchFromBroker(url, token, repoFullName);
136972
- if (first.result !== undefined) {
136973
- return first.result;
136974
- }
137752
+ const first = await action(url, token);
137753
+ if (first.result !== undefined) return first;
136975
137754
 
136976
137755
  if (first.error && source === 'env' && isConnectionError(first.error)) {
136977
137756
  const fileConfig = getBrokerConfigFromFile();
136978
137757
  if (fileConfig && fileConfig.url !== url) {
136979
- const fallback = await doFetchFromBroker(fileConfig.url, fileConfig.token, repoFullName);
136980
- if (fallback.result !== undefined) {
136981
- return fallback.result;
136982
- }
137758
+ return await action(fileConfig.url, fileConfig.token);
136983
137759
  }
136984
137760
  }
137761
+ return first;
137762
+ };
136985
137763
 
136986
- return null;
137764
+ const fetchTokenFromBroker = async (repoFullName) => {
137765
+ const reply = await callBrokerWithFallback((url, token) =>
137766
+ doFetchFromBroker(url, token, repoFullName)
137767
+ );
137768
+ return reply && reply.result ? reply.result : null;
137769
+ };
137770
+
137771
+ const rejectTokenToBroker = async (repoFullName, invalidatedToken) => {
137772
+ await callBrokerWithFallback((url, token) =>
137773
+ doRejectToBroker(url, token, repoFullName, invalidatedToken)
137774
+ );
137775
+ };
137776
+
137777
+ const GH_AUTH_FAILURE_PHRASES = [
137778
+ 'http 401',
137779
+ '401 unauthorized',
137780
+ 'bad credentials',
137781
+ 'requires authentication',
137782
+ 'authentication failed',
137783
+ ];
137784
+
137785
+ const isGhAuthFailureOutput = (stderrText) => {
137786
+ const value = String(stderrText || '').toLowerCase();
137787
+ return GH_AUTH_FAILURE_PHRASES.some((phrase) => value.includes(phrase));
136987
137788
  };
136988
137789
 
136989
137790
  const buildGhEnv = async (ghCommand) => {
136990
137791
  const env = { ...process.env };
136991
137792
  if (hasUserProvidedEnvToken(env)) {
136992
137793
  clearManagedTokenEnv(env);
136993
- return env;
137794
+ return { env };
136994
137795
  }
136995
137796
 
136996
137797
  if (isGhCliAuthed(ghCommand)) {
136997
137798
  clearManagedTokenEnv(env);
136998
- return env;
137799
+ return { env };
136999
137800
  }
137000
137801
 
137001
137802
  const repoFullName = readRepoFullName();
137002
137803
  if (!repoFullName) {
137003
- return env;
137804
+ return { env };
137004
137805
  }
137005
137806
 
137006
137807
  const result = await fetchTokenFromBroker(repoFullName);
137007
137808
  if (result && result.token) {
137008
137809
  injectGhToken(env, result.token);
137810
+ return { env, managed: { token: result.token, repoFullName } };
137009
137811
  }
137010
- return env;
137812
+ return { env };
137011
137813
  };
137012
137814
 
137013
137815
  const main = async () => {
@@ -137017,30 +137819,51 @@ const main = async () => {
137017
137819
  process.exit(127);
137018
137820
  }
137019
137821
 
137020
- const child = spawn(ghCommand, process.argv.slice(2), {
137021
- stdio: 'inherit',
137022
- env: await buildGhEnv(ghCommand),
137822
+ const ghEnv = await buildGhEnv(ghCommand);
137823
+ const child = spawnGh(ghCommand, process.argv.slice(2), {
137824
+ stdio: ['inherit', 'inherit', 'pipe'],
137825
+ env: ghEnv.env,
137826
+ });
137827
+ let stderrText = '';
137828
+ child.stderr.on('data', (chunk) => {
137829
+ const text = String(chunk || '');
137830
+ process.stderr.write(chunk);
137831
+ if (stderrText.length < 20000) {
137832
+ stderrText += text.slice(0, 20000 - stderrText.length);
137833
+ }
137023
137834
  });
137024
137835
 
137025
137836
  child.on('error', () => process.exit(127));
137026
137837
  child.on('close', (code, signal) => {
137027
- if (signal) {
137028
- process.kill(process.pid, signal);
137029
- return;
137030
- }
137031
- process.exit(code ?? 1);
137838
+ void (async () => {
137839
+ if (code && ghEnv.managed && isGhAuthFailureOutput(stderrText)) {
137840
+ await rejectTokenToBroker(ghEnv.managed.repoFullName, ghEnv.managed.token);
137841
+ }
137842
+ if (signal) {
137843
+ process.kill(process.pid, signal);
137844
+ return;
137845
+ }
137846
+ process.exit(code ?? 1);
137847
+ })();
137032
137848
  });
137033
137849
  };
137034
137850
 
137035
137851
  main().catch(() => process.exit(1));
137852
+ `;
137853
+ const windowsLauncherSourceTemplate = `@echo off
137854
+ "${NODE_EXEC_PATH_PLACEHOLDER}" "%~dp0gh" %*
137036
137855
  `;
137037
137856
  const getGhShimHostBinDir = () => path__default.join(os__default.homedir(), ".lody", "bin");
137038
- const getGhShimHostPath = () => path__default.join(getGhShimHostBinDir(), GH_SHIM_BASENAME);
137857
+ const getGhShimHostNodeScriptPath = () => path__default.join(getGhShimHostBinDir(), GH_SHIM_POSIX_BASENAME);
137858
+ const getGhShimHostWindowsLauncherPath = () => path__default.join(getGhShimHostBinDir(), GH_SHIM_WINDOWS_BASENAME);
137859
+ const getGhShimHostPath = () => process.platform === "win32" ? getGhShimHostWindowsLauncherPath() : getGhShimHostNodeScriptPath();
137039
137860
  const escapeForSingleQuotedString = (value) => value.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
137861
+ const escapeForDoubleQuotedCmdString = (value) => value.replace(/"/g, '""');
137040
137862
  const buildGhShimSource = (realGhPath) => {
137041
137863
  const escapedRealPath = realGhPath ? escapeForSingleQuotedString(realGhPath) : "";
137042
137864
  return wrapperSourceTemplate.split(REAL_GH_PATH_PLACEHOLDER).join(escapedRealPath);
137043
137865
  };
137866
+ const buildWindowsLauncherSource = () => windowsLauncherSourceTemplate.split(NODE_EXEC_PATH_PLACEHOLDER).join(escapeForDoubleQuotedCmdString(process.execPath));
137044
137867
  const resolveRealGhPath = () => resolveExecutableFromPath("gh", getGhShimHostPath());
137045
137868
  const ensureParentDirForFile = (filePath) => {
137046
137869
  const dir = path__default.dirname(filePath);
@@ -137082,19 +137905,35 @@ main().catch(() => process.exit(1));
137082
137905
  }
137083
137906
  };
137084
137907
  const ensureGhShimScript = () => {
137085
- const filePath = getGhShimHostPath();
137086
- ensureParentDirForFile(filePath);
137087
- ensureWritableShimTarget(filePath);
137088
137908
  const source = buildGhShimSource(resolveRealGhPath());
137089
- try {
137090
- writeIfChanged(filePath, source, 493);
137091
- } catch (error2) {
137092
- const code2 = error2 instanceof Error && "code" in error2 ? error2.code : void 0;
137093
- if (code2 !== "ENOENT") {
137094
- throw error2;
137909
+ const shimTargets = process.platform === "win32" ? [
137910
+ {
137911
+ filePath: getGhShimHostNodeScriptPath(),
137912
+ content: source
137913
+ },
137914
+ {
137915
+ filePath: getGhShimHostWindowsLauncherPath(),
137916
+ content: buildWindowsLauncherSource()
137095
137917
  }
137918
+ ] : [
137919
+ {
137920
+ filePath: getGhShimHostNodeScriptPath(),
137921
+ content: source
137922
+ }
137923
+ ];
137924
+ for (const { filePath, content } of shimTargets) {
137096
137925
  ensureParentDirForFile(filePath);
137097
- writeIfChanged(filePath, source, 493);
137926
+ ensureWritableShimTarget(filePath);
137927
+ try {
137928
+ writeIfChanged(filePath, content, 493);
137929
+ } catch (error2) {
137930
+ const code2 = error2 instanceof Error && "code" in error2 ? error2.code : void 0;
137931
+ if (code2 !== "ENOENT") {
137932
+ throw error2;
137933
+ }
137934
+ ensureParentDirForFile(filePath);
137935
+ writeIfChanged(filePath, content, 493);
137936
+ }
137098
137937
  }
137099
137938
  };
137100
137939
  const prependGhShimBinDirToPath = (pathEnv) => {
@@ -142405,13 +143244,11 @@ export PATH=${toSingleQuotedShellString(ghShimBinDir)}:"$PATH"
142405
143244
  LODY_SESSION_ID: this.sessionId,
142406
143245
  LODY_WORKSPACE_SESSION_ID: workspaceSessionId
142407
143246
  };
142408
- if (this.config.agentCliType === "builtin" && this.config.agentType === "claude") {
142409
- return scrubInheritedClaudeAuthEnv(merged, {
142410
- ...configEnv,
142411
- ...extraEnv
142412
- });
142413
- }
142414
- return merged;
143247
+ const agentEnv = this.config.agentCliType === "builtin" && this.config.agentType === "claude" ? scrubInheritedClaudeAuthEnv(merged, {
143248
+ ...configEnv,
143249
+ ...extraEnv
143250
+ }) : merged;
143251
+ return withDefaultAcpPathEntries(agentEnv);
142415
143252
  }
142416
143253
  async createAgent(callbacks) {
142417
143254
  const env2 = this.buildShellEnv(callbacks.env);
@@ -142491,6 +143328,7 @@ export PATH=${toSingleQuotedShellString(ghShimBinDir)}:"$PATH"
142491
143328
  onRateLimitUpdate: callbacks.onRateLimitUpdate,
142492
143329
  onThreadGoalUpdated: callbacks.onThreadGoalUpdated,
142493
143330
  onThreadGoalCleared: callbacks.onThreadGoalCleared,
143331
+ onCodexProposedPlan: callbacks.onCodexProposedPlan,
142494
143332
  onCodexImageGenerationBegin: callbacks.onCodexImageGenerationBegin,
142495
143333
  onCodexImageGenerationEnd: callbacks.onCodexImageGenerationEnd,
142496
143334
  sessionId: this.sessionId,
@@ -142719,7 +143557,7 @@ export PATH=${toSingleQuotedShellString(ghShimBinDir)}:"$PATH"
142719
143557
  const sessionId = config2.sessionId;
142720
143558
  this.logger.debug(`[${sessionId}] Session workdir resolved: ${session.getWorkdir()}`);
142721
143559
  let acpSessionId;
142722
- const setting = resolveACPSetting({
143560
+ const launch = resolveACPProcessLaunch({
142723
143561
  cliType: config2.agentCliType,
142724
143562
  agentType: config2.agentType
142725
143563
  });
@@ -142736,9 +143574,9 @@ export PATH=${toSingleQuotedShellString(ghShimBinDir)}:"$PATH"
142736
143574
  acpSessionId = await withSlowOperationWarning(session.createAgent({
142737
143575
  cliType: config2.agentCliType,
142738
143576
  agentType: config2.agentType,
142739
- command: setting.exec.command,
142740
- args: setting.exec.args,
142741
- env: setting.exec.env,
143577
+ command: launch.command,
143578
+ args: launch.args,
143579
+ env: launch.env,
142742
143580
  resumeSessionId: requestedResumeSessionId,
142743
143581
  onStartupStage: (event) => {
142744
143582
  if (event.type === "initialize_start") {
@@ -142787,6 +143625,9 @@ export PATH=${toSingleQuotedShellString(ghShimBinDir)}:"$PATH"
142787
143625
  onThreadGoalCleared: (threadId2) => {
142788
143626
  this.emit("onThreadGoalCleared", sessionId, threadId2);
142789
143627
  },
143628
+ onCodexProposedPlan: (plan) => {
143629
+ this.emit("onCodexProposedPlan", sessionId, plan);
143630
+ },
142790
143631
  onCodexImageGenerationBegin: (event) => {
142791
143632
  this.emit("onCodexImageGenerationBegin", sessionId, event);
142792
143633
  },
@@ -142838,7 +143679,10 @@ export PATH=${toSingleQuotedShellString(ghShimBinDir)}:"$PATH"
142838
143679
  tokenManager?.retainRepoOwner(githubRepo);
142839
143680
  if (tokenManager) {
142840
143681
  try {
142841
- await tokenManager.getTokenForRepo(githubRepo);
143682
+ tokenManager.invalidate(githubRepo);
143683
+ await tokenManager.getTokenForRepo(githubRepo, {
143684
+ operation: "write"
143685
+ });
142842
143686
  this.logger.debug(`[${config2.sessionId}] [github-token] Prefetch succeeded for ${githubRepo}`);
142843
143687
  } catch (error2) {
142844
143688
  this.logger.debug(`[${config2.sessionId}] [github-token] Prefetch failed for ${githubRepo}: ${formatErrorMessage(error2)}`);
@@ -142906,6 +143750,7 @@ export PATH=${toSingleQuotedShellString(ghShimBinDir)}:"$PATH"
142906
143750
  return;
142907
143751
  }
142908
143752
  try {
143753
+ tokenManager.invalidate(githubRepo);
142909
143754
  const token2 = await tokenManager.getTokenForRepo(githubRepo, {
142910
143755
  operation: "write"
142911
143756
  });