@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.
Files changed (84) hide show
  1. package/dist/brain/index.d.ts +5 -0
  2. package/dist/brain/index.d.ts.map +1 -1
  3. package/dist/brain/index.js +80 -32
  4. package/dist/brain/index.js.map +1 -1
  5. package/dist/cli-attach.d.ts +128 -0
  6. package/dist/cli-attach.d.ts.map +1 -0
  7. package/dist/cli-attach.js +254 -0
  8. package/dist/cli-attach.js.map +1 -0
  9. package/dist/cli-preview.d.ts +59 -0
  10. package/dist/cli-preview.d.ts.map +1 -0
  11. package/dist/cli-preview.js +144 -0
  12. package/dist/cli-preview.js.map +1 -0
  13. package/dist/cli-share.d.ts +77 -0
  14. package/dist/cli-share.d.ts.map +1 -0
  15. package/dist/cli-share.js +141 -0
  16. package/dist/cli-share.js.map +1 -0
  17. package/dist/cli-ui.d.ts.map +1 -1
  18. package/dist/cli-ui.js +15 -1
  19. package/dist/cli-ui.js.map +1 -1
  20. package/dist/cli.js +262 -14
  21. package/dist/cli.js.map +1 -1
  22. package/dist/drivers/cursor-overlay.d.ts +18 -10
  23. package/dist/drivers/cursor-overlay.d.ts.map +1 -1
  24. package/dist/drivers/cursor-overlay.js +221 -30
  25. package/dist/drivers/cursor-overlay.js.map +1 -1
  26. package/dist/drivers/overlay-label.d.ts +33 -0
  27. package/dist/drivers/overlay-label.d.ts.map +1 -0
  28. package/dist/drivers/overlay-label.js +92 -0
  29. package/dist/drivers/overlay-label.js.map +1 -0
  30. package/dist/drivers/playwright.d.ts +30 -0
  31. package/dist/drivers/playwright.d.ts.map +1 -1
  32. package/dist/drivers/playwright.js +204 -16
  33. package/dist/drivers/playwright.js.map +1 -1
  34. package/dist/drivers/snapshot.d.ts +12 -0
  35. package/dist/drivers/snapshot.d.ts.map +1 -1
  36. package/dist/drivers/snapshot.js +17 -0
  37. package/dist/drivers/snapshot.js.map +1 -1
  38. package/dist/drivers/types.d.ts +13 -0
  39. package/dist/drivers/types.d.ts.map +1 -1
  40. package/dist/multi-actor.d.ts.map +1 -1
  41. package/dist/multi-actor.js +4 -0
  42. package/dist/multi-actor.js.map +1 -1
  43. package/dist/run-state.d.ts.map +1 -1
  44. package/dist/run-state.js +12 -2
  45. package/dist/run-state.js.map +1 -1
  46. package/dist/runner/interrupt-controller.d.ts +67 -0
  47. package/dist/runner/interrupt-controller.d.ts.map +1 -0
  48. package/dist/runner/interrupt-controller.js +142 -0
  49. package/dist/runner/interrupt-controller.js.map +1 -0
  50. package/dist/runner/overlay-narration.d.ts +83 -0
  51. package/dist/runner/overlay-narration.d.ts.map +1 -0
  52. package/dist/runner/overlay-narration.js +172 -0
  53. package/dist/runner/overlay-narration.js.map +1 -0
  54. package/dist/runner/parallel-runner.d.ts +4 -0
  55. package/dist/runner/parallel-runner.d.ts.map +1 -1
  56. package/dist/runner/parallel-runner.js +1 -0
  57. package/dist/runner/parallel-runner.js.map +1 -1
  58. package/dist/runner/runner.d.ts +17 -0
  59. package/dist/runner/runner.d.ts.map +1 -1
  60. package/dist/runner/runner.js +72 -50
  61. package/dist/runner/runner.js.map +1 -1
  62. package/dist/runner/stream-webhook.d.ts +70 -0
  63. package/dist/runner/stream-webhook.d.ts.map +1 -0
  64. package/dist/runner/stream-webhook.js +132 -0
  65. package/dist/runner/stream-webhook.js.map +1 -0
  66. package/dist/skills/domain-loader.d.ts +78 -0
  67. package/dist/skills/domain-loader.d.ts.map +1 -0
  68. package/dist/skills/domain-loader.js +201 -0
  69. package/dist/skills/domain-loader.js.map +1 -0
  70. package/dist/skills/macro-loader.d.ts +95 -0
  71. package/dist/skills/macro-loader.d.ts.map +1 -0
  72. package/dist/skills/macro-loader.js +237 -0
  73. package/dist/skills/macro-loader.js.map +1 -0
  74. package/dist/supervisor/critic.js +1 -1
  75. package/dist/supervisor/policy.js +7 -0
  76. package/dist/supervisor/policy.js.map +1 -1
  77. package/dist/test-runner.d.ts +13 -0
  78. package/dist/test-runner.d.ts.map +1 -1
  79. package/dist/test-runner.js +6 -0
  80. package/dist/test-runner.js.map +1 -1
  81. package/dist/types.d.ts +19 -1
  82. package/dist/types.d.ts.map +1 -1
  83. package/dist/viewer/viewer.html +122 -8
  84. 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"}