@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.
- package/docs/commands.md +58 -10
- package/docs/dependencies.md +12 -12
- package/docs/hooks.md +41 -28
- package/docs/index.md +29 -25
- package/docs/install-steps.md +9 -7
- package/docs/jobs.md +59 -111
- package/docs/mod.md +200 -174
- package/docs/options.md +2 -2
- package/docs/permissions.md +27 -16
- package/docs/session.md +248 -0
- package/docs/state.md +11 -7
- package/docs/testing.md +66 -23
- package/docs/ui.md +108 -76
- package/jobs/check.d.ts.map +1 -1
- package/jobs/check.js +5 -2
- package/jobs/context.d.ts +7 -8
- package/jobs/context.d.ts.map +1 -1
- package/jobs/context.js +23 -73
- package/jobs/permissions/decide-permission.d.ts +5 -3
- package/jobs/permissions/decide-permission.d.ts.map +1 -1
- package/jobs/permissions/decide-permission.js +6 -4
- package/jobs/permissions/find-project-scope.d.ts +1 -1
- package/jobs/permissions/find-project-scope.d.ts.map +1 -1
- package/jobs/permissions/find-project-scope.js +8 -11
- package/jobs/permissions/match-target.d.ts +2 -2
- package/jobs/permissions/match-target.d.ts.map +1 -1
- package/jobs/permissions.d.ts +1 -1
- package/jobs/permissions.d.ts.map +1 -1
- package/jobs/permissions.js +6 -4
- package/jobs/program.d.ts.map +1 -1
- package/jobs/program.js +8 -3
- package/jobs/prompt.d.ts +4 -2
- package/jobs/prompt.d.ts.map +1 -1
- package/jobs/prompt.js +16 -15
- package/jobs/schedule.d.ts +2 -2
- package/jobs/schedule.d.ts.map +1 -1
- package/jobs/schedule.js +9 -9
- package/jobs/slash-command.d.ts +4 -4
- package/jobs/slash-command.d.ts.map +1 -1
- package/jobs/slash-command.js +14 -9
- package/jobs/status-line.d.ts +1 -11
- package/jobs/status-line.d.ts.map +1 -1
- package/jobs/status-line.js +8 -12
- package/jobs/tool.d.ts +2 -2
- package/jobs/tool.d.ts.map +1 -1
- package/jobs/tool.js +15 -9
- package/mod.d.ts +154 -75
- package/mod.d.ts.map +1 -1
- package/mod.js +94 -3
- package/options.d.ts +2 -2
- package/options.js +19 -19
- package/package.json +3 -3
- package/records.d.ts +10 -8
- package/records.d.ts.map +1 -1
- package/records.js +28 -17
- package/register.d.ts.map +1 -1
- package/register.js +18 -6
- package/runtime/announce.d.ts +18 -0
- package/runtime/announce.d.ts.map +1 -0
- package/runtime/announce.js +66 -0
- package/runtime/bound.d.ts +4 -0
- package/runtime/bound.d.ts.map +1 -0
- package/runtime/bound.js +9 -0
- package/runtime/claude.d.ts +3 -3
- package/runtime/claude.d.ts.map +1 -1
- package/runtime/conversations.d.ts +16 -0
- package/runtime/conversations.d.ts.map +1 -0
- package/runtime/conversations.js +291 -0
- package/runtime/deadline.d.ts +0 -1
- package/runtime/deadline.d.ts.map +1 -1
- package/runtime/deadline.js +1 -1
- package/runtime/dependencies.d.ts +2 -0
- package/runtime/dependencies.d.ts.map +1 -1
- package/runtime/dependencies.js +9 -0
- package/runtime/engine.d.ts +17 -0
- package/runtime/engine.d.ts.map +1 -0
- package/runtime/engine.js +10 -0
- package/runtime/grants.d.ts +4 -0
- package/runtime/grants.d.ts.map +1 -1
- package/runtime/grants.js +63 -33
- package/runtime/hooks.d.ts +5 -4
- package/runtime/hooks.d.ts.map +1 -1
- package/runtime/hooks.js +10 -5
- package/runtime/installer.d.ts.map +1 -1
- package/runtime/installer.js +11 -9
- package/runtime/lifecycle.d.ts +3 -1
- package/runtime/lifecycle.d.ts.map +1 -1
- package/runtime/lifecycle.js +317 -186
- package/runtime/metadata.d.ts +3 -1
- package/runtime/metadata.d.ts.map +1 -1
- package/runtime/metadata.js +21 -11
- package/runtime/options.d.ts +3 -1
- package/runtime/options.d.ts.map +1 -1
- package/runtime/options.js +4 -4
- package/runtime/programs.d.ts +8 -0
- package/runtime/programs.d.ts.map +1 -1
- package/runtime/programs.js +39 -1
- package/runtime/router.d.ts +2 -1
- package/runtime/router.d.ts.map +1 -1
- package/runtime/router.js +11 -3
- package/runtime/sessions.d.ts +36 -0
- package/runtime/sessions.d.ts.map +1 -0
- package/runtime/sessions.js +235 -0
- package/runtime/state.d.ts +6 -2
- package/runtime/state.d.ts.map +1 -1
- package/runtime/state.js +46 -13
- package/runtime/toaster.d.ts +31 -0
- package/runtime/toaster.d.ts.map +1 -0
- package/runtime/toaster.js +176 -0
- package/runtime/ui.d.ts +6 -17
- package/runtime/ui.d.ts.map +1 -1
- package/runtime/ui.js +36 -112
- package/session/context/context.d.ts +10 -0
- package/session/context/context.d.ts.map +1 -0
- package/session/context/context.js +2 -0
- package/session/context/turn/message/document.d.ts +7 -0
- package/session/context/turn/message/document.d.ts.map +1 -0
- package/session/context/turn/message/document.js +1 -0
- package/session/context/turn/message/message.d.ts +54 -0
- package/session/context/turn/message/message.d.ts.map +1 -0
- package/session/context/turn/message/message.js +137 -0
- package/session/context/turn/message/note.d.ts +8 -0
- package/session/context/turn/message/note.d.ts.map +1 -0
- package/session/context/turn/message/note.js +1 -0
- package/session/context/turn/message/notice.d.ts +6 -0
- package/session/context/turn/message/notice.d.ts.map +1 -0
- package/session/context/turn/message/notice.js +1 -0
- package/session/context/turn/message/prompt.d.ts +8 -0
- package/session/context/turn/message/prompt.d.ts.map +1 -0
- package/session/context/turn/message/prompt.js +1 -0
- package/session/context/turn/message/reply.d.ts +6 -0
- package/session/context/turn/message/reply.d.ts.map +1 -0
- package/session/context/turn/message/reply.js +1 -0
- package/session/context/turn/message/sender.d.ts +14 -0
- package/session/context/turn/message/sender.d.ts.map +1 -0
- package/session/context/turn/message/sender.js +30 -0
- package/session/context/turn/message/skill.d.ts +7 -0
- package/session/context/turn/message/skill.d.ts.map +1 -0
- package/session/context/turn/message/skill.js +1 -0
- package/session/context/turn/message/tool-call.d.ts +9 -0
- package/session/context/turn/message/tool-call.d.ts.map +1 -0
- package/session/context/turn/message/tool-call.js +1 -0
- package/session/context/turn/message/tool-result.d.ts +8 -0
- package/session/context/turn/message/tool-result.d.ts.map +1 -0
- package/session/context/turn/message/tool-result.js +1 -0
- package/session/context/turn/turn.d.ts +12 -0
- package/session/context/turn/turn.d.ts.map +1 -0
- package/session/context/turn/turn.js +2 -0
- package/session/conversation.d.ts +64 -0
- package/session/conversation.d.ts.map +1 -0
- package/session/conversation.js +123 -0
- package/session/goal/goal.d.ts +10 -0
- package/session/goal/goal.d.ts.map +1 -0
- package/session/goal/goal.js +1 -0
- package/session/goal/task.d.ts +16 -0
- package/session/goal/task.d.ts.map +1 -0
- package/session/goal/task.js +3 -0
- package/session/session.d.ts +99 -0
- package/session/session.d.ts.map +1 -0
- package/session/session.js +1 -0
- package/src/jobs/check.ts +7 -4
- package/src/jobs/context.ts +24 -82
- package/src/jobs/permissions/decide-permission.ts +10 -8
- package/src/jobs/permissions/find-project-scope.ts +8 -14
- package/src/jobs/permissions/match-target.ts +3 -3
- package/src/jobs/permissions.ts +8 -6
- package/src/jobs/program.ts +7 -3
- package/src/jobs/prompt.ts +21 -24
- package/src/jobs/schedule.ts +14 -14
- package/src/jobs/slash-command.ts +20 -14
- package/src/jobs/status-line.ts +9 -21
- package/src/jobs/tool.ts +17 -11
- package/src/mod.ts +209 -97
- package/src/options.ts +22 -22
- package/src/records.ts +35 -23
- package/src/register.ts +18 -6
- package/src/runtime/announce.ts +74 -0
- package/src/runtime/bound.ts +12 -0
- package/src/runtime/claude.ts +5 -3
- package/src/runtime/conversations.ts +302 -0
- package/src/runtime/deadline.ts +2 -2
- package/src/runtime/dependencies.ts +12 -0
- package/src/runtime/engine.ts +22 -0
- package/src/runtime/grants.ts +66 -31
- package/src/runtime/hooks.ts +19 -10
- package/src/runtime/installer.ts +10 -9
- package/src/runtime/lifecycle.ts +313 -181
- package/src/runtime/metadata.ts +33 -11
- package/src/runtime/options.ts +19 -4
- package/src/runtime/programs.ts +40 -1
- package/src/runtime/router.ts +11 -4
- package/src/runtime/sessions.ts +242 -0
- package/src/runtime/state.ts +50 -14
- package/src/runtime/toaster.ts +218 -0
- package/src/runtime/ui.ts +42 -139
- package/src/session/context/context.ts +12 -0
- package/src/session/context/turn/message/document.ts +8 -0
- package/src/session/context/turn/message/message.ts +165 -0
- package/src/session/context/turn/message/note.ts +9 -0
- package/src/session/context/turn/message/notice.ts +7 -0
- package/src/session/context/turn/message/prompt.ts +9 -0
- package/src/session/context/turn/message/reply.ts +7 -0
- package/src/session/context/turn/message/sender.ts +38 -0
- package/src/session/context/turn/message/skill.ts +8 -0
- package/src/session/context/turn/message/tool-call.ts +10 -0
- package/src/session/context/turn/message/tool-result.ts +9 -0
- package/src/session/context/turn/turn.ts +14 -0
- package/src/session/conversation.ts +169 -0
- package/src/session/goal/goal.ts +9 -0
- package/src/session/goal/task.ts +19 -0
- package/src/session/session.ts +94 -0
- package/src/testing/fake-claude.ts +23 -18
- package/src/testing/fake-elements.ts +3 -2
- package/src/testing/fake-files.ts +1 -1
- package/src/testing/fake-sessions.ts +203 -0
- package/src/testing.ts +112 -33
- package/src/ui/components.ts +3 -3
- package/src/ui/elements.ts +22 -8
- package/src/ui/markdown.ts +22 -26
- package/src/ui/places.ts +49 -0
- package/src/utils/call-effects.ts +3 -3
- package/src/utils/changelog.ts +32 -0
- package/src/utils/matcher.ts +37 -0
- package/src/utils/metadata.ts +17 -15
- package/src/utils/parse-shell.ts +24 -16
- package/src/utils/paths.ts +15 -1
- package/src/utils/text.ts +4 -0
- package/testing/fake-claude.d.ts +4 -2
- package/testing/fake-claude.d.ts.map +1 -1
- package/testing/fake-claude.js +18 -12
- package/testing/fake-elements.js +4 -2
- package/testing/fake-files.js +1 -1
- package/testing/fake-sessions.d.ts +74 -0
- package/testing/fake-sessions.d.ts.map +1 -0
- package/testing/fake-sessions.js +170 -0
- package/testing.d.ts +33 -13
- package/testing.d.ts.map +1 -1
- package/testing.js +92 -21
- package/ui/components.d.ts +2 -3
- package/ui/components.d.ts.map +1 -1
- package/ui/components.js +3 -3
- package/ui/elements.d.ts.map +1 -1
- package/ui/elements.js +19 -9
- package/ui/markdown.d.ts +35 -33
- package/ui/markdown.d.ts.map +1 -1
- package/ui/markdown.js +14 -16
- package/ui/places.d.ts +43 -0
- package/ui/places.d.ts.map +1 -0
- package/ui/places.js +31 -0
- package/utils/call-effects.d.ts +1 -1
- package/utils/call-effects.d.ts.map +1 -1
- package/utils/call-effects.js +2 -2
- package/utils/changelog.d.ts +9 -0
- package/utils/changelog.d.ts.map +1 -0
- package/utils/changelog.js +29 -0
- package/utils/matcher.d.ts +13 -0
- package/utils/matcher.d.ts.map +1 -0
- package/utils/matcher.js +32 -0
- package/utils/metadata.d.ts +3 -3
- package/utils/metadata.js +18 -14
- package/utils/parse-shell.d.ts +1 -0
- package/utils/parse-shell.d.ts.map +1 -1
- package/utils/parse-shell.js +22 -13
- package/utils/paths.d.ts +2 -0
- package/utils/paths.d.ts.map +1 -1
- package/utils/paths.js +13 -1
- package/utils/text.d.ts +1 -0
- package/utils/text.d.ts.map +1 -1
- package/utils/text.js +3 -0
- package/src/ui/slots.ts +0 -36
- package/ui/slots.d.ts +0 -208
- package/ui/slots.d.ts.map +0 -1
- 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
|
+
}
|
package/runtime/deadline.d.ts
CHANGED
|
@@ -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,
|
|
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"}
|
package/runtime/deadline.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export const longestMs = 600_000;
|
|
2
|
-
export const toolDeadline = { ms: 30000,
|
|
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"}
|
package/runtime/dependencies.js
CHANGED
|
@@ -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
|
+
}
|
package/runtime/grants.d.ts
CHANGED
|
@@ -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;
|
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;
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
63
|
-
await gate('
|
|
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:
|
|
69
|
-
await gate('
|
|
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:
|
|
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('
|
|
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:
|
|
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
|
|
103
|
-
return
|
|
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
|
-
|
|
136
|
-
|
|
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
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
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;
|
package/runtime/hooks.d.ts
CHANGED
|
@@ -1,16 +1,17 @@
|
|
|
1
|
-
import type {
|
|
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<
|
|
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<
|
|
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:
|
|
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'];
|
package/runtime/hooks.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hooks.d.ts","sourceRoot":"","sources":["../src/runtime/hooks.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,
|
|
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"}
|