@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.
Files changed (122) hide show
  1. package/docs/commands.md +22 -11
  2. package/docs/dependencies.md +6 -4
  3. package/docs/hooks.md +26 -3
  4. package/docs/index.md +1 -1
  5. package/docs/install-steps.md +5 -3
  6. package/docs/mod.md +29 -3
  7. package/docs/permissions.md +4 -4
  8. package/docs/session.md +126 -63
  9. package/docs/testing.md +7 -5
  10. package/docs/ui.md +9 -1
  11. package/jobs/program.d.ts.map +1 -1
  12. package/jobs/program.js +7 -2
  13. package/mod.d.ts +30 -6
  14. package/mod.d.ts.map +1 -1
  15. package/mod.js +34 -1
  16. package/package.json +1 -1
  17. package/records.d.ts +4 -1
  18. package/records.d.ts.map +1 -1
  19. package/records.js +6 -1
  20. package/register.d.ts.map +1 -1
  21. package/register.js +17 -0
  22. package/runtime/announce.d.ts.map +1 -1
  23. package/runtime/announce.js +2 -0
  24. package/runtime/before.d.ts +32 -0
  25. package/runtime/before.d.ts.map +1 -0
  26. package/runtime/before.js +214 -0
  27. package/runtime/claude.d.ts +2 -1
  28. package/runtime/claude.d.ts.map +1 -1
  29. package/runtime/conversations.d.ts +1 -0
  30. package/runtime/conversations.d.ts.map +1 -1
  31. package/runtime/conversations.js +333 -61
  32. package/runtime/dependencies.js +1 -1
  33. package/runtime/engine.d.ts +2 -6
  34. package/runtime/engine.d.ts.map +1 -1
  35. package/runtime/grants.d.ts.map +1 -1
  36. package/runtime/grants.js +8 -8
  37. package/runtime/hooks.d.ts +1 -1
  38. package/runtime/hooks.d.ts.map +1 -1
  39. package/runtime/hooks.js +19 -4
  40. package/runtime/installer.d.ts +1 -1
  41. package/runtime/installer.d.ts.map +1 -1
  42. package/runtime/installer.js +6 -5
  43. package/runtime/lifecycle.d.ts.map +1 -1
  44. package/runtime/lifecycle.js +129 -58
  45. package/runtime/metadata.d.ts.map +1 -1
  46. package/runtime/metadata.js +5 -4
  47. package/runtime/sessions.d.ts +26 -4
  48. package/runtime/sessions.d.ts.map +1 -1
  49. package/runtime/sessions.js +315 -117
  50. package/runtime/toaster.d.ts +5 -12
  51. package/runtime/toaster.d.ts.map +1 -1
  52. package/runtime/toaster.js +133 -23
  53. package/runtime/ui.d.ts.map +1 -1
  54. package/runtime/ui.js +26 -6
  55. package/session/context/context.d.ts +3 -1
  56. package/session/context/context.d.ts.map +1 -1
  57. package/session/context/turn/message/message.d.ts +1 -0
  58. package/session/context/turn/message/message.d.ts.map +1 -1
  59. package/session/context/turn/message/message.js +42 -1
  60. package/session/context/turn/turn.d.ts +2 -1
  61. package/session/context/turn/turn.d.ts.map +1 -1
  62. package/session/conversation.d.ts +20 -11
  63. package/session/conversation.d.ts.map +1 -1
  64. package/session/conversation.js +50 -27
  65. package/session/goal/goal.d.ts +3 -2
  66. package/session/goal/goal.d.ts.map +1 -1
  67. package/session/goal/task.d.ts +4 -4
  68. package/session/goal/task.d.ts.map +1 -1
  69. package/session/session.d.ts +80 -21
  70. package/session/session.d.ts.map +1 -1
  71. package/session/session.js +1 -0
  72. package/src/jobs/program.ts +7 -2
  73. package/src/mod.ts +48 -5
  74. package/src/records.ts +6 -1
  75. package/src/register.ts +17 -0
  76. package/src/runtime/announce.ts +2 -0
  77. package/src/runtime/before.ts +200 -0
  78. package/src/runtime/claude.ts +3 -0
  79. package/src/runtime/conversations.ts +337 -60
  80. package/src/runtime/dependencies.ts +1 -1
  81. package/src/runtime/engine.ts +2 -1
  82. package/src/runtime/grants.ts +8 -10
  83. package/src/runtime/hooks.ts +20 -4
  84. package/src/runtime/installer.ts +6 -5
  85. package/src/runtime/lifecycle.ts +135 -55
  86. package/src/runtime/metadata.ts +5 -4
  87. package/src/runtime/sessions.ts +316 -111
  88. package/src/runtime/toaster.ts +160 -44
  89. package/src/runtime/ui.ts +23 -4
  90. package/src/session/context/context.ts +3 -1
  91. package/src/session/context/turn/message/message.ts +39 -1
  92. package/src/session/context/turn/turn.ts +2 -1
  93. package/src/session/conversation.ts +56 -31
  94. package/src/session/goal/goal.ts +3 -2
  95. package/src/session/goal/task.ts +4 -4
  96. package/src/session/session.ts +62 -15
  97. package/src/testing/fake-claude.ts +4 -2
  98. package/src/testing/fake-sessions.ts +94 -20
  99. package/src/testing.ts +27 -3
  100. package/src/utils/call-effects.ts +2 -2
  101. package/src/utils/disposable.ts +3 -0
  102. package/src/utils/matcher.ts +1 -1
  103. package/src/utils/paths.ts +6 -3
  104. package/testing/fake-claude.d.ts +1 -0
  105. package/testing/fake-claude.d.ts.map +1 -1
  106. package/testing/fake-claude.js +3 -1
  107. package/testing/fake-sessions.d.ts +8 -1
  108. package/testing/fake-sessions.d.ts.map +1 -1
  109. package/testing/fake-sessions.js +97 -18
  110. package/testing.d.ts +1 -0
  111. package/testing.d.ts.map +1 -1
  112. package/testing.js +23 -2
  113. package/utils/call-effects.d.ts +1 -0
  114. package/utils/call-effects.d.ts.map +1 -1
  115. package/utils/call-effects.js +1 -1
  116. package/utils/disposable.d.ts +4 -0
  117. package/utils/disposable.d.ts.map +1 -0
  118. package/utils/disposable.js +1 -0
  119. package/utils/matcher.js +1 -1
  120. package/utils/paths.d.ts +2 -1
  121. package/utils/paths.d.ts.map +1 -1
  122. package/utils/paths.js +6 -3
@@ -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 goals = `${dataFolder(store, cmodPluginName)}/goals`;
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 dispatch = (to, changes) => {
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 others = [...new Set([...heard.values()].flatMap((sessions) => [...sessions].filter(([id, list]) => id !== session && list.length > 0).map(([id]) => id)))].sort();
51
- const wanted = others.join(' ');
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 (others.length === 0)
73
+ if (args.length === 0)
57
74
  return;
58
- const stream = claude.process.spawn({ argv: ['cmod', 'history', 'follow', ...others, '--json', '--project', project()], env: { CLAUDE_CODE_SESSION_ID: session } });
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 start = (id, source) => {
79
- if (id === session && source !== 'compact')
80
- return;
81
- if (id === session && source === 'compact') {
82
- dispatch(session, conversation.startContext(new Date().toISOString()));
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
- restartFollower();
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.agentId === undefined && e.trigger !== 'precompute' && compacted.skip === undefined) {
130
- await starting;
131
- dispatch(session, conversation.endContext(new Date().toISOString()));
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 isFromCmod = e.origin.kind === 'peer' && !('teammate' in e.origin) && e.origin.plugin === cmodPluginName;
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 parsed = JSON.parse(sent);
155
- if (!isObject(parsed) || typeof parsed['text'] !== 'string')
156
- return next(e);
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(({ context: _context, ...task }) => task);
173
- const liveContext = () => {
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
- return json(['history', 'search', String(request['text']), ...scopeArgs(request), ...(request['running'] === true ? ['--running'] : []), ...countArg('--limit', request['limit']), ...(before === undefined ? [] : ['--before', `${String(before['session'])}:${String(before['id'])}`])]);
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 saved = await json(['history', 'tasks', id, ...textArg('--task', request['task'])]);
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
- if (id === session) {
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
- if (id === session) {
240
- await starting;
241
- dispatch(session, conversation.changeGoal(text));
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
- restartFollower();
506
+ follow();
254
507
  return null;
255
508
  },
256
- async [conversationMethods.send](request, caller) {
257
- if (!(await isGranted(caller, 'prompt')))
258
- throw new Error(`${caller ?? 'A mod'} sends to another session without your grant to send prompts.`);
259
- const type = request['type'] === Note ? Note : Prompt;
260
- const sent = await claude.session.send({ to: { sessionId: String(request['session']) }, text: `<cmod-delivery>${JSON.stringify({ type, text: request['text'], as: request['as'] })}</cmod-delivery>` });
261
- if (sent.isDelivered !== true)
262
- throw new Error(`Claude Code could not deliver the message to session ${String(request['session'])}.`);
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.send && !(await isGranted(e.caller, 'conversation')))
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) };
@@ -24,5 +24,5 @@ export function withBareRefusals(claude) {
24
24
  return { ...claude, cmod: { call } };
25
25
  }
26
26
  export function notInstalled(name) {
27
- return `${name} is not installed. Run cmod install ${name}.`;
27
+ return `${name} is not installed. Add it with /plugin.`;
28
28
  }
@@ -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;
@@ -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;QAAE,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,CAAC;QAAC,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAA;CAC/I,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"}
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"}
@@ -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;AAqBD,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,CAiFrE;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"}
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"}