@cmodjs/core 1.6.0 → 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/docs/commands.md +7 -7
- package/docs/dependencies.md +1 -1
- package/docs/index.md +12 -11
- package/docs/jobs.md +12 -16
- package/docs/mod.md +111 -36
- package/docs/permissions.md +14 -18
- package/docs/session.md +171 -82
- package/docs/state.md +5 -5
- package/docs/testing.md +57 -41
- package/docs/ui.md +9 -9
- package/jobs/permissions/decide-permission.d.ts.map +1 -1
- package/jobs/permissions/decide-permission.js +1 -1
- package/jobs/prompt.d.ts +1 -3
- package/jobs/prompt.d.ts.map +1 -1
- package/jobs/prompt.js +2 -21
- package/jobs/slash-command.d.ts +1 -1
- package/jobs/slash-command.d.ts.map +1 -1
- package/jobs/slash-command.js +1 -1
- package/jobs/status-line.d.ts +0 -1
- package/jobs/status-line.d.ts.map +1 -1
- package/jobs/status-line.js +3 -4
- package/mod.d.ts +53 -105
- package/mod.d.ts.map +1 -1
- package/mod.js +18 -80
- package/package.json +1 -1
- package/records.d.ts +1 -1
- package/records.js +1 -1
- package/register.d.ts.map +1 -1
- package/register.js +8 -5
- package/runtime/before.d.ts +11 -4
- package/runtime/before.d.ts.map +1 -1
- package/runtime/before.js +131 -67
- package/runtime/claude.d.ts +6 -1
- package/runtime/claude.d.ts.map +1 -1
- package/runtime/conversations.d.ts.map +1 -1
- package/runtime/conversations.js +82 -38
- package/runtime/deadline.d.ts +17 -0
- package/runtime/deadline.d.ts.map +1 -1
- package/runtime/deadline.js +34 -0
- package/runtime/dependencies.d.ts +2 -0
- package/runtime/dependencies.d.ts.map +1 -1
- package/runtime/dependencies.js +12 -0
- package/runtime/grants.d.ts.map +1 -1
- package/runtime/grants.js +13 -8
- package/runtime/hooks.d.ts +15 -11
- package/runtime/hooks.d.ts.map +1 -1
- package/runtime/hooks.js +8 -165
- package/runtime/lifecycle.d.ts +4 -2
- package/runtime/lifecycle.d.ts.map +1 -1
- package/runtime/lifecycle.js +96 -62
- package/runtime/permission-check.d.ts +15 -0
- package/runtime/permission-check.d.ts.map +1 -0
- package/runtime/permission-check.js +37 -0
- package/runtime/sessions.d.ts +35 -5
- package/runtime/sessions.d.ts.map +1 -1
- package/runtime/sessions.js +304 -82
- package/runtime/state.d.ts.map +1 -1
- package/runtime/state.js +14 -11
- package/runtime/tool-calls.d.ts +0 -1
- package/runtime/tool-calls.d.ts.map +1 -1
- package/runtime/tool-calls.js +0 -19
- package/session/context/turn/message/instruction-file.d.ts +9 -0
- package/session/context/turn/message/instruction-file.d.ts.map +1 -0
- package/session/context/turn/message/instruction-file.js +4 -0
- package/session/context/turn/message/message.d.ts +6 -4
- package/session/context/turn/message/message.d.ts.map +1 -1
- package/session/context/turn/message/message.js +12 -8
- package/session/context/turn/message/sender.d.ts +2 -1
- package/session/context/turn/message/sender.d.ts.map +1 -1
- package/session/context/turn/message/sender.js +4 -1
- package/session/context/turn/message/tool-call.d.ts +5 -0
- package/session/context/turn/message/tool-call.d.ts.map +1 -1
- package/session/context/turn/message/tool-result.d.ts +6 -1
- package/session/context/turn/message/tool-result.d.ts.map +1 -1
- package/session/context/turn/turn.d.ts +7 -0
- package/session/context/turn/turn.d.ts.map +1 -1
- package/session/conversation.d.ts +10 -3
- package/session/conversation.d.ts.map +1 -1
- package/session/conversation.js +14 -5
- package/session/goal/goal.d.ts +1 -1
- package/session/goal/goal.d.ts.map +1 -1
- package/session/goal/task.d.ts +2 -2
- package/session/goal/task.d.ts.map +1 -1
- package/session/session.d.ts +361 -26
- package/session/session.d.ts.map +1 -1
- package/session/session.js +40 -0
- package/src/jobs/permissions/decide-permission.ts +2 -1
- package/src/jobs/prompt.ts +3 -20
- package/src/jobs/slash-command.ts +2 -2
- package/src/jobs/status-line.ts +4 -7
- package/src/mod.ts +52 -147
- package/src/records.ts +1 -1
- package/src/register.ts +8 -5
- package/src/runtime/before.ts +140 -66
- package/src/runtime/claude.ts +5 -1
- package/src/runtime/conversations.ts +75 -37
- package/src/runtime/deadline.ts +38 -0
- package/src/runtime/dependencies.ts +12 -0
- package/src/runtime/grants.ts +11 -7
- package/src/runtime/hooks.ts +24 -163
- package/src/runtime/lifecycle.ts +110 -77
- package/src/runtime/permission-check.ts +47 -0
- package/src/runtime/sessions.ts +334 -91
- package/src/runtime/state.ts +15 -8
- package/src/runtime/tool-calls.ts +0 -19
- package/src/session/context/turn/message/instruction-file.ts +13 -0
- package/src/session/context/turn/message/message.ts +17 -11
- package/src/session/context/turn/message/sender.ts +5 -2
- package/src/session/context/turn/message/tool-call.ts +3 -0
- package/src/session/context/turn/message/tool-result.ts +6 -1
- package/src/session/context/turn/turn.ts +4 -0
- package/src/session/conversation.ts +21 -8
- package/src/session/goal/goal.ts +1 -1
- package/src/session/goal/task.ts +2 -2
- package/src/session/session.ts +227 -24
- package/src/testing/claude-code.ts +23 -0
- package/src/testing/fake-claude.ts +5 -1
- package/src/testing/fake-sessions.ts +38 -24
- package/src/testing.ts +88 -19
- package/testing/claude-code.d.ts +12 -0
- package/testing/claude-code.d.ts.map +1 -0
- package/testing/claude-code.js +10 -0
- package/testing/fake-claude.d.ts +4 -0
- package/testing/fake-claude.d.ts.map +1 -1
- package/testing/fake-claude.js +4 -1
- package/testing/fake-sessions.d.ts +8 -6
- package/testing/fake-sessions.d.ts.map +1 -1
- package/testing/fake-sessions.js +35 -20
- package/testing.d.ts +38 -9
- package/testing.d.ts.map +1 -1
- package/testing.js +58 -8
- package/docs/hooks.md +0 -259
- package/session/context/turn/message/document.d.ts +0 -7
- package/session/context/turn/message/document.d.ts.map +0 -1
- package/session/context/turn/message/document.js +0 -1
- package/src/session/context/turn/message/document.ts +0 -8
package/runtime/lifecycle.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { hooks, PreToolUse } from '../mod.js';
|
|
2
1
|
import { changelogPage, consentPath, dataFolder, finishStepsMethod, isAtLeast, marketplaceOf, oldestCmodFor, openPageMethod, parseConsent, pendingStepsMethod, parseEvent, permissionWords, readRecord, readSteps, recordPath, scriptsSha256, settingsPagesMethod, storeFolder, updatesToTurnOn } from '../records.js';
|
|
3
2
|
import { homePrefix, ifMissing, relativePath } from '../utils/paths.js';
|
|
4
3
|
import { firstLine, formatExit, listed, messageOf } from '../utils/text.js';
|
|
@@ -6,9 +5,9 @@ import { beforeDeadline } from './deadline.js';
|
|
|
6
5
|
import { announcedOf, announcementMs, announcementOf } from './announce.js';
|
|
7
6
|
import { bind } from './bound.js';
|
|
8
7
|
import { serveConversations } from './conversations.js';
|
|
9
|
-
import { answerCall, cmodPluginName, dependencyCalls, withBareRefusals } from './dependencies.js';
|
|
8
|
+
import { answerCall, answeringOwnCalls, cmodPluginName, dependencyCalls, withBareRefusals } from './dependencies.js';
|
|
10
9
|
import { keepEngine } from './engine.js';
|
|
11
|
-
import { classicHook,
|
|
10
|
+
import { classicHook, permissionEvents, userSkillHook } from './hooks.js';
|
|
12
11
|
import { checkedAnswer, checkingGrants, checkSettings, checksAnswers, checkWrite, isCovered, itemOf, passedDown, programAddresses } from './grants.js';
|
|
13
12
|
import { cmodError, lastLineOf, locatingPrograms, readLines } from './programs.js';
|
|
14
13
|
import { createModFiles, resolvedPath } from './metadata.js';
|
|
@@ -29,6 +28,7 @@ const installId = 'cmod:install';
|
|
|
29
28
|
const installRetrySeconds = [2, 4, 8, 16, 32, 64, 128];
|
|
30
29
|
const cmodCheckMs = 1000;
|
|
31
30
|
const cmodWaitMs = 60_000;
|
|
31
|
+
const cmodSlowCheckMs = 10_000;
|
|
32
32
|
const startHoldMs = 10_000 - 1_000;
|
|
33
33
|
const dependencyCallMs = 30_000;
|
|
34
34
|
const cannotStart = /failed to start: /;
|
|
@@ -93,7 +93,7 @@ async function cmodVersion(claude) {
|
|
|
93
93
|
throw new Error(`cmod --version ${formatExit(result.exitCode, lastLineOf(result.stderr) ?? '')}`);
|
|
94
94
|
return result.stdout.trim().split(/\s+/).at(-1);
|
|
95
95
|
}
|
|
96
|
-
export function createLifecycle(definition,
|
|
96
|
+
export function createLifecycle(definition, permissionRoutes = () => undefined, options = {}, asksPerson = true) {
|
|
97
97
|
let router = createRouter();
|
|
98
98
|
let phase = 'starting';
|
|
99
99
|
let runtime;
|
|
@@ -103,6 +103,7 @@ export function createLifecycle(definition, checksPermissions = () => true, opti
|
|
|
103
103
|
let activation;
|
|
104
104
|
let mod;
|
|
105
105
|
let imports;
|
|
106
|
+
let raise;
|
|
106
107
|
let failure;
|
|
107
108
|
let pluginOptions = options;
|
|
108
109
|
const installer = createInstaller(definition.name, () => claude());
|
|
@@ -250,7 +251,7 @@ export function createLifecycle(definition, checksPermissions = () => true, opti
|
|
|
250
251
|
if (runtime === undefined || plugin === undefined)
|
|
251
252
|
return;
|
|
252
253
|
const activeRouter = createRouter();
|
|
253
|
-
const active = await createMod(definition, { ...runtime, router: activeRouter, dataFolder: dataFolder(plugin.store, plugin.name), store: plugin.store, steps: plugin.steps, granted, refreshGrants,
|
|
254
|
+
const active = await createMod(definition, { ...runtime, router: activeRouter, dataFolder: dataFolder(plugin.store, plugin.name), store: plugin.store, steps: plugin.steps, granted, refreshGrants, permissionRoutes, options: pluginOptions }).catch(async (error) => {
|
|
254
255
|
if (error instanceof MissingOptions && (await askOptions(error)))
|
|
255
256
|
return undefined;
|
|
256
257
|
fail(messageOf(error), error instanceof MissingOptions ? `Set ${error.keys.length === 1 ? 'it' : 'them'} in /config.` : undefined, begin);
|
|
@@ -261,18 +262,26 @@ export function createLifecycle(definition, checksPermissions = () => true, opti
|
|
|
261
262
|
const { added } = active;
|
|
262
263
|
mod = active.mod;
|
|
263
264
|
imports = active.imports;
|
|
265
|
+
raise = active.raise;
|
|
264
266
|
router = activeRouter;
|
|
265
267
|
phase = 'active';
|
|
266
268
|
runtime.claude.ui.invalidate('ui.render');
|
|
267
269
|
void runSteps(active.mod).catch((error) => toastFailure({ id: 'cmod:steps', message: 'Its installer stopped', error }));
|
|
268
270
|
const missed = missedSessionStart;
|
|
269
271
|
missedSessionStart = undefined;
|
|
270
|
-
const
|
|
272
|
+
const replay = (e) => (definition.name === cmodPluginName ? activeRouter.dispatch('classic.SessionStart', e, async () => ({})) : checkedRoute('classic.SessionStart', e, async () => ({})));
|
|
273
|
+
const lateAnswer = missed === undefined ? {} : await replay(missed).catch((error) => {
|
|
271
274
|
toastFailure({ id: 'classic.SessionStart', message: 'The SessionStart hook failed', error });
|
|
272
275
|
return {};
|
|
273
276
|
});
|
|
274
|
-
|
|
275
|
-
|
|
277
|
+
const { additionalContext, ...unread } = lateAnswer;
|
|
278
|
+
for (const text of additionalContext ?? []) {
|
|
279
|
+
const added = await runtime.claude.session.append({ message: { type: 'user', content: [{ type: 'text', text }] } });
|
|
280
|
+
if (added.deny !== undefined)
|
|
281
|
+
runtime.claude.ui.log(`${definition.name}: Claude Code refused its SessionStart note: ${added.deny}`, { to: 'debug' });
|
|
282
|
+
}
|
|
283
|
+
if (Object.keys(unread).length > 0) {
|
|
284
|
+
runtime.claude.ui.log(`${definition.name} started after Claude Code's SessionStart, so Claude Code did not read ${Object.keys(unread).join(', ')} from its SessionStart hooks.`, { to: 'debug' });
|
|
276
285
|
}
|
|
277
286
|
const version = plugin.version ?? '';
|
|
278
287
|
const announced = announcedOf(await runtime.claude.store.get(announcedKey));
|
|
@@ -358,7 +367,7 @@ export function createLifecycle(definition, checksPermissions = () => true, opti
|
|
|
358
367
|
timer.cancel();
|
|
359
368
|
longWait.cancel();
|
|
360
369
|
};
|
|
361
|
-
const
|
|
370
|
+
const poll = (ms) => claude().clock.every(ms, () => {
|
|
362
371
|
check ??= cmodVersion(claude())
|
|
363
372
|
.then((version) => {
|
|
364
373
|
check = undefined;
|
|
@@ -373,9 +382,14 @@ export function createLifecycle(definition, checksPermissions = () => true, opti
|
|
|
373
382
|
report(error);
|
|
374
383
|
});
|
|
375
384
|
});
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
385
|
+
let timer = poll(cmodCheckMs);
|
|
386
|
+
const longWait = claude().clock.after(cmodWaitMs, () => {
|
|
387
|
+
timer.cancel();
|
|
388
|
+
timer = poll(cmodSlowCheckMs);
|
|
389
|
+
installing(latest === undefined
|
|
390
|
+
? "Still waiting for cmod to download cmod. See cmod's own line."
|
|
391
|
+
: `PATH finds cmod ${latest}, and ${definition.name} needs cmod ${oldestCmodFor(plugin?.steps ?? {})} or later. Run npm i -g @cmodjs/cli, or put ~/.local/bin ahead of the old cmod on PATH.`);
|
|
392
|
+
});
|
|
379
393
|
};
|
|
380
394
|
const bootstrap = async (root) => {
|
|
381
395
|
installing();
|
|
@@ -502,6 +516,12 @@ export function createLifecycle(definition, checksPermissions = () => true, opti
|
|
|
502
516
|
get toasts() {
|
|
503
517
|
return runtime?.toaster.shown ?? [];
|
|
504
518
|
},
|
|
519
|
+
async raise(method, payload) {
|
|
520
|
+
if (raise === undefined || imports === undefined)
|
|
521
|
+
throw new Error(`${definition.name} has not started, so ${method} has no listener yet.`);
|
|
522
|
+
bind(imports);
|
|
523
|
+
return raise(method, payload);
|
|
524
|
+
},
|
|
505
525
|
async start(claudeCalls, read) {
|
|
506
526
|
if (runtime !== undefined)
|
|
507
527
|
return;
|
|
@@ -537,8 +557,10 @@ export function createLifecycle(definition, checksPermissions = () => true, opti
|
|
|
537
557
|
if (event === 'cmod.call' && !isCallToThisMod)
|
|
538
558
|
return next(e);
|
|
539
559
|
await (event === 'classic.SessionStart' || isCallToThisMod ? holdUntilStarted() : whenActive());
|
|
540
|
-
if (event === 'classic.SessionStart' && phase !== 'active')
|
|
560
|
+
if (event === 'classic.SessionStart' && phase !== 'active') {
|
|
541
561
|
missedSessionStart = e;
|
|
562
|
+
return next(e);
|
|
563
|
+
}
|
|
542
564
|
if (isCallToThisMod && phase !== 'active') {
|
|
543
565
|
const isStopped = phase === 'declined' || phase === 'failed';
|
|
544
566
|
return { deny: isStopped ? `${definition.name} is not set up. Its toast says why.` : `${definition.name} is installing. Try again when it's ready.` };
|
|
@@ -565,9 +587,8 @@ export function createLifecycle(definition, checksPermissions = () => true, opti
|
|
|
565
587
|
async function createMod(definition, runtime) {
|
|
566
588
|
const { router, toaster } = runtime;
|
|
567
589
|
const report = (failure) => void toaster.fail(failure);
|
|
568
|
-
const claude = withBareRefusals(checkingKeys(runtime.claude, toaster.toast, runtime.steps.keys ?? {}));
|
|
590
|
+
const claude = answeringOwnCalls(withBareRefusals(checkingKeys(runtime.claude, toaster.toast, runtime.steps.keys ?? {})), router);
|
|
569
591
|
const added = [];
|
|
570
|
-
const hooksAdded = [];
|
|
571
592
|
const announce = (feature) => {
|
|
572
593
|
added.push(feature);
|
|
573
594
|
};
|
|
@@ -640,7 +661,7 @@ async function createMod(definition, runtime) {
|
|
|
640
661
|
}));
|
|
641
662
|
const modState = createState({ name: definition.name, initial: definition.state ?? {}, session, root, claude, report, changed: area.changed, forkParent });
|
|
642
663
|
const modOptions = createOptions({ name: definition.name, declared: definition.options ?? {}, fromClaude: runtime.options, claude, report, changed: modState.changed });
|
|
643
|
-
const
|
|
664
|
+
const hookOn = (hook, listener) => router.add(`classic.${hook}`, classicHook(hook, listener, report));
|
|
644
665
|
const expand = (path) => (homePrefix.test(path) ? resolvedPath(path, places) : path);
|
|
645
666
|
const settingsFile = (source) => ({
|
|
646
667
|
read: () => checked.settings.read({ source }),
|
|
@@ -649,8 +670,18 @@ async function createMod(definition, runtime) {
|
|
|
649
670
|
await claude.cmod.call({ to: cmodPluginName, method: 'updateSettings', input: { mod: definition.name, source, project: modState.root, values } });
|
|
650
671
|
},
|
|
651
672
|
});
|
|
652
|
-
const client = createSessionsClient({
|
|
673
|
+
const client = createSessionsClient({
|
|
674
|
+
name: definition.name,
|
|
675
|
+
claude: checked,
|
|
676
|
+
report: (id, message, error) => report({ id, message, error }),
|
|
677
|
+
isGranted: (permission) => flag(permission),
|
|
678
|
+
hookOn: (hook, listener) => hookOn(hook, listener),
|
|
679
|
+
routeOn: (event, hook) => router.add(event, hook),
|
|
680
|
+
permissionRoutes: runtime.permissionRoutes,
|
|
681
|
+
});
|
|
653
682
|
let followed;
|
|
683
|
+
let isSetUp = false;
|
|
684
|
+
const watches = new Set();
|
|
654
685
|
const followFolders = (project, folder) => {
|
|
655
686
|
if (followed === undefined)
|
|
656
687
|
return;
|
|
@@ -659,7 +690,16 @@ async function createMod(definition, runtime) {
|
|
|
659
690
|
};
|
|
660
691
|
const imports = {
|
|
661
692
|
sessions: client.sessions,
|
|
662
|
-
|
|
693
|
+
commands: {
|
|
694
|
+
list: async () => (await checked.command.list()).map(({ name, description, source, plugin }) => ({ name, description, source, ...(plugin === undefined ? {} : { plugin }) })),
|
|
695
|
+
run: async (command, args) => {
|
|
696
|
+
const { text, context } = await checked.command.run({ command, ...(args === undefined ? {} : { args }) });
|
|
697
|
+
return { ...(text === undefined ? {} : { text }), ...(context === undefined || context.length === 0 ? {} : { note: context.join('\n') }) };
|
|
698
|
+
},
|
|
699
|
+
beforeRun: (...args) => client.listen('beforeRun', args),
|
|
700
|
+
onRun: (...args) => client.listen('onRun', args),
|
|
701
|
+
},
|
|
702
|
+
config: {
|
|
663
703
|
list: async () => (await checked.config.list()).map(({ key, label, description, kind, value, options, provider, isLocked }) => ({
|
|
664
704
|
key,
|
|
665
705
|
label,
|
|
@@ -670,9 +710,22 @@ async function createMod(definition, runtime) {
|
|
|
670
710
|
...(options === undefined ? {} : { options }),
|
|
671
711
|
...(provider.plugin === 'engine' ? {} : { plugin: provider.plugin }),
|
|
672
712
|
})),
|
|
713
|
+
},
|
|
714
|
+
settings: {
|
|
673
715
|
user: settingsFile('user'),
|
|
674
716
|
project: settingsFile('project'),
|
|
675
717
|
local: settingsFile('local'),
|
|
718
|
+
beforeFileChange: (matcherOrListener, maybeListener) => {
|
|
719
|
+
const listener = (maybeListener ?? matcherOrListener);
|
|
720
|
+
const matcher = maybeListener === undefined ? undefined : matcherOrListener;
|
|
721
|
+
return hookOn('ConfigChange', async ({ source, file_path }) => {
|
|
722
|
+
const change = { source: source === 'skills' ? 'skills' : source.replace('_settings', ''), ...(file_path === undefined ? {} : { path: file_path }) };
|
|
723
|
+
if (matcher !== undefined && !matches(change, matcher))
|
|
724
|
+
return undefined;
|
|
725
|
+
const refuse = (await listener(change))?.refuse;
|
|
726
|
+
return typeof refuse === 'string' ? { block: refuse } : undefined;
|
|
727
|
+
});
|
|
728
|
+
},
|
|
676
729
|
},
|
|
677
730
|
model: { complete: (completion, options) => checked.model.complete(completion, options) },
|
|
678
731
|
fs: {
|
|
@@ -685,6 +738,23 @@ async function createMod(definition, runtime) {
|
|
|
685
738
|
const { kind, size, mtimeMs, isLink, realPath } = await checked.fs.stat(expand(path), options);
|
|
686
739
|
return { isFile: kind === 'file', isFolder: kind === 'dir', isLink, size, modifiedAt: mtimeMs, ...(realPath === undefined ? {} : { realPath }) };
|
|
687
740
|
},
|
|
741
|
+
onChange({ path }, listener) {
|
|
742
|
+
if (isSetUp)
|
|
743
|
+
throw new Error(`${definition.name}: fs.onChange works only in setup, because Claude Code reads the paths it watches when a session starts.`);
|
|
744
|
+
const paths = [path].flat().map((each) => resolvedPath(each, places));
|
|
745
|
+
const watch = { paths, listener };
|
|
746
|
+
watches.add(watch);
|
|
747
|
+
if (watches.size === 1) {
|
|
748
|
+
hookOn('SessionStart', () => ({ watchPaths: [...new Set([...watches].flatMap((each) => each.paths))] }));
|
|
749
|
+
hookOn('FileChanged', ({ file_path, event }) => {
|
|
750
|
+
for (const each of watches)
|
|
751
|
+
if (each.paths.some((watched) => relativePath(watched, file_path) !== undefined))
|
|
752
|
+
void Promise.resolve(each.listener({ path: file_path, type: event })).catch((error) => report({ id: 'fs:onChange', message: 'The fs.onChange listener failed', error }));
|
|
753
|
+
return undefined;
|
|
754
|
+
});
|
|
755
|
+
}
|
|
756
|
+
return { dispose: () => void watches.delete(watch) };
|
|
757
|
+
},
|
|
688
758
|
},
|
|
689
759
|
metadata: {
|
|
690
760
|
read: (path) => files.read(path),
|
|
@@ -748,26 +818,10 @@ async function createMod(definition, runtime) {
|
|
|
748
818
|
files: allowed('files'),
|
|
749
819
|
config: allowed('config'),
|
|
750
820
|
},
|
|
751
|
-
on(hook, matcherOrListener, listenerOrOptions, maybeOptions) {
|
|
752
|
-
const hasMatcher = typeof listenerOrOptions === 'function';
|
|
753
|
-
const listener = (hasMatcher ? listenerOrOptions : matcherOrListener);
|
|
754
|
-
const matcher = hasMatcher ? matcherOrListener : undefined;
|
|
755
|
-
const options = (hasMatcher ? maybeOptions : listenerOrOptions);
|
|
756
|
-
if (!hooks.includes(hook))
|
|
757
|
-
throw new Error(`${definition.name}: mod.on takes one of Claude Code's hooks, such as PreToolUse, not ${String(hook)}.`);
|
|
758
|
-
const matched = matcher === undefined ? listener : (input) => (matches(input, matcher) ? listener(input) : undefined);
|
|
759
|
-
const bounded = options?.timeoutMs === undefined ? matched : timedHook(hook, matched, checkedMs(definition.name, `the ${hook} hook's timeoutMs`, options.timeoutMs), claude, toaster.toast);
|
|
760
|
-
if (!hooksAdded.includes(hook))
|
|
761
|
-
hooksAdded.push(hook);
|
|
762
|
-
if (hook === PreToolUse)
|
|
763
|
-
return router.add('tool.call', preToolUseHook(definition.name, bounded, claude, agents, held));
|
|
764
|
-
const classic = hook;
|
|
765
|
-
return router.add(`classic.${classic}`, classicHook(definition.name, classic, bounded, claude, agents, report));
|
|
766
|
-
},
|
|
767
821
|
every: (ms, listener) => timer(ms, 'every', claude.clock.every, listener),
|
|
768
822
|
after: (ms, listener) => timer(ms, 'after', claude.clock.after, listener),
|
|
769
823
|
use: (job) => job({ mod, announce, reserveName }),
|
|
770
|
-
log: (text) => claude.ui.log(`${definition.name}: ${text}`, { to: 'debug' }),
|
|
824
|
+
log: (text, options) => claude.ui.log(`${definition.name}: ${text}`, { to: options?.to ?? 'debug' }),
|
|
771
825
|
ui: { ...area.ui, notice: (callId, text) => claude.ui.notice(callId, text) },
|
|
772
826
|
settings: settingsPages,
|
|
773
827
|
dependencies: dependencyCalls(claude, (call, task) => beforeDeadline(claude, { ms: dependencyCallMs }, call, task)),
|
|
@@ -858,9 +912,9 @@ async function createMod(definition, runtime) {
|
|
|
858
912
|
return next(e);
|
|
859
913
|
const call = e;
|
|
860
914
|
if (e.method === conversationMethods.changed && call.caller === cmodPluginName) {
|
|
861
|
-
if (!flag('conversation'))
|
|
862
|
-
return { value: null };
|
|
863
915
|
const { session: changedSession, change, isInProject } = e.input;
|
|
916
|
+
if (changedSession !== client.sessions.current.id && !flag('conversation'))
|
|
917
|
+
return { value: null };
|
|
864
918
|
client.changed(changedSession, change, isInProject !== false);
|
|
865
919
|
return { value: null };
|
|
866
920
|
}
|
|
@@ -894,22 +948,19 @@ async function createMod(definition, runtime) {
|
|
|
894
948
|
throw new MissingOptions(missing, missing.map((key) => definition.options?.[key]?.title ?? key));
|
|
895
949
|
await failsAs('the details of its session did not load', () => followCurrent(undefined));
|
|
896
950
|
await failsAs('its setup function threw', () => definition.setup(mod));
|
|
951
|
+
isSetUp = true;
|
|
897
952
|
const permissionHooks = permissionEvents.filter((event) => router.has(event));
|
|
898
|
-
if (permissionHooks.length > 0 &&
|
|
953
|
+
if (permissionHooks.length > 0 && runtime.permissionRoutes() === undefined) {
|
|
899
954
|
throw new Error(`it decides permissions on ${listed(permissionHooks)}, so hooks/register.ts must call registerPermissionCheck(addHook) after registerMod`);
|
|
900
955
|
}
|
|
901
|
-
if (held !== undefined && hooksAdded.includes(PreToolUse))
|
|
902
|
-
router.add('tool.check', heldDecisionHook(held));
|
|
903
956
|
await failsAs('its open panes did not load', () => area.restorePanes());
|
|
904
|
-
|
|
905
|
-
added.push(`${hooksAdded.length === 1 ? 'a hook' : 'hooks'} on ${listed(hooksAdded)}`);
|
|
906
|
-
return { mod, added, imports };
|
|
957
|
+
return { mod, added, imports, raise: client.raise };
|
|
907
958
|
}
|
|
908
959
|
function checkingKeys(claude, toast, keys) {
|
|
909
960
|
const bound = Object.entries(keys);
|
|
910
961
|
if (bound.length === 0)
|
|
911
962
|
return claude;
|
|
912
|
-
const
|
|
963
|
+
const addCommand = (command) => {
|
|
913
964
|
for (const [key, commandName] of bound) {
|
|
914
965
|
if (commandName === command.name && command.immediate !== true) {
|
|
915
966
|
toast.warning(`${key} runs /${command.name}, which waits for Claude's turn to end`, { id: `key:${key}`, description: `It adds a row to the conversation. Give /${command.name} immediate: true.`, duration: Infinity });
|
|
@@ -917,7 +968,7 @@ function checkingKeys(claude, toast, keys) {
|
|
|
917
968
|
}
|
|
918
969
|
return claude.command.register(command);
|
|
919
970
|
};
|
|
920
|
-
return { ...claude, command: { ...claude.command, register } };
|
|
971
|
+
return { ...claude, command: { ...claude.command, register: addCommand } };
|
|
921
972
|
}
|
|
922
973
|
const longestTimerMs = 2_147_483_647;
|
|
923
974
|
function checkedMs(name, subject, ms) {
|
|
@@ -925,23 +976,6 @@ function checkedMs(name, subject, ms) {
|
|
|
925
976
|
throw new Error(`${name}: ${subject} is ${ms}. Give a whole number of milliseconds above 0 and at most ${longestTimerMs}.`);
|
|
926
977
|
return ms;
|
|
927
978
|
}
|
|
928
|
-
function timedHook(event, hook, ms, claude, toast) {
|
|
929
|
-
return (input) => new Promise((resolve, reject) => {
|
|
930
|
-
const timer = claude.clock.after(ms, () => {
|
|
931
|
-
toast.warning(`The ${event} hook passed its ${ms / 1000} s timeout`, { id: `timeout:${event}` });
|
|
932
|
-
resolve(undefined);
|
|
933
|
-
});
|
|
934
|
-
Promise.resolve()
|
|
935
|
-
.then(() => hook(input))
|
|
936
|
-
.then((answer) => {
|
|
937
|
-
timer.cancel();
|
|
938
|
-
resolve(answer);
|
|
939
|
-
}, (error) => {
|
|
940
|
-
timer.cancel();
|
|
941
|
-
reject(error);
|
|
942
|
-
});
|
|
943
|
-
});
|
|
944
|
-
}
|
|
945
979
|
async function failsAs(subject, step) {
|
|
946
980
|
try {
|
|
947
981
|
await step();
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { Disposable } from '../mod.js';
|
|
2
|
+
import type { HookFields } from './hooks.js';
|
|
3
|
+
import { type HookOn, type RouteOn } from './sessions.js';
|
|
4
|
+
export type PermissionUse = {
|
|
5
|
+
readonly raise: (method: string, payload: HookFields) => Promise<HookFields | undefined>;
|
|
6
|
+
readonly hookOn: HookOn;
|
|
7
|
+
readonly routeOn: RouteOn;
|
|
8
|
+
readonly current: () => {
|
|
9
|
+
readonly id: string;
|
|
10
|
+
readonly cwd: string;
|
|
11
|
+
};
|
|
12
|
+
};
|
|
13
|
+
export type PermissionRoutes = (method: string, use: PermissionUse) => Disposable;
|
|
14
|
+
export declare const permissionRoutes: PermissionRoutes;
|
|
15
|
+
//# sourceMappingURL=permission-check.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"permission-check.d.ts","sourceRoot":"","sources":["../src/runtime/permission-check.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,WAAW,CAAA;AAC3C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAA;AAC5C,OAAO,EAAuC,KAAK,MAAM,EAAE,KAAK,OAAO,EAAkB,MAAM,eAAe,CAAA;AAE9G,MAAM,MAAM,aAAa,GAAG;IAC1B,QAAQ,CAAC,KAAK,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,UAAU,KAAK,OAAO,CAAC,UAAU,GAAG,SAAS,CAAC,CAAA;IACxF,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAA;IACzB,QAAQ,CAAC,OAAO,EAAE,MAAM;QAAE,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;KAAE,CAAA;CACtE,CAAA;AAED,MAAM,MAAM,gBAAgB,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,aAAa,KAAK,UAAU,CAAA;AAMjF,eAAO,MAAM,gBAAgB,EAAE,gBA6B9B,CAAA"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { defined, processPayloadOf, recordOf } from './sessions.js';
|
|
2
|
+
const verdictName = (decision) => (decision === 'deny' ? 'refuse' : decision);
|
|
3
|
+
export const permissionRoutes = (method, { raise, hookOn, routeOn, current }) => {
|
|
4
|
+
const isBefore = method.startsWith('before');
|
|
5
|
+
if (method.endsWith('PermissionCheck')) {
|
|
6
|
+
return routeOn('tool.check', async (e, next) => {
|
|
7
|
+
const below = (await next(e));
|
|
8
|
+
const { id, cwd } = current();
|
|
9
|
+
const payload = { session: id, cwd, ...defined({ task: e.agentId, callId: e.tool_use_id, reason: below.reason, rule: below.rule, ceiling: e.ceiling === undefined ? undefined : verdictName(e.ceiling) }), name: e.tool, input: recordOf(e.input), decision: verdictName(below.decision) };
|
|
10
|
+
if (!isBefore) {
|
|
11
|
+
void raise(method, payload);
|
|
12
|
+
return below;
|
|
13
|
+
}
|
|
14
|
+
const answer = await raise(method, payload);
|
|
15
|
+
if (answer === undefined || below.decision === 'deny')
|
|
16
|
+
return below;
|
|
17
|
+
if (typeof answer['refuse'] === 'string')
|
|
18
|
+
return { decision: 'deny', reason: answer['refuse'] };
|
|
19
|
+
if (typeof answer['ask'] === 'string')
|
|
20
|
+
return { decision: 'ask', reason: answer['ask'] };
|
|
21
|
+
return answer['allow'] === true ? { decision: 'allow' } : below;
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
return hookOn('PermissionRequest', async (input) => {
|
|
25
|
+
const payload = processPayloadOf('PermissionRequest', input);
|
|
26
|
+
if (!isBefore) {
|
|
27
|
+
void raise(method, payload);
|
|
28
|
+
return undefined;
|
|
29
|
+
}
|
|
30
|
+
const answer = await raise(method, payload);
|
|
31
|
+
if (answer === undefined)
|
|
32
|
+
return undefined;
|
|
33
|
+
if (typeof answer['refuse'] === 'string')
|
|
34
|
+
return { decision: defined({ behavior: 'deny', message: answer['refuse'], interrupt: answer['interrupt'] }) };
|
|
35
|
+
return answer['allow'] === true ? { decision: defined({ behavior: 'allow', updatedInput: answer['input'], updatedPermissions: answer['updates'] }) } : undefined;
|
|
36
|
+
});
|
|
37
|
+
};
|
package/runtime/sessions.d.ts
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
|
+
import type { Args, CommandRunResult } from 'claude-code';
|
|
2
|
+
import type { Disposable } from '../mod.js';
|
|
1
3
|
import { ToolResult } from '../session/context/turn/message/tool-result.js';
|
|
2
|
-
import type
|
|
3
|
-
import { type BeforeEvent, type SessionEvent, type Sessions, type Usage } from '../session/session.js';
|
|
4
|
+
import { type SessionChange, type SessionData, type TaskData } from '../session/conversation.js';
|
|
5
|
+
import { type BeforeEvent, type ProcessName, type SessionEvent, type Sessions, type TaskStartArgs, type ToolCallArgs, type Usage } from '../session/session.js';
|
|
4
6
|
import { type SentMatcher } from '../utils/matcher.js';
|
|
5
7
|
import { type Place } from './before.js';
|
|
6
8
|
import type { Claude } from './claude.js';
|
|
9
|
+
import { type HookFields } from './hooks.js';
|
|
10
|
+
import type { PermissionRoutes } from './permission-check.js';
|
|
7
11
|
export type Subscription = {
|
|
8
12
|
readonly event: SessionEvent;
|
|
9
13
|
readonly matcher: SentMatcher | undefined;
|
|
@@ -25,7 +29,7 @@ export declare const conversationMethods: {
|
|
|
25
29
|
readonly changed: 'cmod:changed';
|
|
26
30
|
readonly before: 'cmod:before';
|
|
27
31
|
};
|
|
28
|
-
export type Act = 'send' | 'startTask' | 'callTool' | 'usage' | '
|
|
32
|
+
export type Act = 'send' | 'startTask' | 'callTool' | 'usage' | 'surfaces' | 'abort';
|
|
29
33
|
export declare const beforePermission: (event: BeforeEvent) => string;
|
|
30
34
|
export declare const isAct: (action: string) => action is Act;
|
|
31
35
|
export declare function toolPermission(tool: string): string | undefined;
|
|
@@ -36,11 +40,14 @@ export type Scope = {
|
|
|
36
40
|
readonly turn?: string;
|
|
37
41
|
readonly task?: string;
|
|
38
42
|
readonly isGoal?: true;
|
|
43
|
+
readonly isAgent?: true;
|
|
39
44
|
};
|
|
40
45
|
export type SessionsClient = {
|
|
41
46
|
readonly sessions: Sessions;
|
|
42
47
|
changed(session: string, change: SessionChange, isInProject: boolean): void;
|
|
43
48
|
answerBefore(session: string, event: BeforeEvent, change: SessionChange, isInProject: boolean): Promise<unknown>;
|
|
49
|
+
raise(method: string, payload: Readonly<Record<string, unknown>>): Promise<unknown>;
|
|
50
|
+
listen(method: string, args: readonly unknown[]): Disposable;
|
|
44
51
|
/** An empty title keeps the title the session already has. */
|
|
45
52
|
follow(current: Omit<SessionData, 'isRunning' | 'lastAt'>): void;
|
|
46
53
|
};
|
|
@@ -48,11 +55,34 @@ export declare function usageOf(claude: Pick<Claude, 'session'>): Promise<Usage>
|
|
|
48
55
|
export declare function payloadOf(change: SessionChange): unknown;
|
|
49
56
|
export declare function isHeardBy(event: SessionEvent, matcher: unknown, change: SessionChange): boolean;
|
|
50
57
|
export declare function isInScope(scope: Scope, session: string, change: SessionChange, isUnder: (task: string, ancestor: string) => boolean): boolean;
|
|
51
|
-
export
|
|
58
|
+
export type HookOn = (hook: string, listener: (input: unknown) => HookFields | undefined | Promise<HookFields | undefined>) => Disposable;
|
|
59
|
+
export declare function createSessionsClient({ name, claude, report, isGranted, hookOn, routeOn, permissionRoutes }: {
|
|
52
60
|
readonly name: string;
|
|
53
61
|
readonly claude: Claude;
|
|
54
62
|
readonly report: (id: string, message: string, error: unknown) => void;
|
|
55
63
|
readonly isGranted: (permission: string) => boolean;
|
|
64
|
+
readonly hookOn?: HookOn;
|
|
65
|
+
readonly routeOn?: RouteOn;
|
|
66
|
+
readonly permissionRoutes?: () => PermissionRoutes | undefined;
|
|
56
67
|
}): SessionsClient;
|
|
57
|
-
export
|
|
68
|
+
export type ToolCheck = {
|
|
69
|
+
readonly tool: string;
|
|
70
|
+
readonly input: unknown;
|
|
71
|
+
readonly tool_use_id?: string;
|
|
72
|
+
readonly agentId?: string;
|
|
73
|
+
readonly ceiling?: 'allow' | 'ask' | 'deny';
|
|
74
|
+
};
|
|
75
|
+
export type RouteOn = {
|
|
76
|
+
(event: 'tool.check', hook: (e: ToolCheck, next: (e: ToolCheck) => Promise<unknown>) => Promise<unknown>): Disposable;
|
|
77
|
+
(event: 'command.run', hook: (e: Args<'command.run'>, next: (e: Args<'command.run'>) => Promise<CommandRunResult>) => Promise<CommandRunResult>): Disposable;
|
|
78
|
+
};
|
|
79
|
+
export declare const recordOf: (value: unknown) => Readonly<Record<string, unknown>>;
|
|
80
|
+
export declare const defined: (fields: HookFields) => HookFields;
|
|
81
|
+
type ProcessFieldsName = Exclude<ProcessName, 'PermissionCheck'>;
|
|
82
|
+
export declare function spawnTask(claude: Pick<Claude, 'agent'>, task: TaskStartArgs): Promise<TaskData>;
|
|
83
|
+
export declare function processPayloadOf(named: ProcessFieldsName, input: unknown): HookFields;
|
|
84
|
+
export declare function resultOf(where: Pick<Place, 'session' | 'context' | 'turn'>, call: ToolCallArgs & {
|
|
85
|
+
readonly cwd: string;
|
|
86
|
+
}, answered: Awaited<ReturnType<Claude['tool']['call']>>): ToolResult;
|
|
87
|
+
export {};
|
|
58
88
|
//# sourceMappingURL=sessions.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sessions.d.ts","sourceRoot":"","sources":["../src/runtime/sessions.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"sessions.d.ts","sourceRoot":"","sources":["../src/runtime/sessions.ts"],"names":[],"mappings":"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"}
|