@mjasnikovs/pi-task 0.38.3 → 0.38.5
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.
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import WebSocket from 'ws';
|
|
1
2
|
export type DeepRenderOutcome = {
|
|
2
3
|
outcome: 'pass';
|
|
3
4
|
detail: string;
|
|
@@ -132,6 +133,31 @@ export declare function deriveLegacyFacts(log: SessionRequest[]): Pick<DeepSessi
|
|
|
132
133
|
export declare function judgeDeepSession(f: DeepSessionFacts): DeepRenderOutcome;
|
|
133
134
|
/** Whole-session wall-clock cap, including browser launch (I4). */
|
|
134
135
|
export declare const DEEP_RENDER_TIMEOUT_MS = 45000;
|
|
136
|
+
/** Minimal DevTools-protocol client: request/response ids over one socket, plus
|
|
137
|
+
* event fan-out. Everything the driver needs and nothing more.
|
|
138
|
+
*
|
|
139
|
+
* @internal Exported for its own tests: the id/response matching and the
|
|
140
|
+
* send-failure path are the parts of the driver that need no browser. */
|
|
141
|
+
export declare class Cdp {
|
|
142
|
+
private readonly ws;
|
|
143
|
+
private nextId;
|
|
144
|
+
private readonly pending;
|
|
145
|
+
private readonly handlers;
|
|
146
|
+
constructor(ws: WebSocket);
|
|
147
|
+
on(method: string, cb: (params: Record<string, unknown>) => void): void;
|
|
148
|
+
send(method: string, params?: Record<string, unknown>, sessionId?: string): Promise<Record<string, unknown>>;
|
|
149
|
+
}
|
|
150
|
+
/** Page-side fill: native value setters + input/change events, so a controlled
|
|
151
|
+
* React/Vue/Svelte input actually updates its state (a bare `el.value = …` does
|
|
152
|
+
* not, and the form then submits empty).
|
|
153
|
+
*
|
|
154
|
+
* @internal Exported so the credential ESCAPING is testable: a password with a
|
|
155
|
+
* quote or a backslash in it must not break out of the injected expression. */
|
|
156
|
+
export declare function fillExpr(identifier: string, password: string): string;
|
|
157
|
+
/** Wait until `ms` of silence pass with no new request, or `cap` elapses.
|
|
158
|
+
* @internal Exported for its own tests — the quiet/cap arithmetic decides
|
|
159
|
+
* whether a slow page is judged half-loaded. */
|
|
160
|
+
export declare function settle(lastActivity: () => number, cap: number, quiet?: number): Promise<void>;
|
|
135
161
|
/**
|
|
136
162
|
* Drive one authenticated session against `url` and judge it. `cwd` is the project
|
|
137
163
|
* whose dotenv declares the account. Every failure mode of the DRIVER itself
|
|
@@ -146,4 +172,8 @@ export declare function runDeepRenderCheck(url: string, cwd: string, opts?: {
|
|
|
146
172
|
/** Recorder hook: receives the facts the verdict was made on. Used by the
|
|
147
173
|
* corpus builder; the gate itself never passes it. */
|
|
148
174
|
onFacts?: (f: DeepSessionFacts) => void;
|
|
175
|
+
/** @internal Settle quiet window, for tests only. The three settle windows
|
|
176
|
+
* cost QUIET_MS each against a fake browser that answers instantly, which
|
|
177
|
+
* is the whole runtime of a driver test. The gate never passes it. */
|
|
178
|
+
quietMs?: number;
|
|
149
179
|
}): Promise<DeepRenderOutcome>;
|
|
@@ -348,8 +348,11 @@ const POST_SUBMIT_CAP_MS = 12_000;
|
|
|
348
348
|
* small so the three settle windows together stay inside DEEP_RENDER_TIMEOUT_MS. */
|
|
349
349
|
const RE_NAV_CAP_MS = 6_000;
|
|
350
350
|
/** Minimal DevTools-protocol client: request/response ids over one socket, plus
|
|
351
|
-
* event fan-out. Everything the driver needs and nothing more.
|
|
352
|
-
|
|
351
|
+
* event fan-out. Everything the driver needs and nothing more.
|
|
352
|
+
*
|
|
353
|
+
* @internal Exported for its own tests: the id/response matching and the
|
|
354
|
+
* send-failure path are the parts of the driver that need no browser. */
|
|
355
|
+
export class Cdp {
|
|
353
356
|
ws;
|
|
354
357
|
nextId = 1;
|
|
355
358
|
pending = new Map();
|
|
@@ -418,8 +421,11 @@ const INSPECT_EXPR = `(() => {
|
|
|
418
421
|
})()`;
|
|
419
422
|
/** Page-side fill: native value setters + input/change events, so a controlled
|
|
420
423
|
* React/Vue/Svelte input actually updates its state (a bare `el.value = …` does
|
|
421
|
-
* not, and the form then submits empty).
|
|
422
|
-
|
|
424
|
+
* not, and the form then submits empty).
|
|
425
|
+
*
|
|
426
|
+
* @internal Exported so the credential ESCAPING is testable: a password with a
|
|
427
|
+
* quote or a backslash in it must not break out of the injected expression. */
|
|
428
|
+
export function fillExpr(identifier, password) {
|
|
423
429
|
return `(() => {
|
|
424
430
|
const setValue = (el, v) => {
|
|
425
431
|
const proto = el instanceof HTMLTextAreaElement ? HTMLTextAreaElement.prototype : HTMLInputElement.prototype
|
|
@@ -457,8 +463,10 @@ const SUBMIT_EXPR = `(() => {
|
|
|
457
463
|
if (submit) { submit.click(); return {ok: true, how: 'click'} }
|
|
458
464
|
return {ok: false, reason: 'no submit control'}
|
|
459
465
|
})()`;
|
|
460
|
-
/** Wait until `ms` of silence pass with no new request, or `cap` elapses.
|
|
461
|
-
|
|
466
|
+
/** Wait until `ms` of silence pass with no new request, or `cap` elapses.
|
|
467
|
+
* @internal Exported for its own tests — the quiet/cap arithmetic decides
|
|
468
|
+
* whether a slow page is judged half-loaded. */
|
|
469
|
+
export async function settle(lastActivity, cap, quiet = QUIET_MS) {
|
|
462
470
|
const deadline = Date.now() + cap;
|
|
463
471
|
for (;;) {
|
|
464
472
|
const idleFor = Date.now() - lastActivity();
|
|
@@ -514,7 +522,7 @@ export async function runDeepRenderCheck(url, cwd, opts = {}) {
|
|
|
514
522
|
child = c;
|
|
515
523
|
}, s => {
|
|
516
524
|
socket = s;
|
|
517
|
-
}, opts.onFacts), budget);
|
|
525
|
+
}, opts.onFacts, opts.quietMs), budget);
|
|
518
526
|
}
|
|
519
527
|
catch (e) {
|
|
520
528
|
const why = e instanceof Error ? e.message : String(e);
|
|
@@ -540,7 +548,7 @@ function withTimeout(p, ms) {
|
|
|
540
548
|
});
|
|
541
549
|
});
|
|
542
550
|
}
|
|
543
|
-
async function drive(url, bin, userDataDir, credentials, holdChild, holdSocket, onFacts) {
|
|
551
|
+
async function drive(url, bin, userDataDir, credentials, holdChild, holdSocket, onFacts, quietMs) {
|
|
544
552
|
const origin = new URL(url).origin;
|
|
545
553
|
/** Every verdict goes through here, so a recorder sees the same facts the judge
|
|
546
554
|
* does — the corpus is what the gate itself read, not a reconstruction. */
|
|
@@ -626,7 +634,7 @@ async function drive(url, bin, userDataDir, credentials, holdChild, holdSocket,
|
|
|
626
634
|
return r.result?.value;
|
|
627
635
|
};
|
|
628
636
|
await cdp.send('Page.navigate', { url }, sessionId);
|
|
629
|
-
await settle(() => lastActivity, SETTLE_CAP_MS);
|
|
637
|
+
await settle(() => lastActivity, SETTLE_CAP_MS, quietMs);
|
|
630
638
|
const before = await evaluate(INSPECT_EXPR);
|
|
631
639
|
if (!before)
|
|
632
640
|
throw new Error('the page could not be inspected');
|
|
@@ -698,7 +706,7 @@ async function drive(url, bin, userDataDir, credentials, holdChild, holdSocket,
|
|
|
698
706
|
if (!submitted?.ok)
|
|
699
707
|
return judge(unsubmitted({ submitted: false }));
|
|
700
708
|
lastActivity = Date.now();
|
|
701
|
-
await settle(() => lastActivity, POST_SUBMIT_CAP_MS);
|
|
709
|
+
await settle(() => lastActivity, POST_SUBMIT_CAP_MS, quietMs);
|
|
702
710
|
// The sign-in request: the first same-origin non-GET issued by the submit. Its
|
|
703
711
|
// own 2xx is the precondition for judging anything, and it is excluded from the
|
|
704
712
|
// data evidence (STEP 0: the broken build satisfies "≥1 same-origin 2xx" with
|
|
@@ -731,7 +739,7 @@ async function drive(url, bin, userDataDir, credentials, holdChild, holdSocket,
|
|
|
731
739
|
if (authAccepted && leftAuthWall) {
|
|
732
740
|
await cdp.send('Page.navigate', { url }, sessionId);
|
|
733
741
|
lastActivity = Date.now();
|
|
734
|
-
await settle(() => lastActivity, RE_NAV_CAP_MS);
|
|
742
|
+
await settle(() => lastActivity, RE_NAV_CAP_MS, quietMs);
|
|
735
743
|
}
|
|
736
744
|
return judge(facts(sessionLog(authId, authAt), {
|
|
737
745
|
submitted: true,
|
package/dist/task/gate-deps.d.ts
CHANGED
|
@@ -38,6 +38,19 @@ export declare function collectChangedFiles(cwd: string, signal?: AbortSignal):
|
|
|
38
38
|
* never a blocker. The `.pi-tasks/` bookkeeping is excluded from every git command.
|
|
39
39
|
*/
|
|
40
40
|
export declare function collectAddedLines(cwd: string, signal?: AbortSignal): Promise<AddedLine[]>;
|
|
41
|
+
/**
|
|
42
|
+
* Deterministic sandbox-path-leak pass (see foreign-path.ts, mx5 run 13 PROMPT 4
|
|
43
|
+
* item 1): find absolute paths the task committed that resolve nowhere on this
|
|
44
|
+
* machine while the real file sits in the repo, REPAIR the ones whose relative
|
|
45
|
+
* form provably resolves, and return verify findings for whatever is left.
|
|
46
|
+
*
|
|
47
|
+
* The repair runs here, before the verify child, for the same reason lint-fix
|
|
48
|
+
* does: the defect is mechanical and the correct target is already known, so
|
|
49
|
+
* spending an AUTOFIX round (or a human) on a path substitution is waste. What it
|
|
50
|
+
* cannot repair still reaches the child under rule 4e. Failures degrade to no
|
|
51
|
+
* findings — a sharpener, never a blocker.
|
|
52
|
+
*/
|
|
53
|
+
export declare function collectForeignPathFindings(cwd: string, signal?: AbortSignal, logDebug?: (m: string) => void): Promise<string[]>;
|
|
41
54
|
/**
|
|
42
55
|
* Deterministic neutered-check-script pass (see script-escape.ts, mx5 run 13 PROMPT
|
|
43
56
|
* 4 item 4): check-class scripts that cannot report failure, in a manifest THIS
|
package/dist/task/gate-deps.js
CHANGED
|
@@ -61,6 +61,18 @@ export function truncateToolResult(text, limit = TOOL_RESULT_LOG_LIMIT) {
|
|
|
61
61
|
}
|
|
62
62
|
/** Keep the gate machinery's own artifacts out of every git pathspec below. */
|
|
63
63
|
const EXCLUDE_TASKS_DIR = ':(exclude).pi-tasks';
|
|
64
|
+
/**
|
|
65
|
+
* Pin the diff header prefixes on any command whose output we PARSE for paths.
|
|
66
|
+
*
|
|
67
|
+
* `parseAddedLines` reads the file out of the `+++ b/…` header, but the prefix is
|
|
68
|
+
* user-configurable: `diff.mnemonicPrefix=true` emits `i/` and `w/` instead of
|
|
69
|
+
* `a/` and `b/`, and `diff.noprefix=true` emits none. Both are common developer
|
|
70
|
+
* settings, and under either one every added line was attributed to a path like
|
|
71
|
+
* `w/src/app.ts`, which exists nowhere — so the probe-gaming and sandbox-path
|
|
72
|
+
* probes silently read a diff of files that are not in the repo. Passing the
|
|
73
|
+
* prefixes explicitly makes the output independent of the host's git config.
|
|
74
|
+
*/
|
|
75
|
+
const DIFF_PREFIX_ARGS = ['--src-prefix=a/', '--dst-prefix=b/'];
|
|
64
76
|
const splitLines = (s) => s
|
|
65
77
|
.split('\n')
|
|
66
78
|
.map(l => l.trim())
|
|
@@ -110,7 +122,7 @@ export async function collectChangedFiles(cwd, signal) {
|
|
|
110
122
|
* never a blocker. The `.pi-tasks/` bookkeeping is excluded from every git command.
|
|
111
123
|
*/
|
|
112
124
|
export async function collectAddedLines(cwd, signal) {
|
|
113
|
-
const tracked = await git(cwd, ['diff', 'HEAD', '--', '.', EXCLUDE_TASKS_DIR], signal);
|
|
125
|
+
const tracked = await git(cwd, ['diff', ...DIFF_PREFIX_ARGS, 'HEAD', '--', '.', EXCLUDE_TASKS_DIR], signal);
|
|
114
126
|
const lines = tracked.exitCode === 0 ? parseAddedLines(tracked.stdout) : [];
|
|
115
127
|
const untracked = await git(cwd, ['ls-files', '--others', '--exclude-standard', '--', '.', EXCLUDE_TASKS_DIR], signal);
|
|
116
128
|
for (const name of splitLines(untracked.exitCode === 0 ? untracked.stdout : '')) {
|
|
@@ -124,7 +136,7 @@ export async function collectAddedLines(cwd, signal) {
|
|
|
124
136
|
}
|
|
125
137
|
}
|
|
126
138
|
if (lines.length === 0) {
|
|
127
|
-
const last = await git(cwd, ['diff', 'HEAD~1..HEAD', '--', '.', EXCLUDE_TASKS_DIR], signal);
|
|
139
|
+
const last = await git(cwd, ['diff', ...DIFF_PREFIX_ARGS, 'HEAD~1..HEAD', '--', '.', EXCLUDE_TASKS_DIR], signal);
|
|
128
140
|
return last.exitCode === 0 ? parseAddedLines(last.stdout) : [];
|
|
129
141
|
}
|
|
130
142
|
return lines;
|
|
@@ -141,7 +153,7 @@ export async function collectAddedLines(cwd, signal) {
|
|
|
141
153
|
* cannot repair still reaches the child under rule 4e. Failures degrade to no
|
|
142
154
|
* findings — a sharpener, never a blocker.
|
|
143
155
|
*/
|
|
144
|
-
async function collectForeignPathFindings(cwd, signal, logDebug) {
|
|
156
|
+
export async function collectForeignPathFindings(cwd, signal, logDebug) {
|
|
145
157
|
const lines = await collectAddedLines(cwd, signal);
|
|
146
158
|
if (lines.length === 0)
|
|
147
159
|
return [];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mjasnikovs/pi-task",
|
|
3
|
-
"version": "0.38.
|
|
3
|
+
"version": "0.38.5",
|
|
4
4
|
"description": "Deterministic task planning and spec-orchestration for local models — crash-safe /task pipelines with verify/enforce gates, a real-time remote web view, and web/docs/fetch/worker subagent tools.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|