@sanity-labs/nuum 0.5.2 → 0.5.4

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 (2) hide show
  1. package/dist/index.js +115 -23
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -35674,6 +35674,20 @@ function estimateSummaryTokens(input) {
35674
35674
  }
35675
35675
  // src/agent/loop.ts
35676
35676
  var log5 = Log.create({ service: "agent-loop" });
35677
+ var MODEL_MAX_OUTPUT_TOKENS = {
35678
+ "claude-opus-4-6": 128000,
35679
+ "claude-opus-4-6-20250918": 128000,
35680
+ "claude-sonnet-4-5-20250929": 64000,
35681
+ "claude-sonnet-4-5": 64000,
35682
+ "claude-haiku-4-5-20251001": 64000,
35683
+ "claude-haiku-4-5": 64000,
35684
+ "claude-3-5-sonnet-20241022": 8192,
35685
+ "claude-3-5-haiku-20241022": 8192
35686
+ };
35687
+ var DEFAULT_MAX_OUTPUT_TOKENS = 16384;
35688
+ function getMaxOutputTokens(modelId) {
35689
+ return MODEL_MAX_OUTPUT_TOKENS[modelId] ?? DEFAULT_MAX_OUTPUT_TOKENS;
35690
+ }
35677
35691
  function addCacheMarkers(messages) {
35678
35692
  if (messages.length === 0)
35679
35693
  return messages;
@@ -35702,7 +35716,7 @@ async function runAgentLoop(options) {
35702
35716
  systemPrompt,
35703
35717
  initialMessages,
35704
35718
  tools,
35705
- maxTokens = 4096,
35719
+ maxTokens: maxTokensOverride,
35706
35720
  temperature,
35707
35721
  maxTurns,
35708
35722
  abortSignal,
@@ -35713,6 +35727,12 @@ async function runAgentLoop(options) {
35713
35727
  onBeforeTurn,
35714
35728
  onThinking
35715
35729
  } = options;
35730
+ const maxTokens = maxTokensOverride ?? getMaxOutputTokens(model.modelId);
35731
+ log5.info("agent loop starting", {
35732
+ model: model.modelId,
35733
+ maxTokens,
35734
+ maxTurns
35735
+ });
35716
35736
  if (abortSignal?.aborted) {
35717
35737
  throw new AgentLoopCancelledError;
35718
35738
  }
@@ -35768,6 +35788,13 @@ async function runAgentLoop(options) {
35768
35788
  cacheHitRate: total > 0 ? `${Math.round(cacheRead / total * 100)}%` : "0%"
35769
35789
  });
35770
35790
  }
35791
+ if (response.finishReason === "length") {
35792
+ log5.warn("output truncated - model hit maxTokens limit", {
35793
+ maxTokens,
35794
+ outputTokens: response.usage.completionTokens,
35795
+ hasToolCalls: (response.toolCalls?.length ?? 0) > 0
35796
+ });
35797
+ }
35771
35798
  if (response.text) {
35772
35799
  finalText = response.text;
35773
35800
  await onText?.(response.text);
@@ -35812,6 +35839,15 @@ async function runAgentLoop(options) {
35812
35839
  content: toolResultParts
35813
35840
  };
35814
35841
  messages.push(toolMsg);
35842
+ if (response.finishReason === "length") {
35843
+ const hadInvalidCalls = toolCallInfos.some((tc) => tc.toolName === "__invalid_tool_call__");
35844
+ if (hadInvalidCalls) {
35845
+ messages.push({
35846
+ role: "user",
35847
+ content: "[SYSTEM: Your previous output was truncated because it exceeded the output token limit. " + "Your tool call was incomplete \u2014 parameters were cut off mid-generation. " + "To fix this: break large content into smaller chunks, or use bash with echo/cat to write files incrementally. " + "Do NOT retry the same large tool call \u2014 it will truncate again.]"
35848
+ });
35849
+ }
35850
+ }
35815
35851
  if (isDone(toolCallInfos)) {
35816
35852
  stopReason = "done";
35817
35853
  break;
@@ -36138,7 +36174,6 @@ async function runCompaction(storage, config) {
36138
36174
  systemPrompt: ctx.systemPrompt,
36139
36175
  initialMessages,
36140
36176
  tools,
36141
- maxTokens: 4096,
36142
36177
  temperature: 0,
36143
36178
  maxTurns: 5,
36144
36179
  isDone: stopOnTool("finish_distillation"),
@@ -36244,7 +36279,7 @@ async function runSubAgent(storage, config) {
36244
36279
  extractResult,
36245
36280
  tier = "workhorse",
36246
36281
  maxTurns = 20,
36247
- maxTokens = 4096,
36282
+ maxTokens,
36248
36283
  temperature = 0,
36249
36284
  onToolResult
36250
36285
  } = config;
@@ -36431,8 +36466,7 @@ async function runReflection(storage, question) {
36431
36466
  finishToolName: "finish_reflection",
36432
36467
  extractResult: getAnswer,
36433
36468
  tier: "workhorse",
36434
- maxTurns: 20,
36435
- maxTokens: 4096
36469
+ maxTurns: 20
36436
36470
  });
36437
36471
  const answer = result.result ?? "Unable to find relevant information.";
36438
36472
  activity.reflection.complete(`${result.turnsUsed} turns, ${answer.length} chars`);
@@ -36836,7 +36870,6 @@ async function runResearch(storage, topic) {
36836
36870
  },
36837
36871
  tier: "workhorse",
36838
36872
  maxTurns: MAX_RESEARCH_TURNS,
36839
- maxTokens: 8192,
36840
36873
  onToolResult: (toolCallId) => {
36841
36874
  const toolResult = getLastResult(toolCallId);
36842
36875
  if (!toolResult)
@@ -45086,6 +45119,30 @@ var Mcp;
45086
45119
  function isHttpConfig(config2) {
45087
45120
  return "url" in config2;
45088
45121
  }
45122
+ const TOOL_NAME_PATTERN = /^[a-zA-Z0-9_-]{1,64}$/;
45123
+ function validateToolName(serverName, mcpToolName) {
45124
+ const effectiveName = `${serverName}__${mcpToolName}`;
45125
+ if (TOOL_NAME_PATTERN.test(effectiveName)) {
45126
+ return null;
45127
+ }
45128
+ const invalidChars = effectiveName.split("").filter((c) => !/[a-zA-Z0-9_-]/.test(c));
45129
+ const uniqueInvalid = Array.from(new Set(invalidChars));
45130
+ let message;
45131
+ if (effectiveName.length > 64) {
45132
+ message = `Effective tool name "${effectiveName}" exceeds 64 character limit (${effectiveName.length} chars)`;
45133
+ } else if (effectiveName.length === 0) {
45134
+ message = `Tool name is empty`;
45135
+ } else {
45136
+ message = `Effective tool name "${effectiveName}" contains invalid character(s): ${uniqueInvalid.map((c) => `"${c}"`).join(", ")} (allowed: a-z, A-Z, 0-9, _, -)`;
45137
+ }
45138
+ return {
45139
+ type: "invalid_tool_name",
45140
+ tool: mcpToolName,
45141
+ effectiveName,
45142
+ message
45143
+ };
45144
+ }
45145
+ Mcp.validateToolName = validateToolName;
45089
45146
 
45090
45147
  class Manager {
45091
45148
  servers = new Map;
@@ -45124,6 +45181,8 @@ var Mcp;
45124
45181
  config: config2,
45125
45182
  client: null,
45126
45183
  tools: [],
45184
+ allToolCount: 0,
45185
+ issues: [],
45127
45186
  status: "disabled"
45128
45187
  };
45129
45188
  }
@@ -45141,24 +45200,38 @@ var Mcp;
45141
45200
  new Promise((_, reject) => setTimeout(() => reject(new Error("Connection timeout")), timeoutMs))
45142
45201
  ]);
45143
45202
  const toolsResult = await client.listTools();
45144
- const tools = toolsResult.tools;
45203
+ const allTools = toolsResult.tools;
45204
+ const validTools = [];
45205
+ const issues = [];
45206
+ for (const mcpTool of allTools) {
45207
+ const issue2 = validateToolName(name17, mcpTool.name);
45208
+ if (issue2) {
45209
+ issues.push(issue2);
45210
+ console.error(`[mcp:${name17}] Skipping tool "${mcpTool.name}": ${issue2.message}`);
45211
+ } else {
45212
+ validTools.push(mcpTool);
45213
+ }
45214
+ }
45145
45215
  let sessionId;
45146
45216
  if (transport instanceof StreamableHTTPClientTransport) {
45147
45217
  sessionId = transport.sessionId;
45148
45218
  if (sessionId) {
45149
- console.error(`[mcp:${name17}] Connected with session ${sessionId.slice(0, 8)}..., ${tools.length} tools available`);
45219
+ console.error(`[mcp:${name17}] Connected with session ${sessionId.slice(0, 8)}..., ${validTools.length}/${allTools.length} tools available`);
45150
45220
  } else {
45151
- console.error(`[mcp:${name17}] Connected (no session), ${tools.length} tools available`);
45221
+ console.error(`[mcp:${name17}] Connected (no session), ${validTools.length}/${allTools.length} tools available`);
45152
45222
  }
45153
45223
  } else {
45154
- console.error(`[mcp:${name17}] Connected, ${tools.length} tools available`);
45224
+ console.error(`[mcp:${name17}] Connected, ${validTools.length}/${allTools.length} tools available`);
45155
45225
  }
45226
+ const status = issues.length > 0 ? "degraded" : "connected";
45156
45227
  return {
45157
45228
  name: name17,
45158
45229
  config: config2,
45159
45230
  client,
45160
- tools,
45161
- status: "connected",
45231
+ tools: validTools,
45232
+ allToolCount: allTools.length,
45233
+ issues,
45234
+ status,
45162
45235
  sessionId
45163
45236
  };
45164
45237
  } catch (e) {
@@ -45169,6 +45242,8 @@ var Mcp;
45169
45242
  config: config2,
45170
45243
  client,
45171
45244
  tools: [],
45245
+ allToolCount: 0,
45246
+ issues: [],
45172
45247
  status: "failed",
45173
45248
  error: error2
45174
45249
  };
@@ -45185,10 +45260,18 @@ var Mcp;
45185
45260
  this.servers.set(server.name, server);
45186
45261
  }
45187
45262
  const connected = results.filter((s) => s.status === "connected").length;
45263
+ const degraded = results.filter((s) => s.status === "degraded").length;
45188
45264
  const failed = results.filter((s) => s.status === "failed").length;
45189
45265
  const disabled = results.filter((s) => s.status === "disabled").length;
45190
45266
  if (entries.length > 0) {
45191
- console.error(`[mcp] Initialized: ${connected} connected, ${failed} failed, ${disabled} disabled`);
45267
+ const parts = [`${connected} connected`];
45268
+ if (degraded > 0)
45269
+ parts.push(`${degraded} degraded`);
45270
+ if (failed > 0)
45271
+ parts.push(`${failed} failed`);
45272
+ if (disabled > 0)
45273
+ parts.push(`${disabled} disabled`);
45274
+ console.error(`[mcp] Initialized: ${parts.join(", ")}`);
45192
45275
  }
45193
45276
  }
45194
45277
  async shutdown() {
@@ -45206,14 +45289,16 @@ var Mcp;
45206
45289
  return Array.from(this.servers.values()).map((s) => ({
45207
45290
  name: s.name,
45208
45291
  status: s.status,
45209
- toolCount: s.tools.length,
45292
+ toolCount: s.allToolCount,
45293
+ activeToolCount: s.tools.length,
45294
+ issues: s.issues,
45210
45295
  error: s.error
45211
45296
  }));
45212
45297
  }
45213
45298
  listTools() {
45214
45299
  const tools = [];
45215
45300
  for (const [serverName, server] of this.servers) {
45216
- if (server.status !== "connected")
45301
+ if (server.status !== "connected" && server.status !== "degraded")
45217
45302
  continue;
45218
45303
  for (const mcpTool of server.tools) {
45219
45304
  tools.push(`${serverName}__${mcpTool.name}`);
@@ -45254,7 +45339,7 @@ var Mcp;
45254
45339
  getTools() {
45255
45340
  const tools = {};
45256
45341
  for (const [serverName, server] of this.servers) {
45257
- if (server.status !== "connected")
45342
+ if (server.status !== "connected" && server.status !== "degraded")
45258
45343
  continue;
45259
45344
  for (const mcpTool of server.tools) {
45260
45345
  const toolName = `${serverName}__${mcpTool.name}`;
@@ -45319,8 +45404,8 @@ var Mcp;
45319
45404
  })(Mcp ||= {});
45320
45405
 
45321
45406
  // src/version.ts
45322
- var VERSION = "0.5.2";
45323
- var GIT_HASH = "91ca6b6";
45407
+ var VERSION = "0.5.4";
45408
+ var GIT_HASH = "e1f89a6";
45324
45409
  var VERSION_STRING = `nuum v${VERSION} (${GIT_HASH})`;
45325
45410
 
45326
45411
  // src/tool/mcp-status.ts
@@ -45369,14 +45454,23 @@ Use this when:
45369
45454
  } else {
45370
45455
  let connectedCount = 0;
45371
45456
  for (const server of mcpStatus) {
45372
- const statusIcon = server.status === "connected" ? "\u2713" : server.status === "connecting" ? "\u22EF" : "\u2717";
45457
+ const statusIcon = server.status === "connected" ? "\u2713" : server.status === "degraded" ? "\u26A0" : server.status === "connecting" ? "\u22EF" : "\u2717";
45373
45458
  output += `**${server.name}** ${statusIcon}
45374
45459
  `;
45375
- if (server.status === "connected") {
45460
+ if (server.status === "connected" || server.status === "degraded") {
45376
45461
  connectedCount++;
45377
45462
  const serverTools = mcpToolNames.filter((t) => t.startsWith(`${server.name}__`));
45378
- output += `- ${server.toolCount} tools: ${serverTools.map((t) => t.replace(`${server.name}__`, "")).join(", ")}
45463
+ if (server.status === "degraded") {
45464
+ output += `- ${server.activeToolCount}/${server.toolCount} tools active: ${serverTools.map((t) => t.replace(`${server.name}__`, "")).join(", ")}
45465
+ `;
45466
+ for (const issue2 of server.issues) {
45467
+ output += ` - \u26A0 Skipped "${issue2.tool}": ${issue2.message}
45379
45468
  `;
45469
+ }
45470
+ } else {
45471
+ output += `- ${server.toolCount} tools: ${serverTools.map((t) => t.replace(`${server.name}__`, "")).join(", ")}
45472
+ `;
45473
+ }
45380
45474
  } else if (server.error) {
45381
45475
  output += `- Error: ${server.error}
45382
45476
  `;
@@ -46829,7 +46923,6 @@ async function runConsolidation(storage, messages) {
46829
46923
  },
46830
46924
  tier: "workhorse",
46831
46925
  maxTurns: MAX_CONSOLIDATION_TURNS,
46832
- maxTokens: 2048,
46833
46926
  onToolResult: (toolCallId) => {
46834
46927
  const toolResult = getLastResult(toolCallId);
46835
46928
  if (!toolResult)
@@ -47371,7 +47464,6 @@ async function runAgent(prompt, options) {
47371
47464
  systemPrompt: ctx.systemPrompt,
47372
47465
  initialMessages,
47373
47466
  tools,
47374
- maxTokens: 8192,
47375
47467
  maxTurns: MAX_TURNS,
47376
47468
  abortSignal,
47377
47469
  onText: async (text3) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sanity-labs/nuum",
3
- "version": "0.5.2",
3
+ "version": "0.5.4",
4
4
  "description": "AI coding agent with continuous memory - infinite context across sessions",
5
5
  "type": "module",
6
6
  "bin": {