@teammates/cli 0.5.1 → 0.5.2

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/adapter.js CHANGED
@@ -225,7 +225,6 @@ export function buildTeammatePrompt(teammate, taskPrompt, options) {
225
225
  "- The `# Subject` line is REQUIRED — it becomes the message title.",
226
226
  "- Always write a substantive body. Never return just the subject.",
227
227
  "- Use markdown: headings, lists, code blocks, bold, etc.",
228
- "- **Write your text response FIRST, then update session/memory files.** This ensures visible output even if the agent turn ends early.",
229
228
  "",
230
229
  "### Handoffs",
231
230
  "",
@@ -245,10 +244,10 @@ export function buildTeammatePrompt(teammate, taskPrompt, options) {
245
244
  ];
246
245
  // Session state (conditional)
247
246
  if (options?.sessionFile) {
248
- instrLines.push("", "### Session State", "", `Your session file is at: \`${options.sessionFile}\``, "", "**After writing your text response**, append a brief entry to this file with:", "- What you did", "- Key decisions made", "- Files changed", "- Anything the next task should know", "", "This is how you maintain continuity across tasks. Always read it, always update it.");
247
+ instrLines.push("", "### Session State", "", `Your session file is at: \`${options.sessionFile}\``, "", "**After completing the task**, append a brief entry to this file with:", "- What you did", "- Key decisions made", "- Files changed", "- Anything the next task should know", "", "This is how you maintain continuity across tasks. Always read it, always update it.");
249
248
  }
250
249
  // Memory updates
251
- instrLines.push("", "### Memory Updates", "", "**After writing your text response**, update your memory files:", "", `1. **Daily log** — Read \`.teammates/${teammate.name}/memory/${today}.md\` first (it may have entries from earlier tasks today), then write it back with your entry added. Create the file if it doesn't exist.`, " - What you did", " - Key decisions made", " - Files changed", " - Anything the next task should know", "", `2. **Typed memories** — If you learned something durable (a decision, pattern, feedback, or reference), create a typed memory file at \`.teammates/${teammate.name}/memory/<type>_<topic>.md\` with frontmatter (\`name\`, \`description\`, \`type\`). Update existing memory files if the topic already has one.`, "", "3. **WISDOM.md** — Do not edit directly. Wisdom entries are distilled from typed memories during compaction.", "", "These files are your persistent memory. Without them, your next session starts from scratch.");
250
+ instrLines.push("", "### Memory Updates", "", "**After completing the task**, update your memory files:", "", `1. **Daily log** — Read \`.teammates/${teammate.name}/memory/${today}.md\` first (it may have entries from earlier tasks today), then write it back with your entry added. Create the file if it doesn't exist.`, " - What you did", " - Key decisions made", " - Files changed", " - Anything the next task should know", "", `2. **Typed memories** — If you learned something durable (a decision, pattern, feedback, or reference), create a typed memory file at \`.teammates/${teammate.name}/memory/<type>_<topic>.md\` with frontmatter (\`name\`, \`description\`, \`type\`). Update existing memory files if the topic already has one.`, "", "3. **WISDOM.md** — Do not edit directly. Wisdom entries are distilled from typed memories during compaction.", "", "These files are your persistent memory. Without them, your next session starts from scratch.");
252
251
  // Section Reinforcement — back-references from high-attention bottom edge to each section tag
253
252
  instrLines.push("", "### Section Reinforcement", "");
254
253
  instrLines.push("- Stay in character as defined in `<IDENTITY>` — never break persona or speak as a generic assistant.");
@@ -274,7 +273,7 @@ export function buildTeammatePrompt(teammate, taskPrompt, options) {
274
273
  if (options?.handoffContext) {
275
274
  instrLines.push("- When `<HANDOFF_CONTEXT>` is present, address its requirements and open questions directly.");
276
275
  }
277
- instrLines.push("- Your response must answer `<TASK>` — everything else is supporting context.", "", "**REMINDER: Write your text response (TO: user) FIRST, then update session/memory files. A turn with only file edits and no text output is a failed turn.**");
276
+ instrLines.push("- Your response must answer `<TASK>` — everything else is supporting context.", "", "**REMINDER: You MUST end your turn with visible text output. A turn with only file edits and no text is a failed turn.**");
278
277
  parts.push(instrLines.join("\n"));
279
278
  return parts.join("\n");
280
279
  }
@@ -210,6 +210,7 @@ export class CliProxyAdapter {
210
210
  : spawn.output;
211
211
  const teammateNames = this.roster.map((r) => r.name);
212
212
  const result = parseResult(teammate.name, output, teammateNames, prompt);
213
+ result.fullPrompt = fullPrompt;
213
214
  result.diagnostics = {
214
215
  exitCode: spawn.exitCode,
215
216
  signal: spawn.signal,
@@ -152,7 +152,9 @@ export class CopilotAdapter {
152
152
  // Use the final assistant message content, fall back to collected deltas
153
153
  const output = reply?.data?.content ?? outputParts.join("");
154
154
  const teammateNames = this.roster.map((r) => r.name);
155
- return parseResult(teammate.name, output, teammateNames, prompt);
155
+ const result = parseResult(teammate.name, output, teammateNames, prompt);
156
+ result.fullPrompt = fullPrompt;
157
+ return result;
156
158
  }
157
159
  finally {
158
160
  // Disconnect the session (preserves data for potential resume)
package/dist/cli.js CHANGED
@@ -10,7 +10,7 @@
10
10
  import { exec as execCb, execSync, spawnSync } from "node:child_process";
11
11
  import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
12
12
  import { mkdir, readdir, rm, stat, unlink } from "node:fs/promises";
13
- import { dirname, join, resolve } from "node:path";
13
+ import { dirname, join, resolve, sep } from "node:path";
14
14
  import { createInterface } from "node:readline";
15
15
  import { App, ChatView, concat, esc, pen, renderMarkdown, stripAnsi, } from "@teammates/consolonia";
16
16
  import chalk from "chalk";
@@ -217,6 +217,8 @@ class TeammatesREPL {
217
217
  taskQueue = [];
218
218
  /** Per-agent active tasks — one per agent running in parallel. */
219
219
  agentActive = new Map();
220
+ /** Active system tasks — multiple can run concurrently per agent. */
221
+ systemActive = new Map();
220
222
  /** Agents currently in a silent retry — suppress all events. */
221
223
  silentAgents = new Set();
222
224
  /** Per-agent drain locks — prevents double-draining a single agent. */
@@ -330,37 +332,74 @@ class TeammatesREPL {
330
332
  this.input.setStatus(null);
331
333
  }
332
334
  }
335
+ /**
336
+ * Truncate a path for display, collapsing middle segments if too long.
337
+ * E.g. C:\source\some\deep\project → C:\source\...\project
338
+ */
339
+ static truncatePath(fullPath, maxLen = 30) {
340
+ if (fullPath.length <= maxLen)
341
+ return fullPath;
342
+ const parts = fullPath.split(sep);
343
+ if (parts.length <= 2)
344
+ return fullPath;
345
+ const last = parts[parts.length - 1];
346
+ // Keep adding segments from the front until we'd exceed maxLen
347
+ let front = parts[0];
348
+ for (let i = 1; i < parts.length - 1; i++) {
349
+ const candidate = front + sep + parts[i] + sep + "..." + sep + last;
350
+ if (candidate.length > maxLen)
351
+ break;
352
+ front += sep + parts[i];
353
+ }
354
+ return front + sep + "..." + sep + last;
355
+ }
356
+ /** Format elapsed seconds as (Ns), (Nm Ns), or (Nh Nm Ns). */
357
+ static formatElapsed(totalSeconds) {
358
+ const s = totalSeconds % 60;
359
+ const m = Math.floor(totalSeconds / 60) % 60;
360
+ const h = Math.floor(totalSeconds / 3600);
361
+ if (h > 0)
362
+ return `(${h}h ${m}m ${s}s)`;
363
+ if (m > 0)
364
+ return `(${m}m ${s}s)`;
365
+ return `(${s}s)`;
366
+ }
333
367
  /** Render one frame of the status animation. */
334
368
  renderStatusFrame() {
335
369
  if (this.activeTasks.size === 0)
336
370
  return;
337
371
  const entries = Array.from(this.activeTasks.values());
338
- const idx = this.statusRotateIndex % entries.length;
339
- const { teammate, task } = entries[idx];
372
+ const total = entries.length;
373
+ const idx = this.statusRotateIndex % total;
374
+ const { teammate, task, startTime } = entries[idx];
340
375
  const displayName = teammate === this.selfName ? this.adapterName : teammate;
341
376
  const spinChar = TeammatesREPL.SPINNER[this.statusFrame % TeammatesREPL.SPINNER.length];
342
- const taskPreview = task.length > 50 ? `${task.slice(0, 47)}...` : task;
343
- const queueInfo = this.activeTasks.size > 1 ? ` (${idx + 1}/${this.activeTasks.size})` : "";
344
- if (this.chatView) {
345
- // Strip newlines and truncate task text for single-line display
346
- const cleanTask = task.replace(/[\r\n]+/g, " ").trim();
347
- const maxLen = Math.max(20, (process.stdout.columns || 80) - displayName.length - 10);
348
- const taskText = cleanTask.length > maxLen
349
- ? `${cleanTask.slice(0, maxLen - 1)}…`
377
+ const elapsed = Math.floor((Date.now() - startTime) / 1000);
378
+ const elapsedStr = TeammatesREPL.formatElapsed(elapsed);
379
+ // Build the tag: (1/3 - 2m 5s) when multiple, (2m 5s) when single
380
+ const tag = total > 1
381
+ ? `(${idx + 1}/${total} - ${elapsedStr.slice(1, -1)})`
382
+ : elapsedStr;
383
+ // Target 80 chars total: "<spinner> <name>... <task> <tag>"
384
+ const prefix = `${spinChar} ${displayName}... `;
385
+ const suffix = ` ${tag}`;
386
+ const maxTask = 80 - prefix.length - suffix.length;
387
+ const cleanTask = task.replace(/[\r\n]+/g, " ").trim();
388
+ const taskText = maxTask <= 3
389
+ ? ""
390
+ : cleanTask.length > maxTask
391
+ ? `${cleanTask.slice(0, maxTask - 1)}…`
350
392
  : cleanTask;
351
- const queueTag = this.activeTasks.size > 1
352
- ? ` (${idx + 1}/${this.activeTasks.size})`
353
- : "";
354
- this.chatView.setProgress(concat(tp.accent(`${spinChar} ${displayName}… `), tp.muted(taskText + queueTag)));
393
+ if (this.chatView) {
394
+ this.chatView.setProgress(concat(tp.accent(`${spinChar} ${displayName}... `), tp.muted(`${taskText}${suffix}`)));
355
395
  this.app.refresh();
356
396
  }
357
397
  else {
358
- // Mostly bright blue, periodically flicker to dark blue
359
398
  const spinColor = this.statusFrame % 8 === 0 ? chalk.blue : chalk.blueBright;
360
399
  const line = ` ${spinColor(spinChar)} ` +
361
400
  chalk.bold(displayName) +
362
- chalk.gray(`… ${taskPreview}`) +
363
- (queueInfo ? chalk.gray(queueInfo) : "");
401
+ chalk.gray(`... ${taskText}`) +
402
+ chalk.gray(suffix);
364
403
  this.input.setStatus(line);
365
404
  }
366
405
  }
@@ -1032,9 +1071,24 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
1032
1071
  this.taskQueue.push({ type: "agent", teammate: match, task: input });
1033
1072
  this.kickDrain();
1034
1073
  }
1035
- /** Start draining per-agent queues in parallel. Each agent gets its own drain loop. */
1074
+ /** Returns true if the queue entry is a system-initiated (non-blocking) task. */
1075
+ isSystemTask(entry) {
1076
+ return (entry.type === "compact" ||
1077
+ entry.type === "summarize" ||
1078
+ (entry.type === "agent" && entry.system === true));
1079
+ }
1080
+ /** Start draining per-agent queues in parallel. Each agent gets its own drain loop.
1081
+ * System tasks are extracted and run concurrently without blocking user tasks. */
1036
1082
  kickDrain() {
1037
- // Find agents that have queued tasks but no active drain
1083
+ // Extract system tasks and fire them concurrently (non-blocking)
1084
+ for (let i = this.taskQueue.length - 1; i >= 0; i--) {
1085
+ const entry = this.taskQueue[i];
1086
+ if (this.isSystemTask(entry)) {
1087
+ this.taskQueue.splice(i, 1);
1088
+ this.runSystemTask(entry);
1089
+ }
1090
+ }
1091
+ // Find agents that have user tasks but no active drain
1038
1092
  const agentsWithWork = new Set();
1039
1093
  for (const entry of this.taskQueue) {
1040
1094
  agentsWithWork.add(entry.teammate);
@@ -1048,6 +1102,53 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
1048
1102
  }
1049
1103
  }
1050
1104
  }
1105
+ /**
1106
+ * Run a system-initiated task concurrently without blocking user tasks.
1107
+ * Purely background — no progress bar, no /status. Only reports errors.
1108
+ */
1109
+ async runSystemTask(entry) {
1110
+ const taskId = `sys-${entry.teammate}-${Date.now()}`;
1111
+ this.systemActive.set(taskId, entry);
1112
+ const startTime = Date.now();
1113
+ try {
1114
+ if (entry.type === "compact") {
1115
+ await this.runCompact(entry.teammate, true);
1116
+ }
1117
+ else if (entry.type === "summarize") {
1118
+ const result = await this.orchestrator.assign({
1119
+ teammate: entry.teammate,
1120
+ task: entry.task,
1121
+ system: true,
1122
+ });
1123
+ const raw = result.rawOutput ?? "";
1124
+ this.conversationSummary = raw
1125
+ .replace(/^TO:\s*\S+\s*\n/im, "")
1126
+ .replace(/^#\s+.+\n*/m, "")
1127
+ .replace(/```json\s*\n\s*\{[\s\S]*?\}\s*\n\s*```\s*$/g, "")
1128
+ .trim();
1129
+ }
1130
+ else {
1131
+ // System agent tasks (e.g. wisdom distillation)
1132
+ const result = await this.orchestrator.assign({
1133
+ teammate: entry.teammate,
1134
+ task: entry.task,
1135
+ system: true,
1136
+ });
1137
+ // Write debug entry for system tasks too
1138
+ this.writeDebugEntry(entry.teammate, entry.task, result, startTime);
1139
+ }
1140
+ }
1141
+ catch (err) {
1142
+ // System task errors always show in feed
1143
+ const msg = err?.message ?? String(err);
1144
+ const displayName = entry.teammate === this.selfName ? this.adapterName : entry.teammate;
1145
+ this.feedLine(tp.error(` ✖ @${displayName} (system): ${msg}`));
1146
+ this.refreshView();
1147
+ }
1148
+ finally {
1149
+ this.systemActive.delete(taskId);
1150
+ }
1151
+ }
1051
1152
  // ─── Onboarding ───────────────────────────────────────────────────
1052
1153
  /**
1053
1154
  * Interactive prompt for team onboarding after user profile is set up.
@@ -2355,11 +2456,11 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
2355
2456
  progressStyle: { fg: t.progress, italic: true },
2356
2457
  dropdownHighlightStyle: { fg: t.accent },
2357
2458
  dropdownStyle: { fg: t.textMuted },
2358
- footer: concat(tp.accent(" Teammates"), tp.dim(` v${PKG_VERSION}`), tp.muted(" "), tp.text(this.adapterName)),
2459
+ footer: concat(tp.accent(" Teammates"), tp.dim(` v${PKG_VERSION}`), tp.muted(" "), tp.text(this.adapterName), tp.muted(" "), tp.dim(TeammatesREPL.truncatePath(dirname(this.teammatesDir)))),
2359
2460
  footerRight: tp.muted("? /help "),
2360
2461
  footerStyle: { fg: t.textDim },
2361
2462
  });
2362
- this.defaultFooter = concat(tp.accent(" Teammates"), tp.dim(` v${PKG_VERSION}`), tp.muted(" "), tp.text(this.adapterName));
2463
+ this.defaultFooter = concat(tp.accent(" Teammates"), tp.dim(` v${PKG_VERSION}`), tp.muted(" "), tp.text(this.adapterName), tp.muted(" "), tp.dim(TeammatesREPL.truncatePath(dirname(this.teammatesDir))));
2363
2464
  this.defaultFooterRight = tp.muted("? /help ");
2364
2465
  // Wire ChatView events for input handling
2365
2466
  this.chatView.on("submit", (rawLine) => {
@@ -2968,7 +3069,7 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
2968
3069
  {
2969
3070
  name: "debug",
2970
3071
  aliases: ["raw"],
2971
- usage: "/debug [teammate]",
3072
+ usage: "/debug [teammate] [focus]",
2972
3073
  description: "Analyze the last agent task with the coding agent",
2973
3074
  run: (args) => this.cmdDebug(args),
2974
3075
  },
@@ -3089,16 +3190,24 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
3089
3190
  return;
3090
3191
  switch (event.type) {
3091
3192
  case "task_assigned": {
3193
+ // System tasks (compaction, summarization, wisdom distillation) are
3194
+ // invisible — don't track them in the progress bar.
3195
+ if (event.assignment.system)
3196
+ break;
3092
3197
  // Track this task and start the animated status bar
3093
3198
  const key = event.assignment.teammate;
3094
3199
  this.activeTasks.set(key, {
3095
3200
  teammate: event.assignment.teammate,
3096
3201
  task: event.assignment.task,
3202
+ startTime: Date.now(),
3097
3203
  });
3098
3204
  this.startStatusAnimation();
3099
3205
  break;
3100
3206
  }
3101
3207
  case "task_completed": {
3208
+ // System task completions — don't touch activeTasks (was never added)
3209
+ if (event.result.system)
3210
+ break;
3102
3211
  // Remove from active tasks and stop spinner.
3103
3212
  // Result display is deferred to drainAgentQueue() so the defensive
3104
3213
  // retry can update rawOutput before anything is shown to the user.
@@ -3186,10 +3295,13 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
3186
3295
  this.refreshView();
3187
3296
  }
3188
3297
  async cmdDebug(argsStr) {
3189
- const arg = argsStr.trim().replace(/^@/, "");
3298
+ const parts = argsStr.trim().split(/\s+/);
3299
+ const firstArg = (parts[0] ?? "").replace(/^@/, "");
3300
+ // Everything after the teammate name is the debug focus
3301
+ const debugFocus = parts.slice(1).join(" ").trim() || undefined;
3190
3302
  // Resolve which teammate to debug
3191
3303
  let targetName;
3192
- if (arg === "everyone") {
3304
+ if (firstArg === "everyone") {
3193
3305
  // Pick all teammates with debug files, queue one analysis per teammate
3194
3306
  const names = [];
3195
3307
  for (const [name] of this.lastDebugFiles) {
@@ -3202,28 +3314,29 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
3202
3314
  return;
3203
3315
  }
3204
3316
  for (const name of names) {
3205
- this.queueDebugAnalysis(name);
3317
+ this.queueDebugAnalysis(name, debugFocus);
3206
3318
  }
3207
3319
  return;
3208
3320
  }
3209
- else if (arg) {
3210
- targetName = arg;
3321
+ else if (firstArg) {
3322
+ targetName = firstArg;
3211
3323
  }
3212
3324
  else if (this.lastResult) {
3213
3325
  targetName = this.lastResult.teammate;
3214
3326
  }
3215
3327
  else {
3216
- this.feedLine(tp.muted(" No debug info available. Try: /debug [teammate]"));
3328
+ this.feedLine(tp.muted(" No debug info available. Try: /debug [teammate] [focus]"));
3217
3329
  this.refreshView();
3218
3330
  return;
3219
3331
  }
3220
- this.queueDebugAnalysis(targetName);
3332
+ this.queueDebugAnalysis(targetName, debugFocus);
3221
3333
  }
3222
3334
  /**
3223
3335
  * Queue a debug analysis task — sends the last request + debug log
3224
3336
  * to the base coding agent for analysis.
3337
+ * @param debugFocus Optional focus area the user wants to investigate
3225
3338
  */
3226
- queueDebugAnalysis(teammate) {
3339
+ queueDebugAnalysis(teammate, debugFocus) {
3227
3340
  const debugFile = this.lastDebugFiles.get(teammate);
3228
3341
  const lastPrompt = this.lastTaskPrompts.get(teammate);
3229
3342
  if (!debugFile) {
@@ -3241,8 +3354,11 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
3241
3354
  this.refreshView();
3242
3355
  return;
3243
3356
  }
3357
+ const focusLine = debugFocus
3358
+ ? `\n\n**Focus your analysis on:** ${debugFocus}`
3359
+ : "";
3244
3360
  const analysisPrompt = [
3245
- `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.`,
3361
+ `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.${focusLine}`,
3246
3362
  "",
3247
3363
  "## Last Request Sent to Agent",
3248
3364
  "",
@@ -3254,6 +3370,9 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
3254
3370
  ].join("\n");
3255
3371
  // Show the debug log path — ctrl+click to open
3256
3372
  this.feedLine(concat(tp.muted(" Debug log: "), tp.accent(debugFile)));
3373
+ if (debugFocus) {
3374
+ this.feedLine(tp.muted(` Focus: ${debugFocus}`));
3375
+ }
3257
3376
  this.feedLine(tp.muted(" Queuing analysis…"));
3258
3377
  this.refreshView();
3259
3378
  this.taskQueue.push({
@@ -3280,34 +3399,18 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
3280
3399
  this.feedLine(concat(tp.muted(" Cancelled: "), tp.accent(`@${cancelDisplay}`), tp.muted(" — "), tp.text(removed.task.slice(0, 60))));
3281
3400
  this.refreshView();
3282
3401
  }
3283
- /** Drain tasks for a single agent — runs in parallel with other agents. */
3402
+ /** Drain user tasks for a single agent — runs in parallel with other agents.
3403
+ * System tasks are handled separately by runSystemTask(). */
3284
3404
  async drainAgentQueue(agent) {
3285
3405
  while (true) {
3286
- const idx = this.taskQueue.findIndex((e) => e.teammate === agent);
3406
+ const idx = this.taskQueue.findIndex((e) => e.teammate === agent && !this.isSystemTask(e));
3287
3407
  if (idx < 0)
3288
3408
  break;
3289
3409
  const entry = this.taskQueue.splice(idx, 1)[0];
3290
3410
  this.agentActive.set(agent, entry);
3291
3411
  const startTime = Date.now();
3292
3412
  try {
3293
- if (entry.type === "compact") {
3294
- await this.runCompact(entry.teammate);
3295
- }
3296
- else if (entry.type === "summarize") {
3297
- // Internal housekeeping — summarize older conversation history
3298
- const result = await this.orchestrator.assign({
3299
- teammate: entry.teammate,
3300
- task: entry.task,
3301
- });
3302
- // Extract the summary from the agent's output (strip protocol artifacts)
3303
- const raw = result.rawOutput ?? "";
3304
- this.conversationSummary = raw
3305
- .replace(/^TO:\s*\S+\s*\n/im, "")
3306
- .replace(/^#\s+.+\n*/m, "")
3307
- .replace(/```json\s*\n\s*\{[\s\S]*?\}\s*\n\s*```\s*$/g, "")
3308
- .trim();
3309
- }
3310
- else {
3413
+ {
3311
3414
  // btw and debug tasks skip conversation context (not part of main thread)
3312
3415
  const extraContext = entry.type === "btw" || entry.type === "debug"
3313
3416
  ? ""
@@ -3418,6 +3521,14 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
3418
3521
  task,
3419
3522
  "",
3420
3523
  ];
3524
+ // Include the full prompt sent to the agent (with identity, memory, etc.)
3525
+ const fullPrompt = result?.fullPrompt;
3526
+ if (fullPrompt) {
3527
+ lines.push("## Full Prompt");
3528
+ lines.push("");
3529
+ lines.push(fullPrompt);
3530
+ lines.push("");
3531
+ }
3421
3532
  if (error) {
3422
3533
  lines.push("## Result");
3423
3534
  lines.push("");
@@ -3474,7 +3585,7 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
3474
3585
  lines.push("");
3475
3586
  writeFileSync(debugFile, lines.join("\n"), "utf-8");
3476
3587
  this.lastDebugFiles.set(teammate, debugFile);
3477
- this.lastTaskPrompts.set(teammate, task);
3588
+ this.lastTaskPrompts.set(teammate, fullPrompt ?? task);
3478
3589
  }
3479
3590
  catch {
3480
3591
  // Don't let debug logging break task execution
@@ -3655,12 +3766,12 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
3655
3766
  */
3656
3767
  async runCompact(name, silent = false) {
3657
3768
  const teammateDir = join(this.teammatesDir, name);
3658
- if (this.chatView) {
3769
+ if (!silent && this.chatView) {
3659
3770
  this.chatView.setProgress(`Compacting ${name}...`);
3660
3771
  this.refreshView();
3661
3772
  }
3662
3773
  let spinner = null;
3663
- if (!this.chatView) {
3774
+ if (!silent && !this.chatView) {
3664
3775
  spinner = ora({ text: `Compacting ${name}...`, color: "cyan" }).start();
3665
3776
  }
3666
3777
  try {
@@ -3698,16 +3809,16 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
3698
3809
  if (this.chatView)
3699
3810
  this.feedLine(tp.success(` ✔ ${name}: ${parts.join(", ")}`));
3700
3811
  }
3701
- if (this.chatView)
3812
+ if (!silent && this.chatView)
3702
3813
  this.chatView.setProgress(null);
3703
3814
  // Sync recall index for this teammate (bundled library call)
3704
3815
  try {
3705
- if (this.chatView) {
3816
+ if (!silent && this.chatView) {
3706
3817
  this.chatView.setProgress(`Syncing ${name} index...`);
3707
3818
  this.refreshView();
3708
3819
  }
3709
3820
  let syncSpinner = null;
3710
- if (!this.chatView) {
3821
+ if (!silent && !this.chatView) {
3711
3822
  syncSpinner = ora({
3712
3823
  text: `Syncing ${name} index...`,
3713
3824
  color: "cyan",
@@ -3717,7 +3828,8 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
3717
3828
  if (syncSpinner)
3718
3829
  syncSpinner.succeed(`${name}: index synced`);
3719
3830
  if (this.chatView) {
3720
- this.chatView.setProgress(null);
3831
+ if (!silent)
3832
+ this.chatView.setProgress(null);
3721
3833
  if (!silent)
3722
3834
  this.feedLine(tp.success(` ✔ ${name}: index synced`));
3723
3835
  }
@@ -3734,9 +3846,9 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
3734
3846
  type: "agent",
3735
3847
  teammate: name,
3736
3848
  task: wisdomPrompt,
3849
+ system: true,
3737
3850
  });
3738
- if (this.chatView && !silent)
3739
- this.feedLine(tp.muted(` ↻ ${name}: queued wisdom distillation`));
3851
+ this.kickDrain();
3740
3852
  }
3741
3853
  }
3742
3854
  catch {
@@ -3748,7 +3860,8 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
3748
3860
  if (spinner)
3749
3861
  spinner.fail(`${name}: ${msg}`);
3750
3862
  if (this.chatView) {
3751
- this.chatView.setProgress(null);
3863
+ if (!silent)
3864
+ this.chatView.setProgress(null);
3752
3865
  // Errors always show in feed
3753
3866
  this.feedLine(tp.error(` ✖ ${name}: ${msg}`));
3754
3867
  }
@@ -3879,16 +3992,8 @@ Issues that can't be resolved unilaterally — they need input from other teamma
3879
3992
  // 1. Run compaction for all teammates (auto-compact + episodic + sync + wisdom)
3880
3993
  // Progress bar shows status; feed only shows lines when actual work is done
3881
3994
  for (const name of teammates) {
3882
- if (this.chatView) {
3883
- this.chatView.setProgress(`Maintaining @${name}...`);
3884
- this.refreshView();
3885
- }
3886
3995
  await this.runCompact(name, true);
3887
3996
  }
3888
- if (this.chatView) {
3889
- this.chatView.setProgress(null);
3890
- this.refreshView();
3891
- }
3892
3997
  // 2. Purge daily logs older than 30 days (disk + Vectra)
3893
3998
  const { Indexer } = await import("@teammates/recall");
3894
3999
  const indexer = new Indexer({ teammatesDir: this.teammatesDir });
@@ -82,6 +82,9 @@ export class Orchestrator {
82
82
  const result = await this.adapter.executeTask(sessionId, teammate, prompt, {
83
83
  raw: assignment.raw,
84
84
  });
85
+ // Propagate system flag so event handlers can distinguish system vs user tasks
86
+ if (assignment.system)
87
+ result.system = true;
85
88
  this.onEvent({ type: "task_completed", result });
86
89
  // Update status (preserve presence)
87
90
  const postPresence = this.statuses.get(assignment.teammate)?.presence ?? "online";
package/dist/types.d.ts CHANGED
@@ -64,6 +64,8 @@ export interface HandoffEnvelope {
64
64
  export interface TaskResult {
65
65
  /** The teammate that executed the task */
66
66
  teammate: string;
67
+ /** Whether this was a system-initiated task */
68
+ system?: boolean;
67
69
  /** Whether the task completed successfully */
68
70
  success: boolean;
69
71
  /** Summary of what was done */
@@ -74,6 +76,8 @@ export interface TaskResult {
74
76
  handoffs: HandoffEnvelope[];
75
77
  /** Raw output from the agent */
76
78
  rawOutput?: string;
79
+ /** The full prompt sent to the agent (for debug logging) */
80
+ fullPrompt?: string;
77
81
  /** Process diagnostics for debugging empty/failed responses */
78
82
  diagnostics?: {
79
83
  /** Process exit code (null if killed by signal) */
@@ -98,6 +102,8 @@ export interface TaskAssignment {
98
102
  extraContext?: string;
99
103
  /** When true, skip identity/memory prompt wrapping — send task as-is */
100
104
  raw?: boolean;
105
+ /** When true, this is a system-initiated task — suppress progress bar */
106
+ system?: boolean;
101
107
  }
102
108
  /** Orchestrator event for logging/hooks */
103
109
  export type OrchestratorEvent = {
@@ -116,6 +122,7 @@ export type QueueEntry = {
116
122
  type: "agent";
117
123
  teammate: string;
118
124
  task: string;
125
+ system?: boolean;
119
126
  } | {
120
127
  type: "compact";
121
128
  teammate: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@teammates/cli",
3
- "version": "0.5.1",
3
+ "version": "0.5.2",
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",
@@ -34,8 +34,8 @@
34
34
  "license": "MIT",
35
35
  "dependencies": {
36
36
  "@github/copilot-sdk": "^0.1.32",
37
- "@teammates/consolonia": "0.5.1",
38
- "@teammates/recall": "0.5.1",
37
+ "@teammates/consolonia": "0.5.2",
38
+ "@teammates/recall": "0.5.2",
39
39
  "chalk": "^5.6.2",
40
40
  "ora": "^9.3.0"
41
41
  },