@ccmsg/cli 0.4.2 → 0.4.3

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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ccmsg/cli",
3
- "version": "0.4.2",
3
+ "version": "0.4.3",
4
4
  "description": "The ccmsg daemon, CLI and agent plugins for one instance (= one config home)",
5
5
  "license": "MIT",
6
6
  "author": "kawaz",
@@ -43,6 +43,11 @@ const NOT_ITEMS = new Set([
43
43
  "summary",
44
44
  ]);
45
45
 
46
+ /** The tools that start an agent, in the spellings the harness has used for
47
+ * the one thing. Both are read the same way: the call is also a brief, and
48
+ * what comes back is also an answer. */
49
+ const SPAWNS = new Set(["Agent", "Task"]);
50
+
46
51
  /** What an item is under construction: the contract's shape, before it is
47
52
  * settled. A call learns the id of what answered it only when the answer
48
53
  * arrives, which is why these are written to after they are made. */
@@ -184,7 +189,7 @@ class Classification {
184
189
  ...(fields ?? { input }),
185
190
  });
186
191
  let message: Draft | undefined;
187
- if (name === "Agent") {
192
+ if (SPAWNS.has(name)) {
188
193
  message = make("message:sub:out", {
189
194
  role: "use",
190
195
  prompt: str(input["prompt"]) ?? "",
@@ -247,10 +252,25 @@ class Classification {
247
252
  call.tool["result_item"] = item.uuid;
248
253
  // An agent's id is known only once it has started, so the message that
249
254
  // asked for it learns its own id from the answer.
250
- const result = row(answer);
251
- const agent =
252
- result === undefined ? undefined : (str(result["agentId"]) ?? str(result["agent_id"]));
253
- if (call.message !== undefined && agent !== undefined) call.message["agent_id"] = agent;
255
+ const result = row(answer) ?? {};
256
+ const agent = str(result["agentId"]) ?? str(result["agent_id"]);
257
+ if (call.message === undefined) return;
258
+ if (agent !== undefined) call.message["agent_id"] = agent;
259
+ // An agent that was waited on answers here, in the call's own result. One
260
+ // started in the background answers much later in a notification of its
261
+ // own, and this result then says only that it was launched — so what
262
+ // decides is whether an answer came back, not which tool was called.
263
+ const said = answered(result["content"]);
264
+ if (said === undefined) return;
265
+ const reply = make("message:sub:in", {
266
+ role: "result",
267
+ parent_item: call.message.uuid,
268
+ text: said,
269
+ ...optional("agent_id", agent),
270
+ ...optional("status", str(result["status"])),
271
+ ...optional("duration_ms", count(result["totalDurationMs"])),
272
+ });
273
+ call.message["result_item"] = reply.uuid;
254
274
  }
255
275
 
256
276
  /** A `type: "user"` line whose content is words rather than a tool's answer.
@@ -385,6 +405,15 @@ function attribute(said: string, name: string): string | undefined {
385
405
  return new RegExp(`${name}="([^"]*)"`).exec(said)?.[1] || undefined;
386
406
  }
387
407
 
408
+ /** What an agent handed back, which the harness writes as the blocks of a
409
+ * message. Nothing back is not an answer of no words: a launch says only that
410
+ * the agent started, and reading that as an empty answer would claim it had
411
+ * finished. */
412
+ function answered(raw: unknown): string | undefined {
413
+ const said = text(raw)?.trim();
414
+ return said === undefined || said === "" ? undefined : said;
415
+ }
416
+
388
417
  /** A content field that is sometimes a string and sometimes the blocks of
389
418
  * one. */
390
419
  function text(raw: unknown): string | undefined {
@@ -196,6 +196,12 @@ const RESULT: Record<string, (result: unknown, failed: boolean) => Record<string
196
196
  },
197
197
  };
198
198
 
199
+ /** `Task` is the harness's older name for the tool that starts an agent. The
200
+ * type keeps whichever name the record used — a reader matches what it sees
201
+ * against what it ran — and both are read the same way. */
202
+ USE["Task"] = USE["Agent"] as Reader;
203
+ RESULT["Task"] = RESULT["Agent"] as (result: unknown, failed: boolean) => Record<string, unknown>;
204
+
199
205
  function matches(result: unknown): Record<string, unknown> {
200
206
  const fields = row(result);
201
207
  if (fields === undefined) return {};