@cmodjs/core 1.0.0 → 1.5.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 +22 -11
- package/docs/dependencies.md +6 -4
- package/docs/hooks.md +26 -3
- package/docs/index.md +1 -1
- package/docs/install-steps.md +5 -3
- package/docs/mod.md +29 -3
- package/docs/permissions.md +4 -4
- package/docs/session.md +126 -63
- package/docs/testing.md +7 -5
- package/docs/ui.md +9 -1
- package/jobs/program.d.ts.map +1 -1
- package/jobs/program.js +7 -2
- package/mod.d.ts +30 -6
- package/mod.d.ts.map +1 -1
- package/mod.js +34 -1
- package/package.json +1 -1
- package/records.d.ts +4 -1
- package/records.d.ts.map +1 -1
- package/records.js +6 -1
- package/register.d.ts.map +1 -1
- package/register.js +17 -0
- package/runtime/announce.d.ts.map +1 -1
- package/runtime/announce.js +2 -0
- package/runtime/before.d.ts +32 -0
- package/runtime/before.d.ts.map +1 -0
- package/runtime/before.js +214 -0
- package/runtime/claude.d.ts +2 -1
- package/runtime/claude.d.ts.map +1 -1
- package/runtime/conversations.d.ts +1 -0
- package/runtime/conversations.d.ts.map +1 -1
- package/runtime/conversations.js +333 -61
- package/runtime/dependencies.js +1 -1
- package/runtime/engine.d.ts +2 -6
- package/runtime/engine.d.ts.map +1 -1
- package/runtime/grants.d.ts.map +1 -1
- package/runtime/grants.js +8 -8
- package/runtime/hooks.d.ts +1 -1
- package/runtime/hooks.d.ts.map +1 -1
- package/runtime/hooks.js +19 -4
- package/runtime/installer.d.ts +1 -1
- package/runtime/installer.d.ts.map +1 -1
- package/runtime/installer.js +6 -5
- package/runtime/lifecycle.d.ts.map +1 -1
- package/runtime/lifecycle.js +130 -59
- package/runtime/metadata.d.ts.map +1 -1
- package/runtime/metadata.js +5 -4
- package/runtime/sessions.d.ts +26 -4
- package/runtime/sessions.d.ts.map +1 -1
- package/runtime/sessions.js +315 -117
- package/runtime/toaster.d.ts +5 -12
- package/runtime/toaster.d.ts.map +1 -1
- package/runtime/toaster.js +158 -25
- package/runtime/ui.d.ts.map +1 -1
- package/runtime/ui.js +26 -6
- package/session/context/context.d.ts +3 -1
- package/session/context/context.d.ts.map +1 -1
- package/session/context/turn/message/message.d.ts +1 -0
- package/session/context/turn/message/message.d.ts.map +1 -1
- package/session/context/turn/message/message.js +42 -1
- package/session/context/turn/turn.d.ts +2 -1
- package/session/context/turn/turn.d.ts.map +1 -1
- package/session/conversation.d.ts +20 -11
- package/session/conversation.d.ts.map +1 -1
- package/session/conversation.js +50 -27
- package/session/goal/goal.d.ts +3 -2
- package/session/goal/goal.d.ts.map +1 -1
- package/session/goal/task.d.ts +4 -4
- package/session/goal/task.d.ts.map +1 -1
- package/session/session.d.ts +80 -21
- package/session/session.d.ts.map +1 -1
- package/session/session.js +1 -0
- package/src/jobs/program.ts +7 -2
- package/src/mod.ts +48 -5
- package/src/records.ts +6 -1
- package/src/register.ts +17 -0
- package/src/runtime/announce.ts +2 -0
- package/src/runtime/before.ts +200 -0
- package/src/runtime/claude.ts +3 -0
- package/src/runtime/conversations.ts +337 -60
- package/src/runtime/dependencies.ts +1 -1
- package/src/runtime/engine.ts +2 -1
- package/src/runtime/grants.ts +8 -10
- package/src/runtime/hooks.ts +20 -4
- package/src/runtime/installer.ts +6 -5
- package/src/runtime/lifecycle.ts +136 -56
- package/src/runtime/metadata.ts +5 -4
- package/src/runtime/sessions.ts +316 -111
- package/src/runtime/toaster.ts +183 -45
- package/src/runtime/ui.ts +23 -4
- package/src/session/context/context.ts +3 -1
- package/src/session/context/turn/message/message.ts +39 -1
- package/src/session/context/turn/turn.ts +2 -1
- package/src/session/conversation.ts +56 -31
- package/src/session/goal/goal.ts +3 -2
- package/src/session/goal/task.ts +4 -4
- package/src/session/session.ts +62 -15
- package/src/testing/fake-claude.ts +4 -2
- package/src/testing/fake-sessions.ts +94 -20
- package/src/testing.ts +27 -3
- package/src/utils/call-effects.ts +2 -2
- package/src/utils/disposable.ts +3 -0
- package/src/utils/matcher.ts +1 -1
- package/src/utils/paths.ts +6 -3
- package/testing/fake-claude.d.ts +1 -0
- package/testing/fake-claude.d.ts.map +1 -1
- package/testing/fake-claude.js +3 -1
- package/testing/fake-sessions.d.ts +8 -1
- package/testing/fake-sessions.d.ts.map +1 -1
- package/testing/fake-sessions.js +97 -18
- package/testing.d.ts +1 -0
- package/testing.d.ts.map +1 -1
- package/testing.js +23 -2
- package/utils/call-effects.d.ts +1 -0
- package/utils/call-effects.d.ts.map +1 -1
- package/utils/call-effects.js +1 -1
- package/utils/disposable.d.ts +4 -0
- package/utils/disposable.d.ts.map +1 -0
- package/utils/disposable.js +1 -0
- package/utils/matcher.js +1 -1
- package/utils/paths.d.ts +2 -1
- package/utils/paths.d.ts.map +1 -1
- package/utils/paths.js +6 -3
package/runtime/sessions.d.ts
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
|
+
import { ToolResult } from '../session/context/turn/message/tool-result.js';
|
|
1
2
|
import type { SessionChange, SessionData } from '../session/conversation.js';
|
|
2
|
-
import { type SessionEvent, type Sessions, type Usage } from '../session/session.js';
|
|
3
|
+
import { type BeforeEvent, type SessionEvent, type Sessions, type Usage } from '../session/session.js';
|
|
3
4
|
import { type SentMatcher } from '../utils/matcher.js';
|
|
5
|
+
import { type Place } from './before.js';
|
|
4
6
|
import type { Claude } from './claude.js';
|
|
5
7
|
export type Subscription = {
|
|
6
8
|
readonly event: SessionEvent;
|
|
7
9
|
readonly matcher: SentMatcher | undefined;
|
|
8
10
|
};
|
|
11
|
+
export declare const everySession = "*";
|
|
9
12
|
export declare const conversationMethods: {
|
|
10
13
|
readonly sessions: 'cmod:sessions';
|
|
11
14
|
readonly search: 'cmod:search';
|
|
@@ -15,22 +18,41 @@ export declare const conversationMethods: {
|
|
|
15
18
|
readonly tasks: 'cmod:tasks';
|
|
16
19
|
readonly goal: 'cmod:goal';
|
|
17
20
|
readonly updateGoal: 'cmod:updateGoal';
|
|
21
|
+
readonly usage: 'cmod:usage';
|
|
18
22
|
readonly subscribe: 'cmod:subscribe';
|
|
19
|
-
readonly
|
|
23
|
+
readonly watchBefore: 'cmod:watchBefore';
|
|
24
|
+
readonly act: 'cmod:act';
|
|
20
25
|
readonly changed: 'cmod:changed';
|
|
26
|
+
readonly before: 'cmod:before';
|
|
27
|
+
};
|
|
28
|
+
export type Act = 'send' | 'startTask' | 'callTool' | 'usage' | 'clients';
|
|
29
|
+
export declare const beforePermission: (event: BeforeEvent) => string;
|
|
30
|
+
export declare const isAct: (action: string) => action is Act;
|
|
31
|
+
export declare function toolPermission(tool: string): string | undefined;
|
|
32
|
+
export declare function actPermission(action: Act, input: unknown): string | undefined;
|
|
33
|
+
export type Scope = {
|
|
34
|
+
readonly session?: string;
|
|
35
|
+
readonly context?: number;
|
|
36
|
+
readonly turn?: string;
|
|
37
|
+
readonly task?: string;
|
|
38
|
+
readonly isGoal?: true;
|
|
21
39
|
};
|
|
22
40
|
export type SessionsClient = {
|
|
23
41
|
readonly sessions: Sessions;
|
|
24
|
-
changed(session: string, change: SessionChange): void;
|
|
42
|
+
changed(session: string, change: SessionChange, isInProject: boolean): void;
|
|
43
|
+
answerBefore(session: string, event: BeforeEvent, change: SessionChange, isInProject: boolean): Promise<unknown>;
|
|
25
44
|
/** An empty title keeps the title the session already has. */
|
|
26
45
|
follow(current: Omit<SessionData, 'isRunning' | 'lastAt'>): void;
|
|
27
46
|
};
|
|
28
47
|
export declare function usageOf(claude: Pick<Claude, 'session'>): Promise<Usage>;
|
|
29
48
|
export declare function payloadOf(change: SessionChange): unknown;
|
|
30
49
|
export declare function isHeardBy(event: SessionEvent, matcher: unknown, change: SessionChange): boolean;
|
|
31
|
-
export declare function
|
|
50
|
+
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 }: {
|
|
32
52
|
readonly name: string;
|
|
33
53
|
readonly claude: Claude;
|
|
34
54
|
readonly report: (id: string, message: string, error: unknown) => void;
|
|
55
|
+
readonly isGranted: (permission: string) => boolean;
|
|
35
56
|
}): SessionsClient;
|
|
57
|
+
export declare function resultOf(where: Pick<Place, 'session' | 'context' | 'turn'>, answered: Awaited<ReturnType<Claude['tool']['call']>>): ToolResult;
|
|
36
58
|
//# 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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"}
|
package/runtime/sessions.js
CHANGED
|
@@ -1,9 +1,16 @@
|
|
|
1
|
+
import { ContextEnd, ContextStart } from '../session/context/context.js';
|
|
1
2
|
import { Message } from '../session/context/turn/message/message.js';
|
|
2
3
|
import { Note } from '../session/context/turn/message/note.js';
|
|
3
4
|
import { Person } from '../session/context/turn/message/sender.js';
|
|
5
|
+
import { ToolResult } from '../session/context/turn/message/tool-result.js';
|
|
6
|
+
import { TurnEnd, TurnStart } from '../session/context/turn/turn.js';
|
|
7
|
+
import { GoalChange } from '../session/goal/goal.js';
|
|
8
|
+
import { TaskEnd, TaskStart } from '../session/goal/task.js';
|
|
4
9
|
import { Session } from '../session/session.js';
|
|
5
10
|
import { matches, sentMatcher } from '../utils/matcher.js';
|
|
11
|
+
import { foldAnswers } from './before.js';
|
|
6
12
|
import { cmodPluginName } from './dependencies.js';
|
|
13
|
+
export const everySession = '*';
|
|
7
14
|
export const conversationMethods = {
|
|
8
15
|
sessions: 'cmod:sessions',
|
|
9
16
|
search: 'cmod:search',
|
|
@@ -13,10 +20,25 @@ export const conversationMethods = {
|
|
|
13
20
|
tasks: 'cmod:tasks',
|
|
14
21
|
goal: 'cmod:goal',
|
|
15
22
|
updateGoal: 'cmod:updateGoal',
|
|
23
|
+
usage: 'cmod:usage',
|
|
16
24
|
subscribe: 'cmod:subscribe',
|
|
17
|
-
|
|
25
|
+
watchBefore: 'cmod:watchBefore',
|
|
26
|
+
act: 'cmod:act',
|
|
18
27
|
changed: 'cmod:changed',
|
|
28
|
+
before: 'cmod:before',
|
|
19
29
|
};
|
|
30
|
+
const actPermissions = { send: 'prompt', startTask: 'tasks', usage: 'conversation', clients: 'conversation' };
|
|
31
|
+
const startsTurns = ['CronCreate'];
|
|
32
|
+
const ownSchedule = ['CronDelete', 'CronList'];
|
|
33
|
+
export const beforePermission = (event) => (event === 'toolCall' ? 'tools' : event === TaskStart || event === TaskEnd ? 'tasks' : 'prompt');
|
|
34
|
+
export const isAct = (action) => action === 'callTool' || action in actPermissions;
|
|
35
|
+
export function toolPermission(tool) {
|
|
36
|
+
return startsTurns.includes(tool) ? 'prompt' : ownSchedule.includes(tool) ? undefined : 'tools';
|
|
37
|
+
}
|
|
38
|
+
export function actPermission(action, input) {
|
|
39
|
+
return action === 'callTool' ? (toolPermission(String(input?.name)) ?? 'tools') : actPermissions[action];
|
|
40
|
+
}
|
|
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.`;
|
|
20
42
|
export async function usageOf(claude) {
|
|
21
43
|
const [model, usage] = await Promise.all([claude.session.model(), claude.session.usage()]);
|
|
22
44
|
const { tokens, window, percent } = usage.context;
|
|
@@ -29,65 +51,233 @@ export function payloadOf(change) {
|
|
|
29
51
|
return change.turn;
|
|
30
52
|
if ('context' in change)
|
|
31
53
|
return change.context;
|
|
32
|
-
if ('task' in change)
|
|
33
|
-
|
|
54
|
+
if ('task' in change) {
|
|
55
|
+
const { id, title, agentType, model, openedBy, openedAt, closedAt } = change.task;
|
|
56
|
+
return { id, title, openedBy, openedAt, ...(closedAt === undefined ? {} : { closedAt }), owner: { id, title, agentType, ...(model === undefined ? {} : { model }), startedAt: openedAt } };
|
|
57
|
+
}
|
|
58
|
+
if ('usage' in change)
|
|
59
|
+
return change.usage;
|
|
34
60
|
return change.goal;
|
|
35
61
|
}
|
|
36
62
|
export function isHeardBy(event, matcher, change) {
|
|
37
|
-
const named = change.type === Message ?
|
|
63
|
+
const named = change.type === Message ? event === Message || event === change.message.type : event === change.type;
|
|
38
64
|
return named && (matcher === undefined || matches(payloadOf(change), matcher));
|
|
39
65
|
}
|
|
40
|
-
|
|
66
|
+
function taskOfChange(change) {
|
|
67
|
+
if (change.type === Message)
|
|
68
|
+
return change.message.task;
|
|
69
|
+
if (change.type === TaskStart || change.type === TaskEnd)
|
|
70
|
+
return change.task.id === '' ? change.task.parent : change.task.id;
|
|
71
|
+
if (change.type === TurnStart || change.type === TurnEnd)
|
|
72
|
+
return change.turn.prompt.task;
|
|
73
|
+
if (change.type === ContextStart || change.type === ContextEnd)
|
|
74
|
+
return change.context.task;
|
|
75
|
+
if (change.type === GoalChange)
|
|
76
|
+
return change.goal.task;
|
|
77
|
+
return undefined;
|
|
78
|
+
}
|
|
79
|
+
function ownerOfChange(change) {
|
|
80
|
+
if (change.type === TaskStart || change.type === TaskEnd)
|
|
81
|
+
return change.task.parent;
|
|
82
|
+
return taskOfChange(change);
|
|
83
|
+
}
|
|
84
|
+
function contextOfChange(change) {
|
|
85
|
+
if (change.type === Message)
|
|
86
|
+
return change.message.context;
|
|
87
|
+
if (change.type === TurnStart || change.type === TurnEnd)
|
|
88
|
+
return change.turn.context;
|
|
89
|
+
if (change.type === ContextStart || change.type === ContextEnd)
|
|
90
|
+
return change.context.number;
|
|
91
|
+
if (change.type === TaskStart || change.type === TaskEnd)
|
|
92
|
+
return change.task.context;
|
|
93
|
+
return undefined;
|
|
94
|
+
}
|
|
95
|
+
export function isInScope(scope, session, change, isUnder) {
|
|
96
|
+
if (scope.session !== undefined && scope.session !== session)
|
|
97
|
+
return false;
|
|
98
|
+
const task = taskOfChange(change);
|
|
99
|
+
if (scope.task !== undefined && (task === undefined || !isUnder(task, scope.task)))
|
|
100
|
+
return false;
|
|
101
|
+
if (scope.isGoal === true && change.type !== GoalChange && change.type !== TaskStart && (task === undefined || task === scope.task))
|
|
102
|
+
return false;
|
|
103
|
+
if (scope.context !== undefined && (contextOfChange(change) !== scope.context || ownerOfChange(change) !== scope.task))
|
|
104
|
+
return false;
|
|
105
|
+
if (scope.turn === undefined)
|
|
106
|
+
return true;
|
|
107
|
+
if (change.type === Message)
|
|
108
|
+
return change.message.turn === scope.turn;
|
|
109
|
+
return (change.type === TurnStart || change.type === TurnEnd) && change.turn.prompt.id === scope.turn;
|
|
110
|
+
}
|
|
111
|
+
export function createSessionsClient({ name, claude, report, isGranted }) {
|
|
41
112
|
const call = (method, input) => claude.cmod.call({ to: cmodPluginName, method, input });
|
|
42
113
|
const listening = new Map();
|
|
114
|
+
const deciding = new Map();
|
|
43
115
|
const queued = new Set();
|
|
116
|
+
const parents = new Map();
|
|
117
|
+
const known = new Map();
|
|
44
118
|
let current = { id: '', title: '', project: '', cwd: '', model: '', startedAt: '', lastAt: '', isRunning: true };
|
|
45
|
-
const
|
|
46
|
-
|
|
119
|
+
const keyOf = (scope) => scope.session ?? everySession;
|
|
120
|
+
const subscribe = (key) => {
|
|
121
|
+
if (queued.has(key))
|
|
47
122
|
return;
|
|
48
|
-
queued.add(
|
|
123
|
+
queued.add(key);
|
|
49
124
|
void Promise.resolve().then(async () => {
|
|
50
|
-
queued.delete(
|
|
51
|
-
const
|
|
52
|
-
|
|
125
|
+
queued.delete(key);
|
|
126
|
+
const heard = (from) => [...(from.get(key) ?? [])].map(({ event, matcher }) => ({ event, matcher: matcher === undefined ? undefined : sentMatcher(matcher) }));
|
|
127
|
+
const tracksTasks = [...(listening.get(key) ?? []), ...(deciding.get(key) ?? [])].some(({ scope }) => scope.task !== undefined || scope.isGoal === true);
|
|
128
|
+
const subscriptions = [...heard(listening), ...(tracksTasks ? [{ event: TaskStart }] : [])];
|
|
129
|
+
await call(conversationMethods.subscribe, { session: key, subscriptions }).catch((error) => report(`sessions:${key}`, `Hears nothing from ${key === everySession ? 'the other sessions' : `session ${key}`}`, error));
|
|
130
|
+
await call(conversationMethods.watchBefore, { session: key, subscriptions: heard(deciding) }).catch((error) => report(`before:${key}`, `Changes nothing in ${key === everySession ? 'the other sessions' : `session ${key}`}`, error));
|
|
53
131
|
});
|
|
54
132
|
};
|
|
55
|
-
const
|
|
56
|
-
|
|
57
|
-
|
|
133
|
+
const learnTasks = async (session) => {
|
|
134
|
+
if (parents.has(session))
|
|
135
|
+
return;
|
|
136
|
+
const tree = new Map();
|
|
137
|
+
parents.set(session, tree);
|
|
138
|
+
for (const task of await call(conversationMethods.tasks, { session, all: true }))
|
|
139
|
+
tree.set(task.id, task.parent);
|
|
140
|
+
};
|
|
141
|
+
const isUnderIn = (session) => (task, ancestor) => {
|
|
142
|
+
const tree = parents.get(session);
|
|
143
|
+
for (let at = task, steps = 0; at !== undefined && steps < 64; at = tree?.get(at), steps += 1)
|
|
144
|
+
if (at === ancestor)
|
|
145
|
+
return true;
|
|
146
|
+
return false;
|
|
58
147
|
};
|
|
59
|
-
const
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
148
|
+
const remember = (session, change) => {
|
|
149
|
+
if (change.type === TaskStart)
|
|
150
|
+
parents.get(session)?.set(change.task.id, change.task.parent);
|
|
151
|
+
};
|
|
152
|
+
const add = (into, scope, event, matcherOrListener, maybeListener, onChange) => {
|
|
153
|
+
const listener = (maybeListener ?? matcherOrListener);
|
|
154
|
+
const matcher = maybeListener === undefined ? undefined : matcherOrListener;
|
|
155
|
+
const key = keyOf(scope);
|
|
156
|
+
const entry = { scope, event, matcher, listener };
|
|
157
|
+
const set = into.get(key) ?? new Set();
|
|
158
|
+
set.add(entry);
|
|
159
|
+
into.set(key, set);
|
|
160
|
+
if (scope.task !== undefined || scope.isGoal === true)
|
|
161
|
+
void learnTasks(scope.session ?? current.id).catch((error) => report(`tasks:${key}`, 'Could not read the tasks it listens under', error));
|
|
162
|
+
onChange(key);
|
|
163
|
+
return {
|
|
164
|
+
dispose() {
|
|
165
|
+
for (const [at, held] of into) {
|
|
166
|
+
if (!held.delete(entry))
|
|
167
|
+
continue;
|
|
168
|
+
if (held.size === 0)
|
|
169
|
+
into.delete(at);
|
|
170
|
+
onChange(at);
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
},
|
|
174
|
+
};
|
|
175
|
+
};
|
|
176
|
+
const combined = async (session, event, change, isInProject) => {
|
|
177
|
+
const askers = [...(deciding.get(session) ?? []), ...(isInProject ? (deciding.get(everySession) ?? []) : [])]
|
|
178
|
+
.filter((entry) => entry.event === event)
|
|
179
|
+
.map((entry) => async (latest) => {
|
|
180
|
+
if (!isInScope(entry.scope, session, latest, isUnderIn(session)))
|
|
181
|
+
return undefined;
|
|
182
|
+
if (entry.matcher !== undefined && !matches(payloadOf(latest), entry.matcher))
|
|
183
|
+
return undefined;
|
|
184
|
+
const answer = await entry.listener(payloadFor(session, latest));
|
|
185
|
+
if (answer === undefined || isGranted(beforePermission(event)))
|
|
186
|
+
return answer;
|
|
187
|
+
throw new Error(`Its before(${event}) answer changes nothing until ${beforePermission(event)} is turned on for ${name} in /mods.`);
|
|
188
|
+
});
|
|
189
|
+
return foldAnswers(event, change, askers, (error) => report(`before:${event}`, `The before(${event}) listener failed`, error));
|
|
190
|
+
};
|
|
191
|
+
const recordPart = (scope) => ({
|
|
192
|
+
search: (text, options = {}) => call(conversationMethods.search, { text, ...scope, ...optionsOf(options) }),
|
|
193
|
+
on: (event, matcherOrListener, maybeListener) => add(listening, scope, event, matcherOrListener, maybeListener, subscribe),
|
|
194
|
+
before: (event, matcherOrListener, maybeListener) => add(deciding, scope, event, matcherOrListener, maybeListener, subscribe),
|
|
63
195
|
});
|
|
196
|
+
const turnOf = (scope, data) => {
|
|
197
|
+
const task = data.prompt.task ?? scope.task;
|
|
198
|
+
return {
|
|
199
|
+
...data,
|
|
200
|
+
...recordPart({ ...scope, ...(task === undefined ? {} : { task }), turn: data.prompt.id }),
|
|
201
|
+
messages: () => call(conversationMethods.messages, { session: scope.session, turn: data.prompt.id, ...(task === undefined ? {} : { task }) }),
|
|
202
|
+
};
|
|
203
|
+
};
|
|
204
|
+
const contextOf = (scope, data) => {
|
|
205
|
+
const owned = { ...(scope.session === undefined ? {} : { session: scope.session }), ...(data.task === undefined ? {} : { task: data.task }) };
|
|
206
|
+
return {
|
|
207
|
+
...data,
|
|
208
|
+
...recordPart({ ...owned, context: data.number }),
|
|
209
|
+
turns: async () => (await call(conversationMethods.turns, { ...owned, context: data.number })).map((turn) => turnOf(owned, turn)),
|
|
210
|
+
};
|
|
211
|
+
};
|
|
64
212
|
const taskOf = (session, data) => ({
|
|
65
|
-
|
|
66
|
-
|
|
213
|
+
id: data.id,
|
|
214
|
+
title: data.title,
|
|
215
|
+
openedBy: data.openedBy,
|
|
216
|
+
openedAt: data.openedAt,
|
|
217
|
+
...(data.closedAt === undefined ? {} : { closedAt: data.closedAt }),
|
|
218
|
+
owner: ownerOf(session, data),
|
|
219
|
+
...recordPart({ session, task: data.id }),
|
|
67
220
|
tasks: async () => (await call(conversationMethods.tasks, { session, task: data.id })).map((task) => taskOf(session, task)),
|
|
221
|
+
messages: () => call(conversationMethods.messages, { session, task: data.id }),
|
|
68
222
|
});
|
|
69
|
-
const goalOf = (
|
|
223
|
+
const goalOf = (scope, data) => ({
|
|
70
224
|
...data,
|
|
225
|
+
...recordPart({ ...scope, isGoal: true }),
|
|
226
|
+
tasks: async () => (await call(conversationMethods.tasks, { session: scope.session, ...(scope.task === undefined ? {} : { task: scope.task }) })).map((task) => taskOf(scope.session ?? current.id, task)),
|
|
71
227
|
update: async ({ text }) => {
|
|
72
228
|
if (text.trim() === '')
|
|
73
229
|
throw new Error(`${name}: a goal needs text.`);
|
|
74
|
-
await call(conversationMethods.updateGoal, { session, text });
|
|
230
|
+
await call(conversationMethods.updateGoal, { session: scope.session ?? current.id, ...(scope.task === undefined ? {} : { task: scope.task }), text });
|
|
75
231
|
},
|
|
76
|
-
tasks: async () => (await call(conversationMethods.tasks, { session })).map((task) => taskOf(session, task)),
|
|
77
232
|
});
|
|
78
233
|
const payloadFor = (session, change) => {
|
|
234
|
+
const scope = { session };
|
|
79
235
|
if (change.type === Message)
|
|
80
236
|
return change.message;
|
|
81
237
|
if ('turn' in change)
|
|
82
|
-
return turnOf(
|
|
238
|
+
return turnOf(scope, change.turn);
|
|
83
239
|
if ('context' in change)
|
|
84
|
-
return contextOf(
|
|
240
|
+
return contextOf(scope, change.context);
|
|
85
241
|
if ('task' in change)
|
|
86
242
|
return taskOf(session, change.task);
|
|
87
|
-
|
|
243
|
+
if ('usage' in change)
|
|
244
|
+
return change.usage;
|
|
245
|
+
return goalOf({ session, ...(change.goal.task === undefined ? {} : { task: change.goal.task }) }, { text: change.goal.text });
|
|
246
|
+
};
|
|
247
|
+
const act = (session, action, input, owned) => call(conversationMethods.act, { session: owned?.session ?? session, action, input, ...(owned === undefined ? {} : { task: owned.task }) });
|
|
248
|
+
const sendHere = async ({ type, text, as }) => {
|
|
249
|
+
if (type === Note) {
|
|
250
|
+
if (as !== undefined)
|
|
251
|
+
throw new Error(`${name}: a Note is always from this mod, so it takes no as.`);
|
|
252
|
+
const added = await claude.session.append({ message: { type: 'user', content: [{ type: 'text', text }] } });
|
|
253
|
+
if (added.deny !== undefined)
|
|
254
|
+
throw new Error(`${name}: Claude Code refused the note: ${added.deny}`);
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
const submitted = await claude.prompt.submit({ text, ...(as === Person ? { asUser: true } : {}) });
|
|
258
|
+
if ('drop' in submitted && typeof submitted.drop === 'string')
|
|
259
|
+
throw new Error(`${name}: Claude Code dropped the prompt: ${submitted.drop}`);
|
|
88
260
|
};
|
|
89
|
-
const
|
|
261
|
+
const startHere = async (task) => {
|
|
262
|
+
const started = await claude.agent.spawn({
|
|
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())];
|
|
274
|
+
const sessionOf = (read, owned) => {
|
|
90
275
|
const id = () => read().id;
|
|
276
|
+
const isHere = () => owned === undefined && id() === current.id;
|
|
277
|
+
const scope = () => (owned === undefined ? { session: id() } : { session: owned.session, task: owned.task });
|
|
278
|
+
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(scope(), turn));
|
|
280
|
+
const contextsOf = async () => (await call(conversationMethods.contexts, where())).map((context) => contextOf(scope(), context));
|
|
91
281
|
const session = {
|
|
92
282
|
get id() {
|
|
93
283
|
return read().id;
|
|
@@ -98,12 +288,18 @@ export function createSessionsClient({ name, claude, report }) {
|
|
|
98
288
|
get project() {
|
|
99
289
|
return read().project;
|
|
100
290
|
},
|
|
101
|
-
get
|
|
102
|
-
return read().
|
|
291
|
+
get agentType() {
|
|
292
|
+
return read().agentType;
|
|
103
293
|
},
|
|
104
294
|
get model() {
|
|
105
295
|
return read().model;
|
|
106
296
|
},
|
|
297
|
+
get cwd() {
|
|
298
|
+
return read().cwd;
|
|
299
|
+
},
|
|
300
|
+
get isRunning() {
|
|
301
|
+
return read().isRunning;
|
|
302
|
+
},
|
|
107
303
|
get startedAt() {
|
|
108
304
|
return read().startedAt;
|
|
109
305
|
},
|
|
@@ -113,123 +309,125 @@ export function createSessionsClient({ name, claude, report }) {
|
|
|
113
309
|
get forkedFrom() {
|
|
114
310
|
return read().forkedFrom;
|
|
115
311
|
},
|
|
116
|
-
|
|
117
|
-
|
|
312
|
+
search: (text, options = {}) => recordPart(scope()).search(text, options),
|
|
313
|
+
on: (event, matcherOrListener, maybeListener) => recordPart(scope()).on(event, matcherOrListener, maybeListener),
|
|
314
|
+
before: (event, matcherOrListener, maybeListener) => recordPart(scope()).before(event, matcherOrListener, maybeListener),
|
|
315
|
+
goal: async () => goalOf(scope(), await call(conversationMethods.goal, where())),
|
|
316
|
+
context: async () => {
|
|
317
|
+
const all = await contextsOf();
|
|
318
|
+
const last = all.at(-1);
|
|
319
|
+
if (last === undefined)
|
|
320
|
+
throw new Error(`${name}: session ${id()} has no context yet.`);
|
|
321
|
+
return last;
|
|
322
|
+
},
|
|
323
|
+
contexts: contextsOf,
|
|
324
|
+
turn: async () => {
|
|
325
|
+
const [last] = await turnsOf({ last: 1 });
|
|
326
|
+
return last?.closedAt === undefined ? last : undefined;
|
|
118
327
|
},
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
const listener = (maybeListener ?? matcherOrListener);
|
|
126
|
-
const matcher = maybeListener === undefined ? undefined : matcherOrListener;
|
|
127
|
-
const owner = id();
|
|
128
|
-
const heard = { event, matcher, listener };
|
|
129
|
-
const set = listening.get(owner) ?? new Set();
|
|
130
|
-
set.add(heard);
|
|
131
|
-
listening.set(owner, set);
|
|
132
|
-
subscribe(owner);
|
|
133
|
-
return {
|
|
134
|
-
dispose() {
|
|
135
|
-
if (!set.delete(heard))
|
|
136
|
-
return;
|
|
137
|
-
if (set.size === 0)
|
|
138
|
-
listening.delete(owner);
|
|
139
|
-
subscribe(owner);
|
|
140
|
-
},
|
|
141
|
-
};
|
|
142
|
-
},
|
|
143
|
-
async send({ type, text, as }) {
|
|
144
|
-
if (text.trim() === '')
|
|
328
|
+
turns: turnsOf,
|
|
329
|
+
messages: (options = {}) => call(conversationMethods.messages, { ...where(), ...options }),
|
|
330
|
+
usage: async () => (isHere() ? usageOf(claude) : act(id(), 'usage', null, owned)),
|
|
331
|
+
clients: async () => (isHere() ? clientsHere() : act(id(), 'clients', null, owned)),
|
|
332
|
+
async send(message) {
|
|
333
|
+
if (message.text.trim() === '')
|
|
145
334
|
throw new Error(`${name}: send needs text.`);
|
|
146
|
-
if (
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
if (type === Note) {
|
|
150
|
-
if (as !== undefined)
|
|
151
|
-
throw new Error(`${name}: a Note is always from this mod, so it takes no as.`);
|
|
152
|
-
const added = await claude.session.append({ message: { type: 'user', content: [{ type: 'text', text }] } });
|
|
153
|
-
if (added.deny !== undefined)
|
|
154
|
-
throw new Error(`${name}: Claude Code refused the note: ${added.deny}`);
|
|
155
|
-
return;
|
|
156
|
-
}
|
|
157
|
-
const submitted = await claude.prompt.submit({ text, ...(as === Person ? { asUser: true } : {}) });
|
|
158
|
-
if ('drop' in submitted && typeof submitted.drop === 'string')
|
|
159
|
-
throw new Error(`${name}: Claude Code dropped the prompt: ${submitted.drop}`);
|
|
160
|
-
return;
|
|
161
|
-
}
|
|
162
|
-
if (!read().isRunning)
|
|
163
|
-
throw new Error(`${name}: session ${id()} has ended, so it takes no messages.`);
|
|
164
|
-
if (as === Session) {
|
|
165
|
-
if (type === Note)
|
|
166
|
-
throw new Error(`${name}: a message from this session reaches Claude as a Prompt, so send it with type: Prompt.`);
|
|
167
|
-
const sent = await claude.session.send({ to: { sessionId: id() }, text });
|
|
168
|
-
if (sent.isDelivered !== true)
|
|
169
|
-
throw new Error(`${name}: Claude Code could not deliver the message to session ${id()}.`);
|
|
170
|
-
return;
|
|
171
|
-
}
|
|
172
|
-
await call(conversationMethods.send, { session: id(), type, text, ...(as === undefined ? {} : { as }) });
|
|
335
|
+
if (isHere() && message.as !== Session)
|
|
336
|
+
return sendHere(message);
|
|
337
|
+
await act(id(), 'send', message, owned);
|
|
173
338
|
},
|
|
174
339
|
async startTask(task) {
|
|
175
|
-
if (
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
return taskOf(id(), data);
|
|
188
|
-
},
|
|
189
|
-
messages: (options = {}) => call(conversationMethods.messages, { session: id(), ...options }),
|
|
190
|
-
turns: async (options = {}) => (await call(conversationMethods.turns, { session: id(), ...options })).map((turn) => turnOf(id(), turn)),
|
|
191
|
-
contexts: async () => (await call(conversationMethods.contexts, { session: id() })).map((context) => contextOf(id(), context)),
|
|
192
|
-
goal: async () => goalOf(id(), await call(conversationMethods.goal, { session: id() })),
|
|
340
|
+
if (owned !== undefined)
|
|
341
|
+
throw new Error(asked('startTask'));
|
|
342
|
+
if (isHere())
|
|
343
|
+
return startHere(task);
|
|
344
|
+
const started = await act(id(), 'startTask', task);
|
|
345
|
+
return taskOf(id(), started);
|
|
346
|
+
},
|
|
347
|
+
async callTool(toolCall) {
|
|
348
|
+
if (owned !== undefined)
|
|
349
|
+
throw new Error(asked('callTool'));
|
|
350
|
+
return act(id(), 'callTool', toolCall);
|
|
351
|
+
},
|
|
193
352
|
};
|
|
194
353
|
return session;
|
|
195
354
|
};
|
|
355
|
+
const ownerOf = (session, data) => {
|
|
356
|
+
const parent = session === current.id ? current : known.get(session);
|
|
357
|
+
return sessionOf(() => ({
|
|
358
|
+
id: data.id,
|
|
359
|
+
title: data.title,
|
|
360
|
+
project: parent?.project ?? '',
|
|
361
|
+
agentType: data.agentType,
|
|
362
|
+
model: data.model ?? '',
|
|
363
|
+
cwd: parent?.cwd ?? '',
|
|
364
|
+
isRunning: data.closedAt === undefined && (parent?.isRunning ?? true),
|
|
365
|
+
startedAt: data.openedAt,
|
|
366
|
+
lastAt: data.closedAt ?? data.openedAt,
|
|
367
|
+
}), { session, task: data.id });
|
|
368
|
+
};
|
|
196
369
|
const currentSession = sessionOf(() => current);
|
|
370
|
+
const every = recordPart({});
|
|
371
|
+
const answerBefore = async (session, event, change, isInProject) => (isGranted('conversation') ? combined(session, event, change, isInProject) : undefined);
|
|
197
372
|
return {
|
|
198
373
|
sessions: {
|
|
199
374
|
get current() {
|
|
200
375
|
return currentSession;
|
|
201
376
|
},
|
|
202
377
|
list: async (scope = {}) => (await call(conversationMethods.sessions, scope)).map((data) => {
|
|
203
|
-
if (data.id !== current.id)
|
|
204
|
-
|
|
378
|
+
if (data.id !== current.id) {
|
|
379
|
+
known.set(data.id, data);
|
|
380
|
+
return sessionOf(() => known.get(data.id) ?? data);
|
|
381
|
+
}
|
|
205
382
|
if (current.title === '')
|
|
206
383
|
current = { ...current, title: data.title };
|
|
207
384
|
return currentSession;
|
|
208
385
|
}),
|
|
209
|
-
search: (text, options = {}) => call(conversationMethods.search, { text, ...options, ...(options.
|
|
386
|
+
search: (text, options = {}) => call(conversationMethods.search, { text, ...optionsOf(options), ...(options.running === undefined ? {} : { running: options.running }), ...(options.since === undefined ? {} : { since: options.since }), ...(options.everyProject === undefined ? {} : { everyProject: options.everyProject }) }),
|
|
387
|
+
on: every.on,
|
|
388
|
+
before: every.before,
|
|
210
389
|
},
|
|
211
|
-
changed(session, change) {
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
.
|
|
390
|
+
changed(session, change, isInProject) {
|
|
391
|
+
remember(session, change);
|
|
392
|
+
for (const key of isInProject ? [session, everySession] : [session]) {
|
|
393
|
+
for (const { scope, event, matcher, listener } of listening.get(key) ?? []) {
|
|
394
|
+
if (!isHeardBy(event, matcher, change) || !isInScope(scope, session, change, isUnderIn(session)))
|
|
395
|
+
continue;
|
|
396
|
+
void Promise.resolve()
|
|
397
|
+
.then(() => listener(payloadFor(session, change)))
|
|
398
|
+
.catch((error) => report(`session:${event}`, `The ${event} listener failed`, error));
|
|
399
|
+
}
|
|
218
400
|
}
|
|
219
401
|
},
|
|
402
|
+
answerBefore,
|
|
220
403
|
follow(next) {
|
|
221
404
|
const moved = next.id !== current.id && current.id !== '';
|
|
222
405
|
const previous = current.id;
|
|
223
406
|
current = { ...next, title: next.title || (next.id === current.id ? current.title : ''), isRunning: true, lastAt: new Date().toISOString() };
|
|
224
407
|
if (!moved)
|
|
225
408
|
return;
|
|
226
|
-
const
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
409
|
+
for (const into of [listening, deciding]) {
|
|
410
|
+
const kept = into.get(previous);
|
|
411
|
+
if (kept === undefined)
|
|
412
|
+
continue;
|
|
413
|
+
into.delete(previous);
|
|
414
|
+
for (const entry of kept)
|
|
415
|
+
if (entry.scope.session === previous)
|
|
416
|
+
entry.scope = { ...entry.scope, session: current.id };
|
|
417
|
+
into.set(current.id, new Set([...(into.get(current.id) ?? []), ...kept]));
|
|
418
|
+
}
|
|
419
|
+
parents.delete(previous);
|
|
231
420
|
subscribe(previous);
|
|
232
421
|
subscribe(current.id);
|
|
233
422
|
},
|
|
234
423
|
};
|
|
235
424
|
}
|
|
425
|
+
export function resultOf(where, answered) {
|
|
426
|
+
const base = { id: '', type: ToolResult, at: new Date().toISOString(), session: where.session, context: where.context, ...(where.turn === undefined ? {} : { turn: where.turn.prompt.id }), callId: '' };
|
|
427
|
+
if (answered.deny !== undefined)
|
|
428
|
+
return { ...base, text: answered.deny, isError: true };
|
|
429
|
+
return { ...base, text: answered.text ?? JSON.stringify(answered.result), isError: answered.isError === true };
|
|
430
|
+
}
|
|
431
|
+
function optionsOf(options) {
|
|
432
|
+
return { ...(options.limit === undefined ? {} : { limit: options.limit }), ...(options.before === undefined ? {} : { before: { session: options.before.session, id: options.before.id } }) };
|
|
433
|
+
}
|
package/runtime/toaster.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ElementTable, RenderElement, RenderPropsOf } from 'claude-code';
|
|
2
|
-
import type {
|
|
2
|
+
import type { Toast, ToastId, ToastType } from '../mod.js';
|
|
3
3
|
import type { Claude } from './claude.js';
|
|
4
4
|
export type ShownToast = {
|
|
5
5
|
readonly type: ToastType;
|
|
@@ -11,21 +11,14 @@ export type Failure = {
|
|
|
11
11
|
readonly message: string;
|
|
12
12
|
readonly error?: unknown;
|
|
13
13
|
readonly description?: string;
|
|
14
|
+
readonly retry?: () => unknown;
|
|
14
15
|
};
|
|
15
16
|
export type Toaster = {
|
|
16
17
|
readonly toast: Toast;
|
|
17
18
|
copy(text: string): Promise<void>;
|
|
19
|
+
fail(failure: Failure): ToastId;
|
|
18
20
|
readonly shown: readonly ShownToast[];
|
|
19
|
-
draw(drawn: RenderElement, table: ElementTable, band: RenderPropsOf['AbovePrompt']): RenderElement;
|
|
21
|
+
draw(drawn: RenderElement, table: ElementTable, band: RenderPropsOf['AbovePrompt'], bandId: string): RenderElement;
|
|
20
22
|
};
|
|
21
|
-
export declare function createToaster(claude: Claude, name: string): Toaster;
|
|
22
|
-
export type FailureTarget = {
|
|
23
|
-
readonly name: string;
|
|
24
|
-
readonly ui: Pick<Mod['ui'], 'toast' | 'copy'>;
|
|
25
|
-
readonly claude: {
|
|
26
|
-
readonly plugin: Claude['plugin'];
|
|
27
|
-
readonly fs: Pick<Claude['fs'], 'read'>;
|
|
28
|
-
};
|
|
29
|
-
};
|
|
30
|
-
export declare function showFailure({ name, ui, claude }: FailureTarget, { id, message, error, description }: Failure): ToastId;
|
|
23
|
+
export declare function createToaster(claude: Claude, name: string, version: () => string | undefined): Toaster;
|
|
31
24
|
//# sourceMappingURL=toaster.d.ts.map
|