@gencode/agents 0.0.4 → 0.0.6
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/commands/new.js +6 -6
- package/dist/commands/new.js.map +1 -1
- package/dist/config/types.d.ts +2 -2
- package/dist/config/types.d.ts.map +1 -1
- package/dist/plugins/hooks.d.ts +20 -1
- package/dist/plugins/hooks.d.ts.map +1 -1
- package/dist/plugins/hooks.js.map +1 -1
- package/dist/plugins/index.d.ts +1 -1
- package/dist/plugins/index.d.ts.map +1 -1
- package/dist/plugins/index.js.map +1 -1
- package/dist/runner/agent-runtime.d.ts +62 -0
- package/dist/runner/agent-runtime.d.ts.map +1 -0
- package/dist/runner/agent-runtime.js +179 -0
- package/dist/runner/agent-runtime.js.map +1 -0
- package/dist/runner/announce-loop.d.ts +41 -0
- package/dist/runner/announce-loop.d.ts.map +1 -0
- package/dist/runner/announce-loop.js +94 -0
- package/dist/runner/announce-loop.js.map +1 -0
- package/dist/runner/event-dispatcher.d.ts +31 -0
- package/dist/runner/event-dispatcher.d.ts.map +1 -0
- package/dist/runner/event-dispatcher.js +87 -0
- package/dist/runner/event-dispatcher.js.map +1 -0
- package/dist/runner/finalizer.d.ts +30 -0
- package/dist/runner/finalizer.d.ts.map +1 -0
- package/dist/runner/finalizer.js +75 -0
- package/dist/runner/finalizer.js.map +1 -0
- package/dist/runner/invocation-resolver.d.ts +67 -0
- package/dist/runner/invocation-resolver.d.ts.map +1 -0
- package/dist/runner/invocation-resolver.js +224 -0
- package/dist/runner/invocation-resolver.js.map +1 -0
- package/dist/runner/plugin-context.d.ts +18 -0
- package/dist/runner/plugin-context.d.ts.map +1 -0
- package/dist/runner/plugin-context.js +26 -0
- package/dist/runner/plugin-context.js.map +1 -0
- package/dist/runner/run-context.d.ts +38 -0
- package/dist/runner/run-context.d.ts.map +1 -0
- package/dist/runner/run-context.js +159 -0
- package/dist/runner/run-context.js.map +1 -0
- package/dist/runner/runner-session.d.ts +34 -0
- package/dist/runner/runner-session.d.ts.map +1 -0
- package/dist/runner/runner-session.js +61 -0
- package/dist/runner/runner-session.js.map +1 -0
- package/dist/runner/runner.d.ts +1 -2
- package/dist/runner/runner.d.ts.map +1 -1
- package/dist/runner/runner.js +115 -889
- package/dist/runner/runner.js.map +1 -1
- package/dist/runner/runtime.d.ts +7 -0
- package/dist/runner/runtime.d.ts.map +1 -0
- package/dist/runner/runtime.js +21 -0
- package/dist/runner/runtime.js.map +1 -0
- package/dist/runner/session-lifecycle.d.ts +31 -0
- package/dist/runner/session-lifecycle.d.ts.map +1 -0
- package/dist/runner/session-lifecycle.js +46 -0
- package/dist/runner/session-lifecycle.js.map +1 -0
- package/dist/runner/title.d.ts +3 -0
- package/dist/runner/title.d.ts.map +1 -0
- package/dist/runner/title.js +6 -0
- package/dist/runner/title.js.map +1 -0
- package/dist/runner/turn-executor.d.ts +51 -0
- package/dist/runner/turn-executor.d.ts.map +1 -0
- package/dist/runner/turn-executor.js +255 -0
- package/dist/runner/turn-executor.js.map +1 -0
- package/dist/tools/cron.d.ts +15 -22
- package/dist/tools/cron.d.ts.map +1 -1
- package/dist/tools/cron.js +20 -40
- package/dist/tools/cron.js.map +1 -1
- package/dist/types.d.ts +1 -1
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/dist/config-DJX-VM7S.js +0 -198
- package/dist/config-DJX-VM7S.js.map +0 -1
- package/dist/index-JD6Ye-N5.d.ts +0 -149
- package/dist/index-JD6Ye-N5.d.ts.map +0 -1
- package/dist/manager-qXa-NP0p.js +0 -1651
- package/dist/manager-qXa-NP0p.js.map +0 -1
- package/dist/message.d.ts +0 -11
- package/dist/message.d.ts.map +0 -1
- package/dist/message.js +0 -46
- package/dist/message.js.map +0 -1
- package/dist/security/command-dangerous-rules.d.ts +0 -4
- package/dist/security/command-dangerous-rules.d.ts.map +0 -1
- package/dist/security/command-dangerous-rules.js +0 -26
- package/dist/security/command-dangerous-rules.js.map +0 -1
- package/dist/security/command-parser.d.ts +0 -3
- package/dist/security/command-parser.d.ts.map +0 -1
- package/dist/security/command-parser.js +0 -191
- package/dist/security/command-parser.js.map +0 -1
- package/dist/security/command-path-guard.d.ts +0 -10
- package/dist/security/command-path-guard.d.ts.map +0 -1
- package/dist/security/command-path-guard.js +0 -126
- package/dist/security/command-path-guard.js.map +0 -1
- package/dist/security/command-policy-config.d.ts +0 -5
- package/dist/security/command-policy-config.d.ts.map +0 -1
- package/dist/security/command-policy-config.js +0 -212
- package/dist/security/command-policy-config.js.map +0 -1
- package/dist/security/command-policy-engine.d.ts +0 -8
- package/dist/security/command-policy-engine.d.ts.map +0 -1
- package/dist/security/command-policy-engine.js +0 -122
- package/dist/security/command-policy-engine.js.map +0 -1
- package/dist/security/command-policy-types.d.ts +0 -67
- package/dist/security/command-policy-types.d.ts.map +0 -1
- package/dist/security/command-policy-types.js +0 -2
- package/dist/security/command-policy-types.js.map +0 -1
- package/dist/security/command-safe-bins.d.ts +0 -4
- package/dist/security/command-safe-bins.d.ts.map +0 -1
- package/dist/security/command-safe-bins.js +0 -84
- package/dist/security/command-safe-bins.js.map +0 -1
- package/dist/security/command-trusted-executables.d.ts +0 -6
- package/dist/security/command-trusted-executables.d.ts.map +0 -1
- package/dist/security/command-trusted-executables.js +0 -57
- package/dist/security/command-trusted-executables.js.map +0 -1
package/dist/runner/runner.js
CHANGED
|
@@ -1,46 +1,17 @@
|
|
|
1
|
-
import { Agent } from "@mariozechner/pi-agent-core";
|
|
2
|
-
import { registerApiProvider, registerBuiltInApiProviders } from "@mariozechner/pi-ai";
|
|
3
|
-
import os from "node:os";
|
|
4
|
-
import { createSession, ensureSession, loadTranscript, appendTranscriptEntry, saveSessionMetadata } from "../session/session.js";
|
|
5
1
|
import { loadBootstrapFiles, buildBootstrapContextFiles } from "../bootstrap/bootstrap.js";
|
|
6
2
|
import { loadSkillsWithPluginDirs } from "../skills/skills.js";
|
|
7
|
-
import { buildSystemPrompt } from "../system-prompt/builder.js";
|
|
8
|
-
import { createAgentTools } from "../tools/index.js";
|
|
9
|
-
import { createBuiltinMemoryProvider } from "../memory/builtin-provider.js";
|
|
10
|
-
import { resolveMemoryProvider } from "../memory/provider-registry.js";
|
|
11
|
-
import { startMemoryWatchBridge } from "../memory/watch-bridge.js";
|
|
12
3
|
import { SubagentRegistry } from "../subagent/registry.js";
|
|
13
|
-
import {
|
|
14
|
-
import { manageHistory } from "../history/index.js";
|
|
15
|
-
import { wrapToolsWithLoopDetection, } from "../loop-detection/tool-loop-detection.js";
|
|
16
|
-
import { clearLoopDetectionState, getLoopDetectionState } from "../loop-detection/session-state.js";
|
|
17
|
-
import path from "node:path";
|
|
18
|
-
import { initializePluginSystem } from "../plugins/manager.js";
|
|
19
|
-
import { PluginHookRegistry } from "../plugins/hooks.js";
|
|
20
|
-
import { wrapToolsWithHooks } from "../plugins/tool-hooks.js";
|
|
21
|
-
import { handleSlashCommand, parseResetCommand } from "../commands/index.js";
|
|
22
|
-
import { handleNewCommand } from "../commands/new.js";
|
|
23
|
-
import { handleResetCommand } from "../commands/reset.js";
|
|
24
|
-
import { compactTranscriptNow } from "../commands/compact.js";
|
|
25
|
-
import { LlmRequestError } from "../llm/client.js";
|
|
26
|
-
import { streamOpenAICompletionsCompat, streamSimpleOpenAICompletionsCompat, } from "../llm/openai-completions-compat.js";
|
|
4
|
+
import { clearLoopDetectionState } from "../loop-detection/session-state.js";
|
|
27
5
|
import { SkillUsageTracker } from "./skill-usage.js";
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
const MEMORY_EVENT_DEDUPE_WINDOW_MS = 1_000;
|
|
38
|
-
const EXTERNAL_WATCH_SUPPRESS_WINDOW_MS = 3_000;
|
|
39
|
-
/** Generates a session title by truncating the first user message */
|
|
40
|
-
export function generateSessionTitle(message, maxLength = 80) {
|
|
41
|
-
const trimmed = message.trim().replace(/\s+/g, " ");
|
|
42
|
-
return trimmed.length <= maxLength ? trimmed : trimmed.slice(0, maxLength - 1) + "…";
|
|
43
|
-
}
|
|
6
|
+
import { ensureRunnerRuntimeInitialized } from "./runtime.js";
|
|
7
|
+
import { RunEventDispatcher } from "./event-dispatcher.js";
|
|
8
|
+
import { finalizeCompletedRun } from "./finalizer.js";
|
|
9
|
+
import { handleInvocation, prepareInvocation } from "./invocation-resolver.js";
|
|
10
|
+
import { createAgentRuntime } from "./agent-runtime.js";
|
|
11
|
+
import { executeAgentTurn, persistTurnEntriesWithWriter } from "./turn-executor.js";
|
|
12
|
+
import { runAnnounceLoop } from "./announce-loop.js";
|
|
13
|
+
import { createRunnerSession } from "./runner-session.js";
|
|
14
|
+
export { generateSessionTitle } from "./title.js";
|
|
44
15
|
/**
|
|
45
16
|
* Creates a Model object for use with vLLM/OpenAI-compatible APIs.
|
|
46
17
|
*/
|
|
@@ -68,534 +39,39 @@ function createOpenAICompatModel(params, sessionId) {
|
|
|
68
39
|
},
|
|
69
40
|
};
|
|
70
41
|
}
|
|
71
|
-
function normalizeMemoryChangedFiles(files) {
|
|
72
|
-
const result = [];
|
|
73
|
-
const seen = new Set();
|
|
74
|
-
for (const raw of files) {
|
|
75
|
-
const normalized = raw.replace(/\\/g, "/").replace(/^\.\//, "").trim();
|
|
76
|
-
if (!normalized) {
|
|
77
|
-
continue;
|
|
78
|
-
}
|
|
79
|
-
const canonical = normalized.toLowerCase() === "memory.md" ? "MEMORY.md" : normalized;
|
|
80
|
-
const key = canonical.toLowerCase();
|
|
81
|
-
if (seen.has(key)) {
|
|
82
|
-
continue;
|
|
83
|
-
}
|
|
84
|
-
seen.add(key);
|
|
85
|
-
result.push(canonical);
|
|
86
|
-
}
|
|
87
|
-
return result;
|
|
88
|
-
}
|
|
89
|
-
function isTextRunParams(params) {
|
|
90
|
-
return typeof params.message === "string";
|
|
91
|
-
}
|
|
92
|
-
function extractLeadingUserTextMessage(messages) {
|
|
93
|
-
for (let index = 0; index < messages.length; index += 1) {
|
|
94
|
-
const candidate = messages[index];
|
|
95
|
-
if (candidate.role !== "user") {
|
|
96
|
-
continue;
|
|
97
|
-
}
|
|
98
|
-
if (typeof candidate.content === "string") {
|
|
99
|
-
return { index, text: candidate.content };
|
|
100
|
-
}
|
|
101
|
-
if (!Array.isArray(candidate.content) || candidate.content.length === 0) {
|
|
102
|
-
return null;
|
|
103
|
-
}
|
|
104
|
-
const firstBlock = candidate.content[0];
|
|
105
|
-
if (firstBlock?.type === "text" && typeof firstBlock.text === "string") {
|
|
106
|
-
return { index, text: firstBlock.text };
|
|
107
|
-
}
|
|
108
|
-
return null;
|
|
109
|
-
}
|
|
110
|
-
return null;
|
|
111
|
-
}
|
|
112
|
-
function replaceLeadingUserTextMessage(messages, replacement) {
|
|
113
|
-
const match = extractLeadingUserTextMessage(messages);
|
|
114
|
-
if (!match) {
|
|
115
|
-
return messages;
|
|
116
|
-
}
|
|
117
|
-
return messages.map((message, index) => {
|
|
118
|
-
if (index !== match.index) {
|
|
119
|
-
return message;
|
|
120
|
-
}
|
|
121
|
-
const candidate = message;
|
|
122
|
-
if (typeof candidate.content === "string") {
|
|
123
|
-
return { ...message, content: replacement };
|
|
124
|
-
}
|
|
125
|
-
if (Array.isArray(candidate.content) && candidate.content.length > 0) {
|
|
126
|
-
return {
|
|
127
|
-
...message,
|
|
128
|
-
content: candidate.content.map((block, blockIndex) => {
|
|
129
|
-
if (blockIndex !== 0) {
|
|
130
|
-
return block;
|
|
131
|
-
}
|
|
132
|
-
const typedBlock = block;
|
|
133
|
-
if (typedBlock?.type === "text" && typeof typedBlock.text === "string") {
|
|
134
|
-
return { ...typedBlock, text: replacement };
|
|
135
|
-
}
|
|
136
|
-
return block;
|
|
137
|
-
}),
|
|
138
|
-
};
|
|
139
|
-
}
|
|
140
|
-
return message;
|
|
141
|
-
});
|
|
142
|
-
}
|
|
143
|
-
function serializeUserInput(input) {
|
|
144
|
-
return typeof input === "string" ? input : JSON.stringify(input);
|
|
145
|
-
}
|
|
146
|
-
/** Convert a captured AssistantMessage to an AssistantEntry for the transcript */
|
|
147
|
-
function assistantMessageToEntry(msg) {
|
|
148
|
-
const text = msg.content
|
|
149
|
-
.filter((b) => b.type === "text")
|
|
150
|
-
.map((b) => b.text)
|
|
151
|
-
.join("");
|
|
152
|
-
const toolCalls = msg.content
|
|
153
|
-
.filter((b) => b.type === "toolCall")
|
|
154
|
-
.map((b) => ({ id: b.id, name: b.name, arguments: b.arguments }));
|
|
155
|
-
if (text.trim().length === 0 && toolCalls.length === 0) {
|
|
156
|
-
return null;
|
|
157
|
-
}
|
|
158
|
-
const entry = { role: "assistant", content: text, timestamp: new Date().toISOString() };
|
|
159
|
-
if (toolCalls.length > 0)
|
|
160
|
-
entry.toolCalls = toolCalls;
|
|
161
|
-
return entry;
|
|
162
|
-
}
|
|
163
|
-
/** Convert a captured tool result to a ToolResultEntry for the transcript */
|
|
164
|
-
function capturedToolResultToEntry(tr) {
|
|
165
|
-
return {
|
|
166
|
-
role: "tool_result",
|
|
167
|
-
toolCallId: tr.toolCallId,
|
|
168
|
-
toolName: tr.toolName,
|
|
169
|
-
content: tr.content,
|
|
170
|
-
isError: tr.isError,
|
|
171
|
-
timestamp: new Date().toISOString(),
|
|
172
|
-
};
|
|
173
|
-
}
|
|
174
|
-
function extractAssistantText(message) {
|
|
175
|
-
return message.content
|
|
176
|
-
.filter((block) => block.type === "text")
|
|
177
|
-
.map((block) => block.text)
|
|
178
|
-
.join("");
|
|
179
|
-
}
|
|
180
|
-
// ── Callbacks ─────────────────────────────────────────────────────────────
|
|
181
|
-
/**
|
|
182
|
-
* Sends an HTTP callback to the given URL.
|
|
183
|
-
* Errors are silently ignored to not interfere with the main flow.
|
|
184
|
-
*/
|
|
185
|
-
async function sendCallback(url, payload) {
|
|
186
|
-
try {
|
|
187
|
-
await fetch(url, {
|
|
188
|
-
method: "POST",
|
|
189
|
-
headers: { "content-type": "application/json" },
|
|
190
|
-
body: JSON.stringify(payload),
|
|
191
|
-
});
|
|
192
|
-
}
|
|
193
|
-
catch {
|
|
194
|
-
// Callback failures are non-fatal
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
/**
|
|
198
|
-
* Dispatches a progress event to both the in-process callback and the HTTP callback.
|
|
199
|
-
*/
|
|
200
|
-
async function dispatchProgress(sessionId, event, params) {
|
|
201
|
-
const eventWithMessageId = params.messageId ? { ...event, messageId: params.messageId } : event;
|
|
202
|
-
await params.onProgress?.(eventWithMessageId);
|
|
203
|
-
if (params.callbackUrl) {
|
|
204
|
-
await sendCallback(params.callbackUrl, {
|
|
205
|
-
sessionId,
|
|
206
|
-
channel: params.channel,
|
|
207
|
-
messageId: params.messageId,
|
|
208
|
-
type: "progress",
|
|
209
|
-
event: eventWithMessageId,
|
|
210
|
-
});
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
// ── Helpers ───────────────────────────────────────────────────────────────
|
|
214
|
-
/**
|
|
215
|
-
* Builds a single announce message from a batch of completed subagent runs.
|
|
216
|
-
* Batching reduces the number of extra LLM turns needed.
|
|
217
|
-
*/
|
|
218
|
-
function buildBatchAnnounceMessage(completed) {
|
|
219
|
-
if (completed.length === 1) {
|
|
220
|
-
const r = completed[0];
|
|
221
|
-
return buildSubagentAnnounceMessage({
|
|
222
|
-
task: r.task,
|
|
223
|
-
label: r.label,
|
|
224
|
-
status: r.status,
|
|
225
|
-
result: r.result,
|
|
226
|
-
error: r.error,
|
|
227
|
-
});
|
|
228
|
-
}
|
|
229
|
-
const parts = completed.map((r) => buildSubagentAnnounceMessage({
|
|
230
|
-
task: r.task,
|
|
231
|
-
label: r.label,
|
|
232
|
-
status: r.status,
|
|
233
|
-
result: r.result,
|
|
234
|
-
error: r.error,
|
|
235
|
-
}));
|
|
236
|
-
return `[${completed.length} subagents completed]\n\n${parts.join("\n\n---\n\n")}`;
|
|
237
|
-
}
|
|
238
|
-
// ── Agent turn runner ─────────────────────────────────────────────────────
|
|
239
|
-
/**
|
|
240
|
-
* Runs one invocation of agent.prompt() and collects text output, usage, and
|
|
241
|
-
* full turn records (via `turn_end` events when the SDK fires them).
|
|
242
|
-
*/
|
|
243
|
-
async function runAgentTurn(agent, message, sessionId, modelId, historyMessages, params, skillUsageTracker, hooks, hookCtx, abortSignal) {
|
|
244
|
-
let pendingTextChunk = "";
|
|
245
|
-
let lastTextChunk = "";
|
|
246
|
-
let finalAssistantText = "";
|
|
247
|
-
let inputTokens = 0;
|
|
248
|
-
let outputTokens = 0;
|
|
249
|
-
let runError;
|
|
250
|
-
let sdkError;
|
|
251
|
-
const turnRecords = [];
|
|
252
|
-
const flushPendingTextChunk = async () => {
|
|
253
|
-
if (!pendingTextChunk) {
|
|
254
|
-
return;
|
|
255
|
-
}
|
|
256
|
-
lastTextChunk = pendingTextChunk;
|
|
257
|
-
await dispatchProgress(sessionId, { type: "text", text: pendingTextChunk }, params);
|
|
258
|
-
pendingTextChunk = "";
|
|
259
|
-
};
|
|
260
|
-
const unsubscribe = agent.subscribe(async (event) => {
|
|
261
|
-
if (event.type === "message_update") {
|
|
262
|
-
const assistantEvent = event.assistantMessageEvent;
|
|
263
|
-
if (assistantEvent.type === "text_delta" && typeof assistantEvent.delta === "string") {
|
|
264
|
-
pendingTextChunk += assistantEvent.delta;
|
|
265
|
-
}
|
|
266
|
-
if (assistantEvent.type === "done") {
|
|
267
|
-
const msg = assistantEvent.message;
|
|
268
|
-
finalAssistantText = extractAssistantText(msg);
|
|
269
|
-
if (msg.usage) {
|
|
270
|
-
inputTokens = msg.usage.input;
|
|
271
|
-
outputTokens = msg.usage.output;
|
|
272
|
-
}
|
|
273
|
-
await flushPendingTextChunk();
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
|
-
else if (event.type === "message_end") {
|
|
277
|
-
const msg = event.message;
|
|
278
|
-
if (msg.usage) {
|
|
279
|
-
inputTokens = msg.usage.input;
|
|
280
|
-
outputTokens = msg.usage.output;
|
|
281
|
-
}
|
|
282
|
-
}
|
|
283
|
-
else if (event.type === "turn_end") {
|
|
284
|
-
// Capture full turn for high-fidelity transcript persistence
|
|
285
|
-
const msg = event.message;
|
|
286
|
-
if (msg.stopReason === "error" || msg.stopReason === "aborted") {
|
|
287
|
-
sdkError = msg.errorMessage ?? (msg.stopReason === "aborted" ? "aborted" : "Unknown LLM error");
|
|
288
|
-
}
|
|
289
|
-
const rawResults = (event.toolResults ?? []);
|
|
290
|
-
const toolResults = rawResults.map((r) => ({
|
|
291
|
-
toolCallId: r.toolCallId,
|
|
292
|
-
toolName: r.toolName,
|
|
293
|
-
content: r.content
|
|
294
|
-
.filter((c) => c.type === "text")
|
|
295
|
-
.map((c) => c.text ?? "")
|
|
296
|
-
.join(""),
|
|
297
|
-
isError: r.isError,
|
|
298
|
-
}));
|
|
299
|
-
turnRecords.push({ message: msg, toolResults });
|
|
300
|
-
}
|
|
301
|
-
else if (event.type === "tool_execution_start") {
|
|
302
|
-
await flushPendingTextChunk();
|
|
303
|
-
skillUsageTracker.onToolExecutionStart({ toolName: event.toolName, args: event.args });
|
|
304
|
-
await dispatchProgress(sessionId, { type: "tool_start", name: event.toolName, input: event.args }, params);
|
|
305
|
-
}
|
|
306
|
-
else if (event.type === "tool_execution_end") {
|
|
307
|
-
await skillUsageTracker.onToolExecutionEnd({ toolName: event.toolName, isError: event.isError });
|
|
308
|
-
const output = typeof event.result === "string" ? event.result : JSON.stringify(event.result);
|
|
309
|
-
await dispatchProgress(sessionId, { type: "tool_end", name: event.toolName, output, isError: event.isError }, params);
|
|
310
|
-
}
|
|
311
|
-
});
|
|
312
|
-
try {
|
|
313
|
-
await hooks.dispatch("llm_input", {
|
|
314
|
-
sessionId,
|
|
315
|
-
model: modelId,
|
|
316
|
-
prompt: typeof message === "string" ? message : JSON.stringify(message),
|
|
317
|
-
historyMessages,
|
|
318
|
-
}, hookCtx);
|
|
319
|
-
if (abortSignal?.aborted) {
|
|
320
|
-
throw new Error("aborted");
|
|
321
|
-
}
|
|
322
|
-
if (typeof message === "string") {
|
|
323
|
-
await agent.prompt(message);
|
|
324
|
-
}
|
|
325
|
-
else {
|
|
326
|
-
await agent.prompt(message);
|
|
327
|
-
}
|
|
328
|
-
if (!runError) {
|
|
329
|
-
const stateError = typeof agent.state.error === "string" ? agent.state.error : undefined;
|
|
330
|
-
const latentError = sdkError ?? stateError;
|
|
331
|
-
if (latentError) {
|
|
332
|
-
runError = formatRunError(latentError);
|
|
333
|
-
await dispatchProgress(sessionId, { type: "error", message: runError }, params);
|
|
334
|
-
}
|
|
335
|
-
}
|
|
336
|
-
}
|
|
337
|
-
catch (err) {
|
|
338
|
-
runError = formatRunError(err);
|
|
339
|
-
await dispatchProgress(sessionId, { type: "error", message: runError }, params);
|
|
340
|
-
}
|
|
341
|
-
finally {
|
|
342
|
-
unsubscribe();
|
|
343
|
-
}
|
|
344
|
-
const lastAssistant = turnRecords.length > 0 ? turnRecords[turnRecords.length - 1]?.message : undefined;
|
|
345
|
-
await hooks.dispatch("llm_output", {
|
|
346
|
-
sessionId,
|
|
347
|
-
model: modelId,
|
|
348
|
-
assistantTexts: finalAssistantText ? [finalAssistantText] : lastTextChunk ? [lastTextChunk] : [],
|
|
349
|
-
lastAssistant,
|
|
350
|
-
usage: { input: inputTokens, output: outputTokens, total: inputTokens + outputTokens },
|
|
351
|
-
}, hookCtx);
|
|
352
|
-
const finalTextFromTurnRecord = turnRecords.length > 0 ? extractAssistantText(turnRecords[turnRecords.length - 1].message) : "";
|
|
353
|
-
return {
|
|
354
|
-
text: finalTextFromTurnRecord || finalAssistantText || lastTextChunk,
|
|
355
|
-
inputTokens,
|
|
356
|
-
outputTokens,
|
|
357
|
-
error: runError,
|
|
358
|
-
turnRecords,
|
|
359
|
-
};
|
|
360
|
-
}
|
|
361
|
-
function formatRunError(error) {
|
|
362
|
-
const llmError = toLlmRequestErrorLike(error);
|
|
363
|
-
if (llmError) {
|
|
364
|
-
if (llmError.code === "http_error" && llmError.statusCode) {
|
|
365
|
-
return llmError.providerMessage
|
|
366
|
-
? `LLM upstream returned HTTP ${llmError.statusCode}${llmError.statusText ? ` ${llmError.statusText}` : ""}: ${llmError.providerMessage}`
|
|
367
|
-
: `LLM upstream returned HTTP ${llmError.statusCode}${llmError.statusText ? ` ${llmError.statusText}` : ""}`;
|
|
368
|
-
}
|
|
369
|
-
if (llmError.code === "timeout") {
|
|
370
|
-
return "LLM request timed out";
|
|
371
|
-
}
|
|
372
|
-
if (llmError.code === "network_error") {
|
|
373
|
-
return llmError.message;
|
|
374
|
-
}
|
|
375
|
-
if (llmError.code === "aborted") {
|
|
376
|
-
return "LLM request was aborted";
|
|
377
|
-
}
|
|
378
|
-
return llmError.message;
|
|
379
|
-
}
|
|
380
|
-
return error instanceof Error ? error.message : String(error);
|
|
381
|
-
}
|
|
382
|
-
function toLlmRequestErrorLike(error) {
|
|
383
|
-
if (error instanceof LlmRequestError) {
|
|
384
|
-
return error;
|
|
385
|
-
}
|
|
386
|
-
if (!(error instanceof Error)) {
|
|
387
|
-
return null;
|
|
388
|
-
}
|
|
389
|
-
const candidate = error;
|
|
390
|
-
if (error.name !== "LlmRequestError" && !candidate.code) {
|
|
391
|
-
return null;
|
|
392
|
-
}
|
|
393
|
-
return {
|
|
394
|
-
message: error.message,
|
|
395
|
-
code: candidate.code,
|
|
396
|
-
statusCode: candidate.statusCode,
|
|
397
|
-
statusText: candidate.statusText,
|
|
398
|
-
providerMessage: candidate.providerMessage,
|
|
399
|
-
};
|
|
400
|
-
}
|
|
401
|
-
/**
|
|
402
|
-
* Persist the assistant's response to the transcript.
|
|
403
|
-
* Uses turn_end records (with tool calls/results) when available;
|
|
404
|
-
* falls back to a plain text entry otherwise.
|
|
405
|
-
*/
|
|
406
|
-
async function persistTurnEntries(dataDir, sessionId, turn, options) {
|
|
407
|
-
if (turn.turnRecords.length > 0) {
|
|
408
|
-
for (const record of turn.turnRecords) {
|
|
409
|
-
const assistantEntry = assistantMessageToEntry(record.message);
|
|
410
|
-
if (assistantEntry) {
|
|
411
|
-
await appendTranscriptEntry(dataDir, sessionId, assistantEntry, options);
|
|
412
|
-
}
|
|
413
|
-
for (const tr of record.toolResults) {
|
|
414
|
-
await appendTranscriptEntry(dataDir, sessionId, capturedToolResultToEntry(tr), options);
|
|
415
|
-
}
|
|
416
|
-
}
|
|
417
|
-
}
|
|
418
|
-
else if (turn.text) {
|
|
419
|
-
await appendTranscriptEntry(dataDir, sessionId, {
|
|
420
|
-
role: "assistant",
|
|
421
|
-
content: turn.text,
|
|
422
|
-
timestamp: new Date().toISOString(),
|
|
423
|
-
}, options);
|
|
424
|
-
}
|
|
425
|
-
}
|
|
426
42
|
// ── Internal runner ───────────────────────────────────────────────────────
|
|
427
43
|
/**
|
|
428
44
|
* Internal runner shared by both top-level and subagent invocations.
|
|
429
45
|
*/
|
|
430
46
|
async function runAgentInternal(params, registry) {
|
|
47
|
+
ensureRunnerRuntimeInitialized();
|
|
431
48
|
const startTime = Date.now();
|
|
432
|
-
const
|
|
433
|
-
const
|
|
434
|
-
|
|
435
|
-
const structuredUserText = inputMessages ? extractLeadingUserTextMessage(inputMessages) : null;
|
|
436
|
-
const slashCommandSource = rawMessage ?? structuredUserText?.text;
|
|
437
|
-
const resetCommand = slashCommandSource ? parseResetCommand(slashCommandSource) : null;
|
|
438
|
-
const resetRemainder = resetCommand?.remainder ?? "";
|
|
439
|
-
const resetShortCircuit = Boolean(resetCommand && !resetRemainder);
|
|
440
|
-
const messageForRun = resetCommand && resetRemainder ? resetRemainder : slashCommandSource;
|
|
441
|
-
const promptInput = inputMode === "text"
|
|
442
|
-
? (messageForRun ?? "")
|
|
443
|
-
: (resetCommand && resetRemainder && inputMessages
|
|
444
|
-
? replaceLeadingUserTextMessage(inputMessages, resetRemainder)
|
|
445
|
-
: inputMessages);
|
|
446
|
-
let transcriptMessage = inputMode === "text"
|
|
447
|
-
? (resetShortCircuit ? rawMessage : (messageForRun ?? ""))
|
|
448
|
-
: serializeUserInput(promptInput);
|
|
49
|
+
const eventDispatcher = new RunEventDispatcher(params);
|
|
50
|
+
const invocation = prepareInvocation(params);
|
|
51
|
+
let transcriptMessage = invocation.transcriptMessage;
|
|
449
52
|
let initialUserEntryPersisted = false;
|
|
450
|
-
const previousSessionId =
|
|
451
|
-
const
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
if (sessionId) {
|
|
456
|
-
await ensureSession(params.dataDir, sessionId);
|
|
457
|
-
}
|
|
458
|
-
else {
|
|
459
|
-
sessionId = await createSession(params.dataDir, params.channel);
|
|
460
|
-
}
|
|
461
|
-
const hookContext = {
|
|
462
|
-
sessionId,
|
|
463
|
-
workspaceDir,
|
|
464
|
-
channel: params.channel,
|
|
465
|
-
};
|
|
466
|
-
const pluginSystem = params.plugins
|
|
467
|
-
? initializePluginSystem({
|
|
468
|
-
...params.plugins,
|
|
469
|
-
runtime: {
|
|
470
|
-
llm: params.llm,
|
|
471
|
-
hookCtx: hookContext,
|
|
472
|
-
llmAllowlist: params.plugins.llmAllowlist,
|
|
473
|
-
},
|
|
474
|
-
})
|
|
475
|
-
: undefined;
|
|
476
|
-
const hookRegistry = pluginSystem?.registry.hooks ?? new PluginHookRegistry();
|
|
477
|
-
const memoryProviderId = params.memory?.providerId;
|
|
478
|
-
const memoryPluginId = params.memory?.pluginId ?? pluginSystem?.normalizedConfig.slots?.memory;
|
|
479
|
-
const recentMemoryWrites = new Map();
|
|
480
|
-
const recentMemoryEvents = new Map();
|
|
481
|
-
const emitMemoryChanged = async (event) => {
|
|
482
|
-
const now = Date.now();
|
|
483
|
-
const normalizedFiles = normalizeMemoryChangedFiles(event.files);
|
|
484
|
-
if (normalizedFiles.length === 0) {
|
|
485
|
-
return;
|
|
486
|
-
}
|
|
487
|
-
const filteredFiles = event.reason === "external-watch" && event.source === "memory"
|
|
488
|
-
? normalizedFiles.filter((file) => {
|
|
489
|
-
const lastWriteAt = recentMemoryWrites.get(file.toLowerCase()) ?? 0;
|
|
490
|
-
return now - lastWriteAt > EXTERNAL_WATCH_SUPPRESS_WINDOW_MS;
|
|
491
|
-
})
|
|
492
|
-
: normalizedFiles;
|
|
493
|
-
if (filteredFiles.length === 0) {
|
|
494
|
-
return;
|
|
495
|
-
}
|
|
496
|
-
if (event.reason !== "external-watch" && event.source === "memory") {
|
|
497
|
-
for (const file of filteredFiles) {
|
|
498
|
-
recentMemoryWrites.set(file.toLowerCase(), now);
|
|
499
|
-
}
|
|
500
|
-
}
|
|
501
|
-
const dedupeKey = `${event.reason}|${event.source}|${filteredFiles
|
|
502
|
-
.map((file) => file.toLowerCase())
|
|
503
|
-
.sort()
|
|
504
|
-
.join("|")}`;
|
|
505
|
-
const previousEventAt = recentMemoryEvents.get(dedupeKey) ?? 0;
|
|
506
|
-
if (now - previousEventAt < MEMORY_EVENT_DEDUPE_WINDOW_MS) {
|
|
507
|
-
return;
|
|
508
|
-
}
|
|
509
|
-
recentMemoryEvents.set(dedupeKey, now);
|
|
510
|
-
const eventToEmit = {
|
|
511
|
-
...event,
|
|
512
|
-
files: filteredFiles,
|
|
513
|
-
};
|
|
514
|
-
await dispatchProgress(event.sessionId ?? sessionId, {
|
|
515
|
-
type: "memory_changed",
|
|
516
|
-
reason: eventToEmit.reason,
|
|
517
|
-
files: eventToEmit.files,
|
|
518
|
-
source: eventToEmit.source,
|
|
519
|
-
providerId: eventToEmit.providerId,
|
|
520
|
-
timestamp: eventToEmit.timestamp,
|
|
521
|
-
}, params).catch(() => { });
|
|
522
|
-
await hookRegistry
|
|
523
|
-
.dispatch("memory_changed", eventToEmit, {
|
|
524
|
-
...hookContext,
|
|
525
|
-
sessionId: eventToEmit.sessionId ?? hookContext.sessionId,
|
|
526
|
-
})
|
|
527
|
-
.catch(() => { });
|
|
528
|
-
};
|
|
529
|
-
const transcriptOptions = memoryProviderId || memoryPluginId
|
|
530
|
-
? {
|
|
531
|
-
providerId: memoryProviderId,
|
|
532
|
-
pluginId: memoryPluginId,
|
|
533
|
-
onMemoryChanged: emitMemoryChanged,
|
|
534
|
-
}
|
|
535
|
-
: { onMemoryChanged: emitMemoryChanged };
|
|
536
|
-
const persistInitialUserEntry = async () => {
|
|
537
|
-
if (initialUserEntryPersisted) {
|
|
538
|
-
return;
|
|
539
|
-
}
|
|
540
|
-
await appendTranscriptEntry(params.dataDir, sessionId, {
|
|
541
|
-
role: "user",
|
|
542
|
-
content: transcriptMessage,
|
|
543
|
-
timestamp: new Date().toISOString(),
|
|
544
|
-
}, transcriptOptions);
|
|
545
|
-
initialUserEntryPersisted = true;
|
|
546
|
-
};
|
|
547
|
-
const memoryRoot = path.join(params.dataDir, ".aimax");
|
|
548
|
-
const resolvedProvider = resolveMemoryProvider({
|
|
549
|
-
providerId: memoryProviderId,
|
|
550
|
-
pluginId: memoryPluginId,
|
|
551
|
-
dataDir: params.dataDir,
|
|
552
|
-
memoryDir: memoryRoot,
|
|
553
|
-
sessionId,
|
|
53
|
+
const previousSessionId = invocation.previousSessionId;
|
|
54
|
+
const session = await createRunnerSession({
|
|
55
|
+
runParams: params,
|
|
56
|
+
requestedSessionId: invocation.requestedSessionId,
|
|
57
|
+
eventDispatcher,
|
|
554
58
|
});
|
|
555
|
-
const
|
|
556
|
-
|
|
557
|
-
const
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
59
|
+
const sessionId = session.sessionId;
|
|
60
|
+
const isNewSession = session.isNewSession;
|
|
61
|
+
const workspaceDir = session.workspaceDir;
|
|
62
|
+
const hookContext = session.hookContext;
|
|
63
|
+
const hookRegistry = session.hookRegistry;
|
|
64
|
+
const pluginContext = session.pluginContext;
|
|
65
|
+
const runContext = session.runContext;
|
|
66
|
+
if (invocation.resetCommand) {
|
|
67
|
+
initialUserEntryPersisted = await runContext.persistInitialUserEntry(transcriptMessage);
|
|
68
|
+
}
|
|
69
|
+
await session.start({
|
|
70
|
+
resetCommand: invocation.resetCommand,
|
|
71
|
+
previousSessionId,
|
|
72
|
+
rawMessage: invocation.rawMessage,
|
|
73
|
+
startMessage: typeof invocation.promptInput === "string" ? invocation.promptInput : transcriptMessage,
|
|
563
74
|
});
|
|
564
|
-
if (provider.sync) {
|
|
565
|
-
void provider.sync("session-start").catch(() => { });
|
|
566
|
-
}
|
|
567
|
-
if (resetCommand) {
|
|
568
|
-
await persistInitialUserEntry();
|
|
569
|
-
}
|
|
570
|
-
if (resetCommand && params.callbackUrl) {
|
|
571
|
-
await sendCallback(params.callbackUrl, {
|
|
572
|
-
sessionId,
|
|
573
|
-
channel: params.channel,
|
|
574
|
-
messageId: params.messageId,
|
|
575
|
-
type: "session_reset",
|
|
576
|
-
action: resetCommand.action,
|
|
577
|
-
previousSessionId,
|
|
578
|
-
message: rawMessage,
|
|
579
|
-
});
|
|
580
|
-
}
|
|
581
|
-
if (resetCommand) {
|
|
582
|
-
await hookRegistry.dispatch("session_reset", {
|
|
583
|
-
action: resetCommand.action,
|
|
584
|
-
sessionId,
|
|
585
|
-
previousSessionId,
|
|
586
|
-
message: rawMessage,
|
|
587
|
-
}, hookContext);
|
|
588
|
-
}
|
|
589
|
-
if (params.callbackUrl) {
|
|
590
|
-
await sendCallback(params.callbackUrl, {
|
|
591
|
-
sessionId,
|
|
592
|
-
channel: params.channel,
|
|
593
|
-
messageId: params.messageId,
|
|
594
|
-
type: "start",
|
|
595
|
-
message: typeof promptInput === "string" ? promptInput : transcriptMessage,
|
|
596
|
-
});
|
|
597
|
-
}
|
|
598
|
-
await hookRegistry.dispatch("session_start", { sessionId }, hookContext);
|
|
599
75
|
// 2. Set up abort controller early (needed for manageHistory signal)
|
|
600
76
|
const abortController = new AbortController();
|
|
601
77
|
if (params.abortSignal?.aborted) {
|
|
@@ -611,219 +87,57 @@ async function runAgentInternal(params, registry) {
|
|
|
611
87
|
warn: (message) => bootstrapWarnings.push(message),
|
|
612
88
|
});
|
|
613
89
|
// 4. Load skills (user + plugin skills)
|
|
614
|
-
const
|
|
615
|
-
const skills = await loadSkillsWithPluginDirs(params.dataDir, pluginSkillDirs);
|
|
90
|
+
const skills = await loadSkillsWithPluginDirs(params.dataDir, pluginContext.pluginSkillDirs);
|
|
616
91
|
const skillUsageTracker = new SkillUsageTracker({
|
|
617
92
|
workspaceDir,
|
|
618
93
|
sessionId,
|
|
619
94
|
skills,
|
|
620
|
-
report: (event) => dispatchProgress(sessionId, event
|
|
95
|
+
report: (event) => eventDispatcher.dispatchProgress(sessionId, event),
|
|
621
96
|
});
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
if (slashCommandSource) {
|
|
638
|
-
const slashResult = handleSlashCommand({ message: messageForRun ?? "", skills });
|
|
639
|
-
if (slashResult.kind === "reply") {
|
|
640
|
-
return finalizeSlashCommandResult({
|
|
641
|
-
replyText: slashResult.text,
|
|
642
|
-
sessionId,
|
|
643
|
-
isNewSession,
|
|
644
|
-
transcriptMessage,
|
|
645
|
-
params,
|
|
646
|
-
hookRegistry,
|
|
647
|
-
hookContext,
|
|
648
|
-
startTime,
|
|
649
|
-
});
|
|
650
|
-
}
|
|
651
|
-
if (slashResult.kind === "compact") {
|
|
652
|
-
const rawHistory = params.channel === "CRON" ? [] : await loadTranscript(params.dataDir, sessionId);
|
|
653
|
-
const compactResult = await compactTranscriptNow({
|
|
654
|
-
entries: rawHistory,
|
|
655
|
-
contextWindowTokens: params.llm.contextWindow ?? 200_000,
|
|
656
|
-
llm: {
|
|
657
|
-
baseUrl: params.llm.baseUrl,
|
|
658
|
-
apiKey: params.llm.apiKey,
|
|
659
|
-
model: params.llm.model,
|
|
660
|
-
},
|
|
661
|
-
instructions: slashResult.instructions,
|
|
662
|
-
signal: params.abortSignal,
|
|
663
|
-
hooks: hookRegistry,
|
|
664
|
-
hookCtx: hookContext,
|
|
665
|
-
});
|
|
666
|
-
const compactText = compactResult.status === "compacted"
|
|
667
|
-
? `⚙️ Compacted (kept ${compactResult.keptCount}, dropped ${compactResult.droppedCount}).`
|
|
668
|
-
: `⚙️ Compaction skipped: ${compactResult.reason}`;
|
|
669
|
-
return finalizeSlashCommandResult({
|
|
670
|
-
replyText: compactText,
|
|
671
|
-
sessionId,
|
|
672
|
-
isNewSession,
|
|
673
|
-
transcriptMessage,
|
|
674
|
-
initialUserEntryPersisted,
|
|
675
|
-
params,
|
|
676
|
-
hookRegistry,
|
|
677
|
-
hookContext,
|
|
678
|
-
startTime,
|
|
679
|
-
compactionEntry: compactResult.status === "compacted" ? compactResult.entry : undefined,
|
|
680
|
-
});
|
|
681
|
-
}
|
|
682
|
-
if (slashResult.kind === "rewrite") {
|
|
683
|
-
effectivePrompt = inputMode === "text"
|
|
684
|
-
? slashResult.message
|
|
685
|
-
: replaceLeadingUserTextMessage(promptInput, slashResult.message);
|
|
686
|
-
transcriptMessage = serializeUserInput(effectivePrompt);
|
|
687
|
-
}
|
|
688
|
-
else if (inputMode === "text") {
|
|
689
|
-
effectivePrompt = messageForRun ?? "";
|
|
690
|
-
transcriptMessage = typeof effectivePrompt === "string" ? effectivePrompt : transcriptMessage;
|
|
691
|
-
}
|
|
97
|
+
const appendEntry = (entry) => runContext.appendTranscriptEntry(entry);
|
|
98
|
+
const invocationExecution = await handleInvocation({
|
|
99
|
+
invocation,
|
|
100
|
+
skills,
|
|
101
|
+
sessionId,
|
|
102
|
+
isNewSession,
|
|
103
|
+
initialUserEntryPersisted,
|
|
104
|
+
runParams: params,
|
|
105
|
+
hookRegistry,
|
|
106
|
+
hookContext,
|
|
107
|
+
startTime,
|
|
108
|
+
eventDispatcher,
|
|
109
|
+
});
|
|
110
|
+
if (invocationExecution.kind === "completed") {
|
|
111
|
+
return invocationExecution.result;
|
|
692
112
|
}
|
|
113
|
+
const effectivePrompt = invocationExecution.effectivePrompt;
|
|
114
|
+
transcriptMessage = invocationExecution.transcriptMessage;
|
|
693
115
|
const effectivePromptText = typeof effectivePrompt === "string" ? effectivePrompt : transcriptMessage;
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
await hookRegistry.dispatch("before_compaction", { messageCount: rawHistory.length }, hookContext);
|
|
698
|
-
const modelInfo = { model: params.llm.model, api: "openai-completions" };
|
|
699
|
-
const historyResult = skipHistory
|
|
700
|
-
? {
|
|
701
|
-
messages: [],
|
|
702
|
-
priorSummary: undefined,
|
|
703
|
-
compactionEntry: undefined,
|
|
704
|
-
stats: { originalCount: 0, keptCount: 0, estimatedTokens: 0, compacted: false },
|
|
705
|
-
}
|
|
706
|
-
: await manageHistory({
|
|
707
|
-
entries: rawHistory,
|
|
708
|
-
modelInfo,
|
|
709
|
-
contextWindowTokens: params.llm.contextWindow ?? 200_000,
|
|
710
|
-
llm: {
|
|
711
|
-
baseUrl: params.llm.baseUrl,
|
|
712
|
-
apiKey: params.llm.apiKey,
|
|
713
|
-
model: params.llm.model,
|
|
714
|
-
},
|
|
715
|
-
historyLimit: params.historyLimit,
|
|
716
|
-
compactionEnabled: true,
|
|
717
|
-
signal: abortController.signal,
|
|
718
|
-
hooks: hookRegistry,
|
|
719
|
-
hookCtx: hookContext,
|
|
720
|
-
});
|
|
721
|
-
const depth = params.subagentContext?.depth ?? 0;
|
|
722
|
-
const baseTools = createAgentTools(params.dataDir, {
|
|
723
|
-
registry,
|
|
724
|
-
parentSessionId: sessionId,
|
|
725
|
-
depth,
|
|
726
|
-
channel: params.channel,
|
|
727
|
-
llm: params.llm,
|
|
728
|
-
loopDetection: params.loopDetection,
|
|
729
|
-
memoryOptions: {
|
|
730
|
-
providerId: memoryProviderId,
|
|
731
|
-
pluginId: memoryPluginId,
|
|
116
|
+
const runtime = await createAgentRuntime({
|
|
117
|
+
session: {
|
|
118
|
+
runParams: params,
|
|
732
119
|
sessionId,
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
const pluginTools = pluginSystem?.registry.tools.resolveEnabled(params.plugins?.toolAllowlist) ?? [];
|
|
738
|
-
const rawTools = [...baseTools, ...pluginTools];
|
|
739
|
-
const toolNames = rawTools
|
|
740
|
-
.map((tool) => (typeof tool.name === "string" ? tool.name.trim() : ""))
|
|
741
|
-
.filter(Boolean);
|
|
742
|
-
const toolSummaries = {};
|
|
743
|
-
for (const tool of rawTools) {
|
|
744
|
-
const name = typeof tool.name === "string" ? tool.name.trim() : "";
|
|
745
|
-
const description = typeof tool.description === "string" ? tool.description.trim() : "";
|
|
746
|
-
if (!name || !description || toolSummaries[name])
|
|
747
|
-
continue;
|
|
748
|
-
toolSummaries[name] = description;
|
|
749
|
-
}
|
|
750
|
-
const messagingEnabled = params.messaging?.enabled ?? params.channel !== "CRON";
|
|
751
|
-
const messagingChannels = params.messaging?.channels && params.messaging.channels.length > 0
|
|
752
|
-
? params.messaging.channels
|
|
753
|
-
: [params.channel];
|
|
754
|
-
// 6. Build system prompt (with prior summary injected when available)
|
|
755
|
-
let systemPrompt = buildSystemPrompt({
|
|
756
|
-
dataDir: params.dataDir,
|
|
757
|
-
skills,
|
|
758
|
-
contextFiles,
|
|
759
|
-
toolNames,
|
|
760
|
-
toolSummaries,
|
|
761
|
-
promptMode: depth > 0 ? "minimal" : "full",
|
|
762
|
-
bootstrapWarnings,
|
|
763
|
-
memoryCitationsMode: params.memory?.citationsMode ?? "off",
|
|
764
|
-
messaging: {
|
|
765
|
-
enabled: messagingEnabled,
|
|
766
|
-
channels: messagingChannels,
|
|
767
|
-
},
|
|
768
|
-
docs: {
|
|
769
|
-
localPath: params.docs?.localPath,
|
|
770
|
-
webUrl: params.docs?.webUrl,
|
|
771
|
-
sourceUrl: params.docs?.sourceUrl,
|
|
120
|
+
hookRegistry,
|
|
121
|
+
hookContext,
|
|
122
|
+
runContext,
|
|
123
|
+
eventDispatcher,
|
|
772
124
|
},
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
125
|
+
runtimeInputs: {
|
|
126
|
+
contextFiles,
|
|
127
|
+
bootstrapWarnings,
|
|
128
|
+
skills,
|
|
129
|
+
effectivePromptText,
|
|
130
|
+
pluginTools: pluginContext.pluginTools,
|
|
777
131
|
},
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
132
|
+
dependencies: {
|
|
133
|
+
registry,
|
|
134
|
+
spawnFn: (childParams) => runAgentInternal(childParams, new SubagentRegistry()),
|
|
135
|
+
createModel: createOpenAICompatModel,
|
|
136
|
+
abortSignal: abortController.signal,
|
|
783
137
|
},
|
|
784
|
-
currentDate: new Date().toISOString().split("T")[0],
|
|
785
|
-
priorConversationSummary: historyResult.priorSummary,
|
|
786
|
-
});
|
|
787
|
-
// 7. Persist new compaction entry (if history was summarised this run)
|
|
788
|
-
if (historyResult.compactionEntry) {
|
|
789
|
-
await appendTranscriptEntry(params.dataDir, sessionId, historyResult.compactionEntry, transcriptOptions);
|
|
790
|
-
await dispatchProgress(sessionId, {
|
|
791
|
-
type: "compaction",
|
|
792
|
-
reason: `Summarised ${historyResult.stats.originalCount - historyResult.stats.keptCount} older entries`,
|
|
793
|
-
}, params);
|
|
794
|
-
await hookRegistry.dispatch("after_compaction", {
|
|
795
|
-
messageCount: historyResult.stats.originalCount,
|
|
796
|
-
compactedCount: historyResult.stats.originalCount - historyResult.stats.keptCount,
|
|
797
|
-
}, hookContext);
|
|
798
|
-
}
|
|
799
|
-
const beforePromptResults = await hookRegistry.dispatch("before_prompt_build", { prompt: effectivePromptText }, hookContext);
|
|
800
|
-
for (const result of beforePromptResults) {
|
|
801
|
-
if (!result)
|
|
802
|
-
continue;
|
|
803
|
-
if (result.systemPrompt) {
|
|
804
|
-
systemPrompt = result.systemPrompt;
|
|
805
|
-
}
|
|
806
|
-
if (result.prependContext) {
|
|
807
|
-
systemPrompt = `${result.prependContext}\n\n${systemPrompt}`;
|
|
808
|
-
}
|
|
809
|
-
}
|
|
810
|
-
// 8. Create tools (subagent tools included for all agents)
|
|
811
|
-
const toolsWithHooks = wrapToolsWithHooks(rawTools, hookRegistry, hookContext);
|
|
812
|
-
const tools = wrapToolsWithLoopDetection(toolsWithHooks, { sessionId, config: params.loopDetection }, getLoopDetectionState);
|
|
813
|
-
// 9. Create the model and agent
|
|
814
|
-
let resolvedModelId = params.llm.model;
|
|
815
|
-
const beforeModelResults = await hookRegistry.dispatch("before_model_resolve", { prompt: effectivePromptText }, hookContext);
|
|
816
|
-
for (const result of beforeModelResults) {
|
|
817
|
-
if (result && result.modelOverride) {
|
|
818
|
-
resolvedModelId = result.modelOverride;
|
|
819
|
-
}
|
|
820
|
-
}
|
|
821
|
-
const model = createOpenAICompatModel({ ...params.llm, model: resolvedModelId }, sessionId);
|
|
822
|
-
const apiKey = params.llm.apiKey;
|
|
823
|
-
const agent = new Agent({
|
|
824
|
-
initialState: { systemPrompt, model, tools, messages: [] },
|
|
825
|
-
getApiKey: (_provider) => apiKey,
|
|
826
138
|
});
|
|
139
|
+
const agent = runtime.agent;
|
|
140
|
+
const resolvedModelId = runtime.resolvedModelId;
|
|
827
141
|
// 10. Wire timeout to abort the agent
|
|
828
142
|
const timeoutMs = params.timeoutMs ?? 600_000;
|
|
829
143
|
const timeoutHandle = setTimeout(() => {
|
|
@@ -832,10 +146,6 @@ async function runAgentInternal(params, registry) {
|
|
|
832
146
|
}, timeoutMs);
|
|
833
147
|
// Also propagate future aborts to the agent
|
|
834
148
|
abortController.signal.addEventListener("abort", () => agent.abort());
|
|
835
|
-
// 11. Restore managed history into the agent
|
|
836
|
-
if (historyResult.messages.length > 0) {
|
|
837
|
-
agent.replaceMessages(historyResult.messages);
|
|
838
|
-
}
|
|
839
149
|
let totalInputTokens = 0;
|
|
840
150
|
let totalOutputTokens = 0;
|
|
841
151
|
let lastResponseText = "";
|
|
@@ -843,155 +153,71 @@ async function runAgentInternal(params, registry) {
|
|
|
843
153
|
try {
|
|
844
154
|
// 12. Persist the initial user turn before the first model call so the
|
|
845
155
|
// transcript still records the request if the turn aborts or fails.
|
|
846
|
-
await persistInitialUserEntry();
|
|
156
|
+
initialUserEntryPersisted = (await runContext.persistInitialUserEntry(transcriptMessage)) || initialUserEntryPersisted;
|
|
847
157
|
// 13. First agent turn
|
|
848
|
-
const turn = await
|
|
158
|
+
const turn = await executeAgentTurn({
|
|
159
|
+
agent,
|
|
160
|
+
message: effectivePrompt,
|
|
161
|
+
sessionId,
|
|
162
|
+
modelId: resolvedModelId,
|
|
163
|
+
historyMessages: runtime.historyMessages,
|
|
164
|
+
eventDispatcher,
|
|
165
|
+
skillUsageTracker,
|
|
166
|
+
hooks: hookRegistry,
|
|
167
|
+
hookCtx: hookContext,
|
|
168
|
+
abortSignal: abortController.signal,
|
|
169
|
+
});
|
|
849
170
|
lastResponseText = turn.text;
|
|
850
171
|
totalInputTokens += turn.inputTokens;
|
|
851
172
|
totalOutputTokens += turn.outputTokens;
|
|
852
173
|
if (turn.error)
|
|
853
174
|
runError = turn.error;
|
|
854
175
|
// 14. Persist the assistant/tool outputs from the first turn.
|
|
855
|
-
await
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
await appendTranscriptEntry(params.dataDir, sessionId, {
|
|
876
|
-
role: "user",
|
|
877
|
-
content: announceMsg,
|
|
878
|
-
timestamp: new Date().toISOString(),
|
|
879
|
-
}, transcriptOptions);
|
|
880
|
-
const announceTurn = await runAgentTurn(agent, announceMsg, sessionId, resolvedModelId, [], params, skillUsageTracker, hookRegistry, hookContext, abortController.signal);
|
|
881
|
-
lastResponseText = announceTurn.text;
|
|
882
|
-
totalInputTokens += announceTurn.inputTokens;
|
|
883
|
-
totalOutputTokens += announceTurn.outputTokens;
|
|
884
|
-
if (announceTurn.error && !runError)
|
|
885
|
-
runError = announceTurn.error;
|
|
886
|
-
await persistTurnEntries(params.dataDir, sessionId, announceTurn, transcriptOptions);
|
|
176
|
+
await persistTurnEntriesWithWriter(appendEntry, turn);
|
|
177
|
+
const announceResult = await runAnnounceLoop({
|
|
178
|
+
agent,
|
|
179
|
+
registry,
|
|
180
|
+
sessionId,
|
|
181
|
+
resolvedModelId,
|
|
182
|
+
eventDispatcher,
|
|
183
|
+
skillUsageTracker,
|
|
184
|
+
hookRegistry,
|
|
185
|
+
hookContext,
|
|
186
|
+
abortSignal: abortController.signal,
|
|
187
|
+
appendEntry,
|
|
188
|
+
});
|
|
189
|
+
if (announceResult.text) {
|
|
190
|
+
lastResponseText = announceResult.text;
|
|
191
|
+
}
|
|
192
|
+
totalInputTokens += announceResult.inputTokens;
|
|
193
|
+
totalOutputTokens += announceResult.outputTokens;
|
|
194
|
+
if (announceResult.error && !runError) {
|
|
195
|
+
runError = announceResult.error;
|
|
887
196
|
}
|
|
888
197
|
}
|
|
889
198
|
finally {
|
|
890
199
|
clearTimeout(timeoutHandle);
|
|
891
200
|
clearLoopDetectionState(sessionId);
|
|
892
|
-
|
|
201
|
+
runContext.stop();
|
|
893
202
|
}
|
|
894
|
-
// 15. Save session metadata for new sessions
|
|
895
|
-
if (isNewSession) {
|
|
896
|
-
const title = generateSessionTitle(transcriptMessage);
|
|
897
|
-
const now = new Date().toISOString();
|
|
898
|
-
await saveSessionMetadata(params.dataDir, {
|
|
899
|
-
id: sessionId,
|
|
900
|
-
title,
|
|
901
|
-
channel: params.channel,
|
|
902
|
-
createdAt: now,
|
|
903
|
-
updatedAt: now,
|
|
904
|
-
});
|
|
905
|
-
}
|
|
906
|
-
const durationMs = Date.now() - startTime;
|
|
907
203
|
const usage = {
|
|
908
204
|
input: totalInputTokens,
|
|
909
205
|
output: totalOutputTokens,
|
|
910
206
|
total: totalInputTokens + totalOutputTokens,
|
|
911
207
|
};
|
|
912
|
-
|
|
208
|
+
return finalizeCompletedRun({
|
|
913
209
|
sessionId,
|
|
210
|
+
isNewSession,
|
|
211
|
+
transcriptMessage,
|
|
212
|
+
runParams: params,
|
|
213
|
+
hookRegistry,
|
|
214
|
+
hookContext,
|
|
215
|
+
startTime,
|
|
216
|
+
eventDispatcher,
|
|
914
217
|
text: lastResponseText,
|
|
915
218
|
usage,
|
|
916
|
-
durationMs,
|
|
917
219
|
error: runError,
|
|
918
|
-
};
|
|
919
|
-
await hookRegistry.dispatch("agent_end", { success: !runError, error: runError, durationMs }, hookContext);
|
|
920
|
-
const finalTranscript = await loadTranscript(params.dataDir, sessionId);
|
|
921
|
-
await hookRegistry.dispatch("session_end", { sessionId, messageCount: finalTranscript.length, durationMs }, hookContext);
|
|
922
|
-
// 16. Send final callback
|
|
923
|
-
if (params.callbackUrl) {
|
|
924
|
-
if (runError) {
|
|
925
|
-
await sendCallback(params.callbackUrl, {
|
|
926
|
-
sessionId,
|
|
927
|
-
channel: params.channel,
|
|
928
|
-
messageId: params.messageId,
|
|
929
|
-
type: "error",
|
|
930
|
-
message: runError,
|
|
931
|
-
});
|
|
932
|
-
}
|
|
933
|
-
else {
|
|
934
|
-
await sendCallback(params.callbackUrl, {
|
|
935
|
-
sessionId,
|
|
936
|
-
channel: params.channel,
|
|
937
|
-
messageId: params.messageId,
|
|
938
|
-
type: "done",
|
|
939
|
-
result: { text: lastResponseText, usage, durationMs },
|
|
940
|
-
});
|
|
941
|
-
}
|
|
942
|
-
}
|
|
943
|
-
return result;
|
|
944
|
-
}
|
|
945
|
-
async function finalizeSlashCommandResult(params) {
|
|
946
|
-
const { replyText, sessionId, isNewSession, transcriptMessage, initialUserEntryPersisted = false, params: runParams, hookRegistry, hookContext, startTime, compactionEntry, } = params;
|
|
947
|
-
if (!initialUserEntryPersisted) {
|
|
948
|
-
await appendTranscriptEntry(runParams.dataDir, sessionId, {
|
|
949
|
-
role: "user",
|
|
950
|
-
content: transcriptMessage,
|
|
951
|
-
timestamp: new Date().toISOString(),
|
|
952
|
-
});
|
|
953
|
-
}
|
|
954
|
-
if (compactionEntry) {
|
|
955
|
-
await appendTranscriptEntry(runParams.dataDir, sessionId, compactionEntry);
|
|
956
|
-
}
|
|
957
|
-
await appendTranscriptEntry(runParams.dataDir, sessionId, {
|
|
958
|
-
role: "assistant",
|
|
959
|
-
content: replyText,
|
|
960
|
-
timestamp: new Date().toISOString(),
|
|
961
220
|
});
|
|
962
|
-
if (isNewSession) {
|
|
963
|
-
const titleSource = transcriptMessage.trim() ? transcriptMessage : "New session";
|
|
964
|
-
const title = generateSessionTitle(titleSource);
|
|
965
|
-
const now = new Date().toISOString();
|
|
966
|
-
await saveSessionMetadata(runParams.dataDir, {
|
|
967
|
-
id: sessionId,
|
|
968
|
-
title,
|
|
969
|
-
channel: runParams.channel,
|
|
970
|
-
createdAt: now,
|
|
971
|
-
updatedAt: now,
|
|
972
|
-
});
|
|
973
|
-
}
|
|
974
|
-
const durationMs = Date.now() - startTime;
|
|
975
|
-
const usage = { input: 0, output: 0, total: 0 };
|
|
976
|
-
const result = {
|
|
977
|
-
sessionId,
|
|
978
|
-
text: replyText,
|
|
979
|
-
usage,
|
|
980
|
-
durationMs,
|
|
981
|
-
};
|
|
982
|
-
await hookRegistry.dispatch("agent_end", { success: true, error: undefined, durationMs }, hookContext);
|
|
983
|
-
const finalTranscript = await loadTranscript(runParams.dataDir, sessionId);
|
|
984
|
-
await hookRegistry.dispatch("session_end", { sessionId, messageCount: finalTranscript.length, durationMs }, hookContext);
|
|
985
|
-
if (runParams.callbackUrl) {
|
|
986
|
-
await sendCallback(runParams.callbackUrl, {
|
|
987
|
-
sessionId,
|
|
988
|
-
channel: runParams.channel,
|
|
989
|
-
messageId: runParams.messageId,
|
|
990
|
-
type: "done",
|
|
991
|
-
result: { text: replyText, usage, durationMs },
|
|
992
|
-
});
|
|
993
|
-
}
|
|
994
|
-
return result;
|
|
995
221
|
}
|
|
996
222
|
/**
|
|
997
223
|
* Runs an agent session end-to-end.
|