opencode-matrixx 2.6.4 → 2.6.6

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 (78) hide show
  1. package/dist/agents/oracle/behavioral-summary.d.ts +1 -1
  2. package/dist/agents/oracle/high-accuracy-mode.d.ts +1 -1
  3. package/dist/agents/oracle/interview-mode.d.ts +1 -1
  4. package/dist/agents/oracle/plan-generation.d.ts +1 -1
  5. package/dist/agents/oracle/plan-template.d.ts +1 -1
  6. package/dist/agents/oracle/system-prompt.d.ts +1 -1
  7. package/dist/cli/setup/index.d.ts +1 -0
  8. package/dist/cli/setup/preset-wizard.d.ts +36 -0
  9. package/dist/cli/setup/types.d.ts +5 -0
  10. package/dist/cli.js +499 -64
  11. package/dist/config/index.d.ts +0 -1
  12. package/dist/config/migrations/model-migration.d.ts +1 -2
  13. package/dist/config/preset-applier.d.ts +17 -0
  14. package/dist/config/schema/agent-overrides.d.ts +0 -133
  15. package/dist/config/schema/categories.d.ts +0 -14
  16. package/dist/config/schema/commands.d.ts +1 -1
  17. package/dist/config/schema/hooks.d.ts +1 -0
  18. package/dist/config/schema/matrixx-config.d.ts +29 -144
  19. package/dist/config/schema/model-config.d.ts +0 -22
  20. package/dist/config/schema/model-presets.d.ts +40 -0
  21. package/dist/config/schema/task.d.ts +6 -0
  22. package/dist/config/schema/tasks.d.ts +25 -0
  23. package/dist/config/schema.d.ts +3 -0
  24. package/dist/create-hooks.d.ts +1 -0
  25. package/dist/features/background-agent/handle-index.d.ts +63 -0
  26. package/dist/features/background-agent/handle-index.test.d.ts +1 -0
  27. package/dist/features/background-agent/index.d.ts +1 -0
  28. package/dist/features/background-agent/manager-handles.test.d.ts +1 -0
  29. package/dist/features/background-agent/manager.d.ts +12 -0
  30. package/dist/features/builtin-commands/templates/preset.d.ts +1 -0
  31. package/dist/features/builtin-commands/types.d.ts +1 -1
  32. package/dist/features/preset-state/index.d.ts +1 -0
  33. package/dist/features/preset-state/manager.d.ts +5 -0
  34. package/dist/features/session-state/state.d.ts +6 -0
  35. package/dist/features/task-storage/types.d.ts +2 -0
  36. package/dist/hooks/background-task-blocker/hook.d.ts +10 -0
  37. package/dist/hooks/background-task-blocker/index.d.ts +1 -0
  38. package/dist/hooks/index.d.ts +1 -0
  39. package/dist/hooks/json-error-recovery/hook.d.ts +1 -1
  40. package/dist/hooks/task-continuation-enforcer/continuation-injection.test.d.ts +1 -0
  41. package/dist/hooks/task-continuation-enforcer/staleness.d.ts +7 -1
  42. package/dist/hooks/task-continuation-enforcer/todo.d.ts +8 -0
  43. package/dist/hooks/task-continuation-enforcer/todo.test.d.ts +1 -0
  44. package/dist/hooks/tasks-todowrite-disabler/constants.d.ts +1 -1
  45. package/dist/index.js +923 -624
  46. package/dist/matrixx.schema.json +114 -244
  47. package/dist/mcp/types.d.ts +1 -1
  48. package/dist/mcp/{grep-app.d.ts → white-rabbit.d.ts} +1 -1
  49. package/dist/plugin/hooks/create-core-hooks.d.ts +1 -0
  50. package/dist/plugin/hooks/create-tool-guard-hooks.d.ts +2 -1
  51. package/dist/shared/config-errors.d.ts +3 -0
  52. package/dist/shared/context-mode-prompt.d.ts +12 -0
  53. package/dist/shared/context-mode-prompt.test.d.ts +1 -0
  54. package/dist/shared/index.d.ts +1 -0
  55. package/dist/shared/merge-categories.d.ts +1 -16
  56. package/dist/shared/task-system-gating.d.ts +10 -0
  57. package/dist/tools/background-task/constants.d.ts +0 -1
  58. package/dist/tools/background-task/index.d.ts +1 -1
  59. package/dist/tools/background-task/tools.d.ts +0 -1
  60. package/dist/tools/background-task/types.d.ts +0 -5
  61. package/dist/tools/delegate-task/categories.d.ts +3 -18
  62. package/dist/tools/delegate-task/complexity-constants.d.ts +2 -7
  63. package/dist/tools/delegate-task/executor-types.d.ts +7 -2
  64. package/dist/tools/delegate-task/sync-task.test.d.ts +1 -0
  65. package/dist/tools/delegate-task/timing.d.ts +5 -0
  66. package/dist/tools/delegate-task/timing.test.d.ts +1 -0
  67. package/dist/tools/delegate-task/types.d.ts +5 -2
  68. package/dist/tools/index.d.ts +1 -0
  69. package/dist/tools/preset/index.d.ts +1 -0
  70. package/dist/tools/preset/tools.d.ts +7 -0
  71. package/dist/tools/task/constants.d.ts +1 -0
  72. package/package.json +1 -1
  73. package/dist/config/presets.d.ts +0 -4
  74. package/dist/config/resolve-tiers.d.ts +0 -32
  75. package/dist/features/builtin-commands/templates/profile.d.ts +0 -1
  76. package/dist/shared/model-tiers.d.ts +0 -44
  77. package/dist/shared/tier-resolver.d.ts +0 -26
  78. package/dist/tools/background-task/create-background-task.d.ts +0 -3
package/dist/index.js CHANGED
@@ -29560,7 +29560,7 @@ var init_ulw_research = __esm(() => {
29560
29560
  "look_at",
29561
29561
  "delegate_agent",
29562
29562
  "websearch_web_search_exa",
29563
- "grep_app_searchGitHub",
29563
+ "white_rabbit_searchGitHub",
29564
29564
  "context7_resolve-library-id",
29565
29565
  "context7_query-docs"
29566
29566
  ],
@@ -29671,7 +29671,7 @@ task(
29671
29671
  prompt="""
29672
29672
  Search open source repositories for implementations related to: <CURRENT_LEAD>
29673
29673
 
29674
- Use grep_app_searchGitHub to find real-world code patterns.
29674
+ Use white_rabbit_searchGitHub to find real-world code patterns.
29675
29675
  Focus on:
29676
29676
  - Production implementations in similar projects
29677
29677
  - Reference implementations or canonical examples
@@ -44410,31 +44410,16 @@ var ModelFallbackEntrySchema = exports_external.object({
44410
44410
  model: exports_external.string().min(1),
44411
44411
  variant: exports_external.string().optional()
44412
44412
  });
44413
- var TierSpecSchema = exports_external.object({
44414
- providerPriority: exports_external.array(exports_external.string()),
44415
- modelPattern: exports_external.string().refine((s) => {
44416
- try {
44417
- new RegExp(s);
44418
- return true;
44419
- } catch {
44420
- return false;
44421
- }
44422
- }, { message: "Invalid regex pattern" }),
44423
- fallbackTier: exports_external.string().optional(),
44424
- fallback: exports_external.array(ModelFallbackEntrySchema).optional()
44425
- });
44426
- var TiersSchema = exports_external.record(exports_external.string(), TierSpecSchema);
44427
44413
  var complexityDowngradeValueSchema = exports_external.string().refine((s) => {
44428
- if (s.startsWith("tier:")) {
44429
- const name = s.slice(5);
44430
- return name.length > 0;
44431
- }
44432
- if (s.includes("/")) {
44433
- const parts = s.split("/");
44434
- return parts.length === 2 && parts[0].length > 0 && parts[1].length > 0;
44435
- }
44436
- return false;
44437
- }, { message: "Must be tier:<name> or <provider>/<model>" });
44414
+ if (!s.includes("/"))
44415
+ return false;
44416
+ if (s.startsWith("/") || s.endsWith("/"))
44417
+ return false;
44418
+ if (s.includes("//"))
44419
+ return false;
44420
+ const parts = s.split("/");
44421
+ return parts.length >= 2 && parts.every((p) => p.length > 0);
44422
+ }, { message: "Must be <provider>/<model>" });
44438
44423
  var ComplexityDowngradesSchema = exports_external.record(exports_external.string(), exports_external.record(exports_external.string(), complexityDowngradeValueSchema));
44439
44424
  var AgentModelRequirementsSchema = exports_external.object({
44440
44425
  fallbackChain: exports_external.array(ModelFallbackEntrySchema),
@@ -44861,6 +44846,12 @@ async function resolveCommandsInText(text, depth = 0, maxDepth = 3) {
44861
44846
  return resolved;
44862
44847
  }
44863
44848
  // src/shared/config-errors.ts
44849
+ class ConfigError extends Error {
44850
+ constructor(message) {
44851
+ super(message);
44852
+ this.name = "ConfigError";
44853
+ }
44854
+ }
44864
44855
  var configLoadErrors = [];
44865
44856
  function getConfigLoadErrors() {
44866
44857
  return configLoadErrors;
@@ -48112,7 +48103,7 @@ var TARGET_TOOLS = new Set([
48112
48103
  "context7_query-docs",
48113
48104
  "websearch_web_search_exa",
48114
48105
  "context7_get-library-docs",
48115
- "grep_app_searchgithub"
48106
+ "white_rabbit_searchgithub"
48116
48107
  ]);
48117
48108
  var AGENT_TOOLS = new Set([
48118
48109
  "task",
@@ -48425,8 +48416,11 @@ function getPlanProgress(planPath) {
48425
48416
  const content = readFileSync10(planPath, "utf-8");
48426
48417
  const uncheckedMatches = content.match(/^[-*]\s*\[\s*\]/gm) || [];
48427
48418
  const checkedMatches = content.match(/^[-*]\s*\[[xX]\]/gm) || [];
48428
- const total = uncheckedMatches.length + checkedMatches.length;
48429
- const completed = checkedMatches.length;
48419
+ const numberedUnchecked = content.match(/^[-*]\s*\[\s*\]\s*\d+\./gm) || [];
48420
+ const numberedChecked = content.match(/^[-*]\s*\[[xX]\]\s*\d+\./gm) || [];
48421
+ const useNumbered = numberedUnchecked.length + numberedChecked.length > 0;
48422
+ const total = useNumbered ? numberedUnchecked.length + numberedChecked.length : uncheckedMatches.length + checkedMatches.length;
48423
+ const completed = useNumbered ? numberedChecked.length : checkedMatches.length;
48430
48424
  return {
48431
48425
  total,
48432
48426
  completed,
@@ -48498,10 +48492,14 @@ function registerSubagentSession(sessionID, parentSessionID) {
48498
48492
  subagentSessions.add(sessionID);
48499
48493
  subagentParentMap.set(sessionID, parentSessionID);
48500
48494
  }
48495
+ function unregisterSubagentSession(sessionID) {
48496
+ subagentSessions.delete(sessionID);
48497
+ subagentParentMap.delete(sessionID);
48498
+ }
48501
48499
  function getSubagentSessionIDs(parentSessionID) {
48502
48500
  const ids = [];
48503
- for (const [subID, parentID] of subagentParentMap) {
48504
- if (parentID === parentSessionID)
48501
+ for (const subID of subagentSessions) {
48502
+ if (subagentParentMap.get(subID) === parentSessionID)
48505
48503
  ids.push(subID);
48506
48504
  }
48507
48505
  return ids;
@@ -50255,79 +50253,49 @@ This prevents it from being picked up again.
50255
50253
  - DO begin working immediately if the user included a task with /pickup
50256
50254
  `;
50257
50255
 
50258
- // src/features/builtin-commands/templates/profile.ts
50259
- var PROFILE_TEMPLATE = `# /profile Command
50256
+ // src/features/builtin-commands/templates/preset.ts
50257
+ var PRESET_TEMPLATE = `You are managing the active model preset.
50258
+
50259
+ This command is provided by the Matrixx plugin. It uses the built-in \`preset\` tool to list, show, or switch model presets \u2014 no external scripts or direct config edits needed.
50260
50260
 
50261
50261
  ## Usage
50262
50262
 
50263
50263
  \`\`\`
50264
- /profile \u2192 List available profiles and show current
50265
- /profile list \u2192 List available profiles
50266
- /profile show \u2192 Show currently active profile
50267
- /profile set <name> \u2192 Change profile (edits project config, instructs to start new session)
50268
- /profile set <name> --global \u2192 Change profile globally (~/.config/opencode/matrixx.jsonc)
50269
- /profile set <name> --project \u2192 Change profile project-scoped (.opencode/matrixx.jsonc)
50264
+ /preset \u2192 List available presets and mark the active one
50265
+ /preset list \u2192 List available presets
50266
+ /preset show [<name>] \u2192 Show a preset's agent/category model assignments (defaults to active)
50267
+ /preset set <name> \u2192 Switch the active preset for the current session (live)
50268
+ /preset set <name> --save [--global|--project] \u2192 Also persist active_preset to config
50270
50269
  \`\`\`
50271
50270
 
50272
- ## Available Profiles
50273
-
50274
- | Profile | Description | Key Characteristic |
50275
- |---------|-------------|-------------------|
50276
- | free | Zero-cost, uses only free/open models | Kimi K2.5 Free, Grok (free), GLM |
50277
- | budget | Cost-optimized for tight budgets | Sonnet/Haiku mix, no Opus |
50278
- | economy | Balanced cost and capability | Primarily Sonnet/Haiku |
50279
- | balanced | Default \u2014 best overall value | Opus/Sonnet/Haiku tiered |
50280
- | performance | Maximum quality, no compromises | Opus for all heavy agents |
50281
- | go | Premium OpenCode Go subscription | Kimi K2.6, DeepSeek V4 Pro, GLM 5.1 |
50282
-
50283
- ## How It Works
50284
-
50285
- Each profile sets the \`model\` field on every agent and category in Matrixx's configuration. When you switch profiles, the agent models are swapped accordingly.
50286
-
50287
- The profile name is stored in your \`matrixx.json\` or \`matrixx.jsonc\` config file. On the next session, Matrixx reads it and applies the profile's model mapping, merging it with any explicit per-agent overrides you may have set.
50288
-
50289
- ## Task: Change the Profile
50290
-
50291
- Follow these steps:
50292
-
50293
- ### Step 1: Know the Current Config Location
50294
-
50295
- Matrixx reads config from two locations (user config wins over project config):
50296
- - **Project**: \`.opencode/matrixx.json\` or \`.opencode/matrixx.jsonc\`
50297
- - **Global**: \`~/.config/opencode/matrixx.json\` or \`~/.config/opencode/matrixx.jsonc\`
50298
-
50299
- ### Step 2: Determine Which Config File to Edit
50271
+ ## Step 1: Determine the action
50300
50272
 
50301
- - \`--global\` \u2192 Edit \`~/.config/opencode/matrixx.jsonc\`
50302
- - \`--project\` (default) \u2192 Edit \`.opencode/matrixx.jsonc\`
50303
- - If no flag and neither file exists, use \`--project\`
50273
+ Parse the arguments passed to this command:
50304
50274
 
50305
- ### Step 3: Read the Target Config File
50275
+ - No arguments or \`list\` \u2192 call \`preset\` with \`action: "list"\`.
50276
+ - \`show\` \u2192 call \`preset\` with \`action: "show"\` (optionally \`name\`).
50277
+ - \`set <name>\` \u2192 call \`preset\` with \`action: "set"\`, \`name: "<name>"\`.
50278
+ - \`set <name> --save\` \u2192 also pass \`save: true\`; \`--global\` \u2192 \`scope: "global"\`, \`--project\` (default) \u2192 \`scope: "project"\`.
50306
50279
 
50307
- Use the \`read\` tool to read the config file.
50280
+ ## Step 2: Call the built-in \`preset\` tool
50308
50281
 
50309
- ### Step 4: Validate the Requested Profile
50282
+ Use the \`preset\` tool with the resolved arguments. The tool validates the preset name, applies the session overlay (delegate-task categories switch immediately), and \u2014 with \`--save\` \u2014 persists \`active_preset\` to the project (\`.opencode/matrixx.jsonc\`) or global (\`~/.config/opencode/matrixx.jsonc\`) config.
50310
50283
 
50311
- If the user asked for \`/profile set <name>\`, verify \`<name>\` is in the table above. If not, report the valid profiles and stop.
50284
+ The tool handles all validation and file operations \u2014 do NOT edit config files directly.
50312
50285
 
50313
- ### Step 5: Update the \`profile\` Key
50286
+ ## Step 3: Confirm and instruct
50314
50287
 
50315
- Set \`"profile": "<name>"\` in the config JSON. If the \`profile\` key already exists, update it. If it doesn't exist, add it at the top level. Do NOT modify any other keys.
50316
-
50317
- ### Step 6: Write the Config File Back
50318
-
50319
- Use the \`write\` tool (or \`edit\` tool for minimal changes) to save the updated config.
50320
-
50321
- ### Step 7: Confirm and Advise
50288
+ After a successful switch:
50322
50289
 
50323
- Tell the user:
50324
- 1. The profile has been changed to \`<name>\`
50325
- 2. For the change to take effect, they MUST start a new session (\`/new\` or restart OpenCode)
50326
- 3. If they want session-level switching without restarting, suggest using the profile with \`--global\` or \`--project\` as appropriate
50290
+ 1. Report the tool's output to the user.
50291
+ 2. Note that delegate-task categories use the new preset immediately (session-scoped live switch).
50292
+ 3. If \`--save\` was used, tell the user the persisted \`active_preset\` takes effect for builtin agents on the next session.
50327
50293
 
50328
- ### Step 8: If \`/profile\` Was Called Without Arguments
50294
+ ## Important constraints
50329
50295
 
50330
- Just list the available profiles and show the current configuration's profile value.`;
50296
+ - Use the built-in \`preset\` tool. Do NOT bypass it by editing config files directly.
50297
+ - Do not invent preset names \u2014 if the requested name is unknown, report the available presets and stop.
50298
+ - \`--save\` without a scope defaults to project scope.`;
50331
50299
 
50332
50300
  // src/features/builtin-commands/templates/refactor.ts
50333
50301
  var REFACTOR_TEMPLATE = `# Intelligent Refactor Command
@@ -51596,16 +51564,16 @@ ${REMOVE_DEADCODE_TEMPLATE}
51596
51564
  </command-instruction>`,
51597
51565
  argumentHint: "[target-path] [--scope=<file|module|project>] [--dry-run]"
51598
51566
  },
51599
- profile: {
51600
- description: '(builtin) View recommended tier configurations (replaces the v2.x profile system; use `tier: "..."` in matrixx.jsonc instead)',
51567
+ preset: {
51568
+ description: "(builtin) Switch the active model preset (session-scoped live switch; --save persists to config)",
51601
51569
  template: `<command-instruction>
51602
- ${PROFILE_TEMPLATE}
51570
+ ${PRESET_TEMPLATE}
51603
51571
  </command-instruction>
51604
51572
 
51605
51573
  <user-request>
51606
51574
  $ARGUMENTS
51607
51575
  </user-request>`,
51608
- argumentHint: "[list|show|set <name> [--global|--project]]"
51576
+ argumentHint: "[list|show|set <name> [--save [--global|--project]]]"
51609
51577
  },
51610
51578
  "end-ultrawork": {
51611
51579
  description: "(builtin) Deactivate ultrawork mode and return to default Matrixx behavior for the current session",
@@ -52670,6 +52638,16 @@ function createBackgroundNotificationHook(manager) {
52670
52638
  event: eventHandler
52671
52639
  };
52672
52640
  }
52641
+ // src/hooks/background-task-blocker/hook.ts
52642
+ function createBackgroundTaskBlockerHook() {
52643
+ return {
52644
+ "tool.execute.before": async (input, _output) => {
52645
+ if (input.tool === "background_task") {
52646
+ throw new Error('Tool "background_task" is disabled. Use the "task" tool with category/subagent_type and run_in_background=true for agent delegation.');
52647
+ }
52648
+ }
52649
+ };
52650
+ }
52673
52651
  // src/hooks/bash-file-read-guard.ts
52674
52652
  var WARNING_MESSAGE = "Prefer the Read tool over `cat`/`head`/`tail` for reading file contents. " + "The Read tool provides line numbers and hash anchors for precise editing.";
52675
52653
  var FILE_READ_PATTERNS = [
@@ -59099,7 +59077,7 @@ var JSON_ERROR_TOOL_EXCLUDE_LIST = [
59099
59077
  "grep",
59100
59078
  "webfetch",
59101
59079
  "look_at",
59102
- "grep_app_searchgithub",
59080
+ "white_rabbit_searchgithub",
59103
59081
  "websearch_web_search_exa"
59104
59082
  ];
59105
59083
  var JSON_ERROR_PATTERNS = [
@@ -60125,16 +60103,38 @@ init_logger();
60125
60103
  import { randomUUID } from "crypto";
60126
60104
  import { existsSync as existsSync36, mkdirSync as mkdirSync14, readdirSync as readdirSync11, readFileSync as readFileSync30, renameSync as renameSync3, unlinkSync as unlinkSync8, writeFileSync as writeFileSync16 } from "fs";
60127
60105
  import { basename as basename5, dirname as dirname10, isAbsolute as isAbsolute3, join as join44 } from "path";
60106
+
60107
+ // src/shared/task-system-gating.ts
60108
+ var TASK_SYSTEM_DEFAULT = true;
60109
+ function resolveTasksConfig(config2) {
60110
+ const canonical = config2?.tasks;
60111
+ const legacyTasks = config2?.morpheus?.tasks;
60112
+ const legacyPoll = config2?.task?.pollTimeoutMs;
60113
+ return {
60114
+ enabled: canonical?.enabled ?? config2?.experimental?.task_system ?? config2?.new_task_system_enabled ?? TASK_SYSTEM_DEFAULT,
60115
+ scope: canonical?.scope ?? legacyTasks?.scope ?? "project",
60116
+ storage_path: canonical?.storage_path ?? legacyTasks?.storage_path,
60117
+ task_list_id: canonical?.task_list_id ?? legacyTasks?.task_list_id,
60118
+ stale_after_hours: canonical?.stale_after_hours ?? legacyTasks?.stale_after_hours,
60119
+ session_scoped: canonical?.session_scoped ?? legacyTasks?.session_scoped ?? true,
60120
+ pollTimeoutMs: canonical?.pollTimeoutMs ?? legacyPoll
60121
+ };
60122
+ }
60123
+ function isTaskSystemEnabled(config2) {
60124
+ return resolveTasksConfig(config2).enabled;
60125
+ }
60126
+
60127
+ // src/features/task-storage/storage.ts
60128
60128
  function getProjectTaskDir(directory) {
60129
60129
  return join44(directory, ".matrixx", "tasks");
60130
60130
  }
60131
60131
  function getTaskDir(config2 = {}, directory) {
60132
- const tasksConfig = config2.morpheus?.tasks;
60133
- const storagePath = tasksConfig?.storage_path;
60132
+ const tasksConfig = resolveTasksConfig(config2);
60133
+ const storagePath = tasksConfig.storage_path;
60134
60134
  if (storagePath) {
60135
60135
  return isAbsolute3(storagePath) ? storagePath : join44(directory ?? process.cwd(), storagePath);
60136
60136
  }
60137
- if (tasksConfig?.scope === "global" || !directory) {
60137
+ if (tasksConfig.scope === "global" || !directory) {
60138
60138
  const configDir = getOpenCodeConfigDir({ binary: "opencode" });
60139
60139
  const listId = resolveTaskListId(config2);
60140
60140
  return join44(configDir, "tasks", listId);
@@ -60148,7 +60148,7 @@ function resolveTaskListId(config2 = {}) {
60148
60148
  const envId = process.env.ULTRAWORK_TASK_LIST_ID?.trim();
60149
60149
  if (envId)
60150
60150
  return sanitizePathSegment(envId);
60151
- const configId = config2.morpheus?.tasks?.task_list_id?.trim();
60151
+ const configId = resolveTasksConfig(config2).task_list_id?.trim();
60152
60152
  if (configId)
60153
60153
  return sanitizePathSegment(configId);
60154
60154
  return sanitizePathSegment(basename5(process.cwd()));
@@ -60369,6 +60369,7 @@ async function getGitHead(directory) {
60369
60369
 
60370
60370
  // src/tools/task/constants.ts
60371
60371
  var TASK_ID_PATTERN = /^T-[A-Za-z0-9]+(-[A-Za-z0-9]+)*$/;
60372
+ var DEDUP_WINDOW_MS = 10 * 60 * 1000;
60372
60373
 
60373
60374
  // src/tools/task/types.ts
60374
60375
  var TaskStatusSchema = exports_external.enum(["pending", "in_progress", "completed", "deleted"]);
@@ -64921,7 +64922,7 @@ init_logger();
64921
64922
 
64922
64923
  // src/hooks/task-continuation-enforcer/idle-event.ts
64923
64924
  import { existsSync as existsSync43, readdirSync as readdirSync15 } from "fs";
64924
- import { join as join51 } from "path";
64925
+ import { join as join52 } from "path";
64925
64926
  init_logger();
64926
64927
 
64927
64928
  // src/hooks/task-continuation-enforcer/abort-detection.ts
@@ -64945,14 +64946,15 @@ init_logger();
64945
64946
 
64946
64947
  // src/hooks/task-continuation-enforcer/continuation-injection.ts
64947
64948
  import { existsSync as existsSync42, readdirSync as readdirSync14 } from "fs";
64948
- import { join as join50 } from "path";
64949
+ import { join as join51 } from "path";
64949
64950
  init_logger();
64950
64951
  // src/hooks/task-continuation-enforcer/staleness.ts
64951
64952
  import { statSync as statSync8 } from "fs";
64953
+ import { join as join50 } from "path";
64952
64954
  var DEFAULT_STALE_AFTER_HOURS = 24;
64953
64955
  var HOUR_MS = 60 * 60 * 1000;
64954
64956
  function getStaleAfterMs(config2) {
64955
- const hours = config2?.morpheus?.tasks?.stale_after_hours ?? DEFAULT_STALE_AFTER_HOURS;
64957
+ const hours = resolveTasksConfig(config2).stale_after_hours ?? DEFAULT_STALE_AFTER_HOURS;
64956
64958
  return hours * HOUR_MS;
64957
64959
  }
64958
64960
  function getTaskAgeMs(taskPath) {
@@ -64967,6 +64969,9 @@ function isTaskStale(taskPath, staleAfterMs) {
64967
64969
  const ageMs = getTaskAgeMs(taskPath);
64968
64970
  return ageMs !== null && ageMs > staleAfterMs;
64969
64971
  }
64972
+ function filterFreshIncompleteTasks(tasks, taskDir, staleAfterMs) {
64973
+ return tasks.filter((t) => !isTaskStale(join50(taskDir, `${t.id}.json`), staleAfterMs));
64974
+ }
64970
64975
  function formatTaskAge(ageMs) {
64971
64976
  const hours = Math.floor(ageMs / HOUR_MS);
64972
64977
  if (hours >= 24) {
@@ -64987,6 +64992,17 @@ function getIncompleteTasks(tasks) {
64987
64992
  return task.blockedBy.every((bid) => byId.get(bid)?.status === "completed");
64988
64993
  });
64989
64994
  }
64995
+ function dropSubtasksWithResolvedParent(tasks) {
64996
+ const byId = new Map(tasks.map((t) => [t.id, t]));
64997
+ return tasks.filter((task) => {
64998
+ if (!task.parentID)
64999
+ return true;
65000
+ const parent = byId.get(task.parentID);
65001
+ if (!parent)
65002
+ return true;
65003
+ return parent.status !== "completed" && parent.status !== "deleted";
65004
+ });
65005
+ }
64990
65006
  function filterTasksBySession(tasks, options) {
64991
65007
  if (options.sessionScoped === false)
64992
65008
  return tasks;
@@ -65059,10 +65075,10 @@ async function injectContinuation(args) {
65059
65075
  if (parsed)
65060
65076
  tasks.push(parsed);
65061
65077
  }
65062
- filteredTasks = filterTasksBySession(tasks, {
65078
+ filteredTasks = filterTasksBySession(dropSubtasksWithResolvedParent(tasks), {
65063
65079
  sessionID,
65064
65080
  subagentIDs: getSubagentSessionIDs(sessionID),
65065
- sessionScoped: config2?.morpheus?.tasks?.session_scoped !== false
65081
+ sessionScoped: resolveTasksConfig(config2).session_scoped
65066
65082
  });
65067
65083
  total = filteredTasks.length;
65068
65084
  if (total === 0) {
@@ -65085,9 +65101,10 @@ async function injectContinuation(args) {
65085
65101
  isBootstrap = true;
65086
65102
  stateForBootstrap._bootstrap = undefined;
65087
65103
  }
65088
- const freshIncompleteCount = isBootstrap ? 1 : getIncompleteTasks(filteredTasks).length;
65104
+ const staleAfterMs = getStaleAfterMs(config2);
65105
+ const freshIncompleteCount = isBootstrap ? 1 : filterFreshIncompleteTasks(getIncompleteTasks(filteredTasks), taskDir, staleAfterMs).length;
65089
65106
  if (!isBootstrap && freshIncompleteCount === 0) {
65090
- log(`[${HOOK_NAME14}] Skipped injection: no incomplete tasks`, { sessionID, total });
65107
+ log(`[${HOOK_NAME14}] Skipped injection: only stale tasks remain`, { sessionID, total });
65091
65108
  return;
65092
65109
  }
65093
65110
  let agentName = resolvedInfo?.agent;
@@ -65122,14 +65139,14 @@ async function injectContinuation(args) {
65122
65139
  prompt = BOOTSTRAP_PROMPT;
65123
65140
  } else {
65124
65141
  const incompleteTasks = getIncompleteTasks(filteredTasks);
65125
- const staleAfterMs = getStaleAfterMs(config2);
65142
+ const freshIncompleteTasks = filterFreshIncompleteTasks(incompleteTasks, taskDir, staleAfterMs);
65126
65143
  const taskList = incompleteTasks.map((task) => {
65127
- const ageMs = getTaskAgeMs(join50(taskDir, `${task.id}.json`));
65144
+ const ageMs = getTaskAgeMs(join51(taskDir, `${task.id}.json`));
65128
65145
  const staleSuffix = ageMs !== null && ageMs > staleAfterMs ? ` (stale: ${formatTaskAge(ageMs)})` : "";
65129
65146
  return `- [${task.status}] ${task.subject} (${task.id})${staleSuffix}`;
65130
65147
  }).join(`
65131
65148
  `);
65132
- const staleCount = incompleteTasks.filter((t) => isTaskStale(join50(taskDir, `${t.id}.json`), staleAfterMs)).length;
65149
+ const staleCount = incompleteTasks.length - freshIncompleteTasks.length;
65133
65150
  const staleNote = staleCount > 0 ? `
65134
65151
 
65135
65152
  Note: ${staleCount} remaining task(s) have had no activity for >${Math.round(staleAfterMs / 3600000)}h and may be orphaned. Mark them completed/deleted if superseded.` : "";
@@ -65322,10 +65339,10 @@ async function handleSessionIdle(args) {
65322
65339
  if (parsed)
65323
65340
  tasks.push(parsed);
65324
65341
  }
65325
- const filteredTasks = filterTasksBySession(tasks, {
65342
+ const filteredTasks = filterTasksBySession(dropSubtasksWithResolvedParent(tasks), {
65326
65343
  sessionID,
65327
65344
  subagentIDs: getSubagentSessionIDs(sessionID),
65328
- sessionScoped: config2?.morpheus?.tasks?.session_scoped !== false
65345
+ sessionScoped: resolveTasksConfig(config2).session_scoped
65329
65346
  });
65330
65347
  total = filteredTasks.length;
65331
65348
  if (total === 0) {
@@ -65342,7 +65359,7 @@ async function handleSessionIdle(args) {
65342
65359
  const incompleteTasks = getIncompleteTasks(filteredTasks);
65343
65360
  incompleteCount = incompleteTasks.length;
65344
65361
  const staleAfterMs = getStaleAfterMs(config2);
65345
- const staleIncompleteCount = incompleteTasks.filter((t) => isTaskStale(join51(taskDir, `${t.id}.json`), staleAfterMs)).length;
65362
+ const staleIncompleteCount = incompleteTasks.filter((t) => isTaskStale(join52(taskDir, `${t.id}.json`), staleAfterMs)).length;
65346
65363
  const activeIncompleteCount = incompleteCount - staleIncompleteCount;
65347
65364
  if (activeIncompleteCount === 0 && staleIncompleteCount > 0) {
65348
65365
  log(`[${HOOK_NAME14}] Skipped: only stale tasks remain`, {
@@ -65790,7 +65807,7 @@ var TODO_FETCH_ERROR_FRAGMENT = "Failed to fetch todos for task-notepad";
65790
65807
  // src/hooks/task-notepad/hook.ts
65791
65808
  init_logger();
65792
65809
  import { existsSync as existsSync44, mkdirSync as mkdirSync16, readdirSync as readdirSync16, readFileSync as readFileSync33, statSync as statSync9, writeFileSync as writeFileSync18 } from "fs";
65793
- import { dirname as dirname13, join as join52 } from "path";
65810
+ import { dirname as dirname13, join as join53 } from "path";
65794
65811
  function createTaskNotepadHook(ctx) {
65795
65812
  return {
65796
65813
  "tool.execute.after": async (input, output) => {
@@ -65841,7 +65858,7 @@ async function fetchTodos(ctx, sessionID) {
65841
65858
  }
65842
65859
  }
65843
65860
  function resolveActivePlan(directory) {
65844
- const plansDir = join52(directory, PLAN_DIR_NAME, PLANS_SUBDIR);
65861
+ const plansDir = join53(directory, PLAN_DIR_NAME, PLANS_SUBDIR);
65845
65862
  if (!existsSync44(plansDir))
65846
65863
  return null;
65847
65864
  let entries;
@@ -65854,7 +65871,7 @@ function resolveActivePlan(directory) {
65854
65871
  for (const entry of entries) {
65855
65872
  if (!entry.endsWith(PLAN_EXTENSION))
65856
65873
  continue;
65857
- const fullPath = join52(plansDir, entry);
65874
+ const fullPath = join53(plansDir, entry);
65858
65875
  try {
65859
65876
  const stats = statSync9(fullPath);
65860
65877
  if (!stats.isFile())
@@ -65897,7 +65914,7 @@ function appendCompletion(directory, planName, todo) {
65897
65914
  log(`[${HOOK_NAME16}] Appended completion to notepad`, { plan: planName, taskID: todo.id });
65898
65915
  }
65899
65916
  function notepadPathFor(directory, planName, taskID) {
65900
- return join52(directory, PLAN_DIR_NAME, NOTEPAD_DIR_NAME, planName, TASKS_SUBDIR, `${taskID}${NOTEPAD_EXTENSION}`);
65917
+ return join53(directory, PLAN_DIR_NAME, NOTEPAD_DIR_NAME, planName, TASKS_SUBDIR, `${taskID}${NOTEPAD_EXTENSION}`);
65901
65918
  }
65902
65919
  function renderNotepad(todo) {
65903
65920
  const priority = todo.priority ?? "medium";
@@ -65963,15 +65980,9 @@ to continue: task(session_id="${sessionId}", prompt="...")`;
65963
65980
  "tool.execute.after": toolExecuteAfter
65964
65981
  };
65965
65982
  }
65966
- // src/shared/task-system-gating.ts
65967
- var TASK_SYSTEM_DEFAULT = true;
65968
- function isTaskSystemEnabled(config2) {
65969
- return config2?.experimental?.task_system ?? TASK_SYSTEM_DEFAULT;
65970
- }
65971
-
65972
65983
  // src/hooks/tasks-todowrite-disabler/constants.ts
65973
65984
  var BLOCKED_TOOLS2 = ["TodoWrite", "TodoRead"];
65974
- var REPLACEMENT_MESSAGE = `TodoRead/TodoWrite are DISABLED because experimental.task_system is enabled.
65985
+ var REPLACEMENT_MESSAGE = `TodoRead/TodoWrite are DISABLED because tasks.enabled is true.
65975
65986
 
65976
65987
  **ACTION REQUIRED**: RE-REGISTER what you were about to write as Todo using Task tools NOW. Then ASSIGN yourself and START WORKING immediately.
65977
65988
 
@@ -67579,7 +67590,7 @@ function createWebFetchRedirectGuardHook(_ctx) {
67579
67590
  }
67580
67591
  // src/hooks/write-existing-file-guard/hook.ts
67581
67592
  import { existsSync as existsSync45 } from "fs";
67582
- import { isAbsolute as isAbsolute4, join as join53, normalize, resolve as resolve6, sep } from "path";
67593
+ import { isAbsolute as isAbsolute4, join as join54, normalize, resolve as resolve6, sep } from "path";
67583
67594
  function createWriteExistingFileGuardHook(ctx) {
67584
67595
  return {
67585
67596
  "tool.execute.before": async (input, output) => {
@@ -67594,7 +67605,7 @@ function createWriteExistingFileGuardHook(ctx) {
67594
67605
  }
67595
67606
  const resolvedPath = normalize(isAbsolute4(filePath) ? filePath : resolve6(ctx.directory, filePath));
67596
67607
  if (existsSync45(resolvedPath)) {
67597
- const matrixxRoot = join53(ctx.directory, ".matrixx") + sep;
67608
+ const matrixxRoot = join54(ctx.directory, ".matrixx") + sep;
67598
67609
  const isMatrixMarkdown = resolvedPath.startsWith(matrixxRoot) && resolvedPath.endsWith(".md");
67599
67610
  const normalizedPath = resolvedPath.replace(/\\/g, "/");
67600
67611
  const isPlansPath = normalizedPath.includes(".matrixx/plans");
@@ -68040,7 +68051,7 @@ function getLanguageId(ext) {
68040
68051
  }
68041
68052
  // src/tools/lsp/server-installation.ts
68042
68053
  import { existsSync as existsSync46 } from "fs";
68043
- import { join as join54 } from "path";
68054
+ import { join as join55 } from "path";
68044
68055
  function isServerInstalled(command) {
68045
68056
  if (command.length === 0)
68046
68057
  return false;
@@ -68068,23 +68079,23 @@ function isServerInstalled(command) {
68068
68079
  const paths = pathEnv.split(pathSeparator);
68069
68080
  for (const p of paths) {
68070
68081
  for (const suffix of exts) {
68071
- if (existsSync46(join54(p, cmd + suffix))) {
68082
+ if (existsSync46(join55(p, cmd + suffix))) {
68072
68083
  return true;
68073
68084
  }
68074
68085
  }
68075
68086
  }
68076
68087
  const cwd = process.cwd();
68077
68088
  const configDir = getOpenCodeConfigDir({ binary: "opencode" });
68078
- const dataDir = join54(getDataDir(), "opencode");
68089
+ const dataDir = join55(getDataDir(), "opencode");
68079
68090
  const additionalBases = [
68080
- join54(cwd, "node_modules", ".bin"),
68081
- join54(configDir, "bin"),
68082
- join54(configDir, "node_modules", ".bin"),
68083
- join54(dataDir, "bin")
68091
+ join55(cwd, "node_modules", ".bin"),
68092
+ join55(configDir, "bin"),
68093
+ join55(configDir, "node_modules", ".bin"),
68094
+ join55(dataDir, "bin")
68084
68095
  ];
68085
68096
  for (const base of additionalBases) {
68086
68097
  for (const suffix of exts) {
68087
- if (existsSync46(join54(base, cmd + suffix))) {
68098
+ if (existsSync46(join55(base, cmd + suffix))) {
68088
68099
  return true;
68089
68100
  }
68090
68101
  }
@@ -68096,7 +68107,7 @@ function isServerInstalled(command) {
68096
68107
  }
68097
68108
  // src/tools/lsp/server-config-loader.ts
68098
68109
  import { existsSync as existsSync47, readFileSync as readFileSync34 } from "fs";
68099
- import { join as join55 } from "path";
68110
+ import { join as join56 } from "path";
68100
68111
  function loadJsonFile(path12) {
68101
68112
  if (!existsSync47(path12))
68102
68113
  return null;
@@ -68110,9 +68121,9 @@ function getConfigPaths3() {
68110
68121
  const cwd = process.cwd();
68111
68122
  const configDir = getOpenCodeConfigDir({ binary: "opencode" });
68112
68123
  return {
68113
- project: detectConfigFile(join55(cwd, ".opencode", "matrixx")).path,
68114
- user: detectConfigFile(join55(configDir, "matrixx")).path,
68115
- opencode: detectConfigFile(join55(configDir, "opencode")).path
68124
+ project: detectConfigFile(join56(cwd, ".opencode", "matrixx")).path,
68125
+ user: detectConfigFile(join56(configDir, "matrixx")).path,
68126
+ opencode: detectConfigFile(join56(configDir, "opencode")).path
68116
68127
  };
68117
68128
  }
68118
68129
  function loadAllConfigs() {
@@ -68961,7 +68972,7 @@ class LSPServerManager {
68961
68972
  var lspManager = LSPServerManager.getInstance();
68962
68973
  // src/tools/lsp/lsp-client-wrapper.ts
68963
68974
  import { existsSync as existsSync49, statSync as statSync11 } from "fs";
68964
- import { dirname as dirname14, extname as extname3, join as join56, resolve as resolve8 } from "path";
68975
+ import { dirname as dirname14, extname as extname3, join as join57, resolve as resolve8 } from "path";
68965
68976
  import { fileURLToPath as fileURLToPath3 } from "url";
68966
68977
  function findWorkspaceRoot(filePath) {
68967
68978
  let dir = resolve8(filePath);
@@ -68977,7 +68988,7 @@ function findWorkspaceRoot(filePath) {
68977
68988
  let prevDir = "";
68978
68989
  while (dir !== prevDir) {
68979
68990
  for (const marker of markers) {
68980
- if (existsSync49(join56(dir, marker))) {
68991
+ if (existsSync49(join57(dir, marker))) {
68981
68992
  return dir;
68982
68993
  }
68983
68994
  }
@@ -81829,13 +81840,13 @@ var DEFAULT_MAX_MATCHES = 500;
81829
81840
  // src/tools/ast-grep/sg-cli-path.ts
81830
81841
  import { existsSync as existsSync51, statSync as statSync12 } from "fs";
81831
81842
  import { createRequire as createRequire4 } from "module";
81832
- import { dirname as dirname15, join as join58 } from "path";
81843
+ import { dirname as dirname15, join as join59 } from "path";
81833
81844
 
81834
81845
  // src/tools/ast-grep/downloader.ts
81835
81846
  import { existsSync as existsSync50 } from "fs";
81836
81847
  import { createRequire as createRequire3 } from "module";
81837
81848
  import { homedir as homedir8 } from "os";
81838
- import { join as join57 } from "path";
81849
+ import { join as join58 } from "path";
81839
81850
  init_logger();
81840
81851
  var REPO2 = "ast-grep/ast-grep";
81841
81852
  var DEFAULT_VERSION = "0.40.0";
@@ -81860,12 +81871,12 @@ var PLATFORM_MAP2 = {
81860
81871
  function getCacheDir3() {
81861
81872
  if (process.platform === "win32") {
81862
81873
  const localAppData = process.env.LOCALAPPDATA || process.env.APPDATA;
81863
- const base2 = localAppData || join57(homedir8(), "AppData", "Local");
81864
- return join57(base2, "matrixx", "bin");
81874
+ const base2 = localAppData || join58(homedir8(), "AppData", "Local");
81875
+ return join58(base2, "matrixx", "bin");
81865
81876
  }
81866
81877
  const xdgCache = process.env.XDG_CACHE_HOME;
81867
- const base = xdgCache || join57(homedir8(), ".cache");
81868
- return join57(base, "matrixx", "bin");
81878
+ const base = xdgCache || join58(homedir8(), ".cache");
81879
+ return join58(base, "matrixx", "bin");
81869
81880
  }
81870
81881
  function getBinaryName3() {
81871
81882
  return process.platform === "win32" ? "sg.exe" : "sg";
@@ -81882,7 +81893,7 @@ async function downloadAstGrep(version3 = DEFAULT_VERSION) {
81882
81893
  }
81883
81894
  const cacheDir = getCacheDir3();
81884
81895
  const binaryName = getBinaryName3();
81885
- const binaryPath = join57(cacheDir, binaryName);
81896
+ const binaryPath = join58(cacheDir, binaryName);
81886
81897
  if (existsSync50(binaryPath)) {
81887
81898
  return binaryPath;
81888
81899
  }
@@ -81891,7 +81902,7 @@ async function downloadAstGrep(version3 = DEFAULT_VERSION) {
81891
81902
  const downloadUrl = `https://github.com/${REPO2}/releases/download/${version3}/${assetName}`;
81892
81903
  log(`[matrixx] Downloading ast-grep binary...`);
81893
81904
  try {
81894
- const archivePath = join57(cacheDir, assetName);
81905
+ const archivePath = join58(cacheDir, assetName);
81895
81906
  ensureCacheDir(cacheDir);
81896
81907
  await downloadArchive(downloadUrl, archivePath);
81897
81908
  await extractZipArchive(archivePath, cacheDir);
@@ -81945,7 +81956,7 @@ function findSgCliPathSync() {
81945
81956
  const require2 = createRequire4(import.meta.url);
81946
81957
  const cliPackageJsonPath = require2.resolve("@ast-grep/cli/package.json");
81947
81958
  const cliDirectory = dirname15(cliPackageJsonPath);
81948
- const sgPath = join58(cliDirectory, binaryName);
81959
+ const sgPath = join59(cliDirectory, binaryName);
81949
81960
  if (existsSync51(sgPath) && isValidBinary(sgPath)) {
81950
81961
  return sgPath;
81951
81962
  }
@@ -81957,7 +81968,7 @@ function findSgCliPathSync() {
81957
81968
  const packageJsonPath = require2.resolve(`${platformPackage}/package.json`);
81958
81969
  const packageDirectory = dirname15(packageJsonPath);
81959
81970
  const astGrepBinaryName = process.platform === "win32" ? "ast-grep.exe" : "ast-grep";
81960
- const binaryPath = join58(packageDirectory, astGrepBinaryName);
81971
+ const binaryPath = join59(packageDirectory, astGrepBinaryName);
81961
81972
  if (existsSync51(binaryPath) && isValidBinary(binaryPath)) {
81962
81973
  return binaryPath;
81963
81974
  }
@@ -84388,17 +84399,17 @@ var {spawn: spawn10 } = globalThis.Bun;
84388
84399
  // src/tools/grep/constants.ts
84389
84400
  import { spawnSync as spawnSync2 } from "child_process";
84390
84401
  import { existsSync as existsSync58 } from "fs";
84391
- import { dirname as dirname16, join as join60 } from "path";
84402
+ import { dirname as dirname16, join as join61 } from "path";
84392
84403
 
84393
84404
  // src/tools/grep/downloader.ts
84394
84405
  import { existsSync as existsSync57, readdirSync as readdirSync18 } from "fs";
84395
- import { join as join59 } from "path";
84406
+ import { join as join60 } from "path";
84396
84407
  function findFileRecursive(dir, filename) {
84397
84408
  try {
84398
84409
  const entries = readdirSync18(dir, { withFileTypes: true, recursive: true });
84399
84410
  for (const entry of entries) {
84400
84411
  if (entry.isFile() && entry.name === filename) {
84401
- return join59(entry.parentPath ?? dir, entry.name);
84412
+ return join60(entry.parentPath ?? dir, entry.name);
84402
84413
  }
84403
84414
  }
84404
84415
  } catch {
@@ -84419,11 +84430,11 @@ function getPlatformKey() {
84419
84430
  }
84420
84431
  function getInstallDir() {
84421
84432
  const homeDir = process.env.HOME || process.env.USERPROFILE || ".";
84422
- return join59(homeDir, ".cache", "matrixx", "bin");
84433
+ return join60(homeDir, ".cache", "matrixx", "bin");
84423
84434
  }
84424
84435
  function getRgPath() {
84425
84436
  const isWindows = process.platform === "win32";
84426
- return join59(getInstallDir(), isWindows ? "rg.exe" : "rg");
84437
+ return join60(getInstallDir(), isWindows ? "rg.exe" : "rg");
84427
84438
  }
84428
84439
  async function extractTarGz2(archivePath, destDir) {
84429
84440
  const platformKey = getPlatformKey();
@@ -84440,7 +84451,7 @@ async function extractZip2(archivePath, destDir) {
84440
84451
  const binaryName = process.platform === "win32" ? "rg.exe" : "rg";
84441
84452
  const foundPath = findFileRecursive(destDir, binaryName);
84442
84453
  if (foundPath) {
84443
- const destPath = join59(destDir, binaryName);
84454
+ const destPath = join60(destDir, binaryName);
84444
84455
  if (foundPath !== destPath) {
84445
84456
  const { renameSync: renameSync4 } = await import("fs");
84446
84457
  renameSync4(foundPath, destPath);
@@ -84461,7 +84472,7 @@ async function downloadAndInstallRipgrep() {
84461
84472
  ensureCacheDir(installDir);
84462
84473
  const filename = `ripgrep-${RG_VERSION}-${config4.platform}.${config4.extension}`;
84463
84474
  const url3 = `https://github.com/BurntSushi/ripgrep/releases/download/${RG_VERSION}/${filename}`;
84464
- const archivePath = join59(installDir, filename);
84475
+ const archivePath = join60(installDir, filename);
84465
84476
  try {
84466
84477
  await downloadArchive(url3, archivePath);
84467
84478
  if (config4.extension === "tar.gz") {
@@ -84506,11 +84517,11 @@ function getOpenCodeBundledRg() {
84506
84517
  const isWindows = process.platform === "win32";
84507
84518
  const rgName = isWindows ? "rg.exe" : "rg";
84508
84519
  const candidates = [
84509
- join60(getDataDir(), "opencode", "bin", rgName),
84510
- join60(execDir, rgName),
84511
- join60(execDir, "bin", rgName),
84512
- join60(execDir, "..", "bin", rgName),
84513
- join60(execDir, "..", "libexec", rgName)
84520
+ join61(getDataDir(), "opencode", "bin", rgName),
84521
+ join61(execDir, rgName),
84522
+ join61(execDir, "bin", rgName),
84523
+ join61(execDir, "..", "bin", rgName),
84524
+ join61(execDir, "..", "libexec", rgName)
84514
84525
  ];
84515
84526
  for (const candidate of candidates) {
84516
84527
  if (existsSync58(candidate)) {
@@ -85018,12 +85029,12 @@ var HandoffSchema = exports_external.object({
85018
85029
  });
85019
85030
  // src/features/handoff/storage.ts
85020
85031
  import { existsSync as existsSync59, mkdirSync as mkdirSync17, readFileSync as readFileSync39, renameSync as renameSync4, writeFileSync as writeFileSync22 } from "fs";
85021
- import { join as join61 } from "path";
85032
+ import { join as join62 } from "path";
85022
85033
  function getHandoffFilePath(directory) {
85023
- return join61(directory, HANDOFF_FILE_PATH);
85034
+ return join62(directory, HANDOFF_FILE_PATH);
85024
85035
  }
85025
85036
  function getHandoffConsumedFilePath(directory) {
85026
- return join61(directory, HANDOFF_CONSUMED_FILE_PATH);
85037
+ return join62(directory, HANDOFF_CONSUMED_FILE_PATH);
85027
85038
  }
85028
85039
  function handoffFileExists(directory) {
85029
85040
  try {
@@ -85061,7 +85072,7 @@ function writeHandoffFile(directory, content) {
85061
85072
  }
85062
85073
  function ensureHandoffDir(directory) {
85063
85074
  try {
85064
- mkdirSync17(join61(directory, ".matrixx"), { recursive: true });
85075
+ mkdirSync17(join62(directory, ".matrixx"), { recursive: true });
85065
85076
  return true;
85066
85077
  } catch {
85067
85078
  return false;
@@ -85217,13 +85228,13 @@ ${body}
85217
85228
 
85218
85229
  // src/tools/handoff/list-handler.ts
85219
85230
  import { existsSync as existsSync60, readdirSync as readdirSync19, statSync as statSync14 } from "fs";
85220
- import { join as join62 } from "path";
85231
+ import { join as join63 } from "path";
85221
85232
  var MATRIX_DIR = ".matrixx";
85222
85233
  function isHandoffFile(name) {
85223
85234
  return name === "handoff.md" || name.startsWith("handoff.consumed");
85224
85235
  }
85225
85236
  function handleList(directory) {
85226
- const matrixxDir = join62(directory, MATRIX_DIR);
85237
+ const matrixxDir = join63(directory, MATRIX_DIR);
85227
85238
  if (!existsSync60(matrixxDir)) {
85228
85239
  return "No handoffs found in .matrixx/";
85229
85240
  }
@@ -85241,7 +85252,7 @@ function handleList(directory) {
85241
85252
  lines.push(`Handoff files in ${matrixxDir}:`);
85242
85253
  lines.push("");
85243
85254
  for (const name of handoffs) {
85244
- const fullPath = join62(matrixxDir, name);
85255
+ const fullPath = join63(matrixxDir, name);
85245
85256
  let size = "?";
85246
85257
  let mtime = "?";
85247
85258
  try {
@@ -85467,7 +85478,7 @@ The Bash tool can execute these commands directly. Do NOT retry with interactive
85467
85478
  // src/tools/pdf-extract-figures/tools.ts
85468
85479
  import { existsSync as existsSync61, mkdtempSync, writeFileSync as writeFileSync23 } from "fs";
85469
85480
  import { tmpdir as tmpdir5 } from "os";
85470
- import { join as join63 } from "path";
85481
+ import { join as join64 } from "path";
85471
85482
  var PYTHON_SCRIPT = `#!/usr/bin/env python3
85472
85483
  import sys
85473
85484
  import json
@@ -85618,11 +85629,11 @@ async function runExtraction(args) {
85618
85629
  }
85619
85630
  let tmpDir;
85620
85631
  try {
85621
- tmpDir = mkdtempSync(join63(tmpdir5(), "matrixx-pdf-extract-"));
85632
+ tmpDir = mkdtempSync(join64(tmpdir5(), "matrixx-pdf-extract-"));
85622
85633
  } catch {
85623
85634
  return "Error: Could not create temporary directory.";
85624
85635
  }
85625
- const scriptPath = join63(tmpDir, "extract_pdf_figures.py");
85636
+ const scriptPath = join64(tmpDir, "extract_pdf_figures.py");
85626
85637
  writeFileSync23(scriptPath, PYTHON_SCRIPT);
85627
85638
  const payload = buildArgsPayload(args);
85628
85639
  log("[pdf_extract_figures] Running extraction", { file_path: args.file_path, payload });
@@ -85674,7 +85685,7 @@ import { existsSync as existsSync62 } from "fs";
85674
85685
  import { basename as basename7 } from "path";
85675
85686
 
85676
85687
  // src/tools/plan/types.ts
85677
- import { basename as basename6, isAbsolute as isAbsolute5, join as join64, relative as relative4, resolve as resolve9 } from "path";
85688
+ import { basename as basename6, isAbsolute as isAbsolute5, join as join65, relative as relative4, resolve as resolve9 } from "path";
85678
85689
 
85679
85690
  // src/tools/plan/constants.ts
85680
85691
  var PLANS_DIR3 = ".matrixx/plans";
@@ -85700,8 +85711,8 @@ function validatePlanFilePath(filePath, directory) {
85700
85711
  if (!PLAN_FILENAME_KEBAB_REGEX.test(base)) {
85701
85712
  return { error: `fileName must match kebab-case ^[a-z0-9-]+\\.md$: ${base}` };
85702
85713
  }
85703
- const resolved = isAbsolute5(filePath) ? resolve9(filePath) : resolve9(join64(directory, filePath));
85704
- const plansDir = resolve9(join64(directory, PLANS_DIR3));
85714
+ const resolved = isAbsolute5(filePath) ? resolve9(filePath) : resolve9(join65(directory, filePath));
85715
+ const plansDir = resolve9(join65(directory, PLANS_DIR3));
85705
85716
  const relToPlans = relative4(plansDir, resolved);
85706
85717
  if (relToPlans.startsWith("..") || isAbsolute5(relToPlans)) {
85707
85718
  return { error: `filePath must be inside ${PLANS_DIR3}: ${filePath}` };
@@ -85805,7 +85816,7 @@ function createPlanDeleteTool(ctx) {
85805
85816
  }
85806
85817
  // src/tools/plan/plan-list.ts
85807
85818
  import { existsSync as existsSync64, readdirSync as readdirSync20, statSync as statSync15 } from "fs";
85808
- import { join as join65 } from "path";
85819
+ import { join as join66 } from "path";
85809
85820
  function createPlanListTool(ctx) {
85810
85821
  return tool({
85811
85822
  description: `List plan files under .matrixx/plans/*.md. Filters *.md with kebab-case, sorts by mtime.`,
@@ -85813,13 +85824,13 @@ function createPlanListTool(ctx) {
85813
85824
  execute: async (_args, context) => {
85814
85825
  try {
85815
85826
  const directory = resolveDirectory(context?.directory, ctx?.directory);
85816
- const plansDir = join65(directory, PLANS_DIR3);
85827
+ const plansDir = join66(directory, PLANS_DIR3);
85817
85828
  if (!existsSync64(plansDir)) {
85818
85829
  return JSON.stringify({ plans: [] });
85819
85830
  }
85820
85831
  const entries = readdirSync20(plansDir).filter((f) => f.endsWith(".md") && PLAN_FILENAME_KEBAB_REGEX.test(f));
85821
85832
  const plans = entries.map((fileName) => {
85822
- const filePath = join65(plansDir, fileName);
85833
+ const filePath = join66(plansDir, fileName);
85823
85834
  try {
85824
85835
  const stat2 = statSync15(filePath);
85825
85836
  return {
@@ -86965,10 +86976,152 @@ function createPlanUpdateTool(ctx) {
86965
86976
  }
86966
86977
  });
86967
86978
  }
86979
+ // src/tools/preset/tools.ts
86980
+ import { existsSync as existsSync67, mkdirSync as mkdirSync18, readFileSync as readFileSync40, writeFileSync as writeFileSync24 } from "fs";
86981
+ import { homedir as homedir9 } from "os";
86982
+ import { dirname as dirname17, join as join67 } from "path";
86983
+
86984
+ // src/features/preset-state/manager.ts
86985
+ init_logger();
86986
+ var HOOK_NAME19 = "preset-state";
86987
+ var presetBySession = new Map;
86988
+ function setSessionPreset(sessionID, presetName) {
86989
+ presetBySession.set(sessionID, presetName);
86990
+ log(`[${HOOK_NAME19}] Preset set for session`, { sessionID, presetName });
86991
+ }
86992
+ function getSessionPreset(sessionID) {
86993
+ return presetBySession.get(sessionID);
86994
+ }
86995
+ // src/tools/preset/tools.ts
86996
+ init_logger();
86997
+ var ACTIONS = ["list", "show", "set"];
86998
+ var SCOPES = ["global", "project"];
86999
+ function resolveConfigPath(scope, directory) {
87000
+ if (scope === "global") {
87001
+ return join67(homedir9(), ".config", "opencode", "matrixx.jsonc");
87002
+ }
87003
+ return join67(directory ?? process.cwd(), ".opencode", "matrixx.jsonc");
87004
+ }
87005
+ function persistActivePreset(presetName, scope, directory) {
87006
+ const configPath = resolveConfigPath(scope, directory);
87007
+ let raw = {};
87008
+ if (existsSync67(configPath)) {
87009
+ const content = readFileSync40(configPath, "utf-8");
87010
+ const parsed = parseJsoncSafe(content);
87011
+ if (parsed.errors.length > 0) {
87012
+ return {
87013
+ ok: false,
87014
+ error: `Cannot parse existing config at ${configPath}: ${parsed.errors[0]?.message}`
87015
+ };
87016
+ }
87017
+ raw = parsed.data ?? {};
87018
+ } else {
87019
+ mkdirSync18(dirname17(configPath), { recursive: true });
87020
+ }
87021
+ raw.active_preset = presetName;
87022
+ writeFileSync24(configPath, `${JSON.stringify(raw, null, 2)}
87023
+ `);
87024
+ log("[preset] persisted active_preset", { presetName, scope, configPath });
87025
+ return { ok: true, path: configPath };
87026
+ }
87027
+ function unknownPresetError(name, presetNames) {
87028
+ const available = presetNames.length > 0 ? presetNames.join(", ") : "(none defined)";
87029
+ return `Error: Unknown preset "${name}". Available presets: ${available}`;
87030
+ }
87031
+ function formatPreset(name, preset) {
87032
+ const lines = [`Preset "${name}":`];
87033
+ if (preset.default_model) {
87034
+ lines.push(` default_model: ${preset.default_model}`);
87035
+ }
87036
+ const agentEntries = Object.entries(preset.agents ?? {});
87037
+ if (agentEntries.length > 0) {
87038
+ lines.push(" agents:");
87039
+ for (const [agent, entry] of agentEntries) {
87040
+ lines.push(` - ${agent}: ${entry.model}${entry.variant ? ` (variant: ${entry.variant})` : ""}`);
87041
+ }
87042
+ }
87043
+ const categoryEntries = Object.entries(preset.categories ?? {});
87044
+ if (categoryEntries.length > 0) {
87045
+ lines.push(" categories:");
87046
+ for (const [category, entry] of categoryEntries) {
87047
+ lines.push(` - ${category}: ${entry.model}${entry.variant ? ` (variant: ${entry.variant})` : ""}`);
87048
+ }
87049
+ }
87050
+ return lines.join(`
87051
+ `);
87052
+ }
87053
+ function runPresetCommand(input) {
87054
+ const { action, name, save, scope, sessionID, options } = input;
87055
+ const presets = options?.pluginConfig?.model_presets ?? {};
87056
+ const presetNames = Object.keys(presets);
87057
+ const activePreset = options?.pluginConfig?.active_preset;
87058
+ const sessionPreset = getSessionPreset(sessionID);
87059
+ const effective = sessionPreset ?? activePreset;
87060
+ if (action === "list") {
87061
+ if (presetNames.length === 0) {
87062
+ return "No model presets defined. Add a `model_presets` section to your matrixx config.";
87063
+ }
87064
+ const lines = presetNames.map((p) => `- ${p}${p === effective ? " (active)" : ""}`);
87065
+ const activeLine = effective ? `Active: ${effective}${sessionPreset ? ` (session overlay for ${sessionID})` : ""}` : "Active: (none)";
87066
+ return `Available presets:
87067
+ ${lines.join(`
87068
+ `)}
87069
+
87070
+ ${activeLine}`;
87071
+ }
87072
+ if (action === "show") {
87073
+ const target = name ?? effective;
87074
+ if (!target) {
87075
+ return "No preset specified and no active preset set. Usage: preset show <name>";
87076
+ }
87077
+ const preset = presets[target];
87078
+ if (!preset)
87079
+ return unknownPresetError(target, presetNames);
87080
+ return formatPreset(target, preset);
87081
+ }
87082
+ if (!name) {
87083
+ return "Error: `preset set` requires a preset name. Usage: preset set <name> [--save [--global|--project]]";
87084
+ }
87085
+ if (!presets[name])
87086
+ return unknownPresetError(name, presetNames);
87087
+ setSessionPreset(sessionID, name);
87088
+ let message = `\u2713 Active preset set to "${name}" for this session.
87089
+
87090
+ ` + `- delegate-task categories switch immediately
87091
+ ` + `- builtin agents apply on next session`;
87092
+ if (save) {
87093
+ const result = persistActivePreset(name, scope, options?.directory);
87094
+ if (!result.ok)
87095
+ return `Error: ${result.error}`;
87096
+ message += `
87097
+
87098
+ Persisted active_preset to ${result.path} (scope: ${scope}). Next session starts on "${name}".`;
87099
+ }
87100
+ return message;
87101
+ }
87102
+ function createPresetTool(options) {
87103
+ const preset = tool({
87104
+ description: "Manage model presets: list available presets, show a preset's model assignments, " + "or set the active preset for the current session. `set <name>` switches delegate-task " + "categories immediately and applies to builtin agents on the next session; " + "`set <name> --save [--global|--project]` additionally persists `active_preset` to config.",
87105
+ args: {
87106
+ action: tool.schema.enum(ACTIONS).describe("Operation: list available presets, show a preset's details, or set the active preset"),
87107
+ name: tool.schema.string().optional().describe("Preset name (required for set; optional for show \u2014 defaults to the active preset)"),
87108
+ save: tool.schema.boolean().optional().describe("Persist the active preset to config (default: false \u2014 session-only overlay)"),
87109
+ scope: tool.schema.enum(SCOPES).optional().describe("Where to persist when --save: project (.opencode/matrixx.jsonc, default) or global (~/.config/opencode/matrixx.jsonc)")
87110
+ },
87111
+ async execute(args, context) {
87112
+ const action = args.action;
87113
+ const name = args.name;
87114
+ const save = args.save;
87115
+ const scope = args.scope ?? "project";
87116
+ return runPresetCommand({ action, name, save, scope, sessionID: context.sessionID, options });
87117
+ }
87118
+ });
87119
+ return { preset };
87120
+ }
86968
87121
  // src/tools/session-manager/constants.ts
86969
- import { join as join66 } from "path";
86970
- var TODO_DIR = join66(getOpenCodeConfigDir({ binary: "opencode" }), "todos");
86971
- var TRANSCRIPT_DIR2 = join66(getOpenCodeConfigDir({ binary: "opencode" }), "transcripts");
87122
+ import { join as join68 } from "path";
87123
+ var TODO_DIR = join68(getOpenCodeConfigDir({ binary: "opencode" }), "todos");
87124
+ var TRANSCRIPT_DIR2 = join68(getOpenCodeConfigDir({ binary: "opencode" }), "transcripts");
86972
87125
  var SESSION_LIST_DESCRIPTION = `List all OpenCode sessions with optional filtering.
86973
87126
 
86974
87127
  Returns a list of available session IDs with metadata including message count, date range, and agents used.
@@ -87040,9 +87193,9 @@ Agents Used: build, oracle, librarian
87040
87193
  Has Todos: Yes (12 items, 8 completed)
87041
87194
  Has Transcript: Yes (234 entries)`;
87042
87195
  // src/tools/session-manager/storage.ts
87043
- import { existsSync as existsSync67 } from "fs";
87196
+ import { existsSync as existsSync68 } from "fs";
87044
87197
  import { readdir, readFile as readFile3 } from "fs/promises";
87045
- import { join as join67 } from "path";
87198
+ import { join as join69 } from "path";
87046
87199
  var sdkClient = null;
87047
87200
  function setStorageClient(client2) {
87048
87201
  sdkClient = client2;
@@ -87061,7 +87214,7 @@ async function getMainSessions(options) {
87061
87214
  return [];
87062
87215
  }
87063
87216
  }
87064
- if (!existsSync67(SESSION_STORAGE))
87217
+ if (!existsSync68(SESSION_STORAGE))
87065
87218
  return [];
87066
87219
  const sessions = [];
87067
87220
  try {
@@ -87069,13 +87222,13 @@ async function getMainSessions(options) {
87069
87222
  for (const projectDir of projectDirs) {
87070
87223
  if (!projectDir.isDirectory())
87071
87224
  continue;
87072
- const projectPath = join67(SESSION_STORAGE, projectDir.name);
87225
+ const projectPath = join69(SESSION_STORAGE, projectDir.name);
87073
87226
  const sessionFiles = await readdir(projectPath);
87074
87227
  for (const file3 of sessionFiles) {
87075
87228
  if (!file3.endsWith(".json"))
87076
87229
  continue;
87077
87230
  try {
87078
- const content = await readFile3(join67(projectPath, file3), "utf-8");
87231
+ const content = await readFile3(join69(projectPath, file3), "utf-8");
87079
87232
  const meta3 = JSON.parse(content);
87080
87233
  if (meta3.parentID)
87081
87234
  continue;
@@ -87100,7 +87253,7 @@ async function getAllSessions() {
87100
87253
  return [];
87101
87254
  }
87102
87255
  }
87103
- if (!existsSync67(MESSAGE_STORAGE))
87256
+ if (!existsSync68(MESSAGE_STORAGE))
87104
87257
  return [];
87105
87258
  const sessions = [];
87106
87259
  async function scanDirectory(dir) {
@@ -87108,7 +87261,7 @@ async function getAllSessions() {
87108
87261
  const entries = await readdir(dir, { withFileTypes: true });
87109
87262
  for (const entry of entries) {
87110
87263
  if (entry.isDirectory()) {
87111
- const sessionPath = join67(dir, entry.name);
87264
+ const sessionPath = join69(dir, entry.name);
87112
87265
  const files = await readdir(sessionPath);
87113
87266
  if (files.some((f) => f.endsWith(".json"))) {
87114
87267
  sessions.push(entry.name);
@@ -87169,7 +87322,7 @@ async function readSessionMessages(sessionID) {
87169
87322
  }
87170
87323
  }
87171
87324
  const messageDir = getMessageDir(sessionID);
87172
- if (!messageDir || !existsSync67(messageDir))
87325
+ if (!messageDir || !existsSync68(messageDir))
87173
87326
  return [];
87174
87327
  const messages2 = [];
87175
87328
  try {
@@ -87178,7 +87331,7 @@ async function readSessionMessages(sessionID) {
87178
87331
  if (!file3.endsWith(".json"))
87179
87332
  continue;
87180
87333
  try {
87181
- const content = await readFile3(join67(messageDir, file3), "utf-8");
87334
+ const content = await readFile3(join69(messageDir, file3), "utf-8");
87182
87335
  const meta3 = JSON.parse(content);
87183
87336
  const parts = await readParts2(meta3.id);
87184
87337
  messages2.push({
@@ -87202,8 +87355,8 @@ async function readSessionMessages(sessionID) {
87202
87355
  });
87203
87356
  }
87204
87357
  async function readParts2(messageID) {
87205
- const partDir = join67(PART_STORAGE, messageID);
87206
- if (!existsSync67(partDir))
87358
+ const partDir = join69(PART_STORAGE, messageID);
87359
+ if (!existsSync68(partDir))
87207
87360
  return [];
87208
87361
  const parts = [];
87209
87362
  try {
@@ -87212,7 +87365,7 @@ async function readParts2(messageID) {
87212
87365
  if (!file3.endsWith(".json"))
87213
87366
  continue;
87214
87367
  try {
87215
- const content = await readFile3(join67(partDir, file3), "utf-8");
87368
+ const content = await readFile3(join69(partDir, file3), "utf-8");
87216
87369
  parts.push(JSON.parse(content));
87217
87370
  } catch {}
87218
87371
  }
@@ -87236,14 +87389,14 @@ async function readSessionTodos(sessionID) {
87236
87389
  return [];
87237
87390
  }
87238
87391
  }
87239
- if (!existsSync67(TODO_DIR))
87392
+ if (!existsSync68(TODO_DIR))
87240
87393
  return [];
87241
87394
  try {
87242
87395
  const allFiles = await readdir(TODO_DIR);
87243
87396
  const todoFiles = allFiles.filter((f) => f.includes(sessionID) && f.endsWith(".json"));
87244
87397
  for (const file3 of todoFiles) {
87245
87398
  try {
87246
- const content = await readFile3(join67(TODO_DIR, file3), "utf-8");
87399
+ const content = await readFile3(join69(TODO_DIR, file3), "utf-8");
87247
87400
  const data = JSON.parse(content);
87248
87401
  if (Array.isArray(data)) {
87249
87402
  return data.map((item) => ({
@@ -87261,10 +87414,10 @@ async function readSessionTodos(sessionID) {
87261
87414
  return [];
87262
87415
  }
87263
87416
  async function readSessionTranscript(sessionID) {
87264
- if (!existsSync67(TRANSCRIPT_DIR2))
87417
+ if (!existsSync68(TRANSCRIPT_DIR2))
87265
87418
  return 0;
87266
- const transcriptFile = join67(TRANSCRIPT_DIR2, `${sessionID}.jsonl`);
87267
- if (!existsSync67(transcriptFile))
87419
+ const transcriptFile = join69(TRANSCRIPT_DIR2, `${sessionID}.jsonl`);
87420
+ if (!existsSync68(transcriptFile))
87268
87421
  return 0;
87269
87422
  try {
87270
87423
  const content = await readFile3(transcriptFile, "utf-8");
@@ -87774,20 +87927,20 @@ function createSkillTool(options = {}) {
87774
87927
  }
87775
87928
  var skill = createSkillTool();
87776
87929
  // src/tools/slashcommand/command-discovery.ts
87777
- import { existsSync as existsSync68, readdirSync as readdirSync21, readFileSync as readFileSync40 } from "fs";
87778
- import { basename as basename8, join as join68 } from "path";
87930
+ import { existsSync as existsSync69, readdirSync as readdirSync21, readFileSync as readFileSync41 } from "fs";
87931
+ import { basename as basename8, join as join70 } from "path";
87779
87932
  function discoverCommandsFromDir2(commandsDir, scope) {
87780
- if (!existsSync68(commandsDir))
87933
+ if (!existsSync69(commandsDir))
87781
87934
  return [];
87782
87935
  const entries = readdirSync21(commandsDir, { withFileTypes: true });
87783
87936
  const commands2 = [];
87784
87937
  for (const entry of entries) {
87785
87938
  if (!isMarkdownFile(entry))
87786
87939
  continue;
87787
- const commandPath = join68(commandsDir, entry.name);
87940
+ const commandPath = join70(commandsDir, entry.name);
87788
87941
  const commandName = basename8(entry.name, ".md");
87789
87942
  try {
87790
- const content = readFileSync40(commandPath, "utf-8");
87943
+ const content = readFileSync41(commandPath, "utf-8");
87791
87944
  const { data, body } = parseFrontmatter(content);
87792
87945
  const isOpencodeSource = scope === "opencode" || scope === "opencode-project";
87793
87946
  const metadata = {
@@ -87811,8 +87964,8 @@ function discoverCommandsFromDir2(commandsDir, scope) {
87811
87964
  }
87812
87965
  function discoverCommandsSync(directory) {
87813
87966
  const configDir = getOpenCodeConfigDir({ binary: "opencode" });
87814
- const opencodeGlobalDir = join68(configDir, "command");
87815
- const opencodeProjectDir = join68(directory ?? process.cwd(), ".opencode", "command");
87967
+ const opencodeGlobalDir = join70(configDir, "command");
87968
+ const opencodeProjectDir = join70(directory ?? process.cwd(), ".opencode", "command");
87816
87969
  const opencodeGlobalCommands = discoverCommandsFromDir2(opencodeGlobalDir, "opencode");
87817
87970
  const opencodeProjectCommands = discoverCommandsFromDir2(opencodeProjectDir, "opencode-project");
87818
87971
  const builtinCommandsMap = loadBuiltinCommands();
@@ -87836,7 +87989,7 @@ function discoverCommandsSync(directory) {
87836
87989
  ];
87837
87990
  }
87838
87991
  // src/tools/slashcommand/command-output-formatter.ts
87839
- import { dirname as dirname17 } from "path";
87992
+ import { dirname as dirname18 } from "path";
87840
87993
  async function formatLoadedCommand(command, userMessage) {
87841
87994
  const sections = [];
87842
87995
  sections.push(`# /${command.name} Command
@@ -87875,7 +88028,7 @@ async function formatLoadedCommand(command, userMessage) {
87875
88028
  if (!content && command.lazyContentLoader) {
87876
88029
  content = await command.lazyContentLoader.load();
87877
88030
  }
87878
- const commandDir = command.path ? dirname17(command.path) : process.cwd();
88031
+ const commandDir = command.path ? dirname18(command.path) : process.cwd();
87879
88032
  const withFileReferences = await resolveFileReferencesInText(content, commandDir);
87880
88033
  const resolvedContent = await resolveCommandsInText(withFileReferences);
87881
88034
  let finalContent = resolvedContent.trim();
@@ -88587,8 +88740,6 @@ function createBackgroundOutput(manager, client2) {
88587
88740
  }
88588
88741
  });
88589
88742
  }
88590
- // src/tools/background-task/create-background-task.ts
88591
- init_logger();
88592
88743
  // src/tools/assembly/constants.ts
88593
88744
  var DEFAULT_VOTERS = 3;
88594
88745
  var DEFAULT_ROUNDS = 2;
@@ -88954,9 +89105,9 @@ function createAssemblyTool(options) {
88954
89105
  });
88955
89106
  }
88956
89107
  // src/tools/dcp-switch-profile/tools.ts
88957
- import { existsSync as existsSync69, writeFileSync as writeFileSync24 } from "fs";
88958
- import { homedir as homedir9 } from "os";
88959
- import { join as join69 } from "path";
89108
+ import { existsSync as existsSync70, writeFileSync as writeFileSync25 } from "fs";
89109
+ import { homedir as homedir10 } from "os";
89110
+ import { join as join71 } from "path";
88960
89111
 
88961
89112
  // src/config/schema/dcp.ts
88962
89113
  var DcpCompressOverrideSchema = exports_external.object({
@@ -89129,11 +89280,11 @@ var DcpConfigSchema = exports_external.object({
89129
89280
  });
89130
89281
 
89131
89282
  // src/tools/dcp-switch-profile/tools.ts
89132
- var DCP_PLUGIN_DIR = join69(homedir9(), ".config", "opencode", "node_modules", "@tarquinen", "opencode-dcp");
89133
- var DCP_SYMLINK = join69(homedir9(), ".config", "opencode", "dcp.jsonc");
89283
+ var DCP_PLUGIN_DIR = join71(homedir10(), ".config", "opencode", "node_modules", "@tarquinen", "opencode-dcp");
89284
+ var DCP_SYMLINK = join71(homedir10(), ".config", "opencode", "dcp.jsonc");
89134
89285
  var VALID_PROFILES = ["economy", "balanced", "performance", "ultimate"];
89135
89286
  function checkDcpInstalled() {
89136
- if (!existsSync69(DCP_PLUGIN_DIR)) {
89287
+ if (!existsSync70(DCP_PLUGIN_DIR)) {
89137
89288
  return `DCP is not installed at ${DCP_PLUGIN_DIR}. Install it with: npm install --prefix ~/.config/opencode @tarquinen/opencode-dcp`;
89138
89289
  }
89139
89290
  return null;
@@ -89211,7 +89362,7 @@ function switchProfile(profile, options) {
89211
89362
  if (dcpError)
89212
89363
  return dcpError;
89213
89364
  const inlineConfig = buildInlineConfig(profile, options);
89214
- writeFileSync24(DCP_SYMLINK, `${JSON.stringify(inlineConfig, null, 2)}
89365
+ writeFileSync25(DCP_SYMLINK, `${JSON.stringify(inlineConfig, null, 2)}
89215
89366
  `);
89216
89367
  return `\u2713 Switched to DCP profile: ${profile}
89217
89368
 
@@ -89557,149 +89708,23 @@ function createDelegateAgent(ctx, backgroundManager, disabledAgents = []) {
89557
89708
  }
89558
89709
  });
89559
89710
  }
89560
- // src/shared/tier-resolver.ts
89561
- init_logger();
89562
-
89563
- // src/shared/model-tiers.ts
89564
- function compilePattern(pattern) {
89565
- try {
89566
- return new RegExp(pattern);
89567
- } catch {
89568
- return /(?!)/;
89569
- }
89570
- }
89571
- var DEFAULT_TIER_SPECS = {
89572
- free: {
89573
- name: "free",
89574
- providerPriority: ["opencode", "xai", "opencode-go", "zai-coding-plan"],
89575
- modelPattern: /-free$|kimi-k2\.5-free|minimax-m2\.5-free|grok-code-fast/,
89576
- fallback: [
89577
- { providers: ["opencode"], model: "kimi-k2.5-free" },
89578
- { providers: ["xai"], model: "grok-code-fast-1" }
89579
- ]
89580
- },
89581
- fast: {
89582
- name: "fast",
89583
- providerPriority: ["anthropic", "openai", "google", "opencode-go"],
89584
- modelPattern: /claude-haiku|gpt-5-nano|gemini-2\.5-flash|deepseek-v4-flash/,
89585
- fallback: [{ providers: ["anthropic"], model: "claude-haiku-4-5" }],
89586
- fallbackTier: "free"
89587
- },
89588
- standard: {
89589
- name: "standard",
89590
- providerPriority: ["anthropic", "openai", "google", "opencode-go"],
89591
- modelPattern: /claude-sonnet|gpt-5\.2|gemini-2\.5-pro/,
89592
- fallback: [{ providers: ["anthropic"], model: "claude-sonnet-4-6" }],
89593
- fallbackTier: "fast"
89594
- },
89595
- premium: {
89596
- name: "premium",
89597
- providerPriority: ["anthropic", "openai", "google", "opencode-go"],
89598
- modelPattern: /claude-opus|gpt-5\.3-codex|gemini-3-pro/,
89599
- fallback: [{ providers: ["anthropic"], model: "claude-opus-4-6" }],
89600
- fallbackTier: "standard"
89601
- },
89602
- frontier: {
89603
- name: "frontier",
89604
- providerPriority: ["anthropic", "openai", "google", "opencode-go"],
89605
- modelPattern: /claude-opus|gpt-5\.3-codex|gemini-3\.1-pro/,
89606
- fallback: [{ providers: ["anthropic"], model: "claude-opus-4-6" }],
89607
- fallbackTier: "premium"
89608
- }
89609
- };
89610
- function buildTierSpecs(config4) {
89611
- if (!config4?.tiers)
89612
- return DEFAULT_TIER_SPECS;
89613
- const result = {};
89614
- for (const [name, raw] of Object.entries(config4.tiers)) {
89615
- if (!raw || typeof raw !== "object")
89616
- continue;
89617
- const providerPriority = Array.isArray(raw.providerPriority) ? raw.providerPriority : [];
89618
- const rawPattern = raw.modelPattern;
89619
- const patternString = typeof rawPattern === "string" ? rawPattern : "";
89620
- result[name] = {
89621
- name,
89622
- providerPriority,
89623
- modelPattern: compilePattern(patternString),
89624
- fallbackTier: raw.fallbackTier,
89625
- fallback: raw.fallback
89626
- };
89627
- }
89628
- return result;
89629
- }
89630
- function getTierSpec(tierName, config4) {
89631
- if (!tierName)
89632
- return;
89633
- const specs = buildTierSpecs(config4);
89634
- return specs[tierName];
89635
- }
89636
-
89637
- // src/shared/tier-resolver.ts
89638
- function resolveTier(tier, ctx, config4) {
89639
- const spec = getTierSpec(tier, config4);
89640
- if (!spec)
89641
- return null;
89642
- if (ctx.availableModels.size > 0) {
89643
- for (const provider of spec.providerPriority) {
89644
- const match = findRegexMatch(spec.modelPattern, provider, ctx.availableModels);
89645
- if (match) {
89646
- log("[tier-resolver] resolved via regex", { tier, provider, match });
89647
- return { model: match, provenance: "tier-resolved", tier };
89648
- }
89649
- }
89650
- log("[tier-resolver] no live match found, recursing to fallback tier", { tier });
89651
- } else if (ctx.connectedProviders) {
89652
- const connectedSet = new Set(ctx.connectedProviders);
89653
- const fallback = spec.fallback ?? [];
89654
- for (const entry of fallback) {
89655
- for (const provider of entry.providers) {
89656
- if (connectedSet.has(provider)) {
89657
- const model = `${provider}/${entry.model}`;
89658
- log("[tier-resolver] using config fallback (cold cache)", { tier, provider, model });
89659
- return { model, provenance: "tier-static-fallback", tier };
89660
- }
89661
- }
89662
- }
89663
- log("[tier-resolver] no config fallback provider is connected", { tier });
89664
- } else {
89665
- log("[tier-resolver] no live models and no connected providers cache", { tier });
89666
- }
89667
- if (spec.fallbackTier) {
89668
- return resolveTier(spec.fallbackTier, ctx, config4);
89669
- }
89670
- return null;
89671
- }
89672
- function findRegexMatch(pattern, provider, available) {
89673
- for (const candidate of available) {
89674
- if (!candidate.startsWith(`${provider}/`))
89675
- continue;
89676
- if (pattern.test(candidate))
89677
- return candidate;
89678
- }
89679
- return null;
89680
- }
89681
-
89682
89711
  // src/tools/delegate-task/complexity-types.ts
89683
89712
  function isDowngradable(level) {
89684
89713
  return level === 1 || level === 2;
89685
89714
  }
89686
89715
 
89687
89716
  // src/tools/delegate-task/complexity-constants.ts
89688
- function resolveComplexityModel(category, complexity, originalModel, userDowngrades, config4, tierContext) {
89717
+ function resolveComplexityModel(category, complexity, originalModel, userDowngrades, config4) {
89689
89718
  if (!isDowngradable(complexity)) {
89690
89719
  return { model: originalModel, downgraded: false };
89691
89720
  }
89692
89721
  let complexityDowngrades;
89693
- let tiersConfig;
89694
89722
  if (config4) {
89695
89723
  const maybe = config4;
89696
- const hasHolderKeys = "complexityDowngrades" in maybe || "tiers" in maybe;
89724
+ const hasHolderKeys = "complexityDowngrades" in maybe;
89697
89725
  if (hasHolderKeys) {
89698
89726
  const holder = config4;
89699
89727
  complexityDowngrades = holder.complexityDowngrades;
89700
- if (holder.tiers) {
89701
- tiersConfig = holder;
89702
- }
89703
89728
  } else {
89704
89729
  complexityDowngrades = config4;
89705
89730
  }
@@ -89709,20 +89734,6 @@ function resolveComplexityModel(category, complexity, originalModel, userDowngra
89709
89734
  if (!rawDowngrade) {
89710
89735
  return { model: originalModel, downgraded: false };
89711
89736
  }
89712
- if (rawDowngrade.startsWith("tier:")) {
89713
- const tierName = rawDowngrade.slice(5).trim();
89714
- if (!tierName) {
89715
- return { model: originalModel, downgraded: false };
89716
- }
89717
- if (!tierContext || !tiersConfig) {
89718
- return { model: originalModel, downgraded: false };
89719
- }
89720
- const resolved = resolveTier(tierName, tierContext, tiersConfig);
89721
- if (!resolved) {
89722
- return { model: originalModel, downgraded: false };
89723
- }
89724
- return { model: resolved.model, downgraded: true };
89725
- }
89726
89737
  return { model: rawDowngrade, downgraded: true };
89727
89738
  }
89728
89739
  // src/tools/delegate-task/complexity-scorer.ts
@@ -89999,14 +90010,14 @@ You are NOT an interactive assistant. You are an autonomous problem-solver.
89999
90010
  - Report completion with summary of changes made
90000
90011
  </Category_Context>`;
90001
90012
  var DEFAULT_CATEGORIES = {
90002
- construct: { tier: "standard" },
90003
- source: { tier: "premium", variant: "max" },
90004
- "deep-jack": { tier: "premium", variant: "max" },
90005
- "matrix-bend": { tier: "standard" },
90006
- "bullet-time": { tier: "fast" },
90007
- "blue-pill": { tier: "standard" },
90008
- "red-pill": { tier: "premium", variant: "max" },
90009
- broadcast: { tier: "standard" }
90013
+ construct: {},
90014
+ source: { variant: "max" },
90015
+ "deep-jack": { variant: "max" },
90016
+ "matrix-bend": {},
90017
+ "bullet-time": {},
90018
+ "blue-pill": {},
90019
+ "red-pill": { variant: "max" },
90020
+ broadcast: {}
90010
90021
  };
90011
90022
  var CATEGORY_PROMPT_APPENDS = {
90012
90023
  construct: VISUAL_CATEGORY_PROMPT_APPEND,
@@ -90339,93 +90350,10 @@ function isPlanFamily(category) {
90339
90350
  // src/tools/delegate-task/tools.ts
90340
90351
  init_logger();
90341
90352
 
90342
- // src/config/resolve-tiers.ts
90343
- init_logger();
90344
- function resolveTiersInConfig(config4, ctx) {
90345
- const defaultTier = config4.default_tier;
90346
- const agents = walkAgentOverrides(config4.agents, defaultTier, ctx, config4);
90347
- const categories = walkCategoryOverrides(config4.categories, defaultTier, ctx, config4);
90348
- const { default_tier: _drop, ...rest } = config4;
90349
- return { ...rest, agents, categories };
90350
- }
90351
- function resolveTiersInCategoryRegistry(registry4, ctx, config4) {
90352
- const result = {};
90353
- for (const [name, entry] of Object.entries(registry4)) {
90354
- if (!entry)
90355
- continue;
90356
- result[name] = resolveEntry(entry, undefined, ctx, config4);
90357
- }
90358
- return result;
90359
- }
90360
- function walkAgentOverrides(agents, defaultTier, ctx, config4) {
90361
- if (!agents)
90362
- return agents;
90363
- const result = {};
90364
- for (const [name, entry] of Object.entries(agents)) {
90365
- if (!entry) {
90366
- result[name] = entry;
90367
- continue;
90368
- }
90369
- result[name] = resolveEntry(entry, defaultTier, ctx, config4);
90370
- }
90371
- return result;
90372
- }
90373
- function walkCategoryOverrides(categories, defaultTier, ctx, config4) {
90374
- if (!categories)
90375
- return categories;
90376
- const result = {};
90377
- for (const [name, entry] of Object.entries(categories)) {
90378
- if (!entry) {
90379
- result[name] = entry;
90380
- continue;
90381
- }
90382
- result[name] = resolveEntry(entry, defaultTier, ctx, config4);
90383
- }
90384
- return result;
90385
- }
90386
- function resolveEntry(entry, defaultTier, ctx, config4) {
90387
- if (entry.model) {
90388
- const { tier: _t2, ...rest2 } = entry;
90389
- return rest2;
90390
- }
90391
- const tier = entry.tier ?? defaultTier;
90392
- if (!tier)
90393
- return entry;
90394
- const resolved = resolveTier(tier, ctx, config4);
90395
- if (!resolved) {
90396
- log("[resolveTiersInConfig] tier could not be resolved", { tier });
90397
- return entry;
90398
- }
90399
- const { tier: _t, ...rest } = entry;
90400
- return { ...rest, model: resolved.model };
90401
- }
90402
-
90403
90353
  // src/shared/merge-categories.ts
90404
- function mergeCategories(userCategories, config4) {
90405
- const resolvedDefaults = resolveDefaultCategories(config4);
90406
- const merged = userCategories ? { ...resolvedDefaults, ...userCategories } : { ...resolvedDefaults };
90407
- return Object.fromEntries(Object.entries(merged).filter(([, config5]) => !config5.disable));
90408
- }
90409
- function resolveDefaultCategories(config4) {
90410
- const providerCache = readProviderModelsCache();
90411
- const availableModels = new Set;
90412
- if (providerCache) {
90413
- const connected = new Set(providerCache.connected);
90414
- for (const [providerId, models] of Object.entries(providerCache.models)) {
90415
- if (!connected.has(providerId))
90416
- continue;
90417
- for (const item of models) {
90418
- const modelID = typeof item === "string" ? item : item?.id;
90419
- if (modelID)
90420
- availableModels.add(`${providerId}/${modelID}`);
90421
- }
90422
- }
90423
- }
90424
- const ctx = {
90425
- availableModels,
90426
- connectedProviders: readConnectedProvidersCache()
90427
- };
90428
- return resolveTiersInCategoryRegistry(DEFAULT_CATEGORIES, ctx, config4);
90354
+ function mergeCategories(userCategories) {
90355
+ const merged = userCategories ? { ...DEFAULT_CATEGORIES, ...userCategories } : { ...DEFAULT_CATEGORIES };
90356
+ return Object.fromEntries(Object.entries(merged).filter(([, config4]) => !config4.disable));
90429
90357
  }
90430
90358
  // src/tools/delegate-task/background-continuation.ts
90431
90359
  async function executeBackgroundContinuation(args, ctx, executorCtx, parentContext) {
@@ -90500,6 +90428,9 @@ function getTimingConfig() {
90500
90428
  SESSION_CONTINUATION_STABILITY_MS
90501
90429
  };
90502
90430
  }
90431
+ function setPollTimeoutMs(ms) {
90432
+ MAX_POLL_TIME_MS = ms;
90433
+ }
90503
90434
 
90504
90435
  // src/tools/delegate-task/background-task.ts
90505
90436
  async function executeBackgroundTask(args, ctx, executorCtx, parentContext, agentToUse, categoryModel, systemContent) {
@@ -90578,6 +90509,67 @@ session_id: ${sessionId}
90578
90509
  });
90579
90510
  }
90580
90511
  }
90512
+ // src/config/preset-applier.ts
90513
+ init_logger();
90514
+ function resolvePresetModel(preset, key) {
90515
+ const entry = preset.agents?.[key] ?? preset.categories?.[key];
90516
+ if (entry)
90517
+ return entry;
90518
+ if (preset.default_model)
90519
+ return { model: preset.default_model };
90520
+ return;
90521
+ }
90522
+ function applyActivePreset(config4, presetName) {
90523
+ const name = presetName ?? config4.active_preset;
90524
+ if (!name)
90525
+ return config4;
90526
+ const preset = config4.model_presets?.[name];
90527
+ if (!preset) {
90528
+ log("[applyActivePreset] unknown active_preset", { active_preset: name });
90529
+ return config4;
90530
+ }
90531
+ const agents = fillRegistry(config4.agents, preset.agents, preset.default_model);
90532
+ const categories = fillRegistry(config4.categories, preset.categories, preset.default_model);
90533
+ const result = { ...config4 };
90534
+ if (agents !== config4.agents)
90535
+ result.agents = agents;
90536
+ if (categories !== config4.categories) {
90537
+ result.categories = categories;
90538
+ }
90539
+ return result;
90540
+ }
90541
+ function fillRegistry(registry4, presetEntries, defaultModel) {
90542
+ if (!registry4)
90543
+ return registry4;
90544
+ let changed = false;
90545
+ const result = {};
90546
+ for (const [name, entry] of Object.entries(registry4)) {
90547
+ if (!entry) {
90548
+ result[name] = entry;
90549
+ continue;
90550
+ }
90551
+ const filled = applyEntry(entry, presetEntries?.[name], defaultModel);
90552
+ if (filled) {
90553
+ changed = true;
90554
+ result[name] = filled;
90555
+ } else {
90556
+ result[name] = entry;
90557
+ }
90558
+ }
90559
+ return changed ? result : registry4;
90560
+ }
90561
+ function applyEntry(entry, presetEntry, defaultModel) {
90562
+ if (entry.model)
90563
+ return null;
90564
+ const model = presetEntry?.model ?? defaultModel;
90565
+ if (!model)
90566
+ return null;
90567
+ const variant = entry.variant ?? presetEntry?.variant;
90568
+ if (variant === undefined)
90569
+ return { ...entry, model };
90570
+ return { ...entry, model, variant };
90571
+ }
90572
+
90581
90573
  // src/tools/delegate-task/category-resolver.ts
90582
90574
  init_logger();
90583
90575
 
@@ -90635,7 +90627,7 @@ async function getAvailableModelsForDelegateTask(client2) {
90635
90627
  // src/tools/delegate-task/categories.ts
90636
90628
  init_logger();
90637
90629
  function resolveCategoryConfig(categoryName, options) {
90638
- const { userCategories, inheritedModel: _inheritedModel, systemDefaultModel, availableModels, tierContext, modelRequirements, tiers } = options;
90630
+ const { userCategories, inheritedModel: _inheritedModel, systemDefaultModel, availableModels, modelRequirements, presetEntry } = options;
90639
90631
  const defaultConfig = DEFAULT_CATEGORIES[categoryName];
90640
90632
  const userConfig = userCategories?.[categoryName];
90641
90633
  const hasExplicitUserConfig = userConfig !== undefined;
@@ -90654,16 +90646,13 @@ function resolveCategoryConfig(categoryName, options) {
90654
90646
  if (!defaultConfig && !userConfig) {
90655
90647
  return null;
90656
90648
  }
90657
- const tierConfig = tiers ? { tiers } : undefined;
90658
- const effectiveUserModel = resolveTierOnEntry(userConfig, tierContext, tierConfig);
90659
- const effectiveDefaultModel = resolveTierOnEntry(defaultConfig, tierContext, tierConfig);
90660
- const model = normalizeModel(effectiveUserModel ?? userConfig?.model) ?? normalizeModel(effectiveDefaultModel ?? defaultConfig?.model) ?? systemDefaultModel;
90661
- const hasUserModelOverride = (effectiveUserModel ?? userConfig?.model) !== undefined;
90649
+ const model = normalizeModel(userConfig?.model) ?? normalizeModel(presetEntry?.model) ?? normalizeModel(defaultConfig?.model) ?? systemDefaultModel;
90650
+ const hasUserModelOverride = (userConfig?.model ?? presetEntry?.model) !== undefined;
90662
90651
  const config4 = {
90663
90652
  ...defaultConfig,
90664
90653
  ...userConfig,
90665
90654
  model,
90666
- variant: userConfig?.variant ?? (hasUserModelOverride ? undefined : defaultConfig?.variant)
90655
+ variant: userConfig?.variant ?? presetEntry?.variant ?? (hasUserModelOverride ? undefined : defaultConfig?.variant)
90667
90656
  };
90668
90657
  let promptAppend = defaultPromptAppend;
90669
90658
  if (userConfig?.prompt_append) {
@@ -90673,12 +90662,6 @@ ${userConfig.prompt_append}` : userConfig.prompt_append;
90673
90662
  }
90674
90663
  return { config: config4, promptAppend, model, temperature: config4.temperature };
90675
90664
  }
90676
- function resolveTierOnEntry(entry, ctx, tiersConfig) {
90677
- if (!entry || entry.model || !entry.tier || !ctx)
90678
- return;
90679
- const resolved = resolveTier(entry.tier, ctx, tiersConfig);
90680
- return resolved?.model;
90681
- }
90682
90665
 
90683
90666
  // src/tools/delegate-task/model-selection.ts
90684
90667
  function normalizeModel2(model) {
@@ -90737,11 +90720,33 @@ function resolveModelForDelegateTask(input) {
90737
90720
  var MOUSE_AGENT2 = "mouse";
90738
90721
 
90739
90722
  // src/tools/delegate-task/category-resolver.ts
90723
+ function resolveCategoryPresetEntry(categoryName, ctx) {
90724
+ const { sessionID, modelPresets, activePreset } = ctx;
90725
+ if (!modelPresets)
90726
+ return;
90727
+ const overlayName = sessionID ? getSessionPreset(sessionID) : undefined;
90728
+ let result;
90729
+ if (overlayName) {
90730
+ const overlayPreset = modelPresets[overlayName];
90731
+ if (overlayPreset) {
90732
+ const entry = resolvePresetModel(overlayPreset, categoryName);
90733
+ if (entry)
90734
+ result = entry;
90735
+ } else {
90736
+ log("[preset] unknown session overlay preset, ignoring", { sessionID, presetName: overlayName });
90737
+ }
90738
+ }
90739
+ if (!result && activePreset) {
90740
+ const preset = modelPresets[activePreset];
90741
+ if (preset)
90742
+ result = resolvePresetModel(preset, categoryName);
90743
+ }
90744
+ return result;
90745
+ }
90740
90746
  async function resolveCategoryExecution(args, executorCtx, inheritedModel, systemDefaultModel) {
90741
- const { client: client2, userCategories, mouseModel, globalModel, modelRequirements, complexityDowngrades, tiers } = executorCtx;
90747
+ const { client: client2, userCategories, mouseModel, globalModel, modelRequirements, complexityDowngrades } = executorCtx;
90742
90748
  const categoryRequirements = getCategoryModelRequirements(modelRequirements ? { modelRequirements } : undefined);
90743
90749
  const availableModels = await getAvailableModelsForDelegateTask(client2);
90744
- const connectedProviders = readConnectedProvidersCache();
90745
90750
  const categoryName = args.category;
90746
90751
  const enabledCategories = mergeCategories(userCategories);
90747
90752
  const categoryExists = enabledCategories[categoryName] !== undefined;
@@ -90750,8 +90755,8 @@ async function resolveCategoryExecution(args, executorCtx, inheritedModel, syste
90750
90755
  inheritedModel,
90751
90756
  systemDefaultModel,
90752
90757
  availableModels,
90753
- tierContext: { availableModels, connectedProviders },
90754
- modelRequirements
90758
+ modelRequirements,
90759
+ presetEntry: resolveCategoryPresetEntry(categoryName, executorCtx)
90755
90760
  });
90756
90761
  if (!resolved) {
90757
90762
  const requirement2 = categoryRequirements[categoryName];
@@ -90847,9 +90852,7 @@ Available categories: ${allCategoryNames}`
90847
90852
  }
90848
90853
  if (actualModel && (complexityLevel === 1 || complexityLevel === 2)) {
90849
90854
  const userDowngrades = userCategories?.[args.category]?.complexity_downgrades;
90850
- const holder = complexityDowngrades || tiers ? { complexityDowngrades, tiers } : undefined;
90851
- const tierCtx = { availableModels, connectedProviders };
90852
- const resolvedDowngrade = resolveComplexityModel(args.category, complexityLevel, actualModel, userDowngrades, holder, tierCtx);
90855
+ const resolvedDowngrade = resolveComplexityModel(args.category, complexityLevel, actualModel, userDowngrades, complexityDowngrades);
90853
90856
  if (resolvedDowngrade.downgraded) {
90854
90857
  complexityDowngraded = true;
90855
90858
  log("[complexity] Auto-downgraded model", {
@@ -91365,7 +91368,7 @@ async function pollSyncSession(ctx, client2, input) {
91365
91368
  let idleStallCount = 0;
91366
91369
  let lastMsgCount = 0;
91367
91370
  let stableIdlePolls = 0;
91368
- log("[task] Starting poll loop", { sessionID: input.sessionID, agentToUse: input.agentToUse });
91371
+ log("[task] Starting poll loop", { sessionID: input.sessionID, agentToUse: input.agentToUse, maxPollTimeMs });
91369
91372
  while (Date.now() - pollStart < maxPollTimeMs) {
91370
91373
  if (ctx.abort?.aborted) {
91371
91374
  log("[task] Aborted by user", { sessionID: input.sessionID });
@@ -91743,6 +91746,7 @@ async function executeSyncTask(args, ctx, executorCtx, parentContext, agentToUse
91743
91746
  const toastManager = getTaskToastManager();
91744
91747
  let taskId;
91745
91748
  let syncSessionID;
91749
+ let pollTimedOut = false;
91746
91750
  try {
91747
91751
  const createSessionResult = await deps.createSyncSession(client2, {
91748
91752
  parentSessionID: parentContext.sessionID,
@@ -91825,6 +91829,7 @@ async function executeSyncTask(args, ctx, executorCtx, parentContext, agentToUse
91825
91829
  }
91826
91830
  });
91827
91831
  if (pollError) {
91832
+ pollTimedOut = pollError.startsWith("Poll timeout reached");
91828
91833
  return pollError;
91829
91834
  }
91830
91835
  const result = await deps.fetchSyncResult(client2, sessionID, undefined, {
@@ -91861,10 +91866,12 @@ session_id: ${sessionID}
91861
91866
  });
91862
91867
  } finally {
91863
91868
  if (syncSessionID) {
91864
- subagentSessions.delete(syncSessionID);
91865
- client2.session.abort({ path: { id: syncSessionID } }).catch((err) => {
91866
- log("[delegate-task] Sync session abort failed:", err);
91867
- });
91869
+ unregisterSubagentSession(syncSessionID);
91870
+ if (!pollTimedOut) {
91871
+ client2.session.abort({ path: { id: syncSessionID } }).catch((err) => {
91872
+ log("[delegate-task] Sync session abort failed:", err);
91873
+ });
91874
+ }
91868
91875
  }
91869
91876
  }
91870
91877
  }
@@ -92190,7 +92197,7 @@ Prompts MUST be in English.`;
92190
92197
  let actualModel;
92191
92198
  let isUnstableAgent = false;
92192
92199
  if (args.category) {
92193
- const resolution = await resolveCategoryExecution(args, options, inheritedModel, systemDefaultModel);
92200
+ const resolution = await resolveCategoryExecution(args, { ...options, sessionID: ctx.sessionID }, inheritedModel, systemDefaultModel);
92194
92201
  if (resolution.error) {
92195
92202
  return resolution.error;
92196
92203
  }
@@ -92591,8 +92598,8 @@ Original error: ${createResult.error}`;
92591
92598
  });
92592
92599
  }
92593
92600
  // src/tools/task/task-cleanup.ts
92594
- import { existsSync as existsSync70, readdirSync as readdirSync22, readFileSync as readFileSync41, unlinkSync as unlinkSync11 } from "fs";
92595
- import { join as join70 } from "path";
92601
+ import { existsSync as existsSync71, readdirSync as readdirSync22, readFileSync as readFileSync42, unlinkSync as unlinkSync11 } from "fs";
92602
+ import { join as join72 } from "path";
92596
92603
  init_logger();
92597
92604
  function parseOlderThan(value) {
92598
92605
  const match = value.trim().match(/^(\d+)(d|h|m)$/);
@@ -92651,17 +92658,17 @@ Returns counts: {deleted, remaining, deletedIds}`,
92651
92658
  }
92652
92659
  const directory = context?.directory ?? ctx?.directory ?? process.cwd();
92653
92660
  const taskDir = getTaskDir(config4, directory);
92654
- if (!existsSync70(taskDir)) {
92661
+ if (!existsSync71(taskDir)) {
92655
92662
  return JSON.stringify({ deleted: 0, remaining: 0, deletedIds: [] });
92656
92663
  }
92657
92664
  const files = readdirSync22(taskDir).filter((f) => f.endsWith(".json") && f.startsWith("T-"));
92658
92665
  const total = files.length;
92659
92666
  const deletedIds = [];
92660
92667
  for (const file3 of files) {
92661
- const filePath = join70(taskDir, file3);
92668
+ const filePath = join72(taskDir, file3);
92662
92669
  let raw;
92663
92670
  try {
92664
- const content = readFileSync41(filePath, "utf-8");
92671
+ const content = readFileSync42(filePath, "utf-8");
92665
92672
  raw = JSON.parse(content);
92666
92673
  } catch {
92667
92674
  continue;
@@ -92694,7 +92701,9 @@ Returns counts: {deleted, remaining, deletedIds}`,
92694
92701
  });
92695
92702
  }
92696
92703
  // src/tools/task/task-create.ts
92697
- import { join as join71 } from "path";
92704
+ import { readdirSync as readdirSync23, statSync as statSync17 } from "fs";
92705
+ import { join as join73 } from "path";
92706
+ init_logger();
92698
92707
  function createTaskCreateTool(config4, ctx) {
92699
92708
  return tool({
92700
92709
  description: `Create a new task with auto-generated ID and threadID recording.
@@ -92736,6 +92745,13 @@ async function handleCreate2(args, config4, ctx, context) {
92736
92745
  return JSON.stringify({ error: "task_lock_unavailable" });
92737
92746
  }
92738
92747
  try {
92748
+ const existingTask = findDuplicateTask(taskDir, validatedArgs.subject, directory);
92749
+ if (existingTask) {
92750
+ return JSON.stringify({
92751
+ task: { id: existingTask.id, subject: existingTask.subject },
92752
+ deduplicated: true
92753
+ });
92754
+ }
92739
92755
  const taskId = generateTaskId();
92740
92756
  const task = {
92741
92757
  id: taskId,
@@ -92752,7 +92768,7 @@ async function handleCreate2(args, config4, ctx, context) {
92752
92768
  projectRoot: directory
92753
92769
  };
92754
92770
  const validatedTask = TaskObjectSchema.parse(task);
92755
- writeJsonAtomic(join71(taskDir, `${taskId}.json`), validatedTask);
92771
+ writeJsonAtomic(join73(taskDir, `${taskId}.json`), validatedTask);
92756
92772
  return JSON.stringify({
92757
92773
  task: {
92758
92774
  id: validatedTask.id,
@@ -92772,8 +92788,47 @@ async function handleCreate2(args, config4, ctx, context) {
92772
92788
  return JSON.stringify({ error: "internal_error" });
92773
92789
  }
92774
92790
  }
92791
+ function findDuplicateTask(taskDir, subject, directory) {
92792
+ let files;
92793
+ try {
92794
+ files = readdirSync23(taskDir).filter((f) => f.endsWith(".json") && f.startsWith("T-"));
92795
+ } catch (error95) {
92796
+ log(`[task-create] Failed to list task dir ${taskDir} during dedup scan`, error95);
92797
+ return null;
92798
+ }
92799
+ for (const file3 of files) {
92800
+ const filePath = join73(taskDir, file3);
92801
+ let existing;
92802
+ try {
92803
+ existing = readJsonSafe(filePath, TaskObjectSchema);
92804
+ } catch (error95) {
92805
+ log(`[task-create] Failed to read task file ${filePath} during dedup scan`, error95);
92806
+ continue;
92807
+ }
92808
+ if (!existing)
92809
+ continue;
92810
+ const isActive = existing.status === "pending" || existing.status === "in_progress";
92811
+ if (!isActive)
92812
+ continue;
92813
+ if (existing.subject.trim() !== subject.trim())
92814
+ continue;
92815
+ if (existing.projectRoot !== directory)
92816
+ continue;
92817
+ let mtimeMs;
92818
+ try {
92819
+ mtimeMs = statSync17(filePath).mtimeMs;
92820
+ } catch (error95) {
92821
+ log(`[task-create] Failed to stat task file ${filePath} during dedup scan`, error95);
92822
+ continue;
92823
+ }
92824
+ if (Date.now() - mtimeMs > DEDUP_WINDOW_MS)
92825
+ continue;
92826
+ return existing;
92827
+ }
92828
+ return null;
92829
+ }
92775
92830
  // src/tools/task/task-get.ts
92776
- import { join as join72 } from "path";
92831
+ import { join as join74 } from "path";
92777
92832
  function parseTaskId(id) {
92778
92833
  if (!TASK_ID_PATTERN.test(id))
92779
92834
  return null;
@@ -92798,7 +92853,7 @@ Returns null if the task does not exist or the file is invalid.`,
92798
92853
  }
92799
92854
  const directory = context?.directory ?? ctx?.directory ?? process.cwd();
92800
92855
  const taskDir = getTaskDir(config4, directory);
92801
- const taskPath = join72(taskDir, `${taskId}.json`);
92856
+ const taskPath = join74(taskDir, `${taskId}.json`);
92802
92857
  const task = readJsonSafe(taskPath, TaskObjectSchema);
92803
92858
  return JSON.stringify({ task: task ?? null });
92804
92859
  } catch (error95) {
@@ -92811,35 +92866,42 @@ Returns null if the task does not exist or the file is invalid.`,
92811
92866
  });
92812
92867
  }
92813
92868
  // src/tools/task/task-list.ts
92814
- import { existsSync as existsSync71, readdirSync as readdirSync23 } from "fs";
92815
- import { join as join73 } from "path";
92869
+ import { existsSync as existsSync72, readdirSync as readdirSync24 } from "fs";
92870
+ import { join as join75 } from "path";
92816
92871
  function createTaskList(config4, ctx) {
92817
92872
  return tool({
92818
92873
  description: `List all active tasks with summary information.
92819
92874
 
92820
92875
  Returns tasks excluding completed and deleted statuses by default.
92821
92876
  For each task's blockedBy field, filters to only include unresolved (non-completed) blockers.
92822
- Returns summary format: id, subject, status, owner, blockedBy (not full description).`,
92823
- args: {},
92824
- execute: async (_args, context) => {
92877
+ Returns summary format: id, subject, status, owner, blockedBy (not full description).
92878
+
92879
+ Optional \`parentID\` filter: when provided, returns only tasks whose parentID matches
92880
+ (i.e. the subtasks of that parent task).`,
92881
+ args: {
92882
+ parentID: tool.schema.string().optional().describe("Filter to subtasks of this parent task ID")
92883
+ },
92884
+ execute: async (args, context) => {
92885
+ const parentID = args.parentID;
92825
92886
  const directory = context?.directory ?? ctx?.directory ?? process.cwd();
92826
92887
  const taskDir = getTaskDir(config4, directory);
92827
- if (!existsSync71(taskDir)) {
92888
+ if (!existsSync72(taskDir)) {
92828
92889
  return JSON.stringify({ tasks: [] });
92829
92890
  }
92830
- const files = readdirSync23(taskDir).filter((f) => f.endsWith(".json") && f.startsWith("T-")).map((f) => f.replace(".json", ""));
92891
+ const files = readdirSync24(taskDir).filter((f) => f.endsWith(".json") && f.startsWith("T-")).map((f) => f.replace(".json", ""));
92831
92892
  if (files.length === 0) {
92832
92893
  return JSON.stringify({ tasks: [] });
92833
92894
  }
92834
92895
  const allTasks = [];
92835
92896
  for (const fileId of files) {
92836
- const task = readJsonSafe(join73(taskDir, `${fileId}.json`), TaskObjectSchema);
92897
+ const task = readJsonSafe(join75(taskDir, `${fileId}.json`), TaskObjectSchema);
92837
92898
  if (task) {
92838
92899
  allTasks.push(task);
92839
92900
  }
92840
92901
  }
92841
92902
  const activeTasks = allTasks.filter((task) => task.status !== "completed" && task.status !== "deleted");
92842
- const summaries = activeTasks.map((task) => {
92903
+ const filteredTasks = parentID ? activeTasks.filter((task) => task.parentID === parentID) : activeTasks;
92904
+ const summaries = filteredTasks.map((task) => {
92843
92905
  const unresolvedBlockers = task.blockedBy.filter((blockerId) => {
92844
92906
  const blockerTask = allTasks.find((t) => t.id === blockerId);
92845
92907
  return blockerTask?.status !== "completed";
@@ -92849,7 +92911,8 @@ Returns summary format: id, subject, status, owner, blockedBy (not full descript
92849
92911
  subject: task.subject,
92850
92912
  status: task.status,
92851
92913
  owner: task.owner,
92852
- blockedBy: unresolvedBlockers
92914
+ blockedBy: unresolvedBlockers,
92915
+ parentID: task.parentID
92853
92916
  };
92854
92917
  });
92855
92918
  return JSON.stringify({
@@ -92860,7 +92923,7 @@ Returns summary format: id, subject, status, owner, blockedBy (not full descript
92860
92923
  });
92861
92924
  }
92862
92925
  // src/tools/task/task-update.ts
92863
- import { join as join74 } from "path";
92926
+ import { join as join76 } from "path";
92864
92927
  function parseTaskId2(id) {
92865
92928
  if (!TASK_ID_PATTERN.test(id))
92866
92929
  return null;
@@ -92906,7 +92969,7 @@ async function handleUpdate(args, config4, ctx, context) {
92906
92969
  return JSON.stringify({ error: "task_lock_unavailable" });
92907
92970
  }
92908
92971
  try {
92909
- const taskPath = join74(taskDir, `${taskId}.json`);
92972
+ const taskPath = join76(taskDir, `${taskId}.json`);
92910
92973
  const task = readJsonSafe(taskPath, TaskObjectSchema);
92911
92974
  if (!task) {
92912
92975
  return JSON.stringify({ error: "task_not_found" });
@@ -93051,6 +93114,7 @@ function createToolGuardHooks(args) {
93051
93114
  const { ctx, pluginConfig, isHookEnabled, safeHookEnabled } = args;
93052
93115
  const evolutionEnabled = pluginConfig.evolution?.enabled === true;
93053
93116
  const safeHook = (hookName, factory) => safeCreateHook(hookName, factory, { enabled: safeHookEnabled });
93117
+ const backgroundTaskBlocker = isHookEnabled("background-task-blocker") ? safeHook("background-task-blocker", () => createBackgroundTaskBlockerHook()) : null;
93054
93118
  const commentChecker = isHookEnabled("comment-checker") ? safeHook("comment-checker", () => createCommentCheckerHooks(pluginConfig.comment_checker)) : null;
93055
93119
  const toolOutputTruncator = isHookEnabled("tool-output-truncator") ? safeHook("tool-output-truncator", () => createToolOutputTruncatorHook(ctx, { experimental: pluginConfig.experimental })) : null;
93056
93120
  let directoryAgentsInjector = null;
@@ -93084,6 +93148,7 @@ function createToolGuardHooks(args) {
93084
93148
  const taskEditGuard = isHookEnabled("task-edit-guard") ? safeHook("task-edit-guard", () => createTaskEditGuardHook(ctx)) : null;
93085
93149
  const evolutionWatcher = evolutionEnabled && isHookEnabled("evolution-watcher") ? safeHook("evolution-watcher", () => createEvolutionWatcherHook(ctx, pluginConfig.evolution)) : null;
93086
93150
  return {
93151
+ backgroundTaskBlocker,
93087
93152
  commentChecker,
93088
93153
  toolOutputTruncator,
93089
93154
  directoryAgentsInjector,
@@ -93443,6 +93508,10 @@ class ConcurrencyManager {
93443
93508
  return this.queues.get(model)?.length ?? 0;
93444
93509
  }
93445
93510
  }
93511
+ // src/features/background-agent/handle-index.ts
93512
+ import { existsSync as existsSync73, mkdirSync as mkdirSync19, readdirSync as readdirSync25, readFileSync as readFileSync43, renameSync as renameSync5, unlinkSync as unlinkSync12, writeFileSync as writeFileSync26 } from "fs";
93513
+ import { dirname as dirname19, join as join77 } from "path";
93514
+
93446
93515
  // src/features/background-agent/constants.ts
93447
93516
  var TASK_TTL_MS = 30 * 60 * 1000;
93448
93517
  var MIN_STABILITY_TIME_MS2 = 10 * 1000;
@@ -93457,6 +93526,126 @@ var POLLING_INTERVAL_MS = 3000;
93457
93526
  var PRUNE_THROTTLE_MS = 30000;
93458
93527
  var TASK_CLEANUP_DELAY_MS = 10 * 60 * 1000;
93459
93528
 
93529
+ // src/features/background-agent/handle-index.ts
93530
+ var BG_HANDLE_DIR_NAME = "bg-handles";
93531
+ var BG_HANDLE_FILE_PREFIX = "bg_";
93532
+ var BgHandleSchema = exports_external.object({
93533
+ taskId: exports_external.string(),
93534
+ parentSessionID: exports_external.string(),
93535
+ parentMessageID: exports_external.string(),
93536
+ description: exports_external.string(),
93537
+ agent: exports_external.string(),
93538
+ status: exports_external.enum(["pending", "running", "completed", "error", "cancelled", "interrupt"]),
93539
+ model: exports_external.object({
93540
+ providerID: exports_external.string(),
93541
+ modelID: exports_external.string(),
93542
+ variant: exports_external.string().optional(),
93543
+ temperature: exports_external.number().optional()
93544
+ }).optional(),
93545
+ category: exports_external.string().optional(),
93546
+ concurrencyGroup: exports_external.string().optional(),
93547
+ queuedAt: exports_external.number().optional(),
93548
+ startedAt: exports_external.number().optional(),
93549
+ completedAt: exports_external.number().optional()
93550
+ }).strict();
93551
+ function getBgHandleDir(directory) {
93552
+ return join77(directory, ".matrixx", BG_HANDLE_DIR_NAME);
93553
+ }
93554
+ function getHandlePath(directory, taskId) {
93555
+ return join77(getBgHandleDir(directory), `${taskId}.json`);
93556
+ }
93557
+ function ensureDir3(dirPath) {
93558
+ if (!existsSync73(dirPath)) {
93559
+ mkdirSync19(dirPath, { recursive: true });
93560
+ }
93561
+ }
93562
+ function toHandle(task) {
93563
+ return {
93564
+ taskId: task.id,
93565
+ parentSessionID: task.parentSessionID,
93566
+ parentMessageID: task.parentMessageID,
93567
+ description: task.description,
93568
+ agent: task.agent,
93569
+ status: task.status,
93570
+ model: task.model,
93571
+ category: task.category,
93572
+ concurrencyGroup: task.concurrencyGroup,
93573
+ queuedAt: task.queuedAt?.getTime(),
93574
+ startedAt: task.startedAt?.getTime(),
93575
+ completedAt: task.completedAt?.getTime()
93576
+ };
93577
+ }
93578
+ function writeHandle(directory, task) {
93579
+ const filePath = getHandlePath(directory, task.id);
93580
+ ensureDir3(dirname19(filePath));
93581
+ const tempPath = `${filePath}.tmp.${process.pid}.${Date.now()}`;
93582
+ try {
93583
+ writeFileSync26(tempPath, JSON.stringify(toHandle(task), null, 2), "utf-8");
93584
+ renameSync5(tempPath, filePath);
93585
+ } catch (error95) {
93586
+ try {
93587
+ if (existsSync73(tempPath)) {
93588
+ unlinkSync12(tempPath);
93589
+ }
93590
+ } catch {}
93591
+ throw error95;
93592
+ }
93593
+ }
93594
+ function readHandleFile(filePath) {
93595
+ try {
93596
+ if (!existsSync73(filePath)) {
93597
+ return null;
93598
+ }
93599
+ const parsed = JSON.parse(readFileSync43(filePath, "utf-8"));
93600
+ const result = BgHandleSchema.safeParse(parsed);
93601
+ return result.success ? result.data : null;
93602
+ } catch {
93603
+ return null;
93604
+ }
93605
+ }
93606
+ function readHandles(directory) {
93607
+ const dir = getBgHandleDir(directory);
93608
+ if (!existsSync73(dir)) {
93609
+ return [];
93610
+ }
93611
+ const handles = [];
93612
+ for (const name of readdirSync25(dir)) {
93613
+ if (!name.startsWith(BG_HANDLE_FILE_PREFIX) || !name.endsWith(".json")) {
93614
+ continue;
93615
+ }
93616
+ const handle = readHandleFile(join77(dir, name));
93617
+ if (handle) {
93618
+ handles.push(handle);
93619
+ }
93620
+ }
93621
+ return handles;
93622
+ }
93623
+ function sweepStaleHandles(directory, ttlMs = TASK_TTL_MS, now = Date.now()) {
93624
+ const dir = getBgHandleDir(directory);
93625
+ if (!existsSync73(dir)) {
93626
+ return 0;
93627
+ }
93628
+ let removed = 0;
93629
+ for (const name of readdirSync25(dir)) {
93630
+ if (!name.startsWith(BG_HANDLE_FILE_PREFIX) || !name.endsWith(".json")) {
93631
+ continue;
93632
+ }
93633
+ const filePath = join77(dir, name);
93634
+ const handle = readHandleFile(filePath);
93635
+ const timestamp = handle?.completedAt ?? handle?.startedAt ?? handle?.queuedAt;
93636
+ if (handle && timestamp !== undefined && now - timestamp <= ttlMs) {
93637
+ continue;
93638
+ }
93639
+ try {
93640
+ unlinkSync12(filePath);
93641
+ removed++;
93642
+ } catch {}
93643
+ }
93644
+ if (removed > 0) {
93645
+ log(`[bg-handles] Swept ${removed} stale handle file(s) from ${dir}`);
93646
+ }
93647
+ return removed;
93648
+ }
93460
93649
  // src/features/background-agent/error-helpers.ts
93461
93650
  function getErrorText(error95) {
93462
93651
  if (!error95)
@@ -93559,19 +93748,19 @@ function detectRepetitiveToolUse(window2) {
93559
93748
  }
93560
93749
 
93561
93750
  // src/features/background-agent/message-dir.ts
93562
- import { existsSync as existsSync72, readdirSync as readdirSync24, readFileSync as readFileSync42 } from "fs";
93563
- import { join as join75 } from "path";
93751
+ import { existsSync as existsSync74, readdirSync as readdirSync26, readFileSync as readFileSync44 } from "fs";
93752
+ import { join as join78 } from "path";
93564
93753
  var MESSAGE_DIR_CACHE_CAP = 1000;
93565
93754
  var messageDirCache = new Map;
93566
93755
  function computeMessageDir(sessionID) {
93567
- if (!existsSync72(MESSAGE_STORAGE))
93756
+ if (!existsSync74(MESSAGE_STORAGE))
93568
93757
  return null;
93569
- const directPath = join75(MESSAGE_STORAGE, sessionID);
93570
- if (existsSync72(directPath))
93758
+ const directPath = join78(MESSAGE_STORAGE, sessionID);
93759
+ if (existsSync74(directPath))
93571
93760
  return directPath;
93572
- for (const dir of readdirSync24(MESSAGE_STORAGE)) {
93573
- const sessionPath = join75(MESSAGE_STORAGE, dir, sessionID);
93574
- if (existsSync72(sessionPath))
93761
+ for (const dir of readdirSync26(MESSAGE_STORAGE)) {
93762
+ const sessionPath = join78(MESSAGE_STORAGE, dir, sessionID);
93763
+ if (existsSync74(sessionPath))
93575
93764
  return sessionPath;
93576
93765
  }
93577
93766
  return null;
@@ -93605,11 +93794,11 @@ function hasPartialAgentOrModel(message) {
93605
93794
  }
93606
93795
  function findNearestMessageExcludingCompaction(messageDir) {
93607
93796
  try {
93608
- const files = readdirSync24(messageDir).filter((name) => name.endsWith(".json")).sort().reverse();
93797
+ const files = readdirSync26(messageDir).filter((name) => name.endsWith(".json")).sort().reverse();
93609
93798
  const parsedByFile = new Map;
93610
93799
  for (const file3 of files) {
93611
93800
  try {
93612
- const content = readFileSync42(join75(messageDir, file3), "utf-8");
93801
+ const content = readFileSync44(join78(messageDir, file3), "utf-8");
93613
93802
  const parsed = JSON.parse(content);
93614
93803
  parsedByFile.set(file3, parsed);
93615
93804
  if (hasFullAgentAndModel(parsed)) {
@@ -93757,6 +93946,25 @@ class TaskHistory {
93757
93946
  }
93758
93947
 
93759
93948
  // src/features/background-agent/manager.ts
93949
+ function restoreTaskFromHandle(handle) {
93950
+ const interrupted = handle.status === "running" || handle.status === "pending";
93951
+ return {
93952
+ id: handle.taskId,
93953
+ parentSessionID: handle.parentSessionID,
93954
+ parentMessageID: handle.parentMessageID,
93955
+ description: handle.description,
93956
+ prompt: "",
93957
+ agent: handle.agent,
93958
+ status: interrupted ? "interrupt" : handle.status,
93959
+ queuedAt: handle.queuedAt !== undefined ? new Date(handle.queuedAt) : undefined,
93960
+ startedAt: handle.startedAt !== undefined ? new Date(handle.startedAt) : undefined,
93961
+ completedAt: handle.completedAt !== undefined ? new Date(handle.completedAt) : undefined,
93962
+ model: handle.model,
93963
+ category: handle.category,
93964
+ concurrencyGroup: handle.concurrencyGroup
93965
+ };
93966
+ }
93967
+
93760
93968
  class BackgroundManager {
93761
93969
  static cleanupManagers = new Set;
93762
93970
  static cleanupRegistered = false;
@@ -93797,6 +94005,28 @@ class BackgroundManager {
93797
94005
  this.enableParentSessionNotifications = options?.enableParentSessionNotifications ?? true;
93798
94006
  this.registerProcessCleanup();
93799
94007
  }
94008
+ persistHandle(task) {
94009
+ try {
94010
+ writeHandle(this.directory, task);
94011
+ } catch (error95) {
94012
+ log("[background-agent] Failed to persist background handle:", { taskId: task.id, error: error95 });
94013
+ }
94014
+ }
94015
+ async restoreHandles() {
94016
+ const swept = sweepStaleHandles(this.directory);
94017
+ const handles = readHandles(this.directory);
94018
+ let restored = 0;
94019
+ for (const handle of handles) {
94020
+ if (this.tasks.has(handle.taskId)) {
94021
+ continue;
94022
+ }
94023
+ this.tasks.set(handle.taskId, restoreTaskFromHandle(handle));
94024
+ restored++;
94025
+ }
94026
+ if (restored > 0 || swept > 0) {
94027
+ log("[background-agent] Restored background handles:", { restored, swept });
94028
+ }
94029
+ }
93800
94030
  async launch(input) {
93801
94031
  log("[background-agent] launch() called with:", {
93802
94032
  agent: input.agent,
@@ -93948,6 +94178,7 @@ class BackgroundManager {
93948
94178
  };
93949
94179
  task.concurrencyKey = concurrencyKey;
93950
94180
  task.concurrencyGroup = concurrencyKey;
94181
+ this.persistHandle(task);
93951
94182
  this.taskHistory.record(input.parentSessionID, { id: task.id, sessionID, agent: input.agent, description: input.description, status: "running", category: input.category, startedAt: task.startedAt });
93952
94183
  this.startPolling();
93953
94184
  log("[background-agent] Launching task:", { taskId: task.id, sessionID, agent: input.agent });
@@ -93999,6 +94230,7 @@ class BackgroundManager {
93999
94230
  existingTask.error = errorMessage;
94000
94231
  }
94001
94232
  existingTask.completedAt = new Date;
94233
+ this.persistHandle(existingTask);
94002
94234
  if (existingTask.concurrencyKey) {
94003
94235
  this.concurrencyManager.release(existingTask.concurrencyKey);
94004
94236
  existingTask.concurrencyKey = undefined;
@@ -94101,6 +94333,7 @@ class BackgroundManager {
94101
94333
  concurrencyGroup
94102
94334
  };
94103
94335
  this.tasks.set(task.id, task);
94336
+ this.persistHandle(task);
94104
94337
  registerSubagentSession(input.sessionID, input.parentSessionID);
94105
94338
  this.startPolling();
94106
94339
  this.taskHistory.record(input.parentSessionID, { id: task.id, sessionID: input.sessionID, agent: input.agent || "task", description: input.description, status: "running", startedAt: task.startedAt });
@@ -94147,6 +94380,7 @@ class BackgroundManager {
94147
94380
  existingTask.parentTools = input.parentTools;
94148
94381
  }
94149
94382
  existingTask.startedAt = new Date;
94383
+ this.persistHandle(existingTask);
94150
94384
  existingTask.progress = {
94151
94385
  toolCalls: existingTask.progress?.toolCalls ?? 0,
94152
94386
  lastUpdate: new Date
@@ -94202,6 +94436,7 @@ class BackgroundManager {
94202
94436
  const errorMessage = error95 instanceof Error ? error95.message : String(error95);
94203
94437
  existingTask.error = errorMessage;
94204
94438
  existingTask.completedAt = new Date;
94439
+ this.persistHandle(existingTask);
94205
94440
  if (existingTask.concurrencyKey) {
94206
94441
  this.concurrencyManager.release(existingTask.concurrencyKey);
94207
94442
  existingTask.concurrencyKey = undefined;
@@ -94366,6 +94601,7 @@ class BackgroundManager {
94366
94601
  task.status = "error";
94367
94602
  task.error = errorMessage ?? "Session error";
94368
94603
  task.completedAt = new Date;
94604
+ this.persistHandle(task);
94369
94605
  this.taskHistory.record(task.parentSessionID, { id: task.id, sessionID: task.sessionID, agent: task.agent, description: task.description, status: "error", category: task.category, startedAt: task.startedAt, completedAt: task.completedAt });
94370
94606
  if (task.concurrencyKey) {
94371
94607
  this.concurrencyManager.release(task.concurrencyKey);
@@ -94534,6 +94770,7 @@ class BackgroundManager {
94534
94770
  }
94535
94771
  task.status = "cancelled";
94536
94772
  task.completedAt = new Date;
94773
+ this.persistHandle(task);
94537
94774
  if (reason) {
94538
94775
  task.error = reason;
94539
94776
  }
@@ -94656,6 +94893,7 @@ class BackgroundManager {
94656
94893
  }
94657
94894
  task.status = "completed";
94658
94895
  task.completedAt = new Date;
94896
+ this.persistHandle(task);
94659
94897
  this.taskHistory.record(task.parentSessionID, { id: task.id, sessionID: task.sessionID, agent: task.agent, description: task.description, status: "completed", category: task.category, startedAt: task.startedAt, completedAt: task.completedAt });
94660
94898
  if (task.concurrencyKey) {
94661
94899
  this.concurrencyManager.release(task.concurrencyKey);
@@ -94788,6 +95026,7 @@ class BackgroundManager {
94788
95026
  task.status = "error";
94789
95027
  task.error = errorMessage;
94790
95028
  task.completedAt = new Date;
95029
+ this.persistHandle(task);
94791
95030
  if (task.concurrencyKey) {
94792
95031
  this.concurrencyManager.release(task.concurrencyKey);
94793
95032
  task.concurrencyKey = undefined;
@@ -94813,7 +95052,7 @@ class BackgroundManager {
94813
95052
  }
94814
95053
  this.tasks.delete(taskId);
94815
95054
  if (task.sessionID) {
94816
- subagentSessions.delete(task.sessionID);
95055
+ unregisterSubagentSession(task.sessionID);
94817
95056
  }
94818
95057
  }
94819
95058
  }
@@ -94872,6 +95111,7 @@ class BackgroundManager {
94872
95111
  task.status = "cancelled";
94873
95112
  task.error = `Stale timeout (no activity for ${staleMinutes2}min since start)`;
94874
95113
  task.completedAt = new Date;
95114
+ this.persistHandle(task);
94875
95115
  if (task.concurrencyKey) {
94876
95116
  this.concurrencyManager.release(task.concurrencyKey);
94877
95117
  task.concurrencyKey = undefined;
@@ -94900,6 +95140,7 @@ class BackgroundManager {
94900
95140
  task.status = "cancelled";
94901
95141
  task.error = `Stale timeout (no activity for ${staleMinutes}min)`;
94902
95142
  task.completedAt = new Date;
95143
+ this.persistHandle(task);
94903
95144
  if (task.concurrencyKey) {
94904
95145
  this.concurrencyManager.release(task.concurrencyKey);
94905
95146
  task.concurrencyKey = undefined;
@@ -95791,9 +96032,9 @@ class TmuxSessionManager {
95791
96032
  var SESSION_TIMEOUT_MS3 = 10 * 60 * 1000;
95792
96033
  var MIN_STABILITY_TIME_MS4 = 10 * 1000;
95793
96034
  // src/agents/dynamic-agent-prompt-builder.ts
95794
- import { existsSync as existsSync73, readFileSync as readFileSync43 } from "fs";
95795
- import { homedir as homedir10 } from "os";
95796
- import { join as join76 } from "path";
96035
+ import { existsSync as existsSync75, readFileSync as readFileSync45 } from "fs";
96036
+ import { homedir as homedir11 } from "os";
96037
+ import { join as join79 } from "path";
95797
96038
  init_logger();
95798
96039
  function categorizeTools(toolNames) {
95799
96040
  return toolNames.map((name) => {
@@ -96155,24 +96396,24 @@ function exploreCtxPart(hasGrepGlob) {
96155
96396
  function resolveContextModeDisciplinePath() {
96156
96397
  try {
96157
96398
  const resolved = __require.resolve("context-mode/configs/opencode/AGENTS.md");
96158
- if (existsSync73(resolved))
96399
+ if (existsSync75(resolved))
96159
96400
  return resolved;
96160
96401
  } catch {}
96161
96402
  try {
96162
96403
  const cacheDir = getOpenCodeCacheDir();
96163
- const p = join76(cacheDir, "packages/context-mode@latest/node_modules/context-mode/configs/opencode/AGENTS.md");
96164
- if (existsSync73(p))
96404
+ const p = join79(cacheDir, "packages/context-mode@latest/node_modules/context-mode/configs/opencode/AGENTS.md");
96405
+ if (existsSync75(p))
96165
96406
  return p;
96166
96407
  } catch {}
96167
96408
  try {
96168
- const base = process.env.XDG_CACHE_HOME ?? join76(homedir10(), ".cache");
96169
- const p = join76(base, "opencode/packages/context-mode@latest/node_modules/context-mode/configs/opencode/AGENTS.md");
96170
- if (existsSync73(p))
96409
+ const base = process.env.XDG_CACHE_HOME ?? join79(homedir11(), ".cache");
96410
+ const p = join79(base, "opencode/packages/context-mode@latest/node_modules/context-mode/configs/opencode/AGENTS.md");
96411
+ if (existsSync75(p))
96171
96412
  return p;
96172
96413
  } catch {}
96173
96414
  try {
96174
- const p2 = join76(homedir10(), ".cache/opencode/packages/context-mode@latest/node_modules/context-mode/configs/opencode/AGENTS.md");
96175
- if (existsSync73(p2))
96415
+ const p2 = join79(homedir11(), ".cache/opencode/packages/context-mode@latest/node_modules/context-mode/configs/opencode/AGENTS.md");
96416
+ if (existsSync75(p2))
96176
96417
  return p2;
96177
96418
  } catch {}
96178
96419
  return null;
@@ -96190,11 +96431,11 @@ function loadContextModeDiscipline(hasGrepGlob = true) {
96190
96431
  const p = resolveContextModeDisciplinePath();
96191
96432
  if (!p)
96192
96433
  throw new Error("discipline file not found");
96193
- const raw = readFileSync43(p, "utf8");
96434
+ const raw = readFileSync45(p, "utf8");
96194
96435
  let version4 = "1.0.169";
96195
96436
  try {
96196
96437
  const pkgResolved = __require.resolve("context-mode/package.json");
96197
- const pkgRaw = readFileSync43(pkgResolved, "utf8");
96438
+ const pkgRaw = readFileSync45(pkgResolved, "utf8");
96198
96439
  const pkg = JSON.parse(pkgRaw);
96199
96440
  if (pkg.version)
96200
96441
  version4 = pkg.version;
@@ -96241,11 +96482,11 @@ function loadCompactContextDiscipline(hasGrepGlob = true) {
96241
96482
  const p = resolveContextModeDisciplinePath();
96242
96483
  if (!p)
96243
96484
  throw new Error("discipline file not found");
96244
- const raw = readFileSync43(p, "utf8");
96485
+ const raw = readFileSync45(p, "utf8");
96245
96486
  let version4 = "1.0.169";
96246
96487
  try {
96247
96488
  const pkgResolved = __require.resolve("context-mode/package.json");
96248
- const pkgRaw = readFileSync43(pkgResolved, "utf8");
96489
+ const pkgRaw = readFileSync45(pkgResolved, "utf8");
96249
96490
  const pkg = JSON.parse(pkgRaw);
96250
96491
  if (pkg.version)
96251
96492
  version4 = pkg.version;
@@ -97314,8 +97555,8 @@ function createBddContractAgent(model) {
97314
97555
  createBddContractAgent.mode = MODE2;
97315
97556
 
97316
97557
  // src/agents/builtin-agents/resolve-file-uri.ts
97317
- import { existsSync as existsSync74, readFileSync as readFileSync44 } from "fs";
97318
- import { homedir as homedir11 } from "os";
97558
+ import { existsSync as existsSync76, readFileSync as readFileSync46 } from "fs";
97559
+ import { homedir as homedir12 } from "os";
97319
97560
  import { isAbsolute as isAbsolute6, resolve as resolve10 } from "path";
97320
97561
  function resolvePromptAppend(promptAppend, configDir) {
97321
97562
  if (!promptAppend.startsWith("file://"))
@@ -97324,16 +97565,16 @@ function resolvePromptAppend(promptAppend, configDir) {
97324
97565
  let filePath;
97325
97566
  try {
97326
97567
  const decoded = decodeURIComponent(encoded);
97327
- const expanded = decoded.startsWith("~/") ? decoded.replace(/^~\//, `${homedir11()}/`) : decoded;
97568
+ const expanded = decoded.startsWith("~/") ? decoded.replace(/^~\//, `${homedir12()}/`) : decoded;
97328
97569
  filePath = isAbsolute6(expanded) ? expanded : resolve10(configDir ?? process.cwd(), expanded);
97329
97570
  } catch {
97330
97571
  return `[WARNING: Malformed file URI (invalid percent-encoding): ${promptAppend}]`;
97331
97572
  }
97332
- if (!existsSync74(filePath)) {
97573
+ if (!existsSync76(filePath)) {
97333
97574
  return `[WARNING: Could not resolve file URI: ${promptAppend}]`;
97334
97575
  }
97335
97576
  try {
97336
- return readFileSync44(filePath, "utf8");
97577
+ return readFileSync46(filePath, "utf8");
97337
97578
  } catch {
97338
97579
  return `[WARNING: Could not read file: ${promptAppend}]`;
97339
97580
  }
@@ -99217,16 +99458,19 @@ var OPERATOR_PROMPT_METADATA = {
99217
99458
  category: "exploration",
99218
99459
  cost: "CHEAP",
99219
99460
  promptAlias: "Librarian",
99220
- keyTrigger: "External library/source mentioned \u2192 fire `librarian` background",
99461
+ keyTrigger: "External library/source mentioned or code search needed \u2192 fire `librarian` background",
99221
99462
  triggers: [
99222
- { domain: "Librarian", trigger: "Unfamiliar packages / libraries, struggles at weird behaviour (to find existing implementation of opensource)" }
99463
+ { domain: "Librarian", trigger: "Unfamiliar packages / libraries, struggles at weird behaviour (to find existing implementation of opensource)" },
99464
+ { domain: "Code Search", trigger: "Find real-world code examples, search GitHub repos for patterns, or need OSS implementation references" }
99223
99465
  ],
99224
99466
  useWhen: [
99225
99467
  "How do I use [library]?",
99226
99468
  "What's the best practice for [framework feature]?",
99227
99469
  "Why does [external dependency] behave this way?",
99228
99470
  "Find examples of [library] usage",
99229
- "Working with unfamiliar npm/pip/cargo packages"
99471
+ "Working with unfamiliar npm/pip/cargo packages",
99472
+ "Show me real-world code examples of [pattern]",
99473
+ "Search GitHub for implementations of [feature]"
99230
99474
  ]
99231
99475
  };
99232
99476
  function createOperatorAgent(model) {
@@ -99327,7 +99571,7 @@ context7_query-docs(libraryId: id, query: "specific topic")
99327
99571
  Tool 1: context7_resolve-library-id("library-name")
99328
99572
  \u2192 then context7_query-docs(libraryId: id, query: "specific-topic")
99329
99573
  Tool 2: webfetch(relevant_pages_from_sitemap) // Targeted, not random
99330
- Tool 3: grep_app_searchGitHub(query: "usage pattern", language: ["TypeScript"])
99574
+ Tool 3: white_rabbit_searchGitHub(query: "usage pattern", language: ["TypeScript"])
99331
99575
  \`\`\`
99332
99576
 
99333
99577
  **Output**: Summarize findings with links to official docs (versioned if applicable) and real-world examples.
@@ -99346,8 +99590,8 @@ Step 2: Get commit SHA for permalinks
99346
99590
  cd \${TMPDIR:-/tmp}/repo-name && git rev-parse HEAD
99347
99591
 
99348
99592
  Step 3: Find the implementation
99349
- - grep/ast_grep_search for function/class
99350
- - read the specific file
99593
+ - ctx_search / ast_grep_search for function/class \u2014 NOT grep (blocked under enforce:true)
99594
+ - ctx_execute_file for analysis (or read for targeted sections)
99351
99595
  - git blame for context if needed
99352
99596
 
99353
99597
  Step 4: Construct permalink
@@ -99357,7 +99601,7 @@ Step 4: Construct permalink
99357
99601
  **Parallel acceleration (4+ calls)**:
99358
99602
  \`\`\`
99359
99603
  Tool 1: gh repo clone owner/repo \${TMPDIR:-/tmp}/repo -- --depth 1
99360
- Tool 2: grep_app_searchGitHub(query: "function_name", repo: "owner/repo")
99604
+ Tool 2: white_rabbit_searchGitHub(query: "function_name", repo: "owner/repo")
99361
99605
  Tool 3: gh api repos/owner/repo/commits/HEAD --jq '.sha'
99362
99606
  Tool 4: context7_get-library-docs(id, topic: "relevant-api")
99363
99607
  \`\`\`
@@ -99396,8 +99640,8 @@ Tool 1: context7_resolve-library-id \u2192 context7_query-docs
99396
99640
  Tool 2: webfetch(targeted_doc_pages_from_sitemap)
99397
99641
 
99398
99642
  // Code Search
99399
- Tool 3: grep_app_searchGitHub(query: "pattern1", language: [...])
99400
- Tool 4: grep_app_searchGitHub(query: "pattern2", useRegexp: true)
99643
+ Tool 3: white_rabbit_searchGitHub(query: "pattern1", language: [...])
99644
+ Tool 4: white_rabbit_searchGitHub(query: "pattern2", useRegexp: true)
99401
99645
 
99402
99646
  // Source Analysis
99403
99647
  Tool 5: gh repo clone owner/repo \${TMPDIR:-/tmp}/repo -- --depth 1
@@ -99453,7 +99697,7 @@ https://github.com/tanstack/query/blob/abc123def/packages/react-query/src/useQue
99453
99697
  | **Sitemap Discovery** | webfetch | \`webfetch(docs_url + "/sitemap.xml")\` to understand doc structure |
99454
99698
  | **Read Doc Page** | webfetch | \`webfetch(specific_doc_page)\` for targeted documentation |
99455
99699
  | **Latest Info** | websearch_exa | \`websearch_exa_web_search_exa("query ${new Date().getFullYear()}")\` |
99456
- | **Fast Code Search** | grep_app | \`grep_app_searchGitHub(query, language, useRegexp)\` |
99700
+ | **Fast Code Search** | white_rabbit | \`white_rabbit_searchGitHub(query, language, useRegexp)\` |
99457
99701
  | **Deep Code Search** | gh CLI | \`gh search code "query" --repo owner/repo\` |
99458
99702
  | **Clone Repo** | gh CLI | \`gh repo clone owner/repo \${TMPDIR:-/tmp}/name -- --depth 1\` |
99459
99703
  | **Issues/PRs** | gh CLI | \`gh search issues/prs "query" --repo owner/repo\` |
@@ -99489,16 +99733,16 @@ Use OS-appropriate temp directory:
99489
99733
  **Doc Discovery is SEQUENTIAL** (websearch \u2192 version check \u2192 sitemap \u2192 investigate).
99490
99734
  **Main phase is PARALLEL** once you know where to look.
99491
99735
 
99492
- **Always vary queries** when using grep_app:
99736
+ **Always vary queries** when using white_rabbit:
99493
99737
  \`\`\`
99494
99738
  // GOOD: Different angles
99495
- grep_app_searchGitHub(query: "useQuery(", language: ["TypeScript"])
99496
- grep_app_searchGitHub(query: "queryOptions", language: ["TypeScript"])
99497
- grep_app_searchGitHub(query: "staleTime:", language: ["TypeScript"])
99739
+ white_rabbit_searchGitHub(query: "useQuery(", language: ["TypeScript"])
99740
+ white_rabbit_searchGitHub(query: "queryOptions", language: ["TypeScript"])
99741
+ white_rabbit_searchGitHub(query: "staleTime:", language: ["TypeScript"])
99498
99742
 
99499
99743
  // BAD: Same pattern
99500
- grep_app_searchGitHub(query: "useQuery")
99501
- grep_app_searchGitHub(query: "useQuery")
99744
+ white_rabbit_searchGitHub(query: "useQuery")
99745
+ white_rabbit_searchGitHub(query: "useQuery")
99502
99746
  \`\`\`
99503
99747
 
99504
99748
  ---
@@ -99508,7 +99752,7 @@ grep_app_searchGitHub(query: "useQuery")
99508
99752
  | Failure | Recovery Action |
99509
99753
  |---------|-----------------|
99510
99754
  | context7 not found | Clone repo, read source + README directly |
99511
- | grep_app no results | Broaden query, try concept instead of exact name |
99755
+ | white_rabbit no results | Broaden query, try concept instead of exact name |
99512
99756
  | gh API rate limit | Use cloned repo in temp directory |
99513
99757
  | Repo not found | Search for forks or mirrors |
99514
99758
  | Sitemap not found | Try \`/sitemap-0.xml\`, \`/sitemap_index.xml\`, or fetch docs index page and parse navigation |
@@ -99519,7 +99763,7 @@ grep_app_searchGitHub(query: "useQuery")
99519
99763
 
99520
99764
  ## COMMUNICATION RULES
99521
99765
 
99522
- 1. **NO TOOL NAMES**: Say "I'll search the codebase" not "I'll use grep_app"
99766
+ 1. **NO TOOL NAMES**: Say "I'll search the codebase" not "I'll use white_rabbit"
99523
99767
  2. **NO PREAMBLE**: Answer directly, skip "I'll help you with..."
99524
99768
  3. **ALWAYS CITE**: Every code claim needs a permalink
99525
99769
  4. **USE MARKDOWN**: Code blocks with language identifiers
@@ -100496,8 +100740,8 @@ Your response has **FAILED** if:
100496
100740
  Use the right tool for the job:
100497
100741
  - **Semantic search** (definitions, references): LSP tools
100498
100742
  - **Structural patterns** (function shapes, class structures): ast_grep_search
100499
- - **Text patterns** (strings, comments, logs): grep (if available; otherwise ctx_search / ast_grep_search)
100500
- - **File patterns** (find by name/extension): glob (if available; otherwise ctx_search / LSP)
100743
+ - **Text patterns** (strings, comments, logs): ctx_search (indexed KB) -> ctx_batch_execute / ctx_execute for multi-file analysis. ast_grep_search for structural patterns. grep/glob only as fallback when ctx_* unavailable.
100744
+ - **File patterns** (find by name/extension): ctx_search (indexed KB) -> LSP / ast_grep_search. glob only as fallback when ctx_* unavailable.
100501
100745
  - **History/evolution** (when added, who changed): git commands
100502
100746
 
100503
100747
  Flood with parallel calls. Cross-validate findings across multiple tools.`
@@ -100695,6 +100939,13 @@ This will:
100695
100939
  5. **Seraph Before Plan** - Always catch gaps before committing to plan
100696
100940
  6. **Choice-Based Handoff** - Present "Start Work" vs "High Accuracy Review" choice after plan
100697
100941
  7. **Draft as External Memory** - Continuously record to draft; delete after plan complete
100942
+ 8. **Background-First Delegation** - Default to \`run_in_background=true\` for every delegation; never nest a blocking subagent call inside an already-blocking session
100943
+
100944
+ ### Delegation Policy (MANDATORY)
100945
+
100946
+ - **Default to background mode**: every Oracle delegation passes \`run_in_background=true\`.
100947
+ - **Never nest a blocking subagent call** (\`run_in_background=false\`) inside an already-blocking session. Oracle may itself run under a fixed poll budget (default 600s); a nested blocking call consumes that budget and stalls at the timeout.
100948
+ - **Use sequential top-level calls instead**: fire the delegation in background, return to your work, then collect the result with \`background_output(task_id=...)\`.
100698
100949
 
100699
100950
  ---
100700
100951
 
@@ -100728,12 +100979,14 @@ var ORACLE_HIGH_ACCURACY_MODE = `# PHASE 3: PLAN GENERATION
100728
100979
  \`\`\`typescript
100729
100980
  // After generating initial plan
100730
100981
  while (true) {
100731
- const result = task(
100982
+ // Fire Smith in background (never a blocking nested call), then collect its verdict.
100983
+ const smithTask = task(
100732
100984
  subagent_type="smith",
100733
100985
  load_skills=[],
100734
100986
  prompt=".matrixx/plans/{name}.md",
100735
- run_in_background=false
100987
+ run_in_background=true
100736
100988
  )
100989
+ const result = background_output(task_id=smithTask.task_id)
100737
100990
 
100738
100991
  if (result.verdict === "OKAY") {
100739
100992
  break // Plan approved - exit loop
@@ -101338,7 +101591,7 @@ task(subagent_type="operator", load_skills=[], prompt="I'm designing architectur
101338
101591
 
101339
101592
  **Oracle Consultation** (recommend when stakes are high):
101340
101593
  \`\`\`typescript
101341
- task(subagent_type="oracle", load_skills=[], prompt="Architecture consultation needed: [context]...", run_in_background=false)
101594
+ task(subagent_type="oracle", load_skills=[], prompt="Architecture consultation needed: [context]...", run_in_background=true)
101342
101595
  \`\`\`
101343
101596
 
101344
101597
  **Interview Focus:**
@@ -101500,12 +101753,14 @@ todoWrite([
101500
101753
 
101501
101754
  > **Edge \u2014 Trivial/Simple bypass:** If complexity is 1-2 (Trivial/Simple) and the request is ambiguous but single-scope (one domain, no architectural keywords), **bypass Seraph** and ask directly per Morpheus Phase 0 (ask ONE clarifying question).
101502
101755
 
101503
- If gate **passes**, summon Seraph:
101756
+ If gate **passes**, summon Seraph in **background mode** (never blocking \u2014 see policy below):
101504
101757
 
101505
101758
  \`\`\`typescript
101506
- task(
101759
+ // Fire Seraph in background \u2014 NEVER run_in_background=false from this session.
101760
+ const seraphTask = task(
101507
101761
  subagent_type="seraph",
101508
101762
  load_skills=[],
101763
+ run_in_background=true,
101509
101764
  prompt=\`Review this planning session before I generate the work plan:
101510
101765
 
101511
101766
  **User's Goal**: {summarize what user wants}
@@ -101525,11 +101780,18 @@ task(
101525
101780
  3. Potential scope creep areas to lock down
101526
101781
  4. Assumptions I'm making that need validation
101527
101782
  5. Missing acceptance criteria
101528
- 6. Edge cases not addressed\`,
101529
- run_in_background=false
101783
+ 6. Edge cases not addressed\`
101530
101784
  )
101785
+
101786
+ // Continue drafting/other work, then collect once when needed:
101787
+ const seraphReview = background_output(task_id=seraphTask.task_id)
101531
101788
  \`\`\`
101532
101789
 
101790
+ > **NO-BLOCKING-NESTING POLICY (MANDATORY \u2014 applies to EVERY Oracle delegation):**
101791
+ > - **Default to background mode**: always pass \`run_in_background=true\`. Oracle's own invocations default to background; blocking is never the default.
101792
+ > - **Never nest a blocking subagent call** (\`run_in_background=false\`) inside your own session. Oracle may itself be running inside a fixed poll budget (default 600s); a blocking nested call consumes that same budget and stalls at the timeout.
101793
+ > - **Use sequential top-level calls instead**: fire the delegation in background, return to your work, then collect the result with \`background_output(task_id=...)\`. If a result is required before proceeding, collect it once and continue \u2014 never block on a nested subagent.
101794
+
101533
101795
  If gate **does NOT pass**, skip Seraph and proceed directly to plan generation (note \`Seraph bypassed: complexity=X / no multi-component signal\` in summary).
101534
101796
 
101535
101797
  ## Post-Seraph: Auto-Generate Plan and Summarize
@@ -101875,7 +102137,7 @@ Parallel Speedup: ~40% faster than sequential
101875
102137
 
101876
102138
  | Wave | Tasks | Recommended Agents |
101877
102139
  |------|-------|-------------------|
101878
- | 1 | 1, 5 | task(category="...", load_skills=[...], run_in_background=false) |
102140
+ | 1 | 1, 5 | task(category="...", load_skills=[...], run_in_background=true) |
101879
102141
  | 2 | 2, 3, 6 | dispatch parallel after Wave 1 completes |
101880
102142
  | 3 | 4 | final integration task |
101881
102143
 
@@ -102891,14 +103153,6 @@ var document_reader = {
102891
103153
  enabled: true
102892
103154
  };
102893
103155
 
102894
- // src/mcp/grep-app.ts
102895
- var grep_app = {
102896
- type: "remote",
102897
- url: "https://mcp.grep.app",
102898
- enabled: true,
102899
- oauth: false
102900
- };
102901
-
102902
103156
  // src/mcp/mcp-validator.ts
102903
103157
  function validateWebsearchConfig(config4) {
102904
103158
  const provider = config4?.provider || "exa";
@@ -102947,8 +103201,16 @@ function createWebsearchConfig(config4) {
102947
103201
  }
102948
103202
  var websearch = createWebsearchConfig();
102949
103203
 
103204
+ // src/mcp/white-rabbit.ts
103205
+ var white_rabbit = {
103206
+ type: "remote",
103207
+ url: "https://mcp.grep.app",
103208
+ enabled: true,
103209
+ oauth: false
103210
+ };
103211
+
102950
103212
  // src/mcp/types.ts
102951
- var McpNameSchema = exports_external.enum(["websearch", "context7", "grep_app", "document_reader"]);
103213
+ var McpNameSchema = exports_external.enum(["websearch", "context7", "white_rabbit", "document_reader"]);
102952
103214
  var AnyMcpNameSchema = exports_external.string().min(1);
102953
103215
 
102954
103216
  // src/mcp/index.ts
@@ -102997,8 +103259,8 @@ function createBuiltinMcps(disabledMcps = [], config4, options = {}) {
102997
103259
  if (!disabledMcps.includes("context7")) {
102998
103260
  mcps.context7 = context7;
102999
103261
  }
103000
- if (!disabledMcps.includes("grep_app")) {
103001
- mcps.grep_app = grep_app;
103262
+ if (!disabledMcps.includes("white_rabbit")) {
103263
+ mcps.white_rabbit = white_rabbit;
103002
103264
  }
103003
103265
  if (!disabledMcps.includes("document_reader")) {
103004
103266
  if (commandAvailable("uvx")) {
@@ -103100,7 +103362,7 @@ function applyToolConfig(params) {
103100
103362
  const denyTaskTools = !isTaskSystem ? { "task_*": "deny", task: "deny" } : {};
103101
103363
  params.config.tools = {
103102
103364
  ...params.config.tools,
103103
- "grep_app_*": false,
103365
+ "white_rabbit_*": false,
103104
103366
  LspHover: false,
103105
103367
  LspCodeActions: false,
103106
103368
  LspCodeActionResolve: false,
@@ -103112,7 +103374,7 @@ function applyToolConfig(params) {
103112
103374
  const questionPermission = isCliRunMode ? "deny" : "allow";
103113
103375
  const operator = agentByKey(params.agentResult, "operator");
103114
103376
  if (operator) {
103115
- operator.permission = { ...operator.permission, "grep_app_*": "allow" };
103377
+ operator.permission = { ...operator.permission, "white_rabbit_*": "allow" };
103116
103378
  }
103117
103379
  const construct = agentByKey(params.agentResult, "construct");
103118
103380
  if (construct) {
@@ -103220,6 +103482,9 @@ function createManagers(args) {
103220
103482
  },
103221
103483
  enableParentSessionNotifications: backgroundNotificationHookEnabled
103222
103484
  });
103485
+ backgroundManager.restoreHandles().catch((error95) => {
103486
+ log("[index] Failed to restore background handles:", error95);
103487
+ });
103223
103488
  initTaskToastManager(ctx.client);
103224
103489
  const configHandler = createConfigHandler({
103225
103490
  ctx: { directory: ctx.directory, client: ctx.client },
@@ -103306,7 +103571,8 @@ function createToolRegistry(args) {
103306
103571
  availableSkills: skillContext.availableSkills,
103307
103572
  modelRequirements: pluginConfig.modelRequirements,
103308
103573
  complexityDowngrades: pluginConfig.complexityDowngrades,
103309
- tiers: pluginConfig.tiers,
103574
+ modelPresets: pluginConfig.model_presets,
103575
+ activePreset: pluginConfig.active_preset,
103310
103576
  agentOverrides: pluginConfig.agents,
103311
103577
  onSyncSessionCreated: async (event) => {
103312
103578
  log("[index] onSyncSessionCreated callback", {
@@ -103367,6 +103633,7 @@ function createToolRegistry(args) {
103367
103633
  ...createHandoffTools(ctx),
103368
103634
  ...createPdfExtractFiguresTool(),
103369
103635
  ...createDcpSwitchProfileTool({ pluginConfig }),
103636
+ ...createPresetTool({ pluginConfig, directory: ctx.directory }),
103370
103637
  ...backgroundTools,
103371
103638
  delegate_agent: delegateAgent,
103372
103639
  ...lookAt ? { look_at: lookAt } : {},
@@ -103393,6 +103660,10 @@ function createToolRegistry(args) {
103393
103660
  // src/create-tools.ts
103394
103661
  async function createTools(args) {
103395
103662
  const { ctx, pluginConfig, managers } = args;
103663
+ const pollTimeout = resolveTasksConfig(pluginConfig).pollTimeoutMs;
103664
+ if (pollTimeout !== undefined) {
103665
+ setPollTimeoutMs(pollTimeout);
103666
+ }
103396
103667
  const skillContext = await createSkillContext({
103397
103668
  directory: ctx.directory,
103398
103669
  pluginConfig
@@ -103494,10 +103765,8 @@ var AgentPermissionSchema = exports_external.object({
103494
103765
  });
103495
103766
 
103496
103767
  // src/config/schema/agent-overrides.ts
103497
- var TierNameSchema = exports_external.enum(["free", "fast", "standard", "premium", "frontier"]);
103498
103768
  var AgentOverrideConfigSchema = exports_external.object({
103499
103769
  model: exports_external.string().optional(),
103500
- tier: TierNameSchema.optional(),
103501
103770
  variant: exports_external.string().optional(),
103502
103771
  category: exports_external.string().optional(),
103503
103772
  skills: exports_external.array(exports_external.string()).optional(),
@@ -103588,7 +103857,6 @@ var BrowserAutomationConfigSchema = exports_external.object({
103588
103857
  var CategoryConfigSchema = exports_external.object({
103589
103858
  description: exports_external.string().optional(),
103590
103859
  model: exports_external.string().optional(),
103591
- tier: TierNameSchema.optional(),
103592
103860
  variant: exports_external.string().optional(),
103593
103861
  temperature: exports_external.number().min(0).max(2).optional(),
103594
103862
  top_p: exports_external.number().min(0).max(1).optional(),
@@ -103626,7 +103894,7 @@ var BuiltinCommandNameSchema = exports_external.enum([
103626
103894
  "refactor",
103627
103895
  "start-work",
103628
103896
  "stop-continuation",
103629
- "profile",
103897
+ "preset",
103630
103898
  "end-ultrawork",
103631
103899
  "handoff",
103632
103900
  "research",
@@ -103758,6 +104026,7 @@ var HookNameEnum = exports_external.enum([
103758
104026
  "preemptive-compaction",
103759
104027
  "rules-injector",
103760
104028
  "background-notification",
104029
+ "background-task-blocker",
103761
104030
  "auto-update-checker",
103762
104031
  "startup-toast",
103763
104032
  "keyword-detector",
@@ -103823,6 +104092,29 @@ var MatrixxSelfConfigSkillConfigSchema = exports_external.object({
103823
104092
  proactive: exports_external.boolean().default(false)
103824
104093
  });
103825
104094
 
104095
+ // src/config/schema/model-presets.ts
104096
+ var providerModelSchema = exports_external.string().refine((s) => {
104097
+ if (!s.includes("/"))
104098
+ return false;
104099
+ if (s.startsWith("/") || s.endsWith("/"))
104100
+ return false;
104101
+ if (s.includes("//"))
104102
+ return false;
104103
+ const parts = s.split("/");
104104
+ return parts.length >= 2 && parts.every((p) => p.length > 0);
104105
+ }, { message: "Must be <provider>/<model>" });
104106
+ var ModelPresetEntrySchema = exports_external.object({
104107
+ model: providerModelSchema,
104108
+ variant: exports_external.string().optional()
104109
+ });
104110
+ var ModelPresetSchema = exports_external.object({
104111
+ default_model: providerModelSchema.optional(),
104112
+ agents: exports_external.record(exports_external.string(), ModelPresetEntrySchema).optional(),
104113
+ categories: exports_external.record(exports_external.string(), ModelPresetEntrySchema).optional()
104114
+ });
104115
+ var ModelPresetsSchema = exports_external.record(exports_external.string(), ModelPresetSchema);
104116
+ var ActivePresetSchema = exports_external.string().min(1);
104117
+
103826
104118
  // src/config/schema/morpheus.ts
103827
104119
  var MorpheusTasksConfigSchema = exports_external.object({
103828
104120
  storage_path: exports_external.string().optional(),
@@ -103949,6 +104241,22 @@ var SkillsConfigSchema = exports_external.union([
103949
104241
  }).catchall(SkillEntrySchema)
103950
104242
  ]);
103951
104243
 
104244
+ // src/config/schema/task.ts
104245
+ var TaskConfigSchema = exports_external.object({
104246
+ pollTimeoutMs: exports_external.number().min(60000).optional()
104247
+ });
104248
+
104249
+ // src/config/schema/tasks.ts
104250
+ var TasksConfigSchema = exports_external.object({
104251
+ enabled: exports_external.boolean().optional().default(true),
104252
+ scope: exports_external.enum(["global", "project"]).default("project").optional().describe("Task storage scope: project \u2192 .matrixx/tasks per project (default), global \u2192 <opencode-config>/tasks/{listId}"),
104253
+ storage_path: exports_external.string().optional(),
104254
+ task_list_id: exports_external.string().optional(),
104255
+ stale_after_hours: exports_external.number().int().min(1).optional().describe("Pending tasks with no file activity for this many hours are considered stale (default: 24). Stale tasks are excluded from task-continuation directives."),
104256
+ session_scoped: exports_external.boolean().default(true).optional().describe("When true, the task-continuation-enforcer only considers tasks created by the current session (or its subagent sessions). When false, all project tasks are considered regardless of session origin."),
104257
+ pollTimeoutMs: exports_external.number().min(60000).optional()
104258
+ });
104259
+
103952
104260
  // src/config/schema/tdd-enforcer.ts
103953
104261
  var TddEnforcerConfigSchema = exports_external.object({
103954
104262
  enabled: exports_external.boolean().default(false)
@@ -103980,7 +104288,8 @@ var WebsearchConfigSchema = exports_external.object({
103980
104288
  var MatrixxConfigSchema = exports_external.object({
103981
104289
  $schema: exports_external.string().optional(),
103982
104290
  global_model: exports_external.string().optional(),
103983
- default_tier: TierNameSchema.optional(),
104291
+ model_presets: ModelPresetsSchema.optional(),
104292
+ active_preset: ActivePresetSchema.optional(),
103984
104293
  new_task_system_enabled: exports_external.boolean().optional(),
103985
104294
  default_run_agent: exports_external.string().optional(),
103986
104295
  disabled_mcps: exports_external.array(AnyMcpNameSchema).optional(),
@@ -104002,6 +104311,8 @@ var MatrixxConfigSchema = exports_external.object({
104002
104311
  background_task: BackgroundTaskConfigSchema.optional(),
104003
104312
  notification: NotificationConfigSchema.optional(),
104004
104313
  babysitting: BabysittingConfigSchema.optional(),
104314
+ tasks: TasksConfigSchema.optional(),
104315
+ task: TaskConfigSchema.optional(),
104005
104316
  tdd_enforcer: TddEnforcerConfigSchema.optional(),
104006
104317
  browser_automation_engine: BrowserAutomationConfigSchema.optional(),
104007
104318
  websearch: WebsearchConfigSchema.optional(),
@@ -104017,7 +104328,6 @@ var MatrixxConfigSchema = exports_external.object({
104017
104328
  rtk: RtkConfigSchema.optional(),
104018
104329
  evolution: EvolutionConfigSchema.optional(),
104019
104330
  _migrations: exports_external.array(exports_external.string()).optional(),
104020
- tiers: TiersSchema.optional(),
104021
104331
  modelRequirements: ModelRequirementsSchema.optional(),
104022
104332
  complexityDowngrades: ComplexityDowngradesSchema.optional()
104023
104333
  });
@@ -104025,11 +104335,8 @@ var MatrixxConfigSchema = exports_external.object({
104025
104335
  init_logger();
104026
104336
  var MIGRATION_KEY = "model-migration";
104027
104337
  function normalizeModelInput(value) {
104028
- if (value.startsWith("tier:")) {
104029
- return value;
104030
- }
104031
104338
  if (value.includes("/")) {
104032
- log(`[migration] prefixed model '${value}' is deprecated, use 'tier:<name>' or bare '<model>'`);
104339
+ log(`[migration] prefixed model '${value}' is deprecated, use bare '<model>'`);
104033
104340
  }
104034
104341
  return value;
104035
104342
  }
@@ -104041,26 +104348,6 @@ function migrateMatrixxConfig(raw) {
104041
104348
  if (typeof migrated.global_model === "string") {
104042
104349
  migrated = { ...migrated, global_model: normalizeModelInput(migrated.global_model) };
104043
104350
  }
104044
- if (migrated.tiers) {
104045
- const nextTiers = {};
104046
- for (const [tierName, spec] of Object.entries(migrated.tiers)) {
104047
- if (!spec || typeof spec !== "object") {
104048
- nextTiers[tierName] = spec;
104049
- continue;
104050
- }
104051
- const typed = spec;
104052
- if (!typed.fallback) {
104053
- nextTiers[tierName] = spec;
104054
- continue;
104055
- }
104056
- const nextFallback = typed.fallback.map((entry) => ({
104057
- ...entry,
104058
- model: normalizeModelInput(entry.model)
104059
- }));
104060
- nextTiers[tierName] = { ...spec, fallback: nextFallback };
104061
- }
104062
- migrated = { ...migrated, tiers: nextTiers };
104063
- }
104064
104351
  if (migrated.modelRequirements) {
104065
104352
  const nextMR = { ...migrated.modelRequirements };
104066
104353
  for (const scope of ["agents", "categories"]) {
@@ -104205,7 +104492,7 @@ function mergeConfigs(base, override) {
104205
104492
  });
104206
104493
  return merged;
104207
104494
  }
104208
- function resolveConfigPath(baseDir, configName) {
104495
+ function resolveConfigPath2(baseDir, configName) {
104209
104496
  const newBase = path13.join(baseDir, "matrixx");
104210
104497
  const newDetected = detectConfigFile(newBase);
104211
104498
  if (newDetected.format !== "none") {
@@ -104219,29 +104506,39 @@ function resolveConfigPath(baseDir, configName) {
104219
104506
  }
104220
104507
  return `${newBase}.json`;
104221
104508
  }
104509
+ function assertModelsResolvable(config4) {
104510
+ const globalModel = config4.global_model;
104511
+ const unresolved = [];
104512
+ for (const [name, entry] of Object.entries(config4.agents ?? {})) {
104513
+ if (entry && !entry.disable && !entry.model && !globalModel) {
104514
+ unresolved.push(`agent "${name}"`);
104515
+ }
104516
+ }
104517
+ for (const [name, entry] of Object.entries(config4.categories ?? {})) {
104518
+ if (entry && !entry.disable && !entry.model && !globalModel) {
104519
+ unresolved.push(`category "${name}"`);
104520
+ }
104521
+ }
104522
+ if (unresolved.length > 0) {
104523
+ throw new ConfigError(`Matrixx config error: no model resolved for ${unresolved.join(", ")}. ` + `Set an explicit "model" on the entry, define model_presets with an active_preset, ` + `or run "bunx opencode-matrixx setup" to generate a preset from your connected providers.`);
104524
+ }
104525
+ }
104222
104526
  async function loadPluginConfig(directory, ctx) {
104223
104527
  const configDir = getOpenCodeConfigDir({ binary: "opencode" });
104224
- const userConfigPath = resolveConfigPath(configDir, "matrixx");
104528
+ const userConfigPath = resolveConfigPath2(configDir, "matrixx");
104225
104529
  const projectDir = path13.join(directory, ".opencode");
104226
- const projectConfigPath = resolveConfigPath(projectDir, "matrixx");
104530
+ const projectConfigPath = resolveConfigPath2(projectDir, "matrixx");
104227
104531
  let config4 = loadConfigFromPath(userConfigPath, ctx) ?? {};
104228
104532
  const projectConfig = loadConfigFromPath(projectConfigPath, ctx);
104229
104533
  if (projectConfig) {
104230
104534
  config4 = mergeConfigs(config4, projectConfig);
104231
104535
  }
104232
104536
  config4 = migrateMatrixxConfig(config4);
104233
- const connectedProviders = readConnectedProvidersCache();
104234
- const availableModels = await fetchAvailableModels(undefined, { connectedProviders });
104235
- const tierCtx = {
104236
- availableModels: availableModels ?? new Set,
104237
- connectedProviders
104238
- };
104239
- config4 = resolveTiersInConfig(config4, tierCtx);
104240
- const resolvedDefaults = resolveTiersInCategoryRegistry(DEFAULT_CATEGORIES, tierCtx, config4);
104241
- config4 = {
104242
- ...config4,
104243
- categories: { ...resolvedDefaults, ...config4.categories ?? {} }
104244
- };
104537
+ config4 = applyActivePreset(config4, config4.active_preset);
104538
+ if (config4.active_preset && config4.model_presets?.[config4.active_preset]) {
104539
+ log("Active model preset applied", { active_preset: config4.active_preset });
104540
+ }
104541
+ assertModelsResolvable(config4);
104245
104542
  log("Final merged config", {
104246
104543
  agents: config4.agents,
104247
104544
  disabled_agents: config4.disabled_agents,
@@ -104461,19 +104758,19 @@ function createEventHandler2(args) {
104461
104758
  };
104462
104759
  const recentSyntheticIdles = new Map;
104463
104760
  const recentRealIdles = new Map;
104464
- const DEDUP_WINDOW_MS = 500;
104761
+ const DEDUP_WINDOW_MS2 = 500;
104465
104762
  return async (input) => {
104466
104763
  pruneRecentSyntheticIdles({
104467
104764
  recentSyntheticIdles,
104468
104765
  recentRealIdles,
104469
104766
  now: Date.now(),
104470
- dedupWindowMs: DEDUP_WINDOW_MS
104767
+ dedupWindowMs: DEDUP_WINDOW_MS2
104471
104768
  });
104472
104769
  if (input.event.type === "session.idle") {
104473
104770
  const sessionID = input.event.properties?.sessionID;
104474
104771
  if (sessionID) {
104475
104772
  const emittedAt = recentSyntheticIdles.get(sessionID);
104476
- if (emittedAt && Date.now() - emittedAt < DEDUP_WINDOW_MS) {
104773
+ if (emittedAt && Date.now() - emittedAt < DEDUP_WINDOW_MS2) {
104477
104774
  recentSyntheticIdles.delete(sessionID);
104478
104775
  return;
104479
104776
  }
@@ -104485,7 +104782,7 @@ function createEventHandler2(args) {
104485
104782
  if (syntheticIdle) {
104486
104783
  const sessionID = syntheticIdle.event.properties?.sessionID;
104487
104784
  const emittedAt = recentRealIdles.get(sessionID);
104488
- if (emittedAt && Date.now() - emittedAt < DEDUP_WINDOW_MS) {
104785
+ if (emittedAt && Date.now() - emittedAt < DEDUP_WINDOW_MS2) {
104489
104786
  recentRealIdles.delete(sessionID);
104490
104787
  return;
104491
104788
  }
@@ -104661,22 +104958,22 @@ function createToolExecuteAfterHandler2(args) {
104661
104958
 
104662
104959
  // src/features/assembly-state/manager.ts
104663
104960
  init_logger();
104664
- var HOOK_NAME19 = "assembly-state";
104961
+ var HOOK_NAME20 = "assembly-state";
104665
104962
  var disabledSessions = new Set;
104666
104963
  function isAssemblyDisabled(sessionID) {
104667
104964
  return disabledSessions.has(sessionID);
104668
104965
  }
104669
104966
  function disableAssembly(sessionID) {
104670
104967
  disabledSessions.add(sessionID);
104671
- log(`[${HOOK_NAME19}] Assembly disabled for session`, { sessionID });
104968
+ log(`[${HOOK_NAME20}] Assembly disabled for session`, { sessionID });
104672
104969
  disabledSessions.add(sessionID);
104673
- log(`[${HOOK_NAME19}] Consensus disabled for session`, { sessionID });
104970
+ log(`[${HOOK_NAME20}] Consensus disabled for session`, { sessionID });
104674
104971
  }
104675
104972
  function enableAssembly(sessionID) {
104676
104973
  disabledSessions.delete(sessionID);
104677
- log(`[${HOOK_NAME19}] Assembly enabled for session`, { sessionID });
104974
+ log(`[${HOOK_NAME20}] Assembly enabled for session`, { sessionID });
104678
104975
  disabledSessions.delete(sessionID);
104679
- log(`[${HOOK_NAME19}] Consensus enabled for session`, { sessionID });
104976
+ log(`[${HOOK_NAME20}] Consensus enabled for session`, { sessionID });
104680
104977
  }
104681
104978
  // src/plugin/session-agent-resolver.ts
104682
104979
  async function resolveSessionAgent(client2, sessionId, timeoutMs = 5000) {
@@ -104719,6 +105016,7 @@ function createToolExecuteBeforeHandler2(args) {
104719
105016
  const taskEditGuardHook = hooks2.taskEditGuard?.["tool.execute.before"];
104720
105017
  const tasksTodowriteDisablerHook = hooks2.tasksTodowriteDisabler?.["tool.execute.before"];
104721
105018
  const oracleMdOnlyHook = hooks2.oracleMdOnly?.["tool.execute.before"];
105019
+ const backgroundTaskBlockerHook = hooks2.backgroundTaskBlocker?.["tool.execute.before"];
104722
105020
  const contextModeEnforcerHook = hooks2.contextModeEnforcer?.["tool.execute.before"];
104723
105021
  const nonInteractiveEnvHook = hooks2.nonInteractiveEnv?.["tool.execute.before"];
104724
105022
  const bashFileReadGuardHook = hooks2.bashFileReadGuard?.["tool.execute.before"];
@@ -104739,7 +105037,8 @@ function createToolExecuteBeforeHandler2(args) {
104739
105037
  taskEditGuardHook?.(input, output),
104740
105038
  tasksTodowriteDisablerHook?.(input, output),
104741
105039
  oracleMdOnlyHook?.(input, output),
104742
- contextModeEnforcerHook?.(input, output)
105040
+ contextModeEnforcerHook?.(input, output),
105041
+ backgroundTaskBlockerHook?.(input, output)
104743
105042
  ]);
104744
105043
  let resolvePromise;
104745
105044
  if (input.tool === "task") {