@testchimp/cli 0.1.37 → 0.1.38

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.
@@ -63,7 +63,11 @@ function extractOpencodeFatalError(raw) {
63
63
  try {
64
64
  const ev = JSON.parse(line);
65
65
  if (ev.type === "error" || ev.name === "UnknownError") {
66
- const msg = ev.data?.message || ev.message || line;
66
+ const msg = ev.error?.data?.message ||
67
+ ev.error?.message ||
68
+ ev.data?.message ||
69
+ ev.message ||
70
+ line;
67
71
  const ref = ev.data?.ref ? ` (ref ${ev.data.ref})` : "";
68
72
  return `${msg}${ref}`;
69
73
  }
@@ -83,6 +87,27 @@ function extractOpencodeFatalError(raw) {
83
87
  }
84
88
  return null;
85
89
  }
90
+ function parseOpencodeEvent(line) {
91
+ try {
92
+ return JSON.parse(line);
93
+ }
94
+ catch {
95
+ return null;
96
+ }
97
+ }
98
+ function formatToolUseContent(part) {
99
+ const title = part.state?.title || part.tool || "tool";
100
+ const output = part.state?.output?.trim();
101
+ if (output)
102
+ return `[${title}]\n${output}`;
103
+ return `[${title}]`;
104
+ }
105
+ function opencodeMessageId(prefix, part) {
106
+ const raw = part?.id || part?.messageID;
107
+ if (!raw)
108
+ return undefined;
109
+ return `${prefix}${raw}`;
110
+ }
86
111
  function summarizeOpencodeFailure(stderr, stdout, exitCode) {
87
112
  for (const chunk of [stderr, stdout]) {
88
113
  for (const line of chunk.split("\n")) {
@@ -169,41 +194,65 @@ function runOpencode(prompt, model, childEnv, postEvent) {
169
194
  return new Promise((resolve) => {
170
195
  let buf = "";
171
196
  let fatalError = null;
197
+ const textByPartId = new Map();
198
+ const handleOpencodeLine = (line) => {
199
+ if (!line.trim())
200
+ return;
201
+ const fatal = extractOpencodeFatalError(line);
202
+ if (fatal) {
203
+ fatalError = fatal;
204
+ return;
205
+ }
206
+ const ev = parseOpencodeEvent(line);
207
+ if (!ev?.type)
208
+ return;
209
+ switch (ev.type) {
210
+ case "text": {
211
+ const chunk = ev.part?.text;
212
+ if (!chunk)
213
+ return;
214
+ const partId = ev.part?.id || ev.part?.messageID;
215
+ if (!partId) {
216
+ postEvent(ROLE_ASSISTANT, chunk);
217
+ return;
218
+ }
219
+ const next = (textByPartId.get(partId) || "") + chunk;
220
+ textByPartId.set(partId, next);
221
+ postEvent(ROLE_ASSISTANT, next, undefined, opencodeMessageId("oc_text_", ev.part));
222
+ return;
223
+ }
224
+ case "tool_use": {
225
+ if (ev.part?.state?.status !== "completed")
226
+ return;
227
+ postEvent(ROLE_TOOL, formatToolUseContent(ev.part), undefined, opencodeMessageId("oc_tool_", ev.part));
228
+ return;
229
+ }
230
+ case "error": {
231
+ const msg = ev.error?.data?.message ||
232
+ ev.error?.message ||
233
+ line.trim();
234
+ if (msg)
235
+ fatalError = msg;
236
+ return;
237
+ }
238
+ case "step_start":
239
+ case "step_finish":
240
+ return;
241
+ default:
242
+ return;
243
+ }
244
+ };
172
245
  child.stdout.on("data", (chunk) => {
173
246
  buf += chunk.toString();
174
247
  const lines = buf.split("\n");
175
248
  buf = lines.pop() || "";
176
249
  for (const line of lines) {
177
- if (!line.trim())
178
- continue;
179
- const fatal = extractOpencodeFatalError(line);
180
- if (fatal) {
181
- fatalError = fatal;
182
- continue;
183
- }
184
- let content = line;
185
- let role = ROLE_ASSISTANT;
186
- try {
187
- const ev = JSON.parse(line);
188
- content = ev.content || ev.message || ev.text || JSON.stringify(ev);
189
- if (ev.type === "tool" || ev.role === "tool")
190
- role = ROLE_TOOL;
191
- if (ev.type === "status")
192
- role = ROLE_STATUS;
193
- }
194
- catch {
195
- /* plain line */
196
- }
197
- postEvent(role, content);
250
+ handleOpencodeLine(line);
198
251
  }
199
252
  });
200
253
  child.on("close", (code) => {
201
254
  if (buf.trim()) {
202
- const fatal = extractOpencodeFatalError(buf);
203
- if (fatal)
204
- fatalError = fatal;
205
- else if (!fatalError)
206
- postEvent(ROLE_ASSISTANT, buf.trim());
255
+ handleOpencodeLine(buf.trim());
207
256
  }
208
257
  const stderrFatal = extractOpencodeFatalError(err);
209
258
  if (stderrFatal)
@@ -327,12 +376,14 @@ export async function runChimphands(opts) {
327
376
  let idle = false;
328
377
  let closed = false;
329
378
  let lastUserActivity = Date.now();
330
- const postEvent = (role, content, status) => {
379
+ const postEvent = (role, content, status, messageId) => {
331
380
  const body = {
332
381
  sessionId,
333
382
  role,
334
383
  content: String(content || "").slice(0, 20000),
335
384
  };
385
+ if (messageId)
386
+ body.messageId = messageId;
336
387
  if (status != null)
337
388
  body.status = status;
338
389
  postJsonFireAndForget(backend, apiKey, "/api/chimphands/post_agent_event", body);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@testchimp/cli",
3
- "version": "0.1.37",
3
+ "version": "0.1.38",
4
4
  "description": "TestChimp CLI and MCP server — coverage, plans, EaaS, TrueCoverage, API operations (calls /api/mcp/*)",
5
5
  "type": "module",
6
6
  "main": "dist/bin/testchimp.js",