@cmodjs/core 1.5.1 → 2.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 +5 -5
- package/docs/index.md +12 -11
- package/docs/jobs.md +12 -16
- package/docs/mod.md +117 -39
- package/docs/permissions.md +17 -21
- 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 +3 -1
- package/records.d.ts.map +1 -1
- package/records.js +14 -5
- package/register.d.ts.map +1 -1
- package/register.js +3 -1
- package/runtime/before.d.ts +5 -1
- package/runtime/before.d.ts.map +1 -1
- package/runtime/before.js +81 -39
- 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 +45 -26
- 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 +1 -1
- package/runtime/grants.d.ts.map +1 -1
- package/runtime/grants.js +19 -12
- package/runtime/hooks.d.ts +28 -11
- package/runtime/hooks.d.ts.map +1 -1
- package/runtime/hooks.js +20 -123
- package/runtime/lifecycle.d.ts +2 -1
- package/runtime/lifecycle.d.ts.map +1 -1
- package/runtime/lifecycle.js +67 -50
- package/runtime/sessions.d.ts +31 -5
- package/runtime/sessions.d.ts.map +1 -1
- package/runtime/sessions.js +342 -81
- 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 +5 -4
- package/session/context/turn/message/message.d.ts.map +1 -1
- package/session/context/turn/message/message.js +7 -6
- 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 +15 -5
- package/src/register.ts +3 -1
- package/src/runtime/before.ts +90 -37
- package/src/runtime/claude.ts +5 -1
- package/src/runtime/conversations.ts +41 -25
- package/src/runtime/deadline.ts +38 -0
- package/src/runtime/dependencies.ts +12 -0
- package/src/runtime/grants.ts +15 -10
- package/src/runtime/hooks.ts +48 -116
- package/src/runtime/lifecycle.ts +66 -51
- package/src/runtime/sessions.ts +366 -90
- 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 +11 -9
- 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 +84 -16
- 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 +54 -5
- 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/src/runtime/lifecycle.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { Args, EventResult, Frozen, HookBudget, PluginOptions, RenderElement, Timer } from 'claude-code'
|
|
2
2
|
import type { Step } from '../installer.js'
|
|
3
|
-
import {
|
|
3
|
+
import type { Allowed, FileChange, Disposable, Imports, Mod, ModDefinition, PaneHandle, SettingsFile, SettingsFileChange, SettingsFileChangeListener, Toast } from '../mod.js'
|
|
4
4
|
import type { Options, OptionValues } from '../options.js'
|
|
5
5
|
import { changelogPage, consentPath, dataFolder, finishStepsMethod, isAtLeast, marketplaceOf, oldestCmodFor, openPageMethod, parseConsent, pendingStepsMethod, parseEvent, permissionWords, readRecord, readSteps, recordPath, scriptsSha256, settingsPagesMethod, storeFolder, updatesToTurnOn, type ReadFile, type RunnerEvent, type Steps } from '../records.js'
|
|
6
6
|
import { homePrefix, ifMissing, relativePath } from '../utils/paths.js'
|
|
@@ -10,9 +10,9 @@ import { beforeDeadline } from './deadline.js'
|
|
|
10
10
|
import { announcedOf, announcementMs, announcementOf } from './announce.js'
|
|
11
11
|
import { bind } from './bound.js'
|
|
12
12
|
import { serveConversations } from './conversations.js'
|
|
13
|
-
import { answerCall, cmodPluginName, dependencyCalls, withBareRefusals } from './dependencies.js'
|
|
13
|
+
import { answerCall, answeringOwnCalls, cmodPluginName, dependencyCalls, withBareRefusals } from './dependencies.js'
|
|
14
14
|
import { keepEngine } from './engine.js'
|
|
15
|
-
import { classicHook,
|
|
15
|
+
import { classicHook, permissionEvents, userSkillHook, type Hook, type HookListener, type RoutedEvent } from './hooks.js'
|
|
16
16
|
import { checkedAnswer, checkingGrants, checkSettings, checksAnswers, checkWrite, isCovered, itemOf, passedDown, programAddresses, type AnswerCheck, type Grants } from './grants.js'
|
|
17
17
|
import { cmodError, lastLineOf, locatingPrograms, readLines, type Programs } from './programs.js'
|
|
18
18
|
import { createModFiles, resolvedPath, type Places } from './metadata.js'
|
|
@@ -51,6 +51,7 @@ export type Lifecycle<State extends object> = {
|
|
|
51
51
|
readonly toasts: readonly ShownToast[]
|
|
52
52
|
start(claude: Claude, read: (claude: Claude) => Promise<Plugin>): Promise<void>
|
|
53
53
|
route<N extends RoutedEvent>(event: N, e: Frozen<Args<N>>, next: RouterNext<N>): Promise<EventResult<N>>
|
|
54
|
+
raise(method: string, payload: Readonly<Record<string, unknown>>): Promise<unknown>
|
|
54
55
|
}
|
|
55
56
|
|
|
56
57
|
type ModRuntime = {
|
|
@@ -70,6 +71,7 @@ type ActiveMod<State extends object, Declared extends Options> = {
|
|
|
70
71
|
readonly mod: Mod<State, Declared>
|
|
71
72
|
readonly added: readonly string[]
|
|
72
73
|
readonly imports: Imports
|
|
74
|
+
readonly raise: (method: string, payload: Readonly<Record<string, unknown>>) => Promise<unknown>
|
|
73
75
|
}
|
|
74
76
|
|
|
75
77
|
|
|
@@ -164,6 +166,7 @@ export function createLifecycle<State extends object, Declared extends Options =
|
|
|
164
166
|
let activation: Promise<void> | undefined
|
|
165
167
|
let mod: Mod<State> | undefined
|
|
166
168
|
let imports: Imports | undefined
|
|
169
|
+
let raise: ActiveMod<State, Declared>['raise'] | undefined
|
|
167
170
|
let failure: unknown
|
|
168
171
|
let pluginOptions = options
|
|
169
172
|
const installer = createInstaller(definition.name, () => claude())
|
|
@@ -325,6 +328,7 @@ export function createLifecycle<State extends object, Declared extends Options =
|
|
|
325
328
|
const { added } = active
|
|
326
329
|
mod = active.mod
|
|
327
330
|
imports = active.imports
|
|
331
|
+
raise = active.raise
|
|
328
332
|
router = activeRouter
|
|
329
333
|
phase = 'active'
|
|
330
334
|
runtime.claude.ui.invalidate('ui.render')
|
|
@@ -560,6 +564,11 @@ export function createLifecycle<State extends object, Declared extends Options =
|
|
|
560
564
|
get toasts() {
|
|
561
565
|
return runtime?.toaster.shown ?? []
|
|
562
566
|
},
|
|
567
|
+
async raise(method, payload) {
|
|
568
|
+
if (raise === undefined || imports === undefined) throw new Error(`${definition.name} has not started, so ${method} has no listener yet.`)
|
|
569
|
+
bind(imports)
|
|
570
|
+
return raise(method, payload)
|
|
571
|
+
},
|
|
563
572
|
async start(claudeCalls, read) {
|
|
564
573
|
if (runtime !== undefined) return
|
|
565
574
|
programs = locatingPrograms(claudeCalls)
|
|
@@ -609,9 +618,8 @@ export function createLifecycle<State extends object, Declared extends Options =
|
|
|
609
618
|
async function createMod<State extends object, Declared extends Options>(definition: ModDefinition<State, string, Declared>, runtime: ModRuntime): Promise<ActiveMod<State, Declared>> {
|
|
610
619
|
const { router, toaster } = runtime
|
|
611
620
|
const report = (failure: Failure) => void toaster.fail(failure)
|
|
612
|
-
const claude = withBareRefusals(checkingKeys(runtime.claude, toaster.toast, runtime.steps.keys ?? {}))
|
|
621
|
+
const claude = answeringOwnCalls(withBareRefusals(checkingKeys(runtime.claude, toaster.toast, runtime.steps.keys ?? {})), router)
|
|
613
622
|
const added: string[] = []
|
|
614
|
-
const hooksAdded: Hook[] = []
|
|
615
623
|
const announce = (feature: string) => {
|
|
616
624
|
added.push(feature)
|
|
617
625
|
}
|
|
@@ -680,17 +688,26 @@ async function createMod<State extends object, Declared extends Options>(definit
|
|
|
680
688
|
}))
|
|
681
689
|
const modState = createState<State>({ name: definition.name, initial: definition.state ?? {}, session, root, claude, report, changed: area.changed, forkParent })
|
|
682
690
|
const modOptions = createOptions({ name: definition.name, declared: definition.options ?? {}, fromClaude: runtime.options, claude, report, changed: modState.changed })
|
|
683
|
-
const
|
|
691
|
+
const hookOn = <H extends Hook>(hook: H, listener: HookListener<H>): Disposable => router.add(`classic.${hook}`, classicHook(definition.name, hook, listener, report))
|
|
684
692
|
const expand = (path: string) => (homePrefix.test(path) ? resolvedPath(path, places) : path)
|
|
685
693
|
const settingsFile = (source: 'user' | 'project' | 'local'): SettingsFile => ({
|
|
686
694
|
read: () => checked.settings.read({ source }),
|
|
687
695
|
async update(values) {
|
|
688
|
-
await checkSettings(grants, source,
|
|
696
|
+
await checkSettings(grants, source, values)
|
|
689
697
|
await claude.cmod.call({ to: cmodPluginName, method: 'updateSettings', input: { mod: definition.name, source, project: modState.root, values } })
|
|
690
698
|
},
|
|
691
699
|
})
|
|
692
|
-
const client = createSessionsClient({
|
|
700
|
+
const client = createSessionsClient({
|
|
701
|
+
name: definition.name,
|
|
702
|
+
claude: checked,
|
|
703
|
+
report: (id, message, error) => report({ id, message, error }),
|
|
704
|
+
isGranted: (permission) => flag(permission),
|
|
705
|
+
hookOn: (hook, listener) => hookOn(hook as Hook, listener as HookListener<Hook>),
|
|
706
|
+
routeOn: (event, hook) => router.add(event, hook as never),
|
|
707
|
+
})
|
|
693
708
|
let followed: Parameters<typeof client.follow>[0] | undefined
|
|
709
|
+
let isSetUp = false
|
|
710
|
+
const watches = new Set<{ readonly paths: readonly string[]; readonly listener: (change: FileChange) => unknown }>()
|
|
694
711
|
const followFolders = (project: string, folder: string) => {
|
|
695
712
|
if (followed === undefined) return
|
|
696
713
|
followed = { ...followed, project, cwd: folder }
|
|
@@ -698,7 +715,16 @@ async function createMod<State extends object, Declared extends Options>(definit
|
|
|
698
715
|
}
|
|
699
716
|
const imports: Imports = {
|
|
700
717
|
sessions: client.sessions,
|
|
701
|
-
|
|
718
|
+
commands: {
|
|
719
|
+
list: async () => (await checked.command.list()).map(({ name, description, source, plugin }) => ({ name, description, source, ...(plugin === undefined ? {} : { plugin }) })),
|
|
720
|
+
run: async (command, args) => {
|
|
721
|
+
const { text, context } = await checked.command.run({ command, ...(args === undefined ? {} : { args }) })
|
|
722
|
+
return { ...(text === undefined ? {} : { text }), ...(context === undefined || context.length === 0 ? {} : { note: context.join('\n') }) }
|
|
723
|
+
},
|
|
724
|
+
beforeRun: (...args: unknown[]) => client.listen('beforeRun', args),
|
|
725
|
+
onRun: (...args: unknown[]) => client.listen('onRun', args),
|
|
726
|
+
},
|
|
727
|
+
config: {
|
|
702
728
|
list: async () =>
|
|
703
729
|
(await checked.config.list()).map(({ key, label, description, kind, value, options, provider, isLocked }) => ({
|
|
704
730
|
key,
|
|
@@ -710,9 +736,21 @@ async function createMod<State extends object, Declared extends Options>(definit
|
|
|
710
736
|
...(options === undefined ? {} : { options }),
|
|
711
737
|
...(provider.plugin === 'engine' ? {} : { plugin: provider.plugin }),
|
|
712
738
|
})),
|
|
739
|
+
},
|
|
740
|
+
settings: {
|
|
713
741
|
user: settingsFile('user'),
|
|
714
742
|
project: settingsFile('project'),
|
|
715
743
|
local: settingsFile('local'),
|
|
744
|
+
beforeFileChange: (matcherOrListener: unknown, maybeListener?: SettingsFileChangeListener) => {
|
|
745
|
+
const listener = (maybeListener ?? matcherOrListener) as SettingsFileChangeListener
|
|
746
|
+
const matcher = maybeListener === undefined ? undefined : matcherOrListener
|
|
747
|
+
return hookOn('ConfigChange', async ({ source, file_path }) => {
|
|
748
|
+
const change: SettingsFileChange = { source: source === 'skills' ? 'skills' : (source.replace('_settings', '') as SettingsFileChange['source']), ...(file_path === undefined ? {} : { path: file_path }) }
|
|
749
|
+
if (matcher !== undefined && !matches(change, matcher)) return undefined
|
|
750
|
+
const answer = await listener(change)
|
|
751
|
+
return answer === undefined ? undefined : { decision: 'block', reason: answer.refuse }
|
|
752
|
+
})
|
|
753
|
+
},
|
|
716
754
|
},
|
|
717
755
|
model: { complete: (completion, options) => checked.model.complete(completion, options) },
|
|
718
756
|
fs: {
|
|
@@ -725,6 +763,19 @@ async function createMod<State extends object, Declared extends Options>(definit
|
|
|
725
763
|
const { kind, size, mtimeMs, isLink, realPath } = await checked.fs.stat(expand(path), options)
|
|
726
764
|
return { isFile: kind === 'file', isFolder: kind === 'dir', isLink, size, modifiedAt: mtimeMs, ...(realPath === undefined ? {} : { realPath }) }
|
|
727
765
|
},
|
|
766
|
+
onChange({ path }, listener) {
|
|
767
|
+
if (isSetUp) throw new Error(`${definition.name}: fs.onChange works only in setup, because Claude Code reads the paths it watches when a session starts.`)
|
|
768
|
+
const paths = [path].flat().map((each) => resolvedPath(each, places))
|
|
769
|
+
const watch = { paths, listener }
|
|
770
|
+
watches.add(watch)
|
|
771
|
+
if (watches.size === 1) {
|
|
772
|
+
hookOn('SessionStart', () => ({ hookSpecificOutput: { watchPaths: [...new Set([...watches].flatMap((each) => each.paths))] } }))
|
|
773
|
+
hookOn('FileChanged', ({ file_path, event }) => {
|
|
774
|
+
for (const each of watches) if (each.paths.some((watched) => relativePath(watched, file_path) !== undefined)) void Promise.resolve(each.listener({ path: file_path, type: event })).catch((error: unknown) => report({ id: 'fs:onChange', message: 'The fs.onChange listener failed', error }))
|
|
775
|
+
})
|
|
776
|
+
}
|
|
777
|
+
return { dispose: () => void watches.delete(watch) }
|
|
778
|
+
},
|
|
728
779
|
},
|
|
729
780
|
metadata: {
|
|
730
781
|
read: (path) => files.read(path),
|
|
@@ -787,23 +838,10 @@ async function createMod<State extends object, Declared extends Options>(definit
|
|
|
787
838
|
files: allowed('files'),
|
|
788
839
|
config: allowed('config'),
|
|
789
840
|
},
|
|
790
|
-
on(hook: Hook, matcherOrListener: unknown, listenerOrOptions?: unknown, maybeOptions?: HookOptions): Disposable {
|
|
791
|
-
const hasMatcher = typeof listenerOrOptions === 'function'
|
|
792
|
-
const listener = (hasMatcher ? listenerOrOptions : matcherOrListener) as HookListener<Hook>
|
|
793
|
-
const matcher = hasMatcher ? matcherOrListener : undefined
|
|
794
|
-
const options = (hasMatcher ? maybeOptions : listenerOrOptions) as HookOptions | undefined
|
|
795
|
-
if (!hooks.includes(hook)) throw new Error(`${definition.name}: mod.on takes one of Claude Code's hooks, such as PreToolUse, not ${String(hook)}.`)
|
|
796
|
-
const matched: HookListener<Hook> = matcher === undefined ? listener : (input) => (matches(input, matcher) ? listener(input) : undefined)
|
|
797
|
-
const bounded = options?.timeoutMs === undefined ? matched : timedHook(hook, matched, checkedMs(definition.name, `the ${hook} hook's timeoutMs`, options.timeoutMs), claude, toaster.toast)
|
|
798
|
-
if (!hooksAdded.includes(hook)) hooksAdded.push(hook)
|
|
799
|
-
if (hook === PreToolUse) return router.add('tool.call', preToolUseHook(definition.name, bounded as HookListener<typeof PreToolUse>, claude, agents, held))
|
|
800
|
-
const classic: Exclude<Hook, typeof PreToolUse> = hook
|
|
801
|
-
return router.add(`classic.${classic}`, classicHook(definition.name, classic, bounded as HookListener<typeof classic>, claude, agents, report))
|
|
802
|
-
},
|
|
803
841
|
every: (ms, listener) => timer(ms, 'every', claude.clock.every, listener),
|
|
804
842
|
after: (ms, listener) => timer(ms, 'after', claude.clock.after, listener),
|
|
805
843
|
use: (job) => job({ mod, announce, reserveName }),
|
|
806
|
-
log: (text) => claude.ui.log(`${definition.name}: ${text}`, { to: 'debug' }),
|
|
844
|
+
log: (text, options) => claude.ui.log(`${definition.name}: ${text}`, { to: options?.to ?? 'debug' }),
|
|
807
845
|
ui: { ...area.ui, notice: (callId, text) => claude.ui.notice(callId, text) },
|
|
808
846
|
settings: settingsPages,
|
|
809
847
|
dependencies: dependencyCalls(claude, (call, task) => beforeDeadline(claude, { ms: dependencyCallMs }, call, task)),
|
|
@@ -829,8 +867,8 @@ async function createMod<State extends object, Declared extends Options>(definit
|
|
|
829
867
|
loadedCwd = nextCwd
|
|
830
868
|
if (!hasMovedRoot) area.changed()
|
|
831
869
|
if (nextCwd === oldCwd) return
|
|
832
|
-
const moved
|
|
833
|
-
await router.dispatch('classic.CwdChanged', moved as Frozen<Args<'classic.CwdChanged'>>, async () => ({}))
|
|
870
|
+
const moved = { session_id: await claude.session.id(), cwd: nextCwd, hook_event_name: 'CwdChanged', old_cwd: oldCwd, new_cwd: nextCwd }
|
|
871
|
+
await router.dispatch('classic.CwdChanged', moved as unknown as Frozen<Args<'classic.CwdChanged'>>, async () => ({}))
|
|
834
872
|
} catch (error) {
|
|
835
873
|
report({ id: 'cmod:follow', message: `Keeps the state of ${modState.root} until the next prompt or folder move`, error })
|
|
836
874
|
}
|
|
@@ -883,8 +921,8 @@ async function createMod<State extends object, Declared extends Options>(definit
|
|
|
883
921
|
if (e.to !== definition.name) return next(e)
|
|
884
922
|
const call = e as typeof e & { readonly caller?: string }
|
|
885
923
|
if (e.method === conversationMethods.changed && call.caller === cmodPluginName) {
|
|
886
|
-
if (!flag('conversation')) return { value: null }
|
|
887
924
|
const { session: changedSession, change, isInProject } = e.input as { readonly session: string; readonly change: SessionChange; readonly isInProject?: boolean }
|
|
925
|
+
if (changedSession !== client.sessions.current.id && !flag('conversation')) return { value: null }
|
|
888
926
|
client.changed(changedSession, change, isInProject !== false)
|
|
889
927
|
return { value: null }
|
|
890
928
|
}
|
|
@@ -915,14 +953,13 @@ async function createMod<State extends object, Declared extends Options>(definit
|
|
|
915
953
|
if (missing.length > 0) throw new MissingOptions(missing, missing.map((key) => definition.options?.[key]?.title ?? key))
|
|
916
954
|
await failsAs('the details of its session did not load', () => followCurrent(undefined))
|
|
917
955
|
await failsAs('its setup function threw', () => definition.setup(mod))
|
|
956
|
+
isSetUp = true
|
|
918
957
|
const permissionHooks = permissionEvents.filter((event) => router.has(event))
|
|
919
958
|
if (permissionHooks.length > 0 && !runtime.checksPermissions()) {
|
|
920
959
|
throw new Error(`it decides permissions on ${listed(permissionHooks)}, so hooks/register.ts must call registerPermissionCheck(addHook) after registerMod`)
|
|
921
960
|
}
|
|
922
|
-
if (held !== undefined && hooksAdded.includes(PreToolUse)) router.add('tool.check', heldDecisionHook(held))
|
|
923
961
|
await failsAs('its open panes did not load', () => area.restorePanes())
|
|
924
|
-
|
|
925
|
-
return { mod, added, imports }
|
|
962
|
+
return { mod, added, imports, raise: client.raise }
|
|
926
963
|
}
|
|
927
964
|
|
|
928
965
|
function checkingKeys(claude: Claude, toast: Toast, keys: Readonly<Record<string, string>>): Claude {
|
|
@@ -946,28 +983,6 @@ function checkedMs(name: string, subject: string, ms: number): number {
|
|
|
946
983
|
return ms
|
|
947
984
|
}
|
|
948
985
|
|
|
949
|
-
function timedHook<H extends Hook>(event: H, hook: HookListener<H>, ms: number, claude: Claude, toast: Toast): HookListener<H> {
|
|
950
|
-
return (input) =>
|
|
951
|
-
new Promise((resolve, reject) => {
|
|
952
|
-
const timer = claude.clock.after(ms, () => {
|
|
953
|
-
toast.warning(`The ${event} hook passed its ${ms / 1000} s timeout`, { id: `timeout:${event}` })
|
|
954
|
-
resolve(undefined)
|
|
955
|
-
})
|
|
956
|
-
Promise.resolve()
|
|
957
|
-
.then(() => hook(input))
|
|
958
|
-
.then(
|
|
959
|
-
(answer) => {
|
|
960
|
-
timer.cancel()
|
|
961
|
-
resolve(answer)
|
|
962
|
-
},
|
|
963
|
-
(error: unknown) => {
|
|
964
|
-
timer.cancel()
|
|
965
|
-
reject(error)
|
|
966
|
-
},
|
|
967
|
-
)
|
|
968
|
-
})
|
|
969
|
-
}
|
|
970
|
-
|
|
971
986
|
async function failsAs(subject: string, step: () => unknown): Promise<void> {
|
|
972
987
|
try {
|
|
973
988
|
await step()
|