@agentprojectcontext/apx 1.10.2 → 1.10.4

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/README.md CHANGED
@@ -113,10 +113,14 @@ can distinguish Telegram users from APX agents and future subagents.
113
113
  | Runtime | Description |
114
114
  |---------|-------------|
115
115
  | `claude-code` | Spawns Claude Code CLI with the agent's system prompt injected |
116
- | `codex` | OpenAI Codex CLI |
116
+ | `codex` | OpenAI Codex CLI via non-interactive `codex exec --sandbox workspace-write --skip-git-repo-check` |
117
117
  | `opencode` | OpenCode CLI |
118
118
  | `aider` | Aider CLI |
119
119
 
120
+ Global APX skill installation also writes named helper skills for `codex-cli`, `claude-code`,
121
+ `opencode-cli`, and `openrouter`. They are intentionally narrow and should activate only when those
122
+ tools/providers are explicitly mentioned.
123
+
120
124
  ## Engines (for `apx exec`)
121
125
 
122
126
  Configured in `~/.apx/config.json`:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agentprojectcontext/apx",
3
- "version": "1.10.2",
3
+ "version": "1.10.4",
4
4
  "description": "APX — unified CLI + daemon for the Agent Project Context (APC) standard.",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -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 ensureProjectStorage(apxId);
21
+ return projectStorageRoot(apxId);
22
22
  }
23
23
 
24
24
  const nowIso = () => new Date().toISOString().replace(/\.\d{3}Z$/, "Z");
@@ -32,10 +32,12 @@ export async function cmdSys(args) {
32
32
  hasStarted: false,
33
33
  sessionTitle: "",
34
34
  usage: { input: 0, output: 0, percent: 0 },
35
+ chatScrollOffset: 0,
35
36
  transcript: [],
36
37
  };
37
38
 
38
39
  const previousMessages = [];
40
+ const pendingPrompts = [];
39
41
  let restored = false;
40
42
  let isRequesting = false;
41
43
 
@@ -74,8 +76,6 @@ export async function cmdSys(args) {
74
76
  renderScreen();
75
77
 
76
78
  process.stdin.on("keypress", async (str, key) => {
77
- if (isRequesting) return;
78
-
79
79
  if (key.ctrl && key.name === "c") {
80
80
  close();
81
81
  }
@@ -102,9 +102,16 @@ export async function cmdSys(args) {
102
102
  return;
103
103
  }
104
104
 
105
+ if (handleScrollKey(key, state, renderScreen)) return;
106
+
105
107
  if (isReturnKey(key)) {
108
+ if (isRequesting) {
109
+ queuePrompt(state, pendingPrompts, renderScreen);
110
+ return;
111
+ }
112
+
106
113
  isRequesting = true;
107
- await submitPrompt(pid, state, previousMessages, renderScreen, close);
114
+ await submitPromptQueue(pid, state, previousMessages, pendingPrompts, renderScreen, close);
108
115
  isRequesting = false;
109
116
  return;
110
117
  }
@@ -117,6 +124,37 @@ export function isReturnKey(key) {
117
124
  return key?.name === "return" || key?.name === "enter";
118
125
  }
119
126
 
127
+ export function handleScrollKey(key, state, renderScreen) {
128
+ if (!state.hasStarted || !key) return false;
129
+ const pageSize = key.name === "pageup" || key.name === "pagedown" ? 8 : 3;
130
+
131
+ if (key.name === "pageup" || key.name === "up" || (key.ctrl && key.name === "up")) {
132
+ state.chatScrollOffset = Math.min(100000, (state.chatScrollOffset || 0) + pageSize);
133
+ renderScreen();
134
+ return true;
135
+ }
136
+
137
+ if (key.name === "pagedown" || key.name === "down" || (key.ctrl && key.name === "down")) {
138
+ state.chatScrollOffset = Math.max(0, (state.chatScrollOffset || 0) - pageSize);
139
+ renderScreen();
140
+ return true;
141
+ }
142
+
143
+ if (key.meta && key.name === "up") {
144
+ state.chatScrollOffset = Math.min(100000, (state.chatScrollOffset || 0) + 20);
145
+ renderScreen();
146
+ return true;
147
+ }
148
+
149
+ if (key.meta && key.name === "down") {
150
+ state.chatScrollOffset = 0;
151
+ renderScreen();
152
+ return true;
153
+ }
154
+
155
+ return false;
156
+ }
157
+
120
158
  async function handlePaletteKey(key, cfg, state, renderScreen, close) {
121
159
  if (key.name === "up") {
122
160
  state.paletteSelection = Math.max(0, state.paletteSelection - 1);
@@ -276,18 +314,46 @@ export function handleEditingKey(str, key, state, renderScreen) {
276
314
  return false;
277
315
  }
278
316
 
279
- async function submitPrompt(pid, state, previousMessages, renderScreen, close) {
317
+ function queuePrompt(state, pendingPrompts, renderScreen) {
280
318
  const text = state.inputText.trim();
281
319
  if (!text) return;
282
- if (text.toLowerCase() === "exit" || text.toLowerCase() === "quit") {
320
+
321
+ state.hasStarted = true;
322
+ state.inputText = "";
323
+ state.cursorIndex = 0;
324
+ state.chatScrollOffset = 0;
325
+
326
+ const item = { type: "user", text, meta: "queued" };
327
+ pendingPrompts.push({ text, item });
328
+ state.transcript.push(item);
329
+ renderScreen();
330
+ }
331
+
332
+ async function submitPromptQueue(pid, state, previousMessages, pendingPrompts, renderScreen, close) {
333
+ const firstText = state.inputText.trim();
334
+ if (!firstText) return;
335
+ if (firstText.toLowerCase() === "exit" || firstText.toLowerCase() === "quit") {
283
336
  close();
284
337
  }
285
338
 
286
339
  state.hasStarted = true;
287
340
  state.inputText = "";
288
341
  state.cursorIndex = 0;
289
- state.transcript.push({ type: "user", text });
290
- state.transcript.push({ type: "status", text: "Thinking..." });
342
+ state.chatScrollOffset = 0;
343
+
344
+ const firstItem = { type: "user", text: firstText };
345
+ state.transcript.push(firstItem);
346
+ await runPrompt(pid, state, previousMessages, renderScreen, firstText, firstItem);
347
+
348
+ while (pendingPrompts.length > 0) {
349
+ const queued = pendingPrompts.shift();
350
+ delete queued.item.meta;
351
+ await runPrompt(pid, state, previousMessages, renderScreen, queued.text, queued.item);
352
+ }
353
+ }
354
+
355
+ async function runPrompt(pid, state, previousMessages, renderScreen, text, userItem) {
356
+ appendLiveItem(state, { type: "status", text: "Thinking...", active: true });
291
357
  renderScreen();
292
358
 
293
359
  const startTime = Date.now();
@@ -312,29 +378,42 @@ async function submitPrompt(pid, state, previousMessages, renderScreen, close) {
312
378
  result = await http.post(`/projects/${pid}/super-agent/chat`, body);
313
379
  removeStatus(state);
314
380
  for (const trace of result.trace || []) {
315
- state.transcript.push({ type: "tool", trace });
381
+ appendLiveItem(state, { type: "tool", trace });
316
382
  }
317
383
  }
318
384
 
319
385
  completeSuperAgentResult(result, text, startTime, state, previousMessages);
320
386
  } catch (e) {
321
387
  removeStatus(state);
322
- state.transcript.push({ type: "error", text: e.message });
388
+ appendLiveItem(state, { type: "error", text: e.message });
323
389
  }
324
390
 
391
+ if (userItem) delete userItem.meta;
325
392
  renderScreen();
326
393
  }
327
394
 
328
395
  function removeStatus(state) {
329
- const last = state.transcript[state.transcript.length - 1];
330
- if (last?.type === "status") state.transcript.pop();
396
+ for (let i = state.transcript.length - 1; i >= 0; i--) {
397
+ if (state.transcript[i]?.type === "status" && state.transcript[i]?.active) {
398
+ state.transcript.splice(i, 1);
399
+ return;
400
+ }
401
+ }
402
+ }
403
+
404
+ function appendLiveItem(state, item) {
405
+ const queuedIndex = state.transcript.findIndex(
406
+ (entry) => entry?.type === "user" && entry?.meta === "queued"
407
+ );
408
+ if (queuedIndex >= 0) state.transcript.splice(queuedIndex, 0, item);
409
+ else state.transcript.push(item);
331
410
  }
332
411
 
333
412
  function handleProgressEvent(event, state, renderScreen) {
334
413
  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...";
414
+ const status = [...state.transcript].reverse().find((item) => item?.type === "status" && item?.active);
415
+ if (status) {
416
+ status.text = event.iteration > 1 ? `Thinking... step ${event.iteration}` : "Thinking...";
338
417
  renderScreen();
339
418
  }
340
419
  return;
@@ -342,7 +421,7 @@ function handleProgressEvent(event, state, renderScreen) {
342
421
 
343
422
  if (event.type === "assistant_text" && event.text) {
344
423
  removeStatus(state);
345
- state.transcript.push({
424
+ appendLiveItem(state, {
346
425
  type: "assistant",
347
426
  name: state.activeAgent,
348
427
  text: event.text,
@@ -354,7 +433,7 @@ function handleProgressEvent(event, state, renderScreen) {
354
433
 
355
434
  if (event.type === "tool_start" && event.trace) {
356
435
  removeStatus(state);
357
- state.transcript.push({ type: "tool", trace: event.trace });
436
+ appendLiveItem(state, { type: "tool", trace: event.trace });
358
437
  renderScreen();
359
438
  return;
360
439
  }
@@ -365,7 +444,7 @@ function handleProgressEvent(event, state, renderScreen) {
365
444
  (item) => item.type === "tool" && item.trace?.id && item.trace.id === event.trace.id
366
445
  );
367
446
  if (idx >= 0) state.transcript[idx] = { type: "tool", trace: event.trace };
368
- else state.transcript.push({ type: "tool", trace: event.trace });
447
+ else appendLiveItem(state, { type: "tool", trace: event.trace });
369
448
  renderScreen();
370
449
  }
371
450
  }
@@ -383,7 +462,7 @@ function completeSuperAgentResult(result, userText, startTime, state, previousMe
383
462
  state.usage.output += result.usage?.output_tokens || 0;
384
463
  state.usage.percent = Math.min(99, Math.round((state.usage.input / 200000) * 100));
385
464
 
386
- state.transcript.push({
465
+ appendLiveItem(state, {
387
466
  type: "assistant",
388
467
  name: state.activeAgent,
389
468
  text: result.text,
@@ -275,6 +275,9 @@ function transcriptLines(transcript, width) {
275
275
  for (const chunk of chunks) {
276
276
  addLine(lines, margin + C.primary + "┃" + C.panel + " " + C.text + padAnsi(chunk, inner), C.bg);
277
277
  }
278
+ if (item.meta) {
279
+ addLine(lines, margin + C.primary + "┃" + C.panel + " " + C.muted + padAnsi(item.meta, inner), C.bg);
280
+ }
278
281
  addLine(lines, margin + C.primary + "┃" + C.panel + " " + " ".repeat(inner), C.bg);
279
282
  continue;
280
283
  }
@@ -315,14 +318,22 @@ function transcriptLines(transcript, width) {
315
318
  return lines;
316
319
  }
317
320
 
318
- function renderChat(transcript, chatWidth, height, promptTop) {
321
+ function renderChat(state, chatWidth, height, promptTop) {
319
322
  const maxRows = Math.max(1, promptTop - 1);
320
- const lines = transcriptLines(transcript, chatWidth - 2);
321
- const slice = lines.slice(Math.max(0, lines.length - maxRows));
323
+ const lines = transcriptLines(state.transcript, chatWidth - 2);
324
+ const maxOffset = Math.max(0, lines.length - maxRows);
325
+ const offset = Math.min(Math.max(0, state.chatScrollOffset || 0), maxOffset);
326
+ state.chatScrollOffset = offset;
327
+ const start = Math.max(0, lines.length - maxRows - offset);
328
+ const slice = lines.slice(start, start + maxRows);
322
329
 
323
330
  for (let i = 0; i < slice.length && i < maxRows; i++) {
324
331
  writeAt(i, 0, slice[i].text, chatWidth - 1, slice[i].bg);
325
332
  }
333
+
334
+ if (offset > 0 && maxRows > 1) {
335
+ writeAt(0, 0, C.muted + `↑ ${offset} lines above bottom`, chatWidth - 1, C.bg);
336
+ }
326
337
  }
327
338
 
328
339
  function renderSidebar(state) {
@@ -407,7 +418,7 @@ export function renderTerminalChat(state) {
407
418
  renderLogo(chatWidth, Math.max(1, Math.floor(height / 2) - 8));
408
419
  } else {
409
420
  const prompt = promptGeometry(false, true, chatWidth);
410
- renderChat(state.transcript, chatWidth, height, prompt.top);
421
+ renderChat(state, chatWidth, height, prompt.top);
411
422
  }
412
423
 
413
424
  const cursor = renderPromptBlock(state, chatWidth);
@@ -42,6 +42,22 @@ The output is the agent's full stdout. If it printed `APC_RESULT: <value>`, that
42
42
  apx exec <slug> "<prompt>"
43
43
  ```
44
44
 
45
+ ## Command accuracy
46
+
47
+ Do not invent APX subcommands. Before telling another runtime to call APX, verify the exact CLI
48
+ form with `apx --help` or `apx <command> --help`.
49
+
50
+ Known Telegram form:
51
+
52
+ ```bash
53
+ apx telegram status
54
+ apx telegram send "message"
55
+ apx telegram send "message" --chat 123456
56
+ ```
57
+
58
+ Do not use guessed aliases such as `apx send-telegram` or `apx telegram "message"` unless current
59
+ `apx --help` shows that exact form.
60
+
45
61
  ## MCP tools
46
62
 
47
63
  MCPs declared in `.apc/mcps.json` are proxied through the APX daemon. Use `apx mcp` only for MCPs registered there — not for MCPs that are already running locally in your IDE session.
@@ -0,0 +1,68 @@
1
+ ---
2
+ name: claude-code
3
+ description: "Activate ONLY when the user explicitly mentions Claude Code, Claude CLI, claude command, Anthropic Claude Code, installing Claude Code, using Claude Code, or APX runtime claude-code. Do not activate for generic Claude model discussion."
4
+ homepage: https://docs.anthropic.com/en/docs/claude-code
5
+ ---
6
+ # Claude Code CLI
7
+
8
+ Use this skill only for Claude Code CLI install, auth, usage, or APX runtime dispatch.
9
+
10
+ ## Verify before acting
11
+
12
+ Check the local CLI first:
13
+
14
+ ```bash
15
+ claude --version
16
+ claude --help
17
+ ```
18
+
19
+ Do not invent flags. If a command is uncertain, inspect help for the exact subcommand before
20
+ running it.
21
+
22
+ ## Install
23
+
24
+ Common install/update path:
25
+
26
+ ```bash
27
+ npm install -g @anthropic-ai/claude-code
28
+ claude --version
29
+ ```
30
+
31
+ Claude Code also exposes:
32
+
33
+ ```bash
34
+ claude install
35
+ claude update
36
+ claude auth
37
+ ```
38
+
39
+ Use `claude --help` to confirm current syntax.
40
+
41
+ ## Non-interactive use
42
+
43
+ Prefer headless print mode:
44
+
45
+ ```bash
46
+ claude -p "task" --output-format json
47
+ claude -p "task" --append-system-prompt "system instructions" --output-format json
48
+ ```
49
+
50
+ For high-trust automation in an already sandboxed environment:
51
+
52
+ ```bash
53
+ claude -p "task" --permission-mode bypassPermissions --output-format json
54
+ ```
55
+
56
+ ## APX runtime
57
+
58
+ Run a project agent through Claude Code:
59
+
60
+ ```bash
61
+ apx run <agent> --runtime claude-code "task"
62
+ ```
63
+
64
+ If the task needs Telegram, tell Claude Code the exact APX command:
65
+
66
+ ```bash
67
+ apx telegram send "message"
68
+ ```
@@ -0,0 +1,75 @@
1
+ ---
2
+ name: codex-cli
3
+ description: "Activate ONLY when the user explicitly mentions Codex CLI, OpenAI Codex, @openai/codex, codex command, codex exec, installing Codex, using Codex, ~/.codex, or APX runtime codex."
4
+ homepage: https://developers.openai.com/codex
5
+ ---
6
+ # Codex CLI
7
+
8
+ Use this skill only for Codex CLI install, auth, usage, or APX runtime dispatch.
9
+
10
+ ## Verify before acting
11
+
12
+ Check local CLI and exact flags first:
13
+
14
+ ```bash
15
+ codex --version
16
+ codex --help
17
+ codex exec --help
18
+ ```
19
+
20
+ Do not invent flags. Current Codex CLI may reject older flags such as `--approval-mode` or
21
+ `--full-auto`.
22
+
23
+ ## Install
24
+
25
+ Common install/update path:
26
+
27
+ ```bash
28
+ npm install -g @openai/codex
29
+ codex --version
30
+ ```
31
+
32
+ Codex also exposes:
33
+
34
+ ```bash
35
+ codex login
36
+ codex update
37
+ ```
38
+
39
+ Auth check:
40
+
41
+ ```bash
42
+ test -f ~/.codex/auth.json && echo "codex auth present"
43
+ ```
44
+
45
+ ## Non-interactive use
46
+
47
+ Use `exec`, not interactive TUI, for automation:
48
+
49
+ ```bash
50
+ codex exec --sandbox workspace-write --skip-git-repo-check "task"
51
+ ```
52
+
53
+ Useful options:
54
+
55
+ ```bash
56
+ codex exec --sandbox workspace-write --skip-git-repo-check --output-last-message /tmp/codex-last.txt "task"
57
+ codex exec --json --sandbox workspace-write --skip-git-repo-check "task"
58
+ ```
59
+
60
+ `--skip-git-repo-check` matters for APX default runtime dirs such as `~/.apx/projects/default`,
61
+ which may not be Git repositories.
62
+
63
+ ## APX runtime
64
+
65
+ Run a project agent through Codex:
66
+
67
+ ```bash
68
+ apx run <agent> --runtime codex "task"
69
+ ```
70
+
71
+ If the task needs Telegram, tell Codex the exact APX command:
72
+
73
+ ```bash
74
+ apx telegram send "message"
75
+ ```
@@ -0,0 +1,59 @@
1
+ ---
2
+ name: opencode-cli
3
+ description: "Activate ONLY when the user explicitly mentions OpenCode, opencode command, installing OpenCode, using OpenCode, OpenCode provider setup, or APX runtime opencode."
4
+ homepage: https://opencode.ai/docs
5
+ ---
6
+ # OpenCode CLI
7
+
8
+ Use this skill only for OpenCode CLI install, auth/provider setup, usage, or APX runtime dispatch.
9
+
10
+ ## Verify before acting
11
+
12
+ Check local CLI and exact flags first:
13
+
14
+ ```bash
15
+ opencode --version
16
+ opencode --help
17
+ opencode run --help
18
+ ```
19
+
20
+ Do not invent flags. Inspect help for the exact subcommand before running uncertain commands.
21
+
22
+ ## Install
23
+
24
+ Use the official install method for the target machine, then verify:
25
+
26
+ ```bash
27
+ opencode --version
28
+ ```
29
+
30
+ Provider/auth management:
31
+
32
+ ```bash
33
+ opencode providers
34
+ opencode models
35
+ ```
36
+
37
+ ## Non-interactive use
38
+
39
+ Use headless run:
40
+
41
+ ```bash
42
+ opencode run "task"
43
+ opencode run --model provider/model "task"
44
+ opencode run --dangerously-skip-permissions "task"
45
+ ```
46
+
47
+ ## APX runtime
48
+
49
+ Run a project agent through OpenCode:
50
+
51
+ ```bash
52
+ apx run <agent> --runtime opencode "task"
53
+ ```
54
+
55
+ If the task needs Telegram, tell OpenCode the exact APX command:
56
+
57
+ ```bash
58
+ apx telegram send "message"
59
+ ```
@@ -0,0 +1,54 @@
1
+ ---
2
+ name: openrouter
3
+ description: "Activate ONLY when the user explicitly mentions OpenRouter, OPENROUTER_API_KEY, OpenRouter models, installing OpenRouter provider config, or using OpenRouter with APX, OpenCode, LiteLLM, or an OpenAI-compatible client."
4
+ homepage: https://openrouter.ai/docs
5
+ ---
6
+ # OpenRouter
7
+
8
+ Use this skill only for OpenRouter install/config, model listing, or usage through APX/OpenCode or
9
+ OpenAI-compatible clients.
10
+
11
+ ## Verify before acting
12
+
13
+ OpenRouter is an API/provider, not a local coding runtime by itself. First identify which client
14
+ will use it: APX engine, OpenCode provider, LiteLLM, OpenAI SDK, or another OpenAI-compatible tool.
15
+
16
+ Do not expose keys. Check only presence:
17
+
18
+ ```bash
19
+ test -n "$OPENROUTER_API_KEY" && echo "OPENROUTER_API_KEY present"
20
+ ```
21
+
22
+ ## OpenAI-compatible base URL
23
+
24
+ Typical API settings:
25
+
26
+ ```bash
27
+ OPENAI_BASE_URL=https://openrouter.ai/api/v1
28
+ OPENROUTER_API_KEY=...
29
+ ```
30
+
31
+ Use the selected client's current docs/help before writing config.
32
+
33
+ ## APX guidance
34
+
35
+ If configuring APX engine/provider, inspect current config schema first:
36
+
37
+ ```bash
38
+ apx config --help
39
+ apx status
40
+ ```
41
+
42
+ Then update only non-secret project-safe settings. Keep API keys in user config or environment, not
43
+ in `.apc/` or git.
44
+
45
+ ## OpenCode guidance
46
+
47
+ If using OpenRouter via OpenCode, inspect provider commands first:
48
+
49
+ ```bash
50
+ opencode providers
51
+ opencode models
52
+ ```
53
+
54
+ Then configure OpenRouter through OpenCode's current provider flow.
@@ -87,7 +87,7 @@ function buildSkillMd(content) {
87
87
  const frontmatter = [
88
88
  "---",
89
89
  "name: apx",
90
- "description: APX CLI skill. Activate when: user asks to run or coordinate agents, use MCP tools from .apc/mcps.json, install agents from a team workspace, or explicitly mentions apx commands. Do NOT activate just because .apc/ exists — that is handled by the apc-context skill. Activate on: 'apx run', 'apx exec', 'run an agent', 'coordinate agents', 'MCP not working', 'install agent', 'team agents', 'apx memory', 'daemon'.",
90
+ "description: \"APX CLI skill. Activate when: user asks to run or coordinate agents, use MCP tools from .apc/mcps.json, install agents from a team workspace, or explicitly mentions apx commands. Do NOT activate just because .apc/ exists — that is handled by the apc-context skill. Activate on: 'apx run', 'apx exec', 'run an agent', 'coordinate agents', 'MCP not working', 'install agent', 'team agents', 'apx memory', 'daemon'.\"",
91
91
  "homepage: https://github.com/agentprojectcontext/apx",
92
92
  "---",
93
93
  "",
@@ -95,6 +95,19 @@ function buildSkillMd(content) {
95
95
  return frontmatter + content;
96
96
  }
97
97
 
98
+ function readRuntimeSkillFiles() {
99
+ const skillsDir = path.join(__dirname, "runtime-skills");
100
+ if (!fs.existsSync(skillsDir)) return [];
101
+
102
+ return fs.readdirSync(skillsDir)
103
+ .filter((name) => name.endsWith(".md"))
104
+ .sort()
105
+ .map((name) => ({
106
+ slug: path.basename(name, ".md"),
107
+ md: fs.readFileSync(path.join(skillsDir, name), "utf8").trim(),
108
+ }));
109
+ }
110
+
98
111
  // Install APX + APC context skills into IDE rule files. Returns an array of result objects.
99
112
  // targetIds: array of target ids to install; null = all project targets.
100
113
  export function installIdeSkills(root, targetIds = null) {
@@ -159,6 +172,7 @@ export function installGlobalSkills() {
159
172
  skills.push({ slug: "apx", md: buildSkillMd(fs.readFileSync(apxSrc, "utf8").trim()) });
160
173
  if (fs.existsSync(apcSrc))
161
174
  skills.push({ slug: "apc-context", md: buildApcContextSkillMd(fs.readFileSync(apcSrc, "utf8").trim()) });
175
+ skills.push(...readRuntimeSkillFiles());
162
176
 
163
177
  for (const base of GLOBAL_SKILL_DIRS) {
164
178
  for (const { slug, md } of skills) {
@@ -256,6 +270,7 @@ export function getOrCreateApxId(root) {
256
270
  try { cfg = JSON.parse(fs.readFileSync(p, "utf8")); } catch { return null; }
257
271
  if (cfg.apx_id) return cfg.apx_id;
258
272
  const apxId = crypto.randomUUID().replace(/-/g, "").slice(0, 12);
273
+ console.log(`[apx] Generating new stable ID ${apxId} for project at ${root}`);
259
274
  cfg.apx_id = apxId;
260
275
  fs.writeFileSync(p, JSON.stringify(cfg, null, 2) + "\n");
261
276
  return apxId;
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
- // Ensure stable APX storage root exists (~/.apx/projects/<apx_id>/).
40
+ // Resolve stable APX storage ID (read from .apc/project.json).
41
41
  const apxId = getOrCreateApxId(abs);
42
- const storagePath = ensureProjectStorage(apxId);
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
- // Project runtime messages stay in APX local storage.
52
- entry.logMessage = (payload) => appendMessageToFs({ projectRoot: storagePath, ...payload });
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;
@@ -1,5 +1,5 @@
1
1
  // OpenAI Codex CLI runtime adapter.
2
- // codex exec "<prompt>"
2
+ // codex exec --sandbox workspace-write --skip-git-repo-check "<prompt>"
3
3
  // System prompt is prepended to the prompt body since Codex doesn't have a
4
4
  // dedicated --system flag in `exec` mode.
5
5
  // Reference: https://github.com/openai/codex
@@ -15,7 +15,7 @@ export default {
15
15
  const fullPrompt = system ? `${system}\n\n---\n\n${prompt}` : prompt;
16
16
  const r = await runProcess({
17
17
  command: "codex",
18
- args: ["exec", fullPrompt],
18
+ args: ["exec", "--sandbox", "workspace-write", "--skip-git-repo-check", fullPrompt],
19
19
  cwd,
20
20
  env,
21
21
  timeoutMs,
@@ -99,7 +99,7 @@ export default {
99
99
  type: "object",
100
100
  properties: {
101
101
  project: { type: "string" },
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." },
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." },
103
103
  runtime: {
104
104
  type: "string",
105
105
  enum: RUNTIME_IDS,
@@ -53,11 +53,11 @@ 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. 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.
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
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
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
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.`;