opencode-matrixx 2.1.0 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (163) hide show
  1. package/README.md +129 -18
  2. package/dist/agents/builtin-agents/model-resolution.d.ts +1 -1
  3. package/dist/agents/index.d.ts +1 -1
  4. package/dist/agents/merovingian.d.ts +2 -2
  5. package/dist/config/index.d.ts +2 -2
  6. package/dist/config/schema/agent-names.d.ts +3 -17
  7. package/dist/config/schema/assembly.d.ts +13 -0
  8. package/dist/config/schema/categories.d.ts +0 -1
  9. package/dist/config/schema/commands.d.ts +4 -0
  10. package/dist/config/schema/hooks.d.ts +0 -1
  11. package/dist/config/schema/matrixx-config.d.ts +16 -1
  12. package/dist/config/schema/security.d.ts +0 -5
  13. package/dist/config/schema.d.ts +1 -0
  14. package/dist/features/assembly-state/index.d.ts +1 -0
  15. package/dist/features/assembly-state/manager.d.ts +6 -0
  16. package/dist/features/background-agent/error-helpers.d.ts +6 -0
  17. package/dist/features/background-agent/manager.d.ts +6 -8
  18. package/dist/features/background-agent/message-dir.d.ts +5 -0
  19. package/dist/features/background-agent/notification-builder.d.ts +17 -0
  20. package/dist/features/builtin-commands/templates/assembly.d.ts +1 -0
  21. package/dist/features/builtin-commands/templates/research.d.ts +1 -0
  22. package/dist/features/builtin-commands/templates/ultrawork.d.ts +1 -0
  23. package/dist/features/builtin-commands/types.d.ts +1 -1
  24. package/dist/features/builtin-skills/skills/index.d.ts +2 -0
  25. package/dist/features/builtin-skills/skills/remove-ai-slops.d.ts +2 -0
  26. package/dist/features/builtin-skills/skills/ulw-research.d.ts +2 -0
  27. package/dist/features/claude-code-command-loader/loader.d.ts +0 -1
  28. package/dist/features/hook-message-injector/injector.d.ts +1 -2
  29. package/dist/features/tmux-subagent/index.d.ts +0 -2
  30. package/dist/features/ultrawork-state/index.d.ts +1 -0
  31. package/dist/features/ultrawork-state/manager.d.ts +6 -0
  32. package/dist/hooks/auto-update-checker/cache.d.ts +0 -2
  33. package/dist/hooks/auto-update-checker/index.d.ts +1 -1
  34. package/dist/hooks/interactive-bash-session/parser.d.ts +0 -6
  35. package/dist/index.js +1824 -539
  36. package/dist/shared/index.d.ts +1 -0
  37. package/dist/shared/model-resolution-pipeline.d.ts +1 -24
  38. package/dist/shared/model-resolution-types.d.ts +1 -0
  39. package/dist/shared/model-resolver.d.ts +4 -2
  40. package/dist/shared/opencode-config-dir.d.ts +1 -2
  41. package/dist/shared/session-directory-resolver.d.ts +0 -6
  42. package/dist/shared/tool-guards.d.ts +16 -0
  43. package/dist/tools/assembly/assembly-executor.d.ts +17 -0
  44. package/dist/tools/assembly/constants.d.ts +6 -0
  45. package/dist/tools/assembly/index.d.ts +2 -0
  46. package/dist/tools/assembly/provider-selector.d.ts +3 -0
  47. package/dist/tools/assembly/synthesizer.d.ts +3 -0
  48. package/dist/tools/assembly/tool-description.d.ts +2 -0
  49. package/dist/tools/assembly/tools.d.ts +10 -0
  50. package/dist/tools/assembly/types.d.ts +25 -0
  51. package/dist/tools/assembly/voter-spawner.d.ts +3 -0
  52. package/dist/tools/handoff/constants.d.ts +0 -3
  53. package/dist/tools/index.d.ts +1 -0
  54. package/package.json +4 -29
  55. package/bin/matrixx.js +0 -80
  56. package/bin/platform.js +0 -38
  57. package/bin/platform.test.ts +0 -148
  58. package/dist/cli/cli-installer.d.ts +0 -2
  59. package/dist/cli/cli-program.d.ts +0 -1
  60. package/dist/cli/config-manager/add-plugin-to-opencode-config.d.ts +0 -2
  61. package/dist/cli/config-manager/add-provider-config.d.ts +0 -2
  62. package/dist/cli/config-manager/antigravity-provider-configuration.d.ts +0 -122
  63. package/dist/cli/config-manager/auth-plugins.d.ts +0 -3
  64. package/dist/cli/config-manager/bun-install.d.ts +0 -7
  65. package/dist/cli/config-manager/config-context.d.ts +0 -13
  66. package/dist/cli/config-manager/deep-merge-record.d.ts +0 -1
  67. package/dist/cli/config-manager/detect-current-config.d.ts +0 -2
  68. package/dist/cli/config-manager/ensure-config-directory-exists.d.ts +0 -1
  69. package/dist/cli/config-manager/format-error-with-suggestion.d.ts +0 -1
  70. package/dist/cli/config-manager/generate-matrixx-config.d.ts +0 -2
  71. package/dist/cli/config-manager/jsonc-provider-editor.d.ts +0 -1
  72. package/dist/cli/config-manager/npm-dist-tags.d.ts +0 -7
  73. package/dist/cli/config-manager/opencode-binary.d.ts +0 -2
  74. package/dist/cli/config-manager/opencode-config-format.d.ts +0 -6
  75. package/dist/cli/config-manager/parse-opencode-config-file.d.ts +0 -10
  76. package/dist/cli/config-manager/plugin-name-with-version.d.ts +0 -1
  77. package/dist/cli/config-manager/write-matrixx-config.d.ts +0 -2
  78. package/dist/cli/config-manager.d.ts +0 -14
  79. package/dist/cli/doctor/checks/config.d.ts +0 -2
  80. package/dist/cli/doctor/checks/dependencies.d.ts +0 -4
  81. package/dist/cli/doctor/checks/index.d.ts +0 -7
  82. package/dist/cli/doctor/checks/model-resolution-cache.d.ts +0 -2
  83. package/dist/cli/doctor/checks/model-resolution-config.d.ts +0 -2
  84. package/dist/cli/doctor/checks/model-resolution-details.d.ts +0 -6
  85. package/dist/cli/doctor/checks/model-resolution-effective-model.d.ts +0 -3
  86. package/dist/cli/doctor/checks/model-resolution-types.d.ts +0 -37
  87. package/dist/cli/doctor/checks/model-resolution-variant.d.ts +0 -5
  88. package/dist/cli/doctor/checks/model-resolution.d.ts +0 -6
  89. package/dist/cli/doctor/checks/system-binary.d.ts +0 -8
  90. package/dist/cli/doctor/checks/system-loaded-version.d.ts +0 -10
  91. package/dist/cli/doctor/checks/system-plugin.d.ts +0 -15
  92. package/dist/cli/doctor/checks/system.d.ts +0 -3
  93. package/dist/cli/doctor/checks/tools-gh.d.ts +0 -11
  94. package/dist/cli/doctor/checks/tools-lsp.d.ts +0 -6
  95. package/dist/cli/doctor/checks/tools-mcp.d.ts +0 -3
  96. package/dist/cli/doctor/checks/tools.d.ts +0 -3
  97. package/dist/cli/doctor/constants.d.ts +0 -31
  98. package/dist/cli/doctor/format-default.d.ts +0 -2
  99. package/dist/cli/doctor/format-shared.d.ts +0 -6
  100. package/dist/cli/doctor/format-status.d.ts +0 -2
  101. package/dist/cli/doctor/format-verbose.d.ts +0 -2
  102. package/dist/cli/doctor/formatter.d.ts +0 -3
  103. package/dist/cli/doctor/index.d.ts +0 -5
  104. package/dist/cli/doctor/runner.d.ts +0 -5
  105. package/dist/cli/doctor/types.d.ts +0 -96
  106. package/dist/cli/fallback-chain-resolution.d.ts +0 -10
  107. package/dist/cli/get-local-version/formatter.d.ts +0 -3
  108. package/dist/cli/get-local-version/get-local-version.d.ts +0 -2
  109. package/dist/cli/get-local-version/index.d.ts +0 -2
  110. package/dist/cli/get-local-version/types.d.ts +0 -13
  111. package/dist/cli/index.d.ts +0 -2
  112. package/dist/cli/index.js +0 -30691
  113. package/dist/cli/install-validators.d.ts +0 -31
  114. package/dist/cli/install.d.ts +0 -2
  115. package/dist/cli/mcp-oauth/index.d.ts +0 -6
  116. package/dist/cli/mcp-oauth/login.d.ts +0 -7
  117. package/dist/cli/mcp-oauth/logout.d.ts +0 -5
  118. package/dist/cli/mcp-oauth/status.d.ts +0 -1
  119. package/dist/cli/model-fallback-types.d.ts +0 -25
  120. package/dist/cli/model-fallback.d.ts +0 -4
  121. package/dist/cli/provider-availability.d.ts +0 -4
  122. package/dist/cli/provider-model-id-transform.d.ts +0 -1
  123. package/dist/cli/run/agent-resolver.d.ts +0 -5
  124. package/dist/cli/run/completion.d.ts +0 -2
  125. package/dist/cli/run/event-formatting.d.ts +0 -3
  126. package/dist/cli/run/event-handlers.d.ts +0 -10
  127. package/dist/cli/run/event-state.d.ts +0 -13
  128. package/dist/cli/run/event-stream-processor.d.ts +0 -3
  129. package/dist/cli/run/events.d.ts +0 -4
  130. package/dist/cli/run/index.d.ts +0 -9
  131. package/dist/cli/run/json-output.d.ts +0 -12
  132. package/dist/cli/run/on-complete-hook.d.ts +0 -7
  133. package/dist/cli/run/opencode-bin-path.d.ts +0 -3
  134. package/dist/cli/run/opencode-binary-resolver.d.ts +0 -4
  135. package/dist/cli/run/poll-for-completion.d.ts +0 -9
  136. package/dist/cli/run/runner.d.ts +0 -5
  137. package/dist/cli/run/server-connection.d.ts +0 -6
  138. package/dist/cli/run/session-resolver.d.ts +0 -6
  139. package/dist/cli/run/types.d.ts +0 -119
  140. package/dist/cli/tui-install-prompts.d.ts +0 -2
  141. package/dist/cli/tui-installer.d.ts +0 -2
  142. package/dist/cli/types.d.ts +0 -36
  143. package/dist/features/background-agent/background-task-notification-template.d.ts +0 -11
  144. package/dist/features/background-agent/opencode-client.d.ts +0 -2
  145. package/dist/features/background-agent/task-poller.d.ts +0 -21
  146. package/dist/features/claude-tasks/index.d.ts +0 -3
  147. package/dist/features/claude-tasks/session-storage.d.ts +0 -10
  148. package/dist/features/mcp-oauth/index.d.ts +0 -3
  149. package/dist/features/mcp-oauth/schema.d.ts +0 -5
  150. package/dist/features/run-continuation-state/constants.d.ts +0 -1
  151. package/dist/features/run-continuation-state/index.d.ts +0 -3
  152. package/dist/features/run-continuation-state/storage.d.ts +0 -6
  153. package/dist/features/run-continuation-state/types.d.ts +0 -13
  154. package/dist/features/tmux-subagent/cleanup.d.ts +0 -1
  155. package/dist/features/tmux-subagent/polling.d.ts +0 -1
  156. package/dist/hooks/session-recovery/recover-empty-content-message-sdk.d.ts +0 -13
  157. package/dist/hooks/task-reminder/hook.d.ts +0 -19
  158. package/dist/hooks/task-reminder/index.d.ts +0 -1
  159. package/dist/tools/delegate-agent/background-agent-executor.d.ts +0 -5
  160. package/dist/tools/delegate-agent/message-storage-directory.d.ts +0 -1
  161. package/dist/tools/delegate-agent/subagent-session-creator.d.ts +0 -10
  162. package/dist/tools/delegate-agent/tool-context-with-metadata.d.ts +0 -10
  163. package/postinstall.mjs +0 -43
package/dist/index.js CHANGED
@@ -4531,7 +4531,7 @@ var require_picomatch2 = __commonJS((exports, module) => {
4531
4531
  });
4532
4532
 
4533
4533
  // src/hooks/matrix-loop/constants.ts
4534
- var HOOK_NAME4 = "matrix-loop", DEFAULT_STATE_FILE = ".matrixx/matrix-loop.local.md", DEFAULT_MAX_ITERATIONS = 100, DEFAULT_COMPLETION_PROMISE = "DONE", DEFAULT_VERIFICATION_AGENT = "merovingian", DEFAULT_VERIFICATION_TIMEOUT_MS = 30000, DEFAULT_VERIFICATION_MAX_RETRIES = 2;
4534
+ var HOOK_NAME5 = "matrix-loop", DEFAULT_STATE_FILE = ".matrixx/matrix-loop.local.md", DEFAULT_MAX_ITERATIONS = 100, DEFAULT_COMPLETION_PROMISE = "DONE", DEFAULT_VERIFICATION_AGENT = "merovingian", DEFAULT_VERIFICATION_TIMEOUT_MS = 30000, DEFAULT_VERIFICATION_MAX_RETRIES = 2;
4535
4535
 
4536
4536
  // src/hooks/matrix-loop/storage.ts
4537
4537
  var exports_storage = {};
@@ -18968,6 +18968,7 @@ function getCurrentPaneId() {
18968
18968
  var {spawn: spawn4 } = globalThis.Bun;
18969
18969
 
18970
18970
  // src/tools/interactive-bash/tmux-path-resolver.ts
18971
+ init_logger();
18971
18972
  var {spawn: spawn3 } = globalThis.Bun;
18972
18973
  var tmuxPath = null;
18973
18974
  var initPromise = null;
@@ -19022,7 +19023,9 @@ function getCachedTmuxPath() {
19022
19023
  function startBackgroundCheck() {
19023
19024
  if (!initPromise) {
19024
19025
  initPromise = getTmuxPath();
19025
- initPromise.catch(() => {});
19026
+ initPromise.catch((err) => {
19027
+ log("[interactive-bash] Background tmux path resolution failed:", err);
19028
+ });
19026
19029
  }
19027
19030
  }
19028
19031
 
@@ -19252,6 +19255,13 @@ async function spawnTmuxPane(sessionId, description, config, serverUrl, targetPa
19252
19255
  }
19253
19256
  return { success: true, paneId };
19254
19257
  }
19258
+ // src/shared/tool-guards.ts
19259
+ function isReadTool(toolName) {
19260
+ return toolName.toLowerCase() === "read";
19261
+ }
19262
+ function isWriteTool(toolName) {
19263
+ return toolName.toLowerCase() === "write";
19264
+ }
19255
19265
  // src/shared/transcript-path.ts
19256
19266
  import { join as join15 } from "path";
19257
19267
  var TRANSCRIPT_DIR = join15(getClaudeConfigDir(), "transcripts");
@@ -19406,6 +19416,9 @@ function createAgentUsageReminderHook(_ctx) {
19406
19416
  event: eventHandler
19407
19417
  };
19408
19418
  }
19419
+ // src/hooks/anthropic-context-window-limit-recovery/executor.ts
19420
+ init_logger();
19421
+
19409
19422
  // src/hooks/anthropic-context-window-limit-recovery/aggressive-truncation-strategy.ts
19410
19423
  init_logger();
19411
19424
 
@@ -20270,7 +20283,9 @@ async function runAggressiveTruncationStrategy(params) {
20270
20283
  variant: aggressiveResult.sufficient ? "success" : "warning",
20271
20284
  duration: 4000
20272
20285
  }
20273
- }).catch(() => {});
20286
+ }).catch((err) => {
20287
+ log("[auto-compact] Toast failed:", err);
20288
+ });
20274
20289
  log("[auto-compact] aggressive truncation completed", aggressiveResult);
20275
20290
  if (aggressiveResult.sufficient) {
20276
20291
  clearSessionState(params.autoCompactState, params.sessionID);
@@ -20284,7 +20299,9 @@ async function runAggressiveTruncationStrategy(params) {
20284
20299
  },
20285
20300
  query: { directory: params.directory }
20286
20301
  });
20287
- } catch {}
20302
+ } catch (err) {
20303
+ log("[auto-compact] Failed to send continue prompt:", err);
20304
+ }
20288
20305
  }, 500);
20289
20306
  return { handled: true, nextTruncateAttempt };
20290
20307
  }
@@ -20295,6 +20312,12 @@ async function runAggressiveTruncationStrategy(params) {
20295
20312
  });
20296
20313
  return { handled: false, nextTruncateAttempt };
20297
20314
  }
20315
+ // src/hooks/anthropic-context-window-limit-recovery/summarize-retry-strategy.ts
20316
+ init_logger();
20317
+
20318
+ // src/hooks/anthropic-context-window-limit-recovery/empty-content-recovery.ts
20319
+ init_logger();
20320
+
20298
20321
  // src/hooks/anthropic-context-window-limit-recovery/empty-content-recovery-sdk.ts
20299
20322
  var IGNORE_TYPES2 = new Set(["thinking", "redacted_thinking", "meta"]);
20300
20323
  var TOOL_TYPES2 = new Set(["tool", "tool_use", "tool_result"]);
@@ -20441,7 +20464,9 @@ async function fixEmptyMessages(params) {
20441
20464
  variant: "error",
20442
20465
  duration: 5000
20443
20466
  }
20444
- }).catch(() => {});
20467
+ }).catch((err) => {
20468
+ log("[auto-compact] Toast failed:", err);
20469
+ });
20445
20470
  return false;
20446
20471
  }
20447
20472
  if (result.fixed) {
@@ -20452,7 +20477,9 @@ async function fixEmptyMessages(params) {
20452
20477
  variant: "warning",
20453
20478
  duration: 3000
20454
20479
  }
20455
- }).catch(() => {});
20480
+ }).catch((err) => {
20481
+ log("[auto-compact] Toast failed:", err);
20482
+ });
20456
20483
  }
20457
20484
  return result.fixed;
20458
20485
  }
@@ -20482,7 +20509,9 @@ async function fixEmptyMessages(params) {
20482
20509
  variant: "error",
20483
20510
  duration: 5000
20484
20511
  }
20485
- }).catch(() => {});
20512
+ }).catch((err) => {
20513
+ log("[auto-compact] Toast failed:", err);
20514
+ });
20486
20515
  return false;
20487
20516
  }
20488
20517
  for (const messageID of emptyMessageIds) {
@@ -20507,7 +20536,9 @@ async function fixEmptyMessages(params) {
20507
20536
  variant: "warning",
20508
20537
  duration: 3000
20509
20538
  }
20510
- }).catch(() => {});
20539
+ }).catch((err) => {
20540
+ log("[auto-compact] Toast failed:", err);
20541
+ });
20511
20542
  }
20512
20543
  return fixed;
20513
20544
  }
@@ -20538,7 +20569,9 @@ async function runSummarizeRetryStrategy(params) {
20538
20569
  variant: "error",
20539
20570
  duration: 1e4
20540
20571
  }
20541
- }).catch(() => {});
20572
+ }).catch((err) => {
20573
+ log("[auto-compact] Toast failed:", err);
20574
+ });
20542
20575
  return;
20543
20576
  }
20544
20577
  }
@@ -20561,7 +20594,9 @@ async function runSummarizeRetryStrategy(params) {
20561
20594
  variant: "warning",
20562
20595
  duration: 3000
20563
20596
  }
20564
- }).catch(() => {});
20597
+ }).catch((err) => {
20598
+ log("[auto-compact] Toast failed:", err);
20599
+ });
20565
20600
  const summarizeBody = { providerID, modelID, auto: true };
20566
20601
  await params.client.session.summarize({
20567
20602
  path: { id: params.sessionID },
@@ -20585,7 +20620,9 @@ async function runSummarizeRetryStrategy(params) {
20585
20620
  variant: "warning",
20586
20621
  duration: 3000
20587
20622
  }
20588
- }).catch(() => {});
20623
+ }).catch((err) => {
20624
+ log("[auto-compact] Toast failed:", err);
20625
+ });
20589
20626
  }
20590
20627
  }
20591
20628
  clearSessionState(params.autoCompactState, params.sessionID);
@@ -20596,7 +20633,9 @@ async function runSummarizeRetryStrategy(params) {
20596
20633
  variant: "error",
20597
20634
  duration: 5000
20598
20635
  }
20599
- }).catch(() => {});
20636
+ }).catch((err) => {
20637
+ log("[auto-compact] Toast failed:", err);
20638
+ });
20600
20639
  }
20601
20640
  // src/hooks/anthropic-context-window-limit-recovery/executor.ts
20602
20641
  async function executeCompact(sessionID, msg, autoCompactState, client, directory, experimental) {
@@ -20608,7 +20647,9 @@ async function executeCompact(sessionID, msg, autoCompactState, client, director
20608
20647
  variant: "warning",
20609
20648
  duration: 5000
20610
20649
  }
20611
- }).catch(() => {});
20650
+ }).catch((err) => {
20651
+ log("[auto-compact] Toast failed:", err);
20652
+ });
20612
20653
  return;
20613
20654
  }
20614
20655
  autoCompactState.compactionInProgress.add(sessionID);
@@ -20884,7 +20925,9 @@ function createAnthropicContextWindowLimitRecoveryHook(ctx, options) {
20884
20925
  variant: "warning",
20885
20926
  duration: 3000
20886
20927
  }
20887
- }).catch(() => {});
20928
+ }).catch((err) => {
20929
+ log("[auto-compact] Toast failed:", err);
20930
+ });
20888
20931
  const timeoutID = setTimeout(() => {
20889
20932
  pendingCompactionTimeoutBySession.delete(sessionID);
20890
20933
  executeCompact(sessionID, { providerID, modelID }, autoCompactState, ctx.client, ctx.directory, experimental);
@@ -20935,7 +20978,9 @@ function createAnthropicContextWindowLimitRecoveryHook(ctx, options) {
20935
20978
  variant: "warning",
20936
20979
  duration: 3000
20937
20980
  }
20938
- }).catch(() => {});
20981
+ }).catch((err) => {
20982
+ log("[auto-compact] Toast failed:", err);
20983
+ });
20939
20984
  await executeCompact(sessionID, { providerID, modelID }, autoCompactState, ctx.client, ctx.directory, experimental);
20940
20985
  }
20941
20986
  };
@@ -21884,6 +21929,22 @@ function findSlashCommandPartIndex(parts) {
21884
21929
  import { existsSync as existsSync22, readdirSync as readdirSync10, readFileSync as readFileSync17 } from "fs";
21885
21930
  import { basename as basename3, dirname as dirname4, join as join31 } from "path";
21886
21931
 
21932
+ // src/features/builtin-commands/templates/assembly.ts
21933
+ var ASSEMBLY_TEMPLATE = `Toggle assembly tool availability for the current session.
21934
+
21935
+ This command will:
21936
+ 1. Enable the assembly tool \u2014 makes multi-model voting available to the LLM
21937
+ 2. Disable the assembly tool \u2014 hides it from the LLM for this session
21938
+ 3. Show current assembly tool status
21939
+
21940
+ Usage:
21941
+ /assembly enable \u2014 Show assembly tool
21942
+ /assembly disable \u2014 Hide assembly tool
21943
+ /assembly status \u2014 Show current state
21944
+
21945
+ The change is per-session and clears when the session ends.
21946
+ By default, the assembly tool is enabled and available in every session.`;
21947
+
21887
21948
  // src/features/builtin-commands/templates/end-ultrawork.ts
21888
21949
  var END_ULTRAWORK_TEMPLATE = `Deactivate ultrawork mode for the current session.
21889
21950
 
@@ -23452,6 +23513,51 @@ $ARGUMENTS
23452
23513
  </user-request>
23453
23514
  `;
23454
23515
 
23516
+ // src/features/builtin-commands/templates/research.ts
23517
+ var RESEARCH_TEMPLATE = `# /research \u2014 Saturation Research Command
23518
+
23519
+ Execute a systematic multi-round research process on the given topic.
23520
+
23521
+ ## Usage
23522
+
23523
+ \`\`\`
23524
+ /research <topic> \u2192 Full research (all directions)
23525
+ /research <topic> --scope=code \u2192 Local codebase only
23526
+ /research <topic> --scope=docs \u2192 Documentation only
23527
+ /research <topic> --scope=web \u2192 Web search only
23528
+ /research <topic> --scope=oss \u2192 OSS repos only
23529
+ /research <topic> --max-rounds=3 \u2192 Limit to 3 rounds
23530
+ \`\`\`
23531
+
23532
+ ## What Happens
23533
+
23534
+ 1. Creates artifact directory: \`.matrixx/research-<topic>-<timestamp>/\`
23535
+ 2. Spawns 4 parallel research agents (explore + operator swarms)
23536
+ 3. Collects findings, identifies new leads
23537
+ 4. Recursively follows leads until convergence (novelty-based stop, max 5 rounds)
23538
+ 5. Verifies contested claims by running code
23539
+ 6. Produces cited synthesis in \`final-synthesis.md\`
23540
+
23541
+ ## Output
23542
+
23543
+ All artifacts written to \`.matrixx/research-<topic>-<timestamp>/\`:
23544
+ - \`round-N-findings.md\` \u2014 per-round findings
23545
+ - \`leads.md\` \u2014 tracked leads
23546
+ - \`contested-claims.md\` \u2014 claims needing verification
23547
+ - \`final-synthesis.md\` \u2014 the final cited synthesis
23548
+
23549
+ ## Examples
23550
+
23551
+ \`\`\`
23552
+ /research how does context window management work in this codebase
23553
+ /research React 19 server component patterns --scope=web --max-rounds=3
23554
+ /research authentication middleware best practices --scope=all
23555
+ \`\`\`
23556
+
23557
+ ---
23558
+
23559
+ Execute the full research protocol using the ulw-research skill.`;
23560
+
23455
23561
  // src/features/builtin-commands/templates/start-work.ts
23456
23562
  var START_WORK_TEMPLATE = `You are starting a Morpheus work session.
23457
23563
 
@@ -23541,6 +23647,22 @@ After running this command:
23541
23647
 
23542
23648
  Use this when you need to pause automated continuation and take manual control.`;
23543
23649
 
23650
+ // src/features/builtin-commands/templates/ultrawork.ts
23651
+ var ULTRAWORK_TEMPLATE = `Toggle ultrawork mode for the current session.
23652
+
23653
+ This command will:
23654
+ 1. Enable ultrawork mode \u2014 injects ultrawork mode instructions into every message
23655
+ 2. Disable ultrawork mode \u2014 prevents ultrawork mode from activating (even on "ultrawork" keyword)
23656
+ 3. Show current ultrawork mode status
23657
+
23658
+ Usage:
23659
+ /ultrawork enable \u2014 Force ultrawork mode on all messages
23660
+ /ultrawork disable \u2014 Block ultrawork mode (keyword ignored)
23661
+ /ultrawork status \u2014 Show current state
23662
+
23663
+ The change is per-session and clears when the session ends.
23664
+ By default, ultrawork mode activates when the LLM or user mentions the "ultrawork" keyword.`;
23665
+
23544
23666
  // src/features/builtin-commands/commands.ts
23545
23667
  var BUILTIN_COMMAND_DEFINITIONS = {
23546
23668
  "init-deep": {
@@ -23667,6 +23789,39 @@ ${END_ULTRAWORK_TEMPLATE}
23667
23789
  $ARGUMENTS
23668
23790
  </user-request>`,
23669
23791
  argumentHint: "[optional follow-up task]"
23792
+ },
23793
+ research: {
23794
+ description: "(builtin) Saturation research with parallel swarms and convergence \u2014 code, docs, web, and OSS repos",
23795
+ template: `<command-instruction>
23796
+ ${RESEARCH_TEMPLATE}
23797
+ </command-instruction>
23798
+
23799
+ <user-request>
23800
+ $ARGUMENTS
23801
+ </user-request>`,
23802
+ argumentHint: "<topic> [--scope=code|docs|web|oss|all] [--max-rounds=N]"
23803
+ },
23804
+ assembly: {
23805
+ description: "(builtin) Toggle assembly tool enable/disable for the current session",
23806
+ template: `<command-instruction>
23807
+ ${ASSEMBLY_TEMPLATE}
23808
+ </command-instruction>
23809
+
23810
+ <user-request>
23811
+ $ARGUMENTS
23812
+ </user-request>`,
23813
+ argumentHint: "[enable|disable|status]"
23814
+ },
23815
+ ultrawork: {
23816
+ description: "(builtin) Toggle ultrawork mode on/off for the current session",
23817
+ template: `<command-instruction>
23818
+ ${ULTRAWORK_TEMPLATE}
23819
+ </command-instruction>
23820
+
23821
+ <user-request>
23822
+ $ARGUMENTS
23823
+ </user-request>`,
23824
+ argumentHint: "[enable|disable|status]"
23670
23825
  }
23671
23826
  };
23672
23827
  function loadBuiltinCommands(disabledCommands) {
@@ -30266,17 +30421,306 @@ Fix these inline when possible. For bulk fixes, run \`bun run lint:fix\`.
30266
30421
  | Report done without running verification | Never trust \u2014 always verify |
30267
30422
  `
30268
30423
  };
30424
+ // src/features/builtin-skills/skills/remove-ai-slops.ts
30425
+ var REMOVE_AI_SLOPS_SKILL_NAME = "remove-ai-slops";
30426
+ var REMOVE_AI_SLOPS_SKILL_DESCRIPTION = "Detects and removes AI-generated code smells across 7 categories: verbose comments, redundant error handling, over-engineered patterns, generic AI phrasing, cargo-cult boilerplate, padding/verbosity, weird codegen artifacts. Hybrid mode: scan first, then guide fixes. Triggers: 'cleanup code', 'remove ai slop', 'code cleanup', 'fix ai code', 'de-ai', 'remove ai comments', 'clean ai smells'.";
30427
+ var removeAiSlopsSkill = {
30428
+ name: REMOVE_AI_SLOPS_SKILL_NAME,
30429
+ description: REMOVE_AI_SLOPS_SKILL_DESCRIPTION,
30430
+ allowedTools: [
30431
+ "read",
30432
+ "write",
30433
+ "edit",
30434
+ "grep",
30435
+ "glob",
30436
+ "ast_grep_search",
30437
+ "ast_grep_replace",
30438
+ "lsp_diagnostics",
30439
+ "lsp_find_references"
30440
+ ],
30441
+ template: `# Remove AI Slops \u2014 Code Smell Detector & Fixer
30442
+
30443
+ Detect and fix AI-generated code smells across 7 categories. Operates in **hybrid mode**: scan first to identify issues, then guide you through fixes one finding at a time.
30444
+
30445
+ ## Phase 0: Scope & Setup
30446
+
30447
+ Determine scope:
30448
+ - If no files specified, scan recently changed files (\`git diff --name-only HEAD~1\`)
30449
+ - If a file/dir is specified, scan those
30450
+ - If \`--all\` flag, scan entire \`src/\` directory
30451
+
30452
+ Create \`.matrixx/slop-scan-<timestamp>.md\` for tracking findings.
30453
+
30454
+ ## Phase 1: Scan for Slop
30455
+
30456
+ Run all 7 category detectors. For each category, output findings grouped by file with line ranges.
30457
+
30458
+ ---
30459
+
30460
+ ### Category 1: Verbose Comments
30461
+
30462
+ **Detect**: AST patterns and regex for comments that restate the obvious.
30463
+
30464
+ **Patterns to flag:**
30465
+ - \`// This function does X by doing Y\` \u2014 describes implementation, not intent
30466
+ - \`// Loop through each item\` \u2014 obvious from the \`for\` loop itself
30467
+ - \`// Initialize variable\` \u2014 obvious from \`const x =\`
30468
+ - \`// Return result\` \u2014 obvious from \`return\`
30469
+ - \`// Call the API\` \u2014 obvious from the function call on the next line
30470
+ - \`// Check if condition is true\` \u2014 obvious from \`if\`
30471
+ - Block comments that just repeat what the function signature says
30472
+
30473
+ **Detection:**
30474
+ - Use \`ast_grep_search\` with patterns like:
30475
+ - \`// $COMMENT\` where $COMMENT matches obvious patterns
30476
+ - Function-level JSDoc that adds no info beyond the signature
30477
+ - Use \`grep\` with regex for known AI comment phrases
30478
+
30479
+ **Severity**: LOW (cosmetic, but clutters code)
30480
+
30481
+ ---
30482
+
30483
+ ### Category 2: Redundant Error Handling
30484
+
30485
+ **Detect**: Error handling patterns that add no value.
30486
+
30487
+ **Patterns to flag:**
30488
+ - Empty catch blocks: \`catch (e) {}\`
30489
+ - Catch-and-rethrow: \`catch(e) { console.error(e); throw e; }\` (re-throw with no added value)
30490
+ - Redundant null checks on already-guarded paths
30491
+ - \`try/catch\` that wraps every single function call individually
30492
+ - \`catch (error: any)\` (TypeScript \u2014 \`unknown\` preferred)
30493
+
30494
+ **Detection:**
30495
+ - Use \`ast_grep_search\` for \`catch($$$) { $$$ }\` and analyze body
30496
+ - \`ast_grep_search\` for \`if ($VAR != null)\` patterns on already-checked variables
30497
+ - Use \`lsp_find_references\` to check if null checks are redundant
30498
+
30499
+ **Severity**: MEDIUM (adds noise, but doesn't break things)
30500
+
30501
+ ---
30502
+
30503
+ ### Category 3: Over-Engineered Patterns
30504
+
30505
+ **Detect**: Unnecessary abstraction layers and overly complex patterns.
30506
+
30507
+ **Patterns to flag:**
30508
+ - Abstract factory factory or other nested indirection
30509
+ - AbstractSingletonProxyBean-style pattern names
30510
+ - Single-implementation interfaces (unless part of a public API contract)
30511
+ - Builder pattern for objects with 1-3 simple properties
30512
+ - Strategy pattern with a single strategy
30513
+ - Decorator patterns around every function
30514
+ - Excessive generic constraints (\`<T extends X extends Y extends Z>\`)
30515
+
30516
+ **Detection:**
30517
+ - Use \`lsp_find_references\` to check if interfaces have exactly 1 implementation
30518
+ - \`ast_grep_search\` for class names containing "Abstract", "Factory", "Builder", "Manager"
30519
+ - \`grep\` for pattern names like \`*Abstract*\`, \`*Factory*\`, \`*Proxy*\`, \`*Singleton*\`
30520
+ - Check for builder functions/classes with very few properties
30521
+
30522
+ **Severity**: HIGH (indicates misunderstanding of the codebase)
30523
+
30524
+ ---
30525
+
30526
+ ### Category 4: Generic AI Phrasing
30527
+
30528
+ **Detect**: Code comments and documentation with AI-typical phrasing.
30529
+
30530
+ **Phrases to flag (regex case-insensitive):**
30531
+ - "It's worth noting that"
30532
+ - "It is important to note"
30533
+ - "It is important to mention"
30534
+ - "Let's step through this"
30535
+ - "In conclusion"
30536
+ - "As previously mentioned"
30537
+ - "It should be noted that"
30538
+ - "It's crucial to understand"
30539
+ - "Let's break this down"
30540
+ - "The beauty of this approach"
30541
+ - "This is a classic example of"
30542
+ - "Essentially, what this does is"
30543
+ - "In the world of"
30544
+ - "When it comes to"
30545
+ - "One might argue that"
30546
+ - "It goes without saying"
30547
+
30548
+ **Detection:**
30549
+ - Use \`grep -i\` with regex patterns for each phrase in \`*.ts\` and \`*.md\` files
30550
+ - Focus comments, docstrings, and inline documentation
30551
+
30552
+ **Severity**: LOW (cosmetic, but unprofessional)
30553
+
30554
+ ---
30555
+
30556
+ ### Category 5: Cargo-Cult Boilerplate
30557
+
30558
+ **Detect**: Patterns copied without understanding.
30559
+
30560
+ **Patterns to flag:**
30561
+ - Excessive defensive programming (checking the same condition at multiple levels)
30562
+ - \`try/catch\` with \`console.error\` and rethrow in every function
30563
+ - Identical error handling block copy-pasted across multiple functions
30564
+ - Redundant type guards on already-typed parameters
30565
+ - \`if (process.env.NODE_ENV !== 'production')\` debug logging patterns
30566
+ - \`// TODO: implement\` stubs in production code
30567
+ - Mock implementations that replicate the real implementation exactly
30568
+
30569
+ **Detection:**
30570
+ - Use \`grep\` to find repeated identical code blocks across files
30571
+ - \`ast_grep_search\` for try/catch patterns in every function of a file
30572
+ - \`grep\` for TODO/FIXME/HACK comments in non-test files
30573
+ - Check for \`console.log\` statements in production code
30574
+
30575
+ **Severity**: MEDIUM (code bloat, maintenance burden)
30576
+
30577
+ ---
30578
+
30579
+ ### Category 6: Padding/Verbosity
30580
+
30581
+ **Detect**: Unnecessary code that adds no information.
30582
+
30583
+ **Patterns to flag:**
30584
+ - Empty \`@returns\` tags in JSDoc
30585
+ - \`@param {string} name - The name\` \u2014 repeats the obvious
30586
+ - Redundant type annotations: \`const x: string = "hello"\` (type is obvious)
30587
+ - Unnecessary intermediate variables: \`const result = doSomething(); return result;\` when \`return doSomething()\` works
30588
+ - \`const x = y as SomeType\` when y is already SomeType
30589
+ - \`() => { return x }\` when \`() => x\` works
30590
+ - Destructuring with unused variables
30591
+ - \`export default\` for a single export (prefer named)
30592
+ - Variables assigned but never read
30593
+
30594
+ **Detection:**
30595
+ - Use \`ast_grep_search\` for \`const $VAR = $EXPR as $TYPE\` where $VAR is typed
30596
+ - \`ast_grep_search\` for \`() => { return { $$$ } }\`
30597
+ - \`grep\` for \`@returns\` without description in JSDoc
30598
+ - \`lsp_diagnostics\` will catch unused variables
30599
+
30600
+ **Severity**: LOW (cosmetic, minor bloat)
30601
+
30602
+ ---
30603
+
30604
+ ### Category 7: Weird Codegen Artifacts
30605
+
30606
+ **Detect**: Inconsistencies that suggest AI-generated code was inserted without review.
30607
+
30608
+ **Patterns to flag:**
30609
+ - Mix of import styles in the same file (CommonJS + ESM, default + named)
30610
+ - Inconsistent error handling within the same file (some async/await, some .then())
30611
+ - Mixed indentation or naming conventions in the same file
30612
+ - Unused imports (LSP will catch these)
30613
+ - \`import * as\` that imports everything but only uses one thing
30614
+ - Code that doesn't follow the file's established patterns
30615
+ - Types/interfaces defined inline instead of in a shared types file
30616
+ - Magic numbers and strings that should be constants
30617
+ - Language-mismatched conventions (e.g., Java-style getters/setters in TypeScript)
30618
+
30619
+ **Detection:**
30620
+ - Use \`grep\` for mixed module systems in the same file
30621
+ - \`lsp_diagnostics\` for unused imports
30622
+ - \`ast_grep_search\` for inline type definitions that should be shared
30623
+ - Manual review of file-level pattern consistency
30624
+
30625
+ **Severity**: MEDIUM-HIGH (indicates un-reviewed code)
30626
+
30627
+ ---
30628
+
30629
+ ## Phase 2: Report Findings
30630
+
30631
+ After scanning all categories, present a structured report:
30632
+
30633
+ \`\`\`markdown
30634
+ # AI Slop Scan Results
30635
+
30636
+ Scanned: <files/dirs>
30637
+ Total findings: <count>
30638
+
30639
+ | Category | Count | Critical | High | Medium | Low |
30640
+ |----------|-------|----------|------|--------|-----|
30641
+ | 1. Verbose Comments | N | - | - | - | N |
30642
+ | 2. Redundant Error Handling | N | - | N | N | - |
30643
+ | ... | ... | ... | ... | ... | ... |
30644
+
30645
+ ## Findings by File
30646
+
30647
+ ### <file-path.ts>
30648
+
30649
+ #### [HIGH] Category 3: Over-Engineered \u2014 AbstractUserFactoryManager
30650
+ - **Location**: line 42-89
30651
+ - **Current**: \`class AbstractUserFactoryManager<T extends ...>\`
30652
+ - **Suggestion**: Replace with simple function \`createUser()\`
30653
+ - **Severity**: HIGH
30654
+ - **Status**: PENDING
30655
+
30656
+ #### [MED] Category 2: Redundant Error Handling
30657
+ - **Location**: line 15-18
30658
+ - **Current**:
30659
+ \`\`\`typescript
30660
+ try {
30661
+ await doSomething()
30662
+ } catch (e) {
30663
+ console.error(e)
30664
+ throw e
30665
+ }
30666
+ \`\`\`
30667
+ - **Suggestion**: Remove try/catch \u2014 caller handles errors. Or add meaningful error context.
30668
+ - **Severity**: MEDIUM
30669
+ - **Status**: PENDING
30670
+
30671
+ ... (repeat for all findings)
30672
+ \`\`\`
30673
+
30674
+ ## Phase 3: Guided Fix
30675
+
30676
+ For each finding, present the user with options:
30677
+
30678
+ 1. **Apply fix** \u2014 Show the proposed change as a diff, ask for confirmation
30679
+ 2. **Skip** \u2014 Mark as reviewed and acceptable
30680
+ 3. **Snooze** \u2014 Skip for now but flag for later review
30681
+ 4. **Adjust** \u2014 User provides alternative fix
30682
+
30683
+ Apply fixes using the \`edit\` tool only after user confirmation.
30684
+
30685
+ **Fix application pattern:**
30686
+ 1. Present the proposed replacement code
30687
+ 2. Wait for user response (\`y\`/\`n\`/\`skip\`/\`adjust\`)
30688
+ 3. On approval: \`edit\` the file
30689
+ 4. Run \`lsp_diagnostics\` on the file to verify no new errors
30690
+ 5. Mark finding as FIXED / SKIPPED
30691
+
30692
+ **Batch support**: User can say "apply all LOW" or "apply all for file X" to batch-approve.
30693
+
30694
+ ## Phase 4: Verify
30695
+
30696
+ After all fixes applied:
30697
+ 1. \`bun run typecheck\` \u2014 no new type errors
30698
+ 2. \`bun run lint\` \u2014 no new lint errors
30699
+ 3. Verify no changes broke tests for affected files
30700
+
30701
+ Write final summary to \`.matrixx/slop-scan-<timestamp>.md\`.
30702
+
30703
+ ## Rules
30704
+
30705
+ - NEVER auto-apply fixes without user approval
30706
+ - ALWAYS show the proposed change before applying
30707
+ - ALWAYS run \`lsp_diagnostics\` after each edit
30708
+ - Scan ALL 7 categories every time \u2014 don't skip categories
30709
+ - Flag findings only once (deduplicate across rounds)
30710
+ - For \`--all\` mode, process files in batches of 5 to avoid overload
30711
+ - If a pattern is clearly intentional (pre-existing convention), skip it`
30712
+ };
30269
30713
  // src/features/builtin-skills/skills/review-work.ts
30270
30714
  var REVIEW_WORK_SKILL_NAME = "review-work";
30271
- var REVIEW_WORK_SKILL_DESCRIPTION = "Post-implementation review orchestrator. Launches 5 parallel background sub-agents: Oracle (goal/constraint verification), Oracle (code quality), Oracle (security), unspecified-high (hands-on QA execution), unspecified-high (context mining from GitHub/git/Slack/Notion). All must pass for review to pass. MUST USE after completing any significant implementation work. Triggers: 'review work', 'review my work', 'review changes', 'QA my work', 'verify implementation', 'check my work', 'validate changes', 'post-implementation review'.";
30715
+ var REVIEW_WORK_SKILL_DESCRIPTION = "Post-implementation review orchestrator. Launches 6 parallel background sub-agents: Oracle (goal/constraint verification), Oracle (code quality), Oracle (security), unspecified-high (hands-on QA execution), unspecified-high (context mining from GitHub/git/Slack/Notion), unspecified-high (AI slop detection). All must pass for review to pass. MUST USE after completing any significant implementation work. Triggers: 'review work', 'review my work', 'review changes', 'QA my work', 'verify implementation', 'check my work', 'validate changes', 'post-implementation review'.";
30272
30716
  var reviewWorkSkill = {
30273
30717
  name: REVIEW_WORK_SKILL_NAME,
30274
30718
  description: REVIEW_WORK_SKILL_DESCRIPTION,
30275
30719
  template: `# Review Work - 5-Agent Parallel Review Orchestrator
30276
30720
 
30277
- Launch 5 specialized sub-agents in parallel to review completed implementation work from every angle. All 5 must pass for the review to pass. If even ONE fails, the review fails.
30721
+ Launch 6 specialized sub-agents in parallel to review completed implementation work from every angle. All 6 must pass for the review to pass. If even ONE fails, the review fails.
30278
30722
 
30279
- The 5 agents cover complementary concerns - together they form a comprehensive review that no single reviewer could match:
30723
+ The 6 agents cover complementary concerns - together they form a comprehensive review that no single reviewer could match:
30280
30724
 
30281
30725
  | # | Agent | Type | Role | Focus Level |
30282
30726
  |---|-------|------|------|-------------|
@@ -30285,6 +30729,7 @@ The 5 agents cover complementary concerns - together they form a comprehensive r
30285
30729
  | 3 | Code Reviewer | Oracle | Is the code well-written? | MAIN |
30286
30730
  | 4 | Security Auditor | Oracle | Is it secure? | SUB |
30287
30731
  | 5 | Context Miner | unspecified-high | Did we miss any context? | MAIN |
30732
+ | 6 | AI Slop Detector | unspecified-high | Remove AI-generated code smells | SUP |
30288
30733
 
30289
30734
  ---
30290
30735
 
@@ -30618,9 +31063,54 @@ OUTPUT FORMAT:
30618
31063
 
30619
31064
  ---
30620
31065
 
31066
+ ### Agent 6: AI Slop Detection (unspecified-high) - SUP
31067
+
31068
+ \`\`\`
31069
+ task(
31070
+ category="unspecified-high",
31071
+ run_in_background=true,
31072
+ load_skills=["remove-ai-slops"],
31073
+ description="Detect AI-generated code smells in changes",
31074
+ prompt="""
31075
+ <review_type>AI SLOP DETECTION (supplementary)</review_type>
31076
+
31077
+ <original_goal>
31078
+ {GOAL}
31079
+ </original_goal>
31080
+
31081
+ <changed_files>
31082
+ {CHANGED_FILES}
31083
+ </changed_files>
31084
+
31085
+ <background>
31086
+ {BACKGROUND}
31087
+ </background>
31088
+
31089
+ Review the changed files for AI-generated code smells. Load and follow the \`remove-ai-slops\` skill.
31090
+
31091
+ Run the scan across all 7 categories:
31092
+ 1. Verbose Comments \u2014 comments that restate the obvious
31093
+ 2. Redundant Error Handling \u2014 catch-and-rethrow, empty catches, redundant null checks
31094
+ 3. Over-Engineered Patterns \u2014 unnecessary abstractions, single-use interfaces
31095
+ 4. Generic AI Phrasing \u2014 "It's worth noting...", "Let's step through..."
31096
+ 5. Cargo-Cult Boilerplate \u2014 repeated identical blocks, TODO stubs
31097
+ 6. Padding/Verbosity \u2014 empty JSDoc, redundant type annotations
31098
+ 7. Weird Codegen Artifacts \u2014 mixed import styles, inconsistent patterns
31099
+
31100
+ OUTPUT FORMAT:
31101
+ <verdict>PASS or FAIL (FAIL only if HIGH severity issues found)</verdict>
31102
+ <severity>NONE / LOW / MEDIUM / HIGH</severity>
31103
+ <summary>1-3 sentence overall assessment</summary>
31104
+ <findings>- [CATEGORY_N] [HIGH/MEDIUM/LOW] Description - File:path:line - Current / Suggestion</findings>
31105
+ <recommendations>Suggested fixes for findings. Empty if PASS.</recommendations>
31106
+ """)
31107
+ \`\`\`
31108
+
31109
+ ---
31110
+
30621
31111
  ## Phase 2: Wait & Collect
30622
31112
 
30623
- After launching all 5 agents in one turn, **end your response**. Wait for system notifications as each agent completes.
31113
+ After launching all 6 agents in one turn, **end your response**. Wait for system notifications as each agent completes.
30624
31114
 
30625
31115
  Collect via \`background_output(task_id="...")\` as each completes. Track verdicts:
30626
31116
 
@@ -30631,14 +31121,15 @@ Collect via \`background_output(task_id="...")\` as each completes. Track verdic
30631
31121
  | 3. Code Quality | pending | - |
30632
31122
  | 4. Security | pending | - |
30633
31123
  | 5. Context Mining | pending | - |
31124
+ | 6. AI Slop Detection | pending | - |
30634
31125
 
30635
- Do NOT deliver the final report until ALL 5 have completed.
31126
+ Do NOT deliver the final report until ALL 6 have completed.
30636
31127
 
30637
31128
  ---
30638
31129
 
30639
31130
  ## Phase 3: Deliver Verdict
30640
31131
 
30641
- ALL 5 returned PASS \u2192 **REVIEW PASSED**
31132
+ ALL 6 returned PASS \u2192 **REVIEW PASSED**
30642
31133
  ANY returned FAIL \u2192 **REVIEW FAILED**
30643
31134
 
30644
31135
  Final report format:
@@ -30655,6 +31146,7 @@ Final report format:
30655
31146
  | 3 | Code Quality | Oracle | PASS/FAIL | HIGH/MED/LOW |
30656
31147
  | 4 | Security (supplementary) | Oracle | PASS/FAIL | Severity |
30657
31148
  | 5 | Context Mining | unspecified-high | PASS/FAIL | HIGH/MED/LOW |
31149
+ | 6 | AI Slop Detection (supplementary) | unspecified-high | PASS/FAIL | NONE/LOW/MED/HIGH |
30658
31150
 
30659
31151
  ## Blocking Issues
30660
31152
  [Aggregated from all agents - deduplicated, prioritized]
@@ -31842,6 +32334,252 @@ NEVER write tests AFTER implementation \u2014 write them BEFORE
31842
32334
  \`\`\`
31843
32335
  `
31844
32336
  };
32337
+ // src/features/builtin-skills/skills/ulw-research.ts
32338
+ var ULW_RESEARCH_SKILL_NAME = "ulw-research";
32339
+ var ULW_RESEARCH_SKILL_DESCRIPTION = "Saturation research orchestrator. Spawns parallel explore/librarian swarms across code, docs, web, and OSS repos. Recursively follows EXPAND leads until convergence (novelty-based, max 5 rounds). Proves contested claims by running code. Returns cited synthesis. Writes artifacts to .matrixx/. Triggers: 'research', 'deep dive', 'investigate', 'explore codebase', 'find how', 'saturation research', 'ulw research'.";
32340
+ var ulwResearchSkill = {
32341
+ name: ULW_RESEARCH_SKILL_NAME,
32342
+ description: ULW_RESEARCH_SKILL_DESCRIPTION,
32343
+ agent: "oracle",
32344
+ allowedTools: [
32345
+ "read",
32346
+ "write",
32347
+ "edit",
32348
+ "bash",
32349
+ "grep",
32350
+ "glob",
32351
+ "look_at",
32352
+ "delegate_agent",
32353
+ "websearch_web_search_exa",
32354
+ "grep_app_searchGitHub",
32355
+ "context7_resolve-library-id",
32356
+ "context7_query-docs"
32357
+ ],
32358
+ template: `# ULW-Research \u2014 Saturation Research Orchestrator
32359
+
32360
+ Run a systematic, multi-round research process that converges on comprehensive coverage.
32361
+
32362
+ ## Inputs
32363
+
32364
+ - **TOPIC**: The research question or topic (from user message)
32365
+ - **SCOPE**: Optional scope narrowing (--scope=code|docs|web|oss|all, default: all)
32366
+ - **MAX_ROUNDS**: Optional (--max-rounds=N, default: 5, hard limit: 5)
32367
+
32368
+ ## Artifact Directory
32369
+
32370
+ Create \`.matrixx/research-<slug>-<timestamp>/\` for all outputs:
32371
+ - \`meta.md\` \u2014 Research metadata (topic, scope, start time, max rounds)
32372
+ - \`round-<N>-findings.md\` \u2014 Per-round combined findings
32373
+ - \`leads.md\` \u2014 Tracked EXPAND leads across rounds (status: pending/explored/exhausted)
32374
+ - \`contested-claims.md\` \u2014 Claims that need code verification
32375
+ - \`convergence-log.md\` \u2014 Novelty scores and decisions per round
32376
+ - \`final-synthesis.md\` \u2014 Cited synthesis (final deliverable)
32377
+
32378
+ All artifacts are written via the \`write\` tool. The next session can resume from artifacts if interrupted.
32379
+
32380
+ ## Phase 0: Parse and Seed
32381
+
32382
+ 1. Create artifact directory: \`.matrixx/research-<topic-slug>-<YYYYMMDD-HHmmss>/\`
32383
+ 2. Write \`meta.md\` with topic, timestamp, scope, max rounds
32384
+ 3. Initialize \`leads.md\` with seed queries extracted from the topic:
32385
+ - 1-3 code-local search terms
32386
+ - 1-3 documentation/Context7 search terms
32387
+ - 1-3 web search terms
32388
+ - 1-3 OSS repository search terms
32389
+ 4. Write round 0 to \`convergence-log.md\`
32390
+
32391
+ ## Phase 1: Parallel Swarm (per round)
32392
+
32393
+ For each round, spawn 4 parallel background agents. Each agent gets ONLY its direction-specific query (not the full research state).
32394
+
32395
+ ### Direction 1: Local Code (explore agent)
32396
+
32397
+ \`\`\`
32398
+ task(
32399
+ subagent_type="explore",
32400
+ run_in_background=true,
32401
+ description="Research code: <lead>",
32402
+ prompt="""
32403
+ Search the local codebase for implementations, patterns, and references related to: <CURRENT_LEAD>
32404
+
32405
+ Use grep, glob, and read tools. Find:
32406
+ - Relevant source files and their key functions
32407
+ - Architectural patterns used
32408
+ - Configuration and usage examples
32409
+ - Related tests that reveal intended behavior
32410
+
32411
+ OUTPUT: List of findings with file paths, line numbers, and brief descriptions.
32412
+ Flag any claims that seem contested or unverifiable by reading alone.
32413
+ """)
32414
+ \`\`\`
32415
+
32416
+ ### Direction 2: Documentation (explore agent)
32417
+
32418
+ \`\`\`
32419
+ task(
32420
+ subagent_type="explore",
32421
+ run_in_background=true,
32422
+ description="Research docs: <lead>",
32423
+ prompt="""
32424
+ Research documentation and knowledge base files related to: <CURRENT_LEAD>
32425
+
32426
+ Read AGENTS.md files, README.md, doc/ directories, and inline documentation.
32427
+ Use context7_resolve-library-id and context7_query-docs for library/framework docs.
32428
+
32429
+ OUTPUT: Structured findings from docs with source attribution.
32430
+ Flag any claims that contradict other sources.
32431
+ """)
32432
+ \`\`\`
32433
+
32434
+ ### Direction 3: Web (operator agent)
32435
+
32436
+ \`\`\`
32437
+ task(
32438
+ subagent_type="operator",
32439
+ run_in_background=true,
32440
+ description="Research web: <lead>",
32441
+ prompt="""
32442
+ Research the web for information about: <CURRENT_LEAD>
32443
+
32444
+ Use websearch_web_search_exa and context7 tools. Find:
32445
+ - Official documentation and blog posts
32446
+ - Technical articles and tutorials
32447
+ - Implementation patterns and best practices
32448
+ - Real-world usage examples
32449
+
32450
+ OUTPUT: Cited findings with URLs, publication dates, and reliability assessment.
32451
+ Flag any contested claims or conflicting information.
32452
+ """)
32453
+ \`\`\`
32454
+
32455
+ ### Direction 4: OSS Repos (operator agent)
32456
+
32457
+ \`\`\`
32458
+ task(
32459
+ subagent_type="operator",
32460
+ run_in_background=true,
32461
+ description="Research OSS: <lead>",
32462
+ prompt="""
32463
+ Search open source repositories for implementations related to: <CURRENT_LEAD>
32464
+
32465
+ Use grep_app_searchGitHub to find real-world code patterns.
32466
+ Focus on:
32467
+ - Production implementations in similar projects
32468
+ - Reference implementations or canonical examples
32469
+ - Common patterns and anti-patterns
32470
+ - Version-specific changes or migrations
32471
+
32472
+ OUTPUT: Code patterns with repo URLs, file paths, and usage context.
32473
+ Flag any patterns that seem contested or deprecated.
32474
+ """)
32475
+ \`\`\`
32476
+
32477
+ ### Collect Results
32478
+
32479
+ After all 4 agents complete, collect via \`background_output(task_id="...")\`:
32480
+ 1. Merge findings into \`round-<N>-findings.md\`
32481
+ 2. Extract new EXPAND leads \u2192 append to \`leads.md\`
32482
+ 3. Extract contested claims \u2192 append to \`contested-claims.md\`
32483
+
32484
+ ## Phase 2: Convergence Check (after each round)
32485
+
32486
+ Evaluate novelty by comparing this round's findings against ALL previous rounds:
32487
+
32488
+ 1. **Total items this round**: Count of unique file paths, URLs, code patterns, concepts, library names
32489
+ 2. **Novel items**: Items not present in ANY prior round
32490
+ 3. **Novelty ratio**: novel_items / total_items_this_round
32491
+
32492
+ **Decision:**
32493
+ - \`novelty_ratio > 0.3\` AND \`round < MAX_ROUNDS\` \u2192 **EXPAND**: Pick top 3 new leads, go to Phase 1
32494
+ - \`novelty_ratio <= 0.3\` \u2192 **STOP**: Natural convergence, proceed to Phase 4
32495
+ - \`round == MAX_ROUNDS\` \u2192 **STOP**: Forced convergence
32496
+ - \`total_items_this_round == 0\` \u2192 **STOP**: No findings
32497
+
32498
+ Record convergence state in \`convergence-log.md\` with round number and novelty score.
32499
+
32500
+ **Lead selection for next round**: Pick top 3 leads prioritizing:
32501
+ - Specificity (concrete file paths or URLs > vague concepts)
32502
+ - Freshness (leads from current round > old leads)
32503
+ - Diversity (one per direction preferred)
32504
+
32505
+ ## Phase 3: Contested Claim Verification (if needed)
32506
+
32507
+ If \`contested-claims.md\` is non-empty after convergence, verify each claim:
32508
+
32509
+ \`\`\`
32510
+ task(
32511
+ subagent_type="explore",
32512
+ run_in_background=true,
32513
+ description="Verify claim: <summary>",
32514
+ prompt="""
32515
+ Verify this contested claim by examining code:
32516
+
32517
+ CLAIM: <claim_text>
32518
+ EVIDENCE FOR: <supporting_evidence>
32519
+ EVIDENCE AGAINST: <opposing_evidence>
32520
+
32521
+ Verification steps:
32522
+ 1. Find the relevant source files
32523
+ 2. Read the implementation
32524
+ 3. If runnable: write a minimal test script and execute it
32525
+ 4. Determine verdict
32526
+
32527
+ OUTPUT:
32528
+ <verdict>CONFIRMED / REFUTED / INCONCLUSIVE</verdict>
32529
+ <evidence>Specific code or outputs supporting the verdict</evidence>
32530
+ """)
32531
+ \`\`\`
32532
+
32533
+ Max 10 contested claims verified. If none, skip Phase 3.
32534
+
32535
+ ## Phase 4: Final Synthesis
32536
+
32537
+ Produce \`final-synthesis.md\`:
32538
+
32539
+ \`\`\`markdown
32540
+ # Research Synthesis: <TOPIC>
32541
+
32542
+ **Date**: <timestamp>
32543
+ **Rounds**: <N> (converged at round <M>)
32544
+ **Sources**: <count> unique sources
32545
+
32546
+ ## Executive Summary
32547
+ 2-3 paragraphs synthesizing the most important findings.
32548
+
32549
+ ## Key Findings
32550
+
32551
+ ### Finding 1: <title>
32552
+ - **Status**: CONFIRMED / CONTESTED / INCONCLUSIVE
32553
+ - **Confidence**: HIGH / MEDIUM / LOW
32554
+ - **Summary**: 2-3 sentence synthesis
32555
+ - **Evidence**: Details with citations
32556
+ - **Sources**: [1] path, [2] URL, [3] repo
32557
+
32558
+ (Repeat for all significant findings)
32559
+
32560
+ ## Contested Claims Resolution
32561
+ | Claim | Verdict | Evidence |
32562
+ |-------|---------|----------|
32563
+
32564
+ ## Research Gaps
32565
+ - Areas not fully covered
32566
+ - Questions remaining unanswered
32567
+
32568
+ ## Source Index
32569
+ | # | Type | Source | Key Insight |
32570
+ |---|------|--------|-------------|
32571
+ \`\`\`
32572
+
32573
+ ## Rules
32574
+
32575
+ - NEVER fabricate citations \u2014 every claim must reference a real source found during research
32576
+ - ALWAYS write intermediate artifacts \u2014 preserves partial results if interrupted
32577
+ - MAX 5 rounds regardless of novelty
32578
+ - Each background agent gets a FOCUSED prompt \u2014 never dump the entire research state
32579
+ - Contested claims MUST be verified by code execution when possible
32580
+ - The final synthesis must be self-contained without intermediate artifacts
32581
+ - If a round produces zero findings, STOP immediately`
32582
+ };
31845
32583
  // src/features/builtin-skills/skills.ts
31846
32584
  function createBuiltinSkills(options = {}) {
31847
32585
  const { browserProvider = "playwright", disabledSkills: rawDisabledSkills } = options;
@@ -31854,7 +32592,7 @@ function createBuiltinSkills(options = {}) {
31854
32592
  } else {
31855
32593
  browserSkill = playwrightSkill;
31856
32594
  }
31857
- const skills = [browserSkill, frontendUiUxSkill, gitMasterSkill, devBrowserSkill, dslCoreSkill, dslGrammarSkill, dslCodegenSkill, dslMetamodelSkill, dslToolingSkill, dslTextxEcosystemSkill, dslPyecoreAdvancedSkill, dslModelTransformationSkill, dslTestingSkill, dslValidationSkill, dslCompositionSkill, frontendA11ySkill, frontendBuildToolingSkill, frontendPerfSkill, frontendStateDataSkill, frontendTestingSkill, reactNextjsPatternsSkill, svelteSveltekitPatternsSkill, documentReaderSkill, securityCoreSkill, securitySecretsSkill, securitySastSkill, securityDastSkill, securityDependenciesSkill, securityApiSkill, securityCryptoSkill, securityInfraSkill, securityReviewSkill, tddEnforcerSkill, reviewWorkSkill, qualityGateSkill, softwareDevSkill, matrixxSelfConfigSkill];
32595
+ const skills = [browserSkill, frontendUiUxSkill, gitMasterSkill, devBrowserSkill, dslCoreSkill, dslGrammarSkill, dslCodegenSkill, dslMetamodelSkill, dslToolingSkill, dslTextxEcosystemSkill, dslPyecoreAdvancedSkill, dslModelTransformationSkill, dslTestingSkill, dslValidationSkill, dslCompositionSkill, frontendA11ySkill, frontendBuildToolingSkill, frontendPerfSkill, frontendStateDataSkill, frontendTestingSkill, reactNextjsPatternsSkill, svelteSveltekitPatternsSkill, documentReaderSkill, securityCoreSkill, securitySecretsSkill, securitySastSkill, securityDastSkill, securityDependenciesSkill, securityApiSkill, securityCryptoSkill, securityInfraSkill, securityReviewSkill, tddEnforcerSkill, reviewWorkSkill, qualityGateSkill, softwareDevSkill, matrixxSelfConfigSkill, ulwResearchSkill, removeAiSlopsSkill];
31858
32596
  if (!disabledSkills) {
31859
32597
  return skills;
31860
32598
  }
@@ -32555,67 +33293,6 @@ function revertPinnedVersion(configPath, failedVersion, originalEntry) {
32555
33293
  // src/hooks/auto-update-checker/hook.ts
32556
33294
  init_logger();
32557
33295
 
32558
- // src/cli/config-manager/config-context.ts
32559
- var configContext = null;
32560
- function getConfigContext() {
32561
- if (!configContext) {
32562
- if (true) {
32563
- console.warn("[config-context] getConfigContext() called before initConfigContext(); defaulting to CLI paths.");
32564
- }
32565
- const paths = getOpenCodeConfigPaths({ binary: "opencode", version: null });
32566
- configContext = { binary: "opencode", version: null, paths };
32567
- }
32568
- return configContext;
32569
- }
32570
- function getConfigDir() {
32571
- return getConfigContext().paths.configDir;
32572
- }
32573
- // src/cli/config-manager/bun-install.ts
32574
- var BUN_INSTALL_TIMEOUT_SECONDS = 60;
32575
- var BUN_INSTALL_TIMEOUT_MS = BUN_INSTALL_TIMEOUT_SECONDS * 1000;
32576
- async function runBunInstall() {
32577
- const result = await runBunInstallWithDetails();
32578
- return result.success;
32579
- }
32580
- async function runBunInstallWithDetails() {
32581
- try {
32582
- const proc = Bun.spawn(["bun", "install"], {
32583
- cwd: getConfigDir(),
32584
- stdout: "inherit",
32585
- stderr: "inherit"
32586
- });
32587
- let timeoutId;
32588
- const timeoutPromise = new Promise((resolve3) => {
32589
- timeoutId = setTimeout(() => resolve3("timeout"), BUN_INSTALL_TIMEOUT_MS);
32590
- });
32591
- const exitPromise = proc.exited.then(() => "completed");
32592
- const result = await Promise.race([exitPromise, timeoutPromise]);
32593
- clearTimeout(timeoutId);
32594
- if (result === "timeout") {
32595
- try {
32596
- proc.kill();
32597
- } catch {}
32598
- return {
32599
- success: false,
32600
- timedOut: true,
32601
- error: `bun install timed out after ${BUN_INSTALL_TIMEOUT_SECONDS} seconds. Try running manually: cd ${getConfigDir()} && bun i`
32602
- };
32603
- }
32604
- if (proc.exitCode !== 0) {
32605
- return {
32606
- success: false,
32607
- error: `bun install failed with exit code ${proc.exitCode}`
32608
- };
32609
- }
32610
- return { success: true };
32611
- } catch (err) {
32612
- const message = err instanceof Error ? err.message : String(err);
32613
- return {
32614
- success: false,
32615
- error: `bun install failed: ${message}. Is bun installed? Try: curl -fsSL https://bun.sh/install | bash`
32616
- };
32617
- }
32618
- }
32619
33296
  // src/hooks/auto-update-checker/hook/background-update-check.ts
32620
33297
  init_logger();
32621
33298
 
@@ -32629,7 +33306,9 @@ async function showUpdateAvailableToast(ctx, latestVersion, getToastMessage) {
32629
33306
  variant: "info",
32630
33307
  duration: 8000
32631
33308
  }
32632
- }).catch(() => {});
33309
+ }).catch((err) => {
33310
+ log("[auto-update] Update available toast failed:", err);
33311
+ });
32633
33312
  log(`[auto-update-checker] Update available toast shown: v${latestVersion}`);
32634
33313
  }
32635
33314
  async function showAutoUpdatedToast(ctx, oldVersion, newVersion) {
@@ -32641,14 +33320,36 @@ Restart OpenCode to apply.`,
32641
33320
  variant: "success",
32642
33321
  duration: 8000
32643
33322
  }
32644
- }).catch(() => {});
33323
+ }).catch((err) => {
33324
+ log("[auto-update] Auto-updated toast failed:", err);
33325
+ });
32645
33326
  log(`[auto-update-checker] Auto-updated toast shown: v${oldVersion} \u2192 v${newVersion}`);
32646
33327
  }
32647
33328
 
32648
33329
  // src/hooks/auto-update-checker/hook/background-update-check.ts
33330
+ var BUN_INSTALL_TIMEOUT_SECONDS = 60;
33331
+ var BUN_INSTALL_TIMEOUT_MS = BUN_INSTALL_TIMEOUT_SECONDS * 1000;
32649
33332
  async function runBunInstallSafe() {
32650
33333
  try {
32651
- return await runBunInstall();
33334
+ const configPaths = getOpenCodeConfigPaths({ binary: "opencode", version: null });
33335
+ const proc = Bun.spawn(["bun", "install"], {
33336
+ cwd: configPaths.configDir,
33337
+ stdout: "inherit",
33338
+ stderr: "inherit"
33339
+ });
33340
+ let timeoutId;
33341
+ const timeoutPromise = new Promise((resolve3) => {
33342
+ timeoutId = setTimeout(() => resolve3(false), BUN_INSTALL_TIMEOUT_MS);
33343
+ });
33344
+ const exitPromise = proc.exited.then(() => true);
33345
+ const result = await Promise.race([exitPromise, timeoutPromise]);
33346
+ clearTimeout(timeoutId);
33347
+ if (!result) {
33348
+ try {
33349
+ proc.kill();
33350
+ } catch {}
33351
+ }
33352
+ return result;
32652
33353
  } catch (err) {
32653
33354
  const errorMessage = err instanceof Error ? err.message : String(err);
32654
33355
  log("[auto-update-checker] bun install error:", errorMessage);
@@ -32719,7 +33420,9 @@ ${errorMessages}`,
32719
33420
  variant: "error",
32720
33421
  duration: 1e4
32721
33422
  }
32722
- }).catch(() => {});
33423
+ }).catch((err) => {
33424
+ log("[auto-update] Config errors toast failed:", err);
33425
+ });
32723
33426
  log(`[auto-update-checker] Config load errors shown: ${errors.length} error(s)`);
32724
33427
  clearConfigLoadErrors();
32725
33428
  }
@@ -32728,7 +33431,9 @@ ${errorMessages}`,
32728
33431
  init_logger();
32729
33432
  async function updateAndShowConnectedProvidersCacheStatus(ctx) {
32730
33433
  const hadCache = hasConnectedProvidersCache();
32731
- updateConnectedProvidersCache(ctx.client).catch(() => {});
33434
+ updateConnectedProvidersCache(ctx.client).catch((err) => {
33435
+ log("[auto-update] Provider cache update failed:", err);
33436
+ });
32732
33437
  if (!hadCache) {
32733
33438
  await ctx.client.tui.showToast({
32734
33439
  body: {
@@ -32737,7 +33442,9 @@ async function updateAndShowConnectedProvidersCacheStatus(ctx) {
32737
33442
  variant: "info",
32738
33443
  duration: 8000
32739
33444
  }
32740
- }).catch(() => {});
33445
+ }).catch((err) => {
33446
+ log("[auto-update] Providers toast failed:", err);
33447
+ });
32741
33448
  log("[auto-update-checker] Connected providers cache toast shown (first run)");
32742
33449
  } else {
32743
33450
  log("[auto-update-checker] Connected providers cache exists, updating in background");
@@ -32756,7 +33463,9 @@ async function showModelCacheWarningIfNeeded(ctx) {
32756
33463
  variant: "warning",
32757
33464
  duration: 1e4
32758
33465
  }
32759
- }).catch(() => {});
33466
+ }).catch((err) => {
33467
+ log("[auto-update] Model cache warning toast failed:", err);
33468
+ });
32760
33469
  log("[auto-update-checker] Model cache warning shown");
32761
33470
  }
32762
33471
 
@@ -32764,6 +33473,7 @@ async function showModelCacheWarningIfNeeded(ctx) {
32764
33473
  init_logger();
32765
33474
 
32766
33475
  // src/hooks/auto-update-checker/hook/spinner-toast.ts
33476
+ init_logger();
32767
33477
  var SISYPHUS_SPINNER = ["\xB7", "\u2022", "\u25CF", "\u25CB", "\u25CC", "\u25E6", " "];
32768
33478
  async function showSpinnerToast(ctx, version, message) {
32769
33479
  const totalDuration = 5000;
@@ -32778,7 +33488,9 @@ async function showSpinnerToast(ctx, version, message) {
32778
33488
  variant: "info",
32779
33489
  duration: frameInterval + 50
32780
33490
  }
32781
- }).catch(() => {});
33491
+ }).catch((err) => {
33492
+ log("[auto-update] Spinner toast failed:", err);
33493
+ });
32782
33494
  await new Promise((resolve3) => setTimeout(resolve3, frameInterval));
32783
33495
  }
32784
33496
  }
@@ -32830,13 +33542,17 @@ v${latestVersion} available. Restart OpenCode to apply.` : "OpenCode is now on S
32830
33542
  await updateAndShowConnectedProvidersCacheStatus(ctx);
32831
33543
  if (localDevVersion) {
32832
33544
  if (showStartupToast) {
32833
- showLocalDevToast(ctx, displayVersion, isMorpheusEnabled).catch(() => {});
33545
+ showLocalDevToast(ctx, displayVersion, isMorpheusEnabled).catch((err) => {
33546
+ log("[auto-update] Startup toast failed:", err);
33547
+ });
32834
33548
  }
32835
33549
  log("[auto-update-checker] Local development mode");
32836
33550
  return;
32837
33551
  }
32838
33552
  if (showStartupToast) {
32839
- showVersionToast(ctx, displayVersion, getToastMessage(false)).catch(() => {});
33553
+ showVersionToast(ctx, displayVersion, getToastMessage(false)).catch((err) => {
33554
+ log("[auto-update] Version toast failed:", err);
33555
+ });
32840
33556
  }
32841
33557
  runBackgroundUpdateCheck(ctx, autoUpdate, getToastMessage).catch((err) => {
32842
33558
  log("[auto-update-checker] Background update check failed:", err);
@@ -48741,9 +49457,6 @@ function cleanupStaleEntries() {
48741
49457
  }
48742
49458
  }
48743
49459
  }
48744
- function isWriteTool(toolName) {
48745
- return toolName.toLowerCase() === "write";
48746
- }
48747
49460
  function extractFilePath(args) {
48748
49461
  const path10 = args.path ?? args.filePath ?? args.file_path;
48749
49462
  return typeof path10 === "string" ? path10 : undefined;
@@ -48815,12 +49528,6 @@ var CONTENT_CLOSE_TAG = "</content>";
48815
49528
  var FILE_OPEN_TAG = "<file>";
48816
49529
  var FILE_CLOSE_TAG = "</file>";
48817
49530
  var OPENCODE_LINE_TRUNCATION_SUFFIX = "... (line truncated to 2000 chars)";
48818
- function isReadTool(toolName) {
48819
- return toolName.toLowerCase() === "read";
48820
- }
48821
- function isWriteTool2(toolName) {
48822
- return toolName.toLowerCase() === "write";
48823
- }
48824
49531
  function shouldProcess(config2) {
48825
49532
  return config2.hashline_edit?.enabled ?? false;
48826
49533
  }
@@ -48941,7 +49648,7 @@ function createHashlineReadEnhancerHook(_ctx, config2) {
48941
49648
  return {
48942
49649
  "tool.execute.after": async (input, output) => {
48943
49650
  if (!isReadTool(input.tool)) {
48944
- if (isWriteTool2(input.tool) && typeof output.output === "string" && shouldProcess(config2)) {
49651
+ if (isWriteTool(input.tool) && typeof output.output === "string" && shouldProcess(config2)) {
48945
49652
  await appendWriteHashlineOutput(output);
48946
49653
  }
48947
49654
  return;
@@ -48956,6 +49663,9 @@ function createHashlineReadEnhancerHook(_ctx, config2) {
48956
49663
  }
48957
49664
  };
48958
49665
  }
49666
+ // src/hooks/interactive-bash-session/hook.ts
49667
+ init_logger();
49668
+
48959
49669
  // src/hooks/interactive-bash-session/constants.ts
48960
49670
  import { join as join46 } from "path";
48961
49671
  var INTERACTIVE_BASH_SESSION_STORAGE = join46(OPENCODE_STORAGE, "interactive-bash-session");
@@ -49135,7 +49845,9 @@ function createInteractiveBashSessionHook(ctx) {
49135
49845
  async function killAllTrackedSessionsLocal(state3) {
49136
49846
  await killAllTrackedSessions(state3);
49137
49847
  for (const sessionId of subagentSessions) {
49138
- ctx.client.session.abort({ path: { id: sessionId } }).catch(() => {});
49848
+ ctx.client.session.abort({ path: { id: sessionId } }).catch((err) => {
49849
+ log("[interactive-bash] Session abort failed:", err);
49850
+ });
49139
49851
  }
49140
49852
  }
49141
49853
  const toolExecuteAfter = async (input, output) => {
@@ -49964,6 +50676,21 @@ function detectKeywordsWithType(text, agentName, modelID) {
49964
50676
  function extractPromptText3(parts) {
49965
50677
  return parts.filter((p) => p.type === "text").map((p) => p.text || "").join(" ");
49966
50678
  }
50679
+ // src/features/ultrawork-state/manager.ts
50680
+ init_logger();
50681
+ var HOOK_NAME4 = "ultrawork-state";
50682
+ var sessionStates = new Map;
50683
+ function getUltraworkState(sessionID) {
50684
+ return sessionStates.get(sessionID);
50685
+ }
50686
+ function enableUltrawork(sessionID) {
50687
+ sessionStates.set(sessionID, "enabled");
50688
+ log(`[${HOOK_NAME4}] Ultrawork enabled for session`, { sessionID });
50689
+ }
50690
+ function disableUltrawork(sessionID) {
50691
+ sessionStates.set(sessionID, "disabled");
50692
+ log(`[${HOOK_NAME4}] Ultrawork disabled for session`, { sessionID });
50693
+ }
49967
50694
  // src/hooks/keyword-detector/hook.ts
49968
50695
  function createKeywordDetectorHook(ctx, _collector) {
49969
50696
  return {
@@ -49999,8 +50726,15 @@ function createKeywordDetectorHook(ctx, _collector) {
49999
50726
  return;
50000
50727
  }
50001
50728
  }
50729
+ const ultraworkState = getUltraworkState(input.sessionID);
50002
50730
  const hasUltrawork = detectedKeywords.some((k) => k.type === "ultrawork");
50003
- if (hasUltrawork) {
50731
+ const isUltraworkBlocked = ultraworkState === "disabled";
50732
+ const isUltraworkForced = ultraworkState === "enabled";
50733
+ if (isUltraworkBlocked) {
50734
+ log(`[keyword-detector] Ultrawork blocked by runtime state`, { sessionID: input.sessionID });
50735
+ return;
50736
+ }
50737
+ if (hasUltrawork || isUltraworkForced) {
50004
50738
  log(`[keyword-detector] Ultrawork mode activated`, { sessionID: input.sessionID });
50005
50739
  if (output.message.variant === undefined) {
50006
50740
  output.message.variant = "max";
@@ -50088,7 +50822,7 @@ function createLoopStateController(options) {
50088
50822
  };
50089
50823
  const success2 = writeState(directory, state3, stateDir);
50090
50824
  if (success2) {
50091
- log(`[${HOOK_NAME4}] Loop started`, {
50825
+ log(`[${HOOK_NAME5}] Loop started`, {
50092
50826
  sessionID,
50093
50827
  maxIterations: state3.max_iterations,
50094
50828
  completionPromise: state3.completion_promise
@@ -50103,7 +50837,7 @@ function createLoopStateController(options) {
50103
50837
  }
50104
50838
  const success2 = clearState(directory, stateDir);
50105
50839
  if (success2) {
50106
- log(`[${HOOK_NAME4}] Loop cancelled`, { sessionID, iteration: state3.iteration });
50840
+ log(`[${HOOK_NAME5}] Loop cancelled`, { sessionID, iteration: state3.iteration });
50107
50841
  }
50108
50842
  return success2;
50109
50843
  },
@@ -50199,7 +50933,7 @@ async function detectCompletionInSessionMessages(ctx, options) {
50199
50933
  return false;
50200
50934
  } catch (err) {
50201
50935
  setTimeout(() => {
50202
- log(`[${HOOK_NAME4}] Session messages check failed`, {
50936
+ log(`[${HOOK_NAME5}] Session messages check failed`, {
50203
50937
  sessionID: options.sessionID,
50204
50938
  error: String(err)
50205
50939
  });
@@ -50280,7 +51014,7 @@ async function verifyCompletion(ctx, options) {
50280
51014
  query: { directory: options.directory }
50281
51015
  });
50282
51016
  if (createResult.error || !createResult.data?.id) {
50283
- log(`[${HOOK_NAME4}] Verification session creation failed`);
51017
+ log(`[${HOOK_NAME5}] Verification session creation failed`);
50284
51018
  return { verified: true };
50285
51019
  }
50286
51020
  verifySessionID = createResult.data.id;
@@ -50310,20 +51044,22 @@ async function verifyCompletion(ctx, options) {
50310
51044
  return parseVerificationResponse(assistantText);
50311
51045
  }
50312
51046
  } catch {
50313
- log(`[${HOOK_NAME4}] Verification poll error`);
51047
+ log(`[${HOOK_NAME5}] Verification poll error`);
50314
51048
  }
50315
51049
  await new Promise((resolve5) => {
50316
51050
  setTimeout(resolve5, pollIntervalMs);
50317
51051
  });
50318
51052
  }
50319
- log(`[${HOOK_NAME4}] Verification timed out, assuming pass`);
51053
+ log(`[${HOOK_NAME5}] Verification timed out, assuming pass`);
50320
51054
  return { verified: true };
50321
51055
  } catch (err) {
50322
- log(`[${HOOK_NAME4}] Verification error: ${String(err)}`);
51056
+ log(`[${HOOK_NAME5}] Verification error: ${String(err)}`);
50323
51057
  return { verified: true };
50324
51058
  } finally {
50325
51059
  if (verifySessionID) {
50326
- ctx.client.session.delete({ path: { id: verifySessionID } }).catch(() => {});
51060
+ ctx.client.session.delete({ path: { id: verifySessionID } }).catch((err) => {
51061
+ log("[matrix-loop] Verification session cleanup failed:", err);
51062
+ });
50327
51063
  }
50328
51064
  }
50329
51065
  }
@@ -50393,7 +51129,7 @@ async function injectContinuationPrompt(ctx, options) {
50393
51129
 
50394
51130
  // src/hooks/matrix-loop/matrix-loop-event-handler.ts
50395
51131
  async function handleVerificationFailure(ctx, options, state3, sessionID, reason, failedCount) {
50396
- log(`[${HOOK_NAME4}] Verification failed`, { sessionID, reason });
51132
+ log(`[${HOOK_NAME5}] Verification failed`, { sessionID, reason });
50397
51133
  const newState = options.loopState.incrementIteration();
50398
51134
  if (newState) {
50399
51135
  newState.verification_failed_count = failedCount + 1;
@@ -50411,7 +51147,7 @@ ${buildContinuationPrompt(newState ?? state3)}`,
50411
51147
  apiTimeoutMs: options.apiTimeoutMs
50412
51148
  });
50413
51149
  } catch (err) {
50414
- log(`[${HOOK_NAME4}] Failed to inject verification feedback`, { sessionID, error: String(err) });
51150
+ log(`[${HOOK_NAME5}] Failed to inject verification feedback`, { sessionID, error: String(err) });
50415
51151
  }
50416
51152
  }
50417
51153
  function createMatrixLoopEventHandler(ctx, options) {
@@ -50423,13 +51159,13 @@ function createMatrixLoopEventHandler(ctx, options) {
50423
51159
  if (!sessionID)
50424
51160
  return;
50425
51161
  if (inFlightSessions.has(sessionID)) {
50426
- log(`[${HOOK_NAME4}] Skipped: handler in flight`, { sessionID });
51162
+ log(`[${HOOK_NAME5}] Skipped: handler in flight`, { sessionID });
50427
51163
  return;
50428
51164
  }
50429
51165
  inFlightSessions.add(sessionID);
50430
51166
  try {
50431
51167
  if (options.sessionRecovery.isRecovering(sessionID)) {
50432
- log(`[${HOOK_NAME4}] Skipped: in recovery`, { sessionID });
51168
+ log(`[${HOOK_NAME5}] Skipped: in recovery`, { sessionID });
50433
51169
  return;
50434
51170
  }
50435
51171
  const state3 = options.loopState.getState();
@@ -50441,11 +51177,11 @@ function createMatrixLoopEventHandler(ctx, options) {
50441
51177
  const exists = await options.checkSessionExists(state3.session_id);
50442
51178
  if (!exists) {
50443
51179
  options.loopState.clear();
50444
- log(`[${HOOK_NAME4}] Cleared orphaned state from deleted session`, { orphanedSessionId: state3.session_id, currentSessionId: sessionID });
51180
+ log(`[${HOOK_NAME5}] Cleared orphaned state from deleted session`, { orphanedSessionId: state3.session_id, currentSessionId: sessionID });
50445
51181
  return;
50446
51182
  }
50447
51183
  } catch (err) {
50448
- log(`[${HOOK_NAME4}] Failed to check session existence`, { sessionId: state3.session_id, error: String(err) });
51184
+ log(`[${HOOK_NAME5}] Failed to check session existence`, { sessionId: state3.session_id, error: String(err) });
50449
51185
  }
50450
51186
  }
50451
51187
  return;
@@ -50464,7 +51200,7 @@ function createMatrixLoopEventHandler(ctx, options) {
50464
51200
  messages = Array.isArray(messagesResponse) ? messagesResponse : Array.isArray(responseData) ? responseData : [];
50465
51201
  } catch (err) {
50466
51202
  setTimeout(() => {
50467
- log(`[${HOOK_NAME4}] Session messages fetch failed`, { sessionID, error: String(err) });
51203
+ log(`[${HOOK_NAME5}] Session messages fetch failed`, { sessionID, error: String(err) });
50468
51204
  }, 0);
50469
51205
  messages = [];
50470
51206
  }
@@ -50477,13 +51213,15 @@ function createMatrixLoopEventHandler(ctx, options) {
50477
51213
  preFetchedMessages: messages
50478
51214
  });
50479
51215
  if (completionViaTranscript || completionViaApi) {
50480
- log(`[${HOOK_NAME4}] Completion detected!`, { sessionID, iteration: state3.iteration, promise: state3.completion_promise, detectedVia: completionViaTranscript ? "transcript_file" : "session_messages_api" });
51216
+ log(`[${HOOK_NAME5}] Completion detected!`, { sessionID, iteration: state3.iteration, promise: state3.completion_promise, detectedVia: completionViaTranscript ? "transcript_file" : "session_messages_api" });
50481
51217
  if (options.verification?.enabled === true) {
50482
51218
  const maxRetries = options.verification?.maxRetries ?? DEFAULT_VERIFICATION_MAX_RETRIES;
50483
51219
  const failedCount = state3.verification_failed_count ?? 0;
50484
51220
  if (failedCount >= maxRetries) {
50485
51221
  options.loopState.clear();
50486
- await ctx.client.tui.showToast({ body: { title: "Matrix Loop Complete", message: `Force-completed after ${failedCount} failed verification(s)`, variant: "warning", duration: 5000 } }).catch(() => {});
51222
+ await ctx.client.tui.showToast({ body: { title: "Matrix Loop Complete", message: `Force-completed after ${failedCount} failed verification(s)`, variant: "warning", duration: 5000 } }).catch((err) => {
51223
+ log("[matrix-loop] Force complete toast failed:", err);
51224
+ });
50487
51225
  return;
50488
51226
  }
50489
51227
  const result = await verifyCompletion(ctx, {
@@ -50504,26 +51242,32 @@ function createMatrixLoopEventHandler(ctx, options) {
50504
51242
  options.loopState.clear();
50505
51243
  const title = state3.ultrawork ? "ULTRAWORK LOOP COMPLETE!" : "Matrix Loop Complete!";
50506
51244
  const message = state3.ultrawork ? `JUST ULW ULW! Task completed after ${state3.iteration} iteration(s)` : `Task completed after ${state3.iteration} iteration(s)`;
50507
- await ctx.client.tui.showToast({ body: { title, message, variant: "success", duration: 5000 } }).catch(() => {});
51245
+ await ctx.client.tui.showToast({ body: { title, message, variant: "success", duration: 5000 } }).catch((err) => {
51246
+ log("[matrix-loop] Loop complete toast failed:", err);
51247
+ });
50508
51248
  return;
50509
51249
  }
50510
51250
  if (state3.iteration >= state3.max_iterations) {
50511
- log(`[${HOOK_NAME4}] Max iterations reached`, { sessionID, iteration: state3.iteration, max: state3.max_iterations });
51251
+ log(`[${HOOK_NAME5}] Max iterations reached`, { sessionID, iteration: state3.iteration, max: state3.max_iterations });
50512
51252
  options.loopState.clear();
50513
- await ctx.client.tui.showToast({ body: { title: "Matrix Loop Stopped", message: `Max iterations (${state3.max_iterations}) reached without completion`, variant: "warning", duration: 5000 } }).catch(() => {});
51253
+ await ctx.client.tui.showToast({ body: { title: "Matrix Loop Stopped", message: `Max iterations (${state3.max_iterations}) reached without completion`, variant: "warning", duration: 5000 } }).catch((err) => {
51254
+ log("[matrix-loop] Max iterations toast failed:", err);
51255
+ });
50514
51256
  return;
50515
51257
  }
50516
51258
  const newState = options.loopState.incrementIteration();
50517
51259
  if (!newState) {
50518
- log(`[${HOOK_NAME4}] Failed to increment iteration`, { sessionID });
51260
+ log(`[${HOOK_NAME5}] Failed to increment iteration`, { sessionID });
50519
51261
  return;
50520
51262
  }
50521
- log(`[${HOOK_NAME4}] Continuing loop`, { sessionID, iteration: newState.iteration, max: newState.max_iterations });
50522
- await ctx.client.tui.showToast({ body: { title: "Matrix Loop", message: `Iteration ${newState.iteration}/${newState.max_iterations}`, variant: "info", duration: 2000 } }).catch(() => {});
51263
+ log(`[${HOOK_NAME5}] Continuing loop`, { sessionID, iteration: newState.iteration, max: newState.max_iterations });
51264
+ await ctx.client.tui.showToast({ body: { title: "Matrix Loop", message: `Iteration ${newState.iteration}/${newState.max_iterations}`, variant: "info", duration: 2000 } }).catch((err) => {
51265
+ log("[matrix-loop] Iteration toast failed:", err);
51266
+ });
50523
51267
  try {
50524
51268
  await injectContinuationPrompt(ctx, { sessionID, prompt: buildContinuationPrompt(newState), directory: options.directory, apiTimeoutMs: options.apiTimeoutMs });
50525
51269
  } catch (err) {
50526
- log(`[${HOOK_NAME4}] Failed to inject continuation`, { sessionID, error: String(err) });
51270
+ log(`[${HOOK_NAME5}] Failed to inject continuation`, { sessionID, error: String(err) });
50527
51271
  }
50528
51272
  return;
50529
51273
  } finally {
@@ -50537,7 +51281,7 @@ function createMatrixLoopEventHandler(ctx, options) {
50537
51281
  const state3 = options.loopState.getState();
50538
51282
  if (state3?.session_id === sessionInfo.id) {
50539
51283
  options.loopState.clear();
50540
- log(`[${HOOK_NAME4}] Session deleted, loop cleared`, { sessionID: sessionInfo.id });
51284
+ log(`[${HOOK_NAME5}] Session deleted, loop cleared`, { sessionID: sessionInfo.id });
50541
51285
  }
50542
51286
  options.sessionRecovery.clear(sessionInfo.id);
50543
51287
  return;
@@ -50550,7 +51294,7 @@ function createMatrixLoopEventHandler(ctx, options) {
50550
51294
  const state3 = options.loopState.getState();
50551
51295
  if (state3?.session_id === sessionID) {
50552
51296
  options.loopState.clear();
50553
- log(`[${HOOK_NAME4}] User aborted, loop cleared`, { sessionID });
51297
+ log(`[${HOOK_NAME5}] User aborted, loop cleared`, { sessionID });
50554
51298
  }
50555
51299
  options.sessionRecovery.clear(sessionID);
50556
51300
  }
@@ -50597,7 +51341,7 @@ function createMatrixLoopHook(ctx, options) {
50597
51341
  // src/hooks/matrix-loop/index.ts
50598
51342
  init_storage();
50599
51343
  // src/hooks/mouse-notepad/constants.ts
50600
- var HOOK_NAME5 = "mouse-notepad";
51344
+ var HOOK_NAME6 = "mouse-notepad";
50601
51345
  var NOTEPAD_DIRECTIVE = `
50602
51346
  <Work_Context>
50603
51347
  ## Notepad Location (for recording learnings)
@@ -50644,14 +51388,14 @@ function createMouseNotepadHook(ctx) {
50644
51388
  return;
50645
51389
  }
50646
51390
  output.args.prompt = NOTEPAD_DIRECTIVE + prompt;
50647
- log(`[${HOOK_NAME5}] Injected notepad directive to task`, {
51391
+ log(`[${HOOK_NAME6}] Injected notepad directive to task`, {
50648
51392
  sessionID: input.sessionID
50649
51393
  });
50650
51394
  }
50651
51395
  };
50652
51396
  }
50653
51397
  // src/hooks/non-interactive-env/constants.ts
50654
- var HOOK_NAME6 = "non-interactive-env";
51398
+ var HOOK_NAME7 = "non-interactive-env";
50655
51399
  var NON_INTERACTIVE_ENV = {
50656
51400
  CI: "true",
50657
51401
  DEBIAN_FRONTEND: "noninteractive",
@@ -50742,7 +51486,7 @@ function createNonInteractiveEnvHook(_ctx) {
50742
51486
  }
50743
51487
  const envPrefix = buildEnvPrefix(NON_INTERACTIVE_ENV, "unix");
50744
51488
  output.args.command = `${envPrefix} ${command}`;
50745
- log(`[${HOOK_NAME6}] Prepended non-interactive env vars to git command`, {
51489
+ log(`[${HOOK_NAME7}] Prepended non-interactive env vars to git command`, {
50746
51490
  sessionID: input.sessionID,
50747
51491
  envPrefix
50748
51492
  });
@@ -50891,7 +51635,9 @@ function createPostCompactionDegradationMonitor(args) {
50891
51635
  variant: "warning",
50892
51636
  duration: 5000
50893
51637
  }
50894
- }).catch(() => {});
51638
+ }).catch((err) => {
51639
+ log("[preemptive-compaction] Degradation toast failed:", err);
51640
+ });
50895
51641
  await withTimeout2(client.session.summarize({
50896
51642
  path: { id: sessionID },
50897
51643
  body: { providerID: targetProviderID, modelID: targetModelID },
@@ -51129,7 +51875,7 @@ function createPreemptiveCompactionHook(ctx, pluginConfig = {}, modelCacheState)
51129
51875
  };
51130
51876
  }
51131
51877
  // src/hooks/prometheus-md-only/constants.ts
51132
- var HOOK_NAME7 = "prometheus-md-only";
51878
+ var HOOK_NAME8 = "prometheus-md-only";
51133
51879
  var ORACLE_AGENT = "oracle";
51134
51880
  var ALLOWED_EXTENSIONS = [".md"];
51135
51881
  var BLOCKED_TOOLS = ["Write", "Edit", "write", "edit"];
@@ -51262,7 +52008,7 @@ function createOracleMdOnlyHook(ctx) {
51262
52008
  const prompt = output.args.prompt;
51263
52009
  if (prompt && !prompt.includes(SYSTEM_DIRECTIVE_PREFIX)) {
51264
52010
  output.args.prompt = PLANNING_CONSULT_WARNING + prompt;
51265
- log(`[${HOOK_NAME7}] Injected read-only planning warning to ${toolName}`, {
52011
+ log(`[${HOOK_NAME8}] Injected read-only planning warning to ${toolName}`, {
51266
52012
  sessionID: input.sessionID,
51267
52013
  tool: toolName,
51268
52014
  agent: agentName
@@ -51278,17 +52024,17 @@ function createOracleMdOnlyHook(ctx) {
51278
52024
  return;
51279
52025
  }
51280
52026
  if (!isAllowedFile(filePath, ctx.directory)) {
51281
- log(`[${HOOK_NAME7}] Blocked: Oracle can only write to .matrixx/*.md`, {
52027
+ log(`[${HOOK_NAME8}] Blocked: Oracle can only write to .matrixx/*.md`, {
51282
52028
  sessionID: input.sessionID,
51283
52029
  tool: toolName,
51284
52030
  filePath,
51285
52031
  agent: agentName
51286
52032
  });
51287
- throw new Error(`[${HOOK_NAME7}] ${getAgentDisplayName("oracle")} can only write/edit .md files inside .matrixx/ directory. ` + `Attempted to modify: ${filePath}. ` + `${getAgentDisplayName("oracle")} is a READ-ONLY planner. Use /start-work to execute the plan. ` + `APOLOGIZE TO THE USER, REMIND OF YOUR PLAN WRITING PROCESSES, TELL USER WHAT YOU WILL GOING TO DO AS THE PROCESS, WRITE THE PLAN`);
52033
+ throw new Error(`[${HOOK_NAME8}] ${getAgentDisplayName("oracle")} can only write/edit .md files inside .matrixx/ directory. ` + `Attempted to modify: ${filePath}. ` + `${getAgentDisplayName("oracle")} is a READ-ONLY planner. Use /start-work to execute the plan. ` + `APOLOGIZE TO THE USER, REMIND OF YOUR PLAN WRITING PROCESSES, TELL USER WHAT YOU WILL GOING TO DO AS THE PROCESS, WRITE THE PLAN`);
51288
52034
  }
51289
52035
  const normalizedPath = filePath.toLowerCase().replace(/\\/g, "/");
51290
52036
  if (normalizedPath.includes(".matrixx/plans/") || normalizedPath.includes(".matrixx\\plans\\")) {
51291
- log(`[${HOOK_NAME7}] Injecting workflow reminder for plan write`, {
52037
+ log(`[${HOOK_NAME8}] Injecting workflow reminder for plan write`, {
51292
52038
  sessionID: input.sessionID,
51293
52039
  tool: toolName,
51294
52040
  filePath,
@@ -51296,7 +52042,7 @@ function createOracleMdOnlyHook(ctx) {
51296
52042
  });
51297
52043
  output.message = (output.message || "") + PROMETHEUS_WORKFLOW_REMINDER;
51298
52044
  }
51299
- log(`[${HOOK_NAME7}] Allowed: .matrixx/*.md write permitted`, {
52045
+ log(`[${HOOK_NAME8}] Allowed: .matrixx/*.md write permitted`, {
51300
52046
  sessionID: input.sessionID,
51301
52047
  tool: toolName,
51302
52048
  filePath,
@@ -52029,9 +52775,6 @@ async function resizeImage(base64DataUrl, mimeType, target, deps = {}) {
52029
52775
  // src/hooks/read-image-resizer/hook.ts
52030
52776
  var SUPPORTED_IMAGE_MIMES = new Set(["image/png", "image/jpeg", "image/gif", "image/webp"]);
52031
52777
  var TOKEN_DIVISOR = 750;
52032
- function isReadTool2(toolName) {
52033
- return toolName.toLowerCase() === "read";
52034
- }
52035
52778
  function asRecord(value) {
52036
52779
  if (!value || typeof value !== "object" || Array.isArray(value)) {
52037
52780
  return null;
@@ -52115,7 +52858,7 @@ function resolveFilename(attachment, index) {
52115
52858
  function createReadImageResizerHook(_ctx) {
52116
52859
  return {
52117
52860
  "tool.execute.after": async (input, output) => {
52118
- if (!isReadTool2(input.tool)) {
52861
+ if (!isReadTool(input.tool)) {
52119
52862
  return;
52120
52863
  }
52121
52864
  const sessionModel = getSessionModel(input.sessionID);
@@ -52852,7 +53595,7 @@ var RETRYABLE_ERROR_PATTERNS = [
52852
53595
  /(?:^|\s)503(?:\s|$)/,
52853
53596
  /(?:^|\s)529(?:\s|$)/
52854
53597
  ];
52855
- var HOOK_NAME8 = "runtime-fallback";
53598
+ var HOOK_NAME9 = "runtime-fallback";
52856
53599
 
52857
53600
  // src/hooks/runtime-fallback/fallback-models.ts
52858
53601
  init_logger();
@@ -52943,7 +53686,7 @@ function getRawFallbackModelsForSession(sessionID, agent, pluginConfig) {
52943
53686
  if (result)
52944
53687
  return result;
52945
53688
  }
52946
- log(`[${HOOK_NAME8}] No category/agent fallback models resolved for session`, { sessionID, agent });
53689
+ log(`[${HOOK_NAME9}] No category/agent fallback models resolved for session`, { sessionID, agent });
52947
53690
  return;
52948
53691
  }
52949
53692
 
@@ -52972,21 +53715,21 @@ function findNextAvailableFallback(state3, fallbackModels, cooldownSeconds) {
52972
53715
  if (!isModelInCooldown(candidate, state3, cooldownSeconds)) {
52973
53716
  return candidate;
52974
53717
  }
52975
- log(`[${HOOK_NAME8}] Skipping fallback model in cooldown`, { model: candidate, index: i });
53718
+ log(`[${HOOK_NAME9}] Skipping fallback model in cooldown`, { model: candidate, index: i });
52976
53719
  }
52977
53720
  return;
52978
53721
  }
52979
53722
  function prepareFallback(sessionID, state3, fallbackModels, config2) {
52980
53723
  if (state3.attemptCount >= config2.max_fallback_attempts) {
52981
- log(`[${HOOK_NAME8}] Max fallback attempts reached`, { sessionID, attempts: state3.attemptCount });
53724
+ log(`[${HOOK_NAME9}] Max fallback attempts reached`, { sessionID, attempts: state3.attemptCount });
52982
53725
  return { success: false, error: "Max fallback attempts reached", maxAttemptsReached: true };
52983
53726
  }
52984
53727
  const nextModel = findNextAvailableFallback(state3, fallbackModels, config2.cooldown_seconds);
52985
53728
  if (!nextModel) {
52986
- log(`[${HOOK_NAME8}] No available fallback models`, { sessionID });
53729
+ log(`[${HOOK_NAME9}] No available fallback models`, { sessionID });
52987
53730
  return { success: false, error: "No available fallback models (all in cooldown or exhausted)" };
52988
53731
  }
52989
- log(`[${HOOK_NAME8}] Preparing fallback`, {
53732
+ log(`[${HOOK_NAME9}] Preparing fallback`, {
52990
53733
  sessionID,
52991
53734
  from: state3.currentModel,
52992
53735
  to: nextModel,
@@ -53064,7 +53807,7 @@ function createAutoRetryHelpers(deps) {
53064
53807
  ctx,
53065
53808
  config: config2,
53066
53809
  options,
53067
- sessionStates,
53810
+ sessionStates: sessionStates2,
53068
53811
  sessionLastAccess,
53069
53812
  sessionRetryInFlight,
53070
53813
  sessionAwaitingFallbackResult,
@@ -53075,9 +53818,9 @@ function createAutoRetryHelpers(deps) {
53075
53818
  const abortSessionRequest = async (sessionID, source) => {
53076
53819
  try {
53077
53820
  await ctx.client.session.abort({ path: { id: sessionID } });
53078
- log(`[${HOOK_NAME8}] Aborted in-flight session request (${source})`, { sessionID });
53821
+ log(`[${HOOK_NAME9}] Aborted in-flight session request (${source})`, { sessionID });
53079
53822
  } catch (error51) {
53080
- log(`[${HOOK_NAME8}] Failed to abort in-flight session request (${source})`, {
53823
+ log(`[${HOOK_NAME9}] Failed to abort in-flight session request (${source})`, {
53081
53824
  sessionID,
53082
53825
  error: String(error51)
53083
53826
  });
@@ -53097,11 +53840,11 @@ function createAutoRetryHelpers(deps) {
53097
53840
  return;
53098
53841
  const timer = setTimeout(async () => {
53099
53842
  sessionFallbackTimeouts.delete(sessionID);
53100
- const state3 = sessionStates.get(sessionID);
53843
+ const state3 = sessionStates2.get(sessionID);
53101
53844
  if (!state3)
53102
53845
  return;
53103
53846
  if (sessionRetryInFlight.has(sessionID)) {
53104
- log(`[${HOOK_NAME8}] Overriding in-flight retry due to session timeout`, { sessionID });
53847
+ log(`[${HOOK_NAME9}] Overriding in-flight retry due to session timeout`, { sessionID });
53105
53848
  }
53106
53849
  await abortSessionRequest(sessionID, "session.timeout");
53107
53850
  sessionRetryInFlight.delete(sessionID);
@@ -53111,7 +53854,7 @@ function createAutoRetryHelpers(deps) {
53111
53854
  const fallbackModels = getFallbackModelsForSession(sessionID, resolvedAgent, pluginConfig);
53112
53855
  if (fallbackModels.length === 0)
53113
53856
  return;
53114
- log(`[${HOOK_NAME8}] Session fallback timeout reached`, {
53857
+ log(`[${HOOK_NAME9}] Session fallback timeout reached`, {
53115
53858
  sessionID,
53116
53859
  timeoutSeconds: config2.timeout_seconds,
53117
53860
  currentModel: state3.currentModel
@@ -53125,7 +53868,7 @@ function createAutoRetryHelpers(deps) {
53125
53868
  };
53126
53869
  const autoRetryWithFallback = async (sessionID, newModel, resolvedAgent, source) => {
53127
53870
  if (sessionRetryInFlight.has(sessionID)) {
53128
- log(`[${HOOK_NAME8}] Retry already in flight, skipping (${source})`, { sessionID });
53871
+ log(`[${HOOK_NAME9}] Retry already in flight, skipping (${source})`, { sessionID });
53129
53872
  return;
53130
53873
  }
53131
53874
  const agentSettings = resolvedAgent ? pluginConfig?.agents?.[resolvedAgent] : undefined;
@@ -53134,8 +53877,8 @@ function createAutoRetryHelpers(deps) {
53134
53877
  reasoningEffort: agentSettings.reasoningEffort
53135
53878
  } : undefined);
53136
53879
  if (!retryModelPayload) {
53137
- log(`[${HOOK_NAME8}] Invalid model format (missing provider prefix): ${newModel}`);
53138
- const state3 = sessionStates.get(sessionID);
53880
+ log(`[${HOOK_NAME9}] Invalid model format (missing provider prefix): ${newModel}`);
53881
+ const state3 = sessionStates2.get(sessionID);
53139
53882
  if (state3?.pendingFallbackModel) {
53140
53883
  state3.pendingFallbackModel = undefined;
53141
53884
  }
@@ -53150,7 +53893,7 @@ function createAutoRetryHelpers(deps) {
53150
53893
  });
53151
53894
  const retryParts = getLastUserRetryParts(messagesResp);
53152
53895
  if (retryParts.length > 0) {
53153
- log(`[${HOOK_NAME8}] Auto-retrying with fallback model (${source})`, {
53896
+ log(`[${HOOK_NAME9}] Auto-retrying with fallback model (${source})`, {
53154
53897
  sessionID,
53155
53898
  model: newModel
53156
53899
  });
@@ -53169,16 +53912,16 @@ function createAutoRetryHelpers(deps) {
53169
53912
  });
53170
53913
  retryDispatched = true;
53171
53914
  } else {
53172
- log(`[${HOOK_NAME8}] No user message found for auto-retry (${source})`, { sessionID });
53915
+ log(`[${HOOK_NAME9}] No user message found for auto-retry (${source})`, { sessionID });
53173
53916
  }
53174
53917
  } catch (retryError) {
53175
- log(`[${HOOK_NAME8}] Auto-retry failed (${source})`, { sessionID, error: String(retryError) });
53918
+ log(`[${HOOK_NAME9}] Auto-retry failed (${source})`, { sessionID, error: String(retryError) });
53176
53919
  } finally {
53177
53920
  sessionRetryInFlight.delete(sessionID);
53178
53921
  if (!retryDispatched) {
53179
53922
  sessionAwaitingFallbackResult.delete(sessionID);
53180
53923
  clearSessionFallbackTimeout(sessionID);
53181
- const state3 = sessionStates.get(sessionID);
53924
+ const state3 = sessionStates2.get(sessionID);
53182
53925
  if (state3?.pendingFallbackModel) {
53183
53926
  state3.pendingFallbackModel = undefined;
53184
53927
  }
@@ -53215,7 +53958,7 @@ function createAutoRetryHelpers(deps) {
53215
53958
  let cleanedCount = 0;
53216
53959
  for (const [sessionID, lastAccess] of sessionLastAccess.entries()) {
53217
53960
  if (now - lastAccess > SESSION_TTL_MS) {
53218
- sessionStates.delete(sessionID);
53961
+ sessionStates2.delete(sessionID);
53219
53962
  sessionLastAccess.delete(sessionID);
53220
53963
  sessionRetryInFlight.delete(sessionID);
53221
53964
  sessionAwaitingFallbackResult.delete(sessionID);
@@ -53226,7 +53969,7 @@ function createAutoRetryHelpers(deps) {
53226
53969
  }
53227
53970
  }
53228
53971
  if (cleanedCount > 0) {
53229
- log(`[${HOOK_NAME8}] Cleaned up ${cleanedCount} stale session states`);
53972
+ log(`[${HOOK_NAME9}] Cleaned up ${cleanedCount} stale session states`);
53230
53973
  }
53231
53974
  };
53232
53975
  return {
@@ -53242,12 +53985,12 @@ function createAutoRetryHelpers(deps) {
53242
53985
  // src/hooks/runtime-fallback/chat-message-handler.ts
53243
53986
  init_logger();
53244
53987
  function createChatMessageHandler(deps) {
53245
- const { config: config2, sessionStates, sessionLastAccess } = deps;
53988
+ const { config: config2, sessionStates: sessionStates2, sessionLastAccess } = deps;
53246
53989
  return async (input, output) => {
53247
53990
  if (!config2.enabled)
53248
53991
  return;
53249
53992
  const { sessionID } = input;
53250
- let state3 = sessionStates.get(sessionID);
53993
+ let state3 = sessionStates2.get(sessionID);
53251
53994
  if (!state3)
53252
53995
  return;
53253
53996
  sessionLastAccess.set(sessionID, Date.now());
@@ -53257,19 +54000,19 @@ function createChatMessageHandler(deps) {
53257
54000
  state3.pendingFallbackModel = undefined;
53258
54001
  return;
53259
54002
  }
53260
- log(`[${HOOK_NAME8}] Detected manual model change, resetting fallback state`, {
54003
+ log(`[${HOOK_NAME9}] Detected manual model change, resetting fallback state`, {
53261
54004
  sessionID,
53262
54005
  from: state3.currentModel,
53263
54006
  to: requestedModel
53264
54007
  });
53265
54008
  state3 = createFallbackState(requestedModel);
53266
- sessionStates.set(sessionID, state3);
54009
+ sessionStates2.set(sessionID, state3);
53267
54010
  return;
53268
54011
  }
53269
54012
  if (state3.currentModel === state3.originalModel)
53270
54013
  return;
53271
54014
  const activeModel = state3.currentModel;
53272
- log(`[${HOOK_NAME8}] Applying fallback model override`, {
54015
+ log(`[${HOOK_NAME9}] Applying fallback model override`, {
53273
54016
  sessionID,
53274
54017
  from: input.model,
53275
54018
  to: activeModel
@@ -53456,7 +54199,7 @@ function resolveFallbackBootstrapModel(options) {
53456
54199
  const agentConfig = options.resolvedAgent && agentConfigs ? agentConfigs[options.resolvedAgent] : undefined;
53457
54200
  const agentModel = typeof agentConfig?.model === "string" ? agentConfig.model : undefined;
53458
54201
  if (agentModel) {
53459
- log(`[${HOOK_NAME8}] Derived model from agent config for ${options.source}`, {
54202
+ log(`[${HOOK_NAME9}] Derived model from agent config for ${options.source}`, {
53460
54203
  sessionID: options.sessionID,
53461
54204
  agent: options.resolvedAgent,
53462
54205
  model: agentModel
@@ -53467,7 +54210,7 @@ function resolveFallbackBootstrapModel(options) {
53467
54210
  if (agentCategory) {
53468
54211
  const agentCategoryModel = options.pluginConfig?.categories?.[agentCategory]?.model;
53469
54212
  if (typeof agentCategoryModel === "string" && agentCategoryModel.length > 0) {
53470
- log(`[${HOOK_NAME8}] Derived model from agent category config for ${options.source}`, {
54213
+ log(`[${HOOK_NAME9}] Derived model from agent category config for ${options.source}`, {
53471
54214
  sessionID: options.sessionID,
53472
54215
  agent: options.resolvedAgent,
53473
54216
  category: agentCategory,
@@ -53479,7 +54222,7 @@ function resolveFallbackBootstrapModel(options) {
53479
54222
  const sessionCategory = SessionCategoryRegistry.get(options.sessionID);
53480
54223
  const categoryModel = sessionCategory ? options.pluginConfig?.categories?.[sessionCategory]?.model : undefined;
53481
54224
  if (typeof categoryModel === "string" && categoryModel.length > 0) {
53482
- log(`[${HOOK_NAME8}] Derived model from session category config for ${options.source}`, {
54225
+ log(`[${HOOK_NAME9}] Derived model from session category config for ${options.source}`, {
53483
54226
  sessionID: options.sessionID,
53484
54227
  category: sessionCategory,
53485
54228
  model: categoryModel
@@ -53501,13 +54244,15 @@ async function dispatchFallbackRetry(deps, helpers, options) {
53501
54244
  variant: "warning",
53502
54245
  duration: 5000
53503
54246
  }
53504
- }).catch(() => {});
54247
+ }).catch((err) => {
54248
+ log("[runtime-fallback] Fallback notification toast failed:", err);
54249
+ });
53505
54250
  }
53506
54251
  if (result.success && result.newModel) {
53507
54252
  await helpers.autoRetryWithFallback(options.sessionID, result.newModel, options.resolvedAgent, options.source);
53508
54253
  return;
53509
54254
  }
53510
- log(`[${HOOK_NAME8}] Fallback preparation failed`, {
54255
+ log(`[${HOOK_NAME9}] Fallback preparation failed`, {
53511
54256
  sessionID: options.sessionID,
53512
54257
  source: options.source,
53513
54258
  error: result.error
@@ -53555,7 +54300,7 @@ function extractRetryAttempt(statusAttempt, message) {
53555
54300
  function createSessionStatusHandler(deps, helpers, sessionStatusRetryKeys) {
53556
54301
  const {
53557
54302
  pluginConfig,
53558
- sessionStates,
54303
+ sessionStates: sessionStates2,
53559
54304
  sessionLastAccess,
53560
54305
  sessionRetryInFlight
53561
54306
  } = deps;
@@ -53582,26 +54327,26 @@ function createSessionStatusHandler(deps, helpers, sessionStatusRetryKeys) {
53582
54327
  sessionStatusRetryKeys.set(sessionID, retryKey);
53583
54328
  if (sessionRetryInFlight.has(sessionID)) {
53584
54329
  if (timeoutEnabled) {
53585
- log(`[${HOOK_NAME8}] Overriding in-flight retry due to provider auto-retry signal`, {
54330
+ log(`[${HOOK_NAME9}] Overriding in-flight retry due to provider auto-retry signal`, {
53586
54331
  sessionID,
53587
54332
  model
53588
54333
  });
53589
54334
  await helpers.abortSessionRequest(sessionID, "session.status.retry-signal");
53590
54335
  sessionRetryInFlight.delete(sessionID);
53591
54336
  } else {
53592
- log(`[${HOOK_NAME8}] session.status retry skipped - retry already in flight`, { sessionID });
54337
+ log(`[${HOOK_NAME9}] session.status retry skipped - retry already in flight`, { sessionID });
53593
54338
  return;
53594
54339
  }
53595
54340
  }
53596
54341
  const resolvedAgent = await helpers.resolveAgentForSessionFromContext(sessionID, agent);
53597
54342
  const fallbackModels = getFallbackModelsForSession(sessionID, resolvedAgent, pluginConfig);
53598
54343
  if (fallbackModels.length === 0) {
53599
- if (!sessionStates.has(sessionID)) {
54344
+ if (!sessionStates2.has(sessionID)) {
53600
54345
  sessionStatusRetryKeys.delete(sessionID);
53601
54346
  }
53602
54347
  return;
53603
54348
  }
53604
- let state3 = sessionStates.get(sessionID);
54349
+ let state3 = sessionStates2.get(sessionID);
53605
54350
  if (!state3) {
53606
54351
  const initialModel = resolveFallbackBootstrapModel({
53607
54352
  sessionID,
@@ -53612,29 +54357,29 @@ function createSessionStatusHandler(deps, helpers, sessionStatusRetryKeys) {
53612
54357
  });
53613
54358
  if (!initialModel) {
53614
54359
  sessionStatusRetryKeys.delete(sessionID);
53615
- log(`[${HOOK_NAME8}] session.status retry missing model info, cannot fallback`, { sessionID });
54360
+ log(`[${HOOK_NAME9}] session.status retry missing model info, cannot fallback`, { sessionID });
53616
54361
  return;
53617
54362
  }
53618
54363
  state3 = createFallbackState(initialModel);
53619
- sessionStates.set(sessionID, state3);
54364
+ sessionStates2.set(sessionID, state3);
53620
54365
  }
53621
54366
  sessionLastAccess.set(sessionID, Date.now());
53622
54367
  if (state3.pendingFallbackModel) {
53623
54368
  if (timeoutEnabled) {
53624
- log(`[${HOOK_NAME8}] Clearing pending fallback due to provider auto-retry signal`, {
54369
+ log(`[${HOOK_NAME9}] Clearing pending fallback due to provider auto-retry signal`, {
53625
54370
  sessionID,
53626
54371
  pendingFallbackModel: state3.pendingFallbackModel
53627
54372
  });
53628
54373
  state3.pendingFallbackModel = undefined;
53629
54374
  } else {
53630
- log(`[${HOOK_NAME8}] session.status retry skipped (pending fallback in progress)`, {
54375
+ log(`[${HOOK_NAME9}] session.status retry skipped (pending fallback in progress)`, {
53631
54376
  sessionID,
53632
54377
  pendingFallbackModel: state3.pendingFallbackModel
53633
54378
  });
53634
54379
  return;
53635
54380
  }
53636
54381
  }
53637
- log(`[${HOOK_NAME8}] Detected provider auto-retry signal in session.status`, {
54382
+ log(`[${HOOK_NAME9}] Detected provider auto-retry signal in session.status`, {
53638
54383
  sessionID,
53639
54384
  model: state3.currentModel,
53640
54385
  retryAttempt: status.attempt
@@ -53652,13 +54397,13 @@ function createSessionStatusHandler(deps, helpers, sessionStatusRetryKeys) {
53652
54397
 
53653
54398
  // src/hooks/runtime-fallback/event-handler.ts
53654
54399
  function createEventHandler(deps, helpers) {
53655
- const { config: config2, pluginConfig, sessionStates, sessionLastAccess, sessionRetryInFlight, sessionAwaitingFallbackResult, sessionFallbackTimeouts, sessionStatusRetryKeys } = deps;
54400
+ const { config: config2, pluginConfig, sessionStates: sessionStates2, sessionLastAccess, sessionRetryInFlight, sessionAwaitingFallbackResult, sessionFallbackTimeouts, sessionStatusRetryKeys } = deps;
53656
54401
  const sessionStatusHandler = createSessionStatusHandler(deps, helpers, sessionStatusRetryKeys);
53657
54402
  const cancelledSessions = new Set;
53658
54403
  const resetRetryState = (sessionID) => {
53659
- const state3 = sessionStates.get(sessionID);
54404
+ const state3 = sessionStates2.get(sessionID);
53660
54405
  if (state3) {
53661
- sessionStates.set(sessionID, createFallbackState(state3.originalModel));
54406
+ sessionStates2.set(sessionID, createFallbackState(state3.originalModel));
53662
54407
  }
53663
54408
  sessionRetryInFlight.delete(sessionID);
53664
54409
  sessionAwaitingFallbackResult.delete(sessionID);
@@ -53670,8 +54415,8 @@ function createEventHandler(deps, helpers) {
53670
54415
  const sessionID = sessionInfo?.id;
53671
54416
  const model = sessionInfo?.model;
53672
54417
  if (sessionID && model) {
53673
- log(`[${HOOK_NAME8}] Session created with model`, { sessionID, model });
53674
- sessionStates.set(sessionID, createFallbackState(model));
54418
+ log(`[${HOOK_NAME9}] Session created with model`, { sessionID, model });
54419
+ sessionStates2.set(sessionID, createFallbackState(model));
53675
54420
  sessionLastAccess.set(sessionID, Date.now());
53676
54421
  }
53677
54422
  };
@@ -53679,9 +54424,9 @@ function createEventHandler(deps, helpers) {
53679
54424
  const sessionInfo = props?.info;
53680
54425
  const sessionID = sessionInfo?.id;
53681
54426
  if (sessionID) {
53682
- log(`[${HOOK_NAME8}] Cleaning up session state`, { sessionID });
54427
+ log(`[${HOOK_NAME9}] Cleaning up session state`, { sessionID });
53683
54428
  cancelledSessions.delete(sessionID);
53684
- sessionStates.delete(sessionID);
54429
+ sessionStates2.delete(sessionID);
53685
54430
  sessionLastAccess.delete(sessionID);
53686
54431
  sessionRetryInFlight.delete(sessionID);
53687
54432
  sessionAwaitingFallbackResult.delete(sessionID);
@@ -53699,7 +54444,7 @@ function createEventHandler(deps, helpers) {
53699
54444
  }
53700
54445
  cancelledSessions.add(sessionID);
53701
54446
  resetRetryState(sessionID);
53702
- log(`[${HOOK_NAME8}] Cleared fallback retry state on session.stop`, { sessionID });
54447
+ log(`[${HOOK_NAME9}] Cleared fallback retry state on session.stop`, { sessionID });
53703
54448
  };
53704
54449
  const handleMessageUpdated = (props) => {
53705
54450
  const info = props?.info;
@@ -53715,23 +54460,23 @@ function createEventHandler(deps, helpers) {
53715
54460
  return;
53716
54461
  if (cancelledSessions.has(sessionID)) {
53717
54462
  resetRetryState(sessionID);
53718
- log(`[${HOOK_NAME8}] Cleared fallback retry state for cancelled session on idle`, { sessionID });
54463
+ log(`[${HOOK_NAME9}] Cleared fallback retry state for cancelled session on idle`, { sessionID });
53719
54464
  return;
53720
54465
  }
53721
54466
  if (sessionAwaitingFallbackResult.has(sessionID)) {
53722
- log(`[${HOOK_NAME8}] session.idle while awaiting fallback result; keeping timeout armed`, { sessionID });
54467
+ log(`[${HOOK_NAME9}] session.idle while awaiting fallback result; keeping timeout armed`, { sessionID });
53723
54468
  return;
53724
54469
  }
53725
54470
  const hadTimeout = sessionFallbackTimeouts.has(sessionID);
53726
54471
  helpers.clearSessionFallbackTimeout(sessionID);
53727
54472
  sessionRetryInFlight.delete(sessionID);
53728
54473
  sessionStatusRetryKeys.delete(sessionID);
53729
- const state3 = sessionStates.get(sessionID);
54474
+ const state3 = sessionStates2.get(sessionID);
53730
54475
  if (state3?.pendingFallbackModel) {
53731
54476
  state3.pendingFallbackModel = undefined;
53732
54477
  }
53733
54478
  if (hadTimeout) {
53734
- log(`[${HOOK_NAME8}] Cleared fallback timeout after session completion`, { sessionID });
54479
+ log(`[${HOOK_NAME9}] Cleared fallback timeout after session completion`, { sessionID });
53735
54480
  }
53736
54481
  };
53737
54482
  const handleSessionError = async (props) => {
@@ -53739,18 +54484,18 @@ function createEventHandler(deps, helpers) {
53739
54484
  const error51 = props?.error;
53740
54485
  const agent = props?.agent;
53741
54486
  if (!sessionID) {
53742
- log(`[${HOOK_NAME8}] session.error without sessionID, skipping`);
54487
+ log(`[${HOOK_NAME9}] session.error without sessionID, skipping`);
53743
54488
  return;
53744
54489
  }
53745
54490
  const resolvedAgent = await helpers.resolveAgentForSessionFromContext(sessionID, agent);
53746
54491
  if (isAbortError2(error51)) {
53747
54492
  cancelledSessions.add(sessionID);
53748
54493
  resetRetryState(sessionID);
53749
- log(`[${HOOK_NAME8}] session.error matched cancellation; cleared retry state`, { sessionID, resolvedAgent });
54494
+ log(`[${HOOK_NAME9}] session.error matched cancellation; cleared retry state`, { sessionID, resolvedAgent });
53750
54495
  return;
53751
54496
  }
53752
54497
  if (sessionRetryInFlight.has(sessionID)) {
53753
- log(`[${HOOK_NAME8}] session.error skipped - retry in flight`, {
54498
+ log(`[${HOOK_NAME9}] session.error skipped - retry in flight`, {
53754
54499
  sessionID,
53755
54500
  retryInFlight: true
53756
54501
  });
@@ -53758,7 +54503,7 @@ function createEventHandler(deps, helpers) {
53758
54503
  }
53759
54504
  sessionAwaitingFallbackResult.delete(sessionID);
53760
54505
  helpers.clearSessionFallbackTimeout(sessionID);
53761
- log(`[${HOOK_NAME8}] session.error received`, {
54506
+ log(`[${HOOK_NAME9}] session.error received`, {
53762
54507
  sessionID,
53763
54508
  agent,
53764
54509
  resolvedAgent,
@@ -53767,7 +54512,7 @@ function createEventHandler(deps, helpers) {
53767
54512
  errorType: classifyErrorType(error51)
53768
54513
  });
53769
54514
  if (!isRetryableError(error51, config2.retry_on_errors)) {
53770
- log(`[${HOOK_NAME8}] Error not retryable, skipping fallback`, {
54515
+ log(`[${HOOK_NAME9}] Error not retryable, skipping fallback`, {
53771
54516
  sessionID,
53772
54517
  retryable: false,
53773
54518
  statusCode: extractStatusCode(error51, config2.retry_on_errors),
@@ -53776,10 +54521,10 @@ function createEventHandler(deps, helpers) {
53776
54521
  });
53777
54522
  return;
53778
54523
  }
53779
- let state3 = sessionStates.get(sessionID);
54524
+ let state3 = sessionStates2.get(sessionID);
53780
54525
  const fallbackModels = getFallbackModelsForSession(sessionID, resolvedAgent, pluginConfig);
53781
54526
  if (fallbackModels.length === 0) {
53782
- log(`[${HOOK_NAME8}] No fallback models configured`, { sessionID, agent });
54527
+ log(`[${HOOK_NAME9}] No fallback models configured`, { sessionID, agent });
53783
54528
  return;
53784
54529
  }
53785
54530
  if (!state3) {
@@ -53791,11 +54536,11 @@ function createEventHandler(deps, helpers) {
53791
54536
  pluginConfig
53792
54537
  });
53793
54538
  if (!initialModel) {
53794
- log(`[${HOOK_NAME8}] No model info available, cannot fallback`, { sessionID });
54539
+ log(`[${HOOK_NAME9}] No model info available, cannot fallback`, { sessionID });
53795
54540
  return;
53796
54541
  }
53797
54542
  state3 = createFallbackState(initialModel);
53798
- sessionStates.set(sessionID, state3);
54543
+ sessionStates2.set(sessionID, state3);
53799
54544
  sessionLastAccess.set(sessionID, Date.now());
53800
54545
  } else {
53801
54546
  sessionLastAccess.set(sessionID, Date.now());
@@ -53907,7 +54652,7 @@ function hasVisibleAssistantResponse(extractAutoRetrySignalFn) {
53907
54652
 
53908
54653
  // src/hooks/runtime-fallback/message-update-handler.ts
53909
54654
  function createMessageUpdateHandler(deps, helpers) {
53910
- const { ctx, config: config2, pluginConfig, sessionStates, sessionLastAccess, sessionRetryInFlight, sessionAwaitingFallbackResult, sessionStatusRetryKeys } = deps;
54655
+ const { ctx, config: config2, pluginConfig, sessionStates: sessionStates2, sessionLastAccess, sessionRetryInFlight, sessionAwaitingFallbackResult, sessionStatusRetryKeys } = deps;
53911
54656
  const checkVisibleResponse = hasVisibleAssistantResponse(extractAutoRetrySignal);
53912
54657
  return async (props) => {
53913
54658
  const info = props?.info;
@@ -53931,7 +54676,7 @@ function createMessageUpdateHandler(deps, helpers) {
53931
54676
  }
53932
54677
  const hasVisible = await checkVisibleResponse(ctx, sessionID, info);
53933
54678
  if (!hasVisible) {
53934
- log(`[${HOOK_NAME8}] Assistant update observed without visible final response; keeping fallback timeout`, {
54679
+ log(`[${HOOK_NAME9}] Assistant update observed without visible final response; keeping fallback timeout`, {
53935
54680
  sessionID,
53936
54681
  model
53937
54682
  });
@@ -53940,21 +54685,21 @@ function createMessageUpdateHandler(deps, helpers) {
53940
54685
  sessionAwaitingFallbackResult.delete(sessionID);
53941
54686
  sessionStatusRetryKeys.delete(sessionID);
53942
54687
  helpers.clearSessionFallbackTimeout(sessionID);
53943
- const state3 = sessionStates.get(sessionID);
54688
+ const state3 = sessionStates2.get(sessionID);
53944
54689
  if (state3?.pendingFallbackModel) {
53945
54690
  state3.pendingFallbackModel = undefined;
53946
54691
  }
53947
- log(`[${HOOK_NAME8}] Assistant response observed; cleared fallback timeout`, { sessionID, model });
54692
+ log(`[${HOOK_NAME9}] Assistant response observed; cleared fallback timeout`, { sessionID, model });
53948
54693
  return;
53949
54694
  }
53950
54695
  if (sessionID && role === "assistant" && error51) {
53951
54696
  sessionAwaitingFallbackResult.delete(sessionID);
53952
54697
  if (sessionRetryInFlight.has(sessionID) && !retrySignal) {
53953
- log(`[${HOOK_NAME8}] message.updated fallback skipped (retry in flight)`, { sessionID });
54698
+ log(`[${HOOK_NAME9}] message.updated fallback skipped (retry in flight)`, { sessionID });
53954
54699
  return;
53955
54700
  }
53956
54701
  if (retrySignal && sessionRetryInFlight.has(sessionID) && timeoutEnabled) {
53957
- log(`[${HOOK_NAME8}] Overriding in-flight retry due to provider auto-retry signal`, {
54702
+ log(`[${HOOK_NAME9}] Overriding in-flight retry due to provider auto-retry signal`, {
53958
54703
  sessionID,
53959
54704
  model
53960
54705
  });
@@ -53962,12 +54707,12 @@ function createMessageUpdateHandler(deps, helpers) {
53962
54707
  sessionRetryInFlight.delete(sessionID);
53963
54708
  }
53964
54709
  if (retrySignal && timeoutEnabled) {
53965
- log(`[${HOOK_NAME8}] Detected provider auto-retry signal`, { sessionID, model });
54710
+ log(`[${HOOK_NAME9}] Detected provider auto-retry signal`, { sessionID, model });
53966
54711
  }
53967
54712
  if (!retrySignal) {
53968
54713
  helpers.clearSessionFallbackTimeout(sessionID);
53969
54714
  }
53970
- log(`[${HOOK_NAME8}] message.updated with assistant error`, {
54715
+ log(`[${HOOK_NAME9}] message.updated with assistant error`, {
53971
54716
  sessionID,
53972
54717
  model,
53973
54718
  statusCode: extractStatusCode(error51, config2.retry_on_errors),
@@ -53975,7 +54720,7 @@ function createMessageUpdateHandler(deps, helpers) {
53975
54720
  errorType: classifyErrorType(error51)
53976
54721
  });
53977
54722
  if (!isRetryableError(error51, config2.retry_on_errors)) {
53978
- log(`[${HOOK_NAME8}] message.updated error not retryable, skipping fallback`, {
54723
+ log(`[${HOOK_NAME9}] message.updated error not retryable, skipping fallback`, {
53979
54724
  sessionID,
53980
54725
  statusCode: extractStatusCode(error51, config2.retry_on_errors),
53981
54726
  errorName: extractErrorName(error51),
@@ -53983,7 +54728,7 @@ function createMessageUpdateHandler(deps, helpers) {
53983
54728
  });
53984
54729
  return;
53985
54730
  }
53986
- let state3 = sessionStates.get(sessionID);
54731
+ let state3 = sessionStates2.get(sessionID);
53987
54732
  const agent = info?.agent;
53988
54733
  const resolvedAgent = await helpers.resolveAgentForSessionFromContext(sessionID, agent);
53989
54734
  const fallbackModels = getFallbackModelsForSession(sessionID, resolvedAgent, pluginConfig);
@@ -53999,7 +54744,7 @@ function createMessageUpdateHandler(deps, helpers) {
53999
54744
  pluginConfig
54000
54745
  });
54001
54746
  if (!initialModel) {
54002
- log(`[${HOOK_NAME8}] message.updated missing model info, cannot fallback`, {
54747
+ log(`[${HOOK_NAME9}] message.updated missing model info, cannot fallback`, {
54003
54748
  sessionID,
54004
54749
  errorName: extractErrorName(error51),
54005
54750
  errorType: classifyErrorType(error51)
@@ -54007,19 +54752,19 @@ function createMessageUpdateHandler(deps, helpers) {
54007
54752
  return;
54008
54753
  }
54009
54754
  state3 = createFallbackState(initialModel);
54010
- sessionStates.set(sessionID, state3);
54755
+ sessionStates2.set(sessionID, state3);
54011
54756
  sessionLastAccess.set(sessionID, Date.now());
54012
54757
  } else {
54013
54758
  sessionLastAccess.set(sessionID, Date.now());
54014
54759
  if (state3.pendingFallbackModel) {
54015
54760
  if (retrySignal && timeoutEnabled) {
54016
- log(`[${HOOK_NAME8}] Clearing pending fallback due to provider auto-retry signal`, {
54761
+ log(`[${HOOK_NAME9}] Clearing pending fallback due to provider auto-retry signal`, {
54017
54762
  sessionID,
54018
54763
  pendingFallbackModel: state3.pendingFallbackModel
54019
54764
  });
54020
54765
  state3.pendingFallbackModel = undefined;
54021
54766
  } else {
54022
- log(`[${HOOK_NAME8}] message.updated fallback skipped (pending fallback in progress)`, {
54767
+ log(`[${HOOK_NAME9}] message.updated fallback skipped (pending fallback in progress)`, {
54023
54768
  sessionID,
54024
54769
  pendingFallbackModel: state3.pendingFallbackModel
54025
54770
  });
@@ -54363,6 +55108,7 @@ function createIdleNotificationScheduler(options) {
54363
55108
  }
54364
55109
 
54365
55110
  // src/hooks/session-notification-sender.ts
55111
+ init_logger();
54366
55112
  import { platform } from "os";
54367
55113
 
54368
55114
  // src/hooks/session-notification-formatting.ts
@@ -54390,6 +55136,7 @@ $Notifier.Show($Toast)
54390
55136
  }
54391
55137
 
54392
55138
  // src/hooks/session-notification-utils.ts
55139
+ init_logger();
54393
55140
  async function findCommand2(commandName) {
54394
55141
  try {
54395
55142
  return Bun.which(commandName);
@@ -54421,14 +55168,26 @@ var getPaplayPath = createCommandFinder("paplay");
54421
55168
  var getAplayPath = createCommandFinder("aplay");
54422
55169
  function startBackgroundCheck2(platform) {
54423
55170
  if (platform === "darwin") {
54424
- getOsascriptPath().catch(() => {});
54425
- getAfplayPath().catch(() => {});
55171
+ getOsascriptPath().catch((err) => {
55172
+ log("[notification] Background check failed for osascript:", err);
55173
+ });
55174
+ getAfplayPath().catch((err) => {
55175
+ log("[notification] Background check failed for afplay:", err);
55176
+ });
54426
55177
  } else if (platform === "linux") {
54427
- getNotifySendPath().catch(() => {});
54428
- getPaplayPath().catch(() => {});
54429
- getAplayPath().catch(() => {});
55178
+ getNotifySendPath().catch((err) => {
55179
+ log("[notification] Background check failed for notify-send:", err);
55180
+ });
55181
+ getPaplayPath().catch((err) => {
55182
+ log("[notification] Background check failed for paplay:", err);
55183
+ });
55184
+ getAplayPath().catch((err) => {
55185
+ log("[notification] Background check failed for aplay:", err);
55186
+ });
54430
55187
  } else if (platform === "win32") {
54431
- getPowershellPath().catch(() => {});
55188
+ getPowershellPath().catch((err) => {
55189
+ log("[notification] Background check failed for powershell:", err);
55190
+ });
54432
55191
  }
54433
55192
  }
54434
55193
 
@@ -54459,14 +55218,18 @@ async function sendSessionNotification(ctx, platform2, title, message) {
54459
55218
  return;
54460
55219
  const escapedTitle = escapeAppleScriptText(title);
54461
55220
  const escapedMessage = escapeAppleScriptText(message);
54462
- await ctx.$`${osascriptPath} -e ${`display notification "${escapedMessage}" with title "${escapedTitle}"`}`.catch(() => {});
55221
+ await ctx.$`${osascriptPath} -e ${`display notification "${escapedMessage}" with title "${escapedTitle}"`}`.catch((err) => {
55222
+ log("[notification] macOS notification failed:", err);
55223
+ });
54463
55224
  break;
54464
55225
  }
54465
55226
  case "linux": {
54466
55227
  const notifySendPath = await getNotifySendPath();
54467
55228
  if (!notifySendPath)
54468
55229
  return;
54469
- await ctx.$`${notifySendPath} ${title} ${message} 2>/dev/null`.catch(() => {});
55230
+ await ctx.$`${notifySendPath} ${title} ${message} 2>/dev/null`.catch((err) => {
55231
+ log("[notification] Linux notification failed:", err);
55232
+ });
54470
55233
  break;
54471
55234
  }
54472
55235
  case "win32": {
@@ -54474,7 +55237,9 @@ async function sendSessionNotification(ctx, platform2, title, message) {
54474
55237
  if (!powershellPath)
54475
55238
  return;
54476
55239
  const toastScript = buildWindowsToastScript(title, message);
54477
- await ctx.$`${powershellPath} -Command ${toastScript}`.catch(() => {});
55240
+ await ctx.$`${powershellPath} -Command ${toastScript}`.catch((err) => {
55241
+ log("[notification] Windows notification failed:", err);
55242
+ });
54478
55243
  break;
54479
55244
  }
54480
55245
  }
@@ -54485,17 +55250,23 @@ async function playSessionNotificationSound(ctx, platform2, soundPath) {
54485
55250
  const afplayPath = await getAfplayPath();
54486
55251
  if (!afplayPath)
54487
55252
  return;
54488
- ctx.$`${afplayPath} ${soundPath}`.catch(() => {});
55253
+ ctx.$`${afplayPath} ${soundPath}`.catch((err) => {
55254
+ log("[notification] macOS sound playback failed:", err);
55255
+ });
54489
55256
  break;
54490
55257
  }
54491
55258
  case "linux": {
54492
55259
  const paplayPath = await getPaplayPath();
54493
55260
  if (paplayPath) {
54494
- ctx.$`${paplayPath} ${soundPath} 2>/dev/null`.catch(() => {});
55261
+ ctx.$`${paplayPath} ${soundPath} 2>/dev/null`.catch((err) => {
55262
+ log("[notification] Linux sound playback (paplay) failed:", err);
55263
+ });
54495
55264
  } else {
54496
55265
  const aplayPath = await getAplayPath();
54497
55266
  if (aplayPath) {
54498
- ctx.$`${aplayPath} ${soundPath} 2>/dev/null`.catch(() => {});
55267
+ ctx.$`${aplayPath} ${soundPath} 2>/dev/null`.catch((err) => {
55268
+ log("[notification] Linux sound playback (aplay) failed:", err);
55269
+ });
54499
55270
  }
54500
55271
  }
54501
55272
  break;
@@ -54505,7 +55276,9 @@ async function playSessionNotificationSound(ctx, platform2, soundPath) {
54505
55276
  if (!powershellPath)
54506
55277
  return;
54507
55278
  const escaped = escapePowerShellSingleQuotedText(soundPath);
54508
- ctx.$`${powershellPath} -Command ${`(New-Object Media.SoundPlayer '${escaped}').PlaySync()`}`.catch(() => {});
55279
+ ctx.$`${powershellPath} -Command ${`(New-Object Media.SoundPlayer '${escaped}').PlaySync()`}`.catch((err) => {
55280
+ log("[notification] Windows sound playback failed:", err);
55281
+ });
54509
55282
  break;
54510
55283
  }
54511
55284
  }
@@ -54923,7 +55696,9 @@ function createSessionRecoveryHook(ctx, options) {
54923
55696
  if (onAbortCallback) {
54924
55697
  onAbortCallback(sessionID);
54925
55698
  }
54926
- await ctx.client.session.abort({ path: { id: sessionID } }).catch(() => {});
55699
+ await ctx.client.session.abort({ path: { id: sessionID } }).catch((err) => {
55700
+ log("[session-recovery] Session abort failed:", err);
55701
+ });
54927
55702
  const messagesResp = await ctx.client.session.messages({
54928
55703
  path: { id: sessionID },
54929
55704
  query: { directory: ctx.directory }
@@ -54952,7 +55727,9 @@ function createSessionRecoveryHook(ctx, options) {
54952
55727
  variant: "warning",
54953
55728
  duration: 3000
54954
55729
  }
54955
- }).catch(() => {});
55730
+ }).catch((err) => {
55731
+ log("[session-recovery] Recovery toast failed:", err);
55732
+ });
54956
55733
  let success2 = false;
54957
55734
  if (errorType === "tool_result_missing") {
54958
55735
  success2 = await recoverToolResultMissing(ctx.client, sessionID, failedMsg);
@@ -54993,7 +55770,7 @@ function createSessionRecoveryHook(ctx, options) {
54993
55770
  }
54994
55771
  // src/hooks/start-work/start-work-hook.ts
54995
55772
  init_logger();
54996
- var HOOK_NAME9 = "start-work";
55773
+ var HOOK_NAME10 = "start-work";
54997
55774
  var KEYWORD_PATTERN = /\b(ultrawork|ulw)\b/gi;
54998
55775
  function extractUserRequestPlanName(promptText) {
54999
55776
  const userRequestMatch = promptText.match(/<user-request>\s*([\s\S]*?)\s*<\/user-request>/i);
@@ -55023,7 +55800,7 @@ function createStartWorkHook(ctx) {
55023
55800
  if (!isStartWorkCommand) {
55024
55801
  return;
55025
55802
  }
55026
- log(`[${HOOK_NAME9}] Processing start-work command`, {
55803
+ log(`[${HOOK_NAME10}] Processing start-work command`, {
55027
55804
  sessionID: input.sessionID
55028
55805
  });
55029
55806
  updateSessionAgent(input.sessionID, "architect");
@@ -55033,7 +55810,7 @@ function createStartWorkHook(ctx) {
55033
55810
  let contextInfo = "";
55034
55811
  const explicitPlanName = extractUserRequestPlanName(promptText);
55035
55812
  if (explicitPlanName) {
55036
- log(`[${HOOK_NAME9}] Explicit plan name requested: ${explicitPlanName}`, {
55813
+ log(`[${HOOK_NAME10}] Explicit plan name requested: ${explicitPlanName}`, {
55037
55814
  sessionID: input.sessionID
55038
55815
  });
55039
55816
  const allPlans = findOraclePlans(ctx.directory);
@@ -55174,7 +55951,7 @@ Ask the user which plan to work on. Present the options above and wait for their
55174
55951
  ---
55175
55952
  ${contextInfo}`;
55176
55953
  }
55177
- log(`[${HOOK_NAME9}] Context injected`, {
55954
+ log(`[${HOOK_NAME10}] Context injected`, {
55178
55955
  sessionID: input.sessionID,
55179
55956
  hasExistingState: !!existingState
55180
55957
  });
@@ -55183,16 +55960,16 @@ ${contextInfo}`;
55183
55960
  }
55184
55961
  // src/hooks/stop-continuation-guard/hook.ts
55185
55962
  init_logger();
55186
- var HOOK_NAME10 = "stop-continuation-guard";
55963
+ var HOOK_NAME11 = "stop-continuation-guard";
55187
55964
  function createStopContinuationGuardHook(_ctx, options) {
55188
55965
  const stoppedSessions = new Set;
55189
55966
  const stop = (sessionID) => {
55190
55967
  stoppedSessions.add(sessionID);
55191
- log(`[${HOOK_NAME10}] Continuation stopped for session`, { sessionID });
55968
+ log(`[${HOOK_NAME11}] Continuation stopped for session`, { sessionID });
55192
55969
  if (options?.backgroundManager) {
55193
55970
  const cancelled = options.backgroundManager.cancelAllForSession(sessionID);
55194
55971
  if (cancelled > 0) {
55195
- log(`[${HOOK_NAME10}] Cancelled ${cancelled} background task(s) for session`, { sessionID });
55972
+ log(`[${HOOK_NAME11}] Cancelled ${cancelled} background task(s) for session`, { sessionID });
55196
55973
  }
55197
55974
  }
55198
55975
  };
@@ -55201,7 +55978,7 @@ function createStopContinuationGuardHook(_ctx, options) {
55201
55978
  };
55202
55979
  const clear = (sessionID) => {
55203
55980
  stoppedSessions.delete(sessionID);
55204
- log(`[${HOOK_NAME10}] Continuation guard cleared for session`, { sessionID });
55981
+ log(`[${HOOK_NAME11}] Continuation guard cleared for session`, { sessionID });
55205
55982
  };
55206
55983
  const event = async ({
55207
55984
  event: event2
@@ -55211,7 +55988,7 @@ function createStopContinuationGuardHook(_ctx, options) {
55211
55988
  const sessionInfo = props?.info;
55212
55989
  if (sessionInfo?.id) {
55213
55990
  clear(sessionInfo.id);
55214
- log(`[${HOOK_NAME10}] Session deleted: cleaned up`, { sessionID: sessionInfo.id });
55991
+ log(`[${HOOK_NAME11}] Session deleted: cleaned up`, { sessionID: sessionInfo.id });
55215
55992
  }
55216
55993
  }
55217
55994
  };
@@ -55220,7 +55997,7 @@ function createStopContinuationGuardHook(_ctx, options) {
55220
55997
  }) => {
55221
55998
  if (sessionID && stoppedSessions.has(sessionID)) {
55222
55999
  clear(sessionID);
55223
- log(`[${HOOK_NAME10}] Cleared stop state on new user message`, { sessionID });
56000
+ log(`[${HOOK_NAME11}] Cleared stop state on new user message`, { sessionID });
55224
56001
  }
55225
56002
  };
55226
56003
  return {
@@ -55232,7 +56009,7 @@ function createStopContinuationGuardHook(_ctx, options) {
55232
56009
  };
55233
56010
  }
55234
56011
  // src/hooks/task-notepad/constants.ts
55235
- var HOOK_NAME11 = "task-notepad";
56012
+ var HOOK_NAME12 = "task-notepad";
55236
56013
  var PLAN_DIR_NAME = ".matrixx";
55237
56014
  var PLANS_SUBDIR = "plans";
55238
56015
  var NOTEPAD_DIR_NAME = "notepads";
@@ -55261,7 +56038,7 @@ function createTaskNotepadHook(ctx) {
55261
56038
  const plan = resolveActivePlan(ctx.directory);
55262
56039
  if (inProgress) {
55263
56040
  if (!plan) {
55264
- log(`[${HOOK_NAME11}] No active plan, skipping notepad creation`, {
56041
+ log(`[${HOOK_NAME12}] No active plan, skipping notepad creation`, {
55265
56042
  sessionID: input.sessionID
55266
56043
  });
55267
56044
  } else {
@@ -55288,7 +56065,7 @@ async function fetchTodos(ctx, sessionID) {
55288
56065
  }
55289
56066
  return [];
55290
56067
  } catch (err) {
55291
- log(`[${HOOK_NAME11}] ${TODO_FETCH_ERROR_FRAGMENT}`, {
56068
+ log(`[${HOOK_NAME12}] ${TODO_FETCH_ERROR_FRAGMENT}`, {
55292
56069
  sessionID,
55293
56070
  error: String(err)
55294
56071
  });
@@ -55333,7 +56110,7 @@ function ensureNotepad(directory, planName, todo) {
55333
56110
  mkdirSync12(dirname15(notepadPath), { recursive: true });
55334
56111
  const content = renderNotepad(todo);
55335
56112
  writeFileSync16(notepadPath, content, "utf-8");
55336
- log(`[${HOOK_NAME11}] Created task notepad`, { plan: planName, taskID: todo.id });
56113
+ log(`[${HOOK_NAME12}] Created task notepad`, { plan: planName, taskID: todo.id });
55337
56114
  }
55338
56115
  function appendCompletion(directory, planName, todo) {
55339
56116
  const notepadPath = notepadPathFor(directory, planName, todo.id);
@@ -55349,7 +56126,7 @@ function appendCompletion(directory, planName, todo) {
55349
56126
  - status: completed
55350
56127
  `;
55351
56128
  writeFileSync16(notepadPath, updated, "utf-8");
55352
- log(`[${HOOK_NAME11}] Appended completion to notepad`, { plan: planName, taskID: todo.id });
56129
+ log(`[${HOOK_NAME12}] Appended completion to notepad`, { plan: planName, taskID: todo.id });
55353
56130
  }
55354
56131
  function notepadPathFor(directory, planName, taskID) {
55355
56132
  return join54(directory, PLAN_DIR_NAME, NOTEPAD_DIR_NAME, planName, TASKS_SUBDIR, `${taskID}${NOTEPAD_EXTENSION}`);
@@ -55931,7 +56708,7 @@ function createThinkingBlockValidatorHook() {
55931
56708
  init_logger();
55932
56709
 
55933
56710
  // src/hooks/todo-continuation-enforcer/constants.ts
55934
- var HOOK_NAME12 = "todo-continuation-enforcer";
56711
+ var HOOK_NAME13 = "todo-continuation-enforcer";
55935
56712
  var DEFAULT_SKIP_AGENTS = ["oracle", "compaction"];
55936
56713
  var CONTINUATION_PROMPT2 = `${createSystemDirective(SystemDirectiveTypes.TODO_CONTINUATION)}
55937
56714
 
@@ -55997,12 +56774,12 @@ async function injectContinuation(args) {
55997
56774
  } = args;
55998
56775
  const state3 = sessionStateStore.getExistingState(sessionID);
55999
56776
  if (state3?.isRecovering) {
56000
- log(`[${HOOK_NAME12}] Skipped injection: in recovery`, { sessionID });
56777
+ log(`[${HOOK_NAME13}] Skipped injection: in recovery`, { sessionID });
56001
56778
  return;
56002
56779
  }
56003
56780
  const hasRunningBgTasks = backgroundManager ? backgroundManager.getTasksByParentSession(sessionID).some((task) => task.status === "running") : false;
56004
56781
  if (hasRunningBgTasks) {
56005
- log(`[${HOOK_NAME12}] Skipped injection: background tasks running`, { sessionID });
56782
+ log(`[${HOOK_NAME13}] Skipped injection: background tasks running`, { sessionID });
56006
56783
  return;
56007
56784
  }
56008
56785
  let todos = [];
@@ -56010,12 +56787,12 @@ async function injectContinuation(args) {
56010
56787
  const response = await ctx.client.session.todo({ path: { id: sessionID } });
56011
56788
  todos = normalizeSDKResponse(response, [], { preferResponseOnMissingData: true });
56012
56789
  } catch (error51) {
56013
- log(`[${HOOK_NAME12}] Failed to fetch todos`, { sessionID, error: String(error51) });
56790
+ log(`[${HOOK_NAME13}] Failed to fetch todos`, { sessionID, error: String(error51) });
56014
56791
  return;
56015
56792
  }
56016
56793
  const freshIncompleteCount = getIncompleteCount(todos);
56017
56794
  if (freshIncompleteCount === 0) {
56018
- log(`[${HOOK_NAME12}] Skipped injection: no incomplete todos`, { sessionID });
56795
+ log(`[${HOOK_NAME13}] Skipped injection: no incomplete todos`, { sessionID });
56019
56796
  return;
56020
56797
  }
56021
56798
  let agentName = resolvedInfo?.agent;
@@ -56038,11 +56815,11 @@ async function injectContinuation(args) {
56038
56815
  tools = tools ?? previousMessage?.tools;
56039
56816
  }
56040
56817
  if (agentName && skipAgents.some((s) => getAgentConfigKey(s) === getAgentConfigKey(agentName))) {
56041
- log(`[${HOOK_NAME12}] Skipped: agent in skipAgents list`, { sessionID, agent: agentName });
56818
+ log(`[${HOOK_NAME13}] Skipped: agent in skipAgents list`, { sessionID, agent: agentName });
56042
56819
  return;
56043
56820
  }
56044
56821
  if (!hasWritePermission(tools)) {
56045
- log(`[${HOOK_NAME12}] Skipped: agent lacks write permission`, { sessionID, agent: agentName });
56822
+ log(`[${HOOK_NAME13}] Skipped: agent lacks write permission`, { sessionID, agent: agentName });
56046
56823
  return;
56047
56824
  }
56048
56825
  const incompleteTodos = todos.filter((todo) => todo.status !== "completed" && todo.status !== "cancelled");
@@ -56059,7 +56836,7 @@ ${todoList}`;
56059
56836
  injectionState.inFlight = true;
56060
56837
  }
56061
56838
  try {
56062
- log(`[${HOOK_NAME12}] Injecting continuation`, {
56839
+ log(`[${HOOK_NAME13}] Injecting continuation`, {
56063
56840
  sessionID,
56064
56841
  agent: agentName,
56065
56842
  model,
@@ -56074,14 +56851,14 @@ ${todoList}`;
56074
56851
  },
56075
56852
  query: { directory: ctx.directory }
56076
56853
  });
56077
- log(`[${HOOK_NAME12}] Injection successful`, { sessionID });
56854
+ log(`[${HOOK_NAME13}] Injection successful`, { sessionID });
56078
56855
  if (injectionState) {
56079
56856
  injectionState.inFlight = false;
56080
56857
  injectionState.lastInjectedAt = Date.now();
56081
56858
  injectionState.consecutiveFailures = 0;
56082
56859
  }
56083
56860
  } catch (error51) {
56084
- log(`[${HOOK_NAME12}] Injection failed`, { sessionID, error: String(error51) });
56861
+ log(`[${HOOK_NAME13}] Injection failed`, { sessionID, error: String(error51) });
56085
56862
  if (injectionState) {
56086
56863
  injectionState.inFlight = false;
56087
56864
  injectionState.lastInjectedAt = Date.now();
@@ -56099,7 +56876,9 @@ async function showCountdownToast(ctx, seconds, incompleteCount) {
56099
56876
  variant: "warning",
56100
56877
  duration: TOAST_DURATION_MS
56101
56878
  }
56102
- }).catch(() => {});
56879
+ }).catch((err) => {
56880
+ log("[todo-continuation] Countdown toast failed:", err);
56881
+ });
56103
56882
  }
56104
56883
  function startCountdown(args) {
56105
56884
  const {
@@ -56133,7 +56912,7 @@ function startCountdown(args) {
56133
56912
  sessionStateStore
56134
56913
  });
56135
56914
  }, COUNTDOWN_SECONDS * 1000);
56136
- log(`[${HOOK_NAME12}] Countdown started`, {
56915
+ log(`[${HOOK_NAME13}] Countdown started`, {
56137
56916
  sessionID,
56138
56917
  seconds: COUNTDOWN_SECONDS,
56139
56918
  incompleteCount
@@ -56150,16 +56929,16 @@ async function handleSessionIdle(args) {
56150
56929
  skipAgents = DEFAULT_SKIP_AGENTS,
56151
56930
  isContinuationStopped
56152
56931
  } = args;
56153
- log(`[${HOOK_NAME12}] session.idle`, { sessionID });
56932
+ log(`[${HOOK_NAME13}] session.idle`, { sessionID });
56154
56933
  const state3 = sessionStateStore.getState(sessionID);
56155
56934
  if (state3.isRecovering) {
56156
- log(`[${HOOK_NAME12}] Skipped: in recovery`, { sessionID });
56935
+ log(`[${HOOK_NAME13}] Skipped: in recovery`, { sessionID });
56157
56936
  return;
56158
56937
  }
56159
56938
  if (state3.abortDetectedAt) {
56160
56939
  const timeSinceAbort = Date.now() - state3.abortDetectedAt;
56161
56940
  if (timeSinceAbort < ABORT_WINDOW_MS) {
56162
- log(`[${HOOK_NAME12}] Skipped: abort detected via event ${timeSinceAbort}ms ago`, { sessionID });
56941
+ log(`[${HOOK_NAME13}] Skipped: abort detected via event ${timeSinceAbort}ms ago`, { sessionID });
56163
56942
  state3.abortDetectedAt = undefined;
56164
56943
  return;
56165
56944
  }
@@ -56167,7 +56946,7 @@ async function handleSessionIdle(args) {
56167
56946
  }
56168
56947
  const hasRunningBgTasks = backgroundManager ? backgroundManager.getTasksByParentSession(sessionID).some((task) => task.status === "running") : false;
56169
56948
  if (hasRunningBgTasks) {
56170
- log(`[${HOOK_NAME12}] Skipped: background tasks running`, { sessionID });
56949
+ log(`[${HOOK_NAME13}] Skipped: background tasks running`, { sessionID });
56171
56950
  return;
56172
56951
  }
56173
56952
  try {
@@ -56177,42 +56956,42 @@ async function handleSessionIdle(args) {
56177
56956
  });
56178
56957
  const messages = normalizeSDKResponse(messagesResp, []);
56179
56958
  if (isLastAssistantMessageAborted(messages)) {
56180
- log(`[${HOOK_NAME12}] Skipped: last assistant message was aborted (API fallback)`, { sessionID });
56959
+ log(`[${HOOK_NAME13}] Skipped: last assistant message was aborted (API fallback)`, { sessionID });
56181
56960
  return;
56182
56961
  }
56183
56962
  } catch (error51) {
56184
- log(`[${HOOK_NAME12}] Messages fetch failed, continuing`, { sessionID, error: String(error51) });
56963
+ log(`[${HOOK_NAME13}] Messages fetch failed, continuing`, { sessionID, error: String(error51) });
56185
56964
  }
56186
56965
  let todos = [];
56187
56966
  try {
56188
56967
  const response = await ctx.client.session.todo({ path: { id: sessionID } });
56189
56968
  todos = normalizeSDKResponse(response, [], { preferResponseOnMissingData: true });
56190
56969
  } catch (error51) {
56191
- log(`[${HOOK_NAME12}] Todo fetch failed`, { sessionID, error: String(error51) });
56970
+ log(`[${HOOK_NAME13}] Todo fetch failed`, { sessionID, error: String(error51) });
56192
56971
  return;
56193
56972
  }
56194
56973
  if (!todos || todos.length === 0) {
56195
- log(`[${HOOK_NAME12}] No todos`, { sessionID });
56974
+ log(`[${HOOK_NAME13}] No todos`, { sessionID });
56196
56975
  return;
56197
56976
  }
56198
56977
  const incompleteCount = getIncompleteCount(todos);
56199
56978
  if (incompleteCount === 0) {
56200
- log(`[${HOOK_NAME12}] All todos complete`, { sessionID, total: todos.length });
56979
+ log(`[${HOOK_NAME13}] All todos complete`, { sessionID, total: todos.length });
56201
56980
  return;
56202
56981
  }
56203
56982
  if (state3.inFlight) {
56204
- log(`[${HOOK_NAME12}] Skipped: injection in flight`, { sessionID });
56983
+ log(`[${HOOK_NAME13}] Skipped: injection in flight`, { sessionID });
56205
56984
  return;
56206
56985
  }
56207
56986
  if (state3.consecutiveFailures >= MAX_CONSECUTIVE_FAILURES && state3.lastInjectedAt && Date.now() - state3.lastInjectedAt >= FAILURE_RESET_WINDOW_MS) {
56208
56987
  state3.consecutiveFailures = 0;
56209
- log(`[${HOOK_NAME12}] Reset consecutive failures after recovery window`, {
56988
+ log(`[${HOOK_NAME13}] Reset consecutive failures after recovery window`, {
56210
56989
  sessionID,
56211
56990
  failureResetWindowMs: FAILURE_RESET_WINDOW_MS
56212
56991
  });
56213
56992
  }
56214
56993
  if (state3.consecutiveFailures >= MAX_CONSECUTIVE_FAILURES) {
56215
- log(`[${HOOK_NAME12}] Skipped: max consecutive failures reached`, {
56994
+ log(`[${HOOK_NAME13}] Skipped: max consecutive failures reached`, {
56216
56995
  sessionID,
56217
56996
  consecutiveFailures: state3.consecutiveFailures,
56218
56997
  maxConsecutiveFailures: MAX_CONSECUTIVE_FAILURES
@@ -56221,7 +57000,7 @@ async function handleSessionIdle(args) {
56221
57000
  }
56222
57001
  const effectiveCooldown = CONTINUATION_COOLDOWN_MS2 * 2 ** Math.min(state3.consecutiveFailures, 5);
56223
57002
  if (state3.lastInjectedAt && Date.now() - state3.lastInjectedAt < effectiveCooldown) {
56224
- log(`[${HOOK_NAME12}] Skipped: cooldown active`, {
57003
+ log(`[${HOOK_NAME13}] Skipped: cooldown active`, {
56225
57004
  sessionID,
56226
57005
  effectiveCooldown,
56227
57006
  consecutiveFailures: state3.consecutiveFailures
@@ -56251,20 +57030,20 @@ async function handleSessionIdle(args) {
56251
57030
  }
56252
57031
  }
56253
57032
  } catch (error51) {
56254
- log(`[${HOOK_NAME12}] Failed to fetch messages for agent check`, { sessionID, error: String(error51) });
57033
+ log(`[${HOOK_NAME13}] Failed to fetch messages for agent check`, { sessionID, error: String(error51) });
56255
57034
  }
56256
- log(`[${HOOK_NAME12}] Agent check`, { sessionID, agentName: resolvedInfo?.agent, skipAgents, hasCompactionMessage });
57035
+ log(`[${HOOK_NAME13}] Agent check`, { sessionID, agentName: resolvedInfo?.agent, skipAgents, hasCompactionMessage });
56257
57036
  const resolvedAgentName = resolvedInfo?.agent;
56258
57037
  if (resolvedAgentName && skipAgents.some((s) => getAgentConfigKey(s) === getAgentConfigKey(resolvedAgentName))) {
56259
- log(`[${HOOK_NAME12}] Skipped: agent in skipAgents list`, { sessionID, agent: resolvedAgentName });
57038
+ log(`[${HOOK_NAME13}] Skipped: agent in skipAgents list`, { sessionID, agent: resolvedAgentName });
56260
57039
  return;
56261
57040
  }
56262
57041
  if (hasCompactionMessage && !resolvedInfo?.agent) {
56263
- log(`[${HOOK_NAME12}] Skipped: compaction occurred but no agent info resolved`, { sessionID });
57042
+ log(`[${HOOK_NAME13}] Skipped: compaction occurred but no agent info resolved`, { sessionID });
56264
57043
  return;
56265
57044
  }
56266
57045
  if (isContinuationStopped?.(sessionID)) {
56267
- log(`[${HOOK_NAME12}] Skipped: continuation stopped for session`, { sessionID });
57046
+ log(`[${HOOK_NAME13}] Skipped: continuation stopped for session`, { sessionID });
56268
57047
  return;
56269
57048
  }
56270
57049
  startCountdown({
@@ -56294,7 +57073,7 @@ function handleNonIdleEvent(args) {
56294
57073
  if (state3?.countdownStartedAt) {
56295
57074
  const elapsed = Date.now() - state3.countdownStartedAt;
56296
57075
  if (elapsed < COUNTDOWN_GRACE_PERIOD_MS) {
56297
- log(`[${HOOK_NAME12}] Ignoring user message in grace period`, { sessionID, elapsed });
57076
+ log(`[${HOOK_NAME13}] Ignoring user message in grace period`, { sessionID, elapsed });
56298
57077
  return;
56299
57078
  }
56300
57079
  }
@@ -56338,7 +57117,7 @@ function handleNonIdleEvent(args) {
56338
57117
  const sessionInfo = properties?.info;
56339
57118
  if (sessionInfo?.id) {
56340
57119
  sessionStateStore.cleanup(sessionInfo.id);
56341
- log(`[${HOOK_NAME12}] Session deleted: cleaned up`, { sessionID: sessionInfo.id });
57120
+ log(`[${HOOK_NAME13}] Session deleted: cleaned up`, { sessionID: sessionInfo.id });
56342
57121
  }
56343
57122
  return;
56344
57123
  }
@@ -56363,10 +57142,10 @@ function createTodoContinuationHandler(args) {
56363
57142
  if (error51?.name === "MessageAbortedError" || error51?.name === "AbortError") {
56364
57143
  const state3 = sessionStateStore.getState(sessionID);
56365
57144
  state3.abortDetectedAt = Date.now();
56366
- log(`[${HOOK_NAME12}] Abort detected via session.error`, { sessionID, errorName: error51.name });
57145
+ log(`[${HOOK_NAME13}] Abort detected via session.error`, { sessionID, errorName: error51.name });
56367
57146
  }
56368
57147
  sessionStateStore.cancelCountdown(sessionID);
56369
- log(`[${HOOK_NAME12}] session.error`, { sessionID });
57148
+ log(`[${HOOK_NAME13}] session.error`, { sessionID });
56370
57149
  return;
56371
57150
  }
56372
57151
  if (event.type === "session.idle") {
@@ -56481,13 +57260,13 @@ function createTodoContinuationEnforcer(ctx, options = {}) {
56481
57260
  const state3 = sessionStateStore.getState(sessionID);
56482
57261
  state3.isRecovering = true;
56483
57262
  sessionStateStore.cancelCountdown(sessionID);
56484
- log(`[${HOOK_NAME12}] Session marked as recovering`, { sessionID });
57263
+ log(`[${HOOK_NAME13}] Session marked as recovering`, { sessionID });
56485
57264
  };
56486
57265
  const markRecoveryComplete = (sessionID) => {
56487
57266
  const state3 = sessionStateStore.getExistingState(sessionID);
56488
57267
  if (state3) {
56489
57268
  state3.isRecovering = false;
56490
- log(`[${HOOK_NAME12}] Session recovery complete`, { sessionID });
57269
+ log(`[${HOOK_NAME13}] Session recovery complete`, { sessionID });
56491
57270
  }
56492
57271
  };
56493
57272
  const handler = createTodoContinuationHandler({
@@ -56499,7 +57278,7 @@ function createTodoContinuationEnforcer(ctx, options = {}) {
56499
57278
  });
56500
57279
  const cancelAllCountdowns = () => {
56501
57280
  sessionStateStore.cancelAllCountdowns();
56502
- log(`[${HOOK_NAME12}] All countdowns cancelled`);
57281
+ log(`[${HOOK_NAME13}] All countdowns cancelled`);
56503
57282
  };
56504
57283
  return {
56505
57284
  handler,
@@ -56759,7 +57538,7 @@ This is a reminder only. No automatic action was taken.`;
56759
57538
  }
56760
57539
  // src/hooks/unstable-agent-babysitter/unstable-agent-babysitter-hook.ts
56761
57540
  init_logger();
56762
- var HOOK_NAME13 = "unstable-agent-babysitter";
57541
+ var HOOK_NAME14 = "unstable-agent-babysitter";
56763
57542
  var DEFAULT_TIMEOUT_MS = 120000;
56764
57543
  var COOLDOWN_MS = 5 * 60 * 1000;
56765
57544
  async function resolveMainSessionTarget(ctx, sessionID) {
@@ -56779,7 +57558,7 @@ async function resolveMainSessionTarget(ctx, sessionID) {
56779
57558
  }
56780
57559
  }
56781
57560
  } catch (error51) {
56782
- log(`[${HOOK_NAME13}] Failed to resolve main session agent`, { sessionID, error: String(error51) });
57561
+ log(`[${HOOK_NAME14}] Failed to resolve main session agent`, { sessionID, error: String(error51) });
56783
57562
  }
56784
57563
  return { agent, model };
56785
57564
  }
@@ -56812,7 +57591,7 @@ async function getThinkingSummary(ctx, sessionID) {
56812
57591
  return combined;
56813
57592
  return `${combined.slice(0, THINKING_SUMMARY_MAX_CHARS)}...`;
56814
57593
  } catch (error51) {
56815
- log(`[${HOOK_NAME13}] Failed to fetch thinking summary`, { sessionID, error: String(error51) });
57594
+ log(`[${HOOK_NAME14}] Failed to fetch thinking summary`, { sessionID, error: String(error51) });
56816
57595
  return null;
56817
57596
  }
56818
57597
  }
@@ -56861,9 +57640,9 @@ function createUnstableAgentBabysitterHook(ctx, options) {
56861
57640
  query: { directory: ctx.directory }
56862
57641
  });
56863
57642
  reminderCooldowns.set(task.id, now);
56864
- log(`[${HOOK_NAME13}] Reminder injected`, { taskId: task.id, sessionID: mainSessionID });
57643
+ log(`[${HOOK_NAME14}] Reminder injected`, { taskId: task.id, sessionID: mainSessionID });
56865
57644
  } catch (error51) {
56866
- log(`[${HOOK_NAME13}] Reminder injection failed`, { taskId: task.id, error: String(error51) });
57645
+ log(`[${HOOK_NAME14}] Reminder injection failed`, { taskId: task.id, error: String(error51) });
56867
57646
  }
56868
57647
  }
56869
57648
  };
@@ -58120,12 +58899,18 @@ class LSPClient extends LSPClientConnection {
58120
58899
  });
58121
58900
  }
58122
58901
  }
58902
+ // src/tools/lsp/lsp-server.ts
58903
+ init_logger();
58904
+
58123
58905
  // src/tools/lsp/lsp-manager-process-cleanup.ts
58906
+ init_logger();
58124
58907
  function registerLspManagerProcessCleanup(options) {
58125
58908
  const syncCleanup = () => {
58126
58909
  for (const [, managed] of options.getClients()) {
58127
58910
  try {
58128
- managed.client.stop().catch(() => {});
58911
+ managed.client.stop().catch((err) => {
58912
+ log("[lsp] Sync cleanup failed:", err);
58913
+ });
58129
58914
  } catch {}
58130
58915
  }
58131
58916
  options.clearClients();
@@ -58134,17 +58919,26 @@ function registerLspManagerProcessCleanup(options) {
58134
58919
  const asyncCleanup = async () => {
58135
58920
  const stopPromises = [];
58136
58921
  for (const [, managed] of options.getClients()) {
58137
- stopPromises.push(managed.client.stop().catch(() => {}));
58922
+ stopPromises.push(managed.client.stop().catch((err) => {
58923
+ log("[lsp] Async cleanup failed:", err);
58924
+ return;
58925
+ }));
58138
58926
  }
58139
58927
  await Promise.allSettled(stopPromises);
58140
58928
  options.clearClients();
58141
58929
  options.clearCleanupInterval();
58142
58930
  };
58143
58931
  process.on("exit", syncCleanup);
58144
- process.on("SIGINT", () => void asyncCleanup().catch(() => {}));
58145
- process.on("SIGTERM", () => void asyncCleanup().catch(() => {}));
58932
+ process.on("SIGINT", () => void asyncCleanup().catch((err) => {
58933
+ log("[lsp] Signal handler cleanup failed:", err);
58934
+ }));
58935
+ process.on("SIGTERM", () => void asyncCleanup().catch((err) => {
58936
+ log("[lsp] Signal handler cleanup failed:", err);
58937
+ }));
58146
58938
  if (process.platform === "win32") {
58147
- process.on("SIGBREAK", () => void asyncCleanup().catch(() => {}));
58939
+ process.on("SIGBREAK", () => void asyncCleanup().catch((err) => {
58940
+ log("[lsp] Signal handler cleanup failed:", err);
58941
+ }));
58148
58942
  }
58149
58943
  }
58150
58944
 
@@ -58314,7 +59108,9 @@ class LSPServerManager {
58314
59108
  }
58315
59109
  }).catch(() => {
58316
59110
  this.clients.delete(key);
58317
- client.stop().catch(() => {});
59111
+ client.stop().catch((err) => {
59112
+ log("[lsp] Warmup client stop failed:", err);
59113
+ });
58318
59114
  });
58319
59115
  }
58320
59116
  releaseClient(root, serverId) {
@@ -71172,6 +71968,7 @@ import { existsSync as existsSync48 } from "fs";
71172
71968
  var {spawn: spawn9 } = globalThis.Bun;
71173
71969
 
71174
71970
  // src/tools/ast-grep/cli-binary-path-resolution.ts
71971
+ init_logger();
71175
71972
  import { existsSync as existsSync47 } from "fs";
71176
71973
 
71177
71974
  // src/tools/ast-grep/language-support.ts
@@ -72798,6 +73595,9 @@ function createHandoffTools(ctx) {
72798
73595
  });
72799
73596
  return { handoff };
72800
73597
  }
73598
+ // src/tools/interactive-bash/tools.ts
73599
+ init_logger();
73600
+
72801
73601
  // src/tools/interactive-bash/constants.ts
72802
73602
  var DEFAULT_TIMEOUT_MS5 = 60000;
72803
73603
  var BLOCKED_TMUX_SUBCOMMANDS = [
@@ -72899,7 +73699,9 @@ The Bash tool can execute these commands directly. Do NOT retry with interactive
72899
73699
  const timeoutError = new Error(`Timeout after ${DEFAULT_TIMEOUT_MS5}ms`);
72900
73700
  try {
72901
73701
  proc.kill();
72902
- proc.exited.catch(() => {});
73702
+ proc.exited.catch((err) => {
73703
+ log("[interactive-bash] Process cleanup failed:", err);
73704
+ });
72903
73705
  } catch {}
72904
73706
  reject(timeoutError);
72905
73707
  }, DEFAULT_TIMEOUT_MS5);
@@ -74728,6 +75530,376 @@ function createBackgroundOutput(manager, client2) {
74728
75530
  }
74729
75531
  // src/tools/background-task/create-background-task.ts
74730
75532
  init_logger();
75533
+ // src/tools/assembly/constants.ts
75534
+ var DEFAULT_VOTERS = 3;
75535
+ var DEFAULT_ROUNDS = 2;
75536
+ var VOTER_TIMEOUT_MS = 120000;
75537
+ var POLL_INTERVAL_MS = 1000;
75538
+ var DEFAULT_PROVIDERS = [
75539
+ { providerID: "anthropic", modelID: "claude-sonnet-4-20250514" },
75540
+ { providerID: "openai", modelID: "gpt-4o" },
75541
+ { providerID: "google", modelID: "gemini-2.5-pro" }
75542
+ ];
75543
+
75544
+ // src/tools/assembly/provider-selector.ts
75545
+ function selectProviders(count, modelsOverride, config4) {
75546
+ if (modelsOverride && modelsOverride.length > 0) {
75547
+ return modelsOverride.slice(0, count).map((m) => {
75548
+ const parts = m.split(":");
75549
+ if (parts.length >= 2) {
75550
+ return { providerID: parts[0], modelID: parts.slice(1).join(":") };
75551
+ }
75552
+ return { providerID: "custom", modelID: m };
75553
+ });
75554
+ }
75555
+ const configuredProviders = config4?.providers ?? DEFAULT_PROVIDERS;
75556
+ if (configuredProviders.length === 0) {
75557
+ return DEFAULT_PROVIDERS.slice(0, count);
75558
+ }
75559
+ if (configuredProviders.length <= count) {
75560
+ return configuredProviders;
75561
+ }
75562
+ const selected = [];
75563
+ const usedProviderIds = new Set;
75564
+ for (const p of configuredProviders) {
75565
+ if (selected.length >= count)
75566
+ break;
75567
+ if (!usedProviderIds.has(p.providerID)) {
75568
+ selected.push(p);
75569
+ usedProviderIds.add(p.providerID);
75570
+ }
75571
+ }
75572
+ if (selected.length < count) {
75573
+ for (const p of configuredProviders) {
75574
+ if (selected.length >= count)
75575
+ break;
75576
+ if (!selected.includes(p)) {
75577
+ selected.push(p);
75578
+ }
75579
+ }
75580
+ }
75581
+ return selected;
75582
+ }
75583
+
75584
+ // src/tools/assembly/synthesizer.ts
75585
+ async function synthesizeResults(manager, question, voterResults, round, parentSessionID, parentMessageID) {
75586
+ const voterContext = voterResults.map((v, i) => `--- Voter ${i + 1} (${v.modelLabel}) ---
75587
+ Status: ${v.status}
75588
+ ${v.status === "completed" ? v.reasoning : `Error: ${v.error}`}`).join(`
75589
+
75590
+ `);
75591
+ const synthesisPrompt = `You are the synthesis agent. Multiple AI voters have independently analyzed a question. Your task is to synthesize their reasoning into a unified consensus.
75592
+
75593
+ ## Question
75594
+ ${question}
75595
+
75596
+ ## Voter Analyses
75597
+ ${voterContext}
75598
+
75599
+ ## Synthesis Round
75600
+ This is round #${round}.
75601
+
75602
+ ## Your Task
75603
+ 1. Identify points of agreement across all voters
75604
+ 2. Identify points of disagreement or divergence
75605
+ 3. Weigh the strength of evidence and reasoning from each
75606
+ 4. Produce a unified consensus with confidence level
75607
+
75608
+ ## Output Format
75609
+ ## Consensus
75610
+ [Your unified answer/conclusion]
75611
+
75612
+ ## Confidence Level
75613
+ [High/Medium/Low - with rationale]
75614
+
75615
+ ## Key Agreements
75616
+ - [Point 1]
75617
+ - [Point 2]
75618
+
75619
+ ## Key Disagreements
75620
+ - [Point 1]
75621
+ - [Point 2]
75622
+ `;
75623
+ const synthesisTask = await manager.launch({
75624
+ description: `Assembly synthesis round ${round}`,
75625
+ prompt: synthesisPrompt,
75626
+ agent: "mouse",
75627
+ parentSessionID,
75628
+ parentMessageID,
75629
+ model: {
75630
+ providerID: "anthropic",
75631
+ modelID: "claude-sonnet-4-20250514",
75632
+ temperature: 0.1
75633
+ }
75634
+ });
75635
+ const deadline = Date.now() + 120000;
75636
+ while (Date.now() < deadline) {
75637
+ const current = manager.getTask(synthesisTask.id);
75638
+ if (!current)
75639
+ break;
75640
+ if (current.status === "completed") {
75641
+ return parseSynthesis(current.result ?? "", round);
75642
+ }
75643
+ if (current.status === "error" || current.status === "cancelled" || current.status === "interrupt") {
75644
+ return {
75645
+ round,
75646
+ consensus: `Synthesis failed: ${current.error ?? "Unknown"}`,
75647
+ confidence: "low",
75648
+ disagreements: voterResults.filter((v) => v.status === "completed").map((v) => v.modelLabel)
75649
+ };
75650
+ }
75651
+ await delay4(POLL_INTERVAL_MS);
75652
+ }
75653
+ return {
75654
+ round,
75655
+ consensus: "Synthesis timed out",
75656
+ confidence: "low",
75657
+ disagreements: voterResults.filter((v) => v.status === "completed").map((v) => v.modelLabel)
75658
+ };
75659
+ }
75660
+ function parseSynthesis(text, round) {
75661
+ const confidenceMatch = text.match(/## Confidence Level\s*\n([^\n]+)/);
75662
+ const confidenceRaw = (confidenceMatch?.[1] ?? "medium").toLowerCase();
75663
+ const confidence = confidenceRaw.includes("high") ? "high" : confidenceRaw.includes("low") ? "low" : "medium";
75664
+ const consensusMatch = text.match(/## Consensus\s*\n([\s\S]*?)(?=\n## Confidence Level|\n## Key Agreements|\n$)/);
75665
+ const consensus = consensusMatch?.[1]?.trim() ?? text;
75666
+ const disagreements = [];
75667
+ const disagreementsMatch = text.match(/## Key Disagreements\s*\n([\s\S]*?)(?=\n## |$)/);
75668
+ if (disagreementsMatch) {
75669
+ const lines = disagreementsMatch[1].trim().split(`
75670
+ `);
75671
+ for (const line of lines) {
75672
+ const trimmed = line.replace(/^[-*]\s*/, "").trim();
75673
+ if (trimmed)
75674
+ disagreements.push(trimmed);
75675
+ }
75676
+ }
75677
+ return { round, consensus, confidence, disagreements };
75678
+ }
75679
+ function delay4(ms) {
75680
+ return new Promise((resolve10) => setTimeout(resolve10, ms));
75681
+ }
75682
+
75683
+ // src/tools/assembly/voter-spawner.ts
75684
+ async function spawnVoters(manager, question, providers, parentSessionID, parentMessageID) {
75685
+ const tasks = providers.map((provider, index) => manager.launch({
75686
+ description: `Assembly voter ${index + 1}`,
75687
+ prompt: `You are voter #${index + 1} in a multi-model assembly process. Analyze the following question independently and provide your reasoning. Be thorough and consider multiple angles.
75688
+
75689
+ Question: ${question}
75690
+
75691
+ Output your analysis in this format:
75692
+ ## Reasoning
75693
+ [Your detailed step-by-step reasoning]
75694
+
75695
+ ## Conclusion
75696
+ [Your final answer/conclusion]
75697
+
75698
+ ## Confidence
75699
+ [High/Medium/Low - with justification]`,
75700
+ agent: "mouse",
75701
+ parentSessionID,
75702
+ parentMessageID,
75703
+ model: {
75704
+ providerID: provider.providerID,
75705
+ modelID: provider.modelID,
75706
+ temperature: 0.3
75707
+ }
75708
+ }));
75709
+ const launchedTasks = await Promise.all(tasks);
75710
+ const results = await Promise.all(launchedTasks.map(async (task, index) => {
75711
+ const provider = providers[index];
75712
+ const deadline = Date.now() + VOTER_TIMEOUT_MS;
75713
+ while (Date.now() < deadline) {
75714
+ const current = manager.getTask(task.id);
75715
+ if (!current)
75716
+ break;
75717
+ if (current.status === "completed") {
75718
+ return {
75719
+ voterIndex: index,
75720
+ modelLabel: `${provider.providerID}:${provider.modelID}`,
75721
+ reasoning: current.result ?? "",
75722
+ status: "completed"
75723
+ };
75724
+ }
75725
+ if (current.status === "error") {
75726
+ return {
75727
+ voterIndex: index,
75728
+ modelLabel: `${provider.providerID}:${provider.modelID}`,
75729
+ reasoning: "",
75730
+ status: "error",
75731
+ error: current.error ?? "Unknown error"
75732
+ };
75733
+ }
75734
+ if (current.status === "cancelled" || current.status === "interrupt") {
75735
+ return {
75736
+ voterIndex: index,
75737
+ modelLabel: `${provider.providerID}:${provider.modelID}`,
75738
+ reasoning: "",
75739
+ status: "error",
75740
+ error: `Voter ${index + 1} was ${current.status}`
75741
+ };
75742
+ }
75743
+ await delay5(POLL_INTERVAL_MS);
75744
+ }
75745
+ return {
75746
+ voterIndex: index,
75747
+ modelLabel: `${provider.providerID}:${provider.modelID}`,
75748
+ reasoning: "",
75749
+ status: "timeout",
75750
+ error: `Voter ${index + 1} timed out after ${VOTER_TIMEOUT_MS}ms`
75751
+ };
75752
+ }));
75753
+ return results;
75754
+ }
75755
+ function delay5(ms) {
75756
+ return new Promise((resolve10) => setTimeout(resolve10, ms));
75757
+ }
75758
+
75759
+ // src/tools/assembly/assembly-executor.ts
75760
+ async function executeAssembly(input) {
75761
+ const { manager, args, config: config4, sessionID, messageID } = input;
75762
+ const voterCount = args.voters ?? config4?.default_voters ?? DEFAULT_VOTERS;
75763
+ const roundCount = args.rounds ?? config4?.default_rounds ?? DEFAULT_ROUNDS;
75764
+ const providers = selectProviders(voterCount, args.models, config4);
75765
+ let currentVoterResults = [];
75766
+ const synthesisRounds = [];
75767
+ for (let round = 0;round < roundCount; round++) {
75768
+ if (round === 0) {
75769
+ currentVoterResults = await spawnVoters(manager, args.question, providers, sessionID, messageID);
75770
+ }
75771
+ const synthesis = await synthesizeResults(manager, args.question, currentVoterResults, round + 1, sessionID, messageID);
75772
+ synthesisRounds.push(synthesis);
75773
+ if (synthesis.confidence === "high" && round < roundCount - 1) {
75774
+ break;
75775
+ }
75776
+ if (round < roundCount - 1) {
75777
+ currentVoterResults = currentVoterResults.map((v, _i) => ({
75778
+ ...v,
75779
+ reasoning: `${v.reasoning}
75780
+
75781
+ --- Round ${round + 1} Synthesis Feedback ---
75782
+ ${synthesis.consensus}`
75783
+ }));
75784
+ }
75785
+ }
75786
+ const finalSynthesis = synthesisRounds[synthesisRounds.length - 1];
75787
+ return {
75788
+ question: args.question,
75789
+ voters: currentVoterResults,
75790
+ rounds: synthesisRounds,
75791
+ finalConsensus: formatFinalConsensus(args.question, finalSynthesis, currentVoterResults)
75792
+ };
75793
+ }
75794
+ function formatFinalConsensus(question, final, voterResults) {
75795
+ const completed = voterResults.filter((v) => v.status === "completed");
75796
+ const errored = voterResults.filter((v) => v.status !== "completed");
75797
+ let output = `# Assembly Result
75798
+
75799
+ `;
75800
+ output += `**Question**: ${question}
75801
+ `;
75802
+ output += `**Confidence**: ${final.confidence}
75803
+ `;
75804
+ output += `**Voters**: ${completed.length}/${voterResults.length} completed`;
75805
+ if (errored.length > 0) {
75806
+ output += ` (${errored.map((v) => `${v.voterIndex + 1}: ${v.error}`).join(", ")})`;
75807
+ }
75808
+ output += `
75809
+
75810
+ ## Final Consensus
75811
+
75812
+ ${final.consensus}
75813
+
75814
+ `;
75815
+ if (final.disagreements.length > 0) {
75816
+ output += `## Disagreements
75817
+
75818
+ `;
75819
+ for (const d of final.disagreements) {
75820
+ output += `- ${d}
75821
+ `;
75822
+ }
75823
+ output += `
75824
+ `;
75825
+ }
75826
+ output += `## Per-Voter Reasoning
75827
+
75828
+ `;
75829
+ for (const v of voterResults) {
75830
+ output += `### Voter ${v.voterIndex + 1} (${v.modelLabel})
75831
+ `;
75832
+ output += `Status: ${v.status}
75833
+ `;
75834
+ if (v.status === "completed") {
75835
+ output += `
75836
+ ${v.reasoning}
75837
+
75838
+ `;
75839
+ } else {
75840
+ output += `Error: ${v.error}
75841
+
75842
+ `;
75843
+ }
75844
+ }
75845
+ return output;
75846
+ }
75847
+
75848
+ // src/tools/assembly/tool-description.ts
75849
+ var TOOL_DESCRIPTION = `
75850
+ Spawns multiple AI agents (voters) with different model providers to independently analyze a question or decision, then synthesizes their reasoning into a unified consensus.
75851
+
75852
+ ## When to use
75853
+ - Making critical decisions that benefit from multiple perspectives
75854
+ - Evaluating complex tradeoffs where bias reduction matters
75855
+ - Validating architectural choices through multi-model reasoning
75856
+ - When you need confidence scoring on analysis
75857
+
75858
+ ## How it works
75859
+ 1. **Voter selection**: Auto-selects N models from configured providers (or use your override)
75860
+ 2. **Independent voting**: Each voter independently analyzes the question
75861
+ 3. **Synthesis**: All voter outputs are fed to a synthesis round that identifies agreement, disagreement, and confidence
75862
+ 4. **Multi-round**: Optionally repeat synthesis for deeper convergence
75863
+
75864
+ ## Output format
75865
+ Returns a structured consensus with:
75866
+ - Unified answer/conclusion
75867
+ - Confidence level (high/medium/low)
75868
+ - Key disagreements between voters
75869
+ - Per-voter reasoning traces
75870
+ `;
75871
+
75872
+ // src/tools/assembly/types.ts
75873
+ var AssemblyArgsSchema = exports_external.object({
75874
+ question: exports_external.string().min(1).describe("The question or decision to reach consensus on"),
75875
+ voters: exports_external.number().int().min(2).max(5).optional().describe("Number of voters (default: 3)"),
75876
+ rounds: exports_external.number().int().min(1).max(3).optional().describe("Number of synthesis rounds (default: 2)"),
75877
+ models: exports_external.array(exports_external.string()).optional().describe("Override auto-selected models (e.g., ['anthropic:claude-sonnet-4-20250514', 'openai:gpt-4o'])")
75878
+ });
75879
+
75880
+ // src/tools/assembly/tools.ts
75881
+ function createAssemblyTool(options) {
75882
+ return tool({
75883
+ description: TOOL_DESCRIPTION,
75884
+ args: {
75885
+ question: tool.schema.string().describe("The question or decision to reach consensus on"),
75886
+ voters: tool.schema.number().optional().describe("Number of voters (2-5, default: 3)"),
75887
+ rounds: tool.schema.number().optional().describe("Number of synthesis rounds (1-3, default: 2)"),
75888
+ models: tool.schema.array(tool.schema.string()).optional().describe("Override auto-selected models")
75889
+ },
75890
+ async execute(args, toolContext) {
75891
+ const parsed = AssemblyArgsSchema.parse(args);
75892
+ const output = await executeAssembly({
75893
+ manager: options.manager,
75894
+ args: parsed,
75895
+ config: options.pluginConfig?.assembly,
75896
+ sessionID: toolContext.sessionID,
75897
+ messageID: toolContext.messageID
75898
+ });
75899
+ return output.finalConsensus;
75900
+ }
75901
+ });
75902
+ }
74731
75903
  // src/tools/delegate-agent/constants.ts
74732
75904
  var ALLOWED_AGENTS = [
74733
75905
  "trinity",
@@ -74827,7 +75999,7 @@ Use \`background_output\` tool with task_id="${task.id}" to check progress:
74827
75999
  // src/tools/delegate-agent/completion-poller.ts
74828
76000
  async function waitForCompletion(sessionID, toolContext, ctx) {
74829
76001
  log(`[delegate_agent] Polling for completion...`);
74830
- const POLL_INTERVAL_MS = 500;
76002
+ const POLL_INTERVAL_MS2 = 500;
74831
76003
  const MAX_POLL_TIME_MS = 5 * 60 * 1000;
74832
76004
  const pollStart = Date.now();
74833
76005
  let lastMsgCount = 0;
@@ -74838,7 +76010,7 @@ async function waitForCompletion(sessionID, toolContext, ctx) {
74838
76010
  log(`[delegate_agent] Aborted by user`);
74839
76011
  throw new Error("Task aborted.");
74840
76012
  }
74841
- await new Promise((resolve10) => setTimeout(resolve10, POLL_INTERVAL_MS));
76013
+ await new Promise((resolve10) => setTimeout(resolve10, POLL_INTERVAL_MS2));
74842
76014
  const statusResult = await ctx.client.session.status();
74843
76015
  const allStatuses = normalizeSDKResponse(statusResult, {});
74844
76016
  const sessionStatus = allStatuses[sessionID];
@@ -75132,7 +76304,7 @@ ${task.agent ? `subagent: ${task.agent}
75132
76304
  }
75133
76305
  }
75134
76306
  // src/tools/delegate-task/timing.ts
75135
- var POLL_INTERVAL_MS = 1000;
76307
+ var POLL_INTERVAL_MS2 = 1000;
75136
76308
  var MIN_STABILITY_TIME_MS = 1e4;
75137
76309
  var STABILITY_POLLS_REQUIRED = 3;
75138
76310
  var WAIT_FOR_SESSION_INTERVAL_MS = 100;
@@ -75141,7 +76313,7 @@ var MAX_POLL_TIME_MS = 10 * 60 * 1000;
75141
76313
  var SESSION_CONTINUATION_STABILITY_MS = 5000;
75142
76314
  function getTimingConfig() {
75143
76315
  return {
75144
- POLL_INTERVAL_MS,
76316
+ POLL_INTERVAL_MS: POLL_INTERVAL_MS2,
75145
76317
  MIN_STABILITY_TIME_MS,
75146
76318
  STABILITY_POLLS_REQUIRED,
75147
76319
  WAIT_FOR_SESSION_INTERVAL_MS,
@@ -75644,6 +76816,8 @@ Create the work plan directly - that's your job as the planning agent.`
75644
76816
  return { agentToUse, categoryModel };
75645
76817
  }
75646
76818
  // src/features/task-toast-manager/manager.ts
76819
+ init_logger();
76820
+
75647
76821
  class TaskToastManager {
75648
76822
  tasks = new Map;
75649
76823
  client;
@@ -75763,7 +76937,9 @@ class TaskToastManager {
75763
76937
  variant: "info",
75764
76938
  duration: running.length + queued.length > 2 ? 5000 : 3000
75765
76939
  }
75766
- }).catch(() => {});
76940
+ }).catch((err) => {
76941
+ log("[task-toast] Toast failed:", err);
76942
+ });
75767
76943
  }
75768
76944
  showCompletionToast(task) {
75769
76945
  const tuiClient = this.client;
@@ -75785,7 +76961,9 @@ Still running: ${remaining.length} | Queued: ${queued.length}`;
75785
76961
  variant: "success",
75786
76962
  duration: 5000
75787
76963
  }
75788
- }).catch(() => {});
76964
+ }).catch((err) => {
76965
+ log("[task-toast] Toast failed:", err);
76966
+ });
75789
76967
  }
75790
76968
  }
75791
76969
  var instance = null;
@@ -76260,18 +77438,19 @@ async function sendSyncPrompt(client2, input, deps = sendSyncPromptDeps) {
76260
77438
  try {
76261
77439
  await deps.promptWithModelSuggestionRetry(client2, promptArgs);
76262
77440
  } catch (promptError) {
77441
+ let lastError = promptError;
76263
77442
  if (isOracleAgent2(input.agentToUse) && isUnexpectedEofError(promptError)) {
76264
77443
  try {
76265
77444
  await deps.promptSyncWithModelSuggestionRetry(client2, promptArgs);
76266
77445
  return null;
76267
77446
  } catch (oracleRetryError) {
76268
- promptError = oracleRetryError;
77447
+ lastError = oracleRetryError;
76269
77448
  }
76270
77449
  }
76271
77450
  if (input.toastManager && input.taskId !== undefined) {
76272
77451
  input.toastManager.removeTask(input.taskId);
76273
77452
  }
76274
- const errorMessage = promptError instanceof Error ? promptError.message : String(promptError);
77453
+ const errorMessage = lastError instanceof Error ? lastError.message : String(lastError);
76275
77454
  if (errorMessage.includes("agent.name") || errorMessage.includes("undefined")) {
76276
77455
  return formatDetailedError(new Error(`Agent "${input.agentToUse}" not found. Make sure the agent is registered in your opencode.json or provided by a plugin.`), {
76277
77456
  operation: "Send prompt to agent",
@@ -76281,7 +77460,7 @@ async function sendSyncPrompt(client2, input, deps = sendSyncPromptDeps) {
76281
77460
  category: input.args.category
76282
77461
  });
76283
77462
  }
76284
- return formatDetailedError(promptError, {
77463
+ return formatDetailedError(lastError, {
76285
77464
  operation: "Send prompt",
76286
77465
  args: input.args,
76287
77466
  sessionID: input.sessionID,
@@ -76444,7 +77623,9 @@ session_id: ${sessionID}
76444
77623
  } finally {
76445
77624
  if (syncSessionID) {
76446
77625
  subagentSessions.delete(syncSessionID);
76447
- client2.session.abort({ path: { id: syncSessionID } }).catch(() => {});
77626
+ client2.session.abort({ path: { id: syncSessionID } }).catch((err) => {
77627
+ log("[delegate-task] Sync session abort failed:", err);
77628
+ });
76448
77629
  }
76449
77630
  }
76450
77631
  }
@@ -79171,10 +80352,6 @@ class ConcurrencyManager {
79171
80352
  return this.queues.get(model)?.length ?? 0;
79172
80353
  }
79173
80354
  }
79174
- // src/features/background-agent/manager.ts
79175
- import { existsSync as existsSync57, readdirSync as readdirSync19, readFileSync as readFileSync39 } from "fs";
79176
- import { join as join72 } from "path";
79177
-
79178
80355
  // src/features/background-agent/constants.ts
79179
80356
  var TASK_TTL_MS = 30 * 60 * 1000;
79180
80357
  var MIN_STABILITY_TIME_MS2 = 10 * 1000;
@@ -79189,6 +80366,59 @@ var POLLING_INTERVAL_MS = 3000;
79189
80366
  var PRUNE_THROTTLE_MS = 30000;
79190
80367
  var TASK_CLEANUP_DELAY_MS = 10 * 60 * 1000;
79191
80368
 
80369
+ // src/features/background-agent/error-helpers.ts
80370
+ function formatDuration2(start, end) {
80371
+ const duration5 = (end ?? new Date).getTime() - start.getTime();
80372
+ const seconds = Math.floor(duration5 / 1000);
80373
+ const minutes = Math.floor(seconds / 60);
80374
+ const hours = Math.floor(minutes / 60);
80375
+ if (hours > 0) {
80376
+ return `${hours}h ${minutes % 60}m ${seconds % 60}s`;
80377
+ }
80378
+ if (minutes > 0) {
80379
+ return `${minutes}m ${seconds % 60}s`;
80380
+ }
80381
+ return `${seconds}s`;
80382
+ }
80383
+ function getErrorText(error95) {
80384
+ if (!error95)
80385
+ return "";
80386
+ if (typeof error95 === "string")
80387
+ return error95;
80388
+ if (error95 instanceof Error) {
80389
+ return `${error95.name}: ${error95.message}`;
80390
+ }
80391
+ if (typeof error95 === "object" && error95 !== null) {
80392
+ if ("message" in error95 && typeof error95.message === "string") {
80393
+ return error95.message;
80394
+ }
80395
+ if ("name" in error95 && typeof error95.name === "string") {
80396
+ return error95.name;
80397
+ }
80398
+ }
80399
+ return "";
80400
+ }
80401
+ function isAbortedSessionError(error95) {
80402
+ const message = getErrorText(error95);
80403
+ return message.toLowerCase().includes("aborted");
80404
+ }
80405
+ function isRecord5(value) {
80406
+ return typeof value === "object" && value !== null;
80407
+ }
80408
+ function getSessionErrorMessage(properties) {
80409
+ const errorRaw = properties.error;
80410
+ if (!isRecord5(errorRaw))
80411
+ return;
80412
+ const dataRaw = errorRaw.data;
80413
+ if (isRecord5(dataRaw)) {
80414
+ const message2 = dataRaw.message;
80415
+ if (typeof message2 === "string")
80416
+ return message2;
80417
+ }
80418
+ const message = errorRaw.message;
80419
+ return typeof message === "string" ? message : undefined;
80420
+ }
80421
+
79192
80422
  // src/features/background-agent/loop-detector.ts
79193
80423
  function resolveCircuitBreakerSettings(config4) {
79194
80424
  return {
@@ -79253,6 +80483,145 @@ function detectRepetitiveToolUse(window) {
79253
80483
  };
79254
80484
  }
79255
80485
 
80486
+ // src/features/background-agent/message-dir.ts
80487
+ import { existsSync as existsSync57, readdirSync as readdirSync19, readFileSync as readFileSync39 } from "fs";
80488
+ import { join as join72 } from "path";
80489
+ var MESSAGE_DIR_CACHE_CAP = 1000;
80490
+ var messageDirCache = new Map;
80491
+ function computeMessageDir(sessionID) {
80492
+ if (!existsSync57(MESSAGE_STORAGE))
80493
+ return null;
80494
+ const directPath = join72(MESSAGE_STORAGE, sessionID);
80495
+ if (existsSync57(directPath))
80496
+ return directPath;
80497
+ for (const dir of readdirSync19(MESSAGE_STORAGE)) {
80498
+ const sessionPath = join72(MESSAGE_STORAGE, dir, sessionID);
80499
+ if (existsSync57(sessionPath))
80500
+ return sessionPath;
80501
+ }
80502
+ return null;
80503
+ }
80504
+ function getMessageDir2(sessionID) {
80505
+ const cached3 = messageDirCache.get(sessionID);
80506
+ if (cached3 !== undefined) {
80507
+ messageDirCache.delete(sessionID);
80508
+ messageDirCache.set(sessionID, cached3);
80509
+ return cached3;
80510
+ }
80511
+ const result = computeMessageDir(sessionID);
80512
+ if (messageDirCache.size >= MESSAGE_DIR_CACHE_CAP) {
80513
+ const oldest = messageDirCache.keys().next().value;
80514
+ if (oldest !== undefined)
80515
+ messageDirCache.delete(oldest);
80516
+ }
80517
+ messageDirCache.set(sessionID, result);
80518
+ return result;
80519
+ }
80520
+ function isCompactionAgent(agent) {
80521
+ return agent?.trim().toLowerCase() === "compaction";
80522
+ }
80523
+ function hasFullAgentAndModel(message) {
80524
+ return !!message.agent && !isCompactionAgent(message.agent) && !!message.model?.providerID && !!message.model?.modelID;
80525
+ }
80526
+ function hasPartialAgentOrModel(message) {
80527
+ const hasAgent = !!message.agent && !isCompactionAgent(message.agent);
80528
+ const hasModel = !!message.model?.providerID && !!message.model?.modelID;
80529
+ return hasAgent || hasModel;
80530
+ }
80531
+ function findNearestMessageExcludingCompaction(messageDir) {
80532
+ try {
80533
+ const files = readdirSync19(messageDir).filter((name) => name.endsWith(".json")).sort().reverse();
80534
+ const parsedByFile = new Map;
80535
+ for (const file3 of files) {
80536
+ try {
80537
+ const content = readFileSync39(join72(messageDir, file3), "utf-8");
80538
+ const parsed = JSON.parse(content);
80539
+ parsedByFile.set(file3, parsed);
80540
+ if (hasFullAgentAndModel(parsed)) {
80541
+ return parsed;
80542
+ }
80543
+ } catch {}
80544
+ }
80545
+ for (const parsed of parsedByFile.values()) {
80546
+ if (hasPartialAgentOrModel(parsed)) {
80547
+ return parsed;
80548
+ }
80549
+ }
80550
+ return null;
80551
+ } catch {
80552
+ return null;
80553
+ }
80554
+ }
80555
+
80556
+ // src/features/background-agent/notification-builder.ts
80557
+ function buildCompletionNotification(task, allComplete, completedTasks, remainingCount, duration5, errorInfo) {
80558
+ if (allComplete) {
80559
+ const completedTasksText = completedTasks.map((t) => `- \`${t.id}\`: ${t.description}`).join(`
80560
+ `);
80561
+ return `<system-reminder>
80562
+ [ALL BACKGROUND TASKS COMPLETE]
80563
+
80564
+ **Completed:**
80565
+ ${completedTasksText || `- \`${task.id}\`: ${task.description}`}
80566
+
80567
+ Use \`background_output(task_id="<id>")\` to retrieve each result.
80568
+ </system-reminder>`;
80569
+ }
80570
+ const statusText = task.status === "completed" ? "COMPLETED" : task.status === "interrupt" ? "INTERRUPTED" : "CANCELLED";
80571
+ return `<system-reminder>
80572
+ [BACKGROUND TASK ${statusText}]
80573
+ **ID:** \`${task.id}\`
80574
+ **Description:** ${task.description}
80575
+ **Duration:** ${duration5}${errorInfo}
80576
+
80577
+ **${remainingCount} task${remainingCount === 1 ? "" : "s"} still in progress.** You WILL be notified when ALL complete.
80578
+ Do NOT poll - continue productive work.
80579
+
80580
+ Use \`background_output(task_id="${task.id}")\` to retrieve this result when ready.
80581
+ </system-reminder>`;
80582
+ }
80583
+ async function resolveAgentAndModel(client2, task, enableParentSessionNotifications) {
80584
+ let agent = task.parentAgent;
80585
+ let model;
80586
+ if (!enableParentSessionNotifications) {
80587
+ return { agent, model };
80588
+ }
80589
+ try {
80590
+ const messagesResp = await client2.session.messages({
80591
+ path: { id: task.parentSessionID }
80592
+ });
80593
+ const messages = normalizeSDKResponse(messagesResp, []);
80594
+ for (let i = messages.length - 1;i >= 0; i--) {
80595
+ const info = messages[i].info;
80596
+ if (isCompactionAgent(info?.agent)) {
80597
+ continue;
80598
+ }
80599
+ if (info?.agent || info?.model || info?.modelID && info?.providerID) {
80600
+ agent = info.agent ?? task.parentAgent;
80601
+ model = info.model ?? (info.providerID && info.modelID ? {
80602
+ providerID: info.providerID,
80603
+ modelID: info.modelID
80604
+ } : undefined);
80605
+ break;
80606
+ }
80607
+ }
80608
+ } catch (error95) {
80609
+ if (isAbortedSessionError(error95)) {
80610
+ log("[background-agent] Parent session aborted while loading messages; using messageDir fallback:", {
80611
+ taskParentSessionID: task.parentSessionID
80612
+ });
80613
+ }
80614
+ const messageDir = getMessageDir2(task.parentSessionID);
80615
+ const currentMessage = messageDir ? findNearestMessageExcludingCompaction(messageDir) : null;
80616
+ agent = currentMessage?.agent ?? task.parentAgent;
80617
+ model = currentMessage?.model?.providerID && currentMessage?.model?.modelID ? {
80618
+ providerID: currentMessage.model.providerID,
80619
+ modelID: currentMessage.model.modelID
80620
+ } : undefined;
80621
+ }
80622
+ return { agent, model };
80623
+ }
80624
+
79256
80625
  // src/features/background-agent/task-history.ts
79257
80626
  var MAX_ENTRIES_PER_PARENT = 100;
79258
80627
 
@@ -79559,9 +80928,7 @@ class BackgroundManager {
79559
80928
  this.concurrencyManager.release(existingTask.concurrencyKey);
79560
80929
  existingTask.concurrencyKey = undefined;
79561
80930
  }
79562
- this.client.session.abort({
79563
- path: { id: sessionID }
79564
- }).catch(() => {});
80931
+ this.abortSessionQuietly(sessionID, "error-recovery");
79565
80932
  this.markForNotification(existingTask);
79566
80933
  this.cleanupPendingByParent(existingTask);
79567
80934
  this.enqueueNotificationForParent(existingTask.parentSessionID, () => this.notifyParentSession(existingTask)).catch((err) => {
@@ -79765,9 +81132,7 @@ class BackgroundManager {
79765
81132
  existingTask.concurrencyKey = undefined;
79766
81133
  }
79767
81134
  if (existingTask.sessionID) {
79768
- this.client.session.abort({
79769
- path: { id: existingTask.sessionID }
79770
- }).catch(() => {});
81135
+ this.abortSessionQuietly(existingTask.sessionID, "resume-error");
79771
81136
  }
79772
81137
  this.markForNotification(existingTask);
79773
81138
  this.cleanupPendingByParent(existingTask);
@@ -79922,7 +81287,7 @@ class BackgroundManager {
79922
81287
  const task = this.findBySession(sessionID);
79923
81288
  if (task?.status !== "running")
79924
81289
  return;
79925
- const errorMessage = props ? this.getSessionErrorMessage(props) : undefined;
81290
+ const errorMessage = props ? getSessionErrorMessage(props) : undefined;
79926
81291
  task.status = "error";
79927
81292
  task.error = errorMessage ?? "Session error";
79928
81293
  task.completedAt = new Date;
@@ -80062,6 +81427,14 @@ class BackgroundManager {
80062
81427
  }
80063
81428
  }
80064
81429
  }
81430
+ abortSessionQuietly(sessionID, context) {
81431
+ this.client.session.abort({ path: { id: sessionID } }).catch((err) => {
81432
+ log(`[background-agent] Session abort failed${context ? ` (${context})` : ""}:`, {
81433
+ sessionID,
81434
+ error: err
81435
+ });
81436
+ });
81437
+ }
80065
81438
  async cancelTask(taskId, options) {
80066
81439
  const task = this.tasks.get(taskId);
80067
81440
  if (!task || task.status !== "running" && task.status !== "pending") {
@@ -80106,9 +81479,7 @@ class BackgroundManager {
80106
81479
  }
80107
81480
  this.cleanupPendingByParent(task);
80108
81481
  if (abortSession && task.sessionID) {
80109
- this.client.session.abort({
80110
- path: { id: task.sessionID }
80111
- }).catch(() => {});
81482
+ this.abortSessionQuietly(task.sessionID, "cancel");
80112
81483
  }
80113
81484
  if (options?.skipNotification) {
80114
81485
  const toastManager = getTaskToastManager();
@@ -80223,9 +81594,7 @@ class BackgroundManager {
80223
81594
  this.idleDeferralTimers.delete(task.id);
80224
81595
  }
80225
81596
  if (task.sessionID) {
80226
- this.client.session.abort({
80227
- path: { id: task.sessionID }
80228
- }).catch(() => {});
81597
+ this.abortSessionQuietly(task.sessionID, "complete");
80229
81598
  }
80230
81599
  try {
80231
81600
  await this.enqueueNotificationForParent(task.parentSessionID, () => this.notifyParentSession(task));
@@ -80236,7 +81605,7 @@ class BackgroundManager {
80236
81605
  return true;
80237
81606
  }
80238
81607
  async notifyParentSession(task) {
80239
- const duration5 = this.formatDuration(task.startedAt ?? new Date, task.completedAt);
81608
+ const duration5 = formatDuration2(task.startedAt ?? new Date, task.completedAt);
80240
81609
  log("[background-agent] notifyParentSession called for task:", task.id);
80241
81610
  const toastManager = getTaskToastManager();
80242
81611
  if (toastManager) {
@@ -80260,68 +81629,16 @@ class BackgroundManager {
80260
81629
  allComplete = true;
80261
81630
  }
80262
81631
  const completedTasks = allComplete ? Array.from(this.tasks.values()).filter((t) => t.parentSessionID === task.parentSessionID && t.status !== "running" && t.status !== "pending") : [];
80263
- const statusText = task.status === "completed" ? "COMPLETED" : task.status === "interrupt" ? "INTERRUPTED" : "CANCELLED";
80264
81632
  const errorInfo = task.error ? `
80265
81633
  **Error:** ${task.error}` : "";
80266
- let notification;
80267
- if (allComplete) {
80268
- const completedTasksText = completedTasks.map((t) => `- \`${t.id}\`: ${t.description}`).join(`
80269
- `);
80270
- notification = `<system-reminder>
80271
- [ALL BACKGROUND TASKS COMPLETE]
80272
-
80273
- **Completed:**
80274
- ${completedTasksText || `- \`${task.id}\`: ${task.description}`}
80275
-
80276
- Use \`background_output(task_id="<id>")\` to retrieve each result.
80277
- </system-reminder>`;
80278
- } else {
80279
- notification = `<system-reminder>
80280
- [BACKGROUND TASK ${statusText}]
80281
- **ID:** \`${task.id}\`
80282
- **Description:** ${task.description}
80283
- **Duration:** ${duration5}${errorInfo}
80284
-
80285
- **${remainingCount} task${remainingCount === 1 ? "" : "s"} still in progress.** You WILL be notified when ALL complete.
80286
- Do NOT poll - continue productive work.
80287
-
80288
- Use \`background_output(task_id="${task.id}")\` to retrieve this result when ready.
80289
- </system-reminder>`;
80290
- }
80291
- let agent = task.parentAgent;
80292
- let model;
81634
+ const notification = buildCompletionNotification(task, allComplete, completedTasks, remainingCount, duration5, errorInfo);
81635
+ const { agent, model } = await resolveAgentAndModel(this.client, task, this.enableParentSessionNotifications);
81636
+ log("[background-agent] notifyParentSession context:", {
81637
+ taskId: task.id,
81638
+ resolvedAgent: agent,
81639
+ resolvedModel: model
81640
+ });
80293
81641
  if (this.enableParentSessionNotifications) {
80294
- try {
80295
- const messagesResp = await this.client.session.messages({ path: { id: task.parentSessionID } });
80296
- const messages = normalizeSDKResponse(messagesResp, []);
80297
- for (let i = messages.length - 1;i >= 0; i--) {
80298
- const info = messages[i].info;
80299
- if (isCompactionAgent(info?.agent)) {
80300
- continue;
80301
- }
80302
- if (info?.agent || info?.model || info?.modelID && info?.providerID) {
80303
- agent = info.agent ?? task.parentAgent;
80304
- model = info.model ?? (info.providerID && info.modelID ? { providerID: info.providerID, modelID: info.modelID } : undefined);
80305
- break;
80306
- }
80307
- }
80308
- } catch (error95) {
80309
- if (this.isAbortedSessionError(error95)) {
80310
- log("[background-agent] Parent session aborted while loading messages; using messageDir fallback:", {
80311
- taskId: task.id,
80312
- parentSessionID: task.parentSessionID
80313
- });
80314
- }
80315
- const messageDir = getMessageDir2(task.parentSessionID);
80316
- const currentMessage = messageDir ? findNearestMessageExcludingCompaction(messageDir) : null;
80317
- agent = currentMessage?.agent ?? task.parentAgent;
80318
- model = currentMessage?.model?.providerID && currentMessage?.model?.modelID ? { providerID: currentMessage.model.providerID, modelID: currentMessage.model.modelID } : undefined;
80319
- }
80320
- log("[background-agent] notifyParentSession context:", {
80321
- taskId: task.id,
80322
- resolvedAgent: agent,
80323
- resolvedModel: model
80324
- });
80325
81642
  try {
80326
81643
  await this.client.session.promptAsync({
80327
81644
  path: { id: task.parentSessionID },
@@ -80339,7 +81656,7 @@ Use \`background_output(task_id="${task.id}")\` to retrieve this result when rea
80339
81656
  noReply: !allComplete
80340
81657
  });
80341
81658
  } catch (error95) {
80342
- if (this.isAbortedSessionError(error95)) {
81659
+ if (isAbortedSessionError(error95)) {
80343
81660
  log("[background-agent] Parent session aborted while sending notification; continuing cleanup:", {
80344
81661
  taskId: task.id,
80345
81662
  parentSessionID: task.parentSessionID
@@ -80374,56 +81691,6 @@ Use \`background_output(task_id="${task.id}")\` to retrieve this result when rea
80374
81691
  }
80375
81692
  }
80376
81693
  }
80377
- formatDuration(start, end) {
80378
- const duration5 = (end ?? new Date).getTime() - start.getTime();
80379
- const seconds = Math.floor(duration5 / 1000);
80380
- const minutes = Math.floor(seconds / 60);
80381
- const hours = Math.floor(minutes / 60);
80382
- if (hours > 0) {
80383
- return `${hours}h ${minutes % 60}m ${seconds % 60}s`;
80384
- } else if (minutes > 0) {
80385
- return `${minutes}m ${seconds % 60}s`;
80386
- }
80387
- return `${seconds}s`;
80388
- }
80389
- isAbortedSessionError(error95) {
80390
- const message = this.getErrorText(error95);
80391
- return message.toLowerCase().includes("aborted");
80392
- }
80393
- getErrorText(error95) {
80394
- if (!error95)
80395
- return "";
80396
- if (typeof error95 === "string")
80397
- return error95;
80398
- if (error95 instanceof Error) {
80399
- return `${error95.name}: ${error95.message}`;
80400
- }
80401
- if (typeof error95 === "object" && error95 !== null) {
80402
- if ("message" in error95 && typeof error95.message === "string") {
80403
- return error95.message;
80404
- }
80405
- if ("name" in error95 && typeof error95.name === "string") {
80406
- return error95.name;
80407
- }
80408
- }
80409
- return "";
80410
- }
80411
- isRecord(value) {
80412
- return typeof value === "object" && value !== null;
80413
- }
80414
- getSessionErrorMessage(properties) {
80415
- const errorRaw = properties.error;
80416
- if (!this.isRecord(errorRaw))
80417
- return;
80418
- const dataRaw = errorRaw.data;
80419
- if (this.isRecord(dataRaw)) {
80420
- const message2 = dataRaw.message;
80421
- if (typeof message2 === "string")
80422
- return message2;
80423
- }
80424
- const message = errorRaw.message;
80425
- return typeof message === "string" ? message : undefined;
80426
- }
80427
81694
  hasRunningTasks() {
80428
81695
  for (const task of this.tasks.values()) {
80429
81696
  if (task.status === "running")
@@ -80520,7 +81787,7 @@ Use \`background_output(task_id="${task.id}")\` to retrieve this result when rea
80520
81787
  this.concurrencyManager.release(task.concurrencyKey);
80521
81788
  task.concurrencyKey = undefined;
80522
81789
  }
80523
- this.client.session.abort({ path: { id: sessionID } }).catch(() => {});
81790
+ this.abortSessionQuietly(sessionID, "stale-no-progress");
80524
81791
  log(`[background-agent] Task ${task.id} interrupted: no progress since start`);
80525
81792
  try {
80526
81793
  await this.enqueueNotificationForParent(task.parentSessionID, () => this.notifyParentSession(task));
@@ -80546,7 +81813,7 @@ Use \`background_output(task_id="${task.id}")\` to retrieve this result when rea
80546
81813
  this.concurrencyManager.release(task.concurrencyKey);
80547
81814
  task.concurrencyKey = undefined;
80548
81815
  }
80549
- this.client.session.abort({ path: { id: sessionID } }).catch(() => {});
81816
+ this.abortSessionQuietly(sessionID, "stale-timeout");
80550
81817
  log(`[background-agent] Task ${task.id} interrupted: stale timeout`);
80551
81818
  try {
80552
81819
  await this.enqueueNotificationForParent(task.parentSessionID, () => this.notifyParentSession(task));
@@ -80616,9 +81883,7 @@ Use \`background_output(task_id="${task.id}")\` to retrieve this result when rea
80616
81883
  this.stopPolling();
80617
81884
  for (const task of this.tasks.values()) {
80618
81885
  if (task.status === "running" && task.sessionID) {
80619
- this.client.session.abort({
80620
- path: { id: task.sessionID }
80621
- }).catch(() => {});
81886
+ this.abortSessionQuietly(task.sessionID, "shutdown");
80622
81887
  }
80623
81888
  }
80624
81889
  if (this.onShutdown) {
@@ -80657,13 +81922,17 @@ Use \`background_output(task_id="${task.id}")\` to retrieve this result when rea
80657
81922
  return operation();
80658
81923
  }
80659
81924
  const previous = this.notificationQueueByParent.get(parentSessionID) ?? Promise.resolve();
80660
- const current = previous.catch(() => {}).then(operation);
81925
+ const current = previous.catch((err) => {
81926
+ log("[background-agent] Previous notification failed, continuing queue:", err);
81927
+ }).then(operation);
80661
81928
  this.notificationQueueByParent.set(parentSessionID, current);
80662
81929
  current.finally(() => {
80663
81930
  if (this.notificationQueueByParent.get(parentSessionID) === current) {
80664
81931
  this.notificationQueueByParent.delete(parentSessionID);
80665
81932
  }
80666
- }).catch(() => {});
81933
+ }).catch((err) => {
81934
+ log("[background-agent] Notification queue cleanup failed:", err);
81935
+ });
80667
81936
  return current;
80668
81937
  }
80669
81938
  }
@@ -80678,72 +81947,6 @@ function registerProcessSignal(signal, handler, exitAfter) {
80678
81947
  process.on(signal, listener);
80679
81948
  return listener;
80680
81949
  }
80681
- function computeMessageDir(sessionID) {
80682
- if (!existsSync57(MESSAGE_STORAGE))
80683
- return null;
80684
- const directPath = join72(MESSAGE_STORAGE, sessionID);
80685
- if (existsSync57(directPath))
80686
- return directPath;
80687
- for (const dir of readdirSync19(MESSAGE_STORAGE)) {
80688
- const sessionPath = join72(MESSAGE_STORAGE, dir, sessionID);
80689
- if (existsSync57(sessionPath))
80690
- return sessionPath;
80691
- }
80692
- return null;
80693
- }
80694
- var MESSAGE_DIR_CACHE_CAP = 1000;
80695
- var messageDirCache = new Map;
80696
- function getMessageDir2(sessionID) {
80697
- const cached3 = messageDirCache.get(sessionID);
80698
- if (cached3 !== undefined) {
80699
- messageDirCache.delete(sessionID);
80700
- messageDirCache.set(sessionID, cached3);
80701
- return cached3;
80702
- }
80703
- const result = computeMessageDir(sessionID);
80704
- if (messageDirCache.size >= MESSAGE_DIR_CACHE_CAP) {
80705
- const oldest = messageDirCache.keys().next().value;
80706
- if (oldest !== undefined)
80707
- messageDirCache.delete(oldest);
80708
- }
80709
- messageDirCache.set(sessionID, result);
80710
- return result;
80711
- }
80712
- function isCompactionAgent(agent) {
80713
- return agent?.trim().toLowerCase() === "compaction";
80714
- }
80715
- function hasFullAgentAndModel(message) {
80716
- return !!message.agent && !isCompactionAgent(message.agent) && !!message.model?.providerID && !!message.model?.modelID;
80717
- }
80718
- function hasPartialAgentOrModel(message) {
80719
- const hasAgent = !!message.agent && !isCompactionAgent(message.agent);
80720
- const hasModel = !!message.model?.providerID && !!message.model?.modelID;
80721
- return hasAgent || hasModel;
80722
- }
80723
- function findNearestMessageExcludingCompaction(messageDir) {
80724
- try {
80725
- const files = readdirSync19(messageDir).filter((name) => name.endsWith(".json")).sort().reverse();
80726
- const parsedByFile = new Map;
80727
- for (const file3 of files) {
80728
- try {
80729
- const content = readFileSync39(join72(messageDir, file3), "utf-8");
80730
- const parsed = JSON.parse(content);
80731
- parsedByFile.set(file3, parsed);
80732
- if (hasFullAgentAndModel(parsed)) {
80733
- return parsed;
80734
- }
80735
- } catch {}
80736
- }
80737
- for (const parsed of parsedByFile.values()) {
80738
- if (hasPartialAgentOrModel(parsed)) {
80739
- return parsed;
80740
- }
80741
- }
80742
- return null;
80743
- } catch {
80744
- return null;
80745
- }
80746
- }
80747
81950
  // src/features/background-agent/state.ts
80748
81951
  class TaskStateManager {
80749
81952
  tasks = new Map;
@@ -80916,6 +82119,7 @@ class TaskStateManager {
80916
82119
  }
80917
82120
  }
80918
82121
  // src/features/skill-mcp-manager/cleanup.ts
82122
+ init_logger();
80919
82123
  async function closeManagedClient(managed) {
80920
82124
  try {
80921
82125
  await managed.client.close();
@@ -80936,7 +82140,9 @@ function registerProcessCleanup(state3) {
80936
82140
  state3.pendingConnections.clear();
80937
82141
  };
80938
82142
  const register = (signal) => {
80939
- const listener = () => void cleanup().catch(() => {});
82143
+ const listener = () => void cleanup().catch((err) => {
82144
+ log("[skill-mcp] Signal handler cleanup failed:", err);
82145
+ });
80940
82146
  state3.cleanupHandlers.push({ signal, listener });
80941
82147
  process.on(signal, listener);
80942
82148
  };
@@ -80959,7 +82165,9 @@ function startCleanupTimer(state3) {
80959
82165
  if (state3.cleanupInterval)
80960
82166
  return;
80961
82167
  state3.cleanupInterval = setInterval(() => {
80962
- cleanupIdleClients(state3).catch(() => {});
82168
+ cleanupIdleClients(state3).catch((err) => {
82169
+ log("[skill-mcp] Idle client cleanup failed:", err);
82170
+ });
80963
82171
  }, 60000);
80964
82172
  state3.cleanupInterval.unref();
80965
82173
  }
@@ -84616,13 +85824,13 @@ class StreamableHTTPClientTransport {
84616
85824
  this.onerror?.(new Error(`Maximum reconnection attempts (${maxRetries}) exceeded.`));
84617
85825
  return;
84618
85826
  }
84619
- const delay4 = this._getNextReconnectionDelay(attemptCount);
85827
+ const delay6 = this._getNextReconnectionDelay(attemptCount);
84620
85828
  this._reconnectionTimeout = setTimeout(() => {
84621
85829
  this._startOrAuthSse(options).catch((error95) => {
84622
85830
  this.onerror?.(new Error(`Failed to reconnect SSE stream: ${error95 instanceof Error ? error95.message : String(error95)}`));
84623
85831
  this._scheduleReconnection(options, attemptCount + 1);
84624
85832
  });
84625
- }, delay4);
85833
+ }, delay6);
84626
85834
  }
84627
85835
  _handleSseStream(stream, options, isReconnectable) {
84628
85836
  if (!stream) {
@@ -84933,7 +86141,7 @@ async function getOrRegisterClient(options) {
84933
86141
  }
84934
86142
  }
84935
86143
  function parseRegistrationResponse(data) {
84936
- if (!isRecord5(data))
86144
+ if (!isRecord6(data))
84937
86145
  return null;
84938
86146
  const clientId = data.client_id;
84939
86147
  if (typeof clientId !== "string" || clientId.length === 0)
@@ -84944,7 +86152,7 @@ function parseRegistrationResponse(data) {
84944
86152
  }
84945
86153
  return { clientId };
84946
86154
  }
84947
- function isRecord5(value) {
86155
+ function isRecord6(value) {
84948
86156
  return typeof value === "object" && value !== null;
84949
86157
  }
84950
86158
 
@@ -88290,7 +89498,7 @@ function collectPendingBuiltinAgents(input) {
88290
89498
  if (oracleMetadata) {
88291
89499
  availableAgents.push({
88292
89500
  name: "oracle",
88293
- description: "Read-only consultation agent. High-IQ reasoning specialist for debugging hard problems and high-difficulty architecture design. (Oracle - Matrixx)",
89501
+ description: "Plan Builder agent. Creates detailed, structured work plans from user requests. (Oracle - Matrixx)",
88294
89502
  metadata: oracleMetadata
88295
89503
  });
88296
89504
  }
@@ -89651,7 +90859,7 @@ createMultimodalLookerAgent.mode = MODE5;
89651
90859
  function sanitizeMarkdownTableCell(value) {
89652
90860
  return value.replace(/\r?\n/g, " ").replace(/\|/g, "\\|").replace(/\s+/g, " ").trim();
89653
90861
  }
89654
- function isRecord6(value) {
90862
+ function isRecord7(value) {
89655
90863
  return typeof value === "object" && value !== null;
89656
90864
  }
89657
90865
  function parseRegisteredAgentSummaries(input) {
@@ -89659,7 +90867,7 @@ function parseRegisteredAgentSummaries(input) {
89659
90867
  return [];
89660
90868
  const result = [];
89661
90869
  for (const item of input) {
89662
- if (!isRecord6(item))
90870
+ if (!isRecord7(item))
89663
90871
  continue;
89664
90872
  const name = typeof item.name === "string" ? item.name : undefined;
89665
90873
  if (!name)
@@ -89723,6 +90931,7 @@ var ORACLE_PROMPT_METADATA = {
89723
90931
  var ORACLE_PLAN_BUILDER_METADATA = {
89724
90932
  category: "advisor",
89725
90933
  cost: "EXPENSIVE",
90934
+ keyTrigger: "Complex feature implementation, refactoring, or architecture design \u2192 consult Oracle for planning first",
89726
90935
  triggers: [
89727
90936
  { domain: "Architecture decisions", trigger: "Multi-system tradeoffs, unfamiliar patterns" },
89728
90937
  { domain: "Work planning", trigger: "Complex feature implementation, refactoring" },
@@ -89854,7 +91063,7 @@ Before finalizing answers on architecture, security, or performance:
89854
91063
  <delivery>
89855
91064
  Your response goes directly to the user with no intermediate processing. Make your final message self-contained: a clear recommendation they can act on immediately, covering both what to do and why.
89856
91065
  </delivery>`;
89857
- function createOracleAgent(model) {
91066
+ function createMerovingianAgent(model) {
89858
91067
  const restrictions = createAgentToolRestrictions([
89859
91068
  "write",
89860
91069
  "edit",
@@ -89862,7 +91071,7 @@ function createOracleAgent(model) {
89862
91071
  "task"
89863
91072
  ]);
89864
91073
  const base = {
89865
- description: "Read-only consultation agent. High-IQ reasoning specialist for debugging hard problems and high-difficulty architecture design. (Oracle - Matrixx)",
91074
+ description: "Read-only consultation agent. High-IQ reasoning specialist for debugging hard problems and high-difficulty architecture design. (Merovingian - Matrixx)",
89866
91075
  mode: MODE6,
89867
91076
  model,
89868
91077
  temperature: 0.1,
@@ -89874,7 +91083,7 @@ function createOracleAgent(model) {
89874
91083
  }
89875
91084
  return { ...base, thinking: { type: "enabled", budgetTokens: 16000 } };
89876
91085
  }
89877
- createOracleAgent.mode = MODE6;
91086
+ createMerovingianAgent.mode = MODE6;
89878
91087
 
89879
91088
  // src/agents/operator.ts
89880
91089
  var MODE7 = "subagent";
@@ -91191,7 +92400,7 @@ createExploreAgent.mode = MODE12;
91191
92400
  var agentSources = {
91192
92401
  morpheus: createMorpheusAgent,
91193
92402
  keymaker: createKeymakerAgent,
91194
- merovingian: createOracleAgent,
92403
+ merovingian: createMerovingianAgent,
91195
92404
  operator: createLibrarianAgent,
91196
92405
  trinity: createExploreAgent,
91197
92406
  construct: createMultimodalLookerAgent,
@@ -94481,6 +95690,11 @@ function createToolRegistry(args) {
94481
95690
  } : {};
94482
95691
  const hashlineEnabled = pluginConfig.experimental?.hashline_edit ?? false;
94483
95692
  const hashlineToolsRecord = hashlineEnabled ? { edit: createHashlineEditTool(ctx) } : {};
95693
+ const assemblyEnabled = pluginConfig.assembly?.enabled !== false;
95694
+ const assemblyTool = assemblyEnabled ? createAssemblyTool({
95695
+ manager: managers.backgroundManager,
95696
+ pluginConfig
95697
+ }) : null;
94484
95698
  const allTools = {
94485
95699
  ...builtinTools,
94486
95700
  ...createGrepTools(ctx),
@@ -94497,7 +95711,8 @@ function createToolRegistry(args) {
94497
95711
  slashcommand: slashcommandTool,
94498
95712
  interactive_bash,
94499
95713
  ...taskToolsRecord,
94500
- ...hashlineToolsRecord
95714
+ ...hashlineToolsRecord,
95715
+ ...assemblyTool ? { assembly: assemblyTool } : {}
94501
95716
  };
94502
95717
  const filteredTools = filterDisabledTools(allTools, pluginConfig.disabled_tools);
94503
95718
  return {
@@ -94921,7 +96136,9 @@ var BuiltinSkillNameSchema = exports_external.enum([
94921
96136
  "review-work",
94922
96137
  "quality-gate",
94923
96138
  "software-dev",
94924
- "matrixx-self-config"
96139
+ "matrixx-self-config",
96140
+ "ulw-research",
96141
+ "remove-ai-slops"
94925
96142
  ]);
94926
96143
  // src/config/schema/internal/permission.ts
94927
96144
  var PermissionValueSchema = exports_external.enum(["ask", "allow", "deny"]);
@@ -94987,6 +96204,17 @@ var AgentOverridesSchema = exports_external.object({
94987
96204
  sentinel: AgentOverrideConfigSchema.optional(),
94988
96205
  sati: AgentOverrideConfigSchema.optional()
94989
96206
  });
96207
+ // src/config/schema/assembly.ts
96208
+ var AssemblyConfigSchema = exports_external.object({
96209
+ providers: exports_external.array(exports_external.object({
96210
+ providerID: exports_external.string(),
96211
+ modelID: exports_external.string()
96212
+ })).optional(),
96213
+ default_voters: exports_external.number().int().min(2).max(5).optional(),
96214
+ default_rounds: exports_external.number().int().min(1).max(3).optional(),
96215
+ timeout_ms: exports_external.number().int().min(1e4).max(300000).optional(),
96216
+ enabled: exports_external.boolean().default(false)
96217
+ });
94990
96218
  // src/config/schema/babysitting.ts
94991
96219
  var BabysittingConfigSchema = exports_external.object({
94992
96220
  timeout_ms: exports_external.number().default(120000)
@@ -95066,7 +96294,11 @@ var BuiltinCommandNameSchema = exports_external.enum([
95066
96294
  "start-work",
95067
96295
  "stop-continuation",
95068
96296
  "profile",
95069
- "end-ultrawork"
96297
+ "end-ultrawork",
96298
+ "handoff",
96299
+ "research",
96300
+ "assembly",
96301
+ "ultrawork"
95070
96302
  ]);
95071
96303
  // src/config/schema/comment-checker.ts
95072
96304
  var CommentCheckerConfigSchema = exports_external.object({
@@ -95120,7 +96352,6 @@ var HookNameSchema = exports_external.enum([
95120
96352
  "start-work",
95121
96353
  "architect",
95122
96354
  "unstable-agent-babysitter",
95123
- "task-reminder",
95124
96355
  "task-resume-info",
95125
96356
  "stop-continuation-guard",
95126
96357
  "tasks-todowrite-disabler",
@@ -95321,6 +96552,7 @@ var MatrixxConfigSchema = exports_external.object({
95321
96552
  websearch: WebsearchConfigSchema.optional(),
95322
96553
  tmux: TmuxConfigSchema.optional(),
95323
96554
  morpheus: MorpheusConfigSchema.optional(),
96555
+ assembly: AssemblyConfigSchema.optional(),
95324
96556
  security: SecurityConfigSchema.optional(),
95325
96557
  runtime_fallback: RuntimeFallbackConfigSchema.optional(),
95326
96558
  agent_definitions: AgentDefinitionsConfigSchema.optional(),
@@ -95468,6 +96700,7 @@ function loadPluginConfig(directory, ctx) {
95468
96700
  }
95469
96701
 
95470
96702
  // src/plugin/chat-message.ts
96703
+ init_logger();
95471
96704
  function isStartWorkHookOutput(value) {
95472
96705
  if (typeof value !== "object" || value === null)
95473
96706
  return false;
@@ -95521,7 +96754,9 @@ function createChatMessageHandler2(args) {
95521
96754
  variant: "warning",
95522
96755
  duration: 6000
95523
96756
  }
95524
- }).catch(() => {});
96757
+ }).catch((err) => {
96758
+ log("[plugin] Provider cache toast failed:", err);
96759
+ });
95525
96760
  }
95526
96761
  if (hooks2.matrixLoop) {
95527
96762
  const parts = output.parts;
@@ -95548,11 +96783,11 @@ function createChatMessageHandler2(args) {
95548
96783
  }
95549
96784
 
95550
96785
  // src/plugin/chat-params.ts
95551
- function isRecord7(value) {
96786
+ function isRecord8(value) {
95552
96787
  return typeof value === "object" && value !== null;
95553
96788
  }
95554
96789
  function buildChatParamsInput(raw) {
95555
- if (!isRecord7(raw))
96790
+ if (!isRecord8(raw))
95556
96791
  return null;
95557
96792
  const sessionID = raw.sessionID;
95558
96793
  const agent = raw.agent;
@@ -95563,11 +96798,11 @@ function buildChatParamsInput(raw) {
95563
96798
  return null;
95564
96799
  if (typeof agent !== "string")
95565
96800
  return null;
95566
- if (!isRecord7(model))
96801
+ if (!isRecord8(model))
95567
96802
  return null;
95568
- if (!isRecord7(provider))
96803
+ if (!isRecord8(provider))
95569
96804
  return null;
95570
- if (!isRecord7(message))
96805
+ if (!isRecord8(message))
95571
96806
  return null;
95572
96807
  const providerID = model.providerID;
95573
96808
  const modelID = model.modelID;
@@ -95588,12 +96823,12 @@ function buildChatParamsInput(raw) {
95588
96823
  };
95589
96824
  }
95590
96825
  function isChatParamsOutput(raw) {
95591
- if (!isRecord7(raw))
96826
+ if (!isRecord8(raw))
95592
96827
  return false;
95593
- if (!isRecord7(raw.options)) {
96828
+ if (!isRecord8(raw.options)) {
95594
96829
  raw.options = {};
95595
96830
  }
95596
- return isRecord7(raw.options);
96831
+ return isRecord8(raw.options);
95597
96832
  }
95598
96833
  function createChatParamsHandler(args) {
95599
96834
  return async (input, output) => {
@@ -95611,6 +96846,9 @@ function createChatParamsHandler(args) {
95611
96846
  };
95612
96847
  }
95613
96848
 
96849
+ // src/plugin/event.ts
96850
+ init_logger();
96851
+
95614
96852
  // src/plugin/recent-synthetic-idles.ts
95615
96853
  function pruneRecentSyntheticIdles(args) {
95616
96854
  const { recentSyntheticIdles, recentRealIdles, now, dedupWindowMs } = args;
@@ -95754,7 +96992,9 @@ function createEventHandler2(args) {
95754
96992
  path: { id: sessionID },
95755
96993
  body: { parts: [{ type: "text", text: "continue" }] },
95756
96994
  query: { directory: ctx.directory }
95757
- }).catch(() => {});
96995
+ }).catch((err) => {
96996
+ log("[plugin] Session prompt failed:", err);
96997
+ });
95758
96998
  }
95759
96999
  }
95760
97000
  }
@@ -95868,6 +97108,25 @@ function createToolExecuteAfterHandler2(args) {
95868
97108
  };
95869
97109
  }
95870
97110
 
97111
+ // src/features/assembly-state/manager.ts
97112
+ init_logger();
97113
+ var HOOK_NAME15 = "assembly-state";
97114
+ var disabledSessions = new Set;
97115
+ function isAssemblyDisabled(sessionID) {
97116
+ return disabledSessions.has(sessionID);
97117
+ }
97118
+ function disableAssembly(sessionID) {
97119
+ disabledSessions.add(sessionID);
97120
+ log(`[${HOOK_NAME15}] Assembly disabled for session`, { sessionID });
97121
+ disabledSessions.add(sessionID);
97122
+ log(`[${HOOK_NAME15}] Consensus disabled for session`, { sessionID });
97123
+ }
97124
+ function enableAssembly(sessionID) {
97125
+ disabledSessions.delete(sessionID);
97126
+ log(`[${HOOK_NAME15}] Assembly enabled for session`, { sessionID });
97127
+ disabledSessions.delete(sessionID);
97128
+ log(`[${HOOK_NAME15}] Consensus enabled for session`, { sessionID });
97129
+ }
95871
97130
  // src/plugin/session-agent-resolver.ts
95872
97131
  async function resolveSessionAgent(client2, sessionId, timeoutMs = 5000) {
95873
97132
  try {
@@ -95990,6 +97249,32 @@ function createToolExecuteBeforeHandler2(args) {
95990
97249
  sessionID
95991
97250
  });
95992
97251
  }
97252
+ if (command === "assembly" && sessionID) {
97253
+ const subcommand = rawCommand?.replace(/^\/?assembly\s*/i, "").trim().toLowerCase();
97254
+ if (subcommand === "disable" || subcommand === "off") {
97255
+ disableAssembly(sessionID);
97256
+ } else if (subcommand === "enable" || subcommand === "on") {
97257
+ enableAssembly(sessionID);
97258
+ } else {
97259
+ const state3 = isAssemblyDisabled(sessionID) ? "disabled" : "enabled";
97260
+ log("[assembly] Assembly state check", { sessionID, state: state3 });
97261
+ }
97262
+ }
97263
+ if (command === "ultrawork" && sessionID) {
97264
+ const subcommand = rawCommand?.replace(/^\/?ultrawork\s*/i, "").trim().toLowerCase();
97265
+ if (subcommand === "disable" || subcommand === "off") {
97266
+ disableUltrawork(sessionID);
97267
+ } else if (subcommand === "enable" || subcommand === "on") {
97268
+ enableUltrawork(sessionID);
97269
+ } else {
97270
+ const state3 = getUltraworkState(sessionID) ?? "default (keyword-triggered)";
97271
+ log("[ultrawork] Ultrawork state check", { sessionID, state: state3 });
97272
+ }
97273
+ }
97274
+ if (command === "end-ultrawork" && sessionID) {
97275
+ disableUltrawork(sessionID);
97276
+ log("[end-ultrawork] Ultrawork disabled via /end-ultrawork", { sessionID });
97277
+ }
95993
97278
  }
95994
97279
  };
95995
97280
  }