autokap 2.0.0 → 2.0.2
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/cli-runner.js +13 -2
- package/dist/cli.d.ts +21 -0
- package/dist/cli.js +48 -1
- package/dist/opcode-runner.d.ts +10 -1
- package/dist/opcode-runner.js +1 -0
- package/dist/semantic-resolver.d.ts +10 -0
- package/dist/semantic-resolver.js +0 -0
- package/dist/web-playwright-local.d.ts +1 -0
- package/dist/web-playwright-local.js +36 -10
- package/package.json +1 -1
package/dist/cli-runner.js
CHANGED
|
@@ -174,7 +174,7 @@ export async function runCapture(options) {
|
|
|
174
174
|
// durations + audio assets are only consumed on the upload path (signalVideoComplete), which a dry
|
|
175
175
|
// run never reaches, so skipping prep here is side-effect-free for dry.
|
|
176
176
|
if (!options.dryRun && !options.program && program.mediaMode === 'video') {
|
|
177
|
-
const prepareResult = await prepareVideoSpeechForRun(config, options.presetId, runId, options.regenerateTts ?? false, sessionId);
|
|
177
|
+
const prepareResult = await prepareVideoSpeechForRun(config, options.presetId, runId, options.regenerateTts ?? false, sessionId, options.onProgress);
|
|
178
178
|
if (!prepareResult.success) {
|
|
179
179
|
return { success: false, runId, error: prepareResult.error };
|
|
180
180
|
}
|
|
@@ -524,7 +524,7 @@ async function fetchProgram(config, presetId, environmentName) {
|
|
|
524
524
|
}
|
|
525
525
|
return { success: false, error: 'failed to fetch program: retry attempts exhausted' };
|
|
526
526
|
}
|
|
527
|
-
async function prepareVideoSpeechForRun(config, videoId, runId, regenerateTts, sessionId) {
|
|
527
|
+
async function prepareVideoSpeechForRun(config, videoId, runId, regenerateTts, sessionId, onProgress) {
|
|
528
528
|
if (regenerateTts) {
|
|
529
529
|
logger.info('[capture] Forcing TTS regeneration — all cached segments will be re-synthesized and billed.');
|
|
530
530
|
}
|
|
@@ -578,6 +578,17 @@ async function prepareVideoSpeechForRun(config, videoId, runId, regenerateTts, s
|
|
|
578
578
|
}
|
|
579
579
|
if (event.type === 'tts_progress') {
|
|
580
580
|
logTtsProgress(event);
|
|
581
|
+
// Forward to the caller so cloud runs can surface the narration phase as
|
|
582
|
+
// checkpoints (local runs only ever logged it — the original blind spot).
|
|
583
|
+
onProgress?.({
|
|
584
|
+
type: 'tts_progress',
|
|
585
|
+
variantId: 'tts',
|
|
586
|
+
message: '',
|
|
587
|
+
stage: event.stage,
|
|
588
|
+
index: event.index,
|
|
589
|
+
total: event.total,
|
|
590
|
+
stepId: event.stepId,
|
|
591
|
+
});
|
|
581
592
|
return;
|
|
582
593
|
}
|
|
583
594
|
if (event.type === 'error') {
|
package/dist/cli.d.ts
CHANGED
|
@@ -1,3 +1,24 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { Command } from 'commander';
|
|
3
|
+
import type { ProgressEvent } from './opcode-runner.js';
|
|
3
4
|
export declare const program: Command;
|
|
5
|
+
type CloudCheckpointType = 'preset_progress' | 'upload_start' | 'upload_end';
|
|
6
|
+
/** Interval below which consecutive `granular` checkpoints are coalesced. */
|
|
7
|
+
export declare const GRANULAR_CHECKPOINT_THROTTLE_MS = 1200;
|
|
8
|
+
export declare function cloudCaptureProgressCheckpoint(event: ProgressEvent): {
|
|
9
|
+
type: CloudCheckpointType;
|
|
10
|
+
message: string;
|
|
11
|
+
status?: 'running';
|
|
12
|
+
/** Fine-grained intra-preset event (per-opcode / per-narration) — throttled
|
|
13
|
+
* so a long video's step stream can't bury the coarse structural events in
|
|
14
|
+
* the server's bounded timeline window. Structural events are never marked. */
|
|
15
|
+
granular?: boolean;
|
|
16
|
+
} | null;
|
|
17
|
+
/**
|
|
18
|
+
* Stateful gate that admits at most one `granular` checkpoint per throttle
|
|
19
|
+
* window; structural (non-granular) checkpoints always pass. Create one per
|
|
20
|
+
* preset so each preset streams its own opcode/narration cadence. `now` is
|
|
21
|
+
* injectable for tests.
|
|
22
|
+
*/
|
|
23
|
+
export declare function createGranularCheckpointThrottle(throttleMs?: number, now?: () => number): (granular: boolean) => boolean;
|
|
24
|
+
export {};
|
package/dist/cli.js
CHANGED
|
@@ -51,7 +51,9 @@ function displayPresetName(preset) {
|
|
|
51
51
|
const name = preset.name?.trim();
|
|
52
52
|
return name && name.length > 0 ? name : preset.id;
|
|
53
53
|
}
|
|
54
|
-
|
|
54
|
+
/** Interval below which consecutive `granular` checkpoints are coalesced. */
|
|
55
|
+
export const GRANULAR_CHECKPOINT_THROTTLE_MS = 1200;
|
|
56
|
+
export function cloudCaptureProgressCheckpoint(event) {
|
|
55
57
|
switch (event.type) {
|
|
56
58
|
case 'variant_start':
|
|
57
59
|
return {
|
|
@@ -66,6 +68,26 @@ function cloudCaptureProgressCheckpoint(event) {
|
|
|
66
68
|
: `Capture failed: ${event.variantId}`,
|
|
67
69
|
status: event.status === 'failed' ? 'running' : undefined,
|
|
68
70
|
};
|
|
71
|
+
case 'opcode_start': {
|
|
72
|
+
const position = (event.opcodeIndex ?? 0) + 1;
|
|
73
|
+
const total = event.opcodeTotal ? `/${event.opcodeTotal}` : '';
|
|
74
|
+
const label = event.message || event.opcodeKind || '';
|
|
75
|
+
return {
|
|
76
|
+
type: 'preset_progress',
|
|
77
|
+
granular: true,
|
|
78
|
+
message: label ? `Step ${position}${total}: ${label}` : `Step ${position}${total}`,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
case 'tts_progress': {
|
|
82
|
+
const position = event.index ?? '?';
|
|
83
|
+
const total = event.total ?? '?';
|
|
84
|
+
const suffix = event.stage === 'uploading' ? ' · uploading' : event.stage === 'done' ? ' · done' : '';
|
|
85
|
+
return {
|
|
86
|
+
type: 'preset_progress',
|
|
87
|
+
granular: true,
|
|
88
|
+
message: `Narration ${position}/${total}${suffix}`,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
69
91
|
case 'upload_start':
|
|
70
92
|
return {
|
|
71
93
|
type: 'upload_start',
|
|
@@ -81,6 +103,24 @@ function cloudCaptureProgressCheckpoint(event) {
|
|
|
81
103
|
return null;
|
|
82
104
|
}
|
|
83
105
|
}
|
|
106
|
+
/**
|
|
107
|
+
* Stateful gate that admits at most one `granular` checkpoint per throttle
|
|
108
|
+
* window; structural (non-granular) checkpoints always pass. Create one per
|
|
109
|
+
* preset so each preset streams its own opcode/narration cadence. `now` is
|
|
110
|
+
* injectable for tests.
|
|
111
|
+
*/
|
|
112
|
+
export function createGranularCheckpointThrottle(throttleMs = GRANULAR_CHECKPOINT_THROTTLE_MS, now = Date.now) {
|
|
113
|
+
let lastGranularAt = null;
|
|
114
|
+
return (granular) => {
|
|
115
|
+
if (!granular)
|
|
116
|
+
return true;
|
|
117
|
+
const current = now();
|
|
118
|
+
if (lastGranularAt !== null && current - lastGranularAt < throttleMs)
|
|
119
|
+
return false;
|
|
120
|
+
lastGranularAt = current;
|
|
121
|
+
return true;
|
|
122
|
+
};
|
|
123
|
+
}
|
|
84
124
|
// ── login command ───────────────────────────────────────────────────
|
|
85
125
|
program
|
|
86
126
|
.command('login <key>')
|
|
@@ -420,6 +460,9 @@ program
|
|
|
420
460
|
failedPresets: failures.length,
|
|
421
461
|
message: `Preset started: ${presetDisplayName}`,
|
|
422
462
|
});
|
|
463
|
+
// Fresh throttle per preset so each preset streams its own opcode/
|
|
464
|
+
// narration cadence and the window resets at each preset boundary.
|
|
465
|
+
const admitCheckpoint = createGranularCheckpointThrottle();
|
|
423
466
|
const result = await runCapture({
|
|
424
467
|
presetId: preset.id,
|
|
425
468
|
env: opts.env,
|
|
@@ -437,6 +480,10 @@ program
|
|
|
437
480
|
const checkpoint = cloudCaptureProgressCheckpoint(event);
|
|
438
481
|
if (!checkpoint)
|
|
439
482
|
return;
|
|
483
|
+
// Coalesce fine-grained (opcode / narration) checkpoints; structural
|
|
484
|
+
// ones (capture/export start/end) always pass through.
|
|
485
|
+
if (!admitCheckpoint(checkpoint.granular ?? false))
|
|
486
|
+
return;
|
|
440
487
|
void postCloudCheckpoint({
|
|
441
488
|
type: checkpoint.type,
|
|
442
489
|
presetId: preset.id,
|
package/dist/opcode-runner.d.ts
CHANGED
|
@@ -52,11 +52,20 @@ export interface RunOptions {
|
|
|
52
52
|
dryRun?: boolean;
|
|
53
53
|
}
|
|
54
54
|
export interface ProgressEvent {
|
|
55
|
-
type: 'variant_start' | 'variant_end' | 'opcode_start' | 'opcode_end' | 'recovery' | 'breaker_trip' | 'upload_start' | 'upload_end';
|
|
55
|
+
type: 'variant_start' | 'variant_end' | 'opcode_start' | 'opcode_end' | 'recovery' | 'breaker_trip' | 'upload_start' | 'upload_end' | 'tts_progress';
|
|
56
56
|
variantId: string;
|
|
57
57
|
opcodeIndex?: number;
|
|
58
|
+
/** Total opcode count in the variant program (set on opcode_start). */
|
|
59
|
+
opcodeTotal?: number;
|
|
58
60
|
opcodeKind?: string;
|
|
59
61
|
status?: OpcodeResultStatus;
|
|
60
62
|
message: string;
|
|
63
|
+
/** tts_progress only: 'synthesizing' | 'uploading' | 'done'. */
|
|
64
|
+
stage?: string;
|
|
65
|
+
/** tts_progress only: 1-based segment index and total. */
|
|
66
|
+
index?: number;
|
|
67
|
+
total?: number;
|
|
68
|
+
/** tts_progress only: the SLEEP anchor / step id being synthesized. */
|
|
69
|
+
stepId?: string;
|
|
61
70
|
}
|
|
62
71
|
export declare function executeProgram(program: ExecutionProgram, createAdapter: (variant: VariantSpec) => Promise<RuntimeAdapter>, options?: RunOptions): Promise<RunResult>;
|
package/dist/opcode-runner.js
CHANGED
|
@@ -318,6 +318,7 @@ async function executeVariant(program, variant, createAdapter, recoveryChain, te
|
|
|
318
318
|
type: 'opcode_start',
|
|
319
319
|
variantId: variant.id,
|
|
320
320
|
opcodeIndex: i,
|
|
321
|
+
opcodeTotal: program.steps.length,
|
|
321
322
|
opcodeKind: opcode.kind,
|
|
322
323
|
message: opcode.description,
|
|
323
324
|
});
|
|
@@ -41,3 +41,13 @@ export declare function resolveTarget(page: Page, options: ResolveOptions): Prom
|
|
|
41
41
|
* language subtag → base field → first non-empty map value.
|
|
42
42
|
*/
|
|
43
43
|
export declare function localizeSemanticTarget(target: SemanticTarget, locale?: string): SemanticTarget;
|
|
44
|
+
/**
|
|
45
|
+
* Emits a one-line warning when the active locale has NO entry in any of the
|
|
46
|
+
* target's `*ByLocale` maps, so the resolver is about to fall back to another
|
|
47
|
+
* language's string. This is the failure mode behind captures that pass in
|
|
48
|
+
* every language whose text was captured and fail in the ones that were only
|
|
49
|
+
* guessed (or never translated): the fallback string doesn't match the
|
|
50
|
+
* localized UI, and the CLICK/TYPE fails deep in the run instead of surfacing
|
|
51
|
+
* the real cause. Silent otherwise — no maps or a matching entry logs nothing.
|
|
52
|
+
*/
|
|
53
|
+
export declare function warnOnMissingLocaleEntry(target: SemanticTarget, locale?: string): void;
|
|
Binary file
|
|
@@ -8,7 +8,7 @@ import fs from 'node:fs/promises';
|
|
|
8
8
|
import os from 'node:os';
|
|
9
9
|
import path from 'node:path';
|
|
10
10
|
import { humanType, moveMouse, } from './mouse-animation.js';
|
|
11
|
-
import { resolveTarget } from './semantic-resolver.js';
|
|
11
|
+
import { resolveTarget, localizeSemanticTarget } from './semantic-resolver.js';
|
|
12
12
|
import { logger } from './logger.js';
|
|
13
13
|
import { ClipCaptureLoop } from './clip-capture-loop.js';
|
|
14
14
|
import { FfmpegX11Recorder } from './ffmpeg-x11-recorder.js';
|
|
@@ -1369,18 +1369,44 @@ export class WebPlaywrightLocal {
|
|
|
1369
1369
|
}
|
|
1370
1370
|
}
|
|
1371
1371
|
}
|
|
1372
|
-
function describeResolveOptions(opts) {
|
|
1372
|
+
export function describeResolveOptions(opts) {
|
|
1373
1373
|
const parts = [];
|
|
1374
1374
|
if (opts.selector)
|
|
1375
1375
|
parts.push(`selector="${opts.selector}"`);
|
|
1376
|
-
if (opts.target
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1376
|
+
if (opts.target) {
|
|
1377
|
+
// Project the target onto the active locale so the message reports what was
|
|
1378
|
+
// actually searched: the `*ByLocale` maps collapse onto their base field.
|
|
1379
|
+
// Without this, a target expressed purely through `textByLocale` (no role,
|
|
1380
|
+
// no plain text) rendered as the misleading "no target specified" even
|
|
1381
|
+
// though a localized string was attempted — which sends debugging down the
|
|
1382
|
+
// wrong path (it looks like the opcode carries no target at all).
|
|
1383
|
+
const projected = localizeSemanticTarget(opts.target, opts.locale);
|
|
1384
|
+
if (projected.role)
|
|
1385
|
+
parts.push(`role="${projected.role}"`);
|
|
1386
|
+
if (projected.text)
|
|
1387
|
+
parts.push(`text="${projected.text}"`);
|
|
1388
|
+
if (projected.label)
|
|
1389
|
+
parts.push(`label="${projected.label}"`);
|
|
1390
|
+
if (projected.placeholder)
|
|
1391
|
+
parts.push(`placeholder="${projected.placeholder}"`);
|
|
1392
|
+
if (projected.near)
|
|
1393
|
+
parts.push(`near="${projected.near}"`);
|
|
1394
|
+
if (opts.locale)
|
|
1395
|
+
parts.push(`locale="${opts.locale}"`);
|
|
1396
|
+
// Echo the raw locale maps so a missing or mistranslated entry for the
|
|
1397
|
+
// active locale is obvious at a glance (e.g. `fr` guessed as "Nouvelle
|
|
1398
|
+
// capture" when the deployed UI reads "Nouveau programme de capture").
|
|
1399
|
+
const describeMap = (name, map) => {
|
|
1400
|
+
const entries = Object.entries(map ?? {});
|
|
1401
|
+
if (entries.length > 0) {
|
|
1402
|
+
parts.push(`${name}={${entries.map(([k, v]) => `${k}:"${v}"`).join(', ')}}`);
|
|
1403
|
+
}
|
|
1404
|
+
};
|
|
1405
|
+
describeMap('textByLocale', opts.target.textByLocale);
|
|
1406
|
+
describeMap('labelByLocale', opts.target.labelByLocale);
|
|
1407
|
+
describeMap('placeholderByLocale', opts.target.placeholderByLocale);
|
|
1408
|
+
describeMap('nearByLocale', opts.target.nearByLocale);
|
|
1409
|
+
}
|
|
1384
1410
|
return parts.join(', ') || 'no target specified';
|
|
1385
1411
|
}
|
|
1386
1412
|
async function captureSurfaceMatches(page, expected) {
|