@cmodjs/core 1.6.0 → 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (136) hide show
  1. package/docs/commands.md +7 -7
  2. package/docs/dependencies.md +1 -1
  3. package/docs/index.md +12 -11
  4. package/docs/jobs.md +12 -16
  5. package/docs/mod.md +111 -36
  6. package/docs/permissions.md +14 -18
  7. package/docs/session.md +171 -82
  8. package/docs/state.md +5 -5
  9. package/docs/testing.md +57 -41
  10. package/docs/ui.md +9 -9
  11. package/jobs/permissions/decide-permission.d.ts.map +1 -1
  12. package/jobs/permissions/decide-permission.js +1 -1
  13. package/jobs/prompt.d.ts +1 -3
  14. package/jobs/prompt.d.ts.map +1 -1
  15. package/jobs/prompt.js +2 -21
  16. package/jobs/slash-command.d.ts +1 -1
  17. package/jobs/slash-command.d.ts.map +1 -1
  18. package/jobs/slash-command.js +1 -1
  19. package/jobs/status-line.d.ts +0 -1
  20. package/jobs/status-line.d.ts.map +1 -1
  21. package/jobs/status-line.js +3 -4
  22. package/mod.d.ts +53 -105
  23. package/mod.d.ts.map +1 -1
  24. package/mod.js +18 -80
  25. package/package.json +1 -1
  26. package/records.d.ts +1 -1
  27. package/records.js +1 -1
  28. package/register.d.ts.map +1 -1
  29. package/register.js +8 -5
  30. package/runtime/before.d.ts +11 -4
  31. package/runtime/before.d.ts.map +1 -1
  32. package/runtime/before.js +131 -67
  33. package/runtime/claude.d.ts +6 -1
  34. package/runtime/claude.d.ts.map +1 -1
  35. package/runtime/conversations.d.ts.map +1 -1
  36. package/runtime/conversations.js +82 -38
  37. package/runtime/deadline.d.ts +17 -0
  38. package/runtime/deadline.d.ts.map +1 -1
  39. package/runtime/deadline.js +34 -0
  40. package/runtime/dependencies.d.ts +2 -0
  41. package/runtime/dependencies.d.ts.map +1 -1
  42. package/runtime/dependencies.js +12 -0
  43. package/runtime/grants.d.ts.map +1 -1
  44. package/runtime/grants.js +13 -8
  45. package/runtime/hooks.d.ts +15 -11
  46. package/runtime/hooks.d.ts.map +1 -1
  47. package/runtime/hooks.js +8 -165
  48. package/runtime/lifecycle.d.ts +4 -2
  49. package/runtime/lifecycle.d.ts.map +1 -1
  50. package/runtime/lifecycle.js +96 -62
  51. package/runtime/permission-check.d.ts +15 -0
  52. package/runtime/permission-check.d.ts.map +1 -0
  53. package/runtime/permission-check.js +37 -0
  54. package/runtime/sessions.d.ts +35 -5
  55. package/runtime/sessions.d.ts.map +1 -1
  56. package/runtime/sessions.js +304 -82
  57. package/runtime/state.d.ts.map +1 -1
  58. package/runtime/state.js +14 -11
  59. package/runtime/tool-calls.d.ts +0 -1
  60. package/runtime/tool-calls.d.ts.map +1 -1
  61. package/runtime/tool-calls.js +0 -19
  62. package/session/context/turn/message/instruction-file.d.ts +9 -0
  63. package/session/context/turn/message/instruction-file.d.ts.map +1 -0
  64. package/session/context/turn/message/instruction-file.js +4 -0
  65. package/session/context/turn/message/message.d.ts +6 -4
  66. package/session/context/turn/message/message.d.ts.map +1 -1
  67. package/session/context/turn/message/message.js +12 -8
  68. package/session/context/turn/message/sender.d.ts +2 -1
  69. package/session/context/turn/message/sender.d.ts.map +1 -1
  70. package/session/context/turn/message/sender.js +4 -1
  71. package/session/context/turn/message/tool-call.d.ts +5 -0
  72. package/session/context/turn/message/tool-call.d.ts.map +1 -1
  73. package/session/context/turn/message/tool-result.d.ts +6 -1
  74. package/session/context/turn/message/tool-result.d.ts.map +1 -1
  75. package/session/context/turn/turn.d.ts +7 -0
  76. package/session/context/turn/turn.d.ts.map +1 -1
  77. package/session/conversation.d.ts +10 -3
  78. package/session/conversation.d.ts.map +1 -1
  79. package/session/conversation.js +14 -5
  80. package/session/goal/goal.d.ts +1 -1
  81. package/session/goal/goal.d.ts.map +1 -1
  82. package/session/goal/task.d.ts +2 -2
  83. package/session/goal/task.d.ts.map +1 -1
  84. package/session/session.d.ts +361 -26
  85. package/session/session.d.ts.map +1 -1
  86. package/session/session.js +40 -0
  87. package/src/jobs/permissions/decide-permission.ts +2 -1
  88. package/src/jobs/prompt.ts +3 -20
  89. package/src/jobs/slash-command.ts +2 -2
  90. package/src/jobs/status-line.ts +4 -7
  91. package/src/mod.ts +52 -147
  92. package/src/records.ts +1 -1
  93. package/src/register.ts +8 -5
  94. package/src/runtime/before.ts +140 -66
  95. package/src/runtime/claude.ts +5 -1
  96. package/src/runtime/conversations.ts +75 -37
  97. package/src/runtime/deadline.ts +38 -0
  98. package/src/runtime/dependencies.ts +12 -0
  99. package/src/runtime/grants.ts +11 -7
  100. package/src/runtime/hooks.ts +24 -163
  101. package/src/runtime/lifecycle.ts +110 -77
  102. package/src/runtime/permission-check.ts +47 -0
  103. package/src/runtime/sessions.ts +334 -91
  104. package/src/runtime/state.ts +15 -8
  105. package/src/runtime/tool-calls.ts +0 -19
  106. package/src/session/context/turn/message/instruction-file.ts +13 -0
  107. package/src/session/context/turn/message/message.ts +17 -11
  108. package/src/session/context/turn/message/sender.ts +5 -2
  109. package/src/session/context/turn/message/tool-call.ts +3 -0
  110. package/src/session/context/turn/message/tool-result.ts +6 -1
  111. package/src/session/context/turn/turn.ts +4 -0
  112. package/src/session/conversation.ts +21 -8
  113. package/src/session/goal/goal.ts +1 -1
  114. package/src/session/goal/task.ts +2 -2
  115. package/src/session/session.ts +227 -24
  116. package/src/testing/claude-code.ts +23 -0
  117. package/src/testing/fake-claude.ts +5 -1
  118. package/src/testing/fake-sessions.ts +38 -24
  119. package/src/testing.ts +88 -19
  120. package/testing/claude-code.d.ts +12 -0
  121. package/testing/claude-code.d.ts.map +1 -0
  122. package/testing/claude-code.js +10 -0
  123. package/testing/fake-claude.d.ts +4 -0
  124. package/testing/fake-claude.d.ts.map +1 -1
  125. package/testing/fake-claude.js +4 -1
  126. package/testing/fake-sessions.d.ts +8 -6
  127. package/testing/fake-sessions.d.ts.map +1 -1
  128. package/testing/fake-sessions.js +35 -20
  129. package/testing.d.ts +38 -9
  130. package/testing.d.ts.map +1 -1
  131. package/testing.js +58 -8
  132. package/docs/hooks.md +0 -259
  133. package/session/context/turn/message/document.d.ts +0 -7
  134. package/session/context/turn/message/document.d.ts.map +0 -1
  135. package/session/context/turn/message/document.js +0 -1
  136. package/src/session/context/turn/message/document.ts +0 -8
package/runtime/before.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { ContextEnd } from '../session/context/context.js';
2
- import { Document } from '../session/context/turn/message/document.js';
3
- import { Message, messagesOf, rowOfAppend, withTexts } from '../session/context/turn/message/message.js';
2
+ import { InstructionFile, isRulePath } from '../session/context/turn/message/instruction-file.js';
3
+ import { instructionPathOf, Message, messagesOf, noFiles, rowOfAppend, withTexts } from '../session/context/turn/message/message.js';
4
4
  import { Note } from '../session/context/turn/message/note.js';
5
5
  import { Notice } from '../session/context/turn/message/notice.js';
6
6
  import { Prompt } from '../session/context/turn/message/prompt.js';
@@ -10,10 +10,16 @@ import { Skill } from '../session/context/turn/message/skill.js';
10
10
  import { ToolCall } from '../session/context/turn/message/tool-call.js';
11
11
  import { ToolResult } from '../session/context/turn/message/tool-result.js';
12
12
  import { TurnEnd } from '../session/context/turn/turn.js';
13
- import { TaskEnd, TaskStart } from '../session/goal/task.js';
13
+ import { Task, TaskStart } from '../session/goal/task.js';
14
+ import { sessionEvents } from '../session/session.js';
14
15
  import { cmodPluginName } from './dependencies.js';
15
16
  import { toolInputOf } from './tool-calls.js';
16
- const appended = [Note, Reply, ToolResult, Notice];
17
+ import { resolve } from '../vendor.js';
18
+ const appended = [Note, Reply, Notice];
19
+ const withNote = (below, note) => {
20
+ const fields = (below ?? {});
21
+ return note === undefined ? fields : { ...fields, additionalContext: [...(fields['additionalContext'] ?? []), note] };
22
+ };
17
23
  const relayTag = /^(?:<cross-session-message[^>]*>\s*)?<cmod-relay>([\s\S]*)<\/cmod-relay>(?:\s*<\/cross-session-message>[\s\S]*)?$/;
18
24
  export function relayOf(e) {
19
25
  const isFromCmod = e.origin.kind === 'peer' && !('teammate' in e.origin) && e.origin.plugin === cmodPluginName;
@@ -26,34 +32,56 @@ export function installBefore({ claude, router, hooks }) {
26
32
  const message = (where, unplaced, task, isInTurn = true) => {
27
33
  const turn = isInTurn ? (task === undefined ? where.turn : where.taskTurns[task]) : undefined;
28
34
  const context = task === undefined ? where.context : (where.tasks[task]?.context ?? where.context);
29
- return { type: Message, message: { ...unplaced, session: where.session, context, ...(turn === undefined ? {} : { turn: turn.prompt.id }), ...(task === undefined ? {} : { task }) } };
35
+ const call = unplaced.type === ToolResult ? where.openCalls?.[unplaced.callId] : undefined;
36
+ return { type: Message, message: { ...unplaced, ...call, session: where.session, context, ...(turn === undefined ? {} : { turn: turn.prompt.id }), ...(task === undefined ? {} : { task }) } };
37
+ };
38
+ const titles = new Map();
39
+ const spawned = new Map();
40
+ const agentFolders = new Map();
41
+ const toolResultHook = (isFailure) => async (e, next) => {
42
+ if (!(await wants(ToolResult)))
43
+ return next(e);
44
+ const where = await place();
45
+ const name = String(e['tool_name']);
46
+ const callId = String(e['tool_use_id']);
47
+ const input = (typeof e['tool_input'] === 'object' && e['tool_input'] !== null ? e['tool_input'] : {});
48
+ const task = typeof e['agent_id'] === 'string' ? e['agent_id'] : undefined;
49
+ const result = { id: callId, at: now(), type: ToolResult, callId, name, input, cwd: String(e['cwd']), files: noFiles, output: isFailure ? e['error'] : e['tool_response'], isError: isFailure };
50
+ const answer = await asked(ToolResult, message(where, result, task));
51
+ const below = await next(e);
52
+ const rewritten = answer.output === undefined || isFailure ? {} : name.startsWith('mcp__') ? { updatedMCPToolOutput: answer.output } : { updatedToolOutput: answer.output };
53
+ const failed = answer.isError === true && !isFailure ? { block: answer.note ?? `${name} failed.` } : {};
54
+ return { ...withNote(below, answer.isError === true ? undefined : answer.note), ...rewritten, ...failed };
30
55
  };
31
56
  const added = [
32
57
  router.add('prompt.submit', async (e, next) => {
33
- if (!(await wants(Prompt)) || e.text.startsWith('/'))
58
+ if (!(await wants(Prompt)))
34
59
  return next(e);
35
60
  const where = await place();
36
61
  const answer = await asked(Prompt, message(where, { id: '', at: now(), type: Prompt, text: e.text, from: senderOf(e.origin, false) }, undefined, false));
37
62
  if (answer.refuse !== undefined)
38
63
  return { drop: answer.refuse };
39
- return next(answer.text === undefined ? e : { ...e, text: answer.text });
64
+ const text = answer.text ?? e.text;
65
+ if (answer.title !== undefined)
66
+ titles.set(text, answer.title);
67
+ const context = answer.note === undefined ? e.context : [...(e.context ?? []), answer.note];
68
+ const ran = await next({ ...e, text, ...(context === undefined ? {} : { context }) });
69
+ const passed = titles.get(text);
70
+ if (passed !== undefined && ('drop' in ran || ran.text !== text)) {
71
+ titles.delete(text);
72
+ if (!('drop' in ran) && typeof ran.text === 'string')
73
+ titles.set(ran.text, passed);
74
+ }
75
+ return ran;
40
76
  }),
41
- router.add('command.run', async (e, next) => {
42
- if (!(await wants(Prompt)))
43
- return next(e);
44
- const typed = e.args === '' ? `/${e.command}` : `/${e.command} ${e.args}`;
45
- const where = await place();
46
- const answer = await asked(Prompt, message(where, { id: '', at: now(), type: Prompt, text: typed, from: senderOf(e.origin, false) }, undefined, false));
47
- if (answer.refuse !== undefined)
48
- return { text: answer.refuse };
49
- if (answer.text === undefined || answer.text === typed)
50
- return next(e);
51
- const same = new RegExp(`^/${e.command}(?:\\s+|$)`).exec(answer.text);
52
- if (same !== null)
53
- return next({ ...e, args: answer.text.slice(same[0].length) });
54
- void claude.prompt.submit({ text: answer.text });
55
- return {};
77
+ router.add('classic.UserPromptSubmit', async (e, next) => {
78
+ const named = titles.get(e.prompt);
79
+ titles.delete(e.prompt);
80
+ const below = await next(e);
81
+ return named === undefined ? below : { ...below, sessionTitle: named };
56
82
  }),
83
+ router.add('classic.PostToolUse', toolResultHook(false)),
84
+ router.add('classic.PostToolUseFailure', toolResultHook(true)),
57
85
  router.add('session.receive', async (e, next) => {
58
86
  if (!(await wants(Prompt)) || relayOf(e) !== undefined)
59
87
  return next(e);
@@ -71,11 +99,11 @@ export function installBefore({ claude, router, hooks }) {
71
99
  return answer.text === undefined ? next(e) : { text: answer.text };
72
100
  }),
73
101
  router.add('prompt.attachment', async (e, next) => {
74
- if (!(await wants(Document)) || e.type !== 'nested_memory')
102
+ if (!(await wants(InstructionFile)) || e.type !== 'nested_memory')
75
103
  return next(e);
76
104
  const where = await place();
77
- const path = /^Contents of (.+?) \(/.exec(e.text)?.[1] ?? '';
78
- const answer = await asked(Document, message(where, { id: '', at: now(), type: Document, path, text: e.text }, e.agentId));
105
+ const path = instructionPathOf(e.text);
106
+ const answer = await asked(InstructionFile, message(where, { id: '', at: now(), type: InstructionFile, path, text: e.text, isRule: isRulePath(path) }, e.agentId));
79
107
  if (answer.refuse !== undefined)
80
108
  return { text: null };
81
109
  return answer.text === undefined ? next(e) : { text: answer.text };
@@ -104,7 +132,8 @@ export function installBefore({ claude, router, hooks }) {
104
132
  return next(e);
105
133
  const where = await place();
106
134
  const input = toolInputOf(e);
107
- const answer = await asked(ToolCall, message(where, { id: e.tool_use_id, at: now(), type: ToolCall, callId: e.tool_use_id, name: e.tool, input, cwd: await claude.session.cwd() }, e.agentId));
135
+ const cwd = (e.agentId === undefined ? undefined : agentFolders.get(e.agentId)) ?? (await claude.session.cwd());
136
+ const answer = await asked(ToolCall, message(where, { id: e.tool_use_id, at: now(), type: ToolCall, callId: e.tool_use_id, name: e.tool, input, cwd, files: noFiles }, e.agentId));
108
137
  if (answer.refuse !== undefined)
109
138
  return { deny: answer.refuse };
110
139
  if (answer.result !== undefined)
@@ -115,27 +144,54 @@ export function installBefore({ claude, router, hooks }) {
115
144
  return next({ ...reserved, ...answer.input });
116
145
  }),
117
146
  router.add('agent.spawn', async (e, next) => {
147
+ spawned.set(e.tool_use_id, e.prompt);
148
+ const start = async (args) => {
149
+ const started = await next(args);
150
+ const folder = args.isTeammate === true ? undefined : (args.cwd ?? (args.parentAgentId === undefined ? undefined : agentFolders.get(args.parentAgentId)));
151
+ if (started.agentId !== undefined && folder !== undefined)
152
+ agentFolders.set(started.agentId, resolve(await claude.session.cwd(), folder));
153
+ return started;
154
+ };
118
155
  if (!(await wants(TaskStart)))
119
- return next(e);
156
+ return start(e);
120
157
  const where = await place();
121
158
  const task = { id: '', title: e.description, agentType: e.subagentType, model: e.model ?? e.parentModel, openedBy: e.tool_use_id, openedAt: now(), ...(e.parentAgentId === undefined ? {} : { parent: e.parentAgentId }) };
122
159
  const answer = await asked(TaskStart, { type: TaskStart, task: { ...task, context: where.context } });
123
- if (answer.refuse !== undefined)
160
+ if (answer.refuse !== undefined) {
161
+ spawned.delete(e.tool_use_id);
124
162
  return { deny: answer.refuse };
125
- return next(answer.owner === undefined ? e : { ...e, model: answer.owner.model });
163
+ }
164
+ return start(answer.owner === undefined ? e : { ...e, model: answer.owner.model });
126
165
  }),
127
- router.add('classic.SubagentStop', async (e, next) => {
128
- if (!(await wants(TaskEnd)) && !(await wants(TurnEnd)))
129
- return next(e);
166
+ router.add('classic.SubagentStart', async (e, next) => {
130
167
  const where = await place();
131
- const task = where.tasks[e.agent_id];
132
- if (task === undefined)
168
+ const openedBy = where.tasks[e.agent_id]?.openedBy;
169
+ const prompt = openedBy === undefined ? undefined : spawned.get(openedBy);
170
+ if (openedBy !== undefined)
171
+ spawned.delete(openedBy);
172
+ if (!(await wants(Prompt)))
133
173
  return next(e);
134
- const turn = where.taskTurns[e.agent_id];
135
- const ending = (await wants(TaskEnd)) ? await asked(TaskEnd, { type: TaskEnd, task: { ...task, closedAt: now() } }) : {};
136
- const refuse = ending.refuse ?? ((await wants(TurnEnd)) && turn !== undefined ? (await asked(TurnEnd, { type: TurnEnd, turn: { ...turn, closedAt: now() } })).refuse : undefined);
174
+ const open = where.taskTurns[e.agent_id]?.prompt;
175
+ const text = open?.text ?? prompt;
176
+ if (text === undefined)
177
+ return next(e);
178
+ const answer = await asked(Prompt, message(where, { id: open?.id ?? '', at: now(), type: Prompt, text, from: open?.from ?? { type: Task } }, e.agent_id, false));
179
+ return withNote(await next(e), answer.note);
180
+ }),
181
+ router.add('classic.SubagentStop', async (e, next) => {
182
+ const stopped = (answer) => {
183
+ if (answer.block === undefined)
184
+ agentFolders.delete(e.agent_id);
185
+ return answer;
186
+ };
187
+ if (!(await wants(TurnEnd)))
188
+ return stopped(await next(e));
189
+ const turn = (await place()).taskTurns[e.agent_id];
190
+ if (turn === undefined)
191
+ return stopped(await next(e));
192
+ const answer = await asked(TurnEnd, { type: TurnEnd, turn: { ...turn, closedAt: now() } });
137
193
  const below = await next(e);
138
- return refuse === undefined ? below : { ...below, block: refuse };
194
+ return stopped(answer.refuse === undefined ? below : { ...below, block: answer.refuse });
139
195
  }),
140
196
  router.add('classic.Stop', async (e, next) => {
141
197
  if (!(await wants(TurnEnd)))
@@ -162,6 +218,8 @@ export function installBefore({ claude, router, hooks }) {
162
218
  return { dispose: () => added.forEach((each) => each.dispose()) };
163
219
  }
164
220
  export function withAnswer(change, answer) {
221
+ if (change.type === Message && answer.output !== undefined && change.message.type === ToolResult)
222
+ return { ...change, message: { ...change.message, output: answer.output } };
165
223
  if (change.type === Message && answer.text !== undefined)
166
224
  return { ...change, message: { ...change.message, text: answer.text } };
167
225
  if (change.type === Message && answer.input !== undefined && change.message.type === ToolCall)
@@ -170,18 +228,47 @@ export function withAnswer(change, answer) {
170
228
  return { ...change, task: { ...change.task, model: answer.owner.model } };
171
229
  return change;
172
230
  }
173
- export async function foldAnswers(event, change, askers, report) {
231
+ export const methodOf = (prefix, event) => `${prefix}${Object.entries(sessionEvents).find(([, each]) => each === event)?.[0] ?? event}`;
232
+ export const beforeAnswerKeys = {
233
+ prompt: ['text', 'note', 'title', 'refuse'],
234
+ note: ['text'],
235
+ skill: ['text'],
236
+ instructionFile: ['text', 'refuse'],
237
+ reply: ['text'],
238
+ toolCall: ['input', 'result', 'refuse'],
239
+ toolResult: ['output', 'isError', 'note'],
240
+ notice: ['text'],
241
+ taskStart: ['owner', 'refuse'],
242
+ turnEnd: ['refuse'],
243
+ contextEnd: ['refuse'],
244
+ };
245
+ export function foldBefore(event, change, askers, report) {
246
+ return foldAnswers(methodOf('before', event), beforeAnswerKeys[event], ['refuse', 'result'], change, askers, report, withAnswer);
247
+ }
248
+ export function takenAnswer(method, takes, answer) {
249
+ if (answer === undefined || answer === null || answer === false)
250
+ return undefined;
251
+ if (typeof answer !== 'object' || Array.isArray(answer))
252
+ throw new Error(`it answered ${JSON.stringify(answer)}, but ${method} takes an object or nothing: ${takes.map((key) => `{ ${key} }`).join(', ') || 'no answer'}.`);
253
+ const unread = Object.keys(answer).filter((key) => !takes.includes(key));
254
+ if (unread.length > 0)
255
+ throw new Error(`it answered ${unread.join(' and ')}, which ${method} does not take. It takes ${takes.map((key) => `{ ${key} }`).join(', ') || 'no answer'}.`);
256
+ return answer;
257
+ }
258
+ export async function foldAnswers(method, takes, ends, change, askers, report, applied = (same) => same) {
174
259
  let latest = change;
175
260
  let result;
176
261
  for (const ask of askers) {
177
262
  try {
178
- const answer = answerOf(event, await ask(latest));
179
- if (answer === undefined)
263
+ const answer = takenAnswer(method, takes, await ask(latest));
264
+ if (typeof answer !== 'object' || answer === null)
180
265
  continue;
181
- result = { ...result, ...answer };
182
- if (answer.refuse !== undefined || answer.result !== undefined)
266
+ const given = answer;
267
+ const notes = [result?.['note'], given['note']].filter((note) => typeof note === 'string');
268
+ result = { ...result, ...given, ...(notes.length === 0 ? {} : { note: notes.join('\n\n') }) };
269
+ if (ends.some((key) => given[key] !== undefined))
183
270
  return result;
184
- latest = withAnswer(latest, answer);
271
+ latest = applied(latest, answer);
185
272
  }
186
273
  catch (error) {
187
274
  report(error);
@@ -189,26 +276,3 @@ export async function foldAnswers(event, change, askers, report) {
189
276
  }
190
277
  return result;
191
278
  }
192
- export function answerOf(event, answer) {
193
- if (typeof answer !== 'object' || answer === null)
194
- return undefined;
195
- const given = answer;
196
- const allowed = {
197
- prompt: ['text', 'refuse'],
198
- note: ['text'],
199
- skill: ['text'],
200
- document: ['text', 'refuse'],
201
- reply: ['text'],
202
- toolCall: ['input', 'result', 'refuse'],
203
- toolResult: ['text'],
204
- notice: ['text'],
205
- taskStart: ['owner', 'refuse'],
206
- taskEnd: ['refuse'],
207
- turnEnd: ['refuse'],
208
- contextEnd: ['refuse'],
209
- };
210
- const unread = Object.keys(given).filter((key) => !allowed[event].includes(key));
211
- if (unread.length > 0)
212
- throw new Error(`a before(${event}) listener answered ${unread.join(' and ')}, which ${event} does not take. It takes ${allowed[event].map((key) => `{ ${key} }`).join(', ')}.`);
213
- return given;
214
- }
@@ -1,4 +1,4 @@
1
- import type { AgentInfo, AgentSpawnArgs, AgentSpawnResult, Args, EngineInterface, AskOptions, CommandSpec, ConfigRow, ElementTable, FsEntry, FsStat, FsStatOptions, HookStream, HttpInit, HttpResponse, InvalidatableEventName, PaneCloseArgs, PaneOpenArgs, ProcessRunInit, ProcessRunResult, ProcessSpawnChunk, ProcessSpawnRequest, ProcessSpawnResult, RenderSurface, ResolveInput, SessionUsage, Settings, SettingsReadArgs, Timer, ToolSpec, UiCopyArgs, UiFocusArgs, UiFocusResult, UiCopyResult, UiLogOptions, UiOpenResult, UiPane, UiScrollArgs, UiScrollResult } from 'claude-code';
1
+ import type { AgentInfo, AgentSpawnArgs, AgentSpawnResult, Args, EngineInterface, AskOptions, CommandInfo, CommandRunArgs, CommandRunResult, CommandSpec, ConfigRow, ElementTable, FsEntry, FsStat, FsStatOptions, HookStream, HttpInit, HttpResponse, InvalidatableEventName, PaneCloseArgs, PaneOpenArgs, ProcessRunInit, ProcessRunResult, ProcessSpawnChunk, ProcessSpawnRequest, ProcessSpawnResult, RenderSurface, ResolveInput, SessionUsage, Settings, SettingsReadArgs, Timer, ToolSpec, UiCopyArgs, UiFocusArgs, UiFocusResult, UiCopyResult, UiLogOptions, UiOpenResult, UiPane, UiScrollArgs, UiScrollResult } from 'claude-code';
2
2
  export type Claude = {
3
3
  readonly plugin: {
4
4
  readonly name: string;
@@ -63,6 +63,9 @@ export type Claude = {
63
63
  readonly prompt: {
64
64
  submit: EngineInterface['prompt']['submit'];
65
65
  };
66
+ readonly turn: {
67
+ abort: EngineInterface['turn']['abort'];
68
+ };
66
69
  readonly model: {
67
70
  complete: EngineInterface['model']['complete'];
68
71
  };
@@ -70,6 +73,8 @@ export type Claude = {
70
73
  register(command: CommandSpec): Promise<{
71
74
  command: string;
72
75
  }>;
76
+ list(): Promise<readonly CommandInfo[]>;
77
+ run(args: CommandRunArgs): Promise<CommandRunResult>;
73
78
  };
74
79
  readonly tool: {
75
80
  register(tool: ToolSpec): Promise<{
@@ -1 +1 @@
1
- {"version":3,"file":"claude.d.ts","sourceRoot":"","sources":["../src/runtime/claude.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"claude.d.ts","sourceRoot":"","sources":["../src/runtime/claude.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"conversations.d.ts","sourceRoot":"","sources":["../src/runtime/conversations.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AAY5D,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AAGzC,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AA2BzC,MAAM,MAAM,aAAa,GAAG;IAC1B,MAAM,CAAC,CAAC,EAAE,MAAM,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,GAAG;QAAE,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,WAAW,CAAC,WAAW,CAAC,GAAG,SAAS,CAAC,CAAA;IAClH,KAAK,IAAI,IAAI,CAAA;CACd,CAAA;AAED,wBAAgB,kBAAkB,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,EAAE;IAAE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,MAAM,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,KAAK,IAAI,CAAA;CAAE,GAAG,aAAa,CAugBlR"}
1
+ {"version":3,"file":"conversations.d.ts","sourceRoot":"","sources":["../src/runtime/conversations.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AAY5D,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AAGzC,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AA2BzC,MAAM,MAAM,aAAa,GAAG;IAC1B,MAAM,CAAC,CAAC,EAAE,MAAM,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,GAAG;QAAE,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,WAAW,CAAC,WAAW,CAAC,GAAG,SAAS,CAAC,CAAA;IAClH,KAAK,IAAI,IAAI,CAAA;CACd,CAAA;AAED,wBAAgB,kBAAkB,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,EAAE;IAAE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,MAAM,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,KAAK,IAAI,CAAA;CAAE,GAAG,aAAa,CA6iBlR"}
@@ -8,10 +8,10 @@ import { GoalChange } from '../session/goal/goal.js';
8
8
  import { receivedMatcher } from '../utils/matcher.js';
9
9
  import { ifMissing } from '../utils/paths.js';
10
10
  import { formatExit, messageOf } from '../utils/text.js';
11
- import { foldAnswers, installBefore, relayOf } from './before.js';
11
+ import { foldBefore, installBefore, relayOf } from './before.js';
12
12
  import { cmodPluginName } from './dependencies.js';
13
13
  import { readLines, runCmod } from './programs.js';
14
- import { actPermission, beforePermission, conversationMethods, everySession, isAct, isHeardBy, resultOf, usageOf } from './sessions.js';
14
+ import { actPermission, conversationMethods, everySession, isAct, isHeardBy, resultOf, spawnTask, usageOf } from './sessions.js';
15
15
  const bufferedTaskRows = 200;
16
16
  const beforeDeadlineMs = 5000;
17
17
  const livenessMs = 5000;
@@ -34,7 +34,23 @@ export function serveConversations({ claude, router, store, project, report }) {
34
34
  const liveIds = new Set();
35
35
  const waitingRows = new Map();
36
36
  let follower;
37
- const json = async (args) => JSON.parse(await runCmod(claude, [...args, '--json', '--project', project()], session === '' ? await claude.session.id() : session));
37
+ const engineTurns = new Map();
38
+ router.add('turn.start', async (e, next) => {
39
+ const agent = e.agentId ?? '';
40
+ engineTurns.set(agent, e.turnId);
41
+ return next(e);
42
+ });
43
+ const endTurn = (ending, task) => (below) => {
44
+ if (below.block !== undefined)
45
+ return below;
46
+ engineTurns.delete(task ?? '');
47
+ dispatch(session, conversation.endTurn(new Date().toISOString(), ending, task));
48
+ return below;
49
+ };
50
+ router.add('classic.Stop', async (e, next) => endTurn({ reason: 'answer', ...(e.last_assistant_message === undefined ? {} : { answer: e.last_assistant_message }) })(await next(e)));
51
+ router.add('classic.StopFailure', async (e, next) => endTurn({ reason: 'error', failure: { type: String(e.error), text: e.error_details ?? e.last_assistant_message ?? String(e.error) } })(await next(e)));
52
+ router.add('classic.SubagentStop', async (e, next) => endTurn({ reason: 'answer', ...(e.last_assistant_message === undefined ? {} : { answer: e.last_assistant_message }) }, e.agent_id)(await next(e)));
53
+ const json = async (args) => JSON.parse(await runCmod(claude, [...args, '--json', '--project', project()], await claude.session.id()));
38
54
  const isGranted = async (caller, item) => {
39
55
  if (caller === cmodPluginName)
40
56
  return true;
@@ -61,6 +77,8 @@ export function serveConversations({ claude, router, store, project, report }) {
61
77
  }
62
78
  };
63
79
  const restartFollower = async () => {
80
+ if (session === '')
81
+ return;
64
82
  const wantsEvery = [...heard.values()].some((sessions) => (sessions.get(everySession) ?? []).length > 0);
65
83
  const named = [...heard.values()].flatMap((sessions) => [...sessions].filter(([id, list]) => id !== session && id !== everySession && list.length > 0).map(([id]) => id));
66
84
  const others = [...new Set(named)].sort();
@@ -98,16 +116,39 @@ export function serveConversations({ claude, router, store, project, report }) {
98
116
  if (sent.isDelivered !== true)
99
117
  throw ended(to);
100
118
  };
119
+ const waitingOn = new Map();
120
+ let liveness;
121
+ let isChecking = false;
122
+ const checkLiveness = async () => {
123
+ if (isChecking)
124
+ return;
125
+ isChecking = true;
126
+ try {
127
+ const waiting = [...waitingOn];
128
+ const running = new Set((await json(['history', 'sessions', '--running', '--all'])).map((each) => each.id));
129
+ for (const [settle, to] of waiting)
130
+ if (!running.has(to))
131
+ settle.reject(ended(to));
132
+ }
133
+ finally {
134
+ isChecking = false;
135
+ }
136
+ };
137
+ const watchLife = (settle, to) => {
138
+ waitingOn.set(settle, to);
139
+ liveness ??= claude.clock.every(livenessMs, () => void checkLiveness().catch((error) => report('liveness', 'Could not check which sessions still run', error)));
140
+ return {
141
+ cancel() {
142
+ waitingOn.delete(settle);
143
+ if (waitingOn.size > 0)
144
+ return;
145
+ liveness?.cancel();
146
+ liveness = undefined;
147
+ },
148
+ };
149
+ };
101
150
  const ask = (to, relay, deadlineMs) => new Promise((resolve, reject) => {
102
151
  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
152
  const settle = {
112
153
  resolve(value) {
113
154
  if (asked.delete(id))
@@ -120,6 +161,7 @@ export function serveConversations({ claude, router, store, project, report }) {
120
161
  reject(error);
121
162
  },
122
163
  };
164
+ const timer = deadlineMs === undefined ? watchLife(settle, to) : claude.clock.after(deadlineMs, () => settle.reject(new Error(`session ${to} did not answer within ${deadlineMs / 1000} s`)));
123
165
  asked.set(id, settle);
124
166
  post(to, { ...relay, id, from: session }).catch((error) => settle.reject(error instanceof Error ? error : new Error(messageOf(error))));
125
167
  });
@@ -156,17 +198,16 @@ export function serveConversations({ claude, router, store, project, report }) {
156
198
  watching = readWatchers().catch((error) => report('before', 'Kept the other sessions’ before listeners from before', error));
157
199
  };
158
200
  const place = () => {
159
- const { context, contextOpenedAt, turn, tasks, taskTurns } = conversation.state;
160
- return { session, context, contextOpenedAt, ...(turn === undefined ? {} : { turn }), tasks, taskTurns };
201
+ const { context, contextOpenedAt, turn, tasks, taskTurns, openCalls } = conversation.state;
202
+ return { session, context, contextOpenedAt, ...(turn === undefined ? {} : { turn }), tasks, taskTurns, ...(openCalls === undefined ? {} : { openCalls }) };
161
203
  };
162
204
  const deciders = (event) => {
163
205
  const all = [...[...ownWatches.values()].filter((watch) => watch.target === session || watch.target === everySession).map((watch) => ({ ...watch, from: session })), ...watchers];
164
206
  return [...new Map(all.filter((watcher) => watcher.events.includes(event)).map((watcher) => [`${watcher.from}\n${watcher.mod}`, watcher])).values()];
165
207
  };
166
208
  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}.`);
209
+ if (from !== session && !(await isGranted(mod, 'conversation')))
210
+ throw new Error(`${mod} needs the conversation permission to change what happens in another session. Turn it on in /mods ${mod}.`);
170
211
  return claude.cmod.call({ to: mod, method: conversationMethods.before, input: { session: from, event, change, isInProject } });
171
212
  };
172
213
  const askAway = async (watcher, event, change) => {
@@ -194,7 +235,7 @@ export function serveConversations({ claude, router, store, project, report }) {
194
235
  await Promise.all([starting, watching]);
195
236
  return deciders(event).length > 0;
196
237
  },
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)),
238
+ decide: (event, change) => foldBefore(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
239
  },
199
240
  });
200
241
  const start = (id, source) => {
@@ -325,11 +366,13 @@ export function serveConversations({ claude, router, store, project, report }) {
325
366
  const perform = async (caller, action, input, task) => {
326
367
  await checkAct(caller, action, input);
327
368
  await starting;
369
+ if (action === 'abort')
370
+ return abortTurn(input, task);
328
371
  if (task !== undefined)
329
372
  return performInTask(action, input, task);
330
373
  if (action === 'usage')
331
374
  return usageOf(claude);
332
- if (action === 'clients')
375
+ if (action === 'surfaces')
333
376
  return [...(await claude.session.surfaces())];
334
377
  if (action === 'send') {
335
378
  const { type, text, as } = input;
@@ -342,27 +385,27 @@ export function serveConversations({ claude, router, store, project, report }) {
342
385
  throw new Error(`Claude Code dropped the prompt: ${delivered.drop}`);
343
386
  return null;
344
387
  }
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
- }
388
+ if (action === 'startTask')
389
+ return spawnTask(claude, input);
358
390
  const { name, input: toolInput } = input;
359
- return resultOf(place(), await claude.tool.call({ ...toolInput, tool: name }));
391
+ return resultOf(place(), { name, input: toolInput, cwd: await claude.session.cwd() }, await claude.tool.call({ ...toolInput, tool: name }));
392
+ };
393
+ const abortTurn = async (input, task) => {
394
+ const turnId = engineTurns.get(task ?? '');
395
+ if (turnId === undefined)
396
+ throw new Error(`no turn is running in ${task === undefined ? `session ${session}` : `task ${task}`}.`);
397
+ const asked = isObject(input) ? input['turn'] : undefined;
398
+ const open = task === undefined ? conversation.state.turn : conversation.state.taskTurns[task];
399
+ if (open?.prompt.id !== asked)
400
+ throw new Error(`turn ${String(asked)} has ended, so there is nothing to abort.`);
401
+ await claude.turn.abort({ turnId });
402
+ return null;
360
403
  };
361
404
  const performInTask = async (action, input, task) => {
362
405
  const running = conversation.state.tasks[task];
363
406
  if (action === 'usage')
364
407
  return json(['history', 'usage', session, '--task', task]);
365
- if (action === 'clients')
408
+ if (action === 'surfaces')
366
409
  return running === undefined || running.closedAt !== undefined ? [] : [...(await claude.session.surfaces())];
367
410
  if (action !== 'send')
368
411
  throw new Error(`cmod: ${action} runs only on a session's own main loop.`);
@@ -378,7 +421,7 @@ export function serveConversations({ claude, router, store, project, report }) {
378
421
  const isLive = await isRunning(target);
379
422
  if (!isLive && action === 'usage')
380
423
  return json(['history', 'usage', target, ...(task === undefined ? [] : ['--task', task])]);
381
- if (!isLive && action === 'clients')
424
+ if (!isLive && action === 'surfaces')
382
425
  return [];
383
426
  if (!isLive)
384
427
  throw ended(task ?? target);
@@ -424,8 +467,8 @@ export function serveConversations({ claude, router, store, project, report }) {
424
467
  },
425
468
  async [conversationMethods.search](request) {
426
469
  const before = isObject(request['before']) ? request['before'] : undefined;
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'])}`])]);
470
+ const record = [...textArg('--session', request['session']), ...textArg('--task', request['task']), ...textArg('--turn', request['turn']), ...countArg('--context', request['context']), ...(request['isAgent'] === true ? ['--agent'] : [])];
471
+ return json(['history', 'search', String(request['text']), ...scopeArgs(request), ...record, ...(request['running'] === true ? ['--running'] : []), ...(before === undefined ? [] : ['--before', `${String(before['session'])}:${String(before['id'])}`])]);
429
472
  },
430
473
  async [conversationMethods.messages](request) {
431
474
  const id = String(request['session']);
@@ -523,7 +566,7 @@ export function serveConversations({ claude, router, store, project, report }) {
523
566
  const task = typeof request['task'] === 'string' ? request['task'] : undefined;
524
567
  if (!isAct(action))
525
568
  throw new Error(`cmod takes no action named ${action}.`);
526
- if (target === session)
569
+ if (target === (await claude.session.id()))
527
570
  return perform(caller, action, request['input'], task);
528
571
  return actElsewhere(caller, target, action, request['input'], task);
529
572
  },
@@ -541,7 +584,8 @@ export function serveConversations({ claude, router, store, project, report }) {
541
584
  if (method === undefined)
542
585
  return undefined;
543
586
  const input = isObject(e.input) ? e.input : {};
544
- if (e.method !== conversationMethods.act && !(await isGranted(e.caller, 'conversation')))
587
+ const isDropping = (e.method === conversationMethods.subscribe || e.method === conversationMethods.watchBefore) && Array.isArray(input['subscriptions']) && input['subscriptions'].length === 0;
588
+ if (e.method !== conversationMethods.act && !isDropping && input['session'] !== (await claude.session.id()) && !(await isGranted(e.caller, 'conversation')))
545
589
  return { deny: `${e.caller ?? 'A mod'} reads your conversations without your grant. Turn it on in /mods ${e.caller ?? ''}.`.trim() };
546
590
  try {
547
591
  return { value: await method(input, e.caller) };
@@ -5,5 +5,22 @@ export type Deadline = {
5
5
  };
6
6
  export declare const longestMs = 600000;
7
7
  export declare const toolDeadline: Deadline;
8
+ export type ListenOptions = {
9
+ readonly timeoutMs?: number;
10
+ readonly onFailure?: 'pass' | 'refuse';
11
+ };
12
+ export type ListenTime = {
13
+ readonly deadline: number;
14
+ readonly signal: AbortSignal;
15
+ };
16
+ export declare const listenerMs = 10000;
17
+ export declare class PassedTimeout extends Error {
18
+ }
19
+ export declare function listenerOf(name: string, method: string, args: readonly unknown[]): {
20
+ readonly matcher: unknown;
21
+ readonly listener: (payload: never, time: ListenTime) => unknown;
22
+ readonly options: ListenOptions;
23
+ };
24
+ export declare function withinTime<Value>(clock: Pick<Claude['clock'], 'now'>, method: string, { timeoutMs }: ListenOptions, run: (time: ListenTime) => Value | Promise<Value>): Promise<Value>;
8
25
  export declare function beforeDeadline<Value>(claude: Claude, { ms, job }: Deadline, call: string, task: Promise<Value>): Promise<Value>;
9
26
  //# sourceMappingURL=deadline.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"deadline.d.ts","sourceRoot":"","sources":["../src/runtime/deadline.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AAEzC,MAAM,MAAM,QAAQ,GAAG;IAAE,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAA;CAAE,CAAA;AAErE,eAAO,MAAM,SAAS,SAAU,CAAA;AAEhC,eAAO,MAAM,YAAY,EAAE,QAAqC,CAAA;AAEhE,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,GAAG,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,CAAC,KAAK,CAAC,GAAG,OAAO,CAAC,KAAK,CAAC,CAe/H"}
1
+ {"version":3,"file":"deadline.d.ts","sourceRoot":"","sources":["../src/runtime/deadline.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AAEzC,MAAM,MAAM,QAAQ,GAAG;IAAE,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAA;CAAE,CAAA;AAErE,eAAO,MAAM,SAAS,SAAU,CAAA;AAEhC,eAAO,MAAM,YAAY,EAAE,QAAqC,CAAA;AAEhE,MAAM,MAAM,aAAa,GAAG;IAAE,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,QAAQ,CAAA;CAAE,CAAA;AAEnG,MAAM,MAAM,UAAU,GAAG;IAAE,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAA;CAAE,CAAA;AAEpF,eAAO,MAAM,UAAU,QAAS,CAAA;AAEhC,qBAAa,aAAc,SAAQ,KAAK;CAAG;AAI3C,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,OAAO,EAAE,GAAG;IAAE,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,QAAQ,EAAE,CAAC,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,UAAU,KAAK,OAAO,CAAC;IAAC,QAAQ,CAAC,OAAO,EAAE,aAAa,CAAA;CAAE,CASnN;AAED,wBAAsB,UAAU,CAAC,KAAK,EAAE,KAAK,EAAE,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,KAAK,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE,SAAsB,EAAE,EAAE,aAAa,EAAE,GAAG,EAAE,CAAC,IAAI,EAAE,UAAU,KAAK,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,GAAG,OAAO,CAAC,KAAK,CAAC,CAezM;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,GAAG,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,CAAC,KAAK,CAAC,GAAG,OAAO,CAAC,KAAK,CAAC,CAe/H"}