devsh-memory-mcp 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +39 -9
- package/dist/{chunk-YTXBZTQ2.js → chunk-U4TYMR2Q.js} +966 -1
- package/dist/cli.js +1 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -88,14 +88,37 @@ With custom options:
|
|
|
88
88
|
| `spawn_agent` | Spawn a sub-agent to work on a task |
|
|
89
89
|
| `get_agent_status` | Get status of a spawned agent |
|
|
90
90
|
| `list_spawned_agents` | List all agents in current orchestration |
|
|
91
|
-
| `wait_for_agent` | Wait for agent to complete (
|
|
91
|
+
| `wait_for_agent` | Wait for agent to complete (polling) |
|
|
92
|
+
| `wait_for_events` | Wait for events via SSE (event-driven, recommended) |
|
|
92
93
|
| `cancel_agent` | Cancel a running/pending agent |
|
|
93
94
|
| `get_orchestration_summary` | Get dashboard-style orchestration summary |
|
|
94
|
-
| `pull_orchestration_updates` | Sync local PLAN.json with server |
|
|
95
|
+
| `pull_orchestration_updates` | Sync local PLAN.json with server (read) |
|
|
96
|
+
| `push_orchestration_updates` | Push task status/completion to server (write) |
|
|
95
97
|
| `read_orchestration` | Read PLAN.json, AGENTS.json, or EVENTS.jsonl |
|
|
96
98
|
| `append_event` | Append an orchestration event to EVENTS.jsonl |
|
|
97
99
|
| `update_plan_task` | Update task status in PLAN.json |
|
|
98
100
|
|
|
101
|
+
### Provider Session Tools
|
|
102
|
+
|
|
103
|
+
| Tool | Description |
|
|
104
|
+
|------|-------------|
|
|
105
|
+
| `bind_provider_session` | Bind a Claude session ID or Codex thread ID to task |
|
|
106
|
+
| `get_provider_session` | Get provider session binding for task resume |
|
|
107
|
+
|
|
108
|
+
### Orchestration Learning Tools
|
|
109
|
+
|
|
110
|
+
| Tool | Description |
|
|
111
|
+
|------|-------------|
|
|
112
|
+
| `log_learning` | Log an orchestration learning, error, or feature request to the server |
|
|
113
|
+
| `get_active_orchestration_rules` | Fetch active orchestration rules for the team |
|
|
114
|
+
|
|
115
|
+
**log_learning types:**
|
|
116
|
+
- `learning` - Discovered a better orchestration pattern
|
|
117
|
+
- `error` - Found an error pattern to avoid
|
|
118
|
+
- `feature_request` - Missing capability that would help
|
|
119
|
+
|
|
120
|
+
Logged items are reviewed by team leads and may be promoted to active orchestration rules.
|
|
121
|
+
|
|
99
122
|
### Environment Variables (Orchestration)
|
|
100
123
|
|
|
101
124
|
| Variable | Description |
|
|
@@ -109,15 +132,22 @@ With custom options:
|
|
|
109
132
|
```
|
|
110
133
|
/root/lifecycle/memory/
|
|
111
134
|
├── knowledge/
|
|
112
|
-
│ └── MEMORY.md
|
|
135
|
+
│ └── MEMORY.md # Long-term insights (P0/P1/P2 sections)
|
|
113
136
|
├── daily/
|
|
114
|
-
│ └── {date}.md
|
|
137
|
+
│ └── {date}.md # Daily session logs
|
|
115
138
|
├── orchestration/
|
|
116
|
-
│ ├── PLAN.json
|
|
117
|
-
│ ├── AGENTS.json
|
|
118
|
-
│ └── EVENTS.jsonl
|
|
119
|
-
├──
|
|
120
|
-
|
|
139
|
+
│ ├── PLAN.json # Orchestration task plan
|
|
140
|
+
│ ├── AGENTS.json # Agent registry
|
|
141
|
+
│ └── EVENTS.jsonl # Orchestration event log
|
|
142
|
+
├── behavior/
|
|
143
|
+
│ ├── HOT.md # Active workflow preferences
|
|
144
|
+
│ ├── corrections.jsonl # User corrections log
|
|
145
|
+
│ ├── LEARNINGS.jsonl # Orchestration learnings
|
|
146
|
+
│ ├── ERRORS.jsonl # Error patterns
|
|
147
|
+
│ ├── FEATURE_REQUESTS.jsonl # Feature requests
|
|
148
|
+
│ └── skill-candidates.json # Repeated patterns
|
|
149
|
+
├── TASKS.json # Task registry
|
|
150
|
+
└── MAILBOX.json # Inter-agent messages
|
|
121
151
|
```
|
|
122
152
|
|
|
123
153
|
## Priority Tiers (MEMORY.md)
|
|
@@ -28,7 +28,6 @@ function createMemoryMcpServer(config) {
|
|
|
28
28
|
const eventsPath = path.join(orchestrationDir, "EVENTS.jsonl");
|
|
29
29
|
function readFile(filePath) {
|
|
30
30
|
try {
|
|
31
|
-
if (!fs.existsSync(filePath)) return null;
|
|
32
31
|
return fs.readFileSync(filePath, "utf-8");
|
|
33
32
|
} catch {
|
|
34
33
|
return null;
|
|
@@ -422,6 +421,33 @@ function createMemoryMcpServer(config) {
|
|
|
422
421
|
}
|
|
423
422
|
}
|
|
424
423
|
},
|
|
424
|
+
{
|
|
425
|
+
name: "push_orchestration_updates",
|
|
426
|
+
description: "Push local orchestration state to the server. Reports task completion/failure and head agent status. Used for heartbeats and signaling orchestration completion. Requires CMUX_TASK_RUN_JWT environment variable.",
|
|
427
|
+
inputSchema: {
|
|
428
|
+
type: "object",
|
|
429
|
+
properties: {
|
|
430
|
+
orchestrationId: {
|
|
431
|
+
type: "string",
|
|
432
|
+
description: "The orchestration ID to push updates for. Uses CMUX_ORCHESTRATION_ID env var if not provided."
|
|
433
|
+
},
|
|
434
|
+
headAgentStatus: {
|
|
435
|
+
type: "string",
|
|
436
|
+
enum: ["running", "completed", "failed"],
|
|
437
|
+
description: "Head agent's overall status (optional, for heartbeat/completion signal)"
|
|
438
|
+
},
|
|
439
|
+
message: {
|
|
440
|
+
type: "string",
|
|
441
|
+
description: "Optional status message from head agent"
|
|
442
|
+
},
|
|
443
|
+
taskIds: {
|
|
444
|
+
type: "array",
|
|
445
|
+
items: { type: "string" },
|
|
446
|
+
description: "Optional list of local task IDs to push (default: all completed/failed tasks)"
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
},
|
|
425
451
|
{
|
|
426
452
|
name: "spawn_agent",
|
|
427
453
|
description: "Spawn a sub-agent to work on a task. Requires CMUX_TASK_RUN_JWT for authentication. The sub-agent runs in a new sandbox and works on the specified prompt.",
|
|
@@ -528,6 +554,154 @@ function createMemoryMcpServer(config) {
|
|
|
528
554
|
type: "object",
|
|
529
555
|
properties: {}
|
|
530
556
|
}
|
|
557
|
+
},
|
|
558
|
+
{
|
|
559
|
+
name: "bind_provider_session",
|
|
560
|
+
description: "Bind a provider-specific session ID to the current task. Use to enable session resume on task retry. Claude agents should call this with their session ID, Codex agents with their thread ID.",
|
|
561
|
+
inputSchema: {
|
|
562
|
+
type: "object",
|
|
563
|
+
properties: {
|
|
564
|
+
providerSessionId: {
|
|
565
|
+
type: "string",
|
|
566
|
+
description: "Claude session ID or equivalent for the provider"
|
|
567
|
+
},
|
|
568
|
+
providerThreadId: {
|
|
569
|
+
type: "string",
|
|
570
|
+
description: "Codex thread ID or equivalent for thread-based providers"
|
|
571
|
+
},
|
|
572
|
+
replyChannel: {
|
|
573
|
+
type: "string",
|
|
574
|
+
enum: ["mailbox", "sse", "pty", "ui"],
|
|
575
|
+
description: "Preferred communication channel for receiving messages"
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
},
|
|
580
|
+
{
|
|
581
|
+
name: "get_provider_session",
|
|
582
|
+
description: "Get the provider session binding for a task. Use to check if a session can be resumed.",
|
|
583
|
+
inputSchema: {
|
|
584
|
+
type: "object",
|
|
585
|
+
properties: {
|
|
586
|
+
taskId: {
|
|
587
|
+
type: "string",
|
|
588
|
+
description: "The orchestration task ID to get session for (optional, uses current task)"
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
},
|
|
593
|
+
{
|
|
594
|
+
name: "wait_for_events",
|
|
595
|
+
description: "Wait for orchestration events using SSE streaming. Returns when a matching event arrives or timeout. More efficient than polling wait_for_agent. Use for event-driven head agent loops.",
|
|
596
|
+
inputSchema: {
|
|
597
|
+
type: "object",
|
|
598
|
+
properties: {
|
|
599
|
+
orchestrationId: {
|
|
600
|
+
type: "string",
|
|
601
|
+
description: "The orchestration ID to subscribe to events for"
|
|
602
|
+
},
|
|
603
|
+
eventTypes: {
|
|
604
|
+
type: "array",
|
|
605
|
+
items: { type: "string" },
|
|
606
|
+
description: "Event types to wait for (e.g., ['task_completed', 'approval_required']). If empty, returns on any event."
|
|
607
|
+
},
|
|
608
|
+
timeout: {
|
|
609
|
+
type: "number",
|
|
610
|
+
description: "Maximum wait time in milliseconds (default: 30000 = 30 seconds)"
|
|
611
|
+
}
|
|
612
|
+
},
|
|
613
|
+
required: ["orchestrationId"]
|
|
614
|
+
}
|
|
615
|
+
},
|
|
616
|
+
{
|
|
617
|
+
name: "get_pending_approvals",
|
|
618
|
+
description: "Get pending approval requests for the current orchestration. Returns approvals that require human decision before agents can proceed.",
|
|
619
|
+
inputSchema: {
|
|
620
|
+
type: "object",
|
|
621
|
+
properties: {
|
|
622
|
+
orchestrationId: {
|
|
623
|
+
type: "string",
|
|
624
|
+
description: "The orchestration ID to get pending approvals for (optional, uses current if not provided)"
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
},
|
|
629
|
+
{
|
|
630
|
+
name: "resolve_approval",
|
|
631
|
+
description: "Resolve a pending approval request. Use to approve or deny actions that require human authorization.",
|
|
632
|
+
inputSchema: {
|
|
633
|
+
type: "object",
|
|
634
|
+
properties: {
|
|
635
|
+
requestId: {
|
|
636
|
+
type: "string",
|
|
637
|
+
description: "The approval request ID (apr_xxx format)"
|
|
638
|
+
},
|
|
639
|
+
resolution: {
|
|
640
|
+
type: "string",
|
|
641
|
+
enum: ["allow", "allow_once", "allow_session", "deny", "deny_always"],
|
|
642
|
+
description: "Resolution decision: allow (permit action), allow_once (permit this time only), allow_session (permit for session), deny (reject), deny_always (block permanently)"
|
|
643
|
+
},
|
|
644
|
+
note: {
|
|
645
|
+
type: "string",
|
|
646
|
+
description: "Optional note explaining the decision"
|
|
647
|
+
}
|
|
648
|
+
},
|
|
649
|
+
required: ["requestId", "resolution"]
|
|
650
|
+
}
|
|
651
|
+
},
|
|
652
|
+
{
|
|
653
|
+
name: "refresh_policy_rules",
|
|
654
|
+
description: "Fetch the latest centralized policy rules from the server and update local instruction files. Use this to get updated policies without restarting the sandbox. Requires CMUX_TASK_RUN_JWT.",
|
|
655
|
+
inputSchema: {
|
|
656
|
+
type: "object",
|
|
657
|
+
properties: {}
|
|
658
|
+
}
|
|
659
|
+
},
|
|
660
|
+
{
|
|
661
|
+
name: "log_learning",
|
|
662
|
+
description: "Log an orchestration learning, error, or feature request to the server. Learnings captured here are reviewed by team leads and may be promoted to active orchestration rules. Requires CMUX_TASK_RUN_JWT.",
|
|
663
|
+
inputSchema: {
|
|
664
|
+
type: "object",
|
|
665
|
+
properties: {
|
|
666
|
+
type: {
|
|
667
|
+
type: "string",
|
|
668
|
+
enum: ["learning", "error", "feature_request"],
|
|
669
|
+
description: "Type of event to log"
|
|
670
|
+
},
|
|
671
|
+
text: {
|
|
672
|
+
type: "string",
|
|
673
|
+
description: "The learning, error description, or feature request text"
|
|
674
|
+
},
|
|
675
|
+
lane: {
|
|
676
|
+
type: "string",
|
|
677
|
+
enum: ["hot", "orchestration", "project"],
|
|
678
|
+
description: "Suggested lane for the rule (default: orchestration)"
|
|
679
|
+
},
|
|
680
|
+
confidence: {
|
|
681
|
+
type: "number",
|
|
682
|
+
description: "Confidence score 0.0-1.0 (default: 0.5 for learnings, 0.8 for errors)"
|
|
683
|
+
},
|
|
684
|
+
metadata: {
|
|
685
|
+
type: "object",
|
|
686
|
+
description: "Optional metadata (e.g., error stack, related task IDs)"
|
|
687
|
+
}
|
|
688
|
+
},
|
|
689
|
+
required: ["type", "text"]
|
|
690
|
+
}
|
|
691
|
+
},
|
|
692
|
+
{
|
|
693
|
+
name: "get_active_orchestration_rules",
|
|
694
|
+
description: "Fetch the currently active orchestration rules for this team. Returns rules that are injected into agent instruction files. Requires CMUX_TASK_RUN_JWT.",
|
|
695
|
+
inputSchema: {
|
|
696
|
+
type: "object",
|
|
697
|
+
properties: {
|
|
698
|
+
lane: {
|
|
699
|
+
type: "string",
|
|
700
|
+
enum: ["hot", "orchestration", "project"],
|
|
701
|
+
description: "Filter by lane (optional)"
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
}
|
|
531
705
|
}
|
|
532
706
|
]
|
|
533
707
|
}));
|
|
@@ -874,6 +1048,111 @@ function createMemoryMcpServer(config) {
|
|
|
874
1048
|
};
|
|
875
1049
|
}
|
|
876
1050
|
}
|
|
1051
|
+
case "push_orchestration_updates": {
|
|
1052
|
+
const { orchestrationId: argOrchId, headAgentStatus, message, taskIds } = args;
|
|
1053
|
+
const orchestrationId = argOrchId ?? process.env.CMUX_ORCHESTRATION_ID;
|
|
1054
|
+
const jwt = process.env.CMUX_TASK_RUN_JWT;
|
|
1055
|
+
const apiBaseUrl = process.env.CMUX_API_BASE_URL ?? "https://cmux.sh";
|
|
1056
|
+
if (!orchestrationId) {
|
|
1057
|
+
return {
|
|
1058
|
+
content: [{
|
|
1059
|
+
type: "text",
|
|
1060
|
+
text: "No orchestration ID provided. Pass orchestrationId parameter or set CMUX_ORCHESTRATION_ID env var."
|
|
1061
|
+
}]
|
|
1062
|
+
};
|
|
1063
|
+
}
|
|
1064
|
+
if (!jwt) {
|
|
1065
|
+
return {
|
|
1066
|
+
content: [{
|
|
1067
|
+
type: "text",
|
|
1068
|
+
text: "CMUX_TASK_RUN_JWT environment variable not set. This tool requires JWT authentication."
|
|
1069
|
+
}]
|
|
1070
|
+
};
|
|
1071
|
+
}
|
|
1072
|
+
try {
|
|
1073
|
+
const plan = readPlan();
|
|
1074
|
+
if (!plan && !headAgentStatus) {
|
|
1075
|
+
return {
|
|
1076
|
+
content: [{
|
|
1077
|
+
type: "text",
|
|
1078
|
+
text: "No PLAN.json found and no headAgentStatus provided. Nothing to push."
|
|
1079
|
+
}]
|
|
1080
|
+
};
|
|
1081
|
+
}
|
|
1082
|
+
const tasksToPush = [];
|
|
1083
|
+
if (plan) {
|
|
1084
|
+
for (const task of plan.tasks) {
|
|
1085
|
+
if (taskIds && taskIds.length > 0 && !taskIds.includes(task.id)) {
|
|
1086
|
+
continue;
|
|
1087
|
+
}
|
|
1088
|
+
if (task.status === "completed" || task.status === "failed") {
|
|
1089
|
+
tasksToPush.push({
|
|
1090
|
+
id: task.id,
|
|
1091
|
+
status: task.status,
|
|
1092
|
+
result: task.result,
|
|
1093
|
+
errorMessage: task.errorMessage
|
|
1094
|
+
});
|
|
1095
|
+
}
|
|
1096
|
+
}
|
|
1097
|
+
}
|
|
1098
|
+
const url = `${apiBaseUrl}/api/v1/cmux/orchestration/${orchestrationId}/sync`;
|
|
1099
|
+
const response = await fetch(url, {
|
|
1100
|
+
method: "POST",
|
|
1101
|
+
headers: {
|
|
1102
|
+
"Authorization": `Bearer ${jwt}`,
|
|
1103
|
+
"Content-Type": "application/json"
|
|
1104
|
+
},
|
|
1105
|
+
body: JSON.stringify({
|
|
1106
|
+
orchestrationId,
|
|
1107
|
+
headAgentStatus,
|
|
1108
|
+
message,
|
|
1109
|
+
tasks: tasksToPush.length > 0 ? tasksToPush : void 0
|
|
1110
|
+
})
|
|
1111
|
+
});
|
|
1112
|
+
if (!response.ok) {
|
|
1113
|
+
const errorText = await response.text();
|
|
1114
|
+
return {
|
|
1115
|
+
content: [{
|
|
1116
|
+
type: "text",
|
|
1117
|
+
text: `Failed to push orchestration updates: ${response.status} ${errorText}`
|
|
1118
|
+
}]
|
|
1119
|
+
};
|
|
1120
|
+
}
|
|
1121
|
+
const result = await response.json();
|
|
1122
|
+
appendEvent({
|
|
1123
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1124
|
+
event: "orchestration_pushed",
|
|
1125
|
+
message: result.message ?? `Pushed ${tasksToPush.length} tasks`,
|
|
1126
|
+
metadata: {
|
|
1127
|
+
orchestrationId,
|
|
1128
|
+
headAgentStatus,
|
|
1129
|
+
tasksPushed: tasksToPush.length,
|
|
1130
|
+
tasksUpdated: result.tasksUpdated
|
|
1131
|
+
}
|
|
1132
|
+
});
|
|
1133
|
+
return {
|
|
1134
|
+
content: [{
|
|
1135
|
+
type: "text",
|
|
1136
|
+
text: JSON.stringify({
|
|
1137
|
+
success: true,
|
|
1138
|
+
orchestrationId,
|
|
1139
|
+
headAgentStatus,
|
|
1140
|
+
tasksPushed: tasksToPush.length,
|
|
1141
|
+
tasksUpdated: result.tasksUpdated,
|
|
1142
|
+
message: result.message
|
|
1143
|
+
}, null, 2)
|
|
1144
|
+
}]
|
|
1145
|
+
};
|
|
1146
|
+
} catch (error) {
|
|
1147
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
1148
|
+
return {
|
|
1149
|
+
content: [{
|
|
1150
|
+
type: "text",
|
|
1151
|
+
text: `Error pushing orchestration updates: ${errorMsg}`
|
|
1152
|
+
}]
|
|
1153
|
+
};
|
|
1154
|
+
}
|
|
1155
|
+
}
|
|
877
1156
|
case "spawn_agent": {
|
|
878
1157
|
const { prompt, agentName: spawnAgentName, repo, branch, dependsOn, priority } = args;
|
|
879
1158
|
const jwt = process.env.CMUX_TASK_RUN_JWT;
|
|
@@ -1289,6 +1568,692 @@ function createMemoryMcpServer(config) {
|
|
|
1289
1568
|
};
|
|
1290
1569
|
}
|
|
1291
1570
|
}
|
|
1571
|
+
case "bind_provider_session": {
|
|
1572
|
+
const { providerSessionId, providerThreadId, replyChannel } = args;
|
|
1573
|
+
const jwt = process.env.CMUX_TASK_RUN_JWT;
|
|
1574
|
+
const apiBaseUrl = process.env.CMUX_API_BASE_URL ?? "https://cmux.sh";
|
|
1575
|
+
const orchestrationId = process.env.CMUX_ORCHESTRATION_ID;
|
|
1576
|
+
const taskRunId = process.env.CMUX_TASK_RUN_ID;
|
|
1577
|
+
if (!jwt) {
|
|
1578
|
+
return {
|
|
1579
|
+
content: [{
|
|
1580
|
+
type: "text",
|
|
1581
|
+
text: "CMUX_TASK_RUN_JWT environment variable not set."
|
|
1582
|
+
}]
|
|
1583
|
+
};
|
|
1584
|
+
}
|
|
1585
|
+
if (!orchestrationId) {
|
|
1586
|
+
return {
|
|
1587
|
+
content: [{
|
|
1588
|
+
type: "text",
|
|
1589
|
+
text: "CMUX_ORCHESTRATION_ID environment variable not set."
|
|
1590
|
+
}]
|
|
1591
|
+
};
|
|
1592
|
+
}
|
|
1593
|
+
try {
|
|
1594
|
+
const url = `${apiBaseUrl}/api/v1/cmux/orchestration/sessions/bind`;
|
|
1595
|
+
const response = await fetch(url, {
|
|
1596
|
+
method: "POST",
|
|
1597
|
+
headers: {
|
|
1598
|
+
"Authorization": `Bearer ${jwt}`,
|
|
1599
|
+
"Content-Type": "application/json"
|
|
1600
|
+
},
|
|
1601
|
+
body: JSON.stringify({
|
|
1602
|
+
orchestrationId,
|
|
1603
|
+
taskRunId,
|
|
1604
|
+
providerSessionId,
|
|
1605
|
+
providerThreadId,
|
|
1606
|
+
replyChannel,
|
|
1607
|
+
agentName
|
|
1608
|
+
})
|
|
1609
|
+
});
|
|
1610
|
+
if (!response.ok) {
|
|
1611
|
+
const errorText = await response.text();
|
|
1612
|
+
return {
|
|
1613
|
+
content: [{
|
|
1614
|
+
type: "text",
|
|
1615
|
+
text: `Failed to bind session: ${response.status} ${errorText}`
|
|
1616
|
+
}]
|
|
1617
|
+
};
|
|
1618
|
+
}
|
|
1619
|
+
const result = await response.json();
|
|
1620
|
+
return {
|
|
1621
|
+
content: [{
|
|
1622
|
+
type: "text",
|
|
1623
|
+
text: JSON.stringify({
|
|
1624
|
+
success: true,
|
|
1625
|
+
message: "Provider session bound successfully",
|
|
1626
|
+
bindingId: result.bindingId,
|
|
1627
|
+
updated: result.updated
|
|
1628
|
+
}, null, 2)
|
|
1629
|
+
}]
|
|
1630
|
+
};
|
|
1631
|
+
} catch (error) {
|
|
1632
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
1633
|
+
return {
|
|
1634
|
+
content: [{
|
|
1635
|
+
type: "text",
|
|
1636
|
+
text: `Error binding provider session: ${errorMsg}`
|
|
1637
|
+
}]
|
|
1638
|
+
};
|
|
1639
|
+
}
|
|
1640
|
+
}
|
|
1641
|
+
case "get_provider_session": {
|
|
1642
|
+
const { taskId: providedTaskId } = args;
|
|
1643
|
+
const jwt = process.env.CMUX_TASK_RUN_JWT;
|
|
1644
|
+
const apiBaseUrl = process.env.CMUX_API_BASE_URL ?? "https://cmux.sh";
|
|
1645
|
+
const taskId = providedTaskId ?? process.env.CMUX_TASK_RUN_ID;
|
|
1646
|
+
if (!jwt) {
|
|
1647
|
+
return {
|
|
1648
|
+
content: [{
|
|
1649
|
+
type: "text",
|
|
1650
|
+
text: "CMUX_TASK_RUN_JWT environment variable not set."
|
|
1651
|
+
}]
|
|
1652
|
+
};
|
|
1653
|
+
}
|
|
1654
|
+
if (!taskId) {
|
|
1655
|
+
return {
|
|
1656
|
+
content: [{
|
|
1657
|
+
type: "text",
|
|
1658
|
+
text: "No task ID provided and CMUX_TASK_RUN_ID not set."
|
|
1659
|
+
}]
|
|
1660
|
+
};
|
|
1661
|
+
}
|
|
1662
|
+
try {
|
|
1663
|
+
const url = `${apiBaseUrl}/api/v1/cmux/orchestration/sessions/${taskId}`;
|
|
1664
|
+
const response = await fetch(url, {
|
|
1665
|
+
method: "GET",
|
|
1666
|
+
headers: {
|
|
1667
|
+
"Authorization": `Bearer ${jwt}`
|
|
1668
|
+
}
|
|
1669
|
+
});
|
|
1670
|
+
if (!response.ok) {
|
|
1671
|
+
if (response.status === 404) {
|
|
1672
|
+
return {
|
|
1673
|
+
content: [{
|
|
1674
|
+
type: "text",
|
|
1675
|
+
text: JSON.stringify({
|
|
1676
|
+
found: false,
|
|
1677
|
+
message: "No provider session binding found for this task"
|
|
1678
|
+
}, null, 2)
|
|
1679
|
+
}]
|
|
1680
|
+
};
|
|
1681
|
+
}
|
|
1682
|
+
const errorText = await response.text();
|
|
1683
|
+
return {
|
|
1684
|
+
content: [{
|
|
1685
|
+
type: "text",
|
|
1686
|
+
text: `Failed to get session: ${response.status} ${errorText}`
|
|
1687
|
+
}]
|
|
1688
|
+
};
|
|
1689
|
+
}
|
|
1690
|
+
const session = await response.json();
|
|
1691
|
+
return {
|
|
1692
|
+
content: [{
|
|
1693
|
+
type: "text",
|
|
1694
|
+
text: JSON.stringify({
|
|
1695
|
+
found: true,
|
|
1696
|
+
taskId: session.taskId,
|
|
1697
|
+
orchestrationId: session.orchestrationId,
|
|
1698
|
+
provider: session.provider,
|
|
1699
|
+
agentName: session.agentName,
|
|
1700
|
+
mode: session.mode,
|
|
1701
|
+
providerSessionId: session.providerSessionId,
|
|
1702
|
+
providerThreadId: session.providerThreadId,
|
|
1703
|
+
replyChannel: session.replyChannel,
|
|
1704
|
+
status: session.status,
|
|
1705
|
+
lastActiveAt: session.lastActiveAt
|
|
1706
|
+
}, null, 2)
|
|
1707
|
+
}]
|
|
1708
|
+
};
|
|
1709
|
+
} catch (error) {
|
|
1710
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
1711
|
+
return {
|
|
1712
|
+
content: [{
|
|
1713
|
+
type: "text",
|
|
1714
|
+
text: `Error getting provider session: ${errorMsg}`
|
|
1715
|
+
}]
|
|
1716
|
+
};
|
|
1717
|
+
}
|
|
1718
|
+
}
|
|
1719
|
+
case "wait_for_events": {
|
|
1720
|
+
const { orchestrationId, eventTypes = [], timeout = 3e4 } = args;
|
|
1721
|
+
const jwt = process.env.CMUX_TASK_RUN_JWT;
|
|
1722
|
+
const apiBaseUrl = process.env.CMUX_API_BASE_URL ?? "https://cmux.sh";
|
|
1723
|
+
if (!jwt) {
|
|
1724
|
+
return {
|
|
1725
|
+
content: [{
|
|
1726
|
+
type: "text",
|
|
1727
|
+
text: "CMUX_TASK_RUN_JWT environment variable not set. This tool requires JWT authentication."
|
|
1728
|
+
}]
|
|
1729
|
+
};
|
|
1730
|
+
}
|
|
1731
|
+
const startTime = Date.now();
|
|
1732
|
+
let reader;
|
|
1733
|
+
try {
|
|
1734
|
+
const url = `${apiBaseUrl}/api/orchestrate/v2/events/${orchestrationId}`;
|
|
1735
|
+
const controller = new AbortController();
|
|
1736
|
+
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
1737
|
+
const response = await fetch(url, {
|
|
1738
|
+
method: "GET",
|
|
1739
|
+
headers: {
|
|
1740
|
+
"Authorization": `Bearer ${jwt}`,
|
|
1741
|
+
"Accept": "text/event-stream"
|
|
1742
|
+
},
|
|
1743
|
+
signal: controller.signal
|
|
1744
|
+
});
|
|
1745
|
+
if (!response.ok) {
|
|
1746
|
+
clearTimeout(timeoutId);
|
|
1747
|
+
const errorText = await response.text();
|
|
1748
|
+
return {
|
|
1749
|
+
content: [{
|
|
1750
|
+
type: "text",
|
|
1751
|
+
text: `Failed to connect to event stream: ${response.status} ${errorText}`
|
|
1752
|
+
}]
|
|
1753
|
+
};
|
|
1754
|
+
}
|
|
1755
|
+
reader = response.body?.getReader();
|
|
1756
|
+
if (!reader) {
|
|
1757
|
+
clearTimeout(timeoutId);
|
|
1758
|
+
return {
|
|
1759
|
+
content: [{
|
|
1760
|
+
type: "text",
|
|
1761
|
+
text: "No response body from event stream"
|
|
1762
|
+
}]
|
|
1763
|
+
};
|
|
1764
|
+
}
|
|
1765
|
+
const decoder = new TextDecoder();
|
|
1766
|
+
let buffer = "";
|
|
1767
|
+
while (Date.now() - startTime < timeout) {
|
|
1768
|
+
const { value, done } = await reader.read();
|
|
1769
|
+
if (done) break;
|
|
1770
|
+
buffer += decoder.decode(value, { stream: true });
|
|
1771
|
+
const lines = buffer.split("\n");
|
|
1772
|
+
buffer = lines.pop() ?? "";
|
|
1773
|
+
for (const line of lines) {
|
|
1774
|
+
if (line.startsWith("data: ")) {
|
|
1775
|
+
try {
|
|
1776
|
+
const event = JSON.parse(line.slice(6));
|
|
1777
|
+
if (eventTypes.length === 0 || eventTypes.includes(event.type)) {
|
|
1778
|
+
clearTimeout(timeoutId);
|
|
1779
|
+
return {
|
|
1780
|
+
content: [{
|
|
1781
|
+
type: "text",
|
|
1782
|
+
text: JSON.stringify({
|
|
1783
|
+
event,
|
|
1784
|
+
waitDuration: Date.now() - startTime
|
|
1785
|
+
}, null, 2)
|
|
1786
|
+
}]
|
|
1787
|
+
};
|
|
1788
|
+
}
|
|
1789
|
+
} catch {
|
|
1790
|
+
}
|
|
1791
|
+
}
|
|
1792
|
+
}
|
|
1793
|
+
}
|
|
1794
|
+
clearTimeout(timeoutId);
|
|
1795
|
+
return {
|
|
1796
|
+
content: [{
|
|
1797
|
+
type: "text",
|
|
1798
|
+
text: JSON.stringify({
|
|
1799
|
+
status: "timeout",
|
|
1800
|
+
message: `No matching events received within ${timeout}ms`,
|
|
1801
|
+
eventTypesFilter: eventTypes,
|
|
1802
|
+
waitDuration: Date.now() - startTime
|
|
1803
|
+
}, null, 2)
|
|
1804
|
+
}]
|
|
1805
|
+
};
|
|
1806
|
+
} catch (error) {
|
|
1807
|
+
if (error instanceof Error && error.name === "AbortError") {
|
|
1808
|
+
return {
|
|
1809
|
+
content: [{
|
|
1810
|
+
type: "text",
|
|
1811
|
+
text: JSON.stringify({
|
|
1812
|
+
status: "timeout",
|
|
1813
|
+
message: `Connection timed out after ${timeout}ms`
|
|
1814
|
+
}, null, 2)
|
|
1815
|
+
}]
|
|
1816
|
+
};
|
|
1817
|
+
}
|
|
1818
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
1819
|
+
return {
|
|
1820
|
+
content: [{
|
|
1821
|
+
type: "text",
|
|
1822
|
+
text: `Error waiting for events: ${errorMsg}`
|
|
1823
|
+
}]
|
|
1824
|
+
};
|
|
1825
|
+
} finally {
|
|
1826
|
+
if (reader) {
|
|
1827
|
+
try {
|
|
1828
|
+
reader.cancel();
|
|
1829
|
+
} catch {
|
|
1830
|
+
}
|
|
1831
|
+
}
|
|
1832
|
+
}
|
|
1833
|
+
}
|
|
1834
|
+
case "get_pending_approvals": {
|
|
1835
|
+
const { orchestrationId: providedOrchId } = args;
|
|
1836
|
+
const jwt = process.env.CMUX_TASK_RUN_JWT;
|
|
1837
|
+
const apiBaseUrl = process.env.CMUX_API_BASE_URL ?? "https://cmux.sh";
|
|
1838
|
+
const orchestrationId = providedOrchId ?? process.env.CMUX_ORCHESTRATION_ID;
|
|
1839
|
+
if (!jwt) {
|
|
1840
|
+
return {
|
|
1841
|
+
content: [{
|
|
1842
|
+
type: "text",
|
|
1843
|
+
text: "CMUX_TASK_RUN_JWT environment variable not set. This tool requires JWT authentication."
|
|
1844
|
+
}]
|
|
1845
|
+
};
|
|
1846
|
+
}
|
|
1847
|
+
if (!orchestrationId) {
|
|
1848
|
+
return {
|
|
1849
|
+
content: [{
|
|
1850
|
+
type: "text",
|
|
1851
|
+
text: "No orchestration ID provided and CMUX_ORCHESTRATION_ID not set."
|
|
1852
|
+
}]
|
|
1853
|
+
};
|
|
1854
|
+
}
|
|
1855
|
+
try {
|
|
1856
|
+
const url = `${apiBaseUrl}/api/orchestrate/approvals/${orchestrationId}/pending`;
|
|
1857
|
+
const response = await fetch(url, {
|
|
1858
|
+
method: "GET",
|
|
1859
|
+
headers: {
|
|
1860
|
+
"Authorization": `Bearer ${jwt}`,
|
|
1861
|
+
"Content-Type": "application/json"
|
|
1862
|
+
}
|
|
1863
|
+
});
|
|
1864
|
+
if (!response.ok) {
|
|
1865
|
+
const errorText = await response.text();
|
|
1866
|
+
return {
|
|
1867
|
+
content: [{
|
|
1868
|
+
type: "text",
|
|
1869
|
+
text: `Failed to get pending approvals: ${response.status} ${errorText}`
|
|
1870
|
+
}]
|
|
1871
|
+
};
|
|
1872
|
+
}
|
|
1873
|
+
const approvals = await response.json();
|
|
1874
|
+
return {
|
|
1875
|
+
content: [{
|
|
1876
|
+
type: "text",
|
|
1877
|
+
text: JSON.stringify({
|
|
1878
|
+
orchestrationId,
|
|
1879
|
+
pendingCount: Array.isArray(approvals) ? approvals.length : 0,
|
|
1880
|
+
approvals
|
|
1881
|
+
}, null, 2)
|
|
1882
|
+
}]
|
|
1883
|
+
};
|
|
1884
|
+
} catch (error) {
|
|
1885
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
1886
|
+
return {
|
|
1887
|
+
content: [{
|
|
1888
|
+
type: "text",
|
|
1889
|
+
text: `Error getting pending approvals: ${errorMsg}`
|
|
1890
|
+
}]
|
|
1891
|
+
};
|
|
1892
|
+
}
|
|
1893
|
+
}
|
|
1894
|
+
case "resolve_approval": {
|
|
1895
|
+
const { requestId, resolution, note } = args;
|
|
1896
|
+
const jwt = process.env.CMUX_TASK_RUN_JWT;
|
|
1897
|
+
const apiBaseUrl = process.env.CMUX_API_BASE_URL ?? "https://cmux.sh";
|
|
1898
|
+
if (!jwt) {
|
|
1899
|
+
return {
|
|
1900
|
+
content: [{
|
|
1901
|
+
type: "text",
|
|
1902
|
+
text: "CMUX_TASK_RUN_JWT environment variable not set. This tool requires JWT authentication."
|
|
1903
|
+
}]
|
|
1904
|
+
};
|
|
1905
|
+
}
|
|
1906
|
+
try {
|
|
1907
|
+
const url = `${apiBaseUrl}/api/orchestrate/approvals/${requestId}/resolve`;
|
|
1908
|
+
const response = await fetch(url, {
|
|
1909
|
+
method: "POST",
|
|
1910
|
+
headers: {
|
|
1911
|
+
"Authorization": `Bearer ${jwt}`,
|
|
1912
|
+
"Content-Type": "application/json"
|
|
1913
|
+
},
|
|
1914
|
+
body: JSON.stringify({ resolution, note })
|
|
1915
|
+
});
|
|
1916
|
+
if (!response.ok) {
|
|
1917
|
+
const errorText = await response.text();
|
|
1918
|
+
return {
|
|
1919
|
+
content: [{
|
|
1920
|
+
type: "text",
|
|
1921
|
+
text: `Failed to resolve approval: ${response.status} ${errorText}`
|
|
1922
|
+
}]
|
|
1923
|
+
};
|
|
1924
|
+
}
|
|
1925
|
+
const result = await response.json();
|
|
1926
|
+
return {
|
|
1927
|
+
content: [{
|
|
1928
|
+
type: "text",
|
|
1929
|
+
text: JSON.stringify({
|
|
1930
|
+
requestId,
|
|
1931
|
+
resolution,
|
|
1932
|
+
status: "resolved",
|
|
1933
|
+
...result
|
|
1934
|
+
}, null, 2)
|
|
1935
|
+
}]
|
|
1936
|
+
};
|
|
1937
|
+
} catch (error) {
|
|
1938
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
1939
|
+
return {
|
|
1940
|
+
content: [{
|
|
1941
|
+
type: "text",
|
|
1942
|
+
text: `Error resolving approval: ${errorMsg}`
|
|
1943
|
+
}]
|
|
1944
|
+
};
|
|
1945
|
+
}
|
|
1946
|
+
}
|
|
1947
|
+
case "refresh_policy_rules": {
|
|
1948
|
+
const jwt = process.env.CMUX_TASK_RUN_JWT;
|
|
1949
|
+
const callbackUrl = process.env.CMUX_CALLBACK_URL;
|
|
1950
|
+
const agentNameFull = process.env.CMUX_AGENT_NAME;
|
|
1951
|
+
const isOrchestrationHead = process.env.CMUX_IS_ORCHESTRATION_HEAD === "1";
|
|
1952
|
+
if (!jwt) {
|
|
1953
|
+
return {
|
|
1954
|
+
content: [{
|
|
1955
|
+
type: "text",
|
|
1956
|
+
text: "CMUX_TASK_RUN_JWT environment variable not set. This tool requires JWT authentication."
|
|
1957
|
+
}]
|
|
1958
|
+
};
|
|
1959
|
+
}
|
|
1960
|
+
if (!callbackUrl) {
|
|
1961
|
+
return {
|
|
1962
|
+
content: [{
|
|
1963
|
+
type: "text",
|
|
1964
|
+
text: "CMUX_CALLBACK_URL environment variable not set. Cannot reach policy server."
|
|
1965
|
+
}]
|
|
1966
|
+
};
|
|
1967
|
+
}
|
|
1968
|
+
if (!agentNameFull) {
|
|
1969
|
+
return {
|
|
1970
|
+
content: [{
|
|
1971
|
+
type: "text",
|
|
1972
|
+
text: "CMUX_AGENT_NAME environment variable not set. Cannot determine agent type."
|
|
1973
|
+
}]
|
|
1974
|
+
};
|
|
1975
|
+
}
|
|
1976
|
+
const agentType = agentNameFull.split("/")[0];
|
|
1977
|
+
const validAgentTypes = ["claude", "codex", "gemini", "opencode"];
|
|
1978
|
+
if (!validAgentTypes.includes(agentType)) {
|
|
1979
|
+
return {
|
|
1980
|
+
content: [{
|
|
1981
|
+
type: "text",
|
|
1982
|
+
text: `Unknown agent type '${agentType}'. Expected one of: ${validAgentTypes.join(", ")}`
|
|
1983
|
+
}]
|
|
1984
|
+
};
|
|
1985
|
+
}
|
|
1986
|
+
const context = isOrchestrationHead ? "cloud_workspace" : "task_sandbox";
|
|
1987
|
+
try {
|
|
1988
|
+
const url = new URL(`${callbackUrl}/api/agent/policy-rules`);
|
|
1989
|
+
url.searchParams.set("agentType", agentType);
|
|
1990
|
+
url.searchParams.set("context", context);
|
|
1991
|
+
const response = await fetch(url.toString(), {
|
|
1992
|
+
method: "GET",
|
|
1993
|
+
headers: {
|
|
1994
|
+
"x-cmux-token": jwt,
|
|
1995
|
+
"Convex-Client": "node-1.0.0"
|
|
1996
|
+
}
|
|
1997
|
+
});
|
|
1998
|
+
if (!response.ok) {
|
|
1999
|
+
const errorText = await response.text();
|
|
2000
|
+
return {
|
|
2001
|
+
content: [{
|
|
2002
|
+
type: "text",
|
|
2003
|
+
text: `Failed to fetch policy rules: ${response.status} ${errorText}`
|
|
2004
|
+
}]
|
|
2005
|
+
};
|
|
2006
|
+
}
|
|
2007
|
+
const result = await response.json();
|
|
2008
|
+
if (!result.rules || result.rules.length === 0) {
|
|
2009
|
+
return {
|
|
2010
|
+
content: [{
|
|
2011
|
+
type: "text",
|
|
2012
|
+
text: "No policy rules found for this context."
|
|
2013
|
+
}]
|
|
2014
|
+
};
|
|
2015
|
+
}
|
|
2016
|
+
const categoryOrder = {
|
|
2017
|
+
git_policy: 1,
|
|
2018
|
+
security: 2,
|
|
2019
|
+
workflow: 3,
|
|
2020
|
+
tool_restriction: 4,
|
|
2021
|
+
custom: 5
|
|
2022
|
+
};
|
|
2023
|
+
const categoryLabels = {
|
|
2024
|
+
git_policy: "Git Policy",
|
|
2025
|
+
security: "Security",
|
|
2026
|
+
workflow: "Workflow",
|
|
2027
|
+
tool_restriction: "Tool Restrictions",
|
|
2028
|
+
custom: "Custom"
|
|
2029
|
+
};
|
|
2030
|
+
const byCategory = /* @__PURE__ */ new Map();
|
|
2031
|
+
for (const rule of result.rules) {
|
|
2032
|
+
const existing = byCategory.get(rule.category) ?? [];
|
|
2033
|
+
existing.push(rule);
|
|
2034
|
+
byCategory.set(rule.category, existing);
|
|
2035
|
+
}
|
|
2036
|
+
for (const rules of byCategory.values()) {
|
|
2037
|
+
rules.sort((a, b) => a.priority - b.priority);
|
|
2038
|
+
}
|
|
2039
|
+
let markdown = "# Agent Policy Rules\n\n";
|
|
2040
|
+
markdown += "> These rules are centrally managed by cmux and override repo-level rules.\n";
|
|
2041
|
+
markdown += `> Last refreshed: ${(/* @__PURE__ */ new Date()).toISOString()}
|
|
2042
|
+
|
|
2043
|
+
`;
|
|
2044
|
+
const sortedCategories = Array.from(byCategory.keys()).sort(
|
|
2045
|
+
(a, b) => (categoryOrder[a] ?? 99) - (categoryOrder[b] ?? 99)
|
|
2046
|
+
);
|
|
2047
|
+
for (const category of sortedCategories) {
|
|
2048
|
+
const rules = byCategory.get(category);
|
|
2049
|
+
if (!rules || rules.length === 0) continue;
|
|
2050
|
+
const label = categoryLabels[category] ?? category;
|
|
2051
|
+
markdown += `## ${label}
|
|
2052
|
+
|
|
2053
|
+
`;
|
|
2054
|
+
for (const rule of rules) {
|
|
2055
|
+
markdown += `${rule.ruleText}
|
|
2056
|
+
|
|
2057
|
+
`;
|
|
2058
|
+
}
|
|
2059
|
+
}
|
|
2060
|
+
const updates = [];
|
|
2061
|
+
const claudeMdPath = path.join(process.env.HOME ?? "/root", ".claude", "CLAUDE.md");
|
|
2062
|
+
const codexMdPath = path.join(process.env.HOME ?? "/root", ".codex", "instructions.md");
|
|
2063
|
+
const updateInstructionFile = (filePath) => {
|
|
2064
|
+
try {
|
|
2065
|
+
let content;
|
|
2066
|
+
try {
|
|
2067
|
+
content = fs.readFileSync(filePath, "utf-8");
|
|
2068
|
+
} catch {
|
|
2069
|
+
return false;
|
|
2070
|
+
}
|
|
2071
|
+
const policyMarkerStart = "# Agent Policy Rules";
|
|
2072
|
+
const startIdx = content.indexOf(policyMarkerStart);
|
|
2073
|
+
if (startIdx === -1) {
|
|
2074
|
+
const firstHeadingEnd = content.indexOf("\n\n");
|
|
2075
|
+
if (firstHeadingEnd > 0) {
|
|
2076
|
+
content = content.slice(0, firstHeadingEnd + 2) + markdown + "\n" + content.slice(firstHeadingEnd + 2);
|
|
2077
|
+
} else {
|
|
2078
|
+
content = markdown + "\n" + content;
|
|
2079
|
+
}
|
|
2080
|
+
} else {
|
|
2081
|
+
const nextHeadingMatch = content.slice(startIdx + policyMarkerStart.length).match(/\n# [A-Z]/);
|
|
2082
|
+
const memoryProtocolMatch = content.slice(startIdx + policyMarkerStart.length).match(/\n## cmux Agent Memory Protocol/);
|
|
2083
|
+
let endIdx;
|
|
2084
|
+
if (memoryProtocolMatch && memoryProtocolMatch.index !== void 0) {
|
|
2085
|
+
endIdx = startIdx + policyMarkerStart.length + memoryProtocolMatch.index;
|
|
2086
|
+
} else if (nextHeadingMatch && nextHeadingMatch.index !== void 0) {
|
|
2087
|
+
endIdx = startIdx + policyMarkerStart.length + nextHeadingMatch.index;
|
|
2088
|
+
} else {
|
|
2089
|
+
endIdx = content.length;
|
|
2090
|
+
}
|
|
2091
|
+
content = content.slice(0, startIdx) + markdown + content.slice(endIdx);
|
|
2092
|
+
}
|
|
2093
|
+
fs.writeFileSync(filePath, content, "utf-8");
|
|
2094
|
+
return true;
|
|
2095
|
+
} catch {
|
|
2096
|
+
return false;
|
|
2097
|
+
}
|
|
2098
|
+
};
|
|
2099
|
+
if (updateInstructionFile(claudeMdPath)) {
|
|
2100
|
+
updates.push("~/.claude/CLAUDE.md");
|
|
2101
|
+
}
|
|
2102
|
+
if (updateInstructionFile(codexMdPath)) {
|
|
2103
|
+
updates.push("~/.codex/instructions.md");
|
|
2104
|
+
}
|
|
2105
|
+
return {
|
|
2106
|
+
content: [{
|
|
2107
|
+
type: "text",
|
|
2108
|
+
text: JSON.stringify({
|
|
2109
|
+
refreshed: true,
|
|
2110
|
+
rulesCount: result.rules.length,
|
|
2111
|
+
categories: sortedCategories,
|
|
2112
|
+
updatedFiles: updates,
|
|
2113
|
+
message: updates.length > 0 ? `Successfully refreshed ${result.rules.length} policy rules. Updated: ${updates.join(", ")}` : `Fetched ${result.rules.length} policy rules but could not update local files.`
|
|
2114
|
+
}, null, 2)
|
|
2115
|
+
}]
|
|
2116
|
+
};
|
|
2117
|
+
} catch (error) {
|
|
2118
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
2119
|
+
return {
|
|
2120
|
+
content: [{
|
|
2121
|
+
type: "text",
|
|
2122
|
+
text: `Error refreshing policy rules: ${errorMsg}`
|
|
2123
|
+
}]
|
|
2124
|
+
};
|
|
2125
|
+
}
|
|
2126
|
+
}
|
|
2127
|
+
case "log_learning": {
|
|
2128
|
+
const jwt = process.env.CMUX_TASK_RUN_JWT;
|
|
2129
|
+
const apiBase = process.env.CMUX_API_BASE_URL ?? "https://cmux.sh";
|
|
2130
|
+
if (!jwt) {
|
|
2131
|
+
return {
|
|
2132
|
+
content: [{
|
|
2133
|
+
type: "text",
|
|
2134
|
+
text: "Error: CMUX_TASK_RUN_JWT not set. This tool requires authentication."
|
|
2135
|
+
}]
|
|
2136
|
+
};
|
|
2137
|
+
}
|
|
2138
|
+
const { type, text, lane, confidence, metadata } = args;
|
|
2139
|
+
try {
|
|
2140
|
+
const eventType = type === "learning" ? "learning_logged" : type === "error" ? "error_logged" : "feature_request_logged";
|
|
2141
|
+
const response = await fetch(`${apiBase}/api/v1/cmux/orchestration/learning/log`, {
|
|
2142
|
+
method: "POST",
|
|
2143
|
+
headers: {
|
|
2144
|
+
"Content-Type": "application/json",
|
|
2145
|
+
"Authorization": `Bearer ${jwt}`
|
|
2146
|
+
},
|
|
2147
|
+
body: JSON.stringify({
|
|
2148
|
+
eventType,
|
|
2149
|
+
text,
|
|
2150
|
+
lane: lane ?? "orchestration",
|
|
2151
|
+
confidence: confidence ?? (type === "error" ? 0.8 : 0.5),
|
|
2152
|
+
metadata
|
|
2153
|
+
})
|
|
2154
|
+
});
|
|
2155
|
+
if (!response.ok) {
|
|
2156
|
+
const errText = await response.text();
|
|
2157
|
+
return {
|
|
2158
|
+
content: [{
|
|
2159
|
+
type: "text",
|
|
2160
|
+
text: `Error logging ${type}: ${response.status} ${errText}`
|
|
2161
|
+
}]
|
|
2162
|
+
};
|
|
2163
|
+
}
|
|
2164
|
+
const result = await response.json();
|
|
2165
|
+
return {
|
|
2166
|
+
content: [{
|
|
2167
|
+
type: "text",
|
|
2168
|
+
text: JSON.stringify({
|
|
2169
|
+
logged: true,
|
|
2170
|
+
eventType,
|
|
2171
|
+
eventId: result.eventId,
|
|
2172
|
+
ruleId: result.ruleId,
|
|
2173
|
+
message: `Successfully logged ${type}. It will be reviewed for promotion to active rules.`
|
|
2174
|
+
}, null, 2)
|
|
2175
|
+
}]
|
|
2176
|
+
};
|
|
2177
|
+
} catch (error) {
|
|
2178
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
2179
|
+
return {
|
|
2180
|
+
content: [{
|
|
2181
|
+
type: "text",
|
|
2182
|
+
text: `Error logging ${type}: ${errorMsg}`
|
|
2183
|
+
}]
|
|
2184
|
+
};
|
|
2185
|
+
}
|
|
2186
|
+
}
|
|
2187
|
+
case "get_active_orchestration_rules": {
|
|
2188
|
+
const jwt = process.env.CMUX_TASK_RUN_JWT;
|
|
2189
|
+
const apiBase = process.env.CMUX_API_BASE_URL ?? "https://cmux.sh";
|
|
2190
|
+
if (!jwt) {
|
|
2191
|
+
return {
|
|
2192
|
+
content: [{
|
|
2193
|
+
type: "text",
|
|
2194
|
+
text: "Error: CMUX_TASK_RUN_JWT not set. This tool requires authentication."
|
|
2195
|
+
}]
|
|
2196
|
+
};
|
|
2197
|
+
}
|
|
2198
|
+
const { lane } = args;
|
|
2199
|
+
try {
|
|
2200
|
+
const url = new URL(`${apiBase}/api/v1/cmux/orchestration/rules`);
|
|
2201
|
+
if (lane) {
|
|
2202
|
+
url.searchParams.set("lane", lane);
|
|
2203
|
+
}
|
|
2204
|
+
const response = await fetch(url.toString(), {
|
|
2205
|
+
method: "GET",
|
|
2206
|
+
headers: {
|
|
2207
|
+
"Authorization": `Bearer ${jwt}`
|
|
2208
|
+
}
|
|
2209
|
+
});
|
|
2210
|
+
if (!response.ok) {
|
|
2211
|
+
const errText = await response.text();
|
|
2212
|
+
return {
|
|
2213
|
+
content: [{
|
|
2214
|
+
type: "text",
|
|
2215
|
+
text: `Error fetching orchestration rules: ${response.status} ${errText}`
|
|
2216
|
+
}]
|
|
2217
|
+
};
|
|
2218
|
+
}
|
|
2219
|
+
const result = await response.json();
|
|
2220
|
+
const rules = result.rules ?? [];
|
|
2221
|
+
const byLane = /* @__PURE__ */ new Map();
|
|
2222
|
+
for (const rule of rules) {
|
|
2223
|
+
const laneRules = byLane.get(rule.lane) ?? [];
|
|
2224
|
+
laneRules.push({ text: rule.text, confidence: rule.confidence });
|
|
2225
|
+
byLane.set(rule.lane, laneRules);
|
|
2226
|
+
}
|
|
2227
|
+
let output = `# Active Orchestration Rules (${rules.length} total)
|
|
2228
|
+
|
|
2229
|
+
`;
|
|
2230
|
+
for (const [ruleLane, laneRules] of byLane.entries()) {
|
|
2231
|
+
const laneLabel = ruleLane === "hot" ? "Hot (High Priority)" : ruleLane === "orchestration" ? "Orchestration" : "Project-Specific";
|
|
2232
|
+
output += `## ${laneLabel}
|
|
2233
|
+
|
|
2234
|
+
`;
|
|
2235
|
+
for (const rule of laneRules.sort((a, b) => b.confidence - a.confidence)) {
|
|
2236
|
+
output += `- ${rule.text}
|
|
2237
|
+
`;
|
|
2238
|
+
}
|
|
2239
|
+
output += "\n";
|
|
2240
|
+
}
|
|
2241
|
+
return {
|
|
2242
|
+
content: [{
|
|
2243
|
+
type: "text",
|
|
2244
|
+
text: output
|
|
2245
|
+
}]
|
|
2246
|
+
};
|
|
2247
|
+
} catch (error) {
|
|
2248
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
2249
|
+
return {
|
|
2250
|
+
content: [{
|
|
2251
|
+
type: "text",
|
|
2252
|
+
text: `Error fetching orchestration rules: ${errorMsg}`
|
|
2253
|
+
}]
|
|
2254
|
+
};
|
|
2255
|
+
}
|
|
2256
|
+
}
|
|
1292
2257
|
default:
|
|
1293
2258
|
return { content: [{ type: "text", text: `Unknown tool: ${name}` }] };
|
|
1294
2259
|
}
|
package/dist/cli.js
CHANGED
package/dist/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "devsh-memory-mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "MCP server for devsh/cmux agent memory - enables Claude Desktop and external clients to access sandbox memory and orchestrate multi-agent workflows",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|