@k-leumas/queque-cli 0.5.2 → 0.6.0

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.
package/dist/cli/app.js CHANGED
@@ -83,7 +83,7 @@ var init_uuid = __esm({
83
83
  });
84
84
 
85
85
  // node_modules/.pnpm/@anthropic-ai+sdk@0.92.0_zod@4.1.5/node_modules/@anthropic-ai/sdk/internal/errors.mjs
86
- function isAbortError(err) {
86
+ function isAbortError2(err) {
87
87
  return typeof err === "object" && err !== null && // Spec-compliant fetch implementations
88
88
  ("name" in err && err.name === "AbortError" || // Expo fetch
89
89
  "message" in err && String(err.message).includes("FetchRequestCanceledException"));
@@ -841,7 +841,7 @@ var init_streaming = __esm({
841
841
  }
842
842
  done = true;
843
843
  } catch (e) {
844
- if (isAbortError(e))
844
+ if (isAbortError2(e))
845
845
  return;
846
846
  throw e;
847
847
  } finally {
@@ -884,7 +884,7 @@ var init_streaming = __esm({
884
884
  }
885
885
  done = true;
886
886
  } catch (e) {
887
- if (isAbortError(e))
887
+ if (isAbortError2(e))
888
888
  return;
889
889
  throw e;
890
890
  } finally {
@@ -3225,7 +3225,7 @@ var init_BetaMessageStream = __esm({
3225
3225
  _BetaMessageStream_logger.set(this, void 0);
3226
3226
  _BetaMessageStream_handleError.set(this, (error43) => {
3227
3227
  __classPrivateFieldSet(this, _BetaMessageStream_errored, true, "f");
3228
- if (isAbortError(error43)) {
3228
+ if (isAbortError2(error43)) {
3229
3229
  error43 = new APIUserAbortError();
3230
3230
  }
3231
3231
  if (error43 instanceof APIUserAbortError) {
@@ -5424,7 +5424,7 @@ var init_MessageStream = __esm({
5424
5424
  _MessageStream_logger.set(this, void 0);
5425
5425
  _MessageStream_handleError.set(this, (error43) => {
5426
5426
  __classPrivateFieldSet(this, _MessageStream_errored, true, "f");
5427
- if (isAbortError(error43)) {
5427
+ if (isAbortError2(error43)) {
5428
5428
  error43 = new APIUserAbortError();
5429
5429
  }
5430
5430
  if (error43 instanceof APIUserAbortError) {
@@ -6559,7 +6559,7 @@ var init_client = __esm({
6559
6559
  if (options.signal?.aborted) {
6560
6560
  throw new APIUserAbortError();
6561
6561
  }
6562
- const isTimeout = isAbortError(response) || /timed? ?out/i.test(String(response) + ("cause" in response ? String(response.cause) : ""));
6562
+ const isTimeout = isAbortError2(response) || /timed? ?out/i.test(String(response) + ("cause" in response ? String(response.cause) : ""));
6563
6563
  if (retriesRemaining) {
6564
6564
  loggerFor(this).info(`[${requestLogID}] connection ${isTimeout ? "timed out" : "failed"} - ${retryMessage}`);
6565
6565
  loggerFor(this).debug(`[${requestLogID}] connection ${isTimeout ? "timed out" : "failed"} (${retryMessage})`, formatRequestDetails({
@@ -33239,7 +33239,7 @@ var require_websocket = __commonJS({
33239
33239
  var http = __require("http");
33240
33240
  var net4 = __require("net");
33241
33241
  var tls = __require("tls");
33242
- var { randomBytes, createHash } = __require("crypto");
33242
+ var { randomBytes, createHash: createHash2 } = __require("crypto");
33243
33243
  var { Duplex, Readable } = __require("stream");
33244
33244
  var { URL: URL2 } = __require("url");
33245
33245
  var PerMessageDeflate2 = require_permessage_deflate();
@@ -33899,7 +33899,7 @@ var require_websocket = __commonJS({
33899
33899
  abortHandshake(websocket, socket, "Invalid Upgrade header");
33900
33900
  return;
33901
33901
  }
33902
- const digest = createHash("sha1").update(key + GUID).digest("base64");
33902
+ const digest = createHash2("sha1").update(key + GUID).digest("base64");
33903
33903
  if (res.headers["sec-websocket-accept"] !== digest) {
33904
33904
  abortHandshake(websocket, socket, "Invalid Sec-WebSocket-Accept header");
33905
33905
  return;
@@ -34266,7 +34266,7 @@ var require_websocket_server = __commonJS({
34266
34266
  var EventEmitter3 = __require("events");
34267
34267
  var http = __require("http");
34268
34268
  var { Duplex } = __require("stream");
34269
- var { createHash } = __require("crypto");
34269
+ var { createHash: createHash2 } = __require("crypto");
34270
34270
  var extension2 = require_extension();
34271
34271
  var PerMessageDeflate2 = require_permessage_deflate();
34272
34272
  var subprotocol2 = require_subprotocol();
@@ -34567,7 +34567,7 @@ var require_websocket_server = __commonJS({
34567
34567
  );
34568
34568
  }
34569
34569
  if (this._state > RUNNING) return abortHandshake(socket, 503);
34570
- const digest = createHash("sha1").update(key + GUID).digest("base64");
34570
+ const digest = createHash2("sha1").update(key + GUID).digest("base64");
34571
34571
  const headers = [
34572
34572
  "HTTP/1.1 101 Switching Protocols",
34573
34573
  "Upgrade: websocket",
@@ -48088,7 +48088,14 @@ var privacyConfigSchema = external_exports.object({
48088
48088
  var safetyConfigSchema = external_exports.object({
48089
48089
  destructiveCommandPatterns: external_exports.array(external_exports.string()).optional()
48090
48090
  }).optional();
48091
+ var EFFORT_LEVELS = ["low", "medium", "high", "xhigh", "max"];
48092
+ var DEFAULT_EFFORT = "low";
48093
+ var effortSchema = external_exports.preprocess(
48094
+ (value) => typeof value === "string" ? value.trim().toLowerCase() : value,
48095
+ external_exports.enum(EFFORT_LEVELS)
48096
+ ).optional().catch(void 0);
48091
48097
  var qqConfigFileSchema = external_exports.object({
48098
+ effort: effortSchema,
48092
48099
  privacy: privacyConfigSchema,
48093
48100
  safety: safetyConfigSchema
48094
48101
  });
@@ -48153,10 +48160,14 @@ function loadQqConfig() {
48153
48160
  sensitivePathRegexes: compilePatterns(sensitivePatterns),
48154
48161
  redactLogKeys: new Set(redactKeys),
48155
48162
  allowFileRead,
48156
- destructiveCommandRegexes: compilePatterns(destructivePatterns)
48163
+ destructiveCommandRegexes: compilePatterns(destructivePatterns),
48164
+ effort: file2.effort ?? DEFAULT_EFFORT
48157
48165
  };
48158
48166
  return cachedConfig;
48159
48167
  }
48168
+ function resolveEffort() {
48169
+ return loadQqConfig().effort;
48170
+ }
48160
48171
 
48161
48172
  // src/shared/privacy-filter.ts
48162
48173
  function isSensitivePath(path5) {
@@ -48411,6 +48422,113 @@ function readEnvValueFromDotEnvLocal(key, startDir = process.cwd()) {
48411
48422
  return result;
48412
48423
  }
48413
48424
 
48425
+ // src/providers/candidate-stream.ts
48426
+ var MAX_STREAMED_CANDIDATES = 5;
48427
+ function createCandidateScanner(onCandidate) {
48428
+ let started = false;
48429
+ let finished = false;
48430
+ let depth = 0;
48431
+ let inString = false;
48432
+ let escaped = false;
48433
+ let inObject = false;
48434
+ let objectText = "";
48435
+ let emitted = 0;
48436
+ const emit = (text) => {
48437
+ let parsed;
48438
+ try {
48439
+ parsed = JSON.parse(text);
48440
+ } catch {
48441
+ return;
48442
+ }
48443
+ const candidate = commandCandidateSchema.safeParse(parsed);
48444
+ if (!candidate.success) {
48445
+ return;
48446
+ }
48447
+ emitted++;
48448
+ onCandidate(candidate.data);
48449
+ if (emitted >= MAX_STREAMED_CANDIDATES) {
48450
+ finished = true;
48451
+ }
48452
+ };
48453
+ return (text) => {
48454
+ for (const ch of text) {
48455
+ if (finished) {
48456
+ return;
48457
+ }
48458
+ if (!started) {
48459
+ if (ch === "[") {
48460
+ started = true;
48461
+ depth = 1;
48462
+ }
48463
+ continue;
48464
+ }
48465
+ if (inObject) {
48466
+ objectText += ch;
48467
+ }
48468
+ if (inString) {
48469
+ if (escaped) {
48470
+ escaped = false;
48471
+ } else if (ch === "\\") {
48472
+ escaped = true;
48473
+ } else if (ch === '"') {
48474
+ inString = false;
48475
+ }
48476
+ continue;
48477
+ }
48478
+ if (ch === '"') {
48479
+ inString = true;
48480
+ } else if (ch === "[" || ch === "{") {
48481
+ depth++;
48482
+ if (ch === "{" && depth === 2) {
48483
+ inObject = true;
48484
+ objectText = ch;
48485
+ }
48486
+ } else if (ch === "]" || ch === "}") {
48487
+ depth--;
48488
+ if (ch === "}" && depth === 1 && inObject) {
48489
+ const closed = objectText;
48490
+ inObject = false;
48491
+ objectText = "";
48492
+ emit(closed);
48493
+ } else if (depth <= 0) {
48494
+ finished = true;
48495
+ }
48496
+ }
48497
+ }
48498
+ };
48499
+ }
48500
+ function stripJsonCodeFence(raw) {
48501
+ const match = raw.trim().match(/^```(?:json)?\s*\n?([\s\S]*?)\n?```\s*$/);
48502
+ return match ? match[1].trim() : raw.trim();
48503
+ }
48504
+ function parseStrictCandidateList(text) {
48505
+ let parsed;
48506
+ try {
48507
+ parsed = JSON.parse(stripJsonCodeFence(text));
48508
+ } catch {
48509
+ return void 0;
48510
+ }
48511
+ const list = candidateListSchema.safeParse(parsed);
48512
+ return list.success ? list.data : void 0;
48513
+ }
48514
+
48515
+ // src/providers/provider.ts
48516
+ function abortError() {
48517
+ const error43 = new Error("QueQue: request aborted");
48518
+ error43.name = "AbortError";
48519
+ return error43;
48520
+ }
48521
+ function isNormalStopReason(stopReason) {
48522
+ return stopReason === "end_turn" || stopReason === "stop_sequence";
48523
+ }
48524
+ function isAbortError(error43) {
48525
+ if (error43 === null || typeof error43 !== "object") {
48526
+ return false;
48527
+ }
48528
+ const name = error43.name;
48529
+ return name === "AbortError" || name === "APIUserAbortError";
48530
+ }
48531
+
48414
48532
  // src/shared/tool-catalog.ts
48415
48533
  var CATALOG = Object.freeze(
48416
48534
  `
@@ -48480,6 +48598,8 @@ ${buildSteeringText(chunk.payload.tools)}`;
48480
48598
 
48481
48599
  // src/providers/claude.ts
48482
48600
  var DEFAULT_MODEL = "claude-sonnet-5";
48601
+ var SDK_MAX_TOKENS = 16e3;
48602
+ var TRUNCATED_RESPONSE_MESSAGE = "QueQue: Claude response was truncated";
48483
48603
  var cachedAnthropic;
48484
48604
  async function getAnthropicClient(apiKey) {
48485
48605
  if (cachedAnthropic?.apiKey === apiKey) {
@@ -48492,12 +48612,26 @@ async function getAnthropicClient(apiKey) {
48492
48612
  cachedAnthropic = { apiKey, client: new AnthropicSdk({ apiKey }) };
48493
48613
  return cachedAnthropic.client;
48494
48614
  }
48615
+ var EFFORT_UNSUPPORTED_MODEL_RE = /haiku|claude-3|sonnet-4-5(?!\d)|(?:sonnet|opus)-4-(?:0|1|2025\d*)(?!\d)/i;
48616
+ function sdkOutputConfig(model, effort) {
48617
+ if (EFFORT_UNSUPPORTED_MODEL_RE.test(model)) {
48618
+ return void 0;
48619
+ }
48620
+ return { effort };
48621
+ }
48495
48622
  function resolveModel() {
48496
48623
  return process.env.QQ_MODEL ?? readEnvValueFromDotEnvLocal("QQ_MODEL") ?? DEFAULT_MODEL;
48497
48624
  }
48498
48625
  function extractText(content) {
48499
48626
  return content.filter((block) => block.type === "text" && typeof block.text === "string").map((block) => block.text ?? "").join("").trim();
48500
48627
  }
48628
+ function completeCandidates(text) {
48629
+ const rows = [];
48630
+ createCandidateScanner((candidate) => {
48631
+ rows.push(candidate);
48632
+ })(text);
48633
+ return rows;
48634
+ }
48501
48635
  function stripCodeFence(raw) {
48502
48636
  const m = raw.trim().match(/^```(?:json)?\s*\n?([\s\S]*?)\n?```\s*$/);
48503
48637
  return m ? m[1].trim() : raw.trim();
@@ -48516,6 +48650,24 @@ function parseCandidates(text) {
48516
48650
  return [{ command: 'echo ""', explanation: "QueQue: unexpected response format" }];
48517
48651
  }
48518
48652
  }
48653
+ function finalCandidateList(text, streamed, finishedNormally, logFields = {}) {
48654
+ const strict = parseStrictCandidateList(text);
48655
+ if (strict) {
48656
+ return strict;
48657
+ }
48658
+ if (streamed.length > 0) {
48659
+ return streamed;
48660
+ }
48661
+ if (!finishedNormally) {
48662
+ const complete = completeCandidates(text);
48663
+ if (complete.length === 0) {
48664
+ void appendDebugLog("provider", "response truncated", logFields);
48665
+ throw new Error(TRUNCATED_RESPONSE_MESSAGE);
48666
+ }
48667
+ return complete;
48668
+ }
48669
+ return parseCandidates(text);
48670
+ }
48519
48671
  function shouldForceSelector() {
48520
48672
  const configured = process.env.QQ_FORCE_SELECTOR ?? readEnvValueFromDotEnvLocal("QQ_FORCE_SELECTOR");
48521
48673
  return configured === "1" || configured === "true";
@@ -48559,7 +48711,7 @@ function buildPrompt(envelope) {
48559
48711
  )
48560
48712
  ].join("\n");
48561
48713
  }
48562
- async function fetchCandidates(envelope, rbuffer = "", timeoutMs = 25e3) {
48714
+ async function fetchCandidates(envelope, rbuffer = "", timeoutMs = 25e3, options = {}) {
48563
48715
  const apiKey = usableSecret(process.env.ANTHROPIC_API_KEY) ?? usableSecret(readEnvValueFromDotEnvLocal("ANTHROPIC_API_KEY"));
48564
48716
  if (!apiKey) {
48565
48717
  throw new Error("ANTHROPIC_API_KEY is required in the environment or .env.local");
@@ -48567,33 +48719,57 @@ async function fetchCandidates(envelope, rbuffer = "", timeoutMs = 25e3) {
48567
48719
  const client = await getAnthropicClient(apiKey);
48568
48720
  const prompt = buildPrompt(envelope);
48569
48721
  const model = resolveModel();
48722
+ const outputConfig = sdkOutputConfig(model, resolveEffort());
48570
48723
  void appendDebugLog("provider", "request start", {
48571
48724
  model,
48725
+ effort: outputConfig?.effort ?? "none",
48572
48726
  cwd: envelope.base.cwd,
48573
48727
  rbufferLength: rbuffer.length,
48574
48728
  extras: envelope.extras.map((chunk) => chunk.kind)
48575
48729
  });
48730
+ const { onCandidate, signal } = options;
48731
+ const streamed = [];
48576
48732
  try {
48577
- const response = await client.messages.create(
48733
+ const stream = client.messages.stream(
48578
48734
  {
48579
48735
  model,
48580
- max_tokens: 1024,
48736
+ max_tokens: SDK_MAX_TOKENS,
48581
48737
  system: buildSystemPrompt(envelope),
48582
- messages: [{ role: "user", content: prompt }]
48738
+ messages: [{ role: "user", content: prompt }],
48739
+ ...outputConfig ? { output_config: outputConfig } : {}
48583
48740
  },
48584
- {
48585
- timeout: timeoutMs
48586
- }
48741
+ { timeout: timeoutMs, signal: options.signal }
48587
48742
  );
48588
- const text = extractText(response.content);
48589
- const candidates = ensureSelectableCandidates(parseCandidates(text));
48743
+ if (onCandidate) {
48744
+ const scan = createCandidateScanner((candidate) => {
48745
+ streamed.push(candidate);
48746
+ if (!signal?.aborted) {
48747
+ onCandidate(candidate);
48748
+ }
48749
+ });
48750
+ stream.on("text", (textDelta) => {
48751
+ scan(textDelta);
48752
+ });
48753
+ }
48754
+ const message = await stream.finalMessage();
48755
+ const text = extractText(message.content);
48756
+ const finalList = finalCandidateList(text, streamed, isNormalStopReason(message.stop_reason), {
48757
+ model,
48758
+ stopReason: message.stop_reason
48759
+ });
48760
+ const candidates = ensureSelectableCandidates(finalList);
48590
48761
  void appendDebugLog("provider", "response parsed", {
48591
48762
  model,
48592
48763
  candidateCount: candidates.length,
48764
+ streamedCount: streamed.length,
48593
48765
  forceSelector: shouldForceSelector()
48594
48766
  });
48595
48767
  return candidates;
48596
48768
  } catch (error43) {
48769
+ if (isAbortError(error43) || signal?.aborted) {
48770
+ void appendDebugLog("provider", "request aborted", { model });
48771
+ throw abortError();
48772
+ }
48597
48773
  void appendDebugLog("provider", "request failed", {
48598
48774
  model,
48599
48775
  message: error43 instanceof Error ? error43.message : String(error43)
@@ -48602,158 +48778,636 @@ async function fetchCandidates(envelope, rbuffer = "", timeoutMs = 25e3) {
48602
48778
  }
48603
48779
  }
48604
48780
  var claudeAdapter = {
48605
- fetchCandidates
48781
+ fetchCandidates: (envelope, options) => fetchCandidates(envelope, "", 25e3, options)
48606
48782
  };
48607
48783
 
48608
- // src/providers/claude-exec.ts
48784
+ // src/providers/claude-prewarm.ts
48785
+ import { createHash } from "crypto";
48786
+
48787
+ // src/providers/claude-process.ts
48609
48788
  import { spawn } from "child_process";
48610
- async function execFileAsync3(file2, args, options = {}) {
48611
- const encoding = options.encoding ?? "utf8";
48612
- const maxBuffer = options.maxBuffer ?? 1024 * 1024;
48613
- const killSignal = options.killSignal ?? "SIGTERM";
48614
- const stdio = options.stdio ?? ["ignore", "pipe", "pipe"];
48615
- const timeoutMs = options.timeout;
48616
- if (typeof timeoutMs === "number" && timeoutMs <= 0) {
48617
- const error43 = Object.assign(new Error(`Command timed out: ${file2}`), {
48618
- killed: true,
48619
- signal: killSignal,
48620
- code: null,
48621
- stdout: "",
48622
- stderr: ""
48623
- });
48624
- throw error43;
48789
+ import { StringDecoder } from "string_decoder";
48790
+
48791
+ // src/providers/stream-json.ts
48792
+ var STREAM_JSON_CARRY_CAP_BYTES = 1024 * 1024;
48793
+ var streamJsonResultSchema = external_exports.object({
48794
+ type: external_exports.literal("result"),
48795
+ is_error: external_exports.boolean(),
48796
+ result: external_exports.string().optional(),
48797
+ subtype: external_exports.string().optional(),
48798
+ duration_api_ms: external_exports.number().optional(),
48799
+ /**
48800
+ * Claude Code 2.1.285 records it on `result` (Probe 2, `"end_turn"`); older builds and the
48801
+ * Probe 3 auth result leave it out. Null is kept so the runner can fail closed on it.
48802
+ */
48803
+ stop_reason: external_exports.string().nullable().optional()
48804
+ });
48805
+ function isRecord(value) {
48806
+ return value !== null && typeof value === "object";
48807
+ }
48808
+ function createLineParser(onEvent, options = {}) {
48809
+ const maxCarryBytes = options.maxCarryBytes ?? STREAM_JSON_CARRY_CAP_BYTES;
48810
+ let carry = "";
48811
+ let carryBytes = 0;
48812
+ let overflowed = false;
48813
+ return (chunk) => {
48814
+ if (overflowed) {
48815
+ return;
48816
+ }
48817
+ carry += chunk;
48818
+ let nl = carry.indexOf("\n");
48819
+ if (nl === -1) {
48820
+ carryBytes += Buffer.byteLength(chunk, "utf8");
48821
+ } else {
48822
+ while (nl !== -1) {
48823
+ const line = carry.slice(0, nl).trim();
48824
+ carry = carry.slice(nl + 1);
48825
+ nl = carry.indexOf("\n");
48826
+ if (!line) {
48827
+ continue;
48828
+ }
48829
+ let event;
48830
+ try {
48831
+ event = JSON.parse(line);
48832
+ } catch {
48833
+ continue;
48834
+ }
48835
+ onEvent(event);
48836
+ }
48837
+ carryBytes = Buffer.byteLength(carry, "utf8");
48838
+ }
48839
+ if (carryBytes > maxCarryBytes) {
48840
+ overflowed = true;
48841
+ carry = "";
48842
+ carryBytes = 0;
48843
+ options.onOverflow?.();
48844
+ }
48845
+ };
48846
+ }
48847
+ function textDeltaOf(event) {
48848
+ if (!isRecord(event) || event.type !== "stream_event") {
48849
+ return void 0;
48850
+ }
48851
+ const inner = event.event;
48852
+ if (!isRecord(inner) || inner.type !== "content_block_delta") {
48853
+ return void 0;
48854
+ }
48855
+ const delta = inner.delta;
48856
+ if (!isRecord(delta) || delta.type !== "text_delta" || typeof delta.text !== "string") {
48857
+ return void 0;
48858
+ }
48859
+ return delta.text;
48860
+ }
48861
+ function isInitEvent(event) {
48862
+ return isRecord(event) && event.type === "system" && event.subtype === "init";
48863
+ }
48864
+ function buildStreamJsonUserLine(prompt) {
48865
+ return `${JSON.stringify({ type: "user", message: { role: "user", content: prompt } })}
48866
+ `;
48867
+ }
48868
+
48869
+ // src/providers/claude-process.ts
48870
+ var SIGKILL_GRACE_MS = 1500;
48871
+ var POST_RESULT_BACKSTOP_MS = 2e3;
48872
+ var STDERR_CAP_BYTES = 64 * 1024;
48873
+ function turnFailure(message, reason, sawInit, fields) {
48874
+ return Object.assign(new Error(message), { ...fields, reason, sawInit });
48875
+ }
48876
+ function timeoutFailure(stderr, sawInit) {
48877
+ return turnFailure("Command timed out: claude", "timeout", sawInit, {
48878
+ killed: true,
48879
+ signal: "SIGTERM",
48880
+ code: null,
48881
+ stdout: "",
48882
+ stderr
48883
+ });
48884
+ }
48885
+ function isRunning(child) {
48886
+ return child.pid !== void 0 && child.exitCode === null && child.signalCode === null;
48887
+ }
48888
+ function isRecord2(value) {
48889
+ return value !== null && typeof value === "object";
48890
+ }
48891
+ function spawnClaudeChild(command, argv, cwd2, env3) {
48892
+ return spawn(command, [...argv], {
48893
+ cwd: cwd2,
48894
+ env: env3,
48895
+ stdio: ["pipe", "pipe", "pipe"],
48896
+ shell: false,
48897
+ windowsHide: true
48898
+ });
48899
+ }
48900
+ function terminateChild(child, graceMs = SIGKILL_GRACE_MS) {
48901
+ if (child.exitCode !== null || child.signalCode !== null) {
48902
+ return;
48903
+ }
48904
+ child.kill("SIGTERM");
48905
+ const killTimer = setTimeout(() => {
48906
+ if (child.exitCode === null && child.signalCode === null) {
48907
+ child.kill("SIGKILL");
48908
+ }
48909
+ }, graceMs);
48910
+ killTimer.unref();
48911
+ child.once("exit", () => {
48912
+ clearTimeout(killTimer);
48913
+ });
48914
+ }
48915
+ function runClaudeTurn(options) {
48916
+ const { command, argv, cwd: cwd2, env: env3, prompt, budgetMs, signal, onTextDelta, onStage } = options;
48917
+ const backstopMs = options.backstopMs ?? POST_RESULT_BACKSTOP_MS;
48918
+ const killGraceMs = options.killGraceMs ?? SIGKILL_GRACE_MS;
48919
+ const prewarmed = options.child;
48920
+ if (signal?.aborted) {
48921
+ if (prewarmed) {
48922
+ terminateChild(prewarmed, killGraceMs);
48923
+ }
48924
+ return Promise.reject(abortError());
48925
+ }
48926
+ if (budgetMs <= 0) {
48927
+ if (prewarmed) {
48928
+ terminateChild(prewarmed, killGraceMs);
48929
+ }
48930
+ return Promise.reject(timeoutFailure("", false));
48931
+ }
48932
+ if (prewarmed && !isRunning(prewarmed)) {
48933
+ return Promise.reject(
48934
+ turnFailure("Command failed: claude", "exit-without-result", false, {
48935
+ code: prewarmed.exitCode,
48936
+ killed: false,
48937
+ signal: prewarmed.signalCode,
48938
+ stdout: "",
48939
+ stderr: ""
48940
+ })
48941
+ );
48625
48942
  }
48626
48943
  return new Promise((resolve3, reject) => {
48627
48944
  let settled = false;
48628
- const finish = (callback) => {
48945
+ let sawInit = false;
48946
+ let sawDelta = false;
48947
+ let stderr = "";
48948
+ let stderrBytes = 0;
48949
+ let budgetTimer;
48950
+ let initTimer;
48951
+ let backstopArmed = false;
48952
+ const child = prewarmed ?? spawnClaudeChild(command, argv, cwd2, env3);
48953
+ const onAbort = () => {
48954
+ if (settled) {
48955
+ return;
48956
+ }
48957
+ onStage?.("aborted");
48958
+ terminateChild(child, killGraceMs);
48959
+ finish(() => reject(abortError()));
48960
+ };
48961
+ const collectStderr = (chunk) => {
48962
+ if (stderrBytes >= STDERR_CAP_BYTES) {
48963
+ return;
48964
+ }
48965
+ const bytes = Buffer.byteLength(chunk, "utf8");
48966
+ if (stderrBytes + bytes <= STDERR_CAP_BYTES) {
48967
+ stderr += chunk;
48968
+ stderrBytes += bytes;
48969
+ return;
48970
+ }
48971
+ const room = STDERR_CAP_BYTES - stderrBytes;
48972
+ stderr += new StringDecoder("utf8").write(Buffer.from(chunk, "utf8").subarray(0, room));
48973
+ stderrBytes = STDERR_CAP_BYTES;
48974
+ };
48975
+ const armBackstop = () => {
48976
+ if (backstopArmed) {
48977
+ return;
48978
+ }
48979
+ backstopArmed = true;
48980
+ const backstopTimer = setTimeout(() => {
48981
+ terminateChild(child, killGraceMs);
48982
+ }, backstopMs);
48983
+ backstopTimer.unref();
48984
+ child.once("exit", () => {
48985
+ clearTimeout(backstopTimer);
48986
+ });
48987
+ };
48988
+ const onOverflow = () => {
48989
+ if (settled) {
48990
+ return;
48991
+ }
48992
+ terminateChild(child, killGraceMs);
48993
+ finish(
48994
+ () => reject(
48995
+ turnFailure("Command failed: claude", "invalid-result", sawInit, {
48996
+ code: null,
48997
+ killed: false,
48998
+ signal: null,
48999
+ stdout: "",
49000
+ stderr
49001
+ })
49002
+ )
49003
+ );
49004
+ };
49005
+ function finish(callback) {
48629
49006
  if (settled) {
48630
49007
  return;
48631
49008
  }
48632
49009
  settled = true;
49010
+ if (budgetTimer) {
49011
+ clearTimeout(budgetTimer);
49012
+ }
49013
+ if (initTimer) {
49014
+ clearTimeout(initTimer);
49015
+ }
49016
+ signal?.removeEventListener("abort", onAbort);
48633
49017
  callback();
48634
- };
48635
- const child = spawn(file2, [...args], {
48636
- cwd: options.cwd,
48637
- env: options.env,
48638
- stdio,
48639
- windowsHide: options.windowsHide ?? true,
48640
- shell: options.shell ?? false
48641
- });
48642
- const stdoutChunks = [];
48643
- const stderrChunks = [];
48644
- let exceededBuffer = false;
48645
- let timedOut = false;
48646
- let timer;
48647
- function combined(chunks) {
48648
- return Buffer.concat(chunks).toString(encoding);
48649
49018
  }
48650
- function pushChunk(chunks, chunk) {
48651
- chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
48652
- if (Buffer.concat(chunks).length > maxBuffer) {
48653
- exceededBuffer = true;
48654
- child.kill(killSignal);
49019
+ const handleEvent = (event) => {
49020
+ if (settled) {
49021
+ return;
48655
49022
  }
48656
- }
48657
- if (typeof timeoutMs === "number") {
48658
- timer = setTimeout(() => {
48659
- timedOut = true;
48660
- child.kill(killSignal);
48661
- }, timeoutMs);
48662
- }
48663
- child.stdout?.on("data", (chunk) => {
48664
- pushChunk(stdoutChunks, chunk);
48665
- });
48666
- child.stderr?.on("data", (chunk) => {
48667
- pushChunk(stderrChunks, chunk);
48668
- });
48669
- child.on("error", (error43) => {
48670
- if (timer) {
48671
- clearTimeout(timer);
49023
+ if (isInitEvent(event)) {
49024
+ if (!sawInit) {
49025
+ sawInit = true;
49026
+ if (initTimer) {
49027
+ clearTimeout(initTimer);
49028
+ }
49029
+ onStage?.("init");
49030
+ }
49031
+ return;
48672
49032
  }
48673
- finish(() => {
48674
- reject(error43);
48675
- });
48676
- });
48677
- child.on("close", (code, signal) => {
48678
- if (timer) {
48679
- clearTimeout(timer);
49033
+ const text = textDeltaOf(event);
49034
+ if (text !== void 0) {
49035
+ if (!sawDelta) {
49036
+ sawDelta = true;
49037
+ onStage?.("first-delta");
49038
+ }
49039
+ if (!settled) {
49040
+ onTextDelta?.(text);
49041
+ }
49042
+ return;
48680
49043
  }
48681
- const stdout = combined(stdoutChunks);
48682
- const stderr = combined(stderrChunks);
48683
- if (exceededBuffer || timedOut) {
48684
- const error43 = Object.assign(new Error(`Command timed out: ${file2}`), {
48685
- killed: true,
48686
- signal: signal ?? killSignal,
48687
- code: null,
48688
- stdout,
48689
- stderr
48690
- });
48691
- finish(() => {
48692
- reject(error43);
48693
- });
49044
+ if (!isRecord2(event) || event.type !== "result") {
48694
49045
  return;
48695
49046
  }
48696
- if (code !== 0 && code !== null) {
48697
- const error43 = Object.assign(new Error(`Command failed: ${file2}`), {
48698
- code,
48699
- killed: false,
48700
- signal,
48701
- stdout,
48702
- stderr
48703
- });
48704
- finish(() => {
48705
- reject(error43);
48706
- });
49047
+ armBackstop();
49048
+ const parsed = streamJsonResultSchema.safeParse(event);
49049
+ if (!parsed.success) {
49050
+ finish(
49051
+ () => reject(
49052
+ turnFailure("Command failed: claude", "invalid-result", sawInit, {
49053
+ code: null,
49054
+ killed: false,
49055
+ signal: null,
49056
+ stdout: "",
49057
+ stderr
49058
+ })
49059
+ )
49060
+ );
48707
49061
  return;
48708
49062
  }
48709
- if (signal) {
48710
- const error43 = Object.assign(new Error(`Command killed: ${file2}`), {
48711
- killed: true,
48712
- signal,
48713
- code: null,
48714
- stdout,
48715
- stderr
48716
- });
48717
- finish(() => {
48718
- reject(error43);
48719
- });
49063
+ if (parsed.data.is_error) {
49064
+ finish(
49065
+ () => reject(
49066
+ turnFailure("Command failed: claude", "is-error", sawInit, {
49067
+ code: 1,
49068
+ killed: false,
49069
+ signal: null,
49070
+ stdout: parsed.data.result ?? "",
49071
+ stderr
49072
+ })
49073
+ )
49074
+ );
48720
49075
  return;
48721
49076
  }
48722
- finish(() => {
48723
- resolve3({ stdout, stderr });
48724
- });
49077
+ const stopReason = parsed.data.stop_reason;
49078
+ if (stopReason !== void 0 && !isNormalStopReason(parsed.data.stop_reason)) {
49079
+ finish(
49080
+ () => reject(
49081
+ turnFailure("Command failed: claude", "truncated", sawInit, {
49082
+ code: null,
49083
+ killed: false,
49084
+ signal: null,
49085
+ stdout: parsed.data.result ?? "",
49086
+ stderr,
49087
+ stopReason
49088
+ })
49089
+ )
49090
+ );
49091
+ return;
49092
+ }
49093
+ onStage?.("result");
49094
+ finish(() => resolve3(parsed.data.result ?? ""));
49095
+ };
49096
+ if (!prewarmed && child.pid !== void 0) {
49097
+ onStage?.("spawned", { pid: child.pid });
49098
+ }
49099
+ child.stdout.setEncoding("utf8");
49100
+ child.stdout.on("data", createLineParser(handleEvent, { onOverflow }));
49101
+ child.stderr.setEncoding("utf8");
49102
+ child.stderr.on("data", collectStderr);
49103
+ child.on("error", (error43) => {
49104
+ finish(() => reject(Object.assign(error43, { reason: "spawn-error", sawInit })));
49105
+ });
49106
+ child.on("exit", (code, exitSignal) => {
49107
+ onStage?.("exit", { code, signal: exitSignal });
48725
49108
  });
49109
+ child.on("close", (code, closeSignal) => {
49110
+ finish(
49111
+ () => reject(
49112
+ turnFailure("Command failed: claude", "exit-without-result", sawInit, {
49113
+ code,
49114
+ killed: false,
49115
+ signal: closeSignal,
49116
+ stdout: "",
49117
+ stderr
49118
+ })
49119
+ )
49120
+ );
49121
+ });
49122
+ budgetTimer = setTimeout(() => {
49123
+ if (settled) {
49124
+ return;
49125
+ }
49126
+ onStage?.("timed-out");
49127
+ terminateChild(child, killGraceMs);
49128
+ finish(() => reject(timeoutFailure(stderr, sawInit)));
49129
+ }, budgetMs);
49130
+ budgetTimer.unref();
49131
+ if (options.initDeadlineMs !== void 0) {
49132
+ const initDeadlineMs = options.initDeadlineMs;
49133
+ initTimer = setTimeout(() => {
49134
+ if (settled || sawInit) {
49135
+ return;
49136
+ }
49137
+ onStage?.("init-timed-out");
49138
+ terminateChild(child, killGraceMs);
49139
+ finish(
49140
+ () => reject(
49141
+ turnFailure("Command failed: claude", "stale-prewarm", false, {
49142
+ code: null,
49143
+ killed: false,
49144
+ signal: null,
49145
+ stdout: "",
49146
+ stderr
49147
+ })
49148
+ )
49149
+ );
49150
+ }, initDeadlineMs);
49151
+ initTimer.unref();
49152
+ }
49153
+ signal?.addEventListener("abort", onAbort, { once: true });
49154
+ child.stdin.on("error", () => {
49155
+ });
49156
+ child.stdin.write(buildStreamJsonUserLine(prompt));
49157
+ child.stdin.end();
48726
49158
  });
48727
49159
  }
48728
49160
 
49161
+ // src/providers/claude-prewarm.ts
49162
+ var enabled = false;
49163
+ var slot;
49164
+ var diedIdle = false;
49165
+ function prewarmKey(spec) {
49166
+ return createHash("sha256").update(JSON.stringify({ command: spec.command, cwd: spec.cwd, argv: spec.argv })).digest("hex");
49167
+ }
49168
+ function enablePrewarm() {
49169
+ enabled = true;
49170
+ }
49171
+ function disablePrewarm() {
49172
+ retirePrewarmed();
49173
+ enabled = false;
49174
+ diedIdle = false;
49175
+ }
49176
+ function retireChild(child, graceMs) {
49177
+ if (!isRunning(child)) {
49178
+ return;
49179
+ }
49180
+ child.stdin.end();
49181
+ const killTimer = setTimeout(() => {
49182
+ if (isRunning(child)) {
49183
+ child.kill("SIGKILL");
49184
+ }
49185
+ }, graceMs);
49186
+ killTimer.unref();
49187
+ child.once("exit", () => {
49188
+ clearTimeout(killTimer);
49189
+ });
49190
+ }
49191
+ function retireSlot(graceMs) {
49192
+ const entry = slot;
49193
+ if (!entry) {
49194
+ return;
49195
+ }
49196
+ slot = void 0;
49197
+ entry.state = "retired";
49198
+ void appendDebugLog("provider", "prewarm retired", { pid: entry.child.pid ?? null });
49199
+ retireChild(entry.child, graceMs);
49200
+ }
49201
+ function schedulePrewarm(spec) {
49202
+ if (!enabled) {
49203
+ return;
49204
+ }
49205
+ const key = prewarmKey(spec);
49206
+ if (slot && slot.key === key && isRunning(slot.child)) {
49207
+ return;
49208
+ }
49209
+ retireSlot(SIGKILL_GRACE_MS);
49210
+ diedIdle = false;
49211
+ let child;
49212
+ try {
49213
+ child = spawnClaudeChild(spec.command, spec.argv, spec.cwd, process.env);
49214
+ } catch (error43) {
49215
+ void appendDebugLog("provider", "prewarm spawn failed", {
49216
+ code: error43.code ?? null
49217
+ });
49218
+ return;
49219
+ }
49220
+ const entry = { child, cwd: spec.cwd, key, state: "idle", spawnedAt: Date.now() };
49221
+ slot = entry;
49222
+ const clearIfIdle = () => {
49223
+ if (slot === entry) {
49224
+ slot = void 0;
49225
+ diedIdle = true;
49226
+ }
49227
+ };
49228
+ child.on("error", (error43) => {
49229
+ const wasIdle = entry.state === "idle";
49230
+ clearIfIdle();
49231
+ if (wasIdle) {
49232
+ void appendDebugLog("provider", "prewarm error", { code: error43.code ?? null });
49233
+ }
49234
+ });
49235
+ child.stdin.on("error", () => {
49236
+ });
49237
+ child.on("exit", (code, signal) => {
49238
+ clearIfIdle();
49239
+ if (entry.state !== "taken") {
49240
+ void appendDebugLog("provider", "prewarm exit", {
49241
+ pid: child.pid ?? null,
49242
+ code,
49243
+ signal,
49244
+ retired: entry.state === "retired"
49245
+ });
49246
+ }
49247
+ });
49248
+ if (child.pid !== void 0) {
49249
+ void appendDebugLog("provider", "prewarm spawned", { pid: child.pid });
49250
+ }
49251
+ }
49252
+ function takePrewarmed(spec) {
49253
+ if (!enabled) {
49254
+ return { miss: "disabled" };
49255
+ }
49256
+ const entry = slot;
49257
+ if (!entry) {
49258
+ const reason = diedIdle ? "dead" : "empty";
49259
+ diedIdle = false;
49260
+ return { miss: reason };
49261
+ }
49262
+ if (!isRunning(entry.child)) {
49263
+ slot = void 0;
49264
+ entry.state = "retired";
49265
+ diedIdle = false;
49266
+ return { miss: "dead" };
49267
+ }
49268
+ if (entry.cwd !== spec.cwd) {
49269
+ retireSlot(SIGKILL_GRACE_MS);
49270
+ return { miss: "cwd" };
49271
+ }
49272
+ if (entry.key !== prewarmKey(spec)) {
49273
+ retireSlot(SIGKILL_GRACE_MS);
49274
+ return { miss: "prompt" };
49275
+ }
49276
+ slot = void 0;
49277
+ entry.state = "taken";
49278
+ return { child: entry.child, spawnedAt: entry.spawnedAt };
49279
+ }
49280
+ function retirePrewarmed(graceMs = SIGKILL_GRACE_MS) {
49281
+ retireSlot(graceMs);
49282
+ }
49283
+
48729
49284
  // src/providers/claude-cli.ts
48730
- async function fetchClaudeCliCandidates(envelope, remainingBudgetMs) {
48731
- const prompt = buildPrompt(envelope);
48732
- const args = [
49285
+ var WARM_INIT_DEADLINE_MS = 1500;
49286
+ var PREWARM_BOOT_ALLOWANCE_MS = 3e3;
49287
+ function warmInitDeadlineMs(spawnedAt, now = Date.now()) {
49288
+ const ageMs = Math.max(0, now - spawnedAt);
49289
+ return WARM_INIT_DEADLINE_MS + Math.max(0, PREWARM_BOOT_ALLOWANCE_MS - ageMs);
49290
+ }
49291
+ var CLAUDE_COMMAND = "claude";
49292
+ function buildClaudeCliArgv(envelope) {
49293
+ return [
48733
49294
  "-p",
49295
+ "--input-format",
49296
+ "stream-json",
48734
49297
  "--output-format",
48735
- "text",
49298
+ "stream-json",
49299
+ "--verbose",
49300
+ "--include-partial-messages",
49301
+ "--effort",
49302
+ resolveEffort(),
48736
49303
  "--safe-mode",
48737
49304
  "--tools",
48738
49305
  "",
48739
49306
  "--no-session-persistence",
48740
49307
  "--system-prompt",
48741
- buildSystemPrompt(envelope),
48742
- prompt
49308
+ buildSystemPrompt(envelope)
48743
49309
  ];
48744
- const options = {
48745
- encoding: "utf8",
48746
- timeout: remainingBudgetMs,
48747
- killSignal: "SIGTERM",
48748
- maxBuffer: 1024 * 1024,
48749
- cwd: envelope.base.cwd,
48750
- env: process.env,
48751
- windowsHide: true,
48752
- shell: false,
48753
- stdio: ["ignore", "pipe", "pipe"]
49310
+ }
49311
+ async function fetchClaudeCliCandidates(envelope, remainingBudgetMs, options = {}) {
49312
+ const startedAt = Date.now();
49313
+ const deadline = startedAt + remainingBudgetMs;
49314
+ const streamed = [];
49315
+ const { onCandidate, signal } = options;
49316
+ const spec = {
49317
+ command: CLAUDE_COMMAND,
49318
+ argv: buildClaudeCliArgv(envelope),
49319
+ cwd: envelope.base.cwd
48754
49320
  };
48755
- const { stdout } = await execFileAsync3("claude", args, options);
48756
- return ensureSelectableCandidates(parseCandidates(stdout));
49321
+ const prompt = buildPrompt(envelope);
49322
+ const createTextDeltaHandler = () => onCandidate ? createCandidateScanner((candidate) => {
49323
+ streamed.push(candidate);
49324
+ if (streamed.length === 1) {
49325
+ void appendDebugLog("provider", "cli first candidate", { ms: Date.now() - startedAt });
49326
+ }
49327
+ if (!signal?.aborted) {
49328
+ onCandidate(candidate);
49329
+ }
49330
+ }) : void 0;
49331
+ const runTurn = (child, budgetMs, initDeadlineMs) => runClaudeTurn({
49332
+ command: spec.command,
49333
+ argv: spec.argv,
49334
+ cwd: spec.cwd,
49335
+ env: process.env,
49336
+ prompt,
49337
+ budgetMs,
49338
+ signal,
49339
+ onTextDelta: createTextDeltaHandler(),
49340
+ onStage: (stage, detail) => {
49341
+ logTurnStage(stage, detail, Date.now() - startedAt, child !== void 0);
49342
+ },
49343
+ ...child ? { child } : {},
49344
+ ...initDeadlineMs === void 0 ? {} : { initDeadlineMs }
49345
+ });
49346
+ try {
49347
+ let text;
49348
+ let finishedNormally = true;
49349
+ let stopReason;
49350
+ try {
49351
+ const take = takePrewarmed(spec);
49352
+ if ("child" in take) {
49353
+ void appendDebugLog("provider", "prewarm hit");
49354
+ try {
49355
+ text = await runTurn(take.child, remainingBudgetMs, warmInitDeadlineMs(take.spawnedAt));
49356
+ } catch (error43) {
49357
+ const leftMs = deadline - Date.now();
49358
+ if (!isStalePrewarmFailure(error43) || streamed.length > 0 || signal?.aborted || leftMs <= 0) {
49359
+ throw error43;
49360
+ }
49361
+ void appendDebugLog("provider", "prewarm retry cold", { ms: Date.now() - startedAt });
49362
+ text = await runTurn(void 0, leftMs);
49363
+ }
49364
+ } else {
49365
+ void appendDebugLog("provider", "prewarm miss", { reason: take.miss });
49366
+ text = await runTurn(void 0, remainingBudgetMs);
49367
+ }
49368
+ } catch (error43) {
49369
+ if (!isTruncatedFailure(error43)) {
49370
+ throw error43;
49371
+ }
49372
+ const stdout = error43.stdout;
49373
+ text = typeof stdout === "string" ? stdout : "";
49374
+ finishedNormally = false;
49375
+ stopReason = error43.stopReason;
49376
+ }
49377
+ return ensureSelectableCandidates(
49378
+ finalCandidateList(text, streamed, finishedNormally, { stopReason })
49379
+ );
49380
+ } finally {
49381
+ schedulePrewarm(spec);
49382
+ }
49383
+ }
49384
+ function isStalePrewarmFailure(error43) {
49385
+ if (error43 === null || typeof error43 !== "object") {
49386
+ return false;
49387
+ }
49388
+ const reason = error43.reason;
49389
+ return reason === "exit-without-result" || reason === "stale-prewarm";
49390
+ }
49391
+ function isTruncatedFailure(error43) {
49392
+ if (error43 === null || typeof error43 !== "object") {
49393
+ return false;
49394
+ }
49395
+ const reason = error43.reason;
49396
+ return reason === "truncated";
49397
+ }
49398
+ var STAGE_LOG_NAMES = {
49399
+ spawned: "cli spawned",
49400
+ init: "cli init",
49401
+ "first-delta": "cli first delta",
49402
+ result: "cli result",
49403
+ exit: "cli exit",
49404
+ aborted: "cli aborted",
49405
+ "timed-out": "cli timed out",
49406
+ "init-timed-out": "cli init timed out"
49407
+ };
49408
+ function logTurnStage(stage, detail, ms, warm) {
49409
+ const fields = stage === "exit" ? { ms, warm, code: detail?.code ?? null, signal: detail?.signal ?? null } : { ms, warm };
49410
+ void appendDebugLog("provider", STAGE_LOG_NAMES[stage], fields);
48757
49411
  }
48758
49412
 
48759
49413
  // src/providers/claude-default.ts
@@ -48796,6 +49450,9 @@ function lookupApiKey() {
48796
49450
  return usableSecret(process.env.ANTHROPIC_API_KEY) ?? usableSecret(readEnvValueFromDotEnvLocal("ANTHROPIC_API_KEY"));
48797
49451
  }
48798
49452
  function hardError(error43) {
49453
+ if (error43 instanceof Error && error43.message === TRUNCATED_RESPONSE_MESSAGE) {
49454
+ return error43;
49455
+ }
48799
49456
  if (isEnoent(error43)) {
48800
49457
  return new Error(ENOENT_MESSAGE);
48801
49458
  }
@@ -48808,18 +49465,22 @@ function hardError(error43) {
48808
49465
  return new Error(D13_MESSAGE);
48809
49466
  }
48810
49467
  var claudeDefaultAdapter = {
48811
- async fetchCandidates(envelope) {
49468
+ async fetchCandidates(envelope, options) {
48812
49469
  const deadline = Date.now() + TOTAL_BUDGET_MS;
48813
49470
  const apiKey = lookupApiKey();
48814
49471
  if (apiKey) {
48815
49472
  void appendDebugLog("provider", "sdk path", {
48816
49473
  path: "sdk"
48817
49474
  });
48818
- return fetchCandidates(envelope, "", remainingMs(deadline));
49475
+ return fetchCandidates(envelope, "", remainingMs(deadline), options);
48819
49476
  }
48820
49477
  try {
48821
- return await fetchClaudeCliCandidates(envelope, remainingMs(deadline));
49478
+ return await fetchClaudeCliCandidates(envelope, remainingMs(deadline), options);
48822
49479
  } catch (error43) {
49480
+ if (isAbortError(error43)) {
49481
+ void appendDebugLog("provider", "claude-cli aborted");
49482
+ throw error43;
49483
+ }
48823
49484
  void appendDebugLog("provider", "claude-cli path", {
48824
49485
  path: "claude-cli",
48825
49486
  exitCode: exitCodeDetail(error43),
@@ -57261,8 +57922,8 @@ var Ink = class {
57261
57922
  };
57262
57923
  return { resume, [Symbol.asyncDispose]: resume };
57263
57924
  }
57264
- setAlternateScreen(enabled) {
57265
- this.alternateScreen = this.resolveAlternateScreenOption(enabled, this.interactive);
57925
+ setAlternateScreen(enabled2) {
57926
+ this.alternateScreen = this.resolveAlternateScreenOption(enabled2, this.interactive);
57266
57927
  if (this.alternateScreen) {
57267
57928
  this.writeBestEffort(this.options.stdout, base_exports.enterAlternativeScreen);
57268
57929
  this.writeBestEffort(this.options.stdout, hideCursorEscape);
@@ -58334,7 +58995,10 @@ var ipcRequestSchema = external_exports.discriminatedUnion("kind", [
58334
58995
  }),
58335
58996
  external_exports.object({
58336
58997
  kind: external_exports.literal("fetch-candidates"),
58337
- envelope: contextEnvelopeSchema
58998
+ envelope: contextEnvelopeSchema,
58999
+ // The client wants one `candidate` message per parsed row before the final
59000
+ // `candidates` message (Phase 09.1). Old daemons strip the key and reply once.
59001
+ stream: external_exports.literal(true).optional()
58338
59002
  })
58339
59003
  ]);
58340
59004
  var ipcResponseSchema = external_exports.discriminatedUnion("kind", [
@@ -58342,7 +59006,9 @@ var ipcResponseSchema = external_exports.discriminatedUnion("kind", [
58342
59006
  kind: external_exports.literal("pong"),
58343
59007
  fetchCandidates: external_exports.literal(true).optional(),
58344
59008
  // Daemon accepts envelopes carrying the `installed-tools` chunk (Phase 9).
58345
- installedTools: external_exports.literal(true).optional()
59009
+ installedTools: external_exports.literal(true).optional(),
59010
+ // Daemon streams `candidate` messages to a `stream: true` request (Phase 09.1).
59011
+ streamCandidates: external_exports.literal(true).optional()
58346
59012
  }),
58347
59013
  external_exports.object({
58348
59014
  kind: external_exports.literal("session-ready"),
@@ -58355,6 +59021,12 @@ var ipcResponseSchema = external_exports.discriminatedUnion("kind", [
58355
59021
  external_exports.object({
58356
59022
  kind: external_exports.literal("fetch-accepted")
58357
59023
  }),
59024
+ // One streamed row, sent only when the request carried `stream: true`.
59025
+ external_exports.object({
59026
+ kind: external_exports.literal("candidate"),
59027
+ candidate: commandCandidateSchema
59028
+ }),
59029
+ // The done signal and the authoritative final list, for every request.
58358
59030
  external_exports.object({
58359
59031
  kind: external_exports.literal("candidates"),
58360
59032
  candidates: candidateListSchema
@@ -58447,7 +59119,7 @@ function pingDaemonFetchCap(socketPath2) {
58447
59119
  done("unknown");
58448
59120
  return;
58449
59121
  }
58450
- const current2 = response.data.fetchCandidates === true && response.data.installedTools === true;
59122
+ const current2 = response.data.fetchCandidates === true && response.data.installedTools === true && response.data.streamCandidates === true;
58451
59123
  done(current2 ? "yes" : "no");
58452
59124
  });
58453
59125
  client.on("error", () => {
@@ -58568,12 +59240,19 @@ var IpcTransportError = class extends Error {
58568
59240
  this.name = "IpcTransportError";
58569
59241
  }
58570
59242
  };
58571
- function requestDaemonFetch(socketPath2, envelope, timeoutMs = FETCH_TIMEOUT_MS) {
59243
+ function requestDaemonFetch(socketPath2, envelope, timeoutMs = FETCH_TIMEOUT_MS, options = {}) {
59244
+ const { signal } = options;
59245
+ if (signal?.aborted) {
59246
+ return Promise.reject(abortError());
59247
+ }
58572
59248
  return new Promise((resolve3, reject) => {
58573
59249
  const client = net2.createConnection(socketPath2);
58574
59250
  let buf = "";
58575
59251
  let settled = false;
58576
59252
  let accepted = false;
59253
+ const onAbort = () => {
59254
+ finish(abortError());
59255
+ };
58577
59256
  const finish = (error43, candidates) => {
58578
59257
  if (settled) {
58579
59258
  return;
@@ -58581,6 +59260,7 @@ function requestDaemonFetch(socketPath2, envelope, timeoutMs = FETCH_TIMEOUT_MS)
58581
59260
  settled = true;
58582
59261
  clearTimeout(ackTimer);
58583
59262
  clearTimeout(timer);
59263
+ signal?.removeEventListener("abort", onAbort);
58584
59264
  client.destroy();
58585
59265
  if (error43) {
58586
59266
  reject(error43);
@@ -58596,6 +59276,7 @@ function requestDaemonFetch(socketPath2, envelope, timeoutMs = FETCH_TIMEOUT_MS)
58596
59276
  const timer = setTimeout(() => {
58597
59277
  finish(new IpcTransportError("daemon fetch timed out"));
58598
59278
  }, timeoutMs);
59279
+ signal?.addEventListener("abort", onAbort, { once: true });
58599
59280
  const handleResponse = (parsed) => {
58600
59281
  const response = ipcResponseSchema.safeParse(parsed);
58601
59282
  if (!response.success) {
@@ -58607,6 +59288,12 @@ function requestDaemonFetch(socketPath2, envelope, timeoutMs = FETCH_TIMEOUT_MS)
58607
59288
  clearTimeout(ackTimer);
58608
59289
  return;
58609
59290
  }
59291
+ if (response.data.kind === "candidate") {
59292
+ accepted = true;
59293
+ clearTimeout(ackTimer);
59294
+ options.onCandidate?.(response.data.candidate);
59295
+ return;
59296
+ }
58610
59297
  if (response.data.kind === "candidates") {
58611
59298
  accepted = true;
58612
59299
  finish(void 0, response.data.candidates);
@@ -58620,11 +59307,12 @@ function requestDaemonFetch(socketPath2, envelope, timeoutMs = FETCH_TIMEOUT_MS)
58620
59307
  finish(new IpcTransportError("unexpected daemon fetch response"));
58621
59308
  };
58622
59309
  client.on("connect", () => {
58623
- client.write(`${JSON.stringify({ kind: "fetch-candidates", envelope })}
59310
+ client.write(`${JSON.stringify({ kind: "fetch-candidates", envelope, stream: true })}
58624
59311
  `);
58625
59312
  });
59313
+ client.setEncoding("utf8");
58626
59314
  client.on("data", (chunk) => {
58627
- buf += chunk.toString();
59315
+ buf += String(chunk);
58628
59316
  let nl = buf.indexOf("\n");
58629
59317
  while (nl !== -1) {
58630
59318
  const line = buf.slice(0, nl).trim();
@@ -58649,19 +59337,36 @@ function requestDaemonFetch(socketPath2, envelope, timeoutMs = FETCH_TIMEOUT_MS)
58649
59337
  client.on("error", (error43) => {
58650
59338
  finish(new IpcTransportError(error43.message));
58651
59339
  });
59340
+ client.on("close", () => {
59341
+ finish(new IpcTransportError("daemon connection closed"));
59342
+ });
58652
59343
  });
58653
59344
  }
58654
- async function fetchCandidatesForUi(socketPath2, envelope) {
59345
+ async function fetchCandidatesForUi(socketPath2, envelope, options = {}) {
59346
+ let forwarded = 0;
59347
+ const daemonOptions = {
59348
+ signal: options.signal,
59349
+ onCandidate: (candidate) => {
59350
+ forwarded++;
59351
+ options.onCandidate?.(candidate);
59352
+ }
59353
+ };
58655
59354
  try {
58656
- const candidates = await requestDaemonFetch(socketPath2, envelope);
59355
+ const candidates = await requestDaemonFetch(socketPath2, envelope, void 0, daemonOptions);
58657
59356
  void appendDebugLog("client", "candidates from daemon", { count: candidates.length });
58658
59357
  return candidates;
58659
59358
  } catch (error43) {
58660
59359
  if (!(error43 instanceof IpcTransportError)) {
58661
59360
  throw error43;
58662
59361
  }
59362
+ if (options.signal?.aborted) {
59363
+ throw abortError();
59364
+ }
59365
+ if (forwarded > 0) {
59366
+ throw error43;
59367
+ }
58663
59368
  void appendDebugLog("client", "daemon fetch fallback", { message: error43.message });
58664
- return resolveClaudeDefaultAdapter().fetchCandidates(envelope);
59369
+ return resolveClaudeDefaultAdapter().fetchCandidates(envelope, options);
58665
59370
  }
58666
59371
  }
58667
59372
 
@@ -59421,6 +60126,8 @@ function filterCandidates(candidates, query) {
59421
60126
  );
59422
60127
  }
59423
60128
  var MODAL_VIEWPORT_LINES = 18;
60129
+ var INCOMPLETE_LIST_NOTE = "list may be incomplete";
60130
+ var LIST_STATUS_ROW_LINES = 1;
59424
60131
  var MARK_COLUMNS = 2;
59425
60132
  function estimateCandidateSelectLines(input) {
59426
60133
  let lines = 0;
@@ -59451,6 +60158,9 @@ function estimateCandidateSelectLines(input) {
59451
60158
  lines += 1;
59452
60159
  }
59453
60160
  }
60161
+ if (input.listState === "arriving" || input.listState === "partial") {
60162
+ lines += LIST_STATUS_ROW_LINES;
60163
+ }
59454
60164
  }
59455
60165
  const visible = input.candidates !== null && input.error !== true ? filterCandidates(input.candidates, input.query) : [];
59456
60166
  const selectedCandidate = visible[input.selectedIndex];
@@ -59510,30 +60220,42 @@ function CandidateSelect({
59510
60220
  onSelect,
59511
60221
  onCancel,
59512
60222
  error: error43,
59513
- onLayoutLinesChange
60223
+ onLayoutLinesChange,
60224
+ listState = "complete"
59514
60225
  }) {
59515
60226
  const { isRawModeSupported } = use_stdin_default();
59516
60227
  const { stdout } = use_stdout_default();
59517
60228
  const modalWidth = Math.min(MODAL_MAX_WIDTH, stdout.columns || MODAL_MAX_WIDTH);
59518
60229
  const contentWidth = modalWidth - MODAL_PADDING_X - ROW_GUTTER.length;
59519
60230
  const [selectedIndex, setSelectedIndex] = (0, import_react35.useState)(0);
59520
- const [query, setQuery] = (0, import_react35.useState)(initialQuery ?? "");
60231
+ const [query, setQuery] = (0, import_react35.useState)("");
59521
60232
  const [installToggled, setInstallToggled] = (0, import_react35.useState)(/* @__PURE__ */ new Set());
59522
60233
  const [prevCandidates, setPrevCandidates] = (0, import_react35.useState)(null);
59523
60234
  const paneHold = (0, import_react35.useRef)({
59524
60235
  candidates: null,
59525
60236
  lines: 0
59526
60237
  });
60238
+ const pendingInput = (0, import_react35.useRef)(null);
60239
+ const renderToken = {};
59527
60240
  if (candidates !== prevCandidates) {
59528
60241
  setPrevCandidates(candidates);
59529
- if (selectedIndex !== 0) {
59530
- setSelectedIndex(0);
59531
- }
59532
- if (installToggled.size > 0) {
59533
- setInstallToggled(/* @__PURE__ */ new Set());
59534
- }
59535
- if (candidates && candidates.length > 0 && filterCandidates(candidates, query).length === 0) {
59536
- setQuery("");
60242
+ if (prevCandidates === null) {
60243
+ if (selectedIndex !== 0) {
60244
+ setSelectedIndex(0);
60245
+ }
60246
+ if (installToggled.size > 0) {
60247
+ setInstallToggled(/* @__PURE__ */ new Set());
60248
+ }
60249
+ } else if (candidates !== null) {
60250
+ const highlighted = filterCandidates(prevCandidates, query)[selectedIndex];
60251
+ const visible = filterCandidates(candidates, query);
60252
+ const highlightedKey = highlighted === void 0 ? void 0 : candidateKey(highlighted);
60253
+ const kept = highlightedKey === void 0 ? -1 : visible.findIndex((candidate) => candidateKey(candidate) === highlightedKey);
60254
+ const current2 = Number.isFinite(selectedIndex) && selectedIndex >= 0 ? selectedIndex : 0;
60255
+ const nextIndex = kept >= 0 ? kept : Math.max(0, Math.min(current2, visible.length - 1));
60256
+ if (nextIndex !== selectedIndex) {
60257
+ setSelectedIndex(nextIndex);
60258
+ }
59537
60259
  }
59538
60260
  }
59539
60261
  (0, import_react35.useEffect)(() => {
@@ -59541,9 +60263,18 @@ function CandidateSelect({
59541
60263
  onCancel();
59542
60264
  }
59543
60265
  }, [isRawModeSupported, onCancel]);
59544
- (0, import_react35.useEffect)(() => {
59545
- setSelectedIndex(0);
59546
- }, [query]);
60266
+ const applyInput = (live, next) => {
60267
+ pendingInput.current = next;
60268
+ if (next.query !== live.query) {
60269
+ setQuery(next.query);
60270
+ }
60271
+ if (next.selectedIndex !== live.selectedIndex) {
60272
+ setSelectedIndex(next.selectedIndex);
60273
+ }
60274
+ if (next.installToggled !== live.installToggled) {
60275
+ setInstallToggled(next.installToggled);
60276
+ }
60277
+ };
59547
60278
  const includeDevFooter = process.env.QQ_DEV_ROOT != null;
59548
60279
  (0, import_react35.useEffect)(() => {
59549
60280
  if (!onLayoutLinesChange) {
@@ -59552,6 +60283,9 @@ function CandidateSelect({
59552
60283
  if (candidates === null && error43 !== true) {
59553
60284
  return;
59554
60285
  }
60286
+ if (listState === "arriving" && error43 !== true) {
60287
+ return;
60288
+ }
59555
60289
  const layout = {
59556
60290
  candidates,
59557
60291
  initialQuery,
@@ -59560,7 +60294,8 @@ function CandidateSelect({
59560
60294
  error: error43,
59561
60295
  contentWidth,
59562
60296
  includeDevFooter,
59563
- installToggled
60297
+ installToggled,
60298
+ listState
59564
60299
  };
59565
60300
  const previousHeld = paneHold.current.candidates === candidates ? paneHold.current.lines : 0;
59566
60301
  const lines = heldPaneLines(previousHeld, layout);
@@ -59575,57 +60310,63 @@ function CandidateSelect({
59575
60310
  contentWidth,
59576
60311
  includeDevFooter,
59577
60312
  installToggled,
60313
+ listState,
59578
60314
  onLayoutLinesChange
59579
60315
  ]);
59580
60316
  use_input_default(
59581
60317
  (input, key) => {
60318
+ const live = pendingInput.current?.render === renderToken ? pendingInput.current : { render: renderToken, query, selectedIndex, installToggled };
59582
60319
  if (key.escape) {
59583
60320
  onCancel();
59584
60321
  return;
59585
60322
  }
59586
60323
  if (key.backspace) {
59587
- setQuery((q) => q.slice(0, -1));
60324
+ if (live.query === "") {
60325
+ return;
60326
+ }
60327
+ applyInput(live, { ...live, query: live.query.slice(0, -1), selectedIndex: 0 });
59588
60328
  return;
59589
60329
  }
59590
60330
  if (key.tab) {
59591
60331
  if (!candidates) return;
59592
- const visible = filterCandidates(candidates, query);
59593
- const selected = visible[selectedIndex] ?? visible[0];
60332
+ const visible = filterCandidates(candidates, live.query);
60333
+ const selected = visible[live.selectedIndex] ?? visible[0];
59594
60334
  if (selected?.install?.form == null) return;
59595
60335
  const id = candidateKey(selected);
59596
- setInstallToggled((previous) => {
59597
- const next = new Set(previous);
59598
- if (next.has(id)) {
59599
- next.delete(id);
59600
- } else {
59601
- next.add(id);
59602
- }
59603
- return next;
59604
- });
60336
+ const toggled = new Set(live.installToggled);
60337
+ if (toggled.has(id)) {
60338
+ toggled.delete(id);
60339
+ } else {
60340
+ toggled.add(id);
60341
+ }
60342
+ applyInput(live, { ...live, installToggled: toggled });
59605
60343
  return;
59606
60344
  }
59607
60345
  if (key.return && candidates) {
59608
- const visible = filterCandidates(candidates, query);
60346
+ const visible = filterCandidates(candidates, live.query);
59609
60347
  if (visible.length === 0) return;
59610
- const selected = visible[selectedIndex] ?? visible[0];
59611
- const command = displayedCommand(selected, installToggled);
60348
+ const selected = visible[live.selectedIndex] ?? visible[0];
60349
+ const command = displayedCommand(selected, live.installToggled);
59612
60350
  const original = normalizeCommand2(selected.command);
59613
60351
  const explanation = selected.explanation || `see man ${original.split(" ")[0]}`;
59614
60352
  onSelect(command, explanation);
59615
60353
  return;
59616
60354
  }
59617
60355
  if (input && !key.ctrl && !key.meta) {
59618
- setQuery((q) => q + input);
60356
+ applyInput(live, { ...live, query: live.query + input, selectedIndex: 0 });
59619
60357
  return;
59620
60358
  }
59621
60359
  if (key.upArrow && candidates) {
59622
- const visible = filterCandidates(candidates, query);
59623
- setSelectedIndex((i) => i === 0 ? visible.length - 1 : i - 1);
60360
+ const visible = filterCandidates(candidates, live.query);
60361
+ if (visible.length === 0) return;
60362
+ const index = live.selectedIndex;
60363
+ applyInput(live, { ...live, selectedIndex: index <= 0 ? visible.length - 1 : index - 1 });
59624
60364
  return;
59625
60365
  }
59626
60366
  if (key.downArrow && candidates) {
59627
- const visible = filterCandidates(candidates, query);
59628
- setSelectedIndex((i) => (i + 1) % visible.length);
60367
+ const visible = filterCandidates(candidates, live.query);
60368
+ if (visible.length === 0) return;
60369
+ applyInput(live, { ...live, selectedIndex: (live.selectedIndex + 1) % visible.length });
59629
60370
  return;
59630
60371
  }
59631
60372
  },
@@ -59684,13 +60425,15 @@ function CandidateSelect({
59684
60425
  initialQuery && initialQuery.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Box_default, { flexGrow: 0, height: 1, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Text, { dimColor: true, wrap: "truncate", children: `queque \u203A ${initialQuery}` }) }),
59685
60426
  /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(SearchInput, { query }),
59686
60427
  content,
60428
+ candidates !== null && error43 !== true && listState === "arriving" && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(LoadingSpinner, {}),
60429
+ candidates !== null && error43 !== true && listState === "partial" && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Text, { dimColor: true, children: INCOMPLETE_LIST_NOTE }),
59687
60430
  showDestructiveWarning && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Box_default, { marginTop: 1, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Text, { color: "yellow", children: DESTRUCTIVE_WARNING_TEXT }) }),
59688
60431
  legend !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(Box_default, { marginTop: 1, children: [
59689
60432
  /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Text, { color: "cyan", children: MISSING_TOOL_MARK }),
59690
60433
  /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Text, { dimColor: true, children: legend.slice(MISSING_TOOL_MARK.length) })
59691
60434
  ] }),
59692
60435
  /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(ControlsLine, {}),
59693
- process.env.QQ_DEV_ROOT != null && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Box_default, { justifyContent: "flex-end", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Text, { color: "yellow", children: `dev v${"0.5.2"} ${"5ac6fa8"}` }) })
60436
+ process.env.QQ_DEV_ROOT != null && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Box_default, { justifyContent: "flex-end", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Text, { color: "yellow", children: `dev v${"0.6.0"} ${"3d6ffbb"}` }) })
59694
60437
  ] });
59695
60438
  }
59696
60439
 
@@ -60040,13 +60783,17 @@ async function runForegroundClient(args) {
60040
60783
  await new Promise((resolve3) => {
60041
60784
  let unmount;
60042
60785
  let resolved = false;
60043
- const buildCandidateElement = (candidates, errorState) => import_react36.default.createElement(CandidateSelect, {
60786
+ const fetchAbort = new AbortController();
60787
+ const streamed = [];
60788
+ const buildCandidateElement = (candidates, errorState, listState = "complete") => import_react36.default.createElement(CandidateSelect, {
60044
60789
  candidates,
60045
60790
  initialQuery: request.lbuffer,
60046
60791
  error: errorState,
60792
+ listState,
60047
60793
  onSelect: async (command, explanation) => {
60048
60794
  if (resolved) return;
60049
60795
  resolved = true;
60796
+ fetchAbort.abort();
60050
60797
  const { lbuffer, rbuffer } = buildShellBuffers(command, explanation);
60051
60798
  await writeShellResult(resultFile, {
60052
60799
  kind: "replace-buffer",
@@ -60059,6 +60806,7 @@ async function runForegroundClient(args) {
60059
60806
  onCancel: async () => {
60060
60807
  if (resolved) return;
60061
60808
  resolved = true;
60809
+ fetchAbort.abort();
60062
60810
  await writeShellResult(resultFile, { kind: "cancel" });
60063
60811
  unmount?.();
60064
60812
  },
@@ -60075,6 +60823,7 @@ async function runForegroundClient(args) {
60075
60823
  }
60076
60824
  };
60077
60825
  const cleanupOnSignal = () => {
60826
+ fetchAbort.abort();
60078
60827
  app.unmount();
60079
60828
  clearScrollReserve();
60080
60829
  process.exit(0);
@@ -60088,17 +60837,50 @@ async function runForegroundClient(args) {
60088
60837
  if (!skipClear) clearScrollReserve();
60089
60838
  resolve3();
60090
60839
  };
60091
- fetchCandidatesForUi(socketPath2, envelope).then((candidates) => {
60840
+ const onCandidate = (candidate) => {
60841
+ if (resolved) return;
60842
+ streamed.push(candidate);
60843
+ if (streamed.length === 1) {
60844
+ void appendDebugLog("client", "first candidate received");
60845
+ }
60846
+ app.rerender(
60847
+ buildCandidateElement(
60848
+ annotateCandidates([...streamed], toolInventory),
60849
+ void 0,
60850
+ "arriving"
60851
+ )
60852
+ );
60853
+ };
60854
+ fetchCandidatesForUi(socketPath2, envelope, { signal: fetchAbort.signal, onCandidate }).then((candidates) => {
60855
+ if (resolved) return;
60092
60856
  const displayCandidates = annotateCandidates(candidates, toolInventory);
60093
60857
  void appendDebugLog("client", "candidates received", {
60094
60858
  count: candidates.length,
60859
+ streamed: streamed.length,
60095
60860
  flagged: displayCandidates.filter((c) => c.missingTools !== void 0).length
60096
60861
  });
60097
- app.rerender(buildCandidateElement(displayCandidates));
60862
+ app.rerender(buildCandidateElement(displayCandidates, void 0, "complete"));
60098
60863
  }).catch(async (err) => {
60864
+ if (isAbortError(err)) {
60865
+ void appendDebugLog("client", "fetch aborted");
60866
+ return;
60867
+ }
60099
60868
  const message = err instanceof Error ? err.message : String(err);
60100
60869
  void appendDebugLog("client", "llm request failed", { message });
60101
60870
  if (resolved) return;
60871
+ if (streamed.length > 0) {
60872
+ void appendDebugLog("client", "llm request failed after rows", {
60873
+ count: streamed.length
60874
+ });
60875
+ app.rerender(
60876
+ buildCandidateElement(
60877
+ annotateCandidates([...streamed], toolInventory),
60878
+ void 0,
60879
+ "partial"
60880
+ )
60881
+ );
60882
+ return;
60883
+ }
60102
60884
  resolved = true;
60103
60885
  takeover?.clearNow();
60104
60886
  const errorMsg = formatFifoError(message);
@@ -60152,6 +60934,8 @@ function startDaemonServer(socketPath2) {
60152
60934
  const MAX_BUF_BYTES = 256 * 1024;
60153
60935
  const server = net3.createServer((socket) => {
60154
60936
  let buf = "";
60937
+ const connectionAbort = new AbortController();
60938
+ socket.setEncoding("utf8");
60155
60939
  void appendDebugLog("daemon", "socket connected", { socketPath: socketPath2 });
60156
60940
  const writeJson = (payload) => {
60157
60941
  if (socket.destroyed) {
@@ -60161,11 +60945,11 @@ function startDaemonServer(socketPath2) {
60161
60945
  `);
60162
60946
  };
60163
60947
  socket.on("data", (chunk) => {
60164
- if (buf.length + chunk.length > MAX_BUF_BYTES) {
60948
+ if (Buffer.byteLength(buf) + Buffer.byteLength(chunk) > MAX_BUF_BYTES) {
60165
60949
  socket.destroy(new Error("IPC message too large"));
60166
60950
  return;
60167
60951
  }
60168
- buf += chunk.toString();
60952
+ buf += chunk;
60169
60953
  let nl = buf.indexOf("\n");
60170
60954
  while (nl !== -1) {
60171
60955
  const line = buf.slice(0, nl).trim();
@@ -60187,7 +60971,12 @@ function startDaemonServer(socketPath2) {
60187
60971
  void appendDebugLog("daemon", "request received", { kind: req.kind });
60188
60972
  switch (req.kind) {
60189
60973
  case "ping": {
60190
- writeJson({ kind: "pong", fetchCandidates: true, installedTools: true });
60974
+ writeJson({
60975
+ kind: "pong",
60976
+ fetchCandidates: true,
60977
+ installedTools: true,
60978
+ streamCandidates: true
60979
+ });
60191
60980
  void appendDebugLog("daemon", "replied pong");
60192
60981
  break;
60193
60982
  }
@@ -60204,14 +60993,42 @@ function startDaemonServer(socketPath2) {
60204
60993
  }
60205
60994
  case "fetch-candidates": {
60206
60995
  writeJson({ kind: "fetch-accepted" });
60207
- bootstrapBuiltins();
60208
- const adapter = resolveClaudeDefaultAdapter();
60209
- void adapter.fetchCandidates(req.envelope).then((candidates) => {
60996
+ const envelope = req.envelope;
60997
+ let streamedIndex = 0;
60998
+ const onCandidate = req.stream === true ? (candidate) => {
60999
+ const index = streamedIndex++;
61000
+ if (connectionAbort.signal.aborted) {
61001
+ void appendDebugLog("daemon", "candidate dropped", { index });
61002
+ return;
61003
+ }
61004
+ const row = commandCandidateSchema.safeParse(candidate);
61005
+ if (!row.success) {
61006
+ return;
61007
+ }
61008
+ writeJson({ kind: "candidate", candidate: row.data });
61009
+ void appendDebugLog("daemon", "replied candidate", { index });
61010
+ } : void 0;
61011
+ void Promise.resolve().then(() => {
61012
+ bootstrapBuiltins();
61013
+ const adapter = resolveClaudeDefaultAdapter();
61014
+ return adapter.fetchCandidates(envelope, {
61015
+ signal: connectionAbort.signal,
61016
+ onCandidate
61017
+ });
61018
+ }).then((candidates) => {
61019
+ if (connectionAbort.signal.aborted) {
61020
+ void appendDebugLog("daemon", "fetch aborted");
61021
+ return;
61022
+ }
60210
61023
  writeJson({ kind: "candidates", candidates });
60211
61024
  void appendDebugLog("daemon", "replied candidates", {
60212
61025
  count: candidates.length
60213
61026
  });
60214
61027
  }).catch((error43) => {
61028
+ if (connectionAbort.signal.aborted) {
61029
+ void appendDebugLog("daemon", "fetch aborted");
61030
+ return;
61031
+ }
60215
61032
  const message = error43 instanceof Error ? error43.message : String(error43);
60216
61033
  writeJson({ kind: "fetch-error", message });
60217
61034
  void appendDebugLog("daemon", "replied fetch-error", { message });
@@ -60223,6 +61040,9 @@ function startDaemonServer(socketPath2) {
60223
61040
  });
60224
61041
  socket.on("error", () => {
60225
61042
  });
61043
+ socket.on("close", () => {
61044
+ connectionAbort.abort();
61045
+ });
60226
61046
  });
60227
61047
  server.on("error", reject);
60228
61048
  server.listen(socketPath2, () => {
@@ -60233,6 +61053,32 @@ function startDaemonServer(socketPath2) {
60233
61053
  }
60234
61054
 
60235
61055
  // src/cli/commands/daemon.ts
61056
+ function trackConnections(server) {
61057
+ const connections = /* @__PURE__ */ new Set();
61058
+ server.on("connection", (socket) => {
61059
+ connections.add(socket);
61060
+ socket.once("close", () => {
61061
+ connections.delete(socket);
61062
+ });
61063
+ });
61064
+ return connections;
61065
+ }
61066
+ function shutdownDaemon(server, connections, options = {}) {
61067
+ const exit = options.exit ?? (() => process.exit(0));
61068
+ const graceMs = options.graceMs ?? SIGKILL_GRACE_MS + 100;
61069
+ disablePrewarm();
61070
+ const inFlight = connections.size;
61071
+ server.close(() => {
61072
+ if (inFlight === 0) {
61073
+ exit();
61074
+ return;
61075
+ }
61076
+ setTimeout(exit, graceMs);
61077
+ });
61078
+ for (const socket of connections) {
61079
+ socket.destroy();
61080
+ }
61081
+ }
60236
61082
  async function daemonCommand(options) {
60237
61083
  const sock = options.socket ?? socketPath();
60238
61084
  void appendDebugLog("daemon", "command start", { socket: sock, ensure: options.ensure });
@@ -60242,12 +61088,12 @@ async function daemonCommand(options) {
60242
61088
  return;
60243
61089
  }
60244
61090
  const server = await startDaemonServer(sock);
61091
+ const connections = trackConnections(server);
60245
61092
  void appendDebugLog("daemon", "server started", { socket: sock });
61093
+ enablePrewarm();
60246
61094
  const shutdown = () => {
60247
- void appendDebugLog("daemon", "shutdown", { socket: sock });
60248
- server.close(() => {
60249
- process.exit(0);
60250
- });
61095
+ void appendDebugLog("daemon", "shutdown", { socket: sock, inFlight: connections.size });
61096
+ shutdownDaemon(server, connections);
60251
61097
  };
60252
61098
  process.once("SIGTERM", shutdown);
60253
61099
  process.once("SIGINT", shutdown);
@@ -60336,6 +61182,7 @@ async function main(argv = process.argv.slice(2)) {
60336
61182
  const requestFile = requiredOption("--request-file", options.requestFile);
60337
61183
  const resultFile = requiredOption("--result-file", options.resultFile);
60338
61184
  await clientCommand({ requestFile, resultFile, resultMode: options.resultMode });
61185
+ process.exit(process.exitCode ?? 0);
60339
61186
  });
60340
61187
  cli.command(
60341
61188
  "init <shell>",