opencode-manifold 0.4.12 → 0.4.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +928 -682
- package/dist/tui.js +12 -45
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,449 +1,114 @@
|
|
|
1
1
|
import { createRequire } from "node:module";
|
|
2
2
|
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
3
3
|
|
|
4
|
-
// src/
|
|
5
|
-
import { readFile, writeFile, mkdir, readdir } from "fs/promises";
|
|
4
|
+
// src/tui.ts
|
|
6
5
|
import { existsSync } from "fs";
|
|
7
|
-
import { join
|
|
8
|
-
import { fileURLToPath } from "url";
|
|
6
|
+
import { join } from "path";
|
|
9
7
|
import { homedir } from "os";
|
|
10
|
-
import {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
try {
|
|
17
|
-
const packageJson = require2(join(__dirname2, "..", "package.json"));
|
|
18
|
-
return packageJson.version || "unknown";
|
|
19
|
-
} catch {
|
|
20
|
-
return "unknown";
|
|
21
|
-
}
|
|
8
|
+
import { readFile, writeFile, readdir } from "fs/promises";
|
|
9
|
+
|
|
10
|
+
// node_modules/js-yaml/dist/js-yaml.mjs
|
|
11
|
+
/*! js-yaml 4.1.1 https://github.com/nodeca/js-yaml @license MIT */
|
|
12
|
+
function isNothing(subject) {
|
|
13
|
+
return typeof subject === "undefined" || subject === null;
|
|
22
14
|
}
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
return false;
|
|
26
|
-
try {
|
|
27
|
-
const entries = await readdir(dirPath);
|
|
28
|
-
return entries.length > 0;
|
|
29
|
-
} catch {
|
|
30
|
-
return false;
|
|
31
|
-
}
|
|
15
|
+
function isObject(subject) {
|
|
16
|
+
return typeof subject === "object" && subject !== null;
|
|
32
17
|
}
|
|
33
|
-
|
|
34
|
-
if (
|
|
18
|
+
function toArray(sequence) {
|
|
19
|
+
if (Array.isArray(sequence))
|
|
20
|
+
return sequence;
|
|
21
|
+
else if (isNothing(sequence))
|
|
35
22
|
return [];
|
|
36
|
-
|
|
37
|
-
const copied = [];
|
|
38
|
-
const entries = await readdir(src, { withFileTypes: true });
|
|
39
|
-
for (const entry of entries) {
|
|
40
|
-
const srcPath = join(src, entry.name);
|
|
41
|
-
const destPath = join(dest, entry.name);
|
|
42
|
-
if (entry.isDirectory()) {
|
|
43
|
-
const subCopied = await copyMissingFiles(srcPath, destPath);
|
|
44
|
-
if (subCopied.length > 0) {
|
|
45
|
-
copied.push(entry.name);
|
|
46
|
-
}
|
|
47
|
-
} else if (!existsSync(destPath)) {
|
|
48
|
-
await writeFile(destPath, await readFile(srcPath));
|
|
49
|
-
copied.push(entry.name);
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
return copied;
|
|
23
|
+
return [sequence];
|
|
53
24
|
}
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
25
|
+
function extend(target, source) {
|
|
26
|
+
var index, length, key, sourceKeys;
|
|
27
|
+
if (source) {
|
|
28
|
+
sourceKeys = Object.keys(source);
|
|
29
|
+
for (index = 0, length = sourceKeys.length;index < length; index += 1) {
|
|
30
|
+
key = sourceKeys[index];
|
|
31
|
+
target[key] = source[key];
|
|
60
32
|
}
|
|
61
|
-
});
|
|
62
|
-
if (!existsSync(bundledTemplatesDir)) {
|
|
63
|
-
await ctx.client.app.log({
|
|
64
|
-
body: {
|
|
65
|
-
service: "opencode-manifold",
|
|
66
|
-
level: "error",
|
|
67
|
-
message: `Bundled templates not found at ${bundledTemplatesDir}`
|
|
68
|
-
}
|
|
69
|
-
});
|
|
70
|
-
return;
|
|
71
33
|
}
|
|
72
|
-
|
|
73
|
-
const globalCommandsDir = join(homedir(), ".config", "opencode", "commands");
|
|
74
|
-
const bundledCommandsDir = join(bundledTemplatesDir, "commands");
|
|
75
|
-
if (existsSync(bundledCommandsDir)) {
|
|
76
|
-
await copyMissingFiles(bundledCommandsDir, globalCommandsDir);
|
|
77
|
-
}
|
|
78
|
-
await ctx.client.app.log({
|
|
79
|
-
body: {
|
|
80
|
-
service: "opencode-manifold",
|
|
81
|
-
level: "info",
|
|
82
|
-
message: "Global templates ready"
|
|
83
|
-
}
|
|
84
|
-
});
|
|
34
|
+
return target;
|
|
85
35
|
}
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
service: "opencode-manifold",
|
|
91
|
-
level: "info",
|
|
92
|
-
message: `Running /manifold-init in ${directory}`
|
|
93
|
-
}
|
|
94
|
-
});
|
|
95
|
-
if (!await dirHasContent(globalTemplatesDir)) {
|
|
96
|
-
await client.app.log({
|
|
97
|
-
body: {
|
|
98
|
-
service: "opencode-manifold",
|
|
99
|
-
level: "error",
|
|
100
|
-
message: `Global templates not found at ${globalTemplatesDir}. Plugin may not have loaded correctly.`
|
|
101
|
-
}
|
|
102
|
-
});
|
|
103
|
-
return initialized;
|
|
104
|
-
}
|
|
105
|
-
const agentsCopied = await copyMissingFiles(join(globalTemplatesDir, "agents"), join(directory, ".opencode", "agents"));
|
|
106
|
-
if (agentsCopied.length > 0) {
|
|
107
|
-
initialized.push(`agents (${agentsCopied.join(", ")})`);
|
|
36
|
+
function repeat(string, count) {
|
|
37
|
+
var result = "", cycle;
|
|
38
|
+
for (cycle = 0;cycle < count; cycle += 1) {
|
|
39
|
+
result += string;
|
|
108
40
|
}
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
41
|
+
return result;
|
|
42
|
+
}
|
|
43
|
+
function isNegativeZero(number) {
|
|
44
|
+
return number === 0 && Number.NEGATIVE_INFINITY === 1 / number;
|
|
45
|
+
}
|
|
46
|
+
var isNothing_1 = isNothing;
|
|
47
|
+
var isObject_1 = isObject;
|
|
48
|
+
var toArray_1 = toArray;
|
|
49
|
+
var repeat_1 = repeat;
|
|
50
|
+
var isNegativeZero_1 = isNegativeZero;
|
|
51
|
+
var extend_1 = extend;
|
|
52
|
+
var common = {
|
|
53
|
+
isNothing: isNothing_1,
|
|
54
|
+
isObject: isObject_1,
|
|
55
|
+
toArray: toArray_1,
|
|
56
|
+
repeat: repeat_1,
|
|
57
|
+
isNegativeZero: isNegativeZero_1,
|
|
58
|
+
extend: extend_1
|
|
59
|
+
};
|
|
60
|
+
function formatError(exception, compact) {
|
|
61
|
+
var where = "", message = exception.reason || "(unknown reason)";
|
|
62
|
+
if (!exception.mark)
|
|
63
|
+
return message;
|
|
64
|
+
if (exception.mark.name) {
|
|
65
|
+
where += 'in "' + exception.mark.name + '" ';
|
|
112
66
|
}
|
|
113
|
-
|
|
114
|
-
if (
|
|
115
|
-
|
|
67
|
+
where += "(" + (exception.mark.line + 1) + ":" + (exception.mark.column + 1) + ")";
|
|
68
|
+
if (!compact && exception.mark.snippet) {
|
|
69
|
+
where += `
|
|
70
|
+
|
|
71
|
+
` + exception.mark.snippet;
|
|
116
72
|
}
|
|
117
|
-
|
|
118
|
-
await writeFile(join(directory, "Manifold", "VERSION"), version + `
|
|
119
|
-
`);
|
|
120
|
-
await client.app.log({
|
|
121
|
-
body: {
|
|
122
|
-
service: "opencode-manifold",
|
|
123
|
-
level: "info",
|
|
124
|
-
message: `/manifold-init complete: ${initialized.join(", ") || "already initialized"}`
|
|
125
|
-
}
|
|
126
|
-
});
|
|
127
|
-
return initialized;
|
|
73
|
+
return message + " " + where;
|
|
128
74
|
}
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
import { join as join4 } from "path";
|
|
140
|
-
|
|
141
|
-
// src/session-spawner.ts
|
|
142
|
-
async function waitForSessionIdle(client, sessionId, timeoutMs) {
|
|
143
|
-
const startTime = Date.now();
|
|
144
|
-
while (Date.now() - startTime < timeoutMs) {
|
|
145
|
-
const statusResponse = await client.session.status({});
|
|
146
|
-
const statusData = statusResponse.data;
|
|
147
|
-
if (statusData && statusData[sessionId]) {
|
|
148
|
-
const status = statusData[sessionId];
|
|
149
|
-
if (status.type === "idle") {
|
|
150
|
-
return true;
|
|
151
|
-
}
|
|
152
|
-
if (status.type === "retry") {
|
|
153
|
-
const waitTime = status.next - Date.now();
|
|
154
|
-
if (waitTime > 0) {
|
|
155
|
-
await new Promise((resolve) => setTimeout(resolve, Math.min(waitTime, 1000)));
|
|
156
|
-
}
|
|
157
|
-
continue;
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
75
|
+
function YAMLException$1(reason, mark) {
|
|
76
|
+
Error.call(this);
|
|
77
|
+
this.name = "YAMLException";
|
|
78
|
+
this.reason = reason;
|
|
79
|
+
this.mark = mark;
|
|
80
|
+
this.message = formatError(this, false);
|
|
81
|
+
if (Error.captureStackTrace) {
|
|
82
|
+
Error.captureStackTrace(this, this.constructor);
|
|
83
|
+
} else {
|
|
84
|
+
this.stack = new Error().stack || "";
|
|
161
85
|
}
|
|
162
|
-
return false;
|
|
163
86
|
}
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
87
|
+
YAMLException$1.prototype = Object.create(Error.prototype);
|
|
88
|
+
YAMLException$1.prototype.constructor = YAMLException$1;
|
|
89
|
+
YAMLException$1.prototype.toString = function toString(compact) {
|
|
90
|
+
return this.name + ": " + formatError(this, compact);
|
|
91
|
+
};
|
|
92
|
+
var exception = YAMLException$1;
|
|
93
|
+
function getLine(buffer, lineStart, lineEnd, position, maxLineLength) {
|
|
94
|
+
var head = "";
|
|
95
|
+
var tail = "";
|
|
96
|
+
var maxHalfLength = Math.floor(maxLineLength / 2) - 1;
|
|
97
|
+
if (position - lineStart > maxHalfLength) {
|
|
98
|
+
head = " ... ";
|
|
99
|
+
lineStart = position - maxHalfLength + head.length;
|
|
171
100
|
}
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
if (msg.parts && Array.isArray(msg.parts)) {
|
|
176
|
-
for (const part of msg.parts) {
|
|
177
|
-
if (part.type === "text" && part.text) {
|
|
178
|
-
return part.text;
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
}
|
|
101
|
+
if (lineEnd - position > maxHalfLength) {
|
|
102
|
+
tail = " ...";
|
|
103
|
+
lineEnd = position + maxHalfLength - tail.length;
|
|
183
104
|
}
|
|
184
|
-
return
|
|
105
|
+
return {
|
|
106
|
+
str: head + buffer.slice(lineStart, lineEnd).replace(/\t/g, "→") + tail,
|
|
107
|
+
pos: position - lineStart + head.length
|
|
108
|
+
};
|
|
185
109
|
}
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
await client.session.delete({ path: { id: sessionId } });
|
|
189
|
-
} catch {}
|
|
190
|
-
}
|
|
191
|
-
async function spawnSession(client, agent, prompt, timeoutSeconds) {
|
|
192
|
-
const timeoutMs = timeoutSeconds * 1000;
|
|
193
|
-
try {
|
|
194
|
-
const createResponse = await client.session.create({});
|
|
195
|
-
const session = createResponse.data;
|
|
196
|
-
if (!session || !session.id) {
|
|
197
|
-
return {
|
|
198
|
-
content: "",
|
|
199
|
-
success: false,
|
|
200
|
-
error: "Failed to create session"
|
|
201
|
-
};
|
|
202
|
-
}
|
|
203
|
-
const sessionId = session.id;
|
|
204
|
-
await client.app.log({
|
|
205
|
-
body: {
|
|
206
|
-
service: "opencode-manifold",
|
|
207
|
-
level: "info",
|
|
208
|
-
message: `Created session ${sessionId} for agent ${agent}`
|
|
209
|
-
}
|
|
210
|
-
});
|
|
211
|
-
try {
|
|
212
|
-
await client.session.promptAsync({
|
|
213
|
-
path: { id: sessionId },
|
|
214
|
-
body: {
|
|
215
|
-
agent,
|
|
216
|
-
noReply: true,
|
|
217
|
-
parts: [{ type: "text", text: prompt }]
|
|
218
|
-
}
|
|
219
|
-
});
|
|
220
|
-
const isIdle = await waitForSessionIdle(client, sessionId, timeoutMs);
|
|
221
|
-
if (!isIdle) {
|
|
222
|
-
await client.session.abort({ path: { id: sessionId } });
|
|
223
|
-
return {
|
|
224
|
-
content: "",
|
|
225
|
-
success: false,
|
|
226
|
-
error: `Session timed out after ${timeoutSeconds}s`
|
|
227
|
-
};
|
|
228
|
-
}
|
|
229
|
-
const content = await getLastAssistantMessage(client, sessionId);
|
|
230
|
-
await client.app.log({
|
|
231
|
-
body: {
|
|
232
|
-
service: "opencode-manifold",
|
|
233
|
-
level: "info",
|
|
234
|
-
message: `Session ${sessionId} completed, content length: ${content.length}`
|
|
235
|
-
}
|
|
236
|
-
});
|
|
237
|
-
return {
|
|
238
|
-
content,
|
|
239
|
-
success: true
|
|
240
|
-
};
|
|
241
|
-
} finally {
|
|
242
|
-
await cleanupSession(client, sessionId);
|
|
243
|
-
}
|
|
244
|
-
} catch (error) {
|
|
245
|
-
return {
|
|
246
|
-
content: "",
|
|
247
|
-
success: false,
|
|
248
|
-
error: error instanceof Error ? error.message : String(error)
|
|
249
|
-
};
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
async function spawnClerkSession(client, prompt, agent, timeout) {
|
|
253
|
-
await client.app.log({
|
|
254
|
-
body: {
|
|
255
|
-
service: "opencode-manifold",
|
|
256
|
-
level: "info",
|
|
257
|
-
message: `Spawning Clerk session (agent: ${agent}, timeout: ${timeout}s)`
|
|
258
|
-
}
|
|
259
|
-
});
|
|
260
|
-
return spawnSession(client, agent, prompt, timeout);
|
|
261
|
-
}
|
|
262
|
-
async function spawnSeniorDevSession(client, prompt, agent, timeout) {
|
|
263
|
-
await client.app.log({
|
|
264
|
-
body: {
|
|
265
|
-
service: "opencode-manifold",
|
|
266
|
-
level: "info",
|
|
267
|
-
message: `Spawning Senior Dev session (agent: ${agent}, timeout: ${timeout}s)`
|
|
268
|
-
}
|
|
269
|
-
});
|
|
270
|
-
return spawnSession(client, agent, prompt, timeout);
|
|
271
|
-
}
|
|
272
|
-
async function spawnJuniorDevSession(client, prompt, seniorOutput, agent, timeout) {
|
|
273
|
-
await client.app.log({
|
|
274
|
-
body: {
|
|
275
|
-
service: "opencode-manifold",
|
|
276
|
-
level: "info",
|
|
277
|
-
message: `Spawning Junior Dev session (agent: ${agent}, timeout: ${timeout}s)`
|
|
278
|
-
}
|
|
279
|
-
});
|
|
280
|
-
const fullPrompt = `${prompt}
|
|
281
|
-
|
|
282
|
-
---
|
|
283
|
-
|
|
284
|
-
Senior Dev's Implementation:
|
|
285
|
-
${seniorOutput}`;
|
|
286
|
-
return spawnSession(client, agent, fullPrompt, timeout);
|
|
287
|
-
}
|
|
288
|
-
async function spawnDebugSession(client, prompt, loopHistory, agent, timeout) {
|
|
289
|
-
await client.app.log({
|
|
290
|
-
body: {
|
|
291
|
-
service: "opencode-manifold",
|
|
292
|
-
level: "info",
|
|
293
|
-
message: `Spawning Debug session (agent: ${agent}, timeout: ${timeout}s)`
|
|
294
|
-
}
|
|
295
|
-
});
|
|
296
|
-
const fullPrompt = `${prompt}
|
|
297
|
-
|
|
298
|
-
---
|
|
299
|
-
|
|
300
|
-
Loop History:
|
|
301
|
-
${loopHistory}`;
|
|
302
|
-
return spawnSession(client, agent, fullPrompt, timeout);
|
|
303
|
-
}
|
|
304
|
-
function parseJuniorFirstWord(response) {
|
|
305
|
-
const firstWord = response.trim().split(/\s+/)[0].toUpperCase();
|
|
306
|
-
if (firstWord === "COMPLETE") {
|
|
307
|
-
return "COMPLETE";
|
|
308
|
-
}
|
|
309
|
-
return "QUESTIONS";
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
// src/error-handler.ts
|
|
313
|
-
class ModelCallError extends Error {
|
|
314
|
-
agent;
|
|
315
|
-
attempt;
|
|
316
|
-
constructor(message, agent, attempt) {
|
|
317
|
-
super(message);
|
|
318
|
-
this.agent = agent;
|
|
319
|
-
this.attempt = attempt;
|
|
320
|
-
this.name = "ModelCallError";
|
|
321
|
-
}
|
|
322
|
-
}
|
|
323
|
-
async function retryWithBackoff(fn, options) {
|
|
324
|
-
let lastError;
|
|
325
|
-
for (let attempt = 0;attempt <= options.maxRetries; attempt++) {
|
|
326
|
-
try {
|
|
327
|
-
return await fn();
|
|
328
|
-
} catch (error) {
|
|
329
|
-
lastError = error instanceof Error ? error : new Error(String(error));
|
|
330
|
-
if (attempt < options.maxRetries) {
|
|
331
|
-
options.onRetry?.(attempt + 1, lastError);
|
|
332
|
-
const delay = Math.pow(2, attempt) * 100;
|
|
333
|
-
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
334
|
-
}
|
|
335
|
-
}
|
|
336
|
-
}
|
|
337
|
-
throw lastError;
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
// src/graph.ts
|
|
341
|
-
import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
|
|
342
|
-
import { existsSync as existsSync2 } from "fs";
|
|
343
|
-
import { join as join2, dirname as dirname2 } from "path";
|
|
344
|
-
|
|
345
|
-
// node_modules/js-yaml/dist/js-yaml.mjs
|
|
346
|
-
/*! js-yaml 4.1.1 https://github.com/nodeca/js-yaml @license MIT */
|
|
347
|
-
function isNothing(subject) {
|
|
348
|
-
return typeof subject === "undefined" || subject === null;
|
|
349
|
-
}
|
|
350
|
-
function isObject(subject) {
|
|
351
|
-
return typeof subject === "object" && subject !== null;
|
|
352
|
-
}
|
|
353
|
-
function toArray(sequence) {
|
|
354
|
-
if (Array.isArray(sequence))
|
|
355
|
-
return sequence;
|
|
356
|
-
else if (isNothing(sequence))
|
|
357
|
-
return [];
|
|
358
|
-
return [sequence];
|
|
359
|
-
}
|
|
360
|
-
function extend(target, source) {
|
|
361
|
-
var index, length, key, sourceKeys;
|
|
362
|
-
if (source) {
|
|
363
|
-
sourceKeys = Object.keys(source);
|
|
364
|
-
for (index = 0, length = sourceKeys.length;index < length; index += 1) {
|
|
365
|
-
key = sourceKeys[index];
|
|
366
|
-
target[key] = source[key];
|
|
367
|
-
}
|
|
368
|
-
}
|
|
369
|
-
return target;
|
|
370
|
-
}
|
|
371
|
-
function repeat(string, count) {
|
|
372
|
-
var result = "", cycle;
|
|
373
|
-
for (cycle = 0;cycle < count; cycle += 1) {
|
|
374
|
-
result += string;
|
|
375
|
-
}
|
|
376
|
-
return result;
|
|
377
|
-
}
|
|
378
|
-
function isNegativeZero(number) {
|
|
379
|
-
return number === 0 && Number.NEGATIVE_INFINITY === 1 / number;
|
|
380
|
-
}
|
|
381
|
-
var isNothing_1 = isNothing;
|
|
382
|
-
var isObject_1 = isObject;
|
|
383
|
-
var toArray_1 = toArray;
|
|
384
|
-
var repeat_1 = repeat;
|
|
385
|
-
var isNegativeZero_1 = isNegativeZero;
|
|
386
|
-
var extend_1 = extend;
|
|
387
|
-
var common = {
|
|
388
|
-
isNothing: isNothing_1,
|
|
389
|
-
isObject: isObject_1,
|
|
390
|
-
toArray: toArray_1,
|
|
391
|
-
repeat: repeat_1,
|
|
392
|
-
isNegativeZero: isNegativeZero_1,
|
|
393
|
-
extend: extend_1
|
|
394
|
-
};
|
|
395
|
-
function formatError(exception, compact) {
|
|
396
|
-
var where = "", message = exception.reason || "(unknown reason)";
|
|
397
|
-
if (!exception.mark)
|
|
398
|
-
return message;
|
|
399
|
-
if (exception.mark.name) {
|
|
400
|
-
where += 'in "' + exception.mark.name + '" ';
|
|
401
|
-
}
|
|
402
|
-
where += "(" + (exception.mark.line + 1) + ":" + (exception.mark.column + 1) + ")";
|
|
403
|
-
if (!compact && exception.mark.snippet) {
|
|
404
|
-
where += `
|
|
405
|
-
|
|
406
|
-
` + exception.mark.snippet;
|
|
407
|
-
}
|
|
408
|
-
return message + " " + where;
|
|
409
|
-
}
|
|
410
|
-
function YAMLException$1(reason, mark) {
|
|
411
|
-
Error.call(this);
|
|
412
|
-
this.name = "YAMLException";
|
|
413
|
-
this.reason = reason;
|
|
414
|
-
this.mark = mark;
|
|
415
|
-
this.message = formatError(this, false);
|
|
416
|
-
if (Error.captureStackTrace) {
|
|
417
|
-
Error.captureStackTrace(this, this.constructor);
|
|
418
|
-
} else {
|
|
419
|
-
this.stack = new Error().stack || "";
|
|
420
|
-
}
|
|
421
|
-
}
|
|
422
|
-
YAMLException$1.prototype = Object.create(Error.prototype);
|
|
423
|
-
YAMLException$1.prototype.constructor = YAMLException$1;
|
|
424
|
-
YAMLException$1.prototype.toString = function toString(compact) {
|
|
425
|
-
return this.name + ": " + formatError(this, compact);
|
|
426
|
-
};
|
|
427
|
-
var exception = YAMLException$1;
|
|
428
|
-
function getLine(buffer, lineStart, lineEnd, position, maxLineLength) {
|
|
429
|
-
var head = "";
|
|
430
|
-
var tail = "";
|
|
431
|
-
var maxHalfLength = Math.floor(maxLineLength / 2) - 1;
|
|
432
|
-
if (position - lineStart > maxHalfLength) {
|
|
433
|
-
head = " ... ";
|
|
434
|
-
lineStart = position - maxHalfLength + head.length;
|
|
435
|
-
}
|
|
436
|
-
if (lineEnd - position > maxHalfLength) {
|
|
437
|
-
tail = " ...";
|
|
438
|
-
lineEnd = position + maxHalfLength - tail.length;
|
|
439
|
-
}
|
|
440
|
-
return {
|
|
441
|
-
str: head + buffer.slice(lineStart, lineEnd).replace(/\t/g, "→") + tail,
|
|
442
|
-
pos: position - lineStart + head.length
|
|
443
|
-
};
|
|
444
|
-
}
|
|
445
|
-
function padStart(string, max) {
|
|
446
|
-
return common.repeat(" ", max - string.length) + string;
|
|
110
|
+
function padStart(string, max) {
|
|
111
|
+
return common.repeat(" ", max - string.length) + string;
|
|
447
112
|
}
|
|
448
113
|
function makeSnippet(mark, options) {
|
|
449
114
|
options = Object.create(options || null);
|
|
@@ -2767,267 +2432,847 @@ function writeFlowMapping(state, level, object) {
|
|
|
2767
2432
|
pairBuffer += state.dump;
|
|
2768
2433
|
_result += pairBuffer;
|
|
2769
2434
|
}
|
|
2770
|
-
state.tag = _tag;
|
|
2771
|
-
state.dump = "{" + _result + "}";
|
|
2435
|
+
state.tag = _tag;
|
|
2436
|
+
state.dump = "{" + _result + "}";
|
|
2437
|
+
}
|
|
2438
|
+
function writeBlockMapping(state, level, object, compact) {
|
|
2439
|
+
var _result = "", _tag = state.tag, objectKeyList = Object.keys(object), index, length, objectKey, objectValue, explicitPair, pairBuffer;
|
|
2440
|
+
if (state.sortKeys === true) {
|
|
2441
|
+
objectKeyList.sort();
|
|
2442
|
+
} else if (typeof state.sortKeys === "function") {
|
|
2443
|
+
objectKeyList.sort(state.sortKeys);
|
|
2444
|
+
} else if (state.sortKeys) {
|
|
2445
|
+
throw new exception("sortKeys must be a boolean or a function");
|
|
2446
|
+
}
|
|
2447
|
+
for (index = 0, length = objectKeyList.length;index < length; index += 1) {
|
|
2448
|
+
pairBuffer = "";
|
|
2449
|
+
if (!compact || _result !== "") {
|
|
2450
|
+
pairBuffer += generateNextLine(state, level);
|
|
2451
|
+
}
|
|
2452
|
+
objectKey = objectKeyList[index];
|
|
2453
|
+
objectValue = object[objectKey];
|
|
2454
|
+
if (state.replacer) {
|
|
2455
|
+
objectValue = state.replacer.call(object, objectKey, objectValue);
|
|
2456
|
+
}
|
|
2457
|
+
if (!writeNode(state, level + 1, objectKey, true, true, true)) {
|
|
2458
|
+
continue;
|
|
2459
|
+
}
|
|
2460
|
+
explicitPair = state.tag !== null && state.tag !== "?" || state.dump && state.dump.length > 1024;
|
|
2461
|
+
if (explicitPair) {
|
|
2462
|
+
if (state.dump && CHAR_LINE_FEED === state.dump.charCodeAt(0)) {
|
|
2463
|
+
pairBuffer += "?";
|
|
2464
|
+
} else {
|
|
2465
|
+
pairBuffer += "? ";
|
|
2466
|
+
}
|
|
2467
|
+
}
|
|
2468
|
+
pairBuffer += state.dump;
|
|
2469
|
+
if (explicitPair) {
|
|
2470
|
+
pairBuffer += generateNextLine(state, level);
|
|
2471
|
+
}
|
|
2472
|
+
if (!writeNode(state, level + 1, objectValue, true, explicitPair)) {
|
|
2473
|
+
continue;
|
|
2474
|
+
}
|
|
2475
|
+
if (state.dump && CHAR_LINE_FEED === state.dump.charCodeAt(0)) {
|
|
2476
|
+
pairBuffer += ":";
|
|
2477
|
+
} else {
|
|
2478
|
+
pairBuffer += ": ";
|
|
2479
|
+
}
|
|
2480
|
+
pairBuffer += state.dump;
|
|
2481
|
+
_result += pairBuffer;
|
|
2482
|
+
}
|
|
2483
|
+
state.tag = _tag;
|
|
2484
|
+
state.dump = _result || "{}";
|
|
2485
|
+
}
|
|
2486
|
+
function detectType(state, object, explicit) {
|
|
2487
|
+
var _result, typeList, index, length, type2, style;
|
|
2488
|
+
typeList = explicit ? state.explicitTypes : state.implicitTypes;
|
|
2489
|
+
for (index = 0, length = typeList.length;index < length; index += 1) {
|
|
2490
|
+
type2 = typeList[index];
|
|
2491
|
+
if ((type2.instanceOf || type2.predicate) && (!type2.instanceOf || typeof object === "object" && object instanceof type2.instanceOf) && (!type2.predicate || type2.predicate(object))) {
|
|
2492
|
+
if (explicit) {
|
|
2493
|
+
if (type2.multi && type2.representName) {
|
|
2494
|
+
state.tag = type2.representName(object);
|
|
2495
|
+
} else {
|
|
2496
|
+
state.tag = type2.tag;
|
|
2497
|
+
}
|
|
2498
|
+
} else {
|
|
2499
|
+
state.tag = "?";
|
|
2500
|
+
}
|
|
2501
|
+
if (type2.represent) {
|
|
2502
|
+
style = state.styleMap[type2.tag] || type2.defaultStyle;
|
|
2503
|
+
if (_toString.call(type2.represent) === "[object Function]") {
|
|
2504
|
+
_result = type2.represent(object, style);
|
|
2505
|
+
} else if (_hasOwnProperty.call(type2.represent, style)) {
|
|
2506
|
+
_result = type2.represent[style](object, style);
|
|
2507
|
+
} else {
|
|
2508
|
+
throw new exception("!<" + type2.tag + '> tag resolver accepts not "' + style + '" style');
|
|
2509
|
+
}
|
|
2510
|
+
state.dump = _result;
|
|
2511
|
+
}
|
|
2512
|
+
return true;
|
|
2513
|
+
}
|
|
2514
|
+
}
|
|
2515
|
+
return false;
|
|
2516
|
+
}
|
|
2517
|
+
function writeNode(state, level, object, block, compact, iskey, isblockseq) {
|
|
2518
|
+
state.tag = null;
|
|
2519
|
+
state.dump = object;
|
|
2520
|
+
if (!detectType(state, object, false)) {
|
|
2521
|
+
detectType(state, object, true);
|
|
2522
|
+
}
|
|
2523
|
+
var type2 = _toString.call(state.dump);
|
|
2524
|
+
var inblock = block;
|
|
2525
|
+
var tagStr;
|
|
2526
|
+
if (block) {
|
|
2527
|
+
block = state.flowLevel < 0 || state.flowLevel > level;
|
|
2528
|
+
}
|
|
2529
|
+
var objectOrArray = type2 === "[object Object]" || type2 === "[object Array]", duplicateIndex, duplicate;
|
|
2530
|
+
if (objectOrArray) {
|
|
2531
|
+
duplicateIndex = state.duplicates.indexOf(object);
|
|
2532
|
+
duplicate = duplicateIndex !== -1;
|
|
2533
|
+
}
|
|
2534
|
+
if (state.tag !== null && state.tag !== "?" || duplicate || state.indent !== 2 && level > 0) {
|
|
2535
|
+
compact = false;
|
|
2536
|
+
}
|
|
2537
|
+
if (duplicate && state.usedDuplicates[duplicateIndex]) {
|
|
2538
|
+
state.dump = "*ref_" + duplicateIndex;
|
|
2539
|
+
} else {
|
|
2540
|
+
if (objectOrArray && duplicate && !state.usedDuplicates[duplicateIndex]) {
|
|
2541
|
+
state.usedDuplicates[duplicateIndex] = true;
|
|
2542
|
+
}
|
|
2543
|
+
if (type2 === "[object Object]") {
|
|
2544
|
+
if (block && Object.keys(state.dump).length !== 0) {
|
|
2545
|
+
writeBlockMapping(state, level, state.dump, compact);
|
|
2546
|
+
if (duplicate) {
|
|
2547
|
+
state.dump = "&ref_" + duplicateIndex + state.dump;
|
|
2548
|
+
}
|
|
2549
|
+
} else {
|
|
2550
|
+
writeFlowMapping(state, level, state.dump);
|
|
2551
|
+
if (duplicate) {
|
|
2552
|
+
state.dump = "&ref_" + duplicateIndex + " " + state.dump;
|
|
2553
|
+
}
|
|
2554
|
+
}
|
|
2555
|
+
} else if (type2 === "[object Array]") {
|
|
2556
|
+
if (block && state.dump.length !== 0) {
|
|
2557
|
+
if (state.noArrayIndent && !isblockseq && level > 0) {
|
|
2558
|
+
writeBlockSequence(state, level - 1, state.dump, compact);
|
|
2559
|
+
} else {
|
|
2560
|
+
writeBlockSequence(state, level, state.dump, compact);
|
|
2561
|
+
}
|
|
2562
|
+
if (duplicate) {
|
|
2563
|
+
state.dump = "&ref_" + duplicateIndex + state.dump;
|
|
2564
|
+
}
|
|
2565
|
+
} else {
|
|
2566
|
+
writeFlowSequence(state, level, state.dump);
|
|
2567
|
+
if (duplicate) {
|
|
2568
|
+
state.dump = "&ref_" + duplicateIndex + " " + state.dump;
|
|
2569
|
+
}
|
|
2570
|
+
}
|
|
2571
|
+
} else if (type2 === "[object String]") {
|
|
2572
|
+
if (state.tag !== "?") {
|
|
2573
|
+
writeScalar(state, state.dump, level, iskey, inblock);
|
|
2574
|
+
}
|
|
2575
|
+
} else if (type2 === "[object Undefined]") {
|
|
2576
|
+
return false;
|
|
2577
|
+
} else {
|
|
2578
|
+
if (state.skipInvalid)
|
|
2579
|
+
return false;
|
|
2580
|
+
throw new exception("unacceptable kind of an object to dump " + type2);
|
|
2581
|
+
}
|
|
2582
|
+
if (state.tag !== null && state.tag !== "?") {
|
|
2583
|
+
tagStr = encodeURI(state.tag[0] === "!" ? state.tag.slice(1) : state.tag).replace(/!/g, "%21");
|
|
2584
|
+
if (state.tag[0] === "!") {
|
|
2585
|
+
tagStr = "!" + tagStr;
|
|
2586
|
+
} else if (tagStr.slice(0, 18) === "tag:yaml.org,2002:") {
|
|
2587
|
+
tagStr = "!!" + tagStr.slice(18);
|
|
2588
|
+
} else {
|
|
2589
|
+
tagStr = "!<" + tagStr + ">";
|
|
2590
|
+
}
|
|
2591
|
+
state.dump = tagStr + " " + state.dump;
|
|
2592
|
+
}
|
|
2593
|
+
}
|
|
2594
|
+
return true;
|
|
2595
|
+
}
|
|
2596
|
+
function getDuplicateReferences(object, state) {
|
|
2597
|
+
var objects = [], duplicatesIndexes = [], index, length;
|
|
2598
|
+
inspectNode(object, objects, duplicatesIndexes);
|
|
2599
|
+
for (index = 0, length = duplicatesIndexes.length;index < length; index += 1) {
|
|
2600
|
+
state.duplicates.push(objects[duplicatesIndexes[index]]);
|
|
2601
|
+
}
|
|
2602
|
+
state.usedDuplicates = new Array(length);
|
|
2603
|
+
}
|
|
2604
|
+
function inspectNode(object, objects, duplicatesIndexes) {
|
|
2605
|
+
var objectKeyList, index, length;
|
|
2606
|
+
if (object !== null && typeof object === "object") {
|
|
2607
|
+
index = objects.indexOf(object);
|
|
2608
|
+
if (index !== -1) {
|
|
2609
|
+
if (duplicatesIndexes.indexOf(index) === -1) {
|
|
2610
|
+
duplicatesIndexes.push(index);
|
|
2611
|
+
}
|
|
2612
|
+
} else {
|
|
2613
|
+
objects.push(object);
|
|
2614
|
+
if (Array.isArray(object)) {
|
|
2615
|
+
for (index = 0, length = object.length;index < length; index += 1) {
|
|
2616
|
+
inspectNode(object[index], objects, duplicatesIndexes);
|
|
2617
|
+
}
|
|
2618
|
+
} else {
|
|
2619
|
+
objectKeyList = Object.keys(object);
|
|
2620
|
+
for (index = 0, length = objectKeyList.length;index < length; index += 1) {
|
|
2621
|
+
inspectNode(object[objectKeyList[index]], objects, duplicatesIndexes);
|
|
2622
|
+
}
|
|
2623
|
+
}
|
|
2624
|
+
}
|
|
2625
|
+
}
|
|
2626
|
+
}
|
|
2627
|
+
function dump$1(input, options) {
|
|
2628
|
+
options = options || {};
|
|
2629
|
+
var state = new State(options);
|
|
2630
|
+
if (!state.noRefs)
|
|
2631
|
+
getDuplicateReferences(input, state);
|
|
2632
|
+
var value = input;
|
|
2633
|
+
if (state.replacer) {
|
|
2634
|
+
value = state.replacer.call({ "": value }, "", value);
|
|
2635
|
+
}
|
|
2636
|
+
if (writeNode(state, 0, value, true, true))
|
|
2637
|
+
return state.dump + `
|
|
2638
|
+
`;
|
|
2639
|
+
return "";
|
|
2640
|
+
}
|
|
2641
|
+
var dump_1 = dump$1;
|
|
2642
|
+
var dumper = {
|
|
2643
|
+
dump: dump_1
|
|
2644
|
+
};
|
|
2645
|
+
function renamed(from, to) {
|
|
2646
|
+
return function() {
|
|
2647
|
+
throw new Error("Function yaml." + from + " is removed in js-yaml 4. " + "Use yaml." + to + " instead, which is now safe by default.");
|
|
2648
|
+
};
|
|
2649
|
+
}
|
|
2650
|
+
var Type = type;
|
|
2651
|
+
var Schema = schema;
|
|
2652
|
+
var FAILSAFE_SCHEMA = failsafe;
|
|
2653
|
+
var JSON_SCHEMA = json;
|
|
2654
|
+
var CORE_SCHEMA = core;
|
|
2655
|
+
var DEFAULT_SCHEMA = _default;
|
|
2656
|
+
var load = loader.load;
|
|
2657
|
+
var loadAll = loader.loadAll;
|
|
2658
|
+
var dump = dumper.dump;
|
|
2659
|
+
var YAMLException = exception;
|
|
2660
|
+
var types = {
|
|
2661
|
+
binary,
|
|
2662
|
+
float,
|
|
2663
|
+
map,
|
|
2664
|
+
null: _null,
|
|
2665
|
+
pairs,
|
|
2666
|
+
set,
|
|
2667
|
+
timestamp,
|
|
2668
|
+
bool,
|
|
2669
|
+
int,
|
|
2670
|
+
merge,
|
|
2671
|
+
omap,
|
|
2672
|
+
seq,
|
|
2673
|
+
str
|
|
2674
|
+
};
|
|
2675
|
+
var safeLoad = renamed("safeLoad", "load");
|
|
2676
|
+
var safeLoadAll = renamed("safeLoadAll", "loadAll");
|
|
2677
|
+
var safeDump = renamed("safeDump", "dump");
|
|
2678
|
+
var jsYaml = {
|
|
2679
|
+
Type,
|
|
2680
|
+
Schema,
|
|
2681
|
+
FAILSAFE_SCHEMA,
|
|
2682
|
+
JSON_SCHEMA,
|
|
2683
|
+
CORE_SCHEMA,
|
|
2684
|
+
DEFAULT_SCHEMA,
|
|
2685
|
+
load,
|
|
2686
|
+
loadAll,
|
|
2687
|
+
dump,
|
|
2688
|
+
YAMLException,
|
|
2689
|
+
types,
|
|
2690
|
+
safeLoad,
|
|
2691
|
+
safeLoadAll,
|
|
2692
|
+
safeDump
|
|
2693
|
+
};
|
|
2694
|
+
|
|
2695
|
+
// src/tui.ts
|
|
2696
|
+
var MANIFOLD_AGENTS = ["clerk", "senior-dev", "junior-dev", "debug"];
|
|
2697
|
+
async function getManifoldAgents(directory) {
|
|
2698
|
+
const agentsDir = join(directory, ".opencode", "agents");
|
|
2699
|
+
if (!existsSync(agentsDir)) {
|
|
2700
|
+
return [];
|
|
2701
|
+
}
|
|
2702
|
+
const files = await readdir(agentsDir);
|
|
2703
|
+
const agents = [];
|
|
2704
|
+
for (const file of files) {
|
|
2705
|
+
if (!file.endsWith(".md"))
|
|
2706
|
+
continue;
|
|
2707
|
+
const name = file.replace(".md", "");
|
|
2708
|
+
if (MANIFOLD_AGENTS.includes(name)) {
|
|
2709
|
+
agents.push(name);
|
|
2710
|
+
}
|
|
2711
|
+
}
|
|
2712
|
+
return agents;
|
|
2713
|
+
}
|
|
2714
|
+
async function readAgentFile(agentName, directory) {
|
|
2715
|
+
const agentPath = join(directory, ".opencode", "agents", `${agentName}.md`);
|
|
2716
|
+
if (!existsSync(agentPath)) {
|
|
2717
|
+
const globalPath = join(homedir(), ".config", "opencode", "manifold", "agents", `${agentName}.md`);
|
|
2718
|
+
if (existsSync(globalPath)) {
|
|
2719
|
+
const content2 = await readFile(globalPath, "utf-8");
|
|
2720
|
+
const { frontmatter: frontmatter2, body: body2 } = parseFrontmatter(content2);
|
|
2721
|
+
return { content: content2, frontmatter: frontmatter2, body: body2 };
|
|
2722
|
+
}
|
|
2723
|
+
throw new Error(`Agent file not found for ${agentName}`);
|
|
2724
|
+
}
|
|
2725
|
+
const content = await readFile(agentPath, "utf-8");
|
|
2726
|
+
const { frontmatter, body } = parseFrontmatter(content);
|
|
2727
|
+
return { content, frontmatter, body };
|
|
2728
|
+
}
|
|
2729
|
+
function parseFrontmatter(content) {
|
|
2730
|
+
const match = content.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
|
|
2731
|
+
if (!match) {
|
|
2732
|
+
return { frontmatter: {}, body: content };
|
|
2733
|
+
}
|
|
2734
|
+
const frontmatterYaml = match[1];
|
|
2735
|
+
const body = match[2];
|
|
2736
|
+
const frontmatter = jsYaml.load(frontmatterYaml) || {};
|
|
2737
|
+
return { frontmatter, body };
|
|
2738
|
+
}
|
|
2739
|
+
function buildAgentFile(frontmatter, body) {
|
|
2740
|
+
const yamlContent = jsYaml.dump(frontmatter, {
|
|
2741
|
+
lineWidth: -1,
|
|
2742
|
+
noCompatMode: true
|
|
2743
|
+
});
|
|
2744
|
+
return `---
|
|
2745
|
+
${yamlContent}---
|
|
2746
|
+
${body}`;
|
|
2747
|
+
}
|
|
2748
|
+
async function updateAgentModel(agentName, modelId, directory) {
|
|
2749
|
+
const { frontmatter, body } = await readAgentFile(agentName, directory);
|
|
2750
|
+
frontmatter.model = modelId;
|
|
2751
|
+
const newContent = buildAgentFile(frontmatter, body);
|
|
2752
|
+
const agentPath = join(directory, ".opencode", "agents", `${agentName}.md`);
|
|
2753
|
+
await writeFile(agentPath, newContent);
|
|
2754
|
+
}
|
|
2755
|
+
var tui = async (api) => {
|
|
2756
|
+
api.command.register(() => [
|
|
2757
|
+
{
|
|
2758
|
+
title: "Manifold Models",
|
|
2759
|
+
value: "manifold-models",
|
|
2760
|
+
description: "Set the model for a Manifold sub-agent",
|
|
2761
|
+
category: "Manifold",
|
|
2762
|
+
slash: {
|
|
2763
|
+
name: "manifold-models"
|
|
2764
|
+
}
|
|
2765
|
+
},
|
|
2766
|
+
{
|
|
2767
|
+
title: "Manifold Update",
|
|
2768
|
+
value: "manifold-update",
|
|
2769
|
+
description: "Clear opencode-manifold plugin cache and prompt for restart",
|
|
2770
|
+
category: "Manifold",
|
|
2771
|
+
slash: {
|
|
2772
|
+
name: "manifold-update"
|
|
2773
|
+
}
|
|
2774
|
+
}
|
|
2775
|
+
]);
|
|
2776
|
+
api.event.on("tui.command.execute", async (event) => {
|
|
2777
|
+
if (event.properties.command === "manifold-models") {
|
|
2778
|
+
await handleManifoldModels(api);
|
|
2779
|
+
} else if (event.properties.command === "manifold-update") {
|
|
2780
|
+
await handleManifoldUpdate(api);
|
|
2781
|
+
}
|
|
2782
|
+
});
|
|
2783
|
+
};
|
|
2784
|
+
async function handleManifoldModels(api) {
|
|
2785
|
+
const directory = api.state.path.directory;
|
|
2786
|
+
const agents = await getManifoldAgents(directory);
|
|
2787
|
+
if (agents.length === 0) {
|
|
2788
|
+
api.ui.toast({
|
|
2789
|
+
variant: "error",
|
|
2790
|
+
message: "No Manifold agents found. Run /manifold-init first."
|
|
2791
|
+
});
|
|
2792
|
+
return;
|
|
2793
|
+
}
|
|
2794
|
+
const providers = api.state.provider;
|
|
2795
|
+
const models = [];
|
|
2796
|
+
for (const provider of providers) {
|
|
2797
|
+
if (provider.models) {
|
|
2798
|
+
for (const [modelId, model] of Object.entries(provider.models)) {
|
|
2799
|
+
models.push({
|
|
2800
|
+
id: `${provider.id}/${modelId}`,
|
|
2801
|
+
name: model.name || modelId,
|
|
2802
|
+
providerID: provider.id
|
|
2803
|
+
});
|
|
2804
|
+
}
|
|
2805
|
+
}
|
|
2806
|
+
}
|
|
2807
|
+
if (models.length === 0) {
|
|
2808
|
+
api.ui.toast({
|
|
2809
|
+
variant: "error",
|
|
2810
|
+
message: "No models available. Configure providers first."
|
|
2811
|
+
});
|
|
2812
|
+
return;
|
|
2813
|
+
}
|
|
2814
|
+
models.sort((a, b) => a.name.localeCompare(b.name));
|
|
2815
|
+
const agentOptions = agents.map((agent) => ({
|
|
2816
|
+
title: `${agent} (sub-agent)`,
|
|
2817
|
+
value: agent,
|
|
2818
|
+
description: `Configure model for ${agent}`
|
|
2819
|
+
}));
|
|
2820
|
+
api.ui.dialog.setSize("medium");
|
|
2821
|
+
return new Promise((resolve) => {
|
|
2822
|
+
api.ui.ui.DialogSelect({
|
|
2823
|
+
title: "Select Manifold Sub-Agent",
|
|
2824
|
+
options: agentOptions,
|
|
2825
|
+
onSelect: async (option) => {
|
|
2826
|
+
const selectedAgent = option.value;
|
|
2827
|
+
const currentModel = await readAgentFile(selectedAgent, directory).then((f) => f.frontmatter.model || "not set").catch(() => "not set");
|
|
2828
|
+
const modelOptions = models.map((model) => ({
|
|
2829
|
+
title: `${model.name}`,
|
|
2830
|
+
value: model.id,
|
|
2831
|
+
description: model.id,
|
|
2832
|
+
footer: model.id === currentModel ? "✓ Current" : undefined
|
|
2833
|
+
}));
|
|
2834
|
+
api.ui.ui.DialogSelect({
|
|
2835
|
+
title: `Select Model for ${selectedAgent}`,
|
|
2836
|
+
options: modelOptions,
|
|
2837
|
+
current: currentModel !== "not set" ? currentModel : undefined,
|
|
2838
|
+
onSelect: async (modelOption) => {
|
|
2839
|
+
const selectedModelId = modelOption.value;
|
|
2840
|
+
await updateAgentModel(selectedAgent, selectedModelId, directory);
|
|
2841
|
+
api.ui.toast({
|
|
2842
|
+
variant: "success",
|
|
2843
|
+
message: `Set ${selectedAgent} to ${selectedModelId}`
|
|
2844
|
+
});
|
|
2845
|
+
resolve();
|
|
2846
|
+
}
|
|
2847
|
+
});
|
|
2848
|
+
}
|
|
2849
|
+
});
|
|
2850
|
+
});
|
|
2851
|
+
}
|
|
2852
|
+
async function handleManifoldUpdate(api) {
|
|
2853
|
+
const directory = api.state.path.directory;
|
|
2854
|
+
const { rm } = await import("fs/promises");
|
|
2855
|
+
const settingsPath = join(directory, "Manifold", "settings.json");
|
|
2856
|
+
let settings = {};
|
|
2857
|
+
if (existsSync(settingsPath)) {
|
|
2858
|
+
const content = await readFile(settingsPath, "utf-8");
|
|
2859
|
+
try {
|
|
2860
|
+
settings = JSON.parse(content);
|
|
2861
|
+
} catch {}
|
|
2862
|
+
}
|
|
2863
|
+
const globalConfigDir = join(homedir(), ".config", "opencode", "manifold");
|
|
2864
|
+
const configuredPaths = settings.updateCachePaths || [];
|
|
2865
|
+
const pathsToClear = [...new Set([...configuredPaths, globalConfigDir])];
|
|
2866
|
+
const resolvedPaths = pathsToClear.map((p) => {
|
|
2867
|
+
const expanded = p.startsWith("~") ? join(homedir(), p.slice(1)) : p;
|
|
2868
|
+
return expanded;
|
|
2869
|
+
});
|
|
2870
|
+
api.ui.dialog.setSize("large");
|
|
2871
|
+
return new Promise((resolve) => {
|
|
2872
|
+
api.ui.ui.DialogSelect({
|
|
2873
|
+
title: "Manifold Plugin Update",
|
|
2874
|
+
options: [
|
|
2875
|
+
{
|
|
2876
|
+
title: "Clear Plugin & Template Cache",
|
|
2877
|
+
value: "confirm",
|
|
2878
|
+
description: `Will clear ${resolvedPaths.length} path(s) including global templates`,
|
|
2879
|
+
footer: resolvedPaths.map((p) => `• ${p}`).join(`
|
|
2880
|
+
`)
|
|
2881
|
+
},
|
|
2882
|
+
{
|
|
2883
|
+
title: "Cancel",
|
|
2884
|
+
value: "cancel",
|
|
2885
|
+
description: "Do not clear cache"
|
|
2886
|
+
}
|
|
2887
|
+
],
|
|
2888
|
+
onSelect: async (option) => {
|
|
2889
|
+
if (option.value === "cancel") {
|
|
2890
|
+
resolve();
|
|
2891
|
+
return;
|
|
2892
|
+
}
|
|
2893
|
+
const cleared = [];
|
|
2894
|
+
const blocked = [];
|
|
2895
|
+
for (const pathStr of resolvedPaths) {
|
|
2896
|
+
try {
|
|
2897
|
+
if (existsSync(pathStr)) {
|
|
2898
|
+
await rm(pathStr, { recursive: true, force: true });
|
|
2899
|
+
cleared.push(pathStr);
|
|
2900
|
+
}
|
|
2901
|
+
} catch (error) {
|
|
2902
|
+
blocked.push({ path: pathStr, reason: `${error}` });
|
|
2903
|
+
}
|
|
2904
|
+
}
|
|
2905
|
+
api.ui.toast({
|
|
2906
|
+
variant: cleared.length > 0 ? "success" : "error",
|
|
2907
|
+
message: cleared.length > 0 ? `Cache cleared. Restart opencode to update.` : `No paths cleared or update blocked`
|
|
2908
|
+
});
|
|
2909
|
+
resolve();
|
|
2910
|
+
}
|
|
2911
|
+
});
|
|
2912
|
+
});
|
|
2913
|
+
}
|
|
2914
|
+
|
|
2915
|
+
// src/init.ts
|
|
2916
|
+
import { readFile as readFile2, writeFile as writeFile2, mkdir, readdir as readdir2 } from "fs/promises";
|
|
2917
|
+
import { existsSync as existsSync2 } from "fs";
|
|
2918
|
+
import { join as join2, dirname } from "path";
|
|
2919
|
+
import { fileURLToPath } from "url";
|
|
2920
|
+
import { homedir as homedir2 } from "os";
|
|
2921
|
+
import { createRequire as createRequire2 } from "module";
|
|
2922
|
+
var __dirname2 = dirname(fileURLToPath(import.meta.url));
|
|
2923
|
+
var require2 = createRequire2(import.meta.url);
|
|
2924
|
+
var bundledTemplatesDir = join2(__dirname2, "..", "src", "templates");
|
|
2925
|
+
var globalTemplatesDir = join2(homedir2(), ".config", "opencode", "manifold");
|
|
2926
|
+
async function getPluginVersion() {
|
|
2927
|
+
try {
|
|
2928
|
+
const packageJson = require2(join2(__dirname2, "..", "package.json"));
|
|
2929
|
+
return packageJson.version || "unknown";
|
|
2930
|
+
} catch {
|
|
2931
|
+
return "unknown";
|
|
2932
|
+
}
|
|
2772
2933
|
}
|
|
2773
|
-
function
|
|
2774
|
-
|
|
2775
|
-
|
|
2776
|
-
|
|
2777
|
-
|
|
2778
|
-
|
|
2779
|
-
}
|
|
2780
|
-
|
|
2934
|
+
async function dirHasContent(dirPath) {
|
|
2935
|
+
if (!existsSync2(dirPath))
|
|
2936
|
+
return false;
|
|
2937
|
+
try {
|
|
2938
|
+
const entries = await readdir2(dirPath);
|
|
2939
|
+
return entries.length > 0;
|
|
2940
|
+
} catch {
|
|
2941
|
+
return false;
|
|
2781
2942
|
}
|
|
2782
|
-
|
|
2783
|
-
|
|
2784
|
-
|
|
2785
|
-
|
|
2786
|
-
|
|
2787
|
-
|
|
2788
|
-
|
|
2789
|
-
|
|
2790
|
-
|
|
2791
|
-
|
|
2792
|
-
if (
|
|
2793
|
-
|
|
2794
|
-
|
|
2795
|
-
|
|
2796
|
-
if (explicitPair) {
|
|
2797
|
-
if (state.dump && CHAR_LINE_FEED === state.dump.charCodeAt(0)) {
|
|
2798
|
-
pairBuffer += "?";
|
|
2799
|
-
} else {
|
|
2800
|
-
pairBuffer += "? ";
|
|
2943
|
+
}
|
|
2944
|
+
async function copyMissingFiles(src, dest) {
|
|
2945
|
+
if (!existsSync2(src))
|
|
2946
|
+
return [];
|
|
2947
|
+
await mkdir(dest, { recursive: true });
|
|
2948
|
+
const copied = [];
|
|
2949
|
+
const entries = await readdir2(src, { withFileTypes: true });
|
|
2950
|
+
for (const entry of entries) {
|
|
2951
|
+
const srcPath = join2(src, entry.name);
|
|
2952
|
+
const destPath = join2(dest, entry.name);
|
|
2953
|
+
if (entry.isDirectory()) {
|
|
2954
|
+
const subCopied = await copyMissingFiles(srcPath, destPath);
|
|
2955
|
+
if (subCopied.length > 0) {
|
|
2956
|
+
copied.push(entry.name);
|
|
2801
2957
|
}
|
|
2958
|
+
} else if (!existsSync2(destPath)) {
|
|
2959
|
+
await writeFile2(destPath, await readFile2(srcPath));
|
|
2960
|
+
copied.push(entry.name);
|
|
2802
2961
|
}
|
|
2803
|
-
|
|
2804
|
-
|
|
2805
|
-
|
|
2806
|
-
|
|
2807
|
-
|
|
2808
|
-
|
|
2809
|
-
|
|
2810
|
-
|
|
2811
|
-
|
|
2962
|
+
}
|
|
2963
|
+
return copied;
|
|
2964
|
+
}
|
|
2965
|
+
async function copyFiles(src, dest) {
|
|
2966
|
+
if (!existsSync2(src))
|
|
2967
|
+
return [];
|
|
2968
|
+
await mkdir(dest, { recursive: true });
|
|
2969
|
+
const copied = [];
|
|
2970
|
+
const entries = await readdir2(src, { withFileTypes: true });
|
|
2971
|
+
for (const entry of entries) {
|
|
2972
|
+
const srcPath = join2(src, entry.name);
|
|
2973
|
+
const destPath = join2(dest, entry.name);
|
|
2974
|
+
if (entry.isDirectory()) {
|
|
2975
|
+
const subCopied = await copyFiles(srcPath, destPath);
|
|
2976
|
+
if (subCopied.length > 0) {
|
|
2977
|
+
copied.push(entry.name);
|
|
2978
|
+
}
|
|
2812
2979
|
} else {
|
|
2813
|
-
|
|
2980
|
+
await writeFile2(destPath, await readFile2(srcPath));
|
|
2981
|
+
copied.push(entry.name);
|
|
2814
2982
|
}
|
|
2815
|
-
pairBuffer += state.dump;
|
|
2816
|
-
_result += pairBuffer;
|
|
2817
2983
|
}
|
|
2818
|
-
|
|
2819
|
-
state.dump = _result || "{}";
|
|
2984
|
+
return copied;
|
|
2820
2985
|
}
|
|
2821
|
-
function
|
|
2822
|
-
|
|
2823
|
-
|
|
2824
|
-
|
|
2825
|
-
|
|
2826
|
-
|
|
2827
|
-
if (explicit) {
|
|
2828
|
-
if (type2.multi && type2.representName) {
|
|
2829
|
-
state.tag = type2.representName(object);
|
|
2830
|
-
} else {
|
|
2831
|
-
state.tag = type2.tag;
|
|
2832
|
-
}
|
|
2833
|
-
} else {
|
|
2834
|
-
state.tag = "?";
|
|
2835
|
-
}
|
|
2836
|
-
if (type2.represent) {
|
|
2837
|
-
style = state.styleMap[type2.tag] || type2.defaultStyle;
|
|
2838
|
-
if (_toString.call(type2.represent) === "[object Function]") {
|
|
2839
|
-
_result = type2.represent(object, style);
|
|
2840
|
-
} else if (_hasOwnProperty.call(type2.represent, style)) {
|
|
2841
|
-
_result = type2.represent[style](object, style);
|
|
2842
|
-
} else {
|
|
2843
|
-
throw new exception("!<" + type2.tag + '> tag resolver accepts not "' + style + '" style');
|
|
2844
|
-
}
|
|
2845
|
-
state.dump = _result;
|
|
2846
|
-
}
|
|
2847
|
-
return true;
|
|
2986
|
+
async function ensureGlobalTemplates(ctx) {
|
|
2987
|
+
await ctx.client.app.log({
|
|
2988
|
+
body: {
|
|
2989
|
+
service: "opencode-manifold",
|
|
2990
|
+
level: "info",
|
|
2991
|
+
message: "Synchronizing global templates..."
|
|
2848
2992
|
}
|
|
2993
|
+
});
|
|
2994
|
+
if (!existsSync2(bundledTemplatesDir)) {
|
|
2995
|
+
await ctx.client.app.log({
|
|
2996
|
+
body: {
|
|
2997
|
+
service: "opencode-manifold",
|
|
2998
|
+
level: "error",
|
|
2999
|
+
message: `Bundled templates not found at ${bundledTemplatesDir}`
|
|
3000
|
+
}
|
|
3001
|
+
});
|
|
3002
|
+
return;
|
|
2849
3003
|
}
|
|
2850
|
-
|
|
3004
|
+
await copyFiles(bundledTemplatesDir, globalTemplatesDir);
|
|
3005
|
+
const globalCommandsDir = join2(homedir2(), ".config", "opencode", "commands");
|
|
3006
|
+
const bundledCommandsDir = join2(bundledTemplatesDir, "commands");
|
|
3007
|
+
if (existsSync2(bundledCommandsDir)) {
|
|
3008
|
+
await copyFiles(bundledCommandsDir, globalCommandsDir);
|
|
3009
|
+
}
|
|
3010
|
+
await ctx.client.app.log({
|
|
3011
|
+
body: {
|
|
3012
|
+
service: "opencode-manifold",
|
|
3013
|
+
level: "info",
|
|
3014
|
+
message: "Global templates synchronized"
|
|
3015
|
+
}
|
|
3016
|
+
});
|
|
2851
3017
|
}
|
|
2852
|
-
function
|
|
2853
|
-
|
|
2854
|
-
|
|
2855
|
-
|
|
2856
|
-
|
|
3018
|
+
async function initProject(directory, client) {
|
|
3019
|
+
const initialized = [];
|
|
3020
|
+
await client.app.log({
|
|
3021
|
+
body: {
|
|
3022
|
+
service: "opencode-manifold",
|
|
3023
|
+
level: "info",
|
|
3024
|
+
message: `Running /manifold-init in ${directory}`
|
|
3025
|
+
}
|
|
3026
|
+
});
|
|
3027
|
+
if (!await dirHasContent(globalTemplatesDir)) {
|
|
3028
|
+
await client.app.log({
|
|
3029
|
+
body: {
|
|
3030
|
+
service: "opencode-manifold",
|
|
3031
|
+
level: "error",
|
|
3032
|
+
message: `Global templates not found at ${globalTemplatesDir}. Plugin may not have loaded correctly.`
|
|
3033
|
+
}
|
|
3034
|
+
});
|
|
3035
|
+
return initialized;
|
|
2857
3036
|
}
|
|
2858
|
-
|
|
2859
|
-
|
|
2860
|
-
|
|
2861
|
-
if (block) {
|
|
2862
|
-
block = state.flowLevel < 0 || state.flowLevel > level;
|
|
3037
|
+
const agentsCopied = await copyMissingFiles(join2(globalTemplatesDir, "agents"), join2(directory, ".opencode", "agents"));
|
|
3038
|
+
if (agentsCopied.length > 0) {
|
|
3039
|
+
initialized.push(`agents (${agentsCopied.join(", ")})`);
|
|
2863
3040
|
}
|
|
2864
|
-
|
|
2865
|
-
if (
|
|
2866
|
-
|
|
2867
|
-
duplicate = duplicateIndex !== -1;
|
|
3041
|
+
const skillsCopied = await copyMissingFiles(join2(globalTemplatesDir, "skills"), join2(directory, ".opencode", "skills"));
|
|
3042
|
+
if (skillsCopied.length > 0) {
|
|
3043
|
+
initialized.push(`skills (${skillsCopied.join(", ")})`);
|
|
2868
3044
|
}
|
|
2869
|
-
|
|
2870
|
-
|
|
3045
|
+
const manifoldCopied = await copyMissingFiles(join2(globalTemplatesDir, "manifold"), join2(directory, "Manifold"));
|
|
3046
|
+
if (manifoldCopied.length > 0) {
|
|
3047
|
+
initialized.push(`Manifold/ (${manifoldCopied.join(", ")})`);
|
|
2871
3048
|
}
|
|
2872
|
-
|
|
2873
|
-
|
|
2874
|
-
|
|
2875
|
-
|
|
2876
|
-
|
|
3049
|
+
const version = await getPluginVersion();
|
|
3050
|
+
await writeFile2(join2(directory, "Manifold", "VERSION"), version + `
|
|
3051
|
+
`);
|
|
3052
|
+
await client.app.log({
|
|
3053
|
+
body: {
|
|
3054
|
+
service: "opencode-manifold",
|
|
3055
|
+
level: "info",
|
|
3056
|
+
message: `/manifold-init complete: ${initialized.join(", ") || "already initialized"}`
|
|
2877
3057
|
}
|
|
2878
|
-
|
|
2879
|
-
|
|
2880
|
-
|
|
2881
|
-
|
|
2882
|
-
|
|
2883
|
-
|
|
2884
|
-
|
|
2885
|
-
|
|
2886
|
-
|
|
2887
|
-
|
|
2888
|
-
|
|
3058
|
+
});
|
|
3059
|
+
return initialized;
|
|
3060
|
+
}
|
|
3061
|
+
|
|
3062
|
+
// src/tools/dispatch-task.ts
|
|
3063
|
+
import { tool } from "@opencode-ai/plugin";
|
|
3064
|
+
import { readFile as readFile5, writeFile as writeFile5 } from "fs/promises";
|
|
3065
|
+
import { existsSync as existsSync6 } from "fs";
|
|
3066
|
+
import { join as join6 } from "path";
|
|
3067
|
+
|
|
3068
|
+
// src/state-machine.ts
|
|
3069
|
+
import { readFile as readFile4, writeFile as writeFile4, readdir as readdir3 } from "fs/promises";
|
|
3070
|
+
import { existsSync as existsSync5 } from "fs";
|
|
3071
|
+
import { join as join5 } from "path";
|
|
3072
|
+
|
|
3073
|
+
// src/session-spawner.ts
|
|
3074
|
+
async function waitForSessionIdle(client, sessionId, timeoutMs) {
|
|
3075
|
+
const startTime = Date.now();
|
|
3076
|
+
while (Date.now() - startTime < timeoutMs) {
|
|
3077
|
+
const statusResponse = await client.session.status({});
|
|
3078
|
+
const statusData = statusResponse.data;
|
|
3079
|
+
if (statusData && statusData[sessionId]) {
|
|
3080
|
+
const status = statusData[sessionId];
|
|
3081
|
+
if (status.type === "idle") {
|
|
3082
|
+
return true;
|
|
2889
3083
|
}
|
|
2890
|
-
|
|
2891
|
-
|
|
2892
|
-
if (
|
|
2893
|
-
|
|
2894
|
-
} else {
|
|
2895
|
-
writeBlockSequence(state, level, state.dump, compact);
|
|
2896
|
-
}
|
|
2897
|
-
if (duplicate) {
|
|
2898
|
-
state.dump = "&ref_" + duplicateIndex + state.dump;
|
|
2899
|
-
}
|
|
2900
|
-
} else {
|
|
2901
|
-
writeFlowSequence(state, level, state.dump);
|
|
2902
|
-
if (duplicate) {
|
|
2903
|
-
state.dump = "&ref_" + duplicateIndex + " " + state.dump;
|
|
3084
|
+
if (status.type === "retry") {
|
|
3085
|
+
const waitTime = status.next - Date.now();
|
|
3086
|
+
if (waitTime > 0) {
|
|
3087
|
+
await new Promise((resolve) => setTimeout(resolve, Math.min(waitTime, 1000)));
|
|
2904
3088
|
}
|
|
3089
|
+
continue;
|
|
2905
3090
|
}
|
|
2906
|
-
} else if (type2 === "[object String]") {
|
|
2907
|
-
if (state.tag !== "?") {
|
|
2908
|
-
writeScalar(state, state.dump, level, iskey, inblock);
|
|
2909
|
-
}
|
|
2910
|
-
} else if (type2 === "[object Undefined]") {
|
|
2911
|
-
return false;
|
|
2912
|
-
} else {
|
|
2913
|
-
if (state.skipInvalid)
|
|
2914
|
-
return false;
|
|
2915
|
-
throw new exception("unacceptable kind of an object to dump " + type2);
|
|
2916
|
-
}
|
|
2917
|
-
if (state.tag !== null && state.tag !== "?") {
|
|
2918
|
-
tagStr = encodeURI(state.tag[0] === "!" ? state.tag.slice(1) : state.tag).replace(/!/g, "%21");
|
|
2919
|
-
if (state.tag[0] === "!") {
|
|
2920
|
-
tagStr = "!" + tagStr;
|
|
2921
|
-
} else if (tagStr.slice(0, 18) === "tag:yaml.org,2002:") {
|
|
2922
|
-
tagStr = "!!" + tagStr.slice(18);
|
|
2923
|
-
} else {
|
|
2924
|
-
tagStr = "!<" + tagStr + ">";
|
|
2925
|
-
}
|
|
2926
|
-
state.dump = tagStr + " " + state.dump;
|
|
2927
3091
|
}
|
|
3092
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
2928
3093
|
}
|
|
2929
|
-
return
|
|
3094
|
+
return false;
|
|
2930
3095
|
}
|
|
2931
|
-
function
|
|
2932
|
-
|
|
2933
|
-
|
|
2934
|
-
|
|
2935
|
-
|
|
3096
|
+
async function getLastAssistantMessage(client, sessionId) {
|
|
3097
|
+
const messagesResponse = await client.session.messages({
|
|
3098
|
+
path: { id: sessionId }
|
|
3099
|
+
});
|
|
3100
|
+
const messages = messagesResponse.data;
|
|
3101
|
+
if (!messages || !Array.isArray(messages)) {
|
|
3102
|
+
return "";
|
|
2936
3103
|
}
|
|
2937
|
-
|
|
3104
|
+
for (let i2 = messages.length - 1;i2 >= 0; i2--) {
|
|
3105
|
+
const msg = messages[i2];
|
|
3106
|
+
if (msg.info && msg.info.role === "assistant") {
|
|
3107
|
+
if (msg.parts && Array.isArray(msg.parts)) {
|
|
3108
|
+
for (const part of msg.parts) {
|
|
3109
|
+
if (part.type === "text" && part.text) {
|
|
3110
|
+
return part.text;
|
|
3111
|
+
}
|
|
3112
|
+
}
|
|
3113
|
+
}
|
|
3114
|
+
}
|
|
3115
|
+
}
|
|
3116
|
+
return "";
|
|
2938
3117
|
}
|
|
2939
|
-
function
|
|
2940
|
-
|
|
2941
|
-
|
|
2942
|
-
|
|
2943
|
-
|
|
2944
|
-
|
|
2945
|
-
|
|
3118
|
+
async function cleanupSession(client, sessionId) {
|
|
3119
|
+
try {
|
|
3120
|
+
await client.session.delete({ path: { id: sessionId } });
|
|
3121
|
+
} catch {}
|
|
3122
|
+
}
|
|
3123
|
+
async function spawnSession(client, agent, prompt, timeoutSeconds) {
|
|
3124
|
+
const timeoutMs = timeoutSeconds * 1000;
|
|
3125
|
+
try {
|
|
3126
|
+
const createResponse = await client.session.create({});
|
|
3127
|
+
const session = createResponse.data;
|
|
3128
|
+
if (!session || !session.id) {
|
|
3129
|
+
return {
|
|
3130
|
+
content: "",
|
|
3131
|
+
success: false,
|
|
3132
|
+
error: "Failed to create session"
|
|
3133
|
+
};
|
|
3134
|
+
}
|
|
3135
|
+
const sessionId = session.id;
|
|
3136
|
+
await client.app.log({
|
|
3137
|
+
body: {
|
|
3138
|
+
service: "opencode-manifold",
|
|
3139
|
+
level: "info",
|
|
3140
|
+
message: `Created session ${sessionId} for agent ${agent}`
|
|
2946
3141
|
}
|
|
2947
|
-
}
|
|
2948
|
-
|
|
2949
|
-
|
|
2950
|
-
|
|
2951
|
-
|
|
2952
|
-
|
|
2953
|
-
|
|
2954
|
-
|
|
2955
|
-
for (index = 0, length = objectKeyList.length;index < length; index += 1) {
|
|
2956
|
-
inspectNode(object[objectKeyList[index]], objects, duplicatesIndexes);
|
|
3142
|
+
});
|
|
3143
|
+
try {
|
|
3144
|
+
await client.session.promptAsync({
|
|
3145
|
+
path: { id: sessionId },
|
|
3146
|
+
body: {
|
|
3147
|
+
agent,
|
|
3148
|
+
noReply: true,
|
|
3149
|
+
parts: [{ type: "text", text: prompt }]
|
|
2957
3150
|
}
|
|
3151
|
+
});
|
|
3152
|
+
const isIdle = await waitForSessionIdle(client, sessionId, timeoutMs);
|
|
3153
|
+
if (!isIdle) {
|
|
3154
|
+
await client.session.abort({ path: { id: sessionId } });
|
|
3155
|
+
return {
|
|
3156
|
+
content: "",
|
|
3157
|
+
success: false,
|
|
3158
|
+
error: `Session timed out after ${timeoutSeconds}s`
|
|
3159
|
+
};
|
|
2958
3160
|
}
|
|
3161
|
+
const content = await getLastAssistantMessage(client, sessionId);
|
|
3162
|
+
await client.app.log({
|
|
3163
|
+
body: {
|
|
3164
|
+
service: "opencode-manifold",
|
|
3165
|
+
level: "info",
|
|
3166
|
+
message: `Session ${sessionId} completed, content length: ${content.length}`
|
|
3167
|
+
}
|
|
3168
|
+
});
|
|
3169
|
+
return {
|
|
3170
|
+
content,
|
|
3171
|
+
success: true
|
|
3172
|
+
};
|
|
3173
|
+
} finally {
|
|
3174
|
+
await cleanupSession(client, sessionId);
|
|
2959
3175
|
}
|
|
3176
|
+
} catch (error) {
|
|
3177
|
+
return {
|
|
3178
|
+
content: "",
|
|
3179
|
+
success: false,
|
|
3180
|
+
error: error instanceof Error ? error.message : String(error)
|
|
3181
|
+
};
|
|
2960
3182
|
}
|
|
2961
3183
|
}
|
|
2962
|
-
function
|
|
2963
|
-
|
|
2964
|
-
|
|
2965
|
-
|
|
2966
|
-
|
|
2967
|
-
|
|
2968
|
-
|
|
2969
|
-
|
|
3184
|
+
async function spawnClerkSession(client, prompt, agent, timeout) {
|
|
3185
|
+
await client.app.log({
|
|
3186
|
+
body: {
|
|
3187
|
+
service: "opencode-manifold",
|
|
3188
|
+
level: "info",
|
|
3189
|
+
message: `Spawning Clerk session (agent: ${agent}, timeout: ${timeout}s)`
|
|
3190
|
+
}
|
|
3191
|
+
});
|
|
3192
|
+
return spawnSession(client, agent, prompt, timeout);
|
|
3193
|
+
}
|
|
3194
|
+
async function spawnSeniorDevSession(client, prompt, agent, timeout) {
|
|
3195
|
+
await client.app.log({
|
|
3196
|
+
body: {
|
|
3197
|
+
service: "opencode-manifold",
|
|
3198
|
+
level: "info",
|
|
3199
|
+
message: `Spawning Senior Dev session (agent: ${agent}, timeout: ${timeout}s)`
|
|
3200
|
+
}
|
|
3201
|
+
});
|
|
3202
|
+
return spawnSession(client, agent, prompt, timeout);
|
|
3203
|
+
}
|
|
3204
|
+
async function spawnJuniorDevSession(client, prompt, seniorOutput, agent, timeout) {
|
|
3205
|
+
await client.app.log({
|
|
3206
|
+
body: {
|
|
3207
|
+
service: "opencode-manifold",
|
|
3208
|
+
level: "info",
|
|
3209
|
+
message: `Spawning Junior Dev session (agent: ${agent}, timeout: ${timeout}s)`
|
|
3210
|
+
}
|
|
3211
|
+
});
|
|
3212
|
+
const fullPrompt = `${prompt}
|
|
3213
|
+
|
|
3214
|
+
---
|
|
3215
|
+
|
|
3216
|
+
Senior Dev's Implementation:
|
|
3217
|
+
${seniorOutput}`;
|
|
3218
|
+
return spawnSession(client, agent, fullPrompt, timeout);
|
|
3219
|
+
}
|
|
3220
|
+
async function spawnDebugSession(client, prompt, loopHistory, agent, timeout) {
|
|
3221
|
+
await client.app.log({
|
|
3222
|
+
body: {
|
|
3223
|
+
service: "opencode-manifold",
|
|
3224
|
+
level: "info",
|
|
3225
|
+
message: `Spawning Debug session (agent: ${agent}, timeout: ${timeout}s)`
|
|
3226
|
+
}
|
|
3227
|
+
});
|
|
3228
|
+
const fullPrompt = `${prompt}
|
|
3229
|
+
|
|
3230
|
+
---
|
|
3231
|
+
|
|
3232
|
+
Loop History:
|
|
3233
|
+
${loopHistory}`;
|
|
3234
|
+
return spawnSession(client, agent, fullPrompt, timeout);
|
|
3235
|
+
}
|
|
3236
|
+
function parseJuniorFirstWord(response) {
|
|
3237
|
+
const firstWord = response.trim().split(/\s+/)[0].toUpperCase();
|
|
3238
|
+
if (firstWord === "COMPLETE") {
|
|
3239
|
+
return "COMPLETE";
|
|
2970
3240
|
}
|
|
2971
|
-
|
|
2972
|
-
return state.dump + `
|
|
2973
|
-
`;
|
|
2974
|
-
return "";
|
|
3241
|
+
return "QUESTIONS";
|
|
2975
3242
|
}
|
|
2976
|
-
|
|
2977
|
-
|
|
2978
|
-
|
|
2979
|
-
|
|
2980
|
-
|
|
2981
|
-
|
|
2982
|
-
|
|
2983
|
-
|
|
3243
|
+
|
|
3244
|
+
// src/error-handler.ts
|
|
3245
|
+
class ModelCallError extends Error {
|
|
3246
|
+
agent;
|
|
3247
|
+
attempt;
|
|
3248
|
+
constructor(message, agent, attempt) {
|
|
3249
|
+
super(message);
|
|
3250
|
+
this.agent = agent;
|
|
3251
|
+
this.attempt = attempt;
|
|
3252
|
+
this.name = "ModelCallError";
|
|
3253
|
+
}
|
|
3254
|
+
}
|
|
3255
|
+
async function retryWithBackoff(fn, options) {
|
|
3256
|
+
let lastError;
|
|
3257
|
+
for (let attempt = 0;attempt <= options.maxRetries; attempt++) {
|
|
3258
|
+
try {
|
|
3259
|
+
return await fn();
|
|
3260
|
+
} catch (error) {
|
|
3261
|
+
lastError = error instanceof Error ? error : new Error(String(error));
|
|
3262
|
+
if (attempt < options.maxRetries) {
|
|
3263
|
+
options.onRetry?.(attempt + 1, lastError);
|
|
3264
|
+
const delay = Math.pow(2, attempt) * 100;
|
|
3265
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
3266
|
+
}
|
|
3267
|
+
}
|
|
3268
|
+
}
|
|
3269
|
+
throw lastError;
|
|
2984
3270
|
}
|
|
2985
|
-
var Type = type;
|
|
2986
|
-
var Schema = schema;
|
|
2987
|
-
var FAILSAFE_SCHEMA = failsafe;
|
|
2988
|
-
var JSON_SCHEMA = json;
|
|
2989
|
-
var CORE_SCHEMA = core;
|
|
2990
|
-
var DEFAULT_SCHEMA = _default;
|
|
2991
|
-
var load = loader.load;
|
|
2992
|
-
var loadAll = loader.loadAll;
|
|
2993
|
-
var dump = dumper.dump;
|
|
2994
|
-
var YAMLException = exception;
|
|
2995
|
-
var types = {
|
|
2996
|
-
binary,
|
|
2997
|
-
float,
|
|
2998
|
-
map,
|
|
2999
|
-
null: _null,
|
|
3000
|
-
pairs,
|
|
3001
|
-
set,
|
|
3002
|
-
timestamp,
|
|
3003
|
-
bool,
|
|
3004
|
-
int,
|
|
3005
|
-
merge,
|
|
3006
|
-
omap,
|
|
3007
|
-
seq,
|
|
3008
|
-
str
|
|
3009
|
-
};
|
|
3010
|
-
var safeLoad = renamed("safeLoad", "load");
|
|
3011
|
-
var safeLoadAll = renamed("safeLoadAll", "loadAll");
|
|
3012
|
-
var safeDump = renamed("safeDump", "dump");
|
|
3013
|
-
var jsYaml = {
|
|
3014
|
-
Type,
|
|
3015
|
-
Schema,
|
|
3016
|
-
FAILSAFE_SCHEMA,
|
|
3017
|
-
JSON_SCHEMA,
|
|
3018
|
-
CORE_SCHEMA,
|
|
3019
|
-
DEFAULT_SCHEMA,
|
|
3020
|
-
load,
|
|
3021
|
-
loadAll,
|
|
3022
|
-
dump,
|
|
3023
|
-
YAMLException,
|
|
3024
|
-
types,
|
|
3025
|
-
safeLoad,
|
|
3026
|
-
safeLoadAll,
|
|
3027
|
-
safeDump
|
|
3028
|
-
};
|
|
3029
3271
|
|
|
3030
3272
|
// src/graph.ts
|
|
3273
|
+
import { readFile as readFile3, writeFile as writeFile3, mkdir as mkdir2 } from "fs/promises";
|
|
3274
|
+
import { existsSync as existsSync3 } from "fs";
|
|
3275
|
+
import { join as join3, dirname as dirname2 } from "path";
|
|
3031
3276
|
var ENCODE_MAP = { "/": "__SL__", ".": "__DT__" };
|
|
3032
3277
|
var ENCODE_RE = /[/.]/g;
|
|
3033
3278
|
function pathToGraphFilename(filePath) {
|
|
@@ -3137,12 +3382,12 @@ ${tasksSection}
|
|
|
3137
3382
|
}
|
|
3138
3383
|
async function readGraphFile(directory, filePath) {
|
|
3139
3384
|
const filename = pathToGraphFilename(filePath);
|
|
3140
|
-
const graphPath =
|
|
3141
|
-
if (!
|
|
3385
|
+
const graphPath = join3(directory, "Manifold", "graph", filename);
|
|
3386
|
+
if (!existsSync3(graphPath)) {
|
|
3142
3387
|
return null;
|
|
3143
3388
|
}
|
|
3144
3389
|
try {
|
|
3145
|
-
const content = await
|
|
3390
|
+
const content = await readFile3(graphPath, "utf-8");
|
|
3146
3391
|
return parseGraphContent(content);
|
|
3147
3392
|
} catch {
|
|
3148
3393
|
return null;
|
|
@@ -3150,9 +3395,9 @@ async function readGraphFile(directory, filePath) {
|
|
|
3150
3395
|
}
|
|
3151
3396
|
async function updateGraphFile(directory, filePath, taskId, calls, dependsOn) {
|
|
3152
3397
|
const filename = pathToGraphFilename(filePath);
|
|
3153
|
-
const graphPath =
|
|
3398
|
+
const graphPath = join3(directory, "Manifold", "graph", filename);
|
|
3154
3399
|
let entry;
|
|
3155
|
-
if (
|
|
3400
|
+
if (existsSync3(graphPath)) {
|
|
3156
3401
|
const existing = await readGraphFile(directory, filePath);
|
|
3157
3402
|
entry = existing || {
|
|
3158
3403
|
filePath,
|
|
@@ -3178,11 +3423,11 @@ async function updateGraphFile(directory, filePath, taskId, calls, dependsOn) {
|
|
|
3178
3423
|
entry.tasksThatEdited.push(taskId);
|
|
3179
3424
|
}
|
|
3180
3425
|
const graphDir = dirname2(graphPath);
|
|
3181
|
-
if (!
|
|
3426
|
+
if (!existsSync3(graphDir)) {
|
|
3182
3427
|
await mkdir2(graphDir, { recursive: true });
|
|
3183
3428
|
}
|
|
3184
3429
|
const content = formatGraphContent(entry);
|
|
3185
|
-
await
|
|
3430
|
+
await writeFile3(graphPath, content, "utf-8");
|
|
3186
3431
|
}
|
|
3187
3432
|
async function updateGraphFilesForTask(directory, taskId, files) {
|
|
3188
3433
|
for (const file of files) {
|
|
@@ -3195,9 +3440,9 @@ async function updateGraphFilesForTask(directory, taskId, files) {
|
|
|
3195
3440
|
}
|
|
3196
3441
|
async function replaceGraphCalls(directory, filePath, calls, dependsOn) {
|
|
3197
3442
|
const filename = pathToGraphFilename(filePath);
|
|
3198
|
-
const graphPath =
|
|
3443
|
+
const graphPath = join3(directory, "Manifold", "graph", filename);
|
|
3199
3444
|
let entry;
|
|
3200
|
-
if (
|
|
3445
|
+
if (existsSync3(graphPath)) {
|
|
3201
3446
|
const existing = await readGraphFile(directory, filePath);
|
|
3202
3447
|
entry = existing || {
|
|
3203
3448
|
filePath,
|
|
@@ -3216,20 +3461,20 @@ async function replaceGraphCalls(directory, filePath, calls, dependsOn) {
|
|
|
3216
3461
|
entry.calls = [...new Set(calls)];
|
|
3217
3462
|
entry.dependsOn = [...new Set(dependsOn)];
|
|
3218
3463
|
const graphDir = dirname2(graphPath);
|
|
3219
|
-
if (!
|
|
3464
|
+
if (!existsSync3(graphDir)) {
|
|
3220
3465
|
await mkdir2(graphDir, { recursive: true });
|
|
3221
3466
|
}
|
|
3222
3467
|
const content = formatGraphContent(entry);
|
|
3223
|
-
await
|
|
3468
|
+
await writeFile3(graphPath, content, "utf-8");
|
|
3224
3469
|
}
|
|
3225
3470
|
|
|
3226
3471
|
// src/graph-sync.ts
|
|
3227
3472
|
import Database from "better-sqlite3";
|
|
3228
|
-
import { existsSync as
|
|
3229
|
-
import { join as
|
|
3473
|
+
import { existsSync as existsSync4 } from "fs";
|
|
3474
|
+
import { join as join4 } from "path";
|
|
3230
3475
|
function openIndexDb(projectRoot) {
|
|
3231
|
-
const dbPath =
|
|
3232
|
-
if (!
|
|
3476
|
+
const dbPath = join4(projectRoot, ".opencode", "index", "codebase.db");
|
|
3477
|
+
if (!existsSync4(dbPath)) {
|
|
3233
3478
|
return null;
|
|
3234
3479
|
}
|
|
3235
3480
|
try {
|
|
@@ -3317,9 +3562,9 @@ async function syncGraphFilesFromIndex(projectRoot, files) {
|
|
|
3317
3562
|
|
|
3318
3563
|
// src/state-machine.ts
|
|
3319
3564
|
async function readState(directory) {
|
|
3320
|
-
const statePath =
|
|
3321
|
-
if (
|
|
3322
|
-
const content = await
|
|
3565
|
+
const statePath = join5(directory, "Manifold", "state.json");
|
|
3566
|
+
if (existsSync5(statePath)) {
|
|
3567
|
+
const content = await readFile4(statePath, "utf-8");
|
|
3323
3568
|
return JSON.parse(content);
|
|
3324
3569
|
}
|
|
3325
3570
|
return {
|
|
@@ -3335,8 +3580,8 @@ async function readState(directory) {
|
|
|
3335
3580
|
};
|
|
3336
3581
|
}
|
|
3337
3582
|
async function writeState(directory, state) {
|
|
3338
|
-
const statePath =
|
|
3339
|
-
await
|
|
3583
|
+
const statePath = join5(directory, "Manifold", "state.json");
|
|
3584
|
+
await writeFile4(statePath, JSON.stringify(state, null, 2));
|
|
3340
3585
|
}
|
|
3341
3586
|
function buildLoopHistory(state) {
|
|
3342
3587
|
if (state.loop_history.length === 0) {
|
|
@@ -3348,16 +3593,16 @@ ${entry}`).join(`
|
|
|
3348
3593
|
`);
|
|
3349
3594
|
}
|
|
3350
3595
|
async function readRecentTaskLogs(directory, count) {
|
|
3351
|
-
const tasksDir =
|
|
3352
|
-
if (!
|
|
3596
|
+
const tasksDir = join5(directory, "Manifold", "tasks");
|
|
3597
|
+
if (!existsSync5(tasksDir)) {
|
|
3353
3598
|
return [];
|
|
3354
3599
|
}
|
|
3355
3600
|
try {
|
|
3356
|
-
const files = await
|
|
3601
|
+
const files = await readdir3(tasksDir);
|
|
3357
3602
|
const mdFiles = files.filter((f) => f.endsWith(".md")).sort();
|
|
3358
3603
|
const recentFiles = mdFiles.slice(-count);
|
|
3359
3604
|
const tasks = await Promise.all(recentFiles.map(async (filename) => {
|
|
3360
|
-
const content = await
|
|
3605
|
+
const content = await readFile4(join5(tasksDir, filename), "utf-8");
|
|
3361
3606
|
return { filename, content };
|
|
3362
3607
|
}));
|
|
3363
3608
|
return tasks;
|
|
@@ -4065,9 +4310,9 @@ function setPluginContext(client) {
|
|
|
4065
4310
|
pluginClient = client;
|
|
4066
4311
|
}
|
|
4067
4312
|
async function readSettings(directory) {
|
|
4068
|
-
const settingsPath =
|
|
4069
|
-
if (
|
|
4070
|
-
const content = await
|
|
4313
|
+
const settingsPath = join6(directory, "Manifold", "settings.json");
|
|
4314
|
+
if (existsSync6(settingsPath)) {
|
|
4315
|
+
const content = await readFile5(settingsPath, "utf-8");
|
|
4071
4316
|
return JSON.parse(content);
|
|
4072
4317
|
}
|
|
4073
4318
|
return {
|
|
@@ -4082,10 +4327,10 @@ async function readSettings(directory) {
|
|
|
4082
4327
|
};
|
|
4083
4328
|
}
|
|
4084
4329
|
async function updatePlansRegistry(directory, planFile) {
|
|
4085
|
-
const plansPath =
|
|
4330
|
+
const plansPath = join6(directory, "Manifold", "plans.json");
|
|
4086
4331
|
let plans = {};
|
|
4087
|
-
if (
|
|
4088
|
-
const content = await
|
|
4332
|
+
if (existsSync6(plansPath)) {
|
|
4333
|
+
const content = await readFile5(plansPath, "utf-8");
|
|
4089
4334
|
plans = JSON.parse(content);
|
|
4090
4335
|
}
|
|
4091
4336
|
const planSlug = planFile.replace(/[^a-zA-Z0-9]/g, "-").toLowerCase().substring(0, 30);
|
|
@@ -4099,7 +4344,7 @@ async function updatePlansRegistry(directory, planFile) {
|
|
|
4099
4344
|
};
|
|
4100
4345
|
}
|
|
4101
4346
|
plans[planSlug].task_count++;
|
|
4102
|
-
await
|
|
4347
|
+
await writeFile5(plansPath, JSON.stringify(plans, null, 2));
|
|
4103
4348
|
return plans[planSlug].task_count;
|
|
4104
4349
|
}
|
|
4105
4350
|
function getClient() {
|
|
@@ -4234,5 +4479,6 @@ var ManifoldPlugin = async (ctx) => {
|
|
|
4234
4479
|
};
|
|
4235
4480
|
var server = ManifoldPlugin;
|
|
4236
4481
|
export {
|
|
4482
|
+
tui,
|
|
4237
4483
|
server
|
|
4238
4484
|
};
|