oh-my-opencode-slim 1.1.1 → 2.0.0-beta.1
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 +483 -78
- package/dist/tui.js +4 -63
- 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) {
|
|
@@ -18321,9 +18302,7 @@ var POLL_INTERVAL_BACKGROUND_MS = 2000;
|
|
|
18321
18302
|
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
|
-
var PHASE_REMINDER_TEXT = `!IMPORTANT!
|
|
18325
|
-
Understand → choose the best parallelized path based on your capabilities and agents delegation rules → recall session reuse rules → execute → verify.
|
|
18326
|
-
If delegating, launch the specialist in the same turn you mention it !END!`;
|
|
18305
|
+
var PHASE_REMINDER_TEXT = `!IMPORTANT! Scheduler workflow: plan lanes/dependencies → dispatch background specialists → track task IDs → poll task_status → reconcile terminal results → verify. Do not consume running-job output or advance dependent work. !END!`;
|
|
18327
18306
|
var TMUX_SPAWN_DELAY_MS = 500;
|
|
18328
18307
|
var COUNCILLOR_STAGGER_MS = 250;
|
|
18329
18308
|
var DEFAULT_DISABLED_AGENTS = ["observer"];
|
|
@@ -18670,7 +18649,9 @@ function loadConfigFromPath(configPath, options) {
|
|
|
18670
18649
|
const content = fs.readFileSync(configPath, "utf-8");
|
|
18671
18650
|
let rawConfig;
|
|
18672
18651
|
try {
|
|
18673
|
-
|
|
18652
|
+
const stripped = stripJsonComments(content);
|
|
18653
|
+
const interpolated = stripped.replace(/\{env:([^}]+)\}/g, (_, varName) => process.env[varName] ?? "");
|
|
18654
|
+
rawConfig = JSON.parse(interpolated);
|
|
18674
18655
|
} catch (error) {
|
|
18675
18656
|
const message = error instanceof Error ? error.message : String(error);
|
|
18676
18657
|
options?.onWarning?.({
|
|
@@ -18977,6 +18958,7 @@ ${customAppendPrompt}`;
|
|
|
18977
18958
|
}
|
|
18978
18959
|
var AGENT_DESCRIPTIONS = {
|
|
18979
18960
|
explorer: `@explorer
|
|
18961
|
+
- Lane: Codebase discovery and reconnaissance
|
|
18980
18962
|
- Role: Parallel search specialist for discovering unknowns across the codebase
|
|
18981
18963
|
- Permissions: Read files
|
|
18982
18964
|
- Stats: 2x faster codebase search than orchestrator, 1/2 cost of orchestrator
|
|
@@ -18984,38 +18966,43 @@ var AGENT_DESCRIPTIONS = {
|
|
|
18984
18966
|
- **Delegate when:** Need to discover what exists before planning • Parallel searches speed discovery • Need summarized map vs full contents • Broad/uncertain scope
|
|
18985
18967
|
- **Don't delegate when:** Know the path and need actual content • Need full file anyway • Single specific lookup • About to edit the file`,
|
|
18986
18968
|
librarian: `@librarian
|
|
18969
|
+
- Lane: External knowledge and library research
|
|
18987
18970
|
- Role: Authoritative source for current library docs and API references
|
|
18988
18971
|
- Permissions: External docs/search MCPs; no file edits
|
|
18989
18972
|
- Stats: 10x better finding up-to-date library docs than orchestrator, 1/2 cost of orchestrator
|
|
18990
18973
|
- Capabilities: Fetches latest official docs, examples, API signatures, version-specific behavior via grep_app MCP
|
|
18991
18974
|
- **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
18975
|
- **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?" →
|
|
18976
|
+
- **Rule of thumb:** "How does this library work?" → @librarian. "How does programming work?" → answer directly.`,
|
|
18994
18977
|
oracle: `@oracle
|
|
18978
|
+
- Lane: Architecture, risk, debugging strategy, and review
|
|
18995
18979
|
- Role: Strategic advisor for high-stakes decisions and persistent problems, code reviewer
|
|
18996
18980
|
- Permissions: Read files
|
|
18997
18981
|
- Stats: 5x better decision maker, problem solver, investigator than orchestrator, 0.8x speed of orchestrator, same cost.
|
|
18998
18982
|
- Capabilities: Deep architectural reasoning, system-level trade-offs, complex debugging, code review, simplification, maintainability review
|
|
18999
18983
|
- **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
18984
|
- **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.
|
|
18985
|
+
- **Rule of thumb:** Need senior architect review? → @oracle. Need code review or simplification? → @oracle. Routine coordination or final synthesis? → handle directly.`,
|
|
19002
18986
|
designer: `@designer
|
|
18987
|
+
- Lane: User-facing UI/UX design, polish, and review
|
|
19003
18988
|
- Role: UI/UX specialist for intentional, polished experiences
|
|
19004
18989
|
- Permissions: Read/write files
|
|
19005
18990
|
- Stats: 10x better UI/UX than orchestrator
|
|
19006
18991
|
- Capabilities: Visual relevant edits, interactions, responsive layouts, design systems with aesthetic intent, deep UI/UX knowledge.
|
|
19007
18992
|
- **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
18993
|
- **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? →
|
|
18994
|
+
- **Rule of thumb:** Users see it and polish matters? → @designer. Headless/functional implementation? → schedule @fixer.`,
|
|
19010
18995
|
fixer: `@fixer
|
|
18996
|
+
- Lane: Bounded implementation and test execution
|
|
19011
18997
|
- Role: Fast execution specialist for well-defined tasks, which empowers orchestrator with parallel, speedy executions
|
|
19012
18998
|
- Permissions: Read/write files
|
|
19013
18999
|
- Stats: 2x faster code edits, 1/2 cost of orchestrator, 0.8x quality of orchestrator
|
|
19014
19000
|
- Tools/Constraints: Execution-focused—no research, no architectural decisions
|
|
19015
19001
|
- **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
19002
|
- **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:**
|
|
19003
|
+
- **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
19004
|
council: `@council
|
|
19005
|
+
- Lane: High-stakes multi-model decision support
|
|
19019
19006
|
- Role: Multi-LLM consensus engine that runs several councillors, synthesizes their views, and returns a structured council report.
|
|
19020
19007
|
- Permissions: Read files
|
|
19021
19008
|
- Stats: 3x slower than orchestrator, 3x or more cost of orchestrator
|
|
@@ -19024,15 +19011,16 @@ var AGENT_DESCRIPTIONS = {
|
|
|
19024
19011
|
- **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
19012
|
- **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
19013
|
- **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
|
|
19014
|
+
- **Rule of thumb:** Need second/third opinions from different models? → @council. Need one expert lane? → use the specialist. Need final synthesis? → handle directly.`,
|
|
19028
19015
|
observer: `@observer
|
|
19016
|
+
- Lane: Visual/media analysis isolated from orchestrator context
|
|
19029
19017
|
- Role: Visual analysis specialist for images, PDFs, and diagrams
|
|
19030
19018
|
- Permissions: Read files
|
|
19031
19019
|
- Stats: Saves main context tokens — Observer processes raw files, returns structured observations
|
|
19032
19020
|
- Capabilities: Interprets images, screenshots, PDFs, and diagrams via native read tool; extracts UI elements, layouts, text, relationships
|
|
19033
19021
|
- **Delegate when:** Need to analyze a multimedia file• Extract information
|
|
19034
19022
|
- **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
|
|
19023
|
+
- **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
19024
|
- **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
19025
|
};
|
|
19038
19026
|
var VALIDATION_ROUTING = [
|
|
@@ -19067,7 +19055,9 @@ function buildOrchestratorPrompt(disabledAgents) {
|
|
|
19067
19055
|
}).join(`
|
|
19068
19056
|
`);
|
|
19069
19057
|
return `<Role>
|
|
19070
|
-
You are
|
|
19058
|
+
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.
|
|
19059
|
+
|
|
19060
|
+
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
19061
|
</Role>
|
|
19072
19062
|
|
|
19073
19063
|
<Agents>
|
|
@@ -19085,22 +19075,31 @@ Parse request: explicit requirements + implicit needs.
|
|
|
19085
19075
|
Evaluate approach by: quality, speed, cost, reliability.
|
|
19086
19076
|
Choose the path that optimizes all four.
|
|
19087
19077
|
|
|
19078
|
+
Classify work into lanes: discovery, external knowledge, implementation, UI/UX, review/risk, visual analysis, and final verification.
|
|
19079
|
+
|
|
19088
19080
|
## 3. Delegation Check
|
|
19089
19081
|
**STOP. Review specialists before acting.**
|
|
19090
19082
|
|
|
19091
|
-
!!! Review available agents and
|
|
19083
|
+
!!! Review available agents and lane rules. Decide what to schedule, what depends on what, and what minimal direct coordination is needed. !!!
|
|
19092
19084
|
|
|
19093
|
-
**
|
|
19085
|
+
**Dispatch efficiency:**
|
|
19094
19086
|
- Reference paths/lines, don't paste files (\`src/app.ts:42\` not full contents)
|
|
19095
19087
|
- Provide context summaries, let specialists read what they need
|
|
19096
19088
|
- Brief user on delegation goal before each call
|
|
19097
|
-
-
|
|
19089
|
+
- Keep direct work limited to clarification, minimal routing context, todos, synthesis, and final checks
|
|
19090
|
+
- For trivial conversational answers or tiny mechanical edits, direct execution is allowed when scheduling overhead would clearly dominate
|
|
19098
19091
|
|
|
19099
|
-
## 4.
|
|
19100
|
-
|
|
19092
|
+
## 4. Plan and Parallelize
|
|
19093
|
+
Build a short work graph before dispatching:
|
|
19094
|
+
- Independent lanes that can run now
|
|
19095
|
+
- Dependency-ordered lanes that must wait
|
|
19096
|
+
- Advisory ownership for write-capable lanes
|
|
19097
|
+
- Verification/review lanes that run after implementation
|
|
19098
|
+
|
|
19099
|
+
Can tasks be split into background specialist work?
|
|
19101
19100
|
${enabledParallelExamples}
|
|
19102
19101
|
|
|
19103
|
-
Balance: respect dependencies, avoid parallelizing what must be sequential.
|
|
19102
|
+
Balance: respect dependencies, avoid parallelizing what must be sequential, and avoid overlapping write ownership.
|
|
19104
19103
|
|
|
19105
19104
|
### Context Isolation
|
|
19106
19105
|
If no specialist delegation is needed, consider \`subtask\` before doing
|
|
@@ -19113,6 +19112,8 @@ compact outcome.
|
|
|
19113
19112
|
Use \`subtask\` for focused investigation, bounded analysis, cleanup, or
|
|
19114
19113
|
verification across files/logs/messages.
|
|
19115
19114
|
|
|
19115
|
+
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.
|
|
19116
|
+
|
|
19116
19117
|
Do not use \`subtask\` for tiny tasks, open-ended work, interactive decisions,
|
|
19117
19118
|
work better handled by a named specialist, or cases where the parent must reason
|
|
19118
19119
|
over the details.
|
|
@@ -19121,21 +19122,28 @@ When calling \`subtask\`, give a self-contained prompt with objective,
|
|
|
19121
19122
|
constraints, relevant context, deliverable, and validation. Pass only clearly
|
|
19122
19123
|
relevant files. Wait for the summary, then integrate and verify it.
|
|
19123
19124
|
|
|
19124
|
-
### OpenCode
|
|
19125
|
-
-
|
|
19126
|
-
-
|
|
19127
|
-
-
|
|
19128
|
-
-
|
|
19129
|
-
|
|
19130
|
-
|
|
19131
|
-
|
|
19132
|
-
|
|
19133
|
-
|
|
19134
|
-
|
|
19135
|
-
|
|
19125
|
+
### OpenCode scheduler model
|
|
19126
|
+
- Delegated specialists should be launched as background tasks whenever work can run independently: use \`task(..., background: true)\`.
|
|
19127
|
+
- A dispatch returns a task/session ID immediately; it does not mean completion.
|
|
19128
|
+
- Track each task ID with specialist, objective, state, and any advisory ownership/dependency labels from the dispatch plan.
|
|
19129
|
+
- Continue orchestration while tasks run: planning, scheduling independent lanes, preparing synthesis, and asking needed user questions.
|
|
19130
|
+
- Poll or wait with \`task_status(wait: true, timeout_ms: ...)\` before consuming outputs or starting dependent work.
|
|
19131
|
+
- Parallel background tasks are allowed only when their write scopes do not conflict.
|
|
19132
|
+
- Final response requires relevant tasks to be terminal and reconciled.
|
|
19133
|
+
|
|
19134
|
+
## 5. Dispatch
|
|
19135
|
+
1. Split work into independent and dependency-ordered lanes
|
|
19136
|
+
2. Plan advisory ownership for write-capable lanes
|
|
19137
|
+
3. Dispatch independent specialists as background tasks
|
|
19138
|
+
4. Record task IDs, state, and advisory ownership/dependency labels
|
|
19139
|
+
5. Continue only independent orchestration while jobs run
|
|
19140
|
+
6. Poll/wait for terminal results with \`task_status(wait: true, timeout_ms: ...)\`
|
|
19141
|
+
7. Reconcile results, resolve conflicts, and gate dependent lanes
|
|
19142
|
+
8. Dispatch follow-up jobs if needed
|
|
19143
|
+
9. Verify final state
|
|
19136
19144
|
|
|
19137
19145
|
### Session Reuse
|
|
19138
|
-
- Smartly reuse an available specialist session -
|
|
19146
|
+
- Smartly reuse an available specialist session - context reuse saves time and tokens
|
|
19139
19147
|
- When too much unrelated, and really needed, start a fresh session with the specialist
|
|
19140
19148
|
- If multiple remembered sessions fit, prefer the most recently used matching session.
|
|
19141
19149
|
- Prefer re-uses over creating new sessions all the time
|
|
@@ -19187,7 +19195,7 @@ When user's approach seems problematic:
|
|
|
19187
19195
|
**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
19196
|
|
|
19189
19197
|
**Good:** "Checking Next.js App Router docs via @librarian..."
|
|
19190
|
-
[
|
|
19198
|
+
[continues scheduling or integration]
|
|
19191
19199
|
|
|
19192
19200
|
</Communication>
|
|
19193
19201
|
`;
|
|
@@ -22600,6 +22608,224 @@ function createDisplayNameMentionRewriter(config) {
|
|
|
22600
22608
|
return rewritten;
|
|
22601
22609
|
};
|
|
22602
22610
|
}
|
|
22611
|
+
// src/utils/task.ts
|
|
22612
|
+
function parseTaskIdFromTaskOutput(output) {
|
|
22613
|
+
const lines = output.split(/\r?\n/);
|
|
22614
|
+
for (const line of lines) {
|
|
22615
|
+
const trimmed = line.trim();
|
|
22616
|
+
const match = /^task_id:\s*([^\s()]+)(?:\s*\(.*)?$/.exec(trimmed);
|
|
22617
|
+
if (!match) {
|
|
22618
|
+
continue;
|
|
22619
|
+
}
|
|
22620
|
+
return match[1];
|
|
22621
|
+
}
|
|
22622
|
+
return;
|
|
22623
|
+
}
|
|
22624
|
+
function parseTaskLaunchOutput(output) {
|
|
22625
|
+
const taskID = parseTaskIdFromTaskOutput(output);
|
|
22626
|
+
const state = parseTaskStateFromOutput(output);
|
|
22627
|
+
if (!taskID || state !== "running")
|
|
22628
|
+
return;
|
|
22629
|
+
return {
|
|
22630
|
+
taskID,
|
|
22631
|
+
state,
|
|
22632
|
+
result: parseTaskResultFromOutput(output)
|
|
22633
|
+
};
|
|
22634
|
+
}
|
|
22635
|
+
function parseTaskStatusOutput(output) {
|
|
22636
|
+
const taskID = parseTaskIdFromTaskOutput(output);
|
|
22637
|
+
const state = parseTaskStateFromOutput(output);
|
|
22638
|
+
if (!taskID || !state)
|
|
22639
|
+
return;
|
|
22640
|
+
return {
|
|
22641
|
+
taskID,
|
|
22642
|
+
state,
|
|
22643
|
+
timedOut: state === "running" && /Timed out after \d+ms/i.test(output),
|
|
22644
|
+
result: parseTaskResultFromOutput(output)
|
|
22645
|
+
};
|
|
22646
|
+
}
|
|
22647
|
+
function parseTaskStateFromOutput(output) {
|
|
22648
|
+
for (const line of getTaskHeader(output).split(/\r?\n/)) {
|
|
22649
|
+
const match = /^state:\s*(running|completed|error|cancelled)\s*$/i.exec(line.trim());
|
|
22650
|
+
if (match)
|
|
22651
|
+
return match[1].toLowerCase();
|
|
22652
|
+
}
|
|
22653
|
+
return;
|
|
22654
|
+
}
|
|
22655
|
+
function parseTaskResultFromOutput(output) {
|
|
22656
|
+
const match = /<task_(result|error)>\s*([\s\S]*?)\s*<\/task_\1>/m.exec(output);
|
|
22657
|
+
const result = match?.[2]?.trim();
|
|
22658
|
+
return result || undefined;
|
|
22659
|
+
}
|
|
22660
|
+
function getTaskHeader(output) {
|
|
22661
|
+
const resultIndex = output.search(/<task_(?:result|error)>/);
|
|
22662
|
+
if (resultIndex === -1)
|
|
22663
|
+
return output;
|
|
22664
|
+
return output.slice(0, resultIndex);
|
|
22665
|
+
}
|
|
22666
|
+
|
|
22667
|
+
// src/utils/background-job-board.ts
|
|
22668
|
+
var TERMINAL_STATES = new Set([
|
|
22669
|
+
"completed",
|
|
22670
|
+
"error",
|
|
22671
|
+
"cancelled"
|
|
22672
|
+
]);
|
|
22673
|
+
var AGENT_PREFIX = {
|
|
22674
|
+
council: "cou",
|
|
22675
|
+
designer: "des",
|
|
22676
|
+
explorer: "exp",
|
|
22677
|
+
fixer: "fix",
|
|
22678
|
+
librarian: "lib",
|
|
22679
|
+
observer: "obs",
|
|
22680
|
+
oracle: "ora"
|
|
22681
|
+
};
|
|
22682
|
+
|
|
22683
|
+
class BackgroundJobBoard {
|
|
22684
|
+
jobs = new Map;
|
|
22685
|
+
counters = new Map;
|
|
22686
|
+
registerLaunch(input) {
|
|
22687
|
+
const now = input.now ?? Date.now();
|
|
22688
|
+
const existing = this.jobs.get(input.taskID);
|
|
22689
|
+
if (existing) {
|
|
22690
|
+
const updated = {
|
|
22691
|
+
...existing,
|
|
22692
|
+
agent: input.agent || existing.agent,
|
|
22693
|
+
description: input.description || existing.description,
|
|
22694
|
+
objective: input.objective ?? existing.objective,
|
|
22695
|
+
state: "running",
|
|
22696
|
+
timedOut: false,
|
|
22697
|
+
terminalUnreconciled: false,
|
|
22698
|
+
completedAt: undefined,
|
|
22699
|
+
resultSummary: undefined,
|
|
22700
|
+
updatedAt: now
|
|
22701
|
+
};
|
|
22702
|
+
this.jobs.set(input.taskID, updated);
|
|
22703
|
+
return updated;
|
|
22704
|
+
}
|
|
22705
|
+
const record = {
|
|
22706
|
+
taskID: input.taskID,
|
|
22707
|
+
parentSessionID: input.parentSessionID,
|
|
22708
|
+
agent: input.agent,
|
|
22709
|
+
description: input.description || `background ${input.agent} task`,
|
|
22710
|
+
objective: input.objective,
|
|
22711
|
+
state: "running",
|
|
22712
|
+
timedOut: false,
|
|
22713
|
+
terminalUnreconciled: false,
|
|
22714
|
+
launchedAt: now,
|
|
22715
|
+
updatedAt: now,
|
|
22716
|
+
alias: this.nextAlias(input.parentSessionID, input.agent)
|
|
22717
|
+
};
|
|
22718
|
+
this.jobs.set(input.taskID, record);
|
|
22719
|
+
return record;
|
|
22720
|
+
}
|
|
22721
|
+
updateStatus(input) {
|
|
22722
|
+
const existing = this.jobs.get(input.taskID);
|
|
22723
|
+
if (!existing)
|
|
22724
|
+
return;
|
|
22725
|
+
if (existing.state === "reconciled") {
|
|
22726
|
+
return existing;
|
|
22727
|
+
}
|
|
22728
|
+
const now = input.now ?? Date.now();
|
|
22729
|
+
const terminal = TERMINAL_STATES.has(input.state);
|
|
22730
|
+
const updated = {
|
|
22731
|
+
...existing,
|
|
22732
|
+
state: input.state,
|
|
22733
|
+
timedOut: input.timedOut ?? false,
|
|
22734
|
+
terminalUnreconciled: terminal ? true : existing.terminalUnreconciled,
|
|
22735
|
+
updatedAt: now,
|
|
22736
|
+
completedAt: terminal ? existing.completedAt ?? now : existing.completedAt,
|
|
22737
|
+
resultSummary: input.resultSummary ?? existing.resultSummary
|
|
22738
|
+
};
|
|
22739
|
+
this.jobs.set(input.taskID, updated);
|
|
22740
|
+
return updated;
|
|
22741
|
+
}
|
|
22742
|
+
updateFromStatusOutput(output) {
|
|
22743
|
+
const status = parseTaskStatusOutput(output);
|
|
22744
|
+
if (!status)
|
|
22745
|
+
return;
|
|
22746
|
+
return this.updateStatus({
|
|
22747
|
+
taskID: status.taskID,
|
|
22748
|
+
state: status.state,
|
|
22749
|
+
timedOut: status.timedOut,
|
|
22750
|
+
resultSummary: status.result
|
|
22751
|
+
});
|
|
22752
|
+
}
|
|
22753
|
+
markReconciled(taskID, now = Date.now()) {
|
|
22754
|
+
const existing = this.jobs.get(taskID);
|
|
22755
|
+
if (!existing)
|
|
22756
|
+
return;
|
|
22757
|
+
if (!existing.terminalUnreconciled && !TERMINAL_STATES.has(existing.state)) {
|
|
22758
|
+
return;
|
|
22759
|
+
}
|
|
22760
|
+
const updated = {
|
|
22761
|
+
...existing,
|
|
22762
|
+
state: "reconciled",
|
|
22763
|
+
terminalUnreconciled: false,
|
|
22764
|
+
updatedAt: now
|
|
22765
|
+
};
|
|
22766
|
+
this.jobs.set(taskID, updated);
|
|
22767
|
+
return updated;
|
|
22768
|
+
}
|
|
22769
|
+
get(taskID) {
|
|
22770
|
+
return this.jobs.get(taskID);
|
|
22771
|
+
}
|
|
22772
|
+
list(parentSessionID) {
|
|
22773
|
+
const jobs = [...this.jobs.values()];
|
|
22774
|
+
const filtered = parentSessionID ? jobs.filter((job) => job.parentSessionID === parentSessionID) : jobs;
|
|
22775
|
+
return filtered.sort((a, b) => a.launchedAt - b.launchedAt);
|
|
22776
|
+
}
|
|
22777
|
+
hasRunning(parentSessionID) {
|
|
22778
|
+
return this.list(parentSessionID).some((job) => job.state === "running");
|
|
22779
|
+
}
|
|
22780
|
+
hasTerminalUnreconciled(parentSessionID) {
|
|
22781
|
+
return this.list(parentSessionID).some((job) => job.terminalUnreconciled);
|
|
22782
|
+
}
|
|
22783
|
+
formatForPrompt(parentSessionID) {
|
|
22784
|
+
const jobs = this.list(parentSessionID).filter((job) => job.state === "running" || job.terminalUnreconciled);
|
|
22785
|
+
if (jobs.length === 0)
|
|
22786
|
+
return;
|
|
22787
|
+
return [
|
|
22788
|
+
"### Background Job Board",
|
|
22789
|
+
"Use task_status before consuming running jobs. Reconcile terminal jobs before final response.",
|
|
22790
|
+
"",
|
|
22791
|
+
...jobs.map(formatJob)
|
|
22792
|
+
].join(`
|
|
22793
|
+
`);
|
|
22794
|
+
}
|
|
22795
|
+
clearParent(parentSessionID) {
|
|
22796
|
+
for (const job of this.list(parentSessionID)) {
|
|
22797
|
+
this.jobs.delete(job.taskID);
|
|
22798
|
+
}
|
|
22799
|
+
}
|
|
22800
|
+
drop(taskID) {
|
|
22801
|
+
this.jobs.delete(taskID);
|
|
22802
|
+
}
|
|
22803
|
+
nextAlias(parentSessionID, agent) {
|
|
22804
|
+
const prefix2 = AGENT_PREFIX[agent] ?? (agent.slice(0, 3) || "job");
|
|
22805
|
+
const key = `${parentSessionID}:${prefix2}`;
|
|
22806
|
+
const next = (this.counters.get(key) ?? 0) + 1;
|
|
22807
|
+
this.counters.set(key, next);
|
|
22808
|
+
return `${prefix2}-${next}`;
|
|
22809
|
+
}
|
|
22810
|
+
}
|
|
22811
|
+
function formatJob(job) {
|
|
22812
|
+
const status = job.terminalUnreconciled ? `${job.state}, unreconciled` : job.timedOut ? `${job.state}, timed out` : job.state;
|
|
22813
|
+
const lines = [
|
|
22814
|
+
`- ${job.alias} / ${job.taskID} / ${job.agent} / ${status}`,
|
|
22815
|
+
` Objective: ${job.objective || job.description}`
|
|
22816
|
+
];
|
|
22817
|
+
if (job.resultSummary && job.terminalUnreconciled) {
|
|
22818
|
+
lines.push(` Result: ${singleLine(job.resultSummary)}`);
|
|
22819
|
+
}
|
|
22820
|
+
return lines.join(`
|
|
22821
|
+
`);
|
|
22822
|
+
}
|
|
22823
|
+
function singleLine(value) {
|
|
22824
|
+
const normalized = value.replace(/\s+/g, " ").trim();
|
|
22825
|
+
if (normalized.length <= 160)
|
|
22826
|
+
return normalized;
|
|
22827
|
+
return `${normalized.slice(0, 157)}...`;
|
|
22828
|
+
}
|
|
22603
22829
|
// src/utils/internal-initiator.ts
|
|
22604
22830
|
var SLIM_INTERNAL_INITIATOR_MARKER = "<!-- SLIM_INTERNAL_INITIATOR -->";
|
|
22605
22831
|
function isRecord(value) {
|
|
@@ -22783,7 +23009,7 @@ class SessionManager {
|
|
|
22783
23009
|
return;
|
|
22784
23010
|
return [
|
|
22785
23011
|
"### Resumable Sessions",
|
|
22786
|
-
"Reuse only
|
|
23012
|
+
"Reuse only completed/reconciled threads. Poll running jobs from Background Job Board.",
|
|
22787
23013
|
"",
|
|
22788
23014
|
...lines
|
|
22789
23015
|
].join(`
|
|
@@ -22851,19 +23077,6 @@ function formatContextFiles(files, options) {
|
|
|
22851
23077
|
const rendered = shown.map((file) => `${file.path} (${file.lineCount} lines)`);
|
|
22852
23078
|
return `${rendered.join(", ")}${rest > 0 ? ` (+${rest} more)` : ""}`;
|
|
22853
23079
|
}
|
|
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
23080
|
// src/utils/zip-extractor.ts
|
|
22868
23081
|
import { spawnSync } from "node:child_process";
|
|
22869
23082
|
import { release } from "node:os";
|
|
@@ -23677,14 +23890,13 @@ function createPhaseReminderHook() {
|
|
|
23677
23890
|
if (originalText.includes(SLIM_INTERNAL_INITIATOR_MARKER)) {
|
|
23678
23891
|
return;
|
|
23679
23892
|
}
|
|
23680
|
-
if (
|
|
23893
|
+
if (lastUserMessage.parts.some((p) => p.text?.includes(PHASE_REMINDER))) {
|
|
23681
23894
|
return;
|
|
23682
23895
|
}
|
|
23683
|
-
lastUserMessage.parts
|
|
23684
|
-
|
|
23685
|
-
|
|
23686
|
-
|
|
23687
|
-
${PHASE_REMINDER}`;
|
|
23896
|
+
lastUserMessage.parts.push({
|
|
23897
|
+
type: "text",
|
|
23898
|
+
text: PHASE_REMINDER
|
|
23899
|
+
});
|
|
23688
23900
|
}
|
|
23689
23901
|
};
|
|
23690
23902
|
}
|
|
@@ -24066,6 +24278,34 @@ var AGENT_NAME_SET = new Set([
|
|
|
24066
24278
|
var MAX_PENDING_TASK_CALLS = 100;
|
|
24067
24279
|
var RESUMABLE_SESSIONS_START = "<resumable_sessions>";
|
|
24068
24280
|
var RESUMABLE_SESSIONS_END = "</resumable_sessions>";
|
|
24281
|
+
var BACKGROUND_COMPLETION_COMPLETED = /^Background task completed: /;
|
|
24282
|
+
var BACKGROUND_COMPLETION_FAILED = /^Background task failed: /;
|
|
24283
|
+
var MAX_PROCESSED_INJECTED_COMPLETIONS = 500;
|
|
24284
|
+
function djb2Hash(str) {
|
|
24285
|
+
let hash = 5381;
|
|
24286
|
+
for (let i = 0;i < str.length; i++) {
|
|
24287
|
+
hash = (hash << 5) + hash + str.charCodeAt(i);
|
|
24288
|
+
}
|
|
24289
|
+
return (hash >>> 0).toString(16).padStart(8, "0");
|
|
24290
|
+
}
|
|
24291
|
+
function createOccurrenceId(part, message, partIndex) {
|
|
24292
|
+
if (typeof part.id === "string") {
|
|
24293
|
+
return part.id;
|
|
24294
|
+
}
|
|
24295
|
+
if (typeof message.info.id === "string") {
|
|
24296
|
+
return `${message.info.id}:${partIndex}`;
|
|
24297
|
+
}
|
|
24298
|
+
const sessionID = message.info.sessionID ?? "unknown";
|
|
24299
|
+
const content = typeof part.text === "string" ? part.text : "";
|
|
24300
|
+
const status = parseTaskStatusOutput(content);
|
|
24301
|
+
if (status) {
|
|
24302
|
+
const stableKey = `${sessionID}:${status.taskID}:${status.state}:${status.result ?? ""}`;
|
|
24303
|
+
const hash2 = djb2Hash(stableKey);
|
|
24304
|
+
return `anon:${hash2}`;
|
|
24305
|
+
}
|
|
24306
|
+
const hash = djb2Hash(`${sessionID}:${content}`);
|
|
24307
|
+
return `anon:${hash}`;
|
|
24308
|
+
}
|
|
24069
24309
|
function isAgentName(value) {
|
|
24070
24310
|
return typeof value === "string" && AGENT_NAME_SET.has(value);
|
|
24071
24311
|
}
|
|
@@ -24109,10 +24349,14 @@ function createTaskSessionManagerHook(_ctx, options) {
|
|
|
24109
24349
|
readContextMinLines: options.readContextMinLines,
|
|
24110
24350
|
readContextMaxFiles: options.readContextMaxFiles
|
|
24111
24351
|
});
|
|
24352
|
+
const backgroundJobBoard = options.backgroundJobBoard ?? new BackgroundJobBoard;
|
|
24112
24353
|
const pendingCalls = new Map;
|
|
24113
24354
|
const pendingCallOrder = [];
|
|
24114
24355
|
const contextByTask = new Map;
|
|
24115
24356
|
const pendingManagedTaskIds = new Set;
|
|
24357
|
+
const terminalJobsInjectedByParent = new Map;
|
|
24358
|
+
const processedInjectedCompletions = new Set;
|
|
24359
|
+
const processedInjectedCompletionOrder = [];
|
|
24116
24360
|
let anonymousPendingCallId = 0;
|
|
24117
24361
|
function addTaskContext(taskId, files) {
|
|
24118
24362
|
if (files.length === 0)
|
|
@@ -24156,6 +24400,62 @@ function createTaskSessionManagerHook(_ctx, options) {
|
|
|
24156
24400
|
}
|
|
24157
24401
|
}
|
|
24158
24402
|
}
|
|
24403
|
+
function updateBackgroundJobFromOutput(output) {
|
|
24404
|
+
if (typeof output !== "string")
|
|
24405
|
+
return;
|
|
24406
|
+
const status = parseTaskStatusOutput(output);
|
|
24407
|
+
if (!status)
|
|
24408
|
+
return;
|
|
24409
|
+
const updated = backgroundJobBoard.updateStatus({
|
|
24410
|
+
taskID: status.taskID,
|
|
24411
|
+
state: status.state,
|
|
24412
|
+
timedOut: status.timedOut,
|
|
24413
|
+
resultSummary: status.result
|
|
24414
|
+
});
|
|
24415
|
+
if (!updated)
|
|
24416
|
+
return;
|
|
24417
|
+
if (updated.terminalUnreconciled) {
|
|
24418
|
+
pendingManagedTaskIds.delete(updated.taskID);
|
|
24419
|
+
contextByTask.delete(updated.taskID);
|
|
24420
|
+
pruneContext();
|
|
24421
|
+
}
|
|
24422
|
+
return updated;
|
|
24423
|
+
}
|
|
24424
|
+
function updateFromInjectedCompletion(part, message, _messageIndex, partIndex) {
|
|
24425
|
+
if (part.type !== "text" || typeof part.text !== "string") {
|
|
24426
|
+
return;
|
|
24427
|
+
}
|
|
24428
|
+
const isCompleted = BACKGROUND_COMPLETION_COMPLETED.test(part.text);
|
|
24429
|
+
const isFailed = BACKGROUND_COMPLETION_FAILED.test(part.text);
|
|
24430
|
+
if (part.synthetic !== true || !isCompleted && !isFailed) {
|
|
24431
|
+
return;
|
|
24432
|
+
}
|
|
24433
|
+
const status = parseTaskStatusOutput(part.text);
|
|
24434
|
+
if (!status)
|
|
24435
|
+
return;
|
|
24436
|
+
if (isCompleted && status.state !== "completed")
|
|
24437
|
+
return;
|
|
24438
|
+
if (isFailed && status.state !== "error")
|
|
24439
|
+
return;
|
|
24440
|
+
const occurrenceId = createOccurrenceId(part, message, partIndex);
|
|
24441
|
+
if (processedInjectedCompletions.has(occurrenceId))
|
|
24442
|
+
return;
|
|
24443
|
+
const updated = updateBackgroundJobFromOutput(part.text);
|
|
24444
|
+
if (!updated)
|
|
24445
|
+
return;
|
|
24446
|
+
rememberProcessedInjectedCompletion(occurrenceId);
|
|
24447
|
+
return updated;
|
|
24448
|
+
}
|
|
24449
|
+
function rememberProcessedInjectedCompletion(signature) {
|
|
24450
|
+
processedInjectedCompletions.add(signature);
|
|
24451
|
+
processedInjectedCompletionOrder.push(signature);
|
|
24452
|
+
while (processedInjectedCompletionOrder.length > MAX_PROCESSED_INJECTED_COMPLETIONS) {
|
|
24453
|
+
const evicted = processedInjectedCompletionOrder.shift();
|
|
24454
|
+
if (!evicted)
|
|
24455
|
+
break;
|
|
24456
|
+
processedInjectedCompletions.delete(evicted);
|
|
24457
|
+
}
|
|
24458
|
+
}
|
|
24159
24459
|
function isMissingRememberedSessionError(output) {
|
|
24160
24460
|
const firstLine = output.split(/\r?\n/, 1)[0]?.trim().toLowerCase() ?? "";
|
|
24161
24461
|
return firstLine.startsWith("[error]") && firstLine.includes("session") && (firstLine.includes("not found") || firstLine.includes("no session"));
|
|
@@ -24195,6 +24495,25 @@ function createTaskSessionManagerHook(_ctx, options) {
|
|
|
24195
24495
|
return;
|
|
24196
24496
|
return pendingCallOrder.find((callId) => pendingCalls.get(callId)?.parentSessionId === parentSessionId);
|
|
24197
24497
|
}
|
|
24498
|
+
function rememberInjectedTerminalJobs(parentSessionID) {
|
|
24499
|
+
const taskIDs = backgroundJobBoard.list(parentSessionID).filter((job) => job.terminalUnreconciled).map((job) => job.taskID);
|
|
24500
|
+
if (taskIDs.length === 0)
|
|
24501
|
+
return;
|
|
24502
|
+
const existing = terminalJobsInjectedByParent.get(parentSessionID) ?? new Set;
|
|
24503
|
+
for (const taskID of taskIDs) {
|
|
24504
|
+
existing.add(taskID);
|
|
24505
|
+
}
|
|
24506
|
+
terminalJobsInjectedByParent.set(parentSessionID, existing);
|
|
24507
|
+
}
|
|
24508
|
+
function reconcileInjectedTerminalJobs(parentSessionID) {
|
|
24509
|
+
const taskIDs = terminalJobsInjectedByParent.get(parentSessionID);
|
|
24510
|
+
if (!taskIDs)
|
|
24511
|
+
return;
|
|
24512
|
+
for (const taskID of taskIDs) {
|
|
24513
|
+
backgroundJobBoard.markReconciled(taskID);
|
|
24514
|
+
}
|
|
24515
|
+
terminalJobsInjectedByParent.delete(parentSessionID);
|
|
24516
|
+
}
|
|
24198
24517
|
return {
|
|
24199
24518
|
"tool.execute.before": async (input, output) => {
|
|
24200
24519
|
if (input.tool.toLowerCase() !== "task")
|
|
@@ -24244,11 +24563,31 @@ function createTaskSessionManagerHook(_ctx, options) {
|
|
|
24244
24563
|
}
|
|
24245
24564
|
return;
|
|
24246
24565
|
}
|
|
24566
|
+
if (input.tool.toLowerCase() === "task_status") {
|
|
24567
|
+
if (!input.sessionID || !options.shouldManageSession(input.sessionID)) {
|
|
24568
|
+
return;
|
|
24569
|
+
}
|
|
24570
|
+
updateBackgroundJobFromOutput(output.output);
|
|
24571
|
+
return;
|
|
24572
|
+
}
|
|
24247
24573
|
if (input.tool.toLowerCase() !== "task")
|
|
24248
24574
|
return;
|
|
24249
24575
|
const pending = takePendingCall(input.callID, input.sessionID);
|
|
24250
24576
|
if (!pending || typeof output.output !== "string")
|
|
24251
24577
|
return;
|
|
24578
|
+
const launch = parseTaskLaunchOutput(output.output);
|
|
24579
|
+
if (launch) {
|
|
24580
|
+
backgroundJobBoard.registerLaunch({
|
|
24581
|
+
taskID: launch.taskID,
|
|
24582
|
+
parentSessionID: pending.parentSessionId,
|
|
24583
|
+
agent: pending.agentType,
|
|
24584
|
+
description: pending.label,
|
|
24585
|
+
objective: pending.label
|
|
24586
|
+
});
|
|
24587
|
+
sessionManager.drop(pending.parentSessionId, pending.agentType, pending.resumedTaskId ?? launch.taskID);
|
|
24588
|
+
pendingManagedTaskIds.add(launch.taskID);
|
|
24589
|
+
return;
|
|
24590
|
+
}
|
|
24252
24591
|
const taskId = parseTaskIdFromTaskOutput(output.output);
|
|
24253
24592
|
if (!taskId) {
|
|
24254
24593
|
if (pending.resumedTaskId && isMissingRememberedSessionError(output.output)) {
|
|
@@ -24271,6 +24610,19 @@ function createTaskSessionManagerHook(_ctx, options) {
|
|
|
24271
24610
|
pruneContext();
|
|
24272
24611
|
},
|
|
24273
24612
|
"experimental.chat.messages.transform": async (_input, output) => {
|
|
24613
|
+
for (const [messageIndex, message] of output.messages.entries()) {
|
|
24614
|
+
if (message.info.role !== "user")
|
|
24615
|
+
continue;
|
|
24616
|
+
if (message.info.agent && message.info.agent !== "orchestrator") {
|
|
24617
|
+
continue;
|
|
24618
|
+
}
|
|
24619
|
+
if (!message.info.sessionID || !options.shouldManageSession(message.info.sessionID)) {
|
|
24620
|
+
continue;
|
|
24621
|
+
}
|
|
24622
|
+
for (const [partIndex, part] of message.parts.entries()) {
|
|
24623
|
+
updateFromInjectedCompletion(part, message, messageIndex, partIndex);
|
|
24624
|
+
}
|
|
24625
|
+
}
|
|
24274
24626
|
for (let i = output.messages.length - 1;i >= 0; i -= 1) {
|
|
24275
24627
|
const message = output.messages[i];
|
|
24276
24628
|
if (message.info.role !== "user")
|
|
@@ -24280,8 +24632,11 @@ function createTaskSessionManagerHook(_ctx, options) {
|
|
|
24280
24632
|
if (!message.info.sessionID || !options.shouldManageSession(message.info.sessionID)) {
|
|
24281
24633
|
return;
|
|
24282
24634
|
}
|
|
24283
|
-
const
|
|
24284
|
-
|
|
24635
|
+
const reminders = [
|
|
24636
|
+
backgroundJobBoard.formatForPrompt(message.info.sessionID),
|
|
24637
|
+
sessionManager.formatForPrompt(message.info.sessionID)
|
|
24638
|
+
].filter((item) => Boolean(item));
|
|
24639
|
+
if (reminders.length === 0)
|
|
24285
24640
|
return;
|
|
24286
24641
|
const textPart = message.parts.find((part) => part.type === "text" && typeof part.text === "string");
|
|
24287
24642
|
if (!textPart)
|
|
@@ -24290,11 +24645,14 @@ function createTaskSessionManagerHook(_ctx, options) {
|
|
|
24290
24645
|
return;
|
|
24291
24646
|
if (textPart.text?.includes(RESUMABLE_SESSIONS_START))
|
|
24292
24647
|
return;
|
|
24648
|
+
rememberInjectedTerminalJobs(message.info.sessionID);
|
|
24293
24649
|
textPart.text = [
|
|
24294
24650
|
textPart.text ?? "",
|
|
24295
24651
|
"",
|
|
24296
24652
|
RESUMABLE_SESSIONS_START,
|
|
24297
|
-
|
|
24653
|
+
reminders.join(`
|
|
24654
|
+
|
|
24655
|
+
`),
|
|
24298
24656
|
RESUMABLE_SESSIONS_END
|
|
24299
24657
|
].join(`
|
|
24300
24658
|
`);
|
|
@@ -24309,6 +24667,20 @@ function createTaskSessionManagerHook(_ctx, options) {
|
|
|
24309
24667
|
}
|
|
24310
24668
|
return;
|
|
24311
24669
|
}
|
|
24670
|
+
if (input.event.type === "session.idle" || input.event.type === "session.status" && input.event.properties?.status?.type === "idle") {
|
|
24671
|
+
const sessionId2 = input.event.properties?.info?.id ?? input.event.properties?.sessionID;
|
|
24672
|
+
if (sessionId2 && options.shouldManageSession(sessionId2)) {
|
|
24673
|
+
reconcileInjectedTerminalJobs(sessionId2);
|
|
24674
|
+
}
|
|
24675
|
+
return;
|
|
24676
|
+
}
|
|
24677
|
+
if (input.event.type === "session.error") {
|
|
24678
|
+
const sessionId2 = input.event.properties?.info?.id ?? input.event.properties?.sessionID;
|
|
24679
|
+
if (sessionId2 && options.shouldManageSession(sessionId2)) {
|
|
24680
|
+
terminalJobsInjectedByParent.delete(sessionId2);
|
|
24681
|
+
}
|
|
24682
|
+
return;
|
|
24683
|
+
}
|
|
24312
24684
|
if (input.event.type !== "session.deleted")
|
|
24313
24685
|
return;
|
|
24314
24686
|
const sessionId = input.event.properties?.info?.id ?? input.event.properties?.sessionID;
|
|
@@ -24316,6 +24688,8 @@ function createTaskSessionManagerHook(_ctx, options) {
|
|
|
24316
24688
|
return;
|
|
24317
24689
|
sessionManager.dropTask(sessionId);
|
|
24318
24690
|
sessionManager.clearParent(sessionId);
|
|
24691
|
+
backgroundJobBoard.clearParent(sessionId);
|
|
24692
|
+
terminalJobsInjectedByParent.delete(sessionId);
|
|
24319
24693
|
contextByTask.delete(sessionId);
|
|
24320
24694
|
pendingManagedTaskIds.delete(sessionId);
|
|
24321
24695
|
pruneContext();
|
|
@@ -24544,6 +24918,7 @@ function createTodoContinuationHook(ctx, config) {
|
|
|
24544
24918
|
const cooldownMs = config?.cooldownMs ?? 3000;
|
|
24545
24919
|
const autoEnable = config?.autoEnable ?? false;
|
|
24546
24920
|
const autoEnableThreshold = config?.autoEnableThreshold ?? 4;
|
|
24921
|
+
const backgroundJobBoard = config?.backgroundJobBoard;
|
|
24547
24922
|
const requestSignatureBySession = new Map;
|
|
24548
24923
|
const state = {
|
|
24549
24924
|
enabled: false,
|
|
@@ -24655,8 +25030,10 @@ function createTodoContinuationHook(ctx, config) {
|
|
|
24655
25030
|
}
|
|
24656
25031
|
if (requestSignatureBySession.get(lastUserMessage.sessionID) === lastUserMessage.signature) {
|
|
24657
25032
|
const reminder = hygiene.getPendingReminder(lastUserMessage.sessionID);
|
|
24658
|
-
|
|
24659
|
-
|
|
25033
|
+
const guardrail = backgroundGuardrail(lastUserMessage.sessionID);
|
|
25034
|
+
const combinedReminder = [reminder, guardrail].filter((item) => Boolean(item)).join(" ");
|
|
25035
|
+
if (combinedReminder) {
|
|
25036
|
+
appendTodoHygieneInstruction(lastUserMessage.message, combinedReminder);
|
|
24660
25037
|
} else {
|
|
24661
25038
|
stripTodoHygieneInstructionFromMessage(lastUserMessage.message);
|
|
24662
25039
|
}
|
|
@@ -24694,6 +25071,28 @@ function createTodoContinuationHook(ctx, config) {
|
|
|
24694
25071
|
function registerOrchestratorSession(sessionID) {
|
|
24695
25072
|
state.orchestratorSessionIds.add(sessionID);
|
|
24696
25073
|
}
|
|
25074
|
+
function backgroundGuardrail(sessionID) {
|
|
25075
|
+
if (!backgroundJobBoard)
|
|
25076
|
+
return;
|
|
25077
|
+
const hasRunning = backgroundJobBoard.hasRunning(sessionID);
|
|
25078
|
+
const hasTerminal = backgroundJobBoard.hasTerminalUnreconciled(sessionID);
|
|
25079
|
+
if (hasRunning && hasTerminal) {
|
|
25080
|
+
return "Background jobs are still unresolved: call task_status for running jobs and reconcile terminal Background Job Board results before dependent work or finalizing.";
|
|
25081
|
+
}
|
|
25082
|
+
if (hasTerminal) {
|
|
25083
|
+
return "Background jobs have terminal results: reconcile the Background Job Board results before finalizing.";
|
|
25084
|
+
}
|
|
25085
|
+
if (hasRunning) {
|
|
25086
|
+
return "Background jobs are still running: call task_status before dependent work or finalizing.";
|
|
25087
|
+
}
|
|
25088
|
+
return;
|
|
25089
|
+
}
|
|
25090
|
+
function continuationPrompt(sessionID) {
|
|
25091
|
+
const guardrail = backgroundGuardrail(sessionID);
|
|
25092
|
+
if (!guardrail)
|
|
25093
|
+
return CONTINUATION_PROMPT;
|
|
25094
|
+
return `${CONTINUATION_PROMPT} ${guardrail}`;
|
|
25095
|
+
}
|
|
24697
25096
|
function handleChatMessage(input) {
|
|
24698
25097
|
if (!input.agent) {
|
|
24699
25098
|
return;
|
|
@@ -24885,7 +25284,9 @@ function createTodoContinuationHook(ctx, config) {
|
|
|
24885
25284
|
await ctx.client.session.prompt({
|
|
24886
25285
|
path: { id: sessionID },
|
|
24887
25286
|
body: {
|
|
24888
|
-
parts: [
|
|
25287
|
+
parts: [
|
|
25288
|
+
createInternalAgentTextPart(continuationPrompt(sessionID))
|
|
25289
|
+
]
|
|
24889
25290
|
}
|
|
24890
25291
|
});
|
|
24891
25292
|
state.consecutiveContinuations++;
|
|
@@ -24996,7 +25397,7 @@ function createTodoContinuationHook(ctx, config) {
|
|
|
24996
25397
|
});
|
|
24997
25398
|
}
|
|
24998
25399
|
if (hasIncompleteTodos) {
|
|
24999
|
-
output.parts.push(createInternalAgentTextPart(`${
|
|
25400
|
+
output.parts.push(createInternalAgentTextPart(`${continuationPrompt(input.sessionID)} [Auto-continue enabled: up to ${maxContinuations} continuations.]`));
|
|
25000
25401
|
} else {
|
|
25001
25402
|
output.parts.push(createInternalAgentTextPart(`[Auto-continue: enabled for up to ${maxContinuations} continuations. No incomplete todos right now.]`));
|
|
25002
25403
|
}
|
|
@@ -33436,6 +33837,7 @@ var OhMyOpenCodeLite = async (ctx) => {
|
|
|
33436
33837
|
let todoContinuationHook;
|
|
33437
33838
|
let sessionGoalHook;
|
|
33438
33839
|
let taskSessionManagerHook;
|
|
33840
|
+
let backgroundJobBoard;
|
|
33439
33841
|
let interviewManager;
|
|
33440
33842
|
let presetManager;
|
|
33441
33843
|
let divoomManager;
|
|
@@ -33521,11 +33923,13 @@ var OhMyOpenCodeLite = async (ctx) => {
|
|
|
33521
33923
|
applyPatchHook = createApplyPatchHook(ctx);
|
|
33522
33924
|
jsonErrorRecoveryHook = createJsonErrorRecoveryHook(ctx);
|
|
33523
33925
|
foregroundFallback = new ForegroundFallbackManager(ctx.client, runtimeChains, config.fallback?.enabled !== false && Object.keys(runtimeChains).length > 0);
|
|
33926
|
+
backgroundJobBoard = new BackgroundJobBoard;
|
|
33524
33927
|
todoContinuationHook = createTodoContinuationHook(ctx, {
|
|
33525
33928
|
maxContinuations: config.todoContinuation?.maxContinuations ?? 5,
|
|
33526
33929
|
cooldownMs: config.todoContinuation?.cooldownMs ?? 3000,
|
|
33527
33930
|
autoEnable: config.todoContinuation?.autoEnable ?? false,
|
|
33528
|
-
autoEnableThreshold: config.todoContinuation?.autoEnableThreshold ?? 4
|
|
33931
|
+
autoEnableThreshold: config.todoContinuation?.autoEnableThreshold ?? 4,
|
|
33932
|
+
backgroundJobBoard
|
|
33529
33933
|
});
|
|
33530
33934
|
sessionGoalHook = createSessionGoalHook(ctx, config, {
|
|
33531
33935
|
getAgentName: (sessionID) => sessionAgentMap.get(sessionID)
|
|
@@ -33534,6 +33938,7 @@ var OhMyOpenCodeLite = async (ctx) => {
|
|
|
33534
33938
|
maxSessionsPerAgent: config.sessionManager?.maxSessionsPerAgent ?? 2,
|
|
33535
33939
|
readContextMinLines: config.sessionManager?.readContextMinLines ?? 10,
|
|
33536
33940
|
readContextMaxFiles: config.sessionManager?.readContextMaxFiles ?? 8,
|
|
33941
|
+
backgroundJobBoard,
|
|
33537
33942
|
shouldManageSession: (sessionID) => sessionAgentMap.get(sessionID) === "orchestrator"
|
|
33538
33943
|
});
|
|
33539
33944
|
interviewManager = createInterviewManager(ctx, config);
|