@cmodjs/core 0.2.3 → 1.0.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 (273) hide show
  1. package/docs/commands.md +58 -10
  2. package/docs/dependencies.md +12 -12
  3. package/docs/hooks.md +41 -28
  4. package/docs/index.md +29 -25
  5. package/docs/install-steps.md +9 -7
  6. package/docs/jobs.md +59 -111
  7. package/docs/mod.md +200 -174
  8. package/docs/options.md +2 -2
  9. package/docs/permissions.md +27 -16
  10. package/docs/session.md +248 -0
  11. package/docs/state.md +11 -7
  12. package/docs/testing.md +66 -23
  13. package/docs/ui.md +108 -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 +8 -3
  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 +154 -75
  48. package/mod.d.ts.map +1 -1
  49. package/mod.js +94 -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 +10 -8
  54. package/records.d.ts.map +1 -1
  55. package/records.js +28 -17
  56. package/register.d.ts.map +1 -1
  57. package/register.js +18 -6
  58. package/runtime/announce.d.ts +18 -0
  59. package/runtime/announce.d.ts.map +1 -0
  60. package/runtime/announce.js +66 -0
  61. package/runtime/bound.d.ts +4 -0
  62. package/runtime/bound.d.ts.map +1 -0
  63. package/runtime/bound.js +9 -0
  64. package/runtime/claude.d.ts +3 -3
  65. package/runtime/claude.d.ts.map +1 -1
  66. package/runtime/conversations.d.ts +16 -0
  67. package/runtime/conversations.d.ts.map +1 -0
  68. package/runtime/conversations.js +291 -0
  69. package/runtime/deadline.d.ts +0 -1
  70. package/runtime/deadline.d.ts.map +1 -1
  71. package/runtime/deadline.js +1 -1
  72. package/runtime/dependencies.d.ts +2 -0
  73. package/runtime/dependencies.d.ts.map +1 -1
  74. package/runtime/dependencies.js +9 -0
  75. package/runtime/engine.d.ts +17 -0
  76. package/runtime/engine.d.ts.map +1 -0
  77. package/runtime/engine.js +10 -0
  78. package/runtime/grants.d.ts +4 -0
  79. package/runtime/grants.d.ts.map +1 -1
  80. package/runtime/grants.js +63 -33
  81. package/runtime/hooks.d.ts +5 -4
  82. package/runtime/hooks.d.ts.map +1 -1
  83. package/runtime/hooks.js +10 -5
  84. package/runtime/installer.d.ts.map +1 -1
  85. package/runtime/installer.js +11 -9
  86. package/runtime/lifecycle.d.ts +3 -1
  87. package/runtime/lifecycle.d.ts.map +1 -1
  88. package/runtime/lifecycle.js +317 -186
  89. package/runtime/metadata.d.ts +3 -1
  90. package/runtime/metadata.d.ts.map +1 -1
  91. package/runtime/metadata.js +21 -11
  92. package/runtime/options.d.ts +3 -1
  93. package/runtime/options.d.ts.map +1 -1
  94. package/runtime/options.js +4 -4
  95. package/runtime/programs.d.ts +8 -0
  96. package/runtime/programs.d.ts.map +1 -1
  97. package/runtime/programs.js +39 -1
  98. package/runtime/router.d.ts +2 -1
  99. package/runtime/router.d.ts.map +1 -1
  100. package/runtime/router.js +11 -3
  101. package/runtime/sessions.d.ts +36 -0
  102. package/runtime/sessions.d.ts.map +1 -0
  103. package/runtime/sessions.js +235 -0
  104. package/runtime/state.d.ts +6 -2
  105. package/runtime/state.d.ts.map +1 -1
  106. package/runtime/state.js +46 -13
  107. package/runtime/toaster.d.ts +31 -0
  108. package/runtime/toaster.d.ts.map +1 -0
  109. package/runtime/toaster.js +176 -0
  110. package/runtime/ui.d.ts +6 -17
  111. package/runtime/ui.d.ts.map +1 -1
  112. package/runtime/ui.js +36 -112
  113. package/session/context/context.d.ts +10 -0
  114. package/session/context/context.d.ts.map +1 -0
  115. package/session/context/context.js +2 -0
  116. package/session/context/turn/message/document.d.ts +7 -0
  117. package/session/context/turn/message/document.d.ts.map +1 -0
  118. package/session/context/turn/message/document.js +1 -0
  119. package/session/context/turn/message/message.d.ts +54 -0
  120. package/session/context/turn/message/message.d.ts.map +1 -0
  121. package/session/context/turn/message/message.js +137 -0
  122. package/session/context/turn/message/note.d.ts +8 -0
  123. package/session/context/turn/message/note.d.ts.map +1 -0
  124. package/session/context/turn/message/note.js +1 -0
  125. package/session/context/turn/message/notice.d.ts +6 -0
  126. package/session/context/turn/message/notice.d.ts.map +1 -0
  127. package/session/context/turn/message/notice.js +1 -0
  128. package/session/context/turn/message/prompt.d.ts +8 -0
  129. package/session/context/turn/message/prompt.d.ts.map +1 -0
  130. package/session/context/turn/message/prompt.js +1 -0
  131. package/session/context/turn/message/reply.d.ts +6 -0
  132. package/session/context/turn/message/reply.d.ts.map +1 -0
  133. package/session/context/turn/message/reply.js +1 -0
  134. package/session/context/turn/message/sender.d.ts +14 -0
  135. package/session/context/turn/message/sender.d.ts.map +1 -0
  136. package/session/context/turn/message/sender.js +30 -0
  137. package/session/context/turn/message/skill.d.ts +7 -0
  138. package/session/context/turn/message/skill.d.ts.map +1 -0
  139. package/session/context/turn/message/skill.js +1 -0
  140. package/session/context/turn/message/tool-call.d.ts +9 -0
  141. package/session/context/turn/message/tool-call.d.ts.map +1 -0
  142. package/session/context/turn/message/tool-call.js +1 -0
  143. package/session/context/turn/message/tool-result.d.ts +8 -0
  144. package/session/context/turn/message/tool-result.d.ts.map +1 -0
  145. package/session/context/turn/message/tool-result.js +1 -0
  146. package/session/context/turn/turn.d.ts +12 -0
  147. package/session/context/turn/turn.d.ts.map +1 -0
  148. package/session/context/turn/turn.js +2 -0
  149. package/session/conversation.d.ts +64 -0
  150. package/session/conversation.d.ts.map +1 -0
  151. package/session/conversation.js +123 -0
  152. package/session/goal/goal.d.ts +10 -0
  153. package/session/goal/goal.d.ts.map +1 -0
  154. package/session/goal/goal.js +1 -0
  155. package/session/goal/task.d.ts +16 -0
  156. package/session/goal/task.d.ts.map +1 -0
  157. package/session/goal/task.js +3 -0
  158. package/session/session.d.ts +99 -0
  159. package/session/session.d.ts.map +1 -0
  160. package/session/session.js +1 -0
  161. package/src/jobs/check.ts +7 -4
  162. package/src/jobs/context.ts +24 -82
  163. package/src/jobs/permissions/decide-permission.ts +10 -8
  164. package/src/jobs/permissions/find-project-scope.ts +8 -14
  165. package/src/jobs/permissions/match-target.ts +3 -3
  166. package/src/jobs/permissions.ts +8 -6
  167. package/src/jobs/program.ts +7 -3
  168. package/src/jobs/prompt.ts +21 -24
  169. package/src/jobs/schedule.ts +14 -14
  170. package/src/jobs/slash-command.ts +20 -14
  171. package/src/jobs/status-line.ts +9 -21
  172. package/src/jobs/tool.ts +17 -11
  173. package/src/mod.ts +209 -97
  174. package/src/options.ts +22 -22
  175. package/src/records.ts +35 -23
  176. package/src/register.ts +18 -6
  177. package/src/runtime/announce.ts +74 -0
  178. package/src/runtime/bound.ts +12 -0
  179. package/src/runtime/claude.ts +5 -3
  180. package/src/runtime/conversations.ts +302 -0
  181. package/src/runtime/deadline.ts +2 -2
  182. package/src/runtime/dependencies.ts +12 -0
  183. package/src/runtime/engine.ts +22 -0
  184. package/src/runtime/grants.ts +66 -31
  185. package/src/runtime/hooks.ts +19 -10
  186. package/src/runtime/installer.ts +10 -9
  187. package/src/runtime/lifecycle.ts +313 -181
  188. package/src/runtime/metadata.ts +33 -11
  189. package/src/runtime/options.ts +19 -4
  190. package/src/runtime/programs.ts +40 -1
  191. package/src/runtime/router.ts +11 -4
  192. package/src/runtime/sessions.ts +242 -0
  193. package/src/runtime/state.ts +50 -14
  194. package/src/runtime/toaster.ts +218 -0
  195. package/src/runtime/ui.ts +42 -139
  196. package/src/session/context/context.ts +12 -0
  197. package/src/session/context/turn/message/document.ts +8 -0
  198. package/src/session/context/turn/message/message.ts +165 -0
  199. package/src/session/context/turn/message/note.ts +9 -0
  200. package/src/session/context/turn/message/notice.ts +7 -0
  201. package/src/session/context/turn/message/prompt.ts +9 -0
  202. package/src/session/context/turn/message/reply.ts +7 -0
  203. package/src/session/context/turn/message/sender.ts +38 -0
  204. package/src/session/context/turn/message/skill.ts +8 -0
  205. package/src/session/context/turn/message/tool-call.ts +10 -0
  206. package/src/session/context/turn/message/tool-result.ts +9 -0
  207. package/src/session/context/turn/turn.ts +14 -0
  208. package/src/session/conversation.ts +169 -0
  209. package/src/session/goal/goal.ts +9 -0
  210. package/src/session/goal/task.ts +19 -0
  211. package/src/session/session.ts +94 -0
  212. package/src/testing/fake-claude.ts +23 -18
  213. package/src/testing/fake-elements.ts +3 -2
  214. package/src/testing/fake-files.ts +1 -1
  215. package/src/testing/fake-sessions.ts +203 -0
  216. package/src/testing.ts +112 -33
  217. package/src/ui/components.ts +3 -3
  218. package/src/ui/elements.ts +22 -8
  219. package/src/ui/markdown.ts +22 -26
  220. package/src/ui/places.ts +49 -0
  221. package/src/utils/call-effects.ts +3 -3
  222. package/src/utils/changelog.ts +32 -0
  223. package/src/utils/matcher.ts +37 -0
  224. package/src/utils/metadata.ts +17 -15
  225. package/src/utils/parse-shell.ts +24 -16
  226. package/src/utils/paths.ts +15 -1
  227. package/src/utils/text.ts +4 -0
  228. package/testing/fake-claude.d.ts +4 -2
  229. package/testing/fake-claude.d.ts.map +1 -1
  230. package/testing/fake-claude.js +18 -12
  231. package/testing/fake-elements.js +4 -2
  232. package/testing/fake-files.js +1 -1
  233. package/testing/fake-sessions.d.ts +74 -0
  234. package/testing/fake-sessions.d.ts.map +1 -0
  235. package/testing/fake-sessions.js +170 -0
  236. package/testing.d.ts +33 -13
  237. package/testing.d.ts.map +1 -1
  238. package/testing.js +92 -21
  239. package/ui/components.d.ts +2 -3
  240. package/ui/components.d.ts.map +1 -1
  241. package/ui/components.js +3 -3
  242. package/ui/elements.d.ts.map +1 -1
  243. package/ui/elements.js +19 -9
  244. package/ui/markdown.d.ts +35 -33
  245. package/ui/markdown.d.ts.map +1 -1
  246. package/ui/markdown.js +14 -16
  247. package/ui/places.d.ts +43 -0
  248. package/ui/places.d.ts.map +1 -0
  249. package/ui/places.js +31 -0
  250. package/utils/call-effects.d.ts +1 -1
  251. package/utils/call-effects.d.ts.map +1 -1
  252. package/utils/call-effects.js +2 -2
  253. package/utils/changelog.d.ts +9 -0
  254. package/utils/changelog.d.ts.map +1 -0
  255. package/utils/changelog.js +29 -0
  256. package/utils/matcher.d.ts +13 -0
  257. package/utils/matcher.d.ts.map +1 -0
  258. package/utils/matcher.js +32 -0
  259. package/utils/metadata.d.ts +3 -3
  260. package/utils/metadata.js +18 -14
  261. package/utils/parse-shell.d.ts +1 -0
  262. package/utils/parse-shell.d.ts.map +1 -1
  263. package/utils/parse-shell.js +22 -13
  264. package/utils/paths.d.ts +2 -0
  265. package/utils/paths.d.ts.map +1 -1
  266. package/utils/paths.js +13 -1
  267. package/utils/text.d.ts +1 -0
  268. package/utils/text.d.ts.map +1 -1
  269. package/utils/text.js +3 -0
  270. package/src/ui/slots.ts +0 -36
  271. package/ui/slots.d.ts +0 -208
  272. package/ui/slots.d.ts.map +0 -1
  273. package/ui/slots.js +0 -19
@@ -0,0 +1,291 @@
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 { Prompt } from '../session/context/turn/message/prompt.js';
5
+ import { Person } from '../session/context/turn/message/sender.js';
6
+ import { createConversation, emptyConversation } from '../session/conversation.js';
7
+ import { receivedMatcher } from '../utils/matcher.js';
8
+ import { ifMissing } from '../utils/paths.js';
9
+ import { formatExit, messageOf } from '../utils/text.js';
10
+ import { cmodPluginName } from './dependencies.js';
11
+ import { readLines, runCmod } from './programs.js';
12
+ import { conversationMethods, isHeardBy } from './sessions.js';
13
+ const delivery = /^<cmod-delivery>([\s\S]*)<\/cmod-delivery>$/;
14
+ const bufferedTaskRows = 200;
15
+ export function serveConversations({ claude, router, store, project, report }) {
16
+ const goals = `${dataFolder(store, cmodPluginName)}/goals`;
17
+ const heard = new Map();
18
+ let session = '';
19
+ let conversation = createConversation(emptyConversation(''));
20
+ let starting = Promise.resolve();
21
+ let live = [];
22
+ const liveIds = new Set();
23
+ const waitingRows = new Map();
24
+ let follower;
25
+ const json = async (args) => JSON.parse(await runCmod(claude, [...args, '--json', '--project', project()], session === '' ? await claude.session.id() : session));
26
+ const isGranted = async (caller, item) => {
27
+ if (caller === cmodPluginName)
28
+ return true;
29
+ if (caller === undefined)
30
+ return false;
31
+ const path = consentPath(store);
32
+ const text = await claude.fs.read(path).catch(ifMissing('{}'));
33
+ return (parseConsent(JSON.parse(text), path)[caller] ?? []).includes(item);
34
+ };
35
+ const dispatch = (to, changes) => {
36
+ for (const change of changes) {
37
+ if (change.type === Message && !liveIds.has(change.message.id) && to === session) {
38
+ live.push(change.message);
39
+ liveIds.add(change.message.id);
40
+ }
41
+ for (const [mod, sessions] of heard) {
42
+ const subscriptions = sessions.get(to) ?? [];
43
+ if (!subscriptions.some(({ event, matcher }) => isHeardBy(event, matcher, change)))
44
+ continue;
45
+ void claude.cmod.call({ to: mod, method: conversationMethods.changed, input: { session: to, change } }).catch((error) => report(`changed:${mod}`, `Could not tell ${mod} about session ${to}`, error));
46
+ }
47
+ }
48
+ };
49
+ const restartFollower = () => {
50
+ const others = [...new Set([...heard.values()].flatMap((sessions) => [...sessions].filter(([id, list]) => id !== session && list.length > 0).map(([id]) => id)))].sort();
51
+ const wanted = others.join(' ');
52
+ if (follower?.sessions === wanted)
53
+ return;
54
+ follower?.stop();
55
+ follower = undefined;
56
+ if (others.length === 0)
57
+ return;
58
+ const stream = claude.process.spawn({ argv: ['cmod', 'history', 'follow', ...others, '--json', '--project', project()], env: { CLAUDE_CODE_SESSION_ID: session } });
59
+ const started = { sessions: wanted, stop: () => void stream.return(undefined) };
60
+ follower = started;
61
+ void readLines(stream, (line) => {
62
+ if (line.trim() === '')
63
+ return;
64
+ const parsed = JSON.parse(line);
65
+ dispatch(parsed.session, [parsed.change]);
66
+ })
67
+ .then(({ code, lastError }) => {
68
+ if (follower === started)
69
+ throw new Error(`cmod history follow ${formatExit(code, lastError)}`);
70
+ })
71
+ .catch((error) => {
72
+ if (follower !== started)
73
+ return;
74
+ follower = undefined;
75
+ report('follow', 'Stopped hearing the other sessions', error);
76
+ });
77
+ };
78
+ const start = (id, source) => {
79
+ if (id === session && source !== 'compact')
80
+ return;
81
+ if (id === session && source === 'compact') {
82
+ dispatch(session, conversation.startContext(new Date().toISOString()));
83
+ return;
84
+ }
85
+ session = id;
86
+ live = [];
87
+ liveIds.clear();
88
+ waitingRows.clear();
89
+ conversation = createConversation(emptyConversation(id));
90
+ starting =
91
+ source === 'startup' || source === 'clear'
92
+ ? Promise.resolve(dispatch(id, conversation.startContext(new Date().toISOString())))
93
+ : json(['history', 'state', id]).then((state) => {
94
+ if (session === id)
95
+ conversation = createConversation(state);
96
+ }, (error) => report('state', `Starts session ${id} without its earlier turns`, error));
97
+ restartFollower();
98
+ };
99
+ const take = async (row, task) => {
100
+ await starting;
101
+ if (task !== undefined && conversation.state.tasks[task] === undefined) {
102
+ const waiting = waitingRows.get(task) ?? [];
103
+ waiting.push(row);
104
+ waitingRows.set(task, waiting);
105
+ if (waiting.length < bufferedTaskRows)
106
+ return;
107
+ waitingRows.delete(task);
108
+ dispatch(session, conversation.startTask({ id: task, title: '', agentType: 'general-purpose', openedBy: '', openedAt: row.at }));
109
+ for (const each of waiting)
110
+ dispatch(session, conversation.add(each, task));
111
+ return;
112
+ }
113
+ dispatch(session, conversation.add(row, task));
114
+ };
115
+ router.add('classic.SessionStart', async (e, next) => {
116
+ start(e.session_id, e.source);
117
+ return next(e);
118
+ });
119
+ router.add('session.append', async (e, next) => {
120
+ const kept = await next(e);
121
+ if (kept.deny !== undefined || session === '')
122
+ return kept;
123
+ const cwd = e.message.type === 'assistant' ? await claude.session.cwd() : '';
124
+ 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));
125
+ return kept;
126
+ });
127
+ router.add('session.compact', async (e, next) => {
128
+ const compacted = await next(e);
129
+ if (e.agentId === undefined && e.trigger !== 'precompute' && compacted.skip === undefined) {
130
+ await starting;
131
+ dispatch(session, conversation.endContext(new Date().toISOString()));
132
+ }
133
+ return compacted;
134
+ });
135
+ router.add('agent.spawn', async (e, next) => {
136
+ const spawned = await next(e);
137
+ if (spawned.deny !== undefined || spawned.agentId === undefined)
138
+ return spawned;
139
+ const id = spawned.agentId;
140
+ 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 }) };
141
+ await starting;
142
+ dispatch(session, conversation.startTask(opened));
143
+ const waiting = waitingRows.get(id) ?? [];
144
+ waitingRows.delete(id);
145
+ for (const row of waiting)
146
+ dispatch(session, conversation.add(row, id));
147
+ return spawned;
148
+ });
149
+ router.add('session.receive', async (e, next) => {
150
+ const isFromCmod = e.origin.kind === 'peer' && !('teammate' in e.origin) && e.origin.plugin === cmodPluginName;
151
+ const sent = isFromCmod ? delivery.exec(e.text)?.[1] : undefined;
152
+ if (sent === undefined)
153
+ return next(e);
154
+ const parsed = JSON.parse(sent);
155
+ if (!isObject(parsed) || typeof parsed['text'] !== 'string')
156
+ return next(e);
157
+ const { type, text, as } = parsed;
158
+ claude.clock.after(0, () => {
159
+ const delivered = type === Note ? claude.session.append({ message: { type: 'user', content: [{ type: 'text', text }] } }) : claude.prompt.submit({ text, ...(as === Person ? { asUser: true } : {}) });
160
+ void delivered.catch((error) => report('delivery', 'Could not deliver a message from another session', error));
161
+ });
162
+ return { consumed: 'cmod delivery' };
163
+ });
164
+ const merged = (saved, kept, key) => {
165
+ const keptKeys = new Set(kept.map(key));
166
+ return [...saved.filter((item) => !keptKeys.has(key(item))), ...kept];
167
+ };
168
+ const liveTurns = () => {
169
+ const turn = conversation.state.turn;
170
+ return turn === undefined ? [] : [turn];
171
+ };
172
+ const liveTasks = () => Object.values(conversation.state.tasks).map(({ context: _context, ...task }) => task);
173
+ const liveContext = () => {
174
+ const { context, contextOpenedAt } = conversation.state;
175
+ return context === 0 ? [] : [{ number: context, openedAt: contextOpenedAt }];
176
+ };
177
+ const lastOf = (items, last) => (typeof last === 'number' ? items.slice(-last) : [...items]);
178
+ const methods = {
179
+ async [conversationMethods.sessions](request) {
180
+ const found = await json(['history', 'sessions', ...scopeArgs(request), ...(request['running'] === true ? ['--running'] : [])]);
181
+ return found.map((each) => (each.id === session ? { ...each, isRunning: true } : each));
182
+ },
183
+ async [conversationMethods.search](request) {
184
+ const before = isObject(request['before']) ? request['before'] : undefined;
185
+ return json(['history', 'search', String(request['text']), ...scopeArgs(request), ...(request['running'] === true ? ['--running'] : []), ...countArg('--limit', request['limit']), ...(before === undefined ? [] : ['--before', `${String(before['session'])}:${String(before['id'])}`])]);
186
+ },
187
+ async [conversationMethods.messages](request) {
188
+ const id = String(request['session']);
189
+ const filters = [...textArg('--turn', request['turn']), ...textArg('--task', request['task']), ...textArg('--after', request['after'])];
190
+ const saved = await json(['history', 'messages', id, ...filters, ...countArg('--last', request['last'])]);
191
+ if (id !== session)
192
+ return saved;
193
+ await starting;
194
+ const fits = (message) => (request['turn'] === undefined || message.turn === request['turn']) && message.task === request['task'];
195
+ const after = typeof request['after'] === 'string' ? live.findIndex((message) => message.id === request['after']) : -1;
196
+ const kept = live.slice(after + 1).filter(fits);
197
+ return lastOf(merged(saved, kept, (message) => message.id), request['last']);
198
+ },
199
+ async [conversationMethods.turns](request) {
200
+ const id = String(request['session']);
201
+ const saved = await json(['history', 'turns', id, ...countArg('--context', request['context']), ...countArg('--last', request['last'])]);
202
+ if (id !== session)
203
+ return saved;
204
+ await starting;
205
+ const kept = liveTurns().filter((turn) => request['context'] === undefined || turn.context === request['context']);
206
+ return lastOf(merged(saved, kept, (turn) => turn.prompt.id), request['last']);
207
+ },
208
+ async [conversationMethods.contexts](request) {
209
+ const id = String(request['session']);
210
+ const saved = await json(['history', 'contexts', id]);
211
+ if (id !== session)
212
+ return saved;
213
+ await starting;
214
+ return merged(saved, liveContext(), (context) => String(context.number));
215
+ },
216
+ async [conversationMethods.tasks](request) {
217
+ const id = String(request['session']);
218
+ const saved = await json(['history', 'tasks', id, ...textArg('--task', request['task'])]);
219
+ if (id !== session)
220
+ return saved;
221
+ await starting;
222
+ const kept = liveTasks().filter((task) => task.parent === request['task']);
223
+ return merged(saved, kept, (task) => task.id);
224
+ },
225
+ async [conversationMethods.goal](request) {
226
+ const id = String(request['session']);
227
+ if (id === session) {
228
+ await starting;
229
+ const text = conversation.state.goal;
230
+ if (text !== undefined)
231
+ return { text };
232
+ }
233
+ return json(['history', 'goal', id]);
234
+ },
235
+ async [conversationMethods.updateGoal](request) {
236
+ const id = String(request['session']);
237
+ const text = String(request['text']);
238
+ await claude.fs.write(`${goals}/${id}.txt`, text);
239
+ if (id === session) {
240
+ await starting;
241
+ dispatch(session, conversation.changeGoal(text));
242
+ }
243
+ return null;
244
+ },
245
+ async [conversationMethods.subscribe](request, caller) {
246
+ if (caller === undefined)
247
+ throw new Error('cmod hears no caller for this subscription.');
248
+ const id = String(request['session']);
249
+ const list = Array.isArray(request['subscriptions']) ? request['subscriptions'] : [];
250
+ const sessions = heard.get(caller) ?? new Map();
251
+ sessions.set(id, list.map(({ event, matcher }) => ({ event, matcher: matcher === undefined ? undefined : receivedMatcher(matcher) })));
252
+ heard.set(caller, sessions);
253
+ restartFollower();
254
+ return null;
255
+ },
256
+ async [conversationMethods.send](request, caller) {
257
+ if (!(await isGranted(caller, 'prompt')))
258
+ throw new Error(`${caller ?? 'A mod'} sends to another session without your grant to send prompts.`);
259
+ const type = request['type'] === Note ? Note : Prompt;
260
+ const sent = await claude.session.send({ to: { sessionId: String(request['session']) }, text: `<cmod-delivery>${JSON.stringify({ type, text: request['text'], as: request['as'] })}</cmod-delivery>` });
261
+ if (sent.isDelivered !== true)
262
+ throw new Error(`Claude Code could not deliver the message to session ${String(request['session'])}.`);
263
+ return null;
264
+ },
265
+ };
266
+ return {
267
+ async answer(e) {
268
+ const method = methods[e.method];
269
+ if (method === undefined)
270
+ return undefined;
271
+ const input = isObject(e.input) ? e.input : {};
272
+ if (e.method !== conversationMethods.send && !(await isGranted(e.caller, 'conversation')))
273
+ return { deny: `${e.caller ?? 'A mod'} reads your conversations without your grant. Turn it on in /mods ${e.caller ?? ''}.`.trim() };
274
+ try {
275
+ return { value: await method(input, e.caller) };
276
+ }
277
+ catch (error) {
278
+ return { deny: messageOf(error) };
279
+ }
280
+ },
281
+ };
282
+ }
283
+ function scopeArgs(request) {
284
+ return [...(typeof request['since'] === 'string' ? ['--since', request['since']] : []), ...(request['everyProject'] === true ? ['--all'] : [])];
285
+ }
286
+ function countArg(flag, value) {
287
+ return typeof value === 'number' ? [flag, String(value)] : [];
288
+ }
289
+ function textArg(flag, value) {
290
+ return typeof value === 'string' ? [flag, value] : [];
291
+ }
@@ -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
27
  return `${name} is not installed. Run cmod install ${name}.`;
19
28
  }
@@ -0,0 +1,17 @@
1
+ import type { Claude } from './claude.js';
2
+ import type { Router } from './router.js';
3
+ import type { ToolCalls } from './tool-calls.js';
4
+ export type Engine = {
5
+ readonly claude: Claude;
6
+ readonly router: Router;
7
+ readonly toolCalls: ToolCalls;
8
+ readonly report: (failure: {
9
+ readonly id: string;
10
+ readonly message: string;
11
+ readonly error?: unknown;
12
+ readonly description?: string;
13
+ }) => void;
14
+ };
15
+ export declare function keepEngine(mod: object, engine: Engine): void;
16
+ export declare function engineOf(mod: object): Engine;
17
+ //# 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,SAAS,EAAE,MAAM,iBAAiB,CAAA;AAEhD,MAAM,MAAM,MAAM,GAAG;IACnB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,SAAS,EAAE,SAAS,CAAA;IAC7B,QAAQ,CAAC,MAAM,EAAE,CAAC,OAAO,EAAE;QAAE,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,CAAC;QAAC,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAA;CAC/I,CAAA;AAID,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,CAE5D;AAED,wBAAgB,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAI5C"}
@@ -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
+ }
@@ -9,10 +9,14 @@ export type Grants = {
9
9
  readonly configRoot: string | undefined;
10
10
  readonly projectRoot: () => string;
11
11
  readonly freeFolders: () => readonly string[];
12
+ readonly freeAddresses: () => ReadonlySet<string>;
12
13
  };
13
14
  export declare class PermissionRefused extends Error {
14
15
  }
16
+ export declare function programAddresses(mod: object): Set<string>;
17
+ export declare function checkGrant(grants: Grants, call: string, item: string | undefined): Promise<void>;
15
18
  export declare function checkWrite(grants: Grants, call: string, path: string): Promise<void>;
19
+ export declare function checkSettings(grants: Grants, source: 'user' | 'project' | 'local', keys: readonly string[]): Promise<void>;
16
20
  export declare function checkingGrants(claude: Claude, grants: Grants): Claude;
17
21
  export declare function itemOf(name: string, value?: string): string;
18
22
  export declare function isCovered(granted: ReadonlySet<string>, item: string, home: string | undefined): boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"grants.d.ts","sourceRoot":"","sources":["../src/runtime/grants.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AACzC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAA;AAE7C,MAAM,MAAM,MAAM,GAAG;IACnB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAA;IACpC,QAAQ,CAAC,OAAO,EAAE,MAAM,WAAW,CAAC,MAAM,CAAC,CAAA;IAC3C,QAAQ,CAAC,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;IACrC,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,CAAA;IACjC,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,SAAS,CAAA;IACvC,QAAQ,CAAC,WAAW,EAAE,MAAM,MAAM,CAAA;IAClC,QAAQ,CAAC,WAAW,EAAE,MAAM,SAAS,MAAM,EAAE,CAAA;CAC9C,CAAA;AAED,qBAAa,iBAAkB,SAAQ,KAAK;CAAG;AAc/C,wBAAgB,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAEpF;AAED,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CA6ErE;AAmBD,wBAAgB,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAE3D;AAED,wBAAgB,SAAS,CAAC,OAAO,EAAE,WAAW,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAUvG;AAcD,wBAAgB,OAAO,CAAC,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,MAAM,GAAG,UAAU,GAAG,MAAM,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAI9G;AA4BD,MAAM,MAAM,WAAW,GAAG;IACxB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAA;IAChC,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAAA;CAC1C,CAAA;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE,WAAW,GAAG,OAAO,CAEzD;AAED,wBAAgB,UAAU,CAAC,KAAK,EAAE,WAAW,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,WAAW,GAAG,OAAO,CAa9G;AAED,wBAAsB,aAAa,CAAC,KAAK,EAAE,WAAW,EAAE,CAAC,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,EAAE,KAAK,EAAE,WAAW,GAAG,OAAO,CAAC,OAAO,CAAC,CA+BxJ"}
1
+ {"version":3,"file":"grants.d.ts","sourceRoot":"","sources":["../src/runtime/grants.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AAEzC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAA;AAG7C,MAAM,MAAM,MAAM,GAAG;IACnB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAA;IACpC,QAAQ,CAAC,OAAO,EAAE,MAAM,WAAW,CAAC,MAAM,CAAC,CAAA;IAC3C,QAAQ,CAAC,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;IACrC,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,CAAA;IACjC,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,SAAS,CAAA;IACvC,QAAQ,CAAC,WAAW,EAAE,MAAM,MAAM,CAAA;IAClC,QAAQ,CAAC,WAAW,EAAE,MAAM,SAAS,MAAM,EAAE,CAAA;IAC7C,QAAQ,CAAC,aAAa,EAAE,MAAM,WAAW,CAAC,MAAM,CAAC,CAAA;CAClD,CAAA;AAED,qBAAa,iBAAkB,SAAQ,KAAK;CAAG;AAI/C,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC,CAIzD;AAqBD,wBAAsB,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC,CAItG;AAED,wBAAsB,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAE1F;AAED,wBAAsB,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,EAAE,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAEhI;AAED,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAiFrE;AAmBD,wBAAgB,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAE3D;AAED,wBAAgB,SAAS,CAAC,OAAO,EAAE,WAAW,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAUvG;AAiBD,wBAAgB,OAAO,CAAC,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,MAAM,GAAG,UAAU,GAAG,MAAM,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAI9G;AA4BD,MAAM,MAAM,WAAW,GAAG;IACxB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAA;IAChC,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAAA;CAC1C,CAAA;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE,WAAW,GAAG,OAAO,CAEzD;AAED,wBAAgB,UAAU,CAAC,KAAK,EAAE,WAAW,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,WAAW,GAAG,OAAO,CAa9G;AAED,wBAAsB,aAAa,CAAC,KAAK,EAAE,WAAW,EAAE,CAAC,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,EAAE,KAAK,EAAE,WAAW,GAAG,OAAO,CAAC,OAAO,CAAC,CA+BxJ"}
package/runtime/grants.js CHANGED
@@ -1,19 +1,43 @@
1
1
  import { permissionWords } from '../records.js';
2
2
  import { relativePath } from '../utils/paths.js';
3
+ import { cmodPluginName } from './dependencies.js';
4
+ import { conversationMethods } from './sessions.js';
3
5
  export class PermissionRefused extends Error {
4
6
  }
5
- const configFiles = ['.claude/settings.json', '.claude/settings.local.json', '.mcp.json'];
7
+ const addressesOfPrograms = new WeakMap();
8
+ export function programAddresses(mod) {
9
+ const known = addressesOfPrograms.get(mod) ?? new Set();
10
+ addressesOfPrograms.set(mod, known);
11
+ return known;
12
+ }
13
+ const projectSettingsFiles = ['.claude/settings.json', '.claude/settings.local.json', '.mcp.json'];
6
14
  const startsTurns = ['CronCreate'];
7
15
  const ownSchedule = ['CronDelete', 'CronList'];
8
- async function checkGrant(grants, call, item) {
16
+ const conversationCalls = {
17
+ [conversationMethods.sessions]: 'sessions.list',
18
+ [conversationMethods.search]: 'sessions.search',
19
+ [conversationMethods.messages]: 'messages',
20
+ [conversationMethods.turns]: 'turns',
21
+ [conversationMethods.contexts]: 'contexts',
22
+ [conversationMethods.tasks]: 'tasks',
23
+ [conversationMethods.goal]: 'goal',
24
+ [conversationMethods.updateGoal]: 'goal.update',
25
+ [conversationMethods.subscribe]: 'on',
26
+ [conversationMethods.send]: 'send',
27
+ };
28
+ export async function checkGrant(grants, call, item) {
9
29
  if (item === undefined)
10
30
  return;
11
31
  await grants.refresh();
12
32
  if (!isCovered(grants.granted(), item, grants.home))
13
33
  throw new PermissionRefused(refusal(grants, call, item));
14
34
  }
15
- export function checkWrite(grants, call, path) {
16
- return checkGrant(grants, call, writeItem(grants, path));
35
+ export async function checkWrite(grants, call, path) {
36
+ await checkGrant(grants, call, writeItem(grants, path));
37
+ }
38
+ export async function checkSettings(grants, source, keys) {
39
+ for (const key of keys)
40
+ await checkGrant(grants, `settings.${source}.update(${key})`, `config:${key}`);
17
41
  }
18
42
  export function checkingGrants(claude, grants) {
19
43
  const gate = (call, item) => checkGrant(grants, call, item);
@@ -42,54 +66,60 @@ export function checkingGrants(claude, grants) {
42
66
  },
43
67
  http: {
44
68
  async fetch(url, init) {
45
- await gate(`http.fetch(${url})`, `network:${init?.socketPath ?? hostOf(url)}`);
69
+ if (!grants.freeAddresses().has(init?.socketPath ?? addressOf(url)))
70
+ await gate(`http.fetch(${url})`, `network:${init?.socketPath ?? hostOf(url)}`);
46
71
  return claude.http.fetch(url, init);
47
72
  },
48
73
  },
49
- config: {
50
- ...claude.config,
51
- set: (async (args) => {
52
- await gate('config.set', 'config');
53
- return claude.config.set(args);
54
- }),
55
- },
56
74
  session: {
57
75
  ...claude.session,
58
76
  messages: (async (...args) => {
59
77
  await gate('session.messages', 'conversation');
60
78
  return claude.session.messages(...args);
61
79
  }),
62
- append: (async (args) => {
63
- await gate('session.append', 'prompt');
80
+ append: async (args) => {
81
+ await gate('send', 'prompt');
64
82
  return claude.session.append(args);
65
- }),
83
+ },
84
+ send: async (args) => {
85
+ await gate('send', 'prompt');
86
+ return claude.session.send(args);
87
+ },
66
88
  },
67
89
  prompt: {
68
- submit: (async (args) => {
69
- await gate('session.submit', 'prompt');
90
+ submit: async (args) => {
91
+ await gate('send', 'prompt');
70
92
  return claude.prompt.submit(args);
71
- }),
93
+ },
94
+ },
95
+ cmod: {
96
+ async call(args) {
97
+ const named = args.to === cmodPluginName ? conversationCalls[args.method] : undefined;
98
+ if (named !== undefined)
99
+ await gate(named, args.method === conversationMethods.send ? 'prompt' : 'conversation');
100
+ return claude.cmod.call(args);
101
+ },
72
102
  },
73
103
  model: {
74
- complete: (async (...args) => {
104
+ complete: async (...args) => {
75
105
  await gate('model.complete', 'model');
76
106
  return claude.model.complete(...args);
77
- }),
107
+ },
78
108
  },
79
109
  agent: {
80
110
  ...claude.agent,
81
111
  async spawn(args) {
82
- await gate('agent.spawn', 'agents');
112
+ await gate('startTask', 'tasks');
83
113
  return claude.agent.spawn(args);
84
114
  },
85
115
  },
86
116
  tool: {
87
117
  ...claude.tool,
88
- call: (async (input) => {
118
+ call: async (input) => {
89
119
  const item = startsTurns.includes(input.tool) ? 'prompt' : ownSchedule.includes(input.tool) ? undefined : 'tools';
90
120
  await gate(`tool.call(${input.tool})`, item);
91
121
  return claude.tool.call(input);
92
- }),
122
+ },
93
123
  },
94
124
  };
95
125
  }
@@ -99,8 +129,8 @@ function spawned(started) {
99
129
  const stream = {
100
130
  next: async () => (await started).next(),
101
131
  async return(value) {
102
- const running = await started.catch(() => undefined);
103
- return (await running?.return?.(value)) ?? { done: true, value };
132
+ const [start] = await Promise.allSettled([started]);
133
+ return start.status === 'fulfilled' ? start.value.return(value) : { done: true, value };
104
134
  },
105
135
  [Symbol.asyncIterator]: () => stream,
106
136
  result,
@@ -132,8 +162,10 @@ function readItem(grants, path) {
132
162
  }
133
163
  function writeItem(grants, path) {
134
164
  const root = grants.projectRoot();
135
- if (configFiles.some((file) => `${root}/${file}` === path))
136
- return 'config';
165
+ const settingsFiles = [...projectSettingsFiles.map((file) => `${root}/${file}`), ...(grants.configRoot === undefined ? [] : [`${grants.configRoot}/settings.json`])];
166
+ if (settingsFiles.includes(path)) {
167
+ throw new PermissionRefused(`${grants.name} writes ${path} by hand. Change Claude Code's settings with settings.user.update, settings.project.update, or settings.local.update, which keep the person's own edits and take the mod's values out when it is removed.`);
168
+ }
137
169
  if (grants.freeFolders().some((folder) => relativePath(folder, path) !== undefined))
138
170
  return undefined;
139
171
  return `files:${path}`;
@@ -274,12 +306,10 @@ function programOf(argv) {
274
306
  return command.slice(command.lastIndexOf('/') + 1);
275
307
  }
276
308
  function hostOf(url) {
277
- try {
278
- return new URL(url).hostname;
279
- }
280
- catch {
281
- return url;
282
- }
309
+ return URL.canParse(url) ? new URL(url).hostname : url;
310
+ }
311
+ function addressOf(url) {
312
+ return URL.canParse(url) ? new URL(url).host : url;
283
313
  }
284
314
  function expanded(path, home) {
285
315
  return path.startsWith('~/') && home !== undefined ? `${home}${path.slice(1)}` : path;
@@ -1,16 +1,17 @@
1
- import type { ModEvent, ModHook } from '../mod.js';
1
+ import type { Hook, HookListener } from '../mod.js';
2
2
  import type { Claude } from './claude.js';
3
3
  import type { RoutedHook } from './router.js';
4
+ import type { Failure } from './toaster.js';
4
5
  import { type ToolCalls } from './tool-calls.js';
5
- export type RoutedEvent = `classic.${Exclude<ModEvent, 'PreToolUse'>}` | 'tool.check' | 'tool.call' | 'prompt.submit' | 'prompt.context' | 'command.run' | 'session.measure' | 'skill.prompt' | 'ui.render' | 'ui.press' | 'ui.scroll' | 'ui.close' | 'cmod.call';
6
+ export type RoutedEvent = `classic.${Exclude<Hook, 'PreToolUse'>}` | 'tool.check' | 'tool.call' | 'prompt.submit' | 'prompt.context' | 'command.run' | 'session.measure' | 'skill.prompt' | 'ui.render' | 'ui.press' | 'ui.scroll' | 'ui.close' | 'cmod.call' | 'session.append' | 'session.compact' | 'session.receive' | 'agent.spawn';
6
7
  export declare const permissionEvents: readonly RoutedEvent[];
7
- export declare function classicHook<E extends Exclude<ModEvent, 'PreToolUse'>>(name: string, event: E, hook: ModHook<E>, claude: Claude, calls: ToolCalls): RoutedHook<RoutedEvent>;
8
+ export declare function classicHook<E extends Exclude<Hook, 'PreToolUse'>>(name: string, event: E, hook: HookListener<E>, claude: Claude, calls: ToolCalls, report: (failure: Failure) => void): RoutedHook<RoutedEvent>;
8
9
  type HeldDecision = {
9
10
  readonly decision: 'allow' | 'ask';
10
11
  readonly reason?: string;
11
12
  };
12
13
  export type HeldDecisions = Map<string, HeldDecision>;
13
- export declare function preToolUseHook(name: string, hook: ModHook<'PreToolUse'>, claude: Claude, calls: ToolCalls, held: HeldDecisions | undefined): RoutedHook<'tool.call'>;
14
+ export declare function preToolUseHook(name: string, hook: HookListener<'PreToolUse'>, claude: Claude, calls: ToolCalls, held: HeldDecisions | undefined): RoutedHook<'tool.call'>;
14
15
  export declare function heldDecisionHook(held: HeldDecisions): RoutedHook<'tool.check'>;
15
16
  export declare function userSkillHook(claude: Claude): RoutedHook<'skill.prompt'>;
16
17
  export declare const strictness: readonly ['allow', 'ask', 'deny'];
@@ -1 +1 @@
1
- {"version":3,"file":"hooks.d.ts","sourceRoot":"","sources":["../src/runtime/hooks.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAyB,QAAQ,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AAEzE,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AACzC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AAC7C,OAAO,EAA6B,KAAK,SAAS,EAAE,MAAM,iBAAiB,CAAA;AAK3E,MAAM,MAAM,WAAW,GACnB,WAAW,OAAO,CAAC,QAAQ,EAAE,YAAY,CAAC,EAAE,GAC5C,YAAY,GACZ,WAAW,GACX,eAAe,GACf,gBAAgB,GAChB,aAAa,GACb,iBAAiB,GACjB,cAAc,GACd,WAAW,GACX,UAAU,GACV,WAAW,GACX,UAAU,GACV,WAAW,CAAA;AAEf,eAAO,MAAM,gBAAgB,EAAE,SAAS,WAAW,EAAgD,CAAA;AA6BnG,wBAAgB,WAAW,CAAC,CAAC,SAAS,OAAO,CAAC,QAAQ,EAAE,YAAY,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,EAAE,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,GAAG,UAAU,CAAC,WAAW,CAAC,CAwB1K;AAED,KAAK,YAAY,GAAG;IAAE,QAAQ,CAAC,QAAQ,EAAE,OAAO,GAAG,KAAK,CAAC;IAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,CAAA;AAEpF,MAAM,MAAM,aAAa,GAAG,GAAG,CAAC,MAAM,EAAE,YAAY,CAAC,CAAA;AAErD,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,CAAC,YAAY,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,EAAE,IAAI,EAAE,aAAa,GAAG,SAAS,GAAG,UAAU,CAAC,WAAW,CAAC,CA6BpK;AAED,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,aAAa,GAAG,UAAU,CAAC,YAAY,CAAC,CAM9E;AAYD,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG,UAAU,CAAC,cAAc,CAAC,CAexE;AA4DD,eAAO,MAAM,UAAU,YAAI,OAAO,EAAE,KAAK,EAAE,MAAM,CAAU,CAAA"}
1
+ {"version":3,"file":"hooks.d.ts","sourceRoot":"","sources":["../src/runtime/hooks.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,IAAI,EAAyB,YAAY,EAAE,MAAM,WAAW,CAAA;AAE1E,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AACzC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AAC7C,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAA;AAC3C,OAAO,EAA6B,KAAK,SAAS,EAAE,MAAM,iBAAiB,CAAA;AAM3E,MAAM,MAAM,WAAW,GACnB,WAAW,OAAO,CAAC,IAAI,EAAE,YAAY,CAAC,EAAE,GACxC,YAAY,GACZ,WAAW,GACX,eAAe,GACf,gBAAgB,GAChB,aAAa,GACb,iBAAiB,GACjB,cAAc,GACd,WAAW,GACX,UAAU,GACV,WAAW,GACX,UAAU,GACV,WAAW,GACX,gBAAgB,GAChB,iBAAiB,GACjB,iBAAiB,GACjB,aAAa,CAAA;AAEjB,eAAO,MAAM,gBAAgB,EAAE,SAAS,WAAW,EAAgD,CAAA;AA6BnG,wBAAgB,WAAW,CAAC,CAAC,SAAS,OAAO,CAAC,IAAI,EAAE,YAAY,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,EAAE,IAAI,EAAE,YAAY,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,GAAG,UAAU,CAAC,WAAW,CAAC,CAuB/M;AAED,KAAK,YAAY,GAAG;IAAE,QAAQ,CAAC,QAAQ,EAAE,OAAO,GAAG,KAAK,CAAC;IAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,CAAA;AAEpF,MAAM,MAAM,aAAa,GAAG,GAAG,CAAC,MAAM,EAAE,YAAY,CAAC,CAAA;AAErD,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,YAAY,CAAC,YAAY,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,EAAE,IAAI,EAAE,aAAa,GAAG,SAAS,GAAG,UAAU,CAAC,WAAW,CAAC,CA6BzK;AAED,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,aAAa,GAAG,UAAU,CAAC,YAAY,CAAC,CAM9E;AAYD,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG,UAAU,CAAC,cAAc,CAAC,CAexE;AAgED,eAAO,MAAM,UAAU,YAAI,OAAO,EAAE,KAAK,EAAE,MAAM,CAAU,CAAA"}