@quickchatai/quickreview 1.1.0 → 1.2.0

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 (225) hide show
  1. package/README.md +35 -19
  2. package/config.example.toml +41 -9
  3. package/dist/extensions/cli/login-route.js +5 -0
  4. package/dist/extensions/cli/login-route.js.map +1 -0
  5. package/dist/extensions/login-route.js +180 -0
  6. package/dist/extensions/login-route.js.map +1 -0
  7. package/dist/extensions/onboarding.js +29 -6
  8. package/dist/extensions/onboarding.js.map +1 -1
  9. package/dist/src/ciretry.js +2 -1
  10. package/dist/src/ciretry.js.map +1 -1
  11. package/dist/src/cli.js +43 -12
  12. package/dist/src/cli.js.map +1 -1
  13. package/dist/src/cliops.js +3 -2
  14. package/dist/src/cliops.js.map +1 -1
  15. package/dist/src/commands.js +25 -2
  16. package/dist/src/commands.js.map +1 -1
  17. package/dist/src/config.js +16 -2
  18. package/dist/src/config.js.map +1 -1
  19. package/dist/src/daemon.js +24 -8
  20. package/dist/src/daemon.js.map +1 -1
  21. package/dist/src/debugroundpi.js +5 -1
  22. package/dist/src/debugroundpi.js.map +1 -1
  23. package/dist/src/dispatch.js +5 -4
  24. package/dist/src/dispatch.js.map +1 -1
  25. package/dist/src/engine/debugpi.js +17 -5
  26. package/dist/src/engine/debugpi.js.map +1 -1
  27. package/dist/src/execboundary.js +10 -3
  28. package/dist/src/execboundary.js.map +1 -1
  29. package/dist/src/github.js +51 -16
  30. package/dist/src/github.js.map +1 -1
  31. package/dist/src/githubtoken.js +346 -0
  32. package/dist/src/githubtoken.js.map +1 -0
  33. package/dist/src/loginhint.js +8 -3
  34. package/dist/src/loginhint.js.map +1 -1
  35. package/dist/src/loginrefresh/pirefresh.js +3 -0
  36. package/dist/src/loginrefresh/pirefresh.js.map +1 -1
  37. package/dist/src/npmverbs.js +4 -2
  38. package/dist/src/npmverbs.js.map +1 -1
  39. package/dist/src/onboard/choices.js +29 -1
  40. package/dist/src/onboard/choices.js.map +1 -1
  41. package/dist/src/onboard/engine.js +8 -23
  42. package/dist/src/onboard/engine.js.map +1 -1
  43. package/dist/src/onboard/launch.js +12 -20
  44. package/dist/src/onboard/launch.js.map +1 -1
  45. package/dist/src/onboard/pipin.js +22 -0
  46. package/dist/src/onboard/pipin.js.map +1 -0
  47. package/dist/src/onboard/stepforward.js +41 -0
  48. package/dist/src/onboard/stepforward.js.map +1 -0
  49. package/dist/src/onboard/steps/appslug.js +4 -1
  50. package/dist/src/onboard/steps/appslug.js.map +1 -1
  51. package/dist/src/onboard/steps/compose.js +29 -0
  52. package/dist/src/onboard/steps/compose.js.map +1 -1
  53. package/dist/src/onboard/steps/configfill.js +102 -32
  54. package/dist/src/onboard/steps/configfill.js.map +1 -1
  55. package/dist/src/onboard/steps/githubaccount.js +595 -0
  56. package/dist/src/onboard/steps/githubaccount.js.map +1 -0
  57. package/dist/src/onboard/steps/githubapp.js +65 -7
  58. package/dist/src/onboard/steps/githubapp.js.map +1 -1
  59. package/dist/src/onboard/steps/golive.js +2 -0
  60. package/dist/src/onboard/steps/golive.js.map +1 -1
  61. package/dist/src/onboard/steps/index.js +11 -0
  62. package/dist/src/onboard/steps/index.js.map +1 -1
  63. package/dist/src/onboard/steps/ingress.js +80 -25
  64. package/dist/src/onboard/steps/ingress.js.map +1 -1
  65. package/dist/src/onboard/steps/llmauth.js +103 -30
  66. package/dist/src/onboard/steps/llmauth.js.map +1 -1
  67. package/dist/src/onboard/steps/memory.js +53 -1
  68. package/dist/src/onboard/steps/memory.js.map +1 -1
  69. package/dist/src/onboard/steps/proof.js +366 -56
  70. package/dist/src/onboard/steps/proof.js.map +1 -1
  71. package/dist/src/onboard/steps/topology.js +6 -6
  72. package/dist/src/onboard/steps/topology.js.map +1 -1
  73. package/dist/src/onboard/steps/transport.js +9 -1
  74. package/dist/src/onboard/steps/transport.js.map +1 -1
  75. package/dist/src/onboard/steps/tuiroute.js +102 -0
  76. package/dist/src/onboard/steps/tuiroute.js.map +1 -0
  77. package/dist/src/onboard/steps/verifyup.js +17 -1
  78. package/dist/src/onboard/steps/verifyup.js.map +1 -1
  79. package/dist/src/onboard/toolnames.js +30 -0
  80. package/dist/src/onboard/toolnames.js.map +1 -0
  81. package/dist/src/operatorwrite.js +23 -0
  82. package/dist/src/operatorwrite.js.map +1 -0
  83. package/dist/src/outcomes.js +4 -1
  84. package/dist/src/outcomes.js.map +1 -1
  85. package/dist/src/posting-journal.js +2 -1
  86. package/dist/src/posting-journal.js.map +1 -1
  87. package/dist/src/posting-outbox.js +6 -2
  88. package/dist/src/posting-outbox.js.map +1 -1
  89. package/dist/src/posting-receipt.js +14 -1
  90. package/dist/src/posting-receipt.js.map +1 -1
  91. package/dist/src/recheck.js +2 -1
  92. package/dist/src/recheck.js.map +1 -1
  93. package/dist/src/reconcile.js +22 -2
  94. package/dist/src/reconcile.js.map +1 -1
  95. package/dist/src/reviewhistory.js +32 -6
  96. package/dist/src/reviewhistory.js.map +1 -1
  97. package/dist/src/reviewnow.js +45 -0
  98. package/dist/src/reviewnow.js.map +1 -0
  99. package/dist/src/state.js.map +1 -1
  100. package/dist/src/statemachine.js +4 -1
  101. package/dist/src/statemachine.js.map +1 -1
  102. package/dist/src/takeoverpi.js +13 -0
  103. package/dist/src/takeoverpi.js.map +1 -1
  104. package/dist/src/tui/app.js +49 -0
  105. package/dist/src/tui/app.js.map +1 -1
  106. package/dist/src/tui/board.js +88 -12
  107. package/dist/src/tui/board.js.map +1 -1
  108. package/dist/src/tui/cmdbar.js +4 -1
  109. package/dist/src/tui/cmdbar.js.map +1 -1
  110. package/dist/src/tui/companion.js +74 -10
  111. package/dist/src/tui/companion.js.map +1 -1
  112. package/dist/src/tui/connect.js +7 -1
  113. package/dist/src/tui/connect.js.map +1 -1
  114. package/dist/src/tui/filters.js +5 -2
  115. package/dist/src/tui/filters.js.map +1 -1
  116. package/dist/src/tui/ghread.js +61 -5
  117. package/dist/src/tui/ghread.js.map +1 -1
  118. package/dist/src/tui/index.js +106 -20
  119. package/dist/src/tui/index.js.map +1 -1
  120. package/dist/src/tui/memforward.js +39 -2
  121. package/dist/src/tui/memforward.js.map +1 -1
  122. package/dist/src/tui/model.js +25 -0
  123. package/dist/src/tui/model.js.map +1 -1
  124. package/dist/src/tui/prpause.js +7 -0
  125. package/dist/src/tui/prpause.js.map +1 -1
  126. package/dist/src/tui/remote.js +7 -1
  127. package/dist/src/tui/remote.js.map +1 -1
  128. package/dist/src/tui/route.js +143 -0
  129. package/dist/src/tui/route.js.map +1 -0
  130. package/dist/src/tui/setupstate.js +29 -0
  131. package/dist/src/tui/setupstate.js.map +1 -0
  132. package/dist/src/tui/sshtriage.js +102 -5
  133. package/dist/src/tui/sshtriage.js.map +1 -1
  134. package/dist/src/tui/theme.js +2 -1
  135. package/dist/src/tui/theme.js.map +1 -1
  136. package/dist/src/tui/unwedge.js +2 -12
  137. package/dist/src/tui/unwedge.js.map +1 -1
  138. package/dist/src/verdicts.js +17 -5
  139. package/dist/src/verdicts.js.map +1 -1
  140. package/dist/src/version.js +1 -1
  141. package/dist/src/version.js.map +1 -1
  142. package/dist/src/webhook.js +12 -1
  143. package/dist/src/webhook.js.map +1 -1
  144. package/dist/src/wire.js +28 -6
  145. package/dist/src/wire.js.map +1 -1
  146. package/dist/src/workerworld.js +7 -1
  147. package/dist/src/workerworld.js.map +1 -1
  148. package/docker/companion/entrypoint.sh +20 -1
  149. package/docker/entrypoint.sh +17 -0
  150. package/extensions/cli/login-route.ts +10 -0
  151. package/extensions/login-route.ts +220 -0
  152. package/extensions/onboarding.ts +35 -6
  153. package/package.json +2 -2
  154. package/prompts/onboarding.md +57 -18
  155. package/scripts/mint-token.sh +25 -4
  156. package/src/ciretry.ts +2 -1
  157. package/src/cli.ts +49 -14
  158. package/src/cliops.ts +3 -2
  159. package/src/commands.ts +28 -2
  160. package/src/config.ts +24 -4
  161. package/src/daemon.ts +32 -7
  162. package/src/debugroundpi.ts +11 -1
  163. package/src/dispatch.ts +5 -4
  164. package/src/engine/debugpi.ts +17 -5
  165. package/src/execboundary.ts +16 -4
  166. package/src/github.ts +57 -15
  167. package/src/githubtoken.ts +435 -0
  168. package/src/loginhint.ts +8 -3
  169. package/src/loginrefresh/pirefresh.ts +3 -0
  170. package/src/npmverbs.ts +4 -2
  171. package/src/onboard/choices.ts +34 -1
  172. package/src/onboard/engine.ts +22 -3
  173. package/src/onboard/launch.ts +17 -29
  174. package/src/onboard/pipin.ts +31 -0
  175. package/src/onboard/stepforward.ts +61 -0
  176. package/src/onboard/steps/appslug.ts +4 -1
  177. package/src/onboard/steps/compose.ts +31 -0
  178. package/src/onboard/steps/configfill.ts +121 -33
  179. package/src/onboard/steps/githubaccount.ts +716 -0
  180. package/src/onboard/steps/githubapp.ts +92 -8
  181. package/src/onboard/steps/golive.ts +2 -0
  182. package/src/onboard/steps/index.ts +11 -0
  183. package/src/onboard/steps/ingress.ts +98 -31
  184. package/src/onboard/steps/llmauth.ts +114 -29
  185. package/src/onboard/steps/memory.ts +58 -1
  186. package/src/onboard/steps/proof.ts +473 -55
  187. package/src/onboard/steps/topology.ts +7 -6
  188. package/src/onboard/steps/transport.ts +15 -0
  189. package/src/onboard/steps/tuiroute.ts +119 -0
  190. package/src/onboard/steps/verifyup.ts +20 -1
  191. package/src/onboard/toolnames.ts +34 -0
  192. package/src/operatorwrite.ts +25 -0
  193. package/src/outcomes.ts +8 -1
  194. package/src/posting-journal.ts +6 -1
  195. package/src/posting-outbox.ts +10 -2
  196. package/src/posting-receipt.ts +17 -0
  197. package/src/recheck.ts +2 -1
  198. package/src/reconcile.ts +29 -3
  199. package/src/reviewhistory.ts +20 -6
  200. package/src/reviewnow.ts +76 -0
  201. package/src/state.ts +18 -0
  202. package/src/statemachine.ts +4 -1
  203. package/src/takeoverpi.ts +13 -0
  204. package/src/tui/app.ts +47 -0
  205. package/src/tui/board.ts +111 -14
  206. package/src/tui/cmdbar.ts +4 -1
  207. package/src/tui/companion.ts +78 -10
  208. package/src/tui/connect.ts +7 -1
  209. package/src/tui/filters.ts +7 -3
  210. package/src/tui/ghread.ts +74 -7
  211. package/src/tui/index.ts +114 -23
  212. package/src/tui/memforward.ts +52 -3
  213. package/src/tui/model.ts +36 -2
  214. package/src/tui/prpause.ts +11 -2
  215. package/src/tui/remote.ts +9 -1
  216. package/src/tui/route.ts +173 -0
  217. package/src/tui/setupstate.ts +52 -0
  218. package/src/tui/sshtriage.ts +127 -6
  219. package/src/tui/theme.ts +2 -1
  220. package/src/tui/unwedge.ts +2 -14
  221. package/src/verdicts.ts +23 -5
  222. package/src/version.ts +1 -1
  223. package/src/webhook.ts +10 -1
  224. package/src/wire.ts +36 -7
  225. package/src/workerworld.ts +7 -1
package/src/verdicts.ts CHANGED
@@ -15,6 +15,11 @@ import {
15
15
  // write the companion inbox. There is no path here that touches an existing review:
16
16
  // the invariant against retracting an approval is structural, not prompted
17
17
  // (AGENTS.md invariant 2).
18
+ import {
19
+ botAccountLogin,
20
+ isPersonMode,
21
+ ownLogins,
22
+ } from "@quickchatai/pi-quickreview/src/identity.js";
18
23
  import {
19
24
  normalizePassPhase,
20
25
  normalizeSweepClaim,
@@ -406,6 +411,15 @@ export function decidedVerdictFromReceipt(receipt: PostingReceipt): ReceiptDecis
406
411
  return { verdict: "REQUEST_CHANGES" };
407
412
  if (receipt.intended_event === "APPROVE" && receipt.actual_event === "APPROVE")
408
413
  return { verdict: "APPROVE" };
414
+ // Person mode posts every review as a COMMENT: the approval the session
415
+ // asked for is the decision, and it was not withheld.
416
+ if (
417
+ receipt.person_mode === true &&
418
+ receipt.intended_event === "APPROVE" &&
419
+ receipt.actual_event === "COMMENT" &&
420
+ receipt.reconciled !== true
421
+ )
422
+ return { verdict: "APPROVE" };
409
423
  if (
410
424
  receipt.intended_event === "COMMENT" &&
411
425
  receipt.actual_event === "COMMENT" &&
@@ -1537,12 +1551,11 @@ export class VerdictWatcher {
1537
1551
  if (!this.deps.cfg.dry_run && this.deps.adapter.listReviewsFull) {
1538
1552
  try {
1539
1553
  const marker = postingMarker(round.posting_key);
1540
- const bot = this.deps.cfg.github.bot_login;
1541
1554
  const reviews = await this.deps.adapter.listReviewsFull(round.pr);
1542
1555
  reconciled = true;
1543
1556
  const matches = reviews.filter(
1544
1557
  (review) =>
1545
- (review.user === bot || review.user === `${this.deps.cfg.github.app_slug}[bot]`) &&
1558
+ ownLogins(this.deps.cfg.github).includes(review.user) &&
1546
1559
  review.commit_id?.toLowerCase() === expectedHead.toLowerCase() &&
1547
1560
  review.body.includes(marker),
1548
1561
  );
@@ -1596,6 +1609,7 @@ export class VerdictWatcher {
1596
1609
  gh_commit_id: match.commit_id,
1597
1610
  gh_state: match.state,
1598
1611
  ...(verifiedIntent ? {} : { reconciled: true }),
1612
+ ...(isPersonMode(this.deps.cfg.github) ? { person_mode: true as const } : {}),
1599
1613
  };
1600
1614
  if (verdict && !receiptMatchesVerdict(synthetic, verdict)) {
1601
1615
  return {
@@ -1804,7 +1818,7 @@ export class VerdictWatcher {
1804
1818
  );
1805
1819
  }
1806
1820
  const restored = restoreStandingLifecycle(store, round.pr, standing, {
1807
- botLogin: cfg.github.bot_login,
1821
+ botLogin: botAccountLogin(cfg.github),
1808
1822
  now: () => this.now(),
1809
1823
  reason: standing
1810
1824
  ? `posting-guarded-noop:${standing}`
@@ -2339,7 +2353,11 @@ export class VerdictWatcher {
2339
2353
  v.pr,
2340
2354
  { type: "verdict", verdict: v.verdict },
2341
2355
  { head_sha: v.head_sha },
2342
- { botLogin: cfg.github.bot_login, now: () => this.now(), reason: `verdict:${v.verdict}` },
2356
+ {
2357
+ botLogin: botAccountLogin(cfg.github),
2358
+ now: () => this.now(),
2359
+ reason: `verdict:${v.verdict}`,
2360
+ },
2343
2361
  );
2344
2362
  store.logEvent("verdict_applied", v.pr, {
2345
2363
  file: name,
@@ -3486,7 +3504,7 @@ export class VerdictWatcher {
3486
3504
  pr,
3487
3505
  { type: "round_overdue", standing },
3488
3506
  {},
3489
- { botLogin: cfg.github.bot_login, now: () => this.now(), reason: `anomaly:${kind}` },
3507
+ { botLogin: botAccountLogin(cfg.github), now: () => this.now(), reason: `anomaly:${kind}` },
3490
3508
  );
3491
3509
  const command = round ? store.sessionCommandForRound(pr, round.round_no) : undefined;
3492
3510
  const successor = store.successorFor(pr);
package/src/version.ts CHANGED
@@ -8,7 +8,7 @@ import {
8
8
  SCAN_SCHEMA_VERSION,
9
9
  VERSION_SCHEMA_VERSION,
10
10
  } from "@quickchatai/pi-quickreview/src/contract.js";
11
- import { piPinFromPackageJson } from "./onboard/launch.js";
11
+ import { piPinFromPackageJson } from "./onboard/pipin.js";
12
12
 
13
13
  export interface VersionInfo {
14
14
  schema_version: number;
package/src/webhook.ts CHANGED
@@ -9,6 +9,7 @@ import { createHmac, timingSafeEqual } from "node:crypto";
9
9
  import { createServer, type IncomingMessage, type Server, type ServerResponse } from "node:http";
10
10
  import { captureFromEvent, recordCapturedComment } from "./capture.js";
11
11
  import { reviewTeamSlug, type Config } from "./config.js";
12
+ import { botAccountLogin } from "@quickchatai/pi-quickreview/src/identity.js";
12
13
  import { interpretReviewEvent, isFeedbackBody, ledgerFeedbackSignal } from "./feedback.js";
13
14
  import { applyEvent as applyEventFor, mergedOverBlock } from "./statemachine.js";
14
15
  import type { StateStore, StatusSnapshot } from "./state.js";
@@ -20,6 +21,8 @@ import { blockedPrAuthor } from "./pr-conversation.js";
20
21
  * header, a wrong prefix, or a length mismatch (timingSafeEqual throws on
21
22
  * unequal lengths — we must not let that leak as a 500). */
22
23
  export function verifySignature(raw: Buffer, header: string | undefined, secret: string): boolean {
24
+ // An empty secret signs nothing: anyone can compute that HMAC.
25
+ if (secret === "") return false;
23
26
  if (!header || !header.startsWith("sha256=")) return false;
24
27
  const expected = Buffer.from(signBody(raw, secret), "utf8");
25
28
  const given = Buffer.from(header, "utf8");
@@ -499,6 +502,10 @@ export function handleDelivery(
499
502
  raw: Buffer,
500
503
  ): WebhookHandleResult {
501
504
  const log = deps.log ?? (() => {});
505
+ // The polling shape (and so `[github] auth = "user"`) takes no webhooks:
506
+ // the daemon hands no secret, and every delivery is refused unread. The
507
+ // reconcile poll is that install's only trigger.
508
+ if (deps.secret === "") return { status: 404, reason: "webhooks-disabled" };
502
509
  if (!verifySignature(raw, headers.signature, deps.secret)) {
503
510
  logRejection(deps, log, headers);
504
511
  return { status: 403, reason: "bad-signature" };
@@ -810,7 +817,9 @@ export async function applyInterpretation(
810
817
  i: Interpretation,
811
818
  ): Promise<void> {
812
819
  const { store, cfg } = deps;
813
- const opts = { botLogin: cfg.github.bot_login };
820
+ // The bot ACCOUNT for PR authorship. Person mode has none; no webhook
821
+ // arrives there, but the state machine reads one rule everywhere.
822
+ const opts = { botLogin: botAccountLogin(cfg.github) };
814
823
  const local = <T>(run: () => T): T => store.webhookLocalOnce(deps.deliveryGuid, run);
815
824
  switch (i.kind) {
816
825
  case "trigger":
package/src/wire.ts CHANGED
@@ -54,7 +54,11 @@ import { priorSidecarPath } from "@quickchatai/pi-quickreview/src/priorfindings.
54
54
  import { completeRecordedFindings } from "./recordedfindings.js";
55
55
  import { makePostingGuard } from "@quickchatai/pi-quickreview/src/guards.js";
56
56
  import { classifyCi, ciBlocksApproval } from "@quickchatai/pi-quickreview/src/cigate.js";
57
- import { TokenMinter } from "@quickchatai/pi-quickreview/src/githubAuth.js";
57
+ import {
58
+ githubCredential,
59
+ type GitHubCredential,
60
+ } from "@quickchatai/pi-quickreview/src/githubAuth.js";
61
+ import { botAccountLogin, isPersonMode } from "@quickchatai/pi-quickreview/src/identity.js";
58
62
  import {
59
63
  FeedbackEngine,
60
64
  FeedbackMemoryPump,
@@ -125,6 +129,7 @@ import {
125
129
  } from "./openviking/provisionreport.js";
126
130
  import { countedGapSink } from "./openviking/gapsink.js";
127
131
  import { recoverPrForOperator } from "./operator-recovery.js";
132
+ import { reviewNow } from "./reviewnow.js";
128
133
  import { createWorkerLiveness, inFlightPrs } from "./workerliveness.js";
129
134
  import { resolveTerminalFailure } from "./terminal-failure.js";
130
135
  import type { MemoryStore } from "./memory/store.js";
@@ -158,7 +163,8 @@ import { buildWorkerWorld } from "./workerworld.js";
158
163
  export interface ProductionParts {
159
164
  cfg: Config;
160
165
  root: string;
161
- minter: TokenMinter;
166
+ /** The `[github] auth` credential: the App minter, or the person's token file. */
167
+ minter: GitHubCredential;
162
168
  gate: MutationGate;
163
169
  adapter: GitHubAdapter;
164
170
  /** The tmux manager: it serves what has not moved to workers yet (the overseer) and
@@ -225,7 +231,7 @@ export function buildParts(
225
231
  process.env.QCR_OPENVIKING_BOOT_DISABLED === "1" && overlaid.openviking.enabled
226
232
  ? "OpenViking state migration is incomplete; memory is unavailable for this boot — original files were retained; retry after correcting storage and restarting"
227
233
  : undefined;
228
- const minter = new TokenMinter(cfg.github.secrets_dir);
234
+ const minter = githubCredential(cfg.github);
229
235
  const gate: MutationGate = new MutationGate(
230
236
  cfg.dry_run,
231
237
  join(root, "state", "dryrun"),
@@ -1052,7 +1058,9 @@ export function createProductionDaemon(parts: ProductionParts = buildParts()): D
1052
1058
  // the base clone; the daemon's own bot identity never qualifies.
1053
1059
  directiveAuthor: (sha, paths) =>
1054
1060
  sessions.worktrees.lastHumanAuthor(sha, paths, {
1055
- botLogins: [cfg.github.bot_login, cfg.github.app_slug],
1061
+ // Person mode has no bot account: the person's own commits are a
1062
+ // human's, and they may well be the one to mention.
1063
+ botLogins: isPersonMode(cfg.github) ? [] : [cfg.github.bot_login, cfg.github.app_slug],
1056
1064
  }),
1057
1065
  log,
1058
1066
  // Lazy on purpose: the Slack subsystem is built in onStart, after the
@@ -1552,7 +1560,9 @@ export function createProductionDaemon(parts: ProductionParts = buildParts()): D
1552
1560
  outcomes: d.outcomes,
1553
1561
  memory,
1554
1562
  repo: cfg.github.repo,
1555
- botLogin: cfg.github.bot_login,
1563
+ // The bot ACCOUNT, against captured human comments: none in person
1564
+ // mode, where the person's replies are a human's.
1565
+ botLogin: botAccountLogin(cfg.github),
1556
1566
  healthy: () => health.currentState() === "healthy",
1557
1567
  // No case batch starts while a review round reads memory (its seed,
1558
1568
  // its lesson selection); every batch sent reports its embedding
@@ -1580,7 +1590,7 @@ export function createProductionDaemon(parts: ProductionParts = buildParts()): D
1580
1590
  root,
1581
1591
  eodTime: cfg.eod.time,
1582
1592
  timeZone: cfg.eod.timezone,
1583
- botLogin: cfg.github.bot_login,
1593
+ botLogin: botAccountLogin(cfg.github),
1584
1594
  baseBranch: cfg.github.base_branch,
1585
1595
  config: learnerKnobsOf(cfg.memory),
1586
1596
  git: sessions.worktrees,
@@ -1696,6 +1706,21 @@ export function createProductionDaemon(parts: ProductionParts = buildParts()): D
1696
1706
  "restart-session",
1697
1707
  );
1698
1708
  },
1709
+ // The operator's "review this PR now" (the onboarding proof round):
1710
+ // the label path's state-machine trigger, with no GitHub write to ask.
1711
+ reviewNow(pr) {
1712
+ return reviewNow(
1713
+ {
1714
+ store: d.store,
1715
+ getPr: (n) => adapter.getPr(n),
1716
+ pump: () => d.pump(),
1717
+ // On the own-account route the bot login is the person, whose
1718
+ // own PRs are the point; only an App's bot is never reviewed.
1719
+ ...(isPersonMode(cfg.github) ? {} : { botLogin: cfg.github.bot_login }),
1720
+ },
1721
+ pr,
1722
+ );
1723
+ },
1699
1724
  resolveTerminalFailure(payload) {
1700
1725
  return resolveTerminalFailure(
1701
1726
  {
@@ -1861,7 +1886,9 @@ export function createProductionDaemon(parts: ProductionParts = buildParts()): D
1861
1886
  // Webhook-URL self-heal: only under the tunnel-file ingress shape. The
1862
1887
  // gated PATCH rides the app-self-config class, so it executes even in
1863
1888
  // dry-run (AGENTS.md invariant 6) — loudly.
1864
- if (cfg.ingress.shape === "tunnel-file") {
1889
+ // Never under the person's credential: there is no App whose webhook
1890
+ // URL could be re-pointed (config load already refuses the shape).
1891
+ if (cfg.ingress.shape === "tunnel-file" && !isPersonMode(cfg.github)) {
1865
1892
  webhookHeal = new WebhookHealer({
1866
1893
  hostnameFile: ingressHostnameFile(cfg, root),
1867
1894
  readConfig: () => adapter.getAppWebhookConfig(),
@@ -2112,6 +2139,8 @@ export function createProductionDaemon(parts: ProductionParts = buildParts()): D
2112
2139
  adapter,
2113
2140
  botLogin: cfg.github.bot_login,
2114
2141
  guards: cfg.guards,
2142
+ // Person mode: every review event posts as COMMENT (guards.ts).
2143
+ personMode: isPersonMode(cfg.github),
2115
2144
  log,
2116
2145
  }),
2117
2146
  );
@@ -18,6 +18,7 @@
18
18
  // PiSessionManager that still exists serves what has not moved yet (the overseer, for now)
19
19
  // and lends its path helpers and environment, which is what keeps a worker's session the same
20
20
  // session a tmux round saw.
21
+ import { isPersonMode } from "@quickchatai/pi-quickreview/src/identity.js";
21
22
  import { mkdirSync, readFileSync } from "node:fs";
22
23
  import { join } from "node:path";
23
24
  import { AnswerHost, answerCapsOf } from "./answerworker.js";
@@ -223,6 +224,7 @@ export function buildWorkerWorld(o: WorkerWorldOptions): WorkerWorld {
223
224
  const journal = new PostingJournal({
224
225
  dir: join(stateDir, "posting-receipts"),
225
226
  repo: cfg.github.repo,
227
+ personMode: isPersonMode(cfg.github),
226
228
  });
227
229
 
228
230
  // The takeover and debug managers are built before the supervisor (it takes their gates)
@@ -311,7 +313,11 @@ export function buildWorkerWorld(o: WorkerWorldOptions): WorkerWorld {
311
313
  store,
312
314
  journal,
313
315
  cfg,
314
- botLogins: [cfg.github.bot_login, cfg.github.app_slug],
316
+ // Marker-matched (posting-outbox.ts markedReviewsOf), so the person's
317
+ // login alone is safe in person mode: there is no App slug to add.
318
+ botLogins: isPersonMode(cfg.github)
319
+ ? [cfg.github.bot_login]
320
+ : [cfg.github.bot_login, cfg.github.app_slug],
315
321
  roundOf: commandRoundOf(store, headOf),
316
322
  log,
317
323
  },