@cmodjs/core 4.0.2 → 4.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/docs/commands.md +43 -8
- package/docs/index.md +3 -3
- package/docs/install-steps.md +6 -6
- package/docs/jobs.md +2 -1
- package/docs/mod.md +1 -1
- package/docs/options.md +1 -1
- package/docs/permissions.md +1 -1
- package/docs/session.md +44 -13
- package/docs/testing.md +14 -10
- package/docs/ui.md +43 -11
- package/installer.d.ts +3 -2
- package/installer.d.ts.map +1 -1
- package/jobs/program.d.ts +1 -0
- package/jobs/program.d.ts.map +1 -1
- package/jobs/program.js +39 -23
- package/mod.d.ts +3 -2
- package/mod.d.ts.map +1 -1
- package/mod.js +1 -0
- package/package.json +1 -1
- package/records.d.ts +15 -1
- package/records.d.ts.map +1 -1
- package/records.js +39 -1
- package/register.d.ts.map +1 -1
- package/register.js +5 -2
- package/runtime/before.d.ts +6 -2
- package/runtime/before.d.ts.map +1 -1
- package/runtime/before.js +56 -18
- package/runtime/buttons.d.ts +10 -0
- package/runtime/buttons.d.ts.map +1 -0
- package/runtime/buttons.js +12 -0
- package/runtime/conversations.d.ts.map +1 -1
- package/runtime/conversations.js +134 -25
- package/runtime/engine.d.ts +2 -0
- package/runtime/engine.d.ts.map +1 -1
- package/runtime/grants.d.ts +1 -0
- package/runtime/grants.d.ts.map +1 -1
- package/runtime/grants.js +5 -2
- package/runtime/hotkeys.d.ts.map +1 -1
- package/runtime/hotkeys.js +7 -4
- package/runtime/installer.d.ts +9 -6
- package/runtime/installer.d.ts.map +1 -1
- package/runtime/installer.js +104 -47
- package/runtime/lifecycle.d.ts.map +1 -1
- package/runtime/lifecycle.js +121 -40
- package/runtime/pages.d.ts +3 -1
- package/runtime/pages.d.ts.map +1 -1
- package/runtime/pages.js +34 -10
- package/runtime/parts.d.ts +1 -0
- package/runtime/parts.d.ts.map +1 -1
- package/runtime/parts.js +6 -3
- package/runtime/programs.d.ts +1 -0
- package/runtime/programs.d.ts.map +1 -1
- package/runtime/programs.js +4 -1
- package/runtime/sessions.d.ts +2 -2
- package/runtime/sessions.d.ts.map +1 -1
- package/runtime/sessions.js +88 -58
- package/runtime/toaster.d.ts.map +1 -1
- package/runtime/toaster.js +23 -9
- package/runtime/ui.d.ts +21 -4
- package/runtime/ui.d.ts.map +1 -1
- package/runtime/ui.js +158 -58
- package/runtime/watches.d.ts.map +1 -1
- package/runtime/watches.js +18 -4
- package/runtime/widgets.d.ts +7 -5
- package/runtime/widgets.d.ts.map +1 -1
- package/runtime/widgets.js +29 -20
- package/session/context/turn/message/attached-file.d.ts +13 -0
- package/session/context/turn/message/attached-file.d.ts.map +1 -0
- package/session/context/turn/message/attached-file.js +21 -0
- package/session/context/turn/message/message.d.ts +3 -2
- package/session/context/turn/message/message.d.ts.map +1 -1
- package/session/context/turn/message/message.js +20 -6
- package/session/conversation.d.ts +14 -3
- package/session/conversation.d.ts.map +1 -1
- package/session/conversation.js +23 -4
- package/session/goal/task.d.ts +5 -0
- package/session/goal/task.d.ts.map +1 -1
- package/session/session.d.ts +35 -7
- package/session/session.d.ts.map +1 -1
- package/session/session.js +2 -1
- package/src/installer.ts +3 -2
- package/src/jobs/program.ts +41 -21
- package/src/mod.ts +3 -2
- package/src/records.ts +49 -1
- package/src/register.ts +9 -6
- package/src/runtime/before.ts +61 -22
- package/src/runtime/buttons.ts +22 -0
- package/src/runtime/conversations.ts +138 -32
- package/src/runtime/engine.ts +2 -0
- package/src/runtime/grants.ts +6 -2
- package/src/runtime/hotkeys.ts +7 -4
- package/src/runtime/installer.ts +144 -82
- package/src/runtime/lifecycle.ts +128 -43
- package/src/runtime/pages.ts +45 -8
- package/src/runtime/parts.ts +8 -4
- package/src/runtime/programs.ts +5 -1
- package/src/runtime/sessions.ts +83 -57
- package/src/runtime/toaster.ts +27 -9
- package/src/runtime/ui.ts +163 -61
- package/src/runtime/watches.ts +20 -5
- package/src/runtime/widgets.ts +34 -22
- package/src/session/context/turn/message/attached-file.ts +36 -0
- package/src/session/context/turn/message/message.ts +20 -7
- package/src/session/conversation.ts +30 -6
- package/src/session/goal/task.ts +6 -0
- package/src/session/session.ts +17 -7
- package/src/testing/fake-claude.ts +21 -6
- package/src/testing/fake-elements.ts +51 -8
- package/src/testing/fake-sessions.ts +60 -23
- package/src/testing.ts +86 -28
- package/src/ui/areas.ts +1 -0
- package/src/ui/components.ts +14 -8
- package/src/ui/elements.ts +6 -2
- package/src/ui/markdown.ts +3 -3
- package/src/ui/panes.ts +2 -2
- package/src/ui/styles.ts +6 -2
- package/testing/fake-claude.d.ts +3 -0
- package/testing/fake-claude.d.ts.map +1 -1
- package/testing/fake-claude.js +18 -5
- package/testing/fake-elements.d.ts.map +1 -1
- package/testing/fake-elements.js +46 -7
- package/testing/fake-sessions.d.ts +21 -10
- package/testing/fake-sessions.d.ts.map +1 -1
- package/testing/fake-sessions.js +58 -20
- package/testing.d.ts +8 -12
- package/testing.d.ts.map +1 -1
- package/testing.js +82 -21
- package/ui/areas.d.ts +4 -0
- package/ui/areas.d.ts.map +1 -1
- package/ui/components.d.ts +1 -0
- package/ui/components.d.ts.map +1 -1
- package/ui/components.js +11 -7
- package/ui/elements.d.ts +3 -0
- package/ui/elements.d.ts.map +1 -1
- package/ui/elements.js +6 -1
- package/ui/markdown.d.ts +2 -2
- package/ui/markdown.d.ts.map +1 -1
- package/ui/panes.d.ts +2 -1
- package/ui/panes.d.ts.map +1 -1
- package/ui/styles.d.ts +11 -2
- package/ui/styles.d.ts.map +1 -1
package/runtime/before.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { ContextEnd } from '../session/context/context.js';
|
|
2
|
+
import { AttachedFile, attachedPathOf, attachmentTypeOf } from '../session/context/turn/message/attached-file.js';
|
|
2
3
|
import { InstructionFile, isRulePath } from '../session/context/turn/message/instruction-file.js';
|
|
3
4
|
import { instructionPathOf, Message, messagesOf, noFiles, rowOfAppend, withTexts } from '../session/context/turn/message/message.js';
|
|
4
5
|
import { Note, untagged } from '../session/context/turn/message/note.js';
|
|
@@ -10,6 +11,7 @@ import { Skill } from '../session/context/turn/message/skill.js';
|
|
|
10
11
|
import { ToolCall } from '../session/context/turn/message/tool-call.js';
|
|
11
12
|
import { ToolResult } from '../session/context/turn/message/tool-result.js';
|
|
12
13
|
import { TurnEnd } from '../session/context/turn/turn.js';
|
|
14
|
+
import { ownerOfSpawn } from '../session/conversation.js';
|
|
13
15
|
import { Task, TaskStart } from '../session/goal/task.js';
|
|
14
16
|
import { sessionEvents } from '../session/session.js';
|
|
15
17
|
import { cmodPluginName } from './dependencies.js';
|
|
@@ -20,6 +22,7 @@ const withNote = (below, note) => {
|
|
|
20
22
|
const fields = (below ?? {});
|
|
21
23
|
return note === undefined ? fields : { ...fields, additionalContext: [...(fields['additionalContext'] ?? []), note] };
|
|
22
24
|
};
|
|
25
|
+
const withContext = (ran, note) => note === undefined || ran.deny !== undefined ? ran : { ...ran, context: [...(ran.context ?? []), note] };
|
|
23
26
|
const relayTag = /^(?:<cross-session-message[^>]*>\s*)?<cmod-relay>([\s\S]*)<\/cmod-relay>(?:\s*<\/cross-session-message>[\s\S]*)?$/;
|
|
24
27
|
export function relayOf(e) {
|
|
25
28
|
const isFromCmod = e.origin.kind === 'peer' && !('teammate' in e.origin) && e.origin.plugin === cmodPluginName;
|
|
@@ -37,6 +40,14 @@ export function installBefore({ claude, router, hooks }) {
|
|
|
37
40
|
};
|
|
38
41
|
const titles = new Map();
|
|
39
42
|
const spawned = new Map();
|
|
43
|
+
const startedBy = new Map();
|
|
44
|
+
const startingSpawn = (agentId, agentType) => {
|
|
45
|
+
const known = startedBy.get(agentId);
|
|
46
|
+
if (known !== undefined)
|
|
47
|
+
return known;
|
|
48
|
+
const waiting = [...spawned].filter(([call, spawn]) => spawn.agentType === agentType && ![...startedBy.values()].includes(call));
|
|
49
|
+
return waiting.length === 1 ? waiting[0]?.[0] : undefined;
|
|
50
|
+
};
|
|
40
51
|
const agentFolders = new Map();
|
|
41
52
|
const toolResultHook = (isFailure) => async (e, next) => {
|
|
42
53
|
if (!(await wants(ToolResult)))
|
|
@@ -99,11 +110,15 @@ export function installBefore({ claude, router, hooks }) {
|
|
|
99
110
|
return answer.text === undefined ? next(e) : { text: answer.text };
|
|
100
111
|
}),
|
|
101
112
|
router.add('prompt.attachment', async (e, next) => {
|
|
102
|
-
|
|
113
|
+
const attachmentType = attachmentTypeOf(e.type);
|
|
114
|
+
const event = e.type === 'nested_memory' ? InstructionFile : attachmentType === undefined ? undefined : AttachedFile;
|
|
115
|
+
if (event === undefined || !(await wants(event)))
|
|
103
116
|
return next(e);
|
|
104
117
|
const where = await place();
|
|
105
|
-
const
|
|
106
|
-
|
|
118
|
+
const found = attachmentType === undefined
|
|
119
|
+
? { id: '', at: now(), type: InstructionFile, path: instructionPathOf(e.text), text: e.text, isRule: isRulePath(instructionPathOf(e.text)) }
|
|
120
|
+
: { id: '', at: now(), type: AttachedFile, path: attachedPathOf(e.text), attachmentType, text: e.text };
|
|
121
|
+
const answer = await asked(event, message(where, found, e.agentId));
|
|
107
122
|
if (answer.refuse !== undefined)
|
|
108
123
|
return { text: null };
|
|
109
124
|
return answer.text === undefined ? next(e) : { text: answer.text };
|
|
@@ -138,16 +153,20 @@ export function installBefore({ claude, router, hooks }) {
|
|
|
138
153
|
if (answer.refuse !== undefined)
|
|
139
154
|
return { deny: answer.refuse };
|
|
140
155
|
if (answer.result !== undefined)
|
|
141
|
-
return { result: answer.result };
|
|
156
|
+
return withContext({ result: answer.result }, answer.note);
|
|
142
157
|
if (answer.input === undefined)
|
|
143
|
-
return next(e);
|
|
158
|
+
return withContext(await next(e), answer.note);
|
|
144
159
|
const reserved = Object.fromEntries(Object.entries(e).filter(([key]) => !(key in input)));
|
|
145
|
-
return next({ ...reserved, ...answer.input });
|
|
160
|
+
return withContext(await next({ ...reserved, ...answer.input }), answer.note);
|
|
146
161
|
}),
|
|
147
162
|
router.add('agent.spawn', async (e, next) => {
|
|
148
|
-
spawned.set(e.tool_use_id, e.prompt);
|
|
163
|
+
spawned.set(e.tool_use_id, { prompt: e.prompt, agentType: e.subagentType });
|
|
149
164
|
const start = async (args) => {
|
|
150
165
|
const started = await next(args);
|
|
166
|
+
if (started.agentId === undefined)
|
|
167
|
+
spawned.delete(e.tool_use_id);
|
|
168
|
+
else if (spawned.has(e.tool_use_id))
|
|
169
|
+
startedBy.set(started.agentId, e.tool_use_id);
|
|
151
170
|
const folder = args.isTeammate === true ? undefined : (args.cwd ?? (args.parentAgentId === undefined ? undefined : agentFolders.get(args.parentAgentId)));
|
|
152
171
|
if (started.agentId !== undefined && folder !== undefined)
|
|
153
172
|
agentFolders.set(started.agentId, resolve(await claude.session.cwd(), folder));
|
|
@@ -156,20 +175,36 @@ export function installBefore({ claude, router, hooks }) {
|
|
|
156
175
|
if (!(await wants(TaskStart)))
|
|
157
176
|
return start(e);
|
|
158
177
|
const where = await place();
|
|
159
|
-
const task = { id: '', title: e.description,
|
|
178
|
+
const task = { id: '', title: e.description, ...ownerOfSpawn(e.subagentType, where.session), model: e.model ?? e.parentModel, openedBy: e.tool_use_id, openedAt: now(), ...(e.parentAgentId === undefined ? {} : { parent: e.parentAgentId }) };
|
|
160
179
|
const answer = await asked(TaskStart, { type: TaskStart, task: { ...task, context: where.context } });
|
|
161
180
|
if (answer.refuse !== undefined) {
|
|
162
181
|
spawned.delete(e.tool_use_id);
|
|
163
182
|
return { deny: answer.refuse };
|
|
164
183
|
}
|
|
165
|
-
|
|
184
|
+
if (e.workflow !== undefined)
|
|
185
|
+
return start(e);
|
|
186
|
+
const isFork = task.forkedFrom !== undefined;
|
|
187
|
+
const { agentType, model, cwd } = (isFork ? undefined : answer.owner) ?? {};
|
|
188
|
+
const isAsync = isFork ? undefined : answer.isAsync;
|
|
189
|
+
const answered = {
|
|
190
|
+
...e,
|
|
191
|
+
...(answer.prompt === undefined ? {} : { prompt: answer.prompt }),
|
|
192
|
+
...(answer.title === undefined ? {} : { description: answer.title }),
|
|
193
|
+
...(agentType === undefined ? {} : { subagentType: agentType }),
|
|
194
|
+
...(model === undefined ? {} : { model }),
|
|
195
|
+
...(cwd === undefined ? {} : { cwd }),
|
|
196
|
+
...(isAsync === undefined ? {} : { background: isAsync }),
|
|
197
|
+
};
|
|
198
|
+
spawned.set(e.tool_use_id, { prompt: answered.prompt, agentType: answered.subagentType });
|
|
199
|
+
return start(answered);
|
|
166
200
|
}),
|
|
167
201
|
router.add('classic.SubagentStart', async (e, next) => {
|
|
168
202
|
const where = await place();
|
|
169
|
-
const openedBy = where.tasks[e.agent_id]?.openedBy;
|
|
170
|
-
const prompt = openedBy === undefined ? undefined : spawned.get(openedBy);
|
|
203
|
+
const openedBy = where.tasks[e.agent_id]?.openedBy ?? startingSpawn(e.agent_id, e.agent_type);
|
|
204
|
+
const prompt = openedBy === undefined ? undefined : spawned.get(openedBy)?.prompt;
|
|
171
205
|
if (openedBy !== undefined)
|
|
172
206
|
spawned.delete(openedBy);
|
|
207
|
+
startedBy.delete(e.agent_id);
|
|
173
208
|
if (!(await wants(Prompt)))
|
|
174
209
|
return next(e);
|
|
175
210
|
const open = where.taskTurns[e.agent_id]?.prompt;
|
|
@@ -192,7 +227,7 @@ export function installBefore({ claude, router, hooks }) {
|
|
|
192
227
|
return stopped(await next(e));
|
|
193
228
|
const answer = await asked(TurnEnd, { type: TurnEnd, turn: { ...turn, closedAt: now() } });
|
|
194
229
|
const below = await next(e);
|
|
195
|
-
return stopped(answer.refuse === undefined ? below : { ...below, block: answer.refuse });
|
|
230
|
+
return stopped(answer.refuse === undefined ? withNote(below, answer.note) : { ...below, block: answer.refuse });
|
|
196
231
|
}),
|
|
197
232
|
router.add('classic.Stop', async (e, next) => {
|
|
198
233
|
if (!(await wants(TurnEnd)))
|
|
@@ -202,7 +237,7 @@ export function installBefore({ claude, router, hooks }) {
|
|
|
202
237
|
return next(e);
|
|
203
238
|
const answer = await asked(TurnEnd, { type: TurnEnd, turn: { ...where.turn, closedAt: now() } });
|
|
204
239
|
const below = await next(e);
|
|
205
|
-
return answer.refuse === undefined ? below : { ...below, block: answer.refuse };
|
|
240
|
+
return answer.refuse === undefined ? withNote(below, answer.note) : { ...below, block: answer.refuse };
|
|
206
241
|
}),
|
|
207
242
|
router.add('session.compact', async (e, next) => {
|
|
208
243
|
if (!(await wants(ContextEnd)) || e.trigger === 'precompute')
|
|
@@ -227,8 +262,10 @@ export function withAnswer(change, answer) {
|
|
|
227
262
|
return { ...change, message: { ...change.message, text: answer.text } };
|
|
228
263
|
if (change.type === Message && answer.input !== undefined && change.message.type === ToolCall)
|
|
229
264
|
return { ...change, message: { ...change.message, input: answer.input } };
|
|
230
|
-
if (change.type === TaskStart
|
|
231
|
-
|
|
265
|
+
if (change.type === TaskStart) {
|
|
266
|
+
const { agentType, model } = answer.owner ?? {};
|
|
267
|
+
return { ...change, task: { ...change.task, ...(answer.title === undefined ? {} : { title: answer.title }), ...(agentType === undefined ? {} : { agentType }), ...(model === undefined ? {} : { model }) } };
|
|
268
|
+
}
|
|
232
269
|
return change;
|
|
233
270
|
}
|
|
234
271
|
export const methodOf = (prefix, event) => `${prefix}${Object.entries(sessionEvents).find(([, each]) => each === event)?.[0] ?? event}`;
|
|
@@ -237,12 +274,13 @@ export const beforeAnswerKeys = {
|
|
|
237
274
|
note: ['text'],
|
|
238
275
|
skill: ['text'],
|
|
239
276
|
instructionFile: ['text', 'refuse'],
|
|
277
|
+
attachedFile: ['text', 'refuse'],
|
|
240
278
|
reply: ['text'],
|
|
241
|
-
toolCall: ['input', 'result', 'refuse'],
|
|
279
|
+
toolCall: ['input', 'result', 'refuse', 'note'],
|
|
242
280
|
toolResult: ['output', 'isError', 'note'],
|
|
243
281
|
notice: ['text'],
|
|
244
|
-
taskStart: ['owner', 'refuse'],
|
|
245
|
-
turnEnd: ['refuse'],
|
|
282
|
+
taskStart: ['prompt', 'title', 'owner', 'isAsync', 'refuse'],
|
|
283
|
+
turnEnd: ['refuse', 'note'],
|
|
246
284
|
contextEnd: ['refuse'],
|
|
247
285
|
};
|
|
248
286
|
export function foldBefore(event, change, askers, report) {
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { RenderElement } from 'claude-code';
|
|
2
|
+
export type FilledButton = {
|
|
3
|
+
readonly key: string;
|
|
4
|
+
readonly label: string;
|
|
5
|
+
readonly hotkey?: string;
|
|
6
|
+
readonly variant?: 'primary' | 'secondary';
|
|
7
|
+
readonly onPress: () => void;
|
|
8
|
+
};
|
|
9
|
+
export declare function filledButton({ key, label, hotkey, variant, onPress }: FilledButton): RenderElement;
|
|
10
|
+
//# sourceMappingURL=buttons.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"buttons.d.ts","sourceRoot":"","sources":["../src/runtime/buttons.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAGhD,MAAM,MAAM,YAAY,GAAG;IACzB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,OAAO,CAAC,EAAE,SAAS,GAAG,WAAW,CAAA;IAC1C,QAAQ,CAAC,OAAO,EAAE,MAAM,IAAI,CAAA;CAC7B,CAAA;AAED,wBAAgB,YAAY,CAAC,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM,EAAE,OAAqB,EAAE,OAAO,EAAE,EAAE,YAAY,GAAG,aAAa,CAUhH"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { Button, Text } from '../ui/elements.js';
|
|
2
|
+
export function filledButton({ key, label, hotkey, variant = 'secondary', onPress }) {
|
|
3
|
+
const isPrimary = variant === 'primary';
|
|
4
|
+
return Button({
|
|
5
|
+
plain: true,
|
|
6
|
+
key,
|
|
7
|
+
label,
|
|
8
|
+
...(hotkey === undefined ? {} : { hotkey }),
|
|
9
|
+
onPress,
|
|
10
|
+
children: Text({ backgroundColor: isPrimary ? 'claude' : 'userMessageBackground', color: isPrimary ? 'inverseText' : 'text', bold: isPrimary, children: ` ${label} ` }),
|
|
11
|
+
});
|
|
12
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"conversations.d.ts","sourceRoot":"","sources":["../src/runtime/conversations.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AAK5D,OAAO,
|
|
1
|
+
{"version":3,"file":"conversations.d.ts","sourceRoot":"","sources":["../src/runtime/conversations.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AAK5D,OAAO,EAA0L,KAAK,QAAQ,EAAmB,MAAM,4BAA4B,CAAA;AAUnQ,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AAGzC,OAAO,KAAK,EAAc,MAAM,EAAE,MAAM,aAAa,CAAA;AAyCrD,MAAM,MAAM,aAAa,GAAG;IAC1B,MAAM,CAAC,CAAC,EAAE,MAAM,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,GAAG;QAAE,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,WAAW,CAAC,WAAW,CAAC,GAAG,SAAS,CAAC,CAAA;IAClH,KAAK,IAAI,IAAI,CAAA;IACb,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,QAAQ,GAAG,SAAS,CAAA;CAClD,CAAA;AAMD,wBAAgB,kBAAkB,CAAC,EACjC,MAAM,EACN,MAAM,EACN,KAAK,EACL,OAAO,EACP,MAAM,EACN,KAAK,GACN,EAAE;IACD,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,OAAO,EAAE,MAAM,MAAM,CAAA;IAC9B,QAAQ,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,KAAK,IAAI,CAAA;IACtE,QAAQ,CAAC,KAAK,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAA;CACrC,GAAG,aAAa,CA4rBhB"}
|
package/runtime/conversations.js
CHANGED
|
@@ -2,19 +2,22 @@ import { consentPath, coreVersion, dataFolder, grantFix, isObject, parseConsent
|
|
|
2
2
|
import { Message, rowOfAppend } from '../session/context/turn/message/message.js';
|
|
3
3
|
import { modNote, Note } from '../session/context/turn/message/note.js';
|
|
4
4
|
import { Person } from '../session/context/turn/message/sender.js';
|
|
5
|
-
import { createConversation, emptyConversation } from '../session/conversation.js';
|
|
5
|
+
import { createConversation, emptyConversation, ownerOfSpawn, tasksWithAgents } from '../session/conversation.js';
|
|
6
6
|
import { Session, UsageChange } from '../session/session.js';
|
|
7
7
|
import { GoalChange } from '../session/goal/goal.js';
|
|
8
8
|
import { TurnEnd, TurnStart } from '../session/context/turn/turn.js';
|
|
9
|
+
import { TaskStart } from '../session/goal/task.js';
|
|
9
10
|
import { receivedMatcher } from '../utils/matcher.js';
|
|
11
|
+
import { resolve } from '../vendor.js';
|
|
10
12
|
import { ifMissing } from '../utils/paths.js';
|
|
11
13
|
import { formatExit, messageOf } from '../utils/text.js';
|
|
12
14
|
import { beforeAnswerKeys, foldBefore, installBefore, relayOf } from './before.js';
|
|
13
15
|
import { cmodPluginName } from './dependencies.js';
|
|
14
|
-
import { readLines, runCmod } from './programs.js';
|
|
16
|
+
import { readLines, runCmod, spawnCmod } from './programs.js';
|
|
15
17
|
import { PassedTimeout } from './deadline.js';
|
|
16
18
|
import { actPermission, conversationMethods, everySession, isAct, isHeardBy, isInScope, resultOf, spawnTask, usageOf } from './sessions.js';
|
|
17
|
-
const relayKinds = new Set(['act', 'before', 'answer', 'watch', 'goal']);
|
|
19
|
+
const relayKinds = new Set(['act', 'before', 'answer', 'watch', 'goal', 'taskEnd']);
|
|
20
|
+
const taskVariable = 'CMOD_TASK';
|
|
18
21
|
const isRelay = (value) => isObject(value) && relayKinds.has(value['kind']);
|
|
19
22
|
const bufferedTaskRows = 200;
|
|
20
23
|
const beforeDeadlineMs = 5000;
|
|
@@ -25,8 +28,12 @@ const keptMessages = 200;
|
|
|
25
28
|
export function serveConversations({ claude, router, store, project, report, clear, }) {
|
|
26
29
|
const folder = dataFolder(store, cmodPluginName);
|
|
27
30
|
const goals = `${folder}/goals`;
|
|
31
|
+
const backgroundTasks = `${folder}/tasks`;
|
|
28
32
|
const watches = `${folder}/before`;
|
|
33
|
+
const listeners = `${folder}/listeners`;
|
|
29
34
|
const heard = new Map();
|
|
35
|
+
const subscribed = new Map();
|
|
36
|
+
let savingListeners = Promise.resolve();
|
|
30
37
|
const asked = new Map();
|
|
31
38
|
const ownWatches = new Map();
|
|
32
39
|
let watchers = [];
|
|
@@ -57,6 +64,12 @@ export function serveConversations({ claude, router, store, project, report, cle
|
|
|
57
64
|
router.add('classic.Stop', async (e, next) => endTurn({ reason: 'answer', ...(e.last_assistant_message === undefined ? {} : { answer: e.last_assistant_message }) })(await next(e)));
|
|
58
65
|
router.add('classic.StopFailure', async (e, next) => endTurn({ reason: 'error', failure: { type: String(e.error), text: e.error_details ?? e.last_assistant_message ?? String(e.error) } })(await next(e)));
|
|
59
66
|
router.add('classic.SubagentStop', async (e, next) => endTurn({ reason: 'answer', ...(e.last_assistant_message === undefined ? {} : { answer: e.last_assistant_message }) }, e.agent_id)(await next(e)));
|
|
67
|
+
router.add('classic.Stop', async (e, next) => {
|
|
68
|
+
const below = await next(e);
|
|
69
|
+
if (below.block === undefined)
|
|
70
|
+
void answerTask().catch((error) => report('task', 'Could not record that this session answered its task', error));
|
|
71
|
+
return below;
|
|
72
|
+
});
|
|
60
73
|
const json = async (args) => JSON.parse(await runCmod(claude, [...args, '--json', '--project', project()], await claude.session.id()));
|
|
61
74
|
const isGranted = async (caller, item) => {
|
|
62
75
|
if (caller === cmodPluginName)
|
|
@@ -103,7 +116,7 @@ export function serveConversations({ claude, router, store, project, report, cle
|
|
|
103
116
|
follower = undefined;
|
|
104
117
|
if (args.length === 0)
|
|
105
118
|
return;
|
|
106
|
-
const stream = claude
|
|
119
|
+
const stream = spawnCmod(claude, ['history', 'follow', ...args, '--json', '--project', project()], session);
|
|
107
120
|
const started = { sessions: wanted, stop: () => void stream.return(undefined) };
|
|
108
121
|
follower = started;
|
|
109
122
|
void readLines(stream, (line) => {
|
|
@@ -194,14 +207,42 @@ export function serveConversations({ claude, router, store, project, report, cle
|
|
|
194
207
|
const told = new Set([...running, ...targets].filter((id) => id !== everySession && id !== session));
|
|
195
208
|
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' }))));
|
|
196
209
|
};
|
|
210
|
+
const hear = (caller, id, list) => {
|
|
211
|
+
const sessions = heard.get(caller) ?? new Map();
|
|
212
|
+
sessions.set(id, list.map(({ event, matcher }) => ({ event, matcher: matcher === undefined ? undefined : receivedMatcher(matcher) })));
|
|
213
|
+
heard.set(caller, sessions);
|
|
214
|
+
const lists = subscribed.get(caller) ?? new Map();
|
|
215
|
+
lists.set(id, list);
|
|
216
|
+
subscribed.set(caller, lists);
|
|
217
|
+
};
|
|
218
|
+
const saveListeners = (isEnding = false) => {
|
|
219
|
+
const at = session;
|
|
220
|
+
const kept = isEnding ? {} : Object.fromEntries([...subscribed].map(([caller, lists]) => [caller, Object.fromEntries(lists)]));
|
|
221
|
+
const next = savingListeners.then(() => (at === '' ? undefined : claude.fs.write(`${listeners}/${at}.json`, JSON.stringify(kept))));
|
|
222
|
+
savingListeners = next.catch((error) => claude.ui.log(`cmod: saving the mods' listeners failed: ${messageOf(error)}`, { to: 'debug' }));
|
|
223
|
+
return next;
|
|
224
|
+
};
|
|
225
|
+
const restoreListeners = async () => {
|
|
226
|
+
const kept = JSON.parse(await claude.fs.read(`${listeners}/${session}.json`).catch(ifMissing('{}')));
|
|
227
|
+
for (const [caller, lists] of Object.entries(kept))
|
|
228
|
+
if (!subscribed.has(caller))
|
|
229
|
+
for (const [id, list] of Object.entries(lists))
|
|
230
|
+
hear(caller, id, list);
|
|
231
|
+
};
|
|
197
232
|
const readWatchers = async () => {
|
|
198
233
|
const files = await claude.fs.list(watches).catch(ifMissing([]));
|
|
199
234
|
const found = [];
|
|
200
235
|
for (const file of files) {
|
|
201
236
|
const from = file.name.replace(/\.json$/, '');
|
|
202
|
-
if (
|
|
237
|
+
if (!file.name.endsWith('.json'))
|
|
203
238
|
continue;
|
|
204
239
|
const list = JSON.parse(await claude.fs.read(`${watches}/${file.name}`).catch(ifMissing('[]')));
|
|
240
|
+
if (from === session) {
|
|
241
|
+
for (const watch of list)
|
|
242
|
+
if (!ownWatches.has(`${watch.mod}\n${watch.target}`))
|
|
243
|
+
ownWatches.set(`${watch.mod}\n${watch.target}`, watch);
|
|
244
|
+
continue;
|
|
245
|
+
}
|
|
205
246
|
found.push(...list.filter((watch) => watch.target === session || (watch.target === everySession && watch.project === project())).map((watch) => ({ ...watch, from })));
|
|
206
247
|
}
|
|
207
248
|
watchers = found;
|
|
@@ -249,6 +290,19 @@ export function serveConversations({ claude, router, store, project, report, cle
|
|
|
249
290
|
return undefined;
|
|
250
291
|
}
|
|
251
292
|
};
|
|
293
|
+
const wants = async (event) => {
|
|
294
|
+
await Promise.all([starting, watching]);
|
|
295
|
+
return deciders(event).length > 0;
|
|
296
|
+
};
|
|
297
|
+
const decide = () => {
|
|
298
|
+
let endsAt;
|
|
299
|
+
return (event, change) => foldBefore(event, change, deciders(event).map((watcher) => async (latest) => {
|
|
300
|
+
if (watcher.from === session)
|
|
301
|
+
return askHere(watcher.mod, event, latest);
|
|
302
|
+
endsAt ??= claude.clock.now().then((now) => now + awayFoldMs);
|
|
303
|
+
return askAway(watcher, event, latest, await endsAt);
|
|
304
|
+
}), (error) => report(`before:${event}`, `Passed the ${event} on without a mod's answer`, error));
|
|
305
|
+
};
|
|
252
306
|
installBefore({
|
|
253
307
|
claude,
|
|
254
308
|
router,
|
|
@@ -257,19 +311,8 @@ export function serveConversations({ claude, router, store, project, report, cle
|
|
|
257
311
|
await starting;
|
|
258
312
|
return place();
|
|
259
313
|
},
|
|
260
|
-
wants
|
|
261
|
-
|
|
262
|
-
return deciders(event).length > 0;
|
|
263
|
-
},
|
|
264
|
-
fold: () => {
|
|
265
|
-
let endsAt;
|
|
266
|
-
return (event, change) => foldBefore(event, change, deciders(event).map((watcher) => async (latest) => {
|
|
267
|
-
if (watcher.from === session)
|
|
268
|
-
return askHere(watcher.mod, event, latest);
|
|
269
|
-
endsAt ??= claude.clock.now().then((now) => now + awayFoldMs);
|
|
270
|
-
return askAway(watcher, event, latest, await endsAt);
|
|
271
|
-
}), (error) => report(`before:${event}`, `Passed the ${event} on without a mod's answer`, error));
|
|
272
|
-
},
|
|
314
|
+
wants,
|
|
315
|
+
fold: decide,
|
|
273
316
|
},
|
|
274
317
|
});
|
|
275
318
|
const start = (id, source) => {
|
|
@@ -288,8 +331,13 @@ export function serveConversations({ claude, router, store, project, report, cle
|
|
|
288
331
|
if (session === id)
|
|
289
332
|
conversation = createConversation({ ...emptyConversation(id), ...state });
|
|
290
333
|
}, (error) => report('state', `Starts session ${id} without its earlier turns`, error));
|
|
291
|
-
|
|
334
|
+
const restored = restoreListeners()
|
|
335
|
+
.catch((error) => report('listeners', "Could not read back the mods' listeners of this session", error))
|
|
336
|
+
.then(follow);
|
|
337
|
+
starting = Promise.all([starting, restored]).then(() => undefined);
|
|
292
338
|
reloadWatchers();
|
|
339
|
+
if (subscribed.size > 0)
|
|
340
|
+
void Promise.all([previous === '' ? undefined : claude.fs.write(`${listeners}/${previous}.json`, '{}'), saveListeners()]).catch((error) => report('follow', 'Kept telling the mods about the session from before', error));
|
|
293
341
|
if (ownWatches.size === 0)
|
|
294
342
|
return;
|
|
295
343
|
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));
|
|
@@ -322,6 +370,8 @@ export function serveConversations({ claude, router, store, project, report, cle
|
|
|
322
370
|
router.add('classic.SessionEnd', async (e, next) => {
|
|
323
371
|
if (ownWatches.size > 0)
|
|
324
372
|
await publishWatches(true).catch((error) => report('before', 'Left its before listeners registered', error));
|
|
373
|
+
if (subscribed.size > 0)
|
|
374
|
+
await saveListeners(true).catch((error) => report('follow', 'Left its listeners registered', error));
|
|
325
375
|
return next(e);
|
|
326
376
|
});
|
|
327
377
|
const recordAppend = async (e, next) => {
|
|
@@ -354,8 +404,8 @@ export function serveConversations({ claude, router, store, project, report, cle
|
|
|
354
404
|
if (spawned.deny !== undefined || spawned.agentId === undefined)
|
|
355
405
|
return spawned;
|
|
356
406
|
const id = spawned.agentId;
|
|
357
|
-
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 }) };
|
|
358
407
|
await starting;
|
|
408
|
+
const opened = { id, title: e.description, ...ownerOfSpawn(e.subagentType, session), model: spawned.model, openedBy: e.tool_use_id, openedAt: new Date().toISOString(), ...(e.parentAgentId === undefined ? {} : { parent: e.parentAgentId }) };
|
|
359
409
|
dispatch(session, conversation.startTask(opened));
|
|
360
410
|
const waiting = waitingRows.get(id) ?? [];
|
|
361
411
|
waitingRows.delete(id);
|
|
@@ -394,6 +444,11 @@ export function serveConversations({ claude, router, store, project, report, cle
|
|
|
394
444
|
changeGoal(relay.task, relay.text);
|
|
395
445
|
return;
|
|
396
446
|
}
|
|
447
|
+
if (relay.kind === 'taskEnd') {
|
|
448
|
+
await starting;
|
|
449
|
+
dispatch(session, conversation.endTask(relay.task, relay.task.closedAt ?? new Date().toISOString()));
|
|
450
|
+
return;
|
|
451
|
+
}
|
|
397
452
|
const answered = async (run) => {
|
|
398
453
|
try {
|
|
399
454
|
await post(relay.from, { kind: 'answer', id: relay.id, value: (await run()) ?? null });
|
|
@@ -435,6 +490,12 @@ export function serveConversations({ claude, router, store, project, report, cle
|
|
|
435
490
|
}
|
|
436
491
|
if (action === 'startTask')
|
|
437
492
|
return spawnTask(claude, input);
|
|
493
|
+
if (action === 'fork')
|
|
494
|
+
return spawnTask(claude, input, session);
|
|
495
|
+
if (action === 'startBackgroundTask')
|
|
496
|
+
return startBackground(input);
|
|
497
|
+
if (action === 'clone')
|
|
498
|
+
return startBackground(input, session);
|
|
438
499
|
const { name, input: toolInput } = input;
|
|
439
500
|
return resultOf(place(), { name, input: toolInput, cwd: await claude.session.cwd() }, await claude.tool.call({ ...toolInput, tool: name }));
|
|
440
501
|
};
|
|
@@ -465,6 +526,51 @@ export function serveConversations({ claude, router, store, project, report, cle
|
|
|
465
526
|
throw ended(task);
|
|
466
527
|
return null;
|
|
467
528
|
};
|
|
529
|
+
const startBackground = async (task, forkedFrom) => {
|
|
530
|
+
if (task.title.trim() === '')
|
|
531
|
+
throw new Error('a task needs a title.');
|
|
532
|
+
if (task.name !== undefined)
|
|
533
|
+
throw new Error('a background task takes no name.');
|
|
534
|
+
const fresh = forkedFrom === undefined ? task : {};
|
|
535
|
+
const asked = { id: '', title: task.title, ...(forkedFrom === undefined ? {} : { forkedFrom }), ...(fresh.agentType === undefined ? {} : { agentType: fresh.agentType }), ...(fresh.model === undefined ? {} : { model: fresh.model }), openedBy: '', openedAt: new Date().toISOString(), isBackground: true };
|
|
536
|
+
const answer = (await wants(TaskStart)) ? await decide()(TaskStart, { type: TaskStart, task: { ...asked, context: conversation.state.context } }) : undefined;
|
|
537
|
+
if (answer?.refuse !== undefined)
|
|
538
|
+
throw new Error(`a beforeTaskStart listener refused the task: ${answer.refuse}`);
|
|
539
|
+
const owner = forkedFrom === undefined ? (answer?.owner ?? {}) : {};
|
|
540
|
+
const folder = owner.cwd ?? fresh.cwd;
|
|
541
|
+
const here = await claude.session.cwd();
|
|
542
|
+
const args = [
|
|
543
|
+
'task',
|
|
544
|
+
'start',
|
|
545
|
+
'--title',
|
|
546
|
+
answer?.title ?? task.title,
|
|
547
|
+
...(forkedFrom === undefined ? [] : ['--clone']),
|
|
548
|
+
...textArg('--agent', owner.agentType ?? fresh.agentType),
|
|
549
|
+
...textArg('--model', owner.model ?? fresh.model),
|
|
550
|
+
'--cwd',
|
|
551
|
+
folder === undefined ? here : resolve(here, folder),
|
|
552
|
+
'--json',
|
|
553
|
+
'--',
|
|
554
|
+
answer?.prompt ?? task.prompt,
|
|
555
|
+
];
|
|
556
|
+
const started = JSON.parse(await runCmod(claude, args, session));
|
|
557
|
+
dispatch(session, conversation.startTask(started));
|
|
558
|
+
return started;
|
|
559
|
+
};
|
|
560
|
+
const answerTask = async () => {
|
|
561
|
+
const { env } = await claude.settings.read();
|
|
562
|
+
const asked = isObject(env) ? env[taskVariable] : undefined;
|
|
563
|
+
if (typeof asked !== 'string')
|
|
564
|
+
return;
|
|
565
|
+
const answered = `cmod:task-answered:${session}`;
|
|
566
|
+
if ((await claude.store.get(answered)) === true)
|
|
567
|
+
return;
|
|
568
|
+
await claude.store.set(answered, true);
|
|
569
|
+
const { session: from, ...task } = JSON.parse(asked);
|
|
570
|
+
const ended = { ...task, id: session, closedAt: new Date().toISOString() };
|
|
571
|
+
await claude.fs.write(`${backgroundTasks}/${from}/${session}.json`, JSON.stringify(ended));
|
|
572
|
+
await post(from, { kind: 'taskEnd', task: ended }).catch((error) => claude.ui.log(`cmod: session ${from} did not hear that its task ${session} answered: ${messageOf(error)}`, { to: 'debug' }));
|
|
573
|
+
};
|
|
468
574
|
const actElsewhere = async (caller, target, action, input, task) => {
|
|
469
575
|
await checkAct(caller, action, input);
|
|
470
576
|
const isLive = await isRunning(target);
|
|
@@ -493,12 +599,14 @@ export function serveConversations({ claude, router, store, project, report, cle
|
|
|
493
599
|
const liveTasks = () => Object.values(conversation.state.tasks).map((task) => ({
|
|
494
600
|
id: task.id,
|
|
495
601
|
title: task.title,
|
|
496
|
-
agentType: task.agentType,
|
|
602
|
+
...(task.agentType === undefined ? {} : { agentType: task.agentType }),
|
|
497
603
|
...(task.model === undefined ? {} : { model: task.model }),
|
|
498
604
|
openedBy: task.openedBy,
|
|
499
605
|
openedAt: task.openedAt,
|
|
500
606
|
...(task.closedAt === undefined ? {} : { closedAt: task.closedAt }),
|
|
501
607
|
...(task.parent === undefined ? {} : { parent: task.parent }),
|
|
608
|
+
...(task.forkedFrom === undefined ? {} : { forkedFrom: task.forkedFrom }),
|
|
609
|
+
...(task.isBackground === true ? { isBackground: true } : {}),
|
|
502
610
|
}));
|
|
503
611
|
const liveContext = (task) => {
|
|
504
612
|
if (typeof task === 'string') {
|
|
@@ -565,7 +673,9 @@ export function serveConversations({ claude, router, store, project, report, cle
|
|
|
565
673
|
return saved;
|
|
566
674
|
await starting;
|
|
567
675
|
const kept = liveTasks().filter((task) => isNested || task.parent === request['task']);
|
|
568
|
-
|
|
676
|
+
const running = await claude.agent.list().catch((error) => (claude.ui.log(`cmod: the tasks show no status, because Claude Code listed no agents: ${messageOf(error)}`, { to: 'debug' }), []));
|
|
677
|
+
const background = saved.flatMap(({ id, status }) => (status === undefined ? [] : [{ id, status }]));
|
|
678
|
+
return tasksWithAgents(merged(saved, kept, (task) => task.id), [...running, ...background]);
|
|
569
679
|
},
|
|
570
680
|
async [conversationMethods.goal](request) {
|
|
571
681
|
const id = String(request['session']);
|
|
@@ -597,10 +707,9 @@ export function serveConversations({ claude, router, store, project, report, cle
|
|
|
597
707
|
throw new Error('cmod hears no caller for this subscription.');
|
|
598
708
|
const id = String(request['session']);
|
|
599
709
|
const list = Array.isArray(request['subscriptions']) ? request['subscriptions'] : [];
|
|
600
|
-
|
|
601
|
-
sessions.set(id, list.map(({ event, matcher }) => ({ event, matcher: matcher === undefined ? undefined : receivedMatcher(matcher) })));
|
|
602
|
-
heard.set(caller, sessions);
|
|
710
|
+
hear(caller, id, list);
|
|
603
711
|
follow();
|
|
712
|
+
await saveListeners();
|
|
604
713
|
return null;
|
|
605
714
|
},
|
|
606
715
|
async [conversationMethods.watchBefore](request, caller) {
|
package/runtime/engine.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { HookStream, ProcessSpawnChunk, ProcessSpawnRequest, ProcessSpawnResult } from 'claude-code';
|
|
1
2
|
import type { Claude } from './claude.js';
|
|
2
3
|
import type { Pages } from './pages.js';
|
|
3
4
|
import type { Router } from './router.js';
|
|
@@ -11,6 +12,7 @@ export type Engine = {
|
|
|
11
12
|
readonly report: (failure: Failure) => void;
|
|
12
13
|
readonly hasPermissionCheck: () => boolean;
|
|
13
14
|
readonly answerInput: <Answer>(run: () => Promise<Answer>, passOn: () => Promise<unknown>) => Promise<Answer>;
|
|
15
|
+
readonly spawnCmod: (args: readonly string[], program: ProcessSpawnRequest) => Promise<HookStream<ProcessSpawnChunk, ProcessSpawnResult>>;
|
|
14
16
|
readonly widgets?: Widgets;
|
|
15
17
|
readonly pages?: Pages;
|
|
16
18
|
};
|
package/runtime/engine.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../src/runtime/engine.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AACzC,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,YAAY,CAAA;AACvC,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;AAChD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAA;AAE3C,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;IAC3C,QAAQ,CAAC,kBAAkB,EAAE,MAAM,OAAO,CAAA;IAC1C,QAAQ,CAAC,WAAW,EAAE,CAAC,MAAM,EAAE,GAAG,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,KAAK,OAAO,CAAC,MAAM,CAAC,CAAA;IAC7G,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAA;IAC1B,QAAQ,CAAC,KAAK,CAAC,EAAE,KAAK,CAAA;CACvB,CAAA;AAID,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,CAE5D;AAED,wBAAgB,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAI5C"}
|
|
1
|
+
{"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../src/runtime/engine.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAA;AACzG,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AACzC,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,YAAY,CAAA;AACvC,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;AAChD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAA;AAE3C,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;IAC3C,QAAQ,CAAC,kBAAkB,EAAE,MAAM,OAAO,CAAA;IAC1C,QAAQ,CAAC,WAAW,EAAE,CAAC,MAAM,EAAE,GAAG,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,KAAK,OAAO,CAAC,MAAM,CAAC,CAAA;IAC7G,QAAQ,CAAC,SAAS,EAAE,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,EAAE,OAAO,EAAE,mBAAmB,KAAK,OAAO,CAAC,UAAU,CAAC,iBAAiB,EAAE,kBAAkB,CAAC,CAAC,CAAA;IACzI,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAA;IAC1B,QAAQ,CAAC,KAAK,CAAC,EAAE,KAAK,CAAA;CACvB,CAAA;AAID,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,CAE5D;AAED,wBAAgB,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAI5C"}
|
package/runtime/grants.d.ts
CHANGED
|
@@ -15,6 +15,7 @@ export declare class PermissionRefused extends Error {
|
|
|
15
15
|
}
|
|
16
16
|
export declare function programAddresses(mod: object): Set<string>;
|
|
17
17
|
export declare function checkGrant(grants: Grants, call: string, item: string | undefined): Promise<void>;
|
|
18
|
+
export declare function checkRun(grants: Grants, call: 'process.run' | 'process.spawn', argv: readonly string[]): Promise<void>;
|
|
18
19
|
export declare function checkWrite(grants: Grants, call: string, path: string): Promise<void>;
|
|
19
20
|
export declare function checkSettings(grants: Grants, source: 'user' | 'project' | 'local', values: Readonly<Record<string, unknown>>): Promise<void>;
|
|
20
21
|
export declare function checkingGrants(claude: Claude, grants: Grants): Claude;
|
package/runtime/grants.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"grants.d.ts","sourceRoot":"","sources":["../src/runtime/grants.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AAEzC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAA;AAG7C,MAAM,MAAM,MAAM,GAAG;IACnB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAA;IACpC,QAAQ,CAAC,OAAO,EAAE,MAAM,WAAW,CAAC,MAAM,CAAC,CAAA;IAC3C,QAAQ,CAAC,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;IACrC,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,CAAA;IACjC,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,SAAS,CAAA;IACvC,QAAQ,CAAC,WAAW,EAAE,MAAM,MAAM,CAAA;IAClC,QAAQ,CAAC,WAAW,EAAE,MAAM,SAAS,MAAM,EAAE,CAAA;IAC7C,QAAQ,CAAC,aAAa,EAAE,MAAM,WAAW,CAAC,MAAM,CAAC,CAAA;CAClD,CAAA;AAED,qBAAa,iBAAkB,SAAQ,KAAK;CAAG;AAI/C,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC,CAIzD;AAkBD,wBAAsB,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC,CAItG;AAED,wBAAsB,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAE1F;AAED,wBAAsB,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,EAAE,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAElJ;AAED,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAwFrE;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,CAWvG;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;AA0BD,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":"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"}
|
package/runtime/grants.js
CHANGED
|
@@ -31,6 +31,9 @@ export async function checkGrant(grants, call, item) {
|
|
|
31
31
|
if (!isCovered(grants.granted(), item, grants.home))
|
|
32
32
|
throw new PermissionRefused(refusal(grants, call, item));
|
|
33
33
|
}
|
|
34
|
+
export async function checkRun(grants, call, argv) {
|
|
35
|
+
await checkGrant(grants, `${call}(${argv.join(' ')})`, `run:${programOf(argv)}`);
|
|
36
|
+
}
|
|
34
37
|
export async function checkWrite(grants, call, path) {
|
|
35
38
|
await checkGrant(grants, call, writeItem(grants, path));
|
|
36
39
|
}
|
|
@@ -44,11 +47,11 @@ export function checkingGrants(claude, grants) {
|
|
|
44
47
|
...claude,
|
|
45
48
|
process: {
|
|
46
49
|
async run(argv, init) {
|
|
47
|
-
await
|
|
50
|
+
await checkRun(grants, 'process.run', argv);
|
|
48
51
|
return claude.process.run(argv, init);
|
|
49
52
|
},
|
|
50
53
|
spawn(request) {
|
|
51
|
-
const started =
|
|
54
|
+
const started = checkRun(grants, 'process.spawn', request.argv).then(() => claude.process.spawn(request));
|
|
52
55
|
return spawned(started);
|
|
53
56
|
},
|
|
54
57
|
},
|
package/runtime/hotkeys.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hotkeys.d.ts","sourceRoot":"","sources":["../src/runtime/hotkeys.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,eAAe,CAAA;AAIpD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;
|
|
1
|
+
{"version":3,"file":"hotkeys.d.ts","sourceRoot":"","sources":["../src/runtime/hotkeys.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,eAAe,CAAA;AAIpD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AAEzC,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AACzC,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAA;AAE3C,wBAAsB,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAS7G;AAED,wBAAsB,aAAa,CAAC,EAClC,IAAI,EACJ,QAAQ,EACR,UAAU,EACV,MAAM,EACN,MAAM,EACN,WAAW,EACX,MAAM,GACP,EAAE;IACD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAA;IAC1B,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,SAAS,CAAA;IACvC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,WAAW,EAAE,CAAC,MAAM,EAAE,GAAG,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,KAAK,OAAO,CAAC,MAAM,CAAC,CAAA;IAC7G,QAAQ,CAAC,MAAM,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,CAAA;CAC5C,GAAG,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC,CA8C5C"}
|
package/runtime/hotkeys.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { boundCombos, hotkeyCommand } from '../records.js';
|
|
2
2
|
import { ifMissing } from '../utils/paths.js';
|
|
3
3
|
import { messageOf } from '../utils/text.js';
|
|
4
|
+
import { cmodPluginName } from './dependencies.js';
|
|
4
5
|
export async function readCombos(claude, configRoot) {
|
|
5
6
|
if (configRoot === undefined)
|
|
6
7
|
return new Map();
|
|
@@ -14,6 +15,12 @@ export async function readCombos(claude, configRoot) {
|
|
|
14
15
|
}
|
|
15
16
|
}
|
|
16
17
|
export async function createHotkeys({ name, declared, configRoot, claude, router, answerInput, report, }) {
|
|
18
|
+
if (name === cmodPluginName) {
|
|
19
|
+
router.add('command.describe', async (e, next) => {
|
|
20
|
+
const described = await next(e);
|
|
21
|
+
return e.command.startsWith(hotkeyCommand(e.provider.plugin, '')) ? { ...described, isHidden: true } : described;
|
|
22
|
+
});
|
|
23
|
+
}
|
|
17
24
|
const entries = Object.entries(declared);
|
|
18
25
|
if (entries.length === 0)
|
|
19
26
|
return {};
|
|
@@ -24,10 +31,6 @@ export async function createHotkeys({ name, declared, configRoot, claude, router
|
|
|
24
31
|
listeners.set(hotkey, new Set());
|
|
25
32
|
claude.command.register({ name: hotkeyCommand(name, hotkey), description: title, immediate: true }).then(({ command }) => void commands.set(command, hotkey), (error) => report({ id: `hotkey:${hotkey}`, message: `The ${title} hotkey is not added`, error }));
|
|
26
33
|
}
|
|
27
|
-
router.add('command.describe', async (e, next) => {
|
|
28
|
-
const described = await next(e);
|
|
29
|
-
return commands.has(e.command) ? { ...described, isHidden: true } : described;
|
|
30
|
-
});
|
|
31
34
|
router.add('command.run', async (e, next) => {
|
|
32
35
|
const hotkey = commands.get(e.command);
|
|
33
36
|
if (hotkey === undefined)
|