@agentprojectcontext/apx 1.54.0 → 1.55.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agentprojectcontext/apx",
3
- "version": "1.54.0",
3
+ "version": "1.55.0",
4
4
  "description": "APX — unified CLI + daemon for the Agent Project Context (APC) standard.",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -591,6 +591,75 @@ export function readGlobalMessages({ channel, limit = 100, since } = {}) {
591
591
  return all.slice(-limit);
592
592
  }
593
593
 
594
+ // ---------------------------------------------------------------------------
595
+ // Global channel threads (super-agent chats surfaced in the web Chats sidebar)
596
+ // ---------------------------------------------------------------------------
597
+ // The global ledger is the source of truth for every super-agent turn outside
598
+ // exec (telegram, web quick-chat, desktop, deck …). A "thread" is one
599
+ // channel+day JSONL file — the same granularity the context window reads.
600
+
601
+ const CHANNEL_NAME_RE = /^[a-z0-9_-]+$/i;
602
+
603
+ // List every non-empty channel+day thread, newest-last-activity first.
604
+ export function listGlobalThreads({ channels, _globalMessagesDir } = {}) {
605
+ const base = _globalMessagesDir || GLOBAL_MESSAGES_DIR;
606
+ if (!fs.existsSync(base)) return [];
607
+ const chans = (channels && channels.length
608
+ ? channels
609
+ : fs.readdirSync(base).filter((f) => {
610
+ try { return fs.statSync(path.join(base, f)).isDirectory(); } catch { return false; }
611
+ })
612
+ ).filter((c) => CHANNEL_NAME_RE.test(c));
613
+
614
+ const out = [];
615
+ for (const ch of chans) {
616
+ const dir = path.join(base, ch);
617
+ let files;
618
+ try { files = fs.readdirSync(dir); } catch { continue; }
619
+ for (const f of files) {
620
+ const m = f.match(/^(\d{4}-\d{2}-\d{2})\.jsonl$/);
621
+ if (!m) continue;
622
+ const msgs = parseDayJsonl(fs.readFileSync(path.join(dir, f), "utf8")).filter(
623
+ (r) => r.type === "user" || r.type === "agent"
624
+ );
625
+ if (!msgs.length) continue;
626
+ const firstUser = msgs.find((r) => r.type === "user");
627
+ const title = String((firstUser || msgs[0]).body || "")
628
+ .replace(/\s+/g, " ")
629
+ .trim()
630
+ .slice(0, 80);
631
+ out.push({
632
+ id: m[1],
633
+ channel: ch,
634
+ title: title || `${ch} · ${m[1]}`,
635
+ messages: msgs.length,
636
+ started_at: msgs[0].ts,
637
+ last_ts: msgs[msgs.length - 1].ts,
638
+ });
639
+ }
640
+ }
641
+ out.sort((a, b) => (b.last_ts || "").localeCompare(a.last_ts || ""));
642
+ return out;
643
+ }
644
+
645
+ // Read one channel+day thread shaped for the web chat viewer:
646
+ // { id, channel, messages: [{ role, content, ts }] } — or null when missing.
647
+ export function readGlobalThread({ channel, date, _globalMessagesDir } = {}) {
648
+ if (!CHANNEL_NAME_RE.test(String(channel || ""))) return null;
649
+ if (!/^\d{4}-\d{2}-\d{2}$/.test(String(date || ""))) return null;
650
+ const base = _globalMessagesDir || GLOBAL_MESSAGES_DIR;
651
+ const file = path.join(base, channel, `${date}.jsonl`);
652
+ if (!fs.existsSync(file)) return null;
653
+ const messages = parseDayJsonl(fs.readFileSync(file, "utf8"))
654
+ .filter((r) => r.type === "user" || r.type === "agent")
655
+ .map((r) => ({
656
+ role: r.type === "user" ? "user" : "assistant",
657
+ content: r.body || "",
658
+ ts: r.ts,
659
+ }));
660
+ return { id: date, channel, messages };
661
+ }
662
+
594
663
  // Wipe the cache and re-populate from APX project messages. Reads BOTH `.jsonl`
595
664
  // (current format) and `.md` (legacy). Called by rebuild.
596
665
  export function rebuildMessagesFromFs(db, projectRoot) {
@@ -1,11 +1,14 @@
1
1
  // Per-agent conversation surface: list, fetch, compact, and a2a /send.
2
2
  // GET /projects/:pid/agents/:slug/conversations
3
3
  // GET /projects/:pid/agents/:slug/conversations/:id
4
+ // GET /projects/:pid/super-agent/threads (channel ledger)
5
+ // GET /projects/:pid/super-agent/threads/:channel/:id
4
6
  // POST /projects/:pid/agents/:slug/compact
5
7
  // POST /projects/:pid/agents/:slug/conversations/:id/compact
6
8
  // POST /projects/:pid/send (agent-to-agent)
7
9
  import { readAgents } from "#core/apc/parser.js";
8
10
  import { listConversations, readConversation } from "#core/stores/conversations.js";
11
+ import { listGlobalThreads, readGlobalThread } from "#core/stores/messages.js";
9
12
  import { compactConversation } from "#core/stores/conversations-compactor.js";
10
13
  import { replyAsAgent } from "#core/agent/a2a/reply.js";
11
14
  import { nowIso } from "./shared.js";
@@ -48,6 +51,28 @@ export function register(app, { project, config }) {
48
51
  });
49
52
  });
50
53
 
54
+ // ---- Super-agent channel threads ----
55
+ // The super-agent's chats (telegram, web quick-chat, desktop, deck …) live
56
+ // in the global per-channel ledger, not in per-agent conversation files.
57
+ // These endpoints surface that ledger as day-threads so the web Chats
58
+ // sidebar can list and reopen them.
59
+ app.get("/projects/:pid/super-agent/threads", (req, res) => {
60
+ const p = project(req, res);
61
+ if (!p) return;
62
+ res.json(listGlobalThreads());
63
+ });
64
+
65
+ app.get("/projects/:pid/super-agent/threads/:channel/:id", (req, res) => {
66
+ const p = project(req, res);
67
+ if (!p) return;
68
+ const thread = readGlobalThread({
69
+ channel: req.params.channel,
70
+ date: req.params.id,
71
+ });
72
+ if (!thread) return res.status(404).json({ error: "thread not found" });
73
+ res.json(thread);
74
+ });
75
+
51
76
  async function handleCompact(req, res, filename) {
52
77
  const p = project(req, res);
53
78
  if (!p) return;
@@ -0,0 +1,56 @@
1
+ // `apx code` — launch the APX terminal coding assistant (Solid.js TUI).
2
+ // The TUI runs its TypeScript source directly under bun; there is no
3
+ // legacy readline fallback anymore (removed with the old sys.js chat).
4
+ import { existsSync } from "node:fs";
5
+ import { fileURLToPath } from "node:url";
6
+ import { dirname, resolve } from "node:path";
7
+ import { spawnSync } from "node:child_process";
8
+ import { resolveProjectId } from "./project.js";
9
+ import { readConfig } from "#core/config/index.js";
10
+ import { readIdentity } from "#core/identity/index.js";
11
+
12
+ const __dirname = dirname(fileURLToPath(import.meta.url));
13
+ const TUI_SRC = resolve(__dirname, "../../tui/run.ts");
14
+
15
+ export async function cmdCode(args) {
16
+ const pid = await resolveProjectId(args?.flags?.project);
17
+ const cfg = readConfig();
18
+ const id = readIdentity();
19
+
20
+ // Optional --agent <slug>: route chat to a project agent instead of the APX
21
+ // default. Empty / missing flag means default ("super-agent" mode).
22
+ const agentFlag = typeof args?.flags?.agent === "string" ? args.flags.agent.trim() : "";
23
+ const routedAgentSlug = agentFlag || null;
24
+ const defaultAgentLabel = id?.agent_name || cfg.super_agent?.name || "APX";
25
+
26
+ if (!existsSync(TUI_SRC)) {
27
+ throw new Error(
28
+ "apx code: TUI source not found at src/interfaces/tui/run.ts — reinstall with `npm i -g @agentprojectcontext/apx`."
29
+ );
30
+ }
31
+
32
+ // bun must resolve node_modules/tsconfig from the apx package root, so the
33
+ // spawn cwd stays there — but we pass the user's actual working directory
34
+ // (where they ran `apx code`) via --cwd so the TUI shows the real project
35
+ // path + git branch instead of apx/src.
36
+ const bunBin = process.env.BUN_PATH || "bun";
37
+ const userCwd = process.cwd();
38
+ const result = spawnSync(bunBin, [
39
+ "--preload", "@opentui/solid/preload",
40
+ TUI_SRC,
41
+ "--pid", pid,
42
+ "--agent", routedAgentSlug || defaultAgentLabel,
43
+ "--model", cfg.super_agent?.model || "claude-3-5-sonnet",
44
+ "--cwd", userCwd,
45
+ ], { stdio: "inherit", cwd: resolve(__dirname, "../../..") });
46
+
47
+ if (result.error?.code === "ENOENT") {
48
+ throw new Error(
49
+ "apx code: bun is required to run the TUI but was not found. Install it (https://bun.sh) or set BUN_PATH to the binary."
50
+ );
51
+ }
52
+ if (result.error) throw result.error;
53
+ if (typeof result.status === "number" && result.status !== 0) {
54
+ process.exitCode = result.status;
55
+ }
56
+ }
@@ -90,7 +90,7 @@ import {
90
90
  cmdConversationsList,
91
91
  cmdConversationsGet,
92
92
  } from "./commands/chat.js";
93
- import { cmdSys as cmdCode } from "./commands/sys.js";
93
+ import { cmdCode } from "./commands/code.js";
94
94
  import { cmdRun, cmdEnvDetect } from "./commands/runtime.js";
95
95
  import { cmdSend, cmdConnections } from "./commands/a2a.js";
96
96
  import {