the-campfire 0.1.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.
Files changed (180) hide show
  1. package/bin/cli.ts +182 -0
  2. package/dist/apple-touch-icon.png +0 -0
  3. package/dist/assets/MermaidDiagram-By1J3Whj.js +2 -0
  4. package/dist/assets/_basePickBy-Dq6gnD-p.js +1 -0
  5. package/dist/assets/_baseUniq-SpgN6cGc.js +1 -0
  6. package/dist/assets/arc-D9SEA8dX.js +1 -0
  7. package/dist/assets/architectureDiagram-VXUJARFQ-CoTcwLeR.js +36 -0
  8. package/dist/assets/blockDiagram-VD42YOAC-Dvc63voV.js +122 -0
  9. package/dist/assets/c4Diagram-YG6GDRKO-tuDcG4_4.js +10 -0
  10. package/dist/assets/channel-CxhcHn7S.js +1 -0
  11. package/dist/assets/chunk-4BX2VUAB-x1hChYu9.js +1 -0
  12. package/dist/assets/chunk-55IACEB6-BX6HWnWP.js +1 -0
  13. package/dist/assets/chunk-B4BG7PRW-B4IlF03G.js +165 -0
  14. package/dist/assets/chunk-DI55MBZ5-TpYb5rLg.js +220 -0
  15. package/dist/assets/chunk-FMBD7UC4-DZX4LUPx.js +15 -0
  16. package/dist/assets/chunk-QN33PNHL-CuQs0NDY.js +1 -0
  17. package/dist/assets/chunk-QZHKN3VN-CygWwVx8.js +1 -0
  18. package/dist/assets/chunk-TZMSLE5B-CtI-IdKV.js +1 -0
  19. package/dist/assets/classDiagram-2ON5EDUG-BM0LptJF.js +1 -0
  20. package/dist/assets/classDiagram-v2-WZHVMYZB-BM0LptJF.js +1 -0
  21. package/dist/assets/clone-C6kt1rE7.js +1 -0
  22. package/dist/assets/cose-bilkent-S5V4N54A-PdySs991.js +1 -0
  23. package/dist/assets/cytoscape.esm-BQaXIfA_.js +331 -0
  24. package/dist/assets/dagre-6UL2VRFP-J0SxEiEu.js +4 -0
  25. package/dist/assets/defaultLocale-DX6XiGOO.js +1 -0
  26. package/dist/assets/diagram-PSM6KHXK-CW0HLXaH.js +24 -0
  27. package/dist/assets/diagram-QEK2KX5R-DADHF-k4.js +43 -0
  28. package/dist/assets/diagram-S2PKOQOG-9S0-PC3S.js +24 -0
  29. package/dist/assets/erDiagram-Q2GNP2WA-BEHAiY_q.js +60 -0
  30. package/dist/assets/flowDiagram-NV44I4VS-Cu6n9Xnd.js +162 -0
  31. package/dist/assets/ganttDiagram-JELNMOA3-Cr_BHryu.js +267 -0
  32. package/dist/assets/gitGraphDiagram-V2S2FVAM-D5pT35v5.js +65 -0
  33. package/dist/assets/graph-DKRbrxcs.js +1 -0
  34. package/dist/assets/index-Byjg9zgI.css +32 -0
  35. package/dist/assets/index-Cwj3m0hT.js +284 -0
  36. package/dist/assets/infoDiagram-HS3SLOUP-BaJlrgKV.js +2 -0
  37. package/dist/assets/init-Gi6I4Gst.js +1 -0
  38. package/dist/assets/journeyDiagram-XKPGCS4Q-CXC4-DJv.js +139 -0
  39. package/dist/assets/kanban-definition-3W4ZIXB7-BtHBu9cx.js +89 -0
  40. package/dist/assets/katex-BlHpptmG.js +261 -0
  41. package/dist/assets/layout-DnxSwLDJ.js +1 -0
  42. package/dist/assets/linear-CWbbIT8l.js +1 -0
  43. package/dist/assets/mermaid.core-DXn1cFAK.js +256 -0
  44. package/dist/assets/mindmap-definition-VGOIOE7T-BR8br1hs.js +68 -0
  45. package/dist/assets/ordinal-BENe2yWM.js +1 -0
  46. package/dist/assets/pieDiagram-ADFJNKIX-B7RmXV7I.js +30 -0
  47. package/dist/assets/quadrantDiagram-AYHSOK5B-DK0FhyV_.js +7 -0
  48. package/dist/assets/requirementDiagram-UZGBJVZJ-3zAGxSpL.js +64 -0
  49. package/dist/assets/sankeyDiagram-TZEHDZUN-DZppKtjc.js +10 -0
  50. package/dist/assets/sequenceDiagram-WL72ISMW-D1x3r3wV.js +145 -0
  51. package/dist/assets/stateDiagram-FKZM4ZOC-2ByD08Vq.js +1 -0
  52. package/dist/assets/stateDiagram-v2-4FDKWEC3-Vm1cYjgV.js +1 -0
  53. package/dist/assets/timeline-definition-IT6M3QCI-l5S35biR.js +61 -0
  54. package/dist/assets/treemap-GDKQZRPO-DIPadPiF.js +162 -0
  55. package/dist/assets/xychartDiagram-PRI3JC2R-9GUvwS2b.js +7 -0
  56. package/dist/favicon.svg +3 -0
  57. package/dist/fonts/Geist-Variable.woff2 +0 -0
  58. package/dist/fonts/GeistMono-Variable.woff2 +0 -0
  59. package/dist/icon-192.png +0 -0
  60. package/dist/icon-512.png +0 -0
  61. package/dist/index.html +33 -0
  62. package/dist/logo-codex.svg +14 -0
  63. package/dist/logo.svg +3 -0
  64. package/dist/manifest.json +26 -0
  65. package/dist/og-image.png +0 -0
  66. package/dist/sw.js +130 -0
  67. package/package.json +92 -0
  68. package/server/ADAPTERS.md +121 -0
  69. package/server/adapter-registry-types.ts +28 -0
  70. package/server/adapter-registry.ts +293 -0
  71. package/server/adapter-types.ts +51 -0
  72. package/server/agent-executor.ts +249 -0
  73. package/server/agent-mcp-bridge.ts +148 -0
  74. package/server/agent-mcp-stdio.ts +171 -0
  75. package/server/agent-mcp-tools.ts +76 -0
  76. package/server/agent-store.ts +183 -0
  77. package/server/agent-types.ts +79 -0
  78. package/server/aider-adapter.ts +525 -0
  79. package/server/auth.ts +153 -0
  80. package/server/auto-namer.ts +132 -0
  81. package/server/capability-discovery.ts +431 -0
  82. package/server/claude-container-auth.ts +40 -0
  83. package/server/claude-stdio-adapter.ts +452 -0
  84. package/server/clawhub-export.ts +218 -0
  85. package/server/cli-launcher.ts +1613 -0
  86. package/server/codex-adapter.ts +1987 -0
  87. package/server/codex-container-auth.ts +40 -0
  88. package/server/codex-home.ts +26 -0
  89. package/server/collective-intelligence.ts +401 -0
  90. package/server/commands-discovery.ts +160 -0
  91. package/server/constants.ts +5 -0
  92. package/server/container-manager.ts +328 -0
  93. package/server/cron-scheduler.ts +243 -0
  94. package/server/cron-store.ts +149 -0
  95. package/server/cron-types.ts +63 -0
  96. package/server/deliberation-engine.ts +323 -0
  97. package/server/embedding.ts +89 -0
  98. package/server/env-manager.ts +146 -0
  99. package/server/environment-detector.ts +106 -0
  100. package/server/environment-rules.ts +106 -0
  101. package/server/gallery-store.ts +216 -0
  102. package/server/gallery-types.ts +52 -0
  103. package/server/gallery-votes.ts +101 -0
  104. package/server/git-utils.ts +384 -0
  105. package/server/github-pr.ts +379 -0
  106. package/server/goose-adapter.ts +929 -0
  107. package/server/image-pull-manager.ts +144 -0
  108. package/server/index.ts +363 -0
  109. package/server/linear-project-manager.ts +78 -0
  110. package/server/moltbook-client.ts +160 -0
  111. package/server/openclaw-adapter.ts +916 -0
  112. package/server/openclaw-channel/channel.ts +188 -0
  113. package/server/openclaw-channel/index.ts +68 -0
  114. package/server/openclaw-channel/package.json +19 -0
  115. package/server/openclaw-channel/types.ts +93 -0
  116. package/server/opencode-adapter.ts +951 -0
  117. package/server/openhands-adapter.ts +775 -0
  118. package/server/orchestrator-executor.ts +184 -0
  119. package/server/orchestrator-store.ts +106 -0
  120. package/server/orchestrator-types.ts +86 -0
  121. package/server/path-resolver.ts +168 -0
  122. package/server/pr-poller.ts +162 -0
  123. package/server/proactive-keepalive.ts +121 -0
  124. package/server/prompt-manager.ts +103 -0
  125. package/server/protocol-monitor.ts +197 -0
  126. package/server/race-controller.ts +295 -0
  127. package/server/race-store.ts +82 -0
  128. package/server/recorder.ts +359 -0
  129. package/server/recording-hub/compat-validator.ts +122 -0
  130. package/server/recording-hub/diagnostics.ts +289 -0
  131. package/server/recording-hub/hub-routes.ts +146 -0
  132. package/server/recording-hub/hub-store.ts +224 -0
  133. package/server/replay.ts +78 -0
  134. package/server/routes/adapter-routes.ts +31 -0
  135. package/server/routes/agent-mcp-routes.ts +35 -0
  136. package/server/routes/agent-routes.ts +185 -0
  137. package/server/routes/auth-routes.ts +101 -0
  138. package/server/routes/ci-routes.ts +165 -0
  139. package/server/routes/commands-routes.ts +94 -0
  140. package/server/routes/cron-routes.ts +100 -0
  141. package/server/routes/env-routes.ts +50 -0
  142. package/server/routes/folder-routes.ts +56 -0
  143. package/server/routes/fs-routes.ts +403 -0
  144. package/server/routes/gallery-routes.ts +410 -0
  145. package/server/routes/git-routes.ts +108 -0
  146. package/server/routes/index.ts +62 -0
  147. package/server/routes/linear-routes.ts +254 -0
  148. package/server/routes/monitor-routes.ts +11 -0
  149. package/server/routes/orchestrator-routes.ts +210 -0
  150. package/server/routes/prompt-routes.ts +47 -0
  151. package/server/routes/race-routes.ts +103 -0
  152. package/server/routes/recording-routes.ts +101 -0
  153. package/server/routes/route-deps.ts +32 -0
  154. package/server/routes/session-routes.ts +560 -0
  155. package/server/routes/settings-routes.ts +108 -0
  156. package/server/routes/skills-routes.ts +52 -0
  157. package/server/routes/system-routes.ts +243 -0
  158. package/server/routes/webhook-routes.ts +135 -0
  159. package/server/routes.ts +44 -0
  160. package/server/security-middleware.ts +100 -0
  161. package/server/semantic-memory.ts +389 -0
  162. package/server/service.ts +733 -0
  163. package/server/session-folders.ts +107 -0
  164. package/server/session-git-info.ts +89 -0
  165. package/server/session-linear-issues.ts +94 -0
  166. package/server/session-names.ts +67 -0
  167. package/server/session-store.ts +169 -0
  168. package/server/session-types.ts +415 -0
  169. package/server/settings-manager.ts +120 -0
  170. package/server/shared-context.ts +401 -0
  171. package/server/skills-manager.ts +250 -0
  172. package/server/sub-session-manager.ts +263 -0
  173. package/server/terminal-manager.ts +185 -0
  174. package/server/update-checker.ts +114 -0
  175. package/server/usage-limits.ts +192 -0
  176. package/server/webhook-manager.ts +228 -0
  177. package/server/webhook-store.ts +168 -0
  178. package/server/webhook-types.ts +66 -0
  179. package/server/worktree-tracker.ts +84 -0
  180. package/server/ws-bridge.ts +2322 -0
@@ -0,0 +1,78 @@
1
+ /**
2
+ * Replay utility for session recordings.
3
+ *
4
+ * Loads JSONL recording files and replays them through WsBridge or CodexAdapter
5
+ * to produce browser messages. Used in tests to validate that message processing
6
+ * produces the expected output from recorded real sessions.
7
+ */
8
+
9
+ import { readFileSync } from "node:fs";
10
+ import type { RecordingHeader, RecordingEntry } from "./recorder.js";
11
+
12
+ // ─── Types ───────────────────────────────────────────────────────────────────
13
+
14
+ export interface Recording {
15
+ header: RecordingHeader;
16
+ entries: RecordingEntry[];
17
+ }
18
+
19
+ // ─── Loading ─────────────────────────────────────────────────────────────────
20
+
21
+ /**
22
+ * Load a JSONL recording file. Returns the parsed header and all entries.
23
+ * Throws if the file is missing a valid header.
24
+ */
25
+ export function loadRecording(path: string): Recording {
26
+ const content = readFileSync(path, "utf-8");
27
+ const lines = content.split("\n").filter((l) => l.trim());
28
+
29
+ if (lines.length === 0) {
30
+ throw new Error("Recording file is empty");
31
+ }
32
+
33
+ const header = JSON.parse(lines[0]) as RecordingHeader;
34
+ if (!header._header || header.version !== 1) {
35
+ throw new Error("Invalid recording header: missing _header or version !== 1");
36
+ }
37
+
38
+ const entries: RecordingEntry[] = [];
39
+ for (let i = 1; i < lines.length; i++) {
40
+ try {
41
+ entries.push(JSON.parse(lines[i]) as RecordingEntry);
42
+ } catch {
43
+ // Skip malformed lines — recording might have been truncated
44
+ }
45
+ }
46
+
47
+ return { header, entries };
48
+ }
49
+
50
+ // ─── Replay helpers ──────────────────────────────────────────────────────────
51
+
52
+ /**
53
+ * Filter recording entries by direction and channel.
54
+ * Useful for extracting only incoming CLI messages for replay.
55
+ */
56
+ export function filterEntries(
57
+ entries: RecordingEntry[],
58
+ dir: "in" | "out",
59
+ channel: "cli" | "browser",
60
+ ): RecordingEntry[] {
61
+ return entries.filter((e) => e.dir === dir && e.ch === channel);
62
+ }
63
+
64
+ /**
65
+ * Get all outgoing browser messages from a recording.
66
+ * These represent what the server actually sent to browsers during the recorded session.
67
+ */
68
+ export function getExpectedBrowserMessages(entries: RecordingEntry[]): string[] {
69
+ return filterEntries(entries, "out", "browser").map((e) => e.raw);
70
+ }
71
+
72
+ /**
73
+ * Get all incoming CLI messages from a recording.
74
+ * These are the raw NDJSON/JSON-RPC lines received from the backend.
75
+ */
76
+ export function getIncomingCLIMessages(entries: RecordingEntry[]): string[] {
77
+ return filterEntries(entries, "in", "cli").map((e) => e.raw);
78
+ }
@@ -0,0 +1,31 @@
1
+ import type { Hono } from "hono";
2
+ import type { RouteDeps } from "./route-deps.js";
3
+
4
+ export function registerAdapterRoutes(api: Hono, deps: RouteDeps): void {
5
+ const { adapterRegistry } = deps;
6
+
7
+ api.get("/adapters", (c) => {
8
+ if (!adapterRegistry) return c.json([]);
9
+ return c.json(adapterRegistry.listInstalled());
10
+ });
11
+
12
+ api.post("/adapters/install", async (c) => {
13
+ if (!adapterRegistry) return c.json({ error: "Adapter registry not available" }, 500);
14
+ const { npmPackage } = await c.req.json() as { npmPackage: string };
15
+ if (!npmPackage) return c.json({ error: "npmPackage is required" }, 400);
16
+ try {
17
+ const adapter = await adapterRegistry.install(npmPackage);
18
+ return c.json(adapter, 201);
19
+ } catch (err) {
20
+ return c.json({ error: err instanceof Error ? err.message : String(err) }, 400);
21
+ }
22
+ });
23
+
24
+ api.delete("/adapters/:name", (c) => {
25
+ if (!adapterRegistry) return c.json({ error: "Adapter registry not available" }, 500);
26
+ const name = c.req.param("name");
27
+ const removed = adapterRegistry.uninstall(name);
28
+ if (!removed) return c.json({ error: "Adapter not found" }, 404);
29
+ return c.json({ ok: true });
30
+ });
31
+ }
@@ -0,0 +1,35 @@
1
+ import type { Hono } from "hono";
2
+ import type { RouteDeps } from "./route-deps.js";
3
+ import { generateAgentToolDefinitions } from "../agent-mcp-tools.js";
4
+
5
+ function isInternalAuthorized(c: { req: { header: (name: string) => string | undefined } }): boolean {
6
+ const expected = process.env.CAMPFIRE_INTERNAL_AGENT_MCP_TOKEN;
7
+ if (!expected) return false;
8
+ const header = c.req.header("Authorization") || "";
9
+ return header === `Bearer ${expected}`;
10
+ }
11
+
12
+ export function registerAgentMcpRoutes(api: Hono, deps: RouteDeps): void {
13
+ api.get("/internal/agent-mcp/tools", (c) => {
14
+ if (!isInternalAuthorized(c)) return c.json({ error: "Unauthorized" }, 401);
15
+ return c.json({ tools: generateAgentToolDefinitions() });
16
+ });
17
+
18
+ api.post("/internal/agent-mcp/call", async (c) => {
19
+ if (!isInternalAuthorized(c)) return c.json({ error: "Unauthorized" }, 401);
20
+ if (!deps.agentMcpBridge) return c.json({ error: "Agent MCP bridge is not configured" }, 503);
21
+
22
+ const body = await c.req.json<{
23
+ parentSessionId?: string;
24
+ toolName?: string;
25
+ input?: Record<string, unknown>;
26
+ }>().catch(() => ({} as { parentSessionId?: string; toolName?: string; input?: Record<string, unknown> }));
27
+
28
+ if (!body.parentSessionId || !body.toolName) {
29
+ return c.json({ error: "parentSessionId and toolName are required" }, 400);
30
+ }
31
+
32
+ const result = await deps.agentMcpBridge.callTool(body.parentSessionId, body.toolName, body.input ?? {});
33
+ return c.json(result);
34
+ });
35
+ }
@@ -0,0 +1,185 @@
1
+ import type { Hono } from "hono";
2
+ import type { RouteDeps } from "./route-deps.js";
3
+ import * as agentStore from "../agent-store.js";
4
+
5
+ export function registerAgentRoutes(api: Hono, deps: RouteDeps): void {
6
+ const { agentExecutor } = deps;
7
+
8
+ // ─── List all agents (enriched with runtime state) ──────────────────────
9
+ api.get("/agents", (c) => {
10
+ const agents = agentStore.listAgents();
11
+ const enriched = agents.map((a) => ({
12
+ ...a,
13
+ isRunning: agentExecutor?.isRunning(a.id) ?? false,
14
+ nextRunAt: agentExecutor?.getNextRunTime(a.id)?.getTime() ?? null,
15
+ }));
16
+ return c.json(enriched);
17
+ });
18
+
19
+ // ─── Get single agent ───────────────────────────────────────────────────
20
+ api.get("/agents/:id", (c) => {
21
+ const agent = agentStore.getAgent(c.req.param("id"));
22
+ if (!agent) return c.json({ error: "Agent not found" }, 404);
23
+ return c.json({
24
+ ...agent,
25
+ isRunning: agentExecutor?.isRunning(agent.id) ?? false,
26
+ nextRunAt: agentExecutor?.getNextRunTime(agent.id)?.getTime() ?? null,
27
+ });
28
+ });
29
+
30
+ // ─── Create agent ──────────────────────────────────────────────────────
31
+ api.post("/agents", async (c) => {
32
+ const body = await c.req.json().catch(() => ({}));
33
+ try {
34
+ const agent = agentStore.createAgent({
35
+ name: body.name || "",
36
+ description: body.description || "",
37
+ icon: body.icon,
38
+ backendType: body.backendType || "claude",
39
+ model: body.model || "",
40
+ permissionMode: body.permissionMode || "bypassPermissions",
41
+ cwd: body.cwd || "",
42
+ prompt: body.prompt || "",
43
+ triggers: body.triggers,
44
+ envSlug: body.envSlug,
45
+ env: body.env,
46
+ mcpServers: body.mcpServers,
47
+ codexInternetAccess: body.codexInternetAccess,
48
+ enabled: body.enabled ?? true,
49
+ });
50
+ if (agent.enabled && agent.triggers?.schedule?.enabled) {
51
+ agentExecutor?.scheduleAgent(agent);
52
+ }
53
+ return c.json(agent, 201);
54
+ } catch (e: unknown) {
55
+ return c.json({ error: e instanceof Error ? e.message : "Unknown error" }, 400);
56
+ }
57
+ });
58
+
59
+ // ─── Update agent ──────────────────────────────────────────────────────
60
+ api.put("/agents/:id", async (c) => {
61
+ const id = c.req.param("id");
62
+ const body = await c.req.json().catch(() => ({}));
63
+ try {
64
+ const EDITABLE = [
65
+ "name", "description", "icon", "backendType", "model", "permissionMode",
66
+ "cwd", "prompt", "triggers", "envSlug", "env", "mcpServers",
67
+ "codexInternetAccess", "enabled",
68
+ ] as const;
69
+ const allowed: Record<string, unknown> = {};
70
+ for (const key of EDITABLE) {
71
+ if (key in body) allowed[key] = body[key];
72
+ }
73
+ const agent = agentStore.updateAgent(id, allowed);
74
+ if (!agent) return c.json({ error: "Agent not found" }, 404);
75
+
76
+ // Reschedule if ID changed or schedule updated
77
+ if (agent.id !== id) agentExecutor?.unscheduleAgent(id);
78
+ if (agent.enabled && agent.triggers?.schedule?.enabled) {
79
+ agentExecutor?.scheduleAgent(agent);
80
+ } else {
81
+ agentExecutor?.unscheduleAgent(agent.id);
82
+ }
83
+ return c.json(agent);
84
+ } catch (e: unknown) {
85
+ return c.json({ error: e instanceof Error ? e.message : "Unknown error" }, 400);
86
+ }
87
+ });
88
+
89
+ // ─── Delete agent ──────────────────────────────────────────────────────
90
+ api.delete("/agents/:id", (c) => {
91
+ const id = c.req.param("id");
92
+ agentExecutor?.unscheduleAgent(id);
93
+ const deleted = agentStore.deleteAgent(id);
94
+ if (!deleted) return c.json({ error: "Agent not found" }, 404);
95
+ return c.json({ ok: true });
96
+ });
97
+
98
+ // ─── Toggle enabled ────────────────────────────────────────────────────
99
+ api.post("/agents/:id/toggle", (c) => {
100
+ const id = c.req.param("id");
101
+ const agent = agentStore.getAgent(id);
102
+ if (!agent) return c.json({ error: "Agent not found" }, 404);
103
+ const updated = agentStore.updateAgent(id, { enabled: !agent.enabled });
104
+ if (updated?.enabled && updated.triggers?.schedule?.enabled) {
105
+ agentExecutor?.scheduleAgent(updated);
106
+ } else {
107
+ agentExecutor?.unscheduleAgent(id);
108
+ }
109
+ return c.json(updated);
110
+ });
111
+
112
+ // ─── Run agent manually ────────────────────────────────────────────────
113
+ api.post("/agents/:id/run", async (c) => {
114
+ const id = c.req.param("id");
115
+ const agent = agentStore.getAgent(id);
116
+ if (!agent) return c.json({ error: "Agent not found" }, 404);
117
+ const body = await c.req.json().catch(() => ({}));
118
+ try {
119
+ const execution = await agentExecutor?.executeAgent(id, {
120
+ input: body.input,
121
+ trigger: "manual",
122
+ force: true,
123
+ });
124
+ return c.json({
125
+ ok: true,
126
+ executionId: execution?.executionId,
127
+ sessionId: execution?.sessionId,
128
+ });
129
+ } catch (e: unknown) {
130
+ return c.json({ error: e instanceof Error ? e.message : "Unknown error" }, 500);
131
+ }
132
+ });
133
+
134
+ // ─── Execution history ─────────────────────────────────────────────────
135
+ api.get("/agents/:id/executions", (c) => {
136
+ const id = c.req.param("id");
137
+ return c.json(agentStore.listExecutions(id));
138
+ });
139
+
140
+ // ─── Export agent (portable JSON) ──────────────────────────────────────
141
+ api.get("/agents/:id/export", (c) => {
142
+ const agent = agentStore.getAgent(c.req.param("id"));
143
+ if (!agent) return c.json({ error: "Agent not found" }, 404);
144
+ const { id: _id, createdAt: _ca, updatedAt: _ua, totalRuns: _tr,
145
+ consecutiveFailures: _cf, lastRunAt: _lr, lastSessionId: _ls,
146
+ ...exportData } = agent;
147
+ return c.json(exportData);
148
+ });
149
+
150
+ // ─── Import agent ──────────────────────────────────────────────────────
151
+ api.post("/agents/import", async (c) => {
152
+ const body = await c.req.json().catch(() => ({}));
153
+ try {
154
+ const agent = agentStore.createAgent({
155
+ ...body,
156
+ enabled: false,
157
+ });
158
+ return c.json(agent, 201);
159
+ } catch (e: unknown) {
160
+ return c.json({ error: e instanceof Error ? e.message : "Unknown error" }, 400);
161
+ }
162
+ });
163
+
164
+ // ─── Webhook trigger ───────────────────────────────────────────────────
165
+ api.post("/agents/:id/webhook", async (c) => {
166
+ const id = c.req.param("id");
167
+ const agent = agentStore.getAgent(id);
168
+ if (!agent) return c.json({ error: "Agent not found" }, 404);
169
+ if (!agent.triggers?.webhook?.enabled) return c.json({ error: "Webhook not enabled" }, 403);
170
+ const body = await c.req.json().catch(() => ({}));
171
+ try {
172
+ const execution = await agentExecutor?.executeAgent(id, {
173
+ input: typeof body.input === "string" ? body.input : JSON.stringify(body),
174
+ trigger: "webhook",
175
+ });
176
+ return c.json({
177
+ ok: true,
178
+ executionId: execution?.executionId,
179
+ sessionId: execution?.sessionId,
180
+ });
181
+ } catch (e: unknown) {
182
+ return c.json({ error: e instanceof Error ? e.message : "Unknown error" }, 500);
183
+ }
184
+ });
185
+ }
@@ -0,0 +1,101 @@
1
+ import type { Hono } from "hono";
2
+ import type { RouteDeps } from "./route-deps.js";
3
+ import {
4
+ isAuthEnabled,
5
+ login,
6
+ logout,
7
+ verifyToken,
8
+ getAuthStatus,
9
+ setPassword,
10
+ disableAuth,
11
+ } from "../auth.js";
12
+
13
+ export function registerAuthRoutes(api: Hono, _deps: RouteDeps): void {
14
+ // Check if auth is enabled and whether the user is logged in
15
+ api.get("/auth/status", (c) => {
16
+ const status = getAuthStatus();
17
+ const token = c.req.header("Authorization")?.replace("Bearer ", "")
18
+ || c.req.query("token");
19
+ const isLoggedIn = token ? verifyToken(token) : !status.enabled;
20
+ return c.json({ ...status, isLoggedIn });
21
+ });
22
+
23
+ // Login with password
24
+ api.post("/auth/login", async (c) => {
25
+ const body = await c.req.json<{ password?: string }>().catch(() => ({} as { password?: string }));
26
+ if (!body.password) {
27
+ return c.json({ error: "Password is required" }, 400);
28
+ }
29
+ const userAgent = c.req.header("User-Agent");
30
+ const token = login(body.password, userAgent);
31
+ if (!token) {
32
+ return c.json({ error: "Invalid password" }, 401);
33
+ }
34
+ return c.json({ token });
35
+ });
36
+
37
+ // Logout
38
+ api.post("/auth/logout", async (c) => {
39
+ const token = c.req.header("Authorization")?.replace("Bearer ", "");
40
+ if (token) {
41
+ logout(token);
42
+ }
43
+ return c.json({ ok: true });
44
+ });
45
+
46
+ // Set password (only when no password is set yet, or when authenticated)
47
+ api.post("/auth/setup", async (c) => {
48
+ const status = getAuthStatus();
49
+ // If auth is already enabled, require a valid token
50
+ if (status.enabled) {
51
+ const token = c.req.header("Authorization")?.replace("Bearer ", "");
52
+ if (!token || !verifyToken(token)) {
53
+ return c.json({ error: "Unauthorized" }, 401);
54
+ }
55
+ }
56
+
57
+ const body = await c.req.json<{ password?: string }>().catch(() => ({} as { password?: string }));
58
+ if (!body.password || body.password.length < 4) {
59
+ return c.json({ error: "Password must be at least 4 characters" }, 400);
60
+ }
61
+ setPassword(body.password);
62
+ return c.json({ ok: true, ...getAuthStatus() });
63
+ });
64
+
65
+ // Disable auth (requires valid token)
66
+ api.post("/auth/disable", async (c) => {
67
+ const token = c.req.header("Authorization")?.replace("Bearer ", "");
68
+ if (!token || !verifyToken(token)) {
69
+ return c.json({ error: "Unauthorized" }, 401);
70
+ }
71
+ disableAuth();
72
+ return c.json({ ok: true });
73
+ });
74
+ }
75
+
76
+ /**
77
+ * Middleware that checks auth for all /api/* routes except /api/auth/*
78
+ */
79
+ export function authMiddleware() {
80
+ return async (c: any, next: () => Promise<void>) => {
81
+ // Always allow auth routes
82
+ if (c.req.path.startsWith("/api/auth")) {
83
+ return next();
84
+ }
85
+
86
+ // If auth is not enabled, skip
87
+ if (!isAuthEnabled()) {
88
+ return next();
89
+ }
90
+
91
+ // Check token from Authorization header or query param
92
+ const token = c.req.header("Authorization")?.replace("Bearer ", "")
93
+ || c.req.query("token");
94
+
95
+ if (!token || !verifyToken(token)) {
96
+ return c.json({ error: "Unauthorized", authRequired: true }, 401);
97
+ }
98
+
99
+ return next();
100
+ };
101
+ }
@@ -0,0 +1,165 @@
1
+ import type { Hono } from "hono";
2
+ import type { RouteDeps } from "./route-deps.js";
3
+ import type { BackendType } from "../session-types.js";
4
+
5
+ export function registerCiRoutes(api: Hono, deps: RouteDeps): void {
6
+ const { wsBridge } = deps;
7
+
8
+ // ─── Collective Intelligence: Semantic Memory ──────────────────────────────
9
+ api.get("/sessions/:id/memory", async (c) => {
10
+ const { queryFragments, getConsolidatedKnowledge, getSessionFragments } = await import("../semantic-memory.js");
11
+ const sessionId = c.req.param("id");
12
+ const [fragments, consolidated] = await Promise.all([
13
+ getSessionFragments(sessionId),
14
+ getConsolidatedKnowledge(""), // cross-session consolidated
15
+ ]);
16
+ return c.json({ fragments, consolidated });
17
+ });
18
+
19
+ api.post("/sessions/:id/memory", async (c) => {
20
+ const { storeFragment } = await import("../semantic-memory.js");
21
+ const sessionId = c.req.param("id");
22
+ const body = await c.req.json() as { content: string; type: string; tags?: string[]; gitContext?: Record<string, unknown> };
23
+ if (!body.content) return c.json({ error: "content is required" }, 400);
24
+ const fragment = await storeFragment({
25
+ sessionId,
26
+ agentId: "human",
27
+ backendType: "claude",
28
+ type: (body.type as "observation" | "hypothesis" | "decision" | "pattern") ?? "observation",
29
+ content: body.content,
30
+ tags: body.tags ?? [],
31
+ gitContext: (body.gitContext as unknown as import("../semantic-memory.js").GitContext) ?? { branch: "unknown", files: [], repoRoot: "" },
32
+ });
33
+ return c.json({ fragment }, 201);
34
+ });
35
+
36
+ api.get("/sessions/:id/memory/query", async (c) => {
37
+ const { queryFragments } = await import("../semantic-memory.js");
38
+ const sessionId = c.req.param("id");
39
+ const q = c.req.query("q") ?? "";
40
+ const limit = parseInt(c.req.query("limit") ?? "10", 10);
41
+ const results = await queryFragments(q, { sessionId, limit });
42
+ return c.json({ results });
43
+ });
44
+
45
+ api.post("/sessions/:id/memory/consolidate", async (c) => {
46
+ const { consolidateSession } = await import("../semantic-memory.js");
47
+ const sessionId = c.req.param("id");
48
+ const session = wsBridge.getSession(sessionId);
49
+ const repoRoot = session?.state.cwd ?? "";
50
+ const consolidated = await consolidateSession(sessionId, repoRoot);
51
+ return c.json({ consolidated, count: consolidated.length });
52
+ });
53
+
54
+ api.get("/memory/global", async (c) => {
55
+ const { getConsolidatedKnowledge } = await import("../semantic-memory.js");
56
+ const tag = c.req.query("tag");
57
+ const knowledge = await getConsolidatedKnowledge("", tag);
58
+ return c.json({ knowledge });
59
+ });
60
+
61
+ // ─── Collective Intelligence: Deliberation ─────────────────────────────────
62
+ api.get("/sessions/:id/deliberations", (c) => {
63
+ const { deliberationEngine } = require("../deliberation-engine.js") as typeof import("../deliberation-engine.js");
64
+ const sessionId = c.req.param("id");
65
+ const active = deliberationEngine.getActiveProposals(sessionId);
66
+ return c.json({ active, resolved: [] });
67
+ });
68
+
69
+ api.get("/sessions/:id/deliberations/:proposalId", (c) => {
70
+ const { deliberationEngine } = require("../deliberation-engine.js") as typeof import("../deliberation-engine.js");
71
+ const proposalId = c.req.param("proposalId");
72
+ const proposal = deliberationEngine.getProposal(proposalId);
73
+ if (!proposal) return c.json({ error: "Not found" }, 404);
74
+ const responses = deliberationEngine.getResponses(proposalId);
75
+ return c.json({ proposal, responses });
76
+ });
77
+
78
+ api.post("/sessions/:id/deliberations/:proposalId/respond", async (c) => {
79
+ const { deliberationEngine } = await import("../deliberation-engine.js");
80
+ const proposalId = c.req.param("proposalId");
81
+ const body = await c.req.json() as { stance: string; reasoning: string; suggestedAlternative?: string; concerns?: string[] };
82
+ const response = deliberationEngine.addResponse({
83
+ proposalId,
84
+ responderId: "human",
85
+ responderType: "human",
86
+ timestamp: Date.now(),
87
+ stance: body.stance as "agree" | "disagree" | "suggest_alternative" | "abstain",
88
+ reasoning: body.reasoning,
89
+ suggestedAlternative: body.suggestedAlternative,
90
+ concerns: body.concerns,
91
+ });
92
+ if (!response) return c.json({ error: "Proposal not found or already resolved" }, 404);
93
+ return c.json({ response });
94
+ });
95
+
96
+ api.post("/sessions/:id/deliberations/:proposalId/resolve", async (c) => {
97
+ const { deliberationEngine } = await import("../deliberation-engine.js");
98
+ const proposalId = c.req.param("proposalId");
99
+ const resolution = deliberationEngine.resolveById(proposalId);
100
+ if (!resolution) return c.json({ error: "Proposal not found or already resolved" }, 404);
101
+ return c.json({ resolution });
102
+ });
103
+
104
+ // ─── Collective Intelligence: Capability Routing ───────────────────────────
105
+ api.post("/sessions/route-task", async (c) => {
106
+ const { capabilityDiscovery } = await import("../capability-discovery.js");
107
+ const body = await c.req.json() as { taskDescription: string; availableSessions?: string[]; constraints?: Record<string, unknown> };
108
+ if (!body.taskDescription) return c.json({ error: "taskDescription is required" }, 400);
109
+ const available = body.availableSessions ?? wsBridge.getConnectedSessionIds();
110
+ const result = await capabilityDiscovery.route({
111
+ taskDescription: body.taskDescription,
112
+ availableSessions: available,
113
+ constraints: body.constraints as Record<string, unknown>,
114
+ });
115
+ return c.json(result);
116
+ });
117
+
118
+ api.get("/capabilities", async (c) => {
119
+ const { capabilityDiscovery } = await import("../capability-discovery.js");
120
+ return c.json({ sessions: capabilityDiscovery.getAllCapabilities() });
121
+ });
122
+
123
+ api.get("/capabilities/history", async (c) => {
124
+ const { capabilityDiscovery } = await import("../capability-discovery.js");
125
+ const backendType = c.req.query("backendType") as BackendType | undefined;
126
+ const taskType = c.req.query("taskType");
127
+ const executions = capabilityDiscovery.getExecutionHistory({ backendType, taskType });
128
+ const total = executions.length;
129
+ const successes = executions.filter((e) => e.outcome === "success").length;
130
+ return c.json({ executions, successRate: total > 0 ? successes / total : 0, total });
131
+ });
132
+
133
+ api.post("/capabilities/feedback", async (c) => {
134
+ const { capabilityDiscovery } = await import("../capability-discovery.js");
135
+ const body = await c.req.json() as { sessionId: string; taskId: string; feedback: "positive" | "negative" | "neutral"; backendType?: string; taskDescription?: string };
136
+ capabilityDiscovery.recordFeedback(body.taskId, body.sessionId, (body.backendType as BackendType) ?? "claude", body.taskDescription ?? "", body.feedback);
137
+ return c.json({ ok: true });
138
+ });
139
+
140
+ // ─── Collective Intelligence: Shared Context ───────────────────────────────
141
+ api.get("/sessions/:id/context/stream", (c) => {
142
+ const { sharedContextManager } = require("../shared-context.js") as typeof import("../shared-context.js");
143
+ const sessionId = c.req.param("id");
144
+ const stream = sharedContextManager.get(sessionId);
145
+ return c.json({ fragments: stream?.getAllFragments() ?? [] });
146
+ });
147
+
148
+ api.get("/sessions/:id/context/consensus", (c) => {
149
+ const { sharedContextManager } = require("../shared-context.js") as typeof import("../shared-context.js");
150
+ const sessionId = c.req.param("id");
151
+ const stream = sharedContextManager.get(sessionId);
152
+ if (!stream) return c.json({ error: "No active context stream for this session" }, 404);
153
+ return c.json(stream.getConsensusState());
154
+ });
155
+
156
+ api.get("/sessions/:id/context/thread/:fragmentId", (c) => {
157
+ const { sharedContextManager } = require("../shared-context.js") as typeof import("../shared-context.js");
158
+ const sessionId = c.req.param("id");
159
+ const fragmentId = c.req.param("fragmentId");
160
+ const stream = sharedContextManager.get(sessionId);
161
+ if (!stream) return c.json({ error: "No active context stream for this session" }, 404);
162
+ const thread = stream.getThread(fragmentId);
163
+ return c.json({ thread });
164
+ });
165
+ }