@encoredigitalgroup/opencode 0.2.3 → 0.2.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/README.md +36 -8
- package/dist/config/ConfigLoader.d.ts +2 -0
- package/dist/config/ConfigTypes.d.ts +6 -0
- package/dist/lib/TodoTracker.d.ts +54 -0
- package/dist/lib/TodoTracker.test.d.ts +1 -0
- package/dist/server/index.js +348 -4
- package/dist/tui/index.js +998 -4
- package/package.json +5 -5
package/dist/tui/index.js
CHANGED
|
@@ -1,6 +1,600 @@
|
|
|
1
1
|
// @bun
|
|
2
2
|
// src/tui/index.tsx
|
|
3
3
|
import { Plugin } from "@opencode/plugin/tui";
|
|
4
|
+
import { fg, StyledText } from "@opentui/core";
|
|
5
|
+
|
|
6
|
+
// node_modules/solid-js/dist/solid.js
|
|
7
|
+
var sharedConfig = {
|
|
8
|
+
context: undefined,
|
|
9
|
+
registry: undefined,
|
|
10
|
+
effects: undefined,
|
|
11
|
+
done: false,
|
|
12
|
+
getContextId() {
|
|
13
|
+
return getContextId(this.context.count);
|
|
14
|
+
},
|
|
15
|
+
getNextContextId() {
|
|
16
|
+
return getContextId(this.context.count++);
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
function getContextId(count) {
|
|
20
|
+
const num = String(count), len = num.length - 1;
|
|
21
|
+
return sharedConfig.context.id + (len ? String.fromCharCode(96 + len) : "") + num;
|
|
22
|
+
}
|
|
23
|
+
function setHydrateContext(context) {
|
|
24
|
+
sharedConfig.context = context;
|
|
25
|
+
}
|
|
26
|
+
var IS_DEV = false;
|
|
27
|
+
var equalFn = (a, b) => a === b;
|
|
28
|
+
var $PROXY = Symbol("solid-proxy");
|
|
29
|
+
var $TRACK = Symbol("solid-track");
|
|
30
|
+
var $DEVCOMP = Symbol("solid-dev-component");
|
|
31
|
+
var signalOptions = {
|
|
32
|
+
equals: equalFn
|
|
33
|
+
};
|
|
34
|
+
var ERROR = null;
|
|
35
|
+
var runEffects = runQueue;
|
|
36
|
+
var STALE = 1;
|
|
37
|
+
var PENDING = 2;
|
|
38
|
+
var UNOWNED = {
|
|
39
|
+
owned: null,
|
|
40
|
+
cleanups: null,
|
|
41
|
+
context: null,
|
|
42
|
+
owner: null
|
|
43
|
+
};
|
|
44
|
+
var Owner = null;
|
|
45
|
+
var Transition = null;
|
|
46
|
+
var Scheduler = null;
|
|
47
|
+
var ExternalSourceConfig = null;
|
|
48
|
+
var Listener = null;
|
|
49
|
+
var Updates = null;
|
|
50
|
+
var Effects = null;
|
|
51
|
+
var ExecCount = 0;
|
|
52
|
+
function createSignal(value, options) {
|
|
53
|
+
options = options ? Object.assign({}, signalOptions, options) : signalOptions;
|
|
54
|
+
const s = {
|
|
55
|
+
value,
|
|
56
|
+
observers: null,
|
|
57
|
+
observerSlots: null,
|
|
58
|
+
comparator: options.equals || undefined
|
|
59
|
+
};
|
|
60
|
+
const setter = (value) => {
|
|
61
|
+
if (typeof value === "function") {
|
|
62
|
+
if (Transition && Transition.running && Transition.sources.has(s))
|
|
63
|
+
value = value(s.tValue);
|
|
64
|
+
else
|
|
65
|
+
value = value(s.value);
|
|
66
|
+
}
|
|
67
|
+
return writeSignal(s, value);
|
|
68
|
+
};
|
|
69
|
+
return [readSignal.bind(s), setter];
|
|
70
|
+
}
|
|
71
|
+
function createEffect(fn, value, options) {
|
|
72
|
+
runEffects = runUserEffects;
|
|
73
|
+
const c = createComputation(fn, value, false, STALE), s = SuspenseContext && useContext(SuspenseContext);
|
|
74
|
+
if (s)
|
|
75
|
+
c.suspense = s;
|
|
76
|
+
if (!options || !options.render)
|
|
77
|
+
c.user = true;
|
|
78
|
+
Effects ? Effects.push(c) : updateComputation(c);
|
|
79
|
+
}
|
|
80
|
+
function untrack(fn) {
|
|
81
|
+
if (!ExternalSourceConfig && Listener === null)
|
|
82
|
+
return fn();
|
|
83
|
+
const listener = Listener;
|
|
84
|
+
Listener = null;
|
|
85
|
+
try {
|
|
86
|
+
if (ExternalSourceConfig)
|
|
87
|
+
return ExternalSourceConfig.untrack(fn);
|
|
88
|
+
return fn();
|
|
89
|
+
} finally {
|
|
90
|
+
Listener = listener;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
function onMount(fn) {
|
|
94
|
+
createEffect(() => untrack(fn));
|
|
95
|
+
}
|
|
96
|
+
function onCleanup(fn) {
|
|
97
|
+
if (Owner === null)
|
|
98
|
+
;
|
|
99
|
+
else if (Owner.cleanups === null)
|
|
100
|
+
Owner.cleanups = [fn];
|
|
101
|
+
else
|
|
102
|
+
Owner.cleanups.push(fn);
|
|
103
|
+
return fn;
|
|
104
|
+
}
|
|
105
|
+
function startTransition(fn) {
|
|
106
|
+
if (Transition && Transition.running) {
|
|
107
|
+
fn();
|
|
108
|
+
return Transition.done;
|
|
109
|
+
}
|
|
110
|
+
const l = Listener;
|
|
111
|
+
const o = Owner;
|
|
112
|
+
return Promise.resolve().then(() => {
|
|
113
|
+
Listener = l;
|
|
114
|
+
Owner = o;
|
|
115
|
+
let t;
|
|
116
|
+
if (Scheduler || SuspenseContext) {
|
|
117
|
+
t = Transition || (Transition = {
|
|
118
|
+
sources: new Set,
|
|
119
|
+
effects: [],
|
|
120
|
+
promises: new Set,
|
|
121
|
+
disposed: new Set,
|
|
122
|
+
queue: new Set,
|
|
123
|
+
running: true
|
|
124
|
+
});
|
|
125
|
+
t.done || (t.done = new Promise((res) => t.resolve = res));
|
|
126
|
+
t.running = true;
|
|
127
|
+
}
|
|
128
|
+
runUpdates(fn, false);
|
|
129
|
+
Listener = Owner = null;
|
|
130
|
+
return t ? t.done : undefined;
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
var [transPending, setTransPending] = /* @__PURE__ */ createSignal(false);
|
|
134
|
+
function useContext(context) {
|
|
135
|
+
let value;
|
|
136
|
+
return Owner && Owner.context && (value = Owner.context[context.id]) !== undefined ? value : context.defaultValue;
|
|
137
|
+
}
|
|
138
|
+
var SuspenseContext;
|
|
139
|
+
function readSignal() {
|
|
140
|
+
const runningTransition = Transition && Transition.running;
|
|
141
|
+
if (this.sources && (runningTransition ? this.tState : this.state)) {
|
|
142
|
+
if ((runningTransition ? this.tState : this.state) === STALE)
|
|
143
|
+
updateComputation(this);
|
|
144
|
+
else {
|
|
145
|
+
const updates = Updates;
|
|
146
|
+
Updates = null;
|
|
147
|
+
runUpdates(() => lookUpstream(this), false);
|
|
148
|
+
Updates = updates;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
if (Listener) {
|
|
152
|
+
const sSlot = this.observers ? this.observers.length : 0;
|
|
153
|
+
if (!Listener.sources) {
|
|
154
|
+
Listener.sources = [this];
|
|
155
|
+
Listener.sourceSlots = [sSlot];
|
|
156
|
+
} else {
|
|
157
|
+
Listener.sources.push(this);
|
|
158
|
+
Listener.sourceSlots.push(sSlot);
|
|
159
|
+
}
|
|
160
|
+
if (!this.observers) {
|
|
161
|
+
this.observers = [Listener];
|
|
162
|
+
this.observerSlots = [Listener.sources.length - 1];
|
|
163
|
+
} else {
|
|
164
|
+
this.observers.push(Listener);
|
|
165
|
+
this.observerSlots.push(Listener.sources.length - 1);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
if (runningTransition && Transition.sources.has(this))
|
|
169
|
+
return this.tValue;
|
|
170
|
+
return this.value;
|
|
171
|
+
}
|
|
172
|
+
function writeSignal(node, value, isComp) {
|
|
173
|
+
let current = Transition && Transition.running && Transition.sources.has(node) ? node.tValue : node.value;
|
|
174
|
+
if (!node.comparator || !node.comparator(current, value)) {
|
|
175
|
+
if (Transition) {
|
|
176
|
+
const TransitionRunning = Transition.running;
|
|
177
|
+
if (TransitionRunning || !isComp && Transition.sources.has(node)) {
|
|
178
|
+
Transition.sources.add(node);
|
|
179
|
+
node.tValue = value;
|
|
180
|
+
}
|
|
181
|
+
if (!TransitionRunning)
|
|
182
|
+
node.value = value;
|
|
183
|
+
} else
|
|
184
|
+
node.value = value;
|
|
185
|
+
if (node.observers && node.observers.length) {
|
|
186
|
+
runUpdates(() => {
|
|
187
|
+
for (let i = 0;i < node.observers.length; i += 1) {
|
|
188
|
+
const o = node.observers[i];
|
|
189
|
+
const TransitionRunning = Transition && Transition.running;
|
|
190
|
+
if (TransitionRunning && Transition.disposed.has(o))
|
|
191
|
+
continue;
|
|
192
|
+
if (TransitionRunning ? !o.tState : !o.state) {
|
|
193
|
+
if (o.pure)
|
|
194
|
+
Updates.push(o);
|
|
195
|
+
else
|
|
196
|
+
Effects.push(o);
|
|
197
|
+
if (o.observers)
|
|
198
|
+
markDownstream(o);
|
|
199
|
+
}
|
|
200
|
+
if (!TransitionRunning)
|
|
201
|
+
o.state = STALE;
|
|
202
|
+
else
|
|
203
|
+
o.tState = STALE;
|
|
204
|
+
}
|
|
205
|
+
if (Updates.length > 1e6) {
|
|
206
|
+
Updates = [];
|
|
207
|
+
if (IS_DEV)
|
|
208
|
+
;
|
|
209
|
+
throw new Error;
|
|
210
|
+
}
|
|
211
|
+
}, false);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
return value;
|
|
215
|
+
}
|
|
216
|
+
function updateComputation(node) {
|
|
217
|
+
if (!node.fn)
|
|
218
|
+
return;
|
|
219
|
+
cleanNode(node);
|
|
220
|
+
const time = ExecCount;
|
|
221
|
+
runComputation(node, Transition && Transition.running && Transition.sources.has(node) ? node.tValue : node.value, time);
|
|
222
|
+
if (Transition && !Transition.running && Transition.sources.has(node)) {
|
|
223
|
+
queueMicrotask(() => {
|
|
224
|
+
runUpdates(() => {
|
|
225
|
+
Transition && (Transition.running = true);
|
|
226
|
+
Listener = Owner = node;
|
|
227
|
+
runComputation(node, node.tValue, time);
|
|
228
|
+
Listener = Owner = null;
|
|
229
|
+
}, false);
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
function runComputation(node, value, time) {
|
|
234
|
+
let nextValue;
|
|
235
|
+
const owner = Owner, listener = Listener;
|
|
236
|
+
Listener = Owner = node;
|
|
237
|
+
try {
|
|
238
|
+
nextValue = node.fn(value);
|
|
239
|
+
} catch (err) {
|
|
240
|
+
if (node.pure) {
|
|
241
|
+
if (Transition && Transition.running) {
|
|
242
|
+
node.tState = STALE;
|
|
243
|
+
node.tOwned && node.tOwned.forEach(cleanNode);
|
|
244
|
+
node.tOwned = undefined;
|
|
245
|
+
} else {
|
|
246
|
+
node.state = STALE;
|
|
247
|
+
node.owned && node.owned.forEach(cleanNode);
|
|
248
|
+
node.owned = null;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
node.updatedAt = time + 1;
|
|
252
|
+
return handleError(err);
|
|
253
|
+
} finally {
|
|
254
|
+
Listener = listener;
|
|
255
|
+
Owner = owner;
|
|
256
|
+
}
|
|
257
|
+
if (!node.updatedAt || node.updatedAt <= time) {
|
|
258
|
+
if (node.updatedAt != null && "observers" in node) {
|
|
259
|
+
writeSignal(node, nextValue, true);
|
|
260
|
+
} else if (Transition && Transition.running && node.pure) {
|
|
261
|
+
if (!Transition.sources.has(node))
|
|
262
|
+
node.value = nextValue;
|
|
263
|
+
Transition.sources.add(node);
|
|
264
|
+
node.tValue = nextValue;
|
|
265
|
+
} else
|
|
266
|
+
node.value = nextValue;
|
|
267
|
+
node.updatedAt = time;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
function createComputation(fn, init, pure, state = STALE, options) {
|
|
271
|
+
const c = {
|
|
272
|
+
fn,
|
|
273
|
+
state,
|
|
274
|
+
updatedAt: null,
|
|
275
|
+
owned: null,
|
|
276
|
+
sources: null,
|
|
277
|
+
sourceSlots: null,
|
|
278
|
+
cleanups: null,
|
|
279
|
+
value: init,
|
|
280
|
+
owner: Owner,
|
|
281
|
+
context: Owner ? Owner.context : null,
|
|
282
|
+
pure
|
|
283
|
+
};
|
|
284
|
+
if (Transition && Transition.running) {
|
|
285
|
+
c.state = 0;
|
|
286
|
+
c.tState = state;
|
|
287
|
+
}
|
|
288
|
+
if (Owner === null)
|
|
289
|
+
;
|
|
290
|
+
else if (Owner !== UNOWNED) {
|
|
291
|
+
if (Transition && Transition.running && Owner.pure) {
|
|
292
|
+
if (!Owner.tOwned)
|
|
293
|
+
Owner.tOwned = [c];
|
|
294
|
+
else
|
|
295
|
+
Owner.tOwned.push(c);
|
|
296
|
+
} else {
|
|
297
|
+
if (!Owner.owned)
|
|
298
|
+
Owner.owned = [c];
|
|
299
|
+
else
|
|
300
|
+
Owner.owned.push(c);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
if (ExternalSourceConfig && c.fn) {
|
|
304
|
+
const sourceFn = c.fn;
|
|
305
|
+
const [track, trigger] = createSignal(undefined, {
|
|
306
|
+
equals: false
|
|
307
|
+
});
|
|
308
|
+
const ordinary = ExternalSourceConfig.factory(sourceFn, trigger);
|
|
309
|
+
onCleanup(() => ordinary.dispose());
|
|
310
|
+
let inTransition;
|
|
311
|
+
const triggerInTransition = () => startTransition(trigger).then(() => {
|
|
312
|
+
if (inTransition) {
|
|
313
|
+
inTransition.dispose();
|
|
314
|
+
inTransition = undefined;
|
|
315
|
+
}
|
|
316
|
+
});
|
|
317
|
+
c.fn = (x) => {
|
|
318
|
+
track();
|
|
319
|
+
if (Transition && Transition.running) {
|
|
320
|
+
if (!inTransition)
|
|
321
|
+
inTransition = ExternalSourceConfig.factory(sourceFn, triggerInTransition);
|
|
322
|
+
return inTransition.track(x);
|
|
323
|
+
}
|
|
324
|
+
return ordinary.track(x);
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
return c;
|
|
328
|
+
}
|
|
329
|
+
function runTop(node) {
|
|
330
|
+
const runningTransition = Transition && Transition.running;
|
|
331
|
+
if ((runningTransition ? node.tState : node.state) === 0)
|
|
332
|
+
return;
|
|
333
|
+
if ((runningTransition ? node.tState : node.state) === PENDING)
|
|
334
|
+
return lookUpstream(node);
|
|
335
|
+
if (node.suspense && untrack(node.suspense.inFallback))
|
|
336
|
+
return node.suspense.effects.push(node);
|
|
337
|
+
const ancestors = [node];
|
|
338
|
+
while ((node = node.owner) && (!node.updatedAt || node.updatedAt < ExecCount)) {
|
|
339
|
+
if (runningTransition && Transition.disposed.has(node))
|
|
340
|
+
return;
|
|
341
|
+
if (runningTransition ? node.tState : node.state)
|
|
342
|
+
ancestors.push(node);
|
|
343
|
+
}
|
|
344
|
+
for (let i = ancestors.length - 1;i >= 0; i--) {
|
|
345
|
+
node = ancestors[i];
|
|
346
|
+
if (runningTransition) {
|
|
347
|
+
let top = node, prev = ancestors[i + 1];
|
|
348
|
+
while ((top = top.owner) && top !== prev) {
|
|
349
|
+
if (Transition.disposed.has(top))
|
|
350
|
+
return;
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
if ((runningTransition ? node.tState : node.state) === STALE) {
|
|
354
|
+
updateComputation(node);
|
|
355
|
+
} else if ((runningTransition ? node.tState : node.state) === PENDING) {
|
|
356
|
+
const updates = Updates;
|
|
357
|
+
Updates = null;
|
|
358
|
+
runUpdates(() => lookUpstream(node, ancestors[0]), false);
|
|
359
|
+
Updates = updates;
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
function runUpdates(fn, init) {
|
|
364
|
+
if (Updates)
|
|
365
|
+
return fn();
|
|
366
|
+
let wait = false;
|
|
367
|
+
if (!init)
|
|
368
|
+
Updates = [];
|
|
369
|
+
if (Effects)
|
|
370
|
+
wait = true;
|
|
371
|
+
else
|
|
372
|
+
Effects = [];
|
|
373
|
+
ExecCount++;
|
|
374
|
+
try {
|
|
375
|
+
const res = fn();
|
|
376
|
+
completeUpdates(wait);
|
|
377
|
+
return res;
|
|
378
|
+
} catch (err) {
|
|
379
|
+
if (!wait)
|
|
380
|
+
Effects = null;
|
|
381
|
+
Updates = null;
|
|
382
|
+
handleError(err);
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
function completeUpdates(wait) {
|
|
386
|
+
if (Updates) {
|
|
387
|
+
if (Scheduler && Transition && Transition.running)
|
|
388
|
+
scheduleQueue(Updates);
|
|
389
|
+
else
|
|
390
|
+
runQueue(Updates);
|
|
391
|
+
Updates = null;
|
|
392
|
+
}
|
|
393
|
+
if (wait)
|
|
394
|
+
return;
|
|
395
|
+
let res;
|
|
396
|
+
if (Transition) {
|
|
397
|
+
if (!Transition.promises.size && !Transition.queue.size) {
|
|
398
|
+
const sources = Transition.sources;
|
|
399
|
+
const disposed = Transition.disposed;
|
|
400
|
+
Effects.push.apply(Effects, Transition.effects);
|
|
401
|
+
res = Transition.resolve;
|
|
402
|
+
for (const e of Effects) {
|
|
403
|
+
"tState" in e && (e.state = e.tState);
|
|
404
|
+
delete e.tState;
|
|
405
|
+
}
|
|
406
|
+
Transition = null;
|
|
407
|
+
runUpdates(() => {
|
|
408
|
+
for (const d of disposed)
|
|
409
|
+
cleanNode(d);
|
|
410
|
+
for (const v of sources) {
|
|
411
|
+
v.value = v.tValue;
|
|
412
|
+
if (v.owned) {
|
|
413
|
+
for (let i = 0, len = v.owned.length;i < len; i++)
|
|
414
|
+
cleanNode(v.owned[i]);
|
|
415
|
+
}
|
|
416
|
+
if (v.tOwned)
|
|
417
|
+
v.owned = v.tOwned;
|
|
418
|
+
delete v.tValue;
|
|
419
|
+
delete v.tOwned;
|
|
420
|
+
v.tState = 0;
|
|
421
|
+
}
|
|
422
|
+
setTransPending(false);
|
|
423
|
+
}, false);
|
|
424
|
+
} else if (Transition.running) {
|
|
425
|
+
Transition.running = false;
|
|
426
|
+
Transition.effects.push.apply(Transition.effects, Effects);
|
|
427
|
+
Effects = null;
|
|
428
|
+
setTransPending(true);
|
|
429
|
+
return;
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
const e = Effects;
|
|
433
|
+
Effects = null;
|
|
434
|
+
if (e.length)
|
|
435
|
+
runUpdates(() => runEffects(e), false);
|
|
436
|
+
if (res)
|
|
437
|
+
res();
|
|
438
|
+
}
|
|
439
|
+
function runQueue(queue) {
|
|
440
|
+
for (let i = 0;i < queue.length; i++)
|
|
441
|
+
runTop(queue[i]);
|
|
442
|
+
}
|
|
443
|
+
function scheduleQueue(queue) {
|
|
444
|
+
for (let i = 0;i < queue.length; i++) {
|
|
445
|
+
const item = queue[i];
|
|
446
|
+
const tasks = Transition.queue;
|
|
447
|
+
if (!tasks.has(item)) {
|
|
448
|
+
tasks.add(item);
|
|
449
|
+
Scheduler(() => {
|
|
450
|
+
tasks.delete(item);
|
|
451
|
+
runUpdates(() => {
|
|
452
|
+
Transition.running = true;
|
|
453
|
+
runTop(item);
|
|
454
|
+
}, false);
|
|
455
|
+
Transition && (Transition.running = false);
|
|
456
|
+
});
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
function runUserEffects(queue) {
|
|
461
|
+
let i, userLength = 0;
|
|
462
|
+
for (i = 0;i < queue.length; i++) {
|
|
463
|
+
const e = queue[i];
|
|
464
|
+
if (!e.user)
|
|
465
|
+
runTop(e);
|
|
466
|
+
else
|
|
467
|
+
queue[userLength++] = e;
|
|
468
|
+
}
|
|
469
|
+
if (sharedConfig.context) {
|
|
470
|
+
if (sharedConfig.count) {
|
|
471
|
+
sharedConfig.effects || (sharedConfig.effects = []);
|
|
472
|
+
sharedConfig.effects.push(...queue.slice(0, userLength));
|
|
473
|
+
return;
|
|
474
|
+
}
|
|
475
|
+
setHydrateContext();
|
|
476
|
+
}
|
|
477
|
+
if (sharedConfig.effects && (sharedConfig.done || !sharedConfig.count)) {
|
|
478
|
+
queue = [...sharedConfig.effects, ...queue];
|
|
479
|
+
userLength += sharedConfig.effects.length;
|
|
480
|
+
delete sharedConfig.effects;
|
|
481
|
+
}
|
|
482
|
+
for (i = 0;i < userLength; i++)
|
|
483
|
+
runTop(queue[i]);
|
|
484
|
+
}
|
|
485
|
+
function lookUpstream(node, ignore) {
|
|
486
|
+
const runningTransition = Transition && Transition.running;
|
|
487
|
+
if (runningTransition)
|
|
488
|
+
node.tState = 0;
|
|
489
|
+
else
|
|
490
|
+
node.state = 0;
|
|
491
|
+
for (let i = 0;i < node.sources.length; i += 1) {
|
|
492
|
+
const source = node.sources[i];
|
|
493
|
+
if (source.sources) {
|
|
494
|
+
const state = runningTransition ? source.tState : source.state;
|
|
495
|
+
if (state === STALE) {
|
|
496
|
+
if (source !== ignore && (!source.updatedAt || source.updatedAt < ExecCount))
|
|
497
|
+
runTop(source);
|
|
498
|
+
} else if (state === PENDING)
|
|
499
|
+
lookUpstream(source, ignore);
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
function markDownstream(node) {
|
|
504
|
+
const runningTransition = Transition && Transition.running;
|
|
505
|
+
for (let i = 0;i < node.observers.length; i += 1) {
|
|
506
|
+
const o = node.observers[i];
|
|
507
|
+
if (runningTransition ? !o.tState : !o.state) {
|
|
508
|
+
if (runningTransition)
|
|
509
|
+
o.tState = PENDING;
|
|
510
|
+
else
|
|
511
|
+
o.state = PENDING;
|
|
512
|
+
if (o.pure)
|
|
513
|
+
Updates.push(o);
|
|
514
|
+
else
|
|
515
|
+
Effects.push(o);
|
|
516
|
+
o.observers && markDownstream(o);
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
function cleanNode(node) {
|
|
521
|
+
let i;
|
|
522
|
+
if (node.sources) {
|
|
523
|
+
while (node.sources.length) {
|
|
524
|
+
const source = node.sources.pop(), index = node.sourceSlots.pop(), obs = source.observers;
|
|
525
|
+
if (obs && obs.length) {
|
|
526
|
+
const n = obs.pop(), s = source.observerSlots.pop();
|
|
527
|
+
if (index < obs.length) {
|
|
528
|
+
n.sourceSlots[s] = index;
|
|
529
|
+
obs[index] = n;
|
|
530
|
+
source.observerSlots[index] = s;
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
if (node.tOwned) {
|
|
536
|
+
for (i = node.tOwned.length - 1;i >= 0; i--)
|
|
537
|
+
cleanNode(node.tOwned[i]);
|
|
538
|
+
delete node.tOwned;
|
|
539
|
+
}
|
|
540
|
+
if (Transition && Transition.running && node.pure) {
|
|
541
|
+
reset(node, true);
|
|
542
|
+
} else if (node.owned) {
|
|
543
|
+
for (i = node.owned.length - 1;i >= 0; i--)
|
|
544
|
+
cleanNode(node.owned[i]);
|
|
545
|
+
node.owned = null;
|
|
546
|
+
}
|
|
547
|
+
if (node.cleanups) {
|
|
548
|
+
for (i = node.cleanups.length - 1;i >= 0; i--)
|
|
549
|
+
node.cleanups[i]();
|
|
550
|
+
node.cleanups = null;
|
|
551
|
+
}
|
|
552
|
+
if (Transition && Transition.running)
|
|
553
|
+
node.tState = 0;
|
|
554
|
+
else
|
|
555
|
+
node.state = 0;
|
|
556
|
+
}
|
|
557
|
+
function reset(node, top) {
|
|
558
|
+
if (!top) {
|
|
559
|
+
node.tState = 0;
|
|
560
|
+
Transition.disposed.add(node);
|
|
561
|
+
}
|
|
562
|
+
if (node.owned) {
|
|
563
|
+
for (let i = 0;i < node.owned.length; i++)
|
|
564
|
+
reset(node.owned[i]);
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
function castError(err) {
|
|
568
|
+
if (err instanceof Error)
|
|
569
|
+
return err;
|
|
570
|
+
return new Error(typeof err === "string" ? err : "Unknown error", {
|
|
571
|
+
cause: err
|
|
572
|
+
});
|
|
573
|
+
}
|
|
574
|
+
function runErrors(err, fns, owner) {
|
|
575
|
+
try {
|
|
576
|
+
for (const f of fns)
|
|
577
|
+
f(err);
|
|
578
|
+
} catch (e) {
|
|
579
|
+
handleError(e, owner && owner.owner || null);
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
function handleError(err, owner = Owner) {
|
|
583
|
+
const fns = ERROR && owner && owner.context && owner.context[ERROR];
|
|
584
|
+
const error = castError(err);
|
|
585
|
+
if (!fns)
|
|
586
|
+
throw error;
|
|
587
|
+
if (Effects)
|
|
588
|
+
Effects.push({
|
|
589
|
+
fn() {
|
|
590
|
+
runErrors(error, fns, owner);
|
|
591
|
+
},
|
|
592
|
+
state: STALE
|
|
593
|
+
});
|
|
594
|
+
else
|
|
595
|
+
runErrors(error, fns, owner);
|
|
596
|
+
}
|
|
597
|
+
var FALLBACK = Symbol("fallback");
|
|
4
598
|
|
|
5
599
|
// src/config/ConfigDefaults.ts
|
|
6
600
|
class ConfigDefaults {
|
|
@@ -15,8 +609,11 @@ class ConfigDefaults {
|
|
|
15
609
|
}
|
|
16
610
|
static getDefaultConfig() {
|
|
17
611
|
return {
|
|
612
|
+
enforceTodoTracker: true,
|
|
18
613
|
planDirectory: "docs/_planning",
|
|
19
|
-
|
|
614
|
+
readOnlyTools: [],
|
|
615
|
+
skillMappings: this.getDefaultSkillMappings(),
|
|
616
|
+
writeCapableTools: []
|
|
20
617
|
};
|
|
21
618
|
}
|
|
22
619
|
}
|
|
@@ -39,14 +636,23 @@ class ConfigTypes {
|
|
|
39
636
|
class ConfigMerger {
|
|
40
637
|
static merge(userConfig) {
|
|
41
638
|
const config = ConfigDefaults.getDefaultConfig();
|
|
639
|
+
if (userConfig.enforceTodoTracker !== undefined) {
|
|
640
|
+
config.enforceTodoTracker = userConfig.enforceTodoTracker;
|
|
641
|
+
}
|
|
42
642
|
if (userConfig.planDirectory !== undefined) {
|
|
43
643
|
config.planDirectory = userConfig.planDirectory;
|
|
44
644
|
}
|
|
645
|
+
if (userConfig.readOnlyTools !== undefined) {
|
|
646
|
+
config.readOnlyTools = userConfig.readOnlyTools;
|
|
647
|
+
}
|
|
45
648
|
if (userConfig.skillMappings !== undefined) {
|
|
46
649
|
for (const command of ConfigTypes.getSkillCommands()) {
|
|
47
650
|
config.skillMappings[command] = userConfig.skillMappings[command] ?? config.skillMappings[command];
|
|
48
651
|
}
|
|
49
652
|
}
|
|
653
|
+
if (userConfig.writeCapableTools !== undefined) {
|
|
654
|
+
config.writeCapableTools = userConfig.writeCapableTools;
|
|
655
|
+
}
|
|
50
656
|
return config;
|
|
51
657
|
}
|
|
52
658
|
}
|
|
@@ -58,6 +664,11 @@ class ConfigValidator {
|
|
|
58
664
|
if (config.planDirectory.trim() === "") {
|
|
59
665
|
errors.push("Invalid planDirectory: must be a non-empty string.");
|
|
60
666
|
}
|
|
667
|
+
for (const [option, tools] of [["readOnlyTools", config.readOnlyTools], ["writeCapableTools", config.writeCapableTools]]) {
|
|
668
|
+
if (tools.some((tool) => tool.trim() === "")) {
|
|
669
|
+
errors.push(`Invalid ${option}: tool names must be non-empty strings.`);
|
|
670
|
+
}
|
|
671
|
+
}
|
|
61
672
|
for (const command of ConfigTypes.getSkillCommands()) {
|
|
62
673
|
if (config.skillMappings[command].trim() === "") {
|
|
63
674
|
errors.push(`Invalid skillMappings.${command}: must be a non-empty string.`);
|
|
@@ -84,6 +695,23 @@ class ConfigLoader {
|
|
|
84
695
|
const directory = this.stringOption(value)?.replace(/\/+$/, "");
|
|
85
696
|
return directory === "" ? undefined : directory;
|
|
86
697
|
}
|
|
698
|
+
static booleanOption(value) {
|
|
699
|
+
return typeof value === "boolean" ? value : undefined;
|
|
700
|
+
}
|
|
701
|
+
static toolNamesOption(value) {
|
|
702
|
+
if (!Array.isArray(value)) {
|
|
703
|
+
return;
|
|
704
|
+
}
|
|
705
|
+
const toolNames = [];
|
|
706
|
+
for (const valueTool of value) {
|
|
707
|
+
const tool = this.stringOption(valueTool);
|
|
708
|
+
if (tool === undefined) {
|
|
709
|
+
return;
|
|
710
|
+
}
|
|
711
|
+
toolNames.push(tool);
|
|
712
|
+
}
|
|
713
|
+
return toolNames;
|
|
714
|
+
}
|
|
87
715
|
static skillMappingsOption(value) {
|
|
88
716
|
if (typeof value !== "object" || value === null) {
|
|
89
717
|
return;
|
|
@@ -99,11 +727,17 @@ class ConfigLoader {
|
|
|
99
727
|
return skillMappings;
|
|
100
728
|
}
|
|
101
729
|
static getUserConfig(options) {
|
|
730
|
+
const enforceTodoTracker = this.booleanOption(options?.enforceTodoTracker);
|
|
102
731
|
const planDirectory = this.planDirectoryOption(options?.planDirectory);
|
|
732
|
+
const readOnlyTools = this.toolNamesOption(options?.readOnlyTools);
|
|
103
733
|
const skillMappings = this.skillMappingsOption(options?.skillMappings);
|
|
734
|
+
const writeCapableTools = this.toolNamesOption(options?.writeCapableTools);
|
|
104
735
|
return {
|
|
736
|
+
...enforceTodoTracker === undefined ? {} : { enforceTodoTracker },
|
|
105
737
|
...planDirectory === undefined ? {} : { planDirectory },
|
|
106
|
-
...
|
|
738
|
+
...readOnlyTools === undefined ? {} : { readOnlyTools },
|
|
739
|
+
...skillMappings === undefined ? {} : { skillMappings },
|
|
740
|
+
...writeCapableTools === undefined ? {} : { writeCapableTools }
|
|
107
741
|
};
|
|
108
742
|
}
|
|
109
743
|
static loadConfig(options) {
|
|
@@ -319,22 +953,382 @@ class FilePickerCommands {
|
|
|
319
953
|
}
|
|
320
954
|
}
|
|
321
955
|
|
|
956
|
+
// src/lib/TodoTracker.ts
|
|
957
|
+
var todoTrackerInput = {
|
|
958
|
+
type: "object",
|
|
959
|
+
additionalProperties: false,
|
|
960
|
+
required: ["todos"],
|
|
961
|
+
properties: {
|
|
962
|
+
todos: {
|
|
963
|
+
type: "array",
|
|
964
|
+
items: {
|
|
965
|
+
type: "object",
|
|
966
|
+
additionalProperties: false,
|
|
967
|
+
required: ["content", "status", "priority"],
|
|
968
|
+
properties: {
|
|
969
|
+
content: { type: "string", minLength: 1, pattern: "\\S" },
|
|
970
|
+
status: { enum: ["pending", "in_progress", "completed", "cancelled"] },
|
|
971
|
+
priority: { enum: ["high", "medium", "low"] }
|
|
972
|
+
}
|
|
973
|
+
}
|
|
974
|
+
}
|
|
975
|
+
}
|
|
976
|
+
};
|
|
977
|
+
var todoTrackerName = "todotracker";
|
|
978
|
+
var todoTrackerDescription = "Replace the current session's complete ordered todo list. " + "Pass every remaining todo on each call; pass an empty array to clear the list.";
|
|
979
|
+
var trackerRefreshMessage = "Update todotracker with the complete current todo list, including newly discovered work and current statuses, before continuing tool work.";
|
|
980
|
+
var todoStatuses = ["pending", "in_progress", "completed", "cancelled"];
|
|
981
|
+
var todoPriorities = ["high", "medium", "low"];
|
|
982
|
+
function isRecord(value) {
|
|
983
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
984
|
+
}
|
|
985
|
+
function hasOnlyKeys(value, keys) {
|
|
986
|
+
const expected = new Set(keys);
|
|
987
|
+
return Object.keys(value).length === keys.length && Object.keys(value).every((key) => expected.has(key));
|
|
988
|
+
}
|
|
989
|
+
function isTodoStatus(value) {
|
|
990
|
+
return typeof value === "string" && todoStatuses.includes(value);
|
|
991
|
+
}
|
|
992
|
+
function isTodoPriority(value) {
|
|
993
|
+
return typeof value === "string" && todoPriorities.includes(value);
|
|
994
|
+
}
|
|
995
|
+
function isTodo(value) {
|
|
996
|
+
if (!isRecord(value) || !hasOnlyKeys(value, ["content", "status", "priority"])) {
|
|
997
|
+
return false;
|
|
998
|
+
}
|
|
999
|
+
return typeof value.content === "string" && value.content.length >= 1 && /\S/.test(value.content) && isTodoStatus(value.status) && isTodoPriority(value.priority);
|
|
1000
|
+
}
|
|
1001
|
+
function isTodoList(value) {
|
|
1002
|
+
return Array.isArray(value) && value.every(isTodo);
|
|
1003
|
+
}
|
|
1004
|
+
function isTodoTrackerInput(value) {
|
|
1005
|
+
return isRecord(value) && hasOnlyKeys(value, ["todos"]) && isTodoList(value.todos);
|
|
1006
|
+
}
|
|
1007
|
+
function isTodoResultMetadata(value) {
|
|
1008
|
+
return isTodoTrackerInput(value);
|
|
1009
|
+
}
|
|
1010
|
+
function todosFromToolResult(metadata, content) {
|
|
1011
|
+
if (isTodoResultMetadata(metadata)) {
|
|
1012
|
+
return metadata.todos;
|
|
1013
|
+
}
|
|
1014
|
+
for (const item of content) {
|
|
1015
|
+
if (item.type !== "text" || item.text === undefined) {
|
|
1016
|
+
continue;
|
|
1017
|
+
}
|
|
1018
|
+
try {
|
|
1019
|
+
const todos = JSON.parse(item.text);
|
|
1020
|
+
if (isTodoList(todos)) {
|
|
1021
|
+
return todos;
|
|
1022
|
+
}
|
|
1023
|
+
} catch {}
|
|
1024
|
+
}
|
|
1025
|
+
return [];
|
|
1026
|
+
}
|
|
1027
|
+
function todosFromMessages(messages) {
|
|
1028
|
+
for (const message of messages.toReversed()) {
|
|
1029
|
+
if (message.type !== "assistant")
|
|
1030
|
+
continue;
|
|
1031
|
+
for (const part of message.content.toReversed()) {
|
|
1032
|
+
if (part.type === "tool" && part.name === todoTrackerName && part.state.status === "completed") {
|
|
1033
|
+
return todosFromToolResult(part.state.metadata, part.state.content);
|
|
1034
|
+
}
|
|
1035
|
+
}
|
|
1036
|
+
}
|
|
1037
|
+
return [];
|
|
1038
|
+
}
|
|
1039
|
+
var builtInReadOnlyTools = new Set(["glob", "grep", "lsp", "read", "skill", "webfetch", "websearch"]);
|
|
1040
|
+
var builtInWriteCapableTools = new Set(["edit", "patch", "shell", "subagent", "write"]);
|
|
1041
|
+
var compatibilityWriteCapableTools = new Set(["apply_patch", "bash"]);
|
|
1042
|
+
var readToolsBeforeRefresh = 10;
|
|
1043
|
+
function refreshedTrackerState() {
|
|
1044
|
+
return { kind: "tracked", readToolsSinceRefresh: 0, requiresRefreshBeforeWrite: false };
|
|
1045
|
+
}
|
|
1046
|
+
function isReadOnlyTool(tool, config) {
|
|
1047
|
+
return builtInReadOnlyTools.has(tool) || config.readOnlyTools.includes(tool);
|
|
1048
|
+
}
|
|
1049
|
+
function isWriteCapableTool(tool, config) {
|
|
1050
|
+
return builtInWriteCapableTools.has(tool) || compatibilityWriteCapableTools.has(tool) || config.writeCapableTools.includes(tool);
|
|
1051
|
+
}
|
|
1052
|
+
function requiresTrackerRefresh(state, tool, config) {
|
|
1053
|
+
if (state.kind === "untracked" || tool === todoTrackerName) {
|
|
1054
|
+
return false;
|
|
1055
|
+
}
|
|
1056
|
+
if (isWriteCapableTool(tool, config)) {
|
|
1057
|
+
return state.requiresRefreshBeforeWrite;
|
|
1058
|
+
}
|
|
1059
|
+
if (isReadOnlyTool(tool, config)) {
|
|
1060
|
+
return state.readToolsSinceRefresh >= readToolsBeforeRefresh;
|
|
1061
|
+
}
|
|
1062
|
+
return false;
|
|
1063
|
+
}
|
|
1064
|
+
function requiresTrackerRefreshBeforeMoreWork(state) {
|
|
1065
|
+
return state.kind === "tracked" && (state.requiresRefreshBeforeWrite || state.readToolsSinceRefresh >= readToolsBeforeRefresh);
|
|
1066
|
+
}
|
|
1067
|
+
function trackerStateFromMessages(messages, config) {
|
|
1068
|
+
let state = { kind: "untracked" };
|
|
1069
|
+
for (const message of messages) {
|
|
1070
|
+
if (message.type !== "assistant") {
|
|
1071
|
+
continue;
|
|
1072
|
+
}
|
|
1073
|
+
for (const part of message.content) {
|
|
1074
|
+
if (part.type !== "tool" || part.state.status !== "completed") {
|
|
1075
|
+
continue;
|
|
1076
|
+
}
|
|
1077
|
+
if (part.name === todoTrackerName) {
|
|
1078
|
+
state = refreshedTrackerState();
|
|
1079
|
+
} else if (state.kind === "tracked") {
|
|
1080
|
+
if (isWriteCapableTool(part.name, config)) {
|
|
1081
|
+
state.requiresRefreshBeforeWrite = true;
|
|
1082
|
+
} else if (isReadOnlyTool(part.name, config)) {
|
|
1083
|
+
state.readToolsSinceRefresh += 1;
|
|
1084
|
+
}
|
|
1085
|
+
}
|
|
1086
|
+
}
|
|
1087
|
+
}
|
|
1088
|
+
return state;
|
|
1089
|
+
}
|
|
1090
|
+
|
|
1091
|
+
class TodoTrackerEnforcement {
|
|
1092
|
+
context;
|
|
1093
|
+
config;
|
|
1094
|
+
states = new Map;
|
|
1095
|
+
constructor(context, config) {
|
|
1096
|
+
this.context = context;
|
|
1097
|
+
this.config = config;
|
|
1098
|
+
}
|
|
1099
|
+
async stateFor(sessionID) {
|
|
1100
|
+
const existing = this.states.get(sessionID);
|
|
1101
|
+
if (existing !== undefined) {
|
|
1102
|
+
return existing;
|
|
1103
|
+
}
|
|
1104
|
+
const state = trackerStateFromMessages(await this.context.session.context({ sessionID }), this.config);
|
|
1105
|
+
this.states.set(sessionID, state);
|
|
1106
|
+
return state;
|
|
1107
|
+
}
|
|
1108
|
+
async markWriteRequiresRefresh(sessionID) {
|
|
1109
|
+
const state = await this.stateFor(sessionID);
|
|
1110
|
+
if (state.kind === "tracked") {
|
|
1111
|
+
state.requiresRefreshBeforeWrite = true;
|
|
1112
|
+
}
|
|
1113
|
+
}
|
|
1114
|
+
async register() {
|
|
1115
|
+
let before;
|
|
1116
|
+
let after;
|
|
1117
|
+
let context;
|
|
1118
|
+
try {
|
|
1119
|
+
before = await this.context.tool.hook("execute.before", async (event) => {
|
|
1120
|
+
const state = await this.stateFor(event.sessionID);
|
|
1121
|
+
if (requiresTrackerRefresh(state, event.tool, this.config)) {
|
|
1122
|
+
throw new Error(trackerRefreshMessage);
|
|
1123
|
+
}
|
|
1124
|
+
if (state.kind === "tracked") {
|
|
1125
|
+
if (isWriteCapableTool(event.tool, this.config)) {
|
|
1126
|
+
state.requiresRefreshBeforeWrite = true;
|
|
1127
|
+
} else if (isReadOnlyTool(event.tool, this.config)) {
|
|
1128
|
+
state.readToolsSinceRefresh += 1;
|
|
1129
|
+
}
|
|
1130
|
+
}
|
|
1131
|
+
});
|
|
1132
|
+
after = await this.context.tool.hook("execute.after", async (event) => {
|
|
1133
|
+
if (event.status !== "completed") {
|
|
1134
|
+
return;
|
|
1135
|
+
}
|
|
1136
|
+
if (event.tool === todoTrackerName) {
|
|
1137
|
+
this.states.set(event.sessionID, refreshedTrackerState());
|
|
1138
|
+
return;
|
|
1139
|
+
}
|
|
1140
|
+
const state = await this.stateFor(event.sessionID);
|
|
1141
|
+
if (state.kind !== "tracked") {
|
|
1142
|
+
return;
|
|
1143
|
+
}
|
|
1144
|
+
});
|
|
1145
|
+
context = await this.context.session.hook("context", async (event) => {
|
|
1146
|
+
const state = await this.stateFor(event.sessionID);
|
|
1147
|
+
if (requiresTrackerRefreshBeforeMoreWork(state)) {
|
|
1148
|
+
event.system.push({ type: "text", text: trackerRefreshMessage });
|
|
1149
|
+
}
|
|
1150
|
+
});
|
|
1151
|
+
const beforeRegistration = before;
|
|
1152
|
+
const afterRegistration = after;
|
|
1153
|
+
const contextRegistration = context;
|
|
1154
|
+
const controller = new AbortController;
|
|
1155
|
+
const observer = this.observeSubagentCompletion(controller.signal);
|
|
1156
|
+
return {
|
|
1157
|
+
dispose: async () => {
|
|
1158
|
+
controller.abort();
|
|
1159
|
+
await Promise.all([
|
|
1160
|
+
beforeRegistration.dispose(),
|
|
1161
|
+
afterRegistration.dispose(),
|
|
1162
|
+
contextRegistration.dispose(),
|
|
1163
|
+
observer
|
|
1164
|
+
]);
|
|
1165
|
+
}
|
|
1166
|
+
};
|
|
1167
|
+
} catch (error) {
|
|
1168
|
+
await Promise.all([before?.dispose(), after?.dispose(), context?.dispose()]);
|
|
1169
|
+
throw error;
|
|
1170
|
+
}
|
|
1171
|
+
}
|
|
1172
|
+
async observeSubagentCompletion(signal) {
|
|
1173
|
+
try {
|
|
1174
|
+
for await (const event of this.context.event.subscribe({ signal })) {
|
|
1175
|
+
if (event.type !== "session.idle") {
|
|
1176
|
+
continue;
|
|
1177
|
+
}
|
|
1178
|
+
const session = await this.context.session.get({ sessionID: event.data.sessionID });
|
|
1179
|
+
if (session.parentID !== undefined) {
|
|
1180
|
+
await this.markWriteRequiresRefresh(session.parentID);
|
|
1181
|
+
}
|
|
1182
|
+
}
|
|
1183
|
+
} catch (error) {
|
|
1184
|
+
if (!signal.aborted) {
|
|
1185
|
+
console.error("Unable to watch subagent completion for todo enforcement.", error);
|
|
1186
|
+
}
|
|
1187
|
+
}
|
|
1188
|
+
}
|
|
1189
|
+
}
|
|
1190
|
+
|
|
1191
|
+
class TodoTracker {
|
|
1192
|
+
static marker(status) {
|
|
1193
|
+
switch (status) {
|
|
1194
|
+
case "pending":
|
|
1195
|
+
return "[ ]";
|
|
1196
|
+
case "in_progress":
|
|
1197
|
+
return "[~]";
|
|
1198
|
+
case "completed":
|
|
1199
|
+
return "[x]";
|
|
1200
|
+
case "cancelled":
|
|
1201
|
+
return "[-]";
|
|
1202
|
+
}
|
|
1203
|
+
}
|
|
1204
|
+
static sidebar(messages, completed = false) {
|
|
1205
|
+
const todos = todosFromMessages(messages);
|
|
1206
|
+
if (!completed) {
|
|
1207
|
+
return todos;
|
|
1208
|
+
}
|
|
1209
|
+
return todos.map((todo) => todo.status === "pending" || todo.status === "in_progress" ? { ...todo, status: "completed" } : todo);
|
|
1210
|
+
}
|
|
1211
|
+
static async register(context, config) {
|
|
1212
|
+
const tool = await context.tool.transform((tools) => {
|
|
1213
|
+
tools.add({
|
|
1214
|
+
name: todoTrackerName,
|
|
1215
|
+
description: todoTrackerDescription,
|
|
1216
|
+
input: todoTrackerInput,
|
|
1217
|
+
options: { codemode: false },
|
|
1218
|
+
async execute(input) {
|
|
1219
|
+
if (!isTodoTrackerInput(input)) {
|
|
1220
|
+
throw new Error("Invalid todotracker input.");
|
|
1221
|
+
}
|
|
1222
|
+
return { content: JSON.stringify(input.todos), metadata: { todos: input.todos } };
|
|
1223
|
+
}
|
|
1224
|
+
});
|
|
1225
|
+
});
|
|
1226
|
+
if (!config.enforceTodoTracker) {
|
|
1227
|
+
return tool;
|
|
1228
|
+
}
|
|
1229
|
+
try {
|
|
1230
|
+
const enforcement = await new TodoTrackerEnforcement(context, config).register();
|
|
1231
|
+
return {
|
|
1232
|
+
dispose: async () => {
|
|
1233
|
+
try {
|
|
1234
|
+
await enforcement.dispose();
|
|
1235
|
+
} finally {
|
|
1236
|
+
await tool.dispose();
|
|
1237
|
+
}
|
|
1238
|
+
}
|
|
1239
|
+
};
|
|
1240
|
+
} catch (error) {
|
|
1241
|
+
await tool.dispose();
|
|
1242
|
+
throw error;
|
|
1243
|
+
}
|
|
1244
|
+
}
|
|
1245
|
+
}
|
|
1246
|
+
|
|
322
1247
|
// src/tui/index.tsx
|
|
323
|
-
import { jsx, Fragment } from "@opentui/solid/jsx-runtime";
|
|
1248
|
+
import { jsx, jsxs, Fragment } from "@opentui/solid/jsx-runtime";
|
|
324
1249
|
function PickerCommandLayer(props) {
|
|
325
1250
|
FilePickerCommands.register(props.context, props.config);
|
|
326
1251
|
return /* @__PURE__ */ jsx(Fragment, {});
|
|
327
1252
|
}
|
|
1253
|
+
function TodoSidebar(props) {
|
|
1254
|
+
const todos = () => TodoTracker.sidebar(props.context.data.session.message.list(props.sessionID), props.context.data.session.status(props.sessionID) === "idle");
|
|
1255
|
+
const color = (status) => status === "in_progress" ? props.context.theme.text.feedback.warning.base : props.context.theme.text.muted;
|
|
1256
|
+
const line = (todo) => `${TodoTracker.marker(todo.status)} (${todo.priority}) ${todo.content}`;
|
|
1257
|
+
const plainContent = (items) => items.map(line).join(`
|
|
1258
|
+
`);
|
|
1259
|
+
const content = (items) => new StyledText(items.flatMap((todo, index) => [
|
|
1260
|
+
fg(color(todo.status))(`${line(todo)}${index === items.length - 1 ? "" : `
|
|
1261
|
+
`}`)
|
|
1262
|
+
]));
|
|
1263
|
+
let heading;
|
|
1264
|
+
let list;
|
|
1265
|
+
const render = () => {
|
|
1266
|
+
const items = todos();
|
|
1267
|
+
if (heading !== undefined) {
|
|
1268
|
+
heading.content = items.length > 0 ? "Todos" : "";
|
|
1269
|
+
}
|
|
1270
|
+
if (list !== undefined) {
|
|
1271
|
+
list.content = content(items);
|
|
1272
|
+
}
|
|
1273
|
+
};
|
|
1274
|
+
const initialTodos = todos();
|
|
1275
|
+
onMount(() => {
|
|
1276
|
+
render();
|
|
1277
|
+
const unlisten = props.context.data.listen?.(() => {
|
|
1278
|
+
render();
|
|
1279
|
+
});
|
|
1280
|
+
onCleanup(() => unlisten?.());
|
|
1281
|
+
props.context.data.session.message.sync(props.sessionID).catch(() => {});
|
|
1282
|
+
});
|
|
1283
|
+
return /* @__PURE__ */ jsxs("box", {
|
|
1284
|
+
flexDirection: "column",
|
|
1285
|
+
children: [
|
|
1286
|
+
/* @__PURE__ */ jsx("text", {
|
|
1287
|
+
ref: (node) => {
|
|
1288
|
+
heading = node;
|
|
1289
|
+
},
|
|
1290
|
+
children: initialTodos.length > 0 ? "Todos" : ""
|
|
1291
|
+
}),
|
|
1292
|
+
/* @__PURE__ */ jsx("text", {
|
|
1293
|
+
ref: (node) => {
|
|
1294
|
+
list = node;
|
|
1295
|
+
},
|
|
1296
|
+
fg: props.context.theme.text.muted,
|
|
1297
|
+
content: plainContent(initialTodos)
|
|
1298
|
+
})
|
|
1299
|
+
]
|
|
1300
|
+
});
|
|
1301
|
+
}
|
|
328
1302
|
var tui_default = Plugin.define({
|
|
329
1303
|
id: "@encoredigitalgroup/opencode",
|
|
330
1304
|
setup(ctx) {
|
|
331
|
-
|
|
1305
|
+
const appSlot = ctx.ui.slot({
|
|
332
1306
|
append: "app",
|
|
333
1307
|
render: () => /* @__PURE__ */ jsx(PickerCommandLayer, {
|
|
334
1308
|
context: ctx,
|
|
335
1309
|
config: ConfigLoader.loadConfig(ctx.options)
|
|
336
1310
|
})
|
|
337
1311
|
});
|
|
1312
|
+
let sidebarSlot;
|
|
1313
|
+
try {
|
|
1314
|
+
sidebarSlot = ctx.ui.slot({
|
|
1315
|
+
append: "sidebar.content",
|
|
1316
|
+
render: (input) => /* @__PURE__ */ jsx(TodoSidebar, {
|
|
1317
|
+
context: ctx,
|
|
1318
|
+
sessionID: input.sessionID
|
|
1319
|
+
})
|
|
1320
|
+
});
|
|
1321
|
+
} catch (error) {
|
|
1322
|
+
appSlot();
|
|
1323
|
+
throw error;
|
|
1324
|
+
}
|
|
1325
|
+
return () => {
|
|
1326
|
+
try {
|
|
1327
|
+
sidebarSlot();
|
|
1328
|
+
} finally {
|
|
1329
|
+
appSlot();
|
|
1330
|
+
}
|
|
1331
|
+
};
|
|
338
1332
|
}
|
|
339
1333
|
});
|
|
340
1334
|
export {
|