oh-my-opencode 3.3.2 → 3.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. package/dist/agents/atlas/default.d.ts +1 -1
  2. package/dist/agents/atlas/gpt.d.ts +1 -1
  3. package/dist/agents/dynamic-agent-prompt-builder.d.ts +2 -2
  4. package/dist/agents/prometheus/high-accuracy-mode.d.ts +1 -1
  5. package/dist/agents/prometheus/index.d.ts +1 -1
  6. package/dist/agents/prometheus/interview-mode.d.ts +1 -1
  7. package/dist/agents/prometheus/plan-generation.d.ts +1 -1
  8. package/dist/agents/utils.d.ts +1 -1
  9. package/dist/cli/index.js +51 -34
  10. package/dist/features/background-agent/manager.d.ts +2 -0
  11. package/dist/features/background-agent/spawner/background-session-creator.d.ts +10 -0
  12. package/dist/features/background-agent/spawner/concurrency-key-from-launch-input.d.ts +2 -0
  13. package/dist/features/background-agent/spawner/parent-directory-resolver.d.ts +6 -0
  14. package/dist/features/background-agent/spawner/tmux-callback-invoker.d.ts +8 -0
  15. package/dist/features/builtin-commands/templates/handoff.d.ts +1 -0
  16. package/dist/features/builtin-commands/types.d.ts +1 -1
  17. package/dist/features/claude-tasks/index.d.ts +1 -0
  18. package/dist/features/claude-tasks/session-storage.d.ts +9 -0
  19. package/dist/features/tmux-subagent/manager-cleanup.d.ts +12 -0
  20. package/dist/features/tmux-subagent/manager.d.ts +2 -4
  21. package/dist/features/tmux-subagent/polling-manager.d.ts +12 -0
  22. package/dist/features/tmux-subagent/session-cleaner.d.ts +23 -0
  23. package/dist/features/tmux-subagent/session-spawner.d.ts +34 -0
  24. package/dist/hooks/interactive-bash-session/hook.d.ts +23 -0
  25. package/dist/hooks/interactive-bash-session/index.d.ts +4 -23
  26. package/dist/hooks/interactive-bash-session/parser.d.ts +26 -0
  27. package/dist/hooks/interactive-bash-session/state-manager.d.ts +4 -0
  28. package/dist/hooks/keyword-detector/ultrawork/default.d.ts +1 -1
  29. package/dist/hooks/keyword-detector/ultrawork/gpt5.2.d.ts +1 -1
  30. package/dist/hooks/keyword-detector/ultrawork/planner.d.ts +1 -1
  31. package/dist/hooks/session-recovery/index.d.ts +1 -1
  32. package/dist/index.js +1200 -690
  33. package/dist/plugin-handlers/plan-model-inheritance.d.ts +1 -0
  34. package/dist/shared/git-worktree/collect-git-diff-stats.d.ts +2 -0
  35. package/dist/shared/git-worktree/format-file-changes.d.ts +2 -0
  36. package/dist/shared/git-worktree/index.d.ts +5 -0
  37. package/dist/shared/git-worktree/parse-diff-numstat.d.ts +2 -0
  38. package/dist/shared/git-worktree/parse-status-porcelain.d.ts +2 -0
  39. package/dist/shared/git-worktree/types.d.ts +7 -0
  40. package/dist/shared/index.d.ts +1 -0
  41. package/dist/shared/model-resolution-pipeline.d.ts +1 -0
  42. package/dist/tools/background-task/index.d.ts +1 -2
  43. package/dist/tools/background-task/modules/background-cancel.d.ts +4 -0
  44. package/dist/tools/background-task/modules/background-output.d.ts +3 -0
  45. package/dist/tools/background-task/modules/background-task.d.ts +3 -0
  46. package/dist/tools/background-task/modules/formatters.d.ts +11 -0
  47. package/dist/tools/background-task/modules/message-processing.d.ts +59 -0
  48. package/dist/tools/background-task/modules/utils.d.ts +15 -0
  49. package/dist/tools/background-task/tools.d.ts +7 -47
  50. package/dist/tools/background-task/types.d.ts +70 -0
  51. package/dist/tools/call-omo-agent/background-executor.d.ts +12 -0
  52. package/dist/tools/call-omo-agent/completion-poller.d.ts +11 -0
  53. package/dist/tools/call-omo-agent/message-dir.d.ts +1 -0
  54. package/dist/tools/call-omo-agent/message-processor.d.ts +2 -0
  55. package/dist/tools/call-omo-agent/session-creator.d.ts +15 -0
  56. package/dist/tools/call-omo-agent/sync-executor.d.ts +12 -0
  57. package/dist/tools/call-omo-agent/types.d.ts +10 -0
  58. package/dist/tools/delegate-task/constants.d.ts +11 -4
  59. package/dist/tools/delegate-task/executor.d.ts +5 -1
  60. package/dist/tools/delegate-task/skill-resolver.d.ts +9 -0
  61. package/dist/tools/delegate-task/types.d.ts +15 -1
  62. package/dist/tools/lsp/client.d.ts +1 -0
  63. package/package.json +8 -8
package/dist/index.js CHANGED
@@ -4522,29 +4522,30 @@ var init_agent_category = () => {};
4522
4522
  // src/shared/migration/config-migration.ts
4523
4523
  import * as fs5 from "fs";
4524
4524
  function migrateConfigFile(configPath, rawConfig) {
4525
+ const copy = structuredClone(rawConfig);
4525
4526
  let needsWrite = false;
4526
- const existingMigrations = Array.isArray(rawConfig._migrations) ? new Set(rawConfig._migrations) : new Set;
4527
+ const existingMigrations = Array.isArray(copy._migrations) ? new Set(copy._migrations) : new Set;
4527
4528
  const allNewMigrations = [];
4528
- if (rawConfig.agents && typeof rawConfig.agents === "object") {
4529
- const { migrated, changed } = migrateAgentNames(rawConfig.agents);
4529
+ if (copy.agents && typeof copy.agents === "object") {
4530
+ const { migrated, changed } = migrateAgentNames(copy.agents);
4530
4531
  if (changed) {
4531
- rawConfig.agents = migrated;
4532
+ copy.agents = migrated;
4532
4533
  needsWrite = true;
4533
4534
  }
4534
4535
  }
4535
- if (rawConfig.agents && typeof rawConfig.agents === "object") {
4536
- const { migrated, changed, newMigrations } = migrateModelVersions(rawConfig.agents, existingMigrations);
4536
+ if (copy.agents && typeof copy.agents === "object") {
4537
+ const { migrated, changed, newMigrations } = migrateModelVersions(copy.agents, existingMigrations);
4537
4538
  if (changed) {
4538
- rawConfig.agents = migrated;
4539
+ copy.agents = migrated;
4539
4540
  needsWrite = true;
4540
4541
  log("Migrated model versions in agents config");
4541
4542
  }
4542
4543
  allNewMigrations.push(...newMigrations);
4543
4544
  }
4544
- if (rawConfig.categories && typeof rawConfig.categories === "object") {
4545
- const { migrated, changed, newMigrations } = migrateModelVersions(rawConfig.categories, existingMigrations);
4545
+ if (copy.categories && typeof copy.categories === "object") {
4546
+ const { migrated, changed, newMigrations } = migrateModelVersions(copy.categories, existingMigrations);
4546
4547
  if (changed) {
4547
- rawConfig.categories = migrated;
4548
+ copy.categories = migrated;
4548
4549
  needsWrite = true;
4549
4550
  log("Migrated model versions in categories config");
4550
4551
  }
@@ -4553,18 +4554,18 @@ function migrateConfigFile(configPath, rawConfig) {
4553
4554
  if (allNewMigrations.length > 0) {
4554
4555
  const updatedMigrations = Array.from(existingMigrations);
4555
4556
  updatedMigrations.push(...allNewMigrations);
4556
- rawConfig._migrations = updatedMigrations;
4557
+ copy._migrations = updatedMigrations;
4557
4558
  needsWrite = true;
4558
4559
  }
4559
- if (rawConfig.omo_agent) {
4560
- rawConfig.sisyphus_agent = rawConfig.omo_agent;
4561
- delete rawConfig.omo_agent;
4560
+ if (copy.omo_agent) {
4561
+ copy.sisyphus_agent = copy.omo_agent;
4562
+ delete copy.omo_agent;
4562
4563
  needsWrite = true;
4563
4564
  }
4564
- if (rawConfig.disabled_agents && Array.isArray(rawConfig.disabled_agents)) {
4565
+ if (copy.disabled_agents && Array.isArray(copy.disabled_agents)) {
4565
4566
  const migrated = [];
4566
4567
  let changed = false;
4567
- for (const agent of rawConfig.disabled_agents) {
4568
+ for (const agent of copy.disabled_agents) {
4568
4569
  const newAgent = AGENT_NAME_MAP[agent.toLowerCase()] ?? AGENT_NAME_MAP[agent] ?? agent;
4569
4570
  if (newAgent !== agent) {
4570
4571
  changed = true;
@@ -4572,14 +4573,14 @@ function migrateConfigFile(configPath, rawConfig) {
4572
4573
  migrated.push(newAgent);
4573
4574
  }
4574
4575
  if (changed) {
4575
- rawConfig.disabled_agents = migrated;
4576
+ copy.disabled_agents = migrated;
4576
4577
  needsWrite = true;
4577
4578
  }
4578
4579
  }
4579
- if (rawConfig.disabled_hooks && Array.isArray(rawConfig.disabled_hooks)) {
4580
- const { migrated, changed, removed } = migrateHookNames(rawConfig.disabled_hooks);
4580
+ if (copy.disabled_hooks && Array.isArray(copy.disabled_hooks)) {
4581
+ const { migrated, changed, removed } = migrateHookNames(copy.disabled_hooks);
4581
4582
  if (changed) {
4582
- rawConfig.disabled_hooks = migrated;
4583
+ copy.disabled_hooks = migrated;
4583
4584
  needsWrite = true;
4584
4585
  }
4585
4586
  if (removed.length > 0) {
@@ -4590,13 +4591,20 @@ function migrateConfigFile(configPath, rawConfig) {
4590
4591
  try {
4591
4592
  const timestamp2 = new Date().toISOString().replace(/[:.]/g, "-");
4592
4593
  const backupPath = `${configPath}.bak.${timestamp2}`;
4593
- fs5.copyFileSync(configPath, backupPath);
4594
- fs5.writeFileSync(configPath, JSON.stringify(rawConfig, null, 2) + `
4594
+ try {
4595
+ fs5.copyFileSync(configPath, backupPath);
4596
+ } catch {}
4597
+ fs5.writeFileSync(configPath, JSON.stringify(copy, null, 2) + `
4595
4598
  `, "utf-8");
4596
4599
  log(`Migrated config file: ${configPath} (backup: ${backupPath})`);
4597
4600
  } catch (err) {
4598
4601
  log(`Failed to write migrated config to ${configPath}:`, err);
4602
+ return false;
4603
+ }
4604
+ for (const key of Object.keys(rawConfig)) {
4605
+ delete rawConfig[key];
4599
4606
  }
4607
+ Object.assign(rawConfig, copy);
4600
4608
  }
4601
4609
  return needsWrite;
4602
4610
  }
@@ -5470,7 +5478,8 @@ async function fetchAvailableModels(client, options) {
5470
5478
  log("[fetchAvailableModels] provider-models cache empty, falling back to models.json");
5471
5479
  } else {
5472
5480
  log("[fetchAvailableModels] using provider-models cache (whitelist-filtered)");
5473
- for (const [providerId, modelIds] of Object.entries(providerModelsCache.models)) {
5481
+ const modelsByProvider = providerModelsCache.models;
5482
+ for (const [providerId, modelIds] of Object.entries(modelsByProvider)) {
5474
5483
  if (!connectedSet.has(providerId)) {
5475
5484
  continue;
5476
5485
  }
@@ -5643,7 +5652,7 @@ function resolveModelPipeline(request) {
5643
5652
  return { model: match, provenance: "category-default", attempted };
5644
5653
  }
5645
5654
  } else {
5646
- const connectedProviders = readConnectedProvidersCache();
5655
+ const connectedProviders = constraints.connectedProviders ?? readConnectedProvidersCache();
5647
5656
  if (connectedProviders === null) {
5648
5657
  log("Model resolved via category default (no cache, first run)", {
5649
5658
  model: normalizedCategoryDefault
@@ -5667,7 +5676,7 @@ function resolveModelPipeline(request) {
5667
5676
  }
5668
5677
  if (fallbackChain && fallbackChain.length > 0) {
5669
5678
  if (availableModels.size === 0) {
5670
- const connectedProviders = readConnectedProvidersCache();
5679
+ const connectedProviders = constraints.connectedProviders ?? readConnectedProvidersCache();
5671
5680
  const connectedSet = connectedProviders ? new Set(connectedProviders) : null;
5672
5681
  if (connectedSet === null) {
5673
5682
  log("Model fallback chain skipped (no connected providers cache) - falling through to system default");
@@ -6172,6 +6181,127 @@ function injectServerAuthIntoClient(client) {
6172
6181
  // src/shared/port-utils.ts
6173
6182
  var init_port_utils = () => {};
6174
6183
 
6184
+ // src/shared/git-worktree/parse-status-porcelain.ts
6185
+ function parseGitStatusPorcelain(output) {
6186
+ const map2 = new Map;
6187
+ if (!output)
6188
+ return map2;
6189
+ for (const line of output.split(`
6190
+ `)) {
6191
+ if (!line)
6192
+ continue;
6193
+ const status = line.substring(0, 2).trim();
6194
+ const filePath = line.substring(3);
6195
+ if (!filePath)
6196
+ continue;
6197
+ if (status === "A" || status === "??") {
6198
+ map2.set(filePath, "added");
6199
+ } else if (status === "D") {
6200
+ map2.set(filePath, "deleted");
6201
+ } else {
6202
+ map2.set(filePath, "modified");
6203
+ }
6204
+ }
6205
+ return map2;
6206
+ }
6207
+
6208
+ // src/shared/git-worktree/parse-diff-numstat.ts
6209
+ function parseGitDiffNumstat(output, statusMap) {
6210
+ if (!output)
6211
+ return [];
6212
+ const stats = [];
6213
+ for (const line of output.split(`
6214
+ `)) {
6215
+ const parts = line.split("\t");
6216
+ if (parts.length < 3)
6217
+ continue;
6218
+ const [addedStr, removedStr, path5] = parts;
6219
+ const added = addedStr === "-" ? 0 : parseInt(addedStr, 10);
6220
+ const removed = removedStr === "-" ? 0 : parseInt(removedStr, 10);
6221
+ stats.push({
6222
+ path: path5,
6223
+ added,
6224
+ removed,
6225
+ status: statusMap.get(path5) ?? "modified"
6226
+ });
6227
+ }
6228
+ return stats;
6229
+ }
6230
+
6231
+ // src/shared/git-worktree/collect-git-diff-stats.ts
6232
+ import { execFileSync } from "child_process";
6233
+ function collectGitDiffStats(directory) {
6234
+ try {
6235
+ const diffOutput = execFileSync("git", ["diff", "--numstat", "HEAD"], {
6236
+ cwd: directory,
6237
+ encoding: "utf-8",
6238
+ timeout: 5000,
6239
+ stdio: ["pipe", "pipe", "pipe"]
6240
+ }).trim();
6241
+ if (!diffOutput)
6242
+ return [];
6243
+ const statusOutput = execFileSync("git", ["status", "--porcelain"], {
6244
+ cwd: directory,
6245
+ encoding: "utf-8",
6246
+ timeout: 5000,
6247
+ stdio: ["pipe", "pipe", "pipe"]
6248
+ }).trim();
6249
+ const statusMap = parseGitStatusPorcelain(statusOutput);
6250
+ return parseGitDiffNumstat(diffOutput, statusMap);
6251
+ } catch {
6252
+ return [];
6253
+ }
6254
+ }
6255
+ var init_collect_git_diff_stats = () => {};
6256
+
6257
+ // src/shared/git-worktree/format-file-changes.ts
6258
+ function formatFileChanges(stats, notepadPath) {
6259
+ if (stats.length === 0)
6260
+ return `[FILE CHANGES SUMMARY]
6261
+ No file changes detected.
6262
+ `;
6263
+ const modified = stats.filter((s) => s.status === "modified");
6264
+ const added = stats.filter((s) => s.status === "added");
6265
+ const deleted = stats.filter((s) => s.status === "deleted");
6266
+ const lines = ["[FILE CHANGES SUMMARY]"];
6267
+ if (modified.length > 0) {
6268
+ lines.push("Modified files:");
6269
+ for (const f of modified) {
6270
+ lines.push(` ${f.path} (+${f.added}, -${f.removed})`);
6271
+ }
6272
+ lines.push("");
6273
+ }
6274
+ if (added.length > 0) {
6275
+ lines.push("Created files:");
6276
+ for (const f of added) {
6277
+ lines.push(` ${f.path} (+${f.added})`);
6278
+ }
6279
+ lines.push("");
6280
+ }
6281
+ if (deleted.length > 0) {
6282
+ lines.push("Deleted files:");
6283
+ for (const f of deleted) {
6284
+ lines.push(` ${f.path} (-${f.removed})`);
6285
+ }
6286
+ lines.push("");
6287
+ }
6288
+ if (notepadPath) {
6289
+ const notepadStat = stats.find((s) => s.path.includes("notepad") || s.path.includes(".sisyphus"));
6290
+ if (notepadStat) {
6291
+ lines.push("[NOTEPAD UPDATED]");
6292
+ lines.push(` ${notepadStat.path} (+${notepadStat.added})`);
6293
+ lines.push("");
6294
+ }
6295
+ }
6296
+ return lines.join(`
6297
+ `);
6298
+ }
6299
+
6300
+ // src/shared/git-worktree/index.ts
6301
+ var init_git_worktree = __esm(() => {
6302
+ init_collect_git_diff_stats();
6303
+ });
6304
+
6175
6305
  // src/shared/safe-create-hook.ts
6176
6306
  function safeCreateHook(name, factory, options) {
6177
6307
  const enabled = options?.enabled ?? true;
@@ -6234,6 +6364,7 @@ var init_shared = __esm(() => {
6234
6364
  init_tmux();
6235
6365
  init_model_suggestion_retry();
6236
6366
  init_port_utils();
6367
+ init_git_worktree();
6237
6368
  init_safe_create_hook();
6238
6369
  });
6239
6370
 
@@ -11599,6 +11730,12 @@ function isPlanAgent(agentName) {
11599
11730
  const lowerName = agentName.toLowerCase().trim();
11600
11731
  return PLAN_AGENT_NAMES.some((name) => lowerName === name || lowerName.includes(name));
11601
11732
  }
11733
+ function isPlanFamily(agentName) {
11734
+ if (!agentName)
11735
+ return false;
11736
+ const lowerName = agentName.toLowerCase().trim();
11737
+ return PLAN_FAMILY_NAMES.some((name) => lowerName === name || lowerName.includes(name));
11738
+ }
11602
11739
  var VISUAL_CATEGORY_PROMPT_APPEND = `<Category_Context>
11603
11740
  You are working on VISUAL/UI tasks.
11604
11741
 
@@ -12016,7 +12153,7 @@ WHY THIS FORMAT IS MANDATORY:
12016
12153
  - QA criteria ensure verifiable completion
12017
12154
  </FINAL_OUTPUT_FOR_CALLER>
12018
12155
 
12019
- `, PLAN_AGENT_NAMES;
12156
+ `, PLAN_AGENT_NAMES, PLAN_FAMILY_NAMES;
12020
12157
  var init_constants4 = __esm(() => {
12021
12158
  DEFAULT_CATEGORIES = {
12022
12159
  "visual-engineering": { model: "google/gemini-3-pro" },
@@ -12048,7 +12185,8 @@ var init_constants4 = __esm(() => {
12048
12185
  "unspecified-high": "Tasks that don't fit other categories, high effort required",
12049
12186
  writing: "Documentation, prose, technical writing"
12050
12187
  };
12051
- PLAN_AGENT_NAMES = ["plan", "prometheus", "planner"];
12188
+ PLAN_AGENT_NAMES = ["plan"];
12189
+ PLAN_FAMILY_NAMES = ["plan", "prometheus"];
12052
12190
  });
12053
12191
 
12054
12192
  // node_modules/ajv/dist/compile/codegen/code.js
@@ -12429,11 +12567,11 @@ var require_codegen = __commonJS((exports) => {
12429
12567
  const rhs = this.rhs === undefined ? "" : ` = ${this.rhs}`;
12430
12568
  return `${varKind} ${this.name}${rhs};` + _n;
12431
12569
  }
12432
- optimizeNames(names, constants17) {
12570
+ optimizeNames(names, constants18) {
12433
12571
  if (!names[this.name.str])
12434
12572
  return;
12435
12573
  if (this.rhs)
12436
- this.rhs = optimizeExpr(this.rhs, names, constants17);
12574
+ this.rhs = optimizeExpr(this.rhs, names, constants18);
12437
12575
  return this;
12438
12576
  }
12439
12577
  get names() {
@@ -12451,10 +12589,10 @@ var require_codegen = __commonJS((exports) => {
12451
12589
  render({ _n }) {
12452
12590
  return `${this.lhs} = ${this.rhs};` + _n;
12453
12591
  }
12454
- optimizeNames(names, constants17) {
12592
+ optimizeNames(names, constants18) {
12455
12593
  if (this.lhs instanceof code_1.Name && !names[this.lhs.str] && !this.sideEffects)
12456
12594
  return;
12457
- this.rhs = optimizeExpr(this.rhs, names, constants17);
12595
+ this.rhs = optimizeExpr(this.rhs, names, constants18);
12458
12596
  return this;
12459
12597
  }
12460
12598
  get names() {
@@ -12520,8 +12658,8 @@ var require_codegen = __commonJS((exports) => {
12520
12658
  optimizeNodes() {
12521
12659
  return `${this.code}` ? this : undefined;
12522
12660
  }
12523
- optimizeNames(names, constants17) {
12524
- this.code = optimizeExpr(this.code, names, constants17);
12661
+ optimizeNames(names, constants18) {
12662
+ this.code = optimizeExpr(this.code, names, constants18);
12525
12663
  return this;
12526
12664
  }
12527
12665
  get names() {
@@ -12551,12 +12689,12 @@ var require_codegen = __commonJS((exports) => {
12551
12689
  }
12552
12690
  return nodes.length > 0 ? this : undefined;
12553
12691
  }
12554
- optimizeNames(names, constants17) {
12692
+ optimizeNames(names, constants18) {
12555
12693
  const { nodes } = this;
12556
12694
  let i2 = nodes.length;
12557
12695
  while (i2--) {
12558
12696
  const n = nodes[i2];
12559
- if (n.optimizeNames(names, constants17))
12697
+ if (n.optimizeNames(names, constants18))
12560
12698
  continue;
12561
12699
  subtractNames(names, n.names);
12562
12700
  nodes.splice(i2, 1);
@@ -12613,12 +12751,12 @@ var require_codegen = __commonJS((exports) => {
12613
12751
  return;
12614
12752
  return this;
12615
12753
  }
12616
- optimizeNames(names, constants17) {
12754
+ optimizeNames(names, constants18) {
12617
12755
  var _a;
12618
- this.else = (_a = this.else) === null || _a === undefined ? undefined : _a.optimizeNames(names, constants17);
12619
- if (!(super.optimizeNames(names, constants17) || this.else))
12756
+ this.else = (_a = this.else) === null || _a === undefined ? undefined : _a.optimizeNames(names, constants18);
12757
+ if (!(super.optimizeNames(names, constants18) || this.else))
12620
12758
  return;
12621
- this.condition = optimizeExpr(this.condition, names, constants17);
12759
+ this.condition = optimizeExpr(this.condition, names, constants18);
12622
12760
  return this;
12623
12761
  }
12624
12762
  get names() {
@@ -12643,10 +12781,10 @@ var require_codegen = __commonJS((exports) => {
12643
12781
  render(opts) {
12644
12782
  return `for(${this.iteration})` + super.render(opts);
12645
12783
  }
12646
- optimizeNames(names, constants17) {
12647
- if (!super.optimizeNames(names, constants17))
12784
+ optimizeNames(names, constants18) {
12785
+ if (!super.optimizeNames(names, constants18))
12648
12786
  return;
12649
- this.iteration = optimizeExpr(this.iteration, names, constants17);
12787
+ this.iteration = optimizeExpr(this.iteration, names, constants18);
12650
12788
  return this;
12651
12789
  }
12652
12790
  get names() {
@@ -12684,10 +12822,10 @@ var require_codegen = __commonJS((exports) => {
12684
12822
  render(opts) {
12685
12823
  return `for(${this.varKind} ${this.name} ${this.loop} ${this.iterable})` + super.render(opts);
12686
12824
  }
12687
- optimizeNames(names, constants17) {
12688
- if (!super.optimizeNames(names, constants17))
12825
+ optimizeNames(names, constants18) {
12826
+ if (!super.optimizeNames(names, constants18))
12689
12827
  return;
12690
- this.iterable = optimizeExpr(this.iterable, names, constants17);
12828
+ this.iterable = optimizeExpr(this.iterable, names, constants18);
12691
12829
  return this;
12692
12830
  }
12693
12831
  get names() {
@@ -12732,11 +12870,11 @@ var require_codegen = __commonJS((exports) => {
12732
12870
  (_b = this.finally) === null || _b === undefined || _b.optimizeNodes();
12733
12871
  return this;
12734
12872
  }
12735
- optimizeNames(names, constants17) {
12873
+ optimizeNames(names, constants18) {
12736
12874
  var _a, _b;
12737
- super.optimizeNames(names, constants17);
12738
- (_a = this.catch) === null || _a === undefined || _a.optimizeNames(names, constants17);
12739
- (_b = this.finally) === null || _b === undefined || _b.optimizeNames(names, constants17);
12875
+ super.optimizeNames(names, constants18);
12876
+ (_a = this.catch) === null || _a === undefined || _a.optimizeNames(names, constants18);
12877
+ (_b = this.finally) === null || _b === undefined || _b.optimizeNames(names, constants18);
12740
12878
  return this;
12741
12879
  }
12742
12880
  get names() {
@@ -13010,7 +13148,7 @@ var require_codegen = __commonJS((exports) => {
13010
13148
  function addExprNames(names, from) {
13011
13149
  return from instanceof code_1._CodeOrName ? addNames(names, from.names) : names;
13012
13150
  }
13013
- function optimizeExpr(expr, names, constants17) {
13151
+ function optimizeExpr(expr, names, constants18) {
13014
13152
  if (expr instanceof code_1.Name)
13015
13153
  return replaceName(expr);
13016
13154
  if (!canOptimize(expr))
@@ -13025,14 +13163,14 @@ var require_codegen = __commonJS((exports) => {
13025
13163
  return items;
13026
13164
  }, []));
13027
13165
  function replaceName(n) {
13028
- const c = constants17[n.str];
13166
+ const c = constants18[n.str];
13029
13167
  if (c === undefined || names[n.str] !== 1)
13030
13168
  return n;
13031
13169
  delete names[n.str];
13032
13170
  return c;
13033
13171
  }
13034
13172
  function canOptimize(e) {
13035
- return e instanceof code_1._Code && e._items.some((c) => c instanceof code_1.Name && names[c.str] === 1 && constants17[c.str] !== undefined);
13173
+ return e instanceof code_1._Code && e._items.some((c) => c instanceof code_1.Name && names[c.str] === 1 && constants18[c.str] !== undefined);
13036
13174
  }
13037
13175
  }
13038
13176
  function subtractNames(names, from) {
@@ -13479,37 +13617,37 @@ var require_dataType = __commonJS((exports) => {
13479
13617
  DataType2[DataType2["Wrong"] = 1] = "Wrong";
13480
13618
  })(DataType || (exports.DataType = DataType = {}));
13481
13619
  function getSchemaTypes(schema2) {
13482
- const types21 = getJSONTypes(schema2.type);
13483
- const hasNull = types21.includes("null");
13620
+ const types22 = getJSONTypes(schema2.type);
13621
+ const hasNull = types22.includes("null");
13484
13622
  if (hasNull) {
13485
13623
  if (schema2.nullable === false)
13486
13624
  throw new Error("type: null contradicts nullable: false");
13487
13625
  } else {
13488
- if (!types21.length && schema2.nullable !== undefined) {
13626
+ if (!types22.length && schema2.nullable !== undefined) {
13489
13627
  throw new Error('"nullable" cannot be used without "type"');
13490
13628
  }
13491
13629
  if (schema2.nullable === true)
13492
- types21.push("null");
13630
+ types22.push("null");
13493
13631
  }
13494
- return types21;
13632
+ return types22;
13495
13633
  }
13496
13634
  exports.getSchemaTypes = getSchemaTypes;
13497
13635
  function getJSONTypes(ts) {
13498
- const types21 = Array.isArray(ts) ? ts : ts ? [ts] : [];
13499
- if (types21.every(rules_1.isJSONType))
13500
- return types21;
13501
- throw new Error("type must be JSONType or JSONType[]: " + types21.join(","));
13636
+ const types22 = Array.isArray(ts) ? ts : ts ? [ts] : [];
13637
+ if (types22.every(rules_1.isJSONType))
13638
+ return types22;
13639
+ throw new Error("type must be JSONType or JSONType[]: " + types22.join(","));
13502
13640
  }
13503
13641
  exports.getJSONTypes = getJSONTypes;
13504
- function coerceAndCheckDataType(it, types21) {
13642
+ function coerceAndCheckDataType(it, types22) {
13505
13643
  const { gen, data, opts } = it;
13506
- const coerceTo = coerceToTypes(types21, opts.coerceTypes);
13507
- const checkTypes = types21.length > 0 && !(coerceTo.length === 0 && types21.length === 1 && (0, applicability_1.schemaHasRulesForType)(it, types21[0]));
13644
+ const coerceTo = coerceToTypes(types22, opts.coerceTypes);
13645
+ const checkTypes = types22.length > 0 && !(coerceTo.length === 0 && types22.length === 1 && (0, applicability_1.schemaHasRulesForType)(it, types22[0]));
13508
13646
  if (checkTypes) {
13509
- const wrongType = checkDataTypes(types21, data, opts.strictNumbers, DataType.Wrong);
13647
+ const wrongType = checkDataTypes(types22, data, opts.strictNumbers, DataType.Wrong);
13510
13648
  gen.if(wrongType, () => {
13511
13649
  if (coerceTo.length)
13512
- coerceData(it, types21, coerceTo);
13650
+ coerceData(it, types22, coerceTo);
13513
13651
  else
13514
13652
  reportTypeError(it);
13515
13653
  });
@@ -13518,15 +13656,15 @@ var require_dataType = __commonJS((exports) => {
13518
13656
  }
13519
13657
  exports.coerceAndCheckDataType = coerceAndCheckDataType;
13520
13658
  var COERCIBLE = new Set(["string", "number", "integer", "boolean", "null"]);
13521
- function coerceToTypes(types21, coerceTypes) {
13522
- return coerceTypes ? types21.filter((t) => COERCIBLE.has(t) || coerceTypes === "array" && t === "array") : [];
13659
+ function coerceToTypes(types22, coerceTypes) {
13660
+ return coerceTypes ? types22.filter((t) => COERCIBLE.has(t) || coerceTypes === "array" && t === "array") : [];
13523
13661
  }
13524
- function coerceData(it, types21, coerceTo) {
13662
+ function coerceData(it, types22, coerceTo) {
13525
13663
  const { gen, data, opts } = it;
13526
13664
  const dataType = gen.let("dataType", (0, codegen_1._)`typeof ${data}`);
13527
13665
  const coerced = gen.let("coerced", (0, codegen_1._)`undefined`);
13528
13666
  if (opts.coerceTypes === "array") {
13529
- gen.if((0, codegen_1._)`${dataType} == 'object' && Array.isArray(${data}) && ${data}.length == 1`, () => gen.assign(data, (0, codegen_1._)`${data}[0]`).assign(dataType, (0, codegen_1._)`typeof ${data}`).if(checkDataTypes(types21, data, opts.strictNumbers), () => gen.assign(coerced, data)));
13667
+ gen.if((0, codegen_1._)`${dataType} == 'object' && Array.isArray(${data}) && ${data}.length == 1`, () => gen.assign(data, (0, codegen_1._)`${data}[0]`).assign(dataType, (0, codegen_1._)`typeof ${data}`).if(checkDataTypes(types22, data, opts.strictNumbers), () => gen.assign(coerced, data)));
13530
13668
  }
13531
13669
  gen.if((0, codegen_1._)`${coerced} !== undefined`);
13532
13670
  for (const t of coerceTo) {
@@ -13602,19 +13740,19 @@ var require_dataType = __commonJS((exports) => {
13602
13740
  return checkDataType(dataTypes[0], data, strictNums, correct);
13603
13741
  }
13604
13742
  let cond;
13605
- const types21 = (0, util_1.toHash)(dataTypes);
13606
- if (types21.array && types21.object) {
13743
+ const types22 = (0, util_1.toHash)(dataTypes);
13744
+ if (types22.array && types22.object) {
13607
13745
  const notObj = (0, codegen_1._)`typeof ${data} != "object"`;
13608
- cond = types21.null ? notObj : (0, codegen_1._)`!${data} || ${notObj}`;
13609
- delete types21.null;
13610
- delete types21.array;
13611
- delete types21.object;
13746
+ cond = types22.null ? notObj : (0, codegen_1._)`!${data} || ${notObj}`;
13747
+ delete types22.null;
13748
+ delete types22.array;
13749
+ delete types22.object;
13612
13750
  } else {
13613
13751
  cond = codegen_1.nil;
13614
13752
  }
13615
- if (types21.number)
13616
- delete types21.integer;
13617
- for (const t in types21)
13753
+ if (types22.number)
13754
+ delete types22.integer;
13755
+ for (const t in types22)
13618
13756
  cond = (0, codegen_1.and)(cond, checkDataType(t, data, strictNums, correct));
13619
13757
  return cond;
13620
13758
  }
@@ -14402,9 +14540,9 @@ var require_validate = __commonJS((exports) => {
14402
14540
  function typeAndKeywords(it, errsCount) {
14403
14541
  if (it.opts.jtd)
14404
14542
  return schemaKeywords(it, [], false, errsCount);
14405
- const types21 = (0, dataType_1.getSchemaTypes)(it.schema);
14406
- const checkedTypes = (0, dataType_1.coerceAndCheckDataType)(it, types21);
14407
- schemaKeywords(it, types21, !checkedTypes, errsCount);
14543
+ const types22 = (0, dataType_1.getSchemaTypes)(it.schema);
14544
+ const checkedTypes = (0, dataType_1.coerceAndCheckDataType)(it, types22);
14545
+ schemaKeywords(it, types22, !checkedTypes, errsCount);
14408
14546
  }
14409
14547
  function checkRefsAndKeywords(it) {
14410
14548
  const { schema: schema2, errSchemaPath, opts, self } = it;
@@ -14454,7 +14592,7 @@ var require_validate = __commonJS((exports) => {
14454
14592
  if (items instanceof codegen_1.Name)
14455
14593
  gen.assign((0, codegen_1._)`${evaluated}.items`, items);
14456
14594
  }
14457
- function schemaKeywords(it, types21, typeErrors, errsCount) {
14595
+ function schemaKeywords(it, types22, typeErrors, errsCount) {
14458
14596
  const { gen, schema: schema2, data, allErrors, opts, self } = it;
14459
14597
  const { RULES } = self;
14460
14598
  if (schema2.$ref && (opts.ignoreKeywordsWithRef || !(0, util_1.schemaHasRulesButRef)(schema2, RULES))) {
@@ -14462,7 +14600,7 @@ var require_validate = __commonJS((exports) => {
14462
14600
  return;
14463
14601
  }
14464
14602
  if (!opts.jtd)
14465
- checkStrictTypes(it, types21);
14603
+ checkStrictTypes(it, types22);
14466
14604
  gen.block(() => {
14467
14605
  for (const group of RULES.rules)
14468
14606
  groupKeywords(group);
@@ -14474,7 +14612,7 @@ var require_validate = __commonJS((exports) => {
14474
14612
  if (group.type) {
14475
14613
  gen.if((0, dataType_2.checkDataType)(group.type, data, opts.strictNumbers));
14476
14614
  iterateKeywords(it, group);
14477
- if (types21.length === 1 && types21[0] === group.type && typeErrors) {
14615
+ if (types22.length === 1 && types22[0] === group.type && typeErrors) {
14478
14616
  gen.else();
14479
14617
  (0, dataType_2.reportTypeError)(it);
14480
14618
  }
@@ -14498,27 +14636,27 @@ var require_validate = __commonJS((exports) => {
14498
14636
  }
14499
14637
  });
14500
14638
  }
14501
- function checkStrictTypes(it, types21) {
14639
+ function checkStrictTypes(it, types22) {
14502
14640
  if (it.schemaEnv.meta || !it.opts.strictTypes)
14503
14641
  return;
14504
- checkContextTypes(it, types21);
14642
+ checkContextTypes(it, types22);
14505
14643
  if (!it.opts.allowUnionTypes)
14506
- checkMultipleTypes(it, types21);
14644
+ checkMultipleTypes(it, types22);
14507
14645
  checkKeywordTypes(it, it.dataTypes);
14508
14646
  }
14509
- function checkContextTypes(it, types21) {
14510
- if (!types21.length)
14647
+ function checkContextTypes(it, types22) {
14648
+ if (!types22.length)
14511
14649
  return;
14512
14650
  if (!it.dataTypes.length) {
14513
- it.dataTypes = types21;
14651
+ it.dataTypes = types22;
14514
14652
  return;
14515
14653
  }
14516
- types21.forEach((t) => {
14654
+ types22.forEach((t) => {
14517
14655
  if (!includesType(it.dataTypes, t)) {
14518
14656
  strictTypesError(it, `type "${t}" not allowed by context "${it.dataTypes.join(",")}"`);
14519
14657
  }
14520
14658
  });
14521
- narrowSchemaTypes(it, types21);
14659
+ narrowSchemaTypes(it, types22);
14522
14660
  }
14523
14661
  function checkMultipleTypes(it, ts) {
14524
14662
  if (ts.length > 1 && !(ts.length === 2 && ts.includes("null"))) {
@@ -20108,6 +20246,9 @@ function extractMessageIndex(error) {
20108
20246
  }
20109
20247
  function detectErrorType(error) {
20110
20248
  const message = getErrorMessage(error);
20249
+ if (message.includes("assistant message prefill") || message.includes("conversation must end with a user message")) {
20250
+ return "assistant_prefill_unsupported";
20251
+ }
20111
20252
  if (message.includes("thinking") && (message.includes("first block") || message.includes("must start with") || message.includes("preceeding") || message.includes("final block") || message.includes("cannot be thinking") || message.includes("expected") && message.includes("found"))) {
20112
20253
  return "thinking_block_order";
20113
20254
  }
@@ -20229,12 +20370,14 @@ function createSessionRecoveryHook(ctx, options) {
20229
20370
  const toastTitles = {
20230
20371
  tool_result_missing: "Tool Crash Recovery",
20231
20372
  thinking_block_order: "Thinking Block Recovery",
20232
- thinking_disabled_violation: "Thinking Strip Recovery"
20373
+ thinking_disabled_violation: "Thinking Strip Recovery",
20374
+ assistant_prefill_unsupported: "Prefill Error Recovery"
20233
20375
  };
20234
20376
  const toastMessages = {
20235
20377
  tool_result_missing: "Injecting cancelled tool results...",
20236
20378
  thinking_block_order: "Fixing message structure...",
20237
- thinking_disabled_violation: "Stripping thinking blocks..."
20379
+ thinking_disabled_violation: "Stripping thinking blocks...",
20380
+ assistant_prefill_unsupported: "Sending 'Continue' to recover..."
20238
20381
  };
20239
20382
  await ctx.client.tui.showToast({
20240
20383
  body: {
@@ -20261,6 +20404,8 @@ function createSessionRecoveryHook(ctx, options) {
20261
20404
  const resumeConfig = extractResumeConfig(lastUser, sessionID);
20262
20405
  await resumeSession(ctx.client, resumeConfig);
20263
20406
  }
20407
+ } else if (errorType === "assistant_prefill_unsupported") {
20408
+ success = true;
20264
20409
  }
20265
20410
  return success;
20266
20411
  } catch (err) {
@@ -24082,9 +24227,9 @@ You ARE the planner. Your job: create bulletproof work plans.
24082
24227
  ### Research Protocol
24083
24228
  1. **Fire parallel background agents** for comprehensive context:
24084
24229
  \`\`\`
24085
- task(agent="explore", prompt="Find existing patterns for [topic] in codebase", background=true)
24086
- task(agent="explore", prompt="Find test infrastructure and conventions", background=true)
24087
- task(agent="librarian", prompt="Find official docs and best practices for [technology]", background=true)
24230
+ task(subagent_type="explore", load_skills=[], prompt="Find existing patterns for [topic] in codebase", run_in_background=true)
24231
+ task(subagent_type="explore", load_skills=[], prompt="Find test infrastructure and conventions", run_in_background=true)
24232
+ task(subagent_type="librarian", load_skills=[], prompt="Find official docs and best practices for [technology]", run_in_background=true)
24088
24233
  \`\`\`
24089
24234
  2. **Wait for results** before planning - rushed plans fail
24090
24235
  3. **Synthesize findings** into informed requirements
@@ -24245,10 +24390,10 @@ Use these when they provide clear value based on the decision framework above:
24245
24390
 
24246
24391
  | Resource | When to Use | How to Use |
24247
24392
  |----------|-------------|------------|
24248
- | explore agent | Need codebase patterns you don't have | \`task(subagent_type="explore", run_in_background=true, ...)\` |
24249
- | librarian agent | External library docs, OSS examples | \`task(subagent_type="librarian", run_in_background=true, ...)\` |
24250
- | oracle agent | Stuck on architecture/debugging after 2+ attempts | \`task(subagent_type="oracle", ...)\` |
24251
- | plan agent | Complex multi-step with dependencies (5+ steps) | \`task(subagent_type="plan", ...)\` |
24393
+ | explore agent | Need codebase patterns you don't have | \`task(subagent_type="explore", load_skills=[], run_in_background=true, ...)\` |
24394
+ | librarian agent | External library docs, OSS examples | \`task(subagent_type="librarian", load_skills=[], run_in_background=true, ...)\` |
24395
+ | oracle agent | Stuck on architecture/debugging after 2+ attempts | \`task(subagent_type="oracle", load_skills=[], ...)\` |
24396
+ | plan agent | Complex multi-step with dependencies (5+ steps) | \`task(subagent_type="plan", load_skills=[], ...)\` |
24252
24397
  | task category | Specialized work matching a category | \`task(category="...", load_skills=[...])\` |
24253
24398
 
24254
24399
  <tool_usage_rules>
@@ -24418,7 +24563,7 @@ TELL THE USER WHAT AGENTS YOU WILL LEVERAGE NOW TO SATISFY USER'S REQUEST.
24418
24563
  | Architecture decision needed | MUST call plan agent |
24419
24564
 
24420
24565
  \`\`\`
24421
- task(subagent_type="plan", prompt="<gathered context + user request>")
24566
+ task(subagent_type="plan", load_skills=[], prompt="<gathered context + user request>")
24422
24567
  \`\`\`
24423
24568
 
24424
24569
  **WHY PLAN AGENT IS MANDATORY:**
@@ -24433,9 +24578,9 @@ task(subagent_type="plan", prompt="<gathered context + user request>")
24433
24578
 
24434
24579
  | Scenario | Action |
24435
24580
  |----------|--------|
24436
- | Plan agent asks clarifying questions | \`task(session_id="{returned_session_id}", prompt="<your answer>")\` |
24437
- | Need to refine the plan | \`task(session_id="{returned_session_id}", prompt="Please adjust: <feedback>")\` |
24438
- | Plan needs more detail | \`task(session_id="{returned_session_id}", prompt="Add more detail to Task N")\` |
24581
+ | Plan agent asks clarifying questions | \`task(session_id="{returned_session_id}", load_skills=[], prompt="<your answer>")\` |
24582
+ | Need to refine the plan | \`task(session_id="{returned_session_id}", load_skills=[], prompt="Please adjust: <feedback>")\` |
24583
+ | Plan needs more detail | \`task(session_id="{returned_session_id}", load_skills=[], prompt="Add more detail to Task N")\` |
24439
24584
 
24440
24585
  **WHY SESSION_ID IS CRITICAL:**
24441
24586
  - Plan agent retains FULL conversation context
@@ -24445,10 +24590,10 @@ task(subagent_type="plan", prompt="<gathered context + user request>")
24445
24590
 
24446
24591
  \`\`\`
24447
24592
  // WRONG: Starting fresh loses all context
24448
- task(subagent_type="plan", prompt="Here's more info...")
24593
+ task(subagent_type="plan", load_skills=[], prompt="Here's more info...")
24449
24594
 
24450
24595
  // CORRECT: Resume preserves everything
24451
- task(session_id="ses_abc123", prompt="Here's my answer to your question: ...")
24596
+ task(session_id="ses_abc123", load_skills=[], prompt="Here's my answer to your question: ...")
24452
24597
  \`\`\`
24453
24598
 
24454
24599
  **FAILURE TO CALL PLAN AGENT = INCOMPLETE WORK.**
@@ -24461,10 +24606,10 @@ task(session_id="ses_abc123", prompt="Here's my answer to your question: ...")
24461
24606
 
24462
24607
  | Task Type | Action | Why |
24463
24608
  |-----------|--------|-----|
24464
- | Codebase exploration | task(subagent_type="explore", run_in_background=true) | Parallel, context-efficient |
24465
- | Documentation lookup | task(subagent_type="librarian", run_in_background=true) | Specialized knowledge |
24466
- | Planning | task(subagent_type="plan") | Parallel task graph + structured TODO list |
24467
- | Hard problem (conventional) | task(subagent_type="oracle") | Architecture, debugging, complex logic |
24609
+ | Codebase exploration | task(subagent_type="explore", load_skills=[], run_in_background=true) | Parallel, context-efficient |
24610
+ | Documentation lookup | task(subagent_type="librarian", load_skills=[], run_in_background=true) | Specialized knowledge |
24611
+ | Planning | task(subagent_type="plan", load_skills=[]) | Parallel task graph + structured TODO list |
24612
+ | Hard problem (conventional) | task(subagent_type="oracle", load_skills=[]) | Architecture, debugging, complex logic |
24468
24613
  | Hard problem (non-conventional) | task(category="artistry", load_skills=[...]) | Different approach needed |
24469
24614
  | Implementation | task(category="...", load_skills=[...]) | Domain-optimized models |
24470
24615
 
@@ -24898,7 +25043,7 @@ function clearInteractiveBashSessionState(sessionID) {
24898
25043
  }
24899
25044
  }
24900
25045
 
24901
- // src/hooks/interactive-bash-session/index.ts
25046
+ // src/hooks/interactive-bash-session/parser.ts
24902
25047
  function tokenizeCommand(cmd) {
24903
25048
  const tokens = [];
24904
25049
  let current = "";
@@ -24980,33 +25125,44 @@ function findSubcommand(tokens) {
24980
25125
  }
24981
25126
  return "";
24982
25127
  }
25128
+
25129
+ // src/hooks/interactive-bash-session/state-manager.ts
25130
+ function getOrCreateState(sessionID, sessionStates) {
25131
+ if (!sessionStates.has(sessionID)) {
25132
+ const persisted = loadInteractiveBashSessionState(sessionID);
25133
+ const state2 = persisted ?? {
25134
+ sessionID,
25135
+ tmuxSessions: new Set,
25136
+ updatedAt: Date.now()
25137
+ };
25138
+ sessionStates.set(sessionID, state2);
25139
+ }
25140
+ return sessionStates.get(sessionID);
25141
+ }
25142
+ function isOmoSession(sessionName) {
25143
+ return sessionName !== null && sessionName.startsWith(OMO_SESSION_PREFIX);
25144
+ }
25145
+ async function killAllTrackedSessions(state2) {
25146
+ for (const sessionName of state2.tmuxSessions) {
25147
+ try {
25148
+ const proc = Bun.spawn(["tmux", "kill-session", "-t", sessionName], {
25149
+ stdout: "ignore",
25150
+ stderr: "ignore"
25151
+ });
25152
+ await proc.exited;
25153
+ } catch {}
25154
+ }
25155
+ for (const sessionId of subagentSessions) {}
25156
+ }
25157
+
25158
+ // src/hooks/interactive-bash-session/hook.ts
24983
25159
  function createInteractiveBashSessionHook(ctx) {
24984
25160
  const sessionStates = new Map;
24985
- function getOrCreateState(sessionID) {
24986
- if (!sessionStates.has(sessionID)) {
24987
- const persisted = loadInteractiveBashSessionState(sessionID);
24988
- const state2 = persisted ?? {
24989
- sessionID,
24990
- tmuxSessions: new Set,
24991
- updatedAt: Date.now()
24992
- };
24993
- sessionStates.set(sessionID, state2);
24994
- }
24995
- return sessionStates.get(sessionID);
24996
- }
24997
- function isOmoSession(sessionName) {
24998
- return sessionName !== null && sessionName.startsWith(OMO_SESSION_PREFIX);
25161
+ function getOrCreateStateLocal(sessionID) {
25162
+ return getOrCreateState(sessionID, sessionStates);
24999
25163
  }
25000
- async function killAllTrackedSessions(state2) {
25001
- for (const sessionName of state2.tmuxSessions) {
25002
- try {
25003
- const proc = Bun.spawn(["tmux", "kill-session", "-t", sessionName], {
25004
- stdout: "ignore",
25005
- stderr: "ignore"
25006
- });
25007
- await proc.exited;
25008
- } catch {}
25009
- }
25164
+ async function killAllTrackedSessionsLocal(state2) {
25165
+ await killAllTrackedSessions(state2);
25010
25166
  for (const sessionId of subagentSessions) {
25011
25167
  ctx.client.session.abort({ path: { id: sessionId } }).catch(() => {});
25012
25168
  }
@@ -25023,7 +25179,7 @@ function createInteractiveBashSessionHook(ctx) {
25023
25179
  const tmuxCommand = args.tmux_command;
25024
25180
  const tokens = tokenizeCommand(tmuxCommand);
25025
25181
  const subCommand = findSubcommand(tokens);
25026
- const state2 = getOrCreateState(sessionID);
25182
+ const state2 = getOrCreateStateLocal(sessionID);
25027
25183
  let stateChanged = false;
25028
25184
  const toolOutput = output?.output ?? "";
25029
25185
  if (toolOutput.startsWith("Error:")) {
@@ -25061,8 +25217,8 @@ function createInteractiveBashSessionHook(ctx) {
25061
25217
  const sessionInfo = props?.info;
25062
25218
  const sessionID = sessionInfo?.id;
25063
25219
  if (sessionID) {
25064
- const state2 = getOrCreateState(sessionID);
25065
- await killAllTrackedSessions(state2);
25220
+ const state2 = getOrCreateStateLocal(sessionID);
25221
+ await killAllTrackedSessionsLocal(state2);
25066
25222
  sessionStates.delete(sessionID);
25067
25223
  clearInteractiveBashSessionState(sessionID);
25068
25224
  }
@@ -25210,7 +25366,7 @@ function buildReminderMessage(availableSkills) {
25210
25366
  function createCategorySkillReminderHook(_ctx, availableSkills = []) {
25211
25367
  const sessionStates = new Map;
25212
25368
  const reminderMessage = buildReminderMessage(availableSkills);
25213
- function getOrCreateState(sessionID) {
25369
+ function getOrCreateState2(sessionID) {
25214
25370
  if (!sessionStates.has(sessionID)) {
25215
25371
  sessionStates.set(sessionID, {
25216
25372
  delegationUsed: false,
@@ -25233,7 +25389,7 @@ function createCategorySkillReminderHook(_ctx, availableSkills = []) {
25233
25389
  if (!isTargetAgent(sessionID, input.agent)) {
25234
25390
  return;
25235
25391
  }
25236
- const state2 = getOrCreateState(sessionID);
25392
+ const state2 = getOrCreateState2(sessionID);
25237
25393
  if (DELEGATION_TOOLS.has(toolLower)) {
25238
25394
  state2.delegationUsed = true;
25239
25395
  log("[category-skill-reminder] Delegation tool used", { sessionID, tool });
@@ -25404,7 +25560,7 @@ IMPORTANT:
25404
25560
 
25405
25561
  Original task:
25406
25562
  {{PROMPT}}`;
25407
- var DEFAULT_API_TIMEOUT = 3000;
25563
+ var DEFAULT_API_TIMEOUT = 5000;
25408
25564
  function createRalphLoopHook(ctx, options) {
25409
25565
  const sessions = new Map;
25410
25566
  const config = options?.config;
@@ -25412,6 +25568,21 @@ function createRalphLoopHook(ctx, options) {
25412
25568
  const getTranscriptPath2 = options?.getTranscriptPath ?? getTranscriptPath;
25413
25569
  const apiTimeout = options?.apiTimeout ?? DEFAULT_API_TIMEOUT;
25414
25570
  const checkSessionExists = options?.checkSessionExists;
25571
+ async function withTimeout(promise, timeoutMs) {
25572
+ let timeoutId;
25573
+ const timeoutPromise = new Promise((_, reject) => {
25574
+ timeoutId = setTimeout(() => {
25575
+ reject(new Error("API timeout"));
25576
+ }, timeoutMs);
25577
+ });
25578
+ try {
25579
+ return await Promise.race([promise, timeoutPromise]);
25580
+ } finally {
25581
+ if (timeoutId !== undefined) {
25582
+ clearTimeout(timeoutId);
25583
+ }
25584
+ }
25585
+ }
25415
25586
  function getSessionState(sessionID) {
25416
25587
  let state2 = sessions.get(sessionID);
25417
25588
  if (!state2) {
@@ -25451,26 +25622,35 @@ function createRalphLoopHook(ctx, options) {
25451
25622
  }
25452
25623
  async function detectCompletionInSessionMessages(sessionID, promise) {
25453
25624
  try {
25454
- const response = await Promise.race([
25455
- ctx.client.session.messages({
25456
- path: { id: sessionID },
25457
- query: { directory: ctx.directory }
25458
- }),
25459
- new Promise((_, reject) => setTimeout(() => reject(new Error("API timeout")), apiTimeout))
25460
- ]);
25625
+ const response = await withTimeout(ctx.client.session.messages({
25626
+ path: { id: sessionID },
25627
+ query: { directory: ctx.directory }
25628
+ }), apiTimeout);
25461
25629
  const messages = response.data ?? [];
25462
25630
  if (!Array.isArray(messages))
25463
25631
  return false;
25464
25632
  const assistantMessages = messages.filter((msg) => msg.info?.role === "assistant");
25465
- const lastAssistant = assistantMessages[assistantMessages.length - 1];
25466
- if (!lastAssistant?.parts)
25633
+ if (assistantMessages.length === 0)
25467
25634
  return false;
25468
25635
  const pattern = new RegExp(`<promise>\\s*${escapeRegex(promise)}\\s*</promise>`, "is");
25469
- const responseText = lastAssistant.parts.filter((p) => p.type === "text").map((p) => p.text ?? "").join(`
25636
+ const recentAssistants = assistantMessages.slice(-3);
25637
+ for (const assistant of recentAssistants) {
25638
+ if (!assistant.parts)
25639
+ continue;
25640
+ const responseText = assistant.parts.filter((p) => p.type === "text" || p.type === "reasoning").map((p) => p.text ?? "").join(`
25470
25641
  `);
25471
- return pattern.test(responseText);
25642
+ if (pattern.test(responseText)) {
25643
+ return true;
25644
+ }
25645
+ }
25646
+ return false;
25472
25647
  } catch (err) {
25473
- log(`[${HOOK_NAME3}] Session messages check failed`, { sessionID, error: String(err) });
25648
+ setTimeout(() => {
25649
+ log(`[${HOOK_NAME3}] Session messages check failed`, {
25650
+ sessionID,
25651
+ error: String(err)
25652
+ });
25653
+ }, 0);
25474
25654
  return false;
25475
25655
  }
25476
25656
  }
@@ -25611,7 +25791,7 @@ function createRalphLoopHook(ctx, options) {
25611
25791
  let agent;
25612
25792
  let model;
25613
25793
  try {
25614
- const messagesResp = await ctx.client.session.messages({ path: { id: sessionID } });
25794
+ const messagesResp = await withTimeout(ctx.client.session.messages({ path: { id: sessionID } }), apiTimeout);
25615
25795
  const messages = messagesResp.data ?? [];
25616
25796
  for (let i2 = messages.length - 1;i2 >= 0; i2--) {
25617
25797
  const info = messages[i2].info;
@@ -26817,6 +26997,185 @@ Reading plan and beginning execution...
26817
26997
  - Read the FULL plan file before delegating any tasks
26818
26998
  - Follow atlas delegation protocols (7-section format)`;
26819
26999
 
27000
+ // src/features/builtin-commands/templates/handoff.ts
27001
+ var HANDOFF_TEMPLATE = `# Handoff Command
27002
+
27003
+ ## Purpose
27004
+
27005
+ Use /handoff when:
27006
+ - The current session context is getting too long and quality is degrading
27007
+ - You want to start fresh while preserving essential context from this session
27008
+ - The context window is approaching capacity
27009
+
27010
+ This creates a detailed context summary that can be used to continue work in a new session.
27011
+
27012
+ ---
27013
+
27014
+ # PHASE 0: VALIDATE REQUEST
27015
+
27016
+ Before proceeding, confirm:
27017
+ - [ ] There is meaningful work or context in this session to preserve
27018
+ - [ ] The user wants to create a handoff summary (not just asking about it)
27019
+
27020
+ If the session is nearly empty or has no meaningful context, inform the user there is nothing substantial to hand off.
27021
+
27022
+ ---
27023
+
27024
+ # PHASE 1: GATHER PROGRAMMATIC CONTEXT
27025
+
27026
+ Execute these tools to gather concrete data:
27027
+
27028
+ 1. session_read({ session_id: "$SESSION_ID" }) \u2014 full session history
27029
+ 2. todoread() \u2014 current task progress
27030
+ 3. Bash({ command: "git diff --stat HEAD~10..HEAD" }) \u2014 recent file changes
27031
+ 4. Bash({ command: "git status --porcelain" }) \u2014 uncommitted changes
27032
+
27033
+ Suggested execution order:
27034
+
27035
+ \`\`\`
27036
+ session_read({ session_id: "$SESSION_ID" })
27037
+ todoread()
27038
+ Bash({ command: "git diff --stat HEAD~10..HEAD" })
27039
+ Bash({ command: "git status --porcelain" })
27040
+ \`\`\`
27041
+
27042
+ Analyze the gathered outputs to understand:
27043
+ - What the user asked for (exact wording)
27044
+ - What work was completed
27045
+ - What tasks remain incomplete (include todo state)
27046
+ - What decisions were made
27047
+ - What files were modified or discussed (include git diff/stat + status)
27048
+ - What patterns, constraints, or preferences were established
27049
+
27050
+ ---
27051
+
27052
+ # PHASE 2: EXTRACT CONTEXT
27053
+
27054
+ Write the context summary from first person perspective ("I did...", "I told you...").
27055
+
27056
+ Focus on:
27057
+ - Capabilities and behavior, not file-by-file implementation details
27058
+ - What matters for continuing the work
27059
+ - Avoiding excessive implementation details (variable names, storage keys, constants) unless critical
27060
+ - USER REQUESTS (AS-IS) must be verbatim (do not paraphrase)
27061
+ - EXPLICIT CONSTRAINTS must be verbatim only (do not invent)
27062
+
27063
+ Questions to consider when extracting:
27064
+ - What did I just do or implement?
27065
+ - What instructions did I already give which are still relevant (e.g. follow patterns in the codebase)?
27066
+ - What files did I tell you are important or that I am working on?
27067
+ - Did I provide a plan or spec that should be included?
27068
+ - What did I already tell you that is important (libraries, patterns, constraints, preferences)?
27069
+ - What important technical details did I discover (APIs, methods, patterns)?
27070
+ - What caveats, limitations, or open questions did I find?
27071
+
27072
+ ---
27073
+
27074
+ # PHASE 3: FORMAT OUTPUT
27075
+
27076
+ Generate a handoff summary using this exact format:
27077
+
27078
+ \`\`\`
27079
+ HANDOFF CONTEXT
27080
+ ===============
27081
+
27082
+ USER REQUESTS (AS-IS)
27083
+ ---------------------
27084
+ - [Exact verbatim user requests - NOT paraphrased]
27085
+
27086
+ GOAL
27087
+ ----
27088
+ [One sentence describing what should be done next]
27089
+
27090
+ WORK COMPLETED
27091
+ --------------
27092
+ - [First person bullet points of what was done]
27093
+ - [Include specific file paths when relevant]
27094
+ - [Note key implementation decisions]
27095
+
27096
+ CURRENT STATE
27097
+ -------------
27098
+ - [Current state of the codebase or task]
27099
+ - [Build/test status if applicable]
27100
+ - [Any environment or configuration state]
27101
+
27102
+ PENDING TASKS
27103
+ -------------
27104
+ - [Tasks that were planned but not completed]
27105
+ - [Next logical steps to take]
27106
+ - [Any blockers or issues encountered]
27107
+ - [Include current todo state from todoread()]
27108
+
27109
+ KEY FILES
27110
+ ---------
27111
+ - [path/to/file1] - [brief role description]
27112
+ - [path/to/file2] - [brief role description]
27113
+ (Maximum 10 files, prioritized by importance)
27114
+ - (Include files from git diff/stat and git status)
27115
+
27116
+ IMPORTANT DECISIONS
27117
+ -------------------
27118
+ - [Technical decisions that were made and why]
27119
+ - [Trade-offs that were considered]
27120
+ - [Patterns or conventions established]
27121
+
27122
+ EXPLICIT CONSTRAINTS
27123
+ --------------------
27124
+ - [Verbatim constraints only - from user or existing AGENTS.md]
27125
+ - If none, write: None
27126
+
27127
+ CONTEXT FOR CONTINUATION
27128
+ ------------------------
27129
+ - [What the next session needs to know to continue]
27130
+ - [Warnings or gotchas to be aware of]
27131
+ - [References to documentation if relevant]
27132
+ \`\`\`
27133
+
27134
+ Rules for the summary:
27135
+ - Plain text with bullets
27136
+ - No markdown headers with # (use the format above with dashes)
27137
+ - No bold, italic, or code fences within content
27138
+ - Use workspace-relative paths for files
27139
+ - Keep it focused - only include what matters for continuation
27140
+ - Pick an appropriate length based on complexity
27141
+ - USER REQUESTS (AS-IS) and EXPLICIT CONSTRAINTS must be verbatim only
27142
+
27143
+ ---
27144
+
27145
+ # PHASE 4: PROVIDE INSTRUCTIONS
27146
+
27147
+ After generating the summary, instruct the user:
27148
+
27149
+ \`\`\`
27150
+ ---
27151
+
27152
+ TO CONTINUE IN A NEW SESSION:
27153
+
27154
+ 1. Press 'n' in OpenCode TUI to open a new session, or run 'opencode' in a new terminal
27155
+ 2. Paste the HANDOFF CONTEXT above as your first message
27156
+ 3. Add your request: "Continue from the handoff context above. [Your next task]"
27157
+
27158
+ The new session will have all context needed to continue seamlessly.
27159
+ \`\`\`
27160
+
27161
+ ---
27162
+
27163
+ # IMPORTANT CONSTRAINTS
27164
+
27165
+ - DO NOT attempt to programmatically create new sessions (no API available to agents)
27166
+ - DO provide a self-contained summary that works without access to this session
27167
+ - DO include workspace-relative file paths
27168
+ - DO NOT include sensitive information (API keys, credentials, secrets)
27169
+ - DO NOT exceed 10 files in the KEY FILES section
27170
+ - DO keep the GOAL section to a single sentence or short paragraph
27171
+
27172
+ ---
27173
+
27174
+ # EXECUTE NOW
27175
+
27176
+ Begin by gathering programmatic context, then synthesize the handoff summary.
27177
+ `;
27178
+
26820
27179
  // src/features/builtin-commands/commands.ts
26821
27180
  var BUILTIN_COMMAND_DEFINITIONS = {
26822
27181
  "init-deep": {
@@ -26887,6 +27246,22 @@ $ARGUMENTS
26887
27246
  template: `<command-instruction>
26888
27247
  ${STOP_CONTINUATION_TEMPLATE}
26889
27248
  </command-instruction>`
27249
+ },
27250
+ handoff: {
27251
+ description: "(builtin) Create a detailed context summary for continuing work in a new session",
27252
+ template: `<command-instruction>
27253
+ ${HANDOFF_TEMPLATE}
27254
+ </command-instruction>
27255
+
27256
+ <session-context>
27257
+ Session ID: $SESSION_ID
27258
+ Timestamp: $TIMESTAMP
27259
+ </session-context>
27260
+
27261
+ <user-request>
27262
+ $ARGUMENTS
27263
+ </user-request>`,
27264
+ argumentHint: "[goal]"
26890
27265
  }
26891
27266
  };
26892
27267
  function loadBuiltinCommands(disabledCommands) {
@@ -30128,11 +30503,11 @@ ${contextInfo}`;
30128
30503
  };
30129
30504
  }
30130
30505
  // src/hooks/atlas/index.ts
30131
- import { execSync as execSync2 } from "child_process";
30132
30506
  init_hook_message_injector();
30133
30507
  init_logger();
30134
30508
  init_system_directive();
30135
30509
  init_session_utils();
30510
+ init_git_worktree();
30136
30511
  var HOOK_NAME7 = "atlas";
30137
30512
  function isSisyphusPath(filePath) {
30138
30513
  return /\.sisyphus[/\\]/.test(filePath);
@@ -30372,99 +30747,6 @@ function extractSessionIdFromOutput(output) {
30372
30747
  const match = output.match(/Session ID:\s*(ses_[a-zA-Z0-9]+)/);
30373
30748
  return match?.[1] ?? "<session_id>";
30374
30749
  }
30375
- function getGitDiffStats(directory) {
30376
- try {
30377
- const output = execSync2("git diff --numstat HEAD", {
30378
- cwd: directory,
30379
- encoding: "utf-8",
30380
- timeout: 5000,
30381
- stdio: ["pipe", "pipe", "pipe"]
30382
- }).trim();
30383
- if (!output)
30384
- return [];
30385
- const statusOutput = execSync2("git status --porcelain", {
30386
- cwd: directory,
30387
- encoding: "utf-8",
30388
- timeout: 5000,
30389
- stdio: ["pipe", "pipe", "pipe"]
30390
- }).trim();
30391
- const statusMap = new Map;
30392
- for (const line of statusOutput.split(`
30393
- `)) {
30394
- if (!line)
30395
- continue;
30396
- const status = line.substring(0, 2).trim();
30397
- const filePath = line.substring(3);
30398
- if (status === "A" || status === "??") {
30399
- statusMap.set(filePath, "added");
30400
- } else if (status === "D") {
30401
- statusMap.set(filePath, "deleted");
30402
- } else {
30403
- statusMap.set(filePath, "modified");
30404
- }
30405
- }
30406
- const stats = [];
30407
- for (const line of output.split(`
30408
- `)) {
30409
- const parts = line.split("\t");
30410
- if (parts.length < 3)
30411
- continue;
30412
- const [addedStr, removedStr, path8] = parts;
30413
- const added = addedStr === "-" ? 0 : parseInt(addedStr, 10);
30414
- const removed = removedStr === "-" ? 0 : parseInt(removedStr, 10);
30415
- stats.push({
30416
- path: path8,
30417
- added,
30418
- removed,
30419
- status: statusMap.get(path8) ?? "modified"
30420
- });
30421
- }
30422
- return stats;
30423
- } catch {
30424
- return [];
30425
- }
30426
- }
30427
- function formatFileChanges(stats, notepadPath) {
30428
- if (stats.length === 0)
30429
- return `[FILE CHANGES SUMMARY]
30430
- No file changes detected.
30431
- `;
30432
- const modified = stats.filter((s) => s.status === "modified");
30433
- const added = stats.filter((s) => s.status === "added");
30434
- const deleted = stats.filter((s) => s.status === "deleted");
30435
- const lines = ["[FILE CHANGES SUMMARY]"];
30436
- if (modified.length > 0) {
30437
- lines.push("Modified files:");
30438
- for (const f of modified) {
30439
- lines.push(` ${f.path} (+${f.added}, -${f.removed})`);
30440
- }
30441
- lines.push("");
30442
- }
30443
- if (added.length > 0) {
30444
- lines.push("Created files:");
30445
- for (const f of added) {
30446
- lines.push(` ${f.path} (+${f.added})`);
30447
- }
30448
- lines.push("");
30449
- }
30450
- if (deleted.length > 0) {
30451
- lines.push("Deleted files:");
30452
- for (const f of deleted) {
30453
- lines.push(` ${f.path} (-${f.removed})`);
30454
- }
30455
- lines.push("");
30456
- }
30457
- if (notepadPath) {
30458
- const notepadStat = stats.find((s) => s.path.includes("notepad") || s.path.includes(".sisyphus"));
30459
- if (notepadStat) {
30460
- lines.push("[NOTEPAD UPDATED]");
30461
- lines.push(` ${notepadStat.path} (+${notepadStat.added})`);
30462
- lines.push("");
30463
- }
30464
- }
30465
- return lines.join(`
30466
- `);
30467
- }
30468
30750
  var CONTINUATION_COOLDOWN_MS = 5000;
30469
30751
  function isAbortError(error) {
30470
30752
  if (!error)
@@ -30744,7 +31026,7 @@ function createAtlasHook(ctx, options) {
30744
31026
  return;
30745
31027
  }
30746
31028
  if (output.output && typeof output.output === "string") {
30747
- const gitStats = getGitDiffStats(ctx.directory);
31029
+ const gitStats = collectGitDiffStats(ctx.directory);
30748
31030
  const fileChanges = formatFileChanges(gitStats);
30749
31031
  const subagentSessionId = extractSessionIdFromOutput(output.output);
30750
31032
  const boulderState = readBoulderState(ctx.directory);
@@ -32577,6 +32859,7 @@ class LSPServerManager {
32577
32859
  clients = new Map;
32578
32860
  cleanupInterval = null;
32579
32861
  IDLE_TIMEOUT = 5 * 60 * 1000;
32862
+ INIT_TIMEOUT = 60 * 1000;
32580
32863
  constructor() {
32581
32864
  this.startCleanupTimer();
32582
32865
  this.registerProcessCleanup();
@@ -32641,35 +32924,68 @@ class LSPServerManager {
32641
32924
  async getClient(root, server) {
32642
32925
  const key = this.getKey(root, server.id);
32643
32926
  let managed = this.clients.get(key);
32927
+ if (managed) {
32928
+ const now = Date.now();
32929
+ if (managed.isInitializing && managed.initializingSince !== undefined && now - managed.initializingSince >= this.INIT_TIMEOUT) {
32930
+ try {
32931
+ await managed.client.stop();
32932
+ } catch {}
32933
+ this.clients.delete(key);
32934
+ managed = undefined;
32935
+ }
32936
+ }
32644
32937
  if (managed) {
32645
32938
  if (managed.initPromise) {
32646
- await managed.initPromise;
32939
+ try {
32940
+ await managed.initPromise;
32941
+ } catch {
32942
+ try {
32943
+ await managed.client.stop();
32944
+ } catch {}
32945
+ this.clients.delete(key);
32946
+ managed = undefined;
32947
+ }
32647
32948
  }
32648
- if (managed.client.isAlive()) {
32649
- managed.refCount++;
32650
- managed.lastUsedAt = Date.now();
32651
- return managed.client;
32949
+ if (managed) {
32950
+ if (managed.client.isAlive()) {
32951
+ managed.refCount++;
32952
+ managed.lastUsedAt = Date.now();
32953
+ return managed.client;
32954
+ }
32955
+ try {
32956
+ await managed.client.stop();
32957
+ } catch {}
32958
+ this.clients.delete(key);
32652
32959
  }
32653
- await managed.client.stop();
32654
- this.clients.delete(key);
32655
32960
  }
32656
32961
  const client = new LSPClient(root, server);
32657
32962
  const initPromise3 = (async () => {
32658
32963
  await client.start();
32659
32964
  await client.initialize();
32660
32965
  })();
32966
+ const initStartedAt = Date.now();
32661
32967
  this.clients.set(key, {
32662
32968
  client,
32663
- lastUsedAt: Date.now(),
32969
+ lastUsedAt: initStartedAt,
32664
32970
  refCount: 1,
32665
32971
  initPromise: initPromise3,
32666
- isInitializing: true
32972
+ isInitializing: true,
32973
+ initializingSince: initStartedAt
32667
32974
  });
32668
- await initPromise3;
32975
+ try {
32976
+ await initPromise3;
32977
+ } catch (error) {
32978
+ this.clients.delete(key);
32979
+ try {
32980
+ await client.stop();
32981
+ } catch {}
32982
+ throw error;
32983
+ }
32669
32984
  const m = this.clients.get(key);
32670
32985
  if (m) {
32671
32986
  m.initPromise = undefined;
32672
32987
  m.isInitializing = false;
32988
+ m.initializingSince = undefined;
32673
32989
  }
32674
32990
  return client;
32675
32991
  }
@@ -32682,19 +32998,25 @@ class LSPServerManager {
32682
32998
  await client.start();
32683
32999
  await client.initialize();
32684
33000
  })();
33001
+ const initStartedAt = Date.now();
32685
33002
  this.clients.set(key, {
32686
33003
  client,
32687
- lastUsedAt: Date.now(),
33004
+ lastUsedAt: initStartedAt,
32688
33005
  refCount: 0,
32689
33006
  initPromise: initPromise3,
32690
- isInitializing: true
33007
+ isInitializing: true,
33008
+ initializingSince: initStartedAt
32691
33009
  });
32692
33010
  initPromise3.then(() => {
32693
33011
  const m = this.clients.get(key);
32694
33012
  if (m) {
32695
33013
  m.initPromise = undefined;
32696
33014
  m.isInitializing = false;
33015
+ m.initializingSince = undefined;
32697
33016
  }
33017
+ }).catch(() => {
33018
+ this.clients.delete(key);
33019
+ client.stop().catch(() => {});
32698
33020
  });
32699
33021
  }
32700
33022
  releaseClient(root, serverId) {
@@ -43454,10 +43776,10 @@ function _property(property, schema2, params) {
43454
43776
  ...normalizeParams(params)
43455
43777
  });
43456
43778
  }
43457
- function _mime(types13, params) {
43779
+ function _mime(types14, params) {
43458
43780
  return new $ZodCheckMimeType({
43459
43781
  check: "mime_type",
43460
- mime: types13,
43782
+ mime: types14,
43461
43783
  ...normalizeParams(params)
43462
43784
  });
43463
43785
  }
@@ -45367,7 +45689,7 @@ var ZodFile = /* @__PURE__ */ $constructor("ZodFile", (inst, def) => {
45367
45689
  ZodType.init(inst, def);
45368
45690
  inst.min = (size, params) => inst.check(_minSize(size, params));
45369
45691
  inst.max = (size, params) => inst.check(_maxSize(size, params));
45370
- inst.mime = (types13, params) => inst.check(_mime(Array.isArray(types13) ? types13 : [types13], params));
45692
+ inst.mime = (types14, params) => inst.check(_mime(Array.isArray(types14) ? types14 : [types14], params));
45371
45693
  });
45372
45694
  function file(params) {
45373
45695
  return _file(ZodFile, params);
@@ -48280,10 +48602,9 @@ function createSkillMcpTool(options) {
48280
48602
  var BACKGROUND_OUTPUT_DESCRIPTION = `Get output from background task. Use full_session=true to fetch session messages with filters. System notifies on completion, so block=true rarely needed.`;
48281
48603
  var BACKGROUND_CANCEL_DESCRIPTION = `Cancel running background task(s). Use all=true to cancel ALL before final answer.`;
48282
48604
 
48283
- // src/tools/background-task/tools.ts
48605
+ // src/tools/background-task/modules/background-task.ts
48284
48606
  init_hook_message_injector();
48285
48607
  init_logger();
48286
- init_session_cursor();
48287
48608
 
48288
48609
  // src/features/tool-metadata-store/index.ts
48289
48610
  var pendingStore = new Map;
@@ -48314,9 +48635,8 @@ function consumeToolMetadata(sessionID, callID) {
48314
48635
  return;
48315
48636
  }
48316
48637
 
48317
- // src/tools/background-task/tools.ts
48318
- var MAX_MESSAGE_LIMIT = 100;
48319
- var THINKING_MAX_CHARS = 2000;
48638
+ // src/tools/background-task/modules/utils.ts
48639
+ init_hook_message_injector();
48320
48640
  function formatDuration(start, end) {
48321
48641
  const duration3 = (end ?? new Date).getTime() - start.getTime();
48322
48642
  const seconds = Math.floor(duration3 / 1000);
@@ -48330,14 +48650,70 @@ function formatDuration(start, end) {
48330
48650
  return `${seconds}s`;
48331
48651
  }
48332
48652
  }
48333
- function delay(ms) {
48334
- return new Promise((resolve11) => setTimeout(resolve11, ms));
48335
- }
48336
48653
  function truncateText(text, maxLength) {
48337
48654
  if (text.length <= maxLength)
48338
48655
  return text;
48339
48656
  return text.slice(0, maxLength) + "...";
48340
48657
  }
48658
+ function delay(ms) {
48659
+ return new Promise((resolve11) => setTimeout(resolve11, ms));
48660
+ }
48661
+ function formatMessageTime(value) {
48662
+ if (typeof value === "string") {
48663
+ const date5 = new Date(value);
48664
+ return Number.isNaN(date5.getTime()) ? value : date5.toISOString();
48665
+ }
48666
+ if (typeof value === "object" && value !== null) {
48667
+ if ("created" in value) {
48668
+ const created = value.created;
48669
+ if (typeof created === "number") {
48670
+ return new Date(created).toISOString();
48671
+ }
48672
+ }
48673
+ }
48674
+ return "Unknown time";
48675
+ }
48676
+ // src/tools/background-task/modules/message-processing.ts
48677
+ function getErrorMessage2(value) {
48678
+ if (Array.isArray(value))
48679
+ return null;
48680
+ if (value.error === undefined || value.error === null)
48681
+ return null;
48682
+ if (typeof value.error === "string" && value.error.length > 0)
48683
+ return value.error;
48684
+ return String(value.error);
48685
+ }
48686
+ function isSessionMessage(value) {
48687
+ return typeof value === "object" && value !== null;
48688
+ }
48689
+ function extractMessages2(value) {
48690
+ if (Array.isArray(value)) {
48691
+ return value.filter(isSessionMessage);
48692
+ }
48693
+ if (Array.isArray(value.data)) {
48694
+ return value.data.filter(isSessionMessage);
48695
+ }
48696
+ return [];
48697
+ }
48698
+ function extractToolResultText(part) {
48699
+ if (typeof part.content === "string" && part.content.length > 0) {
48700
+ return [part.content];
48701
+ }
48702
+ if (Array.isArray(part.content)) {
48703
+ const blocks = part.content.filter((block) => (block.type === "text" || block.type === "reasoning") && block.text).map((block) => block.text);
48704
+ if (blocks.length > 0)
48705
+ return blocks;
48706
+ }
48707
+ if (part.output && part.output.length > 0) {
48708
+ return [part.output];
48709
+ }
48710
+ return [];
48711
+ }
48712
+
48713
+ // src/tools/background-task/modules/formatters.ts
48714
+ init_session_cursor();
48715
+ var MAX_MESSAGE_LIMIT = 100;
48716
+ var THINKING_MAX_CHARS = 2000;
48341
48717
  function formatTaskStatus(task) {
48342
48718
  let duration3;
48343
48719
  if (task.status === "pending" && task.queuedAt) {
@@ -48397,27 +48773,6 @@ ${statusNote}
48397
48773
  ${promptPreview}
48398
48774
  \`\`\`${lastMessageSection}`;
48399
48775
  }
48400
- function getErrorMessage2(value) {
48401
- if (Array.isArray(value))
48402
- return null;
48403
- if (value.error === undefined || value.error === null)
48404
- return null;
48405
- if (typeof value.error === "string" && value.error.length > 0)
48406
- return value.error;
48407
- return String(value.error);
48408
- }
48409
- function isSessionMessage(value) {
48410
- return typeof value === "object" && value !== null;
48411
- }
48412
- function extractMessages2(value) {
48413
- if (Array.isArray(value)) {
48414
- return value.filter(isSessionMessage);
48415
- }
48416
- if (Array.isArray(value.data)) {
48417
- return value.data.filter(isSessionMessage);
48418
- }
48419
- return [];
48420
- }
48421
48776
  async function formatTaskResult(task, client2) {
48422
48777
  if (!task.sessionID) {
48423
48778
  return `Error: Task has no sessionID`;
@@ -48508,20 +48863,6 @@ Session ID: ${task.sessionID}
48508
48863
 
48509
48864
  ${textContent || "(No text output)"}`;
48510
48865
  }
48511
- function extractToolResultText(part) {
48512
- if (typeof part.content === "string" && part.content.length > 0) {
48513
- return [part.content];
48514
- }
48515
- if (Array.isArray(part.content)) {
48516
- const blocks = part.content.filter((block) => (block.type === "text" || block.type === "reasoning") && block.text).map((block) => block.text);
48517
- if (blocks.length > 0)
48518
- return blocks;
48519
- }
48520
- if (part.output && part.output.length > 0) {
48521
- return [part.output];
48522
- }
48523
- return [];
48524
- }
48525
48866
  async function formatFullSession(task, client2, options) {
48526
48867
  if (!task.sessionID) {
48527
48868
  return formatTaskStatus(task);
@@ -48615,6 +48956,25 @@ async function formatFullSession(task, client2, options) {
48615
48956
  return lines.join(`
48616
48957
  `);
48617
48958
  }
48959
+
48960
+ // src/tools/background-task/modules/background-output.ts
48961
+ var SISYPHUS_JUNIOR_AGENT = "sisyphus-junior";
48962
+ function resolveToolCallID(ctx) {
48963
+ if (typeof ctx.callID === "string" && ctx.callID.trim() !== "") {
48964
+ return ctx.callID;
48965
+ }
48966
+ if (typeof ctx.callId === "string" && ctx.callId.trim() !== "") {
48967
+ return ctx.callId;
48968
+ }
48969
+ if (typeof ctx.call_id === "string" && ctx.call_id.trim() !== "") {
48970
+ return ctx.call_id;
48971
+ }
48972
+ return;
48973
+ }
48974
+ function formatResolvedTitle(task) {
48975
+ const label = task.agent === SISYPHUS_JUNIOR_AGENT && task.category ? task.category : task.agent;
48976
+ return `${label} - ${task.description}`;
48977
+ }
48618
48978
  function createBackgroundOutput(manager, client2) {
48619
48979
  return tool({
48620
48980
  description: BACKGROUND_OUTPUT_DESCRIPTION,
@@ -48629,12 +48989,29 @@ function createBackgroundOutput(manager, client2) {
48629
48989
  include_tool_results: tool.schema.boolean().optional().describe("Include tool results in full_session output (default: false)"),
48630
48990
  thinking_max_chars: tool.schema.number().optional().describe("Max characters for thinking content (default: 2000)")
48631
48991
  },
48632
- async execute(args) {
48992
+ async execute(args, toolContext) {
48633
48993
  try {
48994
+ const ctx = toolContext;
48634
48995
  const task = manager.getTask(args.task_id);
48635
48996
  if (!task) {
48636
48997
  return `Task not found: ${args.task_id}`;
48637
48998
  }
48999
+ const resolvedTitle = formatResolvedTitle(task);
49000
+ const meta = {
49001
+ title: resolvedTitle,
49002
+ metadata: {
49003
+ task_id: task.id,
49004
+ agent: task.agent,
49005
+ category: task.category,
49006
+ description: task.description,
49007
+ sessionId: task.sessionID ?? "pending"
49008
+ }
49009
+ };
49010
+ await ctx.metadata?.(meta);
49011
+ const callID = resolveToolCallID(ctx);
49012
+ if (callID) {
49013
+ storeToolMetadata(ctx.sessionID, callID, meta);
49014
+ }
48638
49015
  if (args.full_session === true) {
48639
49016
  return await formatFullSession(task, client2, {
48640
49017
  includeThinking: args.include_thinking === true,
@@ -48682,6 +49059,7 @@ ${formatTaskStatus(finalTask)}`;
48682
49059
  }
48683
49060
  });
48684
49061
  }
49062
+ // src/tools/background-task/modules/background-cancel.ts
48685
49063
  function createBackgroundCancel(manager, client2) {
48686
49064
  return tool({
48687
49065
  description: BACKGROUND_CANCEL_DESCRIPTION,
@@ -48774,21 +49152,6 @@ Status: ${task.status}`;
48774
49152
  }
48775
49153
  });
48776
49154
  }
48777
- function formatMessageTime(value) {
48778
- if (typeof value === "string") {
48779
- const date5 = new Date(value);
48780
- return Number.isNaN(date5.getTime()) ? value : date5.toISOString();
48781
- }
48782
- if (typeof value === "object" && value !== null) {
48783
- if ("created" in value) {
48784
- const created = value.created;
48785
- if (typeof created === "number") {
48786
- return new Date(created).toISOString();
48787
- }
48788
- }
48789
- }
48790
- return "Unknown time";
48791
- }
48792
49155
  // src/tools/call-omo-agent/constants.ts
48793
49156
  var ALLOWED_AGENTS = [
48794
49157
  "explore",
@@ -48805,12 +49168,17 @@ Available: {agents}
48805
49168
 
48806
49169
  Pass \`session_id=<id>\` to continue previous agent with full context. Prompts MUST be in English. Use \`background_output\` for async results.`;
48807
49170
  // src/tools/call-omo-agent/tools.ts
48808
- import { existsSync as existsSync49, readdirSync as readdirSync16 } from "fs";
48809
- import { join as join57 } from "path";
48810
49171
  init_shared();
48811
- init_session_cursor();
49172
+
49173
+ // src/tools/call-omo-agent/background-executor.ts
49174
+ init_shared();
49175
+ init_hook_message_injector();
49176
+
49177
+ // src/tools/call-omo-agent/message-dir.ts
48812
49178
  init_hook_message_injector();
48813
- function getMessageDir10(sessionID) {
49179
+ import { existsSync as existsSync49, readdirSync as readdirSync16 } from "fs";
49180
+ import { join as join57 } from "path";
49181
+ function getMessageDir11(sessionID) {
48814
49182
  if (!sessionID.startsWith("ses_"))
48815
49183
  return null;
48816
49184
  if (!existsSync49(MESSAGE_STORAGE))
@@ -48825,40 +49193,11 @@ function getMessageDir10(sessionID) {
48825
49193
  }
48826
49194
  return null;
48827
49195
  }
48828
- function createCallOmoAgent(ctx, backgroundManager) {
48829
- const agentDescriptions = ALLOWED_AGENTS.map((name) => `- ${name}: Specialized agent for ${name} tasks`).join(`
48830
- `);
48831
- const description = CALL_OMO_AGENT_DESCRIPTION.replace("{agents}", agentDescriptions);
48832
- return tool({
48833
- description,
48834
- args: {
48835
- description: tool.schema.string().describe("A short (3-5 words) description of the task"),
48836
- prompt: tool.schema.string().describe("The task for the agent to perform"),
48837
- subagent_type: tool.schema.string().describe("The type of specialized agent to use for this task (explore or librarian only)"),
48838
- run_in_background: tool.schema.boolean().describe("REQUIRED. true: run asynchronously (use background_output to get results), false: run synchronously and wait for completion"),
48839
- session_id: tool.schema.string().describe("Existing Task session to continue").optional()
48840
- },
48841
- async execute(args, toolContext) {
48842
- const toolCtx = toolContext;
48843
- log(`[call_omo_agent] Starting with agent: ${args.subagent_type}, background: ${args.run_in_background}`);
48844
- if (![...ALLOWED_AGENTS].some((name) => name.toLowerCase() === args.subagent_type.toLowerCase())) {
48845
- return `Error: Invalid agent type "${args.subagent_type}". Only ${ALLOWED_AGENTS.join(", ")} are allowed.`;
48846
- }
48847
- const normalizedAgent = args.subagent_type.toLowerCase();
48848
- args = { ...args, subagent_type: normalizedAgent };
48849
- if (args.run_in_background) {
48850
- if (args.session_id) {
48851
- return `Error: session_id is not supported in background mode. Use run_in_background=false to continue an existing session.`;
48852
- }
48853
- return await executeBackground(args, toolCtx, backgroundManager);
48854
- }
48855
- return await executeSync(args, toolCtx, ctx);
48856
- }
48857
- });
48858
- }
49196
+
49197
+ // src/tools/call-omo-agent/background-executor.ts
48859
49198
  async function executeBackground(args, toolContext, manager) {
48860
49199
  try {
48861
- const messageDir = getMessageDir10(toolContext.sessionID);
49200
+ const messageDir = getMessageDir11(toolContext.sessionID);
48862
49201
  const prevMessage = messageDir ? findNearestMessageWithFields(messageDir) : null;
48863
49202
  const firstMessageAgent = messageDir ? findFirstMessageWithAgent(messageDir) : null;
48864
49203
  const sessionAgent = getSessionAgent(toolContext.sessionID);
@@ -48920,8 +49259,14 @@ Use \`background_output\` tool with task_id="${task.id}" to check progress:
48920
49259
  return `Failed to launch background agent task: ${message}`;
48921
49260
  }
48922
49261
  }
48923
- async function executeSync(args, toolContext, ctx) {
48924
- let sessionID;
49262
+
49263
+ // src/tools/call-omo-agent/sync-executor.ts
49264
+ init_shared();
49265
+ init_shared();
49266
+
49267
+ // src/tools/call-omo-agent/session-creator.ts
49268
+ init_shared();
49269
+ async function createOrGetSession(args, toolContext, ctx) {
48925
49270
  if (args.session_id) {
48926
49271
  log(`[call_omo_agent] Using existing session: ${args.session_id}`);
48927
49272
  const sessionResult = await ctx.client.session.get({
@@ -48929,9 +49274,9 @@ async function executeSync(args, toolContext, ctx) {
48929
49274
  });
48930
49275
  if (sessionResult.error) {
48931
49276
  log(`[call_omo_agent] Session get error:`, sessionResult.error);
48932
- return `Error: Failed to get existing session: ${sessionResult.error}`;
49277
+ throw new Error(`Failed to get existing session: ${sessionResult.error}`);
48933
49278
  }
48934
- sessionID = args.session_id;
49279
+ return { sessionID: args.session_id, isNew: false };
48935
49280
  } else {
48936
49281
  log(`[call_omo_agent] Creating new session with parent: ${toolContext.sessionID}`);
48937
49282
  const parentSession = await ctx.client.session.get({
@@ -48958,55 +49303,27 @@ async function executeSync(args, toolContext, ctx) {
48958
49303
  log(`[call_omo_agent] Session create error:`, createResult.error);
48959
49304
  const errorStr = String(createResult.error);
48960
49305
  if (errorStr.toLowerCase().includes("unauthorized")) {
48961
- return `Error: Failed to create session (Unauthorized). This may be due to:
49306
+ throw new Error(`Failed to create session (Unauthorized). This may be due to:
48962
49307
  1. OAuth token restrictions (e.g., Claude Code credentials are restricted to Claude Code only)
48963
49308
  2. Provider authentication issues
48964
49309
  3. Session permission inheritance problems
48965
49310
 
48966
49311
  Try using a different provider or API key authentication.
48967
49312
 
48968
- Original error: ${createResult.error}`;
49313
+ Original error: ${createResult.error}`);
48969
49314
  }
48970
- return `Error: Failed to create session: ${createResult.error}`;
49315
+ throw new Error(`Failed to create session: ${createResult.error}`);
48971
49316
  }
48972
- sessionID = createResult.data.id;
49317
+ const sessionID = createResult.data.id;
48973
49318
  log(`[call_omo_agent] Created session: ${sessionID}`);
49319
+ return { sessionID, isNew: true };
48974
49320
  }
48975
- await toolContext.metadata?.({
48976
- title: args.description,
48977
- metadata: { sessionId: sessionID }
48978
- });
48979
- log(`[call_omo_agent] Sending prompt to session ${sessionID}`);
48980
- log(`[call_omo_agent] Prompt text:`, args.prompt.substring(0, 100));
48981
- try {
48982
- await ctx.client.session.promptAsync({
48983
- path: { id: sessionID },
48984
- body: {
48985
- agent: args.subagent_type,
48986
- tools: {
48987
- ...getAgentToolRestrictions(args.subagent_type),
48988
- task: false
48989
- },
48990
- parts: [{ type: "text", text: args.prompt }]
48991
- }
48992
- });
48993
- } catch (error45) {
48994
- const errorMessage = error45 instanceof Error ? error45.message : String(error45);
48995
- log(`[call_omo_agent] Prompt error:`, errorMessage);
48996
- if (errorMessage.includes("agent.name") || errorMessage.includes("undefined")) {
48997
- return `Error: Agent "${args.subagent_type}" not found. Make sure the agent is registered in your opencode.json or provided by a plugin.
48998
-
48999
- <task_metadata>
49000
- session_id: ${sessionID}
49001
- </task_metadata>`;
49002
- }
49003
- return `Error: Failed to send prompt: ${errorMessage}
49321
+ }
49004
49322
 
49005
- <task_metadata>
49006
- session_id: ${sessionID}
49007
- </task_metadata>`;
49008
- }
49009
- log(`[call_omo_agent] Prompt sent, polling for completion...`);
49323
+ // src/tools/call-omo-agent/completion-poller.ts
49324
+ init_shared();
49325
+ async function waitForCompletion(sessionID, toolContext, ctx) {
49326
+ log(`[call_omo_agent] Polling for completion...`);
49010
49327
  const POLL_INTERVAL_MS = 500;
49011
49328
  const MAX_POLL_TIME_MS = 5 * 60 * 1000;
49012
49329
  const pollStart = Date.now();
@@ -49016,11 +49333,7 @@ session_id: ${sessionID}
49016
49333
  while (Date.now() - pollStart < MAX_POLL_TIME_MS) {
49017
49334
  if (toolContext.abort?.aborted) {
49018
49335
  log(`[call_omo_agent] Aborted by user`);
49019
- return `Task aborted.
49020
-
49021
- <task_metadata>
49022
- session_id: ${sessionID}
49023
- </task_metadata>`;
49336
+ throw new Error("Task aborted.");
49024
49337
  }
49025
49338
  await new Promise((resolve11) => setTimeout(resolve11, POLL_INTERVAL_MS));
49026
49339
  const statusResult = await ctx.client.session.status();
@@ -49047,18 +49360,20 @@ session_id: ${sessionID}
49047
49360
  }
49048
49361
  if (Date.now() - pollStart >= MAX_POLL_TIME_MS) {
49049
49362
  log(`[call_omo_agent] Timeout reached`);
49050
- return `Error: Agent task timed out after 5 minutes.
49051
-
49052
- <task_metadata>
49053
- session_id: ${sessionID}
49054
- </task_metadata>`;
49363
+ throw new Error("Agent task timed out after 5 minutes.");
49055
49364
  }
49365
+ }
49366
+
49367
+ // src/tools/call-omo-agent/message-processor.ts
49368
+ init_shared();
49369
+ init_session_cursor();
49370
+ async function processMessages(sessionID, ctx) {
49056
49371
  const messagesResult = await ctx.client.session.messages({
49057
49372
  path: { id: sessionID }
49058
49373
  });
49059
49374
  if (messagesResult.error) {
49060
49375
  log(`[call_omo_agent] Messages error:`, messagesResult.error);
49061
- return `Error: Failed to get messages: ${messagesResult.error}`;
49376
+ throw new Error(`Failed to get messages: ${messagesResult.error}`);
49062
49377
  }
49063
49378
  const messages = messagesResult.data;
49064
49379
  log(`[call_omo_agent] Got ${messages.length} messages`);
@@ -49066,11 +49381,7 @@ session_id: ${sessionID}
49066
49381
  if (relevantMessages.length === 0) {
49067
49382
  log(`[call_omo_agent] No assistant or tool messages found`);
49068
49383
  log(`[call_omo_agent] All messages:`, JSON.stringify(messages, null, 2));
49069
- return `Error: No assistant or tool response found
49070
-
49071
- <task_metadata>
49072
- session_id: ${sessionID}
49073
- </task_metadata>`;
49384
+ throw new Error("No assistant or tool response found");
49074
49385
  }
49075
49386
  log(`[call_omo_agent] Found ${relevantMessages.length} relevant messages`);
49076
49387
  const sortedMessages = [...relevantMessages].sort((a, b) => {
@@ -49080,11 +49391,7 @@ session_id: ${sessionID}
49080
49391
  });
49081
49392
  const newMessages = consumeNewMessages(sessionID, sortedMessages);
49082
49393
  if (newMessages.length === 0) {
49083
- return `No new output since last check.
49084
-
49085
- <task_metadata>
49086
- session_id: ${sessionID}
49087
- </task_metadata>`;
49394
+ return "No new output since last check.";
49088
49395
  }
49089
49396
  const extractedContent = [];
49090
49397
  for (const message of newMessages) {
@@ -49109,12 +49416,87 @@ session_id: ${sessionID}
49109
49416
 
49110
49417
  `);
49111
49418
  log(`[call_omo_agent] Got response, length: ${responseText.length}`);
49419
+ return responseText;
49420
+ }
49421
+
49422
+ // src/tools/call-omo-agent/sync-executor.ts
49423
+ async function executeSync(args, toolContext, ctx) {
49424
+ const { sessionID } = await createOrGetSession(args, toolContext, ctx);
49425
+ await toolContext.metadata?.({
49426
+ title: args.description,
49427
+ metadata: { sessionId: sessionID }
49428
+ });
49429
+ log(`[call_omo_agent] Sending prompt to session ${sessionID}`);
49430
+ log(`[call_omo_agent] Prompt text:`, args.prompt.substring(0, 100));
49431
+ try {
49432
+ await ctx.client.session.promptAsync({
49433
+ path: { id: sessionID },
49434
+ body: {
49435
+ agent: args.subagent_type,
49436
+ tools: {
49437
+ ...getAgentToolRestrictions(args.subagent_type),
49438
+ task: false
49439
+ },
49440
+ parts: [{ type: "text", text: args.prompt }]
49441
+ }
49442
+ });
49443
+ } catch (error45) {
49444
+ const errorMessage = error45 instanceof Error ? error45.message : String(error45);
49445
+ log(`[call_omo_agent] Prompt error:`, errorMessage);
49446
+ if (errorMessage.includes("agent.name") || errorMessage.includes("undefined")) {
49447
+ return `Error: Agent "${args.subagent_type}" not found. Make sure the agent is registered in your opencode.json or provided by a plugin.
49448
+
49449
+ <task_metadata>
49450
+ session_id: ${sessionID}
49451
+ </task_metadata>`;
49452
+ }
49453
+ return `Error: Failed to send prompt: ${errorMessage}
49454
+
49455
+ <task_metadata>
49456
+ session_id: ${sessionID}
49457
+ </task_metadata>`;
49458
+ }
49459
+ await waitForCompletion(sessionID, toolContext, ctx);
49460
+ const responseText = await processMessages(sessionID, ctx);
49112
49461
  const output = responseText + `
49113
49462
 
49114
49463
  ` + ["<task_metadata>", `session_id: ${sessionID}`, "</task_metadata>"].join(`
49115
49464
  `);
49116
49465
  return output;
49117
49466
  }
49467
+
49468
+ // src/tools/call-omo-agent/tools.ts
49469
+ function createCallOmoAgent(ctx, backgroundManager) {
49470
+ const agentDescriptions = ALLOWED_AGENTS.map((name) => `- ${name}: Specialized agent for ${name} tasks`).join(`
49471
+ `);
49472
+ const description = CALL_OMO_AGENT_DESCRIPTION.replace("{agents}", agentDescriptions);
49473
+ return tool({
49474
+ description,
49475
+ args: {
49476
+ description: tool.schema.string().describe("A short (3-5 words) description of the task"),
49477
+ prompt: tool.schema.string().describe("The task for the agent to perform"),
49478
+ subagent_type: tool.schema.string().describe("The type of specialized agent to use for this task (explore or librarian only)"),
49479
+ run_in_background: tool.schema.boolean().describe("REQUIRED. true: run asynchronously (use background_output to get results), false: run synchronously and wait for completion"),
49480
+ session_id: tool.schema.string().describe("Existing Task session to continue").optional()
49481
+ },
49482
+ async execute(args, toolContext) {
49483
+ const toolCtx = toolContext;
49484
+ log(`[call_omo_agent] Starting with agent: ${args.subagent_type}, background: ${args.run_in_background}`);
49485
+ if (![...ALLOWED_AGENTS].some((name) => name.toLowerCase() === args.subagent_type.toLowerCase())) {
49486
+ return `Error: Invalid agent type "${args.subagent_type}". Only ${ALLOWED_AGENTS.join(", ")} are allowed.`;
49487
+ }
49488
+ const normalizedAgent = args.subagent_type.toLowerCase();
49489
+ args = { ...args, subagent_type: normalizedAgent };
49490
+ if (args.run_in_background) {
49491
+ if (args.session_id) {
49492
+ return `Error: session_id is not supported in background mode. Use run_in_background=false to continue an existing session.`;
49493
+ }
49494
+ return await executeBackground(args, toolCtx, backgroundManager);
49495
+ }
49496
+ return await executeSync(args, toolCtx, ctx);
49497
+ }
49498
+ });
49499
+ }
49118
49500
  // src/tools/look-at/constants.ts
49119
49501
  var MULTIMODAL_LOOKER_AGENT = "multimodal-looker";
49120
49502
  var LOOK_AT_DESCRIPTION = `Analyze media files (PDFs, images, diagrams) that require interpretation beyond raw text. Extracts specific information or summaries from documents, describes visual content. Use when you need analyzed/extracted data rather than literal file contents.`;
@@ -49426,7 +49808,7 @@ function parseModelString(model) {
49426
49808
  }
49427
49809
  return;
49428
49810
  }
49429
- function getMessageDir11(sessionID) {
49811
+ function getMessageDir12(sessionID) {
49430
49812
  if (!sessionID.startsWith("ses_"))
49431
49813
  return null;
49432
49814
  if (!existsSync50(MESSAGE_STORAGE))
@@ -49692,7 +50074,19 @@ init_shared();
49692
50074
  init_model_availability();
49693
50075
  init_connected_providers_cache();
49694
50076
  init_model_requirements();
49695
- var SISYPHUS_JUNIOR_AGENT = "sisyphus-junior";
50077
+ var SISYPHUS_JUNIOR_AGENT2 = "sisyphus-junior";
50078
+ function resolveToolCallID2(ctx) {
50079
+ if (typeof ctx.callID === "string" && ctx.callID.trim() !== "") {
50080
+ return ctx.callID;
50081
+ }
50082
+ if (typeof ctx.callId === "string" && ctx.callId.trim() !== "") {
50083
+ return ctx.callId;
50084
+ }
50085
+ if (typeof ctx.call_id === "string" && ctx.call_id.trim() !== "") {
50086
+ return ctx.call_id;
50087
+ }
50088
+ return;
50089
+ }
49696
50090
  async function resolveSkillContent(skills, options) {
49697
50091
  if (skills.length === 0) {
49698
50092
  return { content: undefined, error: null };
@@ -49708,7 +50102,7 @@ async function resolveSkillContent(skills, options) {
49708
50102
  `), error: null };
49709
50103
  }
49710
50104
  function resolveParentContext(ctx) {
49711
- const messageDir = getMessageDir11(ctx.sessionID);
50105
+ const messageDir = getMessageDir12(ctx.sessionID);
49712
50106
  const prevMessage = messageDir ? findNearestMessageWithFields(messageDir) : null;
49713
50107
  const firstMessageAgent = messageDir ? findFirstMessageWithAgent(messageDir) : null;
49714
50108
  const sessionAgent = getSessionAgent(ctx.sessionID);
@@ -49758,9 +50152,9 @@ async function executeBackgroundContinuation(args, ctx, executorCtx, parentConte
49758
50152
  }
49759
50153
  };
49760
50154
  await ctx.metadata?.(bgContMeta);
49761
- if (ctx.callID) {
49762
- storeToolMetadata(ctx.sessionID, ctx.callID, bgContMeta);
49763
- }
50155
+ const bgContCallID = resolveToolCallID2(ctx);
50156
+ if (bgContCallID)
50157
+ storeToolMetadata(ctx.sessionID, bgContCallID, bgContMeta);
49764
50158
  return `Background task continued.
49765
50159
 
49766
50160
  Task ID: ${task.id}
@@ -49808,12 +50202,13 @@ async function executeSyncContinuation(args, ctx, executorCtx) {
49808
50202
  }
49809
50203
  };
49810
50204
  await ctx.metadata?.(syncContMeta);
49811
- if (ctx.callID) {
49812
- storeToolMetadata(ctx.sessionID, ctx.callID, syncContMeta);
49813
- }
50205
+ const syncContCallID = resolveToolCallID2(ctx);
50206
+ if (syncContCallID)
50207
+ storeToolMetadata(ctx.sessionID, syncContCallID, syncContMeta);
49814
50208
  try {
49815
50209
  let resumeAgent;
49816
50210
  let resumeModel;
50211
+ let resumeVariant;
49817
50212
  try {
49818
50213
  const messagesResp = await client2.session.messages({ path: { id: args.session_id } });
49819
50214
  const messages2 = messagesResp.data ?? [];
@@ -49822,20 +50217,23 @@ async function executeSyncContinuation(args, ctx, executorCtx) {
49822
50217
  if (info?.agent || info?.model || info?.modelID && info?.providerID) {
49823
50218
  resumeAgent = info.agent;
49824
50219
  resumeModel = info.model ?? (info.providerID && info.modelID ? { providerID: info.providerID, modelID: info.modelID } : undefined);
50220
+ resumeVariant = info.variant;
49825
50221
  break;
49826
50222
  }
49827
50223
  }
49828
50224
  } catch {
49829
- const resumeMessageDir = getMessageDir11(args.session_id);
50225
+ const resumeMessageDir = getMessageDir12(args.session_id);
49830
50226
  const resumeMessage = resumeMessageDir ? findNearestMessageWithFields(resumeMessageDir) : null;
49831
50227
  resumeAgent = resumeMessage?.agent;
49832
50228
  resumeModel = resumeMessage?.model?.providerID && resumeMessage?.model?.modelID ? { providerID: resumeMessage.model.providerID, modelID: resumeMessage.model.modelID } : undefined;
50229
+ resumeVariant = resumeMessage?.model?.variant;
49833
50230
  }
49834
- await client2.session.promptAsync({
50231
+ await promptSyncWithModelSuggestionRetry(client2, {
49835
50232
  path: { id: args.session_id },
49836
50233
  body: {
49837
50234
  ...resumeAgent !== undefined ? { agent: resumeAgent } : {},
49838
50235
  ...resumeModel !== undefined ? { model: resumeModel } : {},
50236
+ ...resumeVariant !== undefined ? { variant: resumeVariant } : {},
49839
50237
  tools: {
49840
50238
  ...resumeAgent ? getAgentToolRestrictions(resumeAgent) : {},
49841
50239
  task: false,
@@ -49854,27 +50252,6 @@ async function executeSyncContinuation(args, ctx, executorCtx) {
49854
50252
 
49855
50253
  Session ID: ${args.session_id}`;
49856
50254
  }
49857
- const timing = getTimingConfig();
49858
- const pollStart = Date.now();
49859
- let lastMsgCount = 0;
49860
- let stablePolls = 0;
49861
- while (Date.now() - pollStart < 60000) {
49862
- await new Promise((resolve11) => setTimeout(resolve11, timing.POLL_INTERVAL_MS));
49863
- const elapsed = Date.now() - pollStart;
49864
- if (elapsed < timing.SESSION_CONTINUATION_STABILITY_MS)
49865
- continue;
49866
- const messagesCheck = await client2.session.messages({ path: { id: args.session_id } });
49867
- const msgs = messagesCheck.data ?? messagesCheck;
49868
- const currentMsgCount = msgs.length;
49869
- if (currentMsgCount > 0 && currentMsgCount === lastMsgCount) {
49870
- stablePolls++;
49871
- if (stablePolls >= timing.STABILITY_POLLS_REQUIRED)
49872
- break;
49873
- } else {
49874
- stablePolls = 0;
49875
- lastMsgCount = currentMsgCount;
49876
- }
49877
- }
49878
50255
  const messagesResult = await client2.session.messages({
49879
50256
  path: { id: args.session_id }
49880
50257
  });
@@ -49962,9 +50339,9 @@ Task ID: ${task.id}`;
49962
50339
  }
49963
50340
  };
49964
50341
  await ctx.metadata?.(bgTaskMeta);
49965
- if (ctx.callID) {
49966
- storeToolMetadata(ctx.sessionID, ctx.callID, bgTaskMeta);
49967
- }
50342
+ const bgTaskCallID = resolveToolCallID2(ctx);
50343
+ if (bgTaskCallID)
50344
+ storeToolMetadata(ctx.sessionID, bgTaskCallID, bgTaskMeta);
49968
50345
  const startTime = new Date;
49969
50346
  const timingCfg = getTimingConfig();
49970
50347
  const pollStart = Date.now();
@@ -50087,9 +50464,9 @@ Task ID: ${task.id}`;
50087
50464
  }
50088
50465
  };
50089
50466
  await ctx.metadata?.(unstableMeta);
50090
- if (ctx.callID) {
50091
- storeToolMetadata(ctx.sessionID, ctx.callID, unstableMeta);
50092
- }
50467
+ const unstableCallID = resolveToolCallID2(ctx);
50468
+ if (unstableCallID)
50469
+ storeToolMetadata(ctx.sessionID, unstableCallID, unstableMeta);
50093
50470
  return `Background task launched.
50094
50471
 
50095
50472
  Task ID: ${task.id}
@@ -50176,12 +50553,12 @@ async function executeSyncTask(args, ctx, executorCtx, parentContext, agentToUse
50176
50553
  }
50177
50554
  };
50178
50555
  await ctx.metadata?.(syncTaskMeta);
50179
- if (ctx.callID) {
50180
- storeToolMetadata(ctx.sessionID, ctx.callID, syncTaskMeta);
50181
- }
50556
+ const syncTaskCallID = resolveToolCallID2(ctx);
50557
+ if (syncTaskCallID)
50558
+ storeToolMetadata(ctx.sessionID, syncTaskCallID, syncTaskMeta);
50182
50559
  try {
50183
- const allowTask = isPlanAgent(agentToUse);
50184
- await promptWithModelSuggestionRetry(client2, {
50560
+ const allowTask = isPlanFamily(agentToUse);
50561
+ await promptSyncWithModelSuggestionRetry(client2, {
50185
50562
  path: { id: sessionID },
50186
50563
  body: {
50187
50564
  agent: agentToUse,
@@ -50218,62 +50595,6 @@ async function executeSyncTask(args, ctx, executorCtx, parentContext, agentToUse
50218
50595
  category: args.category
50219
50596
  });
50220
50597
  }
50221
- const syncTiming = getTimingConfig();
50222
- const pollStart = Date.now();
50223
- let lastMsgCount = 0;
50224
- let stablePolls = 0;
50225
- let pollCount = 0;
50226
- log("[task] Starting poll loop", { sessionID, agentToUse });
50227
- while (Date.now() - pollStart < syncTiming.MAX_POLL_TIME_MS) {
50228
- if (ctx.abort?.aborted) {
50229
- log("[task] Aborted by user", { sessionID });
50230
- if (toastManager && taskId)
50231
- toastManager.removeTask(taskId);
50232
- return `Task aborted.
50233
-
50234
- Session ID: ${sessionID}`;
50235
- }
50236
- await new Promise((resolve11) => setTimeout(resolve11, syncTiming.POLL_INTERVAL_MS));
50237
- pollCount++;
50238
- const statusResult = await client2.session.status();
50239
- const allStatuses = statusResult.data ?? {};
50240
- const sessionStatus = allStatuses[sessionID];
50241
- if (pollCount % 10 === 0) {
50242
- log("[task] Poll status", {
50243
- sessionID,
50244
- pollCount,
50245
- elapsed: Math.floor((Date.now() - pollStart) / 1000) + "s",
50246
- sessionStatus: sessionStatus?.type ?? "not_in_status",
50247
- stablePolls,
50248
- lastMsgCount
50249
- });
50250
- }
50251
- if (sessionStatus && sessionStatus.type !== "idle") {
50252
- stablePolls = 0;
50253
- lastMsgCount = 0;
50254
- continue;
50255
- }
50256
- const elapsed = Date.now() - pollStart;
50257
- if (elapsed < syncTiming.MIN_STABILITY_TIME_MS) {
50258
- continue;
50259
- }
50260
- const messagesCheck = await client2.session.messages({ path: { id: sessionID } });
50261
- const msgs = messagesCheck.data ?? messagesCheck;
50262
- const currentMsgCount = msgs.length;
50263
- if (currentMsgCount === lastMsgCount) {
50264
- stablePolls++;
50265
- if (stablePolls >= syncTiming.STABILITY_POLLS_REQUIRED) {
50266
- log("[task] Poll complete - messages stable", { sessionID, pollCount, currentMsgCount });
50267
- break;
50268
- }
50269
- } else {
50270
- stablePolls = 0;
50271
- lastMsgCount = currentMsgCount;
50272
- }
50273
- }
50274
- if (Date.now() - pollStart >= syncTiming.MAX_POLL_TIME_MS) {
50275
- log("[task] Poll timeout reached", { sessionID, pollCount, lastMsgCount, stablePolls });
50276
- }
50277
50598
  const messagesResult = await client2.session.messages({
50278
50599
  path: { id: sessionID }
50279
50600
  });
@@ -50327,8 +50648,8 @@ session_id: ${sessionID}
50327
50648
  }
50328
50649
  async function resolveCategoryExecution(args, executorCtx, inheritedModel, systemDefaultModel) {
50329
50650
  const { client: client2, userCategories, sisyphusJuniorModel } = executorCtx;
50330
- const connectedProviders = readConnectedProvidersCache();
50331
- const availableModels = await fetchAvailableModels(client2, {
50651
+ const connectedProviders = executorCtx.connectedProvidersOverride !== undefined ? executorCtx.connectedProvidersOverride : readConnectedProvidersCache();
50652
+ const availableModels = executorCtx.availableModelsOverride !== undefined ? executorCtx.availableModelsOverride : await fetchAvailableModels(client2, {
50332
50653
  connectedProviders: connectedProviders ?? undefined
50333
50654
  });
50334
50655
  const resolved = resolveCategoryConfig(args.category, {
@@ -50365,7 +50686,7 @@ async function resolveCategoryExecution(args, executorCtx, inheritedModel, syste
50365
50686
  userModel: explicitCategoryModel ?? overrideModel,
50366
50687
  categoryDefaultModel: resolved.model
50367
50688
  },
50368
- constraints: { availableModels },
50689
+ constraints: { availableModels, connectedProviders },
50369
50690
  policy: {
50370
50691
  fallbackChain: requirement.fallbackChain,
50371
50692
  systemDefaultModel
@@ -50433,7 +50754,7 @@ Available categories: ${categoryNames.join(", ")}`
50433
50754
  const unstableModel = actualModel?.toLowerCase();
50434
50755
  const isUnstableAgent = resolved.config.is_unstable_agent === true || (unstableModel ? unstableModel.includes("gemini") || unstableModel.includes("minimax") : false);
50435
50756
  return {
50436
- agentToUse: SISYPHUS_JUNIOR_AGENT,
50757
+ agentToUse: SISYPHUS_JUNIOR_AGENT2,
50437
50758
  categoryModel,
50438
50759
  categoryPromptAppend,
50439
50760
  modelInfo,
@@ -50442,25 +50763,25 @@ Available categories: ${categoryNames.join(", ")}`
50442
50763
  };
50443
50764
  }
50444
50765
  async function resolveSubagentExecution(args, executorCtx, parentAgent, categoryExamples) {
50445
- const { client: client2 } = executorCtx;
50766
+ const { client: client2, agentOverrides } = executorCtx;
50446
50767
  if (!args.subagent_type?.trim()) {
50447
50768
  return { agentToUse: "", categoryModel: undefined, error: `Agent name cannot be empty.` };
50448
50769
  }
50449
50770
  const agentName = args.subagent_type.trim();
50450
- if (agentName.toLowerCase() === SISYPHUS_JUNIOR_AGENT.toLowerCase()) {
50771
+ if (agentName.toLowerCase() === SISYPHUS_JUNIOR_AGENT2.toLowerCase()) {
50451
50772
  return {
50452
50773
  agentToUse: "",
50453
50774
  categoryModel: undefined,
50454
- error: `Cannot use subagent_type="${SISYPHUS_JUNIOR_AGENT}" directly. Use category parameter instead (e.g., ${categoryExamples}).
50775
+ error: `Cannot use subagent_type="${SISYPHUS_JUNIOR_AGENT2}" directly. Use category parameter instead (e.g., ${categoryExamples}).
50455
50776
 
50456
50777
  Sisyphus-Junior is spawned automatically when you specify a category. Pick the appropriate category for your task domain.`
50457
50778
  };
50458
50779
  }
50459
- if (isPlanAgent(agentName) && isPlanAgent(parentAgent)) {
50780
+ if (isPlanFamily(agentName) && isPlanFamily(parentAgent)) {
50460
50781
  return {
50461
50782
  agentToUse: "",
50462
50783
  categoryModel: undefined,
50463
- error: `You are prometheus. You cannot delegate to prometheus via task.
50784
+ error: `You are a plan-family agent (plan/prometheus). You cannot delegate to other plan-family agents via task.
50464
50785
 
50465
50786
  Create the work plan directly - that's your job as the planning agent.`
50466
50787
  };
@@ -50489,7 +50810,34 @@ Create the work plan directly - that's your job as the planning agent.`
50489
50810
  };
50490
50811
  }
50491
50812
  agentToUse = matchedAgent.name;
50492
- if (matchedAgent.model) {
50813
+ const agentNameLower = agentToUse.toLowerCase();
50814
+ const agentOverride = agentOverrides?.[agentNameLower] ?? (agentOverrides ? Object.entries(agentOverrides).find(([key]) => key.toLowerCase() === agentNameLower)?.[1] : undefined);
50815
+ const agentRequirement = AGENT_MODEL_REQUIREMENTS[agentNameLower];
50816
+ if (agentOverride?.model || agentRequirement) {
50817
+ const connectedProviders = executorCtx.connectedProvidersOverride !== undefined ? executorCtx.connectedProvidersOverride : readConnectedProvidersCache();
50818
+ const availableModels = executorCtx.availableModelsOverride !== undefined ? executorCtx.availableModelsOverride : await fetchAvailableModels(client2, {
50819
+ connectedProviders: connectedProviders ?? undefined
50820
+ });
50821
+ const matchedAgentModelStr = matchedAgent.model ? `${matchedAgent.model.providerID}/${matchedAgent.model.modelID}` : undefined;
50822
+ const resolution = resolveModelPipeline({
50823
+ intent: {
50824
+ userModel: agentOverride?.model,
50825
+ categoryDefaultModel: matchedAgentModelStr
50826
+ },
50827
+ constraints: { availableModels, connectedProviders },
50828
+ policy: {
50829
+ fallbackChain: agentRequirement?.fallbackChain,
50830
+ systemDefaultModel: undefined
50831
+ }
50832
+ });
50833
+ if (resolution) {
50834
+ const parsed = parseModelString(resolution.model);
50835
+ if (parsed) {
50836
+ const variantToUse = agentOverride?.variant ?? resolution.variant;
50837
+ categoryModel = variantToUse ? { ...parsed, variant: variantToUse } : parsed;
50838
+ }
50839
+ }
50840
+ } else if (matchedAgent.model) {
50493
50841
  categoryModel = matchedAgent.model;
50494
50842
  }
50495
50843
  } catch {}
@@ -50736,6 +51084,9 @@ function resolveTaskListId(config3 = {}) {
50736
51084
  const envId = process.env.ULTRAWORK_TASK_LIST_ID?.trim();
50737
51085
  if (envId)
50738
51086
  return sanitizePathSegment(envId);
51087
+ const claudeEnvId = process.env.CLAUDE_CODE_TASK_LIST_ID?.trim();
51088
+ if (claudeEnvId)
51089
+ return sanitizePathSegment(claudeEnvId);
50739
51090
  const configId = config3.sisyphus?.tasks?.task_list_id?.trim();
50740
51091
  if (configId)
50741
51092
  return sanitizePathSegment(configId);
@@ -51345,6 +51696,7 @@ class BackgroundManager {
51345
51696
  processingKeys = new Set;
51346
51697
  completionTimers = new Map;
51347
51698
  idleDeferralTimers = new Map;
51699
+ notificationQueueByParent = new Map;
51348
51700
  constructor(ctx, config3, options) {
51349
51701
  this.tasks = new Map;
51350
51702
  this.notifications = new Map;
@@ -51452,13 +51804,14 @@ class BackgroundManager {
51452
51804
  });
51453
51805
  const parentDirectory = parentSession?.data?.directory ?? this.directory;
51454
51806
  log(`[background-agent] Parent dir: ${parentSession?.data?.directory}, using: ${parentDirectory}`);
51807
+ const inheritedPermission = parentSession?.data?.permission;
51808
+ const permissionRules = Array.isArray(inheritedPermission) ? inheritedPermission.filter((r) => r?.permission !== "question") : [];
51809
+ permissionRules.push({ permission: "question", action: "deny", pattern: "*" });
51455
51810
  const createResult = await this.client.session.create({
51456
51811
  body: {
51457
51812
  parentID: input.parentSessionID,
51458
51813
  title: `${input.description} (@${input.agent} subagent)`,
51459
- permission: [
51460
- { permission: "question", action: "deny", pattern: "*" }
51461
- ]
51814
+ permission: permissionRules
51462
51815
  },
51463
51816
  query: {
51464
51817
  directory: parentDirectory
@@ -51553,7 +51906,7 @@ class BackgroundManager {
51553
51906
  }).catch(() => {});
51554
51907
  this.markForNotification(existingTask);
51555
51908
  this.cleanupPendingByParent(existingTask);
51556
- this.notifyParentSession(existingTask).catch((err) => {
51909
+ this.enqueueNotificationForParent(existingTask.parentSessionID, () => this.notifyParentSession(existingTask)).catch((err) => {
51557
51910
  log("[background-agent] Failed to notify on error:", err);
51558
51911
  });
51559
51912
  }
@@ -51747,7 +52100,7 @@ class BackgroundManager {
51747
52100
  }
51748
52101
  this.markForNotification(existingTask);
51749
52102
  this.cleanupPendingByParent(existingTask);
51750
- this.notifyParentSession(existingTask).catch((err) => {
52103
+ this.enqueueNotificationForParent(existingTask.parentSessionID, () => this.notifyParentSession(existingTask)).catch((err) => {
51751
52104
  log("[background-agent] Failed to notify on resume error:", err);
51752
52105
  });
51753
52106
  });
@@ -52000,7 +52353,7 @@ class BackgroundManager {
52000
52353
  }
52001
52354
  this.markForNotification(task);
52002
52355
  try {
52003
- await this.notifyParentSession(task);
52356
+ await this.enqueueNotificationForParent(task.parentSessionID, () => this.notifyParentSession(task));
52004
52357
  log(`[background-agent] Task cancelled via ${source}:`, task.id);
52005
52358
  } catch (err) {
52006
52359
  log("[background-agent] Error in notifyParentSession for cancelled task:", { taskId: task.id, error: err });
@@ -52095,7 +52448,7 @@ class BackgroundManager {
52095
52448
  }).catch(() => {});
52096
52449
  }
52097
52450
  try {
52098
- await this.notifyParentSession(task);
52451
+ await this.enqueueNotificationForParent(task.parentSessionID, () => this.notifyParentSession(task));
52099
52452
  log(`[background-agent] Task completed via ${source}:`, task.id);
52100
52453
  } catch (err) {
52101
52454
  log("[background-agent] Error in notifyParentSession:", { taskId: task.id, error: err });
@@ -52114,14 +52467,18 @@ class BackgroundManager {
52114
52467
  });
52115
52468
  }
52116
52469
  const pendingSet = this.pendingByParent.get(task.parentSessionID);
52470
+ let allComplete = false;
52471
+ let remainingCount = 0;
52117
52472
  if (pendingSet) {
52118
52473
  pendingSet.delete(task.id);
52119
- if (pendingSet.size === 0) {
52474
+ remainingCount = pendingSet.size;
52475
+ allComplete = remainingCount === 0;
52476
+ if (allComplete) {
52120
52477
  this.pendingByParent.delete(task.parentSessionID);
52121
52478
  }
52479
+ } else {
52480
+ allComplete = true;
52122
52481
  }
52123
- const allComplete = !pendingSet || pendingSet.size === 0;
52124
- const remainingCount = pendingSet?.size ?? 0;
52125
52482
  const statusText = task.status === "completed" ? "COMPLETED" : "CANCELLED";
52126
52483
  const errorInfo = task.error ? `
52127
52484
  **Error:** ${task.error}` : "";
@@ -52173,7 +52530,7 @@ Use \`background_output(task_id="${task.id}")\` to retrieve this result when rea
52173
52530
  });
52174
52531
  return;
52175
52532
  }
52176
- const messageDir = getMessageDir12(task.parentSessionID);
52533
+ const messageDir = getMessageDir13(task.parentSessionID);
52177
52534
  const currentMessage = messageDir ? findNearestMessageWithFields(messageDir) : null;
52178
52535
  agent = currentMessage?.agent ?? task.parentAgent;
52179
52536
  model = currentMessage?.model?.providerID && currentMessage?.model?.modelID ? { providerID: currentMessage.model.providerID, modelID: currentMessage.model.modelID } : undefined;
@@ -52346,7 +52703,7 @@ Use \`background_output(task_id="${task.id}")\` to retrieve this result when rea
52346
52703
  }).catch(() => {});
52347
52704
  log(`[background-agent] Task ${task.id} interrupted: stale timeout`);
52348
52705
  try {
52349
- await this.notifyParentSession(task);
52706
+ await this.enqueueNotificationForParent(task.parentSessionID, () => this.notifyParentSession(task));
52350
52707
  } catch (err) {
52351
52708
  log("[background-agent] Error in notifyParentSession for stale task:", { taskId: task.id, error: err });
52352
52709
  }
@@ -52497,11 +52854,26 @@ Use \`background_output(task_id="${task.id}")\` to retrieve this result when rea
52497
52854
  this.tasks.clear();
52498
52855
  this.notifications.clear();
52499
52856
  this.pendingByParent.clear();
52857
+ this.notificationQueueByParent.clear();
52500
52858
  this.queuesByKey.clear();
52501
52859
  this.processingKeys.clear();
52502
52860
  this.unregisterProcessCleanup();
52503
52861
  log("[background-agent] Shutdown complete");
52504
52862
  }
52863
+ enqueueNotificationForParent(parentSessionID, operation) {
52864
+ if (!parentSessionID) {
52865
+ return operation();
52866
+ }
52867
+ const previous = this.notificationQueueByParent.get(parentSessionID) ?? Promise.resolve();
52868
+ const current = previous.catch(() => {}).then(operation);
52869
+ this.notificationQueueByParent.set(parentSessionID, current);
52870
+ current.finally(() => {
52871
+ if (this.notificationQueueByParent.get(parentSessionID) === current) {
52872
+ this.notificationQueueByParent.delete(parentSessionID);
52873
+ }
52874
+ }).catch(() => {});
52875
+ return current;
52876
+ }
52505
52877
  }
52506
52878
  function registerProcessSignal(signal, handler, exitAfter) {
52507
52879
  const listener = () => {
@@ -52514,7 +52886,7 @@ function registerProcessSignal(signal, handler, exitAfter) {
52514
52886
  process.on(signal, listener);
52515
52887
  return listener;
52516
52888
  }
52517
- function getMessageDir12(sessionID) {
52889
+ function getMessageDir13(sessionID) {
52518
52890
  if (!existsSync53(MESSAGE_STORAGE))
52519
52891
  return null;
52520
52892
  const directPath = join64(MESSAGE_STORAGE, sessionID);
@@ -57914,15 +58286,130 @@ async function executeActions(actions, ctx) {
57914
58286
  return { success: true, spawnedPaneId, results };
57915
58287
  }
57916
58288
 
58289
+ // src/features/tmux-subagent/polling-manager.ts
58290
+ init_tmux();
58291
+ init_tmux();
58292
+ init_shared();
58293
+ var SESSION_TIMEOUT_MS2 = 10 * 60 * 1000;
58294
+ var MIN_STABILITY_TIME_MS3 = 10 * 1000;
58295
+ var STABLE_POLLS_REQUIRED = 3;
58296
+
58297
+ class TmuxPollingManager {
58298
+ client;
58299
+ sessions;
58300
+ closeSessionById;
58301
+ pollInterval;
58302
+ constructor(client2, sessions, closeSessionById) {
58303
+ this.client = client2;
58304
+ this.sessions = sessions;
58305
+ this.closeSessionById = closeSessionById;
58306
+ }
58307
+ startPolling() {
58308
+ if (this.pollInterval)
58309
+ return;
58310
+ this.pollInterval = setInterval(() => this.pollSessions(), POLL_INTERVAL_BACKGROUND_MS);
58311
+ log("[tmux-session-manager] polling started");
58312
+ }
58313
+ stopPolling() {
58314
+ if (this.pollInterval) {
58315
+ clearInterval(this.pollInterval);
58316
+ this.pollInterval = undefined;
58317
+ log("[tmux-session-manager] polling stopped");
58318
+ }
58319
+ }
58320
+ async pollSessions() {
58321
+ if (this.sessions.size === 0) {
58322
+ this.stopPolling();
58323
+ return;
58324
+ }
58325
+ try {
58326
+ const statusResult = await this.client.session.status({ path: undefined });
58327
+ const allStatuses = statusResult.data ?? {};
58328
+ log("[tmux-session-manager] pollSessions", {
58329
+ trackedSessions: Array.from(this.sessions.keys()),
58330
+ allStatusKeys: Object.keys(allStatuses)
58331
+ });
58332
+ const now = Date.now();
58333
+ const sessionsToClose = [];
58334
+ for (const [sessionId, tracked] of this.sessions.entries()) {
58335
+ const status = allStatuses[sessionId];
58336
+ const isIdle = status?.type === "idle";
58337
+ if (status) {
58338
+ tracked.lastSeenAt = new Date(now);
58339
+ }
58340
+ const missingSince = !status ? now - tracked.lastSeenAt.getTime() : 0;
58341
+ const missingTooLong = missingSince >= SESSION_MISSING_GRACE_MS;
58342
+ const isTimedOut = now - tracked.createdAt.getTime() > SESSION_TIMEOUT_MS2;
58343
+ const elapsedMs = now - tracked.createdAt.getTime();
58344
+ let shouldCloseViaStability = false;
58345
+ if (isIdle && elapsedMs >= MIN_STABILITY_TIME_MS3) {
58346
+ try {
58347
+ const messagesResult = await this.client.session.messages({
58348
+ path: { id: sessionId }
58349
+ });
58350
+ const currentMsgCount = Array.isArray(messagesResult.data) ? messagesResult.data.length : 0;
58351
+ if (tracked.lastMessageCount === currentMsgCount) {
58352
+ tracked.stableIdlePolls = (tracked.stableIdlePolls ?? 0) + 1;
58353
+ if (tracked.stableIdlePolls >= STABLE_POLLS_REQUIRED) {
58354
+ const recheckResult = await this.client.session.status({ path: undefined });
58355
+ const recheckStatuses = recheckResult.data ?? {};
58356
+ const recheckStatus = recheckStatuses[sessionId];
58357
+ if (recheckStatus?.type === "idle") {
58358
+ shouldCloseViaStability = true;
58359
+ } else {
58360
+ tracked.stableIdlePolls = 0;
58361
+ log("[tmux-session-manager] stability reached but session not idle on recheck, resetting", {
58362
+ sessionId,
58363
+ recheckStatus: recheckStatus?.type
58364
+ });
58365
+ }
58366
+ }
58367
+ } else {
58368
+ tracked.stableIdlePolls = 0;
58369
+ }
58370
+ tracked.lastMessageCount = currentMsgCount;
58371
+ } catch (msgErr) {
58372
+ log("[tmux-session-manager] failed to fetch messages for stability check", {
58373
+ sessionId,
58374
+ error: String(msgErr)
58375
+ });
58376
+ }
58377
+ } else if (!isIdle) {
58378
+ tracked.stableIdlePolls = 0;
58379
+ }
58380
+ log("[tmux-session-manager] session check", {
58381
+ sessionId,
58382
+ statusType: status?.type,
58383
+ isIdle,
58384
+ elapsedMs,
58385
+ stableIdlePolls: tracked.stableIdlePolls,
58386
+ lastMessageCount: tracked.lastMessageCount,
58387
+ missingSince,
58388
+ missingTooLong,
58389
+ isTimedOut,
58390
+ shouldCloseViaStability
58391
+ });
58392
+ if (shouldCloseViaStability || missingTooLong || isTimedOut) {
58393
+ sessionsToClose.push(sessionId);
58394
+ }
58395
+ }
58396
+ for (const sessionId of sessionsToClose) {
58397
+ log("[tmux-session-manager] closing session due to poll", { sessionId });
58398
+ await this.closeSessionById(sessionId);
58399
+ }
58400
+ } catch (err) {
58401
+ log("[tmux-session-manager] poll error", { error: String(err) });
58402
+ }
58403
+ }
58404
+ }
58405
+
57917
58406
  // src/features/tmux-subagent/manager.ts
57918
58407
  var defaultTmuxDeps = {
57919
58408
  isInsideTmux,
57920
58409
  getCurrentPaneId
57921
58410
  };
57922
- var SESSION_TIMEOUT_MS2 = 10 * 60 * 1000;
57923
- var MIN_STABILITY_TIME_MS3 = 10 * 1000;
57924
- var STABLE_POLLS_REQUIRED = 3;
57925
-
58411
+ var SESSION_TIMEOUT_MS3 = 10 * 60 * 1000;
58412
+ var MIN_STABILITY_TIME_MS4 = 10 * 1000;
57926
58413
  class TmuxSessionManager {
57927
58414
  client;
57928
58415
  tmuxConfig;
@@ -57930,8 +58417,8 @@ class TmuxSessionManager {
57930
58417
  sourcePaneId;
57931
58418
  sessions = new Map;
57932
58419
  pendingSessions = new Set;
57933
- pollInterval;
57934
58420
  deps;
58421
+ pollingManager;
57935
58422
  constructor(ctx, tmuxConfig, deps = defaultTmuxDeps) {
57936
58423
  this.client = ctx.client;
57937
58424
  this.tmuxConfig = tmuxConfig;
@@ -57939,6 +58426,7 @@ class TmuxSessionManager {
57939
58426
  const defaultPort = process.env.OPENCODE_PORT ?? "4096";
57940
58427
  this.serverUrl = ctx.serverUrl?.toString() ?? `http://localhost:${defaultPort}`;
57941
58428
  this.sourcePaneId = deps.getCurrentPaneId();
58429
+ this.pollingManager = new TmuxPollingManager(this.client, this.sessions, this.closeSessionById.bind(this));
57942
58430
  log("[tmux-session-manager] initialized", {
57943
58431
  configEnabled: this.tmuxConfig.enabled,
57944
58432
  tmuxConfig: this.tmuxConfig,
@@ -57987,6 +58475,9 @@ class TmuxSessionManager {
57987
58475
  });
57988
58476
  return false;
57989
58477
  }
58478
+ async pollSessions() {
58479
+ await this.pollingManager.pollSessions();
58480
+ }
57990
58481
  async onSessionCreated(event) {
57991
58482
  const enabled = this.isEnabled();
57992
58483
  log("[tmux-session-manager] onSessionCreated called", {
@@ -58081,7 +58572,7 @@ class TmuxSessionManager {
58081
58572
  paneId: result.spawnedPaneId,
58082
58573
  sessionReady
58083
58574
  });
58084
- this.startPolling();
58575
+ this.pollingManager.startPolling();
58085
58576
  } else {
58086
58577
  log("[tmux-session-manager] spawn failed", {
58087
58578
  success: result.success,
@@ -58116,104 +58607,7 @@ class TmuxSessionManager {
58116
58607
  }
58117
58608
  this.sessions.delete(event.sessionID);
58118
58609
  if (this.sessions.size === 0) {
58119
- this.stopPolling();
58120
- }
58121
- }
58122
- startPolling() {
58123
- if (this.pollInterval)
58124
- return;
58125
- this.pollInterval = setInterval(() => this.pollSessions(), POLL_INTERVAL_BACKGROUND_MS);
58126
- log("[tmux-session-manager] polling started");
58127
- }
58128
- stopPolling() {
58129
- if (this.pollInterval) {
58130
- clearInterval(this.pollInterval);
58131
- this.pollInterval = undefined;
58132
- log("[tmux-session-manager] polling stopped");
58133
- }
58134
- }
58135
- async pollSessions() {
58136
- if (this.sessions.size === 0) {
58137
- this.stopPolling();
58138
- return;
58139
- }
58140
- try {
58141
- const statusResult = await this.client.session.status({ path: undefined });
58142
- const allStatuses = statusResult.data ?? {};
58143
- log("[tmux-session-manager] pollSessions", {
58144
- trackedSessions: Array.from(this.sessions.keys()),
58145
- allStatusKeys: Object.keys(allStatuses)
58146
- });
58147
- const now = Date.now();
58148
- const sessionsToClose = [];
58149
- for (const [sessionId, tracked] of this.sessions.entries()) {
58150
- const status = allStatuses[sessionId];
58151
- const isIdle = status?.type === "idle";
58152
- if (status) {
58153
- tracked.lastSeenAt = new Date(now);
58154
- }
58155
- const missingSince = !status ? now - tracked.lastSeenAt.getTime() : 0;
58156
- const missingTooLong = missingSince >= SESSION_MISSING_GRACE_MS;
58157
- const isTimedOut = now - tracked.createdAt.getTime() > SESSION_TIMEOUT_MS2;
58158
- const elapsedMs = now - tracked.createdAt.getTime();
58159
- let shouldCloseViaStability = false;
58160
- if (isIdle && elapsedMs >= MIN_STABILITY_TIME_MS3) {
58161
- try {
58162
- const messagesResult = await this.client.session.messages({
58163
- path: { id: sessionId }
58164
- });
58165
- const currentMsgCount = Array.isArray(messagesResult.data) ? messagesResult.data.length : 0;
58166
- if (tracked.lastMessageCount === currentMsgCount) {
58167
- tracked.stableIdlePolls = (tracked.stableIdlePolls ?? 0) + 1;
58168
- if (tracked.stableIdlePolls >= STABLE_POLLS_REQUIRED) {
58169
- const recheckResult = await this.client.session.status({ path: undefined });
58170
- const recheckStatuses = recheckResult.data ?? {};
58171
- const recheckStatus = recheckStatuses[sessionId];
58172
- if (recheckStatus?.type === "idle") {
58173
- shouldCloseViaStability = true;
58174
- } else {
58175
- tracked.stableIdlePolls = 0;
58176
- log("[tmux-session-manager] stability reached but session not idle on recheck, resetting", {
58177
- sessionId,
58178
- recheckStatus: recheckStatus?.type
58179
- });
58180
- }
58181
- }
58182
- } else {
58183
- tracked.stableIdlePolls = 0;
58184
- }
58185
- tracked.lastMessageCount = currentMsgCount;
58186
- } catch (msgErr) {
58187
- log("[tmux-session-manager] failed to fetch messages for stability check", {
58188
- sessionId,
58189
- error: String(msgErr)
58190
- });
58191
- }
58192
- } else if (!isIdle) {
58193
- tracked.stableIdlePolls = 0;
58194
- }
58195
- log("[tmux-session-manager] session check", {
58196
- sessionId,
58197
- statusType: status?.type,
58198
- isIdle,
58199
- elapsedMs,
58200
- stableIdlePolls: tracked.stableIdlePolls,
58201
- lastMessageCount: tracked.lastMessageCount,
58202
- missingSince,
58203
- missingTooLong,
58204
- isTimedOut,
58205
- shouldCloseViaStability
58206
- });
58207
- if (shouldCloseViaStability || missingTooLong || isTimedOut) {
58208
- sessionsToClose.push(sessionId);
58209
- }
58210
- }
58211
- for (const sessionId of sessionsToClose) {
58212
- log("[tmux-session-manager] closing session due to poll", { sessionId });
58213
- await this.closeSessionById(sessionId);
58214
- }
58215
- } catch (err) {
58216
- log("[tmux-session-manager] poll error", { error: String(err) });
58610
+ this.pollingManager.stopPolling();
58217
58611
  }
58218
58612
  }
58219
58613
  async closeSessionById(sessionId) {
@@ -58230,7 +58624,7 @@ class TmuxSessionManager {
58230
58624
  }
58231
58625
  this.sessions.delete(sessionId);
58232
58626
  if (this.sessions.size === 0) {
58233
- this.stopPolling();
58627
+ this.pollingManager.stopPolling();
58234
58628
  }
58235
58629
  }
58236
58630
  createEventHandler() {
@@ -58239,7 +58633,7 @@ class TmuxSessionManager {
58239
58633
  };
58240
58634
  }
58241
58635
  async cleanup() {
58242
- this.stopPolling();
58636
+ this.pollingManager.stopPolling();
58243
58637
  if (this.sessions.size > 0) {
58244
58638
  log("[tmux-session-manager] closing all panes", { count: this.sessions.size });
58245
58639
  const state2 = this.sourcePaneId ? await queryWindowState(this.sourcePaneId) : null;
@@ -60766,13 +61160,13 @@ ACCUMULATED WISDOM:
60766
61160
 
60767
61161
  **For exploration (explore/librarian)**: ALWAYS background
60768
61162
  \`\`\`typescript
60769
- task(subagent_type="explore", run_in_background=true, ...)
60770
- task(subagent_type="librarian", run_in_background=true, ...)
61163
+ task(subagent_type="explore", load_skills=[], run_in_background=true, ...)
61164
+ task(subagent_type="librarian", load_skills=[], run_in_background=true, ...)
60771
61165
  \`\`\`
60772
61166
 
60773
61167
  **For task execution**: NEVER background
60774
61168
  \`\`\`typescript
60775
- task(category="...", run_in_background=false, ...)
61169
+ task(category="...", load_skills=[...], run_in_background=false, ...)
60776
61170
  \`\`\`
60777
61171
 
60778
61172
  **Parallel task groups**: Invoke multiple in ONE message
@@ -61096,12 +61490,12 @@ ACCUMULATED WISDOM: [from notepad]
61096
61490
  <parallel_execution>
61097
61491
  **Exploration (explore/librarian)**: ALWAYS background
61098
61492
  \`\`\`typescript
61099
- task(subagent_type="explore", run_in_background=true, ...)
61493
+ task(subagent_type="explore", load_skills=[], run_in_background=true, ...)
61100
61494
  \`\`\`
61101
61495
 
61102
61496
  **Task execution**: NEVER background
61103
61497
  \`\`\`typescript
61104
- task(category="...", run_in_background=false, ...)
61498
+ task(category="...", load_skills=[...], run_in_background=false, ...)
61105
61499
  \`\`\`
61106
61500
 
61107
61501
  **Parallel task groups**: Invoke multiple in ONE message
@@ -62200,6 +62594,50 @@ var agentMetadata = {
62200
62594
  function isFactory(source) {
62201
62595
  return typeof source === "function";
62202
62596
  }
62597
+ function sanitizeMarkdownTableCell(value) {
62598
+ return value.replace(/\r?\n/g, " ").replace(/\|/g, "\\|").replace(/\s+/g, " ").trim();
62599
+ }
62600
+ function isRecord3(value) {
62601
+ return typeof value === "object" && value !== null;
62602
+ }
62603
+ function parseRegisteredAgentSummaries(input) {
62604
+ if (!Array.isArray(input))
62605
+ return [];
62606
+ const result = [];
62607
+ for (const item of input) {
62608
+ if (!isRecord3(item))
62609
+ continue;
62610
+ const name = typeof item.name === "string" ? item.name : undefined;
62611
+ if (!name)
62612
+ continue;
62613
+ const hidden = item.hidden;
62614
+ if (hidden === true)
62615
+ continue;
62616
+ const disabled = item.disabled;
62617
+ if (disabled === true)
62618
+ continue;
62619
+ const enabled = item.enabled;
62620
+ if (enabled === false)
62621
+ continue;
62622
+ const description = typeof item.description === "string" ? item.description : "";
62623
+ result.push({ name, description: sanitizeMarkdownTableCell(description) });
62624
+ }
62625
+ return result;
62626
+ }
62627
+ function buildCustomAgentMetadata(agentName, description) {
62628
+ const shortDescription = sanitizeMarkdownTableCell(truncateDescription(description));
62629
+ const safeAgentName = sanitizeMarkdownTableCell(agentName);
62630
+ return {
62631
+ category: "specialist",
62632
+ cost: "CHEAP",
62633
+ triggers: [
62634
+ {
62635
+ domain: `Custom agent: ${safeAgentName}`,
62636
+ trigger: shortDescription || "Use when this agent's description matches the task"
62637
+ }
62638
+ ]
62639
+ };
62640
+ }
62203
62641
  function buildAgent(source, model, categories, gitMasterConfig, browserProvider, disabledSkills) {
62204
62642
  const base = isFactory(source) ? source(model) : source;
62205
62643
  const categoryConfigs = categories ? { ...DEFAULT_CATEGORIES, ...categories } : DEFAULT_CATEGORIES;
@@ -62330,7 +62768,7 @@ function mapScopeToLocation(scope) {
62330
62768
  return "project";
62331
62769
  return "plugin";
62332
62770
  }
62333
- async function createBuiltinAgents(disabledAgents = [], agentOverrides = {}, directory, systemDefaultModel, categories, gitMasterConfig, discoveredSkills = [], client2, browserProvider, uiSelectedModel, disabledSkills) {
62771
+ async function createBuiltinAgents(disabledAgents = [], agentOverrides = {}, directory, systemDefaultModel, categories, gitMasterConfig, discoveredSkills = [], customAgentSummaries, browserProvider, uiSelectedModel, disabledSkills) {
62334
62772
  const connectedProviders = readConnectedProvidersCache();
62335
62773
  const availableModels = await fetchAvailableModels(undefined, {
62336
62774
  connectedProviders: connectedProviders ?? undefined
@@ -62356,6 +62794,9 @@ async function createBuiltinAgents(disabledAgents = [], agentOverrides = {}, dir
62356
62794
  location: mapScopeToLocation(skill2.scope)
62357
62795
  }));
62358
62796
  const availableSkills = [...builtinAvailable, ...discoveredAvailable];
62797
+ const registeredAgents = parseRegisteredAgentSummaries(customAgentSummaries);
62798
+ const builtinAgentNames = new Set(Object.keys(agentSources).map((n) => n.toLowerCase()));
62799
+ const disabledAgentNames = new Set(disabledAgents.map((n) => n.toLowerCase()));
62359
62800
  const pendingAgentConfigs = new Map;
62360
62801
  for (const [name, source] of Object.entries(agentSources)) {
62361
62802
  const agentName = name;
@@ -62407,6 +62848,20 @@ async function createBuiltinAgents(disabledAgents = [], agentOverrides = {}, dir
62407
62848
  });
62408
62849
  }
62409
62850
  }
62851
+ for (const agent of registeredAgents) {
62852
+ const lowerName = agent.name.toLowerCase();
62853
+ if (builtinAgentNames.has(lowerName))
62854
+ continue;
62855
+ if (disabledAgentNames.has(lowerName))
62856
+ continue;
62857
+ if (availableAgents.some((a) => a.name.toLowerCase() === lowerName))
62858
+ continue;
62859
+ availableAgents.push({
62860
+ name: agent.name,
62861
+ description: agent.description,
62862
+ metadata: buildCustomAgentMetadata(agent.name, agent.description)
62863
+ });
62864
+ }
62410
62865
  const sisyphusOverride = agentOverrides["sisyphus"];
62411
62866
  const sisyphusRequirement = AGENT_MODEL_REQUIREMENTS["sisyphus"];
62412
62867
  const hasSisyphusExplicitConfig = sisyphusOverride !== undefined;
@@ -62451,7 +62906,9 @@ async function createBuiltinAgents(disabledAgents = [], agentOverrides = {}, dir
62451
62906
  if (hephaestusResolution) {
62452
62907
  const { model: hephaestusModel, variant: hephaestusResolvedVariant } = hephaestusResolution;
62453
62908
  let hephaestusConfig = createHephaestusAgent(hephaestusModel, availableAgents, undefined, availableSkills, availableCategories);
62454
- hephaestusConfig = { ...hephaestusConfig, variant: hephaestusResolvedVariant ?? "medium" };
62909
+ if (!hephaestusOverride?.variant) {
62910
+ hephaestusConfig = { ...hephaestusConfig, variant: hephaestusResolvedVariant ?? "medium" };
62911
+ }
62455
62912
  const hepOverrideCategory = hephaestusOverride?.category;
62456
62913
  if (hepOverrideCategory) {
62457
62914
  hephaestusConfig = applyCategoryOverride(hephaestusConfig, hepOverrideCategory, mergedCategories);
@@ -62862,8 +63319,8 @@ Or should I just note down this single fix?"
62862
63319
  **Research First:**
62863
63320
  \`\`\`typescript
62864
63321
  // Prompt structure: CONTEXT (what I'm doing) + GOAL (what I'm trying to achieve) + QUESTION (what I need to know) + REQUEST (what to find)
62865
- task(subagent_type="explore", prompt="I'm refactoring [target] and need to understand its impact scope before making changes. Find all usages via lsp_find_references - show calling code, patterns of use, and potential breaking points.", run_in_background=true)
62866
- task(subagent_type="explore", prompt="I'm about to modify [affected code] and need to ensure behavior preservation. Find existing test coverage - which tests exercise this code, what assertions exist, and any gaps in coverage.", run_in_background=true)
63322
+ task(subagent_type="explore", load_skills=[], prompt="I'm refactoring [target] and need to understand its impact scope before making changes. Find all usages via lsp_find_references - show calling code, patterns of use, and potential breaking points.", run_in_background=true)
63323
+ task(subagent_type="explore", load_skills=[], prompt="I'm about to modify [affected code] and need to ensure behavior preservation. Find existing test coverage - which tests exercise this code, what assertions exist, and any gaps in coverage.", run_in_background=true)
62867
63324
  \`\`\`
62868
63325
 
62869
63326
  **Interview Focus:**
@@ -62887,9 +63344,9 @@ task(subagent_type="explore", prompt="I'm about to modify [affected code] and ne
62887
63344
  \`\`\`typescript
62888
63345
  // Launch BEFORE asking user questions
62889
63346
  // Prompt structure: CONTEXT + GOAL + QUESTION + REQUEST
62890
- task(subagent_type="explore", prompt="I'm building a new [feature] and want to maintain codebase consistency. Find similar implementations in this project - their structure, patterns used, and conventions to follow.", run_in_background=true)
62891
- task(subagent_type="explore", prompt="I'm adding [feature type] to the project and need to understand existing conventions. Find how similar features are organized - file structure, naming patterns, and architectural approach.", run_in_background=true)
62892
- task(subagent_type="librarian", prompt="I'm implementing [technology] and want to follow established best practices. Find official documentation and community recommendations - setup patterns, common pitfalls, and production-ready examples.", run_in_background=true)
63347
+ task(subagent_type="explore", load_skills=[], prompt="I'm building a new [feature] and want to maintain codebase consistency. Find similar implementations in this project - their structure, patterns used, and conventions to follow.", run_in_background=true)
63348
+ task(subagent_type="explore", load_skills=[], prompt="I'm adding [feature type] to the project and need to understand existing conventions. Find how similar features are organized - file structure, naming patterns, and architectural approach.", run_in_background=true)
63349
+ task(subagent_type="librarian", load_skills=[], prompt="I'm implementing [technology] and want to follow established best practices. Find official documentation and community recommendations - setup patterns, common pitfalls, and production-ready examples.", run_in_background=true)
62893
63350
  \`\`\`
62894
63351
 
62895
63352
  **Interview Focus** (AFTER research):
@@ -62928,7 +63385,7 @@ Based on your stack, I'd recommend NextAuth.js - it integrates well with Next.js
62928
63385
 
62929
63386
  Run this check:
62930
63387
  \`\`\`typescript
62931
- task(subagent_type="explore", prompt="I'm assessing this project's test setup before planning work that may require TDD. I need to understand what testing capabilities exist. Find test infrastructure: package.json test scripts, config files (jest.config, vitest.config, pytest.ini), and existing test files. Report: 1) Does test infra exist? 2) What framework? 3) Example test patterns.", run_in_background=true)
63388
+ task(subagent_type="explore", load_skills=[], prompt="I'm assessing this project's test setup before planning work that may require TDD. I need to understand what testing capabilities exist. Find test infrastructure: package.json test scripts, config files (jest.config, vitest.config, pytest.ini), and existing test files. Report: 1) Does test infra exist? 2) What framework? 3) Example test patterns.", run_in_background=true)
62932
63389
  \`\`\`
62933
63390
 
62934
63391
  #### Step 2: Ask the Test Question (MANDATORY)
@@ -63026,13 +63483,13 @@ Add to draft immediately:
63026
63483
 
63027
63484
  **Research First:**
63028
63485
  \`\`\`typescript
63029
- task(subagent_type="explore", prompt="I'm planning architectural changes and need to understand the current system design. Find existing architecture: module boundaries, dependency patterns, data flow, and key abstractions used.", run_in_background=true)
63030
- task(subagent_type="librarian", prompt="I'm designing architecture for [domain] and want to make informed decisions. Find architectural best practices - proven patterns, trade-offs, and lessons learned from similar systems.", run_in_background=true)
63486
+ task(subagent_type="explore", load_skills=[], prompt="I'm planning architectural changes and need to understand the current system design. Find existing architecture: module boundaries, dependency patterns, data flow, and key abstractions used.", run_in_background=true)
63487
+ task(subagent_type="librarian", load_skills=[], prompt="I'm designing architecture for [domain] and want to make informed decisions. Find architectural best practices - proven patterns, trade-offs, and lessons learned from similar systems.", run_in_background=true)
63031
63488
  \`\`\`
63032
63489
 
63033
63490
  **Oracle Consultation** (recommend when stakes are high):
63034
63491
  \`\`\`typescript
63035
- task(subagent_type="oracle", prompt="Architecture consultation needed: [context]...", run_in_background=false)
63492
+ task(subagent_type="oracle", load_skills=[], prompt="Architecture consultation needed: [context]...", run_in_background=false)
63036
63493
  \`\`\`
63037
63494
 
63038
63495
  **Interview Focus:**
@@ -63049,9 +63506,9 @@ task(subagent_type="oracle", prompt="Architecture consultation needed: [context]
63049
63506
 
63050
63507
  **Parallel Investigation:**
63051
63508
  \`\`\`typescript
63052
- task(subagent_type="explore", prompt="I'm researching how to implement [feature] and need to understand current approach. Find how X is currently handled in this codebase - implementation details, edge cases covered, and any known limitations.", run_in_background=true)
63053
- task(subagent_type="librarian", prompt="I'm implementing Y and need authoritative guidance. Find official documentation - API reference, configuration options, and recommended usage patterns.", run_in_background=true)
63054
- task(subagent_type="librarian", prompt="I'm looking for battle-tested implementations of Z. Find open source projects that solve this - focus on production-quality code, how they handle edge cases, and any gotchas documented.", run_in_background=true)
63509
+ task(subagent_type="explore", load_skills=[], prompt="I'm researching how to implement [feature] and need to understand current approach. Find how X is currently handled in this codebase - implementation details, edge cases covered, and any known limitations.", run_in_background=true)
63510
+ task(subagent_type="librarian", load_skills=[], prompt="I'm implementing Y and need authoritative guidance. Find official documentation - API reference, configuration options, and recommended usage patterns.", run_in_background=true)
63511
+ task(subagent_type="librarian", load_skills=[], prompt="I'm looking for battle-tested implementations of Z. Find open source projects that solve this - focus on production-quality code, how they handle edge cases, and any gotchas documented.", run_in_background=true)
63055
63512
  \`\`\`
63056
63513
 
63057
63514
  **Interview Focus:**
@@ -63077,17 +63534,17 @@ task(subagent_type="librarian", prompt="I'm looking for battle-tested implementa
63077
63534
 
63078
63535
  **For Understanding Codebase:**
63079
63536
  \`\`\`typescript
63080
- task(subagent_type="explore", prompt="I'm working on [topic] and need to understand how it's organized in this project. Find all related files - show the structure, patterns used, and conventions I should follow.", run_in_background=true)
63537
+ task(subagent_type="explore", load_skills=[], prompt="I'm working on [topic] and need to understand how it's organized in this project. Find all related files - show the structure, patterns used, and conventions I should follow.", run_in_background=true)
63081
63538
  \`\`\`
63082
63539
 
63083
63540
  **For External Knowledge:**
63084
63541
  \`\`\`typescript
63085
- task(subagent_type="librarian", prompt="I'm integrating [library] and need to understand [specific feature]. Find official documentation - API details, configuration options, and recommended best practices.", run_in_background=true)
63542
+ task(subagent_type="librarian", load_skills=[], prompt="I'm integrating [library] and need to understand [specific feature]. Find official documentation - API details, configuration options, and recommended best practices.", run_in_background=true)
63086
63543
  \`\`\`
63087
63544
 
63088
63545
  **For Implementation Examples:**
63089
63546
  \`\`\`typescript
63090
- task(subagent_type="librarian", prompt="I'm implementing [feature] and want to learn from existing solutions. Find open source implementations - focus on production-quality code, architecture decisions, and common patterns.", run_in_background=true)
63547
+ task(subagent_type="librarian", load_skills=[], prompt="I'm implementing [feature] and want to learn from existing solutions. Find open source implementations - focus on production-quality code, architecture decisions, and common patterns.", run_in_background=true)
63091
63548
  \`\`\`
63092
63549
 
63093
63550
  ## Interview Mode Anti-Patterns
@@ -63192,6 +63649,7 @@ todoWrite([
63192
63649
  \`\`\`typescript
63193
63650
  task(
63194
63651
  subagent_type="metis",
63652
+ load_skills=[],
63195
63653
  prompt=\`Review this planning session before I generate the work plan:
63196
63654
 
63197
63655
  **User's Goal**: {summarize what user wants}
@@ -63364,6 +63822,7 @@ var PROMETHEUS_HIGH_ACCURACY_MODE = `# PHASE 3: PLAN GENERATION
63364
63822
  while (true) {
63365
63823
  const result = task(
63366
63824
  subagent_type="momus",
63825
+ load_skills=[],
63367
63826
  prompt=".sisyphus/plans/{name}.md",
63368
63827
  run_in_background=false
63369
63828
  )
@@ -64735,9 +65194,8 @@ function createWebsearchConfig(config3) {
64735
65194
  }
64736
65195
  return {
64737
65196
  type: "remote",
64738
- url: "https://mcp.exa.ai/mcp?tools=web_search_exa",
65197
+ url: process.env.EXA_API_KEY ? `https://mcp.exa.ai/mcp?tools=web_search_exa&exaApiKey=${encodeURIComponent(process.env.EXA_API_KEY)}` : "https://mcp.exa.ai/mcp?tools=web_search_exa",
64739
65198
  enabled: true,
64740
- headers: process.env.EXA_API_KEY ? { "x-api-key": process.env.EXA_API_KEY } : undefined,
64741
65199
  oauth: false
64742
65200
  };
64743
65201
  }
@@ -64780,6 +65238,31 @@ init_shared();
64780
65238
  init_migration();
64781
65239
  init_model_requirements();
64782
65240
  init_constants4();
65241
+
65242
+ // src/plugin-handlers/plan-model-inheritance.ts
65243
+ var MODEL_SETTINGS_KEYS = [
65244
+ "model",
65245
+ "variant",
65246
+ "temperature",
65247
+ "top_p",
65248
+ "maxTokens",
65249
+ "thinking",
65250
+ "reasoningEffort",
65251
+ "textVerbosity",
65252
+ "providerOptions"
65253
+ ];
65254
+ function buildPlanDemoteConfig(prometheusConfig, planOverride) {
65255
+ const modelSettings = {};
65256
+ for (const key of MODEL_SETTINGS_KEYS) {
65257
+ const value = planOverride?.[key] ?? prometheusConfig?.[key];
65258
+ if (value !== undefined) {
65259
+ modelSettings[key] = value;
65260
+ }
65261
+ }
65262
+ return { mode: "subagent", ...modelSettings };
65263
+ }
65264
+
65265
+ // src/plugin-handlers/config-handler.ts
64783
65266
  function resolveCategoryConfig2(categoryName, userCategories) {
64784
65267
  return userCategories?.[categoryName] ?? DEFAULT_CATEGORIES[categoryName];
64785
65268
  }
@@ -64884,7 +65367,24 @@ function createConfigHandler(deps) {
64884
65367
  const browserProvider = pluginConfig.browser_automation_engine?.provider ?? "playwright";
64885
65368
  const currentModel = config3.model;
64886
65369
  const disabledSkills = new Set(pluginConfig.disabled_skills ?? []);
64887
- const builtinAgents = await createBuiltinAgents(migratedDisabledAgents, pluginConfig.agents, ctx.directory, undefined, pluginConfig.categories, pluginConfig.git_master, allDiscoveredSkills, ctx.client, browserProvider, currentModel, disabledSkills);
65370
+ const configAgent = config3.agent;
65371
+ function isRecord4(value) {
65372
+ return typeof value === "object" && value !== null;
65373
+ }
65374
+ function buildCustomAgentSummaryInput(agents) {
65375
+ if (!agents)
65376
+ return [];
65377
+ const result = [];
65378
+ for (const [name, value] of Object.entries(agents)) {
65379
+ if (!isRecord4(value))
65380
+ continue;
65381
+ const description = typeof value.description === "string" ? value.description : "";
65382
+ const hidden = value.hidden === true;
65383
+ const disabled = value.disabled === true || value.enabled === false;
65384
+ result.push({ name, description, hidden, disabled });
65385
+ }
65386
+ return result;
65387
+ }
64888
65388
  const userAgents = pluginConfig.claude_code?.agents ?? true ? loadUserAgents() : {};
64889
65389
  const projectAgents = pluginConfig.claude_code?.agents ?? true ? loadProjectAgents() : {};
64890
65390
  const rawPluginAgents = pluginComponents.agents;
@@ -64892,12 +65392,18 @@ function createConfigHandler(deps) {
64892
65392
  k,
64893
65393
  v ? migrateAgentConfig(v) : v
64894
65394
  ]));
65395
+ const customAgentSummaries = [
65396
+ ...buildCustomAgentSummaryInput(configAgent),
65397
+ ...buildCustomAgentSummaryInput(userAgents),
65398
+ ...buildCustomAgentSummaryInput(projectAgents),
65399
+ ...buildCustomAgentSummaryInput(pluginAgents)
65400
+ ];
65401
+ const builtinAgents = await createBuiltinAgents(migratedDisabledAgents, pluginConfig.agents, ctx.directory, undefined, pluginConfig.categories, pluginConfig.git_master, allDiscoveredSkills, customAgentSummaries, browserProvider, currentModel, disabledSkills);
64895
65402
  const isSisyphusEnabled = pluginConfig.sisyphus_agent?.disabled !== true;
64896
65403
  const builderEnabled = pluginConfig.sisyphus_agent?.default_builder_enabled ?? false;
64897
65404
  const plannerEnabled = pluginConfig.sisyphus_agent?.planner_enabled ?? true;
64898
65405
  const replacePlan = pluginConfig.sisyphus_agent?.replace_plan ?? true;
64899
65406
  const shouldDemotePlan = plannerEnabled && replacePlan;
64900
- const configAgent = config3.agent;
64901
65407
  if (isSisyphusEnabled && builtinAgents.sisyphus) {
64902
65408
  config3.default_agent = "sisyphus";
64903
65409
  const agentConfig = {
@@ -64984,9 +65490,7 @@ function createConfigHandler(deps) {
64984
65490
  value ? migrateAgentConfig(value) : value
64985
65491
  ])) : {};
64986
65492
  const migratedBuild = configAgent?.build ? migrateAgentConfig(configAgent.build) : {};
64987
- const planDemoteConfig = shouldDemotePlan ? {
64988
- mode: "subagent"
64989
- } : undefined;
65493
+ const planDemoteConfig = shouldDemotePlan ? buildPlanDemoteConfig(agentConfig["prometheus"], pluginConfig.agents?.plan) : undefined;
64990
65494
  config3.agent = {
64991
65495
  ...agentConfig,
64992
65496
  ...Object.fromEntries(Object.entries(builtinAgents).filter(([k]) => k !== "sisyphus")),
@@ -65022,6 +65526,7 @@ function createConfigHandler(deps) {
65022
65526
  };
65023
65527
  const isCliRunMode = process.env.OPENCODE_CLI_RUN_MODE === "true";
65024
65528
  const questionPermission = isCliRunMode ? "deny" : "allow";
65529
+ const todoPermission = pluginConfig.experimental?.task_system ? { todowrite: "deny", todoread: "deny" } : {};
65025
65530
  if (agentResult.librarian) {
65026
65531
  const agent = agentResult.librarian;
65027
65532
  agent.permission = { ...agent.permission, "grep_app_*": "allow" };
@@ -65032,23 +65537,23 @@ function createConfigHandler(deps) {
65032
65537
  }
65033
65538
  if (agentResult["atlas"]) {
65034
65539
  const agent = agentResult["atlas"];
65035
- agent.permission = { ...agent.permission, task: "allow", call_omo_agent: "deny", "task_*": "allow", teammate: "allow" };
65540
+ agent.permission = { ...agent.permission, ...todoPermission, task: "allow", call_omo_agent: "deny", "task_*": "allow", teammate: "allow" };
65036
65541
  }
65037
65542
  if (agentResult.sisyphus) {
65038
65543
  const agent = agentResult.sisyphus;
65039
- agent.permission = { ...agent.permission, call_omo_agent: "deny", task: "allow", question: questionPermission, "task_*": "allow", teammate: "allow" };
65544
+ agent.permission = { ...agent.permission, ...todoPermission, call_omo_agent: "deny", task: "allow", question: questionPermission, "task_*": "allow", teammate: "allow" };
65040
65545
  }
65041
65546
  if (agentResult.hephaestus) {
65042
65547
  const agent = agentResult.hephaestus;
65043
- agent.permission = { ...agent.permission, call_omo_agent: "deny", task: "allow", question: questionPermission };
65548
+ agent.permission = { ...agent.permission, ...todoPermission, call_omo_agent: "deny", task: "allow", question: questionPermission };
65044
65549
  }
65045
65550
  if (agentResult["prometheus"]) {
65046
65551
  const agent = agentResult["prometheus"];
65047
- agent.permission = { ...agent.permission, call_omo_agent: "deny", task: "allow", question: questionPermission, "task_*": "allow", teammate: "allow" };
65552
+ agent.permission = { ...agent.permission, ...todoPermission, call_omo_agent: "deny", task: "allow", question: questionPermission, "task_*": "allow", teammate: "allow" };
65048
65553
  }
65049
65554
  if (agentResult["sisyphus-junior"]) {
65050
65555
  const agent = agentResult["sisyphus-junior"];
65051
- agent.permission = { ...agent.permission, task: "allow", "task_*": "allow", teammate: "allow" };
65556
+ agent.permission = { ...agent.permission, ...todoPermission, task: "allow", "task_*": "allow", teammate: "allow" };
65052
65557
  }
65053
65558
  config3.permission = {
65054
65559
  ...config3.permission,
@@ -65264,10 +65769,6 @@ var OhMyOpenCodePlugin = async (ctx) => {
65264
65769
  backgroundManager,
65265
65770
  config: pluginConfig.babysitting
65266
65771
  }), { enabled: safeHookEnabled }) : null;
65267
- if (sessionRecovery && todoContinuationEnforcer) {
65268
- sessionRecovery.setOnAbortCallback(todoContinuationEnforcer.markRecovering);
65269
- sessionRecovery.setOnRecoveryCompleteCallback(todoContinuationEnforcer.markRecoveryComplete);
65270
- }
65271
65772
  const backgroundNotificationHook = isHookEnabled("background-notification") ? safeCreateHook("background-notification", () => createBackgroundNotificationHook(backgroundManager), { enabled: safeHookEnabled }) : null;
65272
65773
  const backgroundTools = createBackgroundTools(backgroundManager, ctx.client);
65273
65774
  const callOmoAgent = createCallOmoAgent(ctx, backgroundManager);
@@ -65322,6 +65823,7 @@ var OhMyOpenCodePlugin = async (ctx) => {
65322
65823
  disabledSkills,
65323
65824
  availableCategories,
65324
65825
  availableSkills,
65826
+ agentOverrides: pluginConfig.agents,
65325
65827
  onSyncSessionCreated: async (event) => {
65326
65828
  log("[index] onSyncSessionCreated callback", {
65327
65829
  sessionID: event.sessionID,
@@ -65367,6 +65869,14 @@ var OhMyOpenCodePlugin = async (ctx) => {
65367
65869
  modelCacheState
65368
65870
  });
65369
65871
  const taskSystemEnabled = pluginConfig.experimental?.task_system ?? false;
65872
+ if (sessionRecovery && todoContinuationEnforcer) {
65873
+ sessionRecovery.setOnAbortCallback((sessionID) => {
65874
+ todoContinuationEnforcer?.markRecovering(sessionID);
65875
+ });
65876
+ sessionRecovery.setOnRecoveryCompleteCallback((sessionID) => {
65877
+ todoContinuationEnforcer?.markRecoveryComplete(sessionID);
65878
+ });
65879
+ }
65370
65880
  const taskToolsRecord = taskSystemEnabled ? {
65371
65881
  task_create: createTaskCreateTool(pluginConfig, ctx),
65372
65882
  task_get: createTaskGetTool(pluginConfig),