@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/src/jobs/status-line.ts
CHANGED
|
@@ -2,21 +2,18 @@ import type { Job, Mod } from '../mod.js'
|
|
|
2
2
|
import type { Deadline } from '../runtime/deadline.js'
|
|
3
3
|
import { engineOf } from '../runtime/engine.js'
|
|
4
4
|
import { usageOf } from '../runtime/sessions.js'
|
|
5
|
+
import { onStateChange } from '../runtime/state.js'
|
|
5
6
|
import type { Usage } from '../session/session.js'
|
|
6
7
|
import { messageOf } from '../utils/text.js'
|
|
7
8
|
import { withinDeadline } from './context.js'
|
|
8
9
|
|
|
9
10
|
const deadline: Deadline = { ms: 2000, job: 'statusLine' }
|
|
10
11
|
|
|
11
|
-
export function statusLine<State extends object = Record<never, never>>(options: {
|
|
12
|
-
|
|
13
|
-
readonly interval?: number
|
|
14
|
-
}): Job<void, State> {
|
|
15
|
-
const { text, interval = 10000 } = options
|
|
12
|
+
export function statusLine<State extends object = Record<never, never>>(options: { readonly text: (usage: Usage, mod: Mod<State>) => string | undefined | Promise<string | undefined> }): Job<void, State> {
|
|
13
|
+
const { text } = options
|
|
16
14
|
return (job) => {
|
|
17
15
|
const { mod, announce, reserveName } = job
|
|
18
16
|
const { claude, router } = engineOf(mod)
|
|
19
|
-
if (!(interval >= 1)) throw new Error(`${mod.name}: the status line interval is ${interval}. Give it in milliseconds, 1 or more.`)
|
|
20
17
|
reserveName('statusLine', '', `${mod.name}: a status line is already added. A mod has one status line: join the texts in one statusLine.`)
|
|
21
18
|
|
|
22
19
|
let shown: string | undefined
|
|
@@ -41,7 +38,7 @@ export function statusLine<State extends object = Record<never, never>>(options:
|
|
|
41
38
|
refresh()
|
|
42
39
|
return next(e)
|
|
43
40
|
})
|
|
44
|
-
|
|
41
|
+
onStateChange(mod.state, refresh)
|
|
45
42
|
refresh()
|
|
46
43
|
}
|
|
47
44
|
}
|
package/src/mod.ts
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import type {
|
|
2
2
|
AskOptions,
|
|
3
|
-
ClassicHookInputs,
|
|
4
3
|
CmodDependencies,
|
|
5
|
-
ConfigRow,
|
|
4
|
+
ConfigRow as ClaudeConfigRow,
|
|
6
5
|
FsStatOptions,
|
|
7
6
|
HookStream,
|
|
8
7
|
HttpInit,
|
|
@@ -11,7 +10,6 @@ import type {
|
|
|
11
10
|
ModelCompleteRequest,
|
|
12
11
|
ModelCompleteResult,
|
|
13
12
|
PaneOpenArgs,
|
|
14
|
-
PermissionRequestDecision,
|
|
15
13
|
ProcessRunInit,
|
|
16
14
|
ProcessRunResult,
|
|
17
15
|
ProcessSpawnChunk,
|
|
@@ -26,7 +24,7 @@ import type { Step } from './installer.js'
|
|
|
26
24
|
import { checkOptions, type Option, type Options, type OptionValues } from './options.js'
|
|
27
25
|
import { bound } from './runtime/bound.js'
|
|
28
26
|
import { Mod as modSender } from './session/context/turn/message/sender.js'
|
|
29
|
-
import type
|
|
27
|
+
import { sessionEvents, type BeforeMethod, type CommandRun, type CommandRunAnswer, type OnMethod, type OnMethods, type Sessions } from './session/session.js'
|
|
30
28
|
import type { Pane } from './ui/define-pane.js'
|
|
31
29
|
import type { Place, PlaceProps } from './ui/places.js'
|
|
32
30
|
import type { Disposable } from './utils/disposable.js'
|
|
@@ -67,132 +65,6 @@ export type PaneHandle = {
|
|
|
67
65
|
readonly isFocused: boolean
|
|
68
66
|
}
|
|
69
67
|
|
|
70
|
-
export const SessionStart = 'SessionStart'
|
|
71
|
-
export const SessionEnd = 'SessionEnd'
|
|
72
|
-
export const UserPromptSubmit = 'UserPromptSubmit'
|
|
73
|
-
export const InstructionsLoaded = 'InstructionsLoaded'
|
|
74
|
-
export const PreToolUse = 'PreToolUse'
|
|
75
|
-
export const PermissionRequest = 'PermissionRequest'
|
|
76
|
-
export const PermissionDenied = 'PermissionDenied'
|
|
77
|
-
export const PostToolUse = 'PostToolUse'
|
|
78
|
-
export const PostToolUseFailure = 'PostToolUseFailure'
|
|
79
|
-
export const PostToolBatch = 'PostToolBatch'
|
|
80
|
-
export const SubagentStart = 'SubagentStart'
|
|
81
|
-
export const SubagentStop = 'SubagentStop'
|
|
82
|
-
export const Notification = 'Notification'
|
|
83
|
-
export const PreCompact = 'PreCompact'
|
|
84
|
-
export const Stop = 'Stop'
|
|
85
|
-
export const StopFailure = 'StopFailure'
|
|
86
|
-
export const CwdChanged = 'CwdChanged'
|
|
87
|
-
export const FileChanged = 'FileChanged'
|
|
88
|
-
export const UserPromptExpansion = 'UserPromptExpansion'
|
|
89
|
-
export const Setup = 'Setup'
|
|
90
|
-
export const ConfigChange = 'ConfigChange'
|
|
91
|
-
export const DirectoryAdded = 'DirectoryAdded'
|
|
92
|
-
export const PreModelSwitch = 'PreModelSwitch'
|
|
93
|
-
export const PostModelSwitch = 'PostModelSwitch'
|
|
94
|
-
export const PostCompact = 'PostCompact'
|
|
95
|
-
export const MessageDisplay = 'MessageDisplay'
|
|
96
|
-
export const TodoCreated = 'TaskCreated'
|
|
97
|
-
export const TodoCompleted = 'TaskCompleted'
|
|
98
|
-
export const TeammateIdle = 'TeammateIdle'
|
|
99
|
-
export const Elicitation = 'Elicitation'
|
|
100
|
-
export const ElicitationResult = 'ElicitationResult'
|
|
101
|
-
export const WorktreeCreate = 'WorktreeCreate'
|
|
102
|
-
export const WorktreeRemove = 'WorktreeRemove'
|
|
103
|
-
|
|
104
|
-
export const hooks = [
|
|
105
|
-
Setup,
|
|
106
|
-
SessionStart,
|
|
107
|
-
SessionEnd,
|
|
108
|
-
UserPromptSubmit,
|
|
109
|
-
UserPromptExpansion,
|
|
110
|
-
InstructionsLoaded,
|
|
111
|
-
ConfigChange,
|
|
112
|
-
DirectoryAdded,
|
|
113
|
-
PreModelSwitch,
|
|
114
|
-
PostModelSwitch,
|
|
115
|
-
PreToolUse,
|
|
116
|
-
PermissionRequest,
|
|
117
|
-
PermissionDenied,
|
|
118
|
-
PostToolUse,
|
|
119
|
-
PostToolUseFailure,
|
|
120
|
-
PostToolBatch,
|
|
121
|
-
MessageDisplay,
|
|
122
|
-
SubagentStart,
|
|
123
|
-
SubagentStop,
|
|
124
|
-
TodoCreated,
|
|
125
|
-
TodoCompleted,
|
|
126
|
-
TeammateIdle,
|
|
127
|
-
Elicitation,
|
|
128
|
-
ElicitationResult,
|
|
129
|
-
Notification,
|
|
130
|
-
PreCompact,
|
|
131
|
-
PostCompact,
|
|
132
|
-
Stop,
|
|
133
|
-
StopFailure,
|
|
134
|
-
CwdChanged,
|
|
135
|
-
FileChanged,
|
|
136
|
-
WorktreeCreate,
|
|
137
|
-
WorktreeRemove,
|
|
138
|
-
] as const
|
|
139
|
-
|
|
140
|
-
export type Hook = (typeof hooks)[number]
|
|
141
|
-
|
|
142
|
-
export type HookAnswer = {
|
|
143
|
-
continue?: boolean
|
|
144
|
-
stopReason?: string
|
|
145
|
-
suppressOutput?: boolean
|
|
146
|
-
systemMessage?: string
|
|
147
|
-
decision?: 'block'
|
|
148
|
-
reason?: string
|
|
149
|
-
hookSpecificOutput?: {
|
|
150
|
-
hookEventName?: Hook
|
|
151
|
-
additionalContext?: string
|
|
152
|
-
permissionDecision?: 'allow' | 'deny' | 'ask'
|
|
153
|
-
permissionDecisionReason?: string
|
|
154
|
-
updatedInput?: Record<string, unknown>
|
|
155
|
-
sessionTitle?: string
|
|
156
|
-
suppressOriginalPrompt?: boolean
|
|
157
|
-
initialUserMessage?: string
|
|
158
|
-
watchPaths?: string[]
|
|
159
|
-
reloadSkills?: boolean
|
|
160
|
-
decision?: PermissionRequestDecision
|
|
161
|
-
updatedToolOutput?: unknown
|
|
162
|
-
updatedMCPToolOutput?: unknown
|
|
163
|
-
retry?: boolean
|
|
164
|
-
displayContent?: string
|
|
165
|
-
worktreePath?: string
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
type CallFiles = { read: string[]; changed: string[] }
|
|
170
|
-
|
|
171
|
-
type HookInputs = Omit<ClassicHookInputs, 'PreToolUse' | 'PostToolUse' | 'PostToolUseFailure' | 'CwdChanged'> & {
|
|
172
|
-
PreToolUse: {
|
|
173
|
-
session_id: string
|
|
174
|
-
cwd: string
|
|
175
|
-
hook_event_name: 'PreToolUse'
|
|
176
|
-
tool_name: string
|
|
177
|
-
tool_input: Record<string, unknown>
|
|
178
|
-
tool_use_id: string
|
|
179
|
-
agent_id?: string
|
|
180
|
-
agent_type?: string
|
|
181
|
-
files: CallFiles
|
|
182
|
-
}
|
|
183
|
-
PostToolUse: ClassicHookInputs['PostToolUse'] & { files: CallFiles }
|
|
184
|
-
PostToolUseFailure: ClassicHookInputs['PostToolUseFailure'] & { files: CallFiles }
|
|
185
|
-
CwdChanged: Omit<ClassicHookInputs['CwdChanged'], 'transcript_path'>
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
export type HookInput<H extends Hook> = HookInputs[H]
|
|
189
|
-
|
|
190
|
-
export type HookListener<H extends Hook> = (input: HookInput<H>) => HookAnswer | void | Promise<HookAnswer | void>
|
|
191
|
-
|
|
192
|
-
export type HookOptions = {
|
|
193
|
-
readonly timeoutMs?: number
|
|
194
|
-
}
|
|
195
|
-
|
|
196
68
|
export type Metadata = Readonly<Record<string, string>>
|
|
197
69
|
|
|
198
70
|
export type FileMatch = {
|
|
@@ -215,6 +87,8 @@ export type Stat = Omit<Entry, 'name'> & {
|
|
|
215
87
|
readonly realPath?: string
|
|
216
88
|
}
|
|
217
89
|
|
|
90
|
+
export type FileChange = { readonly path: string; readonly type: 'change' | 'add' | 'unlink' }
|
|
91
|
+
|
|
218
92
|
export type Allowed = {
|
|
219
93
|
allows(value: string): boolean
|
|
220
94
|
}
|
|
@@ -237,15 +111,36 @@ export type SettingsFile = {
|
|
|
237
111
|
update(values: Readonly<Record<string, unknown>>): Promise<void>
|
|
238
112
|
}
|
|
239
113
|
|
|
240
|
-
export type
|
|
114
|
+
export type SettingsFileChange = { readonly source: 'user' | 'project' | 'local' | 'policy' | 'skills'; readonly path?: string }
|
|
115
|
+
|
|
116
|
+
export type SettingsFileChangeListener = (change: SettingsFileChange) => { readonly refuse: string } | void | Promise<{ readonly refuse: string } | void>
|
|
117
|
+
|
|
118
|
+
export type ConfigRow = Pick<ClaudeConfigRow, 'key' | 'label' | 'kind' | 'value' | 'isLocked'> & {
|
|
241
119
|
readonly description?: string
|
|
242
120
|
readonly options?: readonly string[]
|
|
243
121
|
readonly plugin?: string
|
|
244
122
|
}
|
|
245
123
|
|
|
124
|
+
export type CommandInfo = { readonly name: string; readonly description: string; readonly source: 'builtin' | 'plugin' | 'user' | 'mcp'; readonly plugin?: string }
|
|
125
|
+
|
|
126
|
+
export type CommandRunResult = { readonly text?: string; readonly note?: string }
|
|
127
|
+
|
|
246
128
|
export type Imports = {
|
|
247
129
|
readonly sessions: Sessions
|
|
248
|
-
readonly
|
|
130
|
+
readonly commands: {
|
|
131
|
+
list(): Promise<CommandInfo[]>
|
|
132
|
+
run(name: string, args?: string): Promise<CommandRunResult>
|
|
133
|
+
readonly beforeRun: BeforeMethod<CommandRun, CommandRunAnswer>
|
|
134
|
+
readonly onRun: OnMethod<CommandRun>
|
|
135
|
+
}
|
|
136
|
+
readonly config: { list(): Promise<ConfigRow[]> }
|
|
137
|
+
readonly settings: {
|
|
138
|
+
readonly user: SettingsFile
|
|
139
|
+
readonly project: SettingsFile
|
|
140
|
+
readonly local: SettingsFile
|
|
141
|
+
beforeFileChange(listener: SettingsFileChangeListener): Disposable
|
|
142
|
+
beforeFileChange(matcher: Matcher<SettingsFileChange>, listener: SettingsFileChangeListener): Disposable
|
|
143
|
+
}
|
|
249
144
|
readonly model: { complete(request: ModelCompleteRequest, options?: ModelCompleteOptions): Promise<ModelCompleteResult> }
|
|
250
145
|
readonly fs: {
|
|
251
146
|
read(path: string): Promise<string>
|
|
@@ -254,6 +149,7 @@ export type Imports = {
|
|
|
254
149
|
find(glob: string | readonly string[]): Promise<readonly FileMatch[]>
|
|
255
150
|
list(path?: string): Promise<Entry[]>
|
|
256
151
|
stat(path: string, options?: FsStatOptions): Promise<Stat>
|
|
152
|
+
onChange(matcher: { readonly path: string | readonly string[] }, listener: (change: FileChange) => unknown): Disposable
|
|
257
153
|
}
|
|
258
154
|
readonly metadata: {
|
|
259
155
|
read(path: string): Promise<Metadata>
|
|
@@ -271,16 +167,26 @@ export const sessions: Imports['sessions'] = {
|
|
|
271
167
|
return bound().sessions.current
|
|
272
168
|
},
|
|
273
169
|
list: (options) => bound().sessions.list(options),
|
|
274
|
-
search: (text, options) => bound().sessions.search(text, options),
|
|
275
|
-
on
|
|
276
|
-
before: ((...args: Parameters<Imports['sessions']['before']>) => bound().sessions.before(...args)) as Imports['sessions']['before'],
|
|
170
|
+
search: (text, matcher, options) => bound().sessions.search(text, matcher, options),
|
|
171
|
+
...(Object.fromEntries(Object.keys(sessionEvents).map((named) => [`on${named}`, (...args: unknown[]) => (bound().sessions[`on${named}` as keyof OnMethods] as (...each: unknown[]) => Disposable)(...args)])) as OnMethods),
|
|
277
172
|
}
|
|
278
173
|
|
|
279
174
|
export const settings: Imports['settings'] = {
|
|
280
|
-
list: () => bound().settings.list(),
|
|
281
175
|
user: { read: () => bound().settings.user.read(), update: (values) => bound().settings.user.update(values) },
|
|
282
176
|
project: { read: () => bound().settings.project.read(), update: (values) => bound().settings.project.update(values) },
|
|
283
177
|
local: { read: () => bound().settings.local.read(), update: (values) => bound().settings.local.update(values) },
|
|
178
|
+
beforeFileChange: ((matcherOrListener: never, listener?: never) => bound().settings.beforeFileChange(matcherOrListener, listener as never)) as Imports['settings']['beforeFileChange'],
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
export const commands: Imports['commands'] = {
|
|
182
|
+
list: () => bound().commands.list(),
|
|
183
|
+
run: (name, args) => bound().commands.run(name, args),
|
|
184
|
+
beforeRun: ((...args: never[]) => (bound().commands.beforeRun as (...each: never[]) => Disposable)(...args)) as Imports['commands']['beforeRun'],
|
|
185
|
+
onRun: ((...args: never[]) => (bound().commands.onRun as (...each: never[]) => Disposable)(...args)) as Imports['commands']['onRun'],
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
export const config: Imports['config'] = {
|
|
189
|
+
list: () => bound().config.list(),
|
|
284
190
|
}
|
|
285
191
|
|
|
286
192
|
export const model: Imports['model'] = {
|
|
@@ -294,6 +200,7 @@ export const fs: Imports['fs'] = {
|
|
|
294
200
|
find: (glob) => bound().fs.find(glob),
|
|
295
201
|
list: (path) => bound().fs.list(path),
|
|
296
202
|
stat: (path, options) => bound().fs.stat(path, options),
|
|
203
|
+
onChange: (matcher, listener) => bound().fs.onChange(matcher, listener),
|
|
297
204
|
}
|
|
298
205
|
|
|
299
206
|
export const metadata: Imports['metadata'] = {
|
|
@@ -317,12 +224,10 @@ export type Mod<State extends object = Record<never, never>, Declared extends Op
|
|
|
317
224
|
readonly state: Readonly<State>
|
|
318
225
|
readonly options: OptionValues<Declared>
|
|
319
226
|
readonly permissions: Permissions
|
|
320
|
-
on<H extends Hook>(hook: H, listener: HookListener<H>, options?: HookOptions): Disposable
|
|
321
|
-
on<H extends Hook>(hook: H, matcher: Matcher<HookInput<H>>, listener: HookListener<H>, options?: HookOptions): Disposable
|
|
322
227
|
every(ms: number, listener: () => unknown): Disposable
|
|
323
228
|
after(ms: number, listener: () => unknown): Disposable
|
|
324
229
|
use<Handle>(job: Job<Handle, State>): Handle
|
|
325
|
-
log(text: string): void
|
|
230
|
+
log(text: string, options?: { readonly to?: 'debug' | 'transcript' }): void
|
|
326
231
|
readonly ui: {
|
|
327
232
|
pane(pane: Pane<State>): PaneHandle
|
|
328
233
|
render<P extends Place>(place: P, Component: (props: PlaceProps<P>) => RenderElement): Disposable
|
|
@@ -378,21 +283,21 @@ export function defineMod<State extends StateGroups = Record<never, never>, Name
|
|
|
378
283
|
export type { Matcher } from './utils/matcher.js'
|
|
379
284
|
export type { Place, PlaceProps } from './ui/places.js'
|
|
380
285
|
export { AbovePrompt, AskUserQuestion, CommandOutput, PromptHint, SessionMode, Spinner, ToolGroup, ToolProgress, TurnDuration } from './ui/places.js'
|
|
381
|
-
export { Session,
|
|
382
|
-
export {
|
|
383
|
-
export {
|
|
384
|
-
export { Message,
|
|
286
|
+
export { Session, type AnswerOptions, type BeforeMethods, type Surface, type Listener, type ListenerTime, type OnMethods, type ReadOptions, type RecordPart, type Refusal, type SearchOptions, type SessionScope, type SessionSend, type Sessions, type CurrentSession, type Placement, type ProjectSetup, type SessionStartAsked, type SessionStarted, type SessionEnded, type ModelChange, type CwdChange, type DirectoryAdd, type Worktree, type Notification, type Elicitation, type ElicitationResponse, type McpServer, type PermissionUpdate, type PermissionCheck, type PermissionRequest, type PermissionDenial, type BatchedCall, type ToolBatch, type Todo, type ReplyDisplay, type TaskOwner, type TaskStartArgs, type ToolCallArgs, type Usage, type CommandRun, type CommandRunAnswer } from './session/session.js'
|
|
287
|
+
export type { Context } from './session/context/context.js'
|
|
288
|
+
export type { Turn } from './session/context/turn/turn.js'
|
|
289
|
+
export type { Message, MessageType } from './session/context/turn/message/message.js'
|
|
385
290
|
export { Prompt } from './session/context/turn/message/prompt.js'
|
|
386
291
|
export { Note } from './session/context/turn/message/note.js'
|
|
387
292
|
export { Reply } from './session/context/turn/message/reply.js'
|
|
388
293
|
export { ToolCall } from './session/context/turn/message/tool-call.js'
|
|
389
294
|
export { ToolResult } from './session/context/turn/message/tool-result.js'
|
|
390
295
|
export { Skill } from './session/context/turn/message/skill.js'
|
|
391
|
-
export {
|
|
296
|
+
export { InstructionFile } from './session/context/turn/message/instruction-file.js'
|
|
392
297
|
export { Notice } from './session/context/turn/message/notice.js'
|
|
393
|
-
export { Channel, Person, Schedule, Sdk, type Sender, type SenderType } from './session/context/turn/message/sender.js'
|
|
394
|
-
export {
|
|
395
|
-
export { Task
|
|
298
|
+
export { Channel, Engine, Person, Schedule, Sdk, type Sender, type SenderType } from './session/context/turn/message/sender.js'
|
|
299
|
+
export type { Goal } from './session/goal/goal.js'
|
|
300
|
+
export { Task } from './session/goal/task.js'
|
|
396
301
|
export { longestMs } from './runtime/deadline.js'
|
|
397
302
|
export { notInstalled } from './runtime/dependencies.js'
|
|
398
303
|
export { messageOf } from './utils/text.js'
|
package/src/records.ts
CHANGED
|
@@ -259,7 +259,7 @@ function readKeys(keys: unknown): Steps {
|
|
|
259
259
|
return { keys: commands }
|
|
260
260
|
}
|
|
261
261
|
|
|
262
|
-
export const coreVersion = '
|
|
262
|
+
export const coreVersion = '2.0.0'
|
|
263
263
|
|
|
264
264
|
export function oldestCmodFor(steps: Steps): string {
|
|
265
265
|
if (steps.permissions?.some((item) => item.startsWith('config:')) === true) return '1.6.0'
|
package/src/register.ts
CHANGED
|
@@ -70,8 +70,9 @@ async function startMod($: EngineInterface, eventInput: Frozen<Args<'session.sta
|
|
|
70
70
|
send: (args) => $.session.send(args),
|
|
71
71
|
},
|
|
72
72
|
prompt: { submit: (args) => $.prompt.submit(args) },
|
|
73
|
+
turn: { abort: (args) => $.turn.abort(args) },
|
|
73
74
|
model: { complete: (request, options) => $.model.complete(request, options) },
|
|
74
|
-
command: { register: (command) => $.command.register(command) },
|
|
75
|
+
command: { register: (command) => $.command.register(command), list: () => $.command.list(), run: (args) => $.command.run(args) },
|
|
75
76
|
tool: { register: (tool) => $.tool.register(tool), call: (input: ToolCallArgs) => $.tool.call(input) },
|
|
76
77
|
agent: {
|
|
77
78
|
list: () => $.agent.list(),
|
|
@@ -149,6 +150,7 @@ export function registerMod<State extends object, Declared extends Options>(addH
|
|
|
149
150
|
addHook('session.receive', routeToMod)
|
|
150
151
|
addHook('agent.spawn', routeToMod)
|
|
151
152
|
addHook('prompt.attachment', routeToMod)
|
|
153
|
+
addHook('turn.start', routeToMod)
|
|
152
154
|
}
|
|
153
155
|
|
|
154
156
|
export function registerPermissionCheck(addHook: On): void {
|
package/src/runtime/before.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import type { Args, Frozen } from 'claude-code'
|
|
2
2
|
import type { Disposable } from '../mod.js'
|
|
3
3
|
import { ContextEnd } from '../session/context/context.js'
|
|
4
|
-
import {
|
|
5
|
-
import { Message, messagesOf, rowOfAppend, withTexts, type Unplaced } from '../session/context/turn/message/message.js'
|
|
4
|
+
import { InstructionFile, isRulePath } from '../session/context/turn/message/instruction-file.js'
|
|
5
|
+
import { Message, messagesOf, noFiles, rowOfAppend, withTexts, type Unplaced } from '../session/context/turn/message/message.js'
|
|
6
6
|
import { Note } from '../session/context/turn/message/note.js'
|
|
7
7
|
import { Notice } from '../session/context/turn/message/notice.js'
|
|
8
8
|
import { Prompt } from '../session/context/turn/message/prompt.js'
|
|
@@ -13,22 +13,39 @@ import { ToolCall } from '../session/context/turn/message/tool-call.js'
|
|
|
13
13
|
import { ToolResult } from '../session/context/turn/message/tool-result.js'
|
|
14
14
|
import { TurnEnd } from '../session/context/turn/turn.js'
|
|
15
15
|
import type { ConversationState, SessionChange, TaskData } from '../session/conversation.js'
|
|
16
|
-
import {
|
|
16
|
+
import { Task, TaskStart } from '../session/goal/task.js'
|
|
17
17
|
import type { BeforeEvent } from '../session/session.js'
|
|
18
18
|
import type { Claude } from './claude.js'
|
|
19
19
|
import { cmodPluginName } from './dependencies.js'
|
|
20
20
|
import type { Router } from './router.js'
|
|
21
21
|
import { toolInputOf } from './tool-calls.js'
|
|
22
22
|
|
|
23
|
-
export type Place = Pick<ConversationState, 'session' | 'context' | 'contextOpenedAt' | 'turn' | 'tasks' | 'taskTurns'>
|
|
23
|
+
export type Place = Pick<ConversationState, 'session' | 'context' | 'contextOpenedAt' | 'turn' | 'tasks' | 'taskTurns' | 'openCalls'>
|
|
24
24
|
|
|
25
25
|
export type Decide = (event: BeforeEvent, change: SessionChange) => Promise<Partial<Answers> | undefined>
|
|
26
26
|
|
|
27
|
-
export type Answers = {
|
|
27
|
+
export type Answers = {
|
|
28
|
+
readonly text: string
|
|
29
|
+
readonly output: unknown
|
|
30
|
+
readonly isError: true
|
|
31
|
+
readonly note: string
|
|
32
|
+
readonly title: string
|
|
33
|
+
readonly refuse: string
|
|
34
|
+
readonly input: Readonly<Record<string, unknown>>
|
|
35
|
+
readonly result: string
|
|
36
|
+
readonly owner: { readonly model: string }
|
|
37
|
+
}
|
|
28
38
|
|
|
29
39
|
export type BeforeHooks = { readonly place: () => Promise<Place>; readonly wants: (event: BeforeEvent) => Promise<boolean>; readonly decide: Decide }
|
|
30
40
|
|
|
31
|
-
const appended: readonly BeforeEvent[] = [Note, Reply,
|
|
41
|
+
const appended: readonly BeforeEvent[] = [Note, Reply, Notice]
|
|
42
|
+
|
|
43
|
+
type ClassicFields = Readonly<Record<string, unknown>>
|
|
44
|
+
|
|
45
|
+
const withNote = (below: unknown, note: string | undefined): ClassicFields => {
|
|
46
|
+
const fields = (below ?? {}) as ClassicFields
|
|
47
|
+
return note === undefined ? fields : { ...fields, additionalContext: [...((fields['additionalContext'] as readonly string[] | undefined) ?? []), note] }
|
|
48
|
+
}
|
|
32
49
|
|
|
33
50
|
const relayTag = /^(?:<cross-session-message[^>]*>\s*)?<cmod-relay>([\s\S]*)<\/cmod-relay>(?:\s*<\/cross-session-message>[\s\S]*)?$/
|
|
34
51
|
|
|
@@ -44,28 +61,50 @@ export function installBefore({ claude, router, hooks }: { readonly claude: Clau
|
|
|
44
61
|
const message = (where: Place, unplaced: Unplaced, task: string | undefined, isInTurn = true): SessionChange => {
|
|
45
62
|
const turn = isInTurn ? (task === undefined ? where.turn : where.taskTurns[task]) : undefined
|
|
46
63
|
const context = task === undefined ? where.context : (where.tasks[task]?.context ?? where.context)
|
|
47
|
-
|
|
64
|
+
const call = unplaced.type === ToolResult ? where.openCalls?.[unplaced.callId] : undefined
|
|
65
|
+
return { type: Message, message: { ...unplaced, ...call, session: where.session, context, ...(turn === undefined ? {} : { turn: turn.prompt.id }), ...(task === undefined ? {} : { task }) } as Message }
|
|
66
|
+
}
|
|
67
|
+
const titles = new Map<string, string>()
|
|
68
|
+
const spawned = new Map<string, string>()
|
|
69
|
+
const toolResultHook = (isFailure: boolean) => async (e: Readonly<Record<string, unknown>>, next: (e: never) => Promise<unknown>) => {
|
|
70
|
+
if (!(await wants(ToolResult))) return next(e as never)
|
|
71
|
+
const where = await place()
|
|
72
|
+
const name = String(e['tool_name'])
|
|
73
|
+
const callId = String(e['tool_use_id'])
|
|
74
|
+
const input = (typeof e['tool_input'] === 'object' && e['tool_input'] !== null ? e['tool_input'] : {}) as Readonly<Record<string, unknown>>
|
|
75
|
+
const task = typeof e['agent_id'] === 'string' ? e['agent_id'] : undefined
|
|
76
|
+
const result = { id: callId, at: now(), type: ToolResult, callId, name, input, cwd: String(e['cwd']), files: noFiles, output: isFailure ? e['error'] : e['tool_response'], isError: isFailure } as const
|
|
77
|
+
const answer = await asked(ToolResult, message(where, result, task))
|
|
78
|
+
const below = await next(e as never)
|
|
79
|
+
const rewritten = answer.output === undefined || isFailure ? {} : name.startsWith('mcp__') ? { updatedMCPToolOutput: answer.output } : { updatedToolOutput: answer.output }
|
|
80
|
+
const failed = answer.isError === true && !isFailure ? { block: answer.note ?? `${name} failed.` } : {}
|
|
81
|
+
return { ...withNote(below, answer.isError === true ? undefined : answer.note), ...rewritten, ...failed }
|
|
48
82
|
}
|
|
49
83
|
const added: Disposable[] = [
|
|
50
84
|
router.add('prompt.submit', async (e, next) => {
|
|
51
|
-
if (!(await wants(Prompt))
|
|
85
|
+
if (!(await wants(Prompt))) return next(e)
|
|
52
86
|
const where = await place()
|
|
53
87
|
const answer = await asked(Prompt, message(where, { id: '', at: now(), type: Prompt, text: e.text, from: senderOf(e.origin, false) }, undefined, false))
|
|
54
88
|
if (answer.refuse !== undefined) return { drop: answer.refuse }
|
|
55
|
-
|
|
89
|
+
const text = answer.text ?? e.text
|
|
90
|
+
if (answer.title !== undefined) titles.set(text, answer.title)
|
|
91
|
+
const context = answer.note === undefined ? e.context : [...(e.context ?? []), answer.note]
|
|
92
|
+
const ran = await next({ ...e, text, ...(context === undefined ? {} : { context }) })
|
|
93
|
+
const passed = titles.get(text)
|
|
94
|
+
if (passed !== undefined && ('drop' in ran || ran.text !== text)) {
|
|
95
|
+
titles.delete(text)
|
|
96
|
+
if (!('drop' in ran) && typeof ran.text === 'string') titles.set(ran.text, passed)
|
|
97
|
+
}
|
|
98
|
+
return ran
|
|
56
99
|
}),
|
|
57
|
-
router.add('
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
const
|
|
61
|
-
|
|
62
|
-
if (answer.refuse !== undefined) return { text: answer.refuse }
|
|
63
|
-
if (answer.text === undefined || answer.text === typed) return next(e)
|
|
64
|
-
const same = new RegExp(`^/${e.command}(?:\\s+|$)`).exec(answer.text)
|
|
65
|
-
if (same !== null) return next({ ...e, args: answer.text.slice(same[0].length) })
|
|
66
|
-
void claude.prompt.submit({ text: answer.text })
|
|
67
|
-
return {}
|
|
100
|
+
router.add('classic.UserPromptSubmit', async (e, next) => {
|
|
101
|
+
const named = titles.get(e.prompt)
|
|
102
|
+
titles.delete(e.prompt)
|
|
103
|
+
const below = await next(e)
|
|
104
|
+
return named === undefined ? below : { ...below, sessionTitle: named }
|
|
68
105
|
}),
|
|
106
|
+
router.add('classic.PostToolUse', toolResultHook(false) as never),
|
|
107
|
+
router.add('classic.PostToolUseFailure', toolResultHook(true) as never),
|
|
69
108
|
router.add('session.receive', async (e, next) => {
|
|
70
109
|
if (!(await wants(Prompt)) || relayOf(e) !== undefined) return next(e)
|
|
71
110
|
const where = await place()
|
|
@@ -80,10 +119,10 @@ export function installBefore({ claude, router, hooks }: { readonly claude: Clau
|
|
|
80
119
|
return answer.text === undefined ? next(e) : { text: answer.text }
|
|
81
120
|
}),
|
|
82
121
|
router.add('prompt.attachment', async (e, next) => {
|
|
83
|
-
if (!(await wants(
|
|
122
|
+
if (!(await wants(InstructionFile)) || e.type !== 'nested_memory') return next(e)
|
|
84
123
|
const where = await place()
|
|
85
124
|
const path = /^Contents of (.+?) \(/.exec(e.text)?.[1] ?? ''
|
|
86
|
-
const answer = await asked(
|
|
125
|
+
const answer = await asked(InstructionFile, message(where, { id: '', at: now(), type: InstructionFile, path, text: e.text, isRule: isRulePath(path) }, e.agentId))
|
|
87
126
|
if (answer.refuse !== undefined) return { text: null }
|
|
88
127
|
return answer.text === undefined ? next(e) : { text: answer.text }
|
|
89
128
|
}),
|
|
@@ -106,7 +145,7 @@ export function installBefore({ claude, router, hooks }: { readonly claude: Clau
|
|
|
106
145
|
if (!(await wants(ToolCall))) return next(e)
|
|
107
146
|
const where = await place()
|
|
108
147
|
const input = toolInputOf(e)
|
|
109
|
-
const answer = await asked(ToolCall, message(where, { id: e.tool_use_id, at: now(), type: ToolCall, callId: e.tool_use_id, name: e.tool, input, cwd: await claude.session.cwd() }, e.agentId))
|
|
148
|
+
const answer = await asked(ToolCall, message(where, { id: e.tool_use_id, at: now(), type: ToolCall, callId: e.tool_use_id, name: e.tool, input, cwd: await claude.session.cwd(), files: noFiles }, e.agentId))
|
|
110
149
|
if (answer.refuse !== undefined) return { deny: answer.refuse }
|
|
111
150
|
if (answer.result !== undefined) return { result: answer.result }
|
|
112
151
|
if (answer.input === undefined) return next(e)
|
|
@@ -114,23 +153,36 @@ export function installBefore({ claude, router, hooks }: { readonly claude: Clau
|
|
|
114
153
|
return next({ ...reserved, ...answer.input } as Frozen<Args<'tool.call'>>)
|
|
115
154
|
}),
|
|
116
155
|
router.add('agent.spawn', async (e, next) => {
|
|
156
|
+
spawned.set(e.tool_use_id, e.prompt)
|
|
117
157
|
if (!(await wants(TaskStart))) return next(e)
|
|
118
158
|
const where = await place()
|
|
119
159
|
const task: TaskData = { id: '', title: e.description, agentType: e.subagentType, model: e.model ?? e.parentModel, openedBy: e.tool_use_id, openedAt: now(), ...(e.parentAgentId === undefined ? {} : { parent: e.parentAgentId }) }
|
|
120
160
|
const answer = await asked(TaskStart, { type: TaskStart, task: { ...task, context: where.context } })
|
|
121
|
-
if (answer.refuse !== undefined)
|
|
161
|
+
if (answer.refuse !== undefined) {
|
|
162
|
+
spawned.delete(e.tool_use_id)
|
|
163
|
+
return { deny: answer.refuse }
|
|
164
|
+
}
|
|
122
165
|
return next(answer.owner === undefined ? e : { ...e, model: answer.owner.model })
|
|
123
166
|
}),
|
|
124
|
-
router.add('classic.
|
|
125
|
-
if (!(await wants(TaskEnd)) && !(await wants(TurnEnd))) return next(e)
|
|
167
|
+
router.add('classic.SubagentStart', async (e, next) => {
|
|
126
168
|
const where = await place()
|
|
127
|
-
const
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
const
|
|
169
|
+
const openedBy = where.tasks[e.agent_id]?.openedBy
|
|
170
|
+
const prompt = openedBy === undefined ? undefined : spawned.get(openedBy)
|
|
171
|
+
if (openedBy !== undefined) spawned.delete(openedBy)
|
|
172
|
+
if (!(await wants(Prompt))) return next(e)
|
|
173
|
+
const open = where.taskTurns[e.agent_id]?.prompt
|
|
174
|
+
const text = open?.text ?? prompt
|
|
175
|
+
if (text === undefined) return next(e)
|
|
176
|
+
const answer = await asked(Prompt, message(where, { id: open?.id ?? '', at: now(), type: Prompt, text, from: open?.from ?? { type: Task } }, e.agent_id, false))
|
|
177
|
+
return withNote(await next(e), answer.note)
|
|
178
|
+
}),
|
|
179
|
+
router.add('classic.SubagentStop', async (e, next) => {
|
|
180
|
+
if (!(await wants(TurnEnd))) return next(e)
|
|
181
|
+
const turn = (await place()).taskTurns[e.agent_id]
|
|
182
|
+
if (turn === undefined) return next(e)
|
|
183
|
+
const answer = await asked(TurnEnd, { type: TurnEnd, turn: { ...turn, closedAt: now() } })
|
|
132
184
|
const below = await next(e)
|
|
133
|
-
return refuse === undefined ? below : { ...below, block: refuse }
|
|
185
|
+
return answer.refuse === undefined ? below : { ...below, block: answer.refuse }
|
|
134
186
|
}),
|
|
135
187
|
router.add('classic.Stop', async (e, next) => {
|
|
136
188
|
if (!(await wants(TurnEnd))) return next(e)
|
|
@@ -154,6 +206,7 @@ export function installBefore({ claude, router, hooks }: { readonly claude: Clau
|
|
|
154
206
|
}
|
|
155
207
|
|
|
156
208
|
export function withAnswer(change: SessionChange, answer: Partial<Answers>): SessionChange {
|
|
209
|
+
if (change.type === Message && answer.output !== undefined && change.message.type === ToolResult) return { ...change, message: { ...change.message, output: answer.output } }
|
|
157
210
|
if (change.type === Message && answer.text !== undefined) return { ...change, message: { ...change.message, text: answer.text } as Message }
|
|
158
211
|
if (change.type === Message && answer.input !== undefined && change.message.type === ToolCall) return { ...change, message: { ...change.message, input: answer.input } }
|
|
159
212
|
if (change.type === TaskStart && answer.owner !== undefined) return { ...change, task: { ...change.task, model: answer.owner.model } }
|
|
@@ -167,7 +220,8 @@ export async function foldAnswers(event: BeforeEvent, change: SessionChange, ask
|
|
|
167
220
|
try {
|
|
168
221
|
const answer = answerOf(event, await ask(latest))
|
|
169
222
|
if (answer === undefined) continue
|
|
170
|
-
|
|
223
|
+
const notes = [result?.note, answer.note].filter((note) => note !== undefined)
|
|
224
|
+
result = { ...result, ...answer, ...(notes.length === 0 ? {} : { note: notes.join('\n\n') }) }
|
|
171
225
|
if (answer.refuse !== undefined || answer.result !== undefined) return result
|
|
172
226
|
latest = withAnswer(latest, answer)
|
|
173
227
|
} catch (error) {
|
|
@@ -181,16 +235,15 @@ export function answerOf(event: BeforeEvent, answer: unknown): Partial<Answers>
|
|
|
181
235
|
if (typeof answer !== 'object' || answer === null) return undefined
|
|
182
236
|
const given = answer as Partial<Answers>
|
|
183
237
|
const allowed: Readonly<Record<BeforeEvent, readonly (keyof Answers)[]>> = {
|
|
184
|
-
prompt: ['text', 'refuse'],
|
|
238
|
+
prompt: ['text', 'note', 'title', 'refuse'],
|
|
185
239
|
note: ['text'],
|
|
186
240
|
skill: ['text'],
|
|
187
|
-
|
|
241
|
+
instructionFile: ['text', 'refuse'],
|
|
188
242
|
reply: ['text'],
|
|
189
243
|
toolCall: ['input', 'result', 'refuse'],
|
|
190
|
-
toolResult: ['
|
|
244
|
+
toolResult: ['output', 'isError', 'note'],
|
|
191
245
|
notice: ['text'],
|
|
192
246
|
taskStart: ['owner', 'refuse'],
|
|
193
|
-
taskEnd: ['refuse'],
|
|
194
247
|
turnEnd: ['refuse'],
|
|
195
248
|
contextEnd: ['refuse'],
|
|
196
249
|
}
|
package/src/runtime/claude.ts
CHANGED
|
@@ -5,6 +5,9 @@ import type {
|
|
|
5
5
|
Args,
|
|
6
6
|
EngineInterface,
|
|
7
7
|
AskOptions,
|
|
8
|
+
CommandInfo,
|
|
9
|
+
CommandRunArgs,
|
|
10
|
+
CommandRunResult,
|
|
8
11
|
CommandSpec,
|
|
9
12
|
ConfigRow,
|
|
10
13
|
ElementTable,
|
|
@@ -93,8 +96,9 @@ export type Claude = {
|
|
|
93
96
|
send: EngineInterface['session']['send']
|
|
94
97
|
}
|
|
95
98
|
readonly prompt: { submit: EngineInterface['prompt']['submit'] }
|
|
99
|
+
readonly turn: { abort: EngineInterface['turn']['abort'] }
|
|
96
100
|
readonly model: { complete: EngineInterface['model']['complete'] }
|
|
97
|
-
readonly command: { register(command: CommandSpec): Promise<{ command: string }> }
|
|
101
|
+
readonly command: { register(command: CommandSpec): Promise<{ command: string }>; list(): Promise<readonly CommandInfo[]>; run(args: CommandRunArgs): Promise<CommandRunResult> }
|
|
98
102
|
readonly tool: { register(tool: ToolSpec): Promise<{ tool: string }>; call: EngineInterface['tool']['call'] }
|
|
99
103
|
readonly agent: {
|
|
100
104
|
list(): Promise<AgentInfo[]>
|