moflo 4.8.64 → 4.8.65
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-core-guidance.md +11 -0
- package/.claude/guidance/shipped/moflo-spell-sandboxing.md +23 -0
- package/bin/lib/yaml-upgrader.mjs +80 -0
- package/bin/session-start-launcher.mjs +19 -0
- package/package.json +2 -2
- package/src/modules/cli/dist/src/init/moflo-init.js +10 -0
- package/src/modules/cli/dist/src/version.js +1 -1
- package/src/modules/cli/package.json +1 -1
- package/src/modules/spells/dist/core/platform-sandbox.js +3 -2
|
@@ -584,8 +584,16 @@ status_line:
|
|
|
584
584
|
show_adrs: true # ADR compliance (dashboard only)
|
|
585
585
|
show_agentdb: true # AgentDB vectors/size (dashboard only)
|
|
586
586
|
show_tests: true # Test file count (dashboard only)
|
|
587
|
+
|
|
588
|
+
# Spell step sandboxing (OS-level process isolation for bash steps)
|
|
589
|
+
# Platform support: macOS (sandbox-exec), Linux/WSL (bwrap). Windows has no OS sandbox.
|
|
590
|
+
sandbox:
|
|
591
|
+
enabled: false # Set to true to wrap bash steps in an OS sandbox
|
|
592
|
+
tier: auto # auto | denylist-only | full
|
|
587
593
|
```
|
|
588
594
|
|
|
595
|
+
If your `moflo.yaml` predates the `sandbox:` block, it is auto-appended on the next session start — you never need to re-run `moflo init` after a version bump.
|
|
596
|
+
|
|
589
597
|
### Key Behaviors
|
|
590
598
|
|
|
591
599
|
| Config | Effect |
|
|
@@ -606,6 +614,9 @@ status_line:
|
|
|
606
614
|
| `model_routing.enabled: true` | Auto-select haiku/sonnet/opus based on task complexity |
|
|
607
615
|
| `status_line.mode: dashboard` | Switch to multi-line status display |
|
|
608
616
|
| `status_line.show_swarm: false` | Hide swarm agent count from status bar |
|
|
617
|
+
| `sandbox.enabled: true` | Wrap bash steps in an OS sandbox (macOS/Linux/WSL) — absolute disable when `false`, regardless of tier |
|
|
618
|
+
| `sandbox.tier: full` | Require OS sandbox; throw at runtime if the platform tool is unavailable |
|
|
619
|
+
| `sandbox.tier: denylist-only` | Keep Layer 1 denylist only; skip OS isolation even when enabled |
|
|
609
620
|
|
|
610
621
|
---
|
|
611
622
|
|
|
@@ -210,6 +210,29 @@ The engine **automatically rewrites** `claude -p` commands in bash steps — str
|
|
|
210
210
|
| **[SENSITIVE]** | `agent`, `net`, `browser` | Can read external data or spawn processes |
|
|
211
211
|
| **[DESTRUCTIVE]** | `shell`, `fs:write`, `browser:evaluate`, `credentials` | Can permanently modify/delete data |
|
|
212
212
|
|
|
213
|
+
## OS-Level Sandbox Configuration (`moflo.yaml`)
|
|
214
|
+
|
|
215
|
+
Capabilities and the gateway always apply. An **additional** OS-level process sandbox (Layer 3) wraps bash steps on macOS (`sandbox-exec`) and Linux/WSL (`bwrap`). It is controlled by the `sandbox:` block in `moflo.yaml`:
|
|
216
|
+
|
|
217
|
+
```yaml
|
|
218
|
+
sandbox:
|
|
219
|
+
enabled: false # Master toggle — false = OS sandbox off (denylist + gateway still apply)
|
|
220
|
+
tier: auto # auto | denylist-only | full
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
Semantics (from `resolveEffectiveSandbox()` in `src/modules/spells/src/core/platform-sandbox.ts`):
|
|
224
|
+
|
|
225
|
+
| `enabled` | `tier` | Tool available | OS sandbox runs? | Notes |
|
|
226
|
+
|-----------|--------|----------------|------------------|-------|
|
|
227
|
+
| `false` | (any) | (any) | No | **Absolute disable** — master toggle wins |
|
|
228
|
+
| `true` | `auto` | Yes | Yes | Use detected tool (bwrap/sandbox-exec) |
|
|
229
|
+
| `true` | `auto` | No | No | Graceful fallback; logs "not available" |
|
|
230
|
+
| `true` | `denylist-only` | (any) | No | Layer 1 only, skip OS isolation |
|
|
231
|
+
| `true` | `full` | Yes | Yes | Require OS sandbox |
|
|
232
|
+
| `true` | `full` | No | — | **Throws** at spell start |
|
|
233
|
+
|
|
234
|
+
Existing projects that predate this block get it auto-appended on session start — never require `moflo init` to re-run after a version bump.
|
|
235
|
+
|
|
213
236
|
## See Also
|
|
214
237
|
|
|
215
238
|
- `.claude/guidance/shipped/moflo-spell-engine.md` — Spell engine usage and YAML format
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* moflo.yaml section upgrader.
|
|
3
|
+
*
|
|
4
|
+
* Users must never be required to re-run `moflo init` after upgrading moflo.
|
|
5
|
+
* When we ship a new top-level config section (e.g. `sandbox:`), this module
|
|
6
|
+
* idempotently appends the missing block — with sensible defaults and inline
|
|
7
|
+
* comments — to the user's existing moflo.yaml, without touching any values
|
|
8
|
+
* they've already set.
|
|
9
|
+
*
|
|
10
|
+
* See: .claude/guidance/internal/upgrade-contract.md
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { readFileSync, writeFileSync, existsSync } from 'fs';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Registry of top-level config sections that moflo ships with default blocks.
|
|
17
|
+
*
|
|
18
|
+
* Each entry: { key, block } where `block` is the raw YAML snippet (including
|
|
19
|
+
* its leading comment) to append when the top-level `<key>:` is absent.
|
|
20
|
+
*
|
|
21
|
+
* When adding a new top-level section to the init template in moflo-init.ts,
|
|
22
|
+
* also add the same block here so existing users get it on their next session.
|
|
23
|
+
*/
|
|
24
|
+
export const REQUIRED_SECTIONS = [
|
|
25
|
+
{
|
|
26
|
+
key: 'sandbox',
|
|
27
|
+
block: `# Spell step sandboxing (OS-level process isolation for bash steps)
|
|
28
|
+
# Platform support: macOS (sandbox-exec), Linux/WSL (bwrap). Windows has no OS sandbox.
|
|
29
|
+
# Tiers:
|
|
30
|
+
# auto — Use best available sandbox for this platform (recommended when enabled)
|
|
31
|
+
# denylist-only — Layer 1 only: block catastrophic commands, no OS isolation
|
|
32
|
+
# full — Require full OS isolation; throws if the sandbox tool is unavailable
|
|
33
|
+
sandbox:
|
|
34
|
+
enabled: false # Set to true to wrap bash steps in an OS sandbox
|
|
35
|
+
tier: auto # auto | denylist-only | full
|
|
36
|
+
`,
|
|
37
|
+
},
|
|
38
|
+
];
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Return true if the YAML text already defines the given top-level key.
|
|
42
|
+
* Matches `^<key>:` at column 0 on any line, which is how YAML roots look.
|
|
43
|
+
*/
|
|
44
|
+
export function hasTopLevelSection(yamlText, key) {
|
|
45
|
+
const pattern = new RegExp(`^${key}\\s*:`, 'm');
|
|
46
|
+
return pattern.test(yamlText);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Compute what ensureYamlSections() would append, without writing anything.
|
|
51
|
+
* Returns the list of section keys that are missing from the given yaml text.
|
|
52
|
+
*/
|
|
53
|
+
export function missingSections(yamlText, registry = REQUIRED_SECTIONS) {
|
|
54
|
+
return registry
|
|
55
|
+
.filter((entry) => !hasTopLevelSection(yamlText, entry.key))
|
|
56
|
+
.map((entry) => entry.key);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Append any missing registered sections to the yaml file at `yamlPath`.
|
|
61
|
+
*
|
|
62
|
+
* - Idempotent: sections already present are left alone.
|
|
63
|
+
* - Non-destructive: user values are never read, parsed, or rewritten.
|
|
64
|
+
* - Returns the list of section keys that were appended (empty if no change).
|
|
65
|
+
*/
|
|
66
|
+
export function ensureYamlSections(yamlPath, registry = REQUIRED_SECTIONS) {
|
|
67
|
+
if (!existsSync(yamlPath)) return [];
|
|
68
|
+
|
|
69
|
+
const original = readFileSync(yamlPath, 'utf-8');
|
|
70
|
+
const toAppend = registry.filter((entry) => !hasTopLevelSection(original, entry.key));
|
|
71
|
+
if (toAppend.length === 0) return [];
|
|
72
|
+
|
|
73
|
+
const needsTrailingNewline = !original.endsWith('\n');
|
|
74
|
+
const separator = needsTrailingNewline ? '\n\n' : original.endsWith('\n\n') ? '' : '\n';
|
|
75
|
+
const appended = toAppend.map((entry) => entry.block.trimEnd()).join('\n\n');
|
|
76
|
+
const next = `${original}${separator}${appended}\n`;
|
|
77
|
+
|
|
78
|
+
writeFileSync(yamlPath, next, 'utf-8');
|
|
79
|
+
return toAppend.map((entry) => entry.key);
|
|
80
|
+
}
|
|
@@ -355,6 +355,25 @@ try {
|
|
|
355
355
|
}
|
|
356
356
|
} catch { /* non-fatal */ }
|
|
357
357
|
|
|
358
|
+
// ── 3d-yaml. Append missing top-level sections to moflo.yaml ───────────────
|
|
359
|
+
// Users must never be required to re-run `moflo init` after a version bump.
|
|
360
|
+
// When moflo ships a new top-level config section (e.g. sandbox:), append it
|
|
361
|
+
// with defaults + comments if the user's yaml doesn't already have it.
|
|
362
|
+
// Fully idempotent and never touches user-set values.
|
|
363
|
+
// See: .claude/guidance/internal/upgrade-contract.md
|
|
364
|
+
try {
|
|
365
|
+
const upgraderPaths = [
|
|
366
|
+
resolve(projectRoot, 'node_modules/moflo/bin/lib/yaml-upgrader.mjs'),
|
|
367
|
+
resolve(projectRoot, 'bin/lib/yaml-upgrader.mjs'),
|
|
368
|
+
];
|
|
369
|
+
const upgraderPath = upgraderPaths.find((p) => existsSync(p));
|
|
370
|
+
const mofloYaml = resolve(projectRoot, 'moflo.yaml');
|
|
371
|
+
if (upgraderPath && existsSync(mofloYaml)) {
|
|
372
|
+
const { ensureYamlSections } = await import(`file://${upgraderPath.replace(/\\/g, '/')}`);
|
|
373
|
+
ensureYamlSections(mofloYaml);
|
|
374
|
+
}
|
|
375
|
+
} catch { /* non-fatal — yaml stays as-is, user can still edit manually */ }
|
|
376
|
+
|
|
358
377
|
// ── 3d. Ensure global `flo` shim exists ─────────────────────────────────────
|
|
359
378
|
// Installs a tiny shim into npm's global bin so bare `flo` resolves to the
|
|
360
379
|
// local project's node_modules/.bin/flo. Idempotent — skips if already present.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "moflo",
|
|
3
|
-
"version": "4.8.
|
|
3
|
+
"version": "4.8.65",
|
|
4
4
|
"description": "MoFlo — AI agent orchestration for Claude Code. Forked from ruflo/claude-flow with patches applied to source, plus feature-level orchestration.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -112,7 +112,7 @@
|
|
|
112
112
|
"@types/js-yaml": "^4.0.9",
|
|
113
113
|
"@types/node": "^20.19.37",
|
|
114
114
|
"eslint": "^8.0.0",
|
|
115
|
-
"moflo": "^4.8.
|
|
115
|
+
"moflo": "^4.8.64",
|
|
116
116
|
"tsx": "^4.21.0",
|
|
117
117
|
"typescript": "^5.9.3",
|
|
118
118
|
"vitest": "^4.0.0"
|
|
@@ -340,6 +340,16 @@ mcp:
|
|
|
340
340
|
tool_defer: deferred # Defer 150+ tool schemas; loaded on demand via ToolSearch
|
|
341
341
|
auto_start: false # Auto-start MCP server on session begin
|
|
342
342
|
|
|
343
|
+
# Spell step sandboxing (OS-level process isolation for bash steps)
|
|
344
|
+
# Platform support: macOS (sandbox-exec), Linux/WSL (bwrap). Windows has no OS sandbox.
|
|
345
|
+
# Tiers:
|
|
346
|
+
# auto — Use best available sandbox for this platform (recommended when enabled)
|
|
347
|
+
# denylist-only — Layer 1 only: block catastrophic commands, no OS isolation
|
|
348
|
+
# full — Require full OS isolation; throws if the sandbox tool is unavailable
|
|
349
|
+
sandbox:
|
|
350
|
+
enabled: false # Set to true to wrap bash steps in an OS sandbox
|
|
351
|
+
tier: auto # auto | denylist-only | full
|
|
352
|
+
|
|
343
353
|
# Status line display (shown at bottom of Claude Code)
|
|
344
354
|
# mode: "compact" (default), "single-line", or "dashboard" (full multi-line)
|
|
345
355
|
status_line:
|
|
@@ -152,11 +152,12 @@ export async function loadSandboxConfigFromProject(projectRoot) {
|
|
|
152
152
|
/**
|
|
153
153
|
* Combine detected capability with user config to determine effective sandbox behavior.
|
|
154
154
|
*
|
|
155
|
+
* @param config — resolved user config (enabled + tier)
|
|
156
|
+
* @param capability — optional capability override (for tests; skips OS detection)
|
|
155
157
|
* @returns EffectiveSandbox — includes display status for spell-start logging.
|
|
156
158
|
* @throws Error if tier is 'full' but no OS sandbox is available.
|
|
157
159
|
*/
|
|
158
|
-
export function resolveEffectiveSandbox(config) {
|
|
159
|
-
const capability = detectSandboxCapability();
|
|
160
|
+
export function resolveEffectiveSandbox(config, capability = detectSandboxCapability()) {
|
|
160
161
|
// Config disabled or tier is denylist-only => no OS sandbox
|
|
161
162
|
if (!config.enabled || config.tier === 'denylist-only') {
|
|
162
163
|
return {
|