devsh-memory-mcp 0.2.1 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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 (blocking) |
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 # Long-term insights (P0/P1/P2 sections)
135
+ │ └── MEMORY.md # Long-term insights (P0/P1/P2 sections)
113
136
  ├── daily/
114
- │ └── {date}.md # Daily session logs
137
+ │ └── {date}.md # Daily session logs
115
138
  ├── orchestration/
116
- │ ├── PLAN.json # Orchestration task plan
117
- │ ├── AGENTS.json # Agent registry
118
- │ └── EVENTS.jsonl # Orchestration event log
119
- ├── TASKS.json # Task registry
120
- └── MAILBOX.json # Inter-agent messages
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,685 @@ 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
+ try {
1586
+ const url = `${apiBaseUrl}/api/v1/cmux/orchestration/sessions/bind`;
1587
+ const response = await fetch(url, {
1588
+ method: "POST",
1589
+ headers: {
1590
+ "Authorization": `Bearer ${jwt}`,
1591
+ "Content-Type": "application/json"
1592
+ },
1593
+ body: JSON.stringify({
1594
+ // Only include orchestrationId if set, otherwise API uses taskRunId from JWT
1595
+ ...orchestrationId && { orchestrationId },
1596
+ taskRunId,
1597
+ providerSessionId,
1598
+ providerThreadId,
1599
+ replyChannel,
1600
+ agentName
1601
+ })
1602
+ });
1603
+ if (!response.ok) {
1604
+ const errorText = await response.text();
1605
+ return {
1606
+ content: [{
1607
+ type: "text",
1608
+ text: `Failed to bind session: ${response.status} ${errorText}`
1609
+ }]
1610
+ };
1611
+ }
1612
+ const result = await response.json();
1613
+ return {
1614
+ content: [{
1615
+ type: "text",
1616
+ text: JSON.stringify({
1617
+ success: true,
1618
+ message: "Provider session bound successfully",
1619
+ bindingId: result.bindingId,
1620
+ updated: result.updated
1621
+ }, null, 2)
1622
+ }]
1623
+ };
1624
+ } catch (error) {
1625
+ const errorMsg = error instanceof Error ? error.message : String(error);
1626
+ return {
1627
+ content: [{
1628
+ type: "text",
1629
+ text: `Error binding provider session: ${errorMsg}`
1630
+ }]
1631
+ };
1632
+ }
1633
+ }
1634
+ case "get_provider_session": {
1635
+ const { taskId: providedTaskId } = args;
1636
+ const jwt = process.env.CMUX_TASK_RUN_JWT;
1637
+ const apiBaseUrl = process.env.CMUX_API_BASE_URL ?? "https://cmux.sh";
1638
+ const taskId = providedTaskId ?? process.env.CMUX_TASK_RUN_ID;
1639
+ if (!jwt) {
1640
+ return {
1641
+ content: [{
1642
+ type: "text",
1643
+ text: "CMUX_TASK_RUN_JWT environment variable not set."
1644
+ }]
1645
+ };
1646
+ }
1647
+ if (!taskId) {
1648
+ return {
1649
+ content: [{
1650
+ type: "text",
1651
+ text: "No task ID provided and CMUX_TASK_RUN_ID not set."
1652
+ }]
1653
+ };
1654
+ }
1655
+ try {
1656
+ const url = `${apiBaseUrl}/api/v1/cmux/orchestration/sessions/${taskId}`;
1657
+ const response = await fetch(url, {
1658
+ method: "GET",
1659
+ headers: {
1660
+ "Authorization": `Bearer ${jwt}`
1661
+ }
1662
+ });
1663
+ if (!response.ok) {
1664
+ if (response.status === 404) {
1665
+ return {
1666
+ content: [{
1667
+ type: "text",
1668
+ text: JSON.stringify({
1669
+ found: false,
1670
+ message: "No provider session binding found for this task"
1671
+ }, null, 2)
1672
+ }]
1673
+ };
1674
+ }
1675
+ const errorText = await response.text();
1676
+ return {
1677
+ content: [{
1678
+ type: "text",
1679
+ text: `Failed to get session: ${response.status} ${errorText}`
1680
+ }]
1681
+ };
1682
+ }
1683
+ const session = await response.json();
1684
+ return {
1685
+ content: [{
1686
+ type: "text",
1687
+ text: JSON.stringify({
1688
+ found: true,
1689
+ taskId: session.taskId,
1690
+ orchestrationId: session.orchestrationId,
1691
+ provider: session.provider,
1692
+ agentName: session.agentName,
1693
+ mode: session.mode,
1694
+ providerSessionId: session.providerSessionId,
1695
+ providerThreadId: session.providerThreadId,
1696
+ replyChannel: session.replyChannel,
1697
+ status: session.status,
1698
+ lastActiveAt: session.lastActiveAt
1699
+ }, null, 2)
1700
+ }]
1701
+ };
1702
+ } catch (error) {
1703
+ const errorMsg = error instanceof Error ? error.message : String(error);
1704
+ return {
1705
+ content: [{
1706
+ type: "text",
1707
+ text: `Error getting provider session: ${errorMsg}`
1708
+ }]
1709
+ };
1710
+ }
1711
+ }
1712
+ case "wait_for_events": {
1713
+ const { orchestrationId, eventTypes = [], timeout = 3e4 } = args;
1714
+ const jwt = process.env.CMUX_TASK_RUN_JWT;
1715
+ const apiBaseUrl = process.env.CMUX_API_BASE_URL ?? "https://cmux.sh";
1716
+ if (!jwt) {
1717
+ return {
1718
+ content: [{
1719
+ type: "text",
1720
+ text: "CMUX_TASK_RUN_JWT environment variable not set. This tool requires JWT authentication."
1721
+ }]
1722
+ };
1723
+ }
1724
+ const startTime = Date.now();
1725
+ let reader;
1726
+ try {
1727
+ const url = `${apiBaseUrl}/api/orchestrate/v2/events/${orchestrationId}`;
1728
+ const controller = new AbortController();
1729
+ const timeoutId = setTimeout(() => controller.abort(), timeout);
1730
+ const response = await fetch(url, {
1731
+ method: "GET",
1732
+ headers: {
1733
+ "Authorization": `Bearer ${jwt}`,
1734
+ "Accept": "text/event-stream"
1735
+ },
1736
+ signal: controller.signal
1737
+ });
1738
+ if (!response.ok) {
1739
+ clearTimeout(timeoutId);
1740
+ const errorText = await response.text();
1741
+ return {
1742
+ content: [{
1743
+ type: "text",
1744
+ text: `Failed to connect to event stream: ${response.status} ${errorText}`
1745
+ }]
1746
+ };
1747
+ }
1748
+ reader = response.body?.getReader();
1749
+ if (!reader) {
1750
+ clearTimeout(timeoutId);
1751
+ return {
1752
+ content: [{
1753
+ type: "text",
1754
+ text: "No response body from event stream"
1755
+ }]
1756
+ };
1757
+ }
1758
+ const decoder = new TextDecoder();
1759
+ let buffer = "";
1760
+ while (Date.now() - startTime < timeout) {
1761
+ const { value, done } = await reader.read();
1762
+ if (done) break;
1763
+ buffer += decoder.decode(value, { stream: true });
1764
+ const lines = buffer.split("\n");
1765
+ buffer = lines.pop() ?? "";
1766
+ for (const line of lines) {
1767
+ if (line.startsWith("data: ")) {
1768
+ try {
1769
+ const event = JSON.parse(line.slice(6));
1770
+ if (eventTypes.length === 0 || eventTypes.includes(event.type)) {
1771
+ clearTimeout(timeoutId);
1772
+ return {
1773
+ content: [{
1774
+ type: "text",
1775
+ text: JSON.stringify({
1776
+ event,
1777
+ waitDuration: Date.now() - startTime
1778
+ }, null, 2)
1779
+ }]
1780
+ };
1781
+ }
1782
+ } catch {
1783
+ }
1784
+ }
1785
+ }
1786
+ }
1787
+ clearTimeout(timeoutId);
1788
+ return {
1789
+ content: [{
1790
+ type: "text",
1791
+ text: JSON.stringify({
1792
+ status: "timeout",
1793
+ message: `No matching events received within ${timeout}ms`,
1794
+ eventTypesFilter: eventTypes,
1795
+ waitDuration: Date.now() - startTime
1796
+ }, null, 2)
1797
+ }]
1798
+ };
1799
+ } catch (error) {
1800
+ if (error instanceof Error && error.name === "AbortError") {
1801
+ return {
1802
+ content: [{
1803
+ type: "text",
1804
+ text: JSON.stringify({
1805
+ status: "timeout",
1806
+ message: `Connection timed out after ${timeout}ms`
1807
+ }, null, 2)
1808
+ }]
1809
+ };
1810
+ }
1811
+ const errorMsg = error instanceof Error ? error.message : String(error);
1812
+ return {
1813
+ content: [{
1814
+ type: "text",
1815
+ text: `Error waiting for events: ${errorMsg}`
1816
+ }]
1817
+ };
1818
+ } finally {
1819
+ if (reader) {
1820
+ try {
1821
+ reader.cancel();
1822
+ } catch {
1823
+ }
1824
+ }
1825
+ }
1826
+ }
1827
+ case "get_pending_approvals": {
1828
+ const { orchestrationId: providedOrchId } = args;
1829
+ const jwt = process.env.CMUX_TASK_RUN_JWT;
1830
+ const apiBaseUrl = process.env.CMUX_API_BASE_URL ?? "https://cmux.sh";
1831
+ const orchestrationId = providedOrchId ?? process.env.CMUX_ORCHESTRATION_ID;
1832
+ if (!jwt) {
1833
+ return {
1834
+ content: [{
1835
+ type: "text",
1836
+ text: "CMUX_TASK_RUN_JWT environment variable not set. This tool requires JWT authentication."
1837
+ }]
1838
+ };
1839
+ }
1840
+ if (!orchestrationId) {
1841
+ return {
1842
+ content: [{
1843
+ type: "text",
1844
+ text: "No orchestration ID provided and CMUX_ORCHESTRATION_ID not set."
1845
+ }]
1846
+ };
1847
+ }
1848
+ try {
1849
+ const url = `${apiBaseUrl}/api/orchestrate/approvals/${orchestrationId}/pending`;
1850
+ const response = await fetch(url, {
1851
+ method: "GET",
1852
+ headers: {
1853
+ "Authorization": `Bearer ${jwt}`,
1854
+ "Content-Type": "application/json"
1855
+ }
1856
+ });
1857
+ if (!response.ok) {
1858
+ const errorText = await response.text();
1859
+ return {
1860
+ content: [{
1861
+ type: "text",
1862
+ text: `Failed to get pending approvals: ${response.status} ${errorText}`
1863
+ }]
1864
+ };
1865
+ }
1866
+ const approvals = await response.json();
1867
+ return {
1868
+ content: [{
1869
+ type: "text",
1870
+ text: JSON.stringify({
1871
+ orchestrationId,
1872
+ pendingCount: Array.isArray(approvals) ? approvals.length : 0,
1873
+ approvals
1874
+ }, null, 2)
1875
+ }]
1876
+ };
1877
+ } catch (error) {
1878
+ const errorMsg = error instanceof Error ? error.message : String(error);
1879
+ return {
1880
+ content: [{
1881
+ type: "text",
1882
+ text: `Error getting pending approvals: ${errorMsg}`
1883
+ }]
1884
+ };
1885
+ }
1886
+ }
1887
+ case "resolve_approval": {
1888
+ const { requestId, resolution, note } = args;
1889
+ const jwt = process.env.CMUX_TASK_RUN_JWT;
1890
+ const apiBaseUrl = process.env.CMUX_API_BASE_URL ?? "https://cmux.sh";
1891
+ if (!jwt) {
1892
+ return {
1893
+ content: [{
1894
+ type: "text",
1895
+ text: "CMUX_TASK_RUN_JWT environment variable not set. This tool requires JWT authentication."
1896
+ }]
1897
+ };
1898
+ }
1899
+ try {
1900
+ const url = `${apiBaseUrl}/api/orchestrate/approvals/${requestId}/resolve`;
1901
+ const response = await fetch(url, {
1902
+ method: "POST",
1903
+ headers: {
1904
+ "Authorization": `Bearer ${jwt}`,
1905
+ "Content-Type": "application/json"
1906
+ },
1907
+ body: JSON.stringify({ resolution, note })
1908
+ });
1909
+ if (!response.ok) {
1910
+ const errorText = await response.text();
1911
+ return {
1912
+ content: [{
1913
+ type: "text",
1914
+ text: `Failed to resolve approval: ${response.status} ${errorText}`
1915
+ }]
1916
+ };
1917
+ }
1918
+ const result = await response.json();
1919
+ return {
1920
+ content: [{
1921
+ type: "text",
1922
+ text: JSON.stringify({
1923
+ requestId,
1924
+ resolution,
1925
+ status: "resolved",
1926
+ ...result
1927
+ }, null, 2)
1928
+ }]
1929
+ };
1930
+ } catch (error) {
1931
+ const errorMsg = error instanceof Error ? error.message : String(error);
1932
+ return {
1933
+ content: [{
1934
+ type: "text",
1935
+ text: `Error resolving approval: ${errorMsg}`
1936
+ }]
1937
+ };
1938
+ }
1939
+ }
1940
+ case "refresh_policy_rules": {
1941
+ const jwt = process.env.CMUX_TASK_RUN_JWT;
1942
+ const callbackUrl = process.env.CMUX_CALLBACK_URL;
1943
+ const agentNameFull = process.env.CMUX_AGENT_NAME;
1944
+ const isOrchestrationHead = process.env.CMUX_IS_ORCHESTRATION_HEAD === "1";
1945
+ if (!jwt) {
1946
+ return {
1947
+ content: [{
1948
+ type: "text",
1949
+ text: "CMUX_TASK_RUN_JWT environment variable not set. This tool requires JWT authentication."
1950
+ }]
1951
+ };
1952
+ }
1953
+ if (!callbackUrl) {
1954
+ return {
1955
+ content: [{
1956
+ type: "text",
1957
+ text: "CMUX_CALLBACK_URL environment variable not set. Cannot reach policy server."
1958
+ }]
1959
+ };
1960
+ }
1961
+ if (!agentNameFull) {
1962
+ return {
1963
+ content: [{
1964
+ type: "text",
1965
+ text: "CMUX_AGENT_NAME environment variable not set. Cannot determine agent type."
1966
+ }]
1967
+ };
1968
+ }
1969
+ const agentType = agentNameFull.split("/")[0];
1970
+ const validAgentTypes = ["claude", "codex", "gemini", "opencode"];
1971
+ if (!validAgentTypes.includes(agentType)) {
1972
+ return {
1973
+ content: [{
1974
+ type: "text",
1975
+ text: `Unknown agent type '${agentType}'. Expected one of: ${validAgentTypes.join(", ")}`
1976
+ }]
1977
+ };
1978
+ }
1979
+ const context = isOrchestrationHead ? "cloud_workspace" : "task_sandbox";
1980
+ try {
1981
+ const url = new URL(`${callbackUrl}/api/agent/policy-rules`);
1982
+ url.searchParams.set("agentType", agentType);
1983
+ url.searchParams.set("context", context);
1984
+ const response = await fetch(url.toString(), {
1985
+ method: "GET",
1986
+ headers: {
1987
+ "x-cmux-token": jwt,
1988
+ "Convex-Client": "node-1.0.0"
1989
+ }
1990
+ });
1991
+ if (!response.ok) {
1992
+ const errorText = await response.text();
1993
+ return {
1994
+ content: [{
1995
+ type: "text",
1996
+ text: `Failed to fetch policy rules: ${response.status} ${errorText}`
1997
+ }]
1998
+ };
1999
+ }
2000
+ const result = await response.json();
2001
+ if (!result.rules || result.rules.length === 0) {
2002
+ return {
2003
+ content: [{
2004
+ type: "text",
2005
+ text: "No policy rules found for this context."
2006
+ }]
2007
+ };
2008
+ }
2009
+ const categoryOrder = {
2010
+ git_policy: 1,
2011
+ security: 2,
2012
+ workflow: 3,
2013
+ tool_restriction: 4,
2014
+ custom: 5
2015
+ };
2016
+ const categoryLabels = {
2017
+ git_policy: "Git Policy",
2018
+ security: "Security",
2019
+ workflow: "Workflow",
2020
+ tool_restriction: "Tool Restrictions",
2021
+ custom: "Custom"
2022
+ };
2023
+ const byCategory = /* @__PURE__ */ new Map();
2024
+ for (const rule of result.rules) {
2025
+ const existing = byCategory.get(rule.category) ?? [];
2026
+ existing.push(rule);
2027
+ byCategory.set(rule.category, existing);
2028
+ }
2029
+ for (const rules of byCategory.values()) {
2030
+ rules.sort((a, b) => a.priority - b.priority);
2031
+ }
2032
+ let markdown = "# Agent Policy Rules\n\n";
2033
+ markdown += "> These rules are centrally managed by cmux and override repo-level rules.\n";
2034
+ markdown += `> Last refreshed: ${(/* @__PURE__ */ new Date()).toISOString()}
2035
+
2036
+ `;
2037
+ const sortedCategories = Array.from(byCategory.keys()).sort(
2038
+ (a, b) => (categoryOrder[a] ?? 99) - (categoryOrder[b] ?? 99)
2039
+ );
2040
+ for (const category of sortedCategories) {
2041
+ const rules = byCategory.get(category);
2042
+ if (!rules || rules.length === 0) continue;
2043
+ const label = categoryLabels[category] ?? category;
2044
+ markdown += `## ${label}
2045
+
2046
+ `;
2047
+ for (const rule of rules) {
2048
+ markdown += `${rule.ruleText}
2049
+
2050
+ `;
2051
+ }
2052
+ }
2053
+ const updates = [];
2054
+ const claudeMdPath = path.join(process.env.HOME ?? "/root", ".claude", "CLAUDE.md");
2055
+ const codexMdPath = path.join(process.env.HOME ?? "/root", ".codex", "instructions.md");
2056
+ const updateInstructionFile = (filePath) => {
2057
+ try {
2058
+ let content;
2059
+ try {
2060
+ content = fs.readFileSync(filePath, "utf-8");
2061
+ } catch {
2062
+ return false;
2063
+ }
2064
+ const policyMarkerStart = "# Agent Policy Rules";
2065
+ const startIdx = content.indexOf(policyMarkerStart);
2066
+ if (startIdx === -1) {
2067
+ const firstHeadingEnd = content.indexOf("\n\n");
2068
+ if (firstHeadingEnd > 0) {
2069
+ content = content.slice(0, firstHeadingEnd + 2) + markdown + "\n" + content.slice(firstHeadingEnd + 2);
2070
+ } else {
2071
+ content = markdown + "\n" + content;
2072
+ }
2073
+ } else {
2074
+ const nextHeadingMatch = content.slice(startIdx + policyMarkerStart.length).match(/\n# [A-Z]/);
2075
+ const memoryProtocolMatch = content.slice(startIdx + policyMarkerStart.length).match(/\n## cmux Agent Memory Protocol/);
2076
+ let endIdx;
2077
+ if (memoryProtocolMatch && memoryProtocolMatch.index !== void 0) {
2078
+ endIdx = startIdx + policyMarkerStart.length + memoryProtocolMatch.index;
2079
+ } else if (nextHeadingMatch && nextHeadingMatch.index !== void 0) {
2080
+ endIdx = startIdx + policyMarkerStart.length + nextHeadingMatch.index;
2081
+ } else {
2082
+ endIdx = content.length;
2083
+ }
2084
+ content = content.slice(0, startIdx) + markdown + content.slice(endIdx);
2085
+ }
2086
+ fs.writeFileSync(filePath, content, "utf-8");
2087
+ return true;
2088
+ } catch {
2089
+ return false;
2090
+ }
2091
+ };
2092
+ if (updateInstructionFile(claudeMdPath)) {
2093
+ updates.push("~/.claude/CLAUDE.md");
2094
+ }
2095
+ if (updateInstructionFile(codexMdPath)) {
2096
+ updates.push("~/.codex/instructions.md");
2097
+ }
2098
+ return {
2099
+ content: [{
2100
+ type: "text",
2101
+ text: JSON.stringify({
2102
+ refreshed: true,
2103
+ rulesCount: result.rules.length,
2104
+ categories: sortedCategories,
2105
+ updatedFiles: updates,
2106
+ 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.`
2107
+ }, null, 2)
2108
+ }]
2109
+ };
2110
+ } catch (error) {
2111
+ const errorMsg = error instanceof Error ? error.message : String(error);
2112
+ return {
2113
+ content: [{
2114
+ type: "text",
2115
+ text: `Error refreshing policy rules: ${errorMsg}`
2116
+ }]
2117
+ };
2118
+ }
2119
+ }
2120
+ case "log_learning": {
2121
+ const jwt = process.env.CMUX_TASK_RUN_JWT;
2122
+ const apiBase = process.env.CMUX_API_BASE_URL ?? "https://cmux.sh";
2123
+ if (!jwt) {
2124
+ return {
2125
+ content: [{
2126
+ type: "text",
2127
+ text: "Error: CMUX_TASK_RUN_JWT not set. This tool requires authentication."
2128
+ }]
2129
+ };
2130
+ }
2131
+ const { type, text, lane, confidence, metadata } = args;
2132
+ try {
2133
+ const eventType = type === "learning" ? "learning_logged" : type === "error" ? "error_logged" : "feature_request_logged";
2134
+ const response = await fetch(`${apiBase}/api/v1/cmux/orchestration/learning/log`, {
2135
+ method: "POST",
2136
+ headers: {
2137
+ "Content-Type": "application/json",
2138
+ "Authorization": `Bearer ${jwt}`
2139
+ },
2140
+ body: JSON.stringify({
2141
+ eventType,
2142
+ text,
2143
+ lane: lane ?? "orchestration",
2144
+ confidence: confidence ?? (type === "error" ? 0.8 : 0.5),
2145
+ metadata
2146
+ })
2147
+ });
2148
+ if (!response.ok) {
2149
+ const errText = await response.text();
2150
+ return {
2151
+ content: [{
2152
+ type: "text",
2153
+ text: `Error logging ${type}: ${response.status} ${errText}`
2154
+ }]
2155
+ };
2156
+ }
2157
+ const result = await response.json();
2158
+ return {
2159
+ content: [{
2160
+ type: "text",
2161
+ text: JSON.stringify({
2162
+ logged: true,
2163
+ eventType,
2164
+ eventId: result.eventId,
2165
+ ruleId: result.ruleId,
2166
+ message: `Successfully logged ${type}. It will be reviewed for promotion to active rules.`
2167
+ }, null, 2)
2168
+ }]
2169
+ };
2170
+ } catch (error) {
2171
+ const errorMsg = error instanceof Error ? error.message : String(error);
2172
+ return {
2173
+ content: [{
2174
+ type: "text",
2175
+ text: `Error logging ${type}: ${errorMsg}`
2176
+ }]
2177
+ };
2178
+ }
2179
+ }
2180
+ case "get_active_orchestration_rules": {
2181
+ const jwt = process.env.CMUX_TASK_RUN_JWT;
2182
+ const apiBase = process.env.CMUX_API_BASE_URL ?? "https://cmux.sh";
2183
+ if (!jwt) {
2184
+ return {
2185
+ content: [{
2186
+ type: "text",
2187
+ text: "Error: CMUX_TASK_RUN_JWT not set. This tool requires authentication."
2188
+ }]
2189
+ };
2190
+ }
2191
+ const { lane } = args;
2192
+ try {
2193
+ const url = new URL(`${apiBase}/api/v1/cmux/orchestration/rules`);
2194
+ if (lane) {
2195
+ url.searchParams.set("lane", lane);
2196
+ }
2197
+ const response = await fetch(url.toString(), {
2198
+ method: "GET",
2199
+ headers: {
2200
+ "Authorization": `Bearer ${jwt}`
2201
+ }
2202
+ });
2203
+ if (!response.ok) {
2204
+ const errText = await response.text();
2205
+ return {
2206
+ content: [{
2207
+ type: "text",
2208
+ text: `Error fetching orchestration rules: ${response.status} ${errText}`
2209
+ }]
2210
+ };
2211
+ }
2212
+ const result = await response.json();
2213
+ const rules = result.rules ?? [];
2214
+ const byLane = /* @__PURE__ */ new Map();
2215
+ for (const rule of rules) {
2216
+ const laneRules = byLane.get(rule.lane) ?? [];
2217
+ laneRules.push({ text: rule.text, confidence: rule.confidence });
2218
+ byLane.set(rule.lane, laneRules);
2219
+ }
2220
+ let output = `# Active Orchestration Rules (${rules.length} total)
2221
+
2222
+ `;
2223
+ for (const [ruleLane, laneRules] of byLane.entries()) {
2224
+ const laneLabel = ruleLane === "hot" ? "Hot (High Priority)" : ruleLane === "orchestration" ? "Orchestration" : "Project-Specific";
2225
+ output += `## ${laneLabel}
2226
+
2227
+ `;
2228
+ for (const rule of laneRules.sort((a, b) => b.confidence - a.confidence)) {
2229
+ output += `- ${rule.text}
2230
+ `;
2231
+ }
2232
+ output += "\n";
2233
+ }
2234
+ return {
2235
+ content: [{
2236
+ type: "text",
2237
+ text: output
2238
+ }]
2239
+ };
2240
+ } catch (error) {
2241
+ const errorMsg = error instanceof Error ? error.message : String(error);
2242
+ return {
2243
+ content: [{
2244
+ type: "text",
2245
+ text: `Error fetching orchestration rules: ${errorMsg}`
2246
+ }]
2247
+ };
2248
+ }
2249
+ }
1292
2250
  default:
1293
2251
  return { content: [{ type: "text", text: `Unknown tool: ${name}` }] };
1294
2252
  }
package/dist/cli.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  runServer
4
- } from "./chunk-YTXBZTQ2.js";
4
+ } from "./chunk-U2TWXSPB.js";
5
5
 
6
6
  // src/cli.ts
7
7
  function parseArgs() {
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  createMemoryMcpServer,
3
3
  runServer
4
- } from "./chunk-YTXBZTQ2.js";
4
+ } from "./chunk-U2TWXSPB.js";
5
5
  export {
6
6
  createMemoryMcpServer,
7
7
  runServer
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "devsh-memory-mcp",
3
- "version": "0.2.1",
3
+ "version": "0.3.1",
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",