compact-agent 1.24.2 → 1.26.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 +140 -277
- package/bin/ecc-hooks.cjs +394 -394
- package/dist/codemaps.js +3 -2
- package/dist/codemaps.js.map +1 -1
- package/dist/config.d.ts +17 -0
- package/dist/config.js +89 -5
- package/dist/config.js.map +1 -1
- package/dist/cost-tracker.js +1 -1
- package/dist/ecc.js +10 -10
- package/dist/ecc.js.map +1 -1
- package/dist/hooks.js +3 -3
- package/dist/hooks.js.map +1 -1
- package/dist/index.js +203 -19
- package/dist/index.js.map +1 -1
- package/dist/learning.js +1 -1
- package/dist/login.js +1 -1
- package/dist/memory.js +1 -1
- package/dist/mempalace/index.d.ts +6 -2
- package/dist/mempalace/index.js +10 -3
- package/dist/mempalace/index.js.map +1 -1
- package/dist/mempalace/store.d.ts +2 -2
- package/dist/mempalace/store.js +5 -5
- package/dist/mempalace/store.js.map +1 -1
- package/dist/mempalace/types.d.ts +4 -4
- package/dist/mempalace/types.js +2 -2
- package/dist/package-detect.d.ts +2 -2
- package/dist/package-detect.js +8 -6
- package/dist/package-detect.js.map +1 -1
- package/dist/query.js +17 -0
- package/dist/query.js.map +1 -1
- package/dist/rules.js +795 -795
- package/dist/rules.js.map +1 -1
- package/dist/sessions.js +1 -1
- package/dist/skills.js +1 -1
- package/dist/stitch.js +132 -132
- package/dist/system-prompt.js +85 -85
- package/dist/theme.js +1 -1
- package/dist/theme.js.map +1 -1
- package/dist/tools/stitch.d.ts +1 -1
- package/dist/users.js +1 -1
- package/dist/voice.d.ts +6 -0
- package/dist/voice.js +16 -0
- package/dist/voice.js.map +1 -1
- package/dist/walkthrough.js +111 -111
- package/package.json +73 -68
- package/resources/ecc/skills/repo-scan/SKILL.md +15 -15
package/dist/index.js
CHANGED
|
@@ -118,7 +118,7 @@ async function setupWizard(rl) {
|
|
|
118
118
|
// user can make an informed choice — most users want this on.
|
|
119
119
|
console.log(chalk.white('\n MemPalace persistent memory'));
|
|
120
120
|
console.log(chalk.dim(' Lets the agent remember your preferences, codebase landmarks, and lessons across sessions.'));
|
|
121
|
-
console.log(chalk.dim(' Two stores: global (~/.
|
|
121
|
+
console.log(chalk.dim(' Two stores: global (~/.compact-agent/memory) for cross-project facts, project (.compact-agent/memory'));
|
|
122
122
|
console.log(chalk.dim(' in each repo) for codebase-specific knowledge. Searchable via /memory or by the agent itself.'));
|
|
123
123
|
console.log(chalk.dim(' Zero external dependencies; storage is local JSON files. Can be toggled anytime via /memory disable.'));
|
|
124
124
|
const memoryChoice = await rl.question(chalk.yellow(' Enable MemPalace memory? [Y/n]: '));
|
|
@@ -319,6 +319,8 @@ export function handleSlashCommand(input, config, messages, session, mode) {
|
|
|
319
319
|
console.log(d(' ') + c('/accessibility') + d(' — toggle screen-reader mode, audio cues, destructive-confirm'));
|
|
320
320
|
console.log(d(' Status hotkeys: F1 what now · F2 where am I · F3 read full · F4 read summary'));
|
|
321
321
|
console.log(d(' Playback hotkeys: F5 dictate · F6 pause · F7 replay · F8 skip · F9 speed+ · F10 speed–'));
|
|
322
|
+
console.log(d(' Read hotkeys: F11 input buffer · F12 your last turn'));
|
|
323
|
+
console.log(d(' Shift+Fn: Shift+F1 queued · F2 key pool · F3 last tool · F4 toggle SR · F5 cancel · F6 panic · F12 hotkey list'));
|
|
322
324
|
console.log(h('\n ── Stitch (Google AI UI/UX design) ──'));
|
|
323
325
|
console.log(d(' Use ') + c('/mode design') + d(' or ') + c('/design <task>') + d(' for UI work — the agent uses Stitch automatically.'));
|
|
324
326
|
console.log(d(' ') + c('/stitch') + d(' — show config status'));
|
|
@@ -707,7 +709,7 @@ export function handleSlashCommand(input, config, messages, session, mode) {
|
|
|
707
709
|
case '/hooks': {
|
|
708
710
|
const hooks = listHooks();
|
|
709
711
|
if (hooks.length === 0) {
|
|
710
|
-
console.log(chalk.dim(' No hooks configured. Edit ~/.
|
|
712
|
+
console.log(chalk.dim(' No hooks configured. Edit ~/.compact-agent/hooks.json'));
|
|
711
713
|
}
|
|
712
714
|
else {
|
|
713
715
|
console.log(chalk.cyan(`\n Hooks (${hooks.length}):`));
|
|
@@ -932,7 +934,7 @@ export function handleSlashCommand(input, config, messages, session, mode) {
|
|
|
932
934
|
if (items.length > 20)
|
|
933
935
|
console.log(chalk.dim(` … and ${items.length - 20} more`));
|
|
934
936
|
}
|
|
935
|
-
console.log(chalk.dim('\n Act on findings with /prune (instincts) or by editing ~/.
|
|
937
|
+
console.log(chalk.dim('\n Act on findings with /prune (instincts) or by editing ~/.compact-agent/skills/.\n'));
|
|
936
938
|
return { handled: true };
|
|
937
939
|
}
|
|
938
940
|
// ── ECC agent shortcuts (5 commands, ECC audit item 4) ─────
|
|
@@ -1580,7 +1582,7 @@ export function handleSlashCommand(input, config, messages, session, mode) {
|
|
|
1580
1582
|
return { handled: true };
|
|
1581
1583
|
}
|
|
1582
1584
|
saveStitchConfig(key);
|
|
1583
|
-
console.log(chalk.green(` Stitch API key saved to ~/.
|
|
1585
|
+
console.log(chalk.green(` Stitch API key saved to ~/.compact-agent/stitch.json`));
|
|
1584
1586
|
console.log(chalk.dim(' The `stitch` tool is now available to the agent.'));
|
|
1585
1587
|
console.log(chalk.dim(' Restart the REPL for the tool to appear in /tools.'));
|
|
1586
1588
|
return { handled: true };
|
|
@@ -1707,7 +1709,7 @@ export function handleSlashCommand(input, config, messages, session, mode) {
|
|
|
1707
1709
|
return { handled: true };
|
|
1708
1710
|
}
|
|
1709
1711
|
// ── Reset hooks (clear stale entries from old installs) ──
|
|
1710
|
-
// Wipes ~/.
|
|
1712
|
+
// Wipes ~/.compact-agent/hooks.json, clears the in-memory quarantine, and
|
|
1711
1713
|
// re-seeds the ECC default hooks against this install's bin path. Use
|
|
1712
1714
|
// this when stale dev-machine paths from a prior install are crashing
|
|
1713
1715
|
// every tool call.
|
|
@@ -2128,36 +2130,218 @@ async function main() {
|
|
|
2128
2130
|
const { describeStatus, describeLocation } = await import('./status.js');
|
|
2129
2131
|
readlineCb.emitKeypressEvents(stdin);
|
|
2130
2132
|
// Set of keys we intercept. Anything not in this set falls through to
|
|
2131
|
-
// readline so normal typing isn't affected. All bare F-keys
|
|
2132
|
-
//
|
|
2133
|
+
// readline so normal typing isn't affected. All bare or shifted F-keys
|
|
2134
|
+
// — no Insert/CapsLock/Ctrl-Option modifiers, so we never collide with
|
|
2135
|
+
// NVDA, JAWS, Narrator, Orca, or VoiceOver. F11 + F12 are also browser-
|
|
2136
|
+
// reserved keys (fullscreen / devtools) and therefore reliably free in
|
|
2137
|
+
// every terminal that isn't masquerading as a browser.
|
|
2133
2138
|
const INTERCEPT = new Set([
|
|
2134
|
-
'f1', 'f2', 'f3', 'f4', // status announcements
|
|
2135
|
-
'f5', 'f6', 'f7', 'f8', 'f9', 'f10', // dictation + playback
|
|
2139
|
+
'f1', 'f2', 'f3', 'f4', // status announcements (bare)
|
|
2140
|
+
'f5', 'f6', 'f7', 'f8', 'f9', 'f10', // dictation + playback (bare)
|
|
2141
|
+
'f11', 'f12', // Tier 1: input + last turn (bare)
|
|
2142
|
+
// Shifted F-keys carry the Tier-2 and Tier-3 a11y functions. Each
|
|
2143
|
+
// is checked alongside key.shift below, so a bare F1 still routes
|
|
2144
|
+
// to "status" while Shift+F1 routes to "queued input."
|
|
2136
2145
|
]);
|
|
2137
2146
|
// Define the hotkey listener as a NAMED, TAGGED function so
|
|
2138
2147
|
// suppressInputDuringStream() in query.ts can isolate it among stdin's
|
|
2139
2148
|
// 'keypress' listeners. During streaming we detach readline's own
|
|
2140
2149
|
// keypress listener (to prevent echo + line-buffer pollution) while
|
|
2141
|
-
// keeping this one attached so F1–
|
|
2150
|
+
// keeping this one attached so F1–F12 keep working mid-response.
|
|
2142
2151
|
const hotkeyListener = function hotkeyListener(_str, key) {
|
|
2143
2152
|
if (!key)
|
|
2144
2153
|
return;
|
|
2145
2154
|
const name = String(key.name || '').toLowerCase();
|
|
2146
2155
|
if (!INTERCEPT.has(name))
|
|
2147
2156
|
return;
|
|
2157
|
+
const shift = !!key.shift;
|
|
2148
2158
|
const a = getAccessibilityConfig(config);
|
|
2149
2159
|
const tts = getTtsConfig(config);
|
|
2150
|
-
//
|
|
2151
|
-
// and
|
|
2152
|
-
//
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2160
|
+
// Helper: print to stdout (always — picked up by the OS screen reader)
|
|
2161
|
+
// and optionally layer TTS on top if a key is configured. Used by
|
|
2162
|
+
// every "announce something" branch in the new tier of bindings.
|
|
2163
|
+
const announce = (label, text) => {
|
|
2164
|
+
console.log(chalk.dim(` [${label}] `) + text);
|
|
2165
|
+
if (tts.apiKey) {
|
|
2166
|
+
speak(text, config, { voiceId: tts.assistantVoiceId }).catch(() => { });
|
|
2167
|
+
}
|
|
2168
|
+
};
|
|
2169
|
+
// STATUS hotkeys always work, even when voice is off and even when
|
|
2170
|
+
// there's no TTS key — they print to stdout so an OS-level screen
|
|
2171
|
+
// reader still has something to announce. TTS layers on top when a
|
|
2172
|
+
// key is present. Applies to:
|
|
2173
|
+
// - F1–F4 : original status / location / replay set
|
|
2174
|
+
// - F11/F12 : input buffer / last user turn (Tier 1)
|
|
2175
|
+
// - Shift+* : every shifted F-key is information or control,
|
|
2176
|
+
// never voice-only
|
|
2177
|
+
const isStatusKey = name === 'f1' || name === 'f2' || name === 'f3' || name === 'f4' ||
|
|
2178
|
+
name === 'f11' || name === 'f12' || shift;
|
|
2179
|
+
// F5–F10 (bare) are DICTATION/PLAYBACK hotkeys — they only make
|
|
2180
|
+
// sense when voice features are enabled. Bail early to avoid
|
|
2181
|
+
// spurious ffmpeg spawns and "TTS not configured" log lines.
|
|
2159
2182
|
if (!isStatusKey && !isVoiceEnabled(config))
|
|
2160
2183
|
return;
|
|
2184
|
+
// ──────────────────────────────────────────────────────────────
|
|
2185
|
+
// Tier 2 + 3: shifted F-keys.
|
|
2186
|
+
//
|
|
2187
|
+
// Dispatched BEFORE the bare F-key branches because Shift+F5
|
|
2188
|
+
// shares its `name` ('f5') with the bare F5 dictation toggle —
|
|
2189
|
+
// we want the shifted variant to win without each bare branch
|
|
2190
|
+
// having to add a `!shift` guard.
|
|
2191
|
+
//
|
|
2192
|
+
// Shift+F1 queued input ("3 messages queued: …")
|
|
2193
|
+
// Shift+F2 key-pool health ("3 keys healthy, 1 cooling")
|
|
2194
|
+
// Shift+F3 last tool-call ("bash: ok, 'ls -la' → …")
|
|
2195
|
+
// Shift+F4 toggle screen-reader (persists to config.json)
|
|
2196
|
+
// Shift+F5 soft-cancel turn (graceful abort, partial kept)
|
|
2197
|
+
// Shift+F6 panic-stop TTS (silences for 5s, drops queue)
|
|
2198
|
+
// Shift+F12 read hotkey list (discoverability)
|
|
2199
|
+
//
|
|
2200
|
+
// Unbound shifted F-keys are no-ops and fall through (returning
|
|
2201
|
+
// here keeps them out of the bare-F-key branches below).
|
|
2202
|
+
// ──────────────────────────────────────────────────────────────
|
|
2203
|
+
if (shift) {
|
|
2204
|
+
// ── Shift+F1: queued input ─────────────────────────
|
|
2205
|
+
if (name === 'f1') {
|
|
2206
|
+
const g = globalThis;
|
|
2207
|
+
const q = (g.__crowcoderQueuedInput || '').trim();
|
|
2208
|
+
announce('Shift+F1', q
|
|
2209
|
+
? `Queued during last chain: ${q.slice(0, 200)}`
|
|
2210
|
+
: 'Nothing queued.');
|
|
2211
|
+
return;
|
|
2212
|
+
}
|
|
2213
|
+
// ── Shift+F2: key-pool health ──────────────────────
|
|
2214
|
+
if (name === 'f2') {
|
|
2215
|
+
const ks = keyPoolStatus();
|
|
2216
|
+
if (ks.length === 0) {
|
|
2217
|
+
announce('Shift+F2', 'Key pool: 1 key (no pool configured). Use /keys add to add more.');
|
|
2218
|
+
return;
|
|
2219
|
+
}
|
|
2220
|
+
const healthy = ks.filter((s) => s.healthy).length;
|
|
2221
|
+
const cooling = ks.length - healthy;
|
|
2222
|
+
const cooldownNotes = ks
|
|
2223
|
+
.filter((s) => !s.healthy && s.coolDownRemainingSec)
|
|
2224
|
+
.map((s) => `${s.tail} cooling ${s.coolDownRemainingSec}s`)
|
|
2225
|
+
.join(', ');
|
|
2226
|
+
const text = cooling > 0
|
|
2227
|
+
? `Key pool: ${healthy} healthy, ${cooling} cooling. ${cooldownNotes}.`
|
|
2228
|
+
: `Key pool: ${healthy} healthy, all keys ready.`;
|
|
2229
|
+
announce('Shift+F2', text);
|
|
2230
|
+
return;
|
|
2231
|
+
}
|
|
2232
|
+
// ── Shift+F3: last tool call ───────────────────────
|
|
2233
|
+
if (name === 'f3') {
|
|
2234
|
+
const g = globalThis;
|
|
2235
|
+
const tc = g.__lastToolCall;
|
|
2236
|
+
if (!tc) {
|
|
2237
|
+
announce('Shift+F3', 'No tool calls yet this session.');
|
|
2238
|
+
return;
|
|
2239
|
+
}
|
|
2240
|
+
const status = tc.isError ? 'error' : 'ok';
|
|
2241
|
+
// Output preview kept short for TTS; full output is already on
|
|
2242
|
+
// stdout from the original tool-call print.
|
|
2243
|
+
announce('Shift+F3', `Last tool: ${tc.name}, ${status}. ${tc.argsPreview}${tc.outputPreview ? ' → ' + tc.outputPreview.slice(0, 100) : ''}`);
|
|
2244
|
+
return;
|
|
2245
|
+
}
|
|
2246
|
+
// ── Shift+F4: toggle screen-reader mode ────────────
|
|
2247
|
+
if (name === 'f4') {
|
|
2248
|
+
config.voice = config.voice || {};
|
|
2249
|
+
config.voice.accessibility = config.voice.accessibility || {};
|
|
2250
|
+
const cur = config.voice.accessibility.screenReader === true;
|
|
2251
|
+
config.voice.accessibility.screenReader = !cur;
|
|
2252
|
+
saveConfig(config);
|
|
2253
|
+
const text = !cur
|
|
2254
|
+
? 'Screen-reader mode ON. ANSI colors stripped. Restart recommended for full effect.'
|
|
2255
|
+
: 'Screen-reader mode OFF. Colors restored on next prompt.';
|
|
2256
|
+
announce('Shift+F4', text);
|
|
2257
|
+
return;
|
|
2258
|
+
}
|
|
2259
|
+
// ── Shift+F5: soft-cancel current turn ─────────────
|
|
2260
|
+
if (name === 'f5') {
|
|
2261
|
+
const g = globalThis;
|
|
2262
|
+
if (g.__turnAbortCtl && !g.__turnAbortCtl.signal.aborted) {
|
|
2263
|
+
try {
|
|
2264
|
+
g.__turnAbortCtl.abort();
|
|
2265
|
+
}
|
|
2266
|
+
catch { /* noop */ }
|
|
2267
|
+
announce('Shift+F5', 'Turn cancelled. Partial response kept.');
|
|
2268
|
+
}
|
|
2269
|
+
else {
|
|
2270
|
+
announce('Shift+F5', 'No turn in progress.');
|
|
2271
|
+
}
|
|
2272
|
+
return;
|
|
2273
|
+
}
|
|
2274
|
+
// ── Shift+F6: panic-stop TTS ───────────────────────
|
|
2275
|
+
if (name === 'f6') {
|
|
2276
|
+
// Abort the current playback (same as F6/F8) AND open a 5-second
|
|
2277
|
+
// suppression window so incidental utterances (error
|
|
2278
|
+
// announcements, mode switches, audio cues fired by other code
|
|
2279
|
+
// paths) can't immediately fill the silence.
|
|
2280
|
+
const g = globalThis;
|
|
2281
|
+
if (g.__voicePlaybackCtl && !g.__voicePlaybackCtl.signal.aborted) {
|
|
2282
|
+
try {
|
|
2283
|
+
g.__voicePlaybackCtl.abort();
|
|
2284
|
+
}
|
|
2285
|
+
catch { /* noop */ }
|
|
2286
|
+
}
|
|
2287
|
+
g.__voiceSuppressUntilMs = Date.now() + 5000;
|
|
2288
|
+
// Print only — don't speak this acknowledgement (would defeat
|
|
2289
|
+
// the purpose of "shut up now").
|
|
2290
|
+
console.log(chalk.dim(' [Shift+F6] TTS panic-stop — silenced for 5s.'));
|
|
2291
|
+
return;
|
|
2292
|
+
}
|
|
2293
|
+
// ── Shift+F12: read hotkey list ────────────────────
|
|
2294
|
+
if (name === 'f12') {
|
|
2295
|
+
const lines = [
|
|
2296
|
+
'Hotkey reference.',
|
|
2297
|
+
'F1 status. F2 location. F3 read full last response. F4 read summary.',
|
|
2298
|
+
'F5 dictate. F6 pause. F7 replay. F8 skip. F9 speed up. F10 slow down.',
|
|
2299
|
+
'F11 read input buffer. F12 read your previous turn.',
|
|
2300
|
+
'Shift+F1 queued input. Shift+F2 key pool. Shift+F3 last tool. Shift+F4 toggle screen-reader.',
|
|
2301
|
+
'Shift+F5 soft-cancel turn. Shift+F6 panic-stop TTS. Shift+F12 this list.',
|
|
2302
|
+
];
|
|
2303
|
+
for (const ln of lines)
|
|
2304
|
+
console.log(chalk.dim(' [Shift+F12] ') + ln);
|
|
2305
|
+
if (tts.apiKey) {
|
|
2306
|
+
// Speak as one continuous string so the chunker can pace it.
|
|
2307
|
+
speak(lines.join(' '), config, { voiceId: tts.assistantVoiceId }).catch(() => { });
|
|
2308
|
+
}
|
|
2309
|
+
return;
|
|
2310
|
+
}
|
|
2311
|
+
// Any other shifted F-key: no-op (don't fall through to bare).
|
|
2312
|
+
return;
|
|
2313
|
+
}
|
|
2314
|
+
// ── F11: read current input buffer (Tier 1, bare) ──
|
|
2315
|
+
if (name === 'f11') {
|
|
2316
|
+
// rl.line is readline's internal "what the user has typed so far
|
|
2317
|
+
// on the current prompt." Empty string when the prompt is fresh
|
|
2318
|
+
// or the buffer was just submitted.
|
|
2319
|
+
const buf = rl.line ?? '';
|
|
2320
|
+
announce('F11', buf
|
|
2321
|
+
? `Input buffer: ${buf}`
|
|
2322
|
+
: 'Input buffer is empty.');
|
|
2323
|
+
return;
|
|
2324
|
+
}
|
|
2325
|
+
// ── F12: read previous submitted user turn (Tier 1) ──
|
|
2326
|
+
if (name === 'f12') {
|
|
2327
|
+
// Walk messages newest-first looking for the most-recent user
|
|
2328
|
+
// message. `messages` is the live REPL conversation array; the
|
|
2329
|
+
// last user entry is the prompt the model just answered (or is
|
|
2330
|
+
// answering). Skips system-injected "auto-resume" markers and
|
|
2331
|
+
// tool-result envelopes (those have role 'tool', not 'user').
|
|
2332
|
+
let last = null;
|
|
2333
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
2334
|
+
const m = messages[i];
|
|
2335
|
+
if (m.role === 'user' && typeof m.content === 'string' && m.content.trim()) {
|
|
2336
|
+
last = m.content;
|
|
2337
|
+
break;
|
|
2338
|
+
}
|
|
2339
|
+
}
|
|
2340
|
+
announce('F12', last
|
|
2341
|
+
? `Your last message: ${last.slice(0, 400)}`
|
|
2342
|
+
: 'No prior user message this session.');
|
|
2343
|
+
return;
|
|
2344
|
+
}
|
|
2161
2345
|
// ── F5: push-to-talk dictation toggle ──────────────
|
|
2162
2346
|
if (name === 'f5') {
|
|
2163
2347
|
if (dictateActive) {
|