lody 0.92.1 → 0.93.2

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.
@@ -5,7 +5,7 @@ import { Writable, Readable } from "node:stream";
5
5
  import { StringDecoder } from "node:string_decoder";
6
6
  import fs__default from "node:fs";
7
7
  import path__default from "node:path";
8
- import Xt__default, { constants, readFile } from "node:fs/promises";
8
+ import Xt__default, { constants, readFile, realpath } from "node:fs/promises";
9
9
  import { randomUUID, createHash } from "node:crypto";
10
10
  import process$1 from "node:process";
11
11
  import { fileURLToPath } from "node:url";
@@ -3418,7 +3418,7 @@ var require_main = __commonJS({
3418
3418
  exports$1.createMessageConnection = createMessageConnection2;
3419
3419
  var ril_1 = __importDefault(require_ril());
3420
3420
  ril_1.default.install();
3421
- var path9 = __importStar(__require("path"));
3421
+ var path10 = __importStar(__require("path"));
3422
3422
  var os2 = __importStar(__require("os"));
3423
3423
  var fs8 = __importStar(__require("fs"));
3424
3424
  var crypto_1 = __require("crypto");
@@ -3570,7 +3570,7 @@ var require_main = __commonJS({
3570
3570
  throw new Error(`Unable to generate a random pipe name with ${randomLength} characters.`);
3571
3571
  }
3572
3572
  const randomSuffix = (0, crypto_1.randomBytes)(Math.floor(randomLength / 2)).toString("hex");
3573
- return path9.join(tmpDir, `lsp-${randomSuffix}.sock`);
3573
+ return path10.join(tmpDir, `lsp-${randomSuffix}.sock`);
3574
3574
  }
3575
3575
  function createClientPipeTransport(pipeName, encoding = "utf-8") {
3576
3576
  let connectResolve;
@@ -4461,10 +4461,10 @@ function mergeDefs(...defs) {
4461
4461
  function cloneDef(schema) {
4462
4462
  return mergeDefs(schema._zod.def);
4463
4463
  }
4464
- function getElementAtPath(obj, path9) {
4465
- if (!path9)
4464
+ function getElementAtPath(obj, path10) {
4465
+ if (!path10)
4466
4466
  return obj;
4467
- return path9.reduce((acc, key) => acc?.[key], obj);
4467
+ return path10.reduce((acc, key) => acc?.[key], obj);
4468
4468
  }
4469
4469
  function promiseAllObject(promisesObj) {
4470
4470
  const keys = Object.keys(promisesObj);
@@ -4847,11 +4847,11 @@ function aborted(x, startIndex = 0) {
4847
4847
  }
4848
4848
  return false;
4849
4849
  }
4850
- function prefixIssues(path9, issues) {
4850
+ function prefixIssues(path10, issues) {
4851
4851
  return issues.map((iss) => {
4852
4852
  var _a2;
4853
4853
  (_a2 = iss).path ?? (_a2.path = []);
4854
- iss.path.unshift(path9);
4854
+ iss.path.unshift(path10);
4855
4855
  return iss;
4856
4856
  });
4857
4857
  }
@@ -5032,7 +5032,7 @@ function formatError(error48, mapper = (issue2) => issue2.message) {
5032
5032
  }
5033
5033
  function treeifyError(error48, mapper = (issue2) => issue2.message) {
5034
5034
  const result = { errors: [] };
5035
- const processError = (error49, path9 = []) => {
5035
+ const processError = (error49, path10 = []) => {
5036
5036
  var _a2, _b;
5037
5037
  for (const issue2 of error49.issues) {
5038
5038
  if (issue2.code === "invalid_union" && issue2.errors.length) {
@@ -5042,7 +5042,7 @@ function treeifyError(error48, mapper = (issue2) => issue2.message) {
5042
5042
  } else if (issue2.code === "invalid_element") {
5043
5043
  processError({ issues: issue2.issues }, issue2.path);
5044
5044
  } else {
5045
- const fullpath = [...path9, ...issue2.path];
5045
+ const fullpath = [...path10, ...issue2.path];
5046
5046
  if (fullpath.length === 0) {
5047
5047
  result.errors.push(mapper(issue2));
5048
5048
  continue;
@@ -5074,8 +5074,8 @@ function treeifyError(error48, mapper = (issue2) => issue2.message) {
5074
5074
  }
5075
5075
  function toDotPath(_path) {
5076
5076
  const segs = [];
5077
- const path9 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
5078
- for (const seg of path9) {
5077
+ const path10 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
5078
+ for (const seg of path10) {
5079
5079
  if (typeof seg === "number")
5080
5080
  segs.push(`[${seg}]`);
5081
5081
  else if (typeof seg === "symbol")
@@ -16910,13 +16910,13 @@ function resolveRef(ref, ctx) {
16910
16910
  if (!ref.startsWith("#")) {
16911
16911
  throw new Error("External $ref is not supported, only local refs (#/...) are allowed");
16912
16912
  }
16913
- const path9 = ref.slice(1).split("/").filter(Boolean);
16914
- if (path9.length === 0) {
16913
+ const path10 = ref.slice(1).split("/").filter(Boolean);
16914
+ if (path10.length === 0) {
16915
16915
  return ctx.rootSchema;
16916
16916
  }
16917
16917
  const defsKey = ctx.version === "draft-2020-12" ? "$defs" : "definitions";
16918
- if (path9[0] === defsKey) {
16919
- const key = path9[1];
16918
+ if (path10[0] === defsKey) {
16919
+ const key = path10[1];
16920
16920
  if (!key || !ctx.defs[key]) {
16921
16921
  throw new Error(`Reference not found: ${ref}`);
16922
16922
  }
@@ -21033,7 +21033,32 @@ var LODY_EXTENSION_METHODS = {
21033
21033
  var LODY_TOOL_NAMES = {
21034
21034
  imageGeneration: "ImageGeneration"
21035
21035
  };
21036
+ var LODY_PLAN_MODE_CONFIG_ID = "plan_mode";
21037
+ function createPlanModeConfigOption(currentValue) {
21038
+ return {
21039
+ id: LODY_PLAN_MODE_CONFIG_ID,
21040
+ name: "Plan",
21041
+ description: "Plan before implementing; permissions are controlled separately.",
21042
+ type: "boolean",
21043
+ currentValue
21044
+ };
21045
+ }
21046
+ var GOAL_EXTENSION_VERSION = 1;
21036
21047
  var GOAL_CONTROL_METHOD = LODY_EXTENSION_METHODS.sessionGoal;
21048
+ function parseGoalPromptControl(meta3) {
21049
+ if (typeof meta3 !== "object" || meta3 === null) return null;
21050
+ const lody = meta3["lody"];
21051
+ if (typeof lody !== "object" || lody === null) return null;
21052
+ const control = lody["goalControl"];
21053
+ if (typeof control !== "object" || control === null) return null;
21054
+ const { version: version2, action, objective } = control;
21055
+ if (version2 !== GOAL_EXTENSION_VERSION) return null;
21056
+ if (action === "pause" || action === "resume" || action === "clear") {
21057
+ return { version: GOAL_EXTENSION_VERSION, action };
21058
+ }
21059
+ if (action !== "set") return null;
21060
+ return typeof objective === "string" && objective.trim().length > 0 ? { version: GOAL_EXTENSION_VERSION, action: "set", objective } : null;
21061
+ }
21037
21062
  var ASYNC_TASK_STOP_METHOD = "_session/async_task/stop";
21038
21063
  var AUTH_STATUS_UPDATE_METHOD = "_auth/status_update";
21039
21064
  var AUTH_STATUS_META_KEY = "authStatus";
@@ -21185,9 +21210,17 @@ var CODEX_LODY_CAPABILITIES = {
21185
21210
  },
21186
21211
  tasks: { version: 1, background: true },
21187
21212
  subagents: { version: 1, lifecycle: true },
21188
- goal: { version: 1, actions: ["set", "pause", "resume", "clear"] },
21213
+ goal: {
21214
+ version: 1,
21215
+ actions: ["set", "pause", "resume", "clear"],
21216
+ // Status-only actions are safe mid-prompt; anything that starts work
21217
+ // needs the client's prompt to own the turns it produces.
21218
+ controlActions: ["pause", "clear"],
21219
+ promptActions: ["set", "pause", "resume", "clear"]
21220
+ },
21189
21221
  compaction: { version: 1 },
21190
- sessionHistory: { version: 1 }
21222
+ sessionHistory: { version: 1 },
21223
+ worktreeProject: { version: 1 }
21191
21224
  };
21192
21225
  function getLodyForkTurnId(meta3) {
21193
21226
  if (typeof meta3 !== "object" || meta3 === null) return null;
@@ -22342,13 +22375,13 @@ function createTerminalCommandEvent(event, terminalId, cwd) {
22342
22375
  }
22343
22376
  };
22344
22377
  }
22345
- function createSearchTitle(query, path9) {
22346
- if (query && path9) {
22347
- return `Search for '${query}' in ${path9}`;
22378
+ function createSearchTitle(query, path10) {
22379
+ if (query && path10) {
22380
+ return `Search for '${query}' in ${path10}`;
22348
22381
  } else if (query) {
22349
22382
  return `Search for '${query}'`;
22350
- } else if (path9) {
22351
- return `Search in '${path9}'`;
22383
+ } else if (path10) {
22384
+ return `Search in '${path10}'`;
22352
22385
  }
22353
22386
  return "Search";
22354
22387
  }
@@ -22576,12 +22609,12 @@ function revertPatch(unifiedDiff) {
22576
22609
  if (!patch) return null;
22577
22610
  return reversePatch(patch);
22578
22611
  }
22579
- function createUpdateDiffContent(path9, oldText, newText) {
22612
+ function createUpdateDiffContent(path10, oldText, newText) {
22580
22613
  return {
22581
22614
  type: "diff",
22582
22615
  oldText,
22583
22616
  newText,
22584
- path: path9,
22617
+ path: path10,
22585
22618
  _meta: {
22586
22619
  kind: "update"
22587
22620
  }
@@ -22798,16 +22831,16 @@ function clientSupportsAirCapability(capabilities, capability) {
22798
22831
  const supported = air?.[AIR_EXTENSION_CAPABILITIES_KEY];
22799
22832
  return typeof version2 === "number" && Number.isInteger(version2) && version2 >= AIR_EXTENSION_VERSION && Array.isArray(supported) && supported.includes(capability);
22800
22833
  }
22801
- function normalizeAgentPath(path9) {
22802
- const normalized = path9.trim().replace(/\/+$/, "");
22834
+ function normalizeAgentPath(path10) {
22835
+ const normalized = path10.trim().replace(/\/+$/, "");
22803
22836
  return normalized || "/root";
22804
22837
  }
22805
- function isRootAgentPath(path9) {
22806
- const normalized = normalizeAgentPath(path9);
22838
+ function isRootAgentPath(path10) {
22839
+ const normalized = normalizeAgentPath(path10);
22807
22840
  return normalized === "/root" || normalized === "root";
22808
22841
  }
22809
- function nameFromAgentPath(path9, fallback) {
22810
- const normalized = normalizeAgentPath(path9);
22842
+ function nameFromAgentPath(path10, fallback) {
22843
+ const normalized = normalizeAgentPath(path10);
22811
22844
  const name = normalized.slice(normalized.lastIndexOf("/") + 1).trim();
22812
22845
  if (!name) return fallback;
22813
22846
  const words = name.replace(/[_-]+/g, " ").replace(/\s+/g, " ").trim();
@@ -23040,11 +23073,11 @@ var CodexSubagentEventRouter = class _CodexSubagentEventRouter {
23040
23073
  const child = this.children.get(threadId);
23041
23074
  return child && child.terminalState === void 0 ? [{ threadId, sessionId: child.sessionId }] : [];
23042
23075
  }
23043
- async materialize(childSessionId, path9) {
23076
+ async materialize(childSessionId, path10) {
23044
23077
  if (this.children.has(childSessionId)) return;
23045
23078
  const pending = this.pendingSpawns.get(childSessionId);
23046
- const name = nameFromAgentPath(path9, fallbackName(childSessionId));
23047
- const inferredParent = this.parentForPath(path9);
23079
+ const name = nameFromAgentPath(path10, fallbackName(childSessionId));
23080
+ const inferredParent = this.parentForPath(path10);
23048
23081
  const parentThreadId = pending?.parentThreadId ?? inferredParent.threadId;
23049
23082
  const parentSessionId = pending?.parentSessionId ?? inferredParent.sessionId;
23050
23083
  const task = pending?.task ?? `Delegated task for ${name}`;
@@ -23061,7 +23094,7 @@ var CodexSubagentEventRouter = class _CodexSubagentEventRouter {
23061
23094
  sessionId: childSessionId,
23062
23095
  name,
23063
23096
  task,
23064
- path: normalizeAgentPath(path9),
23097
+ path: normalizeAgentPath(path10),
23065
23098
  generation: 1
23066
23099
  });
23067
23100
  this.pendingSpawns.delete(childSessionId);
@@ -23152,8 +23185,8 @@ var CodexSubagentEventRouter = class _CodexSubagentEventRouter {
23152
23185
  for (const waiter of this.waiters) waiter();
23153
23186
  this.waiters.clear();
23154
23187
  }
23155
- parentForPath(path9) {
23156
- const normalized = normalizeAgentPath(path9);
23188
+ parentForPath(path10) {
23189
+ const normalized = normalizeAgentPath(path10);
23157
23190
  const separator = normalized.lastIndexOf("/");
23158
23191
  if (separator <= 0) return { threadId: this.rootSessionId, sessionId: this.rootSessionId };
23159
23192
  const parentPath = normalized.slice(0, separator);
@@ -24866,7 +24899,7 @@ function permissionProfileContent(permissions) {
24866
24899
  return lines.length > 0 ? [textContent(lines.join("\n"))] : [];
24867
24900
  }
24868
24901
  function locationsField(paths) {
24869
- return paths.length > 0 ? { locations: paths.map((path9) => ({ path: path9 })) } : {};
24902
+ return paths.length > 0 ? { locations: paths.map((path10) => ({ path: path10 })) } : {};
24870
24903
  }
24871
24904
  function textContent(text) {
24872
24905
  return { type: "content", content: { type: "text", text } };
@@ -25709,6 +25742,78 @@ function getCodexAuthMethods(clientCapabilities, env = process.env) {
25709
25742
  function isCodexAuthRequest(request) {
25710
25743
  return request.methodId === "api-key" || request.methodId === "chat-gpt" || request.methodId === "chat-gpt-device-code" || request.methodId === "gateway";
25711
25744
  }
25745
+ function readWorktreeProject(meta3) {
25746
+ if (typeof meta3 !== "object" || meta3 === null) return void 0;
25747
+ const lody = meta3["lody"];
25748
+ if (typeof lody !== "object" || lody === null) return void 0;
25749
+ const value = lody["worktreeProject"];
25750
+ if (value === void 0) return void 0;
25751
+ if (typeof value === "object" && value !== null) {
25752
+ const project = value;
25753
+ if (project["version"] === 1 && typeof project["originProjectPath"] === "string" && path__default.isAbsolute(project["originProjectPath"]) && !project["originProjectPath"].includes("\0")) {
25754
+ return { version: 1, originProjectPath: project["originProjectPath"] };
25755
+ }
25756
+ }
25757
+ throw RequestError.invalidParams(void 0, "worktreeProject requires version 1 and an absolute originProjectPath");
25758
+ }
25759
+ var WorktreeProjects = class {
25760
+ constructor(client) {
25761
+ this.client = client;
25762
+ }
25763
+ client;
25764
+ async resolve(project) {
25765
+ if (!project) return void 0;
25766
+ const originProjectPath = await realpath(project.originProjectPath);
25767
+ const key = projectRootKey(originProjectPath);
25768
+ return this.findOrCreate(originProjectPath, key);
25769
+ }
25770
+ async assign(thread, project, preserveExisting) {
25771
+ if (!project || preserveExisting && thread.projectId) return;
25772
+ const projectId = await this.resolve(project);
25773
+ if (projectId && projectId !== thread.projectId) {
25774
+ await this.client.threadProjectUpdate({ threadId: thread.id, projectId });
25775
+ }
25776
+ }
25777
+ async findOrCreate(originProjectPath, key) {
25778
+ let cursor;
25779
+ const matches = /* @__PURE__ */ new Set();
25780
+ do {
25781
+ const page = await this.client.projectList({ limit: 100, ...cursor ? { cursor } : {} });
25782
+ for (const project of page.data) {
25783
+ for (const root of project.roots) {
25784
+ if (projectRootKey(root.path) === key || await this.matchesRoot(root.path, key)) {
25785
+ matches.add(project.id);
25786
+ break;
25787
+ }
25788
+ }
25789
+ }
25790
+ cursor = page.nextCursor ?? void 0;
25791
+ } while (cursor);
25792
+ if (matches.size > 1) {
25793
+ throw RequestError.invalidParams(void 0, "Multiple Codex projects use this root; consolidate the project assignments first");
25794
+ }
25795
+ const match = matches.values().next().value;
25796
+ if (match) return match;
25797
+ const idempotencyKey = `acp-project-v1:${createHash("sha256").update(key).digest("hex")}`;
25798
+ const response = await this.client.projectCreate({
25799
+ idempotencyKey,
25800
+ name: path__default.basename(originProjectPath) || originProjectPath,
25801
+ roots: [{ path: originProjectPath }]
25802
+ });
25803
+ return response.project.id;
25804
+ }
25805
+ async matchesRoot(storedPath, key) {
25806
+ try {
25807
+ return projectRootKey(await realpath(storedPath)) === key;
25808
+ } catch {
25809
+ return false;
25810
+ }
25811
+ }
25812
+ };
25813
+ function projectRootKey(originProjectPath) {
25814
+ const normalized = path__default.resolve(originProjectPath);
25815
+ return process.platform === "win32" ? normalized.toLowerCase() : normalized;
25816
+ }
25712
25817
  var isDockerCached;
25713
25818
  function hasDockerEnv() {
25714
25819
  try {
@@ -25864,16 +25969,16 @@ var wslDefaultBrowser = async () => {
25864
25969
  });
25865
25970
  return stdout.trim();
25866
25971
  };
25867
- var isUrl = (path9) => /^[a-z]+:\/\//i.test(path9);
25972
+ var isUrl = (path10) => /^[a-z]+:\/\//i.test(path10);
25868
25973
  var convertWslPathToWindows = async (paths) => {
25869
25974
  const isBatch = Array.isArray(paths);
25870
25975
  const pathArray = isBatch ? paths : [paths];
25871
25976
  const indicesToConvert = [];
25872
25977
  const pathsToConvert = [];
25873
- for (const [index, path9] of pathArray.entries()) {
25874
- if (!isUrl(path9)) {
25978
+ for (const [index, path10] of pathArray.entries()) {
25979
+ if (!isUrl(path10)) {
25875
25980
  indicesToConvert.push(index);
25876
- pathsToConvert.push(path9);
25981
+ pathsToConvert.push(path10);
25877
25982
  }
25878
25983
  }
25879
25984
  const results = [...pathArray];
@@ -26476,7 +26581,7 @@ var package_default = {
26476
26581
  publishConfig: {
26477
26582
  access: "public"
26478
26583
  },
26479
- version: "1.10.0",
26584
+ version: "1.10.1",
26480
26585
  description: "An ACP-compatible coding agent powered by Codex",
26481
26586
  main: "dist/index.js",
26482
26587
  bin: {
@@ -26546,29 +26651,17 @@ var package_default = {
26546
26651
  dependencies: {
26547
26652
  "@agentclientprotocol/sdk": "^1.4.0",
26548
26653
  "@openai/codex": "^0.153.4",
26549
- "acp-extension-core": "0.1.0",
26654
+ "acp-extension-core": "0.1.4",
26550
26655
  diff: "^9.0.0",
26551
26656
  open: "^11.0.1",
26552
26657
  "vscode-jsonrpc": "^9.0.1",
26553
26658
  zod: "^4.0.0"
26554
26659
  }
26555
26660
  };
26556
- var COLLABORATION_MODE_CONFIG_ID = "collaboration_mode";
26557
26661
  var DEFAULT_COLLABORATION_MODE = "default";
26558
26662
  var PLAN_COLLABORATION_MODE = "plan";
26559
26663
  function createCollaborationModeConfigOption(currentValue) {
26560
- return {
26561
- id: COLLABORATION_MODE_CONFIG_ID,
26562
- name: "Collaboration mode",
26563
- description: "How Codex collaborates for subsequent turns",
26564
- category: "collaboration_mode",
26565
- type: "select",
26566
- currentValue,
26567
- options: [
26568
- { value: DEFAULT_COLLABORATION_MODE, name: "Default" },
26569
- { value: PLAN_COLLABORATION_MODE, name: "Plan", description: "Plan before making changes" }
26570
- ]
26571
- };
26664
+ return createPlanModeConfigOption(currentValue === PLAN_COLLABORATION_MODE);
26572
26665
  }
26573
26666
  function parseCollaborationMode(value) {
26574
26667
  if (value === DEFAULT_COLLABORATION_MODE) return DEFAULT_COLLABORATION_MODE;
@@ -27034,6 +27127,7 @@ var CodexSubagentSubscriptions = class {
27034
27127
  }
27035
27128
  };
27036
27129
  async function forkSession(request, additionalDirectories, dependencies) {
27130
+ const project = readWorktreeProject(request._meta);
27037
27131
  await dependencies.refreshSkills(request.cwd, additionalDirectories);
27038
27132
  const lastTurnId = await resolveForkTurnId(request, dependencies.codexClient);
27039
27133
  const response = await dependencies.codexClient.threadFork({
@@ -27047,7 +27141,12 @@ async function forkSession(request, additionalDirectories, dependencies) {
27047
27141
  modelProvider: await dependencies.getResumeModelProvider(),
27048
27142
  threadId: request.sessionId
27049
27143
  });
27050
- await dependencies.codexClient.threadUnsubscribe({ threadId: response.thread.id });
27144
+ try {
27145
+ await dependencies.assignProject(response.thread, project);
27146
+ } catch (error48) {
27147
+ await dependencies.codexClient.threadUnsubscribe({ threadId: response.thread.id });
27148
+ throw error48;
27149
+ }
27051
27150
  const models = await dependencies.fetchAvailableModels();
27052
27151
  return {
27053
27152
  sessionId: response.thread.id,
@@ -27139,10 +27238,12 @@ var CodexAcpClient = class {
27139
27238
  pendingAccountUpdated = null;
27140
27239
  sessionNotificationQueues = /* @__PURE__ */ new Map();
27141
27240
  subagents;
27241
+ worktreeProjects;
27142
27242
  skillExtraRoots = [];
27143
27243
  configPath = null;
27144
27244
  constructor(codexClient, codexConfig, modelProvider) {
27145
27245
  this.codexClient = codexClient;
27246
+ this.worktreeProjects = new WorktreeProjects(codexClient);
27146
27247
  this.config = codexConfig ?? {};
27147
27248
  this.modelProvider = modelProvider ?? null;
27148
27249
  this.gatewayConfig = null;
@@ -27493,6 +27594,7 @@ var CodexAcpClient = class {
27493
27594
  return settingsModelProvider?.config?.model_provider ?? null;
27494
27595
  }
27495
27596
  async resumeSession(request, onSubscribed) {
27597
+ const project = readWorktreeProject(request._meta);
27496
27598
  const additionalDirectories = readAdditionalDirectories(request.cwd, request.additionalDirectories, request._meta);
27497
27599
  await this.refreshSkills(request.cwd, additionalDirectories);
27498
27600
  const response = await this.codexClient.threadResume({
@@ -27502,6 +27604,7 @@ var CodexAcpClient = class {
27502
27604
  threadId: request.sessionId
27503
27605
  });
27504
27606
  onSubscribed?.(request.sessionId);
27607
+ await this.worktreeProjects.assign(response.thread, project, true);
27505
27608
  const codexModels = await this.fetchAvailableModels();
27506
27609
  const currentModelId = this.createModelId(codexModels, response.model, response.reasoningEffort).toString();
27507
27610
  return {
@@ -27518,6 +27621,7 @@ var CodexAcpClient = class {
27518
27621
  const additionalDirectories = readAdditionalDirectories(request.cwd, request.additionalDirectories, request._meta);
27519
27622
  return await forkSession(request, additionalDirectories, {
27520
27623
  codexClient: this.codexClient,
27624
+ assignProject: (thread, project) => this.worktreeProjects.assign(thread, project, false),
27521
27625
  refreshSkills: (cwd, directories) => this.refreshSkills(cwd, directories),
27522
27626
  createSessionConfig: (cwd, directories, mcpServers) => this.createSessionConfig(cwd, directories, mcpServers),
27523
27627
  getResumeModelProvider: () => this.getResumeModelProvider(),
@@ -27527,6 +27631,7 @@ var CodexAcpClient = class {
27527
27631
  });
27528
27632
  }
27529
27633
  async loadSession(request, onSubscribed) {
27634
+ const project = readWorktreeProject(request._meta);
27530
27635
  const additionalDirectories = readAdditionalDirectories(request.cwd, request.additionalDirectories, request._meta);
27531
27636
  await this.refreshSkills(request.cwd, additionalDirectories);
27532
27637
  const response = await this.codexClient.threadResume({
@@ -27536,6 +27641,7 @@ var CodexAcpClient = class {
27536
27641
  threadId: request.sessionId
27537
27642
  });
27538
27643
  onSubscribed?.();
27644
+ await this.worktreeProjects.assign(response.thread, project, true);
27539
27645
  const historyResponse = await this.codexClient.threadRead({
27540
27646
  threadId: response.thread.id,
27541
27647
  includeTurns: true
@@ -27557,9 +27663,12 @@ var CodexAcpClient = class {
27557
27663
  return await this.readSessionHistory(sessionId);
27558
27664
  }
27559
27665
  async newSession(request, onSubscribed) {
27666
+ const project = readWorktreeProject(request._meta);
27560
27667
  const additionalDirectories = readAdditionalDirectories(request.cwd, request.additionalDirectories, request._meta);
27561
27668
  await this.refreshSkills(request.cwd, additionalDirectories);
27669
+ const projectId = await this.worktreeProjects.resolve(project);
27562
27670
  const response = await this.codexClient.threadStart({
27671
+ ...projectId ? { projectId } : {},
27563
27672
  config: await this.createSessionConfig(request.cwd, additionalDirectories, request.mcpServers),
27564
27673
  modelProvider: this.getModelProvider(),
27565
27674
  cwd: request.cwd
@@ -27789,6 +27898,14 @@ var CodexAcpClient = class {
27789
27898
  await queue;
27790
27899
  }
27791
27900
  }
27901
+ onConnectionClosed(callback) {
27902
+ const close = this.codexClient.connection.onClose(callback);
27903
+ const dispose = this.codexClient.connection.onDispose(callback);
27904
+ return () => {
27905
+ close.dispose();
27906
+ dispose.dispose();
27907
+ };
27908
+ }
27792
27909
  enqueueSessionNotification(sessionId, operation) {
27793
27910
  const run = async () => {
27794
27911
  try {
@@ -28192,8 +28309,8 @@ function formatUriAsLink(name, uri) {
28192
28309
  return `[@${name}](${uri})`;
28193
28310
  }
28194
28311
  if (uri.startsWith("file://")) {
28195
- const path9 = uri.replace("file://", "");
28196
- const fileName = path9.split("/").pop() ?? path9;
28312
+ const path10 = uri.replace("file://", "");
28313
+ const fileName = path10.split("/").pop() ?? path10;
28197
28314
  return `[@${fileName}](${uri})`;
28198
28315
  }
28199
28316
  return uri;
@@ -28655,6 +28772,15 @@ var CodexAppServerClient = class {
28655
28772
  threadStaleTurns.add(turnId);
28656
28773
  this.staleTurnIds.set(threadId, threadStaleTurns);
28657
28774
  }
28775
+ async projectList(params) {
28776
+ return await this.sendRequest({ method: "project/list", params });
28777
+ }
28778
+ async projectCreate(params) {
28779
+ return await this.sendRequest({ method: "project/create", params });
28780
+ }
28781
+ async threadProjectUpdate(params) {
28782
+ await this.sendRequest({ method: "thread/metadata/update", params });
28783
+ }
28658
28784
  async threadStart(params) {
28659
28785
  return await this.sendRequest({ method: "thread/start", params });
28660
28786
  }
@@ -29193,12 +29319,13 @@ function createReasoningEffortConfigOption(supportedReasoningEfforts, currentEff
29193
29319
  }))
29194
29320
  };
29195
29321
  }
29322
+ var GOAL_OBJECTIVE_MAX_LENGTH = 4e3;
29196
29323
  var GOAL_CONTINUATION_PROMPT = [{
29197
29324
  type: "text",
29198
29325
  text: "Continue working toward the active goal."
29199
29326
  }];
29200
29327
  function resolveGoalCommandHandleResult(turnCompleted) {
29201
- if (turnCompleted?.turn.status === "interrupted") {
29328
+ if (turnCompleted !== null) {
29202
29329
  return { handled: true, turnCompleted };
29203
29330
  }
29204
29331
  return { handled: false, prompt: GOAL_CONTINUATION_PROMPT };
@@ -29281,9 +29408,9 @@ var CodexCommands = class {
29281
29408
  _meta: {
29282
29409
  commandAction: {
29283
29410
  kind: "setConfigOption",
29284
- configId: COLLABORATION_MODE_CONFIG_ID,
29285
- value: PLAN_COLLABORATION_MODE,
29286
- resetValue: DEFAULT_COLLABORATION_MODE,
29411
+ configId: LODY_PLAN_MODE_CONFIG_ID,
29412
+ value: true,
29413
+ resetValue: false,
29287
29414
  presentation: "state"
29288
29415
  }
29289
29416
  }
@@ -29372,8 +29499,8 @@ var CodexCommands = class {
29372
29499
  await this.sendCommandUsageMessage(commandName, "no arguments", sessionId);
29373
29500
  return { handled: true };
29374
29501
  }
29375
- const mode = sessionState.collaborationMode === PLAN_COLLABORATION_MODE ? DEFAULT_COLLABORATION_MODE : PLAN_COLLABORATION_MODE;
29376
- await options.setConfigOption?.(COLLABORATION_MODE_CONFIG_ID, mode);
29502
+ const mode = sessionState.collaborationMode !== PLAN_COLLABORATION_MODE;
29503
+ await options.setConfigOption?.(LODY_PLAN_MODE_CONFIG_ID, mode);
29377
29504
  return { handled: options.setConfigOption !== void 0 };
29378
29505
  }
29379
29506
  case "compact": {
@@ -29476,35 +29603,56 @@ var CodexCommands = class {
29476
29603
  await this.sendCommandUsageMessage("goal", "[<objective>|clear|pause|resume]", sessionId);
29477
29604
  return { handled: true };
29478
29605
  }
29479
- switch (argument.toLowerCase()) {
29480
- case "pause":
29481
- await this.runWithProcessCheck(() => this.codexAcpClient.setGoalStatus(sessionId, "paused"));
29482
- return { handled: true };
29483
- case "resume":
29484
- options.onTurnStartPending?.();
29485
- return this.createGoalCommandResult(await this.runWithProcessCheck(() => this.codexAcpClient.resumeGoal(
29486
- sessionId,
29487
- (turnId) => {
29488
- this.handleCommandTurnStarted(sessionState, options, turnId, sessionId);
29489
- }
29490
- )));
29491
- case "clear":
29492
- await this.runWithProcessCheck(() => this.codexAcpClient.clearGoal(sessionId));
29493
- return { handled: true };
29606
+ const named = argument.toLowerCase();
29607
+ if (named === "pause" || named === "resume" || named === "clear") {
29608
+ return await this.runGoalPromptControl(
29609
+ sessionState,
29610
+ { version: GOAL_EXTENSION_VERSION, action: named },
29611
+ options
29612
+ );
29494
29613
  }
29495
- if (argument.length > 4e3) {
29614
+ if (argument.length > GOAL_OBJECTIVE_MAX_LENGTH) {
29496
29615
  const session = new ACPSessionConnection(this.connection, sessionId);
29497
- await session.update(createAgentTextMessageChunk('Command "/goal" requires goal text of at most 4000 characters.'));
29616
+ await session.update(createAgentTextMessageChunk(
29617
+ `Command "/goal" requires goal text of at most ${GOAL_OBJECTIVE_MAX_LENGTH} characters.`
29618
+ ));
29498
29619
  return { handled: true };
29499
29620
  }
29621
+ return await this.runGoalPromptControl(
29622
+ sessionState,
29623
+ { version: GOAL_EXTENSION_VERSION, action: "set", objective: argument },
29624
+ options
29625
+ );
29626
+ }
29627
+ /**
29628
+ * Run a goal action inside whatever prompt is already executing. `/goal …`
29629
+ * and a prompt carrying `_meta.lody.goalControl` are the same operation
29630
+ * reaching the same place: the caller's prompt owns any turn the action
29631
+ * starts, so neither has to start one behind the client's back.
29632
+ */
29633
+ async runGoalPromptControl(sessionState, control, options = {}) {
29634
+ const sessionId = sessionState.sessionId;
29635
+ if (control.action === "pause") {
29636
+ await this.runWithProcessCheck(() => this.codexAcpClient.setGoalStatus(sessionId, "paused"));
29637
+ return { handled: true };
29638
+ }
29639
+ if (control.action === "clear") {
29640
+ await this.runWithProcessCheck(() => this.codexAcpClient.clearGoal(sessionId));
29641
+ return { handled: true };
29642
+ }
29643
+ if (control.action === "set" && control.objective.trim().length > GOAL_OBJECTIVE_MAX_LENGTH) {
29644
+ throw RequestError.invalidParams(
29645
+ void 0,
29646
+ `A goal objective may be at most ${GOAL_OBJECTIVE_MAX_LENGTH} characters`
29647
+ );
29648
+ }
29500
29649
  options.onTurnStartPending?.();
29501
- return this.createGoalCommandResult(await this.runWithProcessCheck(() => this.codexAcpClient.setGoal(
29502
- sessionId,
29503
- argument,
29504
- (turnId) => {
29505
- this.handleCommandTurnStarted(sessionState, options, turnId, sessionId);
29506
- }
29507
- )));
29650
+ const onTurnStarted = (turnId) => {
29651
+ this.handleCommandTurnStarted(sessionState, options, turnId, sessionId);
29652
+ };
29653
+ return this.createGoalCommandResult(await this.runWithProcessCheck(
29654
+ () => control.action === "set" ? this.codexAcpClient.setGoal(sessionId, control.objective.trim(), onTurnStarted) : this.codexAcpClient.resumeGoal(sessionId, onTurnStarted)
29655
+ ));
29508
29656
  }
29509
29657
  handleCommandTurnStarted(sessionState, options, turnId, threadId) {
29510
29658
  if (options.onTurnStarted) {
@@ -29697,6 +29845,77 @@ var CodexCommands = class {
29697
29845
  return count.toString();
29698
29846
  }
29699
29847
  };
29848
+ var GoalPromptLifecycle = class {
29849
+ constructor(sessionId, goalActive) {
29850
+ this.sessionId = sessionId;
29851
+ this.goalActive = goalActive;
29852
+ }
29853
+ sessionId;
29854
+ goalActive;
29855
+ currentTurnId = null;
29856
+ completed = null;
29857
+ failure = null;
29858
+ waiter = null;
29859
+ cancelled = false;
29860
+ startTurn(turnId) {
29861
+ if (this.currentTurnId === turnId) return;
29862
+ this.currentTurnId = turnId;
29863
+ this.completed = null;
29864
+ }
29865
+ prepareTurn() {
29866
+ this.currentTurnId = null;
29867
+ this.completed = null;
29868
+ }
29869
+ /** A delayed turn/start response must not overwrite already-observed native work. */
29870
+ startSubmittedTurn(turnId) {
29871
+ if (this.currentTurnId !== null || this.completed !== null) return false;
29872
+ this.startTurn(turnId);
29873
+ return true;
29874
+ }
29875
+ observe(event) {
29876
+ if (event.method === "thread/goal/updated" && event.params.threadId === this.sessionId) {
29877
+ this.goalActive = event.params.goal.status === "active";
29878
+ } else if (event.method === "thread/goal/cleared" && event.params.threadId === this.sessionId) {
29879
+ this.goalActive = false;
29880
+ } else if (event.method === "turn/started" && event.params.threadId === this.sessionId) {
29881
+ this.startTurn(event.params.turn.id);
29882
+ } else if (event.method === "turn/completed" && event.params.threadId === this.sessionId) {
29883
+ this.recordCompletion(event.params);
29884
+ } else {
29885
+ return;
29886
+ }
29887
+ this.waiter?.();
29888
+ }
29889
+ recordCompletion(event) {
29890
+ this.completed = event;
29891
+ if (this.currentTurnId === event.turn.id) this.currentTurnId = null;
29892
+ }
29893
+ cancel() {
29894
+ this.cancelled = true;
29895
+ this.waiter?.();
29896
+ }
29897
+ fail(error48) {
29898
+ this.failure = error48;
29899
+ this.waiter?.();
29900
+ }
29901
+ async waitForCompletion(first) {
29902
+ if (first.threadId !== this.sessionId) return first;
29903
+ if (this.completed === null) this.recordCompletion(first);
29904
+ const result = await new Promise((resolve, reject) => {
29905
+ this.waiter = () => {
29906
+ if (this.failure) reject(this.failure);
29907
+ else if (this.cancelled && this.currentTurnId === null) resolve(null);
29908
+ else if (this.completed && this.currentTurnId === null && (!this.goalActive || this.completed.turn.status !== "completed")) {
29909
+ resolve(this.completed);
29910
+ }
29911
+ };
29912
+ this.waiter();
29913
+ }).finally(() => {
29914
+ this.waiter = null;
29915
+ });
29916
+ return result;
29917
+ }
29918
+ };
29700
29919
  var SteeringQueue = class {
29701
29920
  constructor(handle) {
29702
29921
  this.handle = handle;
@@ -29978,8 +30197,8 @@ function imageBlocks(images) {
29978
30197
  return [{ type: "text", text: `[@image](${image})` }];
29979
30198
  }
29980
30199
  const record2 = asRecord2(image);
29981
- const path9 = record2 ? stringValue(record2["path"]) ?? stringValue(record2["url"]) : null;
29982
- return path9 ? [{ type: "text", text: `[@image](${path9})` }] : [];
30200
+ const path10 = record2 ? stringValue(record2["path"]) ?? stringValue(record2["url"]) : null;
30201
+ return path10 ? [{ type: "text", text: `[@image](${path10})` }] : [];
29983
30202
  });
29984
30203
  }
29985
30204
  function contentBlocksFromResponseContent(content) {
@@ -32247,8 +32466,9 @@ Check ${configPath} and project .codex directories, especially their config.toml
32247
32466
  case MODE_CONFIG_ID:
32248
32467
  this.applyModeChange(sessionState, this.stringConfigValue(params));
32249
32468
  break;
32250
- case COLLABORATION_MODE_CONFIG_ID:
32251
- await this.applyCollaborationModeChange(sessionState, this.stringConfigValue(params));
32469
+ case LODY_PLAN_MODE_CONFIG_ID:
32470
+ if (typeof params.value !== "boolean") throw RequestError.invalidParams();
32471
+ await this.applyCollaborationModeChange(sessionState, params.value ? PLAN_COLLABORATION_MODE : DEFAULT_COLLABORATION_MODE);
32252
32472
  break;
32253
32473
  case MODEL_CONFIG_ID:
32254
32474
  this.applyModelChange(sessionState, this.stringConfigValue(params));
@@ -33147,8 +33367,8 @@ Check ${configPath} and project .codex directories, especially their config.toml
33147
33367
  return `[@${name}](${uri})`;
33148
33368
  }
33149
33369
  if (uri.startsWith("file://")) {
33150
- const path9 = uri.replace("file://", "");
33151
- const fileName = path9.split("/").pop() ?? path9;
33370
+ const path10 = uri.replace("file://", "");
33371
+ const fileName = path10.split("/").pop() ?? path10;
33152
33372
  return `[@${fileName}](${uri})`;
33153
33373
  }
33154
33374
  return uri;
@@ -33294,6 +33514,7 @@ Check ${configPath} and project .codex directories, especially their config.toml
33294
33514
  cancelSignal,
33295
33515
  signal: abortController.signal,
33296
33516
  currentTurn: null,
33517
+ hasCompletedTurn: false,
33297
33518
  requestCancel: () => {
33298
33519
  if (abortController.signal.aborted) {
33299
33520
  return;
@@ -33488,12 +33709,19 @@ Check ${configPath} and project .codex directories, especially their config.toml
33488
33709
  });
33489
33710
  const sessionState = this.getSessionState(params.sessionId);
33490
33711
  const agentFileChangeReportRequest = clientSupportsAgentFileChangeReports(this.clientCapabilities) ? parseAgentFileChangeReportRequest(params._meta) : null;
33712
+ const goalPromptControl = parseGoalPromptControl(params._meta);
33491
33713
  let agentFileChangeReportTurnId = null;
33492
33714
  let agentFileChangeReportUnavailableReason = "providerError";
33493
33715
  let promptWasCancelled = false;
33494
33716
  let recoverableSessionFailure = sessionState.sessionFailure;
33495
33717
  sessionState.currentTurnId = null;
33496
33718
  const activePrompt = this.trackActivePrompt(params.sessionId);
33719
+ const goalLifecycle = new GoalPromptLifecycle(params.sessionId, sessionState.currentGoal?.status === "active");
33720
+ const cancelGoalLifecycle = () => goalLifecycle.cancel();
33721
+ activePrompt.signal.addEventListener("abort", cancelGoalLifecycle);
33722
+ const disposeGoalConnection = this.codexAcpClient.onConnectionClosed(() => {
33723
+ goalLifecycle.fail(new Error("Codex connection closed during goal continuation"));
33724
+ });
33497
33725
  let pendingTurnStart = null;
33498
33726
  const ensurePendingTurnStart = () => {
33499
33727
  if (pendingTurnStart === null) {
@@ -33549,6 +33777,23 @@ Check ${configPath} and project .codex directories, especially their config.toml
33549
33777
  await this.codexAcpClient.subscribeToSessionEvents(
33550
33778
  params.sessionId,
33551
33779
  async (event) => {
33780
+ if (this.activePrompts.get(params.sessionId) === activePrompt) {
33781
+ goalLifecycle.observe(event);
33782
+ if (event.method === "turn/started" && event.params.threadId === params.sessionId) {
33783
+ const turn = { threadId: params.sessionId, turnId: event.params.turn.id };
33784
+ activePrompt.currentTurn = turn;
33785
+ if (this.promptShouldStop(params.sessionId, activePrompt)) {
33786
+ this.interruptLateStartedTurn(turn);
33787
+ return;
33788
+ }
33789
+ recoverableSessionFailure = sessionState.sessionFailure;
33790
+ promptNotificationsActive = true;
33791
+ }
33792
+ if (event.method === "turn/completed" && event.params.threadId === params.sessionId && activePrompt.currentTurn?.turnId === event.params.turn.id) {
33793
+ activePrompt.currentTurn = null;
33794
+ activePrompt.hasCompletedTurn = true;
33795
+ }
33796
+ }
33552
33797
  await this.handleSteerAppliedNotification(params.sessionId, event, activePrompt);
33553
33798
  await observeInteraction(event);
33554
33799
  if (!promptNotificationsActive) {
@@ -33570,13 +33815,14 @@ Check ${configPath} and project .codex directories, especially their config.toml
33570
33815
  if (activePrompt.signal.aborted) {
33571
33816
  return cancelledPromptResponse();
33572
33817
  }
33573
- const commandPromise = this.availableCommands.tryHandleCommand(params.prompt, sessionState, {
33818
+ const commandOptions = {
33574
33819
  onTurnStartPending: () => {
33575
33820
  sessionState.lastTokenUsage = null;
33576
33821
  ensurePendingTurnStart();
33577
33822
  },
33578
33823
  onTurnStarted: (turnId, threadId) => {
33579
33824
  const turn = { threadId, turnId };
33825
+ if (threadId === params.sessionId) goalLifecycle.startTurn(turnId);
33580
33826
  activePrompt.currentTurn = turn;
33581
33827
  if (this.promptShouldStop(params.sessionId, activePrompt)) {
33582
33828
  this.interruptLateStartedTurn(turn);
@@ -33590,7 +33836,7 @@ Check ${configPath} and project .codex directories, especially their config.toml
33590
33836
  await this.applySessionConfigOption(sessionState, {
33591
33837
  sessionId: sessionState.sessionId,
33592
33838
  configId,
33593
- value
33839
+ ...typeof value === "boolean" ? { type: "boolean", value } : { value }
33594
33840
  });
33595
33841
  const session = new ACPSessionConnection(this.connection, sessionState.sessionId);
33596
33842
  await session.update({
@@ -33598,13 +33844,14 @@ Check ${configPath} and project .codex directories, especially their config.toml
33598
33844
  configOptions: this.createSessionConfigOptions(sessionState)
33599
33845
  });
33600
33846
  }
33601
- });
33847
+ };
33848
+ const commandPromise = goalPromptControl === null ? this.availableCommands.tryHandleCommand(params.prompt, sessionState, commandOptions) : this.availableCommands.runGoalPromptControl(sessionState, goalPromptControl, commandOptions);
33602
33849
  void commandPromise.catch((err) => {
33603
33850
  if (this.activePrompts.get(params.sessionId) !== activePrompt) {
33604
33851
  logger.error(`Command for cancelled prompt ${params.sessionId} failed after prompt returned`, err);
33605
33852
  }
33606
33853
  });
33607
- const commandResult = await Promise.race([
33854
+ let commandResult = await Promise.race([
33608
33855
  commandPromise,
33609
33856
  activePrompt.closeSignal,
33610
33857
  this.cancelBeforeTurnStarted(activePrompt)
@@ -33613,6 +33860,16 @@ Check ${configPath} and project .codex directories, especially their config.toml
33613
33860
  return cancelledPromptResponse();
33614
33861
  }
33615
33862
  if (commandResult.handled) {
33863
+ if (commandResult.turnCompleted) {
33864
+ await this.codexAcpClient.waitForSessionNotifications(params.sessionId);
33865
+ const firstCommandTurn = commandResult.turnCompleted;
33866
+ const completed = await this.runWithProcessCheck(() => Promise.race([
33867
+ goalLifecycle.waitForCompletion(firstCommandTurn),
33868
+ activePrompt.closeSignal
33869
+ ]));
33870
+ if (completed === null) return cancelledPromptResponse();
33871
+ commandResult = { ...commandResult, turnCompleted: completed };
33872
+ }
33616
33873
  promptNotificationsActive = false;
33617
33874
  logger.log("Prompt handled by a command");
33618
33875
  await this.codexAcpClient.waitForSessionNotifications(params.sessionId);
@@ -33671,6 +33928,7 @@ Check ${configPath} and project .codex directories, especially their config.toml
33671
33928
  );
33672
33929
  sessionState.lastTokenUsage = null;
33673
33930
  ensurePendingTurnStart();
33931
+ goalLifecycle.prepareTurn();
33674
33932
  const sendPromptPromise = this.runWithProcessCheck(
33675
33933
  () => this.codexAcpClient.sendPrompt(
33676
33934
  effectiveParams,
@@ -33682,6 +33940,11 @@ Check ${configPath} and project .codex directories, especially their config.toml
33682
33940
  sessionState.additionalDirectories,
33683
33941
  (turnId) => {
33684
33942
  const turn = { threadId: params.sessionId, turnId };
33943
+ if (!goalLifecycle.startSubmittedTurn(turnId)) {
33944
+ pendingTurnStart?.resolve(turnId);
33945
+ onTurnStarted?.();
33946
+ return;
33947
+ }
33685
33948
  activePrompt.currentTurn = turn;
33686
33949
  if (this.promptShouldStop(params.sessionId, activePrompt)) {
33687
33950
  this.interruptLateStartedTurn(turn);
@@ -33708,6 +33971,13 @@ Check ${configPath} and project .codex directories, especially their config.toml
33708
33971
  return cancelledPromptResponse();
33709
33972
  }
33710
33973
  await this.codexAcpClient.waitForSessionNotifications(params.sessionId);
33974
+ const firstPromptTurn = turnCompleted;
33975
+ turnCompleted = await this.runWithProcessCheck(() => Promise.race([
33976
+ goalLifecycle.waitForCompletion(firstPromptTurn),
33977
+ activePrompt.closeSignal
33978
+ ]));
33979
+ if (turnCompleted === null) return cancelledPromptResponse();
33980
+ await this.codexAcpClient.waitForSessionNotifications(params.sessionId);
33711
33981
  if (turnCompleted.turn.status === "completed") {
33712
33982
  await eventHandler.waitForNativeSubagents(activePrompt.signal);
33713
33983
  if (activePrompt.signal.aborted) return cancelledPromptResponse();
@@ -33760,6 +34030,7 @@ Check ${configPath} and project .codex directories, especially their config.toml
33760
34030
  };
33761
34031
  activePrompt.currentTurn = null;
33762
34032
  sessionState.currentTurnId = null;
34033
+ goalLifecycle.prepareTurn();
33763
34034
  const implementationPromise = this.runWithProcessCheck(
33764
34035
  () => this.codexAcpClient.sendPrompt(
33765
34036
  implementationRequest,
@@ -33771,6 +34042,7 @@ Check ${configPath} and project .codex directories, especially their config.toml
33771
34042
  sessionState.additionalDirectories,
33772
34043
  (turnId) => {
33773
34044
  const turn = { threadId: params.sessionId, turnId };
34045
+ if (!goalLifecycle.startSubmittedTurn(turnId)) return;
33774
34046
  activePrompt.currentTurn = turn;
33775
34047
  if (this.promptShouldStop(params.sessionId, activePrompt)) {
33776
34048
  this.interruptLateStartedTurn(turn);
@@ -33797,6 +34069,13 @@ Check ${configPath} and project .codex directories, especially their config.toml
33797
34069
  return cancelledPromptResponse();
33798
34070
  }
33799
34071
  await this.codexAcpClient.waitForSessionNotifications(params.sessionId);
34072
+ const firstImplementationTurn = turnCompleted;
34073
+ turnCompleted = await this.runWithProcessCheck(() => Promise.race([
34074
+ goalLifecycle.waitForCompletion(firstImplementationTurn),
34075
+ activePrompt.closeSignal
34076
+ ]));
34077
+ if (turnCompleted === null) return cancelledPromptResponse();
34078
+ await this.codexAcpClient.waitForSessionNotifications(params.sessionId);
33800
34079
  if (turnCompleted.turn.status === "completed") {
33801
34080
  await eventHandler.waitForNativeSubagents(activePrompt.signal);
33802
34081
  if (activePrompt.signal.aborted) return cancelledPromptResponse();
@@ -33870,6 +34149,14 @@ Check ${configPath} and project .codex directories, especially their config.toml
33870
34149
  }
33871
34150
  throw err;
33872
34151
  } finally {
34152
+ if ((promptWasCancelled || activePrompt.signal.aborted) && sessionState.currentGoal?.status === "active") {
34153
+ try {
34154
+ const goal = await this.runWithProcessCheck(() => this.codexAcpClient.setGoalStatus(params.sessionId, "paused"));
34155
+ await this.publishGoalSnapshot(sessionState, toThreadGoalSnapshot(goal), false);
34156
+ } catch (error48) {
34157
+ logger.error("Failed to pause goal after prompt cancellation", error48);
34158
+ }
34159
+ }
33873
34160
  promptNotificationsActive = false;
33874
34161
  try {
33875
34162
  await this.codexAcpClient.waitForSessionNotifications(params.sessionId);
@@ -33890,6 +34177,9 @@ Check ${configPath} and project .codex directories, especially their config.toml
33890
34177
  }
33891
34178
  logger.log("Prompt completed", { sessionId: params.sessionId });
33892
34179
  await eventHandler?.dispose();
34180
+ disposeGoalConnection();
34181
+ goalLifecycle.fail(new Error("ACP prompt closed"));
34182
+ activePrompt.signal.removeEventListener("abort", cancelGoalLifecycle);
33893
34183
  disposePromptRequestCancellation();
33894
34184
  sessionState.currentTurnId = null;
33895
34185
  const registeredPendingTurnStart = this.pendingTurnStarts.get(params.sessionId);
@@ -33996,6 +34286,12 @@ ${stderr}` : "";
33996
34286
  logger.log("Cancel request rejected: session not found", { sessionId: params.sessionId });
33997
34287
  return;
33998
34288
  }
34289
+ const activePrompt = this.activePrompts.get(params.sessionId);
34290
+ if (activePrompt?.hasCompletedTurn && activePrompt.currentTurn === null) {
34291
+ activePrompt.requestCancel();
34292
+ return;
34293
+ }
34294
+ if (activePrompt?.currentTurn) activePrompt.requestCancel();
33999
34295
  await this.interruptSessionTurn(sessionState, "Cancel", false);
34000
34296
  }
34001
34297
  };