@staix/agent-hub 0.12.22 → 0.12.24

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.
Files changed (183) hide show
  1. package/CHANGELOG.md +117 -0
  2. package/README.md +22 -9
  3. package/docs/addons/claude-mem.md +24 -0
  4. package/docs/addons/codex-plugin.md +8 -0
  5. package/docs/addons/dot-admit.md +8 -0
  6. package/docs/addons/open-code-review.md +8 -0
  7. package/docs/addons/ponytail.md +23 -0
  8. package/docs/addons/ripwire.md +22 -0
  9. package/docs/agent-notes/README.md +5 -0
  10. package/docs/agent-notes/adapters.md +18 -6
  11. package/docs/agent-notes/benchmarks.md +3 -0
  12. package/docs/agent-notes/budget.md +4 -0
  13. package/docs/agent-notes/bus.md +3 -1
  14. package/docs/agent-notes/daemon.md +14 -1
  15. package/docs/agent-notes/local-worker.md +6 -3
  16. package/docs/agent-notes/tasks.md +6 -1
  17. package/docs/agent-notes/tests.md +4 -1
  18. package/docs/bench.md +28 -3
  19. package/docs/cooperbench.md +4 -0
  20. package/docs/data-policy.md +36 -0
  21. package/docs/events.md +10 -3
  22. package/docs/gsd-collaboration-analysis.md +48 -0
  23. package/docs/memory.md +231 -0
  24. package/docs/operations.md +873 -30
  25. package/docs/plans/2026-10-11-issues-109-sequential.md +81 -0
  26. package/docs/plans/2026-10-11-routing-evaluation-proposal.md +30 -0
  27. package/docs/profiles.md +57 -0
  28. package/docs/quickstart.md +9 -4
  29. package/docs/research.md +31 -1
  30. package/docs/roles.md +85 -0
  31. package/docs/sdlc.md +170 -0
  32. package/docs/security.md +124 -8
  33. package/docs/smoke.md +354 -5
  34. package/docs/specs/2026-09-19-agent-hub-design.md +676 -15
  35. package/docs/specs/2026-10-12-backlog-implementation-plan.md +186 -0
  36. package/docs/specs/2026-10-12-office-view-design.md +212 -0
  37. package/docs/text-projection.md +19 -0
  38. package/docs/verification/2026-10-10-cli-output-phase1.md +309 -0
  39. package/docs/verification/2026-10-10-cli-output-phase2.md +672 -0
  40. package/docs/verification/2026-10-11-0.12.23.md +90 -0
  41. package/docs/verification/2026-10-11-acp-watch-t0.md +33 -0
  42. package/docs/verification/2026-10-11-agent-config-inventory.md +20 -0
  43. package/docs/verification/2026-10-11-jcode-peer-contract.md +27 -0
  44. package/docs/verification/2026-10-13-0.12.24.md +94 -0
  45. package/docs/verification/2026-10-13-board-phone.md +31 -0
  46. package/docs/verification/2026-10-13-fsmonitor-fixture-attribution.md +19 -0
  47. package/docs/verified.json +34 -16
  48. package/package.json +2 -1
  49. package/plugins/agent-hub/.claude-plugin/plugin.json +1 -1
  50. package/plugins/agent-hub/ROLE-NOTICE.md +26 -0
  51. package/plugins/agent-hub/server.js +1108 -261
  52. package/scripts/check-docs.mjs +129 -0
  53. package/src/adapters/acp-quota.ts +13 -0
  54. package/src/adapters/acp.ts +201 -20
  55. package/src/adapters/claude-channel.ts +48 -11
  56. package/src/adapters/codex-appserver.ts +54 -13
  57. package/src/adapters/jcode.ts +110 -0
  58. package/src/adapters/local-worker.ts +10 -7
  59. package/src/adapters/pi.ts +94 -20
  60. package/src/cli/attention.ts +60 -0
  61. package/src/cli/bench.ts +26 -6
  62. package/src/cli/board-read.ts +12 -0
  63. package/src/cli/console-state.ts +131 -39
  64. package/src/cli/console.ts +73 -17
  65. package/src/cli/facts-hook.ts +12 -4
  66. package/src/cli/help.ts +52 -21
  67. package/src/cli/identity.ts +19 -8
  68. package/src/cli/init.ts +48 -10
  69. package/src/cli/jcode-mcp.ts +58 -0
  70. package/src/cli/kimi-mcp.ts +53 -0
  71. package/src/cli/launch.ts +16 -1
  72. package/src/cli/main.ts +641 -193
  73. package/src/cli/memory.ts +193 -0
  74. package/src/cli/output.ts +404 -0
  75. package/src/cli/pet-config.ts +1 -0
  76. package/src/cli/pet.ts +83 -0
  77. package/src/cli/preview.ts +40 -6
  78. package/src/cli/reset.ts +3 -0
  79. package/src/cli/status-lines.ts +1 -29
  80. package/src/cli/tail-render.ts +1 -0
  81. package/src/cli/terminal-recovery.ts +27 -1
  82. package/src/cli/upgrade-runtime.ts +56 -10
  83. package/src/cli/upgrade.ts +23 -5
  84. package/src/cli/watch.ts +31 -0
  85. package/src/hub/activity.ts +53 -0
  86. package/src/hub/ask.ts +10 -5
  87. package/src/hub/bench.ts +10 -3
  88. package/src/hub/board.ts +224 -16
  89. package/src/hub/budget.ts +39 -7
  90. package/src/hub/bus.ts +58 -20
  91. package/src/hub/child-process.ts +11 -0
  92. package/src/hub/citations.ts +7 -0
  93. package/src/hub/conductor.ts +34 -17
  94. package/src/hub/config-trust.ts +20 -7
  95. package/src/hub/conflicts.ts +2 -0
  96. package/src/hub/context-window.ts +8 -1
  97. package/src/hub/crash.ts +11 -1
  98. package/src/hub/daemon.ts +1218 -113
  99. package/src/hub/db-timeout.ts +3 -0
  100. package/src/hub/delivery-journal.ts +7 -1
  101. package/src/hub/envelope.ts +1 -1
  102. package/src/hub/events.ts +14 -2
  103. package/src/hub/execution-budget.ts +2 -0
  104. package/src/hub/facts.ts +28 -8
  105. package/src/hub/git.ts +38 -0
  106. package/src/hub/github-import.ts +28 -0
  107. package/src/hub/hub-tools.ts +49 -10
  108. package/src/hub/inference.ts +1 -1
  109. package/src/hub/lifecycle-schema.ts +17 -0
  110. package/src/hub/lifecycle.ts +10 -4
  111. package/src/hub/listen.ts +15 -0
  112. package/src/hub/manager.ts +26 -6
  113. package/src/hub/path-policy.ts +96 -0
  114. package/src/hub/peer-catalog.ts +28 -0
  115. package/src/hub/peers.ts +5 -3
  116. package/src/hub/permission-mode.ts +33 -5
  117. package/src/hub/planning.ts +70 -0
  118. package/src/hub/profiles.ts +74 -0
  119. package/src/hub/project.ts +2 -1
  120. package/src/hub/projected-diff.ts +73 -0
  121. package/src/hub/projection-exec.ts +24 -0
  122. package/src/hub/recovery-error.ts +31 -0
  123. package/src/hub/recovery-store.ts +6 -5
  124. package/src/hub/registry.ts +2 -1
  125. package/src/hub/research.ts +144 -21
  126. package/src/hub/roles.ts +123 -0
  127. package/src/hub/roster.ts +23 -0
  128. package/src/hub/routing.ts +132 -18
  129. package/src/hub/sdlc-queue.ts +112 -0
  130. package/src/hub/sdlc-release.ts +125 -0
  131. package/src/hub/sdlc.ts +250 -0
  132. package/src/hub/settings.ts +411 -0
  133. package/src/hub/shipped-roles.ts +25 -0
  134. package/src/hub/snapshots.ts +92 -16
  135. package/src/hub/start-mode.ts +50 -0
  136. package/src/hub/supervision.ts +1 -0
  137. package/src/hub/task-sweep.ts +1 -1
  138. package/src/hub/tasks.ts +1014 -111
  139. package/src/hub/terminal-open.ts +64 -0
  140. package/src/hub/text-projection.ts +126 -0
  141. package/src/hub/tooling.ts +159 -0
  142. package/src/hub/ui.ts +40 -14
  143. package/src/hub/workflow.ts +71 -0
  144. package/src/local/deny.ts +29 -7
  145. package/src/local/sandbox.ts +50 -10
  146. package/src/local/tools.ts +11 -6
  147. package/src/memory/brief.ts +28 -14
  148. package/src/memory/import.ts +55 -0
  149. package/src/memory/recall.ts +86 -2
  150. package/src/memory/store.ts +750 -0
  151. package/src/memory/sync.ts +145 -0
  152. package/src/models/mlx.ts +2 -1
  153. package/src/pet/NOTICE.md +10 -0
  154. package/src/pet/characters.ts +1438 -0
  155. package/src/pet/config.ts +31 -0
  156. package/src/pet/frame.ts +17 -0
  157. package/src/pet/pixels.ts +28 -0
  158. package/src/pi/tool-receipts.ts +2 -1
  159. package/src/role-text.d.ts +1 -0
  160. package/src/ui/index.html +983 -40
  161. package/src/ui/needs.ts +58 -0
  162. package/src/ui/office-model.ts +440 -0
  163. package/src/ui/peer-palette.ts +16 -0
  164. package/src/ui/task-board.ts +49 -0
  165. package/src/ui/task-progress.ts +5 -1
  166. package/templates/AGENTS.block.md +3 -2
  167. package/templates/config.json +5 -1
  168. package/templates/profiles/dev.json +22 -0
  169. package/templates/profiles/general.json +26 -0
  170. package/templates/profiles/research.json +57 -0
  171. package/templates/profiles/writing.json +28 -0
  172. package/templates/roles/NOTICE.md +26 -0
  173. package/templates/roles/dev/backend-engineer.md +26 -0
  174. package/templates/roles/dev/code-reviewer.md +26 -0
  175. package/templates/roles/dev/frontend-engineer.md +26 -0
  176. package/templates/roles/dev/security-reviewer.md +26 -0
  177. package/templates/roles/dev/software-architect.md +26 -0
  178. package/templates/roles/dev/test-engineer.md +26 -0
  179. package/templates/roles/research/analyst.md +26 -0
  180. package/templates/roles/research/fact-checker.md +26 -0
  181. package/templates/roles/research/research-lead.md +26 -0
  182. package/templates/roles/research/researcher.md +26 -0
  183. package/templates/roles/research/statistician.md +26 -0
@@ -0,0 +1,145 @@
1
+ import { closeSync, existsSync, openSync, readFileSync, rmSync, writeFileSync, writeSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { hubGitArgv, hubGitEnv } from "../hub/git.ts";
4
+ import { processLiveness, processSignature } from "../pi/process-signature.ts";
5
+ import type { MemoryStore } from "./store.ts";
6
+
7
+ /**
8
+ * #258 phase 2: hub memory between a person's machines through a git remote they configure. The memory directory is a
9
+ * git repository that tracks only `log/*.jsonl`; each machine commits only its own segment, so a merge of another
10
+ * machine's history never conflicts. Pull, then push; fail-open: a failure is a result with one reason, never a throw.
11
+ */
12
+ export type Visibility = "public" | "private" | "unknown";
13
+ export interface SyncResult { ok: boolean; pushed: boolean; entries: number; warning?: string; error?: string }
14
+
15
+ const GITIGNORE = "/*\n!/.gitignore\n!/log/\n/log/*\n!/log/*.jsonl\n";
16
+ const STEP_MS = 60_000;
17
+
18
+ async function git(dir: string, args: string[], signal?: AbortSignal, ms = STEP_MS): Promise<{ code: number; out: string; err: string }> {
19
+ if (signal?.aborted) return { code: -1, out: "", err: "stopped" };
20
+ const p = Bun.spawn(hubGitArgv(["-C", dir, ...args], { credentials: true }), { stdout: "pipe", stderr: "pipe", stdin: "ignore", env: hubGitEnv() });
21
+ // SIGTERM first, so git removes its lock files; SIGKILL only if it is still there after a grace period.
22
+ let hard: ReturnType<typeof setTimeout> | undefined;
23
+ const kill = () => { p.kill("SIGTERM"); hard ??= setTimeout(() => p.kill("SIGKILL"), 2_000); };
24
+ const timer = setTimeout(kill, ms);
25
+ signal?.addEventListener("abort", kill, { once: true });
26
+ try {
27
+ const [code, out, err] = await Promise.all([p.exited, new Response(p.stdout).text(), new Response(p.stderr).text()]);
28
+ return { code, out: out.trim(), err: err.trim() };
29
+ } finally { clearTimeout(timer); if (hard) clearTimeout(hard); signal?.removeEventListener("abort", kill); }
30
+ }
31
+
32
+ /** GitHub remotes are asked through `gh`; any other host, or a `gh` that cannot answer, is unknown. */
33
+ export async function remoteVisibility(url: string): Promise<Visibility> {
34
+ const repo = /github\.com[:/]([^/\s]+\/[^/\s]+?)(?:\.git)?\/?$/.exec(url)?.[1];
35
+ if (!repo) return "unknown";
36
+ try {
37
+ const p = Bun.spawn(["gh", "repo", "view", repo, "--json", "visibility", "--jq", ".visibility"], { stdout: "pipe", stderr: "ignore", stdin: "ignore" });
38
+ const timer = setTimeout(() => p.kill("SIGKILL"), 15_000);
39
+ const [code, out] = await Promise.all([p.exited, new Response(p.stdout).text()]);
40
+ clearTimeout(timer);
41
+ const v = out.trim().toLowerCase();
42
+ return code === 0 && (v === "private" || v === "internal") ? "private" : code === 0 && v === "public" ? "public" : "unknown";
43
+ } catch { return "unknown"; }
44
+ }
45
+
46
+ /** One sync per machine at a time: hubs of several projects share the store. A lock whose owner is gone is taken over. */
47
+ function lock(dir: string): (() => void) | undefined {
48
+ const file = join(dir, "sync.lock");
49
+ for (let attempt = 0; attempt < 2; attempt++) {
50
+ try {
51
+ const fd = openSync(file, "wx", 0o600);
52
+ writeSync(fd, `${process.pid} ${processSignature(process.pid) ?? ""}\n`);
53
+ closeSync(fd);
54
+ return () => rmSync(file, { force: true });
55
+ } catch {
56
+ let owner = "";
57
+ try { owner = readFileSync(file, "utf8").trim(); } catch { continue; }
58
+ const [pid, ...sig] = owner.split(" ");
59
+ if (processLiveness(Number(pid), sig.join(" ") || undefined) !== "gone") return undefined;
60
+ rmSync(file, { force: true });
61
+ }
62
+ }
63
+ return undefined;
64
+ }
65
+
66
+ export interface SyncOptions {
67
+ /** Set (or replace) the remote first: a person's choice. */
68
+ remote?: string;
69
+ visibility?: (url: string) => Promise<Visibility>;
70
+ /** A stopping hub aborts: the running git step is killed and the sync ends without touching the store. */
71
+ signal?: AbortSignal;
72
+ }
73
+
74
+ export async function syncMemory(store: MemoryStore, opts: SyncOptions = {}): Promise<SyncResult> {
75
+ const dir = store.dir;
76
+ const fail = (error: string): SyncResult => ({ ok: false, pushed: false, entries: 0, error });
77
+ if (opts.signal?.aborted) return fail("stopped");
78
+ const noRemote = "no sync remote: a person sets one with ahub memory sync --remote <private git url>";
79
+ if (!opts.remote && !existsSync(join(dir, ".git"))) return fail(noRemote); // never set up: nothing to do, nothing created
80
+ const release = lock(dir);
81
+ if (!release) return fail("another sync is running on this machine");
82
+ try {
83
+ // Holding sync.lock, no other git runs in this repository: what a killed one left behind is stale.
84
+ rmSync(join(dir, ".git", "index.lock"), { force: true });
85
+ if (existsSync(join(dir, ".git", "MERGE_HEAD"))) await git(dir, ["merge", "--abort"]);
86
+ if (!existsSync(join(dir, ".git"))) {
87
+ const init = await git(dir, ["init", "-q"], opts.signal);
88
+ if (init.code !== 0) return fail(`git init failed: ${init.err.slice(0, 200)}`);
89
+ await git(dir, ["symbolic-ref", "HEAD", "refs/heads/main"], opts.signal);
90
+ }
91
+ if (!existsSync(join(dir, ".gitignore")) || readFileSync(join(dir, ".gitignore"), "utf8") !== GITIGNORE) writeFileSync(join(dir, ".gitignore"), GITIGNORE, { mode: 0o600 });
92
+ if (opts.remote) {
93
+ await git(dir, ["remote", "remove", "origin"], opts.signal);
94
+ const add = await git(dir, ["remote", "add", "origin", opts.remote], opts.signal);
95
+ if (add.code !== 0) return fail(`the remote could not be set: ${add.err.slice(0, 200)}`);
96
+ }
97
+ const url = (await git(dir, ["remote", "get-url", "origin"], opts.signal)).out;
98
+ if (opts.signal?.aborted) return fail("stopped");
99
+ if (!url) return fail(noRemote);
100
+ // Long-term entries hold note text: a public remote would publish them.
101
+ const visibility = await (opts.visibility ?? remoteVisibility)(url);
102
+ if (visibility === "public") return fail("the sync remote is public; hub memory syncs only to a private remote");
103
+ const warning = visibility === "unknown" ? "the sync remote's visibility is unknown; keep it private" : undefined;
104
+
105
+ const own = `log/${store.machine.id}.jsonl`;
106
+ const who = ["-c", "user.name=agent-hub", "-c", `user.email=agent-hub@${store.machine.id}.invalid`];
107
+ const staged = await git(dir, ["add", "--", ".gitignore", ...(existsSync(join(dir, own)) ? [own] : [])], opts.signal);
108
+ if (staged.code !== 0) return fail(`git add failed: ${staged.err.slice(0, 200)}`); // never a push that left this machine's entries out
109
+ if ((await git(dir, ["diff", "--cached", "--quiet"], opts.signal)).code === 1) {
110
+ const commit = await git(dir, [...who, "commit", "-q", "--no-verify", "-m", `memory: ${store.machine.label}`], opts.signal);
111
+ if (commit.code !== 0) return fail(`commit failed: ${commit.err.slice(0, 200)}`);
112
+ }
113
+ const pull = async (): Promise<string | undefined> => {
114
+ const fetch = await git(dir, ["fetch", "-q", "origin"], opts.signal);
115
+ if (fetch.code !== 0) return `fetch failed: ${fetch.err.slice(0, 200)}`;
116
+ if ((await git(dir, ["rev-parse", "--verify", "-q", "refs/remotes/origin/main"], opts.signal)).code !== 0) return undefined; // an empty remote
117
+ if ((await git(dir, ["rev-parse", "--verify", "-q", "HEAD"], opts.signal)).code !== 0) {
118
+ const checkout = await git(dir, ["checkout", "-q", "-B", "main", "refs/remotes/origin/main"], opts.signal);
119
+ return checkout.code === 0 ? undefined : `checkout failed: ${checkout.err.slice(0, 200)}`;
120
+ }
121
+ // Each machine writes only its own segment, and every machine starts its own history: unrelated, never conflicting.
122
+ const merge = await git(dir, [...who, "merge", "-q", "--no-edit", "--no-verify", "--allow-unrelated-histories", "refs/remotes/origin/main"], opts.signal);
123
+ if (merge.code !== 0) { await git(dir, ["merge", "--abort"]); return `merge failed: ${merge.err.slice(0, 200)}`; } // cleanup runs even when stopping
124
+ return undefined;
125
+ };
126
+ const pulled = await pull();
127
+ if (pulled) return fail(pulled);
128
+ let pushed = false;
129
+ if ((await git(dir, ["rev-parse", "--verify", "-q", "HEAD"], opts.signal)).code === 0) {
130
+ let push = await git(dir, ["push", "-q", "origin", "HEAD:refs/heads/main"], opts.signal);
131
+ if (push.code !== 0) { // another machine pushed in between: take its history, then push once more
132
+ const again = await pull();
133
+ if (again) return fail(again);
134
+ push = await git(dir, ["push", "-q", "origin", "HEAD:refs/heads/main"], opts.signal);
135
+ }
136
+ if (push.code !== 0) return fail(`push failed: ${push.err.slice(0, 200)}`);
137
+ pushed = true;
138
+ }
139
+ if (opts.signal?.aborted) return fail("stopped");
140
+ store.refresh();
141
+ return { ok: true, pushed, entries: store.counts().long, ...(warning ? { warning } : {}) };
142
+ } catch (e) {
143
+ return fail((e as Error).message.slice(0, 200));
144
+ } finally { release(); }
145
+ }
package/src/models/mlx.ts CHANGED
@@ -73,6 +73,7 @@ export interface ProcessSignature {
73
73
  const DEFAULT_RUNTIME_DIR = join(homedir(), ".agenthub", "runtimes", "mlx");
74
74
  const DEFAULT_MODEL_PATH = join(homedir(), ".agenthub", "models", "qwen3-8b-mlx");
75
75
  const OWNER_FILE = "owner.json";
76
+ const GENERATION_DB_BUSY_TIMEOUT_MS = 100;
76
77
  const GENERATION_DB = "generation-slots.db";
77
78
 
78
79
  const ownerPath = (runtimeDir: string) => join(runtimeDir, OWNER_FILE);
@@ -185,7 +186,7 @@ function generationDatabase(runtimeDir: string): Database {
185
186
  mkdirSync(runtimeDir, { recursive: true, mode: 0o700 });
186
187
  const db = new Database(generationDbPath(runtimeDir), { create: true });
187
188
  try {
188
- db.run("PRAGMA busy_timeout = 100");
189
+ db.run(`PRAGMA busy_timeout = ${GENERATION_DB_BUSY_TIMEOUT_MS}`);
189
190
  db.run(`CREATE TABLE IF NOT EXISTS generation_slots (
190
191
  slot INTEGER PRIMARY KEY,
191
192
  token TEXT NOT NULL,
@@ -0,0 +1,10 @@
1
+ # Character data
2
+
3
+ The project owner approved the STAIx text-frame reduction for public distribution
4
+ and selected all four existing alternative concepts on2026-10-13. This repository's
5
+ MIT license applies to these hand-authored text frames. The STAIx sprite sheet
6
+ and generated concept image bytes are not included or loaded.
7
+
8
+ The alternatives gyul, lantern, cursor-cat and boxbot were reduced from the
9
+ owner's Higgsfield concept images described in issue280, generated on2026-10-10
10
+ with Recraft V4.1. No new generation or provider account operation was needed.