@quantiya/codevibe-claude-plugin 2.0.44 → 2.0.45

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 (25) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/node_modules/@quantiya/codevibe-core/dist/appsync/appsync-client.d.ts +124 -3
  3. package/node_modules/@quantiya/codevibe-core/dist/appsync/queries.d.ts +6 -0
  4. package/node_modules/@quantiya/codevibe-core/dist/index.js +578 -461
  5. package/node_modules/@quantiya/codevibe-core/dist/local-executor/class-b-consumer.d.ts +10 -1
  6. package/node_modules/@quantiya/codevibe-core/dist/local-executor/descendant-reaper.d.ts +81 -0
  7. package/node_modules/@quantiya/codevibe-core/dist/local-executor/local-executor-impl.d.ts +15 -1
  8. package/node_modules/@quantiya/codevibe-core/dist/local-executor/process-tree.d.ts +6 -0
  9. package/node_modules/@quantiya/codevibe-core/dist/local-executor/snapshot-merge.d.ts +10 -0
  10. package/node_modules/@quantiya/codevibe-core/dist/local-executor/spawn.d.ts +1 -1
  11. package/node_modules/@quantiya/codevibe-core/dist/local-executor/types.d.ts +16 -0
  12. package/node_modules/@quantiya/codevibe-core/dist/orchestration-shell/cli.js +2246 -809
  13. package/node_modules/@quantiya/codevibe-core/dist/orchestration-shell/components/LiveProgressLine.d.ts +4 -0
  14. package/node_modules/@quantiya/codevibe-core/dist/orchestration-shell/components/OrchestrationApp.d.ts +13 -0
  15. package/node_modules/@quantiya/codevibe-core/dist/orchestration-shell/index.d.ts +77 -18
  16. package/node_modules/@quantiya/codevibe-core/dist/orchestration-shell/prompt-reply-router.d.ts +53 -0
  17. package/node_modules/@quantiya/codevibe-core/dist/orchestration-shell/quorum-loop.d.ts +138 -7
  18. package/node_modules/@quantiya/codevibe-core/dist/orchestration-shell/task-lifecycle-lines.d.ts +29 -0
  19. package/node_modules/@quantiya/codevibe-core/dist/orchestration-shell/task-progress.d.ts +1 -0
  20. package/node_modules/@quantiya/codevibe-core/dist/orchestration-shell/types.d.ts +48 -1
  21. package/node_modules/@quantiya/codevibe-core/dist/substrate/command-runner.d.ts +2 -0
  22. package/node_modules/@quantiya/codevibe-core/dist/substrate/types.d.ts +5 -0
  23. package/node_modules/@quantiya/codevibe-core/dist/substrate-launch/engage-substrate.d.ts +2 -0
  24. package/node_modules/@quantiya/codevibe-core/package.json +1 -1
  25. package/package.json +2 -2
@@ -2,6 +2,10 @@ import * as React from 'react';
2
2
  import { LiveProgress } from '../types';
3
3
  export interface LiveProgressLineProps {
4
4
  progress: LiveProgress | null;
5
+ /** P46b §3.7 — the task's label (`#2`) when several task lines render. */
6
+ label?: string;
7
+ /** P46b §3.7 — how many more busy tasks are not shown (last line only). */
8
+ moreCount?: number;
5
9
  /** Test seam; otherwise derived from the live terminal width, then 80 (mirrors StatusBar). */
6
10
  columns?: number;
7
11
  }
@@ -4,6 +4,19 @@ import { ConversationEntry, PostDecisionAction, Tier } from '../types';
4
4
  import type { PlannerRuntimeKind } from '../status-format';
5
5
  import type { UpdateReviewerPolicyInput, UserReviewerPolicySnapshot } from '../../types/reviewer';
6
6
  import { type ApplyUserDecisionClient as SharedApplyUserDecisionClient, type GroupDecisionSubmitDeps } from '../gate-decision-submit';
7
+ /**
8
+ * Stable empty list passed to <InputBar> to SUPPRESS slash autocomplete unless
9
+ * it fits (Stage-1 MEDIUM + Stage-2 — #469): the dropdown lives in the live
10
+ * region below the input, so an open dropdown while the live region is tall
11
+ * could push total output over the terminal height and re-trigger Ink's
12
+ * whole-scrollback reprint. P46b §3.7 — the dropdown is offered while tasks run
13
+ * (progress lines no longer suppress it); a pinned gate-prompt card or the
14
+ * reviewer wizard still does, and while it can be open we reserve
15
+ * DROPDOWN_MAX_ROWS plus every progress line beyond the first in the height cap
16
+ * (below) so it always fits. Module-scope constant → stable identity.
17
+ */
18
+ /** P46b §3.7 — at most this many per-task progress lines render (`+n more` after). */
19
+ export declare const MAX_TASK_PROGRESS_LINES = 3;
7
20
  /**
8
21
  * Minimal SDK contract for the applyUserDecision wire. Structurally
9
22
  * compatible with the full `AppSyncClient.applyUserDecision` signature
@@ -49,7 +49,7 @@ import { renderRehydratedSessionContext } from './context-compaction';
49
49
  import type { AgentKind as LocalExecutorAgentKind } from '../local-executor/types';
50
50
  import type { OrchestrationState as OrchestrationStateType } from './types';
51
51
  import { type TaskProgressEvent } from './task-progress';
52
- import { taskLabel } from './task-label';
52
+ import { type PromptReplyOrigin } from './prompt-reply-router';
53
53
  /**
54
54
  * Gathers the implementor agents currently active across single-implementor running tasks
55
55
  * and multi-track Agent Teams tracks. Used for multi-track priority allocation.
@@ -63,14 +63,37 @@ export declare function findActiveContinuationOfferEntry(conversation: ReadonlyA
63
63
  kind: 'gate-prompt';
64
64
  }) | null;
65
65
  /**
66
- * P46a (Stage 1 r1 F2c) — a typed digit from the phone cannot choose between
67
- * several open review gates (the phone shows no task label until P46b): the
68
- * advisory to send back instead of answering the oldest gate, or null when the
69
- * reply is not a bare digit or at most one gate is answerable. Pure.
66
+ * P46a (Stage 1 r1 F2c) / P46b §3.7 — a bare digit from the phone cannot choose
67
+ * between several open prompts: every prompt the phone can answer counts (gate
68
+ * prompts awaiting a number or a note, the retry prompt, a pending planner
69
+ * offer). Returns the advisory naming them, or null when the reply is not a bare
70
+ * digit or at most one prompt is open. Pure.
70
71
  */
71
- export declare function mobileGateReplyAmbiguity(state: Parameters<typeof taskLabel>[0] & {
72
- conversation: ReadonlyArray<ConversationEntry>;
73
- }, text: string): string | null;
72
+ export declare function mobileGateReplyAmbiguity(state: Pick<OrchestrationStateType, 'conversation' | 'taskOrdinals' | 'team' | 'pendingApplyConflicts' | 'pendingPlannerOffer'>, text: string): string | null;
73
+ /**
74
+ * P46b §3.13 item 3 — route a reply that names or must choose among the open
75
+ * prompts. Returns true when the line was consumed (answered, or refused with
76
+ * an advisory); false sends it down today's paths (the keyboard's active
77
+ * prompt, slash commands, the planner).
78
+ *
79
+ * 1. While any prompt is open, `#n <rest>` / `#offer <rest>` is always
80
+ * consumed, from either origin: naming an open prompt it answers it
81
+ * (making a gate the active prompt), otherwise an advisory.
82
+ * 2. Keyboard lines without `#` → today's paths (never refused).
83
+ * 3. Phone lines without `#`: a bare digit with several open prompts is
84
+ * refused; with one it answers it. With several prompts awaiting a note a
85
+ * plain line or bare `/cancel` is refused; with exactly one it is that
86
+ * prompt's note (or leaves its notes entry). Anything else → today's paths.
87
+ */
88
+ export declare function routeOpenPromptReply(deps: {
89
+ store: OrchestrationStore;
90
+ args: RunOrchestrationShellArgs;
91
+ emitShellEventBound: ReturnType<typeof createShellEventEmitter>;
92
+ generator: StructuralSummaryGenerator;
93
+ gateDecisionDeps: GateDecisionSubmitDeps | null;
94
+ origin: PromptReplyOrigin;
95
+ text: string;
96
+ }): Promise<boolean>;
74
97
  export declare class OrchestrationShellStartupError extends Error {
75
98
  readonly cause?: unknown;
76
99
  constructor(message: string, cause?: unknown);
@@ -380,6 +403,8 @@ export declare function runOrchestrationShell(args: RunOrchestrationShellArgs):
380
403
  * simply renders with no review panel (fail-open).
381
404
  */
382
405
  export declare const TASK_REVIEW_SUMMARY_FETCH_TIMEOUT_MS = 5000;
406
+ /** P46b §3.9 — the engine's phone retry prompt kind (core-rs `apply_retry.rs`). */
407
+ export declare const APPLY_RETRY_PROMPT_KIND = "orchestration_apply_retry";
383
408
  /**
384
409
  * Plaintext-first dispatcher per #C8M-7 (Stage 1 R1 M6 lock):
385
410
  * 1. If `event.metadata` is an object, use it directly. Otherwise try
@@ -508,18 +533,48 @@ export declare function createSubscribedEventDispatcher(deps: {
508
533
  onDurableGateResolution?: (resolution: DurableGateResolution) => Promise<void> | void;
509
534
  updateEventStatus?: (input: UpdateEventStatusInput) => Promise<unknown>;
510
535
  }): (event: Event) => void;
536
+ export declare function registerTeamTrackStarts(taskGroupId: string, initialRunning: number): void;
537
+ export declare function routeTeamShellEventToStore(store: OrchestrationStore, evt: TeamTapEvent, postLine?: (text: string) => void): void;
538
+ /**
539
+ * CP-12 W3 — minimal `/team <json>` arg parse. Accepts a JSON `WorkItemInput[]`
540
+ * (≥2 items). Shallow structural validation only — the backend
541
+ * `validate_create_input` (#C12-2 disjointness) is authoritative; we just need
542
+ * a well-formed array to drive `createTaskGroup`. Polished ergonomics
543
+ * (file-path-set shorthand, interactive builder) are [DEFERRED → CP-8 #469].
544
+ */
511
545
  /**
512
- * Route ONE team-mode LE shell event into the store's minimal `team` state.
513
- * The W2.b `emitTrackProgress` emits a NOTIFICATION carrying
514
- * `metadata.track_index` + `task_id` (+ `source: 'track_progress'`); the
515
- * `haltTaskGroup` path emits a NOTIFICATION with `source: 'task_group_halted'`.
516
- * We discriminate on `metadata.source` so non-team NOTIFICATIONs (planner
517
- * health, late-arrival audits) are IGNORED they have no team `source` tag.
546
+ * Audited-path fix (Fix 4 + Fix 5) parse a `/task` invocation into an optional
547
+ * implementor-agent override + the remaining brief. Supports a LEADING `--agent
548
+ * codex|claude|antigravity|agy` flag (case-insensitive value, space or '=' separated); everything after it (or the
549
+ * whole arg list when absent) is the brief, re-joined verbatim. The override is
550
+ * returned even when not detected on this host `selectImplementorAgent` decides
551
+ * whether to honor it (fail-closed to the default with a user-facing note).
518
552
  *
519
- * Pure mapping store dispatch; no rich rendering (that is [DEFERRED CP-8
520
- * #469]). Called from the `teamShellEventTap.fn` installed at startup.
553
+ * Tolerant: an unknown `--agent <value>` is IGNORED (the `--agent` token is dropped,
554
+ * the unrecognized value becomes part of the brief) rather than erroring — keeps the shortcut
555
+ * forgiving. When using the composite `--agent=<value>` form, the entire token including
556
+ * any unrecognized value is dropped so it does not pollute the brief. `--agent` with no
557
+ * following value is dropped (no brief consumed).
521
558
  */
522
- export declare function routeTeamShellEventToStore(store: OrchestrationStore, evt: TeamTapEvent): void;
559
+ export declare function parseTaskCommand(text: string): {
560
+ agent: LocalExecutorAgentKind | null;
561
+ brief: string;
562
+ /** P46a B1 — `/task focus <n>` (or `#n`): the task number to focus, else null. */
563
+ focus: number | null;
564
+ /** P46a C3 (Stage 1 r1 F3) — `/task retry <n>` / `/task discard <n>`: answer the apply-conflict menu of task #n. */
565
+ conflictChoice: {
566
+ choice: 1 | 2;
567
+ ordinal: number;
568
+ } | null;
569
+ /**
570
+ * P46b §3.7 — `/task cancel <n>`: `{ ordinal }`, or `{ ordinal: null }` for a
571
+ * `/task cancel` without a valid number (usage). Matched before the brief
572
+ * fallback, so it never starts a task with the brief "cancel …".
573
+ */
574
+ cancel: {
575
+ ordinal: number | null;
576
+ } | null;
577
+ };
523
578
  export declare function readOnlyAgentPrompt(args: {
524
579
  agent: LocalExecutorAgentKind;
525
580
  intent: AgentMentionIntent;
@@ -656,6 +711,8 @@ export declare function launchTeamFromWorkItems(deps: {
656
711
  * rewrite resolves each chip to the right ref. Retained for REJECT_RESTART reissue.
657
712
  */
658
713
  attachmentPaths?: string[];
714
+ /** P46b §3.10 — the failed group this launch retries (the new group is `#m retry`). */
715
+ retryOf?: string;
659
716
  }): Promise<LaunchTeamOutcome>;
660
717
  /**
661
718
  * PHASE-589/469 W3.2 RESIDUAL 1 — re-dispatch the WHOLE team for a
@@ -679,7 +736,9 @@ export declare function reissueTeamFromRetainedSpec(deps: {
679
736
  quorumLoop?: QuorumLoop;
680
737
  sessionId: string;
681
738
  durableStore: RevertManifestStore;
682
- }, taskGroupId: string): Promise<void>;
739
+ }, taskGroupId: string, options?: {
740
+ retryOf?: string;
741
+ }): Promise<void>;
683
742
  /**
684
743
  * PHASE-590 §3.D — the `team_decompose` routing path. Runs the local decomposer,
685
744
  * shape-checks (the schema is applied inside the decomposer parse) +
@@ -0,0 +1,53 @@
1
+ import type { ConversationEntry, OrchestrationState, PendingApplyConflict, PendingPlannerOffer } from './types';
2
+ export type PromptReplyOrigin = 'keyboard' | 'mobile';
3
+ type GatePromptEntry = Extract<ConversationEntry, {
4
+ kind: 'gate-prompt';
5
+ }>;
6
+ export type OpenPrompt = {
7
+ kind: 'gate';
8
+ /** `#3`, `#5.2`, or the short-id fallback of an unnumbered task. */
9
+ label: string;
10
+ entry: GatePromptEntry;
11
+ awaiting: 'number' | 'note';
12
+ optionCount: number;
13
+ } | {
14
+ kind: 'apply';
15
+ label: string;
16
+ menu: PendingApplyConflict;
17
+ awaiting: 'number';
18
+ optionCount: 2;
19
+ } | {
20
+ kind: 'offer';
21
+ label: 'offer';
22
+ offer: PendingPlannerOffer;
23
+ awaiting: 'number';
24
+ optionCount: number;
25
+ };
26
+ /** The reference a reply uses for `prompt`: `3`, `5.2` or `offer`. */
27
+ export declare function promptReference(prompt: OpenPrompt): string;
28
+ /** How advisories name `prompt`: `Task #3`, `Task #5.2`, `#offer`. */
29
+ export declare function promptName(prompt: OpenPrompt): string;
30
+ /** Every open prompt in presentation order (gates, then menus, then the offer). */
31
+ export declare function listOpenPrompts(state: Pick<OrchestrationState, 'conversation' | 'taskOrdinals' | 'team' | 'pendingApplyConflicts' | 'pendingPlannerOffer'>, origin: PromptReplyOrigin): OpenPrompt[];
32
+ /**
33
+ * `#2 1` → `{ reference: '2', rest: '1' }`; `#5.2 note text` → `{ '5.2', 'note text' }`;
34
+ * `#offer 3` → `{ 'offer', '3' }`; a bare `#2` → rest `''`. Null when the line
35
+ * does not start with a reference followed by whitespace or the end.
36
+ */
37
+ export declare function parsePromptReference(text: string): {
38
+ reference: string;
39
+ rest: string;
40
+ } | null;
41
+ /** The open prompt a reference names, or null. */
42
+ export declare function findPromptByReference(open: ReadonlyArray<OpenPrompt>, reference: string): OpenPrompt | null;
43
+ /** §3.7 — a bare digit from the phone while several prompts are open. */
44
+ export declare function severalPromptsAdvisory(open: ReadonlyArray<OpenPrompt>): string;
45
+ /** §3.13 item 3.3.2 — a plain phone line while several prompts await a note. */
46
+ export declare function severalNotesAdvisory(notes: ReadonlyArray<OpenPrompt>): string;
47
+ /** §3.13 item 3.1 — a `#n` reply the named prompt cannot take. */
48
+ export declare function expectedReplyAdvisory(prompt: OpenPrompt): string;
49
+ /** §3.13 item 3.1 — a `#n` reply naming no open prompt. */
50
+ export declare function noOpenPromptAdvisory(reference: string): string;
51
+ /** §3.13 item 3 — the phone hint when a numbered answer opens a gate's notes entry. */
52
+ export declare function notesHint(prompt: OpenPrompt): string;
53
+ export {};
@@ -1,7 +1,7 @@
1
1
  import type { AppSyncClient, TeamTrackRecoveryRow } from '../appsync/appsync-client';
2
2
  import type { Session } from '../types';
3
3
  import type { LocalExecutor } from '../local-executor';
4
- import type { ClassBInbound } from '../local-executor/types';
4
+ import type { ClassBInbound, QueuedWorkEndedPayload } from '../local-executor/types';
5
5
  import type { ImageAttachment } from './types';
6
6
  import { type TaskAuditSummaryModel, type ReviewHistorySnapshot, type UserDecisionRecord } from './audit-summary';
7
7
  import type { DecryptedReviewSummaryInput } from './gate-prompts';
@@ -596,6 +596,51 @@ export interface TaskOrdinalSink {
596
596
  }): number | undefined;
597
597
  label(taskId: string): string;
598
598
  }
599
+ /**
600
+ * P46a D2 / P46b §3.7 — a single task's recorded state for `/status`. `queued`
601
+ * waits for a running-track slot; `cancelled` ended before it started
602
+ * (`QUEUED_WORK_ENDED`, or a `cancelled` START replay); `already_finished` is a
603
+ * START replay of work that already ended.
604
+ */
605
+ export type SingleTaskOutcome = 'running' | 'queued' | 'applied' | 'discarded' | 'apply_failed' | 'unresolved' | 'rejected' | 'cancelled' | 'already_finished';
606
+ /** P46b §3.3 — what a confirmed START did. */
607
+ export interface StartTaskResultInfo {
608
+ taskId: string;
609
+ outcome: 'running' | 'queued' | 'already_finished' | 'cancelled';
610
+ /** Present when queued: 1-based position in the account's queue. */
611
+ queuePosition?: number;
612
+ runningTracks?: number;
613
+ trackBudget?: number;
614
+ }
615
+ /**
616
+ * P46b §3.13 item 1 — a single task's lifecycle change the shell turns into a
617
+ * store update and a phone line: queued at START, started (immediately or when
618
+ * a slot freed), or cancelled before it started.
619
+ */
620
+ export type TaskLifecycleEvent = {
621
+ kind: 'queued';
622
+ taskId: string;
623
+ agent: AgentKind;
624
+ queuePosition?: number;
625
+ runningTracks?: number;
626
+ trackBudget?: number;
627
+ } | {
628
+ kind: 'started';
629
+ taskId: string;
630
+ agent: AgentKind;
631
+ fromQueue: boolean;
632
+ retryOf?: string;
633
+ } | {
634
+ kind: 'cancelled';
635
+ taskId: string;
636
+ reason: string;
637
+ };
638
+ /** P46b §3.6 — the desktop's slot-lease capability sent on every START. */
639
+ export declare const SLOT_LEASE_PROTOCOL = 1;
640
+ /** P46b §3.6 — one task lease heartbeat per held task every 2 minutes (the engine lease is 600 s). */
641
+ export declare const TASK_LEASE_HEARTBEAT_INTERVAL_MS: number;
642
+ /** P46b §3.3 — map the START answer's state label onto what the START did. */
643
+ export declare function startOutcomeFor(workflowState: string): StartTaskResultInfo['outcome'];
599
644
  /** P46a C3 — the desktop-local "could not apply" menu the loop asks the shell to present. */
600
645
  export interface ApplyConflictMenu {
601
646
  taskId: string;
@@ -606,6 +651,14 @@ export interface ApplyConflictMenu {
606
651
  byTaskLabel?: string;
607
652
  }>;
608
653
  autoRetried: boolean;
654
+ /** P46b §3.10 — `group` for a team promote (`taskId` is the taskGroupId). */
655
+ kind?: 'single' | 'group';
656
+ /** P46b §3.9 — counts only, per cause (the retry prompt's content). */
657
+ counts?: {
658
+ sessionTask: number;
659
+ external: number;
660
+ writeErrors: number;
661
+ };
609
662
  retryBrief: string;
610
663
  /** The ROOT user request (Stage 1 r3 F1) — the menu's retry carries it (web-access decision, `/status`). */
611
664
  request: string;
@@ -636,6 +689,19 @@ export declare class QuorumLoop {
636
689
  private latestInFlightStart;
637
690
  /** Stage 1 r1 F13 — every START still in flight, in issue order; `latestInFlightStart` is its last entry. */
638
691
  private readonly startOrderInFlight;
692
+ /**
693
+ * P46b §3.7 — a queued task's START answer (position, running tracks, budget)
694
+ * and, once it ended before starting, the reason. Kept for `/status`.
695
+ */
696
+ private readonly queueInfoByTask;
697
+ /** P46b §3.7 — single tasks whose implementor has been spawned at least once (`starting` ends). */
698
+ private readonly spawnedSingleTasks;
699
+ /** P46b §3.13 item 1 — the shell's lifecycle sink (store update + phone line). */
700
+ private taskLifecycleSink?;
701
+ /** P46b §3.6 — the one per-session task lease heartbeat timer (null while stopped). */
702
+ private taskLeaseHeartbeatTimer;
703
+ /** P46b §3.6 — tasks whose heartbeat request is still in flight (one at a time per task). */
704
+ private readonly taskLeaseHeartbeatsInFlight;
639
705
  /** Tasks whose next automatic revise round must start a fresh review baseline. */
640
706
  private readonly reviewScopeResetTasks;
641
707
  /** Non-terminal tasks for which `/review-reset` may arm the next revise round. */
@@ -1117,8 +1183,22 @@ export declare class QuorumLoop {
1117
1183
  */
1118
1184
  private recoverySeedingDepth;
1119
1185
  /**
1120
- * Recovery B.1 and B.2 can overlap. A per-call two-row batch is therefore
1121
- * insufficient: two concurrent calls could each launch two implementors.
1186
+ * P46b (E2E T-3) how many barrier holders are in their PASS-2 relaunch (each
1187
+ * counted within `recoverySeedingDepth`), and the tracks they relaunch
1188
+ * (refcounted task ids and brief slots). While EVERY holder is relaunching (none
1189
+ * is still querying, seeding briefs or binding a `/team` create), only packets
1190
+ * for a relaunched track keep buffering — the relaunch-before-drain dedup order
1191
+ * is unchanged for them. A packet for any other track (a queued track promoted
1192
+ * meanwhile, whose brief PASS 1 already seeded) is consumed live instead of
1193
+ * waiting for every relaunched implementor round to finish, which can outlast
1194
+ * its 15-minute lease.
1195
+ */
1196
+ private recoveryRelaunchPhaseDepth;
1197
+ private readonly recoveryRelaunchTaskIds;
1198
+ private readonly recoveryRelaunchSlots;
1199
+ /**
1200
+ * Recovery B.1 and B.2 can overlap. A per-call batch is therefore
1201
+ * insufficient: two concurrent calls could each launch a full batch.
1122
1202
  * These permits are owned by the QuorumLoop/session and cover the complete
1123
1203
  * `launchRecoveredTrack` operation across every recovery entry path.
1124
1204
  */
@@ -1322,6 +1402,43 @@ export declare class QuorumLoop {
1322
1402
  retryOf: string;
1323
1403
  agent: AgentKind;
1324
1404
  }) => void) | undefined): void;
1405
+ /**
1406
+ * P46b §3.10 — a team promote could not be applied (the team promote has no
1407
+ * per-path session attribution, so every changed path is `external`). A team
1408
+ * gets no automatic retry: it always offers the menu (retry reissues the
1409
+ * whole team from its retained launch spec).
1410
+ */
1411
+ reportTeamApplyConflict(info: {
1412
+ taskGroupId: string;
1413
+ conflicts: readonly string[];
1414
+ writeErrors: readonly string[];
1415
+ }): void;
1416
+ /** P46b §3.13 item 1 — install the task lifecycle sink (the shell). */
1417
+ setTaskLifecycleSink(sink: ((event: TaskLifecycleEvent) => void) | undefined): void;
1418
+ /** P46b — notify the lifecycle sink; a throwing sink never breaks the loop. */
1419
+ private emitTaskLifecycle;
1420
+ /**
1421
+ * P46b §3.6 — the first change of a queued task to `cancelled (reason)`. Shared
1422
+ * by the `QUEUED_WORK_ENDED` packet, the `/status` catch-up and a `cancelled`
1423
+ * answer to `/task cancel`, so whichever arrives first acts and the others do
1424
+ * nothing. Returns true only on that first change.
1425
+ */
1426
+ markQueuedTaskCancelled(taskId: string, reason: string): boolean;
1427
+ /** P46b §3.6 — the `QUEUED_WORK_ENDED` Class-B packet. */
1428
+ private handleQueuedWorkEnded;
1429
+ /** P46b §3.6 — start the per-session task lease heartbeat (idempotent). */
1430
+ private ensureTaskLeaseHeartbeat;
1431
+ /** P46b §3.6 — stop the task lease heartbeat (terminal: shutdown or no task left). */
1432
+ private stopTaskLeaseHeartbeat;
1433
+ /**
1434
+ * P46b §3.6 — one heartbeat for every single task this desktop holds in the
1435
+ * implement–review loop (queued, starting, implementing, in review, awaiting
1436
+ * you): `recordExecutionEvent(PROGRESS_EVENT, {kind: TASK_LEASE_HEARTBEAT,
1437
+ * taskId})`, which RENEWs the task's slot lease. A START still in flight is
1438
+ * skipped (its holder may not exist yet); a beat still in flight is not
1439
+ * doubled. Work this desktop no longer knows stops renewing and lapses.
1440
+ */
1441
+ sendTaskLeaseHeartbeats(): Promise<void>;
1325
1442
  /** P46a D1 — `#n` / `#n retry` / short id for any task id (never throws). */
1326
1443
  labelFor(taskId: string): string;
1327
1444
  /**
@@ -1335,9 +1452,15 @@ export declare class QuorumLoop {
1335
1452
  agent: AgentKind;
1336
1453
  brief: string;
1337
1454
  retryOf?: string;
1338
- outcome: 'running' | 'applied' | 'discarded' | 'apply_failed' | 'unresolved' | 'rejected';
1455
+ outcome: SingleTaskOutcome;
1339
1456
  implementorActive: boolean;
1340
1457
  focused: boolean;
1458
+ /** P46b §3.7 — START confirmed or dispatched, implementor not spawned yet. */
1459
+ starting?: boolean;
1460
+ /** P46b §3.7 — queue position from the START answer (queued tasks). */
1461
+ queuePosition?: number;
1462
+ /** P46b §3.6 — why a queued task ended before it started. */
1463
+ cancelReason?: string;
1341
1464
  }>;
1342
1465
  /** Stage 1 r1 F13 — a START confirmed or rejected: the newest START still in flight (if any) becomes the empty-envelope target. */
1343
1466
  private dropInFlightStart;
@@ -1441,6 +1564,7 @@ export declare class QuorumLoop {
1441
1564
  gateDispatch: (payload: GateDispatchPayload, envelopeTaskId?: string) => Promise<void>;
1442
1565
  spawnReviewers: (payload: ReviewerDispatchPayload) => Promise<void>;
1443
1566
  runReviseRound: (payload: ReviseFeedbackPayload) => Promise<void>;
1567
+ queuedWorkEnded: (payload: QueuedWorkEndedPayload) => Promise<void>;
1444
1568
  };
1445
1569
  private cachedSigningKey;
1446
1570
  private verifySignature;
@@ -1490,9 +1614,7 @@ export declare class QuorumLoop {
1490
1614
  * read this instead. Omitted for an ordinary task (its brief IS the request).
1491
1615
  */
1492
1616
  request?: string;
1493
- }): Promise<{
1494
- taskId: string;
1495
- } | null>;
1617
+ }): Promise<StartTaskResultInfo | null>;
1496
1618
  /**
1497
1619
  * CP-1.f Wave 3 FIX — consume a GATE_DISPATCH packet. The engine emits this
1498
1620
  * after `startTask` creates the round-0 `reviewer_quorum` gate; its
@@ -2366,6 +2488,15 @@ export declare class QuorumLoop {
2366
2488
  * here; single-impl / non-team packets bypass this entirely.
2367
2489
  */
2368
2490
  bufferTeamPacketDuringSeeding(kind: 'trackAssigned' | 'gateDispatch', payload: unknown, envelopeTaskId?: string): boolean;
2491
+ /**
2492
+ * P46b (E2E T-3) — whether a live team packet belongs to a track a recovery is
2493
+ * relaunching right now. A packet that names no track this can match (no
2494
+ * envelope task id and no TrackAssigned ids) counts as one, so it keeps
2495
+ * buffering exactly as before.
2496
+ */
2497
+ private isRecoveryRelaunchPacket;
2498
+ private enterRecoveryRelaunchPhase;
2499
+ private leaveRecoveryRelaunchPhase;
2369
2500
  /**
2370
2501
  * §4.3 — drain the buffered live TEAM packets through the normal live handlers
2371
2502
  * AFTER the recovery seed barrier lifts (`recoverySeedingDepth === 0`). Each
@@ -0,0 +1,29 @@
1
+ /** The never-started halt reasons of `QUEUED_WORK_ENDED` (engine `halted_reason`). */
2
+ export type QueuedTaskEndReason = 'user_cancelled_queued_task' | 'tier_changed' | 'queued_task_expired';
3
+ /** `#4` → `4`, `#4 retry` → `4`, `#5.2` → `5.2`; anything else unchanged. */
4
+ export declare function labelNumber(label: string): string;
5
+ /** Desktop answer to a queued START (§3.7). */
6
+ export declare function queuedDesktopLine(label: string, info: {
7
+ runningTracks?: number;
8
+ trackBudget?: number;
9
+ }): string;
10
+ /** Phone line for a queued START (§3.13 item 1). */
11
+ export declare function queuedPhoneLine(label: string, info: {
12
+ runningTracks?: number;
13
+ trackBudget?: number;
14
+ }): string;
15
+ /** Phone line for every single-task start, immediate or from the queue. */
16
+ export declare function startedPhoneLine(label: string, agent: string): string;
17
+ /** Desktop line when a queued task starts because a slot freed. */
18
+ export declare function startedFromQueueDesktopLine(label: string, agent: string): string;
19
+ /** Phone line when a team starts (§3.13 item 1). */
20
+ export declare function teamStartedPhoneLine(label: string, running: number, waiting: number): string;
21
+ /** Phone line when a team track that waited starts later (`#5.4`). */
22
+ export declare function trackStartedPhoneLine(trackLabel: string): string;
23
+ /**
24
+ * The line (desktop and phone) for a task that never started and was halted.
25
+ * An unknown reason renders the neutral form.
26
+ */
27
+ export declare function cancelledLine(label: string, reason: string): string;
28
+ /** `/status` phase for a cancelled task: `cancelled (reason)`. */
29
+ export declare function cancelledStatusLabel(reason?: string): string;
@@ -87,6 +87,7 @@ export type TaskProgressEvent = ({
87
87
  message: string;
88
88
  }) & {
89
89
  epoch?: number;
90
+ progressTaskId?: string;
90
91
  tokens?: number;
91
92
  };
92
93
  /** The callback the QuorumLoop invokes on each progress phase. */
@@ -177,7 +177,8 @@ export interface SessionTaskRecord {
177
177
  /** 'single' = TASK_LIFECYCLE-tracked; 'team' = Agent Teams track. */
178
178
  origin: 'single' | 'team';
179
179
  /**
180
- * Singles: the lifecycle status — 'running' at dispatch, flipped terminal
180
+ * Singles: the lifecycle status — 'queued' while a START waits for a
181
+ * running-track slot (P46b), 'running' at dispatch, flipped terminal
181
182
  * by the task-end CLOSER progress events (promoted → 'completed',
182
183
  * discarded → 'cancelled', round_failed → 'failed'; Stage-2 r1 Codex
183
184
  * MED-2 — no production TASK_LIFECYCLE dispatch ever carries a terminal
@@ -589,6 +590,24 @@ export interface PendingApplyConflict {
589
590
  }>;
590
591
  /** True when this menu follows the one automatic retry's own promote failure. */
591
592
  readonly autoRetried: boolean;
593
+ /**
594
+ * P46b §3.10 — `group` when the failed promote was a team's (`taskId` is then
595
+ * the `taskGroupId`; retry reissues the team from its retained launch spec).
596
+ * Absent = a single task (P46a).
597
+ */
598
+ readonly kind?: 'single' | 'group';
599
+ /** P46b §3.9 — counts only: how many paths each cause failed. */
600
+ readonly counts?: {
601
+ readonly sessionTask: number;
602
+ readonly external: number;
603
+ readonly writeErrors: number;
604
+ };
605
+ /**
606
+ * P46b §3.9 (ID-20) — the engine retry prompt's gate id once
607
+ * `openApplyRetryOffer` succeeded: the phone can answer this menu, and it is
608
+ * resolved with the user's choice. Absent = the desktop-local menu only.
609
+ */
610
+ readonly offerGateId?: string;
592
611
  /** The `advisory` conversation entry that rendered the numbered options. */
593
612
  readonly conversationEntryId: string;
594
613
  /** The composed retry brief (original request + files changed since + reviewed contents). */
@@ -709,6 +728,11 @@ export interface LiveProgress {
709
728
  updatedAt: string;
710
729
  /** ISO — when THIS busy window opened; the wall-clock timer anchor. */
711
730
  startedAt: string;
731
+ /**
732
+ * P46b §3.7 — the task this line belongs to (from `emitProgressForTask`);
733
+ * undefined for a classify/familiarize/advisory window.
734
+ */
735
+ taskId?: string;
712
736
  /**
713
737
  * The per-TASK origin epoch of the event that opened/last-refreshed this window
714
738
  * (§2/§3). Undefined ONLY for the direct-dispatched classify/familiarize windows
@@ -810,6 +834,13 @@ export interface OrchestrationState {
810
834
  * Companion sessions never dispatch `TASK_PROGRESS`, so this stays null.
811
835
  */
812
836
  progress: LiveProgress | null;
837
+ /**
838
+ * `[ADDITIVE WIDENING — P46b §3.7]` One live line per single task that is
839
+ * busy right now, keyed by task id, under the same epoch/closer rules as
840
+ * `progress` applied per task. Team tracks are excluded (their rows live in
841
+ * `MultiTrackStatusNode`). Optional so pre-P46b state literals stay valid.
842
+ */
843
+ progressByTask?: Map<string, LiveProgress>;
813
844
  /**
814
845
  * `[ADDITIVE WIDENING — /reviewer-setup wizard, 2026-06-29]` The active
815
846
  * interactive reviewer-panel setup modal, or `null` when no wizard is open
@@ -1010,6 +1041,22 @@ export type OrchestrationAction = {
1010
1041
  } | {
1011
1042
  type: 'CLEAR_PENDING_APPLY_CONFLICT';
1012
1043
  taskId?: string;
1044
+ } | {
1045
+ type: 'APPLY_CONFLICT_OFFER_OPENED';
1046
+ taskId: string;
1047
+ gateId: string;
1048
+ } | {
1049
+ type: 'SINGLE_TASK_QUEUED';
1050
+ taskId: string;
1051
+ agentKind: string;
1052
+ queuedAt: string;
1053
+ } | {
1054
+ type: 'SINGLE_TASK_QUEUE_ENDED';
1055
+ taskId: string;
1056
+ reason: string;
1057
+ } | {
1058
+ type: 'GATE_PROMPT_ACTIVATED';
1059
+ promptEntryId: string;
1013
1060
  } | {
1014
1061
  type: 'STRUCTURAL_SUMMARY_GENERATED';
1015
1062
  summary: StructuralSummary;
@@ -1,5 +1,6 @@
1
1
  import type { CommandResult, CommandRunner, RunnerProc } from './types';
2
2
  import type { WorkspaceRootAuthority } from '../local-executor/workspace-authority';
3
+ import type { HostDescendantReap } from '../local-executor/descendant-reaper';
3
4
  /**
4
5
  * The production `CommandRunner` — runs `docker` / `sandbox-exec` via
5
6
  * `node:child_process`. No shell is used (argv is passed directly to
@@ -18,5 +19,6 @@ export declare class ChildProcessCommandRunner implements CommandRunner {
18
19
  cwd?: string;
19
20
  cwdAuthority?: WorkspaceRootAuthority;
20
21
  launchPathAuthorities?: readonly WorkspaceRootAuthority[];
22
+ descendantReap?: HostDescendantReap;
21
23
  }): RunnerProc;
22
24
  }
@@ -1,5 +1,6 @@
1
1
  import type { Readable, Writable } from "node:stream";
2
2
  import type { WorkspaceRootAuthority } from "../local-executor/workspace-authority";
3
+ import type { HostDescendantReap } from "../local-executor/descendant-reaper";
3
4
  /**
4
5
  * Isolation backend for a substrate. `firecracker` is [DEF →
5
6
  * A3-after-C2] — declared so the union admits the hosted runner without
@@ -173,6 +174,8 @@ export interface SubstrateHandle {
173
174
  stdinTty: boolean;
174
175
  timeoutMs?: number;
175
176
  signal?: AbortSignal;
177
+ /** P46b ID-31 — the descendant sweep the process host runs if the desktop dies first. */
178
+ descendantReap?: HostDescendantReap;
176
179
  }): Promise<SubstrateProc>;
177
180
  /** Idempotent; MUST destroy the sandbox + its fs view. */
178
181
  teardown(): Promise<void>;
@@ -276,5 +279,7 @@ export interface CommandRunner {
276
279
  * they are not its cwd (for example Claude's private scratch root).
277
280
  */
278
281
  launchPathAuthorities?: readonly WorkspaceRootAuthority[];
282
+ /** P46b ID-31 — the descendant sweep the process host runs if the desktop dies first. */
283
+ descendantReap?: HostDescendantReap;
279
284
  }): RunnerProc;
280
285
  }
@@ -41,6 +41,8 @@ export interface EngageSubstrateInput {
41
41
  * legacy behavior for every existing caller.
42
42
  */
43
43
  requireConfined?: boolean;
44
+ /** F-P46a-4 — the implementor spawn's process-tree id (descendant reaping marker). */
45
+ processTreeId?: string;
44
46
  /** Ladder deps (injectable for tests). */
45
47
  ladderDeps?: FallbackLadderDeps;
46
48
  /** Broker upstream client override (tests inject a stub; default real fetch). */
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@quantiya/codevibe-core",
3
- "version": "2.0.39",
3
+ "version": "2.0.40",
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",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@quantiya/codevibe-claude-plugin",
3
- "version": "2.0.44",
3
+ "version": "2.0.45",
4
4
  "description": "Control Claude Code from your iPhone and Android — real-time sync, approve file edits, send prompts by voice. Part of CodeVibe.",
5
5
  "main": "dist/server.js",
6
6
  "codevibe": {
@@ -48,7 +48,7 @@
48
48
  "node": ">=22.0.0"
49
49
  },
50
50
  "dependencies": {
51
- "@quantiya/codevibe-core": "^2.0.39",
51
+ "@quantiya/codevibe-core": "^2.0.40",
52
52
  "@quantiya/quorum-core": "^1.0.1",
53
53
  "dotenv": "^16.6.1",
54
54
  "express": "^5.1.0",