@encoredigitalgroup/opencode 0.2.4 → 0.2.6
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 +16 -7
- package/dist/config/ConfigLoader.d.ts +2 -0
- package/dist/config/ConfigTypes.d.ts +10 -0
- package/dist/lib/TodoTracker.d.ts +3 -2
- package/dist/server/index.js +184 -7
- package/dist/tui/index.js +226 -174
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -32,7 +32,8 @@ The plugin resolves the mapped skills through OpenCode, so those skills can live
|
|
|
32
32
|
## Configuration
|
|
33
33
|
|
|
34
34
|
Set `planDirectory` and `skillMappings` in the object's `options` field. `planDirectory` is a non-empty path relative to the workspace. `skillMappings` can override
|
|
35
|
-
any supported command's installed skill name.
|
|
35
|
+
any supported command's installed skill name. `enforceTodoTracker` defaults to `true`; set it to `false` to disable tracker-refresh enforcement. Unspecified values
|
|
36
|
+
use `docs/_planning` and the command's existing skill name.
|
|
36
37
|
|
|
37
38
|
```json
|
|
38
39
|
{
|
|
@@ -40,13 +41,14 @@ any supported command's installed skill name. Unspecified values use the default
|
|
|
40
41
|
"plugins": [
|
|
41
42
|
{
|
|
42
43
|
"package": "@encoredigitalgroup/opencode",
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
"
|
|
47
|
-
|
|
44
|
+
"options": {
|
|
45
|
+
"planDirectory": "plans",
|
|
46
|
+
"enforceTodoTracker": false,
|
|
47
|
+
"skillMappings": {
|
|
48
|
+
"plan-review": "custom-plan-review",
|
|
49
|
+
"plan-execute": "custom-plan-execute"
|
|
50
|
+
}
|
|
48
51
|
}
|
|
49
|
-
}
|
|
50
52
|
}
|
|
51
53
|
]
|
|
52
54
|
}
|
|
@@ -68,6 +70,13 @@ Each todo requires:
|
|
|
68
70
|
The checklist renders only in the OpenCode terminal TUI sidebar. After a restart, it appears only when the completed `todotracker` result is in the host's loaded
|
|
69
71
|
message cache. That cache starts with the newest 20 messages, so older results are not shown until the host has loaded them.
|
|
70
72
|
|
|
73
|
+
After a session's first successful `todotracker` call, the plugin requires another update at least once per minute. When an update is due, it blocks every other
|
|
74
|
+
tool until the tracker is updated. A tracked subagent completing also requires its parent session to update the tracker before continuing. Enforcement is disabled
|
|
75
|
+
for sessions that have never used `todotracker`, and can be disabled for all sessions with `"enforceTodoTracker": false`.
|
|
76
|
+
|
|
77
|
+
When a root session is idle, the sidebar renders the final in-progress todo as completed only when every earlier todo is completed. It restores the original status
|
|
78
|
+
when work resumes.
|
|
79
|
+
|
|
71
80
|
## Development
|
|
72
81
|
|
|
73
82
|
```sh
|
|
@@ -4,6 +4,8 @@ import { type PluginConfig } from "./ConfigTypes.js";
|
|
|
4
4
|
export declare class ConfigLoader {
|
|
5
5
|
private static stringOption;
|
|
6
6
|
private static planDirectoryOption;
|
|
7
|
+
private static booleanOption;
|
|
8
|
+
private static toolNamesOption;
|
|
7
9
|
/** Get valid skill mappings from plugin options. */
|
|
8
10
|
private static skillMappingsOption;
|
|
9
11
|
/** Get the configured option values that can override the defaults. */
|
|
@@ -2,12 +2,22 @@ export declare const skillCommands: readonly ["feature-planning", "plan-execute"
|
|
|
2
2
|
export type SkillCommand = (typeof skillCommands)[number];
|
|
3
3
|
export type SkillMappings = Record<SkillCommand, string>;
|
|
4
4
|
export type PluginConfig = {
|
|
5
|
+
enforceTodoTracker: boolean;
|
|
5
6
|
planDirectory: string;
|
|
7
|
+
/** @deprecated Tool classifications no longer affect todo tracker enforcement. */
|
|
8
|
+
readOnlyTools: string[];
|
|
6
9
|
skillMappings: SkillMappings;
|
|
10
|
+
/** @deprecated Tool classifications no longer affect todo tracker enforcement. */
|
|
11
|
+
writeCapableTools: string[];
|
|
7
12
|
};
|
|
8
13
|
export type UserPluginConfig = {
|
|
14
|
+
enforceTodoTracker?: boolean;
|
|
9
15
|
planDirectory?: string;
|
|
16
|
+
/** @deprecated Tool classifications no longer affect todo tracker enforcement. */
|
|
17
|
+
readOnlyTools?: string[];
|
|
10
18
|
skillMappings?: Partial<SkillMappings>;
|
|
19
|
+
/** @deprecated Tool classifications no longer affect todo tracker enforcement. */
|
|
20
|
+
writeCapableTools?: string[];
|
|
11
21
|
};
|
|
12
22
|
/** Configuration type utilities. */
|
|
13
23
|
export declare class ConfigTypes {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { SessionMessageInfo } from "@opencode/client";
|
|
2
2
|
import type { Plugin as ServerPlugin } from "@opencode/plugin";
|
|
3
|
+
import type { PluginConfig } from "../config";
|
|
3
4
|
export type TodoStatus = "pending" | "in_progress" | "completed" | "cancelled";
|
|
4
5
|
export type TodoPriority = "high" | "medium" | "low";
|
|
5
6
|
export type Todo = {
|
|
@@ -48,6 +49,6 @@ export declare function isTodoResultMetadata(value: unknown): value is TodoTrack
|
|
|
48
49
|
export declare function todosFromMessages(messages: readonly SessionMessageInfo[]): readonly Todo[];
|
|
49
50
|
export declare class TodoTracker {
|
|
50
51
|
static marker(status: TodoStatus): string;
|
|
51
|
-
static sidebar(messages: readonly SessionMessageInfo[]): readonly Todo[];
|
|
52
|
-
static register(context: ServerPlugin.Context): Promise<import("@opencode/plugin/promise/registration").Registration>;
|
|
52
|
+
static sidebar(messages: readonly SessionMessageInfo[], completed?: boolean): readonly Todo[];
|
|
53
|
+
static register(context: ServerPlugin.Context, config: PluginConfig): Promise<import("@opencode/plugin/promise/registration").Registration>;
|
|
53
54
|
}
|
package/dist/server/index.js
CHANGED
|
@@ -15,8 +15,11 @@ class ConfigDefaults {
|
|
|
15
15
|
}
|
|
16
16
|
static getDefaultConfig() {
|
|
17
17
|
return {
|
|
18
|
+
enforceTodoTracker: true,
|
|
18
19
|
planDirectory: "docs/_planning",
|
|
19
|
-
|
|
20
|
+
readOnlyTools: [],
|
|
21
|
+
skillMappings: this.getDefaultSkillMappings(),
|
|
22
|
+
writeCapableTools: []
|
|
20
23
|
};
|
|
21
24
|
}
|
|
22
25
|
}
|
|
@@ -39,14 +42,23 @@ class ConfigTypes {
|
|
|
39
42
|
class ConfigMerger {
|
|
40
43
|
static merge(userConfig) {
|
|
41
44
|
const config = ConfigDefaults.getDefaultConfig();
|
|
45
|
+
if (userConfig.enforceTodoTracker !== undefined) {
|
|
46
|
+
config.enforceTodoTracker = userConfig.enforceTodoTracker;
|
|
47
|
+
}
|
|
42
48
|
if (userConfig.planDirectory !== undefined) {
|
|
43
49
|
config.planDirectory = userConfig.planDirectory;
|
|
44
50
|
}
|
|
51
|
+
if (userConfig.readOnlyTools !== undefined) {
|
|
52
|
+
config.readOnlyTools = userConfig.readOnlyTools;
|
|
53
|
+
}
|
|
45
54
|
if (userConfig.skillMappings !== undefined) {
|
|
46
55
|
for (const command of ConfigTypes.getSkillCommands()) {
|
|
47
56
|
config.skillMappings[command] = userConfig.skillMappings[command] ?? config.skillMappings[command];
|
|
48
57
|
}
|
|
49
58
|
}
|
|
59
|
+
if (userConfig.writeCapableTools !== undefined) {
|
|
60
|
+
config.writeCapableTools = userConfig.writeCapableTools;
|
|
61
|
+
}
|
|
50
62
|
return config;
|
|
51
63
|
}
|
|
52
64
|
}
|
|
@@ -58,6 +70,11 @@ class ConfigValidator {
|
|
|
58
70
|
if (config.planDirectory.trim() === "") {
|
|
59
71
|
errors.push("Invalid planDirectory: must be a non-empty string.");
|
|
60
72
|
}
|
|
73
|
+
for (const [option, tools] of [["readOnlyTools", config.readOnlyTools], ["writeCapableTools", config.writeCapableTools]]) {
|
|
74
|
+
if (tools.some((tool) => tool.trim() === "")) {
|
|
75
|
+
errors.push(`Invalid ${option}: tool names must be non-empty strings.`);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
61
78
|
for (const command of ConfigTypes.getSkillCommands()) {
|
|
62
79
|
if (config.skillMappings[command].trim() === "") {
|
|
63
80
|
errors.push(`Invalid skillMappings.${command}: must be a non-empty string.`);
|
|
@@ -84,6 +101,23 @@ class ConfigLoader {
|
|
|
84
101
|
const directory = this.stringOption(value)?.replace(/\/+$/, "");
|
|
85
102
|
return directory === "" ? undefined : directory;
|
|
86
103
|
}
|
|
104
|
+
static booleanOption(value) {
|
|
105
|
+
return typeof value === "boolean" ? value : undefined;
|
|
106
|
+
}
|
|
107
|
+
static toolNamesOption(value) {
|
|
108
|
+
if (!Array.isArray(value)) {
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
const toolNames = [];
|
|
112
|
+
for (const valueTool of value) {
|
|
113
|
+
const tool = this.stringOption(valueTool);
|
|
114
|
+
if (tool === undefined) {
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
toolNames.push(tool);
|
|
118
|
+
}
|
|
119
|
+
return toolNames;
|
|
120
|
+
}
|
|
87
121
|
static skillMappingsOption(value) {
|
|
88
122
|
if (typeof value !== "object" || value === null) {
|
|
89
123
|
return;
|
|
@@ -99,11 +133,17 @@ class ConfigLoader {
|
|
|
99
133
|
return skillMappings;
|
|
100
134
|
}
|
|
101
135
|
static getUserConfig(options) {
|
|
136
|
+
const enforceTodoTracker = this.booleanOption(options?.enforceTodoTracker);
|
|
102
137
|
const planDirectory = this.planDirectoryOption(options?.planDirectory);
|
|
138
|
+
const readOnlyTools = this.toolNamesOption(options?.readOnlyTools);
|
|
103
139
|
const skillMappings = this.skillMappingsOption(options?.skillMappings);
|
|
140
|
+
const writeCapableTools = this.toolNamesOption(options?.writeCapableTools);
|
|
104
141
|
return {
|
|
142
|
+
...enforceTodoTracker === undefined ? {} : { enforceTodoTracker },
|
|
105
143
|
...planDirectory === undefined ? {} : { planDirectory },
|
|
106
|
-
...
|
|
144
|
+
...readOnlyTools === undefined ? {} : { readOnlyTools },
|
|
145
|
+
...skillMappings === undefined ? {} : { skillMappings },
|
|
146
|
+
...writeCapableTools === undefined ? {} : { writeCapableTools }
|
|
107
147
|
};
|
|
108
148
|
}
|
|
109
149
|
static loadConfig(options) {
|
|
@@ -151,6 +191,7 @@ var todoTrackerInput = {
|
|
|
151
191
|
};
|
|
152
192
|
var todoTrackerName = "todotracker";
|
|
153
193
|
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.";
|
|
194
|
+
var trackerRefreshMessage = "Update todotracker with the complete current todo list, including newly discovered work and current statuses, before continuing tool work.";
|
|
154
195
|
var todoStatuses = ["pending", "in_progress", "completed", "cancelled"];
|
|
155
196
|
var todoPriorities = ["high", "medium", "low"];
|
|
156
197
|
function isRecord(value) {
|
|
@@ -210,6 +251,119 @@ function todosFromMessages(messages) {
|
|
|
210
251
|
}
|
|
211
252
|
return [];
|
|
212
253
|
}
|
|
254
|
+
var trackerRefreshIntervalMs = 60000;
|
|
255
|
+
function refreshedTrackerState(refreshedAt = Date.now()) {
|
|
256
|
+
return { kind: "tracked", refreshedAt, refreshRequired: false };
|
|
257
|
+
}
|
|
258
|
+
function trackerRefreshDue(state, now = Date.now()) {
|
|
259
|
+
return state.kind === "tracked" && (state.refreshRequired || now - state.refreshedAt >= trackerRefreshIntervalMs);
|
|
260
|
+
}
|
|
261
|
+
function requiresTrackerRefresh(state, tool, now = Date.now()) {
|
|
262
|
+
return tool !== todoTrackerName && trackerRefreshDue(state, now);
|
|
263
|
+
}
|
|
264
|
+
function trackerStateFromMessages(messages) {
|
|
265
|
+
let state = { kind: "untracked" };
|
|
266
|
+
for (const message of messages) {
|
|
267
|
+
if (message.type !== "assistant") {
|
|
268
|
+
continue;
|
|
269
|
+
}
|
|
270
|
+
for (const part of message.content) {
|
|
271
|
+
if (part.type !== "tool" || part.state.status !== "completed") {
|
|
272
|
+
continue;
|
|
273
|
+
}
|
|
274
|
+
if (part.name === todoTrackerName) {
|
|
275
|
+
state = refreshedTrackerState(part.time?.completed ?? part.time?.ran ?? part.time?.created ?? message.time.completed ?? message.time.created);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
return state;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
class TodoTrackerEnforcement {
|
|
283
|
+
context;
|
|
284
|
+
states = new Map;
|
|
285
|
+
constructor(context) {
|
|
286
|
+
this.context = context;
|
|
287
|
+
}
|
|
288
|
+
async stateFor(sessionID) {
|
|
289
|
+
const existing = this.states.get(sessionID);
|
|
290
|
+
if (existing !== undefined) {
|
|
291
|
+
return existing;
|
|
292
|
+
}
|
|
293
|
+
const state = trackerStateFromMessages(await this.context.session.context({ sessionID }));
|
|
294
|
+
this.states.set(sessionID, state);
|
|
295
|
+
return state;
|
|
296
|
+
}
|
|
297
|
+
async requireTrackerRefresh(sessionID) {
|
|
298
|
+
const state = await this.stateFor(sessionID);
|
|
299
|
+
if (state.kind === "tracked") {
|
|
300
|
+
state.refreshRequired = true;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
async register() {
|
|
304
|
+
let before;
|
|
305
|
+
let after;
|
|
306
|
+
let context;
|
|
307
|
+
try {
|
|
308
|
+
before = await this.context.tool.hook("execute.before", async (event) => {
|
|
309
|
+
const state = await this.stateFor(event.sessionID);
|
|
310
|
+
if (requiresTrackerRefresh(state, event.tool)) {
|
|
311
|
+
throw new Error(trackerRefreshMessage);
|
|
312
|
+
}
|
|
313
|
+
});
|
|
314
|
+
after = await this.context.tool.hook("execute.after", async (event) => {
|
|
315
|
+
if (event.status !== "completed") {
|
|
316
|
+
return;
|
|
317
|
+
}
|
|
318
|
+
if (event.tool === todoTrackerName) {
|
|
319
|
+
this.states.set(event.sessionID, refreshedTrackerState());
|
|
320
|
+
}
|
|
321
|
+
});
|
|
322
|
+
context = await this.context.session.hook("context", async (event) => {
|
|
323
|
+
const state = await this.stateFor(event.sessionID);
|
|
324
|
+
if (trackerRefreshDue(state)) {
|
|
325
|
+
event.system.push({ type: "text", text: trackerRefreshMessage });
|
|
326
|
+
}
|
|
327
|
+
});
|
|
328
|
+
const beforeRegistration = before;
|
|
329
|
+
const afterRegistration = after;
|
|
330
|
+
const contextRegistration = context;
|
|
331
|
+
const controller = new AbortController;
|
|
332
|
+
const observer = this.observeSubagentCompletion(controller.signal);
|
|
333
|
+
return {
|
|
334
|
+
dispose: async () => {
|
|
335
|
+
controller.abort();
|
|
336
|
+
await Promise.all([
|
|
337
|
+
beforeRegistration.dispose(),
|
|
338
|
+
afterRegistration.dispose(),
|
|
339
|
+
contextRegistration.dispose(),
|
|
340
|
+
observer
|
|
341
|
+
]);
|
|
342
|
+
}
|
|
343
|
+
};
|
|
344
|
+
} catch (error) {
|
|
345
|
+
await Promise.all([before?.dispose(), after?.dispose(), context?.dispose()]);
|
|
346
|
+
throw error;
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
async observeSubagentCompletion(signal) {
|
|
350
|
+
try {
|
|
351
|
+
for await (const event of this.context.event.subscribe({ signal })) {
|
|
352
|
+
if (event.type !== "session.idle") {
|
|
353
|
+
continue;
|
|
354
|
+
}
|
|
355
|
+
const session = await this.context.session.get({ sessionID: event.data.sessionID });
|
|
356
|
+
if (session.parentID !== undefined) {
|
|
357
|
+
await this.requireTrackerRefresh(session.parentID);
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
} catch (error) {
|
|
361
|
+
if (!signal.aborted) {
|
|
362
|
+
console.error("Unable to watch subagent completion for todo enforcement.", error);
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
}
|
|
213
367
|
|
|
214
368
|
class TodoTracker {
|
|
215
369
|
static marker(status) {
|
|
@@ -224,11 +378,16 @@ class TodoTracker {
|
|
|
224
378
|
return "[-]";
|
|
225
379
|
}
|
|
226
380
|
}
|
|
227
|
-
static sidebar(messages) {
|
|
228
|
-
|
|
381
|
+
static sidebar(messages, completed = false) {
|
|
382
|
+
const todos = todosFromMessages(messages);
|
|
383
|
+
const finalTodo = todos.at(-1);
|
|
384
|
+
if (!completed || finalTodo?.status !== "in_progress" || todos.slice(0, -1).some((todo) => todo.status !== "completed")) {
|
|
385
|
+
return todos;
|
|
386
|
+
}
|
|
387
|
+
return [...todos.slice(0, -1), { ...finalTodo, status: "completed" }];
|
|
229
388
|
}
|
|
230
|
-
static register(context) {
|
|
231
|
-
|
|
389
|
+
static async register(context, config) {
|
|
390
|
+
const tool = await context.tool.transform((tools) => {
|
|
232
391
|
tools.add({
|
|
233
392
|
name: todoTrackerName,
|
|
234
393
|
description: todoTrackerDescription,
|
|
@@ -242,6 +401,24 @@ class TodoTracker {
|
|
|
242
401
|
}
|
|
243
402
|
});
|
|
244
403
|
});
|
|
404
|
+
if (!config.enforceTodoTracker) {
|
|
405
|
+
return tool;
|
|
406
|
+
}
|
|
407
|
+
try {
|
|
408
|
+
const enforcement = await new TodoTrackerEnforcement(context).register();
|
|
409
|
+
return {
|
|
410
|
+
dispose: async () => {
|
|
411
|
+
try {
|
|
412
|
+
await enforcement.dispose();
|
|
413
|
+
} finally {
|
|
414
|
+
await tool.dispose();
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
};
|
|
418
|
+
} catch (error) {
|
|
419
|
+
await tool.dispose();
|
|
420
|
+
throw error;
|
|
421
|
+
}
|
|
245
422
|
}
|
|
246
423
|
}
|
|
247
424
|
|
|
@@ -273,7 +450,7 @@ var server_default = Plugin.define({
|
|
|
273
450
|
});
|
|
274
451
|
let toolRegistration;
|
|
275
452
|
try {
|
|
276
|
-
toolRegistration = await TodoTracker.register(ctx);
|
|
453
|
+
toolRegistration = await TodoTracker.register(ctx, pluginConfig);
|
|
277
454
|
} catch (error) {
|
|
278
455
|
await commandRegistration.dispose();
|
|
279
456
|
throw error;
|
package/dist/tui/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// @bun
|
|
2
2
|
// src/tui/index.tsx
|
|
3
3
|
import { Plugin } from "@opencode/plugin/tui";
|
|
4
|
+
import { fg, StyledText } from "@opentui/core";
|
|
4
5
|
|
|
5
6
|
// node_modules/solid-js/dist/solid.js
|
|
6
7
|
var sharedConfig = {
|
|
@@ -48,22 +49,6 @@ var Listener = null;
|
|
|
48
49
|
var Updates = null;
|
|
49
50
|
var Effects = null;
|
|
50
51
|
var ExecCount = 0;
|
|
51
|
-
function createRoot(fn, detachedOwner) {
|
|
52
|
-
const listener = Listener, owner = Owner, unowned = fn.length === 0, current = detachedOwner === undefined ? owner : detachedOwner, root = unowned ? UNOWNED : {
|
|
53
|
-
owned: null,
|
|
54
|
-
cleanups: null,
|
|
55
|
-
context: current ? current.context : null,
|
|
56
|
-
owner: current
|
|
57
|
-
}, updateFn = unowned ? fn : () => fn(() => untrack(() => cleanNode(root)));
|
|
58
|
-
Owner = root;
|
|
59
|
-
Listener = null;
|
|
60
|
-
try {
|
|
61
|
-
return runUpdates(updateFn, true);
|
|
62
|
-
} finally {
|
|
63
|
-
Listener = listener;
|
|
64
|
-
Owner = owner;
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
52
|
function createSignal(value, options) {
|
|
68
53
|
options = options ? Object.assign({}, signalOptions, options) : signalOptions;
|
|
69
54
|
const s = {
|
|
@@ -92,19 +77,6 @@ function createEffect(fn, value, options) {
|
|
|
92
77
|
c.user = true;
|
|
93
78
|
Effects ? Effects.push(c) : updateComputation(c);
|
|
94
79
|
}
|
|
95
|
-
function createMemo(fn, value, options) {
|
|
96
|
-
options = options ? Object.assign({}, signalOptions, options) : signalOptions;
|
|
97
|
-
const c = createComputation(fn, value, true, 0);
|
|
98
|
-
c.observers = null;
|
|
99
|
-
c.observerSlots = null;
|
|
100
|
-
c.comparator = options.equals || undefined;
|
|
101
|
-
if (Scheduler && Transition && Transition.running) {
|
|
102
|
-
c.tState = STALE;
|
|
103
|
-
Updates.push(c);
|
|
104
|
-
} else
|
|
105
|
-
updateComputation(c);
|
|
106
|
-
return readSignal.bind(c);
|
|
107
|
-
}
|
|
108
80
|
function untrack(fn) {
|
|
109
81
|
if (!ExternalSourceConfig && Listener === null)
|
|
110
82
|
return fn();
|
|
@@ -623,127 +595,6 @@ function handleError(err, owner = Owner) {
|
|
|
623
595
|
runErrors(error, fns, owner);
|
|
624
596
|
}
|
|
625
597
|
var FALLBACK = Symbol("fallback");
|
|
626
|
-
function dispose(d) {
|
|
627
|
-
for (let i = 0;i < d.length; i++)
|
|
628
|
-
d[i]();
|
|
629
|
-
}
|
|
630
|
-
function mapArray(list, mapFn, options = {}) {
|
|
631
|
-
let items = [], mapped = [], disposers = [], len = 0, indexes = mapFn.length > 1 ? [] : null;
|
|
632
|
-
onCleanup(() => dispose(disposers));
|
|
633
|
-
return () => {
|
|
634
|
-
let newItems = list() || [], newLen = newItems.length, i, j;
|
|
635
|
-
newItems[$TRACK];
|
|
636
|
-
return untrack(() => {
|
|
637
|
-
let newIndices, newIndicesNext, temp, tempdisposers, tempIndexes, start, end, newEnd, item;
|
|
638
|
-
if (newLen === 0) {
|
|
639
|
-
if (len !== 0) {
|
|
640
|
-
dispose(disposers);
|
|
641
|
-
disposers = [];
|
|
642
|
-
items = [];
|
|
643
|
-
mapped = [];
|
|
644
|
-
len = 0;
|
|
645
|
-
indexes && (indexes = []);
|
|
646
|
-
}
|
|
647
|
-
if (options.fallback) {
|
|
648
|
-
items = [FALLBACK];
|
|
649
|
-
mapped[0] = createRoot((disposer) => {
|
|
650
|
-
disposers[0] = disposer;
|
|
651
|
-
return options.fallback();
|
|
652
|
-
});
|
|
653
|
-
len = 1;
|
|
654
|
-
}
|
|
655
|
-
} else if (len === 0) {
|
|
656
|
-
mapped = new Array(newLen);
|
|
657
|
-
for (j = 0;j < newLen; j++) {
|
|
658
|
-
items[j] = newItems[j];
|
|
659
|
-
mapped[j] = createRoot(mapper);
|
|
660
|
-
}
|
|
661
|
-
len = newLen;
|
|
662
|
-
} else {
|
|
663
|
-
temp = new Array(newLen);
|
|
664
|
-
tempdisposers = new Array(newLen);
|
|
665
|
-
indexes && (tempIndexes = new Array(newLen));
|
|
666
|
-
for (start = 0, end = Math.min(len, newLen);start < end && items[start] === newItems[start]; start++)
|
|
667
|
-
;
|
|
668
|
-
for (end = len - 1, newEnd = newLen - 1;end >= start && newEnd >= start && items[end] === newItems[newEnd]; end--, newEnd--) {
|
|
669
|
-
temp[newEnd] = mapped[end];
|
|
670
|
-
tempdisposers[newEnd] = disposers[end];
|
|
671
|
-
indexes && (tempIndexes[newEnd] = indexes[end]);
|
|
672
|
-
}
|
|
673
|
-
newIndices = new Map;
|
|
674
|
-
newIndicesNext = new Array(newEnd + 1);
|
|
675
|
-
for (j = newEnd;j >= start; j--) {
|
|
676
|
-
item = newItems[j];
|
|
677
|
-
i = newIndices.get(item);
|
|
678
|
-
newIndicesNext[j] = i === undefined ? -1 : i;
|
|
679
|
-
newIndices.set(item, j);
|
|
680
|
-
}
|
|
681
|
-
for (i = start;i <= end; i++) {
|
|
682
|
-
item = items[i];
|
|
683
|
-
j = newIndices.get(item);
|
|
684
|
-
if (j !== undefined && j !== -1) {
|
|
685
|
-
temp[j] = mapped[i];
|
|
686
|
-
tempdisposers[j] = disposers[i];
|
|
687
|
-
indexes && (tempIndexes[j] = indexes[i]);
|
|
688
|
-
j = newIndicesNext[j];
|
|
689
|
-
newIndices.set(item, j);
|
|
690
|
-
} else
|
|
691
|
-
disposers[i]();
|
|
692
|
-
}
|
|
693
|
-
for (j = start;j < newLen; j++) {
|
|
694
|
-
if (j in temp) {
|
|
695
|
-
mapped[j] = temp[j];
|
|
696
|
-
disposers[j] = tempdisposers[j];
|
|
697
|
-
if (indexes) {
|
|
698
|
-
indexes[j] = tempIndexes[j];
|
|
699
|
-
indexes[j](j);
|
|
700
|
-
}
|
|
701
|
-
} else
|
|
702
|
-
mapped[j] = createRoot(mapper);
|
|
703
|
-
}
|
|
704
|
-
mapped = mapped.slice(0, len = newLen);
|
|
705
|
-
items = newItems.slice(0);
|
|
706
|
-
}
|
|
707
|
-
return mapped;
|
|
708
|
-
});
|
|
709
|
-
function mapper(disposer) {
|
|
710
|
-
disposers[j] = disposer;
|
|
711
|
-
if (indexes) {
|
|
712
|
-
const [s, set] = createSignal(j);
|
|
713
|
-
indexes[j] = set;
|
|
714
|
-
return mapFn(newItems[j], s);
|
|
715
|
-
}
|
|
716
|
-
return mapFn(newItems[j]);
|
|
717
|
-
}
|
|
718
|
-
};
|
|
719
|
-
}
|
|
720
|
-
var narrowedError = (name) => `Stale read from <${name}>.`;
|
|
721
|
-
function For(props) {
|
|
722
|
-
const fallback = "fallback" in props && {
|
|
723
|
-
fallback: () => props.fallback
|
|
724
|
-
};
|
|
725
|
-
return createMemo(mapArray(() => props.each, props.children, fallback || undefined));
|
|
726
|
-
}
|
|
727
|
-
function Show(props) {
|
|
728
|
-
const keyed = props.keyed;
|
|
729
|
-
const conditionValue = createMemo(() => props.when, undefined, undefined);
|
|
730
|
-
const condition = keyed ? conditionValue : createMemo(conditionValue, undefined, {
|
|
731
|
-
equals: (a, b) => !a === !b
|
|
732
|
-
});
|
|
733
|
-
return createMemo(() => {
|
|
734
|
-
const c = condition();
|
|
735
|
-
if (c) {
|
|
736
|
-
const child = props.children;
|
|
737
|
-
const fn = typeof child === "function" && child.length > 0;
|
|
738
|
-
return fn ? untrack(() => child(keyed ? c : () => {
|
|
739
|
-
if (!untrack(condition))
|
|
740
|
-
throw narrowedError("Show");
|
|
741
|
-
return conditionValue();
|
|
742
|
-
})) : child;
|
|
743
|
-
}
|
|
744
|
-
return props.fallback;
|
|
745
|
-
}, undefined, undefined);
|
|
746
|
-
}
|
|
747
598
|
|
|
748
599
|
// src/config/ConfigDefaults.ts
|
|
749
600
|
class ConfigDefaults {
|
|
@@ -758,8 +609,11 @@ class ConfigDefaults {
|
|
|
758
609
|
}
|
|
759
610
|
static getDefaultConfig() {
|
|
760
611
|
return {
|
|
612
|
+
enforceTodoTracker: true,
|
|
761
613
|
planDirectory: "docs/_planning",
|
|
762
|
-
|
|
614
|
+
readOnlyTools: [],
|
|
615
|
+
skillMappings: this.getDefaultSkillMappings(),
|
|
616
|
+
writeCapableTools: []
|
|
763
617
|
};
|
|
764
618
|
}
|
|
765
619
|
}
|
|
@@ -782,14 +636,23 @@ class ConfigTypes {
|
|
|
782
636
|
class ConfigMerger {
|
|
783
637
|
static merge(userConfig) {
|
|
784
638
|
const config = ConfigDefaults.getDefaultConfig();
|
|
639
|
+
if (userConfig.enforceTodoTracker !== undefined) {
|
|
640
|
+
config.enforceTodoTracker = userConfig.enforceTodoTracker;
|
|
641
|
+
}
|
|
785
642
|
if (userConfig.planDirectory !== undefined) {
|
|
786
643
|
config.planDirectory = userConfig.planDirectory;
|
|
787
644
|
}
|
|
645
|
+
if (userConfig.readOnlyTools !== undefined) {
|
|
646
|
+
config.readOnlyTools = userConfig.readOnlyTools;
|
|
647
|
+
}
|
|
788
648
|
if (userConfig.skillMappings !== undefined) {
|
|
789
649
|
for (const command of ConfigTypes.getSkillCommands()) {
|
|
790
650
|
config.skillMappings[command] = userConfig.skillMappings[command] ?? config.skillMappings[command];
|
|
791
651
|
}
|
|
792
652
|
}
|
|
653
|
+
if (userConfig.writeCapableTools !== undefined) {
|
|
654
|
+
config.writeCapableTools = userConfig.writeCapableTools;
|
|
655
|
+
}
|
|
793
656
|
return config;
|
|
794
657
|
}
|
|
795
658
|
}
|
|
@@ -801,6 +664,11 @@ class ConfigValidator {
|
|
|
801
664
|
if (config.planDirectory.trim() === "") {
|
|
802
665
|
errors.push("Invalid planDirectory: must be a non-empty string.");
|
|
803
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
|
+
}
|
|
804
672
|
for (const command of ConfigTypes.getSkillCommands()) {
|
|
805
673
|
if (config.skillMappings[command].trim() === "") {
|
|
806
674
|
errors.push(`Invalid skillMappings.${command}: must be a non-empty string.`);
|
|
@@ -827,6 +695,23 @@ class ConfigLoader {
|
|
|
827
695
|
const directory = this.stringOption(value)?.replace(/\/+$/, "");
|
|
828
696
|
return directory === "" ? undefined : directory;
|
|
829
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
|
+
}
|
|
830
715
|
static skillMappingsOption(value) {
|
|
831
716
|
if (typeof value !== "object" || value === null) {
|
|
832
717
|
return;
|
|
@@ -842,11 +727,17 @@ class ConfigLoader {
|
|
|
842
727
|
return skillMappings;
|
|
843
728
|
}
|
|
844
729
|
static getUserConfig(options) {
|
|
730
|
+
const enforceTodoTracker = this.booleanOption(options?.enforceTodoTracker);
|
|
845
731
|
const planDirectory = this.planDirectoryOption(options?.planDirectory);
|
|
732
|
+
const readOnlyTools = this.toolNamesOption(options?.readOnlyTools);
|
|
846
733
|
const skillMappings = this.skillMappingsOption(options?.skillMappings);
|
|
734
|
+
const writeCapableTools = this.toolNamesOption(options?.writeCapableTools);
|
|
847
735
|
return {
|
|
736
|
+
...enforceTodoTracker === undefined ? {} : { enforceTodoTracker },
|
|
848
737
|
...planDirectory === undefined ? {} : { planDirectory },
|
|
849
|
-
...
|
|
738
|
+
...readOnlyTools === undefined ? {} : { readOnlyTools },
|
|
739
|
+
...skillMappings === undefined ? {} : { skillMappings },
|
|
740
|
+
...writeCapableTools === undefined ? {} : { writeCapableTools }
|
|
850
741
|
};
|
|
851
742
|
}
|
|
852
743
|
static loadConfig(options) {
|
|
@@ -1085,6 +976,7 @@ var todoTrackerInput = {
|
|
|
1085
976
|
};
|
|
1086
977
|
var todoTrackerName = "todotracker";
|
|
1087
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.";
|
|
1088
980
|
var todoStatuses = ["pending", "in_progress", "completed", "cancelled"];
|
|
1089
981
|
var todoPriorities = ["high", "medium", "low"];
|
|
1090
982
|
function isRecord(value) {
|
|
@@ -1144,6 +1036,119 @@ function todosFromMessages(messages) {
|
|
|
1144
1036
|
}
|
|
1145
1037
|
return [];
|
|
1146
1038
|
}
|
|
1039
|
+
var trackerRefreshIntervalMs = 60000;
|
|
1040
|
+
function refreshedTrackerState(refreshedAt = Date.now()) {
|
|
1041
|
+
return { kind: "tracked", refreshedAt, refreshRequired: false };
|
|
1042
|
+
}
|
|
1043
|
+
function trackerRefreshDue(state, now = Date.now()) {
|
|
1044
|
+
return state.kind === "tracked" && (state.refreshRequired || now - state.refreshedAt >= trackerRefreshIntervalMs);
|
|
1045
|
+
}
|
|
1046
|
+
function requiresTrackerRefresh(state, tool, now = Date.now()) {
|
|
1047
|
+
return tool !== todoTrackerName && trackerRefreshDue(state, now);
|
|
1048
|
+
}
|
|
1049
|
+
function trackerStateFromMessages(messages) {
|
|
1050
|
+
let state = { kind: "untracked" };
|
|
1051
|
+
for (const message of messages) {
|
|
1052
|
+
if (message.type !== "assistant") {
|
|
1053
|
+
continue;
|
|
1054
|
+
}
|
|
1055
|
+
for (const part of message.content) {
|
|
1056
|
+
if (part.type !== "tool" || part.state.status !== "completed") {
|
|
1057
|
+
continue;
|
|
1058
|
+
}
|
|
1059
|
+
if (part.name === todoTrackerName) {
|
|
1060
|
+
state = refreshedTrackerState(part.time?.completed ?? part.time?.ran ?? part.time?.created ?? message.time.completed ?? message.time.created);
|
|
1061
|
+
}
|
|
1062
|
+
}
|
|
1063
|
+
}
|
|
1064
|
+
return state;
|
|
1065
|
+
}
|
|
1066
|
+
|
|
1067
|
+
class TodoTrackerEnforcement {
|
|
1068
|
+
context;
|
|
1069
|
+
states = new Map;
|
|
1070
|
+
constructor(context) {
|
|
1071
|
+
this.context = context;
|
|
1072
|
+
}
|
|
1073
|
+
async stateFor(sessionID) {
|
|
1074
|
+
const existing = this.states.get(sessionID);
|
|
1075
|
+
if (existing !== undefined) {
|
|
1076
|
+
return existing;
|
|
1077
|
+
}
|
|
1078
|
+
const state = trackerStateFromMessages(await this.context.session.context({ sessionID }));
|
|
1079
|
+
this.states.set(sessionID, state);
|
|
1080
|
+
return state;
|
|
1081
|
+
}
|
|
1082
|
+
async requireTrackerRefresh(sessionID) {
|
|
1083
|
+
const state = await this.stateFor(sessionID);
|
|
1084
|
+
if (state.kind === "tracked") {
|
|
1085
|
+
state.refreshRequired = true;
|
|
1086
|
+
}
|
|
1087
|
+
}
|
|
1088
|
+
async register() {
|
|
1089
|
+
let before;
|
|
1090
|
+
let after;
|
|
1091
|
+
let context;
|
|
1092
|
+
try {
|
|
1093
|
+
before = await this.context.tool.hook("execute.before", async (event) => {
|
|
1094
|
+
const state = await this.stateFor(event.sessionID);
|
|
1095
|
+
if (requiresTrackerRefresh(state, event.tool)) {
|
|
1096
|
+
throw new Error(trackerRefreshMessage);
|
|
1097
|
+
}
|
|
1098
|
+
});
|
|
1099
|
+
after = await this.context.tool.hook("execute.after", async (event) => {
|
|
1100
|
+
if (event.status !== "completed") {
|
|
1101
|
+
return;
|
|
1102
|
+
}
|
|
1103
|
+
if (event.tool === todoTrackerName) {
|
|
1104
|
+
this.states.set(event.sessionID, refreshedTrackerState());
|
|
1105
|
+
}
|
|
1106
|
+
});
|
|
1107
|
+
context = await this.context.session.hook("context", async (event) => {
|
|
1108
|
+
const state = await this.stateFor(event.sessionID);
|
|
1109
|
+
if (trackerRefreshDue(state)) {
|
|
1110
|
+
event.system.push({ type: "text", text: trackerRefreshMessage });
|
|
1111
|
+
}
|
|
1112
|
+
});
|
|
1113
|
+
const beforeRegistration = before;
|
|
1114
|
+
const afterRegistration = after;
|
|
1115
|
+
const contextRegistration = context;
|
|
1116
|
+
const controller = new AbortController;
|
|
1117
|
+
const observer = this.observeSubagentCompletion(controller.signal);
|
|
1118
|
+
return {
|
|
1119
|
+
dispose: async () => {
|
|
1120
|
+
controller.abort();
|
|
1121
|
+
await Promise.all([
|
|
1122
|
+
beforeRegistration.dispose(),
|
|
1123
|
+
afterRegistration.dispose(),
|
|
1124
|
+
contextRegistration.dispose(),
|
|
1125
|
+
observer
|
|
1126
|
+
]);
|
|
1127
|
+
}
|
|
1128
|
+
};
|
|
1129
|
+
} catch (error) {
|
|
1130
|
+
await Promise.all([before?.dispose(), after?.dispose(), context?.dispose()]);
|
|
1131
|
+
throw error;
|
|
1132
|
+
}
|
|
1133
|
+
}
|
|
1134
|
+
async observeSubagentCompletion(signal) {
|
|
1135
|
+
try {
|
|
1136
|
+
for await (const event of this.context.event.subscribe({ signal })) {
|
|
1137
|
+
if (event.type !== "session.idle") {
|
|
1138
|
+
continue;
|
|
1139
|
+
}
|
|
1140
|
+
const session = await this.context.session.get({ sessionID: event.data.sessionID });
|
|
1141
|
+
if (session.parentID !== undefined) {
|
|
1142
|
+
await this.requireTrackerRefresh(session.parentID);
|
|
1143
|
+
}
|
|
1144
|
+
}
|
|
1145
|
+
} catch (error) {
|
|
1146
|
+
if (!signal.aborted) {
|
|
1147
|
+
console.error("Unable to watch subagent completion for todo enforcement.", error);
|
|
1148
|
+
}
|
|
1149
|
+
}
|
|
1150
|
+
}
|
|
1151
|
+
}
|
|
1147
1152
|
|
|
1148
1153
|
class TodoTracker {
|
|
1149
1154
|
static marker(status) {
|
|
@@ -1158,11 +1163,16 @@ class TodoTracker {
|
|
|
1158
1163
|
return "[-]";
|
|
1159
1164
|
}
|
|
1160
1165
|
}
|
|
1161
|
-
static sidebar(messages) {
|
|
1162
|
-
|
|
1166
|
+
static sidebar(messages, completed = false) {
|
|
1167
|
+
const todos = todosFromMessages(messages);
|
|
1168
|
+
const finalTodo = todos.at(-1);
|
|
1169
|
+
if (!completed || finalTodo?.status !== "in_progress" || todos.slice(0, -1).some((todo) => todo.status !== "completed")) {
|
|
1170
|
+
return todos;
|
|
1171
|
+
}
|
|
1172
|
+
return [...todos.slice(0, -1), { ...finalTodo, status: "completed" }];
|
|
1163
1173
|
}
|
|
1164
|
-
static register(context) {
|
|
1165
|
-
|
|
1174
|
+
static async register(context, config) {
|
|
1175
|
+
const tool = await context.tool.transform((tools) => {
|
|
1166
1176
|
tools.add({
|
|
1167
1177
|
name: todoTrackerName,
|
|
1168
1178
|
description: todoTrackerDescription,
|
|
@@ -1176,6 +1186,24 @@ class TodoTracker {
|
|
|
1176
1186
|
}
|
|
1177
1187
|
});
|
|
1178
1188
|
});
|
|
1189
|
+
if (!config.enforceTodoTracker) {
|
|
1190
|
+
return tool;
|
|
1191
|
+
}
|
|
1192
|
+
try {
|
|
1193
|
+
const enforcement = await new TodoTrackerEnforcement(context).register();
|
|
1194
|
+
return {
|
|
1195
|
+
dispose: async () => {
|
|
1196
|
+
try {
|
|
1197
|
+
await enforcement.dispose();
|
|
1198
|
+
} finally {
|
|
1199
|
+
await tool.dispose();
|
|
1200
|
+
}
|
|
1201
|
+
}
|
|
1202
|
+
};
|
|
1203
|
+
} catch (error) {
|
|
1204
|
+
await tool.dispose();
|
|
1205
|
+
throw error;
|
|
1206
|
+
}
|
|
1179
1207
|
}
|
|
1180
1208
|
}
|
|
1181
1209
|
|
|
@@ -1186,28 +1214,52 @@ function PickerCommandLayer(props) {
|
|
|
1186
1214
|
return /* @__PURE__ */ jsx(Fragment, {});
|
|
1187
1215
|
}
|
|
1188
1216
|
function TodoSidebar(props) {
|
|
1189
|
-
const todos =
|
|
1217
|
+
const todos = () => TodoTracker.sidebar(props.context.data.session.message.list(props.sessionID), props.context.data.session.status(props.sessionID) === "idle");
|
|
1190
1218
|
const color = (status) => status === "in_progress" ? props.context.theme.text.feedback.warning.base : props.context.theme.text.muted;
|
|
1219
|
+
const line = (todo) => `${TodoTracker.marker(todo.status)} (${todo.priority}) ${todo.content}`;
|
|
1220
|
+
const plainContent = (items) => items.map(line).join(`
|
|
1221
|
+
`);
|
|
1222
|
+
const content = (items) => new StyledText(items.flatMap((todo, index) => [
|
|
1223
|
+
fg(color(todo.status))(`${line(todo)}${index === items.length - 1 ? "" : `
|
|
1224
|
+
`}`)
|
|
1225
|
+
]));
|
|
1226
|
+
let heading;
|
|
1227
|
+
let list;
|
|
1228
|
+
const render = () => {
|
|
1229
|
+
const items = todos();
|
|
1230
|
+
if (heading !== undefined) {
|
|
1231
|
+
heading.content = items.length > 0 ? "Todos" : "";
|
|
1232
|
+
}
|
|
1233
|
+
if (list !== undefined) {
|
|
1234
|
+
list.content = content(items);
|
|
1235
|
+
}
|
|
1236
|
+
};
|
|
1237
|
+
const initialTodos = todos();
|
|
1191
1238
|
onMount(() => {
|
|
1239
|
+
render();
|
|
1240
|
+
const unlisten = props.context.data.listen?.(() => {
|
|
1241
|
+
render();
|
|
1242
|
+
});
|
|
1243
|
+
onCleanup(() => unlisten?.());
|
|
1192
1244
|
props.context.data.session.message.sync(props.sessionID).catch(() => {});
|
|
1193
1245
|
});
|
|
1194
|
-
return /* @__PURE__ */
|
|
1195
|
-
|
|
1196
|
-
children:
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1246
|
+
return /* @__PURE__ */ jsxs("box", {
|
|
1247
|
+
flexDirection: "column",
|
|
1248
|
+
children: [
|
|
1249
|
+
/* @__PURE__ */ jsx("text", {
|
|
1250
|
+
ref: (node) => {
|
|
1251
|
+
heading = node;
|
|
1252
|
+
},
|
|
1253
|
+
children: initialTodos.length > 0 ? "Todos" : ""
|
|
1254
|
+
}),
|
|
1255
|
+
/* @__PURE__ */ jsx("text", {
|
|
1256
|
+
ref: (node) => {
|
|
1257
|
+
list = node;
|
|
1258
|
+
},
|
|
1259
|
+
fg: props.context.theme.text.muted,
|
|
1260
|
+
content: plainContent(initialTodos)
|
|
1261
|
+
})
|
|
1262
|
+
]
|
|
1211
1263
|
});
|
|
1212
1264
|
}
|
|
1213
1265
|
var tui_default = Plugin.define({
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@encoredigitalgroup/opencode",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.2.
|
|
4
|
+
"version": "0.2.6",
|
|
5
5
|
"description": "OpenCode file-picker commands for planning skills.",
|
|
6
6
|
"publishConfig": {
|
|
7
7
|
"access": "public"
|
|
@@ -20,16 +20,16 @@
|
|
|
20
20
|
"solid-js": "1.9.12"
|
|
21
21
|
},
|
|
22
22
|
"devDependencies": {
|
|
23
|
-
"@opencode/cli": "2.
|
|
24
|
-
"@opencode/client": "2.
|
|
25
|
-
"@opencode/plugin": "2.
|
|
23
|
+
"@opencode/cli": "2.1.4",
|
|
24
|
+
"@opencode/client": "2.1.4",
|
|
25
|
+
"@opencode/plugin": "2.1.4",
|
|
26
26
|
"@opentui/core": "0.5.14",
|
|
27
27
|
"@types/bun": "1.4.0",
|
|
28
28
|
"tsfmt": "^0.3.0-beta9",
|
|
29
29
|
"typescript": "5.8.2"
|
|
30
30
|
},
|
|
31
31
|
"scripts": {
|
|
32
|
-
"build": "rm -rf dist && bun build src/server/index.ts src/tui/index.tsx --outdir dist --target bun --external '@opencode/plugin' --external '@opencode/plugin/*' --external '@opentui/solid' --external '@opentui/solid/*' && tsc --emitDeclarationOnly",
|
|
32
|
+
"build": "rm -rf dist && bun build src/server/index.ts src/tui/index.tsx --outdir dist --target bun --external '@opencode/plugin' --external '@opencode/plugin/*' --external '@opentui/core' --external '@opentui/core/*' --external '@opentui/solid' --external '@opentui/solid/*' && tsc --emitDeclarationOnly",
|
|
33
33
|
"check": "bun run typecheck && bun run build",
|
|
34
34
|
"pack:check": "bun pm pack --dry-run",
|
|
35
35
|
"test": "bun test",
|