@quantiya/codevibe-claude-plugin 2.0.101 → 2.0.103

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 (31) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/dist/server.js +18 -18
  3. package/node_modules/@quantiya/codevibe-core/dist/appsync/sync-mobile-echo.d.ts +12 -12
  4. package/node_modules/@quantiya/codevibe-core/dist/appsync/sync-shell-source.d.ts +14 -14
  5. package/node_modules/@quantiya/codevibe-core/dist/index.d.ts +1 -0
  6. package/node_modules/@quantiya/codevibe-core/dist/index.js +676 -653
  7. package/node_modules/@quantiya/codevibe-core/dist/local-executor/binary-classifier.d.ts +8 -0
  8. package/node_modules/@quantiya/codevibe-core/dist/local-executor/blob-store.d.ts +5 -0
  9. package/node_modules/@quantiya/codevibe-core/dist/local-executor/change-manifest.d.ts +5 -0
  10. package/node_modules/@quantiya/codevibe-core/dist/local-executor/local-executor-impl.d.ts +3 -0
  11. package/node_modules/@quantiya/codevibe-core/dist/local-executor/media-metadata.d.ts +16 -0
  12. package/node_modules/@quantiya/codevibe-core/dist/local-executor/shadow-protocol.d.ts +22 -0
  13. package/node_modules/@quantiya/codevibe-core/dist/local-executor/shadow-slice3-cleanups.d.ts +15 -0
  14. package/node_modules/@quantiya/codevibe-core/dist/local-executor/snapshot-merge.d.ts +4 -0
  15. package/node_modules/@quantiya/codevibe-core/dist/local-executor/task-group-types.d.ts +3 -1
  16. package/node_modules/@quantiya/codevibe-core/dist/local-executor/team-media-approval.d.ts +78 -0
  17. package/node_modules/@quantiya/codevibe-core/dist/local-executor/withheld-media.d.ts +14 -0
  18. package/node_modules/@quantiya/codevibe-core/dist/local-executor/workspace-shadow.d.ts +11 -6
  19. package/node_modules/@quantiya/codevibe-core/dist/orchestration-shell/agent-turn-degrade.d.ts +14 -0
  20. package/node_modules/@quantiya/codevibe-core/dist/orchestration-shell/binary-retry-brief.d.ts +8 -0
  21. package/node_modules/@quantiya/codevibe-core/dist/orchestration-shell/brainstorm-quorum.d.ts +3 -1
  22. package/node_modules/@quantiya/codevibe-core/dist/orchestration-shell/cli.js +4779 -2122
  23. package/node_modules/@quantiya/codevibe-core/dist/orchestration-shell/engine-projection.d.ts +9 -0
  24. package/node_modules/@quantiya/codevibe-core/dist/orchestration-shell/media-approval.d.ts +112 -0
  25. package/node_modules/@quantiya/codevibe-core/dist/orchestration-shell/quorum-loop.d.ts +3 -0
  26. package/node_modules/@quantiya/codevibe-core/dist/orchestration-shell/review-bundle.d.ts +40 -0
  27. package/node_modules/@quantiya/codevibe-core/dist/orchestration-shell/review-receipts.d.ts +22 -1
  28. package/node_modules/@quantiya/codevibe-core/dist/orchestration-shell/types.d.ts +6 -0
  29. package/node_modules/@quantiya/codevibe-core/dist/tool-activity/types.d.ts +12 -12
  30. package/node_modules/@quantiya/codevibe-core/package.json +1 -1
  31. package/package.json +2 -2
@@ -18,6 +18,14 @@ export type FileClass = {
18
18
  kind: 'other';
19
19
  format: 'audio' | 'video' | 'archive' | 'other_binary';
20
20
  };
21
+ /** P63 Slice 3 — a captured file's class when it is not text; a diff entry without one is text (§10.1). */
22
+ export type StoredFileClass = Exclude<FileClass, {
23
+ kind: 'text';
24
+ }>;
25
+ /** The same class — `null` / absent is text (§10.1). Compares the class a capture recorded with the class of bytes read later. */
26
+ export declare function sameStoredClass(a: StoredFileClass | null | undefined, b: StoredFileClass | null | undefined): boolean;
27
+ /** A well-formed stored class, exactly as `classifyFile` returns it (records are read back from disk). */
28
+ export declare function isStoredFileClass(value: unknown): value is StoredFileClass;
21
29
  /** §10.2: per-part inflate output limit (64 MiB). */
22
30
  export declare const MAX_OFFICE_PART_BYTES: number;
23
31
  /**
@@ -1,5 +1,6 @@
1
1
  import * as fs from 'node:fs/promises';
2
2
  import type { BlobRef } from './change-manifest';
3
+ import { type FileSource, type StoredFileClass } from './binary-classifier';
3
4
  import { type StoreAnchor } from './store-dir-anchor';
4
5
  export declare const BLOB_CHUNK_BYTES: number;
5
6
  /** One instance's blob namespace in a task state directory. */
@@ -80,6 +81,10 @@ export declare function readBlobChunks(store: BlobStore, ref: BlobRef): AsyncGen
80
81
  * mismatch before anything is published). Opened no-follow; must be a regular file of exactly `ref.size` bytes.
81
82
  */
82
83
  export declare function openBlobForStreaming(store: BlobStore, ref: BlobRef): Promise<fs.FileHandle>;
84
+ /** P63 Slice 3 — random access to one stored blob for the duration of `fn` (the blob is verified as a regular file). */
85
+ export declare function withBlobSource<T>(store: BlobStore, ref: BlobRef, fn: (source: FileSource) => Promise<T>): Promise<T>;
86
+ /** P63 Slice 3 (§10.1) — a blob's class, read through random access to the stored bytes; null for text. */
87
+ export declare function classifyBlob(store: BlobStore, ref: BlobRef): Promise<StoredFileClass | null>;
83
88
  /**
84
89
  * P63 Slice 2 (§9.4) — the strict-text rule of `decodeStrictUtf8` (valid UTF-8, no NUL byte) checked while streaming,
85
90
  * for a file too large to decode whole. Throws the same "binary file changes are not supported safely" refusal.
@@ -1,3 +1,4 @@
1
+ import { type StoredFileClass } from './binary-classifier';
1
2
  import type { ShadowChangeKind } from './shadow-protocol';
2
3
  export declare const CHANGE_MANIFEST_VERSION = 1;
3
4
  export declare const CHANGE_MANIFEST_SHARD_ENTRIES = 10000;
@@ -20,6 +21,10 @@ export interface ChangeManifestEntry {
20
21
  * track's declared base mode); a single task's manifest omits it and its lines are unchanged.
21
22
  */
22
23
  base_mode?: number | null;
24
+ /** P63 Slice 3 (§10.1): the reviewed post-image's class when it is not text. Absent: text (every older line). */
25
+ file_class?: StoredFileClass;
26
+ /** P63 Slice 3 (§10.1): the pre-task file's class when it is not text. Absent: text. */
27
+ base_class?: StoredFileClass;
23
28
  }
24
29
  export interface ChangeManifestShard {
25
30
  /** SHA-256 (hex) of `bytes`; the shard's name in the store. */
@@ -8,6 +8,7 @@ import { TrackRegistry } from './track-registry';
8
8
  import { AgentKind, AuthorityAction, ClassBInbound, GateDispatchPayload, HookEvent, ImplementorHandle, LocalExecutor, MergeGateDispatchPayload, ProbeHandle, ProcessRole, ReviewerDispatchPayload, ReviseFeedbackPayload, QueuedWorkEndedPayload, ReviewerHandle, SpawnArgs, TrackAssignedPayload, type DurableSessionResourceDisposeOptions, type WorkspaceDisposeOptions } from './types';
9
9
  import { type TeamExecution } from './team-execution-flag';
10
10
  import { type SnapshotBundlesView, type SnapshotMergeGateResult } from './snapshot-merge';
11
+ import { type TeamMediaAsker } from './team-media-approval';
11
12
  import { type WorkspaceRootAuthority } from './workspace-authority';
12
13
  import type { RenderedConflict, TrackIntentDiff } from './wb2-review-bundle';
13
14
  import type { ConflictModelCaller } from './class2-resolver';
@@ -371,6 +372,8 @@ export interface LocalExecutorImplDeps {
371
372
  }) => Promise<void>;
372
373
  /** Optional shadow lifecycle hooks shared with implementor workspaces. */
373
374
  snapshotShadowEnv?: ShadowEnv;
375
+ /** P63 Slice 3 §9.5 — ask a team's media question once, before its merged result is applied (team-media-approval.ts). */
376
+ askTeamMediaApproval?: TeamMediaAsker;
374
377
  /** Mark a successfully accepted group terminal and reclaim its bundle state. */
375
378
  completeSnapshotGroup?: (taskGroupId: string) => Promise<void>;
376
379
  /** Normal-exit retirement of all ordinary durable resources from this session. */
@@ -0,0 +1,16 @@
1
+ /**
2
+ * P63 Slice 3 Phase B — §8.1 / §9.5: what the INDEX and the phone question say about a withheld file (audio, video,
3
+ * archives, other binary): its container type, and its duration where the container states one. Read through the same
4
+ * random-access `FileSource` as the classifier, header fields only — never the media data. Malformed or unknown input
5
+ * yields `unknown` / no duration; a read error from the source propagates (the store failed, not the file).
6
+ */
7
+ import type { FileSource } from './binary-classifier';
8
+ export interface MediaMetadata {
9
+ /** A short container name for people: "MPEG-4 (MP4)", "WAV", "ZIP", … or "unknown". */
10
+ container: string;
11
+ /** Whole seconds, when the container header states the duration (MP4/MOV/M4A `mvhd`, WAV byte rate). */
12
+ durationSeconds: number | null;
13
+ }
14
+ export declare function readMediaMetadata(source: FileSource): Promise<MediaMetadata>;
15
+ /** "0:45", "1:04", "1:02:03". */
16
+ export declare function formatDuration(seconds: number): string;
@@ -1,3 +1,4 @@
1
+ import { type StoredFileClass } from './binary-classifier';
1
2
  import * as fs from 'node:fs/promises';
2
3
  import type { BlobRef } from './change-manifest';
3
4
  import type { ChangeManifestRecord } from './change-manifest-store';
@@ -41,6 +42,8 @@ export declare const DEFAULT_MATERIALIZER_LANES = 4;
41
42
  export declare const SHADOW_UUID_SUFFIX_RE: RegExp;
42
43
  /** §6.2's per-spawn advisory records live in the v2 state root beside the task folders, never as a task. */
43
44
  export declare const SHADOW_STATE_ADVISORY_DIR = "advisory";
45
+ /** P63 §9.5: the open team media questions (`team-media-approval.ts`) live in the v2 state root, never as a task. */
46
+ export declare const SHADOW_STATE_TEAM_MEDIA_DIR = "team-media-questions";
44
47
  export declare function isStateRootInfrastructureDirectory(name: string): boolean;
45
48
  export declare function taskIdFromGeneratedShadowName(name: string): string | null;
46
49
  /** Strict lifecycle-lock residue authored by `withAnchoredExclusiveLock`. */
@@ -129,7 +132,24 @@ export interface ShadowDiffFile {
129
132
  * anchored copy. Absent for deletions and for records written before Slice 2.
130
133
  */
131
134
  blob?: BlobRef;
135
+ /** P63 Slice 3 (§10.1) — the new version's class when it is not text (created/modified). Absent: text. */
136
+ file_class?: StoredFileClass;
137
+ /** P63 Slice 3 (§10.1) — the user's version's class when it is not text (modified/deleted). Absent: text. */
138
+ base_class?: StoredFileClass;
132
139
  }
140
+ /** P63 Slice 3 — the class fields of a stored diff entry are well formed and sit on the change kinds they describe. */
141
+ export declare function assertDiffFileClasses(file: {
142
+ change_kind: unknown;
143
+ content?: unknown;
144
+ blob?: unknown;
145
+ file_class?: unknown;
146
+ base_class?: unknown;
147
+ }, label: string): void;
148
+ /** P63 Slice 3 — a change that is binary on either side: whole-file, never decoded as text or line-merged (§10.3). */
149
+ export declare function isBinaryDiffFile(file: {
150
+ file_class?: unknown;
151
+ base_class?: unknown;
152
+ }): boolean;
133
153
  /**
134
154
  * P63 Slice 2 (§9.4) — a diff entry's optional blob reference is well formed and agrees with its inline content:
135
155
  * never on a deletion, a 64-hex SHA-256 and a byte size equal to the content's UTF-8 length.
@@ -138,6 +158,8 @@ export declare function assertDiffBlobRef(file: {
138
158
  change_kind: unknown;
139
159
  content: unknown;
140
160
  blob?: unknown;
161
+ file_class?: unknown;
162
+ base_class?: unknown;
141
163
  }, label: string): void;
142
164
  /**
143
165
  * P63 Slice 2 (§9.4) — text above this many bytes is carried as a blob reference only (`content: ''`): it is never
@@ -0,0 +1,15 @@
1
+ import { type ShadowEnv } from './shadow-protocol';
2
+ import type { WorkspaceRootAuthority } from './workspace-authority';
3
+ /** F-P0-1 — remove the state directory a failed create made, by its creation identity and only when it is empty. */
4
+ export declare function removeEmptyCreatedStateDir(createdStateDir: WorkspaceRootAuthority | null, taskId: string): Promise<void>;
5
+ /**
6
+ * §11 / §16 G2 — a task's capture dirs whose task no longer exists are removed by exact path: the task has no state
7
+ * directory and is not kept by this desktop. A link is never followed; anything that is not a real directory is left.
8
+ * The task folder goes too when that leaves it empty.
9
+ */
10
+ export declare function removeOrphanCaptureDirs(keepTaskIds: ReadonlySet<string>, env?: ShadowEnv): Promise<string[]>;
11
+ /**
12
+ * The orphan reap's last step: once, against this desktop's own (v2) roots, after every pass decided what it keeps. Not
13
+ * after an aborted or failed sweep (`skip`). Never fatal to the sweep.
14
+ */
15
+ export declare function sweepOrphanCaptureDirs(keepTaskIds: ReadonlySet<string>, skip: boolean, env?: ShadowEnv): Promise<void>;
@@ -4,6 +4,8 @@ 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
6
  import { type BlobStore } from './blob-store';
7
+ import type { ShadowDiffFile } from './shadow-protocol';
8
+ import type { WithheldMediaChange } from './withheld-media';
7
9
  import { type WorkspaceRootAuthority } from './workspace-authority';
8
10
  export declare const SNAPSHOT_TRACK_BUNDLE_VERSION: 2;
9
11
  export interface SnapshotTrackBundle {
@@ -90,6 +92,8 @@ export interface RunSnapshotMergeGateArgs {
90
92
  */
91
93
  beforePromotionMutation?: (operationHash: string, files: readonly ReviewedShadowDiffFile[], workspaceRootAuthority: WorkspaceRootAuthority) => Promise<void>;
92
94
  markPromotionApplied: (operationHash: string) => Promise<void>;
95
+ /** P63 Slice 3 §9.5 — a team's media question, before anything is journaled; false applies nothing (team-media-approval.ts). */
96
+ approveMedia?: (files: readonly ReviewedShadowDiffFile[], withheldOf?: (files: readonly ShadowDiffFile[]) => Promise<WithheldMediaChange[]>) => Promise<boolean>;
93
97
  timeoutMs?: number;
94
98
  /** @internal deterministic merge-planning aggregate-work ceiling for tests. */
95
99
  mergePlanMaxWork?: number;
@@ -158,7 +158,9 @@ export interface TrackChangedFiles {
158
158
  /** MergeGate result discriminator (§2.1). SCREAMING wire values. */
159
159
  export type MergeResult = 'PASS' | 'FAIL';
160
160
  /** The 6-class MergeGate failure classification (§2.1, #C12-8). */
161
- export type MergeFailureClass = 'SHARED_FILE_CONFLICT' | 'INTEGRATION_FAILURE' | 'REVIEWER_DISAGREEMENT' | 'SCOPE_VIOLATION' | 'DIRTY_REPO_STATE' | 'BUDGET_EXHAUSTED';
161
+ export type MergeFailureClass = 'SHARED_FILE_CONFLICT' | 'INTEGRATION_FAILURE' | 'REVIEWER_DISAGREEMENT' | 'SCOPE_VIOLATION' | 'DIRTY_REPO_STATE' | 'BUDGET_EXHAUSTED'
162
+ /** P63 Slice 3 §9.5 — the user declined the team's media question; nothing applied (engine: no escalation). */
163
+ | 'USER_DECLINED_MEDIA';
162
164
  /** Retry-target kind discriminator (§2.1). SCREAMING wire values. */
163
165
  export type MergeRetryTargetKind = 'TRACK' | 'SERIAL_OWNER' | 'NONE';
164
166
  /**
@@ -0,0 +1,78 @@
1
+ import type { BlobStore } from './blob-store';
2
+ import type { ShadowDiffFile } from './shadow-protocol';
3
+ import type { TaskGroupHaltedPayload } from './types';
4
+ import { type WithheldMediaChange } from './withheld-media';
5
+ /** Ask the team's question; resolves true only on "yes". */
6
+ export type TeamMediaAsker = (taskGroupId: string, withheld: readonly WithheldMediaChange[]) => Promise<boolean>;
7
+ export declare function takeTeamMediaDeclined(owner: object, taskGroupId: string): boolean;
8
+ /** The longest a team group lives (engine TTL, F2 TM-2: 7 days from creation; 30 days once PROMOTE is authorized,
9
+ * which a group waiting on this question has not reached). An older record names a group the engine has ended. */
10
+ export declare const TEAM_MEDIA_RECORD_MAX_AGE_MS: number;
11
+ /** The engine's halt reason for the user's "no" (core-rs `GROUP_USER_DECLINED_MEDIA`). */
12
+ export declare const GROUP_USER_DECLINED_MEDIA = "group_user_declined_media";
13
+ /**
14
+ * Engine gate D-1: the engine writes no `TaskGroupHalted` row for the user's own "no", so the desktop that submitted the
15
+ * decline runs the group's halt teardown itself — the same `haltTaskGroup` a halt packet drives, with no choices (a v2
16
+ * group's desktop notice is informational; the user has already decided).
17
+ */
18
+ export declare function declinedTeardownPayload(taskGroupId: string, decisionProtocolVersion: unknown): TaskGroupHaltedPayload;
19
+ /** The media in a merged change, from the classes the reviewed bundles carry (§10.1). */
20
+ export declare function teamWithheldMedia(files: readonly ShadowDiffFile[], store: BlobStore | undefined): Promise<WithheldMediaChange[]>;
21
+ /** The durable record of an open team question (a JSON file per group under `<state root>/team-media-questions/`). */
22
+ export interface OpenTeamMediaQuestion {
23
+ version: 1;
24
+ taskGroupId: string;
25
+ /** The session that asked; only that session (resumed) may end the group. */
26
+ sessionId: string;
27
+ /** The MergeGateDispatch the verdict answers (persisted because the engine does not re-deliver it). */
28
+ payload: Record<string, unknown>;
29
+ gateKinds: string[];
30
+ openedAt: string;
31
+ }
32
+ /** Registered as state-root infrastructure (`shadow-protocol.ts`), so no state-root walker treats it as a task folder. */
33
+ export declare function teamMediaQuestionDir(stateRoot: string): string;
34
+ /**
35
+ * Durable (SF-6, X-1 restart drill 2026-11-09: an abrupt stop within the file system's commit interval lost an unsynced
36
+ * record): the bytes are synced before the rename, and the folder (and, when it was just created, its parent) after it.
37
+ */
38
+ export declare function writeOpenTeamMediaQuestion(stateRoot: string, q: OpenTeamMediaQuestion): Promise<void>;
39
+ /** Durable too: a record that came back after a crash would end a group whose question was already answered. */
40
+ export declare function removeOpenTeamMediaQuestion(stateRoot: string, taskGroupId: string): Promise<void>;
41
+ export declare function listOpenTeamMediaQuestions(stateRoot: string): Promise<OpenTeamMediaQuestion[]>;
42
+ /**
43
+ * The merge hook. `true` = apply the merged result; `false` = apply nothing (a decline is remembered for the FAIL
44
+ * verdict; an abort is not a decline). No asker in this session: nothing is applied and it counts as declined, so the
45
+ * group ends instead of waiting for an answer that cannot come.
46
+ */
47
+ export declare function approveTeamMedia(args: {
48
+ owner: object;
49
+ taskGroupId: string;
50
+ files: readonly ShadowDiffFile[];
51
+ /** S2R1-F3: the integration snapshot's own withheld check, bound to the merge base's pre-task bytes. */
52
+ withheldOf?: (files: readonly ShadowDiffFile[]) => Promise<WithheldMediaChange[]>;
53
+ store: BlobStore | undefined;
54
+ ask: TeamMediaAsker | undefined;
55
+ signal?: AbortSignal;
56
+ record?: {
57
+ stateRoot: string;
58
+ sessionId: string;
59
+ payload: Record<string, unknown>;
60
+ gateKinds: string[];
61
+ };
62
+ }): Promise<boolean>;
63
+ /**
64
+ * At start (Hendry 2026-11-09, "Engine ends orphaned groups"; F2 TM-10 was disproven): a resumed session runs under a
65
+ * NEW session id, which the engine rejects for the old group, so the desktop never submits a verdict here. A question
66
+ * the PREDECESSOR session left open is closed locally: its record is removed and the user is told nothing was applied —
67
+ * the engine ends that group by itself once the predecessor's session has ended (`session_end_sweep`; a resume is only
68
+ * admitted after it has). Another session's record is left alone (it may still be asking) unless it is older than the
69
+ * group lifetime, when it is removed.
70
+ */
71
+ export declare function recoverOpenTeamMediaQuestions(args: {
72
+ stateRoot: string;
73
+ /** The session this one resumed from (`Session.resumedFromSessionId`); undefined for a fresh session. */
74
+ resumedFromSessionId?: string;
75
+ nowMs?: number;
76
+ notify: (text: string) => void;
77
+ warn: (message: string, details?: unknown) => void;
78
+ }): Promise<void>;
@@ -0,0 +1,14 @@
1
+ import type { StoredFileClass } from './binary-classifier';
2
+ import { type BlobStore } from './blob-store';
3
+ import type { PreTaskHashes, ShadowDiffFile } from './shadow-protocol';
4
+ export interface WithheldMediaChange {
5
+ path: string;
6
+ change_kind: ShadowDiffFile['change_kind'];
7
+ cls: StoredFileClass;
8
+ size: number | null;
9
+ container: string;
10
+ durationSeconds: number | null;
11
+ }
12
+ export declare function withheldMediaChanges(store: BlobStore, preTaskHashes: PreTaskHashes, files: readonly ShadowDiffFile[],
13
+ /** S2R1-F3: copies the user's current original of `rel` into `store` (the shadow's anchored base read). */
14
+ readLiveBase?: (rel: string) => Promise<unknown>): Promise<WithheldMediaChange[]>;
@@ -6,6 +6,8 @@ import { type CopySpaceVerdict } from './shadow-free-space';
6
6
  import { type TaskIntent } from './shadow-task-intent';
7
7
  import { type BlobStore } from './blob-store';
8
8
  import type { BlobRef } from './change-manifest';
9
+ import { type StoredFileClass } from './binary-classifier';
10
+ import { type WithheldMediaChange } from './withheld-media';
9
11
  import type { CasRecoveryAuthority } from './shadow-protocol';
10
12
  import { RecoveredShadow } from './shadow-recovery';
11
13
  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';
@@ -255,13 +257,12 @@ export declare class WorkspaceShadow {
255
257
  };
256
258
  };
257
259
  /**
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.
260
+ * P63 Slice 2 R1-F1, Slice 3 — the user's file a change modifies or deletes: its current bytes are streamed through the
261
+ * anchored copy into this task's blob store (where the review bundle reuses them as the base version) and classified
262
+ * (§10.1); a binary original is recorded as `base_class`, never refused. `required`: the file must exist (a deletion);
263
+ * for a modification a file the user removed meanwhile is left to the conflict check (null).
263
264
  */
264
- private assertRealBaseIsText;
265
+ private realBaseClass;
265
266
  /**
266
267
  * P63 Slice 2 (§8.1) — the verified blob sources of a review bundle for `files`: each new version from this instance's
267
268
  * blob store (`ownPostImageBlob`), each base version copied from the real file through the anchored helper and kept
@@ -288,7 +289,11 @@ export declare class WorkspaceShadow {
288
289
  size: number | null;
289
290
  changed: boolean;
290
291
  };
292
+ afterClass?: StoredFileClass;
293
+ beforeClass?: StoredFileClass;
291
294
  }>>;
295
+ /** P63 Slice 3 (§9.5) — the changes the user must approve before anything is applied (withheld-media.ts). */
296
+ withheldMediaChanges(files: readonly ShadowDiffFile[]): Promise<WithheldMediaChange[]>;
292
297
  /** §9.4 — this instance's blob store (a team bundle's source when it is published to the group's content store). */
293
298
  contentBlobStore(): BlobStore;
294
299
  /**
@@ -0,0 +1,14 @@
1
+ import type { BrainstormPanelActiveAgent } from './brainstorm-quorum';
2
+ import type { ShellEventEmitResult } from './emit-shell-event';
3
+ export declare const AGENT_TURN_DESKTOP_ONLY_DEGRADE_NOTICE = "Note: companion sync was unavailable for this turn; response shown on desktop only.";
4
+ export interface AgentTurnCellSection {
5
+ readonly authorAgent: BrainstormPanelActiveAgent;
6
+ readonly boundedBody: string;
7
+ }
8
+ export declare function shouldDegradeAgentTurnOnPreEmitFailure(promptResult: ShellEventEmitResult, meta: {
9
+ readonly sessionId: string;
10
+ readonly route: string;
11
+ readonly turnId: string;
12
+ }): boolean;
13
+ export declare function formatAgentTurnCellsMarkdown(cells: ReadonlyArray<AgentTurnCellSection>, formatDisplayName: (agent: BrainstormPanelActiveAgent) => string): string;
14
+ export declare function renderDegradedAgentTurnOnDesktop(cells: ReadonlyArray<AgentTurnCellSection>, formatDisplayName: (agent: BrainstormPanelActiveAgent) => string, dispatchAdvisory: (text: string) => void): void;
@@ -0,0 +1,8 @@
1
+ /**
2
+ * P63 Slice 3 (WORKSPACE-HARDENING-DESIGN §10.1; F-S3-RETRY-BRIEF-BINARY) — in the P46a retry brief, a collided file that
3
+ * is a binary on either side has no text to show: it is named with its class and left to the copy, never decoded.
4
+ * Extracted from quorum-loop.ts (a frozen monolith), which keeps a two-line delegation.
5
+ */
6
+ import { type ShadowDiffFile } from '../local-executor/shadow-protocol';
7
+ /** The brief's lines for a binary collision, or null for text (the caller shows the reviewed text). */
8
+ export declare function binaryRetryBriefLines(filePath: string, who: string, reviewedFile: ShadowDiffFile | undefined): string[] | null;
@@ -75,7 +75,9 @@ export declare function agentIdForAuthor(agent: BrainstormPanelActiveAgent): 'cl
75
75
  * The display names come from the caller (index.ts `agentDisplayName`) so the
76
76
  * mapping stays single-sourced.
77
77
  */
78
- export declare function renderBrainstormPanelHeader(activeAgents: readonly BrainstormPanelActiveAgent[], displayName: (agent: BrainstormPanelActiveAgent) => string): string;
78
+ export declare function renderBrainstormPanelHeader(activeAgents: readonly BrainstormPanelActiveAgent[], displayName: (agent: BrainstormPanelActiveAgent) => string, options?: {
79
+ readonly hasWebContext?: boolean;
80
+ }): string;
79
81
  /**
80
82
  * Below Max: the frontier panel is fenced, but the local model still
81
83
  * brainstorms. No frontier agent was called and no code was changed.