@addai/node 0.4.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 +96 -0
- package/dist/agent-auth.d.ts +9 -0
- package/dist/agent-auth.js +96 -0
- package/dist/attachments.d.ts +20 -0
- package/dist/attachments.js +164 -0
- package/dist/capabilities.d.ts +64 -0
- package/dist/capabilities.js +406 -0
- package/dist/claude-binary.d.ts +16 -0
- package/dist/claude-binary.js +145 -0
- package/dist/claude-config.d.ts +11 -0
- package/dist/claude-config.js +144 -0
- package/dist/claude-print.d.ts +35 -0
- package/dist/claude-print.js +312 -0
- package/dist/claude-spawn.d.ts +40 -0
- package/dist/claude-spawn.js +220 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +137 -0
- package/dist/codex-binary.d.ts +4 -0
- package/dist/codex-binary.js +70 -0
- package/dist/codex-spawn.d.ts +47 -0
- package/dist/codex-spawn.js +320 -0
- package/dist/command-runner.d.ts +9 -0
- package/dist/command-runner.js +592 -0
- package/dist/config.d.ts +7 -0
- package/dist/config.js +38 -0
- package/dist/control-server.d.ts +5 -0
- package/dist/control-server.js +292 -0
- package/dist/diskguard.d.ts +96 -0
- package/dist/diskguard.js +435 -0
- package/dist/error-classify.d.ts +20 -0
- package/dist/error-classify.js +159 -0
- package/dist/events.d.ts +26 -0
- package/dist/events.js +97 -0
- package/dist/gemini-binary.d.ts +4 -0
- package/dist/gemini-binary.js +64 -0
- package/dist/gemini-spawn.d.ts +52 -0
- package/dist/gemini-spawn.js +369 -0
- package/dist/git-identity.d.ts +18 -0
- package/dist/git-identity.js +104 -0
- package/dist/grok-binary.d.ts +4 -0
- package/dist/grok-binary.js +70 -0
- package/dist/grok-spawn.d.ts +41 -0
- package/dist/grok-spawn.js +495 -0
- package/dist/guardrails.d.ts +32 -0
- package/dist/guardrails.js +125 -0
- package/dist/hang-watchdog.d.ts +17 -0
- package/dist/hang-watchdog.js +48 -0
- package/dist/harness-registry.d.ts +55 -0
- package/dist/harness-registry.js +180 -0
- package/dist/heartbeat.d.ts +13 -0
- package/dist/heartbeat.js +95 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.js +440 -0
- package/dist/install.d.ts +63 -0
- package/dist/install.js +467 -0
- package/dist/jsonl-tail.d.ts +20 -0
- package/dist/jsonl-tail.js +192 -0
- package/dist/kimi-binary.d.ts +4 -0
- package/dist/kimi-binary.js +68 -0
- package/dist/kimi-spawn.d.ts +47 -0
- package/dist/kimi-spawn.js +319 -0
- package/dist/lockfile.d.ts +30 -0
- package/dist/lockfile.js +169 -0
- package/dist/mcp-config.d.ts +23 -0
- package/dist/mcp-config.js +133 -0
- package/dist/memory-capture.d.ts +20 -0
- package/dist/memory-capture.js +45 -0
- package/dist/memory-pack.d.ts +34 -0
- package/dist/memory-pack.js +171 -0
- package/dist/model-filter.d.ts +4 -0
- package/dist/model-filter.js +34 -0
- package/dist/node-health.d.ts +18 -0
- package/dist/node-health.js +74 -0
- package/dist/pairing.d.ts +20 -0
- package/dist/pairing.js +129 -0
- package/dist/paths.d.ts +9 -0
- package/dist/paths.js +48 -0
- package/dist/precompact-hook.d.ts +27 -0
- package/dist/precompact-hook.js +136 -0
- package/dist/projects.d.ts +25 -0
- package/dist/projects.js +255 -0
- package/dist/prompt-keystrokes.d.ts +3 -0
- package/dist/prompt-keystrokes.js +56 -0
- package/dist/pty-helper.d.ts +26 -0
- package/dist/pty-helper.js +156 -0
- package/dist/request-pump.d.ts +12 -0
- package/dist/request-pump.js +249 -0
- package/dist/run-retry.d.ts +28 -0
- package/dist/run-retry.js +81 -0
- package/dist/self-update.d.ts +75 -0
- package/dist/self-update.js +192 -0
- package/dist/session-runner.d.ts +130 -0
- package/dist/session-runner.js +2271 -0
- package/dist/sleep-detector.d.ts +11 -0
- package/dist/sleep-detector.js +32 -0
- package/dist/state-migrate.d.ts +8 -0
- package/dist/state-migrate.js +91 -0
- package/dist/store.d.ts +5 -0
- package/dist/store.js +82 -0
- package/dist/supabase-client.d.ts +15 -0
- package/dist/supabase-client.js +119 -0
- package/dist/time-context.d.ts +10 -0
- package/dist/time-context.js +71 -0
- package/dist/tokens.d.ts +4 -0
- package/dist/tokens.js +33 -0
- package/dist/tui/app.d.ts +35 -0
- package/dist/tui/app.js +82 -0
- package/dist/tui/console-capture.d.ts +13 -0
- package/dist/tui/console-capture.js +62 -0
- package/dist/tui/dashboard.d.ts +42 -0
- package/dist/tui/dashboard.js +205 -0
- package/dist/tui/data.d.ts +57 -0
- package/dist/tui/data.js +62 -0
- package/dist/tui/harnesses.d.ts +16 -0
- package/dist/tui/harnesses.js +354 -0
- package/dist/tui/render.d.ts +21 -0
- package/dist/tui/render.js +116 -0
- package/dist/tui/requests.d.ts +18 -0
- package/dist/tui/requests.js +127 -0
- package/dist/tui/run.d.ts +13 -0
- package/dist/tui/run.js +178 -0
- package/dist/tui/transcript.d.ts +39 -0
- package/dist/tui/transcript.js +234 -0
- package/dist/tui.d.ts +1 -0
- package/dist/tui.js +314 -0
- package/dist/types.d.ts +65 -0
- package/dist/types.js +3 -0
- package/dist/win.d.ts +67 -0
- package/dist/win.js +317 -0
- package/package.json +60 -0
- package/scripts/fix-pty-helper.js +28 -0
- package/scripts/precompact-capture.js +292 -0
- package/scripts/smoke-test.sh +74 -0
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// +Ai Memory Phase 4, T7 — PreCompact capture hook (claude agent only).
|
|
3
|
+
//
|
|
4
|
+
// Invoked by Claude Code as a PreCompact command hook — see
|
|
5
|
+
// src/precompact-hook.ts for how/when the daemon writes this into a
|
|
6
|
+
// session's <cwd>/.claude/settings.json (memory_v2-flagged entities only).
|
|
7
|
+
// Runs as a plain child process of claude, so it inherits claude's (and
|
|
8
|
+
// therefore the daemon's PTY spawn's) environment: ENTITY_STUDIO_ENTITY_ID
|
|
9
|
+
// and RUNTIME_REQUEST_ID are threaded in by claude-spawn.ts's extraEnv for
|
|
10
|
+
// exactly this purpose; SUPABASE_URL/SUPABASE_ANON_KEY are NOT threaded as
|
|
11
|
+
// env vars — they're read straight from dist/config.js (the same
|
|
12
|
+
// build-time-public constants session-runner.ts itself uses), which lives
|
|
13
|
+
// one directory up from this script in every install shape (package.json
|
|
14
|
+
// "files": ["dist","scripts", ...]).
|
|
15
|
+
//
|
|
16
|
+
// Best-effort, fire-and-forget, NEVER blocks or fails compaction: every
|
|
17
|
+
// error path here logs to stderr and exits 0 regardless, so Claude Code's
|
|
18
|
+
// own compaction proceeds unaffected whether the capture succeeds, fails,
|
|
19
|
+
// or never had anything to send.
|
|
20
|
+
//
|
|
21
|
+
// Idempotency: this does NOT invent its own turn-hash dedup. It posts the
|
|
22
|
+
// same shape entity-memory-capture already accepts from the normal
|
|
23
|
+
// completed-turn path (surface differs: 'precompact' vs 'chat'/issued_via),
|
|
24
|
+
// and relies on entity-memory-capture's existing episode/turn-hash dedup
|
|
25
|
+
// (T3, sha256(entity_id|user_text|assistant_text)) to make a later
|
|
26
|
+
// completed-turn capture of the SAME exchange a safe no-op.
|
|
27
|
+
//
|
|
28
|
+
// For that to actually hold, this hook's assistantText must match — byte
|
|
29
|
+
// for byte — what the completed-turn path will eventually read back from
|
|
30
|
+
// entity_runtime_requests.assistant_text. That column is populated by the
|
|
31
|
+
// live DB trigger _entity_studio_aggregate_assistant_text (see
|
|
32
|
+
// supabase/migrations/20260720130000_entity_guardrails.sql in the studio
|
|
33
|
+
// repo for the current body), which on the request's terminal transition
|
|
34
|
+
// runs:
|
|
35
|
+
//
|
|
36
|
+
// select string_agg((e.payload ->> 'delta'), '' order by e.id)
|
|
37
|
+
// from public.entity_runtime_request_events e
|
|
38
|
+
// where e.request_id = new.id and e.type = 'assistant_text'
|
|
39
|
+
//
|
|
40
|
+
// i.e. every assistant_text delta event emitted for the WHOLE request row,
|
|
41
|
+
// concatenated in emission order with NO separator, tool_use/tool_result/
|
|
42
|
+
// thinking events excluded entirely. lastTurnFromTranscript below mirrors
|
|
43
|
+
// that by walking back to the last genuine user turn (a role:'user' entry
|
|
44
|
+
// that carries real text — NOT a tool_result-only "user" message, which
|
|
45
|
+
// Claude Code also stamps role:'user') and concatenating every subsequent
|
|
46
|
+
// top-level assistant entry's text blocks, in order, with '' — matching
|
|
47
|
+
// string_agg's join exactly.
|
|
48
|
+
//
|
|
49
|
+
// Sidechain (subagent/Task-tool) entries are skipped (the `entry.isSidechain`
|
|
50
|
+
// check below) as a defensive belt-and-braces measure, but on this daemon's
|
|
51
|
+
// current Claude Code transcript layout they never fire in practice: a
|
|
52
|
+
// subagent's own turns are written to a SEPARATE file tree
|
|
53
|
+
// (`<project>/<session>/subagents/agent-<id>.jsonl`), never interleaved into
|
|
54
|
+
// the top-level `<session>.jsonl` this hook's `transcript_path` and this
|
|
55
|
+
// daemon's own live-turn tailer (jsonl-tail.ts's startJsonlTail, fed the
|
|
56
|
+
// identical spawn.jsonlPath — see session-runner.ts) both read. The DB side
|
|
57
|
+
// (entity_runtime_request_events) is built from that SAME top-level-only
|
|
58
|
+
// tail via events.ts's normalizeJsonlLine, which has no isSidechain check of
|
|
59
|
+
// its own — it doesn't need one, because the file it reads structurally
|
|
60
|
+
// never contains sidechain lines. (Corrected 2026-07-21: this comment
|
|
61
|
+
// previously cited claude-print.ts, the separate --print-mode adapter,
|
|
62
|
+
// which this PreCompact-hooked TUI code path never runs through at all.)
|
|
63
|
+
//
|
|
64
|
+
// assistantText.trim() below (T8 ship-gate fix) matches the completed-turn
|
|
65
|
+
// capture path's own normalization — notifyCaptureIfEnabled in
|
|
66
|
+
// session-runner.ts computes `(row.assistant_text ?? '').trim()` before
|
|
67
|
+
// calling buildCapturePayload — so a genuinely-identical exchange hashes to
|
|
68
|
+
// the SAME turn_hash (sha256(entity_id|user_text|assistant_text), computed
|
|
69
|
+
// server-side in entity-memory-capture/index.ts over exactly the strings
|
|
70
|
+
// this payload sends, no server-side trim) on both paths. Before this fix, a
|
|
71
|
+
// transcript-derived assistantText with incidental leading/trailing
|
|
72
|
+
// whitespace (a trailing newline from the JSONL's own text-block boundary,
|
|
73
|
+
// for instance) would hash differently from the SAME text read back post-
|
|
74
|
+
// trim by the completed-turn path, defeating the dedup this whole mechanism
|
|
75
|
+
// exists to rely on — not just for the documented mid-turn-prefix case
|
|
76
|
+
// below, but for a full, complete, otherwise-identical turn too.
|
|
77
|
+
//
|
|
78
|
+
// Residual (honest, not fixed): PreCompact can still fire MID-turn, before
|
|
79
|
+
// the agent has emitted its final assistant message(s) for this request.
|
|
80
|
+
// When that happens this hook captures a PREFIX of the eventual full
|
|
81
|
+
// assistant_text — same userText, shorter assistantText — which hashes to
|
|
82
|
+
// a DIFFERENT dedup key than the later completed-turn capture. That is a
|
|
83
|
+
// genuine possible duplicate (a "prefix episode" alongside the full one),
|
|
84
|
+
// bounded to at most one extra episode per PreCompact-interrupted turn. We
|
|
85
|
+
// deliberately do NOT special-case this by weakening the dedup key (e.g.
|
|
86
|
+
// hashing on entity_id|user_text alone) — that would blind the general
|
|
87
|
+
// dedup to legitimately different answers to a repeated question.
|
|
88
|
+
//
|
|
89
|
+
// Residual class 2 — user_text normalization: userText here is read
|
|
90
|
+
// straight off the transcript's last real user-turn content block(s)
|
|
91
|
+
// (extractText, '\n'-joined if Claude Code ever splits one turn's content
|
|
92
|
+
// into multiple 'text' blocks) and is NOT trimmed, matching the completed-
|
|
93
|
+
// turn path's own `row.prompt ?? ''` (also untrimmed, read back from the
|
|
94
|
+
// DB's stored prompt column, a plain string with no aggregation step of its
|
|
95
|
+
// own — unlike assistant_text there is no DB trigger to mirror here). Both
|
|
96
|
+
// paths are internally consistent in NOT trimming user_text, so this is not
|
|
97
|
+
// currently a known dedup-breaking mismatch — but it is an unverified
|
|
98
|
+
// assumption: nothing here proves the transcript's literal user-message
|
|
99
|
+
// text is always byte-identical to the stored prompt column (a future
|
|
100
|
+
// prompt-construction change, or a Claude Code content-block split, could
|
|
101
|
+
// silently diverge them). Documented rather than asserted-safe.
|
|
102
|
+
//
|
|
103
|
+
// Residual class 3 — guardrail retry turns (guardrail_meta.final_text):
|
|
104
|
+
// when a guardrail check fails, runGuardrailRetryTurn (session-runner.ts)
|
|
105
|
+
// feeds the violation back as a NEW real user-role message on the SAME
|
|
106
|
+
// claude session/transcript, then accumulates the revised reply. The DB's
|
|
107
|
+
// assistant_text trigger aggregates every assistant_text event emitted for
|
|
108
|
+
// the whole request_id regardless of which sub-turn produced it — original
|
|
109
|
+
// attempt AND revision both — and guardrail_meta.final_text is what
|
|
110
|
+
// actually gets pinned as canonical. If PreCompact fires during or after a
|
|
111
|
+
// guardrail retry, lastTurnFromTranscript walks back to that INTERNAL
|
|
112
|
+
// feedback message as "the last genuine user turn" (it has real text and no
|
|
113
|
+
// way to be distinguished from a genuine human prompt at this layer) — so
|
|
114
|
+
// BOTH userText (the guardrail feedback prompt, not the original caller's
|
|
115
|
+
// text) and assistantText (only the revision, not original+revision) can
|
|
116
|
+
// diverge from what the completed-turn path eventually captures. Unlike the
|
|
117
|
+
// mid-turn-prefix residual above, this is not a strict prefix relationship
|
|
118
|
+
// and not bounded to a whitespace/length difference — it is a different
|
|
119
|
+
// exchange shape entirely. Same posture as the other two residuals: honest,
|
|
120
|
+
// documented, not papered over with a weakened dedup key.
|
|
121
|
+
|
|
122
|
+
const fs = require('fs');
|
|
123
|
+
const path = require('path');
|
|
124
|
+
const os = require('os');
|
|
125
|
+
|
|
126
|
+
function readStdinSync() {
|
|
127
|
+
try {
|
|
128
|
+
return fs.readFileSync(0, 'utf-8');
|
|
129
|
+
} catch {
|
|
130
|
+
return '';
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function extractText(content) {
|
|
135
|
+
if (typeof content === 'string') return content;
|
|
136
|
+
if (Array.isArray(content)) {
|
|
137
|
+
return content
|
|
138
|
+
.filter((b) => b && b.type === 'text' && typeof b.text === 'string')
|
|
139
|
+
.map((b) => b.text)
|
|
140
|
+
.join('\n');
|
|
141
|
+
}
|
|
142
|
+
return '';
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Same block-filtering as extractText, but joined with '' — the DB's
|
|
146
|
+
// string_agg(delta, '') separator — for the assistant-side aggregation
|
|
147
|
+
// specifically. In practice a single assistant message content array never
|
|
148
|
+
// carries two adjacent 'text' blocks (the API always merges them), so this
|
|
149
|
+
// only differs from extractText's '\n' join in that hypothetical case; kept
|
|
150
|
+
// separate anyway so the assistant path is exactly, not approximately,
|
|
151
|
+
// faithful to the trigger it's mirroring.
|
|
152
|
+
function extractAssistantText(content) {
|
|
153
|
+
if (typeof content === 'string') return content;
|
|
154
|
+
if (Array.isArray(content)) {
|
|
155
|
+
return content
|
|
156
|
+
.filter((b) => b && b.type === 'text' && typeof b.text === 'string')
|
|
157
|
+
.map((b) => b.text)
|
|
158
|
+
.join('');
|
|
159
|
+
}
|
|
160
|
+
return '';
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// A "real" user turn: role:'user' carrying actual typed text. Claude Code
|
|
164
|
+
// also stamps role:'user' on tool_result submissions (content is an array
|
|
165
|
+
// of tool_result blocks only) — extractText() already filters those out to
|
|
166
|
+
// '' since it only pulls type:'text' blocks, so they never look like a
|
|
167
|
+
// turn boundary here.
|
|
168
|
+
function isRealUserTurn(msg) {
|
|
169
|
+
return !!msg && msg.role === 'user' && extractText(msg.content).trim() !== '';
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// Walks the transcript JSONL backwards to find the last genuine user turn
|
|
173
|
+
// (the prompt that started the CURRENT request), then walks forward from
|
|
174
|
+
// there aggregating every top-level assistant entry's text in order — the
|
|
175
|
+
// full "turn" for this request, matching how the DB's
|
|
176
|
+
// _entity_studio_aggregate_assistant_text trigger aggregates
|
|
177
|
+
// entity_runtime_request_events for the same request_id (concatenation in
|
|
178
|
+
// emission order, no separator, tool_use/tool_result/thinking excluded).
|
|
179
|
+
// Multi-assistant-message turns (text → tool_use → tool_result → text →
|
|
180
|
+
// ... → final text) are the norm for agentic sessions — PreCompact can
|
|
181
|
+
// fire mid-turn — so aggregating only the single most recent assistant
|
|
182
|
+
// entry (the old behavior) systematically under-captured relative to what
|
|
183
|
+
// the completed-turn path later reads back, producing a different dedup
|
|
184
|
+
// hash for the same exchange. See the Idempotency comment above for the
|
|
185
|
+
// residual this fix does NOT (and should not) paper over.
|
|
186
|
+
function lastTurnFromTranscript(transcriptPath) {
|
|
187
|
+
try {
|
|
188
|
+
const raw = fs.readFileSync(transcriptPath, 'utf-8');
|
|
189
|
+
const lines = raw.split('\n').filter(Boolean);
|
|
190
|
+
const entries = [];
|
|
191
|
+
for (const line of lines) {
|
|
192
|
+
try {
|
|
193
|
+
entries.push(JSON.parse(line));
|
|
194
|
+
} catch {
|
|
195
|
+
continue;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
let turnStart = -1;
|
|
200
|
+
let userText = '';
|
|
201
|
+
for (let i = entries.length - 1; i >= 0; i--) {
|
|
202
|
+
const entry = entries[i];
|
|
203
|
+
if (entry && entry.isSidechain) continue; // subagent/Task-tool chatter — DB side never sees it either
|
|
204
|
+
const msg = entry && entry.message;
|
|
205
|
+
if (isRealUserTurn(msg)) {
|
|
206
|
+
turnStart = i;
|
|
207
|
+
userText = extractText(msg.content);
|
|
208
|
+
break;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
if (turnStart === -1) {
|
|
212
|
+
return { userText: '', assistantText: '' };
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
let assistantText = '';
|
|
216
|
+
for (let i = turnStart + 1; i < entries.length; i++) {
|
|
217
|
+
const entry = entries[i];
|
|
218
|
+
if (entry && entry.isSidechain) continue;
|
|
219
|
+
const msg = entry && entry.message;
|
|
220
|
+
if (!msg || msg.role !== 'assistant') continue;
|
|
221
|
+
assistantText += extractAssistantText(msg.content); // '' separator — matches string_agg(delta, '')
|
|
222
|
+
}
|
|
223
|
+
return { userText, assistantText };
|
|
224
|
+
} catch (err) {
|
|
225
|
+
process.stderr.write(`[precompact-capture] transcript read failed: ${err.message}\n`);
|
|
226
|
+
return { userText: '', assistantText: '' };
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
async function main() {
|
|
231
|
+
const entityId = process.env.ENTITY_STUDIO_ENTITY_ID || '';
|
|
232
|
+
const requestId = process.env.RUNTIME_REQUEST_ID || '';
|
|
233
|
+
if (!entityId || !requestId) {
|
|
234
|
+
// Not a daemon-spawned, entity-attributed session (or the daemon
|
|
235
|
+
// didn't thread identity through) — nothing to attribute a capture
|
|
236
|
+
// to. Exit clean, never block compaction.
|
|
237
|
+
process.exit(0);
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
const stdin = readStdinSync();
|
|
242
|
+
let hookInput = {};
|
|
243
|
+
try {
|
|
244
|
+
hookInput = JSON.parse(stdin || '{}');
|
|
245
|
+
} catch {
|
|
246
|
+
/* malformed/empty stdin — fall through with {} */
|
|
247
|
+
}
|
|
248
|
+
const transcriptPath = hookInput.transcript_path;
|
|
249
|
+
if (!transcriptPath || !fs.existsSync(transcriptPath)) {
|
|
250
|
+
process.exit(0);
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
const { userText, assistantText: rawAssistantText } = lastTurnFromTranscript(transcriptPath);
|
|
255
|
+
// T8 ship-gate fix: trim to match notifyCaptureIfEnabled's own
|
|
256
|
+
// `(row.assistant_text ?? '').trim()` — see the module-header comment's
|
|
257
|
+
// trim-alignment note for why an untrimmed value here would silently
|
|
258
|
+
// defeat the turn_hash dedup this hook depends on, not just for the
|
|
259
|
+
// documented mid-turn-prefix residual but for complete matching turns too.
|
|
260
|
+
const assistantText = rawAssistantText.trim();
|
|
261
|
+
if (!assistantText) {
|
|
262
|
+
process.exit(0); // nothing produced yet this turn
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
try {
|
|
267
|
+
const { SUPABASE_URL, SUPABASE_ANON_KEY } = require(path.join(__dirname, '..', 'dist', 'config.js'));
|
|
268
|
+
const { buildCapturePayload, postCapture } = require(path.join(__dirname, '..', 'dist', 'memory-capture.js'));
|
|
269
|
+
const payload = buildCapturePayload(
|
|
270
|
+
{ entityId, requestId, surface: 'precompact' },
|
|
271
|
+
userText,
|
|
272
|
+
assistantText,
|
|
273
|
+
{ kind: 'jsonl_ref', path: transcriptPath, host: os.hostname() },
|
|
274
|
+
);
|
|
275
|
+
await postCapture(SUPABASE_URL, SUPABASE_ANON_KEY, payload);
|
|
276
|
+
} catch (err) {
|
|
277
|
+
process.stderr.write(`[precompact-capture] capture failed (non-fatal): ${err.message}\n`);
|
|
278
|
+
}
|
|
279
|
+
process.exit(0);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// Guarded so `require()`ing this file for unit tests (of extractText /
|
|
283
|
+
// extractAssistantText / lastTurnFromTranscript) doesn't also run main()
|
|
284
|
+
// — main() reads real stdin/env and makes a real network call.
|
|
285
|
+
if (require.main === module) {
|
|
286
|
+
main().catch((err) => {
|
|
287
|
+
process.stderr.write(`[precompact-capture] fatal (swallowed): ${err && err.message}\n`);
|
|
288
|
+
process.exit(0);
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
module.exports = { extractText, extractAssistantText, lastTurnFromTranscript, isRealUserTurn };
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Daemon smoke test — verifies the running daemon's health + each agent's
|
|
3
|
+
# binary + auth state. Run AFTER upgrading the daemon to 0.2.14+.
|
|
4
|
+
#
|
|
5
|
+
# Usage: bash scripts/smoke-test.sh
|
|
6
|
+
|
|
7
|
+
set -uo pipefail
|
|
8
|
+
LOCKFILE="${HOME}/.entities-runtime/runtime.json"
|
|
9
|
+
|
|
10
|
+
if [ ! -f "$LOCKFILE" ]; then
|
|
11
|
+
echo "✗ No daemon lockfile at $LOCKFILE — daemon not running"
|
|
12
|
+
exit 1
|
|
13
|
+
fi
|
|
14
|
+
|
|
15
|
+
PORT=$(python3 -c "import json; print(json.load(open('${LOCKFILE}'))['port'])")
|
|
16
|
+
echo "Daemon lockfile port: $PORT"
|
|
17
|
+
|
|
18
|
+
probe() {
|
|
19
|
+
local ep="$1"
|
|
20
|
+
local expect_field="$2"
|
|
21
|
+
local body
|
|
22
|
+
body=$(curl -s --max-time 3 "http://127.0.0.1:${PORT}${ep}")
|
|
23
|
+
if [ -z "$body" ]; then
|
|
24
|
+
echo " ✗ $ep — no response"
|
|
25
|
+
return 1
|
|
26
|
+
fi
|
|
27
|
+
if [ -n "$expect_field" ]; then
|
|
28
|
+
if echo "$body" | python3 -c "import json,sys; d=json.load(sys.stdin); assert '$expect_field' in d or any('$expect_field' in str(v) for v in d.values())" 2>/dev/null; then
|
|
29
|
+
echo " ✓ $ep — $(echo "$body" | python3 -c "import json,sys; print(json.dumps(json.load(sys.stdin), separators=(',',':'))[:120])")"
|
|
30
|
+
else
|
|
31
|
+
echo " ⚠ $ep — missing field '$expect_field': $body" | head -c 200
|
|
32
|
+
echo
|
|
33
|
+
fi
|
|
34
|
+
else
|
|
35
|
+
echo " ✓ $ep — $(echo "$body" | head -c 100)"
|
|
36
|
+
fi
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
echo
|
|
40
|
+
echo "=== Daemon control-server endpoints ==="
|
|
41
|
+
probe /status "version"
|
|
42
|
+
probe /self "runtime"
|
|
43
|
+
probe /stats "inflight" # inflight field added in 0.2.11
|
|
44
|
+
probe /requests "requests"
|
|
45
|
+
probe /projects "projects"
|
|
46
|
+
|
|
47
|
+
echo
|
|
48
|
+
echo "=== Agent binary + auth checks ==="
|
|
49
|
+
check_agent() {
|
|
50
|
+
local name="$1" bin="$2" auth_file="$3" env_var="$4"
|
|
51
|
+
printf " %s: " "$name"
|
|
52
|
+
if ! command -v "$bin" >/dev/null 2>&1; then
|
|
53
|
+
echo "✗ binary missing"
|
|
54
|
+
return
|
|
55
|
+
fi
|
|
56
|
+
local ver
|
|
57
|
+
ver=$("$bin" --version 2>&1 | head -1 | tr -d '\n')
|
|
58
|
+
if [ -n "$env_var" ] && [ -n "${!env_var:-}" ]; then
|
|
59
|
+
echo "✓ $ver — authed via \$$env_var"
|
|
60
|
+
return
|
|
61
|
+
fi
|
|
62
|
+
if [ -f "$HOME/$auth_file" ] && [ "$(stat -f %z "$HOME/$auth_file" 2>/dev/null || stat -c %s "$HOME/$auth_file" 2>/dev/null)" -gt 16 ]; then
|
|
63
|
+
echo "✓ $ver — authed via ~/$auth_file"
|
|
64
|
+
else
|
|
65
|
+
echo "⚠ $ver — NO AUTH (set $env_var or login)"
|
|
66
|
+
fi
|
|
67
|
+
}
|
|
68
|
+
check_agent "claude" claude .claude/.credentials.json ANTHROPIC_API_KEY
|
|
69
|
+
check_agent "codex" codex .codex/auth.json OPENAI_API_KEY
|
|
70
|
+
check_agent "kimi" kimi .kimi/auth.toml KIMI_API_KEY
|
|
71
|
+
check_agent "gemini" gemini .gemini/oauth_creds.json GEMINI_API_KEY
|
|
72
|
+
|
|
73
|
+
echo
|
|
74
|
+
echo "=== Done ==="
|