@teammates/cli 0.2.7 → 0.2.8

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.
@@ -16,15 +16,33 @@
16
16
  */
17
17
  import type { AgentAdapter, InstalledService, RosterEntry } from "../adapter.js";
18
18
  import type { SandboxLevel, TaskResult, TeammateConfig } from "../types.js";
19
+ /** Structured result from spawning an agent subprocess. */
20
+ export interface SpawnResult {
21
+ /** Combined stdout + stderr (for backward compat / display) */
22
+ output: string;
23
+ /** stdout only */
24
+ stdout: string;
25
+ /** stderr only */
26
+ stderr: string;
27
+ /** Process exit code (null if killed by signal) */
28
+ exitCode: number | null;
29
+ /** Signal that killed the process (null if exited normally) */
30
+ signal: string | null;
31
+ /** Whether the process was killed by our timeout */
32
+ timedOut: boolean;
33
+ /** Path to the debug log file, if one was written */
34
+ debugFile?: string;
35
+ }
19
36
  export interface AgentPreset {
20
37
  /** Display name */
21
38
  name: string;
22
39
  /** Binary / command to spawn */
23
40
  command: string;
24
- /** Build CLI args. `promptFile` is a temp file path, `prompt` is the raw text. */
41
+ /** Build CLI args. `promptFile` is a temp file path, `prompt` is the raw text, `debugFile` is an optional path for agent debug logs. */
25
42
  buildArgs(ctx: {
26
43
  promptFile: string;
27
44
  prompt: string;
45
+ debugFile?: string;
28
46
  }, teammate: TeammateConfig, options: CliProxyOptions): string[];
29
47
  /** Extra env vars to set (e.g. FORCE_COLOR) */
30
48
  env?: Record<string, string>;
@@ -34,6 +52,8 @@ export interface AgentPreset {
34
52
  shell?: boolean;
35
53
  /** Whether to pipe the prompt via stdin instead of as a CLI argument */
36
54
  stdinPrompt?: boolean;
55
+ /** Whether this preset supports a debug log file (--debug-file) */
56
+ supportsDebugFile?: boolean;
37
57
  /** Optional output parser — transforms raw stdout into clean agent output */
38
58
  parseOutput?(raw: string): string;
39
59
  }
@@ -24,14 +24,17 @@ export const PRESETS = {
24
24
  claude: {
25
25
  name: "claude",
26
26
  command: "claude",
27
- buildArgs(_ctx, _teammate, options) {
27
+ buildArgs(ctx, _teammate, options) {
28
28
  const args = ["-p", "--verbose", "--dangerously-skip-permissions"];
29
29
  if (options.model)
30
30
  args.push("--model", options.model);
31
+ if (ctx.debugFile)
32
+ args.push("--debug-file", ctx.debugFile);
31
33
  return args;
32
34
  },
33
35
  env: { FORCE_COLOR: "1", CLAUDECODE: "" },
34
36
  stdinPrompt: true,
37
+ supportsDebugFile: true,
35
38
  },
36
39
  codex: {
37
40
  name: "codex",
@@ -167,12 +170,20 @@ export class CliProxyAdapter {
167
170
  await writeFile(promptFile, fullPrompt, "utf-8");
168
171
  this.pendingTempFiles.add(promptFile);
169
172
  try {
170
- const rawOutput = await this.spawnAndProxy(teammate, promptFile, fullPrompt);
173
+ const spawn = await this.spawnAndProxy(teammate, promptFile, fullPrompt);
171
174
  const output = this.preset.parseOutput
172
- ? this.preset.parseOutput(rawOutput)
173
- : rawOutput;
175
+ ? this.preset.parseOutput(spawn.output)
176
+ : spawn.output;
174
177
  const teammateNames = this.roster.map((r) => r.name);
175
- return parseResult(teammate.name, output, teammateNames, prompt);
178
+ const result = parseResult(teammate.name, output, teammateNames, prompt);
179
+ result.diagnostics = {
180
+ exitCode: spawn.exitCode,
181
+ signal: spawn.signal,
182
+ stderr: spawn.stderr,
183
+ timedOut: spawn.timedOut,
184
+ debugFile: spawn.debugFile,
185
+ };
186
+ return result;
176
187
  }
177
188
  finally {
178
189
  this.pendingTempFiles.delete(promptFile);
@@ -291,8 +302,12 @@ export class CliProxyAdapter {
291
302
  */
292
303
  spawnAndProxy(teammate, promptFile, fullPrompt) {
293
304
  return new Promise((resolve, reject) => {
305
+ // Generate a debug log file path if the preset supports it
306
+ const debugFile = this.preset.supportsDebugFile
307
+ ? join(tmpdir(), `teammates-debug-${teammate.name}-${Date.now()}.log`)
308
+ : undefined;
294
309
  const args = [
295
- ...this.preset.buildArgs({ promptFile, prompt: fullPrompt }, teammate, this.options),
310
+ ...this.preset.buildArgs({ promptFile, prompt: fullPrompt, debugFile }, teammate, this.options),
296
311
  ...(this.options.extraFlags ?? []),
297
312
  ];
298
313
  const command = this.options.commandPath ?? this.preset.command;
@@ -352,12 +367,13 @@ export class CliProxyAdapter {
352
367
  process.stdin.resume();
353
368
  process.stdin.on("data", onUserInput);
354
369
  }
355
- const captured = [];
370
+ const stdoutBufs = [];
371
+ const stderrBufs = [];
356
372
  child.stdout?.on("data", (chunk) => {
357
- captured.push(chunk);
373
+ stdoutBufs.push(chunk);
358
374
  });
359
375
  child.stderr?.on("data", (chunk) => {
360
- captured.push(chunk);
376
+ stderrBufs.push(chunk);
361
377
  });
362
378
  const cleanup = () => {
363
379
  clearTimeout(timeoutTimer);
@@ -367,15 +383,22 @@ export class CliProxyAdapter {
367
383
  process.stdin.removeListener("data", onUserInput);
368
384
  }
369
385
  };
370
- child.on("close", (_code) => {
386
+ child.on("close", (code, signal) => {
371
387
  cleanup();
372
- const output = Buffer.concat(captured).toString("utf-8");
373
- if (killed) {
374
- resolve(`${output}\n\n[TIMEOUT] Agent process killed after ${timeout}ms`);
375
- }
376
- else {
377
- resolve(output);
378
- }
388
+ const stdout = Buffer.concat(stdoutBufs).toString("utf-8");
389
+ const stderr = Buffer.concat(stderrBufs).toString("utf-8");
390
+ const output = stdout + (stderr ? `\n${stderr}` : "");
391
+ resolve({
392
+ output: killed
393
+ ? `${output}\n\n[TIMEOUT] Agent process killed after ${timeout}ms`
394
+ : output,
395
+ stdout,
396
+ stderr,
397
+ exitCode: code,
398
+ signal: signal ?? null,
399
+ timedOut: killed,
400
+ debugFile,
401
+ });
379
402
  });
380
403
  child.on("error", (err) => {
381
404
  cleanup();
package/dist/cli.js CHANGED
@@ -2275,6 +2275,9 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
2275
2275
  }
2276
2276
  }
2277
2277
  });
2278
+ this.chatView.on("copy", (text) => {
2279
+ this.doCopy(text);
2280
+ });
2278
2281
  this.chatView.on("link", (url) => {
2279
2282
  const quoted = JSON.stringify(url);
2280
2283
  const cmd = process.platform === "darwin"
@@ -2695,6 +2698,19 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
2695
2698
  else {
2696
2699
  this.feedLine(tp.muted(" (no response text — the agent may have only performed tool actions)"));
2697
2700
  this.feedLine(tp.muted(` Use /debug ${event.result.teammate} to view full output`));
2701
+ // Show diagnostic hints for empty responses
2702
+ const diag = event.result.diagnostics;
2703
+ if (diag) {
2704
+ if (diag.exitCode !== 0 && diag.exitCode !== null) {
2705
+ this.feedLine(tp.warning(` ⚠ Process exited with code ${diag.exitCode}`));
2706
+ }
2707
+ if (diag.signal) {
2708
+ this.feedLine(tp.warning(` ⚠ Process killed by signal: ${diag.signal}`));
2709
+ }
2710
+ if (diag.debugFile) {
2711
+ this.feedLine(tp.muted(` Debug log: ${diag.debugFile}`));
2712
+ }
2713
+ }
2698
2714
  }
2699
2715
  // Render handoffs
2700
2716
  const handoffs = event.result.handoffs;
@@ -2984,6 +3000,31 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
2984
3000
  if (result.handoffs.length > 0) {
2985
3001
  lines.push(`**Handoffs:** ${result.handoffs.map((h) => `@${h.to}`).join(", ")}`);
2986
3002
  }
3003
+ // Process diagnostics — exit code, signal, stderr
3004
+ const diag = result.diagnostics;
3005
+ if (diag) {
3006
+ lines.push("");
3007
+ lines.push("### Process");
3008
+ lines.push(`**Exit code:** ${diag.exitCode ?? "(killed by signal)"}`);
3009
+ if (diag.signal)
3010
+ lines.push(`**Signal:** ${diag.signal}`);
3011
+ if (diag.timedOut)
3012
+ lines.push(`**Timed out:** yes`);
3013
+ if (diag.debugFile)
3014
+ lines.push(`**Debug log:** ${diag.debugFile}`);
3015
+ // Show stderr separately — this is where tool call output and errors go
3016
+ if (diag.stderr.trim()) {
3017
+ lines.push("");
3018
+ lines.push("<details><summary>stderr</summary>");
3019
+ lines.push("");
3020
+ lines.push(diag.stderr);
3021
+ lines.push("");
3022
+ lines.push("</details>");
3023
+ }
3024
+ else {
3025
+ lines.push(`**stderr:** (empty)`);
3026
+ }
3027
+ }
2987
3028
  lines.push("");
2988
3029
  lines.push("<details><summary>Raw output</summary>");
2989
3030
  lines.push("");
package/dist/types.d.ts CHANGED
@@ -68,6 +68,19 @@ export interface TaskResult {
68
68
  handoffs: HandoffEnvelope[];
69
69
  /** Raw output from the agent */
70
70
  rawOutput?: string;
71
+ /** Process diagnostics for debugging empty/failed responses */
72
+ diagnostics?: {
73
+ /** Process exit code (null if killed by signal) */
74
+ exitCode: number | null;
75
+ /** Signal that killed the process (null if exited normally) */
76
+ signal: string | null;
77
+ /** stderr output (separate from stdout) */
78
+ stderr: string;
79
+ /** Whether the process was killed by timeout */
80
+ timedOut: boolean;
81
+ /** Path to the agent's debug log file, if written */
82
+ debugFile?: string;
83
+ };
71
84
  }
72
85
  /** Task assignment to a teammate */
73
86
  export interface TaskAssignment {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@teammates/cli",
3
- "version": "0.2.7",
3
+ "version": "0.2.8",
4
4
  "description": "Agent-agnostic CLI for teammates. Routes tasks, manages handoffs, and plugs into any coding agent backend.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",