@quantiya/codevibe-claude-plugin 2.0.41 → 2.0.43

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 (21) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/node_modules/@quantiya/codevibe-core/dist/index.js +454 -445
  3. package/node_modules/@quantiya/codevibe-core/dist/local-executor/class-b-consumer.d.ts +5 -5
  4. package/node_modules/@quantiya/codevibe-core/dist/local-executor/hook-bridge.d.ts +1 -1
  5. package/node_modules/@quantiya/codevibe-core/dist/local-executor/local-executor-impl.d.ts +53 -20
  6. package/node_modules/@quantiya/codevibe-core/dist/local-executor/types.d.ts +8 -2
  7. package/node_modules/@quantiya/codevibe-core/dist/local-executor/workspace-shadow.d.ts +16 -0
  8. package/node_modules/@quantiya/codevibe-core/dist/orchestration-shell/cli.js +1302 -374
  9. package/node_modules/@quantiya/codevibe-core/dist/orchestration-shell/command-intent.d.ts +18 -0
  10. package/node_modules/@quantiya/codevibe-core/dist/orchestration-shell/components/InputBar.d.ts +1 -0
  11. package/node_modules/@quantiya/codevibe-core/dist/orchestration-shell/gate-decision-submit.d.ts +26 -5
  12. package/node_modules/@quantiya/codevibe-core/dist/orchestration-shell/index.d.ts +17 -0
  13. package/node_modules/@quantiya/codevibe-core/dist/orchestration-shell/non-tty-fallback.d.ts +1 -1
  14. package/node_modules/@quantiya/codevibe-core/dist/orchestration-shell/quorum-loop.d.ts +224 -51
  15. package/node_modules/@quantiya/codevibe-core/dist/orchestration-shell/reducer.d.ts +7 -0
  16. package/node_modules/@quantiya/codevibe-core/dist/orchestration-shell/slash-routes/continuation.d.ts +5 -0
  17. package/node_modules/@quantiya/codevibe-core/dist/orchestration-shell/task-label.d.ts +25 -0
  18. package/node_modules/@quantiya/codevibe-core/dist/orchestration-shell/types.d.ts +83 -0
  19. package/node_modules/@quantiya/codevibe-core/dist/substrate-launch/engage-substrate.d.ts +2 -0
  20. package/node_modules/@quantiya/codevibe-core/package.json +1 -1
  21. package/package.json +2 -2
@@ -53,4 +53,22 @@ export declare function extractAllAgentMentionIntents(text: string): AgentMentio
53
53
  export declare function stripLeadingAgentControlToken(text: string): string;
54
54
  export declare function buildCommandIntentEnvelope(intent: AgentMentionIntent): CommandIntentEnvelope;
55
55
  export declare function buildCommandIntentMetadata(text: string): CommandIntentMetadata | undefined;
56
+ /**
57
+ * Detects whether a prompt or brief expresses need for external web browsing or online searching.
58
+ * Used by QuorumLoop to dynamically attach web tools ONLY when needed, preserving full
59
+ * sandboxing for normal non-web tasks.
60
+ *
61
+ * Supports language-agnostic URLs, as well as English, Chinese, and Japanese web search intents.
62
+ */
63
+ export declare function isWebAccessNeeded(text: string): boolean;
64
+ /**
65
+ * Detects whether a prompt expresses intent to create, write, save, or modify local files/reports,
66
+ * as opposed to a purely read-only conversational request.
67
+ * Used as a backstop when classifying or routing web requests so that prompts asking to fetch/search
68
+ * AND write/save to a local file route to `start_task` (implementor execution) instead of desktop `browse`.
69
+ *
70
+ * Designed to handle English, Chinese, Japanese, Korean, European languages, and universal structural patterns
71
+ * (e.g. file targets and shell redirection), while accurately filtering out informational "how-to" / conceptual questions.
72
+ */
73
+ export declare function hasFileMutationIntent(text: string): boolean;
56
74
  export {};
@@ -84,6 +84,7 @@ export declare function computeInputWindow(display: string, caretCol: number, va
84
84
  export type GatePromptMode = {
85
85
  kind: 'awaiting-number';
86
86
  maxOption: number;
87
+ taskLabel?: string;
87
88
  } | {
88
89
  kind: 'awaiting-notes';
89
90
  decisionDraft: string;
@@ -89,13 +89,34 @@ type GatePromptEntry = Extract<ConversationEntry, {
89
89
  kind: 'gate-prompt';
90
90
  }>;
91
91
  /**
92
- * §5.1 + #C8M-11 active-gate-prompt scan. Tail-to-head walk for the first
93
- * `kind:'gate-prompt'` entry with `final === false`. Pure; safe to call from a
92
+ * §5.1 + #C8M-11 active-gate-prompt scan: the OLDEST `kind:'gate-prompt'`
93
+ * entry with `final === false` (head-to-tail). Pure; safe to call from a
94
94
  * render path. Returns the entry verbatim so callers can pattern-match the
95
- * discriminated union directly. (Shared so the TTY component + non-TTY loop use
96
- * the identical scan.)
95
+ * discriminated union directly. (Shared so the TTY component, the mobile
96
+ * reply route and the non-TTY loop use the identical scan.)
97
+ *
98
+ * P46a (F-P46a-5): this used to be tail-to-head (the NEWEST open gate). With
99
+ * several tasks in flight, gates arrive in bursts, and the newest-wins rule
100
+ * moved the target under the user's fingers — the composer read `Task #2`,
101
+ * a gate for `#3` arrived, and the `1` that was typed for #2 approved #3
102
+ * (E2E r3 q1). Oldest-first keeps the active gate — its pinned card, the
103
+ * composer label `Type a number 1..N · Task #n`, and the gate a digit
104
+ * resolves — stable until the user answers it; newer gates wait their turn.
105
+ *
106
+ * Stage 1 r1 F2(b): an older open gate that is NOT answerable — stuck in
107
+ * `confirming` / `submitting` (the F-P46a-1 wedge) or `claimed` elsewhere —
108
+ * must not block every newer gate: the active gate is the oldest open gate in
109
+ * an ANSWERABLE phase (`awaiting-number` / `awaiting-notes`); when no open gate
110
+ * is answerable, the oldest open one (so the UI still shows its state).
111
+ *
112
+ * Stage 1 r2 F1: that choice is STICKY. Callers holding the store state pass
113
+ * `state.activeGatePromptId` (reducer-owned, see `normalizeActiveGatePrompt`):
114
+ * while that entry is open it stays the active gate, so an older gate whose
115
+ * submit rolled back to `awaiting-number` never takes the digits (or a
116
+ * half-typed note) back from the gate the user is answering. Without an id the
117
+ * scan above applies (pure callers and tests).
97
118
  */
98
- export declare function findActiveGatePromptEntry(conversation: ReadonlyArray<ConversationEntry>): GatePromptEntry | null;
119
+ export declare function findActiveGatePromptEntry(conversation: ReadonlyArray<ConversationEntry>, activeGatePromptId?: string | null): GatePromptEntry | null;
99
120
  /**
100
121
  * Submit a gate decision: dispatch SUBMIT_STARTED, fire the SDK, dispatch
101
122
  * RESOLVED on success (and notify `onTerminalDecision`) or SUBMIT_FAILED on
@@ -48,6 +48,23 @@ import { contextItemOccurrence, type ContextDurabilityReceipt } from './context-
48
48
  import { renderRehydratedSessionContext } from './context-compaction';
49
49
  import type { AgentKind as LocalExecutorAgentKind } from '../local-executor/types';
50
50
  import { type TaskProgressEvent } from './task-progress';
51
+ import { taskLabel } from './task-label';
52
+ /**
53
+ * P46a (Stage 1 r1 F2a) — the oldest unresolved continuation-offer gate
54
+ * prompt, whatever other gates are open. Pure.
55
+ */
56
+ export declare function findActiveContinuationOfferEntry(conversation: ReadonlyArray<ConversationEntry>): (ConversationEntry & {
57
+ kind: 'gate-prompt';
58
+ }) | null;
59
+ /**
60
+ * P46a (Stage 1 r1 F2c) — a typed digit from the phone cannot choose between
61
+ * several open review gates (the phone shows no task label until P46b): the
62
+ * advisory to send back instead of answering the oldest gate, or null when the
63
+ * reply is not a bare digit or at most one gate is answerable. Pure.
64
+ */
65
+ export declare function mobileGateReplyAmbiguity(state: Parameters<typeof taskLabel>[0] & {
66
+ conversation: ReadonlyArray<ConversationEntry>;
67
+ }, text: string): string | null;
51
68
  export declare class OrchestrationShellStartupError extends Error {
52
69
  readonly cause?: unknown;
53
70
  constructor(message: string, cause?: unknown);
@@ -48,7 +48,7 @@ export declare function plannerDecisionRenderEnabled(env?: NodeJS.ProcessEnv): b
48
48
  * Renders a single `ConversationEntry` as a line-prefixed text string.
49
49
  * Strict text only — no ANSI escapes, no colors, no `\r` rewrites.
50
50
  */
51
- export declare function renderEntryAsLine(entry: ConversationEntry): string;
51
+ export declare function renderEntryAsLine(entry: ConversationEntry, labelFor?: (taskId: string) => string | undefined): string;
52
52
  /**
53
53
  * Subscribes to the store and prints new conversation entries as
54
54
  * line-prefixed text. Returns a promise that resolves on `EXIT` action
@@ -325,19 +325,6 @@ export interface QuorumLoopDeps {
325
325
  * unset (see {@link resolveTeamExecution}).
326
326
  */
327
327
  teamExecution?: TeamExecution;
328
- /**
329
- * CP-7 Stage-2 r3 (Codex) HIGH — ARM the LE's current/authorized task id so
330
- * the round-0 (and revise) implementor spawn engages the CP-7 trusted-execution
331
- * substrate instead of the pre-TaskAuthorized reduced-trust path. `startTask`
332
- * threads the minted taskId here BEFORE the round-0 spawn drains (and rolls it
333
- * back to `null` on a START rejection); production wires it to
334
- * `LocalExecutorImpl.setActiveTaskId`. ABSENT (legacy/tests) → the LE's task id
335
- * is unchanged (the pre-fix behavior); production MUST wire it so the
336
- * implementor never spawns unbadged/unsandboxed. The same state
337
- * (`LocalExecutorImpl.taskId`) drives `engageSubstrateForSpawn` (local-executor-
338
- * impl.ts:566) to call `substrateEngager` rather than emit a `tier:'none'` badge.
339
- */
340
- armLeTaskId?: (taskId: string | null) => void;
341
328
  /**
342
329
  * CP-7 §8 (Stage-1-resolved) — engage the CP-7 trusted-execution substrate +
343
330
  * broker for ONE reviewer seat. When wired (production via cli.ts →
@@ -599,12 +586,59 @@ interface TeamRunAuthority {
599
586
  * here closes packet-order races without extending or weakening the wire.
600
587
  */
601
588
  export declare function teamRound0GateId(taskId: string, taskGroupId: string, trackIndex: number, dispatchGeneration: number): string;
589
+ /**
590
+ * P46a D1 — the reducer-owned in-session task numbering, reachable from the
591
+ * loop through this sink (installed by the shell after the store exists).
592
+ * `assign` returns the number the store assigned (undefined when unwired);
593
+ * `label` renders `#n` / `#n retry` / the short-id fallback for any task id.
594
+ */
595
+ export interface TaskOrdinalSink {
596
+ assign(taskId: string, opts: {
597
+ kind: 'single' | 'group';
598
+ retryOf?: string;
599
+ }): number | undefined;
600
+ label(taskId: string): string;
601
+ }
602
+ /** P46a C3 — the desktop-local "could not apply" menu the loop asks the shell to present. */
603
+ export interface ApplyConflictMenu {
604
+ taskId: string;
605
+ taskLabel: string;
606
+ conflicts: Array<{
607
+ path: string;
608
+ cause: 'session-task' | 'external' | 'write-error';
609
+ byTaskLabel?: string;
610
+ }>;
611
+ autoRetried: boolean;
612
+ retryBrief: string;
613
+ /** The ROOT user request (Stage 1 r3 F1) — the menu's retry carries it (web-access decision, `/status`). */
614
+ request: string;
615
+ agent: AgentKind;
616
+ }
602
617
  export declare class QuorumLoop {
603
618
  private readonly deps;
604
619
  private readonly sleep;
605
620
  private workspaceOutcomeSink?;
606
- /** The active task this desktop is driving (set at start_task). */
607
- private activeTaskId;
621
+ /**
622
+ * P46a B1 — the FOCUSED single task: the task the UI means when a command
623
+ * names none (`/continue`, `/review-reset`, the status "starting" row).
624
+ * Default = the most recently CONFIRMED START; `/task focus <n>` sets it
625
+ * explicitly; it advances to the newest remaining single task when the
626
+ * focused task ends; a rejected START never moves it. It replaces the three
627
+ * P45 singletons (`activeTaskId` / `activeBrief` / `activeImplementorAgent`):
628
+ * every reader now resolves through the task's OWN record
629
+ * (`singleTaskContextByTask`), never through "the latest task".
630
+ */
631
+ private focusedTaskId;
632
+ /** P46a B1 — confirmed single tasks in START-confirmation order (focus fallback). */
633
+ private readonly confirmedSingleOrder;
634
+ /**
635
+ * P46a B1 — the most recent START still in flight (or null). A legacy
636
+ * GATE_DISPATCH with an EMPTY envelope task id can only belong to it (the P45
637
+ * rule); with no START in flight such a packet is a no-op left to recovery.
638
+ */
639
+ private latestInFlightStart;
640
+ /** Stage 1 r1 F13 — every START still in flight, in issue order; `latestInFlightStart` is its last entry. */
641
+ private readonly startOrderInFlight;
608
642
  /** Tasks whose next automatic revise round must start a fresh review baseline. */
609
643
  private readonly reviewScopeResetTasks;
610
644
  /** Non-terminal tasks for which `/review-reset` may arm the next revise round. */
@@ -613,16 +647,9 @@ export declare class QuorumLoop {
613
647
  private readonly epochByTaskId;
614
648
  private readonly tokensByTaskId;
615
649
  private readonly tokensCountedGates;
616
- /**
617
- * The implementor brief for the active task (set at start_task). The
618
- * GATE_DISPATCH consumer reads this to drive the round-0 implementor spawn —
619
- * the brief is no longer threaded inline through the spawn call (the spawn
620
- * moved out of `startTask` into the GATE_DISPATCH consumer, §3.C.20 FIX).
621
- */
622
- private activeBrief;
623
650
  /**
624
651
  * IMAGE-ATTACHMENT-DESIGN.md §5 — TASK-SCOPED image attachments (parallel to
625
- * `activeBrief`/`activeBriefByTask`). The single-impl path uses `activeAttachments`;
652
+ * `singleTaskContextByTask`/`activeBriefByTask`). The single-impl path uses `activeAttachments`;
626
653
  * the team path keys by the child `taskId` in `activeAttachmentsByTask` (armed at
627
654
  * `registerTeamTaskBrief`, before its buffered GATE_DISPATCH drains). Re-copied
628
655
  * into EACH round's shadow at `runImplementorRound` (round-0, revise, continuation,
@@ -677,19 +704,50 @@ export declare class QuorumLoop {
677
704
  private readonly recoveredBriefsByAuthority;
678
705
  private readonly activeBriefByTask;
679
706
  /**
680
- * P45 D1 (M7 E2E step 8, 2026-09-14) — the brief + agent of EVERY single-
681
- * implementor task this loop started, keyed by task id. `activeTaskId` /
682
- * `activeBrief` / `activeImplementorAgent` are the LATEST task only: when a
683
- * second single task started while the first still ran (the planner routed a
684
- * mid-task turn as a task), the first task's revise round was composed from
685
- * the second task's request (`buildReviseBrief` fell back to `activeBrief`)
686
- * and its round-0 dispatch whose envelope names the FIRST task — would have
687
- * been mis-read as a team dispatch. Round-0 spawns, revise rounds and the
688
- * continuation context resolve through this map first; the singletons stay
689
- * as the legacy fallback (an empty envelope task id, the test seam). Entries
690
- * are removed with the task's other per-task state at its terminal cleanup.
707
+ * P45 D1 (M7 E2E step 8, 2026-09-14) / P46a B1 — the brief + agent of EVERY
708
+ * LIVE single-implementor task this loop started, keyed by task id. P45 added
709
+ * it beside the three "latest task" singletons (a second single task's
710
+ * request had replaced the first task's in its revise round); P46a deletes
711
+ * the singletons round-0 spawns, revise rounds, the continuation context and
712
+ * every other reader resolve ONLY through this map. An entry exists from the
713
+ * START call (in flight) until the task's terminal cleanup; `retryOf` links a
714
+ * `#n retry` task to its `apply_failed` original (P46a C2/C3).
691
715
  */
692
716
  private readonly singleTaskContextByTask;
717
+ /** Agent web browsing (upstream 2.0.37) — whether a task's ORIGINAL request needs web access; ends with the task (`forgetSingleTask`, team cleanup). */
718
+ private readonly webAccessByTask;
719
+ /**
720
+ * P46a D2 — append-only per-loop-life record of every single task this loop
721
+ * started (confirmed or in flight) with its terminal outcome once known, for
722
+ * `/status` (`describeTasks`). Never deleted (bounded by the session's tasks).
723
+ */
724
+ private readonly singleTaskHistory;
725
+ /**
726
+ * P46a C1 — every single-task promote THIS session landed, by workspace-
727
+ * relative path: the promoting task + the post-image hash/mode the promote
728
+ * compare-and-set reads (`readRealMetadata`; `null` hash = a deletion). Used
729
+ * to attribute a later collision to a `session-task` (the path's current
730
+ * metadata equals a recorded post-image) or `external` (anything else —
731
+ * including a session-promoted file the user edited afterwards, and every
732
+ * promote by another session). In-memory, fail-safe: empty after a desktop
733
+ * restart, so every collision then classifies as `external`.
734
+ */
735
+ private readonly promotedByPath;
736
+ /** P46a C2 — originals whose ONE automatic retry has been issued (never a second). */
737
+ private readonly autoRetryIssuedFor;
738
+ /**
739
+ * P46a C2 — retry STARTs planned inside the promote critical section and
740
+ * issued only after it (and the durable `apply_failed` record) completed:
741
+ * `startTask` runs the round-0 spawn before returning, so it must never run
742
+ * inside `promoteShadowLocked`.
743
+ */
744
+ private readonly pendingPromoteRetries;
745
+ /** P46a D1 — the store-owned ordinal sink (installed by the shell after construction). */
746
+ private taskOrdinalSink?;
747
+ /** P46a C3 — the desktop-local apply-conflict menu sink (installed by the shell). */
748
+ private applyConflictSink?;
749
+ /** P46a C2 — notified when an automatic retry task started (the shell records its lifecycle row). */
750
+ private retryStartedSink?;
693
751
  /**
694
752
  * CP-1.f revise-context fix (dogfood task 2ac68708, 2026-06-11) — the BINDING
695
753
  * user-requested changes for a task, accumulated across ALL revise rounds.
@@ -1244,10 +1302,67 @@ export declare class QuorumLoop {
1244
1302
  setWorkspaceOutcomeSink(sink: WorkspaceOutcomeSink | undefined): void;
1245
1303
  isWorkspaceTaskTerminallyRetired(taskId: string): boolean;
1246
1304
  isTaskTerminallyRetired(taskId: string): boolean;
1247
- /** The current active task id (for the recovery poll). */
1305
+ /**
1306
+ * P46a B1 — the FOCUSED single task (null when no single task is live). The
1307
+ * P45 `activeTask` name is kept for the shell's readers; it now means the
1308
+ * focused task, never "the latest task".
1309
+ */
1248
1310
  get activeTask(): string | null;
1249
- /** Arm an explicit, one-shot comprehensive baseline for the active task. */
1250
- requestReviewScopeReset(): boolean;
1311
+ /** P46a B1 every LIVE single task this loop started (in flight or confirmed). */
1312
+ knownSingleTasks(): string[];
1313
+ /**
1314
+ * P46a B1 — `/task focus <n>`: make `taskId` the focused task. Refused (false)
1315
+ * for a task this loop does not know as a live single task.
1316
+ */
1317
+ setFocusedTask(taskId: string): boolean;
1318
+ /** P46a D1 — install the store-owned ordinal sink (the shell, after construction). */
1319
+ setTaskOrdinalSink(sink: TaskOrdinalSink | undefined): void;
1320
+ /** P46a C3 — install the desktop-local apply-conflict menu sink. */
1321
+ setApplyConflictSink(sink: ((menu: ApplyConflictMenu) => void) | undefined): void;
1322
+ /** P46a C2 — install the retry-started notifier (the shell records the lifecycle row). */
1323
+ setRetryStartedSink(sink: ((info: {
1324
+ taskId: string;
1325
+ retryOf: string;
1326
+ agent: AgentKind;
1327
+ }) => void) | undefined): void;
1328
+ /** P46a D1 — `#n` / `#n retry` / short id for any task id (never throws). */
1329
+ labelFor(taskId: string): string;
1330
+ /**
1331
+ * P46a D2 — every single task this loop started, for `/status`: its label,
1332
+ * agent, request (desktop-local plaintext — the user's own words), link to an
1333
+ * `apply_failed` original, loop state and whether it is the focused task.
1334
+ */
1335
+ describeTasks(): Array<{
1336
+ taskId: string;
1337
+ label: string;
1338
+ agent: AgentKind;
1339
+ brief: string;
1340
+ retryOf?: string;
1341
+ outcome: 'running' | 'applied' | 'discarded' | 'apply_failed' | 'unresolved' | 'rejected';
1342
+ implementorActive: boolean;
1343
+ focused: boolean;
1344
+ }>;
1345
+ /** Stage 1 r1 F13 — a START confirmed or rejected: the newest START still in flight (if any) becomes the empty-envelope target. */
1346
+ private dropInFlightStart;
1347
+ /**
1348
+ * P46a B1 — a single task ended (promote, terminal discard, rejected START):
1349
+ * drop its live record and, when it was the focused task, advance the focus
1350
+ * to the newest remaining confirmed single task (or none).
1351
+ */
1352
+ private forgetSingleTask;
1353
+ /**
1354
+ * Arm an explicit, one-shot comprehensive baseline for the FOCUSED task
1355
+ * (P46a B1). Refused with the candidate list when the focused task has no
1356
+ * eligible gate (or no single task is focused) — the shell renders the
1357
+ * `#n` candidates so the user can `/task focus <n>` first.
1358
+ */
1359
+ requestReviewScopeReset(): {
1360
+ armed: true;
1361
+ taskId: string;
1362
+ } | {
1363
+ armed: false;
1364
+ candidates: string[];
1365
+ };
1251
1366
  /**
1252
1367
  * Audited-path fix (Fix 5) — the host's detected implementor agents
1253
1368
  * (UPPERCASE `AgentKind`), as threaded into the loop at construction. Used by
@@ -1368,6 +1483,16 @@ export declare class QuorumLoop {
1368
1483
  agent: AgentKind;
1369
1484
  /** IMAGE-ATTACHMENT-DESIGN.md §5 — task-scoped image attachments, armed before the drain. */
1370
1485
  attachments?: ImageAttachment[];
1486
+ /** P46a C2/C3 — the `apply_failed` original this task retries (renders `#n retry`). */
1487
+ retryOf?: string;
1488
+ /**
1489
+ * P46a C2/C3 (Stage 1 r3 F1) — the ROOT user request a retry carries. A
1490
+ * retry's `brief` is the COMPOSED retry brief (diffs + reviewed contents that
1491
+ * can hold any text — URLs included); decisions that must follow the user's
1492
+ * own words (the agent-web access decision, the next retry brief, `/status`)
1493
+ * read this instead. Omitted for an ordinary task (its brief IS the request).
1494
+ */
1495
+ request?: string;
1371
1496
  }): Promise<{
1372
1497
  taskId: string;
1373
1498
  } | null>;
@@ -1820,6 +1945,27 @@ export declare class QuorumLoop {
1820
1945
  * half-applied tree.
1821
1946
  */
1822
1947
  promoteShadow(taskId: string): Promise<void>;
1948
+ /** P46a C2 — start every planned automatic retry as a new linked task. */
1949
+ private drainPromoteRetries;
1950
+ /** P46a D2 — correct a task's recorded outcome after its terminal cleanup ran. */
1951
+ private markTaskOutcome;
1952
+ /**
1953
+ * P46a C1 — classify each conflicting path: `session-task` when its current
1954
+ * real-tree metadata equals a post-image a single-task promote of THIS
1955
+ * session recorded (`promotedByPath`), else `external`.
1956
+ */
1957
+ private attributeConflicts;
1958
+ /**
1959
+ * P46a C2 — the retry brief: the original request, a "Files changed since
1960
+ * your first attempt" section (a unified diff from the failed task's REVIEWED
1961
+ * contents to the CURRENT real contents, per conflicting path, naming the
1962
+ * session task that changed it), and the reviewed contents themselves. Every
1963
+ * section is capped (`RETRY_BRIEF_SECTION_CAP` per path,
1964
+ * `RETRY_BRIEF_TOTAL_CAP` overall — the brief becomes the retry task's
1965
+ * durable `task_spec`, whose write is fire-and-forget) and secret-scrubbed
1966
+ * (`redactSecretShapesInText`) before insertion.
1967
+ */
1968
+ private buildRetryBrief;
1823
1969
  /**
1824
1970
  * CP-12 W2.b (§3.C.2b (d) / H3-4) — promote-quiescence BARRIER for `taskId`.
1825
1971
  * Enqueues an EMPTY critical section behind any in-flight shadow lifecycle op
@@ -2142,9 +2288,12 @@ export declare class QuorumLoop {
2142
2288
  private submitVerdict;
2143
2289
  /**
2144
2290
  * On WS (re)connect/startup, poll for the seats assigned to this desktop for
2145
- * any in_review gate of the active task and spawn any seat NEITHER already
2146
- * running-in-memory NOR already verdict-submitted (double-guard). Without
2147
- * this, a REVIEWER_DISPATCH dropped during a WS gap hangs the gate forever.
2291
+ * any in_review gate of EVERY in-flight task (P46a B3: the live single tasks,
2292
+ * the tasks with an active implementor, and the registered team tracks — one
2293
+ * query each) and spawn any seat NEITHER already running-in-memory NOR already
2294
+ * verdict-submitted (double-guard). Without this, a REVIEWER_DISPATCH dropped
2295
+ * during a WS gap hangs the gate forever. Residual (unchanged): after a full
2296
+ * desktop restart the in-memory maps are empty.
2148
2297
  */
2149
2298
  recoverInReviewAssignments(): Promise<void>;
2150
2299
  /**
@@ -2332,17 +2481,37 @@ export declare class QuorumLoop {
2332
2481
  */
2333
2482
  private reconstructRound0GateDispatch;
2334
2483
  private handleReviseFeedback;
2335
- /** The implementor agent the loop spawns (set at start_task). */
2336
- private activeImplementorAgent;
2337
- setActiveImplementorAgent(agent: AgentKind): void;
2338
2484
  /**
2339
- * #585 #C10M-9resolve the active task's request context for `/continue
2340
- * request` (the entrypoint wires this to the slash route's
2341
- * `getActiveRequestContext`). Returns `null` when no implementor round is
2342
- * active (no gate to hand off from). The `gateId`/`roundNumber` come from the
2343
- * ACTIVE implementor registry (the round currently running); the brief +
2344
- * source agent come from the loop's active state.
2485
+ * P46a B1set a single task's implementor agent on ITS record (the only
2486
+ * caller is `resumeFromContinuation`, after the user chose a target agent).
2487
+ * Replaces the session-wide `setActiveImplementorAgent` singleton.
2488
+ */
2489
+ private setTaskImplementorAgent;
2490
+ /**
2491
+ * #585 #C10M-9 / P46a B1 (OQ-1, decided) — resolve WHICH single task a
2492
+ * `/continue request` means: the FOCUSED task if it has an active implementor
2493
+ * round; else the single task that has one; else refuse — `ambiguous` lists
2494
+ * the candidates (the shell renders their `#n` so the user can
2495
+ * `/task focus <n>` first), `none` means no task is running a round. The
2496
+ * `gateId`/`roundNumber` come from the ACTIVE implementor registry (the round
2497
+ * currently running); the brief + source agent from the task's own record.
2345
2498
  */
2499
+ resolveContinuationRequest(): {
2500
+ kind: 'ok';
2501
+ ctx: {
2502
+ taskId: string;
2503
+ gateId: string;
2504
+ roundNumber: number;
2505
+ brief: string;
2506
+ sourceAgent: AgentKind;
2507
+ };
2508
+ } | {
2509
+ kind: 'ambiguous';
2510
+ candidates: string[];
2511
+ } | {
2512
+ kind: 'none';
2513
+ };
2514
+ /** The `ok` projection of {@link resolveContinuationRequest} (null otherwise). */
2346
2515
  getActiveContinuationRequestContext(): {
2347
2516
  taskId: string;
2348
2517
  gateId: string;
@@ -2604,8 +2773,12 @@ export declare class QuorumLoop {
2604
2773
  get _runningSeatsForTests(): ReadonlySet<string>;
2605
2774
  /** @internal — test introspection. */
2606
2775
  get _submittedSeatsForTests(): ReadonlySet<string>;
2607
- /** @internal — test seam to set the active task without start_task. */
2608
- _setActiveTaskForTests(taskId: string, brief?: string): void;
2776
+ /**
2777
+ * @internal — test seam to register a single task (its record + focus)
2778
+ * without start_task. P46a B1: the record is what every reader resolves, so
2779
+ * the seam always creates one (an empty brief when none is given).
2780
+ */
2781
+ _setActiveTaskForTests(taskId: string, brief?: string, agent?: AgentKind, gateIds?: string[]): void;
2609
2782
  /** @internal — read a task's accumulated binding user notes. */
2610
2783
  _userNotesForTests(taskId: string): readonly string[];
2611
2784
  /** @internal — A1b: read a task's accumulated structured findings history. */
@@ -14,3 +14,10 @@ export declare function reducer(state: OrchestrationState, action: Orchestration
14
14
  * task-start size heuristic.
15
15
  */
16
16
  export declare function hasRunningSingleTask(state: OrchestrationState): boolean;
17
+ /**
18
+ * P46a C3 — the apply-conflict menu's reply hint. A digit answers it only while
19
+ * no review gate is awaiting a number (the gate composer takes digits first);
20
+ * `/task retry <n>` / `/task discard <n>` answer it unambiguously at any time.
21
+ * Slash commands keep the menu open; any other text closes it (Stage 1 r1 F3).
22
+ */
23
+ export declare const APPLY_CONFLICT_MENU_HINT = "Reply with a number 1-2 (or /task retry <n> / /task discard <n> while another review is open); other text closes this menu.";
@@ -52,6 +52,11 @@ export interface ContinuationCliDeps {
52
52
  } | null>;
53
53
  /** Resolve the active-task request context (loop state). `null` if no active task. */
54
54
  getActiveRequestContext?: () => ActiveContinuationRequestContext | null;
55
+ /**
56
+ * P46a B1 — the refusal text when `getActiveRequestContext` is null (several
57
+ * tasks running a round → the `#n` candidates; none → the plain message).
58
+ */
59
+ explainNoActiveRequest?: () => string;
55
60
  /**
56
61
  * PHASE-CP-10-MIN (#585) #C10M-9/11 — `/continue accept <target>`: the
57
62
  * rescue/headless path. The entrypoint wires this to call
@@ -0,0 +1,25 @@
1
+ import type { OrchestrationState } from './types';
2
+ export declare function shortTaskId(taskId: string): string;
3
+ /**
4
+ * The label for `taskId` (a single task id, a team child task id, a
5
+ * `taskGroupId`, or a `group:<id>` gate id).
6
+ */
7
+ export declare function taskLabel(state: Pick<OrchestrationState, 'taskOrdinals' | 'team'>, taskId: string): string;
8
+ /**
9
+ * Resolve `#n` / `n` (a single task or a team group number) back to the id
10
+ * that holds it, or null. Used by `/task focus <n>`.
11
+ */
12
+ export declare function taskIdForOrdinal(state: Pick<OrchestrationState, 'taskOrdinals'>, ordinal: number): string | null;
13
+ /**
14
+ * P46a (F-P46a-6) — the advisory the shell prints under a gate prompt whose
15
+ * task already ENDED on this side as `discarded` (its implementor round failed
16
+ * here — typically the result's submit lost its response in a network cut —
17
+ * while the engine still had the result, reviewed it and raised the gate).
18
+ * Accepting such a gate applies nothing; the user must know before deciding.
19
+ * Returns null for a live task or any other outcome.
20
+ */
21
+ export declare function endedTaskAdvisory(described: ReadonlyArray<{
22
+ taskId: string;
23
+ label: string;
24
+ outcome: string;
25
+ }>, taskId: string): string | null;
@@ -427,6 +427,7 @@ export type ConversationEntry = {
427
427
  panel: {
428
428
  variant: 'prompt';
429
429
  envelope: GatePromptEnvelope;
430
+ taskLabel?: string;
430
431
  } | {
431
432
  variant: 'summary';
432
433
  reviewSummary: ReviewSummaryPanelModel;
@@ -522,6 +523,51 @@ export interface PendingPlannerOffer {
522
523
  /** §13 Option-2 — ordered-with-dups carried paths for the number-based chip rewrite. */
523
524
  attachmentPaths?: string[];
524
525
  }
526
+ /**
527
+ * P46a D1 — one in-session task number. Assigned by the reducer from ONE
528
+ * per-session counter (`nextTaskOrdinal`) at the START-confirmed point of a
529
+ * single task (`QuorumLoop.startTask`) or when `createTaskGroup` returns for a
530
+ * team (`launchTeamFromWorkItems`), so both paths share the numbering. A team
531
+ * group is one number (its child tasks render `#n.1`, `#n.2` via `state.team`).
532
+ * `retryOf` links a `#n retry` task (P46a C2/C3) to the `apply_failed` original.
533
+ * In-memory only: production has no session resume (design §2.4/A8), so every
534
+ * launch is a new session with its own numbering; nothing is persisted.
535
+ */
536
+ export interface TaskOrdinalRecord {
537
+ readonly ordinal: number;
538
+ readonly kind: 'single' | 'group';
539
+ readonly retryOf?: string;
540
+ }
541
+ /**
542
+ * P46a C3 — the desktop-local "could not apply" menu. Modeled on
543
+ * `pendingPlannerOffer` (a local numbered reply resolves it; free text clears
544
+ * it) but NEVER mirrored to mobile — the durable, mobile-answerable form is
545
+ * P46b A5 (HD-10). Not durable: a desktop relaunch drops it with its session
546
+ * (the failed task's shadow is already discarded; the durable `apply_failed`
547
+ * outcome stands).
548
+ */
549
+ export interface PendingApplyConflict {
550
+ /** The `apply_failed` task the menu is about. */
551
+ readonly taskId: string;
552
+ /** Its rendered label (`#n`, `#n retry`, or the short-id fallback). */
553
+ readonly taskLabel: string;
554
+ /** Workspace-relative paths + their attribution (paths only — never content). */
555
+ readonly conflicts: ReadonlyArray<{
556
+ readonly path: string;
557
+ readonly cause: 'session-task' | 'external' | 'write-error';
558
+ /** Rendered label of the session task whose promote changed the path. */
559
+ readonly byTaskLabel?: string;
560
+ }>;
561
+ /** True when this menu follows the one automatic retry's own promote failure. */
562
+ readonly autoRetried: boolean;
563
+ /** The `advisory` conversation entry that rendered the numbered options. */
564
+ readonly conversationEntryId: string;
565
+ /** The composed retry brief (original request + files changed since + reviewed contents). */
566
+ readonly retryBrief: string;
567
+ /** The ROOT user request the retry carries (Stage 1 r3 F1: web access and `/status` follow the user's own words). */
568
+ readonly request: string;
569
+ readonly agent: AgentKind;
570
+ }
525
571
  /**
526
572
  * [ADDITIVE WIDENING — CP-12 W3, per PHASE-CP-12-DESIGN.md §3.5 "minimal
527
573
  * multi-track status node; rich UX is #469"]. Per-track lifecycle state as the
@@ -679,6 +725,32 @@ export interface OrchestrationState {
679
725
  * source of truth.
680
726
  */
681
727
  pendingPlannerOffer: PendingPlannerOffer | null;
728
+ /**
729
+ * `[ADDITIVE WIDENING — P46a D1]` The in-session task numbers, keyed by
730
+ * single-task id or team `taskGroupId`. Reducer-owned (`TASK_ORDINAL_ASSIGNED`)
731
+ * from the single per-session `nextTaskOrdinal` counter; never persisted.
732
+ */
733
+ taskOrdinals: Map<string, TaskOrdinalRecord>;
734
+ /** `[ADDITIVE WIDENING — P46a D1]` The next number to assign (starts at 1). */
735
+ nextTaskOrdinal: number;
736
+ /**
737
+ * `[ADDITIVE WIDENING — P46a C3]` The OPEN desktop-local apply-conflict menus,
738
+ * keyed by the failed task's id, in presentation order (empty by default).
739
+ * Set by `APPLY_CONFLICT_PRESENTED` (a second task's menu never replaces the
740
+ * first — Stage 1 r2 F2); `/task retry|discard <n>` answers one by number, a
741
+ * bare digit only when exactly one is open; other desktop text closes them
742
+ * (`CLEAR_PENDING_APPLY_CONFLICT`). Never mirrored to mobile (P46b A5).
743
+ */
744
+ pendingApplyConflicts: ReadonlyMap<string, PendingApplyConflict>;
745
+ /**
746
+ * `[ADDITIVE WIDENING — P46a D1]` The gate-prompt entry the desktop composer,
747
+ * its pinned card and a typed digit currently target (null when no open gate
748
+ * is answerable). Reducer-owned and STICKY (Stage 1 r2 F1): it stays on the
749
+ * current gate while that gate is answerable; only when it leaves the
750
+ * answerable phases does the oldest answerable open gate take over — a
751
+ * rolled-back older gate waits its turn instead of taking the digits back.
752
+ */
753
+ activeGatePromptId: string | null;
682
754
  inputHistory: string[];
683
755
  /**
684
756
  * `[ADDITIVE WIDENING — CP-1.d]` per `PHASE-CP-1-D-DESIGN.md` §4.8
@@ -884,6 +956,17 @@ export type OrchestrationAction = {
884
956
  offer: PendingPlannerOffer;
885
957
  } | {
886
958
  type: 'CLEAR_PENDING_PLANNER_OFFER';
959
+ } | {
960
+ type: 'TASK_ORDINAL_ASSIGNED';
961
+ taskId: string;
962
+ kind: 'single' | 'group';
963
+ retryOf?: string;
964
+ } | {
965
+ type: 'APPLY_CONFLICT_PRESENTED';
966
+ menu: Omit<PendingApplyConflict, 'conversationEntryId'>;
967
+ } | {
968
+ type: 'CLEAR_PENDING_APPLY_CONFLICT';
969
+ taskId?: string;
887
970
  } | {
888
971
  type: 'STRUCTURAL_SUMMARY_GENERATED';
889
972
  summary: StructuralSummary;
@@ -80,6 +80,8 @@ export interface EngageSubstrateInput {
80
80
  claudeScratchHostRoot?: string;
81
81
  /** Safe locale source for the sanitized env (default: none). */
82
82
  localeSource?: Readonly<Record<string, string | undefined>>;
83
+ /** If true, the turn has active web tools (MCP bridge) and runs in reduced-trust mode. */
84
+ webBrowsingActive?: boolean;
83
85
  }
84
86
  /**
85
87
  * The substrate-engaged result: the spawn seam runs the agent via
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@quantiya/codevibe-core",
3
- "version": "2.0.36",
3
+ "version": "2.0.38",
4
4
  "description": "Core library for CodeVibe plugins - shared keychain, crypto, AppSync, and auth functionality",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",