@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/pages.js
CHANGED
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
import { isObject } from '../records.js';
|
|
2
|
+
import { Text } from '../ui/elements.js';
|
|
2
3
|
import { messageOf } from '../utils/text.js';
|
|
4
|
+
import { cmodPluginName } from './dependencies.js';
|
|
3
5
|
import { partMethods } from './ui.js';
|
|
4
6
|
const askedWaitMs = 1000;
|
|
5
7
|
export function pagesOf(input) {
|
|
6
8
|
const listed = isObject(input) && Array.isArray(input['pages']) ? input['pages'] : [];
|
|
7
9
|
return listed.filter(isObject).flatMap((each) => (typeof each['id'] === 'string' && typeof each['title'] === 'string' && (each['place'] === 'sidebar' || each['place'] === 'settings') ? [{ id: each['id'], title: each['title'], place: each['place'] }] : []));
|
|
8
10
|
}
|
|
9
|
-
export function servePages({ claude, router, report, widgets, parts }) {
|
|
11
|
+
export function servePages({ claude, router, report, widgets, parts, tellStylers, }) {
|
|
10
12
|
const declared = new Map();
|
|
13
|
+
const stylers = new Set();
|
|
11
14
|
const asking = new Map();
|
|
12
15
|
const pressing = new Set();
|
|
13
16
|
let waiting = [];
|
|
@@ -20,16 +23,18 @@ export function servePages({ claude, router, report, widgets, parts }) {
|
|
|
20
23
|
for (const { mod, requests } of taken)
|
|
21
24
|
await run(mod, requests);
|
|
22
25
|
};
|
|
23
|
-
const settle = async () => {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
+
const settle = async (earlier) => {
|
|
27
|
+
const own = () => [...pressing].filter((pending) => !earlier.has(pending));
|
|
28
|
+
for (let pending = own(); pending.length > 0; pending = own())
|
|
29
|
+
await Promise.allSettled(pending);
|
|
26
30
|
await takeWaiting();
|
|
27
31
|
};
|
|
28
32
|
const wrapped = async (next) => {
|
|
29
33
|
inputDepth += 1;
|
|
34
|
+
const earlier = new Set(pressing);
|
|
30
35
|
try {
|
|
31
36
|
const answer = await next();
|
|
32
|
-
await settle();
|
|
37
|
+
await settle(earlier);
|
|
33
38
|
return answer;
|
|
34
39
|
}
|
|
35
40
|
finally {
|
|
@@ -47,11 +52,27 @@ export function servePages({ claude, router, report, widgets, parts }) {
|
|
|
47
52
|
asking.set(mod, pending);
|
|
48
53
|
return pending;
|
|
49
54
|
};
|
|
55
|
+
const restyle = (mod, isStyler) => {
|
|
56
|
+
if (!isStyler && !stylers.delete(mod))
|
|
57
|
+
return;
|
|
58
|
+
if (isStyler)
|
|
59
|
+
stylers.add(mod);
|
|
60
|
+
parts.forget(mod);
|
|
61
|
+
const listed = [...stylers];
|
|
62
|
+
tellStylers(listed);
|
|
63
|
+
for (const told of declared.keys()) {
|
|
64
|
+
if (told !== cmodPluginName)
|
|
65
|
+
void claude.cmod.call({ to: told, method: partMethods.stylers, input: listed }).catch((error) => claude.ui.log(`cmod: ${told} did not hear which mods style markdown: ${messageOf(error)}`, { to: 'debug' }));
|
|
66
|
+
}
|
|
67
|
+
};
|
|
50
68
|
const update = (mod, { parts: listing, redraw, requests, isAsked }) => {
|
|
51
69
|
if (listing !== undefined)
|
|
52
70
|
declared.set(mod, pagesOf(listing));
|
|
53
|
-
if (listing !== undefined
|
|
54
|
-
|
|
71
|
+
if (listing !== undefined)
|
|
72
|
+
restyle(mod, isObject(listing) && listing['stylesMarkdown'] === true);
|
|
73
|
+
const isRedrawn = redraw === true || requests?.some((request) => 'open' in request) === true;
|
|
74
|
+
if (listing !== undefined || isRedrawn)
|
|
75
|
+
widgets.update(mod, { ...(listing === undefined ? {} : { parts: listing }), ...(isRedrawn ? { redraw: true } : {}) });
|
|
55
76
|
if (requests === undefined || requests.length === 0)
|
|
56
77
|
return false;
|
|
57
78
|
if (isAsked !== true)
|
|
@@ -76,20 +97,23 @@ export function servePages({ claude, router, report, widgets, parts }) {
|
|
|
76
97
|
void pending.then(takeWaiting);
|
|
77
98
|
};
|
|
78
99
|
const cacheKey = (page, params, columns, isFocused) => `${page}\u0000${JSON.stringify(params ?? null)}\u0000${columns}\u0000${isFocused}`;
|
|
100
|
+
const plain = (data) => JSON.parse(JSON.stringify(data));
|
|
79
101
|
return {
|
|
80
102
|
declared: () => [...declared].map(([mod, pages]) => ({ mod, pages })),
|
|
103
|
+
stylers: () => [...stylers],
|
|
81
104
|
element(mod, page, params, columns, isFocused) {
|
|
82
105
|
if (!declared.has(mod))
|
|
83
106
|
void ask(mod);
|
|
84
107
|
const cached = cacheKey(page, params, columns, isFocused);
|
|
85
108
|
const name = `The ${page} page of ${mod}`;
|
|
86
|
-
void parts.fill(mod, cached, { page, params: params ?? null, columns, isFocused }, name)?.then(() => claude.ui.invalidate('ui.render'));
|
|
87
|
-
|
|
109
|
+
void parts.fill(mod, cached, { page, params: plain(params ?? null), columns, isFocused }, name)?.then(() => claude.ui.invalidate('ui.render'));
|
|
110
|
+
const failure = parts.failure(mod, cached);
|
|
111
|
+
return parts.element(mod, cached, `${mod}/${page}:`, name, `page:${mod}/${page}`) ?? (failure === undefined ? undefined : Text({ color: 'error', children: `${name} could not draw: ${failure}` }));
|
|
88
112
|
},
|
|
89
113
|
press,
|
|
90
114
|
update,
|
|
91
115
|
tell(mod, shown) {
|
|
92
|
-
void claude.cmod.call({ to: mod, method: partMethods.shown, input: shown }).catch((error) => claude.ui.log(`cmod: ${mod} did not hear which of its pages show: ${messageOf(error)}`, { to: 'debug' }));
|
|
116
|
+
void claude.cmod.call({ to: mod, method: partMethods.shown, input: plain(shown) }).catch((error) => claude.ui.log(`cmod: ${mod} did not hear which of its pages show: ${messageOf(error)}`, { to: 'debug' }));
|
|
93
117
|
},
|
|
94
118
|
apply(listener) {
|
|
95
119
|
applyTo = listener;
|
package/runtime/parts.d.ts
CHANGED
|
@@ -4,6 +4,7 @@ import { type PartPress } from './ui.js';
|
|
|
4
4
|
export type DrawnParts = {
|
|
5
5
|
fill(mod: string, cached: string, input: Readonly<Record<string, unknown>>, name: string): Promise<void> | undefined;
|
|
6
6
|
element(mod: string, cached: string, keyPrefix: string, name: string, reportId: string): RenderElement | undefined;
|
|
7
|
+
failure(mod: string, cached: string): string | undefined;
|
|
7
8
|
forget(mod: string): void;
|
|
8
9
|
};
|
|
9
10
|
export declare function drawnParts({ claude, report, press }: {
|
package/runtime/parts.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"parts.d.ts","sourceRoot":"","sources":["../src/runtime/parts.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAGhD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AACzC,OAAO,EAA+B,KAAK,SAAS,EAAE,MAAM,SAAS,CAAA;AAErE,MAAM,MAAM,UAAU,GAAG;IACvB,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,GAAG,SAAS,CAAA;IACpH,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,aAAa,GAAG,SAAS,CAAA;IAClH,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAA;CAC1B,CAAA;AAMD,wBAAgB,UAAU,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,EAAE;IAAE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,KAAK,IAAI,CAAA;CAAE,GAAG,UAAU,
|
|
1
|
+
{"version":3,"file":"parts.d.ts","sourceRoot":"","sources":["../src/runtime/parts.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAGhD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AACzC,OAAO,EAA+B,KAAK,SAAS,EAAE,MAAM,SAAS,CAAA;AAErE,MAAM,MAAM,UAAU,GAAG;IACvB,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,GAAG,SAAS,CAAA;IACpH,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,aAAa,GAAG,SAAS,CAAA;IAClH,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAA;IACxD,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAA;CAC1B,CAAA;AAMD,wBAAgB,UAAU,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,EAAE;IAAE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,KAAK,IAAI,CAAA;CAAE,GAAG,UAAU,CAwD9N"}
|
package/runtime/parts.js
CHANGED
|
@@ -14,9 +14,9 @@ export function drawnParts({ claude, report, press }) {
|
|
|
14
14
|
const generation = generationOf(mod);
|
|
15
15
|
if (drawn.get(key)?.generation === generation)
|
|
16
16
|
return undefined;
|
|
17
|
-
const keep = (data) => {
|
|
17
|
+
const keep = (data, failure) => {
|
|
18
18
|
drawn.delete(key);
|
|
19
|
-
drawn.set(key, { data, generation });
|
|
19
|
+
drawn.set(key, { data, ...(failure === undefined ? {} : { failure }), generation });
|
|
20
20
|
const [oldest] = drawn.keys();
|
|
21
21
|
if (drawn.size > keptDrawings && oldest !== undefined)
|
|
22
22
|
drawn.delete(oldest);
|
|
@@ -28,7 +28,7 @@ export function drawnParts({ claude, report, press }) {
|
|
|
28
28
|
.call({ to: mod, method: partMethods.draw, input })
|
|
29
29
|
.then((data) => keep((data ?? null)), (error) => {
|
|
30
30
|
claude.ui.log(`cmod: ${name} drew nothing: ${messageOf(error)}`, { to: 'debug' });
|
|
31
|
-
keep(null);
|
|
31
|
+
keep(drawn.get(key)?.data ?? null, messageOf(error));
|
|
32
32
|
})
|
|
33
33
|
.finally(() => drawing.delete(key));
|
|
34
34
|
drawing.set(key, pending);
|
|
@@ -52,6 +52,9 @@ export function drawnParts({ claude, report, press }) {
|
|
|
52
52
|
return undefined;
|
|
53
53
|
}
|
|
54
54
|
},
|
|
55
|
+
failure(mod, cached) {
|
|
56
|
+
return drawn.get(keyOf(mod, cached))?.failure;
|
|
57
|
+
},
|
|
55
58
|
forget(mod) {
|
|
56
59
|
generations.set(mod, generationOf(mod) + 1);
|
|
57
60
|
},
|
package/runtime/programs.d.ts
CHANGED
|
@@ -10,6 +10,7 @@ export declare function readLines(stream: HookStream<ProcessSpawnChunk, ProcessS
|
|
|
10
10
|
code: number | null;
|
|
11
11
|
lastError: string;
|
|
12
12
|
}>;
|
|
13
|
+
export declare function spawnCmod(claude: Claude, args: readonly string[], session: string, env?: Readonly<Record<string, string>>): HookStream<ProcessSpawnChunk, ProcessSpawnResult>;
|
|
13
14
|
export declare function runCmod(claude: Claude, args: readonly string[], session: string): Promise<string>;
|
|
14
15
|
export declare function locatingPrograms(claude: Claude): Programs;
|
|
15
16
|
//# sourceMappingURL=programs.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"programs.d.ts","sourceRoot":"","sources":["../src/runtime/programs.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAA;AAIpF,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AAEzC,MAAM,MAAM,QAAQ,GAAG;IAAE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IAAC,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;CAAE,CAAA;AAE5E,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAG3D;AAED,wBAAgB,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,CAI7E;AAED,wBAAsB,SAAS,CAAC,MAAM,EAAE,UAAU,CAAC,iBAAiB,EAAE,kBAAkB,CAAC,EAAE,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,GAAG,OAAO,CAAC;IAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,CAAC,CAgB9K;AAED,wBAAsB,OAAO,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,MAAM,EAAE,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAKvG;AAED,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,QAAQ,CAsBzD"}
|
|
1
|
+
{"version":3,"file":"programs.d.ts","sourceRoot":"","sources":["../src/runtime/programs.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAA;AAIpF,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AAEzC,MAAM,MAAM,QAAQ,GAAG;IAAE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IAAC,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;CAAE,CAAA;AAE5E,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAG3D;AAED,wBAAgB,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,CAI7E;AAED,wBAAsB,SAAS,CAAC,MAAM,EAAE,UAAU,CAAC,iBAAiB,EAAE,kBAAkB,CAAC,EAAE,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,GAAG,OAAO,CAAC;IAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,CAAC,CAgB9K;AAED,wBAAgB,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,MAAM,EAAE,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,GAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAM,GAAG,UAAU,CAAC,iBAAiB,EAAE,kBAAkB,CAAC,CAEjL;AAED,wBAAsB,OAAO,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,MAAM,EAAE,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAKvG;AAED,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,QAAQ,CAsBzD"}
|
package/runtime/programs.js
CHANGED
|
@@ -30,9 +30,12 @@ export async function readLines(stream, onLine) {
|
|
|
30
30
|
onLine(pending);
|
|
31
31
|
return { code: piece.value.code, lastError };
|
|
32
32
|
}
|
|
33
|
+
export function spawnCmod(claude, args, session, env = {}) {
|
|
34
|
+
return claude.process.spawn({ argv: ['cmod', ...args], env: { ...env, CLAUDE_CODE_SESSION_ID: session } });
|
|
35
|
+
}
|
|
33
36
|
export async function runCmod(claude, args, session) {
|
|
34
37
|
const lines = [];
|
|
35
|
-
const { code, lastError } = await readLines(claude
|
|
38
|
+
const { code, lastError } = await readLines(spawnCmod(claude, args, session), (line) => void lines.push(line));
|
|
36
39
|
if (code !== 0)
|
|
37
40
|
throw new Error(cmodError(lastError, args[0]) || `cmod ${args.slice(0, 2).join(' ')} ${formatExit(code, '')}.`);
|
|
38
41
|
return lines.join('\n');
|
package/runtime/sessions.d.ts
CHANGED
|
@@ -31,7 +31,7 @@ export declare const conversationMethods: {
|
|
|
31
31
|
readonly changed: 'cmod:changed';
|
|
32
32
|
readonly before: 'cmod:before';
|
|
33
33
|
};
|
|
34
|
-
export type Act = 'send' | 'startTask' | 'callTool' | 'usage' | 'surfaces' | 'abort';
|
|
34
|
+
export type Act = 'send' | 'startTask' | 'fork' | 'clone' | 'startBackgroundTask' | 'callTool' | 'usage' | 'surfaces' | 'abort';
|
|
35
35
|
export declare const beforePermission: (event: BeforeEvent) => string;
|
|
36
36
|
export declare const isAct: (action: string) => action is Act;
|
|
37
37
|
export declare function toolPermission(tool: string): string | undefined;
|
|
@@ -90,7 +90,7 @@ export type RouteOn = {
|
|
|
90
90
|
export declare const recordOf: (value: unknown) => Readonly<Record<string, unknown>>;
|
|
91
91
|
export declare const defined: (fields: HookFields) => HookFields;
|
|
92
92
|
type ProcessFieldsName = Exclude<ProcessName, 'PermissionCheck'>;
|
|
93
|
-
export declare function spawnTask(claude: Pick<Claude, 'agent'>, task: TaskStartArgs): Promise<TaskData>;
|
|
93
|
+
export declare function spawnTask(claude: Pick<Claude, 'agent'>, task: TaskStartArgs, forkedFrom?: string): Promise<TaskData>;
|
|
94
94
|
export declare function processPayloadOf(named: ProcessFieldsName, input: unknown): HookFields;
|
|
95
95
|
export declare function resultOf(where: Pick<Place, 'session' | 'context' | 'turn'>, call: ToolCallArgs & {
|
|
96
96
|
readonly cwd: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sessions.d.ts","sourceRoot":"","sources":["../src/runtime/sessions.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAA;AAC/E,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAA;AAM5D,OAAO,EAAE,UAAU,EAAE,MAAM,gDAAgD,CAAA;AAC3E,OAAO,EAAsB,KAAK,IAAI,EAAE,MAAM,iCAAiC,CAAA;AAC/E,OAAO,
|
|
1
|
+
{"version":3,"file":"sessions.d.ts","sourceRoot":"","sources":["../src/runtime/sessions.ts"],"names":[],"mappings":"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"}
|
package/runtime/sessions.js
CHANGED
|
@@ -5,7 +5,7 @@ import { Person, senderOf } from '../session/context/turn/message/sender.js';
|
|
|
5
5
|
import { ToolCall } from '../session/context/turn/message/tool-call.js';
|
|
6
6
|
import { ToolResult } from '../session/context/turn/message/tool-result.js';
|
|
7
7
|
import { TurnEnd, TurnStart } from '../session/context/turn/turn.js';
|
|
8
|
-
import { searchLimit } from '../session/conversation.js';
|
|
8
|
+
import { forkAgentType, searchLimit } from '../session/conversation.js';
|
|
9
9
|
import { GoalChange } from '../session/goal/goal.js';
|
|
10
10
|
import { TaskEnd, TaskStart } from '../session/goal/task.js';
|
|
11
11
|
import { beforeEvents, processEvents, Session, sessionEvents } from '../session/session.js';
|
|
@@ -32,10 +32,14 @@ export const conversationMethods = {
|
|
|
32
32
|
changed: 'cmod:changed',
|
|
33
33
|
before: 'cmod:before',
|
|
34
34
|
};
|
|
35
|
-
const actPermissions = { send: 'prompt', startTask: 'tasks', usage: 'conversation', surfaces: 'conversation', abort: 'prompt' };
|
|
35
|
+
const actPermissions = { send: 'prompt', startTask: 'tasks', fork: 'tasks', clone: 'tasks', startBackgroundTask: 'tasks', usage: 'conversation', surfaces: 'conversation', abort: 'prompt' };
|
|
36
36
|
const startsTurns = ['CronCreate'];
|
|
37
37
|
const ownSchedule = ['CronDelete', 'CronList'];
|
|
38
38
|
export const beforePermission = (event) => (event === 'toolCall' ? 'tools' : event === TaskStart ? 'tasks' : 'prompt');
|
|
39
|
+
const answerPermissions = (event, answer) => [
|
|
40
|
+
...(Object.keys(answer).some((key) => key !== 'note') ? [beforePermission(event)] : []),
|
|
41
|
+
...(answer.note === undefined ? [] : ['prompt']),
|
|
42
|
+
];
|
|
39
43
|
export const isAct = (action) => action === 'callTool' || action in actPermissions;
|
|
40
44
|
export function toolPermission(tool) {
|
|
41
45
|
return startsTurns.includes(tool) ? 'prompt' : ownSchedule.includes(tool) ? undefined : 'tools';
|
|
@@ -46,7 +50,8 @@ export function actPermission(action, input) {
|
|
|
46
50
|
export async function usageOf(claude) {
|
|
47
51
|
const [model, usage] = await Promise.all([claude.session.model(), claude.session.usage()]);
|
|
48
52
|
const { tokens, window, percent } = usage.context;
|
|
49
|
-
|
|
53
|
+
const rateLimits = usage.rateLimits.map(({ kind, percentUsed, resetsAt }) => ({ type: kind, percent: percentUsed, ...(resetsAt === undefined ? {} : { resetsAt }) }));
|
|
54
|
+
return { model, contextWindow: { ...(tokens === undefined ? {} : { tokenCount: tokens }), size: window, ...(percent === undefined ? {} : { percent }) }, rateLimits, ...(usage.cost === undefined ? {} : { cost: { usd: usage.cost.usd } }) };
|
|
50
55
|
}
|
|
51
56
|
export function payloadOf(change) {
|
|
52
57
|
if (change.type === Message)
|
|
@@ -56,8 +61,9 @@ export function payloadOf(change) {
|
|
|
56
61
|
if ('context' in change)
|
|
57
62
|
return change.context;
|
|
58
63
|
if ('task' in change) {
|
|
59
|
-
const { id, title, agentType, model, openedBy, openedAt, closedAt } = change.task;
|
|
60
|
-
|
|
64
|
+
const { id, title, agentType, model, openedBy, openedAt, closedAt, isBackground, forkedFrom } = change.task;
|
|
65
|
+
const owner = { id, title, ...(agentType === undefined ? {} : { agentType }), ...(model === undefined ? {} : { model }), ...(forkedFrom === undefined ? {} : { forkedFrom }), startedAt: openedAt };
|
|
66
|
+
return { id, title, openedBy, openedAt, ...(closedAt === undefined ? {} : { closedAt }), ...(isBackground === true ? { isBackground } : {}), owner };
|
|
61
67
|
}
|
|
62
68
|
if ('usage' in change)
|
|
63
69
|
return change.usage;
|
|
@@ -195,9 +201,12 @@ export function createSessionsClient({ name, claude, report, isGranted, hookOn,
|
|
|
195
201
|
return undefined;
|
|
196
202
|
const payload = await payloadFor(session, latest);
|
|
197
203
|
const answer = await invoke(entry.method, entry.options, async (time) => takenAnswer(entry.method, beforeAnswerKeys[event], await entry.listener(payload, time)), beforeAnswerKeys[event]);
|
|
198
|
-
if (answer === undefined || answer === null || (typeof answer === 'object' && 'refuse' in answer)
|
|
204
|
+
if (answer === undefined || answer === null || (typeof answer === 'object' && 'refuse' in answer))
|
|
199
205
|
return answer;
|
|
200
|
-
|
|
206
|
+
const missing = answerPermissions(event, answer).find((permission) => !isGranted(permission));
|
|
207
|
+
if (missing === undefined)
|
|
208
|
+
return answer;
|
|
209
|
+
throw new Error(`Its ${entry.method} answer changes nothing until you grant ${name} "${permissionWords(missing)}". ${grantFix}`);
|
|
201
210
|
});
|
|
202
211
|
const answer = await foldBefore(event, change, askers, (error) => report(`session:${methodOf('before', event)}`, `The ${methodOf('before', event)} listener failed`, error));
|
|
203
212
|
return answer?.note === undefined ? answer : { ...answer, note: modNote(name, answer.note) };
|
|
@@ -262,17 +271,25 @@ export function createSessionsClient({ name, claude, report, isGranted, hookOn,
|
|
|
262
271
|
turns: async () => (await call(conversationMethods.turns, { ...owned, context: data.number })).map((turn) => turnOf(owned, turn)),
|
|
263
272
|
};
|
|
264
273
|
};
|
|
265
|
-
const taskOf = (session, data) =>
|
|
266
|
-
id: data.id
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
274
|
+
const taskOf = (session, data) => {
|
|
275
|
+
const record = data.isBackground === true ? { session: data.id } : { session, task: data.id };
|
|
276
|
+
return {
|
|
277
|
+
id: data.id,
|
|
278
|
+
title: data.title,
|
|
279
|
+
openedBy: data.openedBy,
|
|
280
|
+
openedAt: data.openedAt,
|
|
281
|
+
...(data.closedAt === undefined ? {} : { closedAt: data.closedAt }),
|
|
282
|
+
...(data.status === undefined ? {} : { status: data.status }),
|
|
283
|
+
...(data.name === undefined ? {} : { name: data.name }),
|
|
284
|
+
...(data.spawnedBy === undefined ? {} : { spawnedBy: data.spawnedBy }),
|
|
285
|
+
...(data.isBackground === true ? { isBackground: true } : {}),
|
|
286
|
+
owner: ownerOf(session, data),
|
|
287
|
+
...recordPart({ session, task: data.id }),
|
|
288
|
+
search: (text, matcher) => searched({ text, ...record }, matcher),
|
|
289
|
+
tasks: async () => (await call(conversationMethods.tasks, record)).map((task) => taskOf(record.session, task)),
|
|
290
|
+
messages: () => messagesFrom(conversationMethods.messages, record),
|
|
291
|
+
};
|
|
292
|
+
};
|
|
276
293
|
const goalOf = (where) => ({
|
|
277
294
|
search: (text, matcher) => searched({ text, ...where() }, matcher),
|
|
278
295
|
...eventMethods(() => ({ ...where(), isGoal: true }), true),
|
|
@@ -317,9 +334,8 @@ export function createSessionsClient({ name, claude, report, isGranted, hookOn,
|
|
|
317
334
|
if ('drop' in submitted && typeof submitted.drop === 'string')
|
|
318
335
|
throw new Error(`${name}: Claude Code dropped the prompt: ${submitted.drop}`);
|
|
319
336
|
};
|
|
320
|
-
const startHere = async (task) => taskOf(current.id, await spawnTask(claude, task).catch((error) => Promise.reject(new Error(`${name}: ${messageOf(error)}`))));
|
|
321
337
|
const surfacesHere = async () => [...(await claude.session.surfaces())];
|
|
322
|
-
const sessionOf = (read, owned) => {
|
|
338
|
+
const sessionOf = (read, owned, isOwner = owned !== undefined) => {
|
|
323
339
|
const id = () => read().id;
|
|
324
340
|
const isHere = () => owned === undefined && id() === current.id;
|
|
325
341
|
const scope = () => (owned === undefined ? { session: id(), isAgent: true } : { session: owned.session, task: owned.task, isAgent: true });
|
|
@@ -384,31 +400,31 @@ export function createSessionsClient({ name, claude, report, isGranted, hookOn,
|
|
|
384
400
|
await act(id(), 'send', message, owned);
|
|
385
401
|
},
|
|
386
402
|
};
|
|
387
|
-
if (
|
|
403
|
+
if (isOwner)
|
|
388
404
|
return session;
|
|
405
|
+
const started = async (starting) => taskOf(id(), await starting.catch((error) => Promise.reject(new Error(`${name}: ${messageOf(error)}`))));
|
|
389
406
|
return Object.assign(session, {
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
const started = await act(id(), 'startTask', task);
|
|
394
|
-
return taskOf(id(), started);
|
|
395
|
-
},
|
|
407
|
+
startTask: (task) => started(task.isBackground === true ? act(id(), 'startBackgroundTask', task) : isHere() ? spawnTask(claude, task) : act(id(), 'startTask', task)),
|
|
408
|
+
fork: (task) => started(isHere() ? spawnTask(claude, task, current.id) : act(id(), 'fork', task)),
|
|
409
|
+
clone: (task) => started(act(id(), 'clone', task)),
|
|
396
410
|
callTool: async (toolCall) => (await withFiles(await act(id(), 'callTool', toolCall))),
|
|
397
411
|
});
|
|
398
412
|
};
|
|
399
413
|
const ownerOf = (session, data) => {
|
|
400
414
|
const parent = session === current.id ? current : known.get(session);
|
|
415
|
+
const isBackground = data.isBackground === true;
|
|
401
416
|
return sessionOf(() => ({
|
|
402
417
|
id: data.id,
|
|
403
418
|
title: data.title,
|
|
404
419
|
project: parent?.project ?? '',
|
|
405
|
-
agentType: data.agentType,
|
|
420
|
+
...(data.agentType === undefined ? {} : { agentType: data.agentType }),
|
|
406
421
|
model: data.model ?? '',
|
|
407
422
|
cwd: parent?.cwd ?? '',
|
|
408
|
-
isRunning: data.closedAt === undefined && (parent?.isRunning ?? true),
|
|
423
|
+
isRunning: data.closedAt === undefined && (isBackground || (parent?.isRunning ?? true)),
|
|
409
424
|
startedAt: data.openedAt,
|
|
410
425
|
lastAt: data.closedAt ?? data.openedAt,
|
|
411
|
-
|
|
426
|
+
...(data.forkedFrom === undefined ? {} : { forkedFrom: data.forkedFrom }),
|
|
427
|
+
}), isBackground ? undefined : { session, task: data.id }, true);
|
|
412
428
|
};
|
|
413
429
|
const processListeners = new Map();
|
|
414
430
|
const processHearers = (method, payload) => [...(processListeners.get(method) ?? [])].filter(({ matcher, isEveryAgent }) => (payload['task'] === undefined || isEveryAgent) && (matcher === undefined || matches(payload, matcher)));
|
|
@@ -464,7 +480,12 @@ export function createSessionsClient({ name, claude, report, isGranted, hookOn,
|
|
|
464
480
|
const event = processEvents[named];
|
|
465
481
|
const hook = (isBefore ? event.before : undefined) ?? event.on;
|
|
466
482
|
if (method === 'beforeSessionStart')
|
|
467
|
-
routeOn('session.compact',
|
|
483
|
+
routeOn('session.compact', trackCompaction);
|
|
484
|
+
if (method === 'beforeModelChange')
|
|
485
|
+
hookOn(event.on, () => {
|
|
486
|
+
const notes = modelNotes.splice(0);
|
|
487
|
+
return notes.length === 0 ? undefined : { additionalContext: notes };
|
|
488
|
+
});
|
|
468
489
|
const failed = (input, error) => {
|
|
469
490
|
const refusal = refusalFor(method, processPayloadOf(named, input), error);
|
|
470
491
|
return refusal === undefined ? undefined : hookAnswerOf(name, named, refusal);
|
|
@@ -472,34 +493,40 @@ export function createSessionsClient({ name, claude, report, isGranted, hookOn,
|
|
|
472
493
|
return hookOn(hook, async (input) => {
|
|
473
494
|
const payload = processPayloadOf(named, input);
|
|
474
495
|
if (named === 'SessionStart' && payload['source'] === 'compact') {
|
|
475
|
-
|
|
476
|
-
|
|
496
|
+
if (!isBefore)
|
|
497
|
+
return undefined;
|
|
498
|
+
const task = compacting?.agentId;
|
|
499
|
+
const model = task === undefined ? await claude.session.model() : undefined;
|
|
500
|
+
const answer = await raise(method, { ...payload, ...defined({ task, model, title: current.title || undefined }) });
|
|
501
|
+
if (answer === undefined)
|
|
502
|
+
return undefined;
|
|
503
|
+
return hookAnswerOf(name, named, task === undefined ? answer : defined({ note: answer['note'] }));
|
|
477
504
|
}
|
|
478
505
|
if (!isBefore) {
|
|
479
506
|
void raise(method, payload);
|
|
480
507
|
return undefined;
|
|
481
508
|
}
|
|
509
|
+
if (named === 'ModelChange')
|
|
510
|
+
modelNotes.splice(0);
|
|
482
511
|
const answer = await raise(method, payload);
|
|
483
|
-
|
|
512
|
+
if (answer === undefined)
|
|
513
|
+
return undefined;
|
|
514
|
+
if (named === 'ModelChange' && typeof answer['note'] === 'string' && answer['refuse'] === undefined)
|
|
515
|
+
modelNotes.push(modNote(name, answer['note']));
|
|
516
|
+
return hookAnswerOf(name, named, answer);
|
|
484
517
|
}, isBefore ? failed : undefined);
|
|
485
518
|
};
|
|
486
|
-
const
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
if (typeof note !== 'string')
|
|
497
|
-
return compacted;
|
|
498
|
-
if (!isGranted('prompt')) {
|
|
499
|
-
report('session:beforeSessionStart', 'The beforeSessionStart listener failed', new Error(`${name}: its note adds text Claude reads, which needs the prompt permission.`));
|
|
500
|
-
return compacted;
|
|
519
|
+
const modelNotes = [];
|
|
520
|
+
let compacting;
|
|
521
|
+
const trackCompaction = async (e, next) => {
|
|
522
|
+
compacting = e;
|
|
523
|
+
try {
|
|
524
|
+
return await next(e);
|
|
525
|
+
}
|
|
526
|
+
finally {
|
|
527
|
+
if (compacting === e)
|
|
528
|
+
compacting = undefined;
|
|
501
529
|
}
|
|
502
|
-
return { ...compacted, messages: [...compacted.messages, { role: 'user', text: modNote(name, note), toolUses: [] }] };
|
|
503
530
|
};
|
|
504
531
|
const listen = (method, args, isEveryAgent = false) => {
|
|
505
532
|
const heard = listenerOf(name, method, args);
|
|
@@ -604,9 +631,9 @@ const modelChangeOf = (input) => ({
|
|
|
604
631
|
});
|
|
605
632
|
const answerKeys = {
|
|
606
633
|
Run: ['args', 'note', 'result', 'exitCode', 'refuse'],
|
|
607
|
-
ProjectSetup: [],
|
|
634
|
+
ProjectSetup: ['note'],
|
|
608
635
|
SessionStart: ['note', 'title', 'reloadSkills', 'firstPrompt'],
|
|
609
|
-
ModelChange: ['ask', 'refuse'],
|
|
636
|
+
ModelChange: ['ask', 'refuse', 'note'],
|
|
610
637
|
WorktreeCreate: ['result'],
|
|
611
638
|
Elicitation: ['refuse'],
|
|
612
639
|
ElicitationResponse: ['refuse'],
|
|
@@ -651,22 +678,23 @@ const processFields = {
|
|
|
651
678
|
TodoComplete: todoOf,
|
|
652
679
|
ReplyDisplay: (input) => ({ replyId: input['message_id'], index: input['index'], isLast: input['final'], text: input['delta'] }),
|
|
653
680
|
};
|
|
654
|
-
export async function spawnTask(claude, task) {
|
|
681
|
+
export async function spawnTask(claude, task, forkedFrom) {
|
|
655
682
|
if (task.title.trim() === '')
|
|
656
683
|
throw new Error('a task needs a title.');
|
|
684
|
+
const { name, agentType, model, cwd } = forkedFrom === undefined ? task : { prompt: task.prompt, title: task.title, agentType: forkAgentType };
|
|
657
685
|
const started = await claude.agent.spawn({
|
|
658
686
|
prompt: task.prompt,
|
|
659
687
|
description: task.title,
|
|
660
|
-
...(
|
|
661
|
-
...(
|
|
662
|
-
...(
|
|
663
|
-
...(
|
|
688
|
+
...(name === undefined ? {} : { name }),
|
|
689
|
+
...(agentType === undefined ? {} : { subagentType: agentType }),
|
|
690
|
+
...(model === undefined ? {} : { model }),
|
|
691
|
+
...(cwd === undefined ? {} : { cwd }),
|
|
664
692
|
});
|
|
665
693
|
if (started.deny !== undefined)
|
|
666
694
|
throw new Error(`Claude Code refused the task: ${started.deny}`);
|
|
667
695
|
if (started.agentId === undefined)
|
|
668
696
|
throw new Error('Claude Code answered without starting a task.');
|
|
669
|
-
return { id: started.agentId, title: task.title, agentType:
|
|
697
|
+
return { id: started.agentId, title: task.title, ...(forkedFrom === undefined ? { agentType: agentType ?? 'general-purpose' } : { forkedFrom }), model: started.model, openedBy: '', openedAt: new Date().toISOString() };
|
|
670
698
|
}
|
|
671
699
|
export function processPayloadOf(named, input) {
|
|
672
700
|
const fields = recordOf(input);
|
|
@@ -682,6 +710,8 @@ function hookAnswerOf(name, named, answer) {
|
|
|
682
710
|
return { permissionDecision: 'deny', permissionDecisionReason: refuse };
|
|
683
711
|
return typeof answer['ask'] === 'string' ? { permissionDecision: 'ask', permissionDecisionReason: answer['ask'] } : undefined;
|
|
684
712
|
}
|
|
713
|
+
if (named === 'ProjectSetup')
|
|
714
|
+
return defined({ additionalContext: note });
|
|
685
715
|
if (named === 'WorktreeCreate')
|
|
686
716
|
return defined({ worktreePath: answer['result'] });
|
|
687
717
|
if (named === 'PermissionDenial')
|
package/runtime/toaster.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"toaster.d.ts","sourceRoot":"","sources":["../src/runtime/toaster.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,aAAa,EAAE,aAAa,EAAS,MAAM,aAAa,CAAA;AACpF,OAAO,KAAK,EAAE,KAAK,EAAE,OAAO,EAAgB,SAAS,EAAE,MAAM,WAAW,CAAA;AAIxE,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AAEzC,MAAM,MAAM,UAAU,GAAG;IAAE,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAA;CAAE,CAAA;AAE9G,MAAM,MAAM,OAAO,GAAG;IAAE,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,OAAO,CAAA;CAAE,CAAA;AAEhK,MAAM,MAAM,OAAO,GAAG;IACpB,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAA;IACrB,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IACjC,IAAI,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAA;IAC/B,QAAQ,CAAC,KAAK,EAAE,SAAS,UAAU,EAAE,CAAA;IACrC,IAAI,CAAC,KAAK,EAAE,aAAa,EAAE,KAAK,EAAE,YAAY,EAAE,IAAI,EAAE,aAAa,CAAC,aAAa,CAAC,EAAE,MAAM,EAAE,MAAM,GAAG,aAAa,CAAA;CACnH,CAAA;
|
|
1
|
+
{"version":3,"file":"toaster.d.ts","sourceRoot":"","sources":["../src/runtime/toaster.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,aAAa,EAAE,aAAa,EAAS,MAAM,aAAa,CAAA;AACpF,OAAO,KAAK,EAAE,KAAK,EAAE,OAAO,EAAgB,SAAS,EAAE,MAAM,WAAW,CAAA;AAIxE,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AAEzC,MAAM,MAAM,UAAU,GAAG;IAAE,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAA;CAAE,CAAA;AAE9G,MAAM,MAAM,OAAO,GAAG;IAAE,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,OAAO,CAAA;CAAE,CAAA;AAEhK,MAAM,MAAM,OAAO,GAAG;IACpB,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAA;IACrB,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IACjC,IAAI,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAA;IAC/B,QAAQ,CAAC,KAAK,EAAE,SAAS,UAAU,EAAE,CAAA;IACrC,IAAI,CAAC,KAAK,EAAE,aAAa,EAAE,KAAK,EAAE,YAAY,EAAE,IAAI,EAAE,aAAa,CAAC,aAAa,CAAC,EAAE,MAAM,EAAE,MAAM,GAAG,aAAa,CAAA;CACnH,CAAA;AA2CD,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,MAAM,GAAG,SAAS,EAAE,eAAe,EAAE,MAAM,OAAO,GAAG,OAAO,CA+QtI"}
|
package/runtime/toaster.js
CHANGED
|
@@ -8,6 +8,8 @@ const defaultDuration = 4000;
|
|
|
8
8
|
const visibleToasts = 3;
|
|
9
9
|
const spinner = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
|
10
10
|
const spinnerMs = 100;
|
|
11
|
+
const spinFrames = 10_000 / spinnerMs;
|
|
12
|
+
const still = '…';
|
|
11
13
|
const icons = {
|
|
12
14
|
default: { glyph: ' ' },
|
|
13
15
|
success: { glyph: '✓', color: 'success' },
|
|
@@ -31,15 +33,19 @@ export function createToaster(claude, name, version, openPermissions) {
|
|
|
31
33
|
let isHeld = false;
|
|
32
34
|
let showsAll = false;
|
|
33
35
|
let bandId;
|
|
36
|
+
const isLoading = (entry) => entry.type === 'loading' || entry.trying !== undefined;
|
|
37
|
+
const isSpinning = (entry) => entry.spinsFrom !== undefined && frame - entry.spinsFrom < spinFrames;
|
|
34
38
|
const changed = () => {
|
|
35
|
-
const
|
|
36
|
-
|
|
39
|
+
for (const entry of entries.values())
|
|
40
|
+
entry.spinsFrom = isLoading(entry) ? (entry.spinsFrom ?? frame) : undefined;
|
|
41
|
+
const spins = [...entries.values()].some(isSpinning);
|
|
42
|
+
if (spins && spinning === undefined) {
|
|
37
43
|
spinning = claude.clock.every(spinnerMs, () => {
|
|
38
44
|
frame += 1;
|
|
39
|
-
|
|
45
|
+
changed();
|
|
40
46
|
});
|
|
41
47
|
}
|
|
42
|
-
if (!
|
|
48
|
+
if (!spins && spinning !== undefined) {
|
|
43
49
|
spinning.cancel();
|
|
44
50
|
spinning = undefined;
|
|
45
51
|
}
|
|
@@ -83,7 +89,7 @@ export function createToaster(claude, name, version, openPermissions) {
|
|
|
83
89
|
if (previous !== undefined)
|
|
84
90
|
disarm(previous);
|
|
85
91
|
const reporting = hides !== undefined && previous?.hides === hides ? previous.reporting : undefined;
|
|
86
|
-
const entry = { id, type, message, options, actions, ...(hides === undefined ? {} : { hides }), isOpen: previous?.isOpen ?? false, reporting, timer: undefined };
|
|
92
|
+
const entry = { id, type, message, options, actions, ...(hides === undefined ? {} : { hides }), at: new Date(), isOpen: previous?.isOpen ?? false, reporting, timer: undefined };
|
|
87
93
|
if (type === 'error' && previous?.message !== message)
|
|
88
94
|
claude.ui.log(`${name}: ${message}${typeof options.description === 'string' ? `: ${options.description}` : ''}`, { to: 'debug' });
|
|
89
95
|
entries.set(id, entry);
|
|
@@ -134,15 +140,20 @@ export function createToaster(claude, name, version, openPermissions) {
|
|
|
134
140
|
return id;
|
|
135
141
|
hidden.delete(id);
|
|
136
142
|
const shown = entries.get(id);
|
|
137
|
-
if (shown?.hides === hides && shown.trying === undefined && shown.options.description === description)
|
|
143
|
+
if (shown?.hides === hides && shown.trying === undefined && shown.options.description === description) {
|
|
144
|
+
const previous = clockTime(shown.at);
|
|
145
|
+
shown.at = new Date();
|
|
146
|
+
if (clockTime(shown.at) !== previous)
|
|
147
|
+
changed();
|
|
138
148
|
return id;
|
|
149
|
+
}
|
|
139
150
|
const stack = error instanceof Error && error.stack !== undefined ? `\n\n${error.stack}` : '';
|
|
140
151
|
const details = async () => {
|
|
141
152
|
const modVersion = await claude.fs
|
|
142
153
|
.read(`${claude.plugin.root}/.claude-plugin/plugin.json`)
|
|
143
154
|
.then((text) => String(JSON.parse(text).version))
|
|
144
155
|
.catch((failure) => `unknown (${messageOf(failure)})`);
|
|
145
|
-
return `${name} ${modVersion}, @cmodjs/core ${coreVersion}\n${message}${description === undefined ? '' : `\n${description}`}${stack}`;
|
|
156
|
+
return `${name} ${modVersion}, @cmodjs/core ${coreVersion}, at ${(entries.get(id)?.at ?? new Date()).toISOString()}\n${message}${description === undefined ? '' : `\n${description}`}${stack}`;
|
|
146
157
|
};
|
|
147
158
|
const startReporting = async () => {
|
|
148
159
|
const shown = entries.get(id);
|
|
@@ -190,13 +201,13 @@ export function createToaster(claude, name, version, openPermissions) {
|
|
|
190
201
|
const row = (entry) => {
|
|
191
202
|
const { description } = entry.options;
|
|
192
203
|
const isText = typeof description === 'string';
|
|
193
|
-
const icon = entry.type === 'loading' || entry.trying !== undefined ? { glyph: spinner[frame % spinner.length], color: 'claude' } : icons[entry.type];
|
|
204
|
+
const icon = entry.type === 'loading' || entry.trying !== undefined ? { glyph: isSpinning(entry) ? spinner[frame % spinner.length] : still, color: 'claude' } : icons[entry.type];
|
|
194
205
|
const message = isText ? Button({ plain: true, key: `toast:${entry.id}:open`, label: entry.message, onPress: toggle(entry) }) : Text({ children: entry.message });
|
|
195
206
|
const details = !isText ? description : Text({ dimColor: true, wrap: entry.isOpen ? 'wrap' : 'truncate-end', children: entry.isOpen ? description : firstLine(description) });
|
|
196
207
|
const fixed = (child) => Box({ flexShrink: 0, children: child });
|
|
197
208
|
const close = Button({ plain: true, dimColor: true, key: `toast:${entry.id}:close`, label: entry.hides === undefined ? '✕' : 'Hide', onPress: () => hide(entry) });
|
|
198
209
|
const actions = entry.actions.map((action, index) => Button({ plain: true, key: `toast:${entry.id}:${index === 0 ? 'action' : `action-${index}`}`, label: action.label, onPress: press(entry, action.onClick) }));
|
|
199
|
-
const buttons = Box({ gap: 2, flexWrap: 'wrap', children: [...actions, close] });
|
|
210
|
+
const buttons = Box({ gap: 2, flexWrap: 'wrap', children: [...actions, close, Text({ dimColor: true, children: clockTime(entry.at) })].map(fixed) });
|
|
200
211
|
const trying = entry.trying === undefined ? undefined : Box({ gap: 1, children: [Text({ dimColor: true, children: 'Trying again:' }), typeof entry.trying === 'string' ? Text({ dimColor: true, wrap: 'truncate-end', children: entry.trying }) : entry.trying] });
|
|
201
212
|
const text = details === undefined ? [message] : [message, trying ?? details];
|
|
202
213
|
const under = trying !== undefined && details === undefined ? [trying] : entry.reporting === undefined ? [buttons] : reportChoices(entry, entry.reporting);
|
|
@@ -316,3 +327,6 @@ function reportPrompt(name, contact, details) {
|
|
|
316
327
|
: `Email it to ${contact.address}.`;
|
|
317
328
|
return `The ${name} mod failed. Report it to its author. ${where} First check whether it is reported already, and add to that report instead of filing a new one. Show me the report before you post it.\n\n${details}`;
|
|
318
329
|
}
|
|
330
|
+
function clockTime(at) {
|
|
331
|
+
return `${String(at.getHours()).padStart(2, '0')}:${String(at.getMinutes()).padStart(2, '0')}`;
|
|
332
|
+
}
|