@aiscene/shared 8.0.2 → 8.0.4
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/es/agent-tools/agent-behavior-init-args.mjs +44 -0
- package/dist/es/agent-tools/base-tools.mjs +163 -0
- package/dist/es/agent-tools/chrome-path.mjs +50 -0
- package/dist/es/agent-tools/cli-report-session.mjs +78 -0
- package/dist/es/agent-tools/error-formatter.mjs +106 -0
- package/dist/es/agent-tools/index.mjs +9 -0
- package/dist/es/agent-tools/init-arg-utils.mjs +38 -0
- package/dist/es/agent-tools/observation-artifact.mjs +5 -0
- package/dist/es/agent-tools/observation-record.mjs +248 -0
- package/dist/es/agent-tools/tool-defaults.mjs +54 -0
- package/dist/es/agent-tools/tool-generator.mjs +529 -0
- package/dist/es/agent-tools/types.mjs +3 -0
- package/dist/es/agent-tools/user-prompt.mjs +66 -0
- package/dist/es/cli/cli-args.mjs +95 -0
- package/dist/es/cli/cli-error.mjs +24 -0
- package/dist/es/cli/cli-runner.mjs +10 -40
- package/dist/es/cli/index.mjs +4 -2
- package/dist/es/cli/interrupt.mjs +116 -0
- package/dist/es/cli/record-command.mjs +130 -0
- package/dist/es/cli/screenshot-file.mjs +24 -0
- package/dist/es/cli/verbose-ai-act.mjs +230 -0
- package/dist/es/cli/verbose-screenshot.mjs +134 -0
- package/dist/es/cli/verbose.mjs +404 -0
- package/dist/es/constants/example-code.mjs +2 -2
- package/dist/es/constants/index.mjs +6 -1
- package/dist/es/env/model-config-manager.mjs +3 -0
- package/dist/es/env/parse-model-config.mjs +6 -1
- package/dist/es/env/types.mjs +4 -1
- package/dist/es/env/utils.mjs +1 -9
- package/dist/es/extractor/index.mjs +2 -2
- package/dist/es/img/index.mjs +2 -2
- package/dist/es/img/info.mjs +58 -17
- package/dist/es/img/photon-loader.mjs +4 -0
- package/dist/es/key-alias-utils.mjs +19 -0
- package/dist/es/mcp/base-server.mjs +10 -9
- package/dist/es/mcp/base-tools.mjs +72 -4
- package/dist/es/mcp/cli-report-session.mjs +78 -0
- package/dist/es/mcp/error-formatter.mjs +19 -0
- package/dist/es/mcp/index.mjs +2 -0
- package/dist/es/mcp/init-arg-utils.mjs +38 -0
- package/dist/es/mcp/inject-report-html-plugin.mjs +1 -1
- package/dist/es/mcp/tool-generator.mjs +65 -17
- package/dist/es/node/fs.mjs +1 -1
- package/dist/es/recorder.mjs +249 -0
- package/dist/lib/agent-tools/agent-behavior-init-args.js +87 -0
- package/dist/lib/agent-tools/base-tools.js +197 -0
- package/dist/lib/agent-tools/chrome-path.js +87 -0
- package/dist/lib/agent-tools/cli-report-session.js +121 -0
- package/dist/lib/agent-tools/error-formatter.js +149 -0
- package/dist/lib/agent-tools/index.js +114 -0
- package/dist/lib/agent-tools/init-arg-utils.js +78 -0
- package/dist/lib/agent-tools/observation-artifact.js +42 -0
- package/dist/lib/agent-tools/observation-record.js +297 -0
- package/dist/lib/agent-tools/tool-defaults.js +97 -0
- package/dist/lib/agent-tools/tool-generator.js +569 -0
- package/dist/lib/agent-tools/types.js +40 -0
- package/dist/lib/agent-tools/user-prompt.js +103 -0
- package/dist/lib/cli/cli-args.js +138 -0
- package/dist/lib/cli/cli-error.js +61 -0
- package/dist/lib/cli/cli-runner.js +19 -46
- package/dist/lib/cli/index.js +8 -3
- package/dist/lib/cli/interrupt.js +156 -0
- package/dist/lib/cli/record-command.js +164 -0
- package/dist/lib/cli/screenshot-file.js +58 -0
- package/dist/lib/cli/verbose-ai-act.js +273 -0
- package/dist/lib/cli/verbose-screenshot.js +177 -0
- package/dist/lib/cli/verbose.js +465 -0
- package/dist/lib/constants/example-code.js +2 -2
- package/dist/lib/constants/index.js +23 -3
- package/dist/lib/env/model-config-manager.js +3 -0
- package/dist/lib/env/parse-model-config.js +6 -1
- package/dist/lib/env/types.js +9 -0
- package/dist/lib/env/utils.js +0 -11
- package/dist/lib/extractor/index.js +3 -0
- package/dist/lib/img/index.js +3 -0
- package/dist/lib/img/info.js +65 -18
- package/dist/lib/img/photon-loader.js +38 -0
- package/dist/lib/key-alias-utils.js +62 -0
- package/dist/lib/mcp/base-server.js +11 -10
- package/dist/lib/mcp/base-tools.js +74 -6
- package/dist/lib/mcp/cli-report-session.js +121 -0
- package/dist/lib/mcp/error-formatter.js +53 -0
- package/dist/lib/mcp/index.js +24 -10
- package/dist/lib/mcp/init-arg-utils.js +78 -0
- package/dist/lib/mcp/inject-report-html-plugin.js +1 -1
- package/dist/lib/mcp/tool-generator.js +74 -26
- package/dist/lib/node/fs.js +1 -1
- package/dist/lib/recorder.js +307 -0
- package/dist/types/agent-tools/agent-behavior-init-args.d.ts +17 -0
- package/dist/types/agent-tools/base-tools.d.ts +158 -0
- package/dist/types/agent-tools/chrome-path.d.ts +2 -0
- package/dist/types/agent-tools/cli-report-session.d.ts +12 -0
- package/dist/types/agent-tools/error-formatter.d.ts +30 -0
- package/dist/types/agent-tools/index.d.ts +9 -0
- package/dist/types/agent-tools/init-arg-utils.d.ts +13 -0
- package/dist/types/agent-tools/observation-artifact.d.ts +10 -0
- package/dist/types/agent-tools/observation-record.d.ts +38 -0
- package/dist/types/agent-tools/tool-defaults.d.ts +63 -0
- package/dist/types/agent-tools/tool-generator.d.ts +13 -0
- package/dist/types/agent-tools/types.d.ts +213 -0
- package/dist/types/agent-tools/user-prompt.d.ts +13 -0
- package/dist/types/cli/cli-args.d.ts +8 -0
- package/dist/types/cli/cli-error.d.ts +5 -0
- package/dist/types/cli/cli-runner.d.ts +4 -7
- package/dist/types/cli/index.d.ts +3 -1
- package/dist/types/cli/interrupt.d.ts +49 -0
- package/dist/types/cli/record-command.d.ts +3 -0
- package/dist/types/cli/screenshot-file.d.ts +10 -0
- package/dist/types/cli/verbose-ai-act.d.ts +44 -0
- package/dist/types/cli/verbose-screenshot.d.ts +10 -0
- package/dist/types/cli/verbose.d.ts +40 -0
- package/dist/types/constants/example-code.d.ts +1 -1
- package/dist/types/constants/index.d.ts +5 -0
- package/dist/types/env/types.d.ts +14 -5
- package/dist/types/env/utils.d.ts +0 -31
- package/dist/types/extractor/index.d.ts +1 -1
- package/dist/types/img/index.d.ts +1 -1
- package/dist/types/img/info.d.ts +7 -0
- package/dist/types/img/photon-loader.d.ts +2 -0
- package/dist/types/key-alias-utils.d.ts +9 -0
- package/dist/types/mcp/base-tools.d.ts +74 -5
- package/dist/types/mcp/cli-report-session.d.ts +12 -0
- package/dist/types/mcp/error-formatter.d.ts +12 -0
- package/dist/types/mcp/index.d.ts +2 -0
- package/dist/types/mcp/init-arg-utils.d.ts +13 -0
- package/dist/types/mcp/inject-report-html-plugin.d.ts +2 -2
- package/dist/types/mcp/launcher-helper.d.ts +3 -3
- package/dist/types/mcp/tool-generator.d.ts +3 -3
- package/dist/types/mcp/types.d.ts +10 -0
- package/dist/types/recorder.d.ts +113 -0
- package/package.json +1 -1
- package/src/agent-tools/agent-behavior-init-args.ts +109 -0
- package/src/agent-tools/base-tools.ts +399 -0
- package/src/agent-tools/chrome-path.ts +74 -0
- package/src/agent-tools/cli-report-session.ts +130 -0
- package/src/agent-tools/error-formatter.ts +177 -0
- package/src/agent-tools/index.ts +9 -0
- package/src/agent-tools/init-arg-utils.ts +105 -0
- package/src/agent-tools/observation-artifact.ts +29 -0
- package/src/agent-tools/observation-record.ts +331 -0
- package/src/agent-tools/tool-defaults.ts +119 -0
- package/src/agent-tools/tool-generator.ts +866 -0
- package/src/agent-tools/types.ts +250 -0
- package/src/agent-tools/user-prompt.ts +102 -0
- package/src/cli/cli-args.ts +173 -0
- package/src/cli/cli-error.ts +24 -0
- package/src/cli/cli-runner.ts +37 -56
- package/src/cli/index.ts +3 -7
- package/src/cli/interrupt.ts +207 -0
- package/src/cli/record-command.ts +177 -0
- package/src/cli/screenshot-file.ts +61 -0
- package/src/cli/verbose-ai-act.ts +387 -0
- package/src/cli/verbose-screenshot.ts +269 -0
- package/src/cli/verbose.ts +753 -0
- package/src/constants/example-code.ts +2 -2
- package/src/constants/index.ts +10 -0
- package/src/env/model-config-manager.ts +3 -0
- package/src/env/parse-model-config.ts +19 -1
- package/src/env/types.ts +13 -2
- package/src/env/utils.ts +0 -50
- package/src/extractor/index.ts +1 -1
- package/src/img/index.ts +14 -0
- package/src/img/info.ts +97 -45
- package/src/img/photon-loader.ts +5 -0
- package/src/img/transform.ts +261 -2
- package/src/key-alias-utils.ts +23 -0
- package/src/mcp/base-server.ts +10 -12
- package/src/mcp/base-tools.ts +218 -11
- package/src/mcp/cli-report-session.ts +130 -0
- package/src/mcp/error-formatter.ts +52 -0
- package/src/mcp/index.ts +2 -0
- package/src/mcp/init-arg-utils.ts +105 -0
- package/src/mcp/inject-report-html-plugin.ts +3 -3
- package/src/mcp/launcher-helper.ts +3 -3
- package/src/mcp/tool-generator.ts +98 -20
- package/src/mcp/types.ts +18 -1
- package/src/recorder.ts +625 -0
|
@@ -0,0 +1,387 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type CliVerboseScreenshotCollectOptions,
|
|
3
|
+
collectScreenshotRefs,
|
|
4
|
+
pathForReportScreenshot,
|
|
5
|
+
} from './verbose-screenshot';
|
|
6
|
+
|
|
7
|
+
export interface CliVerboseLine {
|
|
8
|
+
key: string;
|
|
9
|
+
text: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/** Generic progress-bus envelope as seen by the CLI (all fields untrusted). */
|
|
13
|
+
export interface CliAgentProgressEvent {
|
|
14
|
+
scope?: unknown;
|
|
15
|
+
phase?: unknown;
|
|
16
|
+
sequence?: unknown;
|
|
17
|
+
data?: unknown;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** Flattened, render-ready aiAct payload derived from the envelope. */
|
|
21
|
+
export interface CliAiActProgressPayload {
|
|
22
|
+
phase?: unknown;
|
|
23
|
+
sequence?: unknown;
|
|
24
|
+
prompt?: unknown;
|
|
25
|
+
planIndex?: unknown;
|
|
26
|
+
planLimit?: unknown;
|
|
27
|
+
action?: unknown;
|
|
28
|
+
thought?: unknown;
|
|
29
|
+
log?: unknown;
|
|
30
|
+
output?: unknown;
|
|
31
|
+
durationMs?: unknown;
|
|
32
|
+
error?: unknown;
|
|
33
|
+
screenshots?: Array<Record<string, unknown>>;
|
|
34
|
+
screenshotPath?: string;
|
|
35
|
+
// Lets the payload satisfy the generic `Record<string, unknown>` dispatch
|
|
36
|
+
// boundary while keeping the named fields documented.
|
|
37
|
+
[key: string]: unknown;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** aiAct scope tag on the progress bus (mirrors core's `aiActProgressScope`). */
|
|
41
|
+
export const cliAiActProgressScope = 'aiAct';
|
|
42
|
+
|
|
43
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
44
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function compactText(value: unknown): string {
|
|
48
|
+
if (value === undefined || value === null || value === '') {
|
|
49
|
+
return '';
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (typeof value === 'string') {
|
|
53
|
+
return value.length > 180 ? `${value.slice(0, 177)}...` : value;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (typeof value === 'number' || typeof value === 'boolean') {
|
|
57
|
+
return String(value);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
try {
|
|
61
|
+
const json = JSON.stringify(value);
|
|
62
|
+
return json.length > 180 ? `${json.slice(0, 177)}...` : json;
|
|
63
|
+
} catch {
|
|
64
|
+
return String(value);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function numericValue(value: unknown): number | undefined {
|
|
69
|
+
return typeof value === 'number' && Number.isFinite(value)
|
|
70
|
+
? value
|
|
71
|
+
: undefined;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function firstText(values: unknown[]): string {
|
|
75
|
+
for (const value of values) {
|
|
76
|
+
const text = compactText(value);
|
|
77
|
+
if (text) {
|
|
78
|
+
return text;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return '';
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function planPrefix(event: CliAiActProgressPayload): string {
|
|
85
|
+
const planIndex = numericValue(event.planIndex);
|
|
86
|
+
if (!planIndex) {
|
|
87
|
+
return '[Midscene][aiAct]';
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const planLimit = numericValue(event.planLimit);
|
|
91
|
+
return `[Midscene][aiAct][Plan ${Math.round(planIndex)}${
|
|
92
|
+
planLimit ? `/${Math.round(planLimit)}` : ''
|
|
93
|
+
}]`;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function eventKey(event: CliAiActProgressPayload, suffix: string): string {
|
|
97
|
+
const sequence = numericValue(event.sequence);
|
|
98
|
+
if (sequence) {
|
|
99
|
+
return `aiAct:${sequence}:${suffix}`;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return [
|
|
103
|
+
'aiAct',
|
|
104
|
+
compactText(event.phase),
|
|
105
|
+
compactText(event.planIndex),
|
|
106
|
+
suffix,
|
|
107
|
+
]
|
|
108
|
+
.filter(Boolean)
|
|
109
|
+
.join(':');
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// --- Structured action -> human-readable text (presentation lives here) ---
|
|
113
|
+
|
|
114
|
+
interface ParsedAiActAction {
|
|
115
|
+
name: string;
|
|
116
|
+
target?: string;
|
|
117
|
+
point?: [number, number];
|
|
118
|
+
bbox?: [number, number, number, number];
|
|
119
|
+
param?: unknown;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function integerText(value: number): string {
|
|
123
|
+
return String(Math.round(value));
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function numberTuple2(value: unknown): [number, number] | undefined {
|
|
127
|
+
if (
|
|
128
|
+
Array.isArray(value) &&
|
|
129
|
+
value.length >= 2 &&
|
|
130
|
+
typeof value[0] === 'number' &&
|
|
131
|
+
typeof value[1] === 'number'
|
|
132
|
+
) {
|
|
133
|
+
return [value[0], value[1]];
|
|
134
|
+
}
|
|
135
|
+
return undefined;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function numberTuple4(
|
|
139
|
+
value: unknown,
|
|
140
|
+
): [number, number, number, number] | undefined {
|
|
141
|
+
if (
|
|
142
|
+
Array.isArray(value) &&
|
|
143
|
+
value.length >= 4 &&
|
|
144
|
+
value.slice(0, 4).every((item) => typeof item === 'number')
|
|
145
|
+
) {
|
|
146
|
+
return [value[0], value[1], value[2], value[3]];
|
|
147
|
+
}
|
|
148
|
+
return undefined;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function parseAction(value: unknown): ParsedAiActAction | undefined {
|
|
152
|
+
if (!isRecord(value) || typeof value.name !== 'string') {
|
|
153
|
+
return undefined;
|
|
154
|
+
}
|
|
155
|
+
return {
|
|
156
|
+
name: value.name,
|
|
157
|
+
target: typeof value.target === 'string' ? value.target : undefined,
|
|
158
|
+
point: numberTuple2(value.point),
|
|
159
|
+
bbox: numberTuple4(value.bbox),
|
|
160
|
+
param: value.param,
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function formatPoint(point: [number, number]): string {
|
|
165
|
+
return `(${integerText(point[0])}, ${integerText(point[1])})`;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function formatBbox(bbox: [number, number, number, number]): string {
|
|
169
|
+
return `(${bbox.map(integerText).join(',')})`;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function sleepActionText(action: ParsedAiActAction): string | undefined {
|
|
173
|
+
if (action.name !== 'Sleep' || !isRecord(action.param)) {
|
|
174
|
+
return undefined;
|
|
175
|
+
}
|
|
176
|
+
const timeMs =
|
|
177
|
+
numericValue(action.param.timeMs) ??
|
|
178
|
+
numericValue(action.param.duration) ??
|
|
179
|
+
numericValue(action.param.timeoutMs);
|
|
180
|
+
return timeMs !== undefined ? `Sleep ${integerText(timeMs)}ms` : undefined;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function plannedActionText(action: ParsedAiActAction): string {
|
|
184
|
+
if (action.point) {
|
|
185
|
+
const targetSegment = action.target ? ` "${action.target}"` : '';
|
|
186
|
+
const bboxSegment = action.bbox ? `, bbox=${formatBbox(action.bbox)}` : '';
|
|
187
|
+
return `${action.name}${targetSegment} at ${formatPoint(action.point)}${bboxSegment}`;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const sleep = sleepActionText(action);
|
|
191
|
+
if (sleep) {
|
|
192
|
+
return sleep;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const paramText = compactText(action.param);
|
|
196
|
+
return paramText ? `${action.name}: ${paramText}` : action.name;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function runningActionText(action: ParsedAiActAction): string {
|
|
200
|
+
if (action.point) {
|
|
201
|
+
return `${action.name} at ${formatPoint(action.point)}`;
|
|
202
|
+
}
|
|
203
|
+
return plannedActionText(action);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
export function normalizeAiActProgressEventForCli(
|
|
207
|
+
event: unknown,
|
|
208
|
+
screenshotOptions: CliVerboseScreenshotCollectOptions = {},
|
|
209
|
+
): CliAiActProgressPayload | undefined {
|
|
210
|
+
if (!isRecord(event) || event.scope !== cliAiActProgressScope) {
|
|
211
|
+
return undefined;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
const data = isRecord(event.data) ? event.data : {};
|
|
215
|
+
|
|
216
|
+
const screenshots = collectScreenshotRefs(data.screenshot, screenshotOptions);
|
|
217
|
+
const latestPath = screenshots
|
|
218
|
+
.slice()
|
|
219
|
+
.reverse()
|
|
220
|
+
.find(
|
|
221
|
+
(item) => typeof item.path === 'string' && item.path.length > 0,
|
|
222
|
+
)?.path;
|
|
223
|
+
const screenshotPath =
|
|
224
|
+
typeof latestPath === 'string'
|
|
225
|
+
? pathForReportScreenshot(latestPath, screenshotOptions.reportFile)
|
|
226
|
+
: undefined;
|
|
227
|
+
|
|
228
|
+
const {
|
|
229
|
+
prompt,
|
|
230
|
+
planIndex,
|
|
231
|
+
planLimit,
|
|
232
|
+
action,
|
|
233
|
+
thought,
|
|
234
|
+
log,
|
|
235
|
+
output,
|
|
236
|
+
durationMs,
|
|
237
|
+
error,
|
|
238
|
+
} = data;
|
|
239
|
+
|
|
240
|
+
return {
|
|
241
|
+
phase: event.phase,
|
|
242
|
+
sequence: event.sequence,
|
|
243
|
+
prompt,
|
|
244
|
+
planIndex,
|
|
245
|
+
planLimit,
|
|
246
|
+
action,
|
|
247
|
+
thought,
|
|
248
|
+
log,
|
|
249
|
+
output,
|
|
250
|
+
durationMs,
|
|
251
|
+
error,
|
|
252
|
+
...(screenshots.length > 0 ? { screenshots } : {}),
|
|
253
|
+
...(screenshotPath ? { screenshotPath } : {}),
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
export function buildAiActProgressEventLines(
|
|
258
|
+
event: CliAiActProgressPayload,
|
|
259
|
+
): CliVerboseLine[] {
|
|
260
|
+
const phase = typeof event.phase === 'string' ? event.phase : '';
|
|
261
|
+
const prefix = planPrefix(event);
|
|
262
|
+
const error = compactText(event.error);
|
|
263
|
+
const duration =
|
|
264
|
+
typeof event.durationMs === 'number'
|
|
265
|
+
? ` cost=${Math.round(event.durationMs)}ms`
|
|
266
|
+
: '';
|
|
267
|
+
const action = parseAction(event.action);
|
|
268
|
+
|
|
269
|
+
switch (phase) {
|
|
270
|
+
case 'start': {
|
|
271
|
+
const prompt = compactText(event.prompt);
|
|
272
|
+
return [
|
|
273
|
+
{
|
|
274
|
+
key: `aiAct:start:${prompt}`,
|
|
275
|
+
text: prompt
|
|
276
|
+
? `[Midscene][aiAct] Start: ${prompt}`
|
|
277
|
+
: '[Midscene][aiAct] Start',
|
|
278
|
+
},
|
|
279
|
+
];
|
|
280
|
+
}
|
|
281
|
+
case 'plan_thinking':
|
|
282
|
+
return event.screenshotPath
|
|
283
|
+
? [
|
|
284
|
+
{
|
|
285
|
+
key: eventKey(event, 'thinking'),
|
|
286
|
+
text: `${prefix} Thinking with the latest screenshot: ${event.screenshotPath}`,
|
|
287
|
+
},
|
|
288
|
+
]
|
|
289
|
+
: [
|
|
290
|
+
{
|
|
291
|
+
key: eventKey(event, 'thinking'),
|
|
292
|
+
text: `${prefix} Thinking with the latest screenshot`,
|
|
293
|
+
},
|
|
294
|
+
];
|
|
295
|
+
case 'plan_planned': {
|
|
296
|
+
const text = firstText([event.log, event.thought, event.output]);
|
|
297
|
+
return text
|
|
298
|
+
? [
|
|
299
|
+
{
|
|
300
|
+
key: eventKey(event, 'planned'),
|
|
301
|
+
text: `${prefix} Planned: ${text}`,
|
|
302
|
+
},
|
|
303
|
+
]
|
|
304
|
+
: [];
|
|
305
|
+
}
|
|
306
|
+
case 'plan_action':
|
|
307
|
+
return action
|
|
308
|
+
? [
|
|
309
|
+
{
|
|
310
|
+
key: eventKey(event, 'action'),
|
|
311
|
+
text: `${prefix} Action: ${plannedActionText(action)}`,
|
|
312
|
+
},
|
|
313
|
+
]
|
|
314
|
+
: [];
|
|
315
|
+
case 'plan_failed':
|
|
316
|
+
return [
|
|
317
|
+
{
|
|
318
|
+
key: eventKey(event, 'plan_failed'),
|
|
319
|
+
text: `${prefix} Failed${error ? `: ${error}` : ''}`,
|
|
320
|
+
},
|
|
321
|
+
];
|
|
322
|
+
case 'action_running':
|
|
323
|
+
return action
|
|
324
|
+
? [
|
|
325
|
+
{
|
|
326
|
+
key: eventKey(event, 'action_running'),
|
|
327
|
+
text: `[Midscene][aiAct][Action] Running: ${runningActionText(action)}`,
|
|
328
|
+
},
|
|
329
|
+
]
|
|
330
|
+
: [];
|
|
331
|
+
case 'action_done':
|
|
332
|
+
return action
|
|
333
|
+
? [
|
|
334
|
+
{
|
|
335
|
+
key: eventKey(event, 'action_done'),
|
|
336
|
+
text: `[Midscene][aiAct][Action] Done: ${action.name}${duration}`,
|
|
337
|
+
},
|
|
338
|
+
]
|
|
339
|
+
: [];
|
|
340
|
+
case 'action_failed':
|
|
341
|
+
return [
|
|
342
|
+
{
|
|
343
|
+
key: eventKey(event, 'action_failed'),
|
|
344
|
+
text: `[Midscene][aiAct][Action] Failed${
|
|
345
|
+
action ? `: ${action.name}` : ''
|
|
346
|
+
}${duration}${error ? ` error=${error}` : ''}`,
|
|
347
|
+
},
|
|
348
|
+
];
|
|
349
|
+
case 'complete': {
|
|
350
|
+
const text = firstText([event.output, event.log, event.thought]);
|
|
351
|
+
return [
|
|
352
|
+
{
|
|
353
|
+
key: eventKey(event, 'complete'),
|
|
354
|
+
text: `[Midscene][aiAct] Complete${text ? `: ${text}` : ''}`,
|
|
355
|
+
},
|
|
356
|
+
];
|
|
357
|
+
}
|
|
358
|
+
case 'failed':
|
|
359
|
+
return [
|
|
360
|
+
{
|
|
361
|
+
key: eventKey(event, 'failed'),
|
|
362
|
+
text: `[Midscene][aiAct] Failed${error ? `: ${error}` : ''}`,
|
|
363
|
+
},
|
|
364
|
+
];
|
|
365
|
+
default:
|
|
366
|
+
return [];
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
/**
|
|
371
|
+
* A producer-specific renderer for the generic CLI progress dispatcher: turn a
|
|
372
|
+
* raw progress envelope into a normalized payload, then that payload into
|
|
373
|
+
* verbose lines. New producers register their own renderer; the dispatcher core
|
|
374
|
+
* stays untouched.
|
|
375
|
+
*/
|
|
376
|
+
export interface CliProgressRenderer {
|
|
377
|
+
normalize(
|
|
378
|
+
event: unknown,
|
|
379
|
+
screenshotOptions: CliVerboseScreenshotCollectOptions,
|
|
380
|
+
): Record<string, unknown> | undefined;
|
|
381
|
+
buildLines(payload: Record<string, unknown>): CliVerboseLine[];
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
export const aiActCliProgressRenderer: CliProgressRenderer = {
|
|
385
|
+
normalize: normalizeAiActProgressEventForCli,
|
|
386
|
+
buildLines: buildAiActProgressEventLines,
|
|
387
|
+
};
|
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
import { basename, dirname, isAbsolute, join, relative } from 'node:path';
|
|
2
|
+
import { writeCliScreenshotFile } from './screenshot-file';
|
|
3
|
+
|
|
4
|
+
export type CliVerboseScreenshotExportMode = 'none' | 'tmp' | 'report';
|
|
5
|
+
|
|
6
|
+
export interface CliVerboseScreenshotCollectOptions {
|
|
7
|
+
reportFile?: unknown;
|
|
8
|
+
exportMode?: CliVerboseScreenshotExportMode;
|
|
9
|
+
cache?: Map<string, string>;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
interface CliVerboseScreenshotRefLike {
|
|
13
|
+
type: 'midscene_screenshot_ref';
|
|
14
|
+
id?: unknown;
|
|
15
|
+
mimeType?: unknown;
|
|
16
|
+
storage?: unknown;
|
|
17
|
+
path?: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
21
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function isCliVerboseScreenshotRefLike(
|
|
25
|
+
value: unknown,
|
|
26
|
+
): value is CliVerboseScreenshotRefLike {
|
|
27
|
+
return isRecord(value) && value.type === 'midscene_screenshot_ref';
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function toSerializableScreenshot(
|
|
31
|
+
value: unknown,
|
|
32
|
+
): CliVerboseScreenshotRefLike | null {
|
|
33
|
+
if (!value || typeof value !== 'object') {
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const maybeSerializable = value as {
|
|
38
|
+
toSerializable?: () => unknown;
|
|
39
|
+
};
|
|
40
|
+
if (typeof maybeSerializable.toSerializable === 'function') {
|
|
41
|
+
try {
|
|
42
|
+
const serialized = maybeSerializable.toSerializable();
|
|
43
|
+
return isCliVerboseScreenshotRefLike(serialized) ? serialized : null;
|
|
44
|
+
} catch {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return isCliVerboseScreenshotRefLike(value) ? value : null;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function getStringProperty(value: unknown, key: string): string | undefined {
|
|
53
|
+
if (!isRecord(value)) {
|
|
54
|
+
return undefined;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
try {
|
|
58
|
+
const property = value[key];
|
|
59
|
+
return typeof property === 'string' && property.length > 0
|
|
60
|
+
? property
|
|
61
|
+
: undefined;
|
|
62
|
+
} catch {
|
|
63
|
+
return undefined;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function screenshotRawBase64(value: unknown): string | undefined {
|
|
68
|
+
const rawBase64 = getStringProperty(value, 'rawBase64');
|
|
69
|
+
if (rawBase64) {
|
|
70
|
+
return rawBase64;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const base64 = getStringProperty(value, 'base64');
|
|
74
|
+
const match = base64?.match(/^data:image\/(?:png|jpeg|jpg);base64,(.+)$/);
|
|
75
|
+
return match?.[1];
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function inlineScreenshotCacheKey(
|
|
79
|
+
rawBase64: string,
|
|
80
|
+
serialized: CliVerboseScreenshotRefLike,
|
|
81
|
+
directoryPath: string | undefined,
|
|
82
|
+
directoryName: string | undefined,
|
|
83
|
+
extension: string | undefined,
|
|
84
|
+
): string {
|
|
85
|
+
return JSON.stringify([
|
|
86
|
+
serialized.id,
|
|
87
|
+
serialized.mimeType,
|
|
88
|
+
directoryPath,
|
|
89
|
+
directoryName,
|
|
90
|
+
extension,
|
|
91
|
+
rawBase64,
|
|
92
|
+
]);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function exportInlineScreenshotForVerbose(
|
|
96
|
+
value: unknown,
|
|
97
|
+
serialized: CliVerboseScreenshotRefLike,
|
|
98
|
+
options: CliVerboseScreenshotCollectOptions,
|
|
99
|
+
): string | undefined {
|
|
100
|
+
if (typeof serialized.path === 'string') {
|
|
101
|
+
return serialized.path;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const exportMode = options.exportMode ?? 'tmp';
|
|
105
|
+
if (exportMode === 'none') {
|
|
106
|
+
return undefined;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const rawBase64 = screenshotRawBase64(value);
|
|
110
|
+
if (!rawBase64) {
|
|
111
|
+
return undefined;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const directoryPath =
|
|
115
|
+
exportMode === 'report' &&
|
|
116
|
+
typeof options.reportFile === 'string' &&
|
|
117
|
+
options.reportFile.length > 0
|
|
118
|
+
? join(dirname(options.reportFile), 'screenshots')
|
|
119
|
+
: undefined;
|
|
120
|
+
const directoryName = directoryPath ? undefined : 'midscene-cli-screenshots';
|
|
121
|
+
const extension = getStringProperty(value, 'extension');
|
|
122
|
+
const cacheKey = inlineScreenshotCacheKey(
|
|
123
|
+
rawBase64,
|
|
124
|
+
serialized,
|
|
125
|
+
directoryPath,
|
|
126
|
+
directoryName,
|
|
127
|
+
extension,
|
|
128
|
+
);
|
|
129
|
+
const cachedPath = options.cache?.get(cacheKey);
|
|
130
|
+
if (cachedPath) {
|
|
131
|
+
return cachedPath;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
try {
|
|
135
|
+
const path = writeCliScreenshotFile(rawBase64, {
|
|
136
|
+
id: serialized.id,
|
|
137
|
+
mimeType: serialized.mimeType,
|
|
138
|
+
extension,
|
|
139
|
+
...(directoryPath ? { directoryPath } : { directoryName }),
|
|
140
|
+
overwrite: false,
|
|
141
|
+
});
|
|
142
|
+
options.cache?.set(cacheKey, path);
|
|
143
|
+
return path;
|
|
144
|
+
} catch {
|
|
145
|
+
return undefined;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export function collectScreenshotRefs(
|
|
150
|
+
value: unknown,
|
|
151
|
+
options: CliVerboseScreenshotCollectOptions = {},
|
|
152
|
+
): Array<Record<string, unknown>> {
|
|
153
|
+
const screenshots: Array<Record<string, unknown>> = [];
|
|
154
|
+
const visit = (candidate: unknown, timing?: unknown): void => {
|
|
155
|
+
if (Array.isArray(candidate)) {
|
|
156
|
+
for (const item of candidate) {
|
|
157
|
+
visit(item, timing);
|
|
158
|
+
}
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const serialized = toSerializableScreenshot(candidate);
|
|
163
|
+
if (serialized?.type === 'midscene_screenshot_ref') {
|
|
164
|
+
const screenshot: Record<string, unknown> = {
|
|
165
|
+
id: serialized.id,
|
|
166
|
+
storage: serialized.storage,
|
|
167
|
+
};
|
|
168
|
+
const exportedPath = exportInlineScreenshotForVerbose(
|
|
169
|
+
candidate,
|
|
170
|
+
serialized,
|
|
171
|
+
options,
|
|
172
|
+
);
|
|
173
|
+
if (exportedPath) {
|
|
174
|
+
screenshot.path = exportedPath;
|
|
175
|
+
screenshot.file = basename(exportedPath);
|
|
176
|
+
}
|
|
177
|
+
if (typeof timing === 'string') {
|
|
178
|
+
screenshot.timing = timing;
|
|
179
|
+
}
|
|
180
|
+
screenshots.push(screenshot);
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
if (!isRecord(candidate)) {
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const screenshotRecord = candidate.screenshot;
|
|
189
|
+
if (screenshotRecord) {
|
|
190
|
+
visit(screenshotRecord, candidate.timing);
|
|
191
|
+
}
|
|
192
|
+
if (candidate.recorder) {
|
|
193
|
+
visit(candidate.recorder);
|
|
194
|
+
}
|
|
195
|
+
if (isRecord(candidate.uiContext)) {
|
|
196
|
+
visit(candidate.uiContext.screenshot);
|
|
197
|
+
}
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
visit(value);
|
|
201
|
+
return screenshots;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
export function pathForReportScreenshot(
|
|
205
|
+
path: string,
|
|
206
|
+
reportFile?: unknown,
|
|
207
|
+
): string {
|
|
208
|
+
const resolvedPath =
|
|
209
|
+
typeof reportFile === 'string' &&
|
|
210
|
+
reportFile.length > 0 &&
|
|
211
|
+
(path.startsWith('./') || path.startsWith('../'))
|
|
212
|
+
? join(dirname(reportFile), path)
|
|
213
|
+
: path;
|
|
214
|
+
|
|
215
|
+
if (!isAbsolute(resolvedPath)) {
|
|
216
|
+
return resolvedPath;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const relativePath = relative(process.cwd(), resolvedPath);
|
|
220
|
+
if (
|
|
221
|
+
relativePath &&
|
|
222
|
+
!relativePath.startsWith('..') &&
|
|
223
|
+
!isAbsolute(relativePath)
|
|
224
|
+
) {
|
|
225
|
+
return relativePath;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
return resolvedPath;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
export function latestScreenshotPathForAiAct(
|
|
232
|
+
value: unknown,
|
|
233
|
+
options: CliVerboseScreenshotCollectOptions = {},
|
|
234
|
+
): string {
|
|
235
|
+
const screenshot = collectScreenshotRefs(value, {
|
|
236
|
+
...options,
|
|
237
|
+
exportMode: options.exportMode ?? 'report',
|
|
238
|
+
})
|
|
239
|
+
.slice()
|
|
240
|
+
.reverse()
|
|
241
|
+
.find((item) => typeof item.path === 'string' && item.path.length > 0);
|
|
242
|
+
const path = typeof screenshot?.path === 'string' ? screenshot.path : '';
|
|
243
|
+
return path ? pathForReportScreenshot(path, options.reportFile) : '';
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
export function renderScreenshotList(screenshots: unknown): string {
|
|
247
|
+
if (!Array.isArray(screenshots) || screenshots.length === 0) {
|
|
248
|
+
return '';
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
return screenshots
|
|
252
|
+
.map((item) => {
|
|
253
|
+
if (!isRecord(item)) {
|
|
254
|
+
return '';
|
|
255
|
+
}
|
|
256
|
+
const path =
|
|
257
|
+
typeof item.path === 'string'
|
|
258
|
+
? item.path
|
|
259
|
+
: typeof item.file === 'string'
|
|
260
|
+
? item.file
|
|
261
|
+
: typeof item.id === 'string'
|
|
262
|
+
? item.id
|
|
263
|
+
: '';
|
|
264
|
+
const timing = typeof item.timing === 'string' ? item.timing : '';
|
|
265
|
+
return [timing, path].filter(Boolean).join(' ');
|
|
266
|
+
})
|
|
267
|
+
.filter(Boolean)
|
|
268
|
+
.join(', ');
|
|
269
|
+
}
|