@teammates/cli 0.4.1 → 0.5.1
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/README.md +36 -4
- package/dist/adapter.d.ts +19 -3
- package/dist/adapter.js +168 -96
- package/dist/adapter.test.js +29 -16
- package/dist/adapters/cli-proxy.d.ts +3 -1
- package/dist/adapters/cli-proxy.js +65 -6
- package/dist/adapters/copilot.d.ts +3 -1
- package/dist/adapters/copilot.js +16 -3
- package/dist/adapters/echo.d.ts +3 -1
- package/dist/adapters/echo.js +4 -2
- package/dist/banner.js +5 -1
- package/dist/cli-args.js +23 -23
- package/dist/cli-args.test.d.ts +1 -0
- package/dist/cli-args.test.js +125 -0
- package/dist/cli.js +486 -220
- package/dist/compact.d.ts +23 -0
- package/dist/compact.js +181 -11
- package/dist/compact.test.js +323 -7
- package/dist/index.d.ts +4 -1
- package/dist/index.js +3 -1
- package/dist/onboard.js +165 -165
- package/dist/orchestrator.js +7 -2
- package/dist/personas.d.ts +42 -0
- package/dist/personas.js +108 -0
- package/dist/personas.test.d.ts +1 -0
- package/dist/personas.test.js +88 -0
- package/dist/registry.test.js +23 -23
- package/dist/theme.test.d.ts +1 -0
- package/dist/theme.test.js +113 -0
- package/dist/types.d.ts +2 -0
- package/package.json +4 -3
- package/personas/architect.md +95 -0
- package/personas/backend.md +97 -0
- package/personas/data-engineer.md +96 -0
- package/personas/designer.md +96 -0
- package/personas/devops.md +97 -0
- package/personas/frontend.md +98 -0
- package/personas/ml-ai.md +100 -0
- package/personas/mobile.md +97 -0
- package/personas/performance.md +96 -0
- package/personas/pm.md +93 -0
- package/personas/prompt-engineer.md +122 -0
- package/personas/qa.md +96 -0
- package/personas/security.md +96 -0
- package/personas/sre.md +97 -0
- package/personas/swe.md +92 -0
- package/personas/tech-writer.md +97 -0
package/dist/cli.js
CHANGED
|
@@ -15,15 +15,16 @@ import { createInterface } from "node:readline";
|
|
|
15
15
|
import { App, ChatView, concat, esc, pen, renderMarkdown, stripAnsi, } from "@teammates/consolonia";
|
|
16
16
|
import chalk from "chalk";
|
|
17
17
|
import ora from "ora";
|
|
18
|
-
import { syncRecallIndex } from "./adapter.js";
|
|
19
|
-
import { AnimatedBanner } from "./banner.js";
|
|
18
|
+
import { DAILY_LOG_BUDGET_TOKENS, syncRecallIndex } from "./adapter.js";
|
|
19
|
+
import { AnimatedBanner, } from "./banner.js";
|
|
20
20
|
import { findTeammatesDir, PKG_VERSION, parseCliArgs, printUsage, resolveAdapter, } from "./cli-args.js";
|
|
21
21
|
import { findAtMention, isImagePath, relativeTime, wrapLine, } from "./cli-utils.js";
|
|
22
|
-
import { buildWisdomPrompt, compactEpisodic } from "./compact.js";
|
|
22
|
+
import { autoCompactForBudget, buildWisdomPrompt, compactEpisodic, purgeStaleDailies, } from "./compact.js";
|
|
23
23
|
import { PromptInput } from "./console/prompt-input.js";
|
|
24
24
|
import { buildTitle } from "./console/startup.js";
|
|
25
25
|
import { buildImportAdaptationPrompt, copyTemplateFiles, getOnboardingPrompt, importTeammates, } from "./onboard.js";
|
|
26
26
|
import { Orchestrator } from "./orchestrator.js";
|
|
27
|
+
import { loadPersonas, scaffoldFromPersona } from "./personas.js";
|
|
27
28
|
import { colorToHex, theme, tp } from "./theme.js";
|
|
28
29
|
// ─── Parsed CLI arguments ────────────────────────────────────────────
|
|
29
30
|
const cliArgs = parseCliArgs();
|
|
@@ -48,6 +49,106 @@ class TeammatesREPL {
|
|
|
48
49
|
text: result.summary,
|
|
49
50
|
});
|
|
50
51
|
}
|
|
52
|
+
/**
|
|
53
|
+
* Render a task result to the feed. Called from drainAgentQueue() AFTER
|
|
54
|
+
* the defensive retry so the user sees the final (possibly retried) output.
|
|
55
|
+
*/
|
|
56
|
+
displayTaskResult(result, entryType) {
|
|
57
|
+
// Suppress display for internal summarization tasks
|
|
58
|
+
if (entryType === "summarize")
|
|
59
|
+
return;
|
|
60
|
+
if (!this.chatView)
|
|
61
|
+
this.input.deactivateAndErase();
|
|
62
|
+
const raw = result.rawOutput ?? "";
|
|
63
|
+
// Strip protocol artifacts
|
|
64
|
+
const cleaned = raw
|
|
65
|
+
.replace(/^TO:\s*\S+\s*\n/im, "")
|
|
66
|
+
.replace(/^#\s+.+\n*/m, "")
|
|
67
|
+
.replace(/```handoff\s*\n@\w+\s*\n[\s\S]*?```/g, "")
|
|
68
|
+
.replace(/```json\s*\n\s*\{[\s\S]*?\}\s*\n\s*```\s*$/g, "")
|
|
69
|
+
.trim();
|
|
70
|
+
// Header: "teammate: subject"
|
|
71
|
+
const subject = result.summary || "Task completed";
|
|
72
|
+
const displayTeammate = result.teammate === this.selfName ? this.adapterName : result.teammate;
|
|
73
|
+
this.feedLine(concat(tp.accent(`${displayTeammate}: `), tp.text(subject)));
|
|
74
|
+
this.lastCleanedOutput = cleaned;
|
|
75
|
+
if (cleaned) {
|
|
76
|
+
this.feedMarkdown(cleaned);
|
|
77
|
+
}
|
|
78
|
+
else if (result.changedFiles.length > 0 || result.summary) {
|
|
79
|
+
// Agent produced no body text but DID do work — generate a synthetic
|
|
80
|
+
// summary from available metadata so the user sees something useful.
|
|
81
|
+
const syntheticLines = [];
|
|
82
|
+
if (result.summary) {
|
|
83
|
+
syntheticLines.push(result.summary);
|
|
84
|
+
}
|
|
85
|
+
if (result.changedFiles.length > 0) {
|
|
86
|
+
syntheticLines.push("");
|
|
87
|
+
syntheticLines.push("**Files changed:**");
|
|
88
|
+
for (const f of result.changedFiles) {
|
|
89
|
+
syntheticLines.push(`- ${f}`);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
this.feedMarkdown(syntheticLines.join("\n"));
|
|
93
|
+
}
|
|
94
|
+
else {
|
|
95
|
+
this.feedLine(tp.muted(" (no response text — the agent may have only performed tool actions)"));
|
|
96
|
+
this.feedLine(tp.muted(` Use /debug ${result.teammate} to view full output`));
|
|
97
|
+
// Show diagnostic hints for empty responses
|
|
98
|
+
const diag = result.diagnostics;
|
|
99
|
+
if (diag) {
|
|
100
|
+
if (diag.exitCode !== 0 && diag.exitCode !== null) {
|
|
101
|
+
this.feedLine(tp.warning(` ⚠ Process exited with code ${diag.exitCode}`));
|
|
102
|
+
}
|
|
103
|
+
if (diag.signal) {
|
|
104
|
+
this.feedLine(tp.warning(` ⚠ Process killed by signal: ${diag.signal}`));
|
|
105
|
+
}
|
|
106
|
+
if (diag.debugFile) {
|
|
107
|
+
this.feedLine(tp.muted(` Debug log: ${diag.debugFile}`));
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
// Render handoffs
|
|
112
|
+
const handoffs = result.handoffs;
|
|
113
|
+
if (handoffs.length > 0) {
|
|
114
|
+
this.renderHandoffs(result.teammate, handoffs);
|
|
115
|
+
}
|
|
116
|
+
// Clickable [reply] [copy] actions after the response
|
|
117
|
+
if (this.chatView && cleaned) {
|
|
118
|
+
const t = theme();
|
|
119
|
+
const teammate = result.teammate;
|
|
120
|
+
const replyId = `reply-${teammate}-${Date.now()}`;
|
|
121
|
+
this._replyContexts.set(replyId, { teammate, message: cleaned });
|
|
122
|
+
this.chatView.appendActionList([
|
|
123
|
+
{
|
|
124
|
+
id: replyId,
|
|
125
|
+
normalStyle: this.makeSpan({
|
|
126
|
+
text: " [reply]",
|
|
127
|
+
style: { fg: t.textDim },
|
|
128
|
+
}),
|
|
129
|
+
hoverStyle: this.makeSpan({
|
|
130
|
+
text: " [reply]",
|
|
131
|
+
style: { fg: t.accent },
|
|
132
|
+
}),
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
id: "copy",
|
|
136
|
+
normalStyle: this.makeSpan({
|
|
137
|
+
text: " [copy]",
|
|
138
|
+
style: { fg: t.textDim },
|
|
139
|
+
}),
|
|
140
|
+
hoverStyle: this.makeSpan({
|
|
141
|
+
text: " [copy]",
|
|
142
|
+
style: { fg: t.accent },
|
|
143
|
+
}),
|
|
144
|
+
},
|
|
145
|
+
]);
|
|
146
|
+
}
|
|
147
|
+
this.feedLine();
|
|
148
|
+
// Auto-detect new teammates added during this task
|
|
149
|
+
this.refreshTeammates();
|
|
150
|
+
this.showPrompt();
|
|
151
|
+
}
|
|
51
152
|
/** Token budget for recent conversation history (24k tokens ≈ 96k chars). */
|
|
52
153
|
static CONV_HISTORY_CHARS = 24_000 * 4;
|
|
53
154
|
buildConversationContext() {
|
|
@@ -116,6 +217,8 @@ class TeammatesREPL {
|
|
|
116
217
|
taskQueue = [];
|
|
117
218
|
/** Per-agent active tasks — one per agent running in parallel. */
|
|
118
219
|
agentActive = new Map();
|
|
220
|
+
/** Agents currently in a silent retry — suppress all events. */
|
|
221
|
+
silentAgents = new Set();
|
|
119
222
|
/** Per-agent drain locks — prevents double-draining a single agent. */
|
|
120
223
|
agentDrainLocks = new Map();
|
|
121
224
|
/** Stored pasted text keyed by paste number, expanded on Enter. */
|
|
@@ -128,7 +231,6 @@ class TeammatesREPL {
|
|
|
128
231
|
ctrlcPending = false; // true after first Ctrl+C, waiting for second
|
|
129
232
|
ctrlcTimer = null;
|
|
130
233
|
lastCleanedOutput = ""; // last teammate output for clipboard copy
|
|
131
|
-
dispatching = false;
|
|
132
234
|
autoApproveHandoffs = false;
|
|
133
235
|
/** Last debug log file path per teammate — for /debug analysis. */
|
|
134
236
|
lastDebugFiles = new Map();
|
|
@@ -235,7 +337,7 @@ class TeammatesREPL {
|
|
|
235
337
|
const entries = Array.from(this.activeTasks.values());
|
|
236
338
|
const idx = this.statusRotateIndex % entries.length;
|
|
237
339
|
const { teammate, task } = entries[idx];
|
|
238
|
-
const displayName = teammate === this.
|
|
340
|
+
const displayName = teammate === this.selfName ? this.adapterName : teammate;
|
|
239
341
|
const spinChar = TeammatesREPL.SPINNER[this.statusFrame % TeammatesREPL.SPINNER.length];
|
|
240
342
|
const taskPreview = task.length > 50 ? `${task.slice(0, 47)}...` : task;
|
|
241
343
|
const queueInfo = this.activeTasks.size > 1 ? ` (${idx + 1}/${this.activeTasks.size})` : "";
|
|
@@ -843,14 +945,14 @@ class TeammatesREPL {
|
|
|
843
945
|
const changes = proposals
|
|
844
946
|
.map((p) => `- **Proposal ${p.index}: ${p.title}**\n - Section: ${p.section}\n - Before: ${p.before}\n - After: ${p.after}`)
|
|
845
947
|
.join("\n\n");
|
|
846
|
-
const applyPrompt = `The user approved the following SOUL.md changes from your retrospective. Apply them now.
|
|
847
|
-
|
|
848
|
-
**Edit your SOUL.md file** (\`.teammates/${teammate}/SOUL.md\`) to incorporate these changes:
|
|
849
|
-
|
|
850
|
-
${changes}
|
|
851
|
-
|
|
852
|
-
After editing SOUL.md, record a brief summary of the retro outcome in your daily log: which proposals were approved and what changed.
|
|
853
|
-
|
|
948
|
+
const applyPrompt = `The user approved the following SOUL.md changes from your retrospective. Apply them now.
|
|
949
|
+
|
|
950
|
+
**Edit your SOUL.md file** (\`.teammates/${teammate}/SOUL.md\`) to incorporate these changes:
|
|
951
|
+
|
|
952
|
+
${changes}
|
|
953
|
+
|
|
954
|
+
After editing SOUL.md, record a brief summary of the retro outcome in your daily log: which proposals were approved and what changed.
|
|
955
|
+
|
|
854
956
|
Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily log.`;
|
|
855
957
|
this.taskQueue.push({ type: "agent", teammate, task: applyPrompt });
|
|
856
958
|
this.feedLine(concat(tp.muted(" Queued SOUL.md update for "), tp.accent(`@${teammate}`)));
|
|
@@ -891,7 +993,8 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
|
|
|
891
993
|
let m;
|
|
892
994
|
mentioned = [];
|
|
893
995
|
while ((m = mentionRegex.exec(input)) !== null) {
|
|
894
|
-
|
|
996
|
+
// Remap adapter name alias → user avatar for routing
|
|
997
|
+
const name = m[1] === this.adapterName && this.userAlias ? this.selfName : m[1];
|
|
895
998
|
if (allNames.includes(name) && !mentioned.includes(name)) {
|
|
896
999
|
mentioned.push(name);
|
|
897
1000
|
}
|
|
@@ -921,7 +1024,7 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
|
|
|
921
1024
|
{
|
|
922
1025
|
const bg = this._userBg;
|
|
923
1026
|
const t = theme();
|
|
924
|
-
const displayName = match === this.
|
|
1027
|
+
const displayName = match === this.selfName ? this.adapterName : match;
|
|
925
1028
|
this.feedUserLine(concat(pen.fg(t.textMuted).bg(bg)(" → "), pen.fg(t.accent).bg(bg)(`@${displayName}`)));
|
|
926
1029
|
}
|
|
927
1030
|
this.feedLine();
|
|
@@ -959,42 +1062,174 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
|
|
|
959
1062
|
console.log(chalk.white(" Set up teammates for this project?\n"));
|
|
960
1063
|
console.log(chalk.cyan(" 1") +
|
|
961
1064
|
chalk.gray(") ") +
|
|
962
|
-
chalk.white("
|
|
963
|
-
chalk.gray(" —
|
|
1065
|
+
chalk.white("Pick teammates") +
|
|
1066
|
+
chalk.gray(" — choose from persona templates"));
|
|
964
1067
|
console.log(chalk.cyan(" 2") +
|
|
1068
|
+
chalk.gray(") ") +
|
|
1069
|
+
chalk.white("Auto-generate") +
|
|
1070
|
+
chalk.gray(" — let your agent analyze the codebase and create teammates"));
|
|
1071
|
+
console.log(chalk.cyan(" 3") +
|
|
965
1072
|
chalk.gray(") ") +
|
|
966
1073
|
chalk.white("Import team") +
|
|
967
1074
|
chalk.gray(" — copy teammates from another project"));
|
|
968
|
-
console.log(chalk.cyan("
|
|
1075
|
+
console.log(chalk.cyan(" 4") +
|
|
969
1076
|
chalk.gray(") ") +
|
|
970
1077
|
chalk.white("Solo mode") +
|
|
971
1078
|
chalk.gray(" — use your agent without teammates"));
|
|
972
|
-
console.log(chalk.cyan("
|
|
1079
|
+
console.log(chalk.cyan(" 5") + chalk.gray(") ") + chalk.white("Exit"));
|
|
973
1080
|
console.log();
|
|
974
|
-
const choice = await this.askChoice("Pick an option (1/2/3/4): ", [
|
|
1081
|
+
const choice = await this.askChoice("Pick an option (1/2/3/4/5): ", [
|
|
975
1082
|
"1",
|
|
976
1083
|
"2",
|
|
977
1084
|
"3",
|
|
978
1085
|
"4",
|
|
1086
|
+
"5",
|
|
979
1087
|
]);
|
|
980
|
-
if (choice === "
|
|
1088
|
+
if (choice === "5") {
|
|
981
1089
|
console.log(chalk.gray(" Goodbye."));
|
|
982
1090
|
return false;
|
|
983
1091
|
}
|
|
984
|
-
if (choice === "
|
|
1092
|
+
if (choice === "4") {
|
|
985
1093
|
console.log(chalk.gray(" Running in solo mode — all tasks go to your agent."));
|
|
986
1094
|
console.log(chalk.gray(" Run /init later to set up teammates."));
|
|
987
1095
|
console.log();
|
|
988
1096
|
return true;
|
|
989
1097
|
}
|
|
990
|
-
if (choice === "
|
|
1098
|
+
if (choice === "3") {
|
|
991
1099
|
await this.runImport(cwd);
|
|
992
1100
|
return true;
|
|
993
1101
|
}
|
|
994
|
-
|
|
995
|
-
|
|
1102
|
+
if (choice === "2") {
|
|
1103
|
+
// Auto-generate via agent
|
|
1104
|
+
await this.runOnboardingAgent(adapter, cwd);
|
|
1105
|
+
return true;
|
|
1106
|
+
}
|
|
1107
|
+
// choice === "1": Pick from persona templates
|
|
1108
|
+
await this.runPersonaOnboarding(teammatesDir);
|
|
996
1109
|
return true;
|
|
997
1110
|
}
|
|
1111
|
+
/**
|
|
1112
|
+
* Persona-based onboarding: show a list of bundled personas, let the user
|
|
1113
|
+
* pick which ones to create, optionally rename them, and scaffold the folders.
|
|
1114
|
+
*/
|
|
1115
|
+
async runPersonaOnboarding(teammatesDir) {
|
|
1116
|
+
const personas = await loadPersonas();
|
|
1117
|
+
if (personas.length === 0) {
|
|
1118
|
+
console.log(chalk.yellow(" No persona templates found."));
|
|
1119
|
+
return;
|
|
1120
|
+
}
|
|
1121
|
+
console.log();
|
|
1122
|
+
console.log(chalk.white(" Available personas:\n"));
|
|
1123
|
+
// Display personas grouped by tier
|
|
1124
|
+
let currentTier = 0;
|
|
1125
|
+
for (let i = 0; i < personas.length; i++) {
|
|
1126
|
+
const p = personas[i];
|
|
1127
|
+
if (p.tier !== currentTier) {
|
|
1128
|
+
currentTier = p.tier;
|
|
1129
|
+
const label = currentTier === 1 ? "Core" : "Specialized";
|
|
1130
|
+
console.log(chalk.gray(` ── ${label} ──`));
|
|
1131
|
+
}
|
|
1132
|
+
const num = String(i + 1).padStart(2, " ");
|
|
1133
|
+
console.log(chalk.cyan(` ${num}`) +
|
|
1134
|
+
chalk.gray(") ") +
|
|
1135
|
+
chalk.white(p.persona) +
|
|
1136
|
+
chalk.gray(` (${p.alias})`) +
|
|
1137
|
+
chalk.gray(` — ${p.description}`));
|
|
1138
|
+
}
|
|
1139
|
+
console.log();
|
|
1140
|
+
console.log(chalk.gray(" Enter numbers separated by commas, e.g. 1,3,5"));
|
|
1141
|
+
console.log();
|
|
1142
|
+
const input = await this.askInput("Personas: ");
|
|
1143
|
+
if (!input) {
|
|
1144
|
+
console.log(chalk.gray(" No personas selected."));
|
|
1145
|
+
return;
|
|
1146
|
+
}
|
|
1147
|
+
// Parse comma-separated numbers
|
|
1148
|
+
const indices = input
|
|
1149
|
+
.split(",")
|
|
1150
|
+
.map((s) => parseInt(s.trim(), 10) - 1)
|
|
1151
|
+
.filter((i) => i >= 0 && i < personas.length);
|
|
1152
|
+
const unique = [...new Set(indices)];
|
|
1153
|
+
if (unique.length === 0) {
|
|
1154
|
+
console.log(chalk.yellow(" No valid selections."));
|
|
1155
|
+
return;
|
|
1156
|
+
}
|
|
1157
|
+
console.log();
|
|
1158
|
+
// Copy framework files first
|
|
1159
|
+
await copyTemplateFiles(teammatesDir);
|
|
1160
|
+
const created = [];
|
|
1161
|
+
for (const idx of unique) {
|
|
1162
|
+
const p = personas[idx];
|
|
1163
|
+
const nameInput = await this.askInput(`Name for ${p.persona} [${p.alias}]: `);
|
|
1164
|
+
const name = nameInput || p.alias;
|
|
1165
|
+
const folderName = name.toLowerCase().replace(/[^a-z0-9_-]/g, "");
|
|
1166
|
+
await scaffoldFromPersona(teammatesDir, folderName, p);
|
|
1167
|
+
created.push(folderName);
|
|
1168
|
+
console.log(chalk.green(" ✔ ") +
|
|
1169
|
+
chalk.white(`@${folderName}`) +
|
|
1170
|
+
chalk.gray(` — ${p.persona}`));
|
|
1171
|
+
}
|
|
1172
|
+
console.log();
|
|
1173
|
+
console.log(chalk.green(` ✔ Created ${created.length} teammate${created.length > 1 ? "s" : ""}: `) + chalk.white(created.map((n) => `@${n}`).join(", ")));
|
|
1174
|
+
console.log(chalk.gray(" Tip: Your agent will adapt ownership and capabilities to this codebase on first task."));
|
|
1175
|
+
console.log();
|
|
1176
|
+
}
|
|
1177
|
+
/**
|
|
1178
|
+
* In-TUI persona picker for /init pick. Uses feedLine + askInline instead
|
|
1179
|
+
* of console.log + askInput.
|
|
1180
|
+
*/
|
|
1181
|
+
async runPersonaOnboardingInline(teammatesDir) {
|
|
1182
|
+
const personas = await loadPersonas();
|
|
1183
|
+
if (personas.length === 0) {
|
|
1184
|
+
this.feedLine(tp.warning(" No persona templates found."));
|
|
1185
|
+
this.refreshView();
|
|
1186
|
+
return;
|
|
1187
|
+
}
|
|
1188
|
+
// Display personas in the feed
|
|
1189
|
+
this.feedLine(tp.text(" Available personas:\n"));
|
|
1190
|
+
let currentTier = 0;
|
|
1191
|
+
for (let i = 0; i < personas.length; i++) {
|
|
1192
|
+
const p = personas[i];
|
|
1193
|
+
if (p.tier !== currentTier) {
|
|
1194
|
+
currentTier = p.tier;
|
|
1195
|
+
const label = currentTier === 1 ? "Core" : "Specialized";
|
|
1196
|
+
this.feedLine(tp.muted(` ── ${label} ──`));
|
|
1197
|
+
}
|
|
1198
|
+
const num = String(i + 1).padStart(2, " ");
|
|
1199
|
+
this.feedLine(concat(tp.text(` ${num}) ${p.persona} `), tp.muted(`(${p.alias}) — ${p.description}`)));
|
|
1200
|
+
}
|
|
1201
|
+
this.feedLine(tp.muted("\n Enter numbers separated by commas, e.g. 1,3,5"));
|
|
1202
|
+
this.refreshView();
|
|
1203
|
+
const input = await this.askInline("Personas: ");
|
|
1204
|
+
if (!input) {
|
|
1205
|
+
this.feedLine(tp.muted(" No personas selected."));
|
|
1206
|
+
this.refreshView();
|
|
1207
|
+
return;
|
|
1208
|
+
}
|
|
1209
|
+
const indices = input
|
|
1210
|
+
.split(",")
|
|
1211
|
+
.map((s) => parseInt(s.trim(), 10) - 1)
|
|
1212
|
+
.filter((i) => i >= 0 && i < personas.length);
|
|
1213
|
+
const unique = [...new Set(indices)];
|
|
1214
|
+
if (unique.length === 0) {
|
|
1215
|
+
this.feedLine(tp.warning(" No valid selections."));
|
|
1216
|
+
this.refreshView();
|
|
1217
|
+
return;
|
|
1218
|
+
}
|
|
1219
|
+
await copyTemplateFiles(teammatesDir);
|
|
1220
|
+
const created = [];
|
|
1221
|
+
for (const idx of unique) {
|
|
1222
|
+
const p = personas[idx];
|
|
1223
|
+
const nameInput = await this.askInline(`Name for ${p.persona} [${p.alias}]: `);
|
|
1224
|
+
const name = nameInput || p.alias;
|
|
1225
|
+
const folderName = name.toLowerCase().replace(/[^a-z0-9_-]/g, "");
|
|
1226
|
+
await scaffoldFromPersona(teammatesDir, folderName, p);
|
|
1227
|
+
created.push(folderName);
|
|
1228
|
+
this.feedLine(concat(tp.success(` ✔ @${folderName}`), tp.muted(` — ${p.persona}`)));
|
|
1229
|
+
}
|
|
1230
|
+
this.feedLine(concat(tp.success(`\n ✔ Created ${created.length} teammate${created.length > 1 ? "s" : ""}: `), tp.text(created.map((n) => `@${n}`).join(", "))));
|
|
1231
|
+
this.refreshView();
|
|
1232
|
+
}
|
|
998
1233
|
/**
|
|
999
1234
|
* Run the onboarding agent to analyze the codebase and create teammates.
|
|
1000
1235
|
* Used by both promptOnboarding (pre-orchestrator) and cmdInit (post-orchestrator).
|
|
@@ -1404,20 +1639,24 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
|
|
|
1404
1639
|
chalk.cyan(`@${login}`) +
|
|
1405
1640
|
(name && name !== login ? chalk.gray(` (${name})`) : ""));
|
|
1406
1641
|
console.log();
|
|
1407
|
-
// Ask for
|
|
1642
|
+
// Ask for remaining fields since GitHub doesn't provide them
|
|
1408
1643
|
const role = await this.askInput("Your role (optional, press Enter to skip): ");
|
|
1644
|
+
const experience = await this.askInput("Relevant experience (e.g., 10 years Go, new to React): ");
|
|
1645
|
+
const preferences = await this.askInput("How you like to work (e.g., terse responses): ");
|
|
1646
|
+
// Auto-detect timezone
|
|
1647
|
+
const detectedTz = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
1648
|
+
const timezone = await this.askInput(`Primary timezone${detectedTz ? ` [${detectedTz}]` : ""}: `);
|
|
1409
1649
|
const answers = {
|
|
1410
1650
|
alias: login,
|
|
1411
1651
|
name: name || login,
|
|
1412
1652
|
role: role || "",
|
|
1413
|
-
experience: "",
|
|
1414
|
-
preferences: "",
|
|
1415
|
-
|
|
1653
|
+
experience: experience || "",
|
|
1654
|
+
preferences: preferences || "",
|
|
1655
|
+
timezone: timezone || detectedTz || "",
|
|
1416
1656
|
};
|
|
1417
1657
|
this.writeUserProfile(teammatesDir, login, answers);
|
|
1418
1658
|
this.createUserAvatar(teammatesDir, login, answers);
|
|
1419
|
-
console.log(chalk.green(" ✔ ") +
|
|
1420
|
-
chalk.gray(`Profile created — avatar @${login}`));
|
|
1659
|
+
console.log(chalk.green(" ✔ ") + chalk.gray(`Profile created — avatar @${login}`));
|
|
1421
1660
|
console.log();
|
|
1422
1661
|
}
|
|
1423
1662
|
/**
|
|
@@ -1427,7 +1666,10 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
|
|
|
1427
1666
|
console.log();
|
|
1428
1667
|
console.log(chalk.gray(" (alias is required, press Enter to skip others)\n"));
|
|
1429
1668
|
const aliasRaw = await this.askInput("Your alias (e.g., alex): ");
|
|
1430
|
-
const alias = aliasRaw
|
|
1669
|
+
const alias = aliasRaw
|
|
1670
|
+
.toLowerCase()
|
|
1671
|
+
.replace(/[^a-z0-9_-]/g, "")
|
|
1672
|
+
.trim();
|
|
1431
1673
|
if (!alias) {
|
|
1432
1674
|
console.log(chalk.yellow(" Alias is required. Run /user to try again.\n"));
|
|
1433
1675
|
return;
|
|
@@ -1436,20 +1678,21 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
|
|
|
1436
1678
|
const role = await this.askInput("Your role (e.g., senior backend engineer): ");
|
|
1437
1679
|
const experience = await this.askInput("Relevant experience (e.g., 10 years Go, new to React): ");
|
|
1438
1680
|
const preferences = await this.askInput("How you like to work (e.g., terse responses): ");
|
|
1439
|
-
|
|
1681
|
+
// Auto-detect timezone
|
|
1682
|
+
const detectedTz = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
1683
|
+
const timezone = await this.askInput(`Primary timezone${detectedTz ? ` [${detectedTz}]` : ""}: `);
|
|
1440
1684
|
const answers = {
|
|
1441
1685
|
alias,
|
|
1442
1686
|
name,
|
|
1443
1687
|
role,
|
|
1444
1688
|
experience,
|
|
1445
1689
|
preferences,
|
|
1446
|
-
|
|
1690
|
+
timezone: timezone || detectedTz || "",
|
|
1447
1691
|
};
|
|
1448
1692
|
this.writeUserProfile(teammatesDir, alias, answers);
|
|
1449
1693
|
this.createUserAvatar(teammatesDir, alias, answers);
|
|
1450
1694
|
console.log();
|
|
1451
|
-
console.log(chalk.green(" ✔ ") +
|
|
1452
|
-
chalk.gray(`Profile created — avatar @${alias}`));
|
|
1695
|
+
console.log(chalk.green(" ✔ ") + chalk.gray(`Profile created — avatar @${alias}`));
|
|
1453
1696
|
console.log(chalk.gray(" Update anytime with /user"));
|
|
1454
1697
|
console.log();
|
|
1455
1698
|
}
|
|
@@ -1464,7 +1707,7 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
|
|
|
1464
1707
|
lines.push(`- **Role:** ${answers.role || "_not provided_"}`);
|
|
1465
1708
|
lines.push(`- **Experience:** ${answers.experience || "_not provided_"}`);
|
|
1466
1709
|
lines.push(`- **Preferences:** ${answers.preferences || "_not provided_"}`);
|
|
1467
|
-
lines.push(`- **
|
|
1710
|
+
lines.push(`- **Primary Timezone:** ${answers.timezone || "_not provided_"}`);
|
|
1468
1711
|
writeFileSync(userMdPath, `${lines.join("\n")}\n`, "utf-8");
|
|
1469
1712
|
}
|
|
1470
1713
|
/**
|
|
@@ -1477,10 +1720,10 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
|
|
|
1477
1720
|
mkdirSync(avatarDir, { recursive: true });
|
|
1478
1721
|
mkdirSync(memoryDir, { recursive: true });
|
|
1479
1722
|
const name = answers.name || alias;
|
|
1480
|
-
const role = answers.role || "
|
|
1723
|
+
const role = answers.role || "I'm a human working on this project";
|
|
1481
1724
|
const experience = answers.experience || "";
|
|
1482
1725
|
const preferences = answers.preferences || "";
|
|
1483
|
-
const
|
|
1726
|
+
const timezone = answers.timezone || "";
|
|
1484
1727
|
// Write SOUL.md
|
|
1485
1728
|
const soulLines = [
|
|
1486
1729
|
`# ${name}`,
|
|
@@ -1495,9 +1738,8 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
|
|
|
1495
1738
|
soulLines.push(`**Experience:** ${experience}`);
|
|
1496
1739
|
if (preferences)
|
|
1497
1740
|
soulLines.push(`**Preferences:** ${preferences}`);
|
|
1498
|
-
if (
|
|
1499
|
-
soulLines.push(
|
|
1500
|
-
}
|
|
1741
|
+
if (timezone)
|
|
1742
|
+
soulLines.push(`**Primary Timezone:** ${timezone}`);
|
|
1501
1743
|
soulLines.push("");
|
|
1502
1744
|
const soulPath = join(avatarDir, "SOUL.md");
|
|
1503
1745
|
writeFileSync(soulPath, soulLines.join("\n"), "utf-8");
|
|
@@ -1531,19 +1773,23 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
|
|
|
1531
1773
|
const avatarDir = join(teammatesDir, alias);
|
|
1532
1774
|
// Read the avatar's SOUL.md if it exists
|
|
1533
1775
|
let soul = "";
|
|
1534
|
-
let role = "
|
|
1776
|
+
let role = "I'm a human working on this project";
|
|
1535
1777
|
try {
|
|
1536
1778
|
soul = readFileSync(join(avatarDir, "SOUL.md"), "utf-8");
|
|
1537
1779
|
const roleMatch = soul.match(/\*\*Role:\*\*\s*(.+)/);
|
|
1538
1780
|
if (roleMatch)
|
|
1539
1781
|
role = roleMatch[1].trim();
|
|
1540
1782
|
}
|
|
1541
|
-
catch {
|
|
1783
|
+
catch {
|
|
1784
|
+
/* avatar folder may not exist yet */
|
|
1785
|
+
}
|
|
1542
1786
|
let wisdom = "";
|
|
1543
1787
|
try {
|
|
1544
1788
|
wisdom = readFileSync(join(avatarDir, "WISDOM.md"), "utf-8");
|
|
1545
1789
|
}
|
|
1546
|
-
catch {
|
|
1790
|
+
catch {
|
|
1791
|
+
/* ok */
|
|
1792
|
+
}
|
|
1547
1793
|
registry.register({
|
|
1548
1794
|
name: alias,
|
|
1549
1795
|
type: "human",
|
|
@@ -1556,7 +1802,9 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
|
|
|
1556
1802
|
routingKeywords: [],
|
|
1557
1803
|
});
|
|
1558
1804
|
// Set presence to online (local user is always online)
|
|
1559
|
-
this.orchestrator
|
|
1805
|
+
this.orchestrator
|
|
1806
|
+
.getAllStatuses()
|
|
1807
|
+
.set(alias, { state: "idle", presence: "online" });
|
|
1560
1808
|
// Update the adapter name so tasks route to the avatar
|
|
1561
1809
|
this.userAlias = alias;
|
|
1562
1810
|
}
|
|
@@ -1648,9 +1896,7 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
|
|
|
1648
1896
|
if (completedArgs > 0)
|
|
1649
1897
|
return [];
|
|
1650
1898
|
const lower = partial.toLowerCase();
|
|
1651
|
-
return TeammatesREPL.CONFIGURABLE_SERVICES
|
|
1652
|
-
.filter((s) => s.startsWith(lower))
|
|
1653
|
-
.map((s) => ({
|
|
1899
|
+
return TeammatesREPL.CONFIGURABLE_SERVICES.filter((s) => s.startsWith(lower)).map((s) => ({
|
|
1654
1900
|
label: s,
|
|
1655
1901
|
description: `configure ${s}`,
|
|
1656
1902
|
completion: `/${cmdName} ${s} `,
|
|
@@ -1744,12 +1990,14 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
|
|
|
1744
1990
|
});
|
|
1745
1991
|
}
|
|
1746
1992
|
for (const name of teammates) {
|
|
1747
|
-
|
|
1993
|
+
// For user avatar, display and match using the adapter name alias
|
|
1994
|
+
const display = name === this.userAlias ? this.adapterName : name;
|
|
1995
|
+
if (display.toLowerCase().startsWith(lower)) {
|
|
1748
1996
|
const t = this.orchestrator.getRegistry().get(name);
|
|
1749
1997
|
items.push({
|
|
1750
|
-
label: `@${
|
|
1998
|
+
label: `@${display}`,
|
|
1751
1999
|
description: t?.role ?? "",
|
|
1752
|
-
completion: `${before}@${
|
|
2000
|
+
completion: `${before}@${display} ${after.replace(/^\s+/, "")}`,
|
|
1753
2001
|
});
|
|
1754
2002
|
}
|
|
1755
2003
|
}
|
|
@@ -1930,7 +2178,7 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
|
|
|
1930
2178
|
registry.register({
|
|
1931
2179
|
name: this.adapterName,
|
|
1932
2180
|
type: "ai",
|
|
1933
|
-
role: "
|
|
2181
|
+
role: "Coding agent that performs tasks on your behalf.",
|
|
1934
2182
|
soul: "",
|
|
1935
2183
|
wisdom: "",
|
|
1936
2184
|
dailyLogs: [],
|
|
@@ -1939,7 +2187,9 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
|
|
|
1939
2187
|
routingKeywords: [],
|
|
1940
2188
|
cwd: dirname(this.teammatesDir),
|
|
1941
2189
|
});
|
|
1942
|
-
this.orchestrator
|
|
2190
|
+
this.orchestrator
|
|
2191
|
+
.getAllStatuses()
|
|
2192
|
+
.set(this.adapterName, { state: "idle", presence: "online" });
|
|
1943
2193
|
}
|
|
1944
2194
|
// Populate roster on the adapter so prompts include team info
|
|
1945
2195
|
// Exclude the user avatar and adapter fallback — neither is an addressable teammate
|
|
@@ -1965,8 +2215,10 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
|
|
|
1965
2215
|
borderStyle: (s) => chalk.gray(s),
|
|
1966
2216
|
colorize: (value) => {
|
|
1967
2217
|
const validNames = new Set([
|
|
1968
|
-
...this.orchestrator
|
|
1969
|
-
|
|
2218
|
+
...this.orchestrator
|
|
2219
|
+
.listTeammates()
|
|
2220
|
+
.filter((n) => n !== this.adapterName && n !== this.userAlias),
|
|
2221
|
+
this.adapterName,
|
|
1970
2222
|
"everyone",
|
|
1971
2223
|
]);
|
|
1972
2224
|
return value
|
|
@@ -2011,13 +2263,22 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
|
|
|
2011
2263
|
const reg = this.orchestrator.getRegistry();
|
|
2012
2264
|
const statuses = this.orchestrator.getAllStatuses();
|
|
2013
2265
|
const bannerTeammates = [];
|
|
2266
|
+
// Add user avatar first (displayed as adapter name alias)
|
|
2267
|
+
if (this.userAlias) {
|
|
2268
|
+
const up = statuses.get(this.userAlias)?.presence ?? "online";
|
|
2269
|
+
bannerTeammates.push({
|
|
2270
|
+
name: this.adapterName,
|
|
2271
|
+
role: "Coding agent that performs tasks on your behalf.",
|
|
2272
|
+
presence: up,
|
|
2273
|
+
});
|
|
2274
|
+
}
|
|
2014
2275
|
for (const name of names) {
|
|
2015
2276
|
const t = reg.get(name);
|
|
2016
2277
|
const p = statuses.get(name)?.presence ?? "online";
|
|
2017
2278
|
bannerTeammates.push({ name, role: t?.role ?? "", presence: p });
|
|
2018
2279
|
}
|
|
2019
2280
|
const bannerWidget = new AnimatedBanner({
|
|
2020
|
-
displayName: `@${this.
|
|
2281
|
+
displayName: `@${this.adapterName}`,
|
|
2021
2282
|
teammateCount: names.length,
|
|
2022
2283
|
cwd: process.cwd(),
|
|
2023
2284
|
teammates: bannerTeammates,
|
|
@@ -2048,8 +2309,10 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
|
|
|
2048
2309
|
}
|
|
2049
2310
|
// Colorize @mentions only if they reference a valid teammate or the user
|
|
2050
2311
|
const validNames = new Set([
|
|
2051
|
-
...this.orchestrator
|
|
2052
|
-
|
|
2312
|
+
...this.orchestrator
|
|
2313
|
+
.listTeammates()
|
|
2314
|
+
.filter((n) => n !== this.adapterName && n !== this.userAlias),
|
|
2315
|
+
this.adapterName,
|
|
2053
2316
|
"everyone",
|
|
2054
2317
|
]);
|
|
2055
2318
|
const mentionPattern = /@(\w+)/g;
|
|
@@ -2339,7 +2602,8 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
|
|
|
2339
2602
|
let pm;
|
|
2340
2603
|
const preMentions = [];
|
|
2341
2604
|
while ((pm = preMentionRegex.exec(rawLine)) !== null) {
|
|
2342
|
-
|
|
2605
|
+
// Remap adapter name alias → user avatar for routing
|
|
2606
|
+
const name = pm[1] === this.adapterName && this.userAlias ? this.selfName : pm[1];
|
|
2343
2607
|
if (allNames.includes(name) && !preMentions.includes(name)) {
|
|
2344
2608
|
preMentions.push(name);
|
|
2345
2609
|
}
|
|
@@ -2411,16 +2675,12 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
|
|
|
2411
2675
|
}
|
|
2412
2676
|
// Slash commands
|
|
2413
2677
|
if (input.startsWith("/")) {
|
|
2414
|
-
this.dispatching = true;
|
|
2415
2678
|
try {
|
|
2416
2679
|
await this.dispatch(input);
|
|
2417
2680
|
}
|
|
2418
2681
|
catch (err) {
|
|
2419
2682
|
this.feedLine(tp.error(`Error: ${err.message}`));
|
|
2420
2683
|
}
|
|
2421
|
-
finally {
|
|
2422
|
-
this.dispatching = false;
|
|
2423
|
-
}
|
|
2424
2684
|
this.refreshView();
|
|
2425
2685
|
return;
|
|
2426
2686
|
}
|
|
@@ -2436,7 +2696,7 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
|
|
|
2436
2696
|
const termWidth = process.stdout.columns || 100;
|
|
2437
2697
|
this.feedLine();
|
|
2438
2698
|
this.feedLine(concat(tp.bold(" Teammates"), tp.muted(` v${PKG_VERSION}`)));
|
|
2439
|
-
this.feedLine(concat(tp.text(` @${this.
|
|
2699
|
+
this.feedLine(concat(tp.text(` @${this.adapterName}`), tp.muted(` · ${teammates.length} teammate${teammates.length === 1 ? "" : "s"}`)));
|
|
2440
2700
|
this.feedLine(` ${process.cwd()}`);
|
|
2441
2701
|
// Service status rows
|
|
2442
2702
|
for (const svc of this.serviceStatuses) {
|
|
@@ -2455,11 +2715,25 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
|
|
|
2455
2715
|
// Roster (with presence indicators)
|
|
2456
2716
|
this.feedLine();
|
|
2457
2717
|
const statuses = this.orchestrator.getAllStatuses();
|
|
2718
|
+
// Show user avatar first (displayed as adapter name alias)
|
|
2719
|
+
if (this.userAlias) {
|
|
2720
|
+
const up = statuses.get(this.userAlias)?.presence ?? "online";
|
|
2721
|
+
const udot = up === "online"
|
|
2722
|
+
? tp.success("●")
|
|
2723
|
+
: up === "reachable"
|
|
2724
|
+
? tp.warning("●")
|
|
2725
|
+
: tp.error("●");
|
|
2726
|
+
this.feedLine(concat(tp.text(" "), udot, tp.accent(` @${this.adapterName.padEnd(14)}`), tp.muted("Coding agent that performs tasks on your behalf.")));
|
|
2727
|
+
}
|
|
2458
2728
|
for (const name of teammates) {
|
|
2459
2729
|
const t = registry.get(name);
|
|
2460
2730
|
if (t) {
|
|
2461
2731
|
const p = statuses.get(name)?.presence ?? "online";
|
|
2462
|
-
const dot = p === "online"
|
|
2732
|
+
const dot = p === "online"
|
|
2733
|
+
? tp.success("●")
|
|
2734
|
+
: p === "reachable"
|
|
2735
|
+
? tp.warning("●")
|
|
2736
|
+
: tp.error("●");
|
|
2463
2737
|
this.feedLine(concat(tp.text(" "), dot, tp.accent(` @${name.padEnd(14)}`), tp.muted(t.role)));
|
|
2464
2738
|
}
|
|
2465
2739
|
}
|
|
@@ -2652,7 +2926,10 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
|
|
|
2652
2926
|
// Get username for confirmation
|
|
2653
2927
|
let username = "";
|
|
2654
2928
|
try {
|
|
2655
|
-
username = execSync("gh api user --jq .login", {
|
|
2929
|
+
username = execSync("gh api user --jq .login", {
|
|
2930
|
+
stdio: "pipe",
|
|
2931
|
+
encoding: "utf-8",
|
|
2932
|
+
}).trim();
|
|
2656
2933
|
}
|
|
2657
2934
|
catch {
|
|
2658
2935
|
// non-critical
|
|
@@ -2705,8 +2982,8 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
|
|
|
2705
2982
|
{
|
|
2706
2983
|
name: "init",
|
|
2707
2984
|
aliases: ["onboard", "setup"],
|
|
2708
|
-
usage: "/init [from-path]",
|
|
2709
|
-
description: "Set up teammates (or import from another project)",
|
|
2985
|
+
usage: "/init [pick | from-path]",
|
|
2986
|
+
description: "Set up teammates (pick from personas, or import from another project)",
|
|
2710
2987
|
run: (args) => this.cmdInit(args),
|
|
2711
2988
|
},
|
|
2712
2989
|
{
|
|
@@ -2802,6 +3079,14 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
|
|
|
2802
3079
|
}
|
|
2803
3080
|
// ─── Event handler ───────────────────────────────────────────────
|
|
2804
3081
|
handleEvent(event) {
|
|
3082
|
+
// Suppress all events for agents in silent retry
|
|
3083
|
+
const evtAgent = event.type === "task_assigned"
|
|
3084
|
+
? event.assignment.teammate
|
|
3085
|
+
: event.type === "task_completed"
|
|
3086
|
+
? event.result.teammate
|
|
3087
|
+
: event.teammate;
|
|
3088
|
+
if (this.silentAgents.has(evtAgent))
|
|
3089
|
+
return;
|
|
2805
3090
|
switch (event.type) {
|
|
2806
3091
|
case "task_assigned": {
|
|
2807
3092
|
// Track this task and start the animated status bar
|
|
@@ -2814,104 +3099,27 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
|
|
|
2814
3099
|
break;
|
|
2815
3100
|
}
|
|
2816
3101
|
case "task_completed": {
|
|
2817
|
-
// Remove from active tasks
|
|
3102
|
+
// Remove from active tasks and stop spinner.
|
|
3103
|
+
// Result display is deferred to drainAgentQueue() so the defensive
|
|
3104
|
+
// retry can update rawOutput before anything is shown to the user.
|
|
2818
3105
|
this.activeTasks.delete(event.result.teammate);
|
|
2819
3106
|
// Stop animation if no more active tasks
|
|
2820
3107
|
if (this.activeTasks.size === 0) {
|
|
2821
3108
|
this.stopStatusAnimation();
|
|
2822
3109
|
}
|
|
2823
|
-
// Suppress display for internal summarization tasks
|
|
2824
|
-
const activeEntry = this.agentActive.get(event.result.teammate);
|
|
2825
|
-
if (activeEntry?.type === "summarize")
|
|
2826
|
-
break;
|
|
2827
|
-
if (!this.chatView)
|
|
2828
|
-
this.input.deactivateAndErase();
|
|
2829
|
-
const raw = event.result.rawOutput ?? "";
|
|
2830
|
-
// Strip protocol artifacts
|
|
2831
|
-
const cleaned = raw
|
|
2832
|
-
.replace(/^TO:\s*\S+\s*\n/im, "")
|
|
2833
|
-
.replace(/^#\s+.+\n*/m, "")
|
|
2834
|
-
.replace(/```handoff\s*\n@\w+\s*\n[\s\S]*?```/g, "")
|
|
2835
|
-
.replace(/```json\s*\n\s*\{[\s\S]*?\}\s*\n\s*```\s*$/g, "")
|
|
2836
|
-
.trim();
|
|
2837
|
-
const sizeKB = cleaned ? Buffer.byteLength(cleaned, "utf-8") / 1024 : 0;
|
|
2838
|
-
// Header: "teammate: subject"
|
|
2839
|
-
const subject = event.result.summary || "Task completed";
|
|
2840
|
-
const displayTeammate = event.result.teammate === this.adapterName ? this.selfName : event.result.teammate;
|
|
2841
|
-
this.feedLine(concat(tp.accent(`${displayTeammate}: `), tp.text(subject)));
|
|
2842
|
-
this.lastCleanedOutput = cleaned;
|
|
2843
|
-
if (cleaned) {
|
|
2844
|
-
this.feedMarkdown(cleaned);
|
|
2845
|
-
}
|
|
2846
|
-
else {
|
|
2847
|
-
this.feedLine(tp.muted(" (no response text — the agent may have only performed tool actions)"));
|
|
2848
|
-
this.feedLine(tp.muted(` Use /debug ${event.result.teammate} to view full output`));
|
|
2849
|
-
// Show diagnostic hints for empty responses
|
|
2850
|
-
const diag = event.result.diagnostics;
|
|
2851
|
-
if (diag) {
|
|
2852
|
-
if (diag.exitCode !== 0 && diag.exitCode !== null) {
|
|
2853
|
-
this.feedLine(tp.warning(` ⚠ Process exited with code ${diag.exitCode}`));
|
|
2854
|
-
}
|
|
2855
|
-
if (diag.signal) {
|
|
2856
|
-
this.feedLine(tp.warning(` ⚠ Process killed by signal: ${diag.signal}`));
|
|
2857
|
-
}
|
|
2858
|
-
if (diag.debugFile) {
|
|
2859
|
-
this.feedLine(tp.muted(` Debug log: ${diag.debugFile}`));
|
|
2860
|
-
}
|
|
2861
|
-
}
|
|
2862
|
-
}
|
|
2863
|
-
// Render handoffs
|
|
2864
|
-
const handoffs = event.result.handoffs;
|
|
2865
|
-
if (handoffs.length > 0) {
|
|
2866
|
-
this.renderHandoffs(event.result.teammate, handoffs);
|
|
2867
|
-
}
|
|
2868
|
-
// Clickable [reply] [copy] actions after the response
|
|
2869
|
-
if (this.chatView && cleaned) {
|
|
2870
|
-
const t = theme();
|
|
2871
|
-
const teammate = event.result.teammate;
|
|
2872
|
-
const replyId = `reply-${teammate}-${Date.now()}`;
|
|
2873
|
-
this._replyContexts.set(replyId, { teammate, message: cleaned });
|
|
2874
|
-
this.chatView.appendActionList([
|
|
2875
|
-
{
|
|
2876
|
-
id: replyId,
|
|
2877
|
-
normalStyle: this.makeSpan({
|
|
2878
|
-
text: " [reply]",
|
|
2879
|
-
style: { fg: t.textDim },
|
|
2880
|
-
}),
|
|
2881
|
-
hoverStyle: this.makeSpan({
|
|
2882
|
-
text: " [reply]",
|
|
2883
|
-
style: { fg: t.accent },
|
|
2884
|
-
}),
|
|
2885
|
-
},
|
|
2886
|
-
{
|
|
2887
|
-
id: "copy",
|
|
2888
|
-
normalStyle: this.makeSpan({
|
|
2889
|
-
text: " [copy]",
|
|
2890
|
-
style: { fg: t.textDim },
|
|
2891
|
-
}),
|
|
2892
|
-
hoverStyle: this.makeSpan({
|
|
2893
|
-
text: " [copy]",
|
|
2894
|
-
style: { fg: t.accent },
|
|
2895
|
-
}),
|
|
2896
|
-
},
|
|
2897
|
-
]);
|
|
2898
|
-
}
|
|
2899
|
-
this.feedLine();
|
|
2900
|
-
// Auto-detect new teammates added during this task
|
|
2901
|
-
this.refreshTeammates();
|
|
2902
|
-
this.showPrompt();
|
|
2903
3110
|
break;
|
|
2904
3111
|
}
|
|
2905
|
-
case "error":
|
|
3112
|
+
case "error": {
|
|
2906
3113
|
this.activeTasks.delete(event.teammate);
|
|
2907
3114
|
if (this.activeTasks.size === 0)
|
|
2908
3115
|
this.stopStatusAnimation();
|
|
2909
3116
|
if (!this.chatView)
|
|
2910
3117
|
this.input.deactivateAndErase();
|
|
2911
|
-
const displayErr = event.teammate === this.
|
|
3118
|
+
const displayErr = event.teammate === this.selfName ? this.adapterName : event.teammate;
|
|
2912
3119
|
this.feedLine(tp.error(` ✖ ${displayErr}: ${event.error}`));
|
|
2913
3120
|
this.showPrompt();
|
|
2914
3121
|
break;
|
|
3122
|
+
}
|
|
2915
3123
|
}
|
|
2916
3124
|
}
|
|
2917
3125
|
async cmdStatus() {
|
|
@@ -2920,14 +3128,12 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
|
|
|
2920
3128
|
this.feedLine();
|
|
2921
3129
|
this.feedLine(tp.bold(" Status"));
|
|
2922
3130
|
this.feedLine(tp.muted(` ${"─".repeat(50)}`));
|
|
2923
|
-
// Show user avatar first if present
|
|
3131
|
+
// Show user avatar first if present (displayed as adapter name alias)
|
|
2924
3132
|
if (this.userAlias) {
|
|
2925
3133
|
const userStatus = statuses.get(this.userAlias);
|
|
2926
3134
|
if (userStatus) {
|
|
2927
|
-
this.feedLine(concat(tp.success("●"), tp.accent(` @${this.
|
|
2928
|
-
|
|
2929
|
-
if (t)
|
|
2930
|
-
this.feedLine(tp.muted(` ${t.role}`));
|
|
3135
|
+
this.feedLine(concat(tp.success("●"), tp.accent(` @${this.adapterName}`), tp.muted(" (you)")));
|
|
3136
|
+
this.feedLine(tp.muted(" Coding agent that performs tasks on your behalf."));
|
|
2931
3137
|
this.feedLine();
|
|
2932
3138
|
}
|
|
2933
3139
|
}
|
|
@@ -3070,7 +3276,7 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
|
|
|
3070
3276
|
return;
|
|
3071
3277
|
}
|
|
3072
3278
|
const removed = this.taskQueue.splice(n - 1, 1)[0];
|
|
3073
|
-
const cancelDisplay = removed.teammate === this.
|
|
3279
|
+
const cancelDisplay = removed.teammate === this.selfName ? this.adapterName : removed.teammate;
|
|
3074
3280
|
this.feedLine(concat(tp.muted(" Cancelled: "), tp.accent(`@${cancelDisplay}`), tp.muted(" — "), tp.text(removed.task.slice(0, 60))));
|
|
3075
3281
|
this.refreshView();
|
|
3076
3282
|
}
|
|
@@ -3106,11 +3312,54 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
|
|
|
3106
3312
|
const extraContext = entry.type === "btw" || entry.type === "debug"
|
|
3107
3313
|
? ""
|
|
3108
3314
|
: this.buildConversationContext();
|
|
3109
|
-
|
|
3315
|
+
let result = await this.orchestrator.assign({
|
|
3110
3316
|
teammate: entry.teammate,
|
|
3111
3317
|
task: entry.task,
|
|
3112
3318
|
extraContext: extraContext || undefined,
|
|
3113
3319
|
});
|
|
3320
|
+
// Defensive retry: if the agent produced no text output but exited
|
|
3321
|
+
// successfully, it likely ended its turn with only file edits.
|
|
3322
|
+
// Retry up to 2 times with progressively simpler prompts.
|
|
3323
|
+
const rawText = (result.rawOutput ?? "").trim();
|
|
3324
|
+
if (!rawText &&
|
|
3325
|
+
result.success &&
|
|
3326
|
+
entry.type !== "btw" &&
|
|
3327
|
+
entry.type !== "debug") {
|
|
3328
|
+
this.silentAgents.add(entry.teammate);
|
|
3329
|
+
// Attempt 1: ask the agent to summarize what it did
|
|
3330
|
+
const retry1 = await this.orchestrator.assign({
|
|
3331
|
+
teammate: entry.teammate,
|
|
3332
|
+
task: `You completed the previous task but produced no visible text output. The user cannot see your work without a text response.\n\nOriginal task: ${entry.task}\n\nPlease respond now with a summary of what you did. Do NOT update session or memory files. Do NOT use any tools. Just produce text output.\n\nFormat:\nTO: user\n# <Subject line>\n\n<Body — what you did, key decisions, files changed>`,
|
|
3333
|
+
raw: true,
|
|
3334
|
+
});
|
|
3335
|
+
const retry1Raw = (retry1.rawOutput ?? "").trim();
|
|
3336
|
+
if (retry1Raw) {
|
|
3337
|
+
result = {
|
|
3338
|
+
...result,
|
|
3339
|
+
rawOutput: retry1.rawOutput,
|
|
3340
|
+
summary: retry1.summary || result.summary,
|
|
3341
|
+
};
|
|
3342
|
+
}
|
|
3343
|
+
else {
|
|
3344
|
+
// Attempt 2: absolute minimum prompt — just ask for one sentence
|
|
3345
|
+
const retry2 = await this.orchestrator.assign({
|
|
3346
|
+
teammate: entry.teammate,
|
|
3347
|
+
task: `Say "Done." followed by one sentence describing what you changed. No tools. No file edits. Just text.`,
|
|
3348
|
+
raw: true,
|
|
3349
|
+
});
|
|
3350
|
+
const retry2Raw = (retry2.rawOutput ?? "").trim();
|
|
3351
|
+
if (retry2Raw) {
|
|
3352
|
+
result = {
|
|
3353
|
+
...result,
|
|
3354
|
+
rawOutput: retry2.rawOutput,
|
|
3355
|
+
summary: retry2.summary || result.summary,
|
|
3356
|
+
};
|
|
3357
|
+
}
|
|
3358
|
+
}
|
|
3359
|
+
this.silentAgents.delete(entry.teammate);
|
|
3360
|
+
}
|
|
3361
|
+
// Display the (possibly retried) result to the user
|
|
3362
|
+
this.displayTaskResult(result, entry.type);
|
|
3114
3363
|
// Write debug entry — skip for debug analysis tasks (avoid recursion)
|
|
3115
3364
|
if (entry.type !== "debug") {
|
|
3116
3365
|
this.writeDebugEntry(entry.teammate, entry.task, result, startTime);
|
|
@@ -3134,7 +3383,7 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
|
|
|
3134
3383
|
if (this.activeTasks.size === 0)
|
|
3135
3384
|
this.stopStatusAnimation();
|
|
3136
3385
|
const msg = err?.message ?? String(err);
|
|
3137
|
-
const displayAgent = agent === this.
|
|
3386
|
+
const displayAgent = agent === this.selfName ? this.adapterName : agent;
|
|
3138
3387
|
this.feedLine(tp.error(` ✖ @${displayAgent}: ${msg}`));
|
|
3139
3388
|
this.refreshView();
|
|
3140
3389
|
}
|
|
@@ -3236,7 +3485,11 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
|
|
|
3236
3485
|
const teammatesDir = join(cwd, ".teammates");
|
|
3237
3486
|
await mkdir(teammatesDir, { recursive: true });
|
|
3238
3487
|
const fromPath = argsStr.trim();
|
|
3239
|
-
if (fromPath) {
|
|
3488
|
+
if (fromPath === "pick") {
|
|
3489
|
+
// Persona picker mode: /init pick
|
|
3490
|
+
await this.runPersonaOnboardingInline(teammatesDir);
|
|
3491
|
+
}
|
|
3492
|
+
else if (fromPath) {
|
|
3240
3493
|
// Import mode: /init <path-to-another-project>
|
|
3241
3494
|
const resolved = resolve(fromPath);
|
|
3242
3495
|
let sourceDir;
|
|
@@ -3395,8 +3648,12 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
|
|
|
3395
3648
|
// Start draining
|
|
3396
3649
|
this.kickDrain();
|
|
3397
3650
|
}
|
|
3398
|
-
/**
|
|
3399
|
-
|
|
3651
|
+
/**
|
|
3652
|
+
* Run compaction + recall index update for a single teammate.
|
|
3653
|
+
* When `silent` is true, routine status messages go to the progress bar
|
|
3654
|
+
* only — the feed is reserved for actual work (weeklies/monthlies created).
|
|
3655
|
+
*/
|
|
3656
|
+
async runCompact(name, silent = false) {
|
|
3400
3657
|
const teammateDir = join(this.teammatesDir, name);
|
|
3401
3658
|
if (this.chatView) {
|
|
3402
3659
|
this.chatView.setProgress(`Compacting ${name}...`);
|
|
@@ -3407,8 +3664,14 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
|
|
|
3407
3664
|
spinner = ora({ text: `Compacting ${name}...`, color: "cyan" }).start();
|
|
3408
3665
|
}
|
|
3409
3666
|
try {
|
|
3667
|
+
// Auto-compact daily logs if they exceed the token budget (creates partial weeklies)
|
|
3668
|
+
const autoResult = await autoCompactForBudget(teammateDir, DAILY_LOG_BUDGET_TOKENS);
|
|
3669
|
+
// Regular episodic compaction (complete weeks → weeklies, old weeklies → monthlies)
|
|
3410
3670
|
const result = await compactEpisodic(teammateDir, name);
|
|
3411
3671
|
const parts = [];
|
|
3672
|
+
if (autoResult) {
|
|
3673
|
+
parts.push(`${autoResult.created.length} auto-compacted (budget overflow)`);
|
|
3674
|
+
}
|
|
3412
3675
|
if (result.weekliesCreated.length > 0) {
|
|
3413
3676
|
parts.push(`${result.weekliesCreated.length} weekly summaries created`);
|
|
3414
3677
|
}
|
|
@@ -3424,10 +3687,12 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
|
|
|
3424
3687
|
if (parts.length === 0) {
|
|
3425
3688
|
if (spinner)
|
|
3426
3689
|
spinner.info(`${name}: nothing to compact`);
|
|
3427
|
-
|
|
3690
|
+
// Silent: progress bar only; verbose: feed line
|
|
3691
|
+
if (this.chatView && !silent)
|
|
3428
3692
|
this.feedLine(tp.muted(` ℹ ${name}: nothing to compact`));
|
|
3429
3693
|
}
|
|
3430
3694
|
else {
|
|
3695
|
+
// Actual work done — always show in feed
|
|
3431
3696
|
if (spinner)
|
|
3432
3697
|
spinner.succeed(`${name}: ${parts.join(", ")}`);
|
|
3433
3698
|
if (this.chatView)
|
|
@@ -3453,7 +3718,8 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
|
|
|
3453
3718
|
syncSpinner.succeed(`${name}: index synced`);
|
|
3454
3719
|
if (this.chatView) {
|
|
3455
3720
|
this.chatView.setProgress(null);
|
|
3456
|
-
|
|
3721
|
+
if (!silent)
|
|
3722
|
+
this.feedLine(tp.success(` ✔ ${name}: index synced`));
|
|
3457
3723
|
}
|
|
3458
3724
|
}
|
|
3459
3725
|
catch {
|
|
@@ -3469,7 +3735,7 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
|
|
|
3469
3735
|
teammate: name,
|
|
3470
3736
|
task: wisdomPrompt,
|
|
3471
3737
|
});
|
|
3472
|
-
if (this.chatView)
|
|
3738
|
+
if (this.chatView && !silent)
|
|
3473
3739
|
this.feedLine(tp.muted(` ↻ ${name}: queued wisdom distillation`));
|
|
3474
3740
|
}
|
|
3475
3741
|
}
|
|
@@ -3483,6 +3749,7 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
|
|
|
3483
3749
|
spinner.fail(`${name}: ${msg}`);
|
|
3484
3750
|
if (this.chatView) {
|
|
3485
3751
|
this.chatView.setProgress(null);
|
|
3752
|
+
// Errors always show in feed
|
|
3486
3753
|
this.feedLine(tp.error(` ✖ ${name}: ${msg}`));
|
|
3487
3754
|
}
|
|
3488
3755
|
}
|
|
@@ -3517,34 +3784,34 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
|
|
|
3517
3784
|
this.refreshView();
|
|
3518
3785
|
return;
|
|
3519
3786
|
}
|
|
3520
|
-
const retroPrompt = `Run a structured self-retrospective. Review your SOUL.md, WISDOM.md, your last 2-3 weekly summaries (or last 7 daily logs if no weeklies exist), and any typed memories in your memory/ folder.
|
|
3521
|
-
|
|
3522
|
-
Produce a response with these four sections:
|
|
3523
|
-
|
|
3524
|
-
## 1. What's Working
|
|
3525
|
-
Things you do well, based on evidence from recent work. Patterns worth reinforcing or codifying into wisdom. Cite specific examples from daily logs or memories.
|
|
3526
|
-
|
|
3527
|
-
## 2. What's Not Working
|
|
3528
|
-
Friction, recurring issues, or patterns that aren't serving the project. Be specific — cite examples from daily logs or memories if possible.
|
|
3529
|
-
|
|
3530
|
-
## 3. Proposed SOUL.md Changes
|
|
3531
|
-
The core output. Each proposal is a **specific edit** to your SOUL.md. Use this exact format for each proposal:
|
|
3532
|
-
|
|
3533
|
-
**Proposal N: <short title>**
|
|
3534
|
-
- **Section:** <which SOUL.md section to change, e.g. Boundaries, Core Principles, Ownership>
|
|
3535
|
-
- **Before:** <the current text to replace, or "(new entry)" if adding>
|
|
3536
|
-
- **After:** <the exact replacement text>
|
|
3537
|
-
- **Why:** <evidence from recent work justifying the change>
|
|
3538
|
-
|
|
3539
|
-
Only propose changes to your own SOUL.md. If a change affects shared files, note that it needs a handoff.
|
|
3540
|
-
|
|
3541
|
-
## 4. Questions for the Team
|
|
3542
|
-
Issues that can't be resolved unilaterally — they need input from other teammates or the user.
|
|
3543
|
-
|
|
3544
|
-
**Rules:**
|
|
3545
|
-
- This is a self-review of YOUR work. Do not evaluate other teammates.
|
|
3546
|
-
- Evidence over opinion — cite specific examples.
|
|
3547
|
-
- No busywork — if everything is working well, say "all good, no changes." That's a valid outcome.
|
|
3787
|
+
const retroPrompt = `Run a structured self-retrospective. Review your SOUL.md, WISDOM.md, your last 2-3 weekly summaries (or last 7 daily logs if no weeklies exist), and any typed memories in your memory/ folder.
|
|
3788
|
+
|
|
3789
|
+
Produce a response with these four sections:
|
|
3790
|
+
|
|
3791
|
+
## 1. What's Working
|
|
3792
|
+
Things you do well, based on evidence from recent work. Patterns worth reinforcing or codifying into wisdom. Cite specific examples from daily logs or memories.
|
|
3793
|
+
|
|
3794
|
+
## 2. What's Not Working
|
|
3795
|
+
Friction, recurring issues, or patterns that aren't serving the project. Be specific — cite examples from daily logs or memories if possible.
|
|
3796
|
+
|
|
3797
|
+
## 3. Proposed SOUL.md Changes
|
|
3798
|
+
The core output. Each proposal is a **specific edit** to your SOUL.md. Use this exact format for each proposal:
|
|
3799
|
+
|
|
3800
|
+
**Proposal N: <short title>**
|
|
3801
|
+
- **Section:** <which SOUL.md section to change, e.g. Boundaries, Core Principles, Ownership>
|
|
3802
|
+
- **Before:** <the current text to replace, or "(new entry)" if adding>
|
|
3803
|
+
- **After:** <the exact replacement text>
|
|
3804
|
+
- **Why:** <evidence from recent work justifying the change>
|
|
3805
|
+
|
|
3806
|
+
Only propose changes to your own SOUL.md. If a change affects shared files, note that it needs a handoff.
|
|
3807
|
+
|
|
3808
|
+
## 4. Questions for the Team
|
|
3809
|
+
Issues that can't be resolved unilaterally — they need input from other teammates or the user.
|
|
3810
|
+
|
|
3811
|
+
**Rules:**
|
|
3812
|
+
- This is a self-review of YOUR work. Do not evaluate other teammates.
|
|
3813
|
+
- Evidence over opinion — cite specific examples.
|
|
3814
|
+
- No busywork — if everything is working well, say "all good, no changes." That's a valid outcome.
|
|
3548
3815
|
- Number each proposal (Proposal 1, Proposal 2, etc.) so the user can approve or reject individually.`;
|
|
3549
3816
|
const label = targets.length > 1
|
|
3550
3817
|
? targets.map((n) => `@${n}`).join(", ")
|
|
@@ -3609,36 +3876,35 @@ Issues that can't be resolved unilaterally — they need input from other teamma
|
|
|
3609
3876
|
.filter((n) => n !== this.selfName && n !== this.adapterName);
|
|
3610
3877
|
if (teammates.length === 0)
|
|
3611
3878
|
return;
|
|
3612
|
-
// 1.
|
|
3613
|
-
|
|
3614
|
-
oneWeekAgo.setDate(oneWeekAgo.getDate() - 7);
|
|
3615
|
-
const cutoff = oneWeekAgo.toISOString().slice(0, 10); // YYYY-MM-DD
|
|
3616
|
-
const needsCompact = [];
|
|
3879
|
+
// 1. Run compaction for all teammates (auto-compact + episodic + sync + wisdom)
|
|
3880
|
+
// Progress bar shows status; feed only shows lines when actual work is done
|
|
3617
3881
|
for (const name of teammates) {
|
|
3618
|
-
|
|
3619
|
-
|
|
3620
|
-
|
|
3621
|
-
const hasStale = entries.some((e) => {
|
|
3622
|
-
if (!e.endsWith(".md"))
|
|
3623
|
-
return false;
|
|
3624
|
-
const stem = e.replace(".md", "");
|
|
3625
|
-
return /^\d{4}-\d{2}-\d{2}$/.test(stem) && stem < cutoff;
|
|
3626
|
-
});
|
|
3627
|
-
if (hasStale)
|
|
3628
|
-
needsCompact.push(name);
|
|
3629
|
-
}
|
|
3630
|
-
catch {
|
|
3631
|
-
/* no memory dir */
|
|
3882
|
+
if (this.chatView) {
|
|
3883
|
+
this.chatView.setProgress(`Maintaining @${name}...`);
|
|
3884
|
+
this.refreshView();
|
|
3632
3885
|
}
|
|
3886
|
+
await this.runCompact(name, true);
|
|
3633
3887
|
}
|
|
3634
|
-
if (
|
|
3635
|
-
this.
|
|
3888
|
+
if (this.chatView) {
|
|
3889
|
+
this.chatView.setProgress(null);
|
|
3636
3890
|
this.refreshView();
|
|
3637
|
-
|
|
3638
|
-
|
|
3891
|
+
}
|
|
3892
|
+
// 2. Purge daily logs older than 30 days (disk + Vectra)
|
|
3893
|
+
const { Indexer } = await import("@teammates/recall");
|
|
3894
|
+
const indexer = new Indexer({ teammatesDir: this.teammatesDir });
|
|
3895
|
+
for (const name of teammates) {
|
|
3896
|
+
try {
|
|
3897
|
+
const purged = await purgeStaleDailies(join(this.teammatesDir, name));
|
|
3898
|
+
for (const file of purged) {
|
|
3899
|
+
const uri = `${name}/memory/${file}`;
|
|
3900
|
+
await indexer.deleteDocument(name, uri).catch(() => { });
|
|
3901
|
+
}
|
|
3902
|
+
}
|
|
3903
|
+
catch {
|
|
3904
|
+
/* purge failed — non-fatal */
|
|
3639
3905
|
}
|
|
3640
3906
|
}
|
|
3641
|
-
//
|
|
3907
|
+
// 3. Sync recall indexes (bundled library call)
|
|
3642
3908
|
try {
|
|
3643
3909
|
await syncRecallIndex(this.teammatesDir);
|
|
3644
3910
|
}
|
|
@@ -3777,7 +4043,7 @@ Issues that can't be resolved unilaterally — they need input from other teamma
|
|
|
3777
4043
|
// Has args — queue a task to apply the change
|
|
3778
4044
|
const task = `Update the file ${userMdPath} with the following change:\n\n${change}\n\nKeep the existing content intact unless the change explicitly replaces something. This is the user's profile — be concise and accurate.`;
|
|
3779
4045
|
this.taskQueue.push({ type: "agent", teammate: this.selfName, task });
|
|
3780
|
-
this.feedLine(concat(tp.muted(" Queued USER.md update → "), tp.accent(`@${this.
|
|
4046
|
+
this.feedLine(concat(tp.muted(" Queued USER.md update → "), tp.accent(`@${this.adapterName}`)));
|
|
3781
4047
|
this.feedLine();
|
|
3782
4048
|
this.refreshView();
|
|
3783
4049
|
this.kickDrain();
|
|
@@ -3794,7 +4060,7 @@ Issues that can't be resolved unilaterally — they need input from other teamma
|
|
|
3794
4060
|
teammate: this.selfName,
|
|
3795
4061
|
task: question,
|
|
3796
4062
|
});
|
|
3797
|
-
this.feedLine(concat(tp.muted(" Side question → "), tp.accent(`@${this.
|
|
4063
|
+
this.feedLine(concat(tp.muted(" Side question → "), tp.accent(`@${this.adapterName}`)));
|
|
3798
4064
|
this.feedLine();
|
|
3799
4065
|
this.refreshView();
|
|
3800
4066
|
this.kickDrain();
|