claude-flow 3.32.2 → 3.32.9
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/statusline.cjs +93 -39
- package/.claude-plugin/hooks/hooks.json +3 -1
- package/.claude-plugin/scripts/ruflo-hook.cjs +2 -2
- package/.claude-plugin/scripts/ruflo-hook.sh +17 -17
- package/package.json +9 -3
- package/v3/@claude-flow/cli/catalog-manifest.json +2 -2
- package/v3/@claude-flow/cli/dist/src/auth/client.d.ts +89 -0
- package/v3/@claude-flow/cli/dist/src/auth/client.js +242 -0
- package/v3/@claude-flow/cli/dist/src/auth/constants.d.ts +7 -0
- package/v3/@claude-flow/cli/dist/src/auth/constants.js +7 -0
- package/v3/@claude-flow/cli/dist/src/auth/scopes.d.ts +14 -0
- package/v3/@claude-flow/cli/dist/src/auth/scopes.js +21 -0
- package/v3/@claude-flow/cli/dist/src/auth/security-bridge.d.ts +36 -0
- package/v3/@claude-flow/cli/dist/src/auth/security-bridge.js +42 -0
- package/v3/@claude-flow/cli/dist/src/auth/session.d.ts +20 -0
- package/v3/@claude-flow/cli/dist/src/auth/session.js +32 -0
- package/v3/@claude-flow/cli/dist/src/auth/state.d.ts +19 -0
- package/v3/@claude-flow/cli/dist/src/auth/state.js +53 -0
- package/v3/@claude-flow/cli/dist/src/auth/types.d.ts +27 -0
- package/v3/@claude-flow/cli/dist/src/auth/types.js +11 -0
- package/v3/@claude-flow/cli/dist/src/commands/auth.d.ts +15 -0
- package/v3/@claude-flow/cli/dist/src/commands/auth.js +244 -0
- package/v3/@claude-flow/cli/dist/src/commands/doctor.js +478 -24
- package/v3/@claude-flow/cli/dist/src/commands/hooks.js +43 -1
- package/v3/@claude-flow/cli/dist/src/commands/index.js +2 -0
- package/v3/@claude-flow/cli/dist/src/commands/proxy-lifecycle.d.ts +20 -0
- package/v3/@claude-flow/cli/dist/src/commands/proxy-lifecycle.js +277 -0
- package/v3/@claude-flow/cli/dist/src/commands/proxy.js +97 -4
- package/v3/@claude-flow/cli/dist/src/funnel/message-transport.d.ts +11 -4
- package/v3/@claude-flow/cli/dist/src/funnel/message-transport.js +11 -4
- package/v3/@claude-flow/cli/dist/src/mcp-tools/memory-tools.js +11 -3
- package/v3/@claude-flow/cli/dist/src/memory/graph-edge-writer.d.ts +9 -0
- package/v3/@claude-flow/cli/dist/src/memory/graph-edge-writer.js +13 -0
- package/v3/@claude-flow/cli/dist/src/memory/memory-bridge.js +32 -5
- package/v3/@claude-flow/cli/dist/src/memory/memory-initializer.js +72 -0
- package/v3/@claude-flow/cli/dist/src/proxy/install.d.ts +29 -0
- package/v3/@claude-flow/cli/dist/src/proxy/install.js +135 -0
- package/v3/@claude-flow/cli/dist/src/proxy/lifecycle.d.ts +61 -0
- package/v3/@claude-flow/cli/dist/src/proxy/lifecycle.js +249 -0
- package/v3/@claude-flow/cli/dist/src/proxy/paths.d.ts +34 -0
- package/v3/@claude-flow/cli/dist/src/proxy/paths.js +70 -0
- package/v3/@claude-flow/cli/dist/src/proxy/release.d.ts +47 -0
- package/v3/@claude-flow/cli/dist/src/proxy/release.js +138 -0
- package/v3/@claude-flow/cli/dist/src/proxy/token-bridge.d.ts +5 -0
- package/v3/@claude-flow/cli/dist/src/proxy/token-bridge.js +61 -0
- package/v3/@claude-flow/cli/dist/src/proxy/verify.d.ts +44 -0
- package/v3/@claude-flow/cli/dist/src/proxy/verify.js +68 -0
- package/v3/@claude-flow/cli/package.json +6 -4
- package/.claude/.proven-config-version +0 -1
- package/.claude/proven-config.json +0 -42
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `ruflo proxy install|start|stop|status|logs|update|uninstall` (ADR-307) —
|
|
3
|
+
* the full lifecycle command set, kept in its own file so
|
|
4
|
+
* src/commands/proxy.ts (the ADR-313/314/315 consent subcommands) stays
|
|
5
|
+
* under the repo's 500-line-per-file convention. Merged into one
|
|
6
|
+
* `proxyCommand` in proxy.ts.
|
|
7
|
+
*/
|
|
8
|
+
import type { Command } from '../types.js';
|
|
9
|
+
import { type ProxyStatus } from '../proxy/lifecycle.js';
|
|
10
|
+
/** Pinned and reviewed; later upgrades remain explicit commands. */
|
|
11
|
+
export declare const DEFAULT_PROXY_RELEASE = "0.4.0";
|
|
12
|
+
/**
|
|
13
|
+
* Human-oriented next steps for `ruflo proxy` and `ruflo proxy status`.
|
|
14
|
+
* Keep this independent of the command framework so the state-specific
|
|
15
|
+
* guidance has a small, direct regression-test surface.
|
|
16
|
+
*/
|
|
17
|
+
export declare function proxyConsoleGuidance(status: ProxyStatus): string[];
|
|
18
|
+
export declare function printProxyConsoleGuidance(status: ProxyStatus): void;
|
|
19
|
+
export declare const proxyLifecycleSubcommands: Command[];
|
|
20
|
+
//# sourceMappingURL=proxy-lifecycle.d.ts.map
|
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `ruflo proxy install|start|stop|status|logs|update|uninstall` (ADR-307) —
|
|
3
|
+
* the full lifecycle command set, kept in its own file so
|
|
4
|
+
* src/commands/proxy.ts (the ADR-313/314/315 consent subcommands) stays
|
|
5
|
+
* under the repo's 500-line-per-file convention. Merged into one
|
|
6
|
+
* `proxyCommand` in proxy.ts.
|
|
7
|
+
*/
|
|
8
|
+
import { output } from '../output.js';
|
|
9
|
+
import { hasConsent, recordConsent, revokeConsent } from '../funnel/index.js';
|
|
10
|
+
import { installProxy, uninstallProxy } from '../proxy/install.js';
|
|
11
|
+
import { startForeground, startBackground, stopProxy, getProxyStatus, readProxyLogTail, watchProxyLog, ProxyNotInstalledError, ProxyAlreadyRunningError, } from '../proxy/lifecycle.js';
|
|
12
|
+
import { proxyTokenPath } from '../proxy/paths.js';
|
|
13
|
+
import { removeInjectedToken, startTokenRefreshPump } from '../proxy/token-bridge.js';
|
|
14
|
+
/** Pinned and reviewed; later upgrades remain explicit commands. */
|
|
15
|
+
export const DEFAULT_PROXY_RELEASE = '0.4.0';
|
|
16
|
+
const PROXY_COMMAND = 'npx ruflo@latest proxy';
|
|
17
|
+
const AUTH_COMMAND = 'npx ruflo@latest auth';
|
|
18
|
+
/**
|
|
19
|
+
* Human-oriented next steps for `ruflo proxy` and `ruflo proxy status`.
|
|
20
|
+
* Keep this independent of the command framework so the state-specific
|
|
21
|
+
* guidance has a small, direct regression-test surface.
|
|
22
|
+
*/
|
|
23
|
+
export function proxyConsoleGuidance(status) {
|
|
24
|
+
if (!status.installed) {
|
|
25
|
+
return [
|
|
26
|
+
'',
|
|
27
|
+
'Next step',
|
|
28
|
+
` ${PROXY_COMMAND} install --yes Install signed Meta-Proxy v${DEFAULT_PROXY_RELEASE}`,
|
|
29
|
+
'',
|
|
30
|
+
'After installation, start it in the background:',
|
|
31
|
+
` ${PROXY_COMMAND} start --service`,
|
|
32
|
+
];
|
|
33
|
+
}
|
|
34
|
+
if (!status.running) {
|
|
35
|
+
return [
|
|
36
|
+
'',
|
|
37
|
+
'Next step',
|
|
38
|
+
` ${PROXY_COMMAND} start --service Start the proxy in the background`,
|
|
39
|
+
'',
|
|
40
|
+
'Foreground mode (shows live logs; Ctrl+C stops it):',
|
|
41
|
+
` ${PROXY_COMMAND} start`,
|
|
42
|
+
'',
|
|
43
|
+
'Optional: enable Cognitum cloud routing (local-only is the default):',
|
|
44
|
+
` ${AUTH_COMMAND} login`,
|
|
45
|
+
` ${PROXY_COMMAND} config --cloud --yes`,
|
|
46
|
+
];
|
|
47
|
+
}
|
|
48
|
+
return [
|
|
49
|
+
'',
|
|
50
|
+
'Meta Proxy is ready.',
|
|
51
|
+
` Logs: ${PROXY_COMMAND} logs`,
|
|
52
|
+
` Stop: ${PROXY_COMMAND} stop`,
|
|
53
|
+
'',
|
|
54
|
+
'Optional: enable Cognitum cloud routing (local-only is the default):',
|
|
55
|
+
` ${AUTH_COMMAND} login`,
|
|
56
|
+
` ${PROXY_COMMAND} config --cloud --yes`,
|
|
57
|
+
];
|
|
58
|
+
}
|
|
59
|
+
export function printProxyConsoleGuidance(status) {
|
|
60
|
+
for (const line of proxyConsoleGuidance(status))
|
|
61
|
+
output.writeln(line);
|
|
62
|
+
}
|
|
63
|
+
const INSTALL_DISCLOSURE = [
|
|
64
|
+
'Installing the Meta LLM Proxy (ADR-304/307).',
|
|
65
|
+
'',
|
|
66
|
+
'This downloads a separately-released Rust binary (Ed25519-signature and',
|
|
67
|
+
'checksum verified before anything is written to disk) and runs it as a',
|
|
68
|
+
'local process bound to 127.0.0.1 only. The proxy routes to LOCAL',
|
|
69
|
+
'backends by default — no prompt leaves this machine. Cloud routing is a',
|
|
70
|
+
'separate, explicit opt-in (`ruflo proxy config --cloud`), never enabled',
|
|
71
|
+
'by install alone.',
|
|
72
|
+
'',
|
|
73
|
+
'Uninstall anytime: ruflo proxy uninstall',
|
|
74
|
+
].join('\n');
|
|
75
|
+
const installSub = {
|
|
76
|
+
name: 'install',
|
|
77
|
+
description: 'Download, verify, and install the meta-proxy binary (ADR-307)',
|
|
78
|
+
options: [
|
|
79
|
+
// NOT named 'version' — index.ts:107 globally intercepts any --version/-v
|
|
80
|
+
// anywhere in argv (`if (flags.version || flags.V) { showVersion(); return; }`)
|
|
81
|
+
// BEFORE subcommand dispatch, regardless of which command defines it. A
|
|
82
|
+
// subcommand-local --version is silently swallowed by the CLI's own
|
|
83
|
+
// `ruflo --version` handling — confirmed the hard way in E2E testing.
|
|
84
|
+
{ name: 'release', description: `Release version to install (default: ${DEFAULT_PROXY_RELEASE})`, type: 'string' },
|
|
85
|
+
{ name: 'yes', description: 'Skip the confirmation prompt', type: 'boolean', default: false },
|
|
86
|
+
],
|
|
87
|
+
action: async (ctx) => {
|
|
88
|
+
// Resolve the reviewed default before the consent gate. An explicit empty
|
|
89
|
+
// override still fails below without recording a consent receipt.
|
|
90
|
+
const version = typeof ctx.flags.release === 'string' ? ctx.flags.release : DEFAULT_PROXY_RELEASE;
|
|
91
|
+
if (!version) {
|
|
92
|
+
output.printError('ruflo proxy install requires --release <x.y.z> — there is no version-discovery ' +
|
|
93
|
+
'endpoint yet (see the plan doc for the tracked follow-up). Find the latest at ' +
|
|
94
|
+
'the release channel and pass it explicitly.');
|
|
95
|
+
return { success: false, exitCode: 1 };
|
|
96
|
+
}
|
|
97
|
+
if (!hasConsent('proxy-install')) {
|
|
98
|
+
output.writeln(INSTALL_DISCLOSURE);
|
|
99
|
+
output.writeln('');
|
|
100
|
+
const confirmed = Boolean(ctx.flags.yes);
|
|
101
|
+
if (!confirmed) {
|
|
102
|
+
output.writeln(`Re-run with --yes to confirm: ruflo proxy install --yes (installs ${version})`);
|
|
103
|
+
return { success: true, data: { confirmed: false } };
|
|
104
|
+
}
|
|
105
|
+
recordConsent('proxy-install', true, 'proxy-install');
|
|
106
|
+
}
|
|
107
|
+
try {
|
|
108
|
+
const spinner = output.createSpinner({ text: `Installing meta-proxy ${version}...`, spinner: 'dots' });
|
|
109
|
+
spinner.start();
|
|
110
|
+
const result = await installProxy({ version, log: (line) => spinner.setText(line) });
|
|
111
|
+
spinner.succeed(`meta-proxy ${version} installed`);
|
|
112
|
+
output.writeln(` binary: ${result.binaryPath}`);
|
|
113
|
+
output.writeln(` sha256: ${result.sha256}`);
|
|
114
|
+
return { success: true, data: result };
|
|
115
|
+
}
|
|
116
|
+
catch (e) {
|
|
117
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
118
|
+
output.printError('Install failed', message);
|
|
119
|
+
return { success: false, message, exitCode: 1 };
|
|
120
|
+
}
|
|
121
|
+
},
|
|
122
|
+
};
|
|
123
|
+
const updateSub = {
|
|
124
|
+
name: 'update',
|
|
125
|
+
description: 'Re-verify and replace the installed binary with a specific version (never automatic)',
|
|
126
|
+
options: [{ name: 'release', description: 'Release version to install', type: 'string', required: true }],
|
|
127
|
+
action: async (ctx) => {
|
|
128
|
+
const version = typeof ctx.flags.release === 'string' ? ctx.flags.release : undefined;
|
|
129
|
+
if (!version) {
|
|
130
|
+
output.printError('ruflo proxy update requires --release <x.y.z>');
|
|
131
|
+
return { success: false, exitCode: 1 };
|
|
132
|
+
}
|
|
133
|
+
try {
|
|
134
|
+
const spinner = output.createSpinner({ text: `Updating meta-proxy to ${version}...`, spinner: 'dots' });
|
|
135
|
+
spinner.start();
|
|
136
|
+
const result = await installProxy({ version, log: (line) => spinner.setText(line) });
|
|
137
|
+
spinner.succeed(`meta-proxy updated to ${version}`);
|
|
138
|
+
output.writeln(` binary: ${result.binaryPath}`);
|
|
139
|
+
return { success: true, data: result };
|
|
140
|
+
}
|
|
141
|
+
catch (e) {
|
|
142
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
143
|
+
output.printError('Update failed', message);
|
|
144
|
+
return { success: false, message, exitCode: 1 };
|
|
145
|
+
}
|
|
146
|
+
},
|
|
147
|
+
};
|
|
148
|
+
const startSub = {
|
|
149
|
+
name: 'start',
|
|
150
|
+
description: 'Start meta-proxy (foreground by default; --service to detach)',
|
|
151
|
+
options: [
|
|
152
|
+
{ name: 'service', description: 'Run detached (background), survives terminal close but not a reboot — full OS-service registration is not yet implemented', type: 'boolean', default: false },
|
|
153
|
+
],
|
|
154
|
+
action: async (ctx) => {
|
|
155
|
+
const service = Boolean(ctx.flags.service);
|
|
156
|
+
try {
|
|
157
|
+
if (service) {
|
|
158
|
+
const { pid } = await startBackground();
|
|
159
|
+
output.printSuccess(`meta-proxy started in the background (pid ${pid})`);
|
|
160
|
+
output.writeln(' Note: survives this terminal closing, but not a reboot — OS-service registration is not yet implemented.');
|
|
161
|
+
output.writeln(' Logs: ruflo proxy logs');
|
|
162
|
+
return { success: true, data: { pid } };
|
|
163
|
+
}
|
|
164
|
+
output.writeln('Starting meta-proxy in the foreground — press Ctrl+C to stop.');
|
|
165
|
+
await startTokenRefreshPump();
|
|
166
|
+
await startForeground(); // never returns normally
|
|
167
|
+
return { success: true };
|
|
168
|
+
}
|
|
169
|
+
catch (e) {
|
|
170
|
+
if (e instanceof ProxyNotInstalledError || e instanceof ProxyAlreadyRunningError) {
|
|
171
|
+
output.printError(e.message);
|
|
172
|
+
return { success: false, message: e.message, exitCode: 1 };
|
|
173
|
+
}
|
|
174
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
175
|
+
output.printError('Failed to start meta-proxy', message);
|
|
176
|
+
return { success: false, message, exitCode: 1 };
|
|
177
|
+
}
|
|
178
|
+
},
|
|
179
|
+
};
|
|
180
|
+
const superviseSub = {
|
|
181
|
+
name: 'supervise',
|
|
182
|
+
description: 'Internal detached supervisor for token refresh and meta-proxy lifecycle',
|
|
183
|
+
action: async () => {
|
|
184
|
+
await startTokenRefreshPump();
|
|
185
|
+
await startForeground(true);
|
|
186
|
+
return { success: true };
|
|
187
|
+
},
|
|
188
|
+
};
|
|
189
|
+
const stopSub = {
|
|
190
|
+
name: 'stop',
|
|
191
|
+
description: 'Stop a running meta-proxy process',
|
|
192
|
+
action: async () => {
|
|
193
|
+
const result = await stopProxy();
|
|
194
|
+
removeInjectedToken();
|
|
195
|
+
if (!result.wasRunning) {
|
|
196
|
+
output.writeln('meta-proxy was not running.');
|
|
197
|
+
return { success: true, data: result };
|
|
198
|
+
}
|
|
199
|
+
output.printSuccess(`meta-proxy stopped (was pid ${result.pid})`);
|
|
200
|
+
return { success: true, data: result };
|
|
201
|
+
},
|
|
202
|
+
};
|
|
203
|
+
const statusSub = {
|
|
204
|
+
name: 'status',
|
|
205
|
+
description: 'Show meta-proxy install + process status',
|
|
206
|
+
options: [{ name: 'json', description: 'Machine-readable output', type: 'boolean', default: false }],
|
|
207
|
+
action: async (ctx) => {
|
|
208
|
+
const status = getProxyStatus();
|
|
209
|
+
if (ctx.flags.json) {
|
|
210
|
+
output.printJson(status);
|
|
211
|
+
return { success: true, data: status };
|
|
212
|
+
}
|
|
213
|
+
output.writeln('Meta Proxy');
|
|
214
|
+
output.writeln(` Installation: ${status.installed ? 'ready' : 'not installed'}`);
|
|
215
|
+
output.writeln(` Process: ${status.running ? `running (pid ${status.pid})` : 'not running'}`);
|
|
216
|
+
if (status.stalePidFile)
|
|
217
|
+
output.writeln(' (a stale PID file was found and will be cleared on next start)');
|
|
218
|
+
printProxyConsoleGuidance(status);
|
|
219
|
+
return { success: true, data: status };
|
|
220
|
+
},
|
|
221
|
+
};
|
|
222
|
+
const logsSub = {
|
|
223
|
+
name: 'logs',
|
|
224
|
+
description: 'Show meta-proxy --service logs',
|
|
225
|
+
options: [
|
|
226
|
+
{ name: 'follow', short: 'f', description: 'Stream new log lines as they arrive', type: 'boolean', default: false },
|
|
227
|
+
],
|
|
228
|
+
action: async (ctx) => {
|
|
229
|
+
if (ctx.flags.follow) {
|
|
230
|
+
output.writeln('Following meta-proxy logs — press Ctrl+C to stop.');
|
|
231
|
+
try {
|
|
232
|
+
const watcher = watchProxyLog((line) => output.writeln(line));
|
|
233
|
+
await new Promise((resolve) => {
|
|
234
|
+
process.on('SIGINT', () => {
|
|
235
|
+
watcher.close();
|
|
236
|
+
resolve();
|
|
237
|
+
});
|
|
238
|
+
});
|
|
239
|
+
return { success: true };
|
|
240
|
+
}
|
|
241
|
+
catch (e) {
|
|
242
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
243
|
+
output.printError(message);
|
|
244
|
+
return { success: false, message, exitCode: 1 };
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
const tail = readProxyLogTail();
|
|
248
|
+
if (!tail) {
|
|
249
|
+
output.writeln('No log content yet — meta-proxy has never been started in --service mode.');
|
|
250
|
+
return { success: true, data: { empty: true } };
|
|
251
|
+
}
|
|
252
|
+
output.writeln(tail);
|
|
253
|
+
return { success: true };
|
|
254
|
+
},
|
|
255
|
+
};
|
|
256
|
+
const uninstallSub = {
|
|
257
|
+
name: 'uninstall',
|
|
258
|
+
description: 'Stop the proxy (if running), remove the binary, token, and consent receipt',
|
|
259
|
+
action: async () => {
|
|
260
|
+
const status = getProxyStatus();
|
|
261
|
+
if (status.running) {
|
|
262
|
+
await stopProxy();
|
|
263
|
+
output.writeln('Stopped the running meta-proxy process.');
|
|
264
|
+
}
|
|
265
|
+
const removed = await uninstallProxy();
|
|
266
|
+
const { existsSync, unlinkSync } = await import('node:fs');
|
|
267
|
+
const tokenPath = proxyTokenPath();
|
|
268
|
+
if (existsSync(tokenPath))
|
|
269
|
+
unlinkSync(tokenPath);
|
|
270
|
+
removeInjectedToken();
|
|
271
|
+
revokeConsent('proxy-install', 'proxy-uninstall');
|
|
272
|
+
output.printSuccess(removed ? 'meta-proxy uninstalled.' : 'Nothing was installed — cleaned up any leftover state.');
|
|
273
|
+
return { success: true, data: { removed } };
|
|
274
|
+
},
|
|
275
|
+
};
|
|
276
|
+
export const proxyLifecycleSubcommands = [installSub, updateSub, startSub, superviseSub, stopSub, statusSub, logsSub, uninstallSub];
|
|
277
|
+
//# sourceMappingURL=proxy-lifecycle.js.map
|
|
@@ -23,6 +23,8 @@ import { clearRateLimitStatus, readRateLimitStatus } from '../funnel/rate-limit-
|
|
|
23
23
|
import { clearQuotaLowStatus, readQuotaLowStatus } from '../funnel/power-saver-notifier.js';
|
|
24
24
|
import { getInstalledCliVersion } from '../init/helper-refresh.js';
|
|
25
25
|
import * as path from 'path';
|
|
26
|
+
import { proxyLifecycleSubcommands, printProxyConsoleGuidance } from './proxy-lifecycle.js';
|
|
27
|
+
import { getProxyStatus } from '../proxy/lifecycle.js';
|
|
26
28
|
const PROXY_CONFIG_FILE = 'proxy-config.toml';
|
|
27
29
|
/**
|
|
28
30
|
* Minimal hand-rolled TOML writer — this config has exactly one boolean
|
|
@@ -40,12 +42,12 @@ function readProxyConfigRaw() {
|
|
|
40
42
|
return '';
|
|
41
43
|
}
|
|
42
44
|
}
|
|
43
|
-
function
|
|
45
|
+
function writeConfigLine(field, rawValue) {
|
|
44
46
|
const dir = funnelStateDir();
|
|
45
47
|
fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
46
48
|
const target = path.join(dir, PROXY_CONFIG_FILE);
|
|
47
49
|
const raw = readProxyConfigRaw();
|
|
48
|
-
const line = `${field} = ${
|
|
50
|
+
const line = `${field} = ${rawValue}`;
|
|
49
51
|
const pattern = new RegExp(`^${field}\\s*=.*$`, 'm');
|
|
50
52
|
let next;
|
|
51
53
|
if (pattern.test(raw)) {
|
|
@@ -58,6 +60,9 @@ function writeConsentMirrorLine(field, value) {
|
|
|
58
60
|
fs.writeFileSync(tmp, next, { encoding: 'utf-8', mode: 0o600 });
|
|
59
61
|
fs.renameSync(tmp, target);
|
|
60
62
|
}
|
|
63
|
+
function writeConsentMirrorLine(field, value) {
|
|
64
|
+
writeConfigLine(field, String(value));
|
|
65
|
+
}
|
|
61
66
|
function writeSponsoredConsentMirror(granted) {
|
|
62
67
|
writeConsentMirrorLine('sponsored_consent_granted', granted);
|
|
63
68
|
}
|
|
@@ -67,6 +72,78 @@ function writePowerSaverConsentMirror(granted) {
|
|
|
67
72
|
function writeTrainingShareConsentMirror(granted) {
|
|
68
73
|
writeConsentMirrorLine('training_share_consent_granted', granted);
|
|
69
74
|
}
|
|
75
|
+
/**
|
|
76
|
+
* `default_data_plane` — the ADR-304 cloud-routing toggle. Values confirmed
|
|
77
|
+
* against meta-proxy's actual `DataPlane` enum (`src/config.rs`,
|
|
78
|
+
* `#[serde(rename_all = "snake_case")]`): "local" | "cloud" | "sponsored" |
|
|
79
|
+
* "passthrough" (the last two are not written by this command — sponsored
|
|
80
|
+
* is ADR-313's own consent flag, passthrough is the proxy's own default).
|
|
81
|
+
*/
|
|
82
|
+
function readDataPlane() {
|
|
83
|
+
const raw = readProxyConfigRaw();
|
|
84
|
+
const match = raw.match(/^default_data_plane\s*=\s*"([^"]*)"/m);
|
|
85
|
+
return match ? match[1] : 'passthrough'; // matches the Rust struct's own default
|
|
86
|
+
}
|
|
87
|
+
function writeDataPlane(plane) {
|
|
88
|
+
writeConfigLine('default_data_plane', `"${plane}"`);
|
|
89
|
+
}
|
|
90
|
+
const CLOUD_ROUTING_DISCLOSURE = [
|
|
91
|
+
'Enabling cloud routing.',
|
|
92
|
+
'',
|
|
93
|
+
'With cloud routing ON, prompts for cloud-tier requests are sent to',
|
|
94
|
+
'api.cognitum.one and forwarded to the selected provider',
|
|
95
|
+
'(Claude / GPT / Gemini / DeepSeek / OpenRouter).',
|
|
96
|
+
'',
|
|
97
|
+
'Requests routed to local backends never leave this machine.',
|
|
98
|
+
'',
|
|
99
|
+
'Disable anytime: ruflo proxy config --local-only',
|
|
100
|
+
].join('\n');
|
|
101
|
+
const configSub = {
|
|
102
|
+
name: 'config',
|
|
103
|
+
description: 'Toggle cloud routing (ADR-304) — local backends only by default',
|
|
104
|
+
options: [
|
|
105
|
+
{ name: 'cloud', description: 'Enable cloud routing (requires cloud-routing consent)', type: 'boolean', default: false },
|
|
106
|
+
{ name: 'local-only', description: 'Disable cloud routing, revert to local-only routing', type: 'boolean', default: false },
|
|
107
|
+
{ name: 'yes', description: 'Skip the confirmation prompt', type: 'boolean', default: false },
|
|
108
|
+
],
|
|
109
|
+
action: async (ctx) => {
|
|
110
|
+
const wantCloud = Boolean(ctx.flags.cloud);
|
|
111
|
+
const wantLocalOnly = Boolean(ctx.flags.localOnly ?? ctx.flags['local-only']);
|
|
112
|
+
if (wantCloud && wantLocalOnly) {
|
|
113
|
+
output.printError('Pass either --cloud or --local-only, not both.');
|
|
114
|
+
return { success: false, exitCode: 1 };
|
|
115
|
+
}
|
|
116
|
+
if (!wantCloud && !wantLocalOnly) {
|
|
117
|
+
const plane = readDataPlane();
|
|
118
|
+
output.writeln(`Current data plane: ${plane}`);
|
|
119
|
+
output.writeln(plane === 'cloud'
|
|
120
|
+
? 'Cloud routing is ON — cloud-tier requests go to api.cognitum.one.'
|
|
121
|
+
: 'Cloud routing is OFF — requests never leave this machine (or use your own Claude subscription on Passthrough).');
|
|
122
|
+
return { success: true, data: { plane } };
|
|
123
|
+
}
|
|
124
|
+
if (wantLocalOnly) {
|
|
125
|
+
writeDataPlane('local');
|
|
126
|
+
revokeConsent('cloud-routing', 'proxy-config-local-only');
|
|
127
|
+
output.printSuccess('Cloud routing disabled — reverted to local-only routing.');
|
|
128
|
+
return { success: true, data: { plane: 'local' } };
|
|
129
|
+
}
|
|
130
|
+
// wantCloud
|
|
131
|
+
if (!hasConsent('cloud-routing')) {
|
|
132
|
+
output.writeln(CLOUD_ROUTING_DISCLOSURE);
|
|
133
|
+
output.writeln('');
|
|
134
|
+
if (!ctx.flags.yes) {
|
|
135
|
+
output.writeln('Re-run with --yes to confirm: ruflo proxy config --cloud --yes');
|
|
136
|
+
return { success: true, data: { confirmed: false } };
|
|
137
|
+
}
|
|
138
|
+
recordConsent('cloud-routing', true, 'proxy-config-cloud');
|
|
139
|
+
}
|
|
140
|
+
writeDataPlane('cloud');
|
|
141
|
+
output.printSuccess('Cloud routing enabled.');
|
|
142
|
+
output.writeln(' Requests routed to local backends still never leave this machine.');
|
|
143
|
+
output.writeln(' Disable anytime: ruflo proxy config --local-only');
|
|
144
|
+
return { success: true, data: { plane: 'cloud' } };
|
|
145
|
+
},
|
|
146
|
+
};
|
|
70
147
|
const SPONSOR_DISCLOSURE = [
|
|
71
148
|
'Enabling sponsored downtime mode.',
|
|
72
149
|
'',
|
|
@@ -292,19 +369,35 @@ const trainingShareStatusSub = {
|
|
|
292
369
|
};
|
|
293
370
|
export const proxyCommand = {
|
|
294
371
|
name: 'proxy',
|
|
295
|
-
description: 'Meta LLM Proxy — sponsored downtime + power saver + training-data sharing (ADR-304/307/313/314/315)',
|
|
372
|
+
description: 'Meta LLM Proxy — install/lifecycle + sponsored downtime + power saver + training-data sharing (ADR-304/307/313/314/315)',
|
|
296
373
|
subcommands: [
|
|
374
|
+
...proxyLifecycleSubcommands,
|
|
375
|
+
configSub,
|
|
297
376
|
sponsorEnableSub, sponsorDisableSub, sponsorStatusSub, sponsorClearSub,
|
|
298
377
|
powerSaverEnableSub, powerSaverDisableSub, powerSaverStatusSub, powerSaverClearSub,
|
|
299
378
|
trainingShareEnableSub, trainingShareDisableSub, trainingShareStatusSub,
|
|
300
379
|
],
|
|
301
380
|
examples: [
|
|
381
|
+
{ command: 'ruflo proxy install --yes', description: 'Install the signed Meta-Proxy v0.4.0 binary' },
|
|
382
|
+
{ command: 'ruflo proxy start', description: 'Start meta-proxy in the foreground' },
|
|
383
|
+
{ command: 'ruflo proxy status', description: 'Show install + process status' },
|
|
384
|
+
{ command: 'ruflo proxy config --cloud --yes', description: 'Enable cloud routing (ADR-304)' },
|
|
385
|
+
{ command: 'ruflo proxy config --local-only', description: 'Revert to local-only routing' },
|
|
302
386
|
{ command: 'ruflo proxy sponsor-status', description: 'Show current sponsored-mode state' },
|
|
303
387
|
{ command: 'ruflo proxy sponsor-enable --yes', description: 'Opt into sponsored downtime capacity' },
|
|
304
388
|
{ command: 'ruflo proxy power-saver-enable --yes', description: 'Opt into power saver mode' },
|
|
305
389
|
{ command: 'ruflo proxy training-share-enable --yes', description: 'Opt into training-data sharing (ADR-315)' },
|
|
306
390
|
],
|
|
307
|
-
action:
|
|
391
|
+
action: async () => {
|
|
392
|
+
const status = getProxyStatus();
|
|
393
|
+
output.writeln('Meta Proxy');
|
|
394
|
+
output.writeln(` Installation: ${status.installed ? 'ready' : 'not installed'}`);
|
|
395
|
+
output.writeln(` Process: ${status.running ? `running (pid ${status.pid})` : 'not running'}`);
|
|
396
|
+
if (status.stalePidFile)
|
|
397
|
+
output.writeln(' (a stale PID file was found and will be cleared on next start)');
|
|
398
|
+
printProxyConsoleGuidance(status);
|
|
399
|
+
return { success: true, data: status };
|
|
400
|
+
},
|
|
308
401
|
};
|
|
309
402
|
export default proxyCommand;
|
|
310
403
|
//# sourceMappingURL=proxy.js.map
|
|
@@ -5,16 +5,23 @@
|
|
|
5
5
|
*
|
|
6
6
|
* Design discipline:
|
|
7
7
|
* 1. **Never blocks the render.** The statusline reads only the local
|
|
8
|
-
* cache. This module refreshes the cache in the background;
|
|
9
|
-
*
|
|
10
|
-
*
|
|
8
|
+
* cache. This module refreshes the cache in the background; per
|
|
9
|
+
* ADR-311 "zero local promo content" there is NO in-code fallback
|
|
10
|
+
* pool — a genuinely fresh install (empty cache) shows nothing on
|
|
11
|
+
* the promo/disclosure row until the first background refresh lands
|
|
12
|
+
* (see hook-handler.cjs's SessionStart-triggered detached
|
|
13
|
+
* `hooks refresh-funnel`, usually within seconds of CLI startup, but
|
|
14
|
+
* contingent on network reachability to the messages endpoint). This
|
|
15
|
+
* comment previously (incorrectly) described an in-code fallback
|
|
16
|
+
* pool; there has never been one since ADR-311 — see disclosure.ts's
|
|
17
|
+
* own "fail-closed" doc comment for the authoritative statement.
|
|
11
18
|
* 2. **Content pipeline stays authoritative.** Every message the server
|
|
12
19
|
* returns is validated by `isValidMessage()` from `messages.ts` —
|
|
13
20
|
* same schema, same host allowlist, same control-char strip, same
|
|
14
21
|
* 80-column cap. A tampered or accidentally-broken remote feed can
|
|
15
22
|
* pollute nothing.
|
|
16
23
|
* 3. **Fail silent.** Any network/parse/validation failure leaves the
|
|
17
|
-
* previously-cached (
|
|
24
|
+
* previously-cached pool intact (empty, if nothing has landed yet).
|
|
18
25
|
* 4. **Bounded cache size.** ≤ 128 KiB and ≤ 200 messages — matches
|
|
19
26
|
* ADR-309's bounded-local-queue discipline.
|
|
20
27
|
* 5. **Kill switch.** `RUFLO_FUNNEL_MESSAGES=0` (or `RUFLO_FUNNEL=0`)
|
|
@@ -5,16 +5,23 @@
|
|
|
5
5
|
*
|
|
6
6
|
* Design discipline:
|
|
7
7
|
* 1. **Never blocks the render.** The statusline reads only the local
|
|
8
|
-
* cache. This module refreshes the cache in the background;
|
|
9
|
-
*
|
|
10
|
-
*
|
|
8
|
+
* cache. This module refreshes the cache in the background; per
|
|
9
|
+
* ADR-311 "zero local promo content" there is NO in-code fallback
|
|
10
|
+
* pool — a genuinely fresh install (empty cache) shows nothing on
|
|
11
|
+
* the promo/disclosure row until the first background refresh lands
|
|
12
|
+
* (see hook-handler.cjs's SessionStart-triggered detached
|
|
13
|
+
* `hooks refresh-funnel`, usually within seconds of CLI startup, but
|
|
14
|
+
* contingent on network reachability to the messages endpoint). This
|
|
15
|
+
* comment previously (incorrectly) described an in-code fallback
|
|
16
|
+
* pool; there has never been one since ADR-311 — see disclosure.ts's
|
|
17
|
+
* own "fail-closed" doc comment for the authoritative statement.
|
|
11
18
|
* 2. **Content pipeline stays authoritative.** Every message the server
|
|
12
19
|
* returns is validated by `isValidMessage()` from `messages.ts` —
|
|
13
20
|
* same schema, same host allowlist, same control-char strip, same
|
|
14
21
|
* 80-column cap. A tampered or accidentally-broken remote feed can
|
|
15
22
|
* pollute nothing.
|
|
16
23
|
* 3. **Fail silent.** Any network/parse/validation failure leaves the
|
|
17
|
-
* previously-cached (
|
|
24
|
+
* previously-cached pool intact (empty, if nothing has landed yet).
|
|
18
25
|
* 4. **Bounded cache size.** ≤ 128 KiB and ≤ 200 messages — matches
|
|
19
26
|
* ADR-309's bounded-local-queue discipline.
|
|
20
27
|
* 5. **Kill switch.** `RUFLO_FUNNEL_MESSAGES=0` (or `RUFLO_FUNNEL=0`)
|
|
@@ -389,7 +389,7 @@ export const memoryTools = [
|
|
|
389
389
|
type: 'object',
|
|
390
390
|
properties: {
|
|
391
391
|
query: { type: 'string', description: 'Search query (semantic similarity)' },
|
|
392
|
-
namespace: { type: 'string', description: 'Namespace to search (default:
|
|
392
|
+
namespace: { type: 'string', description: 'Namespace to search (default: all namespaces — omit to search across every namespace)' },
|
|
393
393
|
limit: { type: 'number', description: 'Maximum results (default: 10)' },
|
|
394
394
|
threshold: { type: 'number', description: 'Minimum similarity threshold 0-1 (default: 0.3)' },
|
|
395
395
|
smart: { type: 'boolean', description: 'Enable SmartRetrieval pipeline — query expansion, RRF fusion, recency boost, MMR diversity (default: false)' },
|
|
@@ -400,10 +400,18 @@ export const memoryTools = [
|
|
|
400
400
|
await ensureInitialized();
|
|
401
401
|
const { searchEntries } = await getMemoryFunctions();
|
|
402
402
|
const query = input.query;
|
|
403
|
-
|
|
403
|
+
// #2646 (3rd occurrence of #1123/#1131 shape): do NOT coerce an omitted
|
|
404
|
+
// namespace to the literal string 'default' here. Both searchEntries()
|
|
405
|
+
// and bridgeSearchEntries() already resolve an omitted/undefined
|
|
406
|
+
// namespace to 'all' (fan out across every namespace) — but 'default'
|
|
407
|
+
// is truthy, so passing it defeats that fallback and silently scopes
|
|
408
|
+
// the search to a namespace that is usually empty. Leave namespace
|
|
409
|
+
// undefined when not provided so the underlying `|| 'all'` fallback
|
|
410
|
+
// in the search layer actually fires.
|
|
411
|
+
const namespace = input.namespace;
|
|
404
412
|
const limit = input.limit ?? 10;
|
|
405
413
|
const threshold = input.threshold ?? 0.3;
|
|
406
|
-
validateMemoryInput(undefined, undefined, query);
|
|
414
|
+
validateMemoryInput(undefined, undefined, query, namespace);
|
|
407
415
|
const startTime = performance.now();
|
|
408
416
|
try {
|
|
409
417
|
// #1846: feature-detect smartSearch on the resolved memory package.
|
|
@@ -90,6 +90,15 @@ export declare function countGraphEdges(dbPath?: string): Promise<number>;
|
|
|
90
90
|
* #2431 fix: also explicitly closes the prior handle so file locks
|
|
91
91
|
* release immediately — better-sqlite3 holds an OS-level file handle
|
|
92
92
|
* which the prior sql.js implementation did not.
|
|
93
|
+
*
|
|
94
|
+
* #2736-followup fix: `close()` alone only checkpoints the WAL back into
|
|
95
|
+
* the main file as a best-effort PASSIVE checkpoint when SQLite considers
|
|
96
|
+
* this the last connection — which is not guaranteed to fully flush under
|
|
97
|
+
* contention, and was observed leaving writes invisible to a same-process
|
|
98
|
+
* sql.js reader that re-reads the raw file immediately after close() on
|
|
99
|
+
* Linux CI runners (not reproduced on Windows). Force a blocking TRUNCATE
|
|
100
|
+
* checkpoint before close so cross-engine readers always see committed
|
|
101
|
+
* writes deterministically, regardless of platform/timing.
|
|
93
102
|
*/
|
|
94
103
|
export declare function _resetBridgeDb(): void;
|
|
95
104
|
//# sourceMappingURL=graph-edge-writer.d.ts.map
|
|
@@ -203,9 +203,22 @@ export async function countGraphEdges(dbPath) {
|
|
|
203
203
|
* #2431 fix: also explicitly closes the prior handle so file locks
|
|
204
204
|
* release immediately — better-sqlite3 holds an OS-level file handle
|
|
205
205
|
* which the prior sql.js implementation did not.
|
|
206
|
+
*
|
|
207
|
+
* #2736-followup fix: `close()` alone only checkpoints the WAL back into
|
|
208
|
+
* the main file as a best-effort PASSIVE checkpoint when SQLite considers
|
|
209
|
+
* this the last connection — which is not guaranteed to fully flush under
|
|
210
|
+
* contention, and was observed leaving writes invisible to a same-process
|
|
211
|
+
* sql.js reader that re-reads the raw file immediately after close() on
|
|
212
|
+
* Linux CI runners (not reproduced on Windows). Force a blocking TRUNCATE
|
|
213
|
+
* checkpoint before close so cross-engine readers always see committed
|
|
214
|
+
* writes deterministically, regardless of platform/timing.
|
|
206
215
|
*/
|
|
207
216
|
export function _resetBridgeDb() {
|
|
208
217
|
if (_db) {
|
|
218
|
+
try {
|
|
219
|
+
_db.pragma('wal_checkpoint(TRUNCATE)');
|
|
220
|
+
}
|
|
221
|
+
catch { /* best-effort */ }
|
|
209
222
|
try {
|
|
210
223
|
_db.close();
|
|
211
224
|
}
|
|
@@ -23,6 +23,28 @@ import { createRequire } from 'node:module';
|
|
|
23
23
|
let registryPromise = null;
|
|
24
24
|
let registryInstance = null;
|
|
25
25
|
let bridgeAvailable = null;
|
|
26
|
+
// #2735 — the native-bridge -> sql.js whole-image fallback used to be
|
|
27
|
+
// completely silent: a registry init failure cached `bridgeAvailable =
|
|
28
|
+
// false` for the rest of the process, and a per-operation bridge exception
|
|
29
|
+
// swallowed itself with a bare `catch { return null; }` — in both cases the
|
|
30
|
+
// caller fell back to memory-initializer.ts's unsafe whole-image sql.js
|
|
31
|
+
// path with zero diagnostic trail. When that path corrupted a database, the
|
|
32
|
+
// demotion that caused it was structurally unknowable after the fact. Log
|
|
33
|
+
// once per distinct reason (not once per call — a hot per-op catch could
|
|
34
|
+
// otherwise spam stderr on every single memory operation) so the first
|
|
35
|
+
// occurrence is attributable. Stderr only — never stdout, which callers may
|
|
36
|
+
// be parsing as JSON.
|
|
37
|
+
const loggedDemotions = new Set();
|
|
38
|
+
function logDemotionOnce(where, reason) {
|
|
39
|
+
const dedupeKey = `${where}:${reason}`;
|
|
40
|
+
if (loggedDemotions.has(dedupeKey))
|
|
41
|
+
return;
|
|
42
|
+
loggedDemotions.add(dedupeKey);
|
|
43
|
+
try {
|
|
44
|
+
process.stderr.write(`[memory-bridge] demoted to sql.js fallback in ${where}: ${reason}\n`);
|
|
45
|
+
}
|
|
46
|
+
catch { /* stderr unavailable — nothing more we can do */ }
|
|
47
|
+
}
|
|
26
48
|
/**
|
|
27
49
|
* Resolve database path with path traversal protection.
|
|
28
50
|
* Only allows paths within or below the project's working directory,
|
|
@@ -350,9 +372,10 @@ async function getRegistry(dbPath) {
|
|
|
350
372
|
bridgeAvailable = true;
|
|
351
373
|
return registry;
|
|
352
374
|
}
|
|
353
|
-
catch {
|
|
375
|
+
catch (err) {
|
|
354
376
|
bridgeAvailable = false;
|
|
355
377
|
registryPromise = null;
|
|
378
|
+
logDemotionOnce('getRegistry (process-wide, cached for the rest of this process)', err instanceof Error ? err.message : String(err));
|
|
356
379
|
return null;
|
|
357
380
|
}
|
|
358
381
|
})();
|
|
@@ -737,7 +760,8 @@ export async function bridgeStoreEntry(options) {
|
|
|
737
760
|
attested: true,
|
|
738
761
|
};
|
|
739
762
|
}
|
|
740
|
-
catch {
|
|
763
|
+
catch (err) {
|
|
764
|
+
logDemotionOnce('bridgeStoreEntry', err instanceof Error ? err.message : String(err));
|
|
741
765
|
return null;
|
|
742
766
|
}
|
|
743
767
|
}
|
|
@@ -1017,7 +1041,8 @@ export async function bridgeGetEntry(options) {
|
|
|
1017
1041
|
await cacheSet(registry, cacheKey, entry);
|
|
1018
1042
|
return { success: true, found: true, cacheHit: false, entry };
|
|
1019
1043
|
}
|
|
1020
|
-
catch {
|
|
1044
|
+
catch (err) {
|
|
1045
|
+
logDemotionOnce('bridgeGetEntry', err instanceof Error ? err.message : String(err));
|
|
1021
1046
|
return null;
|
|
1022
1047
|
}
|
|
1023
1048
|
}
|
|
@@ -1077,7 +1102,8 @@ export async function bridgeDeleteEntry(options) {
|
|
|
1077
1102
|
guarded: true,
|
|
1078
1103
|
};
|
|
1079
1104
|
}
|
|
1080
|
-
catch {
|
|
1105
|
+
catch (err) {
|
|
1106
|
+
logDemotionOnce('bridgeDeleteEntry', err instanceof Error ? err.message : String(err));
|
|
1081
1107
|
return null;
|
|
1082
1108
|
}
|
|
1083
1109
|
}
|
|
@@ -1128,7 +1154,8 @@ export async function bridgePurgeNamespace(options) {
|
|
|
1128
1154
|
guarded: true,
|
|
1129
1155
|
};
|
|
1130
1156
|
}
|
|
1131
|
-
catch {
|
|
1157
|
+
catch (err) {
|
|
1158
|
+
logDemotionOnce('bridgePurgeNamespace', err instanceof Error ? err.message : String(err));
|
|
1132
1159
|
return null;
|
|
1133
1160
|
}
|
|
1134
1161
|
}
|