@teammates/cli 0.3.4 → 0.4.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 +1 -1
- package/dist/adapter.d.ts +14 -1
- package/dist/adapter.js +131 -125
- package/dist/adapter.test.js +75 -0
- package/dist/adapters/cli-proxy.js +12 -11
- package/dist/adapters/copilot.js +11 -11
- package/dist/adapters/echo.test.js +1 -0
- package/dist/banner.d.ts +6 -1
- package/dist/banner.js +18 -3
- package/dist/cli-args.js +0 -1
- package/dist/cli.js +671 -270
- package/dist/console/startup.d.ts +2 -1
- package/dist/console/startup.js +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/orchestrator.d.ts +2 -0
- package/dist/orchestrator.js +15 -12
- package/dist/orchestrator.test.js +2 -1
- package/dist/registry.js +7 -0
- package/dist/registry.test.js +1 -0
- package/dist/types.d.ts +10 -0
- package/package.json +3 -3
package/dist/cli.js
CHANGED
|
@@ -7,13 +7,12 @@
|
|
|
7
7
|
* teammates --adapter codex Use a specific agent adapter
|
|
8
8
|
* teammates --dir <path> Override .teammates/ location
|
|
9
9
|
*/
|
|
10
|
-
import { exec as execCb, execSync,
|
|
10
|
+
import { exec as execCb, execSync, spawnSync } from "node:child_process";
|
|
11
11
|
import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
12
12
|
import { mkdir, readdir, rm, stat, unlink } from "node:fs/promises";
|
|
13
|
-
import { tmpdir } from "node:os";
|
|
14
13
|
import { dirname, join, resolve } from "node:path";
|
|
15
14
|
import { createInterface } from "node:readline";
|
|
16
|
-
import { App, ChatView, concat, esc,
|
|
15
|
+
import { App, ChatView, concat, esc, pen, renderMarkdown, stripAnsi, } from "@teammates/consolonia";
|
|
17
16
|
import chalk from "chalk";
|
|
18
17
|
import ora from "ora";
|
|
19
18
|
import { syncRecallIndex } from "./adapter.js";
|
|
@@ -39,24 +38,78 @@ class TeammatesREPL {
|
|
|
39
38
|
lastResult = null;
|
|
40
39
|
lastResults = new Map();
|
|
41
40
|
conversationHistory = [];
|
|
41
|
+
/** Running summary of older conversation history maintained by the coding agent. */
|
|
42
|
+
conversationSummary = "";
|
|
42
43
|
storeResult(result) {
|
|
43
44
|
this.lastResult = result;
|
|
44
45
|
this.lastResults.set(result.teammate, result);
|
|
45
46
|
this.conversationHistory.push({
|
|
46
47
|
role: result.teammate,
|
|
47
|
-
text: result.
|
|
48
|
+
text: result.summary,
|
|
48
49
|
});
|
|
49
50
|
}
|
|
51
|
+
/** Token budget for recent conversation history (24k tokens ≈ 96k chars). */
|
|
52
|
+
static CONV_HISTORY_CHARS = 24_000 * 4;
|
|
50
53
|
buildConversationContext() {
|
|
51
|
-
if (this.conversationHistory.length === 0)
|
|
54
|
+
if (this.conversationHistory.length === 0 && !this.conversationSummary)
|
|
52
55
|
return "";
|
|
53
|
-
|
|
54
|
-
const
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
56
|
+
const budget = TeammatesREPL.CONV_HISTORY_CHARS;
|
|
57
|
+
const parts = ["## Conversation History\n"];
|
|
58
|
+
// Include running summary of older conversation if present
|
|
59
|
+
if (this.conversationSummary) {
|
|
60
|
+
parts.push(`### Previous Conversation Summary\n\n${this.conversationSummary}\n`);
|
|
61
|
+
}
|
|
62
|
+
// Work backwards from newest — include whole entries up to 24k tokens
|
|
63
|
+
const entries = [];
|
|
64
|
+
let used = 0;
|
|
65
|
+
for (let i = this.conversationHistory.length - 1; i >= 0; i--) {
|
|
66
|
+
const line = `**${this.conversationHistory[i].role}:** ${this.conversationHistory[i].text}\n`;
|
|
67
|
+
if (used + line.length > budget && entries.length > 0)
|
|
68
|
+
break;
|
|
69
|
+
entries.unshift(line);
|
|
70
|
+
used += line.length;
|
|
58
71
|
}
|
|
59
|
-
|
|
72
|
+
if (entries.length > 0)
|
|
73
|
+
parts.push(entries.join("\n"));
|
|
74
|
+
return parts.join("\n");
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Check if conversation history exceeds the 24k token budget.
|
|
78
|
+
* If so, take the older entries that won't fit, combine with existing summary,
|
|
79
|
+
* and queue a summarization task to the coding agent.
|
|
80
|
+
*/
|
|
81
|
+
maybeQueueSummarization() {
|
|
82
|
+
const budget = TeammatesREPL.CONV_HISTORY_CHARS;
|
|
83
|
+
// Calculate how many recent entries fit in the budget (newest first)
|
|
84
|
+
let recentChars = 0;
|
|
85
|
+
let splitIdx = this.conversationHistory.length;
|
|
86
|
+
for (let i = this.conversationHistory.length - 1; i >= 0; i--) {
|
|
87
|
+
const line = `**${this.conversationHistory[i].role}:** ${this.conversationHistory[i].text}\n`;
|
|
88
|
+
if (recentChars + line.length > budget)
|
|
89
|
+
break;
|
|
90
|
+
recentChars += line.length;
|
|
91
|
+
splitIdx = i;
|
|
92
|
+
}
|
|
93
|
+
if (splitIdx === 0)
|
|
94
|
+
return; // everything fits — nothing to summarize
|
|
95
|
+
// Collect entries that are being pushed out
|
|
96
|
+
const toSummarize = this.conversationHistory.slice(0, splitIdx);
|
|
97
|
+
const entriesText = toSummarize
|
|
98
|
+
.map((e) => `**${e.role}:** ${e.text}`)
|
|
99
|
+
.join("\n");
|
|
100
|
+
// Build the summarization prompt
|
|
101
|
+
const prompt = this.conversationSummary
|
|
102
|
+
? `You are maintaining a running summary of an ongoing conversation between a user and their AI teammates. Update the existing summary to incorporate the new conversation entries below.\n\n## Current Summary\n\n${this.conversationSummary}\n\n## New Entries to Incorporate\n\n${entriesText}\n\n## Instructions\n\nReturn ONLY the updated summary — no preamble, no explanation. The summary should:\n- Be a concise bulleted list of key topics discussed, decisions made, and work completed\n- Preserve important context that future messages might reference\n- Drop trivial or redundant details\n- Stay under 2000 characters\n- Do NOT include any output protocol (no TO:, no # Subject, no handoff blocks)`
|
|
103
|
+
: `You are maintaining a running summary of an ongoing conversation between a user and their AI teammates. Summarize the conversation entries below.\n\n## Entries to Summarize\n\n${entriesText}\n\n## Instructions\n\nReturn ONLY the summary — no preamble, no explanation. The summary should:\n- Be a concise bulleted list of key topics discussed, decisions made, and work completed\n- Preserve important context that future messages might reference\n- Drop trivial or redundant details\n- Stay under 2000 characters\n- Do NOT include any output protocol (no TO:, no # Subject, no handoff blocks)`;
|
|
104
|
+
// Remove the summarized entries — they'll be captured in the summary
|
|
105
|
+
this.conversationHistory.splice(0, splitIdx);
|
|
106
|
+
// Queue the summarization task through the user's agent
|
|
107
|
+
this.taskQueue.push({
|
|
108
|
+
type: "summarize",
|
|
109
|
+
teammate: this.selfName,
|
|
110
|
+
task: prompt,
|
|
111
|
+
});
|
|
112
|
+
this.kickDrain();
|
|
60
113
|
}
|
|
61
114
|
adapterName;
|
|
62
115
|
teammatesDir;
|
|
@@ -89,9 +142,16 @@ class TeammatesREPL {
|
|
|
89
142
|
_replyContexts = new Map();
|
|
90
143
|
/** Quoted reply text to expand on next submit. */
|
|
91
144
|
_pendingQuotedReply = null;
|
|
92
|
-
|
|
145
|
+
/** Resolver for inline ask — when set, next submit resolves this instead of normal handling. */
|
|
146
|
+
_pendingAsk = null;
|
|
147
|
+
defaultFooter = null; // cached left footer content
|
|
148
|
+
defaultFooterRight = null; // cached right footer content
|
|
93
149
|
/** Cached service statuses for banner + /configure. */
|
|
94
150
|
serviceStatuses = [];
|
|
151
|
+
/** Reference to the animated banner widget for live updates. */
|
|
152
|
+
banner = null;
|
|
153
|
+
/** The local user's alias (avatar name). Set after USER.md is read or interview completes. */
|
|
154
|
+
userAlias = null;
|
|
95
155
|
// ── Animated status tracker ─────────────────────────────────────
|
|
96
156
|
activeTasks = new Map();
|
|
97
157
|
statusTimer = null;
|
|
@@ -113,6 +173,13 @@ class TeammatesREPL {
|
|
|
113
173
|
constructor(adapterName) {
|
|
114
174
|
this.adapterName = adapterName;
|
|
115
175
|
}
|
|
176
|
+
/**
|
|
177
|
+
* The name used for the local user in the roster.
|
|
178
|
+
* Returns the user's alias if set, otherwise the adapter name.
|
|
179
|
+
*/
|
|
180
|
+
get selfName() {
|
|
181
|
+
return this.userAlias ?? this.adapterName;
|
|
182
|
+
}
|
|
116
183
|
/** Show the prompt with the fenced border. */
|
|
117
184
|
showPrompt() {
|
|
118
185
|
if (this.chatView) {
|
|
@@ -168,27 +235,28 @@ class TeammatesREPL {
|
|
|
168
235
|
const entries = Array.from(this.activeTasks.values());
|
|
169
236
|
const idx = this.statusRotateIndex % entries.length;
|
|
170
237
|
const { teammate, task } = entries[idx];
|
|
238
|
+
const displayName = teammate === this.adapterName ? this.selfName : teammate;
|
|
171
239
|
const spinChar = TeammatesREPL.SPINNER[this.statusFrame % TeammatesREPL.SPINNER.length];
|
|
172
240
|
const taskPreview = task.length > 50 ? `${task.slice(0, 47)}...` : task;
|
|
173
241
|
const queueInfo = this.activeTasks.size > 1 ? ` (${idx + 1}/${this.activeTasks.size})` : "";
|
|
174
242
|
if (this.chatView) {
|
|
175
243
|
// Strip newlines and truncate task text for single-line display
|
|
176
244
|
const cleanTask = task.replace(/[\r\n]+/g, " ").trim();
|
|
177
|
-
const maxLen = Math.max(20, (process.stdout.columns || 80) -
|
|
245
|
+
const maxLen = Math.max(20, (process.stdout.columns || 80) - displayName.length - 10);
|
|
178
246
|
const taskText = cleanTask.length > maxLen
|
|
179
247
|
? `${cleanTask.slice(0, maxLen - 1)}…`
|
|
180
248
|
: cleanTask;
|
|
181
249
|
const queueTag = this.activeTasks.size > 1
|
|
182
250
|
? ` (${idx + 1}/${this.activeTasks.size})`
|
|
183
251
|
: "";
|
|
184
|
-
this.chatView.setProgress(concat(tp.accent(`${spinChar} ${
|
|
252
|
+
this.chatView.setProgress(concat(tp.accent(`${spinChar} ${displayName}… `), tp.muted(taskText + queueTag)));
|
|
185
253
|
this.app.refresh();
|
|
186
254
|
}
|
|
187
255
|
else {
|
|
188
256
|
// Mostly bright blue, periodically flicker to dark blue
|
|
189
257
|
const spinColor = this.statusFrame % 8 === 0 ? chalk.blue : chalk.blueBright;
|
|
190
258
|
const line = ` ${spinColor(spinChar)} ` +
|
|
191
|
-
chalk.bold(
|
|
259
|
+
chalk.bold(displayName) +
|
|
192
260
|
chalk.gray(`… ${taskPreview}`) +
|
|
193
261
|
(queueInfo ? chalk.gray(queueInfo) : "");
|
|
194
262
|
this.input.setStatus(line);
|
|
@@ -246,8 +314,8 @@ class TeammatesREPL {
|
|
|
246
314
|
rendered.push({ type: "text", content: line });
|
|
247
315
|
}
|
|
248
316
|
}
|
|
249
|
-
// Render first line with
|
|
250
|
-
const label =
|
|
317
|
+
// Render first line with alias label
|
|
318
|
+
const label = `${this.selfName}: `;
|
|
251
319
|
const first = rendered.shift();
|
|
252
320
|
if (first) {
|
|
253
321
|
if (first.type === "text") {
|
|
@@ -430,7 +498,7 @@ class TeammatesREPL {
|
|
|
430
498
|
style: { fg: chrome },
|
|
431
499
|
}));
|
|
432
500
|
if (!isValid) {
|
|
433
|
-
this.feedLine(tp.error(` ✖
|
|
501
|
+
this.feedLine(tp.error(` ✖ Unknown teammate: @${h.to}`));
|
|
434
502
|
}
|
|
435
503
|
else if (this.autoApproveHandoffs) {
|
|
436
504
|
this.taskQueue.push({ type: "agent", teammate: h.to, task: h.task });
|
|
@@ -794,13 +862,13 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
|
|
|
794
862
|
if (this.app)
|
|
795
863
|
this.app.refresh();
|
|
796
864
|
}
|
|
797
|
-
queueTask(input) {
|
|
865
|
+
queueTask(input, preMentions) {
|
|
798
866
|
const allNames = this.orchestrator.listTeammates();
|
|
799
867
|
// Check for @everyone — queue to all teammates except the coding agent
|
|
800
868
|
const everyoneMatch = input.match(/^@everyone\s+([\s\S]+)$/i);
|
|
801
869
|
if (everyoneMatch) {
|
|
802
870
|
const task = everyoneMatch[1];
|
|
803
|
-
const names = allNames.filter((n) => n !== this.adapterName);
|
|
871
|
+
const names = allNames.filter((n) => n !== this.selfName && n !== this.adapterName);
|
|
804
872
|
for (const teammate of names) {
|
|
805
873
|
this.taskQueue.push({ type: "agent", teammate, task });
|
|
806
874
|
}
|
|
@@ -812,14 +880,21 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
|
|
|
812
880
|
this.kickDrain();
|
|
813
881
|
return;
|
|
814
882
|
}
|
|
815
|
-
//
|
|
816
|
-
|
|
817
|
-
let
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
883
|
+
// Use pre-resolved mentions if provided (avoids picking up @mentions from expanded paste text),
|
|
884
|
+
// otherwise scan the input directly.
|
|
885
|
+
let mentioned;
|
|
886
|
+
if (preMentions) {
|
|
887
|
+
mentioned = preMentions;
|
|
888
|
+
}
|
|
889
|
+
else {
|
|
890
|
+
const mentionRegex = /@(\S+)/g;
|
|
891
|
+
let m;
|
|
892
|
+
mentioned = [];
|
|
893
|
+
while ((m = mentionRegex.exec(input)) !== null) {
|
|
894
|
+
const name = m[1];
|
|
895
|
+
if (allNames.includes(name) && !mentioned.includes(name)) {
|
|
896
|
+
mentioned.push(name);
|
|
897
|
+
}
|
|
823
898
|
}
|
|
824
899
|
}
|
|
825
900
|
if (mentioned.length > 0) {
|
|
@@ -841,12 +916,13 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
|
|
|
841
916
|
match = this.lastResult.teammate;
|
|
842
917
|
}
|
|
843
918
|
if (!match) {
|
|
844
|
-
match = this.orchestrator.route(input) ?? this.
|
|
919
|
+
match = this.orchestrator.route(input) ?? this.selfName;
|
|
845
920
|
}
|
|
846
921
|
{
|
|
847
922
|
const bg = this._userBg;
|
|
848
923
|
const t = theme();
|
|
849
|
-
this.
|
|
924
|
+
const displayName = match === this.adapterName ? this.selfName : match;
|
|
925
|
+
this.feedUserLine(concat(pen.fg(t.textMuted).bg(bg)(" → "), pen.fg(t.accent).bg(bg)(`@${displayName}`)));
|
|
850
926
|
}
|
|
851
927
|
this.feedLine();
|
|
852
928
|
this.refreshView();
|
|
@@ -871,20 +947,13 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
|
|
|
871
947
|
}
|
|
872
948
|
// ─── Onboarding ───────────────────────────────────────────────────
|
|
873
949
|
/**
|
|
874
|
-
* Interactive prompt
|
|
875
|
-
*
|
|
950
|
+
* Interactive prompt for team onboarding after user profile is set up.
|
|
951
|
+
* .teammates/ already exists at this point. Returns false if user chose to exit.
|
|
876
952
|
*/
|
|
877
|
-
async
|
|
953
|
+
async promptTeamOnboarding(adapter, teammatesDir) {
|
|
878
954
|
const cwd = process.cwd();
|
|
879
|
-
const teammatesDir = join(cwd, ".teammates");
|
|
880
955
|
const termWidth = process.stdout.columns || 100;
|
|
881
956
|
console.log();
|
|
882
|
-
this.printLogo([
|
|
883
|
-
chalk.bold("Teammates") + chalk.gray(` v${PKG_VERSION}`),
|
|
884
|
-
chalk.yellow("No .teammates/ directory found"),
|
|
885
|
-
chalk.gray(cwd),
|
|
886
|
-
]);
|
|
887
|
-
console.log();
|
|
888
957
|
console.log(chalk.gray("─".repeat(termWidth)));
|
|
889
958
|
console.log();
|
|
890
959
|
console.log(chalk.white(" Set up teammates for this project?\n"));
|
|
@@ -899,7 +968,7 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
|
|
|
899
968
|
console.log(chalk.cyan(" 3") +
|
|
900
969
|
chalk.gray(") ") +
|
|
901
970
|
chalk.white("Solo mode") +
|
|
902
|
-
chalk.gray(
|
|
971
|
+
chalk.gray(" — use your agent without teammates"));
|
|
903
972
|
console.log(chalk.cyan(" 4") + chalk.gray(") ") + chalk.white("Exit"));
|
|
904
973
|
console.log();
|
|
905
974
|
const choice = await this.askChoice("Pick an option (1/2/3/4): ", [
|
|
@@ -910,27 +979,21 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
|
|
|
910
979
|
]);
|
|
911
980
|
if (choice === "4") {
|
|
912
981
|
console.log(chalk.gray(" Goodbye."));
|
|
913
|
-
return
|
|
982
|
+
return false;
|
|
914
983
|
}
|
|
915
984
|
if (choice === "3") {
|
|
916
|
-
|
|
917
|
-
console.log();
|
|
918
|
-
console.log(chalk.green(" ✔") + chalk.gray(` Created ${teammatesDir}`));
|
|
919
|
-
console.log(chalk.gray(` Running in solo mode — all tasks go to ${this.adapterName}.`));
|
|
985
|
+
console.log(chalk.gray(" Running in solo mode — all tasks go to your agent."));
|
|
920
986
|
console.log(chalk.gray(" Run /init later to set up teammates."));
|
|
921
987
|
console.log();
|
|
922
|
-
return
|
|
988
|
+
return true;
|
|
923
989
|
}
|
|
924
990
|
if (choice === "2") {
|
|
925
|
-
// Import from another project
|
|
926
|
-
await mkdir(teammatesDir, { recursive: true });
|
|
927
991
|
await this.runImport(cwd);
|
|
928
|
-
return
|
|
992
|
+
return true;
|
|
929
993
|
}
|
|
930
994
|
// choice === "1": Run onboarding via the agent
|
|
931
|
-
await mkdir(teammatesDir, { recursive: true });
|
|
932
995
|
await this.runOnboardingAgent(adapter, cwd);
|
|
933
|
-
return
|
|
996
|
+
return true;
|
|
934
997
|
}
|
|
935
998
|
/**
|
|
936
999
|
* Run the onboarding agent to analyze the codebase and create teammates.
|
|
@@ -939,19 +1002,20 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
|
|
|
939
1002
|
async runOnboardingAgent(adapter, projectDir) {
|
|
940
1003
|
console.log();
|
|
941
1004
|
console.log(chalk.blue(" Starting onboarding...") +
|
|
942
|
-
chalk.gray(
|
|
1005
|
+
chalk.gray(" Your agent will analyze your codebase and create .teammates/"));
|
|
943
1006
|
console.log();
|
|
944
1007
|
// Copy framework files from bundled template
|
|
945
1008
|
const teammatesDir = join(projectDir, ".teammates");
|
|
946
1009
|
const copied = await copyTemplateFiles(teammatesDir);
|
|
947
1010
|
if (copied.length > 0) {
|
|
948
|
-
console.log(chalk.green(" ✔") +
|
|
1011
|
+
console.log(chalk.green(" ✔ ") +
|
|
949
1012
|
chalk.gray(` Copied template files: ${copied.join(", ")}`));
|
|
950
1013
|
console.log();
|
|
951
1014
|
}
|
|
952
1015
|
const onboardingPrompt = await getOnboardingPrompt(projectDir);
|
|
953
1016
|
const tempConfig = {
|
|
954
1017
|
name: this.adapterName,
|
|
1018
|
+
type: "ai",
|
|
955
1019
|
role: "Onboarding agent",
|
|
956
1020
|
soul: "",
|
|
957
1021
|
wisdom: "",
|
|
@@ -963,8 +1027,7 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
|
|
|
963
1027
|
};
|
|
964
1028
|
const sessionId = await adapter.startSession(tempConfig);
|
|
965
1029
|
const spinner = ora({
|
|
966
|
-
text: chalk.
|
|
967
|
-
chalk.gray(" is analyzing your codebase..."),
|
|
1030
|
+
text: chalk.gray("Analyzing your codebase..."),
|
|
968
1031
|
spinner: "dots",
|
|
969
1032
|
}).start();
|
|
970
1033
|
try {
|
|
@@ -972,7 +1035,7 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
|
|
|
972
1035
|
spinner.stop();
|
|
973
1036
|
this.printAgentOutput(result.rawOutput);
|
|
974
1037
|
if (result.success) {
|
|
975
|
-
console.log(chalk.green(" ✔
|
|
1038
|
+
console.log(chalk.green(" ✔ Onboarding complete!"));
|
|
976
1039
|
}
|
|
977
1040
|
else {
|
|
978
1041
|
console.log(chalk.yellow(` ⚠ Onboarding finished with issues: ${result.summary}`));
|
|
@@ -1038,7 +1101,7 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
|
|
|
1038
1101
|
return;
|
|
1039
1102
|
}
|
|
1040
1103
|
if (teammates.length > 0) {
|
|
1041
|
-
console.log(chalk.green(" ✔") +
|
|
1104
|
+
console.log(chalk.green(" ✔ ") +
|
|
1042
1105
|
chalk.white(` Imported ${teammates.length} teammate${teammates.length > 1 ? "s" : ""}: `) +
|
|
1043
1106
|
chalk.cyan(teammates.join(", ")));
|
|
1044
1107
|
console.log(chalk.gray(` (${files.length} files copied)`));
|
|
@@ -1078,11 +1141,12 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
|
|
|
1078
1141
|
const teammatesDir = join(projectDir, ".teammates");
|
|
1079
1142
|
console.log();
|
|
1080
1143
|
console.log(chalk.blue(" Starting adaptation...") +
|
|
1081
|
-
chalk.gray(
|
|
1144
|
+
chalk.gray(" Your agent will scan this project and adapt the team"));
|
|
1082
1145
|
console.log();
|
|
1083
1146
|
const prompt = await buildImportAdaptationPrompt(teammatesDir, teammateNames, sourceProjectPath);
|
|
1084
1147
|
const tempConfig = {
|
|
1085
1148
|
name: this.adapterName,
|
|
1149
|
+
type: "ai",
|
|
1086
1150
|
role: "Adaptation agent",
|
|
1087
1151
|
soul: "",
|
|
1088
1152
|
wisdom: "",
|
|
@@ -1094,8 +1158,7 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
|
|
|
1094
1158
|
};
|
|
1095
1159
|
const sessionId = await adapter.startSession(tempConfig);
|
|
1096
1160
|
const spinner = ora({
|
|
1097
|
-
text: chalk.
|
|
1098
|
-
chalk.gray(" is scanning the project and adapting teammates..."),
|
|
1161
|
+
text: chalk.gray("Scanning the project and adapting teammates..."),
|
|
1099
1162
|
spinner: "dots",
|
|
1100
1163
|
}).start();
|
|
1101
1164
|
try {
|
|
@@ -1103,7 +1166,7 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
|
|
|
1103
1166
|
spinner.stop();
|
|
1104
1167
|
this.printAgentOutput(result.rawOutput);
|
|
1105
1168
|
if (result.success) {
|
|
1106
|
-
console.log(chalk.green(" ✔
|
|
1169
|
+
console.log(chalk.green(" ✔ Team adaptation complete!"));
|
|
1107
1170
|
}
|
|
1108
1171
|
else {
|
|
1109
1172
|
console.log(chalk.yellow(` ⚠ Adaptation finished with issues: ${result.summary}`));
|
|
@@ -1153,6 +1216,30 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
|
|
|
1153
1216
|
});
|
|
1154
1217
|
});
|
|
1155
1218
|
}
|
|
1219
|
+
/**
|
|
1220
|
+
* Ask for input using the ChatView's own prompt (no raw readline).
|
|
1221
|
+
* Temporarily replaces the footer with the prompt text and intercepts the next submit.
|
|
1222
|
+
*/
|
|
1223
|
+
askInline(prompt) {
|
|
1224
|
+
return new Promise((resolve) => {
|
|
1225
|
+
if (!this.chatView) {
|
|
1226
|
+
// Fallback if no ChatView (shouldn't happen during /configure)
|
|
1227
|
+
return this.askInput(prompt).then(resolve);
|
|
1228
|
+
}
|
|
1229
|
+
// Show the prompt in the feed so it's visible
|
|
1230
|
+
this.feedLine(tp.accent(` ${prompt}`));
|
|
1231
|
+
this.chatView.setFooter(tp.accent(` ${prompt}`));
|
|
1232
|
+
this._pendingAsk = (answer) => {
|
|
1233
|
+
// Restore footer
|
|
1234
|
+
if (this.chatView && this.defaultFooter) {
|
|
1235
|
+
this.chatView.setFooter(this.defaultFooter);
|
|
1236
|
+
}
|
|
1237
|
+
this.refreshView();
|
|
1238
|
+
resolve(answer.trim());
|
|
1239
|
+
};
|
|
1240
|
+
this.refreshView();
|
|
1241
|
+
});
|
|
1242
|
+
}
|
|
1156
1243
|
/**
|
|
1157
1244
|
* Check whether USER.md needs to be created or is still template placeholders.
|
|
1158
1245
|
*/
|
|
@@ -1169,71 +1256,309 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
|
|
|
1169
1256
|
}
|
|
1170
1257
|
}
|
|
1171
1258
|
/**
|
|
1172
|
-
*
|
|
1173
|
-
*
|
|
1259
|
+
* Pre-TUI user profile setup. Runs in the console before the ChatView is created.
|
|
1260
|
+
* Offers GitHub-based or manual profile creation.
|
|
1174
1261
|
*/
|
|
1175
|
-
|
|
1176
|
-
|
|
1262
|
+
async runUserSetup(teammatesDir) {
|
|
1263
|
+
const termWidth = process.stdout.columns || 100;
|
|
1264
|
+
console.log();
|
|
1265
|
+
console.log(chalk.gray("─".repeat(termWidth)));
|
|
1266
|
+
console.log();
|
|
1267
|
+
console.log(chalk.white(" Set up your profile\n"));
|
|
1268
|
+
console.log(chalk.cyan(" 1") +
|
|
1269
|
+
chalk.gray(") ") +
|
|
1270
|
+
chalk.white("Use GitHub account") +
|
|
1271
|
+
chalk.gray(" — import your name and username from GitHub"));
|
|
1272
|
+
console.log(chalk.cyan(" 2") +
|
|
1273
|
+
chalk.gray(") ") +
|
|
1274
|
+
chalk.white("Manual setup") +
|
|
1275
|
+
chalk.gray(" — enter your details manually"));
|
|
1276
|
+
console.log(chalk.cyan(" 3") +
|
|
1277
|
+
chalk.gray(") ") +
|
|
1278
|
+
chalk.white("Skip") +
|
|
1279
|
+
chalk.gray(" — set up later with /user"));
|
|
1280
|
+
console.log();
|
|
1281
|
+
const choice = await this.askChoice("Pick an option (1/2/3): ", [
|
|
1282
|
+
"1",
|
|
1283
|
+
"2",
|
|
1284
|
+
"3",
|
|
1285
|
+
]);
|
|
1286
|
+
if (choice === "3") {
|
|
1287
|
+
console.log(chalk.gray(" Skipped — run /user to set up your profile later."));
|
|
1288
|
+
console.log();
|
|
1177
1289
|
return;
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1290
|
+
}
|
|
1291
|
+
if (choice === "1") {
|
|
1292
|
+
await this.setupGitHubProfile(teammatesDir);
|
|
1293
|
+
}
|
|
1294
|
+
else {
|
|
1295
|
+
await this.setupManualProfile(teammatesDir);
|
|
1296
|
+
}
|
|
1297
|
+
}
|
|
1298
|
+
/**
|
|
1299
|
+
* GitHub-based profile setup. Ensures gh is installed and authenticated,
|
|
1300
|
+
* then fetches user info from the GitHub API to create the profile.
|
|
1301
|
+
*/
|
|
1302
|
+
async setupGitHubProfile(teammatesDir) {
|
|
1303
|
+
console.log();
|
|
1304
|
+
// Step 1: Check if gh is installed
|
|
1305
|
+
let ghInstalled = false;
|
|
1306
|
+
try {
|
|
1307
|
+
execSync("gh --version", { stdio: "pipe" });
|
|
1308
|
+
ghInstalled = true;
|
|
1309
|
+
}
|
|
1310
|
+
catch {
|
|
1311
|
+
// not installed
|
|
1312
|
+
}
|
|
1313
|
+
if (!ghInstalled) {
|
|
1314
|
+
console.log(chalk.yellow(" GitHub CLI is not installed.\n"));
|
|
1315
|
+
const plat = process.platform;
|
|
1316
|
+
console.log(chalk.white(" Run this in another terminal:"));
|
|
1317
|
+
if (plat === "win32") {
|
|
1318
|
+
console.log(chalk.cyan(" winget install --id GitHub.cli"));
|
|
1319
|
+
}
|
|
1320
|
+
else if (plat === "darwin") {
|
|
1321
|
+
console.log(chalk.cyan(" brew install gh"));
|
|
1322
|
+
}
|
|
1323
|
+
else {
|
|
1324
|
+
console.log(chalk.cyan(" sudo apt install gh"));
|
|
1325
|
+
console.log(chalk.gray(" (or see https://cli.github.com)"));
|
|
1326
|
+
}
|
|
1327
|
+
console.log();
|
|
1328
|
+
const answer = await this.askChoice("Press Enter when done, or s to skip: ", ["", "s", "S"]);
|
|
1329
|
+
if (answer.toLowerCase() === "s") {
|
|
1330
|
+
console.log(chalk.gray(" Falling back to manual setup.\n"));
|
|
1331
|
+
return this.setupManualProfile(teammatesDir);
|
|
1332
|
+
}
|
|
1333
|
+
// Re-check
|
|
1334
|
+
try {
|
|
1335
|
+
execSync("gh --version", { stdio: "pipe" });
|
|
1336
|
+
ghInstalled = true;
|
|
1337
|
+
console.log(chalk.green(" ✔ GitHub CLI installed"));
|
|
1338
|
+
}
|
|
1339
|
+
catch {
|
|
1340
|
+
console.log(chalk.yellow(" GitHub CLI still not found. You may need to restart your terminal."));
|
|
1341
|
+
console.log(chalk.gray(" Falling back to manual setup.\n"));
|
|
1342
|
+
return this.setupManualProfile(teammatesDir);
|
|
1343
|
+
}
|
|
1344
|
+
}
|
|
1345
|
+
else {
|
|
1346
|
+
console.log(chalk.green(" ✔ GitHub CLI installed"));
|
|
1347
|
+
}
|
|
1348
|
+
// Step 2: Check auth
|
|
1349
|
+
let authed = false;
|
|
1350
|
+
try {
|
|
1351
|
+
execSync("gh auth status", { stdio: "pipe" });
|
|
1352
|
+
authed = true;
|
|
1353
|
+
}
|
|
1354
|
+
catch {
|
|
1355
|
+
// not authenticated
|
|
1356
|
+
}
|
|
1357
|
+
if (!authed) {
|
|
1358
|
+
console.log();
|
|
1359
|
+
console.log(chalk.gray(" Authenticating with GitHub...\n"));
|
|
1360
|
+
const result = spawnSync("gh", ["auth", "login", "--web", "--git-protocol", "https"], {
|
|
1361
|
+
stdio: "inherit",
|
|
1362
|
+
shell: true,
|
|
1363
|
+
});
|
|
1364
|
+
if (result.status !== 0) {
|
|
1365
|
+
console.log(chalk.yellow(" Authentication failed or was cancelled."));
|
|
1366
|
+
console.log(chalk.gray(" Falling back to manual setup.\n"));
|
|
1367
|
+
return this.setupManualProfile(teammatesDir);
|
|
1368
|
+
}
|
|
1369
|
+
// Verify
|
|
1370
|
+
try {
|
|
1371
|
+
execSync("gh auth status", { stdio: "pipe" });
|
|
1372
|
+
authed = true;
|
|
1373
|
+
}
|
|
1374
|
+
catch {
|
|
1375
|
+
console.log(chalk.yellow(" Authentication could not be verified."));
|
|
1376
|
+
console.log(chalk.gray(" Falling back to manual setup.\n"));
|
|
1377
|
+
return this.setupManualProfile(teammatesDir);
|
|
1378
|
+
}
|
|
1379
|
+
}
|
|
1380
|
+
console.log(chalk.green(" ✔ GitHub authenticated"));
|
|
1381
|
+
// Step 3: Fetch user info from GitHub API
|
|
1382
|
+
let login = "";
|
|
1383
|
+
let name = "";
|
|
1384
|
+
try {
|
|
1385
|
+
const json = execSync("gh api user", {
|
|
1386
|
+
stdio: "pipe",
|
|
1387
|
+
encoding: "utf-8",
|
|
1388
|
+
});
|
|
1389
|
+
const user = JSON.parse(json);
|
|
1390
|
+
login = (user.login || "").toLowerCase().replace(/[^a-z0-9_-]/g, "");
|
|
1391
|
+
name = user.name || user.login || "";
|
|
1392
|
+
}
|
|
1393
|
+
catch {
|
|
1394
|
+
console.log(chalk.yellow(" Could not fetch GitHub user info."));
|
|
1395
|
+
console.log(chalk.gray(" Falling back to manual setup.\n"));
|
|
1396
|
+
return this.setupManualProfile(teammatesDir);
|
|
1397
|
+
}
|
|
1398
|
+
if (!login) {
|
|
1399
|
+
console.log(chalk.yellow(" No GitHub username found."));
|
|
1400
|
+
console.log(chalk.gray(" Falling back to manual setup.\n"));
|
|
1401
|
+
return this.setupManualProfile(teammatesDir);
|
|
1402
|
+
}
|
|
1403
|
+
console.log(chalk.green(` ✔ Authenticated as `) +
|
|
1404
|
+
chalk.cyan(`@${login}`) +
|
|
1405
|
+
(name && name !== login ? chalk.gray(` (${name})`) : ""));
|
|
1406
|
+
console.log();
|
|
1407
|
+
// Ask for role (optional) since GitHub doesn't provide this
|
|
1408
|
+
const role = await this.askInput("Your role (optional, press Enter to skip): ");
|
|
1409
|
+
const answers = {
|
|
1410
|
+
alias: login,
|
|
1411
|
+
name: name || login,
|
|
1412
|
+
role: role || "",
|
|
1413
|
+
experience: "",
|
|
1414
|
+
preferences: "",
|
|
1415
|
+
context: "",
|
|
1416
|
+
};
|
|
1417
|
+
this.writeUserProfile(teammatesDir, login, answers);
|
|
1418
|
+
this.createUserAvatar(teammatesDir, login, answers);
|
|
1419
|
+
console.log(chalk.green(" ✔ ") +
|
|
1420
|
+
chalk.gray(`Profile created — avatar @${login}`));
|
|
1421
|
+
console.log();
|
|
1422
|
+
}
|
|
1423
|
+
/**
|
|
1424
|
+
* Manual (console-based) profile setup. Collects fields via askInput().
|
|
1425
|
+
*/
|
|
1426
|
+
async setupManualProfile(teammatesDir) {
|
|
1427
|
+
console.log();
|
|
1428
|
+
console.log(chalk.gray(" (alias is required, press Enter to skip others)\n"));
|
|
1429
|
+
const aliasRaw = await this.askInput("Your alias (e.g., alex): ");
|
|
1430
|
+
const alias = aliasRaw.toLowerCase().replace(/[^a-z0-9_-]/g, "").trim();
|
|
1431
|
+
if (!alias) {
|
|
1432
|
+
console.log(chalk.yellow(" Alias is required. Run /user to try again.\n"));
|
|
1433
|
+
return;
|
|
1434
|
+
}
|
|
1435
|
+
const name = await this.askInput("Your name: ");
|
|
1436
|
+
const role = await this.askInput("Your role (e.g., senior backend engineer): ");
|
|
1437
|
+
const experience = await this.askInput("Relevant experience (e.g., 10 years Go, new to React): ");
|
|
1438
|
+
const preferences = await this.askInput("How you like to work (e.g., terse responses): ");
|
|
1439
|
+
const context = await this.askInput("Anything else: ");
|
|
1440
|
+
const answers = {
|
|
1441
|
+
alias,
|
|
1442
|
+
name,
|
|
1443
|
+
role,
|
|
1444
|
+
experience,
|
|
1445
|
+
preferences,
|
|
1446
|
+
context,
|
|
1447
|
+
};
|
|
1448
|
+
this.writeUserProfile(teammatesDir, alias, answers);
|
|
1449
|
+
this.createUserAvatar(teammatesDir, alias, answers);
|
|
1450
|
+
console.log();
|
|
1451
|
+
console.log(chalk.green(" ✔ ") +
|
|
1452
|
+
chalk.gray(`Profile created — avatar @${alias}`));
|
|
1453
|
+
console.log(chalk.gray(" Update anytime with /user"));
|
|
1454
|
+
console.log();
|
|
1455
|
+
}
|
|
1456
|
+
/**
|
|
1457
|
+
* Write USER.md from collected answers.
|
|
1458
|
+
*/
|
|
1459
|
+
writeUserProfile(teammatesDir, alias, answers) {
|
|
1460
|
+
const userMdPath = join(teammatesDir, "USER.md");
|
|
1461
|
+
const lines = ["# User\n"];
|
|
1462
|
+
lines.push(`- **Alias:** ${alias}`);
|
|
1463
|
+
lines.push(`- **Name:** ${answers.name || "_not provided_"}`);
|
|
1464
|
+
lines.push(`- **Role:** ${answers.role || "_not provided_"}`);
|
|
1465
|
+
lines.push(`- **Experience:** ${answers.experience || "_not provided_"}`);
|
|
1466
|
+
lines.push(`- **Preferences:** ${answers.preferences || "_not provided_"}`);
|
|
1467
|
+
lines.push(`- **Context:** ${answers.context || "_not provided_"}`);
|
|
1468
|
+
writeFileSync(userMdPath, `${lines.join("\n")}\n`, "utf-8");
|
|
1469
|
+
}
|
|
1470
|
+
/**
|
|
1471
|
+
* Create the user's avatar folder with SOUL.md and WISDOM.md.
|
|
1472
|
+
* The avatar is a teammate folder with type: human.
|
|
1473
|
+
*/
|
|
1474
|
+
createUserAvatar(teammatesDir, alias, answers) {
|
|
1475
|
+
const avatarDir = join(teammatesDir, alias);
|
|
1476
|
+
const memoryDir = join(avatarDir, "memory");
|
|
1477
|
+
mkdirSync(avatarDir, { recursive: true });
|
|
1478
|
+
mkdirSync(memoryDir, { recursive: true });
|
|
1479
|
+
const name = answers.name || alias;
|
|
1480
|
+
const role = answers.role || "Team member";
|
|
1481
|
+
const experience = answers.experience || "";
|
|
1482
|
+
const preferences = answers.preferences || "";
|
|
1483
|
+
const context = answers.context || "";
|
|
1484
|
+
// Write SOUL.md
|
|
1485
|
+
const soulLines = [
|
|
1486
|
+
`# ${name}`,
|
|
1487
|
+
"",
|
|
1488
|
+
"## Identity",
|
|
1489
|
+
"",
|
|
1490
|
+
`**Type:** human`,
|
|
1491
|
+
`**Alias:** ${alias}`,
|
|
1492
|
+
`**Role:** ${role}`,
|
|
1493
|
+
];
|
|
1494
|
+
if (experience)
|
|
1495
|
+
soulLines.push(`**Experience:** ${experience}`);
|
|
1496
|
+
if (preferences)
|
|
1497
|
+
soulLines.push(`**Preferences:** ${preferences}`);
|
|
1498
|
+
if (context) {
|
|
1499
|
+
soulLines.push("", "## Context", "", context);
|
|
1500
|
+
}
|
|
1501
|
+
soulLines.push("");
|
|
1502
|
+
const soulPath = join(avatarDir, "SOUL.md");
|
|
1503
|
+
writeFileSync(soulPath, soulLines.join("\n"), "utf-8");
|
|
1504
|
+
// Write empty WISDOM.md
|
|
1505
|
+
const wisdomPath = join(avatarDir, "WISDOM.md");
|
|
1506
|
+
writeFileSync(wisdomPath, `# ${name} — Wisdom\n\nDistilled from work history. Updated during compaction.\n`, "utf-8");
|
|
1507
|
+
// Avatar registration happens later in start() after the orchestrator is initialized.
|
|
1508
|
+
// During pre-TUI setup, the orchestrator doesn't exist yet.
|
|
1509
|
+
}
|
|
1510
|
+
/**
|
|
1511
|
+
* Read USER.md and extract the alias field.
|
|
1512
|
+
* Returns null if USER.md doesn't exist or has no alias.
|
|
1513
|
+
*/
|
|
1514
|
+
readUserAlias(teammatesDir) {
|
|
1515
|
+
try {
|
|
1516
|
+
const content = readFileSync(join(teammatesDir, "USER.md"), "utf-8");
|
|
1517
|
+
const match = content.match(/\*\*Alias:\*\*\s*(\S+)/);
|
|
1518
|
+
return match ? match[1].toLowerCase().replace(/[^a-z0-9_-]/g, "") : null;
|
|
1519
|
+
}
|
|
1520
|
+
catch {
|
|
1521
|
+
return null;
|
|
1522
|
+
}
|
|
1523
|
+
}
|
|
1524
|
+
/**
|
|
1525
|
+
* Register the user's avatar as a teammate in the orchestrator.
|
|
1526
|
+
* Sets presence to "online" since the local user is always online.
|
|
1527
|
+
* Replaces the old coding agent entry.
|
|
1528
|
+
*/
|
|
1529
|
+
registerUserAvatar(teammatesDir, alias) {
|
|
1530
|
+
const registry = this.orchestrator.getRegistry();
|
|
1531
|
+
const avatarDir = join(teammatesDir, alias);
|
|
1532
|
+
// Read the avatar's SOUL.md if it exists
|
|
1533
|
+
let soul = "";
|
|
1534
|
+
let role = "Team member";
|
|
1535
|
+
try {
|
|
1536
|
+
soul = readFileSync(join(avatarDir, "SOUL.md"), "utf-8");
|
|
1537
|
+
const roleMatch = soul.match(/\*\*Role:\*\*\s*(.+)/);
|
|
1538
|
+
if (roleMatch)
|
|
1539
|
+
role = roleMatch[1].trim();
|
|
1540
|
+
}
|
|
1541
|
+
catch { /* avatar folder may not exist yet */ }
|
|
1542
|
+
let wisdom = "";
|
|
1543
|
+
try {
|
|
1544
|
+
wisdom = readFileSync(join(avatarDir, "WISDOM.md"), "utf-8");
|
|
1545
|
+
}
|
|
1546
|
+
catch { /* ok */ }
|
|
1547
|
+
registry.register({
|
|
1548
|
+
name: alias,
|
|
1549
|
+
type: "human",
|
|
1550
|
+
role,
|
|
1551
|
+
soul,
|
|
1552
|
+
wisdom,
|
|
1553
|
+
dailyLogs: [],
|
|
1554
|
+
weeklyLogs: [],
|
|
1555
|
+
ownership: { primary: [], secondary: [] },
|
|
1556
|
+
routingKeywords: [],
|
|
1236
1557
|
});
|
|
1558
|
+
// Set presence to online (local user is always online)
|
|
1559
|
+
this.orchestrator.getAllStatuses().set(alias, { state: "idle", presence: "online" });
|
|
1560
|
+
// Update the adapter name so tasks route to the avatar
|
|
1561
|
+
this.userAlias = alias;
|
|
1237
1562
|
}
|
|
1238
1563
|
// ─── Display helpers ──────────────────────────────────────────────
|
|
1239
1564
|
/**
|
|
@@ -1560,15 +1885,29 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
|
|
|
1560
1885
|
agentPassthrough: cliArgs.agentPassthrough,
|
|
1561
1886
|
});
|
|
1562
1887
|
this.adapter = adapter;
|
|
1563
|
-
//
|
|
1888
|
+
// Detect whether this is a brand-new project (no .teammates/ at all)
|
|
1889
|
+
const isNewProject = !teammatesDir;
|
|
1564
1890
|
if (!teammatesDir) {
|
|
1565
|
-
teammatesDir =
|
|
1566
|
-
|
|
1891
|
+
teammatesDir = join(process.cwd(), ".teammates");
|
|
1892
|
+
await mkdir(teammatesDir, { recursive: true });
|
|
1893
|
+
// Show welcome logo for new projects
|
|
1894
|
+
console.log();
|
|
1895
|
+
this.printLogo([
|
|
1896
|
+
chalk.bold("Teammates") + chalk.gray(` v${PKG_VERSION}`),
|
|
1897
|
+
chalk.yellow("New project setup"),
|
|
1898
|
+
chalk.gray(process.cwd()),
|
|
1899
|
+
]);
|
|
1900
|
+
}
|
|
1901
|
+
// Always onboard the user first if USER.md is missing
|
|
1902
|
+
if (this.needsUserSetup(teammatesDir)) {
|
|
1903
|
+
await this.runUserSetup(teammatesDir);
|
|
1904
|
+
}
|
|
1905
|
+
// Team onboarding if .teammates/ was missing
|
|
1906
|
+
if (isNewProject) {
|
|
1907
|
+
const cont = await this.promptTeamOnboarding(adapter, teammatesDir);
|
|
1908
|
+
if (!cont)
|
|
1567
1909
|
return; // user chose to exit
|
|
1568
1910
|
}
|
|
1569
|
-
// Check if USER.md needs setup — we'll run the interview inside the
|
|
1570
|
-
// ChatView after the UI loads (not before).
|
|
1571
|
-
const pendingUserInterview = this.needsUserSetup(teammatesDir);
|
|
1572
1911
|
// Init orchestrator
|
|
1573
1912
|
this.teammatesDir = teammatesDir;
|
|
1574
1913
|
this.orchestrator = new Orchestrator({
|
|
@@ -1577,26 +1916,38 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
|
|
|
1577
1916
|
onEvent: (e) => this.handleEvent(e),
|
|
1578
1917
|
});
|
|
1579
1918
|
await this.orchestrator.init();
|
|
1580
|
-
// Register the
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1919
|
+
// Register the local user's avatar if alias is configured.
|
|
1920
|
+
// The user's avatar is the entry point for all generic/fallback tasks —
|
|
1921
|
+
// the coding agent is an internal execution engine, not an addressable teammate.
|
|
1922
|
+
const alias = this.readUserAlias(teammatesDir);
|
|
1923
|
+
if (alias) {
|
|
1924
|
+
this.registerUserAvatar(teammatesDir, alias);
|
|
1925
|
+
}
|
|
1926
|
+
else {
|
|
1927
|
+
// No alias yet (solo mode or pre-interview). Register a minimal avatar
|
|
1928
|
+
// under the adapter name so internal tasks (btw, summarize, debug) can execute.
|
|
1929
|
+
const registry = this.orchestrator.getRegistry();
|
|
1930
|
+
registry.register({
|
|
1931
|
+
name: this.adapterName,
|
|
1932
|
+
type: "ai",
|
|
1933
|
+
role: "General-purpose coding agent",
|
|
1934
|
+
soul: "",
|
|
1935
|
+
wisdom: "",
|
|
1936
|
+
dailyLogs: [],
|
|
1937
|
+
weeklyLogs: [],
|
|
1938
|
+
ownership: { primary: [], secondary: [] },
|
|
1939
|
+
routingKeywords: [],
|
|
1940
|
+
cwd: dirname(this.teammatesDir),
|
|
1941
|
+
});
|
|
1942
|
+
this.orchestrator.getAllStatuses().set(this.adapterName, { state: "idle", presence: "online" });
|
|
1943
|
+
}
|
|
1595
1944
|
// Populate roster on the adapter so prompts include team info
|
|
1945
|
+
// Exclude the user avatar and adapter fallback — neither is an addressable teammate
|
|
1596
1946
|
if ("roster" in this.adapter) {
|
|
1597
1947
|
const registry = this.orchestrator.getRegistry();
|
|
1598
1948
|
this.adapter.roster = this.orchestrator
|
|
1599
1949
|
.listTeammates()
|
|
1950
|
+
.filter((n) => n !== this.adapterName && n !== this.userAlias)
|
|
1600
1951
|
.map((name) => {
|
|
1601
1952
|
const t = registry.get(name);
|
|
1602
1953
|
return { name: t.name, role: t.role, ownership: t.ownership };
|
|
@@ -1614,8 +1965,8 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
|
|
|
1614
1965
|
borderStyle: (s) => chalk.gray(s),
|
|
1615
1966
|
colorize: (value) => {
|
|
1616
1967
|
const validNames = new Set([
|
|
1617
|
-
...this.orchestrator.listTeammates(),
|
|
1618
|
-
this.
|
|
1968
|
+
...this.orchestrator.listTeammates().filter((n) => n !== this.adapterName),
|
|
1969
|
+
this.selfName,
|
|
1619
1970
|
"everyone",
|
|
1620
1971
|
]);
|
|
1621
1972
|
return value
|
|
@@ -1654,18 +2005,25 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
|
|
|
1654
2005
|
// ── Detect service statuses ────────────────────────────────────────
|
|
1655
2006
|
this.serviceStatuses = this.detectServices();
|
|
1656
2007
|
// ── Build animated banner for ChatView ─────────────────────────────
|
|
1657
|
-
const names = this.orchestrator
|
|
2008
|
+
const names = this.orchestrator
|
|
2009
|
+
.listTeammates()
|
|
2010
|
+
.filter((n) => n !== this.adapterName && n !== this.userAlias);
|
|
1658
2011
|
const reg = this.orchestrator.getRegistry();
|
|
2012
|
+
const statuses = this.orchestrator.getAllStatuses();
|
|
2013
|
+
const bannerTeammates = [];
|
|
2014
|
+
for (const name of names) {
|
|
2015
|
+
const t = reg.get(name);
|
|
2016
|
+
const p = statuses.get(name)?.presence ?? "online";
|
|
2017
|
+
bannerTeammates.push({ name, role: t?.role ?? "", presence: p });
|
|
2018
|
+
}
|
|
1659
2019
|
const bannerWidget = new AnimatedBanner({
|
|
1660
|
-
|
|
2020
|
+
displayName: `@${this.selfName}`,
|
|
1661
2021
|
teammateCount: names.length,
|
|
1662
2022
|
cwd: process.cwd(),
|
|
1663
|
-
teammates:
|
|
1664
|
-
const t = reg.get(name);
|
|
1665
|
-
return { name, role: t?.role ?? "" };
|
|
1666
|
-
}),
|
|
2023
|
+
teammates: bannerTeammates,
|
|
1667
2024
|
services: this.serviceStatuses,
|
|
1668
2025
|
});
|
|
2026
|
+
this.banner = bannerWidget;
|
|
1669
2027
|
// ── Create ChatView and Consolonia App ────────────────────────────
|
|
1670
2028
|
const t = theme();
|
|
1671
2029
|
this.chatView = new ChatView({
|
|
@@ -1688,10 +2046,10 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
|
|
|
1688
2046
|
styles[i] = accentStyle;
|
|
1689
2047
|
}
|
|
1690
2048
|
}
|
|
1691
|
-
// Colorize @mentions only if they reference a valid teammate or the
|
|
2049
|
+
// Colorize @mentions only if they reference a valid teammate or the user
|
|
1692
2050
|
const validNames = new Set([
|
|
1693
|
-
...this.orchestrator.listTeammates(),
|
|
1694
|
-
this.
|
|
2051
|
+
...this.orchestrator.listTeammates().filter((n) => n !== this.adapterName),
|
|
2052
|
+
this.selfName,
|
|
1695
2053
|
"everyone",
|
|
1696
2054
|
]);
|
|
1697
2055
|
const mentionPattern = /@(\w+)/g;
|
|
@@ -1734,10 +2092,12 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
|
|
|
1734
2092
|
progressStyle: { fg: t.progress, italic: true },
|
|
1735
2093
|
dropdownHighlightStyle: { fg: t.accent },
|
|
1736
2094
|
dropdownStyle: { fg: t.textMuted },
|
|
1737
|
-
footer: concat(tp.accent(" Teammates"), tp.dim(` v${PKG_VERSION}`)),
|
|
2095
|
+
footer: concat(tp.accent(" Teammates"), tp.dim(` v${PKG_VERSION}`), tp.muted(" "), tp.text(this.adapterName)),
|
|
2096
|
+
footerRight: tp.muted("? /help "),
|
|
1738
2097
|
footerStyle: { fg: t.textDim },
|
|
1739
2098
|
});
|
|
1740
|
-
this.defaultFooter = concat(tp.accent(" Teammates"), tp.dim(` v${PKG_VERSION}`));
|
|
2099
|
+
this.defaultFooter = concat(tp.accent(" Teammates"), tp.dim(` v${PKG_VERSION}`), tp.muted(" "), tp.text(this.adapterName));
|
|
2100
|
+
this.defaultFooterRight = tp.muted("? /help ");
|
|
1741
2101
|
// Wire ChatView events for input handling
|
|
1742
2102
|
this.chatView.on("submit", (rawLine) => {
|
|
1743
2103
|
this.handleSubmit(rawLine).catch((err) => {
|
|
@@ -1763,6 +2123,7 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
|
|
|
1763
2123
|
this.escTimer = null;
|
|
1764
2124
|
}
|
|
1765
2125
|
this.chatView.setFooter(this.defaultFooter);
|
|
2126
|
+
this.chatView.setFooterRight(this.defaultFooterRight);
|
|
1766
2127
|
this.refreshView();
|
|
1767
2128
|
}
|
|
1768
2129
|
if (this.ctrlcPending) {
|
|
@@ -1772,6 +2133,7 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
|
|
|
1772
2133
|
this.ctrlcTimer = null;
|
|
1773
2134
|
}
|
|
1774
2135
|
this.chatView.setFooter(this.defaultFooter);
|
|
2136
|
+
this.chatView.setFooterRight(this.defaultFooterRight);
|
|
1775
2137
|
this.refreshView();
|
|
1776
2138
|
}
|
|
1777
2139
|
});
|
|
@@ -1795,22 +2157,21 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
|
|
|
1795
2157
|
}
|
|
1796
2158
|
this.chatView.inputValue = "";
|
|
1797
2159
|
this.chatView.setFooter(this.defaultFooter);
|
|
2160
|
+
this.chatView.setFooterRight(this.defaultFooterRight);
|
|
1798
2161
|
this.pastedTexts.clear();
|
|
1799
2162
|
this.refreshView();
|
|
1800
2163
|
}
|
|
1801
2164
|
else if (this.chatView.inputValue.length > 0) {
|
|
1802
|
-
// First ESC with text — show hint in footer, auto-expire after 2s
|
|
2165
|
+
// First ESC with text — show hint in footer right, auto-expire after 2s
|
|
1803
2166
|
this.escPending = true;
|
|
1804
|
-
|
|
1805
|
-
const hint = "ESC again to clear";
|
|
1806
|
-
const pad = Math.max(0, termW - hint.length - 1);
|
|
1807
|
-
this.chatView.setFooter(concat(tp.dim(" ".repeat(pad)), tp.muted(hint)));
|
|
2167
|
+
this.chatView.setFooterRight(tp.muted("ESC again to clear "));
|
|
1808
2168
|
this.refreshView();
|
|
1809
2169
|
this.escTimer = setTimeout(() => {
|
|
1810
2170
|
this.escTimer = null;
|
|
1811
2171
|
if (this.escPending) {
|
|
1812
2172
|
this.escPending = false;
|
|
1813
2173
|
this.chatView.setFooter(this.defaultFooter);
|
|
2174
|
+
this.chatView.setFooterRight(this.defaultFooterRight);
|
|
1814
2175
|
this.refreshView();
|
|
1815
2176
|
}
|
|
1816
2177
|
}, 2000);
|
|
@@ -1828,29 +2189,31 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
|
|
|
1828
2189
|
this.ctrlcTimer = null;
|
|
1829
2190
|
}
|
|
1830
2191
|
this.chatView.setFooter(this.defaultFooter);
|
|
2192
|
+
this.chatView.setFooterRight(this.defaultFooterRight);
|
|
1831
2193
|
if (this.app)
|
|
1832
2194
|
this.app.stop();
|
|
1833
2195
|
this.orchestrator.shutdown().then(() => process.exit(0));
|
|
1834
2196
|
return;
|
|
1835
2197
|
}
|
|
1836
|
-
// First Ctrl+C — show hint in footer, auto-expire after 2s
|
|
2198
|
+
// First Ctrl+C — show hint in footer right, auto-expire after 2s
|
|
1837
2199
|
this.ctrlcPending = true;
|
|
1838
|
-
|
|
1839
|
-
const hint = "Ctrl+C again to exit";
|
|
1840
|
-
const pad = Math.max(0, termW - hint.length - 1);
|
|
1841
|
-
this.chatView.setFooter(concat(tp.dim(" ".repeat(pad)), tp.muted(hint)));
|
|
2200
|
+
this.chatView.setFooterRight(tp.muted("Ctrl+C again to exit "));
|
|
1842
2201
|
this.refreshView();
|
|
1843
2202
|
this.ctrlcTimer = setTimeout(() => {
|
|
1844
2203
|
this.ctrlcTimer = null;
|
|
1845
2204
|
if (this.ctrlcPending) {
|
|
1846
2205
|
this.ctrlcPending = false;
|
|
1847
2206
|
this.chatView.setFooter(this.defaultFooter);
|
|
2207
|
+
this.chatView.setFooterRight(this.defaultFooterRight);
|
|
1848
2208
|
this.refreshView();
|
|
1849
2209
|
}
|
|
1850
2210
|
}, 2000);
|
|
1851
2211
|
});
|
|
1852
2212
|
this.chatView.on("action", (id) => {
|
|
1853
|
-
if (id
|
|
2213
|
+
if (id.startsWith("copy-cmd:")) {
|
|
2214
|
+
this.doCopy(id.slice("copy-cmd:".length));
|
|
2215
|
+
}
|
|
2216
|
+
else if (id === "copy") {
|
|
1854
2217
|
this.doCopy(this.lastCleanedOutput || undefined);
|
|
1855
2218
|
}
|
|
1856
2219
|
else if (id.startsWith("retro-approve-") ||
|
|
@@ -1899,15 +2262,7 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
|
|
|
1899
2262
|
// Start the banner animation after the first frame renders.
|
|
1900
2263
|
bannerWidget.onDirty = () => this.app?.refresh();
|
|
1901
2264
|
const runPromise = this.app.run();
|
|
1902
|
-
// Hold the banner animation before commands if we need to run the interview
|
|
1903
|
-
if (pendingUserInterview) {
|
|
1904
|
-
bannerWidget.hold();
|
|
1905
|
-
}
|
|
1906
2265
|
bannerWidget.start();
|
|
1907
|
-
// Run user interview inside the ChatView if USER.md needs setup
|
|
1908
|
-
if (pendingUserInterview) {
|
|
1909
|
-
this.startUserInterview(teammatesDir, bannerWidget);
|
|
1910
|
-
}
|
|
1911
2266
|
await runPromise;
|
|
1912
2267
|
}
|
|
1913
2268
|
/**
|
|
@@ -1930,7 +2285,7 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
|
|
|
1930
2285
|
const fileName = trimmed.split(/[/\\]/).pop() || trimmed;
|
|
1931
2286
|
const n = ++this.pasteCounter;
|
|
1932
2287
|
this.pastedTexts.set(n, `[Image: source: ${trimmed}]`);
|
|
1933
|
-
const placeholder = `[Image ${fileName}]`;
|
|
2288
|
+
const placeholder = `[Image ${fileName}] `;
|
|
1934
2289
|
const newVal = current.slice(0, idx) +
|
|
1935
2290
|
placeholder +
|
|
1936
2291
|
current.slice(idx + clean.length);
|
|
@@ -1967,9 +2322,28 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
|
|
|
1967
2322
|
}
|
|
1968
2323
|
/** Handle line submission from ChatView. */
|
|
1969
2324
|
async handleSubmit(rawLine) {
|
|
2325
|
+
// If an inline ask is pending, resolve it instead of normal processing
|
|
2326
|
+
if (this._pendingAsk) {
|
|
2327
|
+
const resolve = this._pendingAsk;
|
|
2328
|
+
this._pendingAsk = null;
|
|
2329
|
+
resolve(rawLine);
|
|
2330
|
+
return;
|
|
2331
|
+
}
|
|
1970
2332
|
this.clearWordwheel();
|
|
1971
2333
|
this.wordwheelItems = [];
|
|
1972
2334
|
this.wordwheelIndex = -1;
|
|
2335
|
+
// Resolve @mentions from the raw input BEFORE paste expansion.
|
|
2336
|
+
// This prevents @mentions inside pasted/expanded text from being picked up.
|
|
2337
|
+
const allNames = this.orchestrator.listTeammates();
|
|
2338
|
+
const preMentionRegex = /@(\S+)/g;
|
|
2339
|
+
let pm;
|
|
2340
|
+
const preMentions = [];
|
|
2341
|
+
while ((pm = preMentionRegex.exec(rawLine)) !== null) {
|
|
2342
|
+
const name = pm[1];
|
|
2343
|
+
if (allNames.includes(name) && !preMentions.includes(name)) {
|
|
2344
|
+
preMentions.push(name);
|
|
2345
|
+
}
|
|
2346
|
+
}
|
|
1973
2347
|
// Expand paste placeholders with actual content
|
|
1974
2348
|
let input = rawLine.replace(/\[Pasted text #(\d+) \+\d+ lines, [\d.]+KB\]\s*/g, (_match, num) => {
|
|
1975
2349
|
const n = parseInt(num, 10);
|
|
@@ -2050,10 +2424,11 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
|
|
|
2050
2424
|
this.refreshView();
|
|
2051
2425
|
return;
|
|
2052
2426
|
}
|
|
2053
|
-
// Everything else gets queued
|
|
2054
|
-
|
|
2427
|
+
// Everything else gets queued.
|
|
2428
|
+
// Pass pre-resolved mentions so @mentions inside expanded paste text are ignored.
|
|
2429
|
+
this.conversationHistory.push({ role: this.selfName, text: input });
|
|
2055
2430
|
this.printUserMessage(input);
|
|
2056
|
-
this.queueTask(input);
|
|
2431
|
+
this.queueTask(input, preMentions);
|
|
2057
2432
|
this.refreshView();
|
|
2058
2433
|
}
|
|
2059
2434
|
printBanner(teammates) {
|
|
@@ -2061,7 +2436,7 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
|
|
|
2061
2436
|
const termWidth = process.stdout.columns || 100;
|
|
2062
2437
|
this.feedLine();
|
|
2063
2438
|
this.feedLine(concat(tp.bold(" Teammates"), tp.muted(` v${PKG_VERSION}`)));
|
|
2064
|
-
this.feedLine(concat(tp.text(`
|
|
2439
|
+
this.feedLine(concat(tp.text(` @${this.selfName}`), tp.muted(` · ${teammates.length} teammate${teammates.length === 1 ? "" : "s"}`)));
|
|
2065
2440
|
this.feedLine(` ${process.cwd()}`);
|
|
2066
2441
|
// Service status rows
|
|
2067
2442
|
for (const svc of this.serviceStatuses) {
|
|
@@ -2077,12 +2452,15 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
|
|
|
2077
2452
|
: `missing — /configure ${svc.name.toLowerCase()}`;
|
|
2078
2453
|
this.feedLine(concat(tp.text(" "), color(icon), color(svc.name), tp.muted(` ${label}`)));
|
|
2079
2454
|
}
|
|
2080
|
-
// Roster
|
|
2455
|
+
// Roster (with presence indicators)
|
|
2081
2456
|
this.feedLine();
|
|
2457
|
+
const statuses = this.orchestrator.getAllStatuses();
|
|
2082
2458
|
for (const name of teammates) {
|
|
2083
2459
|
const t = registry.get(name);
|
|
2084
2460
|
if (t) {
|
|
2085
|
-
|
|
2461
|
+
const p = statuses.get(name)?.presence ?? "online";
|
|
2462
|
+
const dot = p === "online" ? tp.success("●") : p === "reachable" ? tp.warning("●") : tp.error("●");
|
|
2463
|
+
this.feedLine(concat(tp.text(" "), dot, tp.accent(` @${name.padEnd(14)}`), tp.muted(t.role)));
|
|
2086
2464
|
}
|
|
2087
2465
|
}
|
|
2088
2466
|
this.feedLine();
|
|
@@ -2202,43 +2580,21 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
|
|
|
2202
2580
|
this.feedLine(tp.warning(" GitHub CLI is not installed."));
|
|
2203
2581
|
this.feedLine();
|
|
2204
2582
|
const plat = process.platform;
|
|
2205
|
-
|
|
2206
|
-
let installLabel;
|
|
2583
|
+
this.feedLine(tp.text(" Run this in another terminal:"));
|
|
2207
2584
|
if (plat === "win32") {
|
|
2208
|
-
|
|
2209
|
-
installLabel = "winget install --id GitHub.cli";
|
|
2585
|
+
this.feedCommand("winget install --id GitHub.cli");
|
|
2210
2586
|
}
|
|
2211
2587
|
else if (plat === "darwin") {
|
|
2212
|
-
|
|
2213
|
-
installLabel = "brew install gh";
|
|
2588
|
+
this.feedCommand("brew install gh");
|
|
2214
2589
|
}
|
|
2215
2590
|
else {
|
|
2216
|
-
|
|
2217
|
-
|
|
2591
|
+
this.feedCommand("sudo apt install gh");
|
|
2592
|
+
this.feedLine(tp.muted(" (or see https://cli.github.com)"));
|
|
2218
2593
|
}
|
|
2219
|
-
this.feedLine(tp.text(` Install: ${installLabel}`));
|
|
2220
2594
|
this.feedLine();
|
|
2221
|
-
|
|
2222
|
-
const answer = await this.askInput("Run install command? [Y/n] ");
|
|
2595
|
+
const answer = await this.askInline("Press Enter when done (or n to skip)");
|
|
2223
2596
|
if (answer.toLowerCase() === "n") {
|
|
2224
|
-
this.feedLine(tp.muted(" Skipped.
|
|
2225
|
-
this.refreshView();
|
|
2226
|
-
return;
|
|
2227
|
-
}
|
|
2228
|
-
// Spawn install in a visible subprocess
|
|
2229
|
-
this.feedLine(tp.muted(` Running: ${installCmd}`));
|
|
2230
|
-
this.refreshView();
|
|
2231
|
-
const installSuccess = await new Promise((res) => {
|
|
2232
|
-
const parts = installCmd.split(" ");
|
|
2233
|
-
const child = spawn(parts[0], parts.slice(1), {
|
|
2234
|
-
stdio: "inherit",
|
|
2235
|
-
shell: true,
|
|
2236
|
-
});
|
|
2237
|
-
child.on("error", () => res(false));
|
|
2238
|
-
child.on("exit", (code) => res(code === 0));
|
|
2239
|
-
});
|
|
2240
|
-
if (!installSuccess) {
|
|
2241
|
-
this.feedLine(tp.error(" Install failed. Please install manually from https://cli.github.com"));
|
|
2597
|
+
this.feedLine(tp.muted(" Skipped. Run /configure github when ready."));
|
|
2242
2598
|
this.refreshView();
|
|
2243
2599
|
return;
|
|
2244
2600
|
}
|
|
@@ -2249,7 +2605,7 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
|
|
|
2249
2605
|
this.feedLine(tp.success(" ✓ GitHub CLI installed"));
|
|
2250
2606
|
}
|
|
2251
2607
|
catch {
|
|
2252
|
-
this.feedLine(tp.error(" GitHub CLI still not found
|
|
2608
|
+
this.feedLine(tp.error(" GitHub CLI still not found. You may need to restart your terminal."));
|
|
2253
2609
|
this.refreshView();
|
|
2254
2610
|
return;
|
|
2255
2611
|
}
|
|
@@ -2268,31 +2624,19 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
|
|
|
2268
2624
|
// not authenticated
|
|
2269
2625
|
}
|
|
2270
2626
|
if (!authed) {
|
|
2271
|
-
this.feedLine(tp.muted(" Authentication needed — this will open your browser for GitHub OAuth."));
|
|
2272
2627
|
this.feedLine();
|
|
2273
|
-
|
|
2628
|
+
this.feedLine(tp.text(" Run this in another terminal to authenticate:"));
|
|
2629
|
+
this.feedCommand("gh auth login --web --git-protocol https");
|
|
2630
|
+
this.feedLine();
|
|
2631
|
+
this.feedLine(tp.muted(" This will open your browser for GitHub OAuth."));
|
|
2632
|
+
this.feedLine();
|
|
2633
|
+
const answer = await this.askInline("Press Enter when done (or n to skip)");
|
|
2274
2634
|
if (answer.toLowerCase() === "n") {
|
|
2275
2635
|
this.feedLine(tp.muted(" Skipped. Run /configure github when ready."));
|
|
2276
2636
|
this.refreshView();
|
|
2277
2637
|
this.updateServiceStatus("GitHub", "not-configured");
|
|
2278
2638
|
return;
|
|
2279
2639
|
}
|
|
2280
|
-
this.feedLine(tp.muted(" Starting auth flow..."));
|
|
2281
|
-
this.refreshView();
|
|
2282
|
-
const authSuccess = await new Promise((res) => {
|
|
2283
|
-
const child = spawn("gh", ["auth", "login", "--web", "--git-protocol", "https"], {
|
|
2284
|
-
stdio: "inherit",
|
|
2285
|
-
shell: true,
|
|
2286
|
-
});
|
|
2287
|
-
child.on("error", () => res(false));
|
|
2288
|
-
child.on("exit", (code) => res(code === 0));
|
|
2289
|
-
});
|
|
2290
|
-
if (!authSuccess) {
|
|
2291
|
-
this.feedLine(tp.error(" Authentication failed. Try again with /configure github"));
|
|
2292
|
-
this.refreshView();
|
|
2293
|
-
this.updateServiceStatus("GitHub", "not-configured");
|
|
2294
|
-
return;
|
|
2295
|
-
}
|
|
2296
2640
|
// Verify
|
|
2297
2641
|
try {
|
|
2298
2642
|
execSync("gh auth status", { stdio: "pipe" });
|
|
@@ -2320,8 +2664,13 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
|
|
|
2320
2664
|
}
|
|
2321
2665
|
updateServiceStatus(name, status) {
|
|
2322
2666
|
const svc = this.serviceStatuses.find((s) => s.name === name);
|
|
2323
|
-
if (svc)
|
|
2667
|
+
if (svc) {
|
|
2324
2668
|
svc.status = status;
|
|
2669
|
+
if (this.banner) {
|
|
2670
|
+
this.banner.updateServices(this.serviceStatuses);
|
|
2671
|
+
this.refreshView();
|
|
2672
|
+
}
|
|
2673
|
+
}
|
|
2325
2674
|
}
|
|
2326
2675
|
registerCommands() {
|
|
2327
2676
|
const cmds = [
|
|
@@ -2471,6 +2820,10 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
|
|
|
2471
2820
|
if (this.activeTasks.size === 0) {
|
|
2472
2821
|
this.stopStatusAnimation();
|
|
2473
2822
|
}
|
|
2823
|
+
// Suppress display for internal summarization tasks
|
|
2824
|
+
const activeEntry = this.agentActive.get(event.result.teammate);
|
|
2825
|
+
if (activeEntry?.type === "summarize")
|
|
2826
|
+
break;
|
|
2474
2827
|
if (!this.chatView)
|
|
2475
2828
|
this.input.deactivateAndErase();
|
|
2476
2829
|
const raw = event.result.rawOutput ?? "";
|
|
@@ -2484,17 +2837,10 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
|
|
|
2484
2837
|
const sizeKB = cleaned ? Buffer.byteLength(cleaned, "utf-8") / 1024 : 0;
|
|
2485
2838
|
// Header: "teammate: subject"
|
|
2486
2839
|
const subject = event.result.summary || "Task completed";
|
|
2487
|
-
this.
|
|
2840
|
+
const displayTeammate = event.result.teammate === this.adapterName ? this.selfName : event.result.teammate;
|
|
2841
|
+
this.feedLine(concat(tp.accent(`${displayTeammate}: `), tp.text(subject)));
|
|
2488
2842
|
this.lastCleanedOutput = cleaned;
|
|
2489
|
-
if (
|
|
2490
|
-
const tmpFile = join(tmpdir(), `teammates-${event.result.teammate}-${Date.now()}.md`);
|
|
2491
|
-
writeFileSync(tmpFile, cleaned, "utf-8");
|
|
2492
|
-
this.feedLine(tp.muted(` ${"─".repeat(40)}`));
|
|
2493
|
-
this.feedLine(tp.warning(` ⚠ Response is ${sizeKB.toFixed(1)}KB — saved to temp file:`));
|
|
2494
|
-
this.feedLine(tp.muted(` ${tmpFile}`));
|
|
2495
|
-
this.feedLine(tp.muted(` ${"─".repeat(40)}`));
|
|
2496
|
-
}
|
|
2497
|
-
else if (cleaned) {
|
|
2843
|
+
if (cleaned) {
|
|
2498
2844
|
this.feedMarkdown(cleaned);
|
|
2499
2845
|
}
|
|
2500
2846
|
else {
|
|
@@ -2562,7 +2908,8 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
|
|
|
2562
2908
|
this.stopStatusAnimation();
|
|
2563
2909
|
if (!this.chatView)
|
|
2564
2910
|
this.input.deactivateAndErase();
|
|
2565
|
-
this.
|
|
2911
|
+
const displayErr = event.teammate === this.adapterName ? this.selfName : event.teammate;
|
|
2912
|
+
this.feedLine(tp.error(` ✖ ${displayErr}: ${event.error}`));
|
|
2566
2913
|
this.showPrompt();
|
|
2567
2914
|
break;
|
|
2568
2915
|
}
|
|
@@ -2573,16 +2920,36 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
|
|
|
2573
2920
|
this.feedLine();
|
|
2574
2921
|
this.feedLine(tp.bold(" Status"));
|
|
2575
2922
|
this.feedLine(tp.muted(` ${"─".repeat(50)}`));
|
|
2923
|
+
// Show user avatar first if present
|
|
2924
|
+
if (this.userAlias) {
|
|
2925
|
+
const userStatus = statuses.get(this.userAlias);
|
|
2926
|
+
if (userStatus) {
|
|
2927
|
+
this.feedLine(concat(tp.success("●"), tp.accent(` @${this.userAlias}`), tp.muted(" (you)")));
|
|
2928
|
+
const t = registry.get(this.userAlias);
|
|
2929
|
+
if (t)
|
|
2930
|
+
this.feedLine(tp.muted(` ${t.role}`));
|
|
2931
|
+
this.feedLine();
|
|
2932
|
+
}
|
|
2933
|
+
}
|
|
2576
2934
|
for (const [name, status] of statuses) {
|
|
2935
|
+
// Skip the user avatar (shown above) and adapter fallback (not addressable)
|
|
2936
|
+
if (name === this.adapterName || name === this.userAlias)
|
|
2937
|
+
continue;
|
|
2577
2938
|
const t = registry.get(name);
|
|
2578
2939
|
const active = this.agentActive.get(name);
|
|
2579
2940
|
const queued = this.taskQueue.filter((e) => e.teammate === name);
|
|
2941
|
+
// Presence indicator: ● green=online, ● red=offline, ● yellow=reachable
|
|
2942
|
+
const presenceIcon = status.presence === "online"
|
|
2943
|
+
? tp.success("●")
|
|
2944
|
+
: status.presence === "reachable"
|
|
2945
|
+
? tp.warning("●")
|
|
2946
|
+
: tp.error("●");
|
|
2580
2947
|
// Teammate name + state
|
|
2581
2948
|
const stateLabel = active ? "working" : status.state;
|
|
2582
2949
|
const stateColor = stateLabel === "working"
|
|
2583
2950
|
? tp.info(` (${stateLabel})`)
|
|
2584
2951
|
: tp.muted(` (${stateLabel})`);
|
|
2585
|
-
this.feedLine(concat(tp.accent(`
|
|
2952
|
+
this.feedLine(concat(presenceIcon, tp.accent(` @${name}`), stateColor));
|
|
2586
2953
|
// Role
|
|
2587
2954
|
if (t) {
|
|
2588
2955
|
this.feedLine(tp.muted(` ${t.role}`));
|
|
@@ -2620,7 +2987,7 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
|
|
|
2620
2987
|
// Pick all teammates with debug files, queue one analysis per teammate
|
|
2621
2988
|
const names = [];
|
|
2622
2989
|
for (const [name] of this.lastDebugFiles) {
|
|
2623
|
-
if (name !== this.
|
|
2990
|
+
if (name !== this.selfName)
|
|
2624
2991
|
names.push(name);
|
|
2625
2992
|
}
|
|
2626
2993
|
if (names.length === 0) {
|
|
@@ -2685,7 +3052,7 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
|
|
|
2685
3052
|
this.refreshView();
|
|
2686
3053
|
this.taskQueue.push({
|
|
2687
3054
|
type: "debug",
|
|
2688
|
-
teammate: this.
|
|
3055
|
+
teammate: this.selfName,
|
|
2689
3056
|
task: analysisPrompt,
|
|
2690
3057
|
});
|
|
2691
3058
|
this.kickDrain();
|
|
@@ -2703,7 +3070,8 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
|
|
|
2703
3070
|
return;
|
|
2704
3071
|
}
|
|
2705
3072
|
const removed = this.taskQueue.splice(n - 1, 1)[0];
|
|
2706
|
-
|
|
3073
|
+
const cancelDisplay = removed.teammate === this.adapterName ? this.selfName : removed.teammate;
|
|
3074
|
+
this.feedLine(concat(tp.muted(" Cancelled: "), tp.accent(`@${cancelDisplay}`), tp.muted(" — "), tp.text(removed.task.slice(0, 60))));
|
|
2707
3075
|
this.refreshView();
|
|
2708
3076
|
}
|
|
2709
3077
|
/** Drain tasks for a single agent — runs in parallel with other agents. */
|
|
@@ -2719,6 +3087,20 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
|
|
|
2719
3087
|
if (entry.type === "compact") {
|
|
2720
3088
|
await this.runCompact(entry.teammate);
|
|
2721
3089
|
}
|
|
3090
|
+
else if (entry.type === "summarize") {
|
|
3091
|
+
// Internal housekeeping — summarize older conversation history
|
|
3092
|
+
const result = await this.orchestrator.assign({
|
|
3093
|
+
teammate: entry.teammate,
|
|
3094
|
+
task: entry.task,
|
|
3095
|
+
});
|
|
3096
|
+
// Extract the summary from the agent's output (strip protocol artifacts)
|
|
3097
|
+
const raw = result.rawOutput ?? "";
|
|
3098
|
+
this.conversationSummary = raw
|
|
3099
|
+
.replace(/^TO:\s*\S+\s*\n/im, "")
|
|
3100
|
+
.replace(/^#\s+.+\n*/m, "")
|
|
3101
|
+
.replace(/```json\s*\n\s*\{[\s\S]*?\}\s*\n\s*```\s*$/g, "")
|
|
3102
|
+
.trim();
|
|
3103
|
+
}
|
|
2722
3104
|
else {
|
|
2723
3105
|
// btw and debug tasks skip conversation context (not part of main thread)
|
|
2724
3106
|
const extraContext = entry.type === "btw" || entry.type === "debug"
|
|
@@ -2736,6 +3118,8 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
|
|
|
2736
3118
|
// btw and debug results are not stored in conversation history
|
|
2737
3119
|
if (entry.type !== "btw" && entry.type !== "debug") {
|
|
2738
3120
|
this.storeResult(result);
|
|
3121
|
+
// Check if older history needs summarizing
|
|
3122
|
+
this.maybeQueueSummarization();
|
|
2739
3123
|
}
|
|
2740
3124
|
if (entry.type === "retro") {
|
|
2741
3125
|
this.handleRetroResult(result);
|
|
@@ -2750,7 +3134,8 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
|
|
|
2750
3134
|
if (this.activeTasks.size === 0)
|
|
2751
3135
|
this.stopStatusAnimation();
|
|
2752
3136
|
const msg = err?.message ?? String(err);
|
|
2753
|
-
this.
|
|
3137
|
+
const displayAgent = agent === this.adapterName ? this.selfName : agent;
|
|
3138
|
+
this.feedLine(tp.error(` ✖ @${displayAgent}: ${msg}`));
|
|
2754
3139
|
this.refreshView();
|
|
2755
3140
|
}
|
|
2756
3141
|
this.agentActive.delete(agent);
|
|
@@ -2885,11 +3270,11 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
|
|
|
2885
3270
|
// Copy framework files so the agent has TEMPLATE.md etc. available
|
|
2886
3271
|
await copyTemplateFiles(teammatesDir);
|
|
2887
3272
|
// Queue a single adaptation task that handles all teammates
|
|
2888
|
-
this.feedLine(tp.muted(
|
|
3273
|
+
this.feedLine(tp.muted(" Queuing agent to scan this project and adapt the team..."));
|
|
2889
3274
|
const prompt = await buildImportAdaptationPrompt(teammatesDir, allTeammates, sourceDir);
|
|
2890
3275
|
this.taskQueue.push({
|
|
2891
3276
|
type: "agent",
|
|
2892
|
-
teammate: this.
|
|
3277
|
+
teammate: this.selfName,
|
|
2893
3278
|
task: prompt,
|
|
2894
3279
|
});
|
|
2895
3280
|
this.kickDrain();
|
|
@@ -2920,6 +3305,7 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
|
|
|
2920
3305
|
}
|
|
2921
3306
|
async cmdClear() {
|
|
2922
3307
|
this.conversationHistory.length = 0;
|
|
3308
|
+
this.conversationSummary = "";
|
|
2923
3309
|
this.lastResult = null;
|
|
2924
3310
|
this.lastResults.clear();
|
|
2925
3311
|
this.taskQueue.length = 0;
|
|
@@ -2948,9 +3334,11 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
|
|
|
2948
3334
|
return;
|
|
2949
3335
|
const registry = this.orchestrator.getRegistry();
|
|
2950
3336
|
// Update adapter roster so prompts include the new teammates
|
|
3337
|
+
// Exclude the user avatar and adapter fallback — neither is an addressable teammate
|
|
2951
3338
|
if ("roster" in this.adapter) {
|
|
2952
3339
|
this.adapter.roster = this.orchestrator
|
|
2953
3340
|
.listTeammates()
|
|
3341
|
+
.filter((n) => n !== this.adapterName && n !== this.userAlias)
|
|
2954
3342
|
.map((name) => {
|
|
2955
3343
|
const t = registry.get(name);
|
|
2956
3344
|
return { name: t.name, role: t.role, ownership: t.ownership };
|
|
@@ -2972,7 +3360,7 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
|
|
|
2972
3360
|
const arg = argsStr.trim().replace(/^@/, "");
|
|
2973
3361
|
const allTeammates = this.orchestrator
|
|
2974
3362
|
.listTeammates()
|
|
2975
|
-
.filter((n) => n !== this.adapterName);
|
|
3363
|
+
.filter((n) => n !== this.selfName && n !== this.adapterName);
|
|
2976
3364
|
const names = !arg || arg === "everyone" ? allTeammates : [arg];
|
|
2977
3365
|
// Validate all names first
|
|
2978
3366
|
const valid = [];
|
|
@@ -3043,7 +3431,7 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
|
|
|
3043
3431
|
if (spinner)
|
|
3044
3432
|
spinner.succeed(`${name}: ${parts.join(", ")}`);
|
|
3045
3433
|
if (this.chatView)
|
|
3046
|
-
this.feedLine(tp.success(` ✔
|
|
3434
|
+
this.feedLine(tp.success(` ✔ ${name}: ${parts.join(", ")}`));
|
|
3047
3435
|
}
|
|
3048
3436
|
if (this.chatView)
|
|
3049
3437
|
this.chatView.setProgress(null);
|
|
@@ -3065,7 +3453,7 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
|
|
|
3065
3453
|
syncSpinner.succeed(`${name}: index synced`);
|
|
3066
3454
|
if (this.chatView) {
|
|
3067
3455
|
this.chatView.setProgress(null);
|
|
3068
|
-
this.feedLine(tp.success(` ✔
|
|
3456
|
+
this.feedLine(tp.success(` ✔ ${name}: index synced`));
|
|
3069
3457
|
}
|
|
3070
3458
|
}
|
|
3071
3459
|
catch {
|
|
@@ -3095,7 +3483,7 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
|
|
|
3095
3483
|
spinner.fail(`${name}: ${msg}`);
|
|
3096
3484
|
if (this.chatView) {
|
|
3097
3485
|
this.chatView.setProgress(null);
|
|
3098
|
-
this.feedLine(tp.error(` ✖
|
|
3486
|
+
this.feedLine(tp.error(` ✖ ${name}: ${msg}`));
|
|
3099
3487
|
}
|
|
3100
3488
|
}
|
|
3101
3489
|
this.refreshView();
|
|
@@ -3105,7 +3493,7 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
|
|
|
3105
3493
|
// Resolve target list
|
|
3106
3494
|
const allTeammates = this.orchestrator
|
|
3107
3495
|
.listTeammates()
|
|
3108
|
-
.filter((n) => n !== this.adapterName);
|
|
3496
|
+
.filter((n) => n !== this.selfName && n !== this.adapterName);
|
|
3109
3497
|
let targets;
|
|
3110
3498
|
if (arg === "everyone") {
|
|
3111
3499
|
targets = allTeammates;
|
|
@@ -3218,7 +3606,7 @@ Issues that can't be resolved unilaterally — they need input from other teamma
|
|
|
3218
3606
|
}
|
|
3219
3607
|
const teammates = this.orchestrator
|
|
3220
3608
|
.listTeammates()
|
|
3221
|
-
.filter((n) => n !== this.adapterName);
|
|
3609
|
+
.filter((n) => n !== this.selfName && n !== this.adapterName);
|
|
3222
3610
|
if (teammates.length === 0)
|
|
3223
3611
|
return;
|
|
3224
3612
|
// 1. Check each teammate for stale daily logs (older than 7 days)
|
|
@@ -3303,7 +3691,7 @@ Issues that can't be resolved unilaterally — they need input from other teamma
|
|
|
3303
3691
|
child.stdin?.end();
|
|
3304
3692
|
// Show brief "Copied" message in the progress area
|
|
3305
3693
|
if (this.chatView) {
|
|
3306
|
-
this.chatView.setProgress(concat(tp.success("✔
|
|
3694
|
+
this.chatView.setProgress(concat(tp.success("✔ "), tp.muted("Copied to clipboard")));
|
|
3307
3695
|
this.refreshView();
|
|
3308
3696
|
setTimeout(() => {
|
|
3309
3697
|
this.chatView.setProgress(null);
|
|
@@ -3313,7 +3701,7 @@ Issues that can't be resolved unilaterally — they need input from other teamma
|
|
|
3313
3701
|
}
|
|
3314
3702
|
catch {
|
|
3315
3703
|
if (this.chatView) {
|
|
3316
|
-
this.chatView.setProgress(concat(tp.error("✖
|
|
3704
|
+
this.chatView.setProgress(concat(tp.error("✖ "), tp.muted("Failed to copy")));
|
|
3317
3705
|
this.refreshView();
|
|
3318
3706
|
setTimeout(() => {
|
|
3319
3707
|
this.chatView.setProgress(null);
|
|
@@ -3322,6 +3710,19 @@ Issues that can't be resolved unilaterally — they need input from other teamma
|
|
|
3322
3710
|
}
|
|
3323
3711
|
}
|
|
3324
3712
|
}
|
|
3713
|
+
/**
|
|
3714
|
+
* Feed a command line with a clickable [copy] button.
|
|
3715
|
+
* Renders as: ` command text [copy]`
|
|
3716
|
+
*/
|
|
3717
|
+
feedCommand(command) {
|
|
3718
|
+
if (!this.chatView) {
|
|
3719
|
+
this.feedLine(tp.accent(` ${command}`));
|
|
3720
|
+
return;
|
|
3721
|
+
}
|
|
3722
|
+
const normal = concat(tp.accent(` ${command} `), tp.muted("[copy]"));
|
|
3723
|
+
const hover = concat(tp.accent(` ${command} `), tp.accent("[copy]"));
|
|
3724
|
+
this.chatView.appendAction(`copy-cmd:${command}`, normal, hover);
|
|
3725
|
+
}
|
|
3325
3726
|
async cmdHelp() {
|
|
3326
3727
|
this.feedLine();
|
|
3327
3728
|
this.feedLine(tp.bold(" Commands"));
|
|
@@ -3373,10 +3774,10 @@ Issues that can't be resolved unilaterally — they need input from other teamma
|
|
|
3373
3774
|
this.refreshView();
|
|
3374
3775
|
return;
|
|
3375
3776
|
}
|
|
3376
|
-
// Has args — queue a task to
|
|
3777
|
+
// Has args — queue a task to apply the change
|
|
3377
3778
|
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.`;
|
|
3378
|
-
this.taskQueue.push({ type: "agent", teammate: this.
|
|
3379
|
-
this.feedLine(concat(tp.muted(" Queued USER.md update → "), tp.accent(`@${this.
|
|
3779
|
+
this.taskQueue.push({ type: "agent", teammate: this.selfName, task });
|
|
3780
|
+
this.feedLine(concat(tp.muted(" Queued USER.md update → "), tp.accent(`@${this.selfName}`)));
|
|
3380
3781
|
this.feedLine();
|
|
3381
3782
|
this.refreshView();
|
|
3382
3783
|
this.kickDrain();
|
|
@@ -3390,10 +3791,10 @@ Issues that can't be resolved unilaterally — they need input from other teamma
|
|
|
3390
3791
|
}
|
|
3391
3792
|
this.taskQueue.push({
|
|
3392
3793
|
type: "btw",
|
|
3393
|
-
teammate: this.
|
|
3794
|
+
teammate: this.selfName,
|
|
3394
3795
|
task: question,
|
|
3395
3796
|
});
|
|
3396
|
-
this.feedLine(concat(tp.muted(" Side question → "), tp.accent(`@${this.
|
|
3797
|
+
this.feedLine(concat(tp.muted(" Side question → "), tp.accent(`@${this.selfName}`)));
|
|
3397
3798
|
this.feedLine();
|
|
3398
3799
|
this.refreshView();
|
|
3399
3800
|
this.kickDrain();
|
|
@@ -3422,9 +3823,9 @@ Issues that can't be resolved unilaterally — they need input from other teamma
|
|
|
3422
3823
|
row("textDim", t.textDim, "─── separator ───");
|
|
3423
3824
|
this.feedLine();
|
|
3424
3825
|
// Status
|
|
3425
|
-
row("success", t.success, "✔
|
|
3426
|
-
row("warning", t.warning, "⚠
|
|
3427
|
-
row("error", t.error, "✖
|
|
3826
|
+
row("success", t.success, "✔ Task completed");
|
|
3827
|
+
row("warning", t.warning, "⚠ Pending handoff");
|
|
3828
|
+
row("error", t.error, "✖ Something went wrong");
|
|
3428
3829
|
row("info", t.info, "⠋ Working on task...");
|
|
3429
3830
|
this.feedLine();
|
|
3430
3831
|
// Interactive
|
|
@@ -3492,9 +3893,9 @@ Issues that can't be resolved unilaterally — they need input from other teamma
|
|
|
3492
3893
|
"",
|
|
3493
3894
|
"| Language | Status |",
|
|
3494
3895
|
"|------------|---------|",
|
|
3495
|
-
"| JavaScript | ✔
|
|
3496
|
-
"| Python | ✔
|
|
3497
|
-
"| C# | ✔
|
|
3896
|
+
"| JavaScript | ✔ Ready |",
|
|
3897
|
+
"| Python | ✔ Ready |",
|
|
3898
|
+
"| C# | ✔ Ready |",
|
|
3498
3899
|
"",
|
|
3499
3900
|
"---",
|
|
3500
3901
|
].join("\n");
|