@sonnechasser/ntrp 0.2.2 → 0.3.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/dist/ai/guardrails-smoke.js +19902 -0
- package/dist/ai/guardrails-smoke.js.map +1 -0
- package/dist/conversation/loop-guard-smoke.js +20096 -0
- package/dist/conversation/loop-guard-smoke.js.map +1 -0
- package/dist/demo/whimsy-smoke.js +1 -0
- package/dist/demo/whimsy-smoke.js.map +1 -1
- package/dist/index.js +23465 -19403
- package/dist/index.js.map +1 -1
- package/dist/investigation/verbosity-cli.js +15615 -12482
- package/dist/investigation/verbosity-cli.js.map +1 -1
- package/dist/mcp/server.js +17002 -13929
- package/dist/mcp/server.js.map +1 -1
- package/dist/services/transcript-smoke.js +897 -0
- package/dist/services/transcript-smoke.js.map +1 -0
- package/dist/strategist/strategist-smoke.js +1873 -0
- package/dist/strategist/strategist-smoke.js.map +1 -0
- package/dist/whimsy/time-bank-smoke.js +21749 -18097
- package/dist/whimsy/time-bank-smoke.js.map +1 -1
- package/package.json +6 -2
|
@@ -0,0 +1,897 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
process.noDeprecation = true;
|
|
3
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
4
|
+
var __esm = (fn, res) => function __init() {
|
|
5
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
// src/output/formatters.ts
|
|
9
|
+
function formatCurrency(value) {
|
|
10
|
+
if (value >= 1e6) return `$${(value / 1e6).toFixed(1)}M`;
|
|
11
|
+
if (value >= 1e3) return `$${(value / 1e3).toFixed(0)}K`;
|
|
12
|
+
return `$${value.toFixed(0)}`;
|
|
13
|
+
}
|
|
14
|
+
var VITAL_SIGN_LABELS;
|
|
15
|
+
var init_formatters = __esm({
|
|
16
|
+
"src/output/formatters.ts"() {
|
|
17
|
+
"use strict";
|
|
18
|
+
VITAL_SIGN_LABELS = {
|
|
19
|
+
freshness: "Freshness",
|
|
20
|
+
flow_rate: "Flow Rate",
|
|
21
|
+
drop_rate: "Drop Rate",
|
|
22
|
+
signal_to_noise: "Signal:Noise",
|
|
23
|
+
thread_depth: "Thread Depth"
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
// src/services/transcript-smoke.ts
|
|
29
|
+
import { existsSync as existsSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync4 } from "fs";
|
|
30
|
+
import { join as join3 } from "path";
|
|
31
|
+
|
|
32
|
+
// src/services/terminal-capture.ts
|
|
33
|
+
var MAX_LINES_DEFAULT = 2e4;
|
|
34
|
+
var DROP_CHUNK = 500;
|
|
35
|
+
var ANSI_ANY = (
|
|
36
|
+
// eslint-disable-next-line no-control-regex
|
|
37
|
+
/\x1B(?:\[[0-9;?]*[ -/]*[@-~]|\][^\x07\x1B]*(?:\x07|\x1B\\)?|[()][0-9A-Za-z]|[@-Z\\-_=><])/g
|
|
38
|
+
);
|
|
39
|
+
function stripAnsi(value) {
|
|
40
|
+
return value.replace(ANSI_ANY, "").replace(/[\x00-\x08\x0b-\x1f\x7f]/g, "");
|
|
41
|
+
}
|
|
42
|
+
var SECRET_PATTERNS = [
|
|
43
|
+
/\bsk-ant-[A-Za-z0-9_-]{8,}/g,
|
|
44
|
+
// Anthropic
|
|
45
|
+
/\bsk-or-[A-Za-z0-9_-]{8,}/g,
|
|
46
|
+
// OpenRouter
|
|
47
|
+
/\bsk-proj-[A-Za-z0-9_-]{8,}/g,
|
|
48
|
+
// OpenAI project keys
|
|
49
|
+
/\bsk-[A-Za-z0-9_-]{20,}/g,
|
|
50
|
+
// OpenAI / generic sk-
|
|
51
|
+
/\bgsk_[A-Za-z0-9_-]{8,}/g,
|
|
52
|
+
// Groq
|
|
53
|
+
/\bxai-[A-Za-z0-9_-]{8,}/g,
|
|
54
|
+
// xAI
|
|
55
|
+
/\bfw_[A-Za-z0-9_-]{8,}/g,
|
|
56
|
+
// Fireworks
|
|
57
|
+
/\bAIza[A-Za-z0-9_-]{10,}/g,
|
|
58
|
+
// Google
|
|
59
|
+
/\bNTRP-[A-Z0-9][A-Z0-9-]{8,}/g
|
|
60
|
+
// license keys
|
|
61
|
+
];
|
|
62
|
+
function redactSecrets(line) {
|
|
63
|
+
let out = line;
|
|
64
|
+
for (const pattern of SECRET_PATTERNS) {
|
|
65
|
+
out = out.replace(pattern, (m) => `${m.slice(0, 6)}\u2026[redacted]`);
|
|
66
|
+
}
|
|
67
|
+
return out;
|
|
68
|
+
}
|
|
69
|
+
var SCREEN_CLEAR_MARKER = "\u2500\u2500 screen cleared \u2500\u2500";
|
|
70
|
+
var TerminalCapture = class {
|
|
71
|
+
constructor(maxLines = MAX_LINES_DEFAULT) {
|
|
72
|
+
this.maxLines = maxLines;
|
|
73
|
+
}
|
|
74
|
+
maxLines;
|
|
75
|
+
lines = [];
|
|
76
|
+
cur = "";
|
|
77
|
+
col = 0;
|
|
78
|
+
/** Partial escape sequence held across chunk boundaries. */
|
|
79
|
+
carry = "";
|
|
80
|
+
/** A bare \r at a chunk boundary — CRLF vs overwrite is decided by the next char. */
|
|
81
|
+
pendingCr = false;
|
|
82
|
+
dropped = 0;
|
|
83
|
+
/** Feed a raw chunk of terminal output. */
|
|
84
|
+
feed(chunk) {
|
|
85
|
+
const data = this.carry + chunk;
|
|
86
|
+
this.carry = "";
|
|
87
|
+
let i = 0;
|
|
88
|
+
while (i < data.length) {
|
|
89
|
+
const c = data[i];
|
|
90
|
+
if (this.pendingCr) {
|
|
91
|
+
this.pendingCr = false;
|
|
92
|
+
if (c === "\n") {
|
|
93
|
+
this.newline();
|
|
94
|
+
i++;
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
this.col = 0;
|
|
98
|
+
}
|
|
99
|
+
if (c === "\n") {
|
|
100
|
+
this.newline();
|
|
101
|
+
i++;
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
if (c === "\r") {
|
|
105
|
+
this.pendingCr = true;
|
|
106
|
+
i++;
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
if (c === "\x1B") {
|
|
110
|
+
const consumed = this.consumeEscape(data, i);
|
|
111
|
+
if (consumed === -1) {
|
|
112
|
+
this.carry = data.slice(i);
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
i += consumed;
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
if (c === "\b") {
|
|
119
|
+
this.col = Math.max(0, this.col - 1);
|
|
120
|
+
i++;
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
123
|
+
if (c === " ") {
|
|
124
|
+
const next = Math.floor(this.col / 8) * 8 + 8;
|
|
125
|
+
while (this.col < next) this.writeChar(" ");
|
|
126
|
+
i++;
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
if (c < " " || c === "\x7F") {
|
|
130
|
+
i++;
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
this.writeChar(c);
|
|
134
|
+
i++;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
/** Append a standalone line (operator input markers, section notes). */
|
|
138
|
+
note(line) {
|
|
139
|
+
this.commit(line);
|
|
140
|
+
}
|
|
141
|
+
/** Committed lines + the in-progress line (e.g. a live spinner row). */
|
|
142
|
+
snapshot() {
|
|
143
|
+
const out = [...this.lines];
|
|
144
|
+
if (this.cur.trim().length > 0) out.push(this.cur.trimEnd());
|
|
145
|
+
return out;
|
|
146
|
+
}
|
|
147
|
+
/** Lines evicted from the front once maxLines was exceeded. */
|
|
148
|
+
get droppedLineCount() {
|
|
149
|
+
return this.dropped;
|
|
150
|
+
}
|
|
151
|
+
// ----------------------------------------------------------
|
|
152
|
+
writeChar(c) {
|
|
153
|
+
if (this.col < this.cur.length) {
|
|
154
|
+
this.cur = this.cur.slice(0, this.col) + c + this.cur.slice(this.col + 1);
|
|
155
|
+
} else {
|
|
156
|
+
this.cur = this.cur.padEnd(this.col, " ") + c;
|
|
157
|
+
}
|
|
158
|
+
this.col++;
|
|
159
|
+
}
|
|
160
|
+
newline() {
|
|
161
|
+
this.commit(this.cur.trimEnd());
|
|
162
|
+
this.cur = "";
|
|
163
|
+
this.col = 0;
|
|
164
|
+
}
|
|
165
|
+
commit(line) {
|
|
166
|
+
this.lines.push(line);
|
|
167
|
+
if (this.lines.length > this.maxLines) {
|
|
168
|
+
this.lines.splice(0, DROP_CHUNK);
|
|
169
|
+
this.dropped += DROP_CHUNK;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Consume one escape sequence starting at data[start] (which is ESC).
|
|
174
|
+
* Returns the number of chars consumed, or -1 if the sequence is
|
|
175
|
+
* incomplete at the end of the chunk.
|
|
176
|
+
*/
|
|
177
|
+
consumeEscape(data, start) {
|
|
178
|
+
if (start + 1 >= data.length) return -1;
|
|
179
|
+
const kind = data[start + 1];
|
|
180
|
+
if (kind === "[") {
|
|
181
|
+
let i = start + 2;
|
|
182
|
+
while (i < data.length && /[0-9;?]/.test(data[i])) i++;
|
|
183
|
+
while (i < data.length && data[i] >= " " && data[i] <= "/") i++;
|
|
184
|
+
if (i >= data.length) return -1;
|
|
185
|
+
const final = data[i];
|
|
186
|
+
const params = data.slice(start + 2, i).replace(/[?]/g, "");
|
|
187
|
+
this.applyCsi(params, final);
|
|
188
|
+
return i - start + 1;
|
|
189
|
+
}
|
|
190
|
+
if (kind === "]") {
|
|
191
|
+
let i = start + 2;
|
|
192
|
+
while (i < data.length) {
|
|
193
|
+
if (data[i] === "\x07") return i - start + 1;
|
|
194
|
+
if (data[i] === "\x1B" && data[i + 1] === "\\") return i - start + 2;
|
|
195
|
+
i++;
|
|
196
|
+
}
|
|
197
|
+
return -1;
|
|
198
|
+
}
|
|
199
|
+
if (kind === "(" || kind === ")") {
|
|
200
|
+
if (start + 2 >= data.length) return -1;
|
|
201
|
+
return 3;
|
|
202
|
+
}
|
|
203
|
+
return 2;
|
|
204
|
+
}
|
|
205
|
+
applyCsi(params, final) {
|
|
206
|
+
const first = Number.parseInt(params.split(";")[0] ?? "", 10);
|
|
207
|
+
const n = Number.isFinite(first) ? first : void 0;
|
|
208
|
+
switch (final) {
|
|
209
|
+
case "K":
|
|
210
|
+
if (n === 2) {
|
|
211
|
+
this.cur = "";
|
|
212
|
+
} else if (n === 1) {
|
|
213
|
+
const keep = this.cur.slice(this.col);
|
|
214
|
+
this.cur = " ".repeat(Math.min(this.col, this.cur.length)) + keep;
|
|
215
|
+
} else {
|
|
216
|
+
this.cur = this.cur.slice(0, this.col);
|
|
217
|
+
}
|
|
218
|
+
break;
|
|
219
|
+
case "G":
|
|
220
|
+
this.col = Math.max(0, (n ?? 1) - 1);
|
|
221
|
+
break;
|
|
222
|
+
case "J":
|
|
223
|
+
if (n === 2 || n === 3) {
|
|
224
|
+
if (this.cur.trim().length > 0) this.commit(this.cur.trimEnd());
|
|
225
|
+
this.commit(SCREEN_CLEAR_MARKER);
|
|
226
|
+
this.cur = "";
|
|
227
|
+
this.col = 0;
|
|
228
|
+
} else {
|
|
229
|
+
this.cur = this.cur.slice(0, this.col);
|
|
230
|
+
}
|
|
231
|
+
break;
|
|
232
|
+
case "C":
|
|
233
|
+
this.col += n ?? 1;
|
|
234
|
+
break;
|
|
235
|
+
case "D":
|
|
236
|
+
this.col = Math.max(0, this.col - (n ?? 1));
|
|
237
|
+
break;
|
|
238
|
+
case "E":
|
|
239
|
+
// next line
|
|
240
|
+
case "F":
|
|
241
|
+
this.col = 0;
|
|
242
|
+
break;
|
|
243
|
+
case "H":
|
|
244
|
+
// cursor home (row ignored — single-row model)
|
|
245
|
+
case "f":
|
|
246
|
+
this.col = 0;
|
|
247
|
+
break;
|
|
248
|
+
default:
|
|
249
|
+
break;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
};
|
|
253
|
+
|
|
254
|
+
// src/services/transcript.ts
|
|
255
|
+
import { existsSync as existsSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync3, rmSync as rmSync2 } from "fs";
|
|
256
|
+
import { join as join2 } from "path";
|
|
257
|
+
|
|
258
|
+
// src/cli/context.ts
|
|
259
|
+
import { basename, join, resolve, sep } from "path";
|
|
260
|
+
import { existsSync, mkdirSync, writeFileSync as writeFileSync2, readFileSync, readdirSync, statSync, rmSync } from "fs";
|
|
261
|
+
import { homedir } from "os";
|
|
262
|
+
import { randomUUID } from "crypto";
|
|
263
|
+
|
|
264
|
+
// src/services/context-doc.ts
|
|
265
|
+
import { writeFileSync } from "fs";
|
|
266
|
+
init_formatters();
|
|
267
|
+
var AGENT_EXCERPT_CHARS = 400;
|
|
268
|
+
function buildSessionContextDoc(file, opts = {}) {
|
|
269
|
+
const id = file.id;
|
|
270
|
+
const shortId = id.slice(-4);
|
|
271
|
+
const exchanges = file.exchange_count ?? Math.floor(file.messages.length / 2);
|
|
272
|
+
const lines = [];
|
|
273
|
+
lines.push(`# Session context \u2014 ${id}${file.name ? ` (${file.name})` : ""}`);
|
|
274
|
+
lines.push("");
|
|
275
|
+
lines.push("## Status");
|
|
276
|
+
lines.push("");
|
|
277
|
+
lines.push(`- Stage: ${file.stage ?? "new"}`);
|
|
278
|
+
lines.push(`- Created: ${file.created_at}`);
|
|
279
|
+
if (file.ended_at) lines.push(`- Ended: ${file.ended_at}`);
|
|
280
|
+
lines.push(`- Updated: ${(/* @__PURE__ */ new Date()).toISOString()}`);
|
|
281
|
+
lines.push(`- Exchanges: ${exchanges}`);
|
|
282
|
+
if (file.summary) lines.push(`- Summary: ${file.summary}`);
|
|
283
|
+
if (file.resumed_from) lines.push(`- Resumed from: ${file.resumed_from}`);
|
|
284
|
+
lines.push("");
|
|
285
|
+
lines.push("## Dataset");
|
|
286
|
+
lines.push("");
|
|
287
|
+
if (file.dataset?.label || file.dataset?.source) {
|
|
288
|
+
lines.push(`- Label: ${file.dataset.label ?? "(unlabeled)"}`);
|
|
289
|
+
if (file.dataset.source) lines.push(`- Source: ${file.dataset.source}`);
|
|
290
|
+
if (file.dataset.ingested_at) lines.push(`- Ingested: ${file.dataset.ingested_at}`);
|
|
291
|
+
const counts = Object.entries(file.dataset.counts ?? {}).filter(([, n]) => n > 0);
|
|
292
|
+
if (counts.length > 0) {
|
|
293
|
+
lines.push(`- Counts: ${counts.map(([k, n]) => `${n.toLocaleString()} ${k}`).join(", ")}`);
|
|
294
|
+
}
|
|
295
|
+
} else {
|
|
296
|
+
lines.push("- No data loaded.");
|
|
297
|
+
}
|
|
298
|
+
if (file.attachments && file.attachments.length > 0) {
|
|
299
|
+
for (const a of file.attachments) {
|
|
300
|
+
const detail = [a.entity_type, a.row_count != null ? `${a.row_count} rows` : null].filter(Boolean).join(", ");
|
|
301
|
+
lines.push(`- Attachment: ${a.path}${detail ? ` (${detail})` : ""}`);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
lines.push("");
|
|
305
|
+
if (file.scope) {
|
|
306
|
+
lines.push("## Scope");
|
|
307
|
+
lines.push("");
|
|
308
|
+
lines.push(`- Intent: ${file.scope.intent_summary}`);
|
|
309
|
+
lines.push(`- Lens: ${file.scope.primary_lens}`);
|
|
310
|
+
if (file.scope.audience) lines.push(`- Audience: ${file.scope.audience}`);
|
|
311
|
+
if (file.scope.time_horizon) lines.push(`- Time horizon: ${file.scope.time_horizon}`);
|
|
312
|
+
if (file.scope.segments?.length) lines.push(`- Segments: ${file.scope.segments.join(", ")}`);
|
|
313
|
+
if (file.scope.confirmed_at) lines.push(`- Confirmed: ${file.scope.confirmed_at}`);
|
|
314
|
+
lines.push("");
|
|
315
|
+
}
|
|
316
|
+
lines.push("## Analysis");
|
|
317
|
+
lines.push("");
|
|
318
|
+
if (file.analysis) {
|
|
319
|
+
lines.push(`- Primary lens: ${file.analysis.primary}`);
|
|
320
|
+
lines.push(`- Completed: ${file.analysis.completed.join(", ") || "none"}`);
|
|
321
|
+
if (file.analysis.coverage) {
|
|
322
|
+
lines.push(
|
|
323
|
+
`- Coverage: ${file.analysis.coverage.distinct_months} months \xB7 recommended cadence ${file.analysis.coverage.recommended_cadence}`
|
|
324
|
+
);
|
|
325
|
+
}
|
|
326
|
+
if (file.analysis.data_source_type) {
|
|
327
|
+
lines.push(`- Data source type: ${file.analysis.data_source_type}`);
|
|
328
|
+
}
|
|
329
|
+
if (file.analysis.headline?.length) {
|
|
330
|
+
lines.push("");
|
|
331
|
+
lines.push("### Headline metrics");
|
|
332
|
+
lines.push("");
|
|
333
|
+
for (const h of file.analysis.headline) {
|
|
334
|
+
lines.push(`- ${h.label}: ${h.formatted}`);
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
} else {
|
|
338
|
+
lines.push("- No analysis recorded.");
|
|
339
|
+
}
|
|
340
|
+
const health = opts.snapshot?.aggregate;
|
|
341
|
+
if (health) {
|
|
342
|
+
lines.push("");
|
|
343
|
+
lines.push("### GTM health snapshot");
|
|
344
|
+
lines.push("");
|
|
345
|
+
lines.push(`- Overall: ${Math.round(health.overall_score)} (${health.overall_status})`);
|
|
346
|
+
lines.push(`- Gating vital sign: ${health.gating_vital_sign.replace(/_/g, " ")}`);
|
|
347
|
+
if (health.total_value_at_risk != null && health.total_value_at_risk > 0) {
|
|
348
|
+
lines.push(`- Total value at risk: ${formatCurrency(health.total_value_at_risk)}`);
|
|
349
|
+
}
|
|
350
|
+
for (const vs of health.vital_signs) {
|
|
351
|
+
const label = VITAL_SIGN_LABELS[vs.vital_sign] ?? vs.vital_sign;
|
|
352
|
+
const dollars = vs.dollar_value != null ? ` \u2014 ${formatCurrency(vs.dollar_value)}${vs.dollar_label ? ` ${vs.dollar_label}` : ""}` : "";
|
|
353
|
+
lines.push(`- ${label}: ${Math.round(vs.score)} (${vs.status})${dollars}`);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
lines.push("");
|
|
357
|
+
if (file.strategist) {
|
|
358
|
+
lines.push("## Strategist (in flight)");
|
|
359
|
+
lines.push("");
|
|
360
|
+
lines.push(`- Step: ${file.strategist.step}`);
|
|
361
|
+
if (file.strategist.objective) lines.push(`- Objective: ${file.strategist.objective}`);
|
|
362
|
+
if (file.strategist.constraintsNote) {
|
|
363
|
+
lines.push(`- Constraints: ${file.strategist.constraintsNote}`);
|
|
364
|
+
}
|
|
365
|
+
if (file.strategist.origin) lines.push(`- Origin: ${file.strategist.origin}`);
|
|
366
|
+
lines.push("");
|
|
367
|
+
}
|
|
368
|
+
lines.push("## Deliverables");
|
|
369
|
+
lines.push("");
|
|
370
|
+
if (file.deliverables && file.deliverables.length > 0) {
|
|
371
|
+
for (const d of file.deliverables) {
|
|
372
|
+
const detail = [d.path, d.note].filter(Boolean).join(" \u2014 ");
|
|
373
|
+
lines.push(`- ${d.kind} (${d.at})${detail ? `: ${detail}` : ""}`);
|
|
374
|
+
}
|
|
375
|
+
} else {
|
|
376
|
+
lines.push("- None yet.");
|
|
377
|
+
}
|
|
378
|
+
lines.push("");
|
|
379
|
+
lines.push(`## Conversation (${exchanges} exchange${exchanges === 1 ? "" : "s"})`);
|
|
380
|
+
lines.push("");
|
|
381
|
+
if (file.messages.length === 0) {
|
|
382
|
+
lines.push("- No exchanges yet.");
|
|
383
|
+
} else {
|
|
384
|
+
let n = 0;
|
|
385
|
+
for (const msg of file.messages) {
|
|
386
|
+
if (msg.role === "user") {
|
|
387
|
+
n++;
|
|
388
|
+
lines.push(`${n}. \u276F ${excerpt(msg.content, AGENT_EXCERPT_CHARS)}`);
|
|
389
|
+
} else {
|
|
390
|
+
lines.push(` \u21B3 ${excerpt(msg.content, AGENT_EXCERPT_CHARS)}`);
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
lines.push("");
|
|
395
|
+
lines.push("## Files");
|
|
396
|
+
lines.push("");
|
|
397
|
+
lines.push(`- Transcript (raw terminal): \`${transcriptPathForSession(id)}\``);
|
|
398
|
+
lines.push(`- Session data (JSON): \`${sessionJsonPath(id)}\``);
|
|
399
|
+
lines.push(`- Dataset (DuckDB): \`${datasetPathForSession(id)}\``);
|
|
400
|
+
lines.push("");
|
|
401
|
+
lines.push("## Pick up this session");
|
|
402
|
+
lines.push("");
|
|
403
|
+
lines.push(`Run \`ntrp\`, then \`/session ${shortId}\` \u2014 rebinds the dataset and reloads the`);
|
|
404
|
+
lines.push("conversation thread in place. Read the transcript above for the full terminal");
|
|
405
|
+
lines.push("history before continuing.");
|
|
406
|
+
lines.push("");
|
|
407
|
+
return lines.map(redactSecrets).join("\n");
|
|
408
|
+
}
|
|
409
|
+
function excerpt(content, max) {
|
|
410
|
+
const flat = content.replace(/\s+/g, " ").trim();
|
|
411
|
+
return flat.length > max ? `${flat.slice(0, max - 1)}\u2026` : flat;
|
|
412
|
+
}
|
|
413
|
+
function sessionJsonPath(id) {
|
|
414
|
+
return `${getSessionsDir()}/${id}.json`;
|
|
415
|
+
}
|
|
416
|
+
function writeSessionContextDoc(ctx) {
|
|
417
|
+
if (ctx.oneShot) return;
|
|
418
|
+
try {
|
|
419
|
+
const file = buildSessionFileSnapshot(ctx);
|
|
420
|
+
const doc2 = buildSessionContextDoc(file, { snapshot: ctx.snapshot.computeResult });
|
|
421
|
+
writeFileSync(contextDocPathForSession(ctx.sessionId), doc2);
|
|
422
|
+
} catch {
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
// src/cli/context.ts
|
|
427
|
+
var STALE_SESSION_MS = 14 * 24 * 60 * 60 * 1e3;
|
|
428
|
+
function ntrpHomeDir() {
|
|
429
|
+
return process.env.NTRP_HOME ? resolve(process.env.NTRP_HOME) : join(homedir(), ".ntrp");
|
|
430
|
+
}
|
|
431
|
+
function getSessionsDir() {
|
|
432
|
+
const dir = join(ntrpHomeDir(), "sessions");
|
|
433
|
+
if (!existsSync(dir)) {
|
|
434
|
+
mkdirSync(dir, { recursive: true });
|
|
435
|
+
}
|
|
436
|
+
return dir;
|
|
437
|
+
}
|
|
438
|
+
function getDatasetsDir() {
|
|
439
|
+
const dir = join(ntrpHomeDir(), "datasets");
|
|
440
|
+
if (!existsSync(dir)) {
|
|
441
|
+
mkdirSync(dir, { recursive: true });
|
|
442
|
+
}
|
|
443
|
+
return dir;
|
|
444
|
+
}
|
|
445
|
+
function datasetPathForSession(id) {
|
|
446
|
+
return join(getDatasetsDir(), `${id}.duckdb`);
|
|
447
|
+
}
|
|
448
|
+
function transcriptPathForSession(id) {
|
|
449
|
+
return join(getSessionsDir(), `${id}.transcript.md`);
|
|
450
|
+
}
|
|
451
|
+
function contextDocPathForSession(id) {
|
|
452
|
+
return join(getSessionsDir(), `${id}.context.md`);
|
|
453
|
+
}
|
|
454
|
+
function buildSessionFileSnapshot(ctx) {
|
|
455
|
+
const file = {
|
|
456
|
+
id: ctx.sessionId,
|
|
457
|
+
created_at: ctx.messages[0]?.at ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
458
|
+
messages: ctx.messages,
|
|
459
|
+
stage: ctx.stage
|
|
460
|
+
};
|
|
461
|
+
if (ctx.sessionName) file.name = ctx.sessionName;
|
|
462
|
+
if (ctx.dataset) file.dataset = ctx.dataset;
|
|
463
|
+
if (ctx.deliverables.length > 0) file.deliverables = ctx.deliverables;
|
|
464
|
+
if (ctx.conversation.length > 0) file.thread = ctx.conversation;
|
|
465
|
+
if (ctx.resumedFromId) file.resumed_from = ctx.resumedFromId;
|
|
466
|
+
if (ctx.analysis) file.analysis = ctx.analysis;
|
|
467
|
+
if (ctx.scope) file.scope = ctx.scope;
|
|
468
|
+
if (ctx.attachments && ctx.attachments.length > 0) file.attachments = ctx.attachments;
|
|
469
|
+
if (ctx.llm && Object.keys(ctx.llm).length > 0) file.llm = ctx.llm;
|
|
470
|
+
if (ctx.strategistState) file.strategist = ctx.strategistState;
|
|
471
|
+
return file;
|
|
472
|
+
}
|
|
473
|
+
function defaultSessionAnalysis(primary = "gtm_health") {
|
|
474
|
+
return { primary, completed: [] };
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
// src/services/transcript.ts
|
|
478
|
+
var FLUSH_THROTTLE_MS = 250;
|
|
479
|
+
var FLUSH_MAX_STALENESS_MS = 900;
|
|
480
|
+
var state = null;
|
|
481
|
+
var originalStdoutWrite = null;
|
|
482
|
+
var originalStderrWrite = null;
|
|
483
|
+
function startSessionTranscript(ctx) {
|
|
484
|
+
if (ctx.oneShot || state) return;
|
|
485
|
+
installTees();
|
|
486
|
+
state = createState(ctx.sessionId);
|
|
487
|
+
flushNow();
|
|
488
|
+
}
|
|
489
|
+
function rebindSessionTranscript(ctx) {
|
|
490
|
+
if (!state || state.sessionId === ctx.sessionId) return;
|
|
491
|
+
const priorJson = join2(getSessionsDir(), `${state.sessionId}.json`);
|
|
492
|
+
if (existsSync2(priorJson)) {
|
|
493
|
+
finalizeCurrentFile("switched session");
|
|
494
|
+
} else {
|
|
495
|
+
discardSessionTranscript(state.sessionId);
|
|
496
|
+
}
|
|
497
|
+
state = createState(ctx.sessionId);
|
|
498
|
+
flushNow();
|
|
499
|
+
}
|
|
500
|
+
function stopSessionTranscript() {
|
|
501
|
+
if (!state) return;
|
|
502
|
+
finalizeCurrentFile("session closed");
|
|
503
|
+
state = null;
|
|
504
|
+
removeTees();
|
|
505
|
+
}
|
|
506
|
+
function discardSessionTranscript(sessionId) {
|
|
507
|
+
if (state && state.sessionId === sessionId) {
|
|
508
|
+
state.discarded = true;
|
|
509
|
+
clearFlushTimer();
|
|
510
|
+
}
|
|
511
|
+
try {
|
|
512
|
+
rmSync2(transcriptPathForSession(sessionId), { force: true });
|
|
513
|
+
} catch {
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
function pauseTranscriptCapture() {
|
|
517
|
+
if (state) state.paused = true;
|
|
518
|
+
}
|
|
519
|
+
function resumeTranscriptCapture() {
|
|
520
|
+
if (state) state.paused = false;
|
|
521
|
+
}
|
|
522
|
+
function noteTranscriptInput(promptLabel, input) {
|
|
523
|
+
if (!state || state.discarded) return;
|
|
524
|
+
state.capture.note("");
|
|
525
|
+
state.capture.note(`\u276F ${stripAnsi(promptLabel)}${input}`.trimEnd());
|
|
526
|
+
flushNow();
|
|
527
|
+
}
|
|
528
|
+
function isTranscriptActive(sessionId) {
|
|
529
|
+
if (!state || state.discarded) return false;
|
|
530
|
+
return sessionId === void 0 || state.sessionId === sessionId;
|
|
531
|
+
}
|
|
532
|
+
function installTees() {
|
|
533
|
+
if (originalStdoutWrite) return;
|
|
534
|
+
originalStdoutWrite = process.stdout.write.bind(process.stdout);
|
|
535
|
+
originalStderrWrite = process.stderr.write.bind(process.stderr);
|
|
536
|
+
const tee = (original) => (
|
|
537
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
538
|
+
((chunk, encoding, callback) => {
|
|
539
|
+
try {
|
|
540
|
+
if (state && !state.paused && !state.discarded) {
|
|
541
|
+
const text = typeof chunk === "string" ? chunk : Buffer.isBuffer(chunk) ? chunk.toString("utf-8") : String(chunk);
|
|
542
|
+
state.capture.feed(text);
|
|
543
|
+
scheduleFlush();
|
|
544
|
+
}
|
|
545
|
+
} catch {
|
|
546
|
+
}
|
|
547
|
+
return original(chunk, encoding, callback);
|
|
548
|
+
})
|
|
549
|
+
);
|
|
550
|
+
process.stdout.write = tee(originalStdoutWrite);
|
|
551
|
+
process.stderr.write = tee(originalStderrWrite);
|
|
552
|
+
}
|
|
553
|
+
function removeTees() {
|
|
554
|
+
if (originalStdoutWrite) {
|
|
555
|
+
process.stdout.write = originalStdoutWrite;
|
|
556
|
+
originalStdoutWrite = null;
|
|
557
|
+
}
|
|
558
|
+
if (originalStderrWrite) {
|
|
559
|
+
process.stderr.write = originalStderrWrite;
|
|
560
|
+
originalStderrWrite = null;
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
function createState(sessionId) {
|
|
564
|
+
const filePath = transcriptPathForSession(sessionId);
|
|
565
|
+
let base = "";
|
|
566
|
+
if (existsSync2(filePath)) {
|
|
567
|
+
try {
|
|
568
|
+
base = readFileSync2(filePath, "utf-8").trimEnd() + "\n";
|
|
569
|
+
} catch {
|
|
570
|
+
base = "";
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
return {
|
|
574
|
+
sessionId,
|
|
575
|
+
filePath,
|
|
576
|
+
base,
|
|
577
|
+
segmentStartedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
578
|
+
capture: new TerminalCapture(),
|
|
579
|
+
paused: false,
|
|
580
|
+
discarded: false,
|
|
581
|
+
lastFlushMs: 0,
|
|
582
|
+
flushTimer: null
|
|
583
|
+
};
|
|
584
|
+
}
|
|
585
|
+
function renderHeader(sessionId) {
|
|
586
|
+
return [
|
|
587
|
+
`# ntrp transcript \u2014 ${sessionId}`,
|
|
588
|
+
"",
|
|
589
|
+
`- Session data: \`${sessionId}.json\` \xB7 Context brief: \`${sessionId}.context.md\``,
|
|
590
|
+
"- Raw terminal text (ANSI stripped, spinner frames collapsed). Lines starting with `\u276F` are operator input.",
|
|
591
|
+
"",
|
|
592
|
+
""
|
|
593
|
+
].join("\n");
|
|
594
|
+
}
|
|
595
|
+
function renderSegment(s, closedNote) {
|
|
596
|
+
const lines = s.capture.snapshot().map(redactSecrets);
|
|
597
|
+
const dropped = s.capture.droppedLineCount;
|
|
598
|
+
let longestRun = 0;
|
|
599
|
+
for (const line of lines) {
|
|
600
|
+
for (const match of line.matchAll(/`+/g)) {
|
|
601
|
+
if (match[0].length > longestRun) longestRun = match[0].length;
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
const fence = "`".repeat(Math.max(3, longestRun + 1));
|
|
605
|
+
const heading = s.base ? `## Continued \u2014 ${s.segmentStartedAt}` : `## Session start \u2014 ${s.segmentStartedAt}`;
|
|
606
|
+
const parts = [heading, ""];
|
|
607
|
+
if (dropped > 0) {
|
|
608
|
+
parts.push(`_(${dropped.toLocaleString()} earlier lines dropped to bound file size)_`, "");
|
|
609
|
+
}
|
|
610
|
+
parts.push(`${fence}text`, ...lines, fence, "");
|
|
611
|
+
parts.push(
|
|
612
|
+
closedNote ? `_Closed: ${(/* @__PURE__ */ new Date()).toISOString()} (${closedNote})_` : `_Last write: ${(/* @__PURE__ */ new Date()).toISOString()}_`
|
|
613
|
+
);
|
|
614
|
+
parts.push("");
|
|
615
|
+
return parts.join("\n");
|
|
616
|
+
}
|
|
617
|
+
function render(s, closedNote) {
|
|
618
|
+
const prefix = s.base ? s.base + "\n" : renderHeader(s.sessionId);
|
|
619
|
+
return prefix + renderSegment(s, closedNote);
|
|
620
|
+
}
|
|
621
|
+
function flushNow(closedNote) {
|
|
622
|
+
const s = state;
|
|
623
|
+
if (!s || s.discarded) return;
|
|
624
|
+
clearFlushTimer();
|
|
625
|
+
s.lastFlushMs = Date.now();
|
|
626
|
+
try {
|
|
627
|
+
getSessionsDir();
|
|
628
|
+
writeFileSync3(s.filePath, render(s, closedNote));
|
|
629
|
+
} catch {
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
function scheduleFlush() {
|
|
633
|
+
const s = state;
|
|
634
|
+
if (!s || s.discarded) return;
|
|
635
|
+
if (Date.now() - s.lastFlushMs >= FLUSH_MAX_STALENESS_MS) {
|
|
636
|
+
flushNow();
|
|
637
|
+
return;
|
|
638
|
+
}
|
|
639
|
+
if (s.flushTimer) return;
|
|
640
|
+
s.flushTimer = setTimeout(() => {
|
|
641
|
+
if (state) state.flushTimer = null;
|
|
642
|
+
flushNow();
|
|
643
|
+
}, FLUSH_THROTTLE_MS);
|
|
644
|
+
s.flushTimer.unref?.();
|
|
645
|
+
}
|
|
646
|
+
function clearFlushTimer() {
|
|
647
|
+
if (state?.flushTimer) {
|
|
648
|
+
clearTimeout(state.flushTimer);
|
|
649
|
+
state.flushTimer = null;
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
function finalizeCurrentFile(reason) {
|
|
653
|
+
if (!state) return;
|
|
654
|
+
clearFlushTimer();
|
|
655
|
+
if (!state.discarded) flushNow(reason);
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
// src/services/transcript-smoke.ts
|
|
659
|
+
var failures = [];
|
|
660
|
+
function assert(cond, msg) {
|
|
661
|
+
if (!cond) failures.push(msg);
|
|
662
|
+
}
|
|
663
|
+
{
|
|
664
|
+
const cap = new TerminalCapture();
|
|
665
|
+
cap.feed("plain line\n");
|
|
666
|
+
cap.feed("\x1B[31mcolored\x1B[39m line\n");
|
|
667
|
+
assert(cap.snapshot()[0] === "plain line", "plain line committed");
|
|
668
|
+
assert(cap.snapshot()[1] === "colored line", "SGR color codes removed");
|
|
669
|
+
}
|
|
670
|
+
{
|
|
671
|
+
const cap = new TerminalCapture();
|
|
672
|
+
cap.feed("\u280B Computing vitals\u2026");
|
|
673
|
+
cap.feed("\x1B[1G\x1B[2K\u2819 Computing vitals\u2026");
|
|
674
|
+
cap.feed("\x1B[1G\x1B[2K\u2714 Vitals computed\n");
|
|
675
|
+
const lines = cap.snapshot();
|
|
676
|
+
assert(lines.length === 1 && lines[0] === "\u2714 Vitals computed", `spinner frames collapse (got ${JSON.stringify(lines)})`);
|
|
677
|
+
}
|
|
678
|
+
{
|
|
679
|
+
const cap = new TerminalCapture();
|
|
680
|
+
cap.feed("abc\r\n");
|
|
681
|
+
cap.feed("abc\rxyz\n");
|
|
682
|
+
const lines = cap.snapshot();
|
|
683
|
+
assert(lines[0] === "abc", "CRLF commits the line unchanged");
|
|
684
|
+
assert(lines[1] === "xyz", "bare CR overwrites from column 0");
|
|
685
|
+
}
|
|
686
|
+
{
|
|
687
|
+
const cap = new TerminalCapture();
|
|
688
|
+
cap.feed("abc\r");
|
|
689
|
+
cap.feed("\n");
|
|
690
|
+
cap.feed("def\r");
|
|
691
|
+
cap.feed("z\n");
|
|
692
|
+
const lines = cap.snapshot();
|
|
693
|
+
assert(lines[0] === "abc", "chunk-split CRLF still one line");
|
|
694
|
+
assert(lines[1] === "zef", "chunk-split CR overwrite applies at col 0");
|
|
695
|
+
}
|
|
696
|
+
{
|
|
697
|
+
const cap = new TerminalCapture();
|
|
698
|
+
cap.feed("temp");
|
|
699
|
+
cap.feed("\x1B[");
|
|
700
|
+
cap.feed("2K\x1B[1Gfinal\n");
|
|
701
|
+
assert(cap.snapshot()[0] === "final", "chunk-split CSI erase applied");
|
|
702
|
+
}
|
|
703
|
+
{
|
|
704
|
+
const cap = new TerminalCapture();
|
|
705
|
+
cap.feed("abcdef\x1B[3G\x1B[0K123\n");
|
|
706
|
+
assert(cap.snapshot()[0] === "ab123", "cursor-column + erase-right repaint");
|
|
707
|
+
}
|
|
708
|
+
{
|
|
709
|
+
const cap = new TerminalCapture();
|
|
710
|
+
cap.feed("abcd\b\b \n");
|
|
711
|
+
assert(cap.snapshot()[0] === "ab", "backspace + space erase");
|
|
712
|
+
}
|
|
713
|
+
{
|
|
714
|
+
const cap = new TerminalCapture();
|
|
715
|
+
cap.feed("before\n\x1B[2J\x1B[Hafter\n");
|
|
716
|
+
const lines = cap.snapshot();
|
|
717
|
+
assert(lines[0] === "before", "history preserved across screen clear");
|
|
718
|
+
assert(lines[1] === SCREEN_CLEAR_MARKER, "screen clear marker inserted");
|
|
719
|
+
assert(lines[2] === "after", "output continues after clear");
|
|
720
|
+
}
|
|
721
|
+
{
|
|
722
|
+
const cap = new TerminalCapture();
|
|
723
|
+
cap.feed("\x1B]0;ntrp\x07hello\x07 world");
|
|
724
|
+
const lines = cap.snapshot();
|
|
725
|
+
assert(lines[0] === "hello world", "OSC + BEL dropped, live line in snapshot");
|
|
726
|
+
cap.note("\u276F ntrp \u203A use demo data");
|
|
727
|
+
assert(cap.snapshot().includes("\u276F ntrp \u203A use demo data"), "note() appends standalone line");
|
|
728
|
+
}
|
|
729
|
+
{
|
|
730
|
+
const cap = new TerminalCapture(600);
|
|
731
|
+
for (let i = 0; i < 1200; i++) cap.feed(`line ${i}
|
|
732
|
+
`);
|
|
733
|
+
assert(cap.droppedLineCount > 0, "old lines dropped past cap");
|
|
734
|
+
assert(cap.snapshot().length <= 700, "snapshot bounded");
|
|
735
|
+
}
|
|
736
|
+
assert(stripAnsi("\x1B[2mdim\x1B[22m \x1B]0;t\x07x") === "dim x", "stripAnsi removes CSI + OSC");
|
|
737
|
+
assert(
|
|
738
|
+
redactSecrets("key sk-ant-api03-abcdefghijklmnop end").includes("sk-ant\u2026[redacted]"),
|
|
739
|
+
"anthropic key redacted"
|
|
740
|
+
);
|
|
741
|
+
assert(!redactSecrets("gsk_abcdefghijklmnop").includes("abcdefghijklmnop"), "groq key redacted");
|
|
742
|
+
assert(
|
|
743
|
+
redactSecrets("NTRP-AAAA-BBBB-CCCC-DDDD").includes("NTRP-A\u2026[redacted]"),
|
|
744
|
+
"license key redacted"
|
|
745
|
+
);
|
|
746
|
+
assert(redactSecrets("normal text $3.1M at risk") === "normal text $3.1M at risk", "plain text untouched");
|
|
747
|
+
var sessionFile = {
|
|
748
|
+
id: "2026-07-29-ab12",
|
|
749
|
+
created_at: "2026-07-29T10:00:00.000Z",
|
|
750
|
+
stage: "analyzed",
|
|
751
|
+
name: "board-prep",
|
|
752
|
+
dataset: {
|
|
753
|
+
label: "hidden_crisis demo",
|
|
754
|
+
source: "demo:hidden_crisis",
|
|
755
|
+
counts: { contacts: 4200, opportunities: 310 },
|
|
756
|
+
ingested_at: "2026-07-29T10:01:00.000Z"
|
|
757
|
+
},
|
|
758
|
+
scope: {
|
|
759
|
+
intent_summary: "Is our retention real for the board?",
|
|
760
|
+
primary_lens: "revenue_metrics",
|
|
761
|
+
audience: "board"
|
|
762
|
+
},
|
|
763
|
+
analysis: {
|
|
764
|
+
primary: "revenue_metrics",
|
|
765
|
+
completed: ["revenue_metrics"],
|
|
766
|
+
headline: [
|
|
767
|
+
{ metric: "arr", label: "ARR", formatted: "$8.5M" },
|
|
768
|
+
{ metric: "nrr", label: "Net Revenue Retention", formatted: "95%" },
|
|
769
|
+
{ metric: "grr", label: "Gross Revenue Retention", formatted: "76%" }
|
|
770
|
+
]
|
|
771
|
+
},
|
|
772
|
+
deliverables: [{ kind: "board_deck_prompt", at: "2026-07-29T10:30:00.000Z", path: "/tmp/deck.md" }],
|
|
773
|
+
messages: [
|
|
774
|
+
{ role: "user", content: "is our retention real for the board?", at: "2026-07-29T10:02:00.000Z" },
|
|
775
|
+
{ role: "agent", content: "Scope confirmed. Data check complete.", at: "2026-07-29T10:02:05.000Z" },
|
|
776
|
+
{ role: "user", content: "what is ARR?", at: "2026-07-29T10:03:00.000Z" },
|
|
777
|
+
{ role: "agent", content: "ARR is $12.4M based on the latest close history. ".repeat(30), at: "2026-07-29T10:03:10.000Z" }
|
|
778
|
+
],
|
|
779
|
+
exchange_count: 2
|
|
780
|
+
};
|
|
781
|
+
var snapshot = {
|
|
782
|
+
aggregate: {
|
|
783
|
+
overall_score: 42,
|
|
784
|
+
overall_status: "red",
|
|
785
|
+
gating_vital_sign: "freshness",
|
|
786
|
+
total_value_at_risk: 31e5,
|
|
787
|
+
vital_signs: [
|
|
788
|
+
{
|
|
789
|
+
vital_sign: "freshness",
|
|
790
|
+
score: 29,
|
|
791
|
+
status: "red",
|
|
792
|
+
components: {},
|
|
793
|
+
entity_details: [],
|
|
794
|
+
dollar_value: 31e5,
|
|
795
|
+
dollar_label: "pipeline at risk"
|
|
796
|
+
}
|
|
797
|
+
]
|
|
798
|
+
},
|
|
799
|
+
segments: []
|
|
800
|
+
};
|
|
801
|
+
var doc = buildSessionContextDoc(sessionFile, { snapshot });
|
|
802
|
+
assert(doc.includes("# Session context \u2014 2026-07-29-ab12 (board-prep)"), "context doc title + name");
|
|
803
|
+
assert(doc.includes("- Stage: analyzed"), "context doc stage");
|
|
804
|
+
assert(doc.includes("hidden_crisis demo"), "context doc dataset label");
|
|
805
|
+
assert(doc.includes("4,200 contacts"), "context doc entity counts");
|
|
806
|
+
assert(doc.includes("Is our retention real for the board?"), "context doc scope intent");
|
|
807
|
+
assert(doc.includes("- Gating vital sign: freshness"), "context doc gating vital");
|
|
808
|
+
assert(doc.includes("$3.1M pipeline at risk"), "context doc dollar translation");
|
|
809
|
+
assert(doc.includes("### Headline metrics"), "context doc headline section");
|
|
810
|
+
assert(doc.includes("- ARR: $8.5M"), "context doc headline ARR value");
|
|
811
|
+
assert(doc.includes("- Net Revenue Retention: 95%"), "context doc headline NRR value");
|
|
812
|
+
assert(doc.includes("board_deck_prompt"), "context doc deliverable");
|
|
813
|
+
assert(doc.includes("\u276F what is ARR?"), "context doc user question");
|
|
814
|
+
assert(doc.includes("\u2026"), "context doc long agent answer truncated");
|
|
815
|
+
assert(doc.includes("/session ab12"), "context doc pickup hint");
|
|
816
|
+
assert(doc.includes(".transcript.md"), "context doc links transcript");
|
|
817
|
+
function makeCtx(sessionId) {
|
|
818
|
+
return {
|
|
819
|
+
sessionId,
|
|
820
|
+
sessionFile: `${sessionId}.json`,
|
|
821
|
+
oneShot: false,
|
|
822
|
+
execution: { mode: "interactive", output: "terminal", progress: true, color: false, strictStdout: false, quiet: false },
|
|
823
|
+
snapshot: { computeResult: null, divergences: [] },
|
|
824
|
+
messages: [],
|
|
825
|
+
conversation: [],
|
|
826
|
+
stage: "new",
|
|
827
|
+
deliverables: [],
|
|
828
|
+
analysis: defaultSessionAnalysis(),
|
|
829
|
+
wizardDepth: 0,
|
|
830
|
+
attachments: []
|
|
831
|
+
};
|
|
832
|
+
}
|
|
833
|
+
var ctxA = makeCtx("2026-07-29-aaaa");
|
|
834
|
+
var ctxB = makeCtx("2026-07-29-bbbb");
|
|
835
|
+
startSessionTranscript(ctxA);
|
|
836
|
+
assert(isTranscriptActive(ctxA.sessionId), "recorder active for session A");
|
|
837
|
+
console.log("hello from session A");
|
|
838
|
+
console.error("stderr also captured");
|
|
839
|
+
console.log("inline key sk-ant-api03-abcdefghijklmnop here");
|
|
840
|
+
pauseTranscriptCapture();
|
|
841
|
+
console.log("PROMPT-ECHO-NOISE-SHOULD-NOT-APPEAR");
|
|
842
|
+
resumeTranscriptCapture();
|
|
843
|
+
noteTranscriptInput("ntrp \u203A ", "use demo data");
|
|
844
|
+
writeFileSync4(join3(getSessionsDir(), `${ctxA.sessionId}.json`), "{}\n");
|
|
845
|
+
writeFileSync4(join3(getSessionsDir(), `${ctxB.sessionId}.json`), "{}\n");
|
|
846
|
+
rebindSessionTranscript(ctxB);
|
|
847
|
+
console.log("hello from session B");
|
|
848
|
+
stopSessionTranscript();
|
|
849
|
+
var fileA = readFileSync3(transcriptPathForSession(ctxA.sessionId), "utf-8");
|
|
850
|
+
var fileB = readFileSync3(transcriptPathForSession(ctxB.sessionId), "utf-8");
|
|
851
|
+
assert(fileA.includes("# ntrp transcript \u2014 2026-07-29-aaaa"), "transcript A header");
|
|
852
|
+
assert(fileA.includes("hello from session A"), "stdout captured in A");
|
|
853
|
+
assert(fileA.includes("stderr also captured"), "stderr captured in A");
|
|
854
|
+
assert(fileA.includes("sk-ant\u2026[redacted]"), "inline key redacted in transcript");
|
|
855
|
+
assert(!fileA.includes("sk-ant-api03-abcdefghijklmnop"), "raw key absent from transcript");
|
|
856
|
+
assert(!fileA.includes("PROMPT-ECHO-NOISE-SHOULD-NOT-APPEAR"), "paused output not captured");
|
|
857
|
+
assert(fileA.includes("\u276F ntrp \u203A use demo data"), "input marker recorded with prompt label");
|
|
858
|
+
assert(fileA.includes("(switched session)"), "A closed with switch note");
|
|
859
|
+
assert(!fileA.includes("hello from session B"), "B output not in A");
|
|
860
|
+
assert(fileB.includes("hello from session B"), "B transcript captured after rebind");
|
|
861
|
+
assert(fileB.includes("(session closed)"), "B closed on stop");
|
|
862
|
+
startSessionTranscript(ctxA);
|
|
863
|
+
console.log("picked this back up");
|
|
864
|
+
stopSessionTranscript();
|
|
865
|
+
var fileA2 = readFileSync3(transcriptPathForSession(ctxA.sessionId), "utf-8");
|
|
866
|
+
assert(fileA2.includes("hello from session A"), "continue keeps prior history");
|
|
867
|
+
assert(fileA2.includes("## Continued \u2014 "), "continue adds Continued segment");
|
|
868
|
+
assert(fileA2.includes("picked this back up"), "continue captures new output");
|
|
869
|
+
var ctxC = makeCtx("2026-07-29-cccc");
|
|
870
|
+
startSessionTranscript(ctxC);
|
|
871
|
+
console.log("ephemeral");
|
|
872
|
+
discardSessionTranscript(ctxC.sessionId);
|
|
873
|
+
stopSessionTranscript();
|
|
874
|
+
assert(!existsSync3(transcriptPathForSession(ctxC.sessionId)), "discarded transcript deleted");
|
|
875
|
+
var ctxE = makeCtx("2026-07-29-eeee");
|
|
876
|
+
var ctxF = makeCtx("2026-07-29-ffff");
|
|
877
|
+
startSessionTranscript(ctxE);
|
|
878
|
+
console.log("throwaway shell before pickup");
|
|
879
|
+
rebindSessionTranscript(ctxF);
|
|
880
|
+
stopSessionTranscript();
|
|
881
|
+
assert(!existsSync3(transcriptPathForSession(ctxE.sessionId)), "unpersisted session transcript discarded on switch");
|
|
882
|
+
var ctxD = makeCtx("2026-07-29-dddd");
|
|
883
|
+
ctxD.messages.push({ role: "user", content: "why is freshness red?", at: (/* @__PURE__ */ new Date()).toISOString() });
|
|
884
|
+
ctxD.messages.push({ role: "agent", content: "Because 61% of contacts are stale.", at: (/* @__PURE__ */ new Date()).toISOString() });
|
|
885
|
+
writeSessionContextDoc(ctxD);
|
|
886
|
+
var docD = readFileSync3(contextDocPathForSession(ctxD.sessionId), "utf-8");
|
|
887
|
+
assert(docD.includes("# Session context \u2014 2026-07-29-dddd"), "live context doc written");
|
|
888
|
+
assert(docD.includes("why is freshness red?"), "live context doc includes exchange");
|
|
889
|
+
if (failures.length > 0) {
|
|
890
|
+
console.error(`FAIL transcript-smoke (${failures.length}):`);
|
|
891
|
+
for (const f of failures) console.error(` - ${f}`);
|
|
892
|
+
process.exit(1);
|
|
893
|
+
}
|
|
894
|
+
console.log(
|
|
895
|
+
"PASS transcript-smoke (capture emulator, redaction, context brief, recorder lifecycle)"
|
|
896
|
+
);
|
|
897
|
+
//# sourceMappingURL=transcript-smoke.js.map
|