@tangle-network/traces 0.17.0 → 0.17.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.
@@ -2448,7 +2448,10 @@ function distinctClaudeEvent(event, seen, sourcePath, fallbackUid) {
2448
2448
  if (previous.fingerprint !== fingerprint) {
2449
2449
  throw new ClaudeEventConflictError(sourcePath, uid);
2450
2450
  }
2451
- if (previous.originKind === null && originKind !== null) return { originEnrichment: originKind, uid };
2451
+ if (previous.originKind === null && originKind !== null) {
2452
+ seen.set(uid, { fingerprint, originKind });
2453
+ return { originEnrichment: originKind, uid };
2454
+ }
2452
2455
  if (previous.originKind !== null && originKind !== null && previous.originKind !== originKind) {
2453
2456
  throw new ClaudeEventConflictError(sourcePath, uid);
2454
2457
  }
@@ -15478,7 +15481,10 @@ var FILE_DELIVERY_CONTRACT_LINES = [
15478
15481
  ' "confidence": number 0..1}',
15479
15482
  "Do NOT include a rationale field. Keep every string SHORT \u2014 long strings get corrupted in transport and void your work.",
15480
15483
  `Report at most ${MAX_FINDINGS} findings; a finding cites 1..${MAX_SPAN_IDS_PER_FINDING} span_ids, each copied VERBATIM from a span in ${TRAJECTORY_FILE_NAME}.`,
15481
- '"findings" is [] only for a clean trajectory.'
15484
+ '"findings" is [] only for a clean trajectory.',
15485
+ "FINAL MESSAGE RULE: after your REPL work is done, send ONE last message that contains NOTHING BUT the",
15486
+ "fenced ```json block above \u2014 no preamble, no summary, no code identifiers around it. A reply whose last",
15487
+ "message is prose or a partial block is discarded unread."
15482
15488
  ];
15483
15489
  function buildPrompt(question, projection) {
15484
15490
  if (projection.delivery.mode === "file-cwd") {
@@ -15499,16 +15505,23 @@ function buildPrompt(question, projection) {
15499
15505
  projection.rendered
15500
15506
  ].join("\n");
15501
15507
  }
15502
- function buildRepairPrompt(parseDefect, previousReply) {
15508
+ function replyWasTruncated(previousReply, finishReason) {
15509
+ if (finishReason === "length") return true;
15510
+ if (finishReason !== null) return false;
15511
+ return extractJsonObject(previousReply) === null;
15512
+ }
15513
+ function buildRepairPrompt(parseDefect, previousReply, finishReason) {
15514
+ const truncated = replyWasTruncated(previousReply, finishReason);
15503
15515
  return [
15504
15516
  "Your previous reply to a trace-analysis task was structurally malformed and could not be parsed",
15505
- `(${parseDefect}). Below is your previous reply verbatim. Re-emit ONLY the corrected JSON \u2014 one`,
15506
- "fenced ```json block, no other text, no tools. The JSON object has exactly two fields:",
15517
+ `(${parseDefect}${truncated ? "; it was cut off by the output limit" : ""}). Below is your previous reply verbatim.`,
15518
+ "Re-emit ONLY the corrected JSON \u2014 one fenced ```json block, no other text, no tools. The JSON object has exactly two fields:",
15507
15519
  ' "answer": string (ONE short sentence, max 300 chars)',
15508
15520
  ' "findings": array (possibly empty) of {"span_ids": [string, ...],',
15509
15521
  ' "severity": "critical"|"high"|"medium"|"low"|"info", "area": string (max 40 chars),',
15510
15522
  ' "claim": string (max 200 chars), "action": string (optional, max 200 chars), "confidence": number 0..1}',
15511
- "No rationale field. Keep every string SHORT. Preserve the span ids and verdicts of your previous reply exactly; shorten prose freely.",
15523
+ "No rationale field. Keep every string SHORT.",
15524
+ truncated ? "Your previous reply was cut off by the output limit, so re-emit at most 3 findings \u2014 your strongest \u2014 with terse strings (claim and action under 120 chars each); do not reproduce the whole cut-off list." : "Preserve the span ids and verdicts of your previous reply exactly; shorten prose freely.",
15512
15525
  "",
15513
15526
  "PREVIOUS REPLY:",
15514
15527
  previousReply
@@ -15675,7 +15688,12 @@ async function callBridge(transport, url, model, content, timeoutMs, callerSigna
15675
15688
  if (typeof replyContent !== "string" || replyContent.length === 0) {
15676
15689
  throw new PrimeBridgeError(`${turn}bridge reply carries no message content`);
15677
15690
  }
15678
- return { content: replyContent, usage: normalizeUsage(record2.usage) };
15691
+ const finishReason = record2.choices?.[0]?.finish_reason;
15692
+ return {
15693
+ content: replyContent,
15694
+ usage: normalizeUsage(record2.usage),
15695
+ finishReason: typeof finishReason === "string" && finishReason.length > 0 ? finishReason : null
15696
+ };
15679
15697
  }
15680
15698
  function failure(output, error) {
15681
15699
  return { analyzer: ANALYST_ID, kind: "report", ok: false, output, error };
@@ -15748,7 +15766,7 @@ function primeAnalyzer(opts = {}) {
15748
15766
  transport,
15749
15767
  url,
15750
15768
  model,
15751
- buildRepairPrompt(parseDefect, reply.content),
15769
+ buildRepairPrompt(parseDefect, reply.content, reply.finishReason),
15752
15770
  timeoutMs,
15753
15771
  analyzeOpts.signal,
15754
15772
  "repair-turn "
package/dist/cli.js CHANGED
@@ -83,7 +83,7 @@ import {
83
83
  writeSessionIndexFile,
84
84
  writeTraceEvidenceExportFile,
85
85
  writeTraceQuestionsArtifacts
86
- } from "./chunk-4QY4U4XW.js";
86
+ } from "./chunk-SGQOMHZP.js";
87
87
 
88
88
  // src/cli.ts
89
89
  import { readFileSync } from "fs";
package/dist/index.js CHANGED
@@ -270,7 +270,7 @@ import {
270
270
  writeTraceQuestionsArtifacts,
271
271
  zaiChatCaller,
272
272
  zeroPricedCost
273
- } from "./chunk-4QY4U4XW.js";
273
+ } from "./chunk-SGQOMHZP.js";
274
274
 
275
275
  // src/collect.ts
276
276
  async function collectSessions(opts = {}) {
@@ -389,6 +389,47 @@ Output expectations:
389
389
 
390
390
  The scored prime-vs-dspy comparison — same trajectories, same scoring — lives in `@tangle-network/agent-eval`'s analyst benchmark (`runAnalystBenchmark`); this engine is the capture-side entry point, not the scoreboard.
391
391
 
392
+ ### Verifying against a real prime
393
+
394
+ The suite above runs against a scripted bridge over real HTTP. A full real run — real
395
+ prime-agent, real model, real REPL — is a manual, credentials-on-the-host affair:
396
+
397
+ 1. **Install prime-agent the way cli-bridge pins it.** It is a hard fork of pi that reuses
398
+ pi's npm name, so `npm i` gives you the WRONG agent: clone
399
+ `PrimeIntellect-ai/prime-agent`, `git checkout <the commit src/backends/prime.ts names>`
400
+ (be9e2fa0 at the time of writing), build, and expose the bundle as `prime-agent`
401
+ (cli-bridge's `pnpm install:harness prime` does exactly this).
402
+ 2. **Start cli-bridge with the prime backend**: `BRIDGE_BACKENDS=prime
403
+ PRIME_MODELS_JSON=<operator models.json> BRIDGE_PORT=4181 pnpm start`. The
404
+ models.json names an `apiKey` as an exported ENV VAR (not a literal — prime
405
+ resolves env-var-first, so an unforwarded name becomes the literal key), and the
406
+ bridge forwards exactly the names the file names into prime's neutral child env.
407
+ Mind which endpoint a key is funded on: a coding-plan GLM key only works on the
408
+ coding endpoint, and the same key 402s on the general one.
409
+ 3. **Give the kernel a persistent interpreter.** Prime's Python kernel bootstraps a
410
+ venv under its HOME, and the bridge gives every run an isolated one — a
411
+ uv-managed interpreter installed inside an ephemeral HOME dies with it. Pre-provision
412
+ a venv (ipykernel + the default rlm extras + an editable
413
+ `prime-agent-runtime`) from a system Python and export
414
+ `PRIME_AGENT_KERNEL_PYTHON=<that venv's python>` to the bridge; prime skips
415
+ auto-bootstrap when the named interpreter already passes its checks.
416
+ 4. Run with `TRACES_PRIME_BRIDGE_URL=http://localhost:4181 TRACES_PRIME_MODEL=prime/<provider>/<model>`.
417
+ The opt-in suite (`TRACES_PRIME_REAL_E2E=1` in `tests/analyst-engine-prime.test.ts`)
418
+ exercises the same path from vitest.
419
+
420
+ What real runs have shown (GLM-5.3 via a coding-plan endpoint, fork at the pinned commit):
421
+
422
+ - **Inline, small session:** clean single-call success — one fenced JSON block, five findings,
423
+ all grounded, exit 0.
424
+ - **File, ~121 spans / 788 KB artifact:** the RLM loads and pages the file (14 calls, ~156k input
425
+ tokens); its first reply gets cut by the endpoint's output limit mid-JSON, and the
426
+ truncation-aware repair turn (re-emit at most 3 findings, terse strings) recovers it — ok:true,
427
+ findings grounded, exit 0.
428
+ - **File, ~794 spans / 2.1 MB artifact:** ~2.2M input tokens of genuine REPL inspection, but the
429
+ reply never survives the endpoint's output cap even after repair — the run fails honestly
430
+ (exit 1, raw reply preserved, no findings invented). At that trajectory size, a model with a
431
+ larger output budget — or a paged finalization — is the next step, not silence about it.
432
+
392
433
  ## Write one analyst
393
434
 
394
435
  An analyst receives a paged trace store and returns typed findings.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@tangle-network/traces",
3
- "version": "0.17.0",
4
- "description": "Point it at your coding-agent session traces (Claude Code, Codex, OpenCode, Gemini, Pi, \u2026) and get failure-mode + efficiency findings. CLI + SDK over the @tangle-network/agent-eval analyst suite \u2014 observe live sessions, run your own analysts, redact, and upload to the Tangle Intelligence Platform.",
3
+ "version": "0.17.1",
4
+ "description": "Point it at your coding-agent session traces (Claude Code, Codex, OpenCode, Gemini, Pi, …) and get failure-mode + efficiency findings. CLI + SDK over the @tangle-network/agent-eval analyst suite — observe live sessions, run your own analysts, redact, and upload to the Tangle Intelligence Platform.",
5
5
  "type": "module",
6
6
  "license": "MIT",
7
7
  "repository": {