@encoredigitalgroup/opencode 0.2.3 → 0.2.4
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 +21 -7
- package/dist/lib/TodoTracker.d.ts +53 -0
- package/dist/lib/TodoTracker.test.d.ts +1 -0
- package/dist/server/index.js +132 -2
- package/dist/tui/index.js +907 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
`@encoredigitalgroup/opencode` adds file pickers for OpenCode planning commands.
|
|
4
4
|
|
|
5
|
-
The TUI provides `/plan-review`, `/plan-resync`, `/plan-split`, and `/plan-execute` commands. Each command opens a picker for plans in `docs/_planning` and
|
|
6
|
-
selected plan into the prompt. The server entry attaches the matching installed skill to the prompt.
|
|
5
|
+
The TUI provides `/plan-review`, `/plan-resync`, `/plan-split`, and `/plan-execute` commands. Each command opens a picker for plans in `docs/_planning` and
|
|
6
|
+
inserts the selected plan into the prompt. The server entry attaches the matching installed skill to the prompt.
|
|
7
7
|
|
|
8
8
|
## Install
|
|
9
9
|
|
|
@@ -24,15 +24,15 @@ OpenCode v2 loads this package from the `plugins` array. Add an object entry wit
|
|
|
24
24
|
}
|
|
25
25
|
```
|
|
26
26
|
|
|
27
|
-
Restart OpenCode after changing the configuration. OpenCode downloads missing npm plugins that appear in the `plugins` array, so you do not need to install this
|
|
28
|
-
with Bun or npm yourself. This package supports OpenCode v2 only; replace the legacy `plugin` configuration entry when migrating from OpenCode v1.
|
|
27
|
+
Restart OpenCode after changing the configuration. OpenCode downloads missing npm plugins that appear in the `plugins` array, so you do not need to install this
|
|
28
|
+
package with Bun or npm yourself. This package supports OpenCode v2 only; replace the legacy `plugin` configuration entry when migrating from OpenCode v1.
|
|
29
29
|
|
|
30
30
|
The plugin resolves the mapped skills through OpenCode, so those skills can live in any location that OpenCode discovers.
|
|
31
31
|
|
|
32
32
|
## Configuration
|
|
33
33
|
|
|
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
|
-
supported command's installed skill name. Unspecified values use the defaults: `docs/_planning` and the command's existing skill name.
|
|
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. Unspecified values use the defaults: `docs/_planning` and the command's existing skill name.
|
|
36
36
|
|
|
37
37
|
```json
|
|
38
38
|
{
|
|
@@ -52,7 +52,21 @@ supported command's installed skill name. Unspecified values use the defaults: `
|
|
|
52
52
|
}
|
|
53
53
|
```
|
|
54
54
|
|
|
55
|
-
Launching a picker from an existing session submits to that session. Launching one from the home screen creates and displays a new session before submitting the
|
|
55
|
+
Launching a picker from an existing session submits to that session. Launching one from the home screen creates and displays a new session before submitting the
|
|
56
|
+
selected plan.
|
|
57
|
+
|
|
58
|
+
## Todo tracker
|
|
59
|
+
|
|
60
|
+
The plugin registers the direct `todotracker` tool. Each call replaces the current session's complete ordered todo list. Pass an empty `todos` array to clear the
|
|
61
|
+
list.
|
|
62
|
+
Each todo requires:
|
|
63
|
+
|
|
64
|
+
- `content`: a non-empty string containing at least one non-whitespace character.
|
|
65
|
+
- `status`: `pending`, `in_progress`, `completed`, or `cancelled`.
|
|
66
|
+
- `priority`: `high`, `medium`, or `low`.
|
|
67
|
+
|
|
68
|
+
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
|
+
message cache. That cache starts with the newest 20 messages, so older results are not shown until the host has loaded them.
|
|
56
70
|
|
|
57
71
|
## Development
|
|
58
72
|
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import type { SessionMessageInfo } from "@opencode/client";
|
|
2
|
+
import type { Plugin as ServerPlugin } from "@opencode/plugin";
|
|
3
|
+
export type TodoStatus = "pending" | "in_progress" | "completed" | "cancelled";
|
|
4
|
+
export type TodoPriority = "high" | "medium" | "low";
|
|
5
|
+
export type Todo = {
|
|
6
|
+
content: string;
|
|
7
|
+
status: TodoStatus;
|
|
8
|
+
priority: TodoPriority;
|
|
9
|
+
};
|
|
10
|
+
export type TodoTrackerInput = {
|
|
11
|
+
todos: Todo[];
|
|
12
|
+
};
|
|
13
|
+
export type TodoTrackerResultMetadata = {
|
|
14
|
+
todos: Todo[];
|
|
15
|
+
};
|
|
16
|
+
export declare const todoTrackerInput: {
|
|
17
|
+
readonly type: "object";
|
|
18
|
+
readonly additionalProperties: false;
|
|
19
|
+
readonly required: readonly ["todos"];
|
|
20
|
+
readonly properties: {
|
|
21
|
+
readonly todos: {
|
|
22
|
+
readonly type: "array";
|
|
23
|
+
readonly items: {
|
|
24
|
+
readonly type: "object";
|
|
25
|
+
readonly additionalProperties: false;
|
|
26
|
+
readonly required: readonly ["content", "status", "priority"];
|
|
27
|
+
readonly properties: {
|
|
28
|
+
readonly content: {
|
|
29
|
+
readonly type: "string";
|
|
30
|
+
readonly minLength: 1;
|
|
31
|
+
readonly pattern: "\\S";
|
|
32
|
+
};
|
|
33
|
+
readonly status: {
|
|
34
|
+
readonly enum: readonly ["pending", "in_progress", "completed", "cancelled"];
|
|
35
|
+
};
|
|
36
|
+
readonly priority: {
|
|
37
|
+
readonly enum: readonly ["high", "medium", "low"];
|
|
38
|
+
};
|
|
39
|
+
};
|
|
40
|
+
};
|
|
41
|
+
};
|
|
42
|
+
};
|
|
43
|
+
};
|
|
44
|
+
export declare const todoTrackerName = "todotracker";
|
|
45
|
+
export declare const todoTrackerDescription: string;
|
|
46
|
+
export declare function isTodoTrackerInput(value: unknown): value is TodoTrackerInput;
|
|
47
|
+
export declare function isTodoResultMetadata(value: unknown): value is TodoTrackerResultMetadata;
|
|
48
|
+
export declare function todosFromMessages(messages: readonly SessionMessageInfo[]): readonly Todo[];
|
|
49
|
+
export declare class TodoTracker {
|
|
50
|
+
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>;
|
|
53
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/server/index.js
CHANGED
|
@@ -128,6 +128,123 @@ class SkillCommands {
|
|
|
128
128
|
}
|
|
129
129
|
}
|
|
130
130
|
|
|
131
|
+
// src/lib/TodoTracker.ts
|
|
132
|
+
var todoTrackerInput = {
|
|
133
|
+
type: "object",
|
|
134
|
+
additionalProperties: false,
|
|
135
|
+
required: ["todos"],
|
|
136
|
+
properties: {
|
|
137
|
+
todos: {
|
|
138
|
+
type: "array",
|
|
139
|
+
items: {
|
|
140
|
+
type: "object",
|
|
141
|
+
additionalProperties: false,
|
|
142
|
+
required: ["content", "status", "priority"],
|
|
143
|
+
properties: {
|
|
144
|
+
content: { type: "string", minLength: 1, pattern: "\\S" },
|
|
145
|
+
status: { enum: ["pending", "in_progress", "completed", "cancelled"] },
|
|
146
|
+
priority: { enum: ["high", "medium", "low"] }
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
};
|
|
152
|
+
var todoTrackerName = "todotracker";
|
|
153
|
+
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.";
|
|
154
|
+
var todoStatuses = ["pending", "in_progress", "completed", "cancelled"];
|
|
155
|
+
var todoPriorities = ["high", "medium", "low"];
|
|
156
|
+
function isRecord(value) {
|
|
157
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
158
|
+
}
|
|
159
|
+
function hasOnlyKeys(value, keys) {
|
|
160
|
+
const expected = new Set(keys);
|
|
161
|
+
return Object.keys(value).length === keys.length && Object.keys(value).every((key) => expected.has(key));
|
|
162
|
+
}
|
|
163
|
+
function isTodoStatus(value) {
|
|
164
|
+
return typeof value === "string" && todoStatuses.includes(value);
|
|
165
|
+
}
|
|
166
|
+
function isTodoPriority(value) {
|
|
167
|
+
return typeof value === "string" && todoPriorities.includes(value);
|
|
168
|
+
}
|
|
169
|
+
function isTodo(value) {
|
|
170
|
+
if (!isRecord(value) || !hasOnlyKeys(value, ["content", "status", "priority"])) {
|
|
171
|
+
return false;
|
|
172
|
+
}
|
|
173
|
+
return typeof value.content === "string" && value.content.length >= 1 && /\S/.test(value.content) && isTodoStatus(value.status) && isTodoPriority(value.priority);
|
|
174
|
+
}
|
|
175
|
+
function isTodoList(value) {
|
|
176
|
+
return Array.isArray(value) && value.every(isTodo);
|
|
177
|
+
}
|
|
178
|
+
function isTodoTrackerInput(value) {
|
|
179
|
+
return isRecord(value) && hasOnlyKeys(value, ["todos"]) && isTodoList(value.todos);
|
|
180
|
+
}
|
|
181
|
+
function isTodoResultMetadata(value) {
|
|
182
|
+
return isTodoTrackerInput(value);
|
|
183
|
+
}
|
|
184
|
+
function todosFromToolResult(metadata, content) {
|
|
185
|
+
if (isTodoResultMetadata(metadata)) {
|
|
186
|
+
return metadata.todos;
|
|
187
|
+
}
|
|
188
|
+
for (const item of content) {
|
|
189
|
+
if (item.type !== "text" || item.text === undefined) {
|
|
190
|
+
continue;
|
|
191
|
+
}
|
|
192
|
+
try {
|
|
193
|
+
const todos = JSON.parse(item.text);
|
|
194
|
+
if (isTodoList(todos)) {
|
|
195
|
+
return todos;
|
|
196
|
+
}
|
|
197
|
+
} catch {}
|
|
198
|
+
}
|
|
199
|
+
return [];
|
|
200
|
+
}
|
|
201
|
+
function todosFromMessages(messages) {
|
|
202
|
+
for (const message of messages.toReversed()) {
|
|
203
|
+
if (message.type !== "assistant")
|
|
204
|
+
continue;
|
|
205
|
+
for (const part of message.content.toReversed()) {
|
|
206
|
+
if (part.type === "tool" && part.name === todoTrackerName && part.state.status === "completed") {
|
|
207
|
+
return todosFromToolResult(part.state.metadata, part.state.content);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
return [];
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
class TodoTracker {
|
|
215
|
+
static marker(status) {
|
|
216
|
+
switch (status) {
|
|
217
|
+
case "pending":
|
|
218
|
+
return "[ ]";
|
|
219
|
+
case "in_progress":
|
|
220
|
+
return "[~]";
|
|
221
|
+
case "completed":
|
|
222
|
+
return "[x]";
|
|
223
|
+
case "cancelled":
|
|
224
|
+
return "[-]";
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
static sidebar(messages) {
|
|
228
|
+
return todosFromMessages(messages);
|
|
229
|
+
}
|
|
230
|
+
static register(context) {
|
|
231
|
+
return context.tool.transform((tools) => {
|
|
232
|
+
tools.add({
|
|
233
|
+
name: todoTrackerName,
|
|
234
|
+
description: todoTrackerDescription,
|
|
235
|
+
input: todoTrackerInput,
|
|
236
|
+
options: { codemode: false },
|
|
237
|
+
async execute(input) {
|
|
238
|
+
if (!isTodoTrackerInput(input)) {
|
|
239
|
+
throw new Error("Invalid todotracker input.");
|
|
240
|
+
}
|
|
241
|
+
return { content: JSON.stringify(input.todos), metadata: { todos: input.todos } };
|
|
242
|
+
}
|
|
243
|
+
});
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
131
248
|
// src/server/index.ts
|
|
132
249
|
var server_default = Plugin.define({
|
|
133
250
|
id: "@encoredigitalgroup/opencode",
|
|
@@ -135,7 +252,7 @@ var server_default = Plugin.define({
|
|
|
135
252
|
const pluginConfig = ConfigLoader.loadConfig(ctx.options);
|
|
136
253
|
const command = "feature-planning";
|
|
137
254
|
const route = SkillCommands.getCommands(pluginConfig)[command];
|
|
138
|
-
const
|
|
255
|
+
const commandRegistration = await ctx.command.transform((commands) => {
|
|
139
256
|
commands.add({
|
|
140
257
|
name: command,
|
|
141
258
|
description: route.description,
|
|
@@ -154,7 +271,20 @@ var server_default = Plugin.define({
|
|
|
154
271
|
}
|
|
155
272
|
});
|
|
156
273
|
});
|
|
157
|
-
|
|
274
|
+
let toolRegistration;
|
|
275
|
+
try {
|
|
276
|
+
toolRegistration = await TodoTracker.register(ctx);
|
|
277
|
+
} catch (error) {
|
|
278
|
+
await commandRegistration.dispose();
|
|
279
|
+
throw error;
|
|
280
|
+
}
|
|
281
|
+
return async () => {
|
|
282
|
+
try {
|
|
283
|
+
await toolRegistration.dispose();
|
|
284
|
+
} finally {
|
|
285
|
+
await commandRegistration.dispose();
|
|
286
|
+
}
|
|
287
|
+
};
|
|
158
288
|
}
|
|
159
289
|
});
|
|
160
290
|
export {
|
package/dist/tui/index.js
CHANGED
|
@@ -2,6 +2,749 @@
|
|
|
2
2
|
// src/tui/index.tsx
|
|
3
3
|
import { Plugin } from "@opencode/plugin/tui";
|
|
4
4
|
|
|
5
|
+
// node_modules/solid-js/dist/solid.js
|
|
6
|
+
var sharedConfig = {
|
|
7
|
+
context: undefined,
|
|
8
|
+
registry: undefined,
|
|
9
|
+
effects: undefined,
|
|
10
|
+
done: false,
|
|
11
|
+
getContextId() {
|
|
12
|
+
return getContextId(this.context.count);
|
|
13
|
+
},
|
|
14
|
+
getNextContextId() {
|
|
15
|
+
return getContextId(this.context.count++);
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
function getContextId(count) {
|
|
19
|
+
const num = String(count), len = num.length - 1;
|
|
20
|
+
return sharedConfig.context.id + (len ? String.fromCharCode(96 + len) : "") + num;
|
|
21
|
+
}
|
|
22
|
+
function setHydrateContext(context) {
|
|
23
|
+
sharedConfig.context = context;
|
|
24
|
+
}
|
|
25
|
+
var IS_DEV = false;
|
|
26
|
+
var equalFn = (a, b) => a === b;
|
|
27
|
+
var $PROXY = Symbol("solid-proxy");
|
|
28
|
+
var $TRACK = Symbol("solid-track");
|
|
29
|
+
var $DEVCOMP = Symbol("solid-dev-component");
|
|
30
|
+
var signalOptions = {
|
|
31
|
+
equals: equalFn
|
|
32
|
+
};
|
|
33
|
+
var ERROR = null;
|
|
34
|
+
var runEffects = runQueue;
|
|
35
|
+
var STALE = 1;
|
|
36
|
+
var PENDING = 2;
|
|
37
|
+
var UNOWNED = {
|
|
38
|
+
owned: null,
|
|
39
|
+
cleanups: null,
|
|
40
|
+
context: null,
|
|
41
|
+
owner: null
|
|
42
|
+
};
|
|
43
|
+
var Owner = null;
|
|
44
|
+
var Transition = null;
|
|
45
|
+
var Scheduler = null;
|
|
46
|
+
var ExternalSourceConfig = null;
|
|
47
|
+
var Listener = null;
|
|
48
|
+
var Updates = null;
|
|
49
|
+
var Effects = null;
|
|
50
|
+
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
|
+
function createSignal(value, options) {
|
|
68
|
+
options = options ? Object.assign({}, signalOptions, options) : signalOptions;
|
|
69
|
+
const s = {
|
|
70
|
+
value,
|
|
71
|
+
observers: null,
|
|
72
|
+
observerSlots: null,
|
|
73
|
+
comparator: options.equals || undefined
|
|
74
|
+
};
|
|
75
|
+
const setter = (value) => {
|
|
76
|
+
if (typeof value === "function") {
|
|
77
|
+
if (Transition && Transition.running && Transition.sources.has(s))
|
|
78
|
+
value = value(s.tValue);
|
|
79
|
+
else
|
|
80
|
+
value = value(s.value);
|
|
81
|
+
}
|
|
82
|
+
return writeSignal(s, value);
|
|
83
|
+
};
|
|
84
|
+
return [readSignal.bind(s), setter];
|
|
85
|
+
}
|
|
86
|
+
function createEffect(fn, value, options) {
|
|
87
|
+
runEffects = runUserEffects;
|
|
88
|
+
const c = createComputation(fn, value, false, STALE), s = SuspenseContext && useContext(SuspenseContext);
|
|
89
|
+
if (s)
|
|
90
|
+
c.suspense = s;
|
|
91
|
+
if (!options || !options.render)
|
|
92
|
+
c.user = true;
|
|
93
|
+
Effects ? Effects.push(c) : updateComputation(c);
|
|
94
|
+
}
|
|
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
|
+
function untrack(fn) {
|
|
109
|
+
if (!ExternalSourceConfig && Listener === null)
|
|
110
|
+
return fn();
|
|
111
|
+
const listener = Listener;
|
|
112
|
+
Listener = null;
|
|
113
|
+
try {
|
|
114
|
+
if (ExternalSourceConfig)
|
|
115
|
+
return ExternalSourceConfig.untrack(fn);
|
|
116
|
+
return fn();
|
|
117
|
+
} finally {
|
|
118
|
+
Listener = listener;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
function onMount(fn) {
|
|
122
|
+
createEffect(() => untrack(fn));
|
|
123
|
+
}
|
|
124
|
+
function onCleanup(fn) {
|
|
125
|
+
if (Owner === null)
|
|
126
|
+
;
|
|
127
|
+
else if (Owner.cleanups === null)
|
|
128
|
+
Owner.cleanups = [fn];
|
|
129
|
+
else
|
|
130
|
+
Owner.cleanups.push(fn);
|
|
131
|
+
return fn;
|
|
132
|
+
}
|
|
133
|
+
function startTransition(fn) {
|
|
134
|
+
if (Transition && Transition.running) {
|
|
135
|
+
fn();
|
|
136
|
+
return Transition.done;
|
|
137
|
+
}
|
|
138
|
+
const l = Listener;
|
|
139
|
+
const o = Owner;
|
|
140
|
+
return Promise.resolve().then(() => {
|
|
141
|
+
Listener = l;
|
|
142
|
+
Owner = o;
|
|
143
|
+
let t;
|
|
144
|
+
if (Scheduler || SuspenseContext) {
|
|
145
|
+
t = Transition || (Transition = {
|
|
146
|
+
sources: new Set,
|
|
147
|
+
effects: [],
|
|
148
|
+
promises: new Set,
|
|
149
|
+
disposed: new Set,
|
|
150
|
+
queue: new Set,
|
|
151
|
+
running: true
|
|
152
|
+
});
|
|
153
|
+
t.done || (t.done = new Promise((res) => t.resolve = res));
|
|
154
|
+
t.running = true;
|
|
155
|
+
}
|
|
156
|
+
runUpdates(fn, false);
|
|
157
|
+
Listener = Owner = null;
|
|
158
|
+
return t ? t.done : undefined;
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
var [transPending, setTransPending] = /* @__PURE__ */ createSignal(false);
|
|
162
|
+
function useContext(context) {
|
|
163
|
+
let value;
|
|
164
|
+
return Owner && Owner.context && (value = Owner.context[context.id]) !== undefined ? value : context.defaultValue;
|
|
165
|
+
}
|
|
166
|
+
var SuspenseContext;
|
|
167
|
+
function readSignal() {
|
|
168
|
+
const runningTransition = Transition && Transition.running;
|
|
169
|
+
if (this.sources && (runningTransition ? this.tState : this.state)) {
|
|
170
|
+
if ((runningTransition ? this.tState : this.state) === STALE)
|
|
171
|
+
updateComputation(this);
|
|
172
|
+
else {
|
|
173
|
+
const updates = Updates;
|
|
174
|
+
Updates = null;
|
|
175
|
+
runUpdates(() => lookUpstream(this), false);
|
|
176
|
+
Updates = updates;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
if (Listener) {
|
|
180
|
+
const sSlot = this.observers ? this.observers.length : 0;
|
|
181
|
+
if (!Listener.sources) {
|
|
182
|
+
Listener.sources = [this];
|
|
183
|
+
Listener.sourceSlots = [sSlot];
|
|
184
|
+
} else {
|
|
185
|
+
Listener.sources.push(this);
|
|
186
|
+
Listener.sourceSlots.push(sSlot);
|
|
187
|
+
}
|
|
188
|
+
if (!this.observers) {
|
|
189
|
+
this.observers = [Listener];
|
|
190
|
+
this.observerSlots = [Listener.sources.length - 1];
|
|
191
|
+
} else {
|
|
192
|
+
this.observers.push(Listener);
|
|
193
|
+
this.observerSlots.push(Listener.sources.length - 1);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
if (runningTransition && Transition.sources.has(this))
|
|
197
|
+
return this.tValue;
|
|
198
|
+
return this.value;
|
|
199
|
+
}
|
|
200
|
+
function writeSignal(node, value, isComp) {
|
|
201
|
+
let current = Transition && Transition.running && Transition.sources.has(node) ? node.tValue : node.value;
|
|
202
|
+
if (!node.comparator || !node.comparator(current, value)) {
|
|
203
|
+
if (Transition) {
|
|
204
|
+
const TransitionRunning = Transition.running;
|
|
205
|
+
if (TransitionRunning || !isComp && Transition.sources.has(node)) {
|
|
206
|
+
Transition.sources.add(node);
|
|
207
|
+
node.tValue = value;
|
|
208
|
+
}
|
|
209
|
+
if (!TransitionRunning)
|
|
210
|
+
node.value = value;
|
|
211
|
+
} else
|
|
212
|
+
node.value = value;
|
|
213
|
+
if (node.observers && node.observers.length) {
|
|
214
|
+
runUpdates(() => {
|
|
215
|
+
for (let i = 0;i < node.observers.length; i += 1) {
|
|
216
|
+
const o = node.observers[i];
|
|
217
|
+
const TransitionRunning = Transition && Transition.running;
|
|
218
|
+
if (TransitionRunning && Transition.disposed.has(o))
|
|
219
|
+
continue;
|
|
220
|
+
if (TransitionRunning ? !o.tState : !o.state) {
|
|
221
|
+
if (o.pure)
|
|
222
|
+
Updates.push(o);
|
|
223
|
+
else
|
|
224
|
+
Effects.push(o);
|
|
225
|
+
if (o.observers)
|
|
226
|
+
markDownstream(o);
|
|
227
|
+
}
|
|
228
|
+
if (!TransitionRunning)
|
|
229
|
+
o.state = STALE;
|
|
230
|
+
else
|
|
231
|
+
o.tState = STALE;
|
|
232
|
+
}
|
|
233
|
+
if (Updates.length > 1e6) {
|
|
234
|
+
Updates = [];
|
|
235
|
+
if (IS_DEV)
|
|
236
|
+
;
|
|
237
|
+
throw new Error;
|
|
238
|
+
}
|
|
239
|
+
}, false);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
return value;
|
|
243
|
+
}
|
|
244
|
+
function updateComputation(node) {
|
|
245
|
+
if (!node.fn)
|
|
246
|
+
return;
|
|
247
|
+
cleanNode(node);
|
|
248
|
+
const time = ExecCount;
|
|
249
|
+
runComputation(node, Transition && Transition.running && Transition.sources.has(node) ? node.tValue : node.value, time);
|
|
250
|
+
if (Transition && !Transition.running && Transition.sources.has(node)) {
|
|
251
|
+
queueMicrotask(() => {
|
|
252
|
+
runUpdates(() => {
|
|
253
|
+
Transition && (Transition.running = true);
|
|
254
|
+
Listener = Owner = node;
|
|
255
|
+
runComputation(node, node.tValue, time);
|
|
256
|
+
Listener = Owner = null;
|
|
257
|
+
}, false);
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
function runComputation(node, value, time) {
|
|
262
|
+
let nextValue;
|
|
263
|
+
const owner = Owner, listener = Listener;
|
|
264
|
+
Listener = Owner = node;
|
|
265
|
+
try {
|
|
266
|
+
nextValue = node.fn(value);
|
|
267
|
+
} catch (err) {
|
|
268
|
+
if (node.pure) {
|
|
269
|
+
if (Transition && Transition.running) {
|
|
270
|
+
node.tState = STALE;
|
|
271
|
+
node.tOwned && node.tOwned.forEach(cleanNode);
|
|
272
|
+
node.tOwned = undefined;
|
|
273
|
+
} else {
|
|
274
|
+
node.state = STALE;
|
|
275
|
+
node.owned && node.owned.forEach(cleanNode);
|
|
276
|
+
node.owned = null;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
node.updatedAt = time + 1;
|
|
280
|
+
return handleError(err);
|
|
281
|
+
} finally {
|
|
282
|
+
Listener = listener;
|
|
283
|
+
Owner = owner;
|
|
284
|
+
}
|
|
285
|
+
if (!node.updatedAt || node.updatedAt <= time) {
|
|
286
|
+
if (node.updatedAt != null && "observers" in node) {
|
|
287
|
+
writeSignal(node, nextValue, true);
|
|
288
|
+
} else if (Transition && Transition.running && node.pure) {
|
|
289
|
+
if (!Transition.sources.has(node))
|
|
290
|
+
node.value = nextValue;
|
|
291
|
+
Transition.sources.add(node);
|
|
292
|
+
node.tValue = nextValue;
|
|
293
|
+
} else
|
|
294
|
+
node.value = nextValue;
|
|
295
|
+
node.updatedAt = time;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
function createComputation(fn, init, pure, state = STALE, options) {
|
|
299
|
+
const c = {
|
|
300
|
+
fn,
|
|
301
|
+
state,
|
|
302
|
+
updatedAt: null,
|
|
303
|
+
owned: null,
|
|
304
|
+
sources: null,
|
|
305
|
+
sourceSlots: null,
|
|
306
|
+
cleanups: null,
|
|
307
|
+
value: init,
|
|
308
|
+
owner: Owner,
|
|
309
|
+
context: Owner ? Owner.context : null,
|
|
310
|
+
pure
|
|
311
|
+
};
|
|
312
|
+
if (Transition && Transition.running) {
|
|
313
|
+
c.state = 0;
|
|
314
|
+
c.tState = state;
|
|
315
|
+
}
|
|
316
|
+
if (Owner === null)
|
|
317
|
+
;
|
|
318
|
+
else if (Owner !== UNOWNED) {
|
|
319
|
+
if (Transition && Transition.running && Owner.pure) {
|
|
320
|
+
if (!Owner.tOwned)
|
|
321
|
+
Owner.tOwned = [c];
|
|
322
|
+
else
|
|
323
|
+
Owner.tOwned.push(c);
|
|
324
|
+
} else {
|
|
325
|
+
if (!Owner.owned)
|
|
326
|
+
Owner.owned = [c];
|
|
327
|
+
else
|
|
328
|
+
Owner.owned.push(c);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
if (ExternalSourceConfig && c.fn) {
|
|
332
|
+
const sourceFn = c.fn;
|
|
333
|
+
const [track, trigger] = createSignal(undefined, {
|
|
334
|
+
equals: false
|
|
335
|
+
});
|
|
336
|
+
const ordinary = ExternalSourceConfig.factory(sourceFn, trigger);
|
|
337
|
+
onCleanup(() => ordinary.dispose());
|
|
338
|
+
let inTransition;
|
|
339
|
+
const triggerInTransition = () => startTransition(trigger).then(() => {
|
|
340
|
+
if (inTransition) {
|
|
341
|
+
inTransition.dispose();
|
|
342
|
+
inTransition = undefined;
|
|
343
|
+
}
|
|
344
|
+
});
|
|
345
|
+
c.fn = (x) => {
|
|
346
|
+
track();
|
|
347
|
+
if (Transition && Transition.running) {
|
|
348
|
+
if (!inTransition)
|
|
349
|
+
inTransition = ExternalSourceConfig.factory(sourceFn, triggerInTransition);
|
|
350
|
+
return inTransition.track(x);
|
|
351
|
+
}
|
|
352
|
+
return ordinary.track(x);
|
|
353
|
+
};
|
|
354
|
+
}
|
|
355
|
+
return c;
|
|
356
|
+
}
|
|
357
|
+
function runTop(node) {
|
|
358
|
+
const runningTransition = Transition && Transition.running;
|
|
359
|
+
if ((runningTransition ? node.tState : node.state) === 0)
|
|
360
|
+
return;
|
|
361
|
+
if ((runningTransition ? node.tState : node.state) === PENDING)
|
|
362
|
+
return lookUpstream(node);
|
|
363
|
+
if (node.suspense && untrack(node.suspense.inFallback))
|
|
364
|
+
return node.suspense.effects.push(node);
|
|
365
|
+
const ancestors = [node];
|
|
366
|
+
while ((node = node.owner) && (!node.updatedAt || node.updatedAt < ExecCount)) {
|
|
367
|
+
if (runningTransition && Transition.disposed.has(node))
|
|
368
|
+
return;
|
|
369
|
+
if (runningTransition ? node.tState : node.state)
|
|
370
|
+
ancestors.push(node);
|
|
371
|
+
}
|
|
372
|
+
for (let i = ancestors.length - 1;i >= 0; i--) {
|
|
373
|
+
node = ancestors[i];
|
|
374
|
+
if (runningTransition) {
|
|
375
|
+
let top = node, prev = ancestors[i + 1];
|
|
376
|
+
while ((top = top.owner) && top !== prev) {
|
|
377
|
+
if (Transition.disposed.has(top))
|
|
378
|
+
return;
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
if ((runningTransition ? node.tState : node.state) === STALE) {
|
|
382
|
+
updateComputation(node);
|
|
383
|
+
} else if ((runningTransition ? node.tState : node.state) === PENDING) {
|
|
384
|
+
const updates = Updates;
|
|
385
|
+
Updates = null;
|
|
386
|
+
runUpdates(() => lookUpstream(node, ancestors[0]), false);
|
|
387
|
+
Updates = updates;
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
function runUpdates(fn, init) {
|
|
392
|
+
if (Updates)
|
|
393
|
+
return fn();
|
|
394
|
+
let wait = false;
|
|
395
|
+
if (!init)
|
|
396
|
+
Updates = [];
|
|
397
|
+
if (Effects)
|
|
398
|
+
wait = true;
|
|
399
|
+
else
|
|
400
|
+
Effects = [];
|
|
401
|
+
ExecCount++;
|
|
402
|
+
try {
|
|
403
|
+
const res = fn();
|
|
404
|
+
completeUpdates(wait);
|
|
405
|
+
return res;
|
|
406
|
+
} catch (err) {
|
|
407
|
+
if (!wait)
|
|
408
|
+
Effects = null;
|
|
409
|
+
Updates = null;
|
|
410
|
+
handleError(err);
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
function completeUpdates(wait) {
|
|
414
|
+
if (Updates) {
|
|
415
|
+
if (Scheduler && Transition && Transition.running)
|
|
416
|
+
scheduleQueue(Updates);
|
|
417
|
+
else
|
|
418
|
+
runQueue(Updates);
|
|
419
|
+
Updates = null;
|
|
420
|
+
}
|
|
421
|
+
if (wait)
|
|
422
|
+
return;
|
|
423
|
+
let res;
|
|
424
|
+
if (Transition) {
|
|
425
|
+
if (!Transition.promises.size && !Transition.queue.size) {
|
|
426
|
+
const sources = Transition.sources;
|
|
427
|
+
const disposed = Transition.disposed;
|
|
428
|
+
Effects.push.apply(Effects, Transition.effects);
|
|
429
|
+
res = Transition.resolve;
|
|
430
|
+
for (const e of Effects) {
|
|
431
|
+
"tState" in e && (e.state = e.tState);
|
|
432
|
+
delete e.tState;
|
|
433
|
+
}
|
|
434
|
+
Transition = null;
|
|
435
|
+
runUpdates(() => {
|
|
436
|
+
for (const d of disposed)
|
|
437
|
+
cleanNode(d);
|
|
438
|
+
for (const v of sources) {
|
|
439
|
+
v.value = v.tValue;
|
|
440
|
+
if (v.owned) {
|
|
441
|
+
for (let i = 0, len = v.owned.length;i < len; i++)
|
|
442
|
+
cleanNode(v.owned[i]);
|
|
443
|
+
}
|
|
444
|
+
if (v.tOwned)
|
|
445
|
+
v.owned = v.tOwned;
|
|
446
|
+
delete v.tValue;
|
|
447
|
+
delete v.tOwned;
|
|
448
|
+
v.tState = 0;
|
|
449
|
+
}
|
|
450
|
+
setTransPending(false);
|
|
451
|
+
}, false);
|
|
452
|
+
} else if (Transition.running) {
|
|
453
|
+
Transition.running = false;
|
|
454
|
+
Transition.effects.push.apply(Transition.effects, Effects);
|
|
455
|
+
Effects = null;
|
|
456
|
+
setTransPending(true);
|
|
457
|
+
return;
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
const e = Effects;
|
|
461
|
+
Effects = null;
|
|
462
|
+
if (e.length)
|
|
463
|
+
runUpdates(() => runEffects(e), false);
|
|
464
|
+
if (res)
|
|
465
|
+
res();
|
|
466
|
+
}
|
|
467
|
+
function runQueue(queue) {
|
|
468
|
+
for (let i = 0;i < queue.length; i++)
|
|
469
|
+
runTop(queue[i]);
|
|
470
|
+
}
|
|
471
|
+
function scheduleQueue(queue) {
|
|
472
|
+
for (let i = 0;i < queue.length; i++) {
|
|
473
|
+
const item = queue[i];
|
|
474
|
+
const tasks = Transition.queue;
|
|
475
|
+
if (!tasks.has(item)) {
|
|
476
|
+
tasks.add(item);
|
|
477
|
+
Scheduler(() => {
|
|
478
|
+
tasks.delete(item);
|
|
479
|
+
runUpdates(() => {
|
|
480
|
+
Transition.running = true;
|
|
481
|
+
runTop(item);
|
|
482
|
+
}, false);
|
|
483
|
+
Transition && (Transition.running = false);
|
|
484
|
+
});
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
function runUserEffects(queue) {
|
|
489
|
+
let i, userLength = 0;
|
|
490
|
+
for (i = 0;i < queue.length; i++) {
|
|
491
|
+
const e = queue[i];
|
|
492
|
+
if (!e.user)
|
|
493
|
+
runTop(e);
|
|
494
|
+
else
|
|
495
|
+
queue[userLength++] = e;
|
|
496
|
+
}
|
|
497
|
+
if (sharedConfig.context) {
|
|
498
|
+
if (sharedConfig.count) {
|
|
499
|
+
sharedConfig.effects || (sharedConfig.effects = []);
|
|
500
|
+
sharedConfig.effects.push(...queue.slice(0, userLength));
|
|
501
|
+
return;
|
|
502
|
+
}
|
|
503
|
+
setHydrateContext();
|
|
504
|
+
}
|
|
505
|
+
if (sharedConfig.effects && (sharedConfig.done || !sharedConfig.count)) {
|
|
506
|
+
queue = [...sharedConfig.effects, ...queue];
|
|
507
|
+
userLength += sharedConfig.effects.length;
|
|
508
|
+
delete sharedConfig.effects;
|
|
509
|
+
}
|
|
510
|
+
for (i = 0;i < userLength; i++)
|
|
511
|
+
runTop(queue[i]);
|
|
512
|
+
}
|
|
513
|
+
function lookUpstream(node, ignore) {
|
|
514
|
+
const runningTransition = Transition && Transition.running;
|
|
515
|
+
if (runningTransition)
|
|
516
|
+
node.tState = 0;
|
|
517
|
+
else
|
|
518
|
+
node.state = 0;
|
|
519
|
+
for (let i = 0;i < node.sources.length; i += 1) {
|
|
520
|
+
const source = node.sources[i];
|
|
521
|
+
if (source.sources) {
|
|
522
|
+
const state = runningTransition ? source.tState : source.state;
|
|
523
|
+
if (state === STALE) {
|
|
524
|
+
if (source !== ignore && (!source.updatedAt || source.updatedAt < ExecCount))
|
|
525
|
+
runTop(source);
|
|
526
|
+
} else if (state === PENDING)
|
|
527
|
+
lookUpstream(source, ignore);
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
function markDownstream(node) {
|
|
532
|
+
const runningTransition = Transition && Transition.running;
|
|
533
|
+
for (let i = 0;i < node.observers.length; i += 1) {
|
|
534
|
+
const o = node.observers[i];
|
|
535
|
+
if (runningTransition ? !o.tState : !o.state) {
|
|
536
|
+
if (runningTransition)
|
|
537
|
+
o.tState = PENDING;
|
|
538
|
+
else
|
|
539
|
+
o.state = PENDING;
|
|
540
|
+
if (o.pure)
|
|
541
|
+
Updates.push(o);
|
|
542
|
+
else
|
|
543
|
+
Effects.push(o);
|
|
544
|
+
o.observers && markDownstream(o);
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
function cleanNode(node) {
|
|
549
|
+
let i;
|
|
550
|
+
if (node.sources) {
|
|
551
|
+
while (node.sources.length) {
|
|
552
|
+
const source = node.sources.pop(), index = node.sourceSlots.pop(), obs = source.observers;
|
|
553
|
+
if (obs && obs.length) {
|
|
554
|
+
const n = obs.pop(), s = source.observerSlots.pop();
|
|
555
|
+
if (index < obs.length) {
|
|
556
|
+
n.sourceSlots[s] = index;
|
|
557
|
+
obs[index] = n;
|
|
558
|
+
source.observerSlots[index] = s;
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
if (node.tOwned) {
|
|
564
|
+
for (i = node.tOwned.length - 1;i >= 0; i--)
|
|
565
|
+
cleanNode(node.tOwned[i]);
|
|
566
|
+
delete node.tOwned;
|
|
567
|
+
}
|
|
568
|
+
if (Transition && Transition.running && node.pure) {
|
|
569
|
+
reset(node, true);
|
|
570
|
+
} else if (node.owned) {
|
|
571
|
+
for (i = node.owned.length - 1;i >= 0; i--)
|
|
572
|
+
cleanNode(node.owned[i]);
|
|
573
|
+
node.owned = null;
|
|
574
|
+
}
|
|
575
|
+
if (node.cleanups) {
|
|
576
|
+
for (i = node.cleanups.length - 1;i >= 0; i--)
|
|
577
|
+
node.cleanups[i]();
|
|
578
|
+
node.cleanups = null;
|
|
579
|
+
}
|
|
580
|
+
if (Transition && Transition.running)
|
|
581
|
+
node.tState = 0;
|
|
582
|
+
else
|
|
583
|
+
node.state = 0;
|
|
584
|
+
}
|
|
585
|
+
function reset(node, top) {
|
|
586
|
+
if (!top) {
|
|
587
|
+
node.tState = 0;
|
|
588
|
+
Transition.disposed.add(node);
|
|
589
|
+
}
|
|
590
|
+
if (node.owned) {
|
|
591
|
+
for (let i = 0;i < node.owned.length; i++)
|
|
592
|
+
reset(node.owned[i]);
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
function castError(err) {
|
|
596
|
+
if (err instanceof Error)
|
|
597
|
+
return err;
|
|
598
|
+
return new Error(typeof err === "string" ? err : "Unknown error", {
|
|
599
|
+
cause: err
|
|
600
|
+
});
|
|
601
|
+
}
|
|
602
|
+
function runErrors(err, fns, owner) {
|
|
603
|
+
try {
|
|
604
|
+
for (const f of fns)
|
|
605
|
+
f(err);
|
|
606
|
+
} catch (e) {
|
|
607
|
+
handleError(e, owner && owner.owner || null);
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
function handleError(err, owner = Owner) {
|
|
611
|
+
const fns = ERROR && owner && owner.context && owner.context[ERROR];
|
|
612
|
+
const error = castError(err);
|
|
613
|
+
if (!fns)
|
|
614
|
+
throw error;
|
|
615
|
+
if (Effects)
|
|
616
|
+
Effects.push({
|
|
617
|
+
fn() {
|
|
618
|
+
runErrors(error, fns, owner);
|
|
619
|
+
},
|
|
620
|
+
state: STALE
|
|
621
|
+
});
|
|
622
|
+
else
|
|
623
|
+
runErrors(error, fns, owner);
|
|
624
|
+
}
|
|
625
|
+
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
|
+
|
|
5
748
|
// src/config/ConfigDefaults.ts
|
|
6
749
|
class ConfigDefaults {
|
|
7
750
|
static getDefaultSkillMappings() {
|
|
@@ -319,22 +1062,184 @@ class FilePickerCommands {
|
|
|
319
1062
|
}
|
|
320
1063
|
}
|
|
321
1064
|
|
|
1065
|
+
// src/lib/TodoTracker.ts
|
|
1066
|
+
var todoTrackerInput = {
|
|
1067
|
+
type: "object",
|
|
1068
|
+
additionalProperties: false,
|
|
1069
|
+
required: ["todos"],
|
|
1070
|
+
properties: {
|
|
1071
|
+
todos: {
|
|
1072
|
+
type: "array",
|
|
1073
|
+
items: {
|
|
1074
|
+
type: "object",
|
|
1075
|
+
additionalProperties: false,
|
|
1076
|
+
required: ["content", "status", "priority"],
|
|
1077
|
+
properties: {
|
|
1078
|
+
content: { type: "string", minLength: 1, pattern: "\\S" },
|
|
1079
|
+
status: { enum: ["pending", "in_progress", "completed", "cancelled"] },
|
|
1080
|
+
priority: { enum: ["high", "medium", "low"] }
|
|
1081
|
+
}
|
|
1082
|
+
}
|
|
1083
|
+
}
|
|
1084
|
+
}
|
|
1085
|
+
};
|
|
1086
|
+
var todoTrackerName = "todotracker";
|
|
1087
|
+
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.";
|
|
1088
|
+
var todoStatuses = ["pending", "in_progress", "completed", "cancelled"];
|
|
1089
|
+
var todoPriorities = ["high", "medium", "low"];
|
|
1090
|
+
function isRecord(value) {
|
|
1091
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1092
|
+
}
|
|
1093
|
+
function hasOnlyKeys(value, keys) {
|
|
1094
|
+
const expected = new Set(keys);
|
|
1095
|
+
return Object.keys(value).length === keys.length && Object.keys(value).every((key) => expected.has(key));
|
|
1096
|
+
}
|
|
1097
|
+
function isTodoStatus(value) {
|
|
1098
|
+
return typeof value === "string" && todoStatuses.includes(value);
|
|
1099
|
+
}
|
|
1100
|
+
function isTodoPriority(value) {
|
|
1101
|
+
return typeof value === "string" && todoPriorities.includes(value);
|
|
1102
|
+
}
|
|
1103
|
+
function isTodo(value) {
|
|
1104
|
+
if (!isRecord(value) || !hasOnlyKeys(value, ["content", "status", "priority"])) {
|
|
1105
|
+
return false;
|
|
1106
|
+
}
|
|
1107
|
+
return typeof value.content === "string" && value.content.length >= 1 && /\S/.test(value.content) && isTodoStatus(value.status) && isTodoPriority(value.priority);
|
|
1108
|
+
}
|
|
1109
|
+
function isTodoList(value) {
|
|
1110
|
+
return Array.isArray(value) && value.every(isTodo);
|
|
1111
|
+
}
|
|
1112
|
+
function isTodoTrackerInput(value) {
|
|
1113
|
+
return isRecord(value) && hasOnlyKeys(value, ["todos"]) && isTodoList(value.todos);
|
|
1114
|
+
}
|
|
1115
|
+
function isTodoResultMetadata(value) {
|
|
1116
|
+
return isTodoTrackerInput(value);
|
|
1117
|
+
}
|
|
1118
|
+
function todosFromToolResult(metadata, content) {
|
|
1119
|
+
if (isTodoResultMetadata(metadata)) {
|
|
1120
|
+
return metadata.todos;
|
|
1121
|
+
}
|
|
1122
|
+
for (const item of content) {
|
|
1123
|
+
if (item.type !== "text" || item.text === undefined) {
|
|
1124
|
+
continue;
|
|
1125
|
+
}
|
|
1126
|
+
try {
|
|
1127
|
+
const todos = JSON.parse(item.text);
|
|
1128
|
+
if (isTodoList(todos)) {
|
|
1129
|
+
return todos;
|
|
1130
|
+
}
|
|
1131
|
+
} catch {}
|
|
1132
|
+
}
|
|
1133
|
+
return [];
|
|
1134
|
+
}
|
|
1135
|
+
function todosFromMessages(messages) {
|
|
1136
|
+
for (const message of messages.toReversed()) {
|
|
1137
|
+
if (message.type !== "assistant")
|
|
1138
|
+
continue;
|
|
1139
|
+
for (const part of message.content.toReversed()) {
|
|
1140
|
+
if (part.type === "tool" && part.name === todoTrackerName && part.state.status === "completed") {
|
|
1141
|
+
return todosFromToolResult(part.state.metadata, part.state.content);
|
|
1142
|
+
}
|
|
1143
|
+
}
|
|
1144
|
+
}
|
|
1145
|
+
return [];
|
|
1146
|
+
}
|
|
1147
|
+
|
|
1148
|
+
class TodoTracker {
|
|
1149
|
+
static marker(status) {
|
|
1150
|
+
switch (status) {
|
|
1151
|
+
case "pending":
|
|
1152
|
+
return "[ ]";
|
|
1153
|
+
case "in_progress":
|
|
1154
|
+
return "[~]";
|
|
1155
|
+
case "completed":
|
|
1156
|
+
return "[x]";
|
|
1157
|
+
case "cancelled":
|
|
1158
|
+
return "[-]";
|
|
1159
|
+
}
|
|
1160
|
+
}
|
|
1161
|
+
static sidebar(messages) {
|
|
1162
|
+
return todosFromMessages(messages);
|
|
1163
|
+
}
|
|
1164
|
+
static register(context) {
|
|
1165
|
+
return context.tool.transform((tools) => {
|
|
1166
|
+
tools.add({
|
|
1167
|
+
name: todoTrackerName,
|
|
1168
|
+
description: todoTrackerDescription,
|
|
1169
|
+
input: todoTrackerInput,
|
|
1170
|
+
options: { codemode: false },
|
|
1171
|
+
async execute(input) {
|
|
1172
|
+
if (!isTodoTrackerInput(input)) {
|
|
1173
|
+
throw new Error("Invalid todotracker input.");
|
|
1174
|
+
}
|
|
1175
|
+
return { content: JSON.stringify(input.todos), metadata: { todos: input.todos } };
|
|
1176
|
+
}
|
|
1177
|
+
});
|
|
1178
|
+
});
|
|
1179
|
+
}
|
|
1180
|
+
}
|
|
1181
|
+
|
|
322
1182
|
// src/tui/index.tsx
|
|
323
|
-
import { jsx, Fragment } from "@opentui/solid/jsx-runtime";
|
|
1183
|
+
import { jsx, jsxs, Fragment } from "@opentui/solid/jsx-runtime";
|
|
324
1184
|
function PickerCommandLayer(props) {
|
|
325
1185
|
FilePickerCommands.register(props.context, props.config);
|
|
326
1186
|
return /* @__PURE__ */ jsx(Fragment, {});
|
|
327
1187
|
}
|
|
1188
|
+
function TodoSidebar(props) {
|
|
1189
|
+
const todos = createMemo(() => TodoTracker.sidebar(props.context.data.session.message.list(props.sessionID)));
|
|
1190
|
+
const color = (status) => status === "in_progress" ? props.context.theme.text.feedback.warning.base : props.context.theme.text.muted;
|
|
1191
|
+
onMount(() => {
|
|
1192
|
+
props.context.data.session.message.sync(props.sessionID).catch(() => {});
|
|
1193
|
+
});
|
|
1194
|
+
return /* @__PURE__ */ jsx(Show, {
|
|
1195
|
+
when: todos().length > 0,
|
|
1196
|
+
children: /* @__PURE__ */ jsxs("box", {
|
|
1197
|
+
flexDirection: "column",
|
|
1198
|
+
children: [
|
|
1199
|
+
/* @__PURE__ */ jsx("text", {
|
|
1200
|
+
children: "Todos"
|
|
1201
|
+
}),
|
|
1202
|
+
/* @__PURE__ */ jsx(For, {
|
|
1203
|
+
each: todos(),
|
|
1204
|
+
children: (todo) => /* @__PURE__ */ jsx("text", {
|
|
1205
|
+
fg: color(todo.status),
|
|
1206
|
+
children: `${TodoTracker.marker(todo.status)} (${todo.priority}) ${todo.content}`
|
|
1207
|
+
})
|
|
1208
|
+
})
|
|
1209
|
+
]
|
|
1210
|
+
})
|
|
1211
|
+
});
|
|
1212
|
+
}
|
|
328
1213
|
var tui_default = Plugin.define({
|
|
329
1214
|
id: "@encoredigitalgroup/opencode",
|
|
330
1215
|
setup(ctx) {
|
|
331
|
-
|
|
1216
|
+
const appSlot = ctx.ui.slot({
|
|
332
1217
|
append: "app",
|
|
333
1218
|
render: () => /* @__PURE__ */ jsx(PickerCommandLayer, {
|
|
334
1219
|
context: ctx,
|
|
335
1220
|
config: ConfigLoader.loadConfig(ctx.options)
|
|
336
1221
|
})
|
|
337
1222
|
});
|
|
1223
|
+
let sidebarSlot;
|
|
1224
|
+
try {
|
|
1225
|
+
sidebarSlot = ctx.ui.slot({
|
|
1226
|
+
append: "sidebar.content",
|
|
1227
|
+
render: (input) => /* @__PURE__ */ jsx(TodoSidebar, {
|
|
1228
|
+
context: ctx,
|
|
1229
|
+
sessionID: input.sessionID
|
|
1230
|
+
})
|
|
1231
|
+
});
|
|
1232
|
+
} catch (error) {
|
|
1233
|
+
appSlot();
|
|
1234
|
+
throw error;
|
|
1235
|
+
}
|
|
1236
|
+
return () => {
|
|
1237
|
+
try {
|
|
1238
|
+
sidebarSlot();
|
|
1239
|
+
} finally {
|
|
1240
|
+
appSlot();
|
|
1241
|
+
}
|
|
1242
|
+
};
|
|
338
1243
|
}
|
|
339
1244
|
});
|
|
340
1245
|
export {
|