@jungjaehoon/mama-os 0.10.0 → 0.10.2

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 (259) hide show
  1. package/README.md +59 -41
  2. package/dist/agent/agent-loop.d.ts +25 -4
  3. package/dist/agent/agent-loop.d.ts.map +1 -1
  4. package/dist/agent/agent-loop.js +466 -136
  5. package/dist/agent/agent-loop.js.map +1 -1
  6. package/dist/agent/claude-cli-wrapper.d.ts +13 -4
  7. package/dist/agent/claude-cli-wrapper.d.ts.map +1 -1
  8. package/dist/agent/claude-cli-wrapper.js +17 -9
  9. package/dist/agent/claude-cli-wrapper.js.map +1 -1
  10. package/dist/agent/claude-client.js +3 -3
  11. package/dist/agent/claude-client.js.map +1 -1
  12. package/dist/agent/code-act/constants.d.ts +8 -0
  13. package/dist/agent/code-act/constants.d.ts.map +1 -0
  14. package/dist/agent/code-act/constants.js +65 -0
  15. package/dist/agent/code-act/constants.js.map +1 -0
  16. package/dist/agent/code-act/host-bridge.d.ts +35 -0
  17. package/dist/agent/code-act/host-bridge.d.ts.map +1 -0
  18. package/dist/agent/code-act/host-bridge.js +381 -0
  19. package/dist/agent/code-act/host-bridge.js.map +1 -0
  20. package/dist/agent/code-act/index.d.ts +8 -0
  21. package/dist/agent/code-act/index.d.ts.map +1 -0
  22. package/dist/agent/code-act/index.js +16 -0
  23. package/dist/agent/code-act/index.js.map +1 -0
  24. package/dist/agent/code-act/sandbox.d.ts +20 -0
  25. package/dist/agent/code-act/sandbox.d.ts.map +1 -0
  26. package/dist/agent/code-act/sandbox.js +226 -0
  27. package/dist/agent/code-act/sandbox.js.map +1 -0
  28. package/dist/agent/code-act/type-definition-generator.d.ts +12 -0
  29. package/dist/agent/code-act/type-definition-generator.d.ts.map +1 -0
  30. package/dist/agent/code-act/type-definition-generator.js +45 -0
  31. package/dist/agent/code-act/type-definition-generator.js.map +1 -0
  32. package/dist/agent/code-act/types.d.ts +37 -0
  33. package/dist/agent/code-act/types.d.ts.map +1 -0
  34. package/dist/agent/code-act/types.js +10 -0
  35. package/dist/agent/code-act/types.js.map +1 -0
  36. package/dist/agent/codex-mcp-process.d.ts +20 -0
  37. package/dist/agent/codex-mcp-process.d.ts.map +1 -1
  38. package/dist/agent/codex-mcp-process.js +324 -59
  39. package/dist/agent/codex-mcp-process.js.map +1 -1
  40. package/dist/agent/gateway-tool-executor.d.ts +24 -17
  41. package/dist/agent/gateway-tool-executor.d.ts.map +1 -1
  42. package/dist/agent/gateway-tool-executor.js +259 -130
  43. package/dist/agent/gateway-tool-executor.js.map +1 -1
  44. package/dist/agent/gateway-tools.md +35 -0
  45. package/dist/agent/mama-tool-handlers.d.ts +11 -0
  46. package/dist/agent/mama-tool-handlers.d.ts.map +1 -0
  47. package/dist/agent/mama-tool-handlers.js +125 -0
  48. package/dist/agent/mama-tool-handlers.js.map +1 -0
  49. package/dist/agent/mcp-executor.d.ts +0 -16
  50. package/dist/agent/mcp-executor.d.ts.map +1 -1
  51. package/dist/agent/mcp-executor.js +5 -114
  52. package/dist/agent/mcp-executor.js.map +1 -1
  53. package/dist/agent/persistent-cli-adapter.d.ts.map +1 -1
  54. package/dist/agent/persistent-cli-adapter.js +7 -1
  55. package/dist/agent/persistent-cli-adapter.js.map +1 -1
  56. package/dist/agent/persistent-cli-process.d.ts +7 -0
  57. package/dist/agent/persistent-cli-process.d.ts.map +1 -1
  58. package/dist/agent/persistent-cli-process.js +23 -0
  59. package/dist/agent/persistent-cli-process.js.map +1 -1
  60. package/dist/agent/prompt-enhancer.d.ts +12 -3
  61. package/dist/agent/prompt-enhancer.d.ts.map +1 -1
  62. package/dist/agent/prompt-enhancer.js +239 -23
  63. package/dist/agent/prompt-enhancer.js.map +1 -1
  64. package/dist/agent/session-pool.d.ts +1 -1
  65. package/dist/agent/session-pool.d.ts.map +1 -1
  66. package/dist/agent/session-pool.js +8 -2
  67. package/dist/agent/session-pool.js.map +1 -1
  68. package/dist/agent/types.d.ts +33 -5
  69. package/dist/agent/types.d.ts.map +1 -1
  70. package/dist/agent/types.js.map +1 -1
  71. package/dist/api/graph-api-types.d.ts +25 -0
  72. package/dist/api/graph-api-types.d.ts.map +1 -1
  73. package/dist/api/graph-api.d.ts.map +1 -1
  74. package/dist/api/graph-api.js +96 -9
  75. package/dist/api/graph-api.js.map +1 -1
  76. package/dist/api/skills-handler.d.ts.map +1 -1
  77. package/dist/api/skills-handler.js +26 -0
  78. package/dist/api/skills-handler.js.map +1 -1
  79. package/dist/api/upload-handler.d.ts.map +1 -1
  80. package/dist/api/upload-handler.js +60 -25
  81. package/dist/api/upload-handler.js.map +1 -1
  82. package/dist/cli/commands/init.d.ts.map +1 -1
  83. package/dist/cli/commands/init.js +16 -0
  84. package/dist/cli/commands/init.js.map +1 -1
  85. package/dist/cli/commands/run.js +1 -1
  86. package/dist/cli/commands/run.js.map +1 -1
  87. package/dist/cli/commands/start.d.ts.map +1 -1
  88. package/dist/cli/commands/start.js +747 -53
  89. package/dist/cli/commands/start.js.map +1 -1
  90. package/dist/cli/commands/status.d.ts.map +1 -1
  91. package/dist/cli/commands/status.js +27 -1
  92. package/dist/cli/commands/status.js.map +1 -1
  93. package/dist/cli/commands/stop.d.ts +7 -1
  94. package/dist/cli/commands/stop.d.ts.map +1 -1
  95. package/dist/cli/commands/stop.js +162 -0
  96. package/dist/cli/commands/stop.js.map +1 -1
  97. package/dist/cli/config/config-manager.d.ts +9 -0
  98. package/dist/cli/config/config-manager.d.ts.map +1 -1
  99. package/dist/cli/config/config-manager.js +124 -16
  100. package/dist/cli/config/config-manager.js.map +1 -1
  101. package/dist/cli/config/types.d.ts +24 -9
  102. package/dist/cli/config/types.d.ts.map +1 -1
  103. package/dist/cli/config/types.js +3 -3
  104. package/dist/cli/config/types.js.map +1 -1
  105. package/dist/gateways/attachment-utils.d.ts +28 -0
  106. package/dist/gateways/attachment-utils.d.ts.map +1 -0
  107. package/dist/gateways/attachment-utils.js +201 -0
  108. package/dist/gateways/attachment-utils.js.map +1 -0
  109. package/dist/gateways/context-injector.d.ts.map +1 -1
  110. package/dist/gateways/context-injector.js +8 -27
  111. package/dist/gateways/context-injector.js.map +1 -1
  112. package/dist/gateways/discord.d.ts +0 -14
  113. package/dist/gateways/discord.d.ts.map +1 -1
  114. package/dist/gateways/discord.js +61 -172
  115. package/dist/gateways/discord.js.map +1 -1
  116. package/dist/gateways/image-analyzer.d.ts.map +1 -1
  117. package/dist/gateways/image-analyzer.js +12 -4
  118. package/dist/gateways/image-analyzer.js.map +1 -1
  119. package/dist/gateways/message-router.d.ts +4 -0
  120. package/dist/gateways/message-router.d.ts.map +1 -1
  121. package/dist/gateways/message-router.js +77 -58
  122. package/dist/gateways/message-router.js.map +1 -1
  123. package/dist/gateways/slack.d.ts +5 -0
  124. package/dist/gateways/slack.d.ts.map +1 -1
  125. package/dist/gateways/slack.js +136 -25
  126. package/dist/gateways/slack.js.map +1 -1
  127. package/dist/gateways/tool-status-tracker.d.ts +87 -0
  128. package/dist/gateways/tool-status-tracker.d.ts.map +1 -0
  129. package/dist/gateways/tool-status-tracker.js +283 -0
  130. package/dist/gateways/tool-status-tracker.js.map +1 -0
  131. package/dist/gateways/types.d.ts +2 -0
  132. package/dist/gateways/types.d.ts.map +1 -1
  133. package/dist/mcp/code-act-server.d.ts +14 -0
  134. package/dist/mcp/code-act-server.d.ts.map +1 -0
  135. package/dist/mcp/code-act-server.js +206 -0
  136. package/dist/mcp/code-act-server.js.map +1 -0
  137. package/dist/multi-agent/agent-process-manager.d.ts +19 -11
  138. package/dist/multi-agent/agent-process-manager.d.ts.map +1 -1
  139. package/dist/multi-agent/agent-process-manager.js +241 -137
  140. package/dist/multi-agent/agent-process-manager.js.map +1 -1
  141. package/dist/multi-agent/background-task-manager.d.ts +2 -2
  142. package/dist/multi-agent/background-task-manager.js +2 -2
  143. package/dist/multi-agent/bmad-templates.d.ts +67 -0
  144. package/dist/multi-agent/bmad-templates.d.ts.map +1 -0
  145. package/dist/multi-agent/bmad-templates.js +248 -0
  146. package/dist/multi-agent/bmad-templates.js.map +1 -0
  147. package/dist/multi-agent/council-engine.d.ts +60 -0
  148. package/dist/multi-agent/council-engine.d.ts.map +1 -0
  149. package/dist/multi-agent/council-engine.js +284 -0
  150. package/dist/multi-agent/council-engine.js.map +1 -0
  151. package/dist/multi-agent/delegation-manager.d.ts +27 -0
  152. package/dist/multi-agent/delegation-manager.d.ts.map +1 -1
  153. package/dist/multi-agent/delegation-manager.js +41 -0
  154. package/dist/multi-agent/delegation-manager.js.map +1 -1
  155. package/dist/multi-agent/multi-agent-base.d.ts +23 -10
  156. package/dist/multi-agent/multi-agent-base.d.ts.map +1 -1
  157. package/dist/multi-agent/multi-agent-base.js +125 -49
  158. package/dist/multi-agent/multi-agent-base.js.map +1 -1
  159. package/dist/multi-agent/multi-agent-discord.d.ts +3 -35
  160. package/dist/multi-agent/multi-agent-discord.d.ts.map +1 -1
  161. package/dist/multi-agent/multi-agent-discord.js +123 -395
  162. package/dist/multi-agent/multi-agent-discord.js.map +1 -1
  163. package/dist/multi-agent/multi-agent-slack.d.ts +3 -25
  164. package/dist/multi-agent/multi-agent-slack.d.ts.map +1 -1
  165. package/dist/multi-agent/multi-agent-slack.js +223 -255
  166. package/dist/multi-agent/multi-agent-slack.js.map +1 -1
  167. package/dist/multi-agent/runtime-process.d.ts +3 -0
  168. package/dist/multi-agent/runtime-process.d.ts.map +1 -1
  169. package/dist/multi-agent/runtime-process.js +7 -0
  170. package/dist/multi-agent/runtime-process.js.map +1 -1
  171. package/dist/multi-agent/shared-context.d.ts.map +1 -1
  172. package/dist/multi-agent/shared-context.js +4 -4
  173. package/dist/multi-agent/shared-context.js.map +1 -1
  174. package/dist/multi-agent/swarm/swarm-task-runner.d.ts.map +1 -1
  175. package/dist/multi-agent/swarm/swarm-task-runner.js +58 -37
  176. package/dist/multi-agent/swarm/swarm-task-runner.js.map +1 -1
  177. package/dist/multi-agent/system-reminder.d.ts +1 -1
  178. package/dist/multi-agent/system-reminder.js +1 -1
  179. package/dist/multi-agent/types.d.ts +38 -21
  180. package/dist/multi-agent/types.d.ts.map +1 -1
  181. package/dist/multi-agent/types.js +1 -3
  182. package/dist/multi-agent/types.js.map +1 -1
  183. package/dist/multi-agent/ultrawork-state.d.ts +57 -0
  184. package/dist/multi-agent/ultrawork-state.d.ts.map +1 -0
  185. package/dist/multi-agent/ultrawork-state.js +191 -0
  186. package/dist/multi-agent/ultrawork-state.js.map +1 -0
  187. package/dist/multi-agent/ultrawork.d.ts +39 -21
  188. package/dist/multi-agent/ultrawork.d.ts.map +1 -1
  189. package/dist/multi-agent/ultrawork.js +591 -45
  190. package/dist/multi-agent/ultrawork.js.map +1 -1
  191. package/dist/multi-agent/workflow-engine.d.ts +7 -0
  192. package/dist/multi-agent/workflow-engine.d.ts.map +1 -1
  193. package/dist/multi-agent/workflow-engine.js +240 -34
  194. package/dist/multi-agent/workflow-engine.js.map +1 -1
  195. package/dist/multi-agent/workflow-types.d.ts +74 -1
  196. package/dist/multi-agent/workflow-types.d.ts.map +1 -1
  197. package/dist/onboarding/complete-autonomous-prompt.d.ts +1 -1
  198. package/dist/onboarding/complete-autonomous-prompt.d.ts.map +1 -1
  199. package/dist/onboarding/complete-autonomous-prompt.js +27 -10
  200. package/dist/onboarding/complete-autonomous-prompt.js.map +1 -1
  201. package/dist/onboarding/phase-7-integrations.d.ts.map +1 -1
  202. package/dist/onboarding/phase-7-integrations.js +23 -3
  203. package/dist/onboarding/phase-7-integrations.js.map +1 -1
  204. package/dist/onboarding/phase-9-finalization.d.ts.map +1 -1
  205. package/dist/onboarding/phase-9-finalization.js +33 -0
  206. package/dist/onboarding/phase-9-finalization.js.map +1 -1
  207. package/dist/setup/setup-prompt.d.ts +1 -1
  208. package/dist/setup/setup-prompt.d.ts.map +1 -1
  209. package/dist/setup/setup-prompt.js +1 -1
  210. package/dist/skills/skill-registry.d.ts +7 -0
  211. package/dist/skills/skill-registry.d.ts.map +1 -1
  212. package/dist/skills/skill-registry.js +18 -0
  213. package/dist/skills/skill-registry.js.map +1 -1
  214. package/package.json +6 -3
  215. package/public/viewer/js/modules/chat.js +145 -76
  216. package/public/viewer/js/modules/playground.js +148 -0
  217. package/public/viewer/js/modules/settings.js +110 -15
  218. package/public/viewer/js/modules/skills.js +59 -4
  219. package/public/viewer/js/utils/api.js +6 -0
  220. package/public/viewer/js/utils/format.js +11 -8
  221. package/public/viewer/src/modules/chat.ts +223 -83
  222. package/public/viewer/src/modules/playground.ts +173 -0
  223. package/public/viewer/src/modules/settings.ts +133 -16
  224. package/public/viewer/src/modules/skills.ts +61 -4
  225. package/public/viewer/src/utils/api.ts +14 -1
  226. package/public/viewer/src/utils/format.ts +11 -8
  227. package/public/viewer/viewer.html +171 -5
  228. package/templates/AGENTS.claude.md +23 -0
  229. package/templates/AGENTS.codex.md +32 -0
  230. package/templates/bmad/LICENSE +28 -0
  231. package/templates/bmad/architecture.md +343 -0
  232. package/templates/bmad/bmm-workflow-status.template.yaml +66 -0
  233. package/templates/bmad/prd.md +198 -0
  234. package/templates/bmad/product-brief.md +149 -0
  235. package/templates/bmad/sprint-status.template.yaml +35 -0
  236. package/templates/bmad/tech-spec.md +151 -0
  237. package/templates/personas/architect.md +70 -0
  238. package/templates/personas/conductor.md +373 -0
  239. package/templates/personas/developer.md +20 -7
  240. package/templates/personas/pm.md +49 -33
  241. package/templates/personas/reviewer.md +18 -5
  242. package/templates/playgrounds/cron-workflow-lab.html +1601 -0
  243. package/templates/playgrounds/mama-log-viewer.html +1341 -0
  244. package/templates/playgrounds/skill-lab-playground.html +1625 -0
  245. package/templates/playgrounds/wave-visualizer.html +694 -0
  246. package/templates/skills/frontend-design.md +71 -0
  247. package/templates/skills/multi-agent-collab.md +145 -0
  248. package/templates/skills/playground.md +197 -0
  249. package/templates/skills/scheduling.md +84 -0
  250. package/dist/multi-agent/agent-process-pool.d.ts +0 -148
  251. package/dist/multi-agent/agent-process-pool.d.ts.map +0 -1
  252. package/dist/multi-agent/agent-process-pool.js +0 -308
  253. package/dist/multi-agent/agent-process-pool.js.map +0 -1
  254. package/dist/multi-agent/pr-review-poller.d.ts +0 -197
  255. package/dist/multi-agent/pr-review-poller.d.ts.map +0 -1
  256. package/dist/multi-agent/pr-review-poller.js +0 -972
  257. package/dist/multi-agent/pr-review-poller.js.map +0 -1
  258. package/templates/personas/sisyphus-builtin-en.md +0 -161
  259. package/templates/personas/sisyphus.md +0 -218
@@ -45,7 +45,9 @@ var __importStar = (this && this.__importStar) || (function () {
45
45
  })();
46
46
  Object.defineProperty(exports, "__esModule", { value: true });
47
47
  exports.AgentLoop = void 0;
48
+ exports.loadSkillContent = loadSkillContent;
48
49
  exports.loadInstalledSkills = loadInstalledSkills;
50
+ exports.loadBackendAgentsMd = loadBackendAgentsMd;
49
51
  exports.loadComposedSystemPrompt = loadComposedSystemPrompt;
50
52
  exports.getGatewayToolsPrompt = getGatewayToolsPrompt;
51
53
  const fs_1 = require("fs");
@@ -53,7 +55,8 @@ const prompt_size_monitor_js_1 = require("./prompt-size-monitor.js");
53
55
  const codex_mcp_process_js_1 = require("./codex-mcp-process.js");
54
56
  const persistent_cli_adapter_js_1 = require("./persistent-cli-adapter.js");
55
57
  const gateway_tool_executor_js_1 = require("./gateway-tool-executor.js");
56
- const index_js_1 = require("../concurrency/index.js");
58
+ const index_js_1 = require("./code-act/index.js");
59
+ const index_js_2 = require("../concurrency/index.js");
57
60
  const session_pool_js_1 = require("./session-pool.js");
58
61
  const os_1 = require("os");
59
62
  const path_1 = require("path");
@@ -183,16 +186,57 @@ function collectMarkdownFiles(dir, prefix = '') {
183
186
  }
184
187
  return results;
185
188
  }
189
+ // ─── Skill On-Demand Injection ───────────────────────────────────────────────
186
190
  /**
187
- * Load installed & enabled skills from ~/.mama/skills/
188
- * Returns skill content blocks for system prompt injection.
189
- * Reads all .md files recursively (commands/, skills/, etc.)
191
+ * Parse YAML frontmatter from skill .md file
192
+ */
193
+ function parseSkillFrontmatter(content) {
194
+ const match = content.match(/^---\n([\s\S]*?)\n---/);
195
+ if (!match)
196
+ return { name: '', description: '', keywords: [] };
197
+ const block = match[1];
198
+ const name = (block.match(/^name:\s*(.+)$/m)?.[1] ?? '').trim();
199
+ const description = (block.match(/^description:\s*(.+)$/m)?.[1] ?? '').trim();
200
+ const kwBlock = block.match(/^keywords:\n((?:[ \t]+-[ \t]*.+\n?)+)/m);
201
+ const keywords = kwBlock
202
+ ? kwBlock[1]
203
+ .trim()
204
+ .split('\n')
205
+ .map((l) => l.replace(/^[ \t]*-[ \t]*/, '').trim())
206
+ .filter((k) => k.length > 0)
207
+ : [];
208
+ return { name, description, keywords };
209
+ }
210
+ /**
211
+ * Find the main .md file for a directory skill (for frontmatter parsing)
212
+ */
213
+ function findMainSkillFile(skillDir, skillName) {
214
+ for (const name of [`${skillName}.md`, 'skill.md', 'index.md']) {
215
+ const p = (0, path_1.join)(skillDir, name);
216
+ if ((0, fs_1.existsSync)(p))
217
+ return p;
218
+ }
219
+ try {
220
+ const entries = (0, fs_1.readdirSync)(skillDir, { withFileTypes: true });
221
+ for (const e of entries) {
222
+ if (e.isFile() && e.name.endsWith('.md') && !EXCLUDED_SKILL_FILES.has(e.name)) {
223
+ return (0, path_1.join)(skillDir, e.name);
224
+ }
225
+ }
226
+ }
227
+ catch {
228
+ /* ignore */
229
+ }
230
+ return null;
231
+ }
232
+ /**
233
+ * Build skill catalog (one line per enabled skill) for system prompt.
234
+ * Format: "- [source/skillId] keywords: kw1, kw2 | description"
190
235
  */
191
- function loadInstalledSkills(verbose = false, options = {}) {
236
+ function buildSkillCatalog(verbose = false) {
192
237
  const skillsBase = (0, path_1.join)((0, os_1.homedir)(), '.mama', 'skills');
193
238
  const stateFile = (0, path_1.join)(skillsBase, 'state.json');
194
- const blocks = [];
195
- // Load state (enabled/disabled tracking)
239
+ const catalog = [];
196
240
  let state = {};
197
241
  try {
198
242
  if ((0, fs_1.existsSync)(stateFile)) {
@@ -200,7 +244,7 @@ function loadInstalledSkills(verbose = false, options = {}) {
200
244
  }
201
245
  }
202
246
  catch {
203
- // No state file
247
+ /* no state file */
204
248
  }
205
249
  const sources = ['mama', 'cowork', 'external'];
206
250
  for (const source of sources) {
@@ -213,76 +257,132 @@ function loadInstalledSkills(verbose = false, options = {}) {
213
257
  if (!entry.isDirectory())
214
258
  continue;
215
259
  const stateKey = `${source}/${entry.name}`;
216
- // Skip disabled skills
217
260
  if (state[stateKey]?.enabled === false)
218
261
  continue;
219
262
  const skillDir = (0, path_1.join)(sourceDir, entry.name);
220
- let mdFiles = collectMarkdownFiles(skillDir);
221
- if (options.onlyCommands) {
222
- mdFiles = mdFiles.filter((f) => f.path.startsWith('commands/'));
223
- }
224
- if (mdFiles.length > 0) {
225
- const parts = mdFiles.map((f) => `## ${f.path}\n\n${f.content}`);
226
- blocks.push(`# [Skill: ${source}/${entry.name}]\n\n${parts.join('\n\n---\n\n')}`);
263
+ const mainFile = findMainSkillFile(skillDir, entry.name);
264
+ if (!mainFile)
265
+ continue;
266
+ try {
267
+ const content = (0, fs_1.readFileSync)(mainFile, 'utf-8');
268
+ const fm = parseSkillFrontmatter(content);
269
+ const description = fm.description || '';
270
+ const keywords = fm.keywords.length > 0 ? fm.keywords.join(', ') : entry.name;
271
+ catalog.push(`- [${stateKey}] keywords: ${keywords} | ${description}`);
227
272
  if (verbose)
228
- console.log(`[AgentLoop] Loaded skill: ${source}/${entry.name} (${mdFiles.length} files)`);
273
+ console.log(`[AgentLoop] Skill catalog: ${stateKey}`);
274
+ }
275
+ catch {
276
+ /* skip unreadable */
229
277
  }
230
278
  }
231
279
  }
232
280
  catch {
233
- // Directory read failed
281
+ /* directory read failed */
234
282
  }
235
283
  }
236
- // Also load flat .md skill files from ~/.mama/skills/ root
284
+ // Flat .md files at root
237
285
  try {
238
286
  const rootEntries = (0, fs_1.readdirSync)(skillsBase, { withFileTypes: true });
239
287
  for (const entry of rootEntries) {
240
- if (!entry.isFile() || !entry.name.endsWith('.md')) {
288
+ if (!entry.isFile() || !entry.name.endsWith('.md'))
241
289
  continue;
242
- }
243
- if (EXCLUDED_SKILL_FILES.has(entry.name)) {
290
+ if (EXCLUDED_SKILL_FILES.has(entry.name))
244
291
  continue;
245
- }
246
292
  const id = entry.name.replace(/\.md$/, '');
247
293
  const stateKey = `mama/${id}`;
248
- // Skip disabled skills (check state like subdirectory skills)
249
- if (state[stateKey]?.enabled === false) {
294
+ if (state[stateKey]?.enabled === false)
250
295
  continue;
251
- }
252
- // Skip if already loaded from subdirectory
253
- if (blocks.some((b) => b.includes(`[Skill: mama/${id}]`))) {
296
+ if (catalog.some((l) => l.includes(`[${stateKey}]`)))
254
297
  continue;
298
+ try {
299
+ const content = (0, fs_1.readFileSync)((0, path_1.join)(skillsBase, entry.name), 'utf-8');
300
+ const fm = parseSkillFrontmatter(content);
301
+ const description = fm.description || '';
302
+ const keywords = fm.keywords.length > 0 ? fm.keywords.join(', ') : id;
303
+ catalog.push(`- [${stateKey}] keywords: ${keywords} | ${description}`);
304
+ if (verbose)
305
+ console.log(`[AgentLoop] Skill catalog (flat): ${stateKey}`);
255
306
  }
256
- const fullPath = (0, path_1.join)(skillsBase, entry.name);
257
- let content = (0, fs_1.readFileSync)(fullPath, 'utf-8');
258
- if (content.length > MAX_SKILL_FILE_CHARS) {
259
- content = content.slice(0, MAX_SKILL_FILE_CHARS) + '\n\n[... truncated]';
307
+ catch {
308
+ /* skip */
260
309
  }
261
- blocks.push(`# [Skill: mama/${id}]\n\n${content}`);
262
- if (verbose)
263
- console.log(`[AgentLoop] Loaded root skill: ${id}`);
264
310
  }
265
311
  }
266
312
  catch {
267
- // Root directory read failed
313
+ /* root directory read failed */
268
314
  }
269
- return blocks;
315
+ return catalog;
270
316
  }
271
- function loadComposedSystemPrompt(verbose = false, context) {
272
- const mamaHome = (0, path_1.join)((0, os_1.homedir)(), '.mama');
273
- const layers = [];
274
- // Load state for conditional loading (skills + system docs)
275
- const stateFile = (0, path_1.join)(mamaHome, 'skills', 'state.json');
276
- let state = {};
277
- try {
278
- if ((0, fs_1.existsSync)(stateFile)) {
279
- state = JSON.parse((0, fs_1.readFileSync)(stateFile, 'utf-8'));
317
+ /**
318
+ * Load full skill content on-demand for per-message injection.
319
+ * @param skillId - Skill identifier like "mama/playground"
320
+ */
321
+ function loadSkillContent(skillId) {
322
+ const skillsBase = (0, path_1.join)((0, os_1.homedir)(), '.mama', 'skills');
323
+ // Try directory skill first
324
+ const skillDir = (0, path_1.join)(skillsBase, skillId);
325
+ if ((0, fs_1.existsSync)(skillDir)) {
326
+ const mdFiles = collectMarkdownFiles(skillDir);
327
+ if (mdFiles.length > 0) {
328
+ const parts = mdFiles.map((f) => `## ${f.path}\n\n${f.content}`);
329
+ return `# [Skill: ${skillId}]\n\n${parts.join('\n\n---\n\n')}`;
280
330
  }
281
331
  }
282
- catch (err) {
283
- logger.error(`Failed to parse state file ${stateFile}:`, err);
284
- throw new Error(`Failed to parse state file ${stateFile}: ${err instanceof Error ? err.message : String(err)}`);
332
+ // Try flat .md file: "mama/playground" → skills/playground.md
333
+ const idParts = skillId.split('/');
334
+ if (idParts.length >= 2) {
335
+ const flatPath = (0, path_1.join)(skillsBase, `${idParts[idParts.length - 1]}.md`);
336
+ if ((0, fs_1.existsSync)(flatPath)) {
337
+ try {
338
+ return (0, fs_1.readFileSync)(flatPath, 'utf-8');
339
+ }
340
+ catch {
341
+ /* skip */
342
+ }
343
+ }
285
344
  }
345
+ return null;
346
+ }
347
+ /**
348
+ * Load installed & enabled skills from ~/.mama/skills/
349
+ * Returns skill catalog lines for system prompt injection (on-demand mode).
350
+ * Full skill content is injected per-message via detectSkillMatch() in PromptEnhancer.
351
+ */
352
+ function loadInstalledSkills(verbose = false, _options = {}) {
353
+ return buildSkillCatalog(verbose);
354
+ }
355
+ /**
356
+ * Load backend-specific AGENTS.md from ~/.mama/
357
+ * Maps backend to file: 'claude' → AGENTS.claude.md, 'codex-mcp' → AGENTS.codex.md
358
+ */
359
+ function loadBackendAgentsMd(backend, verbose = false) {
360
+ if (!backend) {
361
+ return '';
362
+ }
363
+ const keyMap = {
364
+ claude: 'claude',
365
+ 'codex-mcp': 'codex',
366
+ };
367
+ const key = keyMap[backend];
368
+ if (!key) {
369
+ return '';
370
+ }
371
+ const filePath = (0, path_1.join)((0, os_1.homedir)(), '.mama', `AGENTS.${key}.md`);
372
+ if ((0, fs_1.existsSync)(filePath)) {
373
+ if (verbose) {
374
+ console.log(`[AgentLoop] Loaded backend AGENTS.md: AGENTS.${key}.md`);
375
+ }
376
+ return (0, fs_1.readFileSync)(filePath, 'utf-8');
377
+ }
378
+ if (verbose) {
379
+ console.log(`[AgentLoop] Backend AGENTS.md not found: AGENTS.${key}.md`);
380
+ }
381
+ return '';
382
+ }
383
+ function loadComposedSystemPrompt(verbose = false, context) {
384
+ const mamaHome = (0, path_1.join)((0, os_1.homedir)(), '.mama');
385
+ const layers = [];
286
386
  // Load persona files: SOUL.md, IDENTITY.md, USER.md
287
387
  const personaFiles = ['SOUL.md', 'IDENTITY.md', 'USER.md'];
288
388
  for (const file of personaFiles) {
@@ -298,55 +398,54 @@ function loadComposedSystemPrompt(verbose = false, context) {
298
398
  console.log(`[AgentLoop] Persona file not found (skipping): ${file}`);
299
399
  }
300
400
  }
301
- // Load installed & enabled skills (HIGH PRIORITYbefore CLAUDE.md)
302
- const skillBlocks = loadInstalledSkills(verbose);
303
- if (skillBlocks.length > 0) {
401
+ // Load skill catalog (on-demand modefull content injected per-message by PromptEnhancer)
402
+ const skillCatalog = loadInstalledSkills(verbose);
403
+ if (skillCatalog.length > 0) {
304
404
  const skillDirective = [
305
- '# Installed Skills (PRIORITY)',
405
+ '# Installed Skills',
306
406
  '',
307
- '**IMPORTANT:** The following skills/plugins are installed by the user.',
308
- 'When a user request matches a skill by keywords or description, you MUST:',
309
- '1. Find the matching skill section below (check "keywords" in frontmatter or skill name)',
310
- '2. Follow its "지시사항" / instructions EXACTLY as written — do NOT improvise alternatives',
311
- '3. Use the tools available to you (fetch, Bash, etc.) as the skill directs',
312
- '4. DO NOT create separate scripts or files unless the skill explicitly instructs it',
313
- '5. For [INSTALLED PLUGIN COMMAND] messages, find matching "commands/{name}.md"',
314
- '6. DO NOT use the Skill tool — these are NOT system skills',
407
+ 'To invoke a skill, include its keywords in your message.',
408
+ 'The full skill instructions will be injected automatically when matched.',
315
409
  '',
316
- skillBlocks.join('\n\n---\n\n'),
410
+ ...skillCatalog,
317
411
  ].join('\n');
318
412
  layers.push(skillDirective);
319
413
  if (verbose)
320
- console.log(`[AgentLoop] Injected ${skillBlocks.length} installed skills`);
414
+ console.log(`[AgentLoop] Skill catalog: ${skillCatalog.length} skills`);
321
415
  }
322
- // Add context prompt if AgentContext is provided (role awareness)
416
+ // Add minimal context if AgentContext is provided (role awareness)
323
417
  if (context) {
324
- const contextPrompt = (0, context_prompt_builder_js_1.buildContextPrompt)(context);
325
- if (verbose)
326
- console.log(`[AgentLoop] Injecting context prompt for ${context.roleName}@${context.platform}`);
327
- layers.push(contextPrompt);
418
+ layers.push((0, context_prompt_builder_js_1.buildMinimalContext)(context));
419
+ }
420
+ // Load backend-specific AGENTS.md (e.g., AGENTS.claude.md, AGENTS.codex.md)
421
+ const backendAgentsMd = loadBackendAgentsMd(context?.backend, verbose);
422
+ if (backendAgentsMd) {
423
+ layers.push(backendAgentsMd);
328
424
  }
329
425
  // Load CLAUDE.md (base instructions)
330
426
  const claudeMd = loadSystemPrompt(verbose);
331
427
  layers.push(claudeMd);
332
- // Load ONBOARDING.md only if not disabled in state
333
- // This contains config schema + bot setup guides - only needed during initial setup
334
- if (state['system/onboarding']?.enabled !== false) {
428
+ // Load ONBOARDING.md only during initial setup (before SOUL.md is created)
429
+ const soulPath = (0, path_1.join)(mamaHome, 'SOUL.md');
430
+ if (!(0, fs_1.existsSync)(soulPath)) {
335
431
  const onboardingPath = (0, path_1.join)(mamaHome, 'ONBOARDING.md');
336
432
  if ((0, fs_1.existsSync)(onboardingPath)) {
337
433
  const onboardingContent = (0, fs_1.readFileSync)(onboardingPath, 'utf-8');
338
434
  layers.push(onboardingContent);
339
435
  if (verbose) {
340
- logger.debug('Loaded ONBOARDING.md (setup reference)');
436
+ logger.debug('Loaded ONBOARDING.md (initial setup)');
341
437
  }
342
438
  }
343
439
  }
344
440
  else {
345
441
  if (verbose) {
346
- logger.debug('Skipped ONBOARDING.md (disabled in state)');
442
+ logger.debug('Skipped ONBOARDING.md (SOUL.md exists, setup complete)');
347
443
  }
348
444
  }
349
- return layers.join('\n\n---\n\n');
445
+ const result = layers.join('\n\n---\n\n');
446
+ // Debug: log each layer's size to find what's consuming context
447
+ logger.debug(`[SystemPrompt] Total: ${result.length} chars, layers: ${layers.map((l, i) => `L${i}=${l.length}`).join(', ')}`);
448
+ return result;
350
449
  }
351
450
  /**
352
451
  * Load Gateway Tools prompt from MD file
@@ -390,11 +489,14 @@ class AgentLoop {
390
489
  sessionPool;
391
490
  toolsConfig;
392
491
  isGatewayMode;
492
+ useCodeAct;
393
493
  backend;
394
494
  postToolHandler;
395
495
  stopContinuationHandler;
396
496
  preCompactHandler;
397
497
  preCompactInjected = false;
498
+ currentStreamCallbacks;
499
+ currentTier = 1;
398
500
  constructor(_oauthManager, options = {}, _clientOptions, executorOptions) {
399
501
  // Initialize tools config (hybrid Gateway/MCP routing)
400
502
  this.toolsConfig = {
@@ -414,6 +516,7 @@ class AgentLoop {
414
516
  const useGatewayMode = gatewayTools.includes('*') || gatewayTools.length > 0;
415
517
  const useMCPMode = mcpTools.includes('*') || mcpTools.length > 0;
416
518
  this.isGatewayMode = useGatewayMode;
519
+ this.useCodeAct = options.useCodeAct ?? false;
417
520
  if (useGatewayMode && useMCPMode) {
418
521
  logger.debug('🔀 Hybrid mode: Gateway + MCP tools enabled');
419
522
  }
@@ -423,38 +526,111 @@ class AgentLoop {
423
526
  else {
424
527
  logger.debug('⚙️ Gateway-only mode');
425
528
  }
426
- // Build system prompt
427
- const basePrompt = options.systemPrompt || loadComposedSystemPrompt();
428
- // Only include Gateway Tools prompt if using Gateway mode
429
- const gatewayToolsPrompt = useGatewayMode ? getGatewayToolsPrompt() : '';
430
- let defaultSystemPrompt = gatewayToolsPrompt
431
- ? `${basePrompt}\n\n---\n\n${gatewayToolsPrompt}`
432
- : basePrompt;
433
- // Monitor and enforce prompt size limits
529
+ // Build system prompt with layered truncation support
434
530
  const monitor = new prompt_size_monitor_js_1.PromptSizeMonitor();
435
- const promptLayers = [
436
- { name: 'base', content: basePrompt, priority: 1 },
437
- ...(gatewayToolsPrompt
438
- ? [{ name: 'gatewayTools', content: gatewayToolsPrompt, priority: 2 }]
439
- : []),
440
- ];
531
+ let promptLayers;
532
+ if (options.systemPrompt) {
533
+ // Custom system prompt (e.g., multi-agent): treat as a single critical layer
534
+ promptLayers = [{ name: 'custom', content: options.systemPrompt, priority: 1 }];
535
+ }
536
+ else {
537
+ // Composed prompt: build layers with individual priorities for graceful truncation
538
+ // Priority 1 (never cut): CLAUDE.md base instructions
539
+ // Priority 2 (cut if extreme): personas (SOUL, IDENTITY, USER) + gateway tools
540
+ // Priority 3 (cut first): context prompt + skills + onboarding
541
+ const mamaHome = (0, path_1.join)((0, os_1.homedir)(), '.mama');
542
+ const claudeMd = loadSystemPrompt();
543
+ const personaFiles = ['SOUL.md', 'IDENTITY.md', 'USER.md'];
544
+ const personaParts = [];
545
+ for (const file of personaFiles) {
546
+ const p = (0, path_1.join)(mamaHome, file);
547
+ if ((0, fs_1.existsSync)(p))
548
+ personaParts.push((0, fs_1.readFileSync)(p, 'utf-8'));
549
+ }
550
+ const skillCatalog = loadInstalledSkills();
551
+ // Only load ONBOARDING.md during initial setup (before SOUL.md exists)
552
+ const onboardingContent = !(0, fs_1.existsSync)((0, path_1.join)(mamaHome, 'SOUL.md'))
553
+ ? (() => {
554
+ const op = (0, path_1.join)(mamaHome, 'ONBOARDING.md');
555
+ return (0, fs_1.existsSync)(op) ? (0, fs_1.readFileSync)(op, 'utf-8') : '';
556
+ })()
557
+ : '';
558
+ promptLayers = [
559
+ { name: 'claudeMd', content: claudeMd, priority: 1 },
560
+ ...(personaParts.length > 0
561
+ ? [
562
+ {
563
+ name: 'personas',
564
+ content: personaParts.join('\n\n---\n\n'),
565
+ priority: 2,
566
+ },
567
+ ]
568
+ : []),
569
+ ...(skillCatalog.length > 0
570
+ ? [
571
+ {
572
+ name: 'skills',
573
+ content: [
574
+ '# Installed Skills',
575
+ '',
576
+ 'To invoke a skill, include its keywords in your message.',
577
+ '',
578
+ ...skillCatalog,
579
+ ].join('\n'),
580
+ priority: 3,
581
+ },
582
+ ]
583
+ : []),
584
+ ...(onboardingContent
585
+ ? [{ name: 'onboarding', content: onboardingContent, priority: 4 }]
586
+ : []),
587
+ ];
588
+ }
589
+ const backend = options.backend ?? 'claude';
590
+ // Load backend-specific AGENTS.md (e.g., AGENTS.claude.md, AGENTS.codex.md)
591
+ const backendAgentsMd = loadBackendAgentsMd(backend);
592
+ if (backendAgentsMd) {
593
+ promptLayers.push({ name: 'backendAgents', content: backendAgentsMd, priority: 2 });
594
+ }
595
+ if (useGatewayMode) {
596
+ if (this.useCodeAct) {
597
+ // Code-Act mode: replace verbose gateway tools markdown with compact .d.ts
598
+ const tierForTypeDefs = options.agentContext?.tier === 1 ||
599
+ options.agentContext?.tier === 2 ||
600
+ options.agentContext?.tier === 3
601
+ ? options.agentContext.tier
602
+ : 1;
603
+ const typeDefs = index_js_1.TypeDefinitionGenerator.generate(tierForTypeDefs);
604
+ const codeActBackend = backend === 'codex-mcp' ? 'codex-mcp' : 'claude';
605
+ const codeActPrompt = (0, index_js_1.getCodeActInstructions)(codeActBackend) + '\n```typescript\n' + typeDefs + '\n```';
606
+ promptLayers.push({ name: 'codeAct', content: codeActPrompt, priority: 2 });
607
+ }
608
+ else {
609
+ const gatewayToolsPrompt = getGatewayToolsPrompt();
610
+ if (gatewayToolsPrompt) {
611
+ promptLayers.push({ name: 'gatewayTools', content: gatewayToolsPrompt, priority: 2 });
612
+ }
613
+ }
614
+ }
441
615
  const checkResult = monitor.check(promptLayers);
442
616
  if (checkResult.warning) {
443
617
  logger.warn(checkResult.warning);
444
618
  }
445
- // Actually enforce truncation if over budget
619
+ // Enforce truncation if over budget (priority > 1 layers trimmed first)
446
620
  if (!checkResult.withinBudget) {
447
621
  const { layers: trimmedLayers, result: enforceResult } = monitor.enforce(promptLayers);
448
622
  if (enforceResult.truncatedLayers.length > 0) {
449
623
  logger.warn(`Truncated layers: ${enforceResult.truncatedLayers.join(', ')}`);
450
624
  }
451
- const trimmedBase = trimmedLayers.find((l) => l.name === 'base')?.content || basePrompt;
452
- const trimmedTools = trimmedLayers.find((l) => l.name === 'gatewayTools')?.content || '';
453
- defaultSystemPrompt = trimmedTools ? `${trimmedBase}\n\n---\n\n${trimmedTools}` : trimmedBase;
454
- logger.debug(`System prompt truncated: ${checkResult.totalChars} → ${defaultSystemPrompt.length} chars`);
625
+ promptLayers = trimmedLayers;
626
+ logger.debug(`System prompt truncated: ${checkResult.totalChars} ${enforceResult.totalChars} chars`);
455
627
  }
628
+ const defaultSystemPrompt = promptLayers
629
+ .filter((l) => l.content.length > 0)
630
+ .map((l) => l.content)
631
+ .join('\n\n---\n\n');
456
632
  // Choose backend (default: claude)
457
- this.backend = options.backend ?? 'claude';
633
+ this.backend = backend;
458
634
  if (this.backend === 'codex-mcp') {
459
635
  // Codex MCP mode: standard MCP protocol
460
636
  const workspaceDir = (0, path_1.join)((0, os_1.homedir)(), '.mama', 'workspace');
@@ -469,23 +645,28 @@ class AgentLoop {
469
645
  systemPrompt: defaultSystemPrompt,
470
646
  compactPrompt: 'Summarize the conversation concisely, preserving key decisions and context.',
471
647
  timeoutMs: options.timeoutMs,
648
+ codexHome: (0, path_1.join)((0, os_1.homedir)(), '.mama', '.codex'),
472
649
  });
473
650
  logger.debug('Codex MCP backend enabled');
474
651
  }
475
652
  else {
476
653
  // Claude backend: always use PersistentCLI for fast responses (~2-3s vs ~16-30s)
477
654
  this.persistentCLI = new persistent_cli_adapter_js_1.PersistentCLIAdapter({
478
- model: options.model ?? 'claude-sonnet-4-20250514',
655
+ model: options.model,
479
656
  sessionId,
480
657
  systemPrompt: defaultSystemPrompt,
481
- // Hybrid mode: pass MCP config even with Gateway tools enabled
658
+ // MCP config: only pass when MCP mode is enabled (gateway mode uses GatewayToolExecutor)
482
659
  mcpConfigPath: useMCPMode ? mcpConfigPath : undefined,
483
- // Headless daemon requires skipping permission prompts (no TTY available).
484
- // Security is enforced by MAMA's RoleManager, not Claude CLI's interactive prompts.
485
- // MAMA_TRUSTED_ENV must be set to enable this flag (defense in depth)
486
- dangerouslySkipPermissions: process.env.MAMA_TRUSTED_ENV === 'true' && (options.dangerouslySkipPermissions ?? false),
660
+ // MAMA OS is a headless daemon (no TTY) — Claude CLI's interactive permission prompts
661
+ // cannot work. Security is enforced by MAMA's own RoleManager layer (config.yaml roles).
662
+ // DO NOT gate this on env vars MAMA manages permissions via its config, not Claude CLI.
663
+ dangerouslySkipPermissions: options.dangerouslySkipPermissions ?? false,
487
664
  // Gateway tools are processed by GatewayToolExecutor (hybrid with MCP)
488
665
  useGatewayTools: useGatewayMode,
666
+ // Code-Act: available as optional tool alongside direct tools (no disallowedTools)
667
+ disallowedTools: undefined,
668
+ // Pass configured timeout (default in PersistentCLI: 120s — too short for complex tasks)
669
+ requestTimeout: options.timeoutMs,
489
670
  });
490
671
  this.agent = this.persistentCLI;
491
672
  logger.debug('🚀 Claude PersistentCLI mode enabled - faster responses');
@@ -497,12 +678,11 @@ class AgentLoop {
497
678
  this.mcpExecutor = new gateway_tool_executor_js_1.GatewayToolExecutor(executorOptions);
498
679
  this.systemPromptOverride = options.systemPrompt;
499
680
  this.maxTurns = options.maxTurns ?? DEFAULT_MAX_TURNS;
500
- // Use the same default model as CLI wrappers above (L252, L264)
501
- this.model = options.model ?? 'claude-sonnet-4-20250514';
681
+ this.model = options.model;
502
682
  this.onTurn = options.onTurn;
503
683
  this.onToolUse = options.onToolUse;
504
684
  this.onTokenUsage = options.onTokenUsage;
505
- this.laneManager = (0, index_js_1.getGlobalLaneManager)();
685
+ this.laneManager = (0, index_js_2.getGlobalLaneManager)();
506
686
  this.useLanes = options.useLanes ?? false;
507
687
  this.sessionKey = options.sessionKey ?? 'default';
508
688
  this.sessionPool = (0, session_pool_js_1.getSessionPool)();
@@ -624,10 +804,21 @@ class AgentLoop {
624
804
  * Internal implementation of runWithContent (without lane queueing)
625
805
  */
626
806
  async runWithContentInternal(content, options) {
807
+ this.currentStreamCallbacks = options?.streamCallbacks;
627
808
  const history = [];
628
809
  const totalUsage = { input_tokens: 0, output_tokens: 0 };
629
810
  let turn = 0;
630
811
  let stopReason = 'end_turn';
812
+ // Propagate agentContext to executor for tier-aware tool permissions
813
+ if (options?.agentContext) {
814
+ this.mcpExecutor.setAgentContext?.(options.agentContext);
815
+ const rawTier = options.agentContext.tier ?? 1;
816
+ this.currentTier = (rawTier === 1 || rawTier === 2 || rawTier === 3 ? rawTier : 1);
817
+ }
818
+ else {
819
+ this.mcpExecutor.setAgentContext?.(null);
820
+ this.currentTier = 1;
821
+ }
631
822
  // Infinite loop prevention
632
823
  let consecutiveToolCalls = 0;
633
824
  let lastToolName = '';
@@ -640,10 +831,19 @@ class AgentLoop {
640
831
  // MessageRouter already calls getSession() and passes the result via options
641
832
  let sessionIsNew = options?.resumeSession === undefined ? true : !options.resumeSession;
642
833
  let ownedSession = false;
643
- // Set session ID on the agent (works for both ClaudeCLIWrapper and PersistentCLIAdapter)
834
+ // Set session ID on the agent
835
+ // Claude PersistentCLI: process alive → CONTINUE (stdin message), process dead → NEW (spawn with --session-id)
836
+ // Codex: threadId alive → CONTINUE (codex-reply), threadId null → NEW (codex tool)
837
+ const isCodex = this.backend === 'codex-mcp';
838
+ const sessionLabel = (isNew) => {
839
+ if (isCodex) {
840
+ return isNew ? 'NEW thread' : 'CONTINUE thread';
841
+ }
842
+ return isNew ? 'NEW process' : 'CONTINUE session';
843
+ };
644
844
  if (options?.cliSessionId) {
645
845
  this.agent.setSessionId(options.cliSessionId);
646
- console.log(`[AgentLoop] Using caller session: ${channelKey} ${options.cliSessionId} (${sessionIsNew ? 'NEW' : 'RESUME'})`);
846
+ console.log(`[AgentLoop] [${isCodex ? 'codex' : 'claude'}] ${channelKey} (${sessionLabel(sessionIsNew)})`);
647
847
  }
648
848
  else {
649
849
  // Fallback: get session from pool (for direct AgentLoop usage)
@@ -655,13 +855,27 @@ class AgentLoop {
655
855
  sessionIsNew = isNew;
656
856
  ownedSession = true;
657
857
  this.agent.setSessionId(cliSessionId);
658
- console.log(`[AgentLoop] Session pool: ${channelKey} ${cliSessionId} (${isNew ? 'NEW' : 'RESUME'})`);
858
+ console.log(`[AgentLoop] [${isCodex ? 'codex' : 'claude'}] ${channelKey} (${sessionLabel(isNew)})`);
659
859
  }
660
860
  try {
661
861
  if (options?.systemPrompt) {
662
862
  // Skip gateway tools if already embedded in systemPrompt (e.g. by MessageRouter)
663
- const alreadyHasTools = options.systemPrompt.includes('# Gateway Tools');
664
- const gatewayToolsPrompt = this.isGatewayMode && !alreadyHasTools ? getGatewayToolsPrompt() : '';
863
+ const alreadyHasTools = options.systemPrompt.includes('## Gateway Tools') ||
864
+ options.systemPrompt.includes('# Code Execution') ||
865
+ options.systemPrompt.includes('## Code-Act');
866
+ let gatewayToolsPrompt = '';
867
+ const isResumingSession = options?.resumeSession === true;
868
+ if (this.isGatewayMode && !alreadyHasTools && !isResumingSession) {
869
+ if (this.useCodeAct) {
870
+ const typeDefs = index_js_1.TypeDefinitionGenerator.generate(this.currentTier);
871
+ const codeActBackend = this.backend === 'codex-mcp' ? 'codex-mcp' : 'claude';
872
+ gatewayToolsPrompt =
873
+ (0, index_js_1.getCodeActInstructions)(codeActBackend) + '\n```typescript\n' + typeDefs + '\n```';
874
+ }
875
+ else {
876
+ gatewayToolsPrompt = getGatewayToolsPrompt();
877
+ }
878
+ }
665
879
  const fullPrompt = gatewayToolsPrompt
666
880
  ? `${options.systemPrompt}\n\n---\n\n${gatewayToolsPrompt}`
667
881
  : options.systemPrompt;
@@ -711,29 +925,32 @@ class AgentLoop {
711
925
  throw new types_js_1.AgentError(`Emergency stop: Agent loop exceeded emergency maximum turns (${EMERGENCY_MAX_TURNS})`, 'EMERGENCY_MAX_TURNS', undefined, false);
712
926
  }
713
927
  let response;
928
+ const ext = this.currentStreamCallbacks;
714
929
  const callbacks = {
715
930
  onDelta: (text) => {
716
- console.log('[Streaming] Delta received:', text.length, 'chars');
931
+ ext?.onDelta?.(text);
932
+ },
933
+ onToolUse: (name, input) => {
934
+ ext?.onToolUse?.(name, input);
717
935
  },
718
- onToolUse: (name, _input) => {
719
- console.log(`[Streaming] Tool called: ${name}`);
936
+ onToolComplete: (name, toolUseId, isError) => {
937
+ ext?.onToolComplete?.(name, toolUseId, isError);
720
938
  },
721
- onFinal: (_finalResponse) => {
722
- console.log('[Streaming] Stream complete');
939
+ onFinal: (finalResponse) => {
940
+ ext?.onFinal?.(finalResponse);
723
941
  },
724
942
  onError: (error) => {
725
- console.error('[Streaming] Error:', error);
726
- // Don't throw - let the promise rejection handle it
943
+ ext?.onError?.(error);
727
944
  },
728
945
  };
729
946
  let piResult;
730
- // Pass role-specific model and resume flag based on session state
731
- // First turn of new session: --session-id (inject system prompt)
732
- // Subsequent turns (tool loop) or resumed sessions: --resume (skip system prompt)
947
+ // Claude: First turn --session-id (inject system prompt), subsequent → --resume
948
+ // Codex: resumeSession only controls threadId reset (false=new thread, true=continue)
733
949
  const shouldResume = !sessionIsNew || turn > 1;
734
950
  // Both Claude PersistentCLI and Codex MCP preserve context - only send new messages
735
951
  const promptText = this.formatLastMessageOnly(history);
736
952
  try {
953
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any -- onFinal signature differs between PersistentCLI and Codex
737
954
  piResult = await this.agent.prompt(promptText, callbacks, {
738
955
  model: options?.model,
739
956
  resumeSession: shouldResume,
@@ -753,7 +970,9 @@ class AgentLoop {
753
970
  const isSessionInUse = errorMessage.includes('is already in use');
754
971
  const isPromptTooLong = errorMessage.includes('Prompt is too long') ||
755
972
  errorMessage.includes('prompt is too long') ||
756
- errorMessage.includes('request_too_large');
973
+ errorMessage.includes('request_too_large') ||
974
+ errorMessage.includes('context window') ||
975
+ errorMessage.includes('context_length_exceeded');
757
976
  if (isSessionNotFound || isSessionInUse || isPromptTooLong) {
758
977
  const reason = isSessionNotFound
759
978
  ? 'not found in CLI'
@@ -762,10 +981,10 @@ class AgentLoop {
762
981
  : 'prompt too long (context overflow)';
763
982
  console.log(`[AgentLoop] Session ${reason}, retrying with new session`);
764
983
  // Reset session in pool so it creates a new one
765
- this.sessionPool.resetSession(channelKey);
766
- const newSessionId = this.sessionPool.getSessionId(channelKey);
984
+ const newSessionId = this.sessionPool.resetSession(channelKey);
767
985
  this.agent.setSessionId(newSessionId);
768
986
  // Retry with new session (--session-id instead of --resume)
987
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any -- onFinal signature differs between PersistentCLI and Codex
769
988
  piResult = await this.agent.prompt(promptText, callbacks, {
770
989
  model: options?.model,
771
990
  resumeSession: false, // Force new session
@@ -784,9 +1003,16 @@ class AgentLoop {
784
1003
  // Build content blocks - include tool_use blocks if present
785
1004
  const contentBlocks = [];
786
1005
  let parsedToolCalls = [];
787
- // Parse tool_call blocks from text response (Gateway Tools mode ONLY)
1006
+ // Parse tool_call / code_act blocks from text response (Gateway Tools mode ONLY)
788
1007
  if (this.isGatewayMode) {
789
1008
  parsedToolCalls = this.parseToolCallsFromText(piResult.response || '');
1009
+ // Code-Act: parse ```js blocks only if enabled
1010
+ if (this.useCodeAct) {
1011
+ const codeActCalls = this.parseCodeActBlocks(piResult.response || '');
1012
+ if (codeActCalls.length > 0) {
1013
+ parsedToolCalls.push(...codeActCalls);
1014
+ }
1015
+ }
790
1016
  const textWithoutToolCalls = this.removeToolCallBlocks(piResult.response || '');
791
1017
  if (textWithoutToolCalls.trim()) {
792
1018
  contentBlocks.push({ type: 'text', text: textWithoutToolCalls });
@@ -971,6 +1197,7 @@ class AgentLoop {
971
1197
  if (ownedSession) {
972
1198
  this.sessionPool.releaseSession(channelKey);
973
1199
  }
1200
+ this.currentStreamCallbacks = undefined;
974
1201
  }
975
1202
  }
976
1203
  /**
@@ -982,27 +1209,63 @@ class AgentLoop {
982
1209
  for (const toolUse of toolUseBlocks) {
983
1210
  let result;
984
1211
  let isError = false;
1212
+ // Notify stream: tool execution starting
1213
+ this.currentStreamCallbacks?.onToolUse?.(toolUse.name, toolUse.input);
985
1214
  try {
986
- // PreToolUse: search MAMA for contracts before Write operations
987
- let contractContext = '';
988
- if (toolUse.name === 'Write' && toolUse.input) {
989
- contractContext = await this.searchContractsForTool(toolUse.name, toolUse.input);
1215
+ // Code-Act: execute JS code in sandbox
1216
+ if (toolUse.name === index_js_1.CODE_ACT_MARKER) {
1217
+ const codeInput = toolUse.input;
1218
+ const code = typeof codeInput?.code === 'string' ? codeInput.code : '';
1219
+ const codeActResult = code
1220
+ ? await this.executeCodeAct(code, this.currentTier)
1221
+ : {
1222
+ success: false,
1223
+ error: {
1224
+ name: 'ValidationError',
1225
+ message: 'Missing or invalid "code" field in code_act input',
1226
+ },
1227
+ logs: [],
1228
+ metrics: { durationMs: 0, hostCallCount: 0, memoryUsedBytes: 0 },
1229
+ };
1230
+ result = JSON.stringify(codeActResult, null, 2);
1231
+ if (!codeActResult.success) {
1232
+ isError = true;
1233
+ }
1234
+ this.onToolUse?.(toolUse.name, toolUse.input, codeActResult);
1235
+ this.currentStreamCallbacks?.onToolComplete?.(toolUse.name, toolUse.id, isError);
990
1236
  }
991
- const toolResult = await this.mcpExecutor.execute(toolUse.name, toolUse.input);
992
- result = JSON.stringify(toolResult, null, 2);
993
- if (contractContext) {
994
- result = `${contractContext}\n\n---\n\n${result}`;
1237
+ else {
1238
+ // PreToolUse: search MAMA for contracts before Write operations
1239
+ let contractContext = '';
1240
+ if (toolUse.name === 'Write' && toolUse.input) {
1241
+ contractContext = await this.searchContractsForTool(toolUse.name, toolUse.input);
1242
+ }
1243
+ const toolResult = await this.mcpExecutor.execute(toolUse.name, toolUse.input);
1244
+ result = JSON.stringify(toolResult, null, 2);
1245
+ // Check if tool execution failed
1246
+ const hasSuccess = 'success' in toolResult;
1247
+ const toolFailed = hasSuccess && !toolResult.success;
1248
+ if (toolFailed) {
1249
+ isError = true;
1250
+ }
1251
+ if (contractContext) {
1252
+ result = `${contractContext}\n\n---\n\n${result}`;
1253
+ }
1254
+ // Notify tool use callback
1255
+ this.onToolUse?.(toolUse.name, toolUse.input, toolResult);
1256
+ // PostToolUse: auto-extract contracts (fire-and-forget)
1257
+ this.postToolHandler?.processInBackground(toolUse.name, toolUse.input, toolResult);
1258
+ // Notify stream: tool completed (check actual status)
1259
+ this.currentStreamCallbacks?.onToolComplete?.(toolUse.name, toolUse.id, isError);
995
1260
  }
996
- // Notify tool use callback
997
- this.onToolUse?.(toolUse.name, toolUse.input, toolResult);
998
- // PostToolUse: auto-extract contracts (fire-and-forget)
999
- this.postToolHandler?.processInBackground(toolUse.name, toolUse.input, toolResult);
1000
1261
  }
1001
1262
  catch (error) {
1002
1263
  isError = true;
1003
1264
  result = error instanceof Error ? error.message : String(error);
1004
1265
  // Notify tool use callback with error
1005
1266
  this.onToolUse?.(toolUse.name, toolUse.input, { error: result });
1267
+ // Notify stream: tool completed with error
1268
+ this.currentStreamCallbacks?.onToolComplete?.(toolUse.name, toolUse.id, true);
1006
1269
  }
1007
1270
  results.push({
1008
1271
  type: 'tool_result',
@@ -1078,10 +1341,77 @@ class AgentLoop {
1078
1341
  return toolCalls;
1079
1342
  }
1080
1343
  /**
1081
- * Remove tool_call blocks from text (to avoid duplication in response)
1344
+ * Parse ```js code blocks as code_act tool calls (Code-Act mode)
1345
+ */
1346
+ parseCodeActBlocks(text) {
1347
+ const blocks = [];
1348
+ const codeActRegex = /```(?:js|javascript)\s*\n([\s\S]*?)\n```/g;
1349
+ let match;
1350
+ while ((match = codeActRegex.exec(text)) !== null) {
1351
+ const code = match[1].trim();
1352
+ if (code) {
1353
+ blocks.push({
1354
+ type: 'tool_use',
1355
+ id: `code_act_${(0, crypto_1.randomUUID)()}`,
1356
+ name: index_js_1.CODE_ACT_MARKER,
1357
+ input: { code },
1358
+ });
1359
+ }
1360
+ }
1361
+ return blocks;
1362
+ }
1363
+ /**
1364
+ * Execute Code-Act JS code in a sandboxed QuickJS environment
1365
+ */
1366
+ async executeCodeAct(code, tier = 1) {
1367
+ try {
1368
+ const sandbox = new index_js_1.CodeActSandbox();
1369
+ const bridge = new index_js_1.HostBridge(this.mcpExecutor);
1370
+ bridge.onToolUse = (toolName, input, result) => {
1371
+ if (result === undefined) {
1372
+ // Tool starting — surface to stream
1373
+ this.currentStreamCallbacks?.onToolUse?.(toolName, input);
1374
+ }
1375
+ if (result !== undefined) {
1376
+ // Tool completed — notify callback
1377
+ this.onToolUse?.(toolName, input, result);
1378
+ const isError = typeof result === 'object' &&
1379
+ result !== null &&
1380
+ 'success' in result &&
1381
+ !result.success;
1382
+ this.currentStreamCallbacks?.onToolComplete?.(toolName, `code_act_sub_${Date.now()}`, isError);
1383
+ }
1384
+ };
1385
+ bridge.injectInto(sandbox, tier);
1386
+ const result = await sandbox.execute(code);
1387
+ if (result.logs.length > 0) {
1388
+ console.log(`[CodeAct] console output: ${result.logs.join('\n')}`);
1389
+ }
1390
+ return result;
1391
+ }
1392
+ catch (err) {
1393
+ const message = err instanceof Error ? err.message : String(err);
1394
+ console.error(`[CodeAct] Sandbox initialization failed: ${message}`);
1395
+ return {
1396
+ success: false,
1397
+ error: {
1398
+ name: 'SandboxError',
1399
+ message: `Failed to initialize Code-Act sandbox: ${message}`,
1400
+ },
1401
+ logs: [],
1402
+ metrics: { durationMs: 0, hostCallCount: 0, memoryUsedBytes: 0 },
1403
+ };
1404
+ }
1405
+ }
1406
+ /**
1407
+ * Remove tool_call and code_act blocks from text (to avoid duplication in response)
1082
1408
  */
1083
1409
  removeToolCallBlocks(text) {
1084
- return text.replace(/```tool_call\s*\n[\s\S]*?\n```/g, '').trim();
1410
+ let result = text.replace(/```tool_call\s*\n[\s\S]*?\n```/g, '');
1411
+ if (this.useCodeAct) {
1412
+ result = result.replace(/```(?:js|javascript)\s*\n[\s\S]*?\n```/g, '');
1413
+ }
1414
+ return result.trim();
1085
1415
  }
1086
1416
  extractTextFromContent(content) {
1087
1417
  return content