workspacecord 1.1.2 → 1.1.3

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.
@@ -8,15 +8,15 @@ import {
8
8
  cleanupSessionPanel,
9
9
  deliver,
10
10
  flushDigest,
11
- gateCoordinator,
12
11
  generatePerformanceReport,
13
12
  getPendingAnswers,
14
13
  getQuestionCount,
14
+ getSessionContext,
15
15
  getSessionProjection,
16
+ getSessionView,
16
17
  handleAwaitingHuman,
17
18
  handleResultEvent,
18
19
  initializeSessionPanel,
19
- normalizeCodexEvent,
20
20
  notifyUnmanagedCodexHint,
21
21
  queueDigest,
22
22
  registerExistingStatusCard,
@@ -27,10 +27,10 @@ import {
27
27
  startPerformanceMonitoring,
28
28
  stopPerformanceMonitoring,
29
29
  updateSessionState
30
- } from "./chunk-ZAQV2RZS.js";
30
+ } from "./chunk-7GTUWAQR.js";
31
31
  import {
32
32
  registerMessageAttachments
33
- } from "./chunk-L2ZJXV6H.js";
33
+ } from "./chunk-BFINJJYL.js";
34
34
  import {
35
35
  addMcpServer,
36
36
  addSkill,
@@ -51,14 +51,14 @@ import {
51
51
  setControlChannelId,
52
52
  setHistoryChannelId,
53
53
  setPersonality
54
- } from "./chunk-GMYN4SYT.js";
54
+ } from "./chunk-4KQ7OSK7.js";
55
55
  import {
56
56
  getAllRegisteredProjects
57
- } from "./chunk-54DP53ZK.js";
57
+ } from "./chunk-ZO62NAYX.js";
58
58
  import {
59
59
  buildClaudeSubagentProviderSessionId,
60
60
  registerLocalSession
61
- } from "./chunk-COXPTYH5.js";
61
+ } from "./chunk-WP6YJVAE.js";
62
62
  import {
63
63
  abortSession,
64
64
  abortSessionWithReason,
@@ -67,9 +67,9 @@ import {
67
67
  createSession,
68
68
  debouncedSaveSession,
69
69
  endSession,
70
+ gateService,
70
71
  getAllSessions,
71
72
  getOutputPort,
72
- getSession,
73
73
  getSessionByChannel,
74
74
  getSessionByProviderSession,
75
75
  getSessionController,
@@ -78,6 +78,7 @@ import {
78
78
  getSessionsByCategory,
79
79
  loadSessions,
80
80
  markSessionGenerating,
81
+ normalizeCodexEvent,
81
82
  registerOutputPort,
82
83
  resolveCodexSessionFromMonitor,
83
84
  resolveEffectiveClaudePermissionMode,
@@ -91,21 +92,26 @@ import {
91
92
  setSessionController,
92
93
  setStatusCardBinding,
93
94
  setVerbose,
95
+ stateMachine,
94
96
  updateSession,
95
97
  updateSessionPermissions,
96
98
  updateWorkflowState
97
- } from "./chunk-RK6EIZOL.js";
99
+ } from "./chunk-WON3DPE4.js";
98
100
  import {
101
+ JsonFileRepository,
102
+ MonitorRunEnded,
103
+ MonitorRunStarted,
99
104
  ServiceBus,
100
105
  config,
101
106
  formatDuration,
102
107
  formatRelative,
103
108
  formatUptime,
109
+ getDomainBus,
104
110
  intervalService,
105
111
  isAbortError,
106
112
  isUserAllowed,
107
113
  truncate
108
- } from "./chunk-UEX7U2KW.js";
114
+ } from "./chunk-IVXCJA5I.js";
109
115
  import {
110
116
  __commonJS,
111
117
  __require,
@@ -601,9 +607,9 @@ var require_cross_spawn = __commonJS({
601
607
  }
602
608
  function spawnSync2(command, args, options) {
603
609
  const parsed = parse(command, args, options);
604
- const result = cp.spawnSync(parsed.command, parsed.args, parsed.options);
605
- result.error = result.error || enoent.verifyENOENTSync(result.status, parsed);
606
- return result;
610
+ const result2 = cp.spawnSync(parsed.command, parsed.args, parsed.options);
611
+ result2.error = result2.error || enoent.verifyENOENTSync(result2.status, parsed);
612
+ return result2;
607
613
  }
608
614
  module.exports = spawn2;
609
615
  module.exports.spawn = spawn2;
@@ -858,7 +864,7 @@ providers.set("claude", new ClaudeProvider());
858
864
  var codexLoadAttempted = false;
859
865
  async function loadCodexProvider() {
860
866
  try {
861
- const { CodexProvider } = await import("./codex-provider-NYI7KBGO.js");
867
+ const { CodexProvider } = await import("./codex-provider-Q4Z6UKO6.js");
862
868
  providers.set("codex", new CodexProvider());
863
869
  codexLoadAttempted = true;
864
870
  } catch (err) {
@@ -1033,34 +1039,29 @@ function buildProviderOptions(session, controller, isMonitor = false, runtimeOve
1033
1039
  };
1034
1040
  }
1035
1041
  async function* sendPrompt(sessionId, prompt, runtimeOverrides = {}) {
1036
- const session = getSession(sessionId);
1037
- if (!session) throw new Error(`Session "${sessionId}" not found`);
1038
- if (session.isGenerating) throw new Error("Session is already generating");
1042
+ const ctx = getSessionContext(sessionId);
1043
+ if (!ctx) throw new Error(`Session "${sessionId}" not found`);
1044
+ if (ctx.session.isGenerating) throw new Error("Session is already generating");
1039
1045
  const controller = new AbortController();
1040
- setSessionController(session.id, controller);
1041
- markSessionGenerating(session.id, true);
1042
- const provider = await ensureProvider(session.provider);
1046
+ setSessionController(ctx.sessionId, controller);
1047
+ markSessionGenerating(ctx.sessionId, true);
1048
+ const provider = await ensureProvider(ctx.session.provider);
1043
1049
  try {
1044
1050
  const stream = provider.sendPrompt(
1045
1051
  prompt,
1046
- buildProviderOptions(session, controller, false, runtimeOverrides)
1052
+ buildProviderOptions(ctx.session, controller, false, runtimeOverrides)
1047
1053
  );
1048
1054
  for await (const event of stream) {
1049
1055
  if (event.type === "session_init") {
1050
- const current2 = getSession(sessionId);
1051
- if (current2) {
1052
- current2.providerSessionId = event.providerSessionId || void 0;
1053
- debouncedSaveSession();
1054
- }
1056
+ ctx.session.providerSessionId = event.providerSessionId || void 0;
1057
+ ctx.save();
1055
1058
  }
1056
1059
  if (event.type === "result") {
1057
- const current2 = getSession(sessionId);
1058
- if (current2) current2.totalCost += event.costUsd;
1060
+ ctx.session.totalCost += event.costUsd;
1059
1061
  }
1060
1062
  yield event;
1061
1063
  }
1062
- const current = getSession(sessionId);
1063
- if (current) current.messageCount++;
1064
+ ctx.session.messageCount++;
1064
1065
  } catch (err) {
1065
1066
  if (!isAbortError(err)) {
1066
1067
  throw err;
@@ -1072,39 +1073,34 @@ async function* sendPrompt(sessionId, prompt, runtimeOverrides = {}) {
1072
1073
  await saveSessionImmediate();
1073
1074
  } catch (cleanupErr) {
1074
1075
  console.error(`[ProviderRuntime] cleanup error for session ${sessionId}:`, cleanupErr);
1075
- const s = getSession(sessionId);
1076
- if (s) s.isGenerating = false;
1076
+ const fallback = getSessionContext(sessionId);
1077
+ if (fallback) fallback.session.isGenerating = false;
1077
1078
  }
1078
1079
  }
1079
1080
  }
1080
1081
  async function* continueSessionWithOverrides(sessionId, runtimeOverrides = {}) {
1081
- const session = getSession(sessionId);
1082
- if (!session) throw new Error(`Session "${sessionId}" not found`);
1083
- if (session.isGenerating) throw new Error("Session is already generating");
1082
+ const ctx = getSessionContext(sessionId);
1083
+ if (!ctx) throw new Error(`Session "${sessionId}" not found`);
1084
+ if (ctx.session.isGenerating) throw new Error("Session is already generating");
1084
1085
  const controller = new AbortController();
1085
- setSessionController(session.id, controller);
1086
- markSessionGenerating(session.id, true);
1087
- const provider = await ensureProvider(session.provider);
1086
+ setSessionController(ctx.sessionId, controller);
1087
+ markSessionGenerating(ctx.sessionId, true);
1088
+ const provider = await ensureProvider(ctx.session.provider);
1088
1089
  try {
1089
1090
  const stream = provider.continueSession(
1090
- buildProviderOptions(session, controller, false, runtimeOverrides)
1091
+ buildProviderOptions(ctx.session, controller, false, runtimeOverrides)
1091
1092
  );
1092
1093
  for await (const event of stream) {
1093
1094
  if (event.type === "session_init") {
1094
- const current2 = getSession(sessionId);
1095
- if (current2) {
1096
- current2.providerSessionId = event.providerSessionId || void 0;
1097
- debouncedSaveSession();
1098
- }
1095
+ ctx.session.providerSessionId = event.providerSessionId || void 0;
1096
+ ctx.save();
1099
1097
  }
1100
1098
  if (event.type === "result") {
1101
- const current2 = getSession(sessionId);
1102
- if (current2) current2.totalCost += event.costUsd;
1099
+ ctx.session.totalCost += event.costUsd;
1103
1100
  }
1104
1101
  yield event;
1105
1102
  }
1106
- const current = getSession(sessionId);
1107
- if (current) current.messageCount++;
1103
+ ctx.session.messageCount++;
1108
1104
  } catch (err) {
1109
1105
  if (!isAbortError(err)) {
1110
1106
  throw err;
@@ -1116,39 +1112,33 @@ async function* continueSessionWithOverrides(sessionId, runtimeOverrides = {}) {
1116
1112
  await saveSessionImmediate();
1117
1113
  } catch (cleanupErr) {
1118
1114
  console.error(`[ProviderRuntime] cleanup error for session ${sessionId}:`, cleanupErr);
1119
- const s = getSession(sessionId);
1120
- if (s) s.isGenerating = false;
1115
+ const fallback = getSessionContext(sessionId);
1116
+ if (fallback) fallback.session.isGenerating = false;
1121
1117
  }
1122
1118
  }
1123
1119
  }
1124
1120
  async function* sendMonitorPrompt(sessionId, prompt) {
1125
- const session = getSession(sessionId);
1126
- if (!session) throw new Error(`Session "${sessionId}" not found`);
1127
- const provider = await ensureProvider(session.provider);
1128
- const current = getSession(sessionId);
1129
- if (current) current.lastActivity = Date.now();
1121
+ const ctx = getSessionContext(sessionId);
1122
+ if (!ctx) throw new Error(`Session "${sessionId}" not found`);
1123
+ const provider = await ensureProvider(ctx.session.provider);
1124
+ ctx.session.lastActivity = Date.now();
1130
1125
  const mainController = getSessionController(sessionId);
1131
1126
  const controller = new AbortController();
1132
1127
  const onMainAbort = () => controller.abort();
1133
1128
  mainController?.signal.addEventListener("abort", onMainAbort, { once: true });
1134
1129
  try {
1135
- const stream = provider.sendPrompt(prompt, buildProviderOptions(session, controller, true));
1130
+ const stream = provider.sendPrompt(prompt, buildProviderOptions(ctx.session, controller, true));
1136
1131
  for await (const event of stream) {
1137
1132
  if (event.type === "session_init") {
1138
- const cur2 = getSession(sessionId);
1139
- if (cur2) {
1140
- cur2.monitorProviderSessionId = event.providerSessionId || void 0;
1141
- debouncedSaveSession();
1142
- }
1133
+ ctx.session.monitorProviderSessionId = event.providerSessionId || void 0;
1134
+ ctx.save();
1143
1135
  }
1144
1136
  if (event.type === "result") {
1145
- const cur2 = getSession(sessionId);
1146
- if (cur2) cur2.totalCost += event.costUsd;
1137
+ ctx.session.totalCost += event.costUsd;
1147
1138
  }
1148
1139
  yield event;
1149
1140
  }
1150
- const cur = getSession(sessionId);
1151
- if (cur) cur.lastActivity = Date.now();
1141
+ ctx.session.lastActivity = Date.now();
1152
1142
  debouncedSaveSession();
1153
1143
  } catch (err) {
1154
1144
  if (!isAbortError(err)) {
@@ -1161,21 +1151,21 @@ async function* sendMonitorPrompt(sessionId, prompt) {
1161
1151
 
1162
1152
  // ../engine/src/executor/permission-gate.ts
1163
1153
  function refreshSession(session) {
1164
- return getSession(session.id) ?? session;
1154
+ return getSessionView(session.id) ?? session;
1165
1155
  }
1166
1156
  function waitForGateResolution(session, gateId) {
1167
1157
  console.log(`[SessionExecutor] gate:waiting sessionId=${session.id} gateId=${gateId}`);
1168
1158
  return new Promise((resolve2) => {
1169
1159
  let settled = false;
1170
- const settle = (result) => {
1160
+ const settle = (result2) => {
1171
1161
  if (settled) return;
1172
1162
  settled = true;
1173
1163
  console.log(
1174
- `[SessionExecutor] gate:resolved sessionId=${session.id} gateId=${gateId} action=${result.action} source=${result.source}`
1164
+ `[SessionExecutor] gate:resolved sessionId=${session.id} gateId=${gateId} action=${result2.action} source=${result2.source}`
1175
1165
  );
1176
- resolve2(result);
1166
+ resolve2(result2);
1177
1167
  };
1178
- gateCoordinator.registerReceiptHandle(gateId, {
1168
+ gateService.registerReceiptHandle(gateId, {
1179
1169
  type: session.provider === "codex" ? "codex" : "claude",
1180
1170
  sessionId: session.id,
1181
1171
  resolve: (action, source) => settle({ action, source }),
@@ -1259,7 +1249,7 @@ function shouldUseClaudePermissionHandler(session) {
1259
1249
 
1260
1250
  // ../engine/src/executor/session-hooks.ts
1261
1251
  function refreshSession2(session) {
1262
- return getSession(session.id) ?? session;
1252
+ return getSessionView(session.id) ?? session;
1263
1253
  }
1264
1254
  function applyWorkflowHook(session, hook, patch = {}) {
1265
1255
  return updateWorkflowState(session.id, (current) => ({
@@ -1309,30 +1299,30 @@ function extractRemainingGaps(text) {
1309
1299
  (sentence) => /\b(still|missing|need|remaining|not yet|left to|have not)\b/i.test(sentence)
1310
1300
  ).slice(0, 5);
1311
1301
  }
1312
- function buildWorkerProgressReport(goal, result) {
1313
- const changedFiles = result.changedFiles ?? [];
1314
- const recentCommands = result.recentCommands ?? [];
1302
+ function buildWorkerProgressReport(goal, result2) {
1303
+ const changedFiles = result2.changedFiles ?? [];
1304
+ const recentCommands = result2.recentCommands ?? [];
1315
1305
  const validationCommands = recentCommands.filter(
1316
1306
  (command) => /\b(test|vitest|jest|pytest|npm test|pnpm test|yarn test|grader|validate|check|lint)\b/i.test(
1317
1307
  command
1318
1308
  )
1319
1309
  ).slice(0, 10);
1320
- const meaningfulExecution = result.commandCount > 0 || result.fileChangeCount > 0;
1321
- const blockers = result.hadError ? ["The latest pass reported an error or stalled outcome."] : [];
1310
+ const meaningfulExecution = result2.commandCount > 0 || result2.fileChangeCount > 0;
1311
+ const blockers = result2.hadError ? ["The latest pass reported an error or stalled outcome."] : [];
1322
1312
  return {
1323
1313
  originalGoal: goal,
1324
- textualResponse: summarizeWorkerText(result.text),
1325
- commandCount: result.commandCount,
1326
- fileChangeCount: result.fileChangeCount,
1314
+ textualResponse: summarizeWorkerText(result2.text),
1315
+ commandCount: result2.commandCount,
1316
+ fileChangeCount: result2.fileChangeCount,
1327
1317
  meaningfulExecutionEvidence: meaningfulExecution,
1328
- providerReportedSuccess: result.success === null ? "unknown" : result.success ? "yes" : "no",
1329
- workerErrorsObserved: result.hadError,
1330
- askedForHumanInput: result.askedUser,
1331
- claimedCompletedOutcomes: extractClaimedCompletedOutcomes(result.text),
1318
+ providerReportedSuccess: result2.success === null ? "unknown" : result2.success ? "yes" : "no",
1319
+ workerErrorsObserved: result2.hadError,
1320
+ askedForHumanInput: result2.askedUser,
1321
+ claimedCompletedOutcomes: extractClaimedCompletedOutcomes(result2.text),
1332
1322
  artifacts: changedFiles,
1333
1323
  validationCommands,
1334
- goalAssessment: result.text.trim() ? truncate(result.text.trim(), 1200) : meaningfulExecution ? "The worker executed commands or changed files but did not provide an explicit textual assessment." : "The worker did not provide a substantive assessment of progress toward the goal.",
1335
- remainingGaps: extractRemainingGaps(result.text),
1324
+ goalAssessment: result2.text.trim() ? truncate(result2.text.trim(), 1200) : meaningfulExecution ? "The worker executed commands or changed files but did not provide an explicit textual assessment." : "The worker did not provide a substantive assessment of progress toward the goal.",
1325
+ remainingGaps: extractRemainingGaps(result2.text),
1336
1326
  blockers
1337
1327
  };
1338
1328
  }
@@ -1569,6 +1559,100 @@ function parseAskUserDecision(text) {
1569
1559
  }
1570
1560
  }
1571
1561
 
1562
+ // ../engine/src/executor/monitor-run-store.ts
1563
+ var monitorRepo = new JsonFileRepository({
1564
+ filename: "monitor-runs.json",
1565
+ idField: "id",
1566
+ debounceMs: 0
1567
+ });
1568
+ var initialized = false;
1569
+ async function ensureInit() {
1570
+ if (initialized) return;
1571
+ await monitorRepo.init();
1572
+ initialized = true;
1573
+ }
1574
+ function makeId(sessionId, startedAt) {
1575
+ const suffix = Math.random().toString(36).slice(2, 8);
1576
+ return `${sessionId}:${startedAt}:${suffix}`;
1577
+ }
1578
+ async function beginMonitorRun(params) {
1579
+ await ensureInit();
1580
+ const now = Date.now();
1581
+ const stale = monitorRepo.find({
1582
+ where: { sessionId: params.sessionId, status: "running" }
1583
+ });
1584
+ for (const old of stale) {
1585
+ await monitorRepo.update(old.id, { status: "abandoned", lastCheckpointAt: now });
1586
+ }
1587
+ const run = {
1588
+ id: makeId(params.sessionId, now),
1589
+ sessionId: params.sessionId,
1590
+ goal: params.goal,
1591
+ iteration: 0,
1592
+ maxIterations: params.maxIterations,
1593
+ status: "running",
1594
+ startedAt: now,
1595
+ lastCheckpointAt: now
1596
+ };
1597
+ await monitorRepo.save(run);
1598
+ getDomainBus().emit(
1599
+ MonitorRunStarted,
1600
+ {
1601
+ sessionId: run.sessionId,
1602
+ runId: run.id,
1603
+ goal: run.goal,
1604
+ maxIterations: run.maxIterations
1605
+ },
1606
+ "monitor-run-store"
1607
+ );
1608
+ return run;
1609
+ }
1610
+ async function checkpointMonitorRun(runId, patch) {
1611
+ await ensureInit();
1612
+ return monitorRepo.update(runId, { ...patch, lastCheckpointAt: Date.now() });
1613
+ }
1614
+ async function finishMonitorRun(runId, status, finalPatch = {}) {
1615
+ await ensureInit();
1616
+ const updated = await monitorRepo.update(runId, {
1617
+ ...finalPatch,
1618
+ status,
1619
+ lastCheckpointAt: Date.now()
1620
+ });
1621
+ if (updated) {
1622
+ getDomainBus().emit(
1623
+ MonitorRunEnded,
1624
+ {
1625
+ sessionId: updated.sessionId,
1626
+ runId: updated.id,
1627
+ status,
1628
+ iteration: updated.iteration,
1629
+ rationale: updated.lastRationale
1630
+ },
1631
+ "monitor-run-store"
1632
+ );
1633
+ }
1634
+ }
1635
+ async function reconcileMonitorRunsOnStartup() {
1636
+ await ensureInit();
1637
+ const running = monitorRepo.find({ where: { status: "running" } });
1638
+ const now = Date.now();
1639
+ for (const run of running) {
1640
+ await monitorRepo.update(run.id, { status: "abandoned", lastCheckpointAt: now });
1641
+ getDomainBus().emit(
1642
+ MonitorRunEnded,
1643
+ {
1644
+ sessionId: run.sessionId,
1645
+ runId: run.id,
1646
+ status: "abandoned",
1647
+ iteration: run.iteration,
1648
+ rationale: run.lastRationale
1649
+ },
1650
+ "monitor-run-store"
1651
+ );
1652
+ }
1653
+ return running;
1654
+ }
1655
+
1572
1656
  // ../engine/src/executor/monitor-loop.ts
1573
1657
  var MAX_MONITOR_ITERATIONS = 6;
1574
1658
  async function runMonitorDecision(session, goal, workerResult, iteration) {
@@ -1786,6 +1870,16 @@ async function runMonitorLoop(session, channel, goal, initialResult, runWorkerPa
1786
1870
  console.log(
1787
1871
  `[SessionExecutor] monitor:loop-start sessionId=${session.id} goal=${truncate(goal, 80)}`
1788
1872
  );
1873
+ const monitorRun = await beginMonitorRun({
1874
+ sessionId: session.id,
1875
+ goal,
1876
+ maxIterations: MAX_MONITOR_ITERATIONS
1877
+ }).catch((err) => {
1878
+ console.warn(
1879
+ `[SessionExecutor] failed to persist MonitorRun for session ${session.id}: ${err.message}`
1880
+ );
1881
+ return null;
1882
+ });
1789
1883
  let workerResult = initialResult;
1790
1884
  let currentSession = refreshSession2(session);
1791
1885
  for (let iteration = 1; iteration <= MAX_MONITOR_ITERATIONS; iteration++) {
@@ -1855,6 +1949,13 @@ async function runMonitorLoop(session, channel, goal, initialResult, runWorkerPa
1855
1949
  });
1856
1950
  const summary = decision.completionSummary || decision.rationale || "The monitor judged the request complete.";
1857
1951
  await getOutputPort().handleResult(currentSession.id, createSyntheticResult(true, summary), summary);
1952
+ if (monitorRun) {
1953
+ await finishMonitorRun(monitorRun.id, "completed", {
1954
+ iteration,
1955
+ lastRationale: decision.rationale
1956
+ }).catch(() => {
1957
+ });
1958
+ }
1858
1959
  console.log(
1859
1960
  `[SessionExecutor] monitor:complete sessionId=${currentSession.id} iteration=${iteration} rationale=${truncate(decision.rationale, 80)}`
1860
1961
  );
@@ -1875,11 +1976,26 @@ async function runMonitorLoop(session, channel, goal, initialResult, runWorkerPa
1875
1976
  await getOutputPort().handleAwaitingHuman(currentSession.id, blocker, {
1876
1977
  source: currentSession.provider === "codex" ? "codex" : "claude"
1877
1978
  });
1979
+ if (monitorRun) {
1980
+ await finishMonitorRun(monitorRun.id, "blocked", {
1981
+ iteration,
1982
+ lastRationale: decision.rationale
1983
+ }).catch(() => {
1984
+ });
1985
+ }
1878
1986
  console.warn(
1879
1987
  `[SessionExecutor] monitor:blocked sessionId=${currentSession.id} iteration=${iteration} reason=${truncate(decision.rationale, 80)}`
1880
1988
  );
1881
1989
  return;
1882
1990
  }
1991
+ if (monitorRun) {
1992
+ await checkpointMonitorRun(monitorRun.id, {
1993
+ iteration,
1994
+ lastRationale: decision.rationale,
1995
+ lastWorkerSummary: summarizeWorkerPass(buildWorkerProgressReport(goal, workerResult))
1996
+ }).catch(() => {
1997
+ });
1998
+ }
1883
1999
  await updatePanelState(currentSession, "work_started", channel);
1884
2000
  getOutputPort().queueDigest(currentSession.id, {
1885
2001
  kind: "monitor",
@@ -1926,6 +2042,13 @@ async function runMonitorLoop(session, channel, goal, initialResult, runWorkerPa
1926
2042
  await getOutputPort().handleAwaitingHuman(currentSession.id, limitSummary, {
1927
2043
  source: currentSession.provider === "codex" ? "codex" : "claude"
1928
2044
  });
2045
+ if (monitorRun) {
2046
+ await finishMonitorRun(monitorRun.id, "failed", {
2047
+ iteration: MAX_MONITOR_ITERATIONS,
2048
+ lastRationale: "Reached the continuation safety limit."
2049
+ }).catch(() => {
2050
+ });
2051
+ }
1929
2052
  console.warn(
1930
2053
  `[SessionExecutor] monitor:limit-reached sessionId=${currentSession.id} iterations=${MAX_MONITOR_ITERATIONS}`
1931
2054
  );
@@ -1968,7 +2091,7 @@ async function runWorkerPass(session, channel, prompt, iteration, mode = "prompt
1968
2091
  watchdogTimer = setTimeout(scheduleWatchdog, delay);
1969
2092
  };
1970
2093
  watchdogTimer = setTimeout(scheduleWatchdog, WATCHDOG_SLOW_INTERVAL);
1971
- const result = await getOutputPort().handleOutputStream(
2094
+ const result2 = await getOutputPort().handleOutputStream(
1972
2095
  stream,
1973
2096
  channel,
1974
2097
  session.id,
@@ -1984,10 +2107,10 @@ async function runWorkerPass(session, channel, prompt, iteration, mode = "prompt
1984
2107
  const abortReason = consumeAbortReason(session.id);
1985
2108
  if (watchdogTriggered || abortReason === "watchdog") {
1986
2109
  const stalledResult = {
1987
- ...result,
2110
+ ...result2,
1988
2111
  hadError: true,
1989
2112
  abortReason,
1990
- text: annotateInactivityAbort(result.text, WORKER_IDLE_TIMEOUT_MS)
2113
+ text: annotateInactivityAbort(result2.text, WORKER_IDLE_TIMEOUT_MS)
1991
2114
  };
1992
2115
  const stalledReport = buildWorkerProgressReport("", stalledResult);
1993
2116
  applyWorkflowHook(session, "on_stall", {
@@ -2000,17 +2123,17 @@ async function runWorkerPass(session, channel, prompt, iteration, mode = "prompt
2000
2123
  );
2001
2124
  return stalledResult;
2002
2125
  }
2003
- const resultReport = buildWorkerProgressReport("", result);
2126
+ const resultReport = buildWorkerProgressReport("", result2);
2004
2127
  applyWorkflowHook(session, "after_worker_pass", {
2005
2128
  status: session.mode === "monitor" ? "monitor_review" : "idle",
2006
2129
  lastWorkerSummary: summarizeWorkerPass(resultReport),
2007
2130
  lastWorkerReport: resultReport
2008
2131
  });
2009
2132
  console.log(
2010
- `[SessionExecutor] worker:end sessionId=${session.id} iteration=${iteration} commands=${result.commandCount} files=${result.fileChangeCount} success=${result.success} hasError=${result.hadError}`
2133
+ `[SessionExecutor] worker:end sessionId=${session.id} iteration=${iteration} commands=${result2.commandCount} files=${result2.fileChangeCount} success=${result2.success} hasError=${result2.hadError}`
2011
2134
  );
2012
2135
  return {
2013
- ...result,
2136
+ ...result2,
2014
2137
  abortReason
2015
2138
  };
2016
2139
  } finally {
@@ -2031,7 +2154,7 @@ async function executeSessionPrompt(session, channel, prompt, options = {}) {
2031
2154
  }
2032
2155
  if ((options.updateMonitorGoal ?? true) && goalText && !session.monitorGoal) {
2033
2156
  setMonitorGoal(session.id, goalText);
2034
- session = getSession(session.id) ?? session;
2157
+ session = getSessionView(session.id) ?? session;
2035
2158
  }
2036
2159
  const goal = session.monitorGoal || goalText;
2037
2160
  if (!goal) {
@@ -2141,11 +2264,163 @@ function getExpandableContent(id) {
2141
2264
  return expandableStore.get(id)?.content;
2142
2265
  }
2143
2266
 
2144
- // ../bot/src/output-handler.ts
2145
- import { existsSync } from "fs";
2267
+ // ../engine/src/executor/monitor-autoresume.ts
2268
+ async function reconcileAndCollectAutoResumeCandidates(policy) {
2269
+ const abandoned = await reconcileMonitorRunsOnStartup();
2270
+ if (policy === "abandon-only" || abandoned.length === 0) {
2271
+ return { abandoned, candidates: [] };
2272
+ }
2273
+ const candidates = [];
2274
+ for (const run of abandoned) {
2275
+ const session = getSessionView(run.sessionId);
2276
+ if (!session) continue;
2277
+ if (session.mode !== "monitor") continue;
2278
+ if (session.isGenerating) continue;
2279
+ const goal = session.monitorGoal || run.goal;
2280
+ if (!goal) continue;
2281
+ candidates.push({
2282
+ sessionId: session.id,
2283
+ channelId: session.channelId,
2284
+ runId: run.id,
2285
+ goal,
2286
+ lastIteration: run.iteration,
2287
+ lastRationale: run.lastRationale,
2288
+ abandonedRun: run
2289
+ });
2290
+ }
2291
+ return { abandoned, candidates };
2292
+ }
2146
2293
 
2147
- // ../bot/src/codex-renderer.ts
2148
- import { EmbedBuilder } from "discord.js";
2294
+ // ../bot/src/output/message-streamer.ts
2295
+ function detectRepetition(text) {
2296
+ const sentences = text.split(/[。!?\n]+/).filter((s) => s.trim().length > 5);
2297
+ if (sentences.length < 3) return { isRepetitive: false, cleanedText: text };
2298
+ const counts = /* @__PURE__ */ new Map();
2299
+ for (const sentence of sentences) {
2300
+ const normalized = sentence.trim();
2301
+ counts.set(normalized, (counts.get(normalized) || 0) + 1);
2302
+ }
2303
+ let maxCount = 0;
2304
+ let mostRepeated = "";
2305
+ for (const [sentence, count2] of counts) {
2306
+ if (count2 > maxCount) {
2307
+ maxCount = count2;
2308
+ mostRepeated = sentence;
2309
+ }
2310
+ }
2311
+ if (maxCount >= 5) {
2312
+ const parts = text.split(mostRepeated);
2313
+ const cleaned = parts.slice(0, 3).join(mostRepeated);
2314
+ return {
2315
+ isRepetitive: true,
2316
+ cleanedText: cleaned + `
2317
+
2318
+ \u26A0\uFE0F *[\u68C0\u6D4B\u5230\u91CD\u590D\u8F93\u51FA,\u5DF2\u622A\u65AD ${maxCount - 2} \u6B21\u91CD\u590D]*`
2319
+ };
2320
+ }
2321
+ return { isRepetitive: false, cleanedText: text };
2322
+ }
2323
+ var MessageStreamer = class {
2324
+ _channel;
2325
+ _sessionId;
2326
+ currentText = "";
2327
+ transcriptText = "";
2328
+ dirty = false;
2329
+ flushing = false;
2330
+ timer = null;
2331
+ INTERVAL = 400;
2332
+ constructor(channel, sessionId) {
2333
+ this._channel = channel;
2334
+ this._sessionId = sessionId;
2335
+ }
2336
+ append(text, options = {}) {
2337
+ this.currentText += text;
2338
+ if (options.persist !== false) {
2339
+ this.transcriptText += text;
2340
+ }
2341
+ this.dirty = true;
2342
+ this.scheduleFlush();
2343
+ }
2344
+ scheduleFlush() {
2345
+ if (this.timer || this.flushing) return;
2346
+ this.timer = setTimeout(() => {
2347
+ this.timer = null;
2348
+ this.flush().catch((err) => {
2349
+ console.error(`[MessageStreamer] flush error (session ${this._sessionId}):`, err);
2350
+ });
2351
+ }, this.INTERVAL);
2352
+ }
2353
+ async flush() {
2354
+ if (this.flushing || !this.dirty) return;
2355
+ this.flushing = true;
2356
+ try {
2357
+ this.dirty = false;
2358
+ if (this.currentText.trim()) {
2359
+ const plan = buildDeliveryPlan({
2360
+ sessionId: this._sessionId,
2361
+ chatId: this._channel.id,
2362
+ text: this.currentText,
2363
+ files: [],
2364
+ mode: "progress_update",
2365
+ policy: {
2366
+ textChunkLimit: config.textChunkLimit,
2367
+ chunkMode: config.chunkMode,
2368
+ replyToMode: config.replyToMode,
2369
+ ackReaction: config.ackReaction
2370
+ }
2371
+ });
2372
+ await deliver(this._channel, plan);
2373
+ }
2374
+ } finally {
2375
+ this.flushing = false;
2376
+ if (this.dirty) this.scheduleFlush();
2377
+ }
2378
+ }
2379
+ async waitForFlush() {
2380
+ const deadline = Date.now() + 1e4;
2381
+ while (this.flushing) {
2382
+ if (Date.now() > deadline) {
2383
+ console.warn(`[MessageStreamer] flush wait timeout (session ${this._sessionId}), forcing reset`);
2384
+ this.flushing = false;
2385
+ break;
2386
+ }
2387
+ await new Promise((r) => setTimeout(r, 50));
2388
+ }
2389
+ }
2390
+ async finalize() {
2391
+ if (this.timer) {
2392
+ clearTimeout(this.timer);
2393
+ this.timer = null;
2394
+ }
2395
+ await this.waitForFlush();
2396
+ if (this.dirty) {
2397
+ this.dirty = false;
2398
+ const { cleanedText } = detectRepetition(this.currentText);
2399
+ this.currentText = cleanedText;
2400
+ }
2401
+ }
2402
+ async discard() {
2403
+ if (this.timer) {
2404
+ clearTimeout(this.timer);
2405
+ this.timer = null;
2406
+ }
2407
+ await this.waitForFlush();
2408
+ this.currentText = "";
2409
+ this.dirty = false;
2410
+ }
2411
+ getText() {
2412
+ return this.transcriptText;
2413
+ }
2414
+ destroy() {
2415
+ if (this.timer) {
2416
+ clearTimeout(this.timer);
2417
+ this.timer = null;
2418
+ }
2419
+ }
2420
+ };
2421
+
2422
+ // ../bot/src/output/event-handlers.ts
2423
+ import { existsSync } from "fs";
2149
2424
 
2150
2425
  // ../bot/src/subagent-manager.ts
2151
2426
  import {
@@ -2303,133 +2578,8 @@ function getSubagentSessions() {
2303
2578
  return getAllSessions().filter((s) => s.type === "subagent");
2304
2579
  }
2305
2580
 
2306
- // ../bot/src/output/message-streamer.ts
2307
- function detectRepetition(text) {
2308
- const sentences = text.split(/[。!?\n]+/).filter((s) => s.trim().length > 5);
2309
- if (sentences.length < 3) return { isRepetitive: false, cleanedText: text };
2310
- const counts = /* @__PURE__ */ new Map();
2311
- for (const sentence of sentences) {
2312
- const normalized = sentence.trim();
2313
- counts.set(normalized, (counts.get(normalized) || 0) + 1);
2314
- }
2315
- let maxCount = 0;
2316
- let mostRepeated = "";
2317
- for (const [sentence, count2] of counts) {
2318
- if (count2 > maxCount) {
2319
- maxCount = count2;
2320
- mostRepeated = sentence;
2321
- }
2322
- }
2323
- if (maxCount >= 5) {
2324
- const parts = text.split(mostRepeated);
2325
- const cleaned = parts.slice(0, 3).join(mostRepeated);
2326
- return {
2327
- isRepetitive: true,
2328
- cleanedText: cleaned + `
2329
-
2330
- \u26A0\uFE0F *[\u68C0\u6D4B\u5230\u91CD\u590D\u8F93\u51FA,\u5DF2\u622A\u65AD ${maxCount - 2} \u6B21\u91CD\u590D]*`
2331
- };
2332
- }
2333
- return { isRepetitive: false, cleanedText: text };
2334
- }
2335
- var MessageStreamer = class {
2336
- _channel;
2337
- _sessionId;
2338
- currentText = "";
2339
- transcriptText = "";
2340
- dirty = false;
2341
- flushing = false;
2342
- timer = null;
2343
- INTERVAL = 400;
2344
- constructor(channel, sessionId) {
2345
- this._channel = channel;
2346
- this._sessionId = sessionId;
2347
- }
2348
- append(text, options = {}) {
2349
- this.currentText += text;
2350
- if (options.persist !== false) {
2351
- this.transcriptText += text;
2352
- }
2353
- this.dirty = true;
2354
- this.scheduleFlush();
2355
- }
2356
- scheduleFlush() {
2357
- if (this.timer || this.flushing) return;
2358
- this.timer = setTimeout(() => {
2359
- this.timer = null;
2360
- this.flush().catch((err) => {
2361
- console.error(`[MessageStreamer] flush error (session ${this._sessionId}):`, err);
2362
- });
2363
- }, this.INTERVAL);
2364
- }
2365
- async flush() {
2366
- if (this.flushing || !this.dirty) return;
2367
- this.flushing = true;
2368
- try {
2369
- this.dirty = false;
2370
- if (this.currentText.trim()) {
2371
- const plan = buildDeliveryPlan({
2372
- sessionId: this._sessionId,
2373
- chatId: this._channel.id,
2374
- text: this.currentText,
2375
- files: [],
2376
- mode: "progress_update",
2377
- policy: {
2378
- textChunkLimit: config.textChunkLimit,
2379
- chunkMode: config.chunkMode,
2380
- replyToMode: config.replyToMode,
2381
- ackReaction: config.ackReaction
2382
- }
2383
- });
2384
- await deliver(this._channel, plan);
2385
- }
2386
- } finally {
2387
- this.flushing = false;
2388
- if (this.dirty) this.scheduleFlush();
2389
- }
2390
- }
2391
- async waitForFlush() {
2392
- const deadline = Date.now() + 1e4;
2393
- while (this.flushing) {
2394
- if (Date.now() > deadline) {
2395
- console.warn(`[MessageStreamer] flush wait timeout (session ${this._sessionId}), forcing reset`);
2396
- this.flushing = false;
2397
- break;
2398
- }
2399
- await new Promise((r) => setTimeout(r, 50));
2400
- }
2401
- }
2402
- async finalize() {
2403
- if (this.timer) {
2404
- clearTimeout(this.timer);
2405
- this.timer = null;
2406
- }
2407
- await this.waitForFlush();
2408
- if (this.dirty) {
2409
- this.dirty = false;
2410
- const { cleanedText } = detectRepetition(this.currentText);
2411
- this.currentText = cleanedText;
2412
- }
2413
- }
2414
- async discard() {
2415
- if (this.timer) {
2416
- clearTimeout(this.timer);
2417
- this.timer = null;
2418
- }
2419
- await this.waitForFlush();
2420
- this.currentText = "";
2421
- this.dirty = false;
2422
- }
2423
- getText() {
2424
- return this.transcriptText;
2425
- }
2426
- destroy() {
2427
- if (this.timer) {
2428
- clearTimeout(this.timer);
2429
- this.timer = null;
2430
- }
2431
- }
2432
- };
2581
+ // ../bot/src/codex-renderer.ts
2582
+ import { EmbedBuilder } from "discord.js";
2433
2583
 
2434
2584
  // ../bot/src/output/interaction-controls.ts
2435
2585
  import {
@@ -2474,24 +2624,267 @@ function shouldSuppressCommandExecution(command) {
2474
2624
  return command.toLowerCase().includes("total-recall");
2475
2625
  }
2476
2626
 
2627
+ // ../bot/src/output/event-handlers.ts
2628
+ function providerSource(sessionId) {
2629
+ const session = getSessionView(sessionId);
2630
+ return session?.provider === "codex" ? "codex" : "claude";
2631
+ }
2632
+ var textDelta = (event, ctx) => {
2633
+ ctx.streamer.append(event.text);
2634
+ };
2635
+ var askUser = async (event, ctx) => {
2636
+ ctx.state.askedUser = true;
2637
+ ctx.state.askUserQuestionsJson = event.questionsJson;
2638
+ await ctx.streamer.discard();
2639
+ const session = getSessionView(ctx.sessionId);
2640
+ if (!session) return;
2641
+ const source = providerSource(ctx.sessionId);
2642
+ await updateSessionState(ctx.sessionId, {
2643
+ type: "awaiting_human",
2644
+ sessionId: ctx.sessionId,
2645
+ source,
2646
+ confidence: "high",
2647
+ timestamp: Date.now(),
2648
+ metadata: { detail: event.questionsJson }
2649
+ });
2650
+ await flushDigest(ctx.sessionId);
2651
+ await handleAwaitingHuman(ctx.sessionId, event.questionsJson, { source });
2652
+ };
2653
+ var task = async (event, ctx) => {
2654
+ await ctx.streamer.finalize();
2655
+ queueDigest(ctx.sessionId, { kind: "tool", text: `\u4EFB\u52A1\u5DE5\u5177\uFF1A${event.action}` });
2656
+ ctx.state.lastToolName = event.action;
2657
+ };
2658
+ var taskStarted = async (event, ctx) => {
2659
+ await ctx.streamer.finalize();
2660
+ queueDigest(ctx.sessionId, {
2661
+ kind: "subagent",
2662
+ text: `\u5B50\u4EE3\u7406\u542F\u52A8\uFF1A${truncate(event.description, 80)}`
2663
+ });
2664
+ const session = getSessionView(ctx.sessionId);
2665
+ if (!session || ctx.channel.type === void 0) return;
2666
+ autoSpawnSubagentThread(session, event.taskId, event.description, ctx.channel).then((result2) => {
2667
+ if (result2) ctx.state.taskThreadMap.set(event.taskId, result2.threadId);
2668
+ }).catch(
2669
+ (err) => console.warn(
2670
+ `[OutputHandler] Failed to auto-spawn thread for task ${event.taskId}: ${err.message}`
2671
+ )
2672
+ );
2673
+ };
2674
+ var taskProgress = (event, ctx) => {
2675
+ if (event.summary) {
2676
+ queueDigest(ctx.sessionId, {
2677
+ kind: "subagent",
2678
+ text: `\u5B50\u4EE3\u7406\u8FDB\u5C55\uFF1A${truncate(event.summary, 100)}`
2679
+ });
2680
+ }
2681
+ const threadId = ctx.state.taskThreadMap.get(event.taskId);
2682
+ if (!threadId || !event.summary) return;
2683
+ const thread = ctx.channel.threads?.cache.get(threadId);
2684
+ if (!thread) return;
2685
+ thread.send(`\u{1F4DD} ${truncate(event.summary, 1900)}`).catch(
2686
+ (e) => console.warn(
2687
+ `[OutputHandler] Failed to send progress to thread: ${e.message}`
2688
+ )
2689
+ );
2690
+ };
2691
+ var taskDone = async (event, ctx) => {
2692
+ await ctx.streamer.finalize();
2693
+ queueDigest(ctx.sessionId, {
2694
+ kind: "subagent",
2695
+ text: `\u5B50\u4EE3\u7406${event.status === "completed" ? "\u5B8C\u6210" : "\u7ED3\u675F"}\uFF1A${truncate(event.summary || "No summary.", 100)}`
2696
+ });
2697
+ const threadId = ctx.state.taskThreadMap.get(event.taskId);
2698
+ if (!threadId) return;
2699
+ const thread = ctx.channel.threads?.cache.get(threadId);
2700
+ if (thread) {
2701
+ const emoji = event.status === "completed" ? "\u2705" : "\u274C";
2702
+ thread.send(`${emoji} \u5B50\u4EFB\u52A1${event.status === "completed" ? "\u5B8C\u6210" : "\u7ED3\u675F"}\uFF1A${truncate(event.summary || "", 1900)}`).catch(
2703
+ (e) => console.warn(
2704
+ `[OutputHandler] Failed to send task done to thread: ${e.message}`
2705
+ )
2706
+ );
2707
+ }
2708
+ ctx.state.taskThreadMap.delete(event.taskId);
2709
+ };
2710
+ var webSearch = (event, ctx) => {
2711
+ if (ctx.verbose) {
2712
+ queueDigest(ctx.sessionId, { kind: "search", text: `\u68C0\u7D22\uFF1A${truncate(event.query, 80)}` });
2713
+ }
2714
+ };
2715
+ var toolStart = async (event, ctx) => {
2716
+ await ctx.streamer.finalize();
2717
+ queueDigest(ctx.sessionId, { kind: "tool", text: `\u5DE5\u5177\uFF1A${event.toolName}` });
2718
+ ctx.state.lastToolName = event.toolName;
2719
+ };
2720
+ var toolResult = async (event, ctx) => {
2721
+ await ctx.streamer.finalize();
2722
+ if (ctx.verbose && event.result) {
2723
+ queueDigest(ctx.sessionId, {
2724
+ kind: "tool",
2725
+ text: `\u5DE5\u5177\u7ED3\u679C\uFF1A${truncate(ctx.state.lastToolName || event.toolName || "tool", 60)}`
2726
+ });
2727
+ }
2728
+ };
2729
+ var imageFile = (event, ctx) => {
2730
+ if (existsSync(event.filePath)) {
2731
+ ctx.state.pendingAttachments.push(event.filePath);
2732
+ }
2733
+ };
2734
+ var commandExecution = (event, ctx) => {
2735
+ ctx.state.commandCount++;
2736
+ if (ctx.state.recentCommands.length < 8) ctx.state.recentCommands.push(event.command);
2737
+ if (!shouldSuppressCommandExecution(event.command)) {
2738
+ queueDigest(ctx.sessionId, {
2739
+ kind: "command",
2740
+ text: `\u547D\u4EE4\uFF1A${truncate(event.command, 80)}${event.exitCode !== null ? `\uFF08\u9000\u51FA\u7801 ${event.exitCode}\uFF09` : ""}`
2741
+ });
2742
+ }
2743
+ };
2744
+ var fileChange = (event, ctx) => {
2745
+ ctx.state.fileChangeCount += event.changes.length;
2746
+ for (const change of event.changes) {
2747
+ if (!change.filePath) continue;
2748
+ if (ctx.state.changedFiles.includes(change.filePath)) continue;
2749
+ if (ctx.state.changedFiles.length >= 12) break;
2750
+ ctx.state.changedFiles.push(change.filePath);
2751
+ }
2752
+ queueDigest(ctx.sessionId, {
2753
+ kind: "file",
2754
+ text: `\u6587\u4EF6\u53D8\u66F4\uFF1A${event.changes.length} \u4E2A\uFF08\u6700\u8FD1\uFF1A${truncate(ctx.state.changedFiles.slice(-3).join(", "), 120)}\uFF09`
2755
+ });
2756
+ };
2757
+ var reasoning = (event, ctx) => {
2758
+ if (ctx.verbose) {
2759
+ queueDigest(ctx.sessionId, { kind: "reasoning", text: `\u63A8\u7406\uFF1A${truncate(event.text, 100)}` });
2760
+ }
2761
+ };
2762
+ var todoList = (event, ctx) => {
2763
+ const completed = event.items.filter((item) => item.completed).length;
2764
+ queueDigest(ctx.sessionId, {
2765
+ kind: "todo",
2766
+ text: `\u5F85\u529E\u66F4\u65B0\uFF1A${completed}/${event.items.length} \u5DF2\u5B8C\u6210`
2767
+ });
2768
+ };
2769
+ var MODE_LABELS = {
2770
+ auto: "\u81EA\u52A8",
2771
+ plan: "\u8BA1\u5212",
2772
+ normal: "\u666E\u901A",
2773
+ monitor: "\u76D1\u63A7"
2774
+ };
2775
+ var result = async (event, ctx) => {
2776
+ ctx.state.success = event.success;
2777
+ const lastText = ctx.streamer.getText();
2778
+ const cost = event.costUsd.toFixed(4);
2779
+ const duration = event.durationMs ? `${(event.durationMs / 1e3).toFixed(1)}s` : "unknown";
2780
+ const turns = event.numTurns || 0;
2781
+ const modeLabel = MODE_LABELS[ctx.mode] || "\u81EA\u52A8";
2782
+ const statusLine = event.success ? `-# $${cost} | ${duration} | ${turns} turns | ${modeLabel}` : `-# Error | $${cost} | ${duration} | ${turns} turns`;
2783
+ ctx.streamer.append(`
2784
+ ${statusLine}`, { persist: false });
2785
+ if (!event.success && event.errors.length) {
2786
+ ctx.streamer.append(`
2787
+ \`\`\`
2788
+ ${event.errors.join("\n")}
2789
+ \`\`\``, { persist: false });
2790
+ }
2791
+ await ctx.streamer.finalize();
2792
+ const session = getSessionView(ctx.sessionId);
2793
+ if (!session) {
2794
+ ctx.state.pendingAttachments = [];
2795
+ return;
2796
+ }
2797
+ if (ctx.mode === "monitor") {
2798
+ await flushDigest(ctx.sessionId);
2799
+ await updateSessionState(ctx.sessionId, {
2800
+ type: "work_started",
2801
+ sessionId: ctx.sessionId,
2802
+ source: providerSource(ctx.sessionId),
2803
+ confidence: "high",
2804
+ timestamp: Date.now(),
2805
+ metadata: {
2806
+ phase: "\u7B49\u5F85\u76D1\u7763\u5224\u65AD",
2807
+ summary: event.success ? "\u672C\u8F6E\u6267\u884C\u7ED3\u675F\uFF0C\u7B49\u5F85\u76D1\u7763\u5224\u65AD" : "\u672C\u8F6E\u6267\u884C\u5931\u8D25\uFF0C\u7B49\u5F85\u76D1\u7763\u5224\u65AD"
2808
+ }
2809
+ });
2810
+ } else {
2811
+ ctx.state.deferredResult = {
2812
+ event,
2813
+ text: lastText,
2814
+ attachments: [...ctx.state.pendingAttachments]
2815
+ };
2816
+ }
2817
+ ctx.state.pendingAttachments = [];
2818
+ };
2819
+ var errorEvent = async (event, ctx) => {
2820
+ console.warn(`[OutputHandler] Session ${ctx.sessionId}: error event \u2014 ${event.message}`);
2821
+ ctx.state.hadError = true;
2822
+ await ctx.streamer.finalize();
2823
+ queueDigest(ctx.sessionId, { kind: "error", text: `\u9519\u8BEF\uFF1A${truncate(event.message, 120)}` });
2824
+ if (ctx.mode === "monitor") return;
2825
+ const session = getSessionView(ctx.sessionId);
2826
+ if (!session) return;
2827
+ await flushDigest(ctx.sessionId);
2828
+ await updateSessionState(ctx.sessionId, {
2829
+ type: "errored",
2830
+ sessionId: ctx.sessionId,
2831
+ source: providerSource(ctx.sessionId),
2832
+ confidence: "high",
2833
+ timestamp: Date.now(),
2834
+ metadata: { errorMessage: event.message }
2835
+ });
2836
+ };
2837
+ var sessionInit = () => {
2838
+ };
2839
+ var EVENT_HANDLERS = {
2840
+ text_delta: textDelta,
2841
+ ask_user: askUser,
2842
+ task,
2843
+ task_started: taskStarted,
2844
+ task_progress: taskProgress,
2845
+ task_done: taskDone,
2846
+ web_search: webSearch,
2847
+ tool_start: toolStart,
2848
+ tool_result: toolResult,
2849
+ image_file: imageFile,
2850
+ command_execution: commandExecution,
2851
+ file_change: fileChange,
2852
+ reasoning,
2853
+ todo_list: todoList,
2854
+ result,
2855
+ error: errorEvent,
2856
+ session_init: sessionInit
2857
+ };
2858
+ async function dispatchEvent(event, ctx) {
2859
+ const handler = EVENT_HANDLERS[event.type];
2860
+ if (!handler) return;
2861
+ await handler(event, ctx);
2862
+ }
2863
+
2477
2864
  // ../bot/src/output-handler.ts
2865
+ var DIGEST_FLUSH_INTERVAL_MS = 8e3;
2866
+ function createInitialState() {
2867
+ return {
2868
+ askedUser: false,
2869
+ hadError: false,
2870
+ success: null,
2871
+ commandCount: 0,
2872
+ fileChangeCount: 0,
2873
+ recentCommands: [],
2874
+ changedFiles: [],
2875
+ pendingAttachments: [],
2876
+ lastToolName: null,
2877
+ taskThreadMap: /* @__PURE__ */ new Map()
2878
+ };
2879
+ }
2478
2880
  async function handleOutputStream(stream, channel, sessionId, verbose = false, mode = "auto", _provider = "claude", options = {}) {
2479
2881
  const streamer = new MessageStreamer(channel, sessionId);
2480
- console.log(`[OutputHandler] Stream started for session ${sessionId} (provider: ${_provider}, mode: ${mode}, verbose: ${verbose})`);
2481
- let lastToolName = null;
2482
- let askedUser = false;
2483
- let askUserQuestionsJson;
2484
- let hadError = false;
2485
- let success = null;
2486
- let commandCount = 0;
2487
- let fileChangeCount = 0;
2488
- const recentCommands = [];
2489
- const changedFiles = [];
2490
- const pendingAttachments = [];
2491
- const taskThreadMap = /* @__PURE__ */ new Map();
2492
- let deferredResult;
2882
+ console.log(
2883
+ `[OutputHandler] Stream started for session ${sessionId} (provider: ${_provider}, mode: ${mode}, verbose: ${verbose})`
2884
+ );
2885
+ const state = createInitialState();
2493
2886
  let lastDigestFlushAt = Date.now();
2494
- const session = getSession(sessionId);
2887
+ const session = getSessionView(sessionId);
2495
2888
  if (session) {
2496
2889
  await initializeSessionPanel(sessionId, channel, {
2497
2890
  statusCardMessageId: session.statusCardMessageId,
@@ -2506,238 +2899,34 @@ async function handleOutputStream(stream, channel, sessionId, verbose = false, m
2506
2899
  timestamp: Date.now()
2507
2900
  });
2508
2901
  }
2902
+ const ctx = { sessionId, channel, streamer, verbose, mode, state };
2509
2903
  try {
2510
2904
  for await (const event of stream) {
2511
2905
  options.onEvent?.(event);
2512
- switch (event.type) {
2513
- case "text_delta": {
2514
- streamer.append(event.text);
2515
- break;
2516
- }
2517
- case "ask_user": {
2518
- console.log(`[OutputHandler] Session ${sessionId}: ask_user event (human input requested)`);
2519
- askedUser = true;
2520
- askUserQuestionsJson = event.questionsJson;
2521
- await streamer.discard();
2522
- if (session) {
2523
- await updateSessionState(sessionId, {
2524
- type: "awaiting_human",
2525
- sessionId,
2526
- source: session.provider === "claude" ? "claude" : "codex",
2527
- confidence: "high",
2528
- timestamp: Date.now(),
2529
- metadata: { detail: event.questionsJson }
2530
- });
2531
- await flushDigest(sessionId);
2532
- await handleAwaitingHuman(sessionId, event.questionsJson, {
2533
- source: session.provider === "claude" ? "claude" : "codex"
2534
- });
2535
- }
2536
- break;
2537
- }
2538
- case "task": {
2539
- await streamer.finalize();
2540
- queueDigest(sessionId, { kind: "tool", text: `\u4EFB\u52A1\u5DE5\u5177\uFF1A${event.action}` });
2541
- lastToolName = event.action;
2542
- break;
2543
- }
2544
- case "task_started": {
2545
- await streamer.finalize();
2546
- queueDigest(sessionId, {
2547
- kind: "subagent",
2548
- text: `\u5B50\u4EE3\u7406\u542F\u52A8\uFF1A${truncate(event.description, 80)}`
2549
- });
2550
- if (session && channel.type !== void 0) {
2551
- autoSpawnSubagentThread(session, event.taskId, event.description, channel).then((result) => {
2552
- if (result) taskThreadMap.set(event.taskId, result.threadId);
2553
- }).catch((err) => console.warn(`[OutputHandler] Failed to auto-spawn thread for task ${event.taskId}: ${err.message}`));
2554
- }
2555
- break;
2556
- }
2557
- case "task_progress": {
2558
- if (event.summary) {
2559
- queueDigest(sessionId, {
2560
- kind: "subagent",
2561
- text: `\u5B50\u4EE3\u7406\u8FDB\u5C55\uFF1A${truncate(event.summary, 100)}`
2562
- });
2563
- }
2564
- const progressThreadId = taskThreadMap.get(event.taskId);
2565
- if (progressThreadId && event.summary) {
2566
- const thread = channel.threads?.cache.get(progressThreadId);
2567
- if (thread) {
2568
- thread.send(`\u{1F4DD} ${truncate(event.summary, 1900)}`).catch((e) => console.warn(`[OutputHandler] Failed to send progress to thread: ${e.message}`));
2569
- }
2570
- }
2571
- break;
2572
- }
2573
- case "task_done": {
2574
- await streamer.finalize();
2575
- queueDigest(sessionId, {
2576
- kind: "subagent",
2577
- text: `\u5B50\u4EE3\u7406${event.status === "completed" ? "\u5B8C\u6210" : "\u7ED3\u675F"}\uFF1A${truncate(event.summary || "No summary.", 100)}`
2578
- });
2579
- const doneThreadId = taskThreadMap.get(event.taskId);
2580
- if (doneThreadId) {
2581
- const thread = channel.threads?.cache.get(doneThreadId);
2582
- if (thread) {
2583
- const statusEmoji = event.status === "completed" ? "\u2705" : "\u274C";
2584
- thread.send(`${statusEmoji} \u5B50\u4EFB\u52A1${event.status === "completed" ? "\u5B8C\u6210" : "\u7ED3\u675F"}\uFF1A${truncate(event.summary || "", 1900)}`).catch((e) => console.warn(`[OutputHandler] Failed to send task done to thread: ${e.message}`));
2585
- }
2586
- taskThreadMap.delete(event.taskId);
2587
- }
2588
- break;
2589
- }
2590
- case "web_search": {
2591
- if (verbose) {
2592
- queueDigest(sessionId, { kind: "search", text: `\u68C0\u7D22\uFF1A${truncate(event.query, 80)}` });
2593
- }
2594
- break;
2595
- }
2596
- case "tool_start": {
2597
- await streamer.finalize();
2598
- queueDigest(sessionId, { kind: "tool", text: `\u5DE5\u5177\uFF1A${event.toolName}` });
2599
- lastToolName = event.toolName;
2600
- break;
2601
- }
2602
- case "tool_result": {
2603
- await streamer.finalize();
2604
- if (verbose && event.result) {
2605
- queueDigest(sessionId, {
2606
- kind: "tool",
2607
- text: `\u5DE5\u5177\u7ED3\u679C\uFF1A${truncate(lastToolName || event.toolName || "tool", 60)}`
2608
- });
2609
- }
2610
- break;
2611
- }
2612
- case "image_file": {
2613
- if (existsSync(event.filePath)) {
2614
- pendingAttachments.push(event.filePath);
2615
- }
2616
- break;
2617
- }
2618
- case "command_execution": {
2619
- commandCount++;
2620
- if (recentCommands.length < 8) recentCommands.push(event.command);
2621
- if (!shouldSuppressCommandExecution(event.command)) {
2622
- queueDigest(sessionId, {
2623
- kind: "command",
2624
- text: `\u547D\u4EE4\uFF1A${truncate(event.command, 80)}${event.exitCode !== null ? `\uFF08\u9000\u51FA\u7801 ${event.exitCode}\uFF09` : ""}`
2625
- });
2626
- }
2627
- break;
2628
- }
2629
- case "file_change": {
2630
- fileChangeCount += event.changes.length;
2631
- for (const change of event.changes) {
2632
- if (!change.filePath) continue;
2633
- if (changedFiles.includes(change.filePath)) continue;
2634
- if (changedFiles.length >= 12) break;
2635
- changedFiles.push(change.filePath);
2636
- }
2637
- queueDigest(sessionId, {
2638
- kind: "file",
2639
- text: `\u6587\u4EF6\u53D8\u66F4\uFF1A${event.changes.length} \u4E2A\uFF08\u6700\u8FD1\uFF1A${truncate(changedFiles.slice(-3).join(", "), 120)}\uFF09`
2640
- });
2641
- break;
2642
- }
2643
- case "reasoning": {
2644
- if (verbose) {
2645
- queueDigest(sessionId, { kind: "reasoning", text: `\u63A8\u7406\uFF1A${truncate(event.text, 100)}` });
2646
- }
2647
- break;
2648
- }
2649
- case "todo_list": {
2650
- queueDigest(sessionId, {
2651
- kind: "todo",
2652
- text: `\u5F85\u529E\u66F4\u65B0\uFF1A${event.items.filter((item) => item.completed).length}/${event.items.length} \u5DF2\u5B8C\u6210`
2653
- });
2654
- break;
2655
- }
2656
- case "result": {
2657
- success = event.success;
2658
- const lastText = streamer.getText();
2659
- const cost = event.costUsd.toFixed(4);
2660
- const duration = event.durationMs ? `${(event.durationMs / 1e3).toFixed(1)}s` : "unknown";
2661
- const turns = event.numTurns || 0;
2662
- const modeLabel = { auto: "\u81EA\u52A8", plan: "\u8BA1\u5212", normal: "\u666E\u901A", monitor: "\u76D1\u63A7" }[mode] || "\u81EA\u52A8";
2663
- const statusLine = event.success ? `-# $${cost} | ${duration} | ${turns} turns | ${modeLabel}` : `-# Error | $${cost} | ${duration} | ${turns} turns`;
2664
- streamer.append(`
2665
- ${statusLine}`, { persist: false });
2666
- if (!event.success && event.errors.length) {
2667
- streamer.append(`
2668
- \`\`\`
2669
- ${event.errors.join("\n")}
2670
- \`\`\``, { persist: false });
2671
- }
2672
- await streamer.finalize();
2673
- if (session) {
2674
- if (mode === "monitor") {
2675
- await flushDigest(sessionId);
2676
- await updateSessionState(sessionId, {
2677
- type: "work_started",
2678
- sessionId,
2679
- source: session.provider === "claude" ? "claude" : "codex",
2680
- confidence: "high",
2681
- timestamp: Date.now(),
2682
- metadata: {
2683
- phase: "\u7B49\u5F85\u76D1\u7763\u5224\u65AD",
2684
- summary: event.success ? "\u672C\u8F6E\u6267\u884C\u7ED3\u675F\uFF0C\u7B49\u5F85\u76D1\u7763\u5224\u65AD" : "\u672C\u8F6E\u6267\u884C\u5931\u8D25\uFF0C\u7B49\u5F85\u76D1\u7763\u5224\u65AD"
2685
- }
2686
- });
2687
- } else {
2688
- deferredResult = {
2689
- event,
2690
- text: lastText,
2691
- attachments: [...pendingAttachments]
2692
- };
2693
- }
2694
- }
2695
- pendingAttachments.length = 0;
2696
- break;
2697
- }
2698
- case "error": {
2699
- console.warn(`[OutputHandler] Session ${sessionId}: error event \u2014 ${event.message}`);
2700
- hadError = true;
2701
- await streamer.finalize();
2702
- queueDigest(sessionId, { kind: "error", text: `\u9519\u8BEF\uFF1A${truncate(event.message, 120)}` });
2703
- if (session && mode !== "monitor") {
2704
- await flushDigest(sessionId);
2705
- await updateSessionState(sessionId, {
2706
- type: "errored",
2707
- sessionId,
2708
- source: session.provider === "claude" ? "claude" : "codex",
2709
- confidence: "high",
2710
- timestamp: Date.now(),
2711
- metadata: { errorMessage: event.message }
2712
- });
2713
- }
2714
- break;
2715
- }
2716
- case "session_init": {
2717
- break;
2718
- }
2719
- }
2720
- if (session && Date.now() - lastDigestFlushAt >= 8e3) {
2906
+ await dispatchEvent(event, ctx);
2907
+ if (session && Date.now() - lastDigestFlushAt >= DIGEST_FLUSH_INTERVAL_MS) {
2721
2908
  await flushDigest(sessionId);
2722
2909
  lastDigestFlushAt = Date.now();
2723
2910
  }
2724
2911
  }
2725
- if (session && mode !== "monitor" && deferredResult) {
2726
- console.log(`[OutputHandler] Session ${sessionId}: delivering deferred result (success: ${deferredResult.event.success})`);
2912
+ if (session && mode !== "monitor" && state.deferredResult) {
2913
+ console.log(
2914
+ `[OutputHandler] Session ${sessionId}: delivering deferred result (success: ${state.deferredResult.event.success})`
2915
+ );
2727
2916
  await flushDigest(sessionId);
2728
2917
  await handleResultEvent(
2729
2918
  sessionId,
2730
- deferredResult.event,
2731
- deferredResult.text,
2732
- deferredResult.attachments
2919
+ state.deferredResult.event,
2920
+ state.deferredResult.text,
2921
+ state.deferredResult.attachments
2733
2922
  );
2734
2923
  }
2735
2924
  } catch (err) {
2736
- hadError = true;
2925
+ state.hadError = true;
2737
2926
  await streamer.finalize();
2738
2927
  if (!isAbortError(err)) {
2739
- console.error(`[OutputHandler] Session ${sessionId}: unhandled stream error \u2014 ${err.message || ""}`);
2740
2928
  const errMsg = err.message || "";
2929
+ console.error(`[OutputHandler] Session ${sessionId}: unhandled stream error \u2014 ${errMsg}`);
2741
2930
  queueDigest(sessionId, { kind: "error", text: `\u5F02\u5E38\uFF1A${truncate(errMsg, 120)}` });
2742
2931
  if (session) {
2743
2932
  await flushDigest(sessionId);
@@ -2755,37 +2944,56 @@ ${event.errors.join("\n")}
2755
2944
  streamer.destroy();
2756
2945
  }
2757
2946
  const finalText = streamer.getText();
2758
- console.log(`[OutputHandler] Stream ended for session ${sessionId} (text: ${finalText.length} chars, commands: ${commandCount}, files: ${fileChangeCount}, success: ${success}, hadError: ${hadError})`);
2947
+ console.log(
2948
+ `[OutputHandler] Stream ended for session ${sessionId} (text: ${finalText.length} chars, commands: ${state.commandCount}, files: ${state.fileChangeCount}, success: ${state.success}, hadError: ${state.hadError})`
2949
+ );
2759
2950
  return {
2760
2951
  text: finalText,
2761
- askedUser,
2762
- askUserQuestionsJson,
2763
- hadError,
2764
- success,
2765
- commandCount,
2766
- fileChangeCount,
2767
- recentCommands,
2768
- changedFiles
2952
+ askedUser: state.askedUser,
2953
+ askUserQuestionsJson: state.askUserQuestionsJson,
2954
+ hadError: state.hadError,
2955
+ success: state.success,
2956
+ commandCount: state.commandCount,
2957
+ fileChangeCount: state.fileChangeCount,
2958
+ recentCommands: state.recentCommands,
2959
+ changedFiles: state.changedFiles
2769
2960
  };
2770
2961
  }
2771
2962
 
2772
2963
  // ../bot/src/discord-output-port.ts
2964
+ function asSessionChannel(ref) {
2965
+ return ref;
2966
+ }
2967
+ function toResultEvent(data) {
2968
+ return {
2969
+ type: "result",
2970
+ success: data.success,
2971
+ costUsd: data.costUsd,
2972
+ durationMs: data.durationMs,
2973
+ numTurns: data.numTurns,
2974
+ errors: data.errors,
2975
+ metadata: data.metadata
2976
+ };
2977
+ }
2773
2978
  var DiscordOutputPort = class {
2774
2979
  async initializePanel(session, channel) {
2775
- await initializeSessionPanel(session.id, channel);
2980
+ await initializeSessionPanel(session.id, asSessionChannel(channel));
2776
2981
  }
2777
2982
  async updateState(sessionId, event, options) {
2778
- await updateSessionState(sessionId, event, options?.channel ? { channel: options.channel } : void 0);
2983
+ await updateSessionState(
2984
+ sessionId,
2985
+ event,
2986
+ options?.channel ? { channel: asSessionChannel(options.channel) } : void 0
2987
+ );
2779
2988
  }
2780
2989
  async handleResult(sessionId, resultEvent, summary) {
2781
- const event = resultEvent;
2782
- await handleResultEvent(sessionId, event, summary ?? "");
2990
+ await handleResultEvent(sessionId, toResultEvent(resultEvent), summary ?? "");
2783
2991
  }
2784
2992
  async handleAwaitingHuman(sessionId, reason, options) {
2785
2993
  await handleAwaitingHuman(sessionId, reason, options);
2786
2994
  }
2787
2995
  async relocatePanel(sessionId, channel) {
2788
- await relocateSessionPanelToBottom(sessionId, channel);
2996
+ await relocateSessionPanelToBottom(sessionId, asSessionChannel(channel));
2789
2997
  }
2790
2998
  cleanupPanel(sessionId) {
2791
2999
  cleanupSessionPanel(sessionId);
@@ -2796,7 +3004,7 @@ var DiscordOutputPort = class {
2796
3004
  async handleOutputStream(stream, channel, sessionId, verbose = false, mode = "auto", provider = "claude", options = {}) {
2797
3005
  return handleOutputStream(
2798
3006
  stream,
2799
- channel,
3007
+ asSessionChannel(channel),
2800
3008
  sessionId,
2801
3009
  verbose,
2802
3010
  mode,
@@ -2927,36 +3135,36 @@ function buildProjectCleanupPreview(input) {
2927
3135
  return preview;
2928
3136
  }
2929
3137
  async function archiveSessionsById(guild, sessionIds, summary = "Bulk cleanup from Discord command") {
2930
- const result = {
3138
+ const result2 = {
2931
3139
  archivedSessions: 0,
2932
3140
  skippedGenerating: 0,
2933
3141
  missingSessions: 0,
2934
3142
  failed: []
2935
3143
  };
2936
3144
  for (const sessionId of new Set(Array.from(sessionIds).filter(Boolean))) {
2937
- const session = getSession(sessionId);
3145
+ const session = getSessionView(sessionId);
2938
3146
  if (!session || session.type !== "persistent") {
2939
- result.missingSessions += 1;
3147
+ result2.missingSessions += 1;
2940
3148
  continue;
2941
3149
  }
2942
3150
  if (session.isGenerating) {
2943
- result.skippedGenerating += 1;
3151
+ result2.skippedGenerating += 1;
2944
3152
  continue;
2945
3153
  }
2946
3154
  try {
2947
3155
  await archiveSession(session, guild, summary);
2948
- result.archivedSessions += 1;
3156
+ result2.archivedSessions += 1;
2949
3157
  } catch (error) {
2950
3158
  console.error(`[Housekeeping] Failed to archive session ${sessionId}: ${error instanceof Error ? error.message : String(error)}`);
2951
- result.failed.push({
3159
+ result2.failed.push({
2952
3160
  sessionId: session.id,
2953
3161
  channelId: session.channelId,
2954
3162
  message: error instanceof Error ? error.message : String(error)
2955
3163
  });
2956
3164
  }
2957
3165
  }
2958
- console.log(`[Housekeeping] Archive result: ${result.archivedSessions} archived, ${result.skippedGenerating} skipped, ${result.missingSessions} missing, ${result.failed.length} failed`);
2959
- return result;
3166
+ console.log(`[Housekeeping] Archive result: ${result2.archivedSessions} archived, ${result2.skippedGenerating} skipped, ${result2.missingSessions} missing, ${result2.failed.length} failed`);
3167
+ return result2;
2960
3168
  }
2961
3169
  async function reconcileSessionRecordsWithGuild(guild) {
2962
3170
  const sessions = getAllSessions();
@@ -3446,6 +3654,7 @@ var POLL_INTERVAL_IDLE_MS = 5e3;
3446
3654
  var STALE_CLEANUP_MS = 12e4;
3447
3655
  var MAX_LINES_PER_POLL = 100;
3448
3656
  var MAX_BUFFER_SIZE = 10 * 1024 * 1024;
3657
+ var MAX_FILE_SIZE_BYTES = 500 * 1024 * 1024;
3449
3658
  var CodexLogMonitor = class {
3450
3659
  tracked = /* @__PURE__ */ new Map();
3451
3660
  interval = null;
@@ -3539,6 +3748,14 @@ var CodexLogMonitor = class {
3539
3748
  } catch {
3540
3749
  return;
3541
3750
  }
3751
+ if (stat.size > MAX_FILE_SIZE_BYTES) {
3752
+ if (!this.tracked.has(filePath)) {
3753
+ console.warn(
3754
+ `[CodexLogMonitor] Skipping oversized rollout file (${Math.round(stat.size / 1024 / 1024)}MB): ${filePath}`
3755
+ );
3756
+ }
3757
+ return;
3758
+ }
3542
3759
  let tracked = this.tracked.get(filePath);
3543
3760
  if (!tracked) {
3544
3761
  const sessionId = this.extractSessionId(fileName);
@@ -3785,7 +4002,7 @@ async function handleCodexMonitorStateChange(resolveChannel2, monitorSessionId,
3785
4002
 
3786
4003
  // ../bot/src/ipc-server.ts
3787
4004
  import { createServer } from "net";
3788
- import { unlinkSync, existsSync as existsSync3 } from "fs";
4005
+ import { unlinkSync, existsSync as existsSync3, chmodSync } from "fs";
3789
4006
 
3790
4007
  // ../bot/src/session-discovery.ts
3791
4008
  async function discoverAndRegisterSession(client2, params) {
@@ -3794,7 +4011,7 @@ async function discoverAndRegisterSession(client2, params) {
3794
4011
  console.warn("[Session Discovery] No guild found, cannot register local session");
3795
4012
  return null;
3796
4013
  }
3797
- const result = await registerLocalSession(
4014
+ const result2 = await registerLocalSession(
3798
4015
  {
3799
4016
  provider: params.provider,
3800
4017
  providerSessionId: params.providerSessionId,
@@ -3806,11 +4023,11 @@ async function discoverAndRegisterSession(client2, params) {
3806
4023
  },
3807
4024
  guild
3808
4025
  );
3809
- if (!result) return null;
4026
+ if (!result2) return null;
3810
4027
  return {
3811
- sessionId: result.session.id,
3812
- channelId: result.session.channelId,
3813
- isNew: result.isNewlyCreated
4028
+ sessionId: result2.session.id,
4029
+ channelId: result2.session.channelId,
4030
+ isNew: result2.isNewlyCreated
3814
4031
  };
3815
4032
  }
3816
4033
 
@@ -3871,6 +4088,15 @@ function startIpcServer(client2) {
3871
4088
  });
3872
4089
  });
3873
4090
  server.listen(socketPath, () => {
4091
+ if (process.platform !== "win32") {
4092
+ try {
4093
+ chmodSync(socketPath, 384);
4094
+ } catch (err) {
4095
+ console.warn(
4096
+ `[IpcServer] Failed to set 0600 on socket ${socketPath}: ${err.message}`
4097
+ );
4098
+ }
4099
+ }
3874
4100
  console.log(`[IpcServer] Listening on ${socketPath}`);
3875
4101
  });
3876
4102
  server.on("error", (err) => {
@@ -3941,7 +4167,7 @@ async function handleHookEvent(payload) {
3941
4167
  } : void 0
3942
4168
  });
3943
4169
  if (registered) {
3944
- session = getSession(registered.sessionId);
4170
+ session = getSessionView(registered.sessionId);
3945
4171
  console.log(`[IpcServer] Auto-registered session: ${registered.sessionId}`);
3946
4172
  }
3947
4173
  }
@@ -3961,14 +4187,14 @@ async function handleHookEvent(payload) {
3961
4187
  async function handleGateResolved(payload) {
3962
4188
  const data = payload;
3963
4189
  console.log(`[IpcServer] Terminal resolved gate ${data.gateId}: ${data.action}`);
3964
- const result = gateCoordinator.notifyTerminalResolved(data.gateId, data.action);
3965
- if (!result.success) {
3966
- console.warn(`[IpcServer] Gate resolution failed: ${result.message}`);
4190
+ const result2 = gateService.notifyTerminalResolved(data.gateId, data.action);
4191
+ if (!result2.success) {
4192
+ console.warn(`[IpcServer] Gate resolution failed: ${result2.message}`);
3967
4193
  return;
3968
4194
  }
3969
- const gate = gateCoordinator.getGate(data.gateId);
4195
+ const gate = gateService.getGate(data.gateId);
3970
4196
  if (gate?.discordMessageId) {
3971
- const session = getSession(data.sessionId);
4197
+ const session = getSessionView(data.sessionId);
3972
4198
  if (session) {
3973
4199
  const channel = discordClient?.channels.cache.get(session.channelId);
3974
4200
  if (channel) {
@@ -4656,7 +4882,9 @@ var BotServicesOrchestrator = class {
4656
4882
  logBuffer2.log(`Reconciled ${reconciled.endedMissingSessions} stale session record(s) on startup.`);
4657
4883
  }
4658
4884
  setBotStartTime(Date.now());
4659
- await this.#invalidatePendingGates(client2);
4885
+ await gateService.init();
4886
+ await this.#reconcilePendingGates(client2, logBuffer2);
4887
+ await this.#resumeAbandonedMonitorRuns(client2, logBuffer2);
4660
4888
  if (config.messageRetentionDays) {
4661
4889
  await cleanupOldMessages(client2);
4662
4890
  }
@@ -4667,15 +4895,55 @@ var BotServicesOrchestrator = class {
4667
4895
  logBuffer2.log("Codex log monitor started");
4668
4896
  return { serviceBus: this.#serviceBus, logBuffer: logBuffer2, presenceManager, logChannel, codexMonitor: null };
4669
4897
  }
4670
- async #invalidatePendingGates(client2) {
4671
- const invalidated = gateCoordinator.invalidateAllOnRestart();
4672
- if (!invalidated.length) return;
4673
- console.log(`[GateInvalidation] Invalidating ${invalidated.length} pending gates on restart`);
4674
- for (const { gateId, discordMessageId } of invalidated) {
4898
+ /**
4899
+ * 重启后对悬挂的 Monitor run 执行 reconcile,并按 config.monitorAutoResumePolicy 续跑符合条件的 session。
4900
+ * 续跑采用 fire-and-forget:每个 session 独立异步运行,不阻塞启动。
4901
+ */
4902
+ async #resumeAbandonedMonitorRuns(client2, logBuffer2) {
4903
+ let result2;
4904
+ try {
4905
+ result2 = await reconcileAndCollectAutoResumeCandidates(config.monitorAutoResumePolicy);
4906
+ } catch (err) {
4907
+ console.error("[MonitorAutoResume] reconcile failed:", err);
4908
+ return;
4909
+ }
4910
+ if (result2.abandoned.length > 0) {
4911
+ logBuffer2.log(
4912
+ `Marked ${result2.abandoned.length} orphan monitor run(s) as abandoned on startup.`
4913
+ );
4914
+ }
4915
+ if (result2.candidates.length === 0) return;
4916
+ logBuffer2.log(
4917
+ `Auto-resuming ${result2.candidates.length} monitor session(s) (policy=${config.monitorAutoResumePolicy}).`
4918
+ );
4919
+ for (const candidate of result2.candidates) {
4920
+ const session = getSessionView(candidate.sessionId);
4921
+ if (!session) continue;
4922
+ const channel = client2.channels.cache.get(candidate.channelId);
4923
+ if (!channel) {
4924
+ console.warn(
4925
+ `[MonitorAutoResume] sessionId=${candidate.sessionId} channel ${candidate.channelId} not accessible, skipping`
4926
+ );
4927
+ continue;
4928
+ }
4929
+ console.log(
4930
+ `[MonitorAutoResume] resuming sessionId=${candidate.sessionId} runId=${candidate.runId} lastIteration=${candidate.lastIteration}`
4931
+ );
4932
+ void executeSessionContinue(session, channel).catch((err) => {
4933
+ console.error(
4934
+ `[MonitorAutoResume] sessionId=${candidate.sessionId} resume failed:`,
4935
+ err
4936
+ );
4937
+ });
4938
+ }
4939
+ }
4940
+ async #reconcilePendingGates(client2, logBuffer2) {
4941
+ const result2 = gateService.reconcileOnStartup(config.gateRestartPolicy);
4942
+ for (const { gateId, discordMessageId } of result2.invalidated) {
4675
4943
  if (!discordMessageId) continue;
4676
- const gate = gateCoordinator.getGate(gateId);
4944
+ const gate = gateService.getGate(gateId);
4677
4945
  if (!gate?.sessionId) continue;
4678
- const session = getSession(gate.sessionId);
4946
+ const session = getSessionView(gate.sessionId);
4679
4947
  if (!session) continue;
4680
4948
  const channel = client2.channels.cache.get(session.channelId);
4681
4949
  if (!channel?.messages) continue;
@@ -4692,10 +4960,17 @@ var BotServicesOrchestrator = class {
4692
4960
  }))
4693
4961
  });
4694
4962
  } catch (err) {
4695
- console.error(`[GateInvalidation] Failed to update message for gate ${gateId}:`, err);
4963
+ console.error(`[GateReconcile] Failed to update message for gate ${gateId}:`, err);
4696
4964
  }
4697
4965
  }
4698
- console.log(`[GateInvalidation] ${invalidated.length} gates invalidated`);
4966
+ if (result2.resumed.length > 0) {
4967
+ logBuffer2.log(
4968
+ `Resumed ${result2.resumed.length} pending gate(s) across restart (policy=resume-pending).`
4969
+ );
4970
+ }
4971
+ if (result2.invalidated.length > 0) {
4972
+ logBuffer2.log(`Closed ${result2.invalidated.length} orphan/overdue gate(s) on startup.`);
4973
+ }
4699
4974
  }
4700
4975
  #registerServices(client2, logBuffer2, presence) {
4701
4976
  this.#serviceBus.register({ name: "message-handler", start() {
@@ -4720,10 +4995,10 @@ var BotServicesOrchestrator = class {
4720
4995
  );
4721
4996
  },
4722
4997
  async (providerSessionId, cwd, remoteHumanControl, subagent) => {
4723
- const { registerLocalSession: registerLocalSession2 } = await import("./session-local-registration-RIO5EPZ5.js");
4998
+ const { registerLocalSession: registerLocalSession2 } = await import("./session-local-registration-MISPPGXF.js");
4724
4999
  const g = client2.guilds.cache.first();
4725
5000
  if (!g) return false;
4726
- const result = await registerLocalSession2(
5001
+ const result2 = await registerLocalSession2(
4727
5002
  {
4728
5003
  provider: "codex",
4729
5004
  providerSessionId,
@@ -4735,10 +5010,10 @@ var BotServicesOrchestrator = class {
4735
5010
  },
4736
5011
  g
4737
5012
  );
4738
- if (result?.isNewlyCreated && result.session.remoteHumanControl === false) {
4739
- void notifyUnmanagedCodexHint(client2, result.session.id, result.session.channelId);
5013
+ if (result2?.isNewlyCreated && result2.session.remoteHumanControl === false) {
5014
+ void notifyUnmanagedCodexHint(client2, result2.session.id, result2.session.channelId);
4740
5015
  }
4741
- return result !== null;
5016
+ return result2 !== null;
4742
5017
  }
4743
5018
  );
4744
5019
  this.#serviceBus.register({
@@ -4768,8 +5043,8 @@ var BotServicesOrchestrator = class {
4768
5043
  stopPerformanceMonitoring();
4769
5044
  } });
4770
5045
  this.#serviceBus.register(intervalService("gate-housekeeping", () => {
4771
- const expired = gateCoordinator.cleanupExpired();
4772
- const archived = gateCoordinator.archiveResolved(100);
5046
+ const expired = gateService.cleanupExpired();
5047
+ const archived = gateService.archiveResolved(100);
4773
5048
  if (expired || archived) console.log(`[GateHousekeeping] expired=${expired}, archived=${archived}`);
4774
5049
  }, 6e4));
4775
5050
  this.#serviceBus.register(intervalService("presence", () => presence.updatePresence(), 3e4));
@@ -4874,7 +5149,7 @@ var PROVIDER_COLORS = {
4874
5149
  claude: 3447003,
4875
5150
  codex: 1090431
4876
5151
  };
4877
- var MODE_LABELS = {
5152
+ var MODE_LABELS2 = {
4878
5153
  auto: "\u26A1 \u81EA\u52A8\u6A21\u5F0F \u2014 \u5B8C\u5168\u81EA\u4E3B",
4879
5154
  plan: "\u{1F4CB} \u8BA1\u5212\u6A21\u5F0F \u2014 \u53D8\u66F4\u524D\u5148\u89C4\u5212",
4880
5155
  normal: "\u{1F6E1}\uFE0F \u666E\u901A\u6A21\u5F0F \u2014 \u7834\u574F\u6027\u64CD\u4F5C\u524D\u786E\u8BA4",
@@ -4884,7 +5159,7 @@ var CONTROL_CHANNEL_NAME = "control";
4884
5159
  var CLEANUP_PREVIEW_LIST_LIMIT = 10;
4885
5160
  var CLEANUP_PREVIEW_LABEL_LIMIT = 120;
4886
5161
  async function registerStatusCardWithPanelAdapter(sessionId, channel, statusCardMessageId) {
4887
- const { registerExistingStatusCard: registerExistingStatusCard2 } = await import("./panel-adapter-QTDL3S6O.js");
5162
+ const { registerExistingStatusCard: registerExistingStatusCard2 } = await import("./panel-adapter-U75WXDLB.js");
4888
5163
  if (typeof registerExistingStatusCard2 !== "function") {
4889
5164
  log(`[panel-adapter] registerExistingStatusCard \u672A\u66B4\u9732\uFF0Csession=${sessionId}`);
4890
5165
  return false;
@@ -5514,7 +5789,7 @@ async function handleAgentSpawn(interaction) {
5514
5789
  const embed = new EmbedBuilder4().setColor(3066993).setTitle(`\u2705 Agent \u5DF2\u521B\u5EFA\uFF1A${label}`).addFields(
5515
5790
  { name: "\u9891\u9053", value: `<#${sessionChannel.id}>`, inline: true },
5516
5791
  { name: "\u63D0\u4F9B\u5546", value: PROVIDER_LABELS[provider], inline: true },
5517
- { name: "\u6A21\u5F0F", value: MODE_LABELS?.[mode] ?? mode, inline: false },
5792
+ { name: "\u6A21\u5F0F", value: MODE_LABELS2?.[mode] ?? mode, inline: false },
5518
5793
  { name: "\u76EE\u5F55", value: `\`${session.directory}\``, inline: false }
5519
5794
  );
5520
5795
  if (provider === "claude" && session.claudePermissionMode) {
@@ -5682,7 +5957,7 @@ async function handleAgentMode(interaction) {
5682
5957
  }
5683
5958
  const mode = interaction.options.getString("mode", true);
5684
5959
  setMode(session.id, mode);
5685
- await interaction.reply({ content: `\u6A21\u5F0F\u5DF2\u8BBE\u4E3A **${MODE_LABELS?.[mode] ?? mode}**\u3002`, ephemeral: true });
5960
+ await interaction.reply({ content: `\u6A21\u5F0F\u5DF2\u8BBE\u4E3A **${MODE_LABELS2?.[mode] ?? mode}**\u3002`, ephemeral: true });
5686
5961
  }
5687
5962
  async function handleAgentGoal(interaction) {
5688
5963
  const session = getSessionByChannel(interaction.channelId);
@@ -5736,7 +6011,7 @@ async function handleAgentPermissions(interaction) {
5736
6011
  return;
5737
6012
  }
5738
6013
  await updateSessionPermissions(session.id, patch);
5739
- const refreshed = getSession(session.id) ?? { ...session, ...patch };
6014
+ const refreshed = getSessionView(session.id) ?? { ...session, ...patch };
5740
6015
  const timing = session.isGenerating ? "\u5DF2\u4FDD\u5B58\uFF0C\u5C06\u5728\u4E0B\u4E00\u8F6E\u751F\u6548\u3002" : "\u5DF2\u66F4\u65B0\u5E76\u7ACB\u5373\u751F\u6548\u3002";
5741
6016
  await interaction.reply({
5742
6017
  content: `${timing}
@@ -5912,13 +6187,13 @@ var joinToUint8Array = (uint8ArraysOrStrings) => {
5912
6187
  };
5913
6188
  var stringsToUint8Arrays = (uint8ArraysOrStrings) => uint8ArraysOrStrings.map((uint8ArrayOrString) => typeof uint8ArrayOrString === "string" ? stringToUint8Array(uint8ArrayOrString) : uint8ArrayOrString);
5914
6189
  var concatUint8Arrays = (uint8Arrays) => {
5915
- const result = new Uint8Array(getJoinLength(uint8Arrays));
6190
+ const result2 = new Uint8Array(getJoinLength(uint8Arrays));
5916
6191
  let index = 0;
5917
6192
  for (const uint8Array of uint8Arrays) {
5918
- result.set(uint8Array, index);
6193
+ result2.set(uint8Array, index);
5919
6194
  index += uint8Array.length;
5920
6195
  }
5921
- return result;
6196
+ return result2;
5922
6197
  };
5923
6198
  var getJoinLength = (uint8Arrays) => {
5924
6199
  let joinLength = 0;
@@ -6473,17 +6748,17 @@ var format = (open2, close) => {
6473
6748
  if (index === -1) {
6474
6749
  return openCode + string + closeCode;
6475
6750
  }
6476
- let result = openCode;
6751
+ let result2 = openCode;
6477
6752
  let lastIndex = 0;
6478
6753
  const reopenOnNestedClose = close === 22;
6479
6754
  const replaceCode = (reopenOnNestedClose ? closeCode : "") + openCode;
6480
6755
  while (index !== -1) {
6481
- result += string.slice(lastIndex, index) + replaceCode;
6756
+ result2 += string.slice(lastIndex, index) + replaceCode;
6482
6757
  lastIndex = index + closeCode.length;
6483
6758
  index = string.indexOf(closeCode, lastIndex);
6484
6759
  }
6485
- result += string.slice(lastIndex) + closeCode;
6486
- return result;
6760
+ result2 += string.slice(lastIndex) + closeCode;
6761
+ return result2;
6487
6762
  };
6488
6763
  };
6489
6764
  var reset = format(0, 0);
@@ -6585,8 +6860,8 @@ var appendNewline = (printedLine) => printedLine.endsWith("\n") ? printedLine :
6585
6860
  `;
6586
6861
 
6587
6862
  // ../../node_modules/.pnpm/execa@9.6.1/node_modules/execa/lib/verbose/log.js
6588
- var verboseLog = ({ type, verboseMessage, fdNumber, verboseInfo, result }) => {
6589
- const verboseObject = getVerboseObject({ type, result, verboseInfo });
6863
+ var verboseLog = ({ type, verboseMessage, fdNumber, verboseInfo, result: result2 }) => {
6864
+ const verboseObject = getVerboseObject({ type, result: result2, verboseInfo });
6590
6865
  const printedLines = getPrintedLines(verboseMessage, verboseObject);
6591
6866
  const finalLines = applyVerboseOnLines(printedLines, verboseInfo, fdNumber);
6592
6867
  if (finalLines !== "") {
@@ -6595,7 +6870,7 @@ var verboseLog = ({ type, verboseMessage, fdNumber, verboseInfo, result }) => {
6595
6870
  };
6596
6871
  var getVerboseObject = ({
6597
6872
  type,
6598
- result,
6873
+ result: result2,
6599
6874
  verboseInfo: { escapedCommand, commandId, rawOptions: { piped = false, ...options } }
6600
6875
  }) => ({
6601
6876
  type,
@@ -6603,7 +6878,7 @@ var getVerboseObject = ({
6603
6878
  commandId: `${commandId}`,
6604
6879
  timestamp: /* @__PURE__ */ new Date(),
6605
6880
  piped,
6606
- result,
6881
+ result: result2,
6607
6882
  options
6608
6883
  });
6609
6884
  var getPrintedLines = (verboseMessage, verboseObject) => verboseMessage.split("\n").map((message) => getPrintedLine({ ...verboseObject, message }));
@@ -6732,28 +7007,28 @@ var npmRunPath = ({
6732
7007
  addExecPath = true
6733
7008
  } = {}) => {
6734
7009
  const cwdPath = path2.resolve(toPath(cwd));
6735
- const result = [];
7010
+ const result2 = [];
6736
7011
  const pathParts = pathOption.split(path2.delimiter);
6737
7012
  if (preferLocal) {
6738
- applyPreferLocal(result, pathParts, cwdPath);
7013
+ applyPreferLocal(result2, pathParts, cwdPath);
6739
7014
  }
6740
7015
  if (addExecPath) {
6741
- applyExecPath(result, pathParts, execPath2, cwdPath);
7016
+ applyExecPath(result2, pathParts, execPath2, cwdPath);
6742
7017
  }
6743
- return pathOption === "" || pathOption === path2.delimiter ? `${result.join(path2.delimiter)}${pathOption}` : [...result, pathOption].join(path2.delimiter);
7018
+ return pathOption === "" || pathOption === path2.delimiter ? `${result2.join(path2.delimiter)}${pathOption}` : [...result2, pathOption].join(path2.delimiter);
6744
7019
  };
6745
- var applyPreferLocal = (result, pathParts, cwdPath) => {
7020
+ var applyPreferLocal = (result2, pathParts, cwdPath) => {
6746
7021
  for (const directory of traversePathUp(cwdPath)) {
6747
7022
  const pathPart = path2.join(directory, "node_modules/.bin");
6748
7023
  if (!pathParts.includes(pathPart)) {
6749
- result.push(pathPart);
7024
+ result2.push(pathPart);
6750
7025
  }
6751
7026
  }
6752
7027
  };
6753
- var applyExecPath = (result, pathParts, execPath2, cwdPath) => {
7028
+ var applyExecPath = (result2, pathParts, execPath2, cwdPath) => {
6754
7029
  const pathPart = path2.resolve(cwdPath, toPath(execPath2), "..");
6755
7030
  if (!pathParts.includes(pathPart)) {
6756
- result.push(pathPart);
7031
+ result2.push(pathPart);
6757
7032
  }
6758
7033
  };
6759
7034
  var npmRunPathEnv = ({ env = process4.env, ...options } = {}) => {
@@ -8572,13 +8847,13 @@ var getMaxBufferInfo = (error, maxBuffer) => {
8572
8847
  }
8573
8848
  return { streamName: getStreamName(fdNumber), threshold, unit };
8574
8849
  };
8575
- var isMaxBufferSync = (resultError, output, maxBuffer) => resultError?.code === "ENOBUFS" && output !== null && output.some((result) => result !== null && result.length > getMaxBufferSync(maxBuffer));
8576
- var truncateMaxBufferSync = (result, isMaxBuffer, maxBuffer) => {
8850
+ var isMaxBufferSync = (resultError, output, maxBuffer) => resultError?.code === "ENOBUFS" && output !== null && output.some((result2) => result2 !== null && result2.length > getMaxBufferSync(maxBuffer));
8851
+ var truncateMaxBufferSync = (result2, isMaxBuffer, maxBuffer) => {
8577
8852
  if (!isMaxBuffer) {
8578
- return result;
8853
+ return result2;
8579
8854
  }
8580
8855
  const maxBufferValue = getMaxBufferSync(maxBuffer);
8581
- return result.length > maxBufferValue ? result.slice(0, maxBufferValue) : result;
8856
+ return result2.length > maxBufferValue ? result2.slice(0, maxBufferValue) : result2;
8582
8857
  };
8583
8858
  var getMaxBufferSync = ([, stdoutMaxBuffer]) => stdoutMaxBuffer;
8584
8859
 
@@ -8865,7 +9140,7 @@ var getErrorProperties = ({
8865
9140
  ipcOutput,
8866
9141
  pipedFrom: []
8867
9142
  });
8868
- var omitUndefinedProperties = (result) => Object.fromEntries(Object.entries(result).filter(([, value]) => value !== void 0));
9143
+ var omitUndefinedProperties = (result2) => Object.fromEntries(Object.entries(result2).filter(([, value]) => value !== void 0));
8869
9144
  var normalizeExitPayload = (rawExitCode, rawSignal) => {
8870
9145
  const exitCode = rawExitCode === null ? void 0 : rawExitCode;
8871
9146
  const signal = rawSignal === null ? void 0 : rawSignal;
@@ -8936,25 +9211,25 @@ function prettyMilliseconds(milliseconds, options) {
8936
9211
  options.secondsDecimalDigits = 0;
8937
9212
  options.millisecondsDecimalDigits = 0;
8938
9213
  }
8939
- let result = [];
9214
+ let result2 = [];
8940
9215
  const floorDecimals = (value, decimalDigits) => {
8941
9216
  const flooredInterimValue = Math.floor(value * 10 ** decimalDigits + SECOND_ROUNDING_EPSILON);
8942
9217
  const flooredValue = Math.round(flooredInterimValue) / 10 ** decimalDigits;
8943
9218
  return flooredValue.toFixed(decimalDigits);
8944
9219
  };
8945
9220
  const add = (value, long, short, valueString) => {
8946
- if ((result.length === 0 || !options.colonNotation) && isZero(value) && !(options.colonNotation && short === "m")) {
9221
+ if ((result2.length === 0 || !options.colonNotation) && isZero(value) && !(options.colonNotation && short === "m")) {
8947
9222
  return;
8948
9223
  }
8949
9224
  valueString ??= String(value);
8950
9225
  if (options.colonNotation) {
8951
9226
  const wholeDigits = valueString.includes(".") ? valueString.split(".")[0].length : valueString.length;
8952
- const minLength = result.length > 0 ? 2 : 1;
9227
+ const minLength = result2.length > 0 ? 2 : 1;
8953
9228
  valueString = "0".repeat(Math.max(0, minLength - wholeDigits)) + valueString;
8954
9229
  } else {
8955
9230
  valueString += options.verbose ? " " + pluralize(long, value) : short;
8956
9231
  }
8957
- result.push(valueString);
9232
+ result2.push(valueString);
8958
9233
  };
8959
9234
  const parsed = parseMilliseconds(milliseconds);
8960
9235
  const days = BigInt(parsed.days);
@@ -9001,53 +9276,53 @@ function prettyMilliseconds(milliseconds, options) {
9001
9276
  add(Number.parseFloat(secondsString), "second", "s", secondsString);
9002
9277
  }
9003
9278
  }
9004
- if (result.length === 0) {
9279
+ if (result2.length === 0) {
9005
9280
  return sign + "0" + (options.verbose ? " milliseconds" : "ms");
9006
9281
  }
9007
9282
  const separator = options.colonNotation ? ":" : " ";
9008
9283
  if (typeof options.unitCount === "number") {
9009
- result = result.slice(0, Math.max(options.unitCount, 1));
9284
+ result2 = result2.slice(0, Math.max(options.unitCount, 1));
9010
9285
  }
9011
- return sign + result.join(separator);
9286
+ return sign + result2.join(separator);
9012
9287
  }
9013
9288
 
9014
9289
  // ../../node_modules/.pnpm/execa@9.6.1/node_modules/execa/lib/verbose/error.js
9015
- var logError = (result, verboseInfo) => {
9016
- if (result.failed) {
9290
+ var logError = (result2, verboseInfo) => {
9291
+ if (result2.failed) {
9017
9292
  verboseLog({
9018
9293
  type: "error",
9019
- verboseMessage: result.shortMessage,
9294
+ verboseMessage: result2.shortMessage,
9020
9295
  verboseInfo,
9021
- result
9296
+ result: result2
9022
9297
  });
9023
9298
  }
9024
9299
  };
9025
9300
 
9026
9301
  // ../../node_modules/.pnpm/execa@9.6.1/node_modules/execa/lib/verbose/complete.js
9027
- var logResult = (result, verboseInfo) => {
9302
+ var logResult = (result2, verboseInfo) => {
9028
9303
  if (!isVerbose(verboseInfo)) {
9029
9304
  return;
9030
9305
  }
9031
- logError(result, verboseInfo);
9032
- logDuration(result, verboseInfo);
9306
+ logError(result2, verboseInfo);
9307
+ logDuration(result2, verboseInfo);
9033
9308
  };
9034
- var logDuration = (result, verboseInfo) => {
9035
- const verboseMessage = `(done in ${prettyMilliseconds(result.durationMs)})`;
9309
+ var logDuration = (result2, verboseInfo) => {
9310
+ const verboseMessage = `(done in ${prettyMilliseconds(result2.durationMs)})`;
9036
9311
  verboseLog({
9037
9312
  type: "duration",
9038
9313
  verboseMessage,
9039
9314
  verboseInfo,
9040
- result
9315
+ result: result2
9041
9316
  });
9042
9317
  };
9043
9318
 
9044
9319
  // ../../node_modules/.pnpm/execa@9.6.1/node_modules/execa/lib/return/reject.js
9045
- var handleResult = (result, verboseInfo, { reject }) => {
9046
- logResult(result, verboseInfo);
9047
- if (result.failed && reject) {
9048
- throw result;
9320
+ var handleResult = (result2, verboseInfo, { reject }) => {
9321
+ logResult(result2, verboseInfo);
9322
+ if (result2.failed && reject) {
9323
+ throw result2;
9049
9324
  }
9050
- return result;
9325
+ return result2;
9051
9326
  };
9052
9327
 
9053
9328
  // ../../node_modules/.pnpm/execa@9.6.1/node_modules/execa/lib/stdio/handle-sync.js
@@ -10131,8 +10406,8 @@ var transformOutputSync = ({ fileDescriptors, syncResult: { output }, options, i
10131
10406
  }
10132
10407
  const state = {};
10133
10408
  const outputFiles = /* @__PURE__ */ new Set([]);
10134
- const transformedOutput = output.map((result, fdNumber) => transformOutputResultSync({
10135
- result,
10409
+ const transformedOutput = output.map((result2, fdNumber) => transformOutputResultSync({
10410
+ result: result2,
10136
10411
  fileDescriptors,
10137
10412
  fdNumber,
10138
10413
  state,
@@ -10142,11 +10417,11 @@ var transformOutputSync = ({ fileDescriptors, syncResult: { output }, options, i
10142
10417
  }, options));
10143
10418
  return { output: transformedOutput, ...state };
10144
10419
  };
10145
- var transformOutputResultSync = ({ result, fileDescriptors, fdNumber, state, outputFiles, isMaxBuffer, verboseInfo }, { buffer, encoding, lines, stripFinalNewline: stripFinalNewline2, maxBuffer }) => {
10146
- if (result === null) {
10420
+ var transformOutputResultSync = ({ result: result2, fileDescriptors, fdNumber, state, outputFiles, isMaxBuffer, verboseInfo }, { buffer, encoding, lines, stripFinalNewline: stripFinalNewline2, maxBuffer }) => {
10421
+ if (result2 === null) {
10147
10422
  return;
10148
10423
  }
10149
- const truncatedResult = truncateMaxBufferSync(result, isMaxBuffer, maxBuffer);
10424
+ const truncatedResult = truncateMaxBufferSync(result2, isMaxBuffer, maxBuffer);
10150
10425
  const uint8ArrayResult = bufferToUint8Array(truncatedResult);
10151
10426
  const { stdioItems, objectMode } = fileDescriptors[fdNumber];
10152
10427
  const chunks = runOutputGeneratorsSync([uint8ArrayResult], stdioItems, encoding, state);
@@ -10307,7 +10582,7 @@ var getResultError = (error, exitCode, signal) => {
10307
10582
  // ../../node_modules/.pnpm/execa@9.6.1/node_modules/execa/lib/methods/main-sync.js
10308
10583
  var execaCoreSync = (rawFile, rawArguments, rawOptions) => {
10309
10584
  const { file, commandArguments, command, escapedCommand, startTime, verboseInfo, options, fileDescriptors } = handleSyncArguments(rawFile, rawArguments, rawOptions);
10310
- const result = spawnSubprocessSync({
10585
+ const result2 = spawnSubprocessSync({
10311
10586
  file,
10312
10587
  commandArguments,
10313
10588
  options,
@@ -10317,7 +10592,7 @@ var execaCoreSync = (rawFile, rawArguments, rawOptions) => {
10317
10592
  fileDescriptors,
10318
10593
  startTime
10319
10594
  });
10320
- return handleResult(result, verboseInfo, options);
10595
+ return handleResult(result2, verboseInfo, options);
10321
10596
  };
10322
10597
  var handleSyncArguments = (rawFile, rawArguments, rawOptions) => {
10323
10598
  const { command, escapedCommand, startTime, verboseInfo } = handleCommand(rawFile, rawArguments, rawOptions);
@@ -12453,7 +12728,7 @@ var handlePromise = async ({ subprocess, options, startTime, verboseInfo, fileDe
12453
12728
  onInternalError.resolve();
12454
12729
  const stdio = stdioResults.map((stdioResult, fdNumber) => stripNewline(stdioResult, options, fdNumber));
12455
12730
  const all = stripNewline(allResult, options, "all");
12456
- const result = getAsyncResult({
12731
+ const result2 = getAsyncResult({
12457
12732
  errorInfo,
12458
12733
  exitCode,
12459
12734
  signal,
@@ -12466,7 +12741,7 @@ var handlePromise = async ({ subprocess, options, startTime, verboseInfo, fileDe
12466
12741
  escapedCommand,
12467
12742
  startTime
12468
12743
  });
12469
- return handleResult(result, verboseInfo, options);
12744
+ return handleResult(result2, verboseInfo, options);
12470
12745
  };
12471
12746
  var getAsyncResult = ({ errorInfo, exitCode, signal, stdio, all, ipcOutput, context, options, command, escapedCommand, startTime }) => "error" in errorInfo ? makeError({
12472
12747
  error: errorInfo.error,
@@ -12664,28 +12939,28 @@ async function executeShellCommand(command, cwd, channel) {
12664
12939
  kind: "command",
12665
12940
  text: `\u6267\u884C\u547D\u4EE4\uFF1A${truncate(command, 120)}`
12666
12941
  });
12667
- const result = await child;
12942
+ const result2 = await child;
12668
12943
  const output = [
12669
- result.all?.trim() || "",
12670
- result.timedOut ? "[Timed out after 60s]" : "",
12671
- `[Exit code: ${result.exitCode ?? "killed"}]`
12944
+ result2.all?.trim() || "",
12945
+ result2.timedOut ? "[Timed out after 60s]" : "",
12946
+ `[Exit code: ${result2.exitCode ?? "killed"}]`
12672
12947
  ].filter(Boolean).join("\n");
12673
12948
  runningProcesses.delete(pid);
12674
12949
  execaProcesses.delete(pid);
12675
12950
  queueDigest(shellSessionId, {
12676
12951
  kind: "command",
12677
- text: `\u547D\u4EE4\u7ED3\u675F\uFF1A\u9000\u51FA\u7801 ${result.exitCode ?? "killed"}`
12952
+ text: `\u547D\u4EE4\u7ED3\u675F\uFF1A\u9000\u51FA\u7801 ${result2.exitCode ?? "killed"}`
12678
12953
  });
12679
12954
  await flushDigest(shellSessionId);
12680
12955
  await handleResultEvent(
12681
12956
  shellSessionId,
12682
12957
  {
12683
12958
  type: "result",
12684
- success: result.exitCode === 0,
12959
+ success: result2.exitCode === 0,
12685
12960
  costUsd: 0,
12686
12961
  durationMs: Date.now() - shellProcess.startedAt,
12687
12962
  numTurns: 1,
12688
- errors: result.exitCode === 0 ? [] : [output],
12963
+ errors: result2.exitCode === 0 ? [] : [output],
12689
12964
  metadata: { sessionEnd: false }
12690
12965
  },
12691
12966
  renderShellOutput(command, output)
@@ -12774,9 +13049,6 @@ async function handleShellKill(interaction) {
12774
13049
  });
12775
13050
  }
12776
13051
 
12777
- // ../bot/src/button-handler.ts
12778
- import "discord.js";
12779
-
12780
13052
  // ../bot/src/button-handler-actions.ts
12781
13053
  import {
12782
13054
  ActionRowBuilder as ActionRowBuilder3,
@@ -12786,12 +13058,11 @@ function asComponentLike(component) {
12786
13058
  return component || {};
12787
13059
  }
12788
13060
  async function resolveAwaitingHumanIfNeeded(sessionId) {
12789
- const session = getSession(sessionId);
13061
+ const session = getSessionView(sessionId);
12790
13062
  if (!session?.currentInteractionMessageId) {
12791
13063
  return;
12792
13064
  }
12793
13065
  updateSession(sessionId, {
12794
- humanResolved: true,
12795
13066
  currentInteractionMessageId: void 0
12796
13067
  });
12797
13068
  await updateSessionState(sessionId, {
@@ -12803,19 +13074,19 @@ async function resolveAwaitingHumanIfNeeded(sessionId) {
12803
13074
  metadata: { source: "answer" }
12804
13075
  });
12805
13076
  }
12806
- function renderCleanupResultMessage(result) {
13077
+ function renderCleanupResultMessage(result2) {
12807
13078
  const lines = [
12808
13079
  "\u6279\u91CF\u6E05\u7406\u5B8C\u6210",
12809
13080
  "",
12810
- `- \u5DF2\u5F52\u6863\uFF1A${result.archivedSessions}`,
12811
- `- \u8DF3\u8FC7\u8FDB\u884C\u4E2D\uFF1A${result.skippedGenerating}`,
12812
- `- \u7F3A\u5931\uFF1A${result.missingSessions}`,
12813
- `- \u5931\u8D25\uFF1A${result.failed.length}`
13081
+ `- \u5DF2\u5F52\u6863\uFF1A${result2.archivedSessions}`,
13082
+ `- \u8DF3\u8FC7\u8FDB\u884C\u4E2D\uFF1A${result2.skippedGenerating}`,
13083
+ `- \u7F3A\u5931\uFF1A${result2.missingSessions}`,
13084
+ `- \u5931\u8D25\uFF1A${result2.failed.length}`
12814
13085
  ];
12815
- if (result.failed.length > 0) {
13086
+ if (result2.failed.length > 0) {
12816
13087
  lines.push("", "\u5931\u8D25\u660E\u7EC6\uFF1A");
12817
13088
  lines.push(
12818
- ...result.failed.map((item) => `- ${item.channelId ? `<#${item.channelId}> ` : ""}${item.sessionId}\uFF1A${item.message}`)
13089
+ ...result2.failed.map((item) => `- ${item.channelId ? `<#${item.channelId}> ` : ""}${item.sessionId}\uFF1A${item.message}`)
12819
13090
  );
12820
13091
  }
12821
13092
  return lines.join("\n");
@@ -12838,7 +13109,7 @@ async function handleAwaitingHumanButton(interaction) {
12838
13109
  const sessionId = parts[1];
12839
13110
  const turn = parseInt(parts[2], 10);
12840
13111
  const action = parts[3];
12841
- const session = getSession(sessionId);
13112
+ const session = getSessionView(sessionId);
12842
13113
  if (!session) {
12843
13114
  await interaction.reply({ content: "\u4F1A\u8BDD\u4E0D\u5B58\u5728", ephemeral: true });
12844
13115
  return true;
@@ -12856,24 +13127,24 @@ async function handleAwaitingHumanButton(interaction) {
12856
13127
  await interaction.reply({ content: "\u5DF2\u88AB\u5176\u4ED6\u4EBA\u5904\u7406", ephemeral: true });
12857
13128
  return true;
12858
13129
  }
12859
- const activeGate = session.activeHumanGateId ? gateCoordinator.getGate(session.activeHumanGateId) : gateCoordinator.getActiveGateForSession(sessionId);
13130
+ const activeGate = session.activeHumanGateId ? gateService.getGate(session.activeHumanGateId) : gateService.getActiveGateForSession(sessionId);
12860
13131
  if (!activeGate) {
12861
13132
  await interaction.reply({ content: "\u672A\u627E\u5230\u6D3B\u8DC3\u7684\u95E8\u63A7\u8BB0\u5F55", ephemeral: true });
12862
13133
  return true;
12863
13134
  }
12864
- const result = await gateCoordinator.resolveFromDiscord(
13135
+ const result2 = await gateService.resolveFromDiscord(
12865
13136
  activeGate.id,
12866
13137
  action === "approve" ? "approve" : "reject"
12867
13138
  );
12868
- if (!result.success) {
13139
+ if (!result2.success) {
12869
13140
  await interaction.reply({
12870
- content: `\u5904\u7406\u5931\u8D25: ${result.message || "\u672A\u77E5\u9519\u8BEF"}`,
13141
+ content: `\u5904\u7406\u5931\u8D25: ${result2.message || "\u672A\u77E5\u9519\u8BEF"}`,
12871
13142
  ephemeral: true
12872
13143
  });
12873
13144
  return true;
12874
13145
  }
13146
+ stateMachine.setHumanResolved(sessionId, true);
12875
13147
  updateSession(sessionId, {
12876
- humanResolved: true,
12877
13148
  currentInteractionMessageId: void 0,
12878
13149
  activeHumanGateId: void 0
12879
13150
  });
@@ -12886,7 +13157,7 @@ async function handleAwaitingHumanButton(interaction) {
12886
13157
  }
12887
13158
  }))
12888
13159
  });
12889
- if (result.handledByReceipt) {
13160
+ if (result2.handledByReceipt) {
12890
13161
  return true;
12891
13162
  }
12892
13163
  if (action === "approve") {
@@ -12929,7 +13200,7 @@ async function handleContinueButton(interaction) {
12929
13200
  const customId = interaction.customId;
12930
13201
  if (!customId.startsWith("continue:")) return false;
12931
13202
  const sessionId = customId.slice(9);
12932
- const session = getSession(sessionId);
13203
+ const session = getSessionView(sessionId);
12933
13204
  if (!session) {
12934
13205
  await interaction.reply({ content: "\u4F1A\u8BDD\u4E0D\u5B58\u5728\u3002", ephemeral: true });
12935
13206
  return true;
@@ -13016,13 +13287,13 @@ async function handleCleanupButtons(interaction) {
13016
13287
  await interaction.deferUpdate();
13017
13288
  try {
13018
13289
  console.log(`[ButtonHandler] Cleanup confirmed by ${interaction.user.tag} \u2014 archiving ${request.candidateSessionIds.length} sessions`);
13019
- const result = await archiveSessionsById(
13290
+ const result2 = await archiveSessionsById(
13020
13291
  interaction.guild,
13021
13292
  request.candidateSessionIds,
13022
13293
  "Bulk cleanup from Discord command"
13023
13294
  );
13024
13295
  deleteCleanupRequest(requestId);
13025
- await interaction.editReply({ content: renderCleanupResultMessage(result), components: [] });
13296
+ await interaction.editReply({ content: renderCleanupResultMessage(result2), components: [] });
13026
13297
  } finally {
13027
13298
  releaseCleanupLock(request.categoryId);
13028
13299
  }
@@ -13036,7 +13307,7 @@ async function handleModeButton(interaction) {
13036
13307
  const parts = customId.split(":");
13037
13308
  const sessionId = parts[1];
13038
13309
  const newMode = parts[2];
13039
- const session = getSession(sessionId);
13310
+ const session = getSessionView(sessionId);
13040
13311
  if (!session) {
13041
13312
  await interaction.reply({ content: "\u4F1A\u8BDD\u4E0D\u5B58\u5728\u3002", ephemeral: true });
13042
13313
  return true;
@@ -13053,7 +13324,7 @@ async function handleModeButton(interaction) {
13053
13324
  await interaction.reply({ content: `\u5DF2\u5207\u6362\u81F3 **${labels[newMode]}**`, ephemeral: true });
13054
13325
  try {
13055
13326
  const original = interaction.message;
13056
- const liveSession = getSession(sessionId);
13327
+ const liveSession = getSessionView(sessionId);
13057
13328
  const updatedComponents = original.components.map((row) => {
13058
13329
  if (!("components" in row)) return row;
13059
13330
  const first = asComponentLike(row.components?.[0]);
@@ -13074,7 +13345,7 @@ async function handleSelectMenuAction(interaction) {
13074
13345
  const sessionId = parts[1];
13075
13346
  const questionIndex = parseInt(parts[2], 10);
13076
13347
  const selected = interaction.values[0];
13077
- const session = getSession(sessionId);
13348
+ const session = getSessionView(sessionId);
13078
13349
  if (!session) {
13079
13350
  await interaction.reply({ content: "\u4F1A\u8BDD\u4E0D\u5B58\u5728\u3002", ephemeral: true });
13080
13351
  return true;
@@ -13113,7 +13384,7 @@ async function handleSelectMenuAction(interaction) {
13113
13384
  const afterPrefix = customId.slice(14);
13114
13385
  const sessionId = afterPrefix.includes(":") ? afterPrefix.split(":")[0] : afterPrefix;
13115
13386
  const selected = interaction.values[0];
13116
- const session = getSession(sessionId);
13387
+ const session = getSessionView(sessionId);
13117
13388
  if (!session) {
13118
13389
  await interaction.reply({ content: "\u4F1A\u8BDD\u4E0D\u5B58\u5728\u3002", ephemeral: true });
13119
13390
  return true;
@@ -13132,7 +13403,7 @@ async function handleSelectMenuAction(interaction) {
13132
13403
  if (customId.startsWith("select:")) {
13133
13404
  const sessionId = customId.slice(7);
13134
13405
  const selected = interaction.values[0];
13135
- const session = getSession(sessionId);
13406
+ const session = getSessionView(sessionId);
13136
13407
  if (!session) {
13137
13408
  await interaction.reply({ content: "\u4F1A\u8BDD\u4E0D\u5B58\u5728\u3002", ephemeral: true });
13138
13409
  return true;
@@ -13151,31 +13422,58 @@ async function handleSelectMenuAction(interaction) {
13151
13422
  return false;
13152
13423
  }
13153
13424
 
13154
- // ../bot/src/button-handler.ts
13155
- async function handleButton(interaction) {
13156
- if (!isUserAllowed(interaction.user.id, config.allowedUsers, config.allowAllUsers)) {
13157
- console.warn(`[ButtonHandler] Unauthorized button press by user ${interaction.user.id}: ${interaction.customId}`);
13158
- await interaction.reply({ content: "\u672A\u6388\u6743\u3002", ephemeral: true });
13425
+ // ../bot/src/button-router.ts
13426
+ var BUTTON_ROUTES = [
13427
+ handleStopButton,
13428
+ handleAwaitingHumanButton,
13429
+ handleContinueButton,
13430
+ handleExpandButton,
13431
+ handleDeprecatedInteractionButton,
13432
+ handleCleanupButtons,
13433
+ handleModeButton
13434
+ ];
13435
+ var SELECT_ROUTES = [handleSelectMenuAction];
13436
+ async function checkAuthorization(userId, reply) {
13437
+ if (isUserAllowed(userId, config.allowedUsers, config.allowAllUsers)) return true;
13438
+ await reply("\u672A\u6388\u6743\u3002");
13439
+ return false;
13440
+ }
13441
+ async function routeButton(interaction) {
13442
+ const ok = await checkAuthorization(
13443
+ interaction.user.id,
13444
+ (content) => interaction.reply({ content, ephemeral: true })
13445
+ );
13446
+ if (!ok) {
13447
+ console.warn(
13448
+ `[ButtonHandler] Unauthorized button press by user ${interaction.user.id}: ${interaction.customId}`
13449
+ );
13159
13450
  return;
13160
13451
  }
13161
- if (await handleStopButton(interaction)) return;
13162
- if (await handleAwaitingHumanButton(interaction)) return;
13163
- if (await handleContinueButton(interaction)) return;
13164
- if (await handleExpandButton(interaction)) return;
13165
- if (await handleDeprecatedInteractionButton(interaction)) return;
13166
- if (await handleCleanupButtons(interaction)) return;
13167
- if (await handleModeButton(interaction)) return;
13452
+ for (const route of BUTTON_ROUTES) {
13453
+ if (await route(interaction)) return;
13454
+ }
13168
13455
  await interaction.reply({ content: "\u672A\u77E5\u6309\u94AE\u3002", ephemeral: true });
13169
13456
  }
13170
- async function handleSelectMenu(interaction) {
13171
- if (!isUserAllowed(interaction.user.id, config.allowedUsers, config.allowAllUsers)) {
13172
- await interaction.reply({ content: "\u672A\u6388\u6743\u3002", ephemeral: true });
13173
- return;
13457
+ async function routeSelectMenu(interaction) {
13458
+ const ok = await checkAuthorization(
13459
+ interaction.user.id,
13460
+ (content) => interaction.reply({ content, ephemeral: true })
13461
+ );
13462
+ if (!ok) return;
13463
+ for (const route of SELECT_ROUTES) {
13464
+ if (await route(interaction)) return;
13174
13465
  }
13175
- if (await handleSelectMenuAction(interaction)) return;
13176
13466
  await interaction.reply({ content: "\u672A\u77E5\u9009\u62E9\u3002", ephemeral: true });
13177
13467
  }
13178
13468
 
13469
+ // ../bot/src/button-handler.ts
13470
+ async function handleButton(interaction) {
13471
+ await routeButton(interaction);
13472
+ }
13473
+ async function handleSelectMenu(interaction) {
13474
+ await routeSelectMenu(interaction);
13475
+ }
13476
+
13179
13477
  // ../bot/src/bot-event-router.ts
13180
13478
  import {
13181
13479
  InteractionType
@@ -13356,7 +13654,7 @@ async function runCli() {
13356
13654
  await startBot();
13357
13655
  });
13358
13656
  cli.command("config [action] [key] [value]", "Manage configuration").action(async (action, key, value) => {
13359
- const { handleConfig } = await import("./config-cli-RQR2ZRQ5.js");
13657
+ const { handleConfig } = await import("./config-cli-7JEV3WYY.js");
13360
13658
  const args = [action || "list"].filter(Boolean);
13361
13659
  if (key) args.push(key);
13362
13660
  if (value) args.push(value);
@@ -13364,12 +13662,12 @@ async function runCli() {
13364
13662
  await handleConfig(args);
13365
13663
  });
13366
13664
  cli.command("project [action] [...args]", "Manage mounted projects").action(async (action, args) => {
13367
- const { handleProject: handleProject2 } = await import("./project-cli-6P6ZWDR6.js");
13665
+ const { handleProject: handleProject2 } = await import("./project-cli-ZXMHOFUJ.js");
13368
13666
  const cmdArgs = [action || "help"].filter(Boolean).concat(args || []);
13369
13667
  await handleProject2(cmdArgs);
13370
13668
  });
13371
13669
  cli.command("attachment [action]", "Manage attachments").action(async (action) => {
13372
- const { handleAttachment } = await import("./attachment-cli-IGQBB6YJ.js");
13670
+ const { handleAttachment } = await import("./attachment-cli-MF7XZ4WT.js");
13373
13671
  await handleAttachment([action || "fetch"]);
13374
13672
  });
13375
13673
  cli.command("daemon <action>", "Manage background service").action(async (action) => {
@@ -13377,7 +13675,7 @@ async function runCli() {
13377
13675
  await handleDaemon(action);
13378
13676
  });
13379
13677
  cli.command("codex [...options]", "Launch managed Codex session with remote approval").allowUnknownOptions().action(async function(options) {
13380
- const { handleCodexCommand } = await import("./codex-launcher-VDQ5VZPT.js");
13678
+ const { handleCodexCommand } = await import("./codex-launcher-ZBQ5VL6L.js");
13381
13679
  const parsed = this.options;
13382
13680
  const rawArgs = [];
13383
13681
  for (const [key, value] of Object.entries(parsed)) {