@refrainai/cli 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ai-model-FM6GWCID.js +37 -0
- package/dist/ai-model-FM6GWCID.js.map +1 -0
- package/dist/chunk-2BVDAJZT.js +236 -0
- package/dist/chunk-2BVDAJZT.js.map +1 -0
- package/dist/chunk-2H7UOFLK.js +11 -0
- package/dist/chunk-2H7UOFLK.js.map +1 -0
- package/dist/chunk-7UCVPKD4.js +902 -0
- package/dist/chunk-7UCVPKD4.js.map +1 -0
- package/dist/chunk-AG3CFMYU.js +36 -0
- package/dist/chunk-AG3CFMYU.js.map +1 -0
- package/dist/chunk-CLYJHKPY.js +1131 -0
- package/dist/chunk-CLYJHKPY.js.map +1 -0
- package/dist/chunk-D5SI2PHK.js +74 -0
- package/dist/chunk-D5SI2PHK.js.map +1 -0
- package/dist/chunk-DJVUITRB.js +9084 -0
- package/dist/chunk-DJVUITRB.js.map +1 -0
- package/dist/chunk-H47NWH7N.js +4427 -0
- package/dist/chunk-H47NWH7N.js.map +1 -0
- package/dist/chunk-HQDXLWAY.js +109 -0
- package/dist/chunk-HQDXLWAY.js.map +1 -0
- package/dist/chunk-IGFCYKHC.js +1974 -0
- package/dist/chunk-IGFCYKHC.js.map +1 -0
- package/dist/chunk-RT664YIO.js +245 -0
- package/dist/chunk-RT664YIO.js.map +1 -0
- package/dist/chunk-RYIJPYM3.js +164 -0
- package/dist/chunk-RYIJPYM3.js.map +1 -0
- package/dist/chunk-TDSM3UXI.js +40 -0
- package/dist/chunk-TDSM3UXI.js.map +1 -0
- package/dist/chunk-UGPXCQY3.js +778 -0
- package/dist/chunk-UGPXCQY3.js.map +1 -0
- package/dist/chunk-VPK2MQAZ.js +589 -0
- package/dist/chunk-VPK2MQAZ.js.map +1 -0
- package/dist/chunk-WEYR56ZN.js +953 -0
- package/dist/chunk-WEYR56ZN.js.map +1 -0
- package/dist/chunk-XMFCXPYU.js +275 -0
- package/dist/chunk-XMFCXPYU.js.map +1 -0
- package/dist/chunk-Z33FCOTZ.js +251 -0
- package/dist/chunk-Z33FCOTZ.js.map +1 -0
- package/dist/cli.js +59 -0
- package/dist/cli.js.map +1 -0
- package/dist/compose-MTSIJY5D.js +547 -0
- package/dist/compose-MTSIJY5D.js.map +1 -0
- package/dist/config-ZSUNCFXR.js +9 -0
- package/dist/config-ZSUNCFXR.js.map +1 -0
- package/dist/fix-runbook-ZSBOTLC2.js +294 -0
- package/dist/fix-runbook-ZSBOTLC2.js.map +1 -0
- package/dist/google-sheets-DRWIVEVC.js +482 -0
- package/dist/google-sheets-DRWIVEVC.js.map +1 -0
- package/dist/registry-LZLYTNDJ.js +17 -0
- package/dist/registry-LZLYTNDJ.js.map +1 -0
- package/dist/runbook-data-helpers-KRR2SH76.js +16 -0
- package/dist/runbook-data-helpers-KRR2SH76.js.map +1 -0
- package/dist/runbook-executor-K7T6RJWJ.js +1480 -0
- package/dist/runbook-executor-K7T6RJWJ.js.map +1 -0
- package/dist/runbook-generator-MPXJBQ5N.js +800 -0
- package/dist/runbook-generator-MPXJBQ5N.js.map +1 -0
- package/dist/runbook-schema-3T6TP3JJ.js +35 -0
- package/dist/runbook-schema-3T6TP3JJ.js.map +1 -0
- package/dist/runbook-store-G5GUOWRR.js +11 -0
- package/dist/runbook-store-G5GUOWRR.js.map +1 -0
- package/dist/schema-5G6UQSPT.js +91 -0
- package/dist/schema-5G6UQSPT.js.map +1 -0
- package/dist/server-AG3LXQBI.js +8778 -0
- package/dist/server-AG3LXQBI.js.map +1 -0
- package/dist/tenant-ai-config-QPFEJUVJ.js +14 -0
- package/dist/tenant-ai-config-QPFEJUVJ.js.map +1 -0
- package/dist/yaml-patcher-VGUS2JGH.js +15 -0
- package/dist/yaml-patcher-VGUS2JGH.js.map +1 -0
- package/package.json +37 -0
|
@@ -0,0 +1,1974 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
AgentBrowser,
|
|
4
|
+
DownloadManager,
|
|
5
|
+
InMemoryDataStore,
|
|
6
|
+
classifyFailure,
|
|
7
|
+
createDebugLogger,
|
|
8
|
+
createReviewUserPrompt,
|
|
9
|
+
findElementInSnapshot,
|
|
10
|
+
formatDuration,
|
|
11
|
+
getAgentInstructions,
|
|
12
|
+
getInitialUserMessage,
|
|
13
|
+
getReviewSystemPrompt,
|
|
14
|
+
noopLogger,
|
|
15
|
+
parseAllElements,
|
|
16
|
+
parseAndAppendToMemory,
|
|
17
|
+
sanitizeBrowserError,
|
|
18
|
+
sleep
|
|
19
|
+
} from "./chunk-DJVUITRB.js";
|
|
20
|
+
import {
|
|
21
|
+
filterSnapshot
|
|
22
|
+
} from "./chunk-XMFCXPYU.js";
|
|
23
|
+
import {
|
|
24
|
+
createSkills,
|
|
25
|
+
initBuiltinSkills
|
|
26
|
+
} from "./chunk-AG3CFMYU.js";
|
|
27
|
+
import {
|
|
28
|
+
getLocale,
|
|
29
|
+
log,
|
|
30
|
+
promptSelect,
|
|
31
|
+
promptText,
|
|
32
|
+
t,
|
|
33
|
+
tf
|
|
34
|
+
} from "./chunk-7UCVPKD4.js";
|
|
35
|
+
import {
|
|
36
|
+
getModel,
|
|
37
|
+
trackedGenerateObject,
|
|
38
|
+
trackedGenerateText
|
|
39
|
+
} from "./chunk-UGPXCQY3.js";
|
|
40
|
+
import {
|
|
41
|
+
ParsedRunbookSchema
|
|
42
|
+
} from "./chunk-RT664YIO.js";
|
|
43
|
+
|
|
44
|
+
// src/runbook-generator/exploration-agent.ts
|
|
45
|
+
import { stepCountIs } from "ai";
|
|
46
|
+
import { z as z2 } from "zod";
|
|
47
|
+
|
|
48
|
+
// src/runbook-generator/browser-tool.ts
|
|
49
|
+
import { tool } from "ai";
|
|
50
|
+
import { z } from "zod";
|
|
51
|
+
|
|
52
|
+
// src/browser/selector-builder.ts
|
|
53
|
+
var ROLE_TAG_MAP = {
|
|
54
|
+
textbox: { tagName: "input", inputType: "text" },
|
|
55
|
+
button: { tagName: "button", role: "button" },
|
|
56
|
+
link: { tagName: "a", role: "link" },
|
|
57
|
+
checkbox: { tagName: "input", inputType: "checkbox" },
|
|
58
|
+
radio: { tagName: "input", inputType: "radio" },
|
|
59
|
+
combobox: { tagName: "select", role: "combobox" },
|
|
60
|
+
searchbox: { tagName: "input", inputType: "search" },
|
|
61
|
+
slider: { tagName: "input", inputType: "range" },
|
|
62
|
+
spinbutton: { tagName: "input", inputType: "number" },
|
|
63
|
+
switch: { tagName: "input", inputType: "checkbox", role: "switch" },
|
|
64
|
+
tab: { tagName: "button", role: "tab" },
|
|
65
|
+
menuitem: { tagName: "button", role: "menuitem" },
|
|
66
|
+
option: { tagName: "option", role: "option" },
|
|
67
|
+
heading: { tagName: "h2", role: "heading" },
|
|
68
|
+
img: { tagName: "img", role: "img" },
|
|
69
|
+
navigation: { tagName: "nav", role: "navigation" },
|
|
70
|
+
listitem: { tagName: "li", role: "listitem" }
|
|
71
|
+
};
|
|
72
|
+
function buildSelector(element) {
|
|
73
|
+
const mapping = ROLE_TAG_MAP[element.role];
|
|
74
|
+
const tagName = mapping?.tagName ?? element.role;
|
|
75
|
+
const selector = { tagName };
|
|
76
|
+
if (mapping?.role) {
|
|
77
|
+
selector.role = mapping.role;
|
|
78
|
+
} else if (element.role !== tagName) {
|
|
79
|
+
selector.role = element.role;
|
|
80
|
+
}
|
|
81
|
+
if (mapping?.inputType) {
|
|
82
|
+
selector.inputType = mapping.inputType;
|
|
83
|
+
}
|
|
84
|
+
if (element.name) {
|
|
85
|
+
selector.ariaLabel = element.name;
|
|
86
|
+
if (element.role === "button" || element.role === "link") {
|
|
87
|
+
selector.innerText = element.name.slice(0, 200);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
if (element.attributes.placeholder) {
|
|
91
|
+
selector.placeholder = element.attributes.placeholder;
|
|
92
|
+
}
|
|
93
|
+
if (element.attributes.name) {
|
|
94
|
+
selector.name = element.attributes.name;
|
|
95
|
+
}
|
|
96
|
+
return selector;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// src/harness/loop-detector.ts
|
|
100
|
+
function stepHash(step) {
|
|
101
|
+
const urlPath = extractPath(step.url);
|
|
102
|
+
return `${step.action.action}|${step.action.selector ?? ""}|${urlPath}`;
|
|
103
|
+
}
|
|
104
|
+
function extractPath(url) {
|
|
105
|
+
try {
|
|
106
|
+
return new URL(url).pathname;
|
|
107
|
+
} catch {
|
|
108
|
+
return url;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
function detectLoop(recordedSteps, previousNudgeCount, windowSize = 4) {
|
|
112
|
+
const noLoop = {
|
|
113
|
+
isLoop: false,
|
|
114
|
+
loopType: null,
|
|
115
|
+
nudgeMessage: null,
|
|
116
|
+
nudgeCount: previousNudgeCount,
|
|
117
|
+
shouldPromptHuman: false
|
|
118
|
+
};
|
|
119
|
+
if (recordedSteps.length < 2) return noLoop;
|
|
120
|
+
const last = stepHash(recordedSteps[recordedSteps.length - 1]);
|
|
121
|
+
const secondLast = stepHash(recordedSteps[recordedSteps.length - 2]);
|
|
122
|
+
if (last === secondLast) {
|
|
123
|
+
return buildResult("exact_repeat", previousNudgeCount);
|
|
124
|
+
}
|
|
125
|
+
if (recordedSteps.length >= windowSize) {
|
|
126
|
+
const recentSteps = recordedSteps.slice(-windowSize);
|
|
127
|
+
const dataCollectionActions = /* @__PURE__ */ new Set(["download", "extract", "memory_append", "memory_aggregate", "export"]);
|
|
128
|
+
const hasDataCollection = recentSteps.some((s) => dataCollectionActions.has(s.action.action));
|
|
129
|
+
if (!hasDataCollection) {
|
|
130
|
+
const recentHashes = recentSteps.map(stepHash);
|
|
131
|
+
const halfLen = Math.floor(windowSize / 2);
|
|
132
|
+
const firstHalf = recentHashes.slice(0, halfLen).join(",");
|
|
133
|
+
const secondHalf = recentHashes.slice(halfLen, halfLen * 2).join(",");
|
|
134
|
+
if (firstHalf === secondHalf) {
|
|
135
|
+
return buildResult("action_cycle", previousNudgeCount);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
if (recordedSteps.length >= 4) {
|
|
140
|
+
const recentUrls = recordedSteps.slice(-4).map((s) => extractPath(s.url));
|
|
141
|
+
if (recentUrls[0] === recentUrls[2] && recentUrls[1] === recentUrls[3] && recentUrls[0] !== recentUrls[1]) {
|
|
142
|
+
return buildResult("url_bounce", previousNudgeCount);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
const inputActions = /* @__PURE__ */ new Set(["fill", "type", "select", "check", "uncheck"]);
|
|
146
|
+
const selectorCounts = /* @__PURE__ */ new Map();
|
|
147
|
+
for (const step of recordedSteps) {
|
|
148
|
+
if (!step.success) continue;
|
|
149
|
+
if (!inputActions.has(step.action.action)) continue;
|
|
150
|
+
if (!step.action.selector) continue;
|
|
151
|
+
const key = `${step.action.action}|${step.action.selector}`;
|
|
152
|
+
const existing = selectorCounts.get(key);
|
|
153
|
+
if (existing) {
|
|
154
|
+
existing.count++;
|
|
155
|
+
} else {
|
|
156
|
+
selectorCounts.set(key, { count: 1, description: step.action.description });
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
const refills = [...selectorCounts.entries()].filter(([, v]) => v.count >= 3).map(([k, v]) => ({ selector: k.split("|")[1], count: v.count, description: v.description }));
|
|
160
|
+
if (refills.length > 0) {
|
|
161
|
+
const details = refills.map((r) => `${r.selector} "${r.description}" (${r.count}\u56DE)`).join(", ");
|
|
162
|
+
return buildRefillResult(details, previousNudgeCount);
|
|
163
|
+
}
|
|
164
|
+
return noLoop;
|
|
165
|
+
}
|
|
166
|
+
function buildResult(loopType, previousNudgeCount) {
|
|
167
|
+
const nudgeCount = previousNudgeCount + 1;
|
|
168
|
+
const messages = {
|
|
169
|
+
exact_repeat: "\u540C\u3058\u64CD\u4F5C\u3092\u7E70\u308A\u8FD4\u3057\u3066\u3044\u307E\u3059\u3002\u5225\u306E\u30A2\u30D7\u30ED\u30FC\u30C1\u3092\u8A66\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
|
|
170
|
+
action_cycle: "\u64CD\u4F5C\u304C\u30EB\u30FC\u30D7\u3057\u3066\u3044\u307E\u3059\u3002\u7570\u306A\u308B\u6226\u7565\u3092\u691C\u8A0E\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
|
|
171
|
+
url_bounce: "\u540C\u3058URL\u9593\u3092\u884C\u304D\u6765\u3057\u3066\u3044\u307E\u3059\u3002\u76EE\u7684\u306E\u30DA\u30FC\u30B8\u306B\u7559\u307E\u3063\u3066\u64CD\u4F5C\u3092\u9032\u3081\u3066\u304F\u3060\u3055\u3044\u3002"
|
|
172
|
+
};
|
|
173
|
+
return {
|
|
174
|
+
isLoop: true,
|
|
175
|
+
loopType,
|
|
176
|
+
nudgeMessage: messages[loopType],
|
|
177
|
+
nudgeCount,
|
|
178
|
+
// 1回目はナッジのみ、2回目以降は人間に確認
|
|
179
|
+
shouldPromptHuman: nudgeCount >= 2
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
function buildRefillResult(details, previousNudgeCount) {
|
|
183
|
+
const nudgeCount = previousNudgeCount + 1;
|
|
184
|
+
return {
|
|
185
|
+
isLoop: true,
|
|
186
|
+
loopType: "selector_refill",
|
|
187
|
+
nudgeMessage: `\u540C\u3058\u30D5\u30A9\u30FC\u30E0\u30D5\u30A3\u30FC\u30EB\u30C9\u3092\u7E70\u308A\u8FD4\u3057\u5165\u529B\u3057\u3066\u3044\u307E\u3059\u3002\u4EE5\u4E0B\u306F\u65E2\u306B\u5165\u529B\u6E08\u307F\u3067\u3059: ${details}\u3002\u5165\u529B\u6E08\u307F\u30D5\u30A3\u30FC\u30EB\u30C9\u3092\u30B9\u30AD\u30C3\u30D7\u3057\u3001\u672A\u5165\u529B\u30D5\u30A3\u30FC\u30EB\u30C9\u306B\u9032\u3093\u3067\u304F\u3060\u3055\u3044\u3002`,
|
|
188
|
+
nudgeCount,
|
|
189
|
+
shouldPromptHuman: nudgeCount >= 2
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// src/runbook-generator/browser-tool.ts
|
|
194
|
+
var suggestedCaptureSchema = z.object({
|
|
195
|
+
name: z.string(),
|
|
196
|
+
strategy: z.enum(["snapshot", "url", "ai", "expression", "evaluate"]),
|
|
197
|
+
description: z.string().optional(),
|
|
198
|
+
pattern: z.string().optional(),
|
|
199
|
+
group: z.number().optional(),
|
|
200
|
+
prompt: z.string().optional(),
|
|
201
|
+
expression: z.string().optional()
|
|
202
|
+
});
|
|
203
|
+
var aggregationSchema = z.object({
|
|
204
|
+
collection: z.string(),
|
|
205
|
+
field: z.string(),
|
|
206
|
+
operation: z.enum(["sum", "count", "concat", "min", "max", "avg", "unique_count"]),
|
|
207
|
+
outputVariable: z.string()
|
|
208
|
+
});
|
|
209
|
+
var actionSchema = z.object({
|
|
210
|
+
action: z.enum([
|
|
211
|
+
"click",
|
|
212
|
+
"fill",
|
|
213
|
+
"type",
|
|
214
|
+
"select",
|
|
215
|
+
"check",
|
|
216
|
+
"uncheck",
|
|
217
|
+
"navigate",
|
|
218
|
+
"wait",
|
|
219
|
+
"scroll",
|
|
220
|
+
"extract",
|
|
221
|
+
"download",
|
|
222
|
+
"export",
|
|
223
|
+
"memory_append",
|
|
224
|
+
"memory_aggregate",
|
|
225
|
+
"key"
|
|
226
|
+
]),
|
|
227
|
+
selector: z.string().optional(),
|
|
228
|
+
value: z.string().optional(),
|
|
229
|
+
description: z.string(),
|
|
230
|
+
inputCategory: z.enum(["credential", "user_data", "fixed", "navigation"]).optional(),
|
|
231
|
+
variableName: z.string().optional(),
|
|
232
|
+
suggestedCaptures: z.array(suggestedCaptureSchema).optional(),
|
|
233
|
+
/** extract 用: ページ内で実行する JavaScript */
|
|
234
|
+
script: z.string().optional(),
|
|
235
|
+
/** extract 用: AI にデータを抽出させるプロンプト */
|
|
236
|
+
extractPrompt: z.string().optional(),
|
|
237
|
+
/** memory_append 用: 蓄積先コレクション名 */
|
|
238
|
+
memoryCollection: z.string().optional(),
|
|
239
|
+
/** memory_aggregate 用: 集計設定 */
|
|
240
|
+
aggregation: aggregationSchema.optional(),
|
|
241
|
+
/** download 用: 保存先パス */
|
|
242
|
+
downloadPath: z.string().optional(),
|
|
243
|
+
/** export 用: 出力対象コレクション名 */
|
|
244
|
+
exportCollection: z.string().optional(),
|
|
245
|
+
/** export 用: 出力フォーマット */
|
|
246
|
+
exportFormat: z.enum(["csv", "json"]).optional(),
|
|
247
|
+
/** export 用: 出力先パス */
|
|
248
|
+
exportPath: z.string().optional(),
|
|
249
|
+
/** key 用: 押下するキーボードキー(Playwright 形式、例: "Enter", "Tab") */
|
|
250
|
+
keys: z.string().optional()
|
|
251
|
+
});
|
|
252
|
+
var browserInputSchema = z.object({
|
|
253
|
+
actions: z.array(actionSchema).describe(
|
|
254
|
+
"\u5B9F\u884C\u3059\u308B\u30D6\u30E9\u30A6\u30B6\u64CD\u4F5C\u306E\u914D\u5217\u3002\u7A7A\u914D\u5217\u306A\u3089\u73FE\u5728\u306E\u30DA\u30FC\u30B8\u30B9\u30CA\u30C3\u30D7\u30B7\u30E7\u30C3\u30C8\u306E\u307F\u8FD4\u3059\u3002"
|
|
255
|
+
)
|
|
256
|
+
});
|
|
257
|
+
function truncateExtractedData(data, maxLength) {
|
|
258
|
+
try {
|
|
259
|
+
const parsed = JSON.parse(data);
|
|
260
|
+
if (Array.isArray(parsed)) {
|
|
261
|
+
const total = parsed.length;
|
|
262
|
+
const preview = parsed.slice(0, 3);
|
|
263
|
+
if (total <= 3) return JSON.stringify(preview);
|
|
264
|
+
return `${JSON.stringify(preview).slice(0, maxLength)} ... (\u5168${total}\u4EF6)`;
|
|
265
|
+
}
|
|
266
|
+
} catch {
|
|
267
|
+
}
|
|
268
|
+
if (data.length <= maxLength) return data;
|
|
269
|
+
return `${data.slice(0, maxLength)}... (${data.length}\u6587\u5B57)`;
|
|
270
|
+
}
|
|
271
|
+
function getActionDelay(action, baseDelay) {
|
|
272
|
+
switch (action) {
|
|
273
|
+
case "navigate":
|
|
274
|
+
return Math.max(baseDelay, 1e3);
|
|
275
|
+
case "click":
|
|
276
|
+
return Math.round(baseDelay * 0.6);
|
|
277
|
+
case "fill":
|
|
278
|
+
case "type":
|
|
279
|
+
return Math.round(baseDelay * 0.2);
|
|
280
|
+
case "select":
|
|
281
|
+
return Math.round(baseDelay * 0.4);
|
|
282
|
+
default:
|
|
283
|
+
return baseDelay;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
function cancellableSleep(ms, ic) {
|
|
287
|
+
if (!ic) return sleep(ms);
|
|
288
|
+
return new Promise((resolve) => {
|
|
289
|
+
const interval = 50;
|
|
290
|
+
let elapsed = 0;
|
|
291
|
+
const timer = setInterval(() => {
|
|
292
|
+
elapsed += interval;
|
|
293
|
+
if (elapsed >= ms || ic.isCancelRequested() || ic.isPauseRequested()) {
|
|
294
|
+
clearInterval(timer);
|
|
295
|
+
resolve();
|
|
296
|
+
}
|
|
297
|
+
}, interval);
|
|
298
|
+
});
|
|
299
|
+
}
|
|
300
|
+
function createBrowserTool(browser, recordedSteps, config, dataStore, downloadManager) {
|
|
301
|
+
let ordinal = 0;
|
|
302
|
+
let nudgeCount = 0;
|
|
303
|
+
let lastSnapshot = null;
|
|
304
|
+
let lastExtractedData;
|
|
305
|
+
const dl = config.debugLogger;
|
|
306
|
+
const filledFieldsMap = /* @__PURE__ */ new Map();
|
|
307
|
+
const inputActionTypes = /* @__PURE__ */ new Set(["fill", "type", "select", "check", "uncheck"]);
|
|
308
|
+
const scratchpadEntries = [];
|
|
309
|
+
const MAX_SCRATCHPAD = 10;
|
|
310
|
+
let lastUrl = "";
|
|
311
|
+
const applySnapshotTransform = async (raw, url, filterOptions) => {
|
|
312
|
+
if (config.skills) {
|
|
313
|
+
for (const skill of config.skills) {
|
|
314
|
+
if (skill.shouldActivate(url)) {
|
|
315
|
+
try {
|
|
316
|
+
const result = await skill.transformSnapshot(browser, raw, {
|
|
317
|
+
url,
|
|
318
|
+
locale: getLocale()
|
|
319
|
+
});
|
|
320
|
+
if (result.extractedData) {
|
|
321
|
+
lastExtractedData = result.extractedData;
|
|
322
|
+
}
|
|
323
|
+
return result;
|
|
324
|
+
} catch {
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
const filtered = config.snapshotFilter ? filterSnapshot(raw, filterOptions) : raw;
|
|
330
|
+
return { snapshot: filtered };
|
|
331
|
+
};
|
|
332
|
+
return tool({
|
|
333
|
+
description: "\u30D6\u30E9\u30A6\u30B6\u64CD\u4F5C\u3092\u5B9F\u884C\u3057\u3001\u64CD\u4F5C\u5F8C\u306E\u30DA\u30FC\u30B8\u30B9\u30CA\u30C3\u30D7\u30B7\u30E7\u30C3\u30C8\u3092\u8FD4\u3059\u3002actions\u304C\u7A7A\u306A\u3089\u73FE\u5728\u306E\u30DA\u30FC\u30B8\u72B6\u614B\u306E\u307F\u8FD4\u3059\u3002",
|
|
334
|
+
inputSchema: browserInputSchema,
|
|
335
|
+
execute: async (inputData) => {
|
|
336
|
+
config.onToolCallStart?.(recordedSteps.length);
|
|
337
|
+
try {
|
|
338
|
+
const ic = config.interventionController;
|
|
339
|
+
let interventionNudge;
|
|
340
|
+
if (ic?.isCancelRequested()) {
|
|
341
|
+
const snapshot = lastSnapshot ?? await browser.snapshot();
|
|
342
|
+
const finalSnapshot2 = config.snapshotFilter ? filterSnapshot(snapshot) : snapshot;
|
|
343
|
+
return {
|
|
344
|
+
results: [],
|
|
345
|
+
snapshot: finalSnapshot2,
|
|
346
|
+
url: await browser.url(),
|
|
347
|
+
nudgeMessage: "\u30E6\u30FC\u30B6\u30FC\u304C\u63A2\u7D22\u306E\u30AD\u30E3\u30F3\u30BB\u30EB\u3092\u8981\u6C42\u3057\u307E\u3057\u305F\u3002goalAchieved: false \u3067\u6700\u7D42\u5831\u544A\u3092\u4F5C\u6210\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
|
|
348
|
+
stepCount: recordedSteps.length
|
|
349
|
+
};
|
|
350
|
+
}
|
|
351
|
+
if (ic?.isPauseRequested()) {
|
|
352
|
+
const currentUrl2 = await browser.url();
|
|
353
|
+
const interventionResult = await ic.collectIntervention(
|
|
354
|
+
recordedSteps.length,
|
|
355
|
+
currentUrl2
|
|
356
|
+
);
|
|
357
|
+
if (interventionResult.action === "abort") {
|
|
358
|
+
const snapshot = lastSnapshot ?? await browser.snapshot();
|
|
359
|
+
const finalSnapshot2 = config.snapshotFilter ? filterSnapshot(snapshot) : snapshot;
|
|
360
|
+
return {
|
|
361
|
+
results: [],
|
|
362
|
+
snapshot: finalSnapshot2,
|
|
363
|
+
url: currentUrl2,
|
|
364
|
+
nudgeMessage: "\u30E6\u30FC\u30B6\u30FC\u304C\u63A2\u7D22\u306E\u4E2D\u6B62\u3092\u8981\u6C42\u3057\u307E\u3057\u305F\u3002goalAchieved: false \u3067\u6700\u7D42\u5831\u544A\u3092\u4F5C\u6210\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
|
|
365
|
+
stepCount: recordedSteps.length
|
|
366
|
+
};
|
|
367
|
+
}
|
|
368
|
+
if (interventionResult.guidanceText) {
|
|
369
|
+
interventionNudge = tf("browserTool.interventionGuidance", { text: interventionResult.guidanceText });
|
|
370
|
+
}
|
|
371
|
+
dl?.log({
|
|
372
|
+
phase: "generator",
|
|
373
|
+
event: "user_intervention",
|
|
374
|
+
step: recordedSteps.length,
|
|
375
|
+
data: {
|
|
376
|
+
action: interventionResult.action,
|
|
377
|
+
guidanceText: interventionResult.guidanceText,
|
|
378
|
+
url: currentUrl2
|
|
379
|
+
}
|
|
380
|
+
});
|
|
381
|
+
}
|
|
382
|
+
const results = [];
|
|
383
|
+
if (inputData.actions.length === 0) {
|
|
384
|
+
const snapshot = await browser.snapshot();
|
|
385
|
+
lastSnapshot = snapshot;
|
|
386
|
+
const currentUrl2 = await browser.url();
|
|
387
|
+
const transformResult = await applySnapshotTransform(snapshot, currentUrl2);
|
|
388
|
+
let finalSnapshot2 = transformResult.snapshot;
|
|
389
|
+
let emptyNudge;
|
|
390
|
+
const dataCollectionActionSet2 = /* @__PURE__ */ new Set(["download", "extract", "memory_append", "memory_aggregate"]);
|
|
391
|
+
const hasDataCollectionHistory2 = recordedSteps.some(
|
|
392
|
+
(s) => s.success && dataCollectionActionSet2.has(s.action.action)
|
|
393
|
+
);
|
|
394
|
+
if (hasDataCollectionHistory2) {
|
|
395
|
+
const pageMatch = snapshot.match(/Page\s+(\d+)\s+(?:of|\/)\s+(\d+)/i);
|
|
396
|
+
if (pageMatch) {
|
|
397
|
+
const cp = Number(pageMatch[1]);
|
|
398
|
+
const tp = Number(pageMatch[2]);
|
|
399
|
+
if (cp < tp) {
|
|
400
|
+
const pp = recordedSteps.filter(
|
|
401
|
+
(s) => s.success && (s.action.action === "download" || s.action.action === "extract")
|
|
402
|
+
).length;
|
|
403
|
+
emptyNudge = `Pagination progress: Page ${cp} of ${tp} (${pp} pages processed, ${tp - pp} remaining). You MUST continue processing ALL remaining pages \u2014 do NOT stop early.`;
|
|
404
|
+
const banner = `[PAGINATION INCOMPLETE: ${pp}/${tp} pages processed. ${tp - pp} pages remaining. You MUST continue \u2014 do NOT return goalAchieved yet.]`;
|
|
405
|
+
finalSnapshot2 = `${banner}
|
|
406
|
+
${finalSnapshot2}`;
|
|
407
|
+
if (config.paginationTracker) {
|
|
408
|
+
config.paginationTracker.currentPage = cp;
|
|
409
|
+
config.paginationTracker.totalPages = tp;
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
dl?.log({
|
|
415
|
+
phase: "generator",
|
|
416
|
+
event: "snapshot",
|
|
417
|
+
step: recordedSteps.length,
|
|
418
|
+
data: { url: currentUrl2, snapshot, filteredSnapshot: finalSnapshot2 }
|
|
419
|
+
});
|
|
420
|
+
const skillExtractedPreview = transformResult.extractedData ? truncateExtractedData(transformResult.extractedData, 500) : void 0;
|
|
421
|
+
return {
|
|
422
|
+
results: [],
|
|
423
|
+
snapshot: finalSnapshot2,
|
|
424
|
+
url: currentUrl2,
|
|
425
|
+
nudgeMessage: emptyNudge,
|
|
426
|
+
stepCount: recordedSteps.length,
|
|
427
|
+
...skillExtractedPreview ? { extractedData: skillExtractedPreview } : {}
|
|
428
|
+
};
|
|
429
|
+
}
|
|
430
|
+
const snapshotBefore = lastSnapshot ?? "";
|
|
431
|
+
for (const action of inputData.actions) {
|
|
432
|
+
if (ic?.isCancelRequested()) {
|
|
433
|
+
const snapshot = lastSnapshot ?? await browser.snapshot();
|
|
434
|
+
const finalSnapshot2 = config.snapshotFilter ? filterSnapshot(snapshot) : snapshot;
|
|
435
|
+
return {
|
|
436
|
+
results,
|
|
437
|
+
snapshot: finalSnapshot2,
|
|
438
|
+
url: await browser.url(),
|
|
439
|
+
nudgeMessage: "\u30E6\u30FC\u30B6\u30FC\u304C\u63A2\u7D22\u306E\u30AD\u30E3\u30F3\u30BB\u30EB\u3092\u8981\u6C42\u3057\u307E\u3057\u305F\u3002goalAchieved: false \u3067\u6700\u7D42\u5831\u544A\u3092\u4F5C\u6210\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
|
|
440
|
+
stepCount: recordedSteps.length
|
|
441
|
+
};
|
|
442
|
+
}
|
|
443
|
+
if (ic?.isPauseRequested()) {
|
|
444
|
+
const currentUrl2 = await browser.url();
|
|
445
|
+
const interventionResult = await ic.collectIntervention(
|
|
446
|
+
recordedSteps.length,
|
|
447
|
+
currentUrl2
|
|
448
|
+
);
|
|
449
|
+
if (interventionResult.action === "abort") {
|
|
450
|
+
const snapshot = lastSnapshot ?? await browser.snapshot();
|
|
451
|
+
const finalSnapshot2 = config.snapshotFilter ? filterSnapshot(snapshot) : snapshot;
|
|
452
|
+
return {
|
|
453
|
+
results,
|
|
454
|
+
snapshot: finalSnapshot2,
|
|
455
|
+
url: currentUrl2,
|
|
456
|
+
nudgeMessage: "\u30E6\u30FC\u30B6\u30FC\u304C\u63A2\u7D22\u306E\u4E2D\u6B62\u3092\u8981\u6C42\u3057\u307E\u3057\u305F\u3002goalAchieved: false \u3067\u6700\u7D42\u5831\u544A\u3092\u4F5C\u6210\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
|
|
457
|
+
stepCount: recordedSteps.length
|
|
458
|
+
};
|
|
459
|
+
}
|
|
460
|
+
if (interventionResult.guidanceText) {
|
|
461
|
+
interventionNudge = tf("browserTool.interventionGuidance", { text: interventionResult.guidanceText });
|
|
462
|
+
}
|
|
463
|
+
dl?.log({
|
|
464
|
+
phase: "generator",
|
|
465
|
+
event: "user_intervention",
|
|
466
|
+
step: recordedSteps.length,
|
|
467
|
+
data: {
|
|
468
|
+
action: interventionResult.action,
|
|
469
|
+
guidanceText: interventionResult.guidanceText,
|
|
470
|
+
url: currentUrl2
|
|
471
|
+
}
|
|
472
|
+
});
|
|
473
|
+
}
|
|
474
|
+
log.step(`${action.description} (${action.action} ${action.selector ?? ""})`);
|
|
475
|
+
if (action.action === "memory_append" && dataStore) {
|
|
476
|
+
const actionStart = performance.now();
|
|
477
|
+
const collection = action.memoryCollection ?? "default";
|
|
478
|
+
const rawValue = lastExtractedData ?? action.value ?? "";
|
|
479
|
+
const success = true;
|
|
480
|
+
const error = void 0;
|
|
481
|
+
parseAndAppendToMemory(rawValue, {
|
|
482
|
+
dataStore,
|
|
483
|
+
debugLogger: dl ?? noopLogger,
|
|
484
|
+
phase: "generator",
|
|
485
|
+
step: ordinal,
|
|
486
|
+
collection
|
|
487
|
+
});
|
|
488
|
+
const explorationAction2 = {
|
|
489
|
+
action: action.action,
|
|
490
|
+
value: rawValue,
|
|
491
|
+
description: action.description,
|
|
492
|
+
memoryCollection: collection
|
|
493
|
+
};
|
|
494
|
+
recordedSteps.push({
|
|
495
|
+
ordinal,
|
|
496
|
+
action: explorationAction2,
|
|
497
|
+
snapshotBefore,
|
|
498
|
+
url: await browser.url(),
|
|
499
|
+
success,
|
|
500
|
+
error,
|
|
501
|
+
durationMs: Math.round(performance.now() - actionStart)
|
|
502
|
+
});
|
|
503
|
+
ordinal++;
|
|
504
|
+
results.push({ description: action.description, success, error });
|
|
505
|
+
const collectionCount = dataStore.count(collection);
|
|
506
|
+
if (collectionCount % 50 === 0 || collectionCount <= 5) {
|
|
507
|
+
addScratchpadEntry(scratchpadEntries, MAX_SCRATCHPAD, `Memory: ${collection} now has ${collectionCount} items`);
|
|
508
|
+
}
|
|
509
|
+
continue;
|
|
510
|
+
}
|
|
511
|
+
if (action.action === "memory_aggregate" && dataStore && action.aggregation) {
|
|
512
|
+
const actionStart = performance.now();
|
|
513
|
+
const agg = action.aggregation;
|
|
514
|
+
let success = true;
|
|
515
|
+
let error;
|
|
516
|
+
let aggregateResult = "";
|
|
517
|
+
try {
|
|
518
|
+
aggregateResult = dataStore.aggregate({
|
|
519
|
+
collection: agg.collection,
|
|
520
|
+
field: agg.field,
|
|
521
|
+
operation: agg.operation
|
|
522
|
+
});
|
|
523
|
+
log.success(`Memory: ${agg.operation}(${agg.collection}.${agg.field}) = "${aggregateResult}"`);
|
|
524
|
+
lastExtractedData = aggregateResult;
|
|
525
|
+
} catch (e) {
|
|
526
|
+
success = false;
|
|
527
|
+
error = sanitizeBrowserError(e instanceof Error ? e.message : String(e));
|
|
528
|
+
log.error(`Memory aggregate failed: ${error}`);
|
|
529
|
+
}
|
|
530
|
+
const explorationAction2 = {
|
|
531
|
+
action: action.action,
|
|
532
|
+
description: action.description,
|
|
533
|
+
aggregation: agg
|
|
534
|
+
};
|
|
535
|
+
recordedSteps.push({
|
|
536
|
+
ordinal,
|
|
537
|
+
action: explorationAction2,
|
|
538
|
+
snapshotBefore,
|
|
539
|
+
url: await browser.url(),
|
|
540
|
+
success,
|
|
541
|
+
error,
|
|
542
|
+
durationMs: Math.round(performance.now() - actionStart)
|
|
543
|
+
});
|
|
544
|
+
ordinal++;
|
|
545
|
+
results.push({ description: action.description, success, error });
|
|
546
|
+
continue;
|
|
547
|
+
}
|
|
548
|
+
if (action.action === "extract") {
|
|
549
|
+
const actionStart = performance.now();
|
|
550
|
+
let success = true;
|
|
551
|
+
let error;
|
|
552
|
+
let extractResult = "";
|
|
553
|
+
try {
|
|
554
|
+
if (action.script) {
|
|
555
|
+
dl?.log({
|
|
556
|
+
phase: "generator",
|
|
557
|
+
event: "extract",
|
|
558
|
+
step: ordinal,
|
|
559
|
+
data: { script: action.script.slice(0, 200) }
|
|
560
|
+
});
|
|
561
|
+
const result2 = await browser.evaluate(action.script);
|
|
562
|
+
const isArray = Array.isArray(result2);
|
|
563
|
+
const stringifyApplied = typeof result2 !== "string" && result2 !== null && result2 !== void 0;
|
|
564
|
+
extractResult = result2 === null || result2 === void 0 ? "" : typeof result2 === "string" ? result2 : JSON.stringify(result2);
|
|
565
|
+
dl?.log({
|
|
566
|
+
phase: "generator",
|
|
567
|
+
event: "extract_result",
|
|
568
|
+
step: ordinal,
|
|
569
|
+
data: {
|
|
570
|
+
resultType: result2 === null ? "null" : typeof result2,
|
|
571
|
+
isArray,
|
|
572
|
+
arrayLength: isArray ? result2.length : void 0,
|
|
573
|
+
resultPreview: extractResult.slice(0, 200),
|
|
574
|
+
storedLength: extractResult.length,
|
|
575
|
+
stringifyApplied
|
|
576
|
+
}
|
|
577
|
+
});
|
|
578
|
+
} else if (action.extractPrompt) {
|
|
579
|
+
dl?.log({
|
|
580
|
+
phase: "generator",
|
|
581
|
+
event: "extract",
|
|
582
|
+
step: ordinal,
|
|
583
|
+
data: { extractPrompt: action.extractPrompt.slice(0, 200) }
|
|
584
|
+
});
|
|
585
|
+
const snapshot = await browser.snapshot();
|
|
586
|
+
const { getModel: getModel2, trackedGenerateObject: trackedGenerateObject2 } = await import("./ai-model-FM6GWCID.js");
|
|
587
|
+
const extractSchema = z.object({ data: z.string() });
|
|
588
|
+
const aiResult = await trackedGenerateObject2("extraction", {
|
|
589
|
+
model: getModel2("extraction"),
|
|
590
|
+
prompt: `${action.extractPrompt}
|
|
591
|
+
|
|
592
|
+
\u30B9\u30CA\u30C3\u30D7\u30B7\u30E7\u30C3\u30C8:
|
|
593
|
+
${snapshot}`,
|
|
594
|
+
schema: extractSchema,
|
|
595
|
+
temperature: 0
|
|
596
|
+
});
|
|
597
|
+
extractResult = aiResult.object?.data ?? "";
|
|
598
|
+
dl?.log({
|
|
599
|
+
phase: "generator",
|
|
600
|
+
event: "extract_result",
|
|
601
|
+
step: ordinal,
|
|
602
|
+
data: {
|
|
603
|
+
resultType: "string (AI)",
|
|
604
|
+
resultPreview: extractResult.slice(0, 200),
|
|
605
|
+
storedLength: extractResult.length
|
|
606
|
+
}
|
|
607
|
+
});
|
|
608
|
+
}
|
|
609
|
+
lastExtractedData = extractResult;
|
|
610
|
+
log.success(`Extracted: ${extractResult.slice(0, 100)}${extractResult.length > 100 ? "..." : ""}`);
|
|
611
|
+
const extractLen = extractResult.length;
|
|
612
|
+
addScratchpadEntry(scratchpadEntries, MAX_SCRATCHPAD, `Extracted ${extractLen > 200 ? `${extractLen} chars` : "data"} via ${action.script ? "script" : "AI prompt"}`);
|
|
613
|
+
} catch (e) {
|
|
614
|
+
success = false;
|
|
615
|
+
error = sanitizeBrowserError(e instanceof Error ? e.message : String(e));
|
|
616
|
+
log.error(`Extract failed: ${error}`);
|
|
617
|
+
}
|
|
618
|
+
const explorationAction2 = {
|
|
619
|
+
action: action.action,
|
|
620
|
+
description: action.description,
|
|
621
|
+
script: action.script,
|
|
622
|
+
extractPrompt: action.extractPrompt
|
|
623
|
+
};
|
|
624
|
+
recordedSteps.push({
|
|
625
|
+
ordinal,
|
|
626
|
+
action: explorationAction2,
|
|
627
|
+
snapshotBefore,
|
|
628
|
+
url: await browser.url(),
|
|
629
|
+
success,
|
|
630
|
+
error,
|
|
631
|
+
durationMs: Math.round(performance.now() - actionStart)
|
|
632
|
+
});
|
|
633
|
+
ordinal++;
|
|
634
|
+
results.push({ description: action.description, success, error });
|
|
635
|
+
continue;
|
|
636
|
+
}
|
|
637
|
+
if (action.action === "download") {
|
|
638
|
+
const actionStart = performance.now();
|
|
639
|
+
let success = true;
|
|
640
|
+
let error;
|
|
641
|
+
const downloadPath = action.downloadPath ?? `/tmp/download-${Date.now()}.bin`;
|
|
642
|
+
try {
|
|
643
|
+
if (action.selector) {
|
|
644
|
+
await browser.download(action.selector, downloadPath);
|
|
645
|
+
} else {
|
|
646
|
+
await browser.waitForDownload(downloadPath);
|
|
647
|
+
}
|
|
648
|
+
log.success(`Downloaded: ${downloadPath}`);
|
|
649
|
+
if (downloadManager) {
|
|
650
|
+
downloadManager.addDownload({
|
|
651
|
+
path: downloadPath,
|
|
652
|
+
filename: downloadPath.split("/").pop() ?? "unknown",
|
|
653
|
+
stepOrdinal: ordinal,
|
|
654
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
655
|
+
});
|
|
656
|
+
}
|
|
657
|
+
} catch (e) {
|
|
658
|
+
success = false;
|
|
659
|
+
error = sanitizeBrowserError(e instanceof Error ? e.message : String(e));
|
|
660
|
+
log.error(`Download failed: ${error}`);
|
|
661
|
+
}
|
|
662
|
+
const explorationAction2 = {
|
|
663
|
+
action: action.action,
|
|
664
|
+
selector: action.selector,
|
|
665
|
+
description: action.description,
|
|
666
|
+
downloadPath
|
|
667
|
+
};
|
|
668
|
+
recordedSteps.push({
|
|
669
|
+
ordinal,
|
|
670
|
+
action: explorationAction2,
|
|
671
|
+
snapshotBefore,
|
|
672
|
+
url: await browser.url(),
|
|
673
|
+
success,
|
|
674
|
+
error,
|
|
675
|
+
durationMs: Math.round(performance.now() - actionStart)
|
|
676
|
+
});
|
|
677
|
+
ordinal++;
|
|
678
|
+
results.push({ description: action.description, success, error });
|
|
679
|
+
continue;
|
|
680
|
+
}
|
|
681
|
+
if (action.action === "export" && dataStore) {
|
|
682
|
+
const actionStart = performance.now();
|
|
683
|
+
const collection = action.exportCollection ?? "default";
|
|
684
|
+
const format = action.exportFormat ?? "csv";
|
|
685
|
+
const exportPath = action.exportPath ?? `/tmp/${collection}.${format}`;
|
|
686
|
+
let success = true;
|
|
687
|
+
let error;
|
|
688
|
+
try {
|
|
689
|
+
const exportItems = dataStore.getAll(collection);
|
|
690
|
+
const sampleKeys = exportItems.length > 0 ? JSON.stringify(Object.keys(exportItems[0])) : "N/A";
|
|
691
|
+
dl?.log({
|
|
692
|
+
phase: "generator",
|
|
693
|
+
event: "export",
|
|
694
|
+
step: ordinal,
|
|
695
|
+
data: {
|
|
696
|
+
collection,
|
|
697
|
+
itemCount: exportItems.length,
|
|
698
|
+
format,
|
|
699
|
+
path: exportPath,
|
|
700
|
+
sampleKeys
|
|
701
|
+
}
|
|
702
|
+
});
|
|
703
|
+
await dataStore.writeToFile(collection, exportPath, format);
|
|
704
|
+
log.success(`Exported: ${collection} \u2192 ${exportPath} (${format})`);
|
|
705
|
+
lastExtractedData = exportPath;
|
|
706
|
+
if (downloadManager) {
|
|
707
|
+
downloadManager.addDownload({
|
|
708
|
+
path: exportPath,
|
|
709
|
+
filename: exportPath.split("/").pop() ?? "unknown",
|
|
710
|
+
stepOrdinal: ordinal,
|
|
711
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
712
|
+
});
|
|
713
|
+
}
|
|
714
|
+
} catch (e) {
|
|
715
|
+
success = false;
|
|
716
|
+
error = sanitizeBrowserError(e instanceof Error ? e.message : String(e));
|
|
717
|
+
log.error(`Export failed: ${error}`);
|
|
718
|
+
}
|
|
719
|
+
const explorationAction2 = {
|
|
720
|
+
action: action.action,
|
|
721
|
+
description: action.description,
|
|
722
|
+
exportCollection: collection,
|
|
723
|
+
exportFormat: format,
|
|
724
|
+
exportPath
|
|
725
|
+
};
|
|
726
|
+
recordedSteps.push({
|
|
727
|
+
ordinal,
|
|
728
|
+
action: explorationAction2,
|
|
729
|
+
snapshotBefore,
|
|
730
|
+
url: await browser.url(),
|
|
731
|
+
success,
|
|
732
|
+
error,
|
|
733
|
+
durationMs: Math.round(performance.now() - actionStart)
|
|
734
|
+
});
|
|
735
|
+
ordinal++;
|
|
736
|
+
results.push({ description: action.description, success, error });
|
|
737
|
+
continue;
|
|
738
|
+
}
|
|
739
|
+
if (action.action === "key") {
|
|
740
|
+
const actionStart = performance.now();
|
|
741
|
+
const keys = action.keys ?? action.value ?? "";
|
|
742
|
+
let success = true;
|
|
743
|
+
let error;
|
|
744
|
+
try {
|
|
745
|
+
await browser.pressKeys(keys);
|
|
746
|
+
log.success(`Key: ${keys}`);
|
|
747
|
+
} catch (e) {
|
|
748
|
+
success = false;
|
|
749
|
+
error = sanitizeBrowserError(e instanceof Error ? e.message : String(e));
|
|
750
|
+
log.error(`Key failed: ${error}`);
|
|
751
|
+
}
|
|
752
|
+
const explorationAction2 = {
|
|
753
|
+
action: "key",
|
|
754
|
+
description: action.description,
|
|
755
|
+
keys
|
|
756
|
+
};
|
|
757
|
+
recordedSteps.push({
|
|
758
|
+
ordinal,
|
|
759
|
+
action: explorationAction2,
|
|
760
|
+
snapshotBefore,
|
|
761
|
+
url: await browser.url(),
|
|
762
|
+
success,
|
|
763
|
+
error,
|
|
764
|
+
durationMs: Math.round(performance.now() - actionStart)
|
|
765
|
+
});
|
|
766
|
+
ordinal++;
|
|
767
|
+
results.push({ description: action.description, success, error });
|
|
768
|
+
continue;
|
|
769
|
+
}
|
|
770
|
+
const explorationAction = {
|
|
771
|
+
action: action.action,
|
|
772
|
+
selector: action.selector,
|
|
773
|
+
value: action.value,
|
|
774
|
+
description: action.description,
|
|
775
|
+
inputCategory: action.inputCategory,
|
|
776
|
+
variableName: action.variableName,
|
|
777
|
+
suggestedCaptures: action.suggestedCaptures
|
|
778
|
+
};
|
|
779
|
+
const navigatingActions = /* @__PURE__ */ new Set(["click", "navigate"]);
|
|
780
|
+
const mayNavigate = navigatingActions.has(action.action);
|
|
781
|
+
const urlBeforeAction = mayNavigate ? await browser.url() : "";
|
|
782
|
+
const pageCountBefore = mayNavigate ? await browser.pageCount() : 0;
|
|
783
|
+
const result = await browser.executeStep(explorationAction);
|
|
784
|
+
if (mayNavigate) {
|
|
785
|
+
await browser.waitForPossibleNavigation(
|
|
786
|
+
urlBeforeAction,
|
|
787
|
+
pageCountBefore
|
|
788
|
+
);
|
|
789
|
+
}
|
|
790
|
+
const stepUrl = await browser.url();
|
|
791
|
+
const recorded = {
|
|
792
|
+
ordinal,
|
|
793
|
+
action: explorationAction,
|
|
794
|
+
snapshotBefore,
|
|
795
|
+
url: stepUrl,
|
|
796
|
+
success: result.success,
|
|
797
|
+
error: result.error,
|
|
798
|
+
durationMs: result.durationMs,
|
|
799
|
+
failureCategory: result.success ? void 0 : classifyFailure({
|
|
800
|
+
error: result.error ?? "unknown error",
|
|
801
|
+
selectorResolved: !!action.selector,
|
|
802
|
+
actionExecuted: true
|
|
803
|
+
})
|
|
804
|
+
};
|
|
805
|
+
recordedSteps.push(recorded);
|
|
806
|
+
ordinal++;
|
|
807
|
+
results.push({
|
|
808
|
+
description: action.description,
|
|
809
|
+
success: result.success,
|
|
810
|
+
error: result.error
|
|
811
|
+
});
|
|
812
|
+
dl?.log({
|
|
813
|
+
phase: "generator",
|
|
814
|
+
event: "action",
|
|
815
|
+
step: ordinal - 1,
|
|
816
|
+
data: {
|
|
817
|
+
action: action.action,
|
|
818
|
+
selector: action.selector,
|
|
819
|
+
value: action.value,
|
|
820
|
+
description: action.description,
|
|
821
|
+
result: {
|
|
822
|
+
success: result.success,
|
|
823
|
+
error: result.error,
|
|
824
|
+
durationMs: result.durationMs
|
|
825
|
+
},
|
|
826
|
+
url: stepUrl,
|
|
827
|
+
failureCategory: recorded.failureCategory
|
|
828
|
+
}
|
|
829
|
+
});
|
|
830
|
+
if (result.success) {
|
|
831
|
+
log.success(`OK (${result.durationMs}ms)`);
|
|
832
|
+
if (inputActionTypes.has(action.action) && action.selector) {
|
|
833
|
+
filledFieldsMap.set(action.selector, action.description);
|
|
834
|
+
}
|
|
835
|
+
if (mayNavigate && stepUrl !== urlBeforeAction) {
|
|
836
|
+
addScratchpadEntry(scratchpadEntries, MAX_SCRATCHPAD, `Navigated ${shortenUrl(urlBeforeAction)} \u2192 ${shortenUrl(stepUrl)}`);
|
|
837
|
+
lastUrl = stepUrl;
|
|
838
|
+
addScratchpadEntry(scratchpadEntries, MAX_SCRATCHPAD, `GOAL CHECK: Verify ALL parts of the goal are completed before declaring goalAchieved. Your claim will be independently verified.`);
|
|
839
|
+
}
|
|
840
|
+
if (ordinal > 0 && ordinal % 5 === 0) {
|
|
841
|
+
addScratchpadEntry(scratchpadEntries, MAX_SCRATCHPAD, `REMINDER (step ${ordinal}): Before declaring goalAchieved, re-read the original goal and confirm ALL sub-tasks are done. Include evidence in your summary.`);
|
|
842
|
+
}
|
|
843
|
+
} else {
|
|
844
|
+
log.error(`FAILED: ${result.error}`);
|
|
845
|
+
addScratchpadEntry(scratchpadEntries, MAX_SCRATCHPAD, `${action.action} ${action.selector ?? ""} failed: ${(result.error ?? "unknown").slice(0, 60)}`);
|
|
846
|
+
}
|
|
847
|
+
await cancellableSleep(getActionDelay(action.action, config.stepDelay), ic);
|
|
848
|
+
}
|
|
849
|
+
let navigateNudge;
|
|
850
|
+
if (inputData.actions.some((a) => a.action === "navigate") && lastSnapshot) {
|
|
851
|
+
const linkCount = parseAllElements(lastSnapshot).filter((e) => e.role === "link").length;
|
|
852
|
+
if (linkCount > 0) {
|
|
853
|
+
navigateNudge = `\u6CE8\u610F: \u30DA\u30FC\u30B8\u4E0A\u306B ${linkCount} \u500B\u306E\u30EA\u30F3\u30AF\u304C\u3042\u308A\u307E\u3059\u3002navigate \u3067URL\u3092\u7D44\u307F\u7ACB\u3066\u308B\u306E\u3067\u306F\u306A\u304F\u3001\u30DA\u30FC\u30B8\u4E0A\u306E\u30EA\u30F3\u30AF\u3092 click \u3067\u8FBF\u3063\u3066\u304F\u3060\u3055\u3044\u3002`;
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
let nudgeMessage = navigateNudge;
|
|
857
|
+
if (recordedSteps.length >= 2) {
|
|
858
|
+
const loopResult = detectLoop(recordedSteps, nudgeCount);
|
|
859
|
+
if (loopResult.isLoop) {
|
|
860
|
+
nudgeCount = loopResult.nudgeCount;
|
|
861
|
+
const loopNudge = loopResult.nudgeMessage ?? void 0;
|
|
862
|
+
nudgeMessage = nudgeMessage && loopNudge ? `${nudgeMessage}
|
|
863
|
+
${loopNudge}` : loopNudge ?? nudgeMessage;
|
|
864
|
+
log.warn(`Loop detected: ${loopResult.loopType}`);
|
|
865
|
+
dl?.log({
|
|
866
|
+
phase: "generator",
|
|
867
|
+
event: "loop_detection",
|
|
868
|
+
step: recordedSteps.length - 1,
|
|
869
|
+
data: loopResult
|
|
870
|
+
});
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
const postSnapshot = await browser.snapshot();
|
|
874
|
+
const currentUrl = await browser.url();
|
|
875
|
+
const textOnlyActions = /* @__PURE__ */ new Set(["fill", "type"]);
|
|
876
|
+
const allTextOnly = inputData.actions.length > 0 && inputData.actions.every((a) => textOnlyActions.has(a.action)) && results.every((r) => r.success);
|
|
877
|
+
let finalSnapshot;
|
|
878
|
+
if (allTextOnly && lastSnapshot) {
|
|
879
|
+
const oldSig = extractStructuralSignature(lastSnapshot);
|
|
880
|
+
const newSig = extractStructuralSignature(postSnapshot);
|
|
881
|
+
if (oldSig === newSig) {
|
|
882
|
+
finalSnapshot = buildDiffSnapshot(postSnapshot, filledFieldsMap);
|
|
883
|
+
dl?.log({
|
|
884
|
+
phase: "generator",
|
|
885
|
+
event: "snapshot_diff",
|
|
886
|
+
step: recordedSteps.length - 1,
|
|
887
|
+
data: { url: currentUrl, reason: "structure_unchanged", elementCount: newSig.split("\n").length }
|
|
888
|
+
});
|
|
889
|
+
} else {
|
|
890
|
+
const filledSelectors = filledFieldsMap.size > 0 ? new Set(filledFieldsMap.keys()) : void 0;
|
|
891
|
+
const tr = await applySnapshotTransform(postSnapshot, currentUrl, { filledSelectors });
|
|
892
|
+
finalSnapshot = tr.snapshot;
|
|
893
|
+
dl?.log({
|
|
894
|
+
phase: "generator",
|
|
895
|
+
event: "snapshot",
|
|
896
|
+
step: recordedSteps.length - 1,
|
|
897
|
+
data: { url: currentUrl, snapshot: postSnapshot, filteredSnapshot: finalSnapshot }
|
|
898
|
+
});
|
|
899
|
+
}
|
|
900
|
+
} else {
|
|
901
|
+
const filledSelectors = filledFieldsMap.size > 0 ? new Set(filledFieldsMap.keys()) : void 0;
|
|
902
|
+
const tr = await applySnapshotTransform(postSnapshot, currentUrl, { filledSelectors });
|
|
903
|
+
finalSnapshot = tr.snapshot;
|
|
904
|
+
dl?.log({
|
|
905
|
+
phase: "generator",
|
|
906
|
+
event: "snapshot",
|
|
907
|
+
step: recordedSteps.length - 1,
|
|
908
|
+
data: { url: currentUrl, snapshot: postSnapshot, filteredSnapshot: finalSnapshot }
|
|
909
|
+
});
|
|
910
|
+
}
|
|
911
|
+
lastSnapshot = postSnapshot;
|
|
912
|
+
if (interventionNudge) {
|
|
913
|
+
nudgeMessage = nudgeMessage ? `${interventionNudge}
|
|
914
|
+
${nudgeMessage}` : interventionNudge;
|
|
915
|
+
}
|
|
916
|
+
const memoryStatus = dataStore ? Object.fromEntries(
|
|
917
|
+
dataStore.listCollections().map((c) => [c, dataStore.count(c)])
|
|
918
|
+
) : void 0;
|
|
919
|
+
const hasExtractAction = inputData.actions.some(
|
|
920
|
+
(a) => a.action === "extract" || a.action === "memory_aggregate"
|
|
921
|
+
);
|
|
922
|
+
const skillProvidedData = config.skills?.some((s) => s.shouldActivate(currentUrl)) && lastExtractedData;
|
|
923
|
+
const extractedDataPreview = (hasExtractAction || skillProvidedData) && lastExtractedData ? truncateExtractedData(lastExtractedData, 500) : void 0;
|
|
924
|
+
const keepRecent = 5;
|
|
925
|
+
if (recordedSteps.length > keepRecent) {
|
|
926
|
+
const stepToPrune = recordedSteps[recordedSteps.length - keepRecent - 1];
|
|
927
|
+
if (stepToPrune.snapshotBefore && stepToPrune.action.selector) {
|
|
928
|
+
const ref = stepToPrune.action.selector.replace("@", "");
|
|
929
|
+
const element = findElementInSnapshot(stepToPrune.snapshotBefore, ref);
|
|
930
|
+
if (element) {
|
|
931
|
+
stepToPrune.resolvedElement = buildSelector(element);
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
stepToPrune.snapshotBefore = "";
|
|
935
|
+
}
|
|
936
|
+
const filledFieldsSummary = filledFieldsMap.size > 0 ? [...filledFieldsMap.entries()].map(([sel, desc]) => `${sel}: ${desc}`).join(", ") : void 0;
|
|
937
|
+
let paginationCurrentPage = 0;
|
|
938
|
+
let paginationTotalPages = 0;
|
|
939
|
+
let paginationPagesProcessed = 0;
|
|
940
|
+
const dataCollectionActionSet = /* @__PURE__ */ new Set(["download", "extract", "memory_append", "memory_aggregate"]);
|
|
941
|
+
const hasDataCollectionHistory = recordedSteps.some(
|
|
942
|
+
(s) => s.success && dataCollectionActionSet.has(s.action.action)
|
|
943
|
+
);
|
|
944
|
+
if (hasDataCollectionHistory) {
|
|
945
|
+
const pageMatch = postSnapshot.match(/Page\s+(\d+)\s+(?:of|\/)\s+(\d+)/i);
|
|
946
|
+
if (pageMatch) {
|
|
947
|
+
paginationCurrentPage = Number(pageMatch[1]);
|
|
948
|
+
paginationTotalPages = Number(pageMatch[2]);
|
|
949
|
+
if (paginationCurrentPage < paginationTotalPages) {
|
|
950
|
+
paginationPagesProcessed = recordedSteps.filter(
|
|
951
|
+
(s) => s.success && (s.action.action === "download" || s.action.action === "extract")
|
|
952
|
+
).length;
|
|
953
|
+
const paginationNudge = `Pagination progress: Page ${paginationCurrentPage} of ${paginationTotalPages} (${paginationPagesProcessed} pages processed, ${paginationTotalPages - paginationPagesProcessed} remaining). You MUST continue processing ALL remaining pages \u2014 do NOT stop early.`;
|
|
954
|
+
nudgeMessage = nudgeMessage ? `${nudgeMessage}
|
|
955
|
+
${paginationNudge}` : paginationNudge;
|
|
956
|
+
if (config.paginationTracker) {
|
|
957
|
+
config.paginationTracker.currentPage = paginationCurrentPage;
|
|
958
|
+
config.paginationTracker.totalPages = paginationTotalPages;
|
|
959
|
+
}
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
}
|
|
963
|
+
if (filledFieldsMap.size >= 3) {
|
|
964
|
+
const formRoles = /* @__PURE__ */ new Set(["textbox", "combobox", "checkbox", "spinbutton"]);
|
|
965
|
+
const filledRefs = new Set(
|
|
966
|
+
[...filledFieldsMap.keys()].map((s) => s.replace("@", ""))
|
|
967
|
+
);
|
|
968
|
+
const allElements = parseAllElements(postSnapshot);
|
|
969
|
+
const remainingFormElements = allElements.filter(
|
|
970
|
+
(e) => formRoles.has(e.role) && !filledRefs.has(e.ref)
|
|
971
|
+
);
|
|
972
|
+
if (remainingFormElements.length > 0) {
|
|
973
|
+
const names = remainingFormElements.map((e) => e.name ? `"${e.name}"` : `@${e.ref}`).join(", ");
|
|
974
|
+
const formHint = `Note: ${remainingFormElements.length} form fields not yet filled: ${names}. Fill all remaining fields before submitting.`;
|
|
975
|
+
nudgeMessage = nudgeMessage ? `${nudgeMessage}
|
|
976
|
+
${formHint}` : formHint;
|
|
977
|
+
}
|
|
978
|
+
}
|
|
979
|
+
if (paginationCurrentPage > 0 && paginationCurrentPage < paginationTotalPages) {
|
|
980
|
+
const banner = `[PAGINATION INCOMPLETE: ${paginationPagesProcessed}/${paginationTotalPages} pages processed. ${paginationTotalPages - paginationPagesProcessed} pages remaining. You MUST continue \u2014 do NOT return goalAchieved yet.]`;
|
|
981
|
+
finalSnapshot = `${banner}
|
|
982
|
+
${finalSnapshot}`;
|
|
983
|
+
}
|
|
984
|
+
const scratchpad = scratchpadEntries.length > 0 ? scratchpadEntries.join("\n") : void 0;
|
|
985
|
+
return {
|
|
986
|
+
results,
|
|
987
|
+
snapshot: finalSnapshot,
|
|
988
|
+
url: currentUrl,
|
|
989
|
+
nudgeMessage,
|
|
990
|
+
stepCount: recordedSteps.length,
|
|
991
|
+
...memoryStatus && Object.keys(memoryStatus).length > 0 ? { memoryStatus } : {},
|
|
992
|
+
...extractedDataPreview ? { extractedData: extractedDataPreview } : {},
|
|
993
|
+
...filledFieldsSummary ? { filledFields: filledFieldsSummary } : {},
|
|
994
|
+
...scratchpad ? { scratchpad } : {}
|
|
995
|
+
};
|
|
996
|
+
} finally {
|
|
997
|
+
config.onToolCallEnd?.();
|
|
998
|
+
}
|
|
999
|
+
}
|
|
1000
|
+
});
|
|
1001
|
+
}
|
|
1002
|
+
function addScratchpadEntry(entries, max, entry) {
|
|
1003
|
+
entries.push(entry);
|
|
1004
|
+
if (entries.length > max) {
|
|
1005
|
+
entries.shift();
|
|
1006
|
+
}
|
|
1007
|
+
}
|
|
1008
|
+
function shortenUrl(url) {
|
|
1009
|
+
try {
|
|
1010
|
+
const parsed = new URL(url);
|
|
1011
|
+
return parsed.pathname + (parsed.search ? parsed.search.slice(0, 30) : "");
|
|
1012
|
+
} catch {
|
|
1013
|
+
return url.slice(0, 50);
|
|
1014
|
+
}
|
|
1015
|
+
}
|
|
1016
|
+
function extractStructuralSignature(snapshot) {
|
|
1017
|
+
return snapshot.split("\n").map((line) => {
|
|
1018
|
+
const indent = line.length - line.trimStart().length;
|
|
1019
|
+
const trimmed = line.trimStart().replace(/^-\s+/, "");
|
|
1020
|
+
const roleMatch = trimmed.match(/^(\S+)/);
|
|
1021
|
+
const refMatch = line.match(/\[ref=(e\d+)\]/);
|
|
1022
|
+
return `${indent}:${roleMatch?.[1] ?? ""}:${refMatch?.[1] ?? ""}`;
|
|
1023
|
+
}).join("\n");
|
|
1024
|
+
}
|
|
1025
|
+
function buildDiffSnapshot(snapshot, filledFieldsMap) {
|
|
1026
|
+
const elements = parseAllElements(snapshot);
|
|
1027
|
+
const filledRefs = new Set(
|
|
1028
|
+
[...filledFieldsMap.keys()].map((sel) => sel.replace("@", ""))
|
|
1029
|
+
);
|
|
1030
|
+
const unfilled = [];
|
|
1031
|
+
const filled = [];
|
|
1032
|
+
for (const el of elements) {
|
|
1033
|
+
const label = `@${el.ref} ${el.role}${el.name ? ` "${el.name}"` : ""}`;
|
|
1034
|
+
if (filledRefs.has(el.ref)) {
|
|
1035
|
+
filled.push(label);
|
|
1036
|
+
} else {
|
|
1037
|
+
unfilled.push(label);
|
|
1038
|
+
}
|
|
1039
|
+
}
|
|
1040
|
+
const lines = [
|
|
1041
|
+
`[Structure unchanged after text input. ${elements.length} interactive elements, all refs valid.]`
|
|
1042
|
+
];
|
|
1043
|
+
if (unfilled.length > 0) {
|
|
1044
|
+
lines.push(`Unfilled: ${unfilled.join(", ")}`);
|
|
1045
|
+
}
|
|
1046
|
+
if (filled.length > 0) {
|
|
1047
|
+
lines.push(`Filled (${filled.length}): ${filled.join(", ")}`);
|
|
1048
|
+
}
|
|
1049
|
+
return lines.join("\n");
|
|
1050
|
+
}
|
|
1051
|
+
|
|
1052
|
+
// src/runbook-generator/exploration-agent.ts
|
|
1053
|
+
function routeExplorationModel(stepNumber, messages, recordedSteps) {
|
|
1054
|
+
if (stepNumber <= 2) {
|
|
1055
|
+
return { purpose: "exploration", reason: "initial_observation" };
|
|
1056
|
+
}
|
|
1057
|
+
const toolIndices = [];
|
|
1058
|
+
for (let i = 0; i < messages.length; i++) {
|
|
1059
|
+
if (messages[i].role === "tool") toolIndices.push(i);
|
|
1060
|
+
}
|
|
1061
|
+
if (toolIndices.length > 0) {
|
|
1062
|
+
const lastToolMsg = messages[toolIndices[toolIndices.length - 1]];
|
|
1063
|
+
const lastVal = extractToolOutputValue(lastToolMsg);
|
|
1064
|
+
if (lastVal) {
|
|
1065
|
+
const results = lastVal.results;
|
|
1066
|
+
if (results?.some((r) => !r.success)) {
|
|
1067
|
+
return { purpose: "exploration", reason: "previous_step_failed" };
|
|
1068
|
+
}
|
|
1069
|
+
if (lastVal.nudgeMessage) {
|
|
1070
|
+
return { purpose: "exploration", reason: "nudge_active" };
|
|
1071
|
+
}
|
|
1072
|
+
}
|
|
1073
|
+
}
|
|
1074
|
+
if (recordedSteps.length >= 2) {
|
|
1075
|
+
const last = recordedSteps[recordedSteps.length - 1];
|
|
1076
|
+
const prev = recordedSteps[recordedSteps.length - 2];
|
|
1077
|
+
if (last.url !== prev.url) {
|
|
1078
|
+
return { purpose: "exploration", reason: "page_changed" };
|
|
1079
|
+
}
|
|
1080
|
+
}
|
|
1081
|
+
if (toolIndices.length > 0) {
|
|
1082
|
+
const phase = detectExplorationPhase(messages, toolIndices);
|
|
1083
|
+
if (phase === "form_filling") {
|
|
1084
|
+
return { purpose: "exploration-light", reason: "routine_form_fill" };
|
|
1085
|
+
}
|
|
1086
|
+
if (phase === "data_collection") {
|
|
1087
|
+
return { purpose: "exploration-light", reason: "routine_data_collection" };
|
|
1088
|
+
}
|
|
1089
|
+
}
|
|
1090
|
+
if (recordedSteps.length >= 3) {
|
|
1091
|
+
const recent = recordedSteps.slice(-3);
|
|
1092
|
+
const allSucceeded = recent.every((s) => s.success);
|
|
1093
|
+
const sameUrl = recent.every((s) => s.url === recent[0].url);
|
|
1094
|
+
if (allSucceeded && sameUrl) {
|
|
1095
|
+
return { purpose: "exploration-light", reason: "stable_operation" };
|
|
1096
|
+
}
|
|
1097
|
+
}
|
|
1098
|
+
return { purpose: "exploration", reason: "default" };
|
|
1099
|
+
}
|
|
1100
|
+
async function exploreWithAI(browser, recordedSteps, config, dataStore, downloadManager) {
|
|
1101
|
+
const paginationTracker = { currentPage: 0, totalPages: 0 };
|
|
1102
|
+
const toolConfig = {
|
|
1103
|
+
stepDelay: config.stepDelay,
|
|
1104
|
+
screenshotDir: config.screenshotDir,
|
|
1105
|
+
snapshotFilter: config.snapshotFilter,
|
|
1106
|
+
debugLogger: config.debugLogger,
|
|
1107
|
+
interventionController: config.interventionController,
|
|
1108
|
+
paginationTracker,
|
|
1109
|
+
onToolCallStart: config.onToolCallStart,
|
|
1110
|
+
onToolCallEnd: config.onToolCallEnd,
|
|
1111
|
+
skills: config.skills
|
|
1112
|
+
};
|
|
1113
|
+
const browserTool = createBrowserTool(browser, recordedSteps, toolConfig, dataStore, downloadManager);
|
|
1114
|
+
const totalMaxSteps = config.maxIterations * 3;
|
|
1115
|
+
const promptLocale = config.locale ?? getLocale();
|
|
1116
|
+
let systemContent = getAgentInstructions(
|
|
1117
|
+
config.goal,
|
|
1118
|
+
config.contextMarkdown,
|
|
1119
|
+
config.secrets,
|
|
1120
|
+
promptLocale
|
|
1121
|
+
);
|
|
1122
|
+
if (config.skills && config.skills.length > 0) {
|
|
1123
|
+
const currentUrl = await browser.url();
|
|
1124
|
+
for (const skill of config.skills) {
|
|
1125
|
+
const augmentation = skill.getPromptAugmentation({
|
|
1126
|
+
url: currentUrl,
|
|
1127
|
+
locale: promptLocale
|
|
1128
|
+
});
|
|
1129
|
+
if (augmentation) {
|
|
1130
|
+
systemContent += augmentation;
|
|
1131
|
+
}
|
|
1132
|
+
}
|
|
1133
|
+
}
|
|
1134
|
+
const systemMessage = {
|
|
1135
|
+
role: "system",
|
|
1136
|
+
content: systemContent,
|
|
1137
|
+
providerOptions: {
|
|
1138
|
+
anthropic: { cacheControl: { type: "ephemeral" } }
|
|
1139
|
+
}
|
|
1140
|
+
};
|
|
1141
|
+
const STALL_THRESHOLD = 6;
|
|
1142
|
+
let lastProgressPage = 0;
|
|
1143
|
+
let stepsWithoutProgress = 0;
|
|
1144
|
+
const prepareStep = ({ messages, stepNumber }) => {
|
|
1145
|
+
if (stepNumber <= 1) return void 0;
|
|
1146
|
+
const pruned = pruneOldToolResults(messages, config.historyWindow);
|
|
1147
|
+
if (paginationTracker.currentPage > lastProgressPage) {
|
|
1148
|
+
lastProgressPage = paginationTracker.currentPage;
|
|
1149
|
+
stepsWithoutProgress = 0;
|
|
1150
|
+
} else {
|
|
1151
|
+
stepsWithoutProgress++;
|
|
1152
|
+
}
|
|
1153
|
+
const paginationInProgress = paginationTracker.totalPages > 0 && paginationTracker.currentPage < paginationTracker.totalPages;
|
|
1154
|
+
const notStalled = stepsWithoutProgress < STALL_THRESHOLD;
|
|
1155
|
+
if (config.enableMultiModel) {
|
|
1156
|
+
const decision = routeExplorationModel(stepNumber, messages, recordedSteps);
|
|
1157
|
+
const model = config.aiProvider ? config.aiProvider.getModel(decision.purpose) : getModel(decision.purpose);
|
|
1158
|
+
if (paginationInProgress && notStalled) {
|
|
1159
|
+
return { messages: pruned, toolChoice: "required", model };
|
|
1160
|
+
}
|
|
1161
|
+
return { messages: pruned, model };
|
|
1162
|
+
}
|
|
1163
|
+
if (paginationInProgress && notStalled) {
|
|
1164
|
+
return { messages: pruned, toolChoice: "required" };
|
|
1165
|
+
}
|
|
1166
|
+
return { messages: pruned };
|
|
1167
|
+
};
|
|
1168
|
+
const explorationModel = config.aiProvider ? config.aiProvider.getModel("exploration") : getModel("exploration");
|
|
1169
|
+
const result = await trackedGenerateText("exploration", {
|
|
1170
|
+
model: explorationModel,
|
|
1171
|
+
messages: [
|
|
1172
|
+
systemMessage,
|
|
1173
|
+
{ role: "user", content: getInitialUserMessage(promptLocale) }
|
|
1174
|
+
],
|
|
1175
|
+
tools: { browser: browserTool },
|
|
1176
|
+
// output: Output.object 削除 — responseFormat の干渉を排除
|
|
1177
|
+
stopWhen: [stepCountIs(totalMaxSteps + 1)],
|
|
1178
|
+
prepareStep
|
|
1179
|
+
});
|
|
1180
|
+
const goalResult = parseAgentResultFallback(result.text);
|
|
1181
|
+
const usageInfo = {
|
|
1182
|
+
promptTokens: result.usage.inputTokens ?? 0,
|
|
1183
|
+
completionTokens: result.usage.outputTokens ?? 0,
|
|
1184
|
+
totalTokens: (result.usage.inputTokens ?? 0) + (result.usage.outputTokens ?? 0)
|
|
1185
|
+
};
|
|
1186
|
+
if (goalResult.goalAchieved) {
|
|
1187
|
+
const verification = await verifyGoalAchievement(
|
|
1188
|
+
browser,
|
|
1189
|
+
config.goal,
|
|
1190
|
+
goalResult.summary,
|
|
1191
|
+
recordedSteps,
|
|
1192
|
+
config.aiProvider
|
|
1193
|
+
);
|
|
1194
|
+
if (!verification.achieved) {
|
|
1195
|
+
goalResult.goalAchieved = false;
|
|
1196
|
+
goalResult.summary = `[Verification failed: ${verification.reason}] ${goalResult.summary}`;
|
|
1197
|
+
}
|
|
1198
|
+
} else if (!goalResult.parsed) {
|
|
1199
|
+
const verification = await verifyGoalAchievement(
|
|
1200
|
+
browser,
|
|
1201
|
+
config.goal,
|
|
1202
|
+
goalResult.summary,
|
|
1203
|
+
recordedSteps,
|
|
1204
|
+
config.aiProvider
|
|
1205
|
+
);
|
|
1206
|
+
if (verification.achieved) {
|
|
1207
|
+
goalResult.goalAchieved = true;
|
|
1208
|
+
goalResult.summary = verification.reason;
|
|
1209
|
+
}
|
|
1210
|
+
}
|
|
1211
|
+
return {
|
|
1212
|
+
goalResult,
|
|
1213
|
+
totalTokens: usageInfo
|
|
1214
|
+
};
|
|
1215
|
+
}
|
|
1216
|
+
function pruneOldToolResults(messages, historyWindow = 10) {
|
|
1217
|
+
let toolIndices = [];
|
|
1218
|
+
for (let i = 0; i < messages.length; i++) {
|
|
1219
|
+
if (messages[i].role === "tool") toolIndices.push(i);
|
|
1220
|
+
}
|
|
1221
|
+
if (toolIndices.length <= 2) return messages;
|
|
1222
|
+
const phase = detectExplorationPhase(messages, toolIndices);
|
|
1223
|
+
const rawSize = phase === "form_filling" ? 4 : 2;
|
|
1224
|
+
const compactSize = phase === "form_filling" ? 4 : 2;
|
|
1225
|
+
if (toolIndices.length > historyWindow) {
|
|
1226
|
+
const cutoffIndex = toolIndices[toolIndices.length - historyWindow];
|
|
1227
|
+
let startIndex = cutoffIndex;
|
|
1228
|
+
if (startIndex > 0 && messages[startIndex - 1].role === "assistant") {
|
|
1229
|
+
startIndex = startIndex - 1;
|
|
1230
|
+
}
|
|
1231
|
+
const preamble = messages.filter(
|
|
1232
|
+
(m, i) => i < startIndex && (m.role === "system" || m.role === "user" && i < 3)
|
|
1233
|
+
);
|
|
1234
|
+
const droppedCount = toolIndices.length - historyWindow;
|
|
1235
|
+
const summaryMsg = {
|
|
1236
|
+
role: "user",
|
|
1237
|
+
content: `[${droppedCount} earlier tool turns omitted for context efficiency]`
|
|
1238
|
+
};
|
|
1239
|
+
messages = [...preamble, summaryMsg, ...messages.slice(startIndex)];
|
|
1240
|
+
toolIndices = [];
|
|
1241
|
+
for (let i = 0; i < messages.length; i++) {
|
|
1242
|
+
if (messages[i].role === "tool") toolIndices.push(i);
|
|
1243
|
+
}
|
|
1244
|
+
if (toolIndices.length <= 2) return messages;
|
|
1245
|
+
}
|
|
1246
|
+
const estimatedChars = estimateMessageChars(messages);
|
|
1247
|
+
const HIGH_PRESSURE_CHARS = 4e5;
|
|
1248
|
+
const isHighPressure = estimatedChars > HIGH_PRESSURE_CHARS;
|
|
1249
|
+
const effectiveRawSize = isHighPressure ? Math.max(1, rawSize - 1) : rawSize;
|
|
1250
|
+
const effectiveCompactSize = isHighPressure ? Math.max(1, compactSize - 1) : compactSize;
|
|
1251
|
+
const recentThreshold = toolIndices[Math.max(0, toolIndices.length - effectiveRawSize)];
|
|
1252
|
+
const compactThreshold = toolIndices.length > effectiveRawSize + effectiveCompactSize ? toolIndices[toolIndices.length - effectiveRawSize - effectiveCompactSize] : 0;
|
|
1253
|
+
return messages.map((msg, i) => {
|
|
1254
|
+
if (msg.role !== "tool") return msg;
|
|
1255
|
+
if (i >= recentThreshold) return msg;
|
|
1256
|
+
if (i >= compactThreshold) return compactToolResult(msg);
|
|
1257
|
+
return summarizeToolResult(msg);
|
|
1258
|
+
});
|
|
1259
|
+
}
|
|
1260
|
+
function detectExplorationPhase(messages, toolIndices) {
|
|
1261
|
+
const recentTools = toolIndices.slice(-3);
|
|
1262
|
+
let fillCount = 0;
|
|
1263
|
+
let extractCount = 0;
|
|
1264
|
+
for (const idx of recentTools) {
|
|
1265
|
+
const msg = messages[idx];
|
|
1266
|
+
const val = extractToolOutputValue(msg);
|
|
1267
|
+
if (val) {
|
|
1268
|
+
if ("filledFields" in val && val.filledFields) fillCount++;
|
|
1269
|
+
if ("extractedData" in val && val.extractedData) extractCount++;
|
|
1270
|
+
if ("memoryStatus" in val && val.memoryStatus) extractCount++;
|
|
1271
|
+
}
|
|
1272
|
+
}
|
|
1273
|
+
if (fillCount >= 2) return "form_filling";
|
|
1274
|
+
if (extractCount >= 2) return "data_collection";
|
|
1275
|
+
return "exploring";
|
|
1276
|
+
}
|
|
1277
|
+
function extractToolOutputValue(msg) {
|
|
1278
|
+
if (!Array.isArray(msg.content)) return null;
|
|
1279
|
+
const parts = msg.content;
|
|
1280
|
+
for (const part of parts) {
|
|
1281
|
+
if (part.type !== "tool-result") continue;
|
|
1282
|
+
const output = part.output;
|
|
1283
|
+
if (output?.type === "json" && typeof output.value === "object" && output.value !== null) {
|
|
1284
|
+
return output.value;
|
|
1285
|
+
}
|
|
1286
|
+
}
|
|
1287
|
+
return null;
|
|
1288
|
+
}
|
|
1289
|
+
function estimateMessageChars(messages) {
|
|
1290
|
+
let total = 0;
|
|
1291
|
+
for (const msg of messages) {
|
|
1292
|
+
if (typeof msg.content === "string") {
|
|
1293
|
+
total += msg.content.length;
|
|
1294
|
+
} else if (Array.isArray(msg.content)) {
|
|
1295
|
+
total += JSON.stringify(msg.content).length;
|
|
1296
|
+
}
|
|
1297
|
+
}
|
|
1298
|
+
return total;
|
|
1299
|
+
}
|
|
1300
|
+
function mapToolResultValue(msg, transform) {
|
|
1301
|
+
if (!Array.isArray(msg.content)) return msg;
|
|
1302
|
+
const parts = msg.content;
|
|
1303
|
+
const prunedContent = parts.map((part) => {
|
|
1304
|
+
if (part.type !== "tool-result") return part;
|
|
1305
|
+
const output = part.output;
|
|
1306
|
+
if (typeof output !== "object" || output === null) return part;
|
|
1307
|
+
const out = output;
|
|
1308
|
+
if (out.type === "json" && typeof out.value === "object" && out.value !== null) {
|
|
1309
|
+
const val = { ...out.value };
|
|
1310
|
+
const transformed = transform(val);
|
|
1311
|
+
return { ...part, output: { ...out, value: transformed } };
|
|
1312
|
+
}
|
|
1313
|
+
return part;
|
|
1314
|
+
});
|
|
1315
|
+
return { ...msg, content: prunedContent };
|
|
1316
|
+
}
|
|
1317
|
+
function compactToolResult(msg) {
|
|
1318
|
+
return mapToolResultValue(msg, (val) => {
|
|
1319
|
+
if ("snapshot" in val) {
|
|
1320
|
+
val.snapshot = "[\u7701\u7565]";
|
|
1321
|
+
}
|
|
1322
|
+
if ("extractedData" in val) {
|
|
1323
|
+
delete val.extractedData;
|
|
1324
|
+
}
|
|
1325
|
+
return val;
|
|
1326
|
+
});
|
|
1327
|
+
}
|
|
1328
|
+
function summarizeToolResult(msg) {
|
|
1329
|
+
return mapToolResultValue(msg, (val) => {
|
|
1330
|
+
if ("snapshot" in val) {
|
|
1331
|
+
delete val.snapshot;
|
|
1332
|
+
}
|
|
1333
|
+
if ("extractedData" in val) {
|
|
1334
|
+
delete val.extractedData;
|
|
1335
|
+
}
|
|
1336
|
+
if ("results" in val && Array.isArray(val.results)) {
|
|
1337
|
+
const results = val.results;
|
|
1338
|
+
const failures = results.filter((r) => !r.success);
|
|
1339
|
+
const successes = results.filter((r) => r.success);
|
|
1340
|
+
const compressed = [...failures];
|
|
1341
|
+
if (successes.length > 0) {
|
|
1342
|
+
compressed.push({ description: `${successes.length}\u4EF6\u6210\u529F`, success: true });
|
|
1343
|
+
}
|
|
1344
|
+
val.results = compressed;
|
|
1345
|
+
}
|
|
1346
|
+
return val;
|
|
1347
|
+
});
|
|
1348
|
+
}
|
|
1349
|
+
var agentResultSchema = z2.object({
|
|
1350
|
+
goalAchieved: z2.boolean(),
|
|
1351
|
+
summary: z2.string().default("")
|
|
1352
|
+
});
|
|
1353
|
+
var goalVerificationSchema = z2.object({
|
|
1354
|
+
achieved: z2.boolean(),
|
|
1355
|
+
reason: z2.string()
|
|
1356
|
+
});
|
|
1357
|
+
var SIDE_EFFECT_ACTIONS = /* @__PURE__ */ new Set(["download", "export", "memory_append", "memory_aggregate", "extract"]);
|
|
1358
|
+
function buildExecutionEvidence(recordedSteps) {
|
|
1359
|
+
const totalSteps = recordedSteps.length;
|
|
1360
|
+
const succeededSteps = recordedSteps.filter((s) => s.success).length;
|
|
1361
|
+
const lines = [];
|
|
1362
|
+
lines.push(`Completed Actions (${totalSteps} steps, ${succeededSteps} succeeded):`);
|
|
1363
|
+
for (const step of recordedSteps) {
|
|
1364
|
+
const status = step.success ? "success" : "FAILED";
|
|
1365
|
+
const errorSuffix = step.error ? ` \u2014 ${step.error}` : "";
|
|
1366
|
+
lines.push(`- Step ${step.ordinal}: ${step.action.action} \u2014 ${step.action.description} (${status}${errorSuffix})`);
|
|
1367
|
+
}
|
|
1368
|
+
const sideEffectCounts = /* @__PURE__ */ new Map();
|
|
1369
|
+
for (const step of recordedSteps) {
|
|
1370
|
+
if (step.success && SIDE_EFFECT_ACTIONS.has(step.action.action)) {
|
|
1371
|
+
sideEffectCounts.set(step.action.action, (sideEffectCounts.get(step.action.action) ?? 0) + 1);
|
|
1372
|
+
}
|
|
1373
|
+
}
|
|
1374
|
+
if (sideEffectCounts.size > 0) {
|
|
1375
|
+
const parts = Array.from(sideEffectCounts.entries()).map(
|
|
1376
|
+
([action, count]) => `${count} ${action}`
|
|
1377
|
+
);
|
|
1378
|
+
lines.push(`
|
|
1379
|
+
Side effects (not visible in page snapshot): ${parts.join(", ")}`);
|
|
1380
|
+
}
|
|
1381
|
+
return lines.join("\n");
|
|
1382
|
+
}
|
|
1383
|
+
async function verifyGoalAchievement(browser, goal, declaredSummary, recordedSteps, aiProvider) {
|
|
1384
|
+
try {
|
|
1385
|
+
const snapshot = await browser.snapshot();
|
|
1386
|
+
const currentUrl = await browser.url();
|
|
1387
|
+
const truncatedSnapshot = snapshot.length > 8e3 ? `${snapshot.slice(0, 8e3)}
|
|
1388
|
+
[... truncated]` : snapshot;
|
|
1389
|
+
const executionEvidence = buildExecutionEvidence(recordedSteps);
|
|
1390
|
+
const prompt = `You are an independent verifier. Your task is to check whether a browser automation goal has ACTUALLY been achieved based on the current page state and execution evidence.
|
|
1391
|
+
|
|
1392
|
+
## Goal
|
|
1393
|
+
${goal}
|
|
1394
|
+
|
|
1395
|
+
## Current State
|
|
1396
|
+
URL: ${currentUrl}
|
|
1397
|
+
Page snapshot (accessibility tree):
|
|
1398
|
+
${truncatedSnapshot}
|
|
1399
|
+
|
|
1400
|
+
## Agent's Claim
|
|
1401
|
+
"${declaredSummary}"
|
|
1402
|
+
|
|
1403
|
+
## Execution Evidence
|
|
1404
|
+
${executionEvidence}
|
|
1405
|
+
|
|
1406
|
+
## Verification Checklist
|
|
1407
|
+
Check ALL of the following:
|
|
1408
|
+
1. Does the current page clearly show the expected outcome of the goal?
|
|
1409
|
+
2. Are there any error messages, validation warnings, or "try again" prompts?
|
|
1410
|
+
3. For login/auth goals: Is a login form still visible? Are there auth error messages?
|
|
1411
|
+
4. For form submission goals: Is there a clear success confirmation? Or is the form still displayed?
|
|
1412
|
+
5. For navigation goals: Does the URL and page content match the intended destination?
|
|
1413
|
+
6. For compound goals (A then B then C): Can you see evidence that ALL parts were completed, not just the first part?
|
|
1414
|
+
7. For goals involving file downloads, data export, or memory operations: These side effects are confirmed by the Execution Evidence above, NOT by the page snapshot. If the execution log shows all download/export/aggregate steps succeeded, consider this as valid evidence of completion.
|
|
1415
|
+
|
|
1416
|
+
Be CONSERVATIVE: if the page state is ambiguous or doesn't clearly confirm ALL parts of the goal were achieved, set achieved to false.
|
|
1417
|
+
Do NOT trust the agent's claim \u2014 verify independently from the page state and execution evidence.`;
|
|
1418
|
+
const verificationModel = aiProvider ? aiProvider.getModel("exploration") : getModel("exploration");
|
|
1419
|
+
const verificationResult = await trackedGenerateObject("exploration", {
|
|
1420
|
+
model: verificationModel,
|
|
1421
|
+
prompt,
|
|
1422
|
+
schema: goalVerificationSchema,
|
|
1423
|
+
temperature: 0
|
|
1424
|
+
});
|
|
1425
|
+
return verificationResult.object ?? { achieved: true, reason: "verification_returned_null" };
|
|
1426
|
+
} catch (error) {
|
|
1427
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
1428
|
+
console.warn(`[exploration-agent] Goal verification failed: ${msg}`);
|
|
1429
|
+
return { achieved: true, reason: "verification_error" };
|
|
1430
|
+
}
|
|
1431
|
+
}
|
|
1432
|
+
function parseAgentResultFallback(text) {
|
|
1433
|
+
try {
|
|
1434
|
+
const result = agentResultSchema.parse(JSON.parse(text.trim()));
|
|
1435
|
+
return { ...result, parsed: true };
|
|
1436
|
+
} catch {
|
|
1437
|
+
}
|
|
1438
|
+
const jsonMatch = text.match(/\{[\s\S]*\}/);
|
|
1439
|
+
if (jsonMatch) {
|
|
1440
|
+
try {
|
|
1441
|
+
const result = agentResultSchema.parse(JSON.parse(jsonMatch[0]));
|
|
1442
|
+
return { ...result, parsed: true };
|
|
1443
|
+
} catch {
|
|
1444
|
+
}
|
|
1445
|
+
}
|
|
1446
|
+
return { goalAchieved: false, summary: text.slice(0, 200), parsed: false };
|
|
1447
|
+
}
|
|
1448
|
+
|
|
1449
|
+
// src/runbook-generator/intervention-controller.ts
|
|
1450
|
+
var InterventionController = class {
|
|
1451
|
+
constructor() {
|
|
1452
|
+
this.pauseRequested = false;
|
|
1453
|
+
this.cancelRequested = false;
|
|
1454
|
+
this.stdinCleanup = null;
|
|
1455
|
+
this.options = {};
|
|
1456
|
+
this.interventions = [];
|
|
1457
|
+
}
|
|
1458
|
+
/**
|
|
1459
|
+
* spinner コールバックを後から設定する
|
|
1460
|
+
*/
|
|
1461
|
+
setCallbacks(options) {
|
|
1462
|
+
this.options = options;
|
|
1463
|
+
}
|
|
1464
|
+
/**
|
|
1465
|
+
* stdin の raw mode キーリスナーを開始。
|
|
1466
|
+
* Enter キーまたは p キーで pauseRequested フラグをセット。
|
|
1467
|
+
*/
|
|
1468
|
+
startListening() {
|
|
1469
|
+
if (!process.stdin.isTTY) return;
|
|
1470
|
+
const onData = (data) => {
|
|
1471
|
+
if (data[0] === 113 || data[0] === 3) {
|
|
1472
|
+
if (!this.cancelRequested) {
|
|
1473
|
+
this.cancelRequested = true;
|
|
1474
|
+
log.warn("\u23F9 \u30AD\u30E3\u30F3\u30BB\u30EB\u3092\u8981\u6C42\u3057\u307E\u3057\u305F...");
|
|
1475
|
+
this.options.onCancel?.();
|
|
1476
|
+
}
|
|
1477
|
+
return;
|
|
1478
|
+
}
|
|
1479
|
+
if (data[0] === 13 || data[0] === 112) {
|
|
1480
|
+
if (!this.pauseRequested) {
|
|
1481
|
+
this.pauseRequested = true;
|
|
1482
|
+
log.warn("\u23F8 \u4E00\u6642\u505C\u6B62\u3092\u8981\u6C42\u3057\u307E\u3057\u305F\u3002\u6B21\u306E\u30C4\u30FC\u30EB\u547C\u3073\u51FA\u3057\u3067\u505C\u6B62\u3057\u307E\u3059...");
|
|
1483
|
+
}
|
|
1484
|
+
}
|
|
1485
|
+
};
|
|
1486
|
+
process.stdin.setRawMode(true);
|
|
1487
|
+
process.stdin.resume();
|
|
1488
|
+
process.stdin.on("data", onData);
|
|
1489
|
+
this.stdinCleanup = () => {
|
|
1490
|
+
process.stdin.off("data", onData);
|
|
1491
|
+
try {
|
|
1492
|
+
process.stdin.setRawMode(false);
|
|
1493
|
+
} catch {
|
|
1494
|
+
}
|
|
1495
|
+
process.stdin.pause();
|
|
1496
|
+
};
|
|
1497
|
+
}
|
|
1498
|
+
/**
|
|
1499
|
+
* 一時停止が要求されているか確認
|
|
1500
|
+
*/
|
|
1501
|
+
isPauseRequested() {
|
|
1502
|
+
return this.pauseRequested;
|
|
1503
|
+
}
|
|
1504
|
+
/**
|
|
1505
|
+
* キャンセルが要求されているか確認
|
|
1506
|
+
*/
|
|
1507
|
+
isCancelRequested() {
|
|
1508
|
+
return this.cancelRequested;
|
|
1509
|
+
}
|
|
1510
|
+
/**
|
|
1511
|
+
* 一時停止中にユーザーから介入指示を収集。
|
|
1512
|
+
* stdin raw mode を一時的に解除して @clack/prompts を使う。
|
|
1513
|
+
*/
|
|
1514
|
+
async collectIntervention(currentStepIndex, currentUrl) {
|
|
1515
|
+
this.suspendRawMode();
|
|
1516
|
+
this.options.onPause?.();
|
|
1517
|
+
try {
|
|
1518
|
+
log.info(
|
|
1519
|
+
`--- \u4E00\u6642\u505C\u6B62 (\u30B9\u30C6\u30C3\u30D7 #${currentStepIndex}, URL: ${currentUrl}) ---`
|
|
1520
|
+
);
|
|
1521
|
+
const action = await promptSelect(
|
|
1522
|
+
"\u64CD\u4F5C\u3092\u9078\u629E\u3057\u3066\u304F\u3060\u3055\u3044",
|
|
1523
|
+
[
|
|
1524
|
+
{
|
|
1525
|
+
value: "guidance",
|
|
1526
|
+
label: "\u30AC\u30A4\u30C0\u30F3\u30B9\u3092\u5165\u529B\u3057\u3066\u7D9A\u884C",
|
|
1527
|
+
hint: "AI\u306E\u63A2\u7D22\u65B9\u91DD\u3092\u4FEE\u6B63"
|
|
1528
|
+
},
|
|
1529
|
+
{ value: "abort", label: "\u63A2\u7D22\u3092\u4E2D\u6B62" }
|
|
1530
|
+
]
|
|
1531
|
+
);
|
|
1532
|
+
if (action === "abort") {
|
|
1533
|
+
return { action: "abort" };
|
|
1534
|
+
}
|
|
1535
|
+
const guidanceText = await promptText("\u30AC\u30A4\u30C0\u30F3\u30B9 (AI\u3078\u306E\u6307\u793A)", {
|
|
1536
|
+
validate: (v) => !v?.trim() ? "\u30AC\u30A4\u30C0\u30F3\u30B9\u3092\u5165\u529B\u3057\u3066\u304F\u3060\u3055\u3044" : void 0
|
|
1537
|
+
});
|
|
1538
|
+
const record = {
|
|
1539
|
+
stepIndex: currentStepIndex,
|
|
1540
|
+
userInstruction: guidanceText.trim(),
|
|
1541
|
+
url: currentUrl,
|
|
1542
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
1543
|
+
};
|
|
1544
|
+
this.interventions.push(record);
|
|
1545
|
+
log.success(`\u30AC\u30A4\u30C0\u30F3\u30B9\u3092\u8A18\u9332\u3057\u307E\u3057\u305F: "${guidanceText.trim()}"`);
|
|
1546
|
+
return { action: "guidance", guidanceText: guidanceText.trim() };
|
|
1547
|
+
} finally {
|
|
1548
|
+
this.pauseRequested = false;
|
|
1549
|
+
this.resumeRawMode();
|
|
1550
|
+
this.options.onResume?.();
|
|
1551
|
+
}
|
|
1552
|
+
}
|
|
1553
|
+
/**
|
|
1554
|
+
* リスナーを停止しリソースを解放
|
|
1555
|
+
*/
|
|
1556
|
+
dispose() {
|
|
1557
|
+
if (this.stdinCleanup) {
|
|
1558
|
+
this.stdinCleanup();
|
|
1559
|
+
this.stdinCleanup = null;
|
|
1560
|
+
}
|
|
1561
|
+
}
|
|
1562
|
+
suspendRawMode() {
|
|
1563
|
+
if (this.stdinCleanup) {
|
|
1564
|
+
try {
|
|
1565
|
+
process.stdin.setRawMode(false);
|
|
1566
|
+
} catch {
|
|
1567
|
+
}
|
|
1568
|
+
process.stdin.pause();
|
|
1569
|
+
}
|
|
1570
|
+
}
|
|
1571
|
+
resumeRawMode() {
|
|
1572
|
+
if (this.stdinCleanup) {
|
|
1573
|
+
try {
|
|
1574
|
+
process.stdin.setRawMode(true);
|
|
1575
|
+
} catch {
|
|
1576
|
+
}
|
|
1577
|
+
process.stdin.resume();
|
|
1578
|
+
}
|
|
1579
|
+
}
|
|
1580
|
+
};
|
|
1581
|
+
|
|
1582
|
+
// src/runbook-generator/explorer.ts
|
|
1583
|
+
import { mkdir } from "fs/promises";
|
|
1584
|
+
import { join } from "path";
|
|
1585
|
+
async function explore(config, options) {
|
|
1586
|
+
const browser = options?.browser ?? new AgentBrowser();
|
|
1587
|
+
const ownsBrowser = !options?.browser;
|
|
1588
|
+
const recordedSteps = [];
|
|
1589
|
+
const debugLogger = createDebugLogger({
|
|
1590
|
+
filePath: config.debugLogPath,
|
|
1591
|
+
console: config.debugConsole
|
|
1592
|
+
});
|
|
1593
|
+
const interventionController = options?.interventionController ?? new InterventionController();
|
|
1594
|
+
const downloadManager = options?.downloadManager ?? (config.screenshotDir ? new DownloadManager(join(config.screenshotDir, "..")) : void 0);
|
|
1595
|
+
if (config.screenshotDir) {
|
|
1596
|
+
await mkdir(config.screenshotDir, { recursive: true });
|
|
1597
|
+
}
|
|
1598
|
+
try {
|
|
1599
|
+
log.step(tf("generator.openingUrl", { url: config.url }));
|
|
1600
|
+
await browser.open(config.url, {
|
|
1601
|
+
headless: config.headless,
|
|
1602
|
+
stealth: config.stealth,
|
|
1603
|
+
proxy: config.proxy
|
|
1604
|
+
});
|
|
1605
|
+
await sleep(1e3);
|
|
1606
|
+
log.info(t("generator.pageLoaded"));
|
|
1607
|
+
if (config.videoDir) {
|
|
1608
|
+
try {
|
|
1609
|
+
await browser.startRecording(config.videoDir);
|
|
1610
|
+
log.info(tf("generator.recordingStarted", { path: config.videoDir }));
|
|
1611
|
+
} catch (e) {
|
|
1612
|
+
log.warn(tf("generator.recordingStartFailed", { error: e instanceof Error ? e.message : String(e) }));
|
|
1613
|
+
}
|
|
1614
|
+
}
|
|
1615
|
+
if (config.screenshotDir) {
|
|
1616
|
+
try {
|
|
1617
|
+
const initialPath = join(config.screenshotDir, "initial.png");
|
|
1618
|
+
await browser.screenshot(initialPath);
|
|
1619
|
+
log.step(tf("generator.screenshotSaved", { path: initialPath }));
|
|
1620
|
+
} catch {
|
|
1621
|
+
}
|
|
1622
|
+
}
|
|
1623
|
+
interventionController.setCallbacks({
|
|
1624
|
+
onPause: () => log.info(t("generator.paused")),
|
|
1625
|
+
onResume: () => log.step(t("generator.waitingForAI")),
|
|
1626
|
+
onCancel: () => log.info(t("generator.cancelledByUser"))
|
|
1627
|
+
});
|
|
1628
|
+
const dataStore = new InMemoryDataStore();
|
|
1629
|
+
let skills = [];
|
|
1630
|
+
if (config.skills && config.skills.length > 0) {
|
|
1631
|
+
await initBuiltinSkills();
|
|
1632
|
+
skills = createSkills(config.skills);
|
|
1633
|
+
log.info(`Skills: ${skills.map((s) => s.name).join(", ")}`);
|
|
1634
|
+
}
|
|
1635
|
+
const maxSteps = config.maxIterations * 3;
|
|
1636
|
+
log.step(t("generator.waitingForAI"));
|
|
1637
|
+
interventionController.startListening();
|
|
1638
|
+
const agentStart = performance.now();
|
|
1639
|
+
const result = await exploreWithAI(browser, recordedSteps, {
|
|
1640
|
+
goal: config.goal,
|
|
1641
|
+
locale: config.locale,
|
|
1642
|
+
contextMarkdown: config.contextMarkdown,
|
|
1643
|
+
secrets: config.secrets,
|
|
1644
|
+
maxIterations: config.maxIterations,
|
|
1645
|
+
stepDelay: config.stepDelay,
|
|
1646
|
+
snapshotFilter: config.snapshotFilter,
|
|
1647
|
+
screenshotDir: config.screenshotDir,
|
|
1648
|
+
debugLogger,
|
|
1649
|
+
interventionController,
|
|
1650
|
+
historyWindow: config.historyWindow,
|
|
1651
|
+
enableMultiModel: config.enableMultiModel,
|
|
1652
|
+
skills: skills.length > 0 ? skills : void 0,
|
|
1653
|
+
aiProvider: options?.aiProvider,
|
|
1654
|
+
onToolCallStart: (stepCount) => {
|
|
1655
|
+
log.step(tf("generator.exploringStep", { current: stepCount + 1, total: maxSteps }));
|
|
1656
|
+
},
|
|
1657
|
+
onToolCallEnd: () => {
|
|
1658
|
+
const lastStep = recordedSteps[recordedSteps.length - 1];
|
|
1659
|
+
if (lastStep && options?.onExplorerStep) {
|
|
1660
|
+
options.onExplorerStep(
|
|
1661
|
+
recordedSteps.length,
|
|
1662
|
+
lastStep.action.description ?? lastStep.action.action,
|
|
1663
|
+
lastStep
|
|
1664
|
+
).catch(() => {
|
|
1665
|
+
});
|
|
1666
|
+
}
|
|
1667
|
+
}
|
|
1668
|
+
}, dataStore, downloadManager);
|
|
1669
|
+
interventionController.dispose();
|
|
1670
|
+
log.info(tf("generator.explorationComplete", { duration: formatDuration(performance.now() - agentStart) }));
|
|
1671
|
+
const { goalResult } = result;
|
|
1672
|
+
log.info(`Goal achieved: ${goalResult.goalAchieved}`);
|
|
1673
|
+
log.info(`Summary: ${goalResult.summary}`);
|
|
1674
|
+
log.info(`Tokens: prompt=${result.totalTokens.promptTokens}, completion=${result.totalTokens.completionTokens}, total=${result.totalTokens.totalTokens}`);
|
|
1675
|
+
if (dataStore.listCollections().length > 0) {
|
|
1676
|
+
log.info(`Memory collections: ${dataStore.listCollections().join(", ")}`);
|
|
1677
|
+
const memoryOutputDir = config.screenshotDir ? join(config.screenshotDir, "..") : void 0;
|
|
1678
|
+
if (memoryOutputDir) {
|
|
1679
|
+
for (const collection of dataStore.listCollections()) {
|
|
1680
|
+
const filePath = join(memoryOutputDir, `${collection}.json`);
|
|
1681
|
+
await dataStore.writeToFile(collection, filePath, "json");
|
|
1682
|
+
log.info(`Memory data exported: ${filePath}`);
|
|
1683
|
+
}
|
|
1684
|
+
}
|
|
1685
|
+
}
|
|
1686
|
+
if (downloadManager && downloadManager.getDownloads().length > 0) {
|
|
1687
|
+
log.info(`Downloaded files: ${downloadManager.getDownloads().length}`);
|
|
1688
|
+
for (const dl of downloadManager.getDownloads()) {
|
|
1689
|
+
log.info(` - ${dl.filename} (${dl.path})`);
|
|
1690
|
+
}
|
|
1691
|
+
}
|
|
1692
|
+
await debugLogger.flush();
|
|
1693
|
+
const interventions = interventionController.interventions;
|
|
1694
|
+
const additionalGuidance = interventions.map((iv) => iv.userInstruction);
|
|
1695
|
+
return {
|
|
1696
|
+
recordedSteps,
|
|
1697
|
+
goalAchieved: goalResult.goalAchieved,
|
|
1698
|
+
additionalGuidance,
|
|
1699
|
+
interventions,
|
|
1700
|
+
cancelled: interventionController.isCancelRequested(),
|
|
1701
|
+
downloads: downloadManager?.getDownloads() ?? []
|
|
1702
|
+
};
|
|
1703
|
+
} catch (err) {
|
|
1704
|
+
const interventions = interventionController.interventions;
|
|
1705
|
+
return {
|
|
1706
|
+
recordedSteps,
|
|
1707
|
+
goalAchieved: false,
|
|
1708
|
+
additionalGuidance: interventions.map((iv) => iv.userInstruction),
|
|
1709
|
+
interventions,
|
|
1710
|
+
cancelled: interventionController.isCancelRequested(),
|
|
1711
|
+
downloads: downloadManager?.getDownloads() ?? [],
|
|
1712
|
+
error: err instanceof Error ? err : new Error(String(err))
|
|
1713
|
+
};
|
|
1714
|
+
} finally {
|
|
1715
|
+
try {
|
|
1716
|
+
interventionController.dispose();
|
|
1717
|
+
} catch {
|
|
1718
|
+
}
|
|
1719
|
+
if (browser.isRecording()) {
|
|
1720
|
+
try {
|
|
1721
|
+
const result = await browser.stopRecording();
|
|
1722
|
+
for (const p of result.paths) {
|
|
1723
|
+
log.info(tf("generator.recordingComplete", { path: p }));
|
|
1724
|
+
}
|
|
1725
|
+
} catch (e) {
|
|
1726
|
+
log.warn(tf("generator.recordingStopFailed", { error: e instanceof Error ? e.message : String(e) }));
|
|
1727
|
+
}
|
|
1728
|
+
}
|
|
1729
|
+
if (ownsBrowser) {
|
|
1730
|
+
try {
|
|
1731
|
+
await browser.close();
|
|
1732
|
+
} catch {
|
|
1733
|
+
}
|
|
1734
|
+
}
|
|
1735
|
+
}
|
|
1736
|
+
}
|
|
1737
|
+
|
|
1738
|
+
// src/runbook-generator/runbook-builder.ts
|
|
1739
|
+
import { stringify } from "yaml";
|
|
1740
|
+
function toActionType(action) {
|
|
1741
|
+
switch (action) {
|
|
1742
|
+
case "fill":
|
|
1743
|
+
case "type":
|
|
1744
|
+
return "input";
|
|
1745
|
+
case "check":
|
|
1746
|
+
case "uncheck":
|
|
1747
|
+
case "click":
|
|
1748
|
+
return "click";
|
|
1749
|
+
case "select":
|
|
1750
|
+
return "select";
|
|
1751
|
+
case "navigate":
|
|
1752
|
+
return "navigate";
|
|
1753
|
+
case "scroll":
|
|
1754
|
+
return "scroll";
|
|
1755
|
+
case "wait":
|
|
1756
|
+
return "wait";
|
|
1757
|
+
case "extract":
|
|
1758
|
+
return "extract";
|
|
1759
|
+
case "download":
|
|
1760
|
+
return "download";
|
|
1761
|
+
case "export":
|
|
1762
|
+
return "export";
|
|
1763
|
+
case "memory_append":
|
|
1764
|
+
case "memory_aggregate":
|
|
1765
|
+
return "memory";
|
|
1766
|
+
case "key":
|
|
1767
|
+
return "key";
|
|
1768
|
+
default:
|
|
1769
|
+
return "click";
|
|
1770
|
+
}
|
|
1771
|
+
}
|
|
1772
|
+
function categoryToSource(category) {
|
|
1773
|
+
switch (category) {
|
|
1774
|
+
case "credential":
|
|
1775
|
+
return "prompt";
|
|
1776
|
+
case "user_data":
|
|
1777
|
+
return "prompt";
|
|
1778
|
+
case "fixed":
|
|
1779
|
+
return "fixed";
|
|
1780
|
+
case "navigation":
|
|
1781
|
+
return "fixed";
|
|
1782
|
+
default:
|
|
1783
|
+
return "prompt";
|
|
1784
|
+
}
|
|
1785
|
+
}
|
|
1786
|
+
function buildRunbookYaml(input) {
|
|
1787
|
+
const { goal, startUrl, recordedSteps, goalAchieved, stepDelay, reviewResult, humanGuidance, interventions } = input;
|
|
1788
|
+
const successfulSteps = recordedSteps.filter((s) => s.success);
|
|
1789
|
+
const reviewMap = new Map(
|
|
1790
|
+
reviewResult?.reviewedSteps.map((r) => [r.originalOrdinal, r])
|
|
1791
|
+
);
|
|
1792
|
+
const filteredSteps = reviewResult ? successfulSteps.filter((s) => reviewMap.get(s.ordinal)?.keep !== false) : successfulSteps;
|
|
1793
|
+
const variables = {};
|
|
1794
|
+
const usedVariableNames = /* @__PURE__ */ new Set();
|
|
1795
|
+
filteredSteps.forEach((recorded) => {
|
|
1796
|
+
const { action } = recorded;
|
|
1797
|
+
const actionType = toActionType(action.action);
|
|
1798
|
+
if (actionType === "input" && action.value !== void 0) {
|
|
1799
|
+
const varName = action.variableName ?? `input_${recorded.ordinal}`;
|
|
1800
|
+
if (!usedVariableNames.has(varName)) {
|
|
1801
|
+
usedVariableNames.add(varName);
|
|
1802
|
+
const source = categoryToSource(action.inputCategory);
|
|
1803
|
+
variables[varName] = {
|
|
1804
|
+
source,
|
|
1805
|
+
description: action.description,
|
|
1806
|
+
...source === "prompt" && {
|
|
1807
|
+
required: true,
|
|
1808
|
+
sensitive: action.inputCategory === "credential"
|
|
1809
|
+
},
|
|
1810
|
+
...source === "fixed" && { value: action.value }
|
|
1811
|
+
};
|
|
1812
|
+
}
|
|
1813
|
+
}
|
|
1814
|
+
});
|
|
1815
|
+
const steps = filteredSteps.map((recorded, index) => {
|
|
1816
|
+
const actionType = toActionType(recorded.action.action);
|
|
1817
|
+
const review = reviewMap.get(recorded.ordinal);
|
|
1818
|
+
let selector;
|
|
1819
|
+
if (recorded.action.selector) {
|
|
1820
|
+
const ref = recorded.action.selector.replace("@", "");
|
|
1821
|
+
const element = findElementInSnapshot(recorded.snapshotBefore, ref);
|
|
1822
|
+
if (element) {
|
|
1823
|
+
selector = buildSelector(element);
|
|
1824
|
+
} else if (recorded.resolvedElement) {
|
|
1825
|
+
selector = recorded.resolvedElement;
|
|
1826
|
+
} else {
|
|
1827
|
+
selector = { tagName: "unknown" };
|
|
1828
|
+
}
|
|
1829
|
+
}
|
|
1830
|
+
let value;
|
|
1831
|
+
if (actionType === "input" && recorded.action.value !== void 0) {
|
|
1832
|
+
const varName = recorded.action.variableName ?? `input_${recorded.ordinal}`;
|
|
1833
|
+
value = `{{${varName}}}`;
|
|
1834
|
+
}
|
|
1835
|
+
const step = {
|
|
1836
|
+
ordinal: index,
|
|
1837
|
+
description: recorded.action.description,
|
|
1838
|
+
action: {
|
|
1839
|
+
type: actionType,
|
|
1840
|
+
...selector && { selector },
|
|
1841
|
+
...actionType === "input" && value !== void 0 && { value },
|
|
1842
|
+
...actionType === "select" && recorded.action.value !== void 0 && {
|
|
1843
|
+
optionText: recorded.action.value
|
|
1844
|
+
},
|
|
1845
|
+
...actionType === "navigate" && recorded.action.value && {
|
|
1846
|
+
url: recorded.action.value
|
|
1847
|
+
},
|
|
1848
|
+
...actionType === "extract" && recorded.action.script && {
|
|
1849
|
+
script: recorded.action.script
|
|
1850
|
+
},
|
|
1851
|
+
...actionType === "download" && recorded.action.downloadPath && {
|
|
1852
|
+
downloadPath: recorded.action.downloadPath
|
|
1853
|
+
},
|
|
1854
|
+
...actionType === "export" && recorded.action.exportCollection && {
|
|
1855
|
+
exportCollection: recorded.action.exportCollection
|
|
1856
|
+
},
|
|
1857
|
+
...actionType === "export" && recorded.action.exportFormat && {
|
|
1858
|
+
exportFormat: recorded.action.exportFormat
|
|
1859
|
+
},
|
|
1860
|
+
...actionType === "export" && recorded.action.exportPath && {
|
|
1861
|
+
exportPath: recorded.action.exportPath
|
|
1862
|
+
},
|
|
1863
|
+
...actionType === "key" && {
|
|
1864
|
+
keys: recorded.action.keys ?? recorded.action.value
|
|
1865
|
+
}
|
|
1866
|
+
},
|
|
1867
|
+
url: recorded.url,
|
|
1868
|
+
riskLevel: review?.riskLevel ?? "low",
|
|
1869
|
+
requiresConfirmation: review?.requiresConfirmation ?? false
|
|
1870
|
+
};
|
|
1871
|
+
const captures = buildCaptures(recorded.action.suggestedCaptures);
|
|
1872
|
+
if (captures.length > 0) {
|
|
1873
|
+
step.captures = captures;
|
|
1874
|
+
}
|
|
1875
|
+
if (recorded.action.action === "memory_append" && recorded.action.memoryCollection) {
|
|
1876
|
+
step.memoryOperations = [{
|
|
1877
|
+
type: "append",
|
|
1878
|
+
collection: recorded.action.memoryCollection,
|
|
1879
|
+
source: recorded.action.variableName ?? "extractedData"
|
|
1880
|
+
}];
|
|
1881
|
+
} else if (recorded.action.action === "memory_aggregate" && recorded.action.aggregation) {
|
|
1882
|
+
const agg = recorded.action.aggregation;
|
|
1883
|
+
step.memoryOperations = [{
|
|
1884
|
+
type: "aggregate",
|
|
1885
|
+
collection: agg.collection,
|
|
1886
|
+
field: agg.field,
|
|
1887
|
+
operation: agg.operation,
|
|
1888
|
+
outputVariable: agg.outputVariable
|
|
1889
|
+
}];
|
|
1890
|
+
}
|
|
1891
|
+
return step;
|
|
1892
|
+
});
|
|
1893
|
+
const baseUrl = new URL(startUrl).origin;
|
|
1894
|
+
const runbook = {
|
|
1895
|
+
title: goal,
|
|
1896
|
+
settings: {
|
|
1897
|
+
baseUrl,
|
|
1898
|
+
defaultTimeout: 1e4,
|
|
1899
|
+
pauseBetweenSteps: stepDelay,
|
|
1900
|
+
stopOnError: true
|
|
1901
|
+
},
|
|
1902
|
+
metadata: {
|
|
1903
|
+
startUrl,
|
|
1904
|
+
goal,
|
|
1905
|
+
goalAchieved,
|
|
1906
|
+
totalSteps: filteredSteps.length,
|
|
1907
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1908
|
+
...input.skills && input.skills.length > 0 && { skills: input.skills }
|
|
1909
|
+
},
|
|
1910
|
+
context: input.contextMarkdown
|
|
1911
|
+
};
|
|
1912
|
+
if (Object.keys(variables).length > 0) {
|
|
1913
|
+
runbook.variables = variables;
|
|
1914
|
+
}
|
|
1915
|
+
runbook.steps = steps;
|
|
1916
|
+
const allNotes = [];
|
|
1917
|
+
if (humanGuidance && humanGuidance.length > 0) {
|
|
1918
|
+
allNotes.push(
|
|
1919
|
+
...humanGuidance.map((g, i) => `[\u65B9\u91DD\u4FEE\u6B63 ${i + 1}] ${g}`)
|
|
1920
|
+
);
|
|
1921
|
+
}
|
|
1922
|
+
if (interventions && interventions.length > 0) {
|
|
1923
|
+
allNotes.push(
|
|
1924
|
+
...interventions.map(
|
|
1925
|
+
(iv) => `[\u63A2\u7D22\u4E2D\u4ECB\u5165 \u30B9\u30C6\u30C3\u30D7#${iv.stepIndex}] ${iv.userInstruction}`
|
|
1926
|
+
)
|
|
1927
|
+
);
|
|
1928
|
+
}
|
|
1929
|
+
if (allNotes.length > 0) {
|
|
1930
|
+
runbook.notes = allNotes.join("\n");
|
|
1931
|
+
}
|
|
1932
|
+
ParsedRunbookSchema.parse(runbook);
|
|
1933
|
+
return stringify(runbook, { lineWidth: 120 });
|
|
1934
|
+
}
|
|
1935
|
+
function buildCaptures(suggested) {
|
|
1936
|
+
if (!suggested || suggested.length === 0) return [];
|
|
1937
|
+
return suggested.map((c) => ({
|
|
1938
|
+
name: c.name,
|
|
1939
|
+
strategy: c.strategy,
|
|
1940
|
+
required: false,
|
|
1941
|
+
...c.description && { description: c.description },
|
|
1942
|
+
...c.pattern && { pattern: c.pattern },
|
|
1943
|
+
...c.group !== void 0 && c.group !== 1 && { group: c.group },
|
|
1944
|
+
...c.prompt && { prompt: c.prompt },
|
|
1945
|
+
...c.expression && { expression: c.expression }
|
|
1946
|
+
}));
|
|
1947
|
+
}
|
|
1948
|
+
|
|
1949
|
+
// src/runbook-generator/prompts.ts
|
|
1950
|
+
import { z as z3 } from "zod";
|
|
1951
|
+
var reviewResponseSchema = z3.object({
|
|
1952
|
+
reviewedSteps: z3.array(z3.object({
|
|
1953
|
+
originalOrdinal: z3.number(),
|
|
1954
|
+
keep: z3.boolean(),
|
|
1955
|
+
removalReason: z3.string().optional(),
|
|
1956
|
+
riskLevel: z3.enum(["low", "medium", "high"]),
|
|
1957
|
+
requiresConfirmation: z3.boolean(),
|
|
1958
|
+
confirmationReason: z3.string().optional()
|
|
1959
|
+
})),
|
|
1960
|
+
summary: z3.string()
|
|
1961
|
+
});
|
|
1962
|
+
function createReviewPrompt(goal, recordedSteps, goalAchieved, interventions, locale) {
|
|
1963
|
+
const system = getReviewSystemPrompt(locale);
|
|
1964
|
+
const userPrompt = createReviewUserPrompt(goal, recordedSteps, goalAchieved, interventions, locale);
|
|
1965
|
+
return { system, userPrompt };
|
|
1966
|
+
}
|
|
1967
|
+
|
|
1968
|
+
export {
|
|
1969
|
+
explore,
|
|
1970
|
+
buildRunbookYaml,
|
|
1971
|
+
reviewResponseSchema,
|
|
1972
|
+
createReviewPrompt
|
|
1973
|
+
};
|
|
1974
|
+
//# sourceMappingURL=chunk-IGFCYKHC.js.map
|