@jungjaehoon/mama-os 0.9.4 → 0.9.5
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.
- package/README.md +18 -34
- package/dist/agent/agent-loop.d.ts +9 -3
- package/dist/agent/agent-loop.d.ts.map +1 -1
- package/dist/agent/agent-loop.js +218 -81
- package/dist/agent/agent-loop.js.map +1 -1
- package/dist/agent/claude-cli-wrapper.d.ts +2 -0
- package/dist/agent/claude-cli-wrapper.d.ts.map +1 -1
- package/dist/agent/claude-cli-wrapper.js.map +1 -1
- package/dist/agent/gateway-tool-executor.d.ts +8 -0
- package/dist/agent/gateway-tool-executor.d.ts.map +1 -1
- package/dist/agent/gateway-tool-executor.js +173 -5
- package/dist/agent/gateway-tool-executor.js.map +1 -1
- package/dist/agent/gateway-tools.md +24 -0
- package/dist/agent/persistent-cli-adapter.d.ts.map +1 -1
- package/dist/agent/persistent-cli-adapter.js +1 -0
- package/dist/agent/persistent-cli-adapter.js.map +1 -1
- package/dist/agent/prompt-enhancer.d.ts +12 -3
- package/dist/agent/prompt-enhancer.d.ts.map +1 -1
- package/dist/agent/prompt-enhancer.js +239 -23
- package/dist/agent/prompt-enhancer.js.map +1 -1
- package/dist/agent/types.d.ts +6 -2
- package/dist/agent/types.d.ts.map +1 -1
- package/dist/agent/types.js.map +1 -1
- package/dist/api/graph-api-types.d.ts +13 -0
- package/dist/api/graph-api-types.d.ts.map +1 -1
- package/dist/api/graph-api.d.ts.map +1 -1
- package/dist/api/graph-api.js +26 -4
- package/dist/api/graph-api.js.map +1 -1
- package/dist/api/skills-handler.d.ts.map +1 -1
- package/dist/api/skills-handler.js +26 -0
- package/dist/api/skills-handler.js.map +1 -1
- package/dist/api/upload-handler.d.ts.map +1 -1
- package/dist/api/upload-handler.js +60 -25
- package/dist/api/upload-handler.js.map +1 -1
- package/dist/cli/commands/run.js +1 -1
- package/dist/cli/commands/run.js.map +1 -1
- package/dist/cli/commands/start.d.ts.map +1 -1
- package/dist/cli/commands/start.js +297 -34
- package/dist/cli/commands/start.js.map +1 -1
- package/dist/cli/commands/status.js +1 -1
- package/dist/cli/commands/status.js.map +1 -1
- package/dist/cli/commands/stop.d.ts.map +1 -1
- package/dist/cli/commands/stop.js +80 -0
- package/dist/cli/commands/stop.js.map +1 -1
- package/dist/cli/config/config-manager.d.ts +9 -0
- package/dist/cli/config/config-manager.d.ts.map +1 -1
- package/dist/cli/config/config-manager.js +64 -1
- package/dist/cli/config/config-manager.js.map +1 -1
- package/dist/cli/config/types.d.ts +3 -4
- package/dist/cli/config/types.d.ts.map +1 -1
- package/dist/cli/config/types.js.map +1 -1
- package/dist/gateways/attachment-utils.d.ts +28 -0
- package/dist/gateways/attachment-utils.d.ts.map +1 -0
- package/dist/gateways/attachment-utils.js +201 -0
- package/dist/gateways/attachment-utils.js.map +1 -0
- package/dist/gateways/discord.d.ts +0 -14
- package/dist/gateways/discord.d.ts.map +1 -1
- package/dist/gateways/discord.js +61 -172
- package/dist/gateways/discord.js.map +1 -1
- package/dist/gateways/image-analyzer.d.ts.map +1 -1
- package/dist/gateways/image-analyzer.js +11 -3
- package/dist/gateways/image-analyzer.js.map +1 -1
- package/dist/gateways/message-router.d.ts +3 -0
- package/dist/gateways/message-router.d.ts.map +1 -1
- package/dist/gateways/message-router.js +34 -9
- package/dist/gateways/message-router.js.map +1 -1
- package/dist/gateways/slack.d.ts +5 -0
- package/dist/gateways/slack.d.ts.map +1 -1
- package/dist/gateways/slack.js +129 -7
- package/dist/gateways/slack.js.map +1 -1
- package/dist/gateways/tool-status-tracker.d.ts +87 -0
- package/dist/gateways/tool-status-tracker.d.ts.map +1 -0
- package/dist/gateways/tool-status-tracker.js +283 -0
- package/dist/gateways/tool-status-tracker.js.map +1 -0
- package/dist/multi-agent/agent-process-manager.d.ts +3 -10
- package/dist/multi-agent/agent-process-manager.d.ts.map +1 -1
- package/dist/multi-agent/agent-process-manager.js +108 -103
- package/dist/multi-agent/agent-process-manager.js.map +1 -1
- package/dist/multi-agent/delegation-manager.d.ts +27 -0
- package/dist/multi-agent/delegation-manager.d.ts.map +1 -1
- package/dist/multi-agent/delegation-manager.js +41 -0
- package/dist/multi-agent/delegation-manager.js.map +1 -1
- package/dist/multi-agent/multi-agent-base.d.ts +4 -0
- package/dist/multi-agent/multi-agent-base.d.ts.map +1 -1
- package/dist/multi-agent/multi-agent-base.js +10 -17
- package/dist/multi-agent/multi-agent-base.js.map +1 -1
- package/dist/multi-agent/multi-agent-discord.d.ts.map +1 -1
- package/dist/multi-agent/multi-agent-discord.js +42 -93
- package/dist/multi-agent/multi-agent-discord.js.map +1 -1
- package/dist/multi-agent/multi-agent-slack.d.ts +1 -0
- package/dist/multi-agent/multi-agent-slack.d.ts.map +1 -1
- package/dist/multi-agent/multi-agent-slack.js +52 -4
- package/dist/multi-agent/multi-agent-slack.js.map +1 -1
- package/dist/multi-agent/swarm/swarm-task-runner.d.ts.map +1 -1
- package/dist/multi-agent/swarm/swarm-task-runner.js +58 -37
- package/dist/multi-agent/swarm/swarm-task-runner.js.map +1 -1
- package/dist/multi-agent/types.d.ts +0 -6
- package/dist/multi-agent/types.d.ts.map +1 -1
- package/dist/multi-agent/types.js.map +1 -1
- package/dist/multi-agent/workflow-engine.d.ts.map +1 -1
- package/dist/multi-agent/workflow-engine.js +11 -10
- package/dist/multi-agent/workflow-engine.js.map +1 -1
- package/dist/skills/skill-registry.d.ts +7 -0
- package/dist/skills/skill-registry.d.ts.map +1 -1
- package/dist/skills/skill-registry.js +18 -0
- package/dist/skills/skill-registry.js.map +1 -1
- package/package.json +1 -1
- package/public/viewer/js/modules/chat.js +145 -76
- package/public/viewer/js/modules/playground.js +148 -0
- package/public/viewer/js/modules/skills.js +59 -4
- package/public/viewer/js/utils/api.js +6 -0
- package/public/viewer/js/utils/format.js +1 -1
- package/public/viewer/src/modules/chat.ts +223 -83
- package/public/viewer/src/modules/playground.ts +173 -0
- package/public/viewer/src/modules/skills.ts +61 -4
- package/public/viewer/src/utils/api.ts +12 -0
- package/public/viewer/src/utils/format.ts +1 -1
- package/public/viewer/viewer.html +170 -5
- package/templates/playgrounds/cron-workflow-lab.html +1601 -0
- package/templates/playgrounds/skill-lab-playground.html +1625 -0
- package/templates/playgrounds/wave-visualizer.html +694 -0
- package/templates/skills/frontend-design.md +71 -0
- package/templates/skills/multi-agent-collab.md +145 -0
- package/templates/skills/playground.md +197 -0
- package/templates/skills/scheduling.md +84 -0
- package/dist/multi-agent/agent-process-pool.d.ts +0 -148
- package/dist/multi-agent/agent-process-pool.d.ts.map +0 -1
- package/dist/multi-agent/agent-process-pool.js +0 -308
- package/dist/multi-agent/agent-process-pool.js.map +0 -1
package/README.md
CHANGED
|
@@ -156,6 +156,9 @@ gateways:
|
|
|
156
156
|
# In Slack
|
|
157
157
|
@mama what's the status?
|
|
158
158
|
@mama /report
|
|
159
|
+
|
|
160
|
+
# File upload support
|
|
161
|
+
@mama [attach image] translate this
|
|
159
162
|
```
|
|
160
163
|
|
|
161
164
|
### Telegram Bot
|
|
@@ -222,6 +225,20 @@ Built-in web interface for managing MAMA and chatting with your configured backe
|
|
|
222
225
|
- Topic filtering and search
|
|
223
226
|
- Export decisions (JSON, Markdown, CSV)
|
|
224
227
|
|
|
228
|
+
**🧩 Skills Tab**
|
|
229
|
+
|
|
230
|
+
- Browse installed skills with status badges (published/draft/coworking)
|
|
231
|
+
- Click to open in Skill Lab Playground for editing
|
|
232
|
+
- Skill verification with 12-point checklist
|
|
233
|
+
|
|
234
|
+
**🧪 Playground Tab**
|
|
235
|
+
|
|
236
|
+
- **Skill Lab** — Step-by-step skill creation, modification, and verification
|
|
237
|
+
- **Cron Workflow Lab** — Visual cron-scheduled workflow designer
|
|
238
|
+
- **Wave Visualizer** — Audio waveform playground for voice skill development
|
|
239
|
+
- Bidirectional sync with Skills Tab (select skill → opens in Skill Lab)
|
|
240
|
+
- "Open in new tab" for full-screen editing
|
|
241
|
+
|
|
225
242
|
**⚙️ Settings Tab**
|
|
226
243
|
|
|
227
244
|
- Configure gateway tokens
|
|
@@ -400,7 +417,6 @@ multi_agent:
|
|
|
400
417
|
bot_token: 'DISCORD_BOT_TOKEN_2'
|
|
401
418
|
tier: 1 # Full access for code changes
|
|
402
419
|
auto_continue: true
|
|
403
|
-
pool_size: 3 # Enable 3 parallel processes for this agent
|
|
404
420
|
auto_respond_keywords: ['bug', 'code', 'implement', '구현']
|
|
405
421
|
|
|
406
422
|
reviewer:
|
|
@@ -439,38 +455,6 @@ multi_agent:
|
|
|
439
455
|
global_cooldown_ms: 2000
|
|
440
456
|
```
|
|
441
457
|
|
|
442
|
-
### Process Pool (Parallel Execution)
|
|
443
|
-
|
|
444
|
-
Each agent runs as a separate backend CLI subprocess. By default, each agent has **1 process** (sequential execution). Configure `pool_size` to enable parallel task execution per agent.
|
|
445
|
-
|
|
446
|
-
```yaml
|
|
447
|
-
multi_agent:
|
|
448
|
-
agents:
|
|
449
|
-
developer:
|
|
450
|
-
pool_size: 5 # 5 parallel backend CLI processes for this agent
|
|
451
|
-
```
|
|
452
|
-
|
|
453
|
-
**How it works:**
|
|
454
|
-
|
|
455
|
-
- When a task arrives and all processes are busy, a new process is spawned (up to `pool_size`)
|
|
456
|
-
- Idle processes are reused automatically (no cold start penalty)
|
|
457
|
-
- Processes auto-terminate after 5 minutes of inactivity (`idleTimeoutMs`)
|
|
458
|
-
- Hung processes (busy > 15 minutes) are auto-killed (`hungTimeoutMs`)
|
|
459
|
-
|
|
460
|
-
**Pool status per agent:**
|
|
461
|
-
|
|
462
|
-
| State | Description |
|
|
463
|
-
| ------- | ----------------------------------- |
|
|
464
|
-
| `total` | Total processes currently in pool |
|
|
465
|
-
| `busy` | Processes handling active requests |
|
|
466
|
-
| `idle` | Processes ready for immediate reuse |
|
|
467
|
-
|
|
468
|
-
**Default:** `pool_size: 1` (sequential execution, safe default)
|
|
469
|
-
|
|
470
|
-
**Recommendation:** Start with `pool_size: 3` for implementation agents (Developer) and keep `pool_size: 1` for advisory agents (Reviewer, Explorer).
|
|
471
|
-
|
|
472
|
-
> **Note:** Each process spawns a separate backend CLI subprocess. Higher pool sizes increase memory and API usage proportionally.
|
|
473
|
-
|
|
474
458
|
### Delegation
|
|
475
459
|
|
|
476
460
|
Tier 1 agents can delegate tasks to other agents:
|
|
@@ -910,4 +894,4 @@ The multi-agent swarm architecture was inspired by [oh-my-opencode](https://gith
|
|
|
910
894
|
---
|
|
911
895
|
|
|
912
896
|
**Author:** SpineLift Team
|
|
913
|
-
**Last Updated:** 2026-02-
|
|
897
|
+
**Last Updated:** 2026-02-20
|
|
@@ -11,12 +11,17 @@
|
|
|
11
11
|
*/
|
|
12
12
|
import type { OAuthManager } from '../auth/index.js';
|
|
13
13
|
import type { ContentBlock, ToolDefinition, AgentLoopOptions, AgentLoopResult, ClaudeClientOptions, GatewayToolExecutorOptions, AgentContext } from './types.js';
|
|
14
|
+
/**
|
|
15
|
+
* Load full skill content on-demand for per-message injection.
|
|
16
|
+
* @param skillId - Skill identifier like "mama/playground"
|
|
17
|
+
*/
|
|
18
|
+
export declare function loadSkillContent(skillId: string): string | null;
|
|
14
19
|
/**
|
|
15
20
|
* Load installed & enabled skills from ~/.mama/skills/
|
|
16
|
-
* Returns skill
|
|
17
|
-
*
|
|
21
|
+
* Returns skill catalog lines for system prompt injection (on-demand mode).
|
|
22
|
+
* Full skill content is injected per-message via detectSkillMatch() in PromptEnhancer.
|
|
18
23
|
*/
|
|
19
|
-
export declare function loadInstalledSkills(verbose?: boolean,
|
|
24
|
+
export declare function loadInstalledSkills(verbose?: boolean, _options?: {
|
|
20
25
|
onlyCommands?: boolean;
|
|
21
26
|
}): string[];
|
|
22
27
|
export declare function loadComposedSystemPrompt(verbose?: boolean, context?: AgentContext): string;
|
|
@@ -46,6 +51,7 @@ export declare class AgentLoop {
|
|
|
46
51
|
private readonly stopContinuationHandler;
|
|
47
52
|
private readonly preCompactHandler;
|
|
48
53
|
private preCompactInjected;
|
|
54
|
+
private currentStreamCallbacks?;
|
|
49
55
|
constructor(_oauthManager: OAuthManager, options?: AgentLoopOptions, _clientOptions?: ClaudeClientOptions, executorOptions?: GatewayToolExecutorOptions);
|
|
50
56
|
/**
|
|
51
57
|
* Set session key for lane-based concurrency
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agent-loop.d.ts","sourceRoot":"","sources":["../../src/agent/agent-loop.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAUH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAIrD,OAAO,KAAK,EAEV,YAAY,EAIZ,cAAc,EACd,gBAAgB,EAChB,eAAe,EAIf,mBAAmB,EACnB,0BAA0B,EAE1B,YAAY,EACb,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"agent-loop.d.ts","sourceRoot":"","sources":["../../src/agent/agent-loop.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAUH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAIrD,OAAO,KAAK,EAEV,YAAY,EAIZ,cAAc,EACd,gBAAgB,EAChB,eAAe,EAIf,mBAAmB,EACnB,0BAA0B,EAE1B,YAAY,EACb,MAAM,YAAY,CAAC;AA4QpB;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CA2B/D;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CACjC,OAAO,UAAQ,EACf,QAAQ,GAAE;IAAE,YAAY,CAAC,EAAE,OAAO,CAAA;CAAO,GACxC,MAAM,EAAE,CAEV;AAED,wBAAgB,wBAAwB,CAAC,OAAO,UAAQ,EAAE,OAAO,CAAC,EAAE,YAAY,GAAG,MAAM,CA8ExF;AAED;;;GAGG;AACH,wBAAgB,qBAAqB,IAAI,MAAM,CAuB9C;AAED,qBAAa,SAAS;IACpB,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAyC;IAC/D,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAqC;IACnE,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAsB;IAClD,OAAO,CAAC,oBAAoB,CAAC,CAAS;IACtC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAS;IAC/B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC,CAA2B;IACnD,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAC,CAA8D;IACzF,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAC,CAOnB;IACX,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAc;IAC1C,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAU;IACnC,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAc;IAC1C,OAAO,CAAC,QAAQ,CAAC,WAAW,CAA8B;IAC1D,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAU;IACxC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAyB;IACjD,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAyB;IACzD,OAAO,CAAC,QAAQ,CAAC,uBAAuB,CAAiC;IACzE,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAA2B;IAC7D,OAAO,CAAC,kBAAkB,CAAS;IACnC,OAAO,CAAC,sBAAsB,CAAC,CAAkB;gBAG/C,aAAa,EAAE,YAAY,EAC3B,OAAO,GAAE,gBAAqB,EAC9B,cAAc,CAAC,EAAE,mBAAmB,EACpC,eAAe,CAAC,EAAE,0BAA0B;IA+N9C;;;OAGG;IACH,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI;IAIhC;;OAEG;IACH,aAAa,IAAI,MAAM;IAIvB,OAAO,CAAC,2BAA2B;IASnC;;OAEG;IACH,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI;IAIjD;;OAEG;IACH,iBAAiB,CAAC,OAAO,EAAE;QACzB,WAAW,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;QAC/D,QAAQ,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;QAC/E,SAAS,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;KAClF,GAAG,IAAI;IAIR;;;;;;;;;;;;OAYG;IACG,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,gBAAgB,GAAG,OAAO,CAAC,eAAe,CAAC;IAkB/E;;;;;;;;;OASG;IACG,cAAc,CAClB,OAAO,EAAE,YAAY,EAAE,EACvB,OAAO,CAAC,EAAE,gBAAgB,GACzB,OAAO,CAAC,eAAe,CAAC;IAiB3B;;OAEG;YACW,sBAAsB;IAybpC;;OAEG;YACW,YAAY;IAmE1B;;;;;;OAMG;YACW,sBAAsB;IA4CpC;;;OAGG;IACH,OAAO,CAAC,sBAAsB;IA0B9B;;OAEG;IACH,OAAO,CAAC,oBAAoB;IAI5B,OAAO,CAAC,sBAAsB;IAO9B;;OAEG;IACH,OAAO,CAAC,mBAAmB;IAuB3B;;;OAGG;IACH,OAAO,CAAC,qBAAqB;IA8E7B;;OAEG;IACH,MAAM,CAAC,kBAAkB,IAAI,cAAc,EAAE;IAI7C;;OAEG;IACH,MAAM,CAAC,sBAAsB,IAAI,MAAM;IAIvC;;OAEG;IACH,OAAO,CAAC,OAAO,CAAS;IAExB,IAAI,IAAI,IAAI;CAmBb"}
|
package/dist/agent/agent-loop.js
CHANGED
|
@@ -45,6 +45,7 @@ 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;
|
|
49
50
|
exports.loadComposedSystemPrompt = loadComposedSystemPrompt;
|
|
50
51
|
exports.getGatewayToolsPrompt = getGatewayToolsPrompt;
|
|
@@ -183,16 +184,57 @@ function collectMarkdownFiles(dir, prefix = '') {
|
|
|
183
184
|
}
|
|
184
185
|
return results;
|
|
185
186
|
}
|
|
187
|
+
// ─── Skill On-Demand Injection ───────────────────────────────────────────────
|
|
186
188
|
/**
|
|
187
|
-
*
|
|
188
|
-
|
|
189
|
-
|
|
189
|
+
* Parse YAML frontmatter from skill .md file
|
|
190
|
+
*/
|
|
191
|
+
function parseSkillFrontmatter(content) {
|
|
192
|
+
const match = content.match(/^---\n([\s\S]*?)\n---/);
|
|
193
|
+
if (!match)
|
|
194
|
+
return { name: '', description: '', keywords: [] };
|
|
195
|
+
const block = match[1];
|
|
196
|
+
const name = (block.match(/^name:\s*(.+)$/m)?.[1] ?? '').trim();
|
|
197
|
+
const description = (block.match(/^description:\s*(.+)$/m)?.[1] ?? '').trim();
|
|
198
|
+
const kwBlock = block.match(/^keywords:\n((?:[ \t]+-[ \t]*.+\n?)+)/m);
|
|
199
|
+
const keywords = kwBlock
|
|
200
|
+
? kwBlock[1]
|
|
201
|
+
.trim()
|
|
202
|
+
.split('\n')
|
|
203
|
+
.map((l) => l.replace(/^[ \t]*-[ \t]*/, '').trim())
|
|
204
|
+
.filter((k) => k.length > 0)
|
|
205
|
+
: [];
|
|
206
|
+
return { name, description, keywords };
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Find the main .md file for a directory skill (for frontmatter parsing)
|
|
210
|
+
*/
|
|
211
|
+
function findMainSkillFile(skillDir, skillName) {
|
|
212
|
+
for (const name of [`${skillName}.md`, 'skill.md', 'index.md']) {
|
|
213
|
+
const p = (0, path_1.join)(skillDir, name);
|
|
214
|
+
if ((0, fs_1.existsSync)(p))
|
|
215
|
+
return p;
|
|
216
|
+
}
|
|
217
|
+
try {
|
|
218
|
+
const entries = (0, fs_1.readdirSync)(skillDir, { withFileTypes: true });
|
|
219
|
+
for (const e of entries) {
|
|
220
|
+
if (e.isFile() && e.name.endsWith('.md') && !EXCLUDED_SKILL_FILES.has(e.name)) {
|
|
221
|
+
return (0, path_1.join)(skillDir, e.name);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
catch {
|
|
226
|
+
/* ignore */
|
|
227
|
+
}
|
|
228
|
+
return null;
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* Build skill catalog (one line per enabled skill) for system prompt.
|
|
232
|
+
* Format: "- [source/skillId] keywords: kw1, kw2 | description"
|
|
190
233
|
*/
|
|
191
|
-
function
|
|
234
|
+
function buildSkillCatalog(verbose = false) {
|
|
192
235
|
const skillsBase = (0, path_1.join)((0, os_1.homedir)(), '.mama', 'skills');
|
|
193
236
|
const stateFile = (0, path_1.join)(skillsBase, 'state.json');
|
|
194
|
-
const
|
|
195
|
-
// Load state (enabled/disabled tracking)
|
|
237
|
+
const catalog = [];
|
|
196
238
|
let state = {};
|
|
197
239
|
try {
|
|
198
240
|
if ((0, fs_1.existsSync)(stateFile)) {
|
|
@@ -200,7 +242,7 @@ function loadInstalledSkills(verbose = false, options = {}) {
|
|
|
200
242
|
}
|
|
201
243
|
}
|
|
202
244
|
catch {
|
|
203
|
-
|
|
245
|
+
/* no state file */
|
|
204
246
|
}
|
|
205
247
|
const sources = ['mama', 'cowork', 'external'];
|
|
206
248
|
for (const source of sources) {
|
|
@@ -213,60 +255,100 @@ function loadInstalledSkills(verbose = false, options = {}) {
|
|
|
213
255
|
if (!entry.isDirectory())
|
|
214
256
|
continue;
|
|
215
257
|
const stateKey = `${source}/${entry.name}`;
|
|
216
|
-
// Skip disabled skills
|
|
217
258
|
if (state[stateKey]?.enabled === false)
|
|
218
259
|
continue;
|
|
219
260
|
const skillDir = (0, path_1.join)(sourceDir, entry.name);
|
|
220
|
-
|
|
221
|
-
if (
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
const
|
|
226
|
-
|
|
261
|
+
const mainFile = findMainSkillFile(skillDir, entry.name);
|
|
262
|
+
if (!mainFile)
|
|
263
|
+
continue;
|
|
264
|
+
try {
|
|
265
|
+
const content = (0, fs_1.readFileSync)(mainFile, 'utf-8');
|
|
266
|
+
const fm = parseSkillFrontmatter(content);
|
|
267
|
+
const description = fm.description || '';
|
|
268
|
+
const keywords = fm.keywords.length > 0 ? fm.keywords.join(', ') : entry.name;
|
|
269
|
+
catalog.push(`- [${stateKey}] keywords: ${keywords} | ${description}`);
|
|
227
270
|
if (verbose)
|
|
228
|
-
console.log(`[AgentLoop]
|
|
271
|
+
console.log(`[AgentLoop] Skill catalog: ${stateKey}`);
|
|
272
|
+
}
|
|
273
|
+
catch {
|
|
274
|
+
/* skip unreadable */
|
|
229
275
|
}
|
|
230
276
|
}
|
|
231
277
|
}
|
|
232
278
|
catch {
|
|
233
|
-
|
|
279
|
+
/* directory read failed */
|
|
234
280
|
}
|
|
235
281
|
}
|
|
236
|
-
//
|
|
282
|
+
// Flat .md files at root
|
|
237
283
|
try {
|
|
238
284
|
const rootEntries = (0, fs_1.readdirSync)(skillsBase, { withFileTypes: true });
|
|
239
285
|
for (const entry of rootEntries) {
|
|
240
|
-
if (!entry.isFile() || !entry.name.endsWith('.md'))
|
|
286
|
+
if (!entry.isFile() || !entry.name.endsWith('.md'))
|
|
241
287
|
continue;
|
|
242
|
-
|
|
243
|
-
if (EXCLUDED_SKILL_FILES.has(entry.name)) {
|
|
288
|
+
if (EXCLUDED_SKILL_FILES.has(entry.name))
|
|
244
289
|
continue;
|
|
245
|
-
}
|
|
246
290
|
const id = entry.name.replace(/\.md$/, '');
|
|
247
291
|
const stateKey = `mama/${id}`;
|
|
248
|
-
|
|
249
|
-
if (state[stateKey]?.enabled === false) {
|
|
292
|
+
if (state[stateKey]?.enabled === false)
|
|
250
293
|
continue;
|
|
251
|
-
}
|
|
252
|
-
// Skip if already loaded from subdirectory
|
|
253
|
-
if (blocks.some((b) => b.includes(`[Skill: mama/${id}]`))) {
|
|
294
|
+
if (catalog.some((l) => l.includes(`[${stateKey}]`)))
|
|
254
295
|
continue;
|
|
296
|
+
try {
|
|
297
|
+
const content = (0, fs_1.readFileSync)((0, path_1.join)(skillsBase, entry.name), 'utf-8');
|
|
298
|
+
const fm = parseSkillFrontmatter(content);
|
|
299
|
+
const description = fm.description || '';
|
|
300
|
+
const keywords = fm.keywords.length > 0 ? fm.keywords.join(', ') : id;
|
|
301
|
+
catalog.push(`- [${stateKey}] keywords: ${keywords} | ${description}`);
|
|
302
|
+
if (verbose)
|
|
303
|
+
console.log(`[AgentLoop] Skill catalog (flat): ${stateKey}`);
|
|
255
304
|
}
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
if (content.length > MAX_SKILL_FILE_CHARS) {
|
|
259
|
-
content = content.slice(0, MAX_SKILL_FILE_CHARS) + '\n\n[... truncated]';
|
|
305
|
+
catch {
|
|
306
|
+
/* skip */
|
|
260
307
|
}
|
|
261
|
-
blocks.push(`# [Skill: mama/${id}]\n\n${content}`);
|
|
262
|
-
if (verbose)
|
|
263
|
-
console.log(`[AgentLoop] Loaded root skill: ${id}`);
|
|
264
308
|
}
|
|
265
309
|
}
|
|
266
310
|
catch {
|
|
267
|
-
|
|
311
|
+
/* root directory read failed */
|
|
312
|
+
}
|
|
313
|
+
return catalog;
|
|
314
|
+
}
|
|
315
|
+
/**
|
|
316
|
+
* Load full skill content on-demand for per-message injection.
|
|
317
|
+
* @param skillId - Skill identifier like "mama/playground"
|
|
318
|
+
*/
|
|
319
|
+
function loadSkillContent(skillId) {
|
|
320
|
+
const skillsBase = (0, path_1.join)((0, os_1.homedir)(), '.mama', 'skills');
|
|
321
|
+
// Try directory skill first
|
|
322
|
+
const skillDir = (0, path_1.join)(skillsBase, skillId);
|
|
323
|
+
if ((0, fs_1.existsSync)(skillDir)) {
|
|
324
|
+
const mdFiles = collectMarkdownFiles(skillDir);
|
|
325
|
+
if (mdFiles.length > 0) {
|
|
326
|
+
const parts = mdFiles.map((f) => `## ${f.path}\n\n${f.content}`);
|
|
327
|
+
return `# [Skill: ${skillId}]\n\n${parts.join('\n\n---\n\n')}`;
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
// Try flat .md file: "mama/playground" → skills/playground.md
|
|
331
|
+
const idParts = skillId.split('/');
|
|
332
|
+
if (idParts.length >= 2) {
|
|
333
|
+
const flatPath = (0, path_1.join)(skillsBase, `${idParts[idParts.length - 1]}.md`);
|
|
334
|
+
if ((0, fs_1.existsSync)(flatPath)) {
|
|
335
|
+
try {
|
|
336
|
+
return (0, fs_1.readFileSync)(flatPath, 'utf-8');
|
|
337
|
+
}
|
|
338
|
+
catch {
|
|
339
|
+
/* skip */
|
|
340
|
+
}
|
|
341
|
+
}
|
|
268
342
|
}
|
|
269
|
-
return
|
|
343
|
+
return null;
|
|
344
|
+
}
|
|
345
|
+
/**
|
|
346
|
+
* Load installed & enabled skills from ~/.mama/skills/
|
|
347
|
+
* Returns skill catalog lines for system prompt injection (on-demand mode).
|
|
348
|
+
* Full skill content is injected per-message via detectSkillMatch() in PromptEnhancer.
|
|
349
|
+
*/
|
|
350
|
+
function loadInstalledSkills(verbose = false, _options = {}) {
|
|
351
|
+
return buildSkillCatalog(verbose);
|
|
270
352
|
}
|
|
271
353
|
function loadComposedSystemPrompt(verbose = false, context) {
|
|
272
354
|
const mamaHome = (0, path_1.join)((0, os_1.homedir)(), '.mama');
|
|
@@ -298,26 +380,20 @@ function loadComposedSystemPrompt(verbose = false, context) {
|
|
|
298
380
|
console.log(`[AgentLoop] Persona file not found (skipping): ${file}`);
|
|
299
381
|
}
|
|
300
382
|
}
|
|
301
|
-
// Load
|
|
302
|
-
const
|
|
303
|
-
if (
|
|
383
|
+
// Load skill catalog (on-demand mode — full content injected per-message by PromptEnhancer)
|
|
384
|
+
const skillCatalog = loadInstalledSkills(verbose);
|
|
385
|
+
if (skillCatalog.length > 0) {
|
|
304
386
|
const skillDirective = [
|
|
305
|
-
'# Installed Skills
|
|
387
|
+
'# Installed Skills',
|
|
306
388
|
'',
|
|
307
|
-
'
|
|
308
|
-
'
|
|
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',
|
|
389
|
+
'To invoke a skill, include its keywords in your message.',
|
|
390
|
+
'The full skill instructions will be injected automatically when matched.',
|
|
315
391
|
'',
|
|
316
|
-
|
|
392
|
+
...skillCatalog,
|
|
317
393
|
].join('\n');
|
|
318
394
|
layers.push(skillDirective);
|
|
319
395
|
if (verbose)
|
|
320
|
-
console.log(`[AgentLoop]
|
|
396
|
+
console.log(`[AgentLoop] Skill catalog: ${skillCatalog.length} skills`);
|
|
321
397
|
}
|
|
322
398
|
// Add context prompt if AgentContext is provided (role awareness)
|
|
323
399
|
if (context) {
|
|
@@ -395,6 +471,7 @@ class AgentLoop {
|
|
|
395
471
|
stopContinuationHandler;
|
|
396
472
|
preCompactHandler;
|
|
397
473
|
preCompactInjected = false;
|
|
474
|
+
currentStreamCallbacks;
|
|
398
475
|
constructor(_oauthManager, options = {}, _clientOptions, executorOptions) {
|
|
399
476
|
// Initialize tools config (hybrid Gateway/MCP routing)
|
|
400
477
|
this.toolsConfig = {
|
|
@@ -423,38 +500,88 @@ class AgentLoop {
|
|
|
423
500
|
else {
|
|
424
501
|
logger.debug('⚙️ Gateway-only mode');
|
|
425
502
|
}
|
|
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
|
|
503
|
+
// Build system prompt with layered truncation support
|
|
434
504
|
const monitor = new prompt_size_monitor_js_1.PromptSizeMonitor();
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
505
|
+
let promptLayers;
|
|
506
|
+
if (options.systemPrompt) {
|
|
507
|
+
// Custom system prompt (e.g., multi-agent): treat as a single critical layer
|
|
508
|
+
promptLayers = [{ name: 'custom', content: options.systemPrompt, priority: 1 }];
|
|
509
|
+
}
|
|
510
|
+
else {
|
|
511
|
+
// Composed prompt: build layers with individual priorities for graceful truncation
|
|
512
|
+
// Priority 1 (never cut): CLAUDE.md base instructions
|
|
513
|
+
// Priority 2 (cut if extreme): personas (SOUL, IDENTITY, USER) + gateway tools
|
|
514
|
+
// Priority 3 (cut first): context prompt + skills + onboarding
|
|
515
|
+
const mamaHome = (0, path_1.join)((0, os_1.homedir)(), '.mama');
|
|
516
|
+
const claudeMd = loadSystemPrompt();
|
|
517
|
+
const personaFiles = ['SOUL.md', 'IDENTITY.md', 'USER.md'];
|
|
518
|
+
const personaParts = [];
|
|
519
|
+
for (const file of personaFiles) {
|
|
520
|
+
const p = (0, path_1.join)(mamaHome, file);
|
|
521
|
+
if ((0, fs_1.existsSync)(p))
|
|
522
|
+
personaParts.push((0, fs_1.readFileSync)(p, 'utf-8'));
|
|
523
|
+
}
|
|
524
|
+
const skillCatalog = loadInstalledSkills();
|
|
525
|
+
const onboardingPath = (0, path_1.join)(mamaHome, 'ONBOARDING.md');
|
|
526
|
+
const onboardingContent = (0, fs_1.existsSync)(onboardingPath)
|
|
527
|
+
? (0, fs_1.readFileSync)(onboardingPath, 'utf-8')
|
|
528
|
+
: '';
|
|
529
|
+
promptLayers = [
|
|
530
|
+
{ name: 'claudeMd', content: claudeMd, priority: 1 },
|
|
531
|
+
...(personaParts.length > 0
|
|
532
|
+
? [
|
|
533
|
+
{
|
|
534
|
+
name: 'personas',
|
|
535
|
+
content: personaParts.join('\n\n---\n\n'),
|
|
536
|
+
priority: 2,
|
|
537
|
+
},
|
|
538
|
+
]
|
|
539
|
+
: []),
|
|
540
|
+
...(skillCatalog.length > 0
|
|
541
|
+
? [
|
|
542
|
+
{
|
|
543
|
+
name: 'skills',
|
|
544
|
+
content: [
|
|
545
|
+
'# Installed Skills',
|
|
546
|
+
'',
|
|
547
|
+
'To invoke a skill, include its keywords in your message.',
|
|
548
|
+
'',
|
|
549
|
+
...skillCatalog,
|
|
550
|
+
].join('\n'),
|
|
551
|
+
priority: 3,
|
|
552
|
+
},
|
|
553
|
+
]
|
|
554
|
+
: []),
|
|
555
|
+
...(onboardingContent
|
|
556
|
+
? [{ name: 'onboarding', content: onboardingContent, priority: 4 }]
|
|
557
|
+
: []),
|
|
558
|
+
];
|
|
559
|
+
}
|
|
560
|
+
if (useGatewayMode) {
|
|
561
|
+
const gatewayToolsPrompt = getGatewayToolsPrompt();
|
|
562
|
+
if (gatewayToolsPrompt) {
|
|
563
|
+
promptLayers.push({ name: 'gatewayTools', content: gatewayToolsPrompt, priority: 2 });
|
|
564
|
+
}
|
|
565
|
+
}
|
|
441
566
|
const checkResult = monitor.check(promptLayers);
|
|
442
567
|
if (checkResult.warning) {
|
|
443
568
|
logger.warn(checkResult.warning);
|
|
444
569
|
}
|
|
445
|
-
//
|
|
570
|
+
// Enforce truncation if over budget (priority > 1 layers trimmed first)
|
|
446
571
|
if (!checkResult.withinBudget) {
|
|
447
572
|
const { layers: trimmedLayers, result: enforceResult } = monitor.enforce(promptLayers);
|
|
448
573
|
if (enforceResult.truncatedLayers.length > 0) {
|
|
449
574
|
logger.warn(`Truncated layers: ${enforceResult.truncatedLayers.join(', ')}`);
|
|
450
575
|
}
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
defaultSystemPrompt = trimmedTools ? `${trimmedBase}\n\n---\n\n${trimmedTools}` : trimmedBase;
|
|
454
|
-
logger.debug(`System prompt truncated: ${checkResult.totalChars} → ${defaultSystemPrompt.length} chars`);
|
|
576
|
+
promptLayers = trimmedLayers;
|
|
577
|
+
logger.debug(`System prompt truncated: ${checkResult.totalChars} → ${enforceResult.totalChars} chars`);
|
|
455
578
|
}
|
|
579
|
+
const defaultSystemPrompt = promptLayers
|
|
580
|
+
.filter((l) => l.content.length > 0)
|
|
581
|
+
.map((l) => l.content)
|
|
582
|
+
.join('\n\n---\n\n');
|
|
456
583
|
// Choose backend (default: claude)
|
|
457
|
-
this.backend = options.backend
|
|
584
|
+
this.backend = options.backend;
|
|
458
585
|
if (this.backend === 'codex-mcp') {
|
|
459
586
|
// Codex MCP mode: standard MCP protocol
|
|
460
587
|
const workspaceDir = (0, path_1.join)((0, os_1.homedir)(), '.mama', 'workspace');
|
|
@@ -480,12 +607,14 @@ class AgentLoop {
|
|
|
480
607
|
systemPrompt: defaultSystemPrompt,
|
|
481
608
|
// Hybrid mode: pass MCP config even with Gateway tools enabled
|
|
482
609
|
mcpConfigPath: useMCPMode ? mcpConfigPath : undefined,
|
|
483
|
-
//
|
|
484
|
-
// Security is enforced by MAMA's RoleManager
|
|
485
|
-
//
|
|
486
|
-
dangerouslySkipPermissions:
|
|
610
|
+
// MAMA OS is a headless daemon (no TTY) — Claude CLI's interactive permission prompts
|
|
611
|
+
// cannot work. Security is enforced by MAMA's own RoleManager layer (config.yaml roles).
|
|
612
|
+
// DO NOT gate this on env vars — MAMA manages permissions via its config, not Claude CLI.
|
|
613
|
+
dangerouslySkipPermissions: options.dangerouslySkipPermissions ?? false,
|
|
487
614
|
// Gateway tools are processed by GatewayToolExecutor (hybrid with MCP)
|
|
488
615
|
useGatewayTools: useGatewayMode,
|
|
616
|
+
// Pass configured timeout (default in PersistentCLI: 120s — too short for complex tasks)
|
|
617
|
+
requestTimeout: options.timeoutMs,
|
|
489
618
|
});
|
|
490
619
|
this.agent = this.persistentCLI;
|
|
491
620
|
logger.debug('🚀 Claude PersistentCLI mode enabled - faster responses');
|
|
@@ -623,6 +752,7 @@ class AgentLoop {
|
|
|
623
752
|
* Internal implementation of runWithContent (without lane queueing)
|
|
624
753
|
*/
|
|
625
754
|
async runWithContentInternal(content, options) {
|
|
755
|
+
this.currentStreamCallbacks = options?.streamCallbacks;
|
|
626
756
|
const history = [];
|
|
627
757
|
const totalUsage = { input_tokens: 0, output_tokens: 0 };
|
|
628
758
|
let turn = 0;
|
|
@@ -710,19 +840,19 @@ class AgentLoop {
|
|
|
710
840
|
throw new types_js_1.AgentError(`Emergency stop: Agent loop exceeded emergency maximum turns (${EMERGENCY_MAX_TURNS})`, 'EMERGENCY_MAX_TURNS', undefined, false);
|
|
711
841
|
}
|
|
712
842
|
let response;
|
|
843
|
+
const ext = this.currentStreamCallbacks;
|
|
713
844
|
const callbacks = {
|
|
714
845
|
onDelta: (text) => {
|
|
715
|
-
|
|
846
|
+
ext?.onDelta?.(text);
|
|
716
847
|
},
|
|
717
|
-
onToolUse: (name,
|
|
718
|
-
|
|
848
|
+
onToolUse: (name, input) => {
|
|
849
|
+
ext?.onToolUse?.(name, input);
|
|
719
850
|
},
|
|
720
|
-
onFinal: (
|
|
721
|
-
|
|
851
|
+
onFinal: (finalResponse) => {
|
|
852
|
+
ext?.onFinal?.(finalResponse);
|
|
722
853
|
},
|
|
723
854
|
onError: (error) => {
|
|
724
|
-
|
|
725
|
-
// Don't throw - let the promise rejection handle it
|
|
855
|
+
ext?.onError?.(error);
|
|
726
856
|
},
|
|
727
857
|
};
|
|
728
858
|
let piResult;
|
|
@@ -970,6 +1100,7 @@ class AgentLoop {
|
|
|
970
1100
|
if (ownedSession) {
|
|
971
1101
|
this.sessionPool.releaseSession(channelKey);
|
|
972
1102
|
}
|
|
1103
|
+
this.currentStreamCallbacks = undefined;
|
|
973
1104
|
}
|
|
974
1105
|
}
|
|
975
1106
|
/**
|
|
@@ -981,6 +1112,8 @@ class AgentLoop {
|
|
|
981
1112
|
for (const toolUse of toolUseBlocks) {
|
|
982
1113
|
let result;
|
|
983
1114
|
let isError = false;
|
|
1115
|
+
// Notify stream: tool execution starting
|
|
1116
|
+
this.currentStreamCallbacks?.onToolUse?.(toolUse.name, toolUse.input);
|
|
984
1117
|
try {
|
|
985
1118
|
// PreToolUse: search MAMA for contracts before Write operations
|
|
986
1119
|
let contractContext = '';
|
|
@@ -996,12 +1129,16 @@ class AgentLoop {
|
|
|
996
1129
|
this.onToolUse?.(toolUse.name, toolUse.input, toolResult);
|
|
997
1130
|
// PostToolUse: auto-extract contracts (fire-and-forget)
|
|
998
1131
|
this.postToolHandler?.processInBackground(toolUse.name, toolUse.input, toolResult);
|
|
1132
|
+
// Notify stream: tool completed successfully
|
|
1133
|
+
this.currentStreamCallbacks?.onToolComplete?.(toolUse.name, toolUse.id, false);
|
|
999
1134
|
}
|
|
1000
1135
|
catch (error) {
|
|
1001
1136
|
isError = true;
|
|
1002
1137
|
result = error instanceof Error ? error.message : String(error);
|
|
1003
1138
|
// Notify tool use callback with error
|
|
1004
1139
|
this.onToolUse?.(toolUse.name, toolUse.input, { error: result });
|
|
1140
|
+
// Notify stream: tool completed with error
|
|
1141
|
+
this.currentStreamCallbacks?.onToolComplete?.(toolUse.name, toolUse.id, true);
|
|
1005
1142
|
}
|
|
1006
1143
|
results.push({
|
|
1007
1144
|
type: 'tool_result',
|