@cmodjs/core 4.0.3 → 4.1.0
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 +43 -8
- package/docs/index.md +3 -3
- package/docs/install-steps.md +6 -6
- 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 +44 -13
- package/docs/testing.md +14 -10
- package/docs/ui.md +42 -10
- 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 +5 -2
- 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.map +1 -1
- package/runtime/conversations.js +134 -25
- 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 +9 -6
- package/runtime/installer.d.ts.map +1 -1
- package/runtime/installer.js +104 -47
- package/runtime/lifecycle.d.ts.map +1 -1
- package/runtime/lifecycle.js +121 -40
- package/runtime/pages.d.ts +3 -1
- package/runtime/pages.d.ts.map +1 -1
- package/runtime/pages.js +34 -10
- 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 +2 -2
- package/runtime/sessions.d.ts.map +1 -1
- package/runtime/sessions.js +88 -58
- package/runtime/toaster.d.ts.map +1 -1
- package/runtime/toaster.js +23 -9
- package/runtime/ui.d.ts +21 -4
- package/runtime/ui.d.ts.map +1 -1
- package/runtime/ui.js +158 -58
- 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 +29 -20
- 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 +14 -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 +35 -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 +9 -6
- package/src/runtime/before.ts +61 -22
- package/src/runtime/buttons.ts +22 -0
- package/src/runtime/conversations.ts +138 -32
- 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 +144 -82
- package/src/runtime/lifecycle.ts +128 -43
- package/src/runtime/pages.ts +45 -8
- package/src/runtime/parts.ts +8 -4
- package/src/runtime/programs.ts +5 -1
- package/src/runtime/sessions.ts +83 -57
- package/src/runtime/toaster.ts +27 -9
- package/src/runtime/ui.ts +163 -61
- package/src/runtime/watches.ts +20 -5
- package/src/runtime/widgets.ts +34 -22
- 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 +30 -6
- package/src/session/goal/task.ts +6 -0
- package/src/session/session.ts +17 -7
- package/src/testing/fake-claude.ts +21 -6
- package/src/testing/fake-elements.ts +51 -8
- package/src/testing/fake-sessions.ts +60 -23
- package/src/testing.ts +86 -28
- 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 +46 -7
- package/testing/fake-sessions.d.ts +21 -10
- package/testing/fake-sessions.d.ts.map +1 -1
- package/testing/fake-sessions.js +58 -20
- package/testing.d.ts +8 -12
- package/testing.d.ts.map +1 -1
- package/testing.js +82 -21
- 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/installer.d.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import type { Args, Frozen, RenderElement } from 'claude-code';
|
|
2
1
|
import type { Step } from '../installer.js';
|
|
3
2
|
import type { Mod } from '../mod.js';
|
|
4
3
|
import type { Option } from '../options.js';
|
|
5
4
|
import type { Claude } from './claude.js';
|
|
6
|
-
|
|
5
|
+
import { type PartsUpdate, type ShownPages } from './ui.js';
|
|
6
|
+
export declare const installPage = "cmod:install";
|
|
7
7
|
export type Consent = {
|
|
8
8
|
readonly install: string;
|
|
9
9
|
readonly uninstall: string;
|
|
@@ -12,16 +12,19 @@ export type Consent = {
|
|
|
12
12
|
readonly updates?: string | undefined;
|
|
13
13
|
};
|
|
14
14
|
export type Installer = {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
15
|
+
answer(method: string, input: unknown): Promise<{
|
|
16
|
+
readonly value: unknown;
|
|
17
|
+
}> | undefined;
|
|
18
|
+
hear(shown: ShownPages): void;
|
|
18
19
|
consent(consent: Consent): Promise<boolean>;
|
|
19
20
|
options(missing: readonly (readonly [string, Option])[], save: (key: string, value: string) => Promise<string | undefined>): Promise<boolean>;
|
|
20
21
|
step<State extends object>(mod: Mod<State>, step: Step<State>, index: number, total: number): Promise<boolean>;
|
|
21
22
|
close(): Promise<void>;
|
|
23
|
+
shown(): Promise<void>;
|
|
24
|
+
collect(run: () => Promise<unknown>): Promise<PartsUpdate>;
|
|
22
25
|
};
|
|
23
26
|
export declare function createInstaller(name: string, claude: () => Claude, waiting: {
|
|
24
27
|
show(open: () => Promise<void>): void;
|
|
25
28
|
hide(): void;
|
|
26
|
-
}): Installer;
|
|
29
|
+
}, nextDraw: () => number): Installer;
|
|
27
30
|
//# sourceMappingURL=installer.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"installer.d.ts","sourceRoot":"","sources":["../src/runtime/installer.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"installer.d.ts","sourceRoot":"","sources":["../src/runtime/installer.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAA;AAC3C,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,WAAW,CAAA;AACpC,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,eAAe,CAAA;AAM3C,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AAEzC,OAAO,EAAuD,KAAK,WAAW,EAAE,KAAK,UAAU,EAAE,MAAM,SAAS,CAAA;AAEhH,eAAO,MAAM,WAAW,iBAAiB,CAAA;AAEzC,MAAM,MAAM,OAAO,GAAG;IACpB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;IAC1B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,WAAW,EAAE,SAAS,MAAM,EAAE,CAAA;IACvC,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;CACtC,CAAA;AAOD,MAAM,MAAM,SAAS,GAAG;IACtB,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC;QAAE,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAA;KAAE,CAAC,GAAG,SAAS,CAAA;IACxF,IAAI,CAAC,KAAK,EAAE,UAAU,GAAG,IAAI,CAAA;IAC7B,OAAO,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA;IAC3C,OAAO,CAAC,OAAO,EAAE,SAAS,CAAC,SAAS,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,EAAE,EAAE,IAAI,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA;IAC7I,IAAI,CAAC,KAAK,SAAS,MAAM,EAAE,GAAG,EAAE,GAAG,CAAC,KAAK,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,KAAK,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA;IAC9G,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;IACtB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;IACtB,OAAO,CAAC,GAAG,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC,WAAW,CAAC,CAAA;CAC3D,CAAA;AAID,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,MAAM,EAAE,OAAO,EAAE;IAAE,IAAI,CAAC,IAAI,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IAAC,IAAI,IAAI,IAAI,CAAA;CAAE,EAAE,QAAQ,EAAE,MAAM,MAAM,GAAG,SAAS,CA2MvK"}
|
package/runtime/installer.js
CHANGED
|
@@ -1,48 +1,108 @@
|
|
|
1
|
-
import { permissionWords, secretFix } from '../records.js';
|
|
2
|
-
import { Box,
|
|
1
|
+
import { isObject, permissionWords, secretFix } from '../records.js';
|
|
2
|
+
import { Box, elementData, Input, Text } from '../ui/elements.js';
|
|
3
|
+
import { filledButton } from './buttons.js';
|
|
3
4
|
import { messageOf } from '../utils/text.js';
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
import { cmodPluginName, withBareRefusals } from './dependencies.js';
|
|
6
|
+
import { merged, partMethods } from './ui.js';
|
|
7
|
+
export const installPage = 'cmod:install';
|
|
8
|
+
const keptDraws = 8;
|
|
9
|
+
export function createInstaller(name, claude, waiting, nextDraw) {
|
|
10
|
+
let question;
|
|
8
11
|
let isOpen = false;
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
+
let isSeen = false;
|
|
13
|
+
let waitingForShow = [];
|
|
14
|
+
let collected;
|
|
15
|
+
const draws = new Map();
|
|
16
|
+
const send = async (update) => {
|
|
17
|
+
if (collected !== undefined)
|
|
18
|
+
return void collected.push(update);
|
|
19
|
+
await withBareRefusals(claude()).cmod.call({ to: cmodPluginName, method: partMethods.changed, input: update });
|
|
12
20
|
};
|
|
21
|
+
const log = (what) => (error) => claude().ui.log(`${name}: ${what}: ${messageOf(error)}`, { to: 'debug' });
|
|
22
|
+
const open = (isAsked) => send({ requests: [{ open: installPage, focus: true }], ...(isAsked ? { isAsked: true } : {}) });
|
|
13
23
|
const show = async (build, cancelled) => {
|
|
14
|
-
|
|
24
|
+
question?.cancel();
|
|
15
25
|
const answer = new Promise((resolve) => {
|
|
26
|
+
let isAnswered = false;
|
|
16
27
|
const done = (value) => {
|
|
17
|
-
if (
|
|
18
|
-
|
|
28
|
+
if (isAnswered)
|
|
29
|
+
return;
|
|
30
|
+
isAnswered = true;
|
|
19
31
|
waiting.hide();
|
|
20
|
-
resolve(value);
|
|
32
|
+
claude().clock.after(0, () => resolve(value));
|
|
21
33
|
};
|
|
22
|
-
|
|
23
|
-
page = shown;
|
|
34
|
+
question = { ...build(done), cancel: () => done(cancelled) };
|
|
24
35
|
});
|
|
25
|
-
waiting.show(open);
|
|
26
|
-
if (
|
|
27
|
-
await
|
|
36
|
+
waiting.show(() => open(true));
|
|
37
|
+
if (isOpen) {
|
|
38
|
+
await send({ redraw: true }).catch(log('the installer did not redraw'));
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
isOpen = true;
|
|
42
|
+
await open(false).catch((error) => {
|
|
43
|
+
isOpen = false;
|
|
28
44
|
waiting.hide();
|
|
29
45
|
throw error;
|
|
30
46
|
});
|
|
31
|
-
|
|
47
|
+
}
|
|
48
|
+
for (const resolve of waitingForShow)
|
|
49
|
+
resolve();
|
|
50
|
+
waitingForShow = [];
|
|
32
51
|
return answer;
|
|
33
52
|
};
|
|
34
|
-
const
|
|
35
|
-
const
|
|
53
|
+
const redraw = () => void send({ redraw: true }).catch(log('the installer did not redraw'));
|
|
54
|
+
const actions = (...buttons) => Box({ gap: 2, flexShrink: 0, children: buttons });
|
|
55
|
+
const dialog = (title, ...children) => Box({ flexDirection: 'column', gap: 1, children: [title, ...children] });
|
|
56
|
+
const bullet = (text) => Box({ gap: 1, children: [Text({ dimColor: true, children: '•' }), Text({ wrap: 'wrap', children: text })] });
|
|
57
|
+
const group = (heading, items) => Box({ flexDirection: 'column', children: [Text({ bold: true, children: heading }), ...items.map(bullet)] });
|
|
58
|
+
const draw = (columns, isFocused) => {
|
|
59
|
+
if (question === undefined && !isOpen)
|
|
60
|
+
claude().clock.after(0, () => void send({ requests: [{ close: installPage }] }).catch(log('a leftover install page stayed open')));
|
|
61
|
+
const handlers = [];
|
|
62
|
+
const tree = elementData(question?.draw({ params: undefined, isFocused, columns }) ?? Text({ dimColor: true, children: `${name} needs nothing from you now.` }), handlers);
|
|
63
|
+
const drawn = nextDraw();
|
|
64
|
+
draws.set(drawn, handlers);
|
|
65
|
+
for (const kept of draws.keys())
|
|
66
|
+
if (draws.size > keptDraws)
|
|
67
|
+
draws.delete(kept);
|
|
68
|
+
return { draw: drawn, tree };
|
|
69
|
+
};
|
|
70
|
+
const collect = async (run) => {
|
|
71
|
+
const outer = collected;
|
|
72
|
+
const mine = [];
|
|
73
|
+
collected = mine;
|
|
74
|
+
try {
|
|
75
|
+
await run();
|
|
76
|
+
}
|
|
77
|
+
finally {
|
|
78
|
+
collected = outer;
|
|
79
|
+
}
|
|
80
|
+
return merged(mine);
|
|
81
|
+
};
|
|
82
|
+
const press = async ({ draw: drawn, handler, args }) => {
|
|
83
|
+
const run = draws.get(drawn)?.[handler];
|
|
84
|
+
if (run === undefined)
|
|
85
|
+
throw new Error(`${name}: that button drew before the last redraw. Press the one it shows now.`);
|
|
86
|
+
return collect(() => run(...args));
|
|
87
|
+
};
|
|
36
88
|
return {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
89
|
+
answer(method, input) {
|
|
90
|
+
if (method === partMethods.draw && isObject(input) && input['page'] === installPage)
|
|
91
|
+
return Promise.resolve({ value: draw(Number(input['columns']), input['isFocused'] === true) });
|
|
92
|
+
if (method === partMethods.press && isObject(input) && typeof input['draw'] === 'number' && draws.has(input['draw']))
|
|
93
|
+
return press(input).then((value) => ({ value }));
|
|
94
|
+
return undefined;
|
|
40
95
|
},
|
|
41
|
-
|
|
42
|
-
if (id
|
|
96
|
+
hear(shown) {
|
|
97
|
+
if (shown.open.some((each) => each.id === installPage)) {
|
|
98
|
+
isSeen = isOpen;
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
if (!isSeen)
|
|
43
102
|
return;
|
|
44
103
|
isOpen = false;
|
|
45
|
-
|
|
104
|
+
isSeen = false;
|
|
105
|
+
question?.cancel();
|
|
46
106
|
},
|
|
47
107
|
consent({ install, uninstall, hotkeys, permissions, updates }) {
|
|
48
108
|
const changes = [
|
|
@@ -52,24 +112,17 @@ export function createInstaller(name, claude, waiting) {
|
|
|
52
112
|
...(updates === undefined ? [] : [`Update ${name} automatically from the ${updates} marketplace`]),
|
|
53
113
|
];
|
|
54
114
|
return show((answer) => ({
|
|
55
|
-
draw: () =>
|
|
56
|
-
...(permissions.length === 0 ? [] : [Box({ flexDirection: 'column', children: permissions.map((item) => bullet(permissionWords(item))) })]),
|
|
57
|
-
...(changes.length === 0 ? [] : [Text({ children: permissions.length === 0 ? 'Change your computer:' : 'and change your computer:' }), Box({ flexDirection: 'column', children: changes.map(bullet) })]),
|
|
58
|
-
Text({ dimColor: true, children: 'You can turn each permission off later in Settings.' }),
|
|
59
|
-
], [Button({ key: 'not-now', hotkey: 'n', label: 'Not now', onPress: () => answer(false) }), Button({ key: 'accept', hotkey: 'a', label: 'Accept', onPress: () => answer(true) })]),
|
|
115
|
+
draw: () => dialog(Text({ bold: true, children: `Install ${name}?` }), ...(permissions.length === 0 ? [] : [group(`${name} wants to:`, permissions.map(permissionWords))]), ...(changes.length === 0 ? [] : [group(permissions.length === 0 ? `${name} wants to change your computer:` : 'and change your computer:', changes)]), Text({ dimColor: true, wrap: 'wrap', children: 'You can turn each permission off later in Settings.' }), actions(filledButton({ key: 'accept', hotkey: 'a', variant: 'primary', label: 'Accept', onPress: () => answer(true) }), filledButton({ key: 'not-now', hotkey: 'n', label: 'Not now', onPress: () => answer(false) }))),
|
|
60
116
|
}), false);
|
|
61
117
|
},
|
|
62
118
|
options(missing, save) {
|
|
63
119
|
const saved = new Map();
|
|
64
120
|
const failures = new Map();
|
|
65
121
|
return show((answer) => ({
|
|
66
|
-
draw: () =>
|
|
67
|
-
const label =
|
|
122
|
+
draw: () => dialog(Text({ bold: true, children: `Set up ${name}` }), Text({ children: `${name} needs ${missing.length === 1 ? 'one setting' : `${missing.length} settings`}:` }), ...missing.map(([key, option]) => {
|
|
123
|
+
const label = Box({ flexDirection: 'column', children: [Text({ bold: true, children: option.title }), ...(option.description === '' ? [] : [Text({ dimColor: true, wrap: 'wrap', children: option.description })])] });
|
|
68
124
|
if (option.type === 'secret') {
|
|
69
|
-
return Box({
|
|
70
|
-
flexDirection: 'column',
|
|
71
|
-
children: [label, Text({ dimColor: true, children: `${secretFix} Then press Next.` }), Button({ key: `secret:${key}`, label: 'Open /config', onPress: () => void claude().command.run({ command: 'config' }) })],
|
|
72
|
-
});
|
|
125
|
+
return Box({ flexDirection: 'column', children: [label, Text({ dimColor: true, wrap: 'wrap', children: `${secretFix} Then press Next.` }), filledButton({ key: `secret:${key}`, label: 'Open /config', onPress: () => void claude().command.run({ command: 'config' }) })] });
|
|
73
126
|
}
|
|
74
127
|
const failure = failures.get(key);
|
|
75
128
|
const field = Input({
|
|
@@ -88,11 +141,11 @@ export function createInstaller(name, claude, waiting) {
|
|
|
88
141
|
failures.set(key, refused);
|
|
89
142
|
})
|
|
90
143
|
.catch((error) => failures.set(key, messageOf(error)))
|
|
91
|
-
.finally(
|
|
144
|
+
.finally(redraw),
|
|
92
145
|
});
|
|
93
|
-
const status = failure !== undefined ? Text({ color: 'error', children: failure }) : saved.has(key) ? Text({ color: 'success', children: '
|
|
94
|
-
return Box({ flexDirection: 'column', children: [label, field, status] });
|
|
95
|
-
}),
|
|
146
|
+
const status = failure !== undefined ? [Text({ color: 'error', wrap: 'wrap', children: failure })] : saved.has(key) ? [Text({ color: 'success', children: '✓ saved' })] : [];
|
|
147
|
+
return Box({ flexDirection: 'column', children: [label, field, ...status] });
|
|
148
|
+
}), actions(filledButton({ key: 'next', hotkey: 'x', variant: 'primary', label: 'Next', onPress: () => answer(true) }), filledButton({ key: 'not-now', hotkey: 'n', label: 'Not now', onPress: () => answer(false) }))),
|
|
96
149
|
}), false);
|
|
97
150
|
},
|
|
98
151
|
step(mod, step, index, total) {
|
|
@@ -104,7 +157,7 @@ export function createInstaller(name, claude, waiting) {
|
|
|
104
157
|
body = step.render(mod, props);
|
|
105
158
|
}
|
|
106
159
|
catch (error) {
|
|
107
|
-
body = Text({ color: 'error', children: `The ${step.title} step could not draw: ${messageOf(error)}` });
|
|
160
|
+
body = Text({ color: 'error', wrap: 'wrap', children: `The ${step.title} step could not draw: ${messageOf(error)}` });
|
|
108
161
|
}
|
|
109
162
|
const next = async () => {
|
|
110
163
|
try {
|
|
@@ -115,18 +168,22 @@ export function createInstaller(name, claude, waiting) {
|
|
|
115
168
|
catch (error) {
|
|
116
169
|
notice = { color: 'error', text: `The ${step.title} step's isDone failed: ${messageOf(error)}` };
|
|
117
170
|
}
|
|
118
|
-
|
|
171
|
+
await send({ redraw: true });
|
|
119
172
|
};
|
|
120
|
-
return
|
|
173
|
+
return dialog(Text({ children: [Text({ bold: true, children: step.title }), Text({ dimColor: true, children: ` (${index} of ${total})` })] }), body, ...(notice === undefined ? [] : [Text({ color: notice.color, wrap: 'wrap', children: notice.text })]), actions(filledButton({ key: 'next', hotkey: 'x', variant: 'primary', label: index === total ? 'Finish' : 'Next', onPress: () => void next() }), filledButton({ key: 'not-now', hotkey: 'n', label: 'Not now', onPress: () => answer(false) })));
|
|
121
174
|
},
|
|
122
175
|
}), false);
|
|
123
176
|
},
|
|
124
177
|
async close() {
|
|
125
|
-
|
|
178
|
+
question?.cancel();
|
|
179
|
+
question = undefined;
|
|
126
180
|
if (!isOpen)
|
|
127
181
|
return;
|
|
128
182
|
isOpen = false;
|
|
129
|
-
|
|
183
|
+
isSeen = false;
|
|
184
|
+
await send({ requests: [{ close: installPage }] }).catch(log('the install page stayed open'));
|
|
130
185
|
},
|
|
186
|
+
shown: () => new Promise((resolve) => void waitingForShow.push(resolve)),
|
|
187
|
+
collect,
|
|
131
188
|
};
|
|
132
189
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lifecycle.d.ts","sourceRoot":"","sources":["../src/runtime/lifecycle.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAqB,WAAW,EAAE,MAAM,EAAc,WAAW,EAAE,aAAa,EAAwB,MAAM,aAAa,CAAA;AAG7I,OAAO,KAAK,EAAoB,GAAG,EAAE,aAAa,EAA8G,MAAM,WAAW,CAAA;AACjL,OAAO,KAAK,EAAE,OAAO,EAAgB,MAAM,eAAe,CAAA;AAC1D,OAAO,
|
|
1
|
+
{"version":3,"file":"lifecycle.d.ts","sourceRoot":"","sources":["../src/runtime/lifecycle.ts"],"names":[],"mappings":"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"}
|
package/runtime/lifecycle.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { consentPath, dataFolder, finishStepsMethod, grantFix, coreVersion, isAtLeast, marketplaceOf, parseConsent, pendingStepsMethod, parseEvent, permissionWords, readRecord, readSteps, recordPath, scriptsSha256, storeFolder, updatesToTurnOn } from '../records.js';
|
|
1
|
+
import { consentPath, dataFolder, finishStepsMethod, grantFix, coreVersion, isAtLeast, isProgramNewer, marketplaceOf, parseConsent, pendingStepsMethod, parseEvent, permissionWords, programPath, readRecord, readSteps, recordPath, scriptsSha256, storeFolder, updatesToTurnOn } from '../records.js';
|
|
2
2
|
import { homePrefix, ifMissing, relativePath } from '../utils/paths.js';
|
|
3
3
|
import { firstLine, formatExit, messageOf } from '../utils/text.js';
|
|
4
4
|
import { beforeDeadline } from './deadline.js';
|
|
@@ -8,8 +8,8 @@ import { serveConversations } from './conversations.js';
|
|
|
8
8
|
import { answerCall, answeringOwnCalls, cmodPluginName, dependencyCalls, withBareRefusals } from './dependencies.js';
|
|
9
9
|
import { keepEngine } from './engine.js';
|
|
10
10
|
import { classicFailure, classicHook, userSkillHook } from './hooks.js';
|
|
11
|
-
import { checkedAnswer, checkingGrants, checkSettings, checksAnswers, checkWrite, isCovered, itemOf, passedDown, programAddresses } from './grants.js';
|
|
12
|
-
import { cmodError, locatingPrograms, readLines } from './programs.js';
|
|
11
|
+
import { checkedAnswer, checkingGrants, checkRun, checkSettings, checksAnswers, checkWrite, isCovered, itemOf, passedDown, programAddresses } from './grants.js';
|
|
12
|
+
import { cmodError, locatingPrograms, readLines, spawnCmod } from './programs.js';
|
|
13
13
|
import { createModFiles, resolvedPath } from './metadata.js';
|
|
14
14
|
import { fitsOption } from '../options.js';
|
|
15
15
|
import { createInstaller } from './installer.js';
|
|
@@ -35,6 +35,8 @@ const askingId = 'cmod:asking';
|
|
|
35
35
|
const recordId = 'cmod:record';
|
|
36
36
|
const installRetrySeconds = [2, 4, 8, 16, 32, 64, 128];
|
|
37
37
|
const cmodCheckMs = 1000;
|
|
38
|
+
const recordCheckMs = 1000;
|
|
39
|
+
const drawsPerMs = 1000;
|
|
38
40
|
const cmodWaitMs = 60_000;
|
|
39
41
|
const cmodSlowCheckMs = 10_000;
|
|
40
42
|
const hookMs = 10_000;
|
|
@@ -68,7 +70,8 @@ export async function readPlugin(claude) {
|
|
|
68
70
|
if (version === undefined || record?.scriptsSha256 !== scripts)
|
|
69
71
|
return { ...plugin, isInstalled: false, shouldRecord: false };
|
|
70
72
|
const isInstalled = record.version === version || record.root === root;
|
|
71
|
-
|
|
73
|
+
const programModifiedMs = record.program === null ? undefined : (await claude.fs.stat(programPath(store, record.program, record.version)).catch(ifMissing(undefined)))?.mtimeMs;
|
|
74
|
+
return { ...plugin, isInstalled, shouldRecord: isInstalled && (record.version !== version || isProgramNewer(record, programModifiedMs)) };
|
|
72
75
|
}
|
|
73
76
|
async function filesBelow(claude, folder) {
|
|
74
77
|
const entries = await claude.fs.list(folder);
|
|
@@ -121,6 +124,8 @@ export function createLifecycle(definition, permissionRoutes = () => undefined,
|
|
|
121
124
|
let raise;
|
|
122
125
|
let failure;
|
|
123
126
|
let pluginOptions = options;
|
|
127
|
+
let drawCount = 0;
|
|
128
|
+
const nextDraw = () => (drawCount += 1);
|
|
124
129
|
const installer = createInstaller(definition.name, () => claude(), {
|
|
125
130
|
show: (open) => {
|
|
126
131
|
if (phase !== 'active')
|
|
@@ -128,7 +133,7 @@ export function createLifecycle(definition, permissionRoutes = () => undefined,
|
|
|
128
133
|
runtime?.toaster.toast.info('Installing', { id: askingId, description: 'Waiting for your answer', duration: Infinity, action: { label: 'Answer', onClick: open } });
|
|
129
134
|
},
|
|
130
135
|
hide: () => runtime?.toaster.toast.dismiss(askingId),
|
|
131
|
-
});
|
|
136
|
+
}, nextDraw);
|
|
132
137
|
let shouldRecord = false;
|
|
133
138
|
let recording;
|
|
134
139
|
let missedSessionStart;
|
|
@@ -147,6 +152,7 @@ export function createLifecycle(definition, permissionRoutes = () => undefined,
|
|
|
147
152
|
failure ??= new Error(`${definition.name}: ${reason}${/[.!?]$/.test(reason) ? '' : '.'}${fix === undefined ? '' : ` ${fix}`}`, { cause: error });
|
|
148
153
|
const again = retry === undefined ? {} : { retry: () => ((failure = undefined), stopRetrying(), retry().catch(report)) };
|
|
149
154
|
toastFailure({ id: installId, message: reason, ...(fix === undefined ? {} : { description: fix }), ...(error === undefined ? {} : { error }), ...again });
|
|
155
|
+
watchRecord();
|
|
150
156
|
};
|
|
151
157
|
let consented;
|
|
152
158
|
const installAgain = () => install(consented);
|
|
@@ -255,29 +261,34 @@ export function createLifecycle(definition, permissionRoutes = () => undefined,
|
|
|
255
261
|
};
|
|
256
262
|
const runSteps = async (active) => {
|
|
257
263
|
const steps = asksPerson ? (definition.installer ?? []) : [];
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
264
|
+
try {
|
|
265
|
+
for (const [index, step] of steps.entries()) {
|
|
266
|
+
if (await isStepDone(active, step))
|
|
267
|
+
continue;
|
|
268
|
+
const isAnswered = (await claude().session.surfaces()).length > 0 && (await installer.step(active, step, index + 1, steps.length));
|
|
269
|
+
if (!isAnswered) {
|
|
270
|
+
runtime?.toaster.toast.warning(steps.length - index === 1 ? 'Needs one more step' : `Needs ${steps.length - index} more steps`, {
|
|
271
|
+
id: 'cmod:steps',
|
|
272
|
+
description: `Starting with ${step.title}.`,
|
|
273
|
+
duration: Infinity,
|
|
274
|
+
action: { label: 'Finish', onClick: () => runSteps(active) },
|
|
275
|
+
});
|
|
276
|
+
break;
|
|
277
|
+
}
|
|
270
278
|
}
|
|
271
279
|
}
|
|
272
|
-
|
|
280
|
+
finally {
|
|
281
|
+
await installer.close();
|
|
282
|
+
}
|
|
273
283
|
};
|
|
274
284
|
const activate = async () => {
|
|
275
285
|
if (runtime === undefined || plugin === undefined)
|
|
276
286
|
return;
|
|
277
287
|
const activeRouter = createRouter();
|
|
278
|
-
const active = await createMod(definition, { ...runtime, router: activeRouter, dataFolder: dataFolder(plugin.store, plugin.name), store: plugin.store, steps: plugin.steps, granted, refreshGrants, permissionRoutes, options: pluginOptions }).catch(async (error) => {
|
|
288
|
+
const active = await createMod(definition, { ...runtime, router: activeRouter, dataFolder: dataFolder(plugin.store, plugin.name), store: plugin.store, steps: plugin.steps, granted, refreshGrants, permissionRoutes, options: pluginOptions, nextDraw }).catch(async (error) => {
|
|
279
289
|
if (error instanceof MissingOptions && (await askOptions(error)))
|
|
280
290
|
return undefined;
|
|
291
|
+
await installer.close();
|
|
281
292
|
fail(messageOf(error), error instanceof MissingOptions ? `Retry asks for ${error.keys.length === 1 ? 'it' : 'them'}.` : undefined, begin, error);
|
|
282
293
|
throw error;
|
|
283
294
|
});
|
|
@@ -289,6 +300,7 @@ export function createLifecycle(definition, permissionRoutes = () => undefined,
|
|
|
289
300
|
raise = active.raise;
|
|
290
301
|
router = activeRouter;
|
|
291
302
|
phase = 'active';
|
|
303
|
+
stopWatchingRecord();
|
|
292
304
|
runtime.claude.ui.invalidate('ui.render');
|
|
293
305
|
void runSteps(active.mod).catch((error) => toastFailure({ id: 'cmod:steps', message: 'Its installer stopped', error }));
|
|
294
306
|
const missed = missedSessionStart;
|
|
@@ -343,9 +355,15 @@ export function createLifecycle(definition, permissionRoutes = () => undefined,
|
|
|
343
355
|
toastFailure({ id: 'cmod:updates', message: 'Could not read whether its marketplace updates automatically', error });
|
|
344
356
|
return undefined;
|
|
345
357
|
});
|
|
346
|
-
const
|
|
358
|
+
const installedElsewhere = new Promise((resolve) => (settleAsked = () => resolve('installed')));
|
|
359
|
+
watchRecord();
|
|
360
|
+
const answer = await Promise.race([installer.consent({ ...event, updates }), installedElsewhere]).finally(() => {
|
|
361
|
+
settleAsked = undefined;
|
|
362
|
+
});
|
|
347
363
|
await installer.close();
|
|
348
|
-
if (
|
|
364
|
+
if (answer === 'installed')
|
|
365
|
+
return startInstalled();
|
|
366
|
+
if (answer) {
|
|
349
367
|
consented = event.sha256;
|
|
350
368
|
return install(consented);
|
|
351
369
|
}
|
|
@@ -480,8 +498,8 @@ export function createLifecycle(definition, permissionRoutes = () => undefined,
|
|
|
480
498
|
};
|
|
481
499
|
let recordMs = 0;
|
|
482
500
|
let readStarted;
|
|
483
|
-
const
|
|
484
|
-
if (plugin === undefined || readStarted === undefined
|
|
501
|
+
const isNewlyInstalled = async () => {
|
|
502
|
+
if (plugin === undefined || readStarted === undefined)
|
|
485
503
|
return false;
|
|
486
504
|
const modifiedMs = await stampOf(recordPath(plugin.store, plugin.name));
|
|
487
505
|
if (modifiedMs === recordMs)
|
|
@@ -491,12 +509,40 @@ export function createLifecycle(definition, permissionRoutes = () => undefined,
|
|
|
491
509
|
if (!current.isInstalled)
|
|
492
510
|
return false;
|
|
493
511
|
plugin = current;
|
|
512
|
+
return true;
|
|
513
|
+
};
|
|
514
|
+
const startInstalled = async () => {
|
|
494
515
|
failure = undefined;
|
|
495
516
|
activation = undefined;
|
|
496
517
|
await refreshGrants();
|
|
497
518
|
await finish();
|
|
519
|
+
};
|
|
520
|
+
let settleAsked;
|
|
521
|
+
const recover = async () => {
|
|
522
|
+
if ((settleAsked === undefined && phase !== 'failed' && phase !== 'declined') || !(await isNewlyInstalled()))
|
|
523
|
+
return false;
|
|
524
|
+
if (settleAsked === undefined)
|
|
525
|
+
await startInstalled();
|
|
526
|
+
else
|
|
527
|
+
settleAsked();
|
|
498
528
|
return true;
|
|
499
529
|
};
|
|
530
|
+
let recordCheck;
|
|
531
|
+
let checking;
|
|
532
|
+
const watchRecord = () => {
|
|
533
|
+
recordCheck ??= claude().clock.every(recordCheckMs, () => {
|
|
534
|
+
checking ??= recover()
|
|
535
|
+
.then(() => undefined)
|
|
536
|
+
.catch((error) => claude().ui.log(`${definition.name} could not read whether another cmod installed it: ${messageOf(error)}`, { to: 'debug' }))
|
|
537
|
+
.finally(() => {
|
|
538
|
+
checking = undefined;
|
|
539
|
+
});
|
|
540
|
+
});
|
|
541
|
+
};
|
|
542
|
+
const stopWatchingRecord = () => {
|
|
543
|
+
recordCheck?.cancel();
|
|
544
|
+
recordCheck = undefined;
|
|
545
|
+
};
|
|
500
546
|
const refreshGrants = async () => {
|
|
501
547
|
if (plugin === undefined || plugin.name === cmodPluginName)
|
|
502
548
|
return;
|
|
@@ -579,6 +625,7 @@ export function createLifecycle(definition, permissionRoutes = () => undefined,
|
|
|
579
625
|
runtime = { claude: programs.claude, toaster: createToaster(claudeCalls, definition.name, () => plugin?.version, () => openSettings('permissions')) };
|
|
580
626
|
readStarted = read;
|
|
581
627
|
try {
|
|
628
|
+
drawCount = (await claudeCalls.clock.now()) * drawsPerMs;
|
|
582
629
|
await programs.refresh();
|
|
583
630
|
await begin().catch(report);
|
|
584
631
|
}
|
|
@@ -589,10 +636,16 @@ export function createLifecycle(definition, permissionRoutes = () => undefined,
|
|
|
589
636
|
async route(event, e, next) {
|
|
590
637
|
if (imports !== undefined)
|
|
591
638
|
bind(imports);
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
639
|
+
const asked = e;
|
|
640
|
+
if (event === 'cmod.call' && asked.to === definition.name && asked.caller === cmodPluginName) {
|
|
641
|
+
if (asked.method === partMethods.shown)
|
|
642
|
+
installer.hear(asked.input);
|
|
643
|
+
const answered = installer.answer(asked.method, asked.input);
|
|
644
|
+
if (answered !== undefined)
|
|
645
|
+
return (await answered.catch((error) => ({ deny: `${definition.name}: ${messageOf(error)}` })));
|
|
646
|
+
if (asked.method === partMethods.shown && phase !== 'active')
|
|
647
|
+
return { value: null };
|
|
648
|
+
}
|
|
596
649
|
if (event === 'classic.SessionStart') {
|
|
597
650
|
missedSessionStart = undefined;
|
|
598
651
|
hasHeardSessionStart = true;
|
|
@@ -605,10 +658,11 @@ export function createLifecycle(definition, permissionRoutes = () => undefined,
|
|
|
605
658
|
record();
|
|
606
659
|
if (event === 'classic.UserPromptSubmit' || event === 'classic.SessionStart')
|
|
607
660
|
await recover().catch((error) => claude().ui.log(`${definition.name} stays stopped: ${messageOf(error)}`, { to: 'debug' }));
|
|
608
|
-
const
|
|
661
|
+
const tool = e.tool;
|
|
662
|
+
const isCallToThisMod = (event === 'cmod.call' && e.to === definition.name) || (event === 'tool.call' && typeof tool === 'string' && tool.startsWith(`mcp__${definition.name}__`));
|
|
609
663
|
if (event === 'cmod.call' && !isCallToThisMod)
|
|
610
664
|
return next(e);
|
|
611
|
-
await (event === 'classic.SessionStart' || isCallToThisMod ? holdUntilStarted() : whenActive());
|
|
665
|
+
await (event === 'classic.SessionStart' || event === 'session.append' || isCallToThisMod ? holdUntilStarted() : whenActive());
|
|
612
666
|
if (event === 'classic.SessionStart' && phase !== 'active') {
|
|
613
667
|
missedSessionStart = e;
|
|
614
668
|
return next(e);
|
|
@@ -621,8 +675,11 @@ export function createLifecycle(definition, permissionRoutes = () => undefined,
|
|
|
621
675
|
if (event === 'cmod.call' && call.to === definition.name && mod !== undefined) {
|
|
622
676
|
if (call.method === pendingStepsMethod)
|
|
623
677
|
return { value: await pendingSteps(mod) };
|
|
624
|
-
if (call.method === finishStepsMethod)
|
|
625
|
-
|
|
678
|
+
if (call.method === finishStepsMethod) {
|
|
679
|
+
const active = mod;
|
|
680
|
+
const opened = await installer.collect(() => Promise.race([runSteps(active).catch((error) => void toastFailure({ id: 'cmod:steps', message: 'Could not finish the setup', error })), installer.shown()]));
|
|
681
|
+
return { value: opened };
|
|
682
|
+
}
|
|
626
683
|
}
|
|
627
684
|
if (event === 'classic.UserPromptSubmit' || event === 'classic.SessionStart')
|
|
628
685
|
await refreshGrants().catch((error) => claude().ui.log(`${definition.name} keeps its grants from before: ${messageOf(error)}`, { to: 'debug' }));
|
|
@@ -689,7 +746,13 @@ async function createMod(definition, runtime) {
|
|
|
689
746
|
let parentAsked;
|
|
690
747
|
let hasEarlierPrompts = true;
|
|
691
748
|
let agentType;
|
|
692
|
-
|
|
749
|
+
let parts;
|
|
750
|
+
const borrowed = (styler, cached, input) => {
|
|
751
|
+
const name = `The markdown style of ${styler}`;
|
|
752
|
+
void parts?.fill(styler, cached, input, name)?.then(() => claude.ui.invalidate('ui.render'));
|
|
753
|
+
return parts?.element(styler, cached, `${styler}/markdown:`, name, `markdown:${styler}`);
|
|
754
|
+
};
|
|
755
|
+
const area = createUi({ name: definition.name, claude, router, report, announce, mod: () => mod, borrowed, nextDraw: runtime.nextDraw });
|
|
693
756
|
const askParent = async () => {
|
|
694
757
|
if (definition.name === cmodPluginName)
|
|
695
758
|
return undefined;
|
|
@@ -968,9 +1031,14 @@ async function createMod(definition, runtime) {
|
|
|
968
1031
|
const conversations = definition.name === cmodPluginName ? serveConversations({ claude, router, store: runtime.store, project: () => modState.root, report: (id, message, error) => report({ id, message, error }), clear: (id) => toaster.toast.dismiss(id) }) : undefined;
|
|
969
1032
|
let pages;
|
|
970
1033
|
const reportPart = (id, message, error) => report({ id, message, error });
|
|
971
|
-
const
|
|
972
|
-
|
|
973
|
-
|
|
1034
|
+
const pressElsewhere = (to, input) => void claude.cmod.call({ to, method: partMethods.press, input }).catch((error) => {
|
|
1035
|
+
parts?.forget(to);
|
|
1036
|
+
claude.ui.invalidate('ui.render');
|
|
1037
|
+
reportPart(`press:${to}`, `${to} did not take the press`, error);
|
|
1038
|
+
});
|
|
1039
|
+
parts = drawnParts({ claude, report: reportPart, press: (to, input) => (pages === undefined ? pressElsewhere(to, input) : pages.press(to, input)) });
|
|
1040
|
+
const widgets = conversations === undefined ? undefined : serveWidgets({ claude, router, store: runtime.store, report: reportPart, turnClosedBy: (rowId) => conversations.turnClosedBy(rowId), parts, markdownIn: area.markdownIn });
|
|
1041
|
+
pages = widgets === undefined ? undefined : servePages({ claude, router, report: reportPart, widgets, parts, tellStylers: area.restyled });
|
|
974
1042
|
router.add('cmod.call', async (e, next) => {
|
|
975
1043
|
if (e.to !== definition.name)
|
|
976
1044
|
return next(e);
|
|
@@ -986,13 +1054,13 @@ async function createMod(definition, runtime) {
|
|
|
986
1054
|
if (ask !== undefined)
|
|
987
1055
|
return { value: (await client.answerBefore(ask.session, ask.event, ask.change, ask.isInProject)) ?? null };
|
|
988
1056
|
if (e.method === partMethods.changed && call.caller !== undefined && pages !== undefined) {
|
|
989
|
-
return { value: { isHeld: pages.update(call.caller, e.input) } };
|
|
1057
|
+
return { value: { isHeld: pages.update(call.caller, e.input), stylers: pages.stylers() } };
|
|
990
1058
|
}
|
|
991
|
-
if (call.caller
|
|
1059
|
+
if (call.caller !== undefined) {
|
|
992
1060
|
const turn = e.method === partMethods.draw ? e.input.turn : undefined;
|
|
993
|
-
const answered = area.answer(e.method, turn === undefined ? e.input : { ...e.input, turn: client.turn(turn) });
|
|
1061
|
+
const answered = area.answer(e.method, turn === undefined ? e.input : { ...e.input, turn: client.turn(turn) }, call.caller);
|
|
994
1062
|
if (answered !== undefined)
|
|
995
|
-
return answered;
|
|
1063
|
+
return answered.catch((error) => ({ deny: `${definition.name}: ${messageOf(error)}` }));
|
|
996
1064
|
}
|
|
997
1065
|
const served = await conversations?.answer(call);
|
|
998
1066
|
return served ?? answerCall(definition.name, api, e);
|
|
@@ -1003,7 +1071,20 @@ async function createMod(definition, runtime) {
|
|
|
1003
1071
|
return ask === undefined ? { deny: `${definition.name}: ${error.message}` } : { value: client.failedBefore(ask.session, ask.event, ask.change, ask.isInProject, error) ?? null };
|
|
1004
1072
|
});
|
|
1005
1073
|
const agents = toolCalls(claude, router);
|
|
1006
|
-
keepEngine(mod, {
|
|
1074
|
+
keepEngine(mod, {
|
|
1075
|
+
claude: checked,
|
|
1076
|
+
router,
|
|
1077
|
+
toolCalls: agents,
|
|
1078
|
+
report,
|
|
1079
|
+
hasPermissionCheck: () => runtime.permissionRoutes() !== undefined,
|
|
1080
|
+
answerInput: area.answerInput,
|
|
1081
|
+
spawnCmod: async (args, program) => {
|
|
1082
|
+
await checkRun(grants, 'process.spawn', program.argv);
|
|
1083
|
+
return spawnCmod(claude, args, session, program.env);
|
|
1084
|
+
},
|
|
1085
|
+
...(widgets === undefined ? {} : { widgets }),
|
|
1086
|
+
...(pages === undefined ? {} : { pages }),
|
|
1087
|
+
});
|
|
1007
1088
|
const names = new Set();
|
|
1008
1089
|
const reserveName = (type, name, taken) => {
|
|
1009
1090
|
const key = `${type}:${name}`;
|
package/runtime/pages.d.ts
CHANGED
|
@@ -11,6 +11,7 @@ export type ModPages = {
|
|
|
11
11
|
export type PageApply = (mod: string, requests: readonly PageRequest[]) => Promise<void>;
|
|
12
12
|
export type Pages = {
|
|
13
13
|
declared(): readonly ModPages[];
|
|
14
|
+
stylers(): readonly string[];
|
|
14
15
|
element(mod: string, page: string, params: unknown, columns: number, isFocused: boolean): RenderElement | undefined;
|
|
15
16
|
press(mod: string, press: PartPress): void;
|
|
16
17
|
update(mod: string, update: PartsUpdate): boolean;
|
|
@@ -18,11 +19,12 @@ export type Pages = {
|
|
|
18
19
|
apply(listener: PageApply): void;
|
|
19
20
|
};
|
|
20
21
|
export declare function pagesOf(input: unknown): PageDeclaration[];
|
|
21
|
-
export declare function servePages({ claude, router, report, widgets, parts }: {
|
|
22
|
+
export declare function servePages({ claude, router, report, widgets, parts, tellStylers, }: {
|
|
22
23
|
readonly claude: Claude;
|
|
23
24
|
readonly router: Router;
|
|
24
25
|
readonly report: (id: string, message: string, error: unknown) => void;
|
|
25
26
|
readonly widgets: Pick<Widgets, 'update'>;
|
|
26
27
|
readonly parts: DrawnParts;
|
|
28
|
+
readonly tellStylers: (stylers: readonly string[]) => void;
|
|
27
29
|
}): Pages;
|
|
28
30
|
//# sourceMappingURL=pages.d.ts.map
|
package/runtime/pages.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pages.d.ts","sourceRoot":"","sources":["../src/runtime/pages.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;
|
|
1
|
+
{"version":3,"file":"pages.d.ts","sourceRoot":"","sources":["../src/runtime/pages.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAIhD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AAEzC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAA;AAC5C,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AACzC,OAAO,EAAe,KAAK,eAAe,EAAE,KAAK,WAAW,EAAE,KAAK,SAAS,EAAE,KAAK,WAAW,EAAE,KAAK,UAAU,EAAE,MAAM,SAAS,CAAA;AAChI,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAA;AAI3C,MAAM,MAAM,QAAQ,GAAG;IAAE,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,SAAS,eAAe,EAAE,CAAA;CAAE,CAAA;AAE3F,MAAM,MAAM,SAAS,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,SAAS,WAAW,EAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;AAExF,MAAM,MAAM,KAAK,GAAG;IAClB,QAAQ,IAAI,SAAS,QAAQ,EAAE,CAAA;IAC/B,OAAO,IAAI,SAAS,MAAM,EAAE,CAAA;IAC5B,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,OAAO,GAAG,aAAa,GAAG,SAAS,CAAA;IACnH,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,GAAG,IAAI,CAAA;IAC1C,MAAM,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,GAAG,OAAO,CAAA;IACjD,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,UAAU,GAAG,IAAI,CAAA;IAC1C,KAAK,CAAC,QAAQ,EAAE,SAAS,GAAG,IAAI,CAAA;CACjC,CAAA;AAED,wBAAgB,OAAO,CAAC,KAAK,EAAE,OAAO,GAAG,eAAe,EAAE,CAGzD;AAED,wBAAgB,UAAU,CAAC,EACzB,MAAM,EACN,MAAM,EACN,MAAM,EACN,OAAO,EACP,KAAK,EACL,WAAW,GACZ,EAAE;IACD,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,KAAK,IAAI,CAAA;IACtE,QAAQ,CAAC,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAA;IACzC,QAAQ,CAAC,KAAK,EAAE,UAAU,CAAA;IAC1B,QAAQ,CAAC,WAAW,EAAE,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,KAAK,IAAI,CAAA;CAC3D,GAAG,KAAK,CAqHR"}
|