@teammates/cli 0.2.2 → 0.2.3
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 +2 -0
- package/dist/adapters/cli-proxy.d.ts +1 -0
- package/dist/adapters/cli-proxy.js +3 -0
- package/dist/adapters/copilot.d.ts +1 -0
- package/dist/adapters/copilot.js +3 -0
- package/dist/cli.js +218 -146
- package/dist/onboard.d.ts +10 -5
- package/dist/onboard.js +125 -45
- package/dist/registry.js +2 -1
- package/package.json +1 -1
package/dist/adapter.d.ts
CHANGED
|
@@ -24,6 +24,8 @@ export interface AgentAdapter {
|
|
|
24
24
|
* Falls back to startSession if not implemented.
|
|
25
25
|
*/
|
|
26
26
|
resumeSession?(teammate: TeammateConfig, sessionId: string): Promise<string>;
|
|
27
|
+
/** Get the session file path for a teammate (if session is active). */
|
|
28
|
+
getSessionFile?(teammateName: string): string | undefined;
|
|
27
29
|
/** Clean up a session. */
|
|
28
30
|
destroySession?(sessionId: string): Promise<void>;
|
|
29
31
|
/**
|
|
@@ -70,6 +70,7 @@ export declare class CliProxyAdapter implements AgentAdapter {
|
|
|
70
70
|
startSession(teammate: TeammateConfig): Promise<string>;
|
|
71
71
|
executeTask(_sessionId: string, teammate: TeammateConfig, prompt: string): Promise<TaskResult>;
|
|
72
72
|
routeTask(task: string, roster: RosterEntry[]): Promise<string | null>;
|
|
73
|
+
getSessionFile(teammateName: string): string | undefined;
|
|
73
74
|
destroySession(_sessionId: string): Promise<void>;
|
|
74
75
|
/**
|
|
75
76
|
* Spawn the agent, stream its output live, and capture it.
|
|
@@ -269,6 +269,9 @@ export class CliProxyAdapter {
|
|
|
269
269
|
await unlink(promptFile).catch(() => { });
|
|
270
270
|
}
|
|
271
271
|
}
|
|
272
|
+
getSessionFile(teammateName) {
|
|
273
|
+
return this.sessionFiles.get(teammateName);
|
|
274
|
+
}
|
|
272
275
|
async destroySession(_sessionId) {
|
|
273
276
|
// Clean up any leaked temp prompt files
|
|
274
277
|
for (const file of this.pendingTempFiles) {
|
|
@@ -42,6 +42,7 @@ export declare class CopilotAdapter implements AgentAdapter {
|
|
|
42
42
|
startSession(teammate: TeammateConfig): Promise<string>;
|
|
43
43
|
executeTask(_sessionId: string, teammate: TeammateConfig, prompt: string): Promise<TaskResult>;
|
|
44
44
|
routeTask(task: string, roster: RosterEntry[]): Promise<string | null>;
|
|
45
|
+
getSessionFile(teammateName: string): string | undefined;
|
|
45
46
|
destroySession(_sessionId: string): Promise<void>;
|
|
46
47
|
/**
|
|
47
48
|
* Ensure the CopilotClient is started.
|
package/dist/adapters/copilot.js
CHANGED
|
@@ -173,6 +173,9 @@ export class CopilotAdapter {
|
|
|
173
173
|
await session.disconnect().catch(() => { });
|
|
174
174
|
}
|
|
175
175
|
}
|
|
176
|
+
getSessionFile(teammateName) {
|
|
177
|
+
return this.sessionFiles.get(teammateName);
|
|
178
|
+
}
|
|
176
179
|
async destroySession(_sessionId) {
|
|
177
180
|
// Disconnect all sessions
|
|
178
181
|
for (const [, session] of this.sessions) {
|
package/dist/cli.js
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* teammates --dir <path> Override .teammates/ location
|
|
9
9
|
*/
|
|
10
10
|
import { spawn as cpSpawn, exec as execCb, execSync, } from "node:child_process";
|
|
11
|
-
import { readFileSync, writeFileSync } from "node:fs";
|
|
11
|
+
import { appendFileSync, readFileSync, writeFileSync } from "node:fs";
|
|
12
12
|
import { mkdir, readdir, rm, stat, unlink } from "node:fs/promises";
|
|
13
13
|
import { tmpdir } from "node:os";
|
|
14
14
|
import { join, resolve } from "node:path";
|
|
@@ -24,7 +24,7 @@ import { findAtMention, isImagePath, relativeTime, wrapLine, } from "./cli-utils
|
|
|
24
24
|
import { compactEpisodic } from "./compact.js";
|
|
25
25
|
import { PromptInput } from "./console/prompt-input.js";
|
|
26
26
|
import { buildTitle } from "./console/startup.js";
|
|
27
|
-
import {
|
|
27
|
+
import { buildImportAdaptationPrompt, copyTemplateFiles, getOnboardingPrompt, importTeammates, } from "./onboard.js";
|
|
28
28
|
import { Orchestrator } from "./orchestrator.js";
|
|
29
29
|
import { colorToHex, theme } from "./theme.js";
|
|
30
30
|
// ─── Version ─────────────────────────────────────────────────────────
|
|
@@ -465,6 +465,28 @@ class TeammatesREPL {
|
|
|
465
465
|
lastCleanedOutput = ""; // last teammate output for clipboard copy
|
|
466
466
|
dispatching = false;
|
|
467
467
|
autoApproveHandoffs = false;
|
|
468
|
+
/** Read .teammates/settings.json (returns { version, services, ... } or defaults). */
|
|
469
|
+
readSettings() {
|
|
470
|
+
try {
|
|
471
|
+
const raw = JSON.parse(readFileSync(join(this.teammatesDir, "settings.json"), "utf-8"));
|
|
472
|
+
return {
|
|
473
|
+
version: raw.version ?? 1,
|
|
474
|
+
services: Array.isArray(raw.services) ? raw.services : [],
|
|
475
|
+
...raw,
|
|
476
|
+
};
|
|
477
|
+
}
|
|
478
|
+
catch {
|
|
479
|
+
return { version: 1, services: [] };
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
/** Write .teammates/settings.json. */
|
|
483
|
+
writeSettings(settings) {
|
|
484
|
+
writeFileSync(join(this.teammatesDir, "settings.json"), `${JSON.stringify(settings, null, 2)}\n`);
|
|
485
|
+
}
|
|
486
|
+
/** Check whether a specific service is installed. */
|
|
487
|
+
isServiceInstalled(name) {
|
|
488
|
+
return this.readSettings().services.some((s) => s.name === name);
|
|
489
|
+
}
|
|
468
490
|
/** Pending handoffs awaiting user approval. */
|
|
469
491
|
pendingHandoffs = [];
|
|
470
492
|
/** Pending retro proposals awaiting user approval. */
|
|
@@ -1420,14 +1442,17 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
|
|
|
1420
1442
|
chalk.cyan(teammates.join(", ")));
|
|
1421
1443
|
console.log(chalk.gray(` (${files.length} files copied)`));
|
|
1422
1444
|
console.log();
|
|
1445
|
+
// Copy framework files so the agent has TEMPLATE.md etc. available
|
|
1446
|
+
await copyTemplateFiles(teammatesDir);
|
|
1423
1447
|
// Ask if user wants the agent to adapt teammates to this codebase
|
|
1424
1448
|
console.log(chalk.white(" Adapt teammates to this codebase?"));
|
|
1425
|
-
console.log(chalk.gray(" The agent will
|
|
1449
|
+
console.log(chalk.gray(" The agent will scan this project, evaluate which teammates are needed,"));
|
|
1450
|
+
console.log(chalk.gray(" adapt their files, and create any new teammates the project needs."));
|
|
1426
1451
|
console.log(chalk.gray(" You can also do this later with /init."));
|
|
1427
1452
|
console.log();
|
|
1428
1453
|
const adapt = await this.askChoice("Adapt now? (y/n): ", ["y", "n"]);
|
|
1429
1454
|
if (adapt === "y") {
|
|
1430
|
-
await this.runAdaptationAgent(this.adapter, projectDir, teammates);
|
|
1455
|
+
await this.runAdaptationAgent(this.adapter, projectDir, teammates, sourceDir);
|
|
1431
1456
|
}
|
|
1432
1457
|
else {
|
|
1433
1458
|
console.log(chalk.gray(" Skipped adaptation. Run /init to adapt later."));
|
|
@@ -1439,53 +1464,52 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
|
|
|
1439
1464
|
console.log();
|
|
1440
1465
|
}
|
|
1441
1466
|
/**
|
|
1442
|
-
* Run the agent to adapt imported teammates
|
|
1443
|
-
*
|
|
1444
|
-
*
|
|
1467
|
+
* Run the agent to adapt imported teammates to the current codebase.
|
|
1468
|
+
* Uses a single comprehensive session that scans the project, evaluates
|
|
1469
|
+
* which teammates to keep/drop/create, adapts kept teammates (with
|
|
1470
|
+
* Previous Projects sections), and creates any new teammates needed.
|
|
1445
1471
|
*/
|
|
1446
|
-
async runAdaptationAgent(adapter, projectDir, teammateNames) {
|
|
1472
|
+
async runAdaptationAgent(adapter, projectDir, teammateNames, sourceProjectPath) {
|
|
1447
1473
|
const teammatesDir = join(projectDir, ".teammates");
|
|
1448
1474
|
console.log();
|
|
1449
|
-
console.log(chalk.blue("
|
|
1450
|
-
chalk.gray(` ${this.adapterName} will adapt
|
|
1475
|
+
console.log(chalk.blue(" Starting adaptation...") +
|
|
1476
|
+
chalk.gray(` ${this.adapterName} will scan this project and adapt the team`));
|
|
1451
1477
|
console.log();
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
console.log(chalk.green(` ✔ @${name} adaptation complete!`));
|
|
1476
|
-
}
|
|
1477
|
-
else {
|
|
1478
|
-
console.log(chalk.yellow(` ⚠ @${name} adaptation finished with issues: ${result.summary}`));
|
|
1479
|
-
}
|
|
1480
|
-
}
|
|
1481
|
-
catch (err) {
|
|
1482
|
-
spinner.fail(chalk.red(`@${name} adaptation failed: ${err.message}`));
|
|
1478
|
+
const prompt = await buildImportAdaptationPrompt(teammatesDir, teammateNames, sourceProjectPath);
|
|
1479
|
+
const tempConfig = {
|
|
1480
|
+
name: this.adapterName,
|
|
1481
|
+
role: "Adaptation agent",
|
|
1482
|
+
soul: "",
|
|
1483
|
+
wisdom: "",
|
|
1484
|
+
dailyLogs: [],
|
|
1485
|
+
weeklyLogs: [],
|
|
1486
|
+
ownership: { primary: [], secondary: [] },
|
|
1487
|
+
routingKeywords: [],
|
|
1488
|
+
};
|
|
1489
|
+
const sessionId = await adapter.startSession(tempConfig);
|
|
1490
|
+
const spinner = ora({
|
|
1491
|
+
text: chalk.blue(this.adapterName) +
|
|
1492
|
+
chalk.gray(" is scanning the project and adapting teammates..."),
|
|
1493
|
+
spinner: "dots",
|
|
1494
|
+
}).start();
|
|
1495
|
+
try {
|
|
1496
|
+
const result = await adapter.executeTask(sessionId, tempConfig, prompt);
|
|
1497
|
+
spinner.stop();
|
|
1498
|
+
this.printAgentOutput(result.rawOutput);
|
|
1499
|
+
if (result.success) {
|
|
1500
|
+
console.log(chalk.green(" ✔ Team adaptation complete!"));
|
|
1483
1501
|
}
|
|
1484
|
-
|
|
1485
|
-
|
|
1502
|
+
else {
|
|
1503
|
+
console.log(chalk.yellow(` ⚠ Adaptation finished with issues: ${result.summary}`));
|
|
1486
1504
|
}
|
|
1487
|
-
console.log();
|
|
1488
1505
|
}
|
|
1506
|
+
catch (err) {
|
|
1507
|
+
spinner.fail(chalk.red(`Adaptation failed: ${err.message}`));
|
|
1508
|
+
}
|
|
1509
|
+
if (adapter.destroySession) {
|
|
1510
|
+
await adapter.destroySession(sessionId);
|
|
1511
|
+
}
|
|
1512
|
+
console.log();
|
|
1489
1513
|
}
|
|
1490
1514
|
/**
|
|
1491
1515
|
* Simple blocking prompt — reads one line from stdin and validates.
|
|
@@ -1963,21 +1987,15 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
|
|
|
1963
1987
|
return { name: t.name, role: t.role, ownership: t.ownership };
|
|
1964
1988
|
});
|
|
1965
1989
|
}
|
|
1966
|
-
// Detect installed services from
|
|
1990
|
+
// Detect installed services from settings.json and tell the adapter
|
|
1967
1991
|
if ("services" in this.adapter) {
|
|
1968
1992
|
const services = [];
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
usage: 'teammates-recall search "your query" --dir .teammates',
|
|
1976
|
-
});
|
|
1977
|
-
}
|
|
1978
|
-
}
|
|
1979
|
-
catch {
|
|
1980
|
-
/* no services.json or invalid */
|
|
1993
|
+
if (this.isServiceInstalled("recall")) {
|
|
1994
|
+
services.push({
|
|
1995
|
+
name: "recall",
|
|
1996
|
+
description: "Local semantic search across teammate memories and daily logs. Use this to find relevant context before starting a task.",
|
|
1997
|
+
usage: 'teammates-recall search "your query" --dir .teammates',
|
|
1998
|
+
});
|
|
1981
1999
|
}
|
|
1982
2000
|
this.adapter.services = services;
|
|
1983
2001
|
}
|
|
@@ -2034,14 +2052,7 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
|
|
|
2034
2052
|
// ── Build animated banner for ChatView ─────────────────────────────
|
|
2035
2053
|
const names = this.orchestrator.listTeammates();
|
|
2036
2054
|
const reg = this.orchestrator.getRegistry();
|
|
2037
|
-
|
|
2038
|
-
try {
|
|
2039
|
-
const svcJson = JSON.parse(readFileSync(join(this.teammatesDir, "services.json"), "utf-8"));
|
|
2040
|
-
hasRecall = !!(svcJson && "recall" in svcJson);
|
|
2041
|
-
}
|
|
2042
|
-
catch {
|
|
2043
|
-
/* no services.json */
|
|
2044
|
-
}
|
|
2055
|
+
const hasRecall = this.isServiceInstalled("recall");
|
|
2045
2056
|
const bannerWidget = new AnimatedBanner({
|
|
2046
2057
|
adapterName: this.adapterName,
|
|
2047
2058
|
teammateCount: names.length,
|
|
@@ -2432,15 +2443,8 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
|
|
|
2432
2443
|
printBanner(teammates) {
|
|
2433
2444
|
const registry = this.orchestrator.getRegistry();
|
|
2434
2445
|
const termWidth = process.stdout.columns || 100;
|
|
2435
|
-
// Detect recall from
|
|
2436
|
-
|
|
2437
|
-
try {
|
|
2438
|
-
const svcJson = JSON.parse(readFileSync(join(this.teammatesDir, "services.json"), "utf-8"));
|
|
2439
|
-
recallInstalled = !!(svcJson && "recall" in svcJson);
|
|
2440
|
-
}
|
|
2441
|
-
catch {
|
|
2442
|
-
/* no services.json or invalid */
|
|
2443
|
-
}
|
|
2446
|
+
// Detect recall from settings.json
|
|
2447
|
+
const recallInstalled = this.isServiceInstalled("recall");
|
|
2444
2448
|
this.feedLine();
|
|
2445
2449
|
this.feedLine(concat(tp.bold(" Teammates"), tp.muted(` v${PKG_VERSION}`)));
|
|
2446
2450
|
this.feedLine(concat(tp.text(` ${this.adapterName}`), tp.muted(` · ${teammates.length} teammate${teammates.length === 1 ? "" : "s"}`)));
|
|
@@ -2782,45 +2786,61 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
|
|
|
2782
2786
|
}
|
|
2783
2787
|
async cmdDebug(argsStr) {
|
|
2784
2788
|
const arg = argsStr.trim().replace(/^@/, "");
|
|
2785
|
-
// Resolve
|
|
2786
|
-
let
|
|
2789
|
+
// Resolve which teammates to show debug info for
|
|
2790
|
+
let targetNames;
|
|
2787
2791
|
if (arg === "everyone") {
|
|
2788
|
-
|
|
2789
|
-
for (const [name
|
|
2790
|
-
if (name !== this.adapterName
|
|
2791
|
-
|
|
2792
|
-
}
|
|
2792
|
+
targetNames = [];
|
|
2793
|
+
for (const [name] of this.lastResults) {
|
|
2794
|
+
if (name !== this.adapterName)
|
|
2795
|
+
targetNames.push(name);
|
|
2793
2796
|
}
|
|
2794
|
-
if (
|
|
2795
|
-
this.feedLine(tp.muted(" No
|
|
2797
|
+
if (targetNames.length === 0) {
|
|
2798
|
+
this.feedLine(tp.muted(" No debug info available from any teammate."));
|
|
2796
2799
|
this.refreshView();
|
|
2797
2800
|
return;
|
|
2798
2801
|
}
|
|
2799
2802
|
}
|
|
2803
|
+
else if (arg) {
|
|
2804
|
+
targetNames = [arg];
|
|
2805
|
+
}
|
|
2806
|
+
else if (this.lastResult) {
|
|
2807
|
+
targetNames = [this.lastResult.teammate];
|
|
2808
|
+
}
|
|
2800
2809
|
else {
|
|
2801
|
-
|
|
2802
|
-
|
|
2803
|
-
|
|
2804
|
-
(arg ? "" : " Try: /debug <teammate>")));
|
|
2805
|
-
this.refreshView();
|
|
2806
|
-
return;
|
|
2807
|
-
}
|
|
2808
|
-
targets = [{ name: result.teammate, result }];
|
|
2810
|
+
this.feedLine(tp.muted(" No debug info available. Try: /debug [teammate]"));
|
|
2811
|
+
this.refreshView();
|
|
2812
|
+
return;
|
|
2809
2813
|
}
|
|
2810
|
-
|
|
2811
|
-
|
|
2812
|
-
this.feedLine(tp.muted(` ── raw output from ${name} ──`));
|
|
2814
|
+
let debugText = "";
|
|
2815
|
+
for (const name of targetNames) {
|
|
2813
2816
|
this.feedLine();
|
|
2814
|
-
this.
|
|
2817
|
+
this.feedLine(tp.muted(` ── debug for ${name} ──`));
|
|
2818
|
+
// Read the last debug entry from the session file
|
|
2819
|
+
const sessionEntry = this.readLastDebugEntry(name);
|
|
2820
|
+
if (sessionEntry) {
|
|
2821
|
+
this.feedLine();
|
|
2822
|
+
this.feedMarkdown(sessionEntry);
|
|
2823
|
+
debugText += (debugText ? "\n\n" : "") + sessionEntry;
|
|
2824
|
+
}
|
|
2825
|
+
else {
|
|
2826
|
+
// Fall back to raw output from lastResults
|
|
2827
|
+
const result = this.lastResults.get(name);
|
|
2828
|
+
if (result?.rawOutput) {
|
|
2829
|
+
this.feedLine();
|
|
2830
|
+
this.feedMarkdown(result.rawOutput);
|
|
2831
|
+
debugText += (debugText ? "\n\n" : "") + result.rawOutput;
|
|
2832
|
+
}
|
|
2833
|
+
else {
|
|
2834
|
+
this.feedLine(tp.muted(" No debug info available."));
|
|
2835
|
+
}
|
|
2836
|
+
}
|
|
2815
2837
|
this.feedLine();
|
|
2816
|
-
this.feedLine(tp.muted(" ── end
|
|
2838
|
+
this.feedLine(tp.muted(" ── end debug ──"));
|
|
2817
2839
|
}
|
|
2818
2840
|
// [copy] action for the debug output
|
|
2819
|
-
if (this.chatView) {
|
|
2841
|
+
if (this.chatView && debugText) {
|
|
2820
2842
|
const t = theme();
|
|
2821
|
-
this.lastCleanedOutput =
|
|
2822
|
-
.map((t) => t.result.rawOutput)
|
|
2823
|
-
.join("\n\n");
|
|
2843
|
+
this.lastCleanedOutput = debugText;
|
|
2824
2844
|
this.chatView.appendActionList([
|
|
2825
2845
|
{
|
|
2826
2846
|
id: "copy",
|
|
@@ -2838,6 +2858,28 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
|
|
|
2838
2858
|
this.feedLine();
|
|
2839
2859
|
this.refreshView();
|
|
2840
2860
|
}
|
|
2861
|
+
/**
|
|
2862
|
+
* Read the last debug entry from a teammate's session file.
|
|
2863
|
+
* Debug entries are delimited by "## Debug — " headings.
|
|
2864
|
+
*/
|
|
2865
|
+
readLastDebugEntry(teammate) {
|
|
2866
|
+
try {
|
|
2867
|
+
const sessionFile = this.adapter.getSessionFile?.(teammate);
|
|
2868
|
+
if (!sessionFile)
|
|
2869
|
+
return null;
|
|
2870
|
+
const content = readFileSync(sessionFile, "utf-8");
|
|
2871
|
+
// Split on debug entry headings and return the last one
|
|
2872
|
+
const entries = content.split(/(?=^## Debug — )/m);
|
|
2873
|
+
const last = entries[entries.length - 1];
|
|
2874
|
+
if (last && last.startsWith("## Debug — ")) {
|
|
2875
|
+
return last.trim();
|
|
2876
|
+
}
|
|
2877
|
+
return null;
|
|
2878
|
+
}
|
|
2879
|
+
catch {
|
|
2880
|
+
return null;
|
|
2881
|
+
}
|
|
2882
|
+
}
|
|
2841
2883
|
async cmdCancel(argsStr) {
|
|
2842
2884
|
const n = parseInt(argsStr.trim(), 10);
|
|
2843
2885
|
if (Number.isNaN(n) || n < 1 || n > this.taskQueue.length) {
|
|
@@ -2862,6 +2904,7 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
|
|
|
2862
2904
|
break;
|
|
2863
2905
|
const entry = this.taskQueue.splice(idx, 1)[0];
|
|
2864
2906
|
this.agentActive.set(agent, entry);
|
|
2907
|
+
const startTime = Date.now();
|
|
2865
2908
|
try {
|
|
2866
2909
|
if (entry.type === "compact") {
|
|
2867
2910
|
await this.runCompact(entry.teammate);
|
|
@@ -2874,6 +2917,8 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
|
|
|
2874
2917
|
task: entry.task,
|
|
2875
2918
|
extraContext: extraContext || undefined,
|
|
2876
2919
|
});
|
|
2920
|
+
// Write debug entry to session file
|
|
2921
|
+
this.writeDebugEntry(entry.teammate, entry.task, result, startTime);
|
|
2877
2922
|
// btw results are not stored in conversation history
|
|
2878
2923
|
if (entry.type !== "btw") {
|
|
2879
2924
|
this.storeResult(result);
|
|
@@ -2884,6 +2929,8 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
|
|
|
2884
2929
|
}
|
|
2885
2930
|
}
|
|
2886
2931
|
catch (err) {
|
|
2932
|
+
// Write error debug entry to session file
|
|
2933
|
+
this.writeDebugEntry(entry.teammate, entry.task, null, startTime, err);
|
|
2887
2934
|
// Handle spawn failures, network errors, etc. gracefully
|
|
2888
2935
|
this.activeTasks.delete(agent);
|
|
2889
2936
|
if (this.activeTasks.size === 0)
|
|
@@ -2895,6 +2942,52 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
|
|
|
2895
2942
|
this.agentActive.delete(agent);
|
|
2896
2943
|
}
|
|
2897
2944
|
}
|
|
2945
|
+
/**
|
|
2946
|
+
* Append a debug entry to the teammate's session file.
|
|
2947
|
+
* Captures task prompt, result summary, raw output, timing, and errors.
|
|
2948
|
+
*/
|
|
2949
|
+
writeDebugEntry(teammate, task, result, startTime, error) {
|
|
2950
|
+
try {
|
|
2951
|
+
const sessionFile = this.adapter.getSessionFile?.(teammate);
|
|
2952
|
+
if (!sessionFile)
|
|
2953
|
+
return;
|
|
2954
|
+
const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
|
|
2955
|
+
const timestamp = new Date().toISOString();
|
|
2956
|
+
const lines = [
|
|
2957
|
+
"",
|
|
2958
|
+
`## Debug — ${timestamp}`,
|
|
2959
|
+
"",
|
|
2960
|
+
`**Duration:** ${elapsed}s`,
|
|
2961
|
+
`**Task:** ${task.length > 200 ? `${task.slice(0, 200)}…` : task}`,
|
|
2962
|
+
"",
|
|
2963
|
+
];
|
|
2964
|
+
if (error) {
|
|
2965
|
+
lines.push(`**Status:** ERROR`);
|
|
2966
|
+
lines.push(`**Error:** ${error?.message ?? String(error)}`);
|
|
2967
|
+
}
|
|
2968
|
+
else if (result) {
|
|
2969
|
+
lines.push(`**Status:** ${result.success ? "OK" : "FAILED"}`);
|
|
2970
|
+
lines.push(`**Summary:** ${result.summary || "(no summary)"}`);
|
|
2971
|
+
if (result.changedFiles.length > 0) {
|
|
2972
|
+
lines.push(`**Changed files:** ${result.changedFiles.join(", ")}`);
|
|
2973
|
+
}
|
|
2974
|
+
if (result.handoffs.length > 0) {
|
|
2975
|
+
lines.push(`**Handoffs:** ${result.handoffs.map((h) => `@${h.to}`).join(", ")}`);
|
|
2976
|
+
}
|
|
2977
|
+
lines.push("");
|
|
2978
|
+
lines.push("<details><summary>Raw output</summary>");
|
|
2979
|
+
lines.push("");
|
|
2980
|
+
lines.push(result.rawOutput ?? "(empty)");
|
|
2981
|
+
lines.push("");
|
|
2982
|
+
lines.push("</details>");
|
|
2983
|
+
}
|
|
2984
|
+
lines.push("");
|
|
2985
|
+
appendFileSync(sessionFile, lines.join("\n"), "utf-8");
|
|
2986
|
+
}
|
|
2987
|
+
catch {
|
|
2988
|
+
// Don't let debug logging break task execution
|
|
2989
|
+
}
|
|
2990
|
+
}
|
|
2898
2991
|
async cmdInit(argsStr) {
|
|
2899
2992
|
const cwd = process.cwd();
|
|
2900
2993
|
const teammatesDir = join(cwd, ".teammates");
|
|
@@ -2924,16 +3017,16 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
|
|
|
2924
3017
|
return;
|
|
2925
3018
|
}
|
|
2926
3019
|
this.feedLine(tp.success(` Imported ${teammates.length} teammate${teammates.length > 1 ? "s" : ""}: ${teammates.join(", ")} (${files.length} files)`));
|
|
2927
|
-
//
|
|
2928
|
-
|
|
2929
|
-
|
|
2930
|
-
|
|
2931
|
-
|
|
2932
|
-
|
|
2933
|
-
|
|
2934
|
-
|
|
2935
|
-
|
|
2936
|
-
}
|
|
3020
|
+
// Copy framework files so the agent has TEMPLATE.md etc. available
|
|
3021
|
+
await copyTemplateFiles(teammatesDir);
|
|
3022
|
+
// Queue a single adaptation task that handles all teammates
|
|
3023
|
+
this.feedLine(tp.muted(` Queuing ${this.adapterName} to scan this project and adapt the team...`));
|
|
3024
|
+
const prompt = await buildImportAdaptationPrompt(teammatesDir, teammates, sourceDir);
|
|
3025
|
+
this.taskQueue.push({
|
|
3026
|
+
type: "agent",
|
|
3027
|
+
teammate: this.adapterName,
|
|
3028
|
+
task: prompt,
|
|
3029
|
+
});
|
|
2937
3030
|
this.kickDrain();
|
|
2938
3031
|
}
|
|
2939
3032
|
catch (err) {
|
|
@@ -3022,19 +3115,12 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
|
|
|
3022
3115
|
return;
|
|
3023
3116
|
}
|
|
3024
3117
|
this.feedLine(tp.success(` ✔ ${serviceName} installed successfully`));
|
|
3025
|
-
// Register in
|
|
3026
|
-
const
|
|
3027
|
-
|
|
3028
|
-
|
|
3029
|
-
|
|
3030
|
-
|
|
3031
|
-
catch {
|
|
3032
|
-
/* new file */
|
|
3033
|
-
}
|
|
3034
|
-
if (!(serviceName in svcJson)) {
|
|
3035
|
-
svcJson[serviceName] = {};
|
|
3036
|
-
writeFileSync(svcPath, `${JSON.stringify(svcJson, null, 2)}\n`);
|
|
3037
|
-
this.feedLine(tp.muted(` Registered in services.json`));
|
|
3118
|
+
// Register in settings.json
|
|
3119
|
+
const settings = this.readSettings();
|
|
3120
|
+
if (!settings.services.some((s) => s.name === serviceName)) {
|
|
3121
|
+
settings.services.push({ name: serviceName });
|
|
3122
|
+
this.writeSettings(settings);
|
|
3123
|
+
this.feedLine(tp.muted(` Registered in settings.json`));
|
|
3038
3124
|
}
|
|
3039
3125
|
// Build initial index if this service supports it
|
|
3040
3126
|
if (service.indexCmd) {
|
|
@@ -3133,15 +3219,9 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
|
|
|
3133
3219
|
.catch(() => { });
|
|
3134
3220
|
}
|
|
3135
3221
|
startRecallWatch() {
|
|
3136
|
-
// Only start if recall is installed (check
|
|
3137
|
-
|
|
3138
|
-
|
|
3139
|
-
if (!svcJson || !("recall" in svcJson))
|
|
3140
|
-
return;
|
|
3141
|
-
}
|
|
3142
|
-
catch {
|
|
3143
|
-
return; // No services.json — recall not installed
|
|
3144
|
-
}
|
|
3222
|
+
// Only start if recall is installed (check settings.json)
|
|
3223
|
+
if (!this.isServiceInstalled("recall"))
|
|
3224
|
+
return;
|
|
3145
3225
|
try {
|
|
3146
3226
|
this.recallWatchProcess = cpSpawn("teammates-recall", ["watch", "--dir", this.teammatesDir, "--json"], {
|
|
3147
3227
|
stdio: ["ignore", "ignore", "ignore"],
|
|
@@ -3245,9 +3325,8 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
|
|
|
3245
3325
|
if (this.chatView)
|
|
3246
3326
|
this.chatView.setProgress(null);
|
|
3247
3327
|
// Trigger recall sync if installed
|
|
3248
|
-
|
|
3249
|
-
|
|
3250
|
-
if (svcJson && "recall" in svcJson) {
|
|
3328
|
+
if (this.isServiceInstalled("recall")) {
|
|
3329
|
+
try {
|
|
3251
3330
|
if (this.chatView) {
|
|
3252
3331
|
this.chatView.setProgress(`Syncing ${name} index...`);
|
|
3253
3332
|
this.refreshView();
|
|
@@ -3267,9 +3346,9 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
|
|
|
3267
3346
|
this.feedLine(tp.success(` ✔ ${name}: index synced`));
|
|
3268
3347
|
}
|
|
3269
3348
|
}
|
|
3270
|
-
|
|
3271
|
-
|
|
3272
|
-
|
|
3349
|
+
catch {
|
|
3350
|
+
/* sync failed — non-fatal */
|
|
3351
|
+
}
|
|
3273
3352
|
}
|
|
3274
3353
|
}
|
|
3275
3354
|
catch (err) {
|
|
@@ -3394,14 +3473,7 @@ Issues that can't be resolved unilaterally — they need input from other teamma
|
|
|
3394
3473
|
if (teammates.length === 0)
|
|
3395
3474
|
return;
|
|
3396
3475
|
// Check if recall is installed
|
|
3397
|
-
|
|
3398
|
-
try {
|
|
3399
|
-
const svcJson = JSON.parse(readFileSync(join(this.teammatesDir, "services.json"), "utf-8"));
|
|
3400
|
-
recallInstalled = !!(svcJson && "recall" in svcJson);
|
|
3401
|
-
}
|
|
3402
|
-
catch {
|
|
3403
|
-
/* no services.json */
|
|
3404
|
-
}
|
|
3476
|
+
const recallInstalled = this.isServiceInstalled("recall");
|
|
3405
3477
|
// 1. Check each teammate for stale daily logs (older than 7 days)
|
|
3406
3478
|
const oneWeekAgo = new Date();
|
|
3407
3479
|
oneWeekAgo.setDate(oneWeekAgo.getDate() - 7);
|
package/dist/onboard.d.ts
CHANGED
|
@@ -28,14 +28,19 @@ export declare function importTeammates(sourceDir: string, targetDir: string): P
|
|
|
28
28
|
files: string[];
|
|
29
29
|
}>;
|
|
30
30
|
/**
|
|
31
|
-
* Build
|
|
32
|
-
*
|
|
33
|
-
*
|
|
31
|
+
* Build a comprehensive import-adaptation prompt that runs as a single agent session.
|
|
32
|
+
* The agent will:
|
|
33
|
+
* 1. Scan the current project
|
|
34
|
+
* 2. Evaluate which imported teammates are needed
|
|
35
|
+
* 3. Adapt kept teammates (add Previous Projects section + rewrite for new project)
|
|
36
|
+
* 4. Create any new teammates the project needs
|
|
37
|
+
* 5. Remove teammates that don't apply
|
|
34
38
|
*
|
|
35
39
|
* @param teammatesDir - The .teammates/ directory in the target project
|
|
36
|
-
* @param
|
|
40
|
+
* @param teammateNames - Names of all imported teammates
|
|
41
|
+
* @param sourceProjectPath - Path to the source project (for Previous Projects section)
|
|
37
42
|
*/
|
|
38
|
-
export declare function
|
|
43
|
+
export declare function buildImportAdaptationPrompt(teammatesDir: string, teammateNames: string[], sourceProjectPath: string): Promise<string>;
|
|
39
44
|
/**
|
|
40
45
|
* Load ONBOARDING.md from the project dir, package root, or built-in fallback.
|
|
41
46
|
*/
|
package/dist/onboard.js
CHANGED
|
@@ -60,7 +60,7 @@ export async function copyTemplateFiles(teammatesDir) {
|
|
|
60
60
|
await stat(gitignoreDest);
|
|
61
61
|
}
|
|
62
62
|
catch {
|
|
63
|
-
const gitignoreContent = "USER.md\n
|
|
63
|
+
const gitignoreContent = "USER.md\n.*/\n";
|
|
64
64
|
const { writeFile } = await import("node:fs/promises");
|
|
65
65
|
await writeFile(gitignoreDest, gitignoreContent, "utf-8");
|
|
66
66
|
copied.push(".gitignore");
|
|
@@ -92,7 +92,7 @@ export async function copyTemplateFiles(teammatesDir) {
|
|
|
92
92
|
* Directories starting with "_" are shared non-teammate folders.
|
|
93
93
|
* Files (non-directories) and special names are also excluded.
|
|
94
94
|
*/
|
|
95
|
-
const NON_TEAMMATE_NAMES = new Set(["example", "
|
|
95
|
+
const NON_TEAMMATE_NAMES = new Set(["example", "settings.json"]);
|
|
96
96
|
function isNonTeammateEntry(name) {
|
|
97
97
|
return (name.startsWith(".") || name.startsWith("_") || NON_TEAMMATE_NAMES.has(name));
|
|
98
98
|
}
|
|
@@ -182,66 +182,146 @@ export async function importTeammates(sourceDir, targetDir) {
|
|
|
182
182
|
await stat(gitignoreDest);
|
|
183
183
|
}
|
|
184
184
|
catch {
|
|
185
|
-
await writeFile(gitignoreDest, "USER.md\n
|
|
185
|
+
await writeFile(gitignoreDest, "USER.md\n.*/\n", "utf-8");
|
|
186
186
|
files.push(".gitignore");
|
|
187
187
|
}
|
|
188
188
|
return { teammates, files };
|
|
189
189
|
}
|
|
190
190
|
/**
|
|
191
|
-
* Build
|
|
192
|
-
*
|
|
193
|
-
*
|
|
191
|
+
* Build a comprehensive import-adaptation prompt that runs as a single agent session.
|
|
192
|
+
* The agent will:
|
|
193
|
+
* 1. Scan the current project
|
|
194
|
+
* 2. Evaluate which imported teammates are needed
|
|
195
|
+
* 3. Adapt kept teammates (add Previous Projects section + rewrite for new project)
|
|
196
|
+
* 4. Create any new teammates the project needs
|
|
197
|
+
* 5. Remove teammates that don't apply
|
|
194
198
|
*
|
|
195
199
|
* @param teammatesDir - The .teammates/ directory in the target project
|
|
196
|
-
* @param
|
|
200
|
+
* @param teammateNames - Names of all imported teammates
|
|
201
|
+
* @param sourceProjectPath - Path to the source project (for Previous Projects section)
|
|
197
202
|
*/
|
|
198
|
-
export async function
|
|
199
|
-
const
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
203
|
+
export async function buildImportAdaptationPrompt(teammatesDir, teammateNames, sourceProjectPath) {
|
|
204
|
+
const teammateSections = [];
|
|
205
|
+
for (const name of teammateNames) {
|
|
206
|
+
const dir = join(teammatesDir, name);
|
|
207
|
+
let soulContent = "";
|
|
208
|
+
let wisdomContent = "";
|
|
209
|
+
try {
|
|
210
|
+
soulContent = await readFile(join(dir, "SOUL.md"), "utf-8");
|
|
211
|
+
}
|
|
212
|
+
catch {
|
|
213
|
+
/* missing */
|
|
214
|
+
}
|
|
215
|
+
try {
|
|
216
|
+
wisdomContent = await readFile(join(dir, "WISDOM.md"), "utf-8");
|
|
217
|
+
}
|
|
218
|
+
catch {
|
|
219
|
+
/* missing */
|
|
220
|
+
}
|
|
221
|
+
const soulBlock = soulContent
|
|
222
|
+
? `**SOUL.md:**\n\`\`\`markdown\n${soulContent}\n\`\`\``
|
|
223
|
+
: "*No SOUL.md found*";
|
|
224
|
+
const wisdomBlock = wisdomContent
|
|
225
|
+
? `\n**WISDOM.md:**\n\`\`\`markdown\n${wisdomContent}\n\`\`\``
|
|
226
|
+
: "";
|
|
227
|
+
teammateSections.push(`### @${name}\n${soulBlock}${wisdomBlock}`);
|
|
214
228
|
}
|
|
215
|
-
const
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
229
|
+
const projectDir = dirname(teammatesDir);
|
|
230
|
+
return `You are adapting an imported team to a new project.
|
|
231
|
+
|
|
232
|
+
**Source project:** \`${sourceProjectPath}\`
|
|
233
|
+
**Target project:** \`${projectDir}\`
|
|
234
|
+
**Target .teammates/ directory:** \`${teammatesDir}\`
|
|
235
|
+
**Imported teammates:** ${teammateNames.map((n) => `@${n}`).join(", ")}
|
|
236
|
+
|
|
237
|
+
## Imported Teammates (from source project)
|
|
238
|
+
|
|
239
|
+
${teammateSections.join("\n\n---\n\n")}
|
|
222
240
|
|
|
223
|
-
|
|
241
|
+
## Instructions
|
|
224
242
|
|
|
225
|
-
|
|
226
|
-
${soulSection}${wisdomSection}
|
|
243
|
+
Work through these phases in order. **Pause after Phase 1 and Phase 2** to present your analysis and get user approval before making changes.
|
|
227
244
|
|
|
228
|
-
|
|
245
|
+
### Phase 1: Scan This Project
|
|
229
246
|
|
|
230
|
-
|
|
247
|
+
Analyze the current project to understand its structure:
|
|
248
|
+
- Read the project root: package manifest, README, config files
|
|
249
|
+
- Identify major subsystems, languages, frameworks, file patterns
|
|
250
|
+
- Understand the dependency flow and architecture
|
|
231
251
|
|
|
232
|
-
|
|
233
|
-
- **Preserve**: Identity, Core Principles, Ethics, personality, tone
|
|
234
|
-
- **Update**: Ownership patterns (primary/secondary file globs), Boundaries (reference correct teammate names), Capabilities (commands, file patterns, technologies), Routing keywords, Quality Bar
|
|
235
|
-
- **Adapt**: Any codebase-specific references (paths, package names, tools)
|
|
252
|
+
**Present your analysis to the user and wait for confirmation.**
|
|
236
253
|
|
|
237
|
-
|
|
238
|
-
- **Preserve**: Wisdom entries that are universal (principles, patterns, lessons)
|
|
239
|
-
- **Remove or update**: Entries referencing old project paths, file names, or architecture
|
|
240
|
-
- **Add**: A creation entry noting this teammate was imported and adapted
|
|
254
|
+
### Phase 2: Evaluate Imported Teammates
|
|
241
255
|
|
|
242
|
-
|
|
256
|
+
For each imported teammate, decide:
|
|
257
|
+
- **KEEP** — their domain or expertise is relevant to this project (even if specific details need updating)
|
|
258
|
+
- **DROP** — their domain doesn't exist here and their skills aren't transferable
|
|
243
259
|
|
|
244
|
-
|
|
260
|
+
Also identify **gaps** — major subsystems in this project that none of the imported teammates cover. Propose new teammates for these gaps.
|
|
261
|
+
|
|
262
|
+
**Present your evaluation as a structured plan and wait for user approval:**
|
|
263
|
+
\`\`\`
|
|
264
|
+
KEEP: @name1, @name2
|
|
265
|
+
DROP: @name3
|
|
266
|
+
CREATE: @newname (role description)
|
|
267
|
+
\`\`\`
|
|
268
|
+
|
|
269
|
+
### Phase 3: Adapt Kept Teammates
|
|
270
|
+
|
|
271
|
+
For each KEEP teammate, edit their SOUL.md and WISDOM.md:
|
|
272
|
+
|
|
273
|
+
1. **Add a "Previous Projects" section** to SOUL.md (place it after Ethics, before any appendix). Compress what the teammate did in the source project into a portable summary:
|
|
274
|
+
\`\`\`markdown
|
|
275
|
+
## Previous Projects
|
|
276
|
+
|
|
277
|
+
### <project-name>
|
|
278
|
+
- **Role**: <one-line summary of what they did>
|
|
279
|
+
- **Stack**: <key technologies they worked with>
|
|
280
|
+
- **Domains**: <what they owned — file patterns or subsystem names>
|
|
281
|
+
- **Key learnings**: <1-3 bullets of notable patterns, decisions, or lessons>
|
|
282
|
+
\`\`\`
|
|
283
|
+
|
|
284
|
+
2. **Rewrite the rest of SOUL.md** for this project:
|
|
285
|
+
- **Preserve**: Identity (name, personality), Core Principles, Ethics
|
|
286
|
+
- **Rewrite**: Ownership (primary/secondary file globs for THIS project), Boundaries, Capabilities (commands, file patterns, technologies), Routing keywords, Quality Bar
|
|
287
|
+
- **Update**: Any codebase-specific references (paths, package names, tools, teammate names)
|
|
288
|
+
|
|
289
|
+
3. **Update WISDOM.md**:
|
|
290
|
+
- **Add** a "Previous Projects" section at the top with a compressed note:
|
|
291
|
+
\`\`\`markdown
|
|
292
|
+
## Previous Projects
|
|
293
|
+
|
|
294
|
+
### <project-name>
|
|
295
|
+
- Carried over universal wisdom entries from the source project
|
|
296
|
+
- Project-specific entries removed or adapted
|
|
297
|
+
\`\`\`
|
|
298
|
+
- **Keep** wisdom entries that are universal (general principles, patterns, lessons)
|
|
299
|
+
- **Remove** entries that reference source project paths, architecture, or tools not used here
|
|
300
|
+
- **Adapt** entries with transferable knowledge but old-project-specific details
|
|
301
|
+
|
|
302
|
+
### Phase 4: Handle Dropped Teammates
|
|
303
|
+
|
|
304
|
+
For each DROP teammate, delete their directory under \`${teammatesDir}\`.
|
|
305
|
+
|
|
306
|
+
### Phase 5: Create New Teammates
|
|
307
|
+
|
|
308
|
+
For each new teammate proposed in Phase 2 (after user approval):
|
|
309
|
+
- Create \`${teammatesDir}/<name>/\` with SOUL.md, WISDOM.md, and \`memory/\`
|
|
310
|
+
- Use the template at \`${teammatesDir}/TEMPLATE.md\` for structure
|
|
311
|
+
- WISDOM.md starts with one creation entry
|
|
312
|
+
|
|
313
|
+
### Phase 6: Update Framework Files
|
|
314
|
+
|
|
315
|
+
- Update \`${teammatesDir}/README.md\` with the final roster
|
|
316
|
+
- Update \`${teammatesDir}/CROSS-TEAM.md\` ownership table
|
|
317
|
+
|
|
318
|
+
### Phase 7: Verify
|
|
319
|
+
|
|
320
|
+
- Every kept/new teammate has SOUL.md and WISDOM.md
|
|
321
|
+
- Ownership globs cover the codebase without major gaps
|
|
322
|
+
- Boundaries reference the correct owning teammate
|
|
323
|
+
- Previous Projects sections are present for all imported teammates
|
|
324
|
+
- CROSS-TEAM.md has one row per teammate`;
|
|
245
325
|
}
|
|
246
326
|
/**
|
|
247
327
|
* Load ONBOARDING.md from the project dir, package root, or built-in fallback.
|
package/dist/registry.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* (SOUL.md, WISDOM.md, daily logs, ownership rules).
|
|
6
6
|
*/
|
|
7
7
|
import { readdir, readFile, stat } from "node:fs/promises";
|
|
8
|
-
import { basename, join } from "node:path";
|
|
8
|
+
import { basename, dirname, join } from "node:path";
|
|
9
9
|
export class Registry {
|
|
10
10
|
teammatesDir;
|
|
11
11
|
teammates = new Map();
|
|
@@ -50,6 +50,7 @@ export class Registry {
|
|
|
50
50
|
weeklyLogs,
|
|
51
51
|
ownership,
|
|
52
52
|
routingKeywords,
|
|
53
|
+
cwd: dirname(this.teammatesDir),
|
|
53
54
|
};
|
|
54
55
|
this.teammates.set(name, config);
|
|
55
56
|
return config;
|
package/package.json
CHANGED