@nexrall/code-core 1.4.76 → 1.4.77
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/agent/goal.d.ts +55 -0
- package/dist/agent/goal.d.ts.map +1 -0
- package/dist/agent/goal.js +120 -0
- package/dist/agent/handoffBrief.d.ts +82 -0
- package/dist/agent/handoffBrief.d.ts.map +1 -0
- package/dist/agent/handoffBrief.js +121 -0
- package/dist/agent/modelCatalogue.d.ts +23 -0
- package/dist/agent/modelCatalogue.d.ts.map +1 -1
- package/dist/agent/modelCatalogue.js +28 -0
- package/dist/agent/outputStyles.d.ts +27 -0
- package/dist/agent/outputStyles.d.ts.map +1 -0
- package/dist/agent/outputStyles.js +174 -0
- package/dist/agent/skills.d.ts.map +1 -1
- package/dist/agent/skills.js +7 -0
- package/dist/api/billingLines.d.ts +11 -0
- package/dist/api/billingLines.d.ts.map +1 -0
- package/dist/api/billingLines.js +56 -0
- package/dist/api/client.d.ts +2 -0
- package/dist/api/client.d.ts.map +1 -1
- package/dist/api/scheduleLines.d.ts +53 -0
- package/dist/api/scheduleLines.d.ts.map +1 -0
- package/dist/api/scheduleLines.js +150 -0
- package/dist/context/contextFiles.d.ts +81 -0
- package/dist/context/contextFiles.d.ts.map +1 -0
- package/dist/context/contextFiles.js +310 -0
- package/dist/diff/collect.d.ts +25 -0
- package/dist/diff/collect.d.ts.map +1 -0
- package/dist/diff/collect.js +169 -0
- package/dist/diff/diffView.d.ts +57 -0
- package/dist/diff/diffView.d.ts.map +1 -0
- package/dist/diff/diffView.js +231 -0
- package/dist/diff/reviewQueue.d.ts +53 -0
- package/dist/diff/reviewQueue.d.ts.map +1 -0
- package/dist/diff/reviewQueue.js +349 -0
- package/dist/events/recap.d.ts +54 -0
- package/dist/events/recap.d.ts.map +1 -0
- package/dist/events/recap.js +161 -0
- package/dist/events/sessionEvents.d.ts +54 -0
- package/dist/events/sessionEvents.d.ts.map +1 -0
- package/dist/events/sessionEvents.js +324 -0
- package/dist/events/sessionStats.d.ts +89 -0
- package/dist/events/sessionStats.d.ts.map +1 -0
- package/dist/events/sessionStats.js +264 -0
- package/dist/import/claudeImport.d.ts +44 -0
- package/dist/import/claudeImport.d.ts.map +1 -0
- package/dist/import/claudeImport.js +337 -0
- package/dist/index.d.ts +33 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +33 -0
- package/dist/project/projectCommands.d.ts +33 -0
- package/dist/project/projectCommands.d.ts.map +1 -0
- package/dist/project/projectCommands.js +246 -0
- package/dist/search/conversationTurns.d.ts +53 -0
- package/dist/search/conversationTurns.d.ts.map +1 -0
- package/dist/search/conversationTurns.js +170 -0
- package/dist/search/globalSearch.d.ts +144 -0
- package/dist/search/globalSearch.d.ts.map +1 -0
- package/dist/search/globalSearch.js +350 -0
- package/dist/share/sessionChannel.d.ts +136 -0
- package/dist/share/sessionChannel.d.ts.map +1 -0
- package/dist/share/sessionChannel.js +373 -0
- package/dist/share/sessionShare.d.ts +104 -0
- package/dist/share/sessionShare.d.ts.map +1 -0
- package/dist/share/sessionShare.js +336 -0
- package/dist/ui/configCmd.d.ts +48 -0
- package/dist/ui/configCmd.d.ts.map +1 -0
- package/dist/ui/configCmd.js +146 -0
- package/dist/ui/deployPanel.d.ts +91 -0
- package/dist/ui/deployPanel.d.ts.map +1 -0
- package/dist/ui/deployPanel.js +442 -0
- package/dist/ui/mergeAssistant.d.ts +120 -0
- package/dist/ui/mergeAssistant.d.ts.map +1 -0
- package/dist/ui/mergeAssistant.js +391 -0
- package/dist/ui/notify.d.ts +114 -0
- package/dist/ui/notify.d.ts.map +1 -0
- package/dist/ui/notify.js +235 -0
- package/dist/ui/promptSuggest.d.ts +56 -0
- package/dist/ui/promptSuggest.d.ts.map +1 -0
- package/dist/ui/promptSuggest.js +160 -0
- package/dist/ui/taskGraph.d.ts +44 -0
- package/dist/ui/taskGraph.d.ts.map +1 -0
- package/dist/ui/taskGraph.js +289 -0
- package/dist/util/addDir.d.ts +31 -0
- package/dist/util/addDir.d.ts.map +1 -0
- package/dist/util/addDir.js +124 -0
- package/dist/util/batch.d.ts +16 -0
- package/dist/util/batch.d.ts.map +1 -0
- package/dist/util/batch.js +92 -0
- package/dist/util/cellWidth.d.ts +12 -0
- package/dist/util/cellWidth.d.ts.map +1 -0
- package/dist/util/cellWidth.js +161 -0
- package/dist/util/duration.d.ts +8 -0
- package/dist/util/duration.d.ts.map +1 -0
- package/dist/util/duration.js +50 -0
- package/dist/util/loopJobs.d.ts +67 -0
- package/dist/util/loopJobs.d.ts.map +1 -0
- package/dist/util/loopJobs.js +169 -0
- package/dist/util/settingsFile.d.ts +14 -0
- package/dist/util/settingsFile.d.ts.map +1 -0
- package/dist/util/settingsFile.js +99 -0
- package/dist/util/toolLabel.d.ts +2 -0
- package/dist/util/toolLabel.d.ts.map +1 -0
- package/dist/util/toolLabel.js +17 -0
- package/dist/verify/failureTimeline.d.ts +100 -0
- package/dist/verify/failureTimeline.d.ts.map +1 -0
- package/dist/verify/failureTimeline.js +253 -0
- package/dist/verify/verification.d.ts +75 -0
- package/dist/verify/verification.d.ts.map +1 -0
- package/dist/verify/verification.js +315 -0
- package/package.json +5 -4
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import type { SessionEvent } from './sessionEvents';
|
|
2
|
+
/** Claude Code's cap, and its cut: at a word boundary, never mid-word. */
|
|
3
|
+
export declare const RECAP_MAX_CHARS = 400;
|
|
4
|
+
/**
|
|
5
|
+
* How many event rows the callers read. A bounded TAIL on purpose: a recap is
|
|
6
|
+
* "where you left off", and the newest rows are the ones that describe that.
|
|
7
|
+
*/
|
|
8
|
+
export declare const RECAP_EVENT_WINDOW = 500;
|
|
9
|
+
export interface RecapOpts {
|
|
10
|
+
now: number;
|
|
11
|
+
/** Tests (and future callers) may lower the cap; the default is CC's 400. */
|
|
12
|
+
maxChars?: number;
|
|
13
|
+
}
|
|
14
|
+
/** `2h ago` / `just now` — coarse on purpose; the recap is a pointer, not a clock. */
|
|
15
|
+
export declare function agoLabel(ms: number): string;
|
|
16
|
+
/**
|
|
17
|
+
* The hard cap from the feature spec: over `max`, cut at the LAST space so a
|
|
18
|
+
* word is never sliced in half, then mark it. The returned string is always
|
|
19
|
+
* ≤ `max` characters.
|
|
20
|
+
*/
|
|
21
|
+
export declare function capRecap(text: string, max?: number): string;
|
|
22
|
+
/**
|
|
23
|
+
* One line of "where this session left off", or null when the window holds
|
|
24
|
+
* nothing worth saying (callers print an honest empty state for `/recap` and
|
|
25
|
+
* stay silent for the automatic one).
|
|
26
|
+
*
|
|
27
|
+
* The shape, fixed so the tests can pin it exactly:
|
|
28
|
+
* you asked "…" · 5 turns · 12 tool calls · files: a.ts, b.ts (+2 more) ·
|
|
29
|
+
* last action: edit_file src/a.ts · last activity 2h ago
|
|
30
|
+
*
|
|
31
|
+
* Rules that matter:
|
|
32
|
+
* · the LAST prompt wins ("you asked" means the most recent question);
|
|
33
|
+
* · files are the last two UNIQUE paths — where you left off, not where you
|
|
34
|
+
* started — with a (+n more) tail so the count is never silently trimmed;
|
|
35
|
+
* · malformed rows are ignored, never thrown over (a torn log must not break
|
|
36
|
+
* a session start);
|
|
37
|
+
* · the finished line goes through capRecap, so the 400-char bound holds
|
|
38
|
+
* even for a pathological window.
|
|
39
|
+
*/
|
|
40
|
+
export declare function buildRecap(events: SessionEvent[], opts: RecapOpts): string | null;
|
|
41
|
+
/**
|
|
42
|
+
* Whether the automatic recap prints after `--resume`/`--continue`.
|
|
43
|
+
* Default ON (CC parity). The env var decides first — `0`/`false`/`off` force
|
|
44
|
+
* it off, `1`/`true` force it on — then the `autoRecap` setting in
|
|
45
|
+
* settings.json. For the SETTING only the off direction needs spellings: any
|
|
46
|
+
* truthy value means "on", which is also the default, so accepting `false`,
|
|
47
|
+
* the string `"false"`, `"0"` and `"off"` as off is the user-friendly reading
|
|
48
|
+
* and cannot surprise anyone who did not touch the key.
|
|
49
|
+
*/
|
|
50
|
+
export declare function resolveAutoRecap(f: {
|
|
51
|
+
env?: Record<string, string | undefined>;
|
|
52
|
+
setting?: unknown;
|
|
53
|
+
}): boolean;
|
|
54
|
+
//# sourceMappingURL=recap.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"recap.d.ts","sourceRoot":"","sources":["../../src/events/recap.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAEpD,0EAA0E;AAC1E,eAAO,MAAM,eAAe,MAAM,CAAC;AACnC;;;GAGG;AACH,eAAO,MAAM,kBAAkB,MAAM,CAAC;AAEtC,MAAM,WAAW,SAAS;IACxB,GAAG,EAAE,MAAM,CAAC;IACZ,6EAA6E;IAC7E,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,sFAAsF;AACtF,wBAAgB,QAAQ,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CAO3C;AAWD;;;;GAIG;AACH,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,SAAkB,GAAG,MAAM,CAOpE;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,UAAU,CAAC,MAAM,EAAE,YAAY,EAAE,EAAE,IAAI,EAAE,SAAS,GAAG,MAAM,GAAG,IAAI,CAwDjF;AAED;;;;;;;;GAQG;AACH,wBAAgB,gBAAgB,CAAC,CAAC,EAAE;IAAE,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC;IAAC,OAAO,CAAC,EAAE,OAAO,CAAA;CAAE,GAAG,OAAO,CAQ5G"}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Lives in core (moved from the CLI's ui/recap.ts, 2026-10-04): the VS Code
|
|
3
|
+
// panel rebuilds the same recap from the same event log.
|
|
4
|
+
// S19a — `/recap` and the automatic recap when a session is resumed.
|
|
5
|
+
//
|
|
6
|
+
// Claude Code parity (2.1.x): returning to a session shows a short recap of
|
|
7
|
+
// where it left off, capped at 400 characters on a word boundary, and `/recap`
|
|
8
|
+
// prints it on demand. Ours is rebuilt from the §92 event log we already write
|
|
9
|
+
// — no model call, no cost — and it cannot invent anything that did not happen:
|
|
10
|
+
// the prompt it reads back is the one the log recorded, the counts are counts.
|
|
11
|
+
//
|
|
12
|
+
// Pure (no console, no fs) so the composition and capping rules are unit-
|
|
13
|
+
// testable — see test/recap.test.ts. The callers own the I/O: chat.ts reads
|
|
14
|
+
// the tail of the session's log and prints the line.
|
|
15
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
16
|
+
exports.RECAP_EVENT_WINDOW = exports.RECAP_MAX_CHARS = void 0;
|
|
17
|
+
exports.agoLabel = agoLabel;
|
|
18
|
+
exports.capRecap = capRecap;
|
|
19
|
+
exports.buildRecap = buildRecap;
|
|
20
|
+
exports.resolveAutoRecap = resolveAutoRecap;
|
|
21
|
+
/** Claude Code's cap, and its cut: at a word boundary, never mid-word. */
|
|
22
|
+
exports.RECAP_MAX_CHARS = 400;
|
|
23
|
+
/**
|
|
24
|
+
* How many event rows the callers read. A bounded TAIL on purpose: a recap is
|
|
25
|
+
* "where you left off", and the newest rows are the ones that describe that.
|
|
26
|
+
*/
|
|
27
|
+
exports.RECAP_EVENT_WINDOW = 500;
|
|
28
|
+
/** `2h ago` / `just now` — coarse on purpose; the recap is a pointer, not a clock. */
|
|
29
|
+
function agoLabel(ms) {
|
|
30
|
+
if (!Number.isFinite(ms) || ms < 60000)
|
|
31
|
+
return 'just now';
|
|
32
|
+
const m = Math.floor(ms / 60000);
|
|
33
|
+
if (m < 60)
|
|
34
|
+
return `${m}m ago`;
|
|
35
|
+
const h = Math.floor(m / 60);
|
|
36
|
+
if (h < 24)
|
|
37
|
+
return `${h}h ago`;
|
|
38
|
+
return `${Math.floor(h / 24)}d ago`;
|
|
39
|
+
}
|
|
40
|
+
/** Collapse whitespace and cut to `max` at a word boundary (an ellipsis marks the cut). */
|
|
41
|
+
function clipWords(text, max) {
|
|
42
|
+
const t = text.replace(/\s+/g, ' ').trim();
|
|
43
|
+
if (t.length <= max)
|
|
44
|
+
return t;
|
|
45
|
+
const cut = t.slice(0, Math.max(1, max - 1));
|
|
46
|
+
const sp = cut.lastIndexOf(' ');
|
|
47
|
+
return (sp > 0 ? cut.slice(0, sp) : cut).replace(/[ ,;:·]+$/, '') + '…';
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* The hard cap from the feature spec: over `max`, cut at the LAST space so a
|
|
51
|
+
* word is never sliced in half, then mark it. The returned string is always
|
|
52
|
+
* ≤ `max` characters.
|
|
53
|
+
*/
|
|
54
|
+
function capRecap(text, max = exports.RECAP_MAX_CHARS) {
|
|
55
|
+
if (max <= 0)
|
|
56
|
+
return '';
|
|
57
|
+
if (text.length <= max)
|
|
58
|
+
return text;
|
|
59
|
+
const cut = text.slice(0, max - 1);
|
|
60
|
+
const sp = cut.lastIndexOf(' ');
|
|
61
|
+
const body = (sp > 0 ? cut.slice(0, sp) : cut).replace(/[ ,;:·]+$/, '');
|
|
62
|
+
return body + '…';
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* One line of "where this session left off", or null when the window holds
|
|
66
|
+
* nothing worth saying (callers print an honest empty state for `/recap` and
|
|
67
|
+
* stay silent for the automatic one).
|
|
68
|
+
*
|
|
69
|
+
* The shape, fixed so the tests can pin it exactly:
|
|
70
|
+
* you asked "…" · 5 turns · 12 tool calls · files: a.ts, b.ts (+2 more) ·
|
|
71
|
+
* last action: edit_file src/a.ts · last activity 2h ago
|
|
72
|
+
*
|
|
73
|
+
* Rules that matter:
|
|
74
|
+
* · the LAST prompt wins ("you asked" means the most recent question);
|
|
75
|
+
* · files are the last two UNIQUE paths — where you left off, not where you
|
|
76
|
+
* started — with a (+n more) tail so the count is never silently trimmed;
|
|
77
|
+
* · malformed rows are ignored, never thrown over (a torn log must not break
|
|
78
|
+
* a session start);
|
|
79
|
+
* · the finished line goes through capRecap, so the 400-char bound holds
|
|
80
|
+
* even for a pathological window.
|
|
81
|
+
*/
|
|
82
|
+
function buildRecap(events, opts) {
|
|
83
|
+
let prompt = null;
|
|
84
|
+
let turns = 0;
|
|
85
|
+
let tools = 0;
|
|
86
|
+
let lastTool = null;
|
|
87
|
+
const files = [];
|
|
88
|
+
let lastTs = null;
|
|
89
|
+
for (const ev of events) {
|
|
90
|
+
if (typeof ev?.ts === 'number' && Number.isFinite(ev.ts)) {
|
|
91
|
+
lastTs = lastTs === null ? ev.ts : Math.max(lastTs, ev.ts);
|
|
92
|
+
}
|
|
93
|
+
switch (ev?.type) {
|
|
94
|
+
case 'message.started':
|
|
95
|
+
if (typeof ev.text === 'string' && ev.text.trim())
|
|
96
|
+
prompt = ev.text.trim();
|
|
97
|
+
break;
|
|
98
|
+
case 'message.completed':
|
|
99
|
+
turns++;
|
|
100
|
+
break;
|
|
101
|
+
case 'tool.started': {
|
|
102
|
+
tools++;
|
|
103
|
+
const name = typeof ev.name === 'string' && ev.name.trim() ? ev.name.trim() : 'tool';
|
|
104
|
+
const target = typeof ev.target === 'string' && ev.target.trim() ? ev.target.trim() : null;
|
|
105
|
+
lastTool = { name, target };
|
|
106
|
+
break;
|
|
107
|
+
}
|
|
108
|
+
case 'file.changed': {
|
|
109
|
+
const p = typeof ev.path === 'string' ? ev.path.trim() : '';
|
|
110
|
+
if (p && !files.includes(p))
|
|
111
|
+
files.push(p);
|
|
112
|
+
break;
|
|
113
|
+
}
|
|
114
|
+
default:
|
|
115
|
+
break;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
if (!prompt && turns === 0 && tools === 0)
|
|
119
|
+
return null;
|
|
120
|
+
const segments = [];
|
|
121
|
+
if (prompt)
|
|
122
|
+
segments.push(`you asked "${clipWords(prompt, 140)}"`);
|
|
123
|
+
if (turns > 0 || tools > 0) {
|
|
124
|
+
segments.push(`${turns} turn${turns === 1 ? '' : 's'}`, `${tools} tool call${tools === 1 ? '' : 's'}`);
|
|
125
|
+
}
|
|
126
|
+
if (files.length) {
|
|
127
|
+
const shown = files.slice(-2).join(', ');
|
|
128
|
+
const more = files.length > 2 ? ` (+${files.length - 2} more)` : '';
|
|
129
|
+
segments.push(`files: ${shown}${more}`);
|
|
130
|
+
}
|
|
131
|
+
if (lastTool) {
|
|
132
|
+
const t = lastTool;
|
|
133
|
+
segments.push(`last action: ${t.target ? `${t.name} ${clipWords(t.target, 60)}` : t.name}`);
|
|
134
|
+
}
|
|
135
|
+
if (lastTs !== null) {
|
|
136
|
+
segments.push(`last activity ${agoLabel(opts.now - lastTs)}`);
|
|
137
|
+
}
|
|
138
|
+
return capRecap(segments.join(' · '), opts.maxChars ?? exports.RECAP_MAX_CHARS);
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Whether the automatic recap prints after `--resume`/`--continue`.
|
|
142
|
+
* Default ON (CC parity). The env var decides first — `0`/`false`/`off` force
|
|
143
|
+
* it off, `1`/`true` force it on — then the `autoRecap` setting in
|
|
144
|
+
* settings.json. For the SETTING only the off direction needs spellings: any
|
|
145
|
+
* truthy value means "on", which is also the default, so accepting `false`,
|
|
146
|
+
* the string `"false"`, `"0"` and `"off"` as off is the user-friendly reading
|
|
147
|
+
* and cannot surprise anyone who did not touch the key.
|
|
148
|
+
*/
|
|
149
|
+
function resolveAutoRecap(f) {
|
|
150
|
+
const raw = (f.env ?? {}).NEXRALL_AUTO_RECAP;
|
|
151
|
+
const v = typeof raw === 'string' ? raw.trim().toLowerCase() : '';
|
|
152
|
+
if (v === '0' || v === 'false' || v === 'off')
|
|
153
|
+
return false;
|
|
154
|
+
if (v === '1' || v === 'true')
|
|
155
|
+
return true;
|
|
156
|
+
const s = f.setting;
|
|
157
|
+
if (s === false || s === 'false' || s === '0' || s === 'off')
|
|
158
|
+
return false;
|
|
159
|
+
return true;
|
|
160
|
+
}
|
|
161
|
+
//# sourceMappingURL=recap.js.map
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
export declare const EVENTS_VERSION = 1;
|
|
2
|
+
/** Once the file crosses this, it is trimmed to KEEP_ON_TRIM lines (checked once per process). */
|
|
3
|
+
export declare const MAX_EVENTS_BYTES: number;
|
|
4
|
+
export declare const KEEP_ON_TRIM = 1000;
|
|
5
|
+
export declare const CLIP_TEXT = 240;
|
|
6
|
+
/** A codemod can touch hundreds of files; the stream keeps the first N and stops. */
|
|
7
|
+
export declare const MAX_FILE_EVENTS = 20;
|
|
8
|
+
/** The event vocabulary from the spec (§92), verbatim. */
|
|
9
|
+
export declare const EVENT_TYPES: readonly ["session.started", "session.resumed", "agent.started", "agent.completed", "agent.failed", "message.started", "message.delta", "message.completed", "tool.started", "tool.output", "tool.completed", "file.changed", "permission.requested", "permission.granted", "permission.denied", "task.started", "task.completed", "task.failed", "checkpoint.created", "checkpoint.restored", "context.compacted", "session.completed"];
|
|
10
|
+
export type EventType = (typeof EVENT_TYPES)[number];
|
|
11
|
+
export interface SessionEvent {
|
|
12
|
+
v: typeof EVENTS_VERSION;
|
|
13
|
+
ts: number;
|
|
14
|
+
type: EventType;
|
|
15
|
+
[key: string]: unknown;
|
|
16
|
+
}
|
|
17
|
+
/** NEXRALL_EVENTS=off (or 0/false) turns the whole protocol off for a run. */
|
|
18
|
+
export declare function eventsEnabled(env?: NodeJS.ProcessEnv): boolean;
|
|
19
|
+
export declare function eventsDir(env?: NodeJS.ProcessEnv): string;
|
|
20
|
+
/** File-name-safe id: anything outside [A-Za-z0-9._-] is dropped (ids are hex, but bundles import untrusted ones). */
|
|
21
|
+
export declare function safeId(id: string): string;
|
|
22
|
+
export declare function eventsPath(id: string, env?: NodeJS.ProcessEnv): string;
|
|
23
|
+
export declare function clipText(text: string, max?: number): string;
|
|
24
|
+
export declare function redactValue(key: string, value: unknown): unknown;
|
|
25
|
+
export declare function buildEvent(type: EventType, data?: Record<string, unknown>, now?: number): SessionEvent;
|
|
26
|
+
/** What a tool call acted on, in one clipped line — the useful half of a `tool.started`. */
|
|
27
|
+
export declare function toolTarget(name: string, input: Record<string, unknown>): string;
|
|
28
|
+
export declare function trimEventsFile(file: string): void;
|
|
29
|
+
export declare class EventLog {
|
|
30
|
+
private readonly id;
|
|
31
|
+
private readonly opts;
|
|
32
|
+
private _count;
|
|
33
|
+
private checked;
|
|
34
|
+
constructor(id: string, opts?: {
|
|
35
|
+
file?: string;
|
|
36
|
+
enabled?: boolean;
|
|
37
|
+
});
|
|
38
|
+
get file(): string;
|
|
39
|
+
get count(): number;
|
|
40
|
+
/** Append one event. Returns the written event, or null when disabled/failed — never throws. */
|
|
41
|
+
emit(type: EventType, data?: Record<string, unknown>): SessionEvent | null;
|
|
42
|
+
}
|
|
43
|
+
/** Parse a log file, tolerating a torn last line (kill -9 mid-append). `last` keeps the tail. */
|
|
44
|
+
export declare function readEvents(id: string, opts?: {
|
|
45
|
+
last?: number;
|
|
46
|
+
file?: string;
|
|
47
|
+
}): SessionEvent[];
|
|
48
|
+
/** Replace a log file wholesale — used when importing a bundle from another machine. */
|
|
49
|
+
export declare function writeEvents(id: string, events: SessionEvent[], opts?: {
|
|
50
|
+
file?: string;
|
|
51
|
+
}): number;
|
|
52
|
+
/** One human line for `nex sessions --events` — the same shape a web client would render. */
|
|
53
|
+
export declare function summarizeEvent(ev: SessionEvent): string;
|
|
54
|
+
//# sourceMappingURL=sessionEvents.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sessionEvents.d.ts","sourceRoot":"","sources":["../../src/events/sessionEvents.ts"],"names":[],"mappings":"AAsBA,eAAO,MAAM,cAAc,IAAI,CAAC;AAChC,kGAAkG;AAClG,eAAO,MAAM,gBAAgB,QAAkB,CAAC;AAChD,eAAO,MAAM,YAAY,OAAO,CAAC;AACjC,eAAO,MAAM,SAAS,MAAM,CAAC;AAC7B,qFAAqF;AACrF,eAAO,MAAM,eAAe,KAAK,CAAC;AAElC,0DAA0D;AAC1D,eAAO,MAAM,WAAW,0aAuBd,CAAC;AACX,MAAM,MAAM,SAAS,GAAG,CAAC,OAAO,WAAW,CAAC,CAAC,MAAM,CAAC,CAAC;AAErD,MAAM,WAAW,YAAY;IAC3B,CAAC,EAAE,OAAO,cAAc,CAAC;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,SAAS,CAAC;IAChB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AASD,8EAA8E;AAC9E,wBAAgB,aAAa,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,OAAO,CAG3E;AAED,wBAAgB,SAAS,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,MAAM,CAEtE;AAED,sHAAsH;AACtH,wBAAgB,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CAEzC;AAED,wBAAgB,UAAU,CAAC,EAAE,EAAE,MAAM,EAAE,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,MAAM,CAEnF;AAWD,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,SAAY,GAAG,MAAM,CAG9D;AAED,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,OAAO,CA+BhE;AAED,wBAAgB,UAAU,CACxB,IAAI,EAAE,SAAS,EACf,IAAI,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,EAClC,GAAG,GAAE,MAAmB,GACvB,YAAY,CAOd;AAED,4FAA4F;AAC5F,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAG/E;AAED,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAMjD;AAED,qBAAa,QAAQ;IAIjB,OAAO,CAAC,QAAQ,CAAC,EAAE;IACnB,OAAO,CAAC,QAAQ,CAAC,IAAI;IAJvB,OAAO,CAAC,MAAM,CAAK;IACnB,OAAO,CAAC,OAAO,CAAS;gBAEL,EAAE,EAAE,MAAM,EACV,IAAI,GAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,OAAO,CAAA;KAAO;IAGlE,IAAI,IAAI,IAAI,MAAM,CAEjB;IACD,IAAI,KAAK,IAAI,MAAM,CAElB;IAED,gGAAgG;IAChG,IAAI,CAAC,IAAI,EAAE,SAAS,EAAE,IAAI,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,GAAG,YAAY,GAAG,IAAI;CAoB/E;AAED,iGAAiG;AACjG,wBAAgB,UAAU,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,GAAE;IAAE,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAO,GAAG,YAAY,EAAE,CAmBlG;AAED,wFAAwF;AACxF,wBAAgB,WAAW,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,YAAY,EAAE,EAAE,IAAI,GAAE;IAAE,IAAI,CAAC,EAAE,MAAM,CAAA;CAAO,GAAG,MAAM,CASpG;AAUD,6FAA6F;AAC7F,wBAAgB,cAAc,CAAC,EAAE,EAAE,YAAY,GAAG,MAAM,CAkDvD"}
|
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.EventLog = exports.EVENT_TYPES = exports.MAX_FILE_EVENTS = exports.CLIP_TEXT = exports.KEEP_ON_TRIM = exports.MAX_EVENTS_BYTES = exports.EVENTS_VERSION = void 0;
|
|
37
|
+
exports.eventsEnabled = eventsEnabled;
|
|
38
|
+
exports.eventsDir = eventsDir;
|
|
39
|
+
exports.safeId = safeId;
|
|
40
|
+
exports.eventsPath = eventsPath;
|
|
41
|
+
exports.clipText = clipText;
|
|
42
|
+
exports.redactValue = redactValue;
|
|
43
|
+
exports.buildEvent = buildEvent;
|
|
44
|
+
exports.toolTarget = toolTarget;
|
|
45
|
+
exports.trimEventsFile = trimEventsFile;
|
|
46
|
+
exports.readEvents = readEvents;
|
|
47
|
+
exports.writeEvents = writeEvents;
|
|
48
|
+
exports.summarizeEvent = summarizeEvent;
|
|
49
|
+
// Spec §92 — JSON event protocol.
|
|
50
|
+
//
|
|
51
|
+
// One append-only JSONL file per session (`<sessions-root>/events/<id>.jsonl`), written as
|
|
52
|
+
// the agent works. This is the record other clients (web, VS Code, mobile) can follow to
|
|
53
|
+
// mirror a terminal session — the foundation §93 (terminal-to-web continuity) and §94
|
|
54
|
+
// (resume elsewhere) build on.
|
|
55
|
+
//
|
|
56
|
+
// Deliberate limits, so the log stays cheap and safe:
|
|
57
|
+
// · `message.delta` is NOT emitted — a line per streamed token would dominate the file,
|
|
58
|
+
// and `message.completed` already carries what the turn produced.
|
|
59
|
+
// · `tool.output` is NOT emitted — full output lives in the session transcript; the
|
|
60
|
+
// event stream carries started/completed with timing instead.
|
|
61
|
+
// · Values under secret-looking keys are dropped and long strings are clipped, because
|
|
62
|
+
// this file is meant to be exported and read by other tools.
|
|
63
|
+
// · Every write is best-effort: an event log must never fail a turn.
|
|
64
|
+
const fs = __importStar(require("fs"));
|
|
65
|
+
const os = __importStar(require("os"));
|
|
66
|
+
const path = __importStar(require("path"));
|
|
67
|
+
// Lives in core (moved from the CLI's sessionEvents.ts, 2026-10-04) so the VS
|
|
68
|
+
// Code panel writes the SAME per-session event log /recap and /stats read;
|
|
69
|
+
// the CLI module is a re-export shim.
|
|
70
|
+
exports.EVENTS_VERSION = 1;
|
|
71
|
+
/** Once the file crosses this, it is trimmed to KEEP_ON_TRIM lines (checked once per process). */
|
|
72
|
+
exports.MAX_EVENTS_BYTES = 2 * 1024 * 1024;
|
|
73
|
+
exports.KEEP_ON_TRIM = 1000;
|
|
74
|
+
exports.CLIP_TEXT = 240;
|
|
75
|
+
/** A codemod can touch hundreds of files; the stream keeps the first N and stops. */
|
|
76
|
+
exports.MAX_FILE_EVENTS = 20;
|
|
77
|
+
/** The event vocabulary from the spec (§92), verbatim. */
|
|
78
|
+
exports.EVENT_TYPES = [
|
|
79
|
+
'session.started',
|
|
80
|
+
'session.resumed',
|
|
81
|
+
'agent.started',
|
|
82
|
+
'agent.completed',
|
|
83
|
+
'agent.failed',
|
|
84
|
+
'message.started',
|
|
85
|
+
'message.delta',
|
|
86
|
+
'message.completed',
|
|
87
|
+
'tool.started',
|
|
88
|
+
'tool.output',
|
|
89
|
+
'tool.completed',
|
|
90
|
+
'file.changed',
|
|
91
|
+
'permission.requested',
|
|
92
|
+
'permission.granted',
|
|
93
|
+
'permission.denied',
|
|
94
|
+
'task.started',
|
|
95
|
+
'task.completed',
|
|
96
|
+
'task.failed',
|
|
97
|
+
'checkpoint.created',
|
|
98
|
+
'checkpoint.restored',
|
|
99
|
+
'context.compacted',
|
|
100
|
+
'session.completed',
|
|
101
|
+
];
|
|
102
|
+
// Same root as commands/sessions.ts, resolved here independently so neither module has to
|
|
103
|
+
// import the other (a cycle between storage and telemetry would be a runtime landmine for
|
|
104
|
+
// no benefit). NEXRALL_CLI_SESSIONS_DIR keeps tests off the real ~/.nexrall directory.
|
|
105
|
+
function sessionsRoot(env) {
|
|
106
|
+
return env.NEXRALL_CLI_SESSIONS_DIR || path.join(os.homedir(), '.nexrall', 'cli-sessions');
|
|
107
|
+
}
|
|
108
|
+
/** NEXRALL_EVENTS=off (or 0/false) turns the whole protocol off for a run. */
|
|
109
|
+
function eventsEnabled(env = process.env) {
|
|
110
|
+
const raw = (env.NEXRALL_EVENTS ?? '').trim().toLowerCase();
|
|
111
|
+
return !(raw === 'off' || raw === '0' || raw === 'false');
|
|
112
|
+
}
|
|
113
|
+
function eventsDir(env = process.env) {
|
|
114
|
+
return path.join(sessionsRoot(env), 'events');
|
|
115
|
+
}
|
|
116
|
+
/** File-name-safe id: anything outside [A-Za-z0-9._-] is dropped (ids are hex, but bundles import untrusted ones). */
|
|
117
|
+
function safeId(id) {
|
|
118
|
+
return id.replace(/[^A-Za-z0-9._-]/g, '');
|
|
119
|
+
}
|
|
120
|
+
function eventsPath(id, env = process.env) {
|
|
121
|
+
return path.join(eventsDir(env), `${safeId(id)}.jsonl`);
|
|
122
|
+
}
|
|
123
|
+
const SECRET_KEY_RE = /(token|secret|password|passwd|authorization|api[-_]?key|credential)/i;
|
|
124
|
+
/** A secret is a string (or a blob). A NUMBER under a secret-adjacent key is a tally —
|
|
125
|
+
* `tokens` matches /token/i, and redacting it would blank the very usage counts this
|
|
126
|
+
* stream exists to carry. */
|
|
127
|
+
function secretMustDrop(value) {
|
|
128
|
+
return typeof value === 'string' || (value !== null && typeof value === 'object');
|
|
129
|
+
}
|
|
130
|
+
function clipText(text, max = exports.CLIP_TEXT) {
|
|
131
|
+
if (max <= 0)
|
|
132
|
+
return '';
|
|
133
|
+
return text.length > max ? text.slice(0, max - 1) + '…' : text;
|
|
134
|
+
}
|
|
135
|
+
function redactValue(key, value) {
|
|
136
|
+
if (SECRET_KEY_RE.test(key) && secretMustDrop(value))
|
|
137
|
+
return '[redacted]';
|
|
138
|
+
if (typeof value === 'string')
|
|
139
|
+
return clipText(value);
|
|
140
|
+
if (Array.isArray(value)) {
|
|
141
|
+
return value
|
|
142
|
+
.slice(0, 20)
|
|
143
|
+
.map((v) => typeof v === 'string'
|
|
144
|
+
? clipText(v)
|
|
145
|
+
: typeof v === 'number' || typeof v === 'boolean' || v === null
|
|
146
|
+
? v
|
|
147
|
+
: '[object]');
|
|
148
|
+
}
|
|
149
|
+
if (value && typeof value === 'object') {
|
|
150
|
+
const out = {};
|
|
151
|
+
for (const [k, v] of Object.entries(value)) {
|
|
152
|
+
if (SECRET_KEY_RE.test(k) && secretMustDrop(v)) {
|
|
153
|
+
out[k] = '[redacted]';
|
|
154
|
+
continue;
|
|
155
|
+
}
|
|
156
|
+
out[k] =
|
|
157
|
+
typeof v === 'string'
|
|
158
|
+
? clipText(v)
|
|
159
|
+
: typeof v === 'number' || typeof v === 'boolean' || v === null
|
|
160
|
+
? v
|
|
161
|
+
: '[object]';
|
|
162
|
+
}
|
|
163
|
+
return out;
|
|
164
|
+
}
|
|
165
|
+
return value;
|
|
166
|
+
}
|
|
167
|
+
function buildEvent(type, data = {}, now = Date.now()) {
|
|
168
|
+
const ev = { v: exports.EVENTS_VERSION, ts: now, type };
|
|
169
|
+
for (const [k, val] of Object.entries(data)) {
|
|
170
|
+
if (val === undefined)
|
|
171
|
+
continue;
|
|
172
|
+
ev[k] = redactValue(k, val);
|
|
173
|
+
}
|
|
174
|
+
return ev;
|
|
175
|
+
}
|
|
176
|
+
/** What a tool call acted on, in one clipped line — the useful half of a `tool.started`. */
|
|
177
|
+
function toolTarget(name, input) {
|
|
178
|
+
const pick = input.command ?? input.file_path ?? input.path ?? input.pattern ?? input.query ?? input.url;
|
|
179
|
+
return typeof pick === 'string' && pick.trim() ? clipText(pick.trim(), 120) : name;
|
|
180
|
+
}
|
|
181
|
+
function trimEventsFile(file) {
|
|
182
|
+
const lines = fs
|
|
183
|
+
.readFileSync(file, 'utf-8')
|
|
184
|
+
.split('\n')
|
|
185
|
+
.filter((l) => l.trim());
|
|
186
|
+
fs.writeFileSync(file, lines.slice(-exports.KEEP_ON_TRIM).join('\n') + '\n', 'utf-8');
|
|
187
|
+
}
|
|
188
|
+
class EventLog {
|
|
189
|
+
constructor(id, opts = {}) {
|
|
190
|
+
this.id = id;
|
|
191
|
+
this.opts = opts;
|
|
192
|
+
this._count = 0;
|
|
193
|
+
this.checked = false;
|
|
194
|
+
}
|
|
195
|
+
get file() {
|
|
196
|
+
return this.opts.file ?? eventsPath(this.id);
|
|
197
|
+
}
|
|
198
|
+
get count() {
|
|
199
|
+
return this._count;
|
|
200
|
+
}
|
|
201
|
+
/** Append one event. Returns the written event, or null when disabled/failed — never throws. */
|
|
202
|
+
emit(type, data = {}) {
|
|
203
|
+
if (this.opts.enabled === false)
|
|
204
|
+
return null;
|
|
205
|
+
const ev = buildEvent(type, data);
|
|
206
|
+
try {
|
|
207
|
+
fs.mkdirSync(path.dirname(this.file), { recursive: true });
|
|
208
|
+
if (!this.checked) {
|
|
209
|
+
this.checked = true;
|
|
210
|
+
try {
|
|
211
|
+
if (fs.statSync(this.file).size > exports.MAX_EVENTS_BYTES)
|
|
212
|
+
trimEventsFile(this.file);
|
|
213
|
+
}
|
|
214
|
+
catch {
|
|
215
|
+
/* first write */
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
fs.appendFileSync(this.file, JSON.stringify(ev) + '\n', 'utf-8');
|
|
219
|
+
this._count++;
|
|
220
|
+
return ev;
|
|
221
|
+
}
|
|
222
|
+
catch {
|
|
223
|
+
return null; // telemetry never breaks a turn
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
exports.EventLog = EventLog;
|
|
228
|
+
/** Parse a log file, tolerating a torn last line (kill -9 mid-append). `last` keeps the tail. */
|
|
229
|
+
function readEvents(id, opts = {}) {
|
|
230
|
+
const file = opts.file ?? eventsPath(id);
|
|
231
|
+
let raw;
|
|
232
|
+
try {
|
|
233
|
+
raw = fs.readFileSync(file, 'utf-8');
|
|
234
|
+
}
|
|
235
|
+
catch {
|
|
236
|
+
return [];
|
|
237
|
+
}
|
|
238
|
+
const out = [];
|
|
239
|
+
for (const line of raw.split('\n')) {
|
|
240
|
+
if (!line.trim())
|
|
241
|
+
continue;
|
|
242
|
+
try {
|
|
243
|
+
out.push(JSON.parse(line));
|
|
244
|
+
}
|
|
245
|
+
catch {
|
|
246
|
+
/* torn line */
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
const last = opts.last ?? 0;
|
|
250
|
+
return last > 0 ? out.slice(-last) : out;
|
|
251
|
+
}
|
|
252
|
+
/** Replace a log file wholesale — used when importing a bundle from another machine. */
|
|
253
|
+
function writeEvents(id, events, opts = {}) {
|
|
254
|
+
const file = opts.file ?? eventsPath(id);
|
|
255
|
+
try {
|
|
256
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
257
|
+
fs.writeFileSync(file, events.map((e) => JSON.stringify(e)).join('\n') + (events.length ? '\n' : ''), 'utf-8');
|
|
258
|
+
return events.length;
|
|
259
|
+
}
|
|
260
|
+
catch {
|
|
261
|
+
return 0;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
function num(v) {
|
|
265
|
+
return typeof v === 'number' ? String(v) : '?';
|
|
266
|
+
}
|
|
267
|
+
function fmtMs(v) {
|
|
268
|
+
if (typeof v !== 'number')
|
|
269
|
+
return '';
|
|
270
|
+
return v >= 1000 ? `${Math.round(v / 1000)}s` : `${v}ms`;
|
|
271
|
+
}
|
|
272
|
+
/** One human line for `nex sessions --events` — the same shape a web client would render. */
|
|
273
|
+
function summarizeEvent(ev) {
|
|
274
|
+
const at = new Date(ev.ts).toTimeString().slice(0, 8);
|
|
275
|
+
let body;
|
|
276
|
+
switch (ev.type) {
|
|
277
|
+
case 'session.started':
|
|
278
|
+
body = `session ${String(ev.id ?? '')} started${ev.forkOf ? ` (fork of ${String(ev.forkOf)})` : ''}`;
|
|
279
|
+
break;
|
|
280
|
+
case 'session.resumed':
|
|
281
|
+
body = `session ${String(ev.id ?? '')} resumed`;
|
|
282
|
+
break;
|
|
283
|
+
case 'session.completed':
|
|
284
|
+
body = `session ended (${String(ev.reason ?? 'exit')})`;
|
|
285
|
+
break;
|
|
286
|
+
case 'message.started':
|
|
287
|
+
body = `prompt: ${String(ev.text ?? '')}`;
|
|
288
|
+
break;
|
|
289
|
+
case 'message.completed':
|
|
290
|
+
body = `turn ${String(ev.outcome ?? 'ok')} · ${num(ev.tokens)} tokens · ${fmtMs(ev.ms)}`;
|
|
291
|
+
break;
|
|
292
|
+
case 'tool.started':
|
|
293
|
+
body = `→ ${String(ev.name ?? 'tool')}${ev.target ? ` ${String(ev.target)}` : ''}`;
|
|
294
|
+
break;
|
|
295
|
+
case 'tool.completed':
|
|
296
|
+
body = `${ev.ok === false ? '✗' : '✓'} ${String(ev.name ?? 'tool')}${fmtMs(ev.ms) ? ` (${fmtMs(ev.ms)})` : ''}`;
|
|
297
|
+
break;
|
|
298
|
+
case 'file.changed':
|
|
299
|
+
body = `changed ${String(ev.path ?? '')}`;
|
|
300
|
+
break;
|
|
301
|
+
case 'permission.requested':
|
|
302
|
+
body = `permission requested: ${String(ev.what ?? '')}`;
|
|
303
|
+
break;
|
|
304
|
+
case 'permission.granted':
|
|
305
|
+
body = `permission granted${ev.tool ? `: ${String(ev.tool)}` : ''}`;
|
|
306
|
+
break;
|
|
307
|
+
case 'permission.denied':
|
|
308
|
+
body = `permission denied${ev.tool ? `: ${String(ev.tool)}` : ''}`;
|
|
309
|
+
break;
|
|
310
|
+
case 'checkpoint.created':
|
|
311
|
+
body = `checkpoint saved · ${num(ev.turns)} turn(s)`;
|
|
312
|
+
break;
|
|
313
|
+
case 'checkpoint.restored':
|
|
314
|
+
body = `rewound to #${num(ev.id)} (${num(ev.files)} file(s))`;
|
|
315
|
+
break;
|
|
316
|
+
case 'context.compacted':
|
|
317
|
+
body = `compacted ${num(ev.before)} → ${num(ev.after)} messages${ev.where ? ` (${String(ev.where)})` : ''}`;
|
|
318
|
+
break;
|
|
319
|
+
default:
|
|
320
|
+
body = ev.type;
|
|
321
|
+
}
|
|
322
|
+
return `${at} ${body.length > 100 ? body.slice(0, 99) + '…' : body}`;
|
|
323
|
+
}
|
|
324
|
+
//# sourceMappingURL=sessionEvents.js.map
|