@quantiya/codevibe-core 2.0.91 → 2.0.93

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 (81) hide show
  1. package/dist/adapter/registry.d.ts +2 -2
  2. package/dist/appsync/sync-deferred-inputs.d.ts +2 -2
  3. package/dist/appsync/sync-desktop-registration.d.ts +2 -2
  4. package/dist/appsync/sync-mobile-echo.d.ts +16 -16
  5. package/dist/appsync/sync-prompt-publication.d.ts +8 -8
  6. package/dist/appsync/sync-shell-source.d.ts +18 -18
  7. package/dist/appsync/sync-source-cursor.d.ts +30 -30
  8. package/dist/commerce/upgrade-handoff.d.ts +11 -0
  9. package/dist/companion-mode/codex-native-question-reply.d.ts +22 -0
  10. package/dist/companion-mode/codex-native-state.d.ts +132 -114
  11. package/dist/index.d.ts +2 -0
  12. package/dist/index.js +2605 -2306
  13. package/dist/local-executor/anchored-cas.d.ts +78 -6
  14. package/dist/local-executor/anchored-fs.d.ts +32 -0
  15. package/dist/local-executor/blob-store.d.ts +95 -0
  16. package/dist/local-executor/change-manifest-store.d.ts +32 -0
  17. package/dist/local-executor/change-manifest.d.ts +50 -0
  18. package/dist/local-executor/durable-store.d.ts +72 -15
  19. package/dist/local-executor/implementor-argv.d.ts +43 -7
  20. package/dist/local-executor/local-executor-impl.d.ts +57 -1
  21. package/dist/local-executor/nolink-apply.d.ts +22 -2
  22. package/dist/local-executor/revert-runner.d.ts +3 -0
  23. package/dist/local-executor/shadow-apply-capabilities.d.ts +31 -2
  24. package/dist/local-executor/shadow-free-space.d.ts +25 -0
  25. package/dist/local-executor/shadow-protocol.d.ts +117 -18
  26. package/dist/local-executor/shadow-recovery.d.ts +28 -3
  27. package/dist/local-executor/shadow-task-intent.d.ts +29 -0
  28. package/dist/local-executor/shadow-volume-probe.d.ts +0 -14
  29. package/dist/local-executor/sharded-list.d.ts +32 -0
  30. package/dist/local-executor/snapshot-merge.d.ts +18 -0
  31. package/dist/local-executor/spawn-sweep-binary.d.ts +33 -0
  32. package/dist/local-executor/spawn-sweep.d.ts +3 -0
  33. package/dist/local-executor/store-dir-anchor.d.ts +48 -0
  34. package/dist/local-executor/tier-self-test.d.ts +57 -0
  35. package/dist/local-executor/types.d.ts +26 -0
  36. package/dist/local-executor/verification-gates/diff-sanity.d.ts +6 -0
  37. package/dist/local-executor/workspace-materializer.d.ts +17 -1
  38. package/dist/local-executor/workspace-shadow.d.ts +209 -7
  39. package/dist/orchestration-shell/audit-summary.d.ts +22 -0
  40. package/dist/orchestration-shell/bounded-shutdown.d.ts +2 -0
  41. package/dist/orchestration-shell/cli.js +64053 -57609
  42. package/dist/orchestration-shell/components/OrchestrationApp.d.ts +6 -1
  43. package/dist/orchestration-shell/decision-attempt-recovery.d.ts +14 -0
  44. package/dist/orchestration-shell/engine-projection.d.ts +98 -0
  45. package/dist/orchestration-shell/exit-guard.d.ts +18 -0
  46. package/dist/orchestration-shell/gate-decision-submit.d.ts +2 -0
  47. package/dist/orchestration-shell/index.d.ts +18 -0
  48. package/dist/orchestration-shell/quorum-loop.d.ts +265 -21
  49. package/dist/orchestration-shell/review-bundle.d.ts +77 -0
  50. package/dist/orchestration-shell/review-receipts.d.ts +100 -0
  51. package/dist/orchestration-shell/reviewer-seat-self-test.d.ts +43 -0
  52. package/dist/orchestration-shell/team-decompose.d.ts +1 -0
  53. package/dist/orchestration-shell/types.d.ts +24 -0
  54. package/dist/reviewer/provider.d.ts +28 -0
  55. package/dist/reviewer/providers/antigravity.d.ts +4 -2
  56. package/dist/reviewer/providers/claude.d.ts +7 -2
  57. package/dist/reviewer/providers/codex.d.ts +1 -1
  58. package/dist/session-resume/cli-resume.d.ts +1 -0
  59. package/dist/substrate/bwrap.d.ts +85 -0
  60. package/dist/substrate/command-runner.d.ts +1 -0
  61. package/dist/substrate/docker.d.ts +21 -0
  62. package/dist/substrate/grant-rule.d.ts +71 -0
  63. package/dist/substrate/pasta-isolation-check.d.ts +60 -0
  64. package/dist/substrate/pasta.d.ts +8 -0
  65. package/dist/substrate/relay-channel.d.ts +80 -0
  66. package/dist/substrate/review-mask.d.ts +20 -0
  67. package/dist/substrate/sandbox-exec.d.ts +48 -1
  68. package/dist/substrate/seatbelt-grants.d.ts +46 -0
  69. package/dist/substrate/spawn-records.d.ts +95 -0
  70. package/dist/substrate/tier-relay-main.d.ts +1 -0
  71. package/dist/substrate/tier-relay.d.ts +7 -0
  72. package/dist/substrate/tier-relay.js +3 -0
  73. package/dist/substrate/types.d.ts +48 -1
  74. package/dist/substrate-launch/engage-substrate.d.ts +16 -2
  75. package/dist/substrate-launch/fallback-ladder.d.ts +109 -12
  76. package/dist/substrate-launch/index.d.ts +1 -1
  77. package/dist/substrate-launch/task-folder.d.ts +26 -0
  78. package/native/linux-arm64/codevibe-spawn-sweep +0 -0
  79. package/native/linux-x64/codevibe-spawn-sweep +0 -0
  80. package/native/spawn-sweep.json +8 -0
  81. package/package.json +10 -5
@@ -4,7 +4,7 @@ import { OrchestrationStore } from '../store';
4
4
  import { ConversationEntry, PostDecisionAction, Tier } from '../types';
5
5
  import type { PlannerRuntimeKind } from '../status-format';
6
6
  import type { UpdateReviewerPolicyInput, UserReviewerPolicySnapshot } from '../../types/reviewer';
7
- import { type ApplyUserDecisionClient as SharedApplyUserDecisionClient, type GroupDecisionSubmitDeps } from '../gate-decision-submit';
7
+ import { type ApplyUserDecisionClient as SharedApplyUserDecisionClient, type GateDecisionSubmitDeps, type GroupDecisionSubmitDeps } from '../gate-decision-submit';
8
8
  import { type CompanionSyncStateSource } from '../../appsync/sync-state';
9
9
  import { type CompanionSyncWarningStore } from '../../appsync/sync-usage-warning';
10
10
  /** P46b §3.7 — at most this many per-task progress lines render (`+n more` after). */
@@ -114,6 +114,11 @@ export interface OrchestrationAppProps {
114
114
  * (rejected) shadow, not the restart's fresh one. ABSENT → no capture (no-op).
115
115
  */
116
116
  captureShadow?: (taskId: string) => unknown;
117
+ /**
118
+ * Decision delivery after an unknown outcome: the same object the non-TTY path uses, so an ambiguous submit made on
119
+ * the screen is retried during the session as well (w6jheld: it stayed at "Confirming decision…" for 53 minutes).
120
+ */
121
+ decisionDelivery?: Pick<GateDecisionSubmitDeps, 'decisionAttemptReceiptStore' | 'onDecisionDeliveryAmbiguous' | 'acknowledgeDelayedRejection'>;
117
122
  /**
118
123
  * PHASE-589/469 W3.2 — the two-phase group-escalation decision transport.
119
124
  * Present ONLY for a TEAM session (built in `cli.ts`, completed in
@@ -70,3 +70,17 @@ export declare function recoverDecisionAttemptReceipts(args: {
70
70
  onResolutionEvent: (event: Event) => Promise<DurableGateResolution | null> | DurableGateResolution | null;
71
71
  onActionableRejection: (receipt: DecisionAttemptReceipt, rejectionCode: string) => void;
72
72
  }): Promise<void>;
73
+ export interface LiveDecisionAttemptRecovery {
74
+ schedule(): void;
75
+ stop(): void;
76
+ }
77
+ /**
78
+ * g3paths4: an ambiguous submit stays fenced "and will recover automatically", but only a shell start ran the recovery
79
+ * pass, so one backend error left an Approve stuck for the rest of the session. This runs the same pass during a session,
80
+ * the exact-resubmit it does being safe by design, until no receipt is left.
81
+ */
82
+ export declare function createLiveDecisionAttemptRecovery(args: {
83
+ recover: () => Promise<void>;
84
+ hasPending: () => Promise<boolean>;
85
+ delaysMs?: readonly number[];
86
+ }): LiveDecisionAttemptRecovery;
@@ -0,0 +1,98 @@
1
+ import { type ShadowChangeKind } from '../local-executor/shadow-protocol';
2
+ export declare const PROJECTION_BUDGET_BYTES: number;
3
+ export declare const PROJECTION_INLINE_MAX_BYTES: number;
4
+ export declare const PROJECTION_EXCERPT_MAX_BYTES: number;
5
+ export declare const PROJECTION_BRIEF_MAX_BYTES: number;
6
+ export declare const PROJECTION_SUMMARY_MAX_BYTES = 1024;
7
+ export declare const PROJECTION_MANIFEST_PATH = "CODEVIBE-MANIFEST";
8
+ /** One changed file as the projection sees it. Content is read lazily, so a large file is never held whole. */
9
+ export interface ProjectionFile {
10
+ path: string;
11
+ change_kind: ShadowChangeKind;
12
+ mode?: number;
13
+ /** Byte length of the full post-image (0 for a deletion). */
14
+ size: number;
15
+ /** SHA-256 (hex) of the full post-image ('' for a deletion). */
16
+ sha256: string;
17
+ /** The whole post-image; called only when `size` ≤ {@link PROJECTION_INLINE_MAX_BYTES}. */
18
+ readWhole(): Promise<string>;
19
+ /**
20
+ * The first `maxBytes` (raw, cut at a character boundary) of the excerpt source — the unified diff for a
21
+ * modification, the file for a creation. The identity line's `<size>` is the full post-image's, the same bytes its
22
+ * SHA-256 names, so a file listed by its identity line alone is never read or diffed.
23
+ */
24
+ readExcerpt(maxBytes: number): Promise<string>;
25
+ }
26
+ export interface ProjectedFile {
27
+ path: string;
28
+ change_kind: ShadowChangeKind;
29
+ content: string;
30
+ mode?: number;
31
+ }
32
+ export interface ProjectedPayload {
33
+ kind: 'execute_first_diff';
34
+ task_brief: string;
35
+ summary?: string;
36
+ files: ProjectedFile[];
37
+ }
38
+ export interface ProjectionInput {
39
+ taskBrief: string;
40
+ summary: string;
41
+ files: readonly ProjectionFile[];
42
+ /** Root hash of the change manifest (§9.4), named by a `CODEVIBE-MANIFEST` entry when the list is cut. */
43
+ manifestRootHash: string;
44
+ /** Test seam: the serialized budget. */
45
+ budgetBytes?: number;
46
+ }
47
+ /** Bytes a string takes inside the serialized payload (its JSON string literal, quotes included). */
48
+ export declare function serializedBytes(text: string): number;
49
+ /**
50
+ * §9.4 brief/summary rule: a field whose serialized form exceeds `max` is sent as
51
+ * `[codevibe-<label> v1] first <k> of <n> bytes; sha256 <hex>; review bundle: INDEX.md` followed by its longest
52
+ * prefix for which identity line and prefix together serialize to at most `max` (`<k>` = that prefix's raw bytes).
53
+ */
54
+ export declare function cutFieldToSerialized(text: string, max: number, label: 'brief' | 'summary'): string;
55
+ /**
56
+ * The files the engine receives in a payload this module built — what its #EF-1 empty-artifact rule sees (a large or
57
+ * excerpted file reaches it as an identity line, not as the raw capture's content). Throws on a payload it did not build.
58
+ */
59
+ export declare function projectedPayloadFiles(rawOutput: string): ProjectedFile[];
60
+ /**
61
+ * Build the engine payload: the change projected into at most `budgetBytes` of serialized JSON.
62
+ *
63
+ * Sizes are summed per entry, never by re-serializing the payload, and content is read only for files that can be
64
+ * sent: a change too large for its identity lines (roughly 160 files and more) reads nothing at all.
65
+ */
66
+ export declare function projectExecuteFirstPayload(input: ProjectionInput): Promise<string>;
67
+ /** Cut `text` to at most `maxBytes` raw bytes at a character boundary. */
68
+ export declare function headBytes(text: string, maxBytes: number): string;
69
+ /**
70
+ * The first `maxBytes` of a unified diff of `base` → `after`, computed in linear time: common leading and trailing
71
+ * lines are trimmed and the changed middle is shown as one hunk with 3 lines of context. An excerpt only — the review
72
+ * bundle holds the full diff (§8.1) — so a 16 MiB file never needs an LCS table.
73
+ */
74
+ export declare function excerptDiff(path: string, base: string, after: string, maxBytes: number): string;
75
+ /**
76
+ * A captured file as a projection input. §9.4: the capture holds references only, so a reference entry's size and
77
+ * SHA-256 come from its blob and its text is read only when the projection needs it — whole for an inline file
78
+ * (≤ 24 KiB serialized, so small), for the diff of an excerpt (≤ LARGE_TEXT_BYTES), never for a large file (its head).
79
+ */
80
+ export declare function projectionFileFromCapture(file: {
81
+ path: string;
82
+ change_kind: ShadowChangeKind;
83
+ content: string;
84
+ mode?: number;
85
+ blob?: {
86
+ blob: string;
87
+ size: number;
88
+ };
89
+ },
90
+ /** The pre-task text when the real file still matches its recorded base hash; null otherwise (S2-5). */
91
+ readBase: () => Promise<string | null>,
92
+ /**
93
+ * P63 Slice 2 (§9.4 W3d): the first bytes of a LARGE entry's blob. A large file is always over the inline bound, so
94
+ * only its excerpt — the head of the file, never a diff — is ever read.
95
+ */
96
+ readLargeHead?: (maxBytes: number) => Promise<Buffer>,
97
+ /** §9.4: the text of a reference entry up to LARGE_TEXT_BYTES, read from its blob (required for one). */
98
+ readText?: () => Promise<string>): ProjectionFile;
@@ -0,0 +1,18 @@
1
+ /** How long a clean `process.exit` may take before the watchdog ends it. */
2
+ export declare const EXIT_GUARD_GRACE_MS = 10000;
3
+ /**
4
+ * N5 (Stage 1 R2): the identity is read the same way here and in the watchdog — the same arguments (`-ww`: never cut
5
+ * to a terminal width) and the same environment (`LC_ALL=C` for `lstart`'s date format, no inherited `COLUMNS`) — so a
6
+ * difference in width or locale between this process and the detached watchdog can never make them disagree.
7
+ */
8
+ export declare const PS_IDENTITY_ARGS: readonly string[];
9
+ export declare const PS_IDENTITY_ENV: Readonly<Record<string, string>>;
10
+ /** `ps -ww -o lstart=,command=` for one pid — the identity the watchdog re-checks before it signals. */
11
+ export declare function processIdentity(pid: number): string | null;
12
+ /** The watchdog program (run by `process.execPath -e`): argv = pid, identity, grace ms. */
13
+ export declare const EXIT_GUARD_SCRIPT: string;
14
+ /**
15
+ * Arm the watchdog for this process, then `process.exit(code)`. Arming never throws: if it cannot start (no `ps`,
16
+ * spawn failure) the exit proceeds as before.
17
+ */
18
+ export declare function exitWithGuard(code: number, graceMs?: number): never;
@@ -35,6 +35,8 @@ export interface GateDecisionSubmitDeps {
35
35
  getSessionKey: (sessionId: string) => Promise<string | null>;
36
36
  /** Optional override for isolated tests; production uses the durable 0600 store. */
37
37
  decisionAttemptReceiptStore?: DecisionAttemptReceiptStoreSurface;
38
+ /** A submit's outcome is unknown and its receipt is fenced: production keeps retrying it during the session. */
39
+ onDecisionDeliveryAmbiguous?: () => void;
38
40
  /**
39
41
  * Production effect-completion barrier for a response that loses after the
40
42
  * winner Event was already classified. It returns false when no matching
@@ -124,6 +124,7 @@ export interface RunOrchestrationShellArgs {
124
124
  * (the resume entry point then releases P's resume lock).
125
125
  */
126
126
  resume?: {
127
+ predecessorSessionId: string;
127
128
  restoredItems: import('./context-items').ContextItem[];
128
129
  markerSeq: string;
129
130
  header: {
@@ -721,6 +722,11 @@ export declare function composeTeamTrackBrief(trackDescription: string, sourceBr
721
722
  export type LaunchTeamOutcome = {
722
723
  kind: 'accepted';
723
724
  taskGroupId: string;
725
+ }
726
+ /** P63 §6.4 — a track's agent cannot be confined here; the opt-in question was asked, nothing was created. */
727
+ | {
728
+ kind: 'confinement_refused';
729
+ reason: string;
724
730
  } | {
725
731
  kind: 'disjointness_rejected';
726
732
  rejection: DisjointnessRejection;
@@ -776,6 +782,8 @@ export declare function launchTeamFromWorkItems(deps: {
776
782
  attachmentPaths?: string[];
777
783
  /** P46b §3.10 — the failed group this launch retries (the new group is `#m retry`). */
778
784
  retryOf?: string;
785
+ /** P63 §6.4 — the user opted in to running this group's tracks unconfined (re-issued launch after the question). */
786
+ allowUnconfined?: boolean;
779
787
  }): Promise<LaunchTeamOutcome>;
780
788
  /**
781
789
  * PHASE-589/469 W3.2 RESIDUAL 1 — re-dispatch the WHOLE team for a
@@ -808,6 +816,16 @@ export declare function reissueTeamFromRetainedSpec(deps: {
808
816
  * from the backend rather than an opaque generic failure.
809
817
  * Sanitizes terminal escapes, redacts absolute paths, and caps display length.
810
818
  */
819
+ /** The confirm-before-start reply matcher: only these replies start a task (§6.4 reuses it for the opt-in). */
820
+ export declare function isAffirmativeStartReply(text: string): boolean;
821
+ /**
822
+ * P63 §6.4 — a START the loop refused because its implementor cannot be confined on this machine does not start: the
823
+ * desktop (and, through the planner question, the phone) says why and how to fix it, and offers to run it unconfined
824
+ * THIS ONCE. Any other refusal keeps today's advisory.
825
+ */
826
+ export declare function surfaceStartRefusal(store: OrchestrationStore, quorumLoop: QuorumLoop | undefined): void;
827
+ /** P63 §6.4 — asks the opt-in question when the loop's last START was a confinement refusal; true when it asked. */
828
+ export declare function askConfinementOptIn(store: OrchestrationStore, quorumLoop: QuorumLoop | undefined): boolean;
811
829
  export declare function formatStartTaskRejection(errorOrLoop: unknown): string;
812
830
  /**
813
831
  * PHASE-590 §3.D — the `team_decompose` routing path. Runs the local decomposer,
@@ -2,6 +2,7 @@ import * as fsSync from 'node:fs';
2
2
  import type { AppSyncClient, TeamTrackRecoveryRow } from '../appsync/appsync-client';
3
3
  import type { SyncSecurityPosture } from '../appsync/sync-security-posture';
4
4
  import type { Session } from '../types';
5
+ import { type ReviewerSeatSelfTestConfig } from './reviewer-seat-self-test';
5
6
  import type { LocalExecutor } from '../local-executor';
6
7
  import type { ClassBInbound, QueuedWorkEndedPayload } from '../local-executor/types';
7
8
  import type { ImageAttachment } from './types';
@@ -327,28 +328,9 @@ export interface ResolverIntentContext {
327
328
  * not import (they only help it converge).
328
329
  */
329
330
  declare function renderAgenticResolveBrief(conflictedPaths: readonly string[], implementorRationale?: string | null, intent?: ResolverIntentContext): string;
330
- /**
331
- * CP-7 §8 (Stage-1-resolved) — the narrow result the QuorumLoop consumes from
332
- * the reviewer substrate engager. Mirrors `SubstrateLaunch.EngageResult` /
333
- * `LocalExecutorImpl.SubstrateEngageResult` but is declared STRUCTURALLY here
334
- * (not importing the concrete `engageSubstrate`) so the loop module stays
335
- * decoupled and tests inject a stub. Two variants:
336
- * - `substrate` (the moat is ON for this seat — the loop threads
337
- * `substrateHandle` into `evaluate` + calls `teardown()` when the seat
338
- * completes, on EVERY path).
339
- * - `reduced_trust` (the moat is OFF for this seat — A5 coarse host with no
340
- * runtime / Gemini non-Trusted: the loop runs the reviewer on the LEGACY
341
- * unsandboxed path + surfaces the reduced-trust badge).
342
- *
343
- * THROW semantics (fail-closed): the engager THROWS for a Trusted agent when
344
- * the broker cannot start (or any non-`reduced_trust` engage failure). The
345
- * loop treats a throw as FAIL-CLOSED — it does NOT silently fall back to the
346
- * ambient-cred legacy path (that would defeat the moat); it synthesizes a
347
- * clean ESCALATE so the gate resolves rather than hangs.
348
- */
349
331
  export type ReviewerSubstrateEngageResult = {
350
332
  mode: 'substrate';
351
- tier: 'docker' | 'sandbox_exec';
333
+ tier: 'docker' | 'sandbox_exec' | 'bubblewrap';
352
334
  reducedTrust: boolean;
353
335
  reducedTrustReason?: string;
354
336
  substrateHandle: SubstrateHandle;
@@ -376,8 +358,24 @@ export type ReviewerSubstrateEngager = (input: {
376
358
  taskId: string;
377
359
  /** The reviewer's lowercase agent (the loop maps it to UPPERCASE inside the wiring). */
378
360
  agent: ReviewerAgentKind;
379
- /** Canonical host workdir the sandbox mounts (the loop's working dir). */
361
+ /** Canonical host workdir the sandbox mounts — P63 Slice 2 (§8.2): the seat's private scratch. */
380
362
  workdir: string;
363
+ /** P63 Slice 2 (§8.2) — the seat's read-only grants (review bundle; macOS: masked original). */
364
+ reviewGrants?: {
365
+ bundleDir: string;
366
+ maskedOriginal?: {
367
+ dir: string;
368
+ changedWithheld: readonly string[];
369
+ };
370
+ };
371
+ /**
372
+ * P63 Slice 2 (§8.2) — an Antigravity review seat engages the agy write-confined row (private home, writes = its
373
+ * scratch + that home, reads = the review grants) instead of the unconfined legacy path. Set only for agy seats with a
374
+ * review round; the wiring maps it to `engageSubstrate({ requireConfined: true })`.
375
+ */
376
+ requireConfined?: boolean;
377
+ /** P63 §6.1 / §6.2 — the user's real project folder (the grant rule hides it and never grants a root containing it). */
378
+ projectRoot?: string;
381
379
  }) => Promise<ReviewerSubstrateEngageResult>;
382
380
  export type ReviewerSafetyNoticeSink = (message: string, posture: SyncSecurityPosture) => Promise<void>;
383
381
  export interface QuorumLoopDeps {
@@ -434,6 +432,29 @@ export interface QuorumLoopDeps {
434
432
  * when this engager is wired (see `reviewerSandboxingEnabled`).
435
433
  */
436
434
  engageReviewerSubstrate?: ReviewerSubstrateEngager;
435
+ /** P63 §6.4 — the read-only tier self-test before a reviewer's first confined seat. Absent → none (tests, embedders). */
436
+ reviewerSeatSelfTest?: ReviewerSeatSelfTestConfig;
437
+ /**
438
+ * P63 Slice 2 (§8.1–§8.2) — the per-task folder that holds a task's review rounds (`review/<gate>/{bundle,seat-<n>}`).
439
+ * Production wires `taskFolderFor` (Stream A's folder). Absent → no review round: seats keep today's working
440
+ * directory (the pre-Slice-2 path, byte-identical — and no test ever writes into the user's real cache).
441
+ */
442
+ reviewTaskFolder?: (taskId: string) => string;
443
+ /**
444
+ * P63 §6.4 — can an implementor of this agent be confined on this machine? Asked by `startTask` before a task
445
+ * starts (production wires `SubstrateLaunch.confinementPreflight`). A task whose agent cannot be confined does not
446
+ * start; the shell asks the user whether to run it unconfined this once (`takeConfinementRefusal`). Absent → no
447
+ * preflight (tests); the LE still refuses any unconfined spawn the user did not opt into.
448
+ */
449
+ confinementPreflight?: (input: {
450
+ agentKind: AgentKind;
451
+ webTurn: boolean;
452
+ }) => Promise<{
453
+ confinable: true;
454
+ } | {
455
+ confinable: false;
456
+ reason: string;
457
+ }>;
437
458
  /**
438
459
  * A1e (P3 slice-wiring REMAP) — compose the bounded repository-context
439
460
  * block for a task dispatch: the resolved `context_ref` structural slice
@@ -527,6 +548,8 @@ export interface QuorumLoopDeps {
527
548
  reapOrphanShadows?: (keepTaskIds: ReadonlySet<string>, env: ShadowEnv) => Promise<{
528
549
  reaped: string[];
529
550
  retained: string[];
551
+ spawnBlockedTaskIds?: ReadonlySet<string>;
552
+ spawnSweepFailed?: boolean;
530
553
  }>;
531
554
  };
532
555
  /**
@@ -771,6 +794,8 @@ export interface ApplyConflictMenu {
771
794
  agent: AgentKind;
772
795
  }
773
796
  export declare class QuorumLoop {
797
+ /** P63 §6.4 — one reviewer-seat self-test per key at a time. */
798
+ private readonly seatSelfTestsInFlight;
774
799
  private readonly deps;
775
800
  private readonly sleep;
776
801
  private workspaceOutcomeSink?;
@@ -814,6 +839,41 @@ export declare class QuorumLoop {
814
839
  private lastStartError;
815
840
  /** Retrieve the last error message from a rejected or failed startTask call. */
816
841
  getLastStartError(): string | null;
842
+ /**
843
+ * P63 §6.4 — the last START refused because its agent cannot be confined here: the reason, and the exact start
844
+ * arguments, so the user's "yes, run it unconfined this once" re-issues that same start with `allowUnconfined`.
845
+ * Taking it clears it.
846
+ */
847
+ private lastConfinementRefusal;
848
+ takeConfinementRefusal(): {
849
+ reason: string;
850
+ startArgs: Parameters<QuorumLoop['startTask']>[0];
851
+ } | null;
852
+ /** P63 §6.4 — tasks the user opted into running unconfined (their implementor spawns carry `allowUnconfined`). */
853
+ private readonly unconfinedOptInTasks;
854
+ isUnconfinedOptIn(taskId: string): boolean;
855
+ /**
856
+ * S2-C3 (Stage 2 R1) — tasks reopened after a crash whose earlier run was opted in to run unconfined (their task
857
+ * intent says so). History only: it keeps the after-run folder check and the summary's disclosure; it never lets a
858
+ * spawn run unconfined (`unconfinedOptInTasks` alone does that).
859
+ */
860
+ private readonly ranUnconfinedBeforeReopen;
861
+ /** §6.4 — this task's work was done by an agent running unconfined (opted in now, or before a crash reopened it). */
862
+ private ranUnconfined;
863
+ /** P63 §6.4 — Agent Teams groups the user opted into running unconfined (every track spawn carries the opt-in). */
864
+ private readonly unconfinedOptInGroups;
865
+ markTeamGroupUnconfined(taskGroupId: string): void;
866
+ /**
867
+ * P63 §6.4 — can this agent's implementor be confined here for this request? The same web decision the spawn makes
868
+ * (`isWebAccessNeeded` over the user's own request, Claude/agy only). No preflight wired → confinable (the LE still
869
+ * refuses any unconfined spawn without the opt-in).
870
+ */
871
+ preflightConfinement(agent: AgentKind, request: string): Promise<{
872
+ confinable: true;
873
+ } | {
874
+ confinable: false;
875
+ reason: string;
876
+ }>;
817
877
  /** Non-terminal tasks for which `/review-reset` may arm the next revise round. */
818
878
  private readonly reviewScopeResetEligibleTasks;
819
879
  private taskEpoch;
@@ -886,6 +946,16 @@ export declare class QuorumLoop {
886
946
  * START call (in flight) until the task's terminal cleanup; `retryOf` links a
887
947
  * `#n retry` task to its `apply_failed` original (P46a C2/C3).
888
948
  */
949
+ /** F-RESTART-NOT-RESUMED (§17) — the session this one resumes; its stopped tasks are reopened at startup. */
950
+ private resumedFromSessionId;
951
+ /** The planner decision behind each single task, kept for its durable intent record. */
952
+ private readonly startDecisionByTask;
953
+ /** A reopened task's first round: no agent runs, the copy it inherited is captured as this round's work. */
954
+ private readonly reopenedRoundTasks;
955
+ /** Instances reopened under a new task id; a later recovery pass keeps them and never registers them twice. */
956
+ private readonly reopenedStateFiles;
957
+ private readonly reopenedFromTaskIds;
958
+ private readonly reopenedTaskIds;
889
959
  private readonly singleTaskContextByTask;
890
960
  /** Agent web browsing (upstream 2.0.37) — whether a task's ORIGINAL request needs web access; ends with the task (`forgetSingleTask`, team cleanup). */
891
961
  private readonly webAccessByTask;
@@ -1448,6 +1518,39 @@ export declare class QuorumLoop {
1448
1518
  * instance on promote/discard.
1449
1519
  */
1450
1520
  private readonly reviewedSnapshotByTask;
1521
+ /**
1522
+ * P63 Slice 2 (§8.1–§8.2) — one review round per gate: the read-only bundle every seat reads (built once, before the
1523
+ * first seat) and its hash record for the return check. Null for a gate with no reviewed snapshot (e.g. a merge
1524
+ * gate) — those seats keep today's working directory.
1525
+ */
1526
+ private readonly reviewStateRemovalByTask;
1527
+ private readonly reviewRounds;
1528
+ /**
1529
+ * S2-C1 — per gate, each running seat's current attempt: the round generation it reviews, and a promise settled when
1530
+ * its return check is over (or the seat ends). A seat whose check passed waits for the others of its generation, so a
1531
+ * changed bundle found by any of them discards every verdict of that generation, not only the finder's.
1532
+ */
1533
+ private readonly seatChecksByGate;
1534
+ /**
1535
+ * R2-F1 / R3-F1 (Stage 2 R3) — per gate, the seats whose terminal verdict the engine has CONFIRMED (the submission
1536
+ * returned). Only these count as finished at the gate barrier: a seat whose run ended without a confirmed submission
1537
+ * (a failed submit, a dropped spawn) can still be re-spawned by recovery, so it stays pending.
1538
+ */
1539
+ private readonly confirmedSeatsByGate;
1540
+ /**
1541
+ * R4-F1 / R5-F1 (Stage 2 R4/R5, Hendry's rules 2/3) — a seat whose verdict could not be CONFIRMED after its retries:
1542
+ * the verdict it owes is its OWN original verdict and generation, never a substitute. A lost acknowledgement may mean
1543
+ * the engine already holds it (the first verdict per seat wins), so any resend must be the same verdict. A recovery
1544
+ * re-spawn and the timed resend (R5-F2) send it again; the seat is never reviewed again.
1545
+ */
1546
+ private readonly pendingVerdictBySeat;
1547
+ /** R5-F2 — the timed resend of each owed verdict (task-owned; cleared on confirmation, an abandoned gate or shutdown). */
1548
+ private readonly owedVerdictResendTimers;
1549
+ /** R5-F2 — the wait between timed resends of an owed verdict, and how many are tried (test-overridable). */
1550
+ private owedVerdictResendIntervalMs;
1551
+ private owedVerdictResendTries;
1552
+ /** R4-F1 — the waits between a failed verdict send and its retries (test-overridable). */
1553
+ private verdictSendRetryDelaysMs;
1451
1554
  /** Guards concurrent shadow-create for the same task (round-0 + an early re-deliver). */
1452
1555
  private readonly shadowCreateInFlight;
1453
1556
  /**
@@ -1474,6 +1577,10 @@ export declare class QuorumLoop {
1474
1577
  private readonly backgroundRecoveryAbortControllers;
1475
1578
  /** Once set, no new implementor workspace may be created for this session. */
1476
1579
  private shuttingDown;
1580
+ /** §9.3: the next 10-second retry of each task with a no-link step left pending. */
1581
+ private readonly noLinkRestoreRetries;
1582
+ /** What the attempt that left a step pending knew and cleared; the retry's promotion is the same approved task. */
1583
+ private readonly noLinkRetryContexts;
1477
1584
  private awaitBackgroundRecoveryStep;
1478
1585
  /**
1479
1586
  * Serialize one shadow lifecycle op for `taskId` behind any in-flight op for
@@ -1555,6 +1662,16 @@ export declare class QuorumLoop {
1555
1662
  conflicts: readonly string[];
1556
1663
  writeErrors: readonly string[];
1557
1664
  }): void;
1665
+ /** The team's changes could not be merged: nothing was applied, so there is no menu — the notice names the files. */
1666
+ reportTeamMergeConflict(info: {
1667
+ taskGroupId: string;
1668
+ paths: readonly string[];
1669
+ workLimit: boolean;
1670
+ }): void;
1671
+ /** The merge could not be finished: without this the team only shows pending, with nothing to say why. */
1672
+ reportTeamMergeUnresolved(info: {
1673
+ taskGroupId: string;
1674
+ }): void;
1558
1675
  /** P46b §3.13 item 1 — install the task lifecycle sink (the shell). */
1559
1676
  setTaskLifecycleSink(sink: ((event: TaskLifecycleEvent) => void) | undefined): void;
1560
1677
  /** P46b — notify the lifecycle sink; a throwing sink never breaks the loop. */
@@ -1758,6 +1875,17 @@ export declare class QuorumLoop {
1758
1875
  * read this instead. Omitted for an ordinary task (its brief IS the request).
1759
1876
  */
1760
1877
  request?: string;
1878
+ /**
1879
+ * P63 §6.4 — the user answered yes to running this task unconfined because its agent cannot be confined here.
1880
+ * Skips the preflight and marks the task so its implementor spawns may run without a confining tier.
1881
+ */
1882
+ allowUnconfined?: boolean;
1883
+ /** F-RESTART-NOT-RESUMED — this task continues a stopped one in the copy it left; no agent runs in round 0. */
1884
+ reopen?: {
1885
+ fromTaskId: string;
1886
+ shadow: WorkspaceShadow;
1887
+ ranUnconfined?: boolean;
1888
+ };
1761
1889
  }): Promise<StartTaskResultInfo | null>;
1762
1890
  /**
1763
1891
  * CP-1.f Wave 3 FIX — consume a GATE_DISPATCH packet. The engine emits this
@@ -2011,6 +2139,19 @@ export declare class QuorumLoop {
2011
2139
  * server-side is the one lossy edge (the engine may revise while the desktop
2012
2140
  * cleared) — that degrades to the pre-A1b contextless brief, never worse.
2013
2141
  */
2142
+ /**
2143
+ * P63 Slice 2 (§9.4) — the execute-first payload for a captured change. A modified file's excerpt is its diff
2144
+ * against the pre-task text, read from the real folder only while that file still has its recorded base hash
2145
+ * (ledger S2-5); otherwise the excerpt is the head of the new content.
2146
+ */
2147
+ private projectSubmission;
2148
+ /**
2149
+ * P63 §6.4 — an implementor spawn the LE refused because it cannot be confined and the user did not opt in. The
2150
+ * task stops visibly: a team track is reported failed; a single task's round submits the empty artifact, which the
2151
+ * engine born-halts (so the hosted task does not wait for the sweeper). The copy is discarded either way; the real
2152
+ * folder was never touched.
2153
+ */
2154
+ private haltRefusedImplementorSpawn;
2014
2155
  private reportTeamTrackFailure;
2015
2156
  /**
2016
2157
  * No-op UX (#1) — report the BENIGN no-change terminal for a TEAM track that
@@ -2255,6 +2396,9 @@ export declare class QuorumLoop {
2255
2396
  */
2256
2397
  private promoteApplyLocked;
2257
2398
  /** #SR-2 — surface a halt/fail-closed message to the user (or log if unwired). */
2399
+ private scheduleNoLinkRestoreRetry;
2400
+ /** §9.3: silent while the step stays pending. Once none is, the ordinary promotion finishes the task and reports it. */
2401
+ private retryNoLinkRestore;
2258
2402
  private surfaceHalt;
2259
2403
  /**
2260
2404
  * PHASE-598 — the thin wiring that supplies the real fs/shell deps to the pure
@@ -2378,7 +2522,64 @@ export declare class QuorumLoop {
2378
2522
  * `teardown()` is ALWAYS safe to call (a no-op on the legacy path); the caller
2379
2523
  * invokes it in a `finally` so a sandbox never leaks.
2380
2524
  */
2525
+ /**
2526
+ * P63 Slice 2 (§8.1) — build (once per gate) the read-only review bundle in the task folder from the reviewed
2527
+ * snapshot's verified blobs; null when the gate has no reviewed snapshot or no shadow (merge gates, legacy paths).
2528
+ * The round folder is task-owned scratch: a previous attempt's bundle for the same gate is replaced, never reused.
2529
+ */
2530
+ /**
2531
+ * P63 Slice 2 (§8.1–§8.2, W5 step 5) — drop a task's shadow-map entry AND schedule removal of its review rounds and
2532
+ * capture dirs (`<task folder>/review/`, `<task folder>/capture/`): every path that ends a task's workspace (promote,
2533
+ * discard, halt, restart-replace) goes through here, so full-copy bundles never outlive their task.
2534
+ */
2535
+ private forgetTaskShadow;
2536
+ /** Serialized per task; a new round for the task waits for it (`ensureReviewRound`). Never rejects. */
2537
+ private scheduleTaskReviewStateRemoval;
2538
+ /** @internal — await any scheduled review-state removal for a task (tests). */
2539
+ _awaitReviewStateRemovalForTests(taskId: string): Promise<void>;
2540
+ private ensureReviewRound;
2541
+ /**
2542
+ * §8.1 / §8.5 — a seat's return check: the bundle still matches the hashes recorded when it was built (nothing
2543
+ * else in it), then every receipt request is answered correctly. null = accepted.
2544
+ */
2545
+ private checkSeatReturn;
2546
+ /**
2547
+ * §8.1 — a bundle found changed is rebuilt IN PLACE from the same verified blobs (only `bundle/`: the round folder
2548
+ * also holds the other seats' live scratch), and the gate moves to a new generation (S2-C1): every seat that reviewed
2549
+ * the old one is re-dispatched. Serialized on the gate's round promise; a seat that saw an older generation than the
2550
+ * current one reuses the rebuild another seat already did. The rebuild is byte-identical.
2551
+ */
2552
+ private rebuildReviewBundle;
2553
+ /** S2-C1 — record this seat's attempt on the gate's generation (replacing its previous attempt). */
2554
+ private beginSeatCheck;
2555
+ /** S2-C1 — this seat's return check is over (idempotent). With `remove`, the seat has ended. */
2556
+ private settleSeatCheck;
2557
+ /**
2558
+ * S2-C1 / R2-F1 (Stage 2 R2) — §8.1 "any difference discards the gate's verdicts and re-dispatches the seats", for
2559
+ * the WHOLE gate. After this seat's own check passed it is accepted only when:
2560
+ * 1. the engine's own list of the seats assigned to this gate (`getInReviewAssignments`, the dropped-packet recovery
2561
+ * primitive) can be read and contains this seat;
2562
+ * 2. every OTHER assigned seat has either a CONFIRMED terminal verdict (R3-F1) or settled a return check at this
2563
+ * same generation — a seat not yet
2564
+ * registered (its dispatch or prompt fetch is slow), or still on an older generation, is waited for; one on a
2565
+ * newer generation means the gate already moved on;
2566
+ * 3. the gate's round still exists and is still at this generation.
2567
+ * Anything else — the list unreadable, this seat missing from it, the round missing or its rebuild failed, or the wait
2568
+ * passing one seat timeout — is NOT a hold: the verdict is discarded (re-dispatch, or ESCALATE on the second attempt).
2569
+ */
2570
+ private gateGenerationHolds;
2381
2571
  private engageSeatSubstrate;
2572
+ /** P63 §6.4 — why this confined seat may not run (its read-only tier self-test failed), or null. */
2573
+ private seatSelfTestFailure;
2574
+ /**
2575
+ * P63 Slice 2 (§8.2) — engage an Antigravity review seat in the agy write-confined row (macOS Seatbelt with a
2576
+ * private home per spawn): writes = the seat's scratch + that home; reads = the bundle (+ the masked original on
2577
+ * macOS). Where the row cannot be established (no sandbox tier on this host, or the container tier) the seat runs
2578
+ * on the legacy path with the reduced-trust badge — the same posture a Claude or Codex seat gets on such a host —
2579
+ * and its provider still points agy at the bundle, not the user's folder. An engage FAILURE fails closed (the
2580
+ * caller turns the throw into a clean ESCALATE).
2581
+ */
2582
+ private engageConfinedAgySeat;
2382
2583
  /**
2383
2584
  * CP-7 §8 (Stage-1-resolved) — surface the PROMINENT reduced-trust badge for a
2384
2585
  * reviewer seat (mirrors the implementor's badge, scoped to the loop's
@@ -2544,6 +2745,37 @@ export declare class QuorumLoop {
2544
2745
  * @param reasoning the final clean wire `reasoning` (already a fixed template).
2545
2746
  */
2546
2747
  private synthesizeEscalate;
2748
+ /**
2749
+ * R4-F1 / R5-F1 (Hendry's rules 2/3): send a seat's verdict. When it cannot be CONFIRMED even after its retries, the
2750
+ * seat owes that same verdict: the user is told the gate is waiting for it (not that it escalated: the engine may already
2751
+ * hold it), and it is sent again by a timed, task-owned resend (R5-F2) and by any recovery re-spawn.
2752
+ */
2753
+ private deliverVerdict;
2754
+ /** R5-F2 — resend an owed verdict on a timer, independent of any reconnect; bounded, stops when it no longer applies. */
2755
+ private scheduleOwedVerdictResend;
2756
+ /**
2757
+ * R4-F1 / R5-F1/F2 — send a seat's owed verdict again (the same verdict and generation). True once confirmed, or when it
2758
+ * no longer applies (the gate was abandoned, or nothing is owed); false to try again later.
2759
+ */
2760
+ private resendOwedVerdict;
2761
+ /** @internal test seam (R5-F2): the timed resend's interval and number of tries. */
2762
+ _setOwedVerdictResendForTests(intervalMs: number, tries: number): void;
2763
+ /**
2764
+ * R4-F1 (Hendry's rule 1): another seat of this gate already has a CONFIRMED verdict about an older generation than
2765
+ * `newest`. The engine keeps the first verdict per seat, so that verdict cannot be replaced by one about the changed
2766
+ * files; the gate must not be decided by it, and the seat that found the change ESCALATEs.
2767
+ */
2768
+ private olderVerdictRecorded;
2769
+ /** @internal test seam (R4-F1): record that the engine confirmed a seat's verdict about `generation`. */
2770
+ _recordConfirmedVerdictForTests(gateId: string, seatId: number, generation: number): void;
2771
+ /** @internal test seam (R4-F1): the waits between verdict send retries. */
2772
+ _setVerdictSendRetryDelaysForTests(delays: readonly number[]): void;
2773
+ /**
2774
+ * Returns true once the engine has confirmed this seat's verdict (now or earlier). R4-F1 (Hendry's rule 2): a failed
2775
+ * send is retried with the SAME verdict after each of `verdictSendRetryDelaysMs`; only when every try failed does it
2776
+ * return false (the caller then escalates). `generation` is the review round generation the verdict is about (null
2777
+ * for a seat without a review round).
2778
+ */
2547
2779
  private submitVerdict;
2548
2780
  /**
2549
2781
  * On WS (re)connect/startup, poll for the seats assigned to this desktop for
@@ -2574,6 +2806,16 @@ export declare class QuorumLoop {
2574
2806
  recoverShadows(opts?: {
2575
2807
  drainOnShutdown?: boolean;
2576
2808
  }): Promise<void>;
2809
+ setResumedFromSession(sessionId: string): void;
2810
+ /** The copy stays on disk for a later resume, and this session's exit cleanup no longer owns it. */
2811
+ private detachStoppedTask;
2812
+ /**
2813
+ * F-RESTART-NOT-RESUMED (§17 Restart, §16 C3/C4; Hendry 2026-11-02, option 1): the engine binds a task to its session,
2814
+ * so a stopped task continues as a new task of this session, on the copy it left. Round 0 runs no agent: the copy is
2815
+ * captured as usual and goes through the normal review and gate. The intent is rewritten first, so a second crash
2816
+ * here is resumed from this session.
2817
+ */
2818
+ private reopenStoppedTask;
2577
2819
  private recoverShadowsOwned;
2578
2820
  /**
2579
2821
  * Session-exit ownership boundary. Stop future creates, wait behind every
@@ -2591,6 +2833,8 @@ export declare class QuorumLoop {
2591
2833
  * unresolved authority are preserved, but returned as explicit failures.
2592
2834
  */
2593
2835
  private cleanupOwnedTaskSnapshots;
2836
+ /** F-EXIT-WAITS-CAPTURE (tick): the copies with a progress-count scan in flight (one at a time per copy). */
2837
+ private readonly tickScansInFlight;
2594
2838
  disposeWorkspaces(opts?: {
2595
2839
  strict?: boolean;
2596
2840
  }): Promise<void>;