@cmodjs/core 4.0.3 → 4.1.1
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/docs/commands.md +44 -9
- package/docs/index.md +3 -3
- package/docs/install-steps.md +8 -8
- package/docs/jobs.md +2 -1
- package/docs/mod.md +1 -1
- package/docs/options.md +1 -1
- package/docs/permissions.md +1 -1
- package/docs/session.md +46 -13
- package/docs/testing.md +15 -11
- package/docs/ui.md +43 -11
- package/installer.d.ts +3 -2
- package/installer.d.ts.map +1 -1
- package/jobs/program.d.ts +1 -0
- package/jobs/program.d.ts.map +1 -1
- package/jobs/program.js +39 -23
- package/mod.d.ts +3 -2
- package/mod.d.ts.map +1 -1
- package/mod.js +1 -0
- package/package.json +1 -1
- package/records.d.ts +15 -1
- package/records.d.ts.map +1 -1
- package/records.js +39 -1
- package/register.d.ts.map +1 -1
- package/register.js +8 -5
- package/runtime/before.d.ts +6 -2
- package/runtime/before.d.ts.map +1 -1
- package/runtime/before.js +56 -18
- package/runtime/buttons.d.ts +10 -0
- package/runtime/buttons.d.ts.map +1 -0
- package/runtime/buttons.js +12 -0
- package/runtime/conversations.d.ts +3 -0
- package/runtime/conversations.d.ts.map +1 -1
- package/runtime/conversations.js +164 -27
- package/runtime/engine.d.ts +2 -0
- package/runtime/engine.d.ts.map +1 -1
- package/runtime/grants.d.ts +1 -0
- package/runtime/grants.d.ts.map +1 -1
- package/runtime/grants.js +5 -2
- package/runtime/hotkeys.d.ts.map +1 -1
- package/runtime/hotkeys.js +7 -4
- package/runtime/installer.d.ts +10 -6
- package/runtime/installer.d.ts.map +1 -1
- package/runtime/installer.js +106 -48
- package/runtime/lifecycle.d.ts +2 -2
- package/runtime/lifecycle.d.ts.map +1 -1
- package/runtime/lifecycle.js +144 -42
- package/runtime/pages.d.ts +3 -1
- package/runtime/pages.d.ts.map +1 -1
- package/runtime/pages.js +48 -14
- package/runtime/parts.d.ts +1 -0
- package/runtime/parts.d.ts.map +1 -1
- package/runtime/parts.js +6 -3
- package/runtime/programs.d.ts +1 -0
- package/runtime/programs.d.ts.map +1 -1
- package/runtime/programs.js +4 -1
- package/runtime/sessions.d.ts +10 -3
- package/runtime/sessions.d.ts.map +1 -1
- package/runtime/sessions.js +104 -59
- package/runtime/toaster.d.ts.map +1 -1
- package/runtime/toaster.js +23 -9
- package/runtime/ui.d.ts +22 -4
- package/runtime/ui.d.ts.map +1 -1
- package/runtime/ui.js +161 -59
- package/runtime/watches.d.ts.map +1 -1
- package/runtime/watches.js +18 -4
- package/runtime/widgets.d.ts +7 -5
- package/runtime/widgets.d.ts.map +1 -1
- package/runtime/widgets.js +31 -21
- package/session/context/turn/message/attached-file.d.ts +13 -0
- package/session/context/turn/message/attached-file.d.ts.map +1 -0
- package/session/context/turn/message/attached-file.js +21 -0
- package/session/context/turn/message/message.d.ts +3 -2
- package/session/context/turn/message/message.d.ts.map +1 -1
- package/session/context/turn/message/message.js +20 -6
- package/session/conversation.d.ts +15 -3
- package/session/conversation.d.ts.map +1 -1
- package/session/conversation.js +23 -4
- package/session/goal/task.d.ts +5 -0
- package/session/goal/task.d.ts.map +1 -1
- package/session/session.d.ts +36 -7
- package/session/session.d.ts.map +1 -1
- package/session/session.js +2 -1
- package/src/installer.ts +3 -2
- package/src/jobs/program.ts +41 -21
- package/src/mod.ts +3 -2
- package/src/records.ts +49 -1
- package/src/register.ts +11 -8
- package/src/runtime/before.ts +61 -22
- package/src/runtime/buttons.ts +22 -0
- package/src/runtime/conversations.ts +170 -34
- package/src/runtime/engine.ts +2 -0
- package/src/runtime/grants.ts +6 -2
- package/src/runtime/hotkeys.ts +7 -4
- package/src/runtime/installer.ts +147 -83
- package/src/runtime/lifecycle.ts +149 -48
- package/src/runtime/pages.ts +56 -12
- package/src/runtime/parts.ts +8 -4
- package/src/runtime/programs.ts +5 -1
- package/src/runtime/sessions.ts +106 -59
- package/src/runtime/toaster.ts +27 -9
- package/src/runtime/ui.ts +166 -62
- package/src/runtime/watches.ts +20 -5
- package/src/runtime/widgets.ts +35 -23
- package/src/session/context/turn/message/attached-file.ts +36 -0
- package/src/session/context/turn/message/message.ts +20 -7
- package/src/session/conversation.ts +31 -6
- package/src/session/goal/task.ts +6 -0
- package/src/session/session.ts +18 -7
- package/src/testing/fake-claude.ts +21 -6
- package/src/testing/fake-elements.ts +57 -8
- package/src/testing/fake-sessions.ts +72 -24
- package/src/testing.ts +114 -33
- package/src/ui/areas.ts +1 -0
- package/src/ui/components.ts +14 -8
- package/src/ui/elements.ts +6 -2
- package/src/ui/markdown.ts +3 -3
- package/src/ui/panes.ts +2 -2
- package/src/ui/styles.ts +6 -2
- package/testing/fake-claude.d.ts +3 -0
- package/testing/fake-claude.d.ts.map +1 -1
- package/testing/fake-claude.js +18 -5
- package/testing/fake-elements.d.ts.map +1 -1
- package/testing/fake-elements.js +52 -7
- package/testing/fake-sessions.d.ts +24 -10
- package/testing/fake-sessions.d.ts.map +1 -1
- package/testing/fake-sessions.js +66 -20
- package/testing.d.ts +8 -12
- package/testing.d.ts.map +1 -1
- package/testing.js +113 -27
- package/ui/areas.d.ts +4 -0
- package/ui/areas.d.ts.map +1 -1
- package/ui/components.d.ts +1 -0
- package/ui/components.d.ts.map +1 -1
- package/ui/components.js +11 -7
- package/ui/elements.d.ts +3 -0
- package/ui/elements.d.ts.map +1 -1
- package/ui/elements.js +6 -1
- package/ui/markdown.d.ts +2 -2
- package/ui/markdown.d.ts.map +1 -1
- package/ui/panes.d.ts +2 -1
- package/ui/panes.d.ts.map +1 -1
- package/ui/styles.d.ts +11 -2
- package/ui/styles.d.ts.map +1 -1
package/runtime/ui.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { isObject } from '../records.js';
|
|
2
2
|
import { TurnEnd } from '../ui/areas.js';
|
|
3
|
-
import { Box, drawWith, elementData, Text } from '../ui/elements.js';
|
|
3
|
+
import { Box, drawWith, elementData, Markdown, Text } from '../ui/elements.js';
|
|
4
4
|
import { checkPane, checkPart, sizeKeys } from '../ui/panes.js';
|
|
5
5
|
import { componentOf, nameOf } from '../ui/styles.js';
|
|
6
6
|
import { messageOf } from '../utils/text.js';
|
|
@@ -11,7 +11,16 @@ export const partMethods = {
|
|
|
11
11
|
press: 'cmod:press',
|
|
12
12
|
shown: 'cmod:shown',
|
|
13
13
|
changed: 'cmod:partsChanged',
|
|
14
|
+
stylers: 'cmod:markdownStylers',
|
|
14
15
|
};
|
|
16
|
+
export const stalePress = 'that button drew before the last redraw. Press the one it shows now.';
|
|
17
|
+
const unmeasuredColumns = 80;
|
|
18
|
+
export function viewportColumns(e) {
|
|
19
|
+
return e.viewport?.columns ?? unmeasuredColumns;
|
|
20
|
+
}
|
|
21
|
+
export function styledAt(e, columns = viewportColumns(e)) {
|
|
22
|
+
return { surface: e.surface, columns: Math.max(columns, 0) };
|
|
23
|
+
}
|
|
15
24
|
const bullet = '⏺';
|
|
16
25
|
function paneSize(pane, state) {
|
|
17
26
|
const size = {};
|
|
@@ -75,32 +84,39 @@ function drawPieces(pieces, place) {
|
|
|
75
84
|
return Box({ children: [Box({ minWidth: 2, children: Text({ color: 'text', children: bullet }) }), column(spaced)] });
|
|
76
85
|
return column([opening, Box({ paddingLeft: 2, children: column(rest) })]);
|
|
77
86
|
}
|
|
78
|
-
function merged(updates) {
|
|
87
|
+
export function merged(updates) {
|
|
79
88
|
const parts = updates.findLast((update) => update.parts !== undefined)?.parts;
|
|
80
89
|
const requests = updates.flatMap((update) => update.requests ?? []);
|
|
81
90
|
const isRedrawn = updates.some((update) => update.redraw === true);
|
|
82
91
|
return { ...(parts === undefined ? {} : { parts }), ...(isRedrawn ? { redraw: true } : {}), ...(requests.length === 0 ? {} : { requests }) };
|
|
83
92
|
}
|
|
84
93
|
const keptDrawInputs = 200;
|
|
85
|
-
export function createUi({ name, claude, router, report, announce, mod }) {
|
|
94
|
+
export function createUi({ name, claude, router, report, announce, mod, borrowed, nextDraw }) {
|
|
86
95
|
const panes = new Map();
|
|
87
96
|
const paneLogs = new Map();
|
|
88
97
|
const openPanes = new Set();
|
|
89
98
|
const paneStates = new Map();
|
|
90
99
|
const syncPanes = async () => {
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
if (open
|
|
95
|
-
openPanes.add(
|
|
96
|
-
|
|
100
|
+
const listed = new Map((await claude.ui.panes()).map((open) => [open.id, open]));
|
|
101
|
+
for (const id of panes.keys()) {
|
|
102
|
+
const open = listed.get(id);
|
|
103
|
+
if (open?.isPlaced === true)
|
|
104
|
+
openPanes.add(id);
|
|
105
|
+
else
|
|
106
|
+
openPanes.delete(id);
|
|
107
|
+
if (open === undefined)
|
|
108
|
+
paneStates.delete(id);
|
|
109
|
+
else
|
|
110
|
+
paneStates.set(id, { isShown: open.isShown, isFocused: open.isFocused });
|
|
97
111
|
}
|
|
98
112
|
};
|
|
99
|
-
const readPanes = () =>
|
|
113
|
+
const readPanes = () => syncPanes().catch((error) => claude.ui.log(`${name} keeps its panes' open, shown, and focused state from before: ${messageOf(error)}`, { to: 'debug' }));
|
|
100
114
|
const requestedSizes = new Map();
|
|
101
115
|
const styles = new Set();
|
|
102
116
|
const markdownRenders = new Map();
|
|
103
117
|
let read;
|
|
118
|
+
let stylers = [];
|
|
119
|
+
let stylersSeen = 0;
|
|
104
120
|
let isRouted = false;
|
|
105
121
|
let isResizeQueued = false;
|
|
106
122
|
const widgets = new Map();
|
|
@@ -110,17 +126,55 @@ export function createUi({ name, claude, router, report, announce, mod }) {
|
|
|
110
126
|
const draws = new Map();
|
|
111
127
|
const latestDraws = new Map();
|
|
112
128
|
const failedDraws = new Set();
|
|
113
|
-
let drawCount = 0;
|
|
114
129
|
let beforeStart = [];
|
|
115
130
|
let askedDepth = 0;
|
|
131
|
+
const askedCalls = new Set();
|
|
116
132
|
let unheldRequests = 0;
|
|
117
133
|
let collected;
|
|
118
134
|
let collecting = 0;
|
|
119
135
|
let queued;
|
|
120
136
|
const partList = () => ({
|
|
121
|
-
widgets: [...widgets.values()].map(({ id, title, areas: listed }) => ({ id, title, areas: listed })),
|
|
137
|
+
widgets: [...widgets.values()].map(({ id, title, areas: listed, place }) => ({ id, title, areas: listed, ...(place === undefined ? {} : { place }) })),
|
|
122
138
|
pages: [...pages.values()].map(({ init: { id, title }, place }) => ({ id, title, place })),
|
|
139
|
+
...(markdownRenders.size > 0 ? { stylesMarkdown: true } : {}),
|
|
123
140
|
});
|
|
141
|
+
const hear = (listed, isRestyled) => {
|
|
142
|
+
if (!Array.isArray(listed))
|
|
143
|
+
return;
|
|
144
|
+
const heard = listed.filter((styler) => typeof styler === 'string' && styler !== name);
|
|
145
|
+
if (!isRestyled && heard.join('\0') === stylers.join('\0'))
|
|
146
|
+
return;
|
|
147
|
+
stylers = heard;
|
|
148
|
+
stylersSeen += 1;
|
|
149
|
+
if (name === cmodPluginName || panes.size > 0)
|
|
150
|
+
claude.ui.invalidate('ui.render');
|
|
151
|
+
};
|
|
152
|
+
const treeOf = (render, handlers) => {
|
|
153
|
+
const drawn = render();
|
|
154
|
+
return drawn === undefined ? null : elementData(drawn, handlers);
|
|
155
|
+
};
|
|
156
|
+
const isAnyDrawingChanged = () => {
|
|
157
|
+
for (const [key, inputs] of latestDraws) {
|
|
158
|
+
if (key === 'markdown')
|
|
159
|
+
continue;
|
|
160
|
+
for (const sent of inputs.values()) {
|
|
161
|
+
const handlers = [];
|
|
162
|
+
try {
|
|
163
|
+
if (JSON.stringify(treeOf(sent.render, handlers)) !== sent.tree)
|
|
164
|
+
return true;
|
|
165
|
+
}
|
|
166
|
+
catch (error) {
|
|
167
|
+
claude.ui.log(`${name}: the ${key} drawing threw, so the cmod plugin asks for it again: ${messageOf(error)}`, { to: 'debug' });
|
|
168
|
+
return true;
|
|
169
|
+
}
|
|
170
|
+
const latest = sent.draws.at(-1);
|
|
171
|
+
if (latest !== undefined && draws.has(latest))
|
|
172
|
+
draws.set(latest, { id: key, handlers });
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
return false;
|
|
176
|
+
};
|
|
177
|
+
const withNeededRedraw = ({ redraw, ...update }) => (redraw === true && isAnyDrawingChanged() ? { ...update, redraw } : update);
|
|
124
178
|
const send = (update) => {
|
|
125
179
|
if (collected !== undefined)
|
|
126
180
|
return void collected.push(update);
|
|
@@ -132,21 +186,33 @@ export function createUi({ name, claude, router, report, announce, mod }) {
|
|
|
132
186
|
const batch = [update];
|
|
133
187
|
if (!isAsked)
|
|
134
188
|
queued = batch;
|
|
135
|
-
const tell = () => {
|
|
189
|
+
const tell = async () => {
|
|
136
190
|
if (queued === batch)
|
|
137
191
|
queued = undefined;
|
|
138
|
-
const
|
|
192
|
+
const needed = withNeededRedraw(merged(batch));
|
|
193
|
+
if (Object.keys(needed).length === 0)
|
|
194
|
+
return;
|
|
195
|
+
const input = { ...needed, ...(isAsked ? { isAsked: true } : {}) };
|
|
139
196
|
return claude.cmod.call({ to: cmodPluginName, method: partMethods.changed, input }).then((answer) => {
|
|
140
197
|
if (isAsked && input.requests !== undefined && !(isObject(answer) && answer['isHeld'] === true))
|
|
141
198
|
unheldRequests += 1;
|
|
199
|
+
if (isObject(answer) && name !== cmodPluginName)
|
|
200
|
+
hear(answer['stylers'], false);
|
|
142
201
|
}, (error) => claude.ui.log(`${name}: the cmod plugin did not hear about its pages and widgets: ${messageOf(error)}`, { to: 'debug' }));
|
|
143
202
|
};
|
|
144
|
-
|
|
203
|
+
if (!isAsked)
|
|
204
|
+
return Promise.resolve().then(tell);
|
|
205
|
+
const told = tell().finally(() => askedCalls.delete(told));
|
|
206
|
+
askedCalls.add(told);
|
|
207
|
+
return told;
|
|
145
208
|
};
|
|
146
209
|
const asked = async (run) => {
|
|
147
210
|
askedDepth += 1;
|
|
148
211
|
try {
|
|
149
|
-
|
|
212
|
+
const value = await run();
|
|
213
|
+
while (askedCalls.size > 0)
|
|
214
|
+
await Promise.allSettled(askedCalls);
|
|
215
|
+
return value;
|
|
150
216
|
}
|
|
151
217
|
finally {
|
|
152
218
|
askedDepth -= 1;
|
|
@@ -163,7 +229,7 @@ export function createUi({ name, claude, router, report, announce, mod }) {
|
|
|
163
229
|
}
|
|
164
230
|
const mine = collected;
|
|
165
231
|
collected = collecting === 0 ? undefined : [];
|
|
166
|
-
return merged(mine);
|
|
232
|
+
return withNeededRedraw(merged(mine));
|
|
167
233
|
};
|
|
168
234
|
const logOnce = (what) => {
|
|
169
235
|
let hasLogged = false;
|
|
@@ -174,30 +240,49 @@ export function createUi({ name, claude, router, report, announce, mod }) {
|
|
|
174
240
|
};
|
|
175
241
|
};
|
|
176
242
|
const renderOf = (type) => markdownRenders.get(type);
|
|
177
|
-
const
|
|
243
|
+
const drawnBlock = (piece, at, drawClaudes, table) => {
|
|
244
|
+
if ('text' in piece)
|
|
245
|
+
return drawClaudes(piece.text);
|
|
246
|
+
const { render, props: block } = piece;
|
|
247
|
+
const draw = () => render.Component({ ...block, ...at, Default: ({ source = block.source }) => drawClaudes(source) });
|
|
248
|
+
try {
|
|
249
|
+
return table === undefined ? draw() : drawWith(table, draw);
|
|
250
|
+
}
|
|
251
|
+
catch (error) {
|
|
252
|
+
render.log(threw(error));
|
|
253
|
+
return drawClaudes(block.source);
|
|
254
|
+
}
|
|
255
|
+
};
|
|
256
|
+
const othersFrom = (table, at, from) => (props) => {
|
|
257
|
+
for (const [index, styler] of stylers.entries()) {
|
|
258
|
+
if (index < from)
|
|
259
|
+
continue;
|
|
260
|
+
const cached = `${stylersSeen}\u0000${at.surface}\u0000${at.columns}\u0000${props.text}`;
|
|
261
|
+
const drawn = drawWith(table, () => borrowed(styler, cached, { markdown: props.text, ...at }), othersFrom(table, at, index + 1));
|
|
262
|
+
if (drawn !== undefined)
|
|
263
|
+
return drawn;
|
|
264
|
+
}
|
|
265
|
+
return table.Markdown(props);
|
|
266
|
+
};
|
|
267
|
+
const markdownIn = (table, at) => {
|
|
178
268
|
const reader = read;
|
|
269
|
+
const others = othersFrom(table, at, 0);
|
|
179
270
|
if (reader === undefined)
|
|
180
|
-
return undefined;
|
|
271
|
+
return stylers.length === 0 ? undefined : others;
|
|
181
272
|
return (props) => {
|
|
182
273
|
const pieces = reader(props.text, renderOf);
|
|
183
274
|
if (pieces.every((piece) => 'text' in piece))
|
|
184
|
-
return
|
|
185
|
-
const
|
|
186
|
-
const drawings = pieces.map((piece) => {
|
|
187
|
-
if ('text' in piece)
|
|
188
|
-
return drawClaudes(piece.text);
|
|
189
|
-
const { render, props: block } = piece;
|
|
190
|
-
try {
|
|
191
|
-
return drawWith(table, () => render.Component({ ...block, Default: ({ source = block.source }) => drawClaudes(source) }));
|
|
192
|
-
}
|
|
193
|
-
catch (error) {
|
|
194
|
-
render.log(threw(error));
|
|
195
|
-
return drawClaudes(block.source);
|
|
196
|
-
}
|
|
197
|
-
});
|
|
275
|
+
return others(props);
|
|
276
|
+
const drawings = pieces.map((piece) => drawnBlock(piece, at, (text) => others({ ...props, text }), table));
|
|
198
277
|
return drawPieces(drawings.map((drawing) => ({ drawing, isClaudesRow: false })), 'pane');
|
|
199
278
|
};
|
|
200
279
|
};
|
|
280
|
+
const markdownDrawing = (text, at) => {
|
|
281
|
+
const pieces = read === undefined ? [] : read(text, renderOf);
|
|
282
|
+
if (pieces.every((piece) => 'text' in piece))
|
|
283
|
+
return Markdown({ text });
|
|
284
|
+
return drawPieces(pieces.map((piece) => ({ drawing: drawnBlock(piece, at, (source) => Markdown({ text: source })), isClaudesRow: false })), 'pane');
|
|
285
|
+
};
|
|
201
286
|
const routeMarkdown = (createReader) => {
|
|
202
287
|
if (read !== undefined)
|
|
203
288
|
return;
|
|
@@ -215,7 +300,7 @@ export function createUi({ name, claude, router, report, announce, mod }) {
|
|
|
215
300
|
if ('text' in piece)
|
|
216
301
|
return { drawing: await drawClaudes(piece.text, index === 0), isClaudesRow: true };
|
|
217
302
|
const { render, props: block } = piece;
|
|
218
|
-
const component = await drawComponent(table, (Default) => render.Component({ ...block, Default }), ({ source = block.source }, isClaudesRow) => drawClaudes(source, index === 0 && isClaudesRow));
|
|
303
|
+
const component = await drawComponent(table, (Default) => render.Component({ ...block, ...styledAt(e), id: e.requestId, Default }), ({ source = block.source }, isClaudesRow) => drawClaudes(source, index === 0 && isClaudesRow));
|
|
219
304
|
if (!('failure' in component))
|
|
220
305
|
return component;
|
|
221
306
|
render.log(component.failure);
|
|
@@ -260,10 +345,10 @@ export function createUi({ name, claude, router, report, announce, mod }) {
|
|
|
260
345
|
const { init } = added;
|
|
261
346
|
openPanes.add(init.id);
|
|
262
347
|
paneStates.set(init.id, { isShown: true, isFocused: e.props.isFocused });
|
|
263
|
-
readPanes();
|
|
348
|
+
void readPanes();
|
|
264
349
|
const table = claude.ui.resolve(e);
|
|
265
350
|
try {
|
|
266
|
-
return drawWith(table, () => init.render(mod(), { ...e.props, params: added.params }), markdownIn(table));
|
|
351
|
+
return drawWith(table, () => init.render(mod(), { ...e.props, surface: e.surface, params: added.params }), markdownIn(table, styledAt(e, e.props.bodyColumns)));
|
|
267
352
|
}
|
|
268
353
|
catch (error) {
|
|
269
354
|
paneLogs.get(init.id)?.(`threw: ${messageOf(error)}`);
|
|
@@ -281,8 +366,8 @@ export function createUi({ name, claude, router, report, announce, mod }) {
|
|
|
281
366
|
router.add('ui.close', async (e, next) => {
|
|
282
367
|
openPanes.delete(e.id);
|
|
283
368
|
paneStates.delete(e.id);
|
|
284
|
-
readPanes();
|
|
285
369
|
const closed = await next(e);
|
|
370
|
+
await readPanes();
|
|
286
371
|
const init = panes.get(e.id)?.init;
|
|
287
372
|
if (init?.onClose !== undefined)
|
|
288
373
|
await handle(init, 'onClose', () => init.onClose?.(mod(), e));
|
|
@@ -427,14 +512,15 @@ export function createUi({ name, claude, router, report, announce, mod }) {
|
|
|
427
512
|
const Component = (props) => render(mod(), props);
|
|
428
513
|
const stop = (subscription) => () => {
|
|
429
514
|
subscription?.remove();
|
|
430
|
-
if (markdown !== undefined)
|
|
431
|
-
|
|
515
|
+
if (markdown !== undefined && markdownRenders.delete(markdown.markdown))
|
|
516
|
+
void send({ parts: partList() });
|
|
432
517
|
styles.delete(feature);
|
|
433
518
|
claude.ui.invalidate('ui.render');
|
|
434
519
|
};
|
|
435
520
|
if (markdown !== undefined) {
|
|
436
521
|
markdownRenders.set(markdown.markdown, { Component, log: logOnce(`${nameOf(kind)} style`) });
|
|
437
522
|
routeMarkdown(markdown.createReader);
|
|
523
|
+
void send({ parts: partList() });
|
|
438
524
|
return { kind, remove: stop() };
|
|
439
525
|
}
|
|
440
526
|
const log = logOnce(`${nameOf(kind)} style`);
|
|
@@ -442,7 +528,7 @@ export function createUi({ name, claude, router, report, announce, mod }) {
|
|
|
442
528
|
if (e.component !== component)
|
|
443
529
|
return next(e);
|
|
444
530
|
const table = claude.ui.resolve(e);
|
|
445
|
-
const drawn = await drawComponent(table, (Default) => Component({ ...e.props, Default }), (given, isClaudesRow) => next({ ...e, props: pieceProps(e.props, given, isClaudesRow) }));
|
|
531
|
+
const drawn = await drawComponent(table, (Default) => Component({ ...e.props, ...styledAt(e), id: e.requestId, Default }), (given, isClaudesRow) => next({ ...e, props: pieceProps(e.props, given, isClaudesRow) }));
|
|
446
532
|
if ('failure' in drawn) {
|
|
447
533
|
log(drawn.failure);
|
|
448
534
|
return next(e);
|
|
@@ -455,48 +541,49 @@ export function createUi({ name, claude, router, report, announce, mod }) {
|
|
|
455
541
|
};
|
|
456
542
|
const forgetDraws = (key) => {
|
|
457
543
|
for (const kept of latestDraws.get(key)?.values() ?? [])
|
|
458
|
-
for (const draw of kept)
|
|
544
|
+
for (const draw of kept.draws)
|
|
459
545
|
draws.delete(draw);
|
|
460
546
|
latestDraws.delete(key);
|
|
461
547
|
};
|
|
462
548
|
const drawPart = (key, title, input, render) => {
|
|
463
549
|
try {
|
|
464
550
|
const pressed = [];
|
|
465
|
-
const
|
|
466
|
-
const tree = drawn === undefined ? null : elementData(drawn, pressed);
|
|
551
|
+
const tree = treeOf(render, pressed);
|
|
467
552
|
failedDraws.delete(key);
|
|
468
|
-
|
|
553
|
+
const number = nextDraw();
|
|
469
554
|
const inputs = latestDraws.get(key) ?? new Map();
|
|
470
555
|
latestDraws.set(key, inputs);
|
|
471
556
|
const drawnFor = JSON.stringify('turn' in input ? { ...input, turn: input.turn.prompt.id } : input);
|
|
472
|
-
const earlier = inputs.get(drawnFor) ?? [];
|
|
473
|
-
const kept = [...earlier,
|
|
557
|
+
const earlier = inputs.get(drawnFor)?.draws ?? [];
|
|
558
|
+
const kept = [...earlier, number].slice(-2);
|
|
474
559
|
for (const draw of earlier)
|
|
475
560
|
if (!kept.includes(draw))
|
|
476
561
|
draws.delete(draw);
|
|
477
562
|
inputs.delete(drawnFor);
|
|
478
|
-
inputs.set(drawnFor, kept);
|
|
563
|
+
inputs.set(drawnFor, { draws: kept, tree: JSON.stringify(tree), render });
|
|
479
564
|
const [oldest] = inputs;
|
|
480
565
|
if (inputs.size > keptDrawInputs && oldest !== undefined) {
|
|
481
|
-
for (const draw of oldest[1])
|
|
566
|
+
for (const draw of oldest[1].draws)
|
|
482
567
|
draws.delete(draw);
|
|
483
568
|
inputs.delete(oldest[0]);
|
|
484
569
|
}
|
|
485
|
-
draws.set(
|
|
486
|
-
return { draw:
|
|
570
|
+
draws.set(number, { id: key, handlers: pressed });
|
|
571
|
+
return { draw: number, tree };
|
|
487
572
|
}
|
|
488
573
|
catch (error) {
|
|
489
574
|
if (!failedDraws.has(key))
|
|
490
575
|
report({ id: key, message: `The ${title} could not draw`, error });
|
|
491
576
|
failedDraws.add(key);
|
|
492
|
-
|
|
577
|
+
throw error;
|
|
493
578
|
}
|
|
494
579
|
};
|
|
495
580
|
const draw = (input) => {
|
|
581
|
+
if ('markdown' in input)
|
|
582
|
+
return drawPart('markdown', 'markdown style', input, () => markdownDrawing(input.markdown, { surface: input.surface, columns: input.columns }));
|
|
496
583
|
if ('page' in input) {
|
|
497
584
|
const added = pages.get(input.page);
|
|
498
585
|
if (added === undefined)
|
|
499
|
-
|
|
586
|
+
throw new Error(`${name} has no ${input.page} page.`);
|
|
500
587
|
return drawPart(`page:${input.page}`, `${added.init.title} page`, input, () => added.init.render(mod(), { params: input.params, isFocused: input.isFocused, columns: input.columns }));
|
|
501
588
|
}
|
|
502
589
|
const widget = widgets.get(input.widget);
|
|
@@ -506,19 +593,25 @@ export function createUi({ name, claude, router, report, announce, mod }) {
|
|
|
506
593
|
const context = (input.area === TurnEnd && input.turn !== undefined ? { area: input.area, columns: input.columns, turn: input.turn } : { area: input.area, columns: input.columns });
|
|
507
594
|
return drawPart(`widget:${widget.id}`, `${widget.title} widget`, input, () => widget.render(mod(), context));
|
|
508
595
|
};
|
|
509
|
-
const press = async ({ draw: drawn, handler, args }) => {
|
|
596
|
+
const press = async ({ draw: drawn, handler, args }, caller) => {
|
|
510
597
|
const known = draws.get(drawn);
|
|
511
598
|
const run = known?.handlers[handler];
|
|
512
599
|
if (known === undefined || run === undefined)
|
|
513
|
-
throw new Error(`${name}:
|
|
514
|
-
|
|
600
|
+
throw new Error(`${name}: ${stalePress}`);
|
|
601
|
+
if (caller !== cmodPluginName && known.id !== 'markdown')
|
|
602
|
+
throw new Error(`${name}: ${caller} pressed a button only the cmod plugin draws.`);
|
|
603
|
+
const pressed = async () => {
|
|
515
604
|
try {
|
|
516
605
|
await run(...args);
|
|
517
606
|
}
|
|
518
607
|
catch (error) {
|
|
519
608
|
report({ id: known.id, message: 'Its button failed', error });
|
|
520
609
|
}
|
|
521
|
-
}
|
|
610
|
+
};
|
|
611
|
+
if (caller === cmodPluginName)
|
|
612
|
+
return collect(pressed);
|
|
613
|
+
await pressed();
|
|
614
|
+
return null;
|
|
522
615
|
};
|
|
523
616
|
return {
|
|
524
617
|
panes: { add: addPane },
|
|
@@ -539,19 +632,28 @@ export function createUi({ name, claude, router, report, announce, mod }) {
|
|
|
539
632
|
await passOn();
|
|
540
633
|
return answer;
|
|
541
634
|
},
|
|
542
|
-
answer(method, input) {
|
|
635
|
+
answer(method, input, caller) {
|
|
636
|
+
const isMarkdown = method === partMethods.draw && isObject(input) && typeof input['markdown'] === 'string';
|
|
637
|
+
if (caller !== cmodPluginName && !isMarkdown && method !== partMethods.press)
|
|
638
|
+
return undefined;
|
|
543
639
|
if (method === partMethods.list)
|
|
544
640
|
return Promise.resolve({ value: partList() });
|
|
545
641
|
if (method === partMethods.draw)
|
|
546
|
-
return Promise.resolve({ value: draw(input) });
|
|
642
|
+
return Promise.resolve().then(() => ({ value: draw(input) }));
|
|
547
643
|
if (method === partMethods.press)
|
|
548
|
-
return press(input).then((value) => ({ value }));
|
|
644
|
+
return press(input, caller).then((value) => ({ value }));
|
|
549
645
|
if (method === partMethods.shown) {
|
|
550
646
|
shownPages = input;
|
|
551
647
|
return Promise.resolve({ value: null });
|
|
552
648
|
}
|
|
649
|
+
if (method === partMethods.stylers) {
|
|
650
|
+
hear(input, true);
|
|
651
|
+
return Promise.resolve({ value: null });
|
|
652
|
+
}
|
|
553
653
|
return undefined;
|
|
554
654
|
},
|
|
655
|
+
markdownIn,
|
|
656
|
+
restyled: (stylers) => hear(stylers, true),
|
|
555
657
|
started() {
|
|
556
658
|
const held = merged(beforeStart ?? []);
|
|
557
659
|
beforeStart = undefined;
|
package/runtime/watches.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"watches.d.ts","sourceRoot":"","sources":["../src/runtime/watches.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,WAAW,CAAA;AAE3C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAA;AAC5D,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AACzC,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAA;AAE3C,MAAM,MAAM,OAAO,GAAG;IAAE,GAAG,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,EAAE,QAAQ,EAAE,CAAC,MAAM,EAAE,UAAU,KAAK,OAAO,GAAG,YAAY,CAAA;CAAE,CAAA;
|
|
1
|
+
{"version":3,"file":"watches.d.ts","sourceRoot":"","sources":["../src/runtime/watches.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,WAAW,CAAA;AAE3C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAA;AAC5D,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AACzC,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAA;AAE3C,MAAM,MAAM,OAAO,GAAG;IAAE,GAAG,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,EAAE,QAAQ,EAAE,CAAC,MAAM,EAAE,UAAU,KAAK,OAAO,GAAG,YAAY,CAAA;CAAE,CAAA;AAUhH,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,GAAG,OAAO,CAyEzF"}
|
package/runtime/watches.js
CHANGED
|
@@ -1,12 +1,17 @@
|
|
|
1
1
|
import { ifMissing } from '../utils/paths.js';
|
|
2
2
|
const watchMs = 500;
|
|
3
|
+
const missingEvery = 4;
|
|
3
4
|
const watchedFiles = 2000;
|
|
4
5
|
export function createWatches(claude, report) {
|
|
5
6
|
const watches = new Set();
|
|
6
7
|
let polling;
|
|
7
8
|
let isPolling = false;
|
|
8
|
-
|
|
9
|
+
let ticks = 0;
|
|
10
|
+
const stampsOf = async (watch) => {
|
|
11
|
+
const { paths } = watch;
|
|
9
12
|
const stamps = new Map();
|
|
13
|
+
const missing = new Set();
|
|
14
|
+
const skipped = ticks % missingEvery === 0 ? new Set() : watch.missing;
|
|
10
15
|
const visit = async (path) => {
|
|
11
16
|
const stat = await claude.fs.stat(path).catch(ifMissing(undefined));
|
|
12
17
|
if (stat === undefined)
|
|
@@ -19,8 +24,16 @@ export function createWatches(claude, report) {
|
|
|
19
24
|
for (const entry of await claude.fs.list(path).catch(ifMissing([])))
|
|
20
25
|
await visit(`${path}/${entry.name}`);
|
|
21
26
|
};
|
|
22
|
-
for (const path of paths)
|
|
27
|
+
for (const path of paths) {
|
|
28
|
+
if (skipped.has(path)) {
|
|
29
|
+
missing.add(path);
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
23
32
|
await visit(path);
|
|
33
|
+
if (![...stamps.keys()].some((seen) => seen === path || seen.startsWith(`${path}/`)))
|
|
34
|
+
missing.add(path);
|
|
35
|
+
}
|
|
36
|
+
watch.missing = missing;
|
|
24
37
|
return stamps;
|
|
25
38
|
};
|
|
26
39
|
const stop = (watch) => {
|
|
@@ -30,7 +43,7 @@ export function createWatches(claude, report) {
|
|
|
30
43
|
polling?.cancel();
|
|
31
44
|
polling = undefined;
|
|
32
45
|
};
|
|
33
|
-
const stampsOrStop = (watch) => stampsOf(watch
|
|
46
|
+
const stampsOrStop = (watch) => stampsOf(watch).catch((error) => {
|
|
34
47
|
stop(watch);
|
|
35
48
|
report({ id: 'fs:onChange', message: 'Could not read the files fs.onChange watches', error });
|
|
36
49
|
return undefined;
|
|
@@ -39,6 +52,7 @@ export function createWatches(claude, report) {
|
|
|
39
52
|
if (isPolling)
|
|
40
53
|
return;
|
|
41
54
|
isPolling = true;
|
|
55
|
+
ticks += 1;
|
|
42
56
|
try {
|
|
43
57
|
for (const watch of watches) {
|
|
44
58
|
const now = await stampsOrStop(watch);
|
|
@@ -63,7 +77,7 @@ export function createWatches(claude, report) {
|
|
|
63
77
|
};
|
|
64
78
|
return {
|
|
65
79
|
add(paths, listener) {
|
|
66
|
-
const watch = { paths, listener, seen: undefined };
|
|
80
|
+
const watch = { paths, listener, seen: undefined, missing: new Set() };
|
|
67
81
|
watches.add(watch);
|
|
68
82
|
void stampsOrStop(watch).then((stamps) => void (watch.seen ??= stamps));
|
|
69
83
|
polling ??= claude.clock.every(watchMs, () => void poll());
|
package/runtime/widgets.d.ts
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import type { ElementTable, RenderElement } from 'claude-code';
|
|
1
|
+
import type { ElementTable, MarkdownProps, RenderElement } from 'claude-code';
|
|
2
2
|
import type { TurnData } from '../session/conversation.js';
|
|
3
3
|
import { type Area } from '../ui/areas.js';
|
|
4
|
+
import type { StyledAt } from '../ui/styles.js';
|
|
4
5
|
import type { Claude } from './claude.js';
|
|
5
6
|
import type { DrawnParts } from './parts.js';
|
|
6
7
|
import type { Router } from './router.js';
|
|
7
|
-
import { type PartsUpdate } from './ui.js';
|
|
8
|
+
import { type PartsUpdate, type WidgetDeclaration } from './ui.js';
|
|
8
9
|
export type WidgetRow = {
|
|
9
10
|
readonly left: readonly string[];
|
|
10
11
|
readonly right: readonly string[];
|
|
@@ -18,7 +19,7 @@ export type PlacedWidget = {
|
|
|
18
19
|
readonly mod: string;
|
|
19
20
|
readonly title: string;
|
|
20
21
|
readonly areas: readonly Area[];
|
|
21
|
-
}
|
|
22
|
+
} & Pick<WidgetDeclaration, 'place'>;
|
|
22
23
|
export type Move = 'up' | 'down' | 'left' | 'right';
|
|
23
24
|
export declare const claudeHint = "claude/hint";
|
|
24
25
|
export declare const claudeModes = "claude/modes";
|
|
@@ -31,18 +32,19 @@ export type Widgets = {
|
|
|
31
32
|
reload(): Promise<void>;
|
|
32
33
|
recheck(): Promise<void>;
|
|
33
34
|
rows(area: Area, columns: number): RenderElement[];
|
|
34
|
-
draw(area: Area, table: ElementTable,
|
|
35
|
+
draw(area: Area, table: ElementTable, at: StyledAt, turn?: TurnData): Promise<RenderElement[]>;
|
|
35
36
|
};
|
|
36
37
|
export declare function layoutOf(value: unknown): WidgetLayout;
|
|
37
38
|
export declare function placedIn(layout: WidgetLayout, key: string): Area | undefined;
|
|
38
39
|
export declare function toggled(layout: WidgetLayout, key: string): WidgetLayout;
|
|
39
40
|
export declare function moved(layout: WidgetLayout, widget: PlacedWidget, move: Move): WidgetLayout;
|
|
40
|
-
export declare function serveWidgets({ claude, router, store, report, turnClosedBy, parts, }: {
|
|
41
|
+
export declare function serveWidgets({ claude, router, store, report, turnClosedBy, parts, markdownIn, }: {
|
|
41
42
|
readonly claude: Claude;
|
|
42
43
|
readonly router: Router;
|
|
43
44
|
readonly store: string;
|
|
44
45
|
readonly report: (id: string, message: string, error: unknown) => void;
|
|
45
46
|
readonly turnClosedBy: (rowId: string) => TurnData | undefined;
|
|
46
47
|
readonly parts: DrawnParts;
|
|
48
|
+
readonly markdownIn: (table: ElementTable, at: StyledAt) => ((props: MarkdownProps) => RenderElement) | undefined;
|
|
47
49
|
}): Widgets;
|
|
48
50
|
//# sourceMappingURL=widgets.d.ts.map
|
package/runtime/widgets.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"widgets.d.ts","sourceRoot":"","sources":["../src/runtime/widgets.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;
|
|
1
|
+
{"version":3,"file":"widgets.d.ts","sourceRoot":"","sources":["../src/runtime/widgets.ts"],"names":[],"mappings":"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"}
|