@agentprojectcontext/apx 1.10.1 → 1.10.3
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/package.json +1 -1
- package/src/cli/commands/session.js +2 -2
- package/src/cli/commands/sys.js +79 -17
- package/src/cli/http.js +57 -0
- package/src/cli/terminal-chat/renderer.js +5 -0
- package/src/core/scaffold.js +1 -0
- package/src/daemon/api.js +44 -0
- package/src/daemon/db.js +13 -4
- package/src/daemon/engines/mock.js +17 -0
- package/src/daemon/super-agent-tools/tools/call-runtime.js +43 -2
- package/src/daemon/super-agent.js +33 -8
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@ import path from "node:path";
|
|
|
3
3
|
import { findApfRoot, readAgents } from "../../core/parser.js";
|
|
4
4
|
import { getOrCreateApxId } from "../../core/scaffold.js";
|
|
5
5
|
import { generateSessionId } from "../../core/session-store.js";
|
|
6
|
-
import { ensureProjectStorage } from "../../core/config.js";
|
|
6
|
+
import { projectStorageRoot, ensureProjectStorage } from "../../core/config.js";
|
|
7
7
|
import { http } from "../http.js";
|
|
8
8
|
import { resolveProjectId } from "./project.js";
|
|
9
9
|
|
|
@@ -18,7 +18,7 @@ function requireRoot() {
|
|
|
18
18
|
function requireStorageRoot(root) {
|
|
19
19
|
const apxId = getOrCreateApxId(root);
|
|
20
20
|
if (!apxId) throw new Error("could not resolve APX project storage id");
|
|
21
|
-
return
|
|
21
|
+
return projectStorageRoot(apxId);
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
const nowIso = () => new Date().toISOString().replace(/\.\d{3}Z$/, "Z");
|
package/src/cli/commands/sys.js
CHANGED
|
@@ -300,31 +300,93 @@ async function submitPrompt(pid, state, previousMessages, renderScreen, close) {
|
|
|
300
300
|
model: state.activeModel,
|
|
301
301
|
};
|
|
302
302
|
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
303
|
+
let result;
|
|
304
|
+
try {
|
|
305
|
+
result = await http.streamPost(
|
|
306
|
+
`/projects/${pid}/super-agent/chat/stream`,
|
|
307
|
+
body,
|
|
308
|
+
(event) => handleProgressEvent(event, state, renderScreen)
|
|
309
|
+
);
|
|
310
|
+
} catch (e) {
|
|
311
|
+
if (e.status !== 404) throw e;
|
|
312
|
+
result = await http.post(`/projects/${pid}/super-agent/chat`, body);
|
|
313
|
+
removeStatus(state);
|
|
314
|
+
for (const trace of result.trace || []) {
|
|
315
|
+
state.transcript.push({ type: "tool", trace });
|
|
316
|
+
}
|
|
307
317
|
}
|
|
308
318
|
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
319
|
+
completeSuperAgentResult(result, text, startTime, state, previousMessages);
|
|
320
|
+
} catch (e) {
|
|
321
|
+
removeStatus(state);
|
|
322
|
+
state.transcript.push({ type: "error", text: e.message });
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
renderScreen();
|
|
326
|
+
}
|
|
312
327
|
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
328
|
+
function removeStatus(state) {
|
|
329
|
+
const last = state.transcript[state.transcript.length - 1];
|
|
330
|
+
if (last?.type === "status") state.transcript.pop();
|
|
331
|
+
}
|
|
317
332
|
|
|
333
|
+
function handleProgressEvent(event, state, renderScreen) {
|
|
334
|
+
if (event.type === "model_start") {
|
|
335
|
+
const last = state.transcript[state.transcript.length - 1];
|
|
336
|
+
if (last?.type === "status") {
|
|
337
|
+
last.text = event.iteration > 1 ? `Thinking... step ${event.iteration}` : "Thinking...";
|
|
338
|
+
renderScreen();
|
|
339
|
+
}
|
|
340
|
+
return;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
if (event.type === "assistant_text" && event.text) {
|
|
344
|
+
removeStatus(state);
|
|
318
345
|
state.transcript.push({
|
|
319
346
|
type: "assistant",
|
|
320
347
|
name: state.activeAgent,
|
|
321
|
-
text:
|
|
322
|
-
meta:
|
|
348
|
+
text: event.text,
|
|
349
|
+
meta: "intermediate",
|
|
323
350
|
});
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
state.transcript.push({ type: "error", text: e.message });
|
|
351
|
+
renderScreen();
|
|
352
|
+
return;
|
|
327
353
|
}
|
|
328
354
|
|
|
329
|
-
|
|
355
|
+
if (event.type === "tool_start" && event.trace) {
|
|
356
|
+
removeStatus(state);
|
|
357
|
+
state.transcript.push({ type: "tool", trace: event.trace });
|
|
358
|
+
renderScreen();
|
|
359
|
+
return;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
if (event.type === "tool_result" && event.trace) {
|
|
363
|
+
removeStatus(state);
|
|
364
|
+
const idx = state.transcript.findIndex(
|
|
365
|
+
(item) => item.type === "tool" && item.trace?.id && item.trace.id === event.trace.id
|
|
366
|
+
);
|
|
367
|
+
if (idx >= 0) state.transcript[idx] = { type: "tool", trace: event.trace };
|
|
368
|
+
else state.transcript.push({ type: "tool", trace: event.trace });
|
|
369
|
+
renderScreen();
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
function completeSuperAgentResult(result, userText, startTime, state, previousMessages) {
|
|
374
|
+
removeStatus(state);
|
|
375
|
+
if (!result) throw new Error("super-agent stream ended without final result");
|
|
376
|
+
|
|
377
|
+
previousMessages.push({ role: "user", content: userText });
|
|
378
|
+
previousMessages.push({ role: "assistant", content: result.text });
|
|
379
|
+
if (previousMessages.length > 20) previousMessages.splice(0, previousMessages.length - 20);
|
|
380
|
+
|
|
381
|
+
const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
|
|
382
|
+
state.usage.input += result.usage?.input_tokens || 0;
|
|
383
|
+
state.usage.output += result.usage?.output_tokens || 0;
|
|
384
|
+
state.usage.percent = Math.min(99, Math.round((state.usage.input / 200000) * 100));
|
|
385
|
+
|
|
386
|
+
state.transcript.push({
|
|
387
|
+
type: "assistant",
|
|
388
|
+
name: state.activeAgent,
|
|
389
|
+
text: result.text,
|
|
390
|
+
meta: `${elapsed}s · In: ${result.usage?.input_tokens || 0} Out: ${result.usage?.output_tokens || 0}`,
|
|
391
|
+
});
|
|
330
392
|
}
|
package/src/cli/http.js
CHANGED
|
@@ -91,9 +91,66 @@ async function request(method, path, body, opts = {}) {
|
|
|
91
91
|
return json;
|
|
92
92
|
}
|
|
93
93
|
|
|
94
|
+
async function streamRequest(method, path, body, onEvent, opts = {}) {
|
|
95
|
+
if (opts.autoStart !== false) await ensureDaemon();
|
|
96
|
+
else if (!(await ping())) {
|
|
97
|
+
throw new Error(`apx daemon not running (no response on ${baseUrl()})`);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const res = await fetch(`${baseUrl()}${path}`, {
|
|
101
|
+
method,
|
|
102
|
+
headers: body ? { "content-type": "application/json" } : {},
|
|
103
|
+
body: body ? JSON.stringify(body) : undefined,
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
if (!res.ok) {
|
|
107
|
+
const text = await res.text();
|
|
108
|
+
let json = null;
|
|
109
|
+
try { json = text ? JSON.parse(text) : null; } catch {}
|
|
110
|
+
const err = new Error(json?.error || `${method} ${path} → ${res.status}`);
|
|
111
|
+
err.status = res.status;
|
|
112
|
+
throw err;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (!res.body?.getReader) {
|
|
116
|
+
throw new Error("streaming response is not supported by this Node.js runtime");
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const reader = res.body.getReader();
|
|
120
|
+
const decoder = new TextDecoder();
|
|
121
|
+
let buffer = "";
|
|
122
|
+
let finalResult = null;
|
|
123
|
+
|
|
124
|
+
while (true) {
|
|
125
|
+
const { value, done } = await reader.read();
|
|
126
|
+
if (done) break;
|
|
127
|
+
buffer += decoder.decode(value, { stream: true });
|
|
128
|
+
const lines = buffer.split(/\r?\n/);
|
|
129
|
+
buffer = lines.pop() || "";
|
|
130
|
+
for (const line of lines) {
|
|
131
|
+
if (!line.trim()) continue;
|
|
132
|
+
const event = JSON.parse(line);
|
|
133
|
+
if (event.type === "final") finalResult = event.result;
|
|
134
|
+
if (event.type === "error") throw new Error(event.error || "stream error");
|
|
135
|
+
await onEvent?.(event);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
buffer += decoder.decode();
|
|
140
|
+
if (buffer.trim()) {
|
|
141
|
+
const event = JSON.parse(buffer);
|
|
142
|
+
if (event.type === "final") finalResult = event.result;
|
|
143
|
+
if (event.type === "error") throw new Error(event.error || "stream error");
|
|
144
|
+
await onEvent?.(event);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return finalResult;
|
|
148
|
+
}
|
|
149
|
+
|
|
94
150
|
export const http = {
|
|
95
151
|
get: (p, opts) => request("GET", p, undefined, opts),
|
|
96
152
|
post: (p, body, opts) => request("POST", p, body, opts),
|
|
153
|
+
streamPost: (p, body, onEvent, opts) => streamRequest("POST", p, body, onEvent, opts),
|
|
97
154
|
put: (p, body, opts) => request("PUT", p, body, opts),
|
|
98
155
|
patch: (p, body, opts) => request("PATCH", p, body, opts),
|
|
99
156
|
delete: (p, opts) => request("DELETE", p, undefined, opts),
|
|
@@ -229,6 +229,11 @@ function addToolBlock(lines, item, width) {
|
|
|
229
229
|
addLine(lines, "", C.bg);
|
|
230
230
|
addLine(lines, margin + C.muted + `→ ${label}${target ? " " + fit(String(target), inner) : ""}`, C.bg);
|
|
231
231
|
|
|
232
|
+
if (trace.pending) {
|
|
233
|
+
addLine(lines, margin + C.dim + " " + C.muted + "running...", C.bg);
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
|
|
232
237
|
if (trace.tool === "write_file") {
|
|
233
238
|
const heading = `# Wrote ${args.path || "file"}`;
|
|
234
239
|
addLine(lines, margin + C.panel + " " + C.muted + heading + " ".repeat(Math.max(0, inner - visible(heading))), C.bg);
|
package/src/core/scaffold.js
CHANGED
|
@@ -256,6 +256,7 @@ export function getOrCreateApxId(root) {
|
|
|
256
256
|
try { cfg = JSON.parse(fs.readFileSync(p, "utf8")); } catch { return null; }
|
|
257
257
|
if (cfg.apx_id) return cfg.apx_id;
|
|
258
258
|
const apxId = crypto.randomUUID().replace(/-/g, "").slice(0, 12);
|
|
259
|
+
console.log(`[apx] Generating new stable ID ${apxId} for project at ${root}`);
|
|
259
260
|
cfg.apx_id = apxId;
|
|
260
261
|
fs.writeFileSync(p, JSON.stringify(cfg, null, 2) + "\n");
|
|
261
262
|
return apxId;
|
package/src/daemon/api.js
CHANGED
|
@@ -525,6 +525,50 @@ export function buildApi({ projects, registries, plugins, scheduler, version, st
|
|
|
525
525
|
});
|
|
526
526
|
|
|
527
527
|
// POST /projects/:pid/super-agent/chat
|
|
528
|
+
app.post("/projects/:pid/super-agent/chat/stream", async (req, res) => {
|
|
529
|
+
const p = project(req, res);
|
|
530
|
+
if (!p) return;
|
|
531
|
+
const { prompt, contextNote, previousMessages, model } = req.body || {};
|
|
532
|
+
if (!prompt) return res.status(400).json({ error: "prompt required" });
|
|
533
|
+
|
|
534
|
+
res.setHeader("content-type", "application/x-ndjson; charset=utf-8");
|
|
535
|
+
res.setHeader("cache-control", "no-cache, no-transform");
|
|
536
|
+
res.setHeader("x-accel-buffering", "no");
|
|
537
|
+
res.flushHeaders?.();
|
|
538
|
+
|
|
539
|
+
const send = (event) => {
|
|
540
|
+
res.write(JSON.stringify(event) + "\n");
|
|
541
|
+
};
|
|
542
|
+
|
|
543
|
+
try {
|
|
544
|
+
const saResult = await runSuperAgent({
|
|
545
|
+
globalConfig: config,
|
|
546
|
+
projects,
|
|
547
|
+
plugins,
|
|
548
|
+
registries,
|
|
549
|
+
prompt,
|
|
550
|
+
contextNote: contextNote || `Context: Project ${p.id} (${p.name}) at ${p.path}`,
|
|
551
|
+
previousMessages: previousMessages || [],
|
|
552
|
+
overrideModel: model,
|
|
553
|
+
onEvent: send,
|
|
554
|
+
});
|
|
555
|
+
projects.rebuild(p.id);
|
|
556
|
+
send({
|
|
557
|
+
type: "final",
|
|
558
|
+
result: {
|
|
559
|
+
text: saResult.text,
|
|
560
|
+
usage: saResult.usage,
|
|
561
|
+
name: saResult.name,
|
|
562
|
+
trace: saResult.trace,
|
|
563
|
+
},
|
|
564
|
+
});
|
|
565
|
+
res.end();
|
|
566
|
+
} catch (e) {
|
|
567
|
+
send({ type: "error", error: e.message });
|
|
568
|
+
res.end();
|
|
569
|
+
}
|
|
570
|
+
});
|
|
571
|
+
|
|
528
572
|
app.post("/projects/:pid/super-agent/chat", async (req, res) => {
|
|
529
573
|
const p = project(req, res);
|
|
530
574
|
if (!p) return;
|
package/src/daemon/db.js
CHANGED
|
@@ -37,9 +37,11 @@ export class ProjectManager {
|
|
|
37
37
|
// Ensure directories exist for projects initialized before they were added.
|
|
38
38
|
fs.mkdirSync(path.join(abs, ".apc", "commands"), { recursive: true });
|
|
39
39
|
|
|
40
|
-
//
|
|
40
|
+
// Resolve stable APX storage ID (read from .apc/project.json).
|
|
41
41
|
const apxId = getOrCreateApxId(abs);
|
|
42
|
-
|
|
42
|
+
// Don't create the physical storage folder yet.
|
|
43
|
+
// Just resolve where it SHOULD be.
|
|
44
|
+
const storagePath = path.join(path.dirname(DEFAULT_PROJECT_STORE), apxId || "null");
|
|
43
45
|
|
|
44
46
|
const entry = {
|
|
45
47
|
id: this._nextId++,
|
|
@@ -48,8 +50,15 @@ export class ProjectManager {
|
|
|
48
50
|
apxId,
|
|
49
51
|
config: effectiveConfig(this.globalConfig, abs),
|
|
50
52
|
};
|
|
51
|
-
|
|
52
|
-
|
|
53
|
+
|
|
54
|
+
// Lazy message logger: ensure directory exists ONLY when writing.
|
|
55
|
+
entry.logMessage = (payload) => {
|
|
56
|
+
if (entry.apxId) {
|
|
57
|
+
ensureProjectStorage(entry.apxId);
|
|
58
|
+
}
|
|
59
|
+
return appendMessageToFs({ projectRoot: entry.storagePath, ...payload });
|
|
60
|
+
};
|
|
61
|
+
|
|
53
62
|
this.byId.set(entry.id, entry);
|
|
54
63
|
this.byPath.set(abs, entry);
|
|
55
64
|
return entry;
|
|
@@ -8,6 +8,23 @@ export default {
|
|
|
8
8
|
async chat({ system, messages, model = "mock" }) {
|
|
9
9
|
const last = [...messages].reverse().find((m) => m.role === "user");
|
|
10
10
|
const userText = last?.content || "";
|
|
11
|
+
const requestedTool = userText.match(/\[mock:tool:([a-z_]+)\]/)?.[1];
|
|
12
|
+
const hasToolResult = messages.some((m) => m.role === "tool");
|
|
13
|
+
if (requestedTool && !hasToolResult) {
|
|
14
|
+
const toolCall = {
|
|
15
|
+
id: "mock-call-1",
|
|
16
|
+
type: "function",
|
|
17
|
+
function: { name: requestedTool, arguments: "{}" },
|
|
18
|
+
};
|
|
19
|
+
return {
|
|
20
|
+
text: "",
|
|
21
|
+
tool_calls: [toolCall],
|
|
22
|
+
message: { tool_calls: [toolCall] },
|
|
23
|
+
usage: { input_tokens: userText.length, output_tokens: 4 },
|
|
24
|
+
raw: { model, mock: true },
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
|
|
11
28
|
const sysHint = system ? ` (system: ${system.slice(0, 40)}…)` : "";
|
|
12
29
|
return {
|
|
13
30
|
text: `[mock:${model}] received: ${userText}${sysHint}`,
|
|
@@ -7,6 +7,8 @@ import {
|
|
|
7
7
|
createRuntimeSession,
|
|
8
8
|
extractApfResult,
|
|
9
9
|
} from "../../apc-runtime-context.js";
|
|
10
|
+
import { detectAll } from "../../env-detect.js";
|
|
11
|
+
import { runProcess } from "../../runtimes/_spawn.js";
|
|
10
12
|
import { getRuntime, RUNTIME_IDS } from "../../runtimes/index.js";
|
|
11
13
|
import { buildAgentSystem, confirmedProperty, resolveProject } from "../helpers.js";
|
|
12
14
|
|
|
@@ -61,18 +63,43 @@ function buildRuntimeSystem(project, agent, runtime, sessionId, caller) {
|
|
|
61
63
|
].join("\n\n");
|
|
62
64
|
}
|
|
63
65
|
|
|
66
|
+
async function runtimeAvailability(runtime, rt) {
|
|
67
|
+
const probe = await runProcess({
|
|
68
|
+
command: rt.binary,
|
|
69
|
+
args: rt.versionFlag ? [rt.versionFlag] : ["--version"],
|
|
70
|
+
timeoutMs: 3000,
|
|
71
|
+
});
|
|
72
|
+
if (probe.exitCode === 0 || probe.stdout || probe.stderr) {
|
|
73
|
+
return { ok: true };
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const detected = await detectAll();
|
|
77
|
+
const current = detected.find((d) => d.id === runtime || d.binary === rt.binary);
|
|
78
|
+
if (current?.installed) {
|
|
79
|
+
return { ok: true, detected };
|
|
80
|
+
}
|
|
81
|
+
return {
|
|
82
|
+
ok: false,
|
|
83
|
+
reason: current?.reason || `${rt.binary} not found`,
|
|
84
|
+
detected,
|
|
85
|
+
installed: detected
|
|
86
|
+
.filter((d) => d.category === "runtime" && d.installed)
|
|
87
|
+
.map((d) => d.id),
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
|
|
64
91
|
export default {
|
|
65
92
|
name: "call_runtime",
|
|
66
93
|
schema: {
|
|
67
94
|
type: "function",
|
|
68
95
|
function: {
|
|
69
96
|
name: "call_runtime",
|
|
70
|
-
description: "Spawn an external CLI runtime (Claude Code, Codex, OpenCode, Aider, Cursor Agent, Gemini CLI, Qwen Code)
|
|
97
|
+
description: "Spawn an external CLI runtime (Claude Code, Codex, OpenCode, Aider, Cursor Agent, Gemini CLI, Qwen Code). Omit agent for the base APX/default self-run.",
|
|
71
98
|
parameters: {
|
|
72
99
|
type: "object",
|
|
73
100
|
properties: {
|
|
74
101
|
project: { type: "string" },
|
|
75
|
-
agent: { type: "string", description: "
|
|
102
|
+
agent: { type: "string", description: "APC agent slug. MANDATORY OMIT if you are acting as yourself (APX/vos mismo/default). Use ONLY if the user named a specific agent from AGENTS.md." },
|
|
76
103
|
runtime: {
|
|
77
104
|
type: "string",
|
|
78
105
|
enum: RUNTIME_IDS,
|
|
@@ -108,6 +135,19 @@ export default {
|
|
|
108
135
|
return { error: `${e.message}. Available runtimes: ${RUNTIME_IDS.join(", ")}` };
|
|
109
136
|
}
|
|
110
137
|
|
|
138
|
+
const availability = await runtimeAvailability(runtime, rt);
|
|
139
|
+
if (!availability.ok) {
|
|
140
|
+
return {
|
|
141
|
+
error: `runtime "${runtime}" is not installed or not runnable (${availability.reason})`,
|
|
142
|
+
runtime,
|
|
143
|
+
binary: rt.binary,
|
|
144
|
+
installed_runtimes: availability.installed,
|
|
145
|
+
hint: availability.installed.length
|
|
146
|
+
? `Try one of: ${availability.installed.join(", ")}`
|
|
147
|
+
: "No external runtime CLIs were detected. Run apx env detect for details.",
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
|
|
111
151
|
const actor = agent?.slug || "apx";
|
|
112
152
|
const session = createRuntimeSession({
|
|
113
153
|
projectRoot: p.path,
|
|
@@ -163,6 +203,7 @@ export default {
|
|
|
163
203
|
apc_session: session.id,
|
|
164
204
|
exit_code: r.exitCode,
|
|
165
205
|
output: (r.output || "").slice(0, 4000),
|
|
206
|
+
stderr: (r.stderr || "").slice(0, 2000),
|
|
166
207
|
truncated: (r.output || "").length > 4000,
|
|
167
208
|
external_session_path: r.externalSessionPath || null,
|
|
168
209
|
session_id: r.sessionId || null,
|
|
@@ -53,13 +53,14 @@ HARD RULES (do not deviate):
|
|
|
53
53
|
7. Stay brief: under 6 sentences unless asked for detail.
|
|
54
54
|
8. You DO see recent prior turns of this chat as previous messages when applicable. **Use them ONLY to disambiguate references** (e.g. "el primero" → first project mentioned earlier). For ANY factual data — agent details, MCP details, file contents, memory — RE-CALL the tool. Past turns are context, not a cache. Models change, agents change, files change.
|
|
55
55
|
9. /reset or /new from the user means "forget previous turns and answer this one fresh" — if you see those prefixes the operator already cleared the context for you.
|
|
56
|
-
10.
|
|
57
|
-
11.
|
|
58
|
-
12.
|
|
59
|
-
13.
|
|
60
|
-
14.
|
|
61
|
-
15.
|
|
62
|
-
16.
|
|
56
|
+
10. **SELF-RUN RULE**: If the user says "vos mismo", "tu mismo", "same", "base", "default", "sin agente", or does not explicitly name an agent slug, act as APX. **DO NOT** call list_agents. **DO NOT** pass an 'agent' argument to tools.
|
|
57
|
+
11. DELEGATION RULE: When the user asks a named APC agent to do a task, use call_agent (unless they specify opening it in a runtime, then see rule 12).
|
|
58
|
+
12. **DISPATCH RULE**: Use call_runtime for external runtimes. If the user named an agent, pass it. If they didn't, **DO NOT PASS ANY AGENT**. Running with an empty agent field is how you run as yourself.
|
|
59
|
+
13. PROJECT RULE: When the user gives no project, use project "default". Do not infer a non-default project from old chat history unless the user references it. If they mention a path or project name, look it up or add it with add_project.
|
|
60
|
+
14. VAULT RULE: When the user wants a new existing agent/template, call list_vault_agents first. If a suitable vault agent exists, import_agent into the chosen project. If none fits, say briefly what is missing.
|
|
61
|
+
15. NO-PENDING RULE: never say "give me a second", "I will do it", or "I will try later" as a final answer. Either call the tool in this same turn or say what blocks you.
|
|
62
|
+
16. IDENTITY RULE: when the user asks you to change your name, call yourself something, or update your personality/language, call set_identity and persist the change. Then confirm with your new name.
|
|
63
|
+
17. ROUTINES RULE: NEVER create a routine in the default project (id=0). Routines MUST be tied to a specific registered project. Before adding a routine, call list_projects to find the correct project id or name. Then pass --project <id|name> to apx routine add. If no project fits, ask the user which project to use. Creating routines in project 0/default mixes unrelated projects' schedules and corrupts state.`;
|
|
63
64
|
|
|
64
65
|
function isShortConfirmation(text) {
|
|
65
66
|
return /^(yes|y|si|si dale|dale|ok|okay|confirm|confirmed|go|proceed|do it)\b/i
|
|
@@ -87,6 +88,7 @@ export async function runSuperAgent({
|
|
|
87
88
|
contextNote = "",
|
|
88
89
|
previousMessages = [],
|
|
89
90
|
overrideModel = null,
|
|
91
|
+
onEvent = null,
|
|
90
92
|
}) {
|
|
91
93
|
if (!isSuperAgentEnabled(globalConfig)) {
|
|
92
94
|
throw new Error("super-agent not enabled (set super_agent.enabled and .model in ~/.apx/config.json)");
|
|
@@ -141,6 +143,7 @@ export async function runSuperAgent({
|
|
|
141
143
|
let lastText = "";
|
|
142
144
|
|
|
143
145
|
for (let iter = 0; iter < MAX_TOOL_ITERS; iter++) {
|
|
146
|
+
await emitProgress(onEvent, { type: "model_start", iteration: iter + 1 });
|
|
144
147
|
const result = await callEngine({
|
|
145
148
|
modelId: activeModel,
|
|
146
149
|
system,
|
|
@@ -174,6 +177,11 @@ export async function runSuperAgent({
|
|
|
174
177
|
break;
|
|
175
178
|
}
|
|
176
179
|
|
|
180
|
+
const visibleText = cleanTextOfPseudoToolCalls(lastText).trim();
|
|
181
|
+
if (visibleText) {
|
|
182
|
+
await emitProgress(onEvent, { type: "assistant_text", text: visibleText, iteration: iter + 1 });
|
|
183
|
+
}
|
|
184
|
+
|
|
177
185
|
// Append the assistant turn (with its tool_calls) and execute each call.
|
|
178
186
|
conversation.push({
|
|
179
187
|
role: "assistant",
|
|
@@ -191,6 +199,12 @@ export async function runSuperAgent({
|
|
|
191
199
|
args = args || {};
|
|
192
200
|
|
|
193
201
|
let toolResult;
|
|
202
|
+
const traceId = `${iter + 1}:${trace.length + 1}`;
|
|
203
|
+
await emitProgress(onEvent, {
|
|
204
|
+
type: "tool_start",
|
|
205
|
+
trace: { id: traceId, tool: name, args, pending: true },
|
|
206
|
+
iteration: iter + 1,
|
|
207
|
+
});
|
|
194
208
|
try {
|
|
195
209
|
const handler = handlers[name];
|
|
196
210
|
if (!handler) {
|
|
@@ -202,7 +216,13 @@ export async function runSuperAgent({
|
|
|
202
216
|
toolResult = { error: e.message };
|
|
203
217
|
}
|
|
204
218
|
|
|
205
|
-
|
|
219
|
+
const traceItem = { id: traceId, tool: name, args, result: summarizeForTrace(toolResult) };
|
|
220
|
+
trace.push(traceItem);
|
|
221
|
+
await emitProgress(onEvent, {
|
|
222
|
+
type: "tool_result",
|
|
223
|
+
trace: traceItem,
|
|
224
|
+
iteration: iter + 1,
|
|
225
|
+
});
|
|
206
226
|
|
|
207
227
|
conversation.push({
|
|
208
228
|
role: "tool",
|
|
@@ -220,6 +240,11 @@ export async function runSuperAgent({
|
|
|
220
240
|
};
|
|
221
241
|
}
|
|
222
242
|
|
|
243
|
+
async function emitProgress(onEvent, event) {
|
|
244
|
+
if (typeof onEvent !== "function") return;
|
|
245
|
+
await onEvent(event);
|
|
246
|
+
}
|
|
247
|
+
|
|
223
248
|
function summarizeForTrace(r) {
|
|
224
249
|
if (r === null || r === undefined) return r;
|
|
225
250
|
const s = JSON.stringify(r);
|