happy-imou-cloud 2.0.7 → 2.0.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 (26) hide show
  1. package/bin/happy-cloud.mjs +1 -1
  2. package/dist/{api-Dwkm7s_E.cjs → api-CUTdFiFP.cjs} +3 -4
  3. package/dist/{api-dwwHBzLc.mjs → api-CnvyGas2.mjs} +3 -4
  4. package/dist/{command-Cfq3Uc0S.mjs → command-BGA3qCKR.mjs} +3 -3
  5. package/dist/{command-DiAVIsxX.cjs → command-DLAJZsKX.cjs} +3 -3
  6. package/dist/{index-HyqLXzw-.mjs → index-BpZL4RcT.mjs} +208 -32
  7. package/dist/{index-CfqxEoyl.cjs → index-D4OdFq68.cjs} +210 -34
  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-C9iJODqA.mjs} +98 -3
  13. package/dist/{BaseReasoningProcessor-DphULXS-.cjs → names-YEhZwVT0.cjs} +100 -2
  14. package/dist/{persistence-Dg-rxY2a.mjs → persistence-BPV3AmJL.mjs} +100 -4
  15. package/dist/{persistence-hbhwAYIV.cjs → persistence-CxvL0cwp.cjs} +110 -1
  16. package/dist/{registerKillSessionHandler-BAvk4GYO.mjs → registerKillSessionHandler-C2O8b5wH.mjs} +2 -2
  17. package/dist/{registerKillSessionHandler-D1ouN10n.cjs → registerKillSessionHandler-rqd7duc9.cjs} +2 -2
  18. package/dist/{runClaude-OxYbt3ZQ.mjs → runClaude-8inO7C5p.mjs} +4 -4
  19. package/dist/{runClaude-CZmJ7qEP.cjs → runClaude-KwIVwFp1.cjs} +5 -5
  20. package/dist/{runCodex-ByVTEbSY.mjs → runCodex-BQ-fN5E6.mjs} +5 -6
  21. package/dist/{runCodex-CtncAgso.cjs → runCodex-Ba8COxZe.cjs} +24 -25
  22. package/dist/{runGemini-BRO6A2jm.mjs → runGemini-BE0FizuV.mjs} +5 -6
  23. package/dist/{runGemini-ChwjLmhI.cjs → runGemini-DtdLLX9o.cjs} +20 -21
  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.9";
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-CxvL0cwp.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.9";
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-BPV3AmJL.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-BpZL4RcT.mjs';
2
2
  import 'chalk';
3
- import './api-dwwHBzLc.mjs';
3
+ import './api-CnvyGas2.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-BPV3AmJL.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-D4OdFq68.cjs');
4
4
  require('chalk');
5
- require('./api-Dwkm7s_E.cjs');
5
+ require('./api-CUTdFiFP.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-CxvL0cwp.cjs');
23
23
  require('node:fs/promises');
24
24
  require('os');
25
25
  require('tmp');
@@ -1,6 +1,6 @@
1
1
  import{createRequire as _pkgrollCR}from"node:module";const require=_pkgrollCR(import.meta.url);import chalk from 'chalk';
2
- import { l as logger, e as encodeBase64, c as configuration, h as buildAuthenticatedHeaders, S as SigningBootstrapRequiredError, j as SIGNING_BOOTSTRAP_REQUIRED_MESSAGE, k as encodeBase64Url, f as delay, m as buildClientHeaders, n as decodeBase64, H as HAPPY_CLOUD_DAEMON_PORT, p as packageJson, A as ApiClient, o as getLatestDaemonLog } from './api-dwwHBzLc.mjs';
3
- import { writeCredentialsLegacy, writeCredentialsDataKey, readCredentials, readSettings, updateSettings, readDaemonState, clearDaemonState, acquireDaemonLock, writeDaemonState, releaseDaemonLock, validateProfileForAgent, getProfileEnvironmentVariables, clearCredentials, clearMachineId } from './persistence-Dg-rxY2a.mjs';
2
+ import { l as logger, e as encodeBase64, c as configuration, h as buildAuthenticatedHeaders, S as SigningBootstrapRequiredError, j as SIGNING_BOOTSTRAP_REQUIRED_MESSAGE, k as encodeBase64Url, f as delay, m as buildClientHeaders, n as decodeBase64, H as HAPPY_CLOUD_DAEMON_PORT, p as packageJson, A as ApiClient, o as getLatestDaemonLog } from './api-CnvyGas2.mjs';
3
+ import { writeCredentialsLegacy, writeCredentialsDataKey, readCredentials, readSettings, updateSettings, readDaemonState, clearDaemonState, acquireDaemonLock, writeDaemonState, releaseDaemonLock, validateProfileForAgent, getProfileEnvironmentVariables, clearCredentials, clearMachineId } from './persistence-BPV3AmJL.mjs';
4
4
  import { z } from 'zod';
5
5
  import fs from 'fs/promises';
6
6
  import os, { homedir } from 'os';
@@ -1108,6 +1108,21 @@ function getEnvironmentInfo() {
1108
1108
  terminal: process.env.TERM
1109
1109
  };
1110
1110
  }
1111
+ function resolveDaemonSpawnDiagnostics(projectRoot, fileExists = existsSync) {
1112
+ const wrapperCandidates = [
1113
+ join(projectRoot, "bin", "happy-cloud.mjs"),
1114
+ join(projectRoot, "bin", "happy.mjs")
1115
+ ];
1116
+ const cliEntrypoint = join(projectRoot, "dist", "index.mjs");
1117
+ const wrapperPath = wrapperCandidates.find((candidate) => fileExists(candidate)) ?? wrapperCandidates[0];
1118
+ return {
1119
+ projectRoot,
1120
+ wrapperPath,
1121
+ cliEntrypoint,
1122
+ wrapperExists: fileExists(wrapperPath),
1123
+ cliEntrypointExists: fileExists(cliEntrypoint)
1124
+ };
1125
+ }
1111
1126
  function getLogFiles(logDir) {
1112
1127
  if (!existsSync(logDir)) {
1113
1128
  return [];
@@ -1134,14 +1149,12 @@ async function runDoctorCommand(filter) {
1134
1149
  console.log(`Node.js Version: ${chalk.green(process.version)}`);
1135
1150
  console.log("");
1136
1151
  console.log(chalk.bold("\u{1F527} Daemon Spawn Diagnostics"));
1137
- const projectRoot = projectPath();
1138
- const wrapperPath = join(projectRoot, "bin", "happy.mjs");
1139
- const cliEntrypoint = join(projectRoot, "dist", "index.mjs");
1140
- console.log(`Project Root: ${chalk.blue(projectRoot)}`);
1141
- console.log(`Wrapper Script: ${chalk.blue(wrapperPath)}`);
1142
- console.log(`CLI Entrypoint: ${chalk.blue(cliEntrypoint)}`);
1143
- console.log(`Wrapper Exists: ${existsSync(wrapperPath) ? chalk.green("\u2713 Yes") : chalk.red("\u274C No")}`);
1144
- console.log(`CLI Exists: ${existsSync(cliEntrypoint) ? chalk.green("\u2713 Yes") : chalk.red("\u274C No")}`);
1152
+ const diagnostics = resolveDaemonSpawnDiagnostics(projectPath());
1153
+ console.log(`Project Root: ${chalk.blue(diagnostics.projectRoot)}`);
1154
+ console.log(`Wrapper Script: ${chalk.blue(diagnostics.wrapperPath)}`);
1155
+ console.log(`CLI Entrypoint: ${chalk.blue(diagnostics.cliEntrypoint)}`);
1156
+ console.log(`Wrapper Exists: ${diagnostics.wrapperExists ? chalk.green("\u2713 Yes") : chalk.red("\u274C No")}`);
1157
+ console.log(`CLI Exists: ${diagnostics.cliEntrypointExists ? chalk.green("\u2713 Yes") : chalk.red("\u274C No")}`);
1145
1158
  console.log("");
1146
1159
  console.log(chalk.bold("\u2699\uFE0F Configuration"));
1147
1160
  console.log(`Happy Home: ${chalk.blue(configuration.happyCloudHomeDir)}`);
@@ -4191,6 +4204,9 @@ class DefaultTransport {
4191
4204
  getInitTimeout() {
4192
4205
  return DEFAULT_TIMEOUTS.init;
4193
4206
  }
4207
+ getInitDelayMs() {
4208
+ return 0;
4209
+ }
4194
4210
  /**
4195
4211
  * Default: pass through all lines that are valid JSON objects/arrays
4196
4212
  */
@@ -4251,11 +4267,16 @@ class DefaultTransport {
4251
4267
  determineToolName(toolName, _toolCallId, _input, _context) {
4252
4268
  return toolName;
4253
4269
  }
4270
+ getPostPromptNoUpdatesTimeoutMs() {
4271
+ return 3e4;
4272
+ }
4254
4273
  }
4255
4274
 
4256
4275
  const GEMINI_TIMEOUTS = {
4257
4276
  /** Gemini CLI can be slow on first start (downloading models, etc.) */
4258
4277
  init: 12e4,
4278
+ /** Gemini ACP can swallow an initialize request sent too early after spawn */
4279
+ initDelay: 2500,
4259
4280
  /** Standard tool call timeout */
4260
4281
  toolCall: 12e4,
4261
4282
  /** Investigation tools (codebase_investigator) can run for a long time */
@@ -4290,6 +4311,9 @@ class GeminiTransport {
4290
4311
  getInitTimeout() {
4291
4312
  return GEMINI_TIMEOUTS.init;
4292
4313
  }
4314
+ getInitDelayMs() {
4315
+ return GEMINI_TIMEOUTS.initDelay;
4316
+ }
4293
4317
  /**
4294
4318
  * Filter Gemini CLI debug output from stdout.
4295
4319
  *
@@ -5026,6 +5050,10 @@ ${line}` : line;
5026
5050
  controller.error(error);
5027
5051
  } finally {
5028
5052
  reader.releaseLock();
5053
+ try {
5054
+ params.onDone?.();
5055
+ } catch {
5056
+ }
5029
5057
  if (!controllerErrored) {
5030
5058
  controller.close();
5031
5059
  }
@@ -5088,7 +5116,7 @@ async function killProcessTree(proc, options) {
5088
5116
  if (!pid) {
5089
5117
  return;
5090
5118
  }
5091
- const graceMs = Math.max(1, options?.graceMs);
5119
+ const graceMs = Math.max(1, options?.graceMs ?? 1e3);
5092
5120
  const descendants = await resolveDescendantPids(pid).catch(() => []);
5093
5121
  const allPids = [...descendants, pid];
5094
5122
  for (const targetPid of allPids) {
@@ -5113,6 +5141,29 @@ const RETRY_CONFIG = {
5113
5141
  /** Maximum delay between retries in ms */
5114
5142
  maxDelayMs: 5e3
5115
5143
  };
5144
+ const DEFAULT_POST_PROMPT_NO_UPDATES_TIMEOUT_MS = 3e4;
5145
+ function readPositiveIntegerEnv(name) {
5146
+ const raw = typeof process.env[name] === "string" ? process.env[name].trim() : "";
5147
+ if (!raw) {
5148
+ return null;
5149
+ }
5150
+ const value = Number(raw);
5151
+ if (!Number.isFinite(value) || !Number.isInteger(value) || value <= 0) {
5152
+ return null;
5153
+ }
5154
+ return value;
5155
+ }
5156
+ function resolvePostPromptNoUpdatesTimeoutMs(transport) {
5157
+ const transportValue = transport.getPostPromptNoUpdatesTimeoutMs?.();
5158
+ if (typeof transportValue === "number" && Number.isFinite(transportValue) && transportValue > 0) {
5159
+ return Math.trunc(transportValue);
5160
+ }
5161
+ const envValue = readPositiveIntegerEnv("HAPPY_ACP_POST_PROMPT_NO_UPDATES_TIMEOUT_MS") ?? readPositiveIntegerEnv("HAPPIER_ACP_POST_PROMPT_NO_UPDATES_TIMEOUT_MS");
5162
+ if (envValue != null) {
5163
+ return envValue;
5164
+ }
5165
+ return DEFAULT_POST_PROMPT_NO_UPDATES_TIMEOUT_MS;
5166
+ }
5116
5167
  function getSessionUpdates(params) {
5117
5168
  const notification = params;
5118
5169
  const updates = Array.isArray(notification.updates) ? notification.updates.filter((update) => Boolean(update && typeof update === "object")) : [];
@@ -5288,6 +5339,17 @@ function normalizeAcpError(error) {
5288
5339
  }
5289
5340
  return normalized;
5290
5341
  }
5342
+ function getStatusErrorDetail(message) {
5343
+ if (message.type !== "status" || message.status !== "error") {
5344
+ return null;
5345
+ }
5346
+ const detail = typeof message.detail === "string" ? message.detail.trim() : "";
5347
+ return detail || "Unknown ACP transport error";
5348
+ }
5349
+ function looksLikeDroppedStdoutError(text) {
5350
+ const lower = text.toLowerCase();
5351
+ 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"));
5352
+ }
5291
5353
  function createAcpAbortError(message) {
5292
5354
  const error = new Error(message);
5293
5355
  error.name = "AbortError";
@@ -5410,6 +5472,12 @@ class AcpBackend {
5410
5472
  responseCompletionOutcome = null;
5411
5473
  /** Whether the current prompt is still waiting for completion */
5412
5474
  waitingForResponse = false;
5475
+ /** First fatal prompt-level error observed for the current turn */
5476
+ responseCompletionError = null;
5477
+ /** Fallback completion when prompt returns but the agent emits no session updates */
5478
+ postPromptCompletionIdleTimeout = null;
5479
+ /** Whether at least one session/update arrived after the current prompt */
5480
+ sawSessionUpdateSincePrompt = false;
5413
5481
  /** Transport handler for agent-specific behavior */
5414
5482
  transport;
5415
5483
  /** Keep a short rolling stderr buffer so startup failures can surface the real cause. */
@@ -5433,6 +5501,12 @@ class AcpBackend {
5433
5501
  this.idleTimeout = null;
5434
5502
  }
5435
5503
  }
5504
+ clearPostPromptCompletionIdleTimeout() {
5505
+ if (this.postPromptCompletionIdleTimeout) {
5506
+ clearTimeout(this.postPromptCompletionIdleTimeout);
5507
+ this.postPromptCompletionIdleTimeout = null;
5508
+ }
5509
+ }
5436
5510
  clearToolCallTracking() {
5437
5511
  this.activeToolCalls.clear();
5438
5512
  for (const timeout of this.toolCallTimeouts.values()) {
@@ -5446,9 +5520,26 @@ class AcpBackend {
5446
5520
  }
5447
5521
  resetResponseTrackingForNewPrompt() {
5448
5522
  this.responseCompletionOutcome = null;
5523
+ this.responseCompletionError = null;
5524
+ this.sawSessionUpdateSincePrompt = false;
5449
5525
  this.clearIdleTimeoutState();
5526
+ this.clearPostPromptCompletionIdleTimeout();
5450
5527
  this.clearToolCallTracking();
5451
5528
  }
5529
+ failPendingResponseWait(error) {
5530
+ if (this.responseCompletionError) {
5531
+ return;
5532
+ }
5533
+ this.responseCompletionError = error;
5534
+ this.responseCompletionOutcome = null;
5535
+ this.waitingForResponse = false;
5536
+ this.clearPostPromptCompletionIdleTimeout();
5537
+ if (this.idleRejecter) {
5538
+ this.idleRejecter(error);
5539
+ }
5540
+ this.idleResolver = null;
5541
+ this.idleRejecter = null;
5542
+ }
5452
5543
  settleResponseWaiter(outcome) {
5453
5544
  const hasActiveWaiter = Boolean(this.idleResolver || this.idleRejecter);
5454
5545
  if (!this.waitingForResponse && !hasActiveWaiter) {
@@ -5484,6 +5575,38 @@ class AcpBackend {
5484
5575
  }
5485
5576
  }
5486
5577
  }
5578
+ handleDroppedStdoutLine(entry) {
5579
+ if (entry.reason !== "transport_filter_null" || !this.waitingForResponse || this.responseCompletionError) {
5580
+ return;
5581
+ }
5582
+ const raw = entry.line.trim();
5583
+ if (!raw) {
5584
+ return;
5585
+ }
5586
+ const context = {
5587
+ activeToolCalls: this.activeToolCalls,
5588
+ hasActiveInvestigation: this.transport.isInvestigationTool ? Array.from(this.activeToolCalls).some((id) => this.transport.isInvestigationTool(id)) : false
5589
+ };
5590
+ const transportResult = this.transport.handleStderr?.(entry.line, context);
5591
+ const transportMessage = transportResult?.message ?? null;
5592
+ if (transportMessage) {
5593
+ this.emit(transportMessage);
5594
+ const errorDetail = getStatusErrorDetail(transportMessage);
5595
+ if (errorDetail) {
5596
+ this.failPendingResponseWait(new Error(errorDetail));
5597
+ }
5598
+ return;
5599
+ }
5600
+ if (!looksLikeDroppedStdoutError(raw)) {
5601
+ return;
5602
+ }
5603
+ this.emit({
5604
+ type: "status",
5605
+ status: "error",
5606
+ detail: raw
5607
+ });
5608
+ this.failPendingResponseWait(new Error(raw));
5609
+ }
5487
5610
  async startSession(initialPrompt) {
5488
5611
  if (this.disposed) {
5489
5612
  throw new Error("Backend has been disposed");
@@ -5523,20 +5646,27 @@ class AcpBackend {
5523
5646
  const result = this.transport.handleStderr(text, context);
5524
5647
  if (result.message) {
5525
5648
  this.emit(result.message);
5649
+ const errorDetail = getStatusErrorDetail(result.message);
5650
+ if (errorDetail && this.waitingForResponse) {
5651
+ this.failPendingResponseWait(new Error(errorDetail));
5652
+ }
5526
5653
  }
5527
5654
  }
5528
5655
  });
5529
5656
  this.process.on("error", (err) => {
5530
5657
  logger.debug(`[AcpBackend] Process error:`, err);
5531
- this.settleResponseWaiter({ kind: "rejected", error: err });
5658
+ if (this.waitingForResponse) {
5659
+ this.failPendingResponseWait(err);
5660
+ }
5532
5661
  this.emit({ type: "status", status: "error", detail: err.message });
5533
5662
  });
5534
5663
  this.process.on("exit", (code, signal) => {
5535
5664
  if (!this.disposed && code !== 0 && code !== null) {
5536
- this.settleResponseWaiter({
5537
- kind: "rejected",
5538
- error: new Error(`ACP process exited with code ${code}${signal ? ` (${signal})` : ""}`)
5539
- });
5665
+ if (this.waitingForResponse) {
5666
+ this.failPendingResponseWait(
5667
+ new Error(`ACP process exited with code ${code}${signal ? ` (${signal})` : ""}`)
5668
+ );
5669
+ }
5540
5670
  logger.debug(`[AcpBackend] Process exited with code ${code}, signal ${signal}`);
5541
5671
  this.emit({ type: "status", status: "stopped", detail: `Exit code: ${code}` });
5542
5672
  }
@@ -5553,14 +5683,17 @@ class AcpBackend {
5553
5683
  transport: this.transport,
5554
5684
  onDroppedLine: (entry) => {
5555
5685
  droppedStdoutLines.push(entry);
5686
+ this.handleDroppedStdoutLine(entry);
5687
+ },
5688
+ onDone: () => {
5689
+ if (droppedStdoutLines.length > 0) {
5690
+ logger.debug(
5691
+ `[AcpBackend] Filtered out ${droppedStdoutLines.length} stdout lines from ${this.transport.agentName}`,
5692
+ droppedStdoutLines.slice(0, 5)
5693
+ );
5694
+ }
5556
5695
  }
5557
5696
  });
5558
- if (droppedStdoutLines.length > 0) {
5559
- logger.debug(
5560
- `[AcpBackend] Filtered out ${droppedStdoutLines.length} stdout lines from ${this.transport.agentName}`,
5561
- droppedStdoutLines.slice(0, 5)
5562
- );
5563
- }
5564
5697
  const stream = ndJsonStream(writable, filteredReadable);
5565
5698
  const client = {
5566
5699
  sessionUpdate: async (params) => {
@@ -5722,6 +5855,11 @@ class AcpBackend {
5722
5855
  }
5723
5856
  };
5724
5857
  const initTimeout = this.transport.getInitTimeout();
5858
+ const initDelayMs = this.transport.getInitDelayMs?.() ?? 0;
5859
+ if (initDelayMs > 0) {
5860
+ logger.debug(`[AcpBackend] Waiting ${initDelayMs}ms before initialize (${this.transport.agentName})...`);
5861
+ await delay(initDelayMs);
5862
+ }
5725
5863
  logger.debug(`[AcpBackend] Initializing connection (timeout: ${initTimeout}ms)...`);
5726
5864
  await withRetry(
5727
5865
  async () => {
@@ -5827,6 +5965,16 @@ class AcpBackend {
5827
5965
  return { sessionId };
5828
5966
  } catch (error) {
5829
5967
  const enrichedError = enrichAcpError(error, this.getRecentStderrExcerpt());
5968
+ if (this.process) {
5969
+ try {
5970
+ await killProcessTree(this.process, { graceMs: 250 });
5971
+ } catch {
5972
+ } finally {
5973
+ this.process = null;
5974
+ }
5975
+ }
5976
+ this.connection = null;
5977
+ this.acpSessionId = null;
5830
5978
  logger.debug("[AcpBackend] Error starting session:", enrichedError);
5831
5979
  this.emit({
5832
5980
  type: "status",
@@ -5887,6 +6035,8 @@ class AcpBackend {
5887
6035
  logger.debug("[AcpBackend] Received session update without update field:", params);
5888
6036
  return;
5889
6037
  }
6038
+ this.sawSessionUpdateSincePrompt = true;
6039
+ this.clearPostPromptCompletionIdleTimeout();
5890
6040
  for (const update of updates) {
5891
6041
  const sessionUpdateType = update.sessionUpdate;
5892
6042
  if (sessionUpdateType !== "agent_message_chunk") {
@@ -5966,6 +6116,29 @@ class AcpBackend {
5966
6116
  logger.debug(`[AcpBackend] Prompt request:`, JSON.stringify(promptRequest, null, 2));
5967
6117
  await this.connection.prompt(promptRequest);
5968
6118
  logger.debug("[AcpBackend] Prompt request sent to ACP connection");
6119
+ if (this.waitingForResponse && this.activeToolCalls.size === 0 && this.sawSessionUpdateSincePrompt === false) {
6120
+ const noUpdatesTimeoutMs = resolvePostPromptNoUpdatesTimeoutMs(this.transport);
6121
+ this.postPromptCompletionIdleTimeout = setTimeout(() => {
6122
+ this.postPromptCompletionIdleTimeout = null;
6123
+ if (this.responseCompletionError || !this.waitingForResponse) {
6124
+ return;
6125
+ }
6126
+ if (this.sawSessionUpdateSincePrompt || this.activeToolCalls.size > 0) {
6127
+ return;
6128
+ }
6129
+ const exitCode = this.process?.exitCode;
6130
+ if (typeof exitCode === "number" && Number.isFinite(exitCode) && exitCode !== 0) {
6131
+ this.failPendingResponseWait(new Error(`Exit code: ${exitCode}`));
6132
+ return;
6133
+ }
6134
+ const signalCode = this.process?.signalCode;
6135
+ if (typeof signalCode === "string" && signalCode.trim().length > 0) {
6136
+ this.failPendingResponseWait(new Error(`Signal: ${signalCode}`));
6137
+ return;
6138
+ }
6139
+ this.emitIdleStatus();
6140
+ }, Math.max(100, noUpdatesTimeoutMs));
6141
+ }
5969
6142
  } catch (error) {
5970
6143
  logger.debug("[AcpBackend] Error sending prompt:", error);
5971
6144
  let errorDetail;
@@ -5981,10 +6154,7 @@ class AcpBackend {
5981
6154
  status: "error",
5982
6155
  detail: errorDetail
5983
6156
  });
5984
- this.settleResponseWaiter({
5985
- kind: "rejected",
5986
- error: error instanceof Error ? error : normalizeAcpError(error)
5987
- });
6157
+ this.failPendingResponseWait(error instanceof Error ? error : normalizeAcpError(error));
5988
6158
  throw error;
5989
6159
  }
5990
6160
  }
@@ -5993,6 +6163,9 @@ class AcpBackend {
5993
6163
  * Call this after sendPrompt to wait for Gemini to finish responding
5994
6164
  */
5995
6165
  async waitForResponseComplete(timeoutMs = 12e4) {
6166
+ if (this.responseCompletionError) {
6167
+ throw this.responseCompletionError;
6168
+ }
5996
6169
  const pendingOutcome = this.responseCompletionOutcome;
5997
6170
  if (pendingOutcome) {
5998
6171
  this.responseCompletionOutcome = null;
@@ -6031,12 +6204,14 @@ class AcpBackend {
6031
6204
  * Helper to emit idle status and resolve any waiting promises
6032
6205
  */
6033
6206
  emitIdleStatus() {
6207
+ this.clearPostPromptCompletionIdleTimeout();
6034
6208
  this.emit({ type: "status", status: "idle" });
6035
6209
  this.settleResponseWaiter({ kind: "resolved" });
6036
6210
  }
6037
6211
  async cancel(sessionId) {
6038
6212
  const cancelError = createAcpAbortError("Cancelled by user");
6039
6213
  this.clearIdleTimeoutState();
6214
+ this.clearPostPromptCompletionIdleTimeout();
6040
6215
  this.clearToolCallTracking();
6041
6216
  this.settleResponseWaiter({ kind: "rejected", error: cancelError });
6042
6217
  this.emit({ type: "status", status: "stopped", detail: "Cancelled by user" });
@@ -6097,6 +6272,7 @@ class AcpBackend {
6097
6272
  }
6098
6273
  }
6099
6274
  this.clearIdleTimeoutState();
6275
+ this.clearPostPromptCompletionIdleTimeout();
6100
6276
  this.listeners = [];
6101
6277
  this.connection = null;
6102
6278
  this.acpSessionId = null;
@@ -6745,12 +6921,12 @@ async function ensureUnifiedDaemonStarted() {
6745
6921
  async function executeUnifiedProvider(opts) {
6746
6922
  const credentials = await ensureUnifiedRuntimePrerequisites(opts.credentials);
6747
6923
  if (opts.provider === "claude") {
6748
- const { runClaude } = await import('./runClaude-OxYbt3ZQ.mjs');
6924
+ const { runClaude } = await import('./runClaude-8inO7C5p.mjs');
6749
6925
  await runClaude(credentials, opts.claudeOptions ?? {});
6750
6926
  return;
6751
6927
  }
6752
6928
  if (opts.provider === "codex") {
6753
- const { runCodex } = await import('./runCodex-ByVTEbSY.mjs');
6929
+ const { runCodex } = await import('./runCodex-BQ-fN5E6.mjs');
6754
6930
  await runCodex({
6755
6931
  credentials,
6756
6932
  startedBy: opts.startedBy,
@@ -6760,7 +6936,7 @@ async function executeUnifiedProvider(opts) {
6760
6936
  return;
6761
6937
  }
6762
6938
  if (opts.provider === "gemini") {
6763
- const { runGemini } = await import('./runGemini-BRO6A2jm.mjs');
6939
+ const { runGemini } = await import('./runGemini-BE0FizuV.mjs');
6764
6940
  await runGemini({
6765
6941
  credentials,
6766
6942
  startedBy: opts.startedBy
@@ -6802,7 +6978,7 @@ function shouldRunMainClaudeFlow(opts) {
6802
6978
  return;
6803
6979
  } else if (subcommand === "runtime") {
6804
6980
  if (args[1] === "providers") {
6805
- const { renderRuntimeProviders } = await import('./command-Cfq3Uc0S.mjs');
6981
+ const { renderRuntimeProviders } = await import('./command-BGA3qCKR.mjs');
6806
6982
  console.log(renderRuntimeProviders());
6807
6983
  return;
6808
6984
  }
@@ -6980,8 +7156,8 @@ function shouldRunMainClaudeFlow(opts) {
6980
7156
  const projectId = args[3];
6981
7157
  try {
6982
7158
  const { saveGoogleCloudProjectToConfig } = await Promise.resolve().then(function () { return config; });
6983
- const { readCredentials: readCredentials2 } = await import('./persistence-Dg-rxY2a.mjs');
6984
- const { ApiClient: ApiClient2 } = await import('./api-dwwHBzLc.mjs').then(function (n) { return n.q; });
7159
+ const { readCredentials: readCredentials2 } = await import('./persistence-BPV3AmJL.mjs');
7160
+ const { ApiClient: ApiClient2 } = await import('./api-CnvyGas2.mjs').then(function (n) { return n.q; });
6985
7161
  let userEmail = void 0;
6986
7162
  try {
6987
7163
  const credentials = await readCredentials2();