@ccmsg/cli 0.11.0 → 0.11.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
CHANGED
package/src/daemon/registry.ts
CHANGED
|
@@ -188,9 +188,16 @@ const STARTING_PRESETS = [
|
|
|
188
188
|
},
|
|
189
189
|
{
|
|
190
190
|
name: "journal",
|
|
191
|
-
description: "日記用。人との往復と worker
|
|
191
|
+
description: "日記用。人との往復と worker の答え、中断などの合図、思考は要点だけ",
|
|
192
192
|
opts: {
|
|
193
|
-
types: [
|
|
193
|
+
types: [
|
|
194
|
+
"message.user",
|
|
195
|
+
"message.parent",
|
|
196
|
+
"message.sub.in",
|
|
197
|
+
"message.team.in",
|
|
198
|
+
"notice",
|
|
199
|
+
"thinking",
|
|
200
|
+
],
|
|
194
201
|
},
|
|
195
202
|
},
|
|
196
203
|
{
|
package/src/daemon/supervise.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { chmodSync, mkdirSync, unlinkSync } from "node:fs";
|
|
2
2
|
import { dirname } from "node:path";
|
|
3
3
|
import { type Env, resolveSupervisorSocket } from "../instance/paths.ts";
|
|
4
|
+
import { WriteQueue } from "../transport/index.ts";
|
|
4
5
|
import { CommandError, type SuperviseRequest } from "./link.ts";
|
|
5
6
|
import {
|
|
6
7
|
awaitGone,
|
|
@@ -194,11 +195,27 @@ export class Supervisor {
|
|
|
194
195
|
}
|
|
195
196
|
const handle = (frame: Record<string, unknown>): Promise<unknown> =>
|
|
196
197
|
this.handle(frame as unknown as SuperviseRequest);
|
|
197
|
-
this.#listener = Bun.listen<
|
|
198
|
+
this.#listener = Bun.listen<ControlState>({
|
|
198
199
|
unix: path,
|
|
199
200
|
socket: {
|
|
200
201
|
open(socket) {
|
|
201
|
-
|
|
202
|
+
// Every answer goes through the queue, because `socket.write` takes
|
|
203
|
+
// what fits in the socket buffer and returns a short count for the
|
|
204
|
+
// rest: an answer longer than that — `status --all` on a host with
|
|
205
|
+
// several instances — would otherwise arrive without its newline and
|
|
206
|
+
// leave the caller waiting for a line that never ends.
|
|
207
|
+
const queue = new WriteQueue<Uint8Array>({
|
|
208
|
+
encode: (line) => new TextEncoder().encode(line),
|
|
209
|
+
write(chunk) {
|
|
210
|
+
const written = socket.write(chunk);
|
|
211
|
+
if (written < 0) return undefined; // closing: nothing more will go
|
|
212
|
+
return written === chunk.length ? undefined : chunk.subarray(written);
|
|
213
|
+
},
|
|
214
|
+
flush: () => {
|
|
215
|
+
socket.flush();
|
|
216
|
+
},
|
|
217
|
+
});
|
|
218
|
+
socket.data = { buffer: "", queue };
|
|
202
219
|
},
|
|
203
220
|
data(socket, chunk) {
|
|
204
221
|
socket.data.buffer += new TextDecoder().decode(chunk);
|
|
@@ -207,9 +224,12 @@ export class Supervisor {
|
|
|
207
224
|
const line = socket.data.buffer.slice(0, at);
|
|
208
225
|
socket.data.buffer = socket.data.buffer.slice(at + 1);
|
|
209
226
|
if (line.trim() === "") continue;
|
|
210
|
-
void answer(socket, line, handle);
|
|
227
|
+
void answer(socket.data.queue, line, handle);
|
|
211
228
|
}
|
|
212
229
|
},
|
|
230
|
+
drain(socket) {
|
|
231
|
+
socket.data.queue.drain();
|
|
232
|
+
},
|
|
213
233
|
},
|
|
214
234
|
});
|
|
215
235
|
chmodSync(path, 0o600);
|
|
@@ -489,9 +509,16 @@ export class Supervisor {
|
|
|
489
509
|
}
|
|
490
510
|
}
|
|
491
511
|
|
|
512
|
+
/** What one control connection holds: the half-read line, and the answers
|
|
513
|
+
* waiting for a socket that is not taking them all at once. */
|
|
514
|
+
interface ControlState {
|
|
515
|
+
buffer: string;
|
|
516
|
+
queue: WriteQueue<Uint8Array>;
|
|
517
|
+
}
|
|
518
|
+
|
|
492
519
|
/** Answer one line, in the shape a command reads: the result, or the error. */
|
|
493
520
|
async function answer(
|
|
494
|
-
|
|
521
|
+
queue: WriteQueue<Uint8Array>,
|
|
495
522
|
line: string,
|
|
496
523
|
handle: (frame: Record<string, unknown>) => Promise<unknown>,
|
|
497
524
|
): Promise<void> {
|
|
@@ -499,18 +526,18 @@ async function answer(
|
|
|
499
526
|
try {
|
|
500
527
|
frame = JSON.parse(line) as Record<string, unknown>;
|
|
501
528
|
} catch {
|
|
502
|
-
|
|
529
|
+
queue.push(
|
|
503
530
|
`${JSON.stringify({ ok: false, error: { code: "bad_request", msg: "not valid JSON" } })}\n`,
|
|
504
531
|
);
|
|
505
532
|
return;
|
|
506
533
|
}
|
|
507
534
|
try {
|
|
508
|
-
|
|
535
|
+
queue.push(`${JSON.stringify({ ok: true, result: await handle(frame) })}\n`);
|
|
509
536
|
} catch (cause) {
|
|
510
537
|
const error =
|
|
511
538
|
cause instanceof CommandError
|
|
512
539
|
? { code: cause.code, msg: cause.message }
|
|
513
540
|
: { code: "internal_error", msg: String(cause) };
|
|
514
|
-
|
|
541
|
+
queue.push(`${JSON.stringify({ ok: false, error })}\n`);
|
|
515
542
|
}
|
|
516
543
|
}
|
|
@@ -83,31 +83,14 @@ function heading(file: SessionDumpFile, view: DumpView): string[] {
|
|
|
83
83
|
function draw(item: Item, child: Item | undefined, view: DumpView, parent?: string): string[] {
|
|
84
84
|
const own = fragment(item);
|
|
85
85
|
const answer = child === undefined ? undefined : fragment(child);
|
|
86
|
-
const nested = child !== undefined && spoken(child.type);
|
|
87
86
|
const link = isResult(item)
|
|
88
87
|
? arrow("←", parent)
|
|
89
88
|
: (arrow("→", fields(item)["result_item"]) ?? waiting(item));
|
|
90
89
|
const head = isResult(item)
|
|
91
90
|
? words(prefix(item), link, own.head, clock(item))
|
|
92
|
-
: words(
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
link,
|
|
96
|
-
nested || answer === undefined ? undefined : answer.head,
|
|
97
|
-
clock(item),
|
|
98
|
-
);
|
|
99
|
-
const under = [
|
|
100
|
-
...body(own.body, view),
|
|
101
|
-
...(answer === undefined || nested ? [] : body(answer.body, view)),
|
|
102
|
-
];
|
|
103
|
-
const lines = [head, ...under.map((line) => `${INDENT}${line}`)];
|
|
104
|
-
if (!nested || child === undefined || answer === undefined) return lines;
|
|
105
|
-
// The agent's answer, under the brief that asked for it. It keeps a heading
|
|
106
|
-
// of its own — it has its own instant, and often a status the brief could
|
|
107
|
-
// not have known — and is indented to say whose answer it is.
|
|
108
|
-
lines.push(`${INDENT}${words(prefix(child), answer.head, clock(child))}`);
|
|
109
|
-
for (const line of body(answer.body, view)) lines.push(`${INDENT}${INDENT}${line}`);
|
|
110
|
-
return lines;
|
|
91
|
+
: words(prefix(item), own.head, link, answer?.head, clock(item));
|
|
92
|
+
const under = [...body(own.body, view), ...(answer === undefined ? [] : body(answer.body, view))];
|
|
93
|
+
return [head, ...under.map((line) => `${INDENT}${line}`)];
|
|
111
94
|
}
|
|
112
95
|
|
|
113
96
|
/** `[id] type`, which is how an item is pointed at: the id is what the links
|
|
@@ -238,9 +221,11 @@ function pair(items: readonly Item[]): {
|
|
|
238
221
|
const call = where.get(to);
|
|
239
222
|
if (call === undefined) continue;
|
|
240
223
|
// A pair the reader would have to scroll between is left where each half
|
|
241
|
-
// happened
|
|
242
|
-
//
|
|
243
|
-
|
|
224
|
+
// happened. What is being read is a run of moments in the order they
|
|
225
|
+
// happened, and an answer that arrived minutes later — which is the usual
|
|
226
|
+
// case for an agent — would put a later moment in the middle of an earlier
|
|
227
|
+
// one. The two halves point at each other by id instead.
|
|
228
|
+
if (call !== at - 1) continue;
|
|
244
229
|
child.set(call, at);
|
|
245
230
|
folded.add(at);
|
|
246
231
|
}
|
|
@@ -255,12 +240,3 @@ function pair(items: readonly Item[]): {
|
|
|
255
240
|
function joined(item: Item, key: string): string {
|
|
256
241
|
return `${item.type.startsWith("message.") ? "message" : "tool"}\n${key}`;
|
|
257
242
|
}
|
|
258
|
-
|
|
259
|
-
/** Whether an item is the conversation half of starting an agent, as opposed
|
|
260
|
-
* to the call's own half. What was asked and what came back is one exchange
|
|
261
|
-
* however many turns fell between them, so these are drawn together wherever
|
|
262
|
-
* they ended up — a conversation split across the page is one nobody can
|
|
263
|
-
* follow. */
|
|
264
|
-
function spoken(type: string): boolean {
|
|
265
|
-
return type.startsWith("message.sub") || type.startsWith("message.team");
|
|
266
|
-
}
|