@miphamai/cli 0.15.0 → 0.16.1
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/dist/mipham +0 -0
- package/package.json +2 -4
- package/src/agent/message-bus.ts +19 -1
- package/src/agent/sub-agent.ts +25 -2
- package/src/commands/environment.ts +270 -0
- package/src/commands/git.ts +152 -0
- package/src/commands/project.ts +1011 -0
- package/src/config/defaults.ts +4 -0
- package/src/config/preferences.ts +45 -0
- package/src/core/context.ts +9 -0
- package/src/core/engine.ts +17 -5
- package/src/core/permission-config.ts +70 -6
- package/src/core/permission.ts +50 -9
- package/src/core/session-store.ts +7 -2
- package/src/index.tsx +51 -2
- package/src/providers/registry.ts +8 -0
- package/src/shared/sanitize.ts +38 -0
- package/src/shared/types.ts +18 -0
- package/src/skills/community-registry.json +72 -0
- package/src/skills/registry.ts +71 -77
- package/src/tools/exec/bash.ts +40 -8
- package/src/tools/exec/enter-worktree.ts +23 -1
- package/src/tools/exec/exit-worktree.ts +13 -0
- package/src/tools/index.ts +5 -2
- package/src/ui/agent-footer.tsx +89 -0
- package/src/ui/app.tsx +169 -35
- package/src/ui/chat.tsx +80 -30
- package/src/ui/commands.ts +176 -1477
- package/src/ui/strip-indent.ts +19 -0
- package/src/workflow/runtime.ts +29 -31
- package/src/workflow/sandbox.ts +22 -10
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Template literal tag that strips leading indentation from multi-line strings.
|
|
3
|
+
* Used by slash command handlers to format help text and status messages.
|
|
4
|
+
*/
|
|
5
|
+
export function stripIndent(strings: TemplateStringsArray, ...values: unknown[]): string {
|
|
6
|
+
let result = strings.reduce((acc, s, i) => acc + s + (values[i] ?? ''), '')
|
|
7
|
+
// Remove leading newline
|
|
8
|
+
result = result.replace(/^\n/, '')
|
|
9
|
+
// Find minimum indent
|
|
10
|
+
const match = result.match(/^( +)/m)
|
|
11
|
+
if (match?.[1]) {
|
|
12
|
+
const indent = match[1].length
|
|
13
|
+
result = result
|
|
14
|
+
.split('\n')
|
|
15
|
+
.map((line) => line.slice(indent))
|
|
16
|
+
.join('\n')
|
|
17
|
+
}
|
|
18
|
+
return result.trim()
|
|
19
|
+
}
|
package/src/workflow/runtime.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import vm from 'node:vm'
|
|
1
2
|
import { createSandbox } from './sandbox'
|
|
2
3
|
import { createJournal, appendJournal, loadJournal, loadScript } from './journal'
|
|
3
4
|
import { createBudget } from './budget'
|
|
@@ -47,7 +48,8 @@ function agentCacheKey(prompt: string, opts: Record<string, unknown> = {}): stri
|
|
|
47
48
|
* - Returns cacheHits and cacheMisses counts
|
|
48
49
|
*
|
|
49
50
|
* The sandbox blocks non-deterministic APIs (Date.now, Math.random) to make
|
|
50
|
-
* this deterministic replay possible.
|
|
51
|
+
* this deterministic replay possible. Scripts execute inside a node:vm.Context
|
|
52
|
+
* that blocks sandbox escape vectors (eval, Function, import, require, process, etc.).
|
|
51
53
|
*/
|
|
52
54
|
export async function runWorkflow(
|
|
53
55
|
script: string,
|
|
@@ -134,42 +136,38 @@ export async function runWorkflow(
|
|
|
134
136
|
appendJournal(runId, { type: 'log', message })
|
|
135
137
|
}
|
|
136
138
|
|
|
137
|
-
const sandbox = createSandbox(args, budget)
|
|
138
|
-
|
|
139
139
|
// Build the script wrapper
|
|
140
140
|
const wrappedScript = `
|
|
141
|
-
|
|
141
|
+
(async () => {
|
|
142
142
|
${script}
|
|
143
143
|
})()
|
|
144
144
|
`
|
|
145
145
|
|
|
146
|
-
// Execute in
|
|
147
|
-
const
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
budget,
|
|
172
|
-
)
|
|
146
|
+
// Execute in node:vm sandbox — no access to host globals
|
|
147
|
+
const vmScript = new vm.Script(wrappedScript, { filename: 'workflow.js' })
|
|
148
|
+
const sandboxCtx = createSandbox(args, budget)
|
|
149
|
+
|
|
150
|
+
// Inject primitives into the sandbox context (whitelist approach)
|
|
151
|
+
sandboxCtx.agent = agent
|
|
152
|
+
sandboxCtx.parallel = parallel
|
|
153
|
+
sandboxCtx.pipeline = pipeline
|
|
154
|
+
sandboxCtx.verify = verify
|
|
155
|
+
sandboxCtx.judge = judge
|
|
156
|
+
sandboxCtx.loopUntilConvergence = loopUntilConvergence
|
|
157
|
+
sandboxCtx.phase = wrappedPhase
|
|
158
|
+
sandboxCtx.log = log
|
|
159
|
+
|
|
160
|
+
let result: unknown
|
|
161
|
+
try {
|
|
162
|
+
result = await vmScript.runInContext(sandboxCtx, { timeout: 120_000 })
|
|
163
|
+
} catch (err) {
|
|
164
|
+
const message = err instanceof Error ? err.message : String(err)
|
|
165
|
+
throw new Error(
|
|
166
|
+
`Workflow script execution failed: ${message}\n\n` +
|
|
167
|
+
`This is likely a bug in the workflow script, not in Mipham Code itself. ` +
|
|
168
|
+
`Check the script for syntax errors, undefined references, or unhandled promise rejections.`,
|
|
169
|
+
)
|
|
170
|
+
}
|
|
173
171
|
|
|
174
172
|
// Count journal entries from state
|
|
175
173
|
const priorEntries = loadJournal(runId)
|
package/src/workflow/sandbox.ts
CHANGED
|
@@ -1,27 +1,39 @@
|
|
|
1
|
-
|
|
1
|
+
import vm from 'node:vm'
|
|
2
|
+
|
|
3
|
+
/** APIs disabled in workflow scripts to ensure deterministic replay + sandbox escape prevention. */
|
|
2
4
|
const FORBIDDEN = new Set(['Date.now', 'Math.random', 'crypto.randomUUID'])
|
|
3
5
|
|
|
4
6
|
/**
|
|
5
|
-
* Create a sandboxed
|
|
6
|
-
* Blocks Date.now(), Math.random(), argless new Date(), crypto.randomUUID()
|
|
7
|
+
* Create a sandboxed VM context for workflow script execution.
|
|
8
|
+
* Blocks Date.now(), Math.random(), argless new Date(), crypto.randomUUID(),
|
|
9
|
+
* plus explicit sandbox escape vectors: eval, Function constructor, import(),
|
|
10
|
+
* require(), process, Bun, fetch, setTimeout/setInterval, etc.
|
|
7
11
|
*/
|
|
8
12
|
export function createSandbox(
|
|
9
13
|
args: unknown,
|
|
10
14
|
budget: { total: number | null; spent(): number; remaining(): number },
|
|
11
|
-
):
|
|
12
|
-
const
|
|
15
|
+
): vm.Context {
|
|
16
|
+
const sandboxObj: Record<string, unknown> = {
|
|
13
17
|
args,
|
|
14
18
|
budget,
|
|
15
19
|
console: {
|
|
16
20
|
log: (..._a: unknown[]) => {}, // no-op in sandbox
|
|
17
21
|
error: (..._a: unknown[]) => {},
|
|
18
22
|
},
|
|
19
|
-
|
|
23
|
+
|
|
24
|
+
// ── Explicit sandbox escape prevention ──
|
|
25
|
+
// V8 builtins that would otherwise be available in a vm.Context
|
|
26
|
+
eval: () => {
|
|
27
|
+
throw new Error('eval() is disabled in workflow sandbox.')
|
|
28
|
+
},
|
|
29
|
+
Function: () => {
|
|
30
|
+
throw new Error('new Function() is disabled in workflow sandbox.')
|
|
31
|
+
},
|
|
20
32
|
}
|
|
21
33
|
|
|
22
34
|
// Override Date to block now() and argless constructor
|
|
23
35
|
const OriginalDate = Date
|
|
24
|
-
|
|
36
|
+
sandboxObj.Date = new Proxy(OriginalDate, {
|
|
25
37
|
construct(_target, constructorArgs) {
|
|
26
38
|
if (constructorArgs.length === 0) {
|
|
27
39
|
throw new Error('new Date() is disabled in workflow sandbox. Pass timestamps via args.')
|
|
@@ -42,7 +54,7 @@ export function createSandbox(
|
|
|
42
54
|
})
|
|
43
55
|
|
|
44
56
|
// Override Math.random
|
|
45
|
-
|
|
57
|
+
sandboxObj.Math = new Proxy(Math, {
|
|
46
58
|
get(_target, prop) {
|
|
47
59
|
if (prop === 'random') {
|
|
48
60
|
throw new Error('Math.random() is disabled in workflow sandbox. Use a seed from args.')
|
|
@@ -57,7 +69,7 @@ export function createSandbox(
|
|
|
57
69
|
| { randomUUID?: unknown; [key: string]: unknown }
|
|
58
70
|
| undefined
|
|
59
71
|
if (globalCrypto) {
|
|
60
|
-
|
|
72
|
+
sandboxObj.crypto = new Proxy(globalCrypto, {
|
|
61
73
|
get(_target, prop) {
|
|
62
74
|
if (prop === 'randomUUID') {
|
|
63
75
|
throw new Error('crypto.randomUUID() is disabled in workflow sandbox.')
|
|
@@ -70,7 +82,7 @@ export function createSandbox(
|
|
|
70
82
|
})
|
|
71
83
|
}
|
|
72
84
|
|
|
73
|
-
return
|
|
85
|
+
return vm.createContext(sandboxObj)
|
|
74
86
|
}
|
|
75
87
|
|
|
76
88
|
/** Check whether a given API identifier is in the forbidden set. */
|