@tangle-network/browser-agent-driver 0.24.1 → 0.25.0
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/brain/index.d.ts +5 -0
- package/dist/brain/index.d.ts.map +1 -1
- package/dist/brain/index.js +80 -32
- package/dist/brain/index.js.map +1 -1
- package/dist/cli-attach.d.ts +128 -0
- package/dist/cli-attach.d.ts.map +1 -0
- package/dist/cli-attach.js +254 -0
- package/dist/cli-attach.js.map +1 -0
- package/dist/cli-preview.d.ts +59 -0
- package/dist/cli-preview.d.ts.map +1 -0
- package/dist/cli-preview.js +144 -0
- package/dist/cli-preview.js.map +1 -0
- package/dist/cli-share.d.ts +77 -0
- package/dist/cli-share.d.ts.map +1 -0
- package/dist/cli-share.js +141 -0
- package/dist/cli-share.js.map +1 -0
- package/dist/cli-ui.d.ts.map +1 -1
- package/dist/cli-ui.js +15 -1
- package/dist/cli-ui.js.map +1 -1
- package/dist/cli.js +262 -14
- package/dist/cli.js.map +1 -1
- package/dist/drivers/cursor-overlay.d.ts +18 -10
- package/dist/drivers/cursor-overlay.d.ts.map +1 -1
- package/dist/drivers/cursor-overlay.js +221 -30
- package/dist/drivers/cursor-overlay.js.map +1 -1
- package/dist/drivers/overlay-label.d.ts +33 -0
- package/dist/drivers/overlay-label.d.ts.map +1 -0
- package/dist/drivers/overlay-label.js +92 -0
- package/dist/drivers/overlay-label.js.map +1 -0
- package/dist/drivers/playwright.d.ts +30 -0
- package/dist/drivers/playwright.d.ts.map +1 -1
- package/dist/drivers/playwright.js +204 -16
- package/dist/drivers/playwright.js.map +1 -1
- package/dist/drivers/snapshot.d.ts +12 -0
- package/dist/drivers/snapshot.d.ts.map +1 -1
- package/dist/drivers/snapshot.js +17 -0
- package/dist/drivers/snapshot.js.map +1 -1
- package/dist/drivers/types.d.ts +13 -0
- package/dist/drivers/types.d.ts.map +1 -1
- package/dist/multi-actor.d.ts.map +1 -1
- package/dist/multi-actor.js +4 -0
- package/dist/multi-actor.js.map +1 -1
- package/dist/run-state.d.ts.map +1 -1
- package/dist/run-state.js +12 -2
- package/dist/run-state.js.map +1 -1
- package/dist/runner/interrupt-controller.d.ts +67 -0
- package/dist/runner/interrupt-controller.d.ts.map +1 -0
- package/dist/runner/interrupt-controller.js +142 -0
- package/dist/runner/interrupt-controller.js.map +1 -0
- package/dist/runner/overlay-narration.d.ts +83 -0
- package/dist/runner/overlay-narration.d.ts.map +1 -0
- package/dist/runner/overlay-narration.js +172 -0
- package/dist/runner/overlay-narration.js.map +1 -0
- package/dist/runner/parallel-runner.d.ts +4 -0
- package/dist/runner/parallel-runner.d.ts.map +1 -1
- package/dist/runner/parallel-runner.js +1 -0
- package/dist/runner/parallel-runner.js.map +1 -1
- package/dist/runner/runner.d.ts +17 -0
- package/dist/runner/runner.d.ts.map +1 -1
- package/dist/runner/runner.js +72 -50
- package/dist/runner/runner.js.map +1 -1
- package/dist/runner/stream-webhook.d.ts +70 -0
- package/dist/runner/stream-webhook.d.ts.map +1 -0
- package/dist/runner/stream-webhook.js +132 -0
- package/dist/runner/stream-webhook.js.map +1 -0
- package/dist/skills/domain-loader.d.ts +78 -0
- package/dist/skills/domain-loader.d.ts.map +1 -0
- package/dist/skills/domain-loader.js +201 -0
- package/dist/skills/domain-loader.js.map +1 -0
- package/dist/skills/macro-loader.d.ts +95 -0
- package/dist/skills/macro-loader.d.ts.map +1 -0
- package/dist/skills/macro-loader.js +237 -0
- package/dist/skills/macro-loader.js.map +1 -0
- package/dist/supervisor/critic.js +1 -1
- package/dist/supervisor/policy.js +7 -0
- package/dist/supervisor/policy.js.map +1 -1
- package/dist/test-runner.d.ts +13 -0
- package/dist/test-runner.d.ts.map +1 -1
- package/dist/test-runner.js +6 -0
- package/dist/test-runner.js.map +1 -1
- package/dist/types.d.ts +19 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/viewer/viewer.html +122 -8
- package/package.json +3 -2
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Gen 32 — live event streaming to a webhook.
|
|
3
|
+
*
|
|
4
|
+
* Subscribes to the run's TurnEventBus and POSTs each event as JSON to a
|
|
5
|
+
* user-specified URL. Used by `bad run --stream <url>` so a remote
|
|
6
|
+
* dashboard (e.g., browser.tangle.tools/runs/:id) can watch the agent
|
|
7
|
+
* work in real time — no need to wait for the run to finish + upload an
|
|
8
|
+
* artifact bundle.
|
|
9
|
+
*
|
|
10
|
+
* Design:
|
|
11
|
+
* - Fire-and-forget. A slow or dead endpoint must not stall the run.
|
|
12
|
+
* - One in-flight POST at a time per URL. If events queue up faster
|
|
13
|
+
* than the endpoint drains, we batch them into an `events[]` array
|
|
14
|
+
* in the next POST body. Prevents connection starvation.
|
|
15
|
+
* - Retries are intentionally minimal (best-effort + on-connect only).
|
|
16
|
+
* A streaming endpoint is supplementary — events.jsonl is the
|
|
17
|
+
* canonical record, always written to disk.
|
|
18
|
+
* - Authentication via a shared secret header. Keeps endpoint private
|
|
19
|
+
* without requiring a full OAuth dance for a CLI.
|
|
20
|
+
*/
|
|
21
|
+
export class WebhookStreamer {
|
|
22
|
+
url;
|
|
23
|
+
authToken;
|
|
24
|
+
streamId;
|
|
25
|
+
timeoutMs;
|
|
26
|
+
onError;
|
|
27
|
+
queue = [];
|
|
28
|
+
inFlight = false;
|
|
29
|
+
closed = false;
|
|
30
|
+
unsubscribe;
|
|
31
|
+
constructor(opts) {
|
|
32
|
+
this.url = opts.url;
|
|
33
|
+
this.authToken = opts.authToken;
|
|
34
|
+
this.streamId = opts.streamId;
|
|
35
|
+
this.timeoutMs = opts.timeoutMs ?? 3000;
|
|
36
|
+
this.onError = opts.onError ?? (() => { });
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Attach to a TurnEventBus. Returns the streamer so the CLI can hold a
|
|
40
|
+
* handle for final flush() + close().
|
|
41
|
+
*/
|
|
42
|
+
attach(bus) {
|
|
43
|
+
this.unsubscribe = bus.subscribe((event) => {
|
|
44
|
+
if (this.closed)
|
|
45
|
+
return;
|
|
46
|
+
this.queue.push(event);
|
|
47
|
+
void this.tryFlush();
|
|
48
|
+
}, /* replayBuffered */ true);
|
|
49
|
+
return this;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Flush all queued events. Best-effort — timeout + drop on failure.
|
|
53
|
+
* Safe to call while in-flight (the caller is serialized via the
|
|
54
|
+
* `inFlight` flag).
|
|
55
|
+
*/
|
|
56
|
+
async tryFlush() {
|
|
57
|
+
if (this.inFlight || this.queue.length === 0)
|
|
58
|
+
return;
|
|
59
|
+
this.inFlight = true;
|
|
60
|
+
const batch = this.queue;
|
|
61
|
+
this.queue = [];
|
|
62
|
+
try {
|
|
63
|
+
await this.postBatch(batch);
|
|
64
|
+
}
|
|
65
|
+
catch (err) {
|
|
66
|
+
this.onError(err, batch.length);
|
|
67
|
+
}
|
|
68
|
+
finally {
|
|
69
|
+
this.inFlight = false;
|
|
70
|
+
// If more events arrived during the POST, drain them.
|
|
71
|
+
if (this.queue.length > 0 && !this.closed) {
|
|
72
|
+
void this.tryFlush();
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
async postBatch(batch) {
|
|
77
|
+
const headers = {
|
|
78
|
+
'Content-Type': 'application/json',
|
|
79
|
+
'X-Stream-Id': this.streamId,
|
|
80
|
+
};
|
|
81
|
+
if (this.authToken)
|
|
82
|
+
headers.Authorization = `Bearer ${this.authToken}`;
|
|
83
|
+
const controller = new AbortController();
|
|
84
|
+
const timer = setTimeout(() => controller.abort(), this.timeoutMs);
|
|
85
|
+
try {
|
|
86
|
+
const res = await fetch(this.url, {
|
|
87
|
+
method: 'POST',
|
|
88
|
+
headers,
|
|
89
|
+
body: JSON.stringify({ streamId: this.streamId, events: batch }),
|
|
90
|
+
signal: controller.signal,
|
|
91
|
+
});
|
|
92
|
+
if (!res.ok) {
|
|
93
|
+
throw new Error(`stream POST ${res.status}: ${res.statusText}`);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
finally {
|
|
97
|
+
clearTimeout(timer);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Close the streamer: unsubscribe from the bus and attempt a final
|
|
102
|
+
* flush. Safe to call multiple times.
|
|
103
|
+
*/
|
|
104
|
+
async close() {
|
|
105
|
+
if (this.closed)
|
|
106
|
+
return;
|
|
107
|
+
this.closed = true;
|
|
108
|
+
if (this.unsubscribe) {
|
|
109
|
+
this.unsubscribe();
|
|
110
|
+
this.unsubscribe = undefined;
|
|
111
|
+
}
|
|
112
|
+
// Wait for any in-flight POST to finish, then flush whatever's left.
|
|
113
|
+
const waitInFlight = async () => {
|
|
114
|
+
if (this.inFlight) {
|
|
115
|
+
await new Promise((resolve) => setTimeout(resolve, 25));
|
|
116
|
+
return waitInFlight();
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
await waitInFlight();
|
|
120
|
+
if (this.queue.length > 0) {
|
|
121
|
+
const batch = this.queue;
|
|
122
|
+
this.queue = [];
|
|
123
|
+
try {
|
|
124
|
+
await this.postBatch(batch);
|
|
125
|
+
}
|
|
126
|
+
catch (err) {
|
|
127
|
+
this.onError(err, batch.length);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
//# sourceMappingURL=stream-webhook.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stream-webhook.js","sourceRoot":"","sources":["../../src/runner/stream-webhook.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Domain-skill loader — reads `skills/domain/<host>/SKILL.md` files from
|
|
3
|
+
* the bad repo and exposes them as a `BadExtension`-compatible bundle so
|
|
4
|
+
* the existing `setExtensionRules` path at brain/index.ts:899 picks up the
|
|
5
|
+
* per-domain rules without a second injection site.
|
|
6
|
+
*
|
|
7
|
+
* Inspired by browser-use/browser-harness's `domain-skills/` layout.
|
|
8
|
+
* The differences that matter:
|
|
9
|
+
*
|
|
10
|
+
* 1. Every skill carries YAML frontmatter with `host` + optional
|
|
11
|
+
* `aliases` so one file covers www.amazon.com + smile.amazon.com.
|
|
12
|
+
* 2. The body is plain markdown — no code, no enforcement. It lands
|
|
13
|
+
* verbatim in the system prompt's USER RULES (domain match) section
|
|
14
|
+
* that already exists at brain/index.ts:865-870.
|
|
15
|
+
* 3. Skills under `skills/domain/experimental/<host>/` are loaded into
|
|
16
|
+
* the same registry but tagged so the promotion script can demote
|
|
17
|
+
* those whose bench cases don't produce a measurable win.
|
|
18
|
+
*
|
|
19
|
+
* No YAML parser dep — we handle the tiny subset we support (flat
|
|
20
|
+
* key/value + single-line array aliases) with regex. If a skill needs
|
|
21
|
+
* more expressive metadata, grow the parser with a failing test first.
|
|
22
|
+
*/
|
|
23
|
+
import type { BadExtension } from '../extensions/types.js';
|
|
24
|
+
/** Parsed frontmatter + body split, before we turn it into rules. */
|
|
25
|
+
export interface ParsedDomainSkill {
|
|
26
|
+
/** Primary hostname the skill applies to (e.g. "amazon.com") */
|
|
27
|
+
host: string;
|
|
28
|
+
/** Extra hosts that share the same rules (e.g. ["smile.amazon.com"]) */
|
|
29
|
+
aliases: string[];
|
|
30
|
+
/** Optional human-readable label */
|
|
31
|
+
title?: string;
|
|
32
|
+
/** Markdown body verbatim (no processing — goes straight into the prompt) */
|
|
33
|
+
body: string;
|
|
34
|
+
/** Absolute path to the source file, for logging and promotion flows */
|
|
35
|
+
sourcePath: string;
|
|
36
|
+
/** Whether this skill lives under experimental/ */
|
|
37
|
+
experimental: boolean;
|
|
38
|
+
}
|
|
39
|
+
/** Cap on the body size we'll inject, to keep domain rules from blowing up
|
|
40
|
+
* the system prompt. Bodies longer than this are truncated with a marker
|
|
41
|
+
* and the loader warns. 4 KB is ~1000 tokens — comfortable headroom even
|
|
42
|
+
* across multiple domain matches. */
|
|
43
|
+
export declare const MAX_DOMAIN_BODY_BYTES = 4096;
|
|
44
|
+
/**
|
|
45
|
+
* Parse a single SKILL.md file. The caller has already read the raw text.
|
|
46
|
+
* Throws on missing `host` — that's not a recoverable schema hole, we
|
|
47
|
+
* want the loader to surface it.
|
|
48
|
+
*/
|
|
49
|
+
export declare function parseDomainSkill(raw: string, sourcePath: string, experimental?: boolean): ParsedDomainSkill;
|
|
50
|
+
export interface LoadDomainSkillsOptions {
|
|
51
|
+
/** Root directory containing the domain skill tree. Defaults to the
|
|
52
|
+
* `skills/domain` dir shipped with the bad package. */
|
|
53
|
+
rootDir?: string;
|
|
54
|
+
/** Logger for load errors. Defaults to a stderr warning. */
|
|
55
|
+
onError?: (sourcePath: string, err: unknown) => void;
|
|
56
|
+
}
|
|
57
|
+
export interface LoadDomainSkillsResult {
|
|
58
|
+
skills: ParsedDomainSkill[];
|
|
59
|
+
/** Files that failed to parse. Never throws — a broken file should never
|
|
60
|
+
* bring down the run, but the operator needs to see which file it was. */
|
|
61
|
+
errors: Array<{
|
|
62
|
+
path: string;
|
|
63
|
+
error: string;
|
|
64
|
+
}>;
|
|
65
|
+
}
|
|
66
|
+
/** Locate the packaged `skills/domain` directory. Works both when running
|
|
67
|
+
* from source (tsx/test) and from the built dist/ layout. */
|
|
68
|
+
export declare function defaultDomainSkillsRoot(): string;
|
|
69
|
+
/** Walk the domain tree and return parsed skills. Missing dir ⇒ empty list,
|
|
70
|
+
* not an error: shipping without any seeded skills is a valid state. */
|
|
71
|
+
export declare function loadDomainSkills(options?: LoadDomainSkillsOptions): Promise<LoadDomainSkillsResult>;
|
|
72
|
+
/** Collapse a list of parsed skills into a BadExtension so they flow through
|
|
73
|
+
* the existing resolveExtensions → setExtensionRules path. Aliases are only
|
|
74
|
+
* emitted as additional keys when they can't already be reached by the
|
|
75
|
+
* matcher's substring comparison — otherwise the same body would be
|
|
76
|
+
* concatenated onto itself at match time. */
|
|
77
|
+
export declare function buildDomainSkillExtension(skills: ParsedDomainSkill[]): BadExtension;
|
|
78
|
+
//# sourceMappingURL=domain-loader.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"domain-loader.d.ts","sourceRoot":"","sources":["../../src/skills/domain-loader.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAKH,OAAO,KAAK,EAAE,YAAY,EAAe,MAAM,wBAAwB,CAAA;AAEvE,qEAAqE;AACrE,MAAM,WAAW,iBAAiB;IAChC,gEAAgE;IAChE,IAAI,EAAE,MAAM,CAAA;IACZ,wEAAwE;IACxE,OAAO,EAAE,MAAM,EAAE,CAAA;IACjB,oCAAoC;IACpC,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,6EAA6E;IAC7E,IAAI,EAAE,MAAM,CAAA;IACZ,wEAAwE;IACxE,UAAU,EAAE,MAAM,CAAA;IAClB,mDAAmD;IACnD,YAAY,EAAE,OAAO,CAAA;CACtB;AAED;;;qCAGqC;AACrC,eAAO,MAAM,qBAAqB,OAAO,CAAA;AAEzC;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,YAAY,UAAQ,GAAG,iBAAiB,CAyBzG;AA8CD,MAAM,WAAW,uBAAuB;IACtC;2DACuD;IACvD,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,4DAA4D;IAC5D,OAAO,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,EAAE,GAAG,EAAE,OAAO,KAAK,IAAI,CAAA;CACrD;AAED,MAAM,WAAW,sBAAsB;IACrC,MAAM,EAAE,iBAAiB,EAAE,CAAA;IAC3B;8EAC0E;IAC1E,MAAM,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;CAC/C;AAED;6DAC6D;AAC7D,wBAAgB,uBAAuB,IAAI,MAAM,CAMhD;AAED;wEACwE;AACxE,wBAAsB,gBAAgB,CAAC,OAAO,GAAE,uBAA4B,GAAG,OAAO,CAAC,sBAAsB,CAAC,CAuB7G;AAuBD;;;;6CAI6C;AAC7C,wBAAgB,yBAAyB,CAAC,MAAM,EAAE,iBAAiB,EAAE,GAAG,YAAY,CAenF"}
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Domain-skill loader — reads `skills/domain/<host>/SKILL.md` files from
|
|
3
|
+
* the bad repo and exposes them as a `BadExtension`-compatible bundle so
|
|
4
|
+
* the existing `setExtensionRules` path at brain/index.ts:899 picks up the
|
|
5
|
+
* per-domain rules without a second injection site.
|
|
6
|
+
*
|
|
7
|
+
* Inspired by browser-use/browser-harness's `domain-skills/` layout.
|
|
8
|
+
* The differences that matter:
|
|
9
|
+
*
|
|
10
|
+
* 1. Every skill carries YAML frontmatter with `host` + optional
|
|
11
|
+
* `aliases` so one file covers www.amazon.com + smile.amazon.com.
|
|
12
|
+
* 2. The body is plain markdown — no code, no enforcement. It lands
|
|
13
|
+
* verbatim in the system prompt's USER RULES (domain match) section
|
|
14
|
+
* that already exists at brain/index.ts:865-870.
|
|
15
|
+
* 3. Skills under `skills/domain/experimental/<host>/` are loaded into
|
|
16
|
+
* the same registry but tagged so the promotion script can demote
|
|
17
|
+
* those whose bench cases don't produce a measurable win.
|
|
18
|
+
*
|
|
19
|
+
* No YAML parser dep — we handle the tiny subset we support (flat
|
|
20
|
+
* key/value + single-line array aliases) with regex. If a skill needs
|
|
21
|
+
* more expressive metadata, grow the parser with a failing test first.
|
|
22
|
+
*/
|
|
23
|
+
import * as fs from 'node:fs';
|
|
24
|
+
import * as path from 'node:path';
|
|
25
|
+
import { fileURLToPath } from 'node:url';
|
|
26
|
+
/** Cap on the body size we'll inject, to keep domain rules from blowing up
|
|
27
|
+
* the system prompt. Bodies longer than this are truncated with a marker
|
|
28
|
+
* and the loader warns. 4 KB is ~1000 tokens — comfortable headroom even
|
|
29
|
+
* across multiple domain matches. */
|
|
30
|
+
export const MAX_DOMAIN_BODY_BYTES = 4096;
|
|
31
|
+
/**
|
|
32
|
+
* Parse a single SKILL.md file. The caller has already read the raw text.
|
|
33
|
+
* Throws on missing `host` — that's not a recoverable schema hole, we
|
|
34
|
+
* want the loader to surface it.
|
|
35
|
+
*/
|
|
36
|
+
export function parseDomainSkill(raw, sourcePath, experimental = false) {
|
|
37
|
+
const { frontmatter, body } = splitFrontmatter(raw);
|
|
38
|
+
const meta = parseFrontmatterBlock(frontmatter);
|
|
39
|
+
const host = typeof meta.host === 'string' ? meta.host.trim() : '';
|
|
40
|
+
if (!host) {
|
|
41
|
+
throw new Error(`${sourcePath}: frontmatter missing required "host" field`);
|
|
42
|
+
}
|
|
43
|
+
const aliases = Array.isArray(meta.aliases)
|
|
44
|
+
? meta.aliases.map(String).map((s) => s.trim()).filter(Boolean)
|
|
45
|
+
: [];
|
|
46
|
+
const title = typeof meta.title === 'string' ? meta.title.trim() : undefined;
|
|
47
|
+
let trimmedBody = body.trim();
|
|
48
|
+
if (Buffer.byteLength(trimmedBody, 'utf-8') > MAX_DOMAIN_BODY_BYTES) {
|
|
49
|
+
trimmedBody = trimmedBody.slice(0, MAX_DOMAIN_BODY_BYTES).trimEnd() +
|
|
50
|
+
'\n\n[…truncated — domain skill body exceeded MAX_DOMAIN_BODY_BYTES]';
|
|
51
|
+
}
|
|
52
|
+
return {
|
|
53
|
+
host,
|
|
54
|
+
aliases,
|
|
55
|
+
title,
|
|
56
|
+
body: trimmedBody,
|
|
57
|
+
sourcePath,
|
|
58
|
+
experimental,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
/** Split `---\nfrontmatter\n---\nbody` into its two parts. Files without
|
|
62
|
+
* frontmatter (missing opening `---\n`) are treated as body-only. The
|
|
63
|
+
* strict `---\n` match avoids misinterpreting a body that happens to
|
|
64
|
+
* start with a markdown horizontal rule. */
|
|
65
|
+
function splitFrontmatter(raw) {
|
|
66
|
+
if (!raw.startsWith('---\n') && !raw.startsWith('---\r\n'))
|
|
67
|
+
return { frontmatter: '', body: raw };
|
|
68
|
+
const afterOpenDelimiter = raw.startsWith('---\r\n') ? 5 : 4;
|
|
69
|
+
// Closing delimiter must be on its own line (\n---\n or \n---\r\n or EOF).
|
|
70
|
+
const bodyStart = raw.indexOf('\n---\n', afterOpenDelimiter);
|
|
71
|
+
const altStart = raw.indexOf('\n---\r\n', afterOpenDelimiter);
|
|
72
|
+
const endIndex = altStart !== -1 && (bodyStart === -1 || altStart < bodyStart) ? altStart : bodyStart;
|
|
73
|
+
if (endIndex === -1)
|
|
74
|
+
return { frontmatter: '', body: raw };
|
|
75
|
+
const frontmatter = raw.slice(afterOpenDelimiter, endIndex).trim();
|
|
76
|
+
const bodyOffset = raw.indexOf('\n', endIndex + 4); // skip past the closing --- and its newline
|
|
77
|
+
const body = bodyOffset >= 0 ? raw.slice(bodyOffset + 1) : '';
|
|
78
|
+
return { frontmatter, body };
|
|
79
|
+
}
|
|
80
|
+
/** Tiny YAML subset: flat scalars + single-line [a, b] arrays. Quoted and
|
|
81
|
+
* unquoted strings both work; inline comments (`# …`) are ignored. */
|
|
82
|
+
function parseFrontmatterBlock(block) {
|
|
83
|
+
const out = {};
|
|
84
|
+
if (!block)
|
|
85
|
+
return out;
|
|
86
|
+
for (const line of block.split('\n')) {
|
|
87
|
+
const trimmed = line.trim();
|
|
88
|
+
if (!trimmed || trimmed.startsWith('#'))
|
|
89
|
+
continue;
|
|
90
|
+
const match = trimmed.match(/^([A-Za-z0-9_-]+)\s*:\s*(.*)$/);
|
|
91
|
+
if (!match)
|
|
92
|
+
continue;
|
|
93
|
+
const key = match[1];
|
|
94
|
+
let rawVal = match[2].replace(/\s+#.*$/, '').trim();
|
|
95
|
+
if (rawVal.startsWith('[') && rawVal.endsWith(']')) {
|
|
96
|
+
out[key] = rawVal.slice(1, -1)
|
|
97
|
+
.split(',')
|
|
98
|
+
.map((s) => s.trim().replace(/^['"]|['"]$/g, ''))
|
|
99
|
+
.filter(Boolean);
|
|
100
|
+
}
|
|
101
|
+
else if ((rawVal.startsWith('"') && rawVal.endsWith('"')) || (rawVal.startsWith("'") && rawVal.endsWith("'"))) {
|
|
102
|
+
out[key] = rawVal.slice(1, -1);
|
|
103
|
+
}
|
|
104
|
+
else {
|
|
105
|
+
out[key] = rawVal;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return out;
|
|
109
|
+
}
|
|
110
|
+
/** Locate the packaged `skills/domain` directory. Works both when running
|
|
111
|
+
* from source (tsx/test) and from the built dist/ layout. */
|
|
112
|
+
export function defaultDomainSkillsRoot() {
|
|
113
|
+
const here = fileURLToPath(import.meta.url);
|
|
114
|
+
// src layout: src/skills/domain-loader.ts → <repo>/skills/domain
|
|
115
|
+
// dist layout: dist/skills/domain-loader.js → <repo>/skills/domain
|
|
116
|
+
const packageRoot = path.resolve(path.dirname(here), '..', '..');
|
|
117
|
+
return path.join(packageRoot, 'skills', 'domain');
|
|
118
|
+
}
|
|
119
|
+
/** Walk the domain tree and return parsed skills. Missing dir ⇒ empty list,
|
|
120
|
+
* not an error: shipping without any seeded skills is a valid state. */
|
|
121
|
+
export async function loadDomainSkills(options = {}) {
|
|
122
|
+
const rootDir = options.rootDir ?? defaultDomainSkillsRoot();
|
|
123
|
+
const onError = options.onError ?? ((p, err) => {
|
|
124
|
+
// eslint-disable-next-line no-console
|
|
125
|
+
console.error(`[domain-skill] failed to load ${p}: ${err instanceof Error ? err.message : String(err)}`);
|
|
126
|
+
});
|
|
127
|
+
if (!fs.existsSync(rootDir))
|
|
128
|
+
return { skills: [], errors: [] };
|
|
129
|
+
const skills = [];
|
|
130
|
+
const errors = [];
|
|
131
|
+
for (const candidate of walkSkillMarkdown(rootDir)) {
|
|
132
|
+
try {
|
|
133
|
+
const raw = fs.readFileSync(candidate.path, 'utf-8');
|
|
134
|
+
const parsed = parseDomainSkill(raw, candidate.path, candidate.experimental);
|
|
135
|
+
skills.push(parsed);
|
|
136
|
+
}
|
|
137
|
+
catch (err) {
|
|
138
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
139
|
+
errors.push({ path: candidate.path, error: message });
|
|
140
|
+
onError(candidate.path, err);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
return { skills, errors };
|
|
144
|
+
}
|
|
145
|
+
/** Enumerate <root>/<host>/SKILL.md and <root>/experimental/<host>/SKILL.md.
|
|
146
|
+
* Any other files or directory depths are ignored — the layout is strict on
|
|
147
|
+
* purpose so we don't accidentally pick up READMEs or notes. */
|
|
148
|
+
function* walkSkillMarkdown(rootDir) {
|
|
149
|
+
const entries = fs.readdirSync(rootDir, { withFileTypes: true });
|
|
150
|
+
for (const entry of entries) {
|
|
151
|
+
if (!entry.isDirectory())
|
|
152
|
+
continue;
|
|
153
|
+
const entryPath = path.join(rootDir, entry.name);
|
|
154
|
+
if (entry.name === 'experimental') {
|
|
155
|
+
for (const inner of fs.readdirSync(entryPath, { withFileTypes: true })) {
|
|
156
|
+
if (!inner.isDirectory())
|
|
157
|
+
continue;
|
|
158
|
+
const skillPath = path.join(entryPath, inner.name, 'SKILL.md');
|
|
159
|
+
if (fs.existsSync(skillPath))
|
|
160
|
+
yield { path: skillPath, experimental: true };
|
|
161
|
+
}
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
164
|
+
const skillPath = path.join(entryPath, 'SKILL.md');
|
|
165
|
+
if (fs.existsSync(skillPath))
|
|
166
|
+
yield { path: skillPath, experimental: false };
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
/** Collapse a list of parsed skills into a BadExtension so they flow through
|
|
170
|
+
* the existing resolveExtensions → setExtensionRules path. Aliases are only
|
|
171
|
+
* emitted as additional keys when they can't already be reached by the
|
|
172
|
+
* matcher's substring comparison — otherwise the same body would be
|
|
173
|
+
* concatenated onto itself at match time. */
|
|
174
|
+
export function buildDomainSkillExtension(skills) {
|
|
175
|
+
const addRulesForDomain = {};
|
|
176
|
+
for (const skill of skills) {
|
|
177
|
+
const body = skill.body;
|
|
178
|
+
addBody(addRulesForDomain, skill.host, body);
|
|
179
|
+
for (const alias of skill.aliases) {
|
|
180
|
+
// Skip aliases that already match via substring on the primary or any
|
|
181
|
+
// prior alias — registering them would double-emit the body when the
|
|
182
|
+
// brain matcher walks every registered key.
|
|
183
|
+
if (alias.includes(skill.host))
|
|
184
|
+
continue;
|
|
185
|
+
if (skill.aliases.some((other) => other !== alias && alias.includes(other)))
|
|
186
|
+
continue;
|
|
187
|
+
addBody(addRulesForDomain, alias, body);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
return { addRulesForDomain };
|
|
191
|
+
}
|
|
192
|
+
function addBody(map, host, body) {
|
|
193
|
+
const existing = map[host];
|
|
194
|
+
if (existing && existing.extraRules) {
|
|
195
|
+
existing.extraRules = `${existing.extraRules}\n\n${body}`;
|
|
196
|
+
}
|
|
197
|
+
else {
|
|
198
|
+
map[host] = { extraRules: body };
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
//# sourceMappingURL=domain-loader.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"domain-loader.js","sourceRoot":"","sources":["../../src/skills/domain-loader.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Macro loader and executor. Reads `skills/macros/*.json` files and validates
|
|
3
|
+
* each into a typed MacroDefinition. A macro is a flat ordered list of
|
|
4
|
+
* existing primitive Actions with string-template interpolation over its
|
|
5
|
+
* declared params.
|
|
6
|
+
*
|
|
7
|
+
* This is the minimum viable mutable tool surface: the agent can add new
|
|
8
|
+
* capability by composing safe primitives into named workflows, without us
|
|
9
|
+
* eval-ing arbitrary code. Raw TS/JS handlers are a later generation's fight.
|
|
10
|
+
*
|
|
11
|
+
* Safety invariants (all enforced at load time, tested):
|
|
12
|
+
* 1. Step types are restricted to SAFE_MACRO_STEP_TYPES. No `macro` steps
|
|
13
|
+
* (flat only). No `navigate` — macros are page-interaction primitives,
|
|
14
|
+
* not navigation helpers (macros get triggered when the agent is
|
|
15
|
+
* already somewhere).
|
|
16
|
+
* 2. Param substitution only applies to string fields. Objects and numbers
|
|
17
|
+
* pass through as-is.
|
|
18
|
+
* 3. Unknown params in steps (e.g. `${oops}`) are a load-time error.
|
|
19
|
+
* 4. Duplicate macro names error at registration.
|
|
20
|
+
*/
|
|
21
|
+
import type { Action } from '../types.js';
|
|
22
|
+
/**
|
|
23
|
+
* A step inside a macro is any safe primitive action. We use Action
|
|
24
|
+
* directly — the loader validates that `step.action` is in SAFE_MACRO_STEP_TYPES
|
|
25
|
+
* at load time. Strings in these actions can carry `${param}` placeholders
|
|
26
|
+
* that are substituted from the caller's args.
|
|
27
|
+
*/
|
|
28
|
+
export type MacroStep = Action;
|
|
29
|
+
/** Primitive actions that are safe inside a macro. Explicitly whitelists —
|
|
30
|
+
* any new Action type needs to be added here deliberately. Note that
|
|
31
|
+
* `navigate` and `complete`/`abort` are intentionally absent: macros
|
|
32
|
+
* compose local interactions, not run-level control flow. */
|
|
33
|
+
/** Maximum steps in a single macro. Bounds the total wall-time any one
|
|
34
|
+
* macro can consume (step count × per-action timeout) to keep the
|
|
35
|
+
* CLI's budget cap meaningful. Raising this requires a measured reason —
|
|
36
|
+
* a longer macro is usually a hint that the work should split into two.
|
|
37
|
+
*/
|
|
38
|
+
export declare const MAX_MACRO_STEPS = 8;
|
|
39
|
+
export declare const SAFE_MACRO_STEP_TYPES: Set<"select" | "fill" | "type" | "click" | "scroll" | "navigate" | "wait" | "complete" | "press" | "hover" | "evaluate" | "runScript" | "extractWithIndex" | "verifyPreview" | "abort" | "clickSequence" | "clickAt" | "typeAt" | "clickLabel" | "typeLabel" | "macro">;
|
|
40
|
+
export interface MacroParamSpec {
|
|
41
|
+
name: string;
|
|
42
|
+
description?: string;
|
|
43
|
+
/** Minimum required; if absent the macro can be invoked without this arg */
|
|
44
|
+
required?: boolean;
|
|
45
|
+
}
|
|
46
|
+
export interface MacroDefinition {
|
|
47
|
+
name: string;
|
|
48
|
+
description: string;
|
|
49
|
+
/** Declared parameters. Empty array if the macro takes no args. */
|
|
50
|
+
params: MacroParamSpec[];
|
|
51
|
+
/** Ordered, flat list of primitive actions */
|
|
52
|
+
steps: MacroStep[];
|
|
53
|
+
/** Where on disk this came from (for diagnostics) */
|
|
54
|
+
sourcePath: string;
|
|
55
|
+
/** If true, macro is in staging and is NOT exposed in the agent's prompt
|
|
56
|
+
* until it passes eval-gated promotion. */
|
|
57
|
+
experimental?: boolean;
|
|
58
|
+
}
|
|
59
|
+
export interface MacroRegistry {
|
|
60
|
+
macros: Map<string, MacroDefinition>;
|
|
61
|
+
/** The same list rendered for the LLM's system prompt */
|
|
62
|
+
promptBlock: string;
|
|
63
|
+
}
|
|
64
|
+
/** Exported for tests + promotion script to share the same root resolution.
|
|
65
|
+
* BAD_MACROS_DIR overrides the packaged path — the promotion script uses this
|
|
66
|
+
* to point at a staging directory without mutating the canonical tree. */
|
|
67
|
+
export declare function defaultMacrosRoot(): string;
|
|
68
|
+
export interface LoadMacrosOptions {
|
|
69
|
+
rootDir?: string;
|
|
70
|
+
/** Include experimental/ subdir (for the promotion script only). */
|
|
71
|
+
includeExperimental?: boolean;
|
|
72
|
+
onError?: (p: string, err: unknown) => void;
|
|
73
|
+
}
|
|
74
|
+
export interface LoadMacrosResult {
|
|
75
|
+
macros: MacroDefinition[];
|
|
76
|
+
errors: Array<{
|
|
77
|
+
path: string;
|
|
78
|
+
error: string;
|
|
79
|
+
}>;
|
|
80
|
+
}
|
|
81
|
+
export declare function loadMacros(options?: LoadMacrosOptions): Promise<LoadMacrosResult>;
|
|
82
|
+
export declare function validateMacroDefinition(raw: unknown, sourcePath: string, experimental: boolean): MacroDefinition;
|
|
83
|
+
/** Render the loaded macros into the system-prompt snippet. Called once at
|
|
84
|
+
* runner boot and cached in the brain. Returns empty string if no macros. */
|
|
85
|
+
export declare function renderMacroPromptBlock(macros: MacroDefinition[]): string;
|
|
86
|
+
/** Substitute `${param}` placeholders in string-typed fields of a step,
|
|
87
|
+
* using the provided args. Returns a new object — does not mutate input.
|
|
88
|
+
* Unknown placeholders are collected so the dispatcher can fail fast
|
|
89
|
+
* instead of silently typing a literal `${missing}` into a form. */
|
|
90
|
+
export declare function interpolateStep(step: MacroStep, args: Record<string, string>): {
|
|
91
|
+
step: MacroStep;
|
|
92
|
+
unresolved: string[];
|
|
93
|
+
};
|
|
94
|
+
export declare function buildMacroRegistry(macros: MacroDefinition[]): MacroRegistry;
|
|
95
|
+
//# sourceMappingURL=macro-loader.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"macro-loader.d.ts","sourceRoot":"","sources":["../../src/skills/macro-loader.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAKH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AAEzC;;;;;GAKG;AACH,MAAM,MAAM,SAAS,GAAG,MAAM,CAAA;AAE9B;;;6DAG6D;AAC7D;;;;GAIG;AACH,eAAO,MAAM,eAAe,IAAI,CAAA;AAEhC,eAAO,MAAM,qBAAqB,yQAchC,CAAA;AAEF,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAA;IACZ,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,4EAA4E;IAC5E,QAAQ,CAAC,EAAE,OAAO,CAAA;CACnB;AAED,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAA;IACZ,WAAW,EAAE,MAAM,CAAA;IACnB,mEAAmE;IACnE,MAAM,EAAE,cAAc,EAAE,CAAA;IACxB,8CAA8C;IAC9C,KAAK,EAAE,SAAS,EAAE,CAAA;IAClB,qDAAqD;IACrD,UAAU,EAAE,MAAM,CAAA;IAClB;gDAC4C;IAC5C,YAAY,CAAC,EAAE,OAAO,CAAA;CACvB;AAED,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,eAAe,CAAC,CAAA;IACpC,yDAAyD;IACzD,WAAW,EAAE,MAAM,CAAA;CACpB;AAED;;0EAE0E;AAC1E,wBAAgB,iBAAiB,IAAI,MAAM,CAQ1C;AAED,MAAM,WAAW,iBAAiB;IAChC,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,oEAAoE;IACpE,mBAAmB,CAAC,EAAE,OAAO,CAAA;IAC7B,OAAO,CAAC,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,GAAG,EAAE,OAAO,KAAK,IAAI,CAAA;CAC5C;AAED,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,eAAe,EAAE,CAAA;IACzB,MAAM,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;CAC/C;AAED,wBAAsB,UAAU,CAAC,OAAO,GAAE,iBAAsB,GAAG,OAAO,CAAC,gBAAgB,CAAC,CA6B3F;AAmBD,wBAAgB,uBAAuB,CAAC,GAAG,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,EAAE,YAAY,EAAE,OAAO,GAAG,eAAe,CAgEhH;AAqBD;6EAC6E;AAC7E,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,eAAe,EAAE,GAAG,MAAM,CAaxE;AAED;;;oEAGoE;AACpE,wBAAgB,eAAe,CAC7B,IAAI,EAAE,SAAS,EACf,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAC3B;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,UAAU,EAAE,MAAM,EAAE,CAAA;CAAE,CAc3C;AAED,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,eAAe,EAAE,GAAG,aAAa,CAO3E"}
|