@songsid/agend 0.0.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 (232) hide show
  1. package/README.md +210 -0
  2. package/README.zh-TW.md +134 -0
  3. package/dist/access-path.d.ts +10 -0
  4. package/dist/access-path.js +32 -0
  5. package/dist/access-path.js.map +1 -0
  6. package/dist/adapter-world.d.ts +25 -0
  7. package/dist/adapter-world.js +41 -0
  8. package/dist/adapter-world.js.map +1 -0
  9. package/dist/agent-cli-instructions.md +50 -0
  10. package/dist/agent-cli.d.ts +2 -0
  11. package/dist/agent-cli.js +200 -0
  12. package/dist/agent-cli.js.map +1 -0
  13. package/dist/agent-endpoint.d.ts +25 -0
  14. package/dist/agent-endpoint.js +162 -0
  15. package/dist/agent-endpoint.js.map +1 -0
  16. package/dist/backend/antigravity.d.ts +17 -0
  17. package/dist/backend/antigravity.js +98 -0
  18. package/dist/backend/antigravity.js.map +1 -0
  19. package/dist/backend/claude-code.d.ts +23 -0
  20. package/dist/backend/claude-code.js +171 -0
  21. package/dist/backend/claude-code.js.map +1 -0
  22. package/dist/backend/codex.d.ts +18 -0
  23. package/dist/backend/codex.js +160 -0
  24. package/dist/backend/codex.js.map +1 -0
  25. package/dist/backend/factory.d.ts +2 -0
  26. package/dist/backend/factory.js +28 -0
  27. package/dist/backend/factory.js.map +1 -0
  28. package/dist/backend/gemini-cli.d.ts +17 -0
  29. package/dist/backend/gemini-cli.js +163 -0
  30. package/dist/backend/gemini-cli.js.map +1 -0
  31. package/dist/backend/index.d.ts +7 -0
  32. package/dist/backend/index.js +7 -0
  33. package/dist/backend/index.js.map +1 -0
  34. package/dist/backend/kiro.d.ts +17 -0
  35. package/dist/backend/kiro.js +147 -0
  36. package/dist/backend/kiro.js.map +1 -0
  37. package/dist/backend/marker-utils.d.ts +13 -0
  38. package/dist/backend/marker-utils.js +64 -0
  39. package/dist/backend/marker-utils.js.map +1 -0
  40. package/dist/backend/mock.d.ts +25 -0
  41. package/dist/backend/mock.js +85 -0
  42. package/dist/backend/mock.js.map +1 -0
  43. package/dist/backend/opencode.d.ts +16 -0
  44. package/dist/backend/opencode.js +136 -0
  45. package/dist/backend/opencode.js.map +1 -0
  46. package/dist/backend/types.d.ts +86 -0
  47. package/dist/backend/types.js +33 -0
  48. package/dist/backend/types.js.map +1 -0
  49. package/dist/channel/access-manager.d.ts +18 -0
  50. package/dist/channel/access-manager.js +153 -0
  51. package/dist/channel/access-manager.js.map +1 -0
  52. package/dist/channel/adapters/telegram.d.ts +63 -0
  53. package/dist/channel/adapters/telegram.js +646 -0
  54. package/dist/channel/adapters/telegram.js.map +1 -0
  55. package/dist/channel/attachment-handler.d.ts +15 -0
  56. package/dist/channel/attachment-handler.js +88 -0
  57. package/dist/channel/attachment-handler.js.map +1 -0
  58. package/dist/channel/factory.d.ts +12 -0
  59. package/dist/channel/factory.js +67 -0
  60. package/dist/channel/factory.js.map +1 -0
  61. package/dist/channel/ipc-bridge.d.ts +26 -0
  62. package/dist/channel/ipc-bridge.js +220 -0
  63. package/dist/channel/ipc-bridge.js.map +1 -0
  64. package/dist/channel/mcp-server.d.ts +10 -0
  65. package/dist/channel/mcp-server.js +288 -0
  66. package/dist/channel/mcp-server.js.map +1 -0
  67. package/dist/channel/mcp-tools.d.ts +17 -0
  68. package/dist/channel/mcp-tools.js +110 -0
  69. package/dist/channel/mcp-tools.js.map +1 -0
  70. package/dist/channel/message-bus.d.ts +17 -0
  71. package/dist/channel/message-bus.js +86 -0
  72. package/dist/channel/message-bus.js.map +1 -0
  73. package/dist/channel/message-queue.d.ts +39 -0
  74. package/dist/channel/message-queue.js +253 -0
  75. package/dist/channel/message-queue.js.map +1 -0
  76. package/dist/channel/tool-router.d.ts +6 -0
  77. package/dist/channel/tool-router.js +75 -0
  78. package/dist/channel/tool-router.js.map +1 -0
  79. package/dist/channel/tool-tracker.d.ts +13 -0
  80. package/dist/channel/tool-tracker.js +58 -0
  81. package/dist/channel/tool-tracker.js.map +1 -0
  82. package/dist/channel/types.d.ts +118 -0
  83. package/dist/channel/types.js +2 -0
  84. package/dist/channel/types.js.map +1 -0
  85. package/dist/chat-export.d.ts +4 -0
  86. package/dist/chat-export.js +91 -0
  87. package/dist/chat-export.js.map +1 -0
  88. package/dist/classic-channel-manager.d.ts +59 -0
  89. package/dist/classic-channel-manager.js +193 -0
  90. package/dist/classic-channel-manager.js.map +1 -0
  91. package/dist/cli.d.ts +2 -0
  92. package/dist/cli.js +1833 -0
  93. package/dist/cli.js.map +1 -0
  94. package/dist/config.d.ts +9 -0
  95. package/dist/config.js +118 -0
  96. package/dist/config.js.map +1 -0
  97. package/dist/context-guardian.d.ts +26 -0
  98. package/dist/context-guardian.js +73 -0
  99. package/dist/context-guardian.js.map +1 -0
  100. package/dist/cost-guard.d.ts +36 -0
  101. package/dist/cost-guard.js +147 -0
  102. package/dist/cost-guard.js.map +1 -0
  103. package/dist/daemon-entry.d.ts +1 -0
  104. package/dist/daemon-entry.js +29 -0
  105. package/dist/daemon-entry.js.map +1 -0
  106. package/dist/daemon.d.ts +152 -0
  107. package/dist/daemon.js +1714 -0
  108. package/dist/daemon.js.map +1 -0
  109. package/dist/daily-summary.d.ts +13 -0
  110. package/dist/daily-summary.js +55 -0
  111. package/dist/daily-summary.js.map +1 -0
  112. package/dist/event-log.d.ts +36 -0
  113. package/dist/event-log.js +100 -0
  114. package/dist/event-log.js.map +1 -0
  115. package/dist/export-import.d.ts +2 -0
  116. package/dist/export-import.js +162 -0
  117. package/dist/export-import.js.map +1 -0
  118. package/dist/fleet-context.d.ts +61 -0
  119. package/dist/fleet-context.js +4 -0
  120. package/dist/fleet-context.js.map +1 -0
  121. package/dist/fleet-dashboard-html.d.ts +6 -0
  122. package/dist/fleet-dashboard-html.js +443 -0
  123. package/dist/fleet-dashboard-html.js.map +1 -0
  124. package/dist/fleet-health-server.d.ts +35 -0
  125. package/dist/fleet-health-server.js +290 -0
  126. package/dist/fleet-health-server.js.map +1 -0
  127. package/dist/fleet-instructions.d.ts +5 -0
  128. package/dist/fleet-instructions.js +161 -0
  129. package/dist/fleet-instructions.js.map +1 -0
  130. package/dist/fleet-manager.d.ts +212 -0
  131. package/dist/fleet-manager.js +3655 -0
  132. package/dist/fleet-manager.js.map +1 -0
  133. package/dist/fleet-rpc-handlers.d.ts +42 -0
  134. package/dist/fleet-rpc-handlers.js +356 -0
  135. package/dist/fleet-rpc-handlers.js.map +1 -0
  136. package/dist/fleet-system-prompt.d.ts +11 -0
  137. package/dist/fleet-system-prompt.js +61 -0
  138. package/dist/fleet-system-prompt.js.map +1 -0
  139. package/dist/general-knowledge/skills.md +177 -0
  140. package/dist/hang-detector.d.ts +16 -0
  141. package/dist/hang-detector.js +53 -0
  142. package/dist/hang-detector.js.map +1 -0
  143. package/dist/index.d.ts +8 -0
  144. package/dist/index.js +6 -0
  145. package/dist/index.js.map +1 -0
  146. package/dist/instance-lifecycle.d.ts +90 -0
  147. package/dist/instance-lifecycle.js +592 -0
  148. package/dist/instance-lifecycle.js.map +1 -0
  149. package/dist/instructions.d.ts +15 -0
  150. package/dist/instructions.js +90 -0
  151. package/dist/instructions.js.map +1 -0
  152. package/dist/logger.d.ts +7 -0
  153. package/dist/logger.js +84 -0
  154. package/dist/logger.js.map +1 -0
  155. package/dist/outbound-handlers.d.ts +51 -0
  156. package/dist/outbound-handlers.js +739 -0
  157. package/dist/outbound-handlers.js.map +1 -0
  158. package/dist/outbound-schemas.d.ts +238 -0
  159. package/dist/outbound-schemas.js +248 -0
  160. package/dist/outbound-schemas.js.map +1 -0
  161. package/dist/paths.d.ts +10 -0
  162. package/dist/paths.js +42 -0
  163. package/dist/paths.js.map +1 -0
  164. package/dist/plugin/agend/.claude-plugin/plugin.json +5 -0
  165. package/dist/quickstart.d.ts +1 -0
  166. package/dist/quickstart.js +595 -0
  167. package/dist/quickstart.js.map +1 -0
  168. package/dist/routing-engine.d.ts +22 -0
  169. package/dist/routing-engine.js +44 -0
  170. package/dist/routing-engine.js.map +1 -0
  171. package/dist/safe-async.d.ts +6 -0
  172. package/dist/safe-async.js +20 -0
  173. package/dist/safe-async.js.map +1 -0
  174. package/dist/scheduler/db.d.ts +37 -0
  175. package/dist/scheduler/db.js +360 -0
  176. package/dist/scheduler/db.js.map +1 -0
  177. package/dist/scheduler/db.test.d.ts +1 -0
  178. package/dist/scheduler/db.test.js +92 -0
  179. package/dist/scheduler/db.test.js.map +1 -0
  180. package/dist/scheduler/index.d.ts +4 -0
  181. package/dist/scheduler/index.js +4 -0
  182. package/dist/scheduler/index.js.map +1 -0
  183. package/dist/scheduler/scheduler.d.ts +44 -0
  184. package/dist/scheduler/scheduler.js +197 -0
  185. package/dist/scheduler/scheduler.js.map +1 -0
  186. package/dist/scheduler/scheduler.test.d.ts +1 -0
  187. package/dist/scheduler/scheduler.test.js +119 -0
  188. package/dist/scheduler/scheduler.test.js.map +1 -0
  189. package/dist/scheduler/types.d.ts +107 -0
  190. package/dist/scheduler/types.js +7 -0
  191. package/dist/scheduler/types.js.map +1 -0
  192. package/dist/service-installer.d.ts +17 -0
  193. package/dist/service-installer.js +182 -0
  194. package/dist/service-installer.js.map +1 -0
  195. package/dist/setup-wizard.d.ts +48 -0
  196. package/dist/setup-wizard.js +701 -0
  197. package/dist/setup-wizard.js.map +1 -0
  198. package/dist/statusline-watcher.d.ts +34 -0
  199. package/dist/statusline-watcher.js +73 -0
  200. package/dist/statusline-watcher.js.map +1 -0
  201. package/dist/stt.d.ts +10 -0
  202. package/dist/stt.js +33 -0
  203. package/dist/stt.js.map +1 -0
  204. package/dist/tmux-control.d.ts +52 -0
  205. package/dist/tmux-control.js +207 -0
  206. package/dist/tmux-control.js.map +1 -0
  207. package/dist/tmux-manager.d.ts +44 -0
  208. package/dist/tmux-manager.js +218 -0
  209. package/dist/tmux-manager.js.map +1 -0
  210. package/dist/topic-archiver.d.ts +40 -0
  211. package/dist/topic-archiver.js +103 -0
  212. package/dist/topic-archiver.js.map +1 -0
  213. package/dist/topic-commands.d.ts +28 -0
  214. package/dist/topic-commands.js +359 -0
  215. package/dist/topic-commands.js.map +1 -0
  216. package/dist/transcript-monitor.d.ts +23 -0
  217. package/dist/transcript-monitor.js +164 -0
  218. package/dist/transcript-monitor.js.map +1 -0
  219. package/dist/types.d.ts +211 -0
  220. package/dist/types.js +2 -0
  221. package/dist/types.js.map +1 -0
  222. package/dist/ui/dashboard.html +719 -0
  223. package/dist/web-api.d.ts +101 -0
  224. package/dist/web-api.js +648 -0
  225. package/dist/web-api.js.map +1 -0
  226. package/dist/webhook-emitter.d.ts +15 -0
  227. package/dist/webhook-emitter.js +41 -0
  228. package/dist/webhook-emitter.js.map +1 -0
  229. package/dist/workflow-templates/default.md +35 -0
  230. package/package.json +76 -0
  231. package/templates/launchd.plist.ejs +31 -0
  232. package/templates/systemd.service.ejs +16 -0
@@ -0,0 +1,147 @@
1
+ import { join } from "node:path";
2
+ import { existsSync, mkdirSync, readFileSync, writeFileSync, chmodSync, unlinkSync } from "node:fs";
3
+ import { resolveBinary, validateModel } from "./types.js";
4
+ export class KiroBackend {
5
+ instanceDir;
6
+ binaryName = "kiro-cli";
7
+ binaryPath;
8
+ constructor(instanceDir) {
9
+ this.instanceDir = instanceDir;
10
+ this.binaryPath = resolveBinary("kiro-cli");
11
+ }
12
+ buildCommand(config) {
13
+ let cmd = `${this.binaryPath} chat --legacy-ui`;
14
+ if (config.skipPermissions !== false)
15
+ cmd += " --trust-all-tools";
16
+ // --resume is boolean: Kiro auto-resumes latest conversation for this working directory
17
+ if (!config.skipResume)
18
+ cmd += " --resume";
19
+ if (config.model)
20
+ cmd += ` --model ${validateModel(config.model)}`;
21
+ cmd += " --require-mcp-startup";
22
+ return cmd;
23
+ }
24
+ writeConfig(config) {
25
+ // Kiro CLI reads workspace MCP config from .kiro/settings/mcp.json
26
+ // Format: { "mcpServers": { "name": { command, args, env } } }
27
+ //
28
+ // WORKAROUND: kiro-cli ignores the "env" block in mcp.json — the MCP server
29
+ // subprocess inherits the fleet manager's process env, which has a stale
30
+ // AGEND_SOCKET_PATH from whichever daemon wrote to it last.
31
+ // Fix: generate a wrapper script that exports the correct env vars before
32
+ // exec-ing the real MCP server.
33
+ const mcpDir = join(config.workingDirectory, ".kiro", "settings");
34
+ mkdirSync(mcpDir, { recursive: true });
35
+ const mcpConfigPath = join(mcpDir, "mcp.json");
36
+ let mcpConfig = {};
37
+ try {
38
+ mcpConfig = JSON.parse(readFileSync(mcpConfigPath, "utf-8"));
39
+ }
40
+ catch { /* new file */ }
41
+ const servers = (mcpConfig.mcpServers ?? {});
42
+ // Remove stale agend entries whose wrapper scripts no longer exist
43
+ for (const [key, val] of Object.entries(servers)) {
44
+ if (key.startsWith("agend-")) {
45
+ const cmd = val?.command;
46
+ if (typeof cmd === "string" && !existsSync(cmd)) {
47
+ delete servers[key];
48
+ }
49
+ }
50
+ }
51
+ for (const [name, entry] of Object.entries(config.mcpServers)) {
52
+ const instanceKey = `${name}-${config.instanceName}`;
53
+ const allEnv = { ...entry.env, AGEND_INSTANCE_NAME: config.instanceName };
54
+ // Write a wrapper script that sets env vars explicitly
55
+ const wrapperPath = join(this.instanceDir, `mcp-wrapper-${name}.sh`);
56
+ const envExports = Object.entries(allEnv)
57
+ .map(([k, v]) => `export ${k}='${String(v).replace(/'/g, "'\\''")}'`)
58
+ .join("\n");
59
+ // 0o700 (owner-only rwx): wrapper inlines sensitive env (tokens, socket paths).
60
+ // Other users on the host must not be able to read it. Set mode at creation
61
+ // to avoid a world-readable window between writeFileSync and chmodSync.
62
+ writeFileSync(wrapperPath, `#!/bin/bash\n${envExports}\n# Wait for IPC socket to be ready (up to 10s)\nfor i in $(seq 1 20); do [ -S "$AGEND_SOCKET_PATH" ] && break; sleep 0.5; done\nexec ${entry.command} ${entry.args.map((a) => JSON.stringify(a)).join(" ")}\n`, { mode: 0o700 });
63
+ // Re-chmod in case the file already existed with looser permissions (writeFileSync's
64
+ // mode only applies on create).
65
+ chmodSync(wrapperPath, 0o700);
66
+ servers[instanceKey] = {
67
+ command: wrapperPath,
68
+ args: [],
69
+ };
70
+ }
71
+ // Clean up old non-namespaced key if present
72
+ delete servers["agend"];
73
+ mcpConfig.mcpServers = servers;
74
+ writeFileSync(mcpConfigPath, JSON.stringify(mcpConfig, null, 2));
75
+ // Write fleet instructions to .kiro/steering/ (auto-loaded by Kiro CLI)
76
+ if (config.instructions) {
77
+ try {
78
+ const steeringDir = join(config.workingDirectory, ".kiro", "steering");
79
+ mkdirSync(steeringDir, { recursive: true });
80
+ writeFileSync(join(steeringDir, `agend-${config.instanceName}.md`), config.instructions);
81
+ }
82
+ catch { /* best effort */ }
83
+ }
84
+ }
85
+ getReadyPattern() {
86
+ // Kiro 1.x: "All tools are now trusted" / Kiro 2.x: "Trust All Tools active"
87
+ return /All tools are now trusted|Trust All Tools active|Credits:.*Time:|ask a question or describe a task/m;
88
+ }
89
+ getErrorPatterns() {
90
+ return [];
91
+ }
92
+ getStartupDialogs() {
93
+ return [
94
+ {
95
+ // Kiro CLI --trust-all-tools now shows a confirmation prompt.
96
+ // Default cursor is on "No, exit" — press Down then Enter to select "Yes, I accept".
97
+ pattern: /[❯›]\s*No, exit/m,
98
+ keys: ["Down", "Enter"],
99
+ description: "Kiro --trust-all-tools confirmation — navigate to 'Yes, I accept'",
100
+ },
101
+ ];
102
+ }
103
+ getRuntimeDialogs() {
104
+ return [
105
+ {
106
+ // Same trust prompt can also appear mid-session if Kiro re-validates.
107
+ pattern: /Do you trust the files|Yes, I accept[\s\S]*No, exit/m,
108
+ keys: ["Down", "Enter"],
109
+ description: "Kiro trust confirmation dialog — auto-accept",
110
+ },
111
+ ];
112
+ }
113
+ getContextUsage() {
114
+ return null;
115
+ }
116
+ getSessionId() {
117
+ // Kiro manages sessions internally via SQLite keyed by working directory.
118
+ // No external session ID needed — --resume handles it automatically.
119
+ return null;
120
+ }
121
+ getQuitCommand() { return "/quit"; }
122
+ cleanup(config) {
123
+ // Only remove namespaced keys — non-namespaced "agend" key may belong to
124
+ // another instance sharing this working directory.
125
+ try {
126
+ const mcpConfigPath = join(config.workingDirectory, ".kiro", "settings", "mcp.json");
127
+ if (existsSync(mcpConfigPath)) {
128
+ const mcpConfig = JSON.parse(readFileSync(mcpConfigPath, "utf-8"));
129
+ if (mcpConfig.mcpServers) {
130
+ for (const name of Object.keys(config.mcpServers)) {
131
+ delete mcpConfig.mcpServers[`${name}-${config.instanceName}`];
132
+ }
133
+ writeFileSync(mcpConfigPath, JSON.stringify(mcpConfig, null, 2));
134
+ }
135
+ }
136
+ }
137
+ catch { /* best effort */ }
138
+ // Remove fleet instructions steering file
139
+ try {
140
+ const steeringFile = join(config.workingDirectory, ".kiro", "steering", `agend-${config.instanceName}.md`);
141
+ if (existsSync(steeringFile))
142
+ unlinkSync(steeringFile);
143
+ }
144
+ catch { /* best effort */ }
145
+ }
146
+ }
147
+ //# sourceMappingURL=kiro.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"kiro.js","sourceRoot":"","sources":["../../src/backend/kiro.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Append content wrapped in marker tags to a file. Idempotent — removes any
3
+ * existing block with the same id before appending.
4
+ */
5
+ export declare function appendWithMarker(filePath: string, id: string, content: string): void;
6
+ /**
7
+ * Remove a marker block from a file. Returns true if the file is empty/whitespace
8
+ * after removal (caller may want to delete it).
9
+ * Returns false if the file was not modified or still has content.
10
+ */
11
+ export declare function removeMarker(filePath: string, id: string, logger?: {
12
+ warn: (msg: string) => void;
13
+ }): boolean;
@@ -0,0 +1,64 @@
1
+ import { existsSync, readFileSync, writeFileSync } from "node:fs";
2
+ /** Escape special regex characters in a string */
3
+ function escapeRegex(s) {
4
+ return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
5
+ }
6
+ function beginTag(id) { return `<!-- AGEND:${id}:BEGIN -->`; }
7
+ function endTag(id) { return `<!-- AGEND:${id}:END -->`; }
8
+ /**
9
+ * Append content wrapped in marker tags to a file. Idempotent — removes any
10
+ * existing block with the same id before appending.
11
+ */
12
+ export function appendWithMarker(filePath, id, content) {
13
+ let existing = "";
14
+ try {
15
+ existing = readFileSync(filePath, "utf-8");
16
+ }
17
+ catch { /* new file */ }
18
+ // Remove old block first (idempotent)
19
+ existing = stripMarkerBlock(existing, id);
20
+ // Ensure preceding newline
21
+ const sep = existing.length > 0 && !existing.endsWith("\n") ? "\n\n" : existing.length > 0 ? "\n" : "";
22
+ const block = `${beginTag(id)}\n${content}\n${endTag(id)}\n`;
23
+ writeFileSync(filePath, existing + sep + block);
24
+ }
25
+ /**
26
+ * Remove a marker block from a file. Returns true if the file is empty/whitespace
27
+ * after removal (caller may want to delete it).
28
+ * Returns false if the file was not modified or still has content.
29
+ */
30
+ export function removeMarker(filePath, id, logger) {
31
+ if (!existsSync(filePath))
32
+ return false;
33
+ let content;
34
+ try {
35
+ content = readFileSync(filePath, "utf-8");
36
+ }
37
+ catch {
38
+ return false;
39
+ }
40
+ const escaped = escapeRegex(id);
41
+ const re = new RegExp(`\\n?<!-- AGEND:${escaped}:BEGIN -->\\n[\\s\\S]*?<!-- AGEND:${escaped}:END -->\\n?`, "g");
42
+ const updated = content.replace(re, "");
43
+ if (updated === content) {
44
+ // Regex didn't match — try fallback: find BEGIN marker to end of file
45
+ const beginStr = beginTag(id);
46
+ const idx = content.indexOf(beginStr);
47
+ if (idx >= 0) {
48
+ const fallback = content.slice(0, idx).replace(/\n+$/, "\n");
49
+ writeFileSync(filePath, fallback);
50
+ logger?.warn(`marker-utils: END marker missing for ${id} in ${filePath}, removed from BEGIN to EOF`);
51
+ return fallback.trim().length === 0;
52
+ }
53
+ return false;
54
+ }
55
+ writeFileSync(filePath, updated);
56
+ return updated.trim().length === 0;
57
+ }
58
+ /** Strip a marker block from a string (in-memory, no file I/O) */
59
+ function stripMarkerBlock(content, id) {
60
+ const escaped = escapeRegex(id);
61
+ const re = new RegExp(`\\n?<!-- AGEND:${escaped}:BEGIN -->\\n[\\s\\S]*?<!-- AGEND:${escaped}:END -->\\n?`, "g");
62
+ return content.replace(re, "");
63
+ }
64
+ //# sourceMappingURL=marker-utils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"marker-utils.js","sourceRoot":"","sources":["../../src/backend/marker-utils.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,25 @@
1
+ import type { CliBackend, CliBackendConfig, ErrorPattern } from "./types.js";
2
+ /**
3
+ * Mock backend for E2E testing.
4
+ *
5
+ * Instead of spawning a real CLI (claude, gemini, etc.), it launches a small
6
+ * Node.js script (`mock-claude.mjs`) that:
7
+ * - Starts the real agend MCP server (connects to daemon IPC)
8
+ * - Writes periodic statusline.json updates
9
+ * - Accepts stdin messages and outputs canned responses
10
+ *
11
+ * This lets E2E tests exercise the full agend orchestration (daemon, IPC,
12
+ * routing, tmux) without needing a real AI API or CLI binary.
13
+ */
14
+ export declare class MockBackend implements CliBackend {
15
+ private instanceDir;
16
+ readonly binaryName = "node";
17
+ constructor(instanceDir: string);
18
+ buildCommand(config: CliBackendConfig): string;
19
+ writeConfig(config: CliBackendConfig): void;
20
+ getContextUsage(): number | null;
21
+ getSessionId(): string | null;
22
+ getQuitCommand(): string;
23
+ getReadyPattern(): RegExp;
24
+ getErrorPatterns(): ErrorPattern[];
25
+ }
@@ -0,0 +1,85 @@
1
+ import { join, dirname } from "node:path";
2
+ import { existsSync, readFileSync, writeFileSync } from "node:fs";
3
+ import { fileURLToPath } from "node:url";
4
+ const __filename = fileURLToPath(import.meta.url);
5
+ const __dirname = dirname(__filename);
6
+ /**
7
+ * Mock backend for E2E testing.
8
+ *
9
+ * Instead of spawning a real CLI (claude, gemini, etc.), it launches a small
10
+ * Node.js script (`mock-claude.mjs`) that:
11
+ * - Starts the real agend MCP server (connects to daemon IPC)
12
+ * - Writes periodic statusline.json updates
13
+ * - Accepts stdin messages and outputs canned responses
14
+ *
15
+ * This lets E2E tests exercise the full agend orchestration (daemon, IPC,
16
+ * routing, tmux) without needing a real AI API or CLI binary.
17
+ */
18
+ export class MockBackend {
19
+ instanceDir;
20
+ binaryName = "node";
21
+ constructor(instanceDir) {
22
+ this.instanceDir = instanceDir;
23
+ }
24
+ buildCommand(config) {
25
+ // Try dist path first, then src-relative path for dev mode
26
+ const distScript = join(__dirname, "..", "e2e", "mock-servers", "mock-claude.mjs");
27
+ const srcScript = join(__dirname, "..", "..", "e2e", "mock-servers", "mock-claude.mjs");
28
+ const script = existsSync(distScript) ? distScript : srcScript;
29
+ const q = (v) => `'${v.replace(/'/g, "'\\''")}'`;
30
+ const envPrefix = [
31
+ `AGEND_SOCKET_PATH=${q(join(this.instanceDir, "channel.sock"))}`,
32
+ `AGEND_INSTANCE_NAME=${q(config.instanceName)}`,
33
+ `MOCK_INSTANCE_DIR=${q(this.instanceDir)}`,
34
+ ];
35
+ if (process.env.MOCK_RESPONSE)
36
+ envPrefix.push(`MOCK_RESPONSE=${q(process.env.MOCK_RESPONSE)}`);
37
+ if (process.env.MOCK_DELAY)
38
+ envPrefix.push(`MOCK_DELAY=${q(process.env.MOCK_DELAY)}`);
39
+ return `${envPrefix.join(" ")} node ${q(script)}`;
40
+ }
41
+ writeConfig(config) {
42
+ const mcpConfigPath = join(this.instanceDir, "mcp-config.json");
43
+ writeFileSync(mcpConfigPath, JSON.stringify({ mcpServers: config.mcpServers }, null, 2));
44
+ // Write initial statusline.json matching real Claude Code format
45
+ const statusline = {
46
+ session_id: `mock-${config.instanceName}-${Date.now()}`,
47
+ model: "mock-model",
48
+ cost_usd: 0,
49
+ total_tokens: 0,
50
+ context_window: {
51
+ used_percentage: 0,
52
+ context_window_size: 200000,
53
+ },
54
+ };
55
+ writeFileSync(join(this.instanceDir, "statusline.json"), JSON.stringify(statusline));
56
+ }
57
+ getContextUsage() {
58
+ try {
59
+ const data = JSON.parse(readFileSync(join(this.instanceDir, "statusline.json"), "utf-8"));
60
+ return data.context_window?.used_percentage ?? null;
61
+ }
62
+ catch {
63
+ return null;
64
+ }
65
+ }
66
+ getSessionId() {
67
+ try {
68
+ return readFileSync(join(this.instanceDir, "session-id"), "utf-8").trim() || null;
69
+ }
70
+ catch {
71
+ return null;
72
+ }
73
+ }
74
+ getQuitCommand() { return "/quit"; }
75
+ getReadyPattern() {
76
+ return /MOCK_READY|mock-claude ready/;
77
+ }
78
+ getErrorPatterns() {
79
+ return [
80
+ { pattern: /MOCK_RATE_LIMIT/i, type: "rate_limit", action: "failover", message: "Mock rate limit reached" },
81
+ { pattern: /MOCK_AUTH_ERROR/i, type: "auth_error", action: "notify", message: "Mock auth error" },
82
+ ];
83
+ }
84
+ }
85
+ //# sourceMappingURL=mock.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mock.js","sourceRoot":"","sources":["../../src/backend/mock.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,16 @@
1
+ import { type CliBackend, type CliBackendConfig, type ErrorPattern, type RuntimeDialog } from "./types.js";
2
+ export declare class OpenCodeBackend implements CliBackend {
3
+ private instanceDir;
4
+ readonly binaryName = "opencode";
5
+ private binaryPath;
6
+ constructor(instanceDir: string);
7
+ buildCommand(config: CliBackendConfig): string;
8
+ writeConfig(config: CliBackendConfig): void;
9
+ getReadyPattern(): RegExp;
10
+ getErrorPatterns(): ErrorPattern[];
11
+ getContextUsage(): number | null;
12
+ getSessionId(): string | null;
13
+ getQuitCommand(): string;
14
+ getRuntimeDialogs(): RuntimeDialog[];
15
+ cleanup(config: CliBackendConfig): void;
16
+ }
@@ -0,0 +1,136 @@
1
+ import { join } from "node:path";
2
+ import { existsSync, readFileSync, unlinkSync, writeFileSync } from "node:fs";
3
+ import { resolveBinary, validateModel } from "./types.js";
4
+ export class OpenCodeBackend {
5
+ instanceDir;
6
+ binaryName = "opencode";
7
+ binaryPath;
8
+ constructor(instanceDir) {
9
+ this.instanceDir = instanceDir;
10
+ this.binaryPath = resolveBinary("opencode");
11
+ }
12
+ buildCommand(config) {
13
+ // Use per-instance config via OPENCODE_CONFIG env (set in writeConfig)
14
+ let cmd = this.binaryPath;
15
+ // Resume last session if skipResume is not set
16
+ if (!config.skipResume) {
17
+ const sessionIdFile = join(this.instanceDir, "session-id");
18
+ if (existsSync(sessionIdFile)) {
19
+ const sid = readFileSync(sessionIdFile, "utf-8").trim();
20
+ if (sid)
21
+ cmd += ` --session ${sid}`;
22
+ }
23
+ else {
24
+ cmd += " --continue";
25
+ }
26
+ }
27
+ if (config.model) {
28
+ cmd += ` --model ${validateModel(config.model)}`;
29
+ }
30
+ return cmd;
31
+ }
32
+ writeConfig(config) {
33
+ // OpenCode reads opencode.json from the working directory.
34
+ // Use instance-specific MCP server key name to avoid conflicts when
35
+ // multiple instances share the same working directory.
36
+ const configPath = join(config.workingDirectory, "opencode.json");
37
+ let oc = {};
38
+ try {
39
+ oc = JSON.parse(readFileSync(configPath, "utf-8"));
40
+ }
41
+ catch { /* new file */ }
42
+ // MCP servers — use instance name as key to avoid multi-instance conflicts
43
+ const mcp = (oc.mcp ?? {});
44
+ for (const [name, entry] of Object.entries(config.mcpServers)) {
45
+ const safeInstanceName = config.instanceName.replace(/[^\x20-\x7E]/g, "").replace(/\s+/g, "-") || config.instanceName.replace(/[^a-zA-Z0-9-]/g, "x");
46
+ const instanceKey = `${name}-${safeInstanceName}`;
47
+ // Remove old non-sanitized key if present
48
+ const oldKey = `${name}-${config.instanceName}`;
49
+ if (oldKey !== instanceKey)
50
+ delete mcp[oldKey];
51
+ mcp[instanceKey] = {
52
+ type: "local",
53
+ command: [entry.command, ...entry.args],
54
+ environment: { ...entry.env, AGEND_INSTANCE_NAME: config.instanceName },
55
+ };
56
+ }
57
+ // Clean up old non-namespaced key if present
58
+ delete mcp["agend"];
59
+ oc.mcp = mcp;
60
+ delete oc.mcpServers;
61
+ // Add fleet instructions file to instructions (additive — appends to existing array)
62
+ if (config.instructions) {
63
+ try {
64
+ const instrFile = join(config.instanceDir, "fleet-instructions.md");
65
+ writeFileSync(instrFile, config.instructions);
66
+ const paths = (oc.instructions ?? []);
67
+ if (!paths.includes(instrFile))
68
+ paths.push(instrFile);
69
+ oc.instructions = paths;
70
+ }
71
+ catch { /* best effort */ }
72
+ }
73
+ writeFileSync(configPath, JSON.stringify(oc, null, 2));
74
+ }
75
+ getReadyPattern() {
76
+ return /Ask anything|ctrl\+p commands/m;
77
+ }
78
+ getErrorPatterns() {
79
+ return [
80
+ { pattern: /rate.?limit|too many requests|429/i, type: "rate_limit", action: "failover", message: "Rate limit reached" },
81
+ { pattern: /auth.*error|unauthorized|401/i, type: "auth_error", action: "pause", message: "Authentication error" },
82
+ ];
83
+ }
84
+ getContextUsage() {
85
+ return null;
86
+ }
87
+ getSessionId() {
88
+ try {
89
+ const f = join(this.instanceDir, "session-id");
90
+ return readFileSync(f, "utf-8").trim() || null;
91
+ }
92
+ catch {
93
+ return null;
94
+ }
95
+ }
96
+ getQuitCommand() { return "/quit"; }
97
+ getRuntimeDialogs() {
98
+ return [
99
+ { pattern: /Permission required/i, keys: ["Right", "Enter"], description: "OpenCode permission prompt — Allow always" },
100
+ { pattern: /confirm/i, keys: ["Enter"], description: "OpenCode confirm prompt" },
101
+ ];
102
+ }
103
+ cleanup(config) {
104
+ // Clean up instance-specific MCP entries from opencode.json.
105
+ // Only remove namespaced keys — non-namespaced "agend" key may belong to
106
+ // another instance sharing this working directory.
107
+ try {
108
+ const configPath = join(config.workingDirectory, "opencode.json");
109
+ if (existsSync(configPath)) {
110
+ const oc = JSON.parse(readFileSync(configPath, "utf-8"));
111
+ if (oc.mcp) {
112
+ for (const name of Object.keys(config.mcpServers)) {
113
+ const safeName = config.instanceName.replace(/[^\x20-\x7E]/g, "").replace(/\s+/g, "-") || config.instanceName.replace(/[^a-zA-Z0-9-]/g, "x");
114
+ delete oc.mcp[`${name}-${safeName}`];
115
+ delete oc.mcp[`${name}-${config.instanceName}`]; // clean up old non-sanitized keys
116
+ }
117
+ }
118
+ // Remove fleet instructions path from instructions
119
+ const instrFile = join(config.instanceDir, "fleet-instructions.md");
120
+ if (Array.isArray(oc.instructions)) {
121
+ oc.instructions = oc.instructions.filter((p) => p !== instrFile);
122
+ }
123
+ writeFileSync(configPath, JSON.stringify(oc, null, 2));
124
+ }
125
+ }
126
+ catch { /* best effort */ }
127
+ // Remove fleet instructions file
128
+ try {
129
+ const instrFile = join(config.instanceDir, "fleet-instructions.md");
130
+ if (existsSync(instrFile))
131
+ unlinkSync(instrFile);
132
+ }
133
+ catch { /* best effort */ }
134
+ }
135
+ }
136
+ //# sourceMappingURL=opencode.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"opencode.js","sourceRoot":"","sources":["../../src/backend/opencode.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,86 @@
1
+ export interface McpServerEntry {
2
+ command: string;
3
+ args: string[];
4
+ env: Record<string, string>;
5
+ }
6
+ export interface CliBackendConfig {
7
+ workingDirectory: string;
8
+ instanceDir: string;
9
+ instanceName: string;
10
+ mcpServers: Record<string, McpServerEntry>;
11
+ skipPermissions?: boolean;
12
+ model?: string;
13
+ /** When true, backend should not resume a previous session (crash recovery). */
14
+ skipResume?: boolean;
15
+ /** Fleet instructions content to inject into the CLI's additive system prompt mechanism. */
16
+ instructions?: string;
17
+ /** Agent communication mode: "mcp" (default) or "cli" (HTTP endpoint). */
18
+ agentMode?: "mcp" | "cli";
19
+ /** Health server port for CLI mode (agend-agent connects here). */
20
+ agentPort?: number;
21
+ }
22
+ /** Action to take when an error pattern is detected in PTY output. */
23
+ export type ErrorActionType = "notify" | "failover" | "restart" | "pause";
24
+ /** Categorizes detected errors for logging and response. */
25
+ export type ErrorType = "rate_limit" | "auth_error" | "crash" | "network" | "quota";
26
+ export interface ErrorPattern {
27
+ pattern: RegExp;
28
+ type: ErrorType;
29
+ action: ErrorActionType;
30
+ /** Human-readable description for notifications. */
31
+ message: string;
32
+ }
33
+ /** A dialog that may appear at runtime and needs auto-dismissal via key sequences. */
34
+ export interface RuntimeDialog {
35
+ /** Pattern to detect the dialog in PTY output. */
36
+ pattern: RegExp;
37
+ /** Key sequence to dismiss: strings are literal text, "Up"/"Down"/"Enter"/"Escape" are special keys. */
38
+ keys: string[];
39
+ /** Human-readable description for logging. */
40
+ description: string;
41
+ }
42
+ /** A dialog that may appear during CLI startup (trust prompts, session pickers, etc.). */
43
+ export type StartupDialog = RuntimeDialog;
44
+ export interface CliBackend {
45
+ /** The CLI binary name (e.g. "claude", "gemini", "codex") */
46
+ readonly binaryName: string;
47
+ /** Build the shell command string to launch the CLI in a tmux window. */
48
+ buildCommand(config: CliBackendConfig): string;
49
+ /** Write all config files the CLI needs before launch. */
50
+ writeConfig(config: CliBackendConfig): void;
51
+ /** Read context window usage percentage (0-100). Returns null if unavailable. */
52
+ getContextUsage(): number | null;
53
+ /** Read session ID for resume capability. Returns null if unavailable. */
54
+ getSessionId(): string | null;
55
+ /** Regex to detect when the CLI is ready to accept input. */
56
+ getReadyPattern(): RegExp;
57
+ /** Error patterns to detect in PTY output during operation. */
58
+ getErrorPatterns?(): ErrorPattern[];
59
+ /**
60
+ * Interactive dialogs that can appear during operation (not just startup).
61
+ * The daemon's error monitor auto-dismisses these by sending the specified keys.
62
+ */
63
+ getRuntimeDialogs?(): RuntimeDialog[];
64
+ /**
65
+ * Dialogs that may appear during CLI startup (trust prompts, confirmation dialogs).
66
+ * The daemon's dismissDialogsUntilReady auto-dismisses these before the CLI is ready.
67
+ */
68
+ getStartupDialogs?(): StartupDialog[];
69
+ /** Whether this backend re-reads instruction files on --resume (e.g. Claude Code's --append-system-prompt-file). */
70
+ readonly instructionsReloadedOnResume?: boolean;
71
+ /** Pre-approve a working directory to skip trust dialogs on startup. */
72
+ preTrust?(workingDirectory: string): void;
73
+ /** Command to gracefully quit the CLI (e.g. "/exit", "/quit"). */
74
+ getQuitCommand(): string;
75
+ /** Clean up config files on shutdown. */
76
+ cleanup?(config: CliBackendConfig): void;
77
+ }
78
+ /**
79
+ * Resolve the full path to a CLI binary.
80
+ * tmux new-window runs commands in a minimal shell without user PATH,
81
+ * so we resolve at daemon startup time when the full PATH is available.
82
+ */
83
+ export declare function resolveBinary(name: string): string;
84
+ export declare function validateModel(model: string): string;
85
+ /** POSIX single-quote escape for embedding arbitrary values in a shell command. */
86
+ export declare function shellQuote(s: string): string;
@@ -0,0 +1,33 @@
1
+ import { execFileSync } from "node:child_process";
2
+ /**
3
+ * Resolve the full path to a CLI binary.
4
+ * tmux new-window runs commands in a minimal shell without user PATH,
5
+ * so we resolve at daemon startup time when the full PATH is available.
6
+ */
7
+ export function resolveBinary(name) {
8
+ try {
9
+ return execFileSync("which", [name], { encoding: "utf-8" }).trim();
10
+ }
11
+ catch {
12
+ return name; // fallback to bare name
13
+ }
14
+ }
15
+ /**
16
+ * Whitelist for model names embedded into the shell command line.
17
+ * Allows letters, digits, dot, underscore, hyphen, colon, slash
18
+ * (e.g. "claude-3-5-sonnet", "gpt-4o-mini-2024-07-18", "openrouter/anthropic:beta").
19
+ * Throws if `model` contains anything else, since `buildCommand` returns a
20
+ * shell string consumed by tmux and we cannot rely on argv-style quoting.
21
+ */
22
+ const SAFE_MODEL_RE = /^[A-Za-z0-9._:/-]+$/;
23
+ export function validateModel(model) {
24
+ if (!SAFE_MODEL_RE.test(model)) {
25
+ throw new Error(`Invalid model name: ${JSON.stringify(model)} — must match ${SAFE_MODEL_RE}`);
26
+ }
27
+ return model;
28
+ }
29
+ /** POSIX single-quote escape for embedding arbitrary values in a shell command. */
30
+ export function shellQuote(s) {
31
+ return `'${s.replace(/'/g, "'\\''")}'`;
32
+ }
33
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/backend/types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAmGlD;;;;GAIG;AACH,MAAM,UAAU,aAAa,CAAC,IAAY;IACxC,IAAI,CAAC;QACH,OAAO,YAAY,CAAC,OAAO,EAAE,CAAC,IAAI,CAAC,EAAE,EAAE,QAAQ,EAAE,OAAO,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;IACrE,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC,CAAC,wBAAwB;IACvC,CAAC;AACH,CAAC;AAED;;;;;;GAMG;AACH,MAAM,aAAa,GAAG,qBAAqB,CAAC;AAC5C,MAAM,UAAU,aAAa,CAAC,KAAa;IACzC,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;QAC/B,MAAM,IAAI,KAAK,CAAC,uBAAuB,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,iBAAiB,aAAa,EAAE,CAAC,CAAC;IAChG,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,mFAAmF;AACnF,MAAM,UAAU,UAAU,CAAC,CAAS;IAClC,OAAO,IAAI,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,GAAG,CAAC;AACzC,CAAC"}