@miriad-systems/nuum 0.1.10 → 0.1.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +208 -55
  2. package/dist/index.js +66 -325
  3. package/package.json +3 -3
package/README.md CHANGED
@@ -1,82 +1,234 @@
1
1
  # Nuum
2
2
 
3
- An AI coding agent with continuous memory — infinite context across sessions.
3
+ An AI coding agent with **"infinite memory"**continuous context across sessions.
4
4
 
5
5
  *Nuum* — from "continuum" — maintains persistent memory across conversations, learning your codebase, preferences, and decisions over time.
6
6
 
7
- ## Design Philosophy
7
+ ## Quick Start
8
8
 
9
- Nuum is **optimized for embedding**. While it can run standalone, it's designed to be integrated into host applications, IDEs, and orchestration platforms via a simple **NDJSON-over-stdio** protocol.
9
+ ```bash
10
+ export ANTHROPIC_API_KEY=your-key-here
10
11
 
11
- - **Stateless process, stateful memory** — The process can restart anytime; all state lives in SQLite
12
- - **Simple wire protocol** — JSON messages over stdin/stdout, easy to integrate from any language
13
- - **Mid-turn injection** — Send corrections while the agent is working; they're injected into the conversation
14
- - **Persistent identity** — One database = one agent with continuous memory forever
12
+ # Install and run interactively
13
+ bunx @miriad-systems/nuum --repl
15
14
 
16
- See [docs/protocol.md](docs/protocol.md) for the full wire protocol specification.
15
+ # Or with npx
16
+ npx @miriad-systems/nuum --repl
17
+ ```
17
18
 
18
- ## Installation
19
+ That's it. Start chatting. Your agent remembers everything.
19
20
 
20
- ```bash
21
- # Using bunx (recommended - runs in Bun, fast)
22
- bunx @miriad-systems/nuum
21
+ ### REPL Commands
23
22
 
24
- # Using npx (runs in Node.js)
25
- npx @miriad-systems/nuum
26
23
  ```
24
+ /help Show available commands
25
+ /inspect Show memory statistics
26
+ /dump Show full system prompt
27
+ /quit Exit
28
+ ```
29
+
30
+ ### Other Modes
31
+
32
+ ```bash
33
+ nuum -p "What files are in src/" # Single prompt
34
+ nuum --inspect # View memory stats
35
+ nuum --db ./project.db --repl # Custom database
36
+ ```
37
+
38
+ ---
39
+
40
+ ## ⚠️ Experimental Software
41
+
42
+ Nuum currently runs in **full autonomy mode** — no permission prompts, no confirmations. It was created for [Miriad](https://miriad.systems) as an embedded agent engine, typically running in containerized environments where the host platform manages security.
43
+
44
+ **Why we built this:** We were frustrated with how traditional coding agents seem to suffer some kind of contextual collapse after prolonged use — getting mixed up, repeating mistakes, losing track of decisions. Nuum explores how to keep agents effective indefinitely through recursive memory compression and persistent knowledge.
27
45
 
28
- ## Usage
46
+ ---
29
47
 
30
- ### Embedded Mode (for host applications)
48
+ ## MCP Servers
49
+
50
+ Nuum supports [Model Context Protocol](https://modelcontextprotocol.io/) servers for extended capabilities. Configure via environment variable:
31
51
 
32
52
  ```bash
33
- nuum --stdio # NDJSON protocol over stdin/stdout
34
- nuum --stdio --db ./my.db # With custom database
53
+ export NUUM_MCP_SERVERS='{
54
+ "filesystem": {
55
+ "command": "npx",
56
+ "args": ["-y", "@modelcontextprotocol/server-filesystem", "/path/to/allowed/dir"]
57
+ },
58
+ "github": {
59
+ "command": "npx",
60
+ "args": ["-y", "@modelcontextprotocol/server-github"],
61
+ "env": { "GITHUB_TOKEN": "your-token" }
62
+ }
63
+ }'
35
64
  ```
36
65
 
37
- Send JSON messages to stdin, receive responses on stdout:
66
+ Or pass via protocol when embedding:
38
67
 
39
68
  ```json
40
- {"type":"user","message":{"role":"user","content":"Hello"}}
41
- ← {"type":"assistant","message":{"role":"assistant","content":[{"type":"text","text":"Hello! How can I help?"}]},"session_id":"nuum_01JD..."}
42
- ← {"type":"result","subtype":"success","duration_ms":800,"session_id":"nuum_01JD..."}
69
+ {"type":"user","message":{...},"mcp_servers":{"name":{"command":"...","args":[...]}}}
43
70
  ```
44
71
 
45
- ### Interactive Mode
72
+ MCP tools appear alongside built-in tools. The agent discovers and uses them automatically.
73
+
74
+ ---
75
+
76
+ ## Embedding in Applications
77
+
78
+ Nuum is designed to be **embedded**. While it runs standalone, its primary use case is integration into host applications, IDEs, and orchestration platforms.
46
79
 
47
80
  ```bash
48
- nuum # Start interactive session
49
- nuum --db ./my-project.db # With specific database
81
+ nuum --stdio # NDJSON protocol over stdin/stdout
82
+ nuum --stdio --db ./my.db # With custom database
50
83
  ```
51
84
 
52
- ### Batch Mode
85
+ **Key properties:**
86
+ - **Stateless process, stateful memory** — Process can restart anytime; all state lives in SQLite
87
+ - **Simple wire protocol** — JSON messages over stdin/stdout, easy to integrate from any language
88
+ - **Mid-turn injection** — Send corrections while the agent is working
89
+ - **Persistent identity** — One database = one agent with continuous memory
90
+
91
+ See **[docs/protocol.md](docs/protocol.md)** for the full wire protocol specification.
53
92
 
54
- ```bash
55
- nuum -p "What is 2+2?"
56
- nuum -p "Read src/index.ts and explain what it does"
57
- nuum -p "Refactor the auth module" --verbose
93
+ ---
94
+
95
+ ## Memory Architecture
96
+
97
+ Nuum has a three-tier memory system that mirrors human cognition.
98
+
99
+ **Key insight:** Agents perform best when context is **30-50% full** — informed but not overwhelmed. Nuum's memory system maintains this sweet spot automatically.
100
+
101
+ ```
102
+ ┌─────────────────────────────────────────────────────────────────────────────┐
103
+ │ WORKING MEMORY │
104
+ │ (Temporal Message Store) │
105
+ │ │
106
+ │ Recent messages live here in full detail. As context grows, older │
107
+ │ content is recursively distilled — compressed while retaining what │
108
+ │ matters for effective action. │
109
+ │ │
110
+ │ ┌─────────────────────────────────────────────────────────────────────┐ │
111
+ │ │ msg msg msg msg msg msg msg msg msg msg msg msg msg msg msg msg ... │ │
112
+ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │
113
+ │ │ └───┴───┴───┴───┘ └───┴───┴───┘ └───┴───┴───┘ │ │ │ │ │
114
+ │ │ │ │ │ │ │ │ │ │
115
+ │ │ [distill-1] [distill-2] [distill-3] │ │ │ │ │
116
+ │ │ │ │ │ │ │ │ │ │
117
+ │ │ └───────────────────┴───────────────┘ │ │ │ │ │
118
+ │ │ │ │ │ │ │ │
119
+ │ │ [distill-4] │ │ │ │ │
120
+ │ │ │ │ │ │ │ │
121
+ │ │ └─────────────────────────┘ │ │ │ │
122
+ │ │ │ │ │ │ │
123
+ │ │ [distill-5] [recent msgs] │ │
124
+ │ │ │ │
125
+ │ │ Older ◄──────────────────────────────────────────────────► Newer │ │
126
+ │ └─────────────────────────────────────────────────────────────────────┘ │
127
+ │ │
128
+ │ The agent sees: [distill-5] + [recent messages] │
129
+ │ 55x compression ratio achieved (1.3M tokens → 25k effective) │
130
+ └─────────────────────────────────────────────────────────────────────────────┘
131
+
132
+ ┌─────────────────────────────────────────────────────────────────────────────┐
133
+ │ PRESENT STATE │
134
+ │ │
135
+ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────────────────┐ │
136
+ │ │ Mission │ │ Status │ │ Tasks │ │
137
+ │ │ │ │ │ │ ☑ Setup repository │ │
138
+ │ │ "Build │ │ "reviewing │ │ ☑ Implement auth │ │
139
+ │ │ auth │ │ PR #42" │ │ ☐ Write tests │ │
140
+ │ │ system" │ │ │ │ ☐ Deploy to staging │ │
141
+ │ └─────────────┘ └─────────────┘ └─────────────────────────────────┘ │
142
+ │ │
143
+ │ Agent-managed working state. Updated as work progresses. │
144
+ └─────────────────────────────────────────────────────────────────────────────┘
145
+
146
+ ┌─────────────────────────────────────────────────────────────────────────────┐
147
+ │ LONG-TERM MEMORY │
148
+ │ (Knowledge Base Tree) │
149
+ │ │
150
+ │ /identity ─────────────── "Who I am, my nature and relationships" │
151
+ │ /behavior ─────────────── "How I should operate, user preferences" │
152
+ │ /miriad-code │
153
+ │ ├── /cast-integration ─ "CAST/Miriad integration notes" │
154
+ │ ├── /memory │
155
+ │ │ └── /background-reports-system │
156
+ │ ├── /anthropic-prompt-caching │
157
+ │ └── /distillation-improvements-jan2026 │
158
+ │ /mcp │
159
+ │ ├── /mcp-implementation │
160
+ │ └── /mcp-config-resolution │
161
+ │ │
162
+ │ Hierarchical knowledge that persists forever. Background workers │
163
+ │ extract important information from conversations and organize it here. │
164
+ └─────────────────────────────────────────────────────────────────────────────┘
58
165
  ```
59
166
 
60
- ### CLI Reference
167
+ ### Recursive Distillation
168
+
169
+ **No pause for compaction.** Unlike most coding agents that stop mid-conversation to "compact memory," Nuum's distillation runs concurrently while you work. You never wait for memory management — it happens invisibly in the background.
170
+
171
+ The distillation system is **not summarization** — it's operational intelligence extraction:
172
+
173
+ **RETAIN** (actionable intelligence):
174
+ - File paths and what they contain
175
+ - Decisions made and WHY (rationale matters)
176
+ - User preferences and corrections
177
+ - Specific values: URLs, configs, commands
178
+ - Errors and how they were resolved
179
+
180
+ **EXCISE** (noise):
181
+ - Back-and-forth debugging that led nowhere
182
+ - Missteps and corrections (keep only final approach)
183
+ - Verbose tool outputs
184
+ - Narrative filler ("Let me check...")
185
+ - Casual chatter and acknowledgments
186
+
187
+ Distillations are recursive — older distillations get distilled again, creating a fractal compression where ancient history becomes highly compressed while recent work stays detailed.
188
+
189
+ ### Long-Term Memory Curation
190
+
191
+ A background worker (the **LTM Curator**) runs continuously in the background:
192
+
193
+ 1. **CAPTURES** important information into knowledge entries
194
+ 2. **STRENGTHENS** entries by researching and adding context
195
+ 3. **CURATES** the knowledge tree structure
196
+
197
+ The curator has access to web search, file reading, and the full knowledge base. It works autonomously — you never see it running, but the agent's knowledge grows over time. Reports are filed silently and surfaced to the main agent on the next interaction.
198
+
199
+ ### Reflection
200
+
201
+ When the agent needs to recall something specific — a file path, a decision, a value from weeks ago — it uses the **reflect** tool:
61
202
 
62
203
  ```
63
- nuum Start interactive session
64
- nuum --stdio Embedded mode (NDJSON protocol)
65
- nuum -p "prompt" Batch mode
66
- nuum --help Show help
67
-
68
- Options:
69
- -p, --prompt <text> Run with a prompt (batch mode)
70
- -v, --verbose Show memory state and debug output
71
- --stdio NDJSON protocol mode for embedding
72
- --db <path> SQLite database path (default: ./agent.db)
73
- --format <type> Output format: text or json (default: text)
74
- -h, --help Show help message
204
+ ┌─────────────────────────────────────────────────────────────────────────────┐
205
+ │ REFLECTION │
206
+ │ │
207
+ │ Main Agent Reflection Sub-Agent │
208
+ │ │ │ │
209
+ │ │ "What was the auth bug fix?" │ │
210
+ │ │ ────────────────────────────────────►│ │
211
+ │ │ │ │
212
+ │ │ ┌───────────┴───────────┐ │
213
+ │ │ Search FTS index │ │
214
+ │ │ │ Search LTM entries │ │
215
+ │ │ │ Read relevant docs │ │
216
+ │ │ │ Synthesize answer │ │
217
+ │ │ └───────────┬───────────┘ │
218
+ │ │ │ │
219
+ │ │ "The auth bug was in session.ts, │ │
220
+ │ │ line 42. Fixed by adding null │ │
221
+ │ │ check. Committed in abc123." │ │
222
+ │ │ ◄────────────────────────────────────│ │
223
+ │ │ │ │
224
+ └─────────────────────────────────────────────────────────────────────────────┘
75
225
  ```
76
226
 
77
- ## Configuration
227
+ Reflection searches the full conversation history (via FTS5 full-text search) and the knowledge base, then synthesizes an answer. It's like the agent asking its own memory system a question.
78
228
 
79
- Only `ANTHROPIC_API_KEY` is required:
229
+ ---
230
+
231
+ ## Configuration
80
232
 
81
233
  ```bash
82
234
  # Required
@@ -89,16 +241,7 @@ AGENT_MODEL_FAST=claude-haiku-4-5-20251001
89
241
  AGENT_DB=./agent.db
90
242
  ```
91
243
 
92
- ## How Memory Works
93
-
94
- ### Temporal Memory (Working Memory)
95
- Every message is logged chronologically. As the conversation grows, older content is **distilled** — compressed to retain actionable intelligence: file paths, decisions and rationale, user preferences, specific values.
96
-
97
- ### Present State
98
- Tracks the current mission, status, and task list. Updated by the agent as work progresses. Always visible in context.
99
-
100
- ### Long-Term Memory (LTM)
101
- A hierarchical knowledge base that persists across sessions. Contains identity, behavioral guidelines, and accumulated knowledge. Background workers consolidate important information from conversations into LTM.
244
+ ---
102
245
 
103
246
  ## Development
104
247
 
@@ -110,6 +253,8 @@ bun test # Run tests
110
253
  bun run build # Build for distribution
111
254
  ```
112
255
 
256
+ ---
257
+
113
258
  ## Acknowledgments
114
259
 
115
260
  ### Letta (formerly MemGPT)
@@ -126,6 +271,14 @@ Infrastructure adapted from [OpenCode](https://github.com/anthropics/opencode):
126
271
  - Permission system
127
272
  - Process management
128
273
 
274
+ ---
275
+
129
276
  ## License
130
277
 
131
278
  MIT
279
+
280
+ ---
281
+
282
+ <p align="center">
283
+ Nuum is part of <a href="https://miriad.systems">Miriad</a>, experimental software from <a href="https://sanity.io">Sanity.io</a>
284
+ </p>
package/dist/index.js CHANGED
@@ -16933,7 +16933,7 @@ var require_dist = __commonJS((exports, module) => {
16933
16933
  exports.default = formatsPlugin;
16934
16934
  });
16935
16935
 
16936
- // node_modules/cross-spawn/node_modules/which/node_modules/isexe/windows.js
16936
+ // node_modules/cross-spawn/node_modules/isexe/windows.js
16937
16937
  var require_windows = __commonJS((exports, module) => {
16938
16938
  module.exports = isexe;
16939
16939
  isexe.sync = sync;
@@ -16971,7 +16971,7 @@ var require_windows = __commonJS((exports, module) => {
16971
16971
  }
16972
16972
  });
16973
16973
 
16974
- // node_modules/cross-spawn/node_modules/which/node_modules/isexe/mode.js
16974
+ // node_modules/cross-spawn/node_modules/isexe/mode.js
16975
16975
  var require_mode = __commonJS((exports, module) => {
16976
16976
  module.exports = isexe;
16977
16977
  isexe.sync = sync;
@@ -17002,7 +17002,7 @@ var require_mode = __commonJS((exports, module) => {
17002
17002
  }
17003
17003
  });
17004
17004
 
17005
- // node_modules/cross-spawn/node_modules/which/node_modules/isexe/index.js
17005
+ // node_modules/cross-spawn/node_modules/isexe/index.js
17006
17006
  var require_isexe = __commonJS((exports, module) => {
17007
17007
  var fs7 = __require("fs");
17008
17008
  var core2;
@@ -32053,6 +32053,18 @@ function sleep(ms) {
32053
32053
  return new Promise((resolve2) => setTimeout(resolve2, ms));
32054
32054
  }
32055
32055
 
32056
+ // src/context/environment.ts
32057
+ var currentEnvironment = {};
32058
+ function setEnvironment(env) {
32059
+ currentEnvironment = env;
32060
+ }
32061
+ function getSpawnEnvironment() {
32062
+ return {
32063
+ ...process.env,
32064
+ ...currentEnvironment
32065
+ };
32066
+ }
32067
+
32056
32068
  // src/tool/bash.ts
32057
32069
  var MAX_OUTPUT_LENGTH = 50000;
32058
32070
  var DEFAULT_TIMEOUT_MS = 2 * 60 * 1000;
@@ -32094,7 +32106,7 @@ Use this tool for:
32094
32106
  const proc = spawn2(params.command, {
32095
32107
  shell,
32096
32108
  cwd,
32097
- env: { ...process.env },
32109
+ env: getSpawnEnvironment(),
32098
32110
  stdio: ["ignore", "pipe", "pipe"],
32099
32111
  detached: process.platform !== "win32"
32100
32112
  });
@@ -33791,7 +33803,8 @@ var Ripgrep;
33791
33803
  }
33792
33804
  const proc = spawn3(rgPath, args, {
33793
33805
  cwd: input.cwd,
33794
- stdio: ["ignore", "pipe", "ignore"]
33806
+ stdio: ["ignore", "pipe", "ignore"],
33807
+ env: getSpawnEnvironment()
33795
33808
  });
33796
33809
  let buffer = "";
33797
33810
  for await (const chunk of proc.stdout) {
@@ -33941,7 +33954,8 @@ Respects .gitignore patterns.`,
33941
33954
  }
33942
33955
  args.push(searchPath);
33943
33956
  const proc = spawn4(rgPath, args, {
33944
- stdio: ["ignore", "pipe", "pipe"]
33957
+ stdio: ["ignore", "pipe", "pipe"],
33958
+ env: getSpawnEnvironment()
33945
33959
  });
33946
33960
  let output = "";
33947
33961
  let errorOutput = "";
@@ -34455,6 +34469,10 @@ Use reflect when:
34455
34469
  - You need a specific value or path from earlier work
34456
34470
  - The user asks "remember when..." or "what did we decide about..."
34457
34471
  - You want to verify your memory before acting
34472
+
34473
+ ## Message Prefixes
34474
+
34475
+ Messages in your history have automatic prefixes like \`[2026-01-26 15:30 id:msg_xxx]\` showing timestamp and ID. These are added by the system for internal tracking - you don't need to reference or echo them. Just read the message content normally.
34458
34476
  `;
34459
34477
  const systemPromptOverlay = await storage.session.getSystemPromptOverlay();
34460
34478
  if (systemPromptOverlay) {
@@ -34545,14 +34563,29 @@ Retained facts:
34545
34563
  }
34546
34564
  return turns;
34547
34565
  }
34548
- function formatWithId(id, content) {
34549
- return `[id:${id}] ${content}`;
34566
+ function formatTimestamp(isoTimestamp) {
34567
+ const date2 = new Date(isoTimestamp);
34568
+ const year = date2.getFullYear();
34569
+ const month = String(date2.getMonth() + 1).padStart(2, "0");
34570
+ const day = String(date2.getDate()).padStart(2, "0");
34571
+ const hours = String(date2.getHours()).padStart(2, "0");
34572
+ const minutes = String(date2.getMinutes()).padStart(2, "0");
34573
+ return `${year}-${month}-${day} ${hours}:${minutes}`;
34574
+ }
34575
+ function formatWithId(id, timestamp, content) {
34576
+ return `[${formatTimestamp(timestamp)} id:${id}] ${content}`;
34577
+ }
34578
+ function formatIdRange(firstId, lastId, timestamp) {
34579
+ if (firstId === lastId) {
34580
+ return `[${formatTimestamp(timestamp)} id:${firstId}]`;
34581
+ }
34582
+ return `[${formatTimestamp(timestamp)} id:${firstId}...${lastId}]`;
34550
34583
  }
34551
34584
  function processMessageForTurn(message, allMessages, currentIdx) {
34552
34585
  const turns = [];
34553
34586
  switch (message.type) {
34554
34587
  case "user":
34555
- turns.push({ role: "user", content: formatWithId(message.id, message.content) });
34588
+ turns.push({ role: "user", content: formatWithId(message.id, message.createdAt, message.content) });
34556
34589
  return { turns, nextIdx: currentIdx + 1 };
34557
34590
  case "assistant": {
34558
34591
  const toolCalls = [];
@@ -34594,7 +34627,7 @@ function processMessageForTurn(message, allMessages, currentIdx) {
34594
34627
  }
34595
34628
  if (toolCalls.length > 0) {
34596
34629
  const assistantContent = [];
34597
- const idPrefix = message.id === lastMessageId ? `[id:${message.id}]` : `[id:${message.id}...${lastMessageId}]`;
34630
+ const idPrefix = formatIdRange(message.id, lastMessageId, message.createdAt);
34598
34631
  if (message.content) {
34599
34632
  assistantContent.push({ type: "text", text: `${idPrefix} ${message.content}` });
34600
34633
  } else {
@@ -34614,7 +34647,7 @@ function processMessageForTurn(message, allMessages, currentIdx) {
34614
34647
  turns.push(toolMsg);
34615
34648
  }
34616
34649
  } else {
34617
- turns.push({ role: "assistant", content: formatWithId(message.id, message.content) });
34650
+ turns.push({ role: "assistant", content: formatWithId(message.id, message.createdAt, message.content) });
34618
34651
  }
34619
34652
  return { turns, nextIdx };
34620
34653
  }
@@ -34658,7 +34691,7 @@ function processMessageForTurn(message, allMessages, currentIdx) {
34658
34691
  nextIdx++;
34659
34692
  }
34660
34693
  if (toolCalls.length > 0) {
34661
- const idPrefix = firstMessageId === lastMessageId ? `[id:${firstMessageId}]` : `[id:${firstMessageId}...${lastMessageId}]`;
34694
+ const idPrefix = formatIdRange(firstMessageId, lastMessageId, message.createdAt);
34662
34695
  const assistantContent = [
34663
34696
  { type: "text", text: idPrefix },
34664
34697
  ...toolCalls
@@ -34686,7 +34719,7 @@ function processMessageForTurn(message, allMessages, currentIdx) {
34686
34719
  case "system":
34687
34720
  turns.push({
34688
34721
  role: "assistant",
34689
- content: `[system ${formatWithId(message.id, message.content)}]`
34722
+ content: `[system ${formatWithId(message.id, message.createdAt, message.content)}]`
34690
34723
  });
34691
34724
  return { turns, nextIdx: currentIdx + 1 };
34692
34725
  default:
@@ -35039,11 +35072,13 @@ Your working memory (conversation history) needs to be optimized for effective a
35039
35072
  ${underTarget ? "**Status:** Already at/under target. You may clean up noise if you see any, or call finish_distillation." : `**To distill:** ~${(currentTokens - targetTokens).toLocaleString()} tokens`}
35040
35073
  **Recency buffer:** ${recencyBuffer} most recent messages are protected
35041
35074
 
35042
- The conversation above contains IDs you can reference:
35043
- - Messages have \`[id:xxx]\` prefixes
35075
+ The conversation above contains timestamps and IDs you can reference:
35076
+ - Messages have \`[YYYY-MM-DD HH:MM id:xxx]\` prefixes (timestamp + ID)
35044
35077
  - Distillations show \`[distilled from:xxx to:yyy]\` ranges
35045
35078
  - The most recent ${recencyBuffer} messages cannot be distilled (preserve immediate context)
35046
35079
 
35080
+ Note: These prefixes are for YOUR reference when creating distillations. Do not echo them in responses.
35081
+
35047
35082
  ## Your Task: Distill, Don't Summarize
35048
35083
 
35049
35084
  The goal is NOT narrative summarization ("we discussed X and decided Y").
@@ -45552,7 +45587,7 @@ async function runAgent(prompt, options) {
45552
45587
 
45553
45588
  // src/cli/verbose.ts
45554
45589
  var SEPARATOR = "\u2500".repeat(70);
45555
- function formatTimestamp() {
45590
+ function formatTimestamp2() {
45556
45591
  return new Date().toISOString().slice(11, 23);
45557
45592
  }
45558
45593
 
@@ -45632,7 +45667,7 @@ LTM:`);
45632
45667
  this.events = [];
45633
45668
  }
45634
45669
  event(event) {
45635
- const ts = event.timestamp ?? formatTimestamp();
45670
+ const ts = event.timestamp ?? formatTimestamp2();
45636
45671
  const arrow = event.type === "user" || event.type === "tool_result" ? "\u2192" : "\u2190";
45637
45672
  const typeLabel = event.type.replace("_", " ");
45638
45673
  const content = event.content.length > 100 ? event.content.slice(0, 100) + "..." : event.content;
@@ -46155,7 +46190,8 @@ var UserMessageSchema = exports_external.object({
46155
46190
  }),
46156
46191
  session_id: exports_external.string().optional(),
46157
46192
  system_prompt: exports_external.string().optional(),
46158
- mcp_servers: exports_external.record(exports_external.unknown()).optional()
46193
+ mcp_servers: exports_external.record(exports_external.unknown()).optional(),
46194
+ environment: exports_external.record(exports_external.string()).optional()
46159
46195
  });
46160
46196
  var ControlRequestSchema = exports_external.object({
46161
46197
  type: exports_external.literal("control"),
@@ -46351,6 +46387,14 @@ class Server {
46351
46387
  if (userMessage.mcp_servers !== undefined) {
46352
46388
  await this.reinitializeMcpWithOverride(userMessage.mcp_servers);
46353
46389
  }
46390
+ if (userMessage.environment !== undefined) {
46391
+ setEnvironment(userMessage.environment);
46392
+ log11.info("applied environment from message", {
46393
+ count: Object.keys(userMessage.environment).length
46394
+ });
46395
+ } else {
46396
+ setEnvironment({});
46397
+ }
46354
46398
  this.currentTurn = {
46355
46399
  sessionId,
46356
46400
  abortController,
@@ -46582,12 +46626,6 @@ Goodbye!`);
46582
46626
  console.log("Goodbye!");
46583
46627
  process.exit(0);
46584
46628
  break;
46585
- case "clear":
46586
- this.storage = createStorage(this.options.dbPath);
46587
- await initializeDefaultEntries(this.storage);
46588
- console.log("Conversation cleared. Starting fresh session.");
46589
- this.rl?.prompt();
46590
- break;
46591
46629
  case "inspect":
46592
46630
  try {
46593
46631
  await runInspect(this.options.dbPath);
@@ -46621,7 +46659,6 @@ Goodbye!`);
46621
46659
  console.log("Commands:");
46622
46660
  console.log(" /help, /h, /? Show this help");
46623
46661
  console.log(" /quit, /exit, /q Exit the REPL");
46624
- console.log(" /clear Clear conversation history (fresh session)");
46625
46662
  console.log(" /inspect Show memory statistics");
46626
46663
  console.log(" /dump Show full system prompt");
46627
46664
  console.log();
@@ -46767,288 +46804,9 @@ async function runRepl(options) {
46767
46804
  await session.start();
46768
46805
  }
46769
46806
 
46770
- // src/cli/repl-protocol.ts
46771
- import * as readline3 from "readline";
46772
- import * as fs9 from "fs";
46773
- import * as path10 from "path";
46774
- import * as os4 from "os";
46775
- import { spawn as spawn6 } from "child_process";
46776
- var PROMPT2 = "protocol> ";
46777
- var HISTORY_FILE2 = path10.join(os4.homedir(), ".miriad-code-protocol-history");
46778
- var MAX_HISTORY2 = 1000;
46779
-
46780
- class ProtocolReplSession {
46781
- options;
46782
- rl = null;
46783
- serverProcess = null;
46784
- history = [];
46785
- isRunning = false;
46786
- sessionId = `session_${Date.now()}`;
46787
- lineBuffer = "";
46788
- constructor(options) {
46789
- this.options = options;
46790
- }
46791
- async start() {
46792
- this.loadHistory();
46793
- this.startServer();
46794
- this.rl = readline3.createInterface({
46795
- input: process.stdin,
46796
- output: process.stdout,
46797
- prompt: PROMPT2,
46798
- historySize: MAX_HISTORY2,
46799
- terminal: true
46800
- });
46801
- for (const line of this.history) {
46802
- this.rl.history?.unshift(line);
46803
- }
46804
- this.rl.on("SIGINT", () => {
46805
- if (this.isRunning) {
46806
- this.sendControl("interrupt");
46807
- process.stdout.write(`
46808
- ^C - Interrupt sent
46809
- `);
46810
- } else {
46811
- process.stdout.write(`
46812
- (Use /quit or Ctrl+D to exit)
46813
- `);
46814
- this.rl?.prompt();
46815
- }
46816
- });
46817
- this.rl.on("close", () => {
46818
- this.shutdown();
46819
- });
46820
- this.rl.on("line", (line) => {
46821
- this.handleLine(line).catch((error2) => {
46822
- console.error(`Error: ${error2 instanceof Error ? error2.message : String(error2)}`);
46823
- this.rl?.prompt();
46824
- });
46825
- });
46826
- this.printWelcome();
46827
- this.rl.prompt();
46828
- }
46829
- startServer() {
46830
- const args = ["run", "src/cli/index.ts", "--stdio", "--db", this.options.dbPath];
46831
- this.serverProcess = spawn6("bun", args, {
46832
- stdio: ["pipe", "pipe", "inherit"],
46833
- cwd: process.cwd()
46834
- });
46835
- this.serverProcess.stdout?.on("data", (data) => {
46836
- this.lineBuffer += data.toString();
46837
- const lines = this.lineBuffer.split(`
46838
- `);
46839
- this.lineBuffer = lines.pop() || "";
46840
- for (const line of lines) {
46841
- if (line.trim()) {
46842
- this.handleServerMessage(line.trim());
46843
- }
46844
- }
46845
- });
46846
- this.serverProcess.on("exit", (code) => {
46847
- console.log(`
46848
- Server exited with code ${code}`);
46849
- process.exit(code ?? 0);
46850
- });
46851
- this.serverProcess.on("error", (error2) => {
46852
- console.error(`Server error: ${error2.message}`);
46853
- process.exit(1);
46854
- });
46855
- }
46856
- handleServerMessage(line) {
46857
- try {
46858
- const msg = JSON.parse(line);
46859
- this.displayMessage(msg);
46860
- } catch (error2) {}
46861
- }
46862
- displayMessage(msg) {
46863
- process.stdout.write("\r\x1B[K");
46864
- switch (msg.type) {
46865
- case "assistant":
46866
- const text3 = msg.message.content.filter((b) => b.type === "text").map((b) => b.text).join("");
46867
- if (text3) {
46868
- console.log(`\x1B[36m${text3}\x1B[0m`);
46869
- }
46870
- break;
46871
- case "result":
46872
- this.isRunning = false;
46873
- const status = msg.subtype === "success" ? "\x1B[32m\u2713\x1B[0m" : msg.subtype === "cancelled" ? "\x1B[33m\u2298\x1B[0m" : "\x1B[31m\u2717\x1B[0m";
46874
- console.log(`
46875
- ${status} ${msg.subtype} (${msg.duration_ms}ms, ${msg.num_turns} turns)`);
46876
- if (msg.usage) {
46877
- console.log(` tokens: ${msg.usage.input_tokens} in / ${msg.usage.output_tokens} out`);
46878
- }
46879
- this.rl?.prompt();
46880
- break;
46881
- case "system":
46882
- switch (msg.subtype) {
46883
- case "queued":
46884
- console.log(`\x1B[90m[Queued: position ${msg.position}]\x1B[0m`);
46885
- break;
46886
- case "injected":
46887
- console.log(`\x1B[90m[Injected: ${msg.message_count} message(s)]\x1B[0m`);
46888
- break;
46889
- case "interrupted":
46890
- console.log(`\x1B[33m[Interrupted]\x1B[0m`);
46891
- break;
46892
- case "status":
46893
- console.log(`\x1B[90m[Status: running=${msg.running}, queued=${msg.queued_messages}]\x1B[0m`);
46894
- this.rl?.prompt();
46895
- break;
46896
- case "tool_result":
46897
- break;
46898
- case "error":
46899
- console.log(`\x1B[31m[Error: ${msg.message}]\x1B[0m`);
46900
- break;
46901
- default:
46902
- console.log(`\x1B[90m[${msg.subtype}]\x1B[0m`);
46903
- }
46904
- break;
46905
- }
46906
- }
46907
- printWelcome() {
46908
- console.log();
46909
- console.log("miriad-code protocol REPL");
46910
- console.log("Type messages to send to the agent via protocol");
46911
- console.log("Messages sent while agent is working will be injected mid-turn");
46912
- console.log("Type /help for commands");
46913
- console.log();
46914
- }
46915
- async handleLine(line) {
46916
- const trimmed = line.trim();
46917
- if (!trimmed) {
46918
- this.rl?.prompt();
46919
- return;
46920
- }
46921
- this.addToHistory(trimmed);
46922
- if (trimmed.startsWith("/")) {
46923
- await this.handleCommand(trimmed);
46924
- return;
46925
- }
46926
- this.sendUserMessage(trimmed);
46927
- }
46928
- async handleCommand(input) {
46929
- const parts = input.slice(1).split(/\s+/);
46930
- const command = parts[0].toLowerCase();
46931
- switch (command) {
46932
- case "quit":
46933
- case "exit":
46934
- case "q":
46935
- this.shutdown();
46936
- break;
46937
- case "status":
46938
- this.sendControl("status");
46939
- break;
46940
- case "interrupt":
46941
- case "cancel":
46942
- this.sendControl("interrupt");
46943
- break;
46944
- case "help":
46945
- case "h":
46946
- case "?":
46947
- this.printHelp();
46948
- this.rl?.prompt();
46949
- break;
46950
- default:
46951
- console.log(`Unknown command: /${command}`);
46952
- console.log("Type /help for available commands.");
46953
- this.rl?.prompt();
46954
- }
46955
- }
46956
- printHelp() {
46957
- console.log();
46958
- console.log("Commands:");
46959
- console.log(" /help, /h, /? Show this help");
46960
- console.log(" /quit, /exit, /q Exit the REPL");
46961
- console.log(" /status Get server status");
46962
- console.log(" /interrupt Cancel current turn");
46963
- console.log();
46964
- console.log("Shortcuts:");
46965
- console.log(" Ctrl+C Send interrupt");
46966
- console.log(" Ctrl+D Exit");
46967
- console.log();
46968
- console.log("Mid-turn messaging:");
46969
- console.log(" Type while the agent is working to inject messages");
46970
- console.log(" Messages are queued and injected before the next model call");
46971
- console.log();
46972
- }
46973
- sendUserMessage(content) {
46974
- const msg = {
46975
- type: "user",
46976
- message: { role: "user", content },
46977
- session_id: this.sessionId
46978
- };
46979
- this.serverProcess?.stdin?.write(JSON.stringify(msg) + `
46980
- `);
46981
- this.isRunning = true;
46982
- }
46983
- sendControl(action) {
46984
- const msg = { type: "control", action };
46985
- this.serverProcess?.stdin?.write(JSON.stringify(msg) + `
46986
- `);
46987
- }
46988
- shutdown() {
46989
- this.saveHistory();
46990
- console.log(`
46991
- Goodbye!`);
46992
- this.serverProcess?.kill();
46993
- process.exit(0);
46994
- }
46995
- loadHistory() {
46996
- try {
46997
- if (fs9.existsSync(HISTORY_FILE2)) {
46998
- const content = fs9.readFileSync(HISTORY_FILE2, "utf-8");
46999
- this.history = content.split(`
47000
- `).filter((line) => line.trim()).slice(-MAX_HISTORY2);
47001
- }
47002
- } catch {}
47003
- }
47004
- saveHistory() {
47005
- try {
47006
- const rlHistory = this.rl?.history || [];
47007
- const allHistory = [...new Set([...rlHistory.reverse(), ...this.history])];
47008
- const trimmed = allHistory.slice(-MAX_HISTORY2);
47009
- fs9.writeFileSync(HISTORY_FILE2, trimmed.join(`
47010
- `) + `
47011
- `);
47012
- } catch {}
47013
- }
47014
- addToHistory(line) {
47015
- if (line.startsWith("/"))
47016
- return;
47017
- this.history.push(line);
47018
- }
47019
- }
47020
- async function runProtocolRepl(options) {
47021
- const session = new ProtocolReplSession(options);
47022
- await session.start();
47023
- }
47024
-
47025
46807
  // src/version.ts
47026
- import { execSync } from "child_process";
47027
- import { readFileSync as readFileSync7 } from "fs";
47028
- import { join as join9, dirname as dirname5 } from "path";
47029
- import { fileURLToPath } from "url";
47030
- function getPackageVersion() {
47031
- try {
47032
- const __dirname2 = dirname5(fileURLToPath(import.meta.url));
47033
- const pkgPath = join9(__dirname2, "..", "package.json");
47034
- const pkg = JSON.parse(readFileSync7(pkgPath, "utf-8"));
47035
- return pkg.version || "0.0.0";
47036
- } catch {
47037
- return "0.0.0";
47038
- }
47039
- }
47040
- function getGitHash() {
47041
- try {
47042
- return execSync("git rev-parse --short HEAD", {
47043
- encoding: "utf-8",
47044
- stdio: ["pipe", "pipe", "pipe"]
47045
- }).trim();
47046
- } catch {
47047
- return "unknown";
47048
- }
47049
- }
47050
- var VERSION = getPackageVersion();
47051
- var GIT_HASH = getGitHash();
46808
+ var VERSION = "0.1.12";
46809
+ var GIT_HASH = "712107d";
47052
46810
  var VERSION_STRING = `miriad-code v${VERSION} (${GIT_HASH})`;
47053
46811
 
47054
46812
  // src/cli/index.ts
@@ -47065,8 +46823,7 @@ function parseCliArgs() {
47065
46823
  dump: { type: "boolean", default: false },
47066
46824
  compact: { type: "boolean", default: false },
47067
46825
  stdio: { type: "boolean", default: false },
47068
- repl: { type: "boolean", default: false },
47069
- "repl-protocol": { type: "boolean", default: false }
46826
+ repl: { type: "boolean", default: false }
47070
46827
  },
47071
46828
  allowPositionals: false
47072
46829
  });
@@ -47081,8 +46838,7 @@ function parseCliArgs() {
47081
46838
  dump: values.dump ?? false,
47082
46839
  compact: values.compact ?? false,
47083
46840
  stdio: values.stdio ?? false,
47084
- repl: values.repl ?? false,
47085
- replProtocol: values["repl-protocol"] ?? false
46841
+ repl: values.repl ?? false
47086
46842
  };
47087
46843
  }
47088
46844
  function printHelp() {
@@ -47095,7 +46851,6 @@ Usage:
47095
46851
  miriad-code -p "prompt" Run agent with a prompt
47096
46852
  miriad-code -p "prompt" --verbose Show debug output
47097
46853
  miriad-code --repl Start interactive REPL mode
47098
- miriad-code --repl-protocol Start REPL using protocol server (for testing)
47099
46854
  miriad-code --stdio Start protocol server over stdin/stdout
47100
46855
  miriad-code --inspect Show memory stats (no LLM call)
47101
46856
  miriad-code --dump Show raw system prompt (no LLM call)
@@ -47106,7 +46861,6 @@ Options:
47106
46861
  -p, --prompt <text> The prompt to send to the agent
47107
46862
  -v, --verbose Show memory state, token usage, and execution trace
47108
46863
  --repl Start interactive REPL with readline support
47109
- --repl-protocol Start REPL that uses protocol server (test mid-turn messages)
47110
46864
  --stdio Start Claude Code SDK protocol server on stdin/stdout
47111
46865
  --inspect Show memory statistics: temporal, present, LTM
47112
46866
  --dump Dump the full system prompt that would be sent to LLM
@@ -47171,19 +46925,6 @@ async function main() {
47171
46925
  }
47172
46926
  return;
47173
46927
  }
47174
- if (options.replProtocol) {
47175
- try {
47176
- await runProtocolRepl({ dbPath: options.db });
47177
- } catch (error2) {
47178
- if (error2 instanceof Error) {
47179
- console.error(`Error: ${error2.message}`);
47180
- } else {
47181
- console.error("Unknown error:", error2);
47182
- }
47183
- process.exit(1);
47184
- }
47185
- return;
47186
- }
47187
46928
  if (options.stdio) {
47188
46929
  try {
47189
46930
  await runServer({ dbPath: options.db });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@miriad-systems/nuum",
3
- "version": "0.1.10",
3
+ "version": "0.1.12",
4
4
  "description": "AI coding agent with continuous memory - infinite context across sessions",
5
5
  "type": "module",
6
6
  "bin": {
@@ -11,7 +11,7 @@
11
11
  "src/storage/migrations"
12
12
  ],
13
13
  "scripts": {
14
- "build": "bun build ./src/cli/index.ts --outdir ./dist --target bun && node -e \"const fs=require('fs');const f='./dist/index.js';fs.writeFileSync(f,fs.readFileSync(f,'utf8').replace('#!/usr/bin/env node','#!/usr/bin/env bun'))\"",
14
+ "build": "bun build ./src/cli/index.ts --outdir ./dist --target bun --define \"BUILD_VERSION='$(node -p \"require('./package.json').version\")'\" --define \"BUILD_GIT_HASH='$(git rev-parse --short HEAD 2>/dev/null || echo unknown)'\" && node -e \"const fs=require('fs');const f='./dist/index.js';fs.writeFileSync(f,fs.readFileSync(f,'utf8').replace('#!/usr/bin/env node','#!/usr/bin/env bun'))\"",
15
15
  "dev": "bun run ./src/cli/index.ts",
16
16
  "typecheck": "tsc --noEmit",
17
17
  "test": "bun test",
@@ -31,7 +31,7 @@
31
31
  "license": "MIT",
32
32
  "repository": {
33
33
  "type": "git",
34
- "url": "https://github.com/simen/miriad-code.git"
34
+ "url": "https://github.com/sanity-labs/nuum.git"
35
35
  },
36
36
  "engines": {
37
37
  "node": ">=18.0.0"