@chengchenccc/oh-my-agent 0.1.1-rc.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/README.md +79 -0
- package/dist/cli/args.d.ts +24 -0
- package/dist/cli/args.d.ts.map +1 -0
- package/dist/cli/args.js +120 -0
- package/dist/cli/initial-input.d.ts +32 -0
- package/dist/cli/initial-input.d.ts.map +1 -0
- package/dist/cli/initial-input.js +131 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +3 -0
- package/dist/core/__fixtures__/echo-model.d.ts +6 -0
- package/dist/core/__fixtures__/echo-model.d.ts.map +1 -0
- package/dist/core/__fixtures__/echo-model.js +10 -0
- package/dist/core/__fixtures__/mcp-echo-server.d.ts +2 -0
- package/dist/core/__fixtures__/mcp-echo-server.d.ts.map +1 -0
- package/dist/core/__fixtures__/mcp-echo-server.js +54 -0
- package/dist/core/agent-runtime.d.ts +21 -0
- package/dist/core/agent-runtime.d.ts.map +1 -0
- package/dist/core/agent-runtime.js +14 -0
- package/dist/core/memory/autonomous-memory.d.ts +21 -0
- package/dist/core/memory/autonomous-memory.d.ts.map +1 -0
- package/dist/core/memory/autonomous-memory.js +202 -0
- package/dist/core/persistence/in-memory-session-store.d.ts +3 -0
- package/dist/core/persistence/in-memory-session-store.d.ts.map +1 -0
- package/dist/core/persistence/in-memory-session-store.js +109 -0
- package/dist/core/persistence/session-store.contract.d.ts +5 -0
- package/dist/core/persistence/session-store.contract.d.ts.map +1 -0
- package/dist/core/persistence/session-store.contract.js +183 -0
- package/dist/core/persistence/session-store.d.ts +33 -0
- package/dist/core/persistence/session-store.d.ts.map +1 -0
- package/dist/core/persistence/session-store.js +22 -0
- package/dist/core/persistence/session-tree.d.ts +49 -0
- package/dist/core/persistence/session-tree.d.ts.map +1 -0
- package/dist/core/persistence/session-tree.js +1 -0
- package/dist/core/plugins/plugin-code.d.ts +13 -0
- package/dist/core/plugins/plugin-code.d.ts.map +1 -0
- package/dist/core/plugins/plugin-code.js +62 -0
- package/dist/core/plugins/plugin-marketplace.d.ts +72 -0
- package/dist/core/plugins/plugin-marketplace.d.ts.map +1 -0
- package/dist/core/plugins/plugin-marketplace.js +340 -0
- package/dist/core/plugins/plugin-resolve.d.ts +32 -0
- package/dist/core/plugins/plugin-resolve.d.ts.map +1 -0
- package/dist/core/plugins/plugin-resolve.js +101 -0
- package/dist/core/plugins/plugin-trust.d.ts +24 -0
- package/dist/core/plugins/plugin-trust.d.ts.map +1 -0
- package/dist/core/plugins/plugin-trust.js +89 -0
- package/dist/core/runtime/agent-event.d.ts +98 -0
- package/dist/core/runtime/agent-event.d.ts.map +1 -0
- package/dist/core/runtime/agent-event.js +1 -0
- package/dist/core/runtime/agent-loop.d.ts +142 -0
- package/dist/core/runtime/agent-loop.d.ts.map +1 -0
- package/dist/core/runtime/agent-loop.js +1008 -0
- package/dist/core/runtime/approval.d.ts +23 -0
- package/dist/core/runtime/approval.d.ts.map +1 -0
- package/dist/core/runtime/approval.js +25 -0
- package/dist/core/runtime/compaction.d.ts +22 -0
- package/dist/core/runtime/compaction.d.ts.map +1 -0
- package/dist/core/runtime/compaction.js +90 -0
- package/dist/core/runtime/context-estimate.d.ts +42 -0
- package/dist/core/runtime/context-estimate.d.ts.map +1 -0
- package/dist/core/runtime/context-estimate.js +34 -0
- package/dist/core/runtime/create-runtime.d.ts +81 -0
- package/dist/core/runtime/create-runtime.d.ts.map +1 -0
- package/dist/core/runtime/create-runtime.js +250 -0
- package/dist/core/runtime/fake-provider.d.ts +3 -0
- package/dist/core/runtime/fake-provider.d.ts.map +1 -0
- package/dist/core/runtime/fake-provider.js +87 -0
- package/dist/core/runtime/logger.d.ts +13 -0
- package/dist/core/runtime/logger.d.ts.map +1 -0
- package/dist/core/runtime/logger.js +38 -0
- package/dist/core/runtime/loop-input.d.ts +39 -0
- package/dist/core/runtime/loop-input.d.ts.map +1 -0
- package/dist/core/runtime/loop-input.js +38 -0
- package/dist/core/runtime/message-cache.d.ts +16 -0
- package/dist/core/runtime/message-cache.d.ts.map +1 -0
- package/dist/core/runtime/message-cache.js +23 -0
- package/dist/core/runtime/model-catalog.d.ts +10 -0
- package/dist/core/runtime/model-catalog.d.ts.map +1 -0
- package/dist/core/runtime/model-catalog.js +14 -0
- package/dist/core/runtime/plugin-runtime.d.ts +41 -0
- package/dist/core/runtime/plugin-runtime.d.ts.map +1 -0
- package/dist/core/runtime/plugin-runtime.js +1 -0
- package/dist/core/runtime/plugin.d.ts +86 -0
- package/dist/core/runtime/plugin.d.ts.map +1 -0
- package/dist/core/runtime/plugin.js +23 -0
- package/dist/core/runtime/process-tree.d.ts +12 -0
- package/dist/core/runtime/process-tree.d.ts.map +1 -0
- package/dist/core/runtime/process-tree.js +40 -0
- package/dist/core/runtime/prompt.d.ts +23 -0
- package/dist/core/runtime/prompt.d.ts.map +1 -0
- package/dist/core/runtime/prompt.js +29 -0
- package/dist/core/runtime/prompts.d.ts +16 -0
- package/dist/core/runtime/prompts.d.ts.map +1 -0
- package/dist/core/runtime/prompts.js +36 -0
- package/dist/core/runtime/retry.d.ts +20 -0
- package/dist/core/runtime/retry.d.ts.map +1 -0
- package/dist/core/runtime/retry.js +60 -0
- package/dist/core/runtime/run-runtime.d.ts +93 -0
- package/dist/core/runtime/run-runtime.d.ts.map +1 -0
- package/dist/core/runtime/run-runtime.js +664 -0
- package/dist/core/runtime/runtime-catalog.d.ts +12 -0
- package/dist/core/runtime/runtime-catalog.d.ts.map +1 -0
- package/dist/core/runtime/runtime-catalog.js +89 -0
- package/dist/core/runtime/stream-rules.d.ts +9 -0
- package/dist/core/runtime/stream-rules.d.ts.map +1 -0
- package/dist/core/runtime/stream-rules.js +44 -0
- package/dist/core/runtime/title.d.ts +7 -0
- package/dist/core/runtime/title.d.ts.map +1 -0
- package/dist/core/runtime/title.js +114 -0
- package/dist/core/runtime/todo.d.ts +16 -0
- package/dist/core/runtime/todo.d.ts.map +1 -0
- package/dist/core/runtime/todo.js +40 -0
- package/dist/core/runtime/tool-filter.d.ts +12 -0
- package/dist/core/runtime/tool-filter.d.ts.map +1 -0
- package/dist/core/runtime/tool-filter.js +23 -0
- package/dist/core/runtime/tool-pruning.d.ts +29 -0
- package/dist/core/runtime/tool-pruning.d.ts.map +1 -0
- package/dist/core/runtime/tool-pruning.js +105 -0
- package/dist/core/session/input-history.d.ts +8 -0
- package/dist/core/session/input-history.d.ts.map +1 -0
- package/dist/core/session/input-history.js +41 -0
- package/dist/core/session/session-file.d.ts +84 -0
- package/dist/core/session/session-file.d.ts.map +1 -0
- package/dist/core/session/session-file.js +392 -0
- package/dist/core/session/session-loop.d.ts +33 -0
- package/dist/core/session/session-loop.d.ts.map +1 -0
- package/dist/core/session/session-loop.js +30 -0
- package/dist/core/settings/project-settings.d.ts +46 -0
- package/dist/core/settings/project-settings.d.ts.map +1 -0
- package/dist/core/settings/project-settings.js +85 -0
- package/dist/core/settings/workspace-context.d.ts +10 -0
- package/dist/core/settings/workspace-context.d.ts.map +1 -0
- package/dist/core/settings/workspace-context.js +48 -0
- package/dist/core/tools/agent-fs-like.d.ts +15 -0
- package/dist/core/tools/agent-fs-like.d.ts.map +1 -0
- package/dist/core/tools/agent-fs-like.js +4 -0
- package/dist/core/tools/ask-question.d.ts +5 -0
- package/dist/core/tools/ask-question.d.ts.map +1 -0
- package/dist/core/tools/ask-question.js +51 -0
- package/dist/core/tools/bash.d.ts +5 -0
- package/dist/core/tools/bash.d.ts.map +1 -0
- package/dist/core/tools/bash.js +109 -0
- package/dist/core/tools/eval.d.ts +10 -0
- package/dist/core/tools/eval.d.ts.map +1 -0
- package/dist/core/tools/eval.js +83 -0
- package/dist/core/tools/file-tools.d.ts +18 -0
- package/dist/core/tools/file-tools.d.ts.map +1 -0
- package/dist/core/tools/file-tools.js +249 -0
- package/dist/core/tools/glob.d.ts +5 -0
- package/dist/core/tools/glob.d.ts.map +1 -0
- package/dist/core/tools/glob.js +54 -0
- package/dist/core/tools/grep.d.ts +5 -0
- package/dist/core/tools/grep.d.ts.map +1 -0
- package/dist/core/tools/grep.js +61 -0
- package/dist/core/tools/index.d.ts +14 -0
- package/dist/core/tools/index.d.ts.map +1 -0
- package/dist/core/tools/index.js +14 -0
- package/dist/core/tools/ls-tree.d.ts +10 -0
- package/dist/core/tools/ls-tree.d.ts.map +1 -0
- package/dist/core/tools/ls-tree.js +177 -0
- package/dist/core/tools/mcp-mount.d.ts +63 -0
- package/dist/core/tools/mcp-mount.d.ts.map +1 -0
- package/dist/core/tools/mcp-mount.js +287 -0
- package/dist/core/tools/read-image.d.ts +6 -0
- package/dist/core/tools/read-image.d.ts.map +1 -0
- package/dist/core/tools/read-image.js +98 -0
- package/dist/core/tools/skill.d.ts +11 -0
- package/dist/core/tools/skill.d.ts.map +1 -0
- package/dist/core/tools/skill.js +65 -0
- package/dist/core/tools/skills.d.ts +10 -0
- package/dist/core/tools/skills.d.ts.map +1 -0
- package/dist/core/tools/skills.js +116 -0
- package/dist/core/tools/todo-store.d.ts +8 -0
- package/dist/core/tools/todo-store.d.ts.map +1 -0
- package/dist/core/tools/todo-store.js +100 -0
- package/dist/core/tools/todo.d.ts +8 -0
- package/dist/core/tools/todo.d.ts.map +1 -0
- package/dist/core/tools/todo.js +91 -0
- package/dist/core/tools/url-guard.d.ts +12 -0
- package/dist/core/tools/url-guard.d.ts.map +1 -0
- package/dist/core/tools/url-guard.js +58 -0
- package/dist/core/tools/web-ports-std.d.ts +6 -0
- package/dist/core/tools/web-ports-std.d.ts.map +1 -0
- package/dist/core/tools/web-ports-std.js +145 -0
- package/dist/core/tools/web-ports.d.ts +56 -0
- package/dist/core/tools/web-ports.d.ts.map +1 -0
- package/dist/core/tools/web-ports.js +62 -0
- package/dist/core/tools/workspace-sandbox.d.ts +20 -0
- package/dist/core/tools/workspace-sandbox.d.ts.map +1 -0
- package/dist/core/tools/workspace-sandbox.js +84 -0
- package/dist/core/workflow/subagent-registry.d.ts +40 -0
- package/dist/core/workflow/subagent-registry.d.ts.map +1 -0
- package/dist/core/workflow/subagent-registry.js +118 -0
- package/dist/core/workflow/workflow-evaluator.d.ts +23 -0
- package/dist/core/workflow/workflow-evaluator.d.ts.map +1 -0
- package/dist/core/workflow/workflow-evaluator.js +21 -0
- package/dist/core/workflow/workflow-executor.d.ts +108 -0
- package/dist/core/workflow/workflow-executor.d.ts.map +1 -0
- package/dist/core/workflow/workflow-executor.js +549 -0
- package/dist/core/workflow/workflow-tools.d.ts +50 -0
- package/dist/core/workflow/workflow-tools.d.ts.map +1 -0
- package/dist/core/workflow/workflow-tools.js +217 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +13 -0
- package/dist/main.d.ts +9 -0
- package/dist/main.d.ts.map +1 -0
- package/dist/main.js +97 -0
- package/dist/modes/json-mode.d.ts +5 -0
- package/dist/modes/json-mode.d.ts.map +1 -0
- package/dist/modes/json-mode.js +46 -0
- package/dist/modes/print-mode.d.ts +20 -0
- package/dist/modes/print-mode.d.ts.map +1 -0
- package/dist/modes/print-mode.js +82 -0
- package/dist/modes/rpc/jsonl.d.ts +23 -0
- package/dist/modes/rpc/jsonl.d.ts.map +1 -0
- package/dist/modes/rpc/jsonl.js +88 -0
- package/dist/modes/rpc/rpc-mode.d.ts +18 -0
- package/dist/modes/rpc/rpc-mode.d.ts.map +1 -0
- package/dist/modes/rpc/rpc-mode.js +326 -0
- package/dist/modes/tui/settings-overlay.d.ts +23 -0
- package/dist/modes/tui/settings-overlay.d.ts.map +1 -0
- package/dist/modes/tui/settings-overlay.js +136 -0
- package/dist/modes/tui/tui-commands-session.d.ts +3 -0
- package/dist/modes/tui/tui-commands-session.d.ts.map +1 -0
- package/dist/modes/tui/tui-commands-session.js +191 -0
- package/dist/modes/tui/tui-commands.d.ts +52 -0
- package/dist/modes/tui/tui-commands.d.ts.map +1 -0
- package/dist/modes/tui/tui-commands.js +373 -0
- package/dist/modes/tui/tui-components.d.ts +81 -0
- package/dist/modes/tui/tui-components.d.ts.map +1 -0
- package/dist/modes/tui/tui-components.js +248 -0
- package/dist/modes/tui/tui-format.d.ts +65 -0
- package/dist/modes/tui/tui-format.d.ts.map +1 -0
- package/dist/modes/tui/tui-format.js +246 -0
- package/dist/modes/tui/tui-frame-provider.d.ts +16 -0
- package/dist/modes/tui/tui-frame-provider.d.ts.map +1 -0
- package/dist/modes/tui/tui-frame-provider.js +31 -0
- package/dist/modes/tui/tui-interactive.d.ts +16 -0
- package/dist/modes/tui/tui-interactive.d.ts.map +1 -0
- package/dist/modes/tui/tui-interactive.js +123 -0
- package/dist/modes/tui/tui-io.d.ts +12 -0
- package/dist/modes/tui/tui-io.d.ts.map +1 -0
- package/dist/modes/tui/tui-io.js +620 -0
- package/dist/modes/tui/tui-mode.d.ts +120 -0
- package/dist/modes/tui/tui-mode.d.ts.map +1 -0
- package/dist/modes/tui/tui-mode.js +391 -0
- package/dist/modes/tui/tui-render.d.ts +54 -0
- package/dist/modes/tui/tui-render.d.ts.map +1 -0
- package/dist/modes/tui/tui-render.js +399 -0
- package/dist/modes/tui/tui-slash.d.ts +12 -0
- package/dist/modes/tui/tui-slash.d.ts.map +1 -0
- package/dist/modes/tui/tui-slash.js +64 -0
- package/dist/modes/tui/tui-tool-render.d.ts +6 -0
- package/dist/modes/tui/tui-tool-render.d.ts.map +1 -0
- package/dist/modes/tui/tui-tool-render.js +79 -0
- package/dist/modes/tui/tui-transcript-reconciler.d.ts +25 -0
- package/dist/modes/tui/tui-transcript-reconciler.d.ts.map +1 -0
- package/dist/modes/tui/tui-transcript-reconciler.js +97 -0
- package/dist/modes/tui/view-state.d.ts +67 -0
- package/dist/modes/tui/view-state.d.ts.map +1 -0
- package/dist/modes/tui/view-state.js +353 -0
- package/dist/prompts/agents/subagent.md +26 -0
- package/dist/prompts/system/base.md +20 -0
- package/dist/prompts/system/memory.md +23 -0
- package/dist/prompts/system/safety.md +12 -0
- package/dist/protocol/index.d.ts +5 -0
- package/dist/protocol/index.d.ts.map +1 -0
- package/dist/protocol/index.js +2 -0
- package/dist/protocol/mapping.d.ts +25 -0
- package/dist/protocol/mapping.d.ts.map +1 -0
- package/dist/protocol/mapping.js +115 -0
- package/dist/protocol/transport.d.ts +1396 -0
- package/dist/protocol/transport.d.ts.map +1 -0
- package/dist/protocol/transport.js +170 -0
- package/package.json +49 -0
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { assertSafeUrl, FETCH_TIMEOUT_MS, MAX_REDIRECTS, UrlGuardError } from "./url-guard.js";
|
|
2
|
+
/** Max bytes read from a fetched page (fetch port is heavier than the
|
|
3
|
+
* url-guard default: real pages, not snippets). */
|
|
4
|
+
const FETCH_CAP_BYTES = 512_000;
|
|
5
|
+
function decodeEntities(value) {
|
|
6
|
+
return value
|
|
7
|
+
.replace(/<[^>]*>/g, " ")
|
|
8
|
+
.replace(/&#x([0-9a-f]+);/gi, (_, code) => String.fromCharCode(Number.parseInt(code, 16)))
|
|
9
|
+
.replace(/&#(\d+);/g, (_, code) => String.fromCharCode(Number(code)))
|
|
10
|
+
.replace(/ /gi, " ")
|
|
11
|
+
.replace(/&/gi, "&")
|
|
12
|
+
.replace(/</gi, "<")
|
|
13
|
+
.replace(/>/gi, ">")
|
|
14
|
+
.replace(/"/gi, '"')
|
|
15
|
+
.replace(/'|'/gi, "'")
|
|
16
|
+
.replace(/\s+/g, " ")
|
|
17
|
+
.trim();
|
|
18
|
+
}
|
|
19
|
+
/** Naive HTML → readable text: drop script/style/head, strip tags. */
|
|
20
|
+
function htmlToText(html) {
|
|
21
|
+
return decodeEntities(html
|
|
22
|
+
.replace(/<(script|style|noscript|svg|head)\b[\s\S]*?<\/\1>/gi, " ")
|
|
23
|
+
.replace(/<\/(p|div|li|h[1-6]|tr|section|article)>/gi, "\n"));
|
|
24
|
+
}
|
|
25
|
+
/** Std fetch port: global fetch, manual redirect walk (each hop re-validated
|
|
26
|
+
* against the url guard), timeout + size caps, HTML→text. */
|
|
27
|
+
export function createStdWebFetchPort() {
|
|
28
|
+
return {
|
|
29
|
+
async fetch(url, signal) {
|
|
30
|
+
let current = assertSafeUrl(url).toString();
|
|
31
|
+
for (let hop = 0; hop <= MAX_REDIRECTS; hop++) {
|
|
32
|
+
const resp = await fetch(current, {
|
|
33
|
+
redirect: "manual",
|
|
34
|
+
signal: signal ?? AbortSignal.timeout(FETCH_TIMEOUT_MS),
|
|
35
|
+
headers: { "user-agent": "oma/0.1 (+web_fetch)" },
|
|
36
|
+
});
|
|
37
|
+
if ([301, 302, 303, 307, 308].includes(resp.status)) {
|
|
38
|
+
const location = resp.headers.get("location");
|
|
39
|
+
if (!location)
|
|
40
|
+
throw new Error(`redirect without location from ${current}`);
|
|
41
|
+
current = assertSafeUrl(new URL(location, current).toString()).toString();
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
if (!resp.ok)
|
|
45
|
+
throw new Error(`HTTP ${resp.status} from ${current}`);
|
|
46
|
+
const reader = resp.body?.getReader();
|
|
47
|
+
if (!reader) {
|
|
48
|
+
const text = await resp.text();
|
|
49
|
+
return { text: htmlToText(text).slice(0, FETCH_CAP_BYTES), title: titleOf(text) };
|
|
50
|
+
}
|
|
51
|
+
const chunks = [];
|
|
52
|
+
let bytes = 0;
|
|
53
|
+
for (;;) {
|
|
54
|
+
const { done, value } = await reader.read();
|
|
55
|
+
if (done)
|
|
56
|
+
break;
|
|
57
|
+
bytes += value.byteLength;
|
|
58
|
+
chunks.push(value);
|
|
59
|
+
if (bytes > FETCH_CAP_BYTES)
|
|
60
|
+
break;
|
|
61
|
+
}
|
|
62
|
+
const html = new TextDecoder().decode(concat(chunks));
|
|
63
|
+
return { text: htmlToText(html), title: titleOf(html) };
|
|
64
|
+
}
|
|
65
|
+
throw new UrlGuardError(`too many redirects (>${MAX_REDIRECTS})`);
|
|
66
|
+
},
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
function titleOf(html) {
|
|
70
|
+
const m = /<title[^>]*>([\s\S]*?)<\/title>/i.exec(html);
|
|
71
|
+
return m?.[1] !== undefined ? decodeEntities(m[1]).slice(0, 200) : undefined;
|
|
72
|
+
}
|
|
73
|
+
function concat(chunks) {
|
|
74
|
+
const total = chunks.reduce((n, c) => n + c.byteLength, 0);
|
|
75
|
+
const out = new Uint8Array(total);
|
|
76
|
+
let offset = 0;
|
|
77
|
+
for (const c of chunks) {
|
|
78
|
+
out.set(c, offset);
|
|
79
|
+
offset += c.byteLength;
|
|
80
|
+
}
|
|
81
|
+
return out;
|
|
82
|
+
}
|
|
83
|
+
/** DuckDuckGo no-JS HTML frontend as a zero-config search port (same source
|
|
84
|
+
* omp uses). DDG routes outbound links through /l/?uddg=<encoded> — those
|
|
85
|
+
* are unwrapped back to the target URL. */
|
|
86
|
+
const DDG_HTML_URL = "https://html.duckduckgo.com/html/";
|
|
87
|
+
function unwrapDdgHref(href) {
|
|
88
|
+
const wrap = /[?&]uddg=([^&]+)/.exec(href.replace(/&/gi, "&"));
|
|
89
|
+
if (wrap?.[1] !== undefined) {
|
|
90
|
+
try {
|
|
91
|
+
return decodeURIComponent(wrap[1]);
|
|
92
|
+
}
|
|
93
|
+
catch {
|
|
94
|
+
return undefined;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
if (href.startsWith("//"))
|
|
98
|
+
return `https:${href}`;
|
|
99
|
+
if (href.startsWith("http://") || href.startsWith("https://"))
|
|
100
|
+
return href;
|
|
101
|
+
return undefined;
|
|
102
|
+
}
|
|
103
|
+
function parseDdgResults(html) {
|
|
104
|
+
const results = [];
|
|
105
|
+
const blockRe = /<div\b[^>]*\bclass="[^"]*\bresult\b[^"]*"[^>]*>([\s\S]*?)(?=<div\b[^>]*\bclass="[^"]*\bresult\b|<div\b[^>]*\bclass="[^"]*\bnav-link\b|$)/g;
|
|
106
|
+
const titleRe = /<a\b[^>]*\bclass="[^"]*\bresult__a\b[^"]*"[^>]*\bhref="([^"]+)"[^>]*>([\s\S]*?)<\/a>/;
|
|
107
|
+
const snippetRe = /<(?:a|div|span)\b[^>]*\bclass="[^"]*\bresult__snippet\b[^"]*"[^>]*>([\s\S]*?)<\/(?:a|div|span)>/;
|
|
108
|
+
for (const match of html.matchAll(blockRe)) {
|
|
109
|
+
const block = match[1] ?? "";
|
|
110
|
+
const title = titleRe.exec(block);
|
|
111
|
+
if (!title)
|
|
112
|
+
continue;
|
|
113
|
+
const url = unwrapDdgHref(title[1] ?? "");
|
|
114
|
+
const titleText = decodeEntities(title[2] ?? "");
|
|
115
|
+
if (!url || !titleText)
|
|
116
|
+
continue;
|
|
117
|
+
const snippet = snippetRe.exec(block);
|
|
118
|
+
results.push({
|
|
119
|
+
title: titleText,
|
|
120
|
+
url,
|
|
121
|
+
snippet: snippet ? decodeEntities(snippet[1] ?? "") : "",
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
return results;
|
|
125
|
+
}
|
|
126
|
+
export function createDdgWebSearchPort() {
|
|
127
|
+
return {
|
|
128
|
+
async search(query, limit = 5, signal) {
|
|
129
|
+
const resp = await fetch(DDG_HTML_URL, {
|
|
130
|
+
method: "POST",
|
|
131
|
+
headers: {
|
|
132
|
+
"content-type": "application/x-www-form-urlencoded",
|
|
133
|
+
"user-agent": "oma/0.1 (+web_search)",
|
|
134
|
+
},
|
|
135
|
+
body: new URLSearchParams({ q: query }).toString(),
|
|
136
|
+
signal: signal ?? AbortSignal.timeout(FETCH_TIMEOUT_MS),
|
|
137
|
+
});
|
|
138
|
+
const html = await resp.text();
|
|
139
|
+
if (html.includes("anomaly-modal") || html.includes("anomaly.js")) {
|
|
140
|
+
throw new Error("duckduckgo bot challenge — retry or use a different query");
|
|
141
|
+
}
|
|
142
|
+
return parseDdgResults(html).slice(0, limit);
|
|
143
|
+
},
|
|
144
|
+
};
|
|
145
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/** Port for web search. Implementations inject search providers. */
|
|
2
|
+
export interface WebSearchPort {
|
|
3
|
+
search(query: string, limit?: number, signal?: AbortSignal): Promise<ReadonlyArray<{
|
|
4
|
+
title: string;
|
|
5
|
+
url: string;
|
|
6
|
+
snippet: string;
|
|
7
|
+
}>>;
|
|
8
|
+
}
|
|
9
|
+
/** Port for web fetch. Implementations inject HTTP fetch behavior and own
|
|
10
|
+
* redirect validation, response limits, and timeouts. */
|
|
11
|
+
export interface WebFetchPort {
|
|
12
|
+
fetch(url: string, signal?: AbortSignal): Promise<{
|
|
13
|
+
text: string;
|
|
14
|
+
title?: string;
|
|
15
|
+
}>;
|
|
16
|
+
}
|
|
17
|
+
/** Create a web_search tool backed by the given port. */
|
|
18
|
+
export declare function createWebSearchTool(port: WebSearchPort): {
|
|
19
|
+
name: string;
|
|
20
|
+
description: string;
|
|
21
|
+
inputSchema: {
|
|
22
|
+
readonly type: "object";
|
|
23
|
+
readonly properties: {
|
|
24
|
+
readonly query: {
|
|
25
|
+
readonly type: "string";
|
|
26
|
+
readonly description: "Search query";
|
|
27
|
+
};
|
|
28
|
+
readonly limit: {
|
|
29
|
+
readonly type: "number";
|
|
30
|
+
readonly description: "Max results";
|
|
31
|
+
readonly default: 5;
|
|
32
|
+
};
|
|
33
|
+
};
|
|
34
|
+
readonly required: readonly ["query"];
|
|
35
|
+
};
|
|
36
|
+
execute(args: Readonly<Record<string, unknown>>, signal?: AbortSignal): Promise<Readonly<Record<string, unknown>>>;
|
|
37
|
+
};
|
|
38
|
+
/** Create a web_fetch tool backed by the given port. URL safety (protocol,
|
|
39
|
+
* private-host) is enforced here; redirects/limits/timeouts belong to the
|
|
40
|
+
* injected port implementation. */
|
|
41
|
+
export declare function createWebFetchTool(port: WebFetchPort): {
|
|
42
|
+
name: string;
|
|
43
|
+
description: string;
|
|
44
|
+
inputSchema: {
|
|
45
|
+
readonly type: "object";
|
|
46
|
+
readonly properties: {
|
|
47
|
+
readonly url: {
|
|
48
|
+
readonly type: "string";
|
|
49
|
+
readonly description: "URL to fetch";
|
|
50
|
+
};
|
|
51
|
+
};
|
|
52
|
+
readonly required: readonly ["url"];
|
|
53
|
+
};
|
|
54
|
+
execute(args: Readonly<Record<string, unknown>>, signal?: AbortSignal): Promise<Readonly<Record<string, unknown>>>;
|
|
55
|
+
};
|
|
56
|
+
//# sourceMappingURL=web-ports.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"web-ports.d.ts","sourceRoot":"","sources":["../../../src/core/tools/web-ports.ts"],"names":[],"mappings":"AAEA,oEAAoE;AACpE,MAAM,WAAW,aAAa;IAC5B,MAAM,CACJ,KAAK,EAAE,MAAM,EACb,KAAK,CAAC,EAAE,MAAM,EACd,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,aAAa,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC,CAAC;CAC5E;AAED;0DAC0D;AAC1D,MAAM,WAAW,YAAY;IAC3B,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CACrF;AAED,yDAAyD;AACzD,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,aAAa;;;;;;;;;;;;;;;;;;kBAY/B,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,WAAW,WAAW;EAgB9E;AAED;;oCAEoC;AACpC,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,YAAY;;;;;;;;;;;;;kBAS7B,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,WAAW,WAAW;EAsB9E"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { assertSafeUrl } from "./url-guard.js";
|
|
2
|
+
/** Create a web_search tool backed by the given port. */
|
|
3
|
+
export function createWebSearchTool(port) {
|
|
4
|
+
return {
|
|
5
|
+
name: "web_search",
|
|
6
|
+
description: "Search the web for information.",
|
|
7
|
+
inputSchema: {
|
|
8
|
+
type: "object",
|
|
9
|
+
properties: {
|
|
10
|
+
query: { type: "string", description: "Search query" },
|
|
11
|
+
limit: { type: "number", description: "Max results", default: 5 },
|
|
12
|
+
},
|
|
13
|
+
required: ["query"],
|
|
14
|
+
},
|
|
15
|
+
async execute(args, signal) {
|
|
16
|
+
try {
|
|
17
|
+
const results = await port.search(args.query, args.limit ?? 5, signal);
|
|
18
|
+
return { results };
|
|
19
|
+
}
|
|
20
|
+
catch (err) {
|
|
21
|
+
return {
|
|
22
|
+
error: `web_search failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
23
|
+
isError: true,
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
/** Create a web_fetch tool backed by the given port. URL safety (protocol,
|
|
30
|
+
* private-host) is enforced here; redirects/limits/timeouts belong to the
|
|
31
|
+
* injected port implementation. */
|
|
32
|
+
export function createWebFetchTool(port) {
|
|
33
|
+
return {
|
|
34
|
+
name: "web_fetch",
|
|
35
|
+
description: "Fetch and read the contents of a web page.",
|
|
36
|
+
inputSchema: {
|
|
37
|
+
type: "object",
|
|
38
|
+
properties: { url: { type: "string", description: "URL to fetch" } },
|
|
39
|
+
required: ["url"],
|
|
40
|
+
},
|
|
41
|
+
async execute(args, signal) {
|
|
42
|
+
const rawUrl = args.url;
|
|
43
|
+
try {
|
|
44
|
+
assertSafeUrl(rawUrl);
|
|
45
|
+
const result = await port.fetch(rawUrl, signal);
|
|
46
|
+
return { text: result.text, title: result.title };
|
|
47
|
+
}
|
|
48
|
+
catch (err) {
|
|
49
|
+
if (err instanceof Error && err.name === "UrlGuardError") {
|
|
50
|
+
return {
|
|
51
|
+
error: err.message,
|
|
52
|
+
isError: true,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
return {
|
|
56
|
+
error: `web_fetch failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
57
|
+
isError: true,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
};
|
|
62
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/** Workspace sandbox enforces that all file access stays within an allowed root. */
|
|
2
|
+
export declare class WorkspaceSandbox {
|
|
3
|
+
readonly root: string;
|
|
4
|
+
constructor(root: string);
|
|
5
|
+
/** Validate an existing path is within the sandbox. */
|
|
6
|
+
validate(target: string): string;
|
|
7
|
+
/** Validate a new path for creation. Walks up to the nearest existing
|
|
8
|
+
* parent and checks its realpath is within root. This catches cases
|
|
9
|
+
* where an intermediate directory is a symlink pointing outside. */
|
|
10
|
+
validateNew(target: string): string;
|
|
11
|
+
/** Check a command's cwd is within the sandbox. */
|
|
12
|
+
validateCwd(cwd: string): string;
|
|
13
|
+
private resolve;
|
|
14
|
+
}
|
|
15
|
+
export declare class WorkspaceEscapeError extends Error {
|
|
16
|
+
readonly path: string;
|
|
17
|
+
readonly root: string;
|
|
18
|
+
constructor(path: string, root: string);
|
|
19
|
+
}
|
|
20
|
+
//# sourceMappingURL=workspace-sandbox.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"workspace-sandbox.d.ts","sourceRoot":"","sources":["../../../src/core/tools/workspace-sandbox.ts"],"names":[],"mappings":"AAGA,oFAAoF;AACpF,qBAAa,gBAAgB;IAC3B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;gBAEV,IAAI,EAAE,MAAM;IAIxB,uDAAuD;IACvD,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM;IAehC;;yEAEqE;IACrE,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM;IAuBnC,mDAAmD;IACnD,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM;IAShC,OAAO,CAAC,OAAO;CAKhB;AAED,qBAAa,oBAAqB,SAAQ,KAAK;IAC7C,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;gBACV,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM;CAMvC"}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { existsSync, realpathSync } from "node:fs";
|
|
2
|
+
import { isAbsolute, normalize, resolve } from "node:path";
|
|
3
|
+
/** Workspace sandbox enforces that all file access stays within an allowed root. */
|
|
4
|
+
export class WorkspaceSandbox {
|
|
5
|
+
root;
|
|
6
|
+
constructor(root) {
|
|
7
|
+
this.root = realpathSync(root);
|
|
8
|
+
}
|
|
9
|
+
/** Validate an existing path is within the sandbox. */
|
|
10
|
+
validate(target) {
|
|
11
|
+
const resolved = this.resolve(target);
|
|
12
|
+
if (!resolved.startsWith(`${this.root}/`) && resolved !== this.root) {
|
|
13
|
+
throw new WorkspaceEscapeError(target, this.root);
|
|
14
|
+
}
|
|
15
|
+
// Check realpath for existing files to catch symlink escapes
|
|
16
|
+
if (existsSync(resolved)) {
|
|
17
|
+
const real = realpathSafe(resolved);
|
|
18
|
+
if (real && !real.startsWith(`${this.root}/`) && real !== this.root) {
|
|
19
|
+
throw new WorkspaceEscapeError(target, this.root);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
return resolved;
|
|
23
|
+
}
|
|
24
|
+
/** Validate a new path for creation. Walks up to the nearest existing
|
|
25
|
+
* parent and checks its realpath is within root. This catches cases
|
|
26
|
+
* where an intermediate directory is a symlink pointing outside. */
|
|
27
|
+
validateNew(target) {
|
|
28
|
+
const resolved = this.resolve(target);
|
|
29
|
+
if (!resolved.startsWith(`${this.root}/`) && resolved !== this.root) {
|
|
30
|
+
throw new WorkspaceEscapeError(target, this.root);
|
|
31
|
+
}
|
|
32
|
+
// Walk up to find nearest existing parent
|
|
33
|
+
let checkPath = resolved;
|
|
34
|
+
const tried = [];
|
|
35
|
+
while (checkPath !== this.root && checkPath !== "/") {
|
|
36
|
+
if (existsSync(checkPath))
|
|
37
|
+
break;
|
|
38
|
+
tried.push(checkPath);
|
|
39
|
+
const idx = checkPath.lastIndexOf("/");
|
|
40
|
+
if (idx <= 0)
|
|
41
|
+
break;
|
|
42
|
+
checkPath = checkPath.slice(0, idx);
|
|
43
|
+
}
|
|
44
|
+
// Check realpath of the nearest existing ancestor
|
|
45
|
+
const real = realpathSafe(checkPath);
|
|
46
|
+
if (!real || (!real.startsWith(`${this.root}/`) && real !== this.root)) {
|
|
47
|
+
throw new WorkspaceEscapeError(target, this.root);
|
|
48
|
+
}
|
|
49
|
+
return resolved;
|
|
50
|
+
}
|
|
51
|
+
/** Check a command's cwd is within the sandbox. */
|
|
52
|
+
validateCwd(cwd) {
|
|
53
|
+
const resolved = this.resolve(cwd);
|
|
54
|
+
const real = realpathSafe(resolved);
|
|
55
|
+
if (!real || (!real.startsWith(`${this.root}/`) && real !== this.root)) {
|
|
56
|
+
throw new WorkspaceEscapeError(cwd, this.root);
|
|
57
|
+
}
|
|
58
|
+
return real;
|
|
59
|
+
}
|
|
60
|
+
resolve(target) {
|
|
61
|
+
const normalized = normalize(target);
|
|
62
|
+
if (isAbsolute(normalized))
|
|
63
|
+
return resolve(normalized);
|
|
64
|
+
return resolve(this.root, normalized);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
export class WorkspaceEscapeError extends Error {
|
|
68
|
+
path;
|
|
69
|
+
root;
|
|
70
|
+
constructor(path, root) {
|
|
71
|
+
super(`Path "${path}" escapes workspace root "${root}"`);
|
|
72
|
+
this.name = "WorkspaceEscapeError";
|
|
73
|
+
this.path = path;
|
|
74
|
+
this.root = root;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
function realpathSafe(p) {
|
|
78
|
+
try {
|
|
79
|
+
return existsSync(p) ? realpathSync(p) : null;
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/** 3.4 subagent role registry.
|
|
2
|
+
*
|
|
3
|
+
* A named subagent is either a builtin (`explore` / `worker`) or a
|
|
4
|
+
* workspace definition at `<workspaceRoot>/.oma/agents/<name>.md`:
|
|
5
|
+
*
|
|
6
|
+
* ---
|
|
7
|
+
* name: reviewer
|
|
8
|
+
* description: Reviews diffs for correctness
|
|
9
|
+
* tools: [read, grep]
|
|
10
|
+
* model: anthropic/claude-sonnet
|
|
11
|
+
* ---
|
|
12
|
+
* <body becomes the system prompt>
|
|
13
|
+
*
|
|
14
|
+
* Frontmatter is parsed by hand (line regexes, no YAML dependency) — same
|
|
15
|
+
* precedent as the oma tools skills parseFrontmatter.
|
|
16
|
+
*/
|
|
17
|
+
export interface SubagentRegistryEntry {
|
|
18
|
+
readonly name: string;
|
|
19
|
+
readonly description: string;
|
|
20
|
+
/** The role body — the executor appends the generic subagent tail. */
|
|
21
|
+
readonly systemPrompt: string;
|
|
22
|
+
/** Tool name allowlist; undefined = every executor file tool. */
|
|
23
|
+
readonly tools?: readonly string[];
|
|
24
|
+
/** Optional model override (must resolve in the runtime catalog). */
|
|
25
|
+
readonly modelId?: string;
|
|
26
|
+
}
|
|
27
|
+
/** Validate a subagent/script name: a plain label, never a path segment.
|
|
28
|
+
* Model-supplied names must not escape `.oma/agents` / `.oma/workflow`. */
|
|
29
|
+
export declare function isValidWorkflowName(name: string): boolean;
|
|
30
|
+
/** Parse `.oma/agents/*.md` — YAML-subset frontmatter with the four fields
|
|
31
|
+
* name / description / tools / model; unknown keys are ignored. */
|
|
32
|
+
export declare function parseAgentDefinition(md: string): SubagentRegistryEntry | null;
|
|
33
|
+
export declare function builtinAgentNames(): string[];
|
|
34
|
+
/** Resolve a role: builtin first, then the workspace definition. Returns
|
|
35
|
+
* null when unknown or the name is unsafe. `allowWorkspace` is false for
|
|
36
|
+
* read_only workspaces (no `.oma/agents` files). */
|
|
37
|
+
export declare function resolveAgent(name: string, readAgentDefinition: (name: string) => Promise<string | null>, opts?: {
|
|
38
|
+
allowWorkspace?: boolean;
|
|
39
|
+
}): Promise<SubagentRegistryEntry | null>;
|
|
40
|
+
//# sourceMappingURL=subagent-registry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"subagent-registry.d.ts","sourceRoot":"","sources":["../../../src/core/workflow/subagent-registry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,sEAAsE;IACtE,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,iEAAiE;IACjE,QAAQ,CAAC,KAAK,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACnC,qEAAqE;IACrE,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED;4EAC4E;AAC5E,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEzD;AA0DD;oEACoE;AACpE,wBAAgB,oBAAoB,CAAC,EAAE,EAAE,MAAM,GAAG,qBAAqB,GAAG,IAAI,CAmB7E;AAED,wBAAgB,iBAAiB,IAAI,MAAM,EAAE,CAE5C;AAED;;qDAEqD;AACrD,wBAAsB,YAAY,CAChC,IAAI,EAAE,MAAM,EACZ,mBAAmB,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,EAC7D,IAAI,GAAE;IAAE,cAAc,CAAC,EAAE,OAAO,CAAA;CAAO,GACtC,OAAO,CAAC,qBAAqB,GAAG,IAAI,CAAC,CAWvC"}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/** 3.4 subagent role registry.
|
|
2
|
+
*
|
|
3
|
+
* A named subagent is either a builtin (`explore` / `worker`) or a
|
|
4
|
+
* workspace definition at `<workspaceRoot>/.oma/agents/<name>.md`:
|
|
5
|
+
*
|
|
6
|
+
* ---
|
|
7
|
+
* name: reviewer
|
|
8
|
+
* description: Reviews diffs for correctness
|
|
9
|
+
* tools: [read, grep]
|
|
10
|
+
* model: anthropic/claude-sonnet
|
|
11
|
+
* ---
|
|
12
|
+
* <body becomes the system prompt>
|
|
13
|
+
*
|
|
14
|
+
* Frontmatter is parsed by hand (line regexes, no YAML dependency) — same
|
|
15
|
+
* precedent as the oma tools skills parseFrontmatter.
|
|
16
|
+
*/
|
|
17
|
+
/** Validate a subagent/script name: a plain label, never a path segment.
|
|
18
|
+
* Model-supplied names must not escape `.oma/agents` / `.oma/workflow`. */
|
|
19
|
+
export function isValidWorkflowName(name) {
|
|
20
|
+
return /^[a-z0-9-]{1,64}$/i.test(name);
|
|
21
|
+
}
|
|
22
|
+
/** Builtin roles, usable with no workspace files — the claude Task-family
|
|
23
|
+
* trio (explore / plan / task), with prompt structure following oh-my-pi's
|
|
24
|
+
* builtin agent definitions (scout/task): a terse role line plus
|
|
25
|
+
* <directives>/<critical> sections. `explore` and `plan` are strictly
|
|
26
|
+
* read-only; `task` gets the full executor tool set (tools undefined). */
|
|
27
|
+
const BUILTIN_AGENTS = {
|
|
28
|
+
explore: {
|
|
29
|
+
name: "explore",
|
|
30
|
+
description: "MUST be used for exploratory codebase research and broad pattern searches; read-only",
|
|
31
|
+
systemPrompt: `You are a read-only exploration subagent: investigate the workspace and return structured findings the caller can act on without re-reading everything.
|
|
32
|
+
|
|
33
|
+
<directives>
|
|
34
|
+
- You MUST use grep/glob for broad pattern searches as much as possible; then read only the needed ranges. NEVER read full files unless they are tiny.
|
|
35
|
+
- If a search returns nothing, try at least one alternate pattern before concluding the target is absent.
|
|
36
|
+
- Note the types/interfaces/key functions you touch and how files connect.
|
|
37
|
+
</directives>
|
|
38
|
+
|
|
39
|
+
<critical>
|
|
40
|
+
You MUST operate as read-only. You NEVER write, edit, or modify files, nor execute state-changing commands. You MUST keep going until the investigation is complete.
|
|
41
|
+
</critical>`,
|
|
42
|
+
tools: ["read", "grep", "glob", "tree", "read_image"],
|
|
43
|
+
},
|
|
44
|
+
plan: {
|
|
45
|
+
name: "plan",
|
|
46
|
+
description: "MUST be used for read-only implementation planning; explore the codebase and produce an execution plan",
|
|
47
|
+
systemPrompt: `You are a planning subagent: explore the codebase read-only and produce a concrete implementation plan the caller can execute.
|
|
48
|
+
|
|
49
|
+
<directives>
|
|
50
|
+
- You MUST use read/grep/glob/tree/read_image to understand the relevant code before proposing changes.
|
|
51
|
+
- Identify the files to touch, the order of changes, and any risks/dependencies. Prefer specific paths over vague areas.
|
|
52
|
+
- Keep the plan actionable: steps a worker agent can follow without re-investigating.
|
|
53
|
+
</directives>
|
|
54
|
+
|
|
55
|
+
<critical>
|
|
56
|
+
You MUST operate as read-only. You NEVER write, edit, or modify files, nor execute state-changing commands. You MUST keep going until the plan is complete.
|
|
57
|
+
</critical>`,
|
|
58
|
+
tools: ["read", "grep", "glob", "tree", "read_image"],
|
|
59
|
+
},
|
|
60
|
+
task: {
|
|
61
|
+
name: "task",
|
|
62
|
+
description: "General-purpose task worker with the full file tool set",
|
|
63
|
+
systemPrompt: `You are a general-purpose task subagent: a delegated task executor with full file tools.
|
|
64
|
+
|
|
65
|
+
<directives>
|
|
66
|
+
- MUST hyperfocus the assigned task; NEVER deviate.
|
|
67
|
+
- MUST finish only the assigned work and return the minimum useful result; do not repeat filesystem writes.
|
|
68
|
+
- MUST be concise; no filler, no repetition, no tool transcripts. Your result is notes for the caller.
|
|
69
|
+
- Prefer narrow lookups (grep/glob), then read only needed ranges.
|
|
70
|
+
- Prefer editing existing files over creating new ones; NEVER create documentation files (*.md) unless the task explicitly requests one.
|
|
71
|
+
- Persist durable results as files and report what you changed.
|
|
72
|
+
</directives>`,
|
|
73
|
+
},
|
|
74
|
+
};
|
|
75
|
+
/** Parse `.oma/agents/*.md` — YAML-subset frontmatter with the four fields
|
|
76
|
+
* name / description / tools / model; unknown keys are ignored. */
|
|
77
|
+
export function parseAgentDefinition(md) {
|
|
78
|
+
const match = /^---\n([\s\S]*?)\n---\n?([\s\S]*)$/.exec(md);
|
|
79
|
+
const frontmatter = match?.[1] ?? "";
|
|
80
|
+
const body = (match?.[2] ?? md).trim();
|
|
81
|
+
const field = (key) => new RegExp(`^${key}:\\s*(.+)$`, "m").exec(frontmatter)?.[1]?.trim();
|
|
82
|
+
const name = field("name");
|
|
83
|
+
if (!name)
|
|
84
|
+
return null; // a definition without a name is not loadable
|
|
85
|
+
const tools = field("tools")
|
|
86
|
+
?.replace(/[[\]'"\s]/g, "")
|
|
87
|
+
.split(",")
|
|
88
|
+
.filter(Boolean);
|
|
89
|
+
return {
|
|
90
|
+
name,
|
|
91
|
+
description: field("description") ?? "",
|
|
92
|
+
systemPrompt: body,
|
|
93
|
+
...(tools && tools.length > 0 ? { tools } : {}),
|
|
94
|
+
...(field("model") ? { modelId: field("model") } : {}),
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
export function builtinAgentNames() {
|
|
98
|
+
return Object.keys(BUILTIN_AGENTS);
|
|
99
|
+
}
|
|
100
|
+
/** Resolve a role: builtin first, then the workspace definition. Returns
|
|
101
|
+
* null when unknown or the name is unsafe. `allowWorkspace` is false for
|
|
102
|
+
* read_only workspaces (no `.oma/agents` files). */
|
|
103
|
+
export async function resolveAgent(name, readAgentDefinition, opts = {}) {
|
|
104
|
+
const builtin = BUILTIN_AGENTS[name];
|
|
105
|
+
if (builtin)
|
|
106
|
+
return builtin;
|
|
107
|
+
if (!isValidWorkflowName(name))
|
|
108
|
+
return null;
|
|
109
|
+
if (opts.allowWorkspace === false)
|
|
110
|
+
return null;
|
|
111
|
+
const md = await readAgentDefinition(name);
|
|
112
|
+
if (md === null)
|
|
113
|
+
return null;
|
|
114
|
+
const parsed = parseAgentDefinition(md);
|
|
115
|
+
// The file name is the identity; a mismatched frontmatter name is a
|
|
116
|
+
// hygiene hint, not an authorization boundary.
|
|
117
|
+
return parsed ? { ...parsed, name: parsed.name || name } : null;
|
|
118
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export interface WorkflowPrimitives {
|
|
2
|
+
readonly agent: (prompt: string, opts?: {
|
|
3
|
+
schema?: Readonly<Record<string, unknown>>;
|
|
4
|
+
label?: string;
|
|
5
|
+
}) => Promise<unknown>;
|
|
6
|
+
readonly pipeline: (items: readonly unknown[], fn: (item: unknown) => Promise<unknown>, opts?: {
|
|
7
|
+
concurrency?: number;
|
|
8
|
+
}) => Promise<readonly unknown[]>;
|
|
9
|
+
}
|
|
10
|
+
export interface EvaluateResult {
|
|
11
|
+
readonly value: unknown;
|
|
12
|
+
}
|
|
13
|
+
/** Sandboxed script evaluation: ONLY agent/pipeline/args are injected.
|
|
14
|
+
* require/process/fs/fetch do not exist inside the script. The vm timeout
|
|
15
|
+
* covers synchronous infinite loops; the caller races the returned promise
|
|
16
|
+
* against the script budget for async stalls. */
|
|
17
|
+
export declare function evaluateWorkflowScript(input: {
|
|
18
|
+
readonly script: string;
|
|
19
|
+
readonly args?: unknown;
|
|
20
|
+
readonly primitives: WorkflowPrimitives;
|
|
21
|
+
readonly timeoutMs?: number;
|
|
22
|
+
}): Promise<EvaluateResult>;
|
|
23
|
+
//# sourceMappingURL=workflow-evaluator.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"workflow-evaluator.d.ts","sourceRoot":"","sources":["../../../src/core/workflow/workflow-evaluator.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,KAAK,EAAE,CACd,MAAM,EAAE,MAAM,EACd,IAAI,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,KAClE,OAAO,CAAC,OAAO,CAAC,CAAC;IACtB,QAAQ,CAAC,QAAQ,EAAE,CACjB,KAAK,EAAE,SAAS,OAAO,EAAE,EACzB,EAAE,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,OAAO,CAAC,OAAO,CAAC,EACvC,IAAI,CAAC,EAAE;QAAE,WAAW,CAAC,EAAE,MAAM,CAAA;KAAE,KAC5B,OAAO,CAAC,SAAS,OAAO,EAAE,CAAC,CAAC;CAClC;AAED,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;CACzB;AAED;;;kDAGkD;AAClD,wBAAsB,sBAAsB,CAAC,KAAK,EAAE;IAClD,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,IAAI,CAAC,EAAE,OAAO,CAAC;IACxB,QAAQ,CAAC,UAAU,EAAE,kBAAkB,CAAC;IACxC,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;CAC7B,GAAG,OAAO,CAAC,cAAc,CAAC,CAkB1B"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import vm from "node:vm";
|
|
2
|
+
/** Sandboxed script evaluation: ONLY agent/pipeline/args are injected.
|
|
3
|
+
* require/process/fs/fetch do not exist inside the script. The vm timeout
|
|
4
|
+
* covers synchronous infinite loops; the caller races the returned promise
|
|
5
|
+
* against the script budget for async stalls. */
|
|
6
|
+
export async function evaluateWorkflowScript(input) {
|
|
7
|
+
const timeoutMs = input.timeoutMs ?? 60_000;
|
|
8
|
+
const { agent, pipeline } = input.primitives;
|
|
9
|
+
// codeGeneration off kills the constructor.constructor escape: no dynamic
|
|
10
|
+
// code can be minted from the sandbox (the model's bash tool is already
|
|
11
|
+
// workspace-scoped; this closes the vm's host-Function bridge).
|
|
12
|
+
const context = vm.createContext({ agent, pipeline, args: input.args }, { name: "workflow-script", codeGeneration: { strings: false, wasm: false } });
|
|
13
|
+
const wrapped = `(async () => { ${input.script} })()`;
|
|
14
|
+
const promise = vm.runInContext(wrapped, context, { timeout: timeoutMs });
|
|
15
|
+
const timer = new Promise((_, reject) => {
|
|
16
|
+
const id = setTimeout(() => reject(new Error("workflow script timed out")), timeoutMs);
|
|
17
|
+
void promise.finally(() => clearTimeout(id)).catch(() => { });
|
|
18
|
+
});
|
|
19
|
+
const value = await Promise.race([promise, timer]);
|
|
20
|
+
return { value };
|
|
21
|
+
}
|