@cmodjs/core 1.6.0 → 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/docs/commands.md +7 -7
- package/docs/dependencies.md +1 -1
- package/docs/index.md +12 -11
- package/docs/jobs.md +12 -16
- package/docs/mod.md +111 -36
- package/docs/permissions.md +14 -18
- package/docs/session.md +171 -82
- package/docs/state.md +5 -5
- package/docs/testing.md +57 -41
- package/docs/ui.md +9 -9
- package/jobs/permissions/decide-permission.d.ts.map +1 -1
- package/jobs/permissions/decide-permission.js +1 -1
- package/jobs/prompt.d.ts +1 -3
- package/jobs/prompt.d.ts.map +1 -1
- package/jobs/prompt.js +2 -21
- package/jobs/slash-command.d.ts +1 -1
- package/jobs/slash-command.d.ts.map +1 -1
- package/jobs/slash-command.js +1 -1
- package/jobs/status-line.d.ts +0 -1
- package/jobs/status-line.d.ts.map +1 -1
- package/jobs/status-line.js +3 -4
- package/mod.d.ts +53 -105
- package/mod.d.ts.map +1 -1
- package/mod.js +18 -80
- package/package.json +1 -1
- package/records.d.ts +1 -1
- package/records.js +1 -1
- package/register.d.ts.map +1 -1
- package/register.js +8 -5
- package/runtime/before.d.ts +11 -4
- package/runtime/before.d.ts.map +1 -1
- package/runtime/before.js +131 -67
- package/runtime/claude.d.ts +6 -1
- package/runtime/claude.d.ts.map +1 -1
- package/runtime/conversations.d.ts.map +1 -1
- package/runtime/conversations.js +82 -38
- package/runtime/deadline.d.ts +17 -0
- package/runtime/deadline.d.ts.map +1 -1
- package/runtime/deadline.js +34 -0
- package/runtime/dependencies.d.ts +2 -0
- package/runtime/dependencies.d.ts.map +1 -1
- package/runtime/dependencies.js +12 -0
- package/runtime/grants.d.ts.map +1 -1
- package/runtime/grants.js +13 -8
- package/runtime/hooks.d.ts +15 -11
- package/runtime/hooks.d.ts.map +1 -1
- package/runtime/hooks.js +8 -165
- package/runtime/lifecycle.d.ts +4 -2
- package/runtime/lifecycle.d.ts.map +1 -1
- package/runtime/lifecycle.js +96 -62
- package/runtime/permission-check.d.ts +15 -0
- package/runtime/permission-check.d.ts.map +1 -0
- package/runtime/permission-check.js +37 -0
- package/runtime/sessions.d.ts +35 -5
- package/runtime/sessions.d.ts.map +1 -1
- package/runtime/sessions.js +304 -82
- package/runtime/state.d.ts.map +1 -1
- package/runtime/state.js +14 -11
- package/runtime/tool-calls.d.ts +0 -1
- package/runtime/tool-calls.d.ts.map +1 -1
- package/runtime/tool-calls.js +0 -19
- package/session/context/turn/message/instruction-file.d.ts +9 -0
- package/session/context/turn/message/instruction-file.d.ts.map +1 -0
- package/session/context/turn/message/instruction-file.js +4 -0
- package/session/context/turn/message/message.d.ts +6 -4
- package/session/context/turn/message/message.d.ts.map +1 -1
- package/session/context/turn/message/message.js +12 -8
- package/session/context/turn/message/sender.d.ts +2 -1
- package/session/context/turn/message/sender.d.ts.map +1 -1
- package/session/context/turn/message/sender.js +4 -1
- package/session/context/turn/message/tool-call.d.ts +5 -0
- package/session/context/turn/message/tool-call.d.ts.map +1 -1
- package/session/context/turn/message/tool-result.d.ts +6 -1
- package/session/context/turn/message/tool-result.d.ts.map +1 -1
- package/session/context/turn/turn.d.ts +7 -0
- package/session/context/turn/turn.d.ts.map +1 -1
- package/session/conversation.d.ts +10 -3
- package/session/conversation.d.ts.map +1 -1
- package/session/conversation.js +14 -5
- package/session/goal/goal.d.ts +1 -1
- package/session/goal/goal.d.ts.map +1 -1
- package/session/goal/task.d.ts +2 -2
- package/session/goal/task.d.ts.map +1 -1
- package/session/session.d.ts +361 -26
- package/session/session.d.ts.map +1 -1
- package/session/session.js +40 -0
- package/src/jobs/permissions/decide-permission.ts +2 -1
- package/src/jobs/prompt.ts +3 -20
- package/src/jobs/slash-command.ts +2 -2
- package/src/jobs/status-line.ts +4 -7
- package/src/mod.ts +52 -147
- package/src/records.ts +1 -1
- package/src/register.ts +8 -5
- package/src/runtime/before.ts +140 -66
- package/src/runtime/claude.ts +5 -1
- package/src/runtime/conversations.ts +75 -37
- package/src/runtime/deadline.ts +38 -0
- package/src/runtime/dependencies.ts +12 -0
- package/src/runtime/grants.ts +11 -7
- package/src/runtime/hooks.ts +24 -163
- package/src/runtime/lifecycle.ts +110 -77
- package/src/runtime/permission-check.ts +47 -0
- package/src/runtime/sessions.ts +334 -91
- package/src/runtime/state.ts +15 -8
- package/src/runtime/tool-calls.ts +0 -19
- package/src/session/context/turn/message/instruction-file.ts +13 -0
- package/src/session/context/turn/message/message.ts +17 -11
- package/src/session/context/turn/message/sender.ts +5 -2
- package/src/session/context/turn/message/tool-call.ts +3 -0
- package/src/session/context/turn/message/tool-result.ts +6 -1
- package/src/session/context/turn/turn.ts +4 -0
- package/src/session/conversation.ts +21 -8
- package/src/session/goal/goal.ts +1 -1
- package/src/session/goal/task.ts +2 -2
- package/src/session/session.ts +227 -24
- package/src/testing/claude-code.ts +23 -0
- package/src/testing/fake-claude.ts +5 -1
- package/src/testing/fake-sessions.ts +38 -24
- package/src/testing.ts +88 -19
- package/testing/claude-code.d.ts +12 -0
- package/testing/claude-code.d.ts.map +1 -0
- package/testing/claude-code.js +10 -0
- package/testing/fake-claude.d.ts +4 -0
- package/testing/fake-claude.d.ts.map +1 -1
- package/testing/fake-claude.js +4 -1
- package/testing/fake-sessions.d.ts +8 -6
- package/testing/fake-sessions.d.ts.map +1 -1
- package/testing/fake-sessions.js +35 -20
- package/testing.d.ts +38 -9
- package/testing.d.ts.map +1 -1
- package/testing.js +58 -8
- package/docs/hooks.md +0 -259
- package/session/context/turn/message/document.d.ts +0 -7
- package/session/context/turn/message/document.d.ts.map +0 -1
- package/session/context/turn/message/document.js +0 -1
- package/src/session/context/turn/message/document.ts +0 -8
package/runtime/sessions.js
CHANGED
|
@@ -1,15 +1,20 @@
|
|
|
1
1
|
import { ContextEnd, ContextStart } from '../session/context/context.js';
|
|
2
|
-
import { Message } from '../session/context/turn/message/message.js';
|
|
2
|
+
import { Message, noFiles } from '../session/context/turn/message/message.js';
|
|
3
3
|
import { Note } from '../session/context/turn/message/note.js';
|
|
4
|
-
import { Person } from '../session/context/turn/message/sender.js';
|
|
4
|
+
import { Person, senderOf } from '../session/context/turn/message/sender.js';
|
|
5
|
+
import { ToolCall } from '../session/context/turn/message/tool-call.js';
|
|
5
6
|
import { ToolResult } from '../session/context/turn/message/tool-result.js';
|
|
6
7
|
import { TurnEnd, TurnStart } from '../session/context/turn/turn.js';
|
|
8
|
+
import { searchLimit } from '../session/conversation.js';
|
|
7
9
|
import { GoalChange } from '../session/goal/goal.js';
|
|
8
10
|
import { TaskEnd, TaskStart } from '../session/goal/task.js';
|
|
9
|
-
import { Session } from '../session/session.js';
|
|
11
|
+
import { beforeEvents, processEvents, Session, sessionEvents } from '../session/session.js';
|
|
10
12
|
import { matches, sentMatcher } from '../utils/matcher.js';
|
|
11
|
-
import { foldAnswers } from './before.js';
|
|
13
|
+
import { beforeAnswerKeys, foldAnswers, foldBefore, methodOf, takenAnswer } from './before.js';
|
|
12
14
|
import { cmodPluginName } from './dependencies.js';
|
|
15
|
+
import { messageOf } from '../utils/text.js';
|
|
16
|
+
import { listenerOf, PassedTimeout, withinTime } from './deadline.js';
|
|
17
|
+
import { callFiles } from './hooks.js';
|
|
13
18
|
export const everySession = '*';
|
|
14
19
|
export const conversationMethods = {
|
|
15
20
|
sessions: 'cmod:sessions',
|
|
@@ -27,10 +32,10 @@ export const conversationMethods = {
|
|
|
27
32
|
changed: 'cmod:changed',
|
|
28
33
|
before: 'cmod:before',
|
|
29
34
|
};
|
|
30
|
-
const actPermissions = { send: 'prompt', startTask: 'tasks', usage: 'conversation',
|
|
35
|
+
const actPermissions = { send: 'prompt', startTask: 'tasks', usage: 'conversation', surfaces: 'conversation', abort: 'prompt' };
|
|
31
36
|
const startsTurns = ['CronCreate'];
|
|
32
37
|
const ownSchedule = ['CronDelete', 'CronList'];
|
|
33
|
-
export const beforePermission = (event) => (event === 'toolCall' ? 'tools' : event === TaskStart
|
|
38
|
+
export const beforePermission = (event) => (event === 'toolCall' ? 'tools' : event === TaskStart ? 'tasks' : 'prompt');
|
|
34
39
|
export const isAct = (action) => action === 'callTool' || action in actPermissions;
|
|
35
40
|
export function toolPermission(tool) {
|
|
36
41
|
return startsTurns.includes(tool) ? 'prompt' : ownSchedule.includes(tool) ? undefined : 'tools';
|
|
@@ -38,11 +43,10 @@ export function toolPermission(tool) {
|
|
|
38
43
|
export function actPermission(action, input) {
|
|
39
44
|
return action === 'callTool' ? (toolPermission(String(input?.name)) ?? 'tools') : actPermissions[action];
|
|
40
45
|
}
|
|
41
|
-
const asked = (action) => `cmod: ${action} runs only on a session's own main loop, and Claude Code gives a subagent's tasks no way to start a task or run a tool.`;
|
|
42
46
|
export async function usageOf(claude) {
|
|
43
47
|
const [model, usage] = await Promise.all([claude.session.model(), claude.session.usage()]);
|
|
44
48
|
const { tokens, window, percent } = usage.context;
|
|
45
|
-
return { model,
|
|
49
|
+
return { model, contextWindow: { ...(tokens === undefined ? {} : { tokenCount: tokens }), size: window, ...(percent === undefined ? {} : { percent }) }, ...(usage.cost === undefined ? {} : { cost: { usd: usage.cost.usd } }) };
|
|
46
50
|
}
|
|
47
51
|
export function payloadOf(change) {
|
|
48
52
|
if (change.type === Message)
|
|
@@ -96,10 +100,10 @@ export function isInScope(scope, session, change, isUnder) {
|
|
|
96
100
|
if (scope.session !== undefined && scope.session !== session)
|
|
97
101
|
return false;
|
|
98
102
|
const task = taskOfChange(change);
|
|
103
|
+
if (scope.isAgent === true)
|
|
104
|
+
return (change.type === TaskStart || change.type === TaskEnd ? change.task.parent : task) === scope.task;
|
|
99
105
|
if (scope.task !== undefined && (task === undefined || !isUnder(task, scope.task)))
|
|
100
106
|
return false;
|
|
101
|
-
if (scope.isGoal === true && change.type !== GoalChange && change.type !== TaskStart && (task === undefined || task === scope.task))
|
|
102
|
-
return false;
|
|
103
107
|
if (scope.context !== undefined && (contextOfChange(change) !== scope.context || ownerOfChange(change) !== scope.task))
|
|
104
108
|
return false;
|
|
105
109
|
if (scope.turn === undefined)
|
|
@@ -108,7 +112,7 @@ export function isInScope(scope, session, change, isUnder) {
|
|
|
108
112
|
return change.message.turn === scope.turn;
|
|
109
113
|
return (change.type === TurnStart || change.type === TurnEnd) && change.turn.prompt.id === scope.turn;
|
|
110
114
|
}
|
|
111
|
-
export function createSessionsClient({ name, claude, report, isGranted }) {
|
|
115
|
+
export function createSessionsClient({ name, claude, report, isGranted, hookOn, routeOn, permissionRoutes }) {
|
|
112
116
|
const call = (method, input) => claude.cmod.call({ to: cmodPluginName, method, input });
|
|
113
117
|
const listening = new Map();
|
|
114
118
|
const deciding = new Map();
|
|
@@ -149,11 +153,9 @@ export function createSessionsClient({ name, claude, report, isGranted }) {
|
|
|
149
153
|
if (change.type === TaskStart)
|
|
150
154
|
parents.get(session)?.set(change.task.id, change.task.parent);
|
|
151
155
|
};
|
|
152
|
-
const add = (into, scope, event,
|
|
153
|
-
const listener = (maybeListener ?? matcherOrListener);
|
|
154
|
-
const matcher = maybeListener === undefined ? undefined : matcherOrListener;
|
|
156
|
+
const add = (into, scope, event, method, args, onChange) => {
|
|
155
157
|
const key = keyOf(scope);
|
|
156
|
-
const entry = { scope, event,
|
|
158
|
+
const entry = { scope, event, method, ...listenerOf(name, method, args) };
|
|
157
159
|
const set = into.get(key) ?? new Set();
|
|
158
160
|
set.add(entry);
|
|
159
161
|
into.set(key, set);
|
|
@@ -173,6 +175,12 @@ export function createSessionsClient({ name, claude, report, isGranted }) {
|
|
|
173
175
|
},
|
|
174
176
|
};
|
|
175
177
|
};
|
|
178
|
+
const invoke = (method, options, run, takes = []) => withinTime(claude.clock, method, options, run).catch((error) => {
|
|
179
|
+
if (error instanceof PassedTimeout && !method.startsWith('before'))
|
|
180
|
+
return undefined;
|
|
181
|
+
report(`session:${method}`, `The ${method} listener failed`, error);
|
|
182
|
+
return options.onFailure === 'refuse' && takes.includes('refuse') ? { refuse: `${name}: ${messageOf(error)}` } : undefined;
|
|
183
|
+
});
|
|
176
184
|
const combined = async (session, event, change, isInProject) => {
|
|
177
185
|
const askers = [...(deciding.get(session) ?? []), ...(isInProject ? (deciding.get(everySession) ?? []) : [])]
|
|
178
186
|
.filter((entry) => entry.event === event)
|
|
@@ -181,31 +189,71 @@ export function createSessionsClient({ name, claude, report, isGranted }) {
|
|
|
181
189
|
return undefined;
|
|
182
190
|
if (entry.matcher !== undefined && !matches(payloadOf(latest), entry.matcher))
|
|
183
191
|
return undefined;
|
|
184
|
-
const
|
|
185
|
-
|
|
192
|
+
const payload = await payloadFor(session, latest);
|
|
193
|
+
const answer = await invoke(entry.method, entry.options, async (time) => takenAnswer(entry.method, beforeAnswerKeys[event], await entry.listener(payload, time)), beforeAnswerKeys[event]);
|
|
194
|
+
if (answer === undefined || answer === null || (typeof answer === 'object' && 'refuse' in answer) || isGranted(beforePermission(event)))
|
|
186
195
|
return answer;
|
|
187
|
-
throw new Error(`Its
|
|
196
|
+
throw new Error(`Its ${entry.method} answer changes nothing until ${beforePermission(event)} is turned on for ${name} in /mods.`);
|
|
188
197
|
});
|
|
189
|
-
return
|
|
198
|
+
return foldBefore(event, change, askers, (error) => report(`session:${methodOf('before', event)}`, `The ${methodOf('before', event)} listener failed`, error));
|
|
199
|
+
};
|
|
200
|
+
const eventMethods = (scopeOf, hasBefore, ended) => {
|
|
201
|
+
const method = (into, named, event) => (...args) => {
|
|
202
|
+
if (ended !== undefined)
|
|
203
|
+
throw new Error(`${name}: ${ended} has ended, so its ${named} never fires. Listen on its Session.`);
|
|
204
|
+
return add(into, scopeOf(), event, named, args, subscribe);
|
|
205
|
+
};
|
|
206
|
+
return Object.fromEntries([
|
|
207
|
+
...Object.entries(sessionEvents).map(([named, event]) => [`on${named}`, method(listening, `on${named}`, event)]),
|
|
208
|
+
...(hasBefore ? beforeEvents.map((named) => [`before${named}`, method(deciding, `before${named}`, sessionEvents[named])]) : []),
|
|
209
|
+
]);
|
|
210
|
+
};
|
|
211
|
+
let folders;
|
|
212
|
+
const withFiles = async (message) => {
|
|
213
|
+
if (message.type !== ToolCall && message.type !== ToolResult)
|
|
214
|
+
return message;
|
|
215
|
+
folders ??= Promise.all([claude.env.home(), claude.env.configHome()]).then(([home, configRoot]) => ({ home, configRoot }));
|
|
216
|
+
return { ...message, files: await callFiles(name, claude, folders, { tool: message.name, input: message.input }, message.cwd) };
|
|
190
217
|
};
|
|
191
|
-
const
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
218
|
+
const messagesFrom = async (method, input) => Promise.all((await call(method, input)).map(withFiles));
|
|
219
|
+
const searched = (request, matcher) => ({
|
|
220
|
+
async *[Symbol.asyncIterator]() {
|
|
221
|
+
let before = request['before'];
|
|
222
|
+
for (;;) {
|
|
223
|
+
const page = await messagesFrom(conversationMethods.search, { ...request, ...(before === undefined ? {} : { before }) });
|
|
224
|
+
for (const message of page)
|
|
225
|
+
if (matcher === undefined || matches(message, matcher))
|
|
226
|
+
yield message;
|
|
227
|
+
const last = page.at(-1);
|
|
228
|
+
if (last === undefined || page.length < searchLimit)
|
|
229
|
+
return;
|
|
230
|
+
before = { session: last.session, id: last.id };
|
|
231
|
+
}
|
|
232
|
+
},
|
|
233
|
+
});
|
|
234
|
+
const recordPart = (scope, ended) => ({
|
|
235
|
+
search: (text, matcher) => searched({ text, ...scope }, matcher),
|
|
236
|
+
...eventMethods(() => scope, true, ended),
|
|
195
237
|
});
|
|
196
238
|
const turnOf = (scope, data) => {
|
|
197
239
|
const task = data.prompt.task ?? scope.task;
|
|
198
240
|
return {
|
|
199
241
|
...data,
|
|
200
|
-
...recordPart({ ...scope, ...(task === undefined ? {} : { task }), turn: data.prompt.id }),
|
|
201
|
-
messages: () =>
|
|
242
|
+
...recordPart({ ...scope, ...(task === undefined ? {} : { task }), turn: data.prompt.id }, data.closedAt === undefined ? undefined : `turn ${data.prompt.id}`),
|
|
243
|
+
messages: () => messagesFrom(conversationMethods.messages, { session: scope.session, turn: data.prompt.id, ...(task === undefined ? {} : { task }) }),
|
|
244
|
+
abort: async () => {
|
|
245
|
+
if (data.closedAt !== undefined)
|
|
246
|
+
throw new Error(`${name}: turn ${data.prompt.id} has ended, so there is nothing to abort.`);
|
|
247
|
+
const session = scope.session ?? current.id;
|
|
248
|
+
await act(session, 'abort', { turn: data.prompt.id }, task === undefined ? undefined : { session, task });
|
|
249
|
+
},
|
|
202
250
|
};
|
|
203
251
|
};
|
|
204
252
|
const contextOf = (scope, data) => {
|
|
205
253
|
const owned = { ...(scope.session === undefined ? {} : { session: scope.session }), ...(data.task === undefined ? {} : { task: data.task }) };
|
|
206
254
|
return {
|
|
207
255
|
...data,
|
|
208
|
-
...recordPart({ ...owned, context: data.number }),
|
|
256
|
+
...recordPart({ ...owned, context: data.number }, data.closedAt === undefined ? undefined : `context ${data.number}`),
|
|
209
257
|
turns: async () => (await call(conversationMethods.turns, { ...owned, context: data.number })).map((turn) => turnOf(owned, turn)),
|
|
210
258
|
};
|
|
211
259
|
};
|
|
@@ -218,22 +266,27 @@ export function createSessionsClient({ name, claude, report, isGranted }) {
|
|
|
218
266
|
owner: ownerOf(session, data),
|
|
219
267
|
...recordPart({ session, task: data.id }),
|
|
220
268
|
tasks: async () => (await call(conversationMethods.tasks, { session, task: data.id })).map((task) => taskOf(session, task)),
|
|
221
|
-
messages: () =>
|
|
269
|
+
messages: () => messagesFrom(conversationMethods.messages, { session, task: data.id }),
|
|
222
270
|
});
|
|
223
|
-
const goalOf = (
|
|
224
|
-
...
|
|
225
|
-
...
|
|
226
|
-
|
|
271
|
+
const goalOf = (where) => ({
|
|
272
|
+
search: (text, matcher) => searched({ text, ...where() }, matcher),
|
|
273
|
+
...eventMethods(() => ({ ...where(), isGoal: true }), true),
|
|
274
|
+
text: async () => (await call(conversationMethods.goal, where())).text,
|
|
275
|
+
tasks: async () => {
|
|
276
|
+
const { session = current.id, task } = where();
|
|
277
|
+
return (await call(conversationMethods.tasks, { session, ...(task === undefined ? {} : { task }) })).map((each) => taskOf(session, each));
|
|
278
|
+
},
|
|
227
279
|
update: async ({ text }) => {
|
|
228
280
|
if (text.trim() === '')
|
|
229
281
|
throw new Error(`${name}: a goal needs text.`);
|
|
230
|
-
|
|
282
|
+
const { session = current.id, task } = where();
|
|
283
|
+
await call(conversationMethods.updateGoal, { session, ...(task === undefined ? {} : { task }), text });
|
|
231
284
|
},
|
|
232
285
|
});
|
|
233
|
-
const payloadFor = (session, change) => {
|
|
286
|
+
const payloadFor = async (session, change) => {
|
|
234
287
|
const scope = { session };
|
|
235
288
|
if (change.type === Message)
|
|
236
|
-
return change.message;
|
|
289
|
+
return withFiles(change.message);
|
|
237
290
|
if ('turn' in change)
|
|
238
291
|
return turnOf(scope, change.turn);
|
|
239
292
|
if ('context' in change)
|
|
@@ -242,7 +295,8 @@ export function createSessionsClient({ name, claude, report, isGranted }) {
|
|
|
242
295
|
return taskOf(session, change.task);
|
|
243
296
|
if ('usage' in change)
|
|
244
297
|
return change.usage;
|
|
245
|
-
|
|
298
|
+
const { task } = change.goal;
|
|
299
|
+
return goalOf(() => ({ session, ...(task === undefined ? {} : { task }) }));
|
|
246
300
|
};
|
|
247
301
|
const act = (session, action, input, owned) => call(conversationMethods.act, { session: owned?.session ?? session, action, input, ...(owned === undefined ? {} : { task: owned.task }) });
|
|
248
302
|
const sendHere = async ({ type, text, as }) => {
|
|
@@ -258,26 +312,15 @@ export function createSessionsClient({ name, claude, report, isGranted }) {
|
|
|
258
312
|
if ('drop' in submitted && typeof submitted.drop === 'string')
|
|
259
313
|
throw new Error(`${name}: Claude Code dropped the prompt: ${submitted.drop}`);
|
|
260
314
|
};
|
|
261
|
-
const startHere = async (task) => {
|
|
262
|
-
|
|
263
|
-
prompt: task.prompt,
|
|
264
|
-
...(task.name === undefined ? {} : { description: task.name, name: task.name }),
|
|
265
|
-
...(task.agentType === undefined ? {} : { subagentType: task.agentType }),
|
|
266
|
-
...(task.model === undefined ? {} : { model: task.model }),
|
|
267
|
-
...(task.cwd === undefined ? {} : { cwd: task.cwd }),
|
|
268
|
-
});
|
|
269
|
-
if (started.deny !== undefined)
|
|
270
|
-
throw new Error(`${name}: Claude Code refused the task: ${started.deny}`);
|
|
271
|
-
return taskOf(current.id, { id: started.agentId ?? '', title: task.name ?? '', agentType: task.agentType ?? 'general-purpose', model: started.model, openedBy: '', openedAt: new Date().toISOString() });
|
|
272
|
-
};
|
|
273
|
-
const clientsHere = async () => [...(await claude.session.surfaces())];
|
|
315
|
+
const startHere = async (task) => taskOf(current.id, await spawnTask(claude, task).catch((error) => Promise.reject(new Error(`${name}: ${messageOf(error)}`))));
|
|
316
|
+
const surfacesHere = async () => [...(await claude.session.surfaces())];
|
|
274
317
|
const sessionOf = (read, owned) => {
|
|
275
318
|
const id = () => read().id;
|
|
276
319
|
const isHere = () => owned === undefined && id() === current.id;
|
|
277
|
-
const scope = () => (owned === undefined ? { session: id() } : { session: owned.session, task: owned.task });
|
|
320
|
+
const scope = () => (owned === undefined ? { session: id(), isAgent: true } : { session: owned.session, task: owned.task, isAgent: true });
|
|
278
321
|
const where = () => (owned === undefined ? { session: id() } : { session: owned.session, task: owned.task });
|
|
279
|
-
const turnsOf = async (options = {}) => (await call(conversationMethods.turns, { ...where(), ...options })).map((turn) => turnOf(
|
|
280
|
-
const contextsOf = async () => (await call(conversationMethods.contexts, where())).map((context) => contextOf(
|
|
322
|
+
const turnsOf = async (options = {}) => (await call(conversationMethods.turns, { ...where(), ...options })).map((turn) => turnOf(where(), turn));
|
|
323
|
+
const contextsOf = async () => (await call(conversationMethods.contexts, where())).map((context) => contextOf(where(), context));
|
|
281
324
|
const session = {
|
|
282
325
|
get id() {
|
|
283
326
|
return read().id;
|
|
@@ -309,10 +352,9 @@ export function createSessionsClient({ name, claude, report, isGranted }) {
|
|
|
309
352
|
get forkedFrom() {
|
|
310
353
|
return read().forkedFrom;
|
|
311
354
|
},
|
|
312
|
-
search: (text,
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
goal: async () => goalOf(scope(), await call(conversationMethods.goal, where())),
|
|
355
|
+
search: (text, matcher) => recordPart(scope()).search(text, matcher),
|
|
356
|
+
...eventMethods(scope, true),
|
|
357
|
+
goal: goalOf(where),
|
|
316
358
|
context: async () => {
|
|
317
359
|
const all = await contextsOf();
|
|
318
360
|
const last = all.at(-1);
|
|
@@ -326,9 +368,9 @@ export function createSessionsClient({ name, claude, report, isGranted }) {
|
|
|
326
368
|
return last?.closedAt === undefined ? last : undefined;
|
|
327
369
|
},
|
|
328
370
|
turns: turnsOf,
|
|
329
|
-
messages: (options = {}) =>
|
|
371
|
+
messages: (options = {}) => messagesFrom(conversationMethods.messages, { ...where(), ...options }),
|
|
330
372
|
usage: async () => (isHere() ? usageOf(claude) : act(id(), 'usage', null, owned)),
|
|
331
|
-
|
|
373
|
+
surfaces: async () => (isHere() ? surfacesHere() : act(id(), 'surfaces', null, owned)),
|
|
332
374
|
async send(message) {
|
|
333
375
|
if (message.text.trim() === '')
|
|
334
376
|
throw new Error(`${name}: send needs text.`);
|
|
@@ -336,21 +378,18 @@ export function createSessionsClient({ name, claude, report, isGranted }) {
|
|
|
336
378
|
return sendHere(message);
|
|
337
379
|
await act(id(), 'send', message, owned);
|
|
338
380
|
},
|
|
381
|
+
};
|
|
382
|
+
if (owned !== undefined)
|
|
383
|
+
return session;
|
|
384
|
+
return Object.assign(session, {
|
|
339
385
|
async startTask(task) {
|
|
340
|
-
if (owned !== undefined)
|
|
341
|
-
throw new Error(asked('startTask'));
|
|
342
386
|
if (isHere())
|
|
343
387
|
return startHere(task);
|
|
344
388
|
const started = await act(id(), 'startTask', task);
|
|
345
389
|
return taskOf(id(), started);
|
|
346
390
|
},
|
|
347
|
-
async
|
|
348
|
-
|
|
349
|
-
throw new Error(asked('callTool'));
|
|
350
|
-
return act(id(), 'callTool', toolCall);
|
|
351
|
-
},
|
|
352
|
-
};
|
|
353
|
-
return session;
|
|
391
|
+
callTool: async (toolCall) => (await withFiles(await act(id(), 'callTool', toolCall))),
|
|
392
|
+
});
|
|
354
393
|
};
|
|
355
394
|
const ownerOf = (session, data) => {
|
|
356
395
|
const parent = session === current.id ? current : known.get(session);
|
|
@@ -366,9 +405,79 @@ export function createSessionsClient({ name, claude, report, isGranted }) {
|
|
|
366
405
|
lastAt: data.closedAt ?? data.openedAt,
|
|
367
406
|
}), { session, task: data.id });
|
|
368
407
|
};
|
|
369
|
-
const
|
|
370
|
-
const
|
|
371
|
-
|
|
408
|
+
const processListeners = new Map();
|
|
409
|
+
const raise = async (method, payload) => {
|
|
410
|
+
const heard = [...(processListeners.get(method) ?? [])].filter(({ matcher }) => payload['task'] === undefined && (matcher === undefined || matches(payload, matcher)));
|
|
411
|
+
if (!method.startsWith('before')) {
|
|
412
|
+
await Promise.all(heard.map(({ listener, options }) => invoke(method, options, (time) => listener(payload, time))));
|
|
413
|
+
return undefined;
|
|
414
|
+
}
|
|
415
|
+
const failing = method === 'beforePermissionRequest' ? { onFailure: 'refuse' } : {};
|
|
416
|
+
const takes = answerKeys[method.slice('before'.length)] ?? [];
|
|
417
|
+
const askers = heard.map(({ listener, options }) => () => invoke(method, { ...options, ...failing }, async (time) => takenAnswer(method, takes, await listener(payload, time)), takes));
|
|
418
|
+
return foldAnswers(method, takes, ['refuse'], undefined, askers, (error) => report(`session:${method}`, `The ${method} listener failed`, error));
|
|
419
|
+
};
|
|
420
|
+
const commandRun = async (e) => {
|
|
421
|
+
const source = (await claude.command.list()).find((command) => command.name === e.command)?.source;
|
|
422
|
+
return { session: current.id, cwd: current.cwd, name: e.command, args: e.args, ...defined({ source }), from: senderOf(e.origin, false) };
|
|
423
|
+
};
|
|
424
|
+
const commandListeners = { beforeRun: new Set(), onRun: new Set() };
|
|
425
|
+
for (const [method, held] of Object.entries(commandListeners))
|
|
426
|
+
processListeners.set(method, held);
|
|
427
|
+
routeOn?.('command.run', async (e, next) => {
|
|
428
|
+
if (commandListeners.beforeRun.size === 0 && commandListeners.onRun.size === 0)
|
|
429
|
+
return next(e);
|
|
430
|
+
const run = await commandRun(e);
|
|
431
|
+
const answer = commandListeners.beforeRun.size === 0 ? undefined : await raise('beforeRun', run);
|
|
432
|
+
if (typeof answer?.['refuse'] === 'string')
|
|
433
|
+
return { text: answer['refuse'] };
|
|
434
|
+
const changes = answer !== undefined && isGranted('prompt') ? answer : undefined;
|
|
435
|
+
if (answer !== undefined && changes === undefined)
|
|
436
|
+
report('session:beforeRun', 'The beforeRun listener failed', new Error(`${name}: its answer changes a command, which needs the prompt permission.`));
|
|
437
|
+
if (typeof changes?.['result'] === 'string')
|
|
438
|
+
return { text: changes['result'], ...(typeof changes['exitCode'] === 'number' ? { exitCode: changes['exitCode'] } : {}) };
|
|
439
|
+
const args = typeof changes?.['args'] === 'string' ? changes['args'] : e.args;
|
|
440
|
+
const ran = await next(args === e.args ? e : { ...e, args });
|
|
441
|
+
void raise('onRun', { ...run, args });
|
|
442
|
+
return typeof changes?.['note'] === 'string' ? { ...ran, context: [...(ran.context ?? []), changes['note']] } : ran;
|
|
443
|
+
});
|
|
444
|
+
const listenTo = (method, named) => {
|
|
445
|
+
if (hookOn === undefined || routeOn === undefined)
|
|
446
|
+
throw new Error(`${name}: ${method} works only in a mod's own session.`);
|
|
447
|
+
const isBefore = method.startsWith('before');
|
|
448
|
+
if (named === 'PermissionCheck' || named === 'PermissionRequest') {
|
|
449
|
+
const routes = permissionRoutes?.();
|
|
450
|
+
if (routes !== undefined)
|
|
451
|
+
return routes(method, { raise, hookOn, routeOn, current: () => current });
|
|
452
|
+
return named === 'PermissionCheck' ? routeOn('tool.check', (e, next) => next(e)) : hookOn('PermissionRequest', () => undefined);
|
|
453
|
+
}
|
|
454
|
+
const event = processEvents[named];
|
|
455
|
+
const hook = (isBefore ? event.before : undefined) ?? event.on;
|
|
456
|
+
return hookOn(hook, async (input) => {
|
|
457
|
+
const payload = processPayloadOf(named, input);
|
|
458
|
+
if (!isBefore) {
|
|
459
|
+
if (named !== 'SessionStart' || payload['source'] !== 'compact')
|
|
460
|
+
void raise(method, payload);
|
|
461
|
+
return undefined;
|
|
462
|
+
}
|
|
463
|
+
const answer = await raise(method, payload);
|
|
464
|
+
return answer === undefined ? undefined : hookAnswerOf(named, answer);
|
|
465
|
+
});
|
|
466
|
+
};
|
|
467
|
+
const listen = (method, args) => {
|
|
468
|
+
const entry = listenerOf(name, method, args);
|
|
469
|
+
const held = processListeners.get(method) ?? new Set();
|
|
470
|
+
if (!processListeners.has(method)) {
|
|
471
|
+
listenTo(method, method.replace(/^(on|before)/, ''));
|
|
472
|
+
processListeners.set(method, held);
|
|
473
|
+
}
|
|
474
|
+
held.add(entry);
|
|
475
|
+
return { dispose: () => void held.delete(entry) };
|
|
476
|
+
};
|
|
477
|
+
const processMethods = () => Object.fromEntries(Object.entries(processEvents).flatMap(([named, event]) => [`on${named}`, ...('before' in event ? [`before${named}`] : [])]).map((method) => [method, (...args) => listen(method, args)]));
|
|
478
|
+
const currentSession = Object.assign(sessionOf(() => current), processMethods());
|
|
479
|
+
const every = eventMethods(() => ({}), false);
|
|
480
|
+
const answerBefore = async (session, event, change, isInProject) => (session === current.id || isGranted('conversation') ? combined(session, event, change, isInProject) : undefined);
|
|
372
481
|
return {
|
|
373
482
|
sessions: {
|
|
374
483
|
get current() {
|
|
@@ -383,23 +492,26 @@ export function createSessionsClient({ name, claude, report, isGranted }) {
|
|
|
383
492
|
current = { ...current, title: data.title };
|
|
384
493
|
return currentSession;
|
|
385
494
|
}),
|
|
386
|
-
search: (text, options = {}) =>
|
|
387
|
-
|
|
388
|
-
before: every.before,
|
|
495
|
+
search: (text, matcher, options = {}) => searched({ text, ...searchOptionsOf(options) }, matcher),
|
|
496
|
+
...every,
|
|
389
497
|
},
|
|
390
498
|
changed(session, change, isInProject) {
|
|
391
499
|
remember(session, change);
|
|
500
|
+
let payload;
|
|
392
501
|
for (const key of isInProject ? [session, everySession] : [session]) {
|
|
393
|
-
for (const { scope, event, matcher, listener } of listening.get(key) ?? []) {
|
|
502
|
+
for (const { scope, event, method, matcher, listener, options } of listening.get(key) ?? []) {
|
|
394
503
|
if (!isHeardBy(event, matcher, change) || !isInScope(scope, session, change, isUnderIn(session)))
|
|
395
504
|
continue;
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
.
|
|
505
|
+
payload ??= payloadFor(session, change);
|
|
506
|
+
void payload
|
|
507
|
+
.then((heard) => invoke(method, options, (time) => listener(heard, time)))
|
|
508
|
+
.catch((error) => report(`session:${method}`, `The ${method} listener failed`, error));
|
|
399
509
|
}
|
|
400
510
|
}
|
|
401
511
|
},
|
|
402
512
|
answerBefore,
|
|
513
|
+
raise,
|
|
514
|
+
listen,
|
|
403
515
|
follow(next) {
|
|
404
516
|
const moved = next.id !== current.id && current.id !== '';
|
|
405
517
|
const previous = current.id;
|
|
@@ -422,12 +534,122 @@ export function createSessionsClient({ name, claude, report, isGranted }) {
|
|
|
422
534
|
},
|
|
423
535
|
};
|
|
424
536
|
}
|
|
425
|
-
export
|
|
426
|
-
|
|
537
|
+
export const recordOf = (value) => (typeof value === 'object' && value !== null && !Array.isArray(value) ? value : {});
|
|
538
|
+
export const defined = (fields) => Object.fromEntries(Object.entries(fields).filter(([, value]) => value !== undefined));
|
|
539
|
+
const placementOf = (input) => defined({ session: input['session_id'], task: input['agent_id'], agentType: input['agent_type'], cwd: input['cwd'], permissionMode: input['permission_mode'] });
|
|
540
|
+
const mcpServerOf = (server) => (server === undefined ? undefined : { name: String(recordOf(server)['name']), source: String(recordOf(server)['source']) });
|
|
541
|
+
const modelChangeOf = (input) => ({
|
|
542
|
+
model: input['to_model'],
|
|
543
|
+
previous: input['from_model'],
|
|
544
|
+
requested: input['requested_model'] ?? undefined,
|
|
545
|
+
source: input['source'],
|
|
546
|
+
contextTokenCount: input['context_tokens'],
|
|
547
|
+
isCacheWarm: input['prompt_cache_warm'],
|
|
548
|
+
cacheTtl: input['cache_ttl'],
|
|
549
|
+
cacheWriteUsd: input['estimated_cache_write_usd'],
|
|
550
|
+
pricing: input['pricing'],
|
|
551
|
+
});
|
|
552
|
+
const answerKeys = {
|
|
553
|
+
Run: ['args', 'note', 'result', 'exitCode', 'refuse'],
|
|
554
|
+
ProjectSetup: [],
|
|
555
|
+
SessionStart: ['note', 'title', 'reloadSkills', 'firstPrompt'],
|
|
556
|
+
ModelChange: ['ask', 'refuse'],
|
|
557
|
+
WorktreeCreate: ['result'],
|
|
558
|
+
Elicitation: ['refuse'],
|
|
559
|
+
ElicitationResponse: ['refuse'],
|
|
560
|
+
PermissionCheck: ['allow', 'ask', 'refuse'],
|
|
561
|
+
PermissionRequest: ['allow', 'input', 'updates', 'refuse', 'interrupt'],
|
|
562
|
+
PermissionDenial: ['retry'],
|
|
563
|
+
ToolBatch: ['note'],
|
|
564
|
+
TodoCreate: ['refuse'],
|
|
565
|
+
TodoComplete: ['refuse'],
|
|
566
|
+
ReplyDisplay: ['text'],
|
|
567
|
+
};
|
|
568
|
+
const todoOf = (input) => ({ id: input['task_id'], subject: input['task_subject'], description: input['task_description'], teammate: input['teammate_name'] });
|
|
569
|
+
const processFields = {
|
|
570
|
+
ProjectSetup: (input) => ({ trigger: input['trigger'] }),
|
|
571
|
+
SessionStart: (input) => ({
|
|
572
|
+
source: input['source'],
|
|
573
|
+
model: input['model'],
|
|
574
|
+
title: input['session_title'] || undefined,
|
|
575
|
+
idleSeconds: input['seconds_since_last_response'],
|
|
576
|
+
contextTokenCount: input['context_tokens'],
|
|
577
|
+
isCacheWarm: input['prompt_cache_likely_expired'] === undefined ? undefined : input['prompt_cache_likely_expired'] !== true,
|
|
578
|
+
cacheWriteUsd: input['estimated_cache_write_usd'],
|
|
579
|
+
}),
|
|
580
|
+
SessionEnd: (input) => ({ reason: input['reason'] === 'prompt_input_exit' ? 'exit' : input['reason'] }),
|
|
581
|
+
ModelChange: modelChangeOf,
|
|
582
|
+
CwdChange: (input) => ({ cwd: input['new_cwd'] ?? input['cwd'], previous: input['old_cwd'] }),
|
|
583
|
+
DirectoryAdd: (input) => ({ directory: input['directory'], source: input['source'] === 'slash_command' ? 'command' : 'sdk' }),
|
|
584
|
+
WorktreeCreate: (input) => ({ name: input['name'] }),
|
|
585
|
+
WorktreeRemove: (input) => ({ path: input['worktree_path'] }),
|
|
586
|
+
Notification: (input) => ({ text: input['message'], title: input['title'], type: input['notification_type'] }),
|
|
587
|
+
Elicitation: (input) => ({ id: input['elicitation_id'], server: input['mcp_server_name'], text: input['message'], mode: input['mode'], url: input['url'], schema: input['requested_schema'] }),
|
|
588
|
+
ElicitationResponse: (input) => ({ id: input['elicitation_id'], server: input['mcp_server_name'], mode: input['mode'], action: input['action'], content: input['content'] }),
|
|
589
|
+
PermissionRequest: (input) => ({ name: input['tool_name'], input: recordOf(input['tool_input']), suggestions: input['permission_suggestions'] ?? [], mcpServer: mcpServerOf(input['mcp_server']) }),
|
|
590
|
+
PermissionDenial: (input) => ({ name: input['tool_name'], input: recordOf(input['tool_input']), callId: input['tool_use_id'], reason: input['reason'], mcpServer: mcpServerOf(input['mcp_server']) }),
|
|
591
|
+
ToolBatch: (input) => ({
|
|
592
|
+
calls: (Array.isArray(input['tool_calls']) ? input['tool_calls'] : []).map((call) => {
|
|
593
|
+
const fields = recordOf(call);
|
|
594
|
+
return defined({ callId: fields['tool_use_id'], name: fields['tool_name'], input: recordOf(fields['tool_input']), output: fields['tool_response'] });
|
|
595
|
+
}),
|
|
596
|
+
}),
|
|
597
|
+
TodoCreate: todoOf,
|
|
598
|
+
TodoComplete: todoOf,
|
|
599
|
+
ReplyDisplay: (input) => ({ replyId: input['message_id'], index: input['index'], isLast: input['final'], text: input['delta'] }),
|
|
600
|
+
};
|
|
601
|
+
export async function spawnTask(claude, task) {
|
|
602
|
+
if (task.title.trim() === '')
|
|
603
|
+
throw new Error('a task needs a title.');
|
|
604
|
+
const started = await claude.agent.spawn({
|
|
605
|
+
prompt: task.prompt,
|
|
606
|
+
description: task.title,
|
|
607
|
+
...(task.name === undefined ? {} : { name: task.name }),
|
|
608
|
+
...(task.agentType === undefined ? {} : { subagentType: task.agentType }),
|
|
609
|
+
...(task.model === undefined ? {} : { model: task.model }),
|
|
610
|
+
...(task.cwd === undefined ? {} : { cwd: task.cwd }),
|
|
611
|
+
});
|
|
612
|
+
if (started.deny !== undefined)
|
|
613
|
+
throw new Error(`Claude Code refused the task: ${started.deny}`);
|
|
614
|
+
if (started.agentId === undefined)
|
|
615
|
+
throw new Error('Claude Code answered without starting a task.');
|
|
616
|
+
return { id: started.agentId, title: task.title, agentType: task.agentType ?? 'general-purpose', model: started.model, openedBy: '', openedAt: new Date().toISOString() };
|
|
617
|
+
}
|
|
618
|
+
export function processPayloadOf(named, input) {
|
|
619
|
+
const fields = recordOf(input);
|
|
620
|
+
return { ...placementOf(fields), ...defined(processFields[named](fields)) };
|
|
621
|
+
}
|
|
622
|
+
function hookAnswerOf(named, answer) {
|
|
623
|
+
const refuse = typeof answer['refuse'] === 'string' ? answer['refuse'] : undefined;
|
|
624
|
+
const note = typeof answer['note'] === 'string' ? [answer['note']] : undefined;
|
|
625
|
+
if (named === 'SessionStart')
|
|
626
|
+
return defined({ additionalContext: note, sessionTitle: answer['title'], reloadSkills: answer['reloadSkills'] === true ? true : undefined, initialUserMessage: answer['firstPrompt'] });
|
|
627
|
+
if (named === 'ModelChange') {
|
|
628
|
+
if (refuse !== undefined)
|
|
629
|
+
return { permissionDecision: 'deny', permissionDecisionReason: refuse };
|
|
630
|
+
return typeof answer['ask'] === 'string' ? { permissionDecision: 'ask', permissionDecisionReason: answer['ask'] } : undefined;
|
|
631
|
+
}
|
|
632
|
+
if (named === 'WorktreeCreate')
|
|
633
|
+
return defined({ worktreePath: answer['result'] });
|
|
634
|
+
if (named === 'PermissionDenial')
|
|
635
|
+
return answer['retry'] === true ? { retry: true } : undefined;
|
|
636
|
+
if (named === 'ToolBatch')
|
|
637
|
+
return defined({ additionalContext: note });
|
|
638
|
+
if (named === 'ReplyDisplay')
|
|
639
|
+
return defined({ displayContent: answer['text'] });
|
|
640
|
+
return refuse === undefined ? undefined : { block: refuse };
|
|
641
|
+
}
|
|
642
|
+
export function resultOf(where, call, answered) {
|
|
643
|
+
const base = { id: '', type: ToolResult, at: new Date().toISOString(), session: where.session, context: where.context, ...(where.turn === undefined ? {} : { turn: where.turn.prompt.id }), callId: '', name: call.name, input: call.input, cwd: call.cwd, files: noFiles };
|
|
427
644
|
if (answered.deny !== undefined)
|
|
428
|
-
return { ...base,
|
|
429
|
-
return { ...base,
|
|
645
|
+
return { ...base, output: answered.deny, isError: true };
|
|
646
|
+
return { ...base, output: answered.result ?? answered.text, isError: answered.isError === true };
|
|
430
647
|
}
|
|
431
|
-
function
|
|
432
|
-
return {
|
|
648
|
+
function searchOptionsOf({ running, since, everyProject, before }) {
|
|
649
|
+
return {
|
|
650
|
+
...(running === undefined ? {} : { running }),
|
|
651
|
+
...(since === undefined ? {} : { since }),
|
|
652
|
+
...(everyProject === undefined ? {} : { everyProject }),
|
|
653
|
+
...(before === undefined ? {} : { before: { session: before.session, id: before.id } }),
|
|
654
|
+
};
|
|
433
655
|
}
|
package/runtime/state.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"state.d.ts","sourceRoot":"","sources":["../src/runtime/state.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAA;AACpD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AACzC,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAA;AAQ3C,MAAM,MAAM,YAAY,GAAG;IACzB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,MAAM,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,CAAA;IAC3C,QAAQ,CAAC,OAAO,EAAE,MAAM,IAAI,CAAA;IAC5B,QAAQ,CAAC,UAAU,EAAE,MAAM,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAAA;CACvD,CAAA;AAED,MAAM,MAAM,QAAQ,CAAC,KAAK,SAAS,MAAM,IAAI;IAC3C,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAA;IACrB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,OAAO,IAAI,IAAI,CAAA;IACf,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;IACrB,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IACnC,aAAa,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,iBAAiB,CAAC,cAAc,CAAC,CAAC,QAAQ,CAAC,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CACvH,CAAA;AAQD,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,IAAI,GAAG,IAAI,CAIvE;AAED,wBAAsB,YAAY,CAAC,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAO9H;AAED,wBAAgB,WAAW,CAAC,KAAK,SAAS,MAAM,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,EAAE,YAAY,GAAG,QAAQ,CAAC,KAAK,CAAC,
|
|
1
|
+
{"version":3,"file":"state.d.ts","sourceRoot":"","sources":["../src/runtime/state.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAA;AACpD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AACzC,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAA;AAQ3C,MAAM,MAAM,YAAY,GAAG;IACzB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,MAAM,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,CAAA;IAC3C,QAAQ,CAAC,OAAO,EAAE,MAAM,IAAI,CAAA;IAC5B,QAAQ,CAAC,UAAU,EAAE,MAAM,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAAA;CACvD,CAAA;AAED,MAAM,MAAM,QAAQ,CAAC,KAAK,SAAS,MAAM,IAAI;IAC3C,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAA;IACrB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,OAAO,IAAI,IAAI,CAAA;IACf,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;IACrB,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IACnC,aAAa,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,iBAAiB,CAAC,cAAc,CAAC,CAAC,QAAQ,CAAC,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CACvH,CAAA;AAQD,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,IAAI,GAAG,IAAI,CAIvE;AAED,wBAAsB,YAAY,CAAC,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAO9H;AAED,wBAAgB,WAAW,CAAC,KAAK,SAAS,MAAM,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,EAAE,YAAY,GAAG,QAAQ,CAAC,KAAK,CAAC,CAoH9J"}
|
package/runtime/state.js
CHANGED
|
@@ -46,17 +46,18 @@ export function createState({ name, initial, session, root, claude, report, chan
|
|
|
46
46
|
const transcriptsKey = `${name}.$transcripts.`;
|
|
47
47
|
const forgetEndedSessions = async (current) => {
|
|
48
48
|
const stored = await claude.store.keys();
|
|
49
|
-
|
|
50
|
-
const owner = recorded.slice(transcriptsKey.length);
|
|
51
|
-
if (owner === current)
|
|
52
|
-
continue;
|
|
49
|
+
const ended = async (recorded) => {
|
|
53
50
|
const transcript = await claude.store.get(recorded);
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
51
|
+
return typeof transcript !== 'string' || !(await claude.fs.exists(transcript));
|
|
52
|
+
};
|
|
53
|
+
const owners = stored.filter((key) => key.startsWith(transcriptsKey) && key !== `${transcriptsKey}${current}`);
|
|
54
|
+
await Promise.all(owners.map(async (recorded) => {
|
|
55
|
+
if (!(await ended(recorded)))
|
|
56
|
+
return;
|
|
57
|
+
const owner = recorded.slice(transcriptsKey.length);
|
|
58
|
+
await Promise.all(stored.filter((key) => key.startsWith(`${name}.`) && key.endsWith(`.sessions.${owner}`)).map((value) => claude.store.delete(value)));
|
|
58
59
|
await claude.store.delete(recorded);
|
|
59
|
-
}
|
|
60
|
+
}));
|
|
60
61
|
};
|
|
61
62
|
const loadGroups = async (only, next) => {
|
|
62
63
|
const loaded = [];
|
|
@@ -114,8 +115,10 @@ export function createState({ name, initial, session, root, claude, report, chan
|
|
|
114
115
|
}),
|
|
115
116
|
moveTo: (nextRoot) => inTurn(async () => (nextRoot === projectRoot ? undefined : loadGroups(['project'], { root: nextRoot, session: sessionId }))),
|
|
116
117
|
switchSession: (nextSession, source, transcript) => inTurn(async () => {
|
|
117
|
-
|
|
118
|
-
|
|
118
|
+
if (Object.keys(declared.session).length > 0) {
|
|
119
|
+
await claude.store.set(`${transcriptsKey}${nextSession}`, transcript);
|
|
120
|
+
void forgetEndedSessions(nextSession).catch((error) => report({ id: 'state:sessions', message: 'Could not forget the session values of ended sessions', error }));
|
|
121
|
+
}
|
|
119
122
|
if (nextSession === sessionId)
|
|
120
123
|
return;
|
|
121
124
|
if (source === 'fork')
|
package/runtime/tool-calls.d.ts
CHANGED
|
@@ -6,7 +6,6 @@ export type Agent = {
|
|
|
6
6
|
};
|
|
7
7
|
export type ToolCalls = {
|
|
8
8
|
agentOf(toolUseId: string | undefined): Promise<Agent>;
|
|
9
|
-
cwdOf(toolUseId: string): string | undefined;
|
|
10
9
|
};
|
|
11
10
|
export declare const reservedKeys: readonly string[];
|
|
12
11
|
export declare function toolInputOf(envelope: object): Record<string, unknown>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tool-calls.d.ts","sourceRoot":"","sources":["../src/runtime/tool-calls.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AACzC,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AAEzC,MAAM,MAAM,KAAK,GAAG;IAAE,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,CAAA;AAE5D,MAAM,MAAM,SAAS,GAAG;IACtB,OAAO,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAAC,KAAK,CAAC,CAAA;
|
|
1
|
+
{"version":3,"file":"tool-calls.d.ts","sourceRoot":"","sources":["../src/runtime/tool-calls.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AACzC,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AAEzC,MAAM,MAAM,KAAK,GAAG;IAAE,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,CAAA;AAE5D,MAAM,MAAM,SAAS,GAAG;IACtB,OAAO,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAAC,KAAK,CAAC,CAAA;CACvD,CAAA;AAED,eAAO,MAAM,YAAY,EAAE,SAAS,MAAM,EAAkD,CAAA;AAE5F,wBAAgB,WAAW,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAErE;AAID,wBAAgB,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,SAAS,CA8BnE"}
|