compact-agent 1.27.2 → 1.28.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/crowcoder.js +48 -3
- package/bin/ecc-hooks.cjs +39 -4
- package/dist/debug.d.ts +97 -0
- package/dist/debug.js +217 -0
- package/dist/debug.js.map +1 -0
- package/dist/index.js +142 -16
- package/dist/index.js.map +1 -1
- package/dist/query.js +56 -10
- package/dist/query.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -5,6 +5,7 @@ import { readFileSync as fsReadFileSync, writeFileSync as fsWriteFileSync, unlin
|
|
|
5
5
|
import { tmpdir } from 'node:os';
|
|
6
6
|
import { join as pathJoin } from 'node:path';
|
|
7
7
|
import { spawnSync } from 'node:child_process';
|
|
8
|
+
import { initDebug, emit as dbgEmit, setDebugLevel, getDebugStatus, tailDebug } from './debug.js';
|
|
8
9
|
import chalk from 'chalk';
|
|
9
10
|
import { loadConfig, saveConfig, configExists, getConfigDir } from './config.js';
|
|
10
11
|
import { resetClient } from './api.js';
|
|
@@ -404,9 +405,19 @@ export function handleSlashCommand(input, config, messages, session, mode) {
|
|
|
404
405
|
}
|
|
405
406
|
return { handled: true };
|
|
406
407
|
// ── Clear ─────────────────────────────────────────
|
|
407
|
-
case '/clear':
|
|
408
|
+
case '/clear': {
|
|
409
|
+
// Also reset the global state that's keyed to the conversation
|
|
410
|
+
// so Shift+F3 / Shift+F1 / F12 don't surface stale data from
|
|
411
|
+
// before the clear. The main REPL loop replaces `messages` via
|
|
412
|
+
// `newMessages`; we just nuke the side-channel globals.
|
|
413
|
+
const g = globalThis;
|
|
414
|
+
g.__crowcoderQueuedInput = '';
|
|
415
|
+
g.__voiceLastFullResponse = null;
|
|
416
|
+
g.__voiceLastChunk = null;
|
|
417
|
+
g.__lastToolCall = null;
|
|
408
418
|
console.log(chalk.dim(' Conversation cleared.'));
|
|
409
419
|
return { handled: true, newMessages: [] };
|
|
420
|
+
}
|
|
410
421
|
// ── Backtrack — rewind to a prior user turn (Codex audit item 4) ──
|
|
411
422
|
// /back list recent user messages with numbers
|
|
412
423
|
// /back <n> truncate conversation to BEFORE the nth most-recent
|
|
@@ -465,14 +476,23 @@ export function handleSlashCommand(input, config, messages, session, mode) {
|
|
|
465
476
|
case '/fork':
|
|
466
477
|
case '/branch': {
|
|
467
478
|
const forkName = args.trim() || `fork of ${session.name}`;
|
|
468
|
-
//
|
|
469
|
-
//
|
|
470
|
-
|
|
479
|
+
// Snapshot the pre-fork state in a SEPARATE object before
|
|
480
|
+
// mutating the live `session` reference — otherwise both save
|
|
481
|
+
// calls write to whichever ID the mutation has currently set,
|
|
482
|
+
// overwriting the original branch and silently breaking the
|
|
483
|
+
// "previous session reachable via /resume" promise. (Bug found
|
|
484
|
+
// by audit; previously the spread happened AFTER `session.id`
|
|
485
|
+
// was reassigned, so both saves landed under the new ID.)
|
|
471
486
|
const previousId = session.id;
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
487
|
+
const previousSnapshot = {
|
|
488
|
+
...session,
|
|
489
|
+
id: previousId,
|
|
490
|
+
messages: [...messages],
|
|
491
|
+
};
|
|
492
|
+
saveSession(previousSnapshot).catch(() => { });
|
|
493
|
+
// Now mutate the active session in place. The REPL keeps
|
|
494
|
+
// running against `session` so mutation is enough — no need
|
|
495
|
+
// to plumb a swap through the return value.
|
|
476
496
|
session.id = generateSessionId();
|
|
477
497
|
session.name = forkName;
|
|
478
498
|
session.createdAt = new Date().toISOString();
|
|
@@ -544,6 +564,72 @@ export function handleSlashCommand(input, config, messages, session, mode) {
|
|
|
544
564
|
console.log(chalk.dim(` Sending ${result.length} chars from editor…`));
|
|
545
565
|
return { handled: true, injectPrompt: result };
|
|
546
566
|
}
|
|
567
|
+
// ── Debug — toggle instrumentation + tail event log ──
|
|
568
|
+
// Surface for the NDJSON debug stream written to
|
|
569
|
+
// ~/.compact-agent/debug/<sessionId>.jsonl. Used by reviewers
|
|
570
|
+
// driving the agent + by users diagnosing their own issues.
|
|
571
|
+
//
|
|
572
|
+
// /debug show current level + log path + event count
|
|
573
|
+
// /debug on [level] turn instrumentation on (default level: info)
|
|
574
|
+
// /debug off turn instrumentation off (existing log file is kept)
|
|
575
|
+
// /debug tail [N] print the last N events (default 20) inline
|
|
576
|
+
case '/debug': {
|
|
577
|
+
const parts = args.trim().split(/\s+/).filter(Boolean);
|
|
578
|
+
const sub = (parts[0] || '').toLowerCase();
|
|
579
|
+
if (!sub) {
|
|
580
|
+
const s = getDebugStatus();
|
|
581
|
+
console.log(chalk.dim(` Debug level: ${s.level}`));
|
|
582
|
+
console.log(chalk.dim(` Log file: ${s.logPath || '(no log — level is off)'}`));
|
|
583
|
+
console.log(chalk.dim(` Events: ${s.eventCount} this session`));
|
|
584
|
+
console.log(chalk.dim(` Uptime: ${(s.uptimeMs / 1000).toFixed(1)}s`));
|
|
585
|
+
console.log('');
|
|
586
|
+
console.log(chalk.dim(` /debug on [info|debug|trace]`));
|
|
587
|
+
console.log(chalk.dim(` /debug off`));
|
|
588
|
+
console.log(chalk.dim(` /debug tail [N]`));
|
|
589
|
+
return { handled: true };
|
|
590
|
+
}
|
|
591
|
+
if (sub === 'off') {
|
|
592
|
+
setDebugLevel('off');
|
|
593
|
+
console.log(chalk.green(' Debug: off'));
|
|
594
|
+
return { handled: true };
|
|
595
|
+
}
|
|
596
|
+
if (sub === 'on') {
|
|
597
|
+
const lvl = (parts[1] || 'info').toLowerCase();
|
|
598
|
+
if (lvl !== 'info' && lvl !== 'debug' && lvl !== 'trace') {
|
|
599
|
+
console.log(chalk.yellow(` Unknown level "${lvl}". Use info, debug, or trace.`));
|
|
600
|
+
return { handled: true };
|
|
601
|
+
}
|
|
602
|
+
setDebugLevel(lvl);
|
|
603
|
+
const s = getDebugStatus();
|
|
604
|
+
console.log(chalk.green(` Debug: ${lvl} — writing to ${s.logPath}`));
|
|
605
|
+
return { handled: true };
|
|
606
|
+
}
|
|
607
|
+
if (sub === 'tail') {
|
|
608
|
+
const n = parseInt(parts[1] || '20', 10);
|
|
609
|
+
const lines = tailDebug(Number.isFinite(n) && n > 0 ? n : 20);
|
|
610
|
+
if (lines.length === 0) {
|
|
611
|
+
console.log(chalk.dim(' (no events — turn on with /debug on)'));
|
|
612
|
+
return { handled: true };
|
|
613
|
+
}
|
|
614
|
+
console.log(chalk.dim(` Last ${lines.length} debug events:`));
|
|
615
|
+
for (const ln of lines) {
|
|
616
|
+
try {
|
|
617
|
+
const rec = JSON.parse(ln);
|
|
618
|
+
const ts = String(rec.rel || 0).padStart(6) + 'ms';
|
|
619
|
+
const lvl = (rec.lvl || '').toUpperCase().padEnd(5);
|
|
620
|
+
const ev = rec.ev || '';
|
|
621
|
+
const d = rec.data ? ' ' + JSON.stringify(rec.data).slice(0, 120) : '';
|
|
622
|
+
console.log(chalk.dim(` ${ts} ${lvl} ${ev}${d}`));
|
|
623
|
+
}
|
|
624
|
+
catch {
|
|
625
|
+
console.log(chalk.dim(` ${ln.slice(0, 200)}`));
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
return { handled: true };
|
|
629
|
+
}
|
|
630
|
+
console.log(chalk.yellow(` Unknown /debug subcommand "${sub}". Try: /debug, /debug on, /debug off, /debug tail`));
|
|
631
|
+
return { handled: true };
|
|
632
|
+
}
|
|
547
633
|
// ── History ───────────────────────────────────────
|
|
548
634
|
case '/history': {
|
|
549
635
|
const stats = getCompactionStats(messages);
|
|
@@ -2033,11 +2119,22 @@ export function handleSlashCommand(input, config, messages, session, mode) {
|
|
|
2033
2119
|
// user is testing the pipeline or running under a terminal that strips
|
|
2034
2120
|
// function keys. Records up to 30s, transcribes, injects as next prompt.
|
|
2035
2121
|
case '/dictate': {
|
|
2036
|
-
|
|
2037
|
-
|
|
2122
|
+
// Parse + clamp the duration argument.
|
|
2123
|
+
// /dictate → 30s default
|
|
2124
|
+
// /dictate 60 → 60s, clamped to [1, 300]
|
|
2125
|
+
// /dictate 0 → 30s (user clearly wanted default or
|
|
2126
|
+
// cancel; previously parseInt(0) || 30
|
|
2127
|
+
// gave 30 silently)
|
|
2128
|
+
// /dictate -5 → 30s (negative is nonsense)
|
|
2129
|
+
// /dictate abc → 30s default
|
|
2130
|
+
const parsed = parseInt(args, 10);
|
|
2131
|
+
const sec = Number.isFinite(parsed) && parsed > 0
|
|
2132
|
+
? Math.min(300, parsed)
|
|
2133
|
+
: 30;
|
|
2134
|
+
console.log(chalk.dim(` /dictate — recording up to ${sec}s…`));
|
|
2038
2135
|
// Return as an async-injected prompt; we resolve the recording
|
|
2039
2136
|
// synchronously here for simplicity (REPL is blocking anyway).
|
|
2040
|
-
return { handled: true, injectPrompt: '__DICTATE__' +
|
|
2137
|
+
return { handled: true, injectPrompt: '__DICTATE__' + sec };
|
|
2041
2138
|
}
|
|
2042
2139
|
// /accessibility — show or toggle the accessibility sub-block
|
|
2043
2140
|
// /accessibility — print status
|
|
@@ -2203,6 +2300,19 @@ async function main() {
|
|
|
2203
2300
|
// Create session
|
|
2204
2301
|
const mode = { current: 'dev' };
|
|
2205
2302
|
const session = createSession(process.cwd(), config.model, config.provider, mode.current);
|
|
2303
|
+
// ── Debug instrumentation ─────────────────────────────────
|
|
2304
|
+
// Initialize early so subsequent emit() calls land in the right file.
|
|
2305
|
+
// Reads $COMPACT_AGENT_DEBUG which bin/crowcoder.js may have set from
|
|
2306
|
+
// the --debug CLI flag. Level 'off' is a no-op; non-off opens an
|
|
2307
|
+
// NDJSON log at ~/.compact-agent/debug/<sessionId>.jsonl.
|
|
2308
|
+
initDebug(session.id);
|
|
2309
|
+
dbgEmit('info', 'session.start', {
|
|
2310
|
+
cwd: process.cwd(),
|
|
2311
|
+
model: config.model,
|
|
2312
|
+
provider: config.provider,
|
|
2313
|
+
mode: mode.current,
|
|
2314
|
+
permissionMode: config.permissionMode,
|
|
2315
|
+
});
|
|
2206
2316
|
const messages = [];
|
|
2207
2317
|
// Session start hook + memory persistence
|
|
2208
2318
|
await runHooks({ event: 'SessionStart', sessionId: session.id, cwd: process.cwd(), permissionMode: config.permissionMode });
|
|
@@ -2534,15 +2644,29 @@ async function main() {
|
|
|
2534
2644
|
// Second Esc within window — fire /back.
|
|
2535
2645
|
lastEscapeMs = 0;
|
|
2536
2646
|
// Enqueue the slash command as queued input. The REPL loop
|
|
2537
|
-
// picks it up +
|
|
2647
|
+
// picks it up + dispatches /back on the next iteration.
|
|
2538
2648
|
globalThis.__crowcoderQueuedInput = '/back\n';
|
|
2539
2649
|
announce('Esc-Esc', 'Rewinding to previous user turn.');
|
|
2540
|
-
//
|
|
2541
|
-
//
|
|
2650
|
+
// Resolve the pending rl.question() so the main loop
|
|
2651
|
+
// actually moves on. Previously this used stdin.write('\n')
|
|
2652
|
+
// which DOESN'T interrupt readline — it merely adds a
|
|
2653
|
+
// newline to the input stream that readline reads as if
|
|
2654
|
+
// the user typed it, leaving the REPL stuck until the user
|
|
2655
|
+
// pressed Enter manually. emit('line', '') triggers the
|
|
2656
|
+
// 'line' event that rl.question internally listens for,
|
|
2657
|
+
// resolving the promise immediately. (Bug found by audit.)
|
|
2542
2658
|
try {
|
|
2543
|
-
|
|
2659
|
+
rl.emit('line', '');
|
|
2660
|
+
}
|
|
2661
|
+
catch {
|
|
2662
|
+
// Fallback path if rl.emit isn't accepted for some reason
|
|
2663
|
+
// (e.g. on a future readline version): still nudge via
|
|
2664
|
+
// stdin so the user can recover by pressing any key.
|
|
2665
|
+
try {
|
|
2666
|
+
stdin.write('\n');
|
|
2667
|
+
}
|
|
2668
|
+
catch { /* noop */ }
|
|
2544
2669
|
}
|
|
2545
|
-
catch { /* noop */ }
|
|
2546
2670
|
return;
|
|
2547
2671
|
}
|
|
2548
2672
|
lastEscapeMs = now;
|
|
@@ -2868,6 +2992,8 @@ async function main() {
|
|
|
2868
2992
|
}
|
|
2869
2993
|
// Slash commands
|
|
2870
2994
|
if (trimmed.startsWith('/')) {
|
|
2995
|
+
// Truncate arg preview so debug logs don't blow up on /editor seeds etc.
|
|
2996
|
+
dbgEmit('debug', 'slash.dispatch', { input: trimmed.slice(0, 200) });
|
|
2871
2997
|
const result = handleSlashCommand(trimmed, config, messages, session, mode);
|
|
2872
2998
|
if (result.shouldExit)
|
|
2873
2999
|
break;
|