@cmodjs/core 0.3.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 (280) hide show
  1. package/docs/commands.md +61 -10
  2. package/docs/dependencies.md +16 -14
  3. package/docs/hooks.md +64 -29
  4. package/docs/index.md +29 -25
  5. package/docs/install-steps.md +13 -9
  6. package/docs/jobs.md +59 -111
  7. package/docs/mod.md +227 -175
  8. package/docs/options.md +2 -2
  9. package/docs/permissions.md +27 -16
  10. package/docs/session.md +311 -0
  11. package/docs/state.md +11 -7
  12. package/docs/testing.md +68 -23
  13. package/docs/ui.md +116 -76
  14. package/jobs/check.d.ts.map +1 -1
  15. package/jobs/check.js +5 -2
  16. package/jobs/context.d.ts +7 -8
  17. package/jobs/context.d.ts.map +1 -1
  18. package/jobs/context.js +23 -73
  19. package/jobs/permissions/decide-permission.d.ts +5 -3
  20. package/jobs/permissions/decide-permission.d.ts.map +1 -1
  21. package/jobs/permissions/decide-permission.js +6 -4
  22. package/jobs/permissions/find-project-scope.d.ts +1 -1
  23. package/jobs/permissions/find-project-scope.d.ts.map +1 -1
  24. package/jobs/permissions/find-project-scope.js +8 -11
  25. package/jobs/permissions/match-target.d.ts +2 -2
  26. package/jobs/permissions/match-target.d.ts.map +1 -1
  27. package/jobs/permissions.d.ts +1 -1
  28. package/jobs/permissions.d.ts.map +1 -1
  29. package/jobs/permissions.js +6 -4
  30. package/jobs/program.d.ts.map +1 -1
  31. package/jobs/program.js +14 -4
  32. package/jobs/prompt.d.ts +4 -2
  33. package/jobs/prompt.d.ts.map +1 -1
  34. package/jobs/prompt.js +16 -15
  35. package/jobs/schedule.d.ts +2 -2
  36. package/jobs/schedule.d.ts.map +1 -1
  37. package/jobs/schedule.js +9 -9
  38. package/jobs/slash-command.d.ts +4 -4
  39. package/jobs/slash-command.d.ts.map +1 -1
  40. package/jobs/slash-command.js +14 -9
  41. package/jobs/status-line.d.ts +1 -11
  42. package/jobs/status-line.d.ts.map +1 -1
  43. package/jobs/status-line.js +8 -12
  44. package/jobs/tool.d.ts +2 -2
  45. package/jobs/tool.d.ts.map +1 -1
  46. package/jobs/tool.js +15 -9
  47. package/mod.d.ts +178 -75
  48. package/mod.d.ts.map +1 -1
  49. package/mod.js +127 -3
  50. package/options.d.ts +2 -2
  51. package/options.js +19 -19
  52. package/package.json +3 -3
  53. package/records.d.ts +13 -8
  54. package/records.d.ts.map +1 -1
  55. package/records.js +33 -17
  56. package/register.d.ts.map +1 -1
  57. package/register.js +34 -5
  58. package/runtime/announce.d.ts +18 -0
  59. package/runtime/announce.d.ts.map +1 -0
  60. package/runtime/announce.js +68 -0
  61. package/runtime/before.d.ts +32 -0
  62. package/runtime/before.d.ts.map +1 -0
  63. package/runtime/before.js +214 -0
  64. package/runtime/bound.d.ts +4 -0
  65. package/runtime/bound.d.ts.map +1 -0
  66. package/runtime/bound.js +9 -0
  67. package/runtime/claude.d.ts +4 -2
  68. package/runtime/claude.d.ts.map +1 -1
  69. package/runtime/conversations.d.ts +17 -0
  70. package/runtime/conversations.d.ts.map +1 -0
  71. package/runtime/conversations.js +563 -0
  72. package/runtime/deadline.d.ts +0 -1
  73. package/runtime/deadline.d.ts.map +1 -1
  74. package/runtime/deadline.js +1 -1
  75. package/runtime/dependencies.d.ts +2 -0
  76. package/runtime/dependencies.d.ts.map +1 -1
  77. package/runtime/dependencies.js +10 -1
  78. package/runtime/engine.d.ts +13 -0
  79. package/runtime/engine.d.ts.map +1 -0
  80. package/runtime/engine.js +10 -0
  81. package/runtime/grants.d.ts +4 -0
  82. package/runtime/grants.d.ts.map +1 -1
  83. package/runtime/grants.js +68 -31
  84. package/runtime/hooks.d.ts +5 -4
  85. package/runtime/hooks.d.ts.map +1 -1
  86. package/runtime/hooks.js +29 -9
  87. package/runtime/installer.d.ts +1 -1
  88. package/runtime/installer.d.ts.map +1 -1
  89. package/runtime/installer.js +17 -14
  90. package/runtime/lifecycle.d.ts +3 -1
  91. package/runtime/lifecycle.d.ts.map +1 -1
  92. package/runtime/lifecycle.js +414 -212
  93. package/runtime/metadata.d.ts +3 -1
  94. package/runtime/metadata.d.ts.map +1 -1
  95. package/runtime/metadata.js +25 -14
  96. package/runtime/options.d.ts +3 -1
  97. package/runtime/options.d.ts.map +1 -1
  98. package/runtime/options.js +4 -4
  99. package/runtime/programs.d.ts +8 -0
  100. package/runtime/programs.d.ts.map +1 -1
  101. package/runtime/programs.js +39 -1
  102. package/runtime/router.d.ts +2 -1
  103. package/runtime/router.d.ts.map +1 -1
  104. package/runtime/router.js +11 -3
  105. package/runtime/sessions.d.ts +58 -0
  106. package/runtime/sessions.d.ts.map +1 -0
  107. package/runtime/sessions.js +433 -0
  108. package/runtime/state.d.ts +6 -2
  109. package/runtime/state.d.ts.map +1 -1
  110. package/runtime/state.js +46 -13
  111. package/runtime/toaster.d.ts +24 -0
  112. package/runtime/toaster.d.ts.map +1 -0
  113. package/runtime/toaster.js +286 -0
  114. package/runtime/ui.d.ts +6 -17
  115. package/runtime/ui.d.ts.map +1 -1
  116. package/runtime/ui.js +62 -118
  117. package/session/context/context.d.ts +12 -0
  118. package/session/context/context.d.ts.map +1 -0
  119. package/session/context/context.js +2 -0
  120. package/session/context/turn/message/document.d.ts +7 -0
  121. package/session/context/turn/message/document.d.ts.map +1 -0
  122. package/session/context/turn/message/document.js +1 -0
  123. package/session/context/turn/message/message.d.ts +55 -0
  124. package/session/context/turn/message/message.d.ts.map +1 -0
  125. package/session/context/turn/message/message.js +178 -0
  126. package/session/context/turn/message/note.d.ts +8 -0
  127. package/session/context/turn/message/note.d.ts.map +1 -0
  128. package/session/context/turn/message/note.js +1 -0
  129. package/session/context/turn/message/notice.d.ts +6 -0
  130. package/session/context/turn/message/notice.d.ts.map +1 -0
  131. package/session/context/turn/message/notice.js +1 -0
  132. package/session/context/turn/message/prompt.d.ts +8 -0
  133. package/session/context/turn/message/prompt.d.ts.map +1 -0
  134. package/session/context/turn/message/prompt.js +1 -0
  135. package/session/context/turn/message/reply.d.ts +6 -0
  136. package/session/context/turn/message/reply.d.ts.map +1 -0
  137. package/session/context/turn/message/reply.js +1 -0
  138. package/session/context/turn/message/sender.d.ts +14 -0
  139. package/session/context/turn/message/sender.d.ts.map +1 -0
  140. package/session/context/turn/message/sender.js +30 -0
  141. package/session/context/turn/message/skill.d.ts +7 -0
  142. package/session/context/turn/message/skill.d.ts.map +1 -0
  143. package/session/context/turn/message/skill.js +1 -0
  144. package/session/context/turn/message/tool-call.d.ts +9 -0
  145. package/session/context/turn/message/tool-call.d.ts.map +1 -0
  146. package/session/context/turn/message/tool-call.js +1 -0
  147. package/session/context/turn/message/tool-result.d.ts +8 -0
  148. package/session/context/turn/message/tool-result.d.ts.map +1 -0
  149. package/session/context/turn/message/tool-result.js +1 -0
  150. package/session/context/turn/turn.d.ts +13 -0
  151. package/session/context/turn/turn.d.ts.map +1 -0
  152. package/session/context/turn/turn.js +2 -0
  153. package/session/conversation.d.ts +73 -0
  154. package/session/conversation.d.ts.map +1 -0
  155. package/session/conversation.js +146 -0
  156. package/session/goal/goal.d.ts +11 -0
  157. package/session/goal/goal.d.ts.map +1 -0
  158. package/session/goal/goal.js +1 -0
  159. package/session/goal/task.d.ts +16 -0
  160. package/session/goal/task.d.ts.map +1 -0
  161. package/session/goal/task.js +3 -0
  162. package/session/session.d.ts +158 -0
  163. package/session/session.d.ts.map +1 -0
  164. package/session/session.js +2 -0
  165. package/src/jobs/check.ts +7 -4
  166. package/src/jobs/context.ts +24 -82
  167. package/src/jobs/permissions/decide-permission.ts +10 -8
  168. package/src/jobs/permissions/find-project-scope.ts +8 -14
  169. package/src/jobs/permissions/match-target.ts +3 -3
  170. package/src/jobs/permissions.ts +8 -6
  171. package/src/jobs/program.ts +13 -4
  172. package/src/jobs/prompt.ts +21 -24
  173. package/src/jobs/schedule.ts +14 -14
  174. package/src/jobs/slash-command.ts +20 -14
  175. package/src/jobs/status-line.ts +9 -21
  176. package/src/jobs/tool.ts +17 -11
  177. package/src/mod.ts +252 -97
  178. package/src/options.ts +22 -22
  179. package/src/records.ts +40 -23
  180. package/src/register.ts +34 -5
  181. package/src/runtime/announce.ts +76 -0
  182. package/src/runtime/before.ts +200 -0
  183. package/src/runtime/bound.ts +12 -0
  184. package/src/runtime/claude.ts +7 -2
  185. package/src/runtime/conversations.ts +579 -0
  186. package/src/runtime/deadline.ts +2 -2
  187. package/src/runtime/dependencies.ts +13 -1
  188. package/src/runtime/engine.ts +23 -0
  189. package/src/runtime/grants.ts +69 -29
  190. package/src/runtime/hooks.ts +39 -14
  191. package/src/runtime/installer.ts +16 -14
  192. package/src/runtime/lifecycle.ts +413 -201
  193. package/src/runtime/metadata.ts +37 -14
  194. package/src/runtime/options.ts +19 -4
  195. package/src/runtime/programs.ts +40 -1
  196. package/src/runtime/router.ts +11 -4
  197. package/src/runtime/sessions.ts +447 -0
  198. package/src/runtime/state.ts +50 -14
  199. package/src/runtime/toaster.ts +334 -0
  200. package/src/runtime/ui.ts +65 -143
  201. package/src/session/context/context.ts +14 -0
  202. package/src/session/context/turn/message/document.ts +8 -0
  203. package/src/session/context/turn/message/message.ts +203 -0
  204. package/src/session/context/turn/message/note.ts +9 -0
  205. package/src/session/context/turn/message/notice.ts +7 -0
  206. package/src/session/context/turn/message/prompt.ts +9 -0
  207. package/src/session/context/turn/message/reply.ts +7 -0
  208. package/src/session/context/turn/message/sender.ts +38 -0
  209. package/src/session/context/turn/message/skill.ts +8 -0
  210. package/src/session/context/turn/message/tool-call.ts +10 -0
  211. package/src/session/context/turn/message/tool-result.ts +9 -0
  212. package/src/session/context/turn/turn.ts +15 -0
  213. package/src/session/conversation.ts +194 -0
  214. package/src/session/goal/goal.ts +10 -0
  215. package/src/session/goal/task.ts +19 -0
  216. package/src/session/session.ts +141 -0
  217. package/src/testing/fake-claude.ts +25 -17
  218. package/src/testing/fake-elements.ts +3 -2
  219. package/src/testing/fake-files.ts +1 -1
  220. package/src/testing/fake-sessions.ts +277 -0
  221. package/src/testing.ts +138 -35
  222. package/src/ui/components.ts +3 -3
  223. package/src/ui/elements.ts +22 -8
  224. package/src/ui/markdown.ts +22 -26
  225. package/src/ui/places.ts +49 -0
  226. package/src/utils/call-effects.ts +5 -5
  227. package/src/utils/changelog.ts +32 -0
  228. package/src/utils/disposable.ts +3 -0
  229. package/src/utils/matcher.ts +37 -0
  230. package/src/utils/metadata.ts +17 -15
  231. package/src/utils/parse-shell.ts +9 -8
  232. package/src/utils/paths.ts +21 -4
  233. package/src/utils/text.ts +4 -0
  234. package/testing/fake-claude.d.ts +5 -1
  235. package/testing/fake-claude.d.ts.map +1 -1
  236. package/testing/fake-claude.js +21 -12
  237. package/testing/fake-elements.js +4 -2
  238. package/testing/fake-files.js +1 -1
  239. package/testing/fake-sessions.d.ts +81 -0
  240. package/testing/fake-sessions.d.ts.map +1 -0
  241. package/testing/fake-sessions.js +249 -0
  242. package/testing.d.ts +34 -13
  243. package/testing.d.ts.map +1 -1
  244. package/testing.js +115 -23
  245. package/ui/components.d.ts +2 -3
  246. package/ui/components.d.ts.map +1 -1
  247. package/ui/components.js +3 -3
  248. package/ui/elements.d.ts.map +1 -1
  249. package/ui/elements.js +19 -9
  250. package/ui/markdown.d.ts +35 -33
  251. package/ui/markdown.d.ts.map +1 -1
  252. package/ui/markdown.js +14 -16
  253. package/ui/places.d.ts +43 -0
  254. package/ui/places.d.ts.map +1 -0
  255. package/ui/places.js +31 -0
  256. package/utils/call-effects.d.ts +2 -1
  257. package/utils/call-effects.d.ts.map +1 -1
  258. package/utils/call-effects.js +3 -3
  259. package/utils/changelog.d.ts +9 -0
  260. package/utils/changelog.d.ts.map +1 -0
  261. package/utils/changelog.js +29 -0
  262. package/utils/disposable.d.ts +4 -0
  263. package/utils/disposable.d.ts.map +1 -0
  264. package/utils/disposable.js +1 -0
  265. package/utils/matcher.d.ts +13 -0
  266. package/utils/matcher.d.ts.map +1 -0
  267. package/utils/matcher.js +32 -0
  268. package/utils/metadata.d.ts +3 -3
  269. package/utils/metadata.js +18 -14
  270. package/utils/parse-shell.js +9 -7
  271. package/utils/paths.d.ts +4 -1
  272. package/utils/paths.d.ts.map +1 -1
  273. package/utils/paths.js +19 -4
  274. package/utils/text.d.ts +1 -0
  275. package/utils/text.d.ts.map +1 -1
  276. package/utils/text.js +3 -0
  277. package/src/ui/slots.ts +0 -36
  278. package/ui/slots.d.ts +0 -208
  279. package/ui/slots.d.ts.map +0 -1
  280. package/ui/slots.js +0 -19
@@ -0,0 +1,563 @@
1
+ import { consentPath, dataFolder, isObject, parseConsent } from '../records.js';
2
+ import { Message, rowOfAppend } from '../session/context/turn/message/message.js';
3
+ import { Note } from '../session/context/turn/message/note.js';
4
+ import { Person } from '../session/context/turn/message/sender.js';
5
+ import { createConversation, emptyConversation } from '../session/conversation.js';
6
+ import { Session, UsageChange } from '../session/session.js';
7
+ import { GoalChange } from '../session/goal/goal.js';
8
+ import { receivedMatcher } from '../utils/matcher.js';
9
+ import { ifMissing } from '../utils/paths.js';
10
+ import { formatExit, messageOf } from '../utils/text.js';
11
+ import { foldAnswers, installBefore, relayOf } from './before.js';
12
+ import { cmodPluginName } from './dependencies.js';
13
+ import { readLines, runCmod } from './programs.js';
14
+ import { actPermission, beforePermission, conversationMethods, everySession, isAct, isHeardBy, resultOf, usageOf } from './sessions.js';
15
+ const bufferedTaskRows = 200;
16
+ const beforeDeadlineMs = 5000;
17
+ const livenessMs = 5000;
18
+ export function serveConversations({ claude, router, store, project, report }) {
19
+ const folder = dataFolder(store, cmodPluginName);
20
+ const goals = `${folder}/goals`;
21
+ const watches = `${folder}/before`;
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();
29
+ let session = '';
30
+ let asks = 0;
31
+ let conversation = createConversation(emptyConversation(''));
32
+ let starting = Promise.resolve();
33
+ let live = [];
34
+ const liveIds = new Set();
35
+ const waitingRows = new Map();
36
+ let follower;
37
+ const json = async (args) => JSON.parse(await runCmod(claude, [...args, '--json', '--project', project()], session === '' ? await claude.session.id() : session));
38
+ const isGranted = async (caller, item) => {
39
+ if (caller === cmodPluginName)
40
+ return true;
41
+ if (caller === undefined)
42
+ return false;
43
+ const path = consentPath(store);
44
+ const text = await claude.fs.read(path).catch(ifMissing('{}'));
45
+ return (parseConsent(JSON.parse(text), path)[caller] ?? []).includes(item);
46
+ };
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) => {
50
+ for (const change of changes) {
51
+ if (change.type === Message && !liveIds.has(change.message.id) && to === session) {
52
+ live.push(change.message);
53
+ liveIds.add(change.message.id);
54
+ }
55
+ for (const [mod, sessions] of heard) {
56
+ const subscriptions = [...(sessions.get(to) ?? []), ...(isInProject ? (sessions.get(everySession) ?? []) : [])];
57
+ if (!subscriptions.some(({ event, matcher }) => isHeardBy(event, matcher, change)))
58
+ continue;
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));
60
+ }
61
+ }
62
+ };
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(' ');
69
+ if (follower?.sessions === wanted)
70
+ return;
71
+ follower?.stop();
72
+ follower = undefined;
73
+ if (args.length === 0)
74
+ return;
75
+ const stream = claude.process.spawn({ argv: ['cmod', 'history', 'follow', ...args, '--json', '--project', project()], env: { CLAUDE_CODE_SESSION_ID: session } });
76
+ const started = { sessions: wanted, stop: () => void stream.return(undefined) };
77
+ follower = started;
78
+ void readLines(stream, (line) => {
79
+ if (line.trim() === '')
80
+ return;
81
+ const parsed = JSON.parse(line);
82
+ dispatch(parsed.session, [parsed.change], parsed.isInProject);
83
+ })
84
+ .then(({ code, lastError }) => {
85
+ if (follower === started)
86
+ throw new Error(`cmod history follow ${formatExit(code, lastError)}`);
87
+ })
88
+ .catch((error) => {
89
+ if (follower !== started)
90
+ return;
91
+ follower = undefined;
92
+ report('follow', 'Stopped hearing the other sessions', error);
93
+ });
94
+ };
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 === '')
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 })));
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;
204
+ session = id;
205
+ live = [];
206
+ liveIds.clear();
207
+ waitingRows.clear();
208
+ conversation = createConversation(emptyConversation(id));
209
+ starting =
210
+ source === 'startup' || source === 'clear'
211
+ ? Promise.resolve(dispatch(id, conversation.startContext(new Date().toISOString())))
212
+ : json(['history', 'state', id]).then((state) => {
213
+ if (session === id)
214
+ conversation = createConversation({ ...emptyConversation(id), ...state });
215
+ }, (error) => report('state', `Starts session ${id} without its earlier turns`, error));
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));
221
+ };
222
+ const take = async (row, task) => {
223
+ await starting;
224
+ if (task !== undefined && conversation.state.tasks[task] === undefined) {
225
+ const waiting = waitingRows.get(task) ?? [];
226
+ waiting.push(row);
227
+ waitingRows.set(task, waiting);
228
+ if (waiting.length < bufferedTaskRows)
229
+ return;
230
+ waitingRows.delete(task);
231
+ dispatch(session, conversation.startTask({ id: task, title: '', agentType: 'general-purpose', openedBy: '', openedAt: row.at }));
232
+ for (const each of waiting)
233
+ dispatch(session, conversation.add(each, task));
234
+ return;
235
+ }
236
+ dispatch(session, conversation.add(row, task));
237
+ };
238
+ router.add('classic.SessionStart', async (e, next) => {
239
+ start(e.session_id, e.source);
240
+ return next(e);
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
+ });
247
+ router.add('session.append', async (e, next) => {
248
+ const kept = await next(e);
249
+ if (kept.deny !== undefined || session === '')
250
+ return kept;
251
+ const cwd = e.message.type === 'assistant' ? await claude.session.cwd() : '';
252
+ void take(rowOfAppend({ uuid: kept.uuid, door: e.door, origin: e.origin, message: kept.message }, new Date().toISOString(), cwd), e.agentId).catch((error) => report('append', 'Missed a message', error));
253
+ return kept;
254
+ });
255
+ router.add('session.compact', async (e, next) => {
256
+ const compacted = await next(e);
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));
262
+ return compacted;
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
+ });
270
+ router.add('agent.spawn', async (e, next) => {
271
+ const spawned = await next(e);
272
+ if (spawned.deny !== undefined || spawned.agentId === undefined)
273
+ return spawned;
274
+ const id = spawned.agentId;
275
+ const opened = { id, title: e.description, agentType: e.subagentType, model: spawned.model, openedBy: e.tool_use_id, openedAt: new Date().toISOString(), ...(e.parentAgentId === undefined ? {} : { parent: e.parentAgentId }) };
276
+ await starting;
277
+ dispatch(session, conversation.startTask(opened));
278
+ const waiting = waitingRows.get(id) ?? [];
279
+ waitingRows.delete(id);
280
+ for (const row of waiting)
281
+ dispatch(session, conversation.add(row, id));
282
+ return spawned;
283
+ });
284
+ router.add('session.receive', async (e, next) => {
285
+ const sent = relayOf(e);
286
+ if (sent === undefined)
287
+ return next(e);
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' };
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
+ };
393
+ const merged = (saved, kept, key) => {
394
+ const keptKeys = new Set(kept.map(key));
395
+ return [...saved.filter((item) => !keptKeys.has(key(item))), ...kept];
396
+ };
397
+ const liveTurns = (task) => {
398
+ const turn = typeof task === 'string' ? conversation.state.taskTurns[task] : conversation.state.turn;
399
+ return turn === undefined ? [] : [turn];
400
+ };
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
+ }
416
+ const { context, contextOpenedAt } = conversation.state;
417
+ return context === 0 ? [] : [{ number: context, openedAt: contextOpenedAt }];
418
+ };
419
+ const lastOf = (items, last) => (typeof last === 'number' ? items.slice(-last) : [...items]);
420
+ const methods = {
421
+ async [conversationMethods.sessions](request) {
422
+ const found = await json(['history', 'sessions', ...scopeArgs(request), ...(request['running'] === true ? ['--running'] : [])]);
423
+ return found.map((each) => (each.id === session ? { ...each, isRunning: true } : each));
424
+ },
425
+ async [conversationMethods.search](request) {
426
+ 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'])}`])]);
429
+ },
430
+ async [conversationMethods.messages](request) {
431
+ const id = String(request['session']);
432
+ const filters = [...textArg('--turn', request['turn']), ...textArg('--task', request['task']), ...textArg('--after', request['after'])];
433
+ const saved = await json(['history', 'messages', id, ...filters, ...countArg('--last', request['last'])]);
434
+ if (id !== session)
435
+ return saved;
436
+ await starting;
437
+ const fits = (message) => (request['turn'] === undefined || message.turn === request['turn']) && message.task === request['task'];
438
+ const after = typeof request['after'] === 'string' ? live.findIndex((message) => message.id === request['after']) : -1;
439
+ const kept = live.slice(after + 1).filter(fits);
440
+ return lastOf(merged(saved, kept, (message) => message.id), request['last']);
441
+ },
442
+ async [conversationMethods.turns](request) {
443
+ const id = String(request['session']);
444
+ const saved = await json(['history', 'turns', id, ...textArg('--task', request['task']), ...countArg('--context', request['context']), ...countArg('--last', request['last'])]);
445
+ if (id !== session)
446
+ return saved;
447
+ await starting;
448
+ const kept = liveTurns(request['task']).filter((turn) => request['context'] === undefined || turn.context === request['context']);
449
+ return lastOf(merged(saved, kept, (turn) => turn.prompt.id), request['last']);
450
+ },
451
+ async [conversationMethods.contexts](request) {
452
+ const id = String(request['session']);
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
+ });
458
+ if (id !== session)
459
+ return saved;
460
+ await starting;
461
+ return merged(saved, liveContext(request['task']), (context) => String(context.number));
462
+ },
463
+ async [conversationMethods.tasks](request) {
464
+ const id = String(request['session']);
465
+ const isNested = request['all'] === true;
466
+ const saved = await json(['history', 'tasks', id, ...(isNested ? ['--nested'] : textArg('--task', request['task']))]);
467
+ if (id !== session)
468
+ return saved;
469
+ await starting;
470
+ const kept = liveTasks().filter((task) => isNested || task.parent === request['task']);
471
+ return merged(saved, kept, (task) => task.id);
472
+ },
473
+ async [conversationMethods.goal](request) {
474
+ const id = String(request['session']);
475
+ const task = typeof request['task'] === 'string' ? request['task'] : undefined;
476
+ if (id === session && task === undefined) {
477
+ await starting;
478
+ const text = conversation.state.goal;
479
+ if (text !== undefined)
480
+ return { text };
481
+ }
482
+ return json(['history', 'goal', id, ...textArg('--task', task)]);
483
+ },
484
+ async [conversationMethods.updateGoal](request) {
485
+ const id = String(request['session']);
486
+ const task = typeof request['task'] === 'string' ? request['task'] : undefined;
487
+ const text = String(request['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' }));
495
+ }
496
+ return null;
497
+ },
498
+ async [conversationMethods.subscribe](request, caller) {
499
+ if (caller === undefined)
500
+ throw new Error('cmod hears no caller for this subscription.');
501
+ const id = String(request['session']);
502
+ const list = Array.isArray(request['subscriptions']) ? request['subscriptions'] : [];
503
+ const sessions = heard.get(caller) ?? new Map();
504
+ sessions.set(id, list.map(({ event, matcher }) => ({ event, matcher: matcher === undefined ? undefined : receivedMatcher(matcher) })));
505
+ heard.set(caller, sessions);
506
+ follow();
507
+ return null;
508
+ },
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();
516
+ return null;
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
+ },
530
+ };
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
+ },
539
+ async answer(e) {
540
+ const method = methods[e.method];
541
+ if (method === undefined)
542
+ return undefined;
543
+ const input = isObject(e.input) ? e.input : {};
544
+ if (e.method !== conversationMethods.act && !(await isGranted(e.caller, 'conversation')))
545
+ return { deny: `${e.caller ?? 'A mod'} reads your conversations without your grant. Turn it on in /mods ${e.caller ?? ''}.`.trim() };
546
+ try {
547
+ return { value: await method(input, e.caller) };
548
+ }
549
+ catch (error) {
550
+ return { deny: messageOf(error) };
551
+ }
552
+ },
553
+ };
554
+ }
555
+ function scopeArgs(request) {
556
+ return [...(typeof request['since'] === 'string' ? ['--since', request['since']] : []), ...(request['everyProject'] === true ? ['--all'] : [])];
557
+ }
558
+ function countArg(flag, value) {
559
+ return typeof value === 'number' ? [flag, String(value)] : [];
560
+ }
561
+ function textArg(flag, value) {
562
+ return typeof value === 'string' ? [flag, value] : [];
563
+ }
@@ -2,7 +2,6 @@ import type { Claude } from './claude.js';
2
2
  export type Deadline = {
3
3
  readonly ms: number;
4
4
  readonly job?: string;
5
- readonly longestMs?: number;
6
5
  };
7
6
  export declare const longestMs = 600000;
8
7
  export declare const toolDeadline: Deadline;
@@ -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,CAAC;IAAC,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,CAAA;AAElG,eAAO,MAAM,SAAS,SAAU,CAAA;AAEhC,eAAO,MAAM,YAAY,EAAE,QAAgD,CAAA;AAE3E,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,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,5 +1,5 @@
1
1
  export const longestMs = 600_000;
2
- export const toolDeadline = { ms: 30000, longestMs, job: 'tool' };
2
+ export const toolDeadline = { ms: 30000, job: 'tool' };
3
3
  export function beforeDeadline(claude, { ms, job }, call, task) {
4
4
  return new Promise((resolve, reject) => {
5
5
  const passed = job === undefined ? `its ${ms / 1000} s deadline` : `the ${ms / 1000} s deadline of ${job}`;
@@ -1,8 +1,10 @@
1
1
  import type { Args, CmodDependencies, EventResult, Frozen } from 'claude-code';
2
2
  import type { Claude } from './claude.js';
3
+ export declare const cmodPluginName = "cmod";
3
4
  export declare function answerCall(name: string, api: {
4
5
  readonly [method: string]: (input: never) => unknown;
5
6
  }, e: Frozen<Args<'cmod.call'>>): Promise<EventResult<'cmod.call'>>;
6
7
  export declare function dependencyCalls(claude: Claude, within: (call: string, task: Promise<unknown>) => Promise<unknown>): CmodDependencies;
8
+ export declare function withBareRefusals(claude: Claude): Claude;
7
9
  export declare function notInstalled(name: string): string;
8
10
  //# sourceMappingURL=dependencies.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"dependencies.d.ts","sourceRoot":"","sources":["../src/runtime/dependencies.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,gBAAgB,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AAE9E,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AAEzC,wBAAsB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE;IAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,GAAG,CAAC,KAAK,EAAE,KAAK,KAAK,OAAO,CAAA;CAAE,EAAE,CAAC,EAAE,MAAM,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,GAAG,OAAO,CAAC,WAAW,CAAC,WAAW,CAAC,CAAC,CAQ7K;AAED,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,CAAC,OAAO,CAAC,KAAK,OAAO,CAAC,OAAO,CAAC,GAAG,gBAAgB,CAOpI;AAED,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEjD"}
1
+ {"version":3,"file":"dependencies.d.ts","sourceRoot":"","sources":["../src/runtime/dependencies.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,gBAAgB,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AAE9E,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AAEzC,eAAO,MAAM,cAAc,SAAS,CAAA;AAEpC,wBAAsB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE;IAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,GAAG,CAAC,KAAK,EAAE,KAAK,KAAK,OAAO,CAAA;CAAE,EAAE,CAAC,EAAE,MAAM,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,GAAG,OAAO,CAAC,WAAW,CAAC,WAAW,CAAC,CAAC,CAQ7K;AAED,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,CAAC,OAAO,CAAC,KAAK,OAAO,CAAC,OAAO,CAAC,GAAG,gBAAgB,CAOpI;AAED,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAQvD;AAED,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEjD"}
@@ -1,4 +1,5 @@
1
1
  import { messageOf } from '../utils/text.js';
2
+ export const cmodPluginName = 'cmod';
2
3
  export async function answerCall(name, api, e) {
3
4
  const method = Object.hasOwn(api, e.method) ? api[e.method] : undefined;
4
5
  if (method === undefined)
@@ -14,6 +15,14 @@ export function dependencyCalls(claude, within) {
14
15
  const methodsOf = (to) => new Proxy({}, { get: (_methods, method) => (typeof method === 'string' && method !== 'then' ? (input) => within(`mod.dependencies.${to}.${method}`, claude.cmod.call({ to, method, input })) : undefined) });
15
16
  return new Proxy({}, { get: (_dependencies, to) => (typeof to === 'string' && to !== 'then' ? methodsOf(to) : undefined) });
16
17
  }
18
+ export function withBareRefusals(claude) {
19
+ const prefix = `${claude.plugin.name}: $.cmod.call: `;
20
+ const call = (input) => claude.cmod.call(input).catch((error) => {
21
+ const message = messageOf(error);
22
+ throw message.startsWith(prefix) ? new Error(message.slice(prefix.length), { cause: error }) : error;
23
+ });
24
+ return { ...claude, cmod: { call } };
25
+ }
17
26
  export function notInstalled(name) {
18
- return `${name} is not installed. Run cmod install ${name}.`;
27
+ return `${name} is not installed. Add it with /plugin.`;
19
28
  }
@@ -0,0 +1,13 @@
1
+ import type { Claude } from './claude.js';
2
+ import type { Router } from './router.js';
3
+ import type { Failure } from './toaster.js';
4
+ import type { ToolCalls } from './tool-calls.js';
5
+ export type Engine = {
6
+ readonly claude: Claude;
7
+ readonly router: Router;
8
+ readonly toolCalls: ToolCalls;
9
+ readonly report: (failure: Failure) => void;
10
+ };
11
+ export declare function keepEngine(mod: object, engine: Engine): void;
12
+ export declare function engineOf(mod: object): Engine;
13
+ //# sourceMappingURL=engine.d.ts.map
@@ -0,0 +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,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"}
@@ -0,0 +1,10 @@
1
+ const engines = new WeakMap();
2
+ export function keepEngine(mod, engine) {
3
+ engines.set(mod, engine);
4
+ }
5
+ export function engineOf(mod) {
6
+ const engine = engines.get(mod);
7
+ if (engine === undefined)
8
+ throw new Error('This mod has not started, so it reaches no Claude Code calls yet.');
9
+ return engine;
10
+ }