@teammates/cli 0.2.7 → 0.3.0

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/dist/cli.js CHANGED
@@ -7,21 +7,20 @@
7
7
  * teammates --adapter codex Use a specific agent adapter
8
8
  * teammates --dir <path> Override .teammates/ location
9
9
  */
10
- import { spawn as cpSpawn, exec as execCb, execSync, } from "node:child_process";
11
- import { appendFileSync, readFileSync, writeFileSync } from "node:fs";
10
+ import { exec as execCb, } from "node:child_process";
11
+ import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
12
12
  import { mkdir, readdir, rm, stat, unlink } from "node:fs/promises";
13
13
  import { tmpdir } from "node:os";
14
14
  import { dirname, join, resolve } from "node:path";
15
15
  import { createInterface } from "node:readline";
16
- import { promisify } from "node:util";
17
- const execAsync = promisify(execCb);
18
16
  import { App, ChatView, Control, concat, esc, Interview, pen, renderMarkdown, StyledText, stripAnsi, } from "@teammates/consolonia";
19
17
  import chalk from "chalk";
20
18
  import ora from "ora";
19
+ import { syncRecallIndex } from "./adapter.js";
21
20
  import { CliProxyAdapter, PRESETS } from "./adapters/cli-proxy.js";
22
21
  import { EchoAdapter } from "./adapters/echo.js";
23
22
  import { findAtMention, isImagePath, relativeTime, wrapLine, } from "./cli-utils.js";
24
- import { compactEpisodic } from "./compact.js";
23
+ import { buildWisdomPrompt, compactEpisodic } from "./compact.js";
25
24
  import { PromptInput } from "./console/prompt-input.js";
26
25
  import { buildTitle } from "./console/startup.js";
27
26
  import { buildImportAdaptationPrompt, copyTemplateFiles, getOnboardingPrompt, importTeammates, } from "./onboard.js";
@@ -110,24 +109,6 @@ async function resolveAdapter(name) {
110
109
  console.error(`Available adapters: ${available}`);
111
110
  process.exit(1);
112
111
  }
113
- const SERVICE_REGISTRY = {
114
- recall: {
115
- package: "@teammates/recall",
116
- checkCmd: ["teammates-recall", "--help"],
117
- indexCmd: ["teammates-recall", "index"],
118
- description: "Local semantic search for teammate memory",
119
- wireupTask: [
120
- "The `teammates-recall` service was just installed globally.",
121
- "Wire it up so every teammate knows it's available:",
122
- "",
123
- "1. Verify `teammates-recall --help` works. If it does, great. If not, figure out the correct path to the binary (check recall/package.json bin field) and note it.",
124
- "2. Read .teammates/PROTOCOL.md and .teammates/CROSS-TEAM.md.",
125
- '3. If recall is not already documented there, add a short section explaining that `teammates-recall` is now available for semantic memory search, with basic usage (e.g. `teammates-recall search "query"`).',
126
- "4. Check each teammate's SOUL.md (under .teammates/*/SOUL.md). If a teammate's role involves memory or search, note in their SOUL.md that recall is installed and available.",
127
- "5. Do NOT modify code files — only update .teammates/ markdown files.",
128
- ].join("\n"),
129
- },
130
- };
131
112
  // WordwheelItem is now DropdownItem from @teammates/consolonia
132
113
  // ── Themed pen shortcuts ────────────────────────────────────────────
133
114
  //
@@ -215,10 +196,8 @@ class AnimatedBanner extends Control {
215
196
  lines.push(concat(tp.accent(tmTop), tp.text(gap + info.adapterName), tp.muted(` · ${info.teammateCount} teammate${info.teammateCount === 1 ? "" : "s"}`), tp.muted(` · v${PKG_VERSION}`)));
216
197
  // TM logo row 2 + cwd
217
198
  lines.push(concat(tp.accent(tmBot), tp.muted(gap + info.cwd)));
218
- // Recall status (indented to align with info above)
219
- lines.push(info.recallInstalled
220
- ? concat(tp.text(tmPad + gap), tp.success("● "), tp.success("recall"), tp.muted(" installed"))
221
- : concat(tp.text(tmPad + gap), tp.warning("○ "), tp.warning("recall"), tp.muted(" not installed")));
199
+ // Recall status (bundled as dependency)
200
+ lines.push(concat(tp.text(tmPad + gap), tp.success("● "), tp.success("recall"), tp.muted(" bundled")));
222
201
  // blank
223
202
  lines.push("");
224
203
  this._rosterStart = lines.length;
@@ -241,10 +220,7 @@ class AnimatedBanner extends Control {
241
220
  ["/retro", "run retrospective"],
242
221
  ];
243
222
  const col3 = [
244
- [
245
- info.recallInstalled ? "/copy" : "/install",
246
- info.recallInstalled ? "copy session text" : "add a service",
247
- ],
223
+ ["/copy", "copy session text"],
248
224
  ["/help", "all commands"],
249
225
  ["/exit", "exit session"],
250
226
  ];
@@ -447,7 +423,6 @@ class TeammatesREPL {
447
423
  }
448
424
  adapterName;
449
425
  teammatesDir;
450
- recallWatchProcess = null;
451
426
  taskQueue = [];
452
427
  /** Per-agent active tasks — one per agent running in parallel. */
453
428
  agentActive = new Map();
@@ -465,28 +440,10 @@ class TeammatesREPL {
465
440
  lastCleanedOutput = ""; // last teammate output for clipboard copy
466
441
  dispatching = false;
467
442
  autoApproveHandoffs = false;
468
- /** Read .teammates/settings.json (returns { version, services, ... } or defaults). */
469
- readSettings() {
470
- try {
471
- const raw = JSON.parse(readFileSync(join(this.teammatesDir, "settings.json"), "utf-8"));
472
- return {
473
- version: raw.version ?? 1,
474
- services: Array.isArray(raw.services) ? raw.services : [],
475
- ...raw,
476
- };
477
- }
478
- catch {
479
- return { version: 1, services: [] };
480
- }
481
- }
482
- /** Write .teammates/settings.json. */
483
- writeSettings(settings) {
484
- writeFileSync(join(this.teammatesDir, "settings.json"), `${JSON.stringify(settings, null, 2)}\n`);
485
- }
486
- /** Check whether a specific service is installed. */
487
- isServiceInstalled(name) {
488
- return this.readSettings().services.some((s) => s.name === name);
489
- }
443
+ /** Last debug log file path per teammate for /debug analysis. */
444
+ lastDebugFiles = new Map();
445
+ /** Last task prompt per teammate — for /debug analysis. */
446
+ lastTaskPrompts = new Map();
490
447
  /** Pending handoffs awaiting user approval. */
491
448
  pendingHandoffs = [];
492
449
  /** Pending retro proposals awaiting user approval. */
@@ -1239,11 +1196,13 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
1239
1196
  this.kickDrain();
1240
1197
  return;
1241
1198
  }
1242
- // No mentions — auto-route: resolve teammate synchronously if possible, else use default
1243
- let match = this.orchestrator.route(input);
1199
+ // No mentions — default to the teammate you're chatting with, then try auto-route
1200
+ let match = null;
1201
+ if (this.lastResult) {
1202
+ match = this.lastResult.teammate;
1203
+ }
1244
1204
  if (!match) {
1245
- // Fall back to adapter name — avoid blocking for agent routing
1246
- match = this.adapterName;
1205
+ match = this.orchestrator.route(input) ?? this.adapterName;
1247
1206
  }
1248
1207
  {
1249
1208
  const bg = this._userBg;
@@ -1717,17 +1676,6 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
1717
1676
  };
1718
1677
  /** Build param-completion items for the current line, if any. */
1719
1678
  getParamItems(cmdName, argsBefore, partial) {
1720
- // Service-name completions for /install
1721
- if (cmdName === "install" && !argsBefore.trim()) {
1722
- const lower = partial.toLowerCase();
1723
- return Object.entries(SERVICE_REGISTRY)
1724
- .filter(([name]) => name.startsWith(lower))
1725
- .map(([name, svc]) => ({
1726
- label: name,
1727
- description: svc.description,
1728
- completion: `/install ${name} `,
1729
- }));
1730
- }
1731
1679
  const positions = TeammatesREPL.TEAMMATE_ARG_POSITIONS[cmdName];
1732
1680
  if (!positions)
1733
1681
  return [];
@@ -1996,20 +1944,6 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
1996
1944
  return { name: t.name, role: t.role, ownership: t.ownership };
1997
1945
  });
1998
1946
  }
1999
- // Detect installed services from settings.json and tell the adapter
2000
- if ("services" in this.adapter) {
2001
- const services = [];
2002
- if (this.isServiceInstalled("recall")) {
2003
- services.push({
2004
- name: "recall",
2005
- description: "Local semantic search across teammate memories and daily logs. Use this to find relevant context before starting a task.",
2006
- usage: 'teammates-recall search "your query" --dir .teammates',
2007
- });
2008
- }
2009
- this.adapter.services = services;
2010
- }
2011
- // Start recall watch mode if recall is installed
2012
- this.startRecallWatch();
2013
1947
  // Background maintenance: compact stale dailies + sync recall indexes
2014
1948
  this.startupMaintenance().catch(() => { });
2015
1949
  // Register commands
@@ -2024,6 +1958,7 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
2024
1958
  const validNames = new Set([
2025
1959
  ...this.orchestrator.listTeammates(),
2026
1960
  this.adapterName,
1961
+ "everyone",
2027
1962
  ]);
2028
1963
  return value
2029
1964
  .replace(/@(\w+)/g, (match, name) => validNames.has(name) ? chalk.blue(match) : match)
@@ -2061,12 +1996,10 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
2061
1996
  // ── Build animated banner for ChatView ─────────────────────────────
2062
1997
  const names = this.orchestrator.listTeammates();
2063
1998
  const reg = this.orchestrator.getRegistry();
2064
- const hasRecall = this.isServiceInstalled("recall");
2065
1999
  const bannerWidget = new AnimatedBanner({
2066
2000
  adapterName: this.adapterName,
2067
2001
  teammateCount: names.length,
2068
2002
  cwd: process.cwd(),
2069
- recallInstalled: hasRecall,
2070
2003
  teammates: names.map((name) => {
2071
2004
  const t = reg.get(name);
2072
2005
  return { name, role: t?.role ?? "" };
@@ -2098,6 +2031,7 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
2098
2031
  const validNames = new Set([
2099
2032
  ...this.orchestrator.listTeammates(),
2100
2033
  this.adapterName,
2034
+ "everyone",
2101
2035
  ]);
2102
2036
  const mentionPattern = /@(\w+)/g;
2103
2037
  while ((m = mentionPattern.exec(value)) !== null) {
@@ -2233,7 +2167,6 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
2233
2167
  this.ctrlcTimer = null;
2234
2168
  }
2235
2169
  this.chatView.setFooter(this.defaultFooter);
2236
- this.stopRecallWatch();
2237
2170
  if (this.app)
2238
2171
  this.app.stop();
2239
2172
  this.orchestrator.shutdown().then(() => process.exit(0));
@@ -2275,6 +2208,9 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
2275
2208
  }
2276
2209
  }
2277
2210
  });
2211
+ this.chatView.on("copy", (text) => {
2212
+ this.doCopy(text);
2213
+ });
2278
2214
  this.chatView.on("link", (url) => {
2279
2215
  const quoted = JSON.stringify(url);
2280
2216
  const cmd = process.platform === "darwin"
@@ -2284,6 +2220,15 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
2284
2220
  : `xdg-open ${quoted}`;
2285
2221
  execCb(cmd, () => { });
2286
2222
  });
2223
+ this.chatView.on("file", (filePath) => {
2224
+ const quoted = JSON.stringify(filePath);
2225
+ const cmd = process.platform === "darwin"
2226
+ ? `open ${quoted}`
2227
+ : process.platform === "win32"
2228
+ ? `start "" ${quoted}`
2229
+ : `xdg-open ${quoted}`;
2230
+ execCb(cmd, () => { });
2231
+ });
2287
2232
  this.app = new App({
2288
2233
  root: this.chatView,
2289
2234
  alternateScreen: true,
@@ -2453,15 +2398,11 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
2453
2398
  printBanner(teammates) {
2454
2399
  const registry = this.orchestrator.getRegistry();
2455
2400
  const termWidth = process.stdout.columns || 100;
2456
- // Detect recall from settings.json
2457
- const recallInstalled = this.isServiceInstalled("recall");
2458
2401
  this.feedLine();
2459
2402
  this.feedLine(concat(tp.bold(" Teammates"), tp.muted(` v${PKG_VERSION}`)));
2460
2403
  this.feedLine(concat(tp.text(` ${this.adapterName}`), tp.muted(` · ${teammates.length} teammate${teammates.length === 1 ? "" : "s"}`)));
2461
2404
  this.feedLine(` ${process.cwd()}`);
2462
- this.feedLine(recallInstalled
2463
- ? tp.success(" ● recall installed")
2464
- : tp.warning(" ○ recall not installed"));
2405
+ this.feedLine(concat(tp.success(" ● recall"), tp.muted(" bundled")));
2465
2406
  // Roster
2466
2407
  this.feedLine();
2467
2408
  for (const name of teammates) {
@@ -2480,11 +2421,11 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
2480
2421
  // First run — no teammates yet
2481
2422
  col1 = [
2482
2423
  ["/init", "set up teammates"],
2483
- ["/install", "add a service"],
2424
+ ["/help", "all commands"],
2484
2425
  ];
2485
2426
  col2 = [
2486
- ["/help", "all commands"],
2487
2427
  ["/exit", "exit session"],
2428
+ ["", ""],
2488
2429
  ];
2489
2430
  col3 = [
2490
2431
  ["", ""],
@@ -2503,10 +2444,7 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
2503
2444
  ["/retro", "run retrospective"],
2504
2445
  ];
2505
2446
  col3 = [
2506
- [
2507
- recallInstalled ? "/copy" : "/install",
2508
- recallInstalled ? "copy session text" : "add a service",
2509
- ],
2447
+ ["/copy", "copy session text"],
2510
2448
  ["/help", "all commands"],
2511
2449
  ["/exit", "exit session"],
2512
2450
  ];
@@ -2537,7 +2475,7 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
2537
2475
  name: "debug",
2538
2476
  aliases: ["raw"],
2539
2477
  usage: "/debug [teammate]",
2540
- description: "Show raw agent output from the last task",
2478
+ description: "Analyze the last agent task with the coding agent",
2541
2479
  run: (args) => this.cmdDebug(args),
2542
2480
  },
2543
2481
  {
@@ -2561,13 +2499,6 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
2561
2499
  description: "Clear history and reset the session",
2562
2500
  run: () => this.cmdClear(),
2563
2501
  },
2564
- {
2565
- name: "install",
2566
- aliases: [],
2567
- usage: "/install [service]",
2568
- description: "Install a teammates service (e.g. recall)",
2569
- run: (args) => this.cmdInstall(args),
2570
- },
2571
2502
  {
2572
2503
  name: "compact",
2573
2504
  aliases: [],
@@ -2617,7 +2548,6 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
2617
2548
  description: "Exit the session",
2618
2549
  run: async () => {
2619
2550
  this.feedLine(tp.muted("Shutting down..."));
2620
- this.stopRecallWatch();
2621
2551
  if (this.app)
2622
2552
  this.app.stop();
2623
2553
  await this.orchestrator.shutdown();
@@ -2695,6 +2625,19 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
2695
2625
  else {
2696
2626
  this.feedLine(tp.muted(" (no response text — the agent may have only performed tool actions)"));
2697
2627
  this.feedLine(tp.muted(` Use /debug ${event.result.teammate} to view full output`));
2628
+ // Show diagnostic hints for empty responses
2629
+ const diag = event.result.diagnostics;
2630
+ if (diag) {
2631
+ if (diag.exitCode !== 0 && diag.exitCode !== null) {
2632
+ this.feedLine(tp.warning(` ⚠ Process exited with code ${diag.exitCode}`));
2633
+ }
2634
+ if (diag.signal) {
2635
+ this.feedLine(tp.warning(` ⚠ Process killed by signal: ${diag.signal}`));
2636
+ }
2637
+ if (diag.debugFile) {
2638
+ this.feedLine(tp.muted(` Debug log: ${diag.debugFile}`));
2639
+ }
2640
+ }
2698
2641
  }
2699
2642
  // Render handoffs
2700
2643
  const handoffs = event.result.handoffs;
@@ -2796,99 +2739,81 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
2796
2739
  }
2797
2740
  async cmdDebug(argsStr) {
2798
2741
  const arg = argsStr.trim().replace(/^@/, "");
2799
- // Resolve which teammates to show debug info for
2800
- let targetNames;
2742
+ // Resolve which teammate to debug
2743
+ let targetName;
2801
2744
  if (arg === "everyone") {
2802
- targetNames = [];
2803
- for (const [name] of this.lastResults) {
2745
+ // Pick all teammates with debug files, queue one analysis per teammate
2746
+ const names = [];
2747
+ for (const [name] of this.lastDebugFiles) {
2804
2748
  if (name !== this.adapterName)
2805
- targetNames.push(name);
2749
+ names.push(name);
2806
2750
  }
2807
- if (targetNames.length === 0) {
2751
+ if (names.length === 0) {
2808
2752
  this.feedLine(tp.muted(" No debug info available from any teammate."));
2809
2753
  this.refreshView();
2810
2754
  return;
2811
2755
  }
2756
+ for (const name of names) {
2757
+ this.queueDebugAnalysis(name);
2758
+ }
2759
+ return;
2812
2760
  }
2813
2761
  else if (arg) {
2814
- targetNames = [arg];
2762
+ targetName = arg;
2815
2763
  }
2816
2764
  else if (this.lastResult) {
2817
- targetNames = [this.lastResult.teammate];
2765
+ targetName = this.lastResult.teammate;
2818
2766
  }
2819
2767
  else {
2820
2768
  this.feedLine(tp.muted(" No debug info available. Try: /debug [teammate]"));
2821
2769
  this.refreshView();
2822
2770
  return;
2823
2771
  }
2824
- let debugText = "";
2825
- for (const name of targetNames) {
2826
- this.feedLine();
2827
- this.feedLine(tp.muted(` ── debug for ${name} ──`));
2828
- // Read the last debug entry from the session file
2829
- const sessionEntry = this.readLastDebugEntry(name);
2830
- if (sessionEntry) {
2831
- this.feedLine();
2832
- this.feedMarkdown(sessionEntry);
2833
- debugText += (debugText ? "\n\n" : "") + sessionEntry;
2834
- }
2835
- else {
2836
- // Fall back to raw output from lastResults
2837
- const result = this.lastResults.get(name);
2838
- if (result?.rawOutput) {
2839
- this.feedLine();
2840
- this.feedMarkdown(result.rawOutput);
2841
- debugText += (debugText ? "\n\n" : "") + result.rawOutput;
2842
- }
2843
- else {
2844
- this.feedLine(tp.muted(" No debug info available."));
2845
- }
2846
- }
2847
- this.feedLine();
2848
- this.feedLine(tp.muted(" ── end debug ──"));
2849
- }
2850
- // [copy] action for the debug output
2851
- if (this.chatView && debugText) {
2852
- const t = theme();
2853
- this.lastCleanedOutput = debugText;
2854
- this.chatView.appendActionList([
2855
- {
2856
- id: "copy",
2857
- normalStyle: this.makeSpan({
2858
- text: " [copy]",
2859
- style: { fg: t.textDim },
2860
- }),
2861
- hoverStyle: this.makeSpan({
2862
- text: " [copy]",
2863
- style: { fg: t.accent },
2864
- }),
2865
- },
2866
- ]);
2867
- }
2868
- this.feedLine();
2869
- this.refreshView();
2772
+ this.queueDebugAnalysis(targetName);
2870
2773
  }
2871
2774
  /**
2872
- * Read the last debug entry from a teammate's session file.
2873
- * Debug entries are delimited by "## Debug — " headings.
2775
+ * Queue a debug analysis task sends the last request + debug log
2776
+ * to the base coding agent for analysis.
2874
2777
  */
2875
- readLastDebugEntry(teammate) {
2778
+ queueDebugAnalysis(teammate) {
2779
+ const debugFile = this.lastDebugFiles.get(teammate);
2780
+ const lastPrompt = this.lastTaskPrompts.get(teammate);
2781
+ if (!debugFile) {
2782
+ this.feedLine(tp.muted(` No debug log available for @${teammate}.`));
2783
+ this.refreshView();
2784
+ return;
2785
+ }
2786
+ // Read the debug log file
2787
+ let debugContent;
2876
2788
  try {
2877
- const sessionFile = this.adapter.getSessionFile?.(teammate);
2878
- if (!sessionFile)
2879
- return null;
2880
- const content = readFileSync(sessionFile, "utf-8");
2881
- // Split on debug entry headings and return the last one
2882
- const entries = content.split(/(?=^## Debug — )/m);
2883
- const last = entries[entries.length - 1];
2884
- if (last && last.startsWith("## Debug — ")) {
2885
- return last.trim();
2886
- }
2887
- return null;
2789
+ debugContent = readFileSync(debugFile, "utf-8");
2888
2790
  }
2889
2791
  catch {
2890
- return null;
2792
+ this.feedLine(tp.muted(` Could not read debug log: ${debugFile}`));
2793
+ this.refreshView();
2794
+ return;
2891
2795
  }
2796
+ const analysisPrompt = [
2797
+ `Analyze the following debug log from @${teammate}'s last task execution. Identify any issues, errors, or anomalies. If the response was empty, explain likely causes. Provide a concise diagnosis and suggest fixes if applicable.`,
2798
+ "",
2799
+ "## Last Request Sent to Agent",
2800
+ "",
2801
+ lastPrompt ?? "(not available)",
2802
+ "",
2803
+ "## Debug Log",
2804
+ "",
2805
+ debugContent,
2806
+ ].join("\n");
2807
+ // Show the debug log path — ctrl+click to open
2808
+ this.feedLine(concat(tp.muted(" Debug log: "), tp.accent(debugFile)));
2809
+ this.feedLine(tp.muted(" Queuing analysis…"));
2810
+ this.refreshView();
2811
+ this.taskQueue.push({
2812
+ type: "debug",
2813
+ teammate: this.adapterName,
2814
+ task: analysisPrompt,
2815
+ });
2816
+ this.kickDrain();
2892
2817
  }
2893
2818
  async cmdCancel(argsStr) {
2894
2819
  const n = parseInt(argsStr.trim(), 10);
@@ -2920,17 +2845,21 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
2920
2845
  await this.runCompact(entry.teammate);
2921
2846
  }
2922
2847
  else {
2923
- // btw tasks skip conversation context (side question, not part of main thread)
2924
- const extraContext = entry.type === "btw" ? "" : this.buildConversationContext();
2848
+ // btw and debug tasks skip conversation context (not part of main thread)
2849
+ const extraContext = entry.type === "btw" || entry.type === "debug"
2850
+ ? ""
2851
+ : this.buildConversationContext();
2925
2852
  const result = await this.orchestrator.assign({
2926
2853
  teammate: entry.teammate,
2927
2854
  task: entry.task,
2928
2855
  extraContext: extraContext || undefined,
2929
2856
  });
2930
- // Write debug entry to session file
2931
- this.writeDebugEntry(entry.teammate, entry.task, result, startTime);
2932
- // btw results are not stored in conversation history
2933
- if (entry.type !== "btw") {
2857
+ // Write debug entry skip for debug analysis tasks (avoid recursion)
2858
+ if (entry.type !== "debug") {
2859
+ this.writeDebugEntry(entry.teammate, entry.task, result, startTime);
2860
+ }
2861
+ // btw and debug results are not stored in conversation history
2862
+ if (entry.type !== "btw" && entry.type !== "debug") {
2934
2863
  this.storeResult(result);
2935
2864
  }
2936
2865
  if (entry.type === "retro") {
@@ -2953,29 +2882,42 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
2953
2882
  }
2954
2883
  }
2955
2884
  /**
2956
- * Append a debug entry to the teammate's session file.
2957
- * Captures task prompt, result summary, raw output, timing, and errors.
2885
+ * Write a debug log file to .teammates/.tmp/debug/ for the task.
2886
+ * Each task gets its own file. The path is stored in lastDebugFiles for /debug.
2958
2887
  */
2959
2888
  writeDebugEntry(teammate, task, result, startTime, error) {
2960
2889
  try {
2961
- const sessionFile = this.adapter.getSessionFile?.(teammate);
2962
- if (!sessionFile)
2890
+ const debugDir = join(this.teammatesDir, ".tmp", "debug");
2891
+ try {
2892
+ mkdirSync(debugDir, { recursive: true });
2893
+ }
2894
+ catch {
2963
2895
  return;
2896
+ }
2964
2897
  const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
2965
2898
  const timestamp = new Date().toISOString();
2899
+ const ts = timestamp.replace(/[:.]/g, "-");
2900
+ const debugFile = join(debugDir, `${teammate}-${ts}.md`);
2966
2901
  const lines = [
2902
+ `# Debug — ${teammate}`,
2967
2903
  "",
2968
- `## Debug — ${timestamp}`,
2969
- "",
2904
+ `**Timestamp:** ${timestamp}`,
2970
2905
  `**Duration:** ${elapsed}s`,
2971
- `**Task:** ${task.length > 200 ? `${task.slice(0, 200)}…` : task}`,
2906
+ "",
2907
+ "## Request",
2908
+ "",
2909
+ task,
2972
2910
  "",
2973
2911
  ];
2974
2912
  if (error) {
2913
+ lines.push("## Result");
2914
+ lines.push("");
2975
2915
  lines.push(`**Status:** ERROR`);
2976
2916
  lines.push(`**Error:** ${error?.message ?? String(error)}`);
2977
2917
  }
2978
2918
  else if (result) {
2919
+ lines.push("## Result");
2920
+ lines.push("");
2979
2921
  lines.push(`**Status:** ${result.success ? "OK" : "FAILED"}`);
2980
2922
  lines.push(`**Summary:** ${result.summary || "(no summary)"}`);
2981
2923
  if (result.changedFiles.length > 0) {
@@ -2984,15 +2926,46 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
2984
2926
  if (result.handoffs.length > 0) {
2985
2927
  lines.push(`**Handoffs:** ${result.handoffs.map((h) => `@${h.to}`).join(", ")}`);
2986
2928
  }
2929
+ // Process diagnostics — exit code, signal, stderr
2930
+ const diag = result.diagnostics;
2931
+ if (diag) {
2932
+ lines.push("");
2933
+ lines.push("### Process");
2934
+ lines.push(`**Exit code:** ${diag.exitCode ?? "(killed by signal)"}`);
2935
+ if (diag.signal)
2936
+ lines.push(`**Signal:** ${diag.signal}`);
2937
+ if (diag.timedOut)
2938
+ lines.push(`**Timed out:** yes`);
2939
+ if (diag.debugFile) {
2940
+ lines.push(`**Agent debug log:** ${diag.debugFile}`);
2941
+ // Inline Claude's debug file content if it exists
2942
+ try {
2943
+ const agentDebugContent = readFileSync(diag.debugFile, "utf-8");
2944
+ lines.push("");
2945
+ lines.push("### Agent Debug Log");
2946
+ lines.push("");
2947
+ lines.push(agentDebugContent);
2948
+ }
2949
+ catch {
2950
+ /* debug file may not exist yet or be unreadable */
2951
+ }
2952
+ }
2953
+ if (diag.stderr.trim()) {
2954
+ lines.push("");
2955
+ lines.push("### stderr");
2956
+ lines.push("");
2957
+ lines.push(diag.stderr);
2958
+ }
2959
+ }
2987
2960
  lines.push("");
2988
- lines.push("<details><summary>Raw output</summary>");
2961
+ lines.push("### Raw Output");
2989
2962
  lines.push("");
2990
2963
  lines.push(result.rawOutput ?? "(empty)");
2991
- lines.push("");
2992
- lines.push("</details>");
2993
2964
  }
2994
2965
  lines.push("");
2995
- appendFileSync(sessionFile, lines.join("\n"), "utf-8");
2966
+ writeFileSync(debugFile, lines.join("\n"), "utf-8");
2967
+ this.lastDebugFiles.set(teammate, debugFile);
2968
+ this.lastTaskPrompts.set(teammate, task);
2996
2969
  }
2997
2970
  catch {
2998
2971
  // Don't let debug logging break task execution
@@ -3070,123 +3043,6 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
3070
3043
  this.feedLine(tp.muted(" Run /status to see the roster."));
3071
3044
  this.refreshView();
3072
3045
  }
3073
- async cmdInstall(argsStr) {
3074
- const serviceName = argsStr.trim().toLowerCase();
3075
- if (!serviceName) {
3076
- this.feedLine(tp.bold("\n Available services:"));
3077
- for (const [name, svc] of Object.entries(SERVICE_REGISTRY)) {
3078
- this.feedLine(concat(tp.accent(name.padEnd(16)), tp.muted(svc.description)));
3079
- }
3080
- this.feedLine();
3081
- this.refreshView();
3082
- return;
3083
- }
3084
- const service = SERVICE_REGISTRY[serviceName];
3085
- if (!service) {
3086
- this.feedLine(tp.warning(` Unknown service: ${serviceName}`));
3087
- this.feedLine(tp.muted(` Available: ${Object.keys(SERVICE_REGISTRY).join(", ")}`));
3088
- this.refreshView();
3089
- return;
3090
- }
3091
- // Install the package globally
3092
- if (this.chatView) {
3093
- this.chatView.setProgress(`Installing ${service.package}...`);
3094
- this.refreshView();
3095
- }
3096
- let installSpinner = null;
3097
- if (!this.chatView) {
3098
- installSpinner = ora({
3099
- text: chalk.blue(serviceName) +
3100
- chalk.gray(` installing ${service.package}...`),
3101
- spinner: "dots",
3102
- }).start();
3103
- }
3104
- try {
3105
- await execAsync(`npm install -g ${service.package}`, {
3106
- timeout: 5 * 60 * 1000,
3107
- });
3108
- if (installSpinner)
3109
- installSpinner.stop();
3110
- if (this.chatView)
3111
- this.chatView.setProgress(null);
3112
- }
3113
- catch (err) {
3114
- if (installSpinner)
3115
- installSpinner.fail(chalk.red(`Install failed: ${err.message}`));
3116
- if (this.chatView) {
3117
- this.chatView.setProgress(null);
3118
- this.feedLine(tp.error(` ✖ Install failed: ${err.message}`));
3119
- this.refreshView();
3120
- }
3121
- return;
3122
- }
3123
- // Verify the binary works
3124
- const checkCmdStr = service.checkCmd.join(" ");
3125
- try {
3126
- execSync(checkCmdStr, { stdio: "ignore" });
3127
- }
3128
- catch {
3129
- this.feedLine(tp.success(` ✔ ${serviceName} installed`));
3130
- this.feedLine(tp.warning(` ⚠ Restart your terminal to add ${service.checkCmd[0]} to your PATH, then run /install ${serviceName} again to build the index.`));
3131
- this.refreshView();
3132
- return;
3133
- }
3134
- this.feedLine(tp.success(` ✔ ${serviceName} installed successfully`));
3135
- // Register in settings.json
3136
- const settings = this.readSettings();
3137
- if (!settings.services.some((s) => s.name === serviceName)) {
3138
- settings.services.push({ name: serviceName });
3139
- this.writeSettings(settings);
3140
- this.feedLine(tp.muted(` Registered in settings.json`));
3141
- }
3142
- // Build initial index if this service supports it
3143
- if (service.indexCmd) {
3144
- if (this.chatView) {
3145
- this.chatView.setProgress(`Building ${serviceName} index...`);
3146
- this.refreshView();
3147
- }
3148
- let idxSpinner = null;
3149
- if (!this.chatView) {
3150
- idxSpinner = ora({
3151
- text: chalk.blue(serviceName) + chalk.gray(` building index...`),
3152
- spinner: "dots",
3153
- }).start();
3154
- }
3155
- const indexCmdStr = service.indexCmd.join(" ");
3156
- try {
3157
- await execAsync(indexCmdStr, {
3158
- cwd: resolve(this.teammatesDir, ".."),
3159
- timeout: 5 * 60 * 1000,
3160
- });
3161
- if (idxSpinner)
3162
- idxSpinner.succeed(chalk.blue(serviceName) + chalk.gray(" index built"));
3163
- if (this.chatView) {
3164
- this.chatView.setProgress(null);
3165
- this.feedLine(tp.success(` ✔ ${serviceName} index built`));
3166
- }
3167
- }
3168
- catch (err) {
3169
- if (idxSpinner)
3170
- idxSpinner.warn(chalk.yellow(`Index build failed: ${err.message}`));
3171
- if (this.chatView) {
3172
- this.chatView.setProgress(null);
3173
- this.feedLine(tp.warning(` ⚠ Index build failed: ${err.message}`));
3174
- }
3175
- }
3176
- }
3177
- // Ask the coding agent to wire the service into the project
3178
- if (service.wireupTask) {
3179
- this.feedLine();
3180
- this.feedLine(tp.muted(` Wiring up ${serviceName}...`));
3181
- this.refreshView();
3182
- const result = await this.orchestrator.assign({
3183
- teammate: this.adapterName,
3184
- task: service.wireupTask,
3185
- });
3186
- this.storeResult(result);
3187
- }
3188
- this.refreshView();
3189
- }
3190
3046
  async cmdClear() {
3191
3047
  this.conversationHistory.length = 0;
3192
3048
  this.lastResult = null;
@@ -3235,35 +3091,10 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
3235
3091
  })
3236
3092
  .catch(() => { });
3237
3093
  }
3238
- startRecallWatch() {
3239
- // Only start if recall is installed (check settings.json)
3240
- if (!this.isServiceInstalled("recall"))
3241
- return;
3242
- try {
3243
- this.recallWatchProcess = cpSpawn("teammates-recall", ["watch", "--dir", this.teammatesDir, "--json"], {
3244
- stdio: ["ignore", "ignore", "ignore"],
3245
- detached: false,
3246
- });
3247
- this.recallWatchProcess.on("error", () => {
3248
- // Recall binary not found — silently ignore
3249
- this.recallWatchProcess = null;
3250
- });
3251
- this.recallWatchProcess.on("exit", () => {
3252
- this.recallWatchProcess = null;
3253
- });
3254
- }
3255
- catch {
3256
- this.recallWatchProcess = null;
3257
- }
3258
- }
3259
- stopRecallWatch() {
3260
- if (this.recallWatchProcess) {
3261
- this.recallWatchProcess.kill("SIGTERM");
3262
- this.recallWatchProcess = null;
3263
- }
3264
- }
3094
+ // Recall is now bundled as a library dependency — no watch process needed.
3095
+ // Sync happens via syncRecallIndex() after every task and on startup.
3265
3096
  async cmdCompact(argsStr) {
3266
- const arg = argsStr.trim();
3097
+ const arg = argsStr.trim().replace(/^@/, "");
3267
3098
  const allTeammates = this.orchestrator
3268
3099
  .listTeammates()
3269
3100
  .filter((n) => n !== this.adapterName);
@@ -3341,32 +3172,47 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
3341
3172
  }
3342
3173
  if (this.chatView)
3343
3174
  this.chatView.setProgress(null);
3344
- // Trigger recall sync if installed
3345
- if (this.isServiceInstalled("recall")) {
3346
- try {
3347
- if (this.chatView) {
3348
- this.chatView.setProgress(`Syncing ${name} index...`);
3349
- this.refreshView();
3350
- }
3351
- let syncSpinner = null;
3352
- if (!this.chatView) {
3353
- syncSpinner = ora({
3354
- text: `Syncing ${name} index...`,
3355
- color: "cyan",
3356
- }).start();
3357
- }
3358
- await execAsync(`teammates-recall sync --dir "${this.teammatesDir}"`);
3359
- if (syncSpinner)
3360
- syncSpinner.succeed(`${name}: index synced`);
3361
- if (this.chatView) {
3362
- this.chatView.setProgress(null);
3363
- this.feedLine(tp.success(` ✔ ${name}: index synced`));
3364
- }
3175
+ // Sync recall index for this teammate (bundled library call)
3176
+ try {
3177
+ if (this.chatView) {
3178
+ this.chatView.setProgress(`Syncing ${name} index...`);
3179
+ this.refreshView();
3180
+ }
3181
+ let syncSpinner = null;
3182
+ if (!this.chatView) {
3183
+ syncSpinner = ora({
3184
+ text: `Syncing ${name} index...`,
3185
+ color: "cyan",
3186
+ }).start();
3365
3187
  }
3366
- catch {
3367
- /* sync failed — non-fatal */
3188
+ await syncRecallIndex(this.teammatesDir, name);
3189
+ if (syncSpinner)
3190
+ syncSpinner.succeed(`${name}: index synced`);
3191
+ if (this.chatView) {
3192
+ this.chatView.setProgress(null);
3193
+ this.feedLine(tp.success(` ✔ ${name}: index synced`));
3194
+ }
3195
+ }
3196
+ catch {
3197
+ /* sync failed — non-fatal */
3198
+ }
3199
+ // Queue wisdom distillation agent task
3200
+ try {
3201
+ const teammateDir = join(this.teammatesDir, name);
3202
+ const wisdomPrompt = await buildWisdomPrompt(teammateDir, name);
3203
+ if (wisdomPrompt) {
3204
+ this.taskQueue.push({
3205
+ type: "agent",
3206
+ teammate: name,
3207
+ task: wisdomPrompt,
3208
+ });
3209
+ if (this.chatView)
3210
+ this.feedLine(tp.muted(` ↻ ${name}: queued wisdom distillation`));
3368
3211
  }
3369
3212
  }
3213
+ catch {
3214
+ /* wisdom prompt build failed — non-fatal */
3215
+ }
3370
3216
  }
3371
3217
  catch (err) {
3372
3218
  const msg = err instanceof Error ? err.message : String(err);
@@ -3462,9 +3308,9 @@ Issues that can't be resolved unilaterally — they need input from other teamma
3462
3308
  const fullPath = join(dir, entry.name);
3463
3309
  if (entry.isDirectory()) {
3464
3310
  await this.cleanOldTempFiles(fullPath, maxAgeMs);
3465
- // Remove dir if now empty — but skip "sessions" which is structural
3466
- // and may be recreated concurrently by startSession().
3467
- if (entry.name !== "sessions") {
3311
+ // Remove dir if now empty — but skip structural dirs that are
3312
+ // recreated concurrently (sessions by startSession, debug by writeDebugEntry).
3313
+ if (entry.name !== "sessions" && entry.name !== "debug") {
3468
3314
  const remaining = await readdir(fullPath).catch(() => [""]);
3469
3315
  if (remaining.length === 0)
3470
3316
  await rm(fullPath, { recursive: true }).catch(() => { });
@@ -3479,8 +3325,16 @@ Issues that can't be resolved unilaterally — they need input from other teamma
3479
3325
  }
3480
3326
  }
3481
3327
  async startupMaintenance() {
3482
- // Clean up .teammates/.tmp files older than 1 week
3483
3328
  const tmpDir = join(this.teammatesDir, ".tmp");
3329
+ // Clean up debug log files older than 1 day
3330
+ const debugDir = join(tmpDir, "debug");
3331
+ try {
3332
+ await this.cleanOldTempFiles(debugDir, 24 * 60 * 60 * 1000);
3333
+ }
3334
+ catch {
3335
+ /* debug dir may not exist yet — non-fatal */
3336
+ }
3337
+ // Clean up other .tmp files older than 1 week
3484
3338
  try {
3485
3339
  await this.cleanOldTempFiles(tmpDir, 7 * 24 * 60 * 60 * 1000);
3486
3340
  }
@@ -3492,8 +3346,6 @@ Issues that can't be resolved unilaterally — they need input from other teamma
3492
3346
  .filter((n) => n !== this.adapterName);
3493
3347
  if (teammates.length === 0)
3494
3348
  return;
3495
- // Check if recall is installed
3496
- const recallInstalled = this.isServiceInstalled("recall");
3497
3349
  // 1. Check each teammate for stale daily logs (older than 7 days)
3498
3350
  const oneWeekAgo = new Date();
3499
3351
  oneWeekAgo.setDate(oneWeekAgo.getDate() - 7);
@@ -3523,14 +3375,12 @@ Issues that can't be resolved unilaterally — they need input from other teamma
3523
3375
  await this.runCompact(name);
3524
3376
  }
3525
3377
  }
3526
- // 2. Sync recall indexes if installed
3527
- if (recallInstalled) {
3528
- try {
3529
- await execAsync(`teammates-recall sync --dir "${this.teammatesDir}"`);
3530
- }
3531
- catch {
3532
- /* sync failed — non-fatal */
3533
- }
3378
+ // 2. Sync recall indexes (bundled library call)
3379
+ try {
3380
+ await syncRecallIndex(this.teammatesDir);
3381
+ }
3382
+ catch {
3383
+ /* sync failed — non-fatal */
3534
3384
  }
3535
3385
  }
3536
3386
  async cmdCopy() {