@cmodjs/core 0.3.0 → 1.5.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 +61 -10
- package/docs/dependencies.md +16 -14
- package/docs/hooks.md +64 -29
- package/docs/index.md +29 -25
- package/docs/install-steps.md +13 -9
- package/docs/jobs.md +59 -111
- package/docs/mod.md +227 -175
- package/docs/options.md +2 -2
- package/docs/permissions.md +27 -16
- package/docs/session.md +311 -0
- package/docs/state.md +11 -7
- package/docs/testing.md +68 -23
- package/docs/ui.md +116 -76
- package/jobs/check.d.ts.map +1 -1
- package/jobs/check.js +5 -2
- package/jobs/context.d.ts +7 -8
- package/jobs/context.d.ts.map +1 -1
- package/jobs/context.js +23 -73
- package/jobs/permissions/decide-permission.d.ts +5 -3
- package/jobs/permissions/decide-permission.d.ts.map +1 -1
- package/jobs/permissions/decide-permission.js +6 -4
- package/jobs/permissions/find-project-scope.d.ts +1 -1
- package/jobs/permissions/find-project-scope.d.ts.map +1 -1
- package/jobs/permissions/find-project-scope.js +8 -11
- package/jobs/permissions/match-target.d.ts +2 -2
- package/jobs/permissions/match-target.d.ts.map +1 -1
- package/jobs/permissions.d.ts +1 -1
- package/jobs/permissions.d.ts.map +1 -1
- package/jobs/permissions.js +6 -4
- package/jobs/program.d.ts.map +1 -1
- package/jobs/program.js +14 -4
- package/jobs/prompt.d.ts +4 -2
- package/jobs/prompt.d.ts.map +1 -1
- package/jobs/prompt.js +16 -15
- package/jobs/schedule.d.ts +2 -2
- package/jobs/schedule.d.ts.map +1 -1
- package/jobs/schedule.js +9 -9
- package/jobs/slash-command.d.ts +4 -4
- package/jobs/slash-command.d.ts.map +1 -1
- package/jobs/slash-command.js +14 -9
- package/jobs/status-line.d.ts +1 -11
- package/jobs/status-line.d.ts.map +1 -1
- package/jobs/status-line.js +8 -12
- package/jobs/tool.d.ts +2 -2
- package/jobs/tool.d.ts.map +1 -1
- package/jobs/tool.js +15 -9
- package/mod.d.ts +178 -75
- package/mod.d.ts.map +1 -1
- package/mod.js +127 -3
- package/options.d.ts +2 -2
- package/options.js +19 -19
- package/package.json +3 -3
- package/records.d.ts +13 -8
- package/records.d.ts.map +1 -1
- package/records.js +33 -17
- package/register.d.ts.map +1 -1
- package/register.js +34 -5
- package/runtime/announce.d.ts +18 -0
- package/runtime/announce.d.ts.map +1 -0
- package/runtime/announce.js +68 -0
- package/runtime/before.d.ts +32 -0
- package/runtime/before.d.ts.map +1 -0
- package/runtime/before.js +214 -0
- package/runtime/bound.d.ts +4 -0
- package/runtime/bound.d.ts.map +1 -0
- package/runtime/bound.js +9 -0
- package/runtime/claude.d.ts +4 -2
- package/runtime/claude.d.ts.map +1 -1
- package/runtime/conversations.d.ts +17 -0
- package/runtime/conversations.d.ts.map +1 -0
- package/runtime/conversations.js +563 -0
- package/runtime/deadline.d.ts +0 -1
- package/runtime/deadline.d.ts.map +1 -1
- package/runtime/deadline.js +1 -1
- package/runtime/dependencies.d.ts +2 -0
- package/runtime/dependencies.d.ts.map +1 -1
- package/runtime/dependencies.js +10 -1
- package/runtime/engine.d.ts +13 -0
- package/runtime/engine.d.ts.map +1 -0
- package/runtime/engine.js +10 -0
- package/runtime/grants.d.ts +4 -0
- package/runtime/grants.d.ts.map +1 -1
- package/runtime/grants.js +68 -31
- package/runtime/hooks.d.ts +5 -4
- package/runtime/hooks.d.ts.map +1 -1
- package/runtime/hooks.js +29 -9
- package/runtime/installer.d.ts +1 -1
- package/runtime/installer.d.ts.map +1 -1
- package/runtime/installer.js +17 -14
- package/runtime/lifecycle.d.ts +3 -1
- package/runtime/lifecycle.d.ts.map +1 -1
- package/runtime/lifecycle.js +414 -212
- package/runtime/metadata.d.ts +3 -1
- package/runtime/metadata.d.ts.map +1 -1
- package/runtime/metadata.js +25 -14
- package/runtime/options.d.ts +3 -1
- package/runtime/options.d.ts.map +1 -1
- package/runtime/options.js +4 -4
- package/runtime/programs.d.ts +8 -0
- package/runtime/programs.d.ts.map +1 -1
- package/runtime/programs.js +39 -1
- package/runtime/router.d.ts +2 -1
- package/runtime/router.d.ts.map +1 -1
- package/runtime/router.js +11 -3
- package/runtime/sessions.d.ts +58 -0
- package/runtime/sessions.d.ts.map +1 -0
- package/runtime/sessions.js +433 -0
- package/runtime/state.d.ts +6 -2
- package/runtime/state.d.ts.map +1 -1
- package/runtime/state.js +46 -13
- package/runtime/toaster.d.ts +24 -0
- package/runtime/toaster.d.ts.map +1 -0
- package/runtime/toaster.js +286 -0
- package/runtime/ui.d.ts +6 -17
- package/runtime/ui.d.ts.map +1 -1
- package/runtime/ui.js +62 -118
- package/session/context/context.d.ts +12 -0
- package/session/context/context.d.ts.map +1 -0
- package/session/context/context.js +2 -0
- package/session/context/turn/message/document.d.ts +7 -0
- package/session/context/turn/message/document.d.ts.map +1 -0
- package/session/context/turn/message/document.js +1 -0
- package/session/context/turn/message/message.d.ts +55 -0
- package/session/context/turn/message/message.d.ts.map +1 -0
- package/session/context/turn/message/message.js +178 -0
- package/session/context/turn/message/note.d.ts +8 -0
- package/session/context/turn/message/note.d.ts.map +1 -0
- package/session/context/turn/message/note.js +1 -0
- package/session/context/turn/message/notice.d.ts +6 -0
- package/session/context/turn/message/notice.d.ts.map +1 -0
- package/session/context/turn/message/notice.js +1 -0
- package/session/context/turn/message/prompt.d.ts +8 -0
- package/session/context/turn/message/prompt.d.ts.map +1 -0
- package/session/context/turn/message/prompt.js +1 -0
- package/session/context/turn/message/reply.d.ts +6 -0
- package/session/context/turn/message/reply.d.ts.map +1 -0
- package/session/context/turn/message/reply.js +1 -0
- package/session/context/turn/message/sender.d.ts +14 -0
- package/session/context/turn/message/sender.d.ts.map +1 -0
- package/session/context/turn/message/sender.js +30 -0
- package/session/context/turn/message/skill.d.ts +7 -0
- package/session/context/turn/message/skill.d.ts.map +1 -0
- package/session/context/turn/message/skill.js +1 -0
- package/session/context/turn/message/tool-call.d.ts +9 -0
- package/session/context/turn/message/tool-call.d.ts.map +1 -0
- package/session/context/turn/message/tool-call.js +1 -0
- package/session/context/turn/message/tool-result.d.ts +8 -0
- package/session/context/turn/message/tool-result.d.ts.map +1 -0
- package/session/context/turn/message/tool-result.js +1 -0
- package/session/context/turn/turn.d.ts +13 -0
- package/session/context/turn/turn.d.ts.map +1 -0
- package/session/context/turn/turn.js +2 -0
- package/session/conversation.d.ts +73 -0
- package/session/conversation.d.ts.map +1 -0
- package/session/conversation.js +146 -0
- package/session/goal/goal.d.ts +11 -0
- package/session/goal/goal.d.ts.map +1 -0
- package/session/goal/goal.js +1 -0
- package/session/goal/task.d.ts +16 -0
- package/session/goal/task.d.ts.map +1 -0
- package/session/goal/task.js +3 -0
- package/session/session.d.ts +158 -0
- package/session/session.d.ts.map +1 -0
- package/session/session.js +2 -0
- package/src/jobs/check.ts +7 -4
- package/src/jobs/context.ts +24 -82
- package/src/jobs/permissions/decide-permission.ts +10 -8
- package/src/jobs/permissions/find-project-scope.ts +8 -14
- package/src/jobs/permissions/match-target.ts +3 -3
- package/src/jobs/permissions.ts +8 -6
- package/src/jobs/program.ts +13 -4
- package/src/jobs/prompt.ts +21 -24
- package/src/jobs/schedule.ts +14 -14
- package/src/jobs/slash-command.ts +20 -14
- package/src/jobs/status-line.ts +9 -21
- package/src/jobs/tool.ts +17 -11
- package/src/mod.ts +252 -97
- package/src/options.ts +22 -22
- package/src/records.ts +40 -23
- package/src/register.ts +34 -5
- package/src/runtime/announce.ts +76 -0
- package/src/runtime/before.ts +200 -0
- package/src/runtime/bound.ts +12 -0
- package/src/runtime/claude.ts +7 -2
- package/src/runtime/conversations.ts +579 -0
- package/src/runtime/deadline.ts +2 -2
- package/src/runtime/dependencies.ts +13 -1
- package/src/runtime/engine.ts +23 -0
- package/src/runtime/grants.ts +69 -29
- package/src/runtime/hooks.ts +39 -14
- package/src/runtime/installer.ts +16 -14
- package/src/runtime/lifecycle.ts +413 -201
- package/src/runtime/metadata.ts +37 -14
- package/src/runtime/options.ts +19 -4
- package/src/runtime/programs.ts +40 -1
- package/src/runtime/router.ts +11 -4
- package/src/runtime/sessions.ts +447 -0
- package/src/runtime/state.ts +50 -14
- package/src/runtime/toaster.ts +334 -0
- package/src/runtime/ui.ts +65 -143
- package/src/session/context/context.ts +14 -0
- package/src/session/context/turn/message/document.ts +8 -0
- package/src/session/context/turn/message/message.ts +203 -0
- package/src/session/context/turn/message/note.ts +9 -0
- package/src/session/context/turn/message/notice.ts +7 -0
- package/src/session/context/turn/message/prompt.ts +9 -0
- package/src/session/context/turn/message/reply.ts +7 -0
- package/src/session/context/turn/message/sender.ts +38 -0
- package/src/session/context/turn/message/skill.ts +8 -0
- package/src/session/context/turn/message/tool-call.ts +10 -0
- package/src/session/context/turn/message/tool-result.ts +9 -0
- package/src/session/context/turn/turn.ts +15 -0
- package/src/session/conversation.ts +194 -0
- package/src/session/goal/goal.ts +10 -0
- package/src/session/goal/task.ts +19 -0
- package/src/session/session.ts +141 -0
- package/src/testing/fake-claude.ts +25 -17
- package/src/testing/fake-elements.ts +3 -2
- package/src/testing/fake-files.ts +1 -1
- package/src/testing/fake-sessions.ts +277 -0
- package/src/testing.ts +138 -35
- package/src/ui/components.ts +3 -3
- package/src/ui/elements.ts +22 -8
- package/src/ui/markdown.ts +22 -26
- package/src/ui/places.ts +49 -0
- package/src/utils/call-effects.ts +5 -5
- package/src/utils/changelog.ts +32 -0
- package/src/utils/disposable.ts +3 -0
- package/src/utils/matcher.ts +37 -0
- package/src/utils/metadata.ts +17 -15
- package/src/utils/parse-shell.ts +9 -8
- package/src/utils/paths.ts +21 -4
- package/src/utils/text.ts +4 -0
- package/testing/fake-claude.d.ts +5 -1
- package/testing/fake-claude.d.ts.map +1 -1
- package/testing/fake-claude.js +21 -12
- package/testing/fake-elements.js +4 -2
- package/testing/fake-files.js +1 -1
- package/testing/fake-sessions.d.ts +81 -0
- package/testing/fake-sessions.d.ts.map +1 -0
- package/testing/fake-sessions.js +249 -0
- package/testing.d.ts +34 -13
- package/testing.d.ts.map +1 -1
- package/testing.js +115 -23
- package/ui/components.d.ts +2 -3
- package/ui/components.d.ts.map +1 -1
- package/ui/components.js +3 -3
- package/ui/elements.d.ts.map +1 -1
- package/ui/elements.js +19 -9
- package/ui/markdown.d.ts +35 -33
- package/ui/markdown.d.ts.map +1 -1
- package/ui/markdown.js +14 -16
- package/ui/places.d.ts +43 -0
- package/ui/places.d.ts.map +1 -0
- package/ui/places.js +31 -0
- package/utils/call-effects.d.ts +2 -1
- package/utils/call-effects.d.ts.map +1 -1
- package/utils/call-effects.js +3 -3
- package/utils/changelog.d.ts +9 -0
- package/utils/changelog.d.ts.map +1 -0
- package/utils/changelog.js +29 -0
- package/utils/disposable.d.ts +4 -0
- package/utils/disposable.d.ts.map +1 -0
- package/utils/disposable.js +1 -0
- package/utils/matcher.d.ts +13 -0
- package/utils/matcher.d.ts.map +1 -0
- package/utils/matcher.js +32 -0
- package/utils/metadata.d.ts +3 -3
- package/utils/metadata.js +18 -14
- package/utils/parse-shell.js +9 -7
- package/utils/paths.d.ts +4 -1
- package/utils/paths.d.ts.map +1 -1
- package/utils/paths.js +19 -4
- package/utils/text.d.ts +1 -0
- package/utils/text.d.ts.map +1 -1
- package/utils/text.js +3 -0
- package/src/ui/slots.ts +0 -36
- package/ui/slots.d.ts +0 -208
- package/ui/slots.d.ts.map +0 -1
- package/ui/slots.js +0 -19
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
import { isObject } from '../records.js';
|
|
2
|
+
import { Document } from '../session/context/turn/message/document.js';
|
|
3
|
+
import { Message, searchText, skillPrefix } from '../session/context/turn/message/message.js';
|
|
4
|
+
import { Note } from '../session/context/turn/message/note.js';
|
|
5
|
+
import { Notice } from '../session/context/turn/message/notice.js';
|
|
6
|
+
import { Prompt } from '../session/context/turn/message/prompt.js';
|
|
7
|
+
import { Reply } from '../session/context/turn/message/reply.js';
|
|
8
|
+
import { Mod, Person, Schedule } from '../session/context/turn/message/sender.js';
|
|
9
|
+
import { GoalChange } from '../session/goal/goal.js';
|
|
10
|
+
import { Task } from '../session/goal/task.js';
|
|
11
|
+
import { Skill } from '../session/context/turn/message/skill.js';
|
|
12
|
+
import { ToolCall } from '../session/context/turn/message/tool-call.js';
|
|
13
|
+
import { ToolResult } from '../session/context/turn/message/tool-result.js';
|
|
14
|
+
import { createConversation, emptyConversation, searchLimit } from '../session/conversation.js';
|
|
15
|
+
import { conversationMethods, everySession, isHeardBy, resultOf } from '../runtime/sessions.js';
|
|
16
|
+
import { receivedMatcher } from '../utils/matcher.js';
|
|
17
|
+
import { firstLine, messageOf } from '../utils/text.js';
|
|
18
|
+
export function fakeSessions({ project, given, push, callTool }) {
|
|
19
|
+
const kept = new Map();
|
|
20
|
+
const heard = new Map();
|
|
21
|
+
const watches = new Map();
|
|
22
|
+
const goals = new Map();
|
|
23
|
+
const sent = [];
|
|
24
|
+
let current = '';
|
|
25
|
+
let clock = Date.parse('2026-01-01T00:00:00.000Z');
|
|
26
|
+
let ids = 0;
|
|
27
|
+
const now = () => new Date((clock += 1000)).toISOString();
|
|
28
|
+
const keep = (data) => {
|
|
29
|
+
const session = { data, conversation: createConversation(emptyConversation(data.id)), messages: [], turns: new Map(), contexts: new Map(), tasks: new Map() };
|
|
30
|
+
kept.set(data.id, session);
|
|
31
|
+
return session;
|
|
32
|
+
};
|
|
33
|
+
const record = (session, changes) => {
|
|
34
|
+
for (const change of changes) {
|
|
35
|
+
if (change.type === Message)
|
|
36
|
+
session.messages.push(change.message);
|
|
37
|
+
if ('turn' in change)
|
|
38
|
+
session.turns.set(change.turn.prompt.id, change.turn);
|
|
39
|
+
if ('context' in change)
|
|
40
|
+
session.contexts.set(change.context.number, change.context);
|
|
41
|
+
if ('task' in change)
|
|
42
|
+
session.tasks.set(change.task.id, change.task);
|
|
43
|
+
session.data = { ...session.data, lastAt: change.type === Message ? change.message.at : session.data.lastAt, ...(session.data.title === '' && change.type === Message && change.message.type === Prompt && change.message.from.type === Person ? { title: firstLine(change.message.text) } : {}) };
|
|
44
|
+
for (const [mod, sessions] of heard) {
|
|
45
|
+
if ([...(sessions.get(session.data.id) ?? []), ...(sessions.get(everySession) ?? [])].some(({ event, matcher }) => isHeardBy(event, matcher, change)))
|
|
46
|
+
push(mod, session.data.id, change);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
const add = (session, draft, task) => {
|
|
51
|
+
const before = session.messages.length;
|
|
52
|
+
record(session, session.conversation.add(rowOf(draft, `message-${(ids += 1)}`, now()), task));
|
|
53
|
+
return session.messages.slice(before);
|
|
54
|
+
};
|
|
55
|
+
for (const each of given) {
|
|
56
|
+
const session = keep({ title: '', project: project(), cwd: project(), model: 'test-model', startedAt: now(), lastAt: '', isRunning: false, ...each });
|
|
57
|
+
for (const draft of each.messages ?? [])
|
|
58
|
+
add(session, draft);
|
|
59
|
+
if (each.goal !== undefined)
|
|
60
|
+
record(session, session.conversation.changeGoal(each.goal));
|
|
61
|
+
}
|
|
62
|
+
const sessionOf = (id) => {
|
|
63
|
+
const session = kept.get(id);
|
|
64
|
+
if (session === undefined)
|
|
65
|
+
throw new Error(`No Claude Code transcript is named ${id}.jsonl. Give it to testMod as { sessions: [{ id: '${id}' }] }.`);
|
|
66
|
+
return session;
|
|
67
|
+
};
|
|
68
|
+
const currentSession = () => sessionOf(current);
|
|
69
|
+
const place = () => {
|
|
70
|
+
const { session, context, contextOpenedAt, turn, tasks, taskTurns } = currentSession().conversation.state;
|
|
71
|
+
return { session, context, contextOpenedAt, ...(turn === undefined ? {} : { turn }), tasks, taskTurns };
|
|
72
|
+
};
|
|
73
|
+
const taskOf = (session, id) => {
|
|
74
|
+
const task = session.tasks.get(id);
|
|
75
|
+
if (task === undefined)
|
|
76
|
+
throw new Error(`Session ${session.data.id} has no task ${id}. Start it with tested.startTask.`);
|
|
77
|
+
return task;
|
|
78
|
+
};
|
|
79
|
+
const turnsOf = (session, task) => [...session.turns.values()].filter((turn) => turn.prompt.task === (typeof task === 'string' ? task : undefined));
|
|
80
|
+
const isUnder = (session, task, ancestor) => {
|
|
81
|
+
for (let at = task, steps = 0; at !== undefined && steps < 64; at = session?.tasks.get(at)?.parent, steps += 1)
|
|
82
|
+
if (at === ancestor)
|
|
83
|
+
return true;
|
|
84
|
+
return false;
|
|
85
|
+
};
|
|
86
|
+
const lastOf = (items, last) => (typeof last === 'number' ? items.slice(-last) : [...items]);
|
|
87
|
+
const isInScope = (session, request) => (request['everyProject'] === true || session.data.project === project()) && session.data.lastAt >= (typeof request['since'] === 'string' ? request['since'] : '') && (request['running'] !== true || session.data.isRunning);
|
|
88
|
+
const methods = {
|
|
89
|
+
[conversationMethods.sessions]: (request) => [...kept.values()]
|
|
90
|
+
.filter((session) => isInScope(session, request))
|
|
91
|
+
.map((session) => session.data)
|
|
92
|
+
.toSorted((first, second) => second.lastAt.localeCompare(first.lastAt)),
|
|
93
|
+
[conversationMethods.search](request) {
|
|
94
|
+
const text = String(request['text']);
|
|
95
|
+
if ([...text].length < 3)
|
|
96
|
+
throw new Error('cmod history search takes at least 3 characters.');
|
|
97
|
+
const inRecord = (message) => {
|
|
98
|
+
const session = kept.get(message.session);
|
|
99
|
+
const task = request['task'];
|
|
100
|
+
if (request['session'] !== undefined && message.session !== request['session'])
|
|
101
|
+
return false;
|
|
102
|
+
if (typeof task === 'string' && (message.task === undefined || !isUnder(session, message.task, task) || (request['isGoal'] === true && message.task === task)))
|
|
103
|
+
return false;
|
|
104
|
+
if (task === undefined && request['isGoal'] === true && message.task === undefined)
|
|
105
|
+
return false;
|
|
106
|
+
return (request['turn'] === undefined || message.turn === request['turn']) && (request['context'] === undefined || message.context === request['context']);
|
|
107
|
+
};
|
|
108
|
+
const found = [...kept.values()]
|
|
109
|
+
.filter((session) => request['session'] !== undefined || isInScope(session, request))
|
|
110
|
+
.flatMap((session) => session.messages)
|
|
111
|
+
.filter((message) => inRecord(message) && searchText(message).toLowerCase().includes(text.toLowerCase()))
|
|
112
|
+
.toSorted((first, second) => second.at.localeCompare(first.at));
|
|
113
|
+
const before = isObject(request['before']) ? request['before'] : undefined;
|
|
114
|
+
const start = before === undefined ? 0 : found.findIndex((message) => message.session === before['session'] && message.id === before['id']) + 1;
|
|
115
|
+
return found.slice(start, start + (typeof request['limit'] === 'number' ? request['limit'] : searchLimit));
|
|
116
|
+
},
|
|
117
|
+
[conversationMethods.messages](request) {
|
|
118
|
+
const session = sessionOf(String(request['session']));
|
|
119
|
+
const fits = session.messages.filter((message) => message.task === request['task'] && (request['turn'] === undefined || message.turn === request['turn']));
|
|
120
|
+
const after = typeof request['after'] === 'string' ? fits.findIndex((message) => message.id === request['after']) + 1 : 0;
|
|
121
|
+
return lastOf(fits.slice(after), request['last']);
|
|
122
|
+
},
|
|
123
|
+
[conversationMethods.turns]: (request) => lastOf(turnsOf(sessionOf(String(request['session'])), request['task']).filter((turn) => request['context'] === undefined || turn.context === request['context']), request['last']),
|
|
124
|
+
[conversationMethods.contexts](request) {
|
|
125
|
+
const session = sessionOf(String(request['session']));
|
|
126
|
+
if (typeof request['task'] !== 'string')
|
|
127
|
+
return [...session.contexts.values()];
|
|
128
|
+
const task = taskOf(session, request['task']);
|
|
129
|
+
return [{ number: session.conversation.state.tasks[task.id]?.context ?? 1, openedAt: task.openedAt, ...(task.closedAt === undefined ? {} : { closedAt: task.closedAt }) }];
|
|
130
|
+
},
|
|
131
|
+
[conversationMethods.tasks]: (request) => [...sessionOf(String(request['session'])).tasks.values()].filter((task) => request['all'] === true || task.parent === request['task']),
|
|
132
|
+
[conversationMethods.goal](request) {
|
|
133
|
+
const session = sessionOf(String(request['session']));
|
|
134
|
+
const task = request['task'];
|
|
135
|
+
if (typeof task !== 'string')
|
|
136
|
+
return { text: session.conversation.state.goal ?? '' };
|
|
137
|
+
return { text: goals.get(task) ?? turnsOf(session, task)[0]?.prompt.text ?? '' };
|
|
138
|
+
},
|
|
139
|
+
[conversationMethods.updateGoal](request) {
|
|
140
|
+
const session = sessionOf(String(request['session']));
|
|
141
|
+
const task = typeof request['task'] === 'string' ? request['task'] : undefined;
|
|
142
|
+
const text = String(request['text']);
|
|
143
|
+
if (task === undefined)
|
|
144
|
+
record(session, session.conversation.changeGoal(text));
|
|
145
|
+
else {
|
|
146
|
+
goals.set(task, text);
|
|
147
|
+
record(session, [{ type: GoalChange, goal: { text, task } }]);
|
|
148
|
+
}
|
|
149
|
+
return null;
|
|
150
|
+
},
|
|
151
|
+
[conversationMethods.watchBefore](request, caller) {
|
|
152
|
+
const targets = watches.get(caller) ?? new Map();
|
|
153
|
+
targets.set(String(request['session']), (Array.isArray(request['subscriptions']) ? request['subscriptions'] : []).map(({ event }) => event));
|
|
154
|
+
watches.set(caller, targets);
|
|
155
|
+
return null;
|
|
156
|
+
},
|
|
157
|
+
async [conversationMethods.act](request) {
|
|
158
|
+
const session = sessionOf(String(request['session']));
|
|
159
|
+
const action = String(request['action']);
|
|
160
|
+
const input = isObject(request['input']) ? request['input'] : {};
|
|
161
|
+
const task = typeof request['task'] === 'string' ? request['task'] : undefined;
|
|
162
|
+
const isLive = task === undefined ? session.data.isRunning : session.data.isRunning && taskOf(session, task).closedAt === undefined;
|
|
163
|
+
if (action === 'usage')
|
|
164
|
+
return { model: task === undefined ? session.data.model : (taskOf(session, task).model ?? session.data.model), context: {} };
|
|
165
|
+
if (action === 'clients')
|
|
166
|
+
return isLive ? ['terminal'] : [];
|
|
167
|
+
if (!isLive)
|
|
168
|
+
throw new Error(`session ${task ?? session.data.id} has ended`);
|
|
169
|
+
if (action === 'send') {
|
|
170
|
+
sent.push({ session: session.data.id, ...(task === undefined ? {} : { task }), type: String(input['type']), text: String(input['text']), ...(typeof input['as'] === 'string' ? { as: input['as'] } : {}) });
|
|
171
|
+
return null;
|
|
172
|
+
}
|
|
173
|
+
if (action === 'startTask') {
|
|
174
|
+
const id = `task-${(ids += 1)}`;
|
|
175
|
+
const data = { id, title: typeof input['name'] === 'string' ? input['name'] : '', agentType: typeof input['agentType'] === 'string' ? input['agentType'] : 'general-purpose', openedBy: '', openedAt: now(), ...(typeof input['model'] === 'string' ? { model: input['model'] } : {}) };
|
|
176
|
+
record(session, session.conversation.startTask(data));
|
|
177
|
+
return data;
|
|
178
|
+
}
|
|
179
|
+
if (session.data.id === current)
|
|
180
|
+
return resultOf(place(), await callTool({ ...input['input'], tool: String(input['name']) }));
|
|
181
|
+
return { id: '', type: ToolResult, at: now(), session: session.data.id, context: session.conversation.state.context, callId: '', text: '', isError: false };
|
|
182
|
+
},
|
|
183
|
+
[conversationMethods.subscribe](request, caller) {
|
|
184
|
+
const list = Array.isArray(request['subscriptions']) ? request['subscriptions'] : [];
|
|
185
|
+
const sessions = heard.get(caller) ?? new Map();
|
|
186
|
+
sessions.set(String(request['session']), list.map(({ event, matcher }) => ({ event, matcher: matcher === undefined ? undefined : receivedMatcher(matcher) })));
|
|
187
|
+
heard.set(caller, sessions);
|
|
188
|
+
return null;
|
|
189
|
+
},
|
|
190
|
+
};
|
|
191
|
+
return {
|
|
192
|
+
answer(caller, method, input) {
|
|
193
|
+
const run = methods[method];
|
|
194
|
+
if (run === undefined)
|
|
195
|
+
return undefined;
|
|
196
|
+
try {
|
|
197
|
+
return { value: run(isObject(input) ? input : {}, caller) };
|
|
198
|
+
}
|
|
199
|
+
catch (error) {
|
|
200
|
+
return { deny: messageOf(error) };
|
|
201
|
+
}
|
|
202
|
+
},
|
|
203
|
+
add: (draft, task) => add(currentSession(), draft, task),
|
|
204
|
+
place,
|
|
205
|
+
wants: (mod, event) => [current, everySession].some((target) => (watches.get(mod)?.get(target) ?? []).includes(event)),
|
|
206
|
+
endTurn() {
|
|
207
|
+
const session = currentSession();
|
|
208
|
+
record(session, session.conversation.add({ id: `turn-end-${(ids += 1)}`, at: now(), type: 'system', name: 'turn_duration', isMeta: false, isSummary: false, content: [] }));
|
|
209
|
+
},
|
|
210
|
+
compact() {
|
|
211
|
+
const session = currentSession();
|
|
212
|
+
record(session, session.conversation.add({ id: `compact-${(ids += 1)}`, at: now(), type: 'system', name: 'compact_boundary', isMeta: false, isSummary: false, content: [] }));
|
|
213
|
+
},
|
|
214
|
+
startTask({ title, agentType = 'general-purpose', model, openedBy = '', parent }) {
|
|
215
|
+
const session = currentSession();
|
|
216
|
+
const id = `task-${(ids += 1)}`;
|
|
217
|
+
record(session, session.conversation.startTask({ id, title, agentType, openedBy, openedAt: now(), ...(model === undefined ? {} : { model }), ...(parent === undefined ? {} : { parent }) }));
|
|
218
|
+
return id;
|
|
219
|
+
},
|
|
220
|
+
start(id) {
|
|
221
|
+
current = id;
|
|
222
|
+
const session = kept.get(id) ?? keep({ id, title: '', project: project(), cwd: project(), model: 'test-model', startedAt: now(), lastAt: now(), isRunning: true });
|
|
223
|
+
session.data = { ...session.data, isRunning: true };
|
|
224
|
+
if (session.conversation.state.context === 0)
|
|
225
|
+
record(session, session.conversation.startContext(now()));
|
|
226
|
+
},
|
|
227
|
+
sent,
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
function rowOf(draft, id, at) {
|
|
231
|
+
const base = { id, at, isMeta: false, isSummary: false };
|
|
232
|
+
const text = (value) => [{ type: 'text', text: value }];
|
|
233
|
+
const origin = (from) => (from === undefined || from.type === Person ? { kind: 'human' } : from.type === Mod ? { kind: 'plugin', name: from.name, ...(from.as === Person ? { asUser: true } : {}) } : from.type === 'session' ? { kind: 'peer' } : from.type === Task ? { kind: 'task-notification' } : { kind: from.type === Schedule ? 'scheduled-trigger' : from.type, ...(from.name === undefined ? {} : { server: from.name }) });
|
|
234
|
+
if (draft.type === Prompt)
|
|
235
|
+
return { ...base, type: 'user', content: text(draft.text), origin: origin(draft.from) };
|
|
236
|
+
if (draft.type === Note)
|
|
237
|
+
return { ...base, type: 'user', isMeta: true, content: text(draft.text), origin: origin(draft.from ?? { type: Mod, name: 'test' }) };
|
|
238
|
+
if (draft.type === Reply)
|
|
239
|
+
return { ...base, type: 'assistant', content: text(draft.text) };
|
|
240
|
+
if (draft.type === ToolCall)
|
|
241
|
+
return { ...base, type: 'assistant', content: [{ type: 'tool_use', id: draft.callId ?? `toolu_${id}`, name: draft.name, input: draft.input ?? {} }], cwd: '/test/project' };
|
|
242
|
+
if (draft.type === ToolResult)
|
|
243
|
+
return { ...base, type: 'user', content: [{ type: 'tool_result', tool_use_id: draft.callId, content: draft.text, ...(draft.isError === true ? { is_error: true } : {}) }] };
|
|
244
|
+
if (draft.type === Skill)
|
|
245
|
+
return { ...base, type: 'user', isMeta: true, content: text(`${skillPrefix} /skills/${draft.name}\n\n${draft.text}`) };
|
|
246
|
+
if (draft.type === Document)
|
|
247
|
+
return { ...base, type: 'attachment', name: 'nested_memory', content: text(draft.text), path: draft.path };
|
|
248
|
+
return { ...base, type: 'system', name: 'informational', content: text(draft.text) };
|
|
249
|
+
}
|
package/testing.d.ts
CHANGED
|
@@ -1,11 +1,21 @@
|
|
|
1
|
-
import type { Args, ClassicHookInputs, CmodDependencies, EventResult,
|
|
2
|
-
import type {
|
|
3
|
-
import type {
|
|
1
|
+
import type { Args, ClassicHookInputs, CmodDependencies, EventResult, UiInputArgument } from 'claude-code';
|
|
2
|
+
import type { CommandReply } from './jobs/slash-command.js';
|
|
3
|
+
import type { Hook, ModDefinition } from './mod.js';
|
|
4
4
|
import type { Options, OptionValues } from './options.js';
|
|
5
5
|
import type { RoutedEvent } from './runtime/hooks.js';
|
|
6
|
-
import
|
|
7
|
-
import type {
|
|
8
|
-
|
|
6
|
+
import type { ShownToast } from './runtime/toaster.js';
|
|
7
|
+
import type { Message } from './session/context/turn/message/message.js';
|
|
8
|
+
import { type Fakes, type Shown as ShownByClaude, type TestCall } from './testing/fake-claude.js';
|
|
9
|
+
import { type FakeSession, type FakeSessions, type MessageDraft } from './testing/fake-sessions.js';
|
|
10
|
+
import { type ComponentPlace, type PlaceProps } from './ui/places.js';
|
|
11
|
+
export type { Fakes, TestCall } from './testing/fake-claude.js';
|
|
12
|
+
export type { FakeSession, MessageDraft } from './testing/fake-sessions.js';
|
|
13
|
+
export type { RoutedEvent } from './runtime/hooks.js';
|
|
14
|
+
export type { ShownToast } from './runtime/toaster.js';
|
|
15
|
+
export type Shown = ShownByClaude & {
|
|
16
|
+
readonly toasts: readonly ShownToast[];
|
|
17
|
+
readonly sent: FakeSessions['sent'];
|
|
18
|
+
};
|
|
9
19
|
export type TestOptions<State extends object, Declared extends Options = Options> = {
|
|
10
20
|
readonly state?: {
|
|
11
21
|
readonly [Lifetime in keyof State]?: Partial<State[Lifetime]>;
|
|
@@ -20,22 +30,33 @@ export type TestOptions<State extends object, Declared extends Options = Options
|
|
|
20
30
|
};
|
|
21
31
|
readonly files?: Readonly<Record<string, string>>;
|
|
22
32
|
readonly links?: Readonly<Record<string, string>>;
|
|
33
|
+
readonly sessions?: readonly FakeSession[];
|
|
23
34
|
};
|
|
24
35
|
type FilledField = 'session_id' | 'transcript_path' | 'cwd' | 'hook_event_name' | 'tool_use_id';
|
|
25
|
-
export type TestInput<
|
|
36
|
+
export type TestInput<H extends Hook> = Omit<ClassicHookInputs[H], FilledField> & Partial<Pick<ClassicHookInputs[H], FilledField & keyof ClassicHookInputs[H]>>;
|
|
26
37
|
export type TestedMod<State extends object> = {
|
|
27
38
|
start(): Promise<void>;
|
|
28
|
-
fire<
|
|
39
|
+
fire<H extends Hook>(hook: H, input: TestInput<H>, below?: EventResult<`classic.${H}`>): Promise<EventResult<`classic.${H}`>>;
|
|
29
40
|
fire<N extends RoutedEvent>(event: N, input: Args<N>, below?: EventResult<N>): Promise<EventResult<N>>;
|
|
30
41
|
settle(): Promise<void>;
|
|
42
|
+
add(message: MessageDraft, options?: {
|
|
43
|
+
readonly task?: string;
|
|
44
|
+
}): Promise<Message[]>;
|
|
45
|
+
endTurn(): Promise<void>;
|
|
46
|
+
compact(): Promise<void>;
|
|
47
|
+
startTask(task: {
|
|
48
|
+
readonly title: string;
|
|
49
|
+
readonly agentType?: string;
|
|
50
|
+
readonly model?: string;
|
|
51
|
+
readonly openedBy?: string;
|
|
52
|
+
readonly parent?: string;
|
|
53
|
+
}): Promise<string>;
|
|
31
54
|
lines(paneId: string): Promise<string[]>;
|
|
32
|
-
lines<
|
|
33
|
-
|
|
34
|
-
}>>(slot: S, props: Omit<SlotProps<S>, 'Default'>, requestId?: string): Promise<string[]>;
|
|
35
|
-
type(line: string): Promise<Reply>;
|
|
55
|
+
lines<P extends ComponentPlace>(place: P, props: Omit<PlaceProps<P>, 'Default'>, requestId?: string): Promise<string[]>;
|
|
56
|
+
type(line: string): Promise<CommandReply>;
|
|
36
57
|
callTool(name: string, input: Record<string, unknown>): Promise<EventResult<'tool.call'>>;
|
|
37
58
|
press(paneId: string, key: string, link?: string): Promise<void>;
|
|
38
|
-
input(paneId: string, key: string, text: string,
|
|
59
|
+
input(paneId: string, key: string, text: string, type?: UiInputArgument['kind']): Promise<void>;
|
|
39
60
|
select(paneId: string, key: string, value: string): Promise<void>;
|
|
40
61
|
moveTo(projectRoot: string, cwd?: string): Promise<void>;
|
|
41
62
|
readonly state: Readonly<State>;
|
package/testing.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"testing.d.ts","sourceRoot":"","sources":["src/testing.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,IAAI,EACJ,iBAAiB,EACjB,gBAAgB,EAGhB,WAAW,
|
|
1
|
+
{"version":3,"file":"testing.d.ts","sourceRoot":"","sources":["src/testing.ts"],"names":[],"mappings":"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"}
|
package/testing.js
CHANGED
|
@@ -1,12 +1,20 @@
|
|
|
1
1
|
import { readSteps, storeFolder } from './records.js';
|
|
2
|
-
import { answerCall, notInstalled } from './runtime/dependencies.js';
|
|
2
|
+
import { answerCall, cmodPluginName, notInstalled } from './runtime/dependencies.js';
|
|
3
|
+
import { foldAnswers, installBefore } from './runtime/before.js';
|
|
4
|
+
import { createRouter } from './runtime/router.js';
|
|
3
5
|
import { toolInputOf } from './runtime/tool-calls.js';
|
|
4
6
|
import { createLifecycle } from './runtime/lifecycle.js';
|
|
5
7
|
import { MissingOptions } from './runtime/options.js';
|
|
6
|
-
import {
|
|
8
|
+
import { conversationMethods } from './runtime/sessions.js';
|
|
9
|
+
import { Note } from './session/context/turn/message/note.js';
|
|
10
|
+
import { Prompt } from './session/context/turn/message/prompt.js';
|
|
11
|
+
import { Mod, Person } from './session/context/turn/message/sender.js';
|
|
12
|
+
import { calledAnswer, fakeClaude } from './testing/fake-claude.js';
|
|
7
13
|
import { elements, findElement, rowsOf } from './testing/fake-elements.js';
|
|
8
14
|
import { fakeFiles } from './testing/fake-files.js';
|
|
9
|
-
import {
|
|
15
|
+
import { fakeSessions } from './testing/fake-sessions.js';
|
|
16
|
+
import { listed, messageOf } from './utils/text.js';
|
|
17
|
+
import { componentOf } from './ui/places.js';
|
|
10
18
|
const defaultColumns = 80;
|
|
11
19
|
const defaultRows = 24;
|
|
12
20
|
const presentation = { isFullscreen: false, columns: defaultColumns };
|
|
@@ -26,7 +34,32 @@ export function testMod(definition, options = {}) {
|
|
|
26
34
|
fake.claude.session.id = async () => sessionId;
|
|
27
35
|
fake.claude.session.root = async () => projectRoot;
|
|
28
36
|
fake.claude.session.cwd = async () => cwd;
|
|
29
|
-
|
|
37
|
+
const conversations = fakeSessions({
|
|
38
|
+
callTool: (args) => fake.claude.tool.call(args),
|
|
39
|
+
project: () => projectRoot,
|
|
40
|
+
given: options.sessions ?? [],
|
|
41
|
+
push: (mod, session, change) => {
|
|
42
|
+
if (mod !== name)
|
|
43
|
+
return;
|
|
44
|
+
const pushed = { to: mod, method: conversationMethods.changed, input: { session, change }, caller: cmodPluginName };
|
|
45
|
+
void lifecycle.route('cmod.call', pushed, async () => ({ deny: notInstalled(mod) }));
|
|
46
|
+
},
|
|
47
|
+
});
|
|
48
|
+
conversations.start(sessionId);
|
|
49
|
+
fake.fakes.cmod.call = fakeDependencies(name, options.dependencies ?? {}, conversations);
|
|
50
|
+
const appendRow = fake.claude.session.append;
|
|
51
|
+
fake.claude.session.append = async (args) => {
|
|
52
|
+
const added = await appendRow(args);
|
|
53
|
+
if (added.deny === undefined)
|
|
54
|
+
conversations.add({ type: Note, text: args.message.content.map((block) => (typeof block['text'] === 'string' ? block['text'] : '')).join(''), from: { type: Mod, name } });
|
|
55
|
+
return added;
|
|
56
|
+
};
|
|
57
|
+
const submitRow = fake.claude.prompt.submit;
|
|
58
|
+
fake.claude.prompt.submit = async (args) => {
|
|
59
|
+
const submitted = await submitRow(args);
|
|
60
|
+
conversations.add({ type: Prompt, text: args.text, from: { type: Mod, name, ...(args.asUser === true ? { as: Person } : {}) } });
|
|
61
|
+
return submitted;
|
|
62
|
+
};
|
|
30
63
|
const firedAgents = new Map();
|
|
31
64
|
const listAgents = fake.claude.agent.list;
|
|
32
65
|
fake.claude.agent.list = async () => {
|
|
@@ -34,7 +67,8 @@ export function testMod(definition, options = {}) {
|
|
|
34
67
|
return listAgents();
|
|
35
68
|
return [...firedAgents].map(([id, type]) => ({ id, type, description: '', status: 'running' }));
|
|
36
69
|
};
|
|
37
|
-
|
|
70
|
+
const files = fakeFiles(options.files ?? {}, options.links);
|
|
71
|
+
Object.assign(fake.fakes.fs, files);
|
|
38
72
|
const starting = options.state;
|
|
39
73
|
const declared = Object.entries(definition.state ?? {});
|
|
40
74
|
const state = Object.fromEntries(declared.map(([lifetime, values]) => [lifetime, { ...values, ...starting?.[lifetime] }]));
|
|
@@ -55,6 +89,7 @@ export function testMod(definition, options = {}) {
|
|
|
55
89
|
};
|
|
56
90
|
});
|
|
57
91
|
await started;
|
|
92
|
+
await fake.settle();
|
|
58
93
|
const { failure } = lifecycle;
|
|
59
94
|
if (failure instanceof MissingOptions) {
|
|
60
95
|
const given = failure.keys.map((key) => `${key}: …`).join(', ');
|
|
@@ -63,9 +98,23 @@ export function testMod(definition, options = {}) {
|
|
|
63
98
|
if (lifecycle.phase !== 'active')
|
|
64
99
|
throw failure ?? new Error(`${name} did not start: the lifecycle is ${lifecycle.phase}.`);
|
|
65
100
|
};
|
|
101
|
+
const cmodPlugin = createRouter();
|
|
102
|
+
const askMod = async (event, change) => {
|
|
103
|
+
const asked = { to: name, method: conversationMethods.before, input: { session: sessionId, event, change }, caller: cmodPluginName };
|
|
104
|
+
return calledAnswer(cmodPluginName, await lifecycle.route('cmod.call', asked, async () => ({ deny: notInstalled(name) })));
|
|
105
|
+
};
|
|
106
|
+
installBefore({
|
|
107
|
+
claude: fake.claude,
|
|
108
|
+
router: cmodPlugin,
|
|
109
|
+
hooks: {
|
|
110
|
+
place: async () => conversations.place(),
|
|
111
|
+
wants: async (event) => conversations.wants(name, event),
|
|
112
|
+
decide: (event, change) => foldAnswers(event, change, [(latest) => askMod(event, latest)], (error) => fake.claude.ui.log(`cmod: passed the ${event} on without ${name}'s answer: ${messageOf(error)}`, { to: 'debug' })),
|
|
113
|
+
},
|
|
114
|
+
});
|
|
66
115
|
const route = async (event, input, below) => {
|
|
67
116
|
await start();
|
|
68
|
-
return lifecycle.route(event,
|
|
117
|
+
return cmodPlugin.dispatch(event, input, (passed) => lifecycle.route(event, passed, async (e) => (below ?? claudeCodeAnswer(event, e))));
|
|
69
118
|
};
|
|
70
119
|
const drawPane = async (paneId) => {
|
|
71
120
|
await start();
|
|
@@ -96,14 +145,19 @@ export function testMod(definition, options = {}) {
|
|
|
96
145
|
const fire = ((event, input, below) => {
|
|
97
146
|
if (event.includes('.'))
|
|
98
147
|
return route(event, input, below);
|
|
99
|
-
if (event === 'SessionStart')
|
|
148
|
+
if (event === 'SessionStart') {
|
|
100
149
|
sessionId = input.session_id ?? sessionId;
|
|
150
|
+
conversations.start(sessionId);
|
|
151
|
+
}
|
|
101
152
|
if (event === 'UserPromptSubmit') {
|
|
102
153
|
cwd = cwdAfterPrompt ?? cwd;
|
|
103
154
|
cwdAfterPrompt = undefined;
|
|
104
155
|
}
|
|
105
156
|
const toolUse = toolUseEvents.includes(event) ? { tool_use_id: `toolu_${(toolUses += 1)}` } : {};
|
|
106
|
-
const
|
|
157
|
+
const transcript = `/test/transcripts/${sessionId}.jsonl`;
|
|
158
|
+
const filled = { session_id: sessionId, transcript_path: transcript, cwd, hook_event_name: event, ...toolUse, ...input };
|
|
159
|
+
if (event === 'SessionStart' && !('transcript_path' in input))
|
|
160
|
+
return files.write(transcript, '').then(() => route('classic.SessionStart', filled, below));
|
|
107
161
|
if (event !== 'PreToolUse')
|
|
108
162
|
return route(`classic.${event}`, filled, below);
|
|
109
163
|
return firePreToolUse(filled, below);
|
|
@@ -119,7 +173,7 @@ export function testMod(definition, options = {}) {
|
|
|
119
173
|
const answer = await lifecycle.route('tool.call', envelope, async (e) => {
|
|
120
174
|
reached = e;
|
|
121
175
|
check = await lifecycle.route('tool.check', { tool: e.tool, input: toolInputOf(e), tool_use_id: e.tool_use_id, ...(e.agentId === undefined ? {} : { agentId: e.agentId }) }, async () => asks);
|
|
122
|
-
return
|
|
176
|
+
return check.decision === 'deny' ? { deny: check.reason ?? '' } : { result: '' };
|
|
123
177
|
});
|
|
124
178
|
const updatedInput = toolInputOf(reached);
|
|
125
179
|
const decided = check === asks ? {} : check.decision === 'allow' ? { allow: true } : check.decision === 'ask' ? { ask: check.reason ?? '' } : {};
|
|
@@ -129,14 +183,37 @@ export function testMod(definition, options = {}) {
|
|
|
129
183
|
...(answer.context === undefined || answer.context.length === 0 ? {} : { additionalContext: [...answer.context] }),
|
|
130
184
|
};
|
|
131
185
|
};
|
|
186
|
+
const settled = async (value) => {
|
|
187
|
+
await fake.settle();
|
|
188
|
+
return value;
|
|
189
|
+
};
|
|
132
190
|
return {
|
|
133
191
|
start,
|
|
134
192
|
fire,
|
|
135
193
|
settle: fake.settle,
|
|
136
|
-
|
|
137
|
-
|
|
194
|
+
add: async (message, added) => {
|
|
195
|
+
await start();
|
|
196
|
+
return settled(conversations.add(message, added?.task));
|
|
197
|
+
},
|
|
198
|
+
endTurn: async () => {
|
|
199
|
+
await start();
|
|
200
|
+
await settled(conversations.endTurn());
|
|
201
|
+
},
|
|
202
|
+
compact: async () => {
|
|
203
|
+
await start();
|
|
204
|
+
await settled(conversations.compact());
|
|
205
|
+
},
|
|
206
|
+
startTask: async (task) => {
|
|
207
|
+
await start();
|
|
208
|
+
return settled(conversations.startTask(task));
|
|
209
|
+
},
|
|
210
|
+
lines: async (target, props, requestId) => {
|
|
211
|
+
const component = componentOf(target);
|
|
212
|
+
if (props !== undefined && component === undefined)
|
|
213
|
+
throw new Error(`lines takes a pane id, or a place render draws and its props, and ${target} is neither a place such as ToolCall nor called without props.`);
|
|
214
|
+
const { drawing, columns } = props === undefined || component === undefined ? await drawPane(target) : await drawSlot(component, props, requestId);
|
|
138
215
|
return rowsOf(drawing, columns).map((row) => row.trimEnd());
|
|
139
|
-
}
|
|
216
|
+
},
|
|
140
217
|
async type(line) {
|
|
141
218
|
const typed = /^\/(\S+)\s*(.*)$/s.exec(line);
|
|
142
219
|
if (typed === null)
|
|
@@ -175,10 +252,10 @@ export function testMod(definition, options = {}) {
|
|
|
175
252
|
return { element: e.element };
|
|
176
253
|
});
|
|
177
254
|
},
|
|
178
|
-
async input(paneId, key, text,
|
|
255
|
+
async input(paneId, key, text, type = 'submit') {
|
|
179
256
|
const props = await drawnElement(paneId, 'Input', key);
|
|
180
|
-
const typed = { ...eventIn(paneId, key), kind, value: text };
|
|
181
|
-
await props[
|
|
257
|
+
const typed = { ...eventIn(paneId, key), kind: type, value: text };
|
|
258
|
+
await props[type === 'submit' ? 'onSubmit' : 'onInput']?.(text, typed);
|
|
182
259
|
},
|
|
183
260
|
async select(paneId, key, value) {
|
|
184
261
|
const props = await drawnElement(paneId, 'Select', key);
|
|
@@ -211,20 +288,31 @@ export function testMod(definition, options = {}) {
|
|
|
211
288
|
},
|
|
212
289
|
calls: fake.calls,
|
|
213
290
|
fakes: fake.fakes,
|
|
214
|
-
shown:
|
|
291
|
+
shown: {
|
|
292
|
+
...fake.shown,
|
|
293
|
+
get toasts() {
|
|
294
|
+
return lifecycle.toasts;
|
|
295
|
+
},
|
|
296
|
+
sent: conversations.sent,
|
|
297
|
+
},
|
|
215
298
|
};
|
|
216
299
|
}
|
|
217
|
-
function fakeDependencies(dependencies) {
|
|
218
|
-
const
|
|
300
|
+
function fakeDependencies(caller, dependencies, conversations) {
|
|
301
|
+
const cmod = { forkParent: () => null, updateSettings: () => null };
|
|
302
|
+
const apis = { [cmodPluginName]: cmod, ...dependencies };
|
|
219
303
|
return async (call) => {
|
|
304
|
+
const served = call.to === cmodPluginName ? conversations.answer(caller, call.method, call.input) : undefined;
|
|
305
|
+
if (served !== undefined)
|
|
306
|
+
return calledAnswer(caller, served);
|
|
220
307
|
const api = apis[call.to];
|
|
221
|
-
|
|
222
|
-
if (answer.deny !== undefined)
|
|
223
|
-
throw new Error(answer.deny);
|
|
224
|
-
return answer.value;
|
|
308
|
+
return calledAnswer(caller, api === undefined ? { deny: notInstalled(call.to) } : await answerCall(call.to, api, call));
|
|
225
309
|
};
|
|
226
310
|
}
|
|
227
311
|
function claudeCodeAnswer(event, e) {
|
|
312
|
+
if (event === 'agent.spawn') {
|
|
313
|
+
const { model, parentModel } = e;
|
|
314
|
+
return { agentId: 'agent-1', model: model ?? parentModel };
|
|
315
|
+
}
|
|
228
316
|
if (event !== 'prompt.submit')
|
|
229
317
|
return {};
|
|
230
318
|
const { text, context, origin } = e;
|
|
@@ -239,7 +327,11 @@ function replyOf({ text, context }) {
|
|
|
239
327
|
}
|
|
240
328
|
async function modSteps() {
|
|
241
329
|
const manifest = '../../../package.json';
|
|
242
|
-
const loaded = await import(manifest, { with: { type: 'json' } }).then((module) => module.default, () =>
|
|
330
|
+
const loaded = await import(manifest, { with: { type: 'json' } }).then((module) => module.default, (error) => {
|
|
331
|
+
if (error.code === 'ERR_MODULE_NOT_FOUND')
|
|
332
|
+
return undefined;
|
|
333
|
+
throw error;
|
|
334
|
+
});
|
|
243
335
|
return readSteps(loaded) ?? {};
|
|
244
336
|
}
|
|
245
337
|
function installJsx() {
|
package/ui/components.d.ts
CHANGED
|
@@ -40,9 +40,8 @@ export declare function Pagination({ page, pages, onPage }: {
|
|
|
40
40
|
readonly pages: number;
|
|
41
41
|
readonly onPage: (page: number) => void;
|
|
42
42
|
}): RenderElement;
|
|
43
|
-
export declare function
|
|
44
|
-
readonly
|
|
45
|
-
readonly total: number;
|
|
43
|
+
export declare function Progress({ value, width }: {
|
|
44
|
+
readonly value: number;
|
|
46
45
|
readonly width?: number;
|
|
47
46
|
}): RenderElement;
|
|
48
47
|
//# sourceMappingURL=components.d.ts.map
|
package/ui/components.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"components.d.ts","sourceRoot":"","sources":["../src/ui/components.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAGhE,MAAM,MAAM,GAAG,GAAG;IAAE,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAA;AAElE,wBAAgB,IAAI,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,QAAQ,EAAE,EAAE;IAAE,QAAQ,CAAC,IAAI,EAAE,SAAS,GAAG,EAAE,CAAC;IAAC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,QAAQ,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,cAAc,CAAA;CAAE,GAAG,aAAa,CAQtN;AAED,wBAAgB,KAAK,CAAC,EAAE,QAAQ,EAAE,EAAE;IAAE,QAAQ,CAAC,QAAQ,EAAE,SAAS,aAAa,EAAE,CAAA;CAAE,GAAG,aAAa,CAGlG;AAED,wBAAgB,KAAK,CAAC,EAAE,KAAK,EAAE,QAAQ,EAAE,EAAE;IAAE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,cAAc,CAAA;CAAE,GAAG,aAAa,CAExH;AAED,wBAAgB,MAAM,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,EAAE;IAAE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,QAAQ,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,CAAA;CAAE,GAAG,aAAa,CAExK;AAED,wBAAgB,OAAO,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,cAAc,CAAA;CAAE,GAAG,aAAa,CAExH;AAED,wBAAgB,IAAI,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,YAAY,EAAE,EAAE;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,YAAY,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,IAAI,CAAA;CAAE,GAAG,aAAa,CAEzK;AAED,wBAAgB,MAAM,CAAC,EAAE,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,EAAE;IAAE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,cAAc,CAAC;IAAC,QAAQ,CAAC,OAAO,EAAE,cAAc,CAAA;CAAE,GAAG,aAAa,CAEpK;AAED,wBAAgB,UAAU,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAA;CAAE,GAAG,aAAa,CAS7J;AAED,wBAAgB,
|
|
1
|
+
{"version":3,"file":"components.d.ts","sourceRoot":"","sources":["../src/ui/components.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAGhE,MAAM,MAAM,GAAG,GAAG;IAAE,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAA;AAElE,wBAAgB,IAAI,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,QAAQ,EAAE,EAAE;IAAE,QAAQ,CAAC,IAAI,EAAE,SAAS,GAAG,EAAE,CAAC;IAAC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,QAAQ,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,cAAc,CAAA;CAAE,GAAG,aAAa,CAQtN;AAED,wBAAgB,KAAK,CAAC,EAAE,QAAQ,EAAE,EAAE;IAAE,QAAQ,CAAC,QAAQ,EAAE,SAAS,aAAa,EAAE,CAAA;CAAE,GAAG,aAAa,CAGlG;AAED,wBAAgB,KAAK,CAAC,EAAE,KAAK,EAAE,QAAQ,EAAE,EAAE;IAAE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,cAAc,CAAA;CAAE,GAAG,aAAa,CAExH;AAED,wBAAgB,MAAM,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,EAAE;IAAE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,QAAQ,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,CAAA;CAAE,GAAG,aAAa,CAExK;AAED,wBAAgB,OAAO,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,cAAc,CAAA;CAAE,GAAG,aAAa,CAExH;AAED,wBAAgB,IAAI,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,YAAY,EAAE,EAAE;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,YAAY,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,IAAI,CAAA;CAAE,GAAG,aAAa,CAEzK;AAED,wBAAgB,MAAM,CAAC,EAAE,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,EAAE;IAAE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,cAAc,CAAC;IAAC,QAAQ,CAAC,OAAO,EAAE,cAAc,CAAA;CAAE,GAAG,aAAa,CAEpK;AAED,wBAAgB,UAAU,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAA;CAAE,GAAG,aAAa,CAS7J;AAED,wBAAgB,QAAQ,CAAC,EAAE,KAAK,EAAE,KAAU,EAAE,EAAE;IAAE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,aAAa,CAGlH"}
|
package/ui/components.js
CHANGED
|
@@ -37,9 +37,9 @@ export function Pagination({ page, pages, onPage }) {
|
|
|
37
37
|
],
|
|
38
38
|
});
|
|
39
39
|
}
|
|
40
|
-
export function
|
|
41
|
-
const filled =
|
|
42
|
-
return Text({ children: [Text({ color: 'claude', children: '█'.repeat(filled) }), Text({ color: 'subtle', children: '░'.repeat(width - filled) })
|
|
40
|
+
export function Progress({ value, width = 24 }) {
|
|
41
|
+
const filled = Math.round((Math.min(Math.max(value, 0), 100) / 100) * width);
|
|
42
|
+
return Text({ children: [Text({ color: 'claude', children: '█'.repeat(filled) }), Text({ color: 'subtle', children: '░'.repeat(width - filled) })] });
|
|
43
43
|
}
|
|
44
44
|
const cardColumns = 36;
|
|
45
45
|
function card(text, isShown) {
|
package/ui/elements.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"elements.d.ts","sourceRoot":"","sources":["../src/ui/elements.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAe,YAAY,EAAY,aAAa,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;
|
|
1
|
+
{"version":3,"file":"elements.d.ts","sourceRoot":"","sources":["../src/ui/elements.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAe,YAAY,EAAY,aAAa,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAcpG,wBAAgB,QAAQ,CAAC,KAAK,EAAE,KAAK,EAAE,YAAY,EAAE,IAAI,EAAE,MAAM,KAAK,EAAE,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,aAAa,GAAG,KAAK,CASjI;AAoBD,eAAO,MAAM,GAAG,0EAAiB,CAAA;AACjC,eAAO,MAAM,IAAI,2EAAkB,CAAA;AACnC,eAAO,MAAM,MAAM,6EAAoB,CAAA;AACvC,eAAO,MAAM,IAAI,2EAAkB,CAAA;AACnC,eAAO,MAAM,IAAI,2EAAkB,CAAA;AACnC,eAAO,MAAM,QAAQ,yDAAsB,CAAA;AAC3C,eAAO,MAAM,KAAK,4EAAmB,CAAA;AACrC,eAAO,MAAM,MAAM,6EAAoB,CAAA;AACvC,eAAO,MAAM,KAAK,4EAAmB,CAAA"}
|