oh-my-opencode-slim 1.0.5 → 1.0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +25 -18
- package/dist/cli/divoom.d.ts +23 -0
- package/dist/cli/doctor.d.ts +38 -0
- package/dist/cli/index.js +306 -14
- package/dist/cli/providers.d.ts +3 -0
- package/dist/config/council-schema.d.ts +2 -2
- package/dist/config/index.d.ts +1 -1
- package/dist/config/loader.d.ts +46 -1
- package/dist/config/schema.d.ts +23 -1
- package/dist/divoom/council.gif +0 -0
- package/dist/divoom/designer.gif +0 -0
- package/dist/divoom/explorer.gif +0 -0
- package/dist/divoom/fixer.gif +0 -0
- package/dist/divoom/input.gif +0 -0
- package/dist/divoom/intro.gif +0 -0
- package/dist/divoom/librarian.gif +0 -0
- package/dist/divoom/manager.d.ts +57 -0
- package/dist/divoom/oracle.gif +0 -0
- package/dist/divoom/orchestrator.gif +0 -0
- package/dist/hooks/auto-update-checker/types.d.ts +0 -1
- package/dist/index.js +844 -270
- package/dist/integrations/divoom/index.d.ts +3 -0
- package/dist/integrations/divoom/status-manager.d.ts +31 -0
- package/dist/integrations/divoom/swift-helper-source.d.ts +1 -0
- package/dist/integrations/divoom/swift-transport.d.ts +26 -0
- package/dist/integrations/divoom/types.d.ts +41 -0
- package/dist/multiplexer/tmux/index.d.ts +5 -0
- package/dist/tools/council.d.ts +2 -2
- package/dist/tui-state.d.ts +15 -0
- package/dist/tui.d.ts +4 -0
- package/dist/tui.js +787 -9
- package/dist/utils/session.d.ts +10 -4
- package/oh-my-opencode-slim.schema.json +59 -4
- package/package.json +3 -2
package/dist/index.js
CHANGED
|
@@ -6246,33 +6246,33 @@ var require_URL = __commonJS((exports, module) => {
|
|
|
6246
6246
|
else
|
|
6247
6247
|
return basepath.substring(0, lastslash + 1) + refpath;
|
|
6248
6248
|
}
|
|
6249
|
-
function remove_dot_segments(
|
|
6250
|
-
if (!
|
|
6251
|
-
return
|
|
6249
|
+
function remove_dot_segments(path16) {
|
|
6250
|
+
if (!path16)
|
|
6251
|
+
return path16;
|
|
6252
6252
|
var output = "";
|
|
6253
|
-
while (
|
|
6254
|
-
if (
|
|
6255
|
-
|
|
6253
|
+
while (path16.length > 0) {
|
|
6254
|
+
if (path16 === "." || path16 === "..") {
|
|
6255
|
+
path16 = "";
|
|
6256
6256
|
break;
|
|
6257
6257
|
}
|
|
6258
|
-
var twochars =
|
|
6259
|
-
var threechars =
|
|
6260
|
-
var fourchars =
|
|
6258
|
+
var twochars = path16.substring(0, 2);
|
|
6259
|
+
var threechars = path16.substring(0, 3);
|
|
6260
|
+
var fourchars = path16.substring(0, 4);
|
|
6261
6261
|
if (threechars === "../") {
|
|
6262
|
-
|
|
6262
|
+
path16 = path16.substring(3);
|
|
6263
6263
|
} else if (twochars === "./") {
|
|
6264
|
-
|
|
6264
|
+
path16 = path16.substring(2);
|
|
6265
6265
|
} else if (threechars === "/./") {
|
|
6266
|
-
|
|
6267
|
-
} else if (twochars === "/." &&
|
|
6268
|
-
|
|
6269
|
-
} else if (fourchars === "/../" || threechars === "/.." &&
|
|
6270
|
-
|
|
6266
|
+
path16 = "/" + path16.substring(3);
|
|
6267
|
+
} else if (twochars === "/." && path16.length === 2) {
|
|
6268
|
+
path16 = "/";
|
|
6269
|
+
} else if (fourchars === "/../" || threechars === "/.." && path16.length === 3) {
|
|
6270
|
+
path16 = "/" + path16.substring(4);
|
|
6271
6271
|
output = output.replace(/\/?[^\/]*$/, "");
|
|
6272
6272
|
} else {
|
|
6273
|
-
var segment =
|
|
6273
|
+
var segment = path16.match(/(\/?([^\/]*))/)[0];
|
|
6274
6274
|
output += segment;
|
|
6275
|
-
|
|
6275
|
+
path16 = path16.substring(segment.length);
|
|
6276
6276
|
}
|
|
6277
6277
|
}
|
|
6278
6278
|
return output;
|
|
@@ -18150,14 +18150,14 @@ var require_turndown_cjs = __commonJS((exports, module) => {
|
|
|
18150
18150
|
} else if (node.nodeType === 1) {
|
|
18151
18151
|
replacement = replacementForNode.call(self, node);
|
|
18152
18152
|
}
|
|
18153
|
-
return
|
|
18153
|
+
return join13(output, replacement);
|
|
18154
18154
|
}, "");
|
|
18155
18155
|
}
|
|
18156
18156
|
function postProcess(output) {
|
|
18157
18157
|
var self = this;
|
|
18158
18158
|
this.rules.forEach(function(rule) {
|
|
18159
18159
|
if (typeof rule.append === "function") {
|
|
18160
|
-
output =
|
|
18160
|
+
output = join13(output, rule.append(self.options));
|
|
18161
18161
|
}
|
|
18162
18162
|
});
|
|
18163
18163
|
return output.replace(/^[\t\r\n]+/, "").replace(/[\t\r\n\s]+$/, "");
|
|
@@ -18170,7 +18170,7 @@ var require_turndown_cjs = __commonJS((exports, module) => {
|
|
|
18170
18170
|
content = content.trim();
|
|
18171
18171
|
return whitespace.leading + rule.replacement(content, node, this.options) + whitespace.trailing;
|
|
18172
18172
|
}
|
|
18173
|
-
function
|
|
18173
|
+
function join13(output, replacement) {
|
|
18174
18174
|
var s1 = trimTrailingNewlines(output);
|
|
18175
18175
|
var s2 = trimLeadingNewlines(replacement);
|
|
18176
18176
|
var nls = Math.max(output.length - s1.length, replacement.length - s2.length);
|
|
@@ -18530,6 +18530,17 @@ var SessionManagerConfigSchema = z2.object({
|
|
|
18530
18530
|
readContextMinLines: z2.number().int().min(0).max(1000).default(10),
|
|
18531
18531
|
readContextMaxFiles: z2.number().int().min(0).max(50).default(8)
|
|
18532
18532
|
});
|
|
18533
|
+
var DivoomConfigSchema = z2.object({
|
|
18534
|
+
enabled: z2.boolean().default(false),
|
|
18535
|
+
python: z2.string().min(1).default("/Applications/Divoom MiniToo.app/Contents/Resources/.venv/bin/python"),
|
|
18536
|
+
script: z2.string().min(1).default("/Applications/Divoom MiniToo.app/Contents/Resources/tools/divoom_send.py"),
|
|
18537
|
+
size: z2.number().int().min(1).max(1024).default(128),
|
|
18538
|
+
fps: z2.number().int().min(1).max(60).default(8),
|
|
18539
|
+
speed: z2.number().int().min(1).max(1e4).default(125),
|
|
18540
|
+
maxFrames: z2.number().int().min(1).max(500).default(24),
|
|
18541
|
+
posterizeBits: z2.number().int().min(1).max(8).default(3),
|
|
18542
|
+
gifs: z2.record(z2.string(), z2.string().min(1)).optional()
|
|
18543
|
+
});
|
|
18533
18544
|
var TodoContinuationConfigSchema = z2.object({
|
|
18534
18545
|
maxContinuations: z2.number().int().min(1).max(50).default(5).describe("Maximum consecutive auto-continuations before stopping to ask user"),
|
|
18535
18546
|
cooldownMs: z2.number().int().min(0).max(30000).default(3000).describe("Delay in ms before auto-continuing (gives user time to abort)"),
|
|
@@ -18570,7 +18581,6 @@ var PluginConfigSchema = z2.object({
|
|
|
18570
18581
|
setDefaultAgent: z2.boolean().optional(),
|
|
18571
18582
|
scoringEngineVersion: z2.enum(["v1", "v2-shadow", "v2"]).optional(),
|
|
18572
18583
|
balanceProviderUsage: z2.boolean().optional(),
|
|
18573
|
-
showStartupToast: z2.boolean().optional().describe("Show the startup activation toast when OpenCode starts. Defaults to true."),
|
|
18574
18584
|
autoUpdate: z2.boolean().optional().describe("Disable automatic installation of plugin updates when false. Defaults to true."),
|
|
18575
18585
|
manualPlan: ManualPlanSchema.optional(),
|
|
18576
18586
|
presets: z2.record(z2.string(), PresetSchema).optional(),
|
|
@@ -18582,6 +18592,7 @@ var PluginConfigSchema = z2.object({
|
|
|
18582
18592
|
websearch: WebsearchConfigSchema.optional(),
|
|
18583
18593
|
interview: InterviewConfigSchema.optional(),
|
|
18584
18594
|
sessionManager: SessionManagerConfigSchema.optional(),
|
|
18595
|
+
divoom: DivoomConfigSchema.optional(),
|
|
18585
18596
|
todoContinuation: TodoContinuationConfigSchema.optional(),
|
|
18586
18597
|
fallback: FailoverConfigSchema.optional(),
|
|
18587
18598
|
council: CouncilConfigSchema.optional()
|
|
@@ -18598,20 +18609,49 @@ var PluginConfigSchema = z2.object({
|
|
|
18598
18609
|
|
|
18599
18610
|
// src/config/loader.ts
|
|
18600
18611
|
var PROMPTS_DIR_NAME = "oh-my-opencode-slim";
|
|
18601
|
-
function loadConfigFromPath(configPath) {
|
|
18612
|
+
function loadConfigFromPath(configPath, options) {
|
|
18602
18613
|
try {
|
|
18603
18614
|
const content = fs.readFileSync(configPath, "utf-8");
|
|
18604
|
-
|
|
18615
|
+
let rawConfig;
|
|
18616
|
+
try {
|
|
18617
|
+
rawConfig = JSON.parse(stripJsonComments(content));
|
|
18618
|
+
} catch (error) {
|
|
18619
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
18620
|
+
options?.onWarning?.({
|
|
18621
|
+
path: configPath,
|
|
18622
|
+
kind: "invalid-json",
|
|
18623
|
+
message
|
|
18624
|
+
});
|
|
18625
|
+
if (!options?.silent) {
|
|
18626
|
+
console.warn(`[oh-my-opencode-slim] Invalid JSON in ${configPath}:`, message);
|
|
18627
|
+
}
|
|
18628
|
+
return null;
|
|
18629
|
+
}
|
|
18605
18630
|
const result = PluginConfigSchema.safeParse(rawConfig);
|
|
18606
18631
|
if (!result.success) {
|
|
18607
|
-
|
|
18608
|
-
|
|
18632
|
+
options?.onWarning?.({
|
|
18633
|
+
path: configPath,
|
|
18634
|
+
kind: "invalid-schema",
|
|
18635
|
+
message: "Config does not match schema",
|
|
18636
|
+
formatted: result.error.format()
|
|
18637
|
+
});
|
|
18638
|
+
if (!options?.silent) {
|
|
18639
|
+
console.warn(`[oh-my-opencode-slim] Invalid config at ${configPath}:`);
|
|
18640
|
+
console.warn(result.error.format());
|
|
18641
|
+
}
|
|
18609
18642
|
return null;
|
|
18610
18643
|
}
|
|
18611
18644
|
return result.data;
|
|
18612
18645
|
} catch (error) {
|
|
18613
18646
|
if (error instanceof Error && "code" in error && error.code !== "ENOENT") {
|
|
18614
|
-
|
|
18647
|
+
options?.onWarning?.({
|
|
18648
|
+
path: configPath,
|
|
18649
|
+
kind: "read-error",
|
|
18650
|
+
message: error.message
|
|
18651
|
+
});
|
|
18652
|
+
if (!options?.silent) {
|
|
18653
|
+
console.warn(`[oh-my-opencode-slim] Error reading config from ${configPath}:`, error.message);
|
|
18654
|
+
}
|
|
18615
18655
|
}
|
|
18616
18656
|
return null;
|
|
18617
18657
|
}
|
|
@@ -18636,6 +18676,26 @@ function findConfigPathInDirs(configDirs, baseName) {
|
|
|
18636
18676
|
}
|
|
18637
18677
|
return null;
|
|
18638
18678
|
}
|
|
18679
|
+
function findPluginConfigPaths(directory) {
|
|
18680
|
+
const userConfigPath = findConfigPathInDirs(getConfigSearchDirs(), "oh-my-opencode-slim");
|
|
18681
|
+
const projectConfigBasePath = path.join(directory, ".opencode", "oh-my-opencode-slim");
|
|
18682
|
+
const projectConfigPath = findConfigPath(projectConfigBasePath);
|
|
18683
|
+
return { userConfigPath, projectConfigPath };
|
|
18684
|
+
}
|
|
18685
|
+
function mergePluginConfigs(base, override) {
|
|
18686
|
+
return {
|
|
18687
|
+
...base,
|
|
18688
|
+
...override,
|
|
18689
|
+
agents: deepMerge(base.agents, override.agents),
|
|
18690
|
+
tmux: deepMerge(base.tmux, override.tmux),
|
|
18691
|
+
multiplexer: deepMerge(base.multiplexer, override.multiplexer),
|
|
18692
|
+
interview: deepMerge(base.interview, override.interview),
|
|
18693
|
+
sessionManager: deepMerge(base.sessionManager, override.sessionManager),
|
|
18694
|
+
divoom: deepMerge(base.divoom, override.divoom),
|
|
18695
|
+
fallback: deepMerge(base.fallback, override.fallback),
|
|
18696
|
+
council: deepMerge(base.council, override.council)
|
|
18697
|
+
};
|
|
18698
|
+
}
|
|
18639
18699
|
function deepMerge(base, override) {
|
|
18640
18700
|
if (!base)
|
|
18641
18701
|
return override;
|
|
@@ -18653,24 +18713,12 @@ function deepMerge(base, override) {
|
|
|
18653
18713
|
}
|
|
18654
18714
|
return result;
|
|
18655
18715
|
}
|
|
18656
|
-
function loadPluginConfig(directory) {
|
|
18657
|
-
const userConfigPath =
|
|
18658
|
-
|
|
18659
|
-
const
|
|
18660
|
-
let config = userConfigPath ? loadConfigFromPath(userConfigPath) ?? {} : {};
|
|
18661
|
-
const projectConfig = projectConfigPath ? loadConfigFromPath(projectConfigPath) : null;
|
|
18716
|
+
function loadPluginConfig(directory, options) {
|
|
18717
|
+
const { userConfigPath, projectConfigPath } = findPluginConfigPaths(directory);
|
|
18718
|
+
let config = userConfigPath ? loadConfigFromPath(userConfigPath, options) ?? {} : {};
|
|
18719
|
+
const projectConfig = projectConfigPath ? loadConfigFromPath(projectConfigPath, options) : null;
|
|
18662
18720
|
if (projectConfig) {
|
|
18663
|
-
config =
|
|
18664
|
-
...config,
|
|
18665
|
-
...projectConfig,
|
|
18666
|
-
agents: deepMerge(config.agents, projectConfig.agents),
|
|
18667
|
-
tmux: deepMerge(config.tmux, projectConfig.tmux),
|
|
18668
|
-
multiplexer: deepMerge(config.multiplexer, projectConfig.multiplexer),
|
|
18669
|
-
interview: deepMerge(config.interview, projectConfig.interview),
|
|
18670
|
-
sessionManager: deepMerge(config.sessionManager, projectConfig.sessionManager),
|
|
18671
|
-
fallback: deepMerge(config.fallback, projectConfig.fallback),
|
|
18672
|
-
council: deepMerge(config.council, projectConfig.council)
|
|
18673
|
-
};
|
|
18721
|
+
config = mergePluginConfigs(config, projectConfig);
|
|
18674
18722
|
}
|
|
18675
18723
|
config = migrateTmuxToMultiplexer(config);
|
|
18676
18724
|
const envPreset = process.env.OH_MY_OPENCODE_SLIM_PRESET;
|
|
@@ -18684,7 +18732,15 @@ function loadPluginConfig(directory) {
|
|
|
18684
18732
|
} else {
|
|
18685
18733
|
const presetSource = envPreset === config.preset ? "environment variable" : "config file";
|
|
18686
18734
|
const availablePresets = config.presets ? Object.keys(config.presets).join(", ") : "none";
|
|
18687
|
-
|
|
18735
|
+
const message = `Preset "${config.preset}" not found (from ${presetSource}). Available presets: ${availablePresets}`;
|
|
18736
|
+
options?.onWarning?.({
|
|
18737
|
+
path: projectConfigPath ?? userConfigPath ?? "",
|
|
18738
|
+
kind: "missing-preset",
|
|
18739
|
+
message
|
|
18740
|
+
});
|
|
18741
|
+
if (!options?.silent) {
|
|
18742
|
+
console.warn(`[oh-my-opencode-slim] ${message}`);
|
|
18743
|
+
}
|
|
18688
18744
|
}
|
|
18689
18745
|
}
|
|
18690
18746
|
return config;
|
|
@@ -18745,6 +18801,34 @@ function getCustomAgentNames(config) {
|
|
|
18745
18801
|
});
|
|
18746
18802
|
}
|
|
18747
18803
|
// src/utils/session.ts
|
|
18804
|
+
var SESSION_ABORT_TIMEOUT_MS = 1000;
|
|
18805
|
+
|
|
18806
|
+
class OperationTimeoutError extends Error {
|
|
18807
|
+
constructor(message) {
|
|
18808
|
+
super(message);
|
|
18809
|
+
this.name = "OperationTimeoutError";
|
|
18810
|
+
}
|
|
18811
|
+
}
|
|
18812
|
+
async function withTimeout(operation, timeoutMs, message) {
|
|
18813
|
+
if (timeoutMs <= 0)
|
|
18814
|
+
return operation;
|
|
18815
|
+
let timer;
|
|
18816
|
+
try {
|
|
18817
|
+
return await Promise.race([
|
|
18818
|
+
operation,
|
|
18819
|
+
new Promise((_, reject) => {
|
|
18820
|
+
timer = setTimeout(() => {
|
|
18821
|
+
reject(new OperationTimeoutError(message));
|
|
18822
|
+
}, timeoutMs);
|
|
18823
|
+
})
|
|
18824
|
+
]);
|
|
18825
|
+
} finally {
|
|
18826
|
+
clearTimeout(timer);
|
|
18827
|
+
}
|
|
18828
|
+
}
|
|
18829
|
+
async function abortSessionWithTimeout(client, sessionId, timeoutMs = SESSION_ABORT_TIMEOUT_MS) {
|
|
18830
|
+
await withTimeout(client.session.abort({ path: { id: sessionId } }), timeoutMs, `Session abort timed out after ${timeoutMs}ms`);
|
|
18831
|
+
}
|
|
18748
18832
|
function shortModelLabel(model) {
|
|
18749
18833
|
return model.split("/").pop() ?? model;
|
|
18750
18834
|
}
|
|
@@ -18772,11 +18856,17 @@ async function promptWithTimeout(client, args, timeoutMs) {
|
|
|
18772
18856
|
promptPromise,
|
|
18773
18857
|
new Promise((_, reject) => {
|
|
18774
18858
|
timer = setTimeout(() => {
|
|
18775
|
-
|
|
18776
|
-
reject(new Error(`Prompt timed out after ${timeoutMs}ms`));
|
|
18859
|
+
reject(new OperationTimeoutError(`Prompt timed out after ${timeoutMs}ms`));
|
|
18777
18860
|
}, timeoutMs);
|
|
18778
18861
|
})
|
|
18779
18862
|
]);
|
|
18863
|
+
} catch (error) {
|
|
18864
|
+
if (error instanceof OperationTimeoutError) {
|
|
18865
|
+
try {
|
|
18866
|
+
await abortSessionWithTimeout(client, sessionId);
|
|
18867
|
+
} catch {}
|
|
18868
|
+
}
|
|
18869
|
+
throw error;
|
|
18780
18870
|
} finally {
|
|
18781
18871
|
clearTimeout(timer);
|
|
18782
18872
|
}
|
|
@@ -18823,7 +18913,7 @@ var AGENT_DESCRIPTIONS = {
|
|
|
18823
18913
|
- **Don't delegate when:** Know the path and need actual content • Need full file anyway • Single specific lookup • About to edit the file`,
|
|
18824
18914
|
librarian: `@librarian
|
|
18825
18915
|
- Role: Authoritative source for current library docs and API references
|
|
18826
|
-
- Permissions:
|
|
18916
|
+
- Permissions: External docs/search MCPs; no file edits
|
|
18827
18917
|
- Stats: 10x better finding up-to-date library docs than orchestrator, 1/2 cost of orchestrator
|
|
18828
18918
|
- Capabilities: Fetches latest official docs, examples, API signatures, version-specific behavior via grep_app MCP
|
|
18829
18919
|
- **Delegate when:** Libraries with frequent API changes (React, Next.js, AI SDKs) • Complex APIs needing official examples (ORMs, auth) • Version-specific behavior matters • Unfamiliar library • Edge cases or advanced features • Nuanced best practices
|
|
@@ -19208,7 +19298,8 @@ function createCouncillorAgent(model, customPrompt, customAppendPrompt) {
|
|
|
19208
19298
|
grep: "allow",
|
|
19209
19299
|
lsp: "allow",
|
|
19210
19300
|
list: "allow",
|
|
19211
|
-
codesearch: "allow"
|
|
19301
|
+
codesearch: "allow",
|
|
19302
|
+
ast_grep_search: "allow"
|
|
19212
19303
|
}
|
|
19213
19304
|
}
|
|
19214
19305
|
};
|
|
@@ -19530,10 +19621,14 @@ ${customAppendPrompt}`;
|
|
|
19530
19621
|
|
|
19531
19622
|
// src/agents/index.ts
|
|
19532
19623
|
var COUNCIL_TOOL_ALLOWED_AGENTS = new Set(["council"]);
|
|
19624
|
+
var SAFE_AGENT_ALIAS_RE = /^[a-z][a-z0-9_-]*$/i;
|
|
19533
19625
|
function normalizeDisplayName(displayName) {
|
|
19534
19626
|
const trimmed = displayName.trim();
|
|
19535
19627
|
return trimmed.startsWith("@") ? trimmed.slice(1) : trimmed;
|
|
19536
19628
|
}
|
|
19629
|
+
function isSafeDisplayName(displayName) {
|
|
19630
|
+
return SAFE_AGENT_ALIAS_RE.test(displayName);
|
|
19631
|
+
}
|
|
19537
19632
|
function escapeRegExp(value) {
|
|
19538
19633
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
19539
19634
|
}
|
|
@@ -19567,7 +19662,7 @@ function normalizeCustomAgentName(name) {
|
|
|
19567
19662
|
return name.trim();
|
|
19568
19663
|
}
|
|
19569
19664
|
function isSafeCustomAgentName(name) {
|
|
19570
|
-
return
|
|
19665
|
+
return SAFE_AGENT_ALIAS_RE.test(name) && !isKnownAgentName(name);
|
|
19571
19666
|
}
|
|
19572
19667
|
function hasCustomAgentModel(override) {
|
|
19573
19668
|
if (!override?.model) {
|
|
@@ -19627,6 +19722,9 @@ var SUBAGENT_FACTORIES = {
|
|
|
19627
19722
|
};
|
|
19628
19723
|
function createAgents(config) {
|
|
19629
19724
|
const disabled = getDisabledAgents(config);
|
|
19725
|
+
if (!config?.council) {
|
|
19726
|
+
disabled.add("council");
|
|
19727
|
+
}
|
|
19630
19728
|
const getModelForAgent = (name) => {
|
|
19631
19729
|
if (name === "fixer" && !getAgentOverride(config, "fixer")?.model) {
|
|
19632
19730
|
const librarianOverride = getAgentOverride(config, "librarian")?.model;
|
|
@@ -19713,6 +19811,9 @@ function createAgents(config) {
|
|
|
19713
19811
|
const usedDisplayNames = new Set;
|
|
19714
19812
|
for (const [, displayName] of displayNameMap) {
|
|
19715
19813
|
const normalizedDisplayName = normalizeDisplayName(displayName);
|
|
19814
|
+
if (!isSafeDisplayName(normalizedDisplayName)) {
|
|
19815
|
+
throw new Error(`displayName '${normalizedDisplayName}' must match /^[a-z][a-z0-9_-]*$/i`);
|
|
19816
|
+
}
|
|
19716
19817
|
if (usedDisplayNames.has(normalizedDisplayName)) {
|
|
19717
19818
|
throw new Error(`Duplicate displayName '${normalizedDisplayName}' assigned to multiple agents`);
|
|
19718
19819
|
}
|
|
@@ -20139,6 +20240,286 @@ class CouncilManager {
|
|
|
20139
20240
|
};
|
|
20140
20241
|
}
|
|
20141
20242
|
}
|
|
20243
|
+
// src/divoom/manager.ts
|
|
20244
|
+
import { spawn } from "node:child_process";
|
|
20245
|
+
import { existsSync as existsSync2, mkdirSync as mkdirSync2 } from "node:fs";
|
|
20246
|
+
import * as os2 from "node:os";
|
|
20247
|
+
import path3 from "node:path";
|
|
20248
|
+
import { fileURLToPath } from "node:url";
|
|
20249
|
+
var AGENT_GIFS = {
|
|
20250
|
+
council: "council.gif",
|
|
20251
|
+
councillor: "council.gif",
|
|
20252
|
+
designer: "designer.gif",
|
|
20253
|
+
explorer: "explorer.gif",
|
|
20254
|
+
fixer: "fixer.gif",
|
|
20255
|
+
input: "input.gif",
|
|
20256
|
+
intro: "intro.gif",
|
|
20257
|
+
librarian: "librarian.gif",
|
|
20258
|
+
oracle: "oracle.gif",
|
|
20259
|
+
orchestrator: "orchestrator.gif"
|
|
20260
|
+
};
|
|
20261
|
+
var DEFAULT_DIVOOM_CONFIG = {
|
|
20262
|
+
enabled: false,
|
|
20263
|
+
python: "/Applications/Divoom MiniToo.app/Contents/Resources/.venv/bin/python",
|
|
20264
|
+
script: "/Applications/Divoom MiniToo.app/Contents/Resources/tools/divoom_send.py",
|
|
20265
|
+
size: 128,
|
|
20266
|
+
fps: 8,
|
|
20267
|
+
speed: 125,
|
|
20268
|
+
maxFrames: 24,
|
|
20269
|
+
posterizeBits: 3
|
|
20270
|
+
};
|
|
20271
|
+
var DIVOOM_ENABLE_ENV = "OH_MY_OPENCODE_SLIM_DIVOOM";
|
|
20272
|
+
function resolveAssetDir() {
|
|
20273
|
+
const moduleDir = path3.dirname(fileURLToPath(import.meta.url));
|
|
20274
|
+
const candidates = [
|
|
20275
|
+
moduleDir,
|
|
20276
|
+
path3.resolve(moduleDir, "divoom"),
|
|
20277
|
+
path3.resolve(moduleDir, "../../src/divoom"),
|
|
20278
|
+
path3.resolve(process.cwd(), "src/divoom")
|
|
20279
|
+
];
|
|
20280
|
+
for (const candidate of candidates) {
|
|
20281
|
+
if (existsSync2(path3.join(candidate, "intro.gif"))) {
|
|
20282
|
+
return candidate;
|
|
20283
|
+
}
|
|
20284
|
+
}
|
|
20285
|
+
return null;
|
|
20286
|
+
}
|
|
20287
|
+
function normalizeAgentName(value) {
|
|
20288
|
+
if (typeof value !== "string")
|
|
20289
|
+
return null;
|
|
20290
|
+
const normalized = value.trim().toLowerCase();
|
|
20291
|
+
return normalized.length > 0 ? normalized : null;
|
|
20292
|
+
}
|
|
20293
|
+
function isEnvEnabled(value) {
|
|
20294
|
+
if (!value)
|
|
20295
|
+
return false;
|
|
20296
|
+
return ["1", "true", "yes", "on"].includes(value.trim().toLowerCase());
|
|
20297
|
+
}
|
|
20298
|
+
function inputKey(sessionId, requestId) {
|
|
20299
|
+
return `${sessionId}:${requestId}`;
|
|
20300
|
+
}
|
|
20301
|
+
function getDivoomOutDir(homeDir = os2.homedir()) {
|
|
20302
|
+
const xdg = process.env.XDG_DATA_HOME?.trim();
|
|
20303
|
+
const baseDir = xdg && xdg.length > 0 && path3.isAbsolute(xdg) ? xdg : path3.join(homeDir, ".local", "share");
|
|
20304
|
+
return path3.join(baseDir, "opencode", "storage", "oh-my-opencode-slim", "divoom", "captures");
|
|
20305
|
+
}
|
|
20306
|
+
|
|
20307
|
+
class DivoomManager {
|
|
20308
|
+
sender;
|
|
20309
|
+
assetDir;
|
|
20310
|
+
config;
|
|
20311
|
+
parentStates = new Map;
|
|
20312
|
+
pendingUserInputs = new Set;
|
|
20313
|
+
orchestratorBusy = false;
|
|
20314
|
+
latestRequestedGifPath;
|
|
20315
|
+
lastGifPath;
|
|
20316
|
+
sendQueue = Promise.resolve();
|
|
20317
|
+
constructor(config, sender = defaultSender, options = {}) {
|
|
20318
|
+
this.sender = sender;
|
|
20319
|
+
this.config = {
|
|
20320
|
+
...DEFAULT_DIVOOM_CONFIG,
|
|
20321
|
+
...config,
|
|
20322
|
+
enabled: isEnvEnabled(process.env[DIVOOM_ENABLE_ENV]) ? true : config?.enabled ?? false,
|
|
20323
|
+
gifs: config?.gifs
|
|
20324
|
+
};
|
|
20325
|
+
this.assetDir = options.assetDir ?? resolveAssetDir();
|
|
20326
|
+
if (options.sender) {
|
|
20327
|
+
this.sender = options.sender;
|
|
20328
|
+
}
|
|
20329
|
+
}
|
|
20330
|
+
onPluginLoad() {
|
|
20331
|
+
this.show("intro");
|
|
20332
|
+
}
|
|
20333
|
+
onTaskStart(input) {
|
|
20334
|
+
if (!input.parentSessionId || !input.callId)
|
|
20335
|
+
return;
|
|
20336
|
+
if (!isTaskArgs(input.args))
|
|
20337
|
+
return;
|
|
20338
|
+
const agent = normalizeAgentName(input.args.subagent_type);
|
|
20339
|
+
if (!agent)
|
|
20340
|
+
return;
|
|
20341
|
+
const state = this.getParentState(input.parentSessionId);
|
|
20342
|
+
const wasIdle = state.activeCalls.size === 0;
|
|
20343
|
+
state.activeCalls.set(input.callId, agent);
|
|
20344
|
+
this.orchestratorBusy = true;
|
|
20345
|
+
if (wasIdle && !state.displayedAgent) {
|
|
20346
|
+
state.displayedAgent = agent;
|
|
20347
|
+
}
|
|
20348
|
+
this.render();
|
|
20349
|
+
}
|
|
20350
|
+
onTaskEnd(input) {
|
|
20351
|
+
if (!input.parentSessionId || !input.callId)
|
|
20352
|
+
return;
|
|
20353
|
+
const state = this.parentStates.get(input.parentSessionId);
|
|
20354
|
+
if (!state)
|
|
20355
|
+
return;
|
|
20356
|
+
state.activeCalls.delete(input.callId);
|
|
20357
|
+
if (state.activeCalls.size === 0) {
|
|
20358
|
+
this.parentStates.delete(input.parentSessionId);
|
|
20359
|
+
}
|
|
20360
|
+
this.render();
|
|
20361
|
+
}
|
|
20362
|
+
onUserInputRequired(input) {
|
|
20363
|
+
if (!input.sessionId || !input.requestId)
|
|
20364
|
+
return;
|
|
20365
|
+
this.pendingUserInputs.add(inputKey(input.sessionId, input.requestId));
|
|
20366
|
+
this.render();
|
|
20367
|
+
}
|
|
20368
|
+
onUserInputResolved(input) {
|
|
20369
|
+
if (!input.sessionId || !input.requestId)
|
|
20370
|
+
return;
|
|
20371
|
+
this.pendingUserInputs.delete(inputKey(input.sessionId, input.requestId));
|
|
20372
|
+
this.render();
|
|
20373
|
+
}
|
|
20374
|
+
onOrchestratorStatus(input) {
|
|
20375
|
+
if (!input.sessionId || !input.isOrchestrator)
|
|
20376
|
+
return;
|
|
20377
|
+
if (input.status === "busy") {
|
|
20378
|
+
this.orchestratorBusy = true;
|
|
20379
|
+
this.render();
|
|
20380
|
+
return;
|
|
20381
|
+
}
|
|
20382
|
+
if (input.status === "idle") {
|
|
20383
|
+
this.orchestratorBusy = false;
|
|
20384
|
+
this.parentStates.delete(input.sessionId);
|
|
20385
|
+
this.render();
|
|
20386
|
+
}
|
|
20387
|
+
}
|
|
20388
|
+
onSessionDeleted(input) {
|
|
20389
|
+
const sessionId = input.sessionId;
|
|
20390
|
+
if (!sessionId)
|
|
20391
|
+
return;
|
|
20392
|
+
if (input.isOrchestrator)
|
|
20393
|
+
this.orchestratorBusy = false;
|
|
20394
|
+
this.parentStates.delete(sessionId);
|
|
20395
|
+
this.pendingUserInputs = new Set(Array.from(this.pendingUserInputs).filter((key) => !key.startsWith(`${sessionId}:`)));
|
|
20396
|
+
this.render();
|
|
20397
|
+
}
|
|
20398
|
+
render() {
|
|
20399
|
+
if (this.pendingUserInputs.size > 0) {
|
|
20400
|
+
this.show("input");
|
|
20401
|
+
return;
|
|
20402
|
+
}
|
|
20403
|
+
const activeAgent = Array.from(this.parentStates.values()).find((state) => state.displayedAgent && state.activeCalls.size > 0)?.displayedAgent;
|
|
20404
|
+
if (activeAgent) {
|
|
20405
|
+
this.show(activeAgent);
|
|
20406
|
+
return;
|
|
20407
|
+
}
|
|
20408
|
+
this.show(this.orchestratorBusy ? "orchestrator" : "intro");
|
|
20409
|
+
}
|
|
20410
|
+
getParentState(parentSessionId) {
|
|
20411
|
+
const existing = this.parentStates.get(parentSessionId);
|
|
20412
|
+
if (existing)
|
|
20413
|
+
return existing;
|
|
20414
|
+
const created = {
|
|
20415
|
+
activeCalls: new Map
|
|
20416
|
+
};
|
|
20417
|
+
this.parentStates.set(parentSessionId, created);
|
|
20418
|
+
return created;
|
|
20419
|
+
}
|
|
20420
|
+
show(agent) {
|
|
20421
|
+
if (!this.config.enabled)
|
|
20422
|
+
return;
|
|
20423
|
+
if (!this.assetDir) {
|
|
20424
|
+
log("[divoom] asset directory not found");
|
|
20425
|
+
return;
|
|
20426
|
+
}
|
|
20427
|
+
const fileName = this.config.gifs?.[agent] ?? AGENT_GIFS[agent] ?? AGENT_GIFS.orchestrator;
|
|
20428
|
+
const requestedGifPath = path3.isAbsolute(fileName) ? fileName : path3.join(this.assetDir, fileName);
|
|
20429
|
+
const fallbackGifPath = path3.join(this.assetDir, AGENT_GIFS.intro);
|
|
20430
|
+
const gifPath = existsSync2(requestedGifPath) ? requestedGifPath : agent === "input" ? fallbackGifPath : requestedGifPath;
|
|
20431
|
+
if (!existsSync2(gifPath)) {
|
|
20432
|
+
log("[divoom] gif not found", { agent, gifPath: requestedGifPath });
|
|
20433
|
+
return;
|
|
20434
|
+
}
|
|
20435
|
+
if (gifPath === this.latestRequestedGifPath)
|
|
20436
|
+
return;
|
|
20437
|
+
this.latestRequestedGifPath = gifPath;
|
|
20438
|
+
const outDir = getDivoomOutDir();
|
|
20439
|
+
try {
|
|
20440
|
+
mkdirSync2(outDir, { recursive: true });
|
|
20441
|
+
} catch (error) {
|
|
20442
|
+
this.clearLatestIfCurrent(gifPath);
|
|
20443
|
+
log("[divoom] output directory not writable", {
|
|
20444
|
+
outDir,
|
|
20445
|
+
error: String(error)
|
|
20446
|
+
});
|
|
20447
|
+
return;
|
|
20448
|
+
}
|
|
20449
|
+
const call = {
|
|
20450
|
+
command: this.config.python,
|
|
20451
|
+
args: [
|
|
20452
|
+
this.config.script,
|
|
20453
|
+
gifPath,
|
|
20454
|
+
"--size",
|
|
20455
|
+
String(this.config.size),
|
|
20456
|
+
"--fps",
|
|
20457
|
+
String(this.config.fps),
|
|
20458
|
+
"--speed",
|
|
20459
|
+
String(this.config.speed),
|
|
20460
|
+
"--max-frames",
|
|
20461
|
+
String(this.config.maxFrames),
|
|
20462
|
+
"--posterize-bits",
|
|
20463
|
+
String(this.config.posterizeBits),
|
|
20464
|
+
"--out-dir",
|
|
20465
|
+
outDir
|
|
20466
|
+
]
|
|
20467
|
+
};
|
|
20468
|
+
this.sendQueue = this.sendQueue.catch(() => {}).then(async () => {
|
|
20469
|
+
if (gifPath !== this.latestRequestedGifPath)
|
|
20470
|
+
return;
|
|
20471
|
+
if (!existsSync2(this.config.python)) {
|
|
20472
|
+
this.clearLatestIfCurrent(gifPath);
|
|
20473
|
+
log("[divoom] python executable not found", this.config.python);
|
|
20474
|
+
return;
|
|
20475
|
+
}
|
|
20476
|
+
if (!existsSync2(this.config.script)) {
|
|
20477
|
+
this.clearLatestIfCurrent(gifPath);
|
|
20478
|
+
log("[divoom] sender script not found", this.config.script);
|
|
20479
|
+
return;
|
|
20480
|
+
}
|
|
20481
|
+
try {
|
|
20482
|
+
await this.sender(call);
|
|
20483
|
+
this.lastGifPath = gifPath;
|
|
20484
|
+
log("[divoom] showing gif", { agent, gifPath });
|
|
20485
|
+
} catch (error) {
|
|
20486
|
+
this.clearLatestIfCurrent(gifPath);
|
|
20487
|
+
log("[divoom] failed to send gif", String(error));
|
|
20488
|
+
}
|
|
20489
|
+
});
|
|
20490
|
+
}
|
|
20491
|
+
async flush() {
|
|
20492
|
+
await this.sendQueue.catch(() => {});
|
|
20493
|
+
}
|
|
20494
|
+
clearLatestIfCurrent(gifPath) {
|
|
20495
|
+
if (this.latestRequestedGifPath === gifPath) {
|
|
20496
|
+
this.latestRequestedGifPath = undefined;
|
|
20497
|
+
}
|
|
20498
|
+
if (this.lastGifPath === gifPath) {
|
|
20499
|
+
this.lastGifPath = undefined;
|
|
20500
|
+
}
|
|
20501
|
+
}
|
|
20502
|
+
}
|
|
20503
|
+
function isTaskArgs(value) {
|
|
20504
|
+
return typeof value === "object" && value !== null;
|
|
20505
|
+
}
|
|
20506
|
+
function defaultSender(call) {
|
|
20507
|
+
return new Promise((resolve, reject) => {
|
|
20508
|
+
const child = spawn(call.command, call.args, {
|
|
20509
|
+
detached: true,
|
|
20510
|
+
stdio: "ignore"
|
|
20511
|
+
});
|
|
20512
|
+
child.once("error", reject);
|
|
20513
|
+
child.once("spawn", () => {
|
|
20514
|
+
child.unref();
|
|
20515
|
+
resolve();
|
|
20516
|
+
});
|
|
20517
|
+
});
|
|
20518
|
+
}
|
|
20519
|
+
function createDivoomManager(config) {
|
|
20520
|
+
return new DivoomManager(config);
|
|
20521
|
+
}
|
|
20522
|
+
|
|
20142
20523
|
// src/hooks/apply-patch/errors.ts
|
|
20143
20524
|
var APPLY_PATCH_ERROR_PREFIX = {
|
|
20144
20525
|
blocked: "apply_patch blocked",
|
|
@@ -20207,7 +20588,7 @@ function ensureApplyPatchError(error, context) {
|
|
|
20207
20588
|
|
|
20208
20589
|
// src/hooks/apply-patch/execution-context.ts
|
|
20209
20590
|
import * as fs3 from "node:fs/promises";
|
|
20210
|
-
import
|
|
20591
|
+
import path4 from "node:path";
|
|
20211
20592
|
|
|
20212
20593
|
// src/hooks/apply-patch/codec.ts
|
|
20213
20594
|
function normalizeLineEndings(text) {
|
|
@@ -21076,7 +21457,7 @@ function isMissingPathError(error) {
|
|
|
21076
21457
|
}
|
|
21077
21458
|
async function real(target) {
|
|
21078
21459
|
const parts = [];
|
|
21079
|
-
let current =
|
|
21460
|
+
let current = path4.resolve(target);
|
|
21080
21461
|
while (true) {
|
|
21081
21462
|
const exact = await fs3.realpath(current).catch((error) => {
|
|
21082
21463
|
if (isMissingPathError(error)) {
|
|
@@ -21085,19 +21466,19 @@ async function real(target) {
|
|
|
21085
21466
|
throw createApplyPatchInternalError(`Failed to resolve real path: ${current}`, error);
|
|
21086
21467
|
});
|
|
21087
21468
|
if (exact) {
|
|
21088
|
-
return parts.length === 0 ? exact :
|
|
21469
|
+
return parts.length === 0 ? exact : path4.join(exact, ...parts.reverse());
|
|
21089
21470
|
}
|
|
21090
|
-
const parent =
|
|
21471
|
+
const parent = path4.dirname(current);
|
|
21091
21472
|
if (parent === current) {
|
|
21092
|
-
return parts.length === 0 ? current :
|
|
21473
|
+
return parts.length === 0 ? current : path4.join(current, ...parts.reverse());
|
|
21093
21474
|
}
|
|
21094
|
-
parts.push(
|
|
21475
|
+
parts.push(path4.basename(current));
|
|
21095
21476
|
current = parent;
|
|
21096
21477
|
}
|
|
21097
21478
|
}
|
|
21098
21479
|
function inside(root, target) {
|
|
21099
|
-
const rel =
|
|
21100
|
-
return rel === "" || !rel.startsWith("..") && !
|
|
21480
|
+
const rel = path4.relative(root, target);
|
|
21481
|
+
return rel === "" || !rel.startsWith("..") && !path4.isAbsolute(rel);
|
|
21101
21482
|
}
|
|
21102
21483
|
function createPathGuardContext(root, worktree) {
|
|
21103
21484
|
return {
|
|
@@ -21107,7 +21488,7 @@ function createPathGuardContext(root, worktree) {
|
|
|
21107
21488
|
};
|
|
21108
21489
|
}
|
|
21109
21490
|
async function realCached(ctx, target) {
|
|
21110
|
-
const resolvedTarget =
|
|
21491
|
+
const resolvedTarget = path4.resolve(target);
|
|
21111
21492
|
let pending = ctx.realCache.get(resolvedTarget);
|
|
21112
21493
|
if (!pending) {
|
|
21113
21494
|
pending = real(resolvedTarget);
|
|
@@ -21158,22 +21539,22 @@ async function assertRegularFile(ctx, filePath, verb) {
|
|
|
21158
21539
|
function collectPatchTargets(root, hunks) {
|
|
21159
21540
|
const targets = new Set;
|
|
21160
21541
|
for (const hunk of hunks) {
|
|
21161
|
-
targets.add(
|
|
21542
|
+
targets.add(path4.resolve(root, hunk.path));
|
|
21162
21543
|
if (hunk.type === "update" && hunk.move_path) {
|
|
21163
|
-
targets.add(
|
|
21544
|
+
targets.add(path4.resolve(root, hunk.move_path));
|
|
21164
21545
|
}
|
|
21165
21546
|
}
|
|
21166
21547
|
return [...targets];
|
|
21167
21548
|
}
|
|
21168
21549
|
function toRelativePatchPath(root, target) {
|
|
21169
|
-
const relative =
|
|
21550
|
+
const relative = path4.relative(root, target);
|
|
21170
21551
|
return (relative.length === 0 ? "." : relative).replaceAll("\\", "/");
|
|
21171
21552
|
}
|
|
21172
21553
|
function normalizePatchPath(root, value) {
|
|
21173
|
-
return
|
|
21554
|
+
return path4.isAbsolute(value) ? toRelativePatchPath(root, path4.resolve(value)) : value;
|
|
21174
21555
|
}
|
|
21175
21556
|
function normalizePatchPaths(root, hunks) {
|
|
21176
|
-
const resolvedRoot =
|
|
21557
|
+
const resolvedRoot = path4.resolve(root);
|
|
21177
21558
|
const normalized = [];
|
|
21178
21559
|
let changed = false;
|
|
21179
21560
|
for (const hunk of hunks) {
|
|
@@ -21297,7 +21678,7 @@ function stageAddedText(contents) {
|
|
|
21297
21678
|
`;
|
|
21298
21679
|
}
|
|
21299
21680
|
// src/hooks/apply-patch/rewrite.ts
|
|
21300
|
-
import
|
|
21681
|
+
import path5 from "node:path";
|
|
21301
21682
|
function normalizeTextLineEndings(text) {
|
|
21302
21683
|
return text.replace(/\r\n/g, `
|
|
21303
21684
|
`).replace(/\r/g, `
|
|
@@ -21454,7 +21835,7 @@ async function rewritePatch(root, patchText, cfg, worktree) {
|
|
|
21454
21835
|
const dependencyGroups = new Map;
|
|
21455
21836
|
for (const hunk of hunks) {
|
|
21456
21837
|
if (hunk.type === "add") {
|
|
21457
|
-
const filePath2 =
|
|
21838
|
+
const filePath2 = path5.resolve(root, hunk.path);
|
|
21458
21839
|
await assertPreparedPathMissing(filePath2, "add");
|
|
21459
21840
|
rewritten.push(hunk);
|
|
21460
21841
|
clearDependencyGroup(filePath2);
|
|
@@ -21476,20 +21857,20 @@ async function rewritePatch(root, patchText, cfg, worktree) {
|
|
|
21476
21857
|
continue;
|
|
21477
21858
|
}
|
|
21478
21859
|
if (hunk.type === "delete") {
|
|
21479
|
-
const filePath2 =
|
|
21860
|
+
const filePath2 = path5.resolve(root, hunk.path);
|
|
21480
21861
|
await getPreparedFileState(filePath2, "delete");
|
|
21481
21862
|
clearDependencyGroup(filePath2);
|
|
21482
21863
|
rewritten.push(hunk);
|
|
21483
21864
|
staged.set(filePath2, { exists: false, derived: true });
|
|
21484
21865
|
continue;
|
|
21485
21866
|
}
|
|
21486
|
-
const filePath =
|
|
21867
|
+
const filePath = path5.resolve(root, hunk.path);
|
|
21487
21868
|
const currentDependency = dependencyGroups.get(filePath);
|
|
21488
21869
|
const current = await getPreparedFileState(filePath, "update");
|
|
21489
21870
|
if (!current.exists) {
|
|
21490
21871
|
throw createApplyPatchVerificationError(`Failed to read file to update: ${filePath}`);
|
|
21491
21872
|
}
|
|
21492
|
-
const movePath = hunk.move_path ?
|
|
21873
|
+
const movePath = hunk.move_path ? path5.resolve(root, hunk.move_path) : undefined;
|
|
21493
21874
|
if (movePath && movePath !== filePath) {
|
|
21494
21875
|
await assertPreparedPathMissing(movePath, "move");
|
|
21495
21876
|
}
|
|
@@ -21683,32 +22064,32 @@ function crossSpawn(command, options) {
|
|
|
21683
22064
|
}
|
|
21684
22065
|
};
|
|
21685
22066
|
}
|
|
21686
|
-
async function crossWrite(
|
|
21687
|
-
await fsWriteFile(
|
|
22067
|
+
async function crossWrite(path6, data) {
|
|
22068
|
+
await fsWriteFile(path6, Buffer.from(data));
|
|
21688
22069
|
}
|
|
21689
22070
|
|
|
21690
22071
|
// src/hooks/auto-update-checker/cache.ts
|
|
21691
22072
|
import * as fs5 from "node:fs";
|
|
21692
|
-
import * as
|
|
22073
|
+
import * as path8 from "node:path";
|
|
21693
22074
|
// src/hooks/auto-update-checker/checker.ts
|
|
21694
22075
|
import * as fs4 from "node:fs";
|
|
21695
|
-
import * as
|
|
21696
|
-
import { fileURLToPath } from "node:url";
|
|
22076
|
+
import * as path7 from "node:path";
|
|
22077
|
+
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
21697
22078
|
|
|
21698
22079
|
// src/hooks/auto-update-checker/constants.ts
|
|
21699
|
-
import * as
|
|
21700
|
-
import * as
|
|
22080
|
+
import * as os3 from "node:os";
|
|
22081
|
+
import * as path6 from "node:path";
|
|
21701
22082
|
var PACKAGE_NAME = "oh-my-opencode-slim";
|
|
21702
22083
|
var NPM_REGISTRY_URL = `https://registry.npmjs.org/-/package/${PACKAGE_NAME}/dist-tags`;
|
|
21703
22084
|
var NPM_FETCH_TIMEOUT = 5000;
|
|
21704
22085
|
function getCacheDir() {
|
|
21705
22086
|
if (process.platform === "win32") {
|
|
21706
|
-
return
|
|
22087
|
+
return path6.join(process.env.LOCALAPPDATA ?? os3.homedir(), "opencode");
|
|
21707
22088
|
}
|
|
21708
|
-
return
|
|
22089
|
+
return path6.join(os3.homedir(), ".cache", "opencode");
|
|
21709
22090
|
}
|
|
21710
22091
|
var CACHE_DIR = getCacheDir();
|
|
21711
|
-
var INSTALLED_PACKAGE_JSON =
|
|
22092
|
+
var INSTALLED_PACKAGE_JSON = path6.join(CACHE_DIR, "node_modules", PACKAGE_NAME, "package.json");
|
|
21712
22093
|
var configPaths = getOpenCodeConfigPaths();
|
|
21713
22094
|
var USER_OPENCODE_CONFIG = configPaths[0];
|
|
21714
22095
|
var USER_OPENCODE_CONFIG_JSONC = configPaths[1];
|
|
@@ -21743,8 +22124,8 @@ function extractChannel(version) {
|
|
|
21743
22124
|
}
|
|
21744
22125
|
function getConfigPaths(directory) {
|
|
21745
22126
|
return [
|
|
21746
|
-
|
|
21747
|
-
|
|
22127
|
+
path7.join(directory, ".opencode", "opencode.json"),
|
|
22128
|
+
path7.join(directory, ".opencode", "opencode.jsonc"),
|
|
21748
22129
|
USER_OPENCODE_CONFIG,
|
|
21749
22130
|
USER_OPENCODE_CONFIG_JSONC
|
|
21750
22131
|
];
|
|
@@ -21760,7 +22141,7 @@ function getLocalDevPath(directory) {
|
|
|
21760
22141
|
for (const entry of plugins) {
|
|
21761
22142
|
if (entry.startsWith("file://") && entry.includes(PACKAGE_NAME)) {
|
|
21762
22143
|
try {
|
|
21763
|
-
return
|
|
22144
|
+
return fileURLToPath2(entry);
|
|
21764
22145
|
} catch {
|
|
21765
22146
|
return entry.replace("file://", "");
|
|
21766
22147
|
}
|
|
@@ -21773,9 +22154,9 @@ function getLocalDevPath(directory) {
|
|
|
21773
22154
|
function findPackageJsonUp(startPath) {
|
|
21774
22155
|
try {
|
|
21775
22156
|
const stat2 = fs4.statSync(startPath);
|
|
21776
|
-
let dir = stat2.isDirectory() ? startPath :
|
|
22157
|
+
let dir = stat2.isDirectory() ? startPath : path7.dirname(startPath);
|
|
21777
22158
|
for (let i = 0;i < 10; i++) {
|
|
21778
|
-
const pkgPath =
|
|
22159
|
+
const pkgPath = path7.join(dir, "package.json");
|
|
21779
22160
|
if (fs4.existsSync(pkgPath)) {
|
|
21780
22161
|
try {
|
|
21781
22162
|
const content = fs4.readFileSync(pkgPath, "utf-8");
|
|
@@ -21784,7 +22165,7 @@ function findPackageJsonUp(startPath) {
|
|
|
21784
22165
|
return pkgPath;
|
|
21785
22166
|
} catch {}
|
|
21786
22167
|
}
|
|
21787
|
-
const parent =
|
|
22168
|
+
const parent = path7.dirname(dir);
|
|
21788
22169
|
if (parent === dir)
|
|
21789
22170
|
break;
|
|
21790
22171
|
dir = parent;
|
|
@@ -21809,7 +22190,7 @@ function getLocalDevVersion(directory) {
|
|
|
21809
22190
|
}
|
|
21810
22191
|
function getCurrentRuntimePackageJsonPath(currentModuleUrl = import.meta.url) {
|
|
21811
22192
|
try {
|
|
21812
|
-
const currentDir =
|
|
22193
|
+
const currentDir = path7.dirname(fileURLToPath2(currentModuleUrl));
|
|
21813
22194
|
return findPackageJsonUp(currentDir);
|
|
21814
22195
|
} catch (err) {
|
|
21815
22196
|
log("[auto-update-checker] Failed to resolve runtime package path:", err);
|
|
@@ -21893,7 +22274,7 @@ async function getLatestVersion(channel = "latest") {
|
|
|
21893
22274
|
|
|
21894
22275
|
// src/hooks/auto-update-checker/cache.ts
|
|
21895
22276
|
function removeFromBunLock(installDir, packageName) {
|
|
21896
|
-
const lockPath =
|
|
22277
|
+
const lockPath = path8.join(installDir, "bun.lock");
|
|
21897
22278
|
if (!fs5.existsSync(lockPath))
|
|
21898
22279
|
return false;
|
|
21899
22280
|
try {
|
|
@@ -21944,7 +22325,7 @@ function ensureDependencyVersion(packageJsonPath, packageName, version) {
|
|
|
21944
22325
|
}
|
|
21945
22326
|
}
|
|
21946
22327
|
function removeInstalledPackage(installDir, packageName) {
|
|
21947
|
-
const pkgDir =
|
|
22328
|
+
const pkgDir = path8.join(installDir, "node_modules", packageName);
|
|
21948
22329
|
if (!fs5.existsSync(pkgDir))
|
|
21949
22330
|
return false;
|
|
21950
22331
|
fs5.rmSync(pkgDir, { recursive: true, force: true });
|
|
@@ -21953,18 +22334,18 @@ function removeInstalledPackage(installDir, packageName) {
|
|
|
21953
22334
|
}
|
|
21954
22335
|
function resolveInstallContext(runtimePackageJsonPath = getCurrentRuntimePackageJsonPath()) {
|
|
21955
22336
|
if (runtimePackageJsonPath) {
|
|
21956
|
-
const packageDir =
|
|
21957
|
-
const nodeModulesDir =
|
|
21958
|
-
if (
|
|
21959
|
-
const installDir =
|
|
21960
|
-
const packageJsonPath =
|
|
22337
|
+
const packageDir = path8.dirname(runtimePackageJsonPath);
|
|
22338
|
+
const nodeModulesDir = path8.dirname(packageDir);
|
|
22339
|
+
if (path8.basename(packageDir) === PACKAGE_NAME && path8.basename(nodeModulesDir) === "node_modules") {
|
|
22340
|
+
const installDir = path8.dirname(nodeModulesDir);
|
|
22341
|
+
const packageJsonPath = path8.join(installDir, "package.json");
|
|
21961
22342
|
if (fs5.existsSync(packageJsonPath)) {
|
|
21962
22343
|
return { installDir, packageJsonPath };
|
|
21963
22344
|
}
|
|
21964
22345
|
}
|
|
21965
22346
|
return null;
|
|
21966
22347
|
}
|
|
21967
|
-
const legacyPackageJsonPath =
|
|
22348
|
+
const legacyPackageJsonPath = path8.join(CACHE_DIR, "package.json");
|
|
21968
22349
|
if (fs5.existsSync(legacyPackageJsonPath)) {
|
|
21969
22350
|
return { installDir: CACHE_DIR, packageJsonPath: legacyPackageJsonPath };
|
|
21970
22351
|
}
|
|
@@ -21995,7 +22376,7 @@ function preparePackageUpdate(version, packageName = PACKAGE_NAME, runtimePackag
|
|
|
21995
22376
|
|
|
21996
22377
|
// src/hooks/auto-update-checker/index.ts
|
|
21997
22378
|
function createAutoUpdateCheckerHook(ctx, options = {}) {
|
|
21998
|
-
const {
|
|
22379
|
+
const { autoUpdate = true } = options;
|
|
21999
22380
|
let hasChecked = false;
|
|
22000
22381
|
return {
|
|
22001
22382
|
event: ({ event }) => {
|
|
@@ -22008,19 +22389,11 @@ function createAutoUpdateCheckerHook(ctx, options = {}) {
|
|
|
22008
22389
|
return;
|
|
22009
22390
|
hasChecked = true;
|
|
22010
22391
|
setTimeout(async () => {
|
|
22011
|
-
const cachedVersion = getCachedVersion();
|
|
22012
22392
|
const localDevVersion = getLocalDevVersion(ctx.directory);
|
|
22013
|
-
const displayVersion = localDevVersion ?? cachedVersion;
|
|
22014
22393
|
if (localDevVersion) {
|
|
22015
|
-
if (showStartupToast) {
|
|
22016
|
-
showToast(ctx, `OMO-Slim ${displayVersion} (dev)`, "Running in local development mode.", "info");
|
|
22017
|
-
}
|
|
22018
22394
|
log("[auto-update-checker] Local development mode");
|
|
22019
22395
|
return;
|
|
22020
22396
|
}
|
|
22021
|
-
if (showStartupToast) {
|
|
22022
|
-
showToast(ctx, `OMO-Slim ${displayVersion ?? "unknown"}`, "oh-my-opencode-slim is active.", "info");
|
|
22023
|
-
}
|
|
22024
22397
|
runBackgroundUpdateCheck(ctx, autoUpdate).catch((err) => {
|
|
22025
22398
|
log("[auto-update-checker] Background update check failed:", err);
|
|
22026
22399
|
});
|
|
@@ -22106,7 +22479,7 @@ function showToast(ctx, title, message, variant = "info", duration = 3000) {
|
|
|
22106
22479
|
}).catch(() => {});
|
|
22107
22480
|
}
|
|
22108
22481
|
// src/utils/agent-variant.ts
|
|
22109
|
-
function
|
|
22482
|
+
function normalizeAgentName2(agentName) {
|
|
22110
22483
|
const trimmed = agentName.trim();
|
|
22111
22484
|
return trimmed.startsWith("@") ? trimmed.slice(1) : trimmed;
|
|
22112
22485
|
}
|
|
@@ -22118,7 +22491,7 @@ function getRuntimeAgentNames(config) {
|
|
|
22118
22491
|
return [...unique];
|
|
22119
22492
|
}
|
|
22120
22493
|
function resolveRuntimeAgentName(config, agentName) {
|
|
22121
|
-
const normalized =
|
|
22494
|
+
const normalized = normalizeAgentName2(agentName);
|
|
22122
22495
|
if (!normalized) {
|
|
22123
22496
|
return normalized;
|
|
22124
22497
|
}
|
|
@@ -22130,7 +22503,7 @@ function resolveRuntimeAgentName(config, agentName) {
|
|
|
22130
22503
|
if (!displayName) {
|
|
22131
22504
|
continue;
|
|
22132
22505
|
}
|
|
22133
|
-
if (
|
|
22506
|
+
if (normalizeAgentName2(displayName) === normalized) {
|
|
22134
22507
|
return internalName;
|
|
22135
22508
|
}
|
|
22136
22509
|
}
|
|
@@ -22146,7 +22519,7 @@ function createDisplayNameMentionRewriter(config) {
|
|
|
22146
22519
|
if (!displayName) {
|
|
22147
22520
|
continue;
|
|
22148
22521
|
}
|
|
22149
|
-
const normalizedDisplayName =
|
|
22522
|
+
const normalizedDisplayName = normalizeAgentName2(displayName);
|
|
22150
22523
|
if (!normalizedDisplayName || normalizedDisplayName === internalName) {
|
|
22151
22524
|
continue;
|
|
22152
22525
|
}
|
|
@@ -22456,8 +22829,8 @@ function isPwshAvailable() {
|
|
|
22456
22829
|
});
|
|
22457
22830
|
return result.status === 0;
|
|
22458
22831
|
}
|
|
22459
|
-
function escapePowerShellPath(
|
|
22460
|
-
return
|
|
22832
|
+
function escapePowerShellPath(path9) {
|
|
22833
|
+
return path9.replace(/'/g, "''");
|
|
22461
22834
|
}
|
|
22462
22835
|
function getWindowsZipExtractor() {
|
|
22463
22836
|
const buildNumber = getWindowsBuildNumber();
|
|
@@ -22786,6 +23159,7 @@ function parseModel(model) {
|
|
|
22786
23159
|
return { providerID: model.slice(0, slash), modelID: model.slice(slash + 1) };
|
|
22787
23160
|
}
|
|
22788
23161
|
var DEDUP_WINDOW_MS = 5000;
|
|
23162
|
+
var REPROMPT_DELAY_MS = 500;
|
|
22789
23163
|
|
|
22790
23164
|
class ForegroundFallbackManager {
|
|
22791
23165
|
client;
|
|
@@ -22918,11 +23292,20 @@ class ForegroundFallbackManager {
|
|
|
22918
23292
|
log("[foreground-fallback] no user message found", { sessionID });
|
|
22919
23293
|
return;
|
|
22920
23294
|
}
|
|
22921
|
-
try {
|
|
22922
|
-
await this.client.session.abort({ path: { id: sessionID } });
|
|
22923
|
-
} catch {}
|
|
22924
|
-
await new Promise((r) => setTimeout(r, 500));
|
|
22925
23295
|
const sessionClient = this.client.session;
|
|
23296
|
+
if (typeof sessionClient.promptAsync !== "function") {
|
|
23297
|
+
log("[foreground-fallback] promptAsync unavailable", { sessionID });
|
|
23298
|
+
return;
|
|
23299
|
+
}
|
|
23300
|
+
try {
|
|
23301
|
+
await abortSessionWithTimeout(this.client, sessionID);
|
|
23302
|
+
} catch (error) {
|
|
23303
|
+
log("[foreground-fallback] abort did not complete cleanly", {
|
|
23304
|
+
sessionID,
|
|
23305
|
+
error: error instanceof Error ? error.message : String(error)
|
|
23306
|
+
});
|
|
23307
|
+
}
|
|
23308
|
+
await new Promise((r) => setTimeout(r, REPROMPT_DELAY_MS));
|
|
22926
23309
|
await sessionClient.promptAsync({
|
|
22927
23310
|
path: { id: sessionID },
|
|
22928
23311
|
body: { parts: lastUser.parts, model: ref }
|
|
@@ -22969,8 +23352,8 @@ class ForegroundFallbackManager {
|
|
|
22969
23352
|
// src/hooks/image-hook.ts
|
|
22970
23353
|
import { createHash } from "node:crypto";
|
|
22971
23354
|
import {
|
|
22972
|
-
existsSync as
|
|
22973
|
-
mkdirSync as
|
|
23355
|
+
existsSync as existsSync5,
|
|
23356
|
+
mkdirSync as mkdirSync3,
|
|
22974
23357
|
readdirSync as readdirSync2,
|
|
22975
23358
|
rmdirSync,
|
|
22976
23359
|
statSync as statSync3,
|
|
@@ -23065,7 +23448,7 @@ function writeUniqueFile(dir, name, data, log2) {
|
|
|
23065
23448
|
const ext = extname(name);
|
|
23066
23449
|
const base = basename2(name, ext) || name;
|
|
23067
23450
|
let candidate = join7(dir, name);
|
|
23068
|
-
if (
|
|
23451
|
+
if (existsSync5(candidate)) {
|
|
23069
23452
|
return candidate;
|
|
23070
23453
|
}
|
|
23071
23454
|
let counter = 0;
|
|
@@ -23103,14 +23486,14 @@ function processImageAttachments(args) {
|
|
|
23103
23486
|
}
|
|
23104
23487
|
const saveDir = join7(workDir, ".opencode", "images");
|
|
23105
23488
|
if (messagesWithImages.length === 0) {
|
|
23106
|
-
if (
|
|
23489
|
+
if (existsSync5(saveDir))
|
|
23107
23490
|
cleanupAllSessions(saveDir);
|
|
23108
23491
|
return;
|
|
23109
23492
|
}
|
|
23110
23493
|
const gitignorePath = join7(workDir, ".opencode", ".gitignore");
|
|
23111
23494
|
try {
|
|
23112
|
-
|
|
23113
|
-
if (!
|
|
23495
|
+
mkdirSync3(saveDir, { recursive: true });
|
|
23496
|
+
if (!existsSync5(gitignorePath))
|
|
23114
23497
|
writeFileSync3(gitignorePath, `*
|
|
23115
23498
|
`);
|
|
23116
23499
|
} catch (e) {
|
|
@@ -23121,7 +23504,7 @@ function processImageAttachments(args) {
|
|
|
23121
23504
|
const sessionSubdir = msg.info.sessionID ? sanitizeFilename(msg.info.sessionID) : undefined;
|
|
23122
23505
|
const targetDir = sessionSubdir ? join7(saveDir, sessionSubdir) : saveDir;
|
|
23123
23506
|
try {
|
|
23124
|
-
|
|
23507
|
+
mkdirSync3(targetDir, { recursive: true });
|
|
23125
23508
|
} catch (e) {
|
|
23126
23509
|
log2(`[image-hook] failed to create target image directory: ${e}`);
|
|
23127
23510
|
}
|
|
@@ -23280,7 +23663,7 @@ function createPostFileToolNudgeHook(options = {}) {
|
|
|
23280
23663
|
};
|
|
23281
23664
|
}
|
|
23282
23665
|
// src/hooks/task-session-manager/index.ts
|
|
23283
|
-
import
|
|
23666
|
+
import path9 from "node:path";
|
|
23284
23667
|
var AGENT_NAME_SET = new Set([
|
|
23285
23668
|
"orchestrator",
|
|
23286
23669
|
"oracle",
|
|
@@ -23305,8 +23688,8 @@ function extractPath(output) {
|
|
|
23305
23688
|
return /<path>([^<]+)<\/path>/.exec(output)?.[1];
|
|
23306
23689
|
}
|
|
23307
23690
|
function normalizePath(root, file) {
|
|
23308
|
-
const relative =
|
|
23309
|
-
if (!relative || relative.startsWith("..") ||
|
|
23691
|
+
const relative = path9.relative(root, file);
|
|
23692
|
+
if (!relative || relative.startsWith("..") || path9.isAbsolute(relative)) {
|
|
23310
23693
|
return file;
|
|
23311
23694
|
}
|
|
23312
23695
|
return relative;
|
|
@@ -23342,6 +23725,7 @@ function createTaskSessionManagerHook(_ctx, options) {
|
|
|
23342
23725
|
const pendingCallOrder = [];
|
|
23343
23726
|
const contextByTask = new Map;
|
|
23344
23727
|
const pendingManagedTaskIds = new Set;
|
|
23728
|
+
let anonymousPendingCallId = 0;
|
|
23345
23729
|
function addTaskContext(taskId, files) {
|
|
23346
23730
|
if (files.length === 0)
|
|
23347
23731
|
return;
|
|
@@ -23388,6 +23772,9 @@ function createTaskSessionManagerHook(_ctx, options) {
|
|
|
23388
23772
|
const firstLine = output.split(/\r?\n/, 1)[0]?.trim().toLowerCase() ?? "";
|
|
23389
23773
|
return firstLine.startsWith("[error]") && firstLine.includes("session") && (firstLine.includes("not found") || firstLine.includes("no session"));
|
|
23390
23774
|
}
|
|
23775
|
+
function pendingCallId(input) {
|
|
23776
|
+
return input.callID ?? `${input.sessionID ?? "unknown"}:anonymous-${++anonymousPendingCallId}`;
|
|
23777
|
+
}
|
|
23391
23778
|
function rememberPendingCall(call) {
|
|
23392
23779
|
const existingIndex = pendingCallOrder.indexOf(call.callId);
|
|
23393
23780
|
if (existingIndex >= 0) {
|
|
@@ -23403,17 +23790,23 @@ function createTaskSessionManagerHook(_ctx, options) {
|
|
|
23403
23790
|
pendingCalls.delete(evictedCallId);
|
|
23404
23791
|
}
|
|
23405
23792
|
}
|
|
23406
|
-
function takePendingCall(callId) {
|
|
23407
|
-
|
|
23793
|
+
function takePendingCall(callId, parentSessionId) {
|
|
23794
|
+
const resolvedCallId = callId ?? firstPendingCallForParent(parentSessionId);
|
|
23795
|
+
if (!resolvedCallId)
|
|
23408
23796
|
return;
|
|
23409
|
-
const pending = pendingCalls.get(
|
|
23410
|
-
pendingCalls.delete(
|
|
23411
|
-
const orderIndex = pendingCallOrder.indexOf(
|
|
23797
|
+
const pending = pendingCalls.get(resolvedCallId);
|
|
23798
|
+
pendingCalls.delete(resolvedCallId);
|
|
23799
|
+
const orderIndex = pendingCallOrder.indexOf(resolvedCallId);
|
|
23412
23800
|
if (orderIndex >= 0) {
|
|
23413
23801
|
pendingCallOrder.splice(orderIndex, 1);
|
|
23414
23802
|
}
|
|
23415
23803
|
return pending;
|
|
23416
23804
|
}
|
|
23805
|
+
function firstPendingCallForParent(parentSessionId) {
|
|
23806
|
+
if (!parentSessionId)
|
|
23807
|
+
return;
|
|
23808
|
+
return pendingCallOrder.find((callId) => pendingCalls.get(callId)?.parentSessionId === parentSessionId);
|
|
23809
|
+
}
|
|
23417
23810
|
return {
|
|
23418
23811
|
"tool.execute.before": async (input, output) => {
|
|
23419
23812
|
if (input.tool.toLowerCase() !== "task")
|
|
@@ -23431,14 +23824,16 @@ function createTaskSessionManagerHook(_ctx, options) {
|
|
|
23431
23824
|
prompt: typeof args.prompt === "string" ? args.prompt : undefined,
|
|
23432
23825
|
agentType: args.subagent_type
|
|
23433
23826
|
});
|
|
23434
|
-
|
|
23435
|
-
|
|
23436
|
-
|
|
23437
|
-
|
|
23438
|
-
|
|
23439
|
-
|
|
23440
|
-
|
|
23441
|
-
|
|
23827
|
+
const pendingCall = {
|
|
23828
|
+
callId: pendingCallId({
|
|
23829
|
+
callID: input.callID,
|
|
23830
|
+
sessionID: input.sessionID
|
|
23831
|
+
}),
|
|
23832
|
+
parentSessionId: input.sessionID,
|
|
23833
|
+
agentType: args.subagent_type,
|
|
23834
|
+
label
|
|
23835
|
+
};
|
|
23836
|
+
rememberPendingCall(pendingCall);
|
|
23442
23837
|
if (typeof args.task_id !== "string" || args.task_id.trim() === "") {
|
|
23443
23838
|
return;
|
|
23444
23839
|
}
|
|
@@ -23451,15 +23846,8 @@ function createTaskSessionManagerHook(_ctx, options) {
|
|
|
23451
23846
|
args.task_id = remembered.taskId;
|
|
23452
23847
|
pendingManagedTaskIds.add(remembered.taskId);
|
|
23453
23848
|
sessionManager.markUsed(input.sessionID, args.subagent_type, remembered.taskId);
|
|
23454
|
-
|
|
23455
|
-
|
|
23456
|
-
callId: input.callID,
|
|
23457
|
-
parentSessionId: input.sessionID,
|
|
23458
|
-
agentType: args.subagent_type,
|
|
23459
|
-
label,
|
|
23460
|
-
resumedTaskId: remembered.taskId
|
|
23461
|
-
});
|
|
23462
|
-
}
|
|
23849
|
+
pendingCall.resumedTaskId = remembered.taskId;
|
|
23850
|
+
rememberPendingCall(pendingCall);
|
|
23463
23851
|
},
|
|
23464
23852
|
"tool.execute.after": async (input, output) => {
|
|
23465
23853
|
if (input.tool.toLowerCase() === "read") {
|
|
@@ -23470,7 +23858,7 @@ function createTaskSessionManagerHook(_ctx, options) {
|
|
|
23470
23858
|
}
|
|
23471
23859
|
if (input.tool.toLowerCase() !== "task")
|
|
23472
23860
|
return;
|
|
23473
|
-
const pending = takePendingCall(input.callID);
|
|
23861
|
+
const pending = takePendingCall(input.callID, input.sessionID);
|
|
23474
23862
|
if (!pending || typeof output.output !== "string")
|
|
23475
23863
|
return;
|
|
23476
23864
|
const taskId = parseTaskIdFromTaskOutput(output.output);
|
|
@@ -23538,8 +23926,8 @@ function createTaskSessionManagerHook(_ctx, options) {
|
|
|
23538
23926
|
const sessionId = input.event.properties?.info?.id ?? input.event.properties?.sessionID;
|
|
23539
23927
|
if (!sessionId)
|
|
23540
23928
|
return;
|
|
23541
|
-
sessionManager.clearParent(sessionId);
|
|
23542
23929
|
sessionManager.dropTask(sessionId);
|
|
23930
|
+
sessionManager.clearParent(sessionId);
|
|
23543
23931
|
contextByTask.delete(sessionId);
|
|
23544
23932
|
pendingManagedTaskIds.delete(sessionId);
|
|
23545
23933
|
pruneContext();
|
|
@@ -23693,6 +24081,7 @@ function createTodoHygiene(options) {
|
|
|
23693
24081
|
// src/hooks/todo-continuation/index.ts
|
|
23694
24082
|
var HOOK_NAME = "todo-continuation";
|
|
23695
24083
|
var COMMAND_NAME = "auto-continue";
|
|
24084
|
+
var TODO_STATE_TIMEOUT_MS = 500;
|
|
23696
24085
|
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
24086
|
var TODO_HYGIENE_INSTRUCTION_OPEN = '<instruction name="todo_hygiene">';
|
|
23698
24087
|
var TODO_HYGIENE_INSTRUCTION_CLOSE = "</instruction>";
|
|
@@ -23780,12 +24169,15 @@ function createTodoContinuationHook(ctx, config) {
|
|
|
23780
24169
|
notifyingSessionIds: new Set,
|
|
23781
24170
|
notificationBusyUntilBySession: new Map
|
|
23782
24171
|
};
|
|
24172
|
+
async function fetchTodos(sessionID) {
|
|
24173
|
+
const result = await withTimeout(ctx.client.session.todo({
|
|
24174
|
+
path: { id: sessionID }
|
|
24175
|
+
}), TODO_STATE_TIMEOUT_MS, `Todo state lookup timed out after ${TODO_STATE_TIMEOUT_MS}ms`);
|
|
24176
|
+
return result.data;
|
|
24177
|
+
}
|
|
23783
24178
|
const hygiene = createTodoHygiene({
|
|
23784
24179
|
getTodoState: async (sessionID) => {
|
|
23785
|
-
const
|
|
23786
|
-
path: { id: sessionID }
|
|
23787
|
-
});
|
|
23788
|
-
const todos = result.data;
|
|
24180
|
+
const todos = await fetchTodos(sessionID);
|
|
23789
24181
|
const openTodos = todos.filter((todo) => !TERMINAL_TODO_STATUSES.includes(todo.status));
|
|
23790
24182
|
return {
|
|
23791
24183
|
hasOpenTodos: openTodos.length > 0,
|
|
@@ -23970,10 +24362,7 @@ function createTodoContinuationHook(ctx, config) {
|
|
|
23970
24362
|
}
|
|
23971
24363
|
if (autoEnable && !state.enabled) {
|
|
23972
24364
|
try {
|
|
23973
|
-
const
|
|
23974
|
-
path: { id: sessionID }
|
|
23975
|
-
});
|
|
23976
|
-
const todos = todosResult.data;
|
|
24365
|
+
const todos = await fetchTodos(sessionID);
|
|
23977
24366
|
const incompleteCount2 = todos.filter((t) => !TERMINAL_TODO_STATUSES.includes(t.status)).length;
|
|
23978
24367
|
if (incompleteCount2 >= autoEnableThreshold) {
|
|
23979
24368
|
state.enabled = true;
|
|
@@ -23999,10 +24388,7 @@ function createTodoContinuationHook(ctx, config) {
|
|
|
23999
24388
|
let hasIncompleteTodos = false;
|
|
24000
24389
|
let incompleteCount = 0;
|
|
24001
24390
|
try {
|
|
24002
|
-
const
|
|
24003
|
-
path: { id: sessionID }
|
|
24004
|
-
});
|
|
24005
|
-
const todos = todosResult.data;
|
|
24391
|
+
const todos = await fetchTodos(sessionID);
|
|
24006
24392
|
incompleteCount = todos.filter((t) => !TERMINAL_TODO_STATUSES.includes(t.status)).length;
|
|
24007
24393
|
hasIncompleteTodos = incompleteCount > 0;
|
|
24008
24394
|
log(`[${HOOK_NAME}] Fetched todos`, {
|
|
@@ -24213,10 +24599,7 @@ function createTodoContinuationHook(ctx, config) {
|
|
|
24213
24599
|
});
|
|
24214
24600
|
let hasIncompleteTodos = false;
|
|
24215
24601
|
try {
|
|
24216
|
-
const
|
|
24217
|
-
path: { id: input.sessionID }
|
|
24218
|
-
});
|
|
24219
|
-
const todos = todosResult.data;
|
|
24602
|
+
const todos = await fetchTodos(input.sessionID);
|
|
24220
24603
|
hasIncompleteTodos = todos.some((t) => !TERMINAL_TODO_STATUSES.includes(t.status));
|
|
24221
24604
|
} catch (error) {
|
|
24222
24605
|
log(`[${HOOK_NAME}] Warning: failed to fetch todos in command hook`, {
|
|
@@ -24240,7 +24623,7 @@ function createTodoContinuationHook(ctx, config) {
|
|
|
24240
24623
|
};
|
|
24241
24624
|
}
|
|
24242
24625
|
// src/interview/manager.ts
|
|
24243
|
-
import
|
|
24626
|
+
import path13 from "node:path";
|
|
24244
24627
|
|
|
24245
24628
|
// src/interview/dashboard.ts
|
|
24246
24629
|
import crypto from "node:crypto";
|
|
@@ -24249,28 +24632,28 @@ import fs7 from "node:fs/promises";
|
|
|
24249
24632
|
import {
|
|
24250
24633
|
createServer
|
|
24251
24634
|
} from "node:http";
|
|
24252
|
-
import
|
|
24253
|
-
import
|
|
24635
|
+
import os4 from "node:os";
|
|
24636
|
+
import path11 from "node:path";
|
|
24254
24637
|
import { URL as URL2 } from "node:url";
|
|
24255
24638
|
|
|
24256
24639
|
// src/interview/document.ts
|
|
24257
24640
|
import * as fsSync from "node:fs";
|
|
24258
24641
|
import * as fs6 from "node:fs/promises";
|
|
24259
|
-
import * as
|
|
24642
|
+
import * as path10 from "node:path";
|
|
24260
24643
|
var DEFAULT_OUTPUT_FOLDER = "interview";
|
|
24261
24644
|
function normalizeOutputFolder(outputFolder) {
|
|
24262
24645
|
const normalized = outputFolder.trim().replace(/^\/+|\/+$/g, "");
|
|
24263
24646
|
return normalized || DEFAULT_OUTPUT_FOLDER;
|
|
24264
24647
|
}
|
|
24265
24648
|
function createInterviewDirectoryPath(directory, outputFolder) {
|
|
24266
|
-
return
|
|
24649
|
+
return path10.join(directory, normalizeOutputFolder(outputFolder));
|
|
24267
24650
|
}
|
|
24268
24651
|
function createInterviewFilePath(directory, outputFolder, idea) {
|
|
24269
24652
|
const fileName = `${slugify(idea) || "interview"}.md`;
|
|
24270
|
-
return
|
|
24653
|
+
return path10.join(createInterviewDirectoryPath(directory, outputFolder), fileName);
|
|
24271
24654
|
}
|
|
24272
24655
|
function relativeInterviewPath(directory, filePath) {
|
|
24273
|
-
return
|
|
24656
|
+
return path10.relative(directory, filePath) || path10.basename(filePath);
|
|
24274
24657
|
}
|
|
24275
24658
|
function resolveExistingInterviewPath(directory, outputFolder, value) {
|
|
24276
24659
|
const trimmed = value.trim();
|
|
@@ -24279,22 +24662,22 @@ function resolveExistingInterviewPath(directory, outputFolder, value) {
|
|
|
24279
24662
|
}
|
|
24280
24663
|
const outputDir = createInterviewDirectoryPath(directory, outputFolder);
|
|
24281
24664
|
const candidates = new Set;
|
|
24282
|
-
const resolvedRoot =
|
|
24283
|
-
if (
|
|
24665
|
+
const resolvedRoot = path10.resolve(directory);
|
|
24666
|
+
if (path10.isAbsolute(trimmed)) {
|
|
24284
24667
|
candidates.add(trimmed);
|
|
24285
24668
|
} else {
|
|
24286
|
-
candidates.add(
|
|
24287
|
-
candidates.add(
|
|
24669
|
+
candidates.add(path10.resolve(directory, trimmed));
|
|
24670
|
+
candidates.add(path10.join(outputDir, trimmed));
|
|
24288
24671
|
if (!trimmed.endsWith(".md")) {
|
|
24289
|
-
candidates.add(
|
|
24672
|
+
candidates.add(path10.join(outputDir, `${trimmed}.md`));
|
|
24290
24673
|
}
|
|
24291
24674
|
}
|
|
24292
24675
|
for (const candidate of candidates) {
|
|
24293
|
-
if (
|
|
24676
|
+
if (path10.extname(candidate) !== ".md") {
|
|
24294
24677
|
continue;
|
|
24295
24678
|
}
|
|
24296
|
-
const resolved =
|
|
24297
|
-
if (!resolved.startsWith(resolvedRoot +
|
|
24679
|
+
const resolved = path10.resolve(candidate);
|
|
24680
|
+
if (!resolved.startsWith(resolvedRoot + path10.sep) && resolved !== resolvedRoot) {
|
|
24298
24681
|
continue;
|
|
24299
24682
|
}
|
|
24300
24683
|
if (fsSync.existsSync(candidate)) {
|
|
@@ -24374,7 +24757,7 @@ function parseFrontmatter(content) {
|
|
|
24374
24757
|
return result;
|
|
24375
24758
|
}
|
|
24376
24759
|
async function ensureInterviewFile(record) {
|
|
24377
|
-
await fs6.mkdir(
|
|
24760
|
+
await fs6.mkdir(path10.dirname(record.markdownPath), { recursive: true });
|
|
24378
24761
|
try {
|
|
24379
24762
|
await fs6.access(record.markdownPath);
|
|
24380
24763
|
} catch {
|
|
@@ -26044,12 +26427,12 @@ function renderInterviewPage(interviewId, resumeSlug) {
|
|
|
26044
26427
|
|
|
26045
26428
|
// src/interview/dashboard.ts
|
|
26046
26429
|
function getAuthFilePath(port) {
|
|
26047
|
-
const dataHome = process.env.XDG_DATA_HOME ||
|
|
26048
|
-
return
|
|
26430
|
+
const dataHome = process.env.XDG_DATA_HOME || path11.join(os4.homedir(), ".local", "share");
|
|
26431
|
+
return path11.join(dataHome, "opencode", `.dashboard-${port}.json`);
|
|
26049
26432
|
}
|
|
26050
26433
|
function writeAuthFile(port, token) {
|
|
26051
26434
|
const filePath = getAuthFilePath(port);
|
|
26052
|
-
const dir =
|
|
26435
|
+
const dir = path11.dirname(filePath);
|
|
26053
26436
|
try {
|
|
26054
26437
|
fsSync2.mkdirSync(dir, { recursive: true });
|
|
26055
26438
|
} catch {}
|
|
@@ -26146,7 +26529,7 @@ function createDashboardServer(config) {
|
|
|
26146
26529
|
let scanDays = 30;
|
|
26147
26530
|
function getKnownDirectories() {
|
|
26148
26531
|
const dirs = new Set;
|
|
26149
|
-
dirs.add(
|
|
26532
|
+
dirs.add(os4.homedir());
|
|
26150
26533
|
for (const session2 of sessions.values()) {
|
|
26151
26534
|
if (session2.directory)
|
|
26152
26535
|
dirs.add(session2.directory);
|
|
@@ -26186,7 +26569,7 @@ function createDashboardServer(config) {
|
|
|
26186
26569
|
const directories = getKnownDirectories();
|
|
26187
26570
|
const items = [];
|
|
26188
26571
|
for (const dir of directories) {
|
|
26189
|
-
const interviewDir =
|
|
26572
|
+
const interviewDir = path11.join(dir, config.outputFolder);
|
|
26190
26573
|
let entries;
|
|
26191
26574
|
try {
|
|
26192
26575
|
entries = await fs7.readdir(interviewDir);
|
|
@@ -26198,7 +26581,7 @@ function createDashboardServer(config) {
|
|
|
26198
26581
|
continue;
|
|
26199
26582
|
let content;
|
|
26200
26583
|
try {
|
|
26201
|
-
content = await fs7.readFile(
|
|
26584
|
+
content = await fs7.readFile(path11.join(interviewDir, entry), "utf8");
|
|
26202
26585
|
} catch {
|
|
26203
26586
|
continue;
|
|
26204
26587
|
}
|
|
@@ -26224,7 +26607,7 @@ function createDashboardServer(config) {
|
|
|
26224
26607
|
const directories = getKnownDirectories();
|
|
26225
26608
|
let rebuilt = 0;
|
|
26226
26609
|
for (const dir of directories) {
|
|
26227
|
-
const interviewDir =
|
|
26610
|
+
const interviewDir = path11.join(dir, config.outputFolder);
|
|
26228
26611
|
let entries;
|
|
26229
26612
|
try {
|
|
26230
26613
|
entries = await fs7.readdir(interviewDir);
|
|
@@ -26236,7 +26619,7 @@ function createDashboardServer(config) {
|
|
|
26236
26619
|
continue;
|
|
26237
26620
|
let content;
|
|
26238
26621
|
try {
|
|
26239
|
-
content = await fs7.readFile(
|
|
26622
|
+
content = await fs7.readFile(path11.join(interviewDir, entry), "utf8");
|
|
26240
26623
|
} catch {
|
|
26241
26624
|
continue;
|
|
26242
26625
|
}
|
|
@@ -26262,7 +26645,7 @@ function createDashboardServer(config) {
|
|
|
26262
26645
|
questions: [],
|
|
26263
26646
|
pendingAnswers: null,
|
|
26264
26647
|
lastUpdatedAt: fm.updatedAt ? new Date(fm.updatedAt).getTime() : Date.now(),
|
|
26265
|
-
filePath:
|
|
26648
|
+
filePath: path11.join(interviewDir, entry),
|
|
26266
26649
|
nudgeAction: null
|
|
26267
26650
|
});
|
|
26268
26651
|
if (!sessions.has(fm.sessionID)) {
|
|
@@ -26526,7 +26909,7 @@ function createDashboardServer(config) {
|
|
|
26526
26909
|
const dirs = getKnownDirectories();
|
|
26527
26910
|
for (const dir of dirs) {
|
|
26528
26911
|
const slug = extractResumeSlug(interviewId);
|
|
26529
|
-
const candidate =
|
|
26912
|
+
const candidate = path11.join(dir, config.outputFolder, `${slug}.md`);
|
|
26530
26913
|
try {
|
|
26531
26914
|
document = await fs7.readFile(candidate, "utf8");
|
|
26532
26915
|
markdownPath = candidate;
|
|
@@ -27052,9 +27435,9 @@ function createInterviewServer(deps) {
|
|
|
27052
27435
|
}
|
|
27053
27436
|
|
|
27054
27437
|
// src/interview/service.ts
|
|
27055
|
-
import { spawn } from "node:child_process";
|
|
27438
|
+
import { spawn as spawn2 } from "node:child_process";
|
|
27056
27439
|
import * as fs8 from "node:fs/promises";
|
|
27057
|
-
import * as
|
|
27440
|
+
import * as path12 from "node:path";
|
|
27058
27441
|
|
|
27059
27442
|
// src/interview/types.ts
|
|
27060
27443
|
import { z as z3 } from "zod";
|
|
@@ -27256,7 +27639,7 @@ function openBrowser(url) {
|
|
|
27256
27639
|
args = [url];
|
|
27257
27640
|
}
|
|
27258
27641
|
try {
|
|
27259
|
-
const child =
|
|
27642
|
+
const child = spawn2(command, args, { detached: true, stdio: "ignore" });
|
|
27260
27643
|
child.on("error", (error) => {
|
|
27261
27644
|
log("[interview] failed to open browser:", { error: error.message, url });
|
|
27262
27645
|
});
|
|
@@ -27321,12 +27704,12 @@ function createInterviewService(ctx, config, deps) {
|
|
|
27321
27704
|
if (!newSlug) {
|
|
27322
27705
|
return;
|
|
27323
27706
|
}
|
|
27324
|
-
const currentFileName =
|
|
27707
|
+
const currentFileName = path12.basename(interview.markdownPath, ".md");
|
|
27325
27708
|
if (currentFileName === newSlug) {
|
|
27326
27709
|
return;
|
|
27327
27710
|
}
|
|
27328
|
-
const dir =
|
|
27329
|
-
const newPath =
|
|
27711
|
+
const dir = path12.dirname(interview.markdownPath);
|
|
27712
|
+
const newPath = path12.join(dir, `${newSlug}.md`);
|
|
27330
27713
|
try {
|
|
27331
27714
|
await fs8.access(newPath);
|
|
27332
27715
|
return;
|
|
@@ -27402,9 +27785,9 @@ function createInterviewService(ctx, config, deps) {
|
|
|
27402
27785
|
const messages = await loadMessages(sessionID);
|
|
27403
27786
|
const title = extractTitle(document);
|
|
27404
27787
|
const record = {
|
|
27405
|
-
id: `${Date.now()}-${++idCounter}-${slugify(
|
|
27788
|
+
id: `${Date.now()}-${++idCounter}-${slugify(path12.basename(markdownPath, ".md")) || "interview"}`,
|
|
27406
27789
|
sessionID,
|
|
27407
|
-
idea: title ||
|
|
27790
|
+
idea: title || path12.basename(markdownPath, ".md"),
|
|
27408
27791
|
markdownPath,
|
|
27409
27792
|
createdAt: nowIso(),
|
|
27410
27793
|
status: "active",
|
|
@@ -27631,7 +28014,7 @@ function createInterviewService(ctx, config, deps) {
|
|
|
27631
28014
|
return fileCache.items;
|
|
27632
28015
|
}
|
|
27633
28016
|
const outputDir = createInterviewDirectoryPath(ctx.directory, outputFolder);
|
|
27634
|
-
const activePaths = new Set([...interviewsById.values()].filter((i) => i.status === "active").map((i) =>
|
|
28017
|
+
const activePaths = new Set([...interviewsById.values()].filter((i) => i.status === "active").map((i) => path12.resolve(i.markdownPath)));
|
|
27635
28018
|
let entries;
|
|
27636
28019
|
try {
|
|
27637
28020
|
entries = await fs8.readdir(outputDir);
|
|
@@ -27642,8 +28025,8 @@ function createInterviewService(ctx, config, deps) {
|
|
|
27642
28025
|
for (const entry of entries) {
|
|
27643
28026
|
if (!entry.endsWith(".md"))
|
|
27644
28027
|
continue;
|
|
27645
|
-
const fullPath =
|
|
27646
|
-
if (activePaths.has(
|
|
28028
|
+
const fullPath = path12.join(outputDir, entry);
|
|
28029
|
+
if (activePaths.has(path12.resolve(fullPath)))
|
|
27647
28030
|
continue;
|
|
27648
28031
|
let content;
|
|
27649
28032
|
try {
|
|
@@ -27742,7 +28125,7 @@ function createInterviewManager(ctx, config) {
|
|
|
27742
28125
|
const outputFolder = interviewConfig?.outputFolder ?? "interview";
|
|
27743
28126
|
if (!dashboardEnabled) {
|
|
27744
28127
|
const service2 = createInterviewService(ctx, interviewConfig);
|
|
27745
|
-
const resolvedOutputPath =
|
|
28128
|
+
const resolvedOutputPath = path13.join(ctx.directory, outputFolder);
|
|
27746
28129
|
const server = createInterviewServer({
|
|
27747
28130
|
getState: async (interviewId) => service2.getInterviewState(interviewId),
|
|
27748
28131
|
listInterviewFiles: async () => service2.listInterviewFiles(),
|
|
@@ -27847,7 +28230,7 @@ function createInterviewManager(ctx, config) {
|
|
|
27847
28230
|
listInterviews: () => service.listInterviews(),
|
|
27848
28231
|
submitAnswers: async (interviewId, answers) => service.submitAnswers(interviewId, answers),
|
|
27849
28232
|
handleNudgeAction: async (interviewId, action) => service.handleNudgeAction(interviewId, action),
|
|
27850
|
-
outputFolder:
|
|
28233
|
+
outputFolder: path13.join(ctx.directory, outputFolder),
|
|
27851
28234
|
port: 0
|
|
27852
28235
|
});
|
|
27853
28236
|
service.setBaseUrlResolver(() => perSessionServer.ensureStarted());
|
|
@@ -28115,6 +28498,8 @@ function createBuiltinMcps(disabledMcps = [], websearchConfig) {
|
|
|
28115
28498
|
}
|
|
28116
28499
|
|
|
28117
28500
|
// src/multiplexer/tmux/index.ts
|
|
28501
|
+
var TMUX_LAYOUT_DEBOUNCE_MS = 150;
|
|
28502
|
+
|
|
28118
28503
|
class TmuxMultiplexer {
|
|
28119
28504
|
type = "tmux";
|
|
28120
28505
|
binaryPath = null;
|
|
@@ -28122,6 +28507,8 @@ class TmuxMultiplexer {
|
|
|
28122
28507
|
storedLayout;
|
|
28123
28508
|
storedMainPaneSize;
|
|
28124
28509
|
targetPane = process.env.TMUX_PANE;
|
|
28510
|
+
layoutTimer;
|
|
28511
|
+
layoutGeneration = 0;
|
|
28125
28512
|
constructor(layout = "main-vertical", mainPaneSize = 60) {
|
|
28126
28513
|
this.storedLayout = layout;
|
|
28127
28514
|
this.storedMainPaneSize = mainPaneSize;
|
|
@@ -28183,7 +28570,7 @@ class TmuxMultiplexer {
|
|
|
28183
28570
|
if (exitCode === 0 && paneId) {
|
|
28184
28571
|
const renameProc = crossSpawn([tmux, "select-pane", "-t", paneId, "-T", description.slice(0, 30)], { stdout: "ignore", stderr: "ignore" });
|
|
28185
28572
|
await renameProc.exited;
|
|
28186
|
-
|
|
28573
|
+
this.scheduleLayout();
|
|
28187
28574
|
log("[tmux] spawnPane: SUCCESS", { paneId });
|
|
28188
28575
|
return { success: true, paneId };
|
|
28189
28576
|
}
|
|
@@ -28220,7 +28607,7 @@ class TmuxMultiplexer {
|
|
|
28220
28607
|
const stderr = await proc.stderr();
|
|
28221
28608
|
log("[tmux] closePane: result", { exitCode, stderr: stderr.trim() });
|
|
28222
28609
|
if (exitCode === 0) {
|
|
28223
|
-
|
|
28610
|
+
this.scheduleLayout();
|
|
28224
28611
|
return true;
|
|
28225
28612
|
}
|
|
28226
28613
|
log("[tmux] closePane: failed (pane may already be closed)", { paneId });
|
|
@@ -28231,41 +28618,74 @@ class TmuxMultiplexer {
|
|
|
28231
28618
|
}
|
|
28232
28619
|
}
|
|
28233
28620
|
async applyLayout(layout, mainPaneSize) {
|
|
28621
|
+
if (this.layoutTimer) {
|
|
28622
|
+
clearTimeout(this.layoutTimer);
|
|
28623
|
+
this.layoutTimer = undefined;
|
|
28624
|
+
}
|
|
28625
|
+
this.layoutGeneration++;
|
|
28626
|
+
await this.applyLayoutNow(layout, mainPaneSize);
|
|
28627
|
+
}
|
|
28628
|
+
scheduleLayout() {
|
|
28629
|
+
if (this.layoutTimer)
|
|
28630
|
+
clearTimeout(this.layoutTimer);
|
|
28631
|
+
const gen = ++this.layoutGeneration;
|
|
28632
|
+
this.layoutTimer = setTimeout(() => {
|
|
28633
|
+
this.layoutTimer = undefined;
|
|
28634
|
+
if (this.layoutGeneration === gen) {
|
|
28635
|
+
this.applyLayoutNow(this.storedLayout, this.storedMainPaneSize);
|
|
28636
|
+
}
|
|
28637
|
+
}, TMUX_LAYOUT_DEBOUNCE_MS);
|
|
28638
|
+
this.layoutTimer.unref?.();
|
|
28639
|
+
}
|
|
28640
|
+
async applyLayoutNow(layout, mainPaneSize) {
|
|
28234
28641
|
const tmux = await this.getBinary();
|
|
28235
28642
|
if (!tmux)
|
|
28236
28643
|
return;
|
|
28237
28644
|
this.storedLayout = layout;
|
|
28238
28645
|
this.storedMainPaneSize = mainPaneSize;
|
|
28239
28646
|
try {
|
|
28240
|
-
const
|
|
28241
|
-
|
|
28242
|
-
|
|
28243
|
-
});
|
|
28244
|
-
await layoutProc.exited;
|
|
28647
|
+
const layoutResult = await this.runTmux(tmux, ["select-layout", ...this.targetArgs(), layout]);
|
|
28648
|
+
if (layoutResult !== 0)
|
|
28649
|
+
return;
|
|
28245
28650
|
if (layout === "main-horizontal" || layout === "main-vertical") {
|
|
28246
28651
|
const sizeOption = layout === "main-horizontal" ? "main-pane-height" : "main-pane-width";
|
|
28247
|
-
const
|
|
28248
|
-
tmux,
|
|
28652
|
+
const sizeResult = await this.runTmux(tmux, [
|
|
28249
28653
|
"set-window-option",
|
|
28250
28654
|
...this.targetArgs(),
|
|
28251
28655
|
sizeOption,
|
|
28252
28656
|
`${mainPaneSize}%`
|
|
28253
|
-
]
|
|
28254
|
-
|
|
28255
|
-
|
|
28256
|
-
|
|
28257
|
-
|
|
28258
|
-
|
|
28259
|
-
stdout: "pipe",
|
|
28260
|
-
stderr: "pipe"
|
|
28261
|
-
});
|
|
28262
|
-
await reapplyProc.exited;
|
|
28657
|
+
]);
|
|
28658
|
+
if (sizeResult !== 0)
|
|
28659
|
+
return;
|
|
28660
|
+
const reapplyResult = await this.runTmux(tmux, ["select-layout", ...this.targetArgs(), layout]);
|
|
28661
|
+
if (reapplyResult !== 0)
|
|
28662
|
+
return;
|
|
28263
28663
|
}
|
|
28264
28664
|
log("[tmux] applyLayout: applied", { layout, mainPaneSize });
|
|
28265
28665
|
} catch (err) {
|
|
28266
28666
|
log("[tmux] applyLayout: exception", { error: String(err) });
|
|
28267
28667
|
}
|
|
28268
28668
|
}
|
|
28669
|
+
async runTmux(tmux, args) {
|
|
28670
|
+
const proc = crossSpawn([tmux, ...args], {
|
|
28671
|
+
stdout: "pipe",
|
|
28672
|
+
stderr: "pipe"
|
|
28673
|
+
});
|
|
28674
|
+
const [exitCode, , stderr] = await Promise.all([
|
|
28675
|
+
proc.exited,
|
|
28676
|
+
proc.stdout(),
|
|
28677
|
+
proc.stderr()
|
|
28678
|
+
]);
|
|
28679
|
+
if (exitCode !== 0) {
|
|
28680
|
+
log("[tmux] command failed", {
|
|
28681
|
+
command: args[0],
|
|
28682
|
+
args: [tmux, ...args],
|
|
28683
|
+
exitCode,
|
|
28684
|
+
stderr: stderr.trim()
|
|
28685
|
+
});
|
|
28686
|
+
}
|
|
28687
|
+
return exitCode;
|
|
28688
|
+
}
|
|
28269
28689
|
async getBinary() {
|
|
28270
28690
|
await this.isAvailable();
|
|
28271
28691
|
return this.binaryPath;
|
|
@@ -28287,23 +28707,23 @@ class TmuxMultiplexer {
|
|
|
28287
28707
|
return null;
|
|
28288
28708
|
}
|
|
28289
28709
|
const stdout = await proc.stdout();
|
|
28290
|
-
const
|
|
28710
|
+
const path14 = stdout.trim().split(`
|
|
28291
28711
|
`)[0];
|
|
28292
|
-
if (!
|
|
28712
|
+
if (!path14) {
|
|
28293
28713
|
log("[tmux] findBinary: no path in output");
|
|
28294
28714
|
return null;
|
|
28295
28715
|
}
|
|
28296
|
-
const verifyProc = crossSpawn([
|
|
28716
|
+
const verifyProc = crossSpawn([path14, "-V"], {
|
|
28297
28717
|
stdout: "pipe",
|
|
28298
28718
|
stderr: "pipe"
|
|
28299
28719
|
});
|
|
28300
28720
|
const verifyExit = await verifyProc.exited;
|
|
28301
28721
|
if (verifyExit !== 0) {
|
|
28302
|
-
log("[tmux] findBinary: tmux -V failed", { path:
|
|
28722
|
+
log("[tmux] findBinary: tmux -V failed", { path: path14, verifyExit });
|
|
28303
28723
|
return null;
|
|
28304
28724
|
}
|
|
28305
|
-
log("[tmux] findBinary: found", { path:
|
|
28306
|
-
return
|
|
28725
|
+
log("[tmux] findBinary: found", { path: path14 });
|
|
28726
|
+
return path14;
|
|
28307
28727
|
} catch (err) {
|
|
28308
28728
|
log("[tmux] findBinary: exception", { error: String(err) });
|
|
28309
28729
|
return null;
|
|
@@ -29014,17 +29434,17 @@ async function isServerRunning(serverUrl, timeoutMs = 3000, maxAttempts = 2) {
|
|
|
29014
29434
|
import { tool as tool2 } from "@opencode-ai/plugin/tool";
|
|
29015
29435
|
|
|
29016
29436
|
// src/tools/ast-grep/cli.ts
|
|
29017
|
-
import { existsSync as
|
|
29437
|
+
import { existsSync as existsSync9 } from "node:fs";
|
|
29018
29438
|
|
|
29019
29439
|
// src/tools/ast-grep/constants.ts
|
|
29020
|
-
import { existsSync as
|
|
29440
|
+
import { existsSync as existsSync8, statSync as statSync4 } from "node:fs";
|
|
29021
29441
|
import { createRequire as createRequire3 } from "node:module";
|
|
29022
29442
|
import { dirname as dirname6, join as join11 } from "node:path";
|
|
29023
29443
|
|
|
29024
29444
|
// src/tools/ast-grep/downloader.ts
|
|
29025
|
-
import { chmodSync, existsSync as
|
|
29445
|
+
import { chmodSync, existsSync as existsSync7, mkdirSync as mkdirSync5, unlinkSync as unlinkSync4 } from "node:fs";
|
|
29026
29446
|
import { createRequire as createRequire2 } from "node:module";
|
|
29027
|
-
import { homedir as
|
|
29447
|
+
import { homedir as homedir5 } from "node:os";
|
|
29028
29448
|
import { join as join10 } from "node:path";
|
|
29029
29449
|
var REPO = "ast-grep/ast-grep";
|
|
29030
29450
|
var DEFAULT_VERSION = "0.40.0";
|
|
@@ -29049,11 +29469,11 @@ var PLATFORM_MAP = {
|
|
|
29049
29469
|
function getCacheDir2() {
|
|
29050
29470
|
if (process.platform === "win32") {
|
|
29051
29471
|
const localAppData = process.env.LOCALAPPDATA || process.env.APPDATA;
|
|
29052
|
-
const base2 = localAppData || join10(
|
|
29472
|
+
const base2 = localAppData || join10(homedir5(), "AppData", "Local");
|
|
29053
29473
|
return join10(base2, "oh-my-opencode-slim", "bin");
|
|
29054
29474
|
}
|
|
29055
29475
|
const xdgCache = process.env.XDG_CACHE_HOME;
|
|
29056
|
-
const base = xdgCache || join10(
|
|
29476
|
+
const base = xdgCache || join10(homedir5(), ".cache");
|
|
29057
29477
|
return join10(base, "oh-my-opencode-slim", "bin");
|
|
29058
29478
|
}
|
|
29059
29479
|
function getBinaryName() {
|
|
@@ -29061,7 +29481,7 @@ function getBinaryName() {
|
|
|
29061
29481
|
}
|
|
29062
29482
|
function getCachedBinaryPath() {
|
|
29063
29483
|
const binaryPath = join10(getCacheDir2(), getBinaryName());
|
|
29064
|
-
return
|
|
29484
|
+
return existsSync7(binaryPath) ? binaryPath : null;
|
|
29065
29485
|
}
|
|
29066
29486
|
async function downloadAstGrep(version = DEFAULT_VERSION) {
|
|
29067
29487
|
const platformKey = `${process.platform}-${process.arch}`;
|
|
@@ -29073,16 +29493,16 @@ async function downloadAstGrep(version = DEFAULT_VERSION) {
|
|
|
29073
29493
|
const cacheDir = getCacheDir2();
|
|
29074
29494
|
const binaryName = getBinaryName();
|
|
29075
29495
|
const binaryPath = join10(cacheDir, binaryName);
|
|
29076
|
-
if (
|
|
29496
|
+
if (existsSync7(binaryPath)) {
|
|
29077
29497
|
return binaryPath;
|
|
29078
29498
|
}
|
|
29079
|
-
const { arch, os:
|
|
29080
|
-
const assetName = `app-${arch}-${
|
|
29499
|
+
const { arch, os: os5 } = platformInfo;
|
|
29500
|
+
const assetName = `app-${arch}-${os5}.zip`;
|
|
29081
29501
|
const downloadUrl = `https://github.com/${REPO}/releases/download/${version}/${assetName}`;
|
|
29082
29502
|
console.log(`[oh-my-opencode-slim] Downloading ast-grep binary...`);
|
|
29083
29503
|
try {
|
|
29084
|
-
if (!
|
|
29085
|
-
|
|
29504
|
+
if (!existsSync7(cacheDir)) {
|
|
29505
|
+
mkdirSync5(cacheDir, { recursive: true });
|
|
29086
29506
|
}
|
|
29087
29507
|
const response = await fetch(downloadUrl, { redirect: "follow" });
|
|
29088
29508
|
if (!response.ok) {
|
|
@@ -29092,10 +29512,10 @@ async function downloadAstGrep(version = DEFAULT_VERSION) {
|
|
|
29092
29512
|
const arrayBuffer = await response.arrayBuffer();
|
|
29093
29513
|
await crossWrite(archivePath, arrayBuffer);
|
|
29094
29514
|
await extractZip(archivePath, cacheDir);
|
|
29095
|
-
if (
|
|
29515
|
+
if (existsSync7(archivePath)) {
|
|
29096
29516
|
unlinkSync4(archivePath);
|
|
29097
29517
|
}
|
|
29098
|
-
if (process.platform !== "win32" &&
|
|
29518
|
+
if (process.platform !== "win32" && existsSync7(binaryPath)) {
|
|
29099
29519
|
chmodSync(binaryPath, 493);
|
|
29100
29520
|
}
|
|
29101
29521
|
console.log(`[oh-my-opencode-slim] ast-grep binary ready.`);
|
|
@@ -29178,7 +29598,7 @@ function findSgCliPathSync() {
|
|
|
29178
29598
|
const cliPkgPath = require2.resolve("@ast-grep/cli/package.json");
|
|
29179
29599
|
const cliDir = dirname6(cliPkgPath);
|
|
29180
29600
|
const sgPath = join11(cliDir, binaryName);
|
|
29181
|
-
if (
|
|
29601
|
+
if (existsSync8(sgPath) && isValidBinary(sgPath)) {
|
|
29182
29602
|
return sgPath;
|
|
29183
29603
|
}
|
|
29184
29604
|
} catch {}
|
|
@@ -29190,16 +29610,16 @@ function findSgCliPathSync() {
|
|
|
29190
29610
|
const pkgDir = dirname6(pkgPath);
|
|
29191
29611
|
const astGrepName = process.platform === "win32" ? "ast-grep.exe" : "ast-grep";
|
|
29192
29612
|
const binaryPath = join11(pkgDir, astGrepName);
|
|
29193
|
-
if (
|
|
29613
|
+
if (existsSync8(binaryPath) && isValidBinary(binaryPath)) {
|
|
29194
29614
|
return binaryPath;
|
|
29195
29615
|
}
|
|
29196
29616
|
} catch {}
|
|
29197
29617
|
}
|
|
29198
29618
|
if (process.platform === "darwin") {
|
|
29199
29619
|
const homebrewPaths = ["/opt/homebrew/bin/sg", "/usr/local/bin/sg"];
|
|
29200
|
-
for (const
|
|
29201
|
-
if (
|
|
29202
|
-
return
|
|
29620
|
+
for (const path14 of homebrewPaths) {
|
|
29621
|
+
if (existsSync8(path14) && isValidBinary(path14)) {
|
|
29622
|
+
return path14;
|
|
29203
29623
|
}
|
|
29204
29624
|
}
|
|
29205
29625
|
}
|
|
@@ -29216,8 +29636,8 @@ function getSgCliPath() {
|
|
|
29216
29636
|
}
|
|
29217
29637
|
return "sg";
|
|
29218
29638
|
}
|
|
29219
|
-
function setSgCliPath(
|
|
29220
|
-
resolvedCliPath =
|
|
29639
|
+
function setSgCliPath(path14) {
|
|
29640
|
+
resolvedCliPath = path14;
|
|
29221
29641
|
}
|
|
29222
29642
|
var DEFAULT_TIMEOUT_MS2 = 300000;
|
|
29223
29643
|
var DEFAULT_MAX_OUTPUT_BYTES = 1 * 1024 * 1024;
|
|
@@ -29227,7 +29647,7 @@ var DEFAULT_MAX_MATCHES = 500;
|
|
|
29227
29647
|
var initPromise = null;
|
|
29228
29648
|
async function getAstGrepPath() {
|
|
29229
29649
|
const currentPath = getSgCliPath();
|
|
29230
|
-
if (currentPath !== "sg" &&
|
|
29650
|
+
if (currentPath !== "sg" && existsSync9(currentPath)) {
|
|
29231
29651
|
return currentPath;
|
|
29232
29652
|
}
|
|
29233
29653
|
if (initPromise) {
|
|
@@ -29235,7 +29655,7 @@ async function getAstGrepPath() {
|
|
|
29235
29655
|
}
|
|
29236
29656
|
initPromise = (async () => {
|
|
29237
29657
|
const syncPath = findSgCliPathSync();
|
|
29238
|
-
if (syncPath &&
|
|
29658
|
+
if (syncPath && existsSync9(syncPath)) {
|
|
29239
29659
|
setSgCliPath(syncPath);
|
|
29240
29660
|
return syncPath;
|
|
29241
29661
|
}
|
|
@@ -29274,7 +29694,7 @@ async function runSg(options) {
|
|
|
29274
29694
|
const paths2 = options.paths && options.paths.length > 0 ? options.paths : ["."];
|
|
29275
29695
|
args.push(...paths2);
|
|
29276
29696
|
let cliPath = getSgCliPath();
|
|
29277
|
-
if (!
|
|
29697
|
+
if (!existsSync9(cliPath) && cliPath !== "sg") {
|
|
29278
29698
|
const downloadedPath = await getAstGrepPath();
|
|
29279
29699
|
if (downloadedPath) {
|
|
29280
29700
|
cliPath = downloadedPath;
|
|
@@ -29622,6 +30042,74 @@ Returns the councillor responses with a summary footer.`,
|
|
|
29622
30042
|
});
|
|
29623
30043
|
return { council_session };
|
|
29624
30044
|
}
|
|
30045
|
+
// src/tui-state.ts
|
|
30046
|
+
import * as fs9 from "node:fs";
|
|
30047
|
+
import * as os5 from "node:os";
|
|
30048
|
+
import * as path14 from "node:path";
|
|
30049
|
+
var STATE_DIR = "oh-my-opencode-slim";
|
|
30050
|
+
var STATE_FILE = "tui-state.json";
|
|
30051
|
+
function dataDir() {
|
|
30052
|
+
return process.env.XDG_DATA_HOME ?? path14.join(os5.homedir(), ".local", "share");
|
|
30053
|
+
}
|
|
30054
|
+
function getTuiStatePath() {
|
|
30055
|
+
return path14.join(dataDir(), "opencode", "storage", STATE_DIR, STATE_FILE);
|
|
30056
|
+
}
|
|
30057
|
+
function emptySnapshot() {
|
|
30058
|
+
return {
|
|
30059
|
+
version: 1,
|
|
30060
|
+
updatedAt: Date.now(),
|
|
30061
|
+
agentModels: {}
|
|
30062
|
+
};
|
|
30063
|
+
}
|
|
30064
|
+
function parseSnapshot(value) {
|
|
30065
|
+
const parsed = JSON.parse(value);
|
|
30066
|
+
if (parsed?.version !== 1)
|
|
30067
|
+
return emptySnapshot();
|
|
30068
|
+
return {
|
|
30069
|
+
version: 1,
|
|
30070
|
+
updatedAt: typeof parsed.updatedAt === "number" ? parsed.updatedAt : Date.now(),
|
|
30071
|
+
agentModels: parsed.agentModels ?? {}
|
|
30072
|
+
};
|
|
30073
|
+
}
|
|
30074
|
+
function readTuiSnapshot() {
|
|
30075
|
+
try {
|
|
30076
|
+
return parseSnapshot(fs9.readFileSync(getTuiStatePath(), "utf8"));
|
|
30077
|
+
} catch {
|
|
30078
|
+
return emptySnapshot();
|
|
30079
|
+
}
|
|
30080
|
+
}
|
|
30081
|
+
async function readTuiSnapshotAsync() {
|
|
30082
|
+
try {
|
|
30083
|
+
return parseSnapshot(await fs9.promises.readFile(getTuiStatePath(), "utf8"));
|
|
30084
|
+
} catch {
|
|
30085
|
+
return emptySnapshot();
|
|
30086
|
+
}
|
|
30087
|
+
}
|
|
30088
|
+
function writeTuiSnapshot(snapshot) {
|
|
30089
|
+
try {
|
|
30090
|
+
const filePath = getTuiStatePath();
|
|
30091
|
+
fs9.mkdirSync(path14.dirname(filePath), { recursive: true });
|
|
30092
|
+
fs9.writeFileSync(filePath, `${JSON.stringify(snapshot)}
|
|
30093
|
+
`);
|
|
30094
|
+
} catch {}
|
|
30095
|
+
}
|
|
30096
|
+
function updateSnapshot(mutator) {
|
|
30097
|
+
const snapshot = readTuiSnapshot();
|
|
30098
|
+
mutator(snapshot);
|
|
30099
|
+
snapshot.updatedAt = Date.now();
|
|
30100
|
+
writeTuiSnapshot(snapshot);
|
|
30101
|
+
}
|
|
30102
|
+
function recordTuiAgentModels(input) {
|
|
30103
|
+
updateSnapshot((snapshot) => {
|
|
30104
|
+
snapshot.agentModels = { ...input.agentModels };
|
|
30105
|
+
});
|
|
30106
|
+
}
|
|
30107
|
+
function recordTuiAgentModel(input) {
|
|
30108
|
+
updateSnapshot((snapshot) => {
|
|
30109
|
+
snapshot.agentModels[input.agentName] = input.model;
|
|
30110
|
+
});
|
|
30111
|
+
}
|
|
30112
|
+
|
|
29625
30113
|
// src/tools/preset-manager.ts
|
|
29626
30114
|
var COMMAND_NAME3 = "preset";
|
|
29627
30115
|
function createPresetManager(ctx, config) {
|
|
@@ -29698,6 +30186,14 @@ function createPresetManager(ctx, config) {
|
|
|
29698
30186
|
await ctx.client.config.update({
|
|
29699
30187
|
body: { agent: allUpdates }
|
|
29700
30188
|
});
|
|
30189
|
+
const snapshot = readTuiSnapshot();
|
|
30190
|
+
const agentModels = { ...snapshot.agentModels };
|
|
30191
|
+
for (const [agentName, agentConfig] of Object.entries(allUpdates)) {
|
|
30192
|
+
if (typeof agentConfig.model === "string") {
|
|
30193
|
+
agentModels[agentName] = agentConfig.model;
|
|
30194
|
+
}
|
|
30195
|
+
}
|
|
30196
|
+
recordTuiAgentModels({ agentModels });
|
|
29701
30197
|
activePreset = presetName;
|
|
29702
30198
|
const summaryParts = [];
|
|
29703
30199
|
for (const [name, cfg] of Object.entries(agentUpdates)) {
|
|
@@ -29808,15 +30304,15 @@ var BINARY_PREFIXES = [
|
|
|
29808
30304
|
];
|
|
29809
30305
|
var WEBFETCH_DESCRIPTION = "Fetch a URL with better extraction for static/docs pages. Supports llms.txt probing, content-focused HTML extraction, metadata, redirects, and an optional prompt processed by a cheap secondary model.";
|
|
29810
30306
|
// src/tools/smartfetch/tool.ts
|
|
29811
|
-
import
|
|
29812
|
-
import
|
|
30307
|
+
import os6 from "node:os";
|
|
30308
|
+
import path18 from "node:path";
|
|
29813
30309
|
import {
|
|
29814
30310
|
tool as tool4
|
|
29815
30311
|
} from "@opencode-ai/plugin";
|
|
29816
30312
|
|
|
29817
30313
|
// src/tools/smartfetch/binary.ts
|
|
29818
30314
|
import { mkdir as mkdir2, writeFile as writeFile2 } from "node:fs/promises";
|
|
29819
|
-
import
|
|
30315
|
+
import path15 from "node:path";
|
|
29820
30316
|
function extensionForMime(contentType) {
|
|
29821
30317
|
const mime = contentType.split(";")[0]?.trim().toLowerCase();
|
|
29822
30318
|
const map = {
|
|
@@ -29837,10 +30333,10 @@ function buildBinaryResultMessage(fetchResult, savedPath) {
|
|
|
29837
30333
|
async function saveBinary(binaryDir, data, contentType, filename) {
|
|
29838
30334
|
await mkdir2(binaryDir, { recursive: true });
|
|
29839
30335
|
const initialName = filename || `webfetch-${Date.now()}.${extensionForMime(contentType)}`;
|
|
29840
|
-
const parsed =
|
|
30336
|
+
const parsed = path15.parse(initialName);
|
|
29841
30337
|
for (let attempt = 0;attempt < 1000; attempt++) {
|
|
29842
30338
|
const candidateName = attempt === 0 ? initialName : `${parsed.name}-${attempt}${parsed.ext || `.${extensionForMime(contentType)}`}`;
|
|
29843
|
-
const file =
|
|
30339
|
+
const file = path15.join(binaryDir, candidateName);
|
|
29844
30340
|
try {
|
|
29845
30341
|
await writeFile2(file, data, { flag: "wx" });
|
|
29846
30342
|
return file;
|
|
@@ -30494,7 +30990,7 @@ var L = class u2 {
|
|
|
30494
30990
|
};
|
|
30495
30991
|
|
|
30496
30992
|
// src/tools/smartfetch/network.ts
|
|
30497
|
-
import
|
|
30993
|
+
import path16 from "node:path";
|
|
30498
30994
|
|
|
30499
30995
|
// src/tools/smartfetch/utils.ts
|
|
30500
30996
|
var import_readability = __toESM(require_readability(), 1);
|
|
@@ -31219,7 +31715,7 @@ function inferFilenameFromUrl(url) {
|
|
|
31219
31715
|
function truncateFilename(name, maxLength = 180) {
|
|
31220
31716
|
if (name.length <= maxLength)
|
|
31221
31717
|
return name;
|
|
31222
|
-
const parsed =
|
|
31718
|
+
const parsed = path16.parse(name);
|
|
31223
31719
|
const ext = parsed.ext || "";
|
|
31224
31720
|
const baseLimit = Math.max(1, maxLength - ext.length);
|
|
31225
31721
|
return `${parsed.name.slice(0, baseLimit)}${ext}`;
|
|
@@ -31389,9 +31885,9 @@ function isInvalidLlmsResult(fetchResult) {
|
|
|
31389
31885
|
}
|
|
31390
31886
|
|
|
31391
31887
|
// src/tools/smartfetch/secondary-model.ts
|
|
31392
|
-
import { existsSync as
|
|
31888
|
+
import { existsSync as existsSync10 } from "node:fs";
|
|
31393
31889
|
import { readFile as readFile4 } from "node:fs/promises";
|
|
31394
|
-
import
|
|
31890
|
+
import path17 from "node:path";
|
|
31395
31891
|
function parseModelRef(value) {
|
|
31396
31892
|
if (!value)
|
|
31397
31893
|
return;
|
|
@@ -31417,8 +31913,8 @@ function pickAgentModelRef(value) {
|
|
|
31417
31913
|
}
|
|
31418
31914
|
function findPreferredOpenCodeConfigPath(baseDir) {
|
|
31419
31915
|
for (const file of ["opencode.jsonc", "opencode.json"]) {
|
|
31420
|
-
const fullPath =
|
|
31421
|
-
if (
|
|
31916
|
+
const fullPath = path17.join(baseDir, file);
|
|
31917
|
+
if (existsSync10(fullPath))
|
|
31422
31918
|
return fullPath;
|
|
31423
31919
|
}
|
|
31424
31920
|
return;
|
|
@@ -31434,7 +31930,7 @@ async function readOpenCodeConfigFile(configPath) {
|
|
|
31434
31930
|
}
|
|
31435
31931
|
}
|
|
31436
31932
|
async function readEffectiveOpenCodeConfig(directory) {
|
|
31437
|
-
const projectDir =
|
|
31933
|
+
const projectDir = path17.join(directory, ".opencode");
|
|
31438
31934
|
const userDirs = getConfigSearchDirs();
|
|
31439
31935
|
const projectPath = findPreferredOpenCodeConfigPath(projectDir);
|
|
31440
31936
|
const userPath = userDirs.map((configDir) => findPreferredOpenCodeConfigPath(configDir)).find(Boolean);
|
|
@@ -31595,7 +32091,7 @@ async function runSecondaryModelWithFallback(client, directory, models, prompt,
|
|
|
31595
32091
|
// src/tools/smartfetch/tool.ts
|
|
31596
32092
|
var z5 = tool4.schema;
|
|
31597
32093
|
function createWebfetchTool(pluginCtx, options = {}) {
|
|
31598
|
-
const binaryDir = options.binaryDir ||
|
|
32094
|
+
const binaryDir = options.binaryDir || path18.join(os6.tmpdir(), "opencode-smartfetch");
|
|
31599
32095
|
return tool4({
|
|
31600
32096
|
description: WEBFETCH_DESCRIPTION,
|
|
31601
32097
|
args: {
|
|
@@ -32191,6 +32687,7 @@ var OhMyOpenCodeLite = async (ctx) => {
|
|
|
32191
32687
|
let taskSessionManagerHook;
|
|
32192
32688
|
let interviewManager;
|
|
32193
32689
|
let presetManager;
|
|
32690
|
+
let divoomManager;
|
|
32194
32691
|
let councilTools;
|
|
32195
32692
|
let webfetch;
|
|
32196
32693
|
let rewriteDisplayNameMentions;
|
|
@@ -32258,7 +32755,6 @@ var OhMyOpenCodeLite = async (ctx) => {
|
|
|
32258
32755
|
webfetch = createWebfetchTool(ctx);
|
|
32259
32756
|
multiplexerSessionManager = new MultiplexerSessionManager(ctx, multiplexerConfig);
|
|
32260
32757
|
autoUpdateChecker = createAutoUpdateCheckerHook(ctx, {
|
|
32261
|
-
showStartupToast: config.showStartupToast ?? true,
|
|
32262
32758
|
autoUpdate: config.autoUpdate ?? true
|
|
32263
32759
|
});
|
|
32264
32760
|
phaseReminderHook = createPhaseReminderHook();
|
|
@@ -32286,6 +32782,7 @@ var OhMyOpenCodeLite = async (ctx) => {
|
|
|
32286
32782
|
});
|
|
32287
32783
|
interviewManager = createInterviewManager(ctx, config);
|
|
32288
32784
|
presetManager = createPresetManager(ctx, config);
|
|
32785
|
+
divoomManager = createDivoomManager(config.divoom);
|
|
32289
32786
|
toolCount = Object.keys(councilTools).length + Object.keys(todoContinuationHook.tool).length + 1 + 2;
|
|
32290
32787
|
} catch (err) {
|
|
32291
32788
|
log("[plugin] FATAL: init failed", String(err));
|
|
@@ -32322,6 +32819,7 @@ var OhMyOpenCodeLite = async (ctx) => {
|
|
|
32322
32819
|
appLog(ctx, "warn", msg).catch(() => {});
|
|
32323
32820
|
}
|
|
32324
32821
|
});
|
|
32822
|
+
divoomManager.onPluginLoad();
|
|
32325
32823
|
return {
|
|
32326
32824
|
name: "oh-my-opencode-slim",
|
|
32327
32825
|
agent: agents,
|
|
@@ -32478,6 +32976,15 @@ var OhMyOpenCodeLite = async (ctx) => {
|
|
|
32478
32976
|
}
|
|
32479
32977
|
}
|
|
32480
32978
|
}
|
|
32979
|
+
const tuiAgentModels = {};
|
|
32980
|
+
for (const agentDef of agentDefs) {
|
|
32981
|
+
if (agentDef.name === "councillor")
|
|
32982
|
+
continue;
|
|
32983
|
+
const entry = configAgent[agentDef.name];
|
|
32984
|
+
const resolvedModel = typeof entry?.model === "string" ? entry.model : runtimeChains[agentDef.name]?.[0] ? runtimeChains[agentDef.name][0] : typeof agentDef.config.model === "string" ? agentDef.config.model : undefined;
|
|
32985
|
+
tuiAgentModels[agentDef.name] = resolvedModel ?? "default";
|
|
32986
|
+
}
|
|
32987
|
+
recordTuiAgentModels({ agentModels: tuiAgentModels });
|
|
32481
32988
|
const configMcp = opencodeConfig.mcp;
|
|
32482
32989
|
if (!configMcp) {
|
|
32483
32990
|
opencodeConfig.mcp = { ...mcps };
|
|
@@ -32521,6 +33028,15 @@ var OhMyOpenCodeLite = async (ctx) => {
|
|
|
32521
33028
|
},
|
|
32522
33029
|
event: async (input) => {
|
|
32523
33030
|
const event = input.event;
|
|
33031
|
+
if (event.type === "message.updated") {
|
|
33032
|
+
const info = event.properties?.info;
|
|
33033
|
+
if (typeof info?.agent === "string" && typeof info.providerID === "string" && typeof info.modelID === "string") {
|
|
33034
|
+
recordTuiAgentModel({
|
|
33035
|
+
agentName: resolveRuntimeAgentName(config, info.agent),
|
|
33036
|
+
model: `${info.providerID}/${info.modelID}`
|
|
33037
|
+
});
|
|
33038
|
+
}
|
|
33039
|
+
}
|
|
32524
33040
|
if (event.type === "session.created") {
|
|
32525
33041
|
const childSessionId = event.properties?.info?.id;
|
|
32526
33042
|
const parentSessionId = event.properties?.info?.parentID;
|
|
@@ -32536,6 +33052,37 @@ var OhMyOpenCodeLite = async (ctx) => {
|
|
|
32536
33052
|
await multiplexerSessionManager.onSessionDeleted(event);
|
|
32537
33053
|
await interviewManager.handleEvent(input);
|
|
32538
33054
|
await taskSessionManagerHook.event(input);
|
|
33055
|
+
if (event.type === "permission.asked" || event.type === "question.asked") {
|
|
33056
|
+
const props = event.properties;
|
|
33057
|
+
divoomManager.onUserInputRequired({
|
|
33058
|
+
sessionId: props?.sessionID,
|
|
33059
|
+
requestId: props?.id ?? props?.requestID
|
|
33060
|
+
});
|
|
33061
|
+
}
|
|
33062
|
+
if (event.type === "permission.replied" || event.type === "question.replied" || event.type === "question.rejected") {
|
|
33063
|
+
const props = event.properties;
|
|
33064
|
+
divoomManager.onUserInputResolved({
|
|
33065
|
+
sessionId: props?.sessionID,
|
|
33066
|
+
requestId: props?.requestID ?? props?.id
|
|
33067
|
+
});
|
|
33068
|
+
}
|
|
33069
|
+
if (input.event.type === "session.status") {
|
|
33070
|
+
const props = input.event.properties;
|
|
33071
|
+
const sessionID = props?.sessionID;
|
|
33072
|
+
divoomManager.onOrchestratorStatus({
|
|
33073
|
+
sessionId: sessionID,
|
|
33074
|
+
status: props?.status?.type,
|
|
33075
|
+
isOrchestrator: sessionID ? sessionAgentMap.get(sessionID) === "orchestrator" : false
|
|
33076
|
+
});
|
|
33077
|
+
}
|
|
33078
|
+
if (input.event.type === "session.deleted") {
|
|
33079
|
+
const props = input.event.properties;
|
|
33080
|
+
const sessionID = props?.info?.id ?? props?.sessionID;
|
|
33081
|
+
divoomManager.onSessionDeleted({
|
|
33082
|
+
sessionId: sessionID,
|
|
33083
|
+
isOrchestrator: sessionID ? sessionAgentMap.get(sessionID) === "orchestrator" : false
|
|
33084
|
+
});
|
|
33085
|
+
}
|
|
32539
33086
|
if (input.event.type === "session.deleted") {
|
|
32540
33087
|
const props = input.event.properties;
|
|
32541
33088
|
const sessionID = props?.info?.id ?? props?.sessionID;
|
|
@@ -32550,6 +33097,13 @@ var OhMyOpenCodeLite = async (ctx) => {
|
|
|
32550
33097
|
"tool.execute.before": async (input, output) => {
|
|
32551
33098
|
await applyPatchHook["tool.execute.before"](input, output);
|
|
32552
33099
|
await taskSessionManagerHook["tool.execute.before"](input, output);
|
|
33100
|
+
if (input.tool.toLowerCase() === "task") {
|
|
33101
|
+
divoomManager.onTaskStart({
|
|
33102
|
+
parentSessionId: input.sessionID,
|
|
33103
|
+
callId: input.callID,
|
|
33104
|
+
args: output.args
|
|
33105
|
+
});
|
|
33106
|
+
}
|
|
32553
33107
|
},
|
|
32554
33108
|
"command.execute.before": async (input, output) => {
|
|
32555
33109
|
await todoContinuationHook.handleCommandExecuteBefore(input, output);
|
|
@@ -32612,11 +33166,31 @@ ${output.system[0]}` : "");
|
|
|
32612
33166
|
await filterAvailableSkillsHook["experimental.chat.messages.transform"](input, typedOutput);
|
|
32613
33167
|
},
|
|
32614
33168
|
"tool.execute.after": async (input, output) => {
|
|
32615
|
-
|
|
32616
|
-
|
|
32617
|
-
|
|
32618
|
-
|
|
32619
|
-
|
|
33169
|
+
const meta = input;
|
|
33170
|
+
const runPostToolHook = async (name, fn) => {
|
|
33171
|
+
try {
|
|
33172
|
+
await fn();
|
|
33173
|
+
} catch (error) {
|
|
33174
|
+
log("[plugin] post-tool hook failed open", {
|
|
33175
|
+
hook: name,
|
|
33176
|
+
tool: meta.tool,
|
|
33177
|
+
sessionID: meta.sessionID,
|
|
33178
|
+
callID: meta.callID,
|
|
33179
|
+
error: error instanceof Error ? error.message : String(error)
|
|
33180
|
+
});
|
|
33181
|
+
}
|
|
33182
|
+
};
|
|
33183
|
+
await runPostToolHook("delegate-task-retry", () => delegateTaskRetryHook["tool.execute.after"](input, output));
|
|
33184
|
+
await runPostToolHook("json-error-recovery", () => jsonErrorRecoveryHook["tool.execute.after"](input, output));
|
|
33185
|
+
await runPostToolHook("todo-continuation", () => todoContinuationHook.handleToolExecuteAfter(input, output));
|
|
33186
|
+
await runPostToolHook("post-file-tool-nudge", () => postFileToolNudgeHook["tool.execute.after"](input, output));
|
|
33187
|
+
await runPostToolHook("task-session-manager", () => taskSessionManagerHook["tool.execute.after"](input, output));
|
|
33188
|
+
if (input.tool.toLowerCase() === "task") {
|
|
33189
|
+
divoomManager.onTaskEnd({
|
|
33190
|
+
parentSessionId: input.sessionID,
|
|
33191
|
+
callId: input.callID
|
|
33192
|
+
});
|
|
33193
|
+
}
|
|
32620
33194
|
}
|
|
32621
33195
|
};
|
|
32622
33196
|
};
|