@proagentstore/cli 0.4.133 → 0.4.135

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.
@@ -94,6 +94,7 @@ export const INSPECT_PAGE = `() => {
94
94
  unavailable: new RegExp(UNAVAILABLE_TEXT_SOURCE, "i").test(text) ? (new RegExp(EXPIRED_TEXT_SOURCE, "i").test(text) ? "expired" : "unavailable") : null,
95
95
  };
96
96
  }`;
97
+ const signInQuestion = (site) => `Sign in to ${site} on this machine's application browser, then retry. Nothing was submitted.`;
97
98
  const text = (t, isError = false) => ({ content: [{ type: "text", text: t }], ...(isError ? { isError: true } : {}) });
98
99
  const textOf = (r) => (r.content ?? []).map((c) => c.text ?? "").join("\n");
99
100
  const escapeRe = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
@@ -283,7 +284,9 @@ export class ApplyBridge {
283
284
  if (!v || typeof v.url !== "string")
284
285
  return null;
285
286
  const unavailable = v.unavailable === "expired" || v.unavailable === "unavailable" ? v.unavailable : null;
286
- return { url: v.url, title: String(v.title ?? ""), captcha: !!v.captcha, login: !!v.login, duplicate: !!v.duplicate, antiBot: !!v.antiBot, confirmed: !!v.confirmed, unavailable };
287
+ // A sign-in page by its ADDRESS too (#1100): SEEK's `login.seek.com/login` has no password field, so an
288
+ // "Apply" that bounced there ran on to `awaiting_review` with nothing filled. Same rule as the submit path.
289
+ return { url: v.url, title: String(v.title ?? ""), captcha: !!v.captcha, login: looksLikeSignIn(v.url, !!v.login), duplicate: !!v.duplicate, antiBot: !!v.antiBot, confirmed: !!v.confirmed, unavailable };
287
290
  }
288
291
  blockerOf(s) {
289
292
  return s.captcha ? "captcha" : s.antiBot ? "anti_bot" : s.duplicate ? "duplicate_application" : s.login ? "login_required" : null;
@@ -314,15 +317,19 @@ export class ApplyBridge {
314
317
  const blocker = this.blockerOf(state);
315
318
  if (!blocker)
316
319
  return text(toolText);
320
+ return this.holdFor(blocker, state);
321
+ }
322
+ /** A page that needs a person: pause for the owner, and remember a sign-in they complete (#1094). */
323
+ async holdFor(blocker, state) {
324
+ const host = hostOf(state.url) ?? "";
317
325
  this.host.emit({ type: "browser.blocked", url: state.url, domain: host, detail: { reason: blocker } });
318
326
  const site = blocker === "login_required" && host ? siteKeyOf(host) : null;
319
327
  if (site)
320
328
  await this.host.sessions?.beginSignIn(site).catch(() => undefined);
321
- const ok = await this.pauseFor({ reason: blocker, url: state.url, domain: host }, async () => {
329
+ const ok = await this.pauseFor({ reason: blocker, url: state.url, domain: host, ...(site ? { question: signInQuestion(site) } : {}) }, async () => {
322
330
  const after = await this.inspect();
323
331
  return !!after && this.blockerOf(after) === null;
324
332
  });
325
- // A sign-in the owner completed here is remembered for this browser profile (#1094).
326
333
  if (ok && site)
327
334
  await this.recordSignIn(site);
328
335
  if (!ok)
@@ -533,6 +540,12 @@ export class ApplyBridge {
533
540
  if (!phase)
534
541
  return text("supervisor_checkpoint needs phase initial, post_navigation, before_submit or uncertain.", true);
535
542
  const state = await this.inspect();
543
+ // A sign-in reached after the last landing — a late redirect (#1100, run 804bed49): hold for the owner as
544
+ // on landing. Reported to the cloud as a blocker it was stopped, and ended `incomplete`.
545
+ if (state?.login && !this.blocked) {
546
+ const held = await this.holdFor("login_required", state);
547
+ return held.isError ? held : text("The owner signed in. Take a browser_snapshot to read the page, then call supervisor_checkpoint again.");
548
+ }
536
549
  const blockers = new Set();
537
550
  if (state?.captcha)
538
551
  blockers.add("captcha");
@@ -588,8 +601,16 @@ export class ApplyBridge {
588
601
  this.host.emit({ type: "job.unavailable", url, domain, detail: { reason, source: "page_notice" } });
589
602
  return text(`Recorded the runner-verified ${reason} listing notice. Stop now.`);
590
603
  }
591
- review(summary) {
604
+ async review(summary) {
592
605
  this.summary = summary; // bounded where the result is built (runtime.ts `end`)
606
+ // Nothing entered, on a sign-in page (#1100; runs abb37433, 7fb1e4f5): the outcome is the owner's sign-in,
607
+ // not an empty form "awaiting review" — a card that is not `login_required` offers them no sign-in at all.
608
+ const state = !this.filled && !this.uploaded.size && !this.blocked ? await this.inspect() : null;
609
+ const host = state?.login ? hostOf(state.url) : null;
610
+ if (state && host) {
611
+ this.host.emit({ type: "browser.blocked", url: state.url, domain: host, detail: { reason: "login_required" } });
612
+ this.blocked = { reason: "login_required", questions: [signInQuestion(siteKeyOf(host))] };
613
+ }
593
614
  // #989: "ready for review" with nothing entered is not a review state.
594
615
  //
595
616
  // The live run ended `awaiting_review` with `filled: 0, uploaded: 0` seconds after the cloud
@@ -655,7 +676,7 @@ export class ApplyBridge {
655
676
  ],
656
677
  };
657
678
  }
658
- this.review(oneClick ? "Stopped at a one-click apply control: it can send the application outright, and this run fills and waits for review." : "Stopped at the final submit: this run fills and waits for review.");
679
+ await this.review(oneClick ? "Stopped at a one-click apply control: it can send the application outright, and this run fills and waits for review." : "Stopped at the final submit: this run fills and waits for review.");
659
680
  return text(oneClick
660
681
  ? 'That control ("' + String(forward.element ?? "") + '") can send the application in one click, and this run is fill-and-review: it was NOT pressed. Approve this application if you want it sent. Stop now.'
661
682
  : "That control submits the application, and this run is fill-and-review: it was NOT pressed. The application waits for the owner's review. Stop now.");
@@ -37,29 +37,6 @@ export const LOCAL_APPLY_CANCEL_PATH = "/local-apply/cancel";
37
37
  export const LOCAL_APPLY_PREFLIGHT_PATH = "/local-apply/preflight";
38
38
  /** Delivers a cloud-persisted supervisory decision to one paused checkpoint. */
39
39
  export const LOCAL_APPLY_DIRECTIVE_PATH = "/local-apply/directive";
40
- /**
41
- * A short-lived, owner-authorized view of the *existing* browser page for one local-apply run.
42
- * The id is opaque and carries no browser/profile material. It deliberately is not a task id:
43
- * local-apply runs are not generic runner tasks.
44
- */
45
- export const LOCAL_APPLY_HANDOFF_PATH = "/local-apply/handoff";
46
- export const LOCAL_APPLY_HANDOFF_STATUS_PATH = "/local-apply/handoff-status";
47
- export const LOCAL_APPLY_HANDOFF_FRAME_PATH = "/local-apply/handoff/frame";
48
- export const LOCAL_APPLY_HANDOFF_INPUT_PATH = "/local-apply/handoff/input";
49
- export const LOCAL_APPLY_HANDOFF_RESUME_PATH = "/local-apply/handoff/resume";
50
- export const LOCAL_APPLY_HANDOFF_END_PATH = "/local-apply/handoff/end";
51
- /**
52
- * A separate, browser-only receipt/history check for an already-ended uncertain attempt. This is
53
- * deliberately not an application run: it receives no engine, sources, artifacts or submit gate.
54
- */
55
- export const LOCAL_APPLY_RECONCILIATION_RUN_PATH = "/local-apply/reconciliation/run";
56
- export const LOCAL_APPLY_RECONCILIATION_STATUS_PATH = "/local-apply/reconciliation/status";
57
- export const LOCAL_APPLY_RECONCILIATION_HANDOFF_PATH = "/local-apply/reconciliation/handoff";
58
- export const LOCAL_APPLY_RECONCILIATION_HANDOFF_STATUS_PATH = "/local-apply/reconciliation/handoff-status";
59
- export const LOCAL_APPLY_RECONCILIATION_HANDOFF_FRAME_PATH = "/local-apply/reconciliation/handoff/frame";
60
- export const LOCAL_APPLY_RECONCILIATION_HANDOFF_INPUT_PATH = "/local-apply/reconciliation/handoff/input";
61
- export const LOCAL_APPLY_RECONCILIATION_HANDOFF_RESUME_PATH = "/local-apply/reconciliation/handoff/resume";
62
- export const LOCAL_APPLY_RECONCILIATION_HANDOFF_END_PATH = "/local-apply/reconciliation/handoff/end";
63
40
  export const LOCAL_APPLY_ENGINES = ["claude", "codex"];
64
41
  export const LOCAL_APPLY_AUTH_MODES = ["machine", "subscription"];
65
42
  export const LOCAL_APPLY_MODES = ["fill_and_review", "auto_submit"];
@@ -166,7 +143,6 @@ export const LOCAL_APPLY_EVENT_TYPES = [
166
143
  export const LOCAL_APPLY_ENGINE_AUTHS = ["machine-login", "subscription", "api-key", "missing_login", "unknown"];
167
144
  export const LOCAL_APPLY_SUPERVISOR_DIRECTIVES = ["continue", "request_review", "stop"];
168
145
  export const LOCAL_APPLY_SUPERVISOR_PHASES = ["initial", "before_submit", "post_navigation", "uncertain"];
169
- export const LOCAL_APPLY_HANDOFF_TERMINAL_REASONS = ["unavailable", "profile_unavailable", "profile_mismatch", "page_lost", "expired", "run_ended", "runner_restarted"];
170
146
  export const LOCAL_APPLY_CAPS = { questions: 20, questionChars: 300, answers: 20, answerChars: 2000, summary: 2000 };
171
147
  const str = (v, max) => (typeof v === "string" && v.trim() ? v.trim().slice(0, max) : null);
172
148
  const oneOf = (list, v) => (list.includes(v) ? v : null);
@@ -0,0 +1,34 @@
1
+ /**
2
+ * A short-lived, owner-authorized view of the *existing* browser page for one local-apply run.
3
+ * The id is opaque and carries no browser/profile material. It deliberately is not a task id:
4
+ * local-apply runs are not generic runner tasks.
5
+ */
6
+ export const LOCAL_APPLY_HANDOFF_PATH = "/local-apply/handoff";
7
+ export const LOCAL_APPLY_HANDOFF_STATUS_PATH = "/local-apply/handoff-status";
8
+ export const LOCAL_APPLY_HANDOFF_FRAME_PATH = "/local-apply/handoff/frame";
9
+ export const LOCAL_APPLY_HANDOFF_INPUT_PATH = "/local-apply/handoff/input";
10
+ export const LOCAL_APPLY_HANDOFF_RESUME_PATH = "/local-apply/handoff/resume";
11
+ export const LOCAL_APPLY_HANDOFF_END_PATH = "/local-apply/handoff/end";
12
+ /**
13
+ * A separate, browser-only receipt/history check for an already-ended uncertain attempt. This is
14
+ * deliberately not an application run: it receives no engine, sources, artifacts or submit gate.
15
+ */
16
+ export const LOCAL_APPLY_RECONCILIATION_RUN_PATH = "/local-apply/reconciliation/run";
17
+ export const LOCAL_APPLY_RECONCILIATION_STATUS_PATH = "/local-apply/reconciliation/status";
18
+ export const LOCAL_APPLY_RECONCILIATION_HANDOFF_PATH = "/local-apply/reconciliation/handoff";
19
+ export const LOCAL_APPLY_RECONCILIATION_HANDOFF_STATUS_PATH = "/local-apply/reconciliation/handoff-status";
20
+ export const LOCAL_APPLY_RECONCILIATION_HANDOFF_FRAME_PATH = "/local-apply/reconciliation/handoff/frame";
21
+ export const LOCAL_APPLY_RECONCILIATION_HANDOFF_INPUT_PATH = "/local-apply/reconciliation/handoff/input";
22
+ export const LOCAL_APPLY_RECONCILIATION_HANDOFF_RESUME_PATH = "/local-apply/reconciliation/handoff/resume";
23
+ export const LOCAL_APPLY_RECONCILIATION_HANDOFF_END_PATH = "/local-apply/reconciliation/handoff/end";
24
+ /**
25
+ * A sign-in to the job's site in this machine's application browser profile, AFTER a fill ended
26
+ * `blocked` on `login_required` (#1100). Not a run and not a run's handoff: there is no engine, no
27
+ * source, no artifact and no submit gate — only the profile's page at the job's site, for the owner
28
+ * to sign in, and the profile's sign-in ledger (#1094) to record that they did.
29
+ */
30
+ export const LOCAL_APPLY_SIGN_IN_PATH = "/local-apply/sign-in";
31
+ export const LOCAL_APPLY_SIGN_IN_FRAME_PATH = "/local-apply/sign-in/frame";
32
+ export const LOCAL_APPLY_SIGN_IN_INPUT_PATH = "/local-apply/sign-in/input";
33
+ export const LOCAL_APPLY_SIGN_IN_END_PATH = "/local-apply/sign-in/end";
34
+ export const LOCAL_APPLY_HANDOFF_TERMINAL_REASONS = ["unavailable", "profile_unavailable", "profile_mismatch", "page_lost", "expired", "run_ended", "runner_restarted"];
@@ -0,0 +1,218 @@
1
+ /**
2
+ * A post-run site sign-in in this machine's application browser profile (#1100).
3
+ *
4
+ * #1094 made the profile persistent and pauses a fill `login_required` before an uncertain press, so
5
+ * the owner signs in once through the fill's handoff. But that handoff exists only while the fill is
6
+ * paused. The reported run (9a7d9e85) paused at 11:32, nobody reached it before the run's 20-minute
7
+ * limit, and it ended `blocked` at 11:53; the owner asked to take over at 11:57 — when there was no
8
+ * paused fill, no retained page, and so nothing to stream. Every later fill would bounce off the same
9
+ * sign-in, because the only way to sign in had closed with the run.
10
+ *
11
+ * This opens the profile itself at the job's site, with no run: the owner signs in there, and `end`
12
+ * with `complete` records the sign-in in the profile's ledger (names and expiry only) so the next
13
+ * fill's session gate passes. It has no engine, no bridge, no source and no submit gate — the page is
14
+ * for a person, and nothing here fills or presses anything.
15
+ */
16
+ import { join } from "node:path";
17
+ import { RunnerInputError } from "../errors.js";
18
+ import { siteKeyOf } from "./site-session.js";
19
+ const SAFE_ID = /^[A-Za-z0-9_.:-]{1,300}$/;
20
+ /** As long as the durable takeover it serves (`TAKEOVER_TTL_MS` in the Worker). */
21
+ const TTL_MS = 60 * 60_000;
22
+ export class LocalApplySignInRuntime {
23
+ deps;
24
+ sessions = new Map();
25
+ now;
26
+ schedule;
27
+ constructor(deps) {
28
+ this.deps = deps;
29
+ this.now = deps.now ?? (() => Date.now());
30
+ this.schedule = deps.schedule ?? ((task, delayMs) => {
31
+ const timer = setTimeout(task, delayMs);
32
+ return () => clearTimeout(timer);
33
+ });
34
+ }
35
+ /**
36
+ * Open the profile at the job's site, or read back the one already open under this id. Idempotent,
37
+ * so the cloud may call it whenever a frame finds no live page: a page the owner closed is reported
38
+ * closed, and a runner restart (nothing in memory) simply opens it again.
39
+ */
40
+ async open(raw) {
41
+ const request = parseRequest(raw);
42
+ const existing = this.sessions.get(request.signInId);
43
+ if (existing) {
44
+ if (existing.request.browserProfile !== request.browserProfile || existing.request.url !== request.url)
45
+ throw new RunnerInputError("The sign-in id is already bound to another profile or site", 409);
46
+ if (existing.state === "ready" && !(await this.pageLive(existing)))
47
+ await this.close(existing, "page_lost");
48
+ // A finished sign-in is an answer and is read back; one that closed without one (the owner
49
+ // closed the tab, the deadline passed) opens again — the cloud asks only under a live takeover.
50
+ if (existing.state === "ready" || existing.signedIn)
51
+ return this.view(existing);
52
+ this.sessions.delete(request.signInId);
53
+ }
54
+ for (const [id, s] of this.sessions)
55
+ if (s.state === "closed" && s.expiresAt <= this.now())
56
+ this.sessions.delete(id);
57
+ if ([...this.sessions.values()].some((s) => s.state === "ready" && s.request.browserProfile === request.browserProfile))
58
+ throw new RunnerInputError("Another sign-in is already open in this application browser; finish it first.", 409);
59
+ // The `default` profile's page is the runner's own browser page; never navigate it from under a run.
60
+ if (this.deps.busy())
61
+ throw new RunnerInputError("An application run is using this machine's application browser; sign in when it ends.", 409);
62
+ const site = siteKeyOf(new URL(request.url).hostname);
63
+ const takeover = this.deps.takeover;
64
+ let browser;
65
+ try {
66
+ if (!takeover)
67
+ throw new Error("no takeover");
68
+ browser = await this.deps.browserFor(request.browserProfile, join(this.deps.dataDir, "sign-in", request.signInId));
69
+ }
70
+ catch {
71
+ return this.closedView(request, site, "profile_unavailable");
72
+ }
73
+ const session = { request, site, browser, state: "ready", expiresAt: this.now() + TTL_MS };
74
+ this.sessions.set(request.signInId, session);
75
+ session.cancelDeadline = this.schedule(() => void this.close(session, "expired"), TTL_MS);
76
+ const page = this.page(session);
77
+ if (!page) {
78
+ await this.close(session, "profile_unavailable");
79
+ return this.view(session);
80
+ }
81
+ await browser.sessions?.beginSignIn(site).catch(() => undefined);
82
+ // A slow or redirecting site still leaves a page the owner can use; only a lost page is terminal.
83
+ await page.goto(request.url, { waitUntil: "domcontentloaded", timeout: 20_000 }).catch(() => undefined);
84
+ try {
85
+ await takeover.open({ handoffId: request.signInId }, page);
86
+ }
87
+ catch {
88
+ await this.close(session, "page_lost");
89
+ }
90
+ return this.view(session);
91
+ }
92
+ async frame(raw) {
93
+ const session = await this.live(raw);
94
+ try {
95
+ return await this.deps.takeover.frame(session.request.signInId);
96
+ }
97
+ catch {
98
+ await this.close(session, "page_lost");
99
+ throw new RunnerInputError("The sign-in page is no longer available", 409);
100
+ }
101
+ }
102
+ async input(raw) {
103
+ const o = raw && typeof raw === "object" ? raw : {};
104
+ const session = await this.live(o);
105
+ if (!o.input || typeof o.input !== "object")
106
+ throw new RunnerInputError("sign-in input is required");
107
+ try {
108
+ await this.deps.takeover.input(session.request.signInId, o.input);
109
+ }
110
+ catch {
111
+ await this.close(session, "page_lost");
112
+ throw new RunnerInputError("The sign-in page is no longer available", 409);
113
+ }
114
+ }
115
+ /** Close it. `complete: true` is the owner saying they signed in: the ledger records it first. */
116
+ async end(raw) {
117
+ const o = raw && typeof raw === "object" ? raw : {};
118
+ const session = this.require(o);
119
+ if (session.state === "ready" && o.complete === true) {
120
+ const recorded = await session.browser.sessions?.completeSignIn(session.site).catch(() => null);
121
+ if (recorded)
122
+ session.signedIn = recorded;
123
+ }
124
+ await this.close(session, session.signedIn ? undefined : "unavailable");
125
+ return this.view(session);
126
+ }
127
+ liveWork() {
128
+ return [...this.sessions.values()].filter((s) => s.state === "ready").map((s) => `sign-in:${s.request.signInId}`);
129
+ }
130
+ closeAll() {
131
+ for (const session of this.sessions.values())
132
+ void this.close(session, "runner_restarted");
133
+ }
134
+ async close(session, reason) {
135
+ if (session.state === "closed")
136
+ return;
137
+ // Terminal synchronously, so a deadline racing an `end` cannot tear down twice.
138
+ session.state = "closed";
139
+ session.terminalReason = reason;
140
+ session.cancelDeadline?.();
141
+ session.cancelDeadline = undefined;
142
+ await this.deps.takeover?.end(session.request.signInId).catch(() => undefined);
143
+ await session.browser.stop().catch(() => undefined);
144
+ }
145
+ async live(raw) {
146
+ const session = this.require(raw);
147
+ if (session.state === "ready" && session.expiresAt <= this.now())
148
+ await this.close(session, "expired");
149
+ if (session.state === "ready" && !(await this.pageLive(session)))
150
+ await this.close(session, "page_lost");
151
+ if (session.state !== "ready")
152
+ throw new RunnerInputError(`The sign-in is closed (${session.terminalReason ?? "finished"})`, 409);
153
+ return session;
154
+ }
155
+ async pageLive(session) {
156
+ return !!this.page(session) && (await this.deps.takeover?.state(session.request.signInId)) === "ready";
157
+ }
158
+ page(session) {
159
+ const page = session.browser.handoffPage?.();
160
+ return page && !page.isClosed() ? page : null;
161
+ }
162
+ require(raw) {
163
+ const id = raw?.signInId;
164
+ if (typeof id !== "string" || !SAFE_ID.test(id))
165
+ throw new RunnerInputError("signInId is required");
166
+ const session = this.sessions.get(id);
167
+ if (!session)
168
+ throw new RunnerInputError("Sign-in not found", 404);
169
+ return session;
170
+ }
171
+ view(session) {
172
+ return {
173
+ signInId: session.request.signInId,
174
+ browserProfile: session.request.browserProfile,
175
+ site: session.site,
176
+ state: session.state,
177
+ expiresAt: new Date(session.expiresAt).toISOString(),
178
+ ...(session.terminalReason ? { terminalReason: session.terminalReason } : {}),
179
+ ...(session.signedIn ? { signedIn: session.signedIn } : {}),
180
+ };
181
+ }
182
+ closedView(request, site, terminalReason) {
183
+ return { signInId: request.signInId, browserProfile: request.browserProfile, site, state: "closed", expiresAt: new Date(this.now()).toISOString(), terminalReason };
184
+ }
185
+ }
186
+ /** Separate from the fill's handoff and from reconciliation: this surface has no fill or submit operation. */
187
+ export async function routeLocalApplySignIn(runtime, method, path, read) {
188
+ if (method !== "POST")
189
+ return undefined;
190
+ if (path === "/local-apply/sign-in")
191
+ return runtime.open(await read());
192
+ if (path === "/local-apply/sign-in/frame")
193
+ return runtime.frame(await read());
194
+ if (path === "/local-apply/sign-in/input") {
195
+ await runtime.input(await read());
196
+ return { ok: true };
197
+ }
198
+ if (path === "/local-apply/sign-in/end")
199
+ return runtime.end(await read());
200
+ return undefined;
201
+ }
202
+ function parseRequest(raw) {
203
+ const o = raw && typeof raw === "object" ? raw : {};
204
+ const signInId = typeof o.signInId === "string" ? o.signInId : "";
205
+ const browserProfile = o.browserProfile === "default" || o.browserProfile === "isolated" ? o.browserProfile : null;
206
+ const url = typeof o.url === "string" ? o.url : "";
207
+ let ok = false;
208
+ try {
209
+ const u = new URL(url);
210
+ ok = u.protocol === "https:" || u.protocol === "http:";
211
+ }
212
+ catch {
213
+ ok = false;
214
+ }
215
+ if (!SAFE_ID.test(signInId) || !browserProfile || !ok)
216
+ throw new RunnerInputError("sign-in needs an opaque signInId, a browserProfile and an http(s) url");
217
+ return { signInId, browserProfile, url };
218
+ }
@@ -19,6 +19,7 @@ import { LocalArtifactRuntime } from "./local-artifact/runtime.js";
19
19
  import { LOCAL_ARTIFACT_TASK_TYPE } from "./local-artifact/contract.js";
20
20
  import { LocalApplyRuntime } from "./local-apply/runtime.js";
21
21
  import { LocalApplyReconciliationRuntime } from "./local-apply/reconciliation-runtime.js";
22
+ import { LocalApplySignInRuntime } from "./local-apply/sign-in-runtime.js";
22
23
  import { LOCAL_APPLY_TASK_TYPE } from "./local-apply/contract.js";
23
24
  import { createRunnerBrowserFactories } from "./local-apply/live-run-browser.js";
24
25
  import { createLocalApplyTakeoverAdapter, isLocalApplyTakeover } from "./local-apply/takeover-adapter.js";
@@ -95,6 +96,8 @@ export class LocalRunner {
95
96
  localApply;
96
97
  /** Browser-only, no-bridge reconciliation for an ended uncertain apply attempt. */
97
98
  localApplyReconciliation;
99
+ /** A site sign-in in the application profile after a fill ended blocked on it (#1100): no run, no bridge. */
100
+ localApplySignIn;
98
101
  /** The Website Builder's local author (#841): the signed-in CLI drafting through the PAGS broker. */
99
102
  siteBuilder;
100
103
  /** This runner's own local URL, once the server listens — the bridge forwarder calls back to it. */
@@ -118,6 +121,12 @@ export class LocalRunner {
118
121
  takeover: localApplyTakeover,
119
122
  });
120
123
  this.localApplyReconciliation = new LocalApplyReconciliationRuntime({ dataDir: config.dataDir, browserFor: applyBrowserFor, takeover: localApplyTakeover });
124
+ this.localApplySignIn = new LocalApplySignInRuntime({
125
+ dataDir: config.dataDir,
126
+ browserFor: applyBrowserFor,
127
+ takeover: localApplyTakeover,
128
+ busy: () => this.localApply.liveWork().length + this.localApplyReconciliation.liveWork().length + this.localBrowser.liveWork().length > 0,
129
+ });
121
130
  this.siteBuilder = new SiteBuilderRuntime({ dataDir: config.dataDir, selfUrl: () => this.selfUrl, onEvent: (id, type, message, data) => this.addTaskEvent(this.requireTask(id), type, message, data) });
122
131
  // Tasks paused/running on a previous process are orphaned now — their
123
132
  // pages and takeover sessions are gone. Fail them so the board is clean.
@@ -136,7 +145,7 @@ export class LocalRunner {
136
145
  */
137
146
  liveWork() {
138
147
  const coding = this.coding.liveWork();
139
- const local = [...this.localBrowser.liveWork(), ...this.localArtifact.liveWork(), ...this.localApply.liveWork(), ...this.localApplyReconciliation.liveWork(), ...this.siteBuilder.liveWork()];
148
+ const local = [...this.localBrowser.liveWork(), ...this.localArtifact.liveWork(), ...this.localApply.liveWork(), ...this.localApplyReconciliation.liveWork(), ...this.localApplySignIn.liveWork(), ...this.siteBuilder.liveWork()];
140
149
  return { codingTurns: coding.length, localRuns: local.length, detail: [...coding, ...local].slice(0, 10) };
141
150
  }
142
151
  capabilities() {
@@ -320,6 +329,7 @@ export class LocalRunner {
320
329
  this.localArtifact.closeAll();
321
330
  this.localApply.closeAll();
322
331
  this.localApplyReconciliation.closeAll();
332
+ this.localApplySignIn.closeAll();
323
333
  try {
324
334
  this.coding.closeAll();
325
335
  }
@@ -8,6 +8,7 @@ import { listGithubOrgs, listGithubRepos, searchGithubRepos, getGithubRepoDetail
8
8
  import { routeLocalApplyHandoff } from "./local-apply/handoff-routes.js";
9
9
  import { runNativePreflight } from "./native/preflight.js";
10
10
  import { routeLocalApplyReconciliation } from "./local-apply/reconciliation-routes.js";
11
+ import { routeLocalApplySignIn } from "./local-apply/sign-in-runtime.js";
11
12
  export function createRunnerServer(runner) {
12
13
  return createServer(async (req, res) => {
13
14
  try {
@@ -273,6 +274,9 @@ async function route(runner, req, res) {
273
274
  const reconciliation = await routeLocalApplyReconciliation(runner.localApplyReconciliation, req.method, path, () => readJson(req));
274
275
  if (reconciliation !== undefined)
275
276
  return json(res, 200, reconciliation);
277
+ const signIn = await routeLocalApplySignIn(runner.localApplySignIn, req.method, path, () => readJson(req));
278
+ if (signIn !== undefined)
279
+ return json(res, 200, signIn);
276
280
  if (req.method === "POST" && path === "/local-apply/resume") {
277
281
  return json(res, 200, runner.localApply.resume(await readJson(req)));
278
282
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@proagentstore/cli",
3
- "version": "0.4.133",
3
+ "version": "0.4.135",
4
4
  "description": "CLI for creating, publishing, and running ProAgentStore agents",
5
5
  "license": "MIT",
6
6
  "type": "module",