thoth-agents 0.1.5 → 0.1.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 +35 -8
- package/dist/chunk-DYGVRAMS.js +182 -0
- package/dist/chunk-OES76C67.js +1898 -0
- package/dist/chunk-OJCEGZSA.js +3735 -0
- package/dist/cli/codex-install.d.ts +15 -0
- package/dist/cli/commands.d.ts +9 -0
- package/dist/cli/index.d.ts +3 -2
- package/dist/cli/index.js +543 -4154
- package/dist/cli/operations/codex.d.ts +22 -0
- package/dist/cli/operations/index.d.ts +8 -0
- package/dist/cli/operations/opencode.d.ts +14 -0
- package/dist/cli/operations/types.d.ts +126 -0
- package/dist/cli/parser.d.ts +6 -0
- package/dist/cli/runtime.d.ts +21 -0
- package/dist/cli/skills.d.ts +17 -0
- package/dist/cli/tui/App.d.ts +7 -0
- package/dist/cli/tui/components/Header.d.ts +6 -0
- package/dist/cli/tui/components/Menu.d.ts +12 -0
- package/dist/cli/tui/components/ModelChoiceScreen.d.ts +9 -0
- package/dist/cli/tui/components/ModelScreen.d.ts +14 -0
- package/dist/cli/tui/components/PathLine.d.ts +8 -0
- package/dist/cli/tui/components/PlanPreview.d.ts +8 -0
- package/dist/cli/tui/components/StatusView.d.ts +7 -0
- package/dist/cli/tui/index.d.ts +3 -0
- package/dist/cli/tui/index.js +1285 -0
- package/dist/cli/tui/model-catalog.d.ts +20 -0
- package/dist/cli/tui/operations.d.ts +18 -0
- package/dist/cli/tui/theme.d.ts +9 -0
- package/dist/cli/types.d.ts +19 -0
- package/dist/harness/core/sdd.d.ts +20 -0
- package/dist/harness/core/skills.d.ts +2 -2
- package/dist/index.js +156 -1693
- package/package.json +5 -1
- package/src/skills/sdd-design/SKILL.md +12 -2
package/dist/index.js
CHANGED
|
@@ -1,971 +1,35 @@
|
|
|
1
|
-
// src/index.ts
|
|
2
|
-
import path5 from "path";
|
|
3
|
-
|
|
4
|
-
// src/config/constants.ts
|
|
5
|
-
var AGENT_ALIASES = {
|
|
6
|
-
explore: "explorer",
|
|
7
|
-
"frontend-ui-ux-engineer": "designer"
|
|
8
|
-
};
|
|
9
|
-
var SUBAGENT_NAMES = [
|
|
10
|
-
"explorer",
|
|
11
|
-
"librarian",
|
|
12
|
-
"oracle",
|
|
13
|
-
"designer",
|
|
14
|
-
"quick",
|
|
15
|
-
"deep"
|
|
16
|
-
];
|
|
17
|
-
var DEFAULT_MODELS = {
|
|
18
|
-
orchestrator: void 0,
|
|
19
|
-
oracle: "openai/gpt-5.4",
|
|
20
|
-
librarian: "openai/gpt-5.4-mini",
|
|
21
|
-
explorer: "openai/gpt-5.4-mini",
|
|
22
|
-
designer: "openai/gpt-5.4-mini",
|
|
23
|
-
quick: "openai/gpt-5.4-mini",
|
|
24
|
-
deep: "openai/gpt-5.4"
|
|
25
|
-
};
|
|
26
|
-
var POLL_INTERVAL_BACKGROUND_MS = 2e3;
|
|
27
|
-
var DEFAULT_TIMEOUT_MS = 2 * 60 * 1e3;
|
|
28
|
-
var MAX_POLL_TIME_MS = 5 * 60 * 1e3;
|
|
29
|
-
var DEFAULT_THOTH_COMMAND = ["npx", "-y", "thoth-mem"];
|
|
30
|
-
|
|
31
|
-
// src/config/loader.ts
|
|
32
|
-
import * as fs from "fs";
|
|
33
|
-
import * as path from "path";
|
|
34
|
-
|
|
35
|
-
// src/cli/config-io.ts
|
|
36
1
|
import {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
const configDir = getDefaultOpenCodeConfigDir();
|
|
66
|
-
return [join(configDir, "opencode.json"), join(configDir, "opencode.jsonc")];
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
// src/cli/config-io.ts
|
|
70
|
-
function stripJsonComments(json) {
|
|
71
|
-
const commentPattern = /\\"|"(?:\\"|[^"])*"|(\/\/.*|\/\*[\s\S]*?\*\/)/g;
|
|
72
|
-
const trailingCommaPattern = /\\"|"(?:\\"|[^"])*"|(,)(\s*[}\]])/g;
|
|
73
|
-
return json.replace(
|
|
74
|
-
commentPattern,
|
|
75
|
-
(match, commentGroup) => commentGroup ? "" : match
|
|
76
|
-
).replace(
|
|
77
|
-
trailingCommaPattern,
|
|
78
|
-
(match, comma, closing) => comma ? closing : match
|
|
79
|
-
);
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
// src/config/schema.ts
|
|
83
|
-
import { z } from "zod";
|
|
84
|
-
var AGENT_NAMES = [
|
|
85
|
-
"orchestrator",
|
|
86
|
-
"oracle",
|
|
87
|
-
"designer",
|
|
88
|
-
"explorer",
|
|
89
|
-
"librarian",
|
|
90
|
-
"quick",
|
|
91
|
-
"deep"
|
|
92
|
-
];
|
|
93
|
-
var FALLBACK_AGENT_NAMES = [...AGENT_NAMES];
|
|
94
|
-
var MANUAL_AGENT_NAMES = [...AGENT_NAMES];
|
|
95
|
-
var ProviderModelIdSchema = z.string().regex(
|
|
96
|
-
/^[^/\s]+\/[^\s]+$/,
|
|
97
|
-
"Expected provider/model format (provider/.../model)"
|
|
98
|
-
);
|
|
99
|
-
var ManualAgentPlanSchema = z.object({
|
|
100
|
-
primary: ProviderModelIdSchema,
|
|
101
|
-
fallback1: ProviderModelIdSchema,
|
|
102
|
-
fallback2: ProviderModelIdSchema,
|
|
103
|
-
fallback3: ProviderModelIdSchema
|
|
104
|
-
}).superRefine((value, ctx) => {
|
|
105
|
-
const unique = /* @__PURE__ */ new Set([
|
|
106
|
-
value.primary,
|
|
107
|
-
value.fallback1,
|
|
108
|
-
value.fallback2,
|
|
109
|
-
value.fallback3
|
|
110
|
-
]);
|
|
111
|
-
if (unique.size !== 4) {
|
|
112
|
-
ctx.addIssue({
|
|
113
|
-
code: z.ZodIssueCode.custom,
|
|
114
|
-
message: "primary and fallbacks must be unique per agent"
|
|
115
|
-
});
|
|
116
|
-
}
|
|
117
|
-
});
|
|
118
|
-
var ManualPlanSchema = z.object({
|
|
119
|
-
orchestrator: ManualAgentPlanSchema,
|
|
120
|
-
oracle: ManualAgentPlanSchema,
|
|
121
|
-
designer: ManualAgentPlanSchema,
|
|
122
|
-
explorer: ManualAgentPlanSchema,
|
|
123
|
-
librarian: ManualAgentPlanSchema,
|
|
124
|
-
quick: ManualAgentPlanSchema,
|
|
125
|
-
deep: ManualAgentPlanSchema
|
|
126
|
-
}).strict();
|
|
127
|
-
var AgentModelChainSchema = z.array(z.string()).min(1);
|
|
128
|
-
var FallbackChainsSchema = z.object({
|
|
129
|
-
orchestrator: AgentModelChainSchema.optional(),
|
|
130
|
-
oracle: AgentModelChainSchema.optional(),
|
|
131
|
-
designer: AgentModelChainSchema.optional(),
|
|
132
|
-
explorer: AgentModelChainSchema.optional(),
|
|
133
|
-
librarian: AgentModelChainSchema.optional(),
|
|
134
|
-
quick: AgentModelChainSchema.optional(),
|
|
135
|
-
deep: AgentModelChainSchema.optional()
|
|
136
|
-
}).catchall(AgentModelChainSchema);
|
|
137
|
-
var AgentOverrideConfigSchema = z.object({
|
|
138
|
-
model: z.union([
|
|
139
|
-
z.string(),
|
|
140
|
-
z.array(
|
|
141
|
-
z.union([
|
|
142
|
-
z.string(),
|
|
143
|
-
z.object({
|
|
144
|
-
id: z.string(),
|
|
145
|
-
variant: z.string().optional()
|
|
146
|
-
})
|
|
147
|
-
])
|
|
148
|
-
)
|
|
149
|
-
]).optional(),
|
|
150
|
-
temperature: z.number().min(0).max(2).optional(),
|
|
151
|
-
steps: z.number().int().min(1).optional(),
|
|
152
|
-
variant: z.string().optional().catch(void 0)
|
|
153
|
-
});
|
|
154
|
-
var TmuxLayoutSchema = z.enum([
|
|
155
|
-
"main-horizontal",
|
|
156
|
-
// Main pane on top, agents stacked below
|
|
157
|
-
"main-vertical",
|
|
158
|
-
// Main pane on left, agents stacked on right
|
|
159
|
-
"tiled",
|
|
160
|
-
// All panes equal size grid
|
|
161
|
-
"even-horizontal",
|
|
162
|
-
// All panes side by side
|
|
163
|
-
"even-vertical"
|
|
164
|
-
// All panes stacked vertically
|
|
165
|
-
]);
|
|
166
|
-
var TmuxConfigSchema = z.object({
|
|
167
|
-
enabled: z.boolean().default(false),
|
|
168
|
-
layout: TmuxLayoutSchema.default("main-vertical"),
|
|
169
|
-
main_pane_size: z.number().min(20).max(80).default(60)
|
|
170
|
-
// percentage for main pane
|
|
171
|
-
});
|
|
172
|
-
var PresetSchema = z.record(z.string(), AgentOverrideConfigSchema);
|
|
173
|
-
var AgentNameSchema = z.enum(AGENT_NAMES);
|
|
174
|
-
var McpNameSchema = z.enum([
|
|
175
|
-
"exa",
|
|
176
|
-
"context7",
|
|
177
|
-
"grep_app",
|
|
178
|
-
"thoth_mem"
|
|
179
|
-
]);
|
|
180
|
-
var ThothConfigSchema = z.object({
|
|
181
|
-
command: z.array(z.string()).optional(),
|
|
182
|
-
data_dir: z.string().optional(),
|
|
183
|
-
environment: z.record(z.string(), z.string()).optional(),
|
|
184
|
-
timeout: z.number().optional(),
|
|
185
|
-
http_port: z.number().optional()
|
|
186
|
-
});
|
|
187
|
-
var ArtifactStoreModeSchema = z.enum([
|
|
188
|
-
"thoth-mem",
|
|
189
|
-
"openspec",
|
|
190
|
-
"hybrid"
|
|
191
|
-
]);
|
|
192
|
-
var ArtifactStoreConfigSchema = z.object({
|
|
193
|
-
mode: ArtifactStoreModeSchema.default("hybrid")
|
|
194
|
-
});
|
|
195
|
-
var CodexGenerationConfigSchema = z.object({
|
|
196
|
-
enabled: z.boolean().default(false),
|
|
197
|
-
outputRoot: z.string().optional(),
|
|
198
|
-
dryRun: z.boolean().default(true)
|
|
199
|
-
});
|
|
200
|
-
var FailoverConfigSchema = z.object({
|
|
201
|
-
enabled: z.boolean().default(true),
|
|
202
|
-
timeoutMs: z.number().min(0).default(15e3),
|
|
203
|
-
retryDelayMs: z.number().min(0).default(500),
|
|
204
|
-
chains: FallbackChainsSchema.default({})
|
|
205
|
-
});
|
|
206
|
-
var PluginConfigSchema = z.object({
|
|
207
|
-
preset: z.string().optional(),
|
|
208
|
-
setDefaultAgent: z.boolean().optional(),
|
|
209
|
-
scoringEngineVersion: z.enum(["v1", "v2-shadow", "v2"]).optional(),
|
|
210
|
-
balanceProviderUsage: z.boolean().optional(),
|
|
211
|
-
manualPlan: ManualPlanSchema.optional(),
|
|
212
|
-
presets: z.record(z.string(), PresetSchema).optional(),
|
|
213
|
-
agents: z.record(z.string(), AgentOverrideConfigSchema).optional(),
|
|
214
|
-
disabled_mcps: z.array(z.string()).optional(),
|
|
215
|
-
tmux: TmuxConfigSchema.optional(),
|
|
216
|
-
fallback: FailoverConfigSchema.optional(),
|
|
217
|
-
thoth: ThothConfigSchema.optional(),
|
|
218
|
-
artifactStore: ArtifactStoreConfigSchema.optional(),
|
|
219
|
-
codex: CodexGenerationConfigSchema.optional()
|
|
220
|
-
});
|
|
221
|
-
|
|
222
|
-
// src/config/loader.ts
|
|
223
|
-
var PROMPTS_DIR_NAME = "thoth-agents";
|
|
224
|
-
function loadConfigFromPath(configPath) {
|
|
225
|
-
try {
|
|
226
|
-
const content = fs.readFileSync(configPath, "utf-8");
|
|
227
|
-
const rawConfig = JSON.parse(stripJsonComments(content));
|
|
228
|
-
const result = PluginConfigSchema.safeParse(rawConfig);
|
|
229
|
-
if (!result.success) {
|
|
230
|
-
console.warn(`[thoth-agents] Invalid config at ${configPath}:`);
|
|
231
|
-
console.warn(result.error.format());
|
|
232
|
-
return null;
|
|
233
|
-
}
|
|
234
|
-
return result.data;
|
|
235
|
-
} catch (error) {
|
|
236
|
-
if (error instanceof Error && "code" in error && error.code !== "ENOENT") {
|
|
237
|
-
console.warn(
|
|
238
|
-
`[thoth-agents] Error reading config from ${configPath}:`,
|
|
239
|
-
error.message
|
|
240
|
-
);
|
|
241
|
-
}
|
|
242
|
-
return null;
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
function findConfigPath(basePath) {
|
|
246
|
-
const jsoncPath = `${basePath}.jsonc`;
|
|
247
|
-
const jsonPath = `${basePath}.json`;
|
|
248
|
-
if (fs.existsSync(jsoncPath)) {
|
|
249
|
-
return jsoncPath;
|
|
250
|
-
}
|
|
251
|
-
if (fs.existsSync(jsonPath)) {
|
|
252
|
-
return jsonPath;
|
|
253
|
-
}
|
|
254
|
-
return null;
|
|
255
|
-
}
|
|
256
|
-
function deepMerge(base, override) {
|
|
257
|
-
if (!base) return override;
|
|
258
|
-
if (!override) return base;
|
|
259
|
-
const result = { ...base };
|
|
260
|
-
for (const key of Object.keys(override)) {
|
|
261
|
-
const baseVal = base[key];
|
|
262
|
-
const overrideVal = override[key];
|
|
263
|
-
if (typeof baseVal === "object" && baseVal !== null && typeof overrideVal === "object" && overrideVal !== null && !Array.isArray(baseVal) && !Array.isArray(overrideVal)) {
|
|
264
|
-
result[key] = deepMerge(
|
|
265
|
-
baseVal,
|
|
266
|
-
overrideVal
|
|
267
|
-
);
|
|
268
|
-
} else {
|
|
269
|
-
result[key] = overrideVal;
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
return result;
|
|
273
|
-
}
|
|
274
|
-
function loadPluginConfig(directory) {
|
|
275
|
-
const userConfigBasePath = path.join(getConfigDir(), "thoth-agents");
|
|
276
|
-
const projectConfigBasePath = path.join(
|
|
277
|
-
directory,
|
|
278
|
-
".opencode",
|
|
279
|
-
"thoth-agents"
|
|
280
|
-
);
|
|
281
|
-
const userConfigPath = findConfigPath(userConfigBasePath);
|
|
282
|
-
const projectConfigPath = findConfigPath(projectConfigBasePath);
|
|
283
|
-
let config = userConfigPath ? loadConfigFromPath(userConfigPath) ?? {} : {};
|
|
284
|
-
const projectConfig = projectConfigPath ? loadConfigFromPath(projectConfigPath) : null;
|
|
285
|
-
if (projectConfig) {
|
|
286
|
-
config = {
|
|
287
|
-
...config,
|
|
288
|
-
...projectConfig,
|
|
289
|
-
agents: deepMerge(config.agents, projectConfig.agents),
|
|
290
|
-
tmux: deepMerge(config.tmux, projectConfig.tmux),
|
|
291
|
-
fallback: deepMerge(config.fallback, projectConfig.fallback),
|
|
292
|
-
thoth: deepMerge(config.thoth, projectConfig.thoth),
|
|
293
|
-
artifactStore: deepMerge(
|
|
294
|
-
config.artifactStore,
|
|
295
|
-
projectConfig.artifactStore
|
|
296
|
-
),
|
|
297
|
-
codex: deepMerge(config.codex, projectConfig.codex)
|
|
298
|
-
};
|
|
299
|
-
}
|
|
300
|
-
const envPreset = process.env.THOTH_AGENTS_PRESET;
|
|
301
|
-
if (envPreset) {
|
|
302
|
-
config.preset = envPreset;
|
|
303
|
-
}
|
|
304
|
-
if (config.preset) {
|
|
305
|
-
const preset = config.presets?.[config.preset];
|
|
306
|
-
if (preset) {
|
|
307
|
-
config.agents = deepMerge(preset, config.agents);
|
|
308
|
-
} else {
|
|
309
|
-
const presetSource = envPreset === config.preset ? "environment variable" : "config file";
|
|
310
|
-
const availablePresets = config.presets ? Object.keys(config.presets).join(", ") : "none";
|
|
311
|
-
console.warn(
|
|
312
|
-
`[thoth-agents] Preset "${config.preset}" not found (from ${presetSource}). Available presets: ${availablePresets}`
|
|
313
|
-
);
|
|
314
|
-
}
|
|
315
|
-
}
|
|
316
|
-
return config;
|
|
317
|
-
}
|
|
318
|
-
function loadAgentPrompt(agentName, preset) {
|
|
319
|
-
const presetDirName = preset && /^[a-zA-Z0-9_-]+$/.test(preset) ? preset : void 0;
|
|
320
|
-
const promptsDir = path.join(getConfigDir(), PROMPTS_DIR_NAME);
|
|
321
|
-
const promptSearchDirs = presetDirName ? [path.join(promptsDir, presetDirName), promptsDir] : [promptsDir];
|
|
322
|
-
const result = {};
|
|
323
|
-
const readFirstPrompt = (fileName, errorPrefix) => {
|
|
324
|
-
for (const dir of promptSearchDirs) {
|
|
325
|
-
const promptPath = path.join(dir, fileName);
|
|
326
|
-
if (!fs.existsSync(promptPath)) {
|
|
327
|
-
continue;
|
|
328
|
-
}
|
|
329
|
-
try {
|
|
330
|
-
return fs.readFileSync(promptPath, "utf-8");
|
|
331
|
-
} catch (error) {
|
|
332
|
-
console.warn(
|
|
333
|
-
`[thoth-agents] ${errorPrefix} ${promptPath}:`,
|
|
334
|
-
error instanceof Error ? error.message : String(error)
|
|
335
|
-
);
|
|
336
|
-
}
|
|
337
|
-
}
|
|
338
|
-
return void 0;
|
|
339
|
-
};
|
|
340
|
-
result.prompt = readFirstPrompt(
|
|
341
|
-
`${agentName}.md`,
|
|
342
|
-
"Error reading prompt file"
|
|
343
|
-
);
|
|
344
|
-
result.appendPrompt = readFirstPrompt(
|
|
345
|
-
`${agentName}_append.md`,
|
|
346
|
-
"Error reading append prompt file"
|
|
347
|
-
);
|
|
348
|
-
return result;
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
// src/config/utils.ts
|
|
352
|
-
function getAgentOverride(config, name) {
|
|
353
|
-
const overrides = config?.agents ?? {};
|
|
354
|
-
return overrides[name] ?? overrides[Object.keys(AGENT_ALIASES).find((k) => AGENT_ALIASES[k] === name) ?? ""];
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
// src/agents/prompt-dialects.ts
|
|
358
|
-
var OPENCODE_CAPABILITIES = {
|
|
359
|
-
agentDefinitions: "supported",
|
|
360
|
-
delegatedExecution: "supported",
|
|
361
|
-
parallelDelegation: "supported",
|
|
362
|
-
runtimeHooks: "supported",
|
|
363
|
-
mcpConfiguration: "supported",
|
|
364
|
-
skillPackaging: "supported",
|
|
365
|
-
rolePermissions: "supported",
|
|
366
|
-
parentContextInjection: "supported",
|
|
367
|
-
memoryGovernanceEnforcement: "supported"
|
|
368
|
-
};
|
|
369
|
-
function supportedCapabilityProfile(capabilities) {
|
|
370
|
-
return {
|
|
371
|
-
capabilities,
|
|
372
|
-
renderCapabilityDisclosure: () => void 0
|
|
373
|
-
};
|
|
374
|
-
}
|
|
375
|
-
var OPENCODE_PROMPT_DIALECT = {
|
|
376
|
-
harness: "opencode",
|
|
377
|
-
tools: {
|
|
378
|
-
delegationTool: "task",
|
|
379
|
-
backgroundDelegationTool: "task(background=true)",
|
|
380
|
-
backgroundStatusTool: "task_status",
|
|
381
|
-
userQuestionTool: "question",
|
|
382
|
-
progressTool: "todowrite",
|
|
383
|
-
hostStatusSurface: "task_status",
|
|
384
|
-
roleReference: (role) => `@${role}`
|
|
385
|
-
},
|
|
386
|
-
capabilities: supportedCapabilityProfile(OPENCODE_CAPABILITIES),
|
|
387
|
-
dispatchLabel(method) {
|
|
388
|
-
switch (method) {
|
|
389
|
-
case "root-coordinator":
|
|
390
|
-
return "root coordinator";
|
|
391
|
-
case "task":
|
|
392
|
-
return "task";
|
|
393
|
-
case "synchronous-task-only":
|
|
394
|
-
return "synchronous task only";
|
|
395
|
-
}
|
|
396
|
-
},
|
|
397
|
-
renderRoleInvocation(role) {
|
|
398
|
-
return `@${role}`;
|
|
399
|
-
}
|
|
400
|
-
};
|
|
401
|
-
|
|
402
|
-
// src/agents/prompt-sections.ts
|
|
403
|
-
function createQuestionProtocolSection() {
|
|
404
|
-
return {
|
|
405
|
-
kind: "question-protocol",
|
|
406
|
-
toolConcept: "userQuestion"
|
|
407
|
-
};
|
|
408
|
-
}
|
|
409
|
-
function createSubagentRulesSection(memoryAccess = "base") {
|
|
410
|
-
return {
|
|
411
|
-
kind: "subagent-rules",
|
|
412
|
-
memoryAccess,
|
|
413
|
-
progressConcept: "progress",
|
|
414
|
-
userQuestionConcept: "userQuestion"
|
|
415
|
-
};
|
|
416
|
-
}
|
|
417
|
-
function createResponseBudgetSection() {
|
|
418
|
-
return { kind: "response-budget" };
|
|
419
|
-
}
|
|
420
|
-
function createStepBudgetSection(steps) {
|
|
421
|
-
if (steps === void 0 || !Number.isInteger(steps) || steps <= 0) {
|
|
422
|
-
return void 0;
|
|
423
|
-
}
|
|
424
|
-
return { kind: "step-budget", steps };
|
|
425
|
-
}
|
|
426
|
-
function detectModelFamilyFromModel(model) {
|
|
427
|
-
const id = getPrimaryModelId(model)?.toLowerCase();
|
|
428
|
-
if (!id) {
|
|
429
|
-
return void 0;
|
|
430
|
-
}
|
|
431
|
-
if (id.includes("claude") || id.startsWith("anthropic/")) {
|
|
432
|
-
return "claude";
|
|
433
|
-
}
|
|
434
|
-
if (id.includes("gpt") || id.startsWith("openai/")) {
|
|
435
|
-
return "openai";
|
|
436
|
-
}
|
|
437
|
-
if (id.includes("gemini") || id.startsWith("google/")) {
|
|
438
|
-
return "gemini";
|
|
439
|
-
}
|
|
440
|
-
if (id.includes("kimi") || id.includes("k2")) {
|
|
441
|
-
return "kimi";
|
|
442
|
-
}
|
|
443
|
-
if (id.includes("glm") || id.startsWith("zai-")) {
|
|
444
|
-
return "glm";
|
|
445
|
-
}
|
|
446
|
-
return void 0;
|
|
447
|
-
}
|
|
448
|
-
function createModelFamilySection(role, model) {
|
|
449
|
-
const family = detectModelFamilyFromModel(model);
|
|
450
|
-
if (!family) {
|
|
451
|
-
return void 0;
|
|
452
|
-
}
|
|
453
|
-
return { kind: "model-family", role, family };
|
|
454
|
-
}
|
|
455
|
-
function roleText(template) {
|
|
456
|
-
return { kind: "role-text", template };
|
|
457
|
-
}
|
|
458
|
-
function specialistSections({
|
|
459
|
-
role,
|
|
460
|
-
mode,
|
|
461
|
-
dispatch,
|
|
462
|
-
scope,
|
|
463
|
-
responsibility,
|
|
464
|
-
rules,
|
|
465
|
-
memoryAccess,
|
|
466
|
-
output
|
|
467
|
-
}) {
|
|
468
|
-
const dispatchLabel = dispatch === "task" ? "{{dispatch.task}}" : "{{dispatch.synchronous-task-only}}";
|
|
469
|
-
return [
|
|
470
|
-
roleText(`<role>
|
|
471
|
-
You are ${role}.
|
|
472
|
-
</role>
|
|
473
|
-
|
|
474
|
-
<mode>
|
|
475
|
-
- Mode: ${mode}
|
|
476
|
-
- Dispatch method: ${dispatchLabel}
|
|
477
|
-
- Scope: ${scope}
|
|
478
|
-
</mode>
|
|
479
|
-
|
|
480
|
-
<responsibility>
|
|
481
|
-
${responsibility}
|
|
482
|
-
</responsibility>
|
|
483
|
-
|
|
484
|
-
<rules>`),
|
|
485
|
-
createSubagentRulesSection(memoryAccess),
|
|
486
|
-
roleText(`${rules.join("\n")}
|
|
487
|
-
</rules>`),
|
|
488
|
-
createQuestionProtocolSection(),
|
|
489
|
-
roleText(`<output>`),
|
|
490
|
-
createResponseBudgetSection(),
|
|
491
|
-
roleText(`${output}
|
|
492
|
-
</output>`)
|
|
493
|
-
];
|
|
494
|
-
}
|
|
495
|
-
function createOrchestratorPromptSections() {
|
|
496
|
-
return [
|
|
497
|
-
roleText(`<role>
|
|
498
|
-
You are the delegate-first root coordinator and decision engine for thoth-agents.
|
|
499
|
-
The root agent is the orchestrator/root coordinator for the session.
|
|
500
|
-
Orchestrator-only, root-only, or orchestrator-owned rules still apply even if
|
|
501
|
-
the harness does not name this agent "orchestrator".
|
|
502
|
-
</role>
|
|
503
|
-
|
|
504
|
-
<style>
|
|
505
|
-
Respond in the user's language. Be warm, direct, evidence-led, and concise.
|
|
506
|
-
Push back when context, risk, or assumptions are weak. Avoid verbosity.
|
|
507
|
-
</style>
|
|
508
|
-
|
|
509
|
-
<core-rules>
|
|
510
|
-
- Mode: primary coordinator. Mutation: none.
|
|
511
|
-
- Load \`thoth-mem-agents\` and \`requirements-interview\`.
|
|
512
|
-
- You MUST NOT read or write any file in the workspace except \`openspec/\` coordination artifacts for the SDD pipeline.
|
|
513
|
-
- Delegate all inspection, writing, searching, debugging, and verification.
|
|
514
|
-
- Own the thinking: analyze the request, choose the approach, synthesize facts, make decisions, ask \`{{userQuestionTool}}\`, manage progress, and own root-session memory.
|
|
515
|
-
- Use sub-agents for evidence and action, not to outsource architecture or planning.
|
|
516
|
-
- Never request raw file dumps from sub-agents; ask for findings, paths, line anchors, diffs, verification, and blockers.
|
|
517
|
-
- Use openspec/ for coordination artifacts, especially
|
|
518
|
-
openspec/changes/{change-name}/tasks.md.
|
|
519
|
-
- Visual or UX work and screenshots always go to {{role.designer}}.
|
|
520
|
-
- Verify through delegation, not inline.
|
|
521
|
-
- Verification should follow the user's project instructions and use the smallest sufficient delegated checks: typecheck, lint, focused tests, or build when appropriate.
|
|
522
|
-
</core-rules>
|
|
523
|
-
|
|
524
|
-
<session-bootstrap>
|
|
525
|
-
- At the start of a new root session, when thoth-mem tools are available, load \`thoth-mem-agents\` and \`requirements-interview\`, call \`mem_session_start\` with the current project and session identity, then save the real user prompt with \`mem_save_prompt\`.
|
|
526
|
-
- Save only the real user request with \`mem_save_prompt\`; never save generated sub-agent prompts, handoffs, summaries, or tool scaffolding as user intent.
|
|
527
|
-
- If thoth-mem tools or required session/project identity are unavailable, disclose that memory bootstrap could not run and continue without pretending memory was saved.
|
|
528
|
-
</session-bootstrap>
|
|
529
|
-
|
|
530
|
-
<routing>
|
|
531
|
-
{{role.explorer}}: read-only codebase discovery. Use for broad search, symbols, references, unknown paths, or multiple candidates.
|
|
532
|
-
{{role.librarian}}: read-only external docs/public examples. Use for version-sensitive APIs, official docs, or unfamiliar libraries.
|
|
533
|
-
{{role.oracle}}: read-only review/diagnosis. Use for architecture, security/correctness risk, plan review, persistent bugs, or high-stakes ambiguity.
|
|
534
|
-
{{role.designer}}: write-capable UI/UX owner. Use for user-facing UI, styles, layout, interactions, and all visual QA.
|
|
535
|
-
{{role.quick}}: write-capable narrow implementer. Use for clear, mechanical, low-risk, uniform edits.
|
|
536
|
-
{{role.deep}}: write-capable thorough implementer. Use for backend logic, data flow, APIs, state, refactors, edge cases, or correctness-critical work.
|
|
537
|
-
|
|
538
|
-
Tiebreakers:
|
|
539
|
-
- User-facing UI -> {{role.designer}}. Backend/system logic -> {{role.deep}}. Mechanical pattern -> {{role.quick}}.
|
|
540
|
-
- Discovery first when paths or facts are unknown; implementation agent may read known local context for its own task, but should not redo broad discovery already assigned to {{role.explorer}}/{{role.librarian}}.
|
|
541
|
-
- Do not use {{role.oracle}} for routine synthesis. After {{role.explorer}}/{{role.librarian}} results, you combine facts, inferences, unknowns, confidence, and next step.
|
|
542
|
-
</routing>
|
|
543
|
-
|
|
544
|
-
<subagent-prompts>
|
|
545
|
-
- Every sub-agent prompt you write must be in English, regardless of the user's language.
|
|
546
|
-
- Keep user-facing replies in the user's language, but translate delegated task prompts, internal handoffs, SDD envelopes, and verification requests into English.
|
|
547
|
-
- Prefer 2-3 surgical discovery probes over one broad exploration when independent facts can be gathered in parallel.
|
|
548
|
-
- A surgical probe asks one narrow question and returns only the anchors needed for your decision.
|
|
549
|
-
</subagent-prompts>
|
|
550
|
-
|
|
551
|
-
<internal-handoff>
|
|
552
|
-
Before dispatching {{role.designer}}, {{role.quick}}, or {{role.deep}} after discovery, synthesize a compact internal handoff. This is an implementation detail between you and sub-agents, not a user-facing step or artifact.
|
|
553
|
-
|
|
554
|
-
Internal handoff fields:
|
|
555
|
-
- Goal: the specific outcome for this task.
|
|
556
|
-
- Decision: the chosen approach and why it is the right next move.
|
|
557
|
-
- Evidence: relevant files, symbols, line anchors, docs, constraints, and known invariants from {{role.explorer}}/{{role.librarian}}.
|
|
558
|
-
- Scope: exact files/areas to change and non-goals to avoid.
|
|
559
|
-
- Steps: ordered implementation instructions, including what to preserve.
|
|
560
|
-
- Verification: smallest sufficient checks or visual QA required.
|
|
561
|
-
- Uncertainty: remaining unknowns the implementer may resolve locally, plus what should be escalated instead of guessed.
|
|
562
|
-
|
|
563
|
-
Never mention the internal handoff to the user, ask the user to prepare it, or present handoff preparation as the recommended next step. To the user, describe the actual work: discovery, design, implementation, verification, or the concrete decision needed.
|
|
564
|
-
|
|
565
|
-
For {{role.explorer}}/{{role.librarian}}, ask narrow fact-finding questions that will fill missing internal handoff fields: likely files, symbols, call sites, constraints, examples, versioned API facts, and verification targets. Require decision-ready findings, not raw context.
|
|
566
|
-
</internal-handoff>
|
|
567
|
-
|
|
568
|
-
<dispatch>
|
|
569
|
-
- If independent delegations are ready, launch them in the same response.
|
|
570
|
-
- Default to normal synchronous \`{{delegationTool}}\` execution.
|
|
571
|
-
- Experimental background \`{{backgroundDelegationTool}}\` is allowed only for {{role.explorer}} and {{role.librarian}} for asynchronous delegation.
|
|
572
|
-
- {{role.oracle}}, {{role.designer}}, {{role.quick}}, and {{role.deep}} always use normal synchronous \`{{delegationTool}}\` execution.
|
|
573
|
-
- When using background \`{{delegationTool}}\`, treat it as conditional and non-portable: if the host does not expose the experimental path, fall back to normal synchronous \`{{delegationTool}}\`.
|
|
574
|
-
- Use \`{{backgroundStatusTool}}\` to wait, poll, and collect background task results before synthesizing or reporting completion.
|
|
575
|
-
- If a result is empty, contradictory, or low-confidence, retry once with a materially sharper prompt; then escalate with evidence via \`{{userQuestionTool}}\`.
|
|
576
|
-
- If a named subagent hits capacity, retry that same role up to 3 attempts.
|
|
577
|
-
- Never switch to \`default\`, \`worker\`, or any other role.
|
|
578
|
-
- After 3 failures, stay on the same role; if a same-role model override exists, use it. Otherwise report a capacity blocker.
|
|
579
|
-
- Write-capable dispatches must include the internal handoff when one exists, so implementers can edit instead of rediscovering the plan.
|
|
580
|
-
- Never tell sub-agents to discard working-tree changes.
|
|
581
|
-
</dispatch>
|
|
582
|
-
|
|
583
|
-
<sdd>
|
|
584
|
-
All work always starts with requirements-interview skill.
|
|
585
|
-
|
|
586
|
-
Routes:
|
|
587
|
-
- Direct implementation for low-complexity work.
|
|
588
|
-
- Accelerated SDD: propose -> tasks.
|
|
589
|
-
- Full SDD: propose -> spec -> design -> tasks.
|
|
590
|
-
|
|
591
|
-
Hard gates:
|
|
592
|
-
- Artifact-producing SDD phases are dispatched to {{role.deep}} or {{role.quick}} with the matching skill loaded.
|
|
593
|
-
- {{role.oracle}} is read-only and only handles plan-reviewer.
|
|
594
|
-
- Never skip artifacts or jump from requirements-interview to implementation when SDD is selected.
|
|
595
|
-
- Before SDD execution, load \`executing-plans\`; then track progress in {{progressTool}} plus the persistent artifact.
|
|
596
|
-
- If openspec persistence is selected and openspec/ is missing, dispatch sdd-init first.
|
|
597
|
-
- During SDD execution, batch compatible implementation work.
|
|
598
|
-
- Group consecutive ready SDD tasks for the same execution agent into one dispatch when dependencies, scope, and verification can be handled together. Keep per-task tracking and evidence; do not split a compatible {{role.designer}}/{{role.quick}}/{{role.deep}} run into one delegation per checkbox.
|
|
599
|
-
|
|
600
|
-
SDD dispatch envelope must include: skill name, persistence mode, pipeline type, change name, project name, needed prior artifact context, verification expectation, and return envelope.
|
|
601
|
-
After each phase, verify the sub-agent reported the openspec path and/or thoth-mem topic_key. Retry once if missing.
|
|
602
|
-
|
|
603
|
-
Artifact governance handoff:
|
|
604
|
-
- After \`sdd-tasks\`, you may surface report-only artifact governance findings before execution preparation starts.
|
|
605
|
-
- Delegate governance inspection; do not inspect repository artifacts inline.
|
|
606
|
-
- Do not treat governance findings as an execution gate.
|
|
607
|
-
- Do not let governance validation replace \`plan-reviewer\` or \`executing-plans\`.
|
|
608
|
-
- Root thoth-mem ownership stays with you; sub-agents may surface findings but must not own session memory, prompts, or progress checkpoints.
|
|
609
|
-
|
|
610
|
-
Plan gate: after tasks, ask with \`{{userQuestionTool}}\`: "Review plan with {{role.oracle}} before executing (Recommended)" or "Proceed to execution".
|
|
611
|
-
If reviewed, the review loop is complete only after [OKAY].
|
|
612
|
-
If {{role.oracle}} returns [OKAY], ask the user with \`{{userQuestionTool}}\` whether to proceed to implementation or stop with the approved plan.
|
|
613
|
-
Do not dispatch \`sdd-apply\` after oracle approval until the user confirms implementation.
|
|
614
|
-
Post-execution: delegate sdd-verify, then sdd-archive when verification passes.
|
|
615
|
-
</sdd>
|
|
616
|
-
|
|
617
|
-
<progress-memory>
|
|
618
|
-
- Keep {{progressTool}} top-level and lean for multi-step work.
|
|
619
|
-
- When SDD is active, update both {{progressTool}} and openspec/changes/{change-name}/tasks.md before dispatch and after results.
|
|
620
|
-
- Root-session memory is yours: search before repeated work; save durable decisions, discoveries, bugs, patterns, constraints, and session summaries.
|
|
621
|
-
- Durable \`mem_save\` guidance: save architecture decisions, accepted or rejected recommendations, bug fixes with root cause, non-obvious discoveries, conventions, configuration changes, and durable user preferences. Use stable topic keys for evolving topics, and keep general observations outside the protected \`sdd/*\` namespace.
|
|
622
|
-
- Targeted 3-layer recall protocol: \`mem_search\` with compact results -> \`mem_timeline\` around promising observations -> \`mem_get_observation\` only for records needed in full. Use preview search only when compact results do not disambiguate.
|
|
623
|
-
- SDD memory artifacts use deterministic topic keys only in thoth-mem or hybrid persistence modes: \`sdd/{change}/{artifact}\`.
|
|
624
|
-
- Before ending the root session, call \`mem_session_summary\` with a concise Goal, Instructions, Discoveries, Accomplished, Next Steps, and Relevant Files summary. Do not claim memory was saved unless the tool call succeeded.
|
|
625
|
-
- After compaction, first preserve the compacted summary with \`mem_session_summary\`, then recover recent context and use the 3-layer recall protocol before continuing work.
|
|
626
|
-
</progress-memory>
|
|
627
|
-
|
|
628
|
-
<communication>
|
|
629
|
-
State the plan briefly, delegate, then summarize outcomes without replaying raw work. Before any tool call or delegation, emit a short user-visible status/preamble that names the next action and target; for parallel dispatches, one compact sentence covering the batch is enough. Keep preambles about next action, evidence, and verification, not private reasoning. Separate evidence, inference, and uncertainty when it matters. Never ask blocking questions in prose.
|
|
630
|
-
</communication>`),
|
|
631
|
-
createQuestionProtocolSection()
|
|
632
|
-
];
|
|
633
|
-
}
|
|
634
|
-
function createReadOnlySpecialistPromptSections(role) {
|
|
635
|
-
if (role === "explorer") {
|
|
636
|
-
return specialistSections({
|
|
637
|
-
role,
|
|
638
|
-
mode: "read-only",
|
|
639
|
-
dispatch: "task",
|
|
640
|
-
scope: "local repository discovery",
|
|
641
|
-
responsibility: "Find workspace facts fast. Return decision-ready evidence for internal handoffs: paths, lines, symbols, constraints, edit targets, and conclusions.",
|
|
642
|
-
rules: [
|
|
643
|
-
"- Questions should be rare; exhaust local evidence first.",
|
|
644
|
-
"- Prefer paths, lines, symbols, and concise summaries over dumps.",
|
|
645
|
-
"- When full content is explicitly requested, reproduce it faithfully."
|
|
646
|
-
],
|
|
647
|
-
memoryAccess: "readonly",
|
|
648
|
-
output: `
|
|
649
|
-
Return exactly these sections, in this order:
|
|
650
|
-
|
|
651
|
-
STATUS: one of CONFIRMED | PARTIAL | INCONCLUSIVE
|
|
652
|
-
- CONFIRMED = direct evidence answers the question with high confidence.
|
|
653
|
-
- PARTIAL = some direct evidence, but gaps remain or multiple candidates exist.
|
|
654
|
-
- INCONCLUSIVE = no sufficient evidence found. Never fabricate a confident answer from naming similarity alone.
|
|
655
|
-
|
|
656
|
-
FINDINGS: bullets with claim, evidence type [direct|inferred|assumed], confidence [high|medium|low], and file:line anchors for concrete claims.
|
|
657
|
-
|
|
658
|
-
ALTERNATIVES CONSIDERED: ranked candidates when more than one plausible match exists. Omit if only one candidate.
|
|
659
|
-
|
|
660
|
-
UNRESOLVED QUESTIONS: what remains ambiguous. State what additional context would unblock the search.
|
|
661
|
-
|
|
662
|
-
UNCHECKED AREAS: what you did not inspect that could change the answer. Omit if nothing notable.
|
|
663
|
-
|
|
664
|
-
SHORT EVIDENCE: at most one short excerpt per key finding, max 2 lines each. Skip if citations are self-explanatory.
|
|
665
|
-
|
|
666
|
-
Lead with STATUS. Stay under 40 lines total when possible. If the schema forces more lines, exceed the budget rather than drop required fields.`
|
|
667
|
-
});
|
|
668
|
-
}
|
|
669
|
-
if (role === "librarian") {
|
|
670
|
-
return specialistSections({
|
|
671
|
-
role,
|
|
672
|
-
mode: "read-only",
|
|
673
|
-
dispatch: "task",
|
|
674
|
-
scope: "external research plus local confirmation when needed",
|
|
675
|
-
responsibility: "Gather authoritative external evidence that helps the orchestrator make implementation decisions. Prefer official docs first, then high-signal public examples. Every substantive claim must carry a source URL.",
|
|
676
|
-
rules: [
|
|
677
|
-
"- Questions should be rare; exhaust available sources first.",
|
|
678
|
-
"- Prefer official documentation over commentary when both answer the same point.",
|
|
679
|
-
"- Distinguish clearly between official guidance and community examples."
|
|
680
|
-
],
|
|
681
|
-
memoryAccess: "readonly",
|
|
682
|
-
output: `- Organize by finding. Include a source URL for every claim.
|
|
683
|
-
- Distinguish official docs from community examples.
|
|
684
|
-
- Return synthesized findings, not full documentation excerpts.
|
|
685
|
-
- Target: under 40 lines total.`
|
|
686
|
-
});
|
|
687
|
-
}
|
|
688
|
-
return specialistSections({
|
|
689
|
-
role,
|
|
690
|
-
mode: "read-only",
|
|
691
|
-
dispatch: "synchronous task only",
|
|
692
|
-
scope: "advice, diagnosis, architecture, code review, and plan review",
|
|
693
|
-
responsibility: "Provide strategic technical guidance anchored to evidence. Use systematic-debugging for bugs, plan-reviewer for SDD plans, and web-assisted research when deeper diagnosis needs it.",
|
|
694
|
-
rules: [
|
|
695
|
-
"- Cite exact files and lines for local claims.",
|
|
696
|
-
"- Separate observations, risks, and recommendations.",
|
|
697
|
-
"- Ask only when tradeoffs, risk tolerance, or approval materially change the recommendation."
|
|
698
|
-
],
|
|
699
|
-
memoryAccess: "readonly",
|
|
700
|
-
output: `- Cite exact files and lines \u2014 do not quote large code blocks.
|
|
701
|
-
- Separate observations, risks, and recommendations.
|
|
702
|
-
- For diagnosis: root cause + fix recommendation, not step-by-step trace.
|
|
703
|
-
- Target: under 50 lines total.`
|
|
704
|
-
});
|
|
705
|
-
}
|
|
706
|
-
function createWriteCapableSpecialistPromptSections(role) {
|
|
707
|
-
if (role === "designer") {
|
|
708
|
-
return specialistSections({
|
|
709
|
-
role,
|
|
710
|
-
mode: "write-capable",
|
|
711
|
-
dispatch: "synchronous task only",
|
|
712
|
-
scope: "UI/UX decisions, implementation, and visual verification",
|
|
713
|
-
responsibility: "Own the user-facing solution end to end: choose the UX approach, implement it, and verify it visually. Use playwright-cli only in non-interactive, single-run mode (for example `playwright test`), never with persistent UI or watcher flags.\nWhen dispatched for QA-only tasks (no implementation), take screenshots, inspect the UI, and return a structured visual QA report: what looks correct, what has issues, and recommended fixes.",
|
|
714
|
-
rules: [
|
|
715
|
-
"- Treat the orchestrator's internal handoff as the handoff; do not rediscover settled scope or constraints.",
|
|
716
|
-
"- Own UX decisions instead of bouncing them back unless a real user preference is required.",
|
|
717
|
-
"- Verify visually when feasible; do not stop at code that merely compiles.",
|
|
718
|
-
"- Keep changes focused on the user-facing outcome.",
|
|
719
|
-
"- NEVER run blocking or long-running commands: no `playwright test --ui`, `playwright show-report`, `--headed --debug`, dev servers, or watchers. Use single-run variants and capture screenshots/traces as artifacts."
|
|
720
|
-
],
|
|
721
|
-
memoryAccess: "writable",
|
|
722
|
-
output: `For SDD tasks: use the Task Result envelope (Status, Task, What was done, Files changed, Verification, Issues).
|
|
723
|
-
For non-SDD work: state what was implemented, verification status, and remaining caveats.
|
|
724
|
-
- Include visual verification status when applicable.
|
|
725
|
-
- Target: under 30 lines total.`
|
|
726
|
-
});
|
|
727
|
-
}
|
|
728
|
-
if (role === "quick") {
|
|
729
|
-
return specialistSections({
|
|
730
|
-
role,
|
|
731
|
-
mode: "write-capable",
|
|
732
|
-
dispatch: "synchronous task only",
|
|
733
|
-
scope: "fast bounded implementation",
|
|
734
|
-
responsibility: "Implement well-defined changes quickly. Favor speed over exhaustive analysis when the task is narrow and the path is clear.",
|
|
735
|
-
rules: [
|
|
736
|
-
"- Optimize for fast execution on narrow, clear tasks.",
|
|
737
|
-
"- Treat the orchestrator's internal handoff as the starting point; follow its file anchors, scope, non-goals, and verification target.",
|
|
738
|
-
"- Read only the context you need.",
|
|
739
|
-
"- Do not redo broad discovery. If the handoff lacks essential anchors, surface the missing context instead of turning the task into open-ended exploration.",
|
|
740
|
-
"- Avoid multi-step planning; if the task stops being bounded, surface it.",
|
|
741
|
-
"- Ask only for implementation-local ambiguity, not orchestrator-level routing.",
|
|
742
|
-
"- NEVER run git commands that discard changes (`git restore`, `git checkout --`, `git reset`, `git clean`). Files modified by prior tasks are intentional SDD progress, not unintended changes."
|
|
743
|
-
],
|
|
744
|
-
memoryAccess: "writable",
|
|
745
|
-
output: `For SDD tasks: use the Task Result envelope (Status, Task, What was done, Files changed, Verification, Issues).
|
|
746
|
-
For non-SDD work: status + summary + files changed + issues. Nothing more.
|
|
747
|
-
- Target: under 20 lines total.`
|
|
748
|
-
});
|
|
749
|
-
}
|
|
750
|
-
return specialistSections({
|
|
751
|
-
role,
|
|
752
|
-
mode: "write-capable",
|
|
753
|
-
dispatch: "synchronous task only",
|
|
754
|
-
scope: "thorough implementation and verification",
|
|
755
|
-
responsibility: "Handle correctness-critical, multi-file, or edge-case-heavy changes with full local context analysis. Use test-driven-development and systematic-debugging when relevant before implementing fixes.",
|
|
756
|
-
rules: [
|
|
757
|
-
"- Treat the orchestrator's internal handoff as the architecture handoff; validate it against nearby code, but do not restart upstream discovery unless evidence contradicts it.",
|
|
758
|
-
"- Do not skip verification \u2014 thoroughness is your value proposition.",
|
|
759
|
-
"- Investigate related files, types, and call sites before changing shared behavior, prioritizing the anchors and constraints in the handoff.",
|
|
760
|
-
"- Ask when a real architecture or implementation tradeoff blocks correct execution."
|
|
761
|
-
],
|
|
762
|
-
memoryAccess: "writable",
|
|
763
|
-
output: `For SDD tasks: use the Task Result envelope (Status, Task, What was done, Files changed, Verification, Issues).
|
|
764
|
-
For non-SDD work: summary + files changed + verification results + edge cases considered.
|
|
765
|
-
- Save detailed analysis for follow-up requests; return only actionable conclusions.
|
|
766
|
-
- Target: under 40 lines total.`
|
|
767
|
-
});
|
|
768
|
-
}
|
|
769
|
-
function getPrimaryModelId(model) {
|
|
770
|
-
if (Array.isArray(model)) {
|
|
771
|
-
const first = model[0];
|
|
772
|
-
return typeof first === "string" ? first : first?.id;
|
|
773
|
-
}
|
|
774
|
-
return model;
|
|
775
|
-
}
|
|
776
|
-
function renderQuestionProtocol(_section, dialect) {
|
|
777
|
-
return `<questions>
|
|
778
|
-
Use \`${dialect.tools.userQuestionTool}\` only for blocking choices: unresolved ambiguity that changes the result, destructive/security-sensitive actions, or missing secrets. Do all non-blocked work first, ask one targeted question with a recommended default first, then stop.
|
|
779
|
-
</questions>`;
|
|
780
|
-
}
|
|
781
|
-
function renderSubagentRules(section, dialect) {
|
|
782
|
-
const rules = [
|
|
783
|
-
`- Single-task leaf agent: do not delegate, manage SDD phases, act as orchestrator, or call \`${dialect.tools.progressTool}\`.`,
|
|
784
|
-
`- Use \`${dialect.tools.userQuestionTool}\` only for local blocking decisions.`,
|
|
785
|
-
"- Never discard working-tree changes: no `git restore`, `git checkout -- <path>`, `git reset --hard`, `git clean`, or `git stash`.",
|
|
786
|
-
"- Avoid blocking/watch commands; use terminating checks only."
|
|
787
|
-
];
|
|
788
|
-
if (section.memoryAccess === "readonly") {
|
|
789
|
-
rules.push(
|
|
790
|
-
"- Use read-only thoth-mem only when dispatch gives session_id/project: `mem_search` -> `mem_timeline` -> `mem_get_observation`.",
|
|
791
|
-
"- Never call `mem_session_start`, `mem_session_summary`, or `mem_save_prompt`; those tools are orchestrator-owned.",
|
|
792
|
-
"- Never write memory; memory writes are orchestrator-owned."
|
|
793
|
-
);
|
|
794
|
-
}
|
|
795
|
-
if (section.memoryAccess === "writable") {
|
|
796
|
-
rules.push(
|
|
797
|
-
"- Use delegated thoth-mem tools only (mem_save, mem_search, mem_get_observation, mem_timeline, mem_suggest_topic_key).",
|
|
798
|
-
"- Never call `mem_session_start`, `mem_session_summary`, or `mem_save_prompt`; those tools are orchestrator-owned.",
|
|
799
|
-
"- Always use the parent session_id/project from dispatch for every thoth-mem call.",
|
|
800
|
-
"- If either is missing, do NOT call thoth-mem.",
|
|
801
|
-
"- For reads, use only `mem_search` -> `mem_timeline` -> `mem_get_observation`.",
|
|
802
|
-
"- You do not own durable memory of your own; `mem_save` writes under the orchestrator's session/project only."
|
|
803
|
-
);
|
|
804
|
-
}
|
|
805
|
-
return rules.join("\n");
|
|
806
|
-
}
|
|
807
|
-
function renderResponseBudget() {
|
|
808
|
-
return "Return concise structured results: status, summary, files, verification/issues. Never return raw file dumps.";
|
|
809
|
-
}
|
|
810
|
-
function renderStepBudget(section) {
|
|
811
|
-
return `<step-budget>
|
|
812
|
-
- You have a hard execution budget of ${section.steps} steps.
|
|
813
|
-
- Plan your tool use before acting, prioritize the highest-signal checks first, and stop once you have enough evidence to answer.
|
|
814
|
-
- Avoid repeated searches or reads. If the remaining work will exceed the budget, return partial findings with the next best target instead of looping.
|
|
815
|
-
</step-budget>`;
|
|
816
|
-
}
|
|
817
|
-
function getRoleModelProfile(role) {
|
|
818
|
-
switch (role) {
|
|
819
|
-
case "orchestrator":
|
|
820
|
-
return "- Exploit your role by selecting the right specialist category, launching independent tasks together, and synthesizing facts/inferences/unknowns before the next dispatch.";
|
|
821
|
-
case "explorer":
|
|
822
|
-
return "- Exploit your role by scanning broadly first, then narrowing to symbol/path evidence with ranked candidates and confidence.";
|
|
823
|
-
case "librarian":
|
|
824
|
-
return "- Exploit your role by prioritizing official docs, dates, versions, and source quality before summarizing public examples.";
|
|
825
|
-
case "oracle":
|
|
826
|
-
return "- Exploit your role by challenging assumptions, identifying risk, and giving a decision-ready recommendation backed by evidence.";
|
|
827
|
-
case "designer":
|
|
828
|
-
return "- Exploit your role by making concrete UX choices, implementing them, and verifying the visible result instead of stopping at code review.";
|
|
829
|
-
case "quick":
|
|
830
|
-
return "- Exploit your role by applying the smallest clear edit, avoiding broad exploration, and returning immediately after focused verification.";
|
|
831
|
-
case "deep":
|
|
832
|
-
return "- Exploit your role by building a complete mental model of shared behavior, writing tests first when behavior changes, and verifying edge cases.";
|
|
833
|
-
}
|
|
834
|
-
}
|
|
835
|
-
function renderModelFamily(section) {
|
|
836
|
-
const roleGuidance = getRoleModelProfile(section.role);
|
|
837
|
-
if (section.family === "claude") {
|
|
838
|
-
return `<model-profile family="claude">
|
|
839
|
-
- Use XML-like sections, label uncertainty, and delegate aggressively when agentic.
|
|
840
|
-
${roleGuidance}
|
|
841
|
-
</model-profile>`;
|
|
842
|
-
}
|
|
843
|
-
if (section.family === "openai") {
|
|
844
|
-
return `<model-profile family="openai">
|
|
845
|
-
- Plan briefly, then act. Keep tool dispatch explicit: action, target, return shape.
|
|
846
|
-
${roleGuidance}
|
|
847
|
-
</model-profile>`;
|
|
848
|
-
}
|
|
849
|
-
if (section.family === "gemini") {
|
|
850
|
-
return `<model-profile family="gemini">
|
|
851
|
-
- Use long-context breadth deliberately, then ground conclusions in exact anchors.
|
|
852
|
-
${roleGuidance}
|
|
853
|
-
</model-profile>`;
|
|
854
|
-
}
|
|
855
|
-
if (section.family === "kimi") {
|
|
856
|
-
return `<model-profile family="kimi">
|
|
857
|
-
- Favor repository-scale navigation before edits; keep patches grounded in current file state.
|
|
858
|
-
${roleGuidance}
|
|
859
|
-
</model-profile>`;
|
|
860
|
-
}
|
|
861
|
-
return `<model-profile family="glm">
|
|
862
|
-
- Use compact checklists, conservative steps, clear verification, and concrete blockers.
|
|
863
|
-
${roleGuidance}
|
|
864
|
-
</model-profile>`;
|
|
865
|
-
}
|
|
866
|
-
function renderRoleText(section, dialect) {
|
|
867
|
-
return section.template.replaceAll("{{delegationTool}}", dialect.tools.delegationTool).replaceAll(
|
|
868
|
-
"{{backgroundDelegationTool}}",
|
|
869
|
-
dialect.tools.backgroundDelegationTool ?? dialect.tools.delegationTool
|
|
870
|
-
).replaceAll(
|
|
871
|
-
"{{backgroundStatusTool}}",
|
|
872
|
-
dialect.tools.backgroundStatusTool ?? dialect.tools.hostStatusSurface ?? ""
|
|
873
|
-
).replaceAll("{{userQuestionTool}}", dialect.tools.userQuestionTool).replaceAll("{{progressTool}}", dialect.tools.progressTool).replaceAll("{{dispatch.task}}", dialect.dispatchLabel("task")).replaceAll(
|
|
874
|
-
"{{dispatch.synchronous-task-only}}",
|
|
875
|
-
dialect.dispatchLabel("synchronous-task-only")
|
|
876
|
-
).replace(
|
|
877
|
-
/\{\{role\.(\w+)\}\}/g,
|
|
878
|
-
(_match, role) => dialect.renderRoleInvocation(role)
|
|
879
|
-
);
|
|
880
|
-
}
|
|
881
|
-
function renderPromptSection(section, dialect) {
|
|
882
|
-
switch (section.kind) {
|
|
883
|
-
case "question-protocol":
|
|
884
|
-
return renderQuestionProtocol(section, dialect);
|
|
885
|
-
case "subagent-rules":
|
|
886
|
-
return renderSubagentRules(section, dialect);
|
|
887
|
-
case "response-budget":
|
|
888
|
-
return renderResponseBudget();
|
|
889
|
-
case "step-budget":
|
|
890
|
-
return renderStepBudget(section);
|
|
891
|
-
case "model-family":
|
|
892
|
-
return renderModelFamily(section);
|
|
893
|
-
case "role-text":
|
|
894
|
-
return renderRoleText(section, dialect);
|
|
895
|
-
}
|
|
896
|
-
}
|
|
897
|
-
function renderRolePrompt(sections, dialect) {
|
|
898
|
-
return sections.map((section) => renderPromptSection(section, dialect).trim()).filter(Boolean).join("\n\n");
|
|
899
|
-
}
|
|
2
|
+
spawn,
|
|
3
|
+
spawnSync
|
|
4
|
+
} from "./chunk-DYGVRAMS.js";
|
|
5
|
+
import {
|
|
6
|
+
DEFAULT_MODELS,
|
|
7
|
+
DEFAULT_THOTH_COMMAND,
|
|
8
|
+
OPENCODE_PROMPT_DIALECT,
|
|
9
|
+
POLL_INTERVAL_BACKGROUND_MS,
|
|
10
|
+
SUBAGENT_NAMES,
|
|
11
|
+
appendPromptSections,
|
|
12
|
+
composeAgentPrompt,
|
|
13
|
+
context7,
|
|
14
|
+
createOrchestratorPromptSections,
|
|
15
|
+
createReadOnlySpecialistPromptSections,
|
|
16
|
+
createWriteCapableSpecialistPromptSections,
|
|
17
|
+
detectModelFamily,
|
|
18
|
+
exa,
|
|
19
|
+
getAgentOverride,
|
|
20
|
+
getModelFamilyPromptSection,
|
|
21
|
+
getOpenCodeConfigPaths,
|
|
22
|
+
getStepBudgetPromptSection,
|
|
23
|
+
grep_app,
|
|
24
|
+
installCustomSkills,
|
|
25
|
+
loadAgentPrompt,
|
|
26
|
+
loadPluginConfig,
|
|
27
|
+
renderRolePrompt,
|
|
28
|
+
stripJsonComments
|
|
29
|
+
} from "./chunk-OES76C67.js";
|
|
900
30
|
|
|
901
|
-
// src/
|
|
902
|
-
|
|
903
|
-
createQuestionProtocolSection(),
|
|
904
|
-
OPENCODE_PROMPT_DIALECT
|
|
905
|
-
);
|
|
906
|
-
var SUBAGENT_RULES = renderPromptSection(
|
|
907
|
-
createSubagentRulesSection("base"),
|
|
908
|
-
OPENCODE_PROMPT_DIALECT
|
|
909
|
-
);
|
|
910
|
-
var SUBAGENT_RULES_READONLY = renderPromptSection(
|
|
911
|
-
createSubagentRulesSection("readonly"),
|
|
912
|
-
OPENCODE_PROMPT_DIALECT
|
|
913
|
-
);
|
|
914
|
-
var SUBAGENT_RULES_WRITABLE = renderPromptSection(
|
|
915
|
-
createSubagentRulesSection("writable"),
|
|
916
|
-
OPENCODE_PROMPT_DIALECT
|
|
917
|
-
);
|
|
918
|
-
var RESPONSE_BUDGET = renderPromptSection(
|
|
919
|
-
createResponseBudgetSection(),
|
|
920
|
-
OPENCODE_PROMPT_DIALECT
|
|
921
|
-
);
|
|
922
|
-
function trimPromptSection(section) {
|
|
923
|
-
const trimmed = section?.trim();
|
|
924
|
-
return trimmed ? trimmed : void 0;
|
|
925
|
-
}
|
|
926
|
-
function appendPromptSections(...sections) {
|
|
927
|
-
return sections.map(trimPromptSection).filter(Boolean).join("\n\n");
|
|
928
|
-
}
|
|
929
|
-
function detectModelFamily(model) {
|
|
930
|
-
return detectModelFamilyFromModel(model);
|
|
931
|
-
}
|
|
932
|
-
function getStepBudgetPromptSection(steps) {
|
|
933
|
-
const section = createStepBudgetSection(steps);
|
|
934
|
-
if (!section) {
|
|
935
|
-
return void 0;
|
|
936
|
-
}
|
|
937
|
-
return renderPromptSection(section, OPENCODE_PROMPT_DIALECT);
|
|
938
|
-
}
|
|
939
|
-
function getModelFamilyPromptSection(role, model) {
|
|
940
|
-
const section = createModelFamilySection(role, model);
|
|
941
|
-
if (!section) {
|
|
942
|
-
return void 0;
|
|
943
|
-
}
|
|
944
|
-
return renderPromptSection(section, OPENCODE_PROMPT_DIALECT);
|
|
945
|
-
}
|
|
946
|
-
function replacePromptPlaceholders(template, placeholders = {}) {
|
|
947
|
-
return Object.entries(placeholders).reduce((prompt, [key, value]) => {
|
|
948
|
-
if (value === void 0) {
|
|
949
|
-
return prompt;
|
|
950
|
-
}
|
|
951
|
-
return prompt.replaceAll(`{{${key}}}`, String(value));
|
|
952
|
-
}, template);
|
|
953
|
-
}
|
|
954
|
-
function composeAgentPrompt({
|
|
955
|
-
basePrompt,
|
|
956
|
-
customPrompt,
|
|
957
|
-
customAppendPrompt,
|
|
958
|
-
placeholders
|
|
959
|
-
}) {
|
|
960
|
-
const resolvedBase = replacePromptPlaceholders(basePrompt, placeholders);
|
|
961
|
-
if (customPrompt) {
|
|
962
|
-
return replacePromptPlaceholders(customPrompt, placeholders);
|
|
963
|
-
}
|
|
964
|
-
return appendPromptSections(
|
|
965
|
-
resolvedBase,
|
|
966
|
-
replacePromptPlaceholders(customAppendPrompt ?? "", placeholders)
|
|
967
|
-
);
|
|
968
|
-
}
|
|
31
|
+
// src/index.ts
|
|
32
|
+
import path4 from "path";
|
|
969
33
|
|
|
970
34
|
// src/agents/deep.ts
|
|
971
35
|
var DEEP_PROMPT = renderRolePrompt(
|
|
@@ -1428,112 +492,39 @@ import { createRequire } from "module";
|
|
|
1428
492
|
var require2 = createRequire(import.meta.url);
|
|
1429
493
|
function getLogFile() {
|
|
1430
494
|
const os2 = require2("node:os");
|
|
1431
|
-
const
|
|
1432
|
-
|
|
495
|
+
const path5 = require2("node:path");
|
|
496
|
+
const configuredPath = process.env.THOTH_AGENTS_LOG_FILE?.trim();
|
|
497
|
+
return configuredPath || path5.join(os2.tmpdir(), "thoth-agents.log");
|
|
1433
498
|
}
|
|
1434
499
|
function log(message, data) {
|
|
1435
500
|
try {
|
|
1436
|
-
const
|
|
501
|
+
const fs3 = require2("node:fs");
|
|
1437
502
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
1438
503
|
const logEntry = `[${timestamp}] ${message} ${data ? JSON.stringify(data) : ""}
|
|
1439
504
|
`;
|
|
1440
|
-
|
|
505
|
+
fs3.appendFileSync(getLogFile(), logEntry);
|
|
1441
506
|
} catch {
|
|
1442
507
|
}
|
|
1443
508
|
}
|
|
1444
509
|
|
|
1445
510
|
// src/hooks/auto-update-checker/cache.ts
|
|
1446
|
-
import * as
|
|
1447
|
-
import * as
|
|
1448
|
-
|
|
1449
|
-
// src/cli/system.ts
|
|
1450
|
-
import { statSync as statSync2 } from "fs";
|
|
1451
|
-
|
|
1452
|
-
// src/utils/subprocess.ts
|
|
1453
|
-
import {
|
|
1454
|
-
spawn as nodeSpawn,
|
|
1455
|
-
spawnSync as nodeSpawnSync
|
|
1456
|
-
} from "child_process";
|
|
1457
|
-
import { Readable } from "stream";
|
|
1458
|
-
function emptyReadableStream() {
|
|
1459
|
-
return new ReadableStream({
|
|
1460
|
-
start(controller) {
|
|
1461
|
-
controller.close();
|
|
1462
|
-
}
|
|
1463
|
-
});
|
|
1464
|
-
}
|
|
1465
|
-
function toWebReadable(stream) {
|
|
1466
|
-
if (!stream) {
|
|
1467
|
-
return emptyReadableStream();
|
|
1468
|
-
}
|
|
1469
|
-
return Readable.toWeb(
|
|
1470
|
-
stream
|
|
1471
|
-
);
|
|
1472
|
-
}
|
|
1473
|
-
function fallbackStdin() {
|
|
1474
|
-
return {
|
|
1475
|
-
write: () => void 0,
|
|
1476
|
-
end: () => void 0
|
|
1477
|
-
};
|
|
1478
|
-
}
|
|
1479
|
-
function spawn(command, options = {}) {
|
|
1480
|
-
const child = nodeSpawn(command[0], command.slice(1), {
|
|
1481
|
-
cwd: options.cwd,
|
|
1482
|
-
env: options.env,
|
|
1483
|
-
stdio: [
|
|
1484
|
-
options.stdin ?? "pipe",
|
|
1485
|
-
options.stdout ?? "pipe",
|
|
1486
|
-
options.stderr ?? "pipe"
|
|
1487
|
-
]
|
|
1488
|
-
});
|
|
1489
|
-
const managed = {
|
|
1490
|
-
stdin: child.stdin ?? fallbackStdin(),
|
|
1491
|
-
stdout: toWebReadable(child.stdout),
|
|
1492
|
-
stderr: toWebReadable(child.stderr),
|
|
1493
|
-
exited: new Promise((resolve4) => {
|
|
1494
|
-
child.on("exit", (code) => {
|
|
1495
|
-
managed.exitCode = code;
|
|
1496
|
-
resolve4(code ?? 1);
|
|
1497
|
-
});
|
|
1498
|
-
child.on("error", () => {
|
|
1499
|
-
managed.exitCode = 1;
|
|
1500
|
-
resolve4(1);
|
|
1501
|
-
});
|
|
1502
|
-
}),
|
|
1503
|
-
exitCode: child.exitCode,
|
|
1504
|
-
kill: () => {
|
|
1505
|
-
child.kill();
|
|
1506
|
-
}
|
|
1507
|
-
};
|
|
1508
|
-
return managed;
|
|
1509
|
-
}
|
|
1510
|
-
function spawnSync(command, options = {}) {
|
|
1511
|
-
const result = nodeSpawnSync(command[0], command.slice(1), {
|
|
1512
|
-
cwd: options.cwd,
|
|
1513
|
-
env: options.env,
|
|
1514
|
-
stdio: [
|
|
1515
|
-
options.stdin ?? "ignore",
|
|
1516
|
-
options.stdout ?? "pipe",
|
|
1517
|
-
options.stderr ?? "pipe"
|
|
1518
|
-
]
|
|
1519
|
-
});
|
|
1520
|
-
return { exitCode: result.status };
|
|
1521
|
-
}
|
|
511
|
+
import * as fs from "fs";
|
|
512
|
+
import * as path2 from "path";
|
|
1522
513
|
|
|
1523
514
|
// src/hooks/auto-update-checker/constants.ts
|
|
1524
515
|
import * as os from "os";
|
|
1525
|
-
import * as
|
|
516
|
+
import * as path from "path";
|
|
1526
517
|
var PACKAGE_NAME = "thoth-agents";
|
|
1527
518
|
var NPM_REGISTRY_URL = `https://registry.npmjs.org/-/package/${PACKAGE_NAME}/dist-tags`;
|
|
1528
519
|
var NPM_FETCH_TIMEOUT = 5e3;
|
|
1529
520
|
function getCacheDir() {
|
|
1530
521
|
if (process.platform === "win32") {
|
|
1531
|
-
return
|
|
522
|
+
return path.join(process.env.LOCALAPPDATA ?? os.homedir(), "opencode");
|
|
1532
523
|
}
|
|
1533
|
-
return
|
|
524
|
+
return path.join(os.homedir(), ".cache", "opencode");
|
|
1534
525
|
}
|
|
1535
526
|
var CACHE_DIR = getCacheDir();
|
|
1536
|
-
var INSTALLED_PACKAGE_JSON =
|
|
527
|
+
var INSTALLED_PACKAGE_JSON = path.join(
|
|
1537
528
|
CACHE_DIR,
|
|
1538
529
|
"node_modules",
|
|
1539
530
|
PACKAGE_NAME,
|
|
@@ -1546,22 +537,22 @@ var USER_OPENCODE_CONFIG_JSONC = configPaths[1];
|
|
|
1546
537
|
// src/hooks/auto-update-checker/cache.ts
|
|
1547
538
|
function invalidatePnpmPackageCache(packageName = PACKAGE_NAME) {
|
|
1548
539
|
try {
|
|
1549
|
-
const pkgDir =
|
|
1550
|
-
const pkgJsonPath =
|
|
540
|
+
const pkgDir = path2.join(CACHE_DIR, "node_modules", packageName);
|
|
541
|
+
const pkgJsonPath = path2.join(CACHE_DIR, "package.json");
|
|
1551
542
|
let packageRemoved = false;
|
|
1552
543
|
let dependencyRemoved = false;
|
|
1553
|
-
if (
|
|
1554
|
-
|
|
544
|
+
if (fs.existsSync(pkgDir)) {
|
|
545
|
+
fs.rmSync(pkgDir, { recursive: true, force: true });
|
|
1555
546
|
log(`[auto-update-checker] Package removed: ${pkgDir}`);
|
|
1556
547
|
packageRemoved = true;
|
|
1557
548
|
}
|
|
1558
|
-
if (
|
|
549
|
+
if (fs.existsSync(pkgJsonPath)) {
|
|
1559
550
|
try {
|
|
1560
|
-
const content =
|
|
551
|
+
const content = fs.readFileSync(pkgJsonPath, "utf-8");
|
|
1561
552
|
const pkgJson = JSON.parse(stripJsonComments(content));
|
|
1562
553
|
if (pkgJson.dependencies?.[packageName]) {
|
|
1563
554
|
delete pkgJson.dependencies[packageName];
|
|
1564
|
-
|
|
555
|
+
fs.writeFileSync(pkgJsonPath, JSON.stringify(pkgJson, null, 2));
|
|
1565
556
|
log(
|
|
1566
557
|
`[auto-update-checker] Dependency removed from package.json: ${packageName}`
|
|
1567
558
|
);
|
|
@@ -1588,8 +579,8 @@ function invalidatePnpmPackageCache(packageName = PACKAGE_NAME) {
|
|
|
1588
579
|
}
|
|
1589
580
|
|
|
1590
581
|
// src/hooks/auto-update-checker/checker.ts
|
|
1591
|
-
import * as
|
|
1592
|
-
import * as
|
|
582
|
+
import * as fs2 from "fs";
|
|
583
|
+
import * as path3 from "path";
|
|
1593
584
|
import { fileURLToPath } from "url";
|
|
1594
585
|
function isPrereleaseVersion(version) {
|
|
1595
586
|
return version.includes("-");
|
|
@@ -1611,8 +602,8 @@ function extractChannel(version) {
|
|
|
1611
602
|
}
|
|
1612
603
|
function getConfigPaths(directory) {
|
|
1613
604
|
return [
|
|
1614
|
-
|
|
1615
|
-
|
|
605
|
+
path3.join(directory, ".opencode", "opencode.json"),
|
|
606
|
+
path3.join(directory, ".opencode", "opencode.jsonc"),
|
|
1616
607
|
USER_OPENCODE_CONFIG,
|
|
1617
608
|
USER_OPENCODE_CONFIG_JSONC
|
|
1618
609
|
];
|
|
@@ -1620,8 +611,8 @@ function getConfigPaths(directory) {
|
|
|
1620
611
|
function getLocalDevPath(directory) {
|
|
1621
612
|
for (const configPath of getConfigPaths(directory)) {
|
|
1622
613
|
try {
|
|
1623
|
-
if (!
|
|
1624
|
-
const content =
|
|
614
|
+
if (!fs2.existsSync(configPath)) continue;
|
|
615
|
+
const content = fs2.readFileSync(configPath, "utf-8");
|
|
1625
616
|
const config = JSON.parse(stripJsonComments(content));
|
|
1626
617
|
const plugins = config.plugin ?? [];
|
|
1627
618
|
for (const entry of plugins) {
|
|
@@ -1640,19 +631,19 @@ function getLocalDevPath(directory) {
|
|
|
1640
631
|
}
|
|
1641
632
|
function findPackageJsonUp(startPath) {
|
|
1642
633
|
try {
|
|
1643
|
-
const stat =
|
|
1644
|
-
let dir = stat.isDirectory() ? startPath :
|
|
634
|
+
const stat = fs2.statSync(startPath);
|
|
635
|
+
let dir = stat.isDirectory() ? startPath : path3.dirname(startPath);
|
|
1645
636
|
for (let i = 0; i < 10; i++) {
|
|
1646
|
-
const pkgPath =
|
|
1647
|
-
if (
|
|
637
|
+
const pkgPath = path3.join(dir, "package.json");
|
|
638
|
+
if (fs2.existsSync(pkgPath)) {
|
|
1648
639
|
try {
|
|
1649
|
-
const content =
|
|
640
|
+
const content = fs2.readFileSync(pkgPath, "utf-8");
|
|
1650
641
|
const pkg = JSON.parse(content);
|
|
1651
642
|
if (pkg.name === PACKAGE_NAME) return pkgPath;
|
|
1652
643
|
} catch {
|
|
1653
644
|
}
|
|
1654
645
|
}
|
|
1655
|
-
const parent =
|
|
646
|
+
const parent = path3.dirname(dir);
|
|
1656
647
|
if (parent === dir) break;
|
|
1657
648
|
dir = parent;
|
|
1658
649
|
}
|
|
@@ -1666,7 +657,7 @@ function getLocalDevVersion(directory) {
|
|
|
1666
657
|
try {
|
|
1667
658
|
const pkgPath = findPackageJsonUp(localPath);
|
|
1668
659
|
if (!pkgPath) return null;
|
|
1669
|
-
const content =
|
|
660
|
+
const content = fs2.readFileSync(pkgPath, "utf-8");
|
|
1670
661
|
const pkg = JSON.parse(content);
|
|
1671
662
|
return pkg.version ?? null;
|
|
1672
663
|
} catch {
|
|
@@ -1676,8 +667,8 @@ function getLocalDevVersion(directory) {
|
|
|
1676
667
|
function findPluginEntry(directory) {
|
|
1677
668
|
for (const configPath of getConfigPaths(directory)) {
|
|
1678
669
|
try {
|
|
1679
|
-
if (!
|
|
1680
|
-
const content =
|
|
670
|
+
if (!fs2.existsSync(configPath)) continue;
|
|
671
|
+
const content = fs2.readFileSync(configPath, "utf-8");
|
|
1681
672
|
const config = JSON.parse(stripJsonComments(content));
|
|
1682
673
|
const plugins = config.plugin ?? [];
|
|
1683
674
|
for (const entry of plugins) {
|
|
@@ -1704,8 +695,8 @@ var cachedPackageVersion = null;
|
|
|
1704
695
|
function getCachedVersion() {
|
|
1705
696
|
if (cachedPackageVersion) return cachedPackageVersion;
|
|
1706
697
|
try {
|
|
1707
|
-
if (
|
|
1708
|
-
const content =
|
|
698
|
+
if (fs2.existsSync(INSTALLED_PACKAGE_JSON)) {
|
|
699
|
+
const content = fs2.readFileSync(INSTALLED_PACKAGE_JSON, "utf-8");
|
|
1709
700
|
const pkg = JSON.parse(content);
|
|
1710
701
|
if (pkg.version) {
|
|
1711
702
|
cachedPackageVersion = pkg.version;
|
|
@@ -1715,10 +706,10 @@ function getCachedVersion() {
|
|
|
1715
706
|
} catch {
|
|
1716
707
|
}
|
|
1717
708
|
try {
|
|
1718
|
-
const currentDir =
|
|
709
|
+
const currentDir = path3.dirname(fileURLToPath(import.meta.url));
|
|
1719
710
|
const pkgPath = findPackageJsonUp(currentDir);
|
|
1720
711
|
if (pkgPath) {
|
|
1721
|
-
const content =
|
|
712
|
+
const content = fs2.readFileSync(pkgPath, "utf-8");
|
|
1722
713
|
const pkg = JSON.parse(content);
|
|
1723
714
|
if (pkg.version) {
|
|
1724
715
|
cachedPackageVersion = pkg.version;
|
|
@@ -1735,8 +726,8 @@ function getCachedVersion() {
|
|
|
1735
726
|
}
|
|
1736
727
|
function updatePinnedVersion(configPath, oldEntry, newVersion) {
|
|
1737
728
|
try {
|
|
1738
|
-
if (!
|
|
1739
|
-
const content =
|
|
729
|
+
if (!fs2.existsSync(configPath)) return false;
|
|
730
|
+
const content = fs2.readFileSync(configPath, "utf-8");
|
|
1740
731
|
const newEntry = `${PACKAGE_NAME}@${newVersion}`;
|
|
1741
732
|
const escapedOldEntry = oldEntry.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1742
733
|
const entryRegex = new RegExp(`(["'])${escapedOldEntry}\\1`, "g");
|
|
@@ -1750,7 +741,7 @@ function updatePinnedVersion(configPath, oldEntry, newVersion) {
|
|
|
1750
741
|
if (updatedContent === content) {
|
|
1751
742
|
return false;
|
|
1752
743
|
}
|
|
1753
|
-
|
|
744
|
+
fs2.writeFileSync(configPath, updatedContent, "utf-8");
|
|
1754
745
|
log(
|
|
1755
746
|
`[auto-update-checker] Updated ${configPath}: ${oldEntry} \u2192 ${newEntry}`
|
|
1756
747
|
);
|
|
@@ -2009,22 +1000,22 @@ async function findTmuxPath() {
|
|
|
2009
1000
|
return null;
|
|
2010
1001
|
}
|
|
2011
1002
|
const stdout = await new Response(proc.stdout).text();
|
|
2012
|
-
const
|
|
2013
|
-
if (!
|
|
1003
|
+
const path5 = stdout.trim().split("\n")[0];
|
|
1004
|
+
if (!path5) {
|
|
2014
1005
|
log("[tmux] findTmuxPath: no path in output");
|
|
2015
1006
|
return null;
|
|
2016
1007
|
}
|
|
2017
|
-
const verifyProc = spawn([
|
|
1008
|
+
const verifyProc = spawn([path5, "-V"], {
|
|
2018
1009
|
stdout: "pipe",
|
|
2019
1010
|
stderr: "pipe"
|
|
2020
1011
|
});
|
|
2021
1012
|
const verifyExit = await verifyProc.exited;
|
|
2022
1013
|
if (verifyExit !== 0) {
|
|
2023
|
-
log("[tmux] findTmuxPath: tmux -V failed", { path:
|
|
1014
|
+
log("[tmux] findTmuxPath: tmux -V failed", { path: path5, verifyExit });
|
|
2024
1015
|
return null;
|
|
2025
1016
|
}
|
|
2026
|
-
log("[tmux] findTmuxPath: found tmux", { path:
|
|
2027
|
-
return
|
|
1017
|
+
log("[tmux] findTmuxPath: found tmux", { path: path5 });
|
|
1018
|
+
return path5;
|
|
2028
1019
|
} catch (err) {
|
|
2029
1020
|
log("[tmux] findTmuxPath: exception", { error: String(err) });
|
|
2030
1021
|
return null;
|
|
@@ -2224,8 +1215,8 @@ function isPwshAvailable() {
|
|
|
2224
1215
|
});
|
|
2225
1216
|
return result.exitCode === 0;
|
|
2226
1217
|
}
|
|
2227
|
-
function escapePowerShellPath(
|
|
2228
|
-
return
|
|
1218
|
+
function escapePowerShellPath(path5) {
|
|
1219
|
+
return path5.replace(/'/g, "''");
|
|
2229
1220
|
}
|
|
2230
1221
|
function getWindowsZipExtractor() {
|
|
2231
1222
|
const buildNumber = getWindowsBuildNumber();
|
|
@@ -2807,511 +1798,6 @@ function createPostReadNudgeHook() {
|
|
|
2807
1798
|
};
|
|
2808
1799
|
}
|
|
2809
1800
|
|
|
2810
|
-
// src/cli/custom-skills.ts
|
|
2811
|
-
import {
|
|
2812
|
-
copyFileSync as copyFileSync2,
|
|
2813
|
-
existsSync as existsSync7,
|
|
2814
|
-
mkdirSync as mkdirSync3,
|
|
2815
|
-
readdirSync as readdirSync2,
|
|
2816
|
-
rmSync as rmSync2,
|
|
2817
|
-
statSync as statSync5
|
|
2818
|
-
} from "fs";
|
|
2819
|
-
import { dirname as dirname3, join as join7, parse } from "path";
|
|
2820
|
-
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
2821
|
-
|
|
2822
|
-
// src/harness/core/skills.ts
|
|
2823
|
-
var ORCHESTRATOR_ONLY = ["orchestrator"];
|
|
2824
|
-
var SHARED_SKILL_SUPPORT = {
|
|
2825
|
-
name: "_shared",
|
|
2826
|
-
description: "Shared OpenSpec, persistence, and thoth-mem SDD conventions",
|
|
2827
|
-
allowedRoles: [
|
|
2828
|
-
"orchestrator",
|
|
2829
|
-
"explorer",
|
|
2830
|
-
"librarian",
|
|
2831
|
-
"oracle",
|
|
2832
|
-
"designer",
|
|
2833
|
-
"quick",
|
|
2834
|
-
"deep"
|
|
2835
|
-
],
|
|
2836
|
-
sourcePath: "src/skills/_shared",
|
|
2837
|
-
kind: "shared-support",
|
|
2838
|
-
purpose: "support"
|
|
2839
|
-
};
|
|
2840
|
-
var BUNDLED_SKILL_REGISTRY = [
|
|
2841
|
-
{
|
|
2842
|
-
name: "requirements-interview",
|
|
2843
|
-
description: "Mandatory step-0 discovery interview to understand user intent, clarify scope, and choose the right path before implementation",
|
|
2844
|
-
allowedRoles: ORCHESTRATOR_ONLY,
|
|
2845
|
-
sourcePath: "src/skills/requirements-interview",
|
|
2846
|
-
kind: "skill",
|
|
2847
|
-
purpose: "requirements"
|
|
2848
|
-
},
|
|
2849
|
-
{
|
|
2850
|
-
name: "thoth-mem-agents",
|
|
2851
|
-
description: "Orchestrator/subagent thoth-mem workflow contract for parent session_id/project ownership, prompt-save prohibitions, and safe durable memory usage",
|
|
2852
|
-
allowedRoles: [
|
|
2853
|
-
"orchestrator",
|
|
2854
|
-
"explorer",
|
|
2855
|
-
"librarian",
|
|
2856
|
-
"oracle",
|
|
2857
|
-
"designer",
|
|
2858
|
-
"quick",
|
|
2859
|
-
"deep"
|
|
2860
|
-
],
|
|
2861
|
-
sourcePath: "src/skills/thoth-mem-agents",
|
|
2862
|
-
kind: "skill",
|
|
2863
|
-
purpose: "memory"
|
|
2864
|
-
},
|
|
2865
|
-
{
|
|
2866
|
-
name: "plan-reviewer",
|
|
2867
|
-
description: "Review SDD task plans for execution blockers and valid references",
|
|
2868
|
-
allowedRoles: ["orchestrator", "oracle"],
|
|
2869
|
-
sourcePath: "src/skills/plan-reviewer",
|
|
2870
|
-
kind: "skill",
|
|
2871
|
-
purpose: "review"
|
|
2872
|
-
},
|
|
2873
|
-
{
|
|
2874
|
-
name: "sdd-init",
|
|
2875
|
-
description: "Initialize OpenSpec structure and SDD project context",
|
|
2876
|
-
allowedRoles: ORCHESTRATOR_ONLY,
|
|
2877
|
-
sourcePath: "src/skills/sdd-init",
|
|
2878
|
-
kind: "skill",
|
|
2879
|
-
purpose: "sdd"
|
|
2880
|
-
},
|
|
2881
|
-
{
|
|
2882
|
-
name: "sdd-propose",
|
|
2883
|
-
description: "Create change proposals for OpenSpec workflows",
|
|
2884
|
-
allowedRoles: ORCHESTRATOR_ONLY,
|
|
2885
|
-
sourcePath: "src/skills/sdd-propose",
|
|
2886
|
-
kind: "skill",
|
|
2887
|
-
purpose: "sdd"
|
|
2888
|
-
},
|
|
2889
|
-
{
|
|
2890
|
-
name: "sdd-spec",
|
|
2891
|
-
description: "Write OpenSpec delta specifications",
|
|
2892
|
-
allowedRoles: ORCHESTRATOR_ONLY,
|
|
2893
|
-
sourcePath: "src/skills/sdd-spec",
|
|
2894
|
-
kind: "skill",
|
|
2895
|
-
purpose: "sdd"
|
|
2896
|
-
},
|
|
2897
|
-
{
|
|
2898
|
-
name: "sdd-design",
|
|
2899
|
-
description: "Create technical design artifacts for changes",
|
|
2900
|
-
allowedRoles: ORCHESTRATOR_ONLY,
|
|
2901
|
-
sourcePath: "src/skills/sdd-design",
|
|
2902
|
-
kind: "skill",
|
|
2903
|
-
purpose: "sdd"
|
|
2904
|
-
},
|
|
2905
|
-
{
|
|
2906
|
-
name: "sdd-tasks",
|
|
2907
|
-
description: "Generate phased implementation task checklists",
|
|
2908
|
-
allowedRoles: ORCHESTRATOR_ONLY,
|
|
2909
|
-
sourcePath: "src/skills/sdd-tasks",
|
|
2910
|
-
kind: "skill",
|
|
2911
|
-
purpose: "sdd"
|
|
2912
|
-
},
|
|
2913
|
-
{
|
|
2914
|
-
name: "sdd-apply",
|
|
2915
|
-
description: "Execute tasks and persist implementation progress",
|
|
2916
|
-
allowedRoles: ORCHESTRATOR_ONLY,
|
|
2917
|
-
sourcePath: "src/skills/sdd-apply",
|
|
2918
|
-
kind: "skill",
|
|
2919
|
-
purpose: "sdd"
|
|
2920
|
-
},
|
|
2921
|
-
{
|
|
2922
|
-
name: "executing-plans",
|
|
2923
|
-
description: "Execute SDD task lists with real-time progress tracking, sub-agent dispatch, and verification checkpoints",
|
|
2924
|
-
allowedRoles: ORCHESTRATOR_ONLY,
|
|
2925
|
-
sourcePath: "src/skills/executing-plans",
|
|
2926
|
-
kind: "skill",
|
|
2927
|
-
purpose: "sdd"
|
|
2928
|
-
},
|
|
2929
|
-
{
|
|
2930
|
-
name: "sdd-verify",
|
|
2931
|
-
description: "Build verification reports and compliance matrices",
|
|
2932
|
-
allowedRoles: ORCHESTRATOR_ONLY,
|
|
2933
|
-
sourcePath: "src/skills/sdd-verify",
|
|
2934
|
-
kind: "skill",
|
|
2935
|
-
purpose: "sdd"
|
|
2936
|
-
},
|
|
2937
|
-
{
|
|
2938
|
-
name: "sdd-archive",
|
|
2939
|
-
description: "Archive completed OpenSpec changes with audit trails",
|
|
2940
|
-
allowedRoles: ORCHESTRATOR_ONLY,
|
|
2941
|
-
sourcePath: "src/skills/sdd-archive",
|
|
2942
|
-
kind: "skill",
|
|
2943
|
-
purpose: "sdd"
|
|
2944
|
-
}
|
|
2945
|
-
];
|
|
2946
|
-
var SKILL_REGISTRY = [
|
|
2947
|
-
...BUNDLED_SKILL_REGISTRY,
|
|
2948
|
-
SHARED_SKILL_SUPPORT
|
|
2949
|
-
];
|
|
2950
|
-
|
|
2951
|
-
// src/cli/skill-manifest.ts
|
|
2952
|
-
import { createHash } from "crypto";
|
|
2953
|
-
import {
|
|
2954
|
-
existsSync as existsSync6,
|
|
2955
|
-
mkdirSync as mkdirSync2,
|
|
2956
|
-
readdirSync,
|
|
2957
|
-
readFileSync as readFileSync5,
|
|
2958
|
-
statSync as statSync4,
|
|
2959
|
-
writeFileSync as writeFileSync4
|
|
2960
|
-
} from "fs";
|
|
2961
|
-
import { join as join6, relative } from "path";
|
|
2962
|
-
var SHARED_SKILL_DIRECTORY = "_shared";
|
|
2963
|
-
var SKILLS_SOURCE_ROOT = join6("src", "skills");
|
|
2964
|
-
var MANIFEST_FILE_NAME = ".skill-manifest.json";
|
|
2965
|
-
function getManifestPath() {
|
|
2966
|
-
return join6(getCustomSkillsDir(), MANIFEST_FILE_NAME);
|
|
2967
|
-
}
|
|
2968
|
-
function listFilesRecursive(dirPath) {
|
|
2969
|
-
if (!existsSync6(dirPath)) {
|
|
2970
|
-
return [];
|
|
2971
|
-
}
|
|
2972
|
-
const files = [];
|
|
2973
|
-
const entries = readdirSync(dirPath).sort(
|
|
2974
|
-
(left, right) => left.localeCompare(right)
|
|
2975
|
-
);
|
|
2976
|
-
for (const entry of entries) {
|
|
2977
|
-
const entryPath = join6(dirPath, entry);
|
|
2978
|
-
const stat = statSync4(entryPath);
|
|
2979
|
-
if (stat.isDirectory()) {
|
|
2980
|
-
files.push(...listFilesRecursive(entryPath));
|
|
2981
|
-
continue;
|
|
2982
|
-
}
|
|
2983
|
-
files.push(entryPath);
|
|
2984
|
-
}
|
|
2985
|
-
return files;
|
|
2986
|
-
}
|
|
2987
|
-
function readPackageVersion(packageRoot) {
|
|
2988
|
-
const packageJsonPath = join6(packageRoot, "package.json");
|
|
2989
|
-
const packageJson = JSON.parse(readFileSync5(packageJsonPath, "utf-8"));
|
|
2990
|
-
if (typeof packageJson.version !== "string" || packageJson.version.length === 0) {
|
|
2991
|
-
throw new Error(`Invalid package version in ${packageJsonPath}`);
|
|
2992
|
-
}
|
|
2993
|
-
return packageJson.version;
|
|
2994
|
-
}
|
|
2995
|
-
function readManifest() {
|
|
2996
|
-
const manifestPath = getManifestPath();
|
|
2997
|
-
if (!existsSync6(manifestPath)) {
|
|
2998
|
-
return null;
|
|
2999
|
-
}
|
|
3000
|
-
try {
|
|
3001
|
-
return JSON.parse(readFileSync5(manifestPath, "utf-8"));
|
|
3002
|
-
} catch (error) {
|
|
3003
|
-
console.warn(`Failed to read skill manifest: ${manifestPath}`, error);
|
|
3004
|
-
return null;
|
|
3005
|
-
}
|
|
3006
|
-
}
|
|
3007
|
-
function writeManifest(manifest) {
|
|
3008
|
-
const manifestPath = getManifestPath();
|
|
3009
|
-
mkdirSync2(getCustomSkillsDir(), { recursive: true });
|
|
3010
|
-
writeFileSync4(manifestPath, `${JSON.stringify(manifest, null, 2)}
|
|
3011
|
-
`);
|
|
3012
|
-
}
|
|
3013
|
-
function computeSkillHash(skillDirPath) {
|
|
3014
|
-
const hash = createHash("sha256");
|
|
3015
|
-
for (const filePath of listFilesRecursive(skillDirPath)) {
|
|
3016
|
-
hash.update(`${relative(skillDirPath, filePath)}
|
|
3017
|
-
`);
|
|
3018
|
-
hash.update(readFileSync5(filePath));
|
|
3019
|
-
hash.update("\n");
|
|
3020
|
-
}
|
|
3021
|
-
return hash.digest("hex");
|
|
3022
|
-
}
|
|
3023
|
-
function findRemovedSkills(manifest) {
|
|
3024
|
-
const currentSkillNames = new Set(CUSTOM_SKILLS.map((skill) => skill.name));
|
|
3025
|
-
return Object.keys(manifest.skills).filter(
|
|
3026
|
-
(skillName) => !currentSkillNames.has(skillName)
|
|
3027
|
-
);
|
|
3028
|
-
}
|
|
3029
|
-
function checkSkillsNeedUpdate(packageRoot) {
|
|
3030
|
-
const manifest = readManifest();
|
|
3031
|
-
const pluginVersion = readPackageVersion(packageRoot);
|
|
3032
|
-
const sharedHash = computeSkillHash(
|
|
3033
|
-
join6(packageRoot, SKILLS_SOURCE_ROOT, SHARED_SKILL_DIRECTORY)
|
|
3034
|
-
);
|
|
3035
|
-
const versionChanged = manifest !== null && manifest.pluginVersion !== pluginVersion;
|
|
3036
|
-
const sharedChanged = manifest !== null && manifest.sharedHash !== sharedHash;
|
|
3037
|
-
const removedSkills = manifest ? findRemovedSkills(manifest) : [];
|
|
3038
|
-
const skillsNeedingUpdate = CUSTOM_SKILLS.flatMap((skill) => {
|
|
3039
|
-
const sourcePath = join6(packageRoot, skill.sourcePath);
|
|
3040
|
-
const targetPath = join6(getCustomSkillsDir(), skill.name);
|
|
3041
|
-
const sourceHash = computeSkillHash(sourcePath);
|
|
3042
|
-
const manifestEntry = manifest?.skills[skill.name];
|
|
3043
|
-
const reasons = [];
|
|
3044
|
-
if (!manifest) {
|
|
3045
|
-
reasons.push("manifest-missing");
|
|
3046
|
-
}
|
|
3047
|
-
if (versionChanged) {
|
|
3048
|
-
reasons.push("version-change");
|
|
3049
|
-
}
|
|
3050
|
-
if (sharedChanged) {
|
|
3051
|
-
reasons.push("shared-hash-mismatch");
|
|
3052
|
-
}
|
|
3053
|
-
if (!manifestEntry) {
|
|
3054
|
-
reasons.push("new-skill");
|
|
3055
|
-
} else if (manifestEntry.hash !== sourceHash) {
|
|
3056
|
-
reasons.push("hash-mismatch");
|
|
3057
|
-
}
|
|
3058
|
-
if (!existsSync6(targetPath)) {
|
|
3059
|
-
reasons.push("missing-install");
|
|
3060
|
-
}
|
|
3061
|
-
if (reasons.length === 0) {
|
|
3062
|
-
return [];
|
|
3063
|
-
}
|
|
3064
|
-
return [
|
|
3065
|
-
{
|
|
3066
|
-
skill,
|
|
3067
|
-
sourceHash,
|
|
3068
|
-
targetPath,
|
|
3069
|
-
reasons
|
|
3070
|
-
}
|
|
3071
|
-
];
|
|
3072
|
-
});
|
|
3073
|
-
return {
|
|
3074
|
-
pluginVersion,
|
|
3075
|
-
sharedHash,
|
|
3076
|
-
manifest,
|
|
3077
|
-
versionChanged,
|
|
3078
|
-
sharedChanged,
|
|
3079
|
-
needsUpdate: skillsNeedingUpdate.length > 0 || removedSkills.length > 0,
|
|
3080
|
-
skillsNeedingUpdate,
|
|
3081
|
-
removedSkills
|
|
3082
|
-
};
|
|
3083
|
-
}
|
|
3084
|
-
|
|
3085
|
-
// src/cli/custom-skills.ts
|
|
3086
|
-
var SHARED_SKILL_DIRECTORY2 = "_shared";
|
|
3087
|
-
var SHARED_SKILL_SOURCE_PATH = `src/skills/${SHARED_SKILL_DIRECTORY2}`;
|
|
3088
|
-
var CUSTOM_SKILLS = BUNDLED_SKILL_REGISTRY.map(
|
|
3089
|
-
(skill) => ({
|
|
3090
|
-
name: skill.name,
|
|
3091
|
-
description: skill.description,
|
|
3092
|
-
allowedAgents: [...skill.allowedRoles],
|
|
3093
|
-
sourcePath: skill.sourcePath
|
|
3094
|
-
})
|
|
3095
|
-
);
|
|
3096
|
-
function getCustomSkillsDir() {
|
|
3097
|
-
return join7(getConfigDir(), "skills");
|
|
3098
|
-
}
|
|
3099
|
-
function copyDirRecursive(src, dest) {
|
|
3100
|
-
if (!existsSync7(dest)) {
|
|
3101
|
-
mkdirSync3(dest, { recursive: true });
|
|
3102
|
-
}
|
|
3103
|
-
const entries = readdirSync2(src);
|
|
3104
|
-
for (const entry of entries) {
|
|
3105
|
-
const srcPath = join7(src, entry);
|
|
3106
|
-
const destPath = join7(dest, entry);
|
|
3107
|
-
const stat = statSync5(srcPath);
|
|
3108
|
-
if (stat.isDirectory()) {
|
|
3109
|
-
copyDirRecursive(srcPath, destPath);
|
|
3110
|
-
} else {
|
|
3111
|
-
const destDir = dirname3(destPath);
|
|
3112
|
-
if (!existsSync7(destDir)) {
|
|
3113
|
-
mkdirSync3(destDir, { recursive: true });
|
|
3114
|
-
}
|
|
3115
|
-
copyFileSync2(srcPath, destPath);
|
|
3116
|
-
}
|
|
3117
|
-
}
|
|
3118
|
-
}
|
|
3119
|
-
function installSharedSkillAssets(packageRoot) {
|
|
3120
|
-
const sharedSourcePath = join7(packageRoot, SHARED_SKILL_SOURCE_PATH);
|
|
3121
|
-
const sharedTargetPath = join7(getCustomSkillsDir(), SHARED_SKILL_DIRECTORY2);
|
|
3122
|
-
if (!existsSync7(sharedSourcePath)) {
|
|
3123
|
-
console.error(`Custom skill shared assets not found: ${sharedSourcePath}`);
|
|
3124
|
-
return false;
|
|
3125
|
-
}
|
|
3126
|
-
rmSync2(sharedTargetPath, { recursive: true, force: true });
|
|
3127
|
-
copyDirRecursive(sharedSourcePath, sharedTargetPath);
|
|
3128
|
-
return true;
|
|
3129
|
-
}
|
|
3130
|
-
function findPackageRoot(startDir) {
|
|
3131
|
-
let currentDir = startDir;
|
|
3132
|
-
const filesystemRoot = parse(startDir).root;
|
|
3133
|
-
while (true) {
|
|
3134
|
-
if (existsSync7(join7(currentDir, "package.json")) && existsSync7(join7(currentDir, "src", "skills"))) {
|
|
3135
|
-
return currentDir;
|
|
3136
|
-
}
|
|
3137
|
-
if (currentDir === filesystemRoot) {
|
|
3138
|
-
return null;
|
|
3139
|
-
}
|
|
3140
|
-
const parentDir = dirname3(currentDir);
|
|
3141
|
-
if (parentDir === currentDir) {
|
|
3142
|
-
return null;
|
|
3143
|
-
}
|
|
3144
|
-
currentDir = parentDir;
|
|
3145
|
-
}
|
|
3146
|
-
}
|
|
3147
|
-
function resolvePackageRoot(packageRoot) {
|
|
3148
|
-
if (packageRoot) {
|
|
3149
|
-
return packageRoot;
|
|
3150
|
-
}
|
|
3151
|
-
const moduleDir = fileURLToPath2(new URL(".", import.meta.url));
|
|
3152
|
-
return findPackageRoot(moduleDir) ?? fileURLToPath2(new URL("../..", import.meta.url));
|
|
3153
|
-
}
|
|
3154
|
-
function installCustomSkillFiles(skill, packageRoot) {
|
|
3155
|
-
const sourcePath = join7(packageRoot, skill.sourcePath);
|
|
3156
|
-
const targetPath = join7(getCustomSkillsDir(), skill.name);
|
|
3157
|
-
if (!existsSync7(sourcePath)) {
|
|
3158
|
-
console.error(`Custom skill source not found: ${sourcePath}`);
|
|
3159
|
-
return false;
|
|
3160
|
-
}
|
|
3161
|
-
rmSync2(targetPath, { recursive: true, force: true });
|
|
3162
|
-
copyDirRecursive(sourcePath, targetPath);
|
|
3163
|
-
return true;
|
|
3164
|
-
}
|
|
3165
|
-
function removeObsoleteSkills(removedSkillNames) {
|
|
3166
|
-
const removedSkills = [];
|
|
3167
|
-
for (const skillName of removedSkillNames) {
|
|
3168
|
-
const targetPath = join7(getCustomSkillsDir(), skillName);
|
|
3169
|
-
try {
|
|
3170
|
-
console.log(`Removing obsolete bundled skill: ${skillName}`);
|
|
3171
|
-
rmSync2(targetPath, { recursive: true, force: true });
|
|
3172
|
-
removedSkills.push(skillName);
|
|
3173
|
-
} catch (error) {
|
|
3174
|
-
console.warn(`Failed to remove obsolete bundled skill: ${skillName}`);
|
|
3175
|
-
console.warn(error);
|
|
3176
|
-
}
|
|
3177
|
-
}
|
|
3178
|
-
return removedSkills;
|
|
3179
|
-
}
|
|
3180
|
-
function buildManifest(packageRoot, updatedSkills, previousManifest, pluginVersion, sharedHash) {
|
|
3181
|
-
const installedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
3182
|
-
const updatedSkillNames = new Set(
|
|
3183
|
-
updatedSkills.map(({ skill }) => skill.name)
|
|
3184
|
-
);
|
|
3185
|
-
const skills = Object.fromEntries(
|
|
3186
|
-
CUSTOM_SKILLS.map((skill) => {
|
|
3187
|
-
const previousEntry = previousManifest?.skills[skill.name];
|
|
3188
|
-
const nextInstalledAt = updatedSkillNames.has(skill.name) ? installedAt : previousEntry?.installedAt ?? installedAt;
|
|
3189
|
-
return [
|
|
3190
|
-
skill.name,
|
|
3191
|
-
{
|
|
3192
|
-
hash: computeSkillHash(join7(packageRoot, skill.sourcePath)),
|
|
3193
|
-
installedAt: nextInstalledAt
|
|
3194
|
-
}
|
|
3195
|
-
];
|
|
3196
|
-
})
|
|
3197
|
-
);
|
|
3198
|
-
return {
|
|
3199
|
-
pluginVersion,
|
|
3200
|
-
sharedHash,
|
|
3201
|
-
skills
|
|
3202
|
-
};
|
|
3203
|
-
}
|
|
3204
|
-
function pruneRemovedSkillsFromManifest(manifest, removedSkills) {
|
|
3205
|
-
const removedSkillNames = new Set(removedSkills);
|
|
3206
|
-
return {
|
|
3207
|
-
...manifest,
|
|
3208
|
-
skills: Object.fromEntries(
|
|
3209
|
-
Object.entries(manifest.skills).filter(
|
|
3210
|
-
([skillName]) => !removedSkillNames.has(skillName)
|
|
3211
|
-
)
|
|
3212
|
-
)
|
|
3213
|
-
};
|
|
3214
|
-
}
|
|
3215
|
-
function installCustomSkills(packageRoot = resolvePackageRoot()) {
|
|
3216
|
-
const updateCheck = checkSkillsNeedUpdate(packageRoot);
|
|
3217
|
-
if (!updateCheck.needsUpdate) {
|
|
3218
|
-
return {
|
|
3219
|
-
success: true,
|
|
3220
|
-
updatedSkills: [],
|
|
3221
|
-
skippedSkills: [...CUSTOM_SKILLS],
|
|
3222
|
-
failedSkills: [],
|
|
3223
|
-
removedSkills: []
|
|
3224
|
-
};
|
|
3225
|
-
}
|
|
3226
|
-
const removedSkills = removeObsoleteSkills(updateCheck.removedSkills);
|
|
3227
|
-
if (removedSkills.length > 0 && updateCheck.manifest) {
|
|
3228
|
-
writeManifest(
|
|
3229
|
-
pruneRemovedSkillsFromManifest(updateCheck.manifest, removedSkills)
|
|
3230
|
-
);
|
|
3231
|
-
}
|
|
3232
|
-
if (updateCheck.skillsNeedingUpdate.length === 0) {
|
|
3233
|
-
writeManifest(
|
|
3234
|
-
buildManifest(
|
|
3235
|
-
packageRoot,
|
|
3236
|
-
[],
|
|
3237
|
-
updateCheck.manifest,
|
|
3238
|
-
updateCheck.pluginVersion,
|
|
3239
|
-
updateCheck.sharedHash
|
|
3240
|
-
)
|
|
3241
|
-
);
|
|
3242
|
-
return {
|
|
3243
|
-
success: true,
|
|
3244
|
-
updatedSkills: [],
|
|
3245
|
-
skippedSkills: [...CUSTOM_SKILLS],
|
|
3246
|
-
failedSkills: [],
|
|
3247
|
-
removedSkills
|
|
3248
|
-
};
|
|
3249
|
-
}
|
|
3250
|
-
if (!installSharedSkillAssets(packageRoot)) {
|
|
3251
|
-
return {
|
|
3252
|
-
success: false,
|
|
3253
|
-
updatedSkills: [],
|
|
3254
|
-
skippedSkills: [],
|
|
3255
|
-
failedSkills: updateCheck.skillsNeedingUpdate.map(
|
|
3256
|
-
({ skill, reasons }) => ({
|
|
3257
|
-
skill,
|
|
3258
|
-
reasons
|
|
3259
|
-
})
|
|
3260
|
-
),
|
|
3261
|
-
removedSkills
|
|
3262
|
-
};
|
|
3263
|
-
}
|
|
3264
|
-
const updatesBySkillName = new Map(
|
|
3265
|
-
updateCheck.skillsNeedingUpdate.map((entry) => [entry.skill.name, entry])
|
|
3266
|
-
);
|
|
3267
|
-
const updatedSkills = [];
|
|
3268
|
-
const skippedSkills = [];
|
|
3269
|
-
const failedSkills = [];
|
|
3270
|
-
for (const skill of CUSTOM_SKILLS) {
|
|
3271
|
-
const pendingUpdate = updatesBySkillName.get(skill.name);
|
|
3272
|
-
if (!pendingUpdate) {
|
|
3273
|
-
skippedSkills.push(skill);
|
|
3274
|
-
continue;
|
|
3275
|
-
}
|
|
3276
|
-
if (installCustomSkillFiles(skill, packageRoot)) {
|
|
3277
|
-
updatedSkills.push({
|
|
3278
|
-
skill,
|
|
3279
|
-
reasons: pendingUpdate.reasons
|
|
3280
|
-
});
|
|
3281
|
-
continue;
|
|
3282
|
-
}
|
|
3283
|
-
failedSkills.push({
|
|
3284
|
-
skill,
|
|
3285
|
-
reasons: pendingUpdate.reasons
|
|
3286
|
-
});
|
|
3287
|
-
}
|
|
3288
|
-
if (failedSkills.length > 0) {
|
|
3289
|
-
return {
|
|
3290
|
-
success: false,
|
|
3291
|
-
updatedSkills,
|
|
3292
|
-
skippedSkills,
|
|
3293
|
-
failedSkills,
|
|
3294
|
-
removedSkills
|
|
3295
|
-
};
|
|
3296
|
-
}
|
|
3297
|
-
writeManifest(
|
|
3298
|
-
buildManifest(
|
|
3299
|
-
packageRoot,
|
|
3300
|
-
updatedSkills,
|
|
3301
|
-
updateCheck.manifest,
|
|
3302
|
-
updateCheck.pluginVersion,
|
|
3303
|
-
updateCheck.sharedHash
|
|
3304
|
-
)
|
|
3305
|
-
);
|
|
3306
|
-
return {
|
|
3307
|
-
success: true,
|
|
3308
|
-
updatedSkills,
|
|
3309
|
-
skippedSkills,
|
|
3310
|
-
failedSkills,
|
|
3311
|
-
removedSkills
|
|
3312
|
-
};
|
|
3313
|
-
}
|
|
3314
|
-
|
|
3315
1801
|
// src/hooks/skill-sync.ts
|
|
3316
1802
|
function formatSkillSyncSummary(report) {
|
|
3317
1803
|
if (report.updatedSkills.length === 0 && report.removedSkills.length === 0 && report.failedSkills.length === 0) {
|
|
@@ -3477,12 +1963,12 @@ var HttpThothClient = class {
|
|
|
3477
1963
|
});
|
|
3478
1964
|
return response !== null;
|
|
3479
1965
|
}
|
|
3480
|
-
async httpPost(
|
|
1966
|
+
async httpPost(path5, body) {
|
|
3481
1967
|
if (!this.enabled) {
|
|
3482
1968
|
return null;
|
|
3483
1969
|
}
|
|
3484
1970
|
try {
|
|
3485
|
-
const response = await fetch(`${this.baseUrl}${
|
|
1971
|
+
const response = await fetch(`${this.baseUrl}${path5}`, {
|
|
3486
1972
|
method: "POST",
|
|
3487
1973
|
headers: { "Content-Type": "application/json" },
|
|
3488
1974
|
body: JSON.stringify(body),
|
|
@@ -3494,18 +1980,18 @@ var HttpThothClient = class {
|
|
|
3494
1980
|
return await response.json();
|
|
3495
1981
|
} catch (error) {
|
|
3496
1982
|
log("[thoth] HTTP POST unavailable", {
|
|
3497
|
-
path:
|
|
1983
|
+
path: path5,
|
|
3498
1984
|
error: error instanceof Error ? error.message : String(error)
|
|
3499
1985
|
});
|
|
3500
1986
|
return null;
|
|
3501
1987
|
}
|
|
3502
1988
|
}
|
|
3503
|
-
async httpGet(
|
|
1989
|
+
async httpGet(path5) {
|
|
3504
1990
|
if (!this.enabled) {
|
|
3505
1991
|
return null;
|
|
3506
1992
|
}
|
|
3507
1993
|
try {
|
|
3508
|
-
const response = await fetch(`${this.baseUrl}${
|
|
1994
|
+
const response = await fetch(`${this.baseUrl}${path5}`, {
|
|
3509
1995
|
signal: AbortSignal.timeout(this.timeoutMs)
|
|
3510
1996
|
});
|
|
3511
1997
|
if (!response.ok) {
|
|
@@ -3514,7 +2000,7 @@ var HttpThothClient = class {
|
|
|
3514
2000
|
return await response.json();
|
|
3515
2001
|
} catch (error) {
|
|
3516
2002
|
log("[thoth] HTTP GET unavailable", {
|
|
3517
|
-
path:
|
|
2003
|
+
path: path5,
|
|
3518
2004
|
error: error instanceof Error ? error.message : String(error)
|
|
3519
2005
|
});
|
|
3520
2006
|
return null;
|
|
@@ -3863,29 +2349,6 @@ function createThothMemHook(options) {
|
|
|
3863
2349
|
};
|
|
3864
2350
|
}
|
|
3865
2351
|
|
|
3866
|
-
// src/mcp/context7.ts
|
|
3867
|
-
var CONTEXT7_MCP_URL = "https://mcp.context7.com/mcp";
|
|
3868
|
-
var context7 = {
|
|
3869
|
-
type: "remote",
|
|
3870
|
-
url: CONTEXT7_MCP_URL,
|
|
3871
|
-
headers: process.env.CONTEXT7_API_KEY ? { CONTEXT7_API_KEY: process.env.CONTEXT7_API_KEY } : void 0,
|
|
3872
|
-
oauth: false
|
|
3873
|
-
};
|
|
3874
|
-
|
|
3875
|
-
// src/mcp/exa.ts
|
|
3876
|
-
var exa = {
|
|
3877
|
-
type: "local",
|
|
3878
|
-
command: ["npx", "-y", "exa-mcp-server"]
|
|
3879
|
-
};
|
|
3880
|
-
|
|
3881
|
-
// src/mcp/grep-app.ts
|
|
3882
|
-
var GREP_APP_MCP_URL = "https://mcp.grep.app";
|
|
3883
|
-
var grep_app = {
|
|
3884
|
-
type: "remote",
|
|
3885
|
-
url: GREP_APP_MCP_URL,
|
|
3886
|
-
oauth: false
|
|
3887
|
-
};
|
|
3888
|
-
|
|
3889
2352
|
// src/mcp/thoth.ts
|
|
3890
2353
|
function createThothMcp(config) {
|
|
3891
2354
|
const environment = {
|
|
@@ -3924,19 +2387,19 @@ function createBuiltinMcps(disabledMcps = [], thothConfig) {
|
|
|
3924
2387
|
import { tool } from "@opencode-ai/plugin/tool";
|
|
3925
2388
|
|
|
3926
2389
|
// src/tools/ast-grep/cli.ts
|
|
3927
|
-
import { existsSync as
|
|
2390
|
+
import { existsSync as existsSync5 } from "fs";
|
|
3928
2391
|
|
|
3929
2392
|
// src/tools/ast-grep/constants.ts
|
|
3930
2393
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
3931
|
-
import { existsSync as
|
|
2394
|
+
import { existsSync as existsSync4, statSync as statSync2 } from "fs";
|
|
3932
2395
|
import { createRequire as createRequire3 } from "module";
|
|
3933
|
-
import { dirname as
|
|
2396
|
+
import { dirname as dirname2, join as join5 } from "path";
|
|
3934
2397
|
|
|
3935
2398
|
// src/tools/ast-grep/downloader.ts
|
|
3936
|
-
import { chmodSync, existsSync as
|
|
2399
|
+
import { chmodSync, existsSync as existsSync3, mkdirSync, unlinkSync } from "fs";
|
|
3937
2400
|
import { createRequire as createRequire2 } from "module";
|
|
3938
|
-
import { homedir as
|
|
3939
|
-
import { join as
|
|
2401
|
+
import { homedir as homedir2 } from "os";
|
|
2402
|
+
import { join as join4 } from "path";
|
|
3940
2403
|
|
|
3941
2404
|
// src/utils/file-io.ts
|
|
3942
2405
|
import { readFile, writeFile } from "fs/promises";
|
|
@@ -3971,19 +2434,19 @@ var PLATFORM_MAP = {
|
|
|
3971
2434
|
function getCacheDir2() {
|
|
3972
2435
|
if (process.platform === "win32") {
|
|
3973
2436
|
const localAppData = process.env.LOCALAPPDATA || process.env.APPDATA;
|
|
3974
|
-
const base2 = localAppData ||
|
|
3975
|
-
return
|
|
2437
|
+
const base2 = localAppData || join4(homedir2(), "AppData", "Local");
|
|
2438
|
+
return join4(base2, "thoth-agents", "bin");
|
|
3976
2439
|
}
|
|
3977
2440
|
const xdgCache = process.env.XDG_CACHE_HOME;
|
|
3978
|
-
const base = xdgCache ||
|
|
3979
|
-
return
|
|
2441
|
+
const base = xdgCache || join4(homedir2(), ".cache");
|
|
2442
|
+
return join4(base, "thoth-agents", "bin");
|
|
3980
2443
|
}
|
|
3981
2444
|
function getBinaryName() {
|
|
3982
2445
|
return process.platform === "win32" ? "sg.exe" : "sg";
|
|
3983
2446
|
}
|
|
3984
2447
|
function getCachedBinaryPath() {
|
|
3985
|
-
const binaryPath =
|
|
3986
|
-
return
|
|
2448
|
+
const binaryPath = join4(getCacheDir2(), getBinaryName());
|
|
2449
|
+
return existsSync3(binaryPath) ? binaryPath : null;
|
|
3987
2450
|
}
|
|
3988
2451
|
async function downloadAstGrep(version = DEFAULT_VERSION) {
|
|
3989
2452
|
const platformKey = `${process.platform}-${process.arch}`;
|
|
@@ -3996,8 +2459,8 @@ async function downloadAstGrep(version = DEFAULT_VERSION) {
|
|
|
3996
2459
|
}
|
|
3997
2460
|
const cacheDir = getCacheDir2();
|
|
3998
2461
|
const binaryName = getBinaryName();
|
|
3999
|
-
const binaryPath =
|
|
4000
|
-
if (
|
|
2462
|
+
const binaryPath = join4(cacheDir, binaryName);
|
|
2463
|
+
if (existsSync3(binaryPath)) {
|
|
4001
2464
|
return binaryPath;
|
|
4002
2465
|
}
|
|
4003
2466
|
const { arch, os: os2 } = platformInfo;
|
|
@@ -4005,21 +2468,21 @@ async function downloadAstGrep(version = DEFAULT_VERSION) {
|
|
|
4005
2468
|
const downloadUrl = `https://github.com/${REPO}/releases/download/${version}/${assetName}`;
|
|
4006
2469
|
console.log(`[thoth-agents] Downloading ast-grep binary...`);
|
|
4007
2470
|
try {
|
|
4008
|
-
if (!
|
|
4009
|
-
|
|
2471
|
+
if (!existsSync3(cacheDir)) {
|
|
2472
|
+
mkdirSync(cacheDir, { recursive: true });
|
|
4010
2473
|
}
|
|
4011
2474
|
const response = await fetch(downloadUrl, { redirect: "follow" });
|
|
4012
2475
|
if (!response.ok) {
|
|
4013
2476
|
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
4014
2477
|
}
|
|
4015
|
-
const archivePath =
|
|
2478
|
+
const archivePath = join4(cacheDir, assetName);
|
|
4016
2479
|
const arrayBuffer = await response.arrayBuffer();
|
|
4017
2480
|
await writeTextFile(archivePath, arrayBuffer);
|
|
4018
2481
|
await extractZip(archivePath, cacheDir);
|
|
4019
|
-
if (
|
|
2482
|
+
if (existsSync3(archivePath)) {
|
|
4020
2483
|
unlinkSync(archivePath);
|
|
4021
2484
|
}
|
|
4022
|
-
if (process.platform !== "win32" &&
|
|
2485
|
+
if (process.platform !== "win32" && existsSync3(binaryPath)) {
|
|
4023
2486
|
chmodSync(binaryPath, 493);
|
|
4024
2487
|
}
|
|
4025
2488
|
console.log(`[thoth-agents] ast-grep binary ready.`);
|
|
@@ -4073,7 +2536,7 @@ var CLI_LANGUAGES = [
|
|
|
4073
2536
|
var MIN_BINARY_SIZE = 1e4;
|
|
4074
2537
|
function isValidBinary(filePath) {
|
|
4075
2538
|
try {
|
|
4076
|
-
return
|
|
2539
|
+
return statSync2(filePath).size > MIN_BINARY_SIZE;
|
|
4077
2540
|
} catch {
|
|
4078
2541
|
return false;
|
|
4079
2542
|
}
|
|
@@ -4102,9 +2565,9 @@ function findSgCliPathSync() {
|
|
|
4102
2565
|
try {
|
|
4103
2566
|
const require3 = createRequire3(import.meta.url);
|
|
4104
2567
|
const cliPkgPath = require3.resolve("@ast-grep/cli/package.json");
|
|
4105
|
-
const cliDir =
|
|
4106
|
-
const sgPath =
|
|
4107
|
-
if (
|
|
2568
|
+
const cliDir = dirname2(cliPkgPath);
|
|
2569
|
+
const sgPath = join5(cliDir, binaryName);
|
|
2570
|
+
if (existsSync4(sgPath) && isValidBinary(sgPath)) {
|
|
4108
2571
|
return sgPath;
|
|
4109
2572
|
}
|
|
4110
2573
|
} catch {
|
|
@@ -4114,10 +2577,10 @@ function findSgCliPathSync() {
|
|
|
4114
2577
|
try {
|
|
4115
2578
|
const require3 = createRequire3(import.meta.url);
|
|
4116
2579
|
const pkgPath = require3.resolve(`${platformPkg}/package.json`);
|
|
4117
|
-
const pkgDir =
|
|
2580
|
+
const pkgDir = dirname2(pkgPath);
|
|
4118
2581
|
const astGrepName = process.platform === "win32" ? "ast-grep.exe" : "ast-grep";
|
|
4119
|
-
const binaryPath =
|
|
4120
|
-
if (
|
|
2582
|
+
const binaryPath = join5(pkgDir, astGrepName);
|
|
2583
|
+
if (existsSync4(binaryPath) && isValidBinary(binaryPath)) {
|
|
4121
2584
|
return binaryPath;
|
|
4122
2585
|
}
|
|
4123
2586
|
} catch {
|
|
@@ -4125,9 +2588,9 @@ function findSgCliPathSync() {
|
|
|
4125
2588
|
}
|
|
4126
2589
|
if (process.platform === "darwin") {
|
|
4127
2590
|
const homebrewPaths = ["/opt/homebrew/bin/sg", "/usr/local/bin/sg"];
|
|
4128
|
-
for (const
|
|
4129
|
-
if (
|
|
4130
|
-
return
|
|
2591
|
+
for (const path5 of homebrewPaths) {
|
|
2592
|
+
if (existsSync4(path5) && isValidBinary(path5)) {
|
|
2593
|
+
return path5;
|
|
4131
2594
|
}
|
|
4132
2595
|
}
|
|
4133
2596
|
}
|
|
@@ -4144,10 +2607,10 @@ function getSgCliPath() {
|
|
|
4144
2607
|
}
|
|
4145
2608
|
return "sg";
|
|
4146
2609
|
}
|
|
4147
|
-
function setSgCliPath(
|
|
4148
|
-
resolvedCliPath =
|
|
2610
|
+
function setSgCliPath(path5) {
|
|
2611
|
+
resolvedCliPath = path5;
|
|
4149
2612
|
}
|
|
4150
|
-
var
|
|
2613
|
+
var DEFAULT_TIMEOUT_MS = 3e5;
|
|
4151
2614
|
var DEFAULT_MAX_OUTPUT_BYTES = 1 * 1024 * 1024;
|
|
4152
2615
|
var DEFAULT_MAX_MATCHES = 500;
|
|
4153
2616
|
|
|
@@ -4155,7 +2618,7 @@ var DEFAULT_MAX_MATCHES = 500;
|
|
|
4155
2618
|
var initPromise = null;
|
|
4156
2619
|
async function getAstGrepPath() {
|
|
4157
2620
|
const currentPath = getSgCliPath();
|
|
4158
|
-
if (currentPath !== "sg" &&
|
|
2621
|
+
if (currentPath !== "sg" && existsSync5(currentPath)) {
|
|
4159
2622
|
return currentPath;
|
|
4160
2623
|
}
|
|
4161
2624
|
if (initPromise) {
|
|
@@ -4163,7 +2626,7 @@ async function getAstGrepPath() {
|
|
|
4163
2626
|
}
|
|
4164
2627
|
initPromise = (async () => {
|
|
4165
2628
|
const syncPath = findSgCliPathSync();
|
|
4166
|
-
if (syncPath &&
|
|
2629
|
+
if (syncPath && existsSync5(syncPath)) {
|
|
4167
2630
|
setSgCliPath(syncPath);
|
|
4168
2631
|
return syncPath;
|
|
4169
2632
|
}
|
|
@@ -4202,13 +2665,13 @@ async function runSg(options) {
|
|
|
4202
2665
|
const paths = options.paths && options.paths.length > 0 ? options.paths : ["."];
|
|
4203
2666
|
args.push(...paths);
|
|
4204
2667
|
let cliPath = getSgCliPath();
|
|
4205
|
-
if (!
|
|
2668
|
+
if (!existsSync5(cliPath) && cliPath !== "sg") {
|
|
4206
2669
|
const downloadedPath = await getAstGrepPath();
|
|
4207
2670
|
if (downloadedPath) {
|
|
4208
2671
|
cliPath = downloadedPath;
|
|
4209
2672
|
}
|
|
4210
2673
|
}
|
|
4211
|
-
const timeout =
|
|
2674
|
+
const timeout = DEFAULT_TIMEOUT_MS;
|
|
4212
2675
|
const proc = spawn([cliPath, ...args], {
|
|
4213
2676
|
stdout: "pipe",
|
|
4214
2677
|
stderr: "pipe"
|
|
@@ -4495,9 +2958,9 @@ var ast_grep_replace = tool({
|
|
|
4495
2958
|
});
|
|
4496
2959
|
|
|
4497
2960
|
// src/tools/lsp/client.ts
|
|
4498
|
-
import { readFileSync as
|
|
2961
|
+
import { readFileSync as readFileSync3 } from "fs";
|
|
4499
2962
|
import { extname, resolve as resolve2 } from "path";
|
|
4500
|
-
import { Readable
|
|
2963
|
+
import { Readable, Writable } from "stream";
|
|
4501
2964
|
import { pathToFileURL } from "url";
|
|
4502
2965
|
import {
|
|
4503
2966
|
createMessageConnection,
|
|
@@ -4506,9 +2969,9 @@ import {
|
|
|
4506
2969
|
} from "vscode-jsonrpc/node";
|
|
4507
2970
|
|
|
4508
2971
|
// src/tools/lsp/config.ts
|
|
4509
|
-
import { existsSync as
|
|
4510
|
-
import { homedir as
|
|
4511
|
-
import { join as
|
|
2972
|
+
import { existsSync as existsSync7 } from "fs";
|
|
2973
|
+
import { homedir as homedir3 } from "os";
|
|
2974
|
+
import { join as join6 } from "path";
|
|
4512
2975
|
import whichSync from "which";
|
|
4513
2976
|
|
|
4514
2977
|
// src/tools/lsp/config-store.ts
|
|
@@ -4539,8 +3002,8 @@ function hasUserLspConfig() {
|
|
|
4539
3002
|
}
|
|
4540
3003
|
|
|
4541
3004
|
// src/tools/lsp/constants.ts
|
|
4542
|
-
import { existsSync as
|
|
4543
|
-
import { dirname as
|
|
3005
|
+
import { existsSync as existsSync6, readdirSync, statSync as statSync3 } from "fs";
|
|
3006
|
+
import { dirname as dirname3, resolve } from "path";
|
|
4544
3007
|
var SEVERITY_MAP = {
|
|
4545
3008
|
1: "error",
|
|
4546
3009
|
2: "warning",
|
|
@@ -4559,18 +3022,18 @@ var LOCK_FILE_PATTERNS = [
|
|
|
4559
3022
|
function* walkUpDirectories(start, stop) {
|
|
4560
3023
|
let dir = resolve(start);
|
|
4561
3024
|
try {
|
|
4562
|
-
if (!
|
|
4563
|
-
dir =
|
|
3025
|
+
if (!statSync3(dir).isDirectory()) {
|
|
3026
|
+
dir = dirname3(dir);
|
|
4564
3027
|
}
|
|
4565
3028
|
} catch {
|
|
4566
|
-
dir =
|
|
3029
|
+
dir = dirname3(dir);
|
|
4567
3030
|
}
|
|
4568
3031
|
let prevDir = "";
|
|
4569
3032
|
while (dir !== prevDir && dir !== "/") {
|
|
4570
3033
|
yield dir;
|
|
4571
3034
|
prevDir = dir;
|
|
4572
3035
|
if (dir === stop) break;
|
|
4573
|
-
dir =
|
|
3036
|
+
dir = dirname3(dir);
|
|
4574
3037
|
}
|
|
4575
3038
|
}
|
|
4576
3039
|
function NearestRoot(includePatterns, excludePatterns) {
|
|
@@ -4579,7 +3042,7 @@ function NearestRoot(includePatterns, excludePatterns) {
|
|
|
4579
3042
|
if (excludePatterns) {
|
|
4580
3043
|
for (const dir of walkUpDirectories(file, cwd)) {
|
|
4581
3044
|
for (const pattern of excludePatterns) {
|
|
4582
|
-
if (
|
|
3045
|
+
if (existsSync6(`${dir}/${pattern}`)) {
|
|
4583
3046
|
return void 0;
|
|
4584
3047
|
}
|
|
4585
3048
|
}
|
|
@@ -4589,7 +3052,7 @@ function NearestRoot(includePatterns, excludePatterns) {
|
|
|
4589
3052
|
for (const pattern of includePatterns) {
|
|
4590
3053
|
if (pattern.includes("*")) {
|
|
4591
3054
|
try {
|
|
4592
|
-
const entries =
|
|
3055
|
+
const entries = readdirSync(dir);
|
|
4593
3056
|
const regex = new RegExp(
|
|
4594
3057
|
`^${pattern.replace(/\./g, "\\.").replace(/\*/g, ".*")}$`
|
|
4595
3058
|
);
|
|
@@ -4598,7 +3061,7 @@ function NearestRoot(includePatterns, excludePatterns) {
|
|
|
4598
3061
|
}
|
|
4599
3062
|
} catch {
|
|
4600
3063
|
}
|
|
4601
|
-
} else if (
|
|
3064
|
+
} else if (existsSync6(`${dir}/${pattern}`)) {
|
|
4602
3065
|
return dir;
|
|
4603
3066
|
}
|
|
4604
3067
|
}
|
|
@@ -5220,11 +3683,11 @@ function isServerInstalled(command) {
|
|
|
5220
3683
|
if (command.length === 0) return false;
|
|
5221
3684
|
const cmd = command[0];
|
|
5222
3685
|
if (cmd.includes("/") || cmd.includes("\\")) {
|
|
5223
|
-
return
|
|
3686
|
+
return existsSync7(cmd);
|
|
5224
3687
|
}
|
|
5225
3688
|
const isWindows = process.platform === "win32";
|
|
5226
3689
|
const ext = isWindows ? ".exe" : "";
|
|
5227
|
-
const opencodeBin =
|
|
3690
|
+
const opencodeBin = join6(homedir3(), ".config", "opencode", "bin");
|
|
5228
3691
|
const searchPath = (process.env.PATH ?? "") + (isWindows ? ";" : ":") + opencodeBin;
|
|
5229
3692
|
const result = whichSync.sync(cmd, {
|
|
5230
3693
|
path: searchPath,
|
|
@@ -5235,8 +3698,8 @@ function isServerInstalled(command) {
|
|
|
5235
3698
|
return true;
|
|
5236
3699
|
}
|
|
5237
3700
|
const cwd = process.cwd();
|
|
5238
|
-
const localBin =
|
|
5239
|
-
if (
|
|
3701
|
+
const localBin = join6(cwd, "node_modules", ".bin", cmd);
|
|
3702
|
+
if (existsSync7(localBin) || existsSync7(localBin + ext)) {
|
|
5240
3703
|
return true;
|
|
5241
3704
|
}
|
|
5242
3705
|
return false;
|
|
@@ -5433,7 +3896,7 @@ var LSPClient = class {
|
|
|
5433
3896
|
}
|
|
5434
3897
|
this.startStderrReading();
|
|
5435
3898
|
const stdoutReader = this.proc.stdout.getReader();
|
|
5436
|
-
const nodeReadable = new
|
|
3899
|
+
const nodeReadable = new Readable({
|
|
5437
3900
|
async read() {
|
|
5438
3901
|
try {
|
|
5439
3902
|
const { done, value } = await stdoutReader.read();
|
|
@@ -5576,7 +4039,7 @@ stderr: ${stderr}` : "")
|
|
|
5576
4039
|
log("[lsp] openFile: already open, skipping", { filePath: absPath });
|
|
5577
4040
|
return;
|
|
5578
4041
|
}
|
|
5579
|
-
const text =
|
|
4042
|
+
const text = readFileSync3(absPath, "utf-8");
|
|
5580
4043
|
const ext = extname(absPath);
|
|
5581
4044
|
const languageId = getLanguageId(ext);
|
|
5582
4045
|
log("[lsp] openFile: opening document", {
|
|
@@ -5667,22 +4130,22 @@ import { tool as tool2 } from "@opencode-ai/plugin/tool";
|
|
|
5667
4130
|
|
|
5668
4131
|
// src/tools/lsp/utils.ts
|
|
5669
4132
|
import {
|
|
5670
|
-
existsSync as
|
|
5671
|
-
readFileSync as
|
|
5672
|
-
statSync as
|
|
4133
|
+
existsSync as existsSync8,
|
|
4134
|
+
readFileSync as readFileSync4,
|
|
4135
|
+
statSync as statSync4,
|
|
5673
4136
|
unlinkSync as unlinkSync2,
|
|
5674
|
-
writeFileSync as
|
|
4137
|
+
writeFileSync as writeFileSync3
|
|
5675
4138
|
} from "fs";
|
|
5676
|
-
import { dirname as
|
|
5677
|
-
import { fileURLToPath as
|
|
4139
|
+
import { dirname as dirname4, extname as extname2, join as join7, resolve as resolve3 } from "path";
|
|
4140
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
5678
4141
|
function findServerProjectRoot(filePath, server) {
|
|
5679
4142
|
if (server.root) {
|
|
5680
|
-
return server.root(filePath) ??
|
|
4143
|
+
return server.root(filePath) ?? dirname4(resolve3(filePath));
|
|
5681
4144
|
}
|
|
5682
|
-
return
|
|
4145
|
+
return dirname4(resolve3(filePath));
|
|
5683
4146
|
}
|
|
5684
4147
|
function uriToPath(uri) {
|
|
5685
|
-
return
|
|
4148
|
+
return fileURLToPath2(uri);
|
|
5686
4149
|
}
|
|
5687
4150
|
function formatServerLookupError(result) {
|
|
5688
4151
|
if (result.status === "not_installed") {
|
|
@@ -5708,7 +4171,7 @@ async function withLspClient(filePath, fn) {
|
|
|
5708
4171
|
throw new Error(formatServerLookupError(result));
|
|
5709
4172
|
}
|
|
5710
4173
|
const server = result.server;
|
|
5711
|
-
const root = findServerProjectRoot(absPath, server) ??
|
|
4174
|
+
const root = findServerProjectRoot(absPath, server) ?? dirname4(absPath);
|
|
5712
4175
|
log("[lsp] withLspClient: acquiring client", {
|
|
5713
4176
|
filePath: absPath,
|
|
5714
4177
|
server: server.id,
|
|
@@ -5779,7 +4242,7 @@ function filterDiagnosticsBySeverity(diagnostics, severityFilter) {
|
|
|
5779
4242
|
}
|
|
5780
4243
|
function applyTextEditsToFile(filePath, edits) {
|
|
5781
4244
|
try {
|
|
5782
|
-
const content =
|
|
4245
|
+
const content = readFileSync4(filePath, "utf-8");
|
|
5783
4246
|
const lines = content.split("\n");
|
|
5784
4247
|
const sortedEdits = [...edits].sort((a, b) => {
|
|
5785
4248
|
if (b.range.start.line !== a.range.start.line) {
|
|
@@ -5806,7 +4269,7 @@ function applyTextEditsToFile(filePath, edits) {
|
|
|
5806
4269
|
);
|
|
5807
4270
|
}
|
|
5808
4271
|
}
|
|
5809
|
-
|
|
4272
|
+
writeFileSync3(filePath, lines.join("\n"), "utf-8");
|
|
5810
4273
|
return { success: true, editCount: edits.length };
|
|
5811
4274
|
} catch (err) {
|
|
5812
4275
|
return {
|
|
@@ -5853,7 +4316,7 @@ function applyWorkspaceEdit(edit) {
|
|
|
5853
4316
|
if (change.kind === "create") {
|
|
5854
4317
|
try {
|
|
5855
4318
|
const filePath = uriToPath(change.uri);
|
|
5856
|
-
|
|
4319
|
+
writeFileSync3(filePath, "", "utf-8");
|
|
5857
4320
|
result.filesModified.push(filePath);
|
|
5858
4321
|
} catch (err) {
|
|
5859
4322
|
result.success = false;
|
|
@@ -5863,8 +4326,8 @@ function applyWorkspaceEdit(edit) {
|
|
|
5863
4326
|
try {
|
|
5864
4327
|
const oldPath = uriToPath(change.oldUri);
|
|
5865
4328
|
const newPath = uriToPath(change.newUri);
|
|
5866
|
-
const content =
|
|
5867
|
-
|
|
4329
|
+
const content = readFileSync4(oldPath, "utf-8");
|
|
4330
|
+
writeFileSync3(newPath, content, "utf-8");
|
|
5868
4331
|
unlinkSync2(oldPath);
|
|
5869
4332
|
result.filesModified.push(newPath);
|
|
5870
4333
|
} catch (err) {
|
|
@@ -6247,7 +4710,7 @@ var TmuxSessionManager = class {
|
|
|
6247
4710
|
// src/index.ts
|
|
6248
4711
|
function resolveProjectName(project, directory) {
|
|
6249
4712
|
const runtimeProjectName = "name" in project && typeof project.name === "string" ? project.name : void 0;
|
|
6250
|
-
return runtimeProjectName ||
|
|
4713
|
+
return runtimeProjectName || path4.basename(directory) || "thoth-agents";
|
|
6251
4714
|
}
|
|
6252
4715
|
var ThothAgents = async (ctx, _options) => {
|
|
6253
4716
|
const { client, directory, project, $: shell } = ctx;
|