oh-my-opencode-slim 1.0.6 → 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 +22 -14
- package/dist/cli/divoom.d.ts +23 -0
- package/dist/cli/doctor.d.ts +38 -0
- package/dist/cli/index.js +306 -13
- 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 -0
- 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/index.js +722 -237
- 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.d.ts +1 -0
- package/dist/tui.js +623 -11
- package/dist/utils/session.d.ts +10 -4
- package/oh-my-opencode-slim.schema.json +59 -0
- 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;
|
|
@@ -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)"),
|
|
@@ -18581,6 +18592,7 @@ var PluginConfigSchema = z2.object({
|
|
|
18581
18592
|
websearch: WebsearchConfigSchema.optional(),
|
|
18582
18593
|
interview: InterviewConfigSchema.optional(),
|
|
18583
18594
|
sessionManager: SessionManagerConfigSchema.optional(),
|
|
18595
|
+
divoom: DivoomConfigSchema.optional(),
|
|
18584
18596
|
todoContinuation: TodoContinuationConfigSchema.optional(),
|
|
18585
18597
|
fallback: FailoverConfigSchema.optional(),
|
|
18586
18598
|
council: CouncilConfigSchema.optional()
|
|
@@ -18597,20 +18609,49 @@ var PluginConfigSchema = z2.object({
|
|
|
18597
18609
|
|
|
18598
18610
|
// src/config/loader.ts
|
|
18599
18611
|
var PROMPTS_DIR_NAME = "oh-my-opencode-slim";
|
|
18600
|
-
function loadConfigFromPath(configPath) {
|
|
18612
|
+
function loadConfigFromPath(configPath, options) {
|
|
18601
18613
|
try {
|
|
18602
18614
|
const content = fs.readFileSync(configPath, "utf-8");
|
|
18603
|
-
|
|
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
|
+
}
|
|
18604
18630
|
const result = PluginConfigSchema.safeParse(rawConfig);
|
|
18605
18631
|
if (!result.success) {
|
|
18606
|
-
|
|
18607
|
-
|
|
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
|
+
}
|
|
18608
18642
|
return null;
|
|
18609
18643
|
}
|
|
18610
18644
|
return result.data;
|
|
18611
18645
|
} catch (error) {
|
|
18612
18646
|
if (error instanceof Error && "code" in error && error.code !== "ENOENT") {
|
|
18613
|
-
|
|
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
|
+
}
|
|
18614
18655
|
}
|
|
18615
18656
|
return null;
|
|
18616
18657
|
}
|
|
@@ -18635,6 +18676,26 @@ function findConfigPathInDirs(configDirs, baseName) {
|
|
|
18635
18676
|
}
|
|
18636
18677
|
return null;
|
|
18637
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
|
+
}
|
|
18638
18699
|
function deepMerge(base, override) {
|
|
18639
18700
|
if (!base)
|
|
18640
18701
|
return override;
|
|
@@ -18652,24 +18713,12 @@ function deepMerge(base, override) {
|
|
|
18652
18713
|
}
|
|
18653
18714
|
return result;
|
|
18654
18715
|
}
|
|
18655
|
-
function loadPluginConfig(directory) {
|
|
18656
|
-
const userConfigPath =
|
|
18657
|
-
|
|
18658
|
-
const
|
|
18659
|
-
let config = userConfigPath ? loadConfigFromPath(userConfigPath) ?? {} : {};
|
|
18660
|
-
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;
|
|
18661
18720
|
if (projectConfig) {
|
|
18662
|
-
config =
|
|
18663
|
-
...config,
|
|
18664
|
-
...projectConfig,
|
|
18665
|
-
agents: deepMerge(config.agents, projectConfig.agents),
|
|
18666
|
-
tmux: deepMerge(config.tmux, projectConfig.tmux),
|
|
18667
|
-
multiplexer: deepMerge(config.multiplexer, projectConfig.multiplexer),
|
|
18668
|
-
interview: deepMerge(config.interview, projectConfig.interview),
|
|
18669
|
-
sessionManager: deepMerge(config.sessionManager, projectConfig.sessionManager),
|
|
18670
|
-
fallback: deepMerge(config.fallback, projectConfig.fallback),
|
|
18671
|
-
council: deepMerge(config.council, projectConfig.council)
|
|
18672
|
-
};
|
|
18721
|
+
config = mergePluginConfigs(config, projectConfig);
|
|
18673
18722
|
}
|
|
18674
18723
|
config = migrateTmuxToMultiplexer(config);
|
|
18675
18724
|
const envPreset = process.env.OH_MY_OPENCODE_SLIM_PRESET;
|
|
@@ -18683,7 +18732,15 @@ function loadPluginConfig(directory) {
|
|
|
18683
18732
|
} else {
|
|
18684
18733
|
const presetSource = envPreset === config.preset ? "environment variable" : "config file";
|
|
18685
18734
|
const availablePresets = config.presets ? Object.keys(config.presets).join(", ") : "none";
|
|
18686
|
-
|
|
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
|
+
}
|
|
18687
18744
|
}
|
|
18688
18745
|
}
|
|
18689
18746
|
return config;
|
|
@@ -18744,6 +18801,34 @@ function getCustomAgentNames(config) {
|
|
|
18744
18801
|
});
|
|
18745
18802
|
}
|
|
18746
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
|
+
}
|
|
18747
18832
|
function shortModelLabel(model) {
|
|
18748
18833
|
return model.split("/").pop() ?? model;
|
|
18749
18834
|
}
|
|
@@ -18771,11 +18856,17 @@ async function promptWithTimeout(client, args, timeoutMs) {
|
|
|
18771
18856
|
promptPromise,
|
|
18772
18857
|
new Promise((_, reject) => {
|
|
18773
18858
|
timer = setTimeout(() => {
|
|
18774
|
-
|
|
18775
|
-
reject(new Error(`Prompt timed out after ${timeoutMs}ms`));
|
|
18859
|
+
reject(new OperationTimeoutError(`Prompt timed out after ${timeoutMs}ms`));
|
|
18776
18860
|
}, timeoutMs);
|
|
18777
18861
|
})
|
|
18778
18862
|
]);
|
|
18863
|
+
} catch (error) {
|
|
18864
|
+
if (error instanceof OperationTimeoutError) {
|
|
18865
|
+
try {
|
|
18866
|
+
await abortSessionWithTimeout(client, sessionId);
|
|
18867
|
+
} catch {}
|
|
18868
|
+
}
|
|
18869
|
+
throw error;
|
|
18779
18870
|
} finally {
|
|
18780
18871
|
clearTimeout(timer);
|
|
18781
18872
|
}
|
|
@@ -18822,7 +18913,7 @@ var AGENT_DESCRIPTIONS = {
|
|
|
18822
18913
|
- **Don't delegate when:** Know the path and need actual content • Need full file anyway • Single specific lookup • About to edit the file`,
|
|
18823
18914
|
librarian: `@librarian
|
|
18824
18915
|
- Role: Authoritative source for current library docs and API references
|
|
18825
|
-
- Permissions:
|
|
18916
|
+
- Permissions: External docs/search MCPs; no file edits
|
|
18826
18917
|
- Stats: 10x better finding up-to-date library docs than orchestrator, 1/2 cost of orchestrator
|
|
18827
18918
|
- Capabilities: Fetches latest official docs, examples, API signatures, version-specific behavior via grep_app MCP
|
|
18828
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
|
|
@@ -19207,7 +19298,8 @@ function createCouncillorAgent(model, customPrompt, customAppendPrompt) {
|
|
|
19207
19298
|
grep: "allow",
|
|
19208
19299
|
lsp: "allow",
|
|
19209
19300
|
list: "allow",
|
|
19210
|
-
codesearch: "allow"
|
|
19301
|
+
codesearch: "allow",
|
|
19302
|
+
ast_grep_search: "allow"
|
|
19211
19303
|
}
|
|
19212
19304
|
}
|
|
19213
19305
|
};
|
|
@@ -19529,10 +19621,14 @@ ${customAppendPrompt}`;
|
|
|
19529
19621
|
|
|
19530
19622
|
// src/agents/index.ts
|
|
19531
19623
|
var COUNCIL_TOOL_ALLOWED_AGENTS = new Set(["council"]);
|
|
19624
|
+
var SAFE_AGENT_ALIAS_RE = /^[a-z][a-z0-9_-]*$/i;
|
|
19532
19625
|
function normalizeDisplayName(displayName) {
|
|
19533
19626
|
const trimmed = displayName.trim();
|
|
19534
19627
|
return trimmed.startsWith("@") ? trimmed.slice(1) : trimmed;
|
|
19535
19628
|
}
|
|
19629
|
+
function isSafeDisplayName(displayName) {
|
|
19630
|
+
return SAFE_AGENT_ALIAS_RE.test(displayName);
|
|
19631
|
+
}
|
|
19536
19632
|
function escapeRegExp(value) {
|
|
19537
19633
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
19538
19634
|
}
|
|
@@ -19566,7 +19662,7 @@ function normalizeCustomAgentName(name) {
|
|
|
19566
19662
|
return name.trim();
|
|
19567
19663
|
}
|
|
19568
19664
|
function isSafeCustomAgentName(name) {
|
|
19569
|
-
return
|
|
19665
|
+
return SAFE_AGENT_ALIAS_RE.test(name) && !isKnownAgentName(name);
|
|
19570
19666
|
}
|
|
19571
19667
|
function hasCustomAgentModel(override) {
|
|
19572
19668
|
if (!override?.model) {
|
|
@@ -19626,6 +19722,9 @@ var SUBAGENT_FACTORIES = {
|
|
|
19626
19722
|
};
|
|
19627
19723
|
function createAgents(config) {
|
|
19628
19724
|
const disabled = getDisabledAgents(config);
|
|
19725
|
+
if (!config?.council) {
|
|
19726
|
+
disabled.add("council");
|
|
19727
|
+
}
|
|
19629
19728
|
const getModelForAgent = (name) => {
|
|
19630
19729
|
if (name === "fixer" && !getAgentOverride(config, "fixer")?.model) {
|
|
19631
19730
|
const librarianOverride = getAgentOverride(config, "librarian")?.model;
|
|
@@ -19712,6 +19811,9 @@ function createAgents(config) {
|
|
|
19712
19811
|
const usedDisplayNames = new Set;
|
|
19713
19812
|
for (const [, displayName] of displayNameMap) {
|
|
19714
19813
|
const normalizedDisplayName = normalizeDisplayName(displayName);
|
|
19814
|
+
if (!isSafeDisplayName(normalizedDisplayName)) {
|
|
19815
|
+
throw new Error(`displayName '${normalizedDisplayName}' must match /^[a-z][a-z0-9_-]*$/i`);
|
|
19816
|
+
}
|
|
19715
19817
|
if (usedDisplayNames.has(normalizedDisplayName)) {
|
|
19716
19818
|
throw new Error(`Duplicate displayName '${normalizedDisplayName}' assigned to multiple agents`);
|
|
19717
19819
|
}
|
|
@@ -20138,6 +20240,286 @@ class CouncilManager {
|
|
|
20138
20240
|
};
|
|
20139
20241
|
}
|
|
20140
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
|
+
|
|
20141
20523
|
// src/hooks/apply-patch/errors.ts
|
|
20142
20524
|
var APPLY_PATCH_ERROR_PREFIX = {
|
|
20143
20525
|
blocked: "apply_patch blocked",
|
|
@@ -20206,7 +20588,7 @@ function ensureApplyPatchError(error, context) {
|
|
|
20206
20588
|
|
|
20207
20589
|
// src/hooks/apply-patch/execution-context.ts
|
|
20208
20590
|
import * as fs3 from "node:fs/promises";
|
|
20209
|
-
import
|
|
20591
|
+
import path4 from "node:path";
|
|
20210
20592
|
|
|
20211
20593
|
// src/hooks/apply-patch/codec.ts
|
|
20212
20594
|
function normalizeLineEndings(text) {
|
|
@@ -21075,7 +21457,7 @@ function isMissingPathError(error) {
|
|
|
21075
21457
|
}
|
|
21076
21458
|
async function real(target) {
|
|
21077
21459
|
const parts = [];
|
|
21078
|
-
let current =
|
|
21460
|
+
let current = path4.resolve(target);
|
|
21079
21461
|
while (true) {
|
|
21080
21462
|
const exact = await fs3.realpath(current).catch((error) => {
|
|
21081
21463
|
if (isMissingPathError(error)) {
|
|
@@ -21084,19 +21466,19 @@ async function real(target) {
|
|
|
21084
21466
|
throw createApplyPatchInternalError(`Failed to resolve real path: ${current}`, error);
|
|
21085
21467
|
});
|
|
21086
21468
|
if (exact) {
|
|
21087
|
-
return parts.length === 0 ? exact :
|
|
21469
|
+
return parts.length === 0 ? exact : path4.join(exact, ...parts.reverse());
|
|
21088
21470
|
}
|
|
21089
|
-
const parent =
|
|
21471
|
+
const parent = path4.dirname(current);
|
|
21090
21472
|
if (parent === current) {
|
|
21091
|
-
return parts.length === 0 ? current :
|
|
21473
|
+
return parts.length === 0 ? current : path4.join(current, ...parts.reverse());
|
|
21092
21474
|
}
|
|
21093
|
-
parts.push(
|
|
21475
|
+
parts.push(path4.basename(current));
|
|
21094
21476
|
current = parent;
|
|
21095
21477
|
}
|
|
21096
21478
|
}
|
|
21097
21479
|
function inside(root, target) {
|
|
21098
|
-
const rel =
|
|
21099
|
-
return rel === "" || !rel.startsWith("..") && !
|
|
21480
|
+
const rel = path4.relative(root, target);
|
|
21481
|
+
return rel === "" || !rel.startsWith("..") && !path4.isAbsolute(rel);
|
|
21100
21482
|
}
|
|
21101
21483
|
function createPathGuardContext(root, worktree) {
|
|
21102
21484
|
return {
|
|
@@ -21106,7 +21488,7 @@ function createPathGuardContext(root, worktree) {
|
|
|
21106
21488
|
};
|
|
21107
21489
|
}
|
|
21108
21490
|
async function realCached(ctx, target) {
|
|
21109
|
-
const resolvedTarget =
|
|
21491
|
+
const resolvedTarget = path4.resolve(target);
|
|
21110
21492
|
let pending = ctx.realCache.get(resolvedTarget);
|
|
21111
21493
|
if (!pending) {
|
|
21112
21494
|
pending = real(resolvedTarget);
|
|
@@ -21157,22 +21539,22 @@ async function assertRegularFile(ctx, filePath, verb) {
|
|
|
21157
21539
|
function collectPatchTargets(root, hunks) {
|
|
21158
21540
|
const targets = new Set;
|
|
21159
21541
|
for (const hunk of hunks) {
|
|
21160
|
-
targets.add(
|
|
21542
|
+
targets.add(path4.resolve(root, hunk.path));
|
|
21161
21543
|
if (hunk.type === "update" && hunk.move_path) {
|
|
21162
|
-
targets.add(
|
|
21544
|
+
targets.add(path4.resolve(root, hunk.move_path));
|
|
21163
21545
|
}
|
|
21164
21546
|
}
|
|
21165
21547
|
return [...targets];
|
|
21166
21548
|
}
|
|
21167
21549
|
function toRelativePatchPath(root, target) {
|
|
21168
|
-
const relative =
|
|
21550
|
+
const relative = path4.relative(root, target);
|
|
21169
21551
|
return (relative.length === 0 ? "." : relative).replaceAll("\\", "/");
|
|
21170
21552
|
}
|
|
21171
21553
|
function normalizePatchPath(root, value) {
|
|
21172
|
-
return
|
|
21554
|
+
return path4.isAbsolute(value) ? toRelativePatchPath(root, path4.resolve(value)) : value;
|
|
21173
21555
|
}
|
|
21174
21556
|
function normalizePatchPaths(root, hunks) {
|
|
21175
|
-
const resolvedRoot =
|
|
21557
|
+
const resolvedRoot = path4.resolve(root);
|
|
21176
21558
|
const normalized = [];
|
|
21177
21559
|
let changed = false;
|
|
21178
21560
|
for (const hunk of hunks) {
|
|
@@ -21296,7 +21678,7 @@ function stageAddedText(contents) {
|
|
|
21296
21678
|
`;
|
|
21297
21679
|
}
|
|
21298
21680
|
// src/hooks/apply-patch/rewrite.ts
|
|
21299
|
-
import
|
|
21681
|
+
import path5 from "node:path";
|
|
21300
21682
|
function normalizeTextLineEndings(text) {
|
|
21301
21683
|
return text.replace(/\r\n/g, `
|
|
21302
21684
|
`).replace(/\r/g, `
|
|
@@ -21453,7 +21835,7 @@ async function rewritePatch(root, patchText, cfg, worktree) {
|
|
|
21453
21835
|
const dependencyGroups = new Map;
|
|
21454
21836
|
for (const hunk of hunks) {
|
|
21455
21837
|
if (hunk.type === "add") {
|
|
21456
|
-
const filePath2 =
|
|
21838
|
+
const filePath2 = path5.resolve(root, hunk.path);
|
|
21457
21839
|
await assertPreparedPathMissing(filePath2, "add");
|
|
21458
21840
|
rewritten.push(hunk);
|
|
21459
21841
|
clearDependencyGroup(filePath2);
|
|
@@ -21475,20 +21857,20 @@ async function rewritePatch(root, patchText, cfg, worktree) {
|
|
|
21475
21857
|
continue;
|
|
21476
21858
|
}
|
|
21477
21859
|
if (hunk.type === "delete") {
|
|
21478
|
-
const filePath2 =
|
|
21860
|
+
const filePath2 = path5.resolve(root, hunk.path);
|
|
21479
21861
|
await getPreparedFileState(filePath2, "delete");
|
|
21480
21862
|
clearDependencyGroup(filePath2);
|
|
21481
21863
|
rewritten.push(hunk);
|
|
21482
21864
|
staged.set(filePath2, { exists: false, derived: true });
|
|
21483
21865
|
continue;
|
|
21484
21866
|
}
|
|
21485
|
-
const filePath =
|
|
21867
|
+
const filePath = path5.resolve(root, hunk.path);
|
|
21486
21868
|
const currentDependency = dependencyGroups.get(filePath);
|
|
21487
21869
|
const current = await getPreparedFileState(filePath, "update");
|
|
21488
21870
|
if (!current.exists) {
|
|
21489
21871
|
throw createApplyPatchVerificationError(`Failed to read file to update: ${filePath}`);
|
|
21490
21872
|
}
|
|
21491
|
-
const movePath = hunk.move_path ?
|
|
21873
|
+
const movePath = hunk.move_path ? path5.resolve(root, hunk.move_path) : undefined;
|
|
21492
21874
|
if (movePath && movePath !== filePath) {
|
|
21493
21875
|
await assertPreparedPathMissing(movePath, "move");
|
|
21494
21876
|
}
|
|
@@ -21682,32 +22064,32 @@ function crossSpawn(command, options) {
|
|
|
21682
22064
|
}
|
|
21683
22065
|
};
|
|
21684
22066
|
}
|
|
21685
|
-
async function crossWrite(
|
|
21686
|
-
await fsWriteFile(
|
|
22067
|
+
async function crossWrite(path6, data) {
|
|
22068
|
+
await fsWriteFile(path6, Buffer.from(data));
|
|
21687
22069
|
}
|
|
21688
22070
|
|
|
21689
22071
|
// src/hooks/auto-update-checker/cache.ts
|
|
21690
22072
|
import * as fs5 from "node:fs";
|
|
21691
|
-
import * as
|
|
22073
|
+
import * as path8 from "node:path";
|
|
21692
22074
|
// src/hooks/auto-update-checker/checker.ts
|
|
21693
22075
|
import * as fs4 from "node:fs";
|
|
21694
|
-
import * as
|
|
21695
|
-
import { fileURLToPath } from "node:url";
|
|
22076
|
+
import * as path7 from "node:path";
|
|
22077
|
+
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
21696
22078
|
|
|
21697
22079
|
// src/hooks/auto-update-checker/constants.ts
|
|
21698
|
-
import * as
|
|
21699
|
-
import * as
|
|
22080
|
+
import * as os3 from "node:os";
|
|
22081
|
+
import * as path6 from "node:path";
|
|
21700
22082
|
var PACKAGE_NAME = "oh-my-opencode-slim";
|
|
21701
22083
|
var NPM_REGISTRY_URL = `https://registry.npmjs.org/-/package/${PACKAGE_NAME}/dist-tags`;
|
|
21702
22084
|
var NPM_FETCH_TIMEOUT = 5000;
|
|
21703
22085
|
function getCacheDir() {
|
|
21704
22086
|
if (process.platform === "win32") {
|
|
21705
|
-
return
|
|
22087
|
+
return path6.join(process.env.LOCALAPPDATA ?? os3.homedir(), "opencode");
|
|
21706
22088
|
}
|
|
21707
|
-
return
|
|
22089
|
+
return path6.join(os3.homedir(), ".cache", "opencode");
|
|
21708
22090
|
}
|
|
21709
22091
|
var CACHE_DIR = getCacheDir();
|
|
21710
|
-
var INSTALLED_PACKAGE_JSON =
|
|
22092
|
+
var INSTALLED_PACKAGE_JSON = path6.join(CACHE_DIR, "node_modules", PACKAGE_NAME, "package.json");
|
|
21711
22093
|
var configPaths = getOpenCodeConfigPaths();
|
|
21712
22094
|
var USER_OPENCODE_CONFIG = configPaths[0];
|
|
21713
22095
|
var USER_OPENCODE_CONFIG_JSONC = configPaths[1];
|
|
@@ -21742,8 +22124,8 @@ function extractChannel(version) {
|
|
|
21742
22124
|
}
|
|
21743
22125
|
function getConfigPaths(directory) {
|
|
21744
22126
|
return [
|
|
21745
|
-
|
|
21746
|
-
|
|
22127
|
+
path7.join(directory, ".opencode", "opencode.json"),
|
|
22128
|
+
path7.join(directory, ".opencode", "opencode.jsonc"),
|
|
21747
22129
|
USER_OPENCODE_CONFIG,
|
|
21748
22130
|
USER_OPENCODE_CONFIG_JSONC
|
|
21749
22131
|
];
|
|
@@ -21759,7 +22141,7 @@ function getLocalDevPath(directory) {
|
|
|
21759
22141
|
for (const entry of plugins) {
|
|
21760
22142
|
if (entry.startsWith("file://") && entry.includes(PACKAGE_NAME)) {
|
|
21761
22143
|
try {
|
|
21762
|
-
return
|
|
22144
|
+
return fileURLToPath2(entry);
|
|
21763
22145
|
} catch {
|
|
21764
22146
|
return entry.replace("file://", "");
|
|
21765
22147
|
}
|
|
@@ -21772,9 +22154,9 @@ function getLocalDevPath(directory) {
|
|
|
21772
22154
|
function findPackageJsonUp(startPath) {
|
|
21773
22155
|
try {
|
|
21774
22156
|
const stat2 = fs4.statSync(startPath);
|
|
21775
|
-
let dir = stat2.isDirectory() ? startPath :
|
|
22157
|
+
let dir = stat2.isDirectory() ? startPath : path7.dirname(startPath);
|
|
21776
22158
|
for (let i = 0;i < 10; i++) {
|
|
21777
|
-
const pkgPath =
|
|
22159
|
+
const pkgPath = path7.join(dir, "package.json");
|
|
21778
22160
|
if (fs4.existsSync(pkgPath)) {
|
|
21779
22161
|
try {
|
|
21780
22162
|
const content = fs4.readFileSync(pkgPath, "utf-8");
|
|
@@ -21783,7 +22165,7 @@ function findPackageJsonUp(startPath) {
|
|
|
21783
22165
|
return pkgPath;
|
|
21784
22166
|
} catch {}
|
|
21785
22167
|
}
|
|
21786
|
-
const parent =
|
|
22168
|
+
const parent = path7.dirname(dir);
|
|
21787
22169
|
if (parent === dir)
|
|
21788
22170
|
break;
|
|
21789
22171
|
dir = parent;
|
|
@@ -21808,7 +22190,7 @@ function getLocalDevVersion(directory) {
|
|
|
21808
22190
|
}
|
|
21809
22191
|
function getCurrentRuntimePackageJsonPath(currentModuleUrl = import.meta.url) {
|
|
21810
22192
|
try {
|
|
21811
|
-
const currentDir =
|
|
22193
|
+
const currentDir = path7.dirname(fileURLToPath2(currentModuleUrl));
|
|
21812
22194
|
return findPackageJsonUp(currentDir);
|
|
21813
22195
|
} catch (err) {
|
|
21814
22196
|
log("[auto-update-checker] Failed to resolve runtime package path:", err);
|
|
@@ -21892,7 +22274,7 @@ async function getLatestVersion(channel = "latest") {
|
|
|
21892
22274
|
|
|
21893
22275
|
// src/hooks/auto-update-checker/cache.ts
|
|
21894
22276
|
function removeFromBunLock(installDir, packageName) {
|
|
21895
|
-
const lockPath =
|
|
22277
|
+
const lockPath = path8.join(installDir, "bun.lock");
|
|
21896
22278
|
if (!fs5.existsSync(lockPath))
|
|
21897
22279
|
return false;
|
|
21898
22280
|
try {
|
|
@@ -21943,7 +22325,7 @@ function ensureDependencyVersion(packageJsonPath, packageName, version) {
|
|
|
21943
22325
|
}
|
|
21944
22326
|
}
|
|
21945
22327
|
function removeInstalledPackage(installDir, packageName) {
|
|
21946
|
-
const pkgDir =
|
|
22328
|
+
const pkgDir = path8.join(installDir, "node_modules", packageName);
|
|
21947
22329
|
if (!fs5.existsSync(pkgDir))
|
|
21948
22330
|
return false;
|
|
21949
22331
|
fs5.rmSync(pkgDir, { recursive: true, force: true });
|
|
@@ -21952,18 +22334,18 @@ function removeInstalledPackage(installDir, packageName) {
|
|
|
21952
22334
|
}
|
|
21953
22335
|
function resolveInstallContext(runtimePackageJsonPath = getCurrentRuntimePackageJsonPath()) {
|
|
21954
22336
|
if (runtimePackageJsonPath) {
|
|
21955
|
-
const packageDir =
|
|
21956
|
-
const nodeModulesDir =
|
|
21957
|
-
if (
|
|
21958
|
-
const installDir =
|
|
21959
|
-
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");
|
|
21960
22342
|
if (fs5.existsSync(packageJsonPath)) {
|
|
21961
22343
|
return { installDir, packageJsonPath };
|
|
21962
22344
|
}
|
|
21963
22345
|
}
|
|
21964
22346
|
return null;
|
|
21965
22347
|
}
|
|
21966
|
-
const legacyPackageJsonPath =
|
|
22348
|
+
const legacyPackageJsonPath = path8.join(CACHE_DIR, "package.json");
|
|
21967
22349
|
if (fs5.existsSync(legacyPackageJsonPath)) {
|
|
21968
22350
|
return { installDir: CACHE_DIR, packageJsonPath: legacyPackageJsonPath };
|
|
21969
22351
|
}
|
|
@@ -22097,7 +22479,7 @@ function showToast(ctx, title, message, variant = "info", duration = 3000) {
|
|
|
22097
22479
|
}).catch(() => {});
|
|
22098
22480
|
}
|
|
22099
22481
|
// src/utils/agent-variant.ts
|
|
22100
|
-
function
|
|
22482
|
+
function normalizeAgentName2(agentName) {
|
|
22101
22483
|
const trimmed = agentName.trim();
|
|
22102
22484
|
return trimmed.startsWith("@") ? trimmed.slice(1) : trimmed;
|
|
22103
22485
|
}
|
|
@@ -22109,7 +22491,7 @@ function getRuntimeAgentNames(config) {
|
|
|
22109
22491
|
return [...unique];
|
|
22110
22492
|
}
|
|
22111
22493
|
function resolveRuntimeAgentName(config, agentName) {
|
|
22112
|
-
const normalized =
|
|
22494
|
+
const normalized = normalizeAgentName2(agentName);
|
|
22113
22495
|
if (!normalized) {
|
|
22114
22496
|
return normalized;
|
|
22115
22497
|
}
|
|
@@ -22121,7 +22503,7 @@ function resolveRuntimeAgentName(config, agentName) {
|
|
|
22121
22503
|
if (!displayName) {
|
|
22122
22504
|
continue;
|
|
22123
22505
|
}
|
|
22124
|
-
if (
|
|
22506
|
+
if (normalizeAgentName2(displayName) === normalized) {
|
|
22125
22507
|
return internalName;
|
|
22126
22508
|
}
|
|
22127
22509
|
}
|
|
@@ -22137,7 +22519,7 @@ function createDisplayNameMentionRewriter(config) {
|
|
|
22137
22519
|
if (!displayName) {
|
|
22138
22520
|
continue;
|
|
22139
22521
|
}
|
|
22140
|
-
const normalizedDisplayName =
|
|
22522
|
+
const normalizedDisplayName = normalizeAgentName2(displayName);
|
|
22141
22523
|
if (!normalizedDisplayName || normalizedDisplayName === internalName) {
|
|
22142
22524
|
continue;
|
|
22143
22525
|
}
|
|
@@ -22447,8 +22829,8 @@ function isPwshAvailable() {
|
|
|
22447
22829
|
});
|
|
22448
22830
|
return result.status === 0;
|
|
22449
22831
|
}
|
|
22450
|
-
function escapePowerShellPath(
|
|
22451
|
-
return
|
|
22832
|
+
function escapePowerShellPath(path9) {
|
|
22833
|
+
return path9.replace(/'/g, "''");
|
|
22452
22834
|
}
|
|
22453
22835
|
function getWindowsZipExtractor() {
|
|
22454
22836
|
const buildNumber = getWindowsBuildNumber();
|
|
@@ -22777,6 +23159,7 @@ function parseModel(model) {
|
|
|
22777
23159
|
return { providerID: model.slice(0, slash), modelID: model.slice(slash + 1) };
|
|
22778
23160
|
}
|
|
22779
23161
|
var DEDUP_WINDOW_MS = 5000;
|
|
23162
|
+
var REPROMPT_DELAY_MS = 500;
|
|
22780
23163
|
|
|
22781
23164
|
class ForegroundFallbackManager {
|
|
22782
23165
|
client;
|
|
@@ -22909,11 +23292,20 @@ class ForegroundFallbackManager {
|
|
|
22909
23292
|
log("[foreground-fallback] no user message found", { sessionID });
|
|
22910
23293
|
return;
|
|
22911
23294
|
}
|
|
22912
|
-
try {
|
|
22913
|
-
await this.client.session.abort({ path: { id: sessionID } });
|
|
22914
|
-
} catch {}
|
|
22915
|
-
await new Promise((r) => setTimeout(r, 500));
|
|
22916
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));
|
|
22917
23309
|
await sessionClient.promptAsync({
|
|
22918
23310
|
path: { id: sessionID },
|
|
22919
23311
|
body: { parts: lastUser.parts, model: ref }
|
|
@@ -22960,8 +23352,8 @@ class ForegroundFallbackManager {
|
|
|
22960
23352
|
// src/hooks/image-hook.ts
|
|
22961
23353
|
import { createHash } from "node:crypto";
|
|
22962
23354
|
import {
|
|
22963
|
-
existsSync as
|
|
22964
|
-
mkdirSync as
|
|
23355
|
+
existsSync as existsSync5,
|
|
23356
|
+
mkdirSync as mkdirSync3,
|
|
22965
23357
|
readdirSync as readdirSync2,
|
|
22966
23358
|
rmdirSync,
|
|
22967
23359
|
statSync as statSync3,
|
|
@@ -23056,7 +23448,7 @@ function writeUniqueFile(dir, name, data, log2) {
|
|
|
23056
23448
|
const ext = extname(name);
|
|
23057
23449
|
const base = basename2(name, ext) || name;
|
|
23058
23450
|
let candidate = join7(dir, name);
|
|
23059
|
-
if (
|
|
23451
|
+
if (existsSync5(candidate)) {
|
|
23060
23452
|
return candidate;
|
|
23061
23453
|
}
|
|
23062
23454
|
let counter = 0;
|
|
@@ -23094,14 +23486,14 @@ function processImageAttachments(args) {
|
|
|
23094
23486
|
}
|
|
23095
23487
|
const saveDir = join7(workDir, ".opencode", "images");
|
|
23096
23488
|
if (messagesWithImages.length === 0) {
|
|
23097
|
-
if (
|
|
23489
|
+
if (existsSync5(saveDir))
|
|
23098
23490
|
cleanupAllSessions(saveDir);
|
|
23099
23491
|
return;
|
|
23100
23492
|
}
|
|
23101
23493
|
const gitignorePath = join7(workDir, ".opencode", ".gitignore");
|
|
23102
23494
|
try {
|
|
23103
|
-
|
|
23104
|
-
if (!
|
|
23495
|
+
mkdirSync3(saveDir, { recursive: true });
|
|
23496
|
+
if (!existsSync5(gitignorePath))
|
|
23105
23497
|
writeFileSync3(gitignorePath, `*
|
|
23106
23498
|
`);
|
|
23107
23499
|
} catch (e) {
|
|
@@ -23112,7 +23504,7 @@ function processImageAttachments(args) {
|
|
|
23112
23504
|
const sessionSubdir = msg.info.sessionID ? sanitizeFilename(msg.info.sessionID) : undefined;
|
|
23113
23505
|
const targetDir = sessionSubdir ? join7(saveDir, sessionSubdir) : saveDir;
|
|
23114
23506
|
try {
|
|
23115
|
-
|
|
23507
|
+
mkdirSync3(targetDir, { recursive: true });
|
|
23116
23508
|
} catch (e) {
|
|
23117
23509
|
log2(`[image-hook] failed to create target image directory: ${e}`);
|
|
23118
23510
|
}
|
|
@@ -23271,7 +23663,7 @@ function createPostFileToolNudgeHook(options = {}) {
|
|
|
23271
23663
|
};
|
|
23272
23664
|
}
|
|
23273
23665
|
// src/hooks/task-session-manager/index.ts
|
|
23274
|
-
import
|
|
23666
|
+
import path9 from "node:path";
|
|
23275
23667
|
var AGENT_NAME_SET = new Set([
|
|
23276
23668
|
"orchestrator",
|
|
23277
23669
|
"oracle",
|
|
@@ -23296,8 +23688,8 @@ function extractPath(output) {
|
|
|
23296
23688
|
return /<path>([^<]+)<\/path>/.exec(output)?.[1];
|
|
23297
23689
|
}
|
|
23298
23690
|
function normalizePath(root, file) {
|
|
23299
|
-
const relative =
|
|
23300
|
-
if (!relative || relative.startsWith("..") ||
|
|
23691
|
+
const relative = path9.relative(root, file);
|
|
23692
|
+
if (!relative || relative.startsWith("..") || path9.isAbsolute(relative)) {
|
|
23301
23693
|
return file;
|
|
23302
23694
|
}
|
|
23303
23695
|
return relative;
|
|
@@ -23689,6 +24081,7 @@ function createTodoHygiene(options) {
|
|
|
23689
24081
|
// src/hooks/todo-continuation/index.ts
|
|
23690
24082
|
var HOOK_NAME = "todo-continuation";
|
|
23691
24083
|
var COMMAND_NAME = "auto-continue";
|
|
24084
|
+
var TODO_STATE_TIMEOUT_MS = 500;
|
|
23692
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.]";
|
|
23693
24086
|
var TODO_HYGIENE_INSTRUCTION_OPEN = '<instruction name="todo_hygiene">';
|
|
23694
24087
|
var TODO_HYGIENE_INSTRUCTION_CLOSE = "</instruction>";
|
|
@@ -23776,12 +24169,15 @@ function createTodoContinuationHook(ctx, config) {
|
|
|
23776
24169
|
notifyingSessionIds: new Set,
|
|
23777
24170
|
notificationBusyUntilBySession: new Map
|
|
23778
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
|
+
}
|
|
23779
24178
|
const hygiene = createTodoHygiene({
|
|
23780
24179
|
getTodoState: async (sessionID) => {
|
|
23781
|
-
const
|
|
23782
|
-
path: { id: sessionID }
|
|
23783
|
-
});
|
|
23784
|
-
const todos = result.data;
|
|
24180
|
+
const todos = await fetchTodos(sessionID);
|
|
23785
24181
|
const openTodos = todos.filter((todo) => !TERMINAL_TODO_STATUSES.includes(todo.status));
|
|
23786
24182
|
return {
|
|
23787
24183
|
hasOpenTodos: openTodos.length > 0,
|
|
@@ -23966,10 +24362,7 @@ function createTodoContinuationHook(ctx, config) {
|
|
|
23966
24362
|
}
|
|
23967
24363
|
if (autoEnable && !state.enabled) {
|
|
23968
24364
|
try {
|
|
23969
|
-
const
|
|
23970
|
-
path: { id: sessionID }
|
|
23971
|
-
});
|
|
23972
|
-
const todos = todosResult.data;
|
|
24365
|
+
const todos = await fetchTodos(sessionID);
|
|
23973
24366
|
const incompleteCount2 = todos.filter((t) => !TERMINAL_TODO_STATUSES.includes(t.status)).length;
|
|
23974
24367
|
if (incompleteCount2 >= autoEnableThreshold) {
|
|
23975
24368
|
state.enabled = true;
|
|
@@ -23995,10 +24388,7 @@ function createTodoContinuationHook(ctx, config) {
|
|
|
23995
24388
|
let hasIncompleteTodos = false;
|
|
23996
24389
|
let incompleteCount = 0;
|
|
23997
24390
|
try {
|
|
23998
|
-
const
|
|
23999
|
-
path: { id: sessionID }
|
|
24000
|
-
});
|
|
24001
|
-
const todos = todosResult.data;
|
|
24391
|
+
const todos = await fetchTodos(sessionID);
|
|
24002
24392
|
incompleteCount = todos.filter((t) => !TERMINAL_TODO_STATUSES.includes(t.status)).length;
|
|
24003
24393
|
hasIncompleteTodos = incompleteCount > 0;
|
|
24004
24394
|
log(`[${HOOK_NAME}] Fetched todos`, {
|
|
@@ -24209,10 +24599,7 @@ function createTodoContinuationHook(ctx, config) {
|
|
|
24209
24599
|
});
|
|
24210
24600
|
let hasIncompleteTodos = false;
|
|
24211
24601
|
try {
|
|
24212
|
-
const
|
|
24213
|
-
path: { id: input.sessionID }
|
|
24214
|
-
});
|
|
24215
|
-
const todos = todosResult.data;
|
|
24602
|
+
const todos = await fetchTodos(input.sessionID);
|
|
24216
24603
|
hasIncompleteTodos = todos.some((t) => !TERMINAL_TODO_STATUSES.includes(t.status));
|
|
24217
24604
|
} catch (error) {
|
|
24218
24605
|
log(`[${HOOK_NAME}] Warning: failed to fetch todos in command hook`, {
|
|
@@ -24236,7 +24623,7 @@ function createTodoContinuationHook(ctx, config) {
|
|
|
24236
24623
|
};
|
|
24237
24624
|
}
|
|
24238
24625
|
// src/interview/manager.ts
|
|
24239
|
-
import
|
|
24626
|
+
import path13 from "node:path";
|
|
24240
24627
|
|
|
24241
24628
|
// src/interview/dashboard.ts
|
|
24242
24629
|
import crypto from "node:crypto";
|
|
@@ -24245,28 +24632,28 @@ import fs7 from "node:fs/promises";
|
|
|
24245
24632
|
import {
|
|
24246
24633
|
createServer
|
|
24247
24634
|
} from "node:http";
|
|
24248
|
-
import
|
|
24249
|
-
import
|
|
24635
|
+
import os4 from "node:os";
|
|
24636
|
+
import path11 from "node:path";
|
|
24250
24637
|
import { URL as URL2 } from "node:url";
|
|
24251
24638
|
|
|
24252
24639
|
// src/interview/document.ts
|
|
24253
24640
|
import * as fsSync from "node:fs";
|
|
24254
24641
|
import * as fs6 from "node:fs/promises";
|
|
24255
|
-
import * as
|
|
24642
|
+
import * as path10 from "node:path";
|
|
24256
24643
|
var DEFAULT_OUTPUT_FOLDER = "interview";
|
|
24257
24644
|
function normalizeOutputFolder(outputFolder) {
|
|
24258
24645
|
const normalized = outputFolder.trim().replace(/^\/+|\/+$/g, "");
|
|
24259
24646
|
return normalized || DEFAULT_OUTPUT_FOLDER;
|
|
24260
24647
|
}
|
|
24261
24648
|
function createInterviewDirectoryPath(directory, outputFolder) {
|
|
24262
|
-
return
|
|
24649
|
+
return path10.join(directory, normalizeOutputFolder(outputFolder));
|
|
24263
24650
|
}
|
|
24264
24651
|
function createInterviewFilePath(directory, outputFolder, idea) {
|
|
24265
24652
|
const fileName = `${slugify(idea) || "interview"}.md`;
|
|
24266
|
-
return
|
|
24653
|
+
return path10.join(createInterviewDirectoryPath(directory, outputFolder), fileName);
|
|
24267
24654
|
}
|
|
24268
24655
|
function relativeInterviewPath(directory, filePath) {
|
|
24269
|
-
return
|
|
24656
|
+
return path10.relative(directory, filePath) || path10.basename(filePath);
|
|
24270
24657
|
}
|
|
24271
24658
|
function resolveExistingInterviewPath(directory, outputFolder, value) {
|
|
24272
24659
|
const trimmed = value.trim();
|
|
@@ -24275,22 +24662,22 @@ function resolveExistingInterviewPath(directory, outputFolder, value) {
|
|
|
24275
24662
|
}
|
|
24276
24663
|
const outputDir = createInterviewDirectoryPath(directory, outputFolder);
|
|
24277
24664
|
const candidates = new Set;
|
|
24278
|
-
const resolvedRoot =
|
|
24279
|
-
if (
|
|
24665
|
+
const resolvedRoot = path10.resolve(directory);
|
|
24666
|
+
if (path10.isAbsolute(trimmed)) {
|
|
24280
24667
|
candidates.add(trimmed);
|
|
24281
24668
|
} else {
|
|
24282
|
-
candidates.add(
|
|
24283
|
-
candidates.add(
|
|
24669
|
+
candidates.add(path10.resolve(directory, trimmed));
|
|
24670
|
+
candidates.add(path10.join(outputDir, trimmed));
|
|
24284
24671
|
if (!trimmed.endsWith(".md")) {
|
|
24285
|
-
candidates.add(
|
|
24672
|
+
candidates.add(path10.join(outputDir, `${trimmed}.md`));
|
|
24286
24673
|
}
|
|
24287
24674
|
}
|
|
24288
24675
|
for (const candidate of candidates) {
|
|
24289
|
-
if (
|
|
24676
|
+
if (path10.extname(candidate) !== ".md") {
|
|
24290
24677
|
continue;
|
|
24291
24678
|
}
|
|
24292
|
-
const resolved =
|
|
24293
|
-
if (!resolved.startsWith(resolvedRoot +
|
|
24679
|
+
const resolved = path10.resolve(candidate);
|
|
24680
|
+
if (!resolved.startsWith(resolvedRoot + path10.sep) && resolved !== resolvedRoot) {
|
|
24294
24681
|
continue;
|
|
24295
24682
|
}
|
|
24296
24683
|
if (fsSync.existsSync(candidate)) {
|
|
@@ -24370,7 +24757,7 @@ function parseFrontmatter(content) {
|
|
|
24370
24757
|
return result;
|
|
24371
24758
|
}
|
|
24372
24759
|
async function ensureInterviewFile(record) {
|
|
24373
|
-
await fs6.mkdir(
|
|
24760
|
+
await fs6.mkdir(path10.dirname(record.markdownPath), { recursive: true });
|
|
24374
24761
|
try {
|
|
24375
24762
|
await fs6.access(record.markdownPath);
|
|
24376
24763
|
} catch {
|
|
@@ -26040,12 +26427,12 @@ function renderInterviewPage(interviewId, resumeSlug) {
|
|
|
26040
26427
|
|
|
26041
26428
|
// src/interview/dashboard.ts
|
|
26042
26429
|
function getAuthFilePath(port) {
|
|
26043
|
-
const dataHome = process.env.XDG_DATA_HOME ||
|
|
26044
|
-
return
|
|
26430
|
+
const dataHome = process.env.XDG_DATA_HOME || path11.join(os4.homedir(), ".local", "share");
|
|
26431
|
+
return path11.join(dataHome, "opencode", `.dashboard-${port}.json`);
|
|
26045
26432
|
}
|
|
26046
26433
|
function writeAuthFile(port, token) {
|
|
26047
26434
|
const filePath = getAuthFilePath(port);
|
|
26048
|
-
const dir =
|
|
26435
|
+
const dir = path11.dirname(filePath);
|
|
26049
26436
|
try {
|
|
26050
26437
|
fsSync2.mkdirSync(dir, { recursive: true });
|
|
26051
26438
|
} catch {}
|
|
@@ -26142,7 +26529,7 @@ function createDashboardServer(config) {
|
|
|
26142
26529
|
let scanDays = 30;
|
|
26143
26530
|
function getKnownDirectories() {
|
|
26144
26531
|
const dirs = new Set;
|
|
26145
|
-
dirs.add(
|
|
26532
|
+
dirs.add(os4.homedir());
|
|
26146
26533
|
for (const session2 of sessions.values()) {
|
|
26147
26534
|
if (session2.directory)
|
|
26148
26535
|
dirs.add(session2.directory);
|
|
@@ -26182,7 +26569,7 @@ function createDashboardServer(config) {
|
|
|
26182
26569
|
const directories = getKnownDirectories();
|
|
26183
26570
|
const items = [];
|
|
26184
26571
|
for (const dir of directories) {
|
|
26185
|
-
const interviewDir =
|
|
26572
|
+
const interviewDir = path11.join(dir, config.outputFolder);
|
|
26186
26573
|
let entries;
|
|
26187
26574
|
try {
|
|
26188
26575
|
entries = await fs7.readdir(interviewDir);
|
|
@@ -26194,7 +26581,7 @@ function createDashboardServer(config) {
|
|
|
26194
26581
|
continue;
|
|
26195
26582
|
let content;
|
|
26196
26583
|
try {
|
|
26197
|
-
content = await fs7.readFile(
|
|
26584
|
+
content = await fs7.readFile(path11.join(interviewDir, entry), "utf8");
|
|
26198
26585
|
} catch {
|
|
26199
26586
|
continue;
|
|
26200
26587
|
}
|
|
@@ -26220,7 +26607,7 @@ function createDashboardServer(config) {
|
|
|
26220
26607
|
const directories = getKnownDirectories();
|
|
26221
26608
|
let rebuilt = 0;
|
|
26222
26609
|
for (const dir of directories) {
|
|
26223
|
-
const interviewDir =
|
|
26610
|
+
const interviewDir = path11.join(dir, config.outputFolder);
|
|
26224
26611
|
let entries;
|
|
26225
26612
|
try {
|
|
26226
26613
|
entries = await fs7.readdir(interviewDir);
|
|
@@ -26232,7 +26619,7 @@ function createDashboardServer(config) {
|
|
|
26232
26619
|
continue;
|
|
26233
26620
|
let content;
|
|
26234
26621
|
try {
|
|
26235
|
-
content = await fs7.readFile(
|
|
26622
|
+
content = await fs7.readFile(path11.join(interviewDir, entry), "utf8");
|
|
26236
26623
|
} catch {
|
|
26237
26624
|
continue;
|
|
26238
26625
|
}
|
|
@@ -26258,7 +26645,7 @@ function createDashboardServer(config) {
|
|
|
26258
26645
|
questions: [],
|
|
26259
26646
|
pendingAnswers: null,
|
|
26260
26647
|
lastUpdatedAt: fm.updatedAt ? new Date(fm.updatedAt).getTime() : Date.now(),
|
|
26261
|
-
filePath:
|
|
26648
|
+
filePath: path11.join(interviewDir, entry),
|
|
26262
26649
|
nudgeAction: null
|
|
26263
26650
|
});
|
|
26264
26651
|
if (!sessions.has(fm.sessionID)) {
|
|
@@ -26522,7 +26909,7 @@ function createDashboardServer(config) {
|
|
|
26522
26909
|
const dirs = getKnownDirectories();
|
|
26523
26910
|
for (const dir of dirs) {
|
|
26524
26911
|
const slug = extractResumeSlug(interviewId);
|
|
26525
|
-
const candidate =
|
|
26912
|
+
const candidate = path11.join(dir, config.outputFolder, `${slug}.md`);
|
|
26526
26913
|
try {
|
|
26527
26914
|
document = await fs7.readFile(candidate, "utf8");
|
|
26528
26915
|
markdownPath = candidate;
|
|
@@ -27048,9 +27435,9 @@ function createInterviewServer(deps) {
|
|
|
27048
27435
|
}
|
|
27049
27436
|
|
|
27050
27437
|
// src/interview/service.ts
|
|
27051
|
-
import { spawn } from "node:child_process";
|
|
27438
|
+
import { spawn as spawn2 } from "node:child_process";
|
|
27052
27439
|
import * as fs8 from "node:fs/promises";
|
|
27053
|
-
import * as
|
|
27440
|
+
import * as path12 from "node:path";
|
|
27054
27441
|
|
|
27055
27442
|
// src/interview/types.ts
|
|
27056
27443
|
import { z as z3 } from "zod";
|
|
@@ -27252,7 +27639,7 @@ function openBrowser(url) {
|
|
|
27252
27639
|
args = [url];
|
|
27253
27640
|
}
|
|
27254
27641
|
try {
|
|
27255
|
-
const child =
|
|
27642
|
+
const child = spawn2(command, args, { detached: true, stdio: "ignore" });
|
|
27256
27643
|
child.on("error", (error) => {
|
|
27257
27644
|
log("[interview] failed to open browser:", { error: error.message, url });
|
|
27258
27645
|
});
|
|
@@ -27317,12 +27704,12 @@ function createInterviewService(ctx, config, deps) {
|
|
|
27317
27704
|
if (!newSlug) {
|
|
27318
27705
|
return;
|
|
27319
27706
|
}
|
|
27320
|
-
const currentFileName =
|
|
27707
|
+
const currentFileName = path12.basename(interview.markdownPath, ".md");
|
|
27321
27708
|
if (currentFileName === newSlug) {
|
|
27322
27709
|
return;
|
|
27323
27710
|
}
|
|
27324
|
-
const dir =
|
|
27325
|
-
const newPath =
|
|
27711
|
+
const dir = path12.dirname(interview.markdownPath);
|
|
27712
|
+
const newPath = path12.join(dir, `${newSlug}.md`);
|
|
27326
27713
|
try {
|
|
27327
27714
|
await fs8.access(newPath);
|
|
27328
27715
|
return;
|
|
@@ -27398,9 +27785,9 @@ function createInterviewService(ctx, config, deps) {
|
|
|
27398
27785
|
const messages = await loadMessages(sessionID);
|
|
27399
27786
|
const title = extractTitle(document);
|
|
27400
27787
|
const record = {
|
|
27401
|
-
id: `${Date.now()}-${++idCounter}-${slugify(
|
|
27788
|
+
id: `${Date.now()}-${++idCounter}-${slugify(path12.basename(markdownPath, ".md")) || "interview"}`,
|
|
27402
27789
|
sessionID,
|
|
27403
|
-
idea: title ||
|
|
27790
|
+
idea: title || path12.basename(markdownPath, ".md"),
|
|
27404
27791
|
markdownPath,
|
|
27405
27792
|
createdAt: nowIso(),
|
|
27406
27793
|
status: "active",
|
|
@@ -27627,7 +28014,7 @@ function createInterviewService(ctx, config, deps) {
|
|
|
27627
28014
|
return fileCache.items;
|
|
27628
28015
|
}
|
|
27629
28016
|
const outputDir = createInterviewDirectoryPath(ctx.directory, outputFolder);
|
|
27630
|
-
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)));
|
|
27631
28018
|
let entries;
|
|
27632
28019
|
try {
|
|
27633
28020
|
entries = await fs8.readdir(outputDir);
|
|
@@ -27638,8 +28025,8 @@ function createInterviewService(ctx, config, deps) {
|
|
|
27638
28025
|
for (const entry of entries) {
|
|
27639
28026
|
if (!entry.endsWith(".md"))
|
|
27640
28027
|
continue;
|
|
27641
|
-
const fullPath =
|
|
27642
|
-
if (activePaths.has(
|
|
28028
|
+
const fullPath = path12.join(outputDir, entry);
|
|
28029
|
+
if (activePaths.has(path12.resolve(fullPath)))
|
|
27643
28030
|
continue;
|
|
27644
28031
|
let content;
|
|
27645
28032
|
try {
|
|
@@ -27738,7 +28125,7 @@ function createInterviewManager(ctx, config) {
|
|
|
27738
28125
|
const outputFolder = interviewConfig?.outputFolder ?? "interview";
|
|
27739
28126
|
if (!dashboardEnabled) {
|
|
27740
28127
|
const service2 = createInterviewService(ctx, interviewConfig);
|
|
27741
|
-
const resolvedOutputPath =
|
|
28128
|
+
const resolvedOutputPath = path13.join(ctx.directory, outputFolder);
|
|
27742
28129
|
const server = createInterviewServer({
|
|
27743
28130
|
getState: async (interviewId) => service2.getInterviewState(interviewId),
|
|
27744
28131
|
listInterviewFiles: async () => service2.listInterviewFiles(),
|
|
@@ -27843,7 +28230,7 @@ function createInterviewManager(ctx, config) {
|
|
|
27843
28230
|
listInterviews: () => service.listInterviews(),
|
|
27844
28231
|
submitAnswers: async (interviewId, answers) => service.submitAnswers(interviewId, answers),
|
|
27845
28232
|
handleNudgeAction: async (interviewId, action) => service.handleNudgeAction(interviewId, action),
|
|
27846
|
-
outputFolder:
|
|
28233
|
+
outputFolder: path13.join(ctx.directory, outputFolder),
|
|
27847
28234
|
port: 0
|
|
27848
28235
|
});
|
|
27849
28236
|
service.setBaseUrlResolver(() => perSessionServer.ensureStarted());
|
|
@@ -28111,6 +28498,8 @@ function createBuiltinMcps(disabledMcps = [], websearchConfig) {
|
|
|
28111
28498
|
}
|
|
28112
28499
|
|
|
28113
28500
|
// src/multiplexer/tmux/index.ts
|
|
28501
|
+
var TMUX_LAYOUT_DEBOUNCE_MS = 150;
|
|
28502
|
+
|
|
28114
28503
|
class TmuxMultiplexer {
|
|
28115
28504
|
type = "tmux";
|
|
28116
28505
|
binaryPath = null;
|
|
@@ -28118,6 +28507,8 @@ class TmuxMultiplexer {
|
|
|
28118
28507
|
storedLayout;
|
|
28119
28508
|
storedMainPaneSize;
|
|
28120
28509
|
targetPane = process.env.TMUX_PANE;
|
|
28510
|
+
layoutTimer;
|
|
28511
|
+
layoutGeneration = 0;
|
|
28121
28512
|
constructor(layout = "main-vertical", mainPaneSize = 60) {
|
|
28122
28513
|
this.storedLayout = layout;
|
|
28123
28514
|
this.storedMainPaneSize = mainPaneSize;
|
|
@@ -28179,7 +28570,7 @@ class TmuxMultiplexer {
|
|
|
28179
28570
|
if (exitCode === 0 && paneId) {
|
|
28180
28571
|
const renameProc = crossSpawn([tmux, "select-pane", "-t", paneId, "-T", description.slice(0, 30)], { stdout: "ignore", stderr: "ignore" });
|
|
28181
28572
|
await renameProc.exited;
|
|
28182
|
-
|
|
28573
|
+
this.scheduleLayout();
|
|
28183
28574
|
log("[tmux] spawnPane: SUCCESS", { paneId });
|
|
28184
28575
|
return { success: true, paneId };
|
|
28185
28576
|
}
|
|
@@ -28216,7 +28607,7 @@ class TmuxMultiplexer {
|
|
|
28216
28607
|
const stderr = await proc.stderr();
|
|
28217
28608
|
log("[tmux] closePane: result", { exitCode, stderr: stderr.trim() });
|
|
28218
28609
|
if (exitCode === 0) {
|
|
28219
|
-
|
|
28610
|
+
this.scheduleLayout();
|
|
28220
28611
|
return true;
|
|
28221
28612
|
}
|
|
28222
28613
|
log("[tmux] closePane: failed (pane may already be closed)", { paneId });
|
|
@@ -28227,41 +28618,74 @@ class TmuxMultiplexer {
|
|
|
28227
28618
|
}
|
|
28228
28619
|
}
|
|
28229
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) {
|
|
28230
28641
|
const tmux = await this.getBinary();
|
|
28231
28642
|
if (!tmux)
|
|
28232
28643
|
return;
|
|
28233
28644
|
this.storedLayout = layout;
|
|
28234
28645
|
this.storedMainPaneSize = mainPaneSize;
|
|
28235
28646
|
try {
|
|
28236
|
-
const
|
|
28237
|
-
|
|
28238
|
-
|
|
28239
|
-
});
|
|
28240
|
-
await layoutProc.exited;
|
|
28647
|
+
const layoutResult = await this.runTmux(tmux, ["select-layout", ...this.targetArgs(), layout]);
|
|
28648
|
+
if (layoutResult !== 0)
|
|
28649
|
+
return;
|
|
28241
28650
|
if (layout === "main-horizontal" || layout === "main-vertical") {
|
|
28242
28651
|
const sizeOption = layout === "main-horizontal" ? "main-pane-height" : "main-pane-width";
|
|
28243
|
-
const
|
|
28244
|
-
tmux,
|
|
28652
|
+
const sizeResult = await this.runTmux(tmux, [
|
|
28245
28653
|
"set-window-option",
|
|
28246
28654
|
...this.targetArgs(),
|
|
28247
28655
|
sizeOption,
|
|
28248
28656
|
`${mainPaneSize}%`
|
|
28249
|
-
]
|
|
28250
|
-
|
|
28251
|
-
|
|
28252
|
-
|
|
28253
|
-
|
|
28254
|
-
|
|
28255
|
-
stdout: "pipe",
|
|
28256
|
-
stderr: "pipe"
|
|
28257
|
-
});
|
|
28258
|
-
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;
|
|
28259
28663
|
}
|
|
28260
28664
|
log("[tmux] applyLayout: applied", { layout, mainPaneSize });
|
|
28261
28665
|
} catch (err) {
|
|
28262
28666
|
log("[tmux] applyLayout: exception", { error: String(err) });
|
|
28263
28667
|
}
|
|
28264
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
|
+
}
|
|
28265
28689
|
async getBinary() {
|
|
28266
28690
|
await this.isAvailable();
|
|
28267
28691
|
return this.binaryPath;
|
|
@@ -28283,23 +28707,23 @@ class TmuxMultiplexer {
|
|
|
28283
28707
|
return null;
|
|
28284
28708
|
}
|
|
28285
28709
|
const stdout = await proc.stdout();
|
|
28286
|
-
const
|
|
28710
|
+
const path14 = stdout.trim().split(`
|
|
28287
28711
|
`)[0];
|
|
28288
|
-
if (!
|
|
28712
|
+
if (!path14) {
|
|
28289
28713
|
log("[tmux] findBinary: no path in output");
|
|
28290
28714
|
return null;
|
|
28291
28715
|
}
|
|
28292
|
-
const verifyProc = crossSpawn([
|
|
28716
|
+
const verifyProc = crossSpawn([path14, "-V"], {
|
|
28293
28717
|
stdout: "pipe",
|
|
28294
28718
|
stderr: "pipe"
|
|
28295
28719
|
});
|
|
28296
28720
|
const verifyExit = await verifyProc.exited;
|
|
28297
28721
|
if (verifyExit !== 0) {
|
|
28298
|
-
log("[tmux] findBinary: tmux -V failed", { path:
|
|
28722
|
+
log("[tmux] findBinary: tmux -V failed", { path: path14, verifyExit });
|
|
28299
28723
|
return null;
|
|
28300
28724
|
}
|
|
28301
|
-
log("[tmux] findBinary: found", { path:
|
|
28302
|
-
return
|
|
28725
|
+
log("[tmux] findBinary: found", { path: path14 });
|
|
28726
|
+
return path14;
|
|
28303
28727
|
} catch (err) {
|
|
28304
28728
|
log("[tmux] findBinary: exception", { error: String(err) });
|
|
28305
28729
|
return null;
|
|
@@ -29010,17 +29434,17 @@ async function isServerRunning(serverUrl, timeoutMs = 3000, maxAttempts = 2) {
|
|
|
29010
29434
|
import { tool as tool2 } from "@opencode-ai/plugin/tool";
|
|
29011
29435
|
|
|
29012
29436
|
// src/tools/ast-grep/cli.ts
|
|
29013
|
-
import { existsSync as
|
|
29437
|
+
import { existsSync as existsSync9 } from "node:fs";
|
|
29014
29438
|
|
|
29015
29439
|
// src/tools/ast-grep/constants.ts
|
|
29016
|
-
import { existsSync as
|
|
29440
|
+
import { existsSync as existsSync8, statSync as statSync4 } from "node:fs";
|
|
29017
29441
|
import { createRequire as createRequire3 } from "node:module";
|
|
29018
29442
|
import { dirname as dirname6, join as join11 } from "node:path";
|
|
29019
29443
|
|
|
29020
29444
|
// src/tools/ast-grep/downloader.ts
|
|
29021
|
-
import { chmodSync, existsSync as
|
|
29445
|
+
import { chmodSync, existsSync as existsSync7, mkdirSync as mkdirSync5, unlinkSync as unlinkSync4 } from "node:fs";
|
|
29022
29446
|
import { createRequire as createRequire2 } from "node:module";
|
|
29023
|
-
import { homedir as
|
|
29447
|
+
import { homedir as homedir5 } from "node:os";
|
|
29024
29448
|
import { join as join10 } from "node:path";
|
|
29025
29449
|
var REPO = "ast-grep/ast-grep";
|
|
29026
29450
|
var DEFAULT_VERSION = "0.40.0";
|
|
@@ -29045,11 +29469,11 @@ var PLATFORM_MAP = {
|
|
|
29045
29469
|
function getCacheDir2() {
|
|
29046
29470
|
if (process.platform === "win32") {
|
|
29047
29471
|
const localAppData = process.env.LOCALAPPDATA || process.env.APPDATA;
|
|
29048
|
-
const base2 = localAppData || join10(
|
|
29472
|
+
const base2 = localAppData || join10(homedir5(), "AppData", "Local");
|
|
29049
29473
|
return join10(base2, "oh-my-opencode-slim", "bin");
|
|
29050
29474
|
}
|
|
29051
29475
|
const xdgCache = process.env.XDG_CACHE_HOME;
|
|
29052
|
-
const base = xdgCache || join10(
|
|
29476
|
+
const base = xdgCache || join10(homedir5(), ".cache");
|
|
29053
29477
|
return join10(base, "oh-my-opencode-slim", "bin");
|
|
29054
29478
|
}
|
|
29055
29479
|
function getBinaryName() {
|
|
@@ -29057,7 +29481,7 @@ function getBinaryName() {
|
|
|
29057
29481
|
}
|
|
29058
29482
|
function getCachedBinaryPath() {
|
|
29059
29483
|
const binaryPath = join10(getCacheDir2(), getBinaryName());
|
|
29060
|
-
return
|
|
29484
|
+
return existsSync7(binaryPath) ? binaryPath : null;
|
|
29061
29485
|
}
|
|
29062
29486
|
async function downloadAstGrep(version = DEFAULT_VERSION) {
|
|
29063
29487
|
const platformKey = `${process.platform}-${process.arch}`;
|
|
@@ -29069,16 +29493,16 @@ async function downloadAstGrep(version = DEFAULT_VERSION) {
|
|
|
29069
29493
|
const cacheDir = getCacheDir2();
|
|
29070
29494
|
const binaryName = getBinaryName();
|
|
29071
29495
|
const binaryPath = join10(cacheDir, binaryName);
|
|
29072
|
-
if (
|
|
29496
|
+
if (existsSync7(binaryPath)) {
|
|
29073
29497
|
return binaryPath;
|
|
29074
29498
|
}
|
|
29075
|
-
const { arch, os:
|
|
29076
|
-
const assetName = `app-${arch}-${
|
|
29499
|
+
const { arch, os: os5 } = platformInfo;
|
|
29500
|
+
const assetName = `app-${arch}-${os5}.zip`;
|
|
29077
29501
|
const downloadUrl = `https://github.com/${REPO}/releases/download/${version}/${assetName}`;
|
|
29078
29502
|
console.log(`[oh-my-opencode-slim] Downloading ast-grep binary...`);
|
|
29079
29503
|
try {
|
|
29080
|
-
if (!
|
|
29081
|
-
|
|
29504
|
+
if (!existsSync7(cacheDir)) {
|
|
29505
|
+
mkdirSync5(cacheDir, { recursive: true });
|
|
29082
29506
|
}
|
|
29083
29507
|
const response = await fetch(downloadUrl, { redirect: "follow" });
|
|
29084
29508
|
if (!response.ok) {
|
|
@@ -29088,10 +29512,10 @@ async function downloadAstGrep(version = DEFAULT_VERSION) {
|
|
|
29088
29512
|
const arrayBuffer = await response.arrayBuffer();
|
|
29089
29513
|
await crossWrite(archivePath, arrayBuffer);
|
|
29090
29514
|
await extractZip(archivePath, cacheDir);
|
|
29091
|
-
if (
|
|
29515
|
+
if (existsSync7(archivePath)) {
|
|
29092
29516
|
unlinkSync4(archivePath);
|
|
29093
29517
|
}
|
|
29094
|
-
if (process.platform !== "win32" &&
|
|
29518
|
+
if (process.platform !== "win32" && existsSync7(binaryPath)) {
|
|
29095
29519
|
chmodSync(binaryPath, 493);
|
|
29096
29520
|
}
|
|
29097
29521
|
console.log(`[oh-my-opencode-slim] ast-grep binary ready.`);
|
|
@@ -29174,7 +29598,7 @@ function findSgCliPathSync() {
|
|
|
29174
29598
|
const cliPkgPath = require2.resolve("@ast-grep/cli/package.json");
|
|
29175
29599
|
const cliDir = dirname6(cliPkgPath);
|
|
29176
29600
|
const sgPath = join11(cliDir, binaryName);
|
|
29177
|
-
if (
|
|
29601
|
+
if (existsSync8(sgPath) && isValidBinary(sgPath)) {
|
|
29178
29602
|
return sgPath;
|
|
29179
29603
|
}
|
|
29180
29604
|
} catch {}
|
|
@@ -29186,16 +29610,16 @@ function findSgCliPathSync() {
|
|
|
29186
29610
|
const pkgDir = dirname6(pkgPath);
|
|
29187
29611
|
const astGrepName = process.platform === "win32" ? "ast-grep.exe" : "ast-grep";
|
|
29188
29612
|
const binaryPath = join11(pkgDir, astGrepName);
|
|
29189
|
-
if (
|
|
29613
|
+
if (existsSync8(binaryPath) && isValidBinary(binaryPath)) {
|
|
29190
29614
|
return binaryPath;
|
|
29191
29615
|
}
|
|
29192
29616
|
} catch {}
|
|
29193
29617
|
}
|
|
29194
29618
|
if (process.platform === "darwin") {
|
|
29195
29619
|
const homebrewPaths = ["/opt/homebrew/bin/sg", "/usr/local/bin/sg"];
|
|
29196
|
-
for (const
|
|
29197
|
-
if (
|
|
29198
|
-
return
|
|
29620
|
+
for (const path14 of homebrewPaths) {
|
|
29621
|
+
if (existsSync8(path14) && isValidBinary(path14)) {
|
|
29622
|
+
return path14;
|
|
29199
29623
|
}
|
|
29200
29624
|
}
|
|
29201
29625
|
}
|
|
@@ -29212,8 +29636,8 @@ function getSgCliPath() {
|
|
|
29212
29636
|
}
|
|
29213
29637
|
return "sg";
|
|
29214
29638
|
}
|
|
29215
|
-
function setSgCliPath(
|
|
29216
|
-
resolvedCliPath =
|
|
29639
|
+
function setSgCliPath(path14) {
|
|
29640
|
+
resolvedCliPath = path14;
|
|
29217
29641
|
}
|
|
29218
29642
|
var DEFAULT_TIMEOUT_MS2 = 300000;
|
|
29219
29643
|
var DEFAULT_MAX_OUTPUT_BYTES = 1 * 1024 * 1024;
|
|
@@ -29223,7 +29647,7 @@ var DEFAULT_MAX_MATCHES = 500;
|
|
|
29223
29647
|
var initPromise = null;
|
|
29224
29648
|
async function getAstGrepPath() {
|
|
29225
29649
|
const currentPath = getSgCliPath();
|
|
29226
|
-
if (currentPath !== "sg" &&
|
|
29650
|
+
if (currentPath !== "sg" && existsSync9(currentPath)) {
|
|
29227
29651
|
return currentPath;
|
|
29228
29652
|
}
|
|
29229
29653
|
if (initPromise) {
|
|
@@ -29231,7 +29655,7 @@ async function getAstGrepPath() {
|
|
|
29231
29655
|
}
|
|
29232
29656
|
initPromise = (async () => {
|
|
29233
29657
|
const syncPath = findSgCliPathSync();
|
|
29234
|
-
if (syncPath &&
|
|
29658
|
+
if (syncPath && existsSync9(syncPath)) {
|
|
29235
29659
|
setSgCliPath(syncPath);
|
|
29236
29660
|
return syncPath;
|
|
29237
29661
|
}
|
|
@@ -29270,7 +29694,7 @@ async function runSg(options) {
|
|
|
29270
29694
|
const paths2 = options.paths && options.paths.length > 0 ? options.paths : ["."];
|
|
29271
29695
|
args.push(...paths2);
|
|
29272
29696
|
let cliPath = getSgCliPath();
|
|
29273
|
-
if (!
|
|
29697
|
+
if (!existsSync9(cliPath) && cliPath !== "sg") {
|
|
29274
29698
|
const downloadedPath = await getAstGrepPath();
|
|
29275
29699
|
if (downloadedPath) {
|
|
29276
29700
|
cliPath = downloadedPath;
|
|
@@ -29620,15 +30044,15 @@ Returns the councillor responses with a summary footer.`,
|
|
|
29620
30044
|
}
|
|
29621
30045
|
// src/tui-state.ts
|
|
29622
30046
|
import * as fs9 from "node:fs";
|
|
29623
|
-
import * as
|
|
29624
|
-
import * as
|
|
30047
|
+
import * as os5 from "node:os";
|
|
30048
|
+
import * as path14 from "node:path";
|
|
29625
30049
|
var STATE_DIR = "oh-my-opencode-slim";
|
|
29626
30050
|
var STATE_FILE = "tui-state.json";
|
|
29627
30051
|
function dataDir() {
|
|
29628
|
-
return process.env.XDG_DATA_HOME ??
|
|
30052
|
+
return process.env.XDG_DATA_HOME ?? path14.join(os5.homedir(), ".local", "share");
|
|
29629
30053
|
}
|
|
29630
30054
|
function getTuiStatePath() {
|
|
29631
|
-
return
|
|
30055
|
+
return path14.join(dataDir(), "opencode", "storage", STATE_DIR, STATE_FILE);
|
|
29632
30056
|
}
|
|
29633
30057
|
function emptySnapshot() {
|
|
29634
30058
|
return {
|
|
@@ -29664,7 +30088,7 @@ async function readTuiSnapshotAsync() {
|
|
|
29664
30088
|
function writeTuiSnapshot(snapshot) {
|
|
29665
30089
|
try {
|
|
29666
30090
|
const filePath = getTuiStatePath();
|
|
29667
|
-
fs9.mkdirSync(
|
|
30091
|
+
fs9.mkdirSync(path14.dirname(filePath), { recursive: true });
|
|
29668
30092
|
fs9.writeFileSync(filePath, `${JSON.stringify(snapshot)}
|
|
29669
30093
|
`);
|
|
29670
30094
|
} catch {}
|
|
@@ -29880,15 +30304,15 @@ var BINARY_PREFIXES = [
|
|
|
29880
30304
|
];
|
|
29881
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.";
|
|
29882
30306
|
// src/tools/smartfetch/tool.ts
|
|
29883
|
-
import
|
|
29884
|
-
import
|
|
30307
|
+
import os6 from "node:os";
|
|
30308
|
+
import path18 from "node:path";
|
|
29885
30309
|
import {
|
|
29886
30310
|
tool as tool4
|
|
29887
30311
|
} from "@opencode-ai/plugin";
|
|
29888
30312
|
|
|
29889
30313
|
// src/tools/smartfetch/binary.ts
|
|
29890
30314
|
import { mkdir as mkdir2, writeFile as writeFile2 } from "node:fs/promises";
|
|
29891
|
-
import
|
|
30315
|
+
import path15 from "node:path";
|
|
29892
30316
|
function extensionForMime(contentType) {
|
|
29893
30317
|
const mime = contentType.split(";")[0]?.trim().toLowerCase();
|
|
29894
30318
|
const map = {
|
|
@@ -29909,10 +30333,10 @@ function buildBinaryResultMessage(fetchResult, savedPath) {
|
|
|
29909
30333
|
async function saveBinary(binaryDir, data, contentType, filename) {
|
|
29910
30334
|
await mkdir2(binaryDir, { recursive: true });
|
|
29911
30335
|
const initialName = filename || `webfetch-${Date.now()}.${extensionForMime(contentType)}`;
|
|
29912
|
-
const parsed =
|
|
30336
|
+
const parsed = path15.parse(initialName);
|
|
29913
30337
|
for (let attempt = 0;attempt < 1000; attempt++) {
|
|
29914
30338
|
const candidateName = attempt === 0 ? initialName : `${parsed.name}-${attempt}${parsed.ext || `.${extensionForMime(contentType)}`}`;
|
|
29915
|
-
const file =
|
|
30339
|
+
const file = path15.join(binaryDir, candidateName);
|
|
29916
30340
|
try {
|
|
29917
30341
|
await writeFile2(file, data, { flag: "wx" });
|
|
29918
30342
|
return file;
|
|
@@ -30566,7 +30990,7 @@ var L = class u2 {
|
|
|
30566
30990
|
};
|
|
30567
30991
|
|
|
30568
30992
|
// src/tools/smartfetch/network.ts
|
|
30569
|
-
import
|
|
30993
|
+
import path16 from "node:path";
|
|
30570
30994
|
|
|
30571
30995
|
// src/tools/smartfetch/utils.ts
|
|
30572
30996
|
var import_readability = __toESM(require_readability(), 1);
|
|
@@ -31291,7 +31715,7 @@ function inferFilenameFromUrl(url) {
|
|
|
31291
31715
|
function truncateFilename(name, maxLength = 180) {
|
|
31292
31716
|
if (name.length <= maxLength)
|
|
31293
31717
|
return name;
|
|
31294
|
-
const parsed =
|
|
31718
|
+
const parsed = path16.parse(name);
|
|
31295
31719
|
const ext = parsed.ext || "";
|
|
31296
31720
|
const baseLimit = Math.max(1, maxLength - ext.length);
|
|
31297
31721
|
return `${parsed.name.slice(0, baseLimit)}${ext}`;
|
|
@@ -31461,9 +31885,9 @@ function isInvalidLlmsResult(fetchResult) {
|
|
|
31461
31885
|
}
|
|
31462
31886
|
|
|
31463
31887
|
// src/tools/smartfetch/secondary-model.ts
|
|
31464
|
-
import { existsSync as
|
|
31888
|
+
import { existsSync as existsSync10 } from "node:fs";
|
|
31465
31889
|
import { readFile as readFile4 } from "node:fs/promises";
|
|
31466
|
-
import
|
|
31890
|
+
import path17 from "node:path";
|
|
31467
31891
|
function parseModelRef(value) {
|
|
31468
31892
|
if (!value)
|
|
31469
31893
|
return;
|
|
@@ -31489,8 +31913,8 @@ function pickAgentModelRef(value) {
|
|
|
31489
31913
|
}
|
|
31490
31914
|
function findPreferredOpenCodeConfigPath(baseDir) {
|
|
31491
31915
|
for (const file of ["opencode.jsonc", "opencode.json"]) {
|
|
31492
|
-
const fullPath =
|
|
31493
|
-
if (
|
|
31916
|
+
const fullPath = path17.join(baseDir, file);
|
|
31917
|
+
if (existsSync10(fullPath))
|
|
31494
31918
|
return fullPath;
|
|
31495
31919
|
}
|
|
31496
31920
|
return;
|
|
@@ -31506,7 +31930,7 @@ async function readOpenCodeConfigFile(configPath) {
|
|
|
31506
31930
|
}
|
|
31507
31931
|
}
|
|
31508
31932
|
async function readEffectiveOpenCodeConfig(directory) {
|
|
31509
|
-
const projectDir =
|
|
31933
|
+
const projectDir = path17.join(directory, ".opencode");
|
|
31510
31934
|
const userDirs = getConfigSearchDirs();
|
|
31511
31935
|
const projectPath = findPreferredOpenCodeConfigPath(projectDir);
|
|
31512
31936
|
const userPath = userDirs.map((configDir) => findPreferredOpenCodeConfigPath(configDir)).find(Boolean);
|
|
@@ -31667,7 +32091,7 @@ async function runSecondaryModelWithFallback(client, directory, models, prompt,
|
|
|
31667
32091
|
// src/tools/smartfetch/tool.ts
|
|
31668
32092
|
var z5 = tool4.schema;
|
|
31669
32093
|
function createWebfetchTool(pluginCtx, options = {}) {
|
|
31670
|
-
const binaryDir = options.binaryDir ||
|
|
32094
|
+
const binaryDir = options.binaryDir || path18.join(os6.tmpdir(), "opencode-smartfetch");
|
|
31671
32095
|
return tool4({
|
|
31672
32096
|
description: WEBFETCH_DESCRIPTION,
|
|
31673
32097
|
args: {
|
|
@@ -32263,6 +32687,7 @@ var OhMyOpenCodeLite = async (ctx) => {
|
|
|
32263
32687
|
let taskSessionManagerHook;
|
|
32264
32688
|
let interviewManager;
|
|
32265
32689
|
let presetManager;
|
|
32690
|
+
let divoomManager;
|
|
32266
32691
|
let councilTools;
|
|
32267
32692
|
let webfetch;
|
|
32268
32693
|
let rewriteDisplayNameMentions;
|
|
@@ -32357,6 +32782,7 @@ var OhMyOpenCodeLite = async (ctx) => {
|
|
|
32357
32782
|
});
|
|
32358
32783
|
interviewManager = createInterviewManager(ctx, config);
|
|
32359
32784
|
presetManager = createPresetManager(ctx, config);
|
|
32785
|
+
divoomManager = createDivoomManager(config.divoom);
|
|
32360
32786
|
toolCount = Object.keys(councilTools).length + Object.keys(todoContinuationHook.tool).length + 1 + 2;
|
|
32361
32787
|
} catch (err) {
|
|
32362
32788
|
log("[plugin] FATAL: init failed", String(err));
|
|
@@ -32393,6 +32819,7 @@ var OhMyOpenCodeLite = async (ctx) => {
|
|
|
32393
32819
|
appLog(ctx, "warn", msg).catch(() => {});
|
|
32394
32820
|
}
|
|
32395
32821
|
});
|
|
32822
|
+
divoomManager.onPluginLoad();
|
|
32396
32823
|
return {
|
|
32397
32824
|
name: "oh-my-opencode-slim",
|
|
32398
32825
|
agent: agents,
|
|
@@ -32625,6 +33052,37 @@ var OhMyOpenCodeLite = async (ctx) => {
|
|
|
32625
33052
|
await multiplexerSessionManager.onSessionDeleted(event);
|
|
32626
33053
|
await interviewManager.handleEvent(input);
|
|
32627
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
|
+
}
|
|
32628
33086
|
if (input.event.type === "session.deleted") {
|
|
32629
33087
|
const props = input.event.properties;
|
|
32630
33088
|
const sessionID = props?.info?.id ?? props?.sessionID;
|
|
@@ -32639,6 +33097,13 @@ var OhMyOpenCodeLite = async (ctx) => {
|
|
|
32639
33097
|
"tool.execute.before": async (input, output) => {
|
|
32640
33098
|
await applyPatchHook["tool.execute.before"](input, output);
|
|
32641
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
|
+
}
|
|
32642
33107
|
},
|
|
32643
33108
|
"command.execute.before": async (input, output) => {
|
|
32644
33109
|
await todoContinuationHook.handleCommandExecuteBefore(input, output);
|
|
@@ -32701,11 +33166,31 @@ ${output.system[0]}` : "");
|
|
|
32701
33166
|
await filterAvailableSkillsHook["experimental.chat.messages.transform"](input, typedOutput);
|
|
32702
33167
|
},
|
|
32703
33168
|
"tool.execute.after": async (input, output) => {
|
|
32704
|
-
|
|
32705
|
-
|
|
32706
|
-
|
|
32707
|
-
|
|
32708
|
-
|
|
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
|
+
}
|
|
32709
33194
|
}
|
|
32710
33195
|
};
|
|
32711
33196
|
};
|