oh-my-opencode-slim 2.0.0-beta.13 → 2.0.0-beta.14
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.ja-JP.md +15 -16
- package/README.ko-KR.md +632 -0
- package/README.md +41 -18
- package/README.zh-CN.md +15 -15
- package/dist/cli/background-subagents.d.ts +13 -0
- package/dist/cli/index.d.ts +2 -1
- package/dist/cli/index.js +251 -25
- package/dist/cli/install.d.ts +6 -1
- package/dist/cli/types.d.ts +5 -0
- package/dist/config/schema.d.ts +0 -13
- package/dist/hooks/index.d.ts +0 -1
- package/dist/index.js +93 -852
- package/dist/tools/preset-manager.d.ts +6 -7
- package/dist/tui.js +14 -8
- package/oh-my-opencode-slim.schema.json +0 -31
- package/package.json +3 -1
- package/src/skills/codemap.md +3 -1
- package/src/skills/oh-my-opencode-slim/SKILL.md +326 -0
- package/dist/hooks/todo-continuation/index.d.ts +0 -55
- package/dist/hooks/todo-continuation/todo-hygiene.d.ts +0 -35
package/dist/index.js
CHANGED
|
@@ -18196,6 +18196,13 @@ function getCustomOpenCodeConfigDir() {
|
|
|
18196
18196
|
const configDir = process.env.OPENCODE_CONFIG_DIR?.trim();
|
|
18197
18197
|
return configDir || undefined;
|
|
18198
18198
|
}
|
|
18199
|
+
function getConfigDir() {
|
|
18200
|
+
const customConfigDir = getCustomOpenCodeConfigDir();
|
|
18201
|
+
if (customConfigDir) {
|
|
18202
|
+
return customConfigDir;
|
|
18203
|
+
}
|
|
18204
|
+
return getDefaultOpenCodeConfigDir();
|
|
18205
|
+
}
|
|
18199
18206
|
function getConfigSearchDirs() {
|
|
18200
18207
|
const dirs = [getCustomOpenCodeConfigDir(), getDefaultOpenCodeConfigDir()];
|
|
18201
18208
|
return dirs.filter((dir, index) => {
|
|
@@ -18203,7 +18210,7 @@ function getConfigSearchDirs() {
|
|
|
18203
18210
|
});
|
|
18204
18211
|
}
|
|
18205
18212
|
function getOpenCodeConfigPaths() {
|
|
18206
|
-
const configDir =
|
|
18213
|
+
const configDir = getConfigDir();
|
|
18207
18214
|
return [join(configDir, "opencode.json"), join(configDir, "opencode.jsonc")];
|
|
18208
18215
|
}
|
|
18209
18216
|
|
|
@@ -18232,6 +18239,12 @@ var CUSTOM_SKILLS = [
|
|
|
18232
18239
|
description: "Heavy/complex coding sessions and large modifications workflow",
|
|
18233
18240
|
allowedAgents: ["orchestrator"],
|
|
18234
18241
|
sourcePath: "src/skills/deepwork"
|
|
18242
|
+
},
|
|
18243
|
+
{
|
|
18244
|
+
name: "oh-my-opencode-slim",
|
|
18245
|
+
description: "Configure, customize, and safely improve oh-my-opencode-slim setups",
|
|
18246
|
+
allowedAgents: ["orchestrator"],
|
|
18247
|
+
sourcePath: "src/skills/oh-my-opencode-slim"
|
|
18235
18248
|
}
|
|
18236
18249
|
];
|
|
18237
18250
|
|
|
@@ -18597,12 +18610,6 @@ var DivoomConfigSchema = z2.object({
|
|
|
18597
18610
|
posterizeBits: z2.number().int().min(1).max(8).default(3),
|
|
18598
18611
|
gifs: z2.record(z2.string(), z2.string().min(1)).optional()
|
|
18599
18612
|
});
|
|
18600
|
-
var TodoContinuationConfigSchema = z2.object({
|
|
18601
|
-
maxContinuations: z2.number().int().min(1).max(50).default(5).describe("Maximum consecutive auto-continuations before stopping to ask user"),
|
|
18602
|
-
cooldownMs: z2.number().int().min(0).max(30000).default(3000).describe("Delay in ms before auto-continuing (gives user time to abort)"),
|
|
18603
|
-
autoEnable: z2.boolean().default(false).describe("Automatically enable auto-continue when the orchestrator session has enough todos"),
|
|
18604
|
-
autoEnableThreshold: z2.number().int().min(1).max(50).default(4).describe("Number of todos that triggers auto-enable (only used when autoEnable is true)")
|
|
18605
|
-
});
|
|
18606
18613
|
var FailoverConfigSchema = z2.object({
|
|
18607
18614
|
enabled: z2.boolean().default(true),
|
|
18608
18615
|
timeoutMs: z2.number().min(0).default(15000),
|
|
@@ -18649,7 +18656,6 @@ var PluginConfigSchema = z2.object({
|
|
|
18649
18656
|
interview: InterviewConfigSchema.optional(),
|
|
18650
18657
|
backgroundJobs: BackgroundJobsConfigSchema.optional(),
|
|
18651
18658
|
divoom: DivoomConfigSchema.optional(),
|
|
18652
|
-
todoContinuation: TodoContinuationConfigSchema.optional(),
|
|
18653
18659
|
fallback: FailoverConfigSchema.optional(),
|
|
18654
18660
|
council: CouncilConfigSchema.optional()
|
|
18655
18661
|
}).superRefine((value, ctx) => {
|
|
@@ -18903,34 +18909,32 @@ function parseModelReference(model) {
|
|
|
18903
18909
|
async function promptWithTimeout(client, args, timeoutMs, signal) {
|
|
18904
18910
|
if (signal?.aborted)
|
|
18905
18911
|
throw new Error("Prompt cancelled");
|
|
18906
|
-
if (timeoutMs <= 0) {
|
|
18907
|
-
await client.session.prompt(args);
|
|
18908
|
-
return;
|
|
18909
|
-
}
|
|
18910
18912
|
const sessionId = args.path.id;
|
|
18913
|
+
const hasTimeout = timeoutMs > 0;
|
|
18911
18914
|
let timer;
|
|
18912
18915
|
let onAbort;
|
|
18913
18916
|
try {
|
|
18914
18917
|
const promptPromise = client.session.prompt(args);
|
|
18915
18918
|
promptPromise.catch(() => {});
|
|
18916
|
-
|
|
18917
|
-
|
|
18918
|
-
new Promise((_, reject) => {
|
|
18919
|
+
const racers = [promptPromise];
|
|
18920
|
+
if (hasTimeout) {
|
|
18921
|
+
racers.push(new Promise((_, reject) => {
|
|
18919
18922
|
timer = setTimeout(() => {
|
|
18920
18923
|
reject(new OperationTimeoutError(`Prompt timed out after ${timeoutMs}ms`));
|
|
18921
18924
|
}, timeoutMs);
|
|
18922
|
-
})
|
|
18923
|
-
|
|
18924
|
-
|
|
18925
|
-
|
|
18925
|
+
}));
|
|
18926
|
+
}
|
|
18927
|
+
if (signal) {
|
|
18928
|
+
racers.push(new Promise((_, reject) => {
|
|
18926
18929
|
if (signal.aborted) {
|
|
18927
18930
|
reject(new Error("Prompt cancelled"));
|
|
18928
18931
|
return;
|
|
18929
18932
|
}
|
|
18930
18933
|
onAbort = () => reject(new Error("Prompt cancelled"));
|
|
18931
18934
|
signal.addEventListener("abort", onAbort, { once: true });
|
|
18932
|
-
})
|
|
18933
|
-
|
|
18935
|
+
}));
|
|
18936
|
+
}
|
|
18937
|
+
await Promise.race(racers);
|
|
18934
18938
|
} catch (error) {
|
|
18935
18939
|
if (error instanceof OperationTimeoutError) {
|
|
18936
18940
|
try {
|
|
@@ -19161,13 +19165,6 @@ Balance: respect dependencies, avoid parallelizing what must be sequential, and
|
|
|
19161
19165
|
- If multiple remembered sessions fit, prefer the most recently used matching session.
|
|
19162
19166
|
- Prefer re-uses over creating new sessions all the time
|
|
19163
19167
|
|
|
19164
|
-
### Auto-Continue
|
|
19165
|
-
When working through multi-step tasks, consider enabling auto-continue to avoid stopping between batches:
|
|
19166
|
-
- **Enable when:** User requests autonomous/batch work, or you create 4+ todos in a session
|
|
19167
|
-
- **Don't enable when:** User is in an interactive/conversational flow, or each step needs explicit review
|
|
19168
|
-
- Use the \`auto_continue\` tool with \`enabled: true\` to activate. The system will automatically resume you when incomplete todos remain after you stop.
|
|
19169
|
-
- The user can toggle this anytime via the \`/auto-continue\` command.
|
|
19170
|
-
|
|
19171
19168
|
### Validation routing
|
|
19172
19169
|
- Validation is a workflow stage owned by the Orchestrator, not a separate specialist
|
|
19173
19170
|
${enabledValidationRouting}
|
|
@@ -20039,10 +20036,6 @@ function setActiveRuntimePresetWithPrevious(name) {
|
|
|
20039
20036
|
previousRuntimePreset = activeRuntimePreset;
|
|
20040
20037
|
activeRuntimePreset = name;
|
|
20041
20038
|
}
|
|
20042
|
-
function rollbackRuntimePreset(previous) {
|
|
20043
|
-
activeRuntimePreset = previous;
|
|
20044
|
-
previousRuntimePreset = null;
|
|
20045
|
-
}
|
|
20046
20039
|
|
|
20047
20040
|
// src/utils/logger.ts
|
|
20048
20041
|
import * as fs2 from "node:fs";
|
|
@@ -20055,7 +20048,7 @@ var RETENTION_MS = 7 * 24 * 60 * 60 * 1000;
|
|
|
20055
20048
|
var logFile = null;
|
|
20056
20049
|
var writeChain = Promise.resolve();
|
|
20057
20050
|
function getLogDir() {
|
|
20058
|
-
return process.env.OPENCODE_LOG_DIR ?? path2.join(os.homedir(), ".local/share/opencode");
|
|
20051
|
+
return process.env.OPENCODE_LOG_DIR ?? path2.join(os.homedir(), ".local/share/opencode/log");
|
|
20059
20052
|
}
|
|
20060
20053
|
function cleanupOldLogs(logDir) {
|
|
20061
20054
|
try {
|
|
@@ -24642,715 +24635,6 @@ function formatCancelledTaskStatusOutput(taskID, summary = "cancelled") {
|
|
|
24642
24635
|
].join(`
|
|
24643
24636
|
`);
|
|
24644
24637
|
}
|
|
24645
|
-
// src/hooks/todo-continuation/index.ts
|
|
24646
|
-
import { tool } from "@opencode-ai/plugin";
|
|
24647
|
-
|
|
24648
|
-
// src/hooks/todo-continuation/todo-hygiene.ts
|
|
24649
|
-
var TODO_HYGIENE_REMINDER = "If the active task changed or finished, update the todo list to match the current work state.";
|
|
24650
|
-
var TODO_FINAL_ACTIVE_REMINDER = "If you are finishing now, do not leave the active todo in_progress. Mark it completed, or move unfinished work back to pending.";
|
|
24651
|
-
var RESET = new Set(["todowrite"]);
|
|
24652
|
-
var IGNORE = new Set(["auto_continue"]);
|
|
24653
|
-
function createTodoHygiene(options) {
|
|
24654
|
-
const pending = new Map;
|
|
24655
|
-
const active = new Set;
|
|
24656
|
-
function clearCycle(sessionID) {
|
|
24657
|
-
pending.delete(sessionID);
|
|
24658
|
-
}
|
|
24659
|
-
function clear(sessionID) {
|
|
24660
|
-
clearCycle(sessionID);
|
|
24661
|
-
active.delete(sessionID);
|
|
24662
|
-
}
|
|
24663
|
-
function isFinalActive(state) {
|
|
24664
|
-
return state.inProgressCount === 1 && state.pendingCount === 0 && state.openCount === 1;
|
|
24665
|
-
}
|
|
24666
|
-
function mark(sessionID, reason) {
|
|
24667
|
-
const reasons = pending.get(sessionID) ?? new Set;
|
|
24668
|
-
reasons.add(reason);
|
|
24669
|
-
pending.set(sessionID, reasons);
|
|
24670
|
-
}
|
|
24671
|
-
function pick(reasons) {
|
|
24672
|
-
if (reasons.has("final_active")) {
|
|
24673
|
-
return TODO_FINAL_ACTIVE_REMINDER;
|
|
24674
|
-
}
|
|
24675
|
-
return TODO_HYGIENE_REMINDER;
|
|
24676
|
-
}
|
|
24677
|
-
return {
|
|
24678
|
-
handleRequestStart(input) {
|
|
24679
|
-
clear(input.sessionID);
|
|
24680
|
-
},
|
|
24681
|
-
async handleToolExecuteAfter(input, _output) {
|
|
24682
|
-
if (!input.sessionID) {
|
|
24683
|
-
return;
|
|
24684
|
-
}
|
|
24685
|
-
const tool = input.tool.toLowerCase();
|
|
24686
|
-
if (IGNORE.has(tool)) {
|
|
24687
|
-
return;
|
|
24688
|
-
}
|
|
24689
|
-
try {
|
|
24690
|
-
if (RESET.has(tool)) {
|
|
24691
|
-
if (options.shouldInject && !options.shouldInject(input.sessionID)) {
|
|
24692
|
-
clear(input.sessionID);
|
|
24693
|
-
return;
|
|
24694
|
-
}
|
|
24695
|
-
active.add(input.sessionID);
|
|
24696
|
-
clearCycle(input.sessionID);
|
|
24697
|
-
const state2 = await options.getTodoState(input.sessionID);
|
|
24698
|
-
if (!state2.hasOpenTodos) {
|
|
24699
|
-
active.delete(input.sessionID);
|
|
24700
|
-
options.log?.("Cleared todo hygiene cycle", {
|
|
24701
|
-
sessionID: input.sessionID,
|
|
24702
|
-
tool
|
|
24703
|
-
});
|
|
24704
|
-
return;
|
|
24705
|
-
}
|
|
24706
|
-
if (!isFinalActive(state2)) {
|
|
24707
|
-
options.log?.("Reset todo hygiene cycle", {
|
|
24708
|
-
sessionID: input.sessionID,
|
|
24709
|
-
tool
|
|
24710
|
-
});
|
|
24711
|
-
return;
|
|
24712
|
-
}
|
|
24713
|
-
mark(input.sessionID, "final_active");
|
|
24714
|
-
options.log?.("Armed final-active todo hygiene reminder", {
|
|
24715
|
-
sessionID: input.sessionID,
|
|
24716
|
-
tool
|
|
24717
|
-
});
|
|
24718
|
-
return;
|
|
24719
|
-
}
|
|
24720
|
-
if (!active.has(input.sessionID)) {
|
|
24721
|
-
return;
|
|
24722
|
-
}
|
|
24723
|
-
if (pending.get(input.sessionID)?.has("final_active")) {
|
|
24724
|
-
return;
|
|
24725
|
-
}
|
|
24726
|
-
if (options.shouldInject && !options.shouldInject(input.sessionID)) {
|
|
24727
|
-
clear(input.sessionID);
|
|
24728
|
-
return;
|
|
24729
|
-
}
|
|
24730
|
-
const state = await options.getTodoState(input.sessionID);
|
|
24731
|
-
if (!state.hasOpenTodos) {
|
|
24732
|
-
clear(input.sessionID);
|
|
24733
|
-
return;
|
|
24734
|
-
}
|
|
24735
|
-
if (isFinalActive(state)) {
|
|
24736
|
-
mark(input.sessionID, "final_active");
|
|
24737
|
-
} else {
|
|
24738
|
-
mark(input.sessionID, "general");
|
|
24739
|
-
}
|
|
24740
|
-
options.log?.("Armed todo hygiene reminder", {
|
|
24741
|
-
sessionID: input.sessionID,
|
|
24742
|
-
tool,
|
|
24743
|
-
reasons: Array.from(pending.get(input.sessionID) ?? [])
|
|
24744
|
-
});
|
|
24745
|
-
} catch (error) {
|
|
24746
|
-
options.log?.("Skipped todo hygiene reminder: failed to inspect todos", {
|
|
24747
|
-
sessionID: input.sessionID,
|
|
24748
|
-
tool,
|
|
24749
|
-
error: error instanceof Error ? error.message : String(error)
|
|
24750
|
-
});
|
|
24751
|
-
}
|
|
24752
|
-
},
|
|
24753
|
-
getPendingReminder(sessionID) {
|
|
24754
|
-
const reasons = pending.get(sessionID);
|
|
24755
|
-
if (!reasons || reasons.size === 0) {
|
|
24756
|
-
return null;
|
|
24757
|
-
}
|
|
24758
|
-
if (options.shouldInject && !options.shouldInject(sessionID)) {
|
|
24759
|
-
clear(sessionID);
|
|
24760
|
-
return null;
|
|
24761
|
-
}
|
|
24762
|
-
const reminder = pick(reasons);
|
|
24763
|
-
options.log?.("Read todo hygiene reminder", {
|
|
24764
|
-
sessionID,
|
|
24765
|
-
reminder,
|
|
24766
|
-
reasons: Array.from(reasons)
|
|
24767
|
-
});
|
|
24768
|
-
return reminder;
|
|
24769
|
-
},
|
|
24770
|
-
handleEvent(event) {
|
|
24771
|
-
if (event.type !== "session.deleted") {
|
|
24772
|
-
return;
|
|
24773
|
-
}
|
|
24774
|
-
const sessionID = event.properties?.sessionID ?? event.properties?.info?.id;
|
|
24775
|
-
if (!sessionID) {
|
|
24776
|
-
return;
|
|
24777
|
-
}
|
|
24778
|
-
clear(sessionID);
|
|
24779
|
-
}
|
|
24780
|
-
};
|
|
24781
|
-
}
|
|
24782
|
-
|
|
24783
|
-
// src/hooks/todo-continuation/index.ts
|
|
24784
|
-
var HOOK_NAME = "todo-continuation";
|
|
24785
|
-
var COMMAND_NAME2 = "auto-continue";
|
|
24786
|
-
var TODO_STATE_TIMEOUT_MS = 500;
|
|
24787
|
-
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.]";
|
|
24788
|
-
var TODO_HYGIENE_INSTRUCTION_OPEN = '<instruction name="todo_hygiene">';
|
|
24789
|
-
var TODO_HYGIENE_INSTRUCTION_CLOSE = "</instruction>";
|
|
24790
|
-
var SUPPRESS_AFTER_ABORT_MS = 5000;
|
|
24791
|
-
var NOTIFICATION_BUSY_GRACE_MS = 250;
|
|
24792
|
-
var QUESTION_PHRASES = [
|
|
24793
|
-
"would you like",
|
|
24794
|
-
"should i",
|
|
24795
|
-
"do you want",
|
|
24796
|
-
"please review",
|
|
24797
|
-
"let me know",
|
|
24798
|
-
"what do you think",
|
|
24799
|
-
"can you confirm",
|
|
24800
|
-
"would you prefer",
|
|
24801
|
-
"shall i",
|
|
24802
|
-
"any thoughts"
|
|
24803
|
-
];
|
|
24804
|
-
var TERMINAL_TODO_STATUSES = ["completed", "cancelled"];
|
|
24805
|
-
function isQuestion(text) {
|
|
24806
|
-
const lowerText = text.toLowerCase().trim();
|
|
24807
|
-
if (/\?\s*$/.test(lowerText)) {
|
|
24808
|
-
return true;
|
|
24809
|
-
}
|
|
24810
|
-
return QUESTION_PHRASES.some((phrase) => lowerText.includes(phrase));
|
|
24811
|
-
}
|
|
24812
|
-
function cancelPendingTimer(state) {
|
|
24813
|
-
if (state.pendingTimer) {
|
|
24814
|
-
clearTimeout(state.pendingTimer);
|
|
24815
|
-
state.pendingTimer = null;
|
|
24816
|
-
}
|
|
24817
|
-
state.pendingTimerSessionId = null;
|
|
24818
|
-
}
|
|
24819
|
-
function resetState(state) {
|
|
24820
|
-
cancelPendingTimer(state);
|
|
24821
|
-
state.consecutiveContinuations = 0;
|
|
24822
|
-
state.suppressUntil = 0;
|
|
24823
|
-
state.isAutoInjecting = false;
|
|
24824
|
-
state.notifyingSessionIds.clear();
|
|
24825
|
-
state.notificationBusyUntilBySession.clear();
|
|
24826
|
-
}
|
|
24827
|
-
function stripTodoHygieneInstruction(text) {
|
|
24828
|
-
const trimmed = text.trimEnd();
|
|
24829
|
-
if (!trimmed.endsWith(TODO_HYGIENE_INSTRUCTION_CLOSE)) {
|
|
24830
|
-
return trimmed;
|
|
24831
|
-
}
|
|
24832
|
-
const start = trimmed.lastIndexOf(TODO_HYGIENE_INSTRUCTION_OPEN);
|
|
24833
|
-
if (start === -1) {
|
|
24834
|
-
return trimmed;
|
|
24835
|
-
}
|
|
24836
|
-
return trimmed.slice(0, start).trimEnd();
|
|
24837
|
-
}
|
|
24838
|
-
function appendTodoHygieneInstruction(message, reminder) {
|
|
24839
|
-
const textPart = [...message.parts].reverse().find((part) => part.type === "text" && typeof part.text === "string");
|
|
24840
|
-
if (!textPart)
|
|
24841
|
-
return;
|
|
24842
|
-
const baseText = stripTodoHygieneInstruction(textPart.text ?? "");
|
|
24843
|
-
const instruction = `${TODO_HYGIENE_INSTRUCTION_OPEN}
|
|
24844
|
-
${reminder}
|
|
24845
|
-
${TODO_HYGIENE_INSTRUCTION_CLOSE}`;
|
|
24846
|
-
textPart.text = baseText ? `${baseText}
|
|
24847
|
-
|
|
24848
|
-
${instruction}` : instruction;
|
|
24849
|
-
}
|
|
24850
|
-
function stripTodoHygieneInstructionFromMessage(message) {
|
|
24851
|
-
const textPart = [...message.parts].reverse().find((part) => part.type === "text" && typeof part.text === "string");
|
|
24852
|
-
if (!textPart)
|
|
24853
|
-
return;
|
|
24854
|
-
textPart.text = stripTodoHygieneInstruction(textPart.text ?? "");
|
|
24855
|
-
}
|
|
24856
|
-
function createTodoContinuationHook(ctx, config) {
|
|
24857
|
-
const maxContinuations = config?.maxContinuations ?? 5;
|
|
24858
|
-
const cooldownMs = config?.cooldownMs ?? 3000;
|
|
24859
|
-
const autoEnable = config?.autoEnable ?? false;
|
|
24860
|
-
const autoEnableThreshold = config?.autoEnableThreshold ?? 4;
|
|
24861
|
-
const backgroundJobBoard = config?.backgroundJobBoard;
|
|
24862
|
-
const requestSignatureBySession = new Map;
|
|
24863
|
-
const state = {
|
|
24864
|
-
enabled: false,
|
|
24865
|
-
consecutiveContinuations: 0,
|
|
24866
|
-
pendingTimer: null,
|
|
24867
|
-
pendingTimerSessionId: null,
|
|
24868
|
-
suppressUntil: 0,
|
|
24869
|
-
orchestratorSessionIds: new Set,
|
|
24870
|
-
sawChatMessage: false,
|
|
24871
|
-
isAutoInjecting: false,
|
|
24872
|
-
notifyingSessionIds: new Set,
|
|
24873
|
-
notificationBusyUntilBySession: new Map
|
|
24874
|
-
};
|
|
24875
|
-
async function fetchTodos(sessionID) {
|
|
24876
|
-
const result = await withTimeout(ctx.client.session.todo({
|
|
24877
|
-
path: { id: sessionID }
|
|
24878
|
-
}), TODO_STATE_TIMEOUT_MS, `Todo state lookup timed out after ${TODO_STATE_TIMEOUT_MS}ms`);
|
|
24879
|
-
return result.data;
|
|
24880
|
-
}
|
|
24881
|
-
const hygiene = createTodoHygiene({
|
|
24882
|
-
getTodoState: async (sessionID) => {
|
|
24883
|
-
const todos = await fetchTodos(sessionID);
|
|
24884
|
-
const openTodos = todos.filter((todo) => !TERMINAL_TODO_STATUSES.includes(todo.status));
|
|
24885
|
-
return {
|
|
24886
|
-
hasOpenTodos: openTodos.length > 0,
|
|
24887
|
-
openCount: openTodos.length,
|
|
24888
|
-
inProgressCount: openTodos.filter((todo) => todo.status === "in_progress").length,
|
|
24889
|
-
pendingCount: openTodos.filter((todo) => todo.status === "pending").length
|
|
24890
|
-
};
|
|
24891
|
-
},
|
|
24892
|
-
shouldInject: (sessionID) => isOrchestratorSession(sessionID),
|
|
24893
|
-
log: (message, meta) => log(`[${HOOK_NAME}] ${message}`, meta)
|
|
24894
|
-
});
|
|
24895
|
-
function inferSessionID(messages, index) {
|
|
24896
|
-
const direct = messages[index]?.info.sessionID;
|
|
24897
|
-
if (direct) {
|
|
24898
|
-
return direct;
|
|
24899
|
-
}
|
|
24900
|
-
for (let i = index - 1;i >= 0; i--) {
|
|
24901
|
-
const sessionID = messages[i]?.info.sessionID;
|
|
24902
|
-
if (sessionID) {
|
|
24903
|
-
return sessionID;
|
|
24904
|
-
}
|
|
24905
|
-
}
|
|
24906
|
-
for (let i = index + 1;i < messages.length; i++) {
|
|
24907
|
-
const sessionID = messages[i]?.info.sessionID;
|
|
24908
|
-
if (sessionID) {
|
|
24909
|
-
return sessionID;
|
|
24910
|
-
}
|
|
24911
|
-
}
|
|
24912
|
-
if (state.orchestratorSessionIds.size === 1) {
|
|
24913
|
-
return Array.from(state.orchestratorSessionIds)[0];
|
|
24914
|
-
}
|
|
24915
|
-
return;
|
|
24916
|
-
}
|
|
24917
|
-
function isExternalUserMessage(message) {
|
|
24918
|
-
if (message.info.role !== "user") {
|
|
24919
|
-
return false;
|
|
24920
|
-
}
|
|
24921
|
-
const visibleText = message.parts.filter((part) => part.type === "text" && typeof part.text === "string" && !part.text.includes(SLIM_INTERNAL_INITIATOR_MARKER)).map((part) => part.text?.trim() ?? "").filter(Boolean).join(`
|
|
24922
|
-
`);
|
|
24923
|
-
const hasNonTextPart = message.parts.some((part) => part.type !== "text");
|
|
24924
|
-
return !(!visibleText && !hasNonTextPart && message.parts.some((part) => part.type === "text" && typeof part.text === "string" && part.text.includes(SLIM_INTERNAL_INITIATOR_MARKER)));
|
|
24925
|
-
}
|
|
24926
|
-
function getLastExternalUserMessage(messages) {
|
|
24927
|
-
for (let i = messages.length - 1;i >= 0; i--) {
|
|
24928
|
-
const message = messages[i];
|
|
24929
|
-
if (!isExternalUserMessage(message)) {
|
|
24930
|
-
continue;
|
|
24931
|
-
}
|
|
24932
|
-
const sessionID = inferSessionID(messages, i);
|
|
24933
|
-
const partSignature = message.parts.map((part) => {
|
|
24934
|
-
if (part.type === "text" && typeof part.text === "string") {
|
|
24935
|
-
const text = stripTodoHygieneInstruction(part.text);
|
|
24936
|
-
return `${part.type}:${text.includes(SLIM_INTERNAL_INITIATOR_MARKER) ? "<internal>" : text.trim()}`;
|
|
24937
|
-
}
|
|
24938
|
-
return part.type ?? "unknown";
|
|
24939
|
-
}).join("|");
|
|
24940
|
-
const ordinal = messages.slice(0, i + 1).filter((item) => isExternalUserMessage(item)).length;
|
|
24941
|
-
return {
|
|
24942
|
-
sessionID,
|
|
24943
|
-
agent: message.info.agent,
|
|
24944
|
-
message,
|
|
24945
|
-
signature: message.info.id ? `${message.info.id}:${partSignature}` : `${ordinal}:${partSignature}`
|
|
24946
|
-
};
|
|
24947
|
-
}
|
|
24948
|
-
return null;
|
|
24949
|
-
}
|
|
24950
|
-
async function handleMessagesTransform(output) {
|
|
24951
|
-
const lastUserMessage = getLastExternalUserMessage(output.messages);
|
|
24952
|
-
if (!lastUserMessage) {
|
|
24953
|
-
return;
|
|
24954
|
-
}
|
|
24955
|
-
if (lastUserMessage.agent && lastUserMessage.agent !== "orchestrator") {
|
|
24956
|
-
return;
|
|
24957
|
-
}
|
|
24958
|
-
if (!lastUserMessage.sessionID) {
|
|
24959
|
-
for (const sessionID of state.orchestratorSessionIds) {
|
|
24960
|
-
requestSignatureBySession.delete(sessionID);
|
|
24961
|
-
hygiene.handleRequestStart({ sessionID });
|
|
24962
|
-
}
|
|
24963
|
-
return;
|
|
24964
|
-
}
|
|
24965
|
-
const knownOrchestrator = isOrchestratorSession(lastUserMessage.sessionID);
|
|
24966
|
-
if (lastUserMessage.agent === "orchestrator") {
|
|
24967
|
-
registerOrchestratorSession(lastUserMessage.sessionID);
|
|
24968
|
-
} else if (!knownOrchestrator) {
|
|
24969
|
-
return;
|
|
24970
|
-
}
|
|
24971
|
-
if (requestSignatureBySession.get(lastUserMessage.sessionID) === lastUserMessage.signature) {
|
|
24972
|
-
const reminder = hygiene.getPendingReminder(lastUserMessage.sessionID);
|
|
24973
|
-
const guardrail = backgroundGuardrail(lastUserMessage.sessionID);
|
|
24974
|
-
const combinedReminder = [reminder, guardrail].filter((item) => Boolean(item)).join(" ");
|
|
24975
|
-
if (combinedReminder) {
|
|
24976
|
-
appendTodoHygieneInstruction(lastUserMessage.message, combinedReminder);
|
|
24977
|
-
} else {
|
|
24978
|
-
stripTodoHygieneInstructionFromMessage(lastUserMessage.message);
|
|
24979
|
-
}
|
|
24980
|
-
return;
|
|
24981
|
-
}
|
|
24982
|
-
requestSignatureBySession.set(lastUserMessage.sessionID, lastUserMessage.signature);
|
|
24983
|
-
stripTodoHygieneInstructionFromMessage(lastUserMessage.message);
|
|
24984
|
-
hygiene.handleRequestStart({ sessionID: lastUserMessage.sessionID });
|
|
24985
|
-
}
|
|
24986
|
-
function markNotificationStarted(sessionID) {
|
|
24987
|
-
state.notifyingSessionIds.add(sessionID);
|
|
24988
|
-
}
|
|
24989
|
-
function markNotificationFinished(sessionID) {
|
|
24990
|
-
state.notifyingSessionIds.delete(sessionID);
|
|
24991
|
-
state.notificationBusyUntilBySession.set(sessionID, Date.now() + NOTIFICATION_BUSY_GRACE_MS);
|
|
24992
|
-
}
|
|
24993
|
-
function clearNotificationState(sessionID) {
|
|
24994
|
-
state.notifyingSessionIds.delete(sessionID);
|
|
24995
|
-
state.notificationBusyUntilBySession.delete(sessionID);
|
|
24996
|
-
}
|
|
24997
|
-
function isNotificationBusy(sessionID) {
|
|
24998
|
-
if (state.notifyingSessionIds.has(sessionID)) {
|
|
24999
|
-
return true;
|
|
25000
|
-
}
|
|
25001
|
-
const until = state.notificationBusyUntilBySession.get(sessionID) ?? 0;
|
|
25002
|
-
if (until <= Date.now()) {
|
|
25003
|
-
state.notificationBusyUntilBySession.delete(sessionID);
|
|
25004
|
-
return false;
|
|
25005
|
-
}
|
|
25006
|
-
return true;
|
|
25007
|
-
}
|
|
25008
|
-
function isOrchestratorSession(sessionID) {
|
|
25009
|
-
return state.orchestratorSessionIds.has(sessionID);
|
|
25010
|
-
}
|
|
25011
|
-
function registerOrchestratorSession(sessionID) {
|
|
25012
|
-
state.orchestratorSessionIds.add(sessionID);
|
|
25013
|
-
}
|
|
25014
|
-
function backgroundGuardrail(sessionID) {
|
|
25015
|
-
if (!backgroundJobBoard)
|
|
25016
|
-
return;
|
|
25017
|
-
const hasRunning = backgroundJobBoard.hasRunning(sessionID);
|
|
25018
|
-
const hasTerminal = backgroundJobBoard.hasTerminalUnreconciled(sessionID);
|
|
25019
|
-
if (hasRunning && hasTerminal) {
|
|
25020
|
-
return "Background jobs are still unresolved: call task_status for running jobs and reconcile terminal Background Job Board results before dependent work or finalizing.";
|
|
25021
|
-
}
|
|
25022
|
-
if (hasTerminal) {
|
|
25023
|
-
return "Background jobs have terminal results: reconcile the Background Job Board results before finalizing.";
|
|
25024
|
-
}
|
|
25025
|
-
if (hasRunning) {
|
|
25026
|
-
return "Background jobs are still running: call task_status before dependent work or finalizing.";
|
|
25027
|
-
}
|
|
25028
|
-
return;
|
|
25029
|
-
}
|
|
25030
|
-
function continuationPrompt(sessionID) {
|
|
25031
|
-
const guardrail = backgroundGuardrail(sessionID);
|
|
25032
|
-
if (!guardrail)
|
|
25033
|
-
return CONTINUATION_PROMPT;
|
|
25034
|
-
return `${CONTINUATION_PROMPT} ${guardrail}`;
|
|
25035
|
-
}
|
|
25036
|
-
function handleChatMessage(input) {
|
|
25037
|
-
if (!input.agent) {
|
|
25038
|
-
return;
|
|
25039
|
-
}
|
|
25040
|
-
state.sawChatMessage = true;
|
|
25041
|
-
if (input.agent === "orchestrator") {
|
|
25042
|
-
registerOrchestratorSession(input.sessionID);
|
|
25043
|
-
}
|
|
25044
|
-
}
|
|
25045
|
-
const autoContinue = tool({
|
|
25046
|
-
description: "Toggle auto-continuation for incomplete todos. When enabled, the orchestrator will automatically continue working through its todo list when it stops with incomplete items.",
|
|
25047
|
-
args: { enabled: tool.schema.boolean() },
|
|
25048
|
-
execute: async (args) => {
|
|
25049
|
-
const enabled = args.enabled;
|
|
25050
|
-
state.enabled = enabled;
|
|
25051
|
-
state.consecutiveContinuations = 0;
|
|
25052
|
-
if (enabled) {
|
|
25053
|
-
state.suppressUntil = 0;
|
|
25054
|
-
log(`[${HOOK_NAME}] Auto-continue enabled`, { maxContinuations });
|
|
25055
|
-
return `Auto-continue enabled. Will auto-continue for up to ${maxContinuations} consecutive injections.`;
|
|
25056
|
-
}
|
|
25057
|
-
cancelPendingTimer(state);
|
|
25058
|
-
log(`[${HOOK_NAME}] Auto-continue disabled`);
|
|
25059
|
-
return "Auto-continue disabled.";
|
|
25060
|
-
}
|
|
25061
|
-
});
|
|
25062
|
-
async function handleEvent(input) {
|
|
25063
|
-
const { event } = input;
|
|
25064
|
-
const properties = event.properties ?? {};
|
|
25065
|
-
hygiene.handleEvent({
|
|
25066
|
-
type: event.type,
|
|
25067
|
-
properties: {
|
|
25068
|
-
info: properties.info,
|
|
25069
|
-
sessionID: properties.sessionID
|
|
25070
|
-
}
|
|
25071
|
-
});
|
|
25072
|
-
if (event.type === "session.idle" || event.type === "session.status" && properties.status?.type === "idle") {
|
|
25073
|
-
const sessionID = properties.sessionID;
|
|
25074
|
-
if (!sessionID) {
|
|
25075
|
-
return;
|
|
25076
|
-
}
|
|
25077
|
-
log(`[${HOOK_NAME}] Session idle`, { sessionID });
|
|
25078
|
-
if (!state.sawChatMessage && state.orchestratorSessionIds.size === 0) {
|
|
25079
|
-
registerOrchestratorSession(sessionID);
|
|
25080
|
-
log(`[${HOOK_NAME}] Tracked orchestrator session`, {
|
|
25081
|
-
sessionID
|
|
25082
|
-
});
|
|
25083
|
-
}
|
|
25084
|
-
if (!isOrchestratorSession(sessionID)) {
|
|
25085
|
-
log(`[${HOOK_NAME}] Skipped: not orchestrator session`, {
|
|
25086
|
-
sessionID
|
|
25087
|
-
});
|
|
25088
|
-
return;
|
|
25089
|
-
}
|
|
25090
|
-
if (autoEnable && !state.enabled) {
|
|
25091
|
-
try {
|
|
25092
|
-
const todos = await fetchTodos(sessionID);
|
|
25093
|
-
const incompleteCount2 = todos.filter((t) => !TERMINAL_TODO_STATUSES.includes(t.status)).length;
|
|
25094
|
-
if (incompleteCount2 >= autoEnableThreshold) {
|
|
25095
|
-
state.enabled = true;
|
|
25096
|
-
state.consecutiveContinuations = 0;
|
|
25097
|
-
state.suppressUntil = 0;
|
|
25098
|
-
log(`[${HOOK_NAME}] Auto-enabled: ${incompleteCount2} incomplete todos >= threshold ${autoEnableThreshold}`, { sessionID });
|
|
25099
|
-
} else {
|
|
25100
|
-
log(`[${HOOK_NAME}] Auto-enable skipped: ${incompleteCount2} incomplete todos < threshold ${autoEnableThreshold}`, { sessionID });
|
|
25101
|
-
}
|
|
25102
|
-
} catch (error) {
|
|
25103
|
-
log(`[${HOOK_NAME}] Warning: failed to fetch todos for auto-enable check`, {
|
|
25104
|
-
sessionID,
|
|
25105
|
-
error: error instanceof Error ? error.message : String(error)
|
|
25106
|
-
});
|
|
25107
|
-
}
|
|
25108
|
-
}
|
|
25109
|
-
if (!state.enabled) {
|
|
25110
|
-
log(`[${HOOK_NAME}] Skipped: auto-continue not enabled`, {
|
|
25111
|
-
sessionID
|
|
25112
|
-
});
|
|
25113
|
-
return;
|
|
25114
|
-
}
|
|
25115
|
-
let hasIncompleteTodos = false;
|
|
25116
|
-
let incompleteCount = 0;
|
|
25117
|
-
try {
|
|
25118
|
-
const todos = await fetchTodos(sessionID);
|
|
25119
|
-
incompleteCount = todos.filter((t) => !TERMINAL_TODO_STATUSES.includes(t.status)).length;
|
|
25120
|
-
hasIncompleteTodos = incompleteCount > 0;
|
|
25121
|
-
log(`[${HOOK_NAME}] Fetched todos`, {
|
|
25122
|
-
sessionID,
|
|
25123
|
-
hasIncompleteTodos,
|
|
25124
|
-
total: todos.length
|
|
25125
|
-
});
|
|
25126
|
-
} catch (error) {
|
|
25127
|
-
log(`[${HOOK_NAME}] Warning: failed to fetch todos`, {
|
|
25128
|
-
sessionID,
|
|
25129
|
-
error: error instanceof Error ? error.message : String(error)
|
|
25130
|
-
});
|
|
25131
|
-
return;
|
|
25132
|
-
}
|
|
25133
|
-
if (!hasIncompleteTodos) {
|
|
25134
|
-
log(`[${HOOK_NAME}] Skipped: no incomplete todos`, { sessionID });
|
|
25135
|
-
return;
|
|
25136
|
-
}
|
|
25137
|
-
let lastAssistantIsQuestion = false;
|
|
25138
|
-
try {
|
|
25139
|
-
const messagesResult = await ctx.client.session.messages({
|
|
25140
|
-
path: { id: sessionID }
|
|
25141
|
-
});
|
|
25142
|
-
const messages = messagesResult.data;
|
|
25143
|
-
const lastAssistantMessage = messages.slice().reverse().find((m) => m.info?.role === "assistant");
|
|
25144
|
-
if (lastAssistantMessage?.parts) {
|
|
25145
|
-
const lastText = lastAssistantMessage.parts.map((p) => p.text ?? "").join(" ");
|
|
25146
|
-
lastAssistantIsQuestion = isQuestion(lastText);
|
|
25147
|
-
}
|
|
25148
|
-
log(`[${HOOK_NAME}] Fetched messages`, {
|
|
25149
|
-
sessionID,
|
|
25150
|
-
lastAssistantIsQuestion
|
|
25151
|
-
});
|
|
25152
|
-
} catch (error) {
|
|
25153
|
-
log(`[${HOOK_NAME}] Warning: failed to fetch messages`, {
|
|
25154
|
-
sessionID,
|
|
25155
|
-
error: error instanceof Error ? error.message : String(error)
|
|
25156
|
-
});
|
|
25157
|
-
return;
|
|
25158
|
-
}
|
|
25159
|
-
if (lastAssistantIsQuestion) {
|
|
25160
|
-
log(`[${HOOK_NAME}] Skipped: last message is question`, {
|
|
25161
|
-
sessionID
|
|
25162
|
-
});
|
|
25163
|
-
return;
|
|
25164
|
-
}
|
|
25165
|
-
if (state.consecutiveContinuations >= maxContinuations) {
|
|
25166
|
-
log(`[${HOOK_NAME}] Skipped: max continuations reached`, {
|
|
25167
|
-
sessionID,
|
|
25168
|
-
consecutive: state.consecutiveContinuations,
|
|
25169
|
-
max: maxContinuations
|
|
25170
|
-
});
|
|
25171
|
-
return;
|
|
25172
|
-
}
|
|
25173
|
-
const now = Date.now();
|
|
25174
|
-
if (now < state.suppressUntil) {
|
|
25175
|
-
log(`[${HOOK_NAME}] Skipped: in suppress window`, {
|
|
25176
|
-
sessionID,
|
|
25177
|
-
suppressUntil: state.suppressUntil
|
|
25178
|
-
});
|
|
25179
|
-
return;
|
|
25180
|
-
}
|
|
25181
|
-
if (state.pendingTimer !== null || state.isAutoInjecting) {
|
|
25182
|
-
log(`[${HOOK_NAME}] Skipped: timer pending or injection in flight`, {
|
|
25183
|
-
sessionID
|
|
25184
|
-
});
|
|
25185
|
-
return;
|
|
25186
|
-
}
|
|
25187
|
-
log(`[${HOOK_NAME}] Scheduling continuation`, {
|
|
25188
|
-
sessionID,
|
|
25189
|
-
delayMs: cooldownMs
|
|
25190
|
-
});
|
|
25191
|
-
markNotificationStarted(sessionID);
|
|
25192
|
-
ctx.client.session.prompt({
|
|
25193
|
-
path: { id: sessionID },
|
|
25194
|
-
body: {
|
|
25195
|
-
noReply: true,
|
|
25196
|
-
parts: [
|
|
25197
|
-
{
|
|
25198
|
-
type: "text",
|
|
25199
|
-
text: [
|
|
25200
|
-
`⎔ Auto-continue: ${incompleteCount} incomplete todos remaining — resuming in ${cooldownMs / 1000}s — Esc×2 to cancel`,
|
|
25201
|
-
"",
|
|
25202
|
-
"[system status: continue without acknowledging this notification]"
|
|
25203
|
-
].join(`
|
|
25204
|
-
`)
|
|
25205
|
-
}
|
|
25206
|
-
]
|
|
25207
|
-
}
|
|
25208
|
-
}).catch(() => {}).finally(() => {
|
|
25209
|
-
markNotificationFinished(sessionID);
|
|
25210
|
-
});
|
|
25211
|
-
state.pendingTimerSessionId = sessionID;
|
|
25212
|
-
state.pendingTimer = setTimeout(async () => {
|
|
25213
|
-
state.pendingTimer = null;
|
|
25214
|
-
state.pendingTimerSessionId = null;
|
|
25215
|
-
clearNotificationState(sessionID);
|
|
25216
|
-
if (!state.enabled) {
|
|
25217
|
-
log(`[${HOOK_NAME}] Cancelled: disabled during cooldown`, {
|
|
25218
|
-
sessionID
|
|
25219
|
-
});
|
|
25220
|
-
return;
|
|
25221
|
-
}
|
|
25222
|
-
state.isAutoInjecting = true;
|
|
25223
|
-
try {
|
|
25224
|
-
await ctx.client.session.prompt({
|
|
25225
|
-
path: { id: sessionID },
|
|
25226
|
-
body: {
|
|
25227
|
-
parts: [
|
|
25228
|
-
createInternalAgentTextPart(continuationPrompt(sessionID))
|
|
25229
|
-
]
|
|
25230
|
-
}
|
|
25231
|
-
});
|
|
25232
|
-
state.consecutiveContinuations++;
|
|
25233
|
-
log(`[${HOOK_NAME}] Continuation injected`, {
|
|
25234
|
-
sessionID,
|
|
25235
|
-
consecutive: state.consecutiveContinuations
|
|
25236
|
-
});
|
|
25237
|
-
} catch (error) {
|
|
25238
|
-
log(`[${HOOK_NAME}] Error: failed to inject continuation`, {
|
|
25239
|
-
sessionID,
|
|
25240
|
-
error: error instanceof Error ? error.message : String(error)
|
|
25241
|
-
});
|
|
25242
|
-
} finally {
|
|
25243
|
-
state.isAutoInjecting = false;
|
|
25244
|
-
}
|
|
25245
|
-
}, cooldownMs);
|
|
25246
|
-
} else if (event.type === "session.status") {
|
|
25247
|
-
const status = properties.status;
|
|
25248
|
-
const sessionID = properties.sessionID;
|
|
25249
|
-
if (status?.type === "busy") {
|
|
25250
|
-
const isOrchestrator = isOrchestratorSession(sessionID);
|
|
25251
|
-
const isNotification = isNotificationBusy(sessionID);
|
|
25252
|
-
if (isOrchestrator && !isNotification && state.pendingTimerSessionId === sessionID) {
|
|
25253
|
-
cancelPendingTimer(state);
|
|
25254
|
-
}
|
|
25255
|
-
if (!state.isAutoInjecting && !isNotification && isOrchestrator && state.consecutiveContinuations > 0) {
|
|
25256
|
-
state.consecutiveContinuations = 0;
|
|
25257
|
-
log(`[${HOOK_NAME}] Reset consecutive count on user activity`, {
|
|
25258
|
-
sessionID
|
|
25259
|
-
});
|
|
25260
|
-
}
|
|
25261
|
-
}
|
|
25262
|
-
} else if (event.type === "session.error") {
|
|
25263
|
-
const error = properties.error;
|
|
25264
|
-
const sessionID = properties.sessionID;
|
|
25265
|
-
const errorName = error?.name;
|
|
25266
|
-
const isOrchestrator = isOrchestratorSession(sessionID);
|
|
25267
|
-
if (isOrchestrator && (errorName === "MessageAbortedError" || errorName === "AbortError")) {
|
|
25268
|
-
state.suppressUntil = Date.now() + SUPPRESS_AFTER_ABORT_MS;
|
|
25269
|
-
log(`[${HOOK_NAME}] Suppressed continuation after abort`, {
|
|
25270
|
-
sessionID,
|
|
25271
|
-
errorName
|
|
25272
|
-
});
|
|
25273
|
-
}
|
|
25274
|
-
if (isOrchestrator) {
|
|
25275
|
-
cancelPendingTimer(state);
|
|
25276
|
-
log(`[${HOOK_NAME}] Cancelled pending timer on error`, {
|
|
25277
|
-
sessionID
|
|
25278
|
-
});
|
|
25279
|
-
}
|
|
25280
|
-
} else if (event.type === "session.deleted") {
|
|
25281
|
-
const deletedSessionId = properties.info?.id ?? properties.sessionID;
|
|
25282
|
-
if (deletedSessionId && isOrchestratorSession(deletedSessionId)) {
|
|
25283
|
-
requestSignatureBySession.delete(deletedSessionId);
|
|
25284
|
-
if (state.pendingTimerSessionId === deletedSessionId) {
|
|
25285
|
-
cancelPendingTimer(state);
|
|
25286
|
-
log(`[${HOOK_NAME}] Cancelled pending timer on orchestrator delete`, {
|
|
25287
|
-
sessionID: deletedSessionId
|
|
25288
|
-
});
|
|
25289
|
-
}
|
|
25290
|
-
state.orchestratorSessionIds.delete(deletedSessionId);
|
|
25291
|
-
clearNotificationState(deletedSessionId);
|
|
25292
|
-
if (state.orchestratorSessionIds.size === 0) {
|
|
25293
|
-
resetState(state);
|
|
25294
|
-
state.sawChatMessage = false;
|
|
25295
|
-
}
|
|
25296
|
-
log(`[${HOOK_NAME}] Reset orchestrator session on delete`, {
|
|
25297
|
-
sessionID: deletedSessionId
|
|
25298
|
-
});
|
|
25299
|
-
}
|
|
25300
|
-
}
|
|
25301
|
-
}
|
|
25302
|
-
async function handleCommandExecuteBefore(input, output) {
|
|
25303
|
-
if (input.command !== COMMAND_NAME2) {
|
|
25304
|
-
return;
|
|
25305
|
-
}
|
|
25306
|
-
registerOrchestratorSession(input.sessionID);
|
|
25307
|
-
output.parts.length = 0;
|
|
25308
|
-
const arg = input.arguments.trim().toLowerCase();
|
|
25309
|
-
let newEnabled;
|
|
25310
|
-
if (arg === "on") {
|
|
25311
|
-
newEnabled = true;
|
|
25312
|
-
} else if (arg === "off") {
|
|
25313
|
-
newEnabled = false;
|
|
25314
|
-
} else {
|
|
25315
|
-
newEnabled = !state.enabled;
|
|
25316
|
-
}
|
|
25317
|
-
state.enabled = newEnabled;
|
|
25318
|
-
state.consecutiveContinuations = 0;
|
|
25319
|
-
if (!newEnabled) {
|
|
25320
|
-
cancelPendingTimer(state);
|
|
25321
|
-
output.parts.push(createInternalAgentTextPart("[Auto-continue: disabled by user command.]"));
|
|
25322
|
-
log(`[${HOOK_NAME}] Disabled via /${COMMAND_NAME2} command`);
|
|
25323
|
-
return;
|
|
25324
|
-
}
|
|
25325
|
-
state.suppressUntil = 0;
|
|
25326
|
-
log(`[${HOOK_NAME}] Enabled via /${COMMAND_NAME2} command`, {
|
|
25327
|
-
maxContinuations
|
|
25328
|
-
});
|
|
25329
|
-
let hasIncompleteTodos = false;
|
|
25330
|
-
try {
|
|
25331
|
-
const todos = await fetchTodos(input.sessionID);
|
|
25332
|
-
hasIncompleteTodos = todos.some((t) => !TERMINAL_TODO_STATUSES.includes(t.status));
|
|
25333
|
-
} catch (error) {
|
|
25334
|
-
log(`[${HOOK_NAME}] Warning: failed to fetch todos in command hook`, {
|
|
25335
|
-
sessionID: input.sessionID,
|
|
25336
|
-
error: error instanceof Error ? error.message : String(error)
|
|
25337
|
-
});
|
|
25338
|
-
}
|
|
25339
|
-
if (hasIncompleteTodos) {
|
|
25340
|
-
output.parts.push(createInternalAgentTextPart(`${continuationPrompt(input.sessionID)} [Auto-continue enabled: up to ${maxContinuations} continuations.]`));
|
|
25341
|
-
} else {
|
|
25342
|
-
output.parts.push(createInternalAgentTextPart(`[Auto-continue: enabled for up to ${maxContinuations} continuations. No incomplete todos right now.]`));
|
|
25343
|
-
}
|
|
25344
|
-
}
|
|
25345
|
-
return {
|
|
25346
|
-
tool: { auto_continue: autoContinue },
|
|
25347
|
-
handleToolExecuteAfter: hygiene.handleToolExecuteAfter,
|
|
25348
|
-
handleMessagesTransform,
|
|
25349
|
-
handleEvent,
|
|
25350
|
-
handleChatMessage,
|
|
25351
|
-
handleCommandExecuteBefore
|
|
25352
|
-
};
|
|
25353
|
-
}
|
|
25354
24638
|
// src/interview/manager.ts
|
|
25355
24639
|
import path13 from "node:path";
|
|
25356
24640
|
|
|
@@ -28338,7 +27622,7 @@ function buildAnswerPrompt(answers, questions, maxQuestions) {
|
|
|
28338
27622
|
}
|
|
28339
27623
|
|
|
28340
27624
|
// src/interview/service.ts
|
|
28341
|
-
var
|
|
27625
|
+
var COMMAND_NAME2 = "interview";
|
|
28342
27626
|
var DEFAULT_MAX_QUESTIONS = 2;
|
|
28343
27627
|
function isTruthyEnvFlag(value) {
|
|
28344
27628
|
if (!value) {
|
|
@@ -28585,11 +27869,11 @@ function createInterviewService(ctx, config, deps) {
|
|
|
28585
27869
|
}
|
|
28586
27870
|
function registerCommand(opencodeConfig) {
|
|
28587
27871
|
const configCommand = opencodeConfig.command;
|
|
28588
|
-
if (!configCommand?.[
|
|
27872
|
+
if (!configCommand?.[COMMAND_NAME2]) {
|
|
28589
27873
|
if (!opencodeConfig.command) {
|
|
28590
27874
|
opencodeConfig.command = {};
|
|
28591
27875
|
}
|
|
28592
|
-
opencodeConfig.command[
|
|
27876
|
+
opencodeConfig.command[COMMAND_NAME2] = {
|
|
28593
27877
|
template: "Start an interview and write a live markdown spec",
|
|
28594
27878
|
description: "Open a localhost interview UI linked to the current OpenCode session"
|
|
28595
27879
|
};
|
|
@@ -28663,7 +27947,7 @@ function createInterviewService(ctx, config, deps) {
|
|
|
28663
27947
|
}
|
|
28664
27948
|
}
|
|
28665
27949
|
async function handleCommandExecuteBefore(input, output) {
|
|
28666
|
-
if (input.command !==
|
|
27950
|
+
if (input.command !== COMMAND_NAME2) {
|
|
28667
27951
|
return;
|
|
28668
27952
|
}
|
|
28669
27953
|
const idea = input.arguments.trim();
|
|
@@ -30320,7 +29604,7 @@ async function isServerRunning(serverUrl, timeoutMs = 3000, maxAttempts = 2) {
|
|
|
30320
29604
|
return false;
|
|
30321
29605
|
}
|
|
30322
29606
|
// src/tools/ast-grep/tools.ts
|
|
30323
|
-
import { tool
|
|
29607
|
+
import { tool } from "@opencode-ai/plugin";
|
|
30324
29608
|
|
|
30325
29609
|
// src/tools/ast-grep/cli.ts
|
|
30326
29610
|
import { existsSync as existsSync9 } from "node:fs";
|
|
@@ -30799,14 +30083,14 @@ function showOutputToUser(context, output) {
|
|
|
30799
30083
|
const ctx = context;
|
|
30800
30084
|
ctx.metadata?.({ metadata: { output } });
|
|
30801
30085
|
}
|
|
30802
|
-
var ast_grep_search =
|
|
30086
|
+
var ast_grep_search = tool({
|
|
30803
30087
|
description: "Search code patterns across filesystem using AST-aware matching. Supports 25 languages. " + "Use meta-variables: $VAR (single node), $$$ (multiple nodes). " + "IMPORTANT: Patterns must be complete AST nodes (valid code). " + "For functions, include params and body: 'export async function $NAME($$$) { $$$ }' not 'export async function $NAME'. " + "Examples: 'console.log($MSG)', 'def $FUNC($$$):', 'async function $NAME($$$)'",
|
|
30804
30088
|
args: {
|
|
30805
|
-
pattern:
|
|
30806
|
-
lang:
|
|
30807
|
-
paths:
|
|
30808
|
-
globs:
|
|
30809
|
-
context:
|
|
30089
|
+
pattern: tool.schema.string().describe("AST pattern with meta-variables ($VAR, $$$). Must be complete AST node."),
|
|
30090
|
+
lang: tool.schema.enum(CLI_LANGUAGES).describe("Target language"),
|
|
30091
|
+
paths: tool.schema.array(tool.schema.string()).optional().describe("Paths to search (default: ['.'])"),
|
|
30092
|
+
globs: tool.schema.array(tool.schema.string()).optional().describe("Include/exclude globs (prefix ! to exclude)"),
|
|
30093
|
+
context: tool.schema.number().optional().describe("Context lines around match")
|
|
30810
30094
|
},
|
|
30811
30095
|
execute: async (args, context) => {
|
|
30812
30096
|
try {
|
|
@@ -30835,15 +30119,15 @@ ${hint}`;
|
|
|
30835
30119
|
}
|
|
30836
30120
|
}
|
|
30837
30121
|
});
|
|
30838
|
-
var ast_grep_replace =
|
|
30122
|
+
var ast_grep_replace = tool({
|
|
30839
30123
|
description: "Replace code patterns across filesystem with AST-aware rewriting. " + "Dry-run by default. Use meta-variables in rewrite to preserve matched content. " + "Example: pattern='console.log($MSG)' rewrite='logger.info($MSG)'",
|
|
30840
30124
|
args: {
|
|
30841
|
-
pattern:
|
|
30842
|
-
rewrite:
|
|
30843
|
-
lang:
|
|
30844
|
-
paths:
|
|
30845
|
-
globs:
|
|
30846
|
-
dryRun:
|
|
30125
|
+
pattern: tool.schema.string().describe("AST pattern to match"),
|
|
30126
|
+
rewrite: tool.schema.string().describe("Replacement pattern (can use $VAR from pattern)"),
|
|
30127
|
+
lang: tool.schema.enum(CLI_LANGUAGES).describe("Target language"),
|
|
30128
|
+
paths: tool.schema.array(tool.schema.string()).optional().describe("Paths to search"),
|
|
30129
|
+
globs: tool.schema.array(tool.schema.string()).optional().describe("Include/exclude globs"),
|
|
30130
|
+
dryRun: tool.schema.boolean().optional().describe("Preview changes without applying (default: true)")
|
|
30847
30131
|
},
|
|
30848
30132
|
execute: async (args, context) => {
|
|
30849
30133
|
try {
|
|
@@ -30867,14 +30151,14 @@ var ast_grep_replace = tool2({
|
|
|
30867
30151
|
});
|
|
30868
30152
|
// src/tools/cancel-task.ts
|
|
30869
30153
|
import {
|
|
30870
|
-
tool as
|
|
30154
|
+
tool as tool2
|
|
30871
30155
|
} from "@opencode-ai/plugin";
|
|
30872
|
-
var z4 =
|
|
30156
|
+
var z4 = tool2.schema;
|
|
30873
30157
|
|
|
30874
30158
|
class SessionStillRunningError extends Error {
|
|
30875
30159
|
}
|
|
30876
30160
|
function createCancelTaskTool(options) {
|
|
30877
|
-
const cancel_task =
|
|
30161
|
+
const cancel_task = tool2({
|
|
30878
30162
|
description: `Cancel a tracked background specialist task.
|
|
30879
30163
|
|
|
30880
30164
|
Use only for obsolete, wrong, conflicting, or user-requested cancellation. Accepts either the native task_id/session ID or the parent-scoped alias shown in the Background Job Board. Cancellation is not rollback: if cancelling a writer, inspect and reconcile partial file changes before replacing the lane.`,
|
|
@@ -31264,9 +30548,9 @@ function unknownTaskOutput(taskID, message) {
|
|
|
31264
30548
|
}
|
|
31265
30549
|
// src/tools/council.ts
|
|
31266
30550
|
import {
|
|
31267
|
-
tool as
|
|
30551
|
+
tool as tool3
|
|
31268
30552
|
} from "@opencode-ai/plugin";
|
|
31269
|
-
var z5 =
|
|
30553
|
+
var z5 = tool3.schema;
|
|
31270
30554
|
function formatModelComposition(councillorResults) {
|
|
31271
30555
|
return councillorResults.map((cr) => {
|
|
31272
30556
|
const shortModel = shortModelLabel(cr.model);
|
|
@@ -31274,7 +30558,7 @@ function formatModelComposition(councillorResults) {
|
|
|
31274
30558
|
}).join(", ");
|
|
31275
30559
|
}
|
|
31276
30560
|
function createCouncilTool(_ctx, councilManager) {
|
|
31277
|
-
const council_session =
|
|
30561
|
+
const council_session = tool3({
|
|
31278
30562
|
description: `Launch a multi-LLM council session for consensus-based analysis.
|
|
31279
30563
|
|
|
31280
30564
|
Sends the prompt to multiple models (councillors) in parallel and returns their formatted responses for you to synthesize.
|
|
@@ -31328,6 +30612,9 @@ Returns the councillor responses with a summary footer.`,
|
|
|
31328
30612
|
});
|
|
31329
30613
|
return { council_session };
|
|
31330
30614
|
}
|
|
30615
|
+
// src/tools/preset-manager.ts
|
|
30616
|
+
import * as fs10 from "node:fs";
|
|
30617
|
+
|
|
31331
30618
|
// src/tui-state.ts
|
|
31332
30619
|
import * as fs9 from "node:fs";
|
|
31333
30620
|
import * as os5 from "node:os";
|
|
@@ -31397,11 +30684,11 @@ function recordTuiAgentModel(input) {
|
|
|
31397
30684
|
}
|
|
31398
30685
|
|
|
31399
30686
|
// src/tools/preset-manager.ts
|
|
31400
|
-
var
|
|
30687
|
+
var COMMAND_NAME3 = "preset";
|
|
31401
30688
|
function createPresetManager(ctx, config) {
|
|
31402
30689
|
let activePreset = getActiveRuntimePreset() ?? config.preset ?? null;
|
|
31403
30690
|
async function handleCommandExecuteBefore(input, output) {
|
|
31404
|
-
if (input.command !==
|
|
30691
|
+
if (input.command !== COMMAND_NAME3) {
|
|
31405
30692
|
return;
|
|
31406
30693
|
}
|
|
31407
30694
|
output.parts.length = 0;
|
|
@@ -31420,11 +30707,11 @@ function createPresetManager(ctx, config) {
|
|
|
31420
30707
|
}
|
|
31421
30708
|
function registerCommand(opencodeConfig) {
|
|
31422
30709
|
const configCommand = opencodeConfig.command;
|
|
31423
|
-
if (!configCommand?.[
|
|
30710
|
+
if (!configCommand?.[COMMAND_NAME3]) {
|
|
31424
30711
|
if (!opencodeConfig.command) {
|
|
31425
30712
|
opencodeConfig.command = {};
|
|
31426
30713
|
}
|
|
31427
|
-
opencodeConfig.command[
|
|
30714
|
+
opencodeConfig.command[COMMAND_NAME3] = {
|
|
31428
30715
|
template: "List available presets and switch between them",
|
|
31429
30716
|
description: "Switch agent presets at runtime (e.g., /preset cheap, /preset powerful)"
|
|
31430
30717
|
};
|
|
@@ -31446,64 +30733,47 @@ function createPresetManager(ctx, config) {
|
|
|
31446
30733
|
agentUpdates[resolvedName] = agentConfig;
|
|
31447
30734
|
}
|
|
31448
30735
|
}
|
|
31449
|
-
const currentRuntimePreset = getActiveRuntimePreset();
|
|
31450
|
-
const resetUpdates = {};
|
|
31451
|
-
if (currentRuntimePreset && config.presets?.[currentRuntimePreset]) {
|
|
31452
|
-
const oldPreset = config.presets[currentRuntimePreset];
|
|
31453
|
-
for (const rawName of Object.keys(oldPreset)) {
|
|
31454
|
-
const resolvedOld = AGENT_ALIASES[rawName] ?? rawName;
|
|
31455
|
-
if (resolvedOld in agentUpdates)
|
|
31456
|
-
continue;
|
|
31457
|
-
const baseline = config.agents?.[resolvedOld];
|
|
31458
|
-
if (baseline) {
|
|
31459
|
-
resetUpdates[resolvedOld] = mapOverrideToAgentConfig(baseline);
|
|
31460
|
-
}
|
|
31461
|
-
}
|
|
31462
|
-
}
|
|
31463
30736
|
const hasAgentUpdates = Object.keys(agentUpdates).length > 0;
|
|
31464
|
-
const allUpdates = { ...resetUpdates, ...agentUpdates };
|
|
31465
30737
|
if (!hasAgentUpdates) {
|
|
31466
30738
|
output.parts.push(createInternalAgentTextPart(`Preset "${presetName}" is empty (no agent overrides defined).`));
|
|
31467
30739
|
return;
|
|
31468
30740
|
}
|
|
31469
|
-
const previousPreset = activePreset;
|
|
31470
30741
|
setActiveRuntimePresetWithPrevious(presetName);
|
|
31471
30742
|
try {
|
|
31472
|
-
|
|
31473
|
-
|
|
31474
|
-
|
|
31475
|
-
|
|
31476
|
-
|
|
31477
|
-
|
|
31478
|
-
|
|
31479
|
-
|
|
31480
|
-
|
|
31481
|
-
|
|
31482
|
-
|
|
31483
|
-
|
|
31484
|
-
|
|
31485
|
-
|
|
31486
|
-
|
|
31487
|
-
|
|
31488
|
-
|
|
31489
|
-
|
|
31490
|
-
|
|
31491
|
-
|
|
31492
|
-
|
|
31493
|
-
|
|
31494
|
-
|
|
31495
|
-
|
|
31496
|
-
|
|
31497
|
-
if (
|
|
31498
|
-
|
|
31499
|
-
|
|
31500
|
-
|
|
30743
|
+
const { userConfigPath } = findPluginConfigPaths(ctx.directory);
|
|
30744
|
+
if (userConfigPath) {
|
|
30745
|
+
const raw = fs10.readFileSync(userConfigPath, "utf-8");
|
|
30746
|
+
const persisted = JSON.parse(stripJsonComments(raw));
|
|
30747
|
+
persisted.preset = presetName;
|
|
30748
|
+
fs10.writeFileSync(userConfigPath, `${JSON.stringify(persisted, null, 2)}
|
|
30749
|
+
`);
|
|
30750
|
+
}
|
|
30751
|
+
} catch {}
|
|
30752
|
+
const snapshot = readTuiSnapshot();
|
|
30753
|
+
const agentModels = { ...snapshot.agentModels };
|
|
30754
|
+
for (const [agentName, agentConfig] of Object.entries(agentUpdates)) {
|
|
30755
|
+
if (typeof agentConfig.model === "string") {
|
|
30756
|
+
agentModels[agentName] = agentConfig.model;
|
|
30757
|
+
}
|
|
30758
|
+
}
|
|
30759
|
+
recordTuiAgentModels({ agentModels });
|
|
30760
|
+
activePreset = presetName;
|
|
30761
|
+
const summaryParts = [];
|
|
30762
|
+
for (const [name, cfg] of Object.entries(agentUpdates)) {
|
|
30763
|
+
const parts = [name];
|
|
30764
|
+
if (cfg.model)
|
|
30765
|
+
parts.push(`model: ${cfg.model}`);
|
|
30766
|
+
if (cfg.variant)
|
|
30767
|
+
parts.push(`variant: ${cfg.variant}`);
|
|
30768
|
+
if (cfg.temperature !== undefined)
|
|
30769
|
+
parts.push(`temp: ${cfg.temperature}`);
|
|
30770
|
+
if (cfg.options)
|
|
30771
|
+
parts.push("options: yes");
|
|
30772
|
+
summaryParts.push(parts.join(" → "));
|
|
30773
|
+
}
|
|
30774
|
+
output.parts.push(createInternalAgentTextPart(`Saved preset "${presetName}". Restart or reload OpenCode to apply it to agent configuration. The current session was not reloaded to avoid interrupting the active conversation.
|
|
31501
30775
|
${summaryParts.join(`
|
|
31502
30776
|
`)}`));
|
|
31503
|
-
} catch (err) {
|
|
31504
|
-
rollbackRuntimePreset(previousPreset);
|
|
31505
|
-
output.parts.push(createInternalAgentTextPart(`Failed to switch preset "${presetName}": ${String(err)}`));
|
|
31506
|
-
}
|
|
31507
30777
|
}
|
|
31508
30778
|
function mapOverrideToAgentConfig(override) {
|
|
31509
30779
|
const agentConfig = {};
|
|
@@ -31593,7 +30863,7 @@ var WEBFETCH_DESCRIPTION = "Fetch a URL with better extraction for static/docs p
|
|
|
31593
30863
|
import os6 from "node:os";
|
|
31594
30864
|
import path18 from "node:path";
|
|
31595
30865
|
import {
|
|
31596
|
-
tool as
|
|
30866
|
+
tool as tool4
|
|
31597
30867
|
} from "@opencode-ai/plugin";
|
|
31598
30868
|
|
|
31599
30869
|
// src/tools/smartfetch/binary.ts
|
|
@@ -33375,10 +32645,10 @@ async function runSecondaryModelWithFallback(client, directory, models, prompt,
|
|
|
33375
32645
|
}
|
|
33376
32646
|
|
|
33377
32647
|
// src/tools/smartfetch/tool.ts
|
|
33378
|
-
var z6 =
|
|
32648
|
+
var z6 = tool4.schema;
|
|
33379
32649
|
function createWebfetchTool(pluginCtx, options = {}) {
|
|
33380
32650
|
const binaryDir = options.binaryDir || path18.join(os6.tmpdir(), "opencode-smartfetch");
|
|
33381
|
-
return
|
|
32651
|
+
return tool4({
|
|
33382
32652
|
description: WEBFETCH_DESCRIPTION,
|
|
33383
32653
|
args: {
|
|
33384
32654
|
url: z6.httpUrl(),
|
|
@@ -33969,7 +33239,6 @@ var OhMyOpenCodeLite = async (ctx) => {
|
|
|
33969
33239
|
let applyPatchHook;
|
|
33970
33240
|
let jsonErrorRecoveryHook;
|
|
33971
33241
|
let foregroundFallback;
|
|
33972
|
-
let todoContinuationHook;
|
|
33973
33242
|
let deepworkCommandHook;
|
|
33974
33243
|
let taskSessionManagerHook;
|
|
33975
33244
|
let backgroundJobBoard;
|
|
@@ -34062,13 +33331,6 @@ var OhMyOpenCodeLite = async (ctx) => {
|
|
|
34062
33331
|
applyPatchHook = createApplyPatchHook(ctx);
|
|
34063
33332
|
jsonErrorRecoveryHook = createJsonErrorRecoveryHook(ctx);
|
|
34064
33333
|
foregroundFallback = new ForegroundFallbackManager(ctx.client, runtimeChains, config.fallback?.enabled !== false && Object.keys(runtimeChains).length > 0);
|
|
34065
|
-
todoContinuationHook = createTodoContinuationHook(ctx, {
|
|
34066
|
-
maxContinuations: config.todoContinuation?.maxContinuations ?? 5,
|
|
34067
|
-
cooldownMs: config.todoContinuation?.cooldownMs ?? 3000,
|
|
34068
|
-
autoEnable: config.todoContinuation?.autoEnable ?? false,
|
|
34069
|
-
autoEnableThreshold: config.todoContinuation?.autoEnableThreshold ?? 4,
|
|
34070
|
-
backgroundJobBoard
|
|
34071
|
-
});
|
|
34072
33334
|
deepworkCommandHook = createDeepworkCommandHook();
|
|
34073
33335
|
taskSessionManagerHook = createTaskSessionManagerHook(ctx, {
|
|
34074
33336
|
maxSessionsPerAgent: config.backgroundJobs?.maxSessionsPerAgent ?? 2,
|
|
@@ -34085,7 +33347,7 @@ var OhMyOpenCodeLite = async (ctx) => {
|
|
|
34085
33347
|
backgroundJobBoard,
|
|
34086
33348
|
shouldManageSession: (sessionID) => sessionAgentMap.get(sessionID) === "orchestrator"
|
|
34087
33349
|
});
|
|
34088
|
-
toolCount = Object.keys(councilTools).length + Object.keys(cancelTaskTools).length +
|
|
33350
|
+
toolCount = Object.keys(councilTools).length + Object.keys(cancelTaskTools).length + 1 + 2;
|
|
34089
33351
|
} catch (err) {
|
|
34090
33352
|
log("[plugin] FATAL: init failed", String(err));
|
|
34091
33353
|
await appLog(ctx, "error", `INIT FAILED: ${String(err)}. Report at github.com/alvinunreal/oh-my-opencode-slim/issues/310`);
|
|
@@ -34129,7 +33391,6 @@ var OhMyOpenCodeLite = async (ctx) => {
|
|
|
34129
33391
|
...councilTools,
|
|
34130
33392
|
...cancelTaskTools,
|
|
34131
33393
|
webfetch,
|
|
34132
|
-
...todoContinuationHook.tool,
|
|
34133
33394
|
ast_grep_search,
|
|
34134
33395
|
ast_grep_replace
|
|
34135
33396
|
},
|
|
@@ -34316,16 +33577,6 @@ var OhMyOpenCodeLite = async (ctx) => {
|
|
|
34316
33577
|
}
|
|
34317
33578
|
agentConfigEntry.permission = agentPermission;
|
|
34318
33579
|
}
|
|
34319
|
-
const configCommand = opencodeConfig.command;
|
|
34320
|
-
if (!configCommand?.["auto-continue"]) {
|
|
34321
|
-
if (!opencodeConfig.command) {
|
|
34322
|
-
opencodeConfig.command = {};
|
|
34323
|
-
}
|
|
34324
|
-
opencodeConfig.command["auto-continue"] = {
|
|
34325
|
-
template: "Call the auto_continue tool with enabled=true",
|
|
34326
|
-
description: "Enable auto-continuation — orchestrator keeps working through incomplete todos"
|
|
34327
|
-
};
|
|
34328
|
-
}
|
|
34329
33580
|
interviewManager.registerCommand(opencodeConfig);
|
|
34330
33581
|
deepworkCommandHook.registerCommand(opencodeConfig);
|
|
34331
33582
|
presetManager.registerCommand(opencodeConfig);
|
|
@@ -34352,7 +33603,6 @@ var OhMyOpenCodeLite = async (ctx) => {
|
|
|
34352
33603
|
await multiplexerSessionManager.onSessionStatus(event);
|
|
34353
33604
|
await multiplexerSessionManager.onSessionDeleted(event);
|
|
34354
33605
|
await foregroundFallback.handleEvent(input.event);
|
|
34355
|
-
await todoContinuationHook.handleEvent(input);
|
|
34356
33606
|
await autoUpdateChecker.event(input);
|
|
34357
33607
|
await interviewManager.handleEvent(input);
|
|
34358
33608
|
await taskSessionManagerHook.event(input);
|
|
@@ -34410,7 +33660,6 @@ var OhMyOpenCodeLite = async (ctx) => {
|
|
|
34410
33660
|
}
|
|
34411
33661
|
},
|
|
34412
33662
|
"command.execute.before": async (input, output) => {
|
|
34413
|
-
await todoContinuationHook.handleCommandExecuteBefore(input, output);
|
|
34414
33663
|
await interviewManager.handleCommandExecuteBefore(input, output);
|
|
34415
33664
|
await presetManager.handleCommandExecuteBefore(input, output);
|
|
34416
33665
|
await deepworkCommandHook.handleCommandExecuteBefore(input, output);
|
|
@@ -34425,10 +33674,6 @@ var OhMyOpenCodeLite = async (ctx) => {
|
|
|
34425
33674
|
if (agent) {
|
|
34426
33675
|
sessionAgentMap.set(input.sessionID, agent);
|
|
34427
33676
|
}
|
|
34428
|
-
todoContinuationHook.handleChatMessage({
|
|
34429
|
-
sessionID: input.sessionID,
|
|
34430
|
-
agent
|
|
34431
|
-
});
|
|
34432
33677
|
},
|
|
34433
33678
|
"experimental.chat.system.transform": async (input, output) => {
|
|
34434
33679
|
const agentName = input.sessionID ? sessionAgentMap.get(input.sessionID) : undefined;
|
|
@@ -34463,9 +33708,6 @@ ${output.system[0]}` : "");
|
|
|
34463
33708
|
disabledAgents,
|
|
34464
33709
|
log
|
|
34465
33710
|
});
|
|
34466
|
-
await todoContinuationHook.handleMessagesTransform({
|
|
34467
|
-
messages: typedOutput.messages
|
|
34468
|
-
});
|
|
34469
33711
|
await taskSessionManagerHook["experimental.chat.messages.transform"](input, typedOutput);
|
|
34470
33712
|
await phaseReminderHook["experimental.chat.messages.transform"](input, typedOutput);
|
|
34471
33713
|
await filterAvailableSkillsHook["experimental.chat.messages.transform"](input, typedOutput);
|
|
@@ -34487,7 +33729,6 @@ ${output.system[0]}` : "");
|
|
|
34487
33729
|
};
|
|
34488
33730
|
await runPostToolHook("delegate-task-retry", () => delegateTaskRetryHook["tool.execute.after"](input, output));
|
|
34489
33731
|
await runPostToolHook("json-error-recovery", () => jsonErrorRecoveryHook["tool.execute.after"](input, output));
|
|
34490
|
-
await runPostToolHook("todo-continuation", () => todoContinuationHook.handleToolExecuteAfter(input, output));
|
|
34491
33732
|
await runPostToolHook("post-file-tool-nudge", () => postFileToolNudgeHook["tool.execute.after"](input, output));
|
|
34492
33733
|
await runPostToolHook("task-session-manager", () => taskSessionManagerHook["tool.execute.after"](input, output));
|
|
34493
33734
|
if (input.tool.toLowerCase() === "task") {
|