@ccmsg/cli 0.3.4 → 0.4.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/package.json +2 -2
- package/src/cli.ts +23 -0
- package/src/daemon/registry.ts +61 -3
- package/src/instance/config.ts +161 -5
- package/src/instance/instance.ts +1 -0
- package/src/sessions/dump.ts +91 -53
- package/src/sessions/handlers.ts +14 -1
- package/src/sessions/index.ts +1 -1
- package/src/transcript/items/classify.ts +400 -0
- package/src/transcript/items/ids.ts +0 -0
- package/src/transcript/items/index.ts +4 -0
- package/src/transcript/items/item.ts +23 -0
- package/src/transcript/items/record.ts +71 -0
- package/src/transcript/items/select.ts +144 -0
- package/src/transcript/items/tools.ts +231 -0
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
import { bool, count, lines, list, optional, row, str } from "./record.ts";
|
|
2
|
+
|
|
3
|
+
/** What each tool's call and result are read as.
|
|
4
|
+
*
|
|
5
|
+
* The contract names fields for the tools worth reading closely and leaves
|
|
6
|
+
* every other tool to a generic shape. Fields sharpen how a tool reads; they
|
|
7
|
+
* never decide whether it is kept, so a tool absent from this table still
|
|
8
|
+
* arrives carrying what it was called with and what it answered.
|
|
9
|
+
*
|
|
10
|
+
* A reader returns nothing when the record does not hold what the contract
|
|
11
|
+
* requires of that shape — a `Bash` call with no command is not a `Bash` call
|
|
12
|
+
* as the contract spells one — and the generic shape answers instead. That
|
|
13
|
+
* keeps a malformed line from producing an item nothing can validate, without
|
|
14
|
+
* losing the line. */
|
|
15
|
+
type Reader = (source: Record<string, unknown>) => Record<string, unknown> | undefined;
|
|
16
|
+
|
|
17
|
+
/** The call's own arguments, as the harness recorded them. */
|
|
18
|
+
const USE: Record<string, Reader> = {
|
|
19
|
+
Bash: (input) => {
|
|
20
|
+
const command = str(input["command"]);
|
|
21
|
+
return command === undefined
|
|
22
|
+
? undefined
|
|
23
|
+
: { command, ...optional("description", str(input["description"])) };
|
|
24
|
+
},
|
|
25
|
+
Read: (input) => {
|
|
26
|
+
const file_path = str(input["file_path"]);
|
|
27
|
+
return file_path === undefined
|
|
28
|
+
? undefined
|
|
29
|
+
: {
|
|
30
|
+
file_path,
|
|
31
|
+
...optional("offset", count(input["offset"])),
|
|
32
|
+
...optional("limit", count(input["limit"])),
|
|
33
|
+
};
|
|
34
|
+
},
|
|
35
|
+
/** Bodies are counted, never carried: an edit's two sides are the file's
|
|
36
|
+
* content, and a dump that inlined them would be the file. */
|
|
37
|
+
Write: (input) => {
|
|
38
|
+
const file_path = str(input["file_path"]);
|
|
39
|
+
return file_path === undefined
|
|
40
|
+
? undefined
|
|
41
|
+
: { file_path, ...optional("lines", lines(str(input["content"]))) };
|
|
42
|
+
},
|
|
43
|
+
Edit: (input) => {
|
|
44
|
+
const file_path = str(input["file_path"]);
|
|
45
|
+
return file_path === undefined
|
|
46
|
+
? undefined
|
|
47
|
+
: {
|
|
48
|
+
file_path,
|
|
49
|
+
...optional("old_lines", lines(str(input["old_string"]))),
|
|
50
|
+
...optional("new_lines", lines(str(input["new_string"]))),
|
|
51
|
+
};
|
|
52
|
+
},
|
|
53
|
+
Grep: pattern,
|
|
54
|
+
Glob: pattern,
|
|
55
|
+
WebFetch: (input) => {
|
|
56
|
+
const url = str(input["url"]);
|
|
57
|
+
return url === undefined ? undefined : { url, ...optional("prompt", str(input["prompt"])) };
|
|
58
|
+
},
|
|
59
|
+
WebSearch: (input) => {
|
|
60
|
+
const query = str(input["query"]);
|
|
61
|
+
return query === undefined ? undefined : { query };
|
|
62
|
+
},
|
|
63
|
+
Agent: (input) => {
|
|
64
|
+
const prompt = str(input["prompt"]);
|
|
65
|
+
return prompt === undefined
|
|
66
|
+
? undefined
|
|
67
|
+
: {
|
|
68
|
+
prompt,
|
|
69
|
+
...optional("name", str(input["name"])),
|
|
70
|
+
...optional("subagent_type", str(input["subagent_type"])),
|
|
71
|
+
...optional("description", str(input["description"])),
|
|
72
|
+
};
|
|
73
|
+
},
|
|
74
|
+
SendMessage: (input) => {
|
|
75
|
+
const to = str(input["to"]);
|
|
76
|
+
return to === undefined ? undefined : { to, ...optional("summary", str(input["summary"])) };
|
|
77
|
+
},
|
|
78
|
+
Monitor: (input) => {
|
|
79
|
+
const description = str(input["description"]);
|
|
80
|
+
return description === undefined
|
|
81
|
+
? undefined
|
|
82
|
+
: {
|
|
83
|
+
description,
|
|
84
|
+
...optional("command", str(input["command"])),
|
|
85
|
+
...optional("persistent", bool(input["persistent"])),
|
|
86
|
+
...optional("timeout_ms", count(input["timeout_ms"])),
|
|
87
|
+
};
|
|
88
|
+
},
|
|
89
|
+
Skill: (input) => {
|
|
90
|
+
const skill = str(input["skill"]);
|
|
91
|
+
return skill === undefined ? undefined : { skill, ...optional("args", str(input["args"])) };
|
|
92
|
+
},
|
|
93
|
+
TodoWrite: (input) => {
|
|
94
|
+
const todos = list(input["todos"]).flatMap((entry) => {
|
|
95
|
+
const each = row(entry);
|
|
96
|
+
const content = each === undefined ? undefined : str(each["content"]);
|
|
97
|
+
const status = each === undefined ? undefined : str(each["status"]);
|
|
98
|
+
return content === undefined || status === undefined ? [] : [{ content, status }];
|
|
99
|
+
});
|
|
100
|
+
return { todos };
|
|
101
|
+
},
|
|
102
|
+
TaskStop: (input) => {
|
|
103
|
+
const task_id = str(input["task_id"]) ?? str(input["shell_id"]);
|
|
104
|
+
return task_id === undefined ? undefined : { task_id };
|
|
105
|
+
},
|
|
106
|
+
CronCreate: (input) => {
|
|
107
|
+
const cron = str(input["cron"]);
|
|
108
|
+
return cron === undefined ? undefined : { cron, ...optional("prompt", str(input["prompt"])) };
|
|
109
|
+
},
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
function pattern(input: Record<string, unknown>): Record<string, unknown> | undefined {
|
|
113
|
+
const found = str(input["pattern"]);
|
|
114
|
+
return found === undefined
|
|
115
|
+
? undefined
|
|
116
|
+
: { pattern: found, ...optional("path", str(input["path"])) };
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/** What came back.
|
|
120
|
+
*
|
|
121
|
+
* The harness writes a tool's answer to `toolUseResult` in whatever shape that
|
|
122
|
+
* tool settled on, so each reader knows one shape. `ok` stands where a tool
|
|
123
|
+
* says nothing but whether it worked, and the block's own error flag is what
|
|
124
|
+
* decides it. */
|
|
125
|
+
const RESULT: Record<string, (result: unknown, failed: boolean) => Record<string, unknown>> = {
|
|
126
|
+
Bash: (result) => {
|
|
127
|
+
const fields = row(result);
|
|
128
|
+
if (fields === undefined) return { ...optional("stdout", str(result)) };
|
|
129
|
+
return {
|
|
130
|
+
...optional("stdout", str(fields["stdout"])),
|
|
131
|
+
...optional("stderr", str(fields["stderr"])),
|
|
132
|
+
...optional("interrupted", bool(fields["interrupted"])),
|
|
133
|
+
};
|
|
134
|
+
},
|
|
135
|
+
Read: (result) => {
|
|
136
|
+
const fields = row(result);
|
|
137
|
+
const file = fields === undefined ? undefined : row(fields["file"]);
|
|
138
|
+
const content = file === undefined ? undefined : str(file["content"]);
|
|
139
|
+
return {
|
|
140
|
+
...optional(
|
|
141
|
+
"lines",
|
|
142
|
+
file === undefined ? undefined : (count(file["numLines"]) ?? lines(content)),
|
|
143
|
+
),
|
|
144
|
+
...optional("bytes", content === undefined ? undefined : Buffer.byteLength(content)),
|
|
145
|
+
};
|
|
146
|
+
},
|
|
147
|
+
Write: (_result, failed) => ({ ok: !failed }),
|
|
148
|
+
Edit: (_result, failed) => ({ ok: !failed }),
|
|
149
|
+
Grep: matches,
|
|
150
|
+
Glob: matches,
|
|
151
|
+
WebFetch: (result) => {
|
|
152
|
+
const fields = row(result);
|
|
153
|
+
return {
|
|
154
|
+
...optional(
|
|
155
|
+
"text",
|
|
156
|
+
str(result) ?? (fields === undefined ? undefined : str(fields["result"])),
|
|
157
|
+
),
|
|
158
|
+
};
|
|
159
|
+
},
|
|
160
|
+
WebSearch: (result) => {
|
|
161
|
+
const fields = row(result);
|
|
162
|
+
if (fields === undefined || !Array.isArray(fields["results"])) return {};
|
|
163
|
+
return { results: fields["results"].length };
|
|
164
|
+
},
|
|
165
|
+
Agent: (result) => {
|
|
166
|
+
const fields = row(result) ?? {};
|
|
167
|
+
return {
|
|
168
|
+
...optional("agent_id", str(fields["agentId"]) ?? str(fields["agent_id"])),
|
|
169
|
+
...optional("status", str(fields["status"])),
|
|
170
|
+
};
|
|
171
|
+
},
|
|
172
|
+
SendMessage: (result) => {
|
|
173
|
+
const fields = row(result) ?? {};
|
|
174
|
+
return {
|
|
175
|
+
...optional("msg_id", str(fields["msg_id"])),
|
|
176
|
+
...optional("routing", str(fields["routing"])),
|
|
177
|
+
};
|
|
178
|
+
},
|
|
179
|
+
Monitor: (result) => {
|
|
180
|
+
const fields = row(result) ?? {};
|
|
181
|
+
return { ...optional("task_id", str(fields["taskId"]) ?? str(fields["task_id"])) };
|
|
182
|
+
},
|
|
183
|
+
Skill: (result) => {
|
|
184
|
+
const fields = row(result) ?? {};
|
|
185
|
+
return {
|
|
186
|
+
...optional("agent_id", str(fields["agentId"]) ?? str(fields["agent_id"])),
|
|
187
|
+
...optional("background", bool(fields["background"])),
|
|
188
|
+
...optional("status", str(fields["status"])),
|
|
189
|
+
};
|
|
190
|
+
},
|
|
191
|
+
TodoWrite: (_result, failed) => ({ ok: !failed }),
|
|
192
|
+
TaskStop: (_result, failed) => ({ ok: !failed }),
|
|
193
|
+
CronCreate: (result) => {
|
|
194
|
+
const fields = row(result) ?? {};
|
|
195
|
+
return { ...optional("cron_id", str(fields["cron_id"]) ?? str(fields["id"])) };
|
|
196
|
+
},
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
function matches(result: unknown): Record<string, unknown> {
|
|
200
|
+
const fields = row(result);
|
|
201
|
+
if (fields === undefined) return {};
|
|
202
|
+
const found = count(fields["numFiles"]) ?? count(fields["numLines"]);
|
|
203
|
+
return { ...optional("matches", found) };
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/** The fields of one tool call, or nothing where the generic shape should
|
|
207
|
+
* answer for it. */
|
|
208
|
+
export function useFields(name: string, input: unknown): Record<string, unknown> | undefined {
|
|
209
|
+
const reader = USE[name];
|
|
210
|
+
const fields = row(input) ?? {};
|
|
211
|
+
return reader === undefined ? undefined : reader(fields);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
export function resultFields(
|
|
215
|
+
name: string | undefined,
|
|
216
|
+
result: unknown,
|
|
217
|
+
failed: boolean,
|
|
218
|
+
): Record<string, unknown> | undefined {
|
|
219
|
+
const reader = name === undefined ? undefined : RESULT[name];
|
|
220
|
+
return reader === undefined ? undefined : reader(result, failed);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/** Whatever a tool answered, for a tool nothing knows the shape of. A string
|
|
224
|
+
* answer becomes one field rather than being dropped for not being an
|
|
225
|
+
* object. */
|
|
226
|
+
export function genericResult(result: unknown): Record<string, unknown> {
|
|
227
|
+
const fields = row(result);
|
|
228
|
+
if (fields !== undefined) return fields;
|
|
229
|
+
const text = str(result);
|
|
230
|
+
return text === undefined ? {} : { text };
|
|
231
|
+
}
|