opencode-matrixx 2.6.5 → 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 (33) 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.js +14 -6
  8. package/dist/config/schema/hooks.d.ts +1 -0
  9. package/dist/config/schema/matrixx-config.d.ts +1 -0
  10. package/dist/create-hooks.d.ts +1 -0
  11. package/dist/features/background-agent/handle-index.d.ts +63 -0
  12. package/dist/features/background-agent/handle-index.test.d.ts +1 -0
  13. package/dist/features/background-agent/index.d.ts +1 -0
  14. package/dist/features/background-agent/manager-handles.test.d.ts +1 -0
  15. package/dist/features/background-agent/manager.d.ts +12 -0
  16. package/dist/hooks/background-task-blocker/hook.d.ts +10 -0
  17. package/dist/hooks/background-task-blocker/index.d.ts +1 -0
  18. package/dist/hooks/index.d.ts +1 -0
  19. package/dist/index.js +278 -54
  20. package/dist/matrixx.schema.json +1 -0
  21. package/dist/plugin/hooks/create-core-hooks.d.ts +1 -0
  22. package/dist/plugin/hooks/create-tool-guard-hooks.d.ts +2 -1
  23. package/dist/shared/context-mode-prompt.d.ts +12 -0
  24. package/dist/shared/context-mode-prompt.test.d.ts +1 -0
  25. package/dist/shared/index.d.ts +1 -0
  26. package/dist/tools/background-task/constants.d.ts +0 -1
  27. package/dist/tools/background-task/index.d.ts +1 -1
  28. package/dist/tools/background-task/tools.d.ts +0 -1
  29. package/dist/tools/background-task/types.d.ts +0 -5
  30. package/dist/tools/delegate-task/sync-task.test.d.ts +1 -0
  31. package/dist/tools/delegate-task/timing.test.d.ts +1 -0
  32. package/package.json +1 -1
  33. package/dist/tools/background-task/create-background-task.d.ts +0 -3
package/dist/index.js CHANGED
@@ -44411,11 +44411,14 @@ var ModelFallbackEntrySchema = exports_external.object({
44411
44411
  variant: exports_external.string().optional()
44412
44412
  });
44413
44413
  var complexityDowngradeValueSchema = exports_external.string().refine((s) => {
44414
- if (s.includes("/")) {
44415
- const parts = s.split("/");
44416
- return parts.length === 2 && parts[0].length > 0 && parts[1].length > 0;
44417
- }
44418
- return false;
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);
44419
44422
  }, { message: "Must be <provider>/<model>" });
44420
44423
  var ComplexityDowngradesSchema = exports_external.record(exports_external.string(), exports_external.record(exports_external.string(), complexityDowngradeValueSchema));
44421
44424
  var AgentModelRequirementsSchema = exports_external.object({
@@ -48413,8 +48416,11 @@ function getPlanProgress(planPath) {
48413
48416
  const content = readFileSync10(planPath, "utf-8");
48414
48417
  const uncheckedMatches = content.match(/^[-*]\s*\[\s*\]/gm) || [];
48415
48418
  const checkedMatches = content.match(/^[-*]\s*\[[xX]\]/gm) || [];
48416
- const total = uncheckedMatches.length + checkedMatches.length;
48417
- 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;
48418
48424
  return {
48419
48425
  total,
48420
48426
  completed,
@@ -52632,6 +52638,16 @@ function createBackgroundNotificationHook(manager) {
52632
52638
  event: eventHandler
52633
52639
  };
52634
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
+ }
52635
52651
  // src/hooks/bash-file-read-guard.ts
52636
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.";
52637
52653
  var FILE_READ_PATTERNS = [
@@ -88724,8 +88740,6 @@ function createBackgroundOutput(manager, client2) {
88724
88740
  }
88725
88741
  });
88726
88742
  }
88727
- // src/tools/background-task/create-background-task.ts
88728
- init_logger();
88729
88743
  // src/tools/assembly/constants.ts
88730
88744
  var DEFAULT_VOTERS = 3;
88731
88745
  var DEFAULT_ROUNDS = 2;
@@ -91354,7 +91368,7 @@ async function pollSyncSession(ctx, client2, input) {
91354
91368
  let idleStallCount = 0;
91355
91369
  let lastMsgCount = 0;
91356
91370
  let stableIdlePolls = 0;
91357
- log("[task] Starting poll loop", { sessionID: input.sessionID, agentToUse: input.agentToUse });
91371
+ log("[task] Starting poll loop", { sessionID: input.sessionID, agentToUse: input.agentToUse, maxPollTimeMs });
91358
91372
  while (Date.now() - pollStart < maxPollTimeMs) {
91359
91373
  if (ctx.abort?.aborted) {
91360
91374
  log("[task] Aborted by user", { sessionID: input.sessionID });
@@ -91732,6 +91746,7 @@ async function executeSyncTask(args, ctx, executorCtx, parentContext, agentToUse
91732
91746
  const toastManager = getTaskToastManager();
91733
91747
  let taskId;
91734
91748
  let syncSessionID;
91749
+ let pollTimedOut = false;
91735
91750
  try {
91736
91751
  const createSessionResult = await deps.createSyncSession(client2, {
91737
91752
  parentSessionID: parentContext.sessionID,
@@ -91814,6 +91829,7 @@ async function executeSyncTask(args, ctx, executorCtx, parentContext, agentToUse
91814
91829
  }
91815
91830
  });
91816
91831
  if (pollError) {
91832
+ pollTimedOut = pollError.startsWith("Poll timeout reached");
91817
91833
  return pollError;
91818
91834
  }
91819
91835
  const result = await deps.fetchSyncResult(client2, sessionID, undefined, {
@@ -91851,9 +91867,11 @@ session_id: ${sessionID}
91851
91867
  } finally {
91852
91868
  if (syncSessionID) {
91853
91869
  unregisterSubagentSession(syncSessionID);
91854
- client2.session.abort({ path: { id: syncSessionID } }).catch((err) => {
91855
- log("[delegate-task] Sync session abort failed:", err);
91856
- });
91870
+ if (!pollTimedOut) {
91871
+ client2.session.abort({ path: { id: syncSessionID } }).catch((err) => {
91872
+ log("[delegate-task] Sync session abort failed:", err);
91873
+ });
91874
+ }
91857
91875
  }
91858
91876
  }
91859
91877
  }
@@ -93096,6 +93114,7 @@ function createToolGuardHooks(args) {
93096
93114
  const { ctx, pluginConfig, isHookEnabled, safeHookEnabled } = args;
93097
93115
  const evolutionEnabled = pluginConfig.evolution?.enabled === true;
93098
93116
  const safeHook = (hookName, factory) => safeCreateHook(hookName, factory, { enabled: safeHookEnabled });
93117
+ const backgroundTaskBlocker = isHookEnabled("background-task-blocker") ? safeHook("background-task-blocker", () => createBackgroundTaskBlockerHook()) : null;
93099
93118
  const commentChecker = isHookEnabled("comment-checker") ? safeHook("comment-checker", () => createCommentCheckerHooks(pluginConfig.comment_checker)) : null;
93100
93119
  const toolOutputTruncator = isHookEnabled("tool-output-truncator") ? safeHook("tool-output-truncator", () => createToolOutputTruncatorHook(ctx, { experimental: pluginConfig.experimental })) : null;
93101
93120
  let directoryAgentsInjector = null;
@@ -93129,6 +93148,7 @@ function createToolGuardHooks(args) {
93129
93148
  const taskEditGuard = isHookEnabled("task-edit-guard") ? safeHook("task-edit-guard", () => createTaskEditGuardHook(ctx)) : null;
93130
93149
  const evolutionWatcher = evolutionEnabled && isHookEnabled("evolution-watcher") ? safeHook("evolution-watcher", () => createEvolutionWatcherHook(ctx, pluginConfig.evolution)) : null;
93131
93150
  return {
93151
+ backgroundTaskBlocker,
93132
93152
  commentChecker,
93133
93153
  toolOutputTruncator,
93134
93154
  directoryAgentsInjector,
@@ -93488,6 +93508,10 @@ class ConcurrencyManager {
93488
93508
  return this.queues.get(model)?.length ?? 0;
93489
93509
  }
93490
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
+
93491
93515
  // src/features/background-agent/constants.ts
93492
93516
  var TASK_TTL_MS = 30 * 60 * 1000;
93493
93517
  var MIN_STABILITY_TIME_MS2 = 10 * 1000;
@@ -93502,6 +93526,126 @@ var POLLING_INTERVAL_MS = 3000;
93502
93526
  var PRUNE_THROTTLE_MS = 30000;
93503
93527
  var TASK_CLEANUP_DELAY_MS = 10 * 60 * 1000;
93504
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
+ }
93505
93649
  // src/features/background-agent/error-helpers.ts
93506
93650
  function getErrorText(error95) {
93507
93651
  if (!error95)
@@ -93604,19 +93748,19 @@ function detectRepetitiveToolUse(window2) {
93604
93748
  }
93605
93749
 
93606
93750
  // src/features/background-agent/message-dir.ts
93607
- import { existsSync as existsSync73, readdirSync as readdirSync25, readFileSync as readFileSync43 } from "fs";
93608
- import { join as join77 } from "path";
93751
+ import { existsSync as existsSync74, readdirSync as readdirSync26, readFileSync as readFileSync44 } from "fs";
93752
+ import { join as join78 } from "path";
93609
93753
  var MESSAGE_DIR_CACHE_CAP = 1000;
93610
93754
  var messageDirCache = new Map;
93611
93755
  function computeMessageDir(sessionID) {
93612
- if (!existsSync73(MESSAGE_STORAGE))
93756
+ if (!existsSync74(MESSAGE_STORAGE))
93613
93757
  return null;
93614
- const directPath = join77(MESSAGE_STORAGE, sessionID);
93615
- if (existsSync73(directPath))
93758
+ const directPath = join78(MESSAGE_STORAGE, sessionID);
93759
+ if (existsSync74(directPath))
93616
93760
  return directPath;
93617
- for (const dir of readdirSync25(MESSAGE_STORAGE)) {
93618
- const sessionPath = join77(MESSAGE_STORAGE, dir, sessionID);
93619
- if (existsSync73(sessionPath))
93761
+ for (const dir of readdirSync26(MESSAGE_STORAGE)) {
93762
+ const sessionPath = join78(MESSAGE_STORAGE, dir, sessionID);
93763
+ if (existsSync74(sessionPath))
93620
93764
  return sessionPath;
93621
93765
  }
93622
93766
  return null;
@@ -93650,11 +93794,11 @@ function hasPartialAgentOrModel(message) {
93650
93794
  }
93651
93795
  function findNearestMessageExcludingCompaction(messageDir) {
93652
93796
  try {
93653
- const files = readdirSync25(messageDir).filter((name) => name.endsWith(".json")).sort().reverse();
93797
+ const files = readdirSync26(messageDir).filter((name) => name.endsWith(".json")).sort().reverse();
93654
93798
  const parsedByFile = new Map;
93655
93799
  for (const file3 of files) {
93656
93800
  try {
93657
- const content = readFileSync43(join77(messageDir, file3), "utf-8");
93801
+ const content = readFileSync44(join78(messageDir, file3), "utf-8");
93658
93802
  const parsed = JSON.parse(content);
93659
93803
  parsedByFile.set(file3, parsed);
93660
93804
  if (hasFullAgentAndModel(parsed)) {
@@ -93802,6 +93946,25 @@ class TaskHistory {
93802
93946
  }
93803
93947
 
93804
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
+
93805
93968
  class BackgroundManager {
93806
93969
  static cleanupManagers = new Set;
93807
93970
  static cleanupRegistered = false;
@@ -93842,6 +94005,28 @@ class BackgroundManager {
93842
94005
  this.enableParentSessionNotifications = options?.enableParentSessionNotifications ?? true;
93843
94006
  this.registerProcessCleanup();
93844
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
+ }
93845
94030
  async launch(input) {
93846
94031
  log("[background-agent] launch() called with:", {
93847
94032
  agent: input.agent,
@@ -93993,6 +94178,7 @@ class BackgroundManager {
93993
94178
  };
93994
94179
  task.concurrencyKey = concurrencyKey;
93995
94180
  task.concurrencyGroup = concurrencyKey;
94181
+ this.persistHandle(task);
93996
94182
  this.taskHistory.record(input.parentSessionID, { id: task.id, sessionID, agent: input.agent, description: input.description, status: "running", category: input.category, startedAt: task.startedAt });
93997
94183
  this.startPolling();
93998
94184
  log("[background-agent] Launching task:", { taskId: task.id, sessionID, agent: input.agent });
@@ -94044,6 +94230,7 @@ class BackgroundManager {
94044
94230
  existingTask.error = errorMessage;
94045
94231
  }
94046
94232
  existingTask.completedAt = new Date;
94233
+ this.persistHandle(existingTask);
94047
94234
  if (existingTask.concurrencyKey) {
94048
94235
  this.concurrencyManager.release(existingTask.concurrencyKey);
94049
94236
  existingTask.concurrencyKey = undefined;
@@ -94146,6 +94333,7 @@ class BackgroundManager {
94146
94333
  concurrencyGroup
94147
94334
  };
94148
94335
  this.tasks.set(task.id, task);
94336
+ this.persistHandle(task);
94149
94337
  registerSubagentSession(input.sessionID, input.parentSessionID);
94150
94338
  this.startPolling();
94151
94339
  this.taskHistory.record(input.parentSessionID, { id: task.id, sessionID: input.sessionID, agent: input.agent || "task", description: input.description, status: "running", startedAt: task.startedAt });
@@ -94192,6 +94380,7 @@ class BackgroundManager {
94192
94380
  existingTask.parentTools = input.parentTools;
94193
94381
  }
94194
94382
  existingTask.startedAt = new Date;
94383
+ this.persistHandle(existingTask);
94195
94384
  existingTask.progress = {
94196
94385
  toolCalls: existingTask.progress?.toolCalls ?? 0,
94197
94386
  lastUpdate: new Date
@@ -94247,6 +94436,7 @@ class BackgroundManager {
94247
94436
  const errorMessage = error95 instanceof Error ? error95.message : String(error95);
94248
94437
  existingTask.error = errorMessage;
94249
94438
  existingTask.completedAt = new Date;
94439
+ this.persistHandle(existingTask);
94250
94440
  if (existingTask.concurrencyKey) {
94251
94441
  this.concurrencyManager.release(existingTask.concurrencyKey);
94252
94442
  existingTask.concurrencyKey = undefined;
@@ -94411,6 +94601,7 @@ class BackgroundManager {
94411
94601
  task.status = "error";
94412
94602
  task.error = errorMessage ?? "Session error";
94413
94603
  task.completedAt = new Date;
94604
+ this.persistHandle(task);
94414
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 });
94415
94606
  if (task.concurrencyKey) {
94416
94607
  this.concurrencyManager.release(task.concurrencyKey);
@@ -94579,6 +94770,7 @@ class BackgroundManager {
94579
94770
  }
94580
94771
  task.status = "cancelled";
94581
94772
  task.completedAt = new Date;
94773
+ this.persistHandle(task);
94582
94774
  if (reason) {
94583
94775
  task.error = reason;
94584
94776
  }
@@ -94701,6 +94893,7 @@ class BackgroundManager {
94701
94893
  }
94702
94894
  task.status = "completed";
94703
94895
  task.completedAt = new Date;
94896
+ this.persistHandle(task);
94704
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 });
94705
94898
  if (task.concurrencyKey) {
94706
94899
  this.concurrencyManager.release(task.concurrencyKey);
@@ -94833,6 +95026,7 @@ class BackgroundManager {
94833
95026
  task.status = "error";
94834
95027
  task.error = errorMessage;
94835
95028
  task.completedAt = new Date;
95029
+ this.persistHandle(task);
94836
95030
  if (task.concurrencyKey) {
94837
95031
  this.concurrencyManager.release(task.concurrencyKey);
94838
95032
  task.concurrencyKey = undefined;
@@ -94917,6 +95111,7 @@ class BackgroundManager {
94917
95111
  task.status = "cancelled";
94918
95112
  task.error = `Stale timeout (no activity for ${staleMinutes2}min since start)`;
94919
95113
  task.completedAt = new Date;
95114
+ this.persistHandle(task);
94920
95115
  if (task.concurrencyKey) {
94921
95116
  this.concurrencyManager.release(task.concurrencyKey);
94922
95117
  task.concurrencyKey = undefined;
@@ -94945,6 +95140,7 @@ class BackgroundManager {
94945
95140
  task.status = "cancelled";
94946
95141
  task.error = `Stale timeout (no activity for ${staleMinutes}min)`;
94947
95142
  task.completedAt = new Date;
95143
+ this.persistHandle(task);
94948
95144
  if (task.concurrencyKey) {
94949
95145
  this.concurrencyManager.release(task.concurrencyKey);
94950
95146
  task.concurrencyKey = undefined;
@@ -95836,9 +96032,9 @@ class TmuxSessionManager {
95836
96032
  var SESSION_TIMEOUT_MS3 = 10 * 60 * 1000;
95837
96033
  var MIN_STABILITY_TIME_MS4 = 10 * 1000;
95838
96034
  // src/agents/dynamic-agent-prompt-builder.ts
95839
- import { existsSync as existsSync74, readFileSync as readFileSync44 } from "fs";
96035
+ import { existsSync as existsSync75, readFileSync as readFileSync45 } from "fs";
95840
96036
  import { homedir as homedir11 } from "os";
95841
- import { join as join78 } from "path";
96037
+ import { join as join79 } from "path";
95842
96038
  init_logger();
95843
96039
  function categorizeTools(toolNames) {
95844
96040
  return toolNames.map((name) => {
@@ -96200,24 +96396,24 @@ function exploreCtxPart(hasGrepGlob) {
96200
96396
  function resolveContextModeDisciplinePath() {
96201
96397
  try {
96202
96398
  const resolved = __require.resolve("context-mode/configs/opencode/AGENTS.md");
96203
- if (existsSync74(resolved))
96399
+ if (existsSync75(resolved))
96204
96400
  return resolved;
96205
96401
  } catch {}
96206
96402
  try {
96207
96403
  const cacheDir = getOpenCodeCacheDir();
96208
- const p = join78(cacheDir, "packages/context-mode@latest/node_modules/context-mode/configs/opencode/AGENTS.md");
96209
- if (existsSync74(p))
96404
+ const p = join79(cacheDir, "packages/context-mode@latest/node_modules/context-mode/configs/opencode/AGENTS.md");
96405
+ if (existsSync75(p))
96210
96406
  return p;
96211
96407
  } catch {}
96212
96408
  try {
96213
- const base = process.env.XDG_CACHE_HOME ?? join78(homedir11(), ".cache");
96214
- const p = join78(base, "opencode/packages/context-mode@latest/node_modules/context-mode/configs/opencode/AGENTS.md");
96215
- if (existsSync74(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))
96216
96412
  return p;
96217
96413
  } catch {}
96218
96414
  try {
96219
- const p2 = join78(homedir11(), ".cache/opencode/packages/context-mode@latest/node_modules/context-mode/configs/opencode/AGENTS.md");
96220
- if (existsSync74(p2))
96415
+ const p2 = join79(homedir11(), ".cache/opencode/packages/context-mode@latest/node_modules/context-mode/configs/opencode/AGENTS.md");
96416
+ if (existsSync75(p2))
96221
96417
  return p2;
96222
96418
  } catch {}
96223
96419
  return null;
@@ -96235,11 +96431,11 @@ function loadContextModeDiscipline(hasGrepGlob = true) {
96235
96431
  const p = resolveContextModeDisciplinePath();
96236
96432
  if (!p)
96237
96433
  throw new Error("discipline file not found");
96238
- const raw = readFileSync44(p, "utf8");
96434
+ const raw = readFileSync45(p, "utf8");
96239
96435
  let version4 = "1.0.169";
96240
96436
  try {
96241
96437
  const pkgResolved = __require.resolve("context-mode/package.json");
96242
- const pkgRaw = readFileSync44(pkgResolved, "utf8");
96438
+ const pkgRaw = readFileSync45(pkgResolved, "utf8");
96243
96439
  const pkg = JSON.parse(pkgRaw);
96244
96440
  if (pkg.version)
96245
96441
  version4 = pkg.version;
@@ -96286,11 +96482,11 @@ function loadCompactContextDiscipline(hasGrepGlob = true) {
96286
96482
  const p = resolveContextModeDisciplinePath();
96287
96483
  if (!p)
96288
96484
  throw new Error("discipline file not found");
96289
- const raw = readFileSync44(p, "utf8");
96485
+ const raw = readFileSync45(p, "utf8");
96290
96486
  let version4 = "1.0.169";
96291
96487
  try {
96292
96488
  const pkgResolved = __require.resolve("context-mode/package.json");
96293
- const pkgRaw = readFileSync44(pkgResolved, "utf8");
96489
+ const pkgRaw = readFileSync45(pkgResolved, "utf8");
96294
96490
  const pkg = JSON.parse(pkgRaw);
96295
96491
  if (pkg.version)
96296
96492
  version4 = pkg.version;
@@ -97359,7 +97555,7 @@ function createBddContractAgent(model) {
97359
97555
  createBddContractAgent.mode = MODE2;
97360
97556
 
97361
97557
  // src/agents/builtin-agents/resolve-file-uri.ts
97362
- import { existsSync as existsSync75, readFileSync as readFileSync45 } from "fs";
97558
+ import { existsSync as existsSync76, readFileSync as readFileSync46 } from "fs";
97363
97559
  import { homedir as homedir12 } from "os";
97364
97560
  import { isAbsolute as isAbsolute6, resolve as resolve10 } from "path";
97365
97561
  function resolvePromptAppend(promptAppend, configDir) {
@@ -97374,11 +97570,11 @@ function resolvePromptAppend(promptAppend, configDir) {
97374
97570
  } catch {
97375
97571
  return `[WARNING: Malformed file URI (invalid percent-encoding): ${promptAppend}]`;
97376
97572
  }
97377
- if (!existsSync75(filePath)) {
97573
+ if (!existsSync76(filePath)) {
97378
97574
  return `[WARNING: Could not resolve file URI: ${promptAppend}]`;
97379
97575
  }
97380
97576
  try {
97381
- return readFileSync45(filePath, "utf8");
97577
+ return readFileSync46(filePath, "utf8");
97382
97578
  } catch {
97383
97579
  return `[WARNING: Could not read file: ${promptAppend}]`;
97384
97580
  }
@@ -99394,8 +99590,8 @@ Step 2: Get commit SHA for permalinks
99394
99590
  cd \${TMPDIR:-/tmp}/repo-name && git rev-parse HEAD
99395
99591
 
99396
99592
  Step 3: Find the implementation
99397
- - grep/ast_grep_search for function/class
99398
- - 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)
99399
99595
  - git blame for context if needed
99400
99596
 
99401
99597
  Step 4: Construct permalink
@@ -100544,8 +100740,8 @@ Your response has **FAILED** if:
100544
100740
  Use the right tool for the job:
100545
100741
  - **Semantic search** (definitions, references): LSP tools
100546
100742
  - **Structural patterns** (function shapes, class structures): ast_grep_search
100547
- - **Text patterns** (strings, comments, logs): grep (if available; otherwise ctx_search / ast_grep_search)
100548
- - **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.
100549
100745
  - **History/evolution** (when added, who changed): git commands
100550
100746
 
100551
100747
  Flood with parallel calls. Cross-validate findings across multiple tools.`
@@ -100743,6 +100939,13 @@ This will:
100743
100939
  5. **Seraph Before Plan** - Always catch gaps before committing to plan
100744
100940
  6. **Choice-Based Handoff** - Present "Start Work" vs "High Accuracy Review" choice after plan
100745
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=...)\`.
100746
100949
 
100747
100950
  ---
100748
100951
 
@@ -100776,12 +100979,14 @@ var ORACLE_HIGH_ACCURACY_MODE = `# PHASE 3: PLAN GENERATION
100776
100979
  \`\`\`typescript
100777
100980
  // After generating initial plan
100778
100981
  while (true) {
100779
- const result = task(
100982
+ // Fire Smith in background (never a blocking nested call), then collect its verdict.
100983
+ const smithTask = task(
100780
100984
  subagent_type="smith",
100781
100985
  load_skills=[],
100782
100986
  prompt=".matrixx/plans/{name}.md",
100783
- run_in_background=false
100987
+ run_in_background=true
100784
100988
  )
100989
+ const result = background_output(task_id=smithTask.task_id)
100785
100990
 
100786
100991
  if (result.verdict === "OKAY") {
100787
100992
  break // Plan approved - exit loop
@@ -101386,7 +101591,7 @@ task(subagent_type="operator", load_skills=[], prompt="I'm designing architectur
101386
101591
 
101387
101592
  **Oracle Consultation** (recommend when stakes are high):
101388
101593
  \`\`\`typescript
101389
- 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)
101390
101595
  \`\`\`
101391
101596
 
101392
101597
  **Interview Focus:**
@@ -101548,12 +101753,14 @@ todoWrite([
101548
101753
 
101549
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).
101550
101755
 
101551
- If gate **passes**, summon Seraph:
101756
+ If gate **passes**, summon Seraph in **background mode** (never blocking \u2014 see policy below):
101552
101757
 
101553
101758
  \`\`\`typescript
101554
- task(
101759
+ // Fire Seraph in background \u2014 NEVER run_in_background=false from this session.
101760
+ const seraphTask = task(
101555
101761
  subagent_type="seraph",
101556
101762
  load_skills=[],
101763
+ run_in_background=true,
101557
101764
  prompt=\`Review this planning session before I generate the work plan:
101558
101765
 
101559
101766
  **User's Goal**: {summarize what user wants}
@@ -101573,11 +101780,18 @@ task(
101573
101780
  3. Potential scope creep areas to lock down
101574
101781
  4. Assumptions I'm making that need validation
101575
101782
  5. Missing acceptance criteria
101576
- 6. Edge cases not addressed\`,
101577
- run_in_background=false
101783
+ 6. Edge cases not addressed\`
101578
101784
  )
101785
+
101786
+ // Continue drafting/other work, then collect once when needed:
101787
+ const seraphReview = background_output(task_id=seraphTask.task_id)
101579
101788
  \`\`\`
101580
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
+
101581
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).
101582
101796
 
101583
101797
  ## Post-Seraph: Auto-Generate Plan and Summarize
@@ -101923,7 +102137,7 @@ Parallel Speedup: ~40% faster than sequential
101923
102137
 
101924
102138
  | Wave | Tasks | Recommended Agents |
101925
102139
  |------|-------|-------------------|
101926
- | 1 | 1, 5 | task(category="...", load_skills=[...], run_in_background=false) |
102140
+ | 1 | 1, 5 | task(category="...", load_skills=[...], run_in_background=true) |
101927
102141
  | 2 | 2, 3, 6 | dispatch parallel after Wave 1 completes |
101928
102142
  | 3 | 4 | final integration task |
101929
102143
 
@@ -103268,6 +103482,9 @@ function createManagers(args) {
103268
103482
  },
103269
103483
  enableParentSessionNotifications: backgroundNotificationHookEnabled
103270
103484
  });
103485
+ backgroundManager.restoreHandles().catch((error95) => {
103486
+ log("[index] Failed to restore background handles:", error95);
103487
+ });
103271
103488
  initTaskToastManager(ctx.client);
103272
103489
  const configHandler = createConfigHandler({
103273
103490
  ctx: { directory: ctx.directory, client: ctx.client },
@@ -103809,6 +104026,7 @@ var HookNameEnum = exports_external.enum([
103809
104026
  "preemptive-compaction",
103810
104027
  "rules-injector",
103811
104028
  "background-notification",
104029
+ "background-task-blocker",
103812
104030
  "auto-update-checker",
103813
104031
  "startup-toast",
103814
104032
  "keyword-detector",
@@ -103878,8 +104096,12 @@ var MatrixxSelfConfigSkillConfigSchema = exports_external.object({
103878
104096
  var providerModelSchema = exports_external.string().refine((s) => {
103879
104097
  if (!s.includes("/"))
103880
104098
  return false;
104099
+ if (s.startsWith("/") || s.endsWith("/"))
104100
+ return false;
104101
+ if (s.includes("//"))
104102
+ return false;
103881
104103
  const parts = s.split("/");
103882
- return parts.length === 2 && parts[0].length > 0 && parts[1].length > 0;
104104
+ return parts.length >= 2 && parts.every((p) => p.length > 0);
103883
104105
  }, { message: "Must be <provider>/<model>" });
103884
104106
  var ModelPresetEntrySchema = exports_external.object({
103885
104107
  model: providerModelSchema,
@@ -104794,6 +105016,7 @@ function createToolExecuteBeforeHandler2(args) {
104794
105016
  const taskEditGuardHook = hooks2.taskEditGuard?.["tool.execute.before"];
104795
105017
  const tasksTodowriteDisablerHook = hooks2.tasksTodowriteDisabler?.["tool.execute.before"];
104796
105018
  const oracleMdOnlyHook = hooks2.oracleMdOnly?.["tool.execute.before"];
105019
+ const backgroundTaskBlockerHook = hooks2.backgroundTaskBlocker?.["tool.execute.before"];
104797
105020
  const contextModeEnforcerHook = hooks2.contextModeEnforcer?.["tool.execute.before"];
104798
105021
  const nonInteractiveEnvHook = hooks2.nonInteractiveEnv?.["tool.execute.before"];
104799
105022
  const bashFileReadGuardHook = hooks2.bashFileReadGuard?.["tool.execute.before"];
@@ -104814,7 +105037,8 @@ function createToolExecuteBeforeHandler2(args) {
104814
105037
  taskEditGuardHook?.(input, output),
104815
105038
  tasksTodowriteDisablerHook?.(input, output),
104816
105039
  oracleMdOnlyHook?.(input, output),
104817
- contextModeEnforcerHook?.(input, output)
105040
+ contextModeEnforcerHook?.(input, output),
105041
+ backgroundTaskBlockerHook?.(input, output)
104818
105042
  ]);
104819
105043
  let resolvePromise;
104820
105044
  if (input.tool === "task") {