@ycodium-ai/agent-grok 0.2.3130 → 0.2.3158

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 +274 -109
  2. package/package.json +3 -3
package/dist/index.js CHANGED
@@ -25010,7 +25010,10 @@ function createAcpConnection(options) {
25010
25010
  }
25011
25011
  pending.delete(key);
25012
25012
  try {
25013
- options.onResponse?.(waiter.method);
25013
+ options.onResponse?.(
25014
+ waiter,
25015
+ Object.hasOwn(message, "error") ? { error: message["error"] } : { result: message["result"] }
25016
+ );
25014
25017
  } catch {
25015
25018
  }
25016
25019
  if (Object.hasOwn(message, "error")) {
@@ -25143,12 +25146,17 @@ function createAcpConnection(options) {
25143
25146
  options.readable.on("end", onEnd);
25144
25147
  options.readable.on("error", onError);
25145
25148
  options.writable.once("error", onWritableError);
25146
- const begin = (method, params) => {
25149
+ const begin = (method, params, promptId) => {
25147
25150
  failIfClosed();
25148
25151
  const id = nextRequestId;
25149
25152
  nextRequestId += 1;
25150
25153
  const { promise: promise2, resolve: resolve2, reject } = Promise.withResolvers();
25151
- pending.set(String(id), { method, resolve: resolve2, reject });
25154
+ pending.set(String(id), {
25155
+ method,
25156
+ ...promptId === void 0 ? {} : { promptId },
25157
+ resolve: resolve2,
25158
+ reject
25159
+ });
25152
25160
  const message = params === void 0 ? { jsonrpc: "2.0", id, method } : { jsonrpc: "2.0", id, method, params };
25153
25161
  const written = Promise.race([writeMessage(message), promise2]).then(
25154
25162
  () => void 0,
@@ -25218,12 +25226,14 @@ function createExecutorLifecycle({
25218
25226
  notifyPromptSettled,
25219
25227
  eventStream,
25220
25228
  onInputDead,
25221
- shared
25229
+ shared,
25230
+ promptResults
25222
25231
  }) {
25223
25232
  const cancel = async (turnId) => {
25224
25233
  failIfDisposed();
25225
25234
  const started = requireStarted();
25226
25235
  const targetTurnId = turnId ?? shared.activeTurnId;
25236
+ promptResults.stop(targetTurnId);
25227
25237
  if (targetTurnId !== void 0) {
25228
25238
  interruptedTurnIds.add(targetTurnId);
25229
25239
  }
@@ -25234,6 +25244,9 @@ function createExecutorLifecycle({
25234
25244
  if (shared.sessionId) {
25235
25245
  await connection.notify("session/cancel", { sessionId: shared.sessionId });
25236
25246
  }
25247
+ await promptResults.wait(targetTurnId);
25248
+ if (targetTurnId !== void 0 && shared.activeTurnId !== targetTurnId && !interruptedTurnIds.has(targetTurnId))
25249
+ return;
25237
25250
  const settled = await waitForPromptIdle(cancelGraceMs);
25238
25251
  if (!settled) {
25239
25252
  throw new AcpExecutorCancelTimeoutError();
@@ -25254,6 +25267,7 @@ function createExecutorLifecycle({
25254
25267
  payload: { state: "cancelled", stopReason: "cancelled" }
25255
25268
  });
25256
25269
  interruptedTurnIds.delete(targetTurnId);
25270
+ promptResults.forget(targetTurnId);
25257
25271
  }
25258
25272
  }
25259
25273
  }
@@ -25328,33 +25342,39 @@ function createExecutorLifecycle({
25328
25342
  if (!shared.disposed) emitSessionState("ready");
25329
25343
  }
25330
25344
  };
25331
- const dispose = async () => {
25332
- if (shared.disposed) {
25333
- return;
25334
- }
25345
+ let disposing;
25346
+ const dispose = () => {
25347
+ if (disposing) return disposing;
25348
+ shared.disposing = true;
25349
+ promptResults.stop();
25335
25350
  settleAllApprovals("cancel");
25336
25351
  settleAllUserInputs();
25337
25352
  abortPendingSettlement("executor disposed");
25338
25353
  notifyPromptSettled();
25339
25354
  shared.abortLocalPrompt?.();
25340
- emit({
25341
- type: "session.exited",
25342
- ...stamp(),
25343
- provider: options.provider,
25344
- threadId: options.threadId,
25345
- payload: { reason: "disposed" }
25346
- });
25347
- shared.disposed = true;
25348
- shared.generation += 1;
25349
- shared.status = "disposed";
25350
- eventStream.close();
25351
- options.readable.off("end", onInputDead);
25352
- options.readable.off("error", onInputDead);
25353
- options.readable.pause();
25354
- if (!shared.connectionClosed) {
25355
- await connection.awaitInboundIdle().catch(() => void 0);
25356
- await connection.close().catch(() => void 0);
25357
- }
25355
+ disposing = (async () => {
25356
+ promptResults.finishUnreceived();
25357
+ await promptResults.wait();
25358
+ emit({
25359
+ type: "session.exited",
25360
+ ...stamp(),
25361
+ provider: options.provider,
25362
+ threadId: options.threadId,
25363
+ payload: { reason: "disposed" }
25364
+ });
25365
+ shared.disposed = true;
25366
+ shared.generation += 1;
25367
+ shared.status = "disposed";
25368
+ eventStream.close();
25369
+ options.readable.off("end", onInputDead);
25370
+ options.readable.off("error", onInputDead);
25371
+ options.readable.pause();
25372
+ if (!shared.connectionClosed) {
25373
+ await connection.awaitInboundIdle().catch(() => void 0);
25374
+ await connection.close().catch(() => void 0);
25375
+ }
25376
+ })();
25377
+ return disposing;
25358
25378
  };
25359
25379
  return { cancel, compact, dispose };
25360
25380
  }
@@ -25458,12 +25478,14 @@ function createExecutorPrompt({
25458
25478
  promptMutex,
25459
25479
  applyRequestedModel,
25460
25480
  notifyPromptSettled,
25461
- shared
25481
+ shared,
25482
+ promptResults
25462
25483
  }) {
25463
25484
  const settleTurn = (input2) => {
25464
- if (!isLive(input2.expectedGeneration)) {
25485
+ if (!isLive(input2.expectedGeneration) || input2.record.finished) {
25465
25486
  return false;
25466
25487
  }
25488
+ promptResults.complete(input2.record);
25467
25489
  if (!acpPromptSettlementBelongsToContext({
25468
25490
  liveAcpSessionId: shared.sessionId ?? "",
25469
25491
  expectedAcpSessionId: input2.expectedSessionId,
@@ -25480,6 +25502,7 @@ function createExecutorPrompt({
25480
25502
  if (shared.status !== "disposed") {
25481
25503
  shared.status = "ready";
25482
25504
  }
25505
+ const interrupted = promptResults.hasInterrupted(input2.turnId);
25483
25506
  emit({
25484
25507
  type: "turn.completed",
25485
25508
  ...stamp(),
@@ -25487,30 +25510,44 @@ function createExecutorPrompt({
25487
25510
  threadId: options.threadId,
25488
25511
  turnId: input2.turnId,
25489
25512
  payload: {
25490
- state: input2.state,
25491
- stopReason: input2.stopReason,
25513
+ state: interrupted ? "cancelled" : input2.state,
25514
+ stopReason: interrupted ? "cancelled" : input2.stopReason,
25492
25515
  ...input2.errorMessage === void 0 ? {} : { errorMessage: input2.errorMessage },
25493
25516
  ...input2.state === "failed" ? failureCategoryField(input2.failureCategory) : {}
25494
25517
  }
25495
25518
  });
25496
25519
  interruptedTurnIds.delete(input2.turnId);
25520
+ promptResults.forget(input2.turnId);
25497
25521
  return true;
25498
25522
  };
25499
- const sendPromptRpc = async (sessionId, promptParts, onWritten) => {
25523
+ const sendPromptRpc = async (sessionId, promptParts, onWritten, record3) => {
25500
25524
  const settlementHooks = hooks.promptSettlement;
25501
25525
  const promptKey = settlementHooks === void 0 ? void 0 : randomId();
25502
- const pending = connection.begin("session/prompt", {
25503
- sessionId,
25504
- prompt: promptParts,
25505
- ...settlementHooks !== void 0 && promptKey !== void 0 ? settlementHooks.requestFields(promptKey) : {}
25506
- });
25526
+ const pending = connection.begin(
25527
+ "session/prompt",
25528
+ {
25529
+ sessionId,
25530
+ prompt: promptParts,
25531
+ ...settlementHooks !== void 0 && promptKey !== void 0 ? settlementHooks.requestFields(promptKey) : {}
25532
+ },
25533
+ record3?.promptId
25534
+ );
25507
25535
  void pending.written.then(onWritten, () => void 0);
25508
25536
  const promptRpc = pending.response;
25509
25537
  if (settlementHooks === void 0 || promptKey === void 0) {
25510
25538
  return await promptRpc;
25511
25539
  } else {
25512
25540
  const { promise: early, resolve: settleEarly } = Promise.withResolvers();
25513
- shared.pendingSettlement = { promptKey, sessionId, settle: settleEarly };
25541
+ shared.pendingSettlement = {
25542
+ promptKey,
25543
+ sessionId,
25544
+ settle: settleEarly,
25545
+ ...record3 === void 0 ? {} : {
25546
+ onResponse: (response) => promptResults.receive(record3.promptId, response),
25547
+ onError: () => promptResults.reject(record3.promptId),
25548
+ hasResult: () => record3.response !== void 0
25549
+ }
25550
+ };
25514
25551
  try {
25515
25552
  const outcome = await Promise.race([
25516
25553
  promptRpc.then((value) => {
@@ -25531,9 +25568,11 @@ function createExecutorPrompt({
25531
25568
  const prompt = async (input2) => {
25532
25569
  failIfDisposed();
25533
25570
  const started = requireStarted();
25571
+ let record3;
25534
25572
  const settle2 = (rest) => settleTurn({
25535
25573
  expectedGeneration: started.generation,
25536
25574
  expectedSessionId: started.sessionId,
25575
+ record: record3,
25537
25576
  ...rest
25538
25577
  });
25539
25578
  let turnId;
@@ -25546,6 +25585,17 @@ function createExecutorPrompt({
25546
25585
  randomId
25547
25586
  );
25548
25587
  shared.promptsInFlight += 1;
25588
+ record3 = promptResults.add(
25589
+ {
25590
+ promptId: randomId(),
25591
+ sessionId: started.sessionId,
25592
+ generation: started.generation,
25593
+ turnId
25594
+ },
25595
+ () => {
25596
+ finishPrompt();
25597
+ }
25598
+ );
25549
25599
  shared.activeTurnId = turnId;
25550
25600
  shared.status = "running";
25551
25601
  if (shared.promptsInFlight === 1) {
@@ -25562,6 +25612,42 @@ function createExecutorPrompt({
25562
25612
  threadMutex.release();
25563
25613
  }
25564
25614
  const promptParts = input2.prompt ?? (input2.text !== void 0 ? [{ type: "text", text: input2.text }] : [{ type: "text", text: "" }]);
25615
+ let contextWindow;
25616
+ const finishPrompt = () => {
25617
+ if (!isLive(started.generation) || record3.finished) throw new AcpExecutorDisposedError();
25618
+ const response2 = record3.response;
25619
+ if (record3.interrupted || response2 === void 0) {
25620
+ settle2({ turnId, stopReason: "cancelled", state: "cancelled" });
25621
+ return { threadId: options.threadId, turnId, stopReason: "cancelled" };
25622
+ }
25623
+ const stopReason = hooks.completedStopReason?.(response2) ?? response2.stopReason;
25624
+ if (contextWindow !== void 0) {
25625
+ emit({
25626
+ type: "thread.token-usage.updated",
25627
+ ...stamp(),
25628
+ provider: options.provider,
25629
+ threadId: options.threadId,
25630
+ turnId,
25631
+ payload: {
25632
+ usage: {
25633
+ usedTokens: contextWindow.usedTokens,
25634
+ ...contextWindow.maxTokens === void 0 ? {} : { maxTokens: contextWindow.maxTokens }
25635
+ }
25636
+ }
25637
+ });
25638
+ }
25639
+ settle2({
25640
+ turnId,
25641
+ stopReason,
25642
+ state: response2.stopReason === "cancelled" ? "cancelled" : "completed"
25643
+ });
25644
+ return {
25645
+ threadId: options.threadId,
25646
+ turnId,
25647
+ stopReason,
25648
+ ...response2.usage !== void 0 && response2.usage !== null ? { usage: response2.usage } : {}
25649
+ };
25650
+ };
25565
25651
  await promptMutex.acquire();
25566
25652
  let response;
25567
25653
  try {
@@ -25576,6 +25662,7 @@ function createExecutorPrompt({
25576
25662
  expectedGeneration: started.generation
25577
25663
  });
25578
25664
  }
25665
+ failIfDisposed();
25579
25666
  if (isStale()) {
25580
25667
  settle2({ turnId, stopReason: "cancelled", state: "cancelled" });
25581
25668
  return { threadId: options.threadId, turnId, stopReason: "cancelled" };
@@ -25583,10 +25670,15 @@ function createExecutorPrompt({
25583
25670
  closeActiveAssistantSegment(turnId);
25584
25671
  let written = false;
25585
25672
  try {
25586
- response = await sendPromptRpc(started.sessionId, promptParts, () => {
25587
- written = true;
25588
- input2.onDispatched?.(turnId);
25589
- });
25673
+ response = await sendPromptRpc(
25674
+ started.sessionId,
25675
+ promptParts,
25676
+ () => {
25677
+ written = true;
25678
+ input2.onDispatched?.(turnId);
25679
+ },
25680
+ record3
25681
+ );
25590
25682
  } catch (error62) {
25591
25683
  if (shared.disposed) {
25592
25684
  throw new AcpExecutorDisposedError();
@@ -25625,8 +25717,7 @@ function createExecutorPrompt({
25625
25717
  if (turnId === void 0) {
25626
25718
  throw new AcpExecutorError("internal", "ACP prompt settled without a turn id");
25627
25719
  }
25628
- const settledTurnId = turnId;
25629
- let contextWindow;
25720
+ if (record3.finished) throw new AcpExecutorDisposedError();
25630
25721
  if (hooks.readContextWindow !== void 0) {
25631
25722
  try {
25632
25723
  contextWindow = await hooks.readContextWindow({
@@ -25637,43 +25728,6 @@ function createExecutorPrompt({
25637
25728
  contextWindow = void 0;
25638
25729
  }
25639
25730
  }
25640
- const finishPrompt = () => {
25641
- if (interruptedTurnIds.has(settledTurnId)) {
25642
- settle2({ turnId: settledTurnId, stopReason: "cancelled", state: "cancelled" });
25643
- return {
25644
- threadId: options.threadId,
25645
- turnId: settledTurnId,
25646
- stopReason: "cancelled"
25647
- };
25648
- }
25649
- const stopReason = hooks.completedStopReason?.(response) ?? response.stopReason;
25650
- if (contextWindow !== void 0) {
25651
- emit({
25652
- type: "thread.token-usage.updated",
25653
- ...stamp(),
25654
- provider: options.provider,
25655
- threadId: options.threadId,
25656
- turnId: settledTurnId,
25657
- payload: {
25658
- usage: {
25659
- usedTokens: contextWindow.usedTokens,
25660
- ...contextWindow.maxTokens === void 0 ? {} : { maxTokens: contextWindow.maxTokens }
25661
- }
25662
- }
25663
- });
25664
- }
25665
- settle2({
25666
- turnId: settledTurnId,
25667
- stopReason,
25668
- state: response.stopReason === "cancelled" ? "cancelled" : "completed"
25669
- });
25670
- return {
25671
- threadId: options.threadId,
25672
- turnId: settledTurnId,
25673
- stopReason,
25674
- ...response.usage !== void 0 && response.usage !== null ? { usage: response.usage } : {}
25675
- };
25676
- };
25677
25731
  if (threadMutex.tryAcquire()) {
25678
25732
  try {
25679
25733
  return finishPrompt();
@@ -25691,6 +25745,91 @@ function createExecutorPrompt({
25691
25745
  return { sendPromptRpc, prompt };
25692
25746
  }
25693
25747
 
25748
+ // ../../packages/ycodium-acp-executor/src/executor/prompt-results.ts
25749
+ function createPromptResults({
25750
+ options,
25751
+ shared
25752
+ }) {
25753
+ const prompts = /* @__PURE__ */ new Map();
25754
+ const add = (identity, finish) => {
25755
+ const { promise: done, resolve: resolve2 } = Promise.withResolvers();
25756
+ const record3 = {
25757
+ ...identity,
25758
+ finish,
25759
+ done,
25760
+ resolve: resolve2,
25761
+ rejected: false,
25762
+ interrupted: false,
25763
+ finished: false
25764
+ };
25765
+ prompts.set(record3.promptId, record3);
25766
+ return record3;
25767
+ };
25768
+ const receive = (promptId, response) => {
25769
+ const record3 = prompts.get(promptId);
25770
+ if (!record3 || record3.finished || record3.rejected || record3.response !== void 0) return;
25771
+ if (shared.disposed || shared.generation !== record3.generation || shared.sessionId !== record3.sessionId)
25772
+ return;
25773
+ record3.response = response;
25774
+ };
25775
+ const reject = (promptId) => {
25776
+ const record3 = prompts.get(promptId);
25777
+ if (record3 && record3.response === void 0) record3.rejected = true;
25778
+ };
25779
+ const stop = (turnId) => {
25780
+ for (const record3 of prompts.values()) {
25781
+ if (!record3.finished && (turnId === void 0 || record3.turnId === turnId) && record3.response === void 0) {
25782
+ record3.interrupted = true;
25783
+ }
25784
+ }
25785
+ };
25786
+ const complete = (record3) => {
25787
+ record3.finished = true;
25788
+ record3.resolve();
25789
+ };
25790
+ const hasInterrupted = (turnId) => [...prompts.values()].some((record3) => record3.turnId === turnId && record3.interrupted);
25791
+ const forget = (turnId) => {
25792
+ for (const [id, record3] of prompts) {
25793
+ if (record3.turnId === turnId) {
25794
+ complete(record3);
25795
+ prompts.delete(id);
25796
+ }
25797
+ }
25798
+ };
25799
+ const wait = async (turnId) => {
25800
+ const claimed = [...prompts.values()].filter(
25801
+ (record3) => !record3.finished && record3.response !== void 0 && (turnId === void 0 || record3.turnId === turnId)
25802
+ );
25803
+ if (claimed.length === 0) return;
25804
+ let timer;
25805
+ try {
25806
+ const drained = await Promise.race([
25807
+ Promise.all(claimed.map((record3) => record3.done)).then(() => true),
25808
+ new Promise((resolve2) => {
25809
+ timer = setTimeout(() => resolve2(false), options.resultDrainTimeoutMs ?? 2e3);
25810
+ })
25811
+ ]);
25812
+ if (drained) return;
25813
+ options.log?.warn?.("ACP result settlement timed out", { threadId: options.threadId });
25814
+ for (const record3 of claimed) if (!record3.finished) record3.finish();
25815
+ } finally {
25816
+ clearTimeout(timer);
25817
+ }
25818
+ };
25819
+ const finishUnreceived = () => {
25820
+ const claimedTurns = new Set(
25821
+ [...prompts.values()].filter((record3) => !record3.finished && record3.response !== void 0).map((record3) => record3.turnId)
25822
+ );
25823
+ for (const record3 of prompts.values()) {
25824
+ if (!record3.finished && record3.response === void 0) {
25825
+ if (claimedTurns.has(record3.turnId)) record3.finish();
25826
+ else complete(record3);
25827
+ }
25828
+ }
25829
+ };
25830
+ return { add, receive, reject, stop, complete, hasInterrupted, forget, wait, finishUnreceived };
25831
+ }
25832
+
25694
25833
  // ../../packages/ycodium-acp-executor/src/user-input-pending.ts
25695
25834
  var userInputAnswerOptionId = (question, answers) => {
25696
25835
  const answer = answers[question.id];
@@ -25997,7 +26136,7 @@ function createExecutorRuntime({
25997
26136
  turnId: () => shared.activeTurnId
25998
26137
  });
25999
26138
  const failIfDisposed = () => {
26000
- if (shared.disposed) {
26139
+ if (shared.disposed || shared.disposing) {
26001
26140
  throw new AcpExecutorDisposedError();
26002
26141
  }
26003
26142
  };
@@ -26504,6 +26643,7 @@ function createExecutorSettlement({
26504
26643
  const abortPendingSettlement = (reason, failureCategory) => {
26505
26644
  const waiting = shared.pendingSettlement;
26506
26645
  if (waiting === void 0) return;
26646
+ if (waiting.hasResult?.()) return;
26507
26647
  shared.pendingSettlement = void 0;
26508
26648
  waiting.settle({
26509
26649
  error: withFailureCategory(
@@ -26525,6 +26665,7 @@ function createExecutorSettlement({
26525
26665
  shared.pendingSettlement = void 0;
26526
26666
  rememberSettledPromptKey(waiting.promptKey);
26527
26667
  if (signal.outcome === "error") {
26668
+ waiting.onError?.();
26528
26669
  waiting.settle({
26529
26670
  error: withFailureCategory(
26530
26671
  new AcpExecutorRequestError(
@@ -26535,8 +26676,10 @@ function createExecutorSettlement({
26535
26676
  )
26536
26677
  });
26537
26678
  } else {
26679
+ const response = { stopReason: signal.stopReason ?? "end_turn" };
26680
+ waiting.onResponse?.(response);
26538
26681
  waiting.settle({
26539
- response: { stopReason: signal.stopReason ?? "end_turn" }
26682
+ response
26540
26683
  });
26541
26684
  }
26542
26685
  return true;
@@ -26866,6 +27009,7 @@ function createAcpExecutor(options) {
26866
27009
  const shared = {
26867
27010
  generation: 0,
26868
27011
  disposed: false,
27012
+ disposing: false,
26869
27013
  status: "idle",
26870
27014
  sessionId: void 0,
26871
27015
  modelSelectionState: {
@@ -26953,6 +27097,7 @@ function createAcpExecutor(options) {
26953
27097
  shared
26954
27098
  });
26955
27099
  const { rememberSettledPromptKey, abortPendingSettlement, settleFromNotification } = createExecutorSettlement({ hooks, shared });
27100
+ const promptResults = createPromptResults({ options, shared });
26956
27101
  const connection = createAcpConnection({
26957
27102
  readable: options.readable,
26958
27103
  writable: options.writable,
@@ -26963,8 +27108,12 @@ function createAcpExecutor(options) {
26963
27108
  });
26964
27109
  },
26965
27110
  // 窗口在应答帧到达的那一刻同步收掉:应答之后的通知是真输出,不能再丢。
26966
- onResponse: (method) => {
27111
+ onResponse: ({ method, promptId }, outcome) => {
26967
27112
  if (method === "session/load") shared.sessionLoadInFlight = false;
27113
+ if (promptId !== void 0) {
27114
+ if ("result" in outcome) promptResults.receive(promptId, outcome.result);
27115
+ else promptResults.reject(promptId);
27116
+ }
26968
27117
  },
26969
27118
  onNotification: (notification) => {
26970
27119
  if (notification.method === "session/update") {
@@ -27029,6 +27178,7 @@ function createAcpExecutor(options) {
27029
27178
  shared
27030
27179
  });
27031
27180
  const { sendPromptRpc, prompt } = createExecutorPrompt({
27181
+ promptResults,
27032
27182
  isLive,
27033
27183
  closeActiveAssistantSegment,
27034
27184
  emit,
@@ -27048,6 +27198,7 @@ function createAcpExecutor(options) {
27048
27198
  shared
27049
27199
  });
27050
27200
  const { cancel, compact, dispose } = createExecutorLifecycle({
27201
+ promptResults,
27051
27202
  failIfDisposed,
27052
27203
  requireStarted,
27053
27204
  interruptedTurnIds,
@@ -27099,6 +27250,27 @@ function createAcpExecutor(options) {
27099
27250
  };
27100
27251
  }
27101
27252
 
27253
+ // ../../packages/ycodium-acp-executor/src/session-registry.ts
27254
+ function createAcpSessionRegistry(threads, stopBooked2) {
27255
+ const requireThread = (threadId) => {
27256
+ const booked = threads.get(threadId);
27257
+ if (!booked) throw new Error(`unknown thread ${threadId}`);
27258
+ if (booked.closing) throw new Error(`thread ${threadId} is closing`);
27259
+ return booked;
27260
+ };
27261
+ const stopSession = (threadId, booked = threads.get(threadId)) => {
27262
+ if (!booked) return Promise.resolve();
27263
+ booked.closing ??= Promise.resolve().then(() => stopBooked2(booked)).then(() => {
27264
+ if (threads.get(threadId) === booked) threads.delete(threadId);
27265
+ });
27266
+ return booked.closing;
27267
+ };
27268
+ const stopAll = () => Promise.all([...threads].map(([threadId, booked]) => stopSession(threadId, booked))).then(
27269
+ () => void 0
27270
+ );
27271
+ return { requireThread, stopSession, stopAll };
27272
+ }
27273
+
27102
27274
  // ../../packages/ycodium-acp-executor/src/prompt-dispatch.ts
27103
27275
  var dispatchAcpPrompt = (executor, input2) => {
27104
27276
  const { promise: dispatched, resolve: resolve2 } = Promise.withResolvers();
@@ -28540,11 +28712,8 @@ var createGrokInstance = (config2, host) => {
28540
28712
  const threads = /* @__PURE__ */ new Map();
28541
28713
  const signInOperations = /* @__PURE__ */ new Map();
28542
28714
  let disposed = false;
28543
- const requireThread = (threadId) => {
28544
- const booked = threads.get(threadId);
28545
- if (!booked) throw new Error(`unknown thread ${threadId}`);
28546
- return booked;
28547
- };
28715
+ let disposeTask;
28716
+ const { requireThread, stopSession, stopAll } = createAcpSessionRegistry(threads, stopBooked);
28548
28717
  const probeAuth = () => {
28549
28718
  if ((recipe.environment["XAI_API_KEY"] ?? "").trim().length > 0) {
28550
28719
  return { status: "authenticated", type: "api-key", label: "XAI_API_KEY" };
@@ -28638,8 +28807,8 @@ var createGrokInstance = (config2, host) => {
28638
28807
  if (cwd.trim() === "") throw new Error("workspace path missing");
28639
28808
  const existing = threads.get(input2.threadId);
28640
28809
  if (existing !== void 0) {
28641
- await stopBooked(existing);
28642
- threads.delete(input2.threadId);
28810
+ await stopSession(input2.threadId, existing);
28811
+ if (disposed) throw new Error("instance disposed");
28643
28812
  throwIfStartAborted(signal);
28644
28813
  }
28645
28814
  const configuredHome = recipe.homePath;
@@ -28841,7 +29010,7 @@ var createGrokInstance = (config2, host) => {
28841
29010
  },
28842
29011
  interruptTurn: async (threadId, turnId) => {
28843
29012
  const booked = threads.get(threadId);
28844
- if (booked === void 0) return;
29013
+ if (booked === void 0 || booked.closing) return;
28845
29014
  host.log.info("interrupt turn", { threadId, turnId });
28846
29015
  await booked.executor.cancel(turnId);
28847
29016
  await yieldTick();
@@ -28867,12 +29036,7 @@ var createGrokInstance = (config2, host) => {
28867
29036
  }
28868
29037
  await booked.executor.compact({ commandName: "compact" });
28869
29038
  },
28870
- stopSession: async (threadId) => {
28871
- const booked = threads.get(threadId);
28872
- if (booked === void 0) return;
28873
- threads.delete(threadId);
28874
- await stopBooked(booked);
28875
- },
29039
+ stopSession: (threadId) => stopSession(threadId),
28876
29040
  readThread: async (threadId) => {
28877
29041
  const booked = requireThread(threadId);
28878
29042
  return { threadId, turns: booked.turns };
@@ -28885,18 +29049,19 @@ var createGrokInstance = (config2, host) => {
28885
29049
  fetch: host.fetch
28886
29050
  }),
28887
29051
  continuationKey: grokContinuationKey(recipe),
28888
- dispose: async () => {
28889
- if (disposed) return;
29052
+ dispose: () => {
29053
+ if (disposeTask) return disposeTask;
28890
29054
  disposed = true;
28891
- const operations = [...signInOperations.values()];
28892
- signInOperations.clear();
28893
- for (const operation of operations) {
28894
- await operation.handle.kill("SIGTERM").catch(() => void 0);
28895
- }
28896
- const booked = [...threads.values()];
28897
- threads.clear();
28898
- await Promise.all(booked.map((entry) => stopBooked(entry)));
28899
- events.close();
29055
+ disposeTask = (async () => {
29056
+ const operations = [...signInOperations.values()];
29057
+ signInOperations.clear();
29058
+ for (const operation of operations) {
29059
+ await operation.handle.kill("SIGTERM").catch(() => void 0);
29060
+ }
29061
+ await stopAll();
29062
+ events.close();
29063
+ })();
29064
+ return disposeTask;
28900
29065
  },
28901
29066
  /**
28902
29067
  * 设备码登录:`grok login --device-auth` 是 RFC 8628 流程,CLI 自己打印网址和
@@ -28953,7 +29118,7 @@ var executors = [
28953
29118
  ];
28954
29119
  var index_default = definePlugin({
28955
29120
  id: "agent-grok",
28956
- apiVersion: 14,
29121
+ apiVersion: 15,
28957
29122
  executors,
28958
29123
  activate(ctx) {
28959
29124
  ctx.log.info("grok executor ready");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ycodium-ai/agent-grok",
3
- "version": "0.2.3130",
3
+ "version": "0.2.3158",
4
4
  "description": "Grok executor: grok CLI over Agent Client Protocol stdio.",
5
5
  "keywords": [
6
6
  "ycodium",
@@ -21,8 +21,8 @@
21
21
  "@types/node": "24.12.4",
22
22
  "esbuild": "0.28.1",
23
23
  "vite-plus": "0.2.2",
24
- "@ycodium-ai/plugin-api": "0.2.3130",
25
- "ycodium-acp-executor": "0.2.3130",
24
+ "@ycodium-ai/plugin-api": "0.2.3158",
25
+ "ycodium-acp-executor": "0.2.3158",
26
26
  "ycodium-executor-testing": "0.0.0"
27
27
  },
28
28
  "ycodium": {