praxis-agent 0.51.0 → 0.52.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 CHANGED
@@ -216,9 +216,13 @@ troubleshooting. Run `praxis --help` for the authoritative command surface.
216
216
  tool selection defers `mcp__*` schemas behind a turn-scoped `ToolSearch`;
217
217
  each query activates at most eight deterministic matches for the next model
218
218
  request, and published MCP tool descriptions are capped at 2,048 Unicode
219
- code points. Explicit concrete `--tools` selections load selected tools
220
- directly, while `--disallowedTools ToolSearch` restores the complete tool
221
- list.
219
+ code points. Text-only MCP results above 100,000 UTF-8 bytes are redacted
220
+ into mode-`0600` `.txt` files under the session-scoped `tool-results`
221
+ directory; providers and transcripts receive only a bounded instruction with
222
+ the absolute file path. Results at or below the limit remain inline, while
223
+ structured, mixed-media, and binary-resource handling is unchanged. Explicit
224
+ concrete `--tools` selections load selected tools directly, while
225
+ `--disallowedTools ToolSearch` restores the complete tool list.
222
226
  - **Provider-neutral models** — native Provider Registry/Vault routing, API
223
227
  adapters, an experimental Codex OAuth adapter, explicit capability checks,
224
228
  separate per-attempt connect, byte-idle, and absolute-total timeouts, typed
@@ -1,7 +1,7 @@
1
1
  import { randomBytes } from 'node:crypto';
2
2
  import { mkdir, mkdtemp, open, rm } from 'node:fs/promises';
3
3
  import { homedir, tmpdir } from 'node:os';
4
- import { join } from 'node:path';
4
+ import { join, resolve as resolvePath } from 'node:path';
5
5
  import { SSEClientTransport } from '@modelcontextprotocol/sdk/client/sse.js';
6
6
  import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
7
7
  import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
@@ -26,6 +26,7 @@ function capMcpToolDescription(value) {
26
26
  return [...value].slice(0, MAX_MCP_TOOL_DESCRIPTION_CODE_POINTS).join('');
27
27
  }
28
28
  const MAX_RESOURCE_BYTES = 25 * 1024 * 1024;
29
+ const MAX_INLINE_MCP_TEXT_RESULT_BYTES = 100_000;
29
30
  const NO_MCP_RESOURCES = 'No resources found. MCP servers may still provide tools even if they have no resources.';
30
31
  const MCP_RESOURCE_TOOL_DEFINITIONS = [
31
32
  {
@@ -458,11 +459,21 @@ async function mcpBinaryText(options, sensitiveValues, createdFiles) {
458
459
  : `Resource from ${options.serverName} at ${options.uri}`;
459
460
  return mcpTextBlock(`[${origin}] Binary content (${options.mimeType}, ${options.bytes.length} bytes) saved to ${filePath}`, sensitiveValues);
460
461
  }
462
+ async function externalizeMcpTextResult(serverName, content, context, sensitiveValues, createdFiles) {
463
+ if (!context.toolResultDirectory) {
464
+ throw new Error('MCP large text tool result output directory is unavailable');
465
+ }
466
+ const bytes = Buffer.from(content, 'utf8');
467
+ const filePath = await writeToolBlob(resolvePath(context.cwd, context.toolResultDirectory), serverName, bytes, '.txt');
468
+ createdFiles.push(filePath);
469
+ return mcpTextBlock(`[MCP tool result from ${serverName}] Text content (${bytes.length} bytes) saved to ${filePath}`, sensitiveValues);
470
+ }
461
471
  async function mcpToolResultUnchecked(serverName, result, context, sensitiveValues, createdFiles) {
462
472
  if (!Array.isArray(result.content)) {
463
473
  throw new Error('MCP tool result content must be an array');
464
474
  }
465
475
  const blocks = [];
476
+ let textOnly = result.structuredContent === undefined;
466
477
  let resultBytes = 0;
467
478
  const consumeBytes = (bytes) => {
468
479
  resultBytes += bytes;
@@ -487,6 +498,7 @@ async function mcpToolResultUnchecked(serverName, result, context, sensitiveValu
487
498
  }
488
499
  continue;
489
500
  }
501
+ textOnly = false;
490
502
  if (item.type === 'image') {
491
503
  if (typeof item.mimeType !== 'string' ||
492
504
  !MCP_IMAGE_MEDIA_TYPES.has(item.mimeType) ||
@@ -573,6 +585,15 @@ async function mcpToolResultUnchecked(serverName, result, context, sensitiveValu
573
585
  if (Buffer.byteLength(content) > MAX_RESOURCE_BYTES) {
574
586
  throw new Error(`MCP tool result exceeded ${MAX_RESOURCE_BYTES} bytes`);
575
587
  }
588
+ if (textOnly &&
589
+ Buffer.byteLength(content) > MAX_INLINE_MCP_TEXT_RESULT_BYTES) {
590
+ const pointerBlock = await externalizeMcpTextResult(serverName, content, context, sensitiveValues, createdFiles);
591
+ return {
592
+ content: pointerBlock.text,
593
+ contentBlocks: [pointerBlock],
594
+ isError: result.isError === true,
595
+ };
596
+ }
576
597
  const images = blocks.filter((block) => block.type === 'image');
577
598
  return {
578
599
  content,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "praxis-agent",
3
- "version": "0.51.0",
3
+ "version": "0.52.0",
4
4
  "description": "Local-first, single-user general agent for the command line.",
5
5
  "license": "MIT",
6
6
  "author": "wuqisen",