@agentclientprotocol/codex-acp 1.1.7 → 1.1.9

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 +390 -22
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -23582,34 +23582,68 @@ function sameThreadGoalSnapshot(left, right) {
23582
23582
  }
23583
23583
 
23584
23584
  // src/CodexEventHandler.ts
23585
- var CodexEventHandler = class {
23586
- connection;
23585
+ var CodexEventHandler = class _CodexEventHandler {
23586
+ static PLAN_UPDATE_INTERVAL_MS = 150;
23587
23587
  sessionState;
23588
+ supportsPlanUpdates;
23588
23589
  failure = null;
23590
+ completedPlan = null;
23589
23591
  activeFuzzyFileSearchSessions = /* @__PURE__ */ new Set();
23590
23592
  activeGuardianApprovalReviews = /* @__PURE__ */ new Set();
23591
23593
  activeImageGenerationItems = /* @__PURE__ */ new Set();
23592
23594
  emittedImageViewItems = /* @__PURE__ */ new Set();
23593
23595
  planDeltaTextByItemId = /* @__PURE__ */ new Map();
23596
+ pendingPlanItemIds = /* @__PURE__ */ new Set();
23597
+ lastEmittedPlanTextByItemId = /* @__PURE__ */ new Map();
23598
+ session;
23599
+ planUpdateTimer = null;
23600
+ planUpdateChain = Promise.resolve();
23601
+ disposed = false;
23594
23602
  seenReasoningDeltaItemIds = /* @__PURE__ */ new Set();
23595
23603
  terminalCommandIds = /* @__PURE__ */ new Set();
23596
23604
  terminalCommandOutputIds = /* @__PURE__ */ new Set();
23597
23605
  agentMessagePhases = /* @__PURE__ */ new Map();
23598
23606
  activeSubAgentActivities = /* @__PURE__ */ new Set();
23599
- constructor(connection, sessionState) {
23600
- this.connection = connection;
23607
+ constructor(connection, sessionState, supportsPlanUpdates = false) {
23601
23608
  this.sessionState = sessionState;
23609
+ this.supportsPlanUpdates = supportsPlanUpdates;
23610
+ this.session = new ACPSessionConnection(connection, sessionState.sessionId);
23602
23611
  }
23603
23612
  getFailure() {
23604
23613
  return this.failure;
23605
23614
  }
23615
+ takeCompletedPlan() {
23616
+ const plan = this.completedPlan;
23617
+ this.completedPlan = null;
23618
+ return plan;
23619
+ }
23606
23620
  async handleNotification(notification) {
23607
- const session = new ACPSessionConnection(this.connection, this.sessionState.sessionId);
23608
23621
  const updateEvent = await this.createUpdateEvent(notification);
23609
23622
  if (updateEvent) {
23610
- await session.update(updateEvent);
23623
+ await this.session.update(updateEvent);
23611
23624
  }
23612
23625
  }
23626
+ async flushPendingPlanUpdates() {
23627
+ this.cancelPlanUpdateTimer();
23628
+ do {
23629
+ const itemIds = [...this.pendingPlanItemIds];
23630
+ this.pendingPlanItemIds.clear();
23631
+ await Promise.all(itemIds.map((itemId) => {
23632
+ const text = this.planDeltaTextByItemId.get(itemId) ?? "";
23633
+ return text.length > 0 ? this.enqueuePlanSnapshot(itemId, text) : Promise.resolve();
23634
+ }));
23635
+ await this.planUpdateChain;
23636
+ } while (this.pendingPlanItemIds.size > 0);
23637
+ }
23638
+ async dispose() {
23639
+ if (this.disposed) return;
23640
+ await this.flushPendingPlanUpdates();
23641
+ this.disposed = true;
23642
+ this.cancelPlanUpdateTimer();
23643
+ this.pendingPlanItemIds.clear();
23644
+ this.planDeltaTextByItemId.clear();
23645
+ this.lastEmittedPlanTextByItemId.clear();
23646
+ }
23613
23647
  async createUpdateEvent(notification) {
23614
23648
  switch (notification.method) {
23615
23649
  case "item/agentMessage/delta":
@@ -23628,6 +23662,8 @@ var CodexEventHandler = class {
23628
23662
  this.sessionState.currentTurnId = notification.params.turn.id;
23629
23663
  return null;
23630
23664
  case "turn/completed":
23665
+ await this.flushPendingPlanUpdates();
23666
+ this.clearPlanTurnState();
23631
23667
  this.sessionState.currentTurnId = null;
23632
23668
  return null;
23633
23669
  case "thread/tokenUsage/updated":
@@ -23796,7 +23832,12 @@ ${event.details}` : "";
23796
23832
  return null;
23797
23833
  }
23798
23834
  const text = this.planDeltaTextByItemId.get(event.itemId) ?? "";
23799
- this.planDeltaTextByItemId.set(event.itemId, text + event.delta);
23835
+ const updatedText = text + event.delta;
23836
+ this.planDeltaTextByItemId.set(event.itemId, updatedText);
23837
+ if (this.supportsPlanUpdates) {
23838
+ this.pendingPlanItemIds.add(event.itemId);
23839
+ this.schedulePlanUpdate();
23840
+ }
23800
23841
  return null;
23801
23842
  }
23802
23843
  createReasoningSectionBreakEvent(event) {
@@ -23894,8 +23935,7 @@ ${event.details}` : "";
23894
23935
  return null;
23895
23936
  case "plan": {
23896
23937
  const deltaText = this.planDeltaTextByItemId.get(event.item.id) ?? "";
23897
- this.planDeltaTextByItemId.delete(event.item.id);
23898
- return this.createCompletedPlanEvent(event.item, deltaText);
23938
+ return await this.createCompletedPlanEvent(event.item, deltaText);
23899
23939
  }
23900
23940
  case "exitedReviewMode":
23901
23941
  return this.createExitedReviewModeEvent(event.item);
@@ -23924,13 +23964,64 @@ ${event.details}` : "";
23924
23964
  }
23925
23965
  return this.createAgentThoughtEvent(text, item.id);
23926
23966
  }
23927
- createCompletedPlanEvent(item, deltaText) {
23967
+ async createCompletedPlanEvent(item, deltaText) {
23928
23968
  const text = item.text.length > 0 ? item.text : deltaText;
23969
+ this.pendingPlanItemIds.delete(item.id);
23970
+ if (this.pendingPlanItemIds.size === 0) {
23971
+ this.cancelPlanUpdateTimer();
23972
+ }
23973
+ this.planDeltaTextByItemId.delete(item.id);
23929
23974
  if (text.length === 0) {
23930
23975
  return null;
23931
23976
  }
23977
+ this.completedPlan = { itemId: item.id, text };
23978
+ if (this.supportsPlanUpdates) {
23979
+ await this.enqueuePlanSnapshot(item.id, text);
23980
+ return null;
23981
+ }
23932
23982
  return this.createPlanTextEvent(text, item.id);
23933
23983
  }
23984
+ schedulePlanUpdate() {
23985
+ if (this.disposed || this.planUpdateTimer !== null) return;
23986
+ this.planUpdateTimer = setTimeout(() => {
23987
+ this.planUpdateTimer = null;
23988
+ void this.flushPendingPlanUpdates().catch((error51) => {
23989
+ logger.error("Failed to flush throttled plan updates", error51);
23990
+ });
23991
+ }, _CodexEventHandler.PLAN_UPDATE_INTERVAL_MS);
23992
+ }
23993
+ cancelPlanUpdateTimer() {
23994
+ if (this.planUpdateTimer === null) return;
23995
+ clearTimeout(this.planUpdateTimer);
23996
+ this.planUpdateTimer = null;
23997
+ }
23998
+ enqueuePlanSnapshot(itemId, text) {
23999
+ const send = async () => {
24000
+ if (this.lastEmittedPlanTextByItemId.get(itemId) === text) return;
24001
+ await this.session.update(this.createPlanUpdateEvent(text, itemId));
24002
+ this.lastEmittedPlanTextByItemId.set(itemId, text);
24003
+ };
24004
+ const result = this.planUpdateChain.then(send);
24005
+ this.planUpdateChain = result.catch(() => {
24006
+ });
24007
+ return result;
24008
+ }
24009
+ clearPlanTurnState() {
24010
+ this.cancelPlanUpdateTimer();
24011
+ this.pendingPlanItemIds.clear();
24012
+ this.planDeltaTextByItemId.clear();
24013
+ this.lastEmittedPlanTextByItemId.clear();
24014
+ }
24015
+ createPlanUpdateEvent(text, planId) {
24016
+ return {
24017
+ sessionUpdate: "plan_update",
24018
+ plan: {
24019
+ type: "markdown",
24020
+ planId,
24021
+ content: text
24022
+ }
24023
+ };
24024
+ }
23934
24025
  createPlanTextEvent(text, messageId) {
23935
24026
  return createAgentTextMessageChunk(
23936
24027
  text,
@@ -24182,12 +24273,17 @@ var ApprovalOptionId = {
24182
24273
  };
24183
24274
 
24184
24275
  // src/CodexApprovalHandler.ts
24185
- function permissionOption(optionId, name, kind, codexMeta) {
24276
+ function permissionOption(optionId, name, kind, codexMeta, permission) {
24186
24277
  return {
24187
24278
  optionId,
24188
24279
  name,
24189
24280
  kind,
24190
- ...codexMeta ? { _meta: { codex: codexMeta } } : {}
24281
+ ...codexMeta || permission ? {
24282
+ _meta: {
24283
+ ...permission ? { permission } : {},
24284
+ ...codexMeta ? { codex: codexMeta } : {}
24285
+ }
24286
+ } : {}
24191
24287
  };
24192
24288
  }
24193
24289
  var CodexApprovalHandler = class {
@@ -24294,13 +24390,15 @@ var CodexApprovalHandler = class {
24294
24390
  ApprovalOptionId.AllowPermissionsForSession,
24295
24391
  "Allow for Session",
24296
24392
  "allow_always",
24297
- { decision: "allowPermissionsForSession", permissions: params.permissions }
24393
+ { decision: "allowPermissionsForSession", permissions: params.permissions },
24394
+ this.permissionGrantMetadata(params.permissions, "session")
24298
24395
  ),
24299
24396
  permissionOption(
24300
24397
  ApprovalOptionId.AllowPermissionsForTurn,
24301
24398
  "Allow Once",
24302
24399
  "allow_once",
24303
- { decision: "allowPermissionsForTurn", permissions: params.permissions }
24400
+ { decision: "allowPermissionsForTurn", permissions: params.permissions },
24401
+ this.permissionGrantMetadata(params.permissions, "turn")
24304
24402
  ),
24305
24403
  permissionOption(
24306
24404
  ApprovalOptionId.RejectPermissions,
@@ -24362,7 +24460,24 @@ var CodexApprovalHandler = class {
24362
24460
  ApprovalOptionId.AllowAlways,
24363
24461
  params.networkApprovalContext ? "Allow Host for Session" : "Allow for Session",
24364
24462
  "allow_always",
24365
- { decision: "acceptForSession" }
24463
+ { decision: "acceptForSession" },
24464
+ params.networkApprovalContext ? {
24465
+ version: 1,
24466
+ changes: [{
24467
+ type: "grant",
24468
+ operation: "grant",
24469
+ description: `Allow access to ${params.networkApprovalContext.host} for this session`,
24470
+ lifetime: { scope: "session" },
24471
+ targets: [{
24472
+ type: "network",
24473
+ matcher: {
24474
+ type: "host",
24475
+ host: params.networkApprovalContext.host,
24476
+ protocol: params.networkApprovalContext.protocol
24477
+ }
24478
+ }]
24479
+ }]
24480
+ } : void 0
24366
24481
  ),
24367
24482
  decision: "acceptForSession"
24368
24483
  }
@@ -24376,6 +24491,22 @@ var CodexApprovalHandler = class {
24376
24491
  {
24377
24492
  decision: "acceptWithExecpolicyAmendment",
24378
24493
  execpolicyAmendment: params.proposedExecpolicyAmendment
24494
+ },
24495
+ {
24496
+ version: 1,
24497
+ changes: [{
24498
+ type: "policy_rule",
24499
+ operation: "add",
24500
+ ruleBehavior: "allow",
24501
+ description: `Allow commands starting with ${params.proposedExecpolicyAmendment.join(" ")}`,
24502
+ targets: [{
24503
+ type: "command",
24504
+ matcher: {
24505
+ type: "argv_prefix",
24506
+ argv: params.proposedExecpolicyAmendment
24507
+ }
24508
+ }]
24509
+ }]
24379
24510
  }
24380
24511
  ),
24381
24512
  decision: {
@@ -24394,6 +24525,22 @@ var CodexApprovalHandler = class {
24394
24525
  {
24395
24526
  decision: "applyNetworkPolicyAmendment",
24396
24527
  networkPolicyAmendment: amendment
24528
+ },
24529
+ {
24530
+ version: 1,
24531
+ changes: [{
24532
+ type: "policy_rule",
24533
+ operation: "add",
24534
+ ruleBehavior: amendment.action,
24535
+ description: amendment.action === "allow" ? `Allow access to ${amendment.host}` : `Block access to ${amendment.host}`,
24536
+ targets: [{
24537
+ type: "network",
24538
+ matcher: {
24539
+ type: "host",
24540
+ host: amendment.host
24541
+ }
24542
+ }]
24543
+ }]
24397
24544
  }
24398
24545
  ),
24399
24546
  decision: {
@@ -24420,7 +24567,21 @@ var CodexApprovalHandler = class {
24420
24567
  ApprovalOptionId.AllowAlways,
24421
24568
  params.grantRoot ? "Allow Root for Session" : "Allow for Session",
24422
24569
  "allow_always",
24423
- { decision: "acceptForSession", grantRoot: params.grantRoot ?? null }
24570
+ { decision: "acceptForSession", grantRoot: params.grantRoot ?? null },
24571
+ params.grantRoot ? {
24572
+ version: 1,
24573
+ changes: [{
24574
+ type: "grant",
24575
+ operation: "grant",
24576
+ description: `Allow writes under ${params.grantRoot} for this session`,
24577
+ lifetime: { scope: "session" },
24578
+ targets: [{
24579
+ type: "filesystem",
24580
+ access: ["write"],
24581
+ matcher: { type: "directory", path: params.grantRoot }
24582
+ }]
24583
+ }]
24584
+ } : void 0
24424
24585
  ),
24425
24586
  decision: "acceptForSession"
24426
24587
  },
@@ -24443,6 +24604,68 @@ var CodexApprovalHandler = class {
24443
24604
  ...permissions.fileSystem ? { fileSystem: permissions.fileSystem } : {}
24444
24605
  };
24445
24606
  }
24607
+ permissionGrantMetadata(permissions, scope) {
24608
+ const changes = [];
24609
+ const lifetime = { scope };
24610
+ const suffix = scope === "session" ? " for this session" : " for this turn";
24611
+ if (permissions.network?.enabled !== null && permissions.network?.enabled !== void 0) {
24612
+ const allowed = permissions.network.enabled;
24613
+ changes.push({
24614
+ type: allowed ? "grant" : "policy_rule",
24615
+ operation: allowed ? "grant" : "add",
24616
+ ...allowed ? {} : { ruleBehavior: "deny" },
24617
+ description: `${allowed ? "Allow" : "Deny"} network access${suffix}`,
24618
+ lifetime,
24619
+ targets: [{ type: "network", matcher: { type: "any" } }]
24620
+ });
24621
+ }
24622
+ const fileSystem = permissions.fileSystem;
24623
+ for (const path6 of fileSystem?.read ?? []) {
24624
+ changes.push(this.fileSystemGrantChange(path6, "read", lifetime, suffix));
24625
+ }
24626
+ for (const path6 of fileSystem?.write ?? []) {
24627
+ changes.push(this.fileSystemGrantChange(path6, "write", lifetime, suffix));
24628
+ }
24629
+ for (const entry of fileSystem?.entries ?? []) {
24630
+ const matcher = (() => {
24631
+ switch (entry.path.type) {
24632
+ case "path":
24633
+ return { type: "exact_path", path: entry.path.path };
24634
+ case "glob_pattern":
24635
+ return { type: "glob", pattern: entry.path.pattern };
24636
+ case "special":
24637
+ return { type: "special", provider: "codex", value: entry.path.value };
24638
+ }
24639
+ })();
24640
+ const pathDescription = entry.path.type === "path" ? entry.path.path : entry.path.type === "glob_pattern" ? entry.path.pattern : JSON.stringify(entry.path.value);
24641
+ changes.push({
24642
+ type: entry.access === "deny" ? "policy_rule" : "grant",
24643
+ operation: entry.access === "deny" ? "add" : "grant",
24644
+ ...entry.access === "deny" ? { ruleBehavior: "deny" } : {},
24645
+ description: entry.access === "deny" ? `Deny filesystem access to ${pathDescription}${suffix}` : `Allow ${entry.access} access to ${pathDescription}${suffix}`,
24646
+ lifetime,
24647
+ targets: [{
24648
+ type: "filesystem",
24649
+ ...entry.access === "deny" ? {} : { access: [entry.access] },
24650
+ matcher
24651
+ }]
24652
+ });
24653
+ }
24654
+ return changes.length > 0 ? { version: 1, changes } : void 0;
24655
+ }
24656
+ fileSystemGrantChange(path6, access, lifetime, suffix) {
24657
+ return {
24658
+ type: "grant",
24659
+ operation: "grant",
24660
+ description: `Allow ${access} access to ${path6}${suffix}`,
24661
+ lifetime,
24662
+ targets: [{
24663
+ type: "filesystem",
24664
+ access: [access],
24665
+ matcher: { type: "exact_path", path: path6 }
24666
+ }]
24667
+ };
24668
+ }
24446
24669
  networkPolicyAmendmentOptionId(index) {
24447
24670
  return `${ApprovalOptionId.ApplyNetworkPolicyAmendment}:${index}`;
24448
24671
  }
@@ -25913,7 +26136,7 @@ var package_default = {
25913
26136
  publishConfig: {
25914
26137
  access: "public"
25915
26138
  },
25916
- version: "1.1.7",
26139
+ version: "1.1.9",
25917
26140
  description: "",
25918
26141
  main: "dist/index.js",
25919
26142
  bin: {
@@ -28413,7 +28636,14 @@ function isJetBrains2026_1Client(clientInfo) {
28413
28636
  return (isIntelliJPlatform || isJetBrainsClient) && clientInfo.version.startsWith("2026.1");
28414
28637
  }
28415
28638
 
28639
+ // src/PlanCapabilities.ts
28640
+ function clientSupportsPlanUpdates(clientCapabilities) {
28641
+ return clientCapabilities?.plan != null;
28642
+ }
28643
+
28416
28644
  // src/CodexAcpServer.ts
28645
+ var IMPLEMENT_PLAN_OPTION_ID = "implement_plan";
28646
+ var REVISE_PLAN_OPTION_ID = "revise_plan";
28417
28647
  var CodexAcpServer = class _CodexAcpServer {
28418
28648
  static MODEL_NAME_TOKEN_OVERRIDES = {
28419
28649
  gpt: "GPT",
@@ -29532,7 +29762,7 @@ Check ${configPath} and project .codex directories, especially their config.toml
29532
29762
  case "contextCompaction":
29533
29763
  return [createCompletedContextCompactionUpdate(item)];
29534
29764
  case "plan":
29535
- return [this.createPlanMessageUpdate(item)];
29765
+ return item.text.length > 0 ? [this.createPlanHistoryUpdate(item)] : [];
29536
29766
  }
29537
29767
  }
29538
29768
  createUserMessageUpdates(item) {
@@ -29573,7 +29803,17 @@ Check ${configPath} and project .codex directories, especially their config.toml
29573
29803
  }
29574
29804
  };
29575
29805
  }
29576
- createPlanMessageUpdate(item) {
29806
+ createPlanHistoryUpdate(item) {
29807
+ if (clientSupportsPlanUpdates(this.clientCapabilities)) {
29808
+ return {
29809
+ sessionUpdate: "plan_update",
29810
+ plan: {
29811
+ type: "markdown",
29812
+ planId: item.id,
29813
+ content: item.text
29814
+ }
29815
+ };
29816
+ }
29577
29817
  return createAgentTextMessageChunk(
29578
29818
  item.text,
29579
29819
  item.id,
@@ -29866,8 +30106,14 @@ Check ${configPath} and project .codex directories, especially their config.toml
29866
30106
  return pendingTurnStart;
29867
30107
  };
29868
30108
  const disposePromptRequestCancellation = this.observePromptRequestCancellation(signal, sessionState, activePrompt);
30109
+ let eventHandler = null;
29869
30110
  try {
29870
- const eventHandler = new CodexEventHandler(this.connection, sessionState);
30111
+ const promptEventHandler = new CodexEventHandler(
30112
+ this.connection,
30113
+ sessionState,
30114
+ clientSupportsPlanUpdates(this.clientCapabilities)
30115
+ );
30116
+ eventHandler = promptEventHandler;
29871
30117
  const approvalHandler = new CodexApprovalHandler(this.connection, sessionState, activePrompt.signal);
29872
30118
  const elicitationHandler = new CodexElicitationHandler(
29873
30119
  this.connection,
@@ -29879,7 +30125,7 @@ Check ${configPath} and project .codex directories, especially their config.toml
29879
30125
  params.sessionId,
29880
30126
  async (event) => {
29881
30127
  await elicitationHandler.handleNotification(event);
29882
- return eventHandler.handleNotification(event);
30128
+ return promptEventHandler.handleNotification(event);
29883
30129
  },
29884
30130
  approvalHandler,
29885
30131
  elicitationHandler
@@ -29994,7 +30240,7 @@ Check ${configPath} and project .codex directories, especially their config.toml
29994
30240
  logger.error(`Prompt for cancelled session ${params.sessionId} failed after prompt returned`, err);
29995
30241
  }
29996
30242
  });
29997
- const turnCompleted = await Promise.race([
30243
+ let turnCompleted = await Promise.race([
29998
30244
  sendPromptPromise,
29999
30245
  activePrompt.closeSignal,
30000
30246
  this.cancelBeforeTurnStarted(activePrompt)
@@ -30004,6 +30250,7 @@ Check ${configPath} and project .codex directories, especially their config.toml
30004
30250
  }
30005
30251
  await this.codexAcpClient.waitForSessionNotifications(params.sessionId);
30006
30252
  if (turnCompleted.turn.status === "interrupted") {
30253
+ await eventHandler.flushPendingPlanUpdates();
30007
30254
  await this.notifyConversationInterrupted(params.sessionId);
30008
30255
  return this.cancelledPromptResponse(sessionState);
30009
30256
  }
@@ -30011,6 +30258,75 @@ Check ${configPath} and project .codex directories, especially their config.toml
30011
30258
  if (error51) {
30012
30259
  throw error51;
30013
30260
  }
30261
+ await eventHandler.flushPendingPlanUpdates();
30262
+ const completedPlan = eventHandler.takeCompletedPlan();
30263
+ if (completedPlan !== null && sessionState.collaborationMode === PLAN_COLLABORATION_MODE && !this.promptShouldStop(params.sessionId, activePrompt)) {
30264
+ const approved = await this.requestPlanImplementationPermission(
30265
+ sessionState,
30266
+ completedPlan,
30267
+ activePrompt.signal
30268
+ );
30269
+ if (this.promptShouldStop(params.sessionId, activePrompt)) {
30270
+ return this.cancelledPromptResponse(sessionState);
30271
+ }
30272
+ if (approved && !this.promptShouldStop(params.sessionId, activePrompt)) {
30273
+ await this.applyCollaborationModeChange(sessionState, DEFAULT_COLLABORATION_MODE);
30274
+ const session = new ACPSessionConnection(this.connection, sessionState.sessionId);
30275
+ await session.update({
30276
+ sessionUpdate: "config_option_update",
30277
+ configOptions: this.createSessionConfigOptions(sessionState)
30278
+ });
30279
+ const implementationRequest = {
30280
+ sessionId: params.sessionId,
30281
+ prompt: [{ type: "text", text: "Implement the approved plan." }]
30282
+ };
30283
+ activePrompt.currentTurn = null;
30284
+ const implementationPromise = this.runWithProcessCheck(
30285
+ () => this.codexAcpClient.sendPrompt(
30286
+ implementationRequest,
30287
+ agentMode,
30288
+ modelId,
30289
+ serviceTier,
30290
+ disableSummary,
30291
+ sessionState.cwd,
30292
+ sessionState.additionalDirectories,
30293
+ (turnId) => {
30294
+ const turn = { threadId: params.sessionId, turnId };
30295
+ activePrompt.currentTurn = turn;
30296
+ if (this.promptShouldStop(params.sessionId, activePrompt)) {
30297
+ this.interruptLateStartedTurn(turn);
30298
+ return;
30299
+ }
30300
+ sessionState.currentTurnId = turnId;
30301
+ },
30302
+ () => this.promptShouldStop(params.sessionId, activePrompt)
30303
+ )
30304
+ );
30305
+ void implementationPromise.catch((err) => {
30306
+ if (this.activePrompts.get(params.sessionId) !== activePrompt) {
30307
+ logger.error(`Implementation turn for cancelled prompt ${params.sessionId} failed after prompt returned`, err);
30308
+ }
30309
+ });
30310
+ turnCompleted = await Promise.race([
30311
+ implementationPromise,
30312
+ activePrompt.closeSignal,
30313
+ this.cancelBeforeTurnStarted(activePrompt)
30314
+ ]);
30315
+ if (turnCompleted === null) {
30316
+ return this.cancelledPromptResponse(sessionState);
30317
+ }
30318
+ await this.codexAcpClient.waitForSessionNotifications(params.sessionId);
30319
+ if (turnCompleted.turn.status === "interrupted") {
30320
+ await eventHandler.flushPendingPlanUpdates();
30321
+ await this.notifyConversationInterrupted(params.sessionId);
30322
+ return this.cancelledPromptResponse(sessionState);
30323
+ }
30324
+ const implementationError = eventHandler.getFailure();
30325
+ if (implementationError) {
30326
+ throw implementationError;
30327
+ }
30328
+ }
30329
+ }
30014
30330
  await this.publishFallbackSessionTitle(
30015
30331
  sessionState,
30016
30332
  this.createPromptFallbackTitle(params.prompt)
@@ -30025,6 +30341,7 @@ Check ${configPath} and project .codex directories, especially their config.toml
30025
30341
  throw err;
30026
30342
  } finally {
30027
30343
  logger.log("Prompt completed", { sessionId: params.sessionId });
30344
+ await eventHandler?.dispose();
30028
30345
  disposePromptRequestCancellation();
30029
30346
  sessionState.currentTurnId = null;
30030
30347
  const registeredPendingTurnStart = this.pendingTurnStarts.get(params.sessionId);
@@ -30035,6 +30352,57 @@ Check ${configPath} and project .codex directories, especially their config.toml
30035
30352
  activePrompt.complete();
30036
30353
  }
30037
30354
  }
30355
+ async requestPlanImplementationPermission(sessionState, plan, cancellationSignal) {
30356
+ const toolCallId = `plan-review:${plan.itemId}`;
30357
+ try {
30358
+ const response = await this.connection.request(
30359
+ methods.client.session.requestPermission,
30360
+ {
30361
+ sessionId: sessionState.sessionId,
30362
+ toolCall: {
30363
+ toolCallId,
30364
+ title: "Implement this plan?",
30365
+ kind: "switch_mode",
30366
+ status: "pending",
30367
+ rawInput: { plan: plan.text }
30368
+ },
30369
+ options: [
30370
+ {
30371
+ optionId: IMPLEMENT_PLAN_OPTION_ID,
30372
+ name: "Yes, implement this plan",
30373
+ kind: "allow_once"
30374
+ },
30375
+ {
30376
+ optionId: REVISE_PLAN_OPTION_ID,
30377
+ name: "No, and tell Codex what to do differently",
30378
+ kind: "reject_once"
30379
+ }
30380
+ ],
30381
+ _meta: {
30382
+ codex: {
30383
+ kind: "plan_review",
30384
+ planItemId: plan.itemId
30385
+ }
30386
+ }
30387
+ },
30388
+ { cancellationSignal }
30389
+ );
30390
+ const approved = response.outcome.outcome === "selected" && response.outcome.optionId === IMPLEMENT_PLAN_OPTION_ID;
30391
+ await this.connection.notify(methods.client.session.update, {
30392
+ sessionId: sessionState.sessionId,
30393
+ update: {
30394
+ sessionUpdate: "tool_call_update",
30395
+ toolCallId,
30396
+ status: "completed",
30397
+ rawOutput: approved ? "User approved the plan." : "User kept the session in plan mode."
30398
+ }
30399
+ });
30400
+ return approved;
30401
+ } catch (error51) {
30402
+ logger.error("Error requesting plan implementation permission", error51);
30403
+ return false;
30404
+ }
30405
+ }
30038
30406
  cancelledPromptResponse(sessionState) {
30039
30407
  return {
30040
30408
  stopReason: "cancelled",
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "1.1.7",
6
+ "version": "1.1.9",
7
7
  "description": "",
8
8
  "main": "dist/index.js",
9
9
  "bin": {