@agentprojectcontext/apx 1.10.0 → 1.10.2
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/command.js +25 -5
- package/src/cli/commands/sys.js +79 -17
- package/src/cli/http.js +57 -0
- package/src/cli/index.js +39 -62
- package/src/cli/terminal-chat/renderer.js +5 -0
- package/src/core/apx-skill.md +1 -1
- package/src/daemon/api.js +44 -0
- 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-tools/tools/set-identity.js +1 -1
- package/src/daemon/super-agent-tools/tools/set-permission-mode.js +1 -1
- package/src/daemon/super-agent.js +33 -8
package/package.json
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
import fs from "node:fs";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { findApfRoot } from "../../core/parser.js";
|
|
5
|
+
import { http } from "../http.js";
|
|
6
|
+
import { resolveProjectId } from "./project.js";
|
|
5
7
|
|
|
6
8
|
function commandsDir(root) {
|
|
7
9
|
return path.join(root, ".apc", "commands");
|
|
@@ -13,9 +15,28 @@ function listCommandFiles(root) {
|
|
|
13
15
|
return fs.readdirSync(dir).filter((f) => f.endsWith(".md")).sort();
|
|
14
16
|
}
|
|
15
17
|
|
|
16
|
-
|
|
18
|
+
async function resolveCommandRoot(args = {}) {
|
|
19
|
+
const explicitProject = args?.flags?.project;
|
|
20
|
+
if (explicitProject !== undefined && explicitProject !== null && explicitProject !== "") {
|
|
21
|
+
const pid = await resolveProjectId(explicitProject);
|
|
22
|
+
const projects = await http.get("/projects");
|
|
23
|
+
const project = projects.find((p) => p.id === pid);
|
|
24
|
+
if (!project) throw new Error(`project ${pid} not found`);
|
|
25
|
+
return project.path;
|
|
26
|
+
}
|
|
27
|
+
|
|
17
28
|
const root = findApfRoot();
|
|
18
|
-
if (
|
|
29
|
+
if (root) return root;
|
|
30
|
+
|
|
31
|
+
const pid = await resolveProjectId();
|
|
32
|
+
const projects = await http.get("/projects");
|
|
33
|
+
const project = projects.find((p) => p.id === pid);
|
|
34
|
+
if (!project) throw new Error(`project ${pid} not found`);
|
|
35
|
+
return project.path;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export async function cmdCommandList(args = {}) {
|
|
39
|
+
const root = await resolveCommandRoot(args);
|
|
19
40
|
const files = listCommandFiles(root);
|
|
20
41
|
if (files.length === 0) {
|
|
21
42
|
console.log("(no commands — add .md files to .apc/commands/)");
|
|
@@ -31,11 +52,10 @@ export function cmdCommandList() {
|
|
|
31
52
|
}
|
|
32
53
|
}
|
|
33
54
|
|
|
34
|
-
export function cmdCommandShow(args) {
|
|
55
|
+
export async function cmdCommandShow(args) {
|
|
35
56
|
const name = args._[0];
|
|
36
57
|
if (!name) throw new Error("apx command show: missing <name>");
|
|
37
|
-
const root =
|
|
38
|
-
if (!root) throw new Error("not inside an APC project");
|
|
58
|
+
const root = await resolveCommandRoot(args);
|
|
39
59
|
const file = path.join(commandsDir(root), `${name}.md`);
|
|
40
60
|
if (!fs.existsSync(file)) throw new Error(`command "${name}" not found in .apc/commands/`);
|
|
41
61
|
process.stdout.write(fs.readFileSync(file, "utf8"));
|
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),
|
package/src/cli/index.js
CHANGED
|
@@ -60,7 +60,7 @@ import {
|
|
|
60
60
|
cmdConversationsList,
|
|
61
61
|
cmdConversationsGet,
|
|
62
62
|
} from "./commands/chat.js";
|
|
63
|
-
import { cmdSys } from "./commands/sys.js";
|
|
63
|
+
import { cmdSys as cmdCode } from "./commands/sys.js";
|
|
64
64
|
import { cmdRun, cmdEnvDetect } from "./commands/runtime.js";
|
|
65
65
|
import { cmdSend, cmdConnections } from "./commands/a2a.js";
|
|
66
66
|
import {
|
|
@@ -184,7 +184,7 @@ const HELP_TOPICS = new Map(Object.entries({
|
|
|
184
184
|
title: "apx project add",
|
|
185
185
|
summary: "Register a project with the APX daemon.",
|
|
186
186
|
usage: ["apx project add [path]"],
|
|
187
|
-
examples: ["apx project add .", "apx add
|
|
187
|
+
examples: ["apx project add .", "apx project add ../repo"],
|
|
188
188
|
}),
|
|
189
189
|
"project list": topic({
|
|
190
190
|
title: "apx project list",
|
|
@@ -204,19 +204,6 @@ const HELP_TOPICS = new Map(Object.entries({
|
|
|
204
204
|
usage: ["apx project rebuild [id]"],
|
|
205
205
|
examples: ["apx project rebuild", "apx project rebuild default"],
|
|
206
206
|
}),
|
|
207
|
-
add: topic({
|
|
208
|
-
title: "apx add",
|
|
209
|
-
summary: "Friendly alias namespace for add operations.",
|
|
210
|
-
usage: ["apx add project [path]"],
|
|
211
|
-
commands: [["project [path]", "Alias for apx project add [path]."]],
|
|
212
|
-
examples: ["apx add project ."],
|
|
213
|
-
}),
|
|
214
|
-
"add project": topic({
|
|
215
|
-
title: "apx add project",
|
|
216
|
-
summary: "Alias for apx project add.",
|
|
217
|
-
usage: ["apx add project [path]"],
|
|
218
|
-
examples: ["apx add project ."],
|
|
219
|
-
}),
|
|
220
207
|
agent: topic({
|
|
221
208
|
title: "apx agent",
|
|
222
209
|
summary: "Create, inspect, import, and vault agent definitions.",
|
|
@@ -298,7 +285,7 @@ const HELP_TOPICS = new Map(Object.entries({
|
|
|
298
285
|
}),
|
|
299
286
|
identity: topic({
|
|
300
287
|
title: "apx identity",
|
|
301
|
-
summary: "Read or edit
|
|
288
|
+
summary: "Read or edit APX profile identity fields.",
|
|
302
289
|
usage: ["apx identity <show|set|wizard> [args]"],
|
|
303
290
|
commands: [
|
|
304
291
|
["show", "Print current identity."],
|
|
@@ -309,13 +296,13 @@ const HELP_TOPICS = new Map(Object.entries({
|
|
|
309
296
|
}),
|
|
310
297
|
"identity show": topic({
|
|
311
298
|
title: "apx identity show",
|
|
312
|
-
summary: "Print current
|
|
299
|
+
summary: "Print current APX profile identity.",
|
|
313
300
|
usage: ["apx identity show"],
|
|
314
301
|
examples: ["apx identity show"],
|
|
315
302
|
}),
|
|
316
303
|
"identity set": topic({
|
|
317
304
|
title: "apx identity set",
|
|
318
|
-
summary: "Set one
|
|
305
|
+
summary: "Set one APX profile identity field.",
|
|
319
306
|
usage: ["apx identity set <key> <value>"],
|
|
320
307
|
examples: ["apx identity set agent_name Ada", "apx identity set owner_name Sam"],
|
|
321
308
|
}),
|
|
@@ -364,7 +351,7 @@ const HELP_TOPICS = new Map(Object.entries({
|
|
|
364
351
|
}),
|
|
365
352
|
permission: topic({
|
|
366
353
|
title: "apx permission",
|
|
367
|
-
summary: "Show or set
|
|
354
|
+
summary: "Show or set APX tool permission mode.",
|
|
368
355
|
usage: ["apx permission show", "apx permission set <total|automatico|permiso>"],
|
|
369
356
|
commands: [
|
|
370
357
|
["show", "Print current permission mode."],
|
|
@@ -374,13 +361,13 @@ const HELP_TOPICS = new Map(Object.entries({
|
|
|
374
361
|
}),
|
|
375
362
|
"permission show": topic({
|
|
376
363
|
title: "apx permission show",
|
|
377
|
-
summary: "Print current
|
|
364
|
+
summary: "Print current APX tool permission mode.",
|
|
378
365
|
usage: ["apx permission show"],
|
|
379
366
|
examples: ["apx permission show"],
|
|
380
367
|
}),
|
|
381
368
|
"permission set": topic({
|
|
382
369
|
title: "apx permission set",
|
|
383
|
-
summary: "Set
|
|
370
|
+
summary: "Set APX tool permission mode.",
|
|
384
371
|
usage: ["apx permission set <total|automatico|permiso>"],
|
|
385
372
|
examples: ["apx permission set permiso"],
|
|
386
373
|
}),
|
|
@@ -702,14 +689,14 @@ const HELP_TOPICS = new Map(Object.entries({
|
|
|
702
689
|
],
|
|
703
690
|
examples: ["apx chat reviewer", "apx chat reviewer --conversation abc123"],
|
|
704
691
|
}),
|
|
705
|
-
|
|
706
|
-
title: "apx
|
|
707
|
-
summary: "Start
|
|
708
|
-
usage: ["apx
|
|
692
|
+
code: topic({
|
|
693
|
+
title: "apx code",
|
|
694
|
+
summary: "Start the APX terminal coding assistant with system and workspace context.",
|
|
695
|
+
usage: ["apx code [--project <name|id|path>]"],
|
|
709
696
|
options: [
|
|
710
697
|
["--project <name|id|path>", "Pin command to a specific project."],
|
|
711
698
|
],
|
|
712
|
-
examples: ["apx
|
|
699
|
+
examples: ["apx code", "apx code --project default"],
|
|
713
700
|
}),
|
|
714
701
|
conversations: topic({
|
|
715
702
|
title: "apx conversations",
|
|
@@ -779,17 +766,10 @@ const HELP_TOPICS = new Map(Object.entries({
|
|
|
779
766
|
connections: topic({
|
|
780
767
|
title: "apx connections",
|
|
781
768
|
summary: "Show an agent communication map.",
|
|
782
|
-
usage: ["apx connections <agent> [--project <name|id|path>]"
|
|
769
|
+
usage: ["apx connections <agent> [--project <name|id|path>]"],
|
|
783
770
|
options: [["--project <name|id|path>", "Pin command to a specific project."]],
|
|
784
771
|
examples: ["apx connections reviewer"],
|
|
785
772
|
}),
|
|
786
|
-
graph: topic({
|
|
787
|
-
title: "apx graph",
|
|
788
|
-
summary: "Alias for apx connections.",
|
|
789
|
-
usage: ["apx graph <agent> [--project <name|id|path>]"],
|
|
790
|
-
options: [["--project <name|id|path>", "Pin command to a specific project."]],
|
|
791
|
-
examples: ["apx graph reviewer"],
|
|
792
|
-
}),
|
|
793
773
|
routine: topic({
|
|
794
774
|
title: "apx routine",
|
|
795
775
|
summary: "Manage scheduled APX routines.",
|
|
@@ -931,11 +911,12 @@ const HELP_TOPICS = new Map(Object.entries({
|
|
|
931
911
|
command: topic({
|
|
932
912
|
title: "apx command",
|
|
933
913
|
summary: "List or show workflow commands from .apc/commands/.",
|
|
934
|
-
usage: ["apx command [list]", "apx command show <name>"],
|
|
914
|
+
usage: ["apx command [list] [--project <name|id|path>]", "apx command show <name> [--project <name|id|path>]"],
|
|
935
915
|
commands: [
|
|
936
916
|
["list | ls", "List workflow commands."],
|
|
937
917
|
["show | get <name>", "Print one command file."],
|
|
938
918
|
],
|
|
919
|
+
options: [["--project <name|id|path>", "Pin command to a specific project; defaults to current APC project or default."]],
|
|
939
920
|
examples: ["apx command list", "apx command show release"],
|
|
940
921
|
}),
|
|
941
922
|
commands: topic({
|
|
@@ -947,13 +928,15 @@ const HELP_TOPICS = new Map(Object.entries({
|
|
|
947
928
|
"command list": topic({
|
|
948
929
|
title: "apx command list",
|
|
949
930
|
summary: "List workflow commands in .apc/commands/.",
|
|
950
|
-
usage: ["apx command list", "apx command ls"],
|
|
931
|
+
usage: ["apx command list [--project <name|id|path>]", "apx command ls [--project <name|id|path>]"],
|
|
932
|
+
options: [["--project <name|id|path>", "Pin command to a specific project; defaults to current APC project or default."]],
|
|
951
933
|
examples: ["apx command list"],
|
|
952
934
|
}),
|
|
953
935
|
"command show": topic({
|
|
954
936
|
title: "apx command show",
|
|
955
937
|
summary: "Print one workflow command file.",
|
|
956
|
-
usage: ["apx command show <name>", "apx command get <name>"],
|
|
938
|
+
usage: ["apx command show <name> [--project <name|id|path>]", "apx command get <name> [--project <name|id|path>]"],
|
|
939
|
+
options: [["--project <name|id|path>", "Pin command to a specific project; defaults to current APC project or default."]],
|
|
957
940
|
examples: ["apx command show release"],
|
|
958
941
|
}),
|
|
959
942
|
skills: topic({
|
|
@@ -1019,6 +1002,7 @@ const HELP_TOPICS = new Map(Object.entries({
|
|
|
1019
1002
|
const HELP_ALIASES = new Map(Object.entries({
|
|
1020
1003
|
"project ls": "project list",
|
|
1021
1004
|
"project rm": "project remove",
|
|
1005
|
+
"sys": "code",
|
|
1022
1006
|
"agent ls": "agent list",
|
|
1023
1007
|
"agent show": "agent get",
|
|
1024
1008
|
"agent vault ls": "agent vault list",
|
|
@@ -1074,11 +1058,12 @@ function buildHelp(version) {
|
|
|
1074
1058
|
hCmd("apx setup", 36, "interactive wizard: provider → model → channels → daemon (alias: install)"),
|
|
1075
1059
|
hCmd("apx status", 36, "full system status: daemon, super-agent, engines, telegram, projects"),
|
|
1076
1060
|
hCmd("apx update", 36, "check for updates and upgrade (alias: upgrade)"),
|
|
1061
|
+
|
|
1062
|
+
hSec("Projects"),
|
|
1077
1063
|
hCmd("apx project add [path]", 36, "register a project with the daemon"),
|
|
1078
1064
|
hCmd("apx project list", 36, ""),
|
|
1079
1065
|
hCmd("apx project remove <id>", 36, ""),
|
|
1080
1066
|
hCmd("apx project rebuild [id]", 36, "rebuild project index from filesystem"),
|
|
1081
|
-
hCmd("apx add project [path]", 36, "alias for: apx project add"),
|
|
1082
1067
|
|
|
1083
1068
|
hSec("Agents"),
|
|
1084
1069
|
hCmd("apx agent add <slug>", 36, "--role R --model M --skills a,b --language es-AR --description D"),
|
|
@@ -1088,15 +1073,16 @@ function buildHelp(version) {
|
|
|
1088
1073
|
hCmd("apx agent vault list", 36, ""),
|
|
1089
1074
|
hCmd("apx agent vault add", 36, ""),
|
|
1090
1075
|
|
|
1091
|
-
hSec("
|
|
1092
|
-
hCmd("apx identity show", 36, "print current
|
|
1076
|
+
hSec("APX Profile"),
|
|
1077
|
+
hCmd("apx identity show", 36, "print current APX identity (name, owner, personality)"),
|
|
1093
1078
|
hCmd("apx identity set <k> <v>", 36, "set identity field"),
|
|
1094
1079
|
hCmd("apx identity wizard", 36, "interactive identity setup"),
|
|
1080
|
+
|
|
1081
|
+
hSec("Config"),
|
|
1095
1082
|
hCmd("apx config show", 36, "--effective --only-overrides"),
|
|
1096
|
-
hCmd("apx config set
|
|
1097
|
-
hCmd("apx permission show", 36, "show
|
|
1083
|
+
hCmd("apx config set|unset", 36, "<key> [value] edit .apc/config.json (JSON-aware)"),
|
|
1084
|
+
hCmd("apx permission show", 36, "show APX tool permission mode"),
|
|
1098
1085
|
hCmd("apx permission set <mode>", 36, "mode: total | automatico | permiso"),
|
|
1099
|
-
hCmd("apx config unset <key>", 36, "remove key from .apc/config.json"),
|
|
1100
1086
|
|
|
1101
1087
|
hSec("Memory"),
|
|
1102
1088
|
hCmd("apx memory <slug>", 36, "print memory.md"),
|
|
@@ -1123,7 +1109,7 @@ function buildHelp(version) {
|
|
|
1123
1109
|
hCmd("apx mcp tools <name>", 36, "list available tools"),
|
|
1124
1110
|
hCmd("apx mcp check", 36, "audit multi-source merge"),
|
|
1125
1111
|
|
|
1126
|
-
hSec("Daemon"),
|
|
1112
|
+
hSec("Daemon Service"),
|
|
1127
1113
|
hCmd("apx daemon start", 36, ""),
|
|
1128
1114
|
hCmd("apx daemon stop", 36, ""),
|
|
1129
1115
|
hCmd("apx daemon status", 36, ""),
|
|
@@ -1139,11 +1125,11 @@ function buildHelp(version) {
|
|
|
1139
1125
|
hCmd("apx messages chat", 36, "--channel telegram -n 50"),
|
|
1140
1126
|
hCmd("apx messages search \"q\"", 36, ""),
|
|
1141
1127
|
|
|
1142
|
-
hSec("LLM /
|
|
1143
|
-
hCmd("apx
|
|
1144
|
-
hCmd("apx
|
|
1145
|
-
hCmd("apx
|
|
1146
|
-
hCmd("apx conversations list", 36, "<agent>"),
|
|
1128
|
+
hSec("LLM / Code"),
|
|
1129
|
+
hCmd("apx code", 36, "APX terminal coding assistant"),
|
|
1130
|
+
hCmd("apx exec <agent> \"prompt\"",36, "one-shot agent call --model <id> --max-tokens N"),
|
|
1131
|
+
hCmd("apx chat <agent>", 36, "interactive agent REPL --conversation <id>"),
|
|
1132
|
+
hCmd("apx conversations list", 36, "stored exec/chat conversations for <agent>"),
|
|
1147
1133
|
hCmd("apx conversations get", 36, "<agent> <id>"),
|
|
1148
1134
|
|
|
1149
1135
|
hSec("Runtimes"),
|
|
@@ -1154,7 +1140,6 @@ function buildHelp(version) {
|
|
|
1154
1140
|
hSec("Agent-to-Agent"),
|
|
1155
1141
|
hCmd("apx send <from> <to> \"msg\"",36, "--deliver log A2A message; --deliver runs target engine"),
|
|
1156
1142
|
hCmd("apx connections <agent>", 36, "mental map: who/how/when this agent talked"),
|
|
1157
|
-
hCmd("apx graph <agent>", 36, "alias for connections"),
|
|
1158
1143
|
|
|
1159
1144
|
hSec("Routines & Pipeline"),
|
|
1160
1145
|
hCmd("apx routine list", 36, "list routines + next/last run"),
|
|
@@ -1452,8 +1437,9 @@ async function dispatch(cmd, rest) {
|
|
|
1452
1437
|
await cmdChat(parseArgs(rest));
|
|
1453
1438
|
break;
|
|
1454
1439
|
|
|
1440
|
+
case "code":
|
|
1455
1441
|
case "sys":
|
|
1456
|
-
await
|
|
1442
|
+
await cmdCode(parseArgs(rest));
|
|
1457
1443
|
break;
|
|
1458
1444
|
|
|
1459
1445
|
case "conversations":
|
|
@@ -1482,7 +1468,6 @@ async function dispatch(cmd, rest) {
|
|
|
1482
1468
|
break;
|
|
1483
1469
|
|
|
1484
1470
|
case "connections":
|
|
1485
|
-
case "graph":
|
|
1486
1471
|
await cmdConnections(parseArgs(rest));
|
|
1487
1472
|
break;
|
|
1488
1473
|
|
|
@@ -1543,8 +1528,8 @@ async function dispatch(cmd, rest) {
|
|
|
1543
1528
|
case "commands": {
|
|
1544
1529
|
const sub = rest[0];
|
|
1545
1530
|
const a = parseArgs(rest.slice(1));
|
|
1546
|
-
if (!sub || sub === "list" || sub === "ls") cmdCommandList();
|
|
1547
|
-
else if (sub === "show" || sub === "get") cmdCommandShow(a);
|
|
1531
|
+
if (!sub || sub === "list" || sub === "ls") await cmdCommandList(a);
|
|
1532
|
+
else if (sub === "show" || sub === "get") await cmdCommandShow(a);
|
|
1548
1533
|
else die(`unknown command subcommand: ${sub}`);
|
|
1549
1534
|
break;
|
|
1550
1535
|
}
|
|
@@ -1563,14 +1548,6 @@ async function dispatch(cmd, rest) {
|
|
|
1563
1548
|
await cmdIdentity(parseArgs(rest));
|
|
1564
1549
|
break;
|
|
1565
1550
|
|
|
1566
|
-
case "add": {
|
|
1567
|
-
// apx add <domain> [...args] — consistent alternative to apx <domain> add
|
|
1568
|
-
const sub = rest[0];
|
|
1569
|
-
if (sub === "project") await cmdProjectAdd(parseArgs(rest.slice(1)));
|
|
1570
|
-
else die(`unknown 'add' subcommand: ${sub || "(none)"} — try: project`);
|
|
1571
|
-
break;
|
|
1572
|
-
}
|
|
1573
|
-
|
|
1574
1551
|
case "status":
|
|
1575
1552
|
await cmdStatus();
|
|
1576
1553
|
return; // skip checkForUpdate after status (avoid noise)
|
|
@@ -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/apx-skill.md
CHANGED
|
@@ -88,7 +88,7 @@ apx messages tail --agent <slug> -n 20
|
|
|
88
88
|
Message rows expose `type` (`user`, `agent`, `tool`, `system`) and `actor_id`; use `messages chat`
|
|
89
89
|
when you need a readable transcript.
|
|
90
90
|
|
|
91
|
-
##
|
|
91
|
+
## APX tool permissions
|
|
92
92
|
|
|
93
93
|
```bash
|
|
94
94
|
apx permission show
|
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;
|
|
@@ -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: "Optional APC agent slug from AGENTS.md, not runtime name.
|
|
102
|
+
agent: { type: "string", description: "Optional APC agent slug from AGENTS.md, not runtime name. Use only when the user explicitly named that agent. Omit for 'vos mismo', 'default', 'base', or no agent." },
|
|
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,
|
|
@@ -7,7 +7,7 @@ export default {
|
|
|
7
7
|
type: "function",
|
|
8
8
|
function: {
|
|
9
9
|
name: "set_identity",
|
|
10
|
-
description: "Update
|
|
10
|
+
description: "Update APX profile identity fields. Persists to ~/.apx/identity.json.",
|
|
11
11
|
parameters: {
|
|
12
12
|
type: "object",
|
|
13
13
|
properties: {
|
|
@@ -9,7 +9,7 @@ export default {
|
|
|
9
9
|
type: "function",
|
|
10
10
|
function: {
|
|
11
11
|
name: "set_permission_mode",
|
|
12
|
-
description: "Set
|
|
12
|
+
description: "Set APX tool permission mode in ~/.apx/config.json. Modes: total, automatico, permiso.",
|
|
13
13
|
parameters: {
|
|
14
14
|
type: "object",
|
|
15
15
|
properties: {
|
|
@@ -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 itself. Do NOT call list_agents to choose a candidate. Do NOT pass agent to call_runtime/call_agent.
|
|
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: when the user asks to work inside Claude, Codex, OpenCode, Aider, Cursor, Gemini CLI, or Qwen Code, use call_runtime({runtime: 'claude-code'|'codex'|'opencode'|'aider'|'cursor-agent'|'gemini-cli'|'qwen-code', prompt: <user's request>}). First prefer runtimes the tool reports as installed/runnable; if a runtime is missing or fails, report that fact and its stderr/error instead of pretending success. If the user explicitly named an agent slug, pass agent: <slug>. If they didn't, DO NOT pass agent. When an agent is passed, its memory + skills become the system prompt of the runtime.
|
|
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);
|