flavor-code 1.2.17 → 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.
@@ -14,8 +14,9 @@ import {
14
14
  modelContentTranscriptText,
15
15
  packageVersion,
16
16
  redactErrorText,
17
- transcriptReducer
18
- } from "./chunk-QQCXJC4U.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-QQCXJC4U.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),
@@ -18993,6 +19020,7 @@ var INTERRUPTED_TASK_PLAN_CONTEXT = [
18993
19020
  "Reassess the current query independently.",
18994
19021
  "If the current work needs a plan, create a fresh one with TaskPlan before using TaskUpdate."
18995
19022
  ].join(" ");
19023
+ var DEFAULT_SHUTDOWN_STEP_TIMEOUT_MS = 3e3;
18996
19024
  function createPromptEnvironment(input = {}) {
18997
19025
  const now = input.now ?? /* @__PURE__ */ new Date();
18998
19026
  return {
@@ -20561,23 +20589,24 @@ ${renderMemoryDocument(entries)}`;
20561
20589
  async dispose() {
20562
20590
  if (disposed) return;
20563
20591
  disposed = true;
20592
+ const stepTimeoutMs = options.shutdownStepTimeoutMs ?? DEFAULT_SHUTDOWN_STEP_TIMEOUT_MS;
20564
20593
  collaborationEventsOpen = false;
20565
20594
  unsubscribeCollaboration?.();
20566
- await collaborationEventPump;
20595
+ if (collaborationEventPump !== void 0) await boundedStep(stepTimeoutMs, diagnostics, "collaboration-event-pump", collaborationEventPump);
20567
20596
  mcpDiscarded = true;
20568
- await sleepScheduler?.dispose();
20569
- await memoryCoordinator?.flush();
20570
- await persist();
20571
- await persistTail;
20572
- if (ideSessionId !== void 0) await ide.endSession(ideSessionId);
20573
- 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());
20574
20603
  terminals.dispose();
20575
- await jobs.dispose();
20604
+ await boundedStep(stepTimeoutMs, diagnostics, "jobs", jobs.dispose());
20576
20605
  auditLogger.close();
20577
20606
  evolveService.dispose();
20578
20607
  memoryReviews.dispose();
20579
- await collaborationClient?.close();
20580
- 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 });
20581
20610
  }
20582
20611
  };
20583
20612
  } catch (primaryError) {
@@ -20585,16 +20614,17 @@ ${renderMemoryDocument(entries)}`;
20585
20614
  mcpDiscarded = true;
20586
20615
  memoryReviews.dispose();
20587
20616
  try {
20588
- if (ideSessionId !== void 0) await ide.endSession(ideSessionId);
20589
- await sleepScheduler?.dispose();
20590
- 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());
20591
20621
  terminals.dispose();
20592
- await jobs.dispose();
20622
+ await boundedStep(stepTimeoutMs, diagnostics, "jobs", jobs.dispose());
20593
20623
  unsubscribeCollaboration?.();
20594
20624
  pendingCollaborationEvents.length = 0;
20595
- await collaborationEventPump;
20596
- await collaborationClient?.close();
20597
- 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 });
20598
20628
  } catch (cleanupError) {
20599
20629
  attachCleanupError(primaryError, cleanupError);
20600
20630
  }
@@ -21215,7 +21245,8 @@ function storedConversation(snapshot4) {
21215
21245
  })
21216
21246
  };
21217
21247
  }
21218
- 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;
21219
21250
  let primary;
21220
21251
  try {
21221
21252
  approvals.resolve("deny");
@@ -21229,7 +21260,8 @@ async function cleanupProduction(approvals, questions, pluginHost, mcpManager, h
21229
21260
  else attachCleanupError(primary, error);
21230
21261
  }
21231
21262
  try {
21232
- 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`);
21233
21265
  } catch (error) {
21234
21266
  if (primary === void 0) primary = error;
21235
21267
  else attachCleanupError(primary, error);
@@ -21241,7 +21273,14 @@ async function cleanupProduction(approvals, questions, pluginHost, mcpManager, h
21241
21273
  else attachCleanupError(primary, error);
21242
21274
  }
21243
21275
  try {
21244
- 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
+ }
21245
21284
  } catch (error) {
21246
21285
  if (primary === void 0) primary = error;
21247
21286
  else attachCleanupError(primary, error);
@@ -21249,6 +21288,10 @@ async function cleanupProduction(approvals, questions, pluginHost, mcpManager, h
21249
21288
  }
21250
21289
  if (primary !== void 0) throw primary;
21251
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
+ }
21252
21295
  function attachCleanupError(primary, cleanup) {
21253
21296
  if (typeof primary !== "object" && typeof primary !== "function" || primary === null || !Object.isExtensible(primary)) return;
21254
21297
  try {
@@ -21334,6 +21377,7 @@ function createMemoryTaskId() {
21334
21377
 
21335
21378
  export {
21336
21379
  modelContentTranscriptText,
21380
+ withTimeout,
21337
21381
  message,
21338
21382
  isDestructiveTool,
21339
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-YRIPE5K5.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-QQCXJC4U.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-F4P7RV2X.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) => {
@@ -22696,6 +22723,7 @@ var INTERRUPTED_TASK_PLAN_CONTEXT = [
22696
22723
  "Reassess the current query independently.",
22697
22724
  "If the current work needs a plan, create a fresh one with TaskPlan before using TaskUpdate."
22698
22725
  ].join(" ");
22726
+ var DEFAULT_SHUTDOWN_STEP_TIMEOUT_MS = 3e3;
22699
22727
  function createPromptEnvironment(input = {}) {
22700
22728
  const now = input.now ?? /* @__PURE__ */ new Date();
22701
22729
  return {
@@ -24264,23 +24292,24 @@ ${renderMemoryDocument(entries)}`;
24264
24292
  async dispose() {
24265
24293
  if (disposed) return;
24266
24294
  disposed = true;
24295
+ const stepTimeoutMs = options.shutdownStepTimeoutMs ?? DEFAULT_SHUTDOWN_STEP_TIMEOUT_MS;
24267
24296
  collaborationEventsOpen = false;
24268
24297
  unsubscribeCollaboration?.();
24269
- await collaborationEventPump;
24298
+ if (collaborationEventPump !== void 0) await boundedStep(stepTimeoutMs, diagnostics, "collaboration-event-pump", collaborationEventPump);
24270
24299
  mcpDiscarded = true;
24271
- await sleepScheduler?.dispose();
24272
- await memoryCoordinator?.flush();
24273
- await persist();
24274
- await persistTail;
24275
- if (ideSessionId !== void 0) await ide.endSession(ideSessionId);
24276
- 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());
24277
24306
  terminals.dispose();
24278
- await jobs.dispose();
24307
+ await boundedStep(stepTimeoutMs, diagnostics, "jobs", jobs.dispose());
24279
24308
  auditLogger.close();
24280
24309
  evolveService.dispose();
24281
24310
  memoryReviews.dispose();
24282
- await collaborationClient?.close();
24283
- 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 });
24284
24313
  }
24285
24314
  };
24286
24315
  } catch (primaryError) {
@@ -24288,16 +24317,17 @@ ${renderMemoryDocument(entries)}`;
24288
24317
  mcpDiscarded = true;
24289
24318
  memoryReviews.dispose();
24290
24319
  try {
24291
- if (ideSessionId !== void 0) await ide.endSession(ideSessionId);
24292
- await sleepScheduler?.dispose();
24293
- 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());
24294
24324
  terminals.dispose();
24295
- await jobs.dispose();
24325
+ await boundedStep(stepTimeoutMs, diagnostics, "jobs", jobs.dispose());
24296
24326
  unsubscribeCollaboration?.();
24297
24327
  pendingCollaborationEvents.length = 0;
24298
- await collaborationEventPump;
24299
- await collaborationClient?.close();
24300
- 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 });
24301
24331
  } catch (cleanupError) {
24302
24332
  attachCleanupError(primaryError, cleanupError);
24303
24333
  }
@@ -24918,7 +24948,8 @@ function storedConversation(snapshot4) {
24918
24948
  })
24919
24949
  };
24920
24950
  }
24921
- 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;
24922
24953
  let primary;
24923
24954
  try {
24924
24955
  approvals.resolve("deny");
@@ -24932,7 +24963,8 @@ async function cleanupProduction(approvals, questions, pluginHost, mcpManager, h
24932
24963
  else attachCleanupError(primary, error);
24933
24964
  }
24934
24965
  try {
24935
- 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`);
24936
24968
  } catch (error) {
24937
24969
  if (primary === void 0) primary = error;
24938
24970
  else attachCleanupError(primary, error);
@@ -24944,7 +24976,14 @@ async function cleanupProduction(approvals, questions, pluginHost, mcpManager, h
24944
24976
  else attachCleanupError(primary, error);
24945
24977
  }
24946
24978
  try {
24947
- 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
+ }
24948
24987
  } catch (error) {
24949
24988
  if (primary === void 0) primary = error;
24950
24989
  else attachCleanupError(primary, error);
@@ -24952,6 +24991,10 @@ async function cleanupProduction(approvals, questions, pluginHost, mcpManager, h
24952
24991
  }
24953
24992
  if (primary !== void 0) throw primary;
24954
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
+ }
24955
24998
  function attachCleanupError(primary, cleanup) {
24956
24999
  if (typeof primary !== "object" && typeof primary !== "function" || primary === null || !Object.isExtensible(primary)) return;
24957
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-YRIPE5K5.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-QQCXJC4U.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.17",
3
+ "version": "1.2.18",
4
4
  "author": "YachuanWzh",
5
5
  "type": "module",
6
6
  "publishConfig": {