@lnar/cli 0.0.1-dev.46c4eb5 → 0.0.1-dev.47196fc

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 (58) hide show
  1. package/dist/api-client.d.ts +19 -0
  2. package/dist/api-client.js +38 -0
  3. package/dist/api-client.js.map +1 -1
  4. package/dist/auth.d.ts +20 -0
  5. package/dist/auth.js +74 -0
  6. package/dist/auth.js.map +1 -0
  7. package/dist/cli.js +16 -0
  8. package/dist/cli.js.map +1 -1
  9. package/dist/commands/daemon.js +13 -0
  10. package/dist/commands/daemon.js.map +1 -1
  11. package/dist/commands/record.d.ts +7 -0
  12. package/dist/commands/record.js +117 -0
  13. package/dist/commands/record.js.map +1 -0
  14. package/dist/recording/bundle.d.ts +25 -0
  15. package/dist/recording/bundle.js +55 -0
  16. package/dist/recording/bundle.js.map +1 -0
  17. package/dist/recording/capture.d.ts +53 -0
  18. package/dist/recording/capture.js +163 -0
  19. package/dist/recording/capture.js.map +1 -0
  20. package/dist/recording/session.d.ts +30 -0
  21. package/dist/recording/session.js +47 -0
  22. package/dist/recording/session.js.map +1 -0
  23. package/dist/recording/types.d.ts +59 -0
  24. package/dist/recording/types.js +8 -0
  25. package/dist/recording/types.js.map +1 -0
  26. package/dist/run-client.d.ts +19 -0
  27. package/dist/run-client.js +44 -0
  28. package/dist/run-client.js.map +1 -0
  29. package/dist/run-worker.d.ts +25 -0
  30. package/dist/run-worker.js +85 -0
  31. package/dist/run-worker.js.map +1 -0
  32. package/dist/runtime/actions.d.ts +13 -0
  33. package/dist/runtime/actions.js +107 -0
  34. package/dist/runtime/actions.js.map +1 -0
  35. package/dist/runtime/client.d.ts +3 -0
  36. package/dist/runtime/client.js +85 -0
  37. package/dist/runtime/client.js.map +1 -0
  38. package/dist/runtime/login.d.ts +20 -0
  39. package/dist/runtime/login.js +34 -0
  40. package/dist/runtime/login.js.map +1 -0
  41. package/dist/runtime/loop.d.ts +28 -0
  42. package/dist/runtime/loop.js +68 -0
  43. package/dist/runtime/loop.js.map +1 -0
  44. package/dist/runtime/playbook.d.ts +49 -0
  45. package/dist/runtime/playbook.js +73 -0
  46. package/dist/runtime/playbook.js.map +1 -0
  47. package/dist/runtime/runner.d.ts +20 -0
  48. package/dist/runtime/runner.js +54 -0
  49. package/dist/runtime/runner.js.map +1 -0
  50. package/dist/runtime/types.d.ts +69 -0
  51. package/dist/runtime/types.js +10 -0
  52. package/dist/runtime/types.js.map +1 -0
  53. package/dist/service/files.d.ts +14 -2
  54. package/dist/service/files.js +16 -4
  55. package/dist/service/files.js.map +1 -1
  56. package/dist/service/install.js +11 -3
  57. package/dist/service/install.js.map +1 -1
  58. package/package.json +11 -1
@@ -0,0 +1,163 @@
1
+ /** 全ページに注入する記録スクリプト (self-contained・依存なし)。 */
2
+ export const INIT_SCRIPT = `() => {
3
+ if (window.__lnarRecorderInstalled) return;
4
+ window.__lnarRecorderInstalled = true;
5
+ var MAXV = 300;
6
+ function esc(s){ return (window.CSS && CSS.escape) ? CSS.escape(s) : String(s).replace(/[^a-zA-Z0-9_-]/g, '\\\\$&'); }
7
+ function selectorFor(el){
8
+ if (!el || el.nodeType !== 1) return undefined;
9
+ var tid = el.getAttribute && el.getAttribute('data-testid');
10
+ if (tid) return '[data-testid="' + esc(tid) + '"]';
11
+ if (el.id) return '#' + esc(el.id);
12
+ var parts = [], cur = el, depth = 0;
13
+ while (cur && cur.nodeType === 1 && depth < 4) {
14
+ var part = cur.tagName.toLowerCase();
15
+ var parent = cur.parentElement;
16
+ if (parent) {
17
+ var sibs = Array.prototype.filter.call(parent.children, function(c){ return c.tagName === cur.tagName; });
18
+ if (sibs.length > 1) part += ':nth-of-type(' + (sibs.indexOf(cur) + 1) + ')';
19
+ }
20
+ parts.unshift(part);
21
+ cur = cur.parentElement;
22
+ depth++;
23
+ }
24
+ return parts.join(' > ');
25
+ }
26
+ function labelFor(el){
27
+ if (!el || !el.getAttribute) return undefined;
28
+ var a = el.getAttribute('aria-label') || el.getAttribute('placeholder') || el.getAttribute('name');
29
+ if (a) return String(a).slice(0, 80);
30
+ var t = (el.innerText || el.value || '').trim();
31
+ return t ? t.slice(0, 80) : undefined;
32
+ }
33
+ function isSensitive(el){
34
+ var type = ((el.getAttribute && el.getAttribute('type')) || '').toLowerCase();
35
+ if (type === 'password') return true;
36
+ var ac = ((el.getAttribute && el.getAttribute('autocomplete')) || '').toLowerCase();
37
+ if (/password|cc-|one-time-code/.test(ac)) return true;
38
+ var idn = (((el.name || '') + ' ' + (el.id || '')).toLowerCase());
39
+ return /pass|otp|secret|token|card|cvv|ssn/.test(idn);
40
+ }
41
+ function send(p){ try { window.__lnarRecord(JSON.stringify(p)); } catch (e) {} }
42
+ document.addEventListener('click', function(e){
43
+ var el = e.target;
44
+ send({ kind: 'click', x: Math.round(e.clientX), y: Math.round(e.clientY), selector: selectorFor(el), label: labelFor(el) });
45
+ }, true);
46
+ document.addEventListener('change', function(e){
47
+ var el = e.target;
48
+ if (!el || !('value' in el)) return;
49
+ var sens = isSensitive(el);
50
+ send({ kind: 'input', selector: selectorFor(el), label: labelFor(el), sensitive: sens, value: sens ? '***' : String(el.value).slice(0, MAXV) });
51
+ }, true);
52
+ document.addEventListener('submit', function(e){
53
+ send({ kind: 'submit', selector: selectorFor(e.target), label: labelFor(e.target) });
54
+ }, true);
55
+ document.addEventListener('keydown', function(e){
56
+ if (e.key === 'Enter' || e.key === 'Tab' || e.key === 'Escape') {
57
+ send({ kind: 'keydown', key: e.key, selector: selectorFor(e.target) });
58
+ }
59
+ }, true);
60
+ }`;
61
+ /**
62
+ * 録画状態を保持し、ページ内イベント / ナビゲーションを RecordedAction に変換する。
63
+ *
64
+ * キーフレームは PNG の Buffer としてメモリ上にのみ保持し、ディスクには書かない
65
+ * (録画内容が一瞬でもローカルに残るのを防ぐ)。
66
+ */
67
+ export class Recorder {
68
+ actions = [];
69
+ frames = [];
70
+ startMs;
71
+ now;
72
+ maxScreenshots;
73
+ shotCount = 0;
74
+ constructor(options = {}) {
75
+ this.now = options.now ?? (() => Date.now());
76
+ this.maxScreenshots = options.maxScreenshots ?? 200;
77
+ this.startMs = this.now();
78
+ }
79
+ /** 記録済みキーフレームのファイル名一覧 (manifest 用)。 */
80
+ get screenshots() {
81
+ return this.frames.map((f) => f.name);
82
+ }
83
+ async capture(page) {
84
+ if (this.shotCount >= this.maxScreenshots)
85
+ return undefined;
86
+ // 採番は await の前に同期的に予約する。クリック/遷移/入力が近接して発火すると
87
+ // capture が並行実行されるため、ここで予約しないと同じ番号を取り合い、
88
+ // 同名フレームで上書きして manifest に重複が出る (実データで確認済みの不具合)。
89
+ const index = ++this.shotCount;
90
+ const name = `${String(index).padStart(4, '0')}.png`;
91
+ try {
92
+ const bytes = await page.screenshot({ type: 'png' });
93
+ this.frames.push({ name, bytes });
94
+ return name;
95
+ }
96
+ catch {
97
+ return undefined; // ナビゲーション中などは握り潰す (予約番号は欠番になる)
98
+ }
99
+ }
100
+ /** ページ内イベントを受けてアクションを記録する。 */
101
+ async handleInPage(page, payloadJson) {
102
+ let payload;
103
+ try {
104
+ payload = JSON.parse(payloadJson);
105
+ }
106
+ catch {
107
+ return;
108
+ }
109
+ const screenshot = await this.capture(page);
110
+ this.actions.push({
111
+ type: payload.kind,
112
+ atMs: this.now() - this.startMs,
113
+ url: page.url(),
114
+ selector: payload.selector,
115
+ label: payload.label,
116
+ value: payload.value,
117
+ sensitive: payload.sensitive,
118
+ x: payload.x,
119
+ y: payload.y,
120
+ key: payload.key,
121
+ screenshot,
122
+ });
123
+ }
124
+ /** main frame のナビゲーションを記録する。 */
125
+ async handleNavigate(page, url) {
126
+ const screenshot = await this.capture(page);
127
+ this.actions.push({
128
+ type: 'navigate',
129
+ atMs: this.now() - this.startMs,
130
+ url,
131
+ screenshot,
132
+ });
133
+ }
134
+ }
135
+ /**
136
+ * context にレコーダを取り付ける。exposeBinding → addInitScript の順で行うこと。
137
+ * 取り付け後に作成/遷移したページが記録対象になる。
138
+ */
139
+ export async function attachRecorder(context, recorder) {
140
+ await context.exposeBinding('__lnarRecord', (source, payloadJson) => {
141
+ // 戻り値を待たない (ページ側 send は fire-and-forget)。
142
+ void recorder.handleInPage(source.page, payloadJson);
143
+ });
144
+ // INIT_SCRIPT は関数式。addInitScript は文字列をソースとして評価するだけで呼び出さ
145
+ // ないため、自己実行する形で渡す。
146
+ await context.addInitScript({ content: `(${INIT_SCRIPT})()` });
147
+ const wirePage = (page) => {
148
+ let lastUrl = '';
149
+ page.on('framenavigated', (frame) => {
150
+ if (frame !== page.mainFrame())
151
+ return;
152
+ const url = frame.url();
153
+ if (!url || url === 'about:blank' || url === lastUrl)
154
+ return;
155
+ lastUrl = url;
156
+ void recorder.handleNavigate(page, url);
157
+ });
158
+ };
159
+ for (const page of context.pages())
160
+ wirePage(page);
161
+ context.on('page', wirePage);
162
+ }
163
+ //# sourceMappingURL=capture.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"capture.js","sourceRoot":"","sources":["../../src/recording/capture.ts"],"names":[],"mappings":"AAiCA,8CAA8C;AAC9C,MAAM,CAAC,MAAM,WAAW,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA0DzB,CAAC;AAQH;;;;;GAKG;AACH,MAAM,OAAO,QAAQ;IACV,OAAO,GAAqB,EAAE,CAAC;IAC/B,MAAM,GAAe,EAAE,CAAC;IAEhB,OAAO,CAAS;IAChB,GAAG,CAAe;IAClB,cAAc,CAAS;IAChC,SAAS,GAAG,CAAC,CAAC;IAEtB,YAAY,UAA2B,EAAE;QACvC,IAAI,CAAC,GAAG,GAAG,OAAO,CAAC,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC;QAC7C,IAAI,CAAC,cAAc,GAAG,OAAO,CAAC,cAAc,IAAI,GAAG,CAAC;QACpD,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IAC5B,CAAC;IAED,uCAAuC;IACvC,IAAI,WAAW;QACb,OAAO,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;IACxC,CAAC;IAEO,KAAK,CAAC,OAAO,CAAC,IAAU;QAC9B,IAAI,IAAI,CAAC,SAAS,IAAI,IAAI,CAAC,cAAc;YAAE,OAAO,SAAS,CAAC;QAC5D,6CAA6C;QAC7C,yCAAyC;QACzC,gDAAgD;QAChD,MAAM,KAAK,GAAG,EAAE,IAAI,CAAC,SAAS,CAAC;QAC/B,MAAM,IAAI,GAAG,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC;QACrD,IAAI,CAAC;YACH,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,UAAU,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;YACrD,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;YAClC,OAAO,IAAI,CAAC;QACd,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,SAAS,CAAC,CAAC,+BAA+B;QACnD,CAAC;IACH,CAAC;IAED,8BAA8B;IAC9B,KAAK,CAAC,YAAY,CAAC,IAAU,EAAE,WAAmB;QAChD,IAAI,OAAsB,CAAC;QAC3B,IAAI,CAAC;YACH,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,WAAW,CAAkB,CAAC;QACrD,CAAC;QAAC,MAAM,CAAC;YACP,OAAO;QACT,CAAC;QACD,MAAM,UAAU,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;QAC5C,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC;YAChB,IAAI,EAAE,OAAO,CAAC,IAAI;YAClB,IAAI,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,OAAO;YAC/B,GAAG,EAAE,IAAI,CAAC,GAAG,EAAE;YACf,QAAQ,EAAE,OAAO,CAAC,QAAQ;YAC1B,KAAK,EAAE,OAAO,CAAC,KAAK;YACpB,KAAK,EAAE,OAAO,CAAC,KAAK;YACpB,SAAS,EAAE,OAAO,CAAC,SAAS;YAC5B,CAAC,EAAE,OAAO,CAAC,CAAC;YACZ,CAAC,EAAE,OAAO,CAAC,CAAC;YACZ,GAAG,EAAE,OAAO,CAAC,GAAG;YAChB,UAAU;SACX,CAAC,CAAC;IACL,CAAC;IAED,gCAAgC;IAChC,KAAK,CAAC,cAAc,CAAC,IAAU,EAAE,GAAW;QAC1C,MAAM,UAAU,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;QAC5C,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC;YAChB,IAAI,EAAE,UAAU;YAChB,IAAI,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,OAAO;YAC/B,GAAG;YACH,UAAU;SACX,CAAC,CAAC;IACL,CAAC;CACF;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,OAAuB,EAAE,QAAkB;IAC9E,MAAM,OAAO,CAAC,aAAa,CAAC,cAAc,EAAE,CAAC,MAAM,EAAE,WAAmB,EAAE,EAAE;QAC1E,0CAA0C;QAC1C,KAAK,QAAQ,CAAC,YAAY,CAAC,MAAM,CAAC,IAAI,EAAE,WAAW,CAAC,CAAC;IACvD,CAAC,CAAC,CAAC;IACH,wDAAwD;IACxD,mBAAmB;IACnB,MAAM,OAAO,CAAC,aAAa,CAAC,EAAE,OAAO,EAAE,IAAI,WAAW,KAAK,EAAE,CAAC,CAAC;IAE/D,MAAM,QAAQ,GAAG,CAAC,IAAU,EAAQ,EAAE;QACpC,IAAI,OAAO,GAAG,EAAE,CAAC;QACjB,IAAI,CAAC,EAAE,CAAC,gBAAgB,EAAE,CAAC,KAAK,EAAE,EAAE;YAClC,IAAI,KAAK,KAAK,IAAI,CAAC,SAAS,EAAE;gBAAE,OAAO;YACvC,MAAM,GAAG,GAAG,KAAK,CAAC,GAAG,EAAE,CAAC;YACxB,IAAI,CAAC,GAAG,IAAI,GAAG,KAAK,aAAa,IAAI,GAAG,KAAK,OAAO;gBAAE,OAAO;YAC7D,OAAO,GAAG,GAAG,CAAC;YACd,KAAK,QAAQ,CAAC,cAAc,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;QAC1C,CAAC,CAAC,CAAC;IACL,CAAC,CAAC;IACF,KAAK,MAAM,IAAI,IAAI,OAAO,CAAC,KAAK,EAAE;QAAE,QAAQ,CAAC,IAAI,CAAC,CAAC;IACnD,OAAO,CAAC,EAAE,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;AAC/B,CAAC"}
@@ -0,0 +1,30 @@
1
+ /**
2
+ * 録画セッションのオーケストレーション。
3
+ *
4
+ * ローカルブラウザを起動 → ユーザー操作を記録 → 停止シグナルで終了 → バンドル内容を返す。
5
+ * キーフレームはメモリ上の Buffer として保持し、ディスクには一切書かない。
6
+ * 停止条件 (`waitForStop`) は呼び出し側から注入し、本体をテスト可能に保つ。
7
+ */
8
+ import { type BrowserContext, type Page } from 'playwright';
9
+ import { type Keyframe } from './capture.js';
10
+ import type { RecordedAction, RecordingManifest } from './types.js';
11
+ export interface RecordSessionOptions {
12
+ readonly name: string;
13
+ readonly startUrl: string | null;
14
+ readonly profileDir: string;
15
+ readonly viewport?: {
16
+ readonly width: number;
17
+ readonly height: number;
18
+ };
19
+ readonly headless?: boolean;
20
+ readonly createdAt?: string;
21
+ /** 停止シグナル。resolve でバンドル化に進む。 */
22
+ readonly waitForStop: (page: Page, context: BrowserContext) => Promise<void>;
23
+ }
24
+ export interface RecordSessionResult {
25
+ readonly manifest: RecordingManifest;
26
+ readonly actions: ReadonlyArray<RecordedAction>;
27
+ readonly frames: ReadonlyArray<Keyframe>;
28
+ }
29
+ /** 録画を実行し、バンドル内容 (メモリ上) を返す。 */
30
+ export declare function recordSession(options: RecordSessionOptions): Promise<RecordSessionResult>;
@@ -0,0 +1,47 @@
1
+ /**
2
+ * 録画セッションのオーケストレーション。
3
+ *
4
+ * ローカルブラウザを起動 → ユーザー操作を記録 → 停止シグナルで終了 → バンドル内容を返す。
5
+ * キーフレームはメモリ上の Buffer として保持し、ディスクには一切書かない。
6
+ * 停止条件 (`waitForStop`) は呼び出し側から注入し、本体をテスト可能に保つ。
7
+ */
8
+ import { chromium } from 'playwright';
9
+ import { buildManifest } from './bundle.js';
10
+ import { attachRecorder, Recorder } from './capture.js';
11
+ const DEFAULT_VIEWPORT = { width: 1280, height: 800 };
12
+ /** 録画を実行し、バンドル内容 (メモリ上) を返す。 */
13
+ export async function recordSession(options) {
14
+ const viewport = options.viewport ?? DEFAULT_VIEWPORT;
15
+ // 動画 / trace は記録しない (Playwright が必ずディスクへ書き出すため、
16
+ // 録画内容を一瞬もローカルに残さない方針と両立しない)。
17
+ const context = await chromium.launchPersistentContext(options.profileDir, {
18
+ headless: options.headless ?? false,
19
+ viewport,
20
+ });
21
+ const recorder = new Recorder();
22
+ try {
23
+ await attachRecorder(context, recorder);
24
+ const page = context.pages()[0] ?? (await context.newPage());
25
+ if (options.startUrl) {
26
+ await page.goto(options.startUrl, { waitUntil: 'domcontentloaded' });
27
+ }
28
+ await options.waitForStop(page, context);
29
+ await context.close();
30
+ const manifest = buildManifest({
31
+ name: options.name,
32
+ startUrl: options.startUrl,
33
+ createdAt: options.createdAt ?? new Date().toISOString(),
34
+ viewport,
35
+ actions: recorder.actions,
36
+ screenshots: recorder.screenshots,
37
+ });
38
+ return { manifest, actions: recorder.actions, frames: recorder.frames };
39
+ }
40
+ finally {
41
+ // 例外時も確実に閉じる (close 済みなら no-op 相当)。
42
+ if (context.browser()?.isConnected()) {
43
+ await context.close().catch(() => { });
44
+ }
45
+ }
46
+ }
47
+ //# sourceMappingURL=session.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session.js","sourceRoot":"","sources":["../../src/recording/session.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,EAAuB,QAAQ,EAAa,MAAM,YAAY,CAAC;AACtE,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,cAAc,EAAiB,QAAQ,EAAE,MAAM,cAAc,CAAC;AAoBvE,MAAM,gBAAgB,GAAG,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,EAAW,CAAC;AAE/D,gCAAgC;AAChC,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,OAA6B;IAC/D,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,IAAI,gBAAgB,CAAC;IAEtD,gDAAgD;IAChD,8BAA8B;IAC9B,MAAM,OAAO,GAAG,MAAM,QAAQ,CAAC,uBAAuB,CAAC,OAAO,CAAC,UAAU,EAAE;QACzE,QAAQ,EAAE,OAAO,CAAC,QAAQ,IAAI,KAAK;QACnC,QAAQ;KACT,CAAC,CAAC;IAEH,MAAM,QAAQ,GAAG,IAAI,QAAQ,EAAE,CAAC;IAChC,IAAI,CAAC;QACH,MAAM,cAAc,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;QAExC,MAAM,IAAI,GAAG,OAAO,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC;QAC7D,IAAI,OAAO,CAAC,QAAQ,EAAE,CAAC;YACrB,MAAM,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,QAAQ,EAAE,EAAE,SAAS,EAAE,kBAAkB,EAAE,CAAC,CAAC;QACvE,CAAC;QAED,MAAM,OAAO,CAAC,WAAW,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;QAEzC,MAAM,OAAO,CAAC,KAAK,EAAE,CAAC;QAEtB,MAAM,QAAQ,GAAG,aAAa,CAAC;YAC7B,IAAI,EAAE,OAAO,CAAC,IAAI;YAClB,QAAQ,EAAE,OAAO,CAAC,QAAQ;YAC1B,SAAS,EAAE,OAAO,CAAC,SAAS,IAAI,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;YACxD,QAAQ;YACR,OAAO,EAAE,QAAQ,CAAC,OAAO;YACzB,WAAW,EAAE,QAAQ,CAAC,WAAW;SAClC,CAAC,CAAC;QAEH,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,QAAQ,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,CAAC,MAAM,EAAE,CAAC;IAC1E,CAAC;YAAS,CAAC;QACT,oCAAoC;QACpC,IAAI,OAAO,CAAC,OAAO,EAAE,EAAE,WAAW,EAAE,EAAE,CAAC;YACrC,MAAM,OAAO,CAAC,KAAK,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;QACxC,CAAC;IACH,CAAC;AACH,CAAC"}
@@ -0,0 +1,59 @@
1
+ /**
2
+ * デモ録画 (Demo-to-MCP / Phase 1) の型。
3
+ *
4
+ * ユーザーがローカルブラウザで実演した操作を構造化して保存し、後段 (Phase 2) で
5
+ * gemini-3.5-flash が「タスク手順書 (playbook)」へ解析するための素材にする。
6
+ */
7
+ /** ユーザー操作 1 件。座標はキャプチャ時のビューポートピクセル。 */
8
+ export interface RecordedAction {
9
+ /** 操作種別。 */
10
+ readonly type: 'navigate' | 'click' | 'input' | 'submit' | 'keydown';
11
+ /** 録画開始からの相対ミリ秒。 */
12
+ readonly atMs: number;
13
+ /** 操作時点の URL。 */
14
+ readonly url: string;
15
+ /** 対象要素の CSS セレクタ (best-effort)。navigate では無い。 */
16
+ readonly selector?: string;
17
+ /** 対象要素の可視テキスト/ラベル (要素特定のヒント)。 */
18
+ readonly label?: string;
19
+ /** 入力値。password 等の機微フィールドは "***" にマスクされる。 */
20
+ readonly value?: string;
21
+ /** 入力フィールドが機微 (password 等) だったか。 */
22
+ readonly sensitive?: boolean;
23
+ /** click のビューポート座標。 */
24
+ readonly x?: number;
25
+ readonly y?: number;
26
+ /** keydown のキー。 */
27
+ readonly key?: string;
28
+ /** このアクション直後に保存したキーフレーム画像のファイル名。 */
29
+ readonly screenshot?: string;
30
+ }
31
+ /** 録画バンドルのメタデータ (manifest.json)。API へ JSON で送る。 */
32
+ export interface RecordingManifest {
33
+ /** スキーマバージョン。 */
34
+ readonly version: 1;
35
+ /** 録画の表示名。 */
36
+ readonly name: string;
37
+ /** 録画開始 URL。 */
38
+ readonly startUrl: string | null;
39
+ /** ISO8601 の作成時刻。 */
40
+ readonly createdAt: string;
41
+ /** 記録時のビューポート。座標スケールの基準。 */
42
+ readonly viewport: {
43
+ readonly width: number;
44
+ readonly height: number;
45
+ };
46
+ /** 記録したアクション総数。 */
47
+ readonly actionCount: number;
48
+ /** 動画ファイル名 (取得できた場合)。 */
49
+ readonly video: string | null;
50
+ /** Playwright trace.zip ファイル名 (取得できた場合)。 */
51
+ readonly trace: string | null;
52
+ /** キーフレーム画像ファイル名の一覧。 */
53
+ readonly screenshots: ReadonlyArray<string>;
54
+ }
55
+ /** 録画 1 回の成果物 (バンドル内容)。 */
56
+ export interface RecordingBundle {
57
+ readonly manifest: RecordingManifest;
58
+ readonly actions: ReadonlyArray<RecordedAction>;
59
+ }
@@ -0,0 +1,8 @@
1
+ /**
2
+ * デモ録画 (Demo-to-MCP / Phase 1) の型。
3
+ *
4
+ * ユーザーがローカルブラウザで実演した操作を構造化して保存し、後段 (Phase 2) で
5
+ * gemini-3.5-flash が「タスク手順書 (playbook)」へ解析するための素材にする。
6
+ */
7
+ export {};
8
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/recording/types.ts"],"names":[],"mappings":"AAAA;;;;;GAKG"}
@@ -0,0 +1,19 @@
1
+ export interface PendingRun {
2
+ id: string;
3
+ recording_id: string;
4
+ name: string;
5
+ /** 録画の開始 URL。ここからブラウザを開いて実行を始める。 */
6
+ start_url?: string | null;
7
+ /** 作業の目的。実行時の指示文に含める。 */
8
+ purpose?: string | null;
9
+ params: Record<string, unknown> | null;
10
+ /** 解析済み playbook (parsePlaybook で検証して使う)。 */
11
+ playbook: unknown;
12
+ }
13
+ /** 自分が実行すべき pending run の一覧 (playbook 同梱)。 */
14
+ export declare const listPendingRuns: (baseUrl: string, apiKey: string) => Promise<PendingRun[]>;
15
+ /** ローカル実行用の Gemini API キーを取得する (K1)。 */
16
+ export declare const getRunCredential: (baseUrl: string, apiKey: string) => Promise<string>;
17
+ export declare const claimRun: (baseUrl: string, apiKey: string, runId: string, hostname: string) => Promise<void>;
18
+ export declare const reportRunResult: (baseUrl: string, apiKey: string, runId: string, resultText: string) => Promise<void>;
19
+ export declare const reportRunFailed: (baseUrl: string, apiKey: string, runId: string, error: string) => Promise<void>;
@@ -0,0 +1,44 @@
1
+ /**
2
+ * 録画 run-job の REST クライアント (Demo-to-MCP / ハイブリッド R2)。
3
+ *
4
+ * daemon が「自分宛の pending ジョブ取得 → claim → ローカル実行 → 結果報告」を行うための
5
+ * API 呼び出し群。Gemini 資格情報 (K1) もここで取得する。
6
+ */
7
+ import { ApiError } from './api-client.js';
8
+ const authHeaders = (apiKey) => ({
9
+ Authorization: `Bearer ${apiKey}`,
10
+ });
11
+ async function jsonOrThrow(response) {
12
+ if (!response.ok) {
13
+ throw new ApiError(response.status, await response.text().catch(() => ''));
14
+ }
15
+ return response.json();
16
+ }
17
+ /** 自分が実行すべき pending run の一覧 (playbook 同梱)。 */
18
+ export const listPendingRuns = async (baseUrl, apiKey) => {
19
+ const url = new URL('/v1/recordings/runs/pending', baseUrl).toString();
20
+ const response = await fetch(url, { headers: authHeaders(apiKey) });
21
+ return (await jsonOrThrow(response));
22
+ };
23
+ /** ローカル実行用の Gemini API キーを取得する (K1)。 */
24
+ export const getRunCredential = async (baseUrl, apiKey) => {
25
+ const url = new URL('/v1/recordings/runs/credential', baseUrl).toString();
26
+ const response = await fetch(url, { headers: authHeaders(apiKey) });
27
+ const body = (await jsonOrThrow(response));
28
+ return body.api_key;
29
+ };
30
+ const postRun = async (baseUrl, apiKey, path, body) => {
31
+ const url = new URL(path, baseUrl).toString();
32
+ const response = await fetch(url, {
33
+ method: 'POST',
34
+ headers: { ...authHeaders(apiKey), 'Content-Type': 'application/json' },
35
+ body: JSON.stringify(body),
36
+ });
37
+ await jsonOrThrow(response);
38
+ };
39
+ export const claimRun = (baseUrl, apiKey, runId, hostname) => postRun(baseUrl, apiKey, `/v1/recordings/runs/${runId}/claim`, { hostname });
40
+ export const reportRunResult = (baseUrl, apiKey, runId, resultText) => postRun(baseUrl, apiKey, `/v1/recordings/runs/${runId}/result`, {
41
+ result_text: resultText,
42
+ });
43
+ export const reportRunFailed = (baseUrl, apiKey, runId, error) => postRun(baseUrl, apiKey, `/v1/recordings/runs/${runId}/failed`, { error });
44
+ //# sourceMappingURL=run-client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"run-client.js","sourceRoot":"","sources":["../src/run-client.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAe3C,MAAM,WAAW,GAAG,CAAC,MAAc,EAA0B,EAAE,CAAC,CAAC;IAC/D,aAAa,EAAE,UAAU,MAAM,EAAE;CAClC,CAAC,CAAC;AAEH,KAAK,UAAU,WAAW,CAAC,QAAkB;IAC3C,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;QACjB,MAAM,IAAI,QAAQ,CAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;IAC7E,CAAC;IACD,OAAO,QAAQ,CAAC,IAAI,EAAE,CAAC;AACzB,CAAC;AAED,8CAA8C;AAC9C,MAAM,CAAC,MAAM,eAAe,GAAG,KAAK,EAAE,OAAe,EAAE,MAAc,EAAyB,EAAE;IAC9F,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,6BAA6B,EAAE,OAAO,CAAC,CAAC,QAAQ,EAAE,CAAC;IACvE,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE,EAAE,OAAO,EAAE,WAAW,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;IACpE,OAAO,CAAC,MAAM,WAAW,CAAC,QAAQ,CAAC,CAAiB,CAAC;AACvD,CAAC,CAAC;AAEF,wCAAwC;AACxC,MAAM,CAAC,MAAM,gBAAgB,GAAG,KAAK,EAAE,OAAe,EAAE,MAAc,EAAmB,EAAE;IACzF,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,gCAAgC,EAAE,OAAO,CAAC,CAAC,QAAQ,EAAE,CAAC;IAC1E,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE,EAAE,OAAO,EAAE,WAAW,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;IACpE,MAAM,IAAI,GAAG,CAAC,MAAM,WAAW,CAAC,QAAQ,CAAC,CAAwB,CAAC;IAClE,OAAO,IAAI,CAAC,OAAO,CAAC;AACtB,CAAC,CAAC;AAEF,MAAM,OAAO,GAAG,KAAK,EACnB,OAAe,EACf,MAAc,EACd,IAAY,EACZ,IAA6B,EACd,EAAE;IACjB,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,QAAQ,EAAE,CAAC;IAC9C,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE;QAChC,MAAM,EAAE,MAAM;QACd,OAAO,EAAE,EAAE,GAAG,WAAW,CAAC,MAAM,CAAC,EAAE,cAAc,EAAE,kBAAkB,EAAE;QACvE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC;KAC3B,CAAC,CAAC;IACH,MAAM,WAAW,CAAC,QAAQ,CAAC,CAAC;AAC9B,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,QAAQ,GAAG,CACtB,OAAe,EACf,MAAc,EACd,KAAa,EACb,QAAgB,EACD,EAAE,CAAC,OAAO,CAAC,OAAO,EAAE,MAAM,EAAE,uBAAuB,KAAK,QAAQ,EAAE,EAAE,QAAQ,EAAE,CAAC,CAAC;AAEjG,MAAM,CAAC,MAAM,eAAe,GAAG,CAC7B,OAAe,EACf,MAAc,EACd,KAAa,EACb,UAAkB,EACH,EAAE,CACjB,OAAO,CAAC,OAAO,EAAE,MAAM,EAAE,uBAAuB,KAAK,SAAS,EAAE;IAC9D,WAAW,EAAE,UAAU;CACxB,CAAC,CAAC;AAEL,MAAM,CAAC,MAAM,eAAe,GAAG,CAC7B,OAAe,EACf,MAAc,EACd,KAAa,EACb,KAAa,EACE,EAAE,CAAC,OAAO,CAAC,OAAO,EAAE,MAAM,EAAE,uBAAuB,KAAK,SAAS,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC"}
@@ -0,0 +1,25 @@
1
+ /**
2
+ * 録画 run-job のローカル実行ワーカー (Demo-to-MCP / ハイブリッド R2)。
3
+ *
4
+ * daemon から呼ばれ、自分宛の pending run を順に
5
+ * claim → 鍵取得(K1) → playbook をローカルブラウザ実行 → 結果/失敗を報告
6
+ * する。computer use の Gemini 呼び出しはローカルから直接行われ、スクショは
7
+ * Lnar を経由しない。
8
+ *
9
+ * 依存 (API クライアント / 実行器) は注入可能にしてテストしやすくしている。
10
+ */
11
+ import { claimRun, getRunCredential, listPendingRuns, type PendingRun, reportRunFailed, reportRunResult } from './run-client.js';
12
+ import { runPlaybook } from './runtime/runner.js';
13
+ export interface RunWorkerDeps {
14
+ listPendingRuns: typeof listPendingRuns;
15
+ claimRun: typeof claimRun;
16
+ getRunCredential: typeof getRunCredential;
17
+ reportRunResult: typeof reportRunResult;
18
+ reportRunFailed: typeof reportRunFailed;
19
+ runPlaybook: typeof runPlaybook;
20
+ log?: (message: string) => void;
21
+ }
22
+ /** pending run を 1 件実行して結果を報告する。 */
23
+ export declare function executeRun(baseUrl: string, token: string, hostname: string, run: PendingRun, deps: RunWorkerDeps): Promise<void>;
24
+ /** 自分宛の pending run をすべて実行する。実行件数を返す。 */
25
+ export declare function executePendingRuns(baseUrl: string, token: string, hostname: string, deps?: RunWorkerDeps): Promise<number>;
@@ -0,0 +1,85 @@
1
+ /**
2
+ * 録画 run-job のローカル実行ワーカー (Demo-to-MCP / ハイブリッド R2)。
3
+ *
4
+ * daemon から呼ばれ、自分宛の pending run を順に
5
+ * claim → 鍵取得(K1) → playbook をローカルブラウザ実行 → 結果/失敗を報告
6
+ * する。computer use の Gemini 呼び出しはローカルから直接行われ、スクショは
7
+ * Lnar を経由しない。
8
+ *
9
+ * 依存 (API クライアント / 実行器) は注入可能にしてテストしやすくしている。
10
+ */
11
+ import { ApiError } from './api-client.js';
12
+ import { claimRun, getRunCredential, listPendingRuns, reportRunFailed, reportRunResult, } from './run-client.js';
13
+ import { parsePlaybook } from './runtime/playbook.js';
14
+ import { runPlaybook } from './runtime/runner.js';
15
+ const defaultDeps = () => ({
16
+ listPendingRuns,
17
+ claimRun,
18
+ getRunCredential,
19
+ reportRunResult,
20
+ reportRunFailed,
21
+ runPlaybook,
22
+ });
23
+ /** run.params (任意値) を文字列パラメータに正規化する。 */
24
+ function toStringParams(params) {
25
+ const out = {};
26
+ for (const [k, v] of Object.entries(params ?? {}))
27
+ out[k] = String(v);
28
+ return out;
29
+ }
30
+ function outcomeMessage(outcome) {
31
+ if (outcome.completed)
32
+ return outcome.finalText ?? 'タスクを完了しました。';
33
+ if (outcome.aborted)
34
+ return 'タスクは安全確認の拒否により中断されました。';
35
+ return `タスクは上限ターン数に達しました (${outcome.turns} turns)。`;
36
+ }
37
+ /** pending run を 1 件実行して結果を報告する。 */
38
+ export async function executeRun(baseUrl, token, hostname, run, deps) {
39
+ const log = deps.log ?? (() => { });
40
+ // claim は排他取得。競合 (409 = 別 daemon が先に取得) のときだけスキップする
41
+ // (走っている run を上書きしないため、failed 報告もしない)。それ以外 (認証切れ/
42
+ // ネットワーク/5xx 等) は握りつぶさず上位へ伝播し、daemon 側の try/catch で
43
+ // 可視化する (黙ってスキップすると run が pending のまま詰まり障害検知が遅れる)。
44
+ try {
45
+ await deps.claimRun(baseUrl, token, run.id, hostname);
46
+ }
47
+ catch (err) {
48
+ if (err instanceof ApiError && err.status === 409) {
49
+ log(`run ${run.id} claim skipped (409 conflict)`);
50
+ return;
51
+ }
52
+ throw err;
53
+ }
54
+ // claim 後の実行・報告。ここでの失敗は run を failed として報告する。
55
+ try {
56
+ const apiKey = await deps.getRunCredential(baseUrl, token);
57
+ const playbook = parsePlaybook(run.playbook);
58
+ const outcome = await deps.runPlaybook(playbook, toStringParams(run.params), {
59
+ apiKey,
60
+ logger: log,
61
+ startUrl: run.start_url ?? null,
62
+ purpose: run.purpose ?? null,
63
+ });
64
+ if (outcome.completed) {
65
+ await deps.reportRunResult(baseUrl, token, run.id, outcomeMessage(outcome));
66
+ }
67
+ else {
68
+ await deps.reportRunFailed(baseUrl, token, run.id, outcomeMessage(outcome));
69
+ }
70
+ }
71
+ catch (err) {
72
+ const message = err instanceof Error ? err.message : String(err);
73
+ log(`run ${run.id} failed: ${message}`);
74
+ await deps.reportRunFailed(baseUrl, token, run.id, message).catch(() => { });
75
+ }
76
+ }
77
+ /** 自分宛の pending run をすべて実行する。実行件数を返す。 */
78
+ export async function executePendingRuns(baseUrl, token, hostname, deps = defaultDeps()) {
79
+ const runs = await deps.listPendingRuns(baseUrl, token);
80
+ for (const run of runs) {
81
+ await executeRun(baseUrl, token, hostname, run, deps);
82
+ }
83
+ return runs.length;
84
+ }
85
+ //# sourceMappingURL=run-worker.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"run-worker.js","sourceRoot":"","sources":["../src/run-worker.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Gemini の function_call を Playwright 操作へ変換して実行する。
3
+ * 座標は正規化 (0-999) で来るため実ビューポートへスケールする。
4
+ */
5
+ import type { Page } from 'playwright';
6
+ import type { FunctionCallStep } from './types.js';
7
+ export interface ActionResult {
8
+ readonly name: string;
9
+ readonly callId: string;
10
+ readonly detail: Record<string, unknown>;
11
+ }
12
+ /** 1 つの function_call を実行する。未知のアクションは警告にとどめループを止めない。 */
13
+ export declare function executeAction(page: Page, step: FunctionCallStep): Promise<ActionResult>;
@@ -0,0 +1,107 @@
1
+ const NORM = 1000;
2
+ function toPixel(value, size) {
3
+ return Math.round(((value ?? 0) / NORM) * size);
4
+ }
5
+ function scaleScroll(magnitude, size) {
6
+ return magnitude ?? Math.round(size / 3);
7
+ }
8
+ async function applyScroll(page, args, vp) {
9
+ // スクロール原点が未指定なら (0,0) ではなくビューポート中央を使う。
10
+ const x = args.x === undefined ? Math.round(vp.width / 2) : toPixel(args.x, vp.width);
11
+ const y = args.y === undefined ? Math.round(vp.height / 2) : toPixel(args.y, vp.height);
12
+ const dir = args.direction ?? 'down';
13
+ const dx = dir === 'right'
14
+ ? scaleScroll(args.magnitude_in_pixels, vp.width)
15
+ : dir === 'left'
16
+ ? -scaleScroll(args.magnitude_in_pixels, vp.width)
17
+ : 0;
18
+ const dy = dir === 'down'
19
+ ? scaleScroll(args.magnitude_in_pixels, vp.height)
20
+ : dir === 'up'
21
+ ? -scaleScroll(args.magnitude_in_pixels, vp.height)
22
+ : 0;
23
+ await page.mouse.move(x, y);
24
+ await page.mouse.wheel(dx, dy);
25
+ }
26
+ /** 1 つの function_call を実行する。未知のアクションは警告にとどめループを止めない。 */
27
+ export async function executeAction(page, step) {
28
+ const args = step.arguments;
29
+ const vp = page.viewportSize() ?? { width: 1280, height: 800 };
30
+ const x = toPixel(args.x, vp.width);
31
+ const y = toPixel(args.y, vp.height);
32
+ // クリック系は座標必須。未指定で (0,0) を誤クリックしないよう no-op で返す。
33
+ const needsXY = step.name === 'click' || step.name === 'double_click' || step.name === 'right_click';
34
+ if (needsXY && (args.x === undefined || args.y === undefined)) {
35
+ console.error(`[actions] ${step.name}: 座標未指定のためスキップ`);
36
+ return {
37
+ name: step.name,
38
+ callId: step.id,
39
+ detail: { url: page.url(), skipped: 'missing coordinates' },
40
+ };
41
+ }
42
+ switch (step.name) {
43
+ case 'click':
44
+ await page.mouse.click(x, y);
45
+ break;
46
+ case 'double_click':
47
+ await page.mouse.dblclick(x, y);
48
+ break;
49
+ case 'right_click':
50
+ await page.mouse.click(x, y, { button: 'right' });
51
+ break;
52
+ case 'type':
53
+ if (args.text)
54
+ await page.keyboard.type(args.text);
55
+ if (args.press_enter)
56
+ await page.keyboard.press('Enter');
57
+ break;
58
+ case 'navigate':
59
+ if (args.url)
60
+ await page.goto(args.url, { waitUntil: 'domcontentloaded' });
61
+ break;
62
+ case 'scroll':
63
+ await applyScroll(page, args, vp);
64
+ break;
65
+ case 'press_key':
66
+ if (args.key)
67
+ await page.keyboard.press(args.key);
68
+ break;
69
+ case 'hotkey':
70
+ if (args.keys && args.keys.length > 0)
71
+ await page.keyboard.press(args.keys.join('+'));
72
+ break;
73
+ case 'drag_and_drop': {
74
+ if (args.start_x === undefined ||
75
+ args.start_y === undefined ||
76
+ args.end_x === undefined ||
77
+ args.end_y === undefined) {
78
+ console.error('[actions] drag_and_drop: 座標未指定のためスキップ');
79
+ return {
80
+ name: step.name,
81
+ callId: step.id,
82
+ detail: { url: page.url(), skipped: 'missing coordinates' },
83
+ };
84
+ }
85
+ await page.mouse.move(toPixel(args.start_x, vp.width), toPixel(args.start_y, vp.height));
86
+ await page.mouse.down();
87
+ await page.mouse.move(toPixel(args.end_x, vp.width), toPixel(args.end_y, vp.height));
88
+ await page.mouse.up();
89
+ break;
90
+ }
91
+ case 'go_back':
92
+ await page.goBack({ waitUntil: 'domcontentloaded' }).catch(() => { });
93
+ break;
94
+ case 'go_forward':
95
+ await page.goForward({ waitUntil: 'domcontentloaded' }).catch(() => { });
96
+ break;
97
+ case 'wait':
98
+ await page.waitForTimeout((args.seconds ?? 1) * 1000);
99
+ break;
100
+ case 'take_screenshot':
101
+ break; // 毎ターン loop が取得するため追加操作なし
102
+ default:
103
+ console.error(`[actions] 未対応のアクション: ${String(step.name)}`);
104
+ }
105
+ return { name: step.name, callId: step.id, detail: { url: page.url() } };
106
+ }
107
+ //# sourceMappingURL=actions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"actions.js","sourceRoot":"","sources":["../../src/runtime/actions.ts"],"names":[],"mappings":"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"}