moflo 4.12.4 → 4.12.5
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/guidance/shipped/moflo-agent-rules.md +1 -0
- package/.claude/guidance/shipped/moflo-claude-swarm-cohesion.md +1 -1
- package/.claude/guidance/shipped/moflo-cli-reference.md +4 -4
- package/.claude/guidance/shipped/moflo-core-guidance.md +4 -4
- package/.claude/guidance/shipped/moflo-guidance-rules.md +21 -0
- package/.claude/guidance/shipped/moflo-inline-fixes.md +98 -0
- package/.claude/guidance/shipped/moflo-root-cause-discipline.md +10 -10
- package/.claude/guidance/shipped/moflo-sdd.md +5 -5
- package/.claude/guidance/shipped/moflo-spell-engine.md +1 -1
- package/.claude/guidance/shipped/moflo-yaml-reference.md +8 -8
- package/.claude/helpers/gate.cjs +46 -7
- package/.claude/helpers/pr-create-command.cjs +440 -0
- package/.claude/skills/eldar/SKILL.md +1 -1
- package/.claude/skills/fl/SKILL.md +6 -6
- package/.claude/skills/fl/execution-modes.md +3 -3
- package/.claude/skills/fl/phases.md +8 -8
- package/.claude/skills/fl/sdd.md +6 -6
- package/.claude/skills/guidance/SKILL.md +3 -0
- package/.claude/skills/verify/SKILL.md +4 -4
- package/bin/gate.cjs +46 -7
- package/bin/hooks.mjs +4 -1
- package/bin/lib/retired-files.mjs +38 -6
- package/bin/lib/shipped-scripts.json +2 -1
- package/bin/lib/skill-categories.mjs +81 -17
- package/bin/pr-create-command.cjs +440 -0
- package/bin/session-start-launcher.mjs +11 -6
- package/dist/src/cli/aidefence/domain/entities/threat.js +2 -1
- package/dist/src/cli/commands/doctor-checks-deep.js +3 -2
- package/dist/src/cli/commands/doctor-checks-memory-access.js +3 -2
- package/dist/src/cli/commands/doctor-checks-swarm.js +3 -2
- package/dist/src/cli/commands/hooks.js +2 -1
- package/dist/src/cli/commands/mcp.js +3 -2
- package/dist/src/cli/commands/memory.js +2 -1
- package/dist/src/cli/commands/performance.js +2 -1
- package/dist/src/cli/commands/retire.js +1 -1
- package/dist/src/cli/commands/spell-schedule.js +2 -1
- package/dist/src/cli/commands/swarm.js +216 -104
- package/dist/src/cli/guidance/headless.js +5 -8
- package/dist/src/cli/guidance/retriever.js +27 -6
- package/dist/src/cli/guidance/ruvbot-integration.js +2 -1
- package/dist/src/cli/guidance/uncertainty.js +2 -1
- package/dist/src/cli/hooks/bridge/official-hooks-bridge.js +2 -1
- package/dist/src/cli/hooks/mcp/index.js +3 -2
- package/dist/src/cli/hooks/reasoningbank/index.js +2 -1
- package/dist/src/cli/hooks/registry/index.js +2 -1
- package/dist/src/cli/hooks/swarm/index.js +6 -5
- package/dist/src/cli/init/claudemd-generator.js +2 -1
- package/dist/src/cli/init/helpers-generator.js +1 -1
- package/dist/src/cli/init/moflo-yaml-template.js +4 -4
- package/dist/src/cli/mcp-client.js +5 -2
- package/dist/src/cli/mcp-tools/agent-tools.js +4 -1
- package/dist/src/cli/mcp-tools/hive-mind-tools.js +4 -3
- package/dist/src/cli/mcp-tools/hooks-tools.js +6 -5
- package/dist/src/cli/mcp-tools/json-store.js +3 -2
- package/dist/src/cli/mcp-tools/neural-tools.js +2 -1
- package/dist/src/cli/mcp-tools/performance-tools.js +2 -1
- package/dist/src/cli/mcp-tools/session-tools.js +4 -2
- package/dist/src/cli/mcp-tools/spell-tools.js +2 -1
- package/dist/src/cli/memory/bridge-core.js +8 -2
- package/dist/src/cli/memory/controllers/_shared.js +8 -2
- package/dist/src/cli/memory/controllers/nightly-learner.js +2 -1
- package/dist/src/cli/memory/domain/services/memory-domain-service.js +2 -1
- package/dist/src/cli/memory/entries-write.js +2 -1
- package/dist/src/cli/memory/intelligence.js +2 -1
- package/dist/src/cli/memory/persistent-sona.js +1 -3
- package/dist/src/cli/memory/types.js +2 -3
- package/dist/src/cli/memory/verify.js +3 -2
- package/dist/src/cli/movector/graph-analyzer.js +14 -3
- package/dist/src/cli/neural/pattern-learner.js +4 -2
- package/dist/src/cli/neural/reasoning-bank.js +2 -1
- package/dist/src/cli/neural/reasoningbank-adapter.js +2 -1
- package/dist/src/cli/neural/sona-manager.js +4 -3
- package/dist/src/cli/production/monitoring.js +2 -1
- package/dist/src/cli/services/daemon-service.js +57 -46
- package/dist/src/cli/services/daemon-spell-executor.js +3 -2
- package/dist/src/cli/services/headless-worker-executor.js +4 -2
- package/dist/src/cli/services/learning-service.js +2 -1
- package/dist/src/cli/services/worker-daemon.js +2 -1
- package/dist/src/cli/shared/hooks/example-usage.js +3 -2
- package/dist/src/cli/shared/hooks/safety/git-commit.js +11 -2
- package/dist/src/cli/shared/hooks/session-hooks.js +3 -2
- package/dist/src/cli/shared/hooks/task-hooks.js +2 -1
- package/dist/src/cli/shared/mcp/session-manager.js +2 -1
- package/dist/src/cli/shared/plugins/official/hive-mind-plugin.js +2 -1
- package/dist/src/cli/shared/plugins/official/maestro-plugin.js +3 -2
- package/dist/src/cli/shared/security/index.js +6 -2
- package/dist/src/cli/shared/utils/atomic-file-write.js +9 -1
- package/dist/src/cli/shared/utils/id.js +54 -0
- package/dist/src/cli/shared/utils/platform.js +36 -5
- package/dist/src/cli/spells/commands/destructive-pattern-checker.js +6 -1
- package/dist/src/cli/spells/core/dry-run-validator.js +2 -1
- package/dist/src/cli/spells/core/interpolation.js +8 -3
- package/dist/src/cli/spells/core/permission-resolver.js +2 -2
- package/dist/src/cli/spells/core/runner.js +2 -1
- package/dist/src/cli/spells/core/shell.js +7 -8
- package/dist/src/cli/spells/factory/runner-bridge.js +3 -2
- package/dist/src/cli/spells/factory/runner-factory.js +3 -2
- package/dist/src/cli/spells/scheduler/scheduler.js +2 -1
- package/dist/src/cli/swarm/consensus/gossip.js +2 -1
- package/dist/src/cli/swarm/federation-hub.js +5 -4
- package/dist/src/cli/swarm/queen-coordinator.js +5 -4
- package/dist/src/cli/swarm/shared/events.js +2 -1
- package/dist/src/cli/swarm/unified-coordinator.js +3 -2
- package/dist/src/cli/version.js +1 -1
- package/package.json +2 -2
- package/retired-files.json +0 -72
- package/dist/src/cli/mcp-tools/coordination-tools.js +0 -86
- package/dist/src/cli/shared/security/secure-random.js +0 -142
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
*
|
|
11
11
|
* @module moflo/production/monitoring
|
|
12
12
|
*/
|
|
13
|
+
import { generateId } from '../shared/utils/id.js';
|
|
13
14
|
// ============================================================================
|
|
14
15
|
// Default Configuration
|
|
15
16
|
// ============================================================================
|
|
@@ -308,7 +309,7 @@ export class MonitoringHooks {
|
|
|
308
309
|
Date.now() - a.timestamp < 60000);
|
|
309
310
|
if (!recentAlert) {
|
|
310
311
|
this.alerts.push({
|
|
311
|
-
id:
|
|
312
|
+
id: generateId('alert', { separator: '_' }),
|
|
312
313
|
level,
|
|
313
314
|
metric: name,
|
|
314
315
|
message: `${name} exceeded ${level} threshold: ${value} >= ${threshold}`,
|
|
@@ -10,11 +10,15 @@
|
|
|
10
10
|
*/
|
|
11
11
|
import * as fs from 'fs';
|
|
12
12
|
import { createHash } from 'crypto';
|
|
13
|
-
import { dirname, join, resolve } from 'path';
|
|
13
|
+
import { basename, dirname, join, resolve } from 'path';
|
|
14
14
|
import { homedir } from 'os';
|
|
15
15
|
import { execSync } from 'child_process';
|
|
16
16
|
import { locateMofloCliBin } from './moflo-require.js';
|
|
17
17
|
import { errorDetail } from '../shared/utils/error-detail.js';
|
|
18
|
+
/** Real process boundary. Kept thin so the stub and the default stay swappable. */
|
|
19
|
+
const execSyncRunner = (command, options) => {
|
|
20
|
+
execSync(command, options);
|
|
21
|
+
};
|
|
18
22
|
// ---------------------------------------------------------------------------
|
|
19
23
|
// Constants
|
|
20
24
|
// ---------------------------------------------------------------------------
|
|
@@ -27,37 +31,39 @@ const SCHTASKS_NAME = 'MoFloDaemon';
|
|
|
27
31
|
/**
|
|
28
32
|
* Check if the daemon service is registered for the current platform.
|
|
29
33
|
*/
|
|
30
|
-
export function isDaemonInstalled(projectRoot) {
|
|
34
|
+
export function isDaemonInstalled(projectRoot, options = {}) {
|
|
31
35
|
const resolvedRoot = resolve(projectRoot);
|
|
32
|
-
const platform = process.platform;
|
|
36
|
+
const platform = options.platform ?? process.platform;
|
|
37
|
+
const run = options.runCommand ?? execSyncRunner;
|
|
33
38
|
if (platform === 'darwin') {
|
|
34
|
-
return isDaemonInstalledMacOS(resolvedRoot);
|
|
39
|
+
return isDaemonInstalledMacOS(resolvedRoot, options);
|
|
35
40
|
}
|
|
36
41
|
else if (platform === 'linux') {
|
|
37
|
-
return isDaemonInstalledLinux(resolvedRoot);
|
|
42
|
+
return isDaemonInstalledLinux(resolvedRoot, options);
|
|
38
43
|
}
|
|
39
44
|
else if (platform === 'win32') {
|
|
40
|
-
return isDaemonInstalledWindows(resolvedRoot);
|
|
45
|
+
return isDaemonInstalledWindows(resolvedRoot, run);
|
|
41
46
|
}
|
|
42
47
|
return false;
|
|
43
48
|
}
|
|
44
49
|
/**
|
|
45
50
|
* Install the daemon as an OS-native login service.
|
|
46
51
|
*/
|
|
47
|
-
export function installDaemonService(projectRoot) {
|
|
52
|
+
export function installDaemonService(projectRoot, options = {}) {
|
|
48
53
|
const resolvedRoot = resolve(projectRoot);
|
|
49
54
|
validateProjectRoot(resolvedRoot);
|
|
50
|
-
const platform = process.platform;
|
|
55
|
+
const platform = options.platform ?? process.platform;
|
|
56
|
+
const run = options.runCommand ?? execSyncRunner;
|
|
51
57
|
const nodePath = process.execPath;
|
|
52
58
|
const cliPath = resolveCliPath();
|
|
53
59
|
if (platform === 'darwin') {
|
|
54
|
-
return installMacOS(resolvedRoot, nodePath, cliPath);
|
|
60
|
+
return installMacOS(resolvedRoot, nodePath, cliPath, options);
|
|
55
61
|
}
|
|
56
62
|
else if (platform === 'linux') {
|
|
57
|
-
return installLinux(resolvedRoot, nodePath, cliPath);
|
|
63
|
+
return installLinux(resolvedRoot, nodePath, cliPath, run, options);
|
|
58
64
|
}
|
|
59
65
|
else if (platform === 'win32') {
|
|
60
|
-
return installWindows(resolvedRoot, nodePath, cliPath);
|
|
66
|
+
return installWindows(resolvedRoot, nodePath, cliPath, run);
|
|
61
67
|
}
|
|
62
68
|
return {
|
|
63
69
|
success: false,
|
|
@@ -68,18 +74,19 @@ export function installDaemonService(projectRoot) {
|
|
|
68
74
|
/**
|
|
69
75
|
* Uninstall the daemon OS-native login service.
|
|
70
76
|
*/
|
|
71
|
-
export function uninstallDaemonService(projectRoot) {
|
|
77
|
+
export function uninstallDaemonService(projectRoot, options = {}) {
|
|
72
78
|
const resolvedRoot = resolve(projectRoot);
|
|
73
79
|
validateProjectRoot(resolvedRoot);
|
|
74
|
-
const platform = process.platform;
|
|
80
|
+
const platform = options.platform ?? process.platform;
|
|
81
|
+
const run = options.runCommand ?? execSyncRunner;
|
|
75
82
|
if (platform === 'darwin') {
|
|
76
|
-
return uninstallMacOS(resolvedRoot);
|
|
83
|
+
return uninstallMacOS(resolvedRoot, run, options);
|
|
77
84
|
}
|
|
78
85
|
else if (platform === 'linux') {
|
|
79
|
-
return uninstallLinux(resolvedRoot);
|
|
86
|
+
return uninstallLinux(resolvedRoot, run, options);
|
|
80
87
|
}
|
|
81
88
|
else if (platform === 'win32') {
|
|
82
|
-
return uninstallWindows(resolvedRoot);
|
|
89
|
+
return uninstallWindows(resolvedRoot, run);
|
|
83
90
|
}
|
|
84
91
|
return {
|
|
85
92
|
success: false,
|
|
@@ -89,9 +96,9 @@ export function uninstallDaemonService(projectRoot) {
|
|
|
89
96
|
// ---------------------------------------------------------------------------
|
|
90
97
|
// macOS — launchd
|
|
91
98
|
// ---------------------------------------------------------------------------
|
|
92
|
-
function plistPath(projectRoot) {
|
|
99
|
+
function plistPath(projectRoot, options = {}) {
|
|
93
100
|
const slug = projectRootSlug(projectRoot);
|
|
94
|
-
return join(homedir(), 'Library', 'LaunchAgents', `${PLIST_LABEL}.${slug}.plist`);
|
|
101
|
+
return join(options.homeDir ?? homedir(), 'Library', 'LaunchAgents', `${PLIST_LABEL}.${slug}.plist`);
|
|
95
102
|
}
|
|
96
103
|
function generatePlist(projectRoot, nodePath, cliPath) {
|
|
97
104
|
const slug = projectRootSlug(projectRoot);
|
|
@@ -128,8 +135,8 @@ function generatePlist(projectRoot, nodePath, cliPath) {
|
|
|
128
135
|
'',
|
|
129
136
|
].join('\n');
|
|
130
137
|
}
|
|
131
|
-
function installMacOS(projectRoot, nodePath, cliPath) {
|
|
132
|
-
const dest = plistPath(projectRoot);
|
|
138
|
+
function installMacOS(projectRoot, nodePath, cliPath, options) {
|
|
139
|
+
const dest = plistPath(projectRoot, options);
|
|
133
140
|
const dir = dirname(dest);
|
|
134
141
|
fs.mkdirSync(dir, { recursive: true });
|
|
135
142
|
const content = generatePlist(projectRoot, nodePath, cliPath);
|
|
@@ -140,28 +147,33 @@ function installMacOS(projectRoot, nodePath, cliPath) {
|
|
|
140
147
|
message: `Daemon service installed at ${dest}. It will start automatically on login.`,
|
|
141
148
|
};
|
|
142
149
|
}
|
|
143
|
-
function uninstallMacOS(projectRoot) {
|
|
144
|
-
const dest = plistPath(projectRoot);
|
|
150
|
+
function uninstallMacOS(projectRoot, run, options) {
|
|
151
|
+
const dest = plistPath(projectRoot, options);
|
|
145
152
|
if (!fs.existsSync(dest)) {
|
|
146
153
|
return { success: true, message: 'Daemon service is not installed.' };
|
|
147
154
|
}
|
|
148
155
|
// Unload before removing (ignore errors — may not be loaded)
|
|
149
156
|
try {
|
|
150
|
-
|
|
157
|
+
run(`launchctl unload "${dest}"`, { timeout: 5000, stdio: 'ignore' });
|
|
151
158
|
}
|
|
152
159
|
catch { /* not loaded — fine */ }
|
|
153
160
|
fs.unlinkSync(dest);
|
|
154
161
|
return { success: true, message: `Daemon service removed from ${dest}.` };
|
|
155
162
|
}
|
|
156
|
-
function isDaemonInstalledMacOS(projectRoot) {
|
|
157
|
-
return fs.existsSync(plistPath(projectRoot));
|
|
163
|
+
function isDaemonInstalledMacOS(projectRoot, options) {
|
|
164
|
+
return fs.existsSync(plistPath(projectRoot, options));
|
|
158
165
|
}
|
|
159
166
|
// ---------------------------------------------------------------------------
|
|
160
167
|
// Linux — systemd --user
|
|
161
168
|
// ---------------------------------------------------------------------------
|
|
162
|
-
function systemdUnitPath(projectRoot) {
|
|
169
|
+
function systemdUnitPath(projectRoot, options = {}) {
|
|
163
170
|
const slug = projectRootSlug(projectRoot);
|
|
164
|
-
|
|
171
|
+
// An injected homeDir is a sandbox boundary — XDG_CONFIG_HOME must not
|
|
172
|
+
// override it, or an ambient value would redirect the write back to the
|
|
173
|
+
// developer's real config tree.
|
|
174
|
+
const configDir = options.homeDir
|
|
175
|
+
? join(options.homeDir, '.config')
|
|
176
|
+
: process.env.XDG_CONFIG_HOME || join(homedir(), '.config');
|
|
165
177
|
return join(configDir, 'systemd', 'user', `${SYSTEMD_UNIT.replace('.service', '')}-${slug}.service`);
|
|
166
178
|
}
|
|
167
179
|
function generateSystemdUnit(projectRoot, nodePath, cliPath) {
|
|
@@ -183,17 +195,16 @@ function generateSystemdUnit(projectRoot, nodePath, cliPath) {
|
|
|
183
195
|
'',
|
|
184
196
|
].join('\n');
|
|
185
197
|
}
|
|
186
|
-
function installLinux(projectRoot, nodePath, cliPath) {
|
|
187
|
-
const dest = systemdUnitPath(projectRoot);
|
|
198
|
+
function installLinux(projectRoot, nodePath, cliPath, run, options) {
|
|
199
|
+
const dest = systemdUnitPath(projectRoot, options);
|
|
188
200
|
const dir = dirname(dest);
|
|
189
201
|
fs.mkdirSync(dir, { recursive: true });
|
|
190
202
|
const content = generateSystemdUnit(projectRoot, nodePath, cliPath);
|
|
191
203
|
fs.writeFileSync(dest, content, 'utf-8');
|
|
192
204
|
// Reload systemd and enable
|
|
193
205
|
try {
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
execSync(`systemctl --user enable ${unitName}`, { timeout: 10000, stdio: 'ignore' });
|
|
206
|
+
run('systemctl --user daemon-reload', { timeout: 10000, stdio: 'ignore' });
|
|
207
|
+
run(`systemctl --user enable ${basename(dest)}`, { timeout: 10000, stdio: 'ignore' });
|
|
197
208
|
}
|
|
198
209
|
catch {
|
|
199
210
|
// systemctl may not be available in all environments
|
|
@@ -204,28 +215,28 @@ function installLinux(projectRoot, nodePath, cliPath) {
|
|
|
204
215
|
message: `Daemon service installed at ${dest}. It will start automatically on login.`,
|
|
205
216
|
};
|
|
206
217
|
}
|
|
207
|
-
function uninstallLinux(projectRoot) {
|
|
208
|
-
const dest = systemdUnitPath(projectRoot);
|
|
218
|
+
function uninstallLinux(projectRoot, run, options) {
|
|
219
|
+
const dest = systemdUnitPath(projectRoot, options);
|
|
209
220
|
if (!fs.existsSync(dest)) {
|
|
210
221
|
return { success: true, message: 'Daemon service is not installed.' };
|
|
211
222
|
}
|
|
212
223
|
// Disable and stop before removing
|
|
213
224
|
try {
|
|
214
|
-
const unitName = dest
|
|
215
|
-
|
|
216
|
-
|
|
225
|
+
const unitName = basename(dest);
|
|
226
|
+
run(`systemctl --user disable ${unitName}`, { timeout: 10000, stdio: 'ignore' });
|
|
227
|
+
run(`systemctl --user stop ${unitName}`, { timeout: 10000, stdio: 'ignore' });
|
|
217
228
|
}
|
|
218
229
|
catch { /* may not be running */ }
|
|
219
230
|
fs.unlinkSync(dest);
|
|
220
231
|
// Reload systemd
|
|
221
232
|
try {
|
|
222
|
-
|
|
233
|
+
run('systemctl --user daemon-reload', { timeout: 10000, stdio: 'ignore' });
|
|
223
234
|
}
|
|
224
235
|
catch { /* ignore */ }
|
|
225
236
|
return { success: true, message: `Daemon service removed from ${dest}.` };
|
|
226
237
|
}
|
|
227
|
-
function isDaemonInstalledLinux(projectRoot) {
|
|
228
|
-
return fs.existsSync(systemdUnitPath(projectRoot));
|
|
238
|
+
function isDaemonInstalledLinux(projectRoot, options) {
|
|
239
|
+
return fs.existsSync(systemdUnitPath(projectRoot, options));
|
|
229
240
|
}
|
|
230
241
|
// ---------------------------------------------------------------------------
|
|
231
242
|
// Windows — Task Scheduler
|
|
@@ -234,12 +245,12 @@ function schtasksName(projectRoot) {
|
|
|
234
245
|
const slug = projectRootSlug(projectRoot);
|
|
235
246
|
return `${SCHTASKS_NAME}-${slug}`;
|
|
236
247
|
}
|
|
237
|
-
function installWindows(projectRoot, nodePath, cliPath) {
|
|
248
|
+
function installWindows(projectRoot, nodePath, cliPath, run) {
|
|
238
249
|
const taskName = schtasksName(projectRoot);
|
|
239
250
|
// Build schtasks command — ONLOGON trigger, user-level
|
|
240
251
|
// Use /F to force overwrite if already exists (idempotent)
|
|
241
252
|
try {
|
|
242
|
-
|
|
253
|
+
run(`schtasks /Create /TN "${taskName}" /TR "\\"${nodePath}\\" \\"${cliPath}\\" daemon start --foreground --quiet" /SC ONLOGON /F`, { timeout: 15000, windowsHide: true, cwd: projectRoot, stdio: 'ignore' });
|
|
243
254
|
}
|
|
244
255
|
catch (err) {
|
|
245
256
|
return {
|
|
@@ -254,10 +265,10 @@ function installWindows(projectRoot, nodePath, cliPath) {
|
|
|
254
265
|
message: `Daemon task "${taskName}" registered in Task Scheduler. It will start automatically on login.`,
|
|
255
266
|
};
|
|
256
267
|
}
|
|
257
|
-
function uninstallWindows(projectRoot) {
|
|
268
|
+
function uninstallWindows(projectRoot, run) {
|
|
258
269
|
const taskName = schtasksName(projectRoot);
|
|
259
270
|
try {
|
|
260
|
-
|
|
271
|
+
run(`schtasks /Delete /TN "${taskName}" /F`, { timeout: 15000, windowsHide: true, stdio: 'ignore' });
|
|
261
272
|
}
|
|
262
273
|
catch {
|
|
263
274
|
// schtasks /Delete /F returns non-zero when task doesn't exist
|
|
@@ -265,10 +276,10 @@ function uninstallWindows(projectRoot) {
|
|
|
265
276
|
}
|
|
266
277
|
return { success: true, message: `Daemon task "${taskName}" removed from Task Scheduler.` };
|
|
267
278
|
}
|
|
268
|
-
function isDaemonInstalledWindows(projectRoot) {
|
|
279
|
+
function isDaemonInstalledWindows(projectRoot, run) {
|
|
269
280
|
const taskName = schtasksName(projectRoot);
|
|
270
281
|
try {
|
|
271
|
-
|
|
282
|
+
run(`schtasks /Query /TN "${taskName}"`, {
|
|
272
283
|
timeout: 10000,
|
|
273
284
|
windowsHide: true,
|
|
274
285
|
stdio: 'ignore',
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import { errorDetail } from '../shared/utils/error-detail.js';
|
|
10
10
|
import { loadSpellEngine, } from './engine-loader.js';
|
|
11
|
+
import { generateId } from '../shared/utils/id.js';
|
|
11
12
|
export class DaemonSpellExecutor {
|
|
12
13
|
registry;
|
|
13
14
|
projectRoot;
|
|
@@ -39,11 +40,11 @@ export class DaemonSpellExecutor {
|
|
|
39
40
|
this.registry.invalidate();
|
|
40
41
|
const loaded = this.registry.resolve(spellName);
|
|
41
42
|
if (!loaded) {
|
|
42
|
-
return failedResult(`scheduled-${spellName}
|
|
43
|
+
return failedResult(generateId(`scheduled-${spellName}`), 'STEP_EXECUTION_FAILED', `Spell not found in grimoire: ${spellName}`);
|
|
43
44
|
}
|
|
44
45
|
const engine = await this.ensureEngine();
|
|
45
46
|
const definition = this.applyMofloLevel(loaded.definition, mofloLevel);
|
|
46
|
-
const spellId = `scheduled-${loaded.definition.name}
|
|
47
|
+
const spellId = generateId(`scheduled-${loaded.definition.name}`);
|
|
47
48
|
const onAbort = () => {
|
|
48
49
|
try {
|
|
49
50
|
engine.bridgeCancelSpell(spellId);
|
|
@@ -16,6 +16,8 @@ import { EventEmitter } from 'events';
|
|
|
16
16
|
import { existsSync, readFileSync, readdirSync, mkdirSync, writeFileSync } from 'fs';
|
|
17
17
|
import { join } from 'path';
|
|
18
18
|
import { errorDetail } from '../shared/utils/error-detail.js';
|
|
19
|
+
import { randomSuffix } from '../shared/utils/id.js';
|
|
20
|
+
import { generateId } from '../shared/utils/id.js';
|
|
19
21
|
// ============================================
|
|
20
22
|
// Constants
|
|
21
23
|
// ============================================
|
|
@@ -560,7 +562,7 @@ export class HeadlessWorkerExecutor extends EventEmitter {
|
|
|
560
562
|
const baseConfig = HEADLESS_WORKER_CONFIGS[workerType];
|
|
561
563
|
const headless = { ...baseConfig.headless, ...configOverrides };
|
|
562
564
|
const startTime = Date.now();
|
|
563
|
-
const executionId = `${workerType}_${startTime}_${
|
|
565
|
+
const executionId = `${workerType}_${startTime}_${randomSuffix()}`;
|
|
564
566
|
this.emit('start', { executionId, workerType, config: headless });
|
|
565
567
|
try {
|
|
566
568
|
// Build context from file patterns
|
|
@@ -1023,7 +1025,7 @@ ${ioInstructions}`;
|
|
|
1023
1025
|
sandboxMode: 'strict',
|
|
1024
1026
|
workerType,
|
|
1025
1027
|
timestamp: new Date(),
|
|
1026
|
-
executionId:
|
|
1028
|
+
executionId: generateId('error', { separator: '_' }),
|
|
1027
1029
|
error,
|
|
1028
1030
|
};
|
|
1029
1031
|
}
|
|
@@ -17,6 +17,7 @@ import { dirname } from 'path';
|
|
|
17
17
|
import { memoryDbPath } from './moflo-paths.js';
|
|
18
18
|
import { openDaemonDatabase } from '../memory/daemon-backend.js';
|
|
19
19
|
import { createNeuralEmbeddingProvider, } from './neural-embedding-provider.js';
|
|
20
|
+
import { generateId } from '../shared/utils/id.js';
|
|
20
21
|
// ============================================================================
|
|
21
22
|
// Configuration
|
|
22
23
|
// ============================================================================
|
|
@@ -319,7 +320,7 @@ export class LearningService {
|
|
|
319
320
|
this.dirty = true;
|
|
320
321
|
return { id: existingId, action: 'updated', similarity: ltResults[0].similarity };
|
|
321
322
|
}
|
|
322
|
-
const id =
|
|
323
|
+
const id = generateId('pat', { separator: '_' });
|
|
323
324
|
const embeddingJson = JSON.stringify(Array.from(embedding));
|
|
324
325
|
dbRun(this.db, `
|
|
325
326
|
INSERT INTO learned_patterns (id, pattern, domain, quality, uses, tier, embedding, created_at)
|
|
@@ -36,6 +36,7 @@ import { CircuitBreaker } from '../production/circuit-breaker.js';
|
|
|
36
36
|
import { errorDetail } from '../shared/utils/error-detail.js';
|
|
37
37
|
import { readLoadAverage } from '../shared/utils/load-average.js';
|
|
38
38
|
import { readMofloEnv } from './env-compat.js';
|
|
39
|
+
import { generateId } from '../shared/utils/id.js';
|
|
39
40
|
/**
|
|
40
41
|
* Runtime allow-list of known {@link WorkerType} values. Used by
|
|
41
42
|
* `initializeWorkerStates` to silently drop entries from a stale
|
|
@@ -787,7 +788,7 @@ export class WorkerDaemon extends EventEmitter {
|
|
|
787
788
|
*/
|
|
788
789
|
async executeWorker(workerConfig) {
|
|
789
790
|
const state = this.workers.get(workerConfig.type);
|
|
790
|
-
const workerId =
|
|
791
|
+
const workerId = generateId(workerConfig.type, { separator: '_' });
|
|
791
792
|
const startTime = Date.now();
|
|
792
793
|
// The controller is parked on shared state so setWorkerEnabled(false) can
|
|
793
794
|
// interrupt this in-flight run; runWithTimeout also aborts it on timeout
|
|
@@ -119,7 +119,7 @@ export function setupPerformanceHooks() {
|
|
|
119
119
|
if (!toolName) {
|
|
120
120
|
return { success: true };
|
|
121
121
|
}
|
|
122
|
-
const metricId = `tool-${toolName}
|
|
122
|
+
const metricId = generateId(`tool-${toolName}`);
|
|
123
123
|
performanceMetrics.set(metricId, { start: Date.now() });
|
|
124
124
|
return {
|
|
125
125
|
success: true,
|
|
@@ -216,7 +216,7 @@ export function setupSessionHooks() {
|
|
|
216
216
|
let activeSession = null;
|
|
217
217
|
// Initialize session
|
|
218
218
|
registry.register(HookEvent.SessionStart, async (context) => {
|
|
219
|
-
const sessionId = context.session?.id ||
|
|
219
|
+
const sessionId = context.session?.id || generateId('session');
|
|
220
220
|
activeSession = {
|
|
221
221
|
id: sessionId,
|
|
222
222
|
startTime: new Date(),
|
|
@@ -346,6 +346,7 @@ export async function runDemo() {
|
|
|
346
346
|
}
|
|
347
347
|
// Run demo if executed directly
|
|
348
348
|
import { pathToFileURL as _pfu_eu } from 'url';
|
|
349
|
+
import { generateId } from '../utils/id.js';
|
|
349
350
|
if (import.meta.url === _pfu_eu(process.argv[1]).href) {
|
|
350
351
|
runDemo().catch(console.error);
|
|
351
352
|
}
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
* @module v3/shared/hooks/safety/git-commit
|
|
9
9
|
*/
|
|
10
10
|
import { HookEvent, HookPriority, } from '../types.js';
|
|
11
|
+
import { escapeShellArg } from '../../utils/platform.js';
|
|
11
12
|
const COMMIT_TYPE_PATTERNS = [
|
|
12
13
|
{
|
|
13
14
|
keywords: ['add', 'implement', 'create', 'introduce', 'new'],
|
|
@@ -137,9 +138,17 @@ export class GitCommitHook {
|
|
|
137
138
|
const message = messageMatch[1];
|
|
138
139
|
const branchName = context.metadata?.branchName;
|
|
139
140
|
const result = await this.processCommitMessage(message, branchName);
|
|
140
|
-
// Modify the command with the new message
|
|
141
|
+
// Modify the command with the new message.
|
|
142
|
+
//
|
|
143
|
+
// `escapeShellArg` rather than a hand-rolled quote escape: this rewrites a
|
|
144
|
+
// command that then runs in the consumer's shell, and the Windows branch of
|
|
145
|
+
// the old escape left a trailing backslash escaping its own closing quote
|
|
146
|
+
// (#1419). The replacement is a FUNCTION so that a `$&` or `$1` occurring in
|
|
147
|
+
// the commit message is inserted literally instead of being expanded by
|
|
148
|
+
// String.replace's substitution syntax.
|
|
141
149
|
if (result.success && result.modifiedMessage !== message) {
|
|
142
|
-
const
|
|
150
|
+
const replacement = `-m ${escapeShellArg(result.modifiedMessage ?? '')}`;
|
|
151
|
+
const modifiedCommand = command.replace(/-m\s+["'][^"']+["']/, () => replacement);
|
|
143
152
|
return {
|
|
144
153
|
...result,
|
|
145
154
|
data: {
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
* @module v3/shared/hooks/session-hooks
|
|
8
8
|
*/
|
|
9
9
|
import { HookEvent, HookPriority, } from './types.js';
|
|
10
|
+
import { generateId } from '../utils/id.js';
|
|
10
11
|
/**
|
|
11
12
|
* In-memory session storage (for testing and fallback)
|
|
12
13
|
*/
|
|
@@ -84,7 +85,7 @@ export class SessionHooksManager {
|
|
|
84
85
|
* Handle session start
|
|
85
86
|
*/
|
|
86
87
|
async handleSessionStart(context) {
|
|
87
|
-
this.currentSessionId = context.session?.id ||
|
|
88
|
+
this.currentSessionId = context.session?.id || generateId('session');
|
|
88
89
|
this.sessionStartTime = new Date();
|
|
89
90
|
this.resetActivity();
|
|
90
91
|
return {
|
|
@@ -189,7 +190,7 @@ export class SessionHooksManager {
|
|
|
189
190
|
warnings.push(`${incompleteTasks.length} tasks were incomplete when session ended`);
|
|
190
191
|
}
|
|
191
192
|
// Update current session tracking
|
|
192
|
-
this.currentSessionId =
|
|
193
|
+
this.currentSessionId = `${generateId('session')}-restored`;
|
|
193
194
|
this.sessionStartTime = new Date();
|
|
194
195
|
this.resetActivity();
|
|
195
196
|
return {
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
* @module v3/shared/hooks/task-hooks
|
|
8
8
|
*/
|
|
9
9
|
import { HookEvent, HookPriority, } from './types.js';
|
|
10
|
+
import { generateId } from '../utils/id.js';
|
|
10
11
|
/**
|
|
11
12
|
* Task Hooks Manager
|
|
12
13
|
*
|
|
@@ -107,7 +108,7 @@ export class TaskHooksManager {
|
|
|
107
108
|
outcome,
|
|
108
109
|
learningUpdates,
|
|
109
110
|
patternId: learningUpdates.newPatterns > 0 ? `pattern-${task.id}` : undefined,
|
|
110
|
-
trajectoryId: `trajectory-${task.id}
|
|
111
|
+
trajectoryId: generateId(`trajectory-${task.id}`),
|
|
111
112
|
};
|
|
112
113
|
}
|
|
113
114
|
/**
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
* - Session cleanup: <10ms
|
|
14
14
|
*/
|
|
15
15
|
import { EventEmitter } from 'events';
|
|
16
|
+
import { randomBytes } from 'node:crypto';
|
|
16
17
|
/**
|
|
17
18
|
* Default session configuration
|
|
18
19
|
*/
|
|
@@ -283,7 +284,7 @@ export class SessionManager extends EventEmitter {
|
|
|
283
284
|
* Generate a unique session ID
|
|
284
285
|
*/
|
|
285
286
|
generateSessionId() {
|
|
286
|
-
return `session-${++this.sessionCounter}-${Date.now()}-${
|
|
287
|
+
return `session-${++this.sessionCounter}-${Date.now()}-${randomBytes(6).toString('hex')}`;
|
|
287
288
|
}
|
|
288
289
|
/**
|
|
289
290
|
* Clear all sessions
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
* @module v3/shared/plugins/official/hive-mind
|
|
8
8
|
*/
|
|
9
9
|
import { HookEvent, HookPriority } from '../../hooks/index.js';
|
|
10
|
+
import { generateId } from '../../utils/id.js';
|
|
10
11
|
/**
|
|
11
12
|
* HiveMind Plugin Implementation
|
|
12
13
|
*/
|
|
@@ -74,7 +75,7 @@ export class HiveMindPlugin {
|
|
|
74
75
|
*/
|
|
75
76
|
async requestDecision(question, options) {
|
|
76
77
|
const decision = {
|
|
77
|
-
id:
|
|
78
|
+
id: generateId('decision'),
|
|
78
79
|
question,
|
|
79
80
|
votes: new Map(),
|
|
80
81
|
consensusConfidence: 0,
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import { HookEvent, HookPriority } from '../../hooks/index.js';
|
|
10
10
|
import { errorDetail } from '../../utils/error-detail.js';
|
|
11
|
+
import { generateId } from '../../utils/id.js';
|
|
11
12
|
/**
|
|
12
13
|
* Maestro Plugin Implementation
|
|
13
14
|
*/
|
|
@@ -73,7 +74,7 @@ export class MaestroPlugin {
|
|
|
73
74
|
*/
|
|
74
75
|
createSpell(name, description, steps) {
|
|
75
76
|
const spell = {
|
|
76
|
-
id:
|
|
77
|
+
id: generateId('spell'),
|
|
77
78
|
name,
|
|
78
79
|
description,
|
|
79
80
|
steps: steps.map((step, index) => ({
|
|
@@ -340,7 +341,7 @@ export class MaestroPlugin {
|
|
|
340
341
|
}
|
|
341
342
|
}
|
|
342
343
|
checkpointSpell(spell) {
|
|
343
|
-
spell.checkpoints.set(
|
|
344
|
+
spell.checkpoints.set(generateId('checkpoint'), {
|
|
344
345
|
progress: spell.progress,
|
|
345
346
|
currentStep: spell.currentStep,
|
|
346
347
|
stepStatuses: spell.steps.map((s) => ({ id: s.id, status: s.status })),
|
|
@@ -5,8 +5,12 @@
|
|
|
5
5
|
*
|
|
6
6
|
* @module v3/shared/security
|
|
7
7
|
*/
|
|
8
|
-
//
|
|
9
|
-
|
|
8
|
+
// Unique-ID minting lives in `shared/utils/id.ts`, not here. `secure-random.ts`
|
|
9
|
+
// re-exported fifteen ID helpers from this barrel and not one of them ever had
|
|
10
|
+
// an importer, while ~38 call sites went on minting IDs with `Math.random()`.
|
|
11
|
+
// It was deleted in #1423 rather than adopted: its per-domain exports
|
|
12
|
+
// (`generateAgentId`, `generateTaskId`, …) were the sprawl, and one of them
|
|
13
|
+
// shadowed the live `generateMemoryId` in `memory/types.ts`.
|
|
10
14
|
// Input validation
|
|
11
15
|
export { validateInput, sanitizeString, validatePath, validateCommand, validateTags, isValidIdentifier, escapeForSql, } from './input-validation.js';
|
|
12
16
|
//# sourceMappingURL=index.js.map
|
|
@@ -42,11 +42,19 @@
|
|
|
42
42
|
* @module moflo/cli/shared/utils/atomic-file-write
|
|
43
43
|
*/
|
|
44
44
|
import * as realFs from 'node:fs';
|
|
45
|
+
// `randomBytes` inline rather than `randomSuffix` from './id.js' (#1423): the
|
|
46
|
+
// session-start launcher dynamically imports this module out of a consumer's
|
|
47
|
+
// `node_modules/moflo/dist/`, so every import edge added here widens a closure
|
|
48
|
+
// that has to resolve before moflo.yaml can be written. `node:crypto` is a
|
|
49
|
+
// builtin and adds no file to that closure; a sibling module would.
|
|
50
|
+
// `tests/bin/launcher-visibility.test.ts` stages that closure by hand and fails
|
|
51
|
+
// the moment it grows.
|
|
52
|
+
import { randomBytes } from 'node:crypto';
|
|
45
53
|
const IS_WIN32 = process.platform === 'win32';
|
|
46
54
|
const VERIFY_DEADLINE_MS = 250;
|
|
47
55
|
const VERIFY_STEP_MS = 10;
|
|
48
56
|
export function atomicWriteFileSync(targetPath, data, fs = realFs) {
|
|
49
|
-
const tmpPath = `${targetPath}.tmp.${process.pid}.${
|
|
57
|
+
const tmpPath = `${targetPath}.tmp.${process.pid}.${randomBytes(4).toString('hex')}`;
|
|
50
58
|
try {
|
|
51
59
|
fs.writeFileSync(tmpPath, data);
|
|
52
60
|
fsyncFile(tmpPath, fs);
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Canonical unique-ID minting.
|
|
3
|
+
*
|
|
4
|
+
* ## Why this module exists
|
|
5
|
+
*
|
|
6
|
+
* Story #801 fixed `agent_spawn` after base-36 slicing of `Math.random()` was
|
|
7
|
+
* *observably* colliding under burst spawns — the comment recording that still
|
|
8
|
+
* sits in `mcp-tools/agent-tools.ts`. The fix was applied there and at a handful
|
|
9
|
+
* of neighbours and stopped; ~38 other sites kept minting IDs the same way, and
|
|
10
|
+
* four separate `generateId` helpers accumulated around them (three live and
|
|
11
|
+
* identically named, in `memory/controllers/_shared.ts`, `memory/bridge-core.ts`
|
|
12
|
+
* and `memory/persistent-sona.ts`, plus an unused `shared/security/secure-random.ts`).
|
|
13
|
+
* This module is the one they all now route through (#1423).
|
|
14
|
+
*
|
|
15
|
+
* A base-36 slice of `Math.random()` carries roughly 31 bits of entropy before
|
|
16
|
+
* slicing and far less after — two entities minted in the same
|
|
17
|
+
* millisecond can share an identity, which for a swarm or agent ID means two
|
|
18
|
+
* distinct actors silently occupying one slot.
|
|
19
|
+
*
|
|
20
|
+
* ## Severity
|
|
21
|
+
*
|
|
22
|
+
* This is a **correctness** fix, not a security one. These IDs are not secrets,
|
|
23
|
+
* capabilities, or session tokens; nothing here depends on unpredictability. The
|
|
24
|
+
* property being restored is uniqueness. (`Math.random()` remains entirely
|
|
25
|
+
* correct for neural weight init, epsilon-greedy exploration, HNSW level
|
|
26
|
+
* selection, and jitter — none of those are touched.)
|
|
27
|
+
*/
|
|
28
|
+
import { randomBytes } from 'node:crypto';
|
|
29
|
+
/** Default CSPRNG entropy per ID: 6 bytes = 48 bits = 12 hex characters. */
|
|
30
|
+
const DEFAULT_ENTROPY_BYTES = 6;
|
|
31
|
+
/**
|
|
32
|
+
* Mint a unique ID of the form `<prefix><sep><timestamp><sep><random-hex>`.
|
|
33
|
+
*
|
|
34
|
+
* The timestamp segment is retained from the patterns this replaces: it keeps
|
|
35
|
+
* IDs roughly sortable by creation time and is useful when reading them in logs.
|
|
36
|
+
* Uniqueness rests on the random segment alone.
|
|
37
|
+
*/
|
|
38
|
+
export function generateId(prefix, options = {}) {
|
|
39
|
+
const { separator = '-', bytes = DEFAULT_ENTROPY_BYTES, base36Time = false, } = options;
|
|
40
|
+
const timestamp = base36Time ? Date.now().toString(36) : String(Date.now());
|
|
41
|
+
return `${prefix}${separator}${timestamp}${separator}${randomBytes(bytes).toString('hex')}`;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* A bare random segment, for the ID shapes that are not
|
|
45
|
+
* `prefix + timestamp + random` — a temp-file suffix, a probe stamp, an ID whose
|
|
46
|
+
* middle segment is a loop index rather than a clock reading.
|
|
47
|
+
*
|
|
48
|
+
* Prefer {@link generateId} when the shape does fit; this exists so the odd ones
|
|
49
|
+
* out do not have to keep reaching for `Math.random()`.
|
|
50
|
+
*/
|
|
51
|
+
export function randomSuffix(bytes = DEFAULT_ENTROPY_BYTES) {
|
|
52
|
+
return randomBytes(bytes).toString('hex');
|
|
53
|
+
}
|
|
54
|
+
//# sourceMappingURL=id.js.map
|