@teammates/cli 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +36 -4
- package/dist/adapter.d.ts +13 -3
- package/dist/adapter.js +48 -11
- package/dist/adapter.test.js +1 -0
- package/dist/adapters/cli-proxy.d.ts +3 -1
- package/dist/adapters/cli-proxy.js +19 -4
- package/dist/adapters/copilot.d.ts +3 -1
- package/dist/adapters/copilot.js +16 -2
- package/dist/adapters/echo.d.ts +3 -1
- package/dist/adapters/echo.js +2 -2
- 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 +914 -346
- package/dist/console/startup.d.ts +2 -1
- package/dist/console/startup.js +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.js +1 -0
- package/dist/orchestrator.d.ts +2 -0
- package/dist/orchestrator.js +18 -13
- package/dist/orchestrator.test.js +2 -1
- package/dist/personas.d.ts +42 -0
- package/dist/personas.js +108 -0
- package/dist/registry.js +7 -0
- package/dist/registry.test.js +1 -0
- package/dist/types.d.ts +8 -0
- package/package.json +4 -3
- package/personas/architect.md +91 -0
- package/personas/backend.md +93 -0
- package/personas/data-engineer.md +92 -0
- package/personas/designer.md +92 -0
- package/personas/devops.md +93 -0
- package/personas/frontend.md +94 -0
- package/personas/ml-ai.md +96 -0
- package/personas/mobile.md +93 -0
- package/personas/performance.md +92 -0
- package/personas/pm.md +89 -0
- package/personas/qa.md +92 -0
- package/personas/security.md +92 -0
- package/personas/sre.md +93 -0
- package/personas/swe.md +88 -0
- package/personas/tech-writer.md +93 -0
package/README.md
CHANGED
|
@@ -164,19 +164,51 @@ class MyAdapter implements AgentAdapter {
|
|
|
164
164
|
|
|
165
165
|
Or add a preset to `cli-proxy.ts` for any CLI agent that accepts a prompt and runs to completion.
|
|
166
166
|
|
|
167
|
+
## Startup Lifecycle
|
|
168
|
+
|
|
169
|
+
The CLI startup runs in two phases:
|
|
170
|
+
|
|
171
|
+
**Phase 1 — Pre-TUI (console I/O)**
|
|
172
|
+
1. **User profile setup** — Prompts for alias (required), name, role, experience, preferences, context. Creates `USER.md` and a user avatar folder at `.teammates/<alias>/` with `SOUL.md` (`**Type:** human`).
|
|
173
|
+
2. **Team onboarding** (if no `.teammates/` exists) — Offers New team / Import / Solo / Exit. Onboarding agents run non-interactively to completion.
|
|
174
|
+
3. **Orchestrator init** — Loads existing teammates from `.teammates/`, registers user avatar with `type: "human"` and `presence: "online"`.
|
|
175
|
+
|
|
176
|
+
**Phase 2 — TUI (Consolonia)**
|
|
177
|
+
4. Animated startup banner with roster
|
|
178
|
+
5. REPL starts — routing, slash commands, handoff approval
|
|
179
|
+
|
|
180
|
+
All user interaction during Phase 1 uses plain console I/O (readline + ora spinners), avoiding mouse tracking issues that would occur inside the TUI.
|
|
181
|
+
|
|
182
|
+
## Personas
|
|
183
|
+
|
|
184
|
+
The CLI ships with 15 built-in persona templates that serve as starting points when creating new teammates. Each persona file (`personas/*.md`) contains YAML frontmatter (name, default alias, tier, description) and a complete SOUL.md scaffold pre-filled with the role's identity, principles, quality bar, and ownership structure.
|
|
185
|
+
|
|
186
|
+
### Tiers
|
|
187
|
+
|
|
188
|
+
| Tier | Personas |
|
|
189
|
+
|---|---|
|
|
190
|
+
| **1 — Core** | PM (`scribe`), SWE (`beacon`), DevOps (`pipeline`), QA (`sentinel`) |
|
|
191
|
+
| **2 — Specialist** | Security (`shield`), Designer (`canvas`), Tech Writer (`quill`), Data Engineer (`forge`), SRE (`watchtower`), Architect (`blueprint`) |
|
|
192
|
+
| **3 — Niche** | Frontend (`pixel`), Backend (`engine`), Mobile (`orbit`), ML/AI (`neuron`), Performance (`tempo`) |
|
|
193
|
+
|
|
194
|
+
During onboarding, the CLI uses these personas to scaffold teammates. The user picks roles, optionally renames them, and the persona's SOUL.md body becomes the starting template — project-specific sections (commands, file patterns, technologies) are filled in by the onboarding agent.
|
|
195
|
+
|
|
167
196
|
## Architecture
|
|
168
197
|
|
|
169
198
|
```
|
|
170
199
|
cli/src/
|
|
171
|
-
cli.ts # Entry point, REPL, slash commands, wordwheel UI
|
|
172
|
-
orchestrator.ts # Task routing, session management
|
|
200
|
+
cli.ts # Entry point, startup lifecycle, REPL, slash commands, wordwheel UI
|
|
201
|
+
orchestrator.ts # Task routing, session management, presence tracking
|
|
173
202
|
adapter.ts # AgentAdapter interface, prompt builder, handoff formatting
|
|
174
|
-
registry.ts # Discovers teammates from .teammates/, loads SOUL.md + memory
|
|
175
|
-
|
|
203
|
+
registry.ts # Discovers teammates from .teammates/, loads SOUL.md + memory, type detection
|
|
204
|
+
personas.ts # Persona loader — reads and parses bundled persona templates
|
|
205
|
+
types.ts # Core types (TeammateConfig, TaskResult, HandoffEnvelope, TeammateType, PresenceState)
|
|
206
|
+
onboard.ts # Template copying, team import, onboarding/adaptation prompts
|
|
176
207
|
dropdown.ts # Terminal dropdown/wordwheel widget
|
|
177
208
|
adapters/
|
|
178
209
|
cli-proxy.ts # Generic subprocess adapter with agent presets
|
|
179
210
|
echo.ts # Test adapter (no-op)
|
|
211
|
+
cli/personas/ # 15 persona template files (pm.md, swe.md, devops.md, etc.)
|
|
180
212
|
```
|
|
181
213
|
|
|
182
214
|
### Output Protocol
|
package/dist/adapter.d.ts
CHANGED
|
@@ -19,7 +19,9 @@ export interface AgentAdapter {
|
|
|
19
19
|
* Send a task prompt to a teammate's session.
|
|
20
20
|
* The adapter hydrates the prompt with identity, memory, and handoff context.
|
|
21
21
|
*/
|
|
22
|
-
executeTask(sessionId: string, teammate: TeammateConfig, prompt: string
|
|
22
|
+
executeTask(sessionId: string, teammate: TeammateConfig, prompt: string, options?: {
|
|
23
|
+
raw?: boolean;
|
|
24
|
+
}): Promise<TaskResult>;
|
|
23
25
|
/**
|
|
24
26
|
* Resume an existing session (for agents that support continuity).
|
|
25
27
|
* Falls back to startSession if not implemented.
|
|
@@ -58,10 +60,16 @@ export interface RecallContext {
|
|
|
58
60
|
}
|
|
59
61
|
/**
|
|
60
62
|
* Query the recall index for context relevant to the task prompt.
|
|
61
|
-
*
|
|
63
|
+
*
|
|
64
|
+
* Uses a multi-query strategy (Pass 1 from the recall query architecture):
|
|
65
|
+
* 1. Keyword extraction — generates focused queries from the task prompt
|
|
66
|
+
* 2. Conversation-aware queries — extracts recent topic from conversation history
|
|
67
|
+
* 3. Memory index scanning — text-matches frontmatter against the task prompt
|
|
68
|
+
* 4. Multi-query fusion — fires 2-3 queries and deduplicates by URI
|
|
69
|
+
*
|
|
62
70
|
* Skips auto-sync (sync happens after tasks, not before — keeps pre-task fast).
|
|
63
71
|
*/
|
|
64
|
-
export declare function queryRecallContext(teammatesDir: string, teammateName: string, taskPrompt: string): Promise<RecallContext>;
|
|
72
|
+
export declare function queryRecallContext(teammatesDir: string, teammateName: string, taskPrompt: string, conversationContext?: string): Promise<RecallContext>;
|
|
65
73
|
/**
|
|
66
74
|
* Sync the recall index for a teammate (or all teammates).
|
|
67
75
|
* Wrapper around the recall library's Indexer.
|
|
@@ -87,6 +95,8 @@ export declare function buildTeammatePrompt(teammate: TeammateConfig, taskPrompt
|
|
|
87
95
|
services?: InstalledService[];
|
|
88
96
|
sessionFile?: string;
|
|
89
97
|
recallResults?: SearchResult[];
|
|
98
|
+
/** Contents of USER.md — injected just before the task. */
|
|
99
|
+
userProfile?: string;
|
|
90
100
|
/** Token budget for the prompt wrapper (default 64k). Task is excluded. */
|
|
91
101
|
tokenBudget?: number;
|
|
92
102
|
}): string;
|
package/dist/adapter.js
CHANGED
|
@@ -5,21 +5,39 @@
|
|
|
5
5
|
* Each adapter wraps a specific agent backend (Codex, Claude Code, Cursor, etc.)
|
|
6
6
|
* and translates between the orchestrator's protocol and the agent's native API.
|
|
7
7
|
*/
|
|
8
|
-
import {
|
|
8
|
+
import { platform } from "node:os";
|
|
9
|
+
import { Indexer, buildQueryVariations, matchMemoryCatalog, multiSearch, } from "@teammates/recall";
|
|
9
10
|
/**
|
|
10
11
|
* Query the recall index for context relevant to the task prompt.
|
|
11
|
-
*
|
|
12
|
+
*
|
|
13
|
+
* Uses a multi-query strategy (Pass 1 from the recall query architecture):
|
|
14
|
+
* 1. Keyword extraction — generates focused queries from the task prompt
|
|
15
|
+
* 2. Conversation-aware queries — extracts recent topic from conversation history
|
|
16
|
+
* 3. Memory index scanning — text-matches frontmatter against the task prompt
|
|
17
|
+
* 4. Multi-query fusion — fires 2-3 queries and deduplicates by URI
|
|
18
|
+
*
|
|
12
19
|
* Skips auto-sync (sync happens after tasks, not before — keeps pre-task fast).
|
|
13
20
|
*/
|
|
14
|
-
export async function queryRecallContext(teammatesDir, teammateName, taskPrompt) {
|
|
21
|
+
export async function queryRecallContext(teammatesDir, teammateName, taskPrompt, conversationContext) {
|
|
15
22
|
try {
|
|
16
|
-
|
|
23
|
+
// Build query variations: original + keywords + conversation topic
|
|
24
|
+
// If no separate conversation context provided, use the task prompt itself
|
|
25
|
+
// (which may contain prepended conversation history from the orchestrator)
|
|
26
|
+
const queries = buildQueryVariations(taskPrompt, conversationContext ?? taskPrompt);
|
|
27
|
+
const primaryQuery = queries[0];
|
|
28
|
+
const additionalQueries = queries.slice(1);
|
|
29
|
+
// Scan memory frontmatter for text-matched results (no embeddings needed)
|
|
30
|
+
const catalogMatches = await matchMemoryCatalog(teammatesDir, teammateName, taskPrompt);
|
|
31
|
+
// Fire multi-query search with deduplication
|
|
32
|
+
const results = await multiSearch(primaryQuery, {
|
|
17
33
|
teammatesDir,
|
|
18
34
|
teammate: teammateName,
|
|
19
35
|
maxResults: 5,
|
|
20
36
|
maxChunks: 3,
|
|
21
37
|
maxTokens: 500,
|
|
22
38
|
skipSync: true,
|
|
39
|
+
additionalQueries,
|
|
40
|
+
catalogMatches,
|
|
23
41
|
});
|
|
24
42
|
return { results, ok: true };
|
|
25
43
|
}
|
|
@@ -182,24 +200,43 @@ export function buildTeammatePrompt(teammate, taskPrompt, options) {
|
|
|
182
200
|
lines.push("\n---\n");
|
|
183
201
|
parts.push(lines.join("\n"));
|
|
184
202
|
}
|
|
203
|
+
// ── Recall tool (Pass 2 — agent-driven search) ─────────────────
|
|
204
|
+
// Tell the agent it can search memories mid-task via the CLI tool
|
|
205
|
+
parts.push(`## Recall — Memory Search Tool\n\nYou can search your own memories mid-task for additional context. This is useful when the pre-loaded memories don't cover what you need.\n\n**Usage:** Run this command via your shell/terminal tool:\n\`\`\`\nteammates-recall search "<your query>" --dir .teammates --teammate ${teammate.name} --no-sync --json\n\`\`\`\n\n**Tips:**\n- Use specific, descriptive queries ("hooks lifecycle event naming decision" not "hooks")\n- Search iteratively: query → read result → refine query\n- The \`--json\` flag returns structured results for easier parsing\n- Results include a \`score\` field (0-1) — higher is more relevant\n- You can omit \`--teammate\` to search across all teammates' memories\n\n---\n`);
|
|
185
206
|
// ── Handoff context (required when present) ─────────────────────
|
|
186
207
|
if (options?.handoffContext) {
|
|
187
208
|
parts.push(`## Handoff Context\n\n${options.handoffContext}\n\n---\n`);
|
|
188
209
|
}
|
|
210
|
+
// ── Output protocol (required — BEFORE session/memory updates) ──
|
|
211
|
+
// Placed first so agents produce text response before doing housekeeping.
|
|
212
|
+
// When output protocol is after memory updates, agents often end their turn
|
|
213
|
+
// with file edits and produce no visible text output.
|
|
214
|
+
parts.push(`## Output Protocol (CRITICAL)\n\n**Your #1 job is to produce a visible text response.** Session updates and memory writes are secondary — they support continuity but are not the deliverable. The user sees ONLY your text output. If you update files but return no text, the user sees an empty message and your work is invisible.\n\nFormat your response as:\n\n\`\`\`\nTO: user\n# <Subject line>\n\n<Body — full markdown response>\n\`\`\`\n\n**Handoffs:** To hand off work to a teammate, include a fenced handoff block anywhere in your response:\n\n\`\`\`\n\`\`\`handoff\n@<teammate>\n<task description — what you need them to do, with full context>\n\`\`\`\n\`\`\`\n\n**Rules:**\n- **You MUST end your turn with visible text output.** A turn that ends with only tool calls and no text is a failed turn.\n- The \`# Subject\` line is REQUIRED — it becomes the message title.\n- Always write a substantive body. Never return just the subject.\n- Use markdown: headings, lists, code blocks, bold, etc.\n- Do as much work as you can before handing off.\n- Only hand off to teammates listed in "Your Team" above.\n- The handoff block can appear anywhere in your response — it will be detected automatically.\n- **Write your text response FIRST, then update session/memory files.** This ensures visible output even if the agent turn ends early.\n\n---\n`);
|
|
189
215
|
// ── Session state (required) ────────────────────────────────────
|
|
190
216
|
if (options?.sessionFile) {
|
|
191
|
-
parts.push(`## Session State\n\nYour session file is at: \`${options.sessionFile}\`\n\n**
|
|
217
|
+
parts.push(`## Session State\n\nYour session file is at: \`${options.sessionFile}\`\n\n**After writing your text response**, append a brief entry to this file with:\n- What you did\n- Key decisions made\n- Files changed\n- Anything the next task should know\n\nThis is how you maintain continuity across tasks. Always read it, always update it.\n\n---\n`);
|
|
192
218
|
}
|
|
193
219
|
// ── Memory updates (required) ───────────────────────────────────
|
|
194
220
|
const today = new Date().toISOString().slice(0, 10);
|
|
195
|
-
parts.push(`## Memory Updates\n\n**
|
|
196
|
-
// ──
|
|
197
|
-
parts.push(`## Output Protocol (CRITICAL)\n\n**Your #1 job is to produce a visible text response.** Session updates and memory writes are secondary — they support continuity but are not the deliverable. The user sees ONLY your text output. If you update files but return no text, the user sees an empty message and your work is invisible.\n\nFormat your response as:\n\n\`\`\`\nTO: user\n# <Subject line>\n\n<Body — full markdown response>\n\`\`\`\n\n**Handoffs:** To hand off work to a teammate, include a fenced handoff block anywhere in your response:\n\n\`\`\`\n\`\`\`handoff\n@<teammate>\n<task description — what you need them to do, with full context>\n\`\`\`\n\`\`\`\n\n**Rules:**\n- **You MUST end your turn with visible text output.** A turn that ends with only tool calls and no text is a failed turn.\n- The \`# Subject\` line is REQUIRED — it becomes the message title.\n- Always write a substantive body. Never return just the subject.\n- Use markdown: headings, lists, code blocks, bold, etc.\n- Do as much work as you can before handing off.\n- Only hand off to teammates listed in "Your Team" above.\n- The handoff block can appear anywhere in your response — it will be detected automatically.\n\n---\n`);
|
|
198
|
-
// ── Current date/time (required, small) ─────────────────────────
|
|
221
|
+
parts.push(`## Memory Updates\n\n**After writing your text response**, update your memory files:\n\n1. **Daily log** — Read \`.teammates/${teammate.name}/memory/${today}.md\` first (it may have entries from earlier tasks today), then write it back with your entry added. Create the file if it doesn't exist.\n - What you did\n - Key decisions made\n - Files changed\n - Anything the next task should know\n\n2. **Typed memories** — If you learned something durable (a decision, pattern, feedback, or reference), create a typed memory file at \`.teammates/${teammate.name}/memory/<type>_<topic>.md\` with frontmatter (\`name\`, \`description\`, \`type\`). Update existing memory files if the topic already has one.\n\n3. **WISDOM.md** — Do not edit directly. Wisdom entries are distilled from typed memories during compaction.\n\nThese files are your persistent memory. Without them, your next session starts from scratch.\n\n---\n`);
|
|
222
|
+
// ── Current date/time + environment (required, small) ───────────
|
|
199
223
|
const now = new Date();
|
|
200
|
-
|
|
224
|
+
const os = platform();
|
|
225
|
+
const osLabel = os === "win32" ? "Windows" : os === "darwin" ? "macOS" : "Linux";
|
|
226
|
+
const slashNote = os === "win32"
|
|
227
|
+
? "Use backslashes (`\\`) in file paths."
|
|
228
|
+
: "Use forward slashes (`/`) in file paths.";
|
|
229
|
+
// Extract timezone from USER.md if available
|
|
230
|
+
const tzMatch = options?.userProfile?.match(/\*\*Primary Timezone:\*\*\s*(.+)/);
|
|
231
|
+
const userTimezone = tzMatch?.[1]?.trim();
|
|
232
|
+
const tzLine = userTimezone ? `\n**Timezone:** ${userTimezone}` : "";
|
|
233
|
+
parts.push(`**Current date:** ${now.toLocaleDateString("en-US", { weekday: "long", year: "numeric", month: "long", day: "numeric" })} (${today})\n**Current time:** ${now.toLocaleTimeString("en-US", { hour: "2-digit", minute: "2-digit" })}${tzLine}\n**Environment:** ${osLabel} — ${slashNote}\n\n---\n`);
|
|
234
|
+
// ── User profile (always included when present) ────────────────
|
|
235
|
+
if (options?.userProfile?.trim()) {
|
|
236
|
+
parts.push(`## User Profile\n\n${options.userProfile.trim()}\n\n---\n`);
|
|
237
|
+
}
|
|
201
238
|
// ── Task (always included, excluded from budget) ────────────────
|
|
202
|
-
parts.push(`## Task\n\n${taskPrompt}\n\n---\n\n**REMINDER:
|
|
239
|
+
parts.push(`## Task\n\n${taskPrompt}\n\n---\n\n**REMINDER: Write your text response (TO: user) FIRST, then update session/memory files. A turn with only file edits and no text output is a failed turn.**`);
|
|
203
240
|
return parts.join("\n");
|
|
204
241
|
}
|
|
205
242
|
/**
|
package/dist/adapter.test.js
CHANGED
|
@@ -88,7 +88,9 @@ export declare class CliProxyAdapter implements AgentAdapter {
|
|
|
88
88
|
private pendingTempFiles;
|
|
89
89
|
constructor(options: CliProxyOptions);
|
|
90
90
|
startSession(teammate: TeammateConfig): Promise<string>;
|
|
91
|
-
executeTask(_sessionId: string, teammate: TeammateConfig, prompt: string
|
|
91
|
+
executeTask(_sessionId: string, teammate: TeammateConfig, prompt: string, options?: {
|
|
92
|
+
raw?: boolean;
|
|
93
|
+
}): Promise<TaskResult>;
|
|
92
94
|
routeTask(task: string, roster: RosterEntry[]): Promise<string | null>;
|
|
93
95
|
getSessionFile(teammateName: string): string | undefined;
|
|
94
96
|
destroySession(_sessionId: string): Promise<void>;
|
|
@@ -136,12 +136,15 @@ export class CliProxyAdapter {
|
|
|
136
136
|
this.sessionFiles.set(teammate.name, sessionFile);
|
|
137
137
|
return id;
|
|
138
138
|
}
|
|
139
|
-
async executeTask(_sessionId, teammate, prompt) {
|
|
140
|
-
// If
|
|
141
|
-
//
|
|
139
|
+
async executeTask(_sessionId, teammate, prompt, options) {
|
|
140
|
+
// If raw mode is set, skip all prompt wrapping — send prompt as-is
|
|
141
|
+
// Used for defensive retries where the full prompt template is counterproductive
|
|
142
142
|
const sessionFile = this.sessionFiles.get(teammate.name);
|
|
143
143
|
let fullPrompt;
|
|
144
|
-
if (
|
|
144
|
+
if (options?.raw) {
|
|
145
|
+
fullPrompt = prompt;
|
|
146
|
+
}
|
|
147
|
+
else if (teammate.soul) {
|
|
145
148
|
// Query recall for relevant memories before building prompt
|
|
146
149
|
const teammatesDir = teammate.cwd
|
|
147
150
|
? join(teammate.cwd, ".teammates")
|
|
@@ -149,11 +152,22 @@ export class CliProxyAdapter {
|
|
|
149
152
|
const recall = teammatesDir
|
|
150
153
|
? await queryRecallContext(teammatesDir, teammate.name, prompt)
|
|
151
154
|
: undefined;
|
|
155
|
+
// Read USER.md for injection into the prompt
|
|
156
|
+
let userProfile;
|
|
157
|
+
if (teammatesDir) {
|
|
158
|
+
try {
|
|
159
|
+
userProfile = await readFile(join(teammatesDir, "USER.md"), "utf-8");
|
|
160
|
+
}
|
|
161
|
+
catch {
|
|
162
|
+
// USER.md may not exist yet — that's fine
|
|
163
|
+
}
|
|
164
|
+
}
|
|
152
165
|
fullPrompt = buildTeammatePrompt(teammate, prompt, {
|
|
153
166
|
roster: this.roster,
|
|
154
167
|
services: this.services,
|
|
155
168
|
sessionFile,
|
|
156
169
|
recallResults: recall?.results,
|
|
170
|
+
userProfile,
|
|
157
171
|
});
|
|
158
172
|
}
|
|
159
173
|
else {
|
|
@@ -219,6 +233,7 @@ export class CliProxyAdapter {
|
|
|
219
233
|
const command = this.options.commandPath ?? this.preset.command;
|
|
220
234
|
const args = this.preset.buildArgs({ promptFile, prompt }, {
|
|
221
235
|
name: "_router",
|
|
236
|
+
type: "ai",
|
|
222
237
|
role: "",
|
|
223
238
|
soul: "",
|
|
224
239
|
wisdom: "",
|
|
@@ -40,7 +40,9 @@ export declare class CopilotAdapter implements AgentAdapter {
|
|
|
40
40
|
private sessionsDir;
|
|
41
41
|
constructor(options?: CopilotAdapterOptions);
|
|
42
42
|
startSession(teammate: TeammateConfig): Promise<string>;
|
|
43
|
-
executeTask(_sessionId: string, teammate: TeammateConfig, prompt: string
|
|
43
|
+
executeTask(_sessionId: string, teammate: TeammateConfig, prompt: string, options?: {
|
|
44
|
+
raw?: boolean;
|
|
45
|
+
}): Promise<TaskResult>;
|
|
44
46
|
routeTask(task: string, roster: RosterEntry[]): Promise<string | null>;
|
|
45
47
|
getSessionFile(teammateName: string): string | undefined;
|
|
46
48
|
destroySession(_sessionId: string): Promise<void>;
|
package/dist/adapters/copilot.js
CHANGED
|
@@ -56,12 +56,15 @@ export class CopilotAdapter {
|
|
|
56
56
|
this.sessionFiles.set(teammate.name, sessionFile);
|
|
57
57
|
return id;
|
|
58
58
|
}
|
|
59
|
-
async executeTask(_sessionId, teammate, prompt) {
|
|
59
|
+
async executeTask(_sessionId, teammate, prompt, options) {
|
|
60
60
|
await this.ensureClient(teammate.cwd);
|
|
61
61
|
const sessionFile = this.sessionFiles.get(teammate.name);
|
|
62
62
|
// Build the full teammate prompt (identity + memory + task)
|
|
63
63
|
let fullPrompt;
|
|
64
|
-
if (
|
|
64
|
+
if (options?.raw) {
|
|
65
|
+
fullPrompt = prompt;
|
|
66
|
+
}
|
|
67
|
+
else if (teammate.soul) {
|
|
65
68
|
// Query recall for relevant memories before building prompt
|
|
66
69
|
const teammatesDir = teammate.cwd
|
|
67
70
|
? join(teammate.cwd, ".teammates")
|
|
@@ -69,11 +72,22 @@ export class CopilotAdapter {
|
|
|
69
72
|
const recall = teammatesDir
|
|
70
73
|
? await queryRecallContext(teammatesDir, teammate.name, prompt)
|
|
71
74
|
: undefined;
|
|
75
|
+
// Read USER.md for injection into the prompt
|
|
76
|
+
let userProfile;
|
|
77
|
+
if (teammatesDir) {
|
|
78
|
+
try {
|
|
79
|
+
userProfile = await readFile(join(teammatesDir, "USER.md"), "utf-8");
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
// USER.md may not exist yet — that's fine
|
|
83
|
+
}
|
|
84
|
+
}
|
|
72
85
|
fullPrompt = buildTeammatePrompt(teammate, prompt, {
|
|
73
86
|
roster: this.roster,
|
|
74
87
|
services: this.services,
|
|
75
88
|
sessionFile,
|
|
76
89
|
recallResults: recall?.results,
|
|
90
|
+
userProfile,
|
|
77
91
|
});
|
|
78
92
|
}
|
|
79
93
|
else {
|
package/dist/adapters/echo.d.ts
CHANGED
|
@@ -9,5 +9,7 @@ import type { TaskResult, TeammateConfig } from "../types.js";
|
|
|
9
9
|
export declare class EchoAdapter implements AgentAdapter {
|
|
10
10
|
readonly name = "echo";
|
|
11
11
|
startSession(teammate: TeammateConfig): Promise<string>;
|
|
12
|
-
executeTask(_sessionId: string, teammate: TeammateConfig, prompt: string
|
|
12
|
+
executeTask(_sessionId: string, teammate: TeammateConfig, prompt: string, options?: {
|
|
13
|
+
raw?: boolean;
|
|
14
|
+
}): Promise<TaskResult>;
|
|
13
15
|
}
|
package/dist/adapters/echo.js
CHANGED
|
@@ -11,8 +11,8 @@ export class EchoAdapter {
|
|
|
11
11
|
async startSession(teammate) {
|
|
12
12
|
return `echo-${teammate.name}-${nextId++}`;
|
|
13
13
|
}
|
|
14
|
-
async executeTask(_sessionId, teammate, prompt) {
|
|
15
|
-
const fullPrompt = buildTeammatePrompt(teammate, prompt);
|
|
14
|
+
async executeTask(_sessionId, teammate, prompt, options) {
|
|
15
|
+
const fullPrompt = options?.raw ? prompt : buildTeammatePrompt(teammate, prompt);
|
|
16
16
|
return {
|
|
17
17
|
teammate: teammate.name,
|
|
18
18
|
success: true,
|
package/dist/banner.d.ts
CHANGED
|
@@ -2,18 +2,21 @@
|
|
|
2
2
|
* Animated startup banner for @teammates/cli.
|
|
3
3
|
*/
|
|
4
4
|
import { type Constraint, Control, type DrawingContext, type Rect, type Size } from "@teammates/consolonia";
|
|
5
|
+
import type { PresenceState } from "./types.js";
|
|
5
6
|
export type ServiceStatus = "bundled" | "missing" | "not-configured" | "configured";
|
|
6
7
|
export interface ServiceInfo {
|
|
7
8
|
name: string;
|
|
8
9
|
status: ServiceStatus;
|
|
9
10
|
}
|
|
10
11
|
export interface BannerInfo {
|
|
11
|
-
|
|
12
|
+
/** Display name shown in the banner (user alias or adapter name). */
|
|
13
|
+
displayName: string;
|
|
12
14
|
teammateCount: number;
|
|
13
15
|
cwd: string;
|
|
14
16
|
teammates: {
|
|
15
17
|
name: string;
|
|
16
18
|
role: string;
|
|
19
|
+
presence: PresenceState;
|
|
17
20
|
}[];
|
|
18
21
|
services: ServiceInfo[];
|
|
19
22
|
}
|
|
@@ -66,6 +69,8 @@ export declare class AnimatedBanner extends Control {
|
|
|
66
69
|
* If the animation already reached the hold point, it resumes immediately.
|
|
67
70
|
*/
|
|
68
71
|
releaseHold(): void;
|
|
72
|
+
/** Update service statuses and rebuild the banner lines. */
|
|
73
|
+
updateServices(services: ServiceInfo[]): void;
|
|
69
74
|
/** Cancel any pending animation timer. */
|
|
70
75
|
dispose(): void;
|
|
71
76
|
measure(constraint: Constraint): Size;
|
package/dist/banner.js
CHANGED
|
@@ -72,7 +72,7 @@ export class AnimatedBanner extends Control {
|
|
|
72
72
|
const gap = " ";
|
|
73
73
|
const lines = [];
|
|
74
74
|
// TM logo row 1 + adapter info
|
|
75
|
-
lines.push(concat(tp.accent(tmTop), tp.text(gap + info.
|
|
75
|
+
lines.push(concat(tp.accent(tmTop), tp.text(gap + info.displayName), tp.muted(` · ${info.teammateCount} teammate${info.teammateCount === 1 ? "" : "s"}`), tp.muted(` · v${PKG_VERSION}`)));
|
|
76
76
|
// TM logo row 2 + cwd
|
|
77
77
|
lines.push(concat(tp.accent(tmBot), tp.muted(gap + info.cwd)));
|
|
78
78
|
// Service status rows
|
|
@@ -92,9 +92,14 @@ export class AnimatedBanner extends Control {
|
|
|
92
92
|
// blank
|
|
93
93
|
lines.push("");
|
|
94
94
|
this._rosterStart = lines.length;
|
|
95
|
-
// Teammate roster
|
|
95
|
+
// Teammate roster (with presence indicators)
|
|
96
96
|
for (const t of info.teammates) {
|
|
97
|
-
|
|
97
|
+
const presenceDot = t.presence === "online"
|
|
98
|
+
? tp.success(" ● ")
|
|
99
|
+
: t.presence === "reachable"
|
|
100
|
+
? tp.warning(" ● ")
|
|
101
|
+
: tp.error(" ● ");
|
|
102
|
+
lines.push(concat(presenceDot, tp.accent(`@${t.name}`.padEnd(14)), tp.muted(t.role)));
|
|
98
103
|
}
|
|
99
104
|
// blank
|
|
100
105
|
lines.push("");
|
|
@@ -261,6 +266,16 @@ export class AnimatedBanner extends Control {
|
|
|
261
266
|
this._schedule(80);
|
|
262
267
|
}
|
|
263
268
|
}
|
|
269
|
+
/** Update service statuses and rebuild the banner lines. */
|
|
270
|
+
updateServices(services) {
|
|
271
|
+
this._info.services = services;
|
|
272
|
+
this._buildFinalLines();
|
|
273
|
+
// If animation is done, refresh immediately
|
|
274
|
+
if (this._phase === "done") {
|
|
275
|
+
this._lines = this._finalLines;
|
|
276
|
+
this._apply();
|
|
277
|
+
}
|
|
278
|
+
}
|
|
264
279
|
/** Cancel any pending animation timer. */
|
|
265
280
|
dispose() {
|
|
266
281
|
if (this._timer) {
|
package/dist/cli-args.js
CHANGED
|
@@ -102,7 +102,6 @@ ${chalk.bold("@teammates/cli")} — Agent-agnostic teammate orchestrator
|
|
|
102
102
|
|
|
103
103
|
${chalk.bold("Usage:")}
|
|
104
104
|
teammates <agent> Launch session with an agent
|
|
105
|
-
teammates claude Use Claude Code
|
|
106
105
|
teammates codex Use OpenAI Codex
|
|
107
106
|
teammates aider Use Aider
|
|
108
107
|
|