@cmodjs/core 1.0.0 → 1.5.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 +22 -11
- package/docs/dependencies.md +6 -4
- package/docs/hooks.md +26 -3
- package/docs/index.md +1 -1
- package/docs/install-steps.md +5 -3
- package/docs/mod.md +29 -3
- package/docs/permissions.md +4 -4
- package/docs/session.md +126 -63
- package/docs/testing.md +7 -5
- package/docs/ui.md +9 -1
- package/jobs/program.d.ts.map +1 -1
- package/jobs/program.js +7 -2
- package/mod.d.ts +30 -6
- package/mod.d.ts.map +1 -1
- package/mod.js +34 -1
- package/package.json +1 -1
- package/records.d.ts +4 -1
- package/records.d.ts.map +1 -1
- package/records.js +6 -1
- package/register.d.ts.map +1 -1
- package/register.js +17 -0
- package/runtime/announce.d.ts.map +1 -1
- package/runtime/announce.js +2 -0
- package/runtime/before.d.ts +32 -0
- package/runtime/before.d.ts.map +1 -0
- package/runtime/before.js +214 -0
- package/runtime/claude.d.ts +2 -1
- package/runtime/claude.d.ts.map +1 -1
- package/runtime/conversations.d.ts +1 -0
- package/runtime/conversations.d.ts.map +1 -1
- package/runtime/conversations.js +333 -61
- package/runtime/dependencies.js +1 -1
- package/runtime/engine.d.ts +2 -6
- package/runtime/engine.d.ts.map +1 -1
- package/runtime/grants.d.ts.map +1 -1
- package/runtime/grants.js +8 -8
- package/runtime/hooks.d.ts +1 -1
- package/runtime/hooks.d.ts.map +1 -1
- package/runtime/hooks.js +19 -4
- package/runtime/installer.d.ts +1 -1
- package/runtime/installer.d.ts.map +1 -1
- package/runtime/installer.js +6 -5
- package/runtime/lifecycle.d.ts.map +1 -1
- package/runtime/lifecycle.js +129 -58
- package/runtime/metadata.d.ts.map +1 -1
- package/runtime/metadata.js +5 -4
- package/runtime/sessions.d.ts +26 -4
- package/runtime/sessions.d.ts.map +1 -1
- package/runtime/sessions.js +315 -117
- package/runtime/toaster.d.ts +5 -12
- package/runtime/toaster.d.ts.map +1 -1
- package/runtime/toaster.js +133 -23
- package/runtime/ui.d.ts.map +1 -1
- package/runtime/ui.js +26 -6
- package/session/context/context.d.ts +3 -1
- package/session/context/context.d.ts.map +1 -1
- package/session/context/turn/message/message.d.ts +1 -0
- package/session/context/turn/message/message.d.ts.map +1 -1
- package/session/context/turn/message/message.js +42 -1
- package/session/context/turn/turn.d.ts +2 -1
- package/session/context/turn/turn.d.ts.map +1 -1
- package/session/conversation.d.ts +20 -11
- package/session/conversation.d.ts.map +1 -1
- package/session/conversation.js +50 -27
- package/session/goal/goal.d.ts +3 -2
- package/session/goal/goal.d.ts.map +1 -1
- package/session/goal/task.d.ts +4 -4
- package/session/goal/task.d.ts.map +1 -1
- package/session/session.d.ts +80 -21
- package/session/session.d.ts.map +1 -1
- package/session/session.js +1 -0
- package/src/jobs/program.ts +7 -2
- package/src/mod.ts +48 -5
- package/src/records.ts +6 -1
- package/src/register.ts +17 -0
- package/src/runtime/announce.ts +2 -0
- package/src/runtime/before.ts +200 -0
- package/src/runtime/claude.ts +3 -0
- package/src/runtime/conversations.ts +337 -60
- package/src/runtime/dependencies.ts +1 -1
- package/src/runtime/engine.ts +2 -1
- package/src/runtime/grants.ts +8 -10
- package/src/runtime/hooks.ts +20 -4
- package/src/runtime/installer.ts +6 -5
- package/src/runtime/lifecycle.ts +135 -55
- package/src/runtime/metadata.ts +5 -4
- package/src/runtime/sessions.ts +316 -111
- package/src/runtime/toaster.ts +160 -44
- package/src/runtime/ui.ts +23 -4
- package/src/session/context/context.ts +3 -1
- package/src/session/context/turn/message/message.ts +39 -1
- package/src/session/context/turn/turn.ts +2 -1
- package/src/session/conversation.ts +56 -31
- package/src/session/goal/goal.ts +3 -2
- package/src/session/goal/task.ts +4 -4
- package/src/session/session.ts +62 -15
- package/src/testing/fake-claude.ts +4 -2
- package/src/testing/fake-sessions.ts +94 -20
- package/src/testing.ts +27 -3
- package/src/utils/call-effects.ts +2 -2
- package/src/utils/disposable.ts +3 -0
- package/src/utils/matcher.ts +1 -1
- package/src/utils/paths.ts +6 -3
- package/testing/fake-claude.d.ts +1 -0
- package/testing/fake-claude.d.ts.map +1 -1
- package/testing/fake-claude.js +3 -1
- package/testing/fake-sessions.d.ts +8 -1
- package/testing/fake-sessions.d.ts.map +1 -1
- package/testing/fake-sessions.js +97 -18
- package/testing.d.ts +1 -0
- package/testing.d.ts.map +1 -1
- package/testing.js +23 -2
- package/utils/call-effects.d.ts +1 -0
- package/utils/call-effects.d.ts.map +1 -1
- package/utils/call-effects.js +1 -1
- package/utils/disposable.d.ts +4 -0
- package/utils/disposable.d.ts.map +1 -0
- package/utils/disposable.js +1 -0
- package/utils/matcher.js +1 -1
- package/utils/paths.d.ts +2 -1
- package/utils/paths.d.ts.map +1 -1
- package/utils/paths.js +6 -3
package/runtime/conversations.js
CHANGED
|
@@ -1,21 +1,33 @@
|
|
|
1
1
|
import { consentPath, dataFolder, isObject, parseConsent } from '../records.js';
|
|
2
2
|
import { Message, rowOfAppend } from '../session/context/turn/message/message.js';
|
|
3
3
|
import { Note } from '../session/context/turn/message/note.js';
|
|
4
|
-
import { Prompt } from '../session/context/turn/message/prompt.js';
|
|
5
4
|
import { Person } from '../session/context/turn/message/sender.js';
|
|
6
5
|
import { createConversation, emptyConversation } from '../session/conversation.js';
|
|
6
|
+
import { Session, UsageChange } from '../session/session.js';
|
|
7
|
+
import { GoalChange } from '../session/goal/goal.js';
|
|
7
8
|
import { receivedMatcher } from '../utils/matcher.js';
|
|
8
9
|
import { ifMissing } from '../utils/paths.js';
|
|
9
10
|
import { formatExit, messageOf } from '../utils/text.js';
|
|
11
|
+
import { foldAnswers, installBefore, relayOf } from './before.js';
|
|
10
12
|
import { cmodPluginName } from './dependencies.js';
|
|
11
13
|
import { readLines, runCmod } from './programs.js';
|
|
12
|
-
import { conversationMethods, isHeardBy } from './sessions.js';
|
|
13
|
-
const delivery = /^<cmod-delivery>([\s\S]*)<\/cmod-delivery>$/;
|
|
14
|
+
import { actPermission, beforePermission, conversationMethods, everySession, isAct, isHeardBy, resultOf, usageOf } from './sessions.js';
|
|
14
15
|
const bufferedTaskRows = 200;
|
|
16
|
+
const beforeDeadlineMs = 5000;
|
|
17
|
+
const livenessMs = 5000;
|
|
15
18
|
export function serveConversations({ claude, router, store, project, report }) {
|
|
16
|
-
const
|
|
19
|
+
const folder = dataFolder(store, cmodPluginName);
|
|
20
|
+
const goals = `${folder}/goals`;
|
|
21
|
+
const watches = `${folder}/before`;
|
|
17
22
|
const heard = new Map();
|
|
23
|
+
const asked = new Map();
|
|
24
|
+
const ownWatches = new Map();
|
|
25
|
+
let watchers = [];
|
|
26
|
+
let watching = Promise.resolve();
|
|
27
|
+
let published = new Set();
|
|
28
|
+
let publishing = Promise.resolve();
|
|
18
29
|
let session = '';
|
|
30
|
+
let asks = 0;
|
|
19
31
|
let conversation = createConversation(emptyConversation(''));
|
|
20
32
|
let starting = Promise.resolve();
|
|
21
33
|
let live = [];
|
|
@@ -32,37 +44,42 @@ export function serveConversations({ claude, router, store, project, report }) {
|
|
|
32
44
|
const text = await claude.fs.read(path).catch(ifMissing('{}'));
|
|
33
45
|
return (parseConsent(JSON.parse(text), path)[caller] ?? []).includes(item);
|
|
34
46
|
};
|
|
35
|
-
const
|
|
47
|
+
const ended = (id) => new Error(`session ${id} has ended`);
|
|
48
|
+
const isRunning = async (id) => (await json(['history', 'sessions', '--running', '--all'])).some((each) => each.id === id);
|
|
49
|
+
const dispatch = (to, changes, isInProject = true) => {
|
|
36
50
|
for (const change of changes) {
|
|
37
51
|
if (change.type === Message && !liveIds.has(change.message.id) && to === session) {
|
|
38
52
|
live.push(change.message);
|
|
39
53
|
liveIds.add(change.message.id);
|
|
40
54
|
}
|
|
41
55
|
for (const [mod, sessions] of heard) {
|
|
42
|
-
const subscriptions = sessions.get(to) ?? [];
|
|
56
|
+
const subscriptions = [...(sessions.get(to) ?? []), ...(isInProject ? (sessions.get(everySession) ?? []) : [])];
|
|
43
57
|
if (!subscriptions.some(({ event, matcher }) => isHeardBy(event, matcher, change)))
|
|
44
58
|
continue;
|
|
45
|
-
void claude.cmod.call({ to: mod, method: conversationMethods.changed, input: { session: to, change } }).catch((error) => report(`changed:${mod}`, `Could not tell ${mod} about session ${to}`, error));
|
|
59
|
+
void claude.cmod.call({ to: mod, method: conversationMethods.changed, input: { session: to, change, isInProject } }).catch((error) => report(`changed:${mod}`, `Could not tell ${mod} about session ${to}`, error));
|
|
46
60
|
}
|
|
47
61
|
}
|
|
48
62
|
};
|
|
49
|
-
const restartFollower = () => {
|
|
50
|
-
const
|
|
51
|
-
const
|
|
63
|
+
const restartFollower = async () => {
|
|
64
|
+
const wantsEvery = [...heard.values()].some((sessions) => (sessions.get(everySession) ?? []).length > 0);
|
|
65
|
+
const named = [...heard.values()].flatMap((sessions) => [...sessions].filter(([id, list]) => id !== session && id !== everySession && list.length > 0).map(([id]) => id));
|
|
66
|
+
const others = [...new Set(named)].sort();
|
|
67
|
+
const args = [...others, ...(wantsEvery ? ['--running'] : [])];
|
|
68
|
+
const wanted = [session, project(), ...args].join(' ');
|
|
52
69
|
if (follower?.sessions === wanted)
|
|
53
70
|
return;
|
|
54
71
|
follower?.stop();
|
|
55
72
|
follower = undefined;
|
|
56
|
-
if (
|
|
73
|
+
if (args.length === 0)
|
|
57
74
|
return;
|
|
58
|
-
const stream = claude.process.spawn({ argv: ['cmod', 'history', 'follow', ...
|
|
75
|
+
const stream = claude.process.spawn({ argv: ['cmod', 'history', 'follow', ...args, '--json', '--project', project()], env: { CLAUDE_CODE_SESSION_ID: session } });
|
|
59
76
|
const started = { sessions: wanted, stop: () => void stream.return(undefined) };
|
|
60
77
|
follower = started;
|
|
61
78
|
void readLines(stream, (line) => {
|
|
62
79
|
if (line.trim() === '')
|
|
63
80
|
return;
|
|
64
81
|
const parsed = JSON.parse(line);
|
|
65
|
-
dispatch(parsed.session, [parsed.change]);
|
|
82
|
+
dispatch(parsed.session, [parsed.change], parsed.isInProject);
|
|
66
83
|
})
|
|
67
84
|
.then(({ code, lastError }) => {
|
|
68
85
|
if (follower === started)
|
|
@@ -75,13 +92,115 @@ export function serveConversations({ claude, router, store, project, report }) {
|
|
|
75
92
|
report('follow', 'Stopped hearing the other sessions', error);
|
|
76
93
|
});
|
|
77
94
|
};
|
|
78
|
-
const
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
if (
|
|
82
|
-
|
|
95
|
+
const follow = () => void restartFollower().catch((error) => report('follow', 'Stopped hearing the other sessions', error));
|
|
96
|
+
const post = async (to, relay) => {
|
|
97
|
+
const sent = await claude.session.send({ to: { sessionId: to }, text: `<cmod-relay>${JSON.stringify(relay)}</cmod-relay>` });
|
|
98
|
+
if (sent.isDelivered !== true)
|
|
99
|
+
throw ended(to);
|
|
100
|
+
};
|
|
101
|
+
const ask = (to, relay, deadlineMs) => new Promise((resolve, reject) => {
|
|
102
|
+
const id = `${session}:${(asks += 1)}`;
|
|
103
|
+
const timer = deadlineMs === undefined
|
|
104
|
+
? claude.clock.every(livenessMs, () => {
|
|
105
|
+
void isRunning(to).then((isLive) => {
|
|
106
|
+
if (!isLive)
|
|
107
|
+
settle.reject(ended(to));
|
|
108
|
+
});
|
|
109
|
+
})
|
|
110
|
+
: claude.clock.after(deadlineMs, () => settle.reject(new Error(`session ${to} did not answer within ${deadlineMs / 1000} s`)));
|
|
111
|
+
const settle = {
|
|
112
|
+
resolve(value) {
|
|
113
|
+
if (asked.delete(id))
|
|
114
|
+
timer.cancel();
|
|
115
|
+
resolve(value);
|
|
116
|
+
},
|
|
117
|
+
reject(error) {
|
|
118
|
+
if (asked.delete(id))
|
|
119
|
+
timer.cancel();
|
|
120
|
+
reject(error);
|
|
121
|
+
},
|
|
122
|
+
};
|
|
123
|
+
asked.set(id, settle);
|
|
124
|
+
post(to, { ...relay, id, from: session }).catch((error) => settle.reject(error instanceof Error ? error : new Error(messageOf(error))));
|
|
125
|
+
});
|
|
126
|
+
const publishWatches = (isEnding = false) => {
|
|
127
|
+
const next = publishing.then(() => publishNow(isEnding));
|
|
128
|
+
publishing = next.catch((error) => claude.ui.log(`cmod: publishing before listeners failed: ${messageOf(error)}`, { to: 'debug' }));
|
|
129
|
+
return next;
|
|
130
|
+
};
|
|
131
|
+
const publishNow = async (isEnding) => {
|
|
132
|
+
if (session === '')
|
|
83
133
|
return;
|
|
134
|
+
const list = isEnding ? [] : [...ownWatches.values()].filter((watch) => watch.events.length > 0);
|
|
135
|
+
await claude.fs.write(`${watches}/${session}.json`, JSON.stringify(list));
|
|
136
|
+
const targets = new Set([...published, ...list.map((watch) => watch.target)]);
|
|
137
|
+
published = new Set(list.map((watch) => watch.target));
|
|
138
|
+
const running = targets.has(everySession) ? (await json(['history', 'sessions', '--running'])).map((each) => each.id) : [];
|
|
139
|
+
const told = new Set([...running, ...targets].filter((id) => id !== everySession && id !== session));
|
|
140
|
+
await Promise.all([...told].map((id) => post(id, { kind: 'watch' }).catch((error) => claude.ui.log(`cmod: session ${id} did not hear of a before listener: ${messageOf(error)}`, { to: 'debug' }))));
|
|
141
|
+
};
|
|
142
|
+
const readWatchers = async () => {
|
|
143
|
+
const files = await claude.fs.list(watches).catch(ifMissing([]));
|
|
144
|
+
const found = [];
|
|
145
|
+
for (const file of files) {
|
|
146
|
+
const from = file.name.replace(/\.json$/, '');
|
|
147
|
+
if (from === session || !file.name.endsWith('.json'))
|
|
148
|
+
continue;
|
|
149
|
+
const list = JSON.parse(await claude.fs.read(`${watches}/${file.name}`).catch(ifMissing('[]')));
|
|
150
|
+
found.push(...list.filter((watch) => watch.target === session || (watch.target === everySession && watch.project === project())).map((watch) => ({ ...watch, from })));
|
|
84
151
|
}
|
|
152
|
+
watchers = found;
|
|
153
|
+
};
|
|
154
|
+
const changeGoal = (task, text) => dispatch(session, task === undefined ? conversation.changeGoal(text) : [{ type: GoalChange, goal: { text, task } }]);
|
|
155
|
+
const reloadWatchers = () => {
|
|
156
|
+
watching = readWatchers().catch((error) => report('before', 'Kept the other sessions’ before listeners from before', error));
|
|
157
|
+
};
|
|
158
|
+
const place = () => {
|
|
159
|
+
const { context, contextOpenedAt, turn, tasks, taskTurns } = conversation.state;
|
|
160
|
+
return { session, context, contextOpenedAt, ...(turn === undefined ? {} : { turn }), tasks, taskTurns };
|
|
161
|
+
};
|
|
162
|
+
const deciders = (event) => {
|
|
163
|
+
const all = [...[...ownWatches.values()].filter((watch) => watch.target === session || watch.target === everySession).map((watch) => ({ ...watch, from: session })), ...watchers];
|
|
164
|
+
return [...new Map(all.filter((watcher) => watcher.events.includes(event)).map((watcher) => [`${watcher.from}\n${watcher.mod}`, watcher])).values()];
|
|
165
|
+
};
|
|
166
|
+
const askHere = async (mod, event, change, from = session, isInProject = true) => {
|
|
167
|
+
for (const needed of ['conversation', beforePermission(event)])
|
|
168
|
+
if (!(await isGranted(mod, needed)))
|
|
169
|
+
throw new Error(`${mod} needs the ${needed} permission for before(${event}). Turn it on in /mods ${mod}.`);
|
|
170
|
+
return claude.cmod.call({ to: mod, method: conversationMethods.before, input: { session: from, event, change, isInProject } });
|
|
171
|
+
};
|
|
172
|
+
const askAway = async (watcher, event, change) => {
|
|
173
|
+
try {
|
|
174
|
+
return await ask(watcher.from, { kind: 'before', mod: watcher.mod, event, change, project: project() }, beforeDeadlineMs);
|
|
175
|
+
}
|
|
176
|
+
catch (error) {
|
|
177
|
+
if (await isRunning(watcher.from))
|
|
178
|
+
throw error;
|
|
179
|
+
watchers = watchers.filter((each) => each.from !== watcher.from);
|
|
180
|
+
claude.ui.log(`cmod: session ${watcher.from} ended without clearing its before listeners, so they are cleared now`, { to: 'debug' });
|
|
181
|
+
await claude.fs.write(`${watches}/${watcher.from}.json`, '[]');
|
|
182
|
+
return undefined;
|
|
183
|
+
}
|
|
184
|
+
};
|
|
185
|
+
installBefore({
|
|
186
|
+
claude,
|
|
187
|
+
router,
|
|
188
|
+
hooks: {
|
|
189
|
+
place: async () => {
|
|
190
|
+
await starting;
|
|
191
|
+
return place();
|
|
192
|
+
},
|
|
193
|
+
wants: async (event) => {
|
|
194
|
+
await Promise.all([starting, watching]);
|
|
195
|
+
return deciders(event).length > 0;
|
|
196
|
+
},
|
|
197
|
+
decide: (event, change) => foldAnswers(event, change, deciders(event).map((watcher) => (latest) => (watcher.from === session ? askHere(watcher.mod, event, latest) : askAway(watcher, event, latest))), (error) => report(`before:${event}`, `Passed the ${event} on without a mod's answer`, error)),
|
|
198
|
+
},
|
|
199
|
+
});
|
|
200
|
+
const start = (id, source) => {
|
|
201
|
+
if (id === session)
|
|
202
|
+
return;
|
|
203
|
+
const previous = session;
|
|
85
204
|
session = id;
|
|
86
205
|
live = [];
|
|
87
206
|
liveIds.clear();
|
|
@@ -92,9 +211,13 @@ export function serveConversations({ claude, router, store, project, report }) {
|
|
|
92
211
|
? Promise.resolve(dispatch(id, conversation.startContext(new Date().toISOString())))
|
|
93
212
|
: json(['history', 'state', id]).then((state) => {
|
|
94
213
|
if (session === id)
|
|
95
|
-
conversation = createConversation(state);
|
|
214
|
+
conversation = createConversation({ ...emptyConversation(id), ...state });
|
|
96
215
|
}, (error) => report('state', `Starts session ${id} without its earlier turns`, error));
|
|
97
|
-
|
|
216
|
+
follow();
|
|
217
|
+
reloadWatchers();
|
|
218
|
+
if (ownWatches.size === 0)
|
|
219
|
+
return;
|
|
220
|
+
void Promise.all([previous === '' ? undefined : claude.fs.write(`${watches}/${previous}.json`, '[]'), publishWatches()]).catch((error) => report('before', 'Kept its before listeners on the session from before', error));
|
|
98
221
|
};
|
|
99
222
|
const take = async (row, task) => {
|
|
100
223
|
await starting;
|
|
@@ -116,6 +239,11 @@ export function serveConversations({ claude, router, store, project, report }) {
|
|
|
116
239
|
start(e.session_id, e.source);
|
|
117
240
|
return next(e);
|
|
118
241
|
});
|
|
242
|
+
router.add('classic.SessionEnd', async (e, next) => {
|
|
243
|
+
if (ownWatches.size > 0)
|
|
244
|
+
await publishWatches(true).catch((error) => report('before', 'Left its before listeners registered', error));
|
|
245
|
+
return next(e);
|
|
246
|
+
});
|
|
119
247
|
router.add('session.append', async (e, next) => {
|
|
120
248
|
const kept = await next(e);
|
|
121
249
|
if (kept.deny !== undefined || session === '')
|
|
@@ -126,12 +254,19 @@ export function serveConversations({ claude, router, store, project, report }) {
|
|
|
126
254
|
});
|
|
127
255
|
router.add('session.compact', async (e, next) => {
|
|
128
256
|
const compacted = await next(e);
|
|
129
|
-
if (e.
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
}
|
|
257
|
+
if (e.trigger === 'precompute' || compacted.skip !== undefined || session === '')
|
|
258
|
+
return compacted;
|
|
259
|
+
const at = new Date().toISOString();
|
|
260
|
+
const boundary = { id: `compact:${e.agentId ?? session}:${at}`, at, type: 'system', name: 'compact_boundary', isMeta: false, isSummary: false, content: [] };
|
|
261
|
+
void take(boundary, e.agentId).catch((error) => report('compact', 'Missed a compaction', error));
|
|
133
262
|
return compacted;
|
|
134
263
|
});
|
|
264
|
+
router.add('session.measure', async (e, next) => {
|
|
265
|
+
const measured = await next(e);
|
|
266
|
+
if (session !== '')
|
|
267
|
+
dispatch(session, [{ type: UsageChange, usage: await usageOf(claude) }]);
|
|
268
|
+
return measured;
|
|
269
|
+
});
|
|
135
270
|
router.add('agent.spawn', async (e, next) => {
|
|
136
271
|
const spawned = await next(e);
|
|
137
272
|
if (spawned.deny !== undefined || spawned.agentId === undefined)
|
|
@@ -147,30 +282,137 @@ export function serveConversations({ claude, router, store, project, report }) {
|
|
|
147
282
|
return spawned;
|
|
148
283
|
});
|
|
149
284
|
router.add('session.receive', async (e, next) => {
|
|
150
|
-
const
|
|
151
|
-
const sent = isFromCmod ? delivery.exec(e.text)?.[1] : undefined;
|
|
285
|
+
const sent = relayOf(e);
|
|
152
286
|
if (sent === undefined)
|
|
153
287
|
return next(e);
|
|
154
|
-
const
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
const { type, text, as } = parsed;
|
|
158
|
-
claude.clock.after(0, () => {
|
|
159
|
-
const delivered = type === Note ? claude.session.append({ message: { type: 'user', content: [{ type: 'text', text }] } }) : claude.prompt.submit({ text, ...(as === Person ? { asUser: true } : {}) });
|
|
160
|
-
void delivered.catch((error) => report('delivery', 'Could not deliver a message from another session', error));
|
|
161
|
-
});
|
|
162
|
-
return { consumed: 'cmod delivery' };
|
|
288
|
+
const relay = JSON.parse(sent);
|
|
289
|
+
claude.clock.after(0, () => void received(relay).catch((error) => report('relay', 'Could not answer another session', error)));
|
|
290
|
+
return { consumed: 'cmod relay' };
|
|
163
291
|
});
|
|
292
|
+
const received = async (relay) => {
|
|
293
|
+
if (relay.kind === 'answer') {
|
|
294
|
+
const waiting = asked.get(relay.id);
|
|
295
|
+
if (relay.deny === undefined)
|
|
296
|
+
waiting?.resolve(relay.value);
|
|
297
|
+
else
|
|
298
|
+
waiting?.reject(new Error(relay.deny));
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
if (relay.kind === 'watch')
|
|
302
|
+
return readWatchers();
|
|
303
|
+
if (relay.kind === 'goal') {
|
|
304
|
+
await starting;
|
|
305
|
+
changeGoal(relay.task, relay.text);
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
const answered = async (run) => {
|
|
309
|
+
try {
|
|
310
|
+
await post(relay.from, { kind: 'answer', id: relay.id, value: (await run()) ?? null });
|
|
311
|
+
}
|
|
312
|
+
catch (error) {
|
|
313
|
+
await post(relay.from, { kind: 'answer', id: relay.id, deny: messageOf(error) });
|
|
314
|
+
}
|
|
315
|
+
};
|
|
316
|
+
if (relay.kind === 'act')
|
|
317
|
+
return answered(() => perform(relay.caller, relay.action, relay.input, relay.task));
|
|
318
|
+
return answered(() => askHere(relay.mod, relay.event, relay.change, relay.from, relay.project === project()));
|
|
319
|
+
};
|
|
320
|
+
const checkAct = async (caller, action, input) => {
|
|
321
|
+
const needed = actPermission(action, input);
|
|
322
|
+
if (needed !== undefined && !(await isGranted(caller, needed)))
|
|
323
|
+
throw new Error(`${caller} needs the ${needed} permission for ${action}. Turn it on in /mods ${caller}.`);
|
|
324
|
+
};
|
|
325
|
+
const perform = async (caller, action, input, task) => {
|
|
326
|
+
await checkAct(caller, action, input);
|
|
327
|
+
await starting;
|
|
328
|
+
if (task !== undefined)
|
|
329
|
+
return performInTask(action, input, task);
|
|
330
|
+
if (action === 'usage')
|
|
331
|
+
return usageOf(claude);
|
|
332
|
+
if (action === 'clients')
|
|
333
|
+
return [...(await claude.session.surfaces())];
|
|
334
|
+
if (action === 'send') {
|
|
335
|
+
const { type, text, as } = input;
|
|
336
|
+
if (as === Session)
|
|
337
|
+
throw new Error(`session ${session} cannot send as itself to itself.`);
|
|
338
|
+
const delivered = type === Note ? await claude.session.append({ message: { type: 'user', content: [{ type: 'text', text }] } }) : await claude.prompt.submit({ text, ...(as === Person ? { asUser: true } : {}) });
|
|
339
|
+
if ('deny' in delivered && typeof delivered.deny === 'string')
|
|
340
|
+
throw new Error(`Claude Code refused the note: ${delivered.deny}`);
|
|
341
|
+
if ('drop' in delivered && typeof delivered.drop === 'string')
|
|
342
|
+
throw new Error(`Claude Code dropped the prompt: ${delivered.drop}`);
|
|
343
|
+
return null;
|
|
344
|
+
}
|
|
345
|
+
if (action === 'startTask') {
|
|
346
|
+
const args = input;
|
|
347
|
+
const started = await claude.agent.spawn({
|
|
348
|
+
prompt: args.prompt,
|
|
349
|
+
...(args.name === undefined ? {} : { description: args.name, name: args.name }),
|
|
350
|
+
...(args.agentType === undefined ? {} : { subagentType: args.agentType }),
|
|
351
|
+
...(args.model === undefined ? {} : { model: args.model }),
|
|
352
|
+
...(args.cwd === undefined ? {} : { cwd: args.cwd }),
|
|
353
|
+
});
|
|
354
|
+
if (started.deny !== undefined)
|
|
355
|
+
throw new Error(`Claude Code refused the task: ${started.deny}`);
|
|
356
|
+
return { id: started.agentId ?? '', title: args.name ?? '', agentType: args.agentType ?? 'general-purpose', model: started.model, openedBy: '', openedAt: new Date().toISOString() };
|
|
357
|
+
}
|
|
358
|
+
const { name, input: toolInput } = input;
|
|
359
|
+
return resultOf(place(), await claude.tool.call({ ...toolInput, tool: name }));
|
|
360
|
+
};
|
|
361
|
+
const performInTask = async (action, input, task) => {
|
|
362
|
+
const running = conversation.state.tasks[task];
|
|
363
|
+
if (action === 'usage')
|
|
364
|
+
return json(['history', 'usage', session, '--task', task]);
|
|
365
|
+
if (action === 'clients')
|
|
366
|
+
return running === undefined || running.closedAt !== undefined ? [] : [...(await claude.session.surfaces())];
|
|
367
|
+
if (action !== 'send')
|
|
368
|
+
throw new Error(`cmod: ${action} runs only on a session's own main loop.`);
|
|
369
|
+
if (running === undefined || running.closedAt !== undefined)
|
|
370
|
+
throw ended(task);
|
|
371
|
+
const sent = await claude.session.send({ to: { agentId: task }, text: input.text });
|
|
372
|
+
if (sent.isDelivered !== true)
|
|
373
|
+
throw ended(task);
|
|
374
|
+
return null;
|
|
375
|
+
};
|
|
376
|
+
const actElsewhere = async (caller, target, action, input, task) => {
|
|
377
|
+
await checkAct(caller, action, input);
|
|
378
|
+
const isLive = await isRunning(target);
|
|
379
|
+
if (!isLive && action === 'usage')
|
|
380
|
+
return json(['history', 'usage', target, ...(task === undefined ? [] : ['--task', task])]);
|
|
381
|
+
if (!isLive && action === 'clients')
|
|
382
|
+
return [];
|
|
383
|
+
if (!isLive)
|
|
384
|
+
throw ended(task ?? target);
|
|
385
|
+
if (action === 'send' && task === undefined && input.as === Session) {
|
|
386
|
+
const sent = await claude.session.send({ to: { sessionId: target }, text: input.text });
|
|
387
|
+
if (sent.isDelivered !== true)
|
|
388
|
+
throw ended(target);
|
|
389
|
+
return null;
|
|
390
|
+
}
|
|
391
|
+
return ask(target, { kind: 'act', caller, action, input, ...(task === undefined ? {} : { task }) });
|
|
392
|
+
};
|
|
164
393
|
const merged = (saved, kept, key) => {
|
|
165
394
|
const keptKeys = new Set(kept.map(key));
|
|
166
395
|
return [...saved.filter((item) => !keptKeys.has(key(item))), ...kept];
|
|
167
396
|
};
|
|
168
|
-
const liveTurns = () => {
|
|
169
|
-
const turn = conversation.state.turn;
|
|
397
|
+
const liveTurns = (task) => {
|
|
398
|
+
const turn = typeof task === 'string' ? conversation.state.taskTurns[task] : conversation.state.turn;
|
|
170
399
|
return turn === undefined ? [] : [turn];
|
|
171
400
|
};
|
|
172
|
-
const liveTasks = () => Object.values(conversation.state.tasks).map((
|
|
173
|
-
|
|
401
|
+
const liveTasks = () => Object.values(conversation.state.tasks).map((task) => ({
|
|
402
|
+
id: task.id,
|
|
403
|
+
title: task.title,
|
|
404
|
+
agentType: task.agentType,
|
|
405
|
+
...(task.model === undefined ? {} : { model: task.model }),
|
|
406
|
+
openedBy: task.openedBy,
|
|
407
|
+
openedAt: task.openedAt,
|
|
408
|
+
...(task.closedAt === undefined ? {} : { closedAt: task.closedAt }),
|
|
409
|
+
...(task.parent === undefined ? {} : { parent: task.parent }),
|
|
410
|
+
}));
|
|
411
|
+
const liveContext = (task) => {
|
|
412
|
+
if (typeof task === 'string') {
|
|
413
|
+
const running = conversation.state.tasks[task];
|
|
414
|
+
return running === undefined ? [] : [{ number: running.context, openedAt: running.contextOpenedAt ?? running.openedAt, task, ...(running.closedAt === undefined ? {} : { closedAt: running.closedAt }) }];
|
|
415
|
+
}
|
|
174
416
|
const { context, contextOpenedAt } = conversation.state;
|
|
175
417
|
return context === 0 ? [] : [{ number: context, openedAt: contextOpenedAt }];
|
|
176
418
|
};
|
|
@@ -182,7 +424,8 @@ export function serveConversations({ claude, router, store, project, report }) {
|
|
|
182
424
|
},
|
|
183
425
|
async [conversationMethods.search](request) {
|
|
184
426
|
const before = isObject(request['before']) ? request['before'] : undefined;
|
|
185
|
-
|
|
427
|
+
const record = [...textArg('--session', request['session']), ...textArg('--task', request['task']), ...textArg('--turn', request['turn']), ...countArg('--context', request['context']), ...(request['isGoal'] === true ? ['--goal'] : [])];
|
|
428
|
+
return json(['history', 'search', String(request['text']), ...scopeArgs(request), ...record, ...(request['running'] === true ? ['--running'] : []), ...countArg('--limit', request['limit']), ...(before === undefined ? [] : ['--before', `${String(before['session'])}:${String(before['id'])}`])]);
|
|
186
429
|
},
|
|
187
430
|
async [conversationMethods.messages](request) {
|
|
188
431
|
const id = String(request['session']);
|
|
@@ -198,47 +441,57 @@ export function serveConversations({ claude, router, store, project, report }) {
|
|
|
198
441
|
},
|
|
199
442
|
async [conversationMethods.turns](request) {
|
|
200
443
|
const id = String(request['session']);
|
|
201
|
-
const saved = await json(['history', 'turns', id, ...countArg('--context', request['context']), ...countArg('--last', request['last'])]);
|
|
444
|
+
const saved = await json(['history', 'turns', id, ...textArg('--task', request['task']), ...countArg('--context', request['context']), ...countArg('--last', request['last'])]);
|
|
202
445
|
if (id !== session)
|
|
203
446
|
return saved;
|
|
204
447
|
await starting;
|
|
205
|
-
const kept = liveTurns().filter((turn) => request['context'] === undefined || turn.context === request['context']);
|
|
448
|
+
const kept = liveTurns(request['task']).filter((turn) => request['context'] === undefined || turn.context === request['context']);
|
|
206
449
|
return lastOf(merged(saved, kept, (turn) => turn.prompt.id), request['last']);
|
|
207
450
|
},
|
|
208
451
|
async [conversationMethods.contexts](request) {
|
|
209
452
|
const id = String(request['session']);
|
|
210
|
-
const saved = await json(['history', 'contexts', id])
|
|
453
|
+
const saved = await json(['history', 'contexts', id, ...textArg('--task', request['task'])]).catch((error) => {
|
|
454
|
+
if (id === session && typeof request['task'] === 'string')
|
|
455
|
+
return [];
|
|
456
|
+
throw error;
|
|
457
|
+
});
|
|
211
458
|
if (id !== session)
|
|
212
459
|
return saved;
|
|
213
460
|
await starting;
|
|
214
|
-
return merged(saved, liveContext(), (context) => String(context.number));
|
|
461
|
+
return merged(saved, liveContext(request['task']), (context) => String(context.number));
|
|
215
462
|
},
|
|
216
463
|
async [conversationMethods.tasks](request) {
|
|
217
464
|
const id = String(request['session']);
|
|
218
|
-
const
|
|
465
|
+
const isNested = request['all'] === true;
|
|
466
|
+
const saved = await json(['history', 'tasks', id, ...(isNested ? ['--nested'] : textArg('--task', request['task']))]);
|
|
219
467
|
if (id !== session)
|
|
220
468
|
return saved;
|
|
221
469
|
await starting;
|
|
222
|
-
const kept = liveTasks().filter((task) => task.parent === request['task']);
|
|
470
|
+
const kept = liveTasks().filter((task) => isNested || task.parent === request['task']);
|
|
223
471
|
return merged(saved, kept, (task) => task.id);
|
|
224
472
|
},
|
|
225
473
|
async [conversationMethods.goal](request) {
|
|
226
474
|
const id = String(request['session']);
|
|
227
|
-
|
|
475
|
+
const task = typeof request['task'] === 'string' ? request['task'] : undefined;
|
|
476
|
+
if (id === session && task === undefined) {
|
|
228
477
|
await starting;
|
|
229
478
|
const text = conversation.state.goal;
|
|
230
479
|
if (text !== undefined)
|
|
231
480
|
return { text };
|
|
232
481
|
}
|
|
233
|
-
return json(['history', 'goal', id]);
|
|
482
|
+
return json(['history', 'goal', id, ...textArg('--task', task)]);
|
|
234
483
|
},
|
|
235
484
|
async [conversationMethods.updateGoal](request) {
|
|
236
485
|
const id = String(request['session']);
|
|
486
|
+
const task = typeof request['task'] === 'string' ? request['task'] : undefined;
|
|
237
487
|
const text = String(request['text']);
|
|
238
|
-
await claude.fs.write(`${goals}/${id}.txt`, text);
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
488
|
+
await claude.fs.write(`${goals}/${task ?? id}.txt`, text);
|
|
489
|
+
await starting;
|
|
490
|
+
if (id === session)
|
|
491
|
+
changeGoal(task, text);
|
|
492
|
+
else {
|
|
493
|
+
dispatch(id, [{ type: GoalChange, goal: { text, ...(task === undefined ? {} : { task }) } }]);
|
|
494
|
+
await post(id, { kind: 'goal', ...(task === undefined ? {} : { task }), text }).catch((error) => claude.ui.log(`cmod: session ${id} keeps its goal until it starts again: ${messageOf(error)}`, { to: 'debug' }));
|
|
242
495
|
}
|
|
243
496
|
return null;
|
|
244
497
|
},
|
|
@@ -250,26 +503,45 @@ export function serveConversations({ claude, router, store, project, report }) {
|
|
|
250
503
|
const sessions = heard.get(caller) ?? new Map();
|
|
251
504
|
sessions.set(id, list.map(({ event, matcher }) => ({ event, matcher: matcher === undefined ? undefined : receivedMatcher(matcher) })));
|
|
252
505
|
heard.set(caller, sessions);
|
|
253
|
-
|
|
506
|
+
follow();
|
|
254
507
|
return null;
|
|
255
508
|
},
|
|
256
|
-
async [conversationMethods.
|
|
257
|
-
if (
|
|
258
|
-
throw new Error(
|
|
259
|
-
const
|
|
260
|
-
const
|
|
261
|
-
|
|
262
|
-
|
|
509
|
+
async [conversationMethods.watchBefore](request, caller) {
|
|
510
|
+
if (caller === undefined)
|
|
511
|
+
throw new Error('cmod hears no caller for this before listener.');
|
|
512
|
+
const target = String(request['session']);
|
|
513
|
+
const list = Array.isArray(request['subscriptions']) ? request['subscriptions'] : [];
|
|
514
|
+
ownWatches.set(`${caller}\n${target}`, { mod: caller, target, project: project(), events: [...new Set(list.map(({ event }) => event))] });
|
|
515
|
+
await publishWatches();
|
|
263
516
|
return null;
|
|
264
517
|
},
|
|
518
|
+
async [conversationMethods.act](request, caller) {
|
|
519
|
+
if (caller === undefined)
|
|
520
|
+
throw new Error('cmod hears no caller for this action.');
|
|
521
|
+
const target = String(request['session']);
|
|
522
|
+
const action = String(request['action']);
|
|
523
|
+
const task = typeof request['task'] === 'string' ? request['task'] : undefined;
|
|
524
|
+
if (!isAct(action))
|
|
525
|
+
throw new Error(`cmod takes no action named ${action}.`);
|
|
526
|
+
if (target === session)
|
|
527
|
+
return perform(caller, action, request['input'], task);
|
|
528
|
+
return actElsewhere(caller, target, action, request['input'], task);
|
|
529
|
+
},
|
|
265
530
|
};
|
|
266
531
|
return {
|
|
532
|
+
moved() {
|
|
533
|
+
for (const [key, watch] of ownWatches)
|
|
534
|
+
ownWatches.set(key, { ...watch, project: project() });
|
|
535
|
+
void publishWatches().catch((error) => report('before', 'Kept its before listeners on the project from before', error));
|
|
536
|
+
reloadWatchers();
|
|
537
|
+
follow();
|
|
538
|
+
},
|
|
267
539
|
async answer(e) {
|
|
268
540
|
const method = methods[e.method];
|
|
269
541
|
if (method === undefined)
|
|
270
542
|
return undefined;
|
|
271
543
|
const input = isObject(e.input) ? e.input : {};
|
|
272
|
-
if (e.method !== conversationMethods.
|
|
544
|
+
if (e.method !== conversationMethods.act && !(await isGranted(e.caller, 'conversation')))
|
|
273
545
|
return { deny: `${e.caller ?? 'A mod'} reads your conversations without your grant. Turn it on in /mods ${e.caller ?? ''}.`.trim() };
|
|
274
546
|
try {
|
|
275
547
|
return { value: await method(input, e.caller) };
|
package/runtime/dependencies.js
CHANGED
package/runtime/engine.d.ts
CHANGED
|
@@ -1,16 +1,12 @@
|
|
|
1
1
|
import type { Claude } from './claude.js';
|
|
2
2
|
import type { Router } from './router.js';
|
|
3
|
+
import type { Failure } from './toaster.js';
|
|
3
4
|
import type { ToolCalls } from './tool-calls.js';
|
|
4
5
|
export type Engine = {
|
|
5
6
|
readonly claude: Claude;
|
|
6
7
|
readonly router: Router;
|
|
7
8
|
readonly toolCalls: ToolCalls;
|
|
8
|
-
readonly report: (failure:
|
|
9
|
-
readonly id: string;
|
|
10
|
-
readonly message: string;
|
|
11
|
-
readonly error?: unknown;
|
|
12
|
-
readonly description?: string;
|
|
13
|
-
}) => void;
|
|
9
|
+
readonly report: (failure: Failure) => void;
|
|
14
10
|
};
|
|
15
11
|
export declare function keepEngine(mod: object, engine: Engine): void;
|
|
16
12
|
export declare function engineOf(mod: object): Engine;
|
package/runtime/engine.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../src/runtime/engine.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AACzC,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AACzC,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAA;AAEhD,MAAM,MAAM,MAAM,GAAG;IACnB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,SAAS,EAAE,SAAS,CAAA;IAC7B,QAAQ,CAAC,MAAM,EAAE,CAAC,OAAO,EAAE
|
|
1
|
+
{"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../src/runtime/engine.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AACzC,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AACzC,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAA;AAC3C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAA;AAEhD,MAAM,MAAM,MAAM,GAAG;IACnB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,SAAS,EAAE,SAAS,CAAA;IAC7B,QAAQ,CAAC,MAAM,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,CAAA;CAC5C,CAAA;AAID,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,CAE5D;AAED,wBAAgB,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAI5C"}
|
package/runtime/grants.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"grants.d.ts","sourceRoot":"","sources":["../src/runtime/grants.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AAEzC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAA;AAG7C,MAAM,MAAM,MAAM,GAAG;IACnB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAA;IACpC,QAAQ,CAAC,OAAO,EAAE,MAAM,WAAW,CAAC,MAAM,CAAC,CAAA;IAC3C,QAAQ,CAAC,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;IACrC,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,CAAA;IACjC,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,SAAS,CAAA;IACvC,QAAQ,CAAC,WAAW,EAAE,MAAM,MAAM,CAAA;IAClC,QAAQ,CAAC,WAAW,EAAE,MAAM,SAAS,MAAM,EAAE,CAAA;IAC7C,QAAQ,CAAC,aAAa,EAAE,MAAM,WAAW,CAAC,MAAM,CAAC,CAAA;CAClD,CAAA;AAED,qBAAa,iBAAkB,SAAQ,KAAK;CAAG;AAI/C,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC,CAIzD;
|
|
1
|
+
{"version":3,"file":"grants.d.ts","sourceRoot":"","sources":["../src/runtime/grants.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AAEzC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAA;AAG7C,MAAM,MAAM,MAAM,GAAG;IACnB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAA;IACpC,QAAQ,CAAC,OAAO,EAAE,MAAM,WAAW,CAAC,MAAM,CAAC,CAAA;IAC3C,QAAQ,CAAC,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;IACrC,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,CAAA;IACjC,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,SAAS,CAAA;IACvC,QAAQ,CAAC,WAAW,EAAE,MAAM,MAAM,CAAA;IAClC,QAAQ,CAAC,WAAW,EAAE,MAAM,SAAS,MAAM,EAAE,CAAA;IAC7C,QAAQ,CAAC,aAAa,EAAE,MAAM,WAAW,CAAC,MAAM,CAAC,CAAA;CAClD,CAAA;AAED,qBAAa,iBAAkB,SAAQ,KAAK;CAAG;AAI/C,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC,CAIzD;AAkBD,wBAAsB,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC,CAItG;AAED,wBAAsB,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAE1F;AAED,wBAAsB,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,EAAE,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAEhI;AAED,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAkFrE;AAmBD,wBAAgB,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAE3D;AAED,wBAAgB,SAAS,CAAC,OAAO,EAAE,WAAW,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAUvG;AAiBD,wBAAgB,OAAO,CAAC,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,MAAM,GAAG,UAAU,GAAG,MAAM,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAI9G;AA4BD,MAAM,MAAM,WAAW,GAAG;IACxB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAA;IAChC,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAAA;CAC1C,CAAA;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE,WAAW,GAAG,OAAO,CAEzD;AAED,wBAAgB,UAAU,CAAC,KAAK,EAAE,WAAW,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,WAAW,GAAG,OAAO,CAa9G;AAED,wBAAsB,aAAa,CAAC,KAAK,EAAE,WAAW,EAAE,CAAC,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,EAAE,KAAK,EAAE,WAAW,GAAG,OAAO,CAAC,OAAO,CAAC,CA+BxJ"}
|