@agentprojectcontext/apx 1.0.3 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,150 +1,102 @@
1
1
 
2
2
  # Agent Project Context
3
3
 
4
- This project uses APC. All agent context lives in `.apc/` not in `.claude/`, `.cursor/`, `.windsurf/`, or any other IDE folder.
4
+ This project uses APC. APC stores portable project context in `.apc/` and `AGENTS.md`.
5
+
6
+ APC does not store raw runtime sessions. Sessions, conversations, messages, caches, provider
7
+ threads, and private runtime memory stay in the IDE, CLI, daemon, or user-level store that created
8
+ them.
5
9
 
6
10
  ## FIRST: check for pending migration
7
11
 
8
- **Before doing anything else**, check if `.apc/migrate.md` exists:
12
+ Before doing anything else, check if `.apc/migrate.md` exists:
9
13
 
10
14
  ```bash
11
15
  cat .apc/migrate.md 2>/dev/null
12
16
  ```
13
17
 
14
- If it exists, open the conversation with this message do not answer any other question first:
15
-
16
- > I see this project was just initialized with **Agent Project Context (APC)**.
17
- >
18
- > I found context files that haven't been migrated yet:
19
- > [list files from .apc/migrate.md]
20
- >
21
- > I'll read them, understand what's in them, and migrate intelligently — keeping only what APC doesn't already handle.
22
- >
23
- > **Want me to start?**
18
+ If it exists, offer to migrate before answering anything else. Read detected files, separate durable
19
+ project context from runtime/private state, and migrate only what belongs in APC.
24
20
 
25
- ### How to migrate think, don't copy
21
+ If the user says no or later, delete `.apc/migrate.md` so the offer is not repeated.
26
22
 
27
- **Step 1 Read everything first.** Read all detected context files in full. Also read `AGENTS.md` if it exists. Understand the full project structure, conventions, and any referenced directories (e.g. `works/`, `docs/`, `notes/`).
23
+ ## Migration rule: think, do not copy
28
24
 
29
- **Step 2 — Classify each piece of content:**
25
+ Classify content:
30
26
 
31
- | What it says | What to do |
27
+ | Content | Action |
32
28
  |---|---|
33
- | Agent definitions (role, model, skills) | Create `.apc/agents/<slug>.md` |
34
- | Any instruction about writing sessions to a custom path (`works/sessions/`, `notes/`, etc.) | **Drop it** — APC handles sessions natively in `.apc/agents/<slug>/sessions/` |
35
- | Any instruction about writing memory to a custom path (`works/memory.md`, etc.) | **Drop it** — APC handles memory natively in `.apc/agents/<slug>/memory.md` |
36
- | "List agents in `AGENTS.md`" or any auto-generation rule for `AGENTS.md` | **Drop it** — APC handles this natively |
37
- | Project-specific directories not covered by APC (e.g. `works/specs/`, `works/tasks/`) | **Keep in `AGENTS.md`** — document the convention there |
38
- | Project rules, testing policy, stack notes, URLs, credentials | **Keep in `AGENTS.md`** project context that APC doesn't define |
39
- | IDE-specific shortcuts or instructions (e.g. "run `npm run dev` in Claude terminal") | **Keep in `AGENTS.md`** — still useful to all agents |
40
-
41
- **Step 3 Write `AGENTS.md`.** Start from what already exists in `AGENTS.md`, add what you kept from the classified content. Remove anything that duplicates APC native behavior. Keep it agent-neutral — no IDE-specific framing.
42
-
43
- **Step 4 — Delete the original files** (`CLAUDE.md`, `.cursorrules`, etc.). Do not leave stubs. The content either moved to `AGENTS.md` / `.apc/agents/` or was intentionally dropped because APC covers it.
44
-
45
- **Step 5 — Delete `.apc/migrate.md`** to mark migration complete.
46
-
47
- **Step 6 — Summarize** what was created, what was kept, and what was dropped (and why).
48
-
49
- If the user says no or later: delete `.apc/migrate.md` immediately so this offer is not shown again in future sessions.
50
-
51
- ---
52
-
53
- ## Structure
54
-
55
- ```
56
- AGENTS.md ← project context: rules, stack, conventions (commit)
29
+ | Agent definitions: role, model, skills, description | Put in `.apc/agents/<slug>.md` and/or `AGENTS.md` |
30
+ | Shared project rules, stack notes, commands, testing policy | Keep in `AGENTS.md` |
31
+ | Reusable instruction blocks | Move to `.apc/skills/<name>.md` |
32
+ | Durable safe facts useful to all contributors | Add to `.apc/agents/<slug>/memory.md` only after curation |
33
+ | MCP expectations without secrets | Add to `.apc/mcps.json` |
34
+ | Raw sessions, transcripts, conversations, messages, tool logs | Do not move into `.apc/`; leave with source runtime |
35
+ | Secrets, tokens, credentials, private headers | Do not store in repository |
36
+ | IDE UI settings or personal aliases | Leave in IDE/user config |
37
+ | Instructions to store sessions under `.apc/` | Drop as obsolete |
38
+
39
+ ## APC structure
40
+
41
+ ```text
42
+ AGENTS.md ← root project contract
57
43
  .apc/
58
- project.json ← metadata + apx field (commit)
59
- .gitignore ← safe defaults, created by apx init (commit)
60
- agents/<slug>.md ← agent definition (commit)
61
- agents/<slug>/
62
- memory.md curated memory (commit)
63
- sessions/ raw session logs (local-only, gitignored)
64
- skills/ ← reusable prompt fragments (commit)
65
- mcps.json ← MCP declarations without secrets (commit)
44
+ project.json ← project metadata
45
+ .gitignore ← safety guard
46
+ agents/<slug>.md ← agent definition
47
+ agents/<slug>/memory.md ← optional curated project memory
48
+ skills/<name>.md reusable project instructions
49
+ mcps.json MCP hints without secrets
66
50
  ```
67
51
 
68
- ## Visibility rules
69
-
70
- | What | Visibility | Commit? |
71
- |---|---|---|
72
- | Agent definitions, skills, rules | `stable` / `project` | Yes |
73
- | `memory.md` | `project` | Yes |
74
- | `sessions/` | `local` | No — gitignored |
75
- | `secrets/`, `*.secret.json`, `*.env` | `private` | Never |
76
- | `cache/`, `tmp/` | `ephemeral` | No — gitignored |
77
- | `migrate.md` | `ephemeral` | No — gitignored |
78
-
79
- **Write sessions to `.apc/agents/<slug>/sessions/`** — they stay local. Extract meaningful decisions into `memory.md` — that travels with the repo.
80
-
81
- ## Rules
82
-
83
- 1. Read your definition and memory from `.apc/agents/<your-slug>/`
84
- 2. Write memory to `.apc/agents/<your-slug>/memory.md` — never to IDE-specific folders
85
- 3. Write raw session logs to `.apc/agents/<your-slug>/sessions/` — they are gitignored
86
- 4. `AGENTS.md` is the neutral project context file — edit it directly, it is not auto-generated
87
- 5. To list agents: read `AGENTS.md` or list `.apc/agents/*.md`
88
-
89
- ## Sessions — write one at the end of every task
90
-
91
- Sessions are the record of what was done, for future agents and for the team.
52
+ Do not store:
53
+
54
+ ```text
55
+ .apc/agents/<slug>/sessions/
56
+ .apc/sessions/
57
+ .apc/conversations/
58
+ .apc/messages/
59
+ .apc/project.db
60
+ .apc/cache/
61
+ .apc/tmp/
62
+ .apc/private/
63
+ .apc/secrets/
64
+ ```
92
65
 
93
- **Where to write:**
94
- - APC standard path: `.apc/agents/<slug>/sessions/<date>-<slug>.md` (gitignored)
95
- - Project-specific path: check `AGENTS.md` — some projects use `works/sessions/` or similar
66
+ ## Operating rules
96
67
 
97
- **Format always use this frontmatter:**
68
+ 1. Read `AGENTS.md` and relevant `.apc/` files before assuming project context.
69
+ 2. Read agent definitions from `.apc/agents/<slug>.md` when present.
70
+ 3. Read curated project memory from `.apc/agents/<slug>/memory.md` when present.
71
+ 4. Write only durable, safe, curated facts to APC memory.
72
+ 5. Never write raw sessions, transcripts, messages, conversations, or tool logs into `.apc/`.
73
+ 6. Keep secrets out of APC and out of git.
74
+ 7. Treat `.apc/mcps.json` as MCP configuration hints, not as an MCP implementation.
98
75
 
99
- ```markdown
100
- ---
101
- title: Short title of what was done
102
- description: One-line summary for the index
103
- date: YYYY-MM-DD
104
- status: open | completed | in-progress | abandoned
105
- ---
76
+ ## Sessions
106
77
 
107
- ## Goal
108
- What was the task or request.
78
+ Sessions belong to the runtime that created them.
109
79
 
110
- ## Done
111
- What was completed. Be specific — file paths, decisions, changes.
80
+ Examples:
112
81
 
113
- ## Pending
114
- Anything left unfinished or deferred.
82
+ ```text
83
+ Codex runtime storage
84
+ Claude Code runtime storage
85
+ OpenCode runtime storage
86
+ ~/.apx/projects/<project-id>/agents/<slug>/sessions/
115
87
  ```
116
88
 
117
- **How to create:**
118
- - If APX is installed (`apx` field in `project.json` is `"installed"`):
119
- ```bash
120
- apx session new <slug> --title "Short title"
121
- ```
122
- Then fill in `description`, update `status` to `completed` when done.
123
- - If APX is not available: create the file manually following the format above.
124
-
125
- **When to write:** at the end of every task session, before closing. Not optional.
89
+ At task end, provide the user a concise result. If project memory should be updated, write a short
90
+ sanitized fact to `.apc/agents/<slug>/memory.md` only when useful and safe.
126
91
 
127
- ## APX — check once, remember forever
92
+ ## APX
128
93
 
129
- Read `.apc/project.json`. It has an `apx` field:
94
+ APX can provide a local daemon, MCP management, Telegram bridge, routines, and runtime dispatch
95
+ across Codex, Claude Code, OpenCode, Aider, or direct LLM engines. Those are APX runtime features,
96
+ not APC portable-core requirements.
130
97
 
131
- - **`"installed"`** — APX is available. Use `apx` commands freely when useful.
132
- - **`"declined"`** — user chose not to install. Never suggest or run `apx` commands.
133
- - **`null` or missing** — unknown. Do the one-time check below, then write the result and never ask again.
98
+ APX runtime state belongs outside the repository:
134
99
 
135
- ### One-time APX detection (only when `apx` is null or missing)
136
-
137
- ```bash
138
- apx --version 2>/dev/null && echo "FOUND" || echo "NOT_FOUND"
100
+ ```text
101
+ ~/.apx/projects/<project-id>/
139
102
  ```
140
-
141
- - If `FOUND`: write `"apx": "installed"` to `.apc/project.json`. Use APX normally.
142
- - If `NOT_FOUND`: tell the user once:
143
-
144
- > APX is not installed. It adds agent coordination, MCP server management, and memory commands to this project.
145
- > Want me to install it? (`npm install -g apx`)
146
-
147
- - If yes: run `npm install -g apx`, then write `"apx": "installed"`.
148
- - If no: write `"apx": "declined"`. Never mention APX again in this project.
149
-
150
- **Never run `apx` commands if the field is `null`, `"declined"`, or unconfirmed.**
@@ -1,7 +1,10 @@
1
- # APX — Agent Project Framework
1
+ # APX — Agent Project Context Runtime
2
2
 
3
3
  The daemon runs on `127.0.0.1:7430` and auto-starts on first `apx` call.
4
4
 
5
+ APX reads APC project context from `.apc/`, but APX runtime state belongs outside the repository
6
+ under `~/.apx/projects/<project-id>/`.
7
+
5
8
  ---
6
9
 
7
10
  ## Coordinate with other agents
@@ -48,6 +51,8 @@ apx mcp run filesystem read_file '{"path": "README.md"}'
48
51
 
49
52
  ## Memory
50
53
 
54
+ Write memory only for durable, safe project facts. Do not store raw transcripts or secrets.
55
+
51
56
  ```bash
52
57
  apx memory <slug> # read agent's memory.md
53
58
  apx memory <slug> --append "<fact>" # append a durable note
@@ -56,8 +61,10 @@ apx memory <slug> --replace < file.md # replace entire memory from stdin
56
61
 
57
62
  ## Sessions
58
63
 
64
+ Sessions are APX runtime state. They do not belong in `.apc/`.
65
+
59
66
  ```bash
60
- apx session new <slug> --title "What you did" # create session file
67
+ apx session new <slug> --title "What you did" # create APX local session file
61
68
  apx session list <slug> # list sessions
62
69
  apx session check # exits 1 if session already active
63
70
  ```
@@ -11,6 +11,21 @@ export const TELEGRAM_STATE_PATH = path.join(APX_HOME, "telegram-state.json");
11
11
  // Global channel messages (telegram, direct, whatsapp, …) live here,
12
12
  // separated from any project. Structure: ~/.apx/messages/<channel>/YYYY-MM-DD.jsonl
13
13
  export const GLOBAL_MESSAGES_DIR = path.join(APX_HOME, "messages");
14
+ // Per-project runtime storage (conversations, sessions) — never in the repo.
15
+ // Structure: ~/.apx/projects/<apx_id>/agents/<slug>/conversations/
16
+ export const PROJECT_STORE_ROOT = path.join(APX_HOME, "projects");
17
+ export const DEFAULT_PROJECT_ID = "default";
18
+ export const DEFAULT_PROJECT_STORE = path.join(PROJECT_STORE_ROOT, DEFAULT_PROJECT_ID);
19
+
20
+ export function projectStorageRoot(apxId) {
21
+ return path.join(PROJECT_STORE_ROOT, apxId);
22
+ }
23
+
24
+ export function ensureProjectStorage(apxId) {
25
+ const root = projectStorageRoot(apxId);
26
+ fs.mkdirSync(root, { recursive: true });
27
+ return root;
28
+ }
14
29
 
15
30
  const DEFAULT_CONFIG = {
16
31
  port: 7430,
@@ -1,7 +1,7 @@
1
1
  // Messages store: filesystem source-of-truth + SQLite cache mirror.
2
2
  //
3
3
  // On disk (project-specific — runtime, a2a, exec):
4
- // <project>/.apc/messages/YYYY-MM-DD.jsonl
4
+ // ~/.apx/projects/<project-id>/messages/YYYY-MM-DD.jsonl
5
5
  //
6
6
  // On disk (global cross-project channels — telegram, direct, whatsapp, …):
7
7
  // ~/.apx/messages/<channel>/YYYY-MM-DD.jsonl
@@ -24,12 +24,12 @@ const nowIso = () => new Date().toISOString().replace(/\.\d{3}Z$/, "Z");
24
24
 
25
25
  function dayPathJsonl(projectRoot, ts) {
26
26
  const day = (ts || nowIso()).slice(0, 10);
27
- return path.join(projectRoot, ".apc", "messages", `${day}.jsonl`);
27
+ return path.join(projectRoot, "messages", `${day}.jsonl`);
28
28
  }
29
29
 
30
30
  function dayPathMd(projectRoot, ts) {
31
31
  const day = (ts || nowIso()).slice(0, 10);
32
- return path.join(projectRoot, ".apc", "messages", `${day}.md`);
32
+ return path.join(projectRoot, "messages", `${day}.md`);
33
33
  }
34
34
 
35
35
  export function appendMessageToFs({ projectRoot, channel, direction, author, body, meta = {}, ts, agent_slug, session_id, external_id }) {
@@ -262,7 +262,7 @@ function sanitizeAssistantForContext(content) {
262
262
  // ---------------------------------------------------------------------------
263
263
 
264
264
  export function readProjectMessages(projectRoot, { channel, agent_slug, since, limit = 100 } = {}) {
265
- const dir = path.join(projectRoot, ".apc", "messages");
265
+ const dir = path.join(projectRoot, "messages");
266
266
  if (!fs.existsSync(dir)) return [];
267
267
  const all = [];
268
268
  for (const f of fs.readdirSync(dir).sort()) {
@@ -285,7 +285,7 @@ export function readProjectMessages(projectRoot, { channel, agent_slug, since, l
285
285
  export function searchProjectMessages(projectRoot, query, limit = 50) {
286
286
  if (!query) return [];
287
287
  const q = query.toLowerCase();
288
- const dir = path.join(projectRoot, ".apc", "messages");
288
+ const dir = path.join(projectRoot, "messages");
289
289
  if (!fs.existsSync(dir)) return [];
290
290
  const all = [];
291
291
  for (const f of fs.readdirSync(dir).sort()) {
@@ -392,10 +392,10 @@ export function readGlobalMessages({ channel, limit = 100, since } = {}) {
392
392
  return all.slice(-limit);
393
393
  }
394
394
 
395
- // Wipe the cache and re-populate from .apc/messages/*. Reads BOTH `.jsonl`
395
+ // Wipe the cache and re-populate from APX project messages. Reads BOTH `.jsonl`
396
396
  // (current format) and `.md` (legacy). Called by rebuild.
397
397
  export function rebuildMessagesFromFs(db, projectRoot) {
398
- const dir = path.join(projectRoot, ".apc", "messages");
398
+ const dir = path.join(projectRoot, "messages");
399
399
  if (!fs.existsSync(dir)) return { count: 0 };
400
400
  db.prepare("DELETE FROM messages").run();
401
401
 
@@ -1,6 +1,7 @@
1
1
  import fs from "node:fs";
2
2
  import path from "node:path";
3
3
  import os from "node:os";
4
+ import crypto from "node:crypto";
4
5
  import { fileURLToPath } from "node:url";
5
6
  import { readAgents, readAgentsFromDir, VAULT_DIR } from "./parser.js";
6
7
 
@@ -188,9 +189,13 @@ const AGENTS_MD_TEMPLATE = `# Agents
188
189
  -->
189
190
  `;
190
191
 
191
- const APC_GITIGNORE = `# APC runtime data — local-first by default
192
+ const APC_GITIGNORE = `# APC runtime data — never in the repository
193
+ # Chat conversations and runtime sessions belong in ~/.apx/projects/<id>/
192
194
  agents/*/sessions/
195
+ agents/*/conversations/
193
196
  sessions/
197
+ conversations/
198
+ messages/
194
199
  chats/
195
200
  cache/
196
201
  tmp/
@@ -200,6 +205,7 @@ secrets/
200
205
  *.secret.json
201
206
  *.env
202
207
  *.env.*
208
+ project.db
203
209
  migrate.md
204
210
  `;
205
211
 
@@ -241,6 +247,20 @@ function writeMigrateMd(apfDir, found) {
241
247
  fs.writeFileSync(path.join(apfDir, "migrate.md"), lines.join("\n") + "\n");
242
248
  }
243
249
 
250
+ // Get the stable APX storage ID for a project, generating one if it doesn't exist.
251
+ // Called by the daemon when registering a project.
252
+ export function getOrCreateApxId(root) {
253
+ const p = path.join(root, ".apc", "project.json");
254
+ if (!fs.existsSync(p)) return null;
255
+ let cfg;
256
+ try { cfg = JSON.parse(fs.readFileSync(p, "utf8")); } catch { return null; }
257
+ if (cfg.apx_id) return cfg.apx_id;
258
+ const apxId = crypto.randomUUID().replace(/-/g, "").slice(0, 12);
259
+ cfg.apx_id = apxId;
260
+ fs.writeFileSync(p, JSON.stringify(cfg, null, 2) + "\n");
261
+ return apxId;
262
+ }
263
+
244
264
  export function initApf(directory, { name } = {}) {
245
265
  const root = path.resolve(directory);
246
266
  fs.mkdirSync(root, { recursive: true });
@@ -252,6 +272,7 @@ export function initApf(directory, { name } = {}) {
252
272
 
253
273
  const projectJson = path.join(apfDir, "project.json");
254
274
  if (!fs.existsSync(projectJson)) {
275
+ const apxId = crypto.randomUUID().replace(/-/g, "").slice(0, 12);
255
276
  fs.writeFileSync(
256
277
  projectJson,
257
278
  JSON.stringify(
@@ -261,7 +282,7 @@ export function initApf(directory, { name } = {}) {
261
282
  apf: SPEC_VERSION,
262
283
  created: nowIso(),
263
284
  apx: null,
264
- sessions: { defaultVisibility: "local" },
285
+ apx_id: apxId,
265
286
  },
266
287
  null,
267
288
  2
@@ -291,7 +312,7 @@ export function initApf(directory, { name } = {}) {
291
312
 
292
313
  export function ensureAgentDir(root, slug) {
293
314
  const dir = path.join(root, ".apc", "agents", slug);
294
- fs.mkdirSync(path.join(dir, "sessions"), { recursive: true });
315
+ fs.mkdirSync(dir, { recursive: true });
295
316
  const memory = path.join(dir, "memory.md");
296
317
  if (!fs.existsSync(memory)) {
297
318
  fs.writeFileSync(
@@ -5,9 +5,9 @@
5
5
  import fs from "node:fs";
6
6
  import path from "node:path";
7
7
 
8
- export function generateSessionId(projectRoot, agentSlug) {
8
+ export function generateSessionId(storageRoot, agentSlug) {
9
9
  const today = new Date().toISOString().slice(0, 10);
10
- const dir = path.join(projectRoot, ".apc", "agents", agentSlug, "sessions");
10
+ const dir = path.join(storageRoot, "agents", agentSlug, "sessions");
11
11
  let next = 1;
12
12
  if (fs.existsSync(dir)) {
13
13
  for (const f of fs.readdirSync(dir)) {
@@ -0,0 +1,99 @@
1
+ // Update checker — non-blocking, cached 24h.
2
+ // On each command: reads cache → shows message if newer version exists.
3
+ // In background: refreshes cache from npm registry (fire-and-forget).
4
+ // Never slows down the main command.
5
+
6
+ import fs from "node:fs";
7
+ import path from "node:path";
8
+ import https from "node:https";
9
+ import { APX_HOME } from "./config.js";
10
+
11
+ const PACKAGE_NAME = "@agentprojectcontext/apx";
12
+ const CACHE_PATH = path.join(APX_HOME, "update-check.json");
13
+ const CACHE_TTL_MS = 24 * 60 * 60 * 1000; // 24h
14
+
15
+ function readCache() {
16
+ try {
17
+ const raw = fs.readFileSync(CACHE_PATH, "utf8");
18
+ return JSON.parse(raw);
19
+ } catch {
20
+ return null;
21
+ }
22
+ }
23
+
24
+ function writeCache(data) {
25
+ try {
26
+ fs.mkdirSync(path.dirname(CACHE_PATH), { recursive: true });
27
+ fs.writeFileSync(CACHE_PATH, JSON.stringify(data) + "\n");
28
+ } catch {}
29
+ }
30
+
31
+ // Compare semver strings. Returns true if `latest` > `current`.
32
+ function isNewer(current, latest) {
33
+ if (!current || !latest) return false;
34
+ const parse = (v) => v.replace(/^v/, "").split(".").map(Number);
35
+ const [ma, mi, pa] = parse(current);
36
+ const [mb, mib, pb] = parse(latest);
37
+ if (mb > ma) return true;
38
+ if (mb === ma && mib > mi) return true;
39
+ if (mb === ma && mib === mi && pb > pa) return true;
40
+ return false;
41
+ }
42
+
43
+ // Fetch latest version from npm registry (async, no deps).
44
+ function fetchLatest() {
45
+ return new Promise((resolve) => {
46
+ const url = `https://registry.npmjs.org/${PACKAGE_NAME}/latest`;
47
+ const req = https.get(url, { timeout: 5000 }, (res) => {
48
+ let body = "";
49
+ res.on("data", (c) => (body += c));
50
+ res.on("end", () => {
51
+ try {
52
+ resolve(JSON.parse(body).version || null);
53
+ } catch {
54
+ resolve(null);
55
+ }
56
+ });
57
+ });
58
+ req.on("error", () => resolve(null));
59
+ req.on("timeout", () => { req.destroy(); resolve(null); });
60
+ });
61
+ }
62
+
63
+ // Fire-and-forget background refresh. Never awaited by the caller.
64
+ function refreshInBackground(currentVersion) {
65
+ fetchLatest().then((latest) => {
66
+ if (latest) {
67
+ writeCache({ latest, current: currentVersion, checkedAt: Date.now() });
68
+ }
69
+ }).catch(() => {});
70
+ }
71
+
72
+ // Call this at the END of every command (after output is printed).
73
+ // Shows an update notice if a newer version is cached.
74
+ // Also triggers a background refresh if cache is stale.
75
+ export function checkForUpdate(currentVersion) {
76
+ const cache = readCache();
77
+ const now = Date.now();
78
+
79
+ // Trigger background refresh if cache is stale or missing.
80
+ if (!cache || (now - (cache.checkedAt || 0)) > CACHE_TTL_MS) {
81
+ refreshInBackground(currentVersion);
82
+ }
83
+
84
+ // Show notice if cache has a newer version.
85
+ if (cache && isNewer(currentVersion, cache.latest)) {
86
+ const divider = "─".repeat(56);
87
+ process.stderr.write(
88
+ `\n${divider}\n` +
89
+ ` apx update available ${currentVersion} → ${cache.latest}\n` +
90
+ ` run: apx update\n` +
91
+ `${divider}\n`
92
+ );
93
+ }
94
+ }
95
+
96
+ // Used by `apx update` command to get the latest version (with network call).
97
+ export async function getLatestVersion() {
98
+ return await fetchLatest();
99
+ }
@@ -1,13 +1,13 @@
1
1
  // Helpers that wrap external runtimes (Claude Code, Codex, OpenCode, Aider)
2
2
  // with APC awareness:
3
3
  //
4
- // 1. Create an APC session BEFORE the runtime starts.
4
+ // 1. Create an APX runtime session BEFORE the runtime starts.
5
5
  // 2. Inject an "APC Runtime Context" block into the system prompt so the
6
6
  // runtime knows the session id, the cwd of the project, and the apx
7
7
  // commands it can use to update memory / append session notes.
8
8
  // 3. After the runtime returns, capture the external transcript path
9
9
  // (Claude Code gives one, Codex/OpenCode/Aider don't yet) and write it
10
- // into the APC session frontmatter.
10
+ // into the APX session frontmatter.
11
11
  // 4. Close the session with a synthesised result (truncated stdout).
12
12
  //
13
13
  // Used by both POST /projects/:pid/agents/:slug/runtime (CLI) and the
@@ -22,12 +22,12 @@ const nowIso = () => new Date().toISOString().replace(/\.\d{3}Z$/, "Z");
22
22
  const APC_RUNTIME_HINT = `
23
23
  # APC Runtime Context
24
24
 
25
- You are running inside an APC (Agent Project Framework) project. APC gives you durable session state across runs.
25
+ You are running inside an APC (Agent Project Context) project. APC gives you portable project context. APX gives you local runtime session state.
26
26
 
27
27
  - **Project**: {{name}} ({{path}})
28
28
  - **Agent**: {{agent}}
29
29
  - **APC session id**: {{session_id}}
30
- (filename: .apc/agents/{{agent}}/sessions/{{session_id}}.md)
30
+ (stored in APX local runtime storage, outside .apc/)
31
31
 
32
32
  ## Commands you can use during this run
33
33
 
@@ -52,11 +52,11 @@ export function buildApfHint({ projectName, projectPath, agentSlug, sessionId })
52
52
  .replace(/\{\{session_id\}\}/g, sessionId);
53
53
  }
54
54
 
55
- // Create the APC session file on disk. Returns { id, filename, path }.
56
- export function createRuntimeSession({ projectRoot, agentSlug, runtime, taskRef = "", title }) {
57
- const dir = path.join(projectRoot, ".apc", "agents", agentSlug, "sessions");
55
+ // Create the APX runtime session file on disk. Returns { id, filename, path }.
56
+ export function createRuntimeSession({ projectRoot, storageRoot = projectRoot, agentSlug, runtime, taskRef = "", title }) {
57
+ const dir = path.join(storageRoot, "agents", agentSlug, "sessions");
58
58
  fs.mkdirSync(dir, { recursive: true });
59
- const id = generateSessionId(projectRoot, agentSlug);
59
+ const id = generateSessionId(storageRoot, agentSlug);
60
60
  const file = path.join(dir, `${id}.md`);
61
61
  const started = nowIso();
62
62
  const sessionTitle = title || `Runtime: ${runtime}`;