@teammates/cli 0.2.7 → 0.3.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 +12 -6
- package/dist/adapter.d.ts +20 -0
- package/dist/adapter.js +63 -5
- package/dist/adapters/cli-proxy.d.ts +21 -1
- package/dist/adapters/cli-proxy.js +70 -18
- package/dist/adapters/copilot.js +20 -1
- package/dist/cli.js +224 -374
- package/dist/compact.d.ts +7 -0
- package/dist/compact.js +97 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/types.d.ts +13 -0
- package/package.json +3 -2
- package/template/TEMPLATE.md +1 -1
- package/template/example/SOUL.md +1 -1
package/README.md
CHANGED
|
@@ -128,12 +128,14 @@ The CLI uses a generic adapter interface to support any coding agent. Each adapt
|
|
|
128
128
|
|
|
129
129
|
### How Adapters Work
|
|
130
130
|
|
|
131
|
-
1. The
|
|
132
|
-
2. The prompt
|
|
133
|
-
3. The
|
|
134
|
-
4.
|
|
135
|
-
5.
|
|
136
|
-
6.
|
|
131
|
+
1. The adapter queries the recall index for relevant memories (automatic, in-process)
|
|
132
|
+
2. The orchestrator builds a full prompt (SOUL → WISDOM → recall results → daily logs → weekly summaries → session history → roster → task)
|
|
133
|
+
3. The prompt is written to a temp file
|
|
134
|
+
4. The agent CLI is spawned with the prompt
|
|
135
|
+
5. stdout/stderr are captured for result parsing
|
|
136
|
+
6. The output is parsed for embedded handoff blocks
|
|
137
|
+
7. The recall index is synced to pick up any files the agent created/modified
|
|
138
|
+
8. Temp files are cleaned up
|
|
137
139
|
|
|
138
140
|
### Writing a Custom Adapter
|
|
139
141
|
|
|
@@ -214,6 +216,10 @@ Tests use [Vitest](https://vitest.dev/) and cover the core modules:
|
|
|
214
216
|
| `src/registry.test.ts` | Teammate discovery, SOUL.md parsing (role, ownership), daily logs |
|
|
215
217
|
| `src/adapters/echo.test.ts` | Echo adapter session and task execution |
|
|
216
218
|
|
|
219
|
+
## Dependencies
|
|
220
|
+
|
|
221
|
+
- **`@teammates/recall`** — Bundled as a direct dependency. Provides automatic semantic search over teammate memories before every task. No separate installation or configuration needed.
|
|
222
|
+
|
|
217
223
|
## Requirements
|
|
218
224
|
|
|
219
225
|
- Node.js >= 20
|
package/dist/adapter.d.ts
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
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 { type SearchResult } from "@teammates/recall";
|
|
8
9
|
import type { TaskResult, TeammateConfig } from "./types.js";
|
|
9
10
|
export interface AgentAdapter {
|
|
10
11
|
/** Human-readable name of the agent backend (e.g. "codex", "claude-code") */
|
|
@@ -49,6 +50,23 @@ export interface InstalledService {
|
|
|
49
50
|
description: string;
|
|
50
51
|
usage: string;
|
|
51
52
|
}
|
|
53
|
+
/** Recall search results formatted for prompt injection. */
|
|
54
|
+
export interface RecallContext {
|
|
55
|
+
results: SearchResult[];
|
|
56
|
+
/** Whether the query succeeded (false = index missing or search errored) */
|
|
57
|
+
ok: boolean;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Query the recall index for context relevant to the task prompt.
|
|
61
|
+
* Returns search results that should be injected into the teammate prompt.
|
|
62
|
+
* Skips auto-sync (sync happens after tasks, not before — keeps pre-task fast).
|
|
63
|
+
*/
|
|
64
|
+
export declare function queryRecallContext(teammatesDir: string, teammateName: string, taskPrompt: string): Promise<RecallContext>;
|
|
65
|
+
/**
|
|
66
|
+
* Sync the recall index for a teammate (or all teammates).
|
|
67
|
+
* Wrapper around the recall library's Indexer.
|
|
68
|
+
*/
|
|
69
|
+
export declare function syncRecallIndex(teammatesDir: string, teammate?: string): Promise<void>;
|
|
52
70
|
/**
|
|
53
71
|
* Build the full prompt for a teammate session.
|
|
54
72
|
* Includes identity, memory, roster, output protocol, and the task.
|
|
@@ -58,6 +76,8 @@ export declare function buildTeammatePrompt(teammate: TeammateConfig, taskPrompt
|
|
|
58
76
|
roster?: RosterEntry[];
|
|
59
77
|
services?: InstalledService[];
|
|
60
78
|
sessionFile?: string;
|
|
79
|
+
sessionContent?: string;
|
|
80
|
+
recallResults?: SearchResult[];
|
|
61
81
|
}): string;
|
|
62
82
|
/**
|
|
63
83
|
* Format a handoff envelope into a human-readable context string.
|
package/dist/adapter.js
CHANGED
|
@@ -5,6 +5,41 @@
|
|
|
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 { Indexer, search } from "@teammates/recall";
|
|
9
|
+
/**
|
|
10
|
+
* Query the recall index for context relevant to the task prompt.
|
|
11
|
+
* Returns search results that should be injected into the teammate prompt.
|
|
12
|
+
* Skips auto-sync (sync happens after tasks, not before — keeps pre-task fast).
|
|
13
|
+
*/
|
|
14
|
+
export async function queryRecallContext(teammatesDir, teammateName, taskPrompt) {
|
|
15
|
+
try {
|
|
16
|
+
const results = await search(taskPrompt, {
|
|
17
|
+
teammatesDir,
|
|
18
|
+
teammate: teammateName,
|
|
19
|
+
maxResults: 5,
|
|
20
|
+
maxChunks: 3,
|
|
21
|
+
maxTokens: 500,
|
|
22
|
+
skipSync: true,
|
|
23
|
+
});
|
|
24
|
+
return { results, ok: true };
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
return { results: [], ok: false };
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Sync the recall index for a teammate (or all teammates).
|
|
32
|
+
* Wrapper around the recall library's Indexer.
|
|
33
|
+
*/
|
|
34
|
+
export async function syncRecallIndex(teammatesDir, teammate) {
|
|
35
|
+
const indexer = new Indexer({ teammatesDir });
|
|
36
|
+
if (teammate) {
|
|
37
|
+
await indexer.syncTeammate(teammate);
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
await indexer.syncAll();
|
|
41
|
+
}
|
|
42
|
+
}
|
|
8
43
|
/**
|
|
9
44
|
* Build the full prompt for a teammate session.
|
|
10
45
|
* Includes identity, memory, roster, output protocol, and the task.
|
|
@@ -21,6 +56,19 @@ export function buildTeammatePrompt(teammate, taskPrompt, options) {
|
|
|
21
56
|
parts.push(teammate.wisdom);
|
|
22
57
|
parts.push("\n---\n");
|
|
23
58
|
}
|
|
59
|
+
// ── Recall results (relevant episodic & semantic memories) ────────
|
|
60
|
+
if (options?.recallResults && options.recallResults.length > 0) {
|
|
61
|
+
parts.push("## Relevant Memories (from recall search)\n");
|
|
62
|
+
parts.push("These memories were retrieved based on relevance to the current task:\n");
|
|
63
|
+
for (const r of options.recallResults) {
|
|
64
|
+
const label = r.contentType
|
|
65
|
+
? `[${r.contentType}] ${r.uri}`
|
|
66
|
+
: r.uri;
|
|
67
|
+
parts.push(`### ${label}\n${r.text}\n`);
|
|
68
|
+
}
|
|
69
|
+
parts.push("\n---\n");
|
|
70
|
+
}
|
|
71
|
+
// ── Recent daily logs ──────────────────────────────────────────────
|
|
24
72
|
if (teammate.dailyLogs.length > 0) {
|
|
25
73
|
parts.push("## Recent Daily Logs\n");
|
|
26
74
|
for (const log of teammate.dailyLogs.slice(0, 7)) {
|
|
@@ -36,6 +84,13 @@ export function buildTeammatePrompt(teammate, taskPrompt, options) {
|
|
|
36
84
|
}
|
|
37
85
|
parts.push("\n---\n");
|
|
38
86
|
}
|
|
87
|
+
// ── Session history (prior tasks in this session) ─────────────────
|
|
88
|
+
if (options?.sessionContent?.trim()) {
|
|
89
|
+
parts.push("## Session History\n");
|
|
90
|
+
parts.push("These are entries from your prior tasks in this session:\n");
|
|
91
|
+
parts.push(options.sessionContent);
|
|
92
|
+
parts.push("\n---\n");
|
|
93
|
+
}
|
|
39
94
|
// ── Team roster ───────────────────────────────────────────────────
|
|
40
95
|
if (options?.roster && options.roster.length > 0) {
|
|
41
96
|
parts.push("## Your Team\n");
|
|
@@ -72,8 +127,6 @@ export function buildTeammatePrompt(teammate, taskPrompt, options) {
|
|
|
72
127
|
parts.push("## Session State\n");
|
|
73
128
|
parts.push(`Your session file is at: \`${options.sessionFile}\`
|
|
74
129
|
|
|
75
|
-
**Read this file first** — it contains context from your prior tasks in this session.
|
|
76
|
-
|
|
77
130
|
**Before returning your result**, append a brief entry to this file with:
|
|
78
131
|
- What you did
|
|
79
132
|
- Key decisions made
|
|
@@ -103,8 +156,10 @@ These files are your persistent memory. Without them, your next session starts f
|
|
|
103
156
|
`);
|
|
104
157
|
parts.push("\n---\n");
|
|
105
158
|
// ── Output protocol ───────────────────────────────────────────────
|
|
106
|
-
parts.push("## Output Protocol\n");
|
|
107
|
-
parts.push(
|
|
159
|
+
parts.push("## Output Protocol (CRITICAL)\n");
|
|
160
|
+
parts.push(`**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.
|
|
161
|
+
|
|
162
|
+
Format your response as:
|
|
108
163
|
|
|
109
164
|
\`\`\`
|
|
110
165
|
TO: user
|
|
@@ -123,9 +178,9 @@ TO: user
|
|
|
123
178
|
\`\`\`
|
|
124
179
|
|
|
125
180
|
**Rules:**
|
|
181
|
+
- **You MUST end your turn with visible text output.** A turn that ends with only tool calls and no text is a failed turn.
|
|
126
182
|
- The \`# Subject\` line is REQUIRED — it becomes the message title.
|
|
127
183
|
- Always write a substantive body. Never return just the subject.
|
|
128
|
-
- **Your final message MUST contain your response text.** Do not end your turn with only tool calls — always finish with a visible message to the user.
|
|
129
184
|
- Use markdown: headings, lists, code blocks, bold, etc.
|
|
130
185
|
- Do as much work as you can before handing off.
|
|
131
186
|
- Only hand off to teammates listed in "Your Team" above.
|
|
@@ -140,6 +195,9 @@ TO: user
|
|
|
140
195
|
// ── Task ──────────────────────────────────────────────────────────
|
|
141
196
|
parts.push("## Task\n");
|
|
142
197
|
parts.push(taskPrompt);
|
|
198
|
+
parts.push("\n---\n");
|
|
199
|
+
// ── Final reminder (last thing the agent reads) ─────────────────
|
|
200
|
+
parts.push("**REMINDER: After completing the task and updating session/memory files, you MUST produce a text response starting with `TO: user`. An empty response is a bug.**");
|
|
143
201
|
return parts.join("\n");
|
|
144
202
|
}
|
|
145
203
|
/**
|
|
@@ -16,15 +16,33 @@
|
|
|
16
16
|
*/
|
|
17
17
|
import type { AgentAdapter, InstalledService, RosterEntry } from "../adapter.js";
|
|
18
18
|
import type { SandboxLevel, TaskResult, TeammateConfig } from "../types.js";
|
|
19
|
+
/** Structured result from spawning an agent subprocess. */
|
|
20
|
+
export interface SpawnResult {
|
|
21
|
+
/** Combined stdout + stderr (for backward compat / display) */
|
|
22
|
+
output: string;
|
|
23
|
+
/** stdout only */
|
|
24
|
+
stdout: string;
|
|
25
|
+
/** stderr only */
|
|
26
|
+
stderr: string;
|
|
27
|
+
/** Process exit code (null if killed by signal) */
|
|
28
|
+
exitCode: number | null;
|
|
29
|
+
/** Signal that killed the process (null if exited normally) */
|
|
30
|
+
signal: string | null;
|
|
31
|
+
/** Whether the process was killed by our timeout */
|
|
32
|
+
timedOut: boolean;
|
|
33
|
+
/** Path to the debug log file, if one was written */
|
|
34
|
+
debugFile?: string;
|
|
35
|
+
}
|
|
19
36
|
export interface AgentPreset {
|
|
20
37
|
/** Display name */
|
|
21
38
|
name: string;
|
|
22
39
|
/** Binary / command to spawn */
|
|
23
40
|
command: string;
|
|
24
|
-
/** Build CLI args. `promptFile` is a temp file path, `prompt` is the raw text. */
|
|
41
|
+
/** Build CLI args. `promptFile` is a temp file path, `prompt` is the raw text, `debugFile` is an optional path for agent debug logs. */
|
|
25
42
|
buildArgs(ctx: {
|
|
26
43
|
promptFile: string;
|
|
27
44
|
prompt: string;
|
|
45
|
+
debugFile?: string;
|
|
28
46
|
}, teammate: TeammateConfig, options: CliProxyOptions): string[];
|
|
29
47
|
/** Extra env vars to set (e.g. FORCE_COLOR) */
|
|
30
48
|
env?: Record<string, string>;
|
|
@@ -34,6 +52,8 @@ export interface AgentPreset {
|
|
|
34
52
|
shell?: boolean;
|
|
35
53
|
/** Whether to pipe the prompt via stdin instead of as a CLI argument */
|
|
36
54
|
stdinPrompt?: boolean;
|
|
55
|
+
/** Whether this preset supports a debug log file (--debug-file) */
|
|
56
|
+
supportsDebugFile?: boolean;
|
|
37
57
|
/** Optional output parser — transforms raw stdout into clean agent output */
|
|
38
58
|
parseOutput?(raw: string): string;
|
|
39
59
|
}
|
|
@@ -16,22 +16,26 @@
|
|
|
16
16
|
*/
|
|
17
17
|
import { spawn } from "node:child_process";
|
|
18
18
|
import { randomUUID } from "node:crypto";
|
|
19
|
+
import { mkdirSync } from "node:fs";
|
|
19
20
|
import { mkdir, readFile, unlink, writeFile } from "node:fs/promises";
|
|
20
21
|
import { tmpdir } from "node:os";
|
|
21
22
|
import { join } from "node:path";
|
|
22
|
-
import { buildTeammatePrompt } from "../adapter.js";
|
|
23
|
+
import { buildTeammatePrompt, queryRecallContext } from "../adapter.js";
|
|
23
24
|
export const PRESETS = {
|
|
24
25
|
claude: {
|
|
25
26
|
name: "claude",
|
|
26
27
|
command: "claude",
|
|
27
|
-
buildArgs(
|
|
28
|
+
buildArgs(ctx, _teammate, options) {
|
|
28
29
|
const args = ["-p", "--verbose", "--dangerously-skip-permissions"];
|
|
29
30
|
if (options.model)
|
|
30
31
|
args.push("--model", options.model);
|
|
32
|
+
if (ctx.debugFile)
|
|
33
|
+
args.push("--debug-file", ctx.debugFile);
|
|
31
34
|
return args;
|
|
32
35
|
},
|
|
33
36
|
env: { FORCE_COLOR: "1", CLAUDECODE: "" },
|
|
34
37
|
stdinPrompt: true,
|
|
38
|
+
supportsDebugFile: true,
|
|
35
39
|
},
|
|
36
40
|
codex: {
|
|
37
41
|
name: "codex",
|
|
@@ -136,12 +140,31 @@ export class CliProxyAdapter {
|
|
|
136
140
|
// If the teammate has no soul (e.g. the raw agent), skip identity/memory
|
|
137
141
|
// wrapping but include handoff instructions so it can delegate to teammates
|
|
138
142
|
const sessionFile = this.sessionFiles.get(teammate.name);
|
|
143
|
+
// Read session file content for injection into the prompt
|
|
144
|
+
let sessionContent;
|
|
145
|
+
if (sessionFile) {
|
|
146
|
+
try {
|
|
147
|
+
sessionContent = await readFile(sessionFile, "utf-8");
|
|
148
|
+
}
|
|
149
|
+
catch {
|
|
150
|
+
// Session file may not exist yet — that's fine
|
|
151
|
+
}
|
|
152
|
+
}
|
|
139
153
|
let fullPrompt;
|
|
140
154
|
if (teammate.soul) {
|
|
155
|
+
// Query recall for relevant memories before building prompt
|
|
156
|
+
const teammatesDir = teammate.cwd
|
|
157
|
+
? join(teammate.cwd, ".teammates")
|
|
158
|
+
: undefined;
|
|
159
|
+
const recall = teammatesDir
|
|
160
|
+
? await queryRecallContext(teammatesDir, teammate.name, prompt)
|
|
161
|
+
: undefined;
|
|
141
162
|
fullPrompt = buildTeammatePrompt(teammate, prompt, {
|
|
142
163
|
roster: this.roster,
|
|
143
164
|
services: this.services,
|
|
144
165
|
sessionFile,
|
|
166
|
+
sessionContent,
|
|
167
|
+
recallResults: recall?.results,
|
|
145
168
|
});
|
|
146
169
|
}
|
|
147
170
|
else {
|
|
@@ -167,12 +190,20 @@ export class CliProxyAdapter {
|
|
|
167
190
|
await writeFile(promptFile, fullPrompt, "utf-8");
|
|
168
191
|
this.pendingTempFiles.add(promptFile);
|
|
169
192
|
try {
|
|
170
|
-
const
|
|
193
|
+
const spawn = await this.spawnAndProxy(teammate, promptFile, fullPrompt);
|
|
171
194
|
const output = this.preset.parseOutput
|
|
172
|
-
? this.preset.parseOutput(
|
|
173
|
-
:
|
|
195
|
+
? this.preset.parseOutput(spawn.output)
|
|
196
|
+
: spawn.output;
|
|
174
197
|
const teammateNames = this.roster.map((r) => r.name);
|
|
175
|
-
|
|
198
|
+
const result = parseResult(teammate.name, output, teammateNames, prompt);
|
|
199
|
+
result.diagnostics = {
|
|
200
|
+
exitCode: spawn.exitCode,
|
|
201
|
+
signal: spawn.signal,
|
|
202
|
+
stderr: spawn.stderr,
|
|
203
|
+
timedOut: spawn.timedOut,
|
|
204
|
+
debugFile: spawn.debugFile,
|
|
205
|
+
};
|
|
206
|
+
return result;
|
|
176
207
|
}
|
|
177
208
|
finally {
|
|
178
209
|
this.pendingTempFiles.delete(promptFile);
|
|
@@ -291,8 +322,21 @@ export class CliProxyAdapter {
|
|
|
291
322
|
*/
|
|
292
323
|
spawnAndProxy(teammate, promptFile, fullPrompt) {
|
|
293
324
|
return new Promise((resolve, reject) => {
|
|
325
|
+
// Always generate a debug log file for presets that support it (e.g. Claude's --debug-file).
|
|
326
|
+
// Written to .teammates/.tmp/debug/ so startup maintenance can clean old logs.
|
|
327
|
+
let debugFile;
|
|
328
|
+
if (this.preset.supportsDebugFile) {
|
|
329
|
+
const debugDir = join(teammate.cwd ?? process.cwd(), ".teammates", ".tmp", "debug");
|
|
330
|
+
try {
|
|
331
|
+
mkdirSync(debugDir, { recursive: true });
|
|
332
|
+
}
|
|
333
|
+
catch {
|
|
334
|
+
/* best effort */
|
|
335
|
+
}
|
|
336
|
+
debugFile = join(debugDir, `agent-${teammate.name}-${Date.now()}.log`);
|
|
337
|
+
}
|
|
294
338
|
const args = [
|
|
295
|
-
...this.preset.buildArgs({ promptFile, prompt: fullPrompt }, teammate, this.options),
|
|
339
|
+
...this.preset.buildArgs({ promptFile, prompt: fullPrompt, debugFile }, teammate, this.options),
|
|
296
340
|
...(this.options.extraFlags ?? []),
|
|
297
341
|
];
|
|
298
342
|
const command = this.options.commandPath ?? this.preset.command;
|
|
@@ -352,12 +396,13 @@ export class CliProxyAdapter {
|
|
|
352
396
|
process.stdin.resume();
|
|
353
397
|
process.stdin.on("data", onUserInput);
|
|
354
398
|
}
|
|
355
|
-
const
|
|
399
|
+
const stdoutBufs = [];
|
|
400
|
+
const stderrBufs = [];
|
|
356
401
|
child.stdout?.on("data", (chunk) => {
|
|
357
|
-
|
|
402
|
+
stdoutBufs.push(chunk);
|
|
358
403
|
});
|
|
359
404
|
child.stderr?.on("data", (chunk) => {
|
|
360
|
-
|
|
405
|
+
stderrBufs.push(chunk);
|
|
361
406
|
});
|
|
362
407
|
const cleanup = () => {
|
|
363
408
|
clearTimeout(timeoutTimer);
|
|
@@ -367,15 +412,22 @@ export class CliProxyAdapter {
|
|
|
367
412
|
process.stdin.removeListener("data", onUserInput);
|
|
368
413
|
}
|
|
369
414
|
};
|
|
370
|
-
child.on("close", (
|
|
415
|
+
child.on("close", (code, signal) => {
|
|
371
416
|
cleanup();
|
|
372
|
-
const
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
417
|
+
const stdout = Buffer.concat(stdoutBufs).toString("utf-8");
|
|
418
|
+
const stderr = Buffer.concat(stderrBufs).toString("utf-8");
|
|
419
|
+
const output = stdout + (stderr ? `\n${stderr}` : "");
|
|
420
|
+
resolve({
|
|
421
|
+
output: killed
|
|
422
|
+
? `${output}\n\n[TIMEOUT] Agent process killed after ${timeout}ms`
|
|
423
|
+
: output,
|
|
424
|
+
stdout,
|
|
425
|
+
stderr,
|
|
426
|
+
exitCode: code,
|
|
427
|
+
signal: signal ?? null,
|
|
428
|
+
timedOut: killed,
|
|
429
|
+
debugFile,
|
|
430
|
+
});
|
|
379
431
|
});
|
|
380
432
|
child.on("error", (err) => {
|
|
381
433
|
cleanup();
|
package/dist/adapters/copilot.js
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
13
13
|
import { join } from "node:path";
|
|
14
14
|
import { approveAll, CopilotClient, } from "@github/copilot-sdk";
|
|
15
|
-
import { buildTeammatePrompt } from "../adapter.js";
|
|
15
|
+
import { buildTeammatePrompt, queryRecallContext } from "../adapter.js";
|
|
16
16
|
import { parseResult } from "./cli-proxy.js";
|
|
17
17
|
// ─── Adapter ─────────────────────────────────────────────────────────
|
|
18
18
|
let nextId = 1;
|
|
@@ -59,13 +59,32 @@ export class CopilotAdapter {
|
|
|
59
59
|
async executeTask(_sessionId, teammate, prompt) {
|
|
60
60
|
await this.ensureClient(teammate.cwd);
|
|
61
61
|
const sessionFile = this.sessionFiles.get(teammate.name);
|
|
62
|
+
// Read session file content for injection into the prompt
|
|
63
|
+
let sessionContent;
|
|
64
|
+
if (sessionFile) {
|
|
65
|
+
try {
|
|
66
|
+
sessionContent = await readFile(sessionFile, "utf-8");
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
// Session file may not exist yet — that's fine
|
|
70
|
+
}
|
|
71
|
+
}
|
|
62
72
|
// Build the full teammate prompt (identity + memory + task)
|
|
63
73
|
let fullPrompt;
|
|
64
74
|
if (teammate.soul) {
|
|
75
|
+
// Query recall for relevant memories before building prompt
|
|
76
|
+
const teammatesDir = teammate.cwd
|
|
77
|
+
? join(teammate.cwd, ".teammates")
|
|
78
|
+
: undefined;
|
|
79
|
+
const recall = teammatesDir
|
|
80
|
+
? await queryRecallContext(teammatesDir, teammate.name, prompt)
|
|
81
|
+
: undefined;
|
|
65
82
|
fullPrompt = buildTeammatePrompt(teammate, prompt, {
|
|
66
83
|
roster: this.roster,
|
|
67
84
|
services: this.services,
|
|
68
85
|
sessionFile,
|
|
86
|
+
sessionContent,
|
|
87
|
+
recallResults: recall?.results,
|
|
69
88
|
});
|
|
70
89
|
}
|
|
71
90
|
else {
|