@teammates/cli 0.5.0 → 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/dist/adapter.d.ts +8 -0
- package/dist/adapter.js +147 -108
- package/dist/adapter.test.js +29 -16
- package/dist/adapters/cli-proxy.js +58 -2
- package/dist/adapters/copilot.js +11 -1
- package/dist/adapters/echo.js +3 -1
- 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 +135 -102
- 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 +2 -1
- package/dist/index.js +2 -1
- package/dist/onboard.js +165 -165
- package/dist/orchestrator.js +4 -1
- 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/package.json +3 -3
- package/personas/architect.md +4 -0
- package/personas/backend.md +4 -0
- package/personas/data-engineer.md +4 -0
- package/personas/designer.md +4 -0
- package/personas/devops.md +4 -0
- package/personas/frontend.md +4 -0
- package/personas/ml-ai.md +4 -0
- package/personas/mobile.md +4 -0
- package/personas/performance.md +4 -0
- package/personas/pm.md +4 -0
- package/personas/prompt-engineer.md +122 -0
- package/personas/qa.md +4 -0
- package/personas/security.md +4 -0
- package/personas/sre.md +4 -0
- package/personas/swe.md +4 -0
- package/personas/tech-writer.md +4 -0
package/dist/cli-args.js
CHANGED
|
@@ -97,28 +97,28 @@ export async function resolveAdapter(name, opts = {}) {
|
|
|
97
97
|
}
|
|
98
98
|
// ─── Usage ───────────────────────────────────────────────────────────
|
|
99
99
|
export function printUsage() {
|
|
100
|
-
console.log(`
|
|
101
|
-
${chalk.bold("@teammates/cli")} — Agent-agnostic teammate orchestrator
|
|
102
|
-
|
|
103
|
-
${chalk.bold("Usage:")}
|
|
104
|
-
teammates <agent> Launch session with an agent
|
|
105
|
-
teammates codex Use OpenAI Codex
|
|
106
|
-
teammates aider Use Aider
|
|
107
|
-
|
|
108
|
-
${chalk.bold("Options:")}
|
|
109
|
-
--model <model> Override the agent model
|
|
110
|
-
--dir <path> Override .teammates/ location
|
|
111
|
-
|
|
112
|
-
${chalk.bold("Agents:")}
|
|
113
|
-
claude Claude Code CLI (requires 'claude' on PATH)
|
|
114
|
-
codex OpenAI Codex CLI (requires 'codex' on PATH)
|
|
115
|
-
aider Aider CLI (requires 'aider' on PATH)
|
|
116
|
-
echo Test adapter — echoes prompts (no external agent)
|
|
117
|
-
|
|
118
|
-
${chalk.bold("In-session:")}
|
|
119
|
-
@teammate <task> Assign directly via @mention
|
|
120
|
-
<text> Auto-route to the best teammate
|
|
121
|
-
/status Session overview
|
|
122
|
-
/help All commands
|
|
100
|
+
console.log(`
|
|
101
|
+
${chalk.bold("@teammates/cli")} — Agent-agnostic teammate orchestrator
|
|
102
|
+
|
|
103
|
+
${chalk.bold("Usage:")}
|
|
104
|
+
teammates <agent> Launch session with an agent
|
|
105
|
+
teammates codex Use OpenAI Codex
|
|
106
|
+
teammates aider Use Aider
|
|
107
|
+
|
|
108
|
+
${chalk.bold("Options:")}
|
|
109
|
+
--model <model> Override the agent model
|
|
110
|
+
--dir <path> Override .teammates/ location
|
|
111
|
+
|
|
112
|
+
${chalk.bold("Agents:")}
|
|
113
|
+
claude Claude Code CLI (requires 'claude' on PATH)
|
|
114
|
+
codex OpenAI Codex CLI (requires 'codex' on PATH)
|
|
115
|
+
aider Aider CLI (requires 'aider' on PATH)
|
|
116
|
+
echo Test adapter — echoes prompts (no external agent)
|
|
117
|
+
|
|
118
|
+
${chalk.bold("In-session:")}
|
|
119
|
+
@teammate <task> Assign directly via @mention
|
|
120
|
+
<text> Auto-route to the best teammate
|
|
121
|
+
/status Session overview
|
|
122
|
+
/help All commands
|
|
123
123
|
`.trim());
|
|
124
124
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { mkdir, rm } from "node:fs/promises";
|
|
2
|
+
import { tmpdir } from "node:os";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
5
|
+
import { findTeammatesDir, PKG_VERSION, parseCliArgs, printUsage, } from "./cli-args.js";
|
|
6
|
+
describe("parseCliArgs", () => {
|
|
7
|
+
it("returns defaults with no arguments", () => {
|
|
8
|
+
const result = parseCliArgs([]);
|
|
9
|
+
expect(result).toEqual({
|
|
10
|
+
showHelp: false,
|
|
11
|
+
modelOverride: undefined,
|
|
12
|
+
dirOverride: undefined,
|
|
13
|
+
adapterName: "echo",
|
|
14
|
+
agentPassthrough: [],
|
|
15
|
+
});
|
|
16
|
+
});
|
|
17
|
+
it("parses --help flag", () => {
|
|
18
|
+
const result = parseCliArgs(["--help"]);
|
|
19
|
+
expect(result.showHelp).toBe(true);
|
|
20
|
+
});
|
|
21
|
+
it("parses --model option with value", () => {
|
|
22
|
+
const result = parseCliArgs(["--model", "gpt-4"]);
|
|
23
|
+
expect(result.modelOverride).toBe("gpt-4");
|
|
24
|
+
});
|
|
25
|
+
it("parses --dir option with value", () => {
|
|
26
|
+
const result = parseCliArgs(["--dir", "/custom/path"]);
|
|
27
|
+
expect(result.dirOverride).toBe("/custom/path");
|
|
28
|
+
});
|
|
29
|
+
it("extracts adapter name as first positional argument", () => {
|
|
30
|
+
const result = parseCliArgs(["claude"]);
|
|
31
|
+
expect(result.adapterName).toBe("claude");
|
|
32
|
+
});
|
|
33
|
+
it("passes remaining args as agentPassthrough", () => {
|
|
34
|
+
const result = parseCliArgs(["claude", "--verbose", "--debug"]);
|
|
35
|
+
expect(result.adapterName).toBe("claude");
|
|
36
|
+
expect(result.agentPassthrough).toEqual(["--verbose", "--debug"]);
|
|
37
|
+
});
|
|
38
|
+
it("handles all options together", () => {
|
|
39
|
+
const result = parseCliArgs([
|
|
40
|
+
"--help",
|
|
41
|
+
"--model",
|
|
42
|
+
"sonnet",
|
|
43
|
+
"--dir",
|
|
44
|
+
"/tmp/tm",
|
|
45
|
+
"codex",
|
|
46
|
+
"--extra",
|
|
47
|
+
]);
|
|
48
|
+
expect(result.showHelp).toBe(true);
|
|
49
|
+
expect(result.modelOverride).toBe("sonnet");
|
|
50
|
+
expect(result.dirOverride).toBe("/tmp/tm");
|
|
51
|
+
expect(result.adapterName).toBe("codex");
|
|
52
|
+
expect(result.agentPassthrough).toEqual(["--extra"]);
|
|
53
|
+
});
|
|
54
|
+
it("does not consume --model when no value follows", () => {
|
|
55
|
+
const result = parseCliArgs(["--model"]);
|
|
56
|
+
// getOption finds --model but no value after it, so it's left in args
|
|
57
|
+
// args.shift() then picks it up as the adapter name
|
|
58
|
+
expect(result.modelOverride).toBeUndefined();
|
|
59
|
+
expect(result.adapterName).toBe("--model");
|
|
60
|
+
});
|
|
61
|
+
it("does not treat --help value as an option value", () => {
|
|
62
|
+
const result = parseCliArgs(["--help", "claude"]);
|
|
63
|
+
expect(result.showHelp).toBe(true);
|
|
64
|
+
expect(result.adapterName).toBe("claude");
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
// ── PKG_VERSION ────────────────────────────────────────────────────
|
|
68
|
+
describe("PKG_VERSION", () => {
|
|
69
|
+
it("is a valid semver-like string", () => {
|
|
70
|
+
expect(PKG_VERSION).toMatch(/^\d+\.\d+\.\d+/);
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
// ── findTeammatesDir ───────────────────────────────────────────────
|
|
74
|
+
describe("findTeammatesDir", () => {
|
|
75
|
+
let testDir;
|
|
76
|
+
beforeEach(async () => {
|
|
77
|
+
testDir = join(tmpdir(), `cli-args-test-${Date.now()}-${Math.random().toString(36).slice(2)}`);
|
|
78
|
+
await mkdir(testDir, { recursive: true });
|
|
79
|
+
});
|
|
80
|
+
afterEach(async () => {
|
|
81
|
+
await rm(testDir, { recursive: true, force: true });
|
|
82
|
+
});
|
|
83
|
+
it("returns dirOverride when provided", async () => {
|
|
84
|
+
const result = await findTeammatesDir("/some/custom/dir");
|
|
85
|
+
expect(result).toContain("some");
|
|
86
|
+
});
|
|
87
|
+
it("returns .teammates dir when it exists under cwd", async () => {
|
|
88
|
+
const tmDir = join(testDir, ".teammates");
|
|
89
|
+
await mkdir(tmDir, { recursive: true });
|
|
90
|
+
// Mock process.cwd to return our test dir
|
|
91
|
+
const origCwd = process.cwd;
|
|
92
|
+
process.cwd = () => testDir;
|
|
93
|
+
try {
|
|
94
|
+
const result = await findTeammatesDir(undefined);
|
|
95
|
+
expect(result).toBe(tmDir);
|
|
96
|
+
}
|
|
97
|
+
finally {
|
|
98
|
+
process.cwd = origCwd;
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
it("returns null when no .teammates dir exists", async () => {
|
|
102
|
+
const origCwd = process.cwd;
|
|
103
|
+
process.cwd = () => testDir;
|
|
104
|
+
try {
|
|
105
|
+
const result = await findTeammatesDir(undefined);
|
|
106
|
+
expect(result).toBeNull();
|
|
107
|
+
}
|
|
108
|
+
finally {
|
|
109
|
+
process.cwd = origCwd;
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
// ── printUsage ────────────────────────────────────────────────────
|
|
114
|
+
describe("printUsage", () => {
|
|
115
|
+
it("prints usage text to console", () => {
|
|
116
|
+
const spy = vi.spyOn(console, "log").mockImplementation(() => { });
|
|
117
|
+
printUsage();
|
|
118
|
+
expect(spy).toHaveBeenCalled();
|
|
119
|
+
const output = spy.mock.calls[0][0];
|
|
120
|
+
expect(output).toContain("teammates");
|
|
121
|
+
expect(output).toContain("--model");
|
|
122
|
+
expect(output).toContain("--dir");
|
|
123
|
+
spy.mockRestore();
|
|
124
|
+
});
|
|
125
|
+
});
|
package/dist/cli.js
CHANGED
|
@@ -15,16 +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
|
-
import { loadPersonas, scaffoldFromPersona } from "./personas.js";
|
|
27
26
|
import { Orchestrator } from "./orchestrator.js";
|
|
27
|
+
import { loadPersonas, scaffoldFromPersona } from "./personas.js";
|
|
28
28
|
import { colorToHex, theme, tp } from "./theme.js";
|
|
29
29
|
// ─── Parsed CLI arguments ────────────────────────────────────────────
|
|
30
30
|
const cliArgs = parseCliArgs();
|
|
@@ -69,9 +69,7 @@ class TeammatesREPL {
|
|
|
69
69
|
.trim();
|
|
70
70
|
// Header: "teammate: subject"
|
|
71
71
|
const subject = result.summary || "Task completed";
|
|
72
|
-
const displayTeammate = result.teammate === this.selfName
|
|
73
|
-
? this.adapterName
|
|
74
|
-
: result.teammate;
|
|
72
|
+
const displayTeammate = result.teammate === this.selfName ? this.adapterName : result.teammate;
|
|
75
73
|
this.feedLine(concat(tp.accent(`${displayTeammate}: `), tp.text(subject)));
|
|
76
74
|
this.lastCleanedOutput = cleaned;
|
|
77
75
|
if (cleaned) {
|
|
@@ -233,7 +231,6 @@ class TeammatesREPL {
|
|
|
233
231
|
ctrlcPending = false; // true after first Ctrl+C, waiting for second
|
|
234
232
|
ctrlcTimer = null;
|
|
235
233
|
lastCleanedOutput = ""; // last teammate output for clipboard copy
|
|
236
|
-
dispatching = false;
|
|
237
234
|
autoApproveHandoffs = false;
|
|
238
235
|
/** Last debug log file path per teammate — for /debug analysis. */
|
|
239
236
|
lastDebugFiles = new Map();
|
|
@@ -948,14 +945,14 @@ class TeammatesREPL {
|
|
|
948
945
|
const changes = proposals
|
|
949
946
|
.map((p) => `- **Proposal ${p.index}: ${p.title}**\n - Section: ${p.section}\n - Before: ${p.before}\n - After: ${p.after}`)
|
|
950
947
|
.join("\n\n");
|
|
951
|
-
const applyPrompt = `The user approved the following SOUL.md changes from your retrospective. Apply them now.
|
|
952
|
-
|
|
953
|
-
**Edit your SOUL.md file** (\`.teammates/${teammate}/SOUL.md\`) to incorporate these changes:
|
|
954
|
-
|
|
955
|
-
${changes}
|
|
956
|
-
|
|
957
|
-
After editing SOUL.md, record a brief summary of the retro outcome in your daily log: which proposals were approved and what changed.
|
|
958
|
-
|
|
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
|
+
|
|
959
956
|
Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily log.`;
|
|
960
957
|
this.taskQueue.push({ type: "agent", teammate, task: applyPrompt });
|
|
961
958
|
this.feedLine(concat(tp.muted(" Queued SOUL.md update for "), tp.accent(`@${teammate}`)));
|
|
@@ -997,7 +994,7 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
|
|
|
997
994
|
mentioned = [];
|
|
998
995
|
while ((m = mentionRegex.exec(input)) !== null) {
|
|
999
996
|
// Remap adapter name alias → user avatar for routing
|
|
1000
|
-
const name =
|
|
997
|
+
const name = m[1] === this.adapterName && this.userAlias ? this.selfName : m[1];
|
|
1001
998
|
if (allNames.includes(name) && !mentioned.includes(name)) {
|
|
1002
999
|
mentioned.push(name);
|
|
1003
1000
|
}
|
|
@@ -1168,11 +1165,12 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
|
|
|
1168
1165
|
const folderName = name.toLowerCase().replace(/[^a-z0-9_-]/g, "");
|
|
1169
1166
|
await scaffoldFromPersona(teammatesDir, folderName, p);
|
|
1170
1167
|
created.push(folderName);
|
|
1171
|
-
console.log(chalk.green(" ✔ ") +
|
|
1168
|
+
console.log(chalk.green(" ✔ ") +
|
|
1169
|
+
chalk.white(`@${folderName}`) +
|
|
1170
|
+
chalk.gray(` — ${p.persona}`));
|
|
1172
1171
|
}
|
|
1173
1172
|
console.log();
|
|
1174
|
-
console.log(chalk.green(` ✔ Created ${created.length} teammate${created.length > 1 ? "s" : ""}: `) +
|
|
1175
|
-
chalk.white(created.map((n) => `@${n}`).join(", ")));
|
|
1173
|
+
console.log(chalk.green(` ✔ Created ${created.length} teammate${created.length > 1 ? "s" : ""}: `) + chalk.white(created.map((n) => `@${n}`).join(", ")));
|
|
1176
1174
|
console.log(chalk.gray(" Tip: Your agent will adapt ownership and capabilities to this codebase on first task."));
|
|
1177
1175
|
console.log();
|
|
1178
1176
|
}
|
|
@@ -1658,8 +1656,7 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
|
|
|
1658
1656
|
};
|
|
1659
1657
|
this.writeUserProfile(teammatesDir, login, answers);
|
|
1660
1658
|
this.createUserAvatar(teammatesDir, login, answers);
|
|
1661
|
-
console.log(chalk.green(" ✔ ") +
|
|
1662
|
-
chalk.gray(`Profile created — avatar @${login}`));
|
|
1659
|
+
console.log(chalk.green(" ✔ ") + chalk.gray(`Profile created — avatar @${login}`));
|
|
1663
1660
|
console.log();
|
|
1664
1661
|
}
|
|
1665
1662
|
/**
|
|
@@ -1669,7 +1666,10 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
|
|
|
1669
1666
|
console.log();
|
|
1670
1667
|
console.log(chalk.gray(" (alias is required, press Enter to skip others)\n"));
|
|
1671
1668
|
const aliasRaw = await this.askInput("Your alias (e.g., alex): ");
|
|
1672
|
-
const alias = aliasRaw
|
|
1669
|
+
const alias = aliasRaw
|
|
1670
|
+
.toLowerCase()
|
|
1671
|
+
.replace(/[^a-z0-9_-]/g, "")
|
|
1672
|
+
.trim();
|
|
1673
1673
|
if (!alias) {
|
|
1674
1674
|
console.log(chalk.yellow(" Alias is required. Run /user to try again.\n"));
|
|
1675
1675
|
return;
|
|
@@ -1692,8 +1692,7 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
|
|
|
1692
1692
|
this.writeUserProfile(teammatesDir, alias, answers);
|
|
1693
1693
|
this.createUserAvatar(teammatesDir, alias, answers);
|
|
1694
1694
|
console.log();
|
|
1695
|
-
console.log(chalk.green(" ✔ ") +
|
|
1696
|
-
chalk.gray(`Profile created — avatar @${alias}`));
|
|
1695
|
+
console.log(chalk.green(" ✔ ") + chalk.gray(`Profile created — avatar @${alias}`));
|
|
1697
1696
|
console.log(chalk.gray(" Update anytime with /user"));
|
|
1698
1697
|
console.log();
|
|
1699
1698
|
}
|
|
@@ -1781,12 +1780,16 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
|
|
|
1781
1780
|
if (roleMatch)
|
|
1782
1781
|
role = roleMatch[1].trim();
|
|
1783
1782
|
}
|
|
1784
|
-
catch {
|
|
1783
|
+
catch {
|
|
1784
|
+
/* avatar folder may not exist yet */
|
|
1785
|
+
}
|
|
1785
1786
|
let wisdom = "";
|
|
1786
1787
|
try {
|
|
1787
1788
|
wisdom = readFileSync(join(avatarDir, "WISDOM.md"), "utf-8");
|
|
1788
1789
|
}
|
|
1789
|
-
catch {
|
|
1790
|
+
catch {
|
|
1791
|
+
/* ok */
|
|
1792
|
+
}
|
|
1790
1793
|
registry.register({
|
|
1791
1794
|
name: alias,
|
|
1792
1795
|
type: "human",
|
|
@@ -1799,7 +1802,9 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
|
|
|
1799
1802
|
routingKeywords: [],
|
|
1800
1803
|
});
|
|
1801
1804
|
// Set presence to online (local user is always online)
|
|
1802
|
-
this.orchestrator
|
|
1805
|
+
this.orchestrator
|
|
1806
|
+
.getAllStatuses()
|
|
1807
|
+
.set(alias, { state: "idle", presence: "online" });
|
|
1803
1808
|
// Update the adapter name so tasks route to the avatar
|
|
1804
1809
|
this.userAlias = alias;
|
|
1805
1810
|
}
|
|
@@ -1891,9 +1896,7 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
|
|
|
1891
1896
|
if (completedArgs > 0)
|
|
1892
1897
|
return [];
|
|
1893
1898
|
const lower = partial.toLowerCase();
|
|
1894
|
-
return TeammatesREPL.CONFIGURABLE_SERVICES
|
|
1895
|
-
.filter((s) => s.startsWith(lower))
|
|
1896
|
-
.map((s) => ({
|
|
1899
|
+
return TeammatesREPL.CONFIGURABLE_SERVICES.filter((s) => s.startsWith(lower)).map((s) => ({
|
|
1897
1900
|
label: s,
|
|
1898
1901
|
description: `configure ${s}`,
|
|
1899
1902
|
completion: `/${cmdName} ${s} `,
|
|
@@ -2184,7 +2187,9 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
|
|
|
2184
2187
|
routingKeywords: [],
|
|
2185
2188
|
cwd: dirname(this.teammatesDir),
|
|
2186
2189
|
});
|
|
2187
|
-
this.orchestrator
|
|
2190
|
+
this.orchestrator
|
|
2191
|
+
.getAllStatuses()
|
|
2192
|
+
.set(this.adapterName, { state: "idle", presence: "online" });
|
|
2188
2193
|
}
|
|
2189
2194
|
// Populate roster on the adapter so prompts include team info
|
|
2190
2195
|
// Exclude the user avatar and adapter fallback — neither is an addressable teammate
|
|
@@ -2210,7 +2215,9 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
|
|
|
2210
2215
|
borderStyle: (s) => chalk.gray(s),
|
|
2211
2216
|
colorize: (value) => {
|
|
2212
2217
|
const validNames = new Set([
|
|
2213
|
-
...this.orchestrator
|
|
2218
|
+
...this.orchestrator
|
|
2219
|
+
.listTeammates()
|
|
2220
|
+
.filter((n) => n !== this.adapterName && n !== this.userAlias),
|
|
2214
2221
|
this.adapterName,
|
|
2215
2222
|
"everyone",
|
|
2216
2223
|
]);
|
|
@@ -2258,9 +2265,12 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
|
|
|
2258
2265
|
const bannerTeammates = [];
|
|
2259
2266
|
// Add user avatar first (displayed as adapter name alias)
|
|
2260
2267
|
if (this.userAlias) {
|
|
2261
|
-
const ut = reg.get(this.userAlias);
|
|
2262
2268
|
const up = statuses.get(this.userAlias)?.presence ?? "online";
|
|
2263
|
-
bannerTeammates.push({
|
|
2269
|
+
bannerTeammates.push({
|
|
2270
|
+
name: this.adapterName,
|
|
2271
|
+
role: "Coding agent that performs tasks on your behalf.",
|
|
2272
|
+
presence: up,
|
|
2273
|
+
});
|
|
2264
2274
|
}
|
|
2265
2275
|
for (const name of names) {
|
|
2266
2276
|
const t = reg.get(name);
|
|
@@ -2299,7 +2309,9 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
|
|
|
2299
2309
|
}
|
|
2300
2310
|
// Colorize @mentions only if they reference a valid teammate or the user
|
|
2301
2311
|
const validNames = new Set([
|
|
2302
|
-
...this.orchestrator
|
|
2312
|
+
...this.orchestrator
|
|
2313
|
+
.listTeammates()
|
|
2314
|
+
.filter((n) => n !== this.adapterName && n !== this.userAlias),
|
|
2303
2315
|
this.adapterName,
|
|
2304
2316
|
"everyone",
|
|
2305
2317
|
]);
|
|
@@ -2591,7 +2603,7 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
|
|
|
2591
2603
|
const preMentions = [];
|
|
2592
2604
|
while ((pm = preMentionRegex.exec(rawLine)) !== null) {
|
|
2593
2605
|
// Remap adapter name alias → user avatar for routing
|
|
2594
|
-
const name =
|
|
2606
|
+
const name = pm[1] === this.adapterName && this.userAlias ? this.selfName : pm[1];
|
|
2595
2607
|
if (allNames.includes(name) && !preMentions.includes(name)) {
|
|
2596
2608
|
preMentions.push(name);
|
|
2597
2609
|
}
|
|
@@ -2663,16 +2675,12 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
|
|
|
2663
2675
|
}
|
|
2664
2676
|
// Slash commands
|
|
2665
2677
|
if (input.startsWith("/")) {
|
|
2666
|
-
this.dispatching = true;
|
|
2667
2678
|
try {
|
|
2668
2679
|
await this.dispatch(input);
|
|
2669
2680
|
}
|
|
2670
2681
|
catch (err) {
|
|
2671
2682
|
this.feedLine(tp.error(`Error: ${err.message}`));
|
|
2672
2683
|
}
|
|
2673
|
-
finally {
|
|
2674
|
-
this.dispatching = false;
|
|
2675
|
-
}
|
|
2676
2684
|
this.refreshView();
|
|
2677
2685
|
return;
|
|
2678
2686
|
}
|
|
@@ -2710,14 +2718,22 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
|
|
|
2710
2718
|
// Show user avatar first (displayed as adapter name alias)
|
|
2711
2719
|
if (this.userAlias) {
|
|
2712
2720
|
const up = statuses.get(this.userAlias)?.presence ?? "online";
|
|
2713
|
-
const udot = up === "online"
|
|
2721
|
+
const udot = up === "online"
|
|
2722
|
+
? tp.success("●")
|
|
2723
|
+
: up === "reachable"
|
|
2724
|
+
? tp.warning("●")
|
|
2725
|
+
: tp.error("●");
|
|
2714
2726
|
this.feedLine(concat(tp.text(" "), udot, tp.accent(` @${this.adapterName.padEnd(14)}`), tp.muted("Coding agent that performs tasks on your behalf.")));
|
|
2715
2727
|
}
|
|
2716
2728
|
for (const name of teammates) {
|
|
2717
2729
|
const t = registry.get(name);
|
|
2718
2730
|
if (t) {
|
|
2719
2731
|
const p = statuses.get(name)?.presence ?? "online";
|
|
2720
|
-
const dot = p === "online"
|
|
2732
|
+
const dot = p === "online"
|
|
2733
|
+
? tp.success("●")
|
|
2734
|
+
: p === "reachable"
|
|
2735
|
+
? tp.warning("●")
|
|
2736
|
+
: tp.error("●");
|
|
2721
2737
|
this.feedLine(concat(tp.text(" "), dot, tp.accent(` @${name.padEnd(14)}`), tp.muted(t.role)));
|
|
2722
2738
|
}
|
|
2723
2739
|
}
|
|
@@ -2910,7 +2926,10 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
|
|
|
2910
2926
|
// Get username for confirmation
|
|
2911
2927
|
let username = "";
|
|
2912
2928
|
try {
|
|
2913
|
-
username = execSync("gh api user --jq .login", {
|
|
2929
|
+
username = execSync("gh api user --jq .login", {
|
|
2930
|
+
stdio: "pipe",
|
|
2931
|
+
encoding: "utf-8",
|
|
2932
|
+
}).trim();
|
|
2914
2933
|
}
|
|
2915
2934
|
catch {
|
|
2916
2935
|
// non-critical
|
|
@@ -3090,7 +3109,7 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
|
|
|
3090
3109
|
}
|
|
3091
3110
|
break;
|
|
3092
3111
|
}
|
|
3093
|
-
case "error":
|
|
3112
|
+
case "error": {
|
|
3094
3113
|
this.activeTasks.delete(event.teammate);
|
|
3095
3114
|
if (this.activeTasks.size === 0)
|
|
3096
3115
|
this.stopStatusAnimation();
|
|
@@ -3100,6 +3119,7 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
|
|
|
3100
3119
|
this.feedLine(tp.error(` ✖ ${displayErr}: ${event.error}`));
|
|
3101
3120
|
this.showPrompt();
|
|
3102
3121
|
break;
|
|
3122
|
+
}
|
|
3103
3123
|
}
|
|
3104
3124
|
}
|
|
3105
3125
|
async cmdStatus() {
|
|
@@ -3628,8 +3648,12 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
|
|
|
3628
3648
|
// Start draining
|
|
3629
3649
|
this.kickDrain();
|
|
3630
3650
|
}
|
|
3631
|
-
/**
|
|
3632
|
-
|
|
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) {
|
|
3633
3657
|
const teammateDir = join(this.teammatesDir, name);
|
|
3634
3658
|
if (this.chatView) {
|
|
3635
3659
|
this.chatView.setProgress(`Compacting ${name}...`);
|
|
@@ -3640,8 +3664,14 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
|
|
|
3640
3664
|
spinner = ora({ text: `Compacting ${name}...`, color: "cyan" }).start();
|
|
3641
3665
|
}
|
|
3642
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)
|
|
3643
3670
|
const result = await compactEpisodic(teammateDir, name);
|
|
3644
3671
|
const parts = [];
|
|
3672
|
+
if (autoResult) {
|
|
3673
|
+
parts.push(`${autoResult.created.length} auto-compacted (budget overflow)`);
|
|
3674
|
+
}
|
|
3645
3675
|
if (result.weekliesCreated.length > 0) {
|
|
3646
3676
|
parts.push(`${result.weekliesCreated.length} weekly summaries created`);
|
|
3647
3677
|
}
|
|
@@ -3657,10 +3687,12 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
|
|
|
3657
3687
|
if (parts.length === 0) {
|
|
3658
3688
|
if (spinner)
|
|
3659
3689
|
spinner.info(`${name}: nothing to compact`);
|
|
3660
|
-
|
|
3690
|
+
// Silent: progress bar only; verbose: feed line
|
|
3691
|
+
if (this.chatView && !silent)
|
|
3661
3692
|
this.feedLine(tp.muted(` ℹ ${name}: nothing to compact`));
|
|
3662
3693
|
}
|
|
3663
3694
|
else {
|
|
3695
|
+
// Actual work done — always show in feed
|
|
3664
3696
|
if (spinner)
|
|
3665
3697
|
spinner.succeed(`${name}: ${parts.join(", ")}`);
|
|
3666
3698
|
if (this.chatView)
|
|
@@ -3686,7 +3718,8 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
|
|
|
3686
3718
|
syncSpinner.succeed(`${name}: index synced`);
|
|
3687
3719
|
if (this.chatView) {
|
|
3688
3720
|
this.chatView.setProgress(null);
|
|
3689
|
-
|
|
3721
|
+
if (!silent)
|
|
3722
|
+
this.feedLine(tp.success(` ✔ ${name}: index synced`));
|
|
3690
3723
|
}
|
|
3691
3724
|
}
|
|
3692
3725
|
catch {
|
|
@@ -3702,7 +3735,7 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
|
|
|
3702
3735
|
teammate: name,
|
|
3703
3736
|
task: wisdomPrompt,
|
|
3704
3737
|
});
|
|
3705
|
-
if (this.chatView)
|
|
3738
|
+
if (this.chatView && !silent)
|
|
3706
3739
|
this.feedLine(tp.muted(` ↻ ${name}: queued wisdom distillation`));
|
|
3707
3740
|
}
|
|
3708
3741
|
}
|
|
@@ -3716,6 +3749,7 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
|
|
|
3716
3749
|
spinner.fail(`${name}: ${msg}`);
|
|
3717
3750
|
if (this.chatView) {
|
|
3718
3751
|
this.chatView.setProgress(null);
|
|
3752
|
+
// Errors always show in feed
|
|
3719
3753
|
this.feedLine(tp.error(` ✖ ${name}: ${msg}`));
|
|
3720
3754
|
}
|
|
3721
3755
|
}
|
|
@@ -3750,34 +3784,34 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
|
|
|
3750
3784
|
this.refreshView();
|
|
3751
3785
|
return;
|
|
3752
3786
|
}
|
|
3753
|
-
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.
|
|
3754
|
-
|
|
3755
|
-
Produce a response with these four sections:
|
|
3756
|
-
|
|
3757
|
-
## 1. What's Working
|
|
3758
|
-
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.
|
|
3759
|
-
|
|
3760
|
-
## 2. What's Not Working
|
|
3761
|
-
Friction, recurring issues, or patterns that aren't serving the project. Be specific — cite examples from daily logs or memories if possible.
|
|
3762
|
-
|
|
3763
|
-
## 3. Proposed SOUL.md Changes
|
|
3764
|
-
The core output. Each proposal is a **specific edit** to your SOUL.md. Use this exact format for each proposal:
|
|
3765
|
-
|
|
3766
|
-
**Proposal N: <short title>**
|
|
3767
|
-
- **Section:** <which SOUL.md section to change, e.g. Boundaries, Core Principles, Ownership>
|
|
3768
|
-
- **Before:** <the current text to replace, or "(new entry)" if adding>
|
|
3769
|
-
- **After:** <the exact replacement text>
|
|
3770
|
-
- **Why:** <evidence from recent work justifying the change>
|
|
3771
|
-
|
|
3772
|
-
Only propose changes to your own SOUL.md. If a change affects shared files, note that it needs a handoff.
|
|
3773
|
-
|
|
3774
|
-
## 4. Questions for the Team
|
|
3775
|
-
Issues that can't be resolved unilaterally — they need input from other teammates or the user.
|
|
3776
|
-
|
|
3777
|
-
**Rules:**
|
|
3778
|
-
- This is a self-review of YOUR work. Do not evaluate other teammates.
|
|
3779
|
-
- Evidence over opinion — cite specific examples.
|
|
3780
|
-
- 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.
|
|
3781
3815
|
- Number each proposal (Proposal 1, Proposal 2, etc.) so the user can approve or reject individually.`;
|
|
3782
3816
|
const label = targets.length > 1
|
|
3783
3817
|
? targets.map((n) => `@${n}`).join(", ")
|
|
@@ -3842,36 +3876,35 @@ Issues that can't be resolved unilaterally — they need input from other teamma
|
|
|
3842
3876
|
.filter((n) => n !== this.selfName && n !== this.adapterName);
|
|
3843
3877
|
if (teammates.length === 0)
|
|
3844
3878
|
return;
|
|
3845
|
-
// 1.
|
|
3846
|
-
|
|
3847
|
-
oneWeekAgo.setDate(oneWeekAgo.getDate() - 7);
|
|
3848
|
-
const cutoff = oneWeekAgo.toISOString().slice(0, 10); // YYYY-MM-DD
|
|
3849
|
-
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
|
|
3850
3881
|
for (const name of teammates) {
|
|
3851
|
-
|
|
3852
|
-
|
|
3853
|
-
|
|
3854
|
-
const hasStale = entries.some((e) => {
|
|
3855
|
-
if (!e.endsWith(".md"))
|
|
3856
|
-
return false;
|
|
3857
|
-
const stem = e.replace(".md", "");
|
|
3858
|
-
return /^\d{4}-\d{2}-\d{2}$/.test(stem) && stem < cutoff;
|
|
3859
|
-
});
|
|
3860
|
-
if (hasStale)
|
|
3861
|
-
needsCompact.push(name);
|
|
3862
|
-
}
|
|
3863
|
-
catch {
|
|
3864
|
-
/* no memory dir */
|
|
3882
|
+
if (this.chatView) {
|
|
3883
|
+
this.chatView.setProgress(`Maintaining @${name}...`);
|
|
3884
|
+
this.refreshView();
|
|
3865
3885
|
}
|
|
3886
|
+
await this.runCompact(name, true);
|
|
3866
3887
|
}
|
|
3867
|
-
if (
|
|
3868
|
-
this.
|
|
3888
|
+
if (this.chatView) {
|
|
3889
|
+
this.chatView.setProgress(null);
|
|
3869
3890
|
this.refreshView();
|
|
3870
|
-
|
|
3871
|
-
|
|
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 */
|
|
3872
3905
|
}
|
|
3873
3906
|
}
|
|
3874
|
-
//
|
|
3907
|
+
// 3. Sync recall indexes (bundled library call)
|
|
3875
3908
|
try {
|
|
3876
3909
|
await syncRecallIndex(this.teammatesDir);
|
|
3877
3910
|
}
|