@sapiom/harness 0.0.0 → 0.1.1

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 (246) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +44 -43
  3. package/dist/cli/auth.d.ts +26 -0
  4. package/dist/cli/auth.d.ts.map +1 -0
  5. package/dist/cli/auth.js +39 -0
  6. package/dist/cli/auth.js.map +1 -0
  7. package/dist/cli/bin.d.ts +3 -0
  8. package/dist/cli/bin.d.ts.map +1 -0
  9. package/dist/cli/bin.js +163 -0
  10. package/dist/cli/bin.js.map +1 -0
  11. package/dist/cli/consent.d.ts +15 -0
  12. package/dist/cli/consent.d.ts.map +1 -0
  13. package/dist/cli/consent.js +81 -0
  14. package/dist/cli/consent.js.map +1 -0
  15. package/dist/cli/doctor.d.ts +27 -0
  16. package/dist/cli/doctor.d.ts.map +1 -0
  17. package/dist/cli/doctor.js +74 -0
  18. package/dist/cli/doctor.js.map +1 -0
  19. package/dist/cli/machine-id.d.ts +19 -0
  20. package/dist/cli/machine-id.d.ts.map +1 -0
  21. package/dist/cli/machine-id.js +75 -0
  22. package/dist/cli/machine-id.js.map +1 -0
  23. package/dist/cli/paths.d.ts +3 -0
  24. package/dist/cli/paths.d.ts.map +1 -0
  25. package/dist/cli/paths.js +11 -0
  26. package/dist/cli/paths.js.map +1 -0
  27. package/dist/cli/settings.d.ts +26 -0
  28. package/dist/cli/settings.d.ts.map +1 -0
  29. package/dist/cli/settings.js +123 -0
  30. package/dist/cli/settings.js.map +1 -0
  31. package/dist/core/adapters/claude-code.d.ts +41 -0
  32. package/dist/core/adapters/claude-code.d.ts.map +1 -0
  33. package/dist/core/adapters/claude-code.js +238 -0
  34. package/dist/core/adapters/claude-code.js.map +1 -0
  35. package/dist/core/adapters/codex.d.ts +59 -0
  36. package/dist/core/adapters/codex.d.ts.map +1 -0
  37. package/dist/core/adapters/codex.js +319 -0
  38. package/dist/core/adapters/codex.js.map +1 -0
  39. package/dist/core/canvas-body.d.ts +45 -0
  40. package/dist/core/canvas-body.d.ts.map +1 -0
  41. package/dist/core/canvas-body.js +94 -0
  42. package/dist/core/canvas-body.js.map +1 -0
  43. package/dist/core/canvas-cache.d.ts +23 -0
  44. package/dist/core/canvas-cache.d.ts.map +1 -0
  45. package/dist/core/canvas-cache.js +64 -0
  46. package/dist/core/canvas-cache.js.map +1 -0
  47. package/dist/core/canvas-enrich.d.ts +106 -0
  48. package/dist/core/canvas-enrich.d.ts.map +1 -0
  49. package/dist/core/canvas-enrich.js +178 -0
  50. package/dist/core/canvas-enrich.js.map +1 -0
  51. package/dist/core/canvas-enrichment.d.ts +144 -0
  52. package/dist/core/canvas-enrichment.d.ts.map +1 -0
  53. package/dist/core/canvas-enrichment.js +192 -0
  54. package/dist/core/canvas-enrichment.js.map +1 -0
  55. package/dist/core/canvas-graph.d.ts +71 -0
  56. package/dist/core/canvas-graph.d.ts.map +1 -0
  57. package/dist/core/canvas-graph.js +111 -0
  58. package/dist/core/canvas-graph.js.map +1 -0
  59. package/dist/core/canvas-index-classify.d.ts +41 -0
  60. package/dist/core/canvas-index-classify.d.ts.map +1 -0
  61. package/dist/core/canvas-index-classify.js +63 -0
  62. package/dist/core/canvas-index-classify.js.map +1 -0
  63. package/dist/core/canvas-interconnections.d.ts +24 -0
  64. package/dist/core/canvas-interconnections.d.ts.map +1 -0
  65. package/dist/core/canvas-interconnections.js +96 -0
  66. package/dist/core/canvas-interconnections.js.map +1 -0
  67. package/dist/core/canvas-manifest-check.d.ts +15 -0
  68. package/dist/core/canvas-manifest-check.d.ts.map +1 -0
  69. package/dist/core/canvas-manifest-check.js +90 -0
  70. package/dist/core/canvas-manifest-check.js.map +1 -0
  71. package/dist/core/canvas-render.d.ts +73 -0
  72. package/dist/core/canvas-render.d.ts.map +1 -0
  73. package/dist/core/canvas-render.js +145 -0
  74. package/dist/core/canvas-render.js.map +1 -0
  75. package/dist/core/canvas-svg.d.ts +50 -0
  76. package/dist/core/canvas-svg.d.ts.map +1 -0
  77. package/dist/core/canvas-svg.js +240 -0
  78. package/dist/core/canvas-svg.js.map +1 -0
  79. package/dist/core/canvas-template.d.ts +43 -0
  80. package/dist/core/canvas-template.d.ts.map +1 -0
  81. package/dist/core/canvas-template.js +365 -0
  82. package/dist/core/canvas-template.js.map +1 -0
  83. package/dist/core/canvas-watcher.d.ts +25 -0
  84. package/dist/core/canvas-watcher.d.ts.map +1 -0
  85. package/dist/core/canvas-watcher.js +144 -0
  86. package/dist/core/canvas-watcher.js.map +1 -0
  87. package/dist/core/collector/batcher.d.ts +57 -0
  88. package/dist/core/collector/batcher.d.ts.map +1 -0
  89. package/dist/core/collector/batcher.js +143 -0
  90. package/dist/core/collector/batcher.js.map +1 -0
  91. package/dist/core/collector/codex-tailer.d.ts +85 -0
  92. package/dist/core/collector/codex-tailer.d.ts.map +1 -0
  93. package/dist/core/collector/codex-tailer.js +270 -0
  94. package/dist/core/collector/codex-tailer.js.map +1 -0
  95. package/dist/core/collector/normalizer.d.ts +33 -0
  96. package/dist/core/collector/normalizer.d.ts.map +1 -0
  97. package/dist/core/collector/normalizer.js +94 -0
  98. package/dist/core/collector/normalizer.js.map +1 -0
  99. package/dist/core/collector/seq.d.ts +14 -0
  100. package/dist/core/collector/seq.d.ts.map +1 -0
  101. package/dist/core/collector/seq.js +20 -0
  102. package/dist/core/collector/seq.js.map +1 -0
  103. package/dist/core/collector/store.d.ts +16 -0
  104. package/dist/core/collector/store.d.ts.map +1 -0
  105. package/dist/core/collector/store.js +30 -0
  106. package/dist/core/collector/store.js.map +1 -0
  107. package/dist/core/collector/transcript.d.ts +38 -0
  108. package/dist/core/collector/transcript.d.ts.map +1 -0
  109. package/dist/core/collector/transcript.js +110 -0
  110. package/dist/core/collector/transcript.js.map +1 -0
  111. package/dist/core/event-bus.d.ts +9 -0
  112. package/dist/core/event-bus.d.ts.map +1 -0
  113. package/dist/core/event-bus.js +25 -0
  114. package/dist/core/event-bus.js.map +1 -0
  115. package/dist/core/example-seed.d.ts +34 -0
  116. package/dist/core/example-seed.d.ts.map +1 -0
  117. package/dist/core/example-seed.js +277 -0
  118. package/dist/core/example-seed.js.map +1 -0
  119. package/dist/core/inject/claude-settings.d.ts +34 -0
  120. package/dist/core/inject/claude-settings.d.ts.map +1 -0
  121. package/dist/core/inject/claude-settings.js +152 -0
  122. package/dist/core/inject/claude-settings.js.map +1 -0
  123. package/dist/core/inject/mcp-config.d.ts +26 -0
  124. package/dist/core/inject/mcp-config.d.ts.map +1 -0
  125. package/dist/core/inject/mcp-config.js +39 -0
  126. package/dist/core/inject/mcp-config.js.map +1 -0
  127. package/dist/core/inject/retention.d.ts +66 -0
  128. package/dist/core/inject/retention.d.ts.map +1 -0
  129. package/dist/core/inject/retention.js +119 -0
  130. package/dist/core/inject/retention.js.map +1 -0
  131. package/dist/core/inject/system-prompt.d.ts +11 -0
  132. package/dist/core/inject/system-prompt.d.ts.map +1 -0
  133. package/dist/core/inject/system-prompt.js +22 -0
  134. package/dist/core/inject/system-prompt.js.map +1 -0
  135. package/dist/core/macro-runner.d.ts +36 -0
  136. package/dist/core/macro-runner.d.ts.map +1 -0
  137. package/dist/core/macro-runner.js +66 -0
  138. package/dist/core/macro-runner.js.map +1 -0
  139. package/dist/core/macros.d.ts +10 -0
  140. package/dist/core/macros.d.ts.map +1 -0
  141. package/dist/core/macros.js +59 -0
  142. package/dist/core/macros.js.map +1 -0
  143. package/dist/core/path-safety.d.ts +33 -0
  144. package/dist/core/path-safety.d.ts.map +1 -0
  145. package/dist/core/path-safety.js +63 -0
  146. package/dist/core/path-safety.js.map +1 -0
  147. package/dist/core/paths.d.ts +27 -0
  148. package/dist/core/paths.d.ts.map +1 -0
  149. package/dist/core/paths.js +43 -0
  150. package/dist/core/paths.js.map +1 -0
  151. package/dist/core/port-detector.d.ts +69 -0
  152. package/dist/core/port-detector.d.ts.map +1 -0
  153. package/dist/core/port-detector.js +121 -0
  154. package/dist/core/port-detector.js.map +1 -0
  155. package/dist/core/session-manager.d.ts +275 -0
  156. package/dist/core/session-manager.d.ts.map +1 -0
  157. package/dist/core/session-manager.js +729 -0
  158. package/dist/core/session-manager.js.map +1 -0
  159. package/dist/core/task-manager.d.ts +138 -0
  160. package/dist/core/task-manager.d.ts.map +1 -0
  161. package/dist/core/task-manager.js +286 -0
  162. package/dist/core/task-manager.js.map +1 -0
  163. package/dist/core/task-stream.d.ts +32 -0
  164. package/dist/core/task-stream.d.ts.map +1 -0
  165. package/dist/core/task-stream.js +87 -0
  166. package/dist/core/task-stream.js.map +1 -0
  167. package/dist/core/workflow-registry.d.ts +36 -0
  168. package/dist/core/workflow-registry.d.ts.map +1 -0
  169. package/dist/core/workflow-registry.js +220 -0
  170. package/dist/core/workflow-registry.js.map +1 -0
  171. package/dist/core/workspace-context.d.ts +43 -0
  172. package/dist/core/workspace-context.d.ts.map +1 -0
  173. package/dist/core/workspace-context.js +108 -0
  174. package/dist/core/workspace-context.js.map +1 -0
  175. package/dist/core/workspace-watcher.d.ts +26 -0
  176. package/dist/core/workspace-watcher.d.ts.map +1 -0
  177. package/dist/core/workspace-watcher.js +174 -0
  178. package/dist/core/workspace-watcher.js.map +1 -0
  179. package/dist/index.d.ts +6 -0
  180. package/dist/index.d.ts.map +1 -0
  181. package/dist/index.js +6 -0
  182. package/dist/index.js.map +1 -0
  183. package/dist/profiles/canvas-guidelines.d.ts +20 -0
  184. package/dist/profiles/canvas-guidelines.d.ts.map +1 -0
  185. package/dist/profiles/canvas-guidelines.js +48 -0
  186. package/dist/profiles/canvas-guidelines.js.map +1 -0
  187. package/dist/profiles/default.d.ts +10 -0
  188. package/dist/profiles/default.d.ts.map +1 -0
  189. package/dist/profiles/default.js +70 -0
  190. package/dist/profiles/default.js.map +1 -0
  191. package/dist/server/auth.d.ts +9 -0
  192. package/dist/server/auth.d.ts.map +1 -0
  193. package/dist/server/auth.js +28 -0
  194. package/dist/server/auth.js.map +1 -0
  195. package/dist/server/canvas-render.d.ts +18 -0
  196. package/dist/server/canvas-render.d.ts.map +1 -0
  197. package/dist/server/canvas-render.js +24 -0
  198. package/dist/server/canvas-render.js.map +1 -0
  199. package/dist/server/canvas.d.ts +12 -0
  200. package/dist/server/canvas.d.ts.map +1 -0
  201. package/dist/server/canvas.js +174 -0
  202. package/dist/server/canvas.js.map +1 -0
  203. package/dist/server/events-ws.d.ts +11 -0
  204. package/dist/server/events-ws.d.ts.map +1 -0
  205. package/dist/server/events-ws.js +24 -0
  206. package/dist/server/events-ws.js.map +1 -0
  207. package/dist/server/fs.d.ts +4 -0
  208. package/dist/server/fs.d.ts.map +1 -0
  209. package/dist/server/fs.js +88 -0
  210. package/dist/server/fs.js.map +1 -0
  211. package/dist/server/index.d.ts +83 -0
  212. package/dist/server/index.d.ts.map +1 -0
  213. package/dist/server/index.js +0 -0
  214. package/dist/server/index.js.map +1 -0
  215. package/dist/server/ingest.d.ts +74 -0
  216. package/dist/server/ingest.d.ts.map +1 -0
  217. package/dist/server/ingest.js +74 -0
  218. package/dist/server/ingest.js.map +1 -0
  219. package/dist/server/macros.d.ts +32 -0
  220. package/dist/server/macros.d.ts.map +1 -0
  221. package/dist/server/macros.js +76 -0
  222. package/dist/server/macros.js.map +1 -0
  223. package/dist/server/rest.d.ts +75 -0
  224. package/dist/server/rest.d.ts.map +1 -0
  225. package/dist/server/rest.js +235 -0
  226. package/dist/server/rest.js.map +1 -0
  227. package/dist/server/static.d.ts +8 -0
  228. package/dist/server/static.d.ts.map +1 -0
  229. package/dist/server/static.js +36 -0
  230. package/dist/server/static.js.map +1 -0
  231. package/dist/server/terminal-ws.d.ts +9 -0
  232. package/dist/server/terminal-ws.d.ts.map +1 -0
  233. package/dist/server/terminal-ws.js +59 -0
  234. package/dist/server/terminal-ws.js.map +1 -0
  235. package/dist/server/ws-router.d.ts +16 -0
  236. package/dist/server/ws-router.d.ts.map +1 -0
  237. package/dist/server/ws-router.js +26 -0
  238. package/dist/server/ws-router.js.map +1 -0
  239. package/dist/shared/types.d.ts +485 -0
  240. package/dist/shared/types.d.ts.map +1 -0
  241. package/dist/shared/types.js +80 -0
  242. package/dist/shared/types.js.map +1 -0
  243. package/dist/web/assets/index-B9R4TzH7.css +32 -0
  244. package/dist/web/assets/index-Dh97QAgq.js +192 -0
  245. package/dist/web/index.html +27 -0
  246. package/package.json +84 -7
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Generates the per-session Claude Code `--settings` file and the tiny
3
+ * hook-emitter script it points at.
4
+ *
5
+ * Nothing here touches the user's own `~/.claude` config — everything is
6
+ * written under the harness's generated-config directory and passed to
7
+ * `claude` via `--settings <path>` for the lifetime of one session.
8
+ */
9
+ /** The six Claude Code hook events the harness listens for. */
10
+ declare const HOOK_EVENTS: readonly ["SessionStart", "UserPromptSubmit", "PreToolUse", "PostToolUse", "Stop", "SessionEnd"];
11
+ export type ClaudeHookEvent = (typeof HOOK_EVENTS)[number];
12
+ export interface GenerateClaudeSettingsOptions {
13
+ /** The harness session this config is generated for. */
14
+ harnessSessionId: string;
15
+ /**
16
+ * Root directory generated configs live under. Defaults to
17
+ * `HARNESS_PATHS.generated` (`~/.sapiom/harness/generated`). Override in
18
+ * tests to avoid touching the real home directory.
19
+ */
20
+ generatedRoot?: string;
21
+ }
22
+ export interface GeneratedClaudeSettings {
23
+ /** Absolute path to the generated settings.json (pass as `--settings`). */
24
+ settingsPath: string;
25
+ /** Absolute path to the generated emit.cjs hook script. */
26
+ emitScriptPath: string;
27
+ }
28
+ /**
29
+ * Write the generated settings.json + emit.cjs for one harness session.
30
+ * Returns the settings.json path — pass it as `claude --settings <path>`.
31
+ */
32
+ export declare function generateClaudeSettings(options: GenerateClaudeSettingsOptions): Promise<GeneratedClaudeSettings>;
33
+ export {};
34
+ //# sourceMappingURL=claude-settings.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"claude-settings.d.ts","sourceRoot":"","sources":["../../../src/core/inject/claude-settings.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAQH,+DAA+D;AAC/D,QAAA,MAAM,WAAW,kGAOP,CAAC;AAEX,MAAM,MAAM,eAAe,GAAG,CAAC,OAAO,WAAW,CAAC,CAAC,MAAM,CAAC,CAAC;AAE3D,MAAM,WAAW,6BAA6B;IAC5C,wDAAwD;IACxD,gBAAgB,EAAE,MAAM,CAAC;IACzB;;;;OAIG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,uBAAuB;IACtC,2EAA2E;IAC3E,YAAY,EAAE,MAAM,CAAC;IACrB,2DAA2D;IAC3D,cAAc,EAAE,MAAM,CAAC;CACxB;AA0GD;;;GAGG;AACH,wBAAsB,sBAAsB,CAC1C,OAAO,EAAE,6BAA6B,GACrC,OAAO,CAAC,uBAAuB,CAAC,CA2BlC"}
@@ -0,0 +1,152 @@
1
+ /**
2
+ * Generates the per-session Claude Code `--settings` file and the tiny
3
+ * hook-emitter script it points at.
4
+ *
5
+ * Nothing here touches the user's own `~/.claude` config — everything is
6
+ * written under the harness's generated-config directory and passed to
7
+ * `claude` via `--settings <path>` for the lifetime of one session.
8
+ */
9
+ import * as fs from "node:fs/promises";
10
+ import * as path from "node:path";
11
+ import { HARNESS_PATHS } from "../../shared/types.js";
12
+ import { expandHome } from "../paths.js";
13
+ /** The six Claude Code hook events the harness listens for. */
14
+ const HOOK_EVENTS = [
15
+ "SessionStart",
16
+ "UserPromptSubmit",
17
+ "PreToolUse",
18
+ "PostToolUse",
19
+ "Stop",
20
+ "SessionEnd",
21
+ ];
22
+ /**
23
+ * The hook-emitter script. Self-contained CommonJS, node builtins only — it
24
+ * runs as a bare `node <path>` child process spawned by Claude Code itself,
25
+ * so it can't rely on the harness package (or any node_modules) being
26
+ * resolvable from wherever the user's project lives.
27
+ *
28
+ * Contract with the harness server: reads the hook's stdin JSON, wraps it
29
+ * with the hook event name and this session's id, and POSTs it to
30
+ * SAPIOM_HARNESS_INGEST_URL with a bearer token. A dead or slow harness
31
+ * server must never slow down or break the user's agent, so every failure
32
+ * mode here is swallowed and the process always exits 0 quickly.
33
+ */
34
+ const EMIT_SCRIPT_SOURCE = `#!/usr/bin/env node
35
+ "use strict";
36
+
37
+ // Generated by @sapiom/harness — do not edit by hand, it is overwritten
38
+ // every time a new session starts.
39
+
40
+ const hookEvent = process.argv[2] || "unknown";
41
+
42
+ // Absolute ceiling: whatever else happens, exit quickly so we never hang
43
+ // the agent's hook pipeline.
44
+ const hardStop = setTimeout(() => {
45
+ try {
46
+ process.exit(0);
47
+ } catch {
48
+ // ignore
49
+ }
50
+ }, 1000);
51
+
52
+ function readStdin() {
53
+ return new Promise((resolve) => {
54
+ if (process.stdin.isTTY) {
55
+ resolve("");
56
+ return;
57
+ }
58
+ let data = "";
59
+ const giveUp = setTimeout(() => resolve(data), 300);
60
+ process.stdin.setEncoding("utf8");
61
+ process.stdin.on("data", (chunk) => {
62
+ data += chunk;
63
+ });
64
+ process.stdin.on("end", () => {
65
+ clearTimeout(giveUp);
66
+ resolve(data);
67
+ });
68
+ process.stdin.on("error", () => {
69
+ clearTimeout(giveUp);
70
+ resolve(data);
71
+ });
72
+ });
73
+ }
74
+
75
+ async function main() {
76
+ let payload = {};
77
+ try {
78
+ const raw = await readStdin();
79
+ if (raw) payload = JSON.parse(raw);
80
+ } catch {
81
+ // Malformed stdin — still report that the hook fired, with no payload.
82
+ }
83
+
84
+ const ingestUrl = process.env.SAPIOM_HARNESS_INGEST_URL;
85
+ if (!ingestUrl) return;
86
+
87
+ const body = JSON.stringify({
88
+ hookEvent: hookEvent,
89
+ receivedAt: new Date().toISOString(),
90
+ harnessSessionId: process.env.SAPIOM_HARNESS_SESSION_ID || null,
91
+ payload: payload,
92
+ });
93
+
94
+ const controller = new AbortController();
95
+ const abortTimer = setTimeout(() => controller.abort(), 200);
96
+
97
+ try {
98
+ await fetch(ingestUrl, {
99
+ method: "POST",
100
+ headers: {
101
+ "content-type": "application/json",
102
+ authorization: "Bearer " + (process.env.SAPIOM_HARNESS_INGEST_TOKEN || ""),
103
+ },
104
+ body: body,
105
+ signal: controller.signal,
106
+ });
107
+ } catch {
108
+ // Fire-and-forget: ingest being down/slow must never surface here.
109
+ } finally {
110
+ clearTimeout(abortTimer);
111
+ }
112
+ }
113
+
114
+ main()
115
+ .catch(() => {})
116
+ .finally(() => {
117
+ clearTimeout(hardStop);
118
+ process.exit(0);
119
+ });
120
+ `;
121
+ function settingsDirFor(root, harnessSessionId) {
122
+ return path.join(root, harnessSessionId);
123
+ }
124
+ /**
125
+ * Write the generated settings.json + emit.cjs for one harness session.
126
+ * Returns the settings.json path — pass it as `claude --settings <path>`.
127
+ */
128
+ export async function generateClaudeSettings(options) {
129
+ const root = expandHome(options.generatedRoot ?? HARNESS_PATHS.generated);
130
+ const dir = settingsDirFor(root, options.harnessSessionId);
131
+ await fs.mkdir(dir, { recursive: true });
132
+ const emitScriptPath = path.join(dir, "emit.cjs");
133
+ await fs.writeFile(emitScriptPath, EMIT_SCRIPT_SOURCE, { mode: 0o755 });
134
+ const hooks = {};
135
+ for (const event of HOOK_EVENTS) {
136
+ hooks[event] = [
137
+ {
138
+ matcher: "",
139
+ hooks: [
140
+ {
141
+ type: "command",
142
+ command: `node ${emitScriptPath} ${event}`,
143
+ },
144
+ ],
145
+ },
146
+ ];
147
+ }
148
+ const settingsPath = path.join(dir, "settings.json");
149
+ await fs.writeFile(settingsPath, JSON.stringify({ hooks }, null, 2) + "\n", "utf8");
150
+ return { settingsPath, emitScriptPath };
151
+ }
152
+ //# sourceMappingURL=claude-settings.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"claude-settings.js","sourceRoot":"","sources":["../../../src/core/inject/claude-settings.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,MAAM,kBAAkB,CAAC;AACvC,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAElC,OAAO,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AACtD,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAEzC,+DAA+D;AAC/D,MAAM,WAAW,GAAG;IAClB,cAAc;IACd,kBAAkB;IAClB,YAAY;IACZ,aAAa;IACb,MAAM;IACN,YAAY;CACJ,CAAC;AAsBX;;;;;;;;;;;GAWG;AACH,MAAM,kBAAkB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAsF1B,CAAC;AAEF,SAAS,cAAc,CAAC,IAAY,EAAE,gBAAwB;IAC5D,OAAO,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,gBAAgB,CAAC,CAAC;AAC3C,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,sBAAsB,CAC1C,OAAsC;IAEtC,MAAM,IAAI,GAAG,UAAU,CAAC,OAAO,CAAC,aAAa,IAAI,aAAa,CAAC,SAAS,CAAC,CAAC;IAC1E,MAAM,GAAG,GAAG,cAAc,CAAC,IAAI,EAAE,OAAO,CAAC,gBAAgB,CAAC,CAAC;IAC3D,MAAM,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAEzC,MAAM,cAAc,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,UAAU,CAAC,CAAC;IAClD,MAAM,EAAE,CAAC,SAAS,CAAC,cAAc,EAAE,kBAAkB,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IAExE,MAAM,KAAK,GAA4B,EAAE,CAAC;IAC1C,KAAK,MAAM,KAAK,IAAI,WAAW,EAAE,CAAC;QAChC,KAAK,CAAC,KAAK,CAAC,GAAG;YACb;gBACE,OAAO,EAAE,EAAE;gBACX,KAAK,EAAE;oBACL;wBACE,IAAI,EAAE,SAAS;wBACf,OAAO,EAAE,QAAQ,cAAc,IAAI,KAAK,EAAE;qBAC3C;iBACF;aACF;SACF,CAAC;IACJ,CAAC;IAED,MAAM,YAAY,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,eAAe,CAAC,CAAC;IACrD,MAAM,EAAE,CAAC,SAAS,CAAC,YAAY,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,IAAI,EAAE,MAAM,CAAC,CAAC;IAEpF,OAAO,EAAE,YAAY,EAAE,cAAc,EAAE,CAAC;AAC1C,CAAC"}
@@ -0,0 +1,26 @@
1
+ export interface McpConfigOptions {
2
+ /** SAPIOM_ENVIRONMENT to pass through to the sapiom-dev child process. */
3
+ environment?: string;
4
+ /**
5
+ * Cached Sapiom API key (from CLI auth), if signed in. Sent as
6
+ * `x-api-key` on the remote `sapiom` MCP — empirically verified against
7
+ * api.sapiom.ai/v1/mcp (both `x-api-key` and `Authorization: Bearer` are
8
+ * accepted for `sk_`-prefixed keys; `x-api-key` matches its CORS
9
+ * allow-list order and needs no prefix formatting). Omitted entirely when
10
+ * absent (`--no-auth`, or not yet signed in) — every remote tool call
11
+ * then 401s, same as today.
12
+ */
13
+ apiKey?: string | null;
14
+ /** Root directory generated configs live under. Defaults to
15
+ * HARNESS_PATHS.generated. Override in tests to avoid the real home dir. */
16
+ generatedRoot?: string;
17
+ }
18
+ /**
19
+ * Writes the `--mcp-config` file for a harness session: the remote HTTP
20
+ * capability MCP (`sapiom`) and the local stdio authoring MCP (`sapiom-dev`).
21
+ * Written under `HARNESS_PATHS.generated/<harnessSessionId>/` so concurrent
22
+ * sessions never share (or race on) a config file. Returns the file's
23
+ * absolute path for the adapter to pass as `--mcp-config <path>`.
24
+ */
25
+ export declare function generateMcpConfig(harnessSessionId: string, options?: McpConfigOptions): Promise<string>;
26
+ //# sourceMappingURL=mcp-config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mcp-config.d.ts","sourceRoot":"","sources":["../../../src/core/inject/mcp-config.ts"],"names":[],"mappings":"AAKA,MAAM,WAAW,gBAAgB;IAC/B,0EAA0E;IAC1E,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;;;;;OAQG;IACH,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB;iFAC6E;IAC7E,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED;;;;;;GAMG;AACH,wBAAsB,iBAAiB,CACrC,gBAAgB,EAAE,MAAM,EACxB,OAAO,GAAE,gBAAqB,GAC7B,OAAO,CAAC,MAAM,CAAC,CA6BjB"}
@@ -0,0 +1,39 @@
1
+ import * as fs from "node:fs/promises";
2
+ import * as path from "node:path";
3
+ import { HARNESS_PATHS } from "../../shared/types.js";
4
+ import { expandHome } from "../../cli/paths.js";
5
+ /**
6
+ * Writes the `--mcp-config` file for a harness session: the remote HTTP
7
+ * capability MCP (`sapiom`) and the local stdio authoring MCP (`sapiom-dev`).
8
+ * Written under `HARNESS_PATHS.generated/<harnessSessionId>/` so concurrent
9
+ * sessions never share (or race on) a config file. Returns the file's
10
+ * absolute path for the adapter to pass as `--mcp-config <path>`.
11
+ */
12
+ export async function generateMcpConfig(harnessSessionId, options = {}) {
13
+ const dir = path.join(expandHome(options.generatedRoot ?? HARNESS_PATHS.generated), harnessSessionId);
14
+ await fs.mkdir(dir, { recursive: true });
15
+ const sapiomEnvironment = options.environment ?? process.env.SAPIOM_ENVIRONMENT;
16
+ const devEnv = sapiomEnvironment
17
+ ? { SAPIOM_ENVIRONMENT: sapiomEnvironment }
18
+ : undefined;
19
+ const config = {
20
+ mcpServers: {
21
+ sapiom: {
22
+ type: "http",
23
+ url: "https://api.sapiom.ai/v1/mcp",
24
+ ...(options.apiKey ? { headers: { "x-api-key": options.apiKey } } : {}),
25
+ },
26
+ "sapiom-dev": {
27
+ command: "npx",
28
+ args: ["-y", "@sapiom/mcp"],
29
+ ...(devEnv ? { env: devEnv } : {}),
30
+ },
31
+ },
32
+ };
33
+ const filePath = path.join(dir, "mcp-config.json");
34
+ // May now carry a live API key (the `sapiom` entry's headers) — restrict
35
+ // to the owner, matching how ~/.sapiom/credentials.json is written.
36
+ await fs.writeFile(filePath, JSON.stringify(config, null, 2) + "\n", { mode: 0o600 });
37
+ return filePath;
38
+ }
39
+ //# sourceMappingURL=mcp-config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mcp-config.js","sourceRoot":"","sources":["../../../src/core/inject/mcp-config.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,kBAAkB,CAAC;AACvC,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAClC,OAAO,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AACtD,OAAO,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAoBhD;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,gBAAwB,EACxB,UAA4B,EAAE;IAE9B,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,aAAa,IAAI,aAAa,CAAC,SAAS,CAAC,EAAE,gBAAgB,CAAC,CAAC;IACtG,MAAM,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAEzC,MAAM,iBAAiB,GAAG,OAAO,CAAC,WAAW,IAAI,OAAO,CAAC,GAAG,CAAC,kBAAkB,CAAC;IAChF,MAAM,MAAM,GAAuC,iBAAiB;QAClE,CAAC,CAAC,EAAE,kBAAkB,EAAE,iBAAiB,EAAE;QAC3C,CAAC,CAAC,SAAS,CAAC;IAEd,MAAM,MAAM,GAAG;QACb,UAAU,EAAE;YACV,MAAM,EAAE;gBACN,IAAI,EAAE,MAAM;gBACZ,GAAG,EAAE,8BAA8B;gBACnC,GAAG,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,EAAE,WAAW,EAAE,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;aACxE;YACD,YAAY,EAAE;gBACZ,OAAO,EAAE,KAAK;gBACd,IAAI,EAAE,CAAC,IAAI,EAAE,aAAa,CAAC;gBAC3B,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;aACnC;SACF;KACF,CAAC;IAEF,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,iBAAiB,CAAC,CAAC;IACnD,yEAAyE;IACzE,oEAAoE;IACpE,MAAM,EAAE,CAAC,SAAS,CAAC,QAAQ,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,IAAI,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IACtF,OAAO,QAAQ,CAAC;AAClB,CAAC"}
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Retention policy for the per-session generated config dirs
3
+ * (`~/.sapiom/harness/generated/<harnessSessionId>/` — settings.json,
4
+ * emit.cjs, mcp-config.json, system-prompt.txt). Without it every session
5
+ * launch leaves a dir behind forever (real installs accumulate thousands).
6
+ *
7
+ * Lifecycle facts the policy rests on (see the generators in this directory
8
+ * and server/index.ts's createDefaultBuildLaunchOpts):
9
+ * - Most files are consumed at launch — `--settings`/`--mcp-config` are
10
+ * parsed by the agent at startup, system-prompt.txt is inlined into argv
11
+ * before spawn — EXCEPT emit.cjs, which the agent re-executes as a child
12
+ * process on every hook event. A session's dir must therefore survive for
13
+ * as long as its pty runs.
14
+ * - buildLaunchOpts regenerates every file on BOTH create() and resume(),
15
+ * so nothing in the dir is needed once the session has exited: deleting
16
+ * at exit can never break a later resume.
17
+ *
18
+ * Two mechanisms:
19
+ * - removeGeneratedSessionDir(): per-session delete, wired to the session's
20
+ * "exited" status transition.
21
+ * - sweepGeneratedDirs(): boot-time sweep for dirs the exit-time delete
22
+ * never reached (crashes, force-kills, pre-retention accumulation) —
23
+ * age-gated so anything plausibly still in use is left alone.
24
+ *
25
+ * Safety: deletion only ever targets a plain directory that is a direct
26
+ * child of a root that itself looks like a dedicated generated-config dir
27
+ * (resolveGeneratedRoot / childPath). Symlinked entries are never
28
+ * followed and never deleted.
29
+ */
30
+ /** Sweep threshold: a non-live dir must be at least this stale (mtime) to be
31
+ * removed. Generous — the only cost of keeping a dead dir is disk clutter,
32
+ * while a false positive on some unforeseen liveness signal is worse. */
33
+ export declare const GENERATED_SWEEP_MAX_AGE_MS: number;
34
+ export interface GeneratedRetentionOptions {
35
+ /** Root directory generated configs live under. Defaults to
36
+ * HARNESS_PATHS.generated. Override in tests to avoid the real home dir. */
37
+ generatedRoot?: string;
38
+ }
39
+ export interface SweepGeneratedDirsOptions extends GeneratedRetentionOptions {
40
+ /** Dirs named after a live session id are never removed, whatever their
41
+ * age. Defaults to treating nothing as live. */
42
+ isLiveSession?: (harnessSessionId: string) => boolean;
43
+ /** Minimum staleness before a dir is swept. Defaults to
44
+ * GENERATED_SWEEP_MAX_AGE_MS. */
45
+ maxAgeMs?: number;
46
+ /** Injectable clock (epoch ms) for tests. */
47
+ now?: () => number;
48
+ }
49
+ /**
50
+ * Deletes one session's generated dir. Only call once the session's pty has
51
+ * exited — the agent re-executes emit.cjs from this dir on every hook event
52
+ * while it runs. Returns true if a directory was removed; a missing entry,
53
+ * an escaping/malformed id, or a symlink (never followed) is false.
54
+ */
55
+ export declare function removeGeneratedSessionDir(harnessSessionId: string, options?: GeneratedRetentionOptions): Promise<boolean>;
56
+ /**
57
+ * Removes every generated dir that (a) isn't a live session's and (b) has
58
+ * been stale for at least `maxAgeMs`. Covers everything the exit-time
59
+ * delete can't: sessions that crashed with the harness, force-killed
60
+ * processes, and accumulation from before retention existed. Only plain
61
+ * directories are candidates — symlinks and stray files are left untouched.
62
+ * Best-effort per entry: one undeletable dir doesn't abort the rest.
63
+ * Returns the names of the dirs it removed.
64
+ */
65
+ export declare function sweepGeneratedDirs(options?: SweepGeneratedDirsOptions): Promise<string[]>;
66
+ //# sourceMappingURL=retention.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"retention.d.ts","sourceRoot":"","sources":["../../../src/core/inject/retention.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AASH;;0EAE0E;AAC1E,eAAO,MAAM,0BAA0B,QAA0B,CAAC;AAElE,MAAM,WAAW,yBAAyB;IACxC;iFAC6E;IAC7E,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,yBAA0B,SAAQ,yBAAyB;IAC1E;qDACiD;IACjD,aAAa,CAAC,EAAE,CAAC,gBAAgB,EAAE,MAAM,KAAK,OAAO,CAAC;IACtD;sCACkC;IAClC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,6CAA6C;IAC7C,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;CACpB;AAiBD;;;;;GAKG;AACH,wBAAsB,yBAAyB,CAC7C,gBAAgB,EAAE,MAAM,EACxB,OAAO,GAAE,yBAA8B,GACtC,OAAO,CAAC,OAAO,CAAC,CAQlB;AAED;;;;;;;;GAQG;AACH,wBAAsB,kBAAkB,CAAC,OAAO,GAAE,yBAA8B,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAiCnG"}
@@ -0,0 +1,119 @@
1
+ /**
2
+ * Retention policy for the per-session generated config dirs
3
+ * (`~/.sapiom/harness/generated/<harnessSessionId>/` — settings.json,
4
+ * emit.cjs, mcp-config.json, system-prompt.txt). Without it every session
5
+ * launch leaves a dir behind forever (real installs accumulate thousands).
6
+ *
7
+ * Lifecycle facts the policy rests on (see the generators in this directory
8
+ * and server/index.ts's createDefaultBuildLaunchOpts):
9
+ * - Most files are consumed at launch — `--settings`/`--mcp-config` are
10
+ * parsed by the agent at startup, system-prompt.txt is inlined into argv
11
+ * before spawn — EXCEPT emit.cjs, which the agent re-executes as a child
12
+ * process on every hook event. A session's dir must therefore survive for
13
+ * as long as its pty runs.
14
+ * - buildLaunchOpts regenerates every file on BOTH create() and resume(),
15
+ * so nothing in the dir is needed once the session has exited: deleting
16
+ * at exit can never break a later resume.
17
+ *
18
+ * Two mechanisms:
19
+ * - removeGeneratedSessionDir(): per-session delete, wired to the session's
20
+ * "exited" status transition.
21
+ * - sweepGeneratedDirs(): boot-time sweep for dirs the exit-time delete
22
+ * never reached (crashes, force-kills, pre-retention accumulation) —
23
+ * age-gated so anything plausibly still in use is left alone.
24
+ *
25
+ * Safety: deletion only ever targets a plain directory that is a direct
26
+ * child of a root that itself looks like a dedicated generated-config dir
27
+ * (resolveGeneratedRoot / childPath). Symlinked entries are never
28
+ * followed and never deleted.
29
+ */
30
+ import * as fs from "node:fs/promises";
31
+ import * as path from "node:path";
32
+ import { HARNESS_PATHS } from "../../shared/types.js";
33
+ import { expandHome } from "../paths.js";
34
+ import { childPath } from "../path-safety.js";
35
+ /** Sweep threshold: a non-live dir must be at least this stale (mtime) to be
36
+ * removed. Generous — the only cost of keeping a dead dir is disk clutter,
37
+ * while a false positive on some unforeseen liveness signal is worse. */
38
+ export const GENERATED_SWEEP_MAX_AGE_MS = 7 * 24 * 60 * 60 * 1000;
39
+ /**
40
+ * A recursive delete under a misconfigured root would be catastrophic
41
+ * (imagine generatedRoot resolving to the home dir, or "/"). The generators
42
+ * will happily *write* wherever they're pointed, but deletion demands the
43
+ * root at least look like a dedicated generated-config directory: a
44
+ * non-root absolute path whose final segment is "generated".
45
+ */
46
+ function resolveGeneratedRoot(generatedRoot) {
47
+ const root = expandHome(generatedRoot ?? HARNESS_PATHS.generated);
48
+ if (!path.isAbsolute(root) || path.basename(root) !== "generated" || path.dirname(root) === root) {
49
+ throw new Error(`refusing to delete under suspicious generated root "${root}"`);
50
+ }
51
+ return root;
52
+ }
53
+ /**
54
+ * Deletes one session's generated dir. Only call once the session's pty has
55
+ * exited — the agent re-executes emit.cjs from this dir on every hook event
56
+ * while it runs. Returns true if a directory was removed; a missing entry,
57
+ * an escaping/malformed id, or a symlink (never followed) is false.
58
+ */
59
+ export async function removeGeneratedSessionDir(harnessSessionId, options = {}) {
60
+ const root = resolveGeneratedRoot(options.generatedRoot);
61
+ const dir = childPath(root, harnessSessionId);
62
+ if (!dir)
63
+ return false;
64
+ const stats = await fs.lstat(dir).catch(() => null);
65
+ if (!stats || stats.isSymbolicLink() || !stats.isDirectory())
66
+ return false;
67
+ await fs.rm(dir, { recursive: true, force: true });
68
+ return true;
69
+ }
70
+ /**
71
+ * Removes every generated dir that (a) isn't a live session's and (b) has
72
+ * been stale for at least `maxAgeMs`. Covers everything the exit-time
73
+ * delete can't: sessions that crashed with the harness, force-killed
74
+ * processes, and accumulation from before retention existed. Only plain
75
+ * directories are candidates — symlinks and stray files are left untouched.
76
+ * Best-effort per entry: one undeletable dir doesn't abort the rest.
77
+ * Returns the names of the dirs it removed.
78
+ */
79
+ export async function sweepGeneratedDirs(options = {}) {
80
+ const root = resolveGeneratedRoot(options.generatedRoot);
81
+ const maxAgeMs = options.maxAgeMs ?? GENERATED_SWEEP_MAX_AGE_MS;
82
+ const now = options.now ? options.now() : Date.now();
83
+ let entries;
84
+ try {
85
+ entries = await fs.readdir(root, { withFileTypes: true });
86
+ }
87
+ catch (err) {
88
+ if (err.code === "ENOENT")
89
+ return [];
90
+ throw err;
91
+ }
92
+ const removed = [];
93
+ for (const entry of entries) {
94
+ // Dirent types come from lstat semantics: a symlink to a directory is
95
+ // isSymbolicLink(), not isDirectory() — so this both skips symlinks and
96
+ // guarantees rm below never follows one out of the root.
97
+ if (!entry.isDirectory())
98
+ continue;
99
+ const dir = childPath(root, entry.name);
100
+ if (!dir)
101
+ continue;
102
+ if (options.isLiveSession?.(entry.name))
103
+ continue;
104
+ const stats = await fs.lstat(dir).catch(() => null);
105
+ if (!stats || !stats.isDirectory())
106
+ continue;
107
+ if (now - stats.mtimeMs < maxAgeMs)
108
+ continue;
109
+ try {
110
+ await fs.rm(dir, { recursive: true, force: true });
111
+ removed.push(entry.name);
112
+ }
113
+ catch {
114
+ // Leave it for the next boot's sweep rather than aborting the rest.
115
+ }
116
+ }
117
+ return removed;
118
+ }
119
+ //# sourceMappingURL=retention.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"retention.js","sourceRoot":"","sources":["../../../src/core/inject/retention.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ export interface GenerateSystemPromptFileOptions {
2
+ /** Root directory generated configs live under. Defaults to
3
+ * HARNESS_PATHS.generated. Override in tests to avoid the real home dir. */
4
+ generatedRoot?: string;
5
+ /** Defaults to the harness's default profile (DEFAULT_SYSTEM_PROMPT). */
6
+ prompt?: string;
7
+ }
8
+ /** Writes `<generated>/<harnessSessionId>/system-prompt.txt`. Returns its
9
+ * absolute path (LaunchOpts.systemPromptFile). */
10
+ export declare function generateSystemPromptFile(harnessSessionId: string, options?: GenerateSystemPromptFileOptions): Promise<string>;
11
+ //# sourceMappingURL=system-prompt.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"system-prompt.d.ts","sourceRoot":"","sources":["../../../src/core/inject/system-prompt.ts"],"names":[],"mappings":"AAaA,MAAM,WAAW,+BAA+B;IAC9C;iFAC6E;IAC7E,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,yEAAyE;IACzE,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;mDACmD;AACnD,wBAAsB,wBAAwB,CAC5C,gBAAgB,EAAE,MAAM,EACxB,OAAO,GAAE,+BAAoC,GAC5C,OAAO,CAAC,MAAM,CAAC,CAQjB"}
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Writes the per-session system-prompt file the claude-code adapter reads
3
+ * and inlines via `--append-system-prompt` (see LaunchOpts.systemPromptFile
4
+ * — the adapter wants file contents, not the profile text itself, so the
5
+ * server layer materializes it once per session here).
6
+ */
7
+ import * as fs from "node:fs/promises";
8
+ import * as path from "node:path";
9
+ import { HARNESS_PATHS } from "../../shared/types.js";
10
+ import { expandHome } from "../paths.js";
11
+ import { DEFAULT_SYSTEM_PROMPT } from "../../profiles/default.js";
12
+ /** Writes `<generated>/<harnessSessionId>/system-prompt.txt`. Returns its
13
+ * absolute path (LaunchOpts.systemPromptFile). */
14
+ export async function generateSystemPromptFile(harnessSessionId, options = {}) {
15
+ const root = expandHome(options.generatedRoot ?? HARNESS_PATHS.generated);
16
+ const dir = path.join(root, harnessSessionId);
17
+ await fs.mkdir(dir, { recursive: true });
18
+ const filePath = path.join(dir, "system-prompt.txt");
19
+ await fs.writeFile(filePath, options.prompt ?? DEFAULT_SYSTEM_PROMPT, "utf8");
20
+ return filePath;
21
+ }
22
+ //# sourceMappingURL=system-prompt.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"system-prompt.js","sourceRoot":"","sources":["../../../src/core/inject/system-prompt.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,KAAK,EAAE,MAAM,kBAAkB,CAAC;AACvC,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAElC,OAAO,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AACtD,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,qBAAqB,EAAE,MAAM,2BAA2B,CAAC;AAUlE;mDACmD;AACnD,MAAM,CAAC,KAAK,UAAU,wBAAwB,CAC5C,gBAAwB,EACxB,UAA2C,EAAE;IAE7C,MAAM,IAAI,GAAG,UAAU,CAAC,OAAO,CAAC,aAAa,IAAI,aAAa,CAAC,SAAS,CAAC,CAAC;IAC1E,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,gBAAgB,CAAC,CAAC;IAC9C,MAAM,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAEzC,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,mBAAmB,CAAC,CAAC;IACrD,MAAM,EAAE,CAAC,SAAS,CAAC,QAAQ,EAAE,OAAO,CAAC,MAAM,IAAI,qBAAqB,EAAE,MAAM,CAAC,CAAC;IAC9E,OAAO,QAAQ,CAAC;AAClB,CAAC"}
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Macro resolution (workstream W5's backend slice): substitutes the
3
+ * `{{...}}` placeholders documented on MacroDef (shared/types.ts) into a
4
+ * macro's action, given the run-time context (selected workflow, session,
5
+ * canvas path, free-text subject). Pure and side-effect free — the caller
6
+ * (src/server/macros.ts) executes the resolved action.
7
+ */
8
+ import type { MacroDef, WorkflowInfo } from "../shared/types.js";
9
+ export interface MacroContext {
10
+ workflow: WorkflowInfo | null;
11
+ sessionCwd: string;
12
+ /** Absolute path to the session's canvas index file ({{canvas.path}}). */
13
+ canvasPath: string;
14
+ /** Free-text subject for the Visualize macro ({{subject}}). */
15
+ subject?: string;
16
+ }
17
+ export type ResolvedMacroAction = {
18
+ kind: "inject";
19
+ text: string;
20
+ submit: boolean;
21
+ } | {
22
+ kind: "open-url";
23
+ url: string;
24
+ } | {
25
+ kind: "render-canvas";
26
+ };
27
+ export declare class MacroValidationError extends Error {
28
+ constructor(message: string);
29
+ }
30
+ /**
31
+ * Resolves a macro's action for execution against `ctx`.
32
+ * Throws `MacroValidationError` when `macro.requiresWorkflow` and `ctx.workflow`
33
+ * is null — the caller (the REST handler) turns that into a 400.
34
+ */
35
+ export declare function resolveMacro(macro: MacroDef, ctx: MacroContext): ResolvedMacroAction;
36
+ //# sourceMappingURL=macro-runner.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"macro-runner.d.ts","sourceRoot":"","sources":["../../src/core/macro-runner.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,KAAK,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAEjE,MAAM,WAAW,YAAY;IAC3B,QAAQ,EAAE,YAAY,GAAG,IAAI,CAAC;IAC9B,UAAU,EAAE,MAAM,CAAC;IACnB,0EAA0E;IAC1E,UAAU,EAAE,MAAM,CAAC;IACnB,+DAA+D;IAC/D,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,MAAM,mBAAmB,GAC3B;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,OAAO,CAAA;CAAE,GACjD;IAAE,IAAI,EAAE,UAAU,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,GACjC;IAAE,IAAI,EAAE,eAAe,CAAA;CAAE,CAAC;AAE9B,qBAAa,oBAAqB,SAAQ,KAAK;gBACjC,OAAO,EAAE,MAAM;CAI5B;AAoDD;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,QAAQ,EAAE,GAAG,EAAE,YAAY,GAAG,mBAAmB,CAiBpF"}
@@ -0,0 +1,66 @@
1
+ export class MacroValidationError extends Error {
2
+ constructor(message) {
3
+ super(message);
4
+ this.name = "MacroValidationError";
5
+ }
6
+ }
7
+ const PLACEHOLDER_PATTERN = /\{\{[a-zA-Z.]+\}\}/g;
8
+ function placeholderTable(ctx) {
9
+ return {
10
+ "{{workflow.path}}": { available: ctx.workflow != null, value: ctx.workflow?.path ?? "" },
11
+ "{{workflow.name}}": { available: ctx.workflow != null, value: ctx.workflow?.name ?? "" },
12
+ "{{workflow.definitionId}}": {
13
+ available: ctx.workflow?.definitionId != null,
14
+ value: ctx.workflow?.definitionId != null ? String(ctx.workflow.definitionId) : "",
15
+ },
16
+ "{{session.cwd}}": { available: true, value: ctx.sessionCwd },
17
+ "{{canvas.path}}": { available: true, value: ctx.canvasPath },
18
+ "{{subject}}": { available: Boolean(ctx.subject), value: ctx.subject ?? "" },
19
+ };
20
+ }
21
+ /**
22
+ * Substitutes every known `{{...}}` placeholder found in `template`. Throws
23
+ * `MacroValidationError` listing every placeholder that's present in the
24
+ * template but has no value in `ctx` — instead of silently injecting text
25
+ * with a hole in it (e.g. "visualize to .sapiom/canvas/index.html" with no
26
+ * subject). An unrecognized `{{...}}`-shaped token is left verbatim; it's not
27
+ * a placeholder this engine knows about.
28
+ */
29
+ function substitute(template, ctx) {
30
+ const table = placeholderTable(ctx);
31
+ const missing = [];
32
+ const result = template.replace(PLACEHOLDER_PATTERN, (token) => {
33
+ const entry = table[token];
34
+ if (!entry)
35
+ return token;
36
+ if (!entry.available)
37
+ missing.push(token);
38
+ return entry.value;
39
+ });
40
+ if (missing.length > 0) {
41
+ throw new MacroValidationError(`Missing values for: ${missing.join(", ")}`);
42
+ }
43
+ return result;
44
+ }
45
+ /**
46
+ * Resolves a macro's action for execution against `ctx`.
47
+ * Throws `MacroValidationError` when `macro.requiresWorkflow` and `ctx.workflow`
48
+ * is null — the caller (the REST handler) turns that into a 400.
49
+ */
50
+ export function resolveMacro(macro, ctx) {
51
+ if (macro.requiresWorkflow && !ctx.workflow) {
52
+ throw new MacroValidationError(`Macro '${macro.id}' requires a selected workflow.`);
53
+ }
54
+ if (macro.action.kind === "open-url") {
55
+ return { kind: "open-url", url: substitute(macro.action.url, ctx) };
56
+ }
57
+ if (macro.action.kind === "render-canvas") {
58
+ return { kind: "render-canvas" };
59
+ }
60
+ return {
61
+ kind: "inject",
62
+ text: substitute(macro.action.text, ctx),
63
+ submit: macro.action.submit ?? true,
64
+ };
65
+ }
66
+ //# sourceMappingURL=macro-runner.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"macro-runner.js","sourceRoot":"","sources":["../../src/core/macro-runner.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Default action-rail macros. Pure data — no execution wiring. The server
3
+ * resolves `{{...}}` placeholders (see MacroDef in shared/types.ts) and
4
+ * either injects the text into the session pty or opens the URL. Matches the
5
+ * SPA's MOCK_MACROS fixture (web/src/lib/mock-data.ts) so mock and real mode
6
+ * present the same action rail.
7
+ */
8
+ import type { MacroDef } from "../shared/types.js";
9
+ export declare const DEFAULT_MACROS: MacroDef[];
10
+ //# sourceMappingURL=macros.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"macros.d.ts","sourceRoot":"","sources":["../../src/core/macros.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAEnD,eAAO,MAAM,cAAc,EAAE,QAAQ,EAyDpC,CAAC"}