@teammates/cli 0.5.3 → 0.6.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/dist/adapter.d.ts +7 -1
- package/dist/adapter.js +28 -3
- package/dist/adapter.test.js +10 -13
- package/dist/adapters/cli-proxy.d.ts +3 -0
- package/dist/adapters/cli-proxy.js +133 -108
- package/dist/cli.js +392 -4
- package/dist/compact.d.ts +29 -0
- package/dist/compact.js +125 -0
- package/dist/index.d.ts +4 -2
- package/dist/index.js +2 -1
- package/dist/log-parser.d.ts +53 -0
- package/dist/log-parser.js +228 -0
- package/dist/log-parser.test.d.ts +1 -0
- package/dist/log-parser.test.js +113 -0
- package/dist/orchestrator.d.ts +2 -0
- package/dist/orchestrator.js +4 -0
- package/dist/types.d.ts +18 -0
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
// Public API for @teammates/cli
|
|
2
2
|
export { buildTeammatePrompt, DAILY_LOG_BUDGET_TOKENS, formatHandoffContext, queryRecallContext, syncRecallIndex, } from "./adapter.js";
|
|
3
|
-
export { autoCompactForBudget } from "./compact.js";
|
|
4
3
|
export { CliProxyAdapter, PRESETS, } from "./adapters/cli-proxy.js";
|
|
5
4
|
export { EchoAdapter } from "./adapters/echo.js";
|
|
6
5
|
export { AnimatedBanner } from "./banner.js";
|
|
7
6
|
export { findTeammatesDir, PKG_VERSION, parseCliArgs } from "./cli-args.js";
|
|
7
|
+
export { autoCompactForBudget, buildDailyCompressionPrompt, buildMigrationCompressionPrompt, findUncompressedDailies, } from "./compact.js";
|
|
8
|
+
export { buildConversationLog, formatLogTimeline, parseClaudeDebugLog, parseCodexOutput, parseRawOutput, } from "./log-parser.js";
|
|
8
9
|
export { Orchestrator, } from "./orchestrator.js";
|
|
9
10
|
export { loadPersonas, scaffoldFromPersona } from "./personas.js";
|
|
10
11
|
export { Registry } from "./registry.js";
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent log parser — extracts condensed conversation timelines from agent
|
|
3
|
+
* debug logs and stdout for the interrupt-and-resume system.
|
|
4
|
+
*
|
|
5
|
+
* Each agent type has a different log format:
|
|
6
|
+
* - Claude: structured --debug-file with tool calls and results
|
|
7
|
+
* - Codex: JSONL with item.completed events
|
|
8
|
+
* - Others: raw stdout (truncated)
|
|
9
|
+
*/
|
|
10
|
+
/** A single action extracted from an agent's conversation log. */
|
|
11
|
+
export interface LogEntry {
|
|
12
|
+
/** Tool name or action type (e.g. "Write", "Read", "Search", "text") */
|
|
13
|
+
action: string;
|
|
14
|
+
/** Key parameters — file paths, search queries (NOT full file contents) */
|
|
15
|
+
summary: string;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Parse a Claude --debug-file into condensed log entries.
|
|
19
|
+
*
|
|
20
|
+
* The debug file contains the full conversation log including tool calls
|
|
21
|
+
* and their results. We extract tool names and key parameters (file paths,
|
|
22
|
+
* search queries) but NOT full file contents to keep the resume prompt compact.
|
|
23
|
+
*/
|
|
24
|
+
export declare function parseClaudeDebugLog(debugFilePath: string): LogEntry[];
|
|
25
|
+
/**
|
|
26
|
+
* Parse Codex JSONL output into condensed log entries.
|
|
27
|
+
*/
|
|
28
|
+
export declare function parseCodexOutput(stdout: string): LogEntry[];
|
|
29
|
+
/**
|
|
30
|
+
* Parse raw stdout into condensed log entries (fallback for unknown agents).
|
|
31
|
+
*/
|
|
32
|
+
export declare function parseRawOutput(output: string): LogEntry[];
|
|
33
|
+
/**
|
|
34
|
+
* Format log entries into a condensed markdown timeline for the resume prompt.
|
|
35
|
+
*
|
|
36
|
+
* Keeps the output compact — file paths and search queries only, no content.
|
|
37
|
+
* Groups consecutive same-action entries (e.g., "Wrote 15 files: ...").
|
|
38
|
+
*/
|
|
39
|
+
export declare function formatLogTimeline(entries: LogEntry[]): string;
|
|
40
|
+
/**
|
|
41
|
+
* Estimate the token count of a string (rough: 1 token ≈ 4 chars).
|
|
42
|
+
*/
|
|
43
|
+
export declare function estimateTokens(text: string): number;
|
|
44
|
+
/**
|
|
45
|
+
* Build a condensed conversation log from the available sources.
|
|
46
|
+
* Tries Claude debug file first, then Codex JSONL, then raw stdout.
|
|
47
|
+
* Truncates to the token budget if needed.
|
|
48
|
+
*/
|
|
49
|
+
export declare function buildConversationLog(debugFile: string | undefined, stdout: string, presetName: string, tokenBudget?: number): {
|
|
50
|
+
log: string;
|
|
51
|
+
toolCallCount: number;
|
|
52
|
+
filesChanged: string[];
|
|
53
|
+
};
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent log parser — extracts condensed conversation timelines from agent
|
|
3
|
+
* debug logs and stdout for the interrupt-and-resume system.
|
|
4
|
+
*
|
|
5
|
+
* Each agent type has a different log format:
|
|
6
|
+
* - Claude: structured --debug-file with tool calls and results
|
|
7
|
+
* - Codex: JSONL with item.completed events
|
|
8
|
+
* - Others: raw stdout (truncated)
|
|
9
|
+
*/
|
|
10
|
+
import { readFileSync } from "node:fs";
|
|
11
|
+
/**
|
|
12
|
+
* Parse a Claude --debug-file into condensed log entries.
|
|
13
|
+
*
|
|
14
|
+
* The debug file contains the full conversation log including tool calls
|
|
15
|
+
* and their results. We extract tool names and key parameters (file paths,
|
|
16
|
+
* search queries) but NOT full file contents to keep the resume prompt compact.
|
|
17
|
+
*/
|
|
18
|
+
export function parseClaudeDebugLog(debugFilePath) {
|
|
19
|
+
let content;
|
|
20
|
+
try {
|
|
21
|
+
content = readFileSync(debugFilePath, "utf-8");
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
return [];
|
|
25
|
+
}
|
|
26
|
+
const entries = [];
|
|
27
|
+
// Claude debug logs contain tool_use and tool_result blocks
|
|
28
|
+
// Extract tool calls with their key parameters
|
|
29
|
+
const toolUsePattern = /Tool call:\s*(\w+)\s*\{([^}]*)\}|"type":\s*"tool_use".*?"name":\s*"(\w+)".*?"input":\s*\{([^}]*)\}/g;
|
|
30
|
+
let match;
|
|
31
|
+
while ((match = toolUsePattern.exec(content)) !== null) {
|
|
32
|
+
const toolName = match[1] || match[3];
|
|
33
|
+
const params = match[2] || match[4];
|
|
34
|
+
if (!toolName)
|
|
35
|
+
continue;
|
|
36
|
+
const summary = extractKeyParams(toolName, params);
|
|
37
|
+
entries.push({ action: toolName, summary });
|
|
38
|
+
}
|
|
39
|
+
// If structured parsing found nothing, try line-by-line patterns
|
|
40
|
+
if (entries.length === 0) {
|
|
41
|
+
for (const line of content.split("\n")) {
|
|
42
|
+
// Look for common Claude debug log patterns
|
|
43
|
+
const writeMatch = line.match(/(?:Write|Edit|Create)\s+(?:file:?\s*)?[`"]?([^\s`"]+\.\w+)/i);
|
|
44
|
+
if (writeMatch) {
|
|
45
|
+
entries.push({ action: "Write", summary: writeMatch[1] });
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
const readMatch = line.match(/(?:Read)\s+(?:file:?\s*)?[`"]?([^\s`"]+\.\w+)/i);
|
|
49
|
+
if (readMatch) {
|
|
50
|
+
entries.push({ action: "Read", summary: readMatch[1] });
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
const searchMatch = line.match(/(?:Search|Grep|Glob)\s+.*?["']([^"']+)["']/i);
|
|
54
|
+
if (searchMatch) {
|
|
55
|
+
entries.push({ action: "Search", summary: searchMatch[1] });
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
const bashMatch = line.match(/(?:Bash|Shell|Execute)\s+.*?["'`]([^"'`]+)["'`]/i);
|
|
59
|
+
if (bashMatch) {
|
|
60
|
+
entries.push({
|
|
61
|
+
action: "Bash",
|
|
62
|
+
summary: bashMatch[1].slice(0, 80),
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return entries;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Parse Codex JSONL output into condensed log entries.
|
|
71
|
+
*/
|
|
72
|
+
export function parseCodexOutput(stdout) {
|
|
73
|
+
const entries = [];
|
|
74
|
+
for (const line of stdout.split("\n")) {
|
|
75
|
+
if (!line.trim())
|
|
76
|
+
continue;
|
|
77
|
+
try {
|
|
78
|
+
const event = JSON.parse(line);
|
|
79
|
+
if (event.type === "item.completed") {
|
|
80
|
+
if (event.item?.type === "tool_call") {
|
|
81
|
+
const toolName = event.item.name ?? "tool";
|
|
82
|
+
const summary = event.item.arguments
|
|
83
|
+
? extractKeyParams(toolName, JSON.stringify(event.item.arguments))
|
|
84
|
+
: "";
|
|
85
|
+
entries.push({ action: toolName, summary });
|
|
86
|
+
}
|
|
87
|
+
else if (event.item?.type === "agent_message" && event.item.text) {
|
|
88
|
+
entries.push({
|
|
89
|
+
action: "text",
|
|
90
|
+
summary: event.item.text.slice(0, 100),
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
catch {
|
|
96
|
+
/* skip non-JSON lines */
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return entries;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Parse raw stdout into condensed log entries (fallback for unknown agents).
|
|
103
|
+
*/
|
|
104
|
+
export function parseRawOutput(output) {
|
|
105
|
+
const entries = [];
|
|
106
|
+
for (const line of output.split("\n")) {
|
|
107
|
+
const writeMatch = line.match(/(?:Created|Modified|Updated|Wrote|Edited)\s+(?:file:?\s*)?[`"]?([^\s`"]+\.\w+)/i);
|
|
108
|
+
if (writeMatch) {
|
|
109
|
+
entries.push({ action: "Write", summary: writeMatch[1] });
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
return entries;
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Format log entries into a condensed markdown timeline for the resume prompt.
|
|
116
|
+
*
|
|
117
|
+
* Keeps the output compact — file paths and search queries only, no content.
|
|
118
|
+
* Groups consecutive same-action entries (e.g., "Wrote 15 files: ...").
|
|
119
|
+
*/
|
|
120
|
+
export function formatLogTimeline(entries) {
|
|
121
|
+
if (entries.length === 0)
|
|
122
|
+
return "(no tool calls captured)";
|
|
123
|
+
const lines = [];
|
|
124
|
+
let i = 0;
|
|
125
|
+
while (i < entries.length) {
|
|
126
|
+
const entry = entries[i];
|
|
127
|
+
// Group consecutive same-action entries
|
|
128
|
+
let groupEnd = i + 1;
|
|
129
|
+
while (groupEnd < entries.length &&
|
|
130
|
+
entries[groupEnd].action === entry.action) {
|
|
131
|
+
groupEnd++;
|
|
132
|
+
}
|
|
133
|
+
const groupSize = groupEnd - i;
|
|
134
|
+
if (groupSize > 3 && entry.action !== "text") {
|
|
135
|
+
// Collapse large groups
|
|
136
|
+
const summaries = entries
|
|
137
|
+
.slice(i, groupEnd)
|
|
138
|
+
.map((e) => e.summary)
|
|
139
|
+
.filter(Boolean);
|
|
140
|
+
const preview = summaries.slice(0, 3).join(", ");
|
|
141
|
+
const more = summaries.length > 3 ? ` (+${summaries.length - 3} more)` : "";
|
|
142
|
+
lines.push(`${lines.length + 1}. ${entry.action} ${groupSize} items: ${preview}${more}`);
|
|
143
|
+
i = groupEnd;
|
|
144
|
+
}
|
|
145
|
+
else {
|
|
146
|
+
// Individual entries
|
|
147
|
+
for (let j = i; j < groupEnd; j++) {
|
|
148
|
+
const e = entries[j];
|
|
149
|
+
const desc = e.summary ? ` ${e.summary}` : "";
|
|
150
|
+
lines.push(`${lines.length + 1}. ${e.action}${desc}`);
|
|
151
|
+
}
|
|
152
|
+
i = groupEnd;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
return lines.join("\n");
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Extract key parameters from a tool call — file paths and search queries,
|
|
159
|
+
* NOT full file contents. Keeps the resume prompt compact.
|
|
160
|
+
*/
|
|
161
|
+
function extractKeyParams(toolName, paramsStr) {
|
|
162
|
+
const lower = toolName.toLowerCase();
|
|
163
|
+
// Extract file_path parameter
|
|
164
|
+
const filePathMatch = paramsStr.match(/file_path["']?\s*[:=]\s*["']([^"']+)["']/);
|
|
165
|
+
if (filePathMatch)
|
|
166
|
+
return filePathMatch[1];
|
|
167
|
+
// Extract path parameter
|
|
168
|
+
const pathMatch = paramsStr.match(/["']?path["']?\s*[:=]\s*["']([^"']+)["']/);
|
|
169
|
+
if (pathMatch)
|
|
170
|
+
return pathMatch[1];
|
|
171
|
+
// Extract command for bash/shell tools
|
|
172
|
+
if (lower === "bash" || lower === "shell" || lower === "execute") {
|
|
173
|
+
const cmdMatch = paramsStr.match(/["']?command["']?\s*[:=]\s*["']([^"']+)["']/);
|
|
174
|
+
if (cmdMatch)
|
|
175
|
+
return cmdMatch[1].slice(0, 80);
|
|
176
|
+
}
|
|
177
|
+
// Extract query/pattern for search tools
|
|
178
|
+
if (lower === "search" || lower === "grep" || lower === "glob") {
|
|
179
|
+
const queryMatch = paramsStr.match(/["']?(?:query|pattern)["']?\s*[:=]\s*["']([^"']+)["']/);
|
|
180
|
+
if (queryMatch)
|
|
181
|
+
return queryMatch[1];
|
|
182
|
+
}
|
|
183
|
+
return "";
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Estimate the token count of a string (rough: 1 token ≈ 4 chars).
|
|
187
|
+
*/
|
|
188
|
+
export function estimateTokens(text) {
|
|
189
|
+
return Math.ceil(text.length / 4);
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Build a condensed conversation log from the available sources.
|
|
193
|
+
* Tries Claude debug file first, then Codex JSONL, then raw stdout.
|
|
194
|
+
* Truncates to the token budget if needed.
|
|
195
|
+
*/
|
|
196
|
+
export function buildConversationLog(debugFile, stdout, presetName, tokenBudget = 8_000) {
|
|
197
|
+
let entries;
|
|
198
|
+
if (debugFile && presetName === "claude") {
|
|
199
|
+
entries = parseClaudeDebugLog(debugFile);
|
|
200
|
+
}
|
|
201
|
+
else if (presetName === "codex") {
|
|
202
|
+
entries = parseCodexOutput(stdout);
|
|
203
|
+
}
|
|
204
|
+
else {
|
|
205
|
+
entries = parseRawOutput(stdout);
|
|
206
|
+
}
|
|
207
|
+
// Extract files changed
|
|
208
|
+
const filesChanged = entries
|
|
209
|
+
.filter((e) => ["Write", "Edit", "Create", "write", "edit", "create"].includes(e.action))
|
|
210
|
+
.map((e) => e.summary)
|
|
211
|
+
.filter(Boolean);
|
|
212
|
+
const toolCallCount = entries.filter((e) => e.action !== "text").length;
|
|
213
|
+
let log = formatLogTimeline(entries);
|
|
214
|
+
// Truncate if over budget
|
|
215
|
+
if (estimateTokens(log) > tokenBudget) {
|
|
216
|
+
// Keep first and last entries, summarize the middle
|
|
217
|
+
const lines = log.split("\n");
|
|
218
|
+
const keepFirst = Math.floor(lines.length * 0.3);
|
|
219
|
+
const keepLast = Math.floor(lines.length * 0.2);
|
|
220
|
+
const omitted = lines.length - keepFirst - keepLast;
|
|
221
|
+
log = [
|
|
222
|
+
...lines.slice(0, keepFirst),
|
|
223
|
+
`... (${omitted} steps omitted for brevity) ...`,
|
|
224
|
+
...lines.slice(lines.length - keepLast),
|
|
225
|
+
].join("\n");
|
|
226
|
+
}
|
|
227
|
+
return { log, toolCallCount, filesChanged };
|
|
228
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { buildConversationLog, formatLogTimeline, parseCodexOutput, parseRawOutput, } from "./log-parser.js";
|
|
3
|
+
describe("parseRawOutput", () => {
|
|
4
|
+
it("extracts file write patterns from stdout", () => {
|
|
5
|
+
const output = [
|
|
6
|
+
"Created file: `src/foo.ts`",
|
|
7
|
+
"Modified bar.js",
|
|
8
|
+
"Some other output",
|
|
9
|
+
"Wrote `packages/cli/src/index.ts`",
|
|
10
|
+
].join("\n");
|
|
11
|
+
const entries = parseRawOutput(output);
|
|
12
|
+
expect(entries).toHaveLength(3);
|
|
13
|
+
expect(entries[0]).toEqual({ action: "Write", summary: "src/foo.ts" });
|
|
14
|
+
expect(entries[1]).toEqual({ action: "Write", summary: "bar.js" });
|
|
15
|
+
expect(entries[2]).toEqual({
|
|
16
|
+
action: "Write",
|
|
17
|
+
summary: "packages/cli/src/index.ts",
|
|
18
|
+
});
|
|
19
|
+
});
|
|
20
|
+
it("returns empty array for output with no file patterns", () => {
|
|
21
|
+
const entries = parseRawOutput("Just some plain text output");
|
|
22
|
+
expect(entries).toHaveLength(0);
|
|
23
|
+
});
|
|
24
|
+
});
|
|
25
|
+
describe("parseCodexOutput", () => {
|
|
26
|
+
it("extracts tool calls from JSONL events", () => {
|
|
27
|
+
const output = [
|
|
28
|
+
JSON.stringify({
|
|
29
|
+
type: "item.completed",
|
|
30
|
+
item: {
|
|
31
|
+
type: "tool_call",
|
|
32
|
+
name: "write_file",
|
|
33
|
+
arguments: { path: "src/foo.ts" },
|
|
34
|
+
},
|
|
35
|
+
}),
|
|
36
|
+
JSON.stringify({
|
|
37
|
+
type: "item.completed",
|
|
38
|
+
item: { type: "agent_message", text: "Done with the task" },
|
|
39
|
+
}),
|
|
40
|
+
].join("\n");
|
|
41
|
+
const entries = parseCodexOutput(output);
|
|
42
|
+
expect(entries).toHaveLength(2);
|
|
43
|
+
expect(entries[0].action).toBe("write_file");
|
|
44
|
+
expect(entries[1]).toEqual({
|
|
45
|
+
action: "text",
|
|
46
|
+
summary: "Done with the task",
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
it("skips non-JSON lines", () => {
|
|
50
|
+
const output = "not json\n{invalid\n";
|
|
51
|
+
const entries = parseCodexOutput(output);
|
|
52
|
+
expect(entries).toHaveLength(0);
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
describe("formatLogTimeline", () => {
|
|
56
|
+
it("formats individual entries with numbered steps", () => {
|
|
57
|
+
const entries = [
|
|
58
|
+
{ action: "Read", summary: "src/foo.ts" },
|
|
59
|
+
{ action: "Write", summary: "src/bar.ts" },
|
|
60
|
+
];
|
|
61
|
+
const result = formatLogTimeline(entries);
|
|
62
|
+
expect(result).toBe("1. Read src/foo.ts\n2. Write src/bar.ts");
|
|
63
|
+
});
|
|
64
|
+
it("groups consecutive same-action entries when > 3", () => {
|
|
65
|
+
const entries = [
|
|
66
|
+
{ action: "Write", summary: "a.ts" },
|
|
67
|
+
{ action: "Write", summary: "b.ts" },
|
|
68
|
+
{ action: "Write", summary: "c.ts" },
|
|
69
|
+
{ action: "Write", summary: "d.ts" },
|
|
70
|
+
{ action: "Write", summary: "e.ts" },
|
|
71
|
+
];
|
|
72
|
+
const result = formatLogTimeline(entries);
|
|
73
|
+
expect(result).toContain("Write 5 items");
|
|
74
|
+
expect(result).toContain("a.ts, b.ts, c.ts");
|
|
75
|
+
expect(result).toContain("(+2 more)");
|
|
76
|
+
});
|
|
77
|
+
it("does not group when 3 or fewer consecutive entries", () => {
|
|
78
|
+
const entries = [
|
|
79
|
+
{ action: "Write", summary: "a.ts" },
|
|
80
|
+
{ action: "Write", summary: "b.ts" },
|
|
81
|
+
{ action: "Write", summary: "c.ts" },
|
|
82
|
+
];
|
|
83
|
+
const result = formatLogTimeline(entries);
|
|
84
|
+
expect(result).toBe("1. Write a.ts\n2. Write b.ts\n3. Write c.ts");
|
|
85
|
+
});
|
|
86
|
+
it("returns fallback for empty entries", () => {
|
|
87
|
+
expect(formatLogTimeline([])).toBe("(no tool calls captured)");
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
describe("buildConversationLog", () => {
|
|
91
|
+
it("uses raw output parser for unknown presets", () => {
|
|
92
|
+
const stdout = "Created file: `test.ts`\nDone";
|
|
93
|
+
const result = buildConversationLog(undefined, stdout, "aider");
|
|
94
|
+
expect(result.filesChanged).toContain("test.ts");
|
|
95
|
+
expect(result.toolCallCount).toBe(1);
|
|
96
|
+
expect(result.log).toContain("Write");
|
|
97
|
+
});
|
|
98
|
+
it("groups large batch writes into a compact summary", () => {
|
|
99
|
+
// 200 file writes should be grouped, not listed individually
|
|
100
|
+
const entries = Array.from({ length: 200 }, (_, i) => `Created file: \`file${i}.ts\``).join("\n");
|
|
101
|
+
const result = buildConversationLog(undefined, entries, "generic");
|
|
102
|
+
expect(result.toolCallCount).toBe(200);
|
|
103
|
+
expect(result.log).toContain("Write 200 items");
|
|
104
|
+
// The grouped output should be compact (single line)
|
|
105
|
+
expect(result.log.split("\n")).toHaveLength(1);
|
|
106
|
+
});
|
|
107
|
+
it("handles empty output gracefully", () => {
|
|
108
|
+
const result = buildConversationLog(undefined, "", "claude");
|
|
109
|
+
expect(result.toolCallCount).toBe(0);
|
|
110
|
+
expect(result.filesChanged).toHaveLength(0);
|
|
111
|
+
expect(result.log).toBe("(no tool calls captured)");
|
|
112
|
+
});
|
|
113
|
+
});
|
package/dist/orchestrator.d.ts
CHANGED
|
@@ -42,6 +42,8 @@ export declare class Orchestrator {
|
|
|
42
42
|
listTeammates(): string[];
|
|
43
43
|
/** Get the registry for direct access */
|
|
44
44
|
getRegistry(): Registry;
|
|
45
|
+
/** Get the adapter for direct access (used by /interrupt) */
|
|
46
|
+
getAdapter(): AgentAdapter;
|
|
45
47
|
/**
|
|
46
48
|
* Assign a task to a specific teammate and execute it.
|
|
47
49
|
* If the result contains a handoff, follows the chain automatically.
|
package/dist/orchestrator.js
CHANGED
|
@@ -42,6 +42,10 @@ export class Orchestrator {
|
|
|
42
42
|
getRegistry() {
|
|
43
43
|
return this.registry;
|
|
44
44
|
}
|
|
45
|
+
/** Get the adapter for direct access (used by /interrupt) */
|
|
46
|
+
getAdapter() {
|
|
47
|
+
return this.adapter;
|
|
48
|
+
}
|
|
45
49
|
/**
|
|
46
50
|
* Assign a task to a specific teammate and execute it.
|
|
47
51
|
* If the result contains a handoff, follows the chain automatically.
|
package/dist/types.d.ts
CHANGED
|
@@ -123,6 +123,7 @@ export type QueueEntry = {
|
|
|
123
123
|
teammate: string;
|
|
124
124
|
task: string;
|
|
125
125
|
system?: boolean;
|
|
126
|
+
migration?: boolean;
|
|
126
127
|
} | {
|
|
127
128
|
type: "compact";
|
|
128
129
|
teammate: string;
|
|
@@ -144,6 +145,23 @@ export type QueueEntry = {
|
|
|
144
145
|
teammate: string;
|
|
145
146
|
task: string;
|
|
146
147
|
};
|
|
148
|
+
/** State captured when an agent is interrupted mid-task. */
|
|
149
|
+
export interface InterruptState {
|
|
150
|
+
/** The teammate that was interrupted */
|
|
151
|
+
teammate: string;
|
|
152
|
+
/** The original task prompt (user-facing, not the full wrapped prompt) */
|
|
153
|
+
originalTask: string;
|
|
154
|
+
/** The full prompt sent to the agent (identity + memory + task) */
|
|
155
|
+
originalFullPrompt: string;
|
|
156
|
+
/** Condensed conversation log from the interrupted session */
|
|
157
|
+
conversationLog: string;
|
|
158
|
+
/** How long the agent ran before interruption (ms) */
|
|
159
|
+
elapsedMs: number;
|
|
160
|
+
/** Number of tool calls made before interruption */
|
|
161
|
+
toolCallCount: number;
|
|
162
|
+
/** Files written/modified before interruption */
|
|
163
|
+
filesChanged: string[];
|
|
164
|
+
}
|
|
147
165
|
/** A registered slash command. */
|
|
148
166
|
export interface SlashCommand {
|
|
149
167
|
name: string;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@teammates/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "Agent-agnostic CLI for teammates. Routes tasks, manages handoffs, and plugs into any coding agent backend.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -34,8 +34,8 @@
|
|
|
34
34
|
"license": "MIT",
|
|
35
35
|
"dependencies": {
|
|
36
36
|
"@github/copilot-sdk": "^0.1.32",
|
|
37
|
-
"@teammates/consolonia": "0.
|
|
38
|
-
"@teammates/recall": "0.
|
|
37
|
+
"@teammates/consolonia": "0.6.0",
|
|
38
|
+
"@teammates/recall": "0.6.0",
|
|
39
39
|
"chalk": "^5.6.2",
|
|
40
40
|
"ora": "^9.3.0"
|
|
41
41
|
},
|