@quantiya/codevibe-core 2.0.92 → 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 (79) 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-state.d.ts +110 -110
  10. package/dist/index.js +2605 -2306
  11. package/dist/local-executor/anchored-cas.d.ts +78 -6
  12. package/dist/local-executor/anchored-fs.d.ts +32 -0
  13. package/dist/local-executor/blob-store.d.ts +95 -0
  14. package/dist/local-executor/change-manifest-store.d.ts +32 -0
  15. package/dist/local-executor/change-manifest.d.ts +50 -0
  16. package/dist/local-executor/durable-store.d.ts +72 -15
  17. package/dist/local-executor/implementor-argv.d.ts +43 -7
  18. package/dist/local-executor/local-executor-impl.d.ts +57 -1
  19. package/dist/local-executor/nolink-apply.d.ts +22 -2
  20. package/dist/local-executor/revert-runner.d.ts +3 -0
  21. package/dist/local-executor/shadow-apply-capabilities.d.ts +31 -2
  22. package/dist/local-executor/shadow-free-space.d.ts +25 -0
  23. package/dist/local-executor/shadow-protocol.d.ts +117 -18
  24. package/dist/local-executor/shadow-recovery.d.ts +28 -3
  25. package/dist/local-executor/shadow-task-intent.d.ts +29 -0
  26. package/dist/local-executor/shadow-volume-probe.d.ts +0 -14
  27. package/dist/local-executor/sharded-list.d.ts +32 -0
  28. package/dist/local-executor/snapshot-merge.d.ts +18 -0
  29. package/dist/local-executor/spawn-sweep-binary.d.ts +33 -0
  30. package/dist/local-executor/spawn-sweep.d.ts +3 -0
  31. package/dist/local-executor/store-dir-anchor.d.ts +48 -0
  32. package/dist/local-executor/tier-self-test.d.ts +57 -0
  33. package/dist/local-executor/types.d.ts +26 -0
  34. package/dist/local-executor/verification-gates/diff-sanity.d.ts +6 -0
  35. package/dist/local-executor/workspace-materializer.d.ts +17 -1
  36. package/dist/local-executor/workspace-shadow.d.ts +209 -7
  37. package/dist/orchestration-shell/audit-summary.d.ts +22 -0
  38. package/dist/orchestration-shell/bounded-shutdown.d.ts +2 -0
  39. package/dist/orchestration-shell/cli.js +64053 -57609
  40. package/dist/orchestration-shell/components/OrchestrationApp.d.ts +6 -1
  41. package/dist/orchestration-shell/decision-attempt-recovery.d.ts +14 -0
  42. package/dist/orchestration-shell/engine-projection.d.ts +98 -0
  43. package/dist/orchestration-shell/exit-guard.d.ts +18 -0
  44. package/dist/orchestration-shell/gate-decision-submit.d.ts +2 -0
  45. package/dist/orchestration-shell/index.d.ts +18 -0
  46. package/dist/orchestration-shell/quorum-loop.d.ts +265 -21
  47. package/dist/orchestration-shell/review-bundle.d.ts +77 -0
  48. package/dist/orchestration-shell/review-receipts.d.ts +100 -0
  49. package/dist/orchestration-shell/reviewer-seat-self-test.d.ts +43 -0
  50. package/dist/orchestration-shell/team-decompose.d.ts +1 -0
  51. package/dist/orchestration-shell/types.d.ts +24 -0
  52. package/dist/reviewer/provider.d.ts +28 -0
  53. package/dist/reviewer/providers/antigravity.d.ts +4 -2
  54. package/dist/reviewer/providers/claude.d.ts +7 -2
  55. package/dist/reviewer/providers/codex.d.ts +1 -1
  56. package/dist/session-resume/cli-resume.d.ts +1 -0
  57. package/dist/substrate/bwrap.d.ts +85 -0
  58. package/dist/substrate/command-runner.d.ts +1 -0
  59. package/dist/substrate/docker.d.ts +21 -0
  60. package/dist/substrate/grant-rule.d.ts +71 -0
  61. package/dist/substrate/pasta-isolation-check.d.ts +60 -0
  62. package/dist/substrate/pasta.d.ts +8 -0
  63. package/dist/substrate/relay-channel.d.ts +80 -0
  64. package/dist/substrate/review-mask.d.ts +20 -0
  65. package/dist/substrate/sandbox-exec.d.ts +48 -1
  66. package/dist/substrate/seatbelt-grants.d.ts +46 -0
  67. package/dist/substrate/spawn-records.d.ts +95 -0
  68. package/dist/substrate/tier-relay-main.d.ts +1 -0
  69. package/dist/substrate/tier-relay.d.ts +7 -0
  70. package/dist/substrate/tier-relay.js +3 -0
  71. package/dist/substrate/types.d.ts +48 -1
  72. package/dist/substrate-launch/engage-substrate.d.ts +16 -2
  73. package/dist/substrate-launch/fallback-ladder.d.ts +109 -12
  74. package/dist/substrate-launch/index.d.ts +1 -1
  75. package/dist/substrate-launch/task-folder.d.ts +26 -0
  76. package/native/linux-arm64/codevibe-spawn-sweep +0 -0
  77. package/native/linux-x64/codevibe-spawn-sweep +0 -0
  78. package/native/spawn-sweep.json +8 -0
  79. package/package.json +10 -5
@@ -0,0 +1,77 @@
1
+ import { type BlobStore } from '../local-executor/blob-store';
2
+ import type { BlobRef } from '../local-executor/change-manifest';
3
+ import { type ShadowChangeKind } from '../local-executor/shadow-protocol';
4
+ export interface ReviewBundleFile {
5
+ path: string;
6
+ change_kind: ShadowChangeKind;
7
+ /** The new version (null for a deletion). */
8
+ after: {
9
+ store: BlobStore;
10
+ ref: BlobRef;
11
+ } | null;
12
+ /** The base version (null for a created file, and for a base the user changed since the task started). */
13
+ before: {
14
+ store: BlobStore;
15
+ ref: BlobRef;
16
+ } | null;
17
+ /** R1-F6: the permission bits before and after (null where the side does not exist or is unknown). */
18
+ modes?: {
19
+ before: number | null;
20
+ after: number | null;
21
+ };
22
+ /**
23
+ * R1-F6 / R1-F7: the recorded base's SHA-256 and size, and whether the user's folder no longer holds it (so `before`
24
+ * is null although the file existed at task start). The INDEX says so instead of diffing against nothing.
25
+ */
26
+ base?: {
27
+ sha256: string;
28
+ size: number | null;
29
+ changed: boolean;
30
+ };
31
+ }
32
+ /** §5.7 / §7.4 — what the INDEX lists besides the changed files (R1-F6). */
33
+ export interface ReviewIndexContext {
34
+ /** Private folders in the task copy — written to, never applied — with their §5.7 group. */
35
+ privateFolders: ReadonlyArray<{
36
+ path: string;
37
+ group: 'hidden' | 'dependencies' | 'rebuild-output' | 'codevibe';
38
+ }>;
39
+ /** Links, sockets, FIFOs and devices in the task copy — never applied. */
40
+ notApplicable: readonly string[];
41
+ /** §7.4 — the user's own changes to their folder since the task started (absent when it could not be counted). */
42
+ folderChanges?: {
43
+ changed: number;
44
+ created: number;
45
+ deleted: number;
46
+ };
47
+ }
48
+ /** At most this many names per INDEX list; the counts are always complete. */
49
+ export declare const REVIEW_INDEX_LIST_LIMIT = 200;
50
+ export interface ReviewBundleRecord {
51
+ version: 1;
52
+ /** Every file in the bundle, by its bundle-relative name (forward slashes), sorted. */
53
+ files: Array<{
54
+ name: string;
55
+ sha256: string;
56
+ size: number;
57
+ }>;
58
+ }
59
+ /** Build the bundle. `bundleDir` must not exist; its parent must. Returns the hash record to re-check on return. */
60
+ export declare function buildReviewBundle(input: {
61
+ bundleDir: string;
62
+ taskBrief: string;
63
+ files: readonly ReviewBundleFile[];
64
+ context?: ReviewIndexContext;
65
+ }): Promise<ReviewBundleRecord>;
66
+ /**
67
+ * Remove a task-owned review round folder (bundle + seat scratch) — the bundle is read-only, so write permission is
68
+ * restored first. Never follows a link: entries are lstat'ed and only directories are walked. Absent → no-op.
69
+ */
70
+ export declare function removeReviewRound(roundDir: string): Promise<void>;
71
+ /** §8.1 return check: every recorded file present with its hash, and nothing else in the bundle. */
72
+ export declare function verifyReviewBundle(bundleDir: string, record: ReviewBundleRecord): Promise<{
73
+ ok: true;
74
+ } | {
75
+ ok: false;
76
+ reason: string;
77
+ }>;
@@ -0,0 +1,100 @@
1
+ import type { ShadowChangeKind } from '../local-executor/shadow-protocol';
2
+ export declare const MAX_RECEIPTS_PER_SEAT = 12;
3
+ /** A change larger than this is not fully visible in the engine's inline excerpt — drawn first (§8.6). */
4
+ export declare const RECEIPT_PRIORITY_CHANGE_BYTES: number;
5
+ /**
6
+ * R1-F4 — a line longer than this is never requested: no reviewer can copy a minified file's megabyte line, so such a
7
+ * change falls back to the INDEX request. Lines are scanned as byte ranges, so a line's length never costs memory.
8
+ */
9
+ export declare const MAX_RECEIPT_LINE_BYTES = 512;
10
+ /**
11
+ * §8.1 — above this a version's changed lines are also placed in `large/`, in parts of at most this size: agy's file
12
+ * reader refuses a file over 100 MB (measured, agy 1.4.12) and a confined seat has no command tool (D-11).
13
+ */
14
+ export declare const LARGE_VIEW_FILE_BYTES: number;
15
+ export interface ReceiptFile {
16
+ path: string;
17
+ change_kind: ShadowChangeKind;
18
+ /** Full SHA-256 of the new version (the base version's for a deletion) — the INDEX lists its 12-char prefix. */
19
+ sha256: string;
20
+ hasAfter: boolean;
21
+ hasBefore: boolean;
22
+ }
23
+ export interface ReceiptRequest {
24
+ id: string;
25
+ kind: 'line' | 'index';
26
+ /** Bundle-relative file the request names (`after/<p>`, `before/<p>` or `INDEX.md`). */
27
+ file: string;
28
+ /** 1-based line number (kind `line`). */
29
+ line?: number;
30
+ /** What the reply must equal (line: whitespace-normalised) or contain (index: the SHA-256 prefix). */
31
+ expected: string;
32
+ /** The request as the prompt states it. */
33
+ text: string;
34
+ }
35
+ /** Collapse runs of whitespace and trim — the comparison §8.4 calls "exact (whitespace-normalised)". */
36
+ export declare function normaliseReceiptValue(v: string): string;
37
+ /**
38
+ * The changed region of a text file: after-lines (P, aCount − S] and before-lines (P, bCount − S], where P is the
39
+ * common prefix and S the common suffix (S never overlaps P) — exactly the single hunk `excerptDiff` renders.
40
+ */
41
+ export declare function changedRegion(beforeFile: string | null, afterFile: string | null): Promise<{
42
+ after: [number, number] | null;
43
+ before: [number, number] | null;
44
+ changedBytes: number;
45
+ }>;
46
+ /**
47
+ * §8.1 — writes lines `range` of `file` to `<destDir>/lines-<first>-<last>.txt`, cut at line ends, each at most
48
+ * `partBytes` (a single longer line gets its own part). Line numbers are the whole file's. Deterministic, so a rebuilt
49
+ * bundle is byte-identical. Memory stays one chunk.
50
+ */
51
+ export declare function writeLargeViewParts(file: string, range: [number, number], destDir: string, partBytes?: number): Promise<void>;
52
+ /**
53
+ * Draw one seat's receipt requests from the bundle. `rng(n)` returns an integer in [0, n) — cryptographic by default,
54
+ * so a seat cannot predict its requests; tests inject a deterministic one.
55
+ */
56
+ export declare function drawReceiptRequests(input: {
57
+ bundleDir: string;
58
+ files: readonly ReceiptFile[];
59
+ max?: number;
60
+ rng?: (n: number) => number;
61
+ }): Promise<ReceiptRequest[]>;
62
+ /** Re-read one request's expected value from the bundle (used by tests and the return path). */
63
+ export declare function readRequestedLine(bundleDir: string, req: ReceiptRequest): Promise<string | null>;
64
+ /**
65
+ * Parse `RECEIPT <id>: <value>` lines from a seat's final reply (last answer per id wins). R1-F5: backticks are part of
66
+ * the value — a copied markdown line may start or end with them — except the run that closes a code span the receipt
67
+ * line itself opened (`` `RECEIPT r1: value` ``).
68
+ */
69
+ export declare function parseReceipts(text: string): Map<string, string>;
70
+ /** §8.5 check: the requests that are missing or wrong in the reply (empty → every receipt is valid). */
71
+ export declare function failingReceipts(requests: readonly ReceiptRequest[], replyText: string): ReceiptRequest[];
72
+ /**
73
+ * F-AGY-RECEIPT — a shape-only account of why a reply failed its receipts, for the failure log: never a value or any
74
+ * file content (the reply quotes the user's files). `receiptLines` counts lines naming RECEIPT at all, so "the reply
75
+ * carried no receipts" is told apart from "it answered the wrong thing"; for a mismatch, `near` says whether the
76
+ * answer would have matched once quotes, emphasis or case were ignored — evidence for or against a formatting cause.
77
+ */
78
+ export declare function diagnoseReceiptReply(failing: readonly ReceiptRequest[], replyText: string): {
79
+ replyBytes: number;
80
+ receiptLines: number;
81
+ failing: Array<{
82
+ id: string;
83
+ kind: string;
84
+ state: string;
85
+ near?: string;
86
+ }>;
87
+ };
88
+ /**
89
+ * §8.3 — the fixed section the desktop appends to the engine-rendered reviewer prompt: where the review folder is
90
+ * (and, for a macOS seat that has one, the masked original), that the folder is authoritative over any truncated
91
+ * excerpt above, and the receipt requests. A re-dispatch lists the requests the previous reply failed.
92
+ */
93
+ export declare function renderReviewSeatSection(input: {
94
+ bundleDir: string;
95
+ originalDir?: string;
96
+ requests: readonly ReceiptRequest[];
97
+ failedBefore?: readonly ReceiptRequest[];
98
+ /** A seat whose command attempts end its turn with no reply (a confined agy seat, D-11). */
99
+ fileToolsOnly?: boolean;
100
+ }): string;
@@ -0,0 +1,43 @@
1
+ import type { SelfTestTier, TierSelfTestKeyInput } from '../local-executor/tier-self-test';
2
+ import type { ReviewerEvaluateOptions, ReviewerSpec } from '../reviewer/provider';
3
+ import type { SubstrateHandle } from '../substrate/types';
4
+ export interface ReviewerSeatSelfTestConfig {
5
+ cacheDir: string;
6
+ /** Where the throwaway view goes (the real copy location, so the test sees the same filesystem and grants). */
7
+ viewParent: () => Promise<string>;
8
+ /** Linux bubblewrap tier: the §6.4 key parts (`linuxTierSelfTestKeyParts`). `open` for an open-egress mode. */
9
+ linuxKeyParts?: (open: boolean) => Promise<Pick<TierSelfTestKeyInput, 'filterSha256' | 'bwrapVersion' | 'setprivPath' | 'bashPath' | 'pastaSha256'>>;
10
+ readAgentVersion?: (binary: string) => Promise<string>;
11
+ }
12
+ export interface EngagedSelfTestSeat {
13
+ handle: SubstrateHandle;
14
+ tier: SelfTestTier;
15
+ teardown: () => Promise<void>;
16
+ }
17
+ export interface ReviewerSeatSelfTestInput {
18
+ config: ReviewerSeatSelfTestConfig;
19
+ /** The real seat's spec (its agent, role, tools and model); the prompt and timeout are replaced. */
20
+ spec: ReviewerSpec;
21
+ tier: SelfTestTier;
22
+ /** Whether this seat is granted the masked original (macOS): part of its argv. */
23
+ withOriginal: string | undefined;
24
+ /** One self-test per key at a time; concurrent seats of the same key share it. */
25
+ inFlight: Map<string, Promise<ReviewerSeatSelfTestVerdict>>;
26
+ /** Engage a seat for the throwaway view; null when this host would not confine it (the test cannot run). */
27
+ engage: (args: {
28
+ taskId: string;
29
+ workdir: string;
30
+ bundleDir: string;
31
+ }) => Promise<EngagedSelfTestSeat | null>;
32
+ evaluate: (spec: ReviewerSpec, gateId: string, opts: ReviewerEvaluateOptions) => Promise<unknown>;
33
+ /** Removes the folders the engage made under the self-test's own task id. */
34
+ removeTaskFolder?: (taskId: string) => Promise<void>;
35
+ }
36
+ export interface ReviewerSeatSelfTestVerdict {
37
+ pass: boolean;
38
+ reason?: string;
39
+ }
40
+ /** The seat's argv for the fixed placeholder view, so one task's paths never force a new test. */
41
+ export declare function reviewerArgvTemplate(spec: ReviewerSpec, withOriginal: boolean): string[];
42
+ /** §6.4 — the seat's cached verdict in this tier, or a new run. Never throws for a failing agent. */
43
+ export declare function ensureReviewerSeatSelfTest(input: ReviewerSeatSelfTestInput): Promise<ReviewerSeatSelfTestVerdict>;
@@ -145,6 +145,7 @@ export type DecomposerResult = {
145
145
  */
146
146
  export declare function readImplementorFinalMessage(plan: ReturnType<typeof buildImplementorArgv>, handle: {
147
147
  stdout: () => string;
148
+ lastMessageCapturePath?: string;
148
149
  }, agent: AgentKind, exitCode: number, workdir?: string): Promise<string>;
149
150
  /**
150
151
  * Build the decomposition prompt fed to the local decomposer on stdin (§3.B).
@@ -485,6 +485,27 @@ export interface TaskStartConfirmationState {
485
485
  singleFallbackBrief?: string;
486
486
  };
487
487
  }
488
+ /**
489
+ * P63 §6.4 — a START refused because its implementor cannot be confined on this machine. The question offers to run
490
+ * it unconfined THIS ONCE; only an affirmative reply re-issues exactly these start arguments with `allowUnconfined`.
491
+ */
492
+ export type ConfinementOptInState = {
493
+ kind?: 'single';
494
+ reason: string;
495
+ startArgs: Parameters<import('./quorum-loop').QuorumLoop['startTask']>[0];
496
+ } | {
497
+ kind: 'team';
498
+ reason: string;
499
+ /** The refused launch, re-issued as is (same group key) with `allowUnconfined` on an affirmative reply. */
500
+ team: {
501
+ workItems: WorkItemInput[];
502
+ briefsByTrackIndex: Map<number, string>;
503
+ groupIdempotencyKey: string;
504
+ attachments?: ImageAttachment[];
505
+ attachmentPaths?: string[];
506
+ retryOf?: string;
507
+ };
508
+ };
488
509
  export interface PendingClarification {
489
510
  originalPrompt: string;
490
511
  rounds: Array<{
@@ -514,6 +535,8 @@ export interface PendingClarification {
514
535
  * anything else cancels) without calling the planner classifier.
515
536
  */
516
537
  startConfirmation?: TaskStartConfirmationState;
538
+ /** P63 §6.4 — its own pending kind beside `startConfirmation`: the "run it unconfined this once?" question. */
539
+ confinementOptIn?: ConfinementOptInState;
517
540
  }
518
541
  /**
519
542
  * A1a #3/#4 — one numbered choice in a `PendingPlannerOffer`. Contract-ready
@@ -1023,6 +1046,7 @@ export type OrchestrationAction = {
1023
1046
  taskId?: string;
1024
1047
  brainstorm?: BrainstormContextMetadata;
1025
1048
  startConfirmation?: TaskStartConfirmationState;
1049
+ confinementOptIn?: ConfinementOptInState;
1026
1050
  } | {
1027
1051
  type: 'EVENT_RECEIVED';
1028
1052
  event: EventStreamEntry;
@@ -51,6 +51,34 @@ export interface ReviewerEvaluateOptions {
51
51
  * substrate with) keeps the handle's surface minimal.
52
52
  */
53
53
  workdir?: string;
54
+ /**
55
+ * P63 Slice 2 (§8.2) — present when the gate has a review round: `workdir` is then the seat's private scratch
56
+ * folder and these are the folders the reviewer reads — the read-only review bundle and, when the seat has one,
57
+ * the original folder (on macOS masked by the seat's profile). Every provider runs in `workdir` and grants these:
58
+ * Claude and Antigravity with `--add-dir`; Codex reads them by path under its own read-only sandbox. ABSENT →
59
+ * each provider's pre-Slice-2 behaviour.
60
+ */
61
+ reviewDirs?: {
62
+ bundleDir: string;
63
+ originalDir?: string;
64
+ };
65
+ /**
66
+ * P63 Slice 2 (§8.2) — the boundary the reviewer ENGAGER reported for this seat (never inferred from the handle's
67
+ * presence): `seatbelt` / `docker` tier, and whether that profile gives the seat a read-only view of everything but
68
+ * its scratch (true for a seat with a review round). The Codex provider rewrites `--sandbox read-only` to
69
+ * `danger-full-access` ONLY for `{kind: 'seatbelt', readOnlyView: true}` — Codex's own read-only sandbox cannot
70
+ * start inside Seatbelt (SP-F1 (b)) and the profile is then the boundary; everywhere else `read-only` stays.
71
+ */
72
+ boundary?: {
73
+ kind: 'seatbelt' | 'docker';
74
+ readOnlyView: boolean;
75
+ };
76
+ /**
77
+ * P63 Slice 2 (§8.5) — called with the reviewer's raw final reply (Claude: the envelope's `.result`; Codex: the
78
+ * `--output-last-message` text; agy: stdout) once the CLI exited cleanly, before the verdict is classified. The loop
79
+ * checks the seat's read receipts in it. Never called for a failed run.
80
+ */
81
+ onFinalText?: (text: string) => void;
54
82
  /**
55
83
  * REVIEWER-VERDICT-GEMMA-CLASSIFIER-DESIGN.md (D1) — when present, the provider's
56
84
  * `buildVerdict` calls `await classifyVerdict(rawText, ctx)` (local-Gemma verdict
@@ -37,7 +37,9 @@ export declare class AntigravityReviewerProvider implements ReviewerProvider {
37
37
  * No `--output-format json` exists in agy print mode — stdout is the raw
38
38
  * reply text and there is no usage/token envelope (D10: tokens_used null).
39
39
  */
40
- export declare function buildCommand(executable: string, spec: ReviewerSpec, workdir: string): BuiltCommand;
40
+ export declare function buildCommand(executable: string, spec: ReviewerSpec,
41
+ /** The folders agy's tools may read (`--add-dir` each): the user's folder, or a review round's bundle (+ original). */
42
+ addDirs: string | readonly string[]): BuiltCommand;
41
43
  /**
42
44
  * Map the raw subprocess outcome into a `ReviewerVerdict` or throw a
43
45
  * structured `ReviewerErrorClass`. Exit code first (a crashed CLI's stdout is
@@ -45,4 +47,4 @@ export declare function buildCommand(executable: string, spec: ReviewerSpec, wor
45
47
  * unwrap — agy print mode is plain text) fed to the injected classifier
46
48
  * (default) or the legacy regex parser (`CODEVIBE_REVIEWER_VERDICT_CLASSIFIER=regex`).
47
49
  */
48
- export declare function buildVerdict(spec: ReviewerSpec, gateId: string, outcome: SubprocessOutcome, classifyVerdict?: VerdictClassifier): Promise<ReviewerVerdict>;
50
+ export declare function buildVerdict(spec: ReviewerSpec, gateId: string, outcome: SubprocessOutcome, classifyVerdict?: VerdictClassifier, onFinalText?: (text: string) => void): Promise<ReviewerVerdict>;
@@ -64,11 +64,16 @@ export declare class ClaudeReviewerProvider implements ReviewerProvider {
64
64
  * not via this field. On the LEGACY path (`sandboxed === false`, the default)
65
65
  * the env is built exactly as before — byte-identical non-regression.
66
66
  */
67
- export declare function buildCommand(executable: string, spec: ReviewerSpec, sandboxed?: boolean): BuiltCommand;
67
+ export declare function buildCommand(executable: string, spec: ReviewerSpec, sandboxed?: boolean,
68
+ /** P63 Slice 2 (§8.2) — the review round's bundle (+ original): granted with `--add-dir`. */
69
+ reviewDirs?: {
70
+ bundleDir: string;
71
+ originalDir?: string;
72
+ }): BuiltCommand;
68
73
  /**
69
74
  * Map the raw subprocess outcome into a `ReviewerVerdict` or throw a
70
75
  * structured `ReviewerErrorClass`. Exit code is checked first — a crashed
71
76
  * CLI that happened to print something valid on stdout should still be
72
77
  * treated as a spawn failure, not a silently-accepted verdict.
73
78
  */
74
- export declare function buildVerdict(spec: ReviewerSpec, gateId: string, outcome: SubprocessOutcome, classifyVerdict?: VerdictClassifier): Promise<ReviewerVerdict>;
79
+ export declare function buildVerdict(spec: ReviewerSpec, gateId: string, outcome: SubprocessOutcome, classifyVerdict?: VerdictClassifier, onFinalText?: (text: string) => void): Promise<ReviewerVerdict>;
@@ -56,7 +56,7 @@ export declare function buildCommand(executable: string, spec: ReviewerSpec, las
56
56
  * `claude.ts::buildVerdict` for the shared safety rule (non-zero exit
57
57
  * overrides any parseable stdout / file).
58
58
  */
59
- export declare function buildVerdict(spec: ReviewerSpec, gateId: string, outcome: SubprocessOutcome, lastMessage: string, classifyVerdict?: VerdictClassifier): Promise<ReviewerVerdict>;
59
+ export declare function buildVerdict(spec: ReviewerSpec, gateId: string, outcome: SubprocessOutcome, lastMessage: string, classifyVerdict?: VerdictClassifier, onFinalText?: (text: string) => void): Promise<ReviewerVerdict>;
60
60
  /**
61
61
  * Sum `usage.input_tokens + usage.output_tokens` across every
62
62
  * `turn.completed` JSONL event in `stdout`. Returns `null` when usage is absent,
@@ -9,6 +9,7 @@ import type { Session } from '../types/session';
9
9
  import { type ResumeStart } from './resume-entry';
10
10
  import type { ResumeEntryPoint } from './resume-target';
11
11
  export interface ResumeShellArgs {
12
+ predecessorSessionId: string;
12
13
  restoredItems: ResumeStart['restoredItems'];
13
14
  markerSeq: string;
14
15
  header: {
@@ -1,3 +1,4 @@
1
+ import { type SpawnStartRecord, type SpawnRemovalDeps, type SpawnProcStat } from './spawn-records';
1
2
  import { type WorkspaceRootAuthority } from '../local-executor/workspace-authority';
2
3
  import type { HostDescendantReap } from '../local-executor/descendant-reaper';
3
4
  import type { CommandRunner, EgressFidelity, Substrate, SubstrateHandle, SubstrateProc, SubstrateSpec } from './types';
@@ -33,6 +34,19 @@ export interface BubblewrapDeps {
33
34
  uid?: number;
34
35
  gid?: number;
35
36
  homedir?: string;
37
+ /**
38
+ * §6.2 broker tier — the bundled in-tier relay (`dist/substrate/tier-relay.js` of THIS installed package, resolved by
39
+ * absolute path, never PATH) and the Node binary that runs it inside the tier. Defaults: resolved beside this module;
40
+ * `process.execPath`.
41
+ */
42
+ tierRelayPath?: string;
43
+ nodePath?: string;
44
+ /** §6.2 — the boot id the spawn record carries; default `/proc/sys/kernel/random/boot_id`. */
45
+ readBootId?: () => string;
46
+ /** §6.2 — the spawn sweep run before a desktop-ended spawn's folder is removed (default: the shipped helper). */
47
+ runSweep?: SpawnRemovalDeps['runSweep'];
48
+ /** §6.2 — `/proc/<pid>/stat` reader for the record-time group check (tests inject it). */
49
+ readProcStat?: (pid: number) => SpawnProcStat | null;
36
50
  fsShim?: {
37
51
  lstatSync?: (p: string) => {
38
52
  isSymbolicLink: () => boolean;
@@ -42,6 +56,53 @@ export interface BubblewrapDeps {
42
56
  realpathSync?: (p: string) => string;
43
57
  };
44
58
  }
59
+ /**
60
+ * §6.2 — the bundled in-tier relay of this installed package. Bundles live at `dist/index.js` and
61
+ * `dist/orchestration-shell/cli.js`, so the relay is `dist/substrate/tier-relay.js` from either (and beside this module
62
+ * when it runs unbundled from source). Never PATH; the first that exists as a regular file.
63
+ */
64
+ export declare function defaultTierRelayPath(): string | null;
65
+ /** §6.1 / §6.2 — what the handle needs to assemble the bubblewrap argv at exec. */
66
+ export interface BubblewrapExecPlan {
67
+ /** The chain up to and including the filter path (setpriv … bash step … filter). */
68
+ chain: readonly string[];
69
+ /** bwrap arguments before the exec-time segment (system binds … read-only and writable grants). */
70
+ pre: readonly string[];
71
+ /** bwrap arguments from `--clearenv` on (env, chdir, `--`, the in-tier relay step if any). */
72
+ post: readonly string[];
73
+ /** Toolchain grants (read-only) and the credential files inside them to mask. */
74
+ grants: readonly string[];
75
+ grantMasks: readonly string[];
76
+ /** Folders whose contents are visible (system read-only binds and grants) — a hidden root lies inside one of them. */
77
+ boundRoots: readonly string[];
78
+ /** The roots to hide when they lie inside a bound folder: the project, the copy roots. */
79
+ hideRoots: readonly string[];
80
+ /** This task's own folders, bound again after the hides with their access mode. */
81
+ ownFolders: ReadonlyArray<{
82
+ host: string;
83
+ target: string;
84
+ mode: 'ro' | 'rw';
85
+ }>;
86
+ /** For the agent binary folder checks. */
87
+ project: string;
88
+ copyRoots: readonly string[];
89
+ hostPath: string;
90
+ }
91
+ /**
92
+ * Assemble the full argv at exec: the chain, the launch-time bwrap arguments, then — fixed when this spawn starts — the
93
+ * agent binary's folders (read-only; the run refused when §6.1 forbids them), the `/dev/null` masks over credential
94
+ * files in every grant and binary folder, the `--tmpfs` hides over the project and the copy roots that lie inside a
95
+ * bound folder, this task's own folders bound again after the hides, and the rest (env, chdir, the agent).
96
+ */
97
+ export declare function buildExecArgv(plan: BubblewrapExecPlan, argv: readonly string[]): string[];
98
+ export interface BubblewrapSpawnRecordPlan {
99
+ stateRoot: string;
100
+ base: Omit<SpawnStartRecord, 'version' | 'owner' | 'platform' | 'sandboxArgv' | 'pgid' | 'pid' | 'sid' | 'leaderStarttime'>;
101
+ runSweep?: SpawnRemovalDeps['runSweep'];
102
+ readProcStat?: (pid: number) => SpawnProcStat | null;
103
+ /** The folder was handed over (`spec.spawnFolder`): complete the caller's record instead of writing one. */
104
+ adopted?: boolean;
105
+ }
45
106
  export declare class BubblewrapHandle implements SubstrateHandle {
46
107
  readonly id: string;
47
108
  readonly egressFidelity: EgressFidelity;
@@ -59,6 +120,14 @@ export declare class BubblewrapHandle implements SubstrateHandle {
59
120
  private executed;
60
121
  private inFlightExec;
61
122
  private teardownPromise;
123
+ /** §6.2 broker tier: the desktop's own broker address the relay channel dials, and the channel once exec'd. */
124
+ private readonly relayBroker?;
125
+ private relay;
126
+ private readonly execPlan?;
127
+ /** §6.2 — the spawn record: written before the spawn, the group added after, removed with the folder. */
128
+ private readonly spawnRecord?;
129
+ private recordFile;
130
+ private endedByDesktop;
62
131
  constructor(opts: {
63
132
  id: string;
64
133
  runner: CommandRunner;
@@ -68,6 +137,17 @@ export declare class BubblewrapHandle implements SubstrateHandle {
68
137
  workdir: string;
69
138
  workdirAuthority?: WorkspaceRootAuthority;
70
139
  scratchAuthority?: WorkspaceRootAuthority;
140
+ egressFidelity?: EgressFidelity;
141
+ relayBroker?: {
142
+ host: string;
143
+ port: number;
144
+ };
145
+ /**
146
+ * §6.1 / §6.2 — the exec-time segment: the bubblewrap argv is split around it (everything before `--clearenv`,
147
+ * and from `--clearenv` on), because the agent binary's folders are known only from argv[0].
148
+ */
149
+ execPlan?: BubblewrapExecPlan;
150
+ spawnRecord?: BubblewrapSpawnRecordPlan;
71
151
  });
72
152
  exec(argv: string[], opts: {
73
153
  stdinTty: boolean;
@@ -88,6 +168,11 @@ export declare class BubblewrapSubstrate implements Substrate {
88
168
  private readonly gid;
89
169
  private readonly homedir;
90
170
  private readonly fsShim?;
171
+ private readonly tierRelayPath?;
172
+ private readonly nodePath;
173
+ private readonly readBootId;
174
+ private readonly runSweep?;
175
+ private readonly readProcStat?;
91
176
  constructor(deps?: BubblewrapDeps);
92
177
  launch(spec: SubstrateSpec): Promise<SubstrateHandle>;
93
178
  }
@@ -21,5 +21,6 @@ export declare class ChildProcessCommandRunner implements CommandRunner {
21
21
  cwdAuthority?: WorkspaceRootAuthority;
22
22
  launchPathAuthorities?: readonly WorkspaceRootAuthority[];
23
23
  descendantReap?: HostDescendantReap;
24
+ relayChannel?: boolean;
24
25
  }): RunnerProc;
25
26
  }
@@ -1,8 +1,17 @@
1
1
  import { type ForwarderFactory } from './forwarder';
2
2
  import type { CommandRunner, EgressObservationHook, Substrate, SubstrateHandle, SubstrateSpec } from './types';
3
3
  import { type WorkspaceRootAuthority, type WorkspaceRootLease } from '../local-executor/workspace-authority';
4
+ /**
5
+ * The base image the agent runs inside. **[ASM]** the exact image is an
6
+ * operational detail (any image carrying the agent CLI + Node); pinned here as a
7
+ * default so the argv is deterministic for the conformance test. Overridable
8
+ * via {@link DockerSubstrateDeps.image}.
9
+ */
10
+ export declare const DEFAULT_IMAGE = "codevibe/substrate:rung1";
4
11
  /** Where, inside the container, the worktree is mounted (the agent's cwd/home). */
5
12
  export declare const CONTAINER_WORKDIR = "/workspace";
13
+ /** P63 Slice 2 (§8.2) — where a spawn's capture dir is mounted read-write inside the container. */
14
+ export declare const CONTAINER_CAPTURE_DIR = "/codevibe-capture";
6
15
  /** Injectable deps — default-wired to the real runtime; stubbed in unit tests. */
7
16
  export interface DockerSubstrateDeps {
8
17
  /** Process layer (default: `node:child_process`). Stub in unit tests. */
@@ -77,6 +86,18 @@ export declare function buildRunArgv(args: {
77
86
  hostDir: string;
78
87
  sandboxDir: string;
79
88
  };
89
+ /**
90
+ * P63 Slice 2 (§8.2) — a reviewer seat's review bundle, mounted READ-ONLY at the same absolute path it has on the
91
+ * host (so the prompt names one path on every backend). Bundle only — never the user's folder.
92
+ */
93
+ reviewBundleDir?: string;
94
+ /**
95
+ * P63 Slice 2 (§8.2) — `false` mounts the workdir READ-ONLY (`:ro`): the plan-mode / review read-only view, which
96
+ * Seatbelt enforces with its trailing write deny. Absent / `true` → the shipped read-write mount.
97
+ */
98
+ writableWorkdir?: boolean;
99
+ /** P63 Slice 2 (§8.2) — the spawn's capture dir, mounted read-write at {@link CONTAINER_CAPTURE_DIR}. */
100
+ captureDir?: string;
80
101
  }): string[];
81
102
  /**
82
103
  * `docker exec [-i] <name> env -i <sanitizedEnv> <argv...>` — run the agent
@@ -0,0 +1,71 @@
1
+ /** argv[0] on the HOST PATH (the agent binary the tier will run), or null. */
2
+ export declare function resolveOnHostPath(cmd: string, hostPath?: string): string | null;
3
+ /** Linux: the XDG user folders, from `~/.config/user-dirs.dirs` (what `xdg-user-dir` reads). */
4
+ export declare function xdgUserDirs(homedir: string): string[];
5
+ export interface GrantRuleInput {
6
+ platform: 'darwin' | 'linux';
7
+ pathEntries: readonly string[];
8
+ homedir: string;
9
+ /** The user's real project folder. */
10
+ project: string;
11
+ /** CodeVibe copy-location roots (§5.1). */
12
+ copyRoots: readonly string[];
13
+ /** `~/.codevibe`. */
14
+ codevibeDir: string;
15
+ /** `$TMPDIR` (and its realpath is added by the rule). */
16
+ tmpdir?: string;
17
+ xdgRuntimeDir?: string;
18
+ /** Linux: the XDG user folders as `xdg-user-dir` reports them. */
19
+ xdgUserDirs?: readonly string[];
20
+ isWsl?: boolean;
21
+ /** Folders the tier already reads (system dirs): entries there need no grant but stay on the sandbox PATH. */
22
+ readableSystemDirs: readonly string[];
23
+ /** The shipped default PATH appended after the user's granted entries. */
24
+ defaultPath: readonly string[];
25
+ realpath: (p: string) => string | null;
26
+ isDirectory: (p: string) => boolean;
27
+ }
28
+ export interface GrantRuleResult {
29
+ /** Read-only grants (deduplicated, in first-use order). */
30
+ grants: string[];
31
+ /** Refused candidates with the reason, for the task log. */
32
+ refused: Array<{
33
+ path: string;
34
+ reason: string;
35
+ }>;
36
+ /** The sandbox PATH entries, in the user's order, then the shipped default. */
37
+ sandboxPath: string[];
38
+ }
39
+ export declare function computeToolchainGrants(input: GrantRuleInput): GrantRuleResult;
40
+ /**
41
+ * §6.1 — whether a file inside a granted root is credential-named (compared without regard to letter case). `relDir`
42
+ * is its directory relative to the grant root ('' at the root): `config.json` is hidden only at the root, and
43
+ * `.docker/config.json` anywhere.
44
+ */
45
+ export declare function isCredentialName(name: string, relDir: string): boolean;
46
+ export interface GrantScanResult {
47
+ /** Credential-named files to mask (`--ro-bind /dev/null <file>`). */
48
+ masks: string[];
49
+ /** A Unix socket found inside (refuses the grant / the run on Linux). */
50
+ socket: string | null;
51
+ /** The walk exceeded its bounds (200,000 entries or 10 s). */
52
+ exceeded: boolean;
53
+ }
54
+ /**
55
+ * §6.1 (Linux) — walk a granted root or an agent binary folder with the credential-name test, for every spawn when its
56
+ * argv is built: bounded (200,000 entries, 10 s), symlinks never followed, sockets reported.
57
+ */
58
+ export declare function scanGrantRoot(root: string, bounds?: {
59
+ maxEntries: number;
60
+ maxMs: number;
61
+ }, now?: () => number): GrantScanResult;
62
+ /**
63
+ * §6.1 — the agent binary's folders (the directory of the resolved argv[0] and of its realpath): granted read-only,
64
+ * not by the toolchain rule; the RUN is refused when one lies inside the project or a copy root, or equals, contains or
65
+ * lies inside a temporary or runtime folder.
66
+ */
67
+ export declare function agentBinaryFolders(resolvedArgv0: string, realArgv0: string, input: Pick<GrantRuleInput, 'project' | 'copyRoots' | 'tmpdir' | 'xdgRuntimeDir' | 'realpath' | 'homedir' | 'pathEntries' | 'platform' | 'codevibeDir' | 'readableSystemDirs' | 'defaultPath' | 'isDirectory'>): {
68
+ folders: string[];
69
+ } | {
70
+ refusal: string;
71
+ };