oh-my-opencode-slim 1.0.3 → 1.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/config-io.d.ts +1 -0
- package/dist/cli/index.js +35 -1
- package/dist/cli/install.d.ts +1 -1
- package/dist/config/constants.d.ts +1 -1
- package/dist/config/index.d.ts +1 -1
- package/dist/config/loader.d.ts +9 -0
- package/dist/config/runtime-preset.d.ts +12 -0
- package/dist/hooks/apply-patch/matching.d.ts +9 -1
- package/dist/hooks/apply-patch/rewrite.d.ts +0 -3
- package/dist/hooks/phase-reminder/index.d.ts +1 -1
- package/dist/hooks/post-file-tool-nudge/index.d.ts +3 -19
- package/dist/hooks/task-session-manager/index.d.ts +16 -4
- package/dist/hooks/todo-continuation/index.d.ts +2 -5
- package/dist/hooks/todo-continuation/todo-hygiene.d.ts +2 -8
- package/dist/index.js +589 -277
- package/dist/multiplexer/session-manager.d.ts +3 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -18316,7 +18316,7 @@ var DEFAULT_TIMEOUT_MS = 2 * 60 * 1000;
|
|
|
18316
18316
|
var MAX_POLL_TIME_MS = 5 * 60 * 1000;
|
|
18317
18317
|
var DEFAULT_MAX_SUBAGENT_DEPTH = 3;
|
|
18318
18318
|
var PHASE_REMINDER_TEXT = `!IMPORTANT! Recall the workflow rules:
|
|
18319
|
-
Understand → choose the best parallelized path based on your capabilities and agents delegation rules → execute → verify.
|
|
18319
|
+
Understand → choose the best parallelized path based on your capabilities and agents delegation rules → recall session reuse rules → execute → verify.
|
|
18320
18320
|
If delegating, launch the specialist in the same turn you mention it !END!`;
|
|
18321
18321
|
var TMUX_SPAWN_DELAY_MS = 500;
|
|
18322
18322
|
var COUNCILLOR_STAGGER_MS = 250;
|
|
@@ -18854,14 +18854,15 @@ var AGENT_DESCRIPTIONS = {
|
|
|
18854
18854
|
- **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
|
|
18855
18855
|
- **Rule of thumb:** Explaining > doing? → yourself. Test file modifications and bounded implementation work usually go to @fixer. Bigger or lots of edits, splitting makes sense, parallelized by spawning @fixers per certain scope.`,
|
|
18856
18856
|
council: `@council
|
|
18857
|
-
- Role: Multi-LLM consensus engine
|
|
18857
|
+
- Role: Multi-LLM consensus engine that runs several councillors, synthesizes their views, and returns a structured council report.
|
|
18858
18858
|
- Permissions: Read files
|
|
18859
18859
|
- Stats: 3x slower than orchestrator, 3x or more cost of orchestrator
|
|
18860
|
-
- Capabilities: Runs multiple models in parallel,
|
|
18861
|
-
- **Delegate when:** Critical decisions
|
|
18862
|
-
- **Don't delegate when:** Straightforward tasks you're confident about • Speed matters more than confidence •
|
|
18863
|
-
- **
|
|
18864
|
-
- **
|
|
18860
|
+
- Capabilities: Runs multiple models in parallel, compares their answers, resolves disagreements, and produces a final synthesized answer plus councillor details and consensus summary.
|
|
18861
|
+
- **Delegate when:** Critical decisions need multiple independent perspectives • High-stakes architectural/security/data-integrity choices • Ambiguous problems where disagreement is useful signal • You want confidence beyond a single model • The user explicitly asks for council/consensus/multiple opinions.
|
|
18862
|
+
- **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.
|
|
18863
|
+
- **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.
|
|
18864
|
+
- **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.
|
|
18865
|
+
- **Rule of thumb:** Need second/third opinions from different models? → @council. Need one expert agent or direct execution? → use the specialist or yourself.`,
|
|
18865
18866
|
observer: `@observer
|
|
18866
18867
|
- Role: Visual analysis specialist for images, PDFs, and diagrams
|
|
18867
18868
|
- Permissions: Read files
|
|
@@ -18953,10 +18954,10 @@ Balance: respect dependencies, avoid parallelizing what must be sequential.
|
|
|
18953
18954
|
5. Adjust if needed
|
|
18954
18955
|
|
|
18955
18956
|
### Session Reuse
|
|
18956
|
-
-
|
|
18957
|
-
-
|
|
18957
|
+
- Smartly reuse an available specialist session - constext reuse saves time and tokens
|
|
18958
|
+
- When too much unrelated, and really needed, start a fresh session with the specialist
|
|
18958
18959
|
- If multiple remembered sessions fit, prefer the most recently used matching session.
|
|
18959
|
-
-
|
|
18960
|
+
- Prefer re-uses over creating new sessions all the time
|
|
18960
18961
|
|
|
18961
18962
|
### Auto-Continue
|
|
18962
18963
|
When working through multi-step tasks, consider enabling auto-continue to avoid stopping between batches:
|
|
@@ -19044,27 +19045,47 @@ var COUNCIL_AGENT_PROMPT = `You are the Council agent — a multi-LLM orchestrat
|
|
|
19044
19045
|
1. Call the \`council_session\` tool with the user's prompt
|
|
19045
19046
|
2. Optionally specify a preset (default: "default")
|
|
19046
19047
|
3. Receive the councillor responses formatted for synthesis
|
|
19047
|
-
4.
|
|
19048
|
-
5. Present the
|
|
19049
|
-
|
|
19050
|
-
**Synthesis
|
|
19051
|
-
|
|
19052
|
-
|
|
19053
|
-
|
|
19054
|
-
|
|
19055
|
-
|
|
19056
|
-
|
|
19057
|
-
- If councillors disagree, explain the resolution and your reasoning
|
|
19058
|
-
- Acknowledge if consensus was impossible and explain why
|
|
19059
|
-
- Don't just average responses — choose the best approach and improve upon it
|
|
19060
|
-
- Present the synthesized solution with relevant code examples, concrete details, and clear explanations
|
|
19048
|
+
4. Follow the Synthesis Process below
|
|
19049
|
+
5. Present the result to the user
|
|
19050
|
+
|
|
19051
|
+
**Synthesis Process** (MANDATORY — follow in order):
|
|
19052
|
+
1. Read the original user prompt
|
|
19053
|
+
2. Review each councillor's response individually — note each councillor's key insight and unique contribution by name
|
|
19054
|
+
3. Identify agreements and contradictions between councillors
|
|
19055
|
+
4. Resolve contradictions with explicit reasoning
|
|
19056
|
+
5. Synthesize the optimal final answer
|
|
19057
|
+
6. Format output per the Required Output Format below
|
|
19061
19058
|
|
|
19062
19059
|
**Behavior**:
|
|
19063
19060
|
- Delegate requests directly to council_session
|
|
19064
19061
|
- Don't pre-analyze or filter the prompt before calling council_session
|
|
19065
|
-
-
|
|
19066
|
-
-
|
|
19067
|
-
-
|
|
19062
|
+
- Credit specific insights from individual councillors using their names
|
|
19063
|
+
- If councillors disagree, explain why you chose one approach over another
|
|
19064
|
+
- Do not omit per-councillor details from the final response
|
|
19065
|
+
- Do not collapse the output into only a final summary
|
|
19066
|
+
- Be transparent about trade-offs when different approaches have valid pros/cons
|
|
19067
|
+
- Don't just average responses — choose the best approach and improve upon it
|
|
19068
|
+
|
|
19069
|
+
**Required Output Format**:
|
|
19070
|
+
Always include these sections in your final response:
|
|
19071
|
+
|
|
19072
|
+
## Council Response
|
|
19073
|
+
Provide the best synthesized answer. Integrate the strongest points from the councillors, resolve disagreements, and give the user a clear final recommendation or answer. Include relevant code examples and concrete details.
|
|
19074
|
+
|
|
19075
|
+
## Councillor Details
|
|
19076
|
+
Include each councillor's response separately.
|
|
19077
|
+
|
|
19078
|
+
Use each councillor name exactly as provided in the tool result.
|
|
19079
|
+
|
|
19080
|
+
Format each councillor like:
|
|
19081
|
+
|
|
19082
|
+
### <councillor name>
|
|
19083
|
+
<that councillor's response>
|
|
19084
|
+
|
|
19085
|
+
If a councillor failed or timed out, include that status briefly.
|
|
19086
|
+
|
|
19087
|
+
## Council Summary
|
|
19088
|
+
Summarize where councillors agreed, where they disagreed, why you chose the final answer, and any remaining uncertainty. Include a consensus confidence rating: unanimous, majority, or split.`;
|
|
19068
19089
|
function createCouncilAgent(model, customPrompt, customAppendPrompt) {
|
|
19069
19090
|
const prompt = resolvePrompt(COUNCIL_AGENT_PROMPT, customPrompt, customAppendPrompt);
|
|
19070
19091
|
const definition = {
|
|
@@ -19137,7 +19158,7 @@ ${failedSection}`;
|
|
|
19137
19158
|
|
|
19138
19159
|
---
|
|
19139
19160
|
|
|
19140
|
-
|
|
19161
|
+
You MUST follow the Synthesis Process steps before producing output: review each councillor response individually, then produce the required output with a synthesized Council Response, per-councillor details using their exact names, and a Council Summary with consensus confidence rating (unanimous, majority, or split).`;
|
|
19141
19162
|
return prompt;
|
|
19142
19163
|
}
|
|
19143
19164
|
|
|
@@ -19769,6 +19790,27 @@ function getDisabledAgents(config) {
|
|
|
19769
19790
|
return disabled;
|
|
19770
19791
|
}
|
|
19771
19792
|
|
|
19793
|
+
// src/config/runtime-preset.ts
|
|
19794
|
+
var activeRuntimePreset = null;
|
|
19795
|
+
function setActiveRuntimePreset(name) {
|
|
19796
|
+
activeRuntimePreset = name;
|
|
19797
|
+
}
|
|
19798
|
+
function getActiveRuntimePreset() {
|
|
19799
|
+
return activeRuntimePreset;
|
|
19800
|
+
}
|
|
19801
|
+
var previousRuntimePreset = null;
|
|
19802
|
+
function getPreviousRuntimePreset() {
|
|
19803
|
+
return previousRuntimePreset;
|
|
19804
|
+
}
|
|
19805
|
+
function setActiveRuntimePresetWithPrevious(name) {
|
|
19806
|
+
previousRuntimePreset = activeRuntimePreset;
|
|
19807
|
+
activeRuntimePreset = name;
|
|
19808
|
+
}
|
|
19809
|
+
function rollbackRuntimePreset(previous) {
|
|
19810
|
+
activeRuntimePreset = previous;
|
|
19811
|
+
previousRuntimePreset = null;
|
|
19812
|
+
}
|
|
19813
|
+
|
|
19772
19814
|
// src/utils/logger.ts
|
|
19773
19815
|
import * as fs2 from "node:fs";
|
|
19774
19816
|
import { appendFile } from "node:fs/promises";
|
|
@@ -20214,6 +20256,16 @@ function unexpectedPatchLine(context, line) {
|
|
|
20214
20256
|
const rendered = line.length === 0 ? "<empty>" : line;
|
|
20215
20257
|
throw new Error(`Invalid patch format: unexpected line ${context}: ${rendered}`);
|
|
20216
20258
|
}
|
|
20259
|
+
function parseChangeContext(line) {
|
|
20260
|
+
const context = line.slice(2);
|
|
20261
|
+
if (context.length === 0) {
|
|
20262
|
+
return;
|
|
20263
|
+
}
|
|
20264
|
+
return context.startsWith(" ") ? context.slice(1) || undefined : context;
|
|
20265
|
+
}
|
|
20266
|
+
function isPatchBoundary(line, marker) {
|
|
20267
|
+
return line.trimEnd() === marker;
|
|
20268
|
+
}
|
|
20217
20269
|
function parseChunks(lines, index, mode) {
|
|
20218
20270
|
const chunks = [];
|
|
20219
20271
|
let at = index;
|
|
@@ -20225,7 +20277,7 @@ function parseChunks(lines, index, mode) {
|
|
|
20225
20277
|
at += 1;
|
|
20226
20278
|
continue;
|
|
20227
20279
|
}
|
|
20228
|
-
const context = lines[at]
|
|
20280
|
+
const context = parseChangeContext(lines[at]);
|
|
20229
20281
|
at += 1;
|
|
20230
20282
|
const old_lines = [];
|
|
20231
20283
|
const new_lines = [];
|
|
@@ -20268,12 +20320,11 @@ function parseChunks(lines, index, mode) {
|
|
|
20268
20320
|
return { chunks, next: at };
|
|
20269
20321
|
}
|
|
20270
20322
|
function parseAdd(lines, index, mode) {
|
|
20271
|
-
|
|
20323
|
+
const contents = [];
|
|
20272
20324
|
let at = index;
|
|
20273
20325
|
while (at < lines.length && !lines[at].startsWith("***")) {
|
|
20274
20326
|
if (lines[at].startsWith("+")) {
|
|
20275
|
-
contents
|
|
20276
|
-
`;
|
|
20327
|
+
contents.push(lines[at].slice(1));
|
|
20277
20328
|
at += 1;
|
|
20278
20329
|
continue;
|
|
20279
20330
|
}
|
|
@@ -20282,18 +20333,15 @@ function parseAdd(lines, index, mode) {
|
|
|
20282
20333
|
}
|
|
20283
20334
|
at += 1;
|
|
20284
20335
|
}
|
|
20285
|
-
|
|
20286
|
-
`)
|
|
20287
|
-
contents = contents.slice(0, -1);
|
|
20288
|
-
}
|
|
20289
|
-
return { content: contents, next: at };
|
|
20336
|
+
return { content: contents.join(`
|
|
20337
|
+
`), next: at };
|
|
20290
20338
|
}
|
|
20291
20339
|
function parsePatchInternal(patchText, mode) {
|
|
20292
20340
|
const clean = normalizePatchText(patchText);
|
|
20293
20341
|
const lines = clean.split(`
|
|
20294
20342
|
`);
|
|
20295
|
-
const begin = lines.findIndex((line) => line
|
|
20296
|
-
const end = lines.findIndex((line) => line
|
|
20343
|
+
const begin = lines.findIndex((line) => isPatchBoundary(line, "*** Begin Patch"));
|
|
20344
|
+
const end = lines.findIndex((line, index2) => index2 > begin && isPatchBoundary(line, "*** End Patch"));
|
|
20297
20345
|
if (begin === -1 || end === -1 || begin >= end) {
|
|
20298
20346
|
throw new Error("Invalid patch format: missing Begin/End markers");
|
|
20299
20347
|
}
|
|
@@ -20425,12 +20473,6 @@ function formatPatch(patch) {
|
|
|
20425
20473
|
}
|
|
20426
20474
|
|
|
20427
20475
|
// src/hooks/apply-patch/matching.ts
|
|
20428
|
-
var AUTO_RESCUE_COMPARATOR_NAMES = new Set([
|
|
20429
|
-
"exact",
|
|
20430
|
-
"unicode",
|
|
20431
|
-
"trim-end",
|
|
20432
|
-
"unicode-trim-end"
|
|
20433
|
-
]);
|
|
20434
20476
|
function equalExact(a, b) {
|
|
20435
20477
|
return a === b;
|
|
20436
20478
|
}
|
|
@@ -20449,7 +20491,7 @@ function equalTrim(a, b) {
|
|
|
20449
20491
|
function equalUnicodeTrim(a, b) {
|
|
20450
20492
|
return normalizeUnicode(a.trim()) === normalizeUnicode(b.trim());
|
|
20451
20493
|
}
|
|
20452
|
-
var
|
|
20494
|
+
var autoRescueComparatorEntries = [
|
|
20453
20495
|
{ name: "exact", exact: true, same: equalExact },
|
|
20454
20496
|
{ name: "unicode", exact: false, same: equalUnicodeExact },
|
|
20455
20497
|
{ name: "trim-end", exact: false, same: equalTrimEnd },
|
|
@@ -20457,14 +20499,44 @@ var comparatorEntries = [
|
|
|
20457
20499
|
name: "unicode-trim-end",
|
|
20458
20500
|
exact: false,
|
|
20459
20501
|
same: equalUnicodeTrimEnd
|
|
20460
|
-
}
|
|
20502
|
+
}
|
|
20503
|
+
];
|
|
20504
|
+
var comparatorEntries = [
|
|
20505
|
+
...autoRescueComparatorEntries,
|
|
20461
20506
|
{ name: "trim", exact: false, same: equalTrim },
|
|
20462
20507
|
{ name: "unicode-trim", exact: false, same: equalUnicodeTrim }
|
|
20463
20508
|
];
|
|
20464
|
-
var autoRescueComparatorEntries = comparatorEntries.filter((entry) => AUTO_RESCUE_COMPARATOR_NAMES.has(entry.name));
|
|
20465
20509
|
var MAX_LCS_CHUNK_LINES = 48;
|
|
20466
20510
|
var MAX_LCS_CANDIDATES = 64;
|
|
20467
20511
|
var autoRescueComparators = autoRescueComparatorEntries.map((entry) => entry.same);
|
|
20512
|
+
function prepareAutoRescueTarget(target) {
|
|
20513
|
+
const trimEnd = target.trimEnd();
|
|
20514
|
+
const unicode = normalizeUnicode(target);
|
|
20515
|
+
return {
|
|
20516
|
+
exact: target,
|
|
20517
|
+
unicode,
|
|
20518
|
+
trimEnd,
|
|
20519
|
+
unicodeTrimEnd: trimEnd === target ? unicode : normalizeUnicode(trimEnd)
|
|
20520
|
+
};
|
|
20521
|
+
}
|
|
20522
|
+
function matchPreparedAutoRescueComparator(candidate, target) {
|
|
20523
|
+
if (candidate === target.exact) {
|
|
20524
|
+
return "exact";
|
|
20525
|
+
}
|
|
20526
|
+
const unicode = normalizeUnicode(candidate);
|
|
20527
|
+
if (unicode === target.unicode) {
|
|
20528
|
+
return "unicode";
|
|
20529
|
+
}
|
|
20530
|
+
const trimEnd = candidate.trimEnd();
|
|
20531
|
+
if (trimEnd === target.trimEnd) {
|
|
20532
|
+
return "trim-end";
|
|
20533
|
+
}
|
|
20534
|
+
const unicodeTrimEnd = trimEnd === candidate ? unicode : normalizeUnicode(trimEnd);
|
|
20535
|
+
if (unicodeTrimEnd === target.unicodeTrimEnd) {
|
|
20536
|
+
return "unicode-trim-end";
|
|
20537
|
+
}
|
|
20538
|
+
return;
|
|
20539
|
+
}
|
|
20468
20540
|
var permissiveComparators = comparatorEntries.map((entry) => entry.same);
|
|
20469
20541
|
function tryMatch(lines, pattern, start, comparator, eof) {
|
|
20470
20542
|
if (eof) {
|
|
@@ -20538,6 +20610,19 @@ function list(lines, pattern, start, same) {
|
|
|
20538
20610
|
}
|
|
20539
20611
|
return out;
|
|
20540
20612
|
}
|
|
20613
|
+
function lowerBound(values, target) {
|
|
20614
|
+
let low = 0;
|
|
20615
|
+
let high = values.length;
|
|
20616
|
+
while (low < high) {
|
|
20617
|
+
const middle = Math.floor((low + high) / 2);
|
|
20618
|
+
if (values[middle] < target) {
|
|
20619
|
+
low = middle + 1;
|
|
20620
|
+
continue;
|
|
20621
|
+
}
|
|
20622
|
+
high = middle;
|
|
20623
|
+
}
|
|
20624
|
+
return low;
|
|
20625
|
+
}
|
|
20541
20626
|
function sameRescueLine(a, b) {
|
|
20542
20627
|
return equalExact(a, b) || equalUnicodeExact(a, b);
|
|
20543
20628
|
}
|
|
@@ -20564,36 +20649,102 @@ function rescueByPrefixSuffix(lines, old_lines, new_lines, start) {
|
|
|
20564
20649
|
const left = old_lines.slice(0, prefixLength);
|
|
20565
20650
|
const right = old_lines.slice(old_lines.length - suffixLength);
|
|
20566
20651
|
const middle = new_lines.slice(prefixLength, new_lines.length - suffixLength);
|
|
20567
|
-
|
|
20652
|
+
if (left.length === 1 && right.length === 1) {
|
|
20653
|
+
const { leftHits, rightHits } = collectOneLinePrefixSuffixHits(lines, left[0], right[0], start);
|
|
20654
|
+
return resolvePrefixSuffixHits(leftHits, rightHits, left.length, middle);
|
|
20655
|
+
}
|
|
20656
|
+
const hits = new Set;
|
|
20657
|
+
let hit;
|
|
20568
20658
|
for (const same of autoRescueComparators) {
|
|
20569
|
-
|
|
20659
|
+
const leftHits = list(lines, left, start, same);
|
|
20660
|
+
if (leftHits.length === 0) {
|
|
20661
|
+
continue;
|
|
20662
|
+
}
|
|
20663
|
+
const rightHits = list(lines, right, leftHits[0] + left.length, same);
|
|
20664
|
+
if (rightHits.length === 0) {
|
|
20665
|
+
continue;
|
|
20666
|
+
}
|
|
20667
|
+
for (const leftIndex of leftHits) {
|
|
20570
20668
|
const from = leftIndex + left.length;
|
|
20571
|
-
for (
|
|
20669
|
+
for (let index = lowerBound(rightHits, from);index < rightHits.length; index += 1) {
|
|
20670
|
+
const rightIndex = rightHits[index];
|
|
20572
20671
|
const key = `${from}:${rightIndex}`;
|
|
20573
|
-
hits.
|
|
20672
|
+
if (!hits.has(key)) {
|
|
20673
|
+
hits.add(key);
|
|
20674
|
+
hit = {
|
|
20675
|
+
start: from,
|
|
20676
|
+
del: rightIndex - from,
|
|
20677
|
+
add: [...middle]
|
|
20678
|
+
};
|
|
20679
|
+
}
|
|
20680
|
+
if (hits.size > 1) {
|
|
20681
|
+
return { kind: "ambiguous", phase: "prefix_suffix" };
|
|
20682
|
+
}
|
|
20683
|
+
}
|
|
20684
|
+
}
|
|
20685
|
+
}
|
|
20686
|
+
if (!hit) {
|
|
20687
|
+
return { kind: "miss" };
|
|
20688
|
+
}
|
|
20689
|
+
return { kind: "match", hit };
|
|
20690
|
+
}
|
|
20691
|
+
function collectOneLinePrefixSuffixHits(lines, left, right, start) {
|
|
20692
|
+
const leftTarget = prepareAutoRescueTarget(left);
|
|
20693
|
+
const rightTarget = prepareAutoRescueTarget(right);
|
|
20694
|
+
const leftHits = [];
|
|
20695
|
+
const rightHits = [];
|
|
20696
|
+
for (let index = start;index < lines.length; index += 1) {
|
|
20697
|
+
const line = prepareAutoRescueTarget(lines[index]);
|
|
20698
|
+
if (line.unicodeTrimEnd === leftTarget.unicodeTrimEnd) {
|
|
20699
|
+
leftHits.push(index);
|
|
20700
|
+
}
|
|
20701
|
+
if (index > start && line.unicodeTrimEnd === rightTarget.unicodeTrimEnd) {
|
|
20702
|
+
rightHits.push(index);
|
|
20703
|
+
}
|
|
20704
|
+
}
|
|
20705
|
+
return { leftHits, rightHits };
|
|
20706
|
+
}
|
|
20707
|
+
function resolvePrefixSuffixHits(leftHits, rightHits, leftLength, middle) {
|
|
20708
|
+
if (leftHits.length === 0 || rightHits.length === 0) {
|
|
20709
|
+
return { kind: "miss" };
|
|
20710
|
+
}
|
|
20711
|
+
const hits = new Set;
|
|
20712
|
+
let hit;
|
|
20713
|
+
for (const leftIndex of leftHits) {
|
|
20714
|
+
const from = leftIndex + leftLength;
|
|
20715
|
+
for (let index = lowerBound(rightHits, from);index < rightHits.length; index += 1) {
|
|
20716
|
+
const rightIndex = rightHits[index];
|
|
20717
|
+
const key = `${from}:${rightIndex}`;
|
|
20718
|
+
if (!hits.has(key)) {
|
|
20719
|
+
hits.add(key);
|
|
20720
|
+
hit = {
|
|
20574
20721
|
start: from,
|
|
20575
20722
|
del: rightIndex - from,
|
|
20576
20723
|
add: [...middle]
|
|
20577
|
-
}
|
|
20724
|
+
};
|
|
20725
|
+
}
|
|
20726
|
+
if (hits.size > 1) {
|
|
20727
|
+
return { kind: "ambiguous", phase: "prefix_suffix" };
|
|
20578
20728
|
}
|
|
20579
20729
|
}
|
|
20580
20730
|
}
|
|
20581
|
-
if (
|
|
20731
|
+
if (!hit) {
|
|
20582
20732
|
return { kind: "miss" };
|
|
20583
20733
|
}
|
|
20584
|
-
|
|
20585
|
-
return { kind: "ambiguous", phase: "prefix_suffix" };
|
|
20586
|
-
}
|
|
20587
|
-
return { kind: "match", hit: [...hits.values()][0] };
|
|
20734
|
+
return { kind: "match", hit };
|
|
20588
20735
|
}
|
|
20589
20736
|
function score(a, b) {
|
|
20590
|
-
const
|
|
20737
|
+
const normalizedA = a.map(normalizeLcsLine);
|
|
20738
|
+
const normalizedB = b.map(normalizeLcsLine);
|
|
20739
|
+
let previous = Array(b.length + 1).fill(0);
|
|
20591
20740
|
for (let i = 1;i <= a.length; i += 1) {
|
|
20741
|
+
const current = Array(b.length + 1).fill(0);
|
|
20592
20742
|
for (let j = 1;j <= b.length; j += 1) {
|
|
20593
|
-
|
|
20743
|
+
current[j] = normalizedA[i - 1] === normalizedB[j - 1] ? previous[j - 1] + 1 : Math.max(previous[j], current[j - 1]);
|
|
20594
20744
|
}
|
|
20745
|
+
previous = current;
|
|
20595
20746
|
}
|
|
20596
|
-
return
|
|
20747
|
+
return previous[b.length];
|
|
20597
20748
|
}
|
|
20598
20749
|
function normalizeLcsLine(line) {
|
|
20599
20750
|
return normalizeUnicode(line).trim();
|
|
@@ -20620,45 +20771,23 @@ function countLcsUpperBound(a, b) {
|
|
|
20620
20771
|
}
|
|
20621
20772
|
return shared;
|
|
20622
20773
|
}
|
|
20623
|
-
function hasStableBorders(oldLines, candidate) {
|
|
20624
|
-
if (oldLines.length === 0 || candidate.length !== oldLines.length) {
|
|
20625
|
-
return false;
|
|
20626
|
-
}
|
|
20627
|
-
const same = autoRescueComparators.some((compare) => compare(oldLines[0], candidate[0]));
|
|
20628
|
-
if (!same) {
|
|
20629
|
-
return false;
|
|
20630
|
-
}
|
|
20631
|
-
if (oldLines.length === 1) {
|
|
20632
|
-
return true;
|
|
20633
|
-
}
|
|
20634
|
-
return autoRescueComparators.some((compare) => compare(oldLines[oldLines.length - 1], candidate[candidate.length - 1]));
|
|
20635
|
-
}
|
|
20636
20774
|
function collectBorderAnchoredStarts(lines, oldLines, start) {
|
|
20637
20775
|
if (oldLines.length === 0) {
|
|
20638
20776
|
return [];
|
|
20639
20777
|
}
|
|
20640
|
-
const firstHits = new Set;
|
|
20641
|
-
const lastHits = new Set;
|
|
20642
|
-
const lastLine = oldLines[oldLines.length - 1];
|
|
20643
|
-
for (const same of autoRescueComparators) {
|
|
20644
|
-
for (const index of list(lines, [oldLines[0]], start, same)) {
|
|
20645
|
-
firstHits.add(index);
|
|
20646
|
-
}
|
|
20647
|
-
for (const index of list(lines, [lastLine], start, same)) {
|
|
20648
|
-
lastHits.add(index);
|
|
20649
|
-
}
|
|
20650
|
-
}
|
|
20651
20778
|
const candidates = [];
|
|
20652
|
-
|
|
20653
|
-
|
|
20654
|
-
|
|
20779
|
+
const firstLine = prepareAutoRescueTarget(oldLines[0]);
|
|
20780
|
+
const lastLine = prepareAutoRescueTarget(oldLines[oldLines.length - 1]);
|
|
20781
|
+
const lastOffset = oldLines.length - 1;
|
|
20782
|
+
const maxStart = lines.length - oldLines.length;
|
|
20783
|
+
for (let index = start;index <= maxStart; index += 1) {
|
|
20784
|
+
const end = index + lastOffset;
|
|
20785
|
+
if (matchPreparedAutoRescueComparator(lines[index], firstLine) === undefined) {
|
|
20655
20786
|
continue;
|
|
20656
20787
|
}
|
|
20657
|
-
|
|
20658
|
-
|
|
20659
|
-
continue;
|
|
20788
|
+
if (oldLines.length === 1 || matchPreparedAutoRescueComparator(lines[end], lastLine) !== undefined) {
|
|
20789
|
+
candidates.push(index);
|
|
20660
20790
|
}
|
|
20661
|
-
candidates.push(index);
|
|
20662
20791
|
}
|
|
20663
20792
|
return candidates;
|
|
20664
20793
|
}
|
|
@@ -20666,11 +20795,6 @@ function rescueByLcs(lines, old_lines, new_lines, start) {
|
|
|
20666
20795
|
if (old_lines.length === 0 || lines.length === 0) {
|
|
20667
20796
|
return { kind: "miss" };
|
|
20668
20797
|
}
|
|
20669
|
-
const from = start;
|
|
20670
|
-
const to = lines.length - old_lines.length;
|
|
20671
|
-
if (to < from) {
|
|
20672
|
-
return { kind: "miss" };
|
|
20673
|
-
}
|
|
20674
20798
|
if (old_lines.length > MAX_LCS_CHUNK_LINES) {
|
|
20675
20799
|
return { kind: "miss" };
|
|
20676
20800
|
}
|
|
@@ -20683,9 +20807,6 @@ function rescueByLcs(lines, old_lines, new_lines, start) {
|
|
|
20683
20807
|
let bestScore = 0;
|
|
20684
20808
|
let ties = 0;
|
|
20685
20809
|
for (const index of candidates) {
|
|
20686
|
-
if (index < from || index > to) {
|
|
20687
|
-
continue;
|
|
20688
|
-
}
|
|
20689
20810
|
const window2 = lines.slice(index, index + old_lines.length);
|
|
20690
20811
|
if (countLcsUpperBound(old_lines, window2) < needed) {
|
|
20691
20812
|
continue;
|
|
@@ -20740,26 +20861,29 @@ function resolveChunkStart(lines, chunk, start) {
|
|
|
20740
20861
|
return at === -1 ? start : at + 1;
|
|
20741
20862
|
}
|
|
20742
20863
|
function resolveUniqueAnchor(lines, changeContext, start) {
|
|
20743
|
-
|
|
20744
|
-
|
|
20745
|
-
|
|
20746
|
-
|
|
20864
|
+
let matchedIndex;
|
|
20865
|
+
let matchedComparator;
|
|
20866
|
+
const anchorTarget = prepareAutoRescueTarget(changeContext);
|
|
20867
|
+
for (let index = start;index < lines.length; index += 1) {
|
|
20868
|
+
const comparator = matchPreparedAutoRescueComparator(lines[index], anchorTarget);
|
|
20869
|
+
if (!comparator) {
|
|
20870
|
+
continue;
|
|
20747
20871
|
}
|
|
20872
|
+
if (matchedIndex !== undefined) {
|
|
20873
|
+
return { kind: "ambiguous" };
|
|
20874
|
+
}
|
|
20875
|
+
matchedIndex = index;
|
|
20876
|
+
matchedComparator = comparator;
|
|
20748
20877
|
}
|
|
20749
|
-
if (
|
|
20878
|
+
if (matchedIndex === undefined) {
|
|
20750
20879
|
return { kind: "missing" };
|
|
20751
20880
|
}
|
|
20752
|
-
|
|
20753
|
-
return { kind: "ambiguous" };
|
|
20754
|
-
}
|
|
20755
|
-
const index = [...hits][0];
|
|
20756
|
-
const canonicalLine = lines[index];
|
|
20757
|
-
const comparator = seekMatch(lines, [changeContext], index)?.comparator;
|
|
20881
|
+
const canonicalLine = lines[matchedIndex];
|
|
20758
20882
|
return {
|
|
20759
20883
|
kind: "match",
|
|
20760
|
-
index,
|
|
20884
|
+
index: matchedIndex,
|
|
20761
20885
|
exact: canonicalLine === changeContext,
|
|
20762
|
-
comparator:
|
|
20886
|
+
comparator: matchedComparator ?? "exact",
|
|
20763
20887
|
canonicalLine
|
|
20764
20888
|
};
|
|
20765
20889
|
}
|
|
@@ -20910,10 +21034,11 @@ ${chunk.change_context}`);
|
|
|
20910
21034
|
old_lines: [],
|
|
20911
21035
|
canonical_old_lines: [anchor],
|
|
20912
21036
|
canonical_new_lines: [...chunk.new_lines, anchor],
|
|
21037
|
+
canonical_change_context: anchorMatch.exact ? undefined : anchorMatch.canonicalLine,
|
|
20913
21038
|
resolved_is_end_of_file: insertAt + 1 === lines.length,
|
|
20914
21039
|
rewritten: true,
|
|
20915
21040
|
strategy,
|
|
20916
|
-
matchComparator:
|
|
21041
|
+
matchComparator: anchorMatch.comparator
|
|
20917
21042
|
});
|
|
20918
21043
|
start = insertAt;
|
|
20919
21044
|
continue;
|
|
@@ -21287,15 +21412,6 @@ function renderRewriteDependencyGroup(group, cfg) {
|
|
|
21287
21412
|
}
|
|
21288
21413
|
return group.group.chunks ? createUpdateHunk(group.group.sourcePath, group.group.chunks, group.group.outputPath !== group.group.sourcePath ? group.group.outputPath : undefined) : createCollapsedUpdateHunk(group.group.sourcePath, group.group.sourceFilePath, group.group.baseText, group.group.finalText, cfg, group.group.outputPath !== group.group.sourcePath ? group.group.outputPath : undefined);
|
|
21289
21414
|
}
|
|
21290
|
-
function rewriteModeForDependentUpdate(group) {
|
|
21291
|
-
if (group.kind === "add") {
|
|
21292
|
-
return "collapse:add-followed-by-update";
|
|
21293
|
-
}
|
|
21294
|
-
if (group.group.outputPath !== group.group.sourcePath) {
|
|
21295
|
-
return "collapse:move-followed-by-update";
|
|
21296
|
-
}
|
|
21297
|
-
return "merge:same-file-updates";
|
|
21298
|
-
}
|
|
21299
21415
|
function combineDependentUpdateGroup(filePath, group, nextChunks, finalText, nextOutputPath, nextOutputFilePath, cfg) {
|
|
21300
21416
|
if (group.kind === "add") {
|
|
21301
21417
|
return {
|
|
@@ -21335,12 +21451,6 @@ async function rewritePatch(root, patchText, cfg, worktree) {
|
|
|
21335
21451
|
const normalizedPatchText = normalizePatchText(patchText);
|
|
21336
21452
|
const rewritten = [];
|
|
21337
21453
|
let changed = false;
|
|
21338
|
-
let rewrittenChunks = 0;
|
|
21339
|
-
const rewriteModes = new Set;
|
|
21340
|
-
const totalChunks = hunks.reduce((count, hunk) => count + (hunk.type === "update" ? hunk.chunks.length : 0), 0);
|
|
21341
|
-
if (pathsNormalized) {
|
|
21342
|
-
rewriteModes.add("normalize:patch-paths");
|
|
21343
|
-
}
|
|
21344
21454
|
const dependencyGroups = new Map;
|
|
21345
21455
|
for (const hunk of hunks) {
|
|
21346
21456
|
if (hunk.type === "add") {
|
|
@@ -21395,14 +21505,6 @@ async function rewritePatch(root, patchText, cfg, worktree) {
|
|
|
21395
21505
|
continue;
|
|
21396
21506
|
}
|
|
21397
21507
|
changed = true;
|
|
21398
|
-
rewrittenChunks += 1;
|
|
21399
|
-
if (chunk.strategy) {
|
|
21400
|
-
rewriteModes.add(chunk.strategy);
|
|
21401
|
-
continue;
|
|
21402
|
-
}
|
|
21403
|
-
if (chunk.matchComparator && chunk.matchComparator !== "exact") {
|
|
21404
|
-
rewriteModes.add(`match:${chunk.matchComparator}`);
|
|
21405
|
-
}
|
|
21406
21508
|
}
|
|
21407
21509
|
const nextOutputPath = hunk.move_path ?? hunk.path;
|
|
21408
21510
|
const nextOutputFilePath = movePath ?? filePath;
|
|
@@ -21410,7 +21512,6 @@ async function rewritePatch(root, patchText, cfg, worktree) {
|
|
|
21410
21512
|
const nextGroup = combineDependentUpdateGroup(filePath, currentDependency, next, nextText, nextOutputPath, nextOutputFilePath, cfg);
|
|
21411
21513
|
rewritten[currentDependency.group.index] = renderRewriteDependencyGroup(nextGroup, cfg);
|
|
21412
21514
|
changed = true;
|
|
21413
|
-
rewriteModes.add(rewriteModeForDependentUpdate(currentDependency));
|
|
21414
21515
|
clearDependencyGroup(filePath);
|
|
21415
21516
|
if (movePath && movePath !== filePath) {
|
|
21416
21517
|
clearDependencyGroup(movePath);
|
|
@@ -21457,35 +21558,23 @@ async function rewritePatch(root, patchText, cfg, worktree) {
|
|
|
21457
21558
|
if (pathsNormalized) {
|
|
21458
21559
|
return {
|
|
21459
21560
|
patchText: formatPatch({ hunks }),
|
|
21460
|
-
changed: true
|
|
21461
|
-
rewrittenChunks: 0,
|
|
21462
|
-
totalChunks,
|
|
21463
|
-
rewriteModes: [...rewriteModes].sort()
|
|
21561
|
+
changed: true
|
|
21464
21562
|
};
|
|
21465
21563
|
}
|
|
21466
21564
|
if (normalizedPatchText !== patchText) {
|
|
21467
21565
|
return {
|
|
21468
21566
|
patchText: normalizedPatchText,
|
|
21469
|
-
changed: true
|
|
21470
|
-
rewrittenChunks: 0,
|
|
21471
|
-
totalChunks,
|
|
21472
|
-
rewriteModes: ["normalize:patch-text"]
|
|
21567
|
+
changed: true
|
|
21473
21568
|
};
|
|
21474
21569
|
}
|
|
21475
21570
|
return {
|
|
21476
21571
|
patchText,
|
|
21477
|
-
changed: false
|
|
21478
|
-
rewrittenChunks: 0,
|
|
21479
|
-
totalChunks,
|
|
21480
|
-
rewriteModes: []
|
|
21572
|
+
changed: false
|
|
21481
21573
|
};
|
|
21482
21574
|
}
|
|
21483
21575
|
return {
|
|
21484
21576
|
patchText: formatPatch({ hunks: rewritten }),
|
|
21485
|
-
changed: true
|
|
21486
|
-
rewrittenChunks,
|
|
21487
|
-
totalChunks,
|
|
21488
|
-
rewriteModes: [...rewriteModes].sort()
|
|
21577
|
+
changed: true
|
|
21489
21578
|
};
|
|
21490
21579
|
} catch (error) {
|
|
21491
21580
|
throw ensureApplyPatchError(error, "Unexpected rewrite failure");
|
|
@@ -21505,30 +21594,36 @@ function createApplyPatchHook(ctx) {
|
|
|
21505
21594
|
if (input.tool !== "apply_patch") {
|
|
21506
21595
|
return;
|
|
21507
21596
|
}
|
|
21508
|
-
|
|
21597
|
+
const args = output.args;
|
|
21598
|
+
if (!args || typeof args.patchText !== "string") {
|
|
21509
21599
|
return;
|
|
21510
21600
|
}
|
|
21601
|
+
const patchText = args.patchText;
|
|
21511
21602
|
const root = input.directory || ctx.directory || process.cwd();
|
|
21512
21603
|
const worktree = ctx.worktree || root;
|
|
21513
21604
|
try {
|
|
21514
|
-
const result = await rewritePatch(root,
|
|
21605
|
+
const result = await rewritePatch(root, patchText, APPLY_PATCH_RESCUE_OPTIONS, worktree);
|
|
21515
21606
|
if (result.changed) {
|
|
21516
|
-
|
|
21517
|
-
logHookStatus("rewrite"
|
|
21518
|
-
rewrittenChunks: result.rewrittenChunks,
|
|
21519
|
-
totalChunks: result.totalChunks,
|
|
21520
|
-
strategies: result.rewriteModes
|
|
21521
|
-
});
|
|
21607
|
+
args.patchText = result.patchText;
|
|
21608
|
+
logHookStatus("rewrite");
|
|
21522
21609
|
return;
|
|
21523
21610
|
}
|
|
21524
|
-
logHookStatus("unchanged"
|
|
21525
|
-
rewrittenChunks: 0,
|
|
21526
|
-
totalChunks: result.totalChunks
|
|
21527
|
-
});
|
|
21611
|
+
logHookStatus("unchanged");
|
|
21528
21612
|
return;
|
|
21529
21613
|
} catch (error) {
|
|
21530
21614
|
const normalizedError = isApplyPatchError(error) ? error : createApplyPatchInternalError(`Unexpected hook failure before native apply: ${error instanceof Error ? error.message : String(error)}`, error);
|
|
21531
21615
|
const details = getApplyPatchErrorDetails(normalizedError);
|
|
21616
|
+
if (normalizedError.kind === "blocked" && details?.code === "outside_workspace") {
|
|
21617
|
+
logHookStatus("skipped", {
|
|
21618
|
+
kind: details.kind,
|
|
21619
|
+
code: details.code,
|
|
21620
|
+
reason: normalizedError.message,
|
|
21621
|
+
failOpen: true,
|
|
21622
|
+
rescueOptions: APPLY_PATCH_RESCUE_OPTIONS,
|
|
21623
|
+
rewriteStage: "before-native"
|
|
21624
|
+
});
|
|
21625
|
+
return;
|
|
21626
|
+
}
|
|
21532
21627
|
logHookStatus(isApplyPatchVerificationError(normalizedError) ? "verification" : normalizedError.kind === "validation" ? "validation" : normalizedError.kind === "internal" ? "internal" : "blocked", {
|
|
21533
21628
|
kind: details?.kind ?? "internal",
|
|
21534
21629
|
code: details?.code ?? "internal_unexpected",
|
|
@@ -23110,7 +23205,7 @@ ${JSON_ERROR_REMINDER}`;
|
|
|
23110
23205
|
};
|
|
23111
23206
|
}
|
|
23112
23207
|
// src/hooks/phase-reminder/index.ts
|
|
23113
|
-
var PHASE_REMINDER = `<
|
|
23208
|
+
var PHASE_REMINDER = `<internal_reminder>${PHASE_REMINDER_TEXT}</internal_reminder>`;
|
|
23114
23209
|
function createPhaseReminderHook() {
|
|
23115
23210
|
return {
|
|
23116
23211
|
"experimental.chat.messages.transform": async (_input, output) => {
|
|
@@ -23141,11 +23236,14 @@ function createPhaseReminderHook() {
|
|
|
23141
23236
|
if (originalText.includes(SLIM_INTERNAL_INITIATOR_MARKER)) {
|
|
23142
23237
|
return;
|
|
23143
23238
|
}
|
|
23144
|
-
|
|
23239
|
+
if (originalText.includes(PHASE_REMINDER)) {
|
|
23240
|
+
return;
|
|
23241
|
+
}
|
|
23242
|
+
lastUserMessage.parts[textPartIndex].text = `${originalText}
|
|
23145
23243
|
|
|
23146
23244
|
---
|
|
23147
23245
|
|
|
23148
|
-
${
|
|
23246
|
+
${PHASE_REMINDER}`;
|
|
23149
23247
|
}
|
|
23150
23248
|
};
|
|
23151
23249
|
}
|
|
@@ -23153,32 +23251,31 @@ ${originalText}`;
|
|
|
23153
23251
|
var POST_FILE_TOOL_NUDGE = PHASE_REMINDER_TEXT;
|
|
23154
23252
|
var FILE_TOOLS = new Set(["Read", "read", "Write", "write"]);
|
|
23155
23253
|
function createPostFileToolNudgeHook(options = {}) {
|
|
23156
|
-
|
|
23254
|
+
function appendReminder(output) {
|
|
23255
|
+
if (typeof output.output !== "string") {
|
|
23256
|
+
return;
|
|
23257
|
+
}
|
|
23258
|
+
if (output.output.includes(POST_FILE_TOOL_NUDGE)) {
|
|
23259
|
+
return;
|
|
23260
|
+
}
|
|
23261
|
+
output.output = [
|
|
23262
|
+
output.output,
|
|
23263
|
+
"",
|
|
23264
|
+
"<internal_reminder>",
|
|
23265
|
+
POST_FILE_TOOL_NUDGE,
|
|
23266
|
+
"</internal_reminder>"
|
|
23267
|
+
].join(`
|
|
23268
|
+
`);
|
|
23269
|
+
}
|
|
23157
23270
|
return {
|
|
23158
|
-
"tool.execute.after": async (input,
|
|
23271
|
+
"tool.execute.after": async (input, output) => {
|
|
23159
23272
|
if (!FILE_TOOLS.has(input.tool) || !input.sessionID) {
|
|
23160
23273
|
return;
|
|
23161
23274
|
}
|
|
23162
|
-
pendingSessionIds.add(input.sessionID);
|
|
23163
|
-
},
|
|
23164
|
-
"experimental.chat.system.transform": async (input, output) => {
|
|
23165
|
-
if (!input.sessionID || !pendingSessionIds.delete(input.sessionID)) {
|
|
23166
|
-
return;
|
|
23167
|
-
}
|
|
23168
23275
|
if (options.shouldInject && !options.shouldInject(input.sessionID)) {
|
|
23169
23276
|
return;
|
|
23170
23277
|
}
|
|
23171
|
-
output
|
|
23172
|
-
},
|
|
23173
|
-
event: async (input) => {
|
|
23174
|
-
if (input.event.type !== "session.deleted") {
|
|
23175
|
-
return;
|
|
23176
|
-
}
|
|
23177
|
-
const sessionID = input.event.properties?.sessionID ?? input.event.properties?.info?.id;
|
|
23178
|
-
if (!sessionID) {
|
|
23179
|
-
return;
|
|
23180
|
-
}
|
|
23181
|
-
pendingSessionIds.delete(sessionID);
|
|
23278
|
+
appendReminder(output);
|
|
23182
23279
|
}
|
|
23183
23280
|
};
|
|
23184
23281
|
}
|
|
@@ -23196,6 +23293,8 @@ var AGENT_NAME_SET = new Set([
|
|
|
23196
23293
|
"councillor"
|
|
23197
23294
|
]);
|
|
23198
23295
|
var MAX_PENDING_TASK_CALLS = 100;
|
|
23296
|
+
var RESUMABLE_SESSIONS_START = "<resumable_sessions>";
|
|
23297
|
+
var RESUMABLE_SESSIONS_END = "</resumable_sessions>";
|
|
23199
23298
|
function isAgentName(value) {
|
|
23200
23299
|
return typeof value === "string" && AGENT_NAME_SET.has(value);
|
|
23201
23300
|
}
|
|
@@ -23395,14 +23494,36 @@ function createTaskSessionManagerHook(_ctx, options) {
|
|
|
23395
23494
|
sessionManager.addContext(taskId, contextFiles);
|
|
23396
23495
|
pruneContext();
|
|
23397
23496
|
},
|
|
23398
|
-
"experimental.chat.
|
|
23399
|
-
|
|
23497
|
+
"experimental.chat.messages.transform": async (_input, output) => {
|
|
23498
|
+
for (let i = output.messages.length - 1;i >= 0; i -= 1) {
|
|
23499
|
+
const message = output.messages[i];
|
|
23500
|
+
if (message.info.role !== "user")
|
|
23501
|
+
continue;
|
|
23502
|
+
if (message.info.agent && message.info.agent !== "orchestrator")
|
|
23503
|
+
return;
|
|
23504
|
+
if (!message.info.sessionID || !options.shouldManageSession(message.info.sessionID)) {
|
|
23505
|
+
return;
|
|
23506
|
+
}
|
|
23507
|
+
const reminder = sessionManager.formatForPrompt(message.info.sessionID);
|
|
23508
|
+
if (!reminder)
|
|
23509
|
+
return;
|
|
23510
|
+
const textPart = message.parts.find((part) => part.type === "text" && typeof part.text === "string");
|
|
23511
|
+
if (!textPart)
|
|
23512
|
+
return;
|
|
23513
|
+
if (textPart.text?.includes(SLIM_INTERNAL_INITIATOR_MARKER))
|
|
23514
|
+
return;
|
|
23515
|
+
if (textPart.text?.includes(RESUMABLE_SESSIONS_START))
|
|
23516
|
+
return;
|
|
23517
|
+
textPart.text = [
|
|
23518
|
+
textPart.text ?? "",
|
|
23519
|
+
"",
|
|
23520
|
+
RESUMABLE_SESSIONS_START,
|
|
23521
|
+
reminder,
|
|
23522
|
+
RESUMABLE_SESSIONS_END
|
|
23523
|
+
].join(`
|
|
23524
|
+
`);
|
|
23400
23525
|
return;
|
|
23401
23526
|
}
|
|
23402
|
-
const reminder = sessionManager.formatForPrompt(input.sessionID);
|
|
23403
|
-
if (!reminder)
|
|
23404
|
-
return;
|
|
23405
|
-
output.system.push(reminder);
|
|
23406
23527
|
},
|
|
23407
23528
|
event: async (input) => {
|
|
23408
23529
|
if (input.event.type === "session.created") {
|
|
@@ -23467,7 +23588,7 @@ function createTodoHygiene(options) {
|
|
|
23467
23588
|
handleRequestStart(input) {
|
|
23468
23589
|
clear(input.sessionID);
|
|
23469
23590
|
},
|
|
23470
|
-
async handleToolExecuteAfter(input) {
|
|
23591
|
+
async handleToolExecuteAfter(input, _output) {
|
|
23471
23592
|
if (!input.sessionID) {
|
|
23472
23593
|
return;
|
|
23473
23594
|
}
|
|
@@ -23477,6 +23598,10 @@ function createTodoHygiene(options) {
|
|
|
23477
23598
|
}
|
|
23478
23599
|
try {
|
|
23479
23600
|
if (RESET.has(tool)) {
|
|
23601
|
+
if (options.shouldInject && !options.shouldInject(input.sessionID)) {
|
|
23602
|
+
clear(input.sessionID);
|
|
23603
|
+
return;
|
|
23604
|
+
}
|
|
23480
23605
|
active.add(input.sessionID);
|
|
23481
23606
|
clearCycle(input.sessionID);
|
|
23482
23607
|
const state2 = await options.getTodoState(input.sessionID);
|
|
@@ -23535,39 +23660,22 @@ function createTodoHygiene(options) {
|
|
|
23535
23660
|
});
|
|
23536
23661
|
}
|
|
23537
23662
|
},
|
|
23538
|
-
|
|
23539
|
-
|
|
23540
|
-
return;
|
|
23541
|
-
}
|
|
23542
|
-
const reasons = pending.get(input.sessionID);
|
|
23663
|
+
getPendingReminder(sessionID) {
|
|
23664
|
+
const reasons = pending.get(sessionID);
|
|
23543
23665
|
if (!reasons || reasons.size === 0) {
|
|
23544
|
-
return;
|
|
23545
|
-
}
|
|
23546
|
-
const reminder = pick(reasons);
|
|
23547
|
-
if (options.shouldInject && !options.shouldInject(input.sessionID)) {
|
|
23548
|
-
clear(input.sessionID);
|
|
23549
|
-
return;
|
|
23666
|
+
return null;
|
|
23550
23667
|
}
|
|
23551
|
-
|
|
23552
|
-
|
|
23553
|
-
|
|
23554
|
-
clear(input.sessionID);
|
|
23555
|
-
return;
|
|
23556
|
-
}
|
|
23557
|
-
pending.delete(input.sessionID);
|
|
23558
|
-
output.system.push(reminder);
|
|
23559
|
-
options.log?.("Injected todo hygiene reminder", {
|
|
23560
|
-
sessionID: input.sessionID,
|
|
23561
|
-
reminder,
|
|
23562
|
-
reasons: Array.from(reasons)
|
|
23563
|
-
});
|
|
23564
|
-
} catch (error) {
|
|
23565
|
-
pending.delete(input.sessionID);
|
|
23566
|
-
options.log?.("Skipped todo hygiene reminder: failed to inspect todos", {
|
|
23567
|
-
sessionID: input.sessionID,
|
|
23568
|
-
error: error instanceof Error ? error.message : String(error)
|
|
23569
|
-
});
|
|
23668
|
+
if (options.shouldInject && !options.shouldInject(sessionID)) {
|
|
23669
|
+
clear(sessionID);
|
|
23670
|
+
return null;
|
|
23570
23671
|
}
|
|
23672
|
+
const reminder = pick(reasons);
|
|
23673
|
+
options.log?.("Read todo hygiene reminder", {
|
|
23674
|
+
sessionID,
|
|
23675
|
+
reminder,
|
|
23676
|
+
reasons: Array.from(reasons)
|
|
23677
|
+
});
|
|
23678
|
+
return reminder;
|
|
23571
23679
|
},
|
|
23572
23680
|
handleEvent(event) {
|
|
23573
23681
|
if (event.type !== "session.deleted") {
|
|
@@ -23586,6 +23694,8 @@ function createTodoHygiene(options) {
|
|
|
23586
23694
|
var HOOK_NAME = "todo-continuation";
|
|
23587
23695
|
var COMMAND_NAME = "auto-continue";
|
|
23588
23696
|
var CONTINUATION_PROMPT = "[Auto-continue: enabled - there are incomplete todos remaining. Continue with the next uncompleted item. Press Esc to cancel. If you need user input or review for the next item, ask instead of proceeding.]";
|
|
23697
|
+
var TODO_HYGIENE_INSTRUCTION_OPEN = '<instruction name="todo_hygiene">';
|
|
23698
|
+
var TODO_HYGIENE_INSTRUCTION_CLOSE = "</instruction>";
|
|
23589
23699
|
var SUPPRESS_AFTER_ABORT_MS = 5000;
|
|
23590
23700
|
var NOTIFICATION_BUSY_GRACE_MS = 250;
|
|
23591
23701
|
var QUESTION_PHRASES = [
|
|
@@ -23623,6 +23733,35 @@ function resetState(state) {
|
|
|
23623
23733
|
state.notifyingSessionIds.clear();
|
|
23624
23734
|
state.notificationBusyUntilBySession.clear();
|
|
23625
23735
|
}
|
|
23736
|
+
function stripTodoHygieneInstruction(text) {
|
|
23737
|
+
const trimmed = text.trimEnd();
|
|
23738
|
+
if (!trimmed.endsWith(TODO_HYGIENE_INSTRUCTION_CLOSE)) {
|
|
23739
|
+
return trimmed;
|
|
23740
|
+
}
|
|
23741
|
+
const start = trimmed.lastIndexOf(TODO_HYGIENE_INSTRUCTION_OPEN);
|
|
23742
|
+
if (start === -1) {
|
|
23743
|
+
return trimmed;
|
|
23744
|
+
}
|
|
23745
|
+
return trimmed.slice(0, start).trimEnd();
|
|
23746
|
+
}
|
|
23747
|
+
function appendTodoHygieneInstruction(message, reminder) {
|
|
23748
|
+
const textPart = [...message.parts].reverse().find((part) => part.type === "text" && typeof part.text === "string");
|
|
23749
|
+
if (!textPart)
|
|
23750
|
+
return;
|
|
23751
|
+
const baseText = stripTodoHygieneInstruction(textPart.text ?? "");
|
|
23752
|
+
const instruction = `${TODO_HYGIENE_INSTRUCTION_OPEN}
|
|
23753
|
+
${reminder}
|
|
23754
|
+
${TODO_HYGIENE_INSTRUCTION_CLOSE}`;
|
|
23755
|
+
textPart.text = baseText ? `${baseText}
|
|
23756
|
+
|
|
23757
|
+
${instruction}` : instruction;
|
|
23758
|
+
}
|
|
23759
|
+
function stripTodoHygieneInstructionFromMessage(message) {
|
|
23760
|
+
const textPart = [...message.parts].reverse().find((part) => part.type === "text" && typeof part.text === "string");
|
|
23761
|
+
if (!textPart)
|
|
23762
|
+
return;
|
|
23763
|
+
textPart.text = stripTodoHygieneInstruction(textPart.text ?? "");
|
|
23764
|
+
}
|
|
23626
23765
|
function createTodoContinuationHook(ctx, config) {
|
|
23627
23766
|
const maxContinuations = config?.maxContinuations ?? 5;
|
|
23628
23767
|
const cooldownMs = config?.cooldownMs ?? 3000;
|
|
@@ -23698,7 +23837,8 @@ function createTodoContinuationHook(ctx, config) {
|
|
|
23698
23837
|
const sessionID = inferSessionID(messages, i);
|
|
23699
23838
|
const partSignature = message.parts.map((part) => {
|
|
23700
23839
|
if (part.type === "text" && typeof part.text === "string") {
|
|
23701
|
-
|
|
23840
|
+
const text = stripTodoHygieneInstruction(part.text);
|
|
23841
|
+
return `${part.type}:${text.includes(SLIM_INTERNAL_INITIATOR_MARKER) ? "<internal>" : text.trim()}`;
|
|
23702
23842
|
}
|
|
23703
23843
|
return part.type ?? "unknown";
|
|
23704
23844
|
}).join("|");
|
|
@@ -23706,6 +23846,7 @@ function createTodoContinuationHook(ctx, config) {
|
|
|
23706
23846
|
return {
|
|
23707
23847
|
sessionID,
|
|
23708
23848
|
agent: message.info.agent,
|
|
23849
|
+
message,
|
|
23709
23850
|
signature: message.info.id ? `${message.info.id}:${partSignature}` : `${ordinal}:${partSignature}`
|
|
23710
23851
|
};
|
|
23711
23852
|
}
|
|
@@ -23733,9 +23874,16 @@ function createTodoContinuationHook(ctx, config) {
|
|
|
23733
23874
|
return;
|
|
23734
23875
|
}
|
|
23735
23876
|
if (requestSignatureBySession.get(lastUserMessage.sessionID) === lastUserMessage.signature) {
|
|
23877
|
+
const reminder = hygiene.getPendingReminder(lastUserMessage.sessionID);
|
|
23878
|
+
if (reminder) {
|
|
23879
|
+
appendTodoHygieneInstruction(lastUserMessage.message, reminder);
|
|
23880
|
+
} else {
|
|
23881
|
+
stripTodoHygieneInstructionFromMessage(lastUserMessage.message);
|
|
23882
|
+
}
|
|
23736
23883
|
return;
|
|
23737
23884
|
}
|
|
23738
23885
|
requestSignatureBySession.set(lastUserMessage.sessionID, lastUserMessage.signature);
|
|
23886
|
+
stripTodoHygieneInstructionFromMessage(lastUserMessage.message);
|
|
23739
23887
|
hygiene.handleRequestStart({ sessionID: lastUserMessage.sessionID });
|
|
23740
23888
|
}
|
|
23741
23889
|
function markNotificationStarted(sessionID) {
|
|
@@ -24085,7 +24233,6 @@ function createTodoContinuationHook(ctx, config) {
|
|
|
24085
24233
|
return {
|
|
24086
24234
|
tool: { auto_continue: autoContinue },
|
|
24087
24235
|
handleToolExecuteAfter: hygiene.handleToolExecuteAfter,
|
|
24088
|
-
handleChatSystemTransform: hygiene.handleChatSystemTransform,
|
|
24089
24236
|
handleMessagesTransform,
|
|
24090
24237
|
handleEvent,
|
|
24091
24238
|
handleChatMessage,
|
|
@@ -28537,6 +28684,7 @@ class MultiplexerSessionManager {
|
|
|
28537
28684
|
sessions = new Map;
|
|
28538
28685
|
knownSessions = new Map;
|
|
28539
28686
|
spawningSessions = new Set;
|
|
28687
|
+
closingSessions = new Map;
|
|
28540
28688
|
pollInterval;
|
|
28541
28689
|
enabled = false;
|
|
28542
28690
|
constructor(ctx, config) {
|
|
@@ -28565,17 +28713,22 @@ class MultiplexerSessionManager {
|
|
|
28565
28713
|
const parentId = info.parentID;
|
|
28566
28714
|
const title = info.title ?? "Subagent";
|
|
28567
28715
|
const directory = info.directory ?? this.directory;
|
|
28568
|
-
this.knownSessions.set(sessionId, {
|
|
28569
|
-
parentId,
|
|
28570
|
-
title,
|
|
28571
|
-
directory
|
|
28572
|
-
});
|
|
28573
28716
|
if (this.isTrackedOrSpawning(sessionId)) {
|
|
28574
28717
|
log("[multiplexer-session-manager] session already tracked or spawning", {
|
|
28575
28718
|
sessionId
|
|
28576
28719
|
});
|
|
28577
28720
|
return;
|
|
28578
28721
|
}
|
|
28722
|
+
const closing = this.closingSessions.get(sessionId);
|
|
28723
|
+
if (closing)
|
|
28724
|
+
await closing;
|
|
28725
|
+
if (this.isTrackedOrSpawning(sessionId))
|
|
28726
|
+
return;
|
|
28727
|
+
this.knownSessions.set(sessionId, {
|
|
28728
|
+
parentId,
|
|
28729
|
+
title,
|
|
28730
|
+
directory
|
|
28731
|
+
});
|
|
28579
28732
|
this.spawningSessions.add(sessionId);
|
|
28580
28733
|
try {
|
|
28581
28734
|
const serverRunning = await isServerRunning(this.serverUrl);
|
|
@@ -28585,7 +28738,7 @@ class MultiplexerSessionManager {
|
|
|
28585
28738
|
});
|
|
28586
28739
|
return;
|
|
28587
28740
|
}
|
|
28588
|
-
if (this.sessions.has(sessionId)) {
|
|
28741
|
+
if (this.closingSessions.has(sessionId) || this.sessions.has(sessionId)) {
|
|
28589
28742
|
return;
|
|
28590
28743
|
}
|
|
28591
28744
|
log("[multiplexer-session-manager] child session created, spawning pane", {
|
|
@@ -28599,23 +28752,31 @@ class MultiplexerSessionManager {
|
|
|
28599
28752
|
});
|
|
28600
28753
|
return { success: false, paneId: undefined };
|
|
28601
28754
|
});
|
|
28602
|
-
if (paneResult.success
|
|
28603
|
-
|
|
28604
|
-
|
|
28755
|
+
if (!paneResult.success || !paneResult.paneId)
|
|
28756
|
+
return;
|
|
28757
|
+
if (!this.knownSessions.has(sessionId) || this.closingSessions.has(sessionId)) {
|
|
28758
|
+
await this.multiplexer.closePane(paneResult.paneId).catch((err) => log("[multiplexer-session-manager] closing stale spawned pane failed", {
|
|
28605
28759
|
sessionId,
|
|
28606
28760
|
paneId: paneResult.paneId,
|
|
28607
|
-
|
|
28608
|
-
|
|
28609
|
-
|
|
28610
|
-
createdAt: now,
|
|
28611
|
-
lastSeenAt: now
|
|
28612
|
-
});
|
|
28613
|
-
log("[multiplexer-session-manager] pane spawned", {
|
|
28614
|
-
sessionId,
|
|
28615
|
-
paneId: paneResult.paneId
|
|
28616
|
-
});
|
|
28617
|
-
this.startPolling();
|
|
28761
|
+
error: String(err)
|
|
28762
|
+
}));
|
|
28763
|
+
return;
|
|
28618
28764
|
}
|
|
28765
|
+
const now = Date.now();
|
|
28766
|
+
this.sessions.set(sessionId, {
|
|
28767
|
+
sessionId,
|
|
28768
|
+
paneId: paneResult.paneId,
|
|
28769
|
+
parentId,
|
|
28770
|
+
title,
|
|
28771
|
+
directory,
|
|
28772
|
+
createdAt: now,
|
|
28773
|
+
lastSeenAt: now
|
|
28774
|
+
});
|
|
28775
|
+
log("[multiplexer-session-manager] pane spawned", {
|
|
28776
|
+
sessionId,
|
|
28777
|
+
paneId: paneResult.paneId
|
|
28778
|
+
});
|
|
28779
|
+
this.startPolling();
|
|
28619
28780
|
} finally {
|
|
28620
28781
|
this.spawningSessions.delete(sessionId);
|
|
28621
28782
|
}
|
|
@@ -28629,7 +28790,7 @@ class MultiplexerSessionManager {
|
|
|
28629
28790
|
if (!sessionId)
|
|
28630
28791
|
return;
|
|
28631
28792
|
if (event.properties?.status?.type === "idle") {
|
|
28632
|
-
await this.closeSession(sessionId);
|
|
28793
|
+
await this.closeSession(sessionId, "idle");
|
|
28633
28794
|
return;
|
|
28634
28795
|
}
|
|
28635
28796
|
if (event.properties?.status?.type === "busy") {
|
|
@@ -28641,14 +28802,13 @@ class MultiplexerSessionManager {
|
|
|
28641
28802
|
return;
|
|
28642
28803
|
if (event.type !== "session.deleted")
|
|
28643
28804
|
return;
|
|
28644
|
-
const sessionId = event
|
|
28805
|
+
const sessionId = this.getSessionId(event);
|
|
28645
28806
|
if (!sessionId)
|
|
28646
28807
|
return;
|
|
28647
28808
|
log("[multiplexer-session-manager] session deleted, closing pane", {
|
|
28648
28809
|
sessionId
|
|
28649
28810
|
});
|
|
28650
|
-
await this.closeSession(sessionId);
|
|
28651
|
-
this.knownSessions.delete(sessionId);
|
|
28811
|
+
await this.closeSession(sessionId, "deleted");
|
|
28652
28812
|
}
|
|
28653
28813
|
startPolling() {
|
|
28654
28814
|
if (this.pollInterval)
|
|
@@ -28685,35 +28845,58 @@ class MultiplexerSessionManager {
|
|
|
28685
28845
|
const missingTooLong = !!tracked.missingSince && now - tracked.missingSince >= SESSION_MISSING_GRACE_MS;
|
|
28686
28846
|
const isTimedOut = now - tracked.createdAt > SESSION_TIMEOUT_MS;
|
|
28687
28847
|
if (isIdle || missingTooLong || isTimedOut) {
|
|
28688
|
-
sessionsToClose.push(
|
|
28848
|
+
sessionsToClose.push({
|
|
28849
|
+
sessionId,
|
|
28850
|
+
reason: isIdle ? "idle" : isTimedOut ? "timeout" : "missing"
|
|
28851
|
+
});
|
|
28689
28852
|
}
|
|
28690
28853
|
}
|
|
28691
|
-
for (const sessionId of sessionsToClose) {
|
|
28692
|
-
await this.closeSession(sessionId);
|
|
28854
|
+
for (const { sessionId, reason } of sessionsToClose) {
|
|
28855
|
+
await this.closeSession(sessionId, reason);
|
|
28693
28856
|
}
|
|
28694
28857
|
} catch (err) {
|
|
28695
28858
|
log("[multiplexer-session-manager] poll error", { error: String(err) });
|
|
28696
28859
|
}
|
|
28697
28860
|
}
|
|
28698
|
-
async closeSession(sessionId) {
|
|
28861
|
+
async closeSession(sessionId, reason) {
|
|
28862
|
+
if (reason === "deleted") {
|
|
28863
|
+
this.knownSessions.delete(sessionId);
|
|
28864
|
+
}
|
|
28865
|
+
const existingClose = this.closingSessions.get(sessionId);
|
|
28866
|
+
if (existingClose)
|
|
28867
|
+
return existingClose;
|
|
28699
28868
|
const tracked = this.sessions.get(sessionId);
|
|
28700
28869
|
if (!tracked || !this.multiplexer)
|
|
28701
28870
|
return;
|
|
28871
|
+
this.sessions.delete(sessionId);
|
|
28702
28872
|
log("[multiplexer-session-manager] closing session pane", {
|
|
28703
28873
|
sessionId,
|
|
28704
|
-
paneId: tracked.paneId
|
|
28874
|
+
paneId: tracked.paneId,
|
|
28875
|
+
reason
|
|
28705
28876
|
});
|
|
28706
|
-
|
|
28707
|
-
|
|
28708
|
-
|
|
28709
|
-
|
|
28710
|
-
|
|
28877
|
+
const closePromise = this.multiplexer.closePane(tracked.paneId).then(() => {
|
|
28878
|
+
return;
|
|
28879
|
+
}).catch((err) => log("[multiplexer-session-manager] failed to close session pane", {
|
|
28880
|
+
sessionId,
|
|
28881
|
+
paneId: tracked.paneId,
|
|
28882
|
+
reason,
|
|
28883
|
+
error: String(err)
|
|
28884
|
+
})).finally(() => {
|
|
28885
|
+
this.closingSessions.delete(sessionId);
|
|
28886
|
+
this.updatePolling();
|
|
28887
|
+
});
|
|
28888
|
+
this.closingSessions.set(sessionId, closePromise);
|
|
28889
|
+
await closePromise;
|
|
28711
28890
|
}
|
|
28712
28891
|
async respawnIfKnown(sessionId) {
|
|
28713
28892
|
if (!this.enabled || !this.multiplexer)
|
|
28714
28893
|
return;
|
|
28715
|
-
|
|
28894
|
+
const closing = this.closingSessions.get(sessionId);
|
|
28895
|
+
if (closing)
|
|
28896
|
+
await closing;
|
|
28897
|
+
if (this.isTrackedOrSpawning(sessionId)) {
|
|
28716
28898
|
return;
|
|
28899
|
+
}
|
|
28717
28900
|
const known = this.knownSessions.get(sessionId);
|
|
28718
28901
|
if (!known)
|
|
28719
28902
|
return;
|
|
@@ -28727,8 +28910,9 @@ class MultiplexerSessionManager {
|
|
|
28727
28910
|
});
|
|
28728
28911
|
return;
|
|
28729
28912
|
}
|
|
28730
|
-
if (this.sessions.has(sessionId))
|
|
28913
|
+
if (this.sessions.has(sessionId) || this.closingSessions.has(sessionId)) {
|
|
28731
28914
|
return;
|
|
28915
|
+
}
|
|
28732
28916
|
log("[multiplexer-session-manager] child session busy again, respawning pane", {
|
|
28733
28917
|
sessionId,
|
|
28734
28918
|
parentId: known.parentId,
|
|
@@ -28742,6 +28926,14 @@ class MultiplexerSessionManager {
|
|
|
28742
28926
|
});
|
|
28743
28927
|
if (!paneResult.success || !paneResult.paneId)
|
|
28744
28928
|
return;
|
|
28929
|
+
if (!this.knownSessions.has(sessionId) || this.closingSessions.has(sessionId)) {
|
|
28930
|
+
await this.multiplexer.closePane(paneResult.paneId).catch((err) => log("[multiplexer-session-manager] closing stale respawned pane failed", {
|
|
28931
|
+
sessionId,
|
|
28932
|
+
paneId: paneResult.paneId,
|
|
28933
|
+
error: String(err)
|
|
28934
|
+
}));
|
|
28935
|
+
return;
|
|
28936
|
+
}
|
|
28745
28937
|
const now = Date.now();
|
|
28746
28938
|
this.sessions.set(sessionId, {
|
|
28747
28939
|
sessionId,
|
|
@@ -28764,8 +28956,21 @@ class MultiplexerSessionManager {
|
|
|
28764
28956
|
isTrackedOrSpawning(sessionId) {
|
|
28765
28957
|
return this.sessions.has(sessionId) || this.spawningSessions.has(sessionId);
|
|
28766
28958
|
}
|
|
28959
|
+
updatePolling() {
|
|
28960
|
+
if (this.sessions.size > 0 || this.closingSessions.size > 0) {
|
|
28961
|
+
this.startPolling();
|
|
28962
|
+
} else {
|
|
28963
|
+
this.stopPolling();
|
|
28964
|
+
}
|
|
28965
|
+
}
|
|
28966
|
+
getSessionId(event) {
|
|
28967
|
+
return event.properties?.info?.id ?? event.properties?.sessionID;
|
|
28968
|
+
}
|
|
28767
28969
|
async cleanup() {
|
|
28768
28970
|
this.stopPolling();
|
|
28971
|
+
if (this.closingSessions.size > 0) {
|
|
28972
|
+
await Promise.all(this.closingSessions.values());
|
|
28973
|
+
}
|
|
28769
28974
|
if (this.sessions.size > 0 && this.multiplexer) {
|
|
28770
28975
|
log("[multiplexer-session-manager] closing all panes", {
|
|
28771
28976
|
count: this.sessions.size
|
|
@@ -28780,6 +28985,7 @@ class MultiplexerSessionManager {
|
|
|
28780
28985
|
}
|
|
28781
28986
|
this.knownSessions.clear();
|
|
28782
28987
|
this.spawningSessions.clear();
|
|
28988
|
+
this.closingSessions.clear();
|
|
28783
28989
|
log("[multiplexer-session-manager] cleanup complete");
|
|
28784
28990
|
}
|
|
28785
28991
|
}
|
|
@@ -29419,7 +29625,7 @@ Returns the councillor responses with a summary footer.`,
|
|
|
29419
29625
|
// src/tools/preset-manager.ts
|
|
29420
29626
|
var COMMAND_NAME3 = "preset";
|
|
29421
29627
|
function createPresetManager(ctx, config) {
|
|
29422
|
-
let activePreset = config.preset ?? null;
|
|
29628
|
+
let activePreset = getActiveRuntimePreset() ?? config.preset ?? null;
|
|
29423
29629
|
async function handleCommandExecuteBefore(input, output) {
|
|
29424
29630
|
if (input.command !== COMMAND_NAME3) {
|
|
29425
29631
|
return;
|
|
@@ -29460,21 +29666,41 @@ function createPresetManager(ctx, config) {
|
|
|
29460
29666
|
}
|
|
29461
29667
|
const agentUpdates = {};
|
|
29462
29668
|
for (const [agentName, override] of Object.entries(preset)) {
|
|
29669
|
+
const resolvedName = AGENT_ALIASES[agentName] ?? agentName;
|
|
29463
29670
|
const agentConfig = mapOverrideToAgentConfig(override);
|
|
29464
29671
|
if (Object.keys(agentConfig).length > 0) {
|
|
29465
|
-
agentUpdates[
|
|
29672
|
+
agentUpdates[resolvedName] = agentConfig;
|
|
29466
29673
|
}
|
|
29467
29674
|
}
|
|
29468
|
-
|
|
29675
|
+
const currentRuntimePreset = getActiveRuntimePreset();
|
|
29676
|
+
const resetUpdates = {};
|
|
29677
|
+
if (currentRuntimePreset && config.presets?.[currentRuntimePreset]) {
|
|
29678
|
+
const oldPreset = config.presets[currentRuntimePreset];
|
|
29679
|
+
for (const rawName of Object.keys(oldPreset)) {
|
|
29680
|
+
const resolvedOld = AGENT_ALIASES[rawName] ?? rawName;
|
|
29681
|
+
if (resolvedOld in agentUpdates)
|
|
29682
|
+
continue;
|
|
29683
|
+
const baseline = config.agents?.[resolvedOld];
|
|
29684
|
+
if (baseline) {
|
|
29685
|
+
resetUpdates[resolvedOld] = mapOverrideToAgentConfig(baseline);
|
|
29686
|
+
}
|
|
29687
|
+
}
|
|
29688
|
+
}
|
|
29689
|
+
const hasAgentUpdates = Object.keys(agentUpdates).length > 0;
|
|
29690
|
+
const allUpdates = { ...resetUpdates, ...agentUpdates };
|
|
29691
|
+
if (!hasAgentUpdates) {
|
|
29469
29692
|
output.parts.push(createInternalAgentTextPart(`Preset "${presetName}" is empty (no agent overrides defined).`));
|
|
29470
29693
|
return;
|
|
29471
29694
|
}
|
|
29695
|
+
const previousPreset = activePreset;
|
|
29696
|
+
setActiveRuntimePresetWithPrevious(presetName);
|
|
29472
29697
|
try {
|
|
29473
29698
|
await ctx.client.config.update({
|
|
29474
|
-
body: { agent:
|
|
29699
|
+
body: { agent: allUpdates }
|
|
29475
29700
|
});
|
|
29476
29701
|
activePreset = presetName;
|
|
29477
|
-
const
|
|
29702
|
+
const summaryParts = [];
|
|
29703
|
+
for (const [name, cfg] of Object.entries(agentUpdates)) {
|
|
29478
29704
|
const parts = [name];
|
|
29479
29705
|
if (cfg.model)
|
|
29480
29706
|
parts.push(`model: ${cfg.model}`);
|
|
@@ -29484,12 +29710,16 @@ function createPresetManager(ctx, config) {
|
|
|
29484
29710
|
parts.push(`temp: ${cfg.temperature}`);
|
|
29485
29711
|
if (cfg.options)
|
|
29486
29712
|
parts.push("options: yes");
|
|
29487
|
-
|
|
29488
|
-
}
|
|
29489
|
-
|
|
29713
|
+
summaryParts.push(parts.join(" → "));
|
|
29714
|
+
}
|
|
29715
|
+
if (Object.keys(resetUpdates).length > 0) {
|
|
29716
|
+
summaryParts.push(`Reset to baseline: ${Object.keys(resetUpdates).join(", ")}`);
|
|
29717
|
+
}
|
|
29490
29718
|
output.parts.push(createInternalAgentTextPart(`Switched to preset "${presetName}":
|
|
29491
|
-
${
|
|
29719
|
+
${summaryParts.join(`
|
|
29720
|
+
`)}`));
|
|
29492
29721
|
} catch (err) {
|
|
29722
|
+
rollbackRuntimePreset(previousPreset);
|
|
29493
29723
|
output.parts.push(createInternalAgentTextPart(`Failed to switch preset "${presetName}": ${String(err)}`));
|
|
29494
29724
|
}
|
|
29495
29725
|
}
|
|
@@ -31967,6 +32197,14 @@ var OhMyOpenCodeLite = async (ctx) => {
|
|
|
31967
32197
|
let toolCount = 0;
|
|
31968
32198
|
try {
|
|
31969
32199
|
config = loadPluginConfig(ctx.directory);
|
|
32200
|
+
const runtimePreset = getActiveRuntimePreset();
|
|
32201
|
+
if (runtimePreset && config.presets?.[runtimePreset]) {
|
|
32202
|
+
config.preset = runtimePreset;
|
|
32203
|
+
const presetAgents = config.presets[runtimePreset];
|
|
32204
|
+
config.agents = deepMerge(config.agents, presetAgents);
|
|
32205
|
+
} else if (runtimePreset) {
|
|
32206
|
+
setActiveRuntimePreset(null);
|
|
32207
|
+
}
|
|
31970
32208
|
disabledAgents = getDisabledAgents(config);
|
|
31971
32209
|
rewriteDisplayNameMentions = createDisplayNameMentionRewriter(config);
|
|
31972
32210
|
agentDefs = createAgents(config);
|
|
@@ -32163,6 +32401,83 @@ var OhMyOpenCodeLite = async (ctx) => {
|
|
|
32163
32401
|
});
|
|
32164
32402
|
}
|
|
32165
32403
|
}
|
|
32404
|
+
const runtimePresetName = getActiveRuntimePreset();
|
|
32405
|
+
if (runtimePresetName && config.presets?.[runtimePresetName]) {
|
|
32406
|
+
const runtimePreset = config.presets[runtimePresetName];
|
|
32407
|
+
for (const [agentName, override] of Object.entries(runtimePreset)) {
|
|
32408
|
+
const resolvedName = AGENT_ALIASES[agentName] ?? agentName;
|
|
32409
|
+
const entry = configAgent[resolvedName];
|
|
32410
|
+
if (!entry)
|
|
32411
|
+
continue;
|
|
32412
|
+
if (typeof override.model === "string") {
|
|
32413
|
+
entry.model = override.model;
|
|
32414
|
+
} else if (Array.isArray(override.model) && override.model.length > 0) {
|
|
32415
|
+
const first = override.model[0];
|
|
32416
|
+
entry.model = typeof first === "string" ? first : first.id;
|
|
32417
|
+
if (typeof first !== "string" && first.variant) {
|
|
32418
|
+
entry.variant = first.variant;
|
|
32419
|
+
}
|
|
32420
|
+
}
|
|
32421
|
+
if (typeof override.variant === "string") {
|
|
32422
|
+
entry.variant = override.variant;
|
|
32423
|
+
} else if ("variant" in override) {
|
|
32424
|
+
delete entry.variant;
|
|
32425
|
+
}
|
|
32426
|
+
if (typeof override.temperature === "number") {
|
|
32427
|
+
entry.temperature = override.temperature;
|
|
32428
|
+
} else if ("temperature" in override) {
|
|
32429
|
+
delete entry.temperature;
|
|
32430
|
+
}
|
|
32431
|
+
if (override.options && typeof override.options === "object" && !Array.isArray(override.options)) {
|
|
32432
|
+
entry.options = override.options;
|
|
32433
|
+
} else if ("options" in override) {
|
|
32434
|
+
delete entry.options;
|
|
32435
|
+
}
|
|
32436
|
+
log("[plugin] runtime preset override", {
|
|
32437
|
+
preset: runtimePresetName,
|
|
32438
|
+
agent: agentName,
|
|
32439
|
+
model: entry.model
|
|
32440
|
+
});
|
|
32441
|
+
}
|
|
32442
|
+
const prevPresetName = getPreviousRuntimePreset();
|
|
32443
|
+
if (prevPresetName && config.presets?.[prevPresetName]) {
|
|
32444
|
+
const prevPreset = config.presets[prevPresetName];
|
|
32445
|
+
const newPresetResolved = new Set(Object.keys(runtimePreset).map((k) => AGENT_ALIASES[k] ?? k));
|
|
32446
|
+
for (const agentName of Object.keys(prevPreset)) {
|
|
32447
|
+
const resolvedName = AGENT_ALIASES[agentName] ?? agentName;
|
|
32448
|
+
if (newPresetResolved.has(resolvedName))
|
|
32449
|
+
continue;
|
|
32450
|
+
const entry = configAgent[resolvedName];
|
|
32451
|
+
if (!entry)
|
|
32452
|
+
continue;
|
|
32453
|
+
const baseline = config.agents?.[resolvedName];
|
|
32454
|
+
const prevOverride = prevPreset[agentName];
|
|
32455
|
+
if (typeof baseline?.model === "string") {
|
|
32456
|
+
entry.model = baseline.model;
|
|
32457
|
+
}
|
|
32458
|
+
if (typeof baseline?.variant === "string") {
|
|
32459
|
+
entry.variant = baseline.variant;
|
|
32460
|
+
} else if (prevOverride && "variant" in prevOverride) {
|
|
32461
|
+
delete entry.variant;
|
|
32462
|
+
}
|
|
32463
|
+
if (typeof baseline?.temperature === "number") {
|
|
32464
|
+
entry.temperature = baseline.temperature;
|
|
32465
|
+
} else if (prevOverride && "temperature" in prevOverride) {
|
|
32466
|
+
delete entry.temperature;
|
|
32467
|
+
}
|
|
32468
|
+
if (baseline?.options && typeof baseline.options === "object" && !Array.isArray(baseline.options)) {
|
|
32469
|
+
entry.options = baseline.options;
|
|
32470
|
+
} else if (prevOverride && "options" in prevOverride) {
|
|
32471
|
+
delete entry.options;
|
|
32472
|
+
}
|
|
32473
|
+
log("[plugin] runtime preset reset from previous", {
|
|
32474
|
+
previousPreset: prevPresetName,
|
|
32475
|
+
agent: resolvedName,
|
|
32476
|
+
model: entry.model
|
|
32477
|
+
});
|
|
32478
|
+
}
|
|
32479
|
+
}
|
|
32480
|
+
}
|
|
32166
32481
|
const configMcp = opencodeConfig.mcp;
|
|
32167
32482
|
if (!configMcp) {
|
|
32168
32483
|
opencodeConfig.mcp = { ...mcps };
|
|
@@ -32220,7 +32535,6 @@ var OhMyOpenCodeLite = async (ctx) => {
|
|
|
32220
32535
|
await multiplexerSessionManager.onSessionStatus(event);
|
|
32221
32536
|
await multiplexerSessionManager.onSessionDeleted(event);
|
|
32222
32537
|
await interviewManager.handleEvent(input);
|
|
32223
|
-
await postFileToolNudgeHook.event(input);
|
|
32224
32538
|
await taskSessionManagerHook.event(input);
|
|
32225
32539
|
if (input.event.type === "session.deleted") {
|
|
32226
32540
|
const props = input.event.properties;
|
|
@@ -32269,9 +32583,6 @@ var OhMyOpenCodeLite = async (ctx) => {
|
|
|
32269
32583
|
${output.system[0]}` : "");
|
|
32270
32584
|
}
|
|
32271
32585
|
}
|
|
32272
|
-
await todoContinuationHook.handleChatSystemTransform(input, output);
|
|
32273
|
-
await postFileToolNudgeHook["experimental.chat.system.transform"](input, output);
|
|
32274
|
-
await taskSessionManagerHook["experimental.chat.system.transform"](input, output);
|
|
32275
32586
|
collapseSystemInPlace(output.system);
|
|
32276
32587
|
},
|
|
32277
32588
|
"experimental.chat.messages.transform": async (input, output) => {
|
|
@@ -32296,13 +32607,14 @@ ${output.system[0]}` : "");
|
|
|
32296
32607
|
await todoContinuationHook.handleMessagesTransform({
|
|
32297
32608
|
messages: typedOutput.messages
|
|
32298
32609
|
});
|
|
32610
|
+
await taskSessionManagerHook["experimental.chat.messages.transform"](input, typedOutput);
|
|
32299
32611
|
await phaseReminderHook["experimental.chat.messages.transform"](input, typedOutput);
|
|
32300
32612
|
await filterAvailableSkillsHook["experimental.chat.messages.transform"](input, typedOutput);
|
|
32301
32613
|
},
|
|
32302
32614
|
"tool.execute.after": async (input, output) => {
|
|
32303
32615
|
await delegateTaskRetryHook["tool.execute.after"](input, output);
|
|
32304
32616
|
await jsonErrorRecoveryHook["tool.execute.after"](input, output);
|
|
32305
|
-
await todoContinuationHook.handleToolExecuteAfter(input);
|
|
32617
|
+
await todoContinuationHook.handleToolExecuteAfter(input, output);
|
|
32306
32618
|
await postFileToolNudgeHook["tool.execute.after"](input, output);
|
|
32307
32619
|
await taskSessionManagerHook["tool.execute.after"](input, output);
|
|
32308
32620
|
}
|