@codemap-ai/cli 0.1.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/.claude-plugin/plugin.json +12 -0
- package/.codex-plugin/plugin.json +35 -0
- package/.cursor-plugin/plugin.json +16 -0
- package/agent-pack/README.md +44 -0
- package/agent-pack/agents/codemap-explorer.md +12 -0
- package/agent-pack/commands/feature-area.md +8 -0
- package/agent-pack/rules/install.md +35 -0
- package/agent-pack/rules/mcp-first.md +18 -0
- package/agent-pack/rules/task-lifecycle.md +11 -0
- package/agent-pack/rules/workflow-skills.md +97 -0
- package/agent-pack/skills/brainstorming/SKILL.md +41 -0
- package/agent-pack/skills/executing-plans/SKILL.md +26 -0
- package/agent-pack/skills/feature-area-investigation/SKILL.md +15 -0
- package/agent-pack/skills/interpreting-codemap-output/SKILL.md +29 -0
- package/agent-pack/skills/mcp-first-exploration/SKILL.md +10 -0
- package/agent-pack/skills/safe-edit-and-reimport/SKILL.md +18 -0
- package/agent-pack/skills/symbol-level-debugging/SKILL.md +15 -0
- package/agent-pack/skills/test-driven-development/SKILL.md +36 -0
- package/agent-pack/skills/token-efficient-code-review/SKILL.md +15 -0
- package/agent-pack/skills/verification-before-completion/SKILL.md +25 -0
- package/agent-pack/skills/writing-plans/SKILL.md +32 -0
- package/agent-pack/templates/AGENTS.md +21 -0
- package/agent-pack/templates/CLAUDE.md +19 -0
- package/agent-pack/templates/COPILOT.md +75 -0
- package/agent-pack/templates/GEMINI.md +77 -0
- package/agent-pack/templates/design-spec.md +34 -0
- package/agent-pack/templates/implementation-plan.md +26 -0
- package/agent-pack/templates/opencode-AGENTS.md +76 -0
- package/agent-pack/templates/opencode-INSTALL.md +5 -0
- package/agent-pack/templates/verification-report.md +18 -0
- package/dist/chat-terminal-DFFYQ6AF.js +1743 -0
- package/dist/chunk-A2QHID6K.js +34 -0
- package/dist/chunk-AXGGL47C.js +457 -0
- package/dist/chunk-FFFJKKKM.js +218262 -0
- package/dist/chunk-KWYP3ADN.js +1742 -0
- package/dist/chunk-WNJNT3FC.js +216 -0
- package/dist/chunk-YRXVMFXS.js +99 -0
- package/dist/index.js +9225 -0
- package/dist/login-screen-2DJBDM47.js +143 -0
- package/dist/pi-tui-app-GVZ3AN42.js +2073 -0
- package/package.json +59 -0
|
@@ -0,0 +1,1743 @@
|
|
|
1
|
+
import{createRequire as __cmr}from"node:module";import{fileURLToPath as __f2p}from"node:url";import{dirname as __dn}from"node:path";var require=__cmr(import.meta.url);var __filename=__f2p(import.meta.url);var __dirname=__dn(__filename);
|
|
2
|
+
import {
|
|
3
|
+
fetchResourceContext
|
|
4
|
+
} from "./chunk-WNJNT3FC.js";
|
|
5
|
+
import {
|
|
6
|
+
executeCommand,
|
|
7
|
+
getCachedContext,
|
|
8
|
+
getCommandList,
|
|
9
|
+
loadOrSynthesizeAll,
|
|
10
|
+
loadThreadIntoUI,
|
|
11
|
+
runShell,
|
|
12
|
+
warmupFileSearch
|
|
13
|
+
} from "./chunk-KWYP3ADN.js";
|
|
14
|
+
import {
|
|
15
|
+
buildLocalIndex,
|
|
16
|
+
getMastraCurrentModelId,
|
|
17
|
+
getMastraThreadId,
|
|
18
|
+
getMastraThreadTokenUsage,
|
|
19
|
+
readWorkspacePath,
|
|
20
|
+
refreshLocalFile,
|
|
21
|
+
removeLocalFile,
|
|
22
|
+
resetHarnessSingleton,
|
|
23
|
+
resolveGatewayModel,
|
|
24
|
+
runMultiPhaseWithMastra,
|
|
25
|
+
runWithMastraHarness,
|
|
26
|
+
switchMastraThread,
|
|
27
|
+
tryGetCurrentWorkspaceInfo,
|
|
28
|
+
warmupHarness
|
|
29
|
+
} from "./chunk-FFFJKKKM.js";
|
|
30
|
+
import {
|
|
31
|
+
C_ERROR,
|
|
32
|
+
C_SUCCESS,
|
|
33
|
+
RESET
|
|
34
|
+
} from "./chunk-YRXVMFXS.js";
|
|
35
|
+
import "./chunk-AXGGL47C.js";
|
|
36
|
+
|
|
37
|
+
// src/cli-agent/chat/ui/chat-terminal.ts
|
|
38
|
+
import path2 from "path";
|
|
39
|
+
|
|
40
|
+
// src/cli-agent/chat/harness/cli-runtime.ts
|
|
41
|
+
async function runSingleAgentRuntime(input) {
|
|
42
|
+
return runWithMastraHarness(input);
|
|
43
|
+
}
|
|
44
|
+
async function runMultiPhaseAgentRuntime(input) {
|
|
45
|
+
return runMultiPhaseWithMastra(input);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// src/cli-agent/core/mention-context.ts
|
|
49
|
+
import path from "path";
|
|
50
|
+
import { readFile, stat } from "fs/promises";
|
|
51
|
+
var MAX_FILE_CHARS = 24e3;
|
|
52
|
+
var MAX_TOTAL_CHARS = 6e4;
|
|
53
|
+
var MAX_MENTIONED_FILES = 8;
|
|
54
|
+
async function hydrateMentionContext(message) {
|
|
55
|
+
const mentions = extractFileMentions(message).slice(0, MAX_MENTIONED_FILES);
|
|
56
|
+
if (mentions.length === 0) {
|
|
57
|
+
return { content: message, files: [], warnings: [] };
|
|
58
|
+
}
|
|
59
|
+
const workspacePath = await readWorkspacePath();
|
|
60
|
+
const files = [];
|
|
61
|
+
const warnings = [];
|
|
62
|
+
const blocks = [];
|
|
63
|
+
let totalChars = 0;
|
|
64
|
+
for (const mentionPath of mentions) {
|
|
65
|
+
const safePath = normalizeMentionPath(mentionPath);
|
|
66
|
+
if (!safePath) {
|
|
67
|
+
warnings.push(`Skipped unsafe file mention: ${mentionPath}`);
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
const absolutePath = path.join(workspacePath, safePath);
|
|
71
|
+
try {
|
|
72
|
+
const fileStat = await stat(absolutePath);
|
|
73
|
+
if (!fileStat.isFile()) {
|
|
74
|
+
warnings.push(`Mention is not a file: ${safePath}`);
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
const raw = await readFile(absolutePath, "utf8");
|
|
78
|
+
const remaining = MAX_TOTAL_CHARS - totalChars;
|
|
79
|
+
if (remaining <= 0) {
|
|
80
|
+
warnings.push(
|
|
81
|
+
"Skipped remaining file mentions because context is full."
|
|
82
|
+
);
|
|
83
|
+
break;
|
|
84
|
+
}
|
|
85
|
+
const limit = Math.min(MAX_FILE_CHARS, remaining);
|
|
86
|
+
const content = raw.length > limit ? raw.slice(0, limit) : raw;
|
|
87
|
+
totalChars += content.length;
|
|
88
|
+
files.push(safePath);
|
|
89
|
+
blocks.push(formatFileBlock(safePath, content, raw.length > limit));
|
|
90
|
+
} catch (error) {
|
|
91
|
+
if (error instanceof Error && "code" in error && error.code === "ENOENT") {
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
const message2 = error instanceof Error ? error.message : String(error);
|
|
95
|
+
warnings.push(`Could not read ${safePath}: ${message2}`);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
if (blocks.length === 0) {
|
|
99
|
+
return { content: message, files, warnings };
|
|
100
|
+
}
|
|
101
|
+
return {
|
|
102
|
+
content: `${message}
|
|
103
|
+
|
|
104
|
+
${formatContextBlock(blocks)}`,
|
|
105
|
+
files,
|
|
106
|
+
warnings
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
function extractFileMentions(message) {
|
|
110
|
+
const matches = message.matchAll(/@([^\s`"'<>]+)/g);
|
|
111
|
+
const seen = /* @__PURE__ */ new Set();
|
|
112
|
+
const mentions = [];
|
|
113
|
+
for (const match of matches) {
|
|
114
|
+
const mention = stripTrailingPunctuation(match[1] ?? "");
|
|
115
|
+
if (!mention || seen.has(mention) || !looksLikeFilePath(mention)) continue;
|
|
116
|
+
seen.add(mention);
|
|
117
|
+
mentions.push(mention);
|
|
118
|
+
}
|
|
119
|
+
return mentions;
|
|
120
|
+
}
|
|
121
|
+
function looksLikeFilePath(input) {
|
|
122
|
+
if (input.includes("/")) return true;
|
|
123
|
+
if (input.startsWith(".")) return true;
|
|
124
|
+
if (/\.[a-zA-Z0-9]{1,10}$/.test(input)) return true;
|
|
125
|
+
return false;
|
|
126
|
+
}
|
|
127
|
+
function normalizeMentionPath(input) {
|
|
128
|
+
const normalized = path.posix.normalize(input.replace(/\\/g, "/"));
|
|
129
|
+
if (!normalized || normalized === "." || normalized.startsWith("../") || normalized.startsWith("/")) {
|
|
130
|
+
return null;
|
|
131
|
+
}
|
|
132
|
+
return normalized;
|
|
133
|
+
}
|
|
134
|
+
function stripTrailingPunctuation(input) {
|
|
135
|
+
return input.replace(/[.,;:!?]+$/, "");
|
|
136
|
+
}
|
|
137
|
+
function formatContextBlock(blocks) {
|
|
138
|
+
return `<mentioned_files>
|
|
139
|
+
${blocks.join("\n\n")}
|
|
140
|
+
</mentioned_files>`;
|
|
141
|
+
}
|
|
142
|
+
function formatFileBlock(filePath, content, truncated) {
|
|
143
|
+
const suffix = truncated ? "\n\n[truncated]" : "";
|
|
144
|
+
return `<file path="${escapeAttribute(filePath)}">
|
|
145
|
+
${content}${suffix}
|
|
146
|
+
</file>`;
|
|
147
|
+
}
|
|
148
|
+
function escapeAttribute(input) {
|
|
149
|
+
return input.replace(/&/g, "&").replace(/"/g, """);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// src/cli-agent/core/task-classifier.ts
|
|
153
|
+
var CLASSIFIER_SYSTEM = `You are a task router for a coding assistant. Analyze the user message and respond with ONLY a JSON object.
|
|
154
|
+
|
|
155
|
+
Output format:
|
|
156
|
+
{"phase":"single"|"multi","taskType":"feature"|"bugfix"|"debugging"|"review"|"refactor"|"research"|"general","effort":"low"|"medium"|"high","reason":"<one line>"}
|
|
157
|
+
|
|
158
|
+
Rules:
|
|
159
|
+
- phase "multi": ONLY for large features spanning multiple modules, major architectural refactors, or when user explicitly says "make a plan" / "plan first". Requires genuine multi-step planning before coding.
|
|
160
|
+
- phase "single": everything else \u2014 including simple file edits, bug fixes, test changes, adding a function, renaming things, one-file changes, quick fixes, explaining code, Q&A, and ALL lookup/search/find/explain tasks. Default to "single" unless the task is clearly large/complex.
|
|
161
|
+
- CRITICAL: phase "multi" is NEVER correct for lookup, search, find, explain, or read-only tasks. If the task does not produce code changes, it is always "single".
|
|
162
|
+
- effort "high": complex multi-file debugging, architectural decisions, security/auth/payment changes, multi-phase tasks, user says "carefully"/"thoroughly"/"deep dive", investigating intermittent/hard-to-reproduce issues
|
|
163
|
+
- effort "medium": standard coding tasks \u2014 add a feature, fix a bug, write tests, refactor a module
|
|
164
|
+
- effort "low": rename, one-liner fix, explaining code, lookup/search tasks, Q&A
|
|
165
|
+
|
|
166
|
+
Examples:
|
|
167
|
+
- "fix the bug in auth.ts" \u2192 single, bugfix, medium
|
|
168
|
+
- "add a unit test for parseDate" \u2192 single, feature, low
|
|
169
|
+
- "implement pagination for the project list" \u2192 single, feature, medium
|
|
170
|
+
- "rename variable X to Y in file Z" \u2192 single, refactor, low
|
|
171
|
+
- "s\u1EEDa 1 d\xF2ng trong file X" / "delete line X" \u2192 single, bugfix, low
|
|
172
|
+
- "debug t\u1EA1i sao auth redirect b\u1ECB l\u1ED7i" \u2192 single, debugging, high
|
|
173
|
+
- "fix the race condition in the payment flow" \u2192 single, bugfix, high
|
|
174
|
+
- "investigate why the import worker crashes intermittently" \u2192 single, debugging, high
|
|
175
|
+
- "explain how this function works" \u2192 single, review, low
|
|
176
|
+
- "t\xECm \u0111o\u1EA1n code render X" / "find where X is rendered" \u2192 single, review, low
|
|
177
|
+
- "how does X work" / "ch\u1ED7 n\xE0o x\u1EED l\xFD X" \u2192 single, research, low
|
|
178
|
+
- "implement full OAuth2 system across auth/web/api modules" \u2192 multi, feature, high
|
|
179
|
+
- "refactor the entire database layer" \u2192 multi, refactor, high
|
|
180
|
+
- "make a plan for adding notifications" \u2192 multi, feature, high
|
|
181
|
+
|
|
182
|
+
Respond with ONLY the JSON.`;
|
|
183
|
+
var FALLBACK = {
|
|
184
|
+
phase: "single",
|
|
185
|
+
taskType: "general",
|
|
186
|
+
effort: "medium",
|
|
187
|
+
reason: "classification failed"
|
|
188
|
+
};
|
|
189
|
+
var CONFIRMATION_SYNONYMS = /* @__PURE__ */ new Set([
|
|
190
|
+
"ok",
|
|
191
|
+
"okay",
|
|
192
|
+
"yes",
|
|
193
|
+
"y",
|
|
194
|
+
"go",
|
|
195
|
+
"proceed",
|
|
196
|
+
"sure",
|
|
197
|
+
"do it",
|
|
198
|
+
"implement",
|
|
199
|
+
"\u1EEB",
|
|
200
|
+
"\u1EEBm",
|
|
201
|
+
"\u0111\u1ED3ng \xFD",
|
|
202
|
+
"\u0111\u01B0\u1EE3c",
|
|
203
|
+
"l\xE0m \u0111i",
|
|
204
|
+
"l\xE0m lu\xF4n",
|
|
205
|
+
"ti\u1EBFp t\u1EE5c",
|
|
206
|
+
"ok lu\xF4n",
|
|
207
|
+
"okie",
|
|
208
|
+
"yep",
|
|
209
|
+
"yup",
|
|
210
|
+
"oke"
|
|
211
|
+
]);
|
|
212
|
+
var CONFIRMATION_RESULT = {
|
|
213
|
+
phase: "single",
|
|
214
|
+
taskType: "general",
|
|
215
|
+
effort: "medium",
|
|
216
|
+
reason: "confirmation \u2014 continuing coding task"
|
|
217
|
+
};
|
|
218
|
+
async function classifyTask(message, provider, model, signal) {
|
|
219
|
+
const controller = new AbortController();
|
|
220
|
+
const timeout = setTimeout(() => controller.abort(), 8e3);
|
|
221
|
+
const abort = () => controller.abort();
|
|
222
|
+
signal?.addEventListener("abort", abort, { once: true });
|
|
223
|
+
try {
|
|
224
|
+
if (signal?.aborted) return FALLBACK;
|
|
225
|
+
if (CONFIRMATION_SYNONYMS.has(message.trim().toLowerCase())) {
|
|
226
|
+
return CONFIRMATION_RESULT;
|
|
227
|
+
}
|
|
228
|
+
let raw = "";
|
|
229
|
+
for await (const chunk of provider.stream({
|
|
230
|
+
model,
|
|
231
|
+
system: CLASSIFIER_SYSTEM,
|
|
232
|
+
messages: [{ role: "user", content: message }],
|
|
233
|
+
maxTokens: 120,
|
|
234
|
+
signal: controller.signal
|
|
235
|
+
})) {
|
|
236
|
+
if (chunk.text) raw += chunk.text;
|
|
237
|
+
}
|
|
238
|
+
return parseClassification(raw);
|
|
239
|
+
} catch {
|
|
240
|
+
return FALLBACK;
|
|
241
|
+
} finally {
|
|
242
|
+
clearTimeout(timeout);
|
|
243
|
+
signal?.removeEventListener("abort", abort);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
function parseClassification(raw) {
|
|
247
|
+
const json = raw.replace(/```(?:json)?/gi, "").replace(/```/g, "").trim();
|
|
248
|
+
const parsed = JSON.parse(json);
|
|
249
|
+
if (!isPhase(parsed.phase)) return FALLBACK;
|
|
250
|
+
if (!isTaskType(parsed.taskType)) return FALLBACK;
|
|
251
|
+
const readOnlyTaskType = parsed.taskType === "research" || parsed.taskType === "review";
|
|
252
|
+
const phase = parsed.phase === "multi" && readOnlyTaskType ? "single" : parsed.phase;
|
|
253
|
+
return {
|
|
254
|
+
phase,
|
|
255
|
+
taskType: parsed.taskType,
|
|
256
|
+
effort: isEffort(parsed.effort) ? parsed.effort : "low",
|
|
257
|
+
reason: typeof parsed.reason === "string" ? parsed.reason : ""
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
function isPhase(value) {
|
|
261
|
+
return value === "single" || value === "multi";
|
|
262
|
+
}
|
|
263
|
+
function isTaskType(value) {
|
|
264
|
+
return value === "feature" || value === "bugfix" || value === "debugging" || value === "review" || value === "refactor" || value === "research" || value === "general";
|
|
265
|
+
}
|
|
266
|
+
function isEffort(value) {
|
|
267
|
+
return value === "low" || value === "medium" || value === "high";
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// src/cli-agent/core/debug-logger.ts
|
|
271
|
+
import { appendFileSync, mkdirSync, existsSync } from "fs";
|
|
272
|
+
import { join, dirname } from "path";
|
|
273
|
+
function findGitRoot() {
|
|
274
|
+
let dir = process.cwd();
|
|
275
|
+
while (true) {
|
|
276
|
+
if (existsSync(join(dir, ".git"))) return dir;
|
|
277
|
+
const parent = dirname(dir);
|
|
278
|
+
if (parent === dir) break;
|
|
279
|
+
dir = parent;
|
|
280
|
+
}
|
|
281
|
+
return process.cwd();
|
|
282
|
+
}
|
|
283
|
+
var LOG_DIR = join(findGitRoot(), ".codemap", "debug-logs");
|
|
284
|
+
function ensureDir() {
|
|
285
|
+
if (!existsSync(LOG_DIR)) {
|
|
286
|
+
mkdirSync(LOG_DIR, { recursive: true });
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
function createDebugLogger() {
|
|
290
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 19);
|
|
291
|
+
const logFile = join(LOG_DIR, `chat-${timestamp}.jsonl`);
|
|
292
|
+
let requestIdx = 0;
|
|
293
|
+
ensureDir();
|
|
294
|
+
function write(entry) {
|
|
295
|
+
appendFileSync(
|
|
296
|
+
logFile,
|
|
297
|
+
JSON.stringify(sanitizeForLog({ ...entry, ts: (/* @__PURE__ */ new Date()).toISOString() })) + "\n"
|
|
298
|
+
);
|
|
299
|
+
}
|
|
300
|
+
return {
|
|
301
|
+
logFile,
|
|
302
|
+
logStreamRequest(info) {
|
|
303
|
+
write({ type: "stream_request", req: requestIdx++, ...info });
|
|
304
|
+
},
|
|
305
|
+
logChunk(chunkIdx, info) {
|
|
306
|
+
write({ type: "chunk", req: requestIdx - 1, chunkIdx, ...info });
|
|
307
|
+
},
|
|
308
|
+
logToolStart(name, args, id) {
|
|
309
|
+
write({ type: "tool_start", name, id, args: args.slice(0, 500) });
|
|
310
|
+
},
|
|
311
|
+
logToolResult(name, result) {
|
|
312
|
+
write({ type: "tool_result", name, result: result.slice(0, 500) });
|
|
313
|
+
},
|
|
314
|
+
logToolFallback(reason) {
|
|
315
|
+
write({ type: "tool_fallback", reason });
|
|
316
|
+
},
|
|
317
|
+
logError(err) {
|
|
318
|
+
const entry = {
|
|
319
|
+
type: "error",
|
|
320
|
+
message: err instanceof Error ? err.message : String(err)
|
|
321
|
+
};
|
|
322
|
+
if (err instanceof Error && err.stack) {
|
|
323
|
+
entry.stack = err.stack.split("\n").slice(0, 5).join("\n");
|
|
324
|
+
}
|
|
325
|
+
if (err instanceof Error && "cause" in err && err.cause) {
|
|
326
|
+
entry.cause = String(err.cause);
|
|
327
|
+
}
|
|
328
|
+
write(entry);
|
|
329
|
+
},
|
|
330
|
+
logSummary(info) {
|
|
331
|
+
write({ type: "summary", ...info });
|
|
332
|
+
},
|
|
333
|
+
logDebugInfo(info) {
|
|
334
|
+
write({ type: "debug_info", ...info });
|
|
335
|
+
}
|
|
336
|
+
};
|
|
337
|
+
}
|
|
338
|
+
function sanitizeForLog(value, depth = 0) {
|
|
339
|
+
if (value == null) return value;
|
|
340
|
+
if (typeof value === "string") {
|
|
341
|
+
return value.length > 1e3 ? `${value.slice(0, 1e3)}\u2026[truncated ${value.length}]` : value;
|
|
342
|
+
}
|
|
343
|
+
if (typeof value === "number" || typeof value === "boolean") return value;
|
|
344
|
+
if (Array.isArray(value)) {
|
|
345
|
+
if (depth >= 4) return `[array ${value.length}]`;
|
|
346
|
+
return value.slice(0, 50).map((item) => sanitizeForLog(item, depth + 1));
|
|
347
|
+
}
|
|
348
|
+
if (typeof value === "object") {
|
|
349
|
+
if (depth >= 4) return "[object]";
|
|
350
|
+
const out = {};
|
|
351
|
+
for (const [key, item] of Object.entries(value).slice(0, 80)) {
|
|
352
|
+
out[key] = sanitizeForLog(item, depth + 1);
|
|
353
|
+
}
|
|
354
|
+
return out;
|
|
355
|
+
}
|
|
356
|
+
return String(value);
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
// src/cli-agent/chat/ui/event-bus.ts
|
|
360
|
+
var EventBus = class {
|
|
361
|
+
listeners = /* @__PURE__ */ new Map();
|
|
362
|
+
anyListeners = /* @__PURE__ */ new Set();
|
|
363
|
+
refreshScheduled = false;
|
|
364
|
+
on(type, listener) {
|
|
365
|
+
let set = this.listeners.get(type);
|
|
366
|
+
if (!set) {
|
|
367
|
+
set = /* @__PURE__ */ new Set();
|
|
368
|
+
this.listeners.set(type, set);
|
|
369
|
+
}
|
|
370
|
+
set.add(listener);
|
|
371
|
+
return () => set.delete(listener);
|
|
372
|
+
}
|
|
373
|
+
onAny(listener) {
|
|
374
|
+
this.anyListeners.add(listener);
|
|
375
|
+
return () => this.anyListeners.delete(listener);
|
|
376
|
+
}
|
|
377
|
+
emit(event) {
|
|
378
|
+
const set = this.listeners.get(event.type);
|
|
379
|
+
if (set) {
|
|
380
|
+
for (const fn of set) fn(event);
|
|
381
|
+
}
|
|
382
|
+
for (const fn of this.anyListeners) fn(event);
|
|
383
|
+
}
|
|
384
|
+
/**
|
|
385
|
+
* Schedule a single screen:refresh on the next microtask.
|
|
386
|
+
* Multiple calls in the same tick coalesce into one refresh.
|
|
387
|
+
*/
|
|
388
|
+
scheduleRefresh() {
|
|
389
|
+
if (this.refreshScheduled) return;
|
|
390
|
+
this.refreshScheduled = true;
|
|
391
|
+
queueMicrotask(() => {
|
|
392
|
+
this.refreshScheduled = false;
|
|
393
|
+
this.emit({ type: "screen:refresh" });
|
|
394
|
+
});
|
|
395
|
+
}
|
|
396
|
+
removeAll() {
|
|
397
|
+
this.listeners.clear();
|
|
398
|
+
this.anyListeners.clear();
|
|
399
|
+
}
|
|
400
|
+
};
|
|
401
|
+
|
|
402
|
+
// src/cli-agent/chat/ui/store.ts
|
|
403
|
+
function createInitialState(opts) {
|
|
404
|
+
return {
|
|
405
|
+
screen: "main",
|
|
406
|
+
messages: [],
|
|
407
|
+
taskList: [],
|
|
408
|
+
taskListVisible: true,
|
|
409
|
+
task: {
|
|
410
|
+
phase: "idle",
|
|
411
|
+
toolsCalled: 0
|
|
412
|
+
},
|
|
413
|
+
sessionTokens: 0,
|
|
414
|
+
streaming: {
|
|
415
|
+
active: false,
|
|
416
|
+
content: "",
|
|
417
|
+
entryIndex: -1
|
|
418
|
+
},
|
|
419
|
+
input: {
|
|
420
|
+
busy: false,
|
|
421
|
+
history: [],
|
|
422
|
+
lastUserText: null
|
|
423
|
+
},
|
|
424
|
+
subprocess: {
|
|
425
|
+
active: false,
|
|
426
|
+
command: "",
|
|
427
|
+
logLines: []
|
|
428
|
+
},
|
|
429
|
+
config: {
|
|
430
|
+
model: opts.model,
|
|
431
|
+
debug: opts.debug ?? false,
|
|
432
|
+
availableModels: opts.availableModels ?? []
|
|
433
|
+
},
|
|
434
|
+
chatMode: "auto",
|
|
435
|
+
workspaceState: {
|
|
436
|
+
indexStatus: "unknown",
|
|
437
|
+
isIndexStale: false,
|
|
438
|
+
hasLocalChanges: false,
|
|
439
|
+
changedFilesCount: 0,
|
|
440
|
+
authMode: "local",
|
|
441
|
+
includeDiff: false
|
|
442
|
+
},
|
|
443
|
+
contextState: {
|
|
444
|
+
files: [],
|
|
445
|
+
symbols: [],
|
|
446
|
+
searches: [],
|
|
447
|
+
diffs: [],
|
|
448
|
+
toolCalls: [],
|
|
449
|
+
assumptions: []
|
|
450
|
+
},
|
|
451
|
+
synthRunning: false,
|
|
452
|
+
planMode: false,
|
|
453
|
+
planReview: { active: false, selection: 0, reviseMode: false },
|
|
454
|
+
askQuestion: null,
|
|
455
|
+
debug: opts.debug ?? false,
|
|
456
|
+
debugLogFile: null
|
|
457
|
+
};
|
|
458
|
+
}
|
|
459
|
+
var Store = class {
|
|
460
|
+
state;
|
|
461
|
+
dirty = false;
|
|
462
|
+
bus;
|
|
463
|
+
constructor(initialState, bus) {
|
|
464
|
+
this.state = initialState;
|
|
465
|
+
this.bus = bus;
|
|
466
|
+
}
|
|
467
|
+
getState() {
|
|
468
|
+
return this.state;
|
|
469
|
+
}
|
|
470
|
+
/**
|
|
471
|
+
* Merge a partial state update. Schedules a single screen:refresh
|
|
472
|
+
* on the next microtask, coalescing multiple dispatches in the same tick.
|
|
473
|
+
*/
|
|
474
|
+
dispatch(partial) {
|
|
475
|
+
if (typeof partial === "function") {
|
|
476
|
+
partial = partial(this.state);
|
|
477
|
+
}
|
|
478
|
+
this.state = deepMerge(this.state, partial);
|
|
479
|
+
if (!this.dirty) {
|
|
480
|
+
this.dirty = true;
|
|
481
|
+
queueMicrotask(() => {
|
|
482
|
+
this.dirty = false;
|
|
483
|
+
this.bus.emit({ type: "screen:refresh" });
|
|
484
|
+
});
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
/** Immediate dispatch — used for streaming tokens that need instant redraw. */
|
|
488
|
+
dispatchImmediate(partial) {
|
|
489
|
+
this.state = deepMerge(this.state, partial);
|
|
490
|
+
this.bus.emit({ type: "screen:refresh" });
|
|
491
|
+
}
|
|
492
|
+
};
|
|
493
|
+
function deepMerge(target, source) {
|
|
494
|
+
const result = { ...target };
|
|
495
|
+
for (const key of Object.keys(source)) {
|
|
496
|
+
const srcVal = source[key];
|
|
497
|
+
const tgtVal = target[key];
|
|
498
|
+
if (srcVal !== null && typeof srcVal === "object" && !Array.isArray(srcVal) && tgtVal !== null && typeof tgtVal === "object" && !Array.isArray(tgtVal)) {
|
|
499
|
+
result[key] = deepMerge(tgtVal, srcVal);
|
|
500
|
+
} else if (srcVal !== void 0) {
|
|
501
|
+
result[key] = srcVal;
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
return result;
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
// src/cli-agent/chat/ui/tool-call-messages.ts
|
|
508
|
+
function normalizeToolDisplayName(toolName) {
|
|
509
|
+
return toolName.includes("__") ? toolName.slice(toolName.indexOf("__") + 2) : toolName;
|
|
510
|
+
}
|
|
511
|
+
function parseJsonObject(text) {
|
|
512
|
+
try {
|
|
513
|
+
const parsed = JSON.parse(text);
|
|
514
|
+
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
|
|
515
|
+
} catch {
|
|
516
|
+
return null;
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
function stringField(obj, keys) {
|
|
520
|
+
for (const key of keys) {
|
|
521
|
+
const value = obj[key];
|
|
522
|
+
if (typeof value === "string" && value.trim()) return value.trim();
|
|
523
|
+
}
|
|
524
|
+
return void 0;
|
|
525
|
+
}
|
|
526
|
+
function summarizeToolArgs(toolName, args) {
|
|
527
|
+
const displayName = normalizeToolDisplayName(toolName);
|
|
528
|
+
const parsed = parseJsonObject(args);
|
|
529
|
+
if (!parsed) return `Call ${displayName}`;
|
|
530
|
+
const direct = stringField(parsed, [
|
|
531
|
+
"activeForm",
|
|
532
|
+
"content",
|
|
533
|
+
"path",
|
|
534
|
+
"query",
|
|
535
|
+
"q",
|
|
536
|
+
"url",
|
|
537
|
+
"command",
|
|
538
|
+
"title",
|
|
539
|
+
"question",
|
|
540
|
+
"id"
|
|
541
|
+
]);
|
|
542
|
+
if (direct) return direct;
|
|
543
|
+
const patterns = parsed.pattern;
|
|
544
|
+
if (Array.isArray(patterns) && patterns.length > 0) {
|
|
545
|
+
return patterns.map(String).join(", ");
|
|
546
|
+
}
|
|
547
|
+
return `Call ${displayName}`;
|
|
548
|
+
}
|
|
549
|
+
function summarizeToolResult(resultText) {
|
|
550
|
+
const errorPrefix = "[ERROR] ";
|
|
551
|
+
const isError = resultText.startsWith(errorPrefix);
|
|
552
|
+
const body = isError ? resultText.slice(errorPrefix.length) : resultText;
|
|
553
|
+
const parsed = parseJsonObject(body);
|
|
554
|
+
if (!parsed) return resultText;
|
|
555
|
+
const content = stringField(parsed, ["content", "message", "summary"]);
|
|
556
|
+
if (!content) return resultText;
|
|
557
|
+
return isError ? `${errorPrefix}${content}` : content;
|
|
558
|
+
}
|
|
559
|
+
function withToolCallSummary(messages, toolName, args, toolCallId) {
|
|
560
|
+
const displayName = normalizeToolDisplayName(toolName);
|
|
561
|
+
const next = [...messages];
|
|
562
|
+
for (let i = next.length - 1; i >= 0; i -= 1) {
|
|
563
|
+
const msg = next[i];
|
|
564
|
+
if (!msg) continue;
|
|
565
|
+
if (msg.role === "user") break;
|
|
566
|
+
if (msg.role === "tool_call" && msg.name === displayName && (!toolCallId || msg.toolCallId === toolCallId)) {
|
|
567
|
+
return next;
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
const childLine = summarizeToolArgs(toolName, args);
|
|
571
|
+
const toolCallMsg = {
|
|
572
|
+
role: "tool_call",
|
|
573
|
+
name: displayName,
|
|
574
|
+
toolCallId,
|
|
575
|
+
content: childLine,
|
|
576
|
+
timestamp: Date.now()
|
|
577
|
+
};
|
|
578
|
+
next.push(toolCallMsg);
|
|
579
|
+
return next;
|
|
580
|
+
}
|
|
581
|
+
function markToolDone(messages, toolName, resultText, toolCallId) {
|
|
582
|
+
const success = !resultText.startsWith("[ERROR] ");
|
|
583
|
+
const marker = success ? ` ${C_SUCCESS}\u2713${RESET}` : ` ${C_ERROR}\u2717${RESET}`;
|
|
584
|
+
const displayName = normalizeToolDisplayName(toolName);
|
|
585
|
+
const summarizedResult = summarizeToolResult(resultText);
|
|
586
|
+
const next = [...messages];
|
|
587
|
+
let toolCallIndex = -1;
|
|
588
|
+
for (const mode of ["id", "name"]) {
|
|
589
|
+
if (toolCallIndex >= 0) break;
|
|
590
|
+
if (mode === "id" && !toolCallId) continue;
|
|
591
|
+
for (let i = next.length - 1; i >= 0; i -= 1) {
|
|
592
|
+
const msg = next[i];
|
|
593
|
+
if (!msg) continue;
|
|
594
|
+
if (msg.role === "user") break;
|
|
595
|
+
if (msg.role === "tool_call" && (mode === "id" && msg.toolCallId === toolCallId || mode === "name" && msg.name === displayName)) {
|
|
596
|
+
toolCallIndex = i;
|
|
597
|
+
break;
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
if (toolCallIndex >= 0) {
|
|
602
|
+
const resultName = displayName || next[toolCallIndex]?.name || displayName;
|
|
603
|
+
const toolResult = {
|
|
604
|
+
name: resultName,
|
|
605
|
+
content: summarizedResult,
|
|
606
|
+
fullContent: resultText,
|
|
607
|
+
success
|
|
608
|
+
};
|
|
609
|
+
const existingResults = next[toolCallIndex].toolResults ?? [];
|
|
610
|
+
next[toolCallIndex] = {
|
|
611
|
+
...next[toolCallIndex],
|
|
612
|
+
toolResults: [...existingResults, toolResult],
|
|
613
|
+
content: next[toolCallIndex].content + marker,
|
|
614
|
+
expandedContent: resultText
|
|
615
|
+
};
|
|
616
|
+
return next;
|
|
617
|
+
}
|
|
618
|
+
return next;
|
|
619
|
+
}
|
|
620
|
+
function setToolCallPreview(messages, preview) {
|
|
621
|
+
const next = [...messages];
|
|
622
|
+
for (let i = next.length - 1; i >= 0; i -= 1) {
|
|
623
|
+
const msg = next[i];
|
|
624
|
+
if (!msg) continue;
|
|
625
|
+
if (msg.role === "user") break;
|
|
626
|
+
if (msg.role === "tool_call") {
|
|
627
|
+
next[i] = { ...msg, previewContent: preview };
|
|
628
|
+
return next;
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
return next;
|
|
632
|
+
}
|
|
633
|
+
function markLastPendingToolCallCanceled(messages) {
|
|
634
|
+
const next = [...messages];
|
|
635
|
+
for (let i = next.length - 1; i >= 0; i -= 1) {
|
|
636
|
+
const msg = next[i];
|
|
637
|
+
if (!msg) continue;
|
|
638
|
+
if (msg.role === "user") break;
|
|
639
|
+
if (msg.role !== "tool_call" || (msg.toolResults?.length ?? 0) > 0) {
|
|
640
|
+
continue;
|
|
641
|
+
}
|
|
642
|
+
const name = msg.name ?? "tool";
|
|
643
|
+
const content = "[ERROR] Canceled by user.";
|
|
644
|
+
next[i] = {
|
|
645
|
+
...msg,
|
|
646
|
+
content: msg.content.endsWith(" \u2713") || msg.content.endsWith(" \u2717") ? msg.content : `${msg.content} \u2717`,
|
|
647
|
+
toolResults: [
|
|
648
|
+
...msg.toolResults ?? [],
|
|
649
|
+
{
|
|
650
|
+
name,
|
|
651
|
+
content,
|
|
652
|
+
fullContent: content,
|
|
653
|
+
success: false
|
|
654
|
+
}
|
|
655
|
+
],
|
|
656
|
+
expandedContent: content
|
|
657
|
+
};
|
|
658
|
+
return next;
|
|
659
|
+
}
|
|
660
|
+
return next;
|
|
661
|
+
}
|
|
662
|
+
function isTaskTool(toolName) {
|
|
663
|
+
const normalized = toolName.toLowerCase();
|
|
664
|
+
return normalized.includes("task_write") || normalized.includes("task_update") || normalized.includes("task_complete") || normalized.includes("task_check");
|
|
665
|
+
}
|
|
666
|
+
function syncTaskListFromTool(store, toolName, argsJson, resultJson) {
|
|
667
|
+
if (!isTaskTool(toolName)) return;
|
|
668
|
+
const args = parseJsonObject(argsJson);
|
|
669
|
+
if (!args) return;
|
|
670
|
+
const result = parseJsonObject(resultJson);
|
|
671
|
+
const resultTasks = extractTasksFromResult(result);
|
|
672
|
+
const normalized = toolName.toLowerCase();
|
|
673
|
+
if (normalized.includes("task_write")) {
|
|
674
|
+
handleTaskWrite(store, args, resultTasks);
|
|
675
|
+
} else if (normalized.includes("task_update")) {
|
|
676
|
+
handleTaskUpdate(store, args, resultTasks);
|
|
677
|
+
} else if (normalized.includes("task_complete")) {
|
|
678
|
+
handleTaskComplete(store, args, resultTasks);
|
|
679
|
+
} else if (normalized.includes("task_check")) {
|
|
680
|
+
handleTaskCheck(store, resultTasks);
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
function extractTasksFromResult(result) {
|
|
684
|
+
if (!result) return null;
|
|
685
|
+
const structured = result.structuredContent && typeof result.structuredContent === "object" && !Array.isArray(result.structuredContent) ? result.structuredContent : null;
|
|
686
|
+
if (structured) {
|
|
687
|
+
const data = structured.data;
|
|
688
|
+
if (data && typeof data === "object" && !Array.isArray(data)) {
|
|
689
|
+
const tasks = data.tasks;
|
|
690
|
+
if (Array.isArray(tasks)) {
|
|
691
|
+
const filtered = tasks.filter(
|
|
692
|
+
(t) => typeof t === "object" && t !== null && typeof t.id === "string"
|
|
693
|
+
);
|
|
694
|
+
if (filtered.length > 0) return filtered;
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
const topLevelTasks = result.tasks;
|
|
699
|
+
if (Array.isArray(topLevelTasks)) {
|
|
700
|
+
const filtered = topLevelTasks.filter(
|
|
701
|
+
(t) => typeof t === "object" && t !== null && typeof t.id === "string"
|
|
702
|
+
);
|
|
703
|
+
if (filtered.length > 0) return filtered;
|
|
704
|
+
}
|
|
705
|
+
return null;
|
|
706
|
+
}
|
|
707
|
+
function handleTaskWrite(store, args, resultTasks) {
|
|
708
|
+
const inputTasks = extractInputTasks(args);
|
|
709
|
+
if (!inputTasks && !resultTasks) return;
|
|
710
|
+
const incomingTasks = resultTasks ?? inputTasks;
|
|
711
|
+
if (incomingTasks && incomingTasks.length === 0) {
|
|
712
|
+
store.dispatch({ taskList: [], taskListVisible: false });
|
|
713
|
+
return;
|
|
714
|
+
}
|
|
715
|
+
store.dispatch((prev) => {
|
|
716
|
+
const prevById = new Map(prev.taskList.map((t) => [t.id, t]));
|
|
717
|
+
const source = resultTasks ?? inputTasks ?? [];
|
|
718
|
+
const next = [];
|
|
719
|
+
for (const t of source) {
|
|
720
|
+
if (!t.id) continue;
|
|
721
|
+
const prev2 = prevById.get(t.id);
|
|
722
|
+
next.push({
|
|
723
|
+
id: t.id,
|
|
724
|
+
content: t.content ?? prev2?.content ?? "",
|
|
725
|
+
status: t.status ?? prev2?.status ?? "pending",
|
|
726
|
+
activeForm: t.activeForm ?? prev2?.activeForm ?? ""
|
|
727
|
+
});
|
|
728
|
+
}
|
|
729
|
+
return { taskList: next, taskListVisible: true };
|
|
730
|
+
});
|
|
731
|
+
}
|
|
732
|
+
function handleTaskUpdate(store, args, resultTasks) {
|
|
733
|
+
if (resultTasks && resultTasks.length > 0) {
|
|
734
|
+
store.dispatch({ taskList: resultTasks });
|
|
735
|
+
return;
|
|
736
|
+
}
|
|
737
|
+
const id = typeof args.id === "string" ? args.id : null;
|
|
738
|
+
if (!id) return;
|
|
739
|
+
const content = typeof args.content === "string" ? args.content : void 0;
|
|
740
|
+
const status = typeof args.status === "string" ? args.status : void 0;
|
|
741
|
+
const activeForm = typeof args.activeForm === "string" ? args.activeForm : void 0;
|
|
742
|
+
store.dispatch((prev) => ({
|
|
743
|
+
taskList: prev.taskList.map(
|
|
744
|
+
(t) => t.id === id ? {
|
|
745
|
+
...t,
|
|
746
|
+
...content !== void 0 && { content },
|
|
747
|
+
...status !== void 0 && { status },
|
|
748
|
+
...activeForm !== void 0 && { activeForm }
|
|
749
|
+
} : t
|
|
750
|
+
)
|
|
751
|
+
}));
|
|
752
|
+
}
|
|
753
|
+
function handleTaskComplete(store, args, resultTasks) {
|
|
754
|
+
if (resultTasks && resultTasks.length > 0) {
|
|
755
|
+
store.dispatch({ taskList: resultTasks });
|
|
756
|
+
return;
|
|
757
|
+
}
|
|
758
|
+
const id = typeof args.id === "string" ? args.id : null;
|
|
759
|
+
if (!id) return;
|
|
760
|
+
store.dispatch((prev) => ({
|
|
761
|
+
taskList: prev.taskList.map(
|
|
762
|
+
(t) => t.id === id ? { ...t, status: "completed" } : t
|
|
763
|
+
)
|
|
764
|
+
}));
|
|
765
|
+
}
|
|
766
|
+
function handleTaskCheck(store, resultTasks) {
|
|
767
|
+
if (!resultTasks) return;
|
|
768
|
+
store.dispatch({ taskList: resultTasks });
|
|
769
|
+
}
|
|
770
|
+
function extractInputTasks(args) {
|
|
771
|
+
const tasks = args.tasks;
|
|
772
|
+
if (Array.isArray(tasks)) {
|
|
773
|
+
return tasks.filter(
|
|
774
|
+
(t) => typeof t === "object" && t !== null
|
|
775
|
+
);
|
|
776
|
+
}
|
|
777
|
+
return null;
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
// src/cli-agent/chat/ui/chat-terminal.ts
|
|
781
|
+
function isStrongModel(model) {
|
|
782
|
+
return /\b(strong|opus|sonnet|gpt-5|gpt-4|o3|o4|deepseek-r1|qwen3-coder)\b/i.test(
|
|
783
|
+
model
|
|
784
|
+
);
|
|
785
|
+
}
|
|
786
|
+
function createAbortError() {
|
|
787
|
+
const err = new Error("Task canceled.");
|
|
788
|
+
err.name = "AbortError";
|
|
789
|
+
return err;
|
|
790
|
+
}
|
|
791
|
+
function isAbortError(err) {
|
|
792
|
+
return err instanceof Error && err.name === "AbortError";
|
|
793
|
+
}
|
|
794
|
+
function extractCloudCommitFromGetProject(result) {
|
|
795
|
+
const structured = result?.structuredContent;
|
|
796
|
+
if (!structured || typeof structured !== "object") return void 0;
|
|
797
|
+
const data = structured;
|
|
798
|
+
const commitSha = data.data?.projectContext?.latestImport?.commitSha ?? data.data?.health?.latestImport?.commitSha ?? data.projectContext?.latestImport?.commitSha ?? data.health?.latestImport?.commitSha;
|
|
799
|
+
return typeof commitSha === "string" && commitSha.trim() ? commitSha.trim() : void 0;
|
|
800
|
+
}
|
|
801
|
+
var CODEMAP_AGENT_IDENTITY = [
|
|
802
|
+
"## CodeMap Identity",
|
|
803
|
+
"",
|
|
804
|
+
"You are CodeMap, the AI-powered code intelligence and coding agent CLI.",
|
|
805
|
+
"Mastra and mastracode are internal runtime implementation details, not the product identity.",
|
|
806
|
+
"Never identify yourself as Mastra Code, Mastra, Claude Code, Codex, or another host/runtime.",
|
|
807
|
+
"If asked what AI coding tool you are, answer that you are CodeMap.",
|
|
808
|
+
"Help the user read, understand, modify, and verify code in the current workspace."
|
|
809
|
+
].join("\n");
|
|
810
|
+
function buildSessionContext(modelId) {
|
|
811
|
+
return modelId ? `## Session Info
|
|
812
|
+
|
|
813
|
+
You are running as model: **${modelId}**` : null;
|
|
814
|
+
}
|
|
815
|
+
function buildCurrentTaskContent(content) {
|
|
816
|
+
return [
|
|
817
|
+
"## Current Task",
|
|
818
|
+
"",
|
|
819
|
+
"<task>",
|
|
820
|
+
content,
|
|
821
|
+
"</task>",
|
|
822
|
+
"",
|
|
823
|
+
"Work only on this task. Use repository tools only when they are needed for this task; if the user already named exact files or symbols, inspect those directly."
|
|
824
|
+
].join("\n");
|
|
825
|
+
}
|
|
826
|
+
function buildCodeMapAgentInstructions(resourceContext, projectContext, modelId) {
|
|
827
|
+
const parts = [CODEMAP_AGENT_IDENTITY];
|
|
828
|
+
const sessionContext = buildSessionContext(modelId);
|
|
829
|
+
if (sessionContext) parts.push(sessionContext);
|
|
830
|
+
if (projectContext?.rules) parts.push(projectContext.rules);
|
|
831
|
+
if (projectContext?.conventions) parts.push(projectContext.conventions);
|
|
832
|
+
if (projectContext?.skills) parts.push(projectContext.skills);
|
|
833
|
+
if (resourceContext) parts.push(resourceContext);
|
|
834
|
+
return parts.join("\n\n---\n\n");
|
|
835
|
+
}
|
|
836
|
+
async function abortable(promise, signal) {
|
|
837
|
+
if (signal.aborted) throw createAbortError();
|
|
838
|
+
let cleanup;
|
|
839
|
+
const abortPromise = new Promise((_, reject) => {
|
|
840
|
+
const onAbort = () => reject(createAbortError());
|
|
841
|
+
cleanup = () => signal.removeEventListener("abort", onAbort);
|
|
842
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
843
|
+
});
|
|
844
|
+
return Promise.race([
|
|
845
|
+
promise.then(
|
|
846
|
+
(value) => {
|
|
847
|
+
cleanup?.();
|
|
848
|
+
return value;
|
|
849
|
+
},
|
|
850
|
+
(err) => {
|
|
851
|
+
cleanup?.();
|
|
852
|
+
throw err;
|
|
853
|
+
}
|
|
854
|
+
),
|
|
855
|
+
abortPromise
|
|
856
|
+
]);
|
|
857
|
+
}
|
|
858
|
+
var ChatTerminal = class {
|
|
859
|
+
// Public so App and InputArea can access
|
|
860
|
+
bus;
|
|
861
|
+
store;
|
|
862
|
+
_planReviewResolve = null;
|
|
863
|
+
_askQuestionResolve = null;
|
|
864
|
+
_taskAbort = null;
|
|
865
|
+
_taskSeq = 0;
|
|
866
|
+
_activeTaskId = 0;
|
|
867
|
+
logger = null;
|
|
868
|
+
options;
|
|
869
|
+
// Session-level caches — fetched once on first turn, reused for the entire session.
|
|
870
|
+
_resourceContext = void 0;
|
|
871
|
+
// undefined = not yet fetched
|
|
872
|
+
_projectContext = void 0;
|
|
873
|
+
constructor(options) {
|
|
874
|
+
this.options = options;
|
|
875
|
+
this.bus = new EventBus();
|
|
876
|
+
const debug = process.env.CODEMAP_DEBUG_AGENT_TOOLS === "1";
|
|
877
|
+
this.store = new Store(
|
|
878
|
+
createInitialState({
|
|
879
|
+
model: options.model,
|
|
880
|
+
availableModels: options.availableModels,
|
|
881
|
+
debug
|
|
882
|
+
}),
|
|
883
|
+
this.bus
|
|
884
|
+
);
|
|
885
|
+
if (debug) this.logger = createDebugLogger();
|
|
886
|
+
}
|
|
887
|
+
async refreshWorkspaceCommits() {
|
|
888
|
+
try {
|
|
889
|
+
const info = await tryGetCurrentWorkspaceInfo();
|
|
890
|
+
if (info) {
|
|
891
|
+
const current = this.store.getState().workspace;
|
|
892
|
+
this.store.dispatch({
|
|
893
|
+
workspace: {
|
|
894
|
+
repoName: info.repoName,
|
|
895
|
+
branch: info.branch,
|
|
896
|
+
localCommit: info.commitSha,
|
|
897
|
+
cloudCommit: current?.cloudCommit
|
|
898
|
+
}
|
|
899
|
+
});
|
|
900
|
+
}
|
|
901
|
+
} catch {
|
|
902
|
+
}
|
|
903
|
+
try {
|
|
904
|
+
const result = await this.options.toolClient.callTool("get_project", {
|
|
905
|
+
verbose: false
|
|
906
|
+
});
|
|
907
|
+
const cloudCommit = extractCloudCommitFromGetProject(result);
|
|
908
|
+
if (!cloudCommit) return;
|
|
909
|
+
const current = this.store.getState().workspace;
|
|
910
|
+
if (!current) return;
|
|
911
|
+
this.store.dispatch({
|
|
912
|
+
workspace: {
|
|
913
|
+
repoName: current.repoName,
|
|
914
|
+
branch: current.branch,
|
|
915
|
+
localCommit: current.localCommit,
|
|
916
|
+
cloudCommit
|
|
917
|
+
}
|
|
918
|
+
});
|
|
919
|
+
} catch {
|
|
920
|
+
}
|
|
921
|
+
}
|
|
922
|
+
// ─── Public API for components ───────────────────────────
|
|
923
|
+
/** Cancel the running task and reset to idle. Late async results are ignored by task id. */
|
|
924
|
+
cancelTask() {
|
|
925
|
+
const state = this.store.getState();
|
|
926
|
+
if (!state.input.busy) return null;
|
|
927
|
+
const canceledPrompt = state.input.lastUserText;
|
|
928
|
+
const msgs = state.messages;
|
|
929
|
+
const lastUserIdx = msgs.reduce(
|
|
930
|
+
(acc, m, i) => m.role === "user" ? i : acc,
|
|
931
|
+
-1
|
|
932
|
+
);
|
|
933
|
+
const hasMessagesAfterLastUser = lastUserIdx >= 0 && msgs.slice(lastUserIdx + 1).length > 0;
|
|
934
|
+
const hasStreamingContent = Boolean(state.streaming.content.trim());
|
|
935
|
+
const shouldRollback = !hasMessagesAfterLastUser && !hasStreamingContent;
|
|
936
|
+
this._taskAbort?.abort();
|
|
937
|
+
this._taskAbort = null;
|
|
938
|
+
this._activeTaskId = 0;
|
|
939
|
+
this._planReviewResolve?.("cancel");
|
|
940
|
+
this._planReviewResolve = null;
|
|
941
|
+
this._askQuestionResolve?.("(skipped)");
|
|
942
|
+
this._askQuestionResolve = null;
|
|
943
|
+
const nextMessages = shouldRollback ? lastUserIdx >= 0 ? msgs.slice(0, lastUserIdx) : msgs : markLastPendingToolCallCanceled(msgs);
|
|
944
|
+
this.store.dispatch({
|
|
945
|
+
messages: nextMessages,
|
|
946
|
+
input: { ...state.input, busy: false },
|
|
947
|
+
task: { phase: "idle", toolsCalled: 0 },
|
|
948
|
+
planReview: { active: false, selection: 0 },
|
|
949
|
+
askQuestion: null,
|
|
950
|
+
streaming: { active: false, content: "", entryIndex: -1 }
|
|
951
|
+
});
|
|
952
|
+
return shouldRollback ? canceledPrompt : null;
|
|
953
|
+
}
|
|
954
|
+
/** Called by the multi-phase loop: pause and wait for user plan review. */
|
|
955
|
+
waitForPlanReview() {
|
|
956
|
+
return new Promise((resolve) => {
|
|
957
|
+
this._planReviewResolve = resolve;
|
|
958
|
+
this.store.dispatch({
|
|
959
|
+
planReview: { active: true, selection: 0, reviseMode: false }
|
|
960
|
+
});
|
|
961
|
+
this.bus.scheduleRefresh();
|
|
962
|
+
});
|
|
963
|
+
}
|
|
964
|
+
/** Called by the UI when user makes a plan review decision. */
|
|
965
|
+
resolvePlanReview(action) {
|
|
966
|
+
this._planReviewResolve?.(action);
|
|
967
|
+
this._planReviewResolve = null;
|
|
968
|
+
this.store.dispatch({
|
|
969
|
+
planReview: { active: false, selection: 0, reviseMode: false }
|
|
970
|
+
});
|
|
971
|
+
this.bus.scheduleRefresh();
|
|
972
|
+
}
|
|
973
|
+
/** Called by the multi-phase/single loop: pause and wait for user to answer ask_user question. */
|
|
974
|
+
waitForAskQuestion(questionId, question, options, selectionMode) {
|
|
975
|
+
return new Promise((resolve) => {
|
|
976
|
+
this._askQuestionResolve = resolve;
|
|
977
|
+
this.store.dispatch({
|
|
978
|
+
askQuestion: {
|
|
979
|
+
active: true,
|
|
980
|
+
questionId,
|
|
981
|
+
question,
|
|
982
|
+
options,
|
|
983
|
+
selection: 0,
|
|
984
|
+
selectionMode: selectionMode ?? (options?.length ? "single_select" : void 0),
|
|
985
|
+
selected: []
|
|
986
|
+
}
|
|
987
|
+
});
|
|
988
|
+
this.bus.scheduleRefresh();
|
|
989
|
+
});
|
|
990
|
+
}
|
|
991
|
+
/** Called by the UI when user answers the ask_user question. */
|
|
992
|
+
resolveAskQuestion(answer) {
|
|
993
|
+
this._askQuestionResolve?.(answer);
|
|
994
|
+
this._askQuestionResolve = null;
|
|
995
|
+
this.store.dispatch({ askQuestion: null });
|
|
996
|
+
this.bus.scheduleRefresh();
|
|
997
|
+
}
|
|
998
|
+
// ─── Start ───────────────────────────────────────────────
|
|
999
|
+
async start() {
|
|
1000
|
+
if (!this.options.apiToken && this.options.mcpConfig) {
|
|
1001
|
+
const { showLoginScreen } = await import("./login-screen-2DJBDM47.js");
|
|
1002
|
+
const result = await showLoginScreen(this.options.mcpConfig);
|
|
1003
|
+
if (result === "exit") return;
|
|
1004
|
+
}
|
|
1005
|
+
warmupFileSearch().catch(() => {
|
|
1006
|
+
}).finally(() => this.bus.scheduleRefresh());
|
|
1007
|
+
await this.refreshWorkspaceCommits();
|
|
1008
|
+
const startupModel = this.store.getState().config.model;
|
|
1009
|
+
const mastraTools = await this.options.toolClient.getMastraTools().catch(() => void 0);
|
|
1010
|
+
warmupHarness({
|
|
1011
|
+
toolClient: this.options.toolClient,
|
|
1012
|
+
baseUrl: this.options.provider.baseUrl,
|
|
1013
|
+
apiKey: this.options.provider.apiKey,
|
|
1014
|
+
modelId: startupModel,
|
|
1015
|
+
availableModels: this.store.getState().config.availableModels.map((m) => m.id),
|
|
1016
|
+
onDebug: void 0,
|
|
1017
|
+
extraServerConfigs: this.options.toolClient.getExtraServerConfigs(),
|
|
1018
|
+
mastraTools
|
|
1019
|
+
}).catch(() => {
|
|
1020
|
+
});
|
|
1021
|
+
this.store.dispatch({ synthRunning: true });
|
|
1022
|
+
this.bus.scheduleRefresh();
|
|
1023
|
+
loadOrSynthesizeAll(this.options.provider, this.store.getState().config.model).catch(() => {
|
|
1024
|
+
}).finally(() => {
|
|
1025
|
+
this.store.dispatch({ synthRunning: false });
|
|
1026
|
+
this.bus.scheduleRefresh();
|
|
1027
|
+
});
|
|
1028
|
+
const { startPiTuiApp } = await import("./pi-tui-app-GVZ3AN42.js");
|
|
1029
|
+
await startPiTuiApp(this);
|
|
1030
|
+
}
|
|
1031
|
+
// ─── Session management ───────────────────────────────────
|
|
1032
|
+
persistSession() {
|
|
1033
|
+
}
|
|
1034
|
+
startNewSession() {
|
|
1035
|
+
this.store.dispatch((prev) => ({
|
|
1036
|
+
messages: [],
|
|
1037
|
+
sessionTokens: 0,
|
|
1038
|
+
input: { ...prev.input, history: [] }
|
|
1039
|
+
}));
|
|
1040
|
+
resetHarnessSingleton().catch(() => {
|
|
1041
|
+
});
|
|
1042
|
+
}
|
|
1043
|
+
async loadThreadById(threadId) {
|
|
1044
|
+
try {
|
|
1045
|
+
await switchMastraThread(threadId);
|
|
1046
|
+
await loadThreadIntoUI(
|
|
1047
|
+
threadId,
|
|
1048
|
+
(msgs) => this.store.dispatch({ messages: msgs }),
|
|
1049
|
+
(msg) => this.appendMessage(msg)
|
|
1050
|
+
);
|
|
1051
|
+
const threadUsage = await getMastraThreadTokenUsage().catch(() => null);
|
|
1052
|
+
this.store.dispatch({
|
|
1053
|
+
sessionTokens: threadUsage?.totalTokens ?? 0
|
|
1054
|
+
});
|
|
1055
|
+
} catch (err) {
|
|
1056
|
+
this.appendMessage({
|
|
1057
|
+
role: "system",
|
|
1058
|
+
content: `Failed to load thread: ${err}`
|
|
1059
|
+
});
|
|
1060
|
+
}
|
|
1061
|
+
}
|
|
1062
|
+
// ─── Submit ──────────────────────────────────────────────
|
|
1063
|
+
async handleSubmit(text) {
|
|
1064
|
+
await this.handleSubmitWithContent(text);
|
|
1065
|
+
}
|
|
1066
|
+
async handleSubmitWithContent(text, forceMultiPhase = false, imageFiles) {
|
|
1067
|
+
const state = this.store.getState();
|
|
1068
|
+
if (state.input.busy) return;
|
|
1069
|
+
if (/^\/plan(\s|$)/i.test(text)) {
|
|
1070
|
+
const taskText = text.replace(/^\/plan\s*/i, "").trim();
|
|
1071
|
+
if (taskText) {
|
|
1072
|
+
await this.handleSubmitWithContent(taskText, true);
|
|
1073
|
+
} else {
|
|
1074
|
+
const current = this.store.getState().planMode;
|
|
1075
|
+
this.store.dispatch({ planMode: !current });
|
|
1076
|
+
this.appendMessage({
|
|
1077
|
+
role: "system",
|
|
1078
|
+
content: !current ? "Plan mode ON \u2014 all messages will go through planner \u2192 coder \u2192 reviewer.\nType /plan again to exit." : "Plan mode OFF \u2014 back to normal routing."
|
|
1079
|
+
});
|
|
1080
|
+
}
|
|
1081
|
+
return;
|
|
1082
|
+
}
|
|
1083
|
+
if (text.startsWith("/")) {
|
|
1084
|
+
if (text === "/help") {
|
|
1085
|
+
this.store.dispatch({ screen: "help" });
|
|
1086
|
+
return;
|
|
1087
|
+
}
|
|
1088
|
+
const handled = executeCommand(text, this.buildCommandContext());
|
|
1089
|
+
if (!handled) {
|
|
1090
|
+
this.appendMessage({
|
|
1091
|
+
role: "system",
|
|
1092
|
+
content: "Unknown command. Type /help for available commands."
|
|
1093
|
+
});
|
|
1094
|
+
}
|
|
1095
|
+
return;
|
|
1096
|
+
}
|
|
1097
|
+
if (text.startsWith("!")) {
|
|
1098
|
+
await this.handleShellSubmit(text);
|
|
1099
|
+
return;
|
|
1100
|
+
}
|
|
1101
|
+
this.store.dispatch({
|
|
1102
|
+
input: { ...state.input, busy: true, lastUserText: text }
|
|
1103
|
+
});
|
|
1104
|
+
this.appendMessage({ role: "user", content: text });
|
|
1105
|
+
this.store.dispatch({
|
|
1106
|
+
task: {
|
|
1107
|
+
phase: "thinking",
|
|
1108
|
+
startTime: Date.now(),
|
|
1109
|
+
toolsCalled: 0,
|
|
1110
|
+
model: this.store.getState().config.model,
|
|
1111
|
+
usage: { promptTokens: 0, completionTokens: 0, totalTokens: 0 }
|
|
1112
|
+
}
|
|
1113
|
+
});
|
|
1114
|
+
const taskAbort = new AbortController();
|
|
1115
|
+
const taskId = this.beginTask(taskAbort);
|
|
1116
|
+
let streamingContent = "";
|
|
1117
|
+
let hasStreamingEntry = false;
|
|
1118
|
+
const toolArgsById = /* @__PURE__ */ new Map();
|
|
1119
|
+
const dirtyLocalIndexPaths = /* @__PURE__ */ new Set();
|
|
1120
|
+
let fullIndexRefreshTimer = null;
|
|
1121
|
+
const extractEditedPath = (toolName, argsText) => {
|
|
1122
|
+
if (![
|
|
1123
|
+
"write_file",
|
|
1124
|
+
"string_replace_lsp",
|
|
1125
|
+
"ast_smart_edit",
|
|
1126
|
+
"delete_file"
|
|
1127
|
+
].includes(toolName))
|
|
1128
|
+
return null;
|
|
1129
|
+
try {
|
|
1130
|
+
const args = JSON.parse(argsText);
|
|
1131
|
+
return typeof args.path === "string" && args.path.trim() ? args.path.trim() : null;
|
|
1132
|
+
} catch {
|
|
1133
|
+
return null;
|
|
1134
|
+
}
|
|
1135
|
+
};
|
|
1136
|
+
const scheduleFullLocalIndexRefresh = () => {
|
|
1137
|
+
if (fullIndexRefreshTimer) clearTimeout(fullIndexRefreshTimer);
|
|
1138
|
+
fullIndexRefreshTimer = setTimeout(() => {
|
|
1139
|
+
fullIndexRefreshTimer = null;
|
|
1140
|
+
void buildLocalIndex().catch((error) => {
|
|
1141
|
+
this.logger?.logDebugInfo({
|
|
1142
|
+
event: "local_index_refresh_failed",
|
|
1143
|
+
error: String(error)
|
|
1144
|
+
});
|
|
1145
|
+
});
|
|
1146
|
+
}, 3e3);
|
|
1147
|
+
};
|
|
1148
|
+
const resetStreaming = () => {
|
|
1149
|
+
streamingContent = "";
|
|
1150
|
+
hasStreamingEntry = false;
|
|
1151
|
+
this.store.dispatch({
|
|
1152
|
+
streaming: { active: false, content: "", entryIndex: -1 }
|
|
1153
|
+
});
|
|
1154
|
+
};
|
|
1155
|
+
const sharedCallbacks = {
|
|
1156
|
+
onStreamReset: () => {
|
|
1157
|
+
if (!this.isActiveTask(taskId, taskAbort)) return;
|
|
1158
|
+
streamingContent = "";
|
|
1159
|
+
this.store.dispatch({
|
|
1160
|
+
streaming: { active: false, content: "", entryIndex: -1 }
|
|
1161
|
+
});
|
|
1162
|
+
this.bus.scheduleRefresh();
|
|
1163
|
+
},
|
|
1164
|
+
onToken: (token) => {
|
|
1165
|
+
if (!this.isActiveTask(taskId, taskAbort)) return;
|
|
1166
|
+
const s = this.store.getState();
|
|
1167
|
+
if (!s.task || s.task.phase === "idle") return;
|
|
1168
|
+
streamingContent += token;
|
|
1169
|
+
if (!hasStreamingEntry) {
|
|
1170
|
+
hasStreamingEntry = true;
|
|
1171
|
+
const entryIndex = this.appendMessage({
|
|
1172
|
+
role: "assistant",
|
|
1173
|
+
content: streamingContent
|
|
1174
|
+
});
|
|
1175
|
+
this.store.dispatch({
|
|
1176
|
+
streaming: { active: true, content: streamingContent, entryIndex }
|
|
1177
|
+
});
|
|
1178
|
+
} else {
|
|
1179
|
+
this.updateLastAssistantMessage(streamingContent);
|
|
1180
|
+
this.store.dispatch((prev) => ({
|
|
1181
|
+
streaming: {
|
|
1182
|
+
...prev.streaming,
|
|
1183
|
+
active: true,
|
|
1184
|
+
content: streamingContent
|
|
1185
|
+
}
|
|
1186
|
+
}));
|
|
1187
|
+
}
|
|
1188
|
+
this.store.dispatch({
|
|
1189
|
+
task: {
|
|
1190
|
+
...s.task,
|
|
1191
|
+
phase: "streaming",
|
|
1192
|
+
toolName: void 0,
|
|
1193
|
+
toolArgs: void 0
|
|
1194
|
+
}
|
|
1195
|
+
});
|
|
1196
|
+
this.bus.scheduleRefresh();
|
|
1197
|
+
},
|
|
1198
|
+
onModel: (model) => {
|
|
1199
|
+
if (!this.isActiveTask(taskId, taskAbort)) return;
|
|
1200
|
+
this.store.dispatch({ task: { ...this.store.getState().task, model } });
|
|
1201
|
+
},
|
|
1202
|
+
onUsage: (usage) => {
|
|
1203
|
+
if (!this.isActiveTask(taskId, taskAbort)) return;
|
|
1204
|
+
this.store.dispatch((prev) => ({ task: { ...prev.task, usage } }));
|
|
1205
|
+
},
|
|
1206
|
+
onToolStart: (name, args, id, preview) => {
|
|
1207
|
+
if (!this.isActiveTask(taskId, taskAbort)) return;
|
|
1208
|
+
this.logger?.logToolStart(name, args, id);
|
|
1209
|
+
if (id) toolArgsById.set(id, { name, args });
|
|
1210
|
+
streamingContent = "";
|
|
1211
|
+
this.store.dispatch({
|
|
1212
|
+
streaming: { active: false, content: "", entryIndex: -1 }
|
|
1213
|
+
});
|
|
1214
|
+
const s = this.store.getState();
|
|
1215
|
+
this.store.dispatch({
|
|
1216
|
+
task: {
|
|
1217
|
+
...s.task,
|
|
1218
|
+
phase: "tool",
|
|
1219
|
+
toolName: name,
|
|
1220
|
+
toolArgs: args,
|
|
1221
|
+
toolsCalled: s.task.toolsCalled + 1
|
|
1222
|
+
}
|
|
1223
|
+
});
|
|
1224
|
+
this.store.dispatch((prev) => {
|
|
1225
|
+
const newMsgs = withToolCallSummary(prev.messages, name, args, id);
|
|
1226
|
+
return {
|
|
1227
|
+
messages: preview ? setToolCallPreview(newMsgs, preview) : newMsgs
|
|
1228
|
+
};
|
|
1229
|
+
});
|
|
1230
|
+
this.bus.scheduleRefresh();
|
|
1231
|
+
},
|
|
1232
|
+
onToolResult: (name, resultText, id) => {
|
|
1233
|
+
if (!this.isActiveTask(taskId, taskAbort)) return;
|
|
1234
|
+
this.logger?.logToolResult(name, resultText);
|
|
1235
|
+
resetStreaming();
|
|
1236
|
+
this.store.dispatch({
|
|
1237
|
+
task: {
|
|
1238
|
+
...this.store.getState().task,
|
|
1239
|
+
phase: "executing",
|
|
1240
|
+
toolName: void 0,
|
|
1241
|
+
toolArgs: void 0
|
|
1242
|
+
}
|
|
1243
|
+
});
|
|
1244
|
+
this.store.dispatch((prev) => {
|
|
1245
|
+
const newMsgs = markToolDone(prev.messages, name, resultText, id);
|
|
1246
|
+
return { messages: newMsgs };
|
|
1247
|
+
});
|
|
1248
|
+
const toolMeta = id ? toolArgsById.get(id) : void 0;
|
|
1249
|
+
const rawName = toolMeta?.name ?? name;
|
|
1250
|
+
const rawArgs = toolMeta?.args ?? "{}";
|
|
1251
|
+
if (id) toolArgsById.delete(id);
|
|
1252
|
+
syncTaskListFromTool(this.store, rawName, rawArgs, resultText);
|
|
1253
|
+
const editedPath = extractEditedPath(rawName, rawArgs);
|
|
1254
|
+
if (editedPath) {
|
|
1255
|
+
const relativePath = path2.isAbsolute(editedPath) ? path2.relative(process.cwd(), editedPath) : editedPath;
|
|
1256
|
+
if (!relativePath.startsWith("..")) {
|
|
1257
|
+
dirtyLocalIndexPaths.add(relativePath);
|
|
1258
|
+
const isDelete = rawName === "delete_file";
|
|
1259
|
+
const refresh = isDelete ? removeLocalFile(relativePath).then((removed) => removed) : refreshLocalFile(relativePath).then((updated) => updated);
|
|
1260
|
+
void refresh.then((changed) => {
|
|
1261
|
+
if (changed) scheduleFullLocalIndexRefresh();
|
|
1262
|
+
}).catch((error) => {
|
|
1263
|
+
this.logger?.logDebugInfo({
|
|
1264
|
+
event: isDelete ? "local_file_remove_failed" : "local_file_refresh_failed",
|
|
1265
|
+
filePath: relativePath,
|
|
1266
|
+
error: String(error)
|
|
1267
|
+
});
|
|
1268
|
+
});
|
|
1269
|
+
}
|
|
1270
|
+
}
|
|
1271
|
+
this.bus.scheduleRefresh();
|
|
1272
|
+
},
|
|
1273
|
+
onOMObservation: (tokensObserved, observationTokens) => {
|
|
1274
|
+
if (!this.isActiveTask(taskId, taskAbort)) return;
|
|
1275
|
+
this.appendMessage({
|
|
1276
|
+
role: "system",
|
|
1277
|
+
content: `\u{1F9E0} Memory: observed ${tokensObserved.toLocaleString()} tokens \u2192 distilled to ${observationTokens.toLocaleString()} observation tokens`
|
|
1278
|
+
});
|
|
1279
|
+
this.bus.scheduleRefresh();
|
|
1280
|
+
},
|
|
1281
|
+
onOMReflection: (compressedTokens) => {
|
|
1282
|
+
if (!this.isActiveTask(taskId, taskAbort)) return;
|
|
1283
|
+
this.appendMessage({
|
|
1284
|
+
role: "system",
|
|
1285
|
+
content: `\u{1F9E0} Memory: reflected & compressed ${compressedTokens.toLocaleString()} tokens`
|
|
1286
|
+
});
|
|
1287
|
+
this.bus.scheduleRefresh();
|
|
1288
|
+
},
|
|
1289
|
+
onDebug: (info) => {
|
|
1290
|
+
if (!this.isActiveTask(taskId, taskAbort)) return;
|
|
1291
|
+
if (!this.store.getState().debug) return;
|
|
1292
|
+
if (info.event === "stream_request") {
|
|
1293
|
+
this.logger?.logStreamRequest({
|
|
1294
|
+
model: String(info.model ?? ""),
|
|
1295
|
+
messageCount: Number(info.messageCount ?? 0),
|
|
1296
|
+
toolCount: Number(info.toolCount ?? 0),
|
|
1297
|
+
hasSystem: Boolean(info.hasSystem),
|
|
1298
|
+
toolsCalled: this.store.getState().task.toolsCalled
|
|
1299
|
+
});
|
|
1300
|
+
} else if (info.event === "tool_fallback") {
|
|
1301
|
+
this.logger?.logToolFallback(String(info.reason ?? ""));
|
|
1302
|
+
} else {
|
|
1303
|
+
this.logger?.logDebugInfo(info);
|
|
1304
|
+
}
|
|
1305
|
+
},
|
|
1306
|
+
onAskQuestion: (questionId, question, options, respond, selectionMode) => {
|
|
1307
|
+
if (!this.isActiveTask(taskId, taskAbort)) return;
|
|
1308
|
+
this.waitForAskQuestion(questionId, question, options, selectionMode).then((answer) => {
|
|
1309
|
+
respond(answer);
|
|
1310
|
+
}).catch(() => {
|
|
1311
|
+
respond("(skipped)");
|
|
1312
|
+
});
|
|
1313
|
+
}
|
|
1314
|
+
};
|
|
1315
|
+
try {
|
|
1316
|
+
const mentionContext = await hydrateMentionContext(text);
|
|
1317
|
+
if (!this.isActiveTask(taskId, taskAbort)) return;
|
|
1318
|
+
for (const warning of mentionContext.warnings) {
|
|
1319
|
+
this.appendMessage({ role: "system", content: `\u26A0 ${warning}` });
|
|
1320
|
+
}
|
|
1321
|
+
const currentModel = this.store.getState().config.model;
|
|
1322
|
+
let classification = {
|
|
1323
|
+
phase: "single",
|
|
1324
|
+
taskType: "general",
|
|
1325
|
+
reason: "",
|
|
1326
|
+
effort: "medium"
|
|
1327
|
+
};
|
|
1328
|
+
const planMode = this.store.getState().planMode;
|
|
1329
|
+
if (!forceMultiPhase && !planMode) {
|
|
1330
|
+
this.store.dispatch({
|
|
1331
|
+
task: {
|
|
1332
|
+
...this.store.getState().task,
|
|
1333
|
+
phase: "classifying",
|
|
1334
|
+
model: currentModel
|
|
1335
|
+
}
|
|
1336
|
+
});
|
|
1337
|
+
this.bus.scheduleRefresh();
|
|
1338
|
+
classification = await classifyTask(
|
|
1339
|
+
text,
|
|
1340
|
+
this.options.provider,
|
|
1341
|
+
currentModel,
|
|
1342
|
+
taskAbort.signal
|
|
1343
|
+
);
|
|
1344
|
+
if (!this.isActiveTask(taskId, taskAbort)) return;
|
|
1345
|
+
this.store.dispatch({
|
|
1346
|
+
task: { ...this.store.getState().task, phase: "thinking", effort: classification.effort }
|
|
1347
|
+
});
|
|
1348
|
+
this.bus.scheduleRefresh();
|
|
1349
|
+
}
|
|
1350
|
+
const useMultiPhase = forceMultiPhase || planMode || classification.phase === "multi";
|
|
1351
|
+
if (useMultiPhase) {
|
|
1352
|
+
this.store.dispatch({
|
|
1353
|
+
task: { ...this.store.getState().task, effort: "high" }
|
|
1354
|
+
});
|
|
1355
|
+
} else {
|
|
1356
|
+
this.store.dispatch({
|
|
1357
|
+
task: { ...this.store.getState().task, effort: classification.effort }
|
|
1358
|
+
});
|
|
1359
|
+
}
|
|
1360
|
+
const [sessionResourceCtx, sessionProjectCtx] = await Promise.all([
|
|
1361
|
+
this.getSessionResourceContext(taskAbort.signal),
|
|
1362
|
+
this.getSessionProjectContext()
|
|
1363
|
+
]);
|
|
1364
|
+
const resolvedAgentModel = getMastraCurrentModelId() ?? resolveGatewayModel(
|
|
1365
|
+
this.store.getState().config.model,
|
|
1366
|
+
this.store.getState().config.availableModels.map((m) => m.id)
|
|
1367
|
+
);
|
|
1368
|
+
const agentInstructions = buildCodeMapAgentInstructions(
|
|
1369
|
+
sessionResourceCtx,
|
|
1370
|
+
sessionProjectCtx,
|
|
1371
|
+
resolvedAgentModel
|
|
1372
|
+
);
|
|
1373
|
+
const handlePlanReady = (_plan) => {
|
|
1374
|
+
if (!this.isActiveTask(taskId, taskAbort)) return;
|
|
1375
|
+
resetStreaming();
|
|
1376
|
+
};
|
|
1377
|
+
const result = useMultiPhase ? await runMultiPhaseAgentRuntime({
|
|
1378
|
+
provider: this.options.provider,
|
|
1379
|
+
availableModels: this.store.getState().config.availableModels.map((m) => m.id),
|
|
1380
|
+
model: this.store.getState().config.model,
|
|
1381
|
+
agentInstructions,
|
|
1382
|
+
userMessage: {
|
|
1383
|
+
role: "user",
|
|
1384
|
+
content: buildCurrentTaskContent(mentionContext.content)
|
|
1385
|
+
},
|
|
1386
|
+
toolClient: this.options.toolClient,
|
|
1387
|
+
signal: taskAbort.signal,
|
|
1388
|
+
effort: "high",
|
|
1389
|
+
onPhaseStart: (phase, model) => {
|
|
1390
|
+
if (!this.isActiveTask(taskId, taskAbort)) return;
|
|
1391
|
+
resetStreaming();
|
|
1392
|
+
this.store.dispatch({
|
|
1393
|
+
task: { ...this.store.getState().task, phase, model, effort: "high" }
|
|
1394
|
+
});
|
|
1395
|
+
this.bus.scheduleRefresh();
|
|
1396
|
+
},
|
|
1397
|
+
onPlanReady: handlePlanReady,
|
|
1398
|
+
onPlanWait: () => this.waitForPlanReview(),
|
|
1399
|
+
imageFiles,
|
|
1400
|
+
...sharedCallbacks
|
|
1401
|
+
}) : await runSingleAgentRuntime({
|
|
1402
|
+
provider: this.options.provider,
|
|
1403
|
+
model: this.store.getState().config.model,
|
|
1404
|
+
availableModels: this.store.getState().config.availableModels.map((m) => m.id),
|
|
1405
|
+
agentInstructions,
|
|
1406
|
+
userMessage: {
|
|
1407
|
+
role: "user",
|
|
1408
|
+
content: buildCurrentTaskContent(mentionContext.content)
|
|
1409
|
+
},
|
|
1410
|
+
toolClient: this.options.toolClient,
|
|
1411
|
+
signal: taskAbort.signal,
|
|
1412
|
+
onPlanReady: handlePlanReady,
|
|
1413
|
+
onPlanWait: () => this.waitForPlanReview(),
|
|
1414
|
+
imageFiles,
|
|
1415
|
+
effort: classification.effort,
|
|
1416
|
+
...sharedCallbacks
|
|
1417
|
+
});
|
|
1418
|
+
if (!this.isActiveTask(taskId, taskAbort)) return;
|
|
1419
|
+
const s = this.store.getState();
|
|
1420
|
+
this.store.dispatch({
|
|
1421
|
+
task: {
|
|
1422
|
+
...s.task,
|
|
1423
|
+
phase: "done",
|
|
1424
|
+
toolName: void 0,
|
|
1425
|
+
toolArgs: void 0,
|
|
1426
|
+
endTime: Date.now()
|
|
1427
|
+
}
|
|
1428
|
+
});
|
|
1429
|
+
if (useMultiPhase && planMode && !forceMultiPhase) {
|
|
1430
|
+
this.store.dispatch({ planMode: false });
|
|
1431
|
+
}
|
|
1432
|
+
if (this.logger) {
|
|
1433
|
+
const toolCallsList = result.messages.filter((m) => m.role === "assistant" && m.toolCalls).flatMap((m) => m.toolCalls ?? []).map((tc) => tc.function.name);
|
|
1434
|
+
this.logger.logSummary({
|
|
1435
|
+
totalChunks: 0,
|
|
1436
|
+
textChunks: 0,
|
|
1437
|
+
toolCallChunks: toolCallsList.length,
|
|
1438
|
+
finalToolCalls: toolCallsList,
|
|
1439
|
+
model: this.store.getState().config.model
|
|
1440
|
+
});
|
|
1441
|
+
}
|
|
1442
|
+
if (result.unsupportedToolCalling) {
|
|
1443
|
+
const cs = this.store.getState().config;
|
|
1444
|
+
this.appendMessage({
|
|
1445
|
+
role: "system",
|
|
1446
|
+
content: `\u26A0 Model "${cs.model}" does not support tool calling \u2014 the coder generated text instead of using tools.
|
|
1447
|
+
Check your coder profile in config and switch to a tool-capable model.`
|
|
1448
|
+
});
|
|
1449
|
+
}
|
|
1450
|
+
if (useMultiPhase && !result.usedTools && !result.unsupportedToolCalling) {
|
|
1451
|
+
this.appendMessage({
|
|
1452
|
+
role: "system",
|
|
1453
|
+
content: `\u26A0 Execute phase completed without any tool calls \u2014 the model may not be routing to a tool-capable backend.
|
|
1454
|
+
Check your coder profile configuration or start a new session with /new.`
|
|
1455
|
+
});
|
|
1456
|
+
}
|
|
1457
|
+
if (hasStreamingEntry && result.text) {
|
|
1458
|
+
this.updateLastAssistantMessage(result.text || "(no response)");
|
|
1459
|
+
} else if (!hasStreamingEntry) {
|
|
1460
|
+
this.appendMessage({
|
|
1461
|
+
role: "assistant",
|
|
1462
|
+
content: result.text || "(no response)"
|
|
1463
|
+
});
|
|
1464
|
+
}
|
|
1465
|
+
resetStreaming();
|
|
1466
|
+
this.bus.scheduleRefresh();
|
|
1467
|
+
const mastraUsage = await getMastraThreadTokenUsage().catch(() => null);
|
|
1468
|
+
this.store.dispatch({
|
|
1469
|
+
sessionTokens: mastraUsage?.totalTokens ?? 0
|
|
1470
|
+
});
|
|
1471
|
+
this.bus.scheduleRefresh();
|
|
1472
|
+
} catch (err) {
|
|
1473
|
+
if (isAbortError(err) || taskAbort.signal.aborted) return;
|
|
1474
|
+
this.store.dispatch({ task: { phase: "idle", toolsCalled: 0 } });
|
|
1475
|
+
this.logger?.logError(err);
|
|
1476
|
+
const errMsg = err instanceof Error ? err.message : String(err);
|
|
1477
|
+
const isContextFull = errMsg.includes("context window") || errMsg.includes("context length") || errMsg.includes("maximum context") || errMsg.includes("token limit") || errMsg.includes("too long") || errMsg.includes("exceeds") || errMsg.includes("input is too long") || errMsg.includes("prompt is too long");
|
|
1478
|
+
const isModelBroken = errMsg.includes("zero-length") || errMsg.includes("empty document") || errMsg.includes("429");
|
|
1479
|
+
const isImageUnsupported = errMsg.includes("image input") || errMsg.includes("support image");
|
|
1480
|
+
const cs = this.store.getState().config;
|
|
1481
|
+
if (isContextFull) {
|
|
1482
|
+
this.appendMessage({
|
|
1483
|
+
role: "system",
|
|
1484
|
+
content: "Context window full. Start a new session with /new to free context."
|
|
1485
|
+
});
|
|
1486
|
+
} else if (isModelBroken && cs.availableModels.length > 1) {
|
|
1487
|
+
const strong = cs.availableModels.filter(
|
|
1488
|
+
(m) => m.id !== cs.model && isStrongModel(m.id)
|
|
1489
|
+
);
|
|
1490
|
+
const newModel = strong[0] ?? cs.availableModels.find((m) => m.id !== cs.model) ?? null;
|
|
1491
|
+
if (newModel) {
|
|
1492
|
+
this.store.dispatch({ config: { ...cs, model: newModel.id } });
|
|
1493
|
+
this.appendMessage({
|
|
1494
|
+
role: "system",
|
|
1495
|
+
content: `Model "${cs.model}" failed. Auto-switched to "${newModel}". Resend your message to retry.`
|
|
1496
|
+
});
|
|
1497
|
+
} else {
|
|
1498
|
+
this.appendMessage({
|
|
1499
|
+
role: "system",
|
|
1500
|
+
content: `Model "${cs.model}" failed and no alternative found.`
|
|
1501
|
+
});
|
|
1502
|
+
}
|
|
1503
|
+
} else if (isImageUnsupported) {
|
|
1504
|
+
this.appendMessage({
|
|
1505
|
+
role: "system",
|
|
1506
|
+
content: imageFiles?.length ? `Model "${cs.model}" does not support image input. Switch to a vision-capable model with /model, or resend without images.` : `Model "${cs.model}" does not support image input.`
|
|
1507
|
+
});
|
|
1508
|
+
} else {
|
|
1509
|
+
this.appendMessage({ role: "system", content: `Error: ${errMsg}` });
|
|
1510
|
+
}
|
|
1511
|
+
}
|
|
1512
|
+
if (this.isActiveTask(taskId, taskAbort)) {
|
|
1513
|
+
this.finishTask(taskId);
|
|
1514
|
+
}
|
|
1515
|
+
}
|
|
1516
|
+
async handleShellSubmit(text) {
|
|
1517
|
+
const command = text.slice(1).trim();
|
|
1518
|
+
if (!command) {
|
|
1519
|
+
this.appendMessage({
|
|
1520
|
+
role: "system",
|
|
1521
|
+
content: "Usage: !<shell command>"
|
|
1522
|
+
});
|
|
1523
|
+
return;
|
|
1524
|
+
}
|
|
1525
|
+
this.store.dispatch((prev) => ({ input: { ...prev.input, busy: true } }));
|
|
1526
|
+
this.appendMessage({ role: "user", content: text });
|
|
1527
|
+
this.store.dispatch({
|
|
1528
|
+
task: {
|
|
1529
|
+
phase: "tool",
|
|
1530
|
+
startTime: Date.now(),
|
|
1531
|
+
toolsCalled: 1,
|
|
1532
|
+
toolName: "bash",
|
|
1533
|
+
toolArgs: command,
|
|
1534
|
+
model: this.store.getState().config.model,
|
|
1535
|
+
usage: { promptTokens: 0, completionTokens: 0, totalTokens: 0 }
|
|
1536
|
+
}
|
|
1537
|
+
});
|
|
1538
|
+
const taskAbort = new AbortController();
|
|
1539
|
+
const taskId = this.beginTask(taskAbort);
|
|
1540
|
+
this.appendMessage({ role: "tool_call", name: "bash", content: command });
|
|
1541
|
+
try {
|
|
1542
|
+
const result = await abortable(runShell(command), taskAbort.signal);
|
|
1543
|
+
if (!this.isActiveTask(taskId, taskAbort)) return;
|
|
1544
|
+
const content = result || "(no output)";
|
|
1545
|
+
this.store.dispatch((prev) => ({
|
|
1546
|
+
messages: markToolDone(prev.messages, "bash", content)
|
|
1547
|
+
}));
|
|
1548
|
+
this.store.dispatch({
|
|
1549
|
+
task: {
|
|
1550
|
+
...this.store.getState().task,
|
|
1551
|
+
phase: "done",
|
|
1552
|
+
toolName: void 0,
|
|
1553
|
+
toolArgs: void 0,
|
|
1554
|
+
endTime: Date.now()
|
|
1555
|
+
}
|
|
1556
|
+
});
|
|
1557
|
+
} catch (err) {
|
|
1558
|
+
if (isAbortError(err) || taskAbort.signal.aborted) return;
|
|
1559
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1560
|
+
this.store.dispatch((prev) => ({
|
|
1561
|
+
messages: markToolDone(prev.messages, "bash", `[ERROR] ${message}`)
|
|
1562
|
+
}));
|
|
1563
|
+
this.appendMessage({
|
|
1564
|
+
role: "system",
|
|
1565
|
+
content: `Shell command failed: ${message}`
|
|
1566
|
+
});
|
|
1567
|
+
this.store.dispatch({ task: { phase: "idle", toolsCalled: 0 } });
|
|
1568
|
+
} finally {
|
|
1569
|
+
if (this.isActiveTask(taskId, taskAbort)) {
|
|
1570
|
+
this.finishTask(taskId);
|
|
1571
|
+
}
|
|
1572
|
+
this.bus.scheduleRefresh();
|
|
1573
|
+
}
|
|
1574
|
+
}
|
|
1575
|
+
// ─── Session context cache ────────────────────────────────
|
|
1576
|
+
async getSessionResourceContext(signal) {
|
|
1577
|
+
if (this._resourceContext !== void 0) return this._resourceContext;
|
|
1578
|
+
try {
|
|
1579
|
+
this._resourceContext = await fetchResourceContext(
|
|
1580
|
+
this.options.toolClient
|
|
1581
|
+
);
|
|
1582
|
+
} catch {
|
|
1583
|
+
this._resourceContext = null;
|
|
1584
|
+
}
|
|
1585
|
+
return this._resourceContext;
|
|
1586
|
+
}
|
|
1587
|
+
async getSessionProjectContext() {
|
|
1588
|
+
if (this._projectContext !== void 0) return this._projectContext;
|
|
1589
|
+
try {
|
|
1590
|
+
this._projectContext = await getCachedContext();
|
|
1591
|
+
} catch {
|
|
1592
|
+
this._projectContext = { conventions: null, rules: null, skills: null };
|
|
1593
|
+
}
|
|
1594
|
+
return this._projectContext ?? { conventions: null, rules: null, skills: null };
|
|
1595
|
+
}
|
|
1596
|
+
// ─── Message helpers ──────────────────────────────────────
|
|
1597
|
+
appendMessage(msg) {
|
|
1598
|
+
let index = -1;
|
|
1599
|
+
this.store.dispatch((prev) => {
|
|
1600
|
+
index = prev.messages.length;
|
|
1601
|
+
return {
|
|
1602
|
+
messages: [...prev.messages, { timestamp: Date.now(), ...msg }]
|
|
1603
|
+
};
|
|
1604
|
+
});
|
|
1605
|
+
return index;
|
|
1606
|
+
}
|
|
1607
|
+
updateLastAssistantMessage(content) {
|
|
1608
|
+
this.store.dispatch((prev) => {
|
|
1609
|
+
const msgs = [...prev.messages];
|
|
1610
|
+
for (let i = msgs.length - 1; i >= 0; i--) {
|
|
1611
|
+
if (msgs[i].role === "assistant") {
|
|
1612
|
+
msgs[i] = { ...msgs[i], content };
|
|
1613
|
+
break;
|
|
1614
|
+
}
|
|
1615
|
+
}
|
|
1616
|
+
return { messages: msgs };
|
|
1617
|
+
});
|
|
1618
|
+
}
|
|
1619
|
+
beginTask(controller) {
|
|
1620
|
+
const taskId = ++this._taskSeq;
|
|
1621
|
+
this._activeTaskId = taskId;
|
|
1622
|
+
this._taskAbort = controller;
|
|
1623
|
+
return taskId;
|
|
1624
|
+
}
|
|
1625
|
+
finishTask(taskId) {
|
|
1626
|
+
if (this._activeTaskId !== taskId) return;
|
|
1627
|
+
this._activeTaskId = 0;
|
|
1628
|
+
this._taskAbort = null;
|
|
1629
|
+
this.store.dispatch((prev) => ({ input: { ...prev.input, busy: false } }));
|
|
1630
|
+
}
|
|
1631
|
+
isActiveTask(taskId, controller) {
|
|
1632
|
+
return this._activeTaskId === taskId && this._taskAbort === controller && !controller.signal.aborted;
|
|
1633
|
+
}
|
|
1634
|
+
// ─── Command context ──────────────────────────────────────
|
|
1635
|
+
buildCommandContext() {
|
|
1636
|
+
const s = this.store.getState();
|
|
1637
|
+
return {
|
|
1638
|
+
currentModel: s.config.model,
|
|
1639
|
+
provider: this.options.provider,
|
|
1640
|
+
availableModels: s.config.availableModels.map((m) => m.id),
|
|
1641
|
+
toolClient: this.options.toolClient,
|
|
1642
|
+
getMessages: () => this.store.getState().messages,
|
|
1643
|
+
appendMessage: (msg) => {
|
|
1644
|
+
this.store.dispatch((prev) => ({
|
|
1645
|
+
messages: [
|
|
1646
|
+
...prev.messages,
|
|
1647
|
+
{ timestamp: Date.now(), ...msg }
|
|
1648
|
+
]
|
|
1649
|
+
}));
|
|
1650
|
+
this.bus.scheduleRefresh();
|
|
1651
|
+
},
|
|
1652
|
+
setMessages: (updater) => {
|
|
1653
|
+
if (typeof updater === "function") {
|
|
1654
|
+
this.store.dispatch((prev) => ({ messages: updater(prev.messages) }));
|
|
1655
|
+
} else {
|
|
1656
|
+
this.store.dispatch({ messages: updater });
|
|
1657
|
+
}
|
|
1658
|
+
},
|
|
1659
|
+
setInputHistory: (updater) => {
|
|
1660
|
+
if (typeof updater === "function") {
|
|
1661
|
+
this.store.dispatch((prev) => ({
|
|
1662
|
+
input: { ...prev.input, history: updater(prev.input.history) }
|
|
1663
|
+
}));
|
|
1664
|
+
} else {
|
|
1665
|
+
this.store.dispatch((prev) => ({
|
|
1666
|
+
input: { ...prev.input, history: updater }
|
|
1667
|
+
}));
|
|
1668
|
+
}
|
|
1669
|
+
},
|
|
1670
|
+
setCurrentModel: (m) => {
|
|
1671
|
+
this.store.dispatch((prev) => ({
|
|
1672
|
+
config: { ...prev.config, model: m }
|
|
1673
|
+
}));
|
|
1674
|
+
},
|
|
1675
|
+
setBusy: (b) => {
|
|
1676
|
+
this.store.dispatch((prev) => ({ input: { ...prev.input, busy: b } }));
|
|
1677
|
+
},
|
|
1678
|
+
debug: s.debug,
|
|
1679
|
+
setDebug: (d) => {
|
|
1680
|
+
this.store.dispatch((prev) => ({
|
|
1681
|
+
debug: d,
|
|
1682
|
+
config: { ...prev.config, debug: d }
|
|
1683
|
+
}));
|
|
1684
|
+
if (d && !this.logger) this.logger = createDebugLogger();
|
|
1685
|
+
if (!d) this.logger = null;
|
|
1686
|
+
},
|
|
1687
|
+
debugLogFile: this.logger?.logFile ?? null,
|
|
1688
|
+
lastUserText: s.input.lastUserText,
|
|
1689
|
+
persistSession: () => {
|
|
1690
|
+
},
|
|
1691
|
+
getSessionTokens: () => this.store.getState().sessionTokens,
|
|
1692
|
+
resend: () => {
|
|
1693
|
+
const cs = this.store.getState();
|
|
1694
|
+
if (cs.input.lastUserText && !cs.input.busy)
|
|
1695
|
+
this.handleSubmit(cs.input.lastUserText);
|
|
1696
|
+
},
|
|
1697
|
+
exit: () => {
|
|
1698
|
+
process.stdout.write("\x1B[?1000l\x1B[?1006l");
|
|
1699
|
+
process.exit(0);
|
|
1700
|
+
},
|
|
1701
|
+
newSession: () => this.startNewSession(),
|
|
1702
|
+
reinitHarness: async () => {
|
|
1703
|
+
await resetHarnessSingleton();
|
|
1704
|
+
await warmupHarness({
|
|
1705
|
+
toolClient: this.options.toolClient,
|
|
1706
|
+
baseUrl: this.options.provider.baseUrl,
|
|
1707
|
+
apiKey: this.options.provider.apiKey,
|
|
1708
|
+
modelId: this.store.getState().config.model,
|
|
1709
|
+
availableModels: this.store.getState().config.availableModels.map((m) => m.id),
|
|
1710
|
+
onDebug: void 0,
|
|
1711
|
+
extraServerConfigs: this.options.toolClient.getExtraServerConfigs()
|
|
1712
|
+
});
|
|
1713
|
+
},
|
|
1714
|
+
getMastraThreadId: () => getMastraThreadId(),
|
|
1715
|
+
loadThreadById: (id) => this.loadThreadById(id),
|
|
1716
|
+
startSubprocess: (command) => {
|
|
1717
|
+
this.store.dispatch({
|
|
1718
|
+
subprocess: { active: true, command, logLines: [] }
|
|
1719
|
+
});
|
|
1720
|
+
},
|
|
1721
|
+
logSubprocess: (line) => {
|
|
1722
|
+
this.store.dispatch((prev) => ({
|
|
1723
|
+
subprocess: {
|
|
1724
|
+
...prev.subprocess,
|
|
1725
|
+
logLines: [...prev.subprocess.logLines, line]
|
|
1726
|
+
}
|
|
1727
|
+
}));
|
|
1728
|
+
},
|
|
1729
|
+
endSubprocess: () => {
|
|
1730
|
+
this.store.dispatch({
|
|
1731
|
+
subprocess: { active: false, command: "", logLines: [] }
|
|
1732
|
+
});
|
|
1733
|
+
},
|
|
1734
|
+
refreshWorkspaceCommits: () => this.refreshWorkspaceCommits(),
|
|
1735
|
+
getCommandList
|
|
1736
|
+
};
|
|
1737
|
+
}
|
|
1738
|
+
};
|
|
1739
|
+
export {
|
|
1740
|
+
ChatTerminal,
|
|
1741
|
+
buildCodeMapAgentInstructions,
|
|
1742
|
+
buildCurrentTaskContent
|
|
1743
|
+
};
|