oh-my-opencode-slim 1.1.1 → 2.0.0-beta.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.
package/dist/index.js CHANGED
@@ -18230,19 +18230,6 @@ var CUSTOM_SKILLS = [
18230
18230
  ];
18231
18231
 
18232
18232
  // src/cli/skills.ts
18233
- var RECOMMENDED_SKILLS = [
18234
- {
18235
- name: "agent-browser",
18236
- repo: "https://github.com/vercel-labs/agent-browser",
18237
- skillName: "agent-browser",
18238
- allowedAgents: ["designer"],
18239
- description: "High-performance browser automation",
18240
- postInstallCommands: [
18241
- "npm install -g agent-browser",
18242
- "agent-browser install"
18243
- ]
18244
- }
18245
- ];
18246
18233
  var PERMISSION_ONLY_SKILLS = [
18247
18234
  {
18248
18235
  name: "requesting-code-review",
@@ -18267,12 +18254,6 @@ function getSkillPermissionsForAgent(agentName, skillList) {
18267
18254
  }
18268
18255
  return permissions;
18269
18256
  }
18270
- for (const skill of RECOMMENDED_SKILLS) {
18271
- const isAllowed = skill.allowedAgents.includes("*") || skill.allowedAgents.includes(agentName);
18272
- if (isAllowed) {
18273
- permissions[skill.skillName] = "allow";
18274
- }
18275
- }
18276
18257
  for (const skill of CUSTOM_SKILLS) {
18277
18258
  const isAllowed = skill.allowedAgents.includes("*") || skill.allowedAgents.includes(agentName);
18278
18259
  if (isAllowed) {
@@ -18322,8 +18303,8 @@ var DEFAULT_TIMEOUT_MS = 2 * 60 * 1000;
18322
18303
  var MAX_POLL_TIME_MS = 5 * 60 * 1000;
18323
18304
  var DEFAULT_MAX_SUBAGENT_DEPTH = 3;
18324
18305
  var PHASE_REMINDER_TEXT = `!IMPORTANT! Recall the workflow rules:
18325
- Understand → choose the best parallelized path based on your capabilities and agents delegation rulesrecall session reuse rulesexecute → verify.
18326
- If delegating, launch the specialist in the same turn you mention it !END!`;
18306
+ Understand → build a short work graph with independent lanes, dependencies, and advisory ownership dispatch independent specialists as background tasks record task/session IDs continue orchestration poll task_status for terminal results reconcile → verify.
18307
+ Only consume outputs or advance dependent work when background results are terminal. !END!`;
18327
18308
  var TMUX_SPAWN_DELAY_MS = 500;
18328
18309
  var COUNCILLOR_STAGGER_MS = 250;
18329
18310
  var DEFAULT_DISABLED_AGENTS = ["observer"];
@@ -18670,7 +18651,9 @@ function loadConfigFromPath(configPath, options) {
18670
18651
  const content = fs.readFileSync(configPath, "utf-8");
18671
18652
  let rawConfig;
18672
18653
  try {
18673
- rawConfig = JSON.parse(stripJsonComments(content));
18654
+ const stripped = stripJsonComments(content);
18655
+ const interpolated = stripped.replace(/\{env:([^}]+)\}/g, (_, varName) => process.env[varName] ?? "");
18656
+ rawConfig = JSON.parse(interpolated);
18674
18657
  } catch (error) {
18675
18658
  const message = error instanceof Error ? error.message : String(error);
18676
18659
  options?.onWarning?.({
@@ -18977,6 +18960,7 @@ ${customAppendPrompt}`;
18977
18960
  }
18978
18961
  var AGENT_DESCRIPTIONS = {
18979
18962
  explorer: `@explorer
18963
+ - Lane: Codebase discovery and reconnaissance
18980
18964
  - Role: Parallel search specialist for discovering unknowns across the codebase
18981
18965
  - Permissions: Read files
18982
18966
  - Stats: 2x faster codebase search than orchestrator, 1/2 cost of orchestrator
@@ -18984,38 +18968,43 @@ var AGENT_DESCRIPTIONS = {
18984
18968
  - **Delegate when:** Need to discover what exists before planning • Parallel searches speed discovery • Need summarized map vs full contents • Broad/uncertain scope
18985
18969
  - **Don't delegate when:** Know the path and need actual content • Need full file anyway • Single specific lookup • About to edit the file`,
18986
18970
  librarian: `@librarian
18971
+ - Lane: External knowledge and library research
18987
18972
  - Role: Authoritative source for current library docs and API references
18988
18973
  - Permissions: External docs/search MCPs; no file edits
18989
18974
  - Stats: 10x better finding up-to-date library docs than orchestrator, 1/2 cost of orchestrator
18990
18975
  - Capabilities: Fetches latest official docs, examples, API signatures, version-specific behavior via grep_app MCP
18991
18976
  - **Delegate when:** Libraries with frequent API changes (React, Next.js, AI SDKs) • Complex APIs needing official examples (ORMs, auth) • Version-specific behavior matters • Unfamiliar library • Edge cases or advanced features • Nuanced best practices
18992
18977
  - **Don't delegate when:** Standard usage you're confident • Simple stable APIs • General programming knowledge • Info already in conversation • Built-in language features
18993
- - **Rule of thumb:** "How does this library work?" → @librarian. "How does programming work?" → yourself.`,
18978
+ - **Rule of thumb:** "How does this library work?" → @librarian. "How does programming work?" → answer directly.`,
18994
18979
  oracle: `@oracle
18980
+ - Lane: Architecture, risk, debugging strategy, and review
18995
18981
  - Role: Strategic advisor for high-stakes decisions and persistent problems, code reviewer
18996
18982
  - Permissions: Read files
18997
18983
  - Stats: 5x better decision maker, problem solver, investigator than orchestrator, 0.8x speed of orchestrator, same cost.
18998
18984
  - Capabilities: Deep architectural reasoning, system-level trade-offs, complex debugging, code review, simplification, maintainability review
18999
18985
  - **Delegate when:** Major architectural decisions with long-term impact • Problems persisting after 2+ fix attempts • High-risk multi-system refactors • Costly trade-offs (performance vs maintainability) • Complex debugging with unclear root cause • Security/scalability/data integrity decisions • Genuinely uncertain and cost of wrong choice is high • When a workflow calls for a **reviewer** subagent • Code needs simplification or YAGNI scrutiny
19000
18986
  - **Don't delegate when:** Routine decisions you're confident about • First bug fix attempt • Straightforward trade-offs • Tactical "how" vs strategic "should" • Time-sensitive good-enough decisions • Quick research/testing can answer
19001
- - **Rule of thumb:** Need senior architect review? → @oracle. Need code review or simplification? → @oracle. Just do it and PR? → yourself.`,
18987
+ - **Rule of thumb:** Need senior architect review? → @oracle. Need code review or simplification? → @oracle. Routine coordination or final synthesis? → handle directly.`,
19002
18988
  designer: `@designer
18989
+ - Lane: User-facing UI/UX design, polish, and review
19003
18990
  - Role: UI/UX specialist for intentional, polished experiences
19004
18991
  - Permissions: Read/write files
19005
18992
  - Stats: 10x better UI/UX than orchestrator
19006
18993
  - Capabilities: Visual relevant edits, interactions, responsive layouts, design systems with aesthetic intent, deep UI/UX knowledge.
19007
18994
  - **Delegate when:** User-facing interfaces needing polish • Responsive layouts • UX-critical components (forms, nav, dashboards) • Visual consistency systems • Animations/micro-interactions • Landing/marketing pages • Refining functional→delightful • Reviewing existing UI/UX quality
19008
18995
  - **Don't delegate when:** Backend/logic with no visual • Quick prototypes where design doesn't matter yet
19009
- - **Rule of thumb:** Users see it and polish matters? → @designer. Headless/functional? → yourself.`,
18996
+ - **Rule of thumb:** Users see it and polish matters? → @designer. Headless/functional implementation? → schedule @fixer.`,
19010
18997
  fixer: `@fixer
18998
+ - Lane: Bounded implementation and test execution
19011
18999
  - Role: Fast execution specialist for well-defined tasks, which empowers orchestrator with parallel, speedy executions
19012
19000
  - Permissions: Read/write files
19013
19001
  - Stats: 2x faster code edits, 1/2 cost of orchestrator, 0.8x quality of orchestrator
19014
19002
  - Tools/Constraints: Execution-focused—no research, no architectural decisions
19015
19003
  - **Delegate when:** For implementation work, think and triage first. If the change is non-trivial or multi-file, hand bounded execution to @fixer • Writing or updating tests • Tasks that touch test files, fixtures, mocks, or test helpers. Parallelization benefits: Task involves multiple folders and multiple files modificaiton, scoping work per folder and spawning parallel @fixers for each folder.
19016
19004
  - **Don't delegate when:** Needs discovery/research/decisions • Single small change (<20 lines, one file) • Unclear requirements needing iteration • Explaining to fixer > doing • Tight integration with your current work • Sequential dependencies
19017
- - **Rule of thumb:** Explaining > doing? yourself. Test file modifications and bounded implementation work usually go to @fixer. Bigger or lots of edits, splitting makes sense, parallelized by spawning @fixers per certain scope.`,
19005
+ - **Rule of thumb:** If implementation or tests are needed, schedule @fixer with clear scope. Bigger or lots of edits should be split by ownership and dispatched as parallel background fixer lanes when safe.`,
19018
19006
  council: `@council
19007
+ - Lane: High-stakes multi-model decision support
19019
19008
  - Role: Multi-LLM consensus engine that runs several councillors, synthesizes their views, and returns a structured council report.
19020
19009
  - Permissions: Read files
19021
19010
  - Stats: 3x slower than orchestrator, 3x or more cost of orchestrator
@@ -19024,15 +19013,16 @@ var AGENT_DESCRIPTIONS = {
19024
19013
  - **Don't delegate when:** Straightforward tasks you're confident about • Speed matters more than confidence • Routine implementation/debugging • A single specialist is clearly the right tool • You only need current docs/search/code review rather than multi-model consensus.
19025
19014
  - **How to call:** Send the full question/task and relevant context. Be explicit about what decision, trade-off, or answer the council should resolve. Do not ask council to do routine code edits.
19026
19015
  - **Result handling:** Council returns a structured response that may include: synthesized Council Response, individual Councillor Details, and Council Summary/confidence. Preserve that structure when the user asked for council output. Do not pretend the council only returned a final answer. If you need to act on the council result, first briefly state the council's recommendation, then proceed.
19027
- - **Rule of thumb:** Need second/third opinions from different models? → @council. Need one expert agent or direct execution? → use the specialist or yourself.`,
19016
+ - **Rule of thumb:** Need second/third opinions from different models? → @council. Need one expert lane? → use the specialist. Need final synthesis? → handle directly.`,
19028
19017
  observer: `@observer
19018
+ - Lane: Visual/media analysis isolated from orchestrator context
19029
19019
  - Role: Visual analysis specialist for images, PDFs, and diagrams
19030
19020
  - Permissions: Read files
19031
19021
  - Stats: Saves main context tokens — Observer processes raw files, returns structured observations
19032
19022
  - Capabilities: Interprets images, screenshots, PDFs, and diagrams via native read tool; extracts UI elements, layouts, text, relationships
19033
19023
  - **Delegate when:** Need to analyze a multimedia file• Extract information
19034
19024
  - **Don't delegate when:** Plain text files that Read can handle directly • Files that need editing afterward (need literal content from Read)
19035
- - **Rule of thumb:** Even if your model supports vision, delegate visual analysis to @observer — it isolates large image/PDF bytes from your context window, returning only concise structured text. Need exact file contents for editing? → Read it yourself.
19025
+ - **Rule of thumb:** Even if your model supports vision, delegate visual analysis to @observer — it isolates large image/PDF bytes from your context window, returning only concise structured text. Need exact file contents for routing? → Read only the minimal context yourself.
19036
19026
  - **IMPORTANT:** When delegating to @observer, always include the **full file path** in the prompt so it can read the file. Example: "Analyze the screenshot at /path/to/file.png — describe the UI elements and error messages."`
19037
19027
  };
19038
19028
  var VALIDATION_ROUTING = [
@@ -19067,7 +19057,9 @@ function buildOrchestratorPrompt(disabledAgents) {
19067
19057
  }).join(`
19068
19058
  `);
19069
19059
  return `<Role>
19070
- You are an AI coding orchestrator that optimizes for quality, speed, cost, and reliability by delegating to specialists when it provides net efficiency gains.
19060
+ You are a workflow manager for coding work. Your job is to plan, schedule, delegate, monitor, reconcile, and verify specialist-agent work. You are not the default implementation worker.
19061
+
19062
+ Optimize for quality, speed, cost, and reliability by dispatching the right specialist lanes, tracking background task state, and integrating terminal results into one coherent outcome.
19071
19063
  </Role>
19072
19064
 
19073
19065
  <Agents>
@@ -19085,22 +19077,31 @@ Parse request: explicit requirements + implicit needs.
19085
19077
  Evaluate approach by: quality, speed, cost, reliability.
19086
19078
  Choose the path that optimizes all four.
19087
19079
 
19080
+ Classify work into lanes: discovery, external knowledge, implementation, UI/UX, review/risk, visual analysis, and final verification.
19081
+
19088
19082
  ## 3. Delegation Check
19089
19083
  **STOP. Review specialists before acting.**
19090
19084
 
19091
- !!! Review available agents and delegation rules. Decide whether to delegate or do it yourself. !!!
19085
+ !!! Review available agents and lane rules. Decide what to schedule, what depends on what, and what minimal direct coordination is needed. !!!
19092
19086
 
19093
- **Delegation efficiency:**
19087
+ **Dispatch efficiency:**
19094
19088
  - Reference paths/lines, don't paste files (\`src/app.ts:42\` not full contents)
19095
19089
  - Provide context summaries, let specialists read what they need
19096
19090
  - Brief user on delegation goal before each call
19097
- - Skip delegation if overhead doing it yourself
19091
+ - Keep direct work limited to clarification, minimal routing context, todos, synthesis, and final checks
19092
+ - For trivial conversational answers or tiny mechanical edits, direct execution is allowed when scheduling overhead would clearly dominate
19098
19093
 
19099
- ## 4. Split and Parallelize
19100
- Can tasks be split into subtasks and run in parallel?
19094
+ ## 4. Plan and Parallelize
19095
+ Build a short work graph before dispatching:
19096
+ - Independent lanes that can run now
19097
+ - Dependency-ordered lanes that must wait
19098
+ - Advisory ownership for write-capable lanes
19099
+ - Verification/review lanes that run after implementation
19100
+
19101
+ Can tasks be split into background specialist work?
19101
19102
  ${enabledParallelExamples}
19102
19103
 
19103
- Balance: respect dependencies, avoid parallelizing what must be sequential.
19104
+ Balance: respect dependencies, avoid parallelizing what must be sequential, and avoid overlapping write ownership.
19104
19105
 
19105
19106
  ### Context Isolation
19106
19107
  If no specialist delegation is needed, consider \`subtask\` before doing
@@ -19113,6 +19114,8 @@ compact outcome.
19113
19114
  Use \`subtask\` for focused investigation, bounded analysis, cleanup, or
19114
19115
  verification across files/logs/messages.
19115
19116
 
19117
+ Prefer native background \`task(..., background: true)\` plus \`task_status\` for independent specialist lanes. Use \`subtask\` only for bounded parent-local context isolation when native background specialist scheduling is not the right fit.
19118
+
19116
19119
  Do not use \`subtask\` for tiny tasks, open-ended work, interactive decisions,
19117
19120
  work better handled by a named specialist, or cases where the parent must reason
19118
19121
  over the details.
@@ -19121,21 +19124,28 @@ When calling \`subtask\`, give a self-contained prompt with objective,
19121
19124
  constraints, relevant context, deliverable, and validation. Pass only clearly
19122
19125
  relevant files. Wait for the summary, then integrate and verify it.
19123
19126
 
19124
- ### OpenCode subagent execution model
19125
- - A delegated specialist runs in a separate child session.
19126
- - Delegation is blocking for the parent at that point: send work out, then continue that line after results return.
19127
- - Parallel delegation means launching multiple independent child-session branches.
19128
- - Only parallelize branches that are truly independent; reconcile dependent steps after delegated results come back.
19129
-
19130
- ## 5. Execute
19131
- 1. Break complex tasks into todos
19132
- 2. Fire parallel research/implementation
19133
- 3. Delegate to specialists or do it yourself based on step 3
19134
- 4. Integrate results
19135
- 5. Adjust if needed
19127
+ ### OpenCode scheduler model
19128
+ - Delegated specialists should be launched as background tasks whenever work can run independently: use \`task(..., background: true)\`.
19129
+ - A dispatch returns a task/session ID immediately; it does not mean completion.
19130
+ - Track each task ID with specialist, objective, state, and any advisory ownership/dependency labels from the dispatch plan.
19131
+ - Continue orchestration while tasks run: planning, scheduling independent lanes, preparing synthesis, and asking needed user questions.
19132
+ - Poll or wait with \`task_status(wait: true, timeout_ms: ...)\` before consuming outputs or starting dependent work.
19133
+ - Parallel background tasks are allowed only when their write scopes do not conflict.
19134
+ - Final response requires relevant tasks to be terminal and reconciled.
19135
+
19136
+ ## 5. Dispatch
19137
+ 1. Split work into independent and dependency-ordered lanes
19138
+ 2. Plan advisory ownership for write-capable lanes
19139
+ 3. Dispatch independent specialists as background tasks
19140
+ 4. Record task IDs, state, and advisory ownership/dependency labels
19141
+ 5. Continue only independent orchestration while jobs run
19142
+ 6. Poll/wait for terminal results with \`task_status(wait: true, timeout_ms: ...)\`
19143
+ 7. Reconcile results, resolve conflicts, and gate dependent lanes
19144
+ 8. Dispatch follow-up jobs if needed
19145
+ 9. Verify final state
19136
19146
 
19137
19147
  ### Session Reuse
19138
- - Smartly reuse an available specialist session - constext reuse saves time and tokens
19148
+ - Smartly reuse an available specialist session - context reuse saves time and tokens
19139
19149
  - When too much unrelated, and really needed, start a fresh session with the specialist
19140
19150
  - If multiple remembered sessions fit, prefer the most recently used matching session.
19141
19151
  - Prefer re-uses over creating new sessions all the time
@@ -19187,7 +19197,7 @@ When user's approach seems problematic:
19187
19197
  **Bad:** "Great question! Let me think about the best approach here. I'm going to delegate to @librarian to check the latest Next.js documentation for the App Router, and then I'll implement the solution for you."
19188
19198
 
19189
19199
  **Good:** "Checking Next.js App Router docs via @librarian..."
19190
- [proceeds with implementation]
19200
+ [continues scheduling or integration]
19191
19201
 
19192
19202
  </Communication>
19193
19203
  `;
@@ -22600,6 +22610,221 @@ function createDisplayNameMentionRewriter(config) {
22600
22610
  return rewritten;
22601
22611
  };
22602
22612
  }
22613
+ // src/utils/task.ts
22614
+ function parseTaskIdFromTaskOutput(output) {
22615
+ const lines = output.split(/\r?\n/);
22616
+ for (const line of lines) {
22617
+ const trimmed = line.trim();
22618
+ const match = /^task_id:\s*([^\s()]+)(?:\s*\(.*)?$/.exec(trimmed);
22619
+ if (!match) {
22620
+ continue;
22621
+ }
22622
+ return match[1];
22623
+ }
22624
+ return;
22625
+ }
22626
+ function parseTaskLaunchOutput(output) {
22627
+ const taskID = parseTaskIdFromTaskOutput(output);
22628
+ const state = parseTaskStateFromOutput(output);
22629
+ if (!taskID || state !== "running")
22630
+ return;
22631
+ return {
22632
+ taskID,
22633
+ state,
22634
+ result: parseTaskResultFromOutput(output)
22635
+ };
22636
+ }
22637
+ function parseTaskStatusOutput(output) {
22638
+ const taskID = parseTaskIdFromTaskOutput(output);
22639
+ const state = parseTaskStateFromOutput(output);
22640
+ if (!taskID || !state)
22641
+ return;
22642
+ return {
22643
+ taskID,
22644
+ state,
22645
+ timedOut: state === "running" && /Timed out after \d+ms/i.test(output),
22646
+ result: parseTaskResultFromOutput(output)
22647
+ };
22648
+ }
22649
+ function parseTaskStateFromOutput(output) {
22650
+ for (const line of getTaskHeader(output).split(/\r?\n/)) {
22651
+ const match = /^state:\s*(running|completed|error|cancelled)\s*$/i.exec(line.trim());
22652
+ if (match)
22653
+ return match[1].toLowerCase();
22654
+ }
22655
+ return;
22656
+ }
22657
+ function parseTaskResultFromOutput(output) {
22658
+ const match = /<task_(result|error)>\s*([\s\S]*?)\s*<\/task_\1>/m.exec(output);
22659
+ const result = match?.[2]?.trim();
22660
+ return result || undefined;
22661
+ }
22662
+ function getTaskHeader(output) {
22663
+ const resultIndex = output.search(/<task_(?:result|error)>/);
22664
+ if (resultIndex === -1)
22665
+ return output;
22666
+ return output.slice(0, resultIndex);
22667
+ }
22668
+
22669
+ // src/utils/background-job-board.ts
22670
+ var TERMINAL_STATES = new Set([
22671
+ "completed",
22672
+ "error",
22673
+ "cancelled"
22674
+ ]);
22675
+ var AGENT_PREFIX = {
22676
+ council: "cou",
22677
+ designer: "des",
22678
+ explorer: "exp",
22679
+ fixer: "fix",
22680
+ librarian: "lib",
22681
+ observer: "obs",
22682
+ oracle: "ora"
22683
+ };
22684
+
22685
+ class BackgroundJobBoard {
22686
+ jobs = new Map;
22687
+ counters = new Map;
22688
+ registerLaunch(input) {
22689
+ const now = input.now ?? Date.now();
22690
+ const existing = this.jobs.get(input.taskID);
22691
+ if (existing) {
22692
+ const updated = {
22693
+ ...existing,
22694
+ agent: input.agent || existing.agent,
22695
+ description: input.description || existing.description,
22696
+ objective: input.objective ?? existing.objective,
22697
+ state: "running",
22698
+ timedOut: false,
22699
+ terminalUnreconciled: false,
22700
+ completedAt: undefined,
22701
+ resultSummary: undefined,
22702
+ updatedAt: now
22703
+ };
22704
+ this.jobs.set(input.taskID, updated);
22705
+ return updated;
22706
+ }
22707
+ const record = {
22708
+ taskID: input.taskID,
22709
+ parentSessionID: input.parentSessionID,
22710
+ agent: input.agent,
22711
+ description: input.description || `background ${input.agent} task`,
22712
+ objective: input.objective,
22713
+ state: "running",
22714
+ timedOut: false,
22715
+ terminalUnreconciled: false,
22716
+ launchedAt: now,
22717
+ updatedAt: now,
22718
+ alias: this.nextAlias(input.parentSessionID, input.agent)
22719
+ };
22720
+ this.jobs.set(input.taskID, record);
22721
+ return record;
22722
+ }
22723
+ updateStatus(input) {
22724
+ const existing = this.jobs.get(input.taskID);
22725
+ if (!existing)
22726
+ return;
22727
+ const now = input.now ?? Date.now();
22728
+ const terminal = TERMINAL_STATES.has(input.state);
22729
+ const updated = {
22730
+ ...existing,
22731
+ state: input.state,
22732
+ timedOut: input.timedOut ?? false,
22733
+ terminalUnreconciled: terminal ? true : existing.terminalUnreconciled,
22734
+ updatedAt: now,
22735
+ completedAt: terminal ? existing.completedAt ?? now : existing.completedAt,
22736
+ resultSummary: input.resultSummary ?? existing.resultSummary
22737
+ };
22738
+ this.jobs.set(input.taskID, updated);
22739
+ return updated;
22740
+ }
22741
+ updateFromStatusOutput(output) {
22742
+ const status = parseTaskStatusOutput(output);
22743
+ if (!status)
22744
+ return;
22745
+ return this.updateStatus({
22746
+ taskID: status.taskID,
22747
+ state: status.state,
22748
+ timedOut: status.timedOut,
22749
+ resultSummary: status.result
22750
+ });
22751
+ }
22752
+ markReconciled(taskID, now = Date.now()) {
22753
+ const existing = this.jobs.get(taskID);
22754
+ if (!existing)
22755
+ return;
22756
+ if (!existing.terminalUnreconciled && !TERMINAL_STATES.has(existing.state)) {
22757
+ return;
22758
+ }
22759
+ const updated = {
22760
+ ...existing,
22761
+ state: "reconciled",
22762
+ terminalUnreconciled: false,
22763
+ updatedAt: now
22764
+ };
22765
+ this.jobs.set(taskID, updated);
22766
+ return updated;
22767
+ }
22768
+ get(taskID) {
22769
+ return this.jobs.get(taskID);
22770
+ }
22771
+ list(parentSessionID) {
22772
+ const jobs = [...this.jobs.values()];
22773
+ const filtered = parentSessionID ? jobs.filter((job) => job.parentSessionID === parentSessionID) : jobs;
22774
+ return filtered.sort((a, b) => a.launchedAt - b.launchedAt);
22775
+ }
22776
+ hasRunning(parentSessionID) {
22777
+ return this.list(parentSessionID).some((job) => job.state === "running");
22778
+ }
22779
+ hasTerminalUnreconciled(parentSessionID) {
22780
+ return this.list(parentSessionID).some((job) => job.terminalUnreconciled);
22781
+ }
22782
+ formatForPrompt(parentSessionID) {
22783
+ const jobs = this.list(parentSessionID).filter((job) => job.state === "running" || job.terminalUnreconciled);
22784
+ if (jobs.length === 0)
22785
+ return;
22786
+ return [
22787
+ "### Background Job Board",
22788
+ "Use task_status before consuming running jobs. Reconcile terminal jobs before final response.",
22789
+ "",
22790
+ ...jobs.map(formatJob)
22791
+ ].join(`
22792
+ `);
22793
+ }
22794
+ clearParent(parentSessionID) {
22795
+ for (const job of this.list(parentSessionID)) {
22796
+ this.jobs.delete(job.taskID);
22797
+ }
22798
+ }
22799
+ drop(taskID) {
22800
+ this.jobs.delete(taskID);
22801
+ }
22802
+ nextAlias(parentSessionID, agent) {
22803
+ const prefix2 = AGENT_PREFIX[agent] ?? (agent.slice(0, 3) || "job");
22804
+ const key = `${parentSessionID}:${prefix2}`;
22805
+ const next = (this.counters.get(key) ?? 0) + 1;
22806
+ this.counters.set(key, next);
22807
+ return `${prefix2}-${next}`;
22808
+ }
22809
+ }
22810
+ function formatJob(job) {
22811
+ const status = job.terminalUnreconciled ? `${job.state}, unreconciled` : job.timedOut ? `${job.state}, timed out` : job.state;
22812
+ const lines = [
22813
+ `- ${job.alias} / ${job.taskID} / ${job.agent} / ${status}`,
22814
+ ` Objective: ${job.objective || job.description}`
22815
+ ];
22816
+ if (job.resultSummary && job.terminalUnreconciled) {
22817
+ lines.push(` Result: ${singleLine(job.resultSummary)}`);
22818
+ }
22819
+ return lines.join(`
22820
+ `);
22821
+ }
22822
+ function singleLine(value) {
22823
+ const normalized = value.replace(/\s+/g, " ").trim();
22824
+ if (normalized.length <= 160)
22825
+ return normalized;
22826
+ return `${normalized.slice(0, 157)}...`;
22827
+ }
22603
22828
  // src/utils/internal-initiator.ts
22604
22829
  var SLIM_INTERNAL_INITIATOR_MARKER = "<!-- SLIM_INTERNAL_INITIATOR -->";
22605
22830
  function isRecord(value) {
@@ -22851,19 +23076,6 @@ function formatContextFiles(files, options) {
22851
23076
  const rendered = shown.map((file) => `${file.path} (${file.lineCount} lines)`);
22852
23077
  return `${rendered.join(", ")}${rest > 0 ? ` (+${rest} more)` : ""}`;
22853
23078
  }
22854
- // src/utils/task.ts
22855
- function parseTaskIdFromTaskOutput(output) {
22856
- const lines = output.split(/\r?\n/);
22857
- for (const line of lines) {
22858
- const trimmed = line.trim();
22859
- const match = /^task_id:\s*([^\s()]+)(?:\s*\(.*)?$/.exec(trimmed);
22860
- if (!match) {
22861
- continue;
22862
- }
22863
- return match[1];
22864
- }
22865
- return;
22866
- }
22867
23079
  // src/utils/zip-extractor.ts
22868
23080
  import { spawnSync } from "node:child_process";
22869
23081
  import { release } from "node:os";
@@ -23677,14 +23889,13 @@ function createPhaseReminderHook() {
23677
23889
  if (originalText.includes(SLIM_INTERNAL_INITIATOR_MARKER)) {
23678
23890
  return;
23679
23891
  }
23680
- if (originalText.includes(PHASE_REMINDER)) {
23892
+ if (lastUserMessage.parts.some((p) => p.text?.includes(PHASE_REMINDER))) {
23681
23893
  return;
23682
23894
  }
23683
- lastUserMessage.parts[textPartIndex].text = `${originalText}
23684
-
23685
- ---
23686
-
23687
- ${PHASE_REMINDER}`;
23895
+ lastUserMessage.parts.push({
23896
+ type: "text",
23897
+ text: PHASE_REMINDER
23898
+ });
23688
23899
  }
23689
23900
  };
23690
23901
  }
@@ -24066,6 +24277,9 @@ var AGENT_NAME_SET = new Set([
24066
24277
  var MAX_PENDING_TASK_CALLS = 100;
24067
24278
  var RESUMABLE_SESSIONS_START = "<resumable_sessions>";
24068
24279
  var RESUMABLE_SESSIONS_END = "</resumable_sessions>";
24280
+ var BACKGROUND_COMPLETION_COMPLETED = /^Background task completed: /;
24281
+ var BACKGROUND_COMPLETION_FAILED = /^Background task failed: /;
24282
+ var MAX_PROCESSED_INJECTED_COMPLETIONS = 500;
24069
24283
  function isAgentName(value) {
24070
24284
  return typeof value === "string" && AGENT_NAME_SET.has(value);
24071
24285
  }
@@ -24109,10 +24323,14 @@ function createTaskSessionManagerHook(_ctx, options) {
24109
24323
  readContextMinLines: options.readContextMinLines,
24110
24324
  readContextMaxFiles: options.readContextMaxFiles
24111
24325
  });
24326
+ const backgroundJobBoard = options.backgroundJobBoard ?? new BackgroundJobBoard;
24112
24327
  const pendingCalls = new Map;
24113
24328
  const pendingCallOrder = [];
24114
24329
  const contextByTask = new Map;
24115
24330
  const pendingManagedTaskIds = new Set;
24331
+ const terminalJobsInjectedByParent = new Map;
24332
+ const processedInjectedCompletions = new Set;
24333
+ const processedInjectedCompletionOrder = [];
24116
24334
  let anonymousPendingCallId = 0;
24117
24335
  function addTaskContext(taskId, files) {
24118
24336
  if (files.length === 0)
@@ -24156,6 +24374,62 @@ function createTaskSessionManagerHook(_ctx, options) {
24156
24374
  }
24157
24375
  }
24158
24376
  }
24377
+ function updateBackgroundJobFromOutput(output) {
24378
+ if (typeof output !== "string")
24379
+ return;
24380
+ const status = parseTaskStatusOutput(output);
24381
+ if (!status)
24382
+ return;
24383
+ const updated = backgroundJobBoard.updateStatus({
24384
+ taskID: status.taskID,
24385
+ state: status.state,
24386
+ timedOut: status.timedOut,
24387
+ resultSummary: status.result
24388
+ });
24389
+ if (!updated)
24390
+ return;
24391
+ if (updated.terminalUnreconciled) {
24392
+ pendingManagedTaskIds.delete(updated.taskID);
24393
+ contextByTask.delete(updated.taskID);
24394
+ pruneContext();
24395
+ }
24396
+ return updated;
24397
+ }
24398
+ function updateFromInjectedCompletion(part, message, messageIndex, partIndex) {
24399
+ if (part.type !== "text" || typeof part.text !== "string") {
24400
+ return;
24401
+ }
24402
+ const isCompleted = BACKGROUND_COMPLETION_COMPLETED.test(part.text);
24403
+ const isFailed = BACKGROUND_COMPLETION_FAILED.test(part.text);
24404
+ if (part.synthetic !== true || !isCompleted && !isFailed) {
24405
+ return;
24406
+ }
24407
+ const status = parseTaskStatusOutput(part.text);
24408
+ if (!status)
24409
+ return;
24410
+ if (isCompleted && status.state !== "completed")
24411
+ return;
24412
+ if (isFailed && status.state !== "error")
24413
+ return;
24414
+ const occurrenceId = typeof part.id === "string" ? part.id : typeof message.info.id === "string" ? `${message.info.id}:${partIndex}` : `${message.info.sessionID ?? "unknown"}:${messageIndex}:${partIndex}`;
24415
+ if (processedInjectedCompletions.has(occurrenceId))
24416
+ return;
24417
+ const updated = updateBackgroundJobFromOutput(part.text);
24418
+ if (!updated)
24419
+ return;
24420
+ rememberProcessedInjectedCompletion(occurrenceId);
24421
+ return updated;
24422
+ }
24423
+ function rememberProcessedInjectedCompletion(signature) {
24424
+ processedInjectedCompletions.add(signature);
24425
+ processedInjectedCompletionOrder.push(signature);
24426
+ while (processedInjectedCompletionOrder.length > MAX_PROCESSED_INJECTED_COMPLETIONS) {
24427
+ const evicted = processedInjectedCompletionOrder.shift();
24428
+ if (!evicted)
24429
+ break;
24430
+ processedInjectedCompletions.delete(evicted);
24431
+ }
24432
+ }
24159
24433
  function isMissingRememberedSessionError(output) {
24160
24434
  const firstLine = output.split(/\r?\n/, 1)[0]?.trim().toLowerCase() ?? "";
24161
24435
  return firstLine.startsWith("[error]") && firstLine.includes("session") && (firstLine.includes("not found") || firstLine.includes("no session"));
@@ -24195,6 +24469,25 @@ function createTaskSessionManagerHook(_ctx, options) {
24195
24469
  return;
24196
24470
  return pendingCallOrder.find((callId) => pendingCalls.get(callId)?.parentSessionId === parentSessionId);
24197
24471
  }
24472
+ function rememberInjectedTerminalJobs(parentSessionID) {
24473
+ const taskIDs = backgroundJobBoard.list(parentSessionID).filter((job) => job.terminalUnreconciled).map((job) => job.taskID);
24474
+ if (taskIDs.length === 0)
24475
+ return;
24476
+ const existing = terminalJobsInjectedByParent.get(parentSessionID) ?? new Set;
24477
+ for (const taskID of taskIDs) {
24478
+ existing.add(taskID);
24479
+ }
24480
+ terminalJobsInjectedByParent.set(parentSessionID, existing);
24481
+ }
24482
+ function reconcileInjectedTerminalJobs(parentSessionID) {
24483
+ const taskIDs = terminalJobsInjectedByParent.get(parentSessionID);
24484
+ if (!taskIDs)
24485
+ return;
24486
+ for (const taskID of taskIDs) {
24487
+ backgroundJobBoard.markReconciled(taskID);
24488
+ }
24489
+ terminalJobsInjectedByParent.delete(parentSessionID);
24490
+ }
24198
24491
  return {
24199
24492
  "tool.execute.before": async (input, output) => {
24200
24493
  if (input.tool.toLowerCase() !== "task")
@@ -24244,11 +24537,31 @@ function createTaskSessionManagerHook(_ctx, options) {
24244
24537
  }
24245
24538
  return;
24246
24539
  }
24540
+ if (input.tool.toLowerCase() === "task_status") {
24541
+ if (!input.sessionID || !options.shouldManageSession(input.sessionID)) {
24542
+ return;
24543
+ }
24544
+ updateBackgroundJobFromOutput(output.output);
24545
+ return;
24546
+ }
24247
24547
  if (input.tool.toLowerCase() !== "task")
24248
24548
  return;
24249
24549
  const pending = takePendingCall(input.callID, input.sessionID);
24250
24550
  if (!pending || typeof output.output !== "string")
24251
24551
  return;
24552
+ const launch = parseTaskLaunchOutput(output.output);
24553
+ if (launch) {
24554
+ backgroundJobBoard.registerLaunch({
24555
+ taskID: launch.taskID,
24556
+ parentSessionID: pending.parentSessionId,
24557
+ agent: pending.agentType,
24558
+ description: pending.label,
24559
+ objective: pending.label
24560
+ });
24561
+ sessionManager.drop(pending.parentSessionId, pending.agentType, pending.resumedTaskId ?? launch.taskID);
24562
+ pendingManagedTaskIds.add(launch.taskID);
24563
+ return;
24564
+ }
24252
24565
  const taskId = parseTaskIdFromTaskOutput(output.output);
24253
24566
  if (!taskId) {
24254
24567
  if (pending.resumedTaskId && isMissingRememberedSessionError(output.output)) {
@@ -24271,6 +24584,19 @@ function createTaskSessionManagerHook(_ctx, options) {
24271
24584
  pruneContext();
24272
24585
  },
24273
24586
  "experimental.chat.messages.transform": async (_input, output) => {
24587
+ for (const [messageIndex, message] of output.messages.entries()) {
24588
+ if (message.info.role !== "user")
24589
+ continue;
24590
+ if (message.info.agent && message.info.agent !== "orchestrator") {
24591
+ continue;
24592
+ }
24593
+ if (!message.info.sessionID || !options.shouldManageSession(message.info.sessionID)) {
24594
+ continue;
24595
+ }
24596
+ for (const [partIndex, part] of message.parts.entries()) {
24597
+ updateFromInjectedCompletion(part, message, messageIndex, partIndex);
24598
+ }
24599
+ }
24274
24600
  for (let i = output.messages.length - 1;i >= 0; i -= 1) {
24275
24601
  const message = output.messages[i];
24276
24602
  if (message.info.role !== "user")
@@ -24280,8 +24606,11 @@ function createTaskSessionManagerHook(_ctx, options) {
24280
24606
  if (!message.info.sessionID || !options.shouldManageSession(message.info.sessionID)) {
24281
24607
  return;
24282
24608
  }
24283
- const reminder = sessionManager.formatForPrompt(message.info.sessionID);
24284
- if (!reminder)
24609
+ const reminders = [
24610
+ backgroundJobBoard.formatForPrompt(message.info.sessionID),
24611
+ sessionManager.formatForPrompt(message.info.sessionID)
24612
+ ].filter((item) => Boolean(item));
24613
+ if (reminders.length === 0)
24285
24614
  return;
24286
24615
  const textPart = message.parts.find((part) => part.type === "text" && typeof part.text === "string");
24287
24616
  if (!textPart)
@@ -24290,11 +24619,14 @@ function createTaskSessionManagerHook(_ctx, options) {
24290
24619
  return;
24291
24620
  if (textPart.text?.includes(RESUMABLE_SESSIONS_START))
24292
24621
  return;
24622
+ rememberInjectedTerminalJobs(message.info.sessionID);
24293
24623
  textPart.text = [
24294
24624
  textPart.text ?? "",
24295
24625
  "",
24296
24626
  RESUMABLE_SESSIONS_START,
24297
- reminder,
24627
+ reminders.join(`
24628
+
24629
+ `),
24298
24630
  RESUMABLE_SESSIONS_END
24299
24631
  ].join(`
24300
24632
  `);
@@ -24309,13 +24641,30 @@ function createTaskSessionManagerHook(_ctx, options) {
24309
24641
  }
24310
24642
  return;
24311
24643
  }
24644
+ if (input.event.type === "session.idle" || input.event.type === "session.status" && input.event.properties?.status?.type === "idle") {
24645
+ const sessionId2 = input.event.properties?.info?.id ?? input.event.properties?.sessionID;
24646
+ if (sessionId2 && options.shouldManageSession(sessionId2)) {
24647
+ reconcileInjectedTerminalJobs(sessionId2);
24648
+ }
24649
+ return;
24650
+ }
24651
+ if (input.event.type === "session.error") {
24652
+ const sessionId2 = input.event.properties?.info?.id ?? input.event.properties?.sessionID;
24653
+ if (sessionId2 && options.shouldManageSession(sessionId2)) {
24654
+ terminalJobsInjectedByParent.delete(sessionId2);
24655
+ }
24656
+ return;
24657
+ }
24312
24658
  if (input.event.type !== "session.deleted")
24313
24659
  return;
24314
24660
  const sessionId = input.event.properties?.info?.id ?? input.event.properties?.sessionID;
24315
24661
  if (!sessionId)
24316
24662
  return;
24317
24663
  sessionManager.dropTask(sessionId);
24664
+ backgroundJobBoard.drop(sessionId);
24318
24665
  sessionManager.clearParent(sessionId);
24666
+ backgroundJobBoard.clearParent(sessionId);
24667
+ terminalJobsInjectedByParent.delete(sessionId);
24319
24668
  contextByTask.delete(sessionId);
24320
24669
  pendingManagedTaskIds.delete(sessionId);
24321
24670
  pruneContext();
@@ -24544,6 +24893,7 @@ function createTodoContinuationHook(ctx, config) {
24544
24893
  const cooldownMs = config?.cooldownMs ?? 3000;
24545
24894
  const autoEnable = config?.autoEnable ?? false;
24546
24895
  const autoEnableThreshold = config?.autoEnableThreshold ?? 4;
24896
+ const backgroundJobBoard = config?.backgroundJobBoard;
24547
24897
  const requestSignatureBySession = new Map;
24548
24898
  const state = {
24549
24899
  enabled: false,
@@ -24655,8 +25005,10 @@ function createTodoContinuationHook(ctx, config) {
24655
25005
  }
24656
25006
  if (requestSignatureBySession.get(lastUserMessage.sessionID) === lastUserMessage.signature) {
24657
25007
  const reminder = hygiene.getPendingReminder(lastUserMessage.sessionID);
24658
- if (reminder) {
24659
- appendTodoHygieneInstruction(lastUserMessage.message, reminder);
25008
+ const guardrail = backgroundGuardrail(lastUserMessage.sessionID);
25009
+ const combinedReminder = [reminder, guardrail].filter((item) => Boolean(item)).join(" ");
25010
+ if (combinedReminder) {
25011
+ appendTodoHygieneInstruction(lastUserMessage.message, combinedReminder);
24660
25012
  } else {
24661
25013
  stripTodoHygieneInstructionFromMessage(lastUserMessage.message);
24662
25014
  }
@@ -24694,6 +25046,28 @@ function createTodoContinuationHook(ctx, config) {
24694
25046
  function registerOrchestratorSession(sessionID) {
24695
25047
  state.orchestratorSessionIds.add(sessionID);
24696
25048
  }
25049
+ function backgroundGuardrail(sessionID) {
25050
+ if (!backgroundJobBoard)
25051
+ return;
25052
+ const hasRunning = backgroundJobBoard.hasRunning(sessionID);
25053
+ const hasTerminal = backgroundJobBoard.hasTerminalUnreconciled(sessionID);
25054
+ if (hasRunning && hasTerminal) {
25055
+ return "Background jobs are still unresolved: call task_status for running jobs and reconcile terminal Background Job Board results before dependent work or finalizing.";
25056
+ }
25057
+ if (hasTerminal) {
25058
+ return "Background jobs have terminal results: reconcile the Background Job Board results before finalizing.";
25059
+ }
25060
+ if (hasRunning) {
25061
+ return "Background jobs are still running: call task_status before dependent work or finalizing.";
25062
+ }
25063
+ return;
25064
+ }
25065
+ function continuationPrompt(sessionID) {
25066
+ const guardrail = backgroundGuardrail(sessionID);
25067
+ if (!guardrail)
25068
+ return CONTINUATION_PROMPT;
25069
+ return `${CONTINUATION_PROMPT} ${guardrail}`;
25070
+ }
24697
25071
  function handleChatMessage(input) {
24698
25072
  if (!input.agent) {
24699
25073
  return;
@@ -24885,7 +25259,9 @@ function createTodoContinuationHook(ctx, config) {
24885
25259
  await ctx.client.session.prompt({
24886
25260
  path: { id: sessionID },
24887
25261
  body: {
24888
- parts: [createInternalAgentTextPart(CONTINUATION_PROMPT)]
25262
+ parts: [
25263
+ createInternalAgentTextPart(continuationPrompt(sessionID))
25264
+ ]
24889
25265
  }
24890
25266
  });
24891
25267
  state.consecutiveContinuations++;
@@ -24996,7 +25372,7 @@ function createTodoContinuationHook(ctx, config) {
24996
25372
  });
24997
25373
  }
24998
25374
  if (hasIncompleteTodos) {
24999
- output.parts.push(createInternalAgentTextPart(`${CONTINUATION_PROMPT} [Auto-continue enabled: up to ${maxContinuations} continuations.]`));
25375
+ output.parts.push(createInternalAgentTextPart(`${continuationPrompt(input.sessionID)} [Auto-continue enabled: up to ${maxContinuations} continuations.]`));
25000
25376
  } else {
25001
25377
  output.parts.push(createInternalAgentTextPart(`[Auto-continue: enabled for up to ${maxContinuations} continuations. No incomplete todos right now.]`));
25002
25378
  }
@@ -33436,6 +33812,7 @@ var OhMyOpenCodeLite = async (ctx) => {
33436
33812
  let todoContinuationHook;
33437
33813
  let sessionGoalHook;
33438
33814
  let taskSessionManagerHook;
33815
+ let backgroundJobBoard;
33439
33816
  let interviewManager;
33440
33817
  let presetManager;
33441
33818
  let divoomManager;
@@ -33521,11 +33898,13 @@ var OhMyOpenCodeLite = async (ctx) => {
33521
33898
  applyPatchHook = createApplyPatchHook(ctx);
33522
33899
  jsonErrorRecoveryHook = createJsonErrorRecoveryHook(ctx);
33523
33900
  foregroundFallback = new ForegroundFallbackManager(ctx.client, runtimeChains, config.fallback?.enabled !== false && Object.keys(runtimeChains).length > 0);
33901
+ backgroundJobBoard = new BackgroundJobBoard;
33524
33902
  todoContinuationHook = createTodoContinuationHook(ctx, {
33525
33903
  maxContinuations: config.todoContinuation?.maxContinuations ?? 5,
33526
33904
  cooldownMs: config.todoContinuation?.cooldownMs ?? 3000,
33527
33905
  autoEnable: config.todoContinuation?.autoEnable ?? false,
33528
- autoEnableThreshold: config.todoContinuation?.autoEnableThreshold ?? 4
33906
+ autoEnableThreshold: config.todoContinuation?.autoEnableThreshold ?? 4,
33907
+ backgroundJobBoard
33529
33908
  });
33530
33909
  sessionGoalHook = createSessionGoalHook(ctx, config, {
33531
33910
  getAgentName: (sessionID) => sessionAgentMap.get(sessionID)
@@ -33534,6 +33913,7 @@ var OhMyOpenCodeLite = async (ctx) => {
33534
33913
  maxSessionsPerAgent: config.sessionManager?.maxSessionsPerAgent ?? 2,
33535
33914
  readContextMinLines: config.sessionManager?.readContextMinLines ?? 10,
33536
33915
  readContextMaxFiles: config.sessionManager?.readContextMaxFiles ?? 8,
33916
+ backgroundJobBoard,
33537
33917
  shouldManageSession: (sessionID) => sessionAgentMap.get(sessionID) === "orchestrator"
33538
33918
  });
33539
33919
  interviewManager = createInterviewManager(ctx, config);