autokap 1.8.6 → 1.8.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/dist/action-verifier.d.ts +6 -0
  2. package/dist/action-verifier.js +30 -17
  3. package/dist/browser.d.ts +59 -0
  4. package/dist/browser.js +259 -0
  5. package/dist/cli-config.js +7 -12
  6. package/dist/cli-contract.d.ts +5 -9
  7. package/dist/cli-contract.js +11 -38
  8. package/dist/cli-runner.d.ts +0 -1
  9. package/dist/cli-runner.js +74 -59
  10. package/dist/cli.js +7 -7
  11. package/dist/clip-capture-loop.d.ts +28 -7
  12. package/dist/clip-capture-loop.js +102 -19
  13. package/dist/engine-version.d.ts +24 -0
  14. package/dist/engine-version.js +25 -0
  15. package/dist/execution-schema.d.ts +22 -0
  16. package/dist/execution-schema.js +59 -8
  17. package/dist/execution-types.d.ts +116 -0
  18. package/dist/opcode-runner.d.ts +8 -1
  19. package/dist/opcode-runner.js +120 -29
  20. package/dist/postcondition.d.ts +18 -3
  21. package/dist/postcondition.js +75 -27
  22. package/dist/program-hash.d.ts +11 -0
  23. package/dist/program-hash.js +28 -0
  24. package/dist/program-migrations.d.ts +31 -0
  25. package/dist/program-migrations.js +93 -0
  26. package/dist/program-signing.d.ts +11 -0
  27. package/dist/program-signing.js +1 -0
  28. package/dist/recovery-chain.js +8 -11
  29. package/dist/scenario-cookie.d.ts +36 -0
  30. package/dist/scenario-cookie.js +62 -0
  31. package/dist/security.d.ts +21 -0
  32. package/dist/security.js +46 -8
  33. package/dist/server-credit-usage.d.ts +1 -1
  34. package/dist/version.d.ts +1 -0
  35. package/dist/version.js +1 -0
  36. package/dist/video-narration-schema.d.ts +3 -0
  37. package/dist/video-narration-schema.js +3 -0
  38. package/dist/wait-contract.d.ts +104 -0
  39. package/dist/wait-contract.js +144 -0
  40. package/dist/web-playwright-local.d.ts +9 -1
  41. package/dist/web-playwright-local.js +0 -0
  42. package/package.json +2 -2
  43. package/readme.md +9 -15
@@ -0,0 +1,93 @@
1
+ /**
2
+ * Capture Agent — Program FORM migrations (migrate-on-read)
3
+ *
4
+ * Old presets are stored at whatever `programSchemaVersion` (FORM) was current
5
+ * when they were authored. `upgradeProgram` runs a chain of pure
6
+ * `migrate_vN→vN+1` functions to bring any stored program up to the current
7
+ * form BEFORE strict schema validation, so the runner only ever sees one shape.
8
+ *
9
+ * Properties of this layer (decisions locked in AUT-242):
10
+ * - Compat forever: the chain is kept indefinitely; no support window.
11
+ * - Migrate-on-read only: programs are NEVER rewritten back to storage. The
12
+ * stored form changes only when the generator recompiles (create/modify).
13
+ * - Pure + idempotent: a program already at the current form is a no-op.
14
+ *
15
+ * This module is intentionally free of Node-only imports so it can be pulled
16
+ * into the schema validation chain on any runtime. Content hashing
17
+ * (`node:crypto`) lives in program-hash.ts.
18
+ */
19
+ import { CURRENT_PROGRAM_SCHEMA_VERSION } from './engine-version.js';
20
+ /** Canonical video capture/delivery resolution (1920×1080 @1×). */
21
+ const VIDEO_RESOLUTION = { width: 1920, height: 1080 };
22
+ function isRecord(value) {
23
+ return value !== null && typeof value === 'object' && !Array.isArray(value);
24
+ }
25
+ /**
26
+ * Reads the FORM version a raw (pre-migration) program was stored at.
27
+ * Absent / non-finite ⇒ 0 (the oldest form). Used to stamp run provenance
28
+ * (`program_schema_version_origin`) before `upgradeProgram` bumps it.
29
+ */
30
+ export function readOriginSchemaVersion(raw) {
31
+ if (isRecord(raw) &&
32
+ typeof raw.programSchemaVersion === 'number' &&
33
+ Number.isFinite(raw.programSchemaVersion)) {
34
+ return raw.programSchemaVersion;
35
+ }
36
+ return 0;
37
+ }
38
+ /**
39
+ * v0 → v1: fold the legacy video capture-resolution normalization (formerly the
40
+ * runtime `normalizeVideoCaptureProgram` in cli-runner.ts) into the program
41
+ * FORM. Legacy video presets carrying viewport=2560×1440 / DPR=1.3333 /
42
+ * captureResolution=2560×1440 are normalized to 1920×1080 @1×. Non-video
43
+ * programs pass through unchanged. Defensive on malformed shapes — anything it
44
+ * can't normalize is left for the strict schema to reject with a clean error.
45
+ */
46
+ function migrate_0_to_1(prog) {
47
+ if (prog.mediaMode !== 'video')
48
+ return prog;
49
+ const artifactPlan = isRecord(prog.artifactPlan) ? prog.artifactPlan : undefined;
50
+ const format = artifactPlan && isRecord(artifactPlan.format) ? artifactPlan.format : {};
51
+ const variants = Array.isArray(prog.variants)
52
+ ? prog.variants.map((v) => isRecord(v) ? { ...v, viewport: { ...VIDEO_RESOLUTION }, deviceScaleFactor: 1 } : v)
53
+ : prog.variants;
54
+ return {
55
+ ...prog,
56
+ outputScale: 1,
57
+ variants,
58
+ artifactPlan: {
59
+ ...(artifactPlan ?? {}),
60
+ format: {
61
+ ...format,
62
+ captureResolution: { ...VIDEO_RESOLUTION },
63
+ deliveryResolution: { ...VIDEO_RESOLUTION },
64
+ },
65
+ },
66
+ };
67
+ }
68
+ /** Ordered FORM migrations. `MIGRATIONS[n]` upgrades a vN program to vN+1. */
69
+ const MIGRATIONS = {
70
+ 0: migrate_0_to_1,
71
+ };
72
+ /**
73
+ * Brings any stored program up to {@link CURRENT_PROGRAM_SCHEMA_VERSION} (form)
74
+ * before strict validation. Pure: clones, never mutates `raw`. Idempotent: a
75
+ * program already at the current form is returned with only its version stamped.
76
+ * Non-object input is returned untouched so the schema raises a clean error.
77
+ */
78
+ export function upgradeProgram(raw) {
79
+ if (!isRecord(raw))
80
+ return raw;
81
+ let prog = { ...raw };
82
+ let v = readOriginSchemaVersion(raw);
83
+ while (v < CURRENT_PROGRAM_SCHEMA_VERSION) {
84
+ const migrate = MIGRATIONS[v];
85
+ if (!migrate)
86
+ break; // no path forward; let the schema reject a wrong shape
87
+ prog = migrate(prog);
88
+ v += 1;
89
+ }
90
+ prog.programSchemaVersion = CURRENT_PROGRAM_SCHEMA_VERSION;
91
+ return prog;
92
+ }
93
+ //# sourceMappingURL=program-migrations.js.map
@@ -13,6 +13,13 @@ export interface SignedExecutionProgramEnvelope {
13
13
  signature: string;
14
14
  meta?: {
15
15
  stale?: boolean;
16
+ /**
17
+ * FORM version the stored program was at BEFORE the server migrated it
18
+ * (0 = legacy / no version field). Debug-only run provenance; lives in the
19
+ * UNSIGNED meta because it carries no security weight — the server reads it
20
+ * from the raw preset config before `extractExecutionProgram` normalizes.
21
+ */
22
+ programSchemaVersionOrigin?: number;
16
23
  };
17
24
  }
18
25
  export declare const ProgramSecurityMetadataSchema: z.ZodObject<{
@@ -31,6 +38,8 @@ export declare const SignedExecutionProgramEnvelopeSchema: z.ZodObject<{
31
38
  program: z.ZodObject<{
32
39
  presetId: z.ZodString;
33
40
  programVersion: z.ZodNumber;
41
+ programSchemaVersion: z.ZodOptional<z.ZodNumber>;
42
+ engineVersion: z.ZodOptional<z.ZodNumber>;
34
43
  mediaMode: z.ZodEnum<{
35
44
  video: "video";
36
45
  clip: "clip";
@@ -141,6 +150,7 @@ export declare const SignedExecutionProgramEnvelopeSchema: z.ZodObject<{
141
150
  domain: z.ZodString;
142
151
  path: z.ZodOptional<z.ZodString>;
143
152
  }, z.core.$strict>>>;
153
+ scenario: z.ZodOptional<z.ZodString>;
144
154
  }, z.core.$strict>;
145
155
  steps: z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
146
156
  url: z.ZodString;
@@ -1133,6 +1143,7 @@ export declare const SignedExecutionProgramEnvelopeSchema: z.ZodObject<{
1133
1143
  signature: z.ZodString;
1134
1144
  meta: z.ZodOptional<z.ZodObject<{
1135
1145
  stale: z.ZodOptional<z.ZodBoolean>;
1146
+ programSchemaVersionOrigin: z.ZodOptional<z.ZodNumber>;
1136
1147
  }, z.core.$strict>>;
1137
1148
  }, z.core.$strict>;
1138
1149
  export declare function normalizeAllowedOrigins(origins: Iterable<string>): string[];
@@ -30,6 +30,7 @@ export const SignedExecutionProgramEnvelopeSchema = z.object({
30
30
  signature: z.string().regex(/^[a-f0-9]{64}$/i),
31
31
  meta: z.object({
32
32
  stale: z.boolean().optional(),
33
+ programSchemaVersionOrigin: z.number().int().nonnegative().optional(),
33
34
  }).strict().optional(),
34
35
  }).strict();
35
36
  function stableNormalize(value) {
@@ -9,7 +9,7 @@
9
9
  * 5. LLM Healer (last resort)
10
10
  */
11
11
  import { resolveSelector } from './selector-resolver.js';
12
- import { evaluatePostcondition } from './postcondition.js';
12
+ import { evaluatePostcondition, evaluatePostconditionWithProgress } from './postcondition.js';
13
13
  import { LLMHealer } from './llm-healer.js';
14
14
  import { serializeAKTree } from './ak-tree.js';
15
15
  import { executeOpcodeCoreAction } from './opcode-actions.js';
@@ -35,7 +35,7 @@ export class RecoveryChainImpl {
35
35
  const retryBudget = Math.max(0, Math.min(recovery.retries, options.maxDeterministicRetries ?? recovery.retries));
36
36
  if (retryBudget > 0) {
37
37
  logger.debug(`[recovery ${opcodeIndex}] strategy 1 (retry x${retryBudget})`);
38
- const result = await retryOpcode(failedOpcode, adapter, retryBudget, options.remainingTimeMs, options.currentVariant, this.credentials, options.suppressPageReloads);
38
+ const result = await retryOpcode(failedOpcode, adapter, retryBudget, options.remainingTimeMs, options.currentVariant, this.credentials, options.suppressPageReloads, options.globalDeadlineMs, options.getProgress);
39
39
  logger.debug(`[recovery ${opcodeIndex}] strategy 1 → recovered=${result.recovered}, reason=${result.reason}`);
40
40
  if (result.recovered)
41
41
  return result;
@@ -86,7 +86,7 @@ export class RecoveryChainImpl {
86
86
  }
87
87
  }
88
88
  // ── Strategy 1: Deterministic retry ─────────────────────────────────
89
- async function retryOpcode(opcode, adapter, maxRetries, remainingTimeMs, currentVariant, credentials, suppressPageReloads = false) {
89
+ async function retryOpcode(opcode, adapter, maxRetries, remainingTimeMs, currentVariant, credentials, suppressPageReloads = false, globalDeadlineMs, getProgress) {
90
90
  const deadlineMs = typeof remainingTimeMs === 'number'
91
91
  ? Date.now() + remainingTimeMs
92
92
  : Number.POSITIVE_INFINITY;
@@ -97,7 +97,11 @@ async function retryOpcode(opcode, adapter, maxRetries, remainingTimeMs, current
97
97
  await sleep(500 * (i + 1)); // backoff
98
98
  try {
99
99
  await executeRawAction(opcode, adapter, currentVariant, credentials, suppressPageReloads);
100
- const postcondition = await evaluatePostcondition(adapter, clampPostconditionWait(opcode.postcondition, deadlineMs));
100
+ // AUT-240 (Phase 5): the re-check extends-on-progress up to the global
101
+ // deadline instead of replaying a fixed clamped budget. Without the global
102
+ // deadline (legacy callers), `getProgress` is dropped so it degrades to the
103
+ // compiled postcondition budget.
104
+ const postcondition = await evaluatePostconditionWithProgress(adapter, opcode.postcondition, Date.now(), globalDeadlineMs ?? deadlineMs, globalDeadlineMs !== undefined ? getProgress : undefined);
101
105
  if (postcondition.passed) {
102
106
  return { recovered: true, strategy: 'retry', reason: `retry ${i + 1}/${maxRetries} succeeded` };
103
107
  }
@@ -363,11 +367,4 @@ async function resolveSelectorCoordinates(adapter, selector) {
363
367
  function sleep(ms) {
364
368
  return new Promise(resolve => setTimeout(resolve, ms));
365
369
  }
366
- function clampPostconditionWait(spec, deadlineMs) {
367
- const remainingTimeMs = Math.max(1, deadlineMs - Date.now());
368
- return {
369
- ...spec,
370
- waitMs: Math.max(1, Math.min(spec.waitMs ?? remainingTimeMs, remainingTimeMs)),
371
- };
372
- }
373
370
  //# sourceMappingURL=recovery-chain.js.map
@@ -0,0 +1,36 @@
1
+ import type { PreconditionSpec } from './execution-types.js';
2
+ /**
3
+ * AUT-239 — signing side of the AutoKap Scenario cookie.
4
+ *
5
+ * The runner (this CLI) signs; the client app's `@autokap/scenario` package
6
+ * verifies. At runtime these live in two separate processes/deployments, so the
7
+ * only thing they share is the WIRE FORMAT, not a module. We therefore keep a
8
+ * tiny local signer here rather than taking a runtime dependency on the
9
+ * published `@autokap/scenario` package. `scenario-cookie.test.ts` locks the
10
+ * format against the real resolver so the two can never drift.
11
+ *
12
+ * Format: `<id>.<base64url(HMAC-SHA256(secret, id))>`.
13
+ */
14
+ export declare const SCENARIO_COOKIE_NAME = "__ak_scenario";
15
+ export declare function signScenarioCookie(id: string, secret: string): string;
16
+ /** A cookie ready for Playwright's `context.addCookies`. */
17
+ export interface PreconditionCookie {
18
+ name: string;
19
+ value: string;
20
+ domain: string;
21
+ path?: string;
22
+ secure: boolean;
23
+ }
24
+ /**
25
+ * Build the cookie set injected before navigation: the preset's seed cookies
26
+ * plus, when configured, the signed scenario switch.
27
+ *
28
+ * `secure` is derived from the target protocol — a Secure cookie is never sent
29
+ * over http://localhost, so the runner must NOT default it to true (that would
30
+ * silently drop seed/scenario cookies in local captures and fall back to real
31
+ * data). Returns a `warning` when a scenario is requested but unsignable.
32
+ */
33
+ export declare function buildPreconditionCookies(preconditions: Pick<PreconditionSpec, 'cookies' | 'scenario'>, baseUrl: string, secret: string | undefined): {
34
+ cookies: PreconditionCookie[];
35
+ warning?: string;
36
+ };
@@ -0,0 +1,62 @@
1
+ import { createHmac } from 'node:crypto';
2
+ /**
3
+ * AUT-239 — signing side of the AutoKap Scenario cookie.
4
+ *
5
+ * The runner (this CLI) signs; the client app's `@autokap/scenario` package
6
+ * verifies. At runtime these live in two separate processes/deployments, so the
7
+ * only thing they share is the WIRE FORMAT, not a module. We therefore keep a
8
+ * tiny local signer here rather than taking a runtime dependency on the
9
+ * published `@autokap/scenario` package. `scenario-cookie.test.ts` locks the
10
+ * format against the real resolver so the two can never drift.
11
+ *
12
+ * Format: `<id>.<base64url(HMAC-SHA256(secret, id))>`.
13
+ */
14
+ export const SCENARIO_COOKIE_NAME = '__ak_scenario';
15
+ export function signScenarioCookie(id, secret) {
16
+ const sig = createHmac('sha256', secret).update(id).digest('base64url');
17
+ return `${id}.${sig}`;
18
+ }
19
+ /**
20
+ * Build the cookie set injected before navigation: the preset's seed cookies
21
+ * plus, when configured, the signed scenario switch.
22
+ *
23
+ * `secure` is derived from the target protocol — a Secure cookie is never sent
24
+ * over http://localhost, so the runner must NOT default it to true (that would
25
+ * silently drop seed/scenario cookies in local captures and fall back to real
26
+ * data). Returns a `warning` when a scenario is requested but unsignable.
27
+ */
28
+ export function buildPreconditionCookies(preconditions, baseUrl, secret) {
29
+ let secure = true;
30
+ let hostname = '';
31
+ try {
32
+ const url = new URL(baseUrl);
33
+ secure = url.protocol === 'https:';
34
+ hostname = url.hostname;
35
+ }
36
+ catch {
37
+ /* baseUrl is validated upstream; fall back to Secure on parse failure */
38
+ }
39
+ const cookies = (preconditions.cookies ?? []).map((c) => ({
40
+ ...c,
41
+ secure,
42
+ }));
43
+ let warning;
44
+ if (preconditions.scenario) {
45
+ if (!secret) {
46
+ warning =
47
+ `preconditions.scenario="${preconditions.scenario}" set but AUTOKAP_SCENARIO_SECRET ` +
48
+ `is missing — scenario cookie NOT injected; capture will use real data`;
49
+ }
50
+ else {
51
+ cookies.push({
52
+ name: SCENARIO_COOKIE_NAME,
53
+ value: signScenarioCookie(preconditions.scenario, secret),
54
+ domain: hostname,
55
+ path: '/',
56
+ secure,
57
+ });
58
+ }
59
+ }
60
+ return { cookies, warning };
61
+ }
62
+ //# sourceMappingURL=scenario-cookie.js.map
@@ -16,6 +16,27 @@ export interface SecurityDecision {
16
16
  reason?: string;
17
17
  target?: InteractiveElement | null;
18
18
  }
19
+ /**
20
+ * Is `candidateUrl` part of the same first-party site as `scopeUrl`? Shares the
21
+ * navigation site-scope model (`isWithinProjectScope`): exact-host for IPs /
22
+ * localhost / shared-hosting suffixes (so sibling `*.vercel.app` previews are
23
+ * NOT same-site), sub-domain family match for real registrable domains.
24
+ *
25
+ * Used to scope the adaptive-wait progress signal (AUT-240) to the app's own
26
+ * traffic, so third-party telemetry (PostHog beacons, analytics/ad pixels,
27
+ * Sentry, …) no longer reads as "the page is making progress" and the stuck
28
+ * watchdog can still cut a wait whose condition will never be met.
29
+ *
30
+ * Fail-OPEN by design: a false "first-party" only makes the watchdog slightly
31
+ * more patient (still bounded by the per-media cap), whereas a false "foreign"
32
+ * could suppress a real progress signal and cut a legitimately-slow page early.
33
+ * So when in doubt we count it as first-party:
34
+ * - unparseable / non-http(s) `scopeUrl` (e.g. `about:blank` before the first
35
+ * navigation commits) ⇒ true (don't filter);
36
+ * - non-http(s) `candidateUrl` (`data:` / `blob:` / `about:`) ⇒ true (in-page
37
+ * resource).
38
+ */
39
+ export declare function isFirstPartyUrl(scopeUrl: string | null | undefined, candidateUrl: string): boolean;
19
40
  export declare function evaluateResolvedActionSecurity(action: ActionType, args: Record<string, unknown>, context: SecurityContext, target: InteractiveElement | null): SecurityDecision;
20
41
  export declare function evaluateActionSecurity(action: ActionType, args: Record<string, unknown>, context: SecurityContext): SecurityDecision;
21
42
  export declare function describeSecurityTarget(target: InteractiveElement | null | undefined): string;
package/dist/security.js CHANGED
@@ -109,6 +109,16 @@ function isSharedHostingHostname(hostname) {
109
109
  function normalizeScopeHostname(hostname) {
110
110
  return hostname.startsWith('www.') ? hostname.slice(4) : hostname;
111
111
  }
112
+ function scopeFromParsedUrl(parsed) {
113
+ const hostname = parsed.hostname.toLowerCase();
114
+ const exactHost = isLocalHostname(hostname) || isIpv4Hostname(hostname) || isIpv6Hostname(hostname) || isSharedHostingHostname(hostname);
115
+ const scopeHostname = exactHost ? hostname : normalizeScopeHostname(hostname);
116
+ return {
117
+ hostname: scopeHostname,
118
+ port: effectivePort(parsed),
119
+ exactHost,
120
+ };
121
+ }
112
122
  function buildNavigationScopes(context) {
113
123
  const sources = [
114
124
  context.rootUrl,
@@ -119,18 +129,46 @@ function buildNavigationScopes(context) {
119
129
  const parsed = parseHttpUrl(value);
120
130
  if (!parsed)
121
131
  continue;
122
- const hostname = parsed.hostname.toLowerCase();
123
- const exactHost = isLocalHostname(hostname) || isIpv4Hostname(hostname) || isIpv6Hostname(hostname) || isSharedHostingHostname(hostname);
124
- const scopeHostname = exactHost ? hostname : normalizeScopeHostname(hostname);
125
- const scope = {
126
- hostname: scopeHostname,
127
- port: effectivePort(parsed),
128
- exactHost,
129
- };
132
+ const scope = scopeFromParsedUrl(parsed);
130
133
  dedup.set(`${scope.hostname}:${scope.port}:${scope.exactHost ? 'exact' : 'family'}`, scope);
131
134
  }
132
135
  return Array.from(dedup.values());
133
136
  }
137
+ /**
138
+ * Is `candidateUrl` part of the same first-party site as `scopeUrl`? Shares the
139
+ * navigation site-scope model (`isWithinProjectScope`): exact-host for IPs /
140
+ * localhost / shared-hosting suffixes (so sibling `*.vercel.app` previews are
141
+ * NOT same-site), sub-domain family match for real registrable domains.
142
+ *
143
+ * Used to scope the adaptive-wait progress signal (AUT-240) to the app's own
144
+ * traffic, so third-party telemetry (PostHog beacons, analytics/ad pixels,
145
+ * Sentry, …) no longer reads as "the page is making progress" and the stuck
146
+ * watchdog can still cut a wait whose condition will never be met.
147
+ *
148
+ * Fail-OPEN by design: a false "first-party" only makes the watchdog slightly
149
+ * more patient (still bounded by the per-media cap), whereas a false "foreign"
150
+ * could suppress a real progress signal and cut a legitimately-slow page early.
151
+ * So when in doubt we count it as first-party:
152
+ * - unparseable / non-http(s) `scopeUrl` (e.g. `about:blank` before the first
153
+ * navigation commits) ⇒ true (don't filter);
154
+ * - non-http(s) `candidateUrl` (`data:` / `blob:` / `about:`) ⇒ true (in-page
155
+ * resource).
156
+ */
157
+ export function isFirstPartyUrl(scopeUrl, candidateUrl) {
158
+ const scopeParsed = parseHttpUrl(scopeUrl ?? undefined);
159
+ if (!scopeParsed)
160
+ return true;
161
+ let candidate;
162
+ try {
163
+ candidate = new URL(candidateUrl);
164
+ }
165
+ catch {
166
+ return true;
167
+ }
168
+ if (candidate.protocol !== 'http:' && candidate.protocol !== 'https:')
169
+ return true;
170
+ return isWithinProjectScope(candidate, [scopeFromParsedUrl(scopeParsed)]);
171
+ }
134
172
  function isWithinProjectScope(candidate, scopes) {
135
173
  const hostname = candidate.hostname.toLowerCase();
136
174
  const port = effectivePort(candidate);
@@ -1,5 +1,5 @@
1
1
  import type { SupabaseClient } from '@supabase/supabase-js';
2
- export type CreditUsageType = 'screenshot' | 'clip' | 'video' | 'cloud_recapture' | 'ai_chat' | 'studio_creation' | 'studio_iteration' | 'error_analysis' | 'obsolescence_analysis';
2
+ export type CreditUsageType = 'screenshot' | 'clip' | 'video' | 'cloud_recapture' | 'ai_chat' | 'studio_creation' | 'studio_iteration' | 'error_analysis' | 'obsolescence_analysis' | 'pr_generation';
3
3
  export declare function recordCreditUsage(supabase: SupabaseClient, params: {
4
4
  userId: string;
5
5
  projectId: string | null;
package/dist/version.d.ts CHANGED
@@ -1 +1,2 @@
1
+ /** npm package version of the CLI, transmitted as the `x-autokap-cli-version` header. */
1
2
  export declare const APP_VERSION: string;
package/dist/version.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import { createRequire } from 'node:module';
2
2
  const require = createRequire(import.meta.url);
3
3
  const pkg = require('../package.json');
4
+ /** npm package version of the CLI, transmitted as the `x-autokap-cli-version` header. */
4
5
  export const APP_VERSION = pkg.version;
5
6
  //# sourceMappingURL=version.js.map
@@ -57,6 +57,8 @@ export declare const VideoIngestPayloadSchema: z.ZodObject<{
57
57
  program: z.ZodObject<{
58
58
  presetId: z.ZodString;
59
59
  programVersion: z.ZodNumber;
60
+ programSchemaVersion: z.ZodOptional<z.ZodNumber>;
61
+ engineVersion: z.ZodOptional<z.ZodNumber>;
60
62
  mediaMode: z.ZodEnum<{
61
63
  video: "video";
62
64
  clip: "clip";
@@ -167,6 +169,7 @@ export declare const VideoIngestPayloadSchema: z.ZodObject<{
167
169
  domain: z.ZodString;
168
170
  path: z.ZodOptional<z.ZodString>;
169
171
  }, z.core.$strict>>>;
172
+ scenario: z.ZodOptional<z.ZodString>;
170
173
  }, z.core.$strict>;
171
174
  steps: z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
172
175
  url: z.ZodString;
@@ -83,6 +83,9 @@ export const VideoNarrationOverlaySchema = z.object({
83
83
  * match a `program.steps[*].stepId`.
84
84
  */
85
85
  export const VideoIngestPayloadSchema = z.object({
86
+ // Strict by design: this is a legacy compatibility-test ingest schema with no
87
+ // live consumer (test-only + barrel re-export), so it validates the raw form
88
+ // without migrate-on-read. Live program validation goes through parseProgram.
86
89
  program: ExecutionProgramSchema,
87
90
  narration: VideoNarrationOverlaySchema.optional(),
88
91
  }).strict().superRefine((value, ctx) => {
@@ -0,0 +1,104 @@
1
+ /**
2
+ * Capture Agent — Wait Contract (AUT-240)
3
+ *
4
+ * Single source of truth for the engine's adaptive waiting behaviour. Zero user
5
+ * configuration: every budget/deadline below is an internal constant. Versioned
6
+ * (`WAIT_CONTRACT_VERSION`) so runs can be debugged and future migrations
7
+ * reasoned about; the global engine-versioning infrastructure is a separate
8
+ * effort.
9
+ *
10
+ * The model has three layers (see the AUT-240 issue):
11
+ * - Layer A — actionability & semantic postconditions.
12
+ * - Layer B — `waitForVisuallyStable` (screenshot stabilization).
13
+ * - Layer C — dynamic budgets: a wait extends while the page is making progress
14
+ * (network / DOM) and is cut once it is genuinely stuck, up to a hard
15
+ * per-media global deadline. This is the real cure for the engine's #1 pain —
16
+ * a slow-but-progressing page no longer trips a fixed timeout.
17
+ */
18
+ import type { MediaMode, ProgressSnapshot } from './execution-types.js';
19
+ /** Bump when the wait semantics change in a way worth tracing on a run. */
20
+ export declare const WAIT_CONTRACT_VERSION = 1;
21
+ /**
22
+ * Hard ceiling on adaptive waiting, per media mode. Applied as a FLOOR via
23
+ * `max()` with the compiled opcode timeout (see `resolveGlobalWaitDeadlineMs`)
24
+ * so it never shortens an intentionally-long opcode (`SLEEP`, `END_CLIP`).
25
+ */
26
+ export declare const GLOBAL_WAIT_CAP_MS: Record<MediaMode, number>;
27
+ /** No observed progress for this long ⇒ the wait is "stuck" and gets cut. */
28
+ export declare const STUCK_WINDOW_MS = 4000;
29
+ /** Watchdog poll cadence while a wait is in flight. */
30
+ export declare const PROGRESS_POLL_INTERVAL_MS = 250;
31
+ /**
32
+ * A timed-out wait cannot be cancelled through the adapter API. Before recovery
33
+ * or the next opcode starts, give the orphaned operation this long to settle so
34
+ * it does not mutate the page concurrently. Mirrors the runner's drain.
35
+ */
36
+ export declare const ORPHAN_DRAIN_MS = 2000;
37
+ /** DOM-quiet settle window for visual stabilization (Layer B). */
38
+ export declare const DOM_QUIET_WINDOW_MS = 250;
39
+ /** Bounded pixel-convergence fallback (Layer B). */
40
+ export declare const PIXEL_FALLBACK_MAX_PASSES = 3;
41
+ export declare const PIXEL_FALLBACK_DIFF_THRESHOLD = 0.01;
42
+ /**
43
+ * Resolve the per-opcode global wait deadline (absolute ms timestamp).
44
+ * `compiledTimeoutMs` is the opcode's compiled `timeoutMs` — treated as a FLOOR,
45
+ * never a ceiling, so the deadline can only ever be extended past the compiled
46
+ * value, never shortened below it.
47
+ */
48
+ export declare function resolveGlobalWaitDeadlineMs(startedAtMs: number, compiledTimeoutMs: number, mediaMode: MediaMode): number;
49
+ /**
50
+ * Did the page make observable progress between two snapshots? Any of: a
51
+ * navigation in flight, new network events, in-flight count change, network
52
+ * activity advancing, readyState change, or DOM node-count change. A null
53
+ * current reading is treated conservatively as "no progress"; the first real
54
+ * reading (null `prev`) counts as progress to seed the watchdog window.
55
+ *
56
+ * The network fields are first-party-scoped at the source (see
57
+ * `getProgressSnapshot` / `isFirstPartyUrl`): background third-party telemetry
58
+ * (analytics beacons, ad pixels, polling to other origins) is excluded, so it
59
+ * cannot keep this `true` forever and starve the stuck-cut.
60
+ */
61
+ export declare function hasProgress(prev: ProgressSnapshot | null | undefined, cur: ProgressSnapshot | null | undefined): boolean;
62
+ export type ProgressBudgetCut = 'stuck' | 'deadline';
63
+ export interface ProgressBudgetOptions {
64
+ /** `Date.now()` when the surrounding opcode started (for elapsed/min-budget). */
65
+ startedAtMs: number;
66
+ /** Absolute hard deadline — the wait never runs past this. */
67
+ globalDeadlineMs: number;
68
+ /**
69
+ * Minimum patience (the compiled `timeoutMs` floor) before the stuck-cut may
70
+ * fire. A slow page that produces no early signal still gets at least this
71
+ * long before being judged stuck.
72
+ */
73
+ minBudgetMs: number;
74
+ /** No-progress window before cutting "stuck". Defaults to `STUCK_WINDOW_MS`. */
75
+ stuckWindowMs?: number;
76
+ /** Watchdog cadence. Defaults to `PROGRESS_POLL_INTERVAL_MS`. */
77
+ pollIntervalMs?: number;
78
+ /**
79
+ * Progress probe. When omitted, the stuck-cut is disabled and the operation
80
+ * simply runs with its compiled floor budget (legacy fixed-timeout behaviour)
81
+ * — graceful degradation for adapters with no progress signal.
82
+ */
83
+ getProgress?: () => Promise<ProgressSnapshot | null>;
84
+ }
85
+ export interface ProgressBudgetResult<T> {
86
+ /** Present when the wrapped operation resolved before any cut. */
87
+ result?: T;
88
+ /** Present when the watchdog cut the wait early. */
89
+ cut?: ProgressBudgetCut;
90
+ /** Total time spent (ms). */
91
+ waitedMs: number;
92
+ }
93
+ /**
94
+ * Run an adaptive wait that extends while the page is making progress and is cut
95
+ * once it is genuinely stuck or the global deadline is reached.
96
+ *
97
+ * `run(budgetMs)` is given a generous budget (up to the global deadline) and is
98
+ * expected to resolve as soon as its condition is met (or to time out at the
99
+ * budget). A concurrent watchdog races it: while `run` is still pending, the
100
+ * watchdog cuts the wait if no progress is observed for `stuckWindowMs` (after
101
+ * `minBudgetMs` has elapsed) or once the global deadline passes. If `run`
102
+ * resolves first, its result is returned and the watchdog is moot.
103
+ */
104
+ export declare function runWithProgressBudget<T>(run: (budgetMs: number) => Promise<T>, options: ProgressBudgetOptions): Promise<ProgressBudgetResult<T>>;