@cmodjs/core 1.6.0 → 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 +4 -4
- 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 +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.map +1 -1
- package/runtime/grants.js +13 -8
- 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 +66 -49
- 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 +1 -1
- 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 +11 -7
- package/src/runtime/hooks.ts +48 -116
- package/src/runtime/lifecycle.ts +65 -50
- 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/runtime/hooks.d.ts
CHANGED
|
@@ -1,19 +1,36 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { ClassicHookEvent, ClassicHookInputs, PermissionRequestDecision } from 'claude-code';
|
|
2
2
|
import type { Claude } from './claude.js';
|
|
3
3
|
import type { RoutedHook } from './router.js';
|
|
4
4
|
import type { Failure } from './toaster.js';
|
|
5
|
-
import { type
|
|
6
|
-
export type
|
|
5
|
+
import { type CallBase } from '../utils/call-effects.js';
|
|
6
|
+
export type Hook = Exclude<ClassicHookEvent, 'PreToolUse'>;
|
|
7
|
+
export type HookAnswer = {
|
|
8
|
+
decision?: 'block';
|
|
9
|
+
reason?: string;
|
|
10
|
+
hookSpecificOutput?: {
|
|
11
|
+
hookEventName?: Hook;
|
|
12
|
+
additionalContext?: string;
|
|
13
|
+
permissionDecision?: 'deny' | 'ask';
|
|
14
|
+
permissionDecisionReason?: string;
|
|
15
|
+
sessionTitle?: string;
|
|
16
|
+
initialUserMessage?: string;
|
|
17
|
+
watchPaths?: string[];
|
|
18
|
+
reloadSkills?: boolean;
|
|
19
|
+
decision?: PermissionRequestDecision;
|
|
20
|
+
retry?: boolean;
|
|
21
|
+
displayContent?: string;
|
|
22
|
+
worktreePath?: string;
|
|
23
|
+
};
|
|
24
|
+
};
|
|
25
|
+
export type HookListener<H extends Hook> = (input: ClassicHookInputs[H]) => HookAnswer | void | Promise<HookAnswer | void>;
|
|
26
|
+
export type RoutedEvent = `classic.${Hook}` | 'tool.check' | 'tool.call' | 'prompt.submit' | 'prompt.context' | 'prompt.attachment' | 'command.run' | 'session.measure' | 'skill.prompt' | 'ui.render' | 'ui.press' | 'ui.scroll' | 'ui.close' | 'cmod.call' | 'session.append' | 'session.compact' | 'session.receive' | 'agent.spawn' | 'turn.start';
|
|
7
27
|
export declare const permissionEvents: readonly RoutedEvent[];
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
readonly
|
|
11
|
-
readonly reason?: string;
|
|
28
|
+
type CallFiles = {
|
|
29
|
+
readonly read: string[];
|
|
30
|
+
readonly changed: string[];
|
|
12
31
|
};
|
|
13
|
-
export
|
|
14
|
-
export declare function preToolUseHook(name: string, hook: HookListener<'PreToolUse'>, claude: Claude, calls: ToolCalls, held: HeldDecisions | undefined): RoutedHook<'tool.call'>;
|
|
15
|
-
export declare function heldDecisionHook(held: HeldDecisions): RoutedHook<'tool.check'>;
|
|
32
|
+
export declare function classicHook<H extends Hook>(name: string, event: H, hook: HookListener<H>, report: (failure: Failure) => void): RoutedHook<RoutedEvent>;
|
|
16
33
|
export declare function userSkillHook(claude: Claude): RoutedHook<'skill.prompt'>;
|
|
17
|
-
export declare
|
|
34
|
+
export declare function callFiles(name: string, claude: Claude, use: CallBase, cwd: string): Promise<CallFiles>;
|
|
18
35
|
export {};
|
|
19
36
|
//# sourceMappingURL=hooks.d.ts.map
|
package/runtime/hooks.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hooks.d.ts","sourceRoot":"","sources":["../src/runtime/hooks.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"hooks.d.ts","sourceRoot":"","sources":["../src/runtime/hooks.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,yBAAyB,EAAE,MAAM,aAAa,CAAA;AAEjG,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,EAAe,KAAK,QAAQ,EAAmB,MAAM,0BAA0B,CAAA;AAKtF,MAAM,MAAM,IAAI,GAAG,OAAO,CAAC,gBAAgB,EAAE,YAAY,CAAC,CAAA;AAE1D,MAAM,MAAM,UAAU,GAAG;IACvB,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,kBAAkB,CAAC,EAAE;QACnB,aAAa,CAAC,EAAE,IAAI,CAAA;QACpB,iBAAiB,CAAC,EAAE,MAAM,CAAA;QAC1B,kBAAkB,CAAC,EAAE,MAAM,GAAG,KAAK,CAAA;QACnC,wBAAwB,CAAC,EAAE,MAAM,CAAA;QACjC,YAAY,CAAC,EAAE,MAAM,CAAA;QACrB,kBAAkB,CAAC,EAAE,MAAM,CAAA;QAC3B,UAAU,CAAC,EAAE,MAAM,EAAE,CAAA;QACrB,YAAY,CAAC,EAAE,OAAO,CAAA;QACtB,QAAQ,CAAC,EAAE,yBAAyB,CAAA;QACpC,KAAK,CAAC,EAAE,OAAO,CAAA;QACf,cAAc,CAAC,EAAE,MAAM,CAAA;QACvB,YAAY,CAAC,EAAE,MAAM,CAAA;KACtB,CAAA;CACF,CAAA;AAED,MAAM,MAAM,YAAY,CAAC,CAAC,SAAS,IAAI,IAAI,CAAC,KAAK,EAAE,iBAAiB,CAAC,CAAC,CAAC,KAAK,UAAU,GAAG,IAAI,GAAG,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CAAA;AAE1H,MAAM,MAAM,WAAW,GACnB,WAAW,IAAI,EAAE,GACjB,YAAY,GACZ,WAAW,GACX,eAAe,GACf,gBAAgB,GAChB,mBAAmB,GACnB,aAAa,GACb,iBAAiB,GACjB,cAAc,GACd,WAAW,GACX,UAAU,GACV,WAAW,GACX,UAAU,GACV,WAAW,GACX,gBAAgB,GAChB,iBAAiB,GACjB,iBAAiB,GACjB,aAAa,GACb,YAAY,CAAA;AAEhB,eAAO,MAAM,gBAAgB,EAAE,SAAS,WAAW,EAAgD,CAAA;AAInG,KAAK,SAAS,GAAG;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,CAAC;IAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,EAAE,CAAA;CAAE,CAAA;AAuCxE,wBAAgB,WAAW,CAAC,CAAC,SAAS,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,EAAE,IAAI,EAAE,YAAY,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,GAAG,UAAU,CAAC,WAAW,CAAC,CAgBtJ;AAMD,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG,UAAU,CAAC,cAAc,CAAC,CAexE;AAED,wBAAsB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,QAAQ,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,CAAC,CAgB5G"}
|
package/runtime/hooks.js
CHANGED
|
@@ -1,26 +1,24 @@
|
|
|
1
1
|
import { configFolders } from '../records.js';
|
|
2
|
-
import {
|
|
3
|
-
import { callEffects, inputOf } from '../utils/call-effects.js';
|
|
2
|
+
import { callEffects } from '../utils/call-effects.js';
|
|
4
3
|
import { dynamicPattern } from '../utils/parse-shell.js';
|
|
5
4
|
import { isMissing } from '../utils/paths.js';
|
|
6
5
|
import { messageOf } from '../utils/text.js';
|
|
7
6
|
export const permissionEvents = ['tool.check', 'classic.PermissionRequest'];
|
|
8
|
-
const flags = ['
|
|
7
|
+
const flags = ['reloadSkills', 'retry'];
|
|
9
8
|
const readFields = {
|
|
10
9
|
Setup: ['additionalContext'],
|
|
11
10
|
SessionStart: ['additionalContext', 'initialUserMessage', 'sessionTitle', 'watchPaths', 'reloadSkills'],
|
|
12
11
|
SessionEnd: [],
|
|
13
|
-
UserPromptSubmit: ['additionalContext', 'sessionTitle'
|
|
14
|
-
UserPromptExpansion: ['additionalContext'
|
|
12
|
+
UserPromptSubmit: ['additionalContext', 'sessionTitle'],
|
|
13
|
+
UserPromptExpansion: ['additionalContext'],
|
|
15
14
|
InstructionsLoaded: [],
|
|
16
15
|
ConfigChange: [],
|
|
17
16
|
DirectoryAdded: [],
|
|
18
17
|
PreModelSwitch: ['permissionDecision', 'permissionDecisionReason'],
|
|
19
18
|
PostModelSwitch: ['additionalContext'],
|
|
20
|
-
PreToolUse: ['additionalContext', 'permissionDecision', 'permissionDecisionReason', 'updatedInput'],
|
|
21
19
|
PermissionRequest: ['decision'],
|
|
22
20
|
PermissionDenied: ['retry'],
|
|
23
|
-
PostToolUse: ['additionalContext'
|
|
21
|
+
PostToolUse: ['additionalContext'],
|
|
24
22
|
PostToolUseFailure: ['additionalContext'],
|
|
25
23
|
PostToolBatch: ['additionalContext'],
|
|
26
24
|
MessageDisplay: ['displayContent'],
|
|
@@ -41,27 +39,17 @@ const readFields = {
|
|
|
41
39
|
WorktreeCreate: ['worktreePath'],
|
|
42
40
|
WorktreeRemove: [],
|
|
43
41
|
};
|
|
44
|
-
export function classicHook(name, event, hook,
|
|
45
|
-
const hookInputOf = async (e) => {
|
|
46
|
-
if (event !== 'PostToolUse' && event !== 'PostToolUseFailure')
|
|
47
|
-
return e;
|
|
48
|
-
const input = e;
|
|
49
|
-
const cwd = calls.cwdOf(input.tool_use_id) ?? input.cwd;
|
|
50
|
-
return { ...input, files: await callFiles(name, claude, { tool: input.tool_name, input: inputOf(input.tool_input) }, cwd) };
|
|
51
|
-
};
|
|
52
|
-
const resultOf = async (e) => {
|
|
53
|
-
const answer = await hook((await hookInputOf(e)));
|
|
54
|
-
if (answer === undefined)
|
|
55
|
-
return undefined;
|
|
56
|
-
if (answer.systemMessage !== undefined)
|
|
57
|
-
claude.ui.log(answer.systemMessage);
|
|
58
|
-
return classicResult(event, answer);
|
|
59
|
-
};
|
|
42
|
+
export function classicHook(name, event, hook, report) {
|
|
60
43
|
const routed = async (e, next) => {
|
|
61
|
-
const ours = await
|
|
44
|
+
const ours = await Promise.resolve()
|
|
45
|
+
.then(async () => {
|
|
46
|
+
const answer = await hook(e);
|
|
47
|
+
return answer === undefined ? undefined : classicResult(event, answer);
|
|
48
|
+
})
|
|
49
|
+
.catch((error) => {
|
|
62
50
|
if (event === 'PermissionRequest')
|
|
63
|
-
return { decision: { behavior: 'deny', message: `${name}: the ${event}
|
|
64
|
-
report({ id: `hook:${event}`, message: `The ${event}
|
|
51
|
+
return { decision: { behavior: 'deny', message: `${name}: the ${event} listener failed: ${messageOf(error)}` } };
|
|
52
|
+
report({ id: `hook:${event}`, message: `The ${event} listener failed`, error });
|
|
65
53
|
return undefined;
|
|
66
54
|
});
|
|
67
55
|
if (ours === undefined)
|
|
@@ -70,57 +58,6 @@ export function classicHook(name, event, hook, claude, calls, report) {
|
|
|
70
58
|
};
|
|
71
59
|
return routed;
|
|
72
60
|
}
|
|
73
|
-
export function preToolUseHook(name, hook, claude, calls, held) {
|
|
74
|
-
return async (e, next) => {
|
|
75
|
-
let ours;
|
|
76
|
-
try {
|
|
77
|
-
const answer = await hook(await preToolUseInput(name, e, claude, calls.agentOf));
|
|
78
|
-
if (answer?.systemMessage !== undefined)
|
|
79
|
-
claude.ui.log(answer.systemMessage);
|
|
80
|
-
ours = answer === undefined ? {} : classicResult('PreToolUse', answer);
|
|
81
|
-
}
|
|
82
|
-
catch (error) {
|
|
83
|
-
return { deny: `${name}: the PreToolUse hook failed: ${messageOf(error)}` };
|
|
84
|
-
}
|
|
85
|
-
if (typeof ours['deny'] === 'string')
|
|
86
|
-
return { deny: ours['deny'] };
|
|
87
|
-
const decided = heldDecisionOf(ours);
|
|
88
|
-
if (decided !== undefined && held === undefined) {
|
|
89
|
-
return { deny: `${name}: the PreToolUse hook answered permissionDecision "${decided.decision}", so hooks/register.ts must call registerPermissionCheck(addHook) after registerMod.` };
|
|
90
|
-
}
|
|
91
|
-
const updated = ours['updatedInput'];
|
|
92
|
-
const reserved = Object.fromEntries(Object.entries(e).filter(([key]) => reservedKeys.includes(key)));
|
|
93
|
-
const earlier = held?.get(e.tool_use_id);
|
|
94
|
-
if (decided !== undefined)
|
|
95
|
-
held?.set(e.tool_use_id, earlier !== undefined && strictness.indexOf(earlier.decision) >= strictness.indexOf(decided.decision) ? earlier : decided);
|
|
96
|
-
let result;
|
|
97
|
-
try {
|
|
98
|
-
result = await next(updated === undefined ? e : { ...reserved, ...updated });
|
|
99
|
-
}
|
|
100
|
-
finally {
|
|
101
|
-
if (decided !== undefined)
|
|
102
|
-
held?.delete(e.tool_use_id);
|
|
103
|
-
}
|
|
104
|
-
const context = ours['additionalContext'];
|
|
105
|
-
if (context === undefined || result.deny !== undefined)
|
|
106
|
-
return result;
|
|
107
|
-
return { ...result, context: [...(result.context ?? []), ...context] };
|
|
108
|
-
};
|
|
109
|
-
}
|
|
110
|
-
export function heldDecisionHook(held) {
|
|
111
|
-
return async (e, next) => {
|
|
112
|
-
const below = await next(e);
|
|
113
|
-
const ours = e.tool_use_id === undefined ? undefined : held.get(e.tool_use_id);
|
|
114
|
-
return ours === undefined || below.decision === 'deny' ? below : ours;
|
|
115
|
-
};
|
|
116
|
-
}
|
|
117
|
-
function heldDecisionOf(ours) {
|
|
118
|
-
if (ours['allow'] !== undefined)
|
|
119
|
-
return { decision: 'allow' };
|
|
120
|
-
if (typeof ours['ask'] !== 'string')
|
|
121
|
-
return undefined;
|
|
122
|
-
return ours['ask'] === '' ? { decision: 'ask' } : { decision: 'ask', reason: ours['ask'] };
|
|
123
|
-
}
|
|
124
61
|
const frontmatter = /^---\r?\n[\s\S]*?\r?\n---[ \t]*(?:\r?\n|$)/;
|
|
125
62
|
const baseDirectoryLine = /^Base directory for this skill: [^\n]*\n\n/;
|
|
126
63
|
export function userSkillHook(claude) {
|
|
@@ -143,23 +80,7 @@ export function userSkillHook(claude) {
|
|
|
143
80
|
return next(e);
|
|
144
81
|
};
|
|
145
82
|
}
|
|
146
|
-
async function
|
|
147
|
-
const { tool, tool_use_id } = envelope;
|
|
148
|
-
const [session_id, cwd, { agentId, agentType }] = await Promise.all([claude.session.id(), claude.session.cwd(), agentOf(tool_use_id)]);
|
|
149
|
-
const tool_input = toolInputOf(envelope);
|
|
150
|
-
return {
|
|
151
|
-
session_id,
|
|
152
|
-
cwd,
|
|
153
|
-
hook_event_name: 'PreToolUse',
|
|
154
|
-
tool_name: tool,
|
|
155
|
-
tool_input,
|
|
156
|
-
tool_use_id,
|
|
157
|
-
...(agentId === undefined ? {} : { agent_id: agentId }),
|
|
158
|
-
...(agentType === undefined ? {} : { agent_type: agentType }),
|
|
159
|
-
files: await callFiles(name, claude, { tool, input: tool_input }, cwd),
|
|
160
|
-
};
|
|
161
|
-
}
|
|
162
|
-
async function callFiles(name, claude, use, cwd) {
|
|
83
|
+
export async function callFiles(name, claude, use, cwd) {
|
|
163
84
|
try {
|
|
164
85
|
const [home, configRoot] = await Promise.all([claude.env.home(), claude.env.configHome()]);
|
|
165
86
|
if (home === undefined)
|
|
@@ -181,42 +102,18 @@ async function callFiles(name, claude, use, cwd) {
|
|
|
181
102
|
}
|
|
182
103
|
function classicResult(event, answer) {
|
|
183
104
|
const result = {};
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
if (answer.continue === false || answer.stopReason !== undefined)
|
|
187
|
-
throw unread('continue or stopReason');
|
|
188
|
-
if (answer.decision === 'block')
|
|
189
|
-
result['deny'] = answer.reason ?? '';
|
|
190
|
-
}
|
|
191
|
-
else {
|
|
192
|
-
if (answer.continue === false)
|
|
193
|
-
result['preventContinuation'] = true;
|
|
194
|
-
if (answer.stopReason !== undefined)
|
|
195
|
-
result['stopReason'] = answer.stopReason;
|
|
196
|
-
if (answer.decision === 'block')
|
|
197
|
-
result['block'] = answer.reason ?? '';
|
|
198
|
-
}
|
|
105
|
+
if (answer.decision === 'block')
|
|
106
|
+
result['block'] = answer.reason ?? '';
|
|
199
107
|
const specific = answer.hookSpecificOutput ?? {};
|
|
200
|
-
if (specific.hookEventName !== undefined && specific.hookEventName !== event) {
|
|
201
|
-
throw new Error(`it answered hookSpecificOutput.hookEventName "${specific.hookEventName}". Set it to "${event}" or leave it out.`);
|
|
202
|
-
}
|
|
203
108
|
for (const [field, value] of Object.entries(specific)) {
|
|
204
|
-
if (field === 'hookEventName' ||
|
|
109
|
+
if (field === 'hookEventName' || (value === false && flags.includes(field)))
|
|
205
110
|
continue;
|
|
206
111
|
if (!readFields[event].includes(field))
|
|
207
|
-
throw
|
|
208
|
-
|
|
209
|
-
result['additionalContext'] = [value];
|
|
210
|
-
else if (field === 'permissionDecision' && event === 'PreToolUse') {
|
|
211
|
-
const reason = specific.permissionDecisionReason ?? '';
|
|
212
|
-
result[value] = value === 'allow' ? true : reason;
|
|
213
|
-
}
|
|
214
|
-
else if (field !== 'permissionDecisionReason' || event !== 'PreToolUse')
|
|
215
|
-
result[field] = value;
|
|
112
|
+
throw new Error(`it answered ${field}, which ${event} does not read.`);
|
|
113
|
+
result[field] = field === 'additionalContext' ? [value] : value;
|
|
216
114
|
}
|
|
217
115
|
return result;
|
|
218
116
|
}
|
|
219
|
-
export const strictness = ['allow', 'ask', 'deny'];
|
|
220
117
|
function mergeClassic(below, ours) {
|
|
221
118
|
const merged = { ...below, ...ours };
|
|
222
119
|
const context = [...(below['additionalContext'] ?? []), ...(ours['additionalContext'] ?? [])];
|
package/runtime/lifecycle.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Args, EventResult, Frozen, PluginOptions } from 'claude-code';
|
|
2
|
-
import {
|
|
2
|
+
import type { Mod, ModDefinition } from '../mod.js';
|
|
3
3
|
import type { Options } from '../options.js';
|
|
4
4
|
import { type Steps } from '../records.js';
|
|
5
5
|
import type { Claude } from './claude.js';
|
|
@@ -24,6 +24,7 @@ export type Lifecycle<State extends object> = {
|
|
|
24
24
|
readonly toasts: readonly ShownToast[];
|
|
25
25
|
start(claude: Claude, read: (claude: Claude) => Promise<Plugin>): Promise<void>;
|
|
26
26
|
route<N extends RoutedEvent>(event: N, e: Frozen<Args<N>>, next: RouterNext<N>): Promise<EventResult<N>>;
|
|
27
|
+
raise(method: string, payload: Readonly<Record<string, unknown>>): Promise<unknown>;
|
|
27
28
|
};
|
|
28
29
|
export declare function readPlugin(claude: Claude): Promise<Plugin>;
|
|
29
30
|
export declare function createLifecycle<State extends object, Declared extends Options = Options>(definition: ModDefinition<State, string, Declared>, checksPermissions?: () => boolean, options?: PluginOptions, asksPerson?: boolean): Lifecycle<State>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lifecycle.d.ts","sourceRoot":"","sources":["../src/runtime/lifecycle.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,WAAW,EAAE,MAAM,EAAc,aAAa,EAAwB,MAAM,aAAa,CAAA;AAE7G,OAAO,
|
|
1
|
+
{"version":3,"file":"lifecycle.d.ts","sourceRoot":"","sources":["../src/runtime/lifecycle.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,WAAW,EAAE,MAAM,EAAc,aAAa,EAAwB,MAAM,aAAa,CAAA;AAE7G,OAAO,KAAK,EAA4C,GAAG,EAAE,aAAa,EAAmF,MAAM,WAAW,CAAA;AAC9K,OAAO,KAAK,EAAE,OAAO,EAAgB,MAAM,eAAe,CAAA;AAC1D,OAAO,EAA2T,KAAK,KAAK,EAAE,MAAM,eAAe,CAAA;AAGnW,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AAOzC,OAAO,EAA8E,KAAK,WAAW,EAAE,MAAM,YAAY,CAAA;AAOzH,OAAO,EAA6B,KAAK,UAAU,EAAE,MAAM,aAAa,CAAA;AAMxE,OAAO,EAA+B,KAAK,UAAU,EAAgB,MAAM,cAAc,CAAA;AAMzF,MAAM,MAAM,MAAM,GAAG;IACnB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,SAAS,CAAA;IACpC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAA;IAC7B,QAAQ,CAAC,YAAY,EAAE,OAAO,CAAA;IAC9B,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAA;IACrB,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAA;CACpC,CAAA;AAED,MAAM,MAAM,KAAK,GAAG,UAAU,GAAG,YAAY,GAAG,SAAS,GAAG,UAAU,GAAG,QAAQ,GAAG,OAAO,GAAG,QAAQ,CAAA;AAEtG,MAAM,MAAM,SAAS,CAAC,KAAK,SAAS,MAAM,IAAI;IAC5C,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAA;IACrB,QAAQ,CAAC,GAAG,EAAE,GAAG,CAAC,KAAK,CAAC,GAAG,SAAS,CAAA;IACpC,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAA;IACzB,QAAQ,CAAC,MAAM,EAAE,SAAS,UAAU,EAAE,CAAA;IACtC,KAAK,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAC/E,KAAK,CAAC,CAAC,SAAS,WAAW,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,UAAU,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,CAAA;IACxG,KAAK,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA;CACpF,CAAA;AAyCD,wBAAsB,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAwBhE;AAkCD,wBAAgB,eAAe,CAAC,KAAK,SAAS,MAAM,EAAE,QAAQ,SAAS,OAAO,GAAG,OAAO,EACtF,UAAU,EAAE,aAAa,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,CAAC,EAClD,iBAAiB,GAAE,MAAM,OAAoB,EAC7C,OAAO,GAAE,aAAkB,EAC3B,UAAU,UAAO,GAChB,SAAS,CAAC,KAAK,CAAC,CAyclB"}
|
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';
|
|
@@ -103,6 +102,7 @@ export function createLifecycle(definition, checksPermissions = () => true, opti
|
|
|
103
102
|
let activation;
|
|
104
103
|
let mod;
|
|
105
104
|
let imports;
|
|
105
|
+
let raise;
|
|
106
106
|
let failure;
|
|
107
107
|
let pluginOptions = options;
|
|
108
108
|
const installer = createInstaller(definition.name, () => claude());
|
|
@@ -261,6 +261,7 @@ export function createLifecycle(definition, checksPermissions = () => true, opti
|
|
|
261
261
|
const { added } = active;
|
|
262
262
|
mod = active.mod;
|
|
263
263
|
imports = active.imports;
|
|
264
|
+
raise = active.raise;
|
|
264
265
|
router = activeRouter;
|
|
265
266
|
phase = 'active';
|
|
266
267
|
runtime.claude.ui.invalidate('ui.render');
|
|
@@ -502,6 +503,12 @@ export function createLifecycle(definition, checksPermissions = () => true, opti
|
|
|
502
503
|
get toasts() {
|
|
503
504
|
return runtime?.toaster.shown ?? [];
|
|
504
505
|
},
|
|
506
|
+
async raise(method, payload) {
|
|
507
|
+
if (raise === undefined || imports === undefined)
|
|
508
|
+
throw new Error(`${definition.name} has not started, so ${method} has no listener yet.`);
|
|
509
|
+
bind(imports);
|
|
510
|
+
return raise(method, payload);
|
|
511
|
+
},
|
|
505
512
|
async start(claudeCalls, read) {
|
|
506
513
|
if (runtime !== undefined)
|
|
507
514
|
return;
|
|
@@ -565,9 +572,8 @@ export function createLifecycle(definition, checksPermissions = () => true, opti
|
|
|
565
572
|
async function createMod(definition, runtime) {
|
|
566
573
|
const { router, toaster } = runtime;
|
|
567
574
|
const report = (failure) => void toaster.fail(failure);
|
|
568
|
-
const claude = withBareRefusals(checkingKeys(runtime.claude, toaster.toast, runtime.steps.keys ?? {}));
|
|
575
|
+
const claude = answeringOwnCalls(withBareRefusals(checkingKeys(runtime.claude, toaster.toast, runtime.steps.keys ?? {})), router);
|
|
569
576
|
const added = [];
|
|
570
|
-
const hooksAdded = [];
|
|
571
577
|
const announce = (feature) => {
|
|
572
578
|
added.push(feature);
|
|
573
579
|
};
|
|
@@ -640,7 +646,7 @@ async function createMod(definition, runtime) {
|
|
|
640
646
|
}));
|
|
641
647
|
const modState = createState({ name: definition.name, initial: definition.state ?? {}, session, root, claude, report, changed: area.changed, forkParent });
|
|
642
648
|
const modOptions = createOptions({ name: definition.name, declared: definition.options ?? {}, fromClaude: runtime.options, claude, report, changed: modState.changed });
|
|
643
|
-
const
|
|
649
|
+
const hookOn = (hook, listener) => router.add(`classic.${hook}`, classicHook(definition.name, hook, listener, report));
|
|
644
650
|
const expand = (path) => (homePrefix.test(path) ? resolvedPath(path, places) : path);
|
|
645
651
|
const settingsFile = (source) => ({
|
|
646
652
|
read: () => checked.settings.read({ source }),
|
|
@@ -649,8 +655,17 @@ async function createMod(definition, runtime) {
|
|
|
649
655
|
await claude.cmod.call({ to: cmodPluginName, method: 'updateSettings', input: { mod: definition.name, source, project: modState.root, values } });
|
|
650
656
|
},
|
|
651
657
|
});
|
|
652
|
-
const client = createSessionsClient({
|
|
658
|
+
const client = createSessionsClient({
|
|
659
|
+
name: definition.name,
|
|
660
|
+
claude: checked,
|
|
661
|
+
report: (id, message, error) => report({ id, message, error }),
|
|
662
|
+
isGranted: (permission) => flag(permission),
|
|
663
|
+
hookOn: (hook, listener) => hookOn(hook, listener),
|
|
664
|
+
routeOn: (event, hook) => router.add(event, hook),
|
|
665
|
+
});
|
|
653
666
|
let followed;
|
|
667
|
+
let isSetUp = false;
|
|
668
|
+
const watches = new Set();
|
|
654
669
|
const followFolders = (project, folder) => {
|
|
655
670
|
if (followed === undefined)
|
|
656
671
|
return;
|
|
@@ -659,7 +674,16 @@ async function createMod(definition, runtime) {
|
|
|
659
674
|
};
|
|
660
675
|
const imports = {
|
|
661
676
|
sessions: client.sessions,
|
|
662
|
-
|
|
677
|
+
commands: {
|
|
678
|
+
list: async () => (await checked.command.list()).map(({ name, description, source, plugin }) => ({ name, description, source, ...(plugin === undefined ? {} : { plugin }) })),
|
|
679
|
+
run: async (command, args) => {
|
|
680
|
+
const { text, context } = await checked.command.run({ command, ...(args === undefined ? {} : { args }) });
|
|
681
|
+
return { ...(text === undefined ? {} : { text }), ...(context === undefined || context.length === 0 ? {} : { note: context.join('\n') }) };
|
|
682
|
+
},
|
|
683
|
+
beforeRun: (...args) => client.listen('beforeRun', args),
|
|
684
|
+
onRun: (...args) => client.listen('onRun', args),
|
|
685
|
+
},
|
|
686
|
+
config: {
|
|
663
687
|
list: async () => (await checked.config.list()).map(({ key, label, description, kind, value, options, provider, isLocked }) => ({
|
|
664
688
|
key,
|
|
665
689
|
label,
|
|
@@ -670,9 +694,22 @@ async function createMod(definition, runtime) {
|
|
|
670
694
|
...(options === undefined ? {} : { options }),
|
|
671
695
|
...(provider.plugin === 'engine' ? {} : { plugin: provider.plugin }),
|
|
672
696
|
})),
|
|
697
|
+
},
|
|
698
|
+
settings: {
|
|
673
699
|
user: settingsFile('user'),
|
|
674
700
|
project: settingsFile('project'),
|
|
675
701
|
local: settingsFile('local'),
|
|
702
|
+
beforeFileChange: (matcherOrListener, maybeListener) => {
|
|
703
|
+
const listener = (maybeListener ?? matcherOrListener);
|
|
704
|
+
const matcher = maybeListener === undefined ? undefined : matcherOrListener;
|
|
705
|
+
return hookOn('ConfigChange', async ({ source, file_path }) => {
|
|
706
|
+
const change = { source: source === 'skills' ? 'skills' : source.replace('_settings', ''), ...(file_path === undefined ? {} : { path: file_path }) };
|
|
707
|
+
if (matcher !== undefined && !matches(change, matcher))
|
|
708
|
+
return undefined;
|
|
709
|
+
const answer = await listener(change);
|
|
710
|
+
return answer === undefined ? undefined : { decision: 'block', reason: answer.refuse };
|
|
711
|
+
});
|
|
712
|
+
},
|
|
676
713
|
},
|
|
677
714
|
model: { complete: (completion, options) => checked.model.complete(completion, options) },
|
|
678
715
|
fs: {
|
|
@@ -685,6 +722,22 @@ async function createMod(definition, runtime) {
|
|
|
685
722
|
const { kind, size, mtimeMs, isLink, realPath } = await checked.fs.stat(expand(path), options);
|
|
686
723
|
return { isFile: kind === 'file', isFolder: kind === 'dir', isLink, size, modifiedAt: mtimeMs, ...(realPath === undefined ? {} : { realPath }) };
|
|
687
724
|
},
|
|
725
|
+
onChange({ path }, listener) {
|
|
726
|
+
if (isSetUp)
|
|
727
|
+
throw new Error(`${definition.name}: fs.onChange works only in setup, because Claude Code reads the paths it watches when a session starts.`);
|
|
728
|
+
const paths = [path].flat().map((each) => resolvedPath(each, places));
|
|
729
|
+
const watch = { paths, listener };
|
|
730
|
+
watches.add(watch);
|
|
731
|
+
if (watches.size === 1) {
|
|
732
|
+
hookOn('SessionStart', () => ({ hookSpecificOutput: { watchPaths: [...new Set([...watches].flatMap((each) => each.paths))] } }));
|
|
733
|
+
hookOn('FileChanged', ({ file_path, event }) => {
|
|
734
|
+
for (const each of watches)
|
|
735
|
+
if (each.paths.some((watched) => relativePath(watched, file_path) !== undefined))
|
|
736
|
+
void Promise.resolve(each.listener({ path: file_path, type: event })).catch((error) => report({ id: 'fs:onChange', message: 'The fs.onChange listener failed', error }));
|
|
737
|
+
});
|
|
738
|
+
}
|
|
739
|
+
return { dispose: () => void watches.delete(watch) };
|
|
740
|
+
},
|
|
688
741
|
},
|
|
689
742
|
metadata: {
|
|
690
743
|
read: (path) => files.read(path),
|
|
@@ -748,26 +801,10 @@ async function createMod(definition, runtime) {
|
|
|
748
801
|
files: allowed('files'),
|
|
749
802
|
config: allowed('config'),
|
|
750
803
|
},
|
|
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
804
|
every: (ms, listener) => timer(ms, 'every', claude.clock.every, listener),
|
|
768
805
|
after: (ms, listener) => timer(ms, 'after', claude.clock.after, listener),
|
|
769
806
|
use: (job) => job({ mod, announce, reserveName }),
|
|
770
|
-
log: (text) => claude.ui.log(`${definition.name}: ${text}`, { to: 'debug' }),
|
|
807
|
+
log: (text, options) => claude.ui.log(`${definition.name}: ${text}`, { to: options?.to ?? 'debug' }),
|
|
771
808
|
ui: { ...area.ui, notice: (callId, text) => claude.ui.notice(callId, text) },
|
|
772
809
|
settings: settingsPages,
|
|
773
810
|
dependencies: dependencyCalls(claude, (call, task) => beforeDeadline(claude, { ms: dependencyCallMs }, call, task)),
|
|
@@ -858,9 +895,9 @@ async function createMod(definition, runtime) {
|
|
|
858
895
|
return next(e);
|
|
859
896
|
const call = e;
|
|
860
897
|
if (e.method === conversationMethods.changed && call.caller === cmodPluginName) {
|
|
861
|
-
if (!flag('conversation'))
|
|
862
|
-
return { value: null };
|
|
863
898
|
const { session: changedSession, change, isInProject } = e.input;
|
|
899
|
+
if (changedSession !== client.sessions.current.id && !flag('conversation'))
|
|
900
|
+
return { value: null };
|
|
864
901
|
client.changed(changedSession, change, isInProject !== false);
|
|
865
902
|
return { value: null };
|
|
866
903
|
}
|
|
@@ -894,16 +931,13 @@ async function createMod(definition, runtime) {
|
|
|
894
931
|
throw new MissingOptions(missing, missing.map((key) => definition.options?.[key]?.title ?? key));
|
|
895
932
|
await failsAs('the details of its session did not load', () => followCurrent(undefined));
|
|
896
933
|
await failsAs('its setup function threw', () => definition.setup(mod));
|
|
934
|
+
isSetUp = true;
|
|
897
935
|
const permissionHooks = permissionEvents.filter((event) => router.has(event));
|
|
898
936
|
if (permissionHooks.length > 0 && !runtime.checksPermissions()) {
|
|
899
937
|
throw new Error(`it decides permissions on ${listed(permissionHooks)}, so hooks/register.ts must call registerPermissionCheck(addHook) after registerMod`);
|
|
900
938
|
}
|
|
901
|
-
if (held !== undefined && hooksAdded.includes(PreToolUse))
|
|
902
|
-
router.add('tool.check', heldDecisionHook(held));
|
|
903
939
|
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 };
|
|
940
|
+
return { mod, added, imports, raise: client.raise };
|
|
907
941
|
}
|
|
908
942
|
function checkingKeys(claude, toast, keys) {
|
|
909
943
|
const bound = Object.entries(keys);
|
|
@@ -925,23 +959,6 @@ function checkedMs(name, subject, ms) {
|
|
|
925
959
|
throw new Error(`${name}: ${subject} is ${ms}. Give a whole number of milliseconds above 0 and at most ${longestTimerMs}.`);
|
|
926
960
|
return ms;
|
|
927
961
|
}
|
|
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
962
|
async function failsAs(subject, step) {
|
|
946
963
|
try {
|
|
947
964
|
await step();
|
package/runtime/sessions.d.ts
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
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 { processEvents, type BeforeEvent, 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 HookAnswer } from './hooks.js';
|
|
7
10
|
export type Subscription = {
|
|
8
11
|
readonly event: SessionEvent;
|
|
9
12
|
readonly matcher: SentMatcher | undefined;
|
|
@@ -25,7 +28,7 @@ export declare const conversationMethods: {
|
|
|
25
28
|
readonly changed: 'cmod:changed';
|
|
26
29
|
readonly before: 'cmod:before';
|
|
27
30
|
};
|
|
28
|
-
export type Act = 'send' | 'startTask' | 'callTool' | 'usage' | '
|
|
31
|
+
export type Act = 'send' | 'startTask' | 'callTool' | 'usage' | 'surfaces' | 'abort';
|
|
29
32
|
export declare const beforePermission: (event: BeforeEvent) => string;
|
|
30
33
|
export declare const isAct: (action: string) => action is Act;
|
|
31
34
|
export declare function toolPermission(tool: string): string | undefined;
|
|
@@ -36,11 +39,14 @@ export type Scope = {
|
|
|
36
39
|
readonly turn?: string;
|
|
37
40
|
readonly task?: string;
|
|
38
41
|
readonly isGoal?: true;
|
|
42
|
+
readonly isAgent?: true;
|
|
39
43
|
};
|
|
40
44
|
export type SessionsClient = {
|
|
41
45
|
readonly sessions: Sessions;
|
|
42
46
|
changed(session: string, change: SessionChange, isInProject: boolean): void;
|
|
43
47
|
answerBefore(session: string, event: BeforeEvent, change: SessionChange, isInProject: boolean): Promise<unknown>;
|
|
48
|
+
raise(method: string, payload: Readonly<Record<string, unknown>>): Promise<unknown>;
|
|
49
|
+
listen(method: string, args: readonly unknown[]): Disposable;
|
|
44
50
|
/** An empty title keeps the title the session already has. */
|
|
45
51
|
follow(current: Omit<SessionData, 'isRunning' | 'lastAt'>): void;
|
|
46
52
|
};
|
|
@@ -48,11 +54,31 @@ export declare function usageOf(claude: Pick<Claude, 'session'>): Promise<Usage>
|
|
|
48
54
|
export declare function payloadOf(change: SessionChange): unknown;
|
|
49
55
|
export declare function isHeardBy(event: SessionEvent, matcher: unknown, change: SessionChange): boolean;
|
|
50
56
|
export declare function isInScope(scope: Scope, session: string, change: SessionChange, isUnder: (task: string, ancestor: string) => boolean): boolean;
|
|
51
|
-
export declare function createSessionsClient({ name, claude, report, isGranted }: {
|
|
57
|
+
export declare function createSessionsClient({ name, claude, report, isGranted, hookOn, routeOn }: {
|
|
52
58
|
readonly name: string;
|
|
53
59
|
readonly claude: Claude;
|
|
54
60
|
readonly report: (id: string, message: string, error: unknown) => void;
|
|
55
61
|
readonly isGranted: (permission: string) => boolean;
|
|
62
|
+
readonly hookOn?: (hook: string, listener: (input: unknown) => unknown) => Disposable;
|
|
63
|
+
readonly routeOn?: RouteOn;
|
|
56
64
|
}): SessionsClient;
|
|
57
|
-
|
|
65
|
+
type HookFields = Readonly<Record<string, unknown>>;
|
|
66
|
+
type ToolCheck = {
|
|
67
|
+
readonly tool: string;
|
|
68
|
+
readonly input: unknown;
|
|
69
|
+
readonly tool_use_id?: string;
|
|
70
|
+
readonly agentId?: string;
|
|
71
|
+
readonly ceiling?: 'allow' | 'ask' | 'deny';
|
|
72
|
+
};
|
|
73
|
+
export type RouteOn = {
|
|
74
|
+
(event: 'tool.check', hook: (e: ToolCheck, next: (e: ToolCheck) => Promise<unknown>) => Promise<unknown>): Disposable;
|
|
75
|
+
(event: 'command.run', hook: (e: Args<'command.run'>, next: (e: Args<'command.run'>) => Promise<CommandRunResult>) => Promise<CommandRunResult>): Disposable;
|
|
76
|
+
};
|
|
77
|
+
export declare function spawnTask(claude: Pick<Claude, 'agent'>, task: TaskStartArgs): Promise<TaskData>;
|
|
78
|
+
export declare function processPayloadOf(named: Exclude<keyof typeof processEvents, 'PermissionCheck'>, input: unknown): HookFields;
|
|
79
|
+
export declare function hookAnswerOf(named: string, hook: string, answer: HookFields): HookAnswer | undefined;
|
|
80
|
+
export declare function resultOf(where: Pick<Place, 'session' | 'context' | 'turn'>, call: ToolCallArgs & {
|
|
81
|
+
readonly cwd: string;
|
|
82
|
+
}, answered: Awaited<ReturnType<Claude['tool']['call']>>): ToolResult;
|
|
83
|
+
export {};
|
|
58
84
|
//# 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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"}
|