happy-imou-cloud 2.0.7 → 2.0.8

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 (26) hide show
  1. package/bin/happy-cloud.mjs +1 -1
  2. package/dist/{api-Dwkm7s_E.cjs → api-CN-WqYd_.cjs} +3 -4
  3. package/dist/{api-dwwHBzLc.mjs → api-D-uiH_TF.mjs} +3 -4
  4. package/dist/{command-Cfq3Uc0S.mjs → command-DGFsZx58.mjs} +3 -3
  5. package/dist/{command-DiAVIsxX.cjs → command-DjIfRZQS.cjs} +3 -3
  6. package/dist/{index-CfqxEoyl.cjs → index-DM6z3aeG.cjs} +189 -26
  7. package/dist/{index-HyqLXzw-.mjs → index-DhheEtRl.mjs} +187 -24
  8. package/dist/index.cjs +3 -3
  9. package/dist/index.mjs +3 -3
  10. package/dist/lib.cjs +1 -1
  11. package/dist/lib.mjs +1 -1
  12. package/dist/{BaseReasoningProcessor-ClrT-x-H.mjs → names-BjEof0E2.mjs} +98 -3
  13. package/dist/{BaseReasoningProcessor-DphULXS-.cjs → names-BnV67N_O.cjs} +100 -2
  14. package/dist/{persistence-hbhwAYIV.cjs → persistence-DBGkO8gB.cjs} +110 -1
  15. package/dist/{persistence-Dg-rxY2a.mjs → persistence-DiNg1DPF.mjs} +100 -4
  16. package/dist/{registerKillSessionHandler-D1ouN10n.cjs → registerKillSessionHandler-CYc0SIjF.cjs} +2 -2
  17. package/dist/{registerKillSessionHandler-BAvk4GYO.mjs → registerKillSessionHandler-Cu9rHGsI.mjs} +2 -2
  18. package/dist/{runClaude-CZmJ7qEP.cjs → runClaude-CPhWaFrX.cjs} +5 -5
  19. package/dist/{runClaude-OxYbt3ZQ.mjs → runClaude-DAR_hw3C.mjs} +4 -4
  20. package/dist/{runCodex-CtncAgso.cjs → runCodex--QLrOs8X.cjs} +24 -25
  21. package/dist/{runCodex-ByVTEbSY.mjs → runCodex-B4QAb-Go.mjs} +5 -6
  22. package/dist/{runGemini-ChwjLmhI.cjs → runGemini-CDyhCucw.cjs} +20 -21
  23. package/dist/{runGemini-BRO6A2jm.mjs → runGemini-DqowSR2w.mjs} +5 -6
  24. package/package.json +3 -4
  25. package/scripts/build.mjs +66 -0
  26. package/scripts/release-smoke.mjs +166 -30
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env node
1
+ #!/usr/bin/env node
2
2
 
3
3
  import { execFileSync } from 'child_process';
4
4
  import { fileURLToPath } from 'url';
@@ -19,7 +19,7 @@ var path = require('path');
19
19
  var expoServerSdk = require('expo-server-sdk');
20
20
 
21
21
  var name = "happy-imou-cloud";
22
- var version = "2.0.7";
22
+ var version = "2.0.8";
23
23
  var description = "hicloud - Imou 企业定制版。关键是 happy!移动端远程 AI 编程工具,支持 Claude Code、Codex 和 Gemini CLI";
24
24
  var author = "long.zhu";
25
25
  var license = "MIT";
@@ -62,7 +62,7 @@ var scripts = {
62
62
  "// ==== TypeScript & Build ====": "",
63
63
  "why do we need to build before running tests / dev?": "We need the binary to be built so we run daemon commands which directly run the binary - we don't want them to go out of sync or have custom spawn logic depending how we started happy",
64
64
  typecheck: "tsc --noEmit",
65
- build: "shx rm -rf dist && npx tsc --noEmit && pkgroll",
65
+ build: "node ./scripts/build.mjs",
66
66
  "// ==== Testing ====": "",
67
67
  test: "yarn build && vitest run",
68
68
  "test:integration": "yarn build && vitest run --config vitest.integration.config.ts",
@@ -118,7 +118,6 @@ var dependencies = {
118
118
  "expo-server-sdk": "^3.15.0",
119
119
  fastify: "^5.6.2",
120
120
  "fastify-type-provider-zod": "4.0.2",
121
- "happy-protocol": "0.1.0",
122
121
  "http-proxy": "^1.18.1",
123
122
  "http-proxy-middleware": "^3.0.5",
124
123
  ink: "^6.5.1",
@@ -433,7 +432,7 @@ async function listDaemonLogFiles(limit = 50) {
433
432
  return { file, path: fullPath, modified: stats.mtime };
434
433
  }).sort((a, b) => b.modified.getTime() - a.modified.getTime());
435
434
  try {
436
- const { readDaemonState } = await Promise.resolve().then(function () { return require('./persistence-hbhwAYIV.cjs'); });
435
+ const { readDaemonState } = await Promise.resolve().then(function () { return require('./persistence-DBGkO8gB.cjs'); });
437
436
  const state = await readDaemonState();
438
437
  if (!state) {
439
438
  return logs;
@@ -17,7 +17,7 @@ import { resolve, join as join$1 } from 'path';
17
17
  import { Expo } from 'expo-server-sdk';
18
18
 
19
19
  var name = "happy-imou-cloud";
20
- var version = "2.0.7";
20
+ var version = "2.0.8";
21
21
  var description = "hicloud - Imou 企业定制版。关键是 happy!移动端远程 AI 编程工具,支持 Claude Code、Codex 和 Gemini CLI";
22
22
  var author = "long.zhu";
23
23
  var license = "MIT";
@@ -60,7 +60,7 @@ var scripts = {
60
60
  "// ==== TypeScript & Build ====": "",
61
61
  "why do we need to build before running tests / dev?": "We need the binary to be built so we run daemon commands which directly run the binary - we don't want them to go out of sync or have custom spawn logic depending how we started happy",
62
62
  typecheck: "tsc --noEmit",
63
- build: "shx rm -rf dist && npx tsc --noEmit && pkgroll",
63
+ build: "node ./scripts/build.mjs",
64
64
  "// ==== Testing ====": "",
65
65
  test: "yarn build && vitest run",
66
66
  "test:integration": "yarn build && vitest run --config vitest.integration.config.ts",
@@ -116,7 +116,6 @@ var dependencies = {
116
116
  "expo-server-sdk": "^3.15.0",
117
117
  fastify: "^5.6.2",
118
118
  "fastify-type-provider-zod": "4.0.2",
119
- "happy-protocol": "0.1.0",
120
119
  "http-proxy": "^1.18.1",
121
120
  "http-proxy-middleware": "^3.0.5",
122
121
  ink: "^6.5.1",
@@ -431,7 +430,7 @@ async function listDaemonLogFiles(limit = 50) {
431
430
  return { file, path: fullPath, modified: stats.mtime };
432
431
  }).sort((a, b) => b.modified.getTime() - a.modified.getTime());
433
432
  try {
434
- const { readDaemonState } = await import('./persistence-Dg-rxY2a.mjs');
433
+ const { readDaemonState } = await import('./persistence-DiNg1DPF.mjs');
435
434
  const state = await readDaemonState();
436
435
  if (!state) {
437
436
  return logs;
@@ -1,6 +1,6 @@
1
- import { c as createDefaultRuntimeShell } from './index-HyqLXzw-.mjs';
1
+ import { c as createDefaultRuntimeShell } from './index-DhheEtRl.mjs';
2
2
  import 'chalk';
3
- import './api-dwwHBzLc.mjs';
3
+ import './api-D-uiH_TF.mjs';
4
4
  import 'axios';
5
5
  import 'fs';
6
6
  import 'node:fs';
@@ -17,7 +17,7 @@ import 'fs/promises';
17
17
  import 'crypto';
18
18
  import 'path';
19
19
  import 'expo-server-sdk';
20
- import './persistence-Dg-rxY2a.mjs';
20
+ import './persistence-DiNg1DPF.mjs';
21
21
  import 'node:fs/promises';
22
22
  import 'os';
23
23
  import 'tmp';
@@ -1,8 +1,8 @@
1
1
  'use strict';
2
2
 
3
- var index = require('./index-CfqxEoyl.cjs');
3
+ var index = require('./index-DM6z3aeG.cjs');
4
4
  require('chalk');
5
- require('./api-Dwkm7s_E.cjs');
5
+ require('./api-CN-WqYd_.cjs');
6
6
  require('axios');
7
7
  require('fs');
8
8
  require('node:fs');
@@ -19,7 +19,7 @@ require('fs/promises');
19
19
  require('crypto');
20
20
  require('path');
21
21
  require('expo-server-sdk');
22
- require('./persistence-hbhwAYIV.cjs');
22
+ require('./persistence-DBGkO8gB.cjs');
23
23
  require('node:fs/promises');
24
24
  require('os');
25
25
  require('tmp');
@@ -1,8 +1,8 @@
1
1
  'use strict';
2
2
 
3
3
  var chalk = require('chalk');
4
- var api = require('./api-Dwkm7s_E.cjs');
5
- var persistence = require('./persistence-hbhwAYIV.cjs');
4
+ var api = require('./api-CN-WqYd_.cjs');
5
+ var persistence = require('./persistence-DBGkO8gB.cjs');
6
6
  var z = require('zod');
7
7
  var fs$1 = require('fs/promises');
8
8
  var os$1 = require('os');
@@ -70,7 +70,7 @@ async function openBrowser(url) {
70
70
  }
71
71
  }
72
72
 
73
- const require$1 = node_module.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index-CfqxEoyl.cjs', document.baseURI).href)));
73
+ const require$1 = node_module.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index-DM6z3aeG.cjs', document.baseURI).href)));
74
74
  const QRCode = require$1("qrcode-terminal/vendor/QRCode");
75
75
  const QRErrorCorrectLevel = require$1("qrcode-terminal/vendor/QRCode/QRErrorCorrectLevel");
76
76
  const pendingTempFiles = /* @__PURE__ */ new Set();
@@ -693,7 +693,7 @@ function setupCleanupHandlers() {
693
693
  });
694
694
  }
695
695
 
696
- const __dirname$1 = path.dirname(url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index-CfqxEoyl.cjs', document.baseURI).href))));
696
+ const __dirname$1 = path.dirname(url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index-DM6z3aeG.cjs', document.baseURI).href))));
697
697
  function projectPath() {
698
698
  const path$1 = path.resolve(__dirname$1, "..");
699
699
  return path$1;
@@ -4213,6 +4213,9 @@ class DefaultTransport {
4213
4213
  getInitTimeout() {
4214
4214
  return DEFAULT_TIMEOUTS.init;
4215
4215
  }
4216
+ getInitDelayMs() {
4217
+ return 0;
4218
+ }
4216
4219
  /**
4217
4220
  * Default: pass through all lines that are valid JSON objects/arrays
4218
4221
  */
@@ -4273,11 +4276,16 @@ class DefaultTransport {
4273
4276
  determineToolName(toolName, _toolCallId, _input, _context) {
4274
4277
  return toolName;
4275
4278
  }
4279
+ getPostPromptNoUpdatesTimeoutMs() {
4280
+ return 3e4;
4281
+ }
4276
4282
  }
4277
4283
 
4278
4284
  const GEMINI_TIMEOUTS = {
4279
4285
  /** Gemini CLI can be slow on first start (downloading models, etc.) */
4280
4286
  init: 12e4,
4287
+ /** Gemini ACP can swallow an initialize request sent too early after spawn */
4288
+ initDelay: 2500,
4281
4289
  /** Standard tool call timeout */
4282
4290
  toolCall: 12e4,
4283
4291
  /** Investigation tools (codebase_investigator) can run for a long time */
@@ -4312,6 +4320,9 @@ class GeminiTransport {
4312
4320
  getInitTimeout() {
4313
4321
  return GEMINI_TIMEOUTS.init;
4314
4322
  }
4323
+ getInitDelayMs() {
4324
+ return GEMINI_TIMEOUTS.initDelay;
4325
+ }
4315
4326
  /**
4316
4327
  * Filter Gemini CLI debug output from stdout.
4317
4328
  *
@@ -5048,6 +5059,10 @@ ${line}` : line;
5048
5059
  controller.error(error);
5049
5060
  } finally {
5050
5061
  reader.releaseLock();
5062
+ try {
5063
+ params.onDone?.();
5064
+ } catch {
5065
+ }
5051
5066
  if (!controllerErrored) {
5052
5067
  controller.close();
5053
5068
  }
@@ -5110,7 +5125,7 @@ async function killProcessTree(proc, options) {
5110
5125
  if (!pid) {
5111
5126
  return;
5112
5127
  }
5113
- const graceMs = Math.max(1, options?.graceMs);
5128
+ const graceMs = Math.max(1, options?.graceMs ?? 1e3);
5114
5129
  const descendants = await resolveDescendantPids(pid).catch(() => []);
5115
5130
  const allPids = [...descendants, pid];
5116
5131
  for (const targetPid of allPids) {
@@ -5135,6 +5150,29 @@ const RETRY_CONFIG = {
5135
5150
  /** Maximum delay between retries in ms */
5136
5151
  maxDelayMs: 5e3
5137
5152
  };
5153
+ const DEFAULT_POST_PROMPT_NO_UPDATES_TIMEOUT_MS = 3e4;
5154
+ function readPositiveIntegerEnv(name) {
5155
+ const raw = typeof process.env[name] === "string" ? process.env[name].trim() : "";
5156
+ if (!raw) {
5157
+ return null;
5158
+ }
5159
+ const value = Number(raw);
5160
+ if (!Number.isFinite(value) || !Number.isInteger(value) || value <= 0) {
5161
+ return null;
5162
+ }
5163
+ return value;
5164
+ }
5165
+ function resolvePostPromptNoUpdatesTimeoutMs(transport) {
5166
+ const transportValue = transport.getPostPromptNoUpdatesTimeoutMs?.();
5167
+ if (typeof transportValue === "number" && Number.isFinite(transportValue) && transportValue > 0) {
5168
+ return Math.trunc(transportValue);
5169
+ }
5170
+ const envValue = readPositiveIntegerEnv("HAPPY_ACP_POST_PROMPT_NO_UPDATES_TIMEOUT_MS") ?? readPositiveIntegerEnv("HAPPIER_ACP_POST_PROMPT_NO_UPDATES_TIMEOUT_MS");
5171
+ if (envValue != null) {
5172
+ return envValue;
5173
+ }
5174
+ return DEFAULT_POST_PROMPT_NO_UPDATES_TIMEOUT_MS;
5175
+ }
5138
5176
  function getSessionUpdates(params) {
5139
5177
  const notification = params;
5140
5178
  const updates = Array.isArray(notification.updates) ? notification.updates.filter((update) => Boolean(update && typeof update === "object")) : [];
@@ -5310,6 +5348,17 @@ function normalizeAcpError(error) {
5310
5348
  }
5311
5349
  return normalized;
5312
5350
  }
5351
+ function getStatusErrorDetail(message) {
5352
+ if (message.type !== "status" || message.status !== "error") {
5353
+ return null;
5354
+ }
5355
+ const detail = typeof message.detail === "string" ? message.detail.trim() : "";
5356
+ return detail || "Unknown ACP transport error";
5357
+ }
5358
+ function looksLikeDroppedStdoutError(text) {
5359
+ const lower = text.toLowerCase();
5360
+ return lower.startsWith("error") || lower.includes("error:") || lower.includes("exception") || lower.includes("traceback") || lower.includes("invalid request") || lower.includes("invalid_request") || lower.includes("unauthorized") || lower.includes("forbidden") || lower.includes("permission denied") || /\b(4\d\d|5\d\d)\b/.test(lower) && (lower.includes("http") || lower.includes("status") || lower.includes("request"));
5361
+ }
5313
5362
  function createAcpAbortError(message) {
5314
5363
  const error = new Error(message);
5315
5364
  error.name = "AbortError";
@@ -5432,6 +5481,12 @@ class AcpBackend {
5432
5481
  responseCompletionOutcome = null;
5433
5482
  /** Whether the current prompt is still waiting for completion */
5434
5483
  waitingForResponse = false;
5484
+ /** First fatal prompt-level error observed for the current turn */
5485
+ responseCompletionError = null;
5486
+ /** Fallback completion when prompt returns but the agent emits no session updates */
5487
+ postPromptCompletionIdleTimeout = null;
5488
+ /** Whether at least one session/update arrived after the current prompt */
5489
+ sawSessionUpdateSincePrompt = false;
5435
5490
  /** Transport handler for agent-specific behavior */
5436
5491
  transport;
5437
5492
  /** Keep a short rolling stderr buffer so startup failures can surface the real cause. */
@@ -5455,6 +5510,12 @@ class AcpBackend {
5455
5510
  this.idleTimeout = null;
5456
5511
  }
5457
5512
  }
5513
+ clearPostPromptCompletionIdleTimeout() {
5514
+ if (this.postPromptCompletionIdleTimeout) {
5515
+ clearTimeout(this.postPromptCompletionIdleTimeout);
5516
+ this.postPromptCompletionIdleTimeout = null;
5517
+ }
5518
+ }
5458
5519
  clearToolCallTracking() {
5459
5520
  this.activeToolCalls.clear();
5460
5521
  for (const timeout of this.toolCallTimeouts.values()) {
@@ -5468,9 +5529,26 @@ class AcpBackend {
5468
5529
  }
5469
5530
  resetResponseTrackingForNewPrompt() {
5470
5531
  this.responseCompletionOutcome = null;
5532
+ this.responseCompletionError = null;
5533
+ this.sawSessionUpdateSincePrompt = false;
5471
5534
  this.clearIdleTimeoutState();
5535
+ this.clearPostPromptCompletionIdleTimeout();
5472
5536
  this.clearToolCallTracking();
5473
5537
  }
5538
+ failPendingResponseWait(error) {
5539
+ if (this.responseCompletionError) {
5540
+ return;
5541
+ }
5542
+ this.responseCompletionError = error;
5543
+ this.responseCompletionOutcome = null;
5544
+ this.waitingForResponse = false;
5545
+ this.clearPostPromptCompletionIdleTimeout();
5546
+ if (this.idleRejecter) {
5547
+ this.idleRejecter(error);
5548
+ }
5549
+ this.idleResolver = null;
5550
+ this.idleRejecter = null;
5551
+ }
5474
5552
  settleResponseWaiter(outcome) {
5475
5553
  const hasActiveWaiter = Boolean(this.idleResolver || this.idleRejecter);
5476
5554
  if (!this.waitingForResponse && !hasActiveWaiter) {
@@ -5506,6 +5584,38 @@ class AcpBackend {
5506
5584
  }
5507
5585
  }
5508
5586
  }
5587
+ handleDroppedStdoutLine(entry) {
5588
+ if (entry.reason !== "transport_filter_null" || !this.waitingForResponse || this.responseCompletionError) {
5589
+ return;
5590
+ }
5591
+ const raw = entry.line.trim();
5592
+ if (!raw) {
5593
+ return;
5594
+ }
5595
+ const context = {
5596
+ activeToolCalls: this.activeToolCalls,
5597
+ hasActiveInvestigation: this.transport.isInvestigationTool ? Array.from(this.activeToolCalls).some((id) => this.transport.isInvestigationTool(id)) : false
5598
+ };
5599
+ const transportResult = this.transport.handleStderr?.(entry.line, context);
5600
+ const transportMessage = transportResult?.message ?? null;
5601
+ if (transportMessage) {
5602
+ this.emit(transportMessage);
5603
+ const errorDetail = getStatusErrorDetail(transportMessage);
5604
+ if (errorDetail) {
5605
+ this.failPendingResponseWait(new Error(errorDetail));
5606
+ }
5607
+ return;
5608
+ }
5609
+ if (!looksLikeDroppedStdoutError(raw)) {
5610
+ return;
5611
+ }
5612
+ this.emit({
5613
+ type: "status",
5614
+ status: "error",
5615
+ detail: raw
5616
+ });
5617
+ this.failPendingResponseWait(new Error(raw));
5618
+ }
5509
5619
  async startSession(initialPrompt) {
5510
5620
  if (this.disposed) {
5511
5621
  throw new Error("Backend has been disposed");
@@ -5545,20 +5655,27 @@ class AcpBackend {
5545
5655
  const result = this.transport.handleStderr(text, context);
5546
5656
  if (result.message) {
5547
5657
  this.emit(result.message);
5658
+ const errorDetail = getStatusErrorDetail(result.message);
5659
+ if (errorDetail && this.waitingForResponse) {
5660
+ this.failPendingResponseWait(new Error(errorDetail));
5661
+ }
5548
5662
  }
5549
5663
  }
5550
5664
  });
5551
5665
  this.process.on("error", (err) => {
5552
5666
  api.logger.debug(`[AcpBackend] Process error:`, err);
5553
- this.settleResponseWaiter({ kind: "rejected", error: err });
5667
+ if (this.waitingForResponse) {
5668
+ this.failPendingResponseWait(err);
5669
+ }
5554
5670
  this.emit({ type: "status", status: "error", detail: err.message });
5555
5671
  });
5556
5672
  this.process.on("exit", (code, signal) => {
5557
5673
  if (!this.disposed && code !== 0 && code !== null) {
5558
- this.settleResponseWaiter({
5559
- kind: "rejected",
5560
- error: new Error(`ACP process exited with code ${code}${signal ? ` (${signal})` : ""}`)
5561
- });
5674
+ if (this.waitingForResponse) {
5675
+ this.failPendingResponseWait(
5676
+ new Error(`ACP process exited with code ${code}${signal ? ` (${signal})` : ""}`)
5677
+ );
5678
+ }
5562
5679
  api.logger.debug(`[AcpBackend] Process exited with code ${code}, signal ${signal}`);
5563
5680
  this.emit({ type: "status", status: "stopped", detail: `Exit code: ${code}` });
5564
5681
  }
@@ -5575,14 +5692,17 @@ class AcpBackend {
5575
5692
  transport: this.transport,
5576
5693
  onDroppedLine: (entry) => {
5577
5694
  droppedStdoutLines.push(entry);
5695
+ this.handleDroppedStdoutLine(entry);
5696
+ },
5697
+ onDone: () => {
5698
+ if (droppedStdoutLines.length > 0) {
5699
+ api.logger.debug(
5700
+ `[AcpBackend] Filtered out ${droppedStdoutLines.length} stdout lines from ${this.transport.agentName}`,
5701
+ droppedStdoutLines.slice(0, 5)
5702
+ );
5703
+ }
5578
5704
  }
5579
5705
  });
5580
- if (droppedStdoutLines.length > 0) {
5581
- api.logger.debug(
5582
- `[AcpBackend] Filtered out ${droppedStdoutLines.length} stdout lines from ${this.transport.agentName}`,
5583
- droppedStdoutLines.slice(0, 5)
5584
- );
5585
- }
5586
5706
  const stream = sdk.ndJsonStream(writable, filteredReadable);
5587
5707
  const client = {
5588
5708
  sessionUpdate: async (params) => {
@@ -5744,6 +5864,11 @@ class AcpBackend {
5744
5864
  }
5745
5865
  };
5746
5866
  const initTimeout = this.transport.getInitTimeout();
5867
+ const initDelayMs = this.transport.getInitDelayMs?.() ?? 0;
5868
+ if (initDelayMs > 0) {
5869
+ api.logger.debug(`[AcpBackend] Waiting ${initDelayMs}ms before initialize (${this.transport.agentName})...`);
5870
+ await api.delay(initDelayMs);
5871
+ }
5747
5872
  api.logger.debug(`[AcpBackend] Initializing connection (timeout: ${initTimeout}ms)...`);
5748
5873
  await withRetry(
5749
5874
  async () => {
@@ -5849,6 +5974,16 @@ class AcpBackend {
5849
5974
  return { sessionId };
5850
5975
  } catch (error) {
5851
5976
  const enrichedError = enrichAcpError(error, this.getRecentStderrExcerpt());
5977
+ if (this.process) {
5978
+ try {
5979
+ await killProcessTree(this.process, { graceMs: 250 });
5980
+ } catch {
5981
+ } finally {
5982
+ this.process = null;
5983
+ }
5984
+ }
5985
+ this.connection = null;
5986
+ this.acpSessionId = null;
5852
5987
  api.logger.debug("[AcpBackend] Error starting session:", enrichedError);
5853
5988
  this.emit({
5854
5989
  type: "status",
@@ -5909,6 +6044,8 @@ class AcpBackend {
5909
6044
  api.logger.debug("[AcpBackend] Received session update without update field:", params);
5910
6045
  return;
5911
6046
  }
6047
+ this.sawSessionUpdateSincePrompt = true;
6048
+ this.clearPostPromptCompletionIdleTimeout();
5912
6049
  for (const update of updates) {
5913
6050
  const sessionUpdateType = update.sessionUpdate;
5914
6051
  if (sessionUpdateType !== "agent_message_chunk") {
@@ -5988,6 +6125,29 @@ class AcpBackend {
5988
6125
  api.logger.debug(`[AcpBackend] Prompt request:`, JSON.stringify(promptRequest, null, 2));
5989
6126
  await this.connection.prompt(promptRequest);
5990
6127
  api.logger.debug("[AcpBackend] Prompt request sent to ACP connection");
6128
+ if (this.waitingForResponse && this.activeToolCalls.size === 0 && this.sawSessionUpdateSincePrompt === false) {
6129
+ const noUpdatesTimeoutMs = resolvePostPromptNoUpdatesTimeoutMs(this.transport);
6130
+ this.postPromptCompletionIdleTimeout = setTimeout(() => {
6131
+ this.postPromptCompletionIdleTimeout = null;
6132
+ if (this.responseCompletionError || !this.waitingForResponse) {
6133
+ return;
6134
+ }
6135
+ if (this.sawSessionUpdateSincePrompt || this.activeToolCalls.size > 0) {
6136
+ return;
6137
+ }
6138
+ const exitCode = this.process?.exitCode;
6139
+ if (typeof exitCode === "number" && Number.isFinite(exitCode) && exitCode !== 0) {
6140
+ this.failPendingResponseWait(new Error(`Exit code: ${exitCode}`));
6141
+ return;
6142
+ }
6143
+ const signalCode = this.process?.signalCode;
6144
+ if (typeof signalCode === "string" && signalCode.trim().length > 0) {
6145
+ this.failPendingResponseWait(new Error(`Signal: ${signalCode}`));
6146
+ return;
6147
+ }
6148
+ this.emitIdleStatus();
6149
+ }, Math.max(100, noUpdatesTimeoutMs));
6150
+ }
5991
6151
  } catch (error) {
5992
6152
  api.logger.debug("[AcpBackend] Error sending prompt:", error);
5993
6153
  let errorDetail;
@@ -6003,10 +6163,7 @@ class AcpBackend {
6003
6163
  status: "error",
6004
6164
  detail: errorDetail
6005
6165
  });
6006
- this.settleResponseWaiter({
6007
- kind: "rejected",
6008
- error: error instanceof Error ? error : normalizeAcpError(error)
6009
- });
6166
+ this.failPendingResponseWait(error instanceof Error ? error : normalizeAcpError(error));
6010
6167
  throw error;
6011
6168
  }
6012
6169
  }
@@ -6015,6 +6172,9 @@ class AcpBackend {
6015
6172
  * Call this after sendPrompt to wait for Gemini to finish responding
6016
6173
  */
6017
6174
  async waitForResponseComplete(timeoutMs = 12e4) {
6175
+ if (this.responseCompletionError) {
6176
+ throw this.responseCompletionError;
6177
+ }
6018
6178
  const pendingOutcome = this.responseCompletionOutcome;
6019
6179
  if (pendingOutcome) {
6020
6180
  this.responseCompletionOutcome = null;
@@ -6053,12 +6213,14 @@ class AcpBackend {
6053
6213
  * Helper to emit idle status and resolve any waiting promises
6054
6214
  */
6055
6215
  emitIdleStatus() {
6216
+ this.clearPostPromptCompletionIdleTimeout();
6056
6217
  this.emit({ type: "status", status: "idle" });
6057
6218
  this.settleResponseWaiter({ kind: "resolved" });
6058
6219
  }
6059
6220
  async cancel(sessionId) {
6060
6221
  const cancelError = createAcpAbortError("Cancelled by user");
6061
6222
  this.clearIdleTimeoutState();
6223
+ this.clearPostPromptCompletionIdleTimeout();
6062
6224
  this.clearToolCallTracking();
6063
6225
  this.settleResponseWaiter({ kind: "rejected", error: cancelError });
6064
6226
  this.emit({ type: "status", status: "stopped", detail: "Cancelled by user" });
@@ -6119,6 +6281,7 @@ class AcpBackend {
6119
6281
  }
6120
6282
  }
6121
6283
  this.clearIdleTimeoutState();
6284
+ this.clearPostPromptCompletionIdleTimeout();
6122
6285
  this.listeners = [];
6123
6286
  this.connection = null;
6124
6287
  this.acpSessionId = null;
@@ -6767,12 +6930,12 @@ async function ensureUnifiedDaemonStarted() {
6767
6930
  async function executeUnifiedProvider(opts) {
6768
6931
  const credentials = await ensureUnifiedRuntimePrerequisites(opts.credentials);
6769
6932
  if (opts.provider === "claude") {
6770
- const { runClaude } = await Promise.resolve().then(function () { return require('./runClaude-CZmJ7qEP.cjs'); });
6933
+ const { runClaude } = await Promise.resolve().then(function () { return require('./runClaude-CPhWaFrX.cjs'); });
6771
6934
  await runClaude(credentials, opts.claudeOptions ?? {});
6772
6935
  return;
6773
6936
  }
6774
6937
  if (opts.provider === "codex") {
6775
- const { runCodex } = await Promise.resolve().then(function () { return require('./runCodex-CtncAgso.cjs'); });
6938
+ const { runCodex } = await Promise.resolve().then(function () { return require('./runCodex--QLrOs8X.cjs'); });
6776
6939
  await runCodex({
6777
6940
  credentials,
6778
6941
  startedBy: opts.startedBy,
@@ -6782,7 +6945,7 @@ async function executeUnifiedProvider(opts) {
6782
6945
  return;
6783
6946
  }
6784
6947
  if (opts.provider === "gemini") {
6785
- const { runGemini } = await Promise.resolve().then(function () { return require('./runGemini-ChwjLmhI.cjs'); });
6948
+ const { runGemini } = await Promise.resolve().then(function () { return require('./runGemini-CDyhCucw.cjs'); });
6786
6949
  await runGemini({
6787
6950
  credentials,
6788
6951
  startedBy: opts.startedBy
@@ -6824,7 +6987,7 @@ function shouldRunMainClaudeFlow(opts) {
6824
6987
  return;
6825
6988
  } else if (subcommand === "runtime") {
6826
6989
  if (args[1] === "providers") {
6827
- const { renderRuntimeProviders } = await Promise.resolve().then(function () { return require('./command-DiAVIsxX.cjs'); });
6990
+ const { renderRuntimeProviders } = await Promise.resolve().then(function () { return require('./command-DjIfRZQS.cjs'); });
6828
6991
  console.log(renderRuntimeProviders());
6829
6992
  return;
6830
6993
  }
@@ -7002,8 +7165,8 @@ function shouldRunMainClaudeFlow(opts) {
7002
7165
  const projectId = args[3];
7003
7166
  try {
7004
7167
  const { saveGoogleCloudProjectToConfig } = await Promise.resolve().then(function () { return config; });
7005
- const { readCredentials: readCredentials2 } = await Promise.resolve().then(function () { return require('./persistence-hbhwAYIV.cjs'); });
7006
- const { ApiClient: ApiClient2 } = await Promise.resolve().then(function () { return require('./api-Dwkm7s_E.cjs'); }).then(function (n) { return n.api; });
7168
+ const { readCredentials: readCredentials2 } = await Promise.resolve().then(function () { return require('./persistence-DBGkO8gB.cjs'); });
7169
+ const { ApiClient: ApiClient2 } = await Promise.resolve().then(function () { return require('./api-CN-WqYd_.cjs'); }).then(function (n) { return n.api; });
7007
7170
  let userEmail = void 0;
7008
7171
  try {
7009
7172
  const credentials = await readCredentials2();