oh-my-opencode-slim 2.0.0 → 2.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.ja-JP.md +69 -20
- package/README.ko-KR.md +66 -19
- package/README.md +72 -19
- package/README.zh-CN.md +78 -31
- package/dist/agents/orchestrator.d.ts +0 -2
- package/dist/cli/index.js +50 -37
- package/dist/config/agent-mcps.d.ts +0 -4
- package/dist/config/constants.d.ts +1 -7
- package/dist/config/council-schema.d.ts +0 -15
- package/dist/config/runtime-preset.d.ts +0 -1
- package/dist/config/schema.d.ts +2 -68
- package/dist/hooks/auto-update-checker/skill-sync.d.ts +9 -0
- package/dist/hooks/filter-available-skills/index.d.ts +1 -13
- package/dist/hooks/foreground-fallback/index.d.ts +1 -1
- package/dist/hooks/image-hook.d.ts +1 -13
- package/dist/hooks/index.d.ts +3 -2
- package/dist/hooks/phase-reminder/index.d.ts +10 -16
- package/dist/hooks/reflect/index.d.ts +13 -0
- package/dist/hooks/task-session-manager/index.d.ts +2 -16
- package/dist/hooks/types.d.ts +23 -0
- package/dist/index.js +391 -289
- package/dist/tui.js +16 -25
- package/dist/utils/agent-variant.d.ts +0 -40
- package/dist/utils/compat.d.ts +0 -1
- package/dist/utils/guards.d.ts +4 -0
- package/dist/utils/index.d.ts +1 -2
- package/dist/utils/logger.d.ts +1 -1
- package/dist/utils/task.d.ts +0 -2
- package/oh-my-opencode-slim.schema.json +2 -249
- package/package.json +1 -1
- package/src/skills/codemap.md +4 -1
- package/src/skills/reflect/SKILL.md +193 -0
- package/src/skills/worktrees/SKILL.md +164 -0
- package/dist/config/fallback-chains.d.ts +0 -1
- package/dist/hooks/apply-patch/patch.d.ts +0 -2
- package/dist/hooks/delegate-task-retry/guidance.d.ts +0 -2
- package/dist/hooks/delegate-task-retry/index.d.ts +0 -4
- package/dist/hooks/json-error-recovery/index.d.ts +0 -1
- package/dist/utils/env.d.ts +0 -1
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(path18) {
|
|
6250
|
+
if (!path18)
|
|
6251
|
+
return path18;
|
|
6252
6252
|
var output = "";
|
|
6253
|
-
while (
|
|
6254
|
-
if (
|
|
6255
|
-
|
|
6253
|
+
while (path18.length > 0) {
|
|
6254
|
+
if (path18 === "." || path18 === "..") {
|
|
6255
|
+
path18 = "";
|
|
6256
6256
|
break;
|
|
6257
6257
|
}
|
|
6258
|
-
var twochars =
|
|
6259
|
-
var threechars =
|
|
6260
|
-
var fourchars =
|
|
6258
|
+
var twochars = path18.substring(0, 2);
|
|
6259
|
+
var threechars = path18.substring(0, 3);
|
|
6260
|
+
var fourchars = path18.substring(0, 4);
|
|
6261
6261
|
if (threechars === "../") {
|
|
6262
|
-
|
|
6262
|
+
path18 = path18.substring(3);
|
|
6263
6263
|
} else if (twochars === "./") {
|
|
6264
|
-
|
|
6264
|
+
path18 = path18.substring(2);
|
|
6265
6265
|
} else if (threechars === "/./") {
|
|
6266
|
-
|
|
6267
|
-
} else if (twochars === "/." &&
|
|
6268
|
-
|
|
6269
|
-
} else if (fourchars === "/../" || threechars === "/.." &&
|
|
6270
|
-
|
|
6266
|
+
path18 = "/" + path18.substring(3);
|
|
6267
|
+
} else if (twochars === "/." && path18.length === 2) {
|
|
6268
|
+
path18 = "/";
|
|
6269
|
+
} else if (fourchars === "/../" || threechars === "/.." && path18.length === 3) {
|
|
6270
|
+
path18 = "/" + path18.substring(4);
|
|
6271
6271
|
output = output.replace(/\/?[^\/]*$/, "");
|
|
6272
6272
|
} else {
|
|
6273
|
-
var segment =
|
|
6273
|
+
var segment = path18.match(/(\/?([^\/]*))/)[0];
|
|
6274
6274
|
output += segment;
|
|
6275
|
-
|
|
6275
|
+
path18 = path18.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 join16(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 = join16(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 join16(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);
|
|
@@ -18240,11 +18240,23 @@ var CUSTOM_SKILLS = [
|
|
|
18240
18240
|
allowedAgents: ["orchestrator"],
|
|
18241
18241
|
sourcePath: "src/skills/deepwork"
|
|
18242
18242
|
},
|
|
18243
|
+
{
|
|
18244
|
+
name: "reflect",
|
|
18245
|
+
description: "Review repeated work and suggest reusable workflow improvements",
|
|
18246
|
+
allowedAgents: ["orchestrator"],
|
|
18247
|
+
sourcePath: "src/skills/reflect"
|
|
18248
|
+
},
|
|
18243
18249
|
{
|
|
18244
18250
|
name: "oh-my-opencode-slim",
|
|
18245
18251
|
description: "Configure, customize, and safely improve oh-my-opencode-slim setups",
|
|
18246
18252
|
allowedAgents: ["orchestrator"],
|
|
18247
18253
|
sourcePath: "src/skills/oh-my-opencode-slim"
|
|
18254
|
+
},
|
|
18255
|
+
{
|
|
18256
|
+
name: "worktrees",
|
|
18257
|
+
description: "Manage Git worktrees as OMO safe isolated coding lanes for complex/risky/parallel work",
|
|
18258
|
+
allowedAgents: ["orchestrator"],
|
|
18259
|
+
sourcePath: "src/skills/worktrees"
|
|
18248
18260
|
}
|
|
18249
18261
|
];
|
|
18250
18262
|
|
|
@@ -18303,8 +18315,7 @@ var SUBAGENT_NAMES = [
|
|
|
18303
18315
|
"council",
|
|
18304
18316
|
"councillor"
|
|
18305
18317
|
];
|
|
18306
|
-
var
|
|
18307
|
-
var ALL_AGENT_NAMES = [ORCHESTRATOR_NAME, ...SUBAGENT_NAMES];
|
|
18318
|
+
var ALL_AGENT_NAMES = ["orchestrator", ...SUBAGENT_NAMES];
|
|
18308
18319
|
var PROTECTED_AGENTS = new Set(["orchestrator", "councillor"]);
|
|
18309
18320
|
var DEFAULT_MODELS = {
|
|
18310
18321
|
orchestrator: undefined,
|
|
@@ -18318,10 +18329,10 @@ var DEFAULT_MODELS = {
|
|
|
18318
18329
|
councillor: "openai/gpt-5.4-mini"
|
|
18319
18330
|
};
|
|
18320
18331
|
var POLL_INTERVAL_BACKGROUND_MS = 2000;
|
|
18321
|
-
var DEFAULT_TIMEOUT_MS = 2 * 60 * 1000;
|
|
18322
18332
|
var MAX_POLL_TIME_MS = 5 * 60 * 1000;
|
|
18323
18333
|
var DEFAULT_MAX_SUBAGENT_DEPTH = 3;
|
|
18324
18334
|
var PHASE_REMINDER_TEXT = `!IMPORTANT! Scheduler workflow: plan lanes/dependencies → dispatch background specialists → track task IDs → wait for hook-driven completion → reconcile terminal results → verify. Do not poll running jobs, consume running-job output, or advance dependent work. !END!`;
|
|
18335
|
+
var PHASE_REMINDER = `<internal_reminder>${PHASE_REMINDER_TEXT}</internal_reminder>`;
|
|
18325
18336
|
var WRITABLE_FILE_OPERATIONS_RULES = `**File Operations Rules**:
|
|
18326
18337
|
- Prefer dedicated file tools for normal code work: glob/grep/ast_grep_search for discovery, read for file contents, and edit/write/apply_patch for targeted source changes.
|
|
18327
18338
|
- Use bash for execution and automation: git, package managers, tests, builds, scripts, diagnostics, and shell-native filesystem operations.
|
|
@@ -18386,17 +18397,11 @@ var CouncilConfigSchema = z.object({
|
|
|
18386
18397
|
default_preset: z.string().default("default"),
|
|
18387
18398
|
councillor_execution_mode: CouncillorExecutionModeSchema.describe('Execution mode for councillors. "serial" runs them one at a time (required for single-model systems). "parallel" runs them concurrently (default, faster for multi-model systems).'),
|
|
18388
18399
|
councillor_retries: z.number().int().min(0).max(5).default(3).describe("Number of retry attempts for councillors that return empty responses " + "(e.g. due to provider rate limiting). Default: 3 retries."),
|
|
18389
|
-
master: z.unknown().optional().describe("DEPRECATED — ignored. Council agent synthesizes directly.")
|
|
18390
|
-
master_timeout: z.unknown().optional().describe('DEPRECATED — ignored. Use "timeout" instead.'),
|
|
18391
|
-
master_fallback: z.unknown().optional().describe("DEPRECATED — ignored. No separate master session.")
|
|
18400
|
+
master: z.unknown().optional().describe("DEPRECATED — ignored. Council agent synthesizes directly.")
|
|
18392
18401
|
}).transform((data) => {
|
|
18393
18402
|
const deprecated = [];
|
|
18394
18403
|
if (data.master !== undefined)
|
|
18395
18404
|
deprecated.push("master");
|
|
18396
|
-
if (data.master_timeout !== undefined)
|
|
18397
|
-
deprecated.push("master_timeout");
|
|
18398
|
-
if (data.master_fallback !== undefined)
|
|
18399
|
-
deprecated.push("master_fallback");
|
|
18400
18405
|
const legacyMasterModel = typeof data.master === "object" && data.master !== null && "model" in data.master && typeof data.master.model === "string" ? data.master.model : undefined;
|
|
18401
18406
|
return {
|
|
18402
18407
|
presets: data.presets,
|
|
@@ -18415,7 +18420,6 @@ import * as path from "node:path";
|
|
|
18415
18420
|
// src/utils/compat.ts
|
|
18416
18421
|
import { spawn as nodeSpawn } from "node:child_process";
|
|
18417
18422
|
import { writeFile as fsWriteFile } from "node:fs/promises";
|
|
18418
|
-
var isBun = typeof globalThis.Bun !== "undefined";
|
|
18419
18423
|
function collectStream(stream) {
|
|
18420
18424
|
if (!stream)
|
|
18421
18425
|
return () => Promise.resolve("");
|
|
@@ -18534,15 +18538,6 @@ var ManualPlanSchema = z2.object({
|
|
|
18534
18538
|
librarian: ManualAgentPlanSchema,
|
|
18535
18539
|
fixer: ManualAgentPlanSchema
|
|
18536
18540
|
}).strict();
|
|
18537
|
-
var AgentModelChainSchema = z2.array(z2.string()).min(1);
|
|
18538
|
-
var FallbackChainsSchema = z2.object({
|
|
18539
|
-
orchestrator: AgentModelChainSchema.optional(),
|
|
18540
|
-
oracle: AgentModelChainSchema.optional(),
|
|
18541
|
-
designer: AgentModelChainSchema.optional(),
|
|
18542
|
-
explorer: AgentModelChainSchema.optional(),
|
|
18543
|
-
librarian: AgentModelChainSchema.optional(),
|
|
18544
|
-
fixer: AgentModelChainSchema.optional()
|
|
18545
|
-
}).catchall(AgentModelChainSchema);
|
|
18546
18541
|
var AgentOverrideConfigSchema = z2.object({
|
|
18547
18542
|
model: z2.union([
|
|
18548
18543
|
z2.string(),
|
|
@@ -18605,9 +18600,8 @@ var FailoverConfigSchema = z2.object({
|
|
|
18605
18600
|
enabled: z2.boolean().default(true),
|
|
18606
18601
|
timeoutMs: z2.number().min(0).default(15000),
|
|
18607
18602
|
retryDelayMs: z2.number().min(0).default(500),
|
|
18608
|
-
chains: FallbackChainsSchema.default({}),
|
|
18609
18603
|
retry_on_empty: z2.boolean().default(true).describe("When true (default), empty provider responses are treated as failures, " + "triggering fallback/retry. Set to false to treat them as successes.")
|
|
18610
|
-
});
|
|
18604
|
+
}).strict();
|
|
18611
18605
|
var CompanionConfigSchema = z2.object({
|
|
18612
18606
|
enabled: z2.boolean().optional(),
|
|
18613
18607
|
position: z2.enum(["bottom-right", "bottom-left", "top-right", "top-left"]).optional(),
|
|
@@ -18638,10 +18632,7 @@ function validateCustomOnlyPromptFields(overrides, ctx, pathPrefix) {
|
|
|
18638
18632
|
var PluginConfigSchema = z2.object({
|
|
18639
18633
|
preset: z2.string().optional(),
|
|
18640
18634
|
setDefaultAgent: z2.boolean().optional(),
|
|
18641
|
-
scoringEngineVersion: z2.enum(["v1", "v2-shadow", "v2"]).optional(),
|
|
18642
|
-
balanceProviderUsage: z2.boolean().optional(),
|
|
18643
18635
|
autoUpdate: z2.boolean().optional().describe("Disable automatic installation of plugin updates when false. Defaults to true."),
|
|
18644
|
-
manualPlan: ManualPlanSchema.optional(),
|
|
18645
18636
|
presets: z2.record(z2.string(), PresetSchema).optional(),
|
|
18646
18637
|
agents: z2.record(z2.string(), AgentOverrideConfigSchema).optional(),
|
|
18647
18638
|
disabled_agents: z2.array(z2.string()).optional().describe("Agent names to disable completely. " + "Disabled agents are not instantiated and cannot be delegated to. " + "Orchestrator and council internal agents (councillor) cannot be disabled. " + "By default, 'observer' is disabled. Remove it from this list and configure a vision-capable model to enable."),
|
|
@@ -19201,7 +19192,6 @@ When user's approach seems problematic:
|
|
|
19201
19192
|
</Communication>
|
|
19202
19193
|
`;
|
|
19203
19194
|
}
|
|
19204
|
-
var ORCHESTRATOR_PROMPT = buildOrchestratorPrompt();
|
|
19205
19195
|
function createOrchestratorAgent(model, customPrompt, customAppendPrompt, disabledAgents) {
|
|
19206
19196
|
const basePrompt = buildOrchestratorPrompt(disabledAgents);
|
|
19207
19197
|
const prompt = resolvePrompt(basePrompt, customPrompt, customAppendPrompt);
|
|
@@ -20271,35 +20261,6 @@ class CompanionManager {
|
|
|
20271
20261
|
}
|
|
20272
20262
|
}
|
|
20273
20263
|
|
|
20274
|
-
// src/config/fallback-chains.ts
|
|
20275
|
-
function normalizeFallbackChainsForPreset(chains, presetName) {
|
|
20276
|
-
const normalized = {};
|
|
20277
|
-
for (const [rawKey, chainModels] of Object.entries(chains)) {
|
|
20278
|
-
if (!chainModels?.length)
|
|
20279
|
-
continue;
|
|
20280
|
-
const separatorIndex = rawKey.indexOf(":");
|
|
20281
|
-
const hasPresetScope = separatorIndex !== -1;
|
|
20282
|
-
const scopedPreset = hasPresetScope ? rawKey.slice(0, separatorIndex) : "";
|
|
20283
|
-
const agentName = hasPresetScope ? rawKey.slice(separatorIndex + 1) : rawKey;
|
|
20284
|
-
if (!agentName)
|
|
20285
|
-
continue;
|
|
20286
|
-
if (hasPresetScope && scopedPreset !== presetName)
|
|
20287
|
-
continue;
|
|
20288
|
-
const existing = normalized[agentName] ?? [];
|
|
20289
|
-
const seen = new Set(existing);
|
|
20290
|
-
for (const chainModel of chainModels) {
|
|
20291
|
-
if (seen.has(chainModel))
|
|
20292
|
-
continue;
|
|
20293
|
-
seen.add(chainModel);
|
|
20294
|
-
existing.push(chainModel);
|
|
20295
|
-
}
|
|
20296
|
-
if (existing.length > 0) {
|
|
20297
|
-
normalized[agentName] = existing;
|
|
20298
|
-
}
|
|
20299
|
-
}
|
|
20300
|
-
return normalized;
|
|
20301
|
-
}
|
|
20302
|
-
|
|
20303
20264
|
// src/config/runtime-preset.ts
|
|
20304
20265
|
var activeRuntimePreset = null;
|
|
20305
20266
|
function setActiveRuntimePreset(name) {
|
|
@@ -22083,6 +22044,9 @@ function createApplyPatchHook(ctx) {
|
|
|
22083
22044
|
}
|
|
22084
22045
|
};
|
|
22085
22046
|
}
|
|
22047
|
+
// src/hooks/auto-update-checker/index.ts
|
|
22048
|
+
import * as path10 from "node:path";
|
|
22049
|
+
|
|
22086
22050
|
// src/hooks/auto-update-checker/cache.ts
|
|
22087
22051
|
import * as fs5 from "node:fs";
|
|
22088
22052
|
import * as path8 from "node:path";
|
|
@@ -22542,6 +22506,133 @@ function preparePackageUpdate(version, packageName = PACKAGE_NAME, runtimePackag
|
|
|
22542
22506
|
}
|
|
22543
22507
|
}
|
|
22544
22508
|
|
|
22509
|
+
// src/hooks/auto-update-checker/skill-sync.ts
|
|
22510
|
+
import {
|
|
22511
|
+
copyFileSync,
|
|
22512
|
+
existsSync as existsSync5,
|
|
22513
|
+
lstatSync,
|
|
22514
|
+
mkdirSync as mkdirSync3,
|
|
22515
|
+
mkdtempSync,
|
|
22516
|
+
readdirSync as readdirSync2,
|
|
22517
|
+
renameSync as renameSync2,
|
|
22518
|
+
rmSync as rmSync2
|
|
22519
|
+
} from "node:fs";
|
|
22520
|
+
import * as path9 from "node:path";
|
|
22521
|
+
function copyDirRecursive(src, dest) {
|
|
22522
|
+
const stat2 = lstatSync(src);
|
|
22523
|
+
if (stat2.isSymbolicLink()) {
|
|
22524
|
+
return;
|
|
22525
|
+
}
|
|
22526
|
+
if (stat2.isDirectory()) {
|
|
22527
|
+
mkdirSync3(dest, { recursive: true });
|
|
22528
|
+
const entries = readdirSync2(src);
|
|
22529
|
+
for (const entry of entries) {
|
|
22530
|
+
copyDirRecursive(path9.join(src, entry), path9.join(dest, entry));
|
|
22531
|
+
}
|
|
22532
|
+
} else if (stat2.isFile()) {
|
|
22533
|
+
const destDir = path9.dirname(dest);
|
|
22534
|
+
if (!existsSync5(destDir)) {
|
|
22535
|
+
mkdirSync3(destDir, { recursive: true });
|
|
22536
|
+
}
|
|
22537
|
+
copyFileSync(src, dest);
|
|
22538
|
+
}
|
|
22539
|
+
}
|
|
22540
|
+
function syncBundledSkillsFromPackage(packageRoot) {
|
|
22541
|
+
const installed = [];
|
|
22542
|
+
const skippedExisting = [];
|
|
22543
|
+
const failed = [];
|
|
22544
|
+
const sourceSkillsDir = path9.join(packageRoot, "src", "skills");
|
|
22545
|
+
try {
|
|
22546
|
+
const stat2 = lstatSync(sourceSkillsDir);
|
|
22547
|
+
if (stat2.isSymbolicLink() || !stat2.isDirectory()) {
|
|
22548
|
+
log(`[skill-sync] Source skills directory is not a valid directory: ${sourceSkillsDir}`);
|
|
22549
|
+
return { installed, skippedExisting, failed };
|
|
22550
|
+
}
|
|
22551
|
+
} catch {
|
|
22552
|
+
log(`[skill-sync] Source skills directory does not exist or is unreadable: ${sourceSkillsDir}`);
|
|
22553
|
+
return { installed, skippedExisting, failed };
|
|
22554
|
+
}
|
|
22555
|
+
const destSkillsDir = path9.join(getConfigDir(), "skills");
|
|
22556
|
+
try {
|
|
22557
|
+
if (!existsSync5(destSkillsDir)) {
|
|
22558
|
+
mkdirSync3(destSkillsDir, { recursive: true });
|
|
22559
|
+
}
|
|
22560
|
+
} catch (err) {
|
|
22561
|
+
log(`[skill-sync] Failed to create destination skills directory: ${destSkillsDir}`, err);
|
|
22562
|
+
}
|
|
22563
|
+
let entries = [];
|
|
22564
|
+
try {
|
|
22565
|
+
entries = readdirSync2(sourceSkillsDir);
|
|
22566
|
+
} catch (err) {
|
|
22567
|
+
log(`[skill-sync] Failed to read source skills directory: ${sourceSkillsDir}`, err);
|
|
22568
|
+
return { installed, skippedExisting, failed };
|
|
22569
|
+
}
|
|
22570
|
+
for (const entry of entries) {
|
|
22571
|
+
const entryPath = path9.join(sourceSkillsDir, entry);
|
|
22572
|
+
try {
|
|
22573
|
+
if (entry.startsWith(".")) {
|
|
22574
|
+
continue;
|
|
22575
|
+
}
|
|
22576
|
+
const entryStat = lstatSync(entryPath);
|
|
22577
|
+
if (entryStat.isSymbolicLink() || !entryStat.isDirectory()) {
|
|
22578
|
+
continue;
|
|
22579
|
+
}
|
|
22580
|
+
const skillMdPath = path9.join(entryPath, "SKILL.md");
|
|
22581
|
+
try {
|
|
22582
|
+
const skillMdStat = lstatSync(skillMdPath);
|
|
22583
|
+
if (skillMdStat.isSymbolicLink() || !skillMdStat.isFile()) {
|
|
22584
|
+
continue;
|
|
22585
|
+
}
|
|
22586
|
+
} catch {
|
|
22587
|
+
continue;
|
|
22588
|
+
}
|
|
22589
|
+
const destPath = path9.join(destSkillsDir, entry);
|
|
22590
|
+
let destExists = false;
|
|
22591
|
+
try {
|
|
22592
|
+
lstatSync(destPath);
|
|
22593
|
+
destExists = true;
|
|
22594
|
+
} catch {}
|
|
22595
|
+
if (destExists) {
|
|
22596
|
+
log(`[skill-sync] Skill already exists in destination: ${entry}`);
|
|
22597
|
+
skippedExisting.push(entry);
|
|
22598
|
+
continue;
|
|
22599
|
+
}
|
|
22600
|
+
const stagingDir = mkdtempSync(path9.join(destSkillsDir, `.sync-staging-${entry}-`));
|
|
22601
|
+
try {
|
|
22602
|
+
copyDirRecursive(entryPath, stagingDir);
|
|
22603
|
+
let destExistsLate = false;
|
|
22604
|
+
try {
|
|
22605
|
+
lstatSync(destPath);
|
|
22606
|
+
destExistsLate = true;
|
|
22607
|
+
} catch {}
|
|
22608
|
+
if (destExistsLate) {
|
|
22609
|
+
log(`[skill-sync] Destination path was created during staging for ${entry}, skipping promotion.`);
|
|
22610
|
+
skippedExisting.push(entry);
|
|
22611
|
+
} else {
|
|
22612
|
+
renameSync2(stagingDir, destPath);
|
|
22613
|
+
installed.push(entry);
|
|
22614
|
+
log(`[skill-sync] Successfully synced skill: ${entry}`);
|
|
22615
|
+
}
|
|
22616
|
+
} catch (err) {
|
|
22617
|
+
log(`[skill-sync] Failed to sync skill ${entry}:`, err);
|
|
22618
|
+
failed.push(entry);
|
|
22619
|
+
} finally {
|
|
22620
|
+
try {
|
|
22621
|
+
if (existsSync5(stagingDir)) {
|
|
22622
|
+
rmSync2(stagingDir, { recursive: true, force: true });
|
|
22623
|
+
}
|
|
22624
|
+
} catch (err) {
|
|
22625
|
+
log(`[skill-sync] Failed to clean up staging directory ${stagingDir}:`, err);
|
|
22626
|
+
}
|
|
22627
|
+
}
|
|
22628
|
+
} catch (err) {
|
|
22629
|
+
log(`[skill-sync] Error processing source entry ${entry}:`, err);
|
|
22630
|
+
failed.push(entry);
|
|
22631
|
+
}
|
|
22632
|
+
}
|
|
22633
|
+
return { installed, skippedExisting, failed };
|
|
22634
|
+
}
|
|
22635
|
+
|
|
22545
22636
|
// src/hooks/auto-update-checker/index.ts
|
|
22546
22637
|
function createAutoUpdateCheckerHook(ctx, options = {}) {
|
|
22547
22638
|
const { autoUpdate = true } = options;
|
|
@@ -22624,8 +22715,28 @@ Version is pinned. Update your plugin config to apply.`, "info", 8000);
|
|
|
22624
22715
|
}
|
|
22625
22716
|
const installSuccess = await runBunInstallSafe(installDir);
|
|
22626
22717
|
if (installSuccess) {
|
|
22627
|
-
|
|
22628
|
-
|
|
22718
|
+
let installedSkills = [];
|
|
22719
|
+
try {
|
|
22720
|
+
const packageRoot = path10.join(installDir, "node_modules", PACKAGE_NAME);
|
|
22721
|
+
const syncResult = syncBundledSkillsFromPackage(packageRoot);
|
|
22722
|
+
installedSkills = syncResult.installed;
|
|
22723
|
+
if (syncResult.failed.length > 0) {
|
|
22724
|
+
log(`[auto-update-checker] Skill sync warnings/failures: ${syncResult.failed.join(", ")}`);
|
|
22725
|
+
}
|
|
22726
|
+
if (syncResult.skippedExisting.length > 0) {
|
|
22727
|
+
log(`[auto-update-checker] Skill sync skipped existing: ${syncResult.skippedExisting.join(", ")}`);
|
|
22728
|
+
}
|
|
22729
|
+
} catch (err) {
|
|
22730
|
+
log("[auto-update-checker] Skill sync failed silently:", err);
|
|
22731
|
+
}
|
|
22732
|
+
let message = `v${currentVersion} → v${latestVersion}
|
|
22733
|
+
Restart OpenCode to apply.`;
|
|
22734
|
+
if (installedSkills.length > 0) {
|
|
22735
|
+
message = `v${currentVersion} → v${latestVersion}
|
|
22736
|
+
Added bundled skills: ${installedSkills.join(", ")}
|
|
22737
|
+
Restart OpenCode to apply.`;
|
|
22738
|
+
}
|
|
22739
|
+
showToast(ctx, "OMO-Slim Updated!", message, "success", 8000);
|
|
22629
22740
|
log(`[auto-update-checker] Update installed: ${currentVersion} → ${latestVersion}`);
|
|
22630
22741
|
} else {
|
|
22631
22742
|
showToast(ctx, `OMO-Slim ${latestVersion}`, `v${latestVersion} available, but auto-update failed to install it. Check logs or retry manually.`, "error", 8000);
|
|
@@ -22728,10 +22839,6 @@ function createDisplayNameMentionRewriter(config) {
|
|
|
22728
22839
|
};
|
|
22729
22840
|
}
|
|
22730
22841
|
// src/utils/task.ts
|
|
22731
|
-
var TRANSIENT_PROCESS_ERROR_TEXT = new Set([
|
|
22732
|
-
"Task is not running in this process and has no final output.",
|
|
22733
|
-
"Task is not running in this process and has not produced output."
|
|
22734
|
-
]);
|
|
22735
22842
|
function parseTaskIdFromTaskOutput(output) {
|
|
22736
22843
|
const xmlMatch = /<task\s+[^>]*\bid=["']([^"']+)["'][^>]*>/i.exec(output);
|
|
22737
22844
|
if (xmlMatch)
|
|
@@ -23134,11 +23241,13 @@ function normalizeCancelReason(reason) {
|
|
|
23134
23241
|
const normalized = reason?.replace(/\s+/g, " ").trim();
|
|
23135
23242
|
return normalized ? `cancelled: ${normalized}` : "cancelled";
|
|
23136
23243
|
}
|
|
23137
|
-
// src/utils/
|
|
23138
|
-
var SLIM_INTERNAL_INITIATOR_MARKER = "<!-- SLIM_INTERNAL_INITIATOR -->";
|
|
23244
|
+
// src/utils/guards.ts
|
|
23139
23245
|
function isRecord(value) {
|
|
23140
23246
|
return typeof value === "object" && value !== null;
|
|
23141
23247
|
}
|
|
23248
|
+
|
|
23249
|
+
// src/utils/internal-initiator.ts
|
|
23250
|
+
var SLIM_INTERNAL_INITIATOR_MARKER = "<!-- SLIM_INTERNAL_INITIATOR -->";
|
|
23142
23251
|
function createInternalAgentTextPart(text) {
|
|
23143
23252
|
return {
|
|
23144
23253
|
type: "text",
|
|
@@ -23178,8 +23287,8 @@ function isPwshAvailable() {
|
|
|
23178
23287
|
});
|
|
23179
23288
|
return result.status === 0;
|
|
23180
23289
|
}
|
|
23181
|
-
function escapePowerShellPath(
|
|
23182
|
-
return
|
|
23290
|
+
function escapePowerShellPath(path11) {
|
|
23291
|
+
return path11.replace(/'/g, "''");
|
|
23183
23292
|
}
|
|
23184
23293
|
function getWindowsZipExtractor() {
|
|
23185
23294
|
const buildNumber = getWindowsBuildNumber();
|
|
@@ -23392,7 +23501,7 @@ function detectDelegateTaskError(output) {
|
|
|
23392
23501
|
return null;
|
|
23393
23502
|
}
|
|
23394
23503
|
|
|
23395
|
-
// src/hooks/delegate-task-retry/
|
|
23504
|
+
// src/hooks/delegate-task-retry/hook.ts
|
|
23396
23505
|
function extractAvailableList(output) {
|
|
23397
23506
|
const match = output.match(/Allowed agents:\s*(.+)$/m);
|
|
23398
23507
|
if (match)
|
|
@@ -23422,7 +23531,6 @@ function buildRetryGuidance(errorInfo) {
|
|
|
23422
23531
|
return lines.join(`
|
|
23423
23532
|
`);
|
|
23424
23533
|
}
|
|
23425
|
-
// src/hooks/delegate-task-retry/hook.ts
|
|
23426
23534
|
function createDelegateTaskRetryHook(_ctx) {
|
|
23427
23535
|
return {
|
|
23428
23536
|
"tool.execute.after": async (input, output) => {
|
|
@@ -23548,12 +23656,6 @@ function isRateLimitError(error) {
|
|
|
23548
23656
|
].join(" ");
|
|
23549
23657
|
return RATE_LIMIT_PATTERNS.some((p) => p.test(text));
|
|
23550
23658
|
}
|
|
23551
|
-
function parseModel(model) {
|
|
23552
|
-
const slash = model.indexOf("/");
|
|
23553
|
-
if (slash <= 0 || slash >= model.length - 1)
|
|
23554
|
-
return null;
|
|
23555
|
-
return { providerID: model.slice(0, slash), modelID: model.slice(slash + 1) };
|
|
23556
|
-
}
|
|
23557
23659
|
var DEDUP_WINDOW_MS = 5000;
|
|
23558
23660
|
var REPROMPT_DELAY_MS = 500;
|
|
23559
23661
|
|
|
@@ -23671,7 +23773,7 @@ class ForegroundFallbackManager {
|
|
|
23671
23773
|
return;
|
|
23672
23774
|
}
|
|
23673
23775
|
tried.add(nextModel);
|
|
23674
|
-
const ref =
|
|
23776
|
+
const ref = parseModelReference(nextModel);
|
|
23675
23777
|
if (!ref) {
|
|
23676
23778
|
log("[foreground-fallback] invalid model format", {
|
|
23677
23779
|
sessionID,
|
|
@@ -23748,15 +23850,15 @@ class ForegroundFallbackManager {
|
|
|
23748
23850
|
// src/hooks/image-hook.ts
|
|
23749
23851
|
import { createHash } from "node:crypto";
|
|
23750
23852
|
import {
|
|
23751
|
-
existsSync as
|
|
23752
|
-
mkdirSync as
|
|
23753
|
-
readdirSync as
|
|
23853
|
+
existsSync as existsSync6,
|
|
23854
|
+
mkdirSync as mkdirSync4,
|
|
23855
|
+
readdirSync as readdirSync3,
|
|
23754
23856
|
rmdirSync,
|
|
23755
23857
|
statSync as statSync3,
|
|
23756
23858
|
unlinkSync as unlinkSync2,
|
|
23757
23859
|
writeFileSync as writeFileSync4
|
|
23758
23860
|
} from "node:fs";
|
|
23759
|
-
import { basename as basename2, extname, join as
|
|
23861
|
+
import { basename as basename2, extname, join as join10 } from "node:path";
|
|
23760
23862
|
var lastCleanupByDir = new Map;
|
|
23761
23863
|
var CLEANUP_INTERVAL = 10 * 60 * 1000;
|
|
23762
23864
|
function isImagePart(p) {
|
|
@@ -23803,8 +23905,8 @@ function cleanupAllSessions(saveDir) {
|
|
|
23803
23905
|
const maxAge = 60 * 60 * 1000;
|
|
23804
23906
|
const dirsToScan = [];
|
|
23805
23907
|
try {
|
|
23806
|
-
for (const entry of
|
|
23807
|
-
const fp =
|
|
23908
|
+
for (const entry of readdirSync3(saveDir, { withFileTypes: true })) {
|
|
23909
|
+
const fp = join10(saveDir, entry.name);
|
|
23808
23910
|
if (entry.isDirectory()) {
|
|
23809
23911
|
dirsToScan.push(fp);
|
|
23810
23912
|
} else {
|
|
@@ -23819,9 +23921,9 @@ function cleanupAllSessions(saveDir) {
|
|
|
23819
23921
|
try {
|
|
23820
23922
|
let isEmpty = true;
|
|
23821
23923
|
let allRemoved = true;
|
|
23822
|
-
for (const f of
|
|
23924
|
+
for (const f of readdirSync3(dir)) {
|
|
23823
23925
|
isEmpty = false;
|
|
23824
|
-
const fp =
|
|
23926
|
+
const fp = join10(dir, f);
|
|
23825
23927
|
try {
|
|
23826
23928
|
if (now - statSync3(fp).mtimeMs > maxAge) {
|
|
23827
23929
|
unlinkSync2(fp);
|
|
@@ -23843,8 +23945,8 @@ function cleanupAllSessions(saveDir) {
|
|
|
23843
23945
|
function writeUniqueFile(dir, name, data, log2) {
|
|
23844
23946
|
const ext = extname(name);
|
|
23845
23947
|
const base = basename2(name, ext) || name;
|
|
23846
|
-
let candidate =
|
|
23847
|
-
if (
|
|
23948
|
+
let candidate = join10(dir, name);
|
|
23949
|
+
if (existsSync6(candidate)) {
|
|
23848
23950
|
return candidate;
|
|
23849
23951
|
}
|
|
23850
23952
|
let counter = 0;
|
|
@@ -23856,7 +23958,7 @@ function writeUniqueFile(dir, name, data, log2) {
|
|
|
23856
23958
|
} catch (e) {
|
|
23857
23959
|
if (e instanceof Error && e.code === "EEXIST") {
|
|
23858
23960
|
counter += 1;
|
|
23859
|
-
candidate =
|
|
23961
|
+
candidate = join10(dir, `${base}-${counter}${ext}`);
|
|
23860
23962
|
continue;
|
|
23861
23963
|
}
|
|
23862
23964
|
log2(`[image-hook] failed to save image: ${e}`);
|
|
@@ -23880,16 +23982,16 @@ function processImageAttachments(args) {
|
|
|
23880
23982
|
messagesWithImages.push({ msg, imageParts });
|
|
23881
23983
|
}
|
|
23882
23984
|
}
|
|
23883
|
-
const saveDir =
|
|
23985
|
+
const saveDir = join10(workDir, ".opencode", "images");
|
|
23884
23986
|
if (messagesWithImages.length === 0) {
|
|
23885
|
-
if (
|
|
23987
|
+
if (existsSync6(saveDir))
|
|
23886
23988
|
cleanupAllSessions(saveDir);
|
|
23887
23989
|
return;
|
|
23888
23990
|
}
|
|
23889
|
-
const gitignorePath =
|
|
23991
|
+
const gitignorePath = join10(workDir, ".opencode", ".gitignore");
|
|
23890
23992
|
try {
|
|
23891
|
-
|
|
23892
|
-
if (!
|
|
23993
|
+
mkdirSync4(saveDir, { recursive: true });
|
|
23994
|
+
if (!existsSync6(gitignorePath))
|
|
23893
23995
|
writeFileSync4(gitignorePath, `*
|
|
23894
23996
|
`);
|
|
23895
23997
|
} catch (e) {
|
|
@@ -23898,9 +24000,9 @@ function processImageAttachments(args) {
|
|
|
23898
24000
|
cleanupAllSessions(saveDir);
|
|
23899
24001
|
for (const { msg, imageParts } of messagesWithImages) {
|
|
23900
24002
|
const sessionSubdir = msg.info.sessionID ? sanitizeFilename(msg.info.sessionID) : undefined;
|
|
23901
|
-
const targetDir = sessionSubdir ?
|
|
24003
|
+
const targetDir = sessionSubdir ? join10(saveDir, sessionSubdir) : saveDir;
|
|
23902
24004
|
try {
|
|
23903
|
-
|
|
24005
|
+
mkdirSync4(targetDir, { recursive: true });
|
|
23904
24006
|
} catch (e) {
|
|
23905
24007
|
log2(`[image-hook] failed to create target image directory: ${e}`);
|
|
23906
24008
|
}
|
|
@@ -23984,7 +24086,6 @@ ${JSON_ERROR_REMINDER}`;
|
|
|
23984
24086
|
};
|
|
23985
24087
|
}
|
|
23986
24088
|
// src/hooks/phase-reminder/index.ts
|
|
23987
|
-
var PHASE_REMINDER = `<internal_reminder>${PHASE_REMINDER_TEXT}</internal_reminder>`;
|
|
23988
24089
|
function createPhaseReminderHook() {
|
|
23989
24090
|
return {
|
|
23990
24091
|
"experimental.chat.messages.transform": async (_input, output) => {
|
|
@@ -24026,24 +24127,18 @@ function createPhaseReminderHook() {
|
|
|
24026
24127
|
};
|
|
24027
24128
|
}
|
|
24028
24129
|
// src/hooks/post-file-tool-nudge/index.ts
|
|
24029
|
-
var POST_FILE_TOOL_NUDGE = PHASE_REMINDER_TEXT;
|
|
24030
24130
|
var FILE_TOOLS = new Set(["Read", "read", "Write", "write"]);
|
|
24031
24131
|
function createPostFileToolNudgeHook(options = {}) {
|
|
24032
24132
|
function appendReminder(output) {
|
|
24033
24133
|
if (typeof output.output !== "string") {
|
|
24034
24134
|
return;
|
|
24035
24135
|
}
|
|
24036
|
-
if (output.output.includes(
|
|
24136
|
+
if (output.output.includes(PHASE_REMINDER)) {
|
|
24037
24137
|
return;
|
|
24038
24138
|
}
|
|
24039
|
-
output.output =
|
|
24040
|
-
|
|
24041
|
-
|
|
24042
|
-
"<internal_reminder>",
|
|
24043
|
-
POST_FILE_TOOL_NUDGE,
|
|
24044
|
-
"</internal_reminder>"
|
|
24045
|
-
].join(`
|
|
24046
|
-
`);
|
|
24139
|
+
output.output = `${output.output}
|
|
24140
|
+
|
|
24141
|
+
${PHASE_REMINDER}`;
|
|
24047
24142
|
}
|
|
24048
24143
|
return {
|
|
24049
24144
|
"tool.execute.after": async (input, output) => {
|
|
@@ -24057,8 +24152,59 @@ function createPostFileToolNudgeHook(options = {}) {
|
|
|
24057
24152
|
}
|
|
24058
24153
|
};
|
|
24059
24154
|
}
|
|
24155
|
+
// src/hooks/reflect/index.ts
|
|
24156
|
+
var COMMAND_NAME2 = "reflect";
|
|
24157
|
+
function activationPrompt2(focus) {
|
|
24158
|
+
const focusBlock = focus ? ["Focus:", focus] : [
|
|
24159
|
+
"Focus:",
|
|
24160
|
+
"Review recent work broadly and identify repeated workflow friction worth improving."
|
|
24161
|
+
];
|
|
24162
|
+
return [
|
|
24163
|
+
"Use the reflect skill for this request.",
|
|
24164
|
+
"",
|
|
24165
|
+
"Reflect requirements:",
|
|
24166
|
+
"- inspect existing skills, commands, agents, prompt overrides, MCP permissions, config, and project playbooks before suggesting anything new;",
|
|
24167
|
+
"- find repeated workflow patterns from the current conversation, project notes, local memories, logs, or session artifacts that are available and safe to inspect;",
|
|
24168
|
+
"- prefer evidence from repeated recent behavior over speculation;",
|
|
24169
|
+
"- recommend the smallest useful improvement: prompt/config rule, skill, command, custom agent, MCP/tool permission change, project playbook, or skip;",
|
|
24170
|
+
"- treat creating nothing as a valid result when evidence is weak;",
|
|
24171
|
+
"- ask before changing prompts, skills, commands, agents, MCP access, or config unless the user explicitly requested the exact edit;",
|
|
24172
|
+
"- return a compact report with findings, recommended changes, skipped candidates, and items needing more evidence.",
|
|
24173
|
+
"",
|
|
24174
|
+
...focusBlock
|
|
24175
|
+
].join(`
|
|
24176
|
+
`);
|
|
24177
|
+
}
|
|
24178
|
+
function createReflectCommandHook() {
|
|
24179
|
+
let shouldHandleCommand = false;
|
|
24180
|
+
return {
|
|
24181
|
+
registerCommand: (opencodeConfig) => {
|
|
24182
|
+
const commandConfig = opencodeConfig.command;
|
|
24183
|
+
if (commandConfig?.[COMMAND_NAME2]) {
|
|
24184
|
+
shouldHandleCommand = false;
|
|
24185
|
+
return;
|
|
24186
|
+
}
|
|
24187
|
+
if (!opencodeConfig.command)
|
|
24188
|
+
opencodeConfig.command = {};
|
|
24189
|
+
opencodeConfig.command[COMMAND_NAME2] = {
|
|
24190
|
+
template: "Review repeated work and suggest workflow improvements",
|
|
24191
|
+
description: "Use reflect to learn from repeated workflows and suggest reusable improvements"
|
|
24192
|
+
};
|
|
24193
|
+
shouldHandleCommand = true;
|
|
24194
|
+
},
|
|
24195
|
+
handleCommandExecuteBefore: async (input, output) => {
|
|
24196
|
+
if (input.command !== COMMAND_NAME2 || !shouldHandleCommand)
|
|
24197
|
+
return;
|
|
24198
|
+
output.parts.length = 0;
|
|
24199
|
+
output.parts.push({
|
|
24200
|
+
type: "text",
|
|
24201
|
+
text: activationPrompt2(input.arguments.trim())
|
|
24202
|
+
});
|
|
24203
|
+
}
|
|
24204
|
+
};
|
|
24205
|
+
}
|
|
24060
24206
|
// src/hooks/task-session-manager/index.ts
|
|
24061
|
-
import
|
|
24207
|
+
import path11 from "node:path";
|
|
24062
24208
|
var AGENT_NAME_SET = new Set([
|
|
24063
24209
|
"orchestrator",
|
|
24064
24210
|
"oracle",
|
|
@@ -24104,9 +24250,6 @@ function createOccurrenceId(part, message, partIndex) {
|
|
|
24104
24250
|
function isAgentName(value) {
|
|
24105
24251
|
return typeof value === "string" && AGENT_NAME_SET.has(value);
|
|
24106
24252
|
}
|
|
24107
|
-
function isObjectRecord(value) {
|
|
24108
|
-
return typeof value === "object" && value !== null;
|
|
24109
|
-
}
|
|
24110
24253
|
function extractPath(output) {
|
|
24111
24254
|
return /<path>([^<]+)<\/path>/.exec(output)?.[1];
|
|
24112
24255
|
}
|
|
@@ -24115,8 +24258,8 @@ function extractTaskSummary(output) {
|
|
|
24115
24258
|
return summary?.trim() || undefined;
|
|
24116
24259
|
}
|
|
24117
24260
|
function normalizePath(root, file) {
|
|
24118
|
-
const relative =
|
|
24119
|
-
if (!relative || relative.startsWith("..") ||
|
|
24261
|
+
const relative = path11.relative(root, file);
|
|
24262
|
+
if (!relative || relative.startsWith("..") || path11.isAbsolute(relative)) {
|
|
24120
24263
|
return file;
|
|
24121
24264
|
}
|
|
24122
24265
|
return relative;
|
|
@@ -24384,7 +24527,7 @@ function createTaskSessionManagerHook(_ctx, options) {
|
|
|
24384
24527
|
if (!input.sessionID || !options.shouldManageSession(input.sessionID)) {
|
|
24385
24528
|
return;
|
|
24386
24529
|
}
|
|
24387
|
-
if (!
|
|
24530
|
+
if (!isRecord(output.args))
|
|
24388
24531
|
return;
|
|
24389
24532
|
const args = output.args;
|
|
24390
24533
|
if (!isAgentName(args.subagent_type)) {
|
|
@@ -24658,7 +24801,7 @@ function createTaskSessionManagerHook(_ctx, options) {
|
|
|
24658
24801
|
result: status.result
|
|
24659
24802
|
});
|
|
24660
24803
|
output.output = formatCancelledTaskStatusOutput(status.taskID, existing?.resultSummary);
|
|
24661
|
-
if (
|
|
24804
|
+
if (isRecord(output) && isRecord(output.metadata)) {
|
|
24662
24805
|
output.metadata.state = "cancelled";
|
|
24663
24806
|
}
|
|
24664
24807
|
}
|
|
@@ -24682,7 +24825,7 @@ function formatCancelledTaskStatusOutput(taskID, summary = "cancelled") {
|
|
|
24682
24825
|
`);
|
|
24683
24826
|
}
|
|
24684
24827
|
// src/interview/manager.ts
|
|
24685
|
-
import
|
|
24828
|
+
import path15 from "node:path";
|
|
24686
24829
|
|
|
24687
24830
|
// src/interview/dashboard.ts
|
|
24688
24831
|
import crypto from "node:crypto";
|
|
@@ -24692,27 +24835,27 @@ import {
|
|
|
24692
24835
|
createServer
|
|
24693
24836
|
} from "node:http";
|
|
24694
24837
|
import os4 from "node:os";
|
|
24695
|
-
import
|
|
24838
|
+
import path13 from "node:path";
|
|
24696
24839
|
import { URL as URL2 } from "node:url";
|
|
24697
24840
|
|
|
24698
24841
|
// src/interview/document.ts
|
|
24699
24842
|
import * as fsSync from "node:fs";
|
|
24700
24843
|
import * as fs6 from "node:fs/promises";
|
|
24701
|
-
import * as
|
|
24844
|
+
import * as path12 from "node:path";
|
|
24702
24845
|
var DEFAULT_OUTPUT_FOLDER = "interview";
|
|
24703
24846
|
function normalizeOutputFolder(outputFolder) {
|
|
24704
24847
|
const normalized = outputFolder.trim().replace(/^\/+|\/+$/g, "");
|
|
24705
24848
|
return normalized || DEFAULT_OUTPUT_FOLDER;
|
|
24706
24849
|
}
|
|
24707
24850
|
function createInterviewDirectoryPath(directory, outputFolder) {
|
|
24708
|
-
return
|
|
24851
|
+
return path12.join(directory, normalizeOutputFolder(outputFolder));
|
|
24709
24852
|
}
|
|
24710
24853
|
function createInterviewFilePath(directory, outputFolder, idea) {
|
|
24711
24854
|
const fileName = `${slugify(idea) || "interview"}.md`;
|
|
24712
|
-
return
|
|
24855
|
+
return path12.join(createInterviewDirectoryPath(directory, outputFolder), fileName);
|
|
24713
24856
|
}
|
|
24714
24857
|
function relativeInterviewPath(directory, filePath) {
|
|
24715
|
-
return
|
|
24858
|
+
return path12.relative(directory, filePath) || path12.basename(filePath);
|
|
24716
24859
|
}
|
|
24717
24860
|
function resolveExistingInterviewPath(directory, outputFolder, value) {
|
|
24718
24861
|
const trimmed = value.trim();
|
|
@@ -24721,22 +24864,22 @@ function resolveExistingInterviewPath(directory, outputFolder, value) {
|
|
|
24721
24864
|
}
|
|
24722
24865
|
const outputDir = createInterviewDirectoryPath(directory, outputFolder);
|
|
24723
24866
|
const candidates = new Set;
|
|
24724
|
-
const resolvedRoot =
|
|
24725
|
-
if (
|
|
24867
|
+
const resolvedRoot = path12.resolve(directory);
|
|
24868
|
+
if (path12.isAbsolute(trimmed)) {
|
|
24726
24869
|
candidates.add(trimmed);
|
|
24727
24870
|
} else {
|
|
24728
|
-
candidates.add(
|
|
24729
|
-
candidates.add(
|
|
24871
|
+
candidates.add(path12.resolve(directory, trimmed));
|
|
24872
|
+
candidates.add(path12.join(outputDir, trimmed));
|
|
24730
24873
|
if (!trimmed.endsWith(".md")) {
|
|
24731
|
-
candidates.add(
|
|
24874
|
+
candidates.add(path12.join(outputDir, `${trimmed}.md`));
|
|
24732
24875
|
}
|
|
24733
24876
|
}
|
|
24734
24877
|
for (const candidate of candidates) {
|
|
24735
|
-
if (
|
|
24878
|
+
if (path12.extname(candidate) !== ".md") {
|
|
24736
24879
|
continue;
|
|
24737
24880
|
}
|
|
24738
|
-
const resolved =
|
|
24739
|
-
if (!resolved.startsWith(resolvedRoot +
|
|
24881
|
+
const resolved = path12.resolve(candidate);
|
|
24882
|
+
if (!resolved.startsWith(resolvedRoot + path12.sep) && resolved !== resolvedRoot) {
|
|
24740
24883
|
continue;
|
|
24741
24884
|
}
|
|
24742
24885
|
if (fsSync.existsSync(candidate)) {
|
|
@@ -24816,7 +24959,7 @@ function parseFrontmatter(content) {
|
|
|
24816
24959
|
return result;
|
|
24817
24960
|
}
|
|
24818
24961
|
async function ensureInterviewFile(record) {
|
|
24819
|
-
await fs6.mkdir(
|
|
24962
|
+
await fs6.mkdir(path12.dirname(record.markdownPath), { recursive: true });
|
|
24820
24963
|
try {
|
|
24821
24964
|
await fs6.access(record.markdownPath);
|
|
24822
24965
|
} catch {
|
|
@@ -26486,12 +26629,12 @@ function renderInterviewPage(interviewId, resumeSlug) {
|
|
|
26486
26629
|
|
|
26487
26630
|
// src/interview/dashboard.ts
|
|
26488
26631
|
function getAuthFilePath(port) {
|
|
26489
|
-
const dataHome = process.env.XDG_DATA_HOME ||
|
|
26490
|
-
return
|
|
26632
|
+
const dataHome = process.env.XDG_DATA_HOME || path13.join(os4.homedir(), ".local", "share");
|
|
26633
|
+
return path13.join(dataHome, "opencode", `.dashboard-${port}.json`);
|
|
26491
26634
|
}
|
|
26492
26635
|
function writeAuthFile(port, token) {
|
|
26493
26636
|
const filePath = getAuthFilePath(port);
|
|
26494
|
-
const dir =
|
|
26637
|
+
const dir = path13.dirname(filePath);
|
|
26495
26638
|
try {
|
|
26496
26639
|
fsSync2.mkdirSync(dir, { recursive: true });
|
|
26497
26640
|
} catch {}
|
|
@@ -26628,7 +26771,7 @@ function createDashboardServer(config) {
|
|
|
26628
26771
|
const directories = getKnownDirectories();
|
|
26629
26772
|
const items = [];
|
|
26630
26773
|
for (const dir of directories) {
|
|
26631
|
-
const interviewDir =
|
|
26774
|
+
const interviewDir = path13.join(dir, config.outputFolder);
|
|
26632
26775
|
let entries;
|
|
26633
26776
|
try {
|
|
26634
26777
|
entries = await fs7.readdir(interviewDir);
|
|
@@ -26640,7 +26783,7 @@ function createDashboardServer(config) {
|
|
|
26640
26783
|
continue;
|
|
26641
26784
|
let content;
|
|
26642
26785
|
try {
|
|
26643
|
-
content = await fs7.readFile(
|
|
26786
|
+
content = await fs7.readFile(path13.join(interviewDir, entry), "utf8");
|
|
26644
26787
|
} catch {
|
|
26645
26788
|
continue;
|
|
26646
26789
|
}
|
|
@@ -26666,7 +26809,7 @@ function createDashboardServer(config) {
|
|
|
26666
26809
|
const directories = getKnownDirectories();
|
|
26667
26810
|
let rebuilt = 0;
|
|
26668
26811
|
for (const dir of directories) {
|
|
26669
|
-
const interviewDir =
|
|
26812
|
+
const interviewDir = path13.join(dir, config.outputFolder);
|
|
26670
26813
|
let entries;
|
|
26671
26814
|
try {
|
|
26672
26815
|
entries = await fs7.readdir(interviewDir);
|
|
@@ -26678,7 +26821,7 @@ function createDashboardServer(config) {
|
|
|
26678
26821
|
continue;
|
|
26679
26822
|
let content;
|
|
26680
26823
|
try {
|
|
26681
|
-
content = await fs7.readFile(
|
|
26824
|
+
content = await fs7.readFile(path13.join(interviewDir, entry), "utf8");
|
|
26682
26825
|
} catch {
|
|
26683
26826
|
continue;
|
|
26684
26827
|
}
|
|
@@ -26704,7 +26847,7 @@ function createDashboardServer(config) {
|
|
|
26704
26847
|
questions: [],
|
|
26705
26848
|
pendingAnswers: null,
|
|
26706
26849
|
lastUpdatedAt: fm.updatedAt ? new Date(fm.updatedAt).getTime() : Date.now(),
|
|
26707
|
-
filePath:
|
|
26850
|
+
filePath: path13.join(interviewDir, entry),
|
|
26708
26851
|
nudgeAction: null
|
|
26709
26852
|
});
|
|
26710
26853
|
if (!sessions.has(fm.sessionID)) {
|
|
@@ -26968,7 +27111,7 @@ function createDashboardServer(config) {
|
|
|
26968
27111
|
const dirs = getKnownDirectories();
|
|
26969
27112
|
for (const dir of dirs) {
|
|
26970
27113
|
const slug = extractResumeSlug(interviewId);
|
|
26971
|
-
const candidate =
|
|
27114
|
+
const candidate = path13.join(dir, config.outputFolder, `${slug}.md`);
|
|
26972
27115
|
try {
|
|
26973
27116
|
document = await fs7.readFile(candidate, "utf8");
|
|
26974
27117
|
markdownPath = candidate;
|
|
@@ -27496,7 +27639,7 @@ function createInterviewServer(deps) {
|
|
|
27496
27639
|
// src/interview/service.ts
|
|
27497
27640
|
import { spawn as spawn2 } from "node:child_process";
|
|
27498
27641
|
import * as fs8 from "node:fs/promises";
|
|
27499
|
-
import * as
|
|
27642
|
+
import * as path14 from "node:path";
|
|
27500
27643
|
|
|
27501
27644
|
// src/interview/types.ts
|
|
27502
27645
|
import { z as z3 } from "zod";
|
|
@@ -27668,7 +27811,7 @@ function buildAnswerPrompt(answers, questions, maxQuestions) {
|
|
|
27668
27811
|
}
|
|
27669
27812
|
|
|
27670
27813
|
// src/interview/service.ts
|
|
27671
|
-
var
|
|
27814
|
+
var COMMAND_NAME3 = "interview";
|
|
27672
27815
|
var DEFAULT_MAX_QUESTIONS = 2;
|
|
27673
27816
|
function isTruthyEnvFlag(value) {
|
|
27674
27817
|
if (!value) {
|
|
@@ -27676,12 +27819,12 @@ function isTruthyEnvFlag(value) {
|
|
|
27676
27819
|
}
|
|
27677
27820
|
return value !== "0" && value.toLowerCase() !== "false";
|
|
27678
27821
|
}
|
|
27679
|
-
function isAutomatedRuntime(
|
|
27680
|
-
return
|
|
27822
|
+
function isAutomatedRuntime(env) {
|
|
27823
|
+
return env.NODE_ENV === "test" || isTruthyEnvFlag(env.CI) || isTruthyEnvFlag(env.BUN_TEST) || isTruthyEnvFlag(env.VITEST) || env.JEST_WORKER_ID !== undefined;
|
|
27681
27824
|
}
|
|
27682
|
-
function shouldAutoOpenBrowser(config,
|
|
27825
|
+
function shouldAutoOpenBrowser(config, env) {
|
|
27683
27826
|
const requested = config?.autoOpenBrowser ?? true;
|
|
27684
|
-
return requested && !isAutomatedRuntime(
|
|
27827
|
+
return requested && !isAutomatedRuntime(env);
|
|
27685
27828
|
}
|
|
27686
27829
|
function openBrowser(url) {
|
|
27687
27830
|
const platform2 = process.platform;
|
|
@@ -27763,12 +27906,12 @@ function createInterviewService(ctx, config, deps) {
|
|
|
27763
27906
|
if (!newSlug) {
|
|
27764
27907
|
return;
|
|
27765
27908
|
}
|
|
27766
|
-
const currentFileName =
|
|
27909
|
+
const currentFileName = path14.basename(interview.markdownPath, ".md");
|
|
27767
27910
|
if (currentFileName === newSlug) {
|
|
27768
27911
|
return;
|
|
27769
27912
|
}
|
|
27770
|
-
const dir =
|
|
27771
|
-
const newPath =
|
|
27913
|
+
const dir = path14.dirname(interview.markdownPath);
|
|
27914
|
+
const newPath = path14.join(dir, `${newSlug}.md`);
|
|
27772
27915
|
try {
|
|
27773
27916
|
await fs8.access(newPath);
|
|
27774
27917
|
return;
|
|
@@ -27844,9 +27987,9 @@ function createInterviewService(ctx, config, deps) {
|
|
|
27844
27987
|
const messages = await loadMessages(sessionID);
|
|
27845
27988
|
const title = extractTitle(document);
|
|
27846
27989
|
const record = {
|
|
27847
|
-
id: `${Date.now()}-${++idCounter}-${slugify(
|
|
27990
|
+
id: `${Date.now()}-${++idCounter}-${slugify(path14.basename(markdownPath, ".md")) || "interview"}`,
|
|
27848
27991
|
sessionID,
|
|
27849
|
-
idea: title ||
|
|
27992
|
+
idea: title || path14.basename(markdownPath, ".md"),
|
|
27850
27993
|
markdownPath,
|
|
27851
27994
|
createdAt: nowIso(),
|
|
27852
27995
|
status: "active",
|
|
@@ -27915,11 +28058,11 @@ function createInterviewService(ctx, config, deps) {
|
|
|
27915
28058
|
}
|
|
27916
28059
|
function registerCommand(opencodeConfig) {
|
|
27917
28060
|
const configCommand = opencodeConfig.command;
|
|
27918
|
-
if (!configCommand?.[
|
|
28061
|
+
if (!configCommand?.[COMMAND_NAME3]) {
|
|
27919
28062
|
if (!opencodeConfig.command) {
|
|
27920
28063
|
opencodeConfig.command = {};
|
|
27921
28064
|
}
|
|
27922
|
-
opencodeConfig.command[
|
|
28065
|
+
opencodeConfig.command[COMMAND_NAME3] = {
|
|
27923
28066
|
template: "Start an interview and write a live markdown spec",
|
|
27924
28067
|
description: "Open a localhost interview UI linked to the current OpenCode session"
|
|
27925
28068
|
};
|
|
@@ -27993,7 +28136,7 @@ function createInterviewService(ctx, config, deps) {
|
|
|
27993
28136
|
}
|
|
27994
28137
|
}
|
|
27995
28138
|
async function handleCommandExecuteBefore(input, output) {
|
|
27996
|
-
if (input.command !==
|
|
28139
|
+
if (input.command !== COMMAND_NAME3) {
|
|
27997
28140
|
return;
|
|
27998
28141
|
}
|
|
27999
28142
|
const idea = input.arguments.trim();
|
|
@@ -28073,7 +28216,7 @@ function createInterviewService(ctx, config, deps) {
|
|
|
28073
28216
|
return fileCache.items;
|
|
28074
28217
|
}
|
|
28075
28218
|
const outputDir = createInterviewDirectoryPath(ctx.directory, outputFolder);
|
|
28076
|
-
const activePaths = new Set([...interviewsById.values()].filter((i) => i.status === "active").map((i) =>
|
|
28219
|
+
const activePaths = new Set([...interviewsById.values()].filter((i) => i.status === "active").map((i) => path14.resolve(i.markdownPath)));
|
|
28077
28220
|
let entries;
|
|
28078
28221
|
try {
|
|
28079
28222
|
entries = await fs8.readdir(outputDir);
|
|
@@ -28084,8 +28227,8 @@ function createInterviewService(ctx, config, deps) {
|
|
|
28084
28227
|
for (const entry of entries) {
|
|
28085
28228
|
if (!entry.endsWith(".md"))
|
|
28086
28229
|
continue;
|
|
28087
|
-
const fullPath =
|
|
28088
|
-
if (activePaths.has(
|
|
28230
|
+
const fullPath = path14.join(outputDir, entry);
|
|
28231
|
+
if (activePaths.has(path14.resolve(fullPath)))
|
|
28089
28232
|
continue;
|
|
28090
28233
|
let content;
|
|
28091
28234
|
try {
|
|
@@ -28184,7 +28327,7 @@ function createInterviewManager(ctx, config) {
|
|
|
28184
28327
|
const outputFolder = interviewConfig?.outputFolder ?? "interview";
|
|
28185
28328
|
if (!dashboardEnabled) {
|
|
28186
28329
|
const service2 = createInterviewService(ctx, interviewConfig);
|
|
28187
|
-
const resolvedOutputPath =
|
|
28330
|
+
const resolvedOutputPath = path15.join(ctx.directory, outputFolder);
|
|
28188
28331
|
const server = createInterviewServer({
|
|
28189
28332
|
getState: async (interviewId) => service2.getInterviewState(interviewId),
|
|
28190
28333
|
listInterviewFiles: async () => service2.listInterviewFiles(),
|
|
@@ -28289,7 +28432,7 @@ function createInterviewManager(ctx, config) {
|
|
|
28289
28432
|
listInterviews: () => service.listInterviews(),
|
|
28290
28433
|
submitAnswers: async (interviewId, answers) => service.submitAnswers(interviewId, answers),
|
|
28291
28434
|
handleNudgeAction: async (interviewId, action) => service.handleNudgeAction(interviewId, action),
|
|
28292
|
-
outputFolder:
|
|
28435
|
+
outputFolder: path15.join(ctx.directory, outputFolder),
|
|
28293
28436
|
port: 0
|
|
28294
28437
|
});
|
|
28295
28438
|
service.setBaseUrlResolver(() => perSessionServer.ensureStarted());
|
|
@@ -28774,23 +28917,23 @@ class TmuxMultiplexer {
|
|
|
28774
28917
|
return null;
|
|
28775
28918
|
}
|
|
28776
28919
|
const stdout = await proc.stdout();
|
|
28777
|
-
const
|
|
28920
|
+
const path16 = stdout.trim().split(`
|
|
28778
28921
|
`)[0];
|
|
28779
|
-
if (!
|
|
28922
|
+
if (!path16) {
|
|
28780
28923
|
log("[tmux] findBinary: no path in output");
|
|
28781
28924
|
return null;
|
|
28782
28925
|
}
|
|
28783
|
-
const verifyProc = crossSpawn([
|
|
28926
|
+
const verifyProc = crossSpawn([path16, "-V"], {
|
|
28784
28927
|
stdout: "pipe",
|
|
28785
28928
|
stderr: "pipe"
|
|
28786
28929
|
});
|
|
28787
28930
|
const verifyExit = await verifyProc.exited;
|
|
28788
28931
|
if (verifyExit !== 0) {
|
|
28789
|
-
log("[tmux] findBinary: tmux -V failed", { path:
|
|
28932
|
+
log("[tmux] findBinary: tmux -V failed", { path: path16, verifyExit });
|
|
28790
28933
|
return null;
|
|
28791
28934
|
}
|
|
28792
|
-
log("[tmux] findBinary: found", { path:
|
|
28793
|
-
return
|
|
28935
|
+
log("[tmux] findBinary: found", { path: path16 });
|
|
28936
|
+
return path16;
|
|
28794
28937
|
} catch (err) {
|
|
28795
28938
|
log("[tmux] findBinary: exception", { error: String(err) });
|
|
28796
28939
|
return null;
|
|
@@ -29745,18 +29888,18 @@ async function isServerRunning(serverUrl, timeoutMs = 3000, maxAttempts = 2) {
|
|
|
29745
29888
|
import { tool } from "@opencode-ai/plugin";
|
|
29746
29889
|
|
|
29747
29890
|
// src/tools/ast-grep/cli.ts
|
|
29748
|
-
import { existsSync as
|
|
29891
|
+
import { existsSync as existsSync10 } from "node:fs";
|
|
29749
29892
|
|
|
29750
29893
|
// src/tools/ast-grep/constants.ts
|
|
29751
|
-
import { existsSync as
|
|
29894
|
+
import { existsSync as existsSync9, statSync as statSync4 } from "node:fs";
|
|
29752
29895
|
import { createRequire as createRequire3 } from "node:module";
|
|
29753
|
-
import { dirname as
|
|
29896
|
+
import { dirname as dirname8, join as join14 } from "node:path";
|
|
29754
29897
|
|
|
29755
29898
|
// src/tools/ast-grep/downloader.ts
|
|
29756
|
-
import { chmodSync, existsSync as
|
|
29899
|
+
import { chmodSync, existsSync as existsSync8, mkdirSync as mkdirSync6, unlinkSync as unlinkSync4 } from "node:fs";
|
|
29757
29900
|
import { createRequire as createRequire2 } from "node:module";
|
|
29758
29901
|
import { homedir as homedir5 } from "node:os";
|
|
29759
|
-
import { join as
|
|
29902
|
+
import { join as join13 } from "node:path";
|
|
29760
29903
|
var REPO = "ast-grep/ast-grep";
|
|
29761
29904
|
var DEFAULT_VERSION = "0.40.0";
|
|
29762
29905
|
function getAstGrepVersion() {
|
|
@@ -29780,19 +29923,19 @@ var PLATFORM_MAP = {
|
|
|
29780
29923
|
function getCacheDir2() {
|
|
29781
29924
|
if (process.platform === "win32") {
|
|
29782
29925
|
const localAppData = process.env.LOCALAPPDATA || process.env.APPDATA;
|
|
29783
|
-
const base2 = localAppData ||
|
|
29784
|
-
return
|
|
29926
|
+
const base2 = localAppData || join13(homedir5(), "AppData", "Local");
|
|
29927
|
+
return join13(base2, "oh-my-opencode-slim", "bin");
|
|
29785
29928
|
}
|
|
29786
29929
|
const xdgCache = process.env.XDG_CACHE_HOME;
|
|
29787
|
-
const base = xdgCache ||
|
|
29788
|
-
return
|
|
29930
|
+
const base = xdgCache || join13(homedir5(), ".cache");
|
|
29931
|
+
return join13(base, "oh-my-opencode-slim", "bin");
|
|
29789
29932
|
}
|
|
29790
29933
|
function getBinaryName() {
|
|
29791
29934
|
return process.platform === "win32" ? "sg.exe" : "sg";
|
|
29792
29935
|
}
|
|
29793
29936
|
function getCachedBinaryPath() {
|
|
29794
|
-
const binaryPath2 =
|
|
29795
|
-
return
|
|
29937
|
+
const binaryPath2 = join13(getCacheDir2(), getBinaryName());
|
|
29938
|
+
return existsSync8(binaryPath2) ? binaryPath2 : null;
|
|
29796
29939
|
}
|
|
29797
29940
|
async function downloadAstGrep(version = DEFAULT_VERSION) {
|
|
29798
29941
|
const platformKey = `${process.platform}-${process.arch}`;
|
|
@@ -29803,8 +29946,8 @@ async function downloadAstGrep(version = DEFAULT_VERSION) {
|
|
|
29803
29946
|
}
|
|
29804
29947
|
const cacheDir = getCacheDir2();
|
|
29805
29948
|
const binaryName = getBinaryName();
|
|
29806
|
-
const binaryPath2 =
|
|
29807
|
-
if (
|
|
29949
|
+
const binaryPath2 = join13(cacheDir, binaryName);
|
|
29950
|
+
if (existsSync8(binaryPath2)) {
|
|
29808
29951
|
return binaryPath2;
|
|
29809
29952
|
}
|
|
29810
29953
|
const { arch, os: os5 } = platformInfo;
|
|
@@ -29812,21 +29955,21 @@ async function downloadAstGrep(version = DEFAULT_VERSION) {
|
|
|
29812
29955
|
const downloadUrl = `https://github.com/${REPO}/releases/download/${version}/${assetName}`;
|
|
29813
29956
|
console.log(`[oh-my-opencode-slim] Downloading ast-grep binary...`);
|
|
29814
29957
|
try {
|
|
29815
|
-
if (!
|
|
29816
|
-
|
|
29958
|
+
if (!existsSync8(cacheDir)) {
|
|
29959
|
+
mkdirSync6(cacheDir, { recursive: true });
|
|
29817
29960
|
}
|
|
29818
29961
|
const response = await fetch(downloadUrl, { redirect: "follow" });
|
|
29819
29962
|
if (!response.ok) {
|
|
29820
29963
|
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
29821
29964
|
}
|
|
29822
|
-
const archivePath =
|
|
29965
|
+
const archivePath = join13(cacheDir, assetName);
|
|
29823
29966
|
const arrayBuffer = await response.arrayBuffer();
|
|
29824
29967
|
await crossWrite(archivePath, arrayBuffer);
|
|
29825
29968
|
await extractZip(archivePath, cacheDir);
|
|
29826
|
-
if (
|
|
29969
|
+
if (existsSync8(archivePath)) {
|
|
29827
29970
|
unlinkSync4(archivePath);
|
|
29828
29971
|
}
|
|
29829
|
-
if (process.platform !== "win32" &&
|
|
29972
|
+
if (process.platform !== "win32" && existsSync8(binaryPath2)) {
|
|
29830
29973
|
chmodSync(binaryPath2, 493);
|
|
29831
29974
|
}
|
|
29832
29975
|
console.log(`[oh-my-opencode-slim] ast-grep binary ready.`);
|
|
@@ -29907,9 +30050,9 @@ function findSgCliPathSync() {
|
|
|
29907
30050
|
try {
|
|
29908
30051
|
const require2 = createRequire3(import.meta.url);
|
|
29909
30052
|
const cliPkgPath = require2.resolve("@ast-grep/cli/package.json");
|
|
29910
|
-
const cliDir =
|
|
29911
|
-
const sgPath =
|
|
29912
|
-
if (
|
|
30053
|
+
const cliDir = dirname8(cliPkgPath);
|
|
30054
|
+
const sgPath = join14(cliDir, binaryName);
|
|
30055
|
+
if (existsSync9(sgPath) && isValidBinary(sgPath)) {
|
|
29913
30056
|
return sgPath;
|
|
29914
30057
|
}
|
|
29915
30058
|
} catch {}
|
|
@@ -29918,19 +30061,19 @@ function findSgCliPathSync() {
|
|
|
29918
30061
|
try {
|
|
29919
30062
|
const require2 = createRequire3(import.meta.url);
|
|
29920
30063
|
const pkgPath = require2.resolve(`${platformPkg}/package.json`);
|
|
29921
|
-
const pkgDir =
|
|
30064
|
+
const pkgDir = dirname8(pkgPath);
|
|
29922
30065
|
const astGrepName = process.platform === "win32" ? "ast-grep.exe" : "ast-grep";
|
|
29923
|
-
const binaryPath2 =
|
|
29924
|
-
if (
|
|
30066
|
+
const binaryPath2 = join14(pkgDir, astGrepName);
|
|
30067
|
+
if (existsSync9(binaryPath2) && isValidBinary(binaryPath2)) {
|
|
29925
30068
|
return binaryPath2;
|
|
29926
30069
|
}
|
|
29927
30070
|
} catch {}
|
|
29928
30071
|
}
|
|
29929
30072
|
if (process.platform === "darwin") {
|
|
29930
30073
|
const homebrewPaths = ["/opt/homebrew/bin/sg", "/usr/local/bin/sg"];
|
|
29931
|
-
for (const
|
|
29932
|
-
if (
|
|
29933
|
-
return
|
|
30074
|
+
for (const path16 of homebrewPaths) {
|
|
30075
|
+
if (existsSync9(path16) && isValidBinary(path16)) {
|
|
30076
|
+
return path16;
|
|
29934
30077
|
}
|
|
29935
30078
|
}
|
|
29936
30079
|
}
|
|
@@ -29947,10 +30090,10 @@ function getSgCliPath() {
|
|
|
29947
30090
|
}
|
|
29948
30091
|
return "sg";
|
|
29949
30092
|
}
|
|
29950
|
-
function setSgCliPath(
|
|
29951
|
-
resolvedCliPath =
|
|
30093
|
+
function setSgCliPath(path16) {
|
|
30094
|
+
resolvedCliPath = path16;
|
|
29952
30095
|
}
|
|
29953
|
-
var
|
|
30096
|
+
var DEFAULT_TIMEOUT_MS = 300000;
|
|
29954
30097
|
var DEFAULT_MAX_OUTPUT_BYTES = 1 * 1024 * 1024;
|
|
29955
30098
|
var DEFAULT_MAX_MATCHES = 500;
|
|
29956
30099
|
|
|
@@ -29958,7 +30101,7 @@ var DEFAULT_MAX_MATCHES = 500;
|
|
|
29958
30101
|
var initPromise = null;
|
|
29959
30102
|
async function getAstGrepPath() {
|
|
29960
30103
|
const currentPath = getSgCliPath();
|
|
29961
|
-
if (currentPath !== "sg" &&
|
|
30104
|
+
if (currentPath !== "sg" && existsSync10(currentPath)) {
|
|
29962
30105
|
return currentPath;
|
|
29963
30106
|
}
|
|
29964
30107
|
if (initPromise) {
|
|
@@ -29966,7 +30109,7 @@ async function getAstGrepPath() {
|
|
|
29966
30109
|
}
|
|
29967
30110
|
initPromise = (async () => {
|
|
29968
30111
|
const syncPath = findSgCliPathSync();
|
|
29969
|
-
if (syncPath &&
|
|
30112
|
+
if (syncPath && existsSync10(syncPath)) {
|
|
29970
30113
|
setSgCliPath(syncPath);
|
|
29971
30114
|
return syncPath;
|
|
29972
30115
|
}
|
|
@@ -30005,13 +30148,13 @@ async function runSg(options) {
|
|
|
30005
30148
|
const paths2 = options.paths && options.paths.length > 0 ? options.paths : ["."];
|
|
30006
30149
|
args.push(...paths2);
|
|
30007
30150
|
let cliPath = getSgCliPath();
|
|
30008
|
-
if (!
|
|
30151
|
+
if (!existsSync10(cliPath) && cliPath !== "sg") {
|
|
30009
30152
|
const downloadedPath = await getAstGrepPath();
|
|
30010
30153
|
if (downloadedPath) {
|
|
30011
30154
|
cliPath = downloadedPath;
|
|
30012
30155
|
}
|
|
30013
30156
|
}
|
|
30014
|
-
const timeout =
|
|
30157
|
+
const timeout = DEFAULT_TIMEOUT_MS;
|
|
30015
30158
|
const proc = crossSpawn([cliPath, ...args], {
|
|
30016
30159
|
stdout: "pipe",
|
|
30017
30160
|
stderr: "pipe"
|
|
@@ -30614,7 +30757,7 @@ async function getSessionStatus(client, taskID) {
|
|
|
30614
30757
|
try {
|
|
30615
30758
|
const result = await client.session.status();
|
|
30616
30759
|
const data = result.data;
|
|
30617
|
-
if (!
|
|
30760
|
+
if (!isRecord(data)) {
|
|
30618
30761
|
return { status: undefined, source: "invalid-data", keys: [] };
|
|
30619
30762
|
}
|
|
30620
30763
|
const keys = Object.keys(data).slice(0, 20);
|
|
@@ -30622,14 +30765,14 @@ async function getSessionStatus(client, taskID) {
|
|
|
30622
30765
|
if (item === undefined) {
|
|
30623
30766
|
return { status: "idle", source: "missing-from-map", keys };
|
|
30624
30767
|
}
|
|
30625
|
-
if (
|
|
30768
|
+
if (isRecord(item) && typeof item.type === "string") {
|
|
30626
30769
|
return { status: item.type, source: "task-map-entry", keys };
|
|
30627
30770
|
}
|
|
30628
30771
|
if (typeof data.type === "string") {
|
|
30629
30772
|
return { status: data.type, source: "legacy-data-type", keys };
|
|
30630
30773
|
}
|
|
30631
30774
|
const nested = data.status;
|
|
30632
|
-
if (
|
|
30775
|
+
if (isRecord(nested) && typeof nested.type === "string") {
|
|
30633
30776
|
return { status: nested.type, source: "legacy-data-status", keys };
|
|
30634
30777
|
}
|
|
30635
30778
|
return { status: undefined, source: "unknown-shape", keys };
|
|
@@ -30644,9 +30787,6 @@ async function getSessionStatus(client, taskID) {
|
|
|
30644
30787
|
function delay(ms) {
|
|
30645
30788
|
return new Promise((resolve3) => setTimeout(resolve3, ms));
|
|
30646
30789
|
}
|
|
30647
|
-
function isObjectRecord2(value) {
|
|
30648
|
-
return typeof value === "object" && value !== null;
|
|
30649
|
-
}
|
|
30650
30790
|
function isSessionID(value) {
|
|
30651
30791
|
return /^ses_[\w-]+$/.test(value);
|
|
30652
30792
|
}
|
|
@@ -30661,7 +30801,7 @@ async function getSessionParentID(client, taskID) {
|
|
|
30661
30801
|
try {
|
|
30662
30802
|
const response = await session2.get({ path: { id: taskID } });
|
|
30663
30803
|
const data = response.data;
|
|
30664
|
-
if (!
|
|
30804
|
+
if (!isRecord(data))
|
|
30665
30805
|
return;
|
|
30666
30806
|
const parentID = data.parentID;
|
|
30667
30807
|
return typeof parentID === "string" ? parentID : undefined;
|
|
@@ -30756,14 +30896,14 @@ import * as fs10 from "node:fs";
|
|
|
30756
30896
|
// src/tui-state.ts
|
|
30757
30897
|
import * as fs9 from "node:fs";
|
|
30758
30898
|
import * as os5 from "node:os";
|
|
30759
|
-
import * as
|
|
30899
|
+
import * as path16 from "node:path";
|
|
30760
30900
|
var STATE_DIR = "oh-my-opencode-slim";
|
|
30761
30901
|
var STATE_FILE = "tui-state.json";
|
|
30762
30902
|
function dataDir() {
|
|
30763
|
-
return process.env.XDG_DATA_HOME ??
|
|
30903
|
+
return process.env.XDG_DATA_HOME ?? path16.join(os5.homedir(), ".local", "share");
|
|
30764
30904
|
}
|
|
30765
30905
|
function getTuiStatePath() {
|
|
30766
|
-
return
|
|
30906
|
+
return path16.join(dataDir(), "opencode", "storage", STATE_DIR, STATE_FILE);
|
|
30767
30907
|
}
|
|
30768
30908
|
function emptySnapshot() {
|
|
30769
30909
|
return {
|
|
@@ -30799,7 +30939,7 @@ async function readTuiSnapshotAsync() {
|
|
|
30799
30939
|
function writeTuiSnapshot(snapshot) {
|
|
30800
30940
|
try {
|
|
30801
30941
|
const filePath = getTuiStatePath();
|
|
30802
|
-
fs9.mkdirSync(
|
|
30942
|
+
fs9.mkdirSync(path16.dirname(filePath), { recursive: true });
|
|
30803
30943
|
fs9.writeFileSync(filePath, `${JSON.stringify(snapshot)}
|
|
30804
30944
|
`);
|
|
30805
30945
|
} catch {}
|
|
@@ -30822,11 +30962,11 @@ function recordTuiAgentModel(input) {
|
|
|
30822
30962
|
}
|
|
30823
30963
|
|
|
30824
30964
|
// src/tools/preset-manager.ts
|
|
30825
|
-
var
|
|
30965
|
+
var COMMAND_NAME4 = "preset";
|
|
30826
30966
|
function createPresetManager(ctx, config) {
|
|
30827
30967
|
let activePreset = getActiveRuntimePreset() ?? config.preset ?? null;
|
|
30828
30968
|
async function handleCommandExecuteBefore(input, output) {
|
|
30829
|
-
if (input.command !==
|
|
30969
|
+
if (input.command !== COMMAND_NAME4) {
|
|
30830
30970
|
return;
|
|
30831
30971
|
}
|
|
30832
30972
|
output.parts.length = 0;
|
|
@@ -30845,11 +30985,11 @@ function createPresetManager(ctx, config) {
|
|
|
30845
30985
|
}
|
|
30846
30986
|
function registerCommand(opencodeConfig) {
|
|
30847
30987
|
const configCommand = opencodeConfig.command;
|
|
30848
|
-
if (!configCommand?.[
|
|
30988
|
+
if (!configCommand?.[COMMAND_NAME4]) {
|
|
30849
30989
|
if (!opencodeConfig.command) {
|
|
30850
30990
|
opencodeConfig.command = {};
|
|
30851
30991
|
}
|
|
30852
|
-
opencodeConfig.command[
|
|
30992
|
+
opencodeConfig.command[COMMAND_NAME4] = {
|
|
30853
30993
|
template: "List available presets and switch between them",
|
|
30854
30994
|
description: "Switch agent presets at runtime (e.g., /preset cheap, /preset powerful)"
|
|
30855
30995
|
};
|
|
@@ -30999,14 +31139,14 @@ var BINARY_PREFIXES = [
|
|
|
30999
31139
|
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.";
|
|
31000
31140
|
// src/tools/smartfetch/tool.ts
|
|
31001
31141
|
import os6 from "node:os";
|
|
31002
|
-
import
|
|
31142
|
+
import path20 from "node:path";
|
|
31003
31143
|
import {
|
|
31004
31144
|
tool as tool4
|
|
31005
31145
|
} from "@opencode-ai/plugin";
|
|
31006
31146
|
|
|
31007
31147
|
// src/tools/smartfetch/binary.ts
|
|
31008
31148
|
import { mkdir as mkdir2, writeFile as writeFile2 } from "node:fs/promises";
|
|
31009
|
-
import
|
|
31149
|
+
import path17 from "node:path";
|
|
31010
31150
|
function extensionForMime(contentType) {
|
|
31011
31151
|
const mime = contentType.split(";")[0]?.trim().toLowerCase();
|
|
31012
31152
|
const map = {
|
|
@@ -31027,10 +31167,10 @@ function buildBinaryResultMessage(fetchResult, savedPath) {
|
|
|
31027
31167
|
async function saveBinary(binaryDir, data, contentType, filename) {
|
|
31028
31168
|
await mkdir2(binaryDir, { recursive: true });
|
|
31029
31169
|
const initialName = filename || `webfetch-${Date.now()}.${extensionForMime(contentType)}`;
|
|
31030
|
-
const parsed =
|
|
31170
|
+
const parsed = path17.parse(initialName);
|
|
31031
31171
|
for (let attempt = 0;attempt < 1000; attempt++) {
|
|
31032
31172
|
const candidateName = attempt === 0 ? initialName : `${parsed.name}-${attempt}${parsed.ext || `.${extensionForMime(contentType)}`}`;
|
|
31033
|
-
const file =
|
|
31173
|
+
const file = path17.join(binaryDir, candidateName);
|
|
31034
31174
|
try {
|
|
31035
31175
|
await writeFile2(file, data, { flag: "wx" });
|
|
31036
31176
|
return file;
|
|
@@ -31684,7 +31824,7 @@ var M = class u2 {
|
|
|
31684
31824
|
};
|
|
31685
31825
|
|
|
31686
31826
|
// src/tools/smartfetch/network.ts
|
|
31687
|
-
import
|
|
31827
|
+
import path18 from "node:path";
|
|
31688
31828
|
|
|
31689
31829
|
// src/tools/smartfetch/utils.ts
|
|
31690
31830
|
var import_readability = __toESM(require_readability(), 1);
|
|
@@ -32409,7 +32549,7 @@ function inferFilenameFromUrl(url) {
|
|
|
32409
32549
|
function truncateFilename(name, maxLength = 180) {
|
|
32410
32550
|
if (name.length <= maxLength)
|
|
32411
32551
|
return name;
|
|
32412
|
-
const parsed =
|
|
32552
|
+
const parsed = path18.parse(name);
|
|
32413
32553
|
const ext = parsed.ext || "";
|
|
32414
32554
|
const baseLimit = Math.max(1, maxLength - ext.length);
|
|
32415
32555
|
return `${parsed.name.slice(0, baseLimit)}${ext}`;
|
|
@@ -32579,9 +32719,9 @@ function isInvalidLlmsResult(fetchResult) {
|
|
|
32579
32719
|
}
|
|
32580
32720
|
|
|
32581
32721
|
// src/tools/smartfetch/secondary-model.ts
|
|
32582
|
-
import { existsSync as
|
|
32722
|
+
import { existsSync as existsSync11 } from "node:fs";
|
|
32583
32723
|
import { readFile as readFile4 } from "node:fs/promises";
|
|
32584
|
-
import
|
|
32724
|
+
import path19 from "node:path";
|
|
32585
32725
|
function parseModelRef(value) {
|
|
32586
32726
|
if (!value)
|
|
32587
32727
|
return;
|
|
@@ -32607,8 +32747,8 @@ function pickAgentModelRef(value) {
|
|
|
32607
32747
|
}
|
|
32608
32748
|
function findPreferredOpenCodeConfigPath(baseDir) {
|
|
32609
32749
|
for (const file of ["opencode.jsonc", "opencode.json"]) {
|
|
32610
|
-
const fullPath =
|
|
32611
|
-
if (
|
|
32750
|
+
const fullPath = path19.join(baseDir, file);
|
|
32751
|
+
if (existsSync11(fullPath))
|
|
32612
32752
|
return fullPath;
|
|
32613
32753
|
}
|
|
32614
32754
|
return;
|
|
@@ -32624,7 +32764,7 @@ async function readOpenCodeConfigFile(configPath) {
|
|
|
32624
32764
|
}
|
|
32625
32765
|
}
|
|
32626
32766
|
async function readEffectiveOpenCodeConfig(directory) {
|
|
32627
|
-
const projectDir =
|
|
32767
|
+
const projectDir = path19.join(directory, ".opencode");
|
|
32628
32768
|
const userDirs = getConfigSearchDirs();
|
|
32629
32769
|
const projectPath = findPreferredOpenCodeConfigPath(projectDir);
|
|
32630
32770
|
const userPath = userDirs.map((configDir) => findPreferredOpenCodeConfigPath(configDir)).find(Boolean);
|
|
@@ -32785,7 +32925,7 @@ async function runSecondaryModelWithFallback(client, directory, models, prompt,
|
|
|
32785
32925
|
// src/tools/smartfetch/tool.ts
|
|
32786
32926
|
var z6 = tool4.schema;
|
|
32787
32927
|
function createWebfetchTool(pluginCtx, options = {}) {
|
|
32788
|
-
const binaryDir = options.binaryDir ||
|
|
32928
|
+
const binaryDir = options.binaryDir || path20.join(os6.tmpdir(), "opencode-smartfetch");
|
|
32789
32929
|
return tool4({
|
|
32790
32930
|
description: WEBFETCH_DESCRIPTION,
|
|
32791
32931
|
args: {
|
|
@@ -33378,6 +33518,7 @@ var OhMyOpenCodeLite = async (ctx) => {
|
|
|
33378
33518
|
let jsonErrorRecoveryHook;
|
|
33379
33519
|
let foregroundFallback;
|
|
33380
33520
|
let deepworkCommandHook;
|
|
33521
|
+
let reflectCommandHook;
|
|
33381
33522
|
let taskSessionManagerHook;
|
|
33382
33523
|
let backgroundJobBoard;
|
|
33383
33524
|
let interviewManager;
|
|
@@ -33403,32 +33544,13 @@ var OhMyOpenCodeLite = async (ctx) => {
|
|
|
33403
33544
|
agentDefs = createAgents(config);
|
|
33404
33545
|
agents = getAgentConfigs(config);
|
|
33405
33546
|
modelArrayMap = {};
|
|
33406
|
-
for (const agentDef of agentDefs) {
|
|
33407
|
-
if (agentDef._modelArray && agentDef._modelArray.length > 0) {
|
|
33408
|
-
modelArrayMap[agentDef.name] = agentDef._modelArray;
|
|
33409
|
-
}
|
|
33410
|
-
}
|
|
33411
33547
|
runtimeChains = {};
|
|
33412
33548
|
for (const agentDef of agentDefs) {
|
|
33413
33549
|
if (agentDef._modelArray?.length) {
|
|
33550
|
+
modelArrayMap[agentDef.name] = agentDef._modelArray;
|
|
33414
33551
|
runtimeChains[agentDef.name] = agentDef._modelArray.map((m) => m.id);
|
|
33415
33552
|
}
|
|
33416
33553
|
}
|
|
33417
|
-
const activePresetForFallback = getActiveRuntimePreset() ?? config.preset ?? null;
|
|
33418
|
-
if (config.fallback?.enabled !== false) {
|
|
33419
|
-
const chains = normalizeFallbackChainsForPreset(config.fallback?.chains ?? {}, activePresetForFallback);
|
|
33420
|
-
for (const [agentName, chainModels] of Object.entries(chains)) {
|
|
33421
|
-
const existing = runtimeChains[agentName] ?? [];
|
|
33422
|
-
const seen = new Set(existing);
|
|
33423
|
-
for (const m of chainModels) {
|
|
33424
|
-
if (!seen.has(m)) {
|
|
33425
|
-
seen.add(m);
|
|
33426
|
-
existing.push(m);
|
|
33427
|
-
}
|
|
33428
|
-
}
|
|
33429
|
-
runtimeChains[agentName] = existing;
|
|
33430
|
-
}
|
|
33431
|
-
}
|
|
33432
33554
|
multiplexerConfig = {
|
|
33433
33555
|
type: config.multiplexer?.type ?? "none",
|
|
33434
33556
|
layout: config.multiplexer?.layout ?? "main-vertical",
|
|
@@ -33470,6 +33592,7 @@ var OhMyOpenCodeLite = async (ctx) => {
|
|
|
33470
33592
|
jsonErrorRecoveryHook = createJsonErrorRecoveryHook(ctx);
|
|
33471
33593
|
foregroundFallback = new ForegroundFallbackManager(ctx.client, runtimeChains, config.fallback?.enabled !== false && Object.keys(runtimeChains).length > 0);
|
|
33472
33594
|
deepworkCommandHook = createDeepworkCommandHook();
|
|
33595
|
+
reflectCommandHook = createReflectCommandHook();
|
|
33473
33596
|
taskSessionManagerHook = createTaskSessionManagerHook(ctx, {
|
|
33474
33597
|
maxSessionsPerAgent: config.backgroundJobs?.maxSessionsPerAgent ?? 2,
|
|
33475
33598
|
readContextMinLines: config.backgroundJobs?.readContextMinLines ?? 10,
|
|
@@ -33555,34 +33678,11 @@ var OhMyOpenCodeLite = async (ctx) => {
|
|
|
33555
33678
|
}
|
|
33556
33679
|
}
|
|
33557
33680
|
const configAgent = opencodeConfig.agent;
|
|
33558
|
-
|
|
33559
|
-
|
|
33560
|
-
|
|
33561
|
-
const effectiveArrays = {};
|
|
33562
|
-
for (const [agentName, models] of Object.entries(modelArrayMap)) {
|
|
33563
|
-
effectiveArrays[agentName] = [...models];
|
|
33564
|
-
}
|
|
33565
|
-
for (const [agentName, chainModels] of Object.entries(fallbackChains)) {
|
|
33566
|
-
if (!chainModels || chainModels.length === 0)
|
|
33567
|
-
continue;
|
|
33568
|
-
if (!effectiveArrays[agentName]) {
|
|
33569
|
-
const entry = configAgent[agentName];
|
|
33570
|
-
const currentModel = typeof entry?.model === "string" ? entry.model : undefined;
|
|
33571
|
-
effectiveArrays[agentName] = currentModel ? [{ id: currentModel }] : [];
|
|
33572
|
-
}
|
|
33573
|
-
const seen = new Set(effectiveArrays[agentName].map((m) => m.id));
|
|
33574
|
-
for (const chainModel of chainModels) {
|
|
33575
|
-
if (!seen.has(chainModel)) {
|
|
33576
|
-
seen.add(chainModel);
|
|
33577
|
-
effectiveArrays[agentName].push({ id: chainModel });
|
|
33578
|
-
}
|
|
33579
|
-
}
|
|
33580
|
-
}
|
|
33581
|
-
if (Object.keys(effectiveArrays).length > 0) {
|
|
33582
|
-
for (const [agentName, modelArray] of Object.entries(effectiveArrays)) {
|
|
33583
|
-
if (modelArray.length === 0)
|
|
33681
|
+
if (Object.keys(modelArrayMap).length > 0) {
|
|
33682
|
+
for (const [agentName, models] of Object.entries(modelArrayMap)) {
|
|
33683
|
+
if (models.length === 0)
|
|
33584
33684
|
continue;
|
|
33585
|
-
const chosen =
|
|
33685
|
+
const chosen = models[0];
|
|
33586
33686
|
const entry = configAgent[agentName];
|
|
33587
33687
|
if (entry) {
|
|
33588
33688
|
entry.model = chosen.id;
|
|
@@ -33718,6 +33818,7 @@ var OhMyOpenCodeLite = async (ctx) => {
|
|
|
33718
33818
|
}
|
|
33719
33819
|
interviewManager.registerCommand(opencodeConfig);
|
|
33720
33820
|
deepworkCommandHook.registerCommand(opencodeConfig);
|
|
33821
|
+
reflectCommandHook.registerCommand(opencodeConfig);
|
|
33721
33822
|
presetManager.registerCommand(opencodeConfig);
|
|
33722
33823
|
},
|
|
33723
33824
|
event: async (input) => {
|
|
@@ -33784,6 +33885,7 @@ var OhMyOpenCodeLite = async (ctx) => {
|
|
|
33784
33885
|
await interviewManager.handleCommandExecuteBefore(input, output);
|
|
33785
33886
|
await presetManager.handleCommandExecuteBefore(input, output);
|
|
33786
33887
|
await deepworkCommandHook.handleCommandExecuteBefore(input, output);
|
|
33888
|
+
await reflectCommandHook.handleCommandExecuteBefore(input, output);
|
|
33787
33889
|
},
|
|
33788
33890
|
"chat.headers": chatHeadersHook["chat.headers"],
|
|
33789
33891
|
"chat.message": async (input, output) => {
|