@opencode_weave/weave 0.7.4-preview.1 → 0.7.5
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/agents/tapestry/prompt-composer.d.ts +0 -2
- package/dist/config/loader.d.ts +19 -0
- package/dist/config/schema.d.ts +6 -0
- package/dist/features/builtin-commands/types.d.ts +1 -1
- package/dist/features/health-report.d.ts +7 -0
- package/dist/index.js +1309 -342
- package/dist/shared/index.d.ts +2 -2
- package/dist/shared/log.d.ts +11 -1
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -1,11 +1,814 @@
|
|
|
1
1
|
// src/index.ts
|
|
2
|
-
import { join as
|
|
2
|
+
import { join as join12 } from "path";
|
|
3
3
|
|
|
4
4
|
// src/config/loader.ts
|
|
5
|
-
import { existsSync
|
|
6
|
-
import { join
|
|
7
|
-
import { homedir
|
|
8
|
-
|
|
5
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
6
|
+
import { join } from "node:path";
|
|
7
|
+
import { homedir } from "node:os";
|
|
8
|
+
|
|
9
|
+
// node_modules/jsonc-parser/lib/esm/impl/scanner.js
|
|
10
|
+
function createScanner(text, ignoreTrivia = false) {
|
|
11
|
+
const len = text.length;
|
|
12
|
+
let pos = 0, value = "", tokenOffset = 0, token = 16, lineNumber = 0, lineStartOffset = 0, tokenLineStartOffset = 0, prevTokenLineStartOffset = 0, scanError = 0;
|
|
13
|
+
function scanHexDigits(count, exact) {
|
|
14
|
+
let digits = 0;
|
|
15
|
+
let value2 = 0;
|
|
16
|
+
while (digits < count || !exact) {
|
|
17
|
+
let ch = text.charCodeAt(pos);
|
|
18
|
+
if (ch >= 48 && ch <= 57) {
|
|
19
|
+
value2 = value2 * 16 + ch - 48;
|
|
20
|
+
} else if (ch >= 65 && ch <= 70) {
|
|
21
|
+
value2 = value2 * 16 + ch - 65 + 10;
|
|
22
|
+
} else if (ch >= 97 && ch <= 102) {
|
|
23
|
+
value2 = value2 * 16 + ch - 97 + 10;
|
|
24
|
+
} else {
|
|
25
|
+
break;
|
|
26
|
+
}
|
|
27
|
+
pos++;
|
|
28
|
+
digits++;
|
|
29
|
+
}
|
|
30
|
+
if (digits < count) {
|
|
31
|
+
value2 = -1;
|
|
32
|
+
}
|
|
33
|
+
return value2;
|
|
34
|
+
}
|
|
35
|
+
function setPosition(newPosition) {
|
|
36
|
+
pos = newPosition;
|
|
37
|
+
value = "";
|
|
38
|
+
tokenOffset = 0;
|
|
39
|
+
token = 16;
|
|
40
|
+
scanError = 0;
|
|
41
|
+
}
|
|
42
|
+
function scanNumber() {
|
|
43
|
+
let start = pos;
|
|
44
|
+
if (text.charCodeAt(pos) === 48) {
|
|
45
|
+
pos++;
|
|
46
|
+
} else {
|
|
47
|
+
pos++;
|
|
48
|
+
while (pos < text.length && isDigit(text.charCodeAt(pos))) {
|
|
49
|
+
pos++;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
if (pos < text.length && text.charCodeAt(pos) === 46) {
|
|
53
|
+
pos++;
|
|
54
|
+
if (pos < text.length && isDigit(text.charCodeAt(pos))) {
|
|
55
|
+
pos++;
|
|
56
|
+
while (pos < text.length && isDigit(text.charCodeAt(pos))) {
|
|
57
|
+
pos++;
|
|
58
|
+
}
|
|
59
|
+
} else {
|
|
60
|
+
scanError = 3;
|
|
61
|
+
return text.substring(start, pos);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
let end = pos;
|
|
65
|
+
if (pos < text.length && (text.charCodeAt(pos) === 69 || text.charCodeAt(pos) === 101)) {
|
|
66
|
+
pos++;
|
|
67
|
+
if (pos < text.length && text.charCodeAt(pos) === 43 || text.charCodeAt(pos) === 45) {
|
|
68
|
+
pos++;
|
|
69
|
+
}
|
|
70
|
+
if (pos < text.length && isDigit(text.charCodeAt(pos))) {
|
|
71
|
+
pos++;
|
|
72
|
+
while (pos < text.length && isDigit(text.charCodeAt(pos))) {
|
|
73
|
+
pos++;
|
|
74
|
+
}
|
|
75
|
+
end = pos;
|
|
76
|
+
} else {
|
|
77
|
+
scanError = 3;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return text.substring(start, end);
|
|
81
|
+
}
|
|
82
|
+
function scanString() {
|
|
83
|
+
let result = "", start = pos;
|
|
84
|
+
while (true) {
|
|
85
|
+
if (pos >= len) {
|
|
86
|
+
result += text.substring(start, pos);
|
|
87
|
+
scanError = 2;
|
|
88
|
+
break;
|
|
89
|
+
}
|
|
90
|
+
const ch = text.charCodeAt(pos);
|
|
91
|
+
if (ch === 34) {
|
|
92
|
+
result += text.substring(start, pos);
|
|
93
|
+
pos++;
|
|
94
|
+
break;
|
|
95
|
+
}
|
|
96
|
+
if (ch === 92) {
|
|
97
|
+
result += text.substring(start, pos);
|
|
98
|
+
pos++;
|
|
99
|
+
if (pos >= len) {
|
|
100
|
+
scanError = 2;
|
|
101
|
+
break;
|
|
102
|
+
}
|
|
103
|
+
const ch2 = text.charCodeAt(pos++);
|
|
104
|
+
switch (ch2) {
|
|
105
|
+
case 34:
|
|
106
|
+
result += '"';
|
|
107
|
+
break;
|
|
108
|
+
case 92:
|
|
109
|
+
result += "\\";
|
|
110
|
+
break;
|
|
111
|
+
case 47:
|
|
112
|
+
result += "/";
|
|
113
|
+
break;
|
|
114
|
+
case 98:
|
|
115
|
+
result += "\b";
|
|
116
|
+
break;
|
|
117
|
+
case 102:
|
|
118
|
+
result += "\f";
|
|
119
|
+
break;
|
|
120
|
+
case 110:
|
|
121
|
+
result += `
|
|
122
|
+
`;
|
|
123
|
+
break;
|
|
124
|
+
case 114:
|
|
125
|
+
result += "\r";
|
|
126
|
+
break;
|
|
127
|
+
case 116:
|
|
128
|
+
result += "\t";
|
|
129
|
+
break;
|
|
130
|
+
case 117:
|
|
131
|
+
const ch3 = scanHexDigits(4, true);
|
|
132
|
+
if (ch3 >= 0) {
|
|
133
|
+
result += String.fromCharCode(ch3);
|
|
134
|
+
} else {
|
|
135
|
+
scanError = 4;
|
|
136
|
+
}
|
|
137
|
+
break;
|
|
138
|
+
default:
|
|
139
|
+
scanError = 5;
|
|
140
|
+
}
|
|
141
|
+
start = pos;
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
144
|
+
if (ch >= 0 && ch <= 31) {
|
|
145
|
+
if (isLineBreak(ch)) {
|
|
146
|
+
result += text.substring(start, pos);
|
|
147
|
+
scanError = 2;
|
|
148
|
+
break;
|
|
149
|
+
} else {
|
|
150
|
+
scanError = 6;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
pos++;
|
|
154
|
+
}
|
|
155
|
+
return result;
|
|
156
|
+
}
|
|
157
|
+
function scanNext() {
|
|
158
|
+
value = "";
|
|
159
|
+
scanError = 0;
|
|
160
|
+
tokenOffset = pos;
|
|
161
|
+
lineStartOffset = lineNumber;
|
|
162
|
+
prevTokenLineStartOffset = tokenLineStartOffset;
|
|
163
|
+
if (pos >= len) {
|
|
164
|
+
tokenOffset = len;
|
|
165
|
+
return token = 17;
|
|
166
|
+
}
|
|
167
|
+
let code = text.charCodeAt(pos);
|
|
168
|
+
if (isWhiteSpace(code)) {
|
|
169
|
+
do {
|
|
170
|
+
pos++;
|
|
171
|
+
value += String.fromCharCode(code);
|
|
172
|
+
code = text.charCodeAt(pos);
|
|
173
|
+
} while (isWhiteSpace(code));
|
|
174
|
+
return token = 15;
|
|
175
|
+
}
|
|
176
|
+
if (isLineBreak(code)) {
|
|
177
|
+
pos++;
|
|
178
|
+
value += String.fromCharCode(code);
|
|
179
|
+
if (code === 13 && text.charCodeAt(pos) === 10) {
|
|
180
|
+
pos++;
|
|
181
|
+
value += `
|
|
182
|
+
`;
|
|
183
|
+
}
|
|
184
|
+
lineNumber++;
|
|
185
|
+
tokenLineStartOffset = pos;
|
|
186
|
+
return token = 14;
|
|
187
|
+
}
|
|
188
|
+
switch (code) {
|
|
189
|
+
case 123:
|
|
190
|
+
pos++;
|
|
191
|
+
return token = 1;
|
|
192
|
+
case 125:
|
|
193
|
+
pos++;
|
|
194
|
+
return token = 2;
|
|
195
|
+
case 91:
|
|
196
|
+
pos++;
|
|
197
|
+
return token = 3;
|
|
198
|
+
case 93:
|
|
199
|
+
pos++;
|
|
200
|
+
return token = 4;
|
|
201
|
+
case 58:
|
|
202
|
+
pos++;
|
|
203
|
+
return token = 6;
|
|
204
|
+
case 44:
|
|
205
|
+
pos++;
|
|
206
|
+
return token = 5;
|
|
207
|
+
case 34:
|
|
208
|
+
pos++;
|
|
209
|
+
value = scanString();
|
|
210
|
+
return token = 10;
|
|
211
|
+
case 47:
|
|
212
|
+
const start = pos - 1;
|
|
213
|
+
if (text.charCodeAt(pos + 1) === 47) {
|
|
214
|
+
pos += 2;
|
|
215
|
+
while (pos < len) {
|
|
216
|
+
if (isLineBreak(text.charCodeAt(pos))) {
|
|
217
|
+
break;
|
|
218
|
+
}
|
|
219
|
+
pos++;
|
|
220
|
+
}
|
|
221
|
+
value = text.substring(start, pos);
|
|
222
|
+
return token = 12;
|
|
223
|
+
}
|
|
224
|
+
if (text.charCodeAt(pos + 1) === 42) {
|
|
225
|
+
pos += 2;
|
|
226
|
+
const safeLength = len - 1;
|
|
227
|
+
let commentClosed = false;
|
|
228
|
+
while (pos < safeLength) {
|
|
229
|
+
const ch = text.charCodeAt(pos);
|
|
230
|
+
if (ch === 42 && text.charCodeAt(pos + 1) === 47) {
|
|
231
|
+
pos += 2;
|
|
232
|
+
commentClosed = true;
|
|
233
|
+
break;
|
|
234
|
+
}
|
|
235
|
+
pos++;
|
|
236
|
+
if (isLineBreak(ch)) {
|
|
237
|
+
if (ch === 13 && text.charCodeAt(pos) === 10) {
|
|
238
|
+
pos++;
|
|
239
|
+
}
|
|
240
|
+
lineNumber++;
|
|
241
|
+
tokenLineStartOffset = pos;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
if (!commentClosed) {
|
|
245
|
+
pos++;
|
|
246
|
+
scanError = 1;
|
|
247
|
+
}
|
|
248
|
+
value = text.substring(start, pos);
|
|
249
|
+
return token = 13;
|
|
250
|
+
}
|
|
251
|
+
value += String.fromCharCode(code);
|
|
252
|
+
pos++;
|
|
253
|
+
return token = 16;
|
|
254
|
+
case 45:
|
|
255
|
+
value += String.fromCharCode(code);
|
|
256
|
+
pos++;
|
|
257
|
+
if (pos === len || !isDigit(text.charCodeAt(pos))) {
|
|
258
|
+
return token = 16;
|
|
259
|
+
}
|
|
260
|
+
case 48:
|
|
261
|
+
case 49:
|
|
262
|
+
case 50:
|
|
263
|
+
case 51:
|
|
264
|
+
case 52:
|
|
265
|
+
case 53:
|
|
266
|
+
case 54:
|
|
267
|
+
case 55:
|
|
268
|
+
case 56:
|
|
269
|
+
case 57:
|
|
270
|
+
value += scanNumber();
|
|
271
|
+
return token = 11;
|
|
272
|
+
default:
|
|
273
|
+
while (pos < len && isUnknownContentCharacter(code)) {
|
|
274
|
+
pos++;
|
|
275
|
+
code = text.charCodeAt(pos);
|
|
276
|
+
}
|
|
277
|
+
if (tokenOffset !== pos) {
|
|
278
|
+
value = text.substring(tokenOffset, pos);
|
|
279
|
+
switch (value) {
|
|
280
|
+
case "true":
|
|
281
|
+
return token = 8;
|
|
282
|
+
case "false":
|
|
283
|
+
return token = 9;
|
|
284
|
+
case "null":
|
|
285
|
+
return token = 7;
|
|
286
|
+
}
|
|
287
|
+
return token = 16;
|
|
288
|
+
}
|
|
289
|
+
value += String.fromCharCode(code);
|
|
290
|
+
pos++;
|
|
291
|
+
return token = 16;
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
function isUnknownContentCharacter(code) {
|
|
295
|
+
if (isWhiteSpace(code) || isLineBreak(code)) {
|
|
296
|
+
return false;
|
|
297
|
+
}
|
|
298
|
+
switch (code) {
|
|
299
|
+
case 125:
|
|
300
|
+
case 93:
|
|
301
|
+
case 123:
|
|
302
|
+
case 91:
|
|
303
|
+
case 34:
|
|
304
|
+
case 58:
|
|
305
|
+
case 44:
|
|
306
|
+
case 47:
|
|
307
|
+
return false;
|
|
308
|
+
}
|
|
309
|
+
return true;
|
|
310
|
+
}
|
|
311
|
+
function scanNextNonTrivia() {
|
|
312
|
+
let result;
|
|
313
|
+
do {
|
|
314
|
+
result = scanNext();
|
|
315
|
+
} while (result >= 12 && result <= 15);
|
|
316
|
+
return result;
|
|
317
|
+
}
|
|
318
|
+
return {
|
|
319
|
+
setPosition,
|
|
320
|
+
getPosition: () => pos,
|
|
321
|
+
scan: ignoreTrivia ? scanNextNonTrivia : scanNext,
|
|
322
|
+
getToken: () => token,
|
|
323
|
+
getTokenValue: () => value,
|
|
324
|
+
getTokenOffset: () => tokenOffset,
|
|
325
|
+
getTokenLength: () => pos - tokenOffset,
|
|
326
|
+
getTokenStartLine: () => lineStartOffset,
|
|
327
|
+
getTokenStartCharacter: () => tokenOffset - prevTokenLineStartOffset,
|
|
328
|
+
getTokenError: () => scanError
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
function isWhiteSpace(ch) {
|
|
332
|
+
return ch === 32 || ch === 9;
|
|
333
|
+
}
|
|
334
|
+
function isLineBreak(ch) {
|
|
335
|
+
return ch === 10 || ch === 13;
|
|
336
|
+
}
|
|
337
|
+
function isDigit(ch) {
|
|
338
|
+
return ch >= 48 && ch <= 57;
|
|
339
|
+
}
|
|
340
|
+
var CharacterCodes;
|
|
341
|
+
(function(CharacterCodes2) {
|
|
342
|
+
CharacterCodes2[CharacterCodes2["lineFeed"] = 10] = "lineFeed";
|
|
343
|
+
CharacterCodes2[CharacterCodes2["carriageReturn"] = 13] = "carriageReturn";
|
|
344
|
+
CharacterCodes2[CharacterCodes2["space"] = 32] = "space";
|
|
345
|
+
CharacterCodes2[CharacterCodes2["_0"] = 48] = "_0";
|
|
346
|
+
CharacterCodes2[CharacterCodes2["_1"] = 49] = "_1";
|
|
347
|
+
CharacterCodes2[CharacterCodes2["_2"] = 50] = "_2";
|
|
348
|
+
CharacterCodes2[CharacterCodes2["_3"] = 51] = "_3";
|
|
349
|
+
CharacterCodes2[CharacterCodes2["_4"] = 52] = "_4";
|
|
350
|
+
CharacterCodes2[CharacterCodes2["_5"] = 53] = "_5";
|
|
351
|
+
CharacterCodes2[CharacterCodes2["_6"] = 54] = "_6";
|
|
352
|
+
CharacterCodes2[CharacterCodes2["_7"] = 55] = "_7";
|
|
353
|
+
CharacterCodes2[CharacterCodes2["_8"] = 56] = "_8";
|
|
354
|
+
CharacterCodes2[CharacterCodes2["_9"] = 57] = "_9";
|
|
355
|
+
CharacterCodes2[CharacterCodes2["a"] = 97] = "a";
|
|
356
|
+
CharacterCodes2[CharacterCodes2["b"] = 98] = "b";
|
|
357
|
+
CharacterCodes2[CharacterCodes2["c"] = 99] = "c";
|
|
358
|
+
CharacterCodes2[CharacterCodes2["d"] = 100] = "d";
|
|
359
|
+
CharacterCodes2[CharacterCodes2["e"] = 101] = "e";
|
|
360
|
+
CharacterCodes2[CharacterCodes2["f"] = 102] = "f";
|
|
361
|
+
CharacterCodes2[CharacterCodes2["g"] = 103] = "g";
|
|
362
|
+
CharacterCodes2[CharacterCodes2["h"] = 104] = "h";
|
|
363
|
+
CharacterCodes2[CharacterCodes2["i"] = 105] = "i";
|
|
364
|
+
CharacterCodes2[CharacterCodes2["j"] = 106] = "j";
|
|
365
|
+
CharacterCodes2[CharacterCodes2["k"] = 107] = "k";
|
|
366
|
+
CharacterCodes2[CharacterCodes2["l"] = 108] = "l";
|
|
367
|
+
CharacterCodes2[CharacterCodes2["m"] = 109] = "m";
|
|
368
|
+
CharacterCodes2[CharacterCodes2["n"] = 110] = "n";
|
|
369
|
+
CharacterCodes2[CharacterCodes2["o"] = 111] = "o";
|
|
370
|
+
CharacterCodes2[CharacterCodes2["p"] = 112] = "p";
|
|
371
|
+
CharacterCodes2[CharacterCodes2["q"] = 113] = "q";
|
|
372
|
+
CharacterCodes2[CharacterCodes2["r"] = 114] = "r";
|
|
373
|
+
CharacterCodes2[CharacterCodes2["s"] = 115] = "s";
|
|
374
|
+
CharacterCodes2[CharacterCodes2["t"] = 116] = "t";
|
|
375
|
+
CharacterCodes2[CharacterCodes2["u"] = 117] = "u";
|
|
376
|
+
CharacterCodes2[CharacterCodes2["v"] = 118] = "v";
|
|
377
|
+
CharacterCodes2[CharacterCodes2["w"] = 119] = "w";
|
|
378
|
+
CharacterCodes2[CharacterCodes2["x"] = 120] = "x";
|
|
379
|
+
CharacterCodes2[CharacterCodes2["y"] = 121] = "y";
|
|
380
|
+
CharacterCodes2[CharacterCodes2["z"] = 122] = "z";
|
|
381
|
+
CharacterCodes2[CharacterCodes2["A"] = 65] = "A";
|
|
382
|
+
CharacterCodes2[CharacterCodes2["B"] = 66] = "B";
|
|
383
|
+
CharacterCodes2[CharacterCodes2["C"] = 67] = "C";
|
|
384
|
+
CharacterCodes2[CharacterCodes2["D"] = 68] = "D";
|
|
385
|
+
CharacterCodes2[CharacterCodes2["E"] = 69] = "E";
|
|
386
|
+
CharacterCodes2[CharacterCodes2["F"] = 70] = "F";
|
|
387
|
+
CharacterCodes2[CharacterCodes2["G"] = 71] = "G";
|
|
388
|
+
CharacterCodes2[CharacterCodes2["H"] = 72] = "H";
|
|
389
|
+
CharacterCodes2[CharacterCodes2["I"] = 73] = "I";
|
|
390
|
+
CharacterCodes2[CharacterCodes2["J"] = 74] = "J";
|
|
391
|
+
CharacterCodes2[CharacterCodes2["K"] = 75] = "K";
|
|
392
|
+
CharacterCodes2[CharacterCodes2["L"] = 76] = "L";
|
|
393
|
+
CharacterCodes2[CharacterCodes2["M"] = 77] = "M";
|
|
394
|
+
CharacterCodes2[CharacterCodes2["N"] = 78] = "N";
|
|
395
|
+
CharacterCodes2[CharacterCodes2["O"] = 79] = "O";
|
|
396
|
+
CharacterCodes2[CharacterCodes2["P"] = 80] = "P";
|
|
397
|
+
CharacterCodes2[CharacterCodes2["Q"] = 81] = "Q";
|
|
398
|
+
CharacterCodes2[CharacterCodes2["R"] = 82] = "R";
|
|
399
|
+
CharacterCodes2[CharacterCodes2["S"] = 83] = "S";
|
|
400
|
+
CharacterCodes2[CharacterCodes2["T"] = 84] = "T";
|
|
401
|
+
CharacterCodes2[CharacterCodes2["U"] = 85] = "U";
|
|
402
|
+
CharacterCodes2[CharacterCodes2["V"] = 86] = "V";
|
|
403
|
+
CharacterCodes2[CharacterCodes2["W"] = 87] = "W";
|
|
404
|
+
CharacterCodes2[CharacterCodes2["X"] = 88] = "X";
|
|
405
|
+
CharacterCodes2[CharacterCodes2["Y"] = 89] = "Y";
|
|
406
|
+
CharacterCodes2[CharacterCodes2["Z"] = 90] = "Z";
|
|
407
|
+
CharacterCodes2[CharacterCodes2["asterisk"] = 42] = "asterisk";
|
|
408
|
+
CharacterCodes2[CharacterCodes2["backslash"] = 92] = "backslash";
|
|
409
|
+
CharacterCodes2[CharacterCodes2["closeBrace"] = 125] = "closeBrace";
|
|
410
|
+
CharacterCodes2[CharacterCodes2["closeBracket"] = 93] = "closeBracket";
|
|
411
|
+
CharacterCodes2[CharacterCodes2["colon"] = 58] = "colon";
|
|
412
|
+
CharacterCodes2[CharacterCodes2["comma"] = 44] = "comma";
|
|
413
|
+
CharacterCodes2[CharacterCodes2["dot"] = 46] = "dot";
|
|
414
|
+
CharacterCodes2[CharacterCodes2["doubleQuote"] = 34] = "doubleQuote";
|
|
415
|
+
CharacterCodes2[CharacterCodes2["minus"] = 45] = "minus";
|
|
416
|
+
CharacterCodes2[CharacterCodes2["openBrace"] = 123] = "openBrace";
|
|
417
|
+
CharacterCodes2[CharacterCodes2["openBracket"] = 91] = "openBracket";
|
|
418
|
+
CharacterCodes2[CharacterCodes2["plus"] = 43] = "plus";
|
|
419
|
+
CharacterCodes2[CharacterCodes2["slash"] = 47] = "slash";
|
|
420
|
+
CharacterCodes2[CharacterCodes2["formFeed"] = 12] = "formFeed";
|
|
421
|
+
CharacterCodes2[CharacterCodes2["tab"] = 9] = "tab";
|
|
422
|
+
})(CharacterCodes || (CharacterCodes = {}));
|
|
423
|
+
|
|
424
|
+
// node_modules/jsonc-parser/lib/esm/impl/string-intern.js
|
|
425
|
+
var cachedSpaces = new Array(20).fill(0).map((_, index) => {
|
|
426
|
+
return " ".repeat(index);
|
|
427
|
+
});
|
|
428
|
+
var maxCachedValues = 200;
|
|
429
|
+
var cachedBreakLinesWithSpaces = {
|
|
430
|
+
" ": {
|
|
431
|
+
"\n": new Array(maxCachedValues).fill(0).map((_, index) => {
|
|
432
|
+
return `
|
|
433
|
+
` + " ".repeat(index);
|
|
434
|
+
}),
|
|
435
|
+
"\r": new Array(maxCachedValues).fill(0).map((_, index) => {
|
|
436
|
+
return "\r" + " ".repeat(index);
|
|
437
|
+
}),
|
|
438
|
+
"\r\n": new Array(maxCachedValues).fill(0).map((_, index) => {
|
|
439
|
+
return `\r
|
|
440
|
+
` + " ".repeat(index);
|
|
441
|
+
})
|
|
442
|
+
},
|
|
443
|
+
"\t": {
|
|
444
|
+
"\n": new Array(maxCachedValues).fill(0).map((_, index) => {
|
|
445
|
+
return `
|
|
446
|
+
` + "\t".repeat(index);
|
|
447
|
+
}),
|
|
448
|
+
"\r": new Array(maxCachedValues).fill(0).map((_, index) => {
|
|
449
|
+
return "\r" + "\t".repeat(index);
|
|
450
|
+
}),
|
|
451
|
+
"\r\n": new Array(maxCachedValues).fill(0).map((_, index) => {
|
|
452
|
+
return `\r
|
|
453
|
+
` + "\t".repeat(index);
|
|
454
|
+
})
|
|
455
|
+
}
|
|
456
|
+
};
|
|
457
|
+
|
|
458
|
+
// node_modules/jsonc-parser/lib/esm/impl/parser.js
|
|
459
|
+
var ParseOptions;
|
|
460
|
+
(function(ParseOptions2) {
|
|
461
|
+
ParseOptions2.DEFAULT = {
|
|
462
|
+
allowTrailingComma: false
|
|
463
|
+
};
|
|
464
|
+
})(ParseOptions || (ParseOptions = {}));
|
|
465
|
+
function parse(text, errors = [], options = ParseOptions.DEFAULT) {
|
|
466
|
+
let currentProperty = null;
|
|
467
|
+
let currentParent = [];
|
|
468
|
+
const previousParents = [];
|
|
469
|
+
function onValue(value) {
|
|
470
|
+
if (Array.isArray(currentParent)) {
|
|
471
|
+
currentParent.push(value);
|
|
472
|
+
} else if (currentProperty !== null) {
|
|
473
|
+
currentParent[currentProperty] = value;
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
const visitor = {
|
|
477
|
+
onObjectBegin: () => {
|
|
478
|
+
const object = {};
|
|
479
|
+
onValue(object);
|
|
480
|
+
previousParents.push(currentParent);
|
|
481
|
+
currentParent = object;
|
|
482
|
+
currentProperty = null;
|
|
483
|
+
},
|
|
484
|
+
onObjectProperty: (name) => {
|
|
485
|
+
currentProperty = name;
|
|
486
|
+
},
|
|
487
|
+
onObjectEnd: () => {
|
|
488
|
+
currentParent = previousParents.pop();
|
|
489
|
+
},
|
|
490
|
+
onArrayBegin: () => {
|
|
491
|
+
const array = [];
|
|
492
|
+
onValue(array);
|
|
493
|
+
previousParents.push(currentParent);
|
|
494
|
+
currentParent = array;
|
|
495
|
+
currentProperty = null;
|
|
496
|
+
},
|
|
497
|
+
onArrayEnd: () => {
|
|
498
|
+
currentParent = previousParents.pop();
|
|
499
|
+
},
|
|
500
|
+
onLiteralValue: onValue,
|
|
501
|
+
onError: (error, offset, length) => {
|
|
502
|
+
errors.push({ error, offset, length });
|
|
503
|
+
}
|
|
504
|
+
};
|
|
505
|
+
visit(text, visitor, options);
|
|
506
|
+
return currentParent[0];
|
|
507
|
+
}
|
|
508
|
+
function visit(text, visitor, options = ParseOptions.DEFAULT) {
|
|
509
|
+
const _scanner = createScanner(text, false);
|
|
510
|
+
const _jsonPath = [];
|
|
511
|
+
let suppressedCallbacks = 0;
|
|
512
|
+
function toNoArgVisit(visitFunction) {
|
|
513
|
+
return visitFunction ? () => suppressedCallbacks === 0 && visitFunction(_scanner.getTokenOffset(), _scanner.getTokenLength(), _scanner.getTokenStartLine(), _scanner.getTokenStartCharacter()) : () => true;
|
|
514
|
+
}
|
|
515
|
+
function toOneArgVisit(visitFunction) {
|
|
516
|
+
return visitFunction ? (arg) => suppressedCallbacks === 0 && visitFunction(arg, _scanner.getTokenOffset(), _scanner.getTokenLength(), _scanner.getTokenStartLine(), _scanner.getTokenStartCharacter()) : () => true;
|
|
517
|
+
}
|
|
518
|
+
function toOneArgVisitWithPath(visitFunction) {
|
|
519
|
+
return visitFunction ? (arg) => suppressedCallbacks === 0 && visitFunction(arg, _scanner.getTokenOffset(), _scanner.getTokenLength(), _scanner.getTokenStartLine(), _scanner.getTokenStartCharacter(), () => _jsonPath.slice()) : () => true;
|
|
520
|
+
}
|
|
521
|
+
function toBeginVisit(visitFunction) {
|
|
522
|
+
return visitFunction ? () => {
|
|
523
|
+
if (suppressedCallbacks > 0) {
|
|
524
|
+
suppressedCallbacks++;
|
|
525
|
+
} else {
|
|
526
|
+
let cbReturn = visitFunction(_scanner.getTokenOffset(), _scanner.getTokenLength(), _scanner.getTokenStartLine(), _scanner.getTokenStartCharacter(), () => _jsonPath.slice());
|
|
527
|
+
if (cbReturn === false) {
|
|
528
|
+
suppressedCallbacks = 1;
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
} : () => true;
|
|
532
|
+
}
|
|
533
|
+
function toEndVisit(visitFunction) {
|
|
534
|
+
return visitFunction ? () => {
|
|
535
|
+
if (suppressedCallbacks > 0) {
|
|
536
|
+
suppressedCallbacks--;
|
|
537
|
+
}
|
|
538
|
+
if (suppressedCallbacks === 0) {
|
|
539
|
+
visitFunction(_scanner.getTokenOffset(), _scanner.getTokenLength(), _scanner.getTokenStartLine(), _scanner.getTokenStartCharacter());
|
|
540
|
+
}
|
|
541
|
+
} : () => true;
|
|
542
|
+
}
|
|
543
|
+
const onObjectBegin = toBeginVisit(visitor.onObjectBegin), onObjectProperty = toOneArgVisitWithPath(visitor.onObjectProperty), onObjectEnd = toEndVisit(visitor.onObjectEnd), onArrayBegin = toBeginVisit(visitor.onArrayBegin), onArrayEnd = toEndVisit(visitor.onArrayEnd), onLiteralValue = toOneArgVisitWithPath(visitor.onLiteralValue), onSeparator = toOneArgVisit(visitor.onSeparator), onComment = toNoArgVisit(visitor.onComment), onError = toOneArgVisit(visitor.onError);
|
|
544
|
+
const disallowComments = options && options.disallowComments;
|
|
545
|
+
const allowTrailingComma = options && options.allowTrailingComma;
|
|
546
|
+
function scanNext() {
|
|
547
|
+
while (true) {
|
|
548
|
+
const token = _scanner.scan();
|
|
549
|
+
switch (_scanner.getTokenError()) {
|
|
550
|
+
case 4:
|
|
551
|
+
handleError(14);
|
|
552
|
+
break;
|
|
553
|
+
case 5:
|
|
554
|
+
handleError(15);
|
|
555
|
+
break;
|
|
556
|
+
case 3:
|
|
557
|
+
handleError(13);
|
|
558
|
+
break;
|
|
559
|
+
case 1:
|
|
560
|
+
if (!disallowComments) {
|
|
561
|
+
handleError(11);
|
|
562
|
+
}
|
|
563
|
+
break;
|
|
564
|
+
case 2:
|
|
565
|
+
handleError(12);
|
|
566
|
+
break;
|
|
567
|
+
case 6:
|
|
568
|
+
handleError(16);
|
|
569
|
+
break;
|
|
570
|
+
}
|
|
571
|
+
switch (token) {
|
|
572
|
+
case 12:
|
|
573
|
+
case 13:
|
|
574
|
+
if (disallowComments) {
|
|
575
|
+
handleError(10);
|
|
576
|
+
} else {
|
|
577
|
+
onComment();
|
|
578
|
+
}
|
|
579
|
+
break;
|
|
580
|
+
case 16:
|
|
581
|
+
handleError(1);
|
|
582
|
+
break;
|
|
583
|
+
case 15:
|
|
584
|
+
case 14:
|
|
585
|
+
break;
|
|
586
|
+
default:
|
|
587
|
+
return token;
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
function handleError(error, skipUntilAfter = [], skipUntil = []) {
|
|
592
|
+
onError(error);
|
|
593
|
+
if (skipUntilAfter.length + skipUntil.length > 0) {
|
|
594
|
+
let token = _scanner.getToken();
|
|
595
|
+
while (token !== 17) {
|
|
596
|
+
if (skipUntilAfter.indexOf(token) !== -1) {
|
|
597
|
+
scanNext();
|
|
598
|
+
break;
|
|
599
|
+
} else if (skipUntil.indexOf(token) !== -1) {
|
|
600
|
+
break;
|
|
601
|
+
}
|
|
602
|
+
token = scanNext();
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
function parseString(isValue) {
|
|
607
|
+
const value = _scanner.getTokenValue();
|
|
608
|
+
if (isValue) {
|
|
609
|
+
onLiteralValue(value);
|
|
610
|
+
} else {
|
|
611
|
+
onObjectProperty(value);
|
|
612
|
+
_jsonPath.push(value);
|
|
613
|
+
}
|
|
614
|
+
scanNext();
|
|
615
|
+
return true;
|
|
616
|
+
}
|
|
617
|
+
function parseLiteral() {
|
|
618
|
+
switch (_scanner.getToken()) {
|
|
619
|
+
case 11:
|
|
620
|
+
const tokenValue = _scanner.getTokenValue();
|
|
621
|
+
let value = Number(tokenValue);
|
|
622
|
+
if (isNaN(value)) {
|
|
623
|
+
handleError(2);
|
|
624
|
+
value = 0;
|
|
625
|
+
}
|
|
626
|
+
onLiteralValue(value);
|
|
627
|
+
break;
|
|
628
|
+
case 7:
|
|
629
|
+
onLiteralValue(null);
|
|
630
|
+
break;
|
|
631
|
+
case 8:
|
|
632
|
+
onLiteralValue(true);
|
|
633
|
+
break;
|
|
634
|
+
case 9:
|
|
635
|
+
onLiteralValue(false);
|
|
636
|
+
break;
|
|
637
|
+
default:
|
|
638
|
+
return false;
|
|
639
|
+
}
|
|
640
|
+
scanNext();
|
|
641
|
+
return true;
|
|
642
|
+
}
|
|
643
|
+
function parseProperty() {
|
|
644
|
+
if (_scanner.getToken() !== 10) {
|
|
645
|
+
handleError(3, [], [2, 5]);
|
|
646
|
+
return false;
|
|
647
|
+
}
|
|
648
|
+
parseString(false);
|
|
649
|
+
if (_scanner.getToken() === 6) {
|
|
650
|
+
onSeparator(":");
|
|
651
|
+
scanNext();
|
|
652
|
+
if (!parseValue()) {
|
|
653
|
+
handleError(4, [], [2, 5]);
|
|
654
|
+
}
|
|
655
|
+
} else {
|
|
656
|
+
handleError(5, [], [2, 5]);
|
|
657
|
+
}
|
|
658
|
+
_jsonPath.pop();
|
|
659
|
+
return true;
|
|
660
|
+
}
|
|
661
|
+
function parseObject() {
|
|
662
|
+
onObjectBegin();
|
|
663
|
+
scanNext();
|
|
664
|
+
let needsComma = false;
|
|
665
|
+
while (_scanner.getToken() !== 2 && _scanner.getToken() !== 17) {
|
|
666
|
+
if (_scanner.getToken() === 5) {
|
|
667
|
+
if (!needsComma) {
|
|
668
|
+
handleError(4, [], []);
|
|
669
|
+
}
|
|
670
|
+
onSeparator(",");
|
|
671
|
+
scanNext();
|
|
672
|
+
if (_scanner.getToken() === 2 && allowTrailingComma) {
|
|
673
|
+
break;
|
|
674
|
+
}
|
|
675
|
+
} else if (needsComma) {
|
|
676
|
+
handleError(6, [], []);
|
|
677
|
+
}
|
|
678
|
+
if (!parseProperty()) {
|
|
679
|
+
handleError(4, [], [2, 5]);
|
|
680
|
+
}
|
|
681
|
+
needsComma = true;
|
|
682
|
+
}
|
|
683
|
+
onObjectEnd();
|
|
684
|
+
if (_scanner.getToken() !== 2) {
|
|
685
|
+
handleError(7, [2], []);
|
|
686
|
+
} else {
|
|
687
|
+
scanNext();
|
|
688
|
+
}
|
|
689
|
+
return true;
|
|
690
|
+
}
|
|
691
|
+
function parseArray() {
|
|
692
|
+
onArrayBegin();
|
|
693
|
+
scanNext();
|
|
694
|
+
let isFirstElement = true;
|
|
695
|
+
let needsComma = false;
|
|
696
|
+
while (_scanner.getToken() !== 4 && _scanner.getToken() !== 17) {
|
|
697
|
+
if (_scanner.getToken() === 5) {
|
|
698
|
+
if (!needsComma) {
|
|
699
|
+
handleError(4, [], []);
|
|
700
|
+
}
|
|
701
|
+
onSeparator(",");
|
|
702
|
+
scanNext();
|
|
703
|
+
if (_scanner.getToken() === 4 && allowTrailingComma) {
|
|
704
|
+
break;
|
|
705
|
+
}
|
|
706
|
+
} else if (needsComma) {
|
|
707
|
+
handleError(6, [], []);
|
|
708
|
+
}
|
|
709
|
+
if (isFirstElement) {
|
|
710
|
+
_jsonPath.push(0);
|
|
711
|
+
isFirstElement = false;
|
|
712
|
+
} else {
|
|
713
|
+
_jsonPath[_jsonPath.length - 1]++;
|
|
714
|
+
}
|
|
715
|
+
if (!parseValue()) {
|
|
716
|
+
handleError(4, [], [4, 5]);
|
|
717
|
+
}
|
|
718
|
+
needsComma = true;
|
|
719
|
+
}
|
|
720
|
+
onArrayEnd();
|
|
721
|
+
if (!isFirstElement) {
|
|
722
|
+
_jsonPath.pop();
|
|
723
|
+
}
|
|
724
|
+
if (_scanner.getToken() !== 4) {
|
|
725
|
+
handleError(8, [4], []);
|
|
726
|
+
} else {
|
|
727
|
+
scanNext();
|
|
728
|
+
}
|
|
729
|
+
return true;
|
|
730
|
+
}
|
|
731
|
+
function parseValue() {
|
|
732
|
+
switch (_scanner.getToken()) {
|
|
733
|
+
case 3:
|
|
734
|
+
return parseArray();
|
|
735
|
+
case 1:
|
|
736
|
+
return parseObject();
|
|
737
|
+
case 10:
|
|
738
|
+
return parseString(true);
|
|
739
|
+
default:
|
|
740
|
+
return parseLiteral();
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
scanNext();
|
|
744
|
+
if (_scanner.getToken() === 17) {
|
|
745
|
+
if (options.allowEmptyContent) {
|
|
746
|
+
return true;
|
|
747
|
+
}
|
|
748
|
+
handleError(4, [], []);
|
|
749
|
+
return false;
|
|
750
|
+
}
|
|
751
|
+
if (!parseValue()) {
|
|
752
|
+
handleError(4, [], []);
|
|
753
|
+
return false;
|
|
754
|
+
}
|
|
755
|
+
if (_scanner.getToken() !== 17) {
|
|
756
|
+
handleError(9, [], []);
|
|
757
|
+
}
|
|
758
|
+
return true;
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
// node_modules/jsonc-parser/lib/esm/main.js
|
|
762
|
+
var ScanError;
|
|
763
|
+
(function(ScanError2) {
|
|
764
|
+
ScanError2[ScanError2["None"] = 0] = "None";
|
|
765
|
+
ScanError2[ScanError2["UnexpectedEndOfComment"] = 1] = "UnexpectedEndOfComment";
|
|
766
|
+
ScanError2[ScanError2["UnexpectedEndOfString"] = 2] = "UnexpectedEndOfString";
|
|
767
|
+
ScanError2[ScanError2["UnexpectedEndOfNumber"] = 3] = "UnexpectedEndOfNumber";
|
|
768
|
+
ScanError2[ScanError2["InvalidUnicode"] = 4] = "InvalidUnicode";
|
|
769
|
+
ScanError2[ScanError2["InvalidEscapeCharacter"] = 5] = "InvalidEscapeCharacter";
|
|
770
|
+
ScanError2[ScanError2["InvalidCharacter"] = 6] = "InvalidCharacter";
|
|
771
|
+
})(ScanError || (ScanError = {}));
|
|
772
|
+
var SyntaxKind;
|
|
773
|
+
(function(SyntaxKind2) {
|
|
774
|
+
SyntaxKind2[SyntaxKind2["OpenBraceToken"] = 1] = "OpenBraceToken";
|
|
775
|
+
SyntaxKind2[SyntaxKind2["CloseBraceToken"] = 2] = "CloseBraceToken";
|
|
776
|
+
SyntaxKind2[SyntaxKind2["OpenBracketToken"] = 3] = "OpenBracketToken";
|
|
777
|
+
SyntaxKind2[SyntaxKind2["CloseBracketToken"] = 4] = "CloseBracketToken";
|
|
778
|
+
SyntaxKind2[SyntaxKind2["CommaToken"] = 5] = "CommaToken";
|
|
779
|
+
SyntaxKind2[SyntaxKind2["ColonToken"] = 6] = "ColonToken";
|
|
780
|
+
SyntaxKind2[SyntaxKind2["NullKeyword"] = 7] = "NullKeyword";
|
|
781
|
+
SyntaxKind2[SyntaxKind2["TrueKeyword"] = 8] = "TrueKeyword";
|
|
782
|
+
SyntaxKind2[SyntaxKind2["FalseKeyword"] = 9] = "FalseKeyword";
|
|
783
|
+
SyntaxKind2[SyntaxKind2["StringLiteral"] = 10] = "StringLiteral";
|
|
784
|
+
SyntaxKind2[SyntaxKind2["NumericLiteral"] = 11] = "NumericLiteral";
|
|
785
|
+
SyntaxKind2[SyntaxKind2["LineCommentTrivia"] = 12] = "LineCommentTrivia";
|
|
786
|
+
SyntaxKind2[SyntaxKind2["BlockCommentTrivia"] = 13] = "BlockCommentTrivia";
|
|
787
|
+
SyntaxKind2[SyntaxKind2["LineBreakTrivia"] = 14] = "LineBreakTrivia";
|
|
788
|
+
SyntaxKind2[SyntaxKind2["Trivia"] = 15] = "Trivia";
|
|
789
|
+
SyntaxKind2[SyntaxKind2["Unknown"] = 16] = "Unknown";
|
|
790
|
+
SyntaxKind2[SyntaxKind2["EOF"] = 17] = "EOF";
|
|
791
|
+
})(SyntaxKind || (SyntaxKind = {}));
|
|
792
|
+
var parse2 = parse;
|
|
793
|
+
var ParseErrorCode;
|
|
794
|
+
(function(ParseErrorCode2) {
|
|
795
|
+
ParseErrorCode2[ParseErrorCode2["InvalidSymbol"] = 1] = "InvalidSymbol";
|
|
796
|
+
ParseErrorCode2[ParseErrorCode2["InvalidNumberFormat"] = 2] = "InvalidNumberFormat";
|
|
797
|
+
ParseErrorCode2[ParseErrorCode2["PropertyNameExpected"] = 3] = "PropertyNameExpected";
|
|
798
|
+
ParseErrorCode2[ParseErrorCode2["ValueExpected"] = 4] = "ValueExpected";
|
|
799
|
+
ParseErrorCode2[ParseErrorCode2["ColonExpected"] = 5] = "ColonExpected";
|
|
800
|
+
ParseErrorCode2[ParseErrorCode2["CommaExpected"] = 6] = "CommaExpected";
|
|
801
|
+
ParseErrorCode2[ParseErrorCode2["CloseBraceExpected"] = 7] = "CloseBraceExpected";
|
|
802
|
+
ParseErrorCode2[ParseErrorCode2["CloseBracketExpected"] = 8] = "CloseBracketExpected";
|
|
803
|
+
ParseErrorCode2[ParseErrorCode2["EndOfFileExpected"] = 9] = "EndOfFileExpected";
|
|
804
|
+
ParseErrorCode2[ParseErrorCode2["InvalidCommentToken"] = 10] = "InvalidCommentToken";
|
|
805
|
+
ParseErrorCode2[ParseErrorCode2["UnexpectedEndOfComment"] = 11] = "UnexpectedEndOfComment";
|
|
806
|
+
ParseErrorCode2[ParseErrorCode2["UnexpectedEndOfString"] = 12] = "UnexpectedEndOfString";
|
|
807
|
+
ParseErrorCode2[ParseErrorCode2["UnexpectedEndOfNumber"] = 13] = "UnexpectedEndOfNumber";
|
|
808
|
+
ParseErrorCode2[ParseErrorCode2["InvalidUnicode"] = 14] = "InvalidUnicode";
|
|
809
|
+
ParseErrorCode2[ParseErrorCode2["InvalidEscapeCharacter"] = 15] = "InvalidEscapeCharacter";
|
|
810
|
+
ParseErrorCode2[ParseErrorCode2["InvalidCharacter"] = 16] = "InvalidCharacter";
|
|
811
|
+
})(ParseErrorCode || (ParseErrorCode = {}));
|
|
9
812
|
|
|
10
813
|
// src/config/schema.ts
|
|
11
814
|
import { z } from "zod";
|
|
@@ -101,7 +904,8 @@ var WeaveConfigSchema = z.object({
|
|
|
101
904
|
analytics: AnalyticsConfigSchema.optional(),
|
|
102
905
|
tmux: TmuxConfigSchema.optional(),
|
|
103
906
|
experimental: ExperimentalConfigSchema.optional(),
|
|
104
|
-
workflows: WorkflowConfigSchema.optional()
|
|
907
|
+
workflows: WorkflowConfigSchema.optional(),
|
|
908
|
+
log_level: z.enum(["DEBUG", "INFO", "WARN", "ERROR"]).optional()
|
|
105
909
|
});
|
|
106
910
|
|
|
107
911
|
// src/config/merge.ts
|
|
@@ -141,30 +945,56 @@ function mergeConfigs(user, project) {
|
|
|
141
945
|
}
|
|
142
946
|
|
|
143
947
|
// src/shared/log.ts
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
948
|
+
var LEVEL_PRIORITY = {
|
|
949
|
+
DEBUG: 0,
|
|
950
|
+
INFO: 1,
|
|
951
|
+
WARN: 2,
|
|
952
|
+
ERROR: 3
|
|
953
|
+
};
|
|
954
|
+
function parseLogLevel(value) {
|
|
955
|
+
if (value === "DEBUG" || value === "INFO" || value === "WARN" || value === "ERROR") {
|
|
956
|
+
return value;
|
|
957
|
+
}
|
|
958
|
+
return "INFO";
|
|
150
959
|
}
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
960
|
+
var activeLevel = parseLogLevel(process.env.WEAVE_LOG_LEVEL);
|
|
961
|
+
var client = null;
|
|
962
|
+
function setClient(c) {
|
|
963
|
+
client = c;
|
|
964
|
+
}
|
|
965
|
+
function setLogLevel(level) {
|
|
966
|
+
activeLevel = level;
|
|
967
|
+
}
|
|
968
|
+
function shouldLog(level) {
|
|
969
|
+
return LEVEL_PRIORITY[level] >= LEVEL_PRIORITY[activeLevel];
|
|
970
|
+
}
|
|
971
|
+
function emit(level, message, data) {
|
|
972
|
+
if (!shouldLog(level))
|
|
973
|
+
return;
|
|
974
|
+
const appRef = client?.app;
|
|
975
|
+
if (appRef && typeof appRef.log === "function") {
|
|
976
|
+
const extra = data !== undefined ? typeof data === "object" && data !== null ? data : { value: data } : undefined;
|
|
977
|
+
appRef.log({ body: { service: "weave", level: level.toLowerCase(), message, extra } }).catch(() => {});
|
|
978
|
+
} else {
|
|
979
|
+
if (level === "ERROR" || level === "WARN") {
|
|
980
|
+
console.error(`[weave:${level}] ${message}`, data ?? "");
|
|
156
981
|
}
|
|
157
|
-
}
|
|
158
|
-
|
|
982
|
+
}
|
|
983
|
+
}
|
|
984
|
+
function debug(message, data) {
|
|
985
|
+
emit("DEBUG", message, data);
|
|
986
|
+
}
|
|
987
|
+
function info(message, data) {
|
|
988
|
+
emit("INFO", message, data);
|
|
989
|
+
}
|
|
990
|
+
function warn(message, data) {
|
|
991
|
+
emit("WARN", message, data);
|
|
992
|
+
}
|
|
993
|
+
function error(message, data) {
|
|
994
|
+
emit("ERROR", message, data);
|
|
159
995
|
}
|
|
160
|
-
var LOG_FILE = resolveLogFile();
|
|
161
996
|
function log(message, data) {
|
|
162
|
-
|
|
163
|
-
const timestamp = new Date().toISOString();
|
|
164
|
-
const entry = `[${timestamp}] ${message}${data !== undefined ? " " + JSON.stringify(data) : ""}
|
|
165
|
-
`;
|
|
166
|
-
fs.appendFileSync(LOG_FILE, entry);
|
|
167
|
-
} catch {}
|
|
997
|
+
info(message, data);
|
|
168
998
|
}
|
|
169
999
|
function logDelegation(event) {
|
|
170
1000
|
const prefix = `[delegation:${event.phase}]`;
|
|
@@ -177,44 +1007,127 @@ function logDelegation(event) {
|
|
|
177
1007
|
}
|
|
178
1008
|
|
|
179
1009
|
// src/config/loader.ts
|
|
1010
|
+
var lastLoadResult = null;
|
|
1011
|
+
function getLastConfigLoadResult() {
|
|
1012
|
+
return lastLoadResult;
|
|
1013
|
+
}
|
|
180
1014
|
function readJsoncFile(filePath) {
|
|
181
1015
|
try {
|
|
182
1016
|
const text = readFileSync(filePath, "utf-8");
|
|
183
1017
|
const errors = [];
|
|
184
|
-
const parsed =
|
|
1018
|
+
const parsed = parse2(text, errors);
|
|
185
1019
|
if (errors.length > 0) {
|
|
186
|
-
|
|
1020
|
+
warn(`JSONC parse warnings in ${filePath}: ${errors.length} issue(s)`);
|
|
187
1021
|
}
|
|
188
1022
|
return parsed ?? {};
|
|
189
1023
|
} catch (e) {
|
|
190
|
-
|
|
1024
|
+
error(`Failed to read config file ${filePath}`, e);
|
|
191
1025
|
return {};
|
|
192
1026
|
}
|
|
193
1027
|
}
|
|
194
1028
|
function detectConfigFile(basePath) {
|
|
195
1029
|
const jsoncPath = basePath + ".jsonc";
|
|
196
|
-
if (
|
|
1030
|
+
if (existsSync(jsoncPath))
|
|
197
1031
|
return jsoncPath;
|
|
198
1032
|
const jsonPath = basePath + ".json";
|
|
199
|
-
if (
|
|
1033
|
+
if (existsSync(jsonPath))
|
|
200
1034
|
return jsonPath;
|
|
201
1035
|
return null;
|
|
202
1036
|
}
|
|
203
1037
|
function loadWeaveConfig(directory, _ctx, _homeDir) {
|
|
204
|
-
const userBasePath =
|
|
205
|
-
const projectBasePath =
|
|
1038
|
+
const userBasePath = join(_homeDir ?? homedir(), ".config", "opencode", "weave-opencode");
|
|
1039
|
+
const projectBasePath = join(directory, ".opencode", "weave-opencode");
|
|
206
1040
|
const userConfigPath = detectConfigFile(userBasePath);
|
|
207
1041
|
const projectConfigPath = detectConfigFile(projectBasePath);
|
|
1042
|
+
debug("Loading Weave config", {
|
|
1043
|
+
userConfig: userConfigPath ?? "(none)",
|
|
1044
|
+
projectConfig: projectConfigPath ?? "(none)"
|
|
1045
|
+
});
|
|
1046
|
+
const loadedFiles = [];
|
|
1047
|
+
if (userConfigPath)
|
|
1048
|
+
loadedFiles.push(userConfigPath);
|
|
1049
|
+
if (projectConfigPath)
|
|
1050
|
+
loadedFiles.push(projectConfigPath);
|
|
208
1051
|
const userRaw = userConfigPath ? readJsoncFile(userConfigPath) : {};
|
|
209
1052
|
const projectRaw = projectConfigPath ? readJsoncFile(projectConfigPath) : {};
|
|
210
1053
|
const merged = mergeConfigs(userRaw, projectRaw);
|
|
211
1054
|
const result = WeaveConfigSchema.safeParse(merged);
|
|
212
1055
|
if (!result.success) {
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
1056
|
+
const recovery = recoverValidSections(merged, result.error.issues);
|
|
1057
|
+
if (recovery) {
|
|
1058
|
+
lastLoadResult = { config: recovery.config, loadedFiles, diagnostics: recovery.diagnostics };
|
|
1059
|
+
return recovery.config;
|
|
1060
|
+
}
|
|
1061
|
+
const diagnostics = [{
|
|
1062
|
+
level: "error",
|
|
1063
|
+
section: "(root)",
|
|
1064
|
+
message: "Config validation failed entirely — using defaults",
|
|
1065
|
+
fields: result.error.issues.map((i) => ({
|
|
1066
|
+
path: i.path.join(".") || "(root)",
|
|
1067
|
+
message: i.message
|
|
1068
|
+
}))
|
|
1069
|
+
}];
|
|
1070
|
+
error("WeaveConfig validation errors — using defaults. Fix the issues below and restart.", result.error.issues.map((i) => ({
|
|
1071
|
+
path: i.path.join(".") || "(root)",
|
|
1072
|
+
message: i.message
|
|
1073
|
+
})));
|
|
1074
|
+
const fallback = WeaveConfigSchema.parse({});
|
|
1075
|
+
lastLoadResult = { config: fallback, loadedFiles, diagnostics };
|
|
1076
|
+
return fallback;
|
|
1077
|
+
}
|
|
1078
|
+
debug("Weave config loaded successfully", {
|
|
1079
|
+
hasAgentOverrides: !!result.data.agents && Object.keys(result.data.agents).length > 0,
|
|
1080
|
+
disabledAgents: result.data.disabled_agents ?? [],
|
|
1081
|
+
customAgents: result.data.custom_agents ? Object.keys(result.data.custom_agents) : [],
|
|
1082
|
+
logLevel: result.data.log_level ?? "(default)",
|
|
1083
|
+
analyticsEnabled: result.data.analytics?.enabled ?? false
|
|
1084
|
+
});
|
|
1085
|
+
lastLoadResult = { config: result.data, loadedFiles, diagnostics: [] };
|
|
216
1086
|
return result.data;
|
|
217
1087
|
}
|
|
1088
|
+
function recoverValidSections(merged, issues) {
|
|
1089
|
+
const failingKeys = new Set;
|
|
1090
|
+
for (const issue of issues) {
|
|
1091
|
+
if (issue.path.length > 0) {
|
|
1092
|
+
failingKeys.add(String(issue.path[0]));
|
|
1093
|
+
}
|
|
1094
|
+
}
|
|
1095
|
+
if (failingKeys.size === 0)
|
|
1096
|
+
return null;
|
|
1097
|
+
const diagnostics = [];
|
|
1098
|
+
for (const key of failingKeys) {
|
|
1099
|
+
const sectionIssues = issues.filter((i) => i.path.length > 0 && String(i.path[0]) === key);
|
|
1100
|
+
const fields = sectionIssues.map((i) => ({
|
|
1101
|
+
path: i.path.slice(1).join("."),
|
|
1102
|
+
message: i.message
|
|
1103
|
+
}));
|
|
1104
|
+
const details = fields.map((f) => f.path ? ` → ${f.path}: ${f.message}` : ` → ${f.message}`);
|
|
1105
|
+
diagnostics.push({
|
|
1106
|
+
level: "warn",
|
|
1107
|
+
section: key,
|
|
1108
|
+
message: `Section "${key}" was dropped due to validation errors`,
|
|
1109
|
+
fields
|
|
1110
|
+
});
|
|
1111
|
+
warn(`Config section "${key}" has validation errors and was dropped:
|
|
1112
|
+
${details.join(`
|
|
1113
|
+
`)}
|
|
1114
|
+
Remaining config sections are preserved. Fix the errors above and restart.`);
|
|
1115
|
+
}
|
|
1116
|
+
const stripped = { ...merged };
|
|
1117
|
+
for (const key of failingKeys) {
|
|
1118
|
+
delete stripped[key];
|
|
1119
|
+
}
|
|
1120
|
+
const retry = WeaveConfigSchema.safeParse(stripped);
|
|
1121
|
+
if (retry.success) {
|
|
1122
|
+
debug("Config recovery succeeded", {
|
|
1123
|
+
droppedSections: [...failingKeys],
|
|
1124
|
+
hasAgentOverrides: !!retry.data.agents && Object.keys(retry.data.agents).length > 0,
|
|
1125
|
+
customAgents: retry.data.custom_agents ? Object.keys(retry.data.custom_agents) : []
|
|
1126
|
+
});
|
|
1127
|
+
return { config: retry.data, diagnostics };
|
|
1128
|
+
}
|
|
1129
|
+
return null;
|
|
1130
|
+
}
|
|
218
1131
|
|
|
219
1132
|
// src/shared/agent-display-names.ts
|
|
220
1133
|
var AGENT_DISPLAY_NAMES = {
|
|
@@ -377,6 +1290,16 @@ ${RUN_WORKFLOW_TEMPLATE}
|
|
|
377
1290
|
<session-context>Session ID: $SESSION_ID Timestamp: $TIMESTAMP</session-context>
|
|
378
1291
|
<user-request>$ARGUMENTS</user-request>`,
|
|
379
1292
|
argumentHint: '<workflow-name> ["goal"]'
|
|
1293
|
+
},
|
|
1294
|
+
"weave-health": {
|
|
1295
|
+
name: "weave-health",
|
|
1296
|
+
description: "Show Weave config health and any validation issues",
|
|
1297
|
+
agent: "loom",
|
|
1298
|
+
template: `<command-instruction>
|
|
1299
|
+
Display the Weave health report below to the user. Present warnings and errors prominently.
|
|
1300
|
+
If there are no issues, confirm that Weave config is healthy.
|
|
1301
|
+
</command-instruction>
|
|
1302
|
+
<weave-health>$ARGUMENTS</weave-health>`
|
|
380
1303
|
}
|
|
381
1304
|
};
|
|
382
1305
|
// src/managers/config-handler.ts
|
|
@@ -510,9 +1433,9 @@ class BackgroundManager {
|
|
|
510
1433
|
}
|
|
511
1434
|
|
|
512
1435
|
// src/managers/skill-mcp-manager.ts
|
|
513
|
-
function createStdioClient(config,
|
|
1436
|
+
function createStdioClient(config, info2) {
|
|
514
1437
|
const { command, args = [] } = config;
|
|
515
|
-
const { serverName, skillName } =
|
|
1438
|
+
const { serverName, skillName } = info2;
|
|
516
1439
|
if (!command) {
|
|
517
1440
|
throw new Error(`missing 'command' field for stdio MCP server '${serverName}' in skill '${skillName}'`);
|
|
518
1441
|
}
|
|
@@ -541,28 +1464,28 @@ function createStdioClient(config, info) {
|
|
|
541
1464
|
}
|
|
542
1465
|
};
|
|
543
1466
|
}
|
|
544
|
-
function getClientKey(
|
|
545
|
-
return `${
|
|
1467
|
+
function getClientKey(info2) {
|
|
1468
|
+
return `${info2.sessionID}:${info2.skillName}:${info2.serverName}`;
|
|
546
1469
|
}
|
|
547
1470
|
|
|
548
1471
|
class SkillMcpManager {
|
|
549
1472
|
clients = new Map;
|
|
550
|
-
async getOrCreateClient(
|
|
551
|
-
const key = getClientKey(
|
|
1473
|
+
async getOrCreateClient(info2, config) {
|
|
1474
|
+
const key = getClientKey(info2);
|
|
552
1475
|
const existing = this.clients.get(key);
|
|
553
1476
|
if (existing) {
|
|
554
1477
|
return existing;
|
|
555
1478
|
}
|
|
556
|
-
const { serverName, skillName } =
|
|
1479
|
+
const { serverName, skillName } = info2;
|
|
557
1480
|
if (config.type === "http") {
|
|
558
1481
|
throw new Error("HTTP MCP not supported in v1");
|
|
559
1482
|
}
|
|
560
1483
|
if (!config.command) {
|
|
561
1484
|
throw new Error(`missing 'command' field for stdio MCP server '${serverName}' in skill '${skillName}'`);
|
|
562
1485
|
}
|
|
563
|
-
const
|
|
564
|
-
this.clients.set(key,
|
|
565
|
-
return
|
|
1486
|
+
const client2 = createStdioClient(config, info2);
|
|
1487
|
+
this.clients.set(key, client2);
|
|
1488
|
+
return client2;
|
|
566
1489
|
}
|
|
567
1490
|
async disconnectSession(sessionID) {
|
|
568
1491
|
const prefix = `${sessionID}:`;
|
|
@@ -573,16 +1496,16 @@ class SkillMcpManager {
|
|
|
573
1496
|
}
|
|
574
1497
|
}
|
|
575
1498
|
await Promise.all(keysToRemove.map(async (key) => {
|
|
576
|
-
const
|
|
577
|
-
if (
|
|
578
|
-
await
|
|
1499
|
+
const client2 = this.clients.get(key);
|
|
1500
|
+
if (client2) {
|
|
1501
|
+
await client2.close();
|
|
579
1502
|
this.clients.delete(key);
|
|
580
1503
|
}
|
|
581
1504
|
}));
|
|
582
1505
|
}
|
|
583
1506
|
async disconnectAll() {
|
|
584
|
-
await Promise.all(Array.from(this.clients.entries()).map(async ([key,
|
|
585
|
-
await
|
|
1507
|
+
await Promise.all(Array.from(this.clients.entries()).map(async ([key, client2]) => {
|
|
1508
|
+
await client2.close();
|
|
586
1509
|
this.clients.delete(key);
|
|
587
1510
|
}));
|
|
588
1511
|
this.clients.clear();
|
|
@@ -590,19 +1513,19 @@ class SkillMcpManager {
|
|
|
590
1513
|
getConnectedServers() {
|
|
591
1514
|
return Array.from(this.clients.keys());
|
|
592
1515
|
}
|
|
593
|
-
isConnected(
|
|
594
|
-
return this.clients.has(getClientKey(
|
|
1516
|
+
isConnected(info2) {
|
|
1517
|
+
return this.clients.has(getClientKey(info2));
|
|
595
1518
|
}
|
|
596
|
-
async callTool(
|
|
1519
|
+
async callTool(info2, config, name, args) {
|
|
597
1520
|
const maxAttempts = 3;
|
|
598
1521
|
let lastError = null;
|
|
599
1522
|
for (let attempt = 1;attempt <= maxAttempts; attempt++) {
|
|
600
1523
|
try {
|
|
601
|
-
const
|
|
602
|
-
const result = await
|
|
1524
|
+
const client2 = await this.getOrCreateClient(info2, config);
|
|
1525
|
+
const result = await client2.callTool({ name, arguments: args });
|
|
603
1526
|
return result.content;
|
|
604
|
-
} catch (
|
|
605
|
-
lastError =
|
|
1527
|
+
} catch (error2) {
|
|
1528
|
+
lastError = error2 instanceof Error ? error2 : new Error(String(error2));
|
|
606
1529
|
if (!lastError.message.toLowerCase().includes("not connected")) {
|
|
607
1530
|
throw lastError;
|
|
608
1531
|
}
|
|
@@ -729,8 +1652,6 @@ function buildDelegationSection(disabled) {
|
|
|
729
1652
|
lines.push("- MUST use Warp for security audits when changes touch auth, crypto, certificates, tokens, signatures, input validation, secrets, passwords, sessions, CORS, CSP, .env files, or OAuth/OIDC/SAML flows — not optional.");
|
|
730
1653
|
}
|
|
731
1654
|
lines.push("- Delegate aggressively to keep your context lean");
|
|
732
|
-
lines.push("");
|
|
733
|
-
lines.push('RATIONALIZATION CHECK: If you catch yourself thinking "this is just a quick fix" but it touches 3+ files — delegate. Quick fixes that grow are the most common failure mode. When in doubt, delegate.');
|
|
734
1655
|
return `<Delegation>
|
|
735
1656
|
${lines.join(`
|
|
736
1657
|
`)}
|
|
@@ -995,33 +1916,6 @@ After completing work for each task — BEFORE marking \`- [ ]\` → \`- [x]\`:
|
|
|
995
1916
|
**Gate**: Only mark complete when ALL checks pass. If ANY check fails, fix first.
|
|
996
1917
|
</Verification>`;
|
|
997
1918
|
}
|
|
998
|
-
function buildTapestryVerificationGateSection() {
|
|
999
|
-
return `<VerificationGate>
|
|
1000
|
-
BEFORE claiming ANY status — "done", "passes", "works", "fixed", "complete":
|
|
1001
|
-
|
|
1002
|
-
1. IDENTIFY: What command proves this claim? (test runner, build, linter, curl, etc.)
|
|
1003
|
-
2. RUN: Execute the command NOW — fresh, complete, in this message
|
|
1004
|
-
3. READ: Check exit code, count failures, read full output
|
|
1005
|
-
4. VERIFY: Does the output confirm the claim?
|
|
1006
|
-
- YES → State the claim WITH the evidence
|
|
1007
|
-
- NO → State actual status with evidence. Fix. Re-run.
|
|
1008
|
-
|
|
1009
|
-
| Claim | Requires | NOT Sufficient |
|
|
1010
|
-
|-------|----------|----------------|
|
|
1011
|
-
| "Tests pass" | Test command output showing 0 failures | Previous run, "should pass", partial suite |
|
|
1012
|
-
| "Build succeeds" | Build command with exit 0 | Linter passing, "looks correct" |
|
|
1013
|
-
| "Bug is fixed" | Failing test now passes | "Code changed, should be fixed" |
|
|
1014
|
-
| "No regressions" | Full test suite output | Spot-checking a few files |
|
|
1015
|
-
|
|
1016
|
-
RED FLAGS — if you catch yourself writing these, STOP:
|
|
1017
|
-
- "should", "probably", "seems to", "looks correct"
|
|
1018
|
-
- "Great!", "Done!", "Perfect!" before running verification
|
|
1019
|
-
- Claiming completion based on a previous run
|
|
1020
|
-
- Trusting your own Edit/Write calls without reading the result
|
|
1021
|
-
|
|
1022
|
-
**Verification you didn't run in this message does not exist.**
|
|
1023
|
-
</VerificationGate>`;
|
|
1024
|
-
}
|
|
1025
1919
|
function buildTapestryPostExecutionReviewSection(disabled) {
|
|
1026
1920
|
const hasWeft = isAgentEnabled("weft", disabled);
|
|
1027
1921
|
const hasWarp = isAgentEnabled("warp", disabled);
|
|
@@ -1067,30 +1961,6 @@ function buildTapestryExecutionSection() {
|
|
|
1067
1961
|
- Report completion with evidence (test output, file paths, commands run)
|
|
1068
1962
|
</Execution>`;
|
|
1069
1963
|
}
|
|
1070
|
-
function buildTapestryDebuggingSection() {
|
|
1071
|
-
return `<WhenStuck>
|
|
1072
|
-
When a task fails or produces unexpected results:
|
|
1073
|
-
|
|
1074
|
-
1. **Read error messages completely** — stack traces, line numbers, exit codes. They often contain the answer.
|
|
1075
|
-
2. **Form a single hypothesis** — "I think X is the root cause because Y." Be specific.
|
|
1076
|
-
3. **Make the smallest possible change** to test that hypothesis. One variable at a time.
|
|
1077
|
-
4. **Verify** — did it work? If yes, continue. If no, form a NEW hypothesis.
|
|
1078
|
-
|
|
1079
|
-
ESCALATION RULE:
|
|
1080
|
-
- Fix attempt #1 failed → re-read errors, try different hypothesis
|
|
1081
|
-
- Fix attempt #2 failed → step back, trace the data flow from source to error
|
|
1082
|
-
- Fix attempt #3 failed → **STOP. Do NOT attempt fix #4.**
|
|
1083
|
-
- Document: what you tried, what happened, what you think the root cause is
|
|
1084
|
-
- Report to the user: "Blocked after 3 attempts on task N. Here's what I've tried: [...]"
|
|
1085
|
-
- This is likely an architectural issue, not a code bug. The user needs to decide.
|
|
1086
|
-
|
|
1087
|
-
RED FLAGS — you are debugging wrong if you:
|
|
1088
|
-
- Propose fixes without reading the error message carefully
|
|
1089
|
-
- Change multiple things at once ("shotgun debugging")
|
|
1090
|
-
- Re-try the same approach hoping for a different result
|
|
1091
|
-
- Think "just one more fix" after 2 failures
|
|
1092
|
-
</WhenStuck>`;
|
|
1093
|
-
}
|
|
1094
1964
|
function buildTapestryStyleSection() {
|
|
1095
1965
|
return `<Style>
|
|
1096
1966
|
- Terse status updates only
|
|
@@ -1106,10 +1976,8 @@ function composeTapestryPrompt(options = {}) {
|
|
|
1106
1976
|
buildTapestrySidebarTodosSection(),
|
|
1107
1977
|
buildTapestryPlanExecutionSection(disabled),
|
|
1108
1978
|
buildTapestryVerificationSection(),
|
|
1109
|
-
buildTapestryVerificationGateSection(),
|
|
1110
1979
|
buildTapestryPostExecutionReviewSection(disabled),
|
|
1111
1980
|
buildTapestryExecutionSection(),
|
|
1112
|
-
buildTapestryDebuggingSection(),
|
|
1113
1981
|
buildTapestryStyleSection()
|
|
1114
1982
|
];
|
|
1115
1983
|
return sections.join(`
|
|
@@ -1270,30 +2138,6 @@ FILES FIELD: For verification-only tasks that have no associated files (e.g., "r
|
|
|
1270
2138
|
- After completing a plan, tell the user: "Plan saved to \`.weave/plans/{name}.md\`. Run /start-work to begin execution."
|
|
1271
2139
|
</Constraints>
|
|
1272
2140
|
|
|
1273
|
-
<NoPlaceholders>
|
|
1274
|
-
Every task must contain the actual detail an engineer needs to start working. These are PLAN FAILURES — never write them:
|
|
1275
|
-
|
|
1276
|
-
- "TBD", "TODO", "implement later", "fill in details"
|
|
1277
|
-
- "Add appropriate error handling" / "add validation" / "handle edge cases"
|
|
1278
|
-
- "Write tests for the above" (without describing what to test)
|
|
1279
|
-
- "Similar to Task N" (repeat the detail — the executor may read tasks independently)
|
|
1280
|
-
- Steps that describe WHAT to do without specifying HOW (file paths, approach, acceptance criteria required)
|
|
1281
|
-
- References to types, functions, or files that aren't defined or explained in any task
|
|
1282
|
-
|
|
1283
|
-
If you can't specify something concretely, you haven't researched enough. Go read more code.
|
|
1284
|
-
</NoPlaceholders>
|
|
1285
|
-
|
|
1286
|
-
<SelfReview>
|
|
1287
|
-
After writing the complete plan, review it with fresh eyes:
|
|
1288
|
-
|
|
1289
|
-
1. **Requirement coverage**: Re-read the original request. Can you point to a task for each requirement? List any gaps.
|
|
1290
|
-
2. **Placeholder scan**: Search your plan for any patterns from the \`<NoPlaceholders>\` list above. Fix them.
|
|
1291
|
-
3. **Name consistency**: Do file paths, function names, and type names used in later tasks match what you defined in earlier tasks? A function called \`createUser()\` in Task 2 but \`addUser()\` in Task 5 is a bug.
|
|
1292
|
-
4. **Dependency order**: Can each task be started after completing only the tasks before it? If Task 4 depends on Task 6, reorder.
|
|
1293
|
-
|
|
1294
|
-
Fix any issues inline. Then report the plan as complete.
|
|
1295
|
-
</SelfReview>
|
|
1296
|
-
|
|
1297
2141
|
<Research>
|
|
1298
2142
|
- Read relevant files before planning
|
|
1299
2143
|
- Check existing patterns in the codebase
|
|
@@ -1422,10 +2266,9 @@ You operate in two modes depending on what you're asked to review:
|
|
|
1422
2266
|
|
|
1423
2267
|
**Work Review** (reviewing completed implementation):
|
|
1424
2268
|
- Read every changed file (use git diff --stat, then Read each file)
|
|
1425
|
-
-
|
|
1426
|
-
-
|
|
1427
|
-
-
|
|
1428
|
-
- Verify tests exist and test real behavior (not mocks of mocks)
|
|
2269
|
+
- Check the code actually does what the task required
|
|
2270
|
+
- Look for stubs, TODOs, placeholders, hardcoded values
|
|
2271
|
+
- Verify tests exist and test real behavior
|
|
1429
2272
|
- Check for scope creep (changes outside the task spec)
|
|
1430
2273
|
</ReviewModes>
|
|
1431
2274
|
|
|
@@ -1733,34 +2576,47 @@ var AGENT_MODEL_REQUIREMENTS = {
|
|
|
1733
2576
|
function resolveAgentModel(agentName, options) {
|
|
1734
2577
|
const { availableModels, agentMode, uiSelectedModel, categoryModel, overrideModel, systemDefaultModel, customFallbackChain } = options;
|
|
1735
2578
|
const requirement = AGENT_MODEL_REQUIREMENTS[agentName];
|
|
1736
|
-
if (overrideModel)
|
|
2579
|
+
if (overrideModel) {
|
|
2580
|
+
debug(`Model resolved for "${agentName}"`, { via: "override", model: overrideModel });
|
|
1737
2581
|
return overrideModel;
|
|
2582
|
+
}
|
|
1738
2583
|
if (uiSelectedModel && (agentMode === "primary" || agentMode === "all")) {
|
|
2584
|
+
debug(`Model resolved for "${agentName}"`, { via: "ui-selection", model: uiSelectedModel, agentMode });
|
|
1739
2585
|
return uiSelectedModel;
|
|
1740
2586
|
}
|
|
1741
|
-
if (categoryModel && availableModels.has(categoryModel))
|
|
2587
|
+
if (categoryModel && availableModels.has(categoryModel)) {
|
|
2588
|
+
debug(`Model resolved for "${agentName}"`, { via: "category", model: categoryModel });
|
|
1742
2589
|
return categoryModel;
|
|
2590
|
+
}
|
|
1743
2591
|
const fallbackChain = requirement?.fallbackChain ?? customFallbackChain;
|
|
1744
2592
|
if (fallbackChain) {
|
|
1745
2593
|
for (const entry of fallbackChain) {
|
|
1746
2594
|
for (const provider of entry.providers) {
|
|
1747
2595
|
const qualified = `${provider}/${entry.model}`;
|
|
1748
|
-
if (availableModels.has(qualified))
|
|
2596
|
+
if (availableModels.has(qualified)) {
|
|
2597
|
+
debug(`Model resolved for "${agentName}"`, { via: "fallback-chain", model: qualified });
|
|
1749
2598
|
return qualified;
|
|
1750
|
-
|
|
2599
|
+
}
|
|
2600
|
+
if (availableModels.has(entry.model)) {
|
|
2601
|
+
debug(`Model resolved for "${agentName}"`, { via: "fallback-chain", model: entry.model });
|
|
1751
2602
|
return entry.model;
|
|
2603
|
+
}
|
|
1752
2604
|
}
|
|
1753
2605
|
}
|
|
1754
2606
|
}
|
|
1755
|
-
if (systemDefaultModel)
|
|
2607
|
+
if (systemDefaultModel) {
|
|
2608
|
+
debug(`Model resolved for "${agentName}"`, { via: "system-default", model: systemDefaultModel });
|
|
1756
2609
|
return systemDefaultModel;
|
|
2610
|
+
}
|
|
1757
2611
|
if (fallbackChain && fallbackChain.length > 0) {
|
|
1758
2612
|
const first = fallbackChain[0];
|
|
1759
2613
|
if (first.providers.length > 0) {
|
|
1760
|
-
|
|
2614
|
+
const guessed = `${first.providers[0]}/${first.model}`;
|
|
2615
|
+
debug(`Model resolved for "${agentName}" (offline best-guess — no available models matched)`, { via: "offline-guess", model: guessed });
|
|
2616
|
+
return guessed;
|
|
1761
2617
|
}
|
|
1762
2618
|
}
|
|
1763
|
-
|
|
2619
|
+
warn(`No model resolved for agent "${agentName}" — falling back to default github-copilot/claude-opus-4.6`, { agentName });
|
|
1764
2620
|
return "github-copilot/claude-opus-4.6";
|
|
1765
2621
|
}
|
|
1766
2622
|
|
|
@@ -1873,8 +2729,10 @@ function createBuiltinAgents(options = {}) {
|
|
|
1873
2729
|
const disabledSet = new Set(disabledAgents);
|
|
1874
2730
|
const result = {};
|
|
1875
2731
|
for (const [name, factory] of Object.entries(AGENT_FACTORIES)) {
|
|
1876
|
-
if (disabledSet.has(name))
|
|
2732
|
+
if (disabledSet.has(name)) {
|
|
2733
|
+
debug(`Builtin agent "${name}" is disabled — skipping`);
|
|
1877
2734
|
continue;
|
|
2735
|
+
}
|
|
1878
2736
|
const override = agentOverrides[name];
|
|
1879
2737
|
const overrideModel = override?.model;
|
|
1880
2738
|
const resolvedModel = resolveAgentModel(name, {
|
|
@@ -1884,6 +2742,9 @@ function createBuiltinAgents(options = {}) {
|
|
|
1884
2742
|
systemDefaultModel,
|
|
1885
2743
|
overrideModel
|
|
1886
2744
|
});
|
|
2745
|
+
if (overrideModel) {
|
|
2746
|
+
debug(`Builtin agent "${name}" model overridden via config`, { model: resolvedModel });
|
|
2747
|
+
}
|
|
1887
2748
|
let built;
|
|
1888
2749
|
if (name === "loom") {
|
|
1889
2750
|
built = createLoomAgentWithOptions(resolvedModel, disabledSet, fingerprint, customAgentMetadata);
|
|
@@ -1921,7 +2782,7 @@ function createBuiltinAgents(options = {}) {
|
|
|
1921
2782
|
}
|
|
1922
2783
|
|
|
1923
2784
|
// src/agents/prompt-loader.ts
|
|
1924
|
-
import { readFileSync as readFileSync2, existsSync as
|
|
2785
|
+
import { readFileSync as readFileSync2, existsSync as existsSync2 } from "fs";
|
|
1925
2786
|
import { resolve, isAbsolute as isAbsolute2, normalize, sep } from "path";
|
|
1926
2787
|
function loadPromptFile(promptFilePath, basePath) {
|
|
1927
2788
|
if (isAbsolute2(promptFilePath)) {
|
|
@@ -1932,7 +2793,7 @@ function loadPromptFile(promptFilePath, basePath) {
|
|
|
1932
2793
|
if (!resolvedPath.startsWith(base + sep) && resolvedPath !== base) {
|
|
1933
2794
|
return null;
|
|
1934
2795
|
}
|
|
1935
|
-
if (!
|
|
2796
|
+
if (!existsSync2(resolvedPath)) {
|
|
1936
2797
|
return null;
|
|
1937
2798
|
}
|
|
1938
2799
|
return readFileSync2(resolvedPath, "utf-8").trim();
|
|
@@ -1968,11 +2829,17 @@ function buildCustomAgent(name, config, options = {}) {
|
|
|
1968
2829
|
}
|
|
1969
2830
|
const { resolveSkills, disabledSkills, availableModels = new Set, systemDefaultModel, uiSelectedModel, configDir } = options;
|
|
1970
2831
|
let prompt = config.prompt ?? "";
|
|
2832
|
+
let promptSource = "inline";
|
|
1971
2833
|
if (config.prompt_file) {
|
|
1972
2834
|
const fileContent = loadPromptFile(config.prompt_file, configDir);
|
|
1973
2835
|
if (fileContent) {
|
|
1974
2836
|
prompt = fileContent;
|
|
2837
|
+
promptSource = `file:${config.prompt_file}`;
|
|
2838
|
+
} else {
|
|
2839
|
+
promptSource = `file:${config.prompt_file} (not found — falling back to inline)`;
|
|
1975
2840
|
}
|
|
2841
|
+
} else if (config.skills?.length) {
|
|
2842
|
+
promptSource = `skills:[${config.skills.join(",")}]`;
|
|
1976
2843
|
}
|
|
1977
2844
|
if (config.skills?.length && resolveSkills) {
|
|
1978
2845
|
const skillContent = resolveSkills(config.skills, disabledSkills);
|
|
@@ -1995,6 +2862,13 @@ function buildCustomAgent(name, config, options = {}) {
|
|
|
1995
2862
|
const displayName = config.display_name ?? name;
|
|
1996
2863
|
registerAgentDisplayName(name, displayName);
|
|
1997
2864
|
registerAgentNameVariants(name, displayName !== name ? [name, displayName] : undefined);
|
|
2865
|
+
debug(`Custom agent "${name}" built`, {
|
|
2866
|
+
model,
|
|
2867
|
+
displayName,
|
|
2868
|
+
mode,
|
|
2869
|
+
promptSource,
|
|
2870
|
+
hasPrompt: !!prompt
|
|
2871
|
+
});
|
|
1998
2872
|
const agentConfig = {
|
|
1999
2873
|
model,
|
|
2000
2874
|
prompt: prompt || undefined,
|
|
@@ -2062,7 +2936,7 @@ function createManagers(options) {
|
|
|
2062
2936
|
}
|
|
2063
2937
|
} catch (err) {
|
|
2064
2938
|
if (err instanceof Error && err.message.includes("not a built-in agent")) {
|
|
2065
|
-
|
|
2939
|
+
debug(`Skipping display_name override for non-builtin agent "${name}"`);
|
|
2066
2940
|
} else {
|
|
2067
2941
|
throw err;
|
|
2068
2942
|
}
|
|
@@ -2095,8 +2969,8 @@ function createManagers(options) {
|
|
|
2095
2969
|
}
|
|
2096
2970
|
|
|
2097
2971
|
// src/features/skill-loader/loader.ts
|
|
2098
|
-
import * as
|
|
2099
|
-
import * as
|
|
2972
|
+
import * as path2 from "path";
|
|
2973
|
+
import * as os from "os";
|
|
2100
2974
|
|
|
2101
2975
|
// src/features/skill-loader/opencode-client.ts
|
|
2102
2976
|
function deriveScope(location) {
|
|
@@ -2111,11 +2985,11 @@ async function fetchSkillsFromOpenCode(serverUrl, directory) {
|
|
|
2111
2985
|
try {
|
|
2112
2986
|
response = await fetch(url, { signal: AbortSignal.timeout(3000) });
|
|
2113
2987
|
} catch (err) {
|
|
2114
|
-
|
|
2988
|
+
error("Failed to fetch skills from OpenCode — skills will not be loaded", { url, error: String(err) });
|
|
2115
2989
|
return [];
|
|
2116
2990
|
}
|
|
2117
2991
|
if (!response.ok) {
|
|
2118
|
-
|
|
2992
|
+
warn("OpenCode /skill endpoint returned non-OK status — skills will not be loaded", {
|
|
2119
2993
|
url,
|
|
2120
2994
|
status: response.status
|
|
2121
2995
|
});
|
|
@@ -2125,11 +2999,11 @@ async function fetchSkillsFromOpenCode(serverUrl, directory) {
|
|
|
2125
2999
|
try {
|
|
2126
3000
|
data = await response.json();
|
|
2127
3001
|
} catch (err) {
|
|
2128
|
-
|
|
3002
|
+
error("Failed to parse skills response from OpenCode", { url, error: String(err) });
|
|
2129
3003
|
return [];
|
|
2130
3004
|
}
|
|
2131
3005
|
if (!Array.isArray(data)) {
|
|
2132
|
-
|
|
3006
|
+
warn("Unexpected skills response shape from OpenCode — expected array", { url });
|
|
2133
3007
|
return [];
|
|
2134
3008
|
}
|
|
2135
3009
|
const skills = [];
|
|
@@ -2148,8 +3022,8 @@ async function fetchSkillsFromOpenCode(serverUrl, directory) {
|
|
|
2148
3022
|
}
|
|
2149
3023
|
|
|
2150
3024
|
// src/features/skill-loader/discovery.ts
|
|
2151
|
-
import * as
|
|
2152
|
-
import * as
|
|
3025
|
+
import * as fs from "fs";
|
|
3026
|
+
import * as path from "path";
|
|
2153
3027
|
function parseFrontmatter(text) {
|
|
2154
3028
|
const frontmatterRegex = /^---\r?\n([\s\S]*?)\r?\n---\r?\n([\s\S]*)$/;
|
|
2155
3029
|
const match = frontmatterRegex.exec(text);
|
|
@@ -2196,7 +3070,7 @@ function parseFrontmatter(text) {
|
|
|
2196
3070
|
i++;
|
|
2197
3071
|
}
|
|
2198
3072
|
} catch (err) {
|
|
2199
|
-
|
|
3073
|
+
warn("Failed to parse YAML frontmatter", { error: String(err) });
|
|
2200
3074
|
return { metadata: {}, content: text };
|
|
2201
3075
|
}
|
|
2202
3076
|
return { metadata, content: body };
|
|
@@ -2224,22 +3098,22 @@ function setMetadataField(metadata, key, value) {
|
|
|
2224
3098
|
}
|
|
2225
3099
|
function scanDirectory(options) {
|
|
2226
3100
|
const { directory, scope } = options;
|
|
2227
|
-
if (!
|
|
3101
|
+
if (!fs.existsSync(directory)) {
|
|
2228
3102
|
return [];
|
|
2229
3103
|
}
|
|
2230
3104
|
let entries;
|
|
2231
3105
|
try {
|
|
2232
|
-
entries =
|
|
3106
|
+
entries = fs.readdirSync(directory, { withFileTypes: true });
|
|
2233
3107
|
} catch (err) {
|
|
2234
|
-
|
|
3108
|
+
warn("Failed to read skills directory", { directory, error: String(err) });
|
|
2235
3109
|
return [];
|
|
2236
3110
|
}
|
|
2237
3111
|
const skills = [];
|
|
2238
3112
|
for (const entry of entries) {
|
|
2239
|
-
const fullPath =
|
|
3113
|
+
const fullPath = path.join(directory, entry.name);
|
|
2240
3114
|
if (entry.isDirectory()) {
|
|
2241
|
-
const skillFile =
|
|
2242
|
-
if (
|
|
3115
|
+
const skillFile = path.join(fullPath, "SKILL.md");
|
|
3116
|
+
if (fs.existsSync(skillFile)) {
|
|
2243
3117
|
const skill = loadSkillFile(skillFile, scope);
|
|
2244
3118
|
if (skill)
|
|
2245
3119
|
skills.push(skill);
|
|
@@ -2257,14 +3131,14 @@ function scanDirectory(options) {
|
|
|
2257
3131
|
function loadSkillFile(filePath, scope) {
|
|
2258
3132
|
let text;
|
|
2259
3133
|
try {
|
|
2260
|
-
text =
|
|
3134
|
+
text = fs.readFileSync(filePath, "utf8");
|
|
2261
3135
|
} catch (err) {
|
|
2262
|
-
|
|
3136
|
+
warn("Failed to read skill file", { filePath, error: String(err) });
|
|
2263
3137
|
return null;
|
|
2264
3138
|
}
|
|
2265
3139
|
const { metadata, content } = parseFrontmatter(text);
|
|
2266
3140
|
if (!metadata.name) {
|
|
2267
|
-
|
|
3141
|
+
debug("Skill file missing name in frontmatter — skipping", { filePath });
|
|
2268
3142
|
return null;
|
|
2269
3143
|
}
|
|
2270
3144
|
return { name: metadata.name, description: metadata.description ?? "", content, scope, path: filePath, model: metadata.model };
|
|
@@ -2292,8 +3166,8 @@ function resolveSafePath(dir, projectRoot) {
|
|
|
2292
3166
|
|
|
2293
3167
|
// src/features/skill-loader/loader.ts
|
|
2294
3168
|
function scanFilesystemSkills(directory, customDirs) {
|
|
2295
|
-
const userDir =
|
|
2296
|
-
const projectDir =
|
|
3169
|
+
const userDir = path2.join(os.homedir(), ".config", "opencode", "skills");
|
|
3170
|
+
const projectDir = path2.join(directory, ".opencode", "skills");
|
|
2297
3171
|
const userSkills = scanDirectory({ directory: userDir, scope: "user" });
|
|
2298
3172
|
const projectSkills = scanDirectory({ directory: projectDir, scope: "project" });
|
|
2299
3173
|
const customSkills = [];
|
|
@@ -2324,7 +3198,7 @@ async function loadSkills(options) {
|
|
|
2324
3198
|
const fsSkills = scanFilesystemSkills(directory, customDirs);
|
|
2325
3199
|
const skills = mergeSkillSources(apiSkills, fsSkills);
|
|
2326
3200
|
if (apiSkills.length === 0 && fsSkills.length > 0) {
|
|
2327
|
-
|
|
3201
|
+
debug("OpenCode API returned no skills — using filesystem fallback", {
|
|
2328
3202
|
fsSkillCount: fsSkills.length,
|
|
2329
3203
|
fsSkillNames: fsSkills.map((s) => s.name)
|
|
2330
3204
|
});
|
|
@@ -2382,11 +3256,11 @@ function checkContextWindow(state, thresholds = { warningPct: 0.8, criticalPct:
|
|
|
2382
3256
|
const usagePct = state.maxTokens > 0 ? state.usedTokens / state.maxTokens : 0;
|
|
2383
3257
|
if (usagePct >= thresholds.criticalPct) {
|
|
2384
3258
|
const message = buildRecoveryMessage(state, usagePct);
|
|
2385
|
-
|
|
3259
|
+
warn(`[context-window] CRITICAL ${(usagePct * 100).toFixed(1)}% used in session ${state.sessionId}`);
|
|
2386
3260
|
return { action: "recover", usagePct, message };
|
|
2387
3261
|
}
|
|
2388
3262
|
if (usagePct >= thresholds.warningPct) {
|
|
2389
|
-
|
|
3263
|
+
warn(`[context-window] WARNING ${(usagePct * 100).toFixed(1)}% used in session ${state.sessionId}`);
|
|
2390
3264
|
return { action: "warn", usagePct, message: buildWarningMessage(usagePct) };
|
|
2391
3265
|
}
|
|
2392
3266
|
return { action: "none", usagePct };
|
|
@@ -2411,7 +3285,7 @@ Update the sidebar: use todowrite to create a todo (in_progress, high priority):
|
|
|
2411
3285
|
}
|
|
2412
3286
|
|
|
2413
3287
|
// src/hooks/write-existing-file-guard.ts
|
|
2414
|
-
import * as
|
|
3288
|
+
import * as fs2 from "fs";
|
|
2415
3289
|
function createWriteGuardState() {
|
|
2416
3290
|
return { readFiles: new Set };
|
|
2417
3291
|
}
|
|
@@ -2419,14 +3293,14 @@ function trackFileRead(state, filePath) {
|
|
|
2419
3293
|
state.readFiles.add(filePath);
|
|
2420
3294
|
}
|
|
2421
3295
|
function checkWriteAllowed(state, filePath) {
|
|
2422
|
-
if (!
|
|
3296
|
+
if (!fs2.existsSync(filePath)) {
|
|
2423
3297
|
return { allowed: true };
|
|
2424
3298
|
}
|
|
2425
3299
|
if (state.readFiles.has(filePath)) {
|
|
2426
3300
|
return { allowed: true };
|
|
2427
3301
|
}
|
|
2428
3302
|
const warning = `⚠️ Write guard: Attempting to write to '${filePath}' without reading it first. Read the file before overwriting to avoid data loss.`;
|
|
2429
|
-
|
|
3303
|
+
warn(`[write-guard] BLOCKED write to unread file: ${filePath}`);
|
|
2430
3304
|
return { allowed: false, warning };
|
|
2431
3305
|
}
|
|
2432
3306
|
function createWriteGuard(state) {
|
|
@@ -2437,13 +3311,13 @@ function createWriteGuard(state) {
|
|
|
2437
3311
|
}
|
|
2438
3312
|
|
|
2439
3313
|
// src/hooks/rules-injector.ts
|
|
2440
|
-
import * as
|
|
2441
|
-
import * as
|
|
3314
|
+
import * as fs3 from "fs";
|
|
3315
|
+
import * as path3 from "path";
|
|
2442
3316
|
var RULES_FILENAMES = ["AGENTS.md", ".rules", "CLAUDE.md"];
|
|
2443
3317
|
function findRulesFile(directory) {
|
|
2444
3318
|
for (const filename of RULES_FILENAMES) {
|
|
2445
|
-
const candidate =
|
|
2446
|
-
if (
|
|
3319
|
+
const candidate = path3.join(directory, filename);
|
|
3320
|
+
if (fs3.existsSync(candidate)) {
|
|
2447
3321
|
return candidate;
|
|
2448
3322
|
}
|
|
2449
3323
|
}
|
|
@@ -2454,11 +3328,11 @@ function loadRulesForDirectory(directory) {
|
|
|
2454
3328
|
if (!rulesFile)
|
|
2455
3329
|
return;
|
|
2456
3330
|
try {
|
|
2457
|
-
const content =
|
|
2458
|
-
|
|
3331
|
+
const content = fs3.readFileSync(rulesFile, "utf8");
|
|
3332
|
+
debug(`[rules-injector] Loaded rules from ${rulesFile}`);
|
|
2459
3333
|
return content;
|
|
2460
3334
|
} catch {
|
|
2461
|
-
|
|
3335
|
+
warn(`[rules-injector] Failed to read rules file: ${rulesFile}`);
|
|
2462
3336
|
return;
|
|
2463
3337
|
}
|
|
2464
3338
|
}
|
|
@@ -2466,7 +3340,7 @@ function shouldInjectRules(toolName) {
|
|
|
2466
3340
|
return toolName === "read" || toolName === "write" || toolName === "edit";
|
|
2467
3341
|
}
|
|
2468
3342
|
function getDirectoryFromFilePath(filePath) {
|
|
2469
|
-
return
|
|
3343
|
+
return path3.dirname(path3.resolve(filePath));
|
|
2470
3344
|
}
|
|
2471
3345
|
function buildRulesInjection(rulesContent, directory) {
|
|
2472
3346
|
return `<rules source="${directory}">
|
|
@@ -2525,7 +3399,7 @@ function buildKeywordInjection(detected) {
|
|
|
2525
3399
|
function processMessageForKeywords(message, sessionId, actions) {
|
|
2526
3400
|
const detected = detectKeywords(message, actions);
|
|
2527
3401
|
if (detected.length > 0) {
|
|
2528
|
-
|
|
3402
|
+
debug(`[keyword-detector] Detected keywords in session ${sessionId}: ${detected.map((a) => a.keyword).join(", ")}`);
|
|
2529
3403
|
}
|
|
2530
3404
|
return buildKeywordInjection(detected);
|
|
2531
3405
|
}
|
|
@@ -2562,15 +3436,15 @@ var WORK_STATE_FILE = "state.json";
|
|
|
2562
3436
|
var WORK_STATE_PATH = `${WEAVE_DIR}/${WORK_STATE_FILE}`;
|
|
2563
3437
|
var PLANS_DIR = `${WEAVE_DIR}/plans`;
|
|
2564
3438
|
// src/features/work-state/storage.ts
|
|
2565
|
-
import { existsSync as
|
|
2566
|
-
import { join as
|
|
3439
|
+
import { existsSync as existsSync6, readFileSync as readFileSync5, writeFileSync, unlinkSync, mkdirSync, readdirSync as readdirSync2, statSync } from "fs";
|
|
3440
|
+
import { join as join5, basename } from "path";
|
|
2567
3441
|
import { execSync } from "child_process";
|
|
2568
3442
|
var UNCHECKED_RE = /^[-*]\s*\[\s*\]/gm;
|
|
2569
3443
|
var CHECKED_RE = /^[-*]\s*\[[xX]\]/gm;
|
|
2570
3444
|
function readWorkState(directory) {
|
|
2571
|
-
const filePath =
|
|
3445
|
+
const filePath = join5(directory, WEAVE_DIR, WORK_STATE_FILE);
|
|
2572
3446
|
try {
|
|
2573
|
-
if (!
|
|
3447
|
+
if (!existsSync6(filePath))
|
|
2574
3448
|
return null;
|
|
2575
3449
|
const raw = readFileSync5(filePath, "utf-8");
|
|
2576
3450
|
const parsed = JSON.parse(raw);
|
|
@@ -2588,20 +3462,20 @@ function readWorkState(directory) {
|
|
|
2588
3462
|
}
|
|
2589
3463
|
function writeWorkState(directory, state) {
|
|
2590
3464
|
try {
|
|
2591
|
-
const dir =
|
|
2592
|
-
if (!
|
|
2593
|
-
|
|
3465
|
+
const dir = join5(directory, WEAVE_DIR);
|
|
3466
|
+
if (!existsSync6(dir)) {
|
|
3467
|
+
mkdirSync(dir, { recursive: true });
|
|
2594
3468
|
}
|
|
2595
|
-
writeFileSync(
|
|
3469
|
+
writeFileSync(join5(dir, WORK_STATE_FILE), JSON.stringify(state, null, 2), "utf-8");
|
|
2596
3470
|
return true;
|
|
2597
3471
|
} catch {
|
|
2598
3472
|
return false;
|
|
2599
3473
|
}
|
|
2600
3474
|
}
|
|
2601
3475
|
function clearWorkState(directory) {
|
|
2602
|
-
const filePath =
|
|
3476
|
+
const filePath = join5(directory, WEAVE_DIR, WORK_STATE_FILE);
|
|
2603
3477
|
try {
|
|
2604
|
-
if (
|
|
3478
|
+
if (existsSync6(filePath)) {
|
|
2605
3479
|
unlinkSync(filePath);
|
|
2606
3480
|
}
|
|
2607
3481
|
return true;
|
|
@@ -2643,12 +3517,12 @@ function getHeadSha(directory) {
|
|
|
2643
3517
|
}
|
|
2644
3518
|
}
|
|
2645
3519
|
function findPlans(directory) {
|
|
2646
|
-
const plansDir =
|
|
3520
|
+
const plansDir = join5(directory, PLANS_DIR);
|
|
2647
3521
|
try {
|
|
2648
|
-
if (!
|
|
3522
|
+
if (!existsSync6(plansDir))
|
|
2649
3523
|
return [];
|
|
2650
3524
|
const files = readdirSync2(plansDir).filter((f) => f.endsWith(".md")).map((f) => {
|
|
2651
|
-
const fullPath =
|
|
3525
|
+
const fullPath = join5(plansDir, f);
|
|
2652
3526
|
const stat = statSync(fullPath);
|
|
2653
3527
|
return { path: fullPath, mtime: stat.mtimeMs };
|
|
2654
3528
|
}).sort((a, b) => b.mtime - a.mtime).map((f) => f.path);
|
|
@@ -2658,7 +3532,7 @@ function findPlans(directory) {
|
|
|
2658
3532
|
}
|
|
2659
3533
|
}
|
|
2660
3534
|
function getPlanProgress(planPath) {
|
|
2661
|
-
if (!
|
|
3535
|
+
if (!existsSync6(planPath)) {
|
|
2662
3536
|
return { total: 0, completed: 0, isComplete: true };
|
|
2663
3537
|
}
|
|
2664
3538
|
try {
|
|
@@ -2694,7 +3568,7 @@ function resumeWork(directory) {
|
|
|
2694
3568
|
return writeWorkState(directory, state);
|
|
2695
3569
|
}
|
|
2696
3570
|
// src/features/work-state/validation.ts
|
|
2697
|
-
import { readFileSync as readFileSync6, existsSync as
|
|
3571
|
+
import { readFileSync as readFileSync6, existsSync as existsSync7 } from "fs";
|
|
2698
3572
|
import { resolve as resolve4, sep as sep3 } from "path";
|
|
2699
3573
|
function validatePlan(planPath, projectDir) {
|
|
2700
3574
|
const errors = [];
|
|
@@ -2709,7 +3583,7 @@ function validatePlan(planPath, projectDir) {
|
|
|
2709
3583
|
});
|
|
2710
3584
|
return { valid: false, errors, warnings };
|
|
2711
3585
|
}
|
|
2712
|
-
if (!
|
|
3586
|
+
if (!existsSync7(resolvedPlanPath)) {
|
|
2713
3587
|
errors.push({
|
|
2714
3588
|
severity: "error",
|
|
2715
3589
|
category: "structure",
|
|
@@ -2899,7 +3773,7 @@ function validateFileReferences(content, projectDir, warnings) {
|
|
|
2899
3773
|
});
|
|
2900
3774
|
continue;
|
|
2901
3775
|
}
|
|
2902
|
-
if (!
|
|
3776
|
+
if (!existsSync7(absolutePath)) {
|
|
2903
3777
|
warnings.push({
|
|
2904
3778
|
severity: "warning",
|
|
2905
3779
|
category: "file-references",
|
|
@@ -2990,8 +3864,8 @@ var ACTIVE_INSTANCE_FILE = "active-instance.json";
|
|
|
2990
3864
|
var WORKFLOWS_DIR_PROJECT = ".opencode/workflows";
|
|
2991
3865
|
var WORKFLOWS_DIR_USER = "workflows";
|
|
2992
3866
|
// src/features/workflow/storage.ts
|
|
2993
|
-
import { existsSync as
|
|
2994
|
-
import { join as
|
|
3867
|
+
import { existsSync as existsSync8, readFileSync as readFileSync7, writeFileSync as writeFileSync2, unlinkSync as unlinkSync2, mkdirSync as mkdirSync2, readdirSync as readdirSync3 } from "fs";
|
|
3868
|
+
import { join as join6 } from "path";
|
|
2995
3869
|
import { randomBytes } from "node:crypto";
|
|
2996
3870
|
function generateInstanceId() {
|
|
2997
3871
|
return `wf_${randomBytes(4).toString("hex")}`;
|
|
@@ -3027,9 +3901,9 @@ function createWorkflowInstance(definition, definitionPath, goal, sessionId) {
|
|
|
3027
3901
|
};
|
|
3028
3902
|
}
|
|
3029
3903
|
function readWorkflowInstance(directory, instanceId) {
|
|
3030
|
-
const filePath =
|
|
3904
|
+
const filePath = join6(directory, WORKFLOWS_STATE_DIR, instanceId, INSTANCE_STATE_FILE);
|
|
3031
3905
|
try {
|
|
3032
|
-
if (!
|
|
3906
|
+
if (!existsSync8(filePath))
|
|
3033
3907
|
return null;
|
|
3034
3908
|
const raw = readFileSync7(filePath, "utf-8");
|
|
3035
3909
|
const parsed = JSON.parse(raw);
|
|
@@ -3044,20 +3918,20 @@ function readWorkflowInstance(directory, instanceId) {
|
|
|
3044
3918
|
}
|
|
3045
3919
|
function writeWorkflowInstance(directory, instance) {
|
|
3046
3920
|
try {
|
|
3047
|
-
const dir =
|
|
3048
|
-
if (!
|
|
3049
|
-
|
|
3921
|
+
const dir = join6(directory, WORKFLOWS_STATE_DIR, instance.instance_id);
|
|
3922
|
+
if (!existsSync8(dir)) {
|
|
3923
|
+
mkdirSync2(dir, { recursive: true });
|
|
3050
3924
|
}
|
|
3051
|
-
writeFileSync2(
|
|
3925
|
+
writeFileSync2(join6(dir, INSTANCE_STATE_FILE), JSON.stringify(instance, null, 2), "utf-8");
|
|
3052
3926
|
return true;
|
|
3053
3927
|
} catch {
|
|
3054
3928
|
return false;
|
|
3055
3929
|
}
|
|
3056
3930
|
}
|
|
3057
3931
|
function readActiveInstance(directory) {
|
|
3058
|
-
const filePath =
|
|
3932
|
+
const filePath = join6(directory, WORKFLOWS_STATE_DIR, ACTIVE_INSTANCE_FILE);
|
|
3059
3933
|
try {
|
|
3060
|
-
if (!
|
|
3934
|
+
if (!existsSync8(filePath))
|
|
3061
3935
|
return null;
|
|
3062
3936
|
const raw = readFileSync7(filePath, "utf-8");
|
|
3063
3937
|
const parsed = JSON.parse(raw);
|
|
@@ -3070,21 +3944,21 @@ function readActiveInstance(directory) {
|
|
|
3070
3944
|
}
|
|
3071
3945
|
function setActiveInstance(directory, instanceId) {
|
|
3072
3946
|
try {
|
|
3073
|
-
const dir =
|
|
3074
|
-
if (!
|
|
3075
|
-
|
|
3947
|
+
const dir = join6(directory, WORKFLOWS_STATE_DIR);
|
|
3948
|
+
if (!existsSync8(dir)) {
|
|
3949
|
+
mkdirSync2(dir, { recursive: true });
|
|
3076
3950
|
}
|
|
3077
3951
|
const pointer = { instance_id: instanceId };
|
|
3078
|
-
writeFileSync2(
|
|
3952
|
+
writeFileSync2(join6(dir, ACTIVE_INSTANCE_FILE), JSON.stringify(pointer, null, 2), "utf-8");
|
|
3079
3953
|
return true;
|
|
3080
3954
|
} catch {
|
|
3081
3955
|
return false;
|
|
3082
3956
|
}
|
|
3083
3957
|
}
|
|
3084
3958
|
function clearActiveInstance(directory) {
|
|
3085
|
-
const filePath =
|
|
3959
|
+
const filePath = join6(directory, WORKFLOWS_STATE_DIR, ACTIVE_INSTANCE_FILE);
|
|
3086
3960
|
try {
|
|
3087
|
-
if (
|
|
3961
|
+
if (existsSync8(filePath)) {
|
|
3088
3962
|
unlinkSync2(filePath);
|
|
3089
3963
|
}
|
|
3090
3964
|
return true;
|
|
@@ -3099,10 +3973,9 @@ function getActiveWorkflowInstance(directory) {
|
|
|
3099
3973
|
return readWorkflowInstance(directory, pointer.instance_id);
|
|
3100
3974
|
}
|
|
3101
3975
|
// src/features/workflow/discovery.ts
|
|
3102
|
-
import * as
|
|
3103
|
-
import * as
|
|
3104
|
-
import * as
|
|
3105
|
-
import { parse as parseJsonc } from "jsonc-parser";
|
|
3976
|
+
import * as fs4 from "fs";
|
|
3977
|
+
import * as path4 from "path";
|
|
3978
|
+
import * as os2 from "os";
|
|
3106
3979
|
|
|
3107
3980
|
// src/features/workflow/schema.ts
|
|
3108
3981
|
import { z as z2 } from "zod";
|
|
@@ -3140,21 +4013,21 @@ var WorkflowDefinitionSchema = z2.object({
|
|
|
3140
4013
|
function loadWorkflowDefinition(filePath) {
|
|
3141
4014
|
let raw;
|
|
3142
4015
|
try {
|
|
3143
|
-
raw =
|
|
4016
|
+
raw = fs4.readFileSync(filePath, "utf-8");
|
|
3144
4017
|
} catch (err) {
|
|
3145
|
-
|
|
4018
|
+
error("Failed to read workflow definition file", { filePath, error: String(err) });
|
|
3146
4019
|
return null;
|
|
3147
4020
|
}
|
|
3148
4021
|
let parsed;
|
|
3149
4022
|
try {
|
|
3150
|
-
parsed =
|
|
4023
|
+
parsed = parse2(raw);
|
|
3151
4024
|
} catch (err) {
|
|
3152
|
-
|
|
4025
|
+
error("Failed to parse workflow definition JSONC", { filePath, error: String(err) });
|
|
3153
4026
|
return null;
|
|
3154
4027
|
}
|
|
3155
4028
|
const result = WorkflowDefinitionSchema.safeParse(parsed);
|
|
3156
4029
|
if (!result.success) {
|
|
3157
|
-
|
|
4030
|
+
warn("Workflow definition failed validation", {
|
|
3158
4031
|
filePath,
|
|
3159
4032
|
errors: result.error.issues.map((i) => i.message)
|
|
3160
4033
|
});
|
|
@@ -3163,13 +4036,13 @@ function loadWorkflowDefinition(filePath) {
|
|
|
3163
4036
|
return result.data;
|
|
3164
4037
|
}
|
|
3165
4038
|
function scanWorkflowDirectory(directory, scope) {
|
|
3166
|
-
if (!
|
|
4039
|
+
if (!fs4.existsSync(directory))
|
|
3167
4040
|
return [];
|
|
3168
4041
|
let entries;
|
|
3169
4042
|
try {
|
|
3170
|
-
entries =
|
|
4043
|
+
entries = fs4.readdirSync(directory, { withFileTypes: true });
|
|
3171
4044
|
} catch (err) {
|
|
3172
|
-
|
|
4045
|
+
warn("Failed to read workflows directory", { directory, error: String(err) });
|
|
3173
4046
|
return [];
|
|
3174
4047
|
}
|
|
3175
4048
|
const workflows = [];
|
|
@@ -3178,7 +4051,7 @@ function scanWorkflowDirectory(directory, scope) {
|
|
|
3178
4051
|
continue;
|
|
3179
4052
|
if (!entry.name.endsWith(".jsonc") && !entry.name.endsWith(".json"))
|
|
3180
4053
|
continue;
|
|
3181
|
-
const filePath =
|
|
4054
|
+
const filePath = path4.join(directory, entry.name);
|
|
3182
4055
|
const definition = loadWorkflowDefinition(filePath);
|
|
3183
4056
|
if (definition) {
|
|
3184
4057
|
workflows.push({ definition, path: filePath, scope });
|
|
@@ -3187,8 +4060,8 @@ function scanWorkflowDirectory(directory, scope) {
|
|
|
3187
4060
|
return workflows;
|
|
3188
4061
|
}
|
|
3189
4062
|
function discoverWorkflows(directory, customDirs) {
|
|
3190
|
-
const projectDir =
|
|
3191
|
-
const userDir =
|
|
4063
|
+
const projectDir = path4.join(directory, WORKFLOWS_DIR_PROJECT);
|
|
4064
|
+
const userDir = path4.join(os2.homedir(), ".config", "opencode", WORKFLOWS_DIR_USER);
|
|
3192
4065
|
const userWorkflows = scanWorkflowDirectory(userDir, "user");
|
|
3193
4066
|
const projectWorkflows = scanWorkflowDirectory(projectDir, "project");
|
|
3194
4067
|
const customWorkflows = [];
|
|
@@ -3310,8 +4183,8 @@ function truncateSummary(text) {
|
|
|
3310
4183
|
return text.slice(0, maxLength - 3) + "...";
|
|
3311
4184
|
}
|
|
3312
4185
|
// src/features/workflow/completion.ts
|
|
3313
|
-
import { existsSync as
|
|
3314
|
-
import { join as
|
|
4186
|
+
import { existsSync as existsSync10 } from "fs";
|
|
4187
|
+
import { join as join8 } from "path";
|
|
3315
4188
|
var DEFAULT_CONFIRM_KEYWORDS = ["confirmed", "approved", "continue", "done", "let's proceed", "looks good", "lgtm"];
|
|
3316
4189
|
var VERDICT_APPROVE_RE = /\[\s*APPROVE\s*\]/i;
|
|
3317
4190
|
var VERDICT_REJECT_RE = /\[\s*REJECT\s*\]/i;
|
|
@@ -3363,8 +4236,8 @@ function checkPlanCreated(context) {
|
|
|
3363
4236
|
summary: `Plan created at ${matchingPlan}`
|
|
3364
4237
|
};
|
|
3365
4238
|
}
|
|
3366
|
-
const directPath =
|
|
3367
|
-
if (
|
|
4239
|
+
const directPath = join8(directory, ".weave", "plans", `${planName}.md`);
|
|
4240
|
+
if (existsSync10(directPath)) {
|
|
3368
4241
|
return {
|
|
3369
4242
|
complete: true,
|
|
3370
4243
|
artifacts: { plan_path: directPath },
|
|
@@ -3379,8 +4252,8 @@ function checkPlanComplete(context) {
|
|
|
3379
4252
|
if (!planName) {
|
|
3380
4253
|
return { complete: false, reason: "plan_complete requires plan_name in completion config" };
|
|
3381
4254
|
}
|
|
3382
|
-
const planPath =
|
|
3383
|
-
if (!
|
|
4255
|
+
const planPath = join8(directory, ".weave", "plans", `${planName}.md`);
|
|
4256
|
+
if (!existsSync10(planPath)) {
|
|
3384
4257
|
return { complete: false, reason: `Plan file not found: ${planPath}` };
|
|
3385
4258
|
}
|
|
3386
4259
|
const progress = getPlanProgress(planPath);
|
|
@@ -3811,7 +4684,7 @@ ${available ? `Available workflows: ${available}` : "No workflow definitions ava
|
|
|
3811
4684
|
sessionId,
|
|
3812
4685
|
directory
|
|
3813
4686
|
});
|
|
3814
|
-
|
|
4687
|
+
info("Workflow started", {
|
|
3815
4688
|
workflowName: match.definition.name,
|
|
3816
4689
|
goal,
|
|
3817
4690
|
agent: action.agent
|
|
@@ -4176,8 +5049,8 @@ function formatValidationResults(result) {
|
|
|
4176
5049
|
if (result.errors.length > 0)
|
|
4177
5050
|
lines.push("");
|
|
4178
5051
|
lines.push("**Warnings:**");
|
|
4179
|
-
for (const
|
|
4180
|
-
lines.push(`- [${
|
|
5052
|
+
for (const warn2 of result.warnings) {
|
|
5053
|
+
lines.push(`- [${warn2.category}] ${warn2.message}`);
|
|
4181
5054
|
}
|
|
4182
5055
|
}
|
|
4183
5056
|
return lines.join(`
|
|
@@ -4376,21 +5249,21 @@ async function resolveTodoWriter() {
|
|
|
4376
5249
|
}
|
|
4377
5250
|
|
|
4378
5251
|
// src/hooks/compaction-todo-preserver.ts
|
|
4379
|
-
function createCompactionTodoPreserver(
|
|
5252
|
+
function createCompactionTodoPreserver(client2) {
|
|
4380
5253
|
const snapshots = new Map;
|
|
4381
5254
|
async function capture(sessionID) {
|
|
4382
5255
|
try {
|
|
4383
|
-
const response = await
|
|
5256
|
+
const response = await client2.session.todo({ path: { id: sessionID } });
|
|
4384
5257
|
const todos = response.data ?? [];
|
|
4385
5258
|
if (todos.length > 0) {
|
|
4386
5259
|
snapshots.set(sessionID, todos);
|
|
4387
|
-
|
|
5260
|
+
debug("[compaction-todo-preserver] Captured snapshot", {
|
|
4388
5261
|
sessionID,
|
|
4389
5262
|
count: todos.length
|
|
4390
5263
|
});
|
|
4391
5264
|
}
|
|
4392
5265
|
} catch (err) {
|
|
4393
|
-
|
|
5266
|
+
warn("[compaction-todo-preserver] Failed to capture snapshot (non-fatal)", {
|
|
4394
5267
|
sessionID,
|
|
4395
5268
|
error: String(err)
|
|
4396
5269
|
});
|
|
@@ -4402,10 +5275,10 @@ function createCompactionTodoPreserver(client) {
|
|
|
4402
5275
|
return;
|
|
4403
5276
|
}
|
|
4404
5277
|
try {
|
|
4405
|
-
const response = await
|
|
5278
|
+
const response = await client2.session.todo({ path: { id: sessionID } });
|
|
4406
5279
|
const currentTodos = response.data ?? [];
|
|
4407
5280
|
if (currentTodos.length > 0) {
|
|
4408
|
-
|
|
5281
|
+
debug("[compaction-todo-preserver] Todos survived compaction, skipping restore", {
|
|
4409
5282
|
sessionID,
|
|
4410
5283
|
currentCount: currentTodos.length
|
|
4411
5284
|
});
|
|
@@ -4415,18 +5288,18 @@ function createCompactionTodoPreserver(client) {
|
|
|
4415
5288
|
const todoWriter = await resolveTodoWriter();
|
|
4416
5289
|
if (todoWriter) {
|
|
4417
5290
|
todoWriter({ sessionID, todos: snapshot });
|
|
4418
|
-
|
|
5291
|
+
debug("[compaction-todo-preserver] Restored todos via direct write", {
|
|
4419
5292
|
sessionID,
|
|
4420
5293
|
count: snapshot.length
|
|
4421
5294
|
});
|
|
4422
5295
|
} else {
|
|
4423
|
-
|
|
5296
|
+
warn("[compaction-todo-preserver] Direct write unavailable — todos cannot be restored", {
|
|
4424
5297
|
sessionID,
|
|
4425
5298
|
count: snapshot.length
|
|
4426
5299
|
});
|
|
4427
5300
|
}
|
|
4428
5301
|
} catch (err) {
|
|
4429
|
-
|
|
5302
|
+
warn("[compaction-todo-preserver] Failed to restore todos (non-fatal)", {
|
|
4430
5303
|
sessionID,
|
|
4431
5304
|
error: String(err)
|
|
4432
5305
|
});
|
|
@@ -4447,7 +5320,7 @@ function createCompactionTodoPreserver(client) {
|
|
|
4447
5320
|
const sessionID = props?.sessionID ?? props?.info?.id ?? "";
|
|
4448
5321
|
if (sessionID) {
|
|
4449
5322
|
snapshots.delete(sessionID);
|
|
4450
|
-
|
|
5323
|
+
debug("[compaction-todo-preserver] Cleaned up snapshot on session delete", { sessionID });
|
|
4451
5324
|
}
|
|
4452
5325
|
return;
|
|
4453
5326
|
}
|
|
@@ -4459,7 +5332,7 @@ function createCompactionTodoPreserver(client) {
|
|
|
4459
5332
|
}
|
|
4460
5333
|
// src/hooks/todo-continuation-enforcer.ts
|
|
4461
5334
|
var FINALIZE_TODOS_MARKER = "<!-- weave:finalize-todos -->";
|
|
4462
|
-
function createTodoContinuationEnforcer(
|
|
5335
|
+
function createTodoContinuationEnforcer(client2, options) {
|
|
4463
5336
|
const todoFinalizedSessions = new Set;
|
|
4464
5337
|
let todoWriterPromise;
|
|
4465
5338
|
if (options !== undefined && "todoWriterOverride" in options) {
|
|
@@ -4469,9 +5342,9 @@ function createTodoContinuationEnforcer(client, options) {
|
|
|
4469
5342
|
}
|
|
4470
5343
|
todoWriterPromise.then((writer) => {
|
|
4471
5344
|
if (writer) {
|
|
4472
|
-
|
|
5345
|
+
debug("[todo-continuation-enforcer] Direct write: available");
|
|
4473
5346
|
} else {
|
|
4474
|
-
|
|
5347
|
+
debug("[todo-continuation-enforcer] Direct write: unavailable, will fall back to LLM prompt");
|
|
4475
5348
|
}
|
|
4476
5349
|
}).catch(() => {});
|
|
4477
5350
|
async function checkAndFinalize(sessionID) {
|
|
@@ -4479,7 +5352,7 @@ function createTodoContinuationEnforcer(client, options) {
|
|
|
4479
5352
|
return;
|
|
4480
5353
|
}
|
|
4481
5354
|
try {
|
|
4482
|
-
const todosResponse = await
|
|
5355
|
+
const todosResponse = await client2.session.todo({ path: { id: sessionID } });
|
|
4483
5356
|
const todos = todosResponse.data ?? [];
|
|
4484
5357
|
const inProgressTodos = todos.filter((t) => t.status === "in_progress");
|
|
4485
5358
|
if (inProgressTodos.length === 0) {
|
|
@@ -4490,14 +5363,14 @@ function createTodoContinuationEnforcer(client, options) {
|
|
|
4490
5363
|
if (todoWriter) {
|
|
4491
5364
|
const updatedTodos = todos.map((t) => t.status === "in_progress" ? { ...t, status: "completed" } : t);
|
|
4492
5365
|
todoWriter({ sessionID, todos: updatedTodos });
|
|
4493
|
-
|
|
5366
|
+
debug("[todo-continuation-enforcer] Finalized via direct write (0 tokens)", {
|
|
4494
5367
|
sessionID,
|
|
4495
5368
|
count: inProgressTodos.length
|
|
4496
5369
|
});
|
|
4497
5370
|
} else {
|
|
4498
5371
|
const inProgressItems = inProgressTodos.map((t) => ` - "${t.content}"`).join(`
|
|
4499
5372
|
`);
|
|
4500
|
-
await
|
|
5373
|
+
await client2.session.promptAsync({
|
|
4501
5374
|
path: { id: sessionID },
|
|
4502
5375
|
body: {
|
|
4503
5376
|
parts: [
|
|
@@ -4512,14 +5385,14 @@ Use todowrite NOW to mark all of them as "completed" (or "cancelled" if abandone
|
|
|
4512
5385
|
]
|
|
4513
5386
|
}
|
|
4514
5387
|
});
|
|
4515
|
-
|
|
5388
|
+
debug("[todo-continuation-enforcer] Finalized via LLM prompt (fallback)", {
|
|
4516
5389
|
sessionID,
|
|
4517
5390
|
count: inProgressTodos.length
|
|
4518
5391
|
});
|
|
4519
5392
|
}
|
|
4520
5393
|
} catch (err) {
|
|
4521
5394
|
todoFinalizedSessions.delete(sessionID);
|
|
4522
|
-
|
|
5395
|
+
warn("[todo-continuation-enforcer] Failed to check/finalize todos (non-fatal, will retry)", {
|
|
4523
5396
|
sessionID,
|
|
4524
5397
|
error: String(err)
|
|
4525
5398
|
});
|
|
@@ -4546,8 +5419,8 @@ Use todowrite NOW to mark all of them as "completed" (or "cancelled" if abandone
|
|
|
4546
5419
|
};
|
|
4547
5420
|
}
|
|
4548
5421
|
// src/features/analytics/storage.ts
|
|
4549
|
-
import { existsSync as
|
|
4550
|
-
import { join as
|
|
5422
|
+
import { existsSync as existsSync11, mkdirSync as mkdirSync3, appendFileSync, readFileSync as readFileSync9, writeFileSync as writeFileSync3, statSync as statSync2 } from "fs";
|
|
5423
|
+
import { join as join9 } from "path";
|
|
4551
5424
|
|
|
4552
5425
|
// src/features/analytics/types.ts
|
|
4553
5426
|
var ANALYTICS_DIR = ".weave/analytics";
|
|
@@ -4562,17 +5435,17 @@ function zeroTokenUsage() {
|
|
|
4562
5435
|
// src/features/analytics/storage.ts
|
|
4563
5436
|
var MAX_SESSION_ENTRIES = 1000;
|
|
4564
5437
|
function ensureAnalyticsDir(directory) {
|
|
4565
|
-
const dir =
|
|
4566
|
-
|
|
5438
|
+
const dir = join9(directory, ANALYTICS_DIR);
|
|
5439
|
+
mkdirSync3(dir, { recursive: true, mode: 448 });
|
|
4567
5440
|
return dir;
|
|
4568
5441
|
}
|
|
4569
5442
|
function appendSessionSummary(directory, summary) {
|
|
4570
5443
|
try {
|
|
4571
5444
|
const dir = ensureAnalyticsDir(directory);
|
|
4572
|
-
const filePath =
|
|
5445
|
+
const filePath = join9(dir, SESSION_SUMMARIES_FILE);
|
|
4573
5446
|
const line = JSON.stringify(summary) + `
|
|
4574
5447
|
`;
|
|
4575
|
-
|
|
5448
|
+
appendFileSync(filePath, line, { encoding: "utf-8", mode: 384 });
|
|
4576
5449
|
try {
|
|
4577
5450
|
const TYPICAL_ENTRY_BYTES = 200;
|
|
4578
5451
|
const rotationSizeThreshold = MAX_SESSION_ENTRIES * TYPICAL_ENTRY_BYTES * 0.9;
|
|
@@ -4595,9 +5468,9 @@ function appendSessionSummary(directory, summary) {
|
|
|
4595
5468
|
}
|
|
4596
5469
|
}
|
|
4597
5470
|
function readSessionSummaries(directory) {
|
|
4598
|
-
const filePath =
|
|
5471
|
+
const filePath = join9(directory, ANALYTICS_DIR, SESSION_SUMMARIES_FILE);
|
|
4599
5472
|
try {
|
|
4600
|
-
if (!
|
|
5473
|
+
if (!existsSync11(filePath))
|
|
4601
5474
|
return [];
|
|
4602
5475
|
const content = readFileSync9(filePath, "utf-8");
|
|
4603
5476
|
const lines = content.split(`
|
|
@@ -4616,7 +5489,7 @@ function readSessionSummaries(directory) {
|
|
|
4616
5489
|
function writeFingerprint(directory, fingerprint) {
|
|
4617
5490
|
try {
|
|
4618
5491
|
const dir = ensureAnalyticsDir(directory);
|
|
4619
|
-
const filePath =
|
|
5492
|
+
const filePath = join9(dir, FINGERPRINT_FILE);
|
|
4620
5493
|
writeFileSync3(filePath, JSON.stringify(fingerprint, null, 2), { encoding: "utf-8", mode: 384 });
|
|
4621
5494
|
return true;
|
|
4622
5495
|
} catch {
|
|
@@ -4624,9 +5497,9 @@ function writeFingerprint(directory, fingerprint) {
|
|
|
4624
5497
|
}
|
|
4625
5498
|
}
|
|
4626
5499
|
function readFingerprint(directory) {
|
|
4627
|
-
const filePath =
|
|
5500
|
+
const filePath = join9(directory, ANALYTICS_DIR, FINGERPRINT_FILE);
|
|
4628
5501
|
try {
|
|
4629
|
-
if (!
|
|
5502
|
+
if (!existsSync11(filePath))
|
|
4630
5503
|
return null;
|
|
4631
5504
|
const content = readFileSync9(filePath, "utf-8");
|
|
4632
5505
|
const parsed = JSON.parse(content);
|
|
@@ -4640,10 +5513,10 @@ function readFingerprint(directory) {
|
|
|
4640
5513
|
function writeMetricsReport(directory, report) {
|
|
4641
5514
|
try {
|
|
4642
5515
|
const dir = ensureAnalyticsDir(directory);
|
|
4643
|
-
const filePath =
|
|
5516
|
+
const filePath = join9(dir, METRICS_REPORTS_FILE);
|
|
4644
5517
|
const line = JSON.stringify(report) + `
|
|
4645
5518
|
`;
|
|
4646
|
-
|
|
5519
|
+
appendFileSync(filePath, line, { encoding: "utf-8", mode: 384 });
|
|
4647
5520
|
try {
|
|
4648
5521
|
const TYPICAL_ENTRY_BYTES = 200;
|
|
4649
5522
|
const rotationSizeThreshold = MAX_METRICS_ENTRIES * TYPICAL_ENTRY_BYTES * 0.9;
|
|
@@ -4666,9 +5539,9 @@ function writeMetricsReport(directory, report) {
|
|
|
4666
5539
|
}
|
|
4667
5540
|
}
|
|
4668
5541
|
function readMetricsReports(directory) {
|
|
4669
|
-
const filePath =
|
|
5542
|
+
const filePath = join9(directory, ANALYTICS_DIR, METRICS_REPORTS_FILE);
|
|
4670
5543
|
try {
|
|
4671
|
-
if (!
|
|
5544
|
+
if (!existsSync11(filePath))
|
|
4672
5545
|
return [];
|
|
4673
5546
|
const content = readFileSync9(filePath, "utf-8");
|
|
4674
5547
|
const lines = content.split(`
|
|
@@ -5198,7 +6071,7 @@ function generateMetricsReport(directory, state) {
|
|
|
5198
6071
|
totalTokens
|
|
5199
6072
|
});
|
|
5200
6073
|
} catch (qualityErr) {
|
|
5201
|
-
|
|
6074
|
+
warn("[analytics] Failed to calculate quality score (non-fatal)", {
|
|
5202
6075
|
error: String(qualityErr)
|
|
5203
6076
|
});
|
|
5204
6077
|
}
|
|
@@ -5219,10 +6092,10 @@ function generateMetricsReport(directory, state) {
|
|
|
5219
6092
|
};
|
|
5220
6093
|
const written = writeMetricsReport(directory, report);
|
|
5221
6094
|
if (!written) {
|
|
5222
|
-
|
|
6095
|
+
warn("[analytics] Failed to write metrics report (non-fatal)");
|
|
5223
6096
|
return null;
|
|
5224
6097
|
}
|
|
5225
|
-
|
|
6098
|
+
debug("[analytics] Metrics report generated", {
|
|
5226
6099
|
plan: report.planName,
|
|
5227
6100
|
coverage: adherence.coverage,
|
|
5228
6101
|
precision: adherence.precision,
|
|
@@ -5230,20 +6103,90 @@ function generateMetricsReport(directory, state) {
|
|
|
5230
6103
|
});
|
|
5231
6104
|
return report;
|
|
5232
6105
|
} catch (err) {
|
|
5233
|
-
|
|
6106
|
+
warn("[analytics] Failed to generate metrics report (non-fatal)", {
|
|
5234
6107
|
error: String(err)
|
|
5235
6108
|
});
|
|
5236
6109
|
return null;
|
|
5237
6110
|
}
|
|
5238
6111
|
}
|
|
5239
6112
|
|
|
6113
|
+
// src/features/health-report.ts
|
|
6114
|
+
function generateHealthReport(loadResult, agents) {
|
|
6115
|
+
if (!loadResult) {
|
|
6116
|
+
return "⚠ No config load result available — Weave may not have initialized properly.";
|
|
6117
|
+
}
|
|
6118
|
+
const lines = [];
|
|
6119
|
+
const { config, loadedFiles, diagnostics } = loadResult;
|
|
6120
|
+
const hasIssues = diagnostics.length > 0;
|
|
6121
|
+
lines.push(hasIssues ? "## ⚠ Weave Config Health: Issues Found" : "## ✅ Weave Config Health: OK");
|
|
6122
|
+
lines.push("");
|
|
6123
|
+
lines.push("### Config Files");
|
|
6124
|
+
if (loadedFiles.length === 0) {
|
|
6125
|
+
lines.push("No config files found (using defaults)");
|
|
6126
|
+
} else {
|
|
6127
|
+
for (const f of loadedFiles) {
|
|
6128
|
+
lines.push(`- \`${f}\``);
|
|
6129
|
+
}
|
|
6130
|
+
}
|
|
6131
|
+
lines.push("");
|
|
6132
|
+
if (diagnostics.length > 0) {
|
|
6133
|
+
lines.push("### Validation Issues");
|
|
6134
|
+
lines.push("");
|
|
6135
|
+
for (const d of diagnostics) {
|
|
6136
|
+
const icon = d.level === "error" ? "\uD83D\uDD34" : "\uD83D\uDFE1";
|
|
6137
|
+
lines.push(`${icon} **${d.section}**: ${d.message}`);
|
|
6138
|
+
if (d.fields?.length) {
|
|
6139
|
+
for (const f of d.fields) {
|
|
6140
|
+
const fieldLabel = f.path || "(root)";
|
|
6141
|
+
lines.push(` - \`${fieldLabel}\`: ${f.message}`);
|
|
6142
|
+
}
|
|
6143
|
+
}
|
|
6144
|
+
lines.push("");
|
|
6145
|
+
}
|
|
6146
|
+
lines.push("Fix the issues above in your config file and restart opencode.");
|
|
6147
|
+
lines.push("");
|
|
6148
|
+
}
|
|
6149
|
+
lines.push("### Loaded Agents");
|
|
6150
|
+
const builtinKeys = ["loom", "tapestry", "shuttle", "pattern", "thread", "spindle", "warp", "weft"];
|
|
6151
|
+
const builtinDisplayNames = new Set(builtinKeys.map((k) => getAgentDisplayName(k)));
|
|
6152
|
+
const agentNames = Object.keys(agents);
|
|
6153
|
+
const builtinAgents = agentNames.filter((n) => builtinDisplayNames.has(n));
|
|
6154
|
+
const customAgents = agentNames.filter((n) => !builtinDisplayNames.has(n));
|
|
6155
|
+
lines.push(`- Builtin: ${builtinAgents.length}/8 (${builtinAgents.join(", ")})`);
|
|
6156
|
+
if (customAgents.length > 0) {
|
|
6157
|
+
lines.push(`- Custom: ${customAgents.length} (${customAgents.join(", ")})`);
|
|
6158
|
+
} else {
|
|
6159
|
+
lines.push("- Custom: 0");
|
|
6160
|
+
}
|
|
6161
|
+
lines.push("");
|
|
6162
|
+
if (config.custom_agents && Object.keys(config.custom_agents).length > 0) {
|
|
6163
|
+
lines.push("### Custom Agent Config");
|
|
6164
|
+
for (const [name, agentConfig] of Object.entries(config.custom_agents)) {
|
|
6165
|
+
const mode = agentConfig.mode ?? "subagent";
|
|
6166
|
+
const model = agentConfig.model ?? "(default)";
|
|
6167
|
+
lines.push(`- **${agentConfig.display_name ?? name}** — mode: ${mode}, model: ${model}`);
|
|
6168
|
+
}
|
|
6169
|
+
lines.push("");
|
|
6170
|
+
}
|
|
6171
|
+
const disabled = config.disabled_agents ?? [];
|
|
6172
|
+
if (disabled.length > 0) {
|
|
6173
|
+
lines.push(`### Disabled Agents: ${disabled.join(", ")}`);
|
|
6174
|
+
lines.push("");
|
|
6175
|
+
}
|
|
6176
|
+
lines.push("### Logs");
|
|
6177
|
+
lines.push("Detailed logs: `~/.local/share/opencode/log/` (grep for `service=weave`)");
|
|
6178
|
+
lines.push("Real-time: `opencode --print-logs --log-level WARN`");
|
|
6179
|
+
return lines.join(`
|
|
6180
|
+
`);
|
|
6181
|
+
}
|
|
6182
|
+
|
|
5240
6183
|
// src/plugin/plugin-interface.ts
|
|
5241
6184
|
function createPluginInterface(args) {
|
|
5242
|
-
const { pluginConfig, hooks, tools, configHandler, agents, client, directory = "", tracker } = args;
|
|
6185
|
+
const { pluginConfig, hooks, tools, configHandler, agents, client: client2, directory = "", tracker } = args;
|
|
5243
6186
|
const lastAssistantMessageText = new Map;
|
|
5244
6187
|
const lastUserMessageText = new Map;
|
|
5245
|
-
const compactionPreserver = hooks.compactionTodoPreserverEnabled &&
|
|
5246
|
-
const todoContinuationEnforcer = hooks.todoContinuationEnforcerEnabled &&
|
|
6188
|
+
const compactionPreserver = hooks.compactionTodoPreserverEnabled && client2 ? createCompactionTodoPreserver(client2) : null;
|
|
6189
|
+
const todoContinuationEnforcer = hooks.todoContinuationEnforcerEnabled && client2 ? createTodoContinuationEnforcer(client2) : null;
|
|
5247
6190
|
return {
|
|
5248
6191
|
tool: tools,
|
|
5249
6192
|
config: async (config) => {
|
|
@@ -5254,14 +6197,14 @@ function createPluginInterface(args) {
|
|
|
5254
6197
|
});
|
|
5255
6198
|
const existingAgents = config.agent ?? {};
|
|
5256
6199
|
if (Object.keys(existingAgents).length > 0) {
|
|
5257
|
-
|
|
6200
|
+
debug("[config] Merging Weave agents over existing agents", {
|
|
5258
6201
|
existingCount: Object.keys(existingAgents).length,
|
|
5259
6202
|
weaveCount: Object.keys(result.agents).length,
|
|
5260
6203
|
existingKeys: Object.keys(existingAgents)
|
|
5261
6204
|
});
|
|
5262
6205
|
const collisions = Object.keys(result.agents).filter((key) => (key in existingAgents));
|
|
5263
6206
|
if (collisions.length > 0) {
|
|
5264
|
-
|
|
6207
|
+
info("[config] Weave agents overriding user-defined agents with same name", {
|
|
5265
6208
|
overriddenKeys: collisions
|
|
5266
6209
|
});
|
|
5267
6210
|
}
|
|
@@ -5300,6 +6243,12 @@ function createPluginInterface(args) {
|
|
|
5300
6243
|
const result = isWorkflowCommand ? { contextInjection: null, switchAgent: null } : hooks.startWork(promptText, sessionID);
|
|
5301
6244
|
if (result.switchAgent && message) {
|
|
5302
6245
|
message.agent = getAgentDisplayName(result.switchAgent);
|
|
6246
|
+
debug("[start-work] Switching agent for plan execution", {
|
|
6247
|
+
sessionId: sessionID,
|
|
6248
|
+
agent: result.switchAgent,
|
|
6249
|
+
displayName: message.agent,
|
|
6250
|
+
hasContextInjection: !!result.contextInjection
|
|
6251
|
+
});
|
|
5303
6252
|
}
|
|
5304
6253
|
if (result.contextInjection && parts) {
|
|
5305
6254
|
const idx = parts.findIndex((p) => p.type === "text" && p.text);
|
|
@@ -5322,6 +6271,11 @@ ${result.contextInjection}`;
|
|
|
5322
6271
|
const result = hooks.workflowStart(promptText, sessionID);
|
|
5323
6272
|
if (result.switchAgent && message) {
|
|
5324
6273
|
message.agent = getAgentDisplayName(result.switchAgent);
|
|
6274
|
+
debug("[workflow] Switching agent for workflow execution", {
|
|
6275
|
+
sessionId: sessionID,
|
|
6276
|
+
agent: result.switchAgent,
|
|
6277
|
+
displayName: message.agent
|
|
6278
|
+
});
|
|
5325
6279
|
}
|
|
5326
6280
|
if (result.contextInjection && parts) {
|
|
5327
6281
|
const idx = parts.findIndex((p) => p.type === "text" && p.text);
|
|
@@ -5371,6 +6325,10 @@ ${cmdResult.contextInjection}`;
|
|
|
5371
6325
|
}
|
|
5372
6326
|
if (cmdResult.switchAgent && message) {
|
|
5373
6327
|
message.agent = getAgentDisplayName(cmdResult.switchAgent);
|
|
6328
|
+
debug("[workflow] Switching agent via workflow command", {
|
|
6329
|
+
agent: cmdResult.switchAgent,
|
|
6330
|
+
displayName: message.agent
|
|
6331
|
+
});
|
|
5374
6332
|
}
|
|
5375
6333
|
}
|
|
5376
6334
|
}
|
|
@@ -5391,7 +6349,7 @@ ${cmdResult.contextInjection}`;
|
|
|
5391
6349
|
const state = readWorkState(directory);
|
|
5392
6350
|
if (state && !state.paused) {
|
|
5393
6351
|
pauseWork(directory);
|
|
5394
|
-
|
|
6352
|
+
info("[work-continuation] Auto-paused: user message received during active plan", { sessionId: sessionID });
|
|
5395
6353
|
}
|
|
5396
6354
|
}
|
|
5397
6355
|
}
|
|
@@ -5402,7 +6360,7 @@ ${cmdResult.contextInjection}`;
|
|
|
5402
6360
|
const maxTokens = input.model?.limit?.context ?? 0;
|
|
5403
6361
|
if (sessionId && maxTokens > 0) {
|
|
5404
6362
|
setContextLimit(sessionId, maxTokens);
|
|
5405
|
-
|
|
6363
|
+
debug("[context-window] Captured context limit", { sessionId, maxTokens });
|
|
5406
6364
|
}
|
|
5407
6365
|
if (tracker && hooks.analyticsEnabled && sessionId && input.agent) {
|
|
5408
6366
|
tracker.setAgentName(sessionId, input.agent);
|
|
@@ -5437,7 +6395,7 @@ ${cmdResult.contextInjection}`;
|
|
|
5437
6395
|
try {
|
|
5438
6396
|
tracker.endSession(evt.properties.info.id);
|
|
5439
6397
|
} catch (err) {
|
|
5440
|
-
|
|
6398
|
+
warn("[analytics] Failed to end session (non-fatal)", { error: String(err) });
|
|
5441
6399
|
}
|
|
5442
6400
|
if (directory) {
|
|
5443
6401
|
try {
|
|
@@ -5449,29 +6407,29 @@ ${cmdResult.contextInjection}`;
|
|
|
5449
6407
|
}
|
|
5450
6408
|
}
|
|
5451
6409
|
} catch (err) {
|
|
5452
|
-
|
|
6410
|
+
warn("[analytics] Failed to generate metrics report on session end (non-fatal)", { error: String(err) });
|
|
5453
6411
|
}
|
|
5454
6412
|
}
|
|
5455
6413
|
}
|
|
5456
6414
|
}
|
|
5457
6415
|
if (event.type === "message.updated") {
|
|
5458
6416
|
const evt = event;
|
|
5459
|
-
const
|
|
5460
|
-
if (
|
|
6417
|
+
const info2 = evt.properties?.info;
|
|
6418
|
+
if (info2?.role === "assistant" && info2.sessionID) {
|
|
5461
6419
|
if (hooks.checkContextWindow) {
|
|
5462
|
-
const inputTokens =
|
|
6420
|
+
const inputTokens = info2.tokens?.input ?? 0;
|
|
5463
6421
|
if (inputTokens > 0) {
|
|
5464
|
-
updateUsage(
|
|
5465
|
-
const tokenState = getState(
|
|
6422
|
+
updateUsage(info2.sessionID, inputTokens);
|
|
6423
|
+
const tokenState = getState(info2.sessionID);
|
|
5466
6424
|
if (tokenState && tokenState.maxTokens > 0) {
|
|
5467
6425
|
const result = hooks.checkContextWindow({
|
|
5468
6426
|
usedTokens: tokenState.usedTokens,
|
|
5469
6427
|
maxTokens: tokenState.maxTokens,
|
|
5470
|
-
sessionId:
|
|
6428
|
+
sessionId: info2.sessionID
|
|
5471
6429
|
});
|
|
5472
6430
|
if (result.action !== "none") {
|
|
5473
|
-
|
|
5474
|
-
sessionId:
|
|
6431
|
+
warn("[context-window] Threshold crossed", {
|
|
6432
|
+
sessionId: info2.sessionID,
|
|
5475
6433
|
action: result.action,
|
|
5476
6434
|
usagePct: result.usagePct
|
|
5477
6435
|
});
|
|
@@ -5483,18 +6441,18 @@ ${cmdResult.contextInjection}`;
|
|
|
5483
6441
|
}
|
|
5484
6442
|
if (event.type === "message.updated" && tracker && hooks.analyticsEnabled) {
|
|
5485
6443
|
const evt = event;
|
|
5486
|
-
const
|
|
5487
|
-
if (
|
|
5488
|
-
if (typeof
|
|
5489
|
-
tracker.trackCost(
|
|
6444
|
+
const info2 = evt.properties?.info;
|
|
6445
|
+
if (info2?.role === "assistant" && info2.sessionID) {
|
|
6446
|
+
if (typeof info2.cost === "number" && info2.cost > 0) {
|
|
6447
|
+
tracker.trackCost(info2.sessionID, info2.cost);
|
|
5490
6448
|
}
|
|
5491
|
-
if (
|
|
5492
|
-
tracker.trackTokenUsage(
|
|
5493
|
-
input:
|
|
5494
|
-
output:
|
|
5495
|
-
reasoning:
|
|
5496
|
-
cacheRead:
|
|
5497
|
-
cacheWrite:
|
|
6449
|
+
if (info2.tokens) {
|
|
6450
|
+
tracker.trackTokenUsage(info2.sessionID, {
|
|
6451
|
+
input: info2.tokens.input ?? 0,
|
|
6452
|
+
output: info2.tokens.output ?? 0,
|
|
6453
|
+
reasoning: info2.tokens.reasoning ?? 0,
|
|
6454
|
+
cacheRead: info2.tokens.cache?.read ?? 0,
|
|
6455
|
+
cacheWrite: info2.tokens.cache?.write ?? 0
|
|
5498
6456
|
});
|
|
5499
6457
|
}
|
|
5500
6458
|
}
|
|
@@ -5503,12 +6461,12 @@ ${cmdResult.contextInjection}`;
|
|
|
5503
6461
|
const evt = event;
|
|
5504
6462
|
if (evt.properties?.command === "session.interrupt") {
|
|
5505
6463
|
pauseWork(directory);
|
|
5506
|
-
|
|
6464
|
+
info("[work-continuation] User interrupt detected — work paused");
|
|
5507
6465
|
if (directory) {
|
|
5508
6466
|
const activeWorkflow = getActiveWorkflowInstance(directory);
|
|
5509
6467
|
if (activeWorkflow && activeWorkflow.status === "running") {
|
|
5510
6468
|
pauseWorkflow(directory, "User interrupt");
|
|
5511
|
-
|
|
6469
|
+
info("[workflow] User interrupt detected — workflow paused");
|
|
5512
6470
|
}
|
|
5513
6471
|
}
|
|
5514
6472
|
}
|
|
@@ -5530,21 +6488,21 @@ ${cmdResult.contextInjection}`;
|
|
|
5530
6488
|
const lastMsg = lastAssistantMessageText.get(sessionId) ?? undefined;
|
|
5531
6489
|
const lastUserMsg = lastUserMessageText.get(sessionId) ?? undefined;
|
|
5532
6490
|
const result = hooks.workflowContinuation(sessionId, lastMsg, lastUserMsg);
|
|
5533
|
-
if (result.continuationPrompt &&
|
|
6491
|
+
if (result.continuationPrompt && client2) {
|
|
5534
6492
|
try {
|
|
5535
|
-
await
|
|
6493
|
+
await client2.session.promptAsync({
|
|
5536
6494
|
path: { id: sessionId },
|
|
5537
6495
|
body: {
|
|
5538
6496
|
parts: [{ type: "text", text: result.continuationPrompt }],
|
|
5539
6497
|
...result.switchAgent ? { agent: getAgentDisplayName(result.switchAgent) } : {}
|
|
5540
6498
|
}
|
|
5541
6499
|
});
|
|
5542
|
-
|
|
6500
|
+
debug("[workflow] Injected workflow continuation prompt", {
|
|
5543
6501
|
sessionId,
|
|
5544
6502
|
agent: result.switchAgent
|
|
5545
6503
|
});
|
|
5546
6504
|
} catch (err) {
|
|
5547
|
-
|
|
6505
|
+
error("[workflow] Failed to inject workflow continuation", { sessionId, error: String(err) });
|
|
5548
6506
|
}
|
|
5549
6507
|
return;
|
|
5550
6508
|
}
|
|
@@ -5556,21 +6514,21 @@ ${cmdResult.contextInjection}`;
|
|
|
5556
6514
|
const sessionId = evt.properties?.sessionID ?? "";
|
|
5557
6515
|
if (sessionId) {
|
|
5558
6516
|
const result = hooks.workContinuation(sessionId);
|
|
5559
|
-
if (result.continuationPrompt &&
|
|
6517
|
+
if (result.continuationPrompt && client2) {
|
|
5560
6518
|
try {
|
|
5561
|
-
await
|
|
6519
|
+
await client2.session.promptAsync({
|
|
5562
6520
|
path: { id: sessionId },
|
|
5563
6521
|
body: {
|
|
5564
6522
|
parts: [{ type: "text", text: result.continuationPrompt }]
|
|
5565
6523
|
}
|
|
5566
6524
|
});
|
|
5567
|
-
|
|
6525
|
+
debug("[work-continuation] Injected continuation prompt", { sessionId });
|
|
5568
6526
|
continuationFired = true;
|
|
5569
6527
|
} catch (err) {
|
|
5570
|
-
|
|
6528
|
+
error("[work-continuation] Failed to inject continuation", { sessionId, error: String(err) });
|
|
5571
6529
|
}
|
|
5572
6530
|
} else if (result.continuationPrompt) {
|
|
5573
|
-
|
|
6531
|
+
debug("[work-continuation] continuationPrompt available but no client", { sessionId });
|
|
5574
6532
|
}
|
|
5575
6533
|
}
|
|
5576
6534
|
}
|
|
@@ -5656,6 +6614,11 @@ ${cmdResult.contextInjection}`;
|
|
|
5656
6614
|
const metricsMarkdown = formatMetricsMarkdown(reports, summaries, args2);
|
|
5657
6615
|
parts.push({ type: "text", text: metricsMarkdown });
|
|
5658
6616
|
}
|
|
6617
|
+
if (command === "weave-health") {
|
|
6618
|
+
const loadResult = getLastConfigLoadResult();
|
|
6619
|
+
const reportText = generateHealthReport(loadResult, agents);
|
|
6620
|
+
parts.push({ type: "text", text: reportText });
|
|
6621
|
+
}
|
|
5659
6622
|
},
|
|
5660
6623
|
"tool.definition": async (input, output) => {
|
|
5661
6624
|
if (hooks.todoDescriptionOverride) {
|
|
@@ -5674,14 +6637,14 @@ ${cmdResult.contextInjection}`;
|
|
|
5674
6637
|
};
|
|
5675
6638
|
}
|
|
5676
6639
|
// src/features/analytics/fingerprint.ts
|
|
5677
|
-
import { existsSync as
|
|
5678
|
-
import { join as
|
|
6640
|
+
import { existsSync as existsSync12, readFileSync as readFileSync12, readdirSync as readdirSync5 } from "fs";
|
|
6641
|
+
import { join as join11 } from "path";
|
|
5679
6642
|
import { arch } from "os";
|
|
5680
6643
|
|
|
5681
6644
|
// src/shared/version.ts
|
|
5682
6645
|
import { readFileSync as readFileSync11 } from "fs";
|
|
5683
6646
|
import { fileURLToPath } from "url";
|
|
5684
|
-
import { dirname as dirname2, join as
|
|
6647
|
+
import { dirname as dirname2, join as join10 } from "path";
|
|
5685
6648
|
var cachedVersion;
|
|
5686
6649
|
function getWeaveVersion() {
|
|
5687
6650
|
if (cachedVersion !== undefined)
|
|
@@ -5690,7 +6653,7 @@ function getWeaveVersion() {
|
|
|
5690
6653
|
const thisDir = dirname2(fileURLToPath(import.meta.url));
|
|
5691
6654
|
for (const rel of ["../../package.json", "../package.json"]) {
|
|
5692
6655
|
try {
|
|
5693
|
-
const pkg = JSON.parse(readFileSync11(
|
|
6656
|
+
const pkg = JSON.parse(readFileSync11(join10(thisDir, rel), "utf-8"));
|
|
5694
6657
|
if (pkg.name === "@opencode_weave/weave" && typeof pkg.version === "string") {
|
|
5695
6658
|
const version = pkg.version;
|
|
5696
6659
|
cachedVersion = version;
|
|
@@ -5795,7 +6758,7 @@ function detectStack(directory) {
|
|
|
5795
6758
|
const detected = [];
|
|
5796
6759
|
for (const marker of STACK_MARKERS) {
|
|
5797
6760
|
for (const file of marker.files) {
|
|
5798
|
-
if (
|
|
6761
|
+
if (existsSync12(join11(directory, file))) {
|
|
5799
6762
|
detected.push({
|
|
5800
6763
|
name: marker.name,
|
|
5801
6764
|
confidence: marker.confidence,
|
|
@@ -5806,8 +6769,8 @@ function detectStack(directory) {
|
|
|
5806
6769
|
}
|
|
5807
6770
|
}
|
|
5808
6771
|
try {
|
|
5809
|
-
const pkgPath =
|
|
5810
|
-
if (
|
|
6772
|
+
const pkgPath = join11(directory, "package.json");
|
|
6773
|
+
if (existsSync12(pkgPath)) {
|
|
5811
6774
|
const pkg = JSON.parse(readFileSync12(pkgPath, "utf-8"));
|
|
5812
6775
|
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
5813
6776
|
if (deps.react) {
|
|
@@ -5841,26 +6804,26 @@ function detectStack(directory) {
|
|
|
5841
6804
|
});
|
|
5842
6805
|
}
|
|
5843
6806
|
function detectPackageManager(directory) {
|
|
5844
|
-
if (
|
|
6807
|
+
if (existsSync12(join11(directory, "bun.lockb")))
|
|
5845
6808
|
return "bun";
|
|
5846
|
-
if (
|
|
6809
|
+
if (existsSync12(join11(directory, "pnpm-lock.yaml")))
|
|
5847
6810
|
return "pnpm";
|
|
5848
|
-
if (
|
|
6811
|
+
if (existsSync12(join11(directory, "yarn.lock")))
|
|
5849
6812
|
return "yarn";
|
|
5850
|
-
if (
|
|
6813
|
+
if (existsSync12(join11(directory, "package-lock.json")))
|
|
5851
6814
|
return "npm";
|
|
5852
|
-
if (
|
|
6815
|
+
if (existsSync12(join11(directory, "package.json")))
|
|
5853
6816
|
return "npm";
|
|
5854
6817
|
return;
|
|
5855
6818
|
}
|
|
5856
6819
|
function detectMonorepo(directory) {
|
|
5857
6820
|
for (const marker of MONOREPO_MARKERS) {
|
|
5858
|
-
if (
|
|
6821
|
+
if (existsSync12(join11(directory, marker)))
|
|
5859
6822
|
return true;
|
|
5860
6823
|
}
|
|
5861
6824
|
try {
|
|
5862
|
-
const pkgPath =
|
|
5863
|
-
if (
|
|
6825
|
+
const pkgPath = join11(directory, "package.json");
|
|
6826
|
+
if (existsSync12(pkgPath)) {
|
|
5864
6827
|
const pkg = JSON.parse(readFileSync12(pkgPath, "utf-8"));
|
|
5865
6828
|
if (pkg.workspaces)
|
|
5866
6829
|
return true;
|
|
@@ -5895,14 +6858,14 @@ function fingerprintProject(directory) {
|
|
|
5895
6858
|
try {
|
|
5896
6859
|
const fingerprint = generateFingerprint(directory);
|
|
5897
6860
|
writeFingerprint(directory, fingerprint);
|
|
5898
|
-
|
|
6861
|
+
debug("[analytics] Project fingerprinted", {
|
|
5899
6862
|
stack: fingerprint.stack.map((s) => s.name),
|
|
5900
6863
|
primaryLanguage: fingerprint.primaryLanguage,
|
|
5901
6864
|
packageManager: fingerprint.packageManager
|
|
5902
6865
|
});
|
|
5903
6866
|
return fingerprint;
|
|
5904
6867
|
} catch (err) {
|
|
5905
|
-
|
|
6868
|
+
warn("[analytics] Fingerprinting failed (non-fatal)", { error: String(err) });
|
|
5906
6869
|
return null;
|
|
5907
6870
|
}
|
|
5908
6871
|
}
|
|
@@ -5914,14 +6877,14 @@ function getOrCreateFingerprint(directory) {
|
|
|
5914
6877
|
if (existing.weaveVersion === currentVersion) {
|
|
5915
6878
|
return existing;
|
|
5916
6879
|
}
|
|
5917
|
-
|
|
6880
|
+
debug("[analytics] Fingerprint version mismatch — regenerating", {
|
|
5918
6881
|
cached: existing.weaveVersion ?? "none",
|
|
5919
6882
|
current: currentVersion
|
|
5920
6883
|
});
|
|
5921
6884
|
}
|
|
5922
6885
|
return fingerprintProject(directory);
|
|
5923
6886
|
} catch (err) {
|
|
5924
|
-
|
|
6887
|
+
warn("[analytics] getOrCreateFingerprint failed (non-fatal)", { error: String(err) });
|
|
5925
6888
|
return null;
|
|
5926
6889
|
}
|
|
5927
6890
|
}
|
|
@@ -6045,7 +7008,7 @@ class SessionTracker {
|
|
|
6045
7008
|
};
|
|
6046
7009
|
try {
|
|
6047
7010
|
appendSessionSummary(this.directory, summary);
|
|
6048
|
-
|
|
7011
|
+
debug("[analytics] Session summary persisted", {
|
|
6049
7012
|
sessionId,
|
|
6050
7013
|
totalToolCalls,
|
|
6051
7014
|
totalDelegations: session.delegations.length,
|
|
@@ -6055,7 +7018,7 @@ class SessionTracker {
|
|
|
6055
7018
|
} : {}
|
|
6056
7019
|
});
|
|
6057
7020
|
} catch (err) {
|
|
6058
|
-
|
|
7021
|
+
warn("[analytics] Failed to persist session summary (non-fatal)", {
|
|
6059
7022
|
sessionId,
|
|
6060
7023
|
error: String(err)
|
|
6061
7024
|
});
|
|
@@ -6084,13 +7047,17 @@ function createAnalytics(directory, fingerprint) {
|
|
|
6084
7047
|
|
|
6085
7048
|
// src/index.ts
|
|
6086
7049
|
var WeavePlugin = async (ctx) => {
|
|
7050
|
+
setClient(ctx.client);
|
|
6087
7051
|
const pluginConfig = loadWeaveConfig(ctx.directory, ctx);
|
|
7052
|
+
if (pluginConfig.log_level) {
|
|
7053
|
+
setLogLevel(pluginConfig.log_level);
|
|
7054
|
+
}
|
|
6088
7055
|
const disabledHooks = new Set(pluginConfig.disabled_hooks ?? []);
|
|
6089
7056
|
const isHookEnabled = (name) => !disabledHooks.has(name);
|
|
6090
7057
|
const analyticsEnabled = pluginConfig.analytics?.enabled === true;
|
|
6091
7058
|
const fingerprintEnabled = analyticsEnabled && pluginConfig.analytics?.use_fingerprint === true;
|
|
6092
7059
|
const fingerprint = fingerprintEnabled ? getOrCreateFingerprint(ctx.directory) : null;
|
|
6093
|
-
const configDir =
|
|
7060
|
+
const configDir = join12(ctx.directory, ".opencode");
|
|
6094
7061
|
const toolsResult = await createTools({ ctx, pluginConfig });
|
|
6095
7062
|
const managers = createManagers({ ctx, pluginConfig, resolveSkills: toolsResult.resolveSkillsFn, fingerprint, configDir });
|
|
6096
7063
|
const hooks = createHooks({ pluginConfig, isHookEnabled, directory: ctx.directory, analyticsEnabled });
|