loop-sdk 0.1.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 (62) hide show
  1. package/README.md +591 -0
  2. package/dist/agent.d.ts +31 -0
  3. package/dist/agent.d.ts.map +1 -0
  4. package/dist/agent.js +48 -0
  5. package/dist/agent.js.map +1 -0
  6. package/dist/checkpoint.d.ts +16 -0
  7. package/dist/checkpoint.d.ts.map +1 -0
  8. package/dist/checkpoint.js +20 -0
  9. package/dist/checkpoint.js.map +1 -0
  10. package/dist/claude-cli.d.ts +35 -0
  11. package/dist/claude-cli.d.ts.map +1 -0
  12. package/dist/claude-cli.js +135 -0
  13. package/dist/claude-cli.js.map +1 -0
  14. package/dist/context.d.ts +53 -0
  15. package/dist/context.d.ts.map +1 -0
  16. package/dist/context.js +95 -0
  17. package/dist/context.js.map +1 -0
  18. package/dist/events.d.ts +111 -0
  19. package/dist/events.d.ts.map +1 -0
  20. package/dist/events.js +53 -0
  21. package/dist/events.js.map +1 -0
  22. package/dist/flow.d.ts +36 -0
  23. package/dist/flow.d.ts.map +1 -0
  24. package/dist/flow.js +62 -0
  25. package/dist/flow.js.map +1 -0
  26. package/dist/index.d.ts +26 -0
  27. package/dist/index.d.ts.map +1 -0
  28. package/dist/index.js +14 -0
  29. package/dist/index.js.map +1 -0
  30. package/dist/logger.d.ts +37 -0
  31. package/dist/logger.d.ts.map +1 -0
  32. package/dist/logger.js +64 -0
  33. package/dist/logger.js.map +1 -0
  34. package/dist/loop.d.ts +199 -0
  35. package/dist/loop.d.ts.map +1 -0
  36. package/dist/loop.js +403 -0
  37. package/dist/loop.js.map +1 -0
  38. package/dist/loopfile.d.ts +82 -0
  39. package/dist/loopfile.d.ts.map +1 -0
  40. package/dist/loopfile.js +235 -0
  41. package/dist/loopfile.js.map +1 -0
  42. package/dist/mcp/server.d.ts +26 -0
  43. package/dist/mcp/server.d.ts.map +1 -0
  44. package/dist/mcp/server.js +160 -0
  45. package/dist/mcp/server.js.map +1 -0
  46. package/dist/notify.d.ts +43 -0
  47. package/dist/notify.d.ts.map +1 -0
  48. package/dist/notify.js +68 -0
  49. package/dist/notify.js.map +1 -0
  50. package/dist/plugins/retry.d.ts +41 -0
  51. package/dist/plugins/retry.d.ts.map +1 -0
  52. package/dist/plugins/retry.js +53 -0
  53. package/dist/plugins/retry.js.map +1 -0
  54. package/dist/providers/playwright.d.ts +38 -0
  55. package/dist/providers/playwright.d.ts.map +1 -0
  56. package/dist/providers/playwright.js +155 -0
  57. package/dist/providers/playwright.js.map +1 -0
  58. package/dist/session.d.ts +43 -0
  59. package/dist/session.d.ts.map +1 -0
  60. package/dist/session.js +26 -0
  61. package/dist/session.js.map +1 -0
  62. package/package.json +78 -0
@@ -0,0 +1,20 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ export function writeCheckpoint(file, data) {
4
+ fs.mkdirSync(path.dirname(path.resolve(file)), { recursive: true });
5
+ fs.writeFileSync(file, JSON.stringify(data, null, 2));
6
+ }
7
+ export function readCheckpoint(file) {
8
+ const raw = fs.readFileSync(file, 'utf8');
9
+ return JSON.parse(raw);
10
+ }
11
+ export function checkpointExists(file) {
12
+ return fs.existsSync(file);
13
+ }
14
+ export function deleteCheckpoint(file) {
15
+ try {
16
+ fs.unlinkSync(file);
17
+ }
18
+ catch { }
19
+ }
20
+ //# sourceMappingURL=checkpoint.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"checkpoint.js","sourceRoot":"","sources":["../src/checkpoint.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,SAAS,CAAA;AACxB,OAAO,IAAI,MAAM,WAAW,CAAA;AAc5B,MAAM,UAAU,eAAe,CAAC,IAAY,EAAE,IAAgB;IAC5D,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAA;IACnE,EAAE,CAAC,aAAa,CAAC,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAA;AACvD,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,IAAY;IACzC,MAAM,GAAG,GAAG,EAAE,CAAC,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAA;IACzC,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAe,CAAA;AACtC,CAAC;AAED,MAAM,UAAU,gBAAgB,CAAC,IAAY;IAC3C,OAAO,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,CAAA;AAC5B,CAAC;AAED,MAAM,UAAU,gBAAgB,CAAC,IAAY;IAC3C,IAAI,CAAC;QAAC,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,CAAA;IAAC,CAAC;IAAC,MAAM,CAAC,CAAA,CAAC;AACtC,CAAC"}
@@ -0,0 +1,35 @@
1
+ import type { Context } from './context.js';
2
+ export interface ClaudeCliOptions {
3
+ /** Claude model name, passed as --model. Defaults to the CLI's configured default. */
4
+ model?: string;
5
+ /** Attach a screenshot of the current browser state before invoking claude. */
6
+ screenshot?: boolean;
7
+ /** Restrict the AI to specific MCP tool names. */
8
+ tools?: string[];
9
+ /** Subprocess timeout in milliseconds. Default: 240000 (4 min). */
10
+ timeout?: number;
11
+ /** Number of retry attempts on network errors. Default: 3. */
12
+ retries?: number;
13
+ }
14
+ export interface ClaudeCliResult {
15
+ ok: true;
16
+ output: string;
17
+ }
18
+ /**
19
+ * claudeCli — spawn `claude -p` as a loop step.
20
+ *
21
+ * Unlike agent(), which calls a provider directly, claudeCli() delegates the
22
+ * entire tool-use loop to the Claude Code CLI subprocess. Use this when you
23
+ * want Claude's built-in retry logic, permission model, and MCP handling.
24
+ *
25
+ * Requires `claude` to be installed and on PATH.
26
+ * If ctx.session.mcpUrl is set, claude gets browser tool access via --mcp-config.
27
+ *
28
+ * @example
29
+ * await claudeCli(ctx, 'Fill out the visible form fields.', {
30
+ * screenshot: true,
31
+ * model: 'claude-opus-4-8',
32
+ * })
33
+ */
34
+ export declare function claudeCli(ctx: Context, prompt: string, { model, screenshot, tools, timeout, retries, }?: ClaudeCliOptions): Promise<ClaudeCliResult>;
35
+ //# sourceMappingURL=claude-cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"claude-cli.d.ts","sourceRoot":"","sources":["../src/claude-cli.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAA;AAE3C,MAAM,WAAW,gBAAgB;IAC/B,sFAAsF;IACtF,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,+EAA+E;IAC/E,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,kDAAkD;IAClD,KAAK,CAAC,EAAE,MAAM,EAAE,CAAA;IAChB,mEAAmE;IACnE,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,8DAA8D;IAC9D,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB;AAED,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,IAAI,CAAA;IACR,MAAM,EAAE,MAAM,CAAA;CACf;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,SAAS,CAC7B,GAAG,EAAE,OAAO,EACZ,MAAM,EAAE,MAAM,EACd,EACE,KAAK,EACL,UAAkB,EAClB,KAAU,EACV,OAAiB,EACjB,OAAW,GACZ,GAAE,gBAAqB,GACvB,OAAO,CAAC,eAAe,CAAC,CAyE1B"}
@@ -0,0 +1,135 @@
1
+ import { spawn } from 'node:child_process';
2
+ import { writeFileSync } from 'node:fs';
3
+ import { join } from 'node:path';
4
+ import { tmpdir } from 'node:os';
5
+ /**
6
+ * claudeCli — spawn `claude -p` as a loop step.
7
+ *
8
+ * Unlike agent(), which calls a provider directly, claudeCli() delegates the
9
+ * entire tool-use loop to the Claude Code CLI subprocess. Use this when you
10
+ * want Claude's built-in retry logic, permission model, and MCP handling.
11
+ *
12
+ * Requires `claude` to be installed and on PATH.
13
+ * If ctx.session.mcpUrl is set, claude gets browser tool access via --mcp-config.
14
+ *
15
+ * @example
16
+ * await claudeCli(ctx, 'Fill out the visible form fields.', {
17
+ * screenshot: true,
18
+ * model: 'claude-opus-4-8',
19
+ * })
20
+ */
21
+ export async function claudeCli(ctx, prompt, { model, screenshot = false, tools = [], timeout = 240_000, retries = 3, } = {}) {
22
+ const finalPrompt = `${prompt}
23
+
24
+ At the very end of your response, on its own line, write exactly one of:
25
+ RESULT: ok
26
+ RESULT: failed — <brief reason>
27
+
28
+ Use "failed" if you could not complete the task.`;
29
+ const args = ['-p', finalPrompt, '--dangerously-skip-permissions'];
30
+ if (model)
31
+ args.push('--model', model);
32
+ if (tools.length)
33
+ args.push('--allowedTools', ...tools);
34
+ if (ctx.session?.mcpUrl) {
35
+ const mcpConfig = {
36
+ mcpServers: {
37
+ browser: { type: 'http', url: ctx.session.mcpUrl },
38
+ },
39
+ };
40
+ const configFile = join(tmpdir(), `loop-mcp-${ctx.session.id}-${Date.now()}.json`);
41
+ writeFileSync(configFile, JSON.stringify(mcpConfig));
42
+ args.push('--mcp-config', configFile);
43
+ }
44
+ if (screenshot && ctx.session?.screenshot) {
45
+ const imgBytes = await ctx.session.screenshot();
46
+ const shotFile = join(tmpdir(), `loop-shot-${ctx.session.id}-${Date.now()}.jpg`);
47
+ writeFileSync(shotFile, imgBytes);
48
+ args.push(shotFile);
49
+ }
50
+ let raw = '';
51
+ let lastError = '';
52
+ for (let attempt = 1; attempt <= retries; attempt++) {
53
+ if (ctx.signal?.aborted)
54
+ throw new Error('cancelled');
55
+ const result = await spawnAsync('claude', args, { timeout, cwd: tmpdir(), signal: ctx.signal });
56
+ if (result.aborted)
57
+ throw new Error('cancelled');
58
+ if (!result.error && result.exitCode === 0) {
59
+ raw = result.stdout.trim();
60
+ break;
61
+ }
62
+ const errMsg = result.error?.message ?? result.error?.code ?? `exit ${result.exitCode}`;
63
+ lastError = errMsg;
64
+ if (attempt < retries && isRetryable(errMsg)) {
65
+ ctx.log(`claudeCli: retry ${attempt}/${retries} — ${errMsg.slice(0, 80)}`);
66
+ await sleep(3000 * attempt);
67
+ }
68
+ else {
69
+ break;
70
+ }
71
+ }
72
+ if (!raw && lastError) {
73
+ throw new Error(`claude CLI failed (${lastError})`);
74
+ }
75
+ const statusLine = raw.match(/^RESULT:\s*(ok|failed.*)$/im);
76
+ if (statusLine?.[1] && !statusLine[1].toLowerCase().startsWith('ok')) {
77
+ throw new Error(`claude reported: ${statusLine[1].replace(/^failed\s*[-—]\s*/i, '')}`);
78
+ }
79
+ // Strip the RESULT: signal line before returning — it's a protocol marker, not content
80
+ const output = raw.replace(/\n*^RESULT:\s*(ok|failed.*)$/im, '').trim();
81
+ if (output)
82
+ ctx.log(`claude: ${output.split('\n')[0].slice(0, 120)}${output.includes('\n') ? ' …' : ''}`);
83
+ return { ok: true, output };
84
+ }
85
+ function isRetryable(msg) {
86
+ return ['fetch failed', 'ECONNRESET', 'ECONNREFUSED', 'ETIMEDOUT'].some(s => msg.includes(s));
87
+ }
88
+ function sleep(ms) {
89
+ return new Promise(r => setTimeout(r, ms));
90
+ }
91
+ function spawnAsync(cmd, args, opts) {
92
+ return new Promise(resolve => {
93
+ const proc = spawn(cmd, args, { cwd: opts.cwd });
94
+ let stdout = '';
95
+ let stderr = '';
96
+ let settled = false;
97
+ let aborted = false;
98
+ const timeoutId = setTimeout(() => {
99
+ if (!settled) {
100
+ settled = true;
101
+ proc.kill('SIGTERM');
102
+ resolve({ stdout, stderr, exitCode: null, error: Object.assign(new Error('ETIMEDOUT'), { code: 'ETIMEDOUT' }), aborted: false });
103
+ }
104
+ }, opts.timeout);
105
+ const onAbort = () => {
106
+ if (!settled) {
107
+ settled = true;
108
+ aborted = true;
109
+ clearTimeout(timeoutId);
110
+ proc.kill('SIGTERM');
111
+ resolve({ stdout, stderr, exitCode: null, aborted: true });
112
+ }
113
+ };
114
+ opts.signal?.addEventListener('abort', onAbort, { once: true });
115
+ proc.stdout?.on('data', (d) => { stdout += d.toString(); });
116
+ proc.stderr?.on('data', (d) => { stderr += d.toString(); });
117
+ proc.on('error', (err) => {
118
+ if (!settled) {
119
+ settled = true;
120
+ clearTimeout(timeoutId);
121
+ opts.signal?.removeEventListener('abort', onAbort);
122
+ resolve({ stdout, stderr, exitCode: null, error: err, aborted });
123
+ }
124
+ });
125
+ proc.on('close', (code) => {
126
+ if (!settled) {
127
+ settled = true;
128
+ clearTimeout(timeoutId);
129
+ opts.signal?.removeEventListener('abort', onAbort);
130
+ resolve({ stdout, stderr, exitCode: code, aborted });
131
+ }
132
+ });
133
+ });
134
+ }
135
+ //# sourceMappingURL=claude-cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"claude-cli.js","sourceRoot":"","sources":["../src/claude-cli.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,53 @@
1
+ import type { Session, ClickOptions, ScrollOptions } from './session.js';
2
+ import type { Logger } from './logger.js';
3
+ import type { Emitter } from './events.js';
4
+ export interface ContextOptions {
5
+ session: Session;
6
+ vars?: Record<string, unknown>;
7
+ state?: Map<string, unknown>;
8
+ logger?: Logger | null;
9
+ checkpointFile?: string | null;
10
+ emitter?: Emitter | null;
11
+ signal?: AbortSignal | null;
12
+ }
13
+ export declare class Context {
14
+ readonly session: Session;
15
+ readonly vars: Record<string, unknown>;
16
+ readonly signal: AbortSignal | null;
17
+ private readonly _state;
18
+ private readonly _logger;
19
+ _emitter: Emitter | null;
20
+ /** Set by the Loop runner — enables ctx.saveCheckpoint(). */
21
+ _checkpointFile: string | null;
22
+ _loopName: string;
23
+ _completedSteps: string[];
24
+ _lastCompletedIndex: number;
25
+ constructor({ session, vars, state, logger, checkpointFile, emitter, signal, }: ContextOptions);
26
+ get<T = unknown>(key: string): T | undefined;
27
+ set(key: string, value: unknown): this;
28
+ has(key: string): boolean;
29
+ snapshot(): Record<string, unknown>;
30
+ /**
31
+ * Emit a custom event from inside a step. Listeners on the Loop will receive
32
+ * it and can block the step until they complete (useful for human-in-the-loop
33
+ * gates, approval flows, or metrics).
34
+ *
35
+ * @example
36
+ * loop.step('draft', async (ctx) => {
37
+ * const text = await generateDraft(ctx)
38
+ * await ctx.emit('draft:ready', { text }) // waits for approval listener
39
+ * await publish(text)
40
+ * })
41
+ */
42
+ emit(event: string, data?: unknown): Promise<void>;
43
+ saveCheckpoint(): void;
44
+ log(msg: string, data?: Record<string, unknown>): void;
45
+ fork(vars?: Record<string, unknown>, session?: Session | null): Context;
46
+ navigate(url: string): Promise<void>;
47
+ click(opts: ClickOptions): Promise<void>;
48
+ type(text: string): Promise<void>;
49
+ key(key: string): Promise<void>;
50
+ scroll(opts: ScrollOptions): Promise<void>;
51
+ screenshot(): Promise<Buffer>;
52
+ }
53
+ //# sourceMappingURL=context.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../src/context.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,cAAc,CAAA;AACxE,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AACzC,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,aAAa,CAAA;AAG1C,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,OAAO,CAAA;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IAC9B,KAAK,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IAC5B,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACtB,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC9B,OAAO,CAAC,EAAE,OAAO,GAAG,IAAI,CAAA;IACxB,MAAM,CAAC,EAAE,WAAW,GAAG,IAAI,CAAA;CAC5B;AAED,qBAAa,OAAO;IAClB,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAA;IACzB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IACtC,QAAQ,CAAC,MAAM,EAAE,WAAW,GAAG,IAAI,CAAA;IACnC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAsB;IAC7C,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAe;IACvC,QAAQ,EAAE,OAAO,GAAG,IAAI,CAAA;IAExB,6DAA6D;IAC7D,eAAe,EAAE,MAAM,GAAG,IAAI,CAAA;IAC9B,SAAS,EAAE,MAAM,CAAK;IACtB,eAAe,EAAE,MAAM,EAAE,CAAK;IAC9B,mBAAmB,EAAE,MAAM,CAAK;gBAEpB,EACV,OAAO,EACP,IAAS,EACT,KAAiB,EACjB,MAAa,EACb,cAAqB,EACrB,OAAc,EACd,MAAa,GACd,EAAE,cAAc;IAYjB,GAAG,CAAC,CAAC,GAAG,OAAO,EAAE,GAAG,EAAE,MAAM,GAAG,CAAC,GAAG,SAAS;IAI5C,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,IAAI;IAKtC,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO;IAIzB,QAAQ,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IAMnC;;;;;;;;;;;OAWG;IACG,IAAI,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IAMxD,cAAc,IAAI,IAAI;IAkBtB,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI;IAQtD,IAAI,CAAC,IAAI,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,EAAE,OAAO,GAAE,OAAO,GAAG,IAAW,GAAG,OAAO;IAcjF,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IACpC,KAAK,CAAC,IAAI,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC;IACxC,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IACjC,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAC/B,MAAM,CAAC,IAAI,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC;IAC1C,UAAU,IAAI,OAAO,CAAC,MAAM,CAAC;CAC9B"}
@@ -0,0 +1,95 @@
1
+ import { writeCheckpoint } from './checkpoint.js';
2
+ export class Context {
3
+ session;
4
+ vars;
5
+ signal;
6
+ _state;
7
+ _logger;
8
+ _emitter;
9
+ /** Set by the Loop runner — enables ctx.saveCheckpoint(). */
10
+ _checkpointFile;
11
+ _loopName = '';
12
+ _completedSteps = [];
13
+ _lastCompletedIndex = -1;
14
+ constructor({ session, vars = {}, state = new Map(), logger = null, checkpointFile = null, emitter = null, signal = null, }) {
15
+ this.session = session;
16
+ this.vars = vars;
17
+ this._state = state;
18
+ this._logger = logger;
19
+ this._checkpointFile = checkpointFile;
20
+ this._emitter = emitter;
21
+ this.signal = signal;
22
+ }
23
+ // ── state ────────────────────────────────────────────────────────────────────
24
+ get(key) {
25
+ return this._state.get(key);
26
+ }
27
+ set(key, value) {
28
+ this._state.set(key, value);
29
+ return this;
30
+ }
31
+ has(key) {
32
+ return this._state.has(key);
33
+ }
34
+ snapshot() {
35
+ return Object.fromEntries(this._state);
36
+ }
37
+ // ── events ────────────────────────────────────────────────────────────────────
38
+ /**
39
+ * Emit a custom event from inside a step. Listeners on the Loop will receive
40
+ * it and can block the step until they complete (useful for human-in-the-loop
41
+ * gates, approval flows, or metrics).
42
+ *
43
+ * @example
44
+ * loop.step('draft', async (ctx) => {
45
+ * const text = await generateDraft(ctx)
46
+ * await ctx.emit('draft:ready', { text }) // waits for approval listener
47
+ * await publish(text)
48
+ * })
49
+ */
50
+ async emit(event, data) {
51
+ await this._emitter?.emit(event, data);
52
+ }
53
+ // ── checkpointing ─────────────────────────────────────────────────────────────
54
+ saveCheckpoint() {
55
+ if (!this._checkpointFile) {
56
+ throw new Error('saveCheckpoint() requires checkpointFile to be set in loop.run()');
57
+ }
58
+ const data = {
59
+ loop: this._loopName,
60
+ session: this.session.id,
61
+ savedAt: new Date().toISOString(),
62
+ completedSteps: [...this._completedSteps],
63
+ lastCompletedIndex: this._lastCompletedIndex,
64
+ state: this.snapshot(),
65
+ };
66
+ writeCheckpoint(this._checkpointFile, data);
67
+ this.log(`checkpoint saved → ${this._checkpointFile}`);
68
+ }
69
+ // ── logging ──────────────────────────────────────────────────────────────────
70
+ log(msg, data) {
71
+ const extra = data ? ` ${JSON.stringify(data)}` : '';
72
+ process.stdout.write(` ${msg}${extra}\n`);
73
+ this._logger?.write({ msg, data });
74
+ }
75
+ // ── forking ──────────────────────────────────────────────────────────────────
76
+ fork(vars = {}, session = null) {
77
+ return new Context({
78
+ session: session ?? this.session,
79
+ vars: { ...this.vars, ...vars },
80
+ state: this._state,
81
+ logger: this._logger,
82
+ checkpointFile: this._checkpointFile,
83
+ emitter: this._emitter,
84
+ signal: this.signal,
85
+ });
86
+ }
87
+ // ── session convenience methods ───────────────────────────────────────────────
88
+ navigate(url) { return this.session.navigate(url); }
89
+ click(opts) { return this.session.click(opts); }
90
+ type(text) { return this.session.type(text); }
91
+ key(key) { return this.session.key(key); }
92
+ scroll(opts) { return this.session.scroll(opts); }
93
+ screenshot() { return this.session.screenshot(); }
94
+ }
95
+ //# sourceMappingURL=context.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context.js","sourceRoot":"","sources":["../src/context.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,111 @@
1
+ /**
2
+ * events.ts — typed event system for loop lifecycle and custom step events.
3
+ *
4
+ * The Loop fires built-in events at every lifecycle moment. Steps can emit
5
+ * custom events via ctx.emit(). Listeners can be async — the loop awaits all
6
+ * listeners before continuing, so a listener can pause execution (e.g. a
7
+ * human-in-the-loop gate that waits for approval).
8
+ *
9
+ * @example
10
+ * loop.on('step:complete', async ({ step, durationMs }) => {
11
+ * await slack.send(`✓ ${step} finished in ${durationMs}ms`)
12
+ * })
13
+ *
14
+ * loop.on('loop:error', async ({ error }) => {
15
+ * await pagerduty.alert(error.message)
16
+ * })
17
+ *
18
+ * // Custom event from inside a step:
19
+ * loop.step('review', async (ctx) => {
20
+ * const result = await agent(ctx, 'Draft the email.')
21
+ * await ctx.emit('draft:ready', { text: result.text })
22
+ * })
23
+ *
24
+ * loop.on('draft:ready', async ({ text }) => {
25
+ * await humanReview(text) // blocks until approved
26
+ * })
27
+ */
28
+ export interface LoopStartEvent {
29
+ loop: string;
30
+ session: string;
31
+ totalSteps: number;
32
+ resumedFrom?: string;
33
+ }
34
+ export interface LoopCompleteEvent {
35
+ loop: string;
36
+ session: string;
37
+ status: 'completed' | 'failed' | 'cancelled';
38
+ durationMs: number;
39
+ stepsCompleted: number;
40
+ }
41
+ export interface StepStartEvent {
42
+ loop: string;
43
+ step: string;
44
+ index: number;
45
+ total: number;
46
+ }
47
+ export interface StepCompleteEvent {
48
+ loop: string;
49
+ step: string;
50
+ index: number;
51
+ durationMs: number;
52
+ }
53
+ export interface StepErrorEvent {
54
+ loop: string;
55
+ step: string;
56
+ index: number;
57
+ error: Error;
58
+ durationMs: number;
59
+ }
60
+ export interface StepSkipEvent {
61
+ loop: string;
62
+ step: string;
63
+ index: number;
64
+ reason: 'checkpoint' | 'range' | 'error';
65
+ }
66
+ export interface StepRetryEvent {
67
+ loop: string;
68
+ step: string;
69
+ index: number;
70
+ plugin: string;
71
+ }
72
+ export interface CheckpointSavedEvent {
73
+ loop: string;
74
+ file: string;
75
+ step: string;
76
+ completedCount: number;
77
+ totalSteps: number;
78
+ }
79
+ /** Map of all built-in event names → their payload types. */
80
+ export interface LoopEvents {
81
+ 'loop:start': LoopStartEvent;
82
+ 'loop:complete': LoopCompleteEvent;
83
+ 'step:start': StepStartEvent;
84
+ 'step:complete': StepCompleteEvent;
85
+ 'step:error': StepErrorEvent;
86
+ 'step:skip': StepSkipEvent;
87
+ 'step:retry': StepRetryEvent;
88
+ 'checkpoint:saved': CheckpointSavedEvent;
89
+ }
90
+ type KnownListener<K extends keyof LoopEvents> = (data: LoopEvents[K]) => void | Promise<void>;
91
+ type AnyListener = (data: unknown) => void | Promise<void>;
92
+ export declare class Emitter {
93
+ private readonly _listeners;
94
+ /** Listen to a built-in lifecycle event (fully typed). */
95
+ on<K extends keyof LoopEvents>(event: K, listener: KnownListener<K>): this;
96
+ /** Listen to a custom event emitted via ctx.emit(). */
97
+ on(event: string, listener: AnyListener): this;
98
+ /** Remove a listener. */
99
+ off<K extends keyof LoopEvents>(event: K, listener: KnownListener<K>): this;
100
+ off(event: string, listener: AnyListener): this;
101
+ /**
102
+ * Fire an event. Awaits all listeners concurrently.
103
+ * Listener errors are caught and logged — they never crash the loop.
104
+ */
105
+ emit<K extends keyof LoopEvents>(event: K, data: LoopEvents[K]): Promise<void>;
106
+ emit(event: string, data: unknown): Promise<void>;
107
+ /** True if any listeners are registered for the given event. */
108
+ hasListeners(event: string): boolean;
109
+ }
110
+ export {};
111
+ //# sourceMappingURL=events.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"events.d.ts","sourceRoot":"","sources":["../src/events.ts"],"names":[],"mappings":"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"}
package/dist/events.js ADDED
@@ -0,0 +1,53 @@
1
+ /**
2
+ * events.ts — typed event system for loop lifecycle and custom step events.
3
+ *
4
+ * The Loop fires built-in events at every lifecycle moment. Steps can emit
5
+ * custom events via ctx.emit(). Listeners can be async — the loop awaits all
6
+ * listeners before continuing, so a listener can pause execution (e.g. a
7
+ * human-in-the-loop gate that waits for approval).
8
+ *
9
+ * @example
10
+ * loop.on('step:complete', async ({ step, durationMs }) => {
11
+ * await slack.send(`✓ ${step} finished in ${durationMs}ms`)
12
+ * })
13
+ *
14
+ * loop.on('loop:error', async ({ error }) => {
15
+ * await pagerduty.alert(error.message)
16
+ * })
17
+ *
18
+ * // Custom event from inside a step:
19
+ * loop.step('review', async (ctx) => {
20
+ * const result = await agent(ctx, 'Draft the email.')
21
+ * await ctx.emit('draft:ready', { text: result.text })
22
+ * })
23
+ *
24
+ * loop.on('draft:ready', async ({ text }) => {
25
+ * await humanReview(text) // blocks until approved
26
+ * })
27
+ */
28
+ export class Emitter {
29
+ _listeners = new Map();
30
+ on(event, listener) {
31
+ if (!this._listeners.has(event))
32
+ this._listeners.set(event, new Set());
33
+ this._listeners.get(event).add(listener);
34
+ return this;
35
+ }
36
+ off(event, listener) {
37
+ this._listeners.get(event)?.delete(listener);
38
+ return this;
39
+ }
40
+ async emit(event, data) {
41
+ const listeners = this._listeners.get(event);
42
+ if (!listeners?.size)
43
+ return;
44
+ await Promise.all([...listeners].map(fn => Promise.resolve(fn(data)).catch(err => {
45
+ console.error(` [event:${event}] listener error: ${err.message}`);
46
+ })));
47
+ }
48
+ /** True if any listeners are registered for the given event. */
49
+ hasListeners(event) {
50
+ return (this._listeners.get(event)?.size ?? 0) > 0;
51
+ }
52
+ }
53
+ //# sourceMappingURL=events.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"events.js","sourceRoot":"","sources":["../src/events.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAgFH,MAAM,OAAO,OAAO;IACD,UAAU,GAAG,IAAI,GAAG,EAA4B,CAAA;IAMjE,EAAE,CAAC,KAAa,EAAE,QAAqB;QACrC,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,KAAK,CAAC;YAAE,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,KAAK,EAAE,IAAI,GAAG,EAAE,CAAC,CAAA;QACtE,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,KAAK,CAAE,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAA;QACzC,OAAO,IAAI,CAAA;IACb,CAAC;IAKD,GAAG,CAAC,KAAa,EAAE,QAAqB;QACtC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC,QAAQ,CAAC,CAAA;QAC5C,OAAO,IAAI,CAAA;IACb,CAAC;IAQD,KAAK,CAAC,IAAI,CAAC,KAAa,EAAE,IAAa;QACrC,MAAM,SAAS,GAAG,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,KAAK,CAAC,CAAA;QAC5C,IAAI,CAAC,SAAS,EAAE,IAAI;YAAE,OAAM;QAC5B,MAAM,OAAO,CAAC,GAAG,CACf,CAAC,GAAG,SAAS,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,CACtB,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE;YACpC,OAAO,CAAC,KAAK,CAAC,YAAY,KAAK,qBAAsB,GAAa,CAAC,OAAO,EAAE,CAAC,CAAA;QAC/E,CAAC,CAAC,CACH,CACF,CAAA;IACH,CAAC;IAED,gEAAgE;IAChE,YAAY,CAAC,KAAa;QACxB,OAAO,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,IAAI,IAAI,CAAC,CAAC,GAAG,CAAC,CAAA;IACpD,CAAC;CACF"}
package/dist/flow.d.ts ADDED
@@ -0,0 +1,36 @@
1
+ import type { Context } from './context.js';
2
+ import type { Loop } from './loop.js';
3
+ export type Item = string | {
4
+ type: string;
5
+ subtypes?: string[];
6
+ };
7
+ export interface EachOptions {
8
+ continueOnError?: boolean;
9
+ }
10
+ /**
11
+ * each — iterate over a list of items, running a step function for each one.
12
+ *
13
+ * Each item receives a forked Context with per-item vars merged in:
14
+ * ctx.vars.item — the item value
15
+ * ctx.vars.subtype — the subtype (if applicable)
16
+ *
17
+ * @example
18
+ * await each(ctx, ['https://a.com', 'https://b.com'], async (ctx) => {
19
+ * await ctx.navigate(ctx.vars.item as string)
20
+ * await agent(ctx, 'Summarize this page.', { model })
21
+ * })
22
+ */
23
+ export declare function each(ctx: Context, items: Item[], fn: (ctx: Context) => Promise<unknown>, { continueOnError }?: EachOptions): Promise<void>;
24
+ /**
25
+ * sub — run a Loop instance as a sub-step, sharing the current session.
26
+ *
27
+ * @example
28
+ * const loginLoop = new Loop('login')
29
+ * loginLoop.step('auth', async (ctx) => { ... })
30
+ *
31
+ * loop.step('login', async (ctx) => {
32
+ * await sub(ctx, loginLoop, { loginUrl: '/login', username: 'admin' })
33
+ * })
34
+ */
35
+ export declare function sub(ctx: Context, loop: Loop, vars?: Record<string, unknown>): Promise<void>;
36
+ //# sourceMappingURL=flow.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"flow.d.ts","sourceRoot":"","sources":["../src/flow.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAA;AAC3C,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AAErC,MAAM,MAAM,IAAI,GAAG,MAAM,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAA;CAAE,CAAA;AAEjE,MAAM,WAAW,WAAW;IAC1B,eAAe,CAAC,EAAE,OAAO,CAAA;CAC1B;AAED;;;;;;;;;;;;GAYG;AACH,wBAAsB,IAAI,CACxB,GAAG,EAAE,OAAO,EACZ,KAAK,EAAE,IAAI,EAAE,EACb,EAAE,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,OAAO,CAAC,OAAO,CAAC,EACtC,EAAE,eAAuB,EAAE,GAAE,WAAgB,GAC5C,OAAO,CAAC,IAAI,CAAC,CAef;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,GAAG,CACvB,GAAG,EAAE,OAAO,EACZ,IAAI,EAAE,IAAI,EACV,IAAI,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,GACjC,OAAO,CAAC,IAAI,CAAC,CAGf"}
package/dist/flow.js ADDED
@@ -0,0 +1,62 @@
1
+ /**
2
+ * each — iterate over a list of items, running a step function for each one.
3
+ *
4
+ * Each item receives a forked Context with per-item vars merged in:
5
+ * ctx.vars.item — the item value
6
+ * ctx.vars.subtype — the subtype (if applicable)
7
+ *
8
+ * @example
9
+ * await each(ctx, ['https://a.com', 'https://b.com'], async (ctx) => {
10
+ * await ctx.navigate(ctx.vars.item as string)
11
+ * await agent(ctx, 'Summarize this page.', { model })
12
+ * })
13
+ */
14
+ export async function each(ctx, items, fn, { continueOnError = false } = {}) {
15
+ const pairs = expandItems(items);
16
+ for (const vars of pairs) {
17
+ const label = vars.subtype ? `${vars.item} / ${vars.subtype}` : vars.item;
18
+ const childCtx = ctx.fork(vars);
19
+ try {
20
+ await fn(childCtx);
21
+ }
22
+ catch (err) {
23
+ if (!continueOnError)
24
+ throw err;
25
+ const msg = err instanceof Error ? err.message : String(err);
26
+ ctx.log(`each: "${label}" failed — ${msg}`);
27
+ }
28
+ }
29
+ }
30
+ /**
31
+ * sub — run a Loop instance as a sub-step, sharing the current session.
32
+ *
33
+ * @example
34
+ * const loginLoop = new Loop('login')
35
+ * loginLoop.step('auth', async (ctx) => { ... })
36
+ *
37
+ * loop.step('login', async (ctx) => {
38
+ * await sub(ctx, loginLoop, { loginUrl: '/login', username: 'admin' })
39
+ * })
40
+ */
41
+ export async function sub(ctx, loop, vars = {}) {
42
+ const childCtx = ctx.fork(vars);
43
+ return loop.runWith(childCtx);
44
+ }
45
+ function expandItems(items) {
46
+ const pairs = [];
47
+ for (const entry of items) {
48
+ if (typeof entry === 'string') {
49
+ pairs.push({ item: entry });
50
+ }
51
+ else if (entry.subtypes?.length) {
52
+ for (const subtype of entry.subtypes) {
53
+ pairs.push({ item: entry.type, subtype });
54
+ }
55
+ }
56
+ else {
57
+ pairs.push({ item: entry.type });
58
+ }
59
+ }
60
+ return pairs;
61
+ }
62
+ //# sourceMappingURL=flow.js.map