@mjasnikovs/pi-task 0.40.35 → 0.40.37

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.
@@ -9,6 +9,8 @@ export declare const CHILD_BASE_ARGS: readonly ["--print", "--no-skills", "--no-
9
9
  export interface WritableLike {
10
10
  write(chunk: string): boolean;
11
11
  end(): void;
12
+ /** Optional so a mock stdin stays two methods; a real pipe always has it. */
13
+ on?(event: 'error', listener: (err: unknown) => void): unknown;
12
14
  }
13
15
  export interface ProcLike extends EventEmitter {
14
16
  /** Present only when the child was spawned with stdin 'pipe' (prompt delivery). */
@@ -236,6 +236,10 @@ export function runChild(spawn, invocation, cwd, signal, opts) {
236
236
  ...(invocation.env ? { env: invocation.env } : {})
237
237
  });
238
238
  if (usesStdin) {
239
+ // A child killed before it read the prompt leaves the pipe broken, and an
240
+ // unhandled EPIPE on stdin takes the whole process down. The child's own
241
+ // exit is what reports that run; this write has nothing left to say.
242
+ proc.stdin?.on?.('error', () => { });
239
243
  // pi reads the prompt from stdin and waits for EOF, so write then end.
240
244
  proc.stdin?.write(invocation.stdin);
241
245
  proc.stdin?.end();
@@ -113,9 +113,9 @@ export interface DeepSessionFacts {
113
113
  * The three request-shaped facts, computed from the log and from nothing else. The
114
114
  * driver records `sessionRequests` and calls this; the values are exactly what the
115
115
  * pre-log driver computed by filtering the same map (the sign-in request is the
116
- * first same-origin non-GET issued at or after the submit; the data requests are the
117
- * same-origin
118
- * XHR/fetch issued at or after the submit, the sign-in request itself excluded).
116
+ * first same-origin non-GET issued at or after the submit; the data requests are
117
+ * the same-origin XHR/fetch issued at or after the sign-in request, that request
118
+ * itself excluded).
119
119
  */
120
120
  export declare function deriveLegacyFacts(log: SessionRequest[]): Pick<DeepSessionFacts, 'authRequest' | 'postAuthDataAttempted' | 'postAuthData2xx'>;
121
121
  /**
@@ -202,9 +202,9 @@ export function pinnedLocalPort(vars) {
202
202
  * The three request-shaped facts, computed from the log and from nothing else. The
203
203
  * driver records `sessionRequests` and calls this; the values are exactly what the
204
204
  * pre-log driver computed by filtering the same map (the sign-in request is the
205
- * first same-origin non-GET issued at or after the submit; the data requests are the
206
- * same-origin
207
- * XHR/fetch issued at or after the submit, the sign-in request itself excluded).
205
+ * first same-origin non-GET issued at or after the submit; the data requests are
206
+ * the same-origin XHR/fetch issued at or after the sign-in request, that request
207
+ * itself excluded).
208
208
  */
209
209
  export function deriveLegacyFacts(log) {
210
210
  const auth = log.find(r => r.phase === 'auth') ?? null;
@@ -659,10 +659,15 @@ export async function driveSession(cdp, { url, credentials, judge, quietMs }) {
659
659
  // judges it is the chain's last, so the first hop stays and the eventual
660
660
  // response lands on it. Overwriting reads a POST that 302s as a GET, and
661
661
  // then no sign-in request is ever found.
662
- if (p.redirectResponse === undefined || !requests.has(id)) {
663
- const req = p.request;
662
+ const req = p.request;
663
+ const existing = requests.get(id);
664
+ if (p.redirectResponse !== undefined && existing) {
665
+ existing.finalUrl = String(req?.url ?? existing.finalUrl);
666
+ }
667
+ else {
664
668
  requests.set(id, {
665
669
  url: String(req?.url ?? ''),
670
+ finalUrl: String(req?.url ?? ''),
666
671
  method: String(req?.method ?? 'GET'),
667
672
  type: String(p.type ?? ''),
668
673
  status: null,
@@ -706,15 +711,15 @@ export async function driveSession(cdp, { url, credentials, judge, quietMs }) {
706
711
  const before = await evaluate(INSPECT_EXPR);
707
712
  if (!before)
708
713
  throw new Error('the page could not be inspected');
709
- const sameOrigin = (r) => r.url.startsWith(`${origin}/`) || r.url === origin;
714
+ const sameOrigin = (r) => r.finalUrl.startsWith(`${origin}/`) || r.finalUrl === origin;
710
715
  const isData = (r) => r.type === 'XHR' || r.type === 'Fetch';
711
716
  const foreignOriginFailures = () => {
712
717
  const out = new Set();
713
718
  for (const r of requests.values()) {
714
- if (sameOrigin(r) || !r.failed || !r.url.startsWith('http'))
719
+ if (sameOrigin(r) || !r.failed || !r.finalUrl.startsWith('http'))
715
720
  continue;
716
721
  try {
717
- out.add(new URL(r.url).origin);
722
+ out.add(new URL(r.finalUrl).origin);
718
723
  }
719
724
  catch {
720
725
  // unparseable url — nothing to name
@@ -725,10 +730,11 @@ export async function driveSession(cdp, { url, credentials, judge, quietMs }) {
725
730
  const initiatorOf = (r) => isData(r) ? 'xhr'
726
731
  : r.type === 'Document' ? 'document'
727
732
  : 'other';
728
- /** The same-origin request log, phased against the submit. `postSeq` is the
729
- * first `seq` the submit could issue — Infinity while no submit has happened —
730
- * so before a submit every request so far is 'pre'; the sign-in request sits at
731
- * or after the boundary but is 'auth', not 'post'. */
733
+ /** The same-origin request log. `postSeq` is where the authenticated half
734
+ * begins — Infinity while no submit has happened — so everything before it is
735
+ * 'pre'; the sign-in request itself is 'auth', not 'post'. The boundary is the
736
+ * sign-in request, not the submit: a CSRF token or a beacon the submit fires
737
+ * BEFORE the login is not evidence about the authenticated client. */
732
738
  const sessionLog = (authId, postSeq) => [...requests]
733
739
  .filter(([, r]) => sameOrigin(r))
734
740
  .map(([id, r]) => ({
@@ -759,6 +765,7 @@ export async function driveSession(cdp, { url, credentials, judge, quietMs }) {
759
765
  const unsubmitted = (over) => facts(sessionLog(null, Number.POSITIVE_INFINITY), over);
760
766
  if (!before.hasPassword || credentials === null)
761
767
  return judge(unsubmitted({}));
768
+ const fillSeq = nextSeq;
762
769
  const filled = await evaluate(fillExpr(credentials.identifier, credentials.password));
763
770
  if (!filled?.ok)
764
771
  return judge(unsubmitted({ submitted: false }));
@@ -784,6 +791,18 @@ export async function driveSession(cdp, { url, credentials, judge, quietMs }) {
784
791
  break;
785
792
  }
786
793
  }
794
+ // Nothing after the submit: a form that submits from the fill's own input
795
+ // events already sent it. Only a NAVIGATION qualifies here — a form submission
796
+ // is one, and the background traffic the fill races (beacons, telemetry) is
797
+ // XHR or fetch, never a document.
798
+ if (authId === null) {
799
+ for (const [id, r] of requests) {
800
+ if (r.seq >= fillSeq && sameOrigin(r) && r.method !== 'GET' && r.type === 'Document') {
801
+ authId = id;
802
+ break;
803
+ }
804
+ }
805
+ }
787
806
  const authReq = authId === null ? null : requests.get(authId);
788
807
  const now = await evaluate(INSPECT_EXPR);
789
808
  const domJudgment = judgeRenderedDom(now?.html ?? '');
@@ -805,7 +824,7 @@ export async function driveSession(cdp, { url, credentials, judge, quietMs }) {
805
824
  lastActivity = Date.now();
806
825
  await settle(() => lastActivity, RE_NAV_CAP_MS, quietMs);
807
826
  }
808
- return judge(facts(sessionLog(authId, submitSeq), {
827
+ return judge(facts(sessionLog(authId, authReq?.seq ?? submitSeq), {
809
828
  submitted: true,
810
829
  foreignOriginFailures: foreignOriginFailures(),
811
830
  leftAuthWall,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mjasnikovs/pi-task",
3
- "version": "0.40.35",
3
+ "version": "0.40.37",
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",