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/README.md +19 -10
- package/dist/cli/index.js +4 -82
- package/dist/cli/skills.d.ts +2 -30
- package/dist/cli/types.d.ts +0 -1
- package/dist/config/constants.d.ts +1 -1
- package/dist/hooks/phase-reminder/index.d.ts +1 -1
- package/dist/hooks/task-session-manager/index.d.ts +9 -0
- package/dist/hooks/todo-continuation/index.d.ts +2 -0
- package/dist/index.js +456 -76
- package/dist/tui.js +5 -62
- package/dist/utils/background-job-board.d.ts +48 -0
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/task.d.ts +16 -0
- package/package.json +4 -2
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 →
|
|
18326
|
-
|
|
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
|
-
|
|
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?" →
|
|
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.
|
|
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? →
|
|
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:**
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
**
|
|
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
|
-
-
|
|
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.
|
|
19100
|
-
|
|
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
|
|
19125
|
-
-
|
|
19126
|
-
-
|
|
19127
|
-
-
|
|
19128
|
-
-
|
|
19129
|
-
|
|
19130
|
-
|
|
19131
|
-
|
|
19132
|
-
|
|
19133
|
-
|
|
19134
|
-
|
|
19135
|
-
|
|
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 -
|
|
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
|
-
[
|
|
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 (
|
|
23892
|
+
if (lastUserMessage.parts.some((p) => p.text?.includes(PHASE_REMINDER))) {
|
|
23681
23893
|
return;
|
|
23682
23894
|
}
|
|
23683
|
-
lastUserMessage.parts
|
|
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
|
|
24284
|
-
|
|
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
|
-
|
|
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
|
-
|
|
24659
|
-
|
|
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: [
|
|
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(`${
|
|
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);
|