flavor-code 1.2.16 → 1.2.18

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.
@@ -15,7 +15,7 @@ export interface AgentRunRequest {
15
15
  /** Messages supplied while a tool batch is running, injected before the next model call. */
16
16
  getSteeringMessages?(): readonly string[];
17
17
  }
18
- export type AgentErrorCode = ProviderErrorCode | "iteration_limit" | "incomplete_stream";
18
+ export type AgentErrorCode = ProviderErrorCode | "iteration_limit" | "incomplete_stream" | "empty_response";
19
19
  export interface AgentError {
20
20
  code: AgentErrorCode;
21
21
  message: string;
@@ -14,8 +14,9 @@ import {
14
14
  modelContentTranscriptText,
15
15
  packageVersion,
16
16
  redactErrorText,
17
- transcriptReducer
18
- } from "./chunk-5SRARNAC.js";
17
+ transcriptReducer,
18
+ withTimeout
19
+ } from "./chunk-STZQ3Y3P.js";
19
20
  import "./chunk-IG4Z23CL.js";
20
21
  import {
21
22
  Box_default,
@@ -874,21 +875,27 @@ function CodeBlock({ token }) {
874
875
  const langLabel = token.lang?.trim();
875
876
  const label = langLabel && langLabel.length > 0 ? langLabel : "code";
876
877
  const topBarWidth = Math.max(0, columns - 4 - label.length - 4);
877
- return /* @__PURE__ */ jsxs(Box_default, { flexDirection: "column", paddingX: 1, children: [
878
- /* @__PURE__ */ jsxs(Text, { color: "gray", children: [
879
- "\u256D\u2500 ",
880
- /* @__PURE__ */ jsx(Text, { color: "cyan", children: label }),
881
- " ",
882
- "\u2500".repeat(topBarWidth),
883
- "\u256E"
884
- ] }),
885
- /* @__PURE__ */ jsx(Box_default, { flexDirection: "column", paddingX: 1, children: truncated.map((line, i) => /* @__PURE__ */ jsx(Text, { color: "gray", children: line }, i)) }),
886
- /* @__PURE__ */ jsxs(Text, { color: "gray", children: [
887
- "\u2570",
888
- "\u2500".repeat(Math.max(0, columns - 6)),
889
- "\u256F"
878
+ return (
879
+ // Code blocks are self-contained painted regions. Marking the region
880
+ // opaque prevents ScrollBox's incremental renderer from blitting cached
881
+ // blank cells over clean, single-line code bodies during transcript
882
+ // scrolls/redraws.
883
+ /* @__PURE__ */ jsxs(Box_default, { flexDirection: "column", paddingX: 1, opaque: true, children: [
884
+ /* @__PURE__ */ jsxs(Text, { color: "gray", children: [
885
+ "\u256D\u2500 ",
886
+ /* @__PURE__ */ jsx(Text, { color: "cyan", children: label }),
887
+ " ",
888
+ "\u2500".repeat(topBarWidth),
889
+ "\u256E"
890
+ ] }),
891
+ /* @__PURE__ */ jsx(Box_default, { flexDirection: "column", paddingX: 1, children: truncated.map((line, i) => /* @__PURE__ */ jsx(Text, { color: "gray", children: line }, i)) }),
892
+ /* @__PURE__ */ jsxs(Text, { color: "gray", children: [
893
+ "\u2570",
894
+ "\u2500".repeat(Math.max(0, columns - 6)),
895
+ "\u256F"
896
+ ] })
890
897
  ] })
891
- ] });
898
+ );
892
899
  }
893
900
  function BlockquoteView({ token }) {
894
901
  return /* @__PURE__ */ jsx(Box_default, { flexDirection: "column", children: token.tokens.map((child, i) => {
@@ -1098,9 +1105,15 @@ function AssistantTextInner({ text }) {
1098
1105
  var AssistantText = React2.memo(AssistantTextInner);
1099
1106
 
1100
1107
  // src/ui/signals.ts
1101
- function createSessionInterruptHandler(getSession, shutdown) {
1108
+ function createSessionInterruptHandler(getSession, shutdown, options) {
1109
+ let shuttingDown = false;
1102
1110
  return () => {
1111
+ if (shuttingDown) {
1112
+ options.forceExit();
1113
+ return;
1114
+ }
1103
1115
  if (getSession()?.interrupt() === "cancelled") return;
1116
+ shuttingDown = true;
1104
1117
  void shutdown();
1105
1118
  };
1106
1119
  }
@@ -1901,7 +1914,8 @@ function App({ workspace, home, resumeSession, instanceId, palAlias }) {
1901
1914
  const interruptRef = useRef4(void 0);
1902
1915
  interruptRef.current ??= createSessionInterruptHandler(
1903
1916
  () => runtimeRef.current?.session,
1904
- () => shutdownRef.current(runtimeRef.current)
1917
+ () => shutdownRef.current(runtimeRef.current),
1918
+ { forceExit: () => process.exit(1) }
1905
1919
  );
1906
1920
  const interrupt = interruptRef.current;
1907
1921
  useEffect3(() => {
@@ -3529,11 +3543,23 @@ async function submitSafely(session, prompt, report) {
3529
3543
  safeReport(report, safeUiError(error));
3530
3544
  }
3531
3545
  }
3532
- async function shutdownRuntime(runtime, exit, report) {
3546
+ var SHUTDOWN_TIMEOUT_MS = 8e3;
3547
+ var FORCE_EXIT_GRACE_MS = 250;
3548
+ async function shutdownRuntime(runtime, exit, report, options = {}) {
3549
+ const timeoutMs = options.shutdownTimeoutMs ?? SHUTDOWN_TIMEOUT_MS;
3550
+ let timedOut = false;
3533
3551
  try {
3534
- await closeAndDisposeRuntime(runtime, report);
3552
+ const outcome = await withTimeout(closeAndDisposeRuntime(runtime, report), timeoutMs);
3553
+ timedOut = outcome.timedOut;
3554
+ if (timedOut) report(`Shutdown timed out after ${timeoutMs}ms; forcing exit.`);
3535
3555
  } finally {
3536
3556
  exit();
3557
+ if (timedOut) {
3558
+ const forceExit = options.forceExit ?? (() => {
3559
+ process.exit(1);
3560
+ });
3561
+ setTimeout(forceExit, FORCE_EXIT_GRACE_MS);
3562
+ }
3537
3563
  }
3538
3564
  }
3539
3565
  async function closeAndDisposeRuntime(runtime, report) {
@@ -3565,6 +3591,7 @@ export {
3565
3591
  HISTORY_CAP,
3566
3592
  MentionMenu,
3567
3593
  PromptLine,
3594
+ SHUTDOWN_TIMEOUT_MS,
3568
3595
  SinglePendingPrompt,
3569
3596
  TerminalLayout,
3570
3597
  appRuntimeOptions,
@@ -4,7 +4,7 @@ import {
4
4
  message,
5
5
  redactErrorText,
6
6
  redactSecrets
7
- } from "./chunk-5SRARNAC.js";
7
+ } from "./chunk-STZQ3Y3P.js";
8
8
 
9
9
  // src/rpc/server.ts
10
10
  import { createInterface } from "readline";
@@ -62,6 +62,52 @@ function normalizeProviderError(error) {
62
62
  return { code, message: message2 };
63
63
  }
64
64
 
65
+ // src/utils/async.ts
66
+ function withTimeout(promise, ms) {
67
+ return new Promise((resolve26, reject) => {
68
+ let settled = false;
69
+ const timer = setTimeout(() => {
70
+ if (settled) return;
71
+ settled = true;
72
+ resolve26({ timedOut: true, value: void 0 });
73
+ }, ms);
74
+ timer.unref?.();
75
+ promise.then(
76
+ (value) => {
77
+ if (!settled) {
78
+ settled = true;
79
+ clearTimeout(timer);
80
+ resolve26({ timedOut: false, value });
81
+ }
82
+ },
83
+ (error) => {
84
+ if (!settled) {
85
+ settled = true;
86
+ clearTimeout(timer);
87
+ reject(error);
88
+ }
89
+ }
90
+ );
91
+ });
92
+ }
93
+ function awaitWithSignal(promise, signal) {
94
+ signal.throwIfAborted();
95
+ return new Promise((resolve26, reject) => {
96
+ const onAbort = () => reject(signal.reason);
97
+ signal.addEventListener("abort", onAbort, { once: true });
98
+ promise.then(
99
+ (value) => {
100
+ signal.removeEventListener("abort", onAbort);
101
+ resolve26(value);
102
+ },
103
+ (error) => {
104
+ signal.removeEventListener("abort", onAbort);
105
+ reject(error);
106
+ }
107
+ );
108
+ });
109
+ }
110
+
65
111
  // src/utils/error.ts
66
112
  function message(error) {
67
113
  return error instanceof Error ? error.message : String(error);
@@ -5058,25 +5104,6 @@ function hasCycle(nodes) {
5058
5104
  return visited !== nodes.length;
5059
5105
  }
5060
5106
 
5061
- // src/utils/async.ts
5062
- function awaitWithSignal(promise, signal) {
5063
- signal.throwIfAborted();
5064
- return new Promise((resolve26, reject) => {
5065
- const onAbort = () => reject(signal.reason);
5066
- signal.addEventListener("abort", onAbort, { once: true });
5067
- promise.then(
5068
- (value) => {
5069
- signal.removeEventListener("abort", onAbort);
5070
- resolve26(value);
5071
- },
5072
- (error) => {
5073
- signal.removeEventListener("abort", onAbort);
5074
- reject(error);
5075
- }
5076
- );
5077
- });
5078
- }
5079
-
5080
5107
  // src/agent/subagents.ts
5081
5108
  var SubagentResultSchema = z5.object({
5082
5109
  taskId: z5.string().min(1),
@@ -6539,7 +6566,8 @@ var RECOVERABLE_MODEL_ERRORS = /* @__PURE__ */ new Set([
6539
6566
  "network",
6540
6567
  "rate_limit",
6541
6568
  "unknown",
6542
- "incomplete_stream"
6569
+ "incomplete_stream",
6570
+ "empty_response"
6543
6571
  ]);
6544
6572
  function envMaxIterations() {
6545
6573
  const raw = process.env["FLAVOR_MAX_ITERATIONS"];
@@ -6733,6 +6761,12 @@ var AgentLoop = class {
6733
6761
  } finally {
6734
6762
  if (!completed && terminalError === void 0) {
6735
6763
  terminalError = { code: "incomplete_stream", message: "Provider stream ended without a done or error event" };
6764
+ } else if (completed && assistantText.trim().length === 0 && collectedToolCalls.length === 0) {
6765
+ completed = false;
6766
+ terminalError = {
6767
+ code: "empty_response",
6768
+ message: "Provider completed without visible text or tool calls"
6769
+ };
6736
6770
  }
6737
6771
  providerError = terminalError !== void 0;
6738
6772
  try {
@@ -6772,7 +6806,7 @@ var AgentLoop = class {
6772
6806
  totalCacheCreationTokens += usage.cacheCreationTokens ?? 0;
6773
6807
  yield { type: "usage", ...usage, totalInputTokens, totalOutputTokens };
6774
6808
  }
6775
- const providerProducedOutput = assistantText.length > 0 || collectedToolCalls.length > 0;
6809
+ const providerProducedOutput = assistantText.trim().length > 0 || collectedToolCalls.length > 0;
6776
6810
  if (terminalError?.code === "context_overflow" && !reactiveRetried && !providerProducedOutput && attempt < maxAttempts) {
6777
6811
  let compacted = false;
6778
6812
  try {
@@ -18986,6 +19020,7 @@ var INTERRUPTED_TASK_PLAN_CONTEXT = [
18986
19020
  "Reassess the current query independently.",
18987
19021
  "If the current work needs a plan, create a fresh one with TaskPlan before using TaskUpdate."
18988
19022
  ].join(" ");
19023
+ var DEFAULT_SHUTDOWN_STEP_TIMEOUT_MS = 3e3;
18989
19024
  function createPromptEnvironment(input = {}) {
18990
19025
  const now = input.now ?? /* @__PURE__ */ new Date();
18991
19026
  return {
@@ -19755,7 +19790,7 @@ async function createProductionRuntime(options) {
19755
19790
  const messages = allMessages.slice(memoryLifecycle.messageStart ?? 0);
19756
19791
  const transcriptHash = memoryTranscriptHash(messages);
19757
19792
  if (memoryLifecycle.status === "completed" && memoryLifecycle.transcriptHash === transcriptHash) {
19758
- return "Task was already completed and evaluated for long-term memory.";
19793
+ return "This conversation segment was already evaluated for long-term memory.";
19759
19794
  }
19760
19795
  if (!manual && memoryBehavior.autoExtractPaused) {
19761
19796
  return "Automatic long-term-memory extraction is paused after repeated dismissals; use /finish, /remember, or an explicit \u201Cremember\u201D request to store memory manually.";
@@ -19763,7 +19798,7 @@ async function createProductionRuntime(options) {
19763
19798
  autoStoredContents.length = 0;
19764
19799
  const finalization = memoryCoordinator === void 0 || !config.memory.autoExtract || options.approvalPolicy === "deny" ? { evaluated: true, candidates: false, stored: 0 } : await memoryCoordinator.finalize(memoryLifecycle.taskId ?? sessionId, messages);
19765
19800
  if (!finalization.evaluated) {
19766
- return "Task memory evaluation failed and was not marked complete; retry /finish after checking diagnostics.";
19801
+ return "Long-term-memory evaluation failed; retry /finish after checking diagnostics.";
19767
19802
  }
19768
19803
  memoryLifecycle = {
19769
19804
  status: "completed",
@@ -19775,9 +19810,9 @@ async function createProductionRuntime(options) {
19775
19810
  await persist();
19776
19811
  if (finalization.candidates && finalization.stored > 0) {
19777
19812
  const storedText = autoStoredContents[0] ?? "a high-confidence memory";
19778
- return `Task completed. Stored high-confidence long-term memory directly: "${storedText}". Run /forget to remove it if undesired.`;
19813
+ return `Long-term memory updated. Stored high-confidence entry: "${storedText}". Run /forget to remove it if undesired.`;
19779
19814
  }
19780
- return finalization.candidates ? "Task completed. Review the generated long-term-memory candidates before anything is stored." : "Task completed; no durable long-term-memory candidates passed the threshold.";
19815
+ return finalization.candidates ? "Long-term-memory evaluation completed. Review the generated candidates before anything is stored." : "Long-term-memory evaluation completed; no durable candidates passed the threshold.";
19781
19816
  };
19782
19817
  let explicitMemoryRequest;
19783
19818
  let automaticMemoryTask = false;
@@ -19788,10 +19823,10 @@ async function createProductionRuntime(options) {
19788
19823
  timelineState = transcriptReducer(timelineState, { type: "submit", prompt });
19789
19824
  automaticMemoryTask = !prompt.startsWith("/") && harness.permissionProfile !== "d2c";
19790
19825
  if (automaticMemoryTask) memoryReviews.dismissAll();
19791
- if (!prompt.startsWith("/") && memoryLifecycle.status === "completed") {
19826
+ if (!prompt.startsWith("/")) {
19792
19827
  memoryLifecycle = {
19793
19828
  status: "active",
19794
- taskId: createMemoryTaskId(),
19829
+ taskId: memoryLifecycle.status === "completed" ? createMemoryTaskId() : memoryLifecycle.taskId ?? createMemoryTaskId(),
19795
19830
  messageStart: harness.main.context.snapshot().messages.length
19796
19831
  };
19797
19832
  }
@@ -19833,9 +19868,9 @@ async function createProductionRuntime(options) {
19833
19868
  }
19834
19869
  if (explicit === void 0 && automatic && event.payload.outcome === "completed" && memoryCoordinator !== void 0 && config.memory.autoExtract && options.approvalPolicy !== "deny" && !memoryBehavior.autoExtractPaused) {
19835
19870
  const result = await finalizeMemoryTask();
19836
- if (result.startsWith("Task memory evaluation failed")) {
19871
+ if (result.startsWith("Long-term-memory evaluation failed")) {
19837
19872
  emitOutput({ type: "notice", message: "Automatic long-term-memory evaluation failed; use /finish to retry after checking diagnostics." });
19838
- } else if (result.startsWith("Task completed. Review") || result.startsWith("Task completed. Stored")) {
19873
+ } else if (result.startsWith("Long-term-memory evaluation completed. Review") || result.startsWith("Long-term memory updated.")) {
19839
19874
  emitOutput({ type: "notice", message: result });
19840
19875
  }
19841
19876
  }
@@ -20554,23 +20589,24 @@ ${renderMemoryDocument(entries)}`;
20554
20589
  async dispose() {
20555
20590
  if (disposed) return;
20556
20591
  disposed = true;
20592
+ const stepTimeoutMs = options.shutdownStepTimeoutMs ?? DEFAULT_SHUTDOWN_STEP_TIMEOUT_MS;
20557
20593
  collaborationEventsOpen = false;
20558
20594
  unsubscribeCollaboration?.();
20559
- await collaborationEventPump;
20595
+ if (collaborationEventPump !== void 0) await boundedStep(stepTimeoutMs, diagnostics, "collaboration-event-pump", collaborationEventPump);
20560
20596
  mcpDiscarded = true;
20561
- await sleepScheduler?.dispose();
20562
- await memoryCoordinator?.flush();
20563
- await persist();
20564
- await persistTail;
20565
- if (ideSessionId !== void 0) await ide.endSession(ideSessionId);
20566
- await executionEnvironment?.dispose();
20597
+ if (sleepScheduler !== void 0) await boundedStep(stepTimeoutMs, diagnostics, "sleep-scheduler", sleepScheduler.dispose());
20598
+ if (memoryCoordinator !== void 0) await boundedStep(stepTimeoutMs, diagnostics, "memory-flush", memoryCoordinator.flush());
20599
+ await boundedStep(stepTimeoutMs, diagnostics, "persist", persist());
20600
+ if (persistTail !== void 0) await boundedStep(stepTimeoutMs, diagnostics, "persist-tail", persistTail);
20601
+ if (ideSessionId !== void 0) await boundedStep(stepTimeoutMs, diagnostics, "ide-end-session", ide.endSession(ideSessionId));
20602
+ if (executionEnvironment !== void 0) await boundedStep(stepTimeoutMs, diagnostics, "execution-environment", executionEnvironment.dispose());
20567
20603
  terminals.dispose();
20568
- await jobs.dispose();
20604
+ await boundedStep(stepTimeoutMs, diagnostics, "jobs", jobs.dispose());
20569
20605
  auditLogger.close();
20570
20606
  evolveService.dispose();
20571
20607
  memoryReviews.dispose();
20572
- await collaborationClient?.close();
20573
- await cleanupProduction(approvals, questions, pluginHost, mcpManager, harness);
20608
+ if (collaborationClient !== void 0) await boundedStep(stepTimeoutMs, diagnostics, "collaboration-close", collaborationClient.close());
20609
+ await cleanupProduction(approvals, questions, pluginHost, mcpManager, harness, { stepTimeoutMs });
20574
20610
  }
20575
20611
  };
20576
20612
  } catch (primaryError) {
@@ -20578,16 +20614,17 @@ ${renderMemoryDocument(entries)}`;
20578
20614
  mcpDiscarded = true;
20579
20615
  memoryReviews.dispose();
20580
20616
  try {
20581
- if (ideSessionId !== void 0) await ide.endSession(ideSessionId);
20582
- await sleepScheduler?.dispose();
20583
- await executionEnvironment?.dispose();
20617
+ const stepTimeoutMs = options.shutdownStepTimeoutMs ?? DEFAULT_SHUTDOWN_STEP_TIMEOUT_MS;
20618
+ if (ideSessionId !== void 0) await boundedStep(stepTimeoutMs, diagnostics, "ide-end-session", ide.endSession(ideSessionId));
20619
+ if (sleepScheduler !== void 0) await boundedStep(stepTimeoutMs, diagnostics, "sleep-scheduler", sleepScheduler.dispose());
20620
+ if (executionEnvironment !== void 0) await boundedStep(stepTimeoutMs, diagnostics, "execution-environment", executionEnvironment.dispose());
20584
20621
  terminals.dispose();
20585
- await jobs.dispose();
20622
+ await boundedStep(stepTimeoutMs, diagnostics, "jobs", jobs.dispose());
20586
20623
  unsubscribeCollaboration?.();
20587
20624
  pendingCollaborationEvents.length = 0;
20588
- await collaborationEventPump;
20589
- await collaborationClient?.close();
20590
- await cleanupProduction(approvals, questions, pluginHost, mcpManager, harnessCreated ? harness : void 0);
20625
+ if (collaborationEventPump !== void 0) await boundedStep(stepTimeoutMs, diagnostics, "collaboration-event-pump", collaborationEventPump);
20626
+ if (collaborationClient !== void 0) await boundedStep(stepTimeoutMs, diagnostics, "collaboration-close", collaborationClient.close());
20627
+ await cleanupProduction(approvals, questions, pluginHost, mcpManager, harnessCreated ? harness : void 0, { stepTimeoutMs });
20591
20628
  } catch (cleanupError) {
20592
20629
  attachCleanupError(primaryError, cleanupError);
20593
20630
  }
@@ -21208,7 +21245,8 @@ function storedConversation(snapshot4) {
21208
21245
  })
21209
21246
  };
21210
21247
  }
21211
- async function cleanupProduction(approvals, questions, pluginHost, mcpManager, harness) {
21248
+ async function cleanupProduction(approvals, questions, pluginHost, mcpManager, harness, cleanupOptions = { stepTimeoutMs: DEFAULT_SHUTDOWN_STEP_TIMEOUT_MS }) {
21249
+ const { stepTimeoutMs } = cleanupOptions;
21212
21250
  let primary;
21213
21251
  try {
21214
21252
  approvals.resolve("deny");
@@ -21222,7 +21260,8 @@ async function cleanupProduction(approvals, questions, pluginHost, mcpManager, h
21222
21260
  else attachCleanupError(primary, error);
21223
21261
  }
21224
21262
  try {
21225
- await pluginHost.unloadAll();
21263
+ const outcome = await withTimeout(pluginHost.unloadAll(), stepTimeoutMs);
21264
+ if (outcome.timedOut) primary = new Error(`Plugin unload timed out after ${stepTimeoutMs}ms`);
21226
21265
  } catch (error) {
21227
21266
  if (primary === void 0) primary = error;
21228
21267
  else attachCleanupError(primary, error);
@@ -21234,7 +21273,14 @@ async function cleanupProduction(approvals, questions, pluginHost, mcpManager, h
21234
21273
  else attachCleanupError(primary, error);
21235
21274
  }
21236
21275
  try {
21237
- await mcpManager?.close();
21276
+ if (mcpManager !== void 0) {
21277
+ const outcome = await withTimeout(mcpManager.close(), stepTimeoutMs);
21278
+ if (outcome.timedOut) {
21279
+ const timeoutError = new Error(`MCP shutdown timed out after ${stepTimeoutMs}ms`);
21280
+ if (primary === void 0) primary = timeoutError;
21281
+ else attachCleanupError(primary, timeoutError);
21282
+ }
21283
+ }
21238
21284
  } catch (error) {
21239
21285
  if (primary === void 0) primary = error;
21240
21286
  else attachCleanupError(primary, error);
@@ -21242,6 +21288,10 @@ async function cleanupProduction(approvals, questions, pluginHost, mcpManager, h
21242
21288
  }
21243
21289
  if (primary !== void 0) throw primary;
21244
21290
  }
21291
+ async function boundedStep(timeoutMs, diagnostics, name, step) {
21292
+ const outcome = await withTimeout(step, timeoutMs);
21293
+ if (outcome.timedOut) diagnostics.push(`Shutdown step "${name}" timed out after ${timeoutMs}ms and was abandoned.`);
21294
+ }
21245
21295
  function attachCleanupError(primary, cleanup) {
21246
21296
  if (typeof primary !== "object" && typeof primary !== "function" || primary === null || !Object.isExtensible(primary)) return;
21247
21297
  try {
@@ -21327,6 +21377,7 @@ function createMemoryTaskId() {
21327
21377
 
21328
21378
  export {
21329
21379
  modelContentTranscriptText,
21380
+ withTimeout,
21330
21381
  message,
21331
21382
  isDestructiveTool,
21332
21383
  redactErrorText,
package/dist/cli.js CHANGED
@@ -3,7 +3,7 @@ import { createRequire } from "node:module"; const require = createRequire(impor
3
3
  import {
4
4
  FlavorRpcServer,
5
5
  TraceRecorder
6
- } from "./chunk-PGS3M32Y.js";
6
+ } from "./chunk-JB57IX2A.js";
7
7
  import {
8
8
  staticTaskLines
9
9
  } from "./chunk-KKIBYDJI.js";
@@ -22,7 +22,7 @@ import {
22
22
  packageVersion,
23
23
  redactErrorText,
24
24
  runPalBroker
25
- } from "./chunk-5SRARNAC.js";
25
+ } from "./chunk-STZQ3Y3P.js";
26
26
  import {
27
27
  McpServerConfigSchema,
28
28
  McpServerNameSchema,
@@ -861,7 +861,7 @@ async function runInteractiveCli(props) {
861
861
  const [{ render, AlternateScreen }, { createElement }, { App }] = await Promise.all([
862
862
  import("./claude-ink-ORUA7GWG.js"),
863
863
  import("react"),
864
- import("./app-7ZCJ3UOI.js")
864
+ import("./app-YE3I7JMR.js")
865
865
  ]);
866
866
  const instance = await render(createElement(
867
867
  AlternateScreen,
@@ -4348,6 +4348,33 @@ function hasCycle(nodes) {
4348
4348
  }
4349
4349
 
4350
4350
  // src/utils/async.ts
4351
+ function withTimeout(promise, ms) {
4352
+ return new Promise((resolve34, reject) => {
4353
+ let settled = false;
4354
+ const timer = setTimeout(() => {
4355
+ if (settled) return;
4356
+ settled = true;
4357
+ resolve34({ timedOut: true, value: void 0 });
4358
+ }, ms);
4359
+ timer.unref?.();
4360
+ promise.then(
4361
+ (value) => {
4362
+ if (!settled) {
4363
+ settled = true;
4364
+ clearTimeout(timer);
4365
+ resolve34({ timedOut: false, value });
4366
+ }
4367
+ },
4368
+ (error) => {
4369
+ if (!settled) {
4370
+ settled = true;
4371
+ clearTimeout(timer);
4372
+ reject(error);
4373
+ }
4374
+ }
4375
+ );
4376
+ });
4377
+ }
4351
4378
  function awaitWithSignal3(promise, signal) {
4352
4379
  signal.throwIfAborted();
4353
4380
  return new Promise((resolve34, reject) => {
@@ -6128,7 +6155,8 @@ var RECOVERABLE_MODEL_ERRORS = /* @__PURE__ */ new Set([
6128
6155
  "network",
6129
6156
  "rate_limit",
6130
6157
  "unknown",
6131
- "incomplete_stream"
6158
+ "incomplete_stream",
6159
+ "empty_response"
6132
6160
  ]);
6133
6161
  function envMaxIterations() {
6134
6162
  const raw = process.env["FLAVOR_MAX_ITERATIONS"];
@@ -6322,6 +6350,12 @@ var AgentLoop = class {
6322
6350
  } finally {
6323
6351
  if (!completed && terminalError === void 0) {
6324
6352
  terminalError = { code: "incomplete_stream", message: "Provider stream ended without a done or error event" };
6353
+ } else if (completed && assistantText.trim().length === 0 && collectedToolCalls.length === 0) {
6354
+ completed = false;
6355
+ terminalError = {
6356
+ code: "empty_response",
6357
+ message: "Provider completed without visible text or tool calls"
6358
+ };
6325
6359
  }
6326
6360
  providerError = terminalError !== void 0;
6327
6361
  try {
@@ -6361,7 +6395,7 @@ var AgentLoop = class {
6361
6395
  totalCacheCreationTokens += usage.cacheCreationTokens ?? 0;
6362
6396
  yield { type: "usage", ...usage, totalInputTokens, totalOutputTokens };
6363
6397
  }
6364
- const providerProducedOutput = assistantText.length > 0 || collectedToolCalls.length > 0;
6398
+ const providerProducedOutput = assistantText.trim().length > 0 || collectedToolCalls.length > 0;
6365
6399
  if (terminalError?.code === "context_overflow" && !reactiveRetried && !providerProducedOutput && attempt < maxAttempts) {
6366
6400
  let compacted = false;
6367
6401
  try {
@@ -22689,6 +22723,7 @@ var INTERRUPTED_TASK_PLAN_CONTEXT = [
22689
22723
  "Reassess the current query independently.",
22690
22724
  "If the current work needs a plan, create a fresh one with TaskPlan before using TaskUpdate."
22691
22725
  ].join(" ");
22726
+ var DEFAULT_SHUTDOWN_STEP_TIMEOUT_MS = 3e3;
22692
22727
  function createPromptEnvironment(input = {}) {
22693
22728
  const now = input.now ?? /* @__PURE__ */ new Date();
22694
22729
  return {
@@ -23458,7 +23493,7 @@ async function createProductionRuntime(options) {
23458
23493
  const messages = allMessages.slice(memoryLifecycle.messageStart ?? 0);
23459
23494
  const transcriptHash = memoryTranscriptHash(messages);
23460
23495
  if (memoryLifecycle.status === "completed" && memoryLifecycle.transcriptHash === transcriptHash) {
23461
- return "Task was already completed and evaluated for long-term memory.";
23496
+ return "This conversation segment was already evaluated for long-term memory.";
23462
23497
  }
23463
23498
  if (!manual && memoryBehavior.autoExtractPaused) {
23464
23499
  return "Automatic long-term-memory extraction is paused after repeated dismissals; use /finish, /remember, or an explicit \u201Cremember\u201D request to store memory manually.";
@@ -23466,7 +23501,7 @@ async function createProductionRuntime(options) {
23466
23501
  autoStoredContents.length = 0;
23467
23502
  const finalization = memoryCoordinator === void 0 || !config.memory.autoExtract || options.approvalPolicy === "deny" ? { evaluated: true, candidates: false, stored: 0 } : await memoryCoordinator.finalize(memoryLifecycle.taskId ?? sessionId, messages);
23468
23503
  if (!finalization.evaluated) {
23469
- return "Task memory evaluation failed and was not marked complete; retry /finish after checking diagnostics.";
23504
+ return "Long-term-memory evaluation failed; retry /finish after checking diagnostics.";
23470
23505
  }
23471
23506
  memoryLifecycle = {
23472
23507
  status: "completed",
@@ -23478,9 +23513,9 @@ async function createProductionRuntime(options) {
23478
23513
  await persist();
23479
23514
  if (finalization.candidates && finalization.stored > 0) {
23480
23515
  const storedText = autoStoredContents[0] ?? "a high-confidence memory";
23481
- return `Task completed. Stored high-confidence long-term memory directly: "${storedText}". Run /forget to remove it if undesired.`;
23516
+ return `Long-term memory updated. Stored high-confidence entry: "${storedText}". Run /forget to remove it if undesired.`;
23482
23517
  }
23483
- return finalization.candidates ? "Task completed. Review the generated long-term-memory candidates before anything is stored." : "Task completed; no durable long-term-memory candidates passed the threshold.";
23518
+ return finalization.candidates ? "Long-term-memory evaluation completed. Review the generated candidates before anything is stored." : "Long-term-memory evaluation completed; no durable candidates passed the threshold.";
23484
23519
  };
23485
23520
  let explicitMemoryRequest;
23486
23521
  let automaticMemoryTask = false;
@@ -23491,10 +23526,10 @@ async function createProductionRuntime(options) {
23491
23526
  timelineState = transcriptReducer(timelineState, { type: "submit", prompt });
23492
23527
  automaticMemoryTask = !prompt.startsWith("/") && harness.permissionProfile !== "d2c";
23493
23528
  if (automaticMemoryTask) memoryReviews.dismissAll();
23494
- if (!prompt.startsWith("/") && memoryLifecycle.status === "completed") {
23529
+ if (!prompt.startsWith("/")) {
23495
23530
  memoryLifecycle = {
23496
23531
  status: "active",
23497
- taskId: createMemoryTaskId(),
23532
+ taskId: memoryLifecycle.status === "completed" ? createMemoryTaskId() : memoryLifecycle.taskId ?? createMemoryTaskId(),
23498
23533
  messageStart: harness.main.context.snapshot().messages.length
23499
23534
  };
23500
23535
  }
@@ -23536,9 +23571,9 @@ async function createProductionRuntime(options) {
23536
23571
  }
23537
23572
  if (explicit === void 0 && automatic && event.payload.outcome === "completed" && memoryCoordinator !== void 0 && config.memory.autoExtract && options.approvalPolicy !== "deny" && !memoryBehavior.autoExtractPaused) {
23538
23573
  const result = await finalizeMemoryTask();
23539
- if (result.startsWith("Task memory evaluation failed")) {
23574
+ if (result.startsWith("Long-term-memory evaluation failed")) {
23540
23575
  emitOutput({ type: "notice", message: "Automatic long-term-memory evaluation failed; use /finish to retry after checking diagnostics." });
23541
- } else if (result.startsWith("Task completed. Review") || result.startsWith("Task completed. Stored")) {
23576
+ } else if (result.startsWith("Long-term-memory evaluation completed. Review") || result.startsWith("Long-term memory updated.")) {
23542
23577
  emitOutput({ type: "notice", message: result });
23543
23578
  }
23544
23579
  }
@@ -24257,23 +24292,24 @@ ${renderMemoryDocument(entries)}`;
24257
24292
  async dispose() {
24258
24293
  if (disposed) return;
24259
24294
  disposed = true;
24295
+ const stepTimeoutMs = options.shutdownStepTimeoutMs ?? DEFAULT_SHUTDOWN_STEP_TIMEOUT_MS;
24260
24296
  collaborationEventsOpen = false;
24261
24297
  unsubscribeCollaboration?.();
24262
- await collaborationEventPump;
24298
+ if (collaborationEventPump !== void 0) await boundedStep(stepTimeoutMs, diagnostics, "collaboration-event-pump", collaborationEventPump);
24263
24299
  mcpDiscarded = true;
24264
- await sleepScheduler?.dispose();
24265
- await memoryCoordinator?.flush();
24266
- await persist();
24267
- await persistTail;
24268
- if (ideSessionId !== void 0) await ide.endSession(ideSessionId);
24269
- await executionEnvironment?.dispose();
24300
+ if (sleepScheduler !== void 0) await boundedStep(stepTimeoutMs, diagnostics, "sleep-scheduler", sleepScheduler.dispose());
24301
+ if (memoryCoordinator !== void 0) await boundedStep(stepTimeoutMs, diagnostics, "memory-flush", memoryCoordinator.flush());
24302
+ await boundedStep(stepTimeoutMs, diagnostics, "persist", persist());
24303
+ if (persistTail !== void 0) await boundedStep(stepTimeoutMs, diagnostics, "persist-tail", persistTail);
24304
+ if (ideSessionId !== void 0) await boundedStep(stepTimeoutMs, diagnostics, "ide-end-session", ide.endSession(ideSessionId));
24305
+ if (executionEnvironment !== void 0) await boundedStep(stepTimeoutMs, diagnostics, "execution-environment", executionEnvironment.dispose());
24270
24306
  terminals.dispose();
24271
- await jobs.dispose();
24307
+ await boundedStep(stepTimeoutMs, diagnostics, "jobs", jobs.dispose());
24272
24308
  auditLogger.close();
24273
24309
  evolveService.dispose();
24274
24310
  memoryReviews.dispose();
24275
- await collaborationClient?.close();
24276
- await cleanupProduction(approvals, questions, pluginHost, mcpManager, harness);
24311
+ if (collaborationClient !== void 0) await boundedStep(stepTimeoutMs, diagnostics, "collaboration-close", collaborationClient.close());
24312
+ await cleanupProduction(approvals, questions, pluginHost, mcpManager, harness, { stepTimeoutMs });
24277
24313
  }
24278
24314
  };
24279
24315
  } catch (primaryError) {
@@ -24281,16 +24317,17 @@ ${renderMemoryDocument(entries)}`;
24281
24317
  mcpDiscarded = true;
24282
24318
  memoryReviews.dispose();
24283
24319
  try {
24284
- if (ideSessionId !== void 0) await ide.endSession(ideSessionId);
24285
- await sleepScheduler?.dispose();
24286
- await executionEnvironment?.dispose();
24320
+ const stepTimeoutMs = options.shutdownStepTimeoutMs ?? DEFAULT_SHUTDOWN_STEP_TIMEOUT_MS;
24321
+ if (ideSessionId !== void 0) await boundedStep(stepTimeoutMs, diagnostics, "ide-end-session", ide.endSession(ideSessionId));
24322
+ if (sleepScheduler !== void 0) await boundedStep(stepTimeoutMs, diagnostics, "sleep-scheduler", sleepScheduler.dispose());
24323
+ if (executionEnvironment !== void 0) await boundedStep(stepTimeoutMs, diagnostics, "execution-environment", executionEnvironment.dispose());
24287
24324
  terminals.dispose();
24288
- await jobs.dispose();
24325
+ await boundedStep(stepTimeoutMs, diagnostics, "jobs", jobs.dispose());
24289
24326
  unsubscribeCollaboration?.();
24290
24327
  pendingCollaborationEvents.length = 0;
24291
- await collaborationEventPump;
24292
- await collaborationClient?.close();
24293
- await cleanupProduction(approvals, questions, pluginHost, mcpManager, harnessCreated ? harness : void 0);
24328
+ if (collaborationEventPump !== void 0) await boundedStep(stepTimeoutMs, diagnostics, "collaboration-event-pump", collaborationEventPump);
24329
+ if (collaborationClient !== void 0) await boundedStep(stepTimeoutMs, diagnostics, "collaboration-close", collaborationClient.close());
24330
+ await cleanupProduction(approvals, questions, pluginHost, mcpManager, harnessCreated ? harness : void 0, { stepTimeoutMs });
24294
24331
  } catch (cleanupError) {
24295
24332
  attachCleanupError(primaryError, cleanupError);
24296
24333
  }
@@ -24911,7 +24948,8 @@ function storedConversation(snapshot4) {
24911
24948
  })
24912
24949
  };
24913
24950
  }
24914
- async function cleanupProduction(approvals, questions, pluginHost, mcpManager, harness) {
24951
+ async function cleanupProduction(approvals, questions, pluginHost, mcpManager, harness, cleanupOptions = { stepTimeoutMs: DEFAULT_SHUTDOWN_STEP_TIMEOUT_MS }) {
24952
+ const { stepTimeoutMs } = cleanupOptions;
24915
24953
  let primary;
24916
24954
  try {
24917
24955
  approvals.resolve("deny");
@@ -24925,7 +24963,8 @@ async function cleanupProduction(approvals, questions, pluginHost, mcpManager, h
24925
24963
  else attachCleanupError(primary, error);
24926
24964
  }
24927
24965
  try {
24928
- await pluginHost.unloadAll();
24966
+ const outcome = await withTimeout(pluginHost.unloadAll(), stepTimeoutMs);
24967
+ if (outcome.timedOut) primary = new Error(`Plugin unload timed out after ${stepTimeoutMs}ms`);
24929
24968
  } catch (error) {
24930
24969
  if (primary === void 0) primary = error;
24931
24970
  else attachCleanupError(primary, error);
@@ -24937,7 +24976,14 @@ async function cleanupProduction(approvals, questions, pluginHost, mcpManager, h
24937
24976
  else attachCleanupError(primary, error);
24938
24977
  }
24939
24978
  try {
24940
- await mcpManager?.close();
24979
+ if (mcpManager !== void 0) {
24980
+ const outcome = await withTimeout(mcpManager.close(), stepTimeoutMs);
24981
+ if (outcome.timedOut) {
24982
+ const timeoutError = new Error(`MCP shutdown timed out after ${stepTimeoutMs}ms`);
24983
+ if (primary === void 0) primary = timeoutError;
24984
+ else attachCleanupError(primary, timeoutError);
24985
+ }
24986
+ }
24941
24987
  } catch (error) {
24942
24988
  if (primary === void 0) primary = error;
24943
24989
  else attachCleanupError(primary, error);
@@ -24945,6 +24991,10 @@ async function cleanupProduction(approvals, questions, pluginHost, mcpManager, h
24945
24991
  }
24946
24992
  if (primary !== void 0) throw primary;
24947
24993
  }
24994
+ async function boundedStep(timeoutMs, diagnostics, name, step) {
24995
+ const outcome = await withTimeout(step, timeoutMs);
24996
+ if (outcome.timedOut) diagnostics.push(`Shutdown step "${name}" timed out after ${timeoutMs}ms and was abandoned.`);
24997
+ }
24948
24998
  function attachCleanupError(primary, cleanup) {
24949
24999
  if (typeof primary !== "object" && typeof primary !== "function" || primary === null || !Object.isExtensible(primary)) return;
24950
25000
  try {
@@ -11,6 +11,12 @@ import { type TranscriptState } from "./ui/transcript.js";
11
11
  import { MemoryReviewBridge } from "./memory/review.js";
12
12
  import { type JobSnapshot } from "./jobs/registry.js";
13
13
  import { type PalClientLike } from "./pals/tools.js";
14
+ /**
15
+ * Per-step shutdown budget. On Windows a hung child-process pipe, named pipe,
16
+ * or MCP stdio server can block one cleanup step forever; abandoning that step
17
+ * after the budget lets the remaining steps — and process exit — proceed.
18
+ */
19
+ export declare const DEFAULT_SHUTDOWN_STEP_TIMEOUT_MS = 3000;
14
20
  export interface ProductionRuntimeOptions {
15
21
  workspace?: string;
16
22
  home?: string;
@@ -27,6 +33,8 @@ export interface ProductionRuntimeOptions {
27
33
  resumeSession?: string | true;
28
34
  /** Test and embedding seam for creating configured MCP clients. */
29
35
  mcpClientFactory?: McpClientFactory;
36
+ /** Per-step timeout (ms) for shutdown/disposal steps so a hung cleanup cannot block process exit. */
37
+ shutdownStepTimeoutMs?: number;
30
38
  /** Additional tools provided by embedders (e.g. desktop-only D2C tools). */
31
39
  extraTools?: readonly ToolDefinition<unknown>[];
32
40
  /** Explicitly opt into CLI-local collaboration. Omit for print/RPC/eval callers. */
package/dist/sdk/index.js CHANGED
@@ -4,7 +4,7 @@ import {
4
4
  FlavorRpcServer,
5
5
  TraceRecordSchema,
6
6
  TraceRecorder
7
- } from "../chunk-PGS3M32Y.js";
7
+ } from "../chunk-JB57IX2A.js";
8
8
  import {
9
9
  AgentMessageQueue,
10
10
  DockerExecutionEnvironment,
@@ -13,7 +13,7 @@ import {
13
13
  WorkspaceCheckpointStore,
14
14
  buildDockerInvocation,
15
15
  createProductionRuntime
16
- } from "../chunk-5SRARNAC.js";
16
+ } from "../chunk-STZQ3Y3P.js";
17
17
  import "../chunk-IG4Z23CL.js";
18
18
  import "../chunk-RQTYHUMK.js";
19
19
  import "../chunk-S32JRDZK.js";
@@ -1,3 +1,17 @@
1
+ export interface TimeoutOutcome<T> {
2
+ /** True when the deadline passed before the promise settled. */
3
+ timedOut: boolean;
4
+ /** The promise value; undefined when timed out (or when the value itself is undefined). */
5
+ value: T | undefined;
6
+ }
7
+ /**
8
+ * Race a promise against a deadline. Settles like the promise when it finishes
9
+ * in time; when the deadline passes first it resolves with `{ timedOut: true }`.
10
+ * The timer is always cleared so it never keeps the event loop alive on its own.
11
+ * The underlying promise is NOT cancelled — it keeps running in the background —
12
+ * so only use this for work you are willing to abandon (e.g. shutdown cleanup).
13
+ */
14
+ export declare function withTimeout<T>(promise: Promise<T>, ms: number): Promise<TimeoutOutcome<T>>;
1
15
  /**
2
16
  * Race a promise against an AbortSignal. If the signal fires before the
3
17
  * promise settles the returned promise rejects with the signal's reason.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "flavor-code",
3
- "version": "1.2.16",
3
+ "version": "1.2.18",
4
4
  "author": "YachuanWzh",
5
5
  "type": "module",
6
6
  "publishConfig": {