@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
@@ -0,0 +1,32 @@
1
+ export declare const SHARDED_LIST_VERSION = 1;
2
+ export declare const SHARDED_LIST_SHARD_ITEMS = 10000;
3
+ /** A shard holds at most 10,000 items of a few hundred bytes each; anything far larger is not a shard. */
4
+ export declare const MAX_SHARDED_LIST_SHARD_BYTES: number;
5
+ /** One record's list namespace in a content store: names `<prefix>-<sha256>`. */
6
+ export interface ListStore {
7
+ dir: string;
8
+ prefix: string;
9
+ }
10
+ /** What a record holds in place of a list. `kind` names the list, so a record can never borrow another list's shards. */
11
+ export interface ShardedListRecord {
12
+ shardedList: typeof SHARDED_LIST_VERSION;
13
+ kind: string;
14
+ entries: number;
15
+ shards: Array<{
16
+ sha256: string;
17
+ entries: number;
18
+ }>;
19
+ rootHash: string;
20
+ }
21
+ /** Canonical JSON of one item: object keys in sorted order at every depth, so equal items serialize to equal bytes. */
22
+ export declare function canonicalJson(value: unknown): string;
23
+ /**
24
+ * Write `items` as shards (each once; an existing name must already hold exactly those bytes) and return the record.
25
+ * The store directory is fsynced before returning, so a record written afterwards never names a shard a crash lost.
26
+ */
27
+ export declare function writeShardedList(store: ListStore, kind: string, items: readonly unknown[], itemsPerShard?: number): Promise<ShardedListRecord>;
28
+ /** Validate a record's shape and root hash (no file access). */
29
+ export declare function isShardedListRecord(value: unknown): value is ShardedListRecord;
30
+ export declare function assertShardedListRecord(value: unknown, kind: string): asserts value is ShardedListRecord;
31
+ /** Read every item a record names, verifying each shard and the record. */
32
+ export declare function readShardedList(store: ListStore, record: unknown, kind: string): Promise<unknown[]>;
@@ -3,6 +3,7 @@ import type { GateRunResult } from './verification-runner';
3
3
  import { type MetadataBudget } from './bounded-directory';
4
4
  import type { PromoteManifestInput, RevertManifestEntry, RevertManifestStore, SnapshotPromotionJournalV1 } from './durable-store';
5
5
  import { WorkspaceShadow, type ReviewedShadowDiffFile, type ShadowEnv } from './workspace-shadow';
6
+ import { type BlobStore } from './blob-store';
6
7
  import { type WorkspaceRootAuthority } from './workspace-authority';
7
8
  export declare const SNAPSHOT_TRACK_BUNDLE_VERSION: 2;
8
9
  export interface SnapshotTrackBundle {
@@ -43,9 +44,15 @@ export type SnapshotMergePlan = {
43
44
  status: 'conflict';
44
45
  paths: string[];
45
46
  reason: string;
47
+ workLimit?: true;
46
48
  };
47
49
  export interface SnapshotMergePlanOptions {
48
50
  signal?: AbortSignal;
51
+ /**
52
+ * §9.4 — the group's content store: every bundle's reference post-images were copied there at publication, and a
53
+ * line-merged result is written there and carried as a reference. Required when any bundle has reference files.
54
+ */
55
+ contentStore?: BlobStore;
49
56
  /** @internal deterministic aggregate-work ceiling for tests. */
50
57
  maxWork?: number;
51
58
  /** @internal deterministic planning deadline for tests. */
@@ -59,10 +66,16 @@ export declare function verifySnapshotTrackBundle(bundle: SnapshotTrackBundle, e
59
66
  files?: MetadataBudget;
60
67
  testSurfaces?: MetadataBudget;
61
68
  }): void;
69
+ /** §9.4 — the content address of a file list: canonical JSON, so a list read back from content-store shards verifies. */
70
+ export declare function filesContentHash(files: readonly unknown[]): string;
71
+ /** A recorded file-list hash: canonical, or the `JSON.stringify` form of a record written before §9.4 shards. */
72
+ export declare function filesHashMatches(files: readonly unknown[], hash: string): boolean;
62
73
  export declare function snapshotPromotionOperationHash(taskGroupId: string, bundles: readonly SnapshotTrackBundle[], gateKinds: readonly VerificationGateKind[]): string;
63
74
  export interface RunSnapshotMergeGateArgs {
64
75
  workingDir: string;
65
76
  taskGroupId: string;
77
+ /** §9.4 — the group's content store (bundle post-images, merge results); see {@link SnapshotMergePlanOptions}. */
78
+ contentStore?: BlobStore;
66
79
  ownerTrackIndex: number;
67
80
  bundles: readonly SnapshotTrackBundle[];
68
81
  gateKinds: readonly VerificationGateKind[];
@@ -101,6 +114,11 @@ export interface RunSnapshotMergeGateArgs {
101
114
  conflicts: readonly string[];
102
115
  writeErrors: readonly string[];
103
116
  }) => void;
117
+ /** The tracks' changes could not be merged, before anything was applied. Paths are workspace-relative. */
118
+ onMergeConflict?: (info: {
119
+ paths: readonly string[];
120
+ workLimit: boolean;
121
+ }) => void;
104
122
  }
105
123
  /**
106
124
  * `UNRESOLVED` is deliberately not a wire-level MergeResult. It means durable
@@ -0,0 +1,33 @@
1
+ export declare const SPAWN_SWEEP_MANIFEST = "spawn-sweep.json";
2
+ export declare const SPAWN_SWEEP_BINARY = "codevibe-spawn-sweep";
3
+ export type SpawnSweepTarget = 'linux-x64' | 'linux-arm64';
4
+ export interface SpawnSweepManifest {
5
+ version: 1;
6
+ /** The core-rs commit whose pipeline built the bytes. */
7
+ coreRsSha: string;
8
+ targets: Record<SpawnSweepTarget, {
9
+ sha256: string;
10
+ versionId: string | null;
11
+ }>;
12
+ }
13
+ export type SpawnSweepHelperResolution = {
14
+ ok: true;
15
+ path: string;
16
+ sha256: string;
17
+ target: SpawnSweepTarget;
18
+ } | {
19
+ ok: false;
20
+ reason: string;
21
+ };
22
+ export declare function spawnSweepTarget(platform?: NodeJS.Platform, arch?: string): SpawnSweepTarget | null;
23
+ export declare function parseSpawnSweepManifest(text: string): SpawnSweepManifest | null;
24
+ export interface ResolveSpawnSweepOptions {
25
+ platform?: NodeJS.Platform;
26
+ arch?: string;
27
+ /** Overrides the package `native/` directories searched (tests). */
28
+ nativeDirs?: string[];
29
+ uid?: number;
30
+ }
31
+ export declare function resolveSpawnSweepHelper(opts?: ResolveSpawnSweepOptions): SpawnSweepHelperResolution;
32
+ /** @internal tests */
33
+ export declare function _resetSpawnSweepHelperCache(): void;
@@ -1,4 +1,5 @@
1
1
  import cp from 'node:child_process';
2
+ import { resolveSpawnSweepHelper } from './spawn-sweep-binary';
2
3
  export interface SpawnSweepRecord {
3
4
  version: 1;
4
5
  bootId: string;
@@ -49,6 +50,8 @@ export interface SpawnSweepOptions {
49
50
  readProcStat?: (pid: number) => ProcStatOutcome;
50
51
  /** Injectable spawner for unit tests */
51
52
  spawnHelper?: typeof cp.spawn;
53
+ /** Injectable packaged-helper resolution (tests; default `resolveSpawnSweepHelper`). */
54
+ resolveHelper?: typeof resolveSpawnSweepHelper;
52
55
  }
53
56
  /** Reset active spawn sweeps map (for test isolation) */
54
57
  export declare function _resetActiveSpawnSweeps(): void;
@@ -0,0 +1,48 @@
1
+ export interface StoreDirIdentity {
2
+ dev: number;
3
+ ino: number;
4
+ }
5
+ /** One held store directory. `close()` belongs in a `finally`. */
6
+ export interface StoreAnchor {
7
+ /** The canonical directory path (no symlink in it when the anchor was opened). */
8
+ readonly dir: string;
9
+ readonly identity: StoreDirIdentity;
10
+ /** The path that names `name` inside the HELD directory (Linux: through its descriptor). */
11
+ at(name: string): string;
12
+ /**
13
+ * The no-follow flag for every open through {@link at}: `O_NOFOLLOW` (Linux), `O_NOFOLLOW_ANY` (macOS — it also refuses
14
+ * a final symlink, and the kernel rejects the two together with EINVAL).
15
+ */
16
+ readonly noFollow: number;
17
+ /** Whether names resolve through the held descriptor (exact), not through the path. */
18
+ readonly pinned: boolean;
19
+ /** The held directory still has the identity it was opened with, and the path still names it. */
20
+ assertStill(label: string): Promise<void>;
21
+ /** fsync the held directory. */
22
+ sync(): Promise<void>;
23
+ /** List the held directory. */
24
+ list(): Promise<string[]>;
25
+ close(): Promise<void>;
26
+ }
27
+ /**
28
+ * Open (and with `create`, first create 0700 inside its existing parent, whose entry is fsynced) a store directory and
29
+ * hold it. A store path that is a symlink or anything but a real directory is refused, never followed; null when
30
+ * nothing is there and `create` is false.
31
+ */
32
+ export declare function openStoreAnchor(dir: string, opts: {
33
+ create: boolean;
34
+ }): Promise<StoreAnchor | null>;
35
+ /**
36
+ * Remove exactly the regular files whose names start with one of `prefixes` from the store directory `dir`, then the
37
+ * directory if that left it empty. Every unlink happens in a helper whose working directory was checked to be the
38
+ * store's inode, so a store replaced at any point is refused (`instance content store was replaced during its
39
+ * retirement`) and nothing in the replacement is touched. Anything else inside keeps the directory.
40
+ */
41
+ export declare function removeStoreEntriesAnchored(dir: string, prefixes: readonly string[], opts?: {
42
+ helperTimeoutMs?: number; /** @internal test seam: after the identity is taken, before the helper starts. */
43
+ beforeHelper?: () => Promise<void>;
44
+ }): Promise<{
45
+ removed: number;
46
+ kept: number;
47
+ directoryRemoved: boolean;
48
+ }>;
@@ -0,0 +1,57 @@
1
+ import type { AgentKind, ImplementorHandle, SpawnArgs } from './types';
2
+ export type SelfTestMode = 'execute' | 'plan';
3
+ export type SelfTestTier = 'docker' | 'sandbox_exec' | 'bubblewrap';
4
+ export type AgyCommandTool = 'works' | 'no-command-tool' | 'unknown';
5
+ export interface TierSelfTestKeyInput {
6
+ agentKind: AgentKind;
7
+ agentVersion: string;
8
+ tier: SelfTestTier;
9
+ /** `review` = the reviewer-seat read-only argv (`orchestration-shell/reviewer-seat-self-test.ts`). */
10
+ mode: SelfTestMode | 'review';
11
+ /** The mode's argv template (built for the placeholder view). */
12
+ argvTemplate: readonly string[];
13
+ /** Linux bubblewrap tier: the seccomp program's SHA-256, `bwrap --version`, and `setpriv` / `bash` paths. */
14
+ filterSha256?: string;
15
+ bwrapVersion?: string;
16
+ setprivPath?: string;
17
+ bashPath?: string;
18
+ /** Linux open-egress mode: the pasta binary's SHA-256. */
19
+ pastaSha256?: string;
20
+ }
21
+ export declare const SELF_TEST_VIEW_PLACEHOLDER = "/codevibe-self-test-view";
22
+ export declare function tierSelfTestKey(k: TierSelfTestKeyInput): string;
23
+ export interface TierSelfTestResult {
24
+ key: string;
25
+ agentKind: AgentKind;
26
+ agentVersion: string;
27
+ tier: SelfTestTier;
28
+ mode: SelfTestMode | 'review';
29
+ pass: boolean;
30
+ reason?: string;
31
+ agyCommandTool?: AgyCommandTool;
32
+ checkedAt: string;
33
+ }
34
+ export declare function readTierSelfTest(cacheDir: string, key: string): TierSelfTestResult | null;
35
+ export declare function writeTierSelfTest(cacheDir: string, result: TierSelfTestResult): void;
36
+ /** `<agent> --version`, first line, bounded; '' when it cannot be read (a version change still re-keys). */
37
+ export declare function readAgentVersion(binary: string): Promise<string>;
38
+ export interface RunTierSelfTestInput {
39
+ agentKind: AgentKind;
40
+ mode: SelfTestMode;
41
+ /** Where the throwaway view goes (the real copy location, so the test sees the same filesystem and grants). */
42
+ viewParent: string;
43
+ /** The mode's real argv for a view (`buildImplementorArgv(agent, mode, view).argv`). */
44
+ argvFor: (view: string) => string[];
45
+ /** One implementor spawn through the same engage path as a real task (the LE's own `spawnImplementor`). */
46
+ spawn: (args: SpawnArgs) => Promise<ImplementorHandle>;
47
+ timeoutMs?: number;
48
+ /** Removes the folders the spawns made under the self-test's own task ids (capture, private homes). */
49
+ removeTaskFolder?: (taskId: string) => Promise<void>;
50
+ }
51
+ /** Runs the §6.4 self-test once; never throws for a failing agent (the verdict says why). */
52
+ export declare function runTierSelfTest(input: RunTierSelfTestInput): Promise<{
53
+ pass: boolean;
54
+ reason?: string;
55
+ agyCommandTool?: AgyCommandTool;
56
+ transient?: boolean;
57
+ }>;
@@ -19,6 +19,20 @@ export interface SpawnArgs {
19
19
  signal?: AbortSignal;
20
20
  taskId?: string;
21
21
  webTurnActive?: boolean;
22
+ /**
23
+ * P63 §6.1 / §6.2 — the user's real project folder this spawn's copy was made from. The Linux bubblewrap tier needs
24
+ * it (with the copy roots) for the grant rule: a granted toolchain root may never contain it, and a project lying
25
+ * inside a bound system folder is hidden with `--tmpfs`. A task spawn without it cannot engage that tier.
26
+ */
27
+ projectRoot?: string;
28
+ /**
29
+ * P63 §6.4 — the user opted in, for THIS task, to running its implementor unconfined when no confining tier can be
30
+ * established. Without it a `reduced_trust` engage refuses the spawn (`SpawnConfinementUnavailable`) — CodeVibe never
31
+ * runs an implementor unconfined on its own.
32
+ */
33
+ allowUnconfined?: boolean;
34
+ /** P63 §6.4 — this spawn IS a tier self-test (never itself self-tested). Set only by the LE. */
35
+ tierSelfTest?: boolean;
22
36
  processTreeId?: string;
23
37
  taskPrivateWorkingDir?: boolean;
24
38
  lifecycleAudit?: 'active_task' | 'none';
@@ -30,6 +44,12 @@ export interface SpawnArgs {
30
44
  onCleanupOwnerRetired?: (owner: import('./process-tree').ProcessCleanupOwner) => Promise<void>;
31
45
  substrate?: SubstrateHandle;
32
46
  requireConfined?: boolean;
47
+ /**
48
+ * P63 Slice 2 (§8.2) — the implementor mode this argv was built for. `'plan'` engages the substrate with a READ-ONLY
49
+ * view (`writableWorkdir: false`). The LE also reads plan mode from the argv itself
50
+ * (`isPlanModeImplementorArgv`), so omitting or contradicting this cannot widen a plan run to a writable view.
51
+ */
52
+ implementorMode?: 'plan' | 'execute';
33
53
  }
34
54
  export interface ProcessSpawnedInfo {
35
55
  pid: number;
@@ -65,6 +85,12 @@ export interface SpawnHandleBase {
65
85
  }
66
86
  export interface ImplementorHandle extends SpawnHandleBase {
67
87
  kind: 'implementor';
88
+ /**
89
+ * P63 Slice 2 (§8.2) — when this spawn ran with a read-only view and a capture dir, the HOST path of the Codex
90
+ * `--output-last-message` file inside it; plan readers pass it to `readImplementorLastMessage` (read first; the
91
+ * capture dir is removed once read). Absent on every other spawn.
92
+ */
93
+ lastMessageCapturePath?: string;
68
94
  }
69
95
  export interface ReviewerHandle extends SpawnHandleBase {
70
96
  kind: 'reviewer';
@@ -1,10 +1,16 @@
1
1
  import { type ShadowDiffFile } from '../workspace-shadow';
2
+ import { type BlobStore } from '../blob-store';
2
3
  import type { GateRunResult } from '../verification-runner';
3
4
  import { type WorkspaceRootAuthority } from '../workspace-authority';
4
5
  export interface DiffSanityGateArgs {
5
6
  cwd: string;
6
7
  timeoutMs: number;
7
8
  files?: readonly ShadowDiffFile[];
9
+ /**
10
+ * §9.4 — where `files`' reference entries (no inline text) live: each is checked by streaming its verified blob, so
11
+ * the gate reads exactly the reviewed bytes at any size. Required when `files` has reference entries.
12
+ */
13
+ contentStore?: BlobStore;
8
14
  workingDirAuthority?: WorkspaceRootAuthority;
9
15
  /** @internal deterministic root-replacement seam. */
10
16
  afterAuthorityCheck?: () => Promise<void> | void;
@@ -5,9 +5,18 @@ export interface MaterializedFileMetadata {
5
5
  size: number;
6
6
  mode: number;
7
7
  }
8
+ /**
9
+ * P63 Slice 2 (§9.4) — the largest file whose bytes a hash session returns with its hash (`readFile`): the base64
10
+ * reply stays inside the helper protocol's 128 KiB line limit. Larger files are copied by the streaming helper.
11
+ */
12
+ export declare const SESSION_READ_MAX_BYTES: number;
8
13
  export interface WorkspaceMaterializerSession {
9
14
  copyFile(source: string, destination: string, maxLogicalBytes?: number): Promise<MaterializedFileMetadata>;
10
- hashDestinationFile(destination: string, maxBytes: number): Promise<MaterializedFileMetadata>;
15
+ hashDestinationFile(destination: string, maxBytes: number,
16
+ /** P63 Slice 2: also return the bytes when the file is at most this size (≤ {@link SESSION_READ_MAX_BYTES}). */
17
+ contentMaxBytes?: number): Promise<MaterializedFileMetadata & {
18
+ content?: Buffer;
19
+ }>;
11
20
  relocateDestinationRoot(authority: WorkspaceRootAuthority): Promise<void>;
12
21
  /**
13
22
  * §5.3 (macOS): clone every listed regular file of one directory with a single `cp -c`, so each later
@@ -21,6 +30,13 @@ export interface WorkspaceMaterializerSession {
21
30
  }
22
31
  export interface WorkspaceFileHashSession {
23
32
  hashFile(file: string): Promise<MaterializedFileMetadata>;
33
+ /**
34
+ * P63 Slice 2 (§9.4 capture): the same identity-bound read as `hashFile`, also returning the bytes when the file is
35
+ * at most {@link SESSION_READ_MAX_BYTES} (verified against the reported hash and size); `content` is absent above that.
36
+ */
37
+ readFile(file: string): Promise<MaterializedFileMetadata & {
38
+ content?: Buffer;
39
+ }>;
24
40
  close(): Promise<void>;
25
41
  }
26
42
  export interface WorkspaceFileHashOptions {
@@ -2,6 +2,11 @@ import type { PromoteManifestInput } from './durable-store';
2
2
  import { type ProcessCleanupOwner } from './process-tree';
3
3
  import { type WorkspaceRootAuthority } from './workspace-authority';
4
4
  import { ReviewedShadowDiffFile, ShadowDiffFile, ShadowEnv, ShadowPromoteState, TrackBranchSpec, UnresolvedBundlePublicationRetention } from './shadow-protocol';
5
+ import { type CopySpaceVerdict } from './shadow-free-space';
6
+ import { type TaskIntent } from './shadow-task-intent';
7
+ import { type BlobStore } from './blob-store';
8
+ import type { BlobRef } from './change-manifest';
9
+ import type { CasRecoveryAuthority } from './shadow-protocol';
5
10
  import { RecoveredShadow } from './shadow-recovery';
6
11
  export { MAX_CHANGE_SET_FILE_COUNT, MAX_CHANGE_SET_PATH_BYTES, MAX_TRACKED_FILE_LOGICAL_BYTES, MAX_TRACKED_WORKSPACE_LOGICAL_BYTES, MAX_TRACKED_FILE_COUNT, MAX_TRACKED_PATH_BYTES, MAX_REVIEW_FILE_BYTES, MAX_PROMOTION_PREIMAGE_AGGREGATE_BYTES, MAX_STATE_MARKER_BYTES, DEFAULT_SHADOW_TTL_MS, } from './shadow-protocol';
7
12
  export type { ShadowChangeKind, ShadowDiffFile, ReviewedShadowDiffFile, CasHelperCrashStage, UnresolvedBundlePublicationRetention, ShadowEnv, TrackBranchSpec, } from './shadow-protocol';
@@ -9,13 +14,25 @@ export { listRecoverableShadows, reconcileShadowPublicationAuthority, listShadow
9
14
  export type { RecoveredShadow, ShadowCleanupResidue } from './shadow-recovery';
10
15
  export { replaceWorkspaceFileIfMatches, removeWorkspaceFileIfMatches, } from './anchored-cas';
11
16
  export type { AcceptedAlternateImage } from './anchored-cas';
17
+ export declare function treeRemovalTimeoutMs(entries: number): number;
12
18
  /**
13
- * The desktop's per-task shadow workspace. ONE instance per active task; the
14
- * QuorumLoop creates it before the round-0 spawn, points the implementor cwd at
15
- * `shadowDir`, captures the diff after the implementor exits, and the class-B
16
- * consumer promotes (TaskAuthorized) or discards (every other terminal).
19
+ * F-EXIT-WAITS-CAPTURE (tick): a capture asked to stop past `maxFiles` (the progress tick's count) found a larger
20
+ * copy. Nothing was hashed, stored or recorded; it is not a capture failure.
17
21
  */
22
+ export declare class ShadowCaptureTooLargeError extends Error {
23
+ constructor();
24
+ }
25
+ /**
26
+ * F-APPLY-RETRY: a helper that stopped answering or ended is transient — the next helper re-proves the file's exact
27
+ * state. A conflict, a second writer, an abort or any other error is definite.
28
+ */
29
+ export declare function isTransientPlacementFailure(err: unknown, reason: string): boolean;
30
+ /** An in-flight capture ended by session exit (`WorkspaceShadow.cancelCapture`), not a capture failure. */
31
+ export declare class ShadowCaptureCancelledError extends Error {
32
+ constructor();
33
+ }
18
34
  export declare class WorkspaceShadow {
35
+ private captureCancelled;
19
36
  readonly taskId: string;
20
37
  /** The real workspace root (canonical). */
21
38
  readonly workingDir: string;
@@ -67,6 +84,17 @@ export declare class WorkspaceShadow {
67
84
  private copyStatTable?;
68
85
  /** §7.4: the user's own changes to their folder since the start record, from the last capture. */
69
86
  userFolderChanges?: import('./shadow-start-record').FolderChangesSinceStart;
87
+ /** R1-F10: bytes copied into this instance's content store since its last free-space check. */
88
+ private pendingStoreCopyBytes;
89
+ /**
90
+ * P63 Slice 2 R1-F10 (§5.5, §9.4) — a copy into this instance's content store (a captured post-image, a base version)
91
+ * is admitted by free space like the task's own copy: before a file of COPY_SPACE_CHECK_BYTES or more, and whenever
92
+ * smaller copies since the last check add up to that much, with §5.5's headroom. Refused → the capture stops with the
93
+ * reason; nothing is applied, the user's folder is untouched.
94
+ */
95
+ private admitStoreCopy;
96
+ /** P63 Slice 2 R1-F6 (§5.7): the private folders and not-applicable entries of the copy, from the last capture. */
97
+ privateEntries?: import('./shadow-recovery').ShadowPrivateEntries;
70
98
  /**
71
99
  * §7.4: count the user's own edits to their folder since the task started. Reviewers see this in Slice 2's review
72
100
  * INDEX; Slice 1 records it and writes it to the task log. Never fails a capture: the count is information.
@@ -75,6 +103,8 @@ export declare class WorkspaceShadow {
75
103
  private readonly env;
76
104
  private readonly maxStateMarkerBytes;
77
105
  private promotedInProcess;
106
+ /** §9.3 "What can stay in the user's folder": kept files, until the caller names them to the user. */
107
+ private readonly keptBesideApply;
78
108
  private cleanupBlocked;
79
109
  private cleanupOwners;
80
110
  private unresolvedAuthorityRetention?;
@@ -111,6 +141,17 @@ export declare class WorkspaceShadow {
111
141
  * Fail-closed (#SR-2): a create failure throws — the caller surfaces it and
112
142
  * does NOT spawn against a half-built shadow.
113
143
  */
144
+ /**
145
+ * §5.5 before the task starts (N4, Stage 1 R2): "Not enough space → the task does not start". The same copy-location
146
+ * choice and the same {@link assertCopySpaceForTask} decision `create` makes, run before the task is created, so a
147
+ * folder that cannot be copied is refused before "Started task". Returns the refusal, or null when the copy fits or
148
+ * the decision cannot be made here (an unreadable folder): `create` keeps the authoritative check inside the quota
149
+ * lock, so space that changes in between is still caught. Writes nothing except an absent copy root (created as
150
+ * `create` creates it, S2-C6) and the clone probe's own temporary entry, as `create` does.
151
+ */
152
+ static precheckCopySpace(workingDir: string, env?: ShadowEnv): Promise<Extract<CopySpaceVerdict, {
153
+ ok: false;
154
+ }> | null>;
114
155
  static create(taskId: string, workingDir: string, env?: ShadowEnv, opts?: {
115
156
  trackBranch?: TrackBranchSpec;
116
157
  }): Promise<WorkspaceShadow>;
@@ -126,7 +167,27 @@ export declare class WorkspaceShadow {
126
167
  * Snapshots use a tree compare bounded to the tracked set. Across revise
127
168
  * rounds the diff is CUMULATIVE shadow-vs-real.
128
169
  */
129
- captureDiff(): Promise<ShadowDiffFile[]>;
170
+ captureDiff(opts?: {
171
+ cancellable?: boolean;
172
+ maxFiles?: number;
173
+ }): Promise<ShadowDiffFile[]>;
174
+ /** F-RESTART-NOT-RESUMED (§17): durable before the agent starts, so a later resume can reopen this task. */
175
+ writeTaskIntent(intent: TaskIntent): Promise<void>;
176
+ /**
177
+ * F-APPLY-RETRY: one file's placement with bounded retries of a TRANSIENT helper failure. Attempt 0 is the normal
178
+ * placement; each retry runs on a fresh helper through the crash-recovery proof. Only the failures below are
179
+ * transient; everything else (a conflict result, a second writer, an abort, a plain error) returns or throws at once.
180
+ */
181
+ private placeWithRetry;
182
+ /** The test fault for this file on this attempt (tests may inject only on the first attempt), or undefined. */
183
+ private injectedPromotionFault;
184
+ /**
185
+ * F-EXIT-WAITS-CAPTURE: session exit ends an in-flight capture of an agent's round at the next file, so teardown does
186
+ * not wait for the rest of it. The blobs captured so far leave with the task's state. Only a capture that asked for
187
+ * this stops: the capture inside an approved apply always finishes, because exit must not lose an approved change.
188
+ */
189
+ cancelCapture(): void;
190
+ private assertCaptureNotCancelled;
130
191
  /**
131
192
  * Bind a reviewed diff to this shadow's pre-task hashes. Team execution stores
132
193
  * these immutable bundles instead of Git commits; the integration gate uses
@@ -163,7 +224,104 @@ export declare class WorkspaceShadow {
163
224
  * remains bound to the bundle's reviewed base hash, never to whatever bytes
164
225
  * happened to exist when the integration snapshot was created.
165
226
  */
166
- applyDiffToShadow(diff: readonly ReviewedShadowDiffFile[]): Promise<void>;
227
+ applyDiffToShadow(diff: readonly ReviewedShadowDiffFile[],
228
+ /** §9.4 — where reference entries live (a team merge plan: the group's content store); streamed from there. */
229
+ sourceStore?: BlobStore): Promise<void>;
230
+ private applyDiffToShadowWith;
231
+ /**
232
+ * P63 Slice 2 (§9.4) — capture one changed file: copied into this instance's blob store through the anchored helper
233
+ * (the same chain anchoring as `readShadowTextFile`) first, checked as strict UTF-8 text and carried as the
234
+ * reference only (`content: ''`, §9.4: in-memory structures hold references only) whatever its size.
235
+ * `expectedSha256` (a modified file's hash from the stat pass) must match too.
236
+ */
237
+ private captureFile;
238
+ /**
239
+ * §9.4 — the text of a reference entry, read from this instance's blob store and verified (hash, size, strict UTF-8)
240
+ * for the one consumer that needs it now; never kept. Inline (legacy) entries return their text. A large entry is
241
+ * never read whole: callers excerpt it (`readPostImageHead`) or treat it whole-file.
242
+ */
243
+ readDiffText(file: ShadowDiffFile): Promise<string>;
244
+ /** P63 Slice 2 R1-F6 — the §5.7 / §7.4 lists and counts of the review INDEX, from the last capture. */
245
+ reviewIndexContext(): {
246
+ privateFolders: Array<{
247
+ path: string;
248
+ group: 'hidden' | 'dependencies' | 'rebuild-output' | 'codevibe';
249
+ }>;
250
+ notApplicable: string[];
251
+ folderChanges?: {
252
+ changed: number;
253
+ created: number;
254
+ deleted: number;
255
+ };
256
+ };
257
+ /**
258
+ * P63 Slice 2 R1-F1 — the user's file a change modifies or deletes is text: its current bytes are streamed through the
259
+ * anchored copy into this task's blob store and checked as strict UTF-8, so a binary original (a deleted `clip.mp4`, a
260
+ * `logo.png` overwritten with text) stops the task with the binary reason before any review, as the strict decode of
261
+ * the promote preflight did before Slice 2 — and its bytes never reach a review bundle. `required`: the file must
262
+ * exist (a deletion); for a modification a file the user removed meanwhile is left to the conflict check.
263
+ */
264
+ private assertRealBaseIsText;
265
+ /**
266
+ * P63 Slice 2 (§8.1) — the verified blob sources of a review bundle for `files`: each new version from this instance's
267
+ * blob store (`ownPostImageBlob`), each base version copied from the real file through the anchored helper and kept
268
+ * only when it is still exactly the recorded base (a base the user changed since the task started is `null` — the
269
+ * reviewers then see the new version and the diff is against nothing, never against bytes that were not the base).
270
+ */
271
+ reviewBundleSources(files: readonly ShadowDiffFile[]): Promise<Array<{
272
+ path: string;
273
+ change_kind: ShadowDiffFile['change_kind'];
274
+ after: {
275
+ store: BlobStore;
276
+ ref: BlobRef;
277
+ } | null;
278
+ before: {
279
+ store: BlobStore;
280
+ ref: BlobRef;
281
+ } | null;
282
+ modes: {
283
+ before: number | null;
284
+ after: number | null;
285
+ };
286
+ base?: {
287
+ sha256: string;
288
+ size: number | null;
289
+ changed: boolean;
290
+ };
291
+ }>>;
292
+ /** §9.4 — this instance's blob store (a team bundle's source when it is published to the group's content store). */
293
+ contentBlobStore(): BlobStore;
294
+ /**
295
+ * P63 Slice 2 (§9.4 W3d) — the first `maxBytes` of a post-image in this instance's blob store (excerpts of large
296
+ * files for the engine projection; never the whole file).
297
+ */
298
+ readPostImageHead(ref: BlobRef, maxBytes: number): Promise<Buffer>;
299
+ /**
300
+ * P63 Slice 2 (§9.4 W3c) — the reference of `file`'s post-image in this instance's blob store: its capture blob when
301
+ * that blob is here (same size, bound to the content's hash), else the content stored now (deduplicated by hash).
302
+ */
303
+ private ownPostImageBlob;
304
+ /**
305
+ * P63 Slice 2 (S2-9) — the APPLYING marker's promotion authority as post-images plus the CAS recovery authority, read
306
+ * from whichever form the marker holds: the shard-backed promote manifest (every shard read and verified, every
307
+ * entry's base hash checked against this instance's baseline, the CAS authority recomputed with
308
+ * `buildCasRecoveryAuthority` exactly as marker parsing does for the inline form) or the legacy inline lists. Null when
309
+ * the marker is not APPLYING.
310
+ */
311
+ readPromotionAuthority(): Promise<{
312
+ postImages: Array<{
313
+ path: string;
314
+ hash: string | null;
315
+ mode: number | null;
316
+ }>;
317
+ casRecovery: CasRecoveryAuthority[];
318
+ } | null>;
319
+ /**
320
+ * P63 Slice 2 (S2-9) — the change manifest of a promoted set: per path its kind, post-mode, pre-task base hash and
321
+ * post-image blob reference (the post-hash promotion computed; size from the content). Pure: equal promoted sets
322
+ * build equal roots, so promotion re-entry compares roots instead of inline lists.
323
+ */
324
+ private promoteManifestFor;
167
325
  private captureCopyDiff;
168
326
  /**
169
327
  * Materialize the changed-file SET over the TRACKED SET ONLY (#PM-1/#PM-2):
@@ -183,7 +341,24 @@ export declare class WorkspaceShadow {
183
341
  * "could not promote — N files at <paths>". Returns the conflicts/errors so
184
342
  * the caller renders them; throws only on a marker-write failure.
185
343
  */
344
+ private recordKept;
345
+ /**
346
+ * §9.3: the files the last promote left in the user's folder, relative to it, each returned once. The caller lists
347
+ * them in the task summary and on the phone, whatever the promote's outcome.
348
+ */
349
+ /**
350
+ * §9.3: `pending-restore` entries are retried every 10 seconds while the desktop runs. One pass over this snapshot's
351
+ * open entries; the paths still pending. Nothing new is applied here: once none is pending the ordinary promotion
352
+ * finishes the task, exactly as at a start.
353
+ */
354
+ retryPendingNoLinkRestores(): Promise<string[]>;
355
+ takeKeptFiles(): string[];
186
356
  promote(diff: ShadowDiffFile[], opts?: {
357
+ /**
358
+ * §9.4 — where reference entries of `diff` that are not in this instance's store live (a team merge plan: the
359
+ * group's content store). Each is stream-copied into this instance's store before APPLYING.
360
+ */
361
+ sourceStore?: BlobStore;
187
362
  /**
188
363
  * PHASE-589/469 W3.1 (C1) — capture a durable revert manifest for a TEAM
189
364
  * track. For each in-scope, hash-matched file the promote is about to
@@ -223,9 +398,12 @@ export declare class WorkspaceShadow {
223
398
  recovered: boolean;
224
399
  promoted: string[];
225
400
  conflicts: string[];
401
+ /** `noLinkPending`: a §9.3 step to retry while the desktop runs; `temporarilyAt`: where its original is meanwhile. */
226
402
  errors: Array<{
227
403
  path: string;
228
404
  error: string;
405
+ noLinkPending?: true;
406
+ temporarilyAt?: string;
229
407
  }>;
230
408
  /**
231
409
  * PHASE-589/469 W3.1 (C1) — the captured pre-image manifest entries (only
@@ -235,7 +413,18 @@ export declare class WorkspaceShadow {
235
413
  * entry per file that the promote actually applied.
236
414
  */
237
415
  manifestEntries: PromoteManifestInput[];
416
+ /**
417
+ * P63 Slice 2 (F-S2-6c) — on `applied`, the exact post-image (hash, mode) of every diff path, as this call verified
418
+ * it in the real tree (the same values the PROMOTED marker records). Absent where the volume does not track modes
419
+ * (the real mode is then the volume's, not the post-image's) and on every other outcome; callers then read the real
420
+ * tree themselves.
421
+ */
422
+ postImages?: ReadonlyMap<string, {
423
+ hash: string | null;
424
+ mode: number | null;
425
+ }>;
238
426
  }>;
427
+ private promoteWith;
239
428
  /**
240
429
  * Tear down the shadow on every non-`TaskAuthorized` terminal (reject/abort,
241
430
  * each Halted reason, parser-empty, session-end, crash). Leaves the real tree
@@ -263,6 +452,8 @@ export declare class WorkspaceShadow {
263
452
  helperTimeoutMs?: number;
264
453
  signal?: AbortSignal;
265
454
  }): Promise<boolean>;
455
+ /** Sized from the tree when it is still at its name; a tombstone being finished has only the baseline to go by. */
456
+ private treeRemovalBudgetMs;
266
457
  private retirePhysicalCopyRootIfUnused;
267
458
  private retainExposureWriteAuthority;
268
459
  private runOwnedStateFileWrite;
@@ -339,7 +530,18 @@ export declare function reopenShadow(recovered: RecoveredShadow, env?: ShadowEnv
339
530
  * intends to recover). A `promoted` shadow whose code already landed is GC'd
340
531
  * (its real tree is done) regardless of TTL.
341
532
  */
533
+ /**
534
+ * F-RESTART-NOT-RESUMED: a reopened task runs under a new id while its copy keeps its first one, so the reviewer records
535
+ * made under the new id have no instance to retire their folders. `rmdir` only: anything still inside stays and the exit
536
+ * audit reports it.
537
+ */
538
+ export declare function removeEmptyTaskFolders(taskId: string, env?: ShadowEnv): Promise<void>;
539
+ /** §6.2: what the spawn sweep could not clear. A task listed here, or any task after a failed sweep, is not captured yet. */
540
+ interface SpawnSweepReport {
541
+ spawnBlockedTaskIds?: ReadonlySet<string>;
542
+ spawnSweepFailed?: boolean;
543
+ }
342
544
  export declare function reapOrphanShadows(keepTaskIds: ReadonlySet<string>, env?: ShadowEnv): Promise<{
343
545
  reaped: string[];
344
546
  retained: string[];
345
- }>;
547
+ } & SpawnSweepReport>;