@claude-flow/cli 3.56.6 → 3.56.7
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/.claude/helpers/auto-memory-hook.mjs +109 -10
- package/.claude/helpers/helpers.manifest.json +4 -4
- package/.claude/helpers/intelligence.cjs +57 -4
- package/bin/cli.js +11 -1
- package/bin/mcp-server.js +11 -1
- package/catalog-manifest.json +2 -2
- package/dist/src/commands/doctor-native-addon.d.ts +23 -0
- package/dist/src/commands/doctor-native-addon.js +135 -0
- package/dist/src/commands/doctor.js +3 -0
- package/dist/src/commands/hooks.js +8 -1
- package/dist/src/commands/index.js +2 -0
- package/dist/src/commands/init.d.ts +2 -0
- package/dist/src/commands/init.js +37 -5
- package/dist/src/commands/mods.d.ts +3 -0
- package/dist/src/commands/mods.js +19 -5
- package/dist/src/commands/tui.d.ts +12 -0
- package/dist/src/commands/tui.js +98 -0
- package/dist/src/fs-secure.d.ts +25 -0
- package/dist/src/fs-secure.js +51 -2
- package/dist/src/init/executor.js +11 -14
- package/dist/src/init/helper-commands.d.ts +2 -0
- package/dist/src/init/helper-commands.js +2 -0
- package/dist/src/init/helpers-generator.js +4 -1
- package/dist/src/init/settings-generator.js +3 -3
- package/dist/src/mcp-server.js +10 -2
- package/dist/src/mcp-tools/capability-brain.d.ts +1 -1
- package/dist/src/mcp-tools/capability-brain.js +1 -1
- package/dist/src/mcp-tools/hooks-tools.js +26 -4
- package/dist/src/mcp-tools/memory-tools.js +2 -1
- package/dist/src/memory/live-memory-row.d.ts +3 -1
- package/dist/src/memory/live-memory-row.js +3 -1
- package/dist/src/memory/memory-bridge.d.ts +4 -0
- package/dist/src/memory/memory-bridge.js +3 -2
- package/dist/src/memory/memory-initializer.d.ts +4 -0
- package/dist/src/memory/memory-initializer.js +12 -11
- package/dist/src/mods/claude-installs.js +7 -3
- package/dist/src/mods/install.js +32 -12
- package/dist/src/mods/plugin-resolve.js +3 -1
- package/dist/src/mods/probe.js +92 -4
- package/dist/src/services/command-recording.d.ts +17 -0
- package/dist/src/services/command-recording.js +91 -0
- package/node_modules/@claude-flow/codex/README.md +20 -3
- package/node_modules/@claude-flow/codex/dist/cli.js +7 -0
- package/node_modules/@claude-flow/codex/dist/cli.js.map +1 -1
- package/node_modules/@claude-flow/codex/dist/index.d.ts +3 -3
- package/node_modules/@claude-flow/codex/dist/index.js +1 -1
- package/node_modules/@claude-flow/codex/dist/initializer.d.ts +8 -3
- package/node_modules/@claude-flow/codex/dist/initializer.d.ts.map +1 -1
- package/node_modules/@claude-flow/codex/dist/initializer.js +75 -13
- package/node_modules/@claude-flow/codex/dist/initializer.js.map +1 -1
- package/node_modules/@claude-flow/codex/dist/types.d.ts +8 -0
- package/node_modules/@claude-flow/codex/dist/types.d.ts.map +1 -1
- package/node_modules/@claude-flow/codex/package.json +1 -1
- package/node_modules/@claude-flow/plugin-agent-federation/dist/bin.js +0 -0
- package/package.json +2 -2
- package/plugins/ruflo-console/dist/tui.mjs +271 -0
|
@@ -76,6 +76,15 @@ async function resolveCodexInitializer(cwd) {
|
|
|
76
76
|
}
|
|
77
77
|
return undefined;
|
|
78
78
|
}
|
|
79
|
+
// Trust level for a newly generated .codex/config.toml. Safe by default: only
|
|
80
|
+
// an explicit `--codex-trust full` (or RUFLO_CODEX_TRUST=full, read by the
|
|
81
|
+
// adapter) restores danger-full-access. Anything else is rejected here.
|
|
82
|
+
export function readCodexTrustFlag(flags) {
|
|
83
|
+
const raw = flags.codexTrust ?? flags['codex-trust'];
|
|
84
|
+
if (raw === undefined || raw === null || raw === false || raw === '')
|
|
85
|
+
return undefined;
|
|
86
|
+
return raw === 'standard' || raw === 'full' ? raw : null;
|
|
87
|
+
}
|
|
79
88
|
// Keep Codex out of the CLI dependency graph so cold `npx ruflo --version`
|
|
80
89
|
// remains fast (#2561). An explicit `init --codex` may fetch the small,
|
|
81
90
|
// stable adapter on demand when it is not already installed by an umbrella
|
|
@@ -89,6 +98,7 @@ export function runCodexInitializerCli(cwd, options) {
|
|
|
89
98
|
options.template,
|
|
90
99
|
...(options.force ? ['--force'] : []),
|
|
91
100
|
...(options.dual ? ['--dual'] : []),
|
|
101
|
+
...(options.codexTrust ? ['--codex-trust', options.codexTrust] : []),
|
|
92
102
|
];
|
|
93
103
|
const result = process.platform === 'win32'
|
|
94
104
|
? spawnSync(process.env.ComSpec || 'cmd.exe', ['/d', '/s', '/c', ['npx', ...npxArgs].join(' ')], { cwd, stdio: 'inherit', windowsHide: true })
|
|
@@ -106,6 +116,7 @@ export function runCodexInitializerCli(cwd, options) {
|
|
|
106
116
|
// init) and scripted `--format json` output.
|
|
107
117
|
async function maybeAutoDetectCodex(ctx, options) {
|
|
108
118
|
try {
|
|
119
|
+
const codexTrust = readCodexTrustFlag(ctx.flags) ?? undefined;
|
|
109
120
|
// The CLI parser turns `--no-codex-detect` into `codexDetect: false`.
|
|
110
121
|
// Retain the literal key for programmatic CommandContext callers.
|
|
111
122
|
if (ctx.flags.codexDetect === false || ctx.flags['no-codex-detect'] === true)
|
|
@@ -127,6 +138,7 @@ async function maybeAutoDetectCodex(ctx, options) {
|
|
|
127
138
|
template: (options.minimal ? 'minimal' : options.full ? 'full' : 'default'),
|
|
128
139
|
force: options.force,
|
|
129
140
|
dual: false, // Claude Code files were already written by the main init flow above
|
|
141
|
+
codexTrust,
|
|
130
142
|
});
|
|
131
143
|
if (!result.success)
|
|
132
144
|
return; // best-effort — never fail the primary init over this
|
|
@@ -352,6 +364,11 @@ async function maybeInstallSkillsSh(ctx) {
|
|
|
352
364
|
// Codex initialization action
|
|
353
365
|
async function initCodexAction(ctx, options) {
|
|
354
366
|
const { force, minimal, full, dualMode } = options;
|
|
367
|
+
const codexTrust = readCodexTrustFlag(ctx.flags);
|
|
368
|
+
if (codexTrust === null) {
|
|
369
|
+
output.printError(`Invalid --codex-trust: ${String(ctx.flags.codexTrust ?? ctx.flags['codex-trust'])} (valid: standard, full)`);
|
|
370
|
+
return { success: false, exitCode: 1 };
|
|
371
|
+
}
|
|
355
372
|
output.writeln();
|
|
356
373
|
output.writeln(output.bold('Initializing RuFlo V3 for OpenAI Codex'));
|
|
357
374
|
output.writeln();
|
|
@@ -364,7 +381,7 @@ async function initCodexAction(ctx, options) {
|
|
|
364
381
|
if (!CodexInitializer) {
|
|
365
382
|
spinner.stop();
|
|
366
383
|
output.printInfo('Fetching the stable Codex adapter for this initialization...');
|
|
367
|
-
const success = runCodexInitializerCli(ctx.cwd, { template, force, dual: dualMode });
|
|
384
|
+
const success = runCodexInitializerCli(ctx.cwd, { template, force, dual: dualMode, codexTrust });
|
|
368
385
|
if (!success) {
|
|
369
386
|
output.printError('Codex initialization failed while running @claude-flow/codex@latest.');
|
|
370
387
|
return { success: false, exitCode: 1 };
|
|
@@ -377,6 +394,7 @@ async function initCodexAction(ctx, options) {
|
|
|
377
394
|
template: template,
|
|
378
395
|
force,
|
|
379
396
|
dual: dualMode,
|
|
397
|
+
codexTrust,
|
|
380
398
|
});
|
|
381
399
|
if (!result.success) {
|
|
382
400
|
spinner.fail('Codex initialization failed');
|
|
@@ -404,14 +422,23 @@ async function initCodexAction(ctx, options) {
|
|
|
404
422
|
dualMode ? `CLAUDE.md: Claude Code compatibility` : '',
|
|
405
423
|
].filter(Boolean).join('\n'), 'OpenAI Codex Integration');
|
|
406
424
|
output.writeln();
|
|
425
|
+
// The trust level of .codex/config.toml is always shown (never truncated
|
|
426
|
+
// behind other warnings): it decides what Codex may do without asking.
|
|
427
|
+
const isTrustNote = (w) => w.startsWith('.codex/config.toml');
|
|
428
|
+
const trustNotes = (result.warnings ?? []).filter(isTrustNote);
|
|
429
|
+
for (const note of trustNotes)
|
|
430
|
+
output.printInfo(note);
|
|
431
|
+
if (trustNotes.length > 0)
|
|
432
|
+
output.writeln();
|
|
407
433
|
// Warnings
|
|
408
|
-
|
|
434
|
+
const otherWarnings = (result.warnings ?? []).filter((w) => !isTrustNote(w));
|
|
435
|
+
if (otherWarnings.length > 0) {
|
|
409
436
|
output.printWarning('Warnings:');
|
|
410
|
-
for (const warning of
|
|
437
|
+
for (const warning of otherWarnings.slice(0, 5)) {
|
|
411
438
|
output.printInfo(` • ${warning}`);
|
|
412
439
|
}
|
|
413
|
-
if (
|
|
414
|
-
output.printInfo(` ... and ${
|
|
440
|
+
if (otherWarnings.length > 5) {
|
|
441
|
+
output.printInfo(` ... and ${otherWarnings.length - 5} more`);
|
|
415
442
|
}
|
|
416
443
|
output.writeln();
|
|
417
444
|
}
|
|
@@ -1577,6 +1604,11 @@ export const initCommand = {
|
|
|
1577
1604
|
type: 'boolean',
|
|
1578
1605
|
default: false,
|
|
1579
1606
|
},
|
|
1607
|
+
{
|
|
1608
|
+
name: 'codex-trust',
|
|
1609
|
+
description: 'Trust level of the generated .codex/config.toml: standard (default: workspace-write sandbox, on-request approvals, cached web search) or full (danger-full-access, no approvals, live web search). An existing file is never rewritten',
|
|
1610
|
+
type: 'string',
|
|
1611
|
+
},
|
|
1580
1612
|
{
|
|
1581
1613
|
name: 'no-codex-detect',
|
|
1582
1614
|
description: 'Skip auto-detecting the OpenAI Codex CLI and configuring its MCP server + skills',
|
|
@@ -8,6 +8,9 @@
|
|
|
8
8
|
* are never removed: they stay the default and the fallback.
|
|
9
9
|
*/
|
|
10
10
|
import type { Command } from '../types.js';
|
|
11
|
+
import { type Finding } from '../mods/probe.js';
|
|
12
|
+
/** The doctor's exit gate: a failure always fails; with --strict a warning does too. */
|
|
13
|
+
export declare function hasFailureOrStrictWarning(findings: readonly Finding[], strict: boolean): boolean;
|
|
11
14
|
export declare const modsCommand: Command;
|
|
12
15
|
export default modsCommand;
|
|
13
16
|
//# sourceMappingURL=mods.d.ts.map
|
|
@@ -105,19 +105,32 @@ const uninstallSub = {
|
|
|
105
105
|
return { success: result.failed.length === 0, exitCode: result.failed.length ? 1 : 0, data: result };
|
|
106
106
|
},
|
|
107
107
|
};
|
|
108
|
+
/** The doctor's exit gate: a failure always fails; with --strict a warning does too. */
|
|
109
|
+
export function hasFailureOrStrictWarning(findings, strict) {
|
|
110
|
+
return findings.some((f) => f.status === 'fail' || (strict && f.status === 'warn'));
|
|
111
|
+
}
|
|
108
112
|
function findingsCommand(name, description) {
|
|
109
113
|
return {
|
|
110
114
|
name,
|
|
111
115
|
description,
|
|
112
|
-
options: [
|
|
116
|
+
options: [
|
|
117
|
+
rootOption,
|
|
118
|
+
{ name: 'json', description: 'Output as JSON', type: 'boolean', default: false },
|
|
119
|
+
...(name === 'doctor' ? [{ name: 'strict', description: 'Exit 1 on a warning too (a mod that is not fully loaded is a failure for a CI gate)', type: 'boolean', default: false }] : []),
|
|
120
|
+
],
|
|
113
121
|
action: async (ctx) => {
|
|
114
122
|
const findings = probeMods({ projectRoot: projectRoot(ctx) });
|
|
115
123
|
if (ctx.flags.json)
|
|
116
124
|
output.printJson(findings);
|
|
117
125
|
else
|
|
118
126
|
printFindings(findings);
|
|
119
|
-
const
|
|
120
|
-
|
|
127
|
+
const strict = name === 'doctor' && ctx.flags.strict === true;
|
|
128
|
+
const failed = hasFailureOrStrictWarning(findings, strict);
|
|
129
|
+
if (failed && !ctx.flags.json) {
|
|
130
|
+
const first = findings.find((f) => f.status === 'fail') ?? findings.find((f) => f.status === 'warn');
|
|
131
|
+
output.writeln(output.error(`doctor: ${findings.filter((f) => f.status === 'fail').length} failure(s), ${findings.filter((f) => f.status === 'warn').length} warning(s)${first?.fix ? `; start with: ${first.fix}` : ''}`));
|
|
132
|
+
}
|
|
133
|
+
// status reports (exit 0); doctor gates on a failure, and with --strict on a warning.
|
|
121
134
|
return name === 'doctor' && failed ? { success: false, exitCode: 1, data: findings } : { success: true, data: findings };
|
|
122
135
|
},
|
|
123
136
|
};
|
|
@@ -153,7 +166,7 @@ export const modsCommand = {
|
|
|
153
166
|
installSub,
|
|
154
167
|
uninstallSub,
|
|
155
168
|
statusSub,
|
|
156
|
-
findingsCommand('doctor', 'Check the mod path; exits 1
|
|
169
|
+
findingsCommand('doctor', 'Check the mod path; exits 1 on a failure (--strict: on a warning too)'),
|
|
157
170
|
syncPolicySub,
|
|
158
171
|
],
|
|
159
172
|
examples: [
|
|
@@ -161,7 +174,8 @@ export const modsCommand = {
|
|
|
161
174
|
{ command: 'ruflo mods install --scope project', description: 'Same, in the committed settings.json (what ruflo init does)' },
|
|
162
175
|
{ command: 'ruflo mods install --no-plugin-install', description: 'Only write settings; run no claude command' },
|
|
163
176
|
{ command: 'ruflo mods install --source local', description: 'Dogfood: load the mods live from this ruflo checkout (directory marketplace)' },
|
|
164
|
-
{ command: 'ruflo mods doctor', description: 'Marketplace fresh? Plugins loadable? Function hooks on? Refused by policy?' },
|
|
177
|
+
{ command: 'ruflo mods doctor', description: 'Marketplace fresh? Plugins loadable? Function hooks on? Refused by policy? Each problem prints a one-line fix' },
|
|
178
|
+
{ command: 'ruflo mods doctor --strict', description: 'Same, and exit 1 when anything is not fully working (for CI)' },
|
|
165
179
|
{ command: 'ruflo mods uninstall', description: 'Remove only what install added' },
|
|
166
180
|
],
|
|
167
181
|
action: statusSub.action,
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { Command } from '../types.js';
|
|
2
|
+
/** `<own install>/plugins/ruflo-console/dist/tui.mjs`, the first that exists. */
|
|
3
|
+
export declare function locateTuiBundle(startDir?: string): string | null;
|
|
4
|
+
/** The AI page's settings from the flags (and one environment variable for the endpoint list). Absent flags leave the defaults; a bad number is ignored, never passed on. */
|
|
5
|
+
export declare function aiPage(flags: Record<string, unknown> | undefined): {
|
|
6
|
+
model?: string;
|
|
7
|
+
maxSpendUsd?: number;
|
|
8
|
+
convoTargets?: string;
|
|
9
|
+
};
|
|
10
|
+
export declare const tuiCommand: Command;
|
|
11
|
+
export default tuiCommand;
|
|
12
|
+
//# sourceMappingURL=tui.d.ts.map
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `ruflo tui` — the ruflo console in a terminal of its own (ADR-489).
|
|
3
|
+
*
|
|
4
|
+
* Runs the same views the Claude Code mod (plugins/ruflo-console) draws, over a Node host instead of the mod host. Read-only in
|
|
5
|
+
* the pages: Overview, Cost, Swarm, Events and Missions are read-only; AI (key 6) asks Codex (read-only, the pinned 0.162.0) or OpenRouter
|
|
6
|
+
* (https, openrouter.ai or a convoTargets host), and every question shows a card first. The bundle is built from the plugin's source (scripts/build-tui.mjs) and loaded from this
|
|
7
|
+
* CLI's OWN install only (never from the working directory: opening a repository must not run code that repository supplies).
|
|
8
|
+
* No bundle is a message and exit 1, never a crash; ruflo stays operational without it. Without a terminal (a pipe, CI) the live view cannot run:
|
|
9
|
+
* a one-line hint and exit 2, unless --plain asks for one frame as plain text and exit 0 (ADR-489 decision E).
|
|
10
|
+
*
|
|
11
|
+
* npx ruflo tui # interactive, in a terminal
|
|
12
|
+
* npx ruflo tui --view swarm # start on the Swarm page (keys 1-5 or o c s e m switch pages)
|
|
13
|
+
* npx ruflo tui --plain | head # one plain frame, exit 0 (scripts, CI)
|
|
14
|
+
* npx ruflo tui | cat # not a terminal and no --plain: a hint on stderr, exit 2
|
|
15
|
+
*/
|
|
16
|
+
import { existsSync } from 'node:fs';
|
|
17
|
+
import { dirname, join } from 'node:path';
|
|
18
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
19
|
+
import { output } from '../output.js';
|
|
20
|
+
import { ownPluginDirs } from '../plugins/own-install.js';
|
|
21
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
22
|
+
const VIEWS = ['overview', 'cost', 'swarm', 'events', 'missions', 'ai'];
|
|
23
|
+
/** `<own install>/plugins/ruflo-console/dist/tui.mjs`, the first that exists. */
|
|
24
|
+
export function locateTuiBundle(startDir = __dirname) {
|
|
25
|
+
for (const dir of ownPluginDirs(startDir, 'ruflo-console', 'dist')) {
|
|
26
|
+
const file = join(dir, 'tui.mjs');
|
|
27
|
+
if (existsSync(file))
|
|
28
|
+
return file;
|
|
29
|
+
}
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
/** The running CLI's entry file, so the console's read-only probes run this CLI and never download another. */
|
|
33
|
+
function cliEntry() {
|
|
34
|
+
const entry = join(__dirname, '..', '..', '..', 'bin', 'cli.js');
|
|
35
|
+
return existsSync(entry) ? entry : undefined;
|
|
36
|
+
}
|
|
37
|
+
/** The AI page's settings from the flags (and one environment variable for the endpoint list). Absent flags leave the defaults; a bad number is ignored, never passed on. */
|
|
38
|
+
export function aiPage(flags) {
|
|
39
|
+
const model = typeof flags?.model === 'string' && flags.model !== '' ? flags.model : undefined;
|
|
40
|
+
const cap = flags?.maxSpendUsd ?? flags?.['max-spend-usd'];
|
|
41
|
+
const maxSpendUsd = typeof cap === 'number' && Number.isFinite(cap) && cap >= 0 ? cap : typeof cap === 'string' && /^\d+(\.\d+)?$/.test(cap) ? Number(cap) : undefined;
|
|
42
|
+
const targets = flags?.convoTargets ?? flags?.['convo-targets'] ?? process.env.RUFLO_TUI_CONVO_TARGETS;
|
|
43
|
+
return { ...(model !== undefined && { model }), ...(maxSpendUsd !== undefined && { maxSpendUsd }), ...(typeof targets === 'string' && targets !== '' && { convoTargets: targets.slice(0, 2000) }) };
|
|
44
|
+
}
|
|
45
|
+
export const tuiCommand = {
|
|
46
|
+
name: 'tui',
|
|
47
|
+
description: 'The ruflo console in your terminal: Overview, Cost, Swarm, Events, Missions (read-only) and an AI page that asks Codex or OpenRouter, each question after a card.',
|
|
48
|
+
options: [
|
|
49
|
+
{ name: 'view', description: `Page to open: ${VIEWS.join(' | ')}`, type: 'string', default: 'overview' },
|
|
50
|
+
{ name: 'ai', description: 'The AI page (ask Codex read-only or OpenRouter, each question after a confirm card). --no-ai turns it off', type: 'boolean', default: true },
|
|
51
|
+
{ name: 'model', description: 'OpenRouter model for this session (default openrouter/auto)', type: 'string' },
|
|
52
|
+
{ name: 'max-spend-usd', description: 'Stop sending to OpenRouter once the provider-reported spend reaches this (default 0.5)', type: 'number' },
|
|
53
|
+
{ name: 'convo-targets', description: 'Extra OpenAI-compatible endpoints, the console convoTargets syntax: endpoint:<name>=<https-base-url>|<KEY_ENV>|<model>;... (a key is named by its environment variable). Also RUFLO_TUI_CONVO_TARGETS', type: 'string' },
|
|
54
|
+
{ name: 'plain', description: 'Print one frame as plain text and exit 0 (scripts and CI). Without it, a non-terminal gets a hint and exit 2', type: 'boolean', default: false },
|
|
55
|
+
],
|
|
56
|
+
examples: [
|
|
57
|
+
{ command: 'npx ruflo tui', description: 'Open the console in this terminal' },
|
|
58
|
+
{ command: 'npx ruflo tui --view swarm', description: 'Start on the Swarm page (keys 1-5 switch pages, tab cycles, q quits)' },
|
|
59
|
+
{ command: 'npx ruflo tui --view ai --model openai/gpt-4o-mini --max-spend-usd 0.1', description: 'The AI page on a cheap OpenRouter model, stopping at $0.10 reported (key in $OPENROUTER_API_KEY, never shown)' },
|
|
60
|
+
{ command: 'npx ruflo tui --plain | head -40', description: 'One plain frame, exit 0: for scripts and CI (no terminal needed)' },
|
|
61
|
+
],
|
|
62
|
+
async action(context) {
|
|
63
|
+
const ctx = context;
|
|
64
|
+
const wanted = ctx.flags?.view ?? ctx.args?.find(arg => !arg.startsWith('-')) ?? 'overview';
|
|
65
|
+
if (!VIEWS.includes(wanted)) {
|
|
66
|
+
output.writeln(output.error(`tui: unknown view "${String(wanted).slice(0, 40)}". Use one of: ${VIEWS.join(', ')}`));
|
|
67
|
+
return { success: false, exitCode: 2, data: { error: 'unknown-view' } };
|
|
68
|
+
}
|
|
69
|
+
const bundle = locateTuiBundle();
|
|
70
|
+
if (bundle === null) {
|
|
71
|
+
output.writeln(output.warning('tui: the terminal console is not available in this install (plugins/ruflo-console/dist/tui.mjs not found).'));
|
|
72
|
+
output.writeln('From a ruflo checkout: node plugins/ruflo-console/scripts/build-tui.mjs');
|
|
73
|
+
return { success: false, exitCode: 1, data: { error: 'bundle-missing' } };
|
|
74
|
+
}
|
|
75
|
+
let mod;
|
|
76
|
+
try {
|
|
77
|
+
mod = (await import(pathToFileURL(bundle).href));
|
|
78
|
+
}
|
|
79
|
+
catch (error) {
|
|
80
|
+
output.writeln(output.error(`tui: could not load the console bundle: ${error instanceof Error ? error.message.slice(0, 160) : 'unknown error'}`));
|
|
81
|
+
return { success: false, exitCode: 1, data: { error: 'bundle-load-failed' } };
|
|
82
|
+
}
|
|
83
|
+
const stop = new AbortController();
|
|
84
|
+
const onSignal = () => stop.abort();
|
|
85
|
+
process.once('SIGTERM', onSignal);
|
|
86
|
+
process.once('SIGHUP', onSignal);
|
|
87
|
+
try {
|
|
88
|
+
const code = await mod.runTui({ cwd: ctx.cwd ?? process.cwd(), view: wanted, ...(ctx.flags?.plain === true && { plain: true }), ...(ctx.flags?.ai !== false && { aiPage: aiPage(ctx.flags) }), ...(cliEntry() !== undefined && { cliEntry: cliEntry() }), ...(process.env.CLAUDE_CONFIG_DIR !== undefined && { configDir: process.env.CLAUDE_CONFIG_DIR }) }, { stdin: process.stdin, stdout: process.stdout, stderr: process.stderr, env: process.env, signal: stop.signal });
|
|
89
|
+
return { success: code === 0, exitCode: code };
|
|
90
|
+
}
|
|
91
|
+
finally {
|
|
92
|
+
process.off('SIGTERM', onSignal);
|
|
93
|
+
process.off('SIGHUP', onSignal);
|
|
94
|
+
}
|
|
95
|
+
},
|
|
96
|
+
};
|
|
97
|
+
export default tuiCommand;
|
|
98
|
+
//# sourceMappingURL=tui.js.map
|
package/dist/src/fs-secure.d.ts
CHANGED
|
@@ -66,6 +66,31 @@ export interface WriteOptions {
|
|
|
66
66
|
* writeFileRestricted(path, data, { encrypt: true }) // opt-in encryption
|
|
67
67
|
*/
|
|
68
68
|
export declare function writeFileRestricted(path: string, data: string | Buffer, optsOrEncoding?: BufferEncoding | WriteOptions): void;
|
|
69
|
+
/**
|
|
70
|
+
* The at-rest format of an existing database file: `true` = RFE1-encrypted,
|
|
71
|
+
* `false` = plaintext, `null` = missing or unreadable (nothing to preserve).
|
|
72
|
+
* Reads four bytes only.
|
|
73
|
+
*/
|
|
74
|
+
export declare function existingFileIsEncrypted(path: string): boolean | null;
|
|
75
|
+
/**
|
|
76
|
+
* Write a whole-file database image back in the format it was read in (#4057).
|
|
77
|
+
*
|
|
78
|
+
* A read-modify-write of a database image (sql.js loads the file, mutates it,
|
|
79
|
+
* exports it) must not change what the file IS. With encryption at rest on, a
|
|
80
|
+
* plain `{ encrypt: true }` write turned a plaintext native SQLite store into
|
|
81
|
+
* an RFE1 blob that the native bridge and every SQLite reader then rejected,
|
|
82
|
+
* while the command reported success. And the reverse, a plain
|
|
83
|
+
* `writeFileAtomic` of a decrypted image, silently downgraded an encrypted
|
|
84
|
+
* store to plaintext.
|
|
85
|
+
*
|
|
86
|
+
* existing file RFE1 -> RFE1 (encrypted)
|
|
87
|
+
* existing file plaintext -> plaintext
|
|
88
|
+
* no existing file -> `encrypt` (default true: the env gate decides)
|
|
89
|
+
* `encrypt: false` -> plaintext, always (explicit caller choice)
|
|
90
|
+
*/
|
|
91
|
+
export declare function writeDbImagePreservingFormat(path: string, data: Uint8Array | Buffer, opts?: {
|
|
92
|
+
encrypt?: boolean;
|
|
93
|
+
}): void;
|
|
69
94
|
/**
|
|
70
95
|
* Read a file and transparently decrypt if it carries the RFE1 magic.
|
|
71
96
|
*
|
package/dist/src/fs-secure.js
CHANGED
|
@@ -16,9 +16,9 @@
|
|
|
16
16
|
* sniff so legacy plaintext files keep working unchanged during the
|
|
17
17
|
* incremental migration.
|
|
18
18
|
*/
|
|
19
|
-
import { chmodSync, closeSync, fsyncSync, mkdirSync, openSync, readFileSync, renameSync, unlinkSync, writeSync, } from 'node:fs';
|
|
19
|
+
import { chmodSync, closeSync, fsyncSync, mkdirSync, openSync, readFileSync, readSync, renameSync, unlinkSync, writeSync, } from 'node:fs';
|
|
20
20
|
import { basename, dirname, join } from 'node:path';
|
|
21
|
-
import { decryptBuffer, encryptBuffer, getKey, isEncryptedBlob, isEncryptionEnabled, } from './encryption/vault.js';
|
|
21
|
+
import { decryptBuffer, encryptBuffer, getKey, isEncryptedBlob, isEncryptionEnabled, MAGIC, } from './encryption/vault.js';
|
|
22
22
|
/**
|
|
23
23
|
* Crash-safe atomic file write (issue #2584).
|
|
24
24
|
*
|
|
@@ -119,6 +119,55 @@ export function writeFileRestricted(path, data, optsOrEncoding = 'utf-8') {
|
|
|
119
119
|
const buf = Buffer.isBuffer(payload) ? payload : Buffer.from(payload, encoding);
|
|
120
120
|
writeFileAtomic(path, buf, { mode: 0o600 });
|
|
121
121
|
}
|
|
122
|
+
/**
|
|
123
|
+
* The at-rest format of an existing database file: `true` = RFE1-encrypted,
|
|
124
|
+
* `false` = plaintext, `null` = missing or unreadable (nothing to preserve).
|
|
125
|
+
* Reads four bytes only.
|
|
126
|
+
*/
|
|
127
|
+
export function existingFileIsEncrypted(path) {
|
|
128
|
+
let fd;
|
|
129
|
+
try {
|
|
130
|
+
fd = openSync(path, 'r');
|
|
131
|
+
const head = Buffer.alloc(4);
|
|
132
|
+
const n = readSync(fd, head, 0, 4, 0);
|
|
133
|
+
if (n < 4)
|
|
134
|
+
return null;
|
|
135
|
+
return head.equals(MAGIC);
|
|
136
|
+
}
|
|
137
|
+
catch {
|
|
138
|
+
return null;
|
|
139
|
+
}
|
|
140
|
+
finally {
|
|
141
|
+
if (fd !== undefined) {
|
|
142
|
+
try {
|
|
143
|
+
closeSync(fd);
|
|
144
|
+
}
|
|
145
|
+
catch { /* best effort */ }
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Write a whole-file database image back in the format it was read in (#4057).
|
|
151
|
+
*
|
|
152
|
+
* A read-modify-write of a database image (sql.js loads the file, mutates it,
|
|
153
|
+
* exports it) must not change what the file IS. With encryption at rest on, a
|
|
154
|
+
* plain `{ encrypt: true }` write turned a plaintext native SQLite store into
|
|
155
|
+
* an RFE1 blob that the native bridge and every SQLite reader then rejected,
|
|
156
|
+
* while the command reported success. And the reverse, a plain
|
|
157
|
+
* `writeFileAtomic` of a decrypted image, silently downgraded an encrypted
|
|
158
|
+
* store to plaintext.
|
|
159
|
+
*
|
|
160
|
+
* existing file RFE1 -> RFE1 (encrypted)
|
|
161
|
+
* existing file plaintext -> plaintext
|
|
162
|
+
* no existing file -> `encrypt` (default true: the env gate decides)
|
|
163
|
+
* `encrypt: false` -> plaintext, always (explicit caller choice)
|
|
164
|
+
*/
|
|
165
|
+
export function writeDbImagePreservingFormat(path, data, opts = {}) {
|
|
166
|
+
const requested = opts.encrypt ?? true;
|
|
167
|
+
const existing = existingFileIsEncrypted(path);
|
|
168
|
+
const encrypt = requested === false ? false : (existing ?? requested);
|
|
169
|
+
writeFileRestricted(path, Buffer.isBuffer(data) ? data : Buffer.from(data), { encrypt });
|
|
170
|
+
}
|
|
122
171
|
export function readFileMaybeEncrypted(path, encoding = 'utf-8') {
|
|
123
172
|
const raw = readFileSync(path);
|
|
124
173
|
let plain;
|
|
@@ -20,7 +20,7 @@ import { generateClaudeMd } from './claudemd-generator.js';
|
|
|
20
20
|
import { recordMemoryPackagePath } from './memory-package-resolver.js';
|
|
21
21
|
import { ensureCommonJsCompanions } from './helper-companions.js';
|
|
22
22
|
import { scanSettingsForRisk, formatRiskFindingsAsWarnings } from './settings-risk-scanner.js';
|
|
23
|
-
import { helperHookCommand, helperScopeFor, helperStatusLineCommand, pinToUserHelpers } from './helper-commands.js';
|
|
23
|
+
import { AUTO_MEMORY_HOOK_TIMEOUT_S, helperHookCommand, helperScopeFor, helperStatusLineCommand, pinToUserHelpers } from './helper-commands.js';
|
|
24
24
|
/**
|
|
25
25
|
* Skills to copy based on configuration
|
|
26
26
|
*/
|
|
@@ -307,18 +307,15 @@ export function mergeSettingsForUpgrade(existing, targetDir) {
|
|
|
307
307
|
// 2. Merge hooks (preserve existing, add new Agent Teams + auto-memory hooks)
|
|
308
308
|
const existingHooks = existing.hooks || {};
|
|
309
309
|
merged.hooks = { ...existingHooks };
|
|
310
|
-
//
|
|
311
|
-
//
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
310
|
+
// Auto-memory hook commands. They go through helperHookCommand like every other
|
|
311
|
+
// helper hook (#4058): the previous `node -e "…import(file)" import` form left the
|
|
312
|
+
// subcommand in process.argv[1], the helper read argv[2], and both hooks only
|
|
313
|
+
// printed the status banner. Hooks run in the project directory with
|
|
314
|
+
// CLAUDE_PROJECT_DIR set, so the shared probe resolves the helper without a
|
|
315
|
+
// `git rev-parse` of its own.
|
|
315
316
|
const autoMemoryScript = '.claude/helpers/auto-memory-hook.mjs';
|
|
316
|
-
const autoMemoryImportCmd =
|
|
317
|
-
|
|
318
|
-
: `node -e "${gitRootResolver}var f=p.join(r,'${autoMemoryScript}');import(u.pathToFileURL(f).href)" import`;
|
|
319
|
-
const autoMemorySyncCmd = scope === 'user'
|
|
320
|
-
? helperHookCommand(autoMemoryScript, 'sync', 'user')
|
|
321
|
-
: `node -e "${gitRootResolver}var f=p.join(r,'${autoMemoryScript}');import(u.pathToFileURL(f).href)" sync`;
|
|
317
|
+
const autoMemoryImportCmd = helperHookCommand(autoMemoryScript, 'import', scope);
|
|
318
|
+
const autoMemorySyncCmd = helperHookCommand(autoMemoryScript, 'sync', scope);
|
|
322
319
|
// Add auto-memory import to SessionStart (if not already present)
|
|
323
320
|
const sessionStartHooks = existingHooks.SessionStart;
|
|
324
321
|
const hasAutoMemoryImport = sessionStartHooks?.some(group => group.hooks?.some(h => h.command?.includes('auto-memory-hook')));
|
|
@@ -333,7 +330,7 @@ export function mergeSettingsForUpgrade(existing, targetDir) {
|
|
|
333
330
|
startGroup.hooks.push({
|
|
334
331
|
type: 'command',
|
|
335
332
|
command: autoMemoryImportCmd,
|
|
336
|
-
timeout:
|
|
333
|
+
timeout: AUTO_MEMORY_HOOK_TIMEOUT_S,
|
|
337
334
|
continueOnError: true,
|
|
338
335
|
});
|
|
339
336
|
}
|
|
@@ -352,7 +349,7 @@ export function mergeSettingsForUpgrade(existing, targetDir) {
|
|
|
352
349
|
endGroup.hooks.unshift({
|
|
353
350
|
type: 'command',
|
|
354
351
|
command: autoMemorySyncCmd,
|
|
355
|
-
timeout:
|
|
352
|
+
timeout: AUTO_MEMORY_HOOK_TIMEOUT_S,
|
|
356
353
|
continueOnError: true,
|
|
357
354
|
});
|
|
358
355
|
}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
export type HelperScope = 'project' | 'user';
|
|
2
|
+
/** Claude Code reads a hook's `timeout` in SECONDS; the auto-memory hooks wrote 6000-10000, i.e. up to 100 minutes (#4058). */
|
|
3
|
+
export declare const AUTO_MEMORY_HOOK_TIMEOUT_S = 30;
|
|
2
4
|
/** Settings written into the user's home directory are user-level settings. */
|
|
3
5
|
export declare function helperScopeFor(targetDir: string | undefined): HelperScope;
|
|
4
6
|
/** Command that runs `node <script> <subcommand>` for a hook. */
|
|
@@ -14,6 +14,8 @@
|
|
|
14
14
|
import * as fs from 'fs';
|
|
15
15
|
import * as os from 'os';
|
|
16
16
|
import * as path from 'path';
|
|
17
|
+
/** Claude Code reads a hook's `timeout` in SECONDS; the auto-memory hooks wrote 6000-10000, i.e. up to 100 minutes (#4058). */
|
|
18
|
+
export const AUTO_MEMORY_HOOK_TIMEOUT_S = 30;
|
|
17
19
|
const IS_WINDOWS = process.platform === 'win32';
|
|
18
20
|
const STATUSLINE_SCRIPT = '.claude/helpers/statusline.cjs';
|
|
19
21
|
function canonical(dir) {
|
|
@@ -1306,7 +1306,10 @@ function doStatus() {
|
|
|
1306
1306
|
// Suppress unhandled rejection warnings from dynamic import() failures
|
|
1307
1307
|
process.on('unhandledRejection', () => {});
|
|
1308
1308
|
|
|
1309
|
-
|
|
1309
|
+
// Older settings.json files ran node -e with the helper imported, which puts the
|
|
1310
|
+
// subcommand in argv[1] (#4058); accept that shape as well as script import.
|
|
1311
|
+
const COMMANDS = ['import', 'sync', 'status'];
|
|
1312
|
+
const command = process.argv[2] || (COMMANDS.includes(process.argv[1]) ? process.argv[1] : 'status');
|
|
1310
1313
|
|
|
1311
1314
|
try {
|
|
1312
1315
|
switch (command) {
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Creates .claude/settings.json with V3-optimized hook configurations
|
|
4
4
|
*/
|
|
5
5
|
import { detectPlatform } from './types.js';
|
|
6
|
-
import { helperHookCommand, helperScopeFor, helperStatusLineCommand } from './helper-commands.js';
|
|
6
|
+
import { AUTO_MEMORY_HOOK_TIMEOUT_S, helperHookCommand, helperScopeFor, helperStatusLineCommand } from './helper-commands.js';
|
|
7
7
|
/**
|
|
8
8
|
* Generate the complete settings.json content
|
|
9
9
|
*/
|
|
@@ -278,7 +278,7 @@ function generateHooksConfig(config, scope) {
|
|
|
278
278
|
{
|
|
279
279
|
type: 'command',
|
|
280
280
|
command: autoMemoryCmd('import', scope),
|
|
281
|
-
timeout:
|
|
281
|
+
timeout: AUTO_MEMORY_HOOK_TIMEOUT_S,
|
|
282
282
|
},
|
|
283
283
|
],
|
|
284
284
|
},
|
|
@@ -306,7 +306,7 @@ function generateHooksConfig(config, scope) {
|
|
|
306
306
|
{
|
|
307
307
|
type: 'command',
|
|
308
308
|
command: autoMemoryCmd('sync', scope),
|
|
309
|
-
timeout:
|
|
309
|
+
timeout: AUTO_MEMORY_HOOK_TIMEOUT_S,
|
|
310
310
|
},
|
|
311
311
|
],
|
|
312
312
|
},
|
package/dist/src/mcp-server.js
CHANGED
|
@@ -619,7 +619,7 @@ export class MCPServerManager extends EventEmitter {
|
|
|
619
619
|
version: VERSION,
|
|
620
620
|
capabilities: {
|
|
621
621
|
tools: { listChanged: true },
|
|
622
|
-
resources: {
|
|
622
|
+
resources: {},
|
|
623
623
|
},
|
|
624
624
|
},
|
|
625
625
|
},
|
|
@@ -696,7 +696,7 @@ export class MCPServerManager extends EventEmitter {
|
|
|
696
696
|
serverInfo: { name: 'ruflo', version: '3.0.0' },
|
|
697
697
|
capabilities: {
|
|
698
698
|
tools: { listChanged: true },
|
|
699
|
-
resources: {
|
|
699
|
+
resources: {},
|
|
700
700
|
},
|
|
701
701
|
},
|
|
702
702
|
};
|
|
@@ -755,6 +755,14 @@ export class MCPServerManager extends EventEmitter {
|
|
|
755
755
|
},
|
|
756
756
|
};
|
|
757
757
|
}
|
|
758
|
+
// The server advertises `resources` (so clients such as Codex offer list_mcp_resources), and a declared capability must
|
|
759
|
+
// be served: it exposes no resources, so the answers are empty, and a read is the spec's "resource not found" (ADR-490).
|
|
760
|
+
case 'resources/list':
|
|
761
|
+
return { jsonrpc: '2.0', id: message.id, result: { resources: [] } };
|
|
762
|
+
case 'resources/templates/list':
|
|
763
|
+
return { jsonrpc: '2.0', id: message.id, result: { resourceTemplates: [] } };
|
|
764
|
+
case 'resources/read':
|
|
765
|
+
return { jsonrpc: '2.0', id: message.id, error: { code: -32002, message: 'Resource not found' } };
|
|
758
766
|
case 'notifications/initialized':
|
|
759
767
|
// Client notification - no response needed
|
|
760
768
|
console.error(`[${new Date().toISOString()}] INFO [claude-flow-mcp] (${sessionId}) Client initialized`);
|
|
@@ -97,7 +97,7 @@ export interface CapabilityBrain {
|
|
|
97
97
|
};
|
|
98
98
|
}
|
|
99
99
|
/** Top-level commands from the CLI command loader. Kept as a checked manifest. */
|
|
100
|
-
export declare const RUFLO_CLI_COMMANDS: readonly ["init", "start", "status", "task", "session", "agent", "swarm", "memory", "mcp", "config", "migrate", "hooks", "workflow", "hive-mind", "process", "daemon", "version", "neural", "security", "performance", "providers", "plugins", "deployment", "claims", "policy", "embeddings", "completions", "doctor", "verify", "analyze", "route", "progress", "issues", "update", "ruvector", "benchmark", "guidance", "appliance", "appliance-advanced", "transfer-store", "cleanup", "autopilot", "gaia-bench", "metaharness", "eject", "funnel", "settings", "auth", "proxy", "advisor", "spinner", "announcements"];
|
|
100
|
+
export declare const RUFLO_CLI_COMMANDS: readonly ["init", "start", "status", "task", "session", "agent", "swarm", "memory", "mcp", "config", "migrate", "hooks", "workflow", "hive-mind", "process", "daemon", "version", "neural", "security", "performance", "providers", "plugins", "deployment", "claims", "policy", "embeddings", "completions", "doctor", "verify", "analyze", "route", "progress", "issues", "update", "ruvector", "benchmark", "guidance", "appliance", "appliance-advanced", "transfer-store", "cleanup", "autopilot", "gaia-bench", "metaharness", "eject", "funnel", "settings", "auth", "proxy", "advisor", "spinner", "announcements", "tui"];
|
|
101
101
|
export interface ImplementationLoopStep {
|
|
102
102
|
id: string;
|
|
103
103
|
name: string;
|
|
@@ -16,7 +16,7 @@ export const RUFLO_CLI_COMMANDS = [
|
|
|
16
16
|
'ruvector', 'benchmark', 'guidance', 'appliance', 'appliance-advanced',
|
|
17
17
|
'transfer-store', 'cleanup', 'autopilot', 'gaia-bench', 'metaharness',
|
|
18
18
|
'eject', 'funnel', 'settings', 'auth', 'proxy', 'advisor', 'spinner',
|
|
19
|
-
'announcements',
|
|
19
|
+
'announcements', 'tui',
|
|
20
20
|
];
|
|
21
21
|
export const IMPLEMENTATION_LOOP = [
|
|
22
22
|
{
|
|
@@ -8,6 +8,7 @@ import * as nodeFs from 'fs';
|
|
|
8
8
|
import { dirname, join, resolve } from 'path';
|
|
9
9
|
import { getProjectCwd } from './types.js';
|
|
10
10
|
import { validateIdentifier, validateText, validatePath } from './validate-input.js';
|
|
11
|
+
import { commandRecordingDisabled, redactCommandForStorage } from '../services/command-recording.js';
|
|
11
12
|
import { checkCommandLoop, recordCommandOutcome } from './tool-loop-guardrail.js';
|
|
12
13
|
import { buildLearnedRoutingPatterns, } from '../services/learned-routing.js';
|
|
13
14
|
import { applyTypesafeRouting, getTypesafeRouter } from '../ruvector/typesafe-router.js';
|
|
@@ -1014,13 +1015,34 @@ export const hooksPostCommand = {
|
|
|
1014
1015
|
// #6: feed the tool-loop circuit breaker so pre-command can warn/block on
|
|
1015
1016
|
// repeated consecutive failures of the same command.
|
|
1016
1017
|
recordCommandOutcome(command, success);
|
|
1018
|
+
// #4056: command history is opt-out (CLAUDE_FLOW_HOOKS_RECORD_COMMANDS=0,
|
|
1019
|
+
// CLAUDE_FLOW_HOOKS_ENABLED=false, or hooks.recordCommands=false in the
|
|
1020
|
+
// config). The circuit breaker above is in-memory only; nothing below
|
|
1021
|
+
// runs when recording is off, so no command text reaches the store or
|
|
1022
|
+
// the trajectory pipeline.
|
|
1023
|
+
if (commandRecordingDisabled(process.env, getProjectCwd())) {
|
|
1024
|
+
return {
|
|
1025
|
+
recorded: false,
|
|
1026
|
+
disabled: true,
|
|
1027
|
+
command: undefined,
|
|
1028
|
+
exitCode,
|
|
1029
|
+
success,
|
|
1030
|
+
timestamp: new Date().toISOString(),
|
|
1031
|
+
_storedIn: 'none',
|
|
1032
|
+
learningPath: 'recorded-only',
|
|
1033
|
+
trajectoriesDelta: 0,
|
|
1034
|
+
note: 'Command recording is switched off (CLAUDE_FLOW_HOOKS_RECORD_COMMANDS / CLAUDE_FLOW_HOOKS_ENABLED / hooks.recordCommands); nothing was stored.',
|
|
1035
|
+
};
|
|
1036
|
+
}
|
|
1037
|
+
// What is stored is the redacted, length-capped form, never the raw text.
|
|
1038
|
+
const storedCommand = redactCommandForStorage(command);
|
|
1017
1039
|
// Persist command outcome via AgentDB
|
|
1018
1040
|
let _storedIn = 'none';
|
|
1019
1041
|
try {
|
|
1020
1042
|
const bridge = await import('../memory/memory-bridge.js');
|
|
1021
1043
|
const stored = await bridge.bridgeStoreEntry({
|
|
1022
1044
|
key: `cmd-${Date.now()}`,
|
|
1023
|
-
value: JSON.stringify({ command, exitCode, success }),
|
|
1045
|
+
value: JSON.stringify({ command: storedCommand, exitCode, success }),
|
|
1024
1046
|
namespace: 'commands',
|
|
1025
1047
|
tags: [success ? 'success' : 'error'],
|
|
1026
1048
|
ttl,
|
|
@@ -1042,7 +1064,7 @@ export const hooksPostCommand = {
|
|
|
1042
1064
|
}
|
|
1043
1065
|
const key = `cmd-${now}`;
|
|
1044
1066
|
store.entries[key] = {
|
|
1045
|
-
key, value: JSON.stringify({ command, exitCode, success }), namespace: 'commands',
|
|
1067
|
+
key, value: JSON.stringify({ command: storedCommand, exitCode, success }), namespace: 'commands',
|
|
1046
1068
|
storedAt: new Date(now).toISOString(), lastAccessed: new Date(now).toISOString(), accessCount: 0,
|
|
1047
1069
|
expiresAt: new Date(now + ttl * 1000).toISOString(),
|
|
1048
1070
|
};
|
|
@@ -1063,8 +1085,8 @@ export const hooksPostCommand = {
|
|
|
1063
1085
|
const before = intel.getIntelligenceStats().trajectoriesRecorded;
|
|
1064
1086
|
await intel.recordTrajectory([{
|
|
1065
1087
|
type: 'action',
|
|
1066
|
-
content: `Command \`${
|
|
1067
|
-
metadata: { hook: 'post-command', command:
|
|
1088
|
+
content: `Command \`${storedCommand.slice(0, 200)}\` exited ${exitCode} (${success ? 'success' : 'failure'})`,
|
|
1089
|
+
metadata: { hook: 'post-command', command: storedCommand.slice(0, 500), exitCode, success },
|
|
1068
1090
|
timestamp: Date.now(),
|
|
1069
1091
|
}], success ? 'success' : 'failure');
|
|
1070
1092
|
trajectoriesDelta = intel.getIntelligenceStats().trajectoriesRecorded - before;
|
|
@@ -1402,7 +1402,8 @@ export const memoryTools = [
|
|
|
1402
1402
|
if (!v.valid)
|
|
1403
1403
|
throw new Error(v.error);
|
|
1404
1404
|
}
|
|
1405
|
-
|
|
1405
|
+
// #4056: TTL hides expired rows from normal reads, so the cleanup has to ask for them.
|
|
1406
|
+
const all = await listEntries({ limit: 100000, namespace, includeExpired: true });
|
|
1406
1407
|
const now = Date.now();
|
|
1407
1408
|
const expired = all.entries.filter(e => {
|
|
1408
1409
|
const exp = e.expiresAt;
|