@quantiya/codevibe-codex-plugin 2.0.9 → 2.0.11

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 (53) hide show
  1. package/README.md +24 -3
  2. package/dist/server.js +32 -31
  3. package/hooks/common.sh +10 -13
  4. package/libexec/companion-launcher +21 -17
  5. package/libexec/pane-mirror-writer.js +127 -0
  6. package/node_modules/@quantiya/codevibe-core/dist/appsync/__tests__/appsync-client-flag-locator.test.d.ts +1 -0
  7. package/node_modules/@quantiya/codevibe-core/dist/appsync/__tests__/dispose-watcher-socket.test.d.ts +1 -0
  8. package/node_modules/@quantiya/codevibe-core/dist/appsync/appsync-client.d.ts +62 -0
  9. package/node_modules/@quantiya/codevibe-core/dist/auth/__tests__/keychain-cli-nudge.test.d.ts +1 -0
  10. package/node_modules/@quantiya/codevibe-core/dist/auth/keychain-cli.d.ts +8 -0
  11. package/node_modules/@quantiya/codevibe-core/dist/index.js +523 -351
  12. package/node_modules/@quantiya/codevibe-core/dist/keychain/__tests__/migrate-nudge.test.d.ts +1 -0
  13. package/node_modules/@quantiya/codevibe-core/dist/keychain/keychain-migrate.d.ts +17 -0
  14. package/node_modules/@quantiya/codevibe-core/dist/keychain/migrate-nudge.d.ts +38 -0
  15. package/node_modules/@quantiya/codevibe-core/dist/local-executor/__tests__/protocol-backlog-wiring.test.d.ts +1 -0
  16. package/node_modules/@quantiya/codevibe-core/dist/local-executor/__tests__/protocol-backlog.test.d.ts +1 -0
  17. package/node_modules/@quantiya/codevibe-core/dist/local-executor/__tests__/workspace-cleanup-audit.test.d.ts +1 -0
  18. package/node_modules/@quantiya/codevibe-core/dist/local-executor/anchored-fs.d.ts +7 -0
  19. package/node_modules/@quantiya/codevibe-core/dist/local-executor/bounded-directory.d.ts +14 -0
  20. package/node_modules/@quantiya/codevibe-core/dist/local-executor/process-identity.d.ts +30 -0
  21. package/node_modules/@quantiya/codevibe-core/dist/local-executor/process-tree.d.ts +1 -1
  22. package/node_modules/@quantiya/codevibe-core/dist/local-executor/protocol-backlog.d.ts +62 -0
  23. package/node_modules/@quantiya/codevibe-core/dist/local-executor/workspace-cleanup-audit.d.ts +3 -0
  24. package/node_modules/@quantiya/codevibe-core/dist/local-executor/workspace-materializer.d.ts +23 -2
  25. package/node_modules/@quantiya/codevibe-core/dist/local-executor/workspace-shadow.d.ts +2 -0
  26. package/node_modules/@quantiya/codevibe-core/dist/orchestration-shell/__tests__/direct-file-delete.test.d.ts +1 -0
  27. package/node_modules/@quantiya/codevibe-core/dist/orchestration-shell/__tests__/flag-command.test.d.ts +1 -0
  28. package/node_modules/@quantiya/codevibe-core/dist/orchestration-shell/__tests__/halt-message-wire-content.test.d.ts +1 -0
  29. package/node_modules/@quantiya/codevibe-core/dist/orchestration-shell/audit-browser.d.ts +54 -1
  30. package/node_modules/@quantiya/codevibe-core/dist/orchestration-shell/cli.d.ts +10 -0
  31. package/node_modules/@quantiya/codevibe-core/dist/orchestration-shell/cli.js +2711 -1474
  32. package/node_modules/@quantiya/codevibe-core/dist/orchestration-shell/context-items.d.ts +3 -0
  33. package/node_modules/@quantiya/codevibe-core/dist/orchestration-shell/direct-file-delete.d.ts +22 -0
  34. package/node_modules/@quantiya/codevibe-core/dist/orchestration-shell/flag-command.d.ts +81 -0
  35. package/node_modules/@quantiya/codevibe-core/dist/orchestration-shell/gate-prompts.d.ts +41 -0
  36. package/node_modules/@quantiya/codevibe-core/dist/orchestration-shell/index.d.ts +31 -6
  37. package/node_modules/@quantiya/codevibe-core/dist/orchestration-shell/quorum-loop.d.ts +42 -1
  38. package/node_modules/@quantiya/codevibe-core/dist/orchestration-shell/task-progress.d.ts +3 -0
  39. package/node_modules/@quantiya/codevibe-core/dist/orchestration-shell/types.d.ts +58 -0
  40. package/node_modules/@quantiya/codevibe-core/dist/orchestration-shell/workspace-shutdown.d.ts +20 -0
  41. package/node_modules/@quantiya/codevibe-core/dist/reviewer/providers/codex.d.ts +5 -3
  42. package/node_modules/@quantiya/codevibe-core/dist/reviewer/token-usage.d.ts +41 -12
  43. package/node_modules/@quantiya/codevibe-core/dist/reviewer/types.d.ts +3 -0
  44. package/node_modules/@quantiya/codevibe-core/dist/substrate/__tests__/command-runner.test.d.ts +1 -0
  45. package/node_modules/@quantiya/codevibe-core/dist/substrate/types.d.ts +8 -0
  46. package/node_modules/@quantiya/codevibe-core/dist/track-presentation.d.ts +2 -0
  47. package/node_modules/@quantiya/codevibe-core/dist/track-presentation.test.d.ts +1 -0
  48. package/node_modules/@quantiya/codevibe-core/package.json +1 -1
  49. package/node_modules/fs-ext/build/Makefile +2 -2
  50. package/node_modules/fs-ext/build/Release/fs_ext.node +0 -0
  51. package/node_modules/fs-ext/build/Release/obj.target/fs_ext/fs-ext.o +0 -0
  52. package/node_modules/fs-ext/build/config.gypi +1 -1
  53. package/package.json +2 -2
@@ -81,6 +81,8 @@ export interface ContextItem {
81
81
  prev_hash: string | null;
82
82
  /** Typed payload per kind. */
83
83
  body: unknown;
84
+ /** Non-rendered counters/locator metadata; excluded from semantic content ids. */
85
+ telemetry?: unknown;
84
86
  /** ISO timestamp the item was authored (corpus-ready, §4.1). */
85
87
  authored_at: string;
86
88
  }
@@ -91,6 +93,7 @@ export interface ContextItemDraft {
91
93
  sensitivity: ContextItemSensitivity;
92
94
  visible_to: ContextItemProjectionRole[];
93
95
  body: unknown;
96
+ telemetry?: unknown;
94
97
  task_id?: string;
95
98
  track_id?: string;
96
99
  refs?: string[];
@@ -0,0 +1,22 @@
1
+ export interface DirectFileDeleteRequest {
2
+ paths: string[];
3
+ }
4
+ export interface DirectFileDeleteResult {
5
+ deleted: string[];
6
+ }
7
+ /**
8
+ * Recognize only a closed, deterministic delete request. Anything ambiguous
9
+ * remains on the ordinary planner/implementor path.
10
+ *
11
+ * Supported examples:
12
+ * Delete only a.txt.
13
+ * Please delete only `a file.txt` and `b.txt`.
14
+ * Delete only a.txt. Do not create, modify, or delete any other file.
15
+ */
16
+ export declare function parseDirectFileDeleteRequest(text: string): DirectFileDeleteRequest | null;
17
+ /**
18
+ * Delete only the explicitly named regular files. All targets are validated
19
+ * before the first mutation; each unlink is bound to the captured parent and
20
+ * file incarnation and directory-fsynced by the anchored helper.
21
+ */
22
+ export declare function executeDirectFileDelete(cwd: string, request: DirectFileDeleteRequest): Promise<DirectFileDeleteResult>;
@@ -0,0 +1,81 @@
1
+ import type { AppSyncClient } from '../appsync';
2
+ import { type AuditBrowserModel } from './audit-browser';
3
+ import type { AuditFlagIndex } from './types';
4
+ /**
5
+ * Build the session-scoped flag index from a just-rendered `/audit` browser
6
+ * model. Called at the `/audit <task-id>` dispatch site on a successful
7
+ * render; the entrypoint retains the result via `AUDIT_FLAG_INDEX_RETAINED`
8
+ * (last render wins — the reducer carries the `flagged` set forward).
9
+ * Shape-only: coordinates + wire kind + the precomputed flaggable bit; the
10
+ * decrypted payload never leaves the render.
11
+ */
12
+ export declare function buildAuditFlagIndex(taskId: string, model: AuditBrowserModel): AuditFlagIndex;
13
+ /**
14
+ * The minimal AppSync surface `/flag` needs — the existing
15
+ * `recordExecutionEvent` Class-A method (D4: the full plumbing below it
16
+ * already exists). Narrowed so tests inject a stub without the full client.
17
+ */
18
+ export type FlagAppSyncClient = Pick<AppSyncClient, 'recordExecutionEvent'>;
19
+ export interface RunFlagCommandDeps {
20
+ /** The authenticated AppSync client. */
21
+ appsyncClient: FlagAppSyncClient;
22
+ /** The retained last-rendered `/audit` index (null → never rendered). */
23
+ index: AuditFlagIndex | null;
24
+ /** The `/flag <entry-id>` argument. */
25
+ entryId: string;
26
+ /** Optional free-text note (encrypted into the flag payload; never logged). */
27
+ note?: string;
28
+ /** The shell session (locator/emit scoping + session-key lookup). */
29
+ sessionId: string;
30
+ /** Test seam — defaults to `keychainManager.getSessionKey(sessionId)`. */
31
+ getSessionKeyFn?: (sessionId: string) => Promise<string | null>;
32
+ }
33
+ /**
34
+ * The outcome of a `/flag` run. Every arm carries a ready-to-render `line`;
35
+ * `runFlagCommand` NEVER throws (mirrors `runAuditBrowser`).
36
+ */
37
+ export type RunFlagCommandResult = {
38
+ kind: 'no_index';
39
+ line: string;
40
+ } | {
41
+ kind: 'unknown_entry';
42
+ line: string;
43
+ } | {
44
+ kind: 'not_flaggable';
45
+ line: string;
46
+ } | {
47
+ kind: 'missing_seq';
48
+ line: string;
49
+ } | {
50
+ kind: 'already_flagged';
51
+ line: string;
52
+ } | {
53
+ kind: 'no_session_key';
54
+ line: string;
55
+ } | {
56
+ kind: 'flagged';
57
+ entryId: string;
58
+ line: string;
59
+ } | {
60
+ kind: 'server_rejected';
61
+ line: string;
62
+ reason: string;
63
+ } | {
64
+ kind: 'error';
65
+ line: string;
66
+ reason: string;
67
+ };
68
+ /** `/flag` before any `/audit <task-id>` render — nothing to resolve against. */
69
+ export declare const FLAG_NO_INDEX_LINE: string;
70
+ /**
71
+ * Run `/flag <entry-id>`: retained-index resolve → client approve-subset
72
+ * check → locator + dedup-key construction → Class-A `FLAG_BAD_APPROVAL`
73
+ * emit with locator forwarding. Returns a discriminated result the shell
74
+ * renders; the CALLER dispatches `AUDIT_FLAG_RECORDED` on the `flagged` arm
75
+ * (the runner stays store-free, like `runAuditBrowser`).
76
+ */
77
+ export declare function runFlagCommand(deps: RunFlagCommandDeps): Promise<RunFlagCommandResult>;
78
+ /**
79
+ * Render a `RunFlagCommandResult` as the inline `/flag` shell output line.
80
+ */
81
+ export declare function renderFlagResultText(result: RunFlagCommandResult): string;
@@ -461,11 +461,32 @@ export interface ReviewSummaryTerminalModel {
461
461
  };
462
462
  readonly tokenUsage: {
463
463
  readonly knownTotal: number | null;
464
+ readonly knownInput: number | null;
465
+ readonly knownOutput: number | null;
466
+ readonly knownCachedInput: number | null;
464
467
  readonly availability: string;
465
468
  readonly unavailableRoles: ReadonlyArray<string>;
469
+ readonly invocations: ReadonlyArray<ReviewSummaryTokenInvocationModel>;
466
470
  };
467
471
  readonly residualRisks: ReadonlyArray<string>;
468
472
  }
473
+ export interface ReviewSummaryTokenInvocationModel {
474
+ readonly role: string;
475
+ readonly agent: string;
476
+ readonly taskId: string | null;
477
+ readonly trackIndex: number | null;
478
+ readonly round: number | null;
479
+ readonly gateId: string | null;
480
+ readonly seatId: number | null;
481
+ readonly reviewerRole: string | null;
482
+ readonly inputTokens: number | null;
483
+ readonly outputTokens: number | null;
484
+ readonly cachedInputTokens: number | null;
485
+ readonly totalTokens: number | null;
486
+ readonly elapsedMs: number | null;
487
+ readonly availability: string;
488
+ readonly unavailableReason: string | null;
489
+ }
469
490
  /**
470
491
  * The fully-extracted, render-ready per-round review summary model. PURE
471
492
  * function of the decrypted summary. `null`-attached (entry has no
@@ -518,8 +539,28 @@ export interface DecryptedReviewSummaryInput {
518
539
  };
519
540
  readonly tokenUsage?: {
520
541
  readonly knownTotal?: number;
542
+ readonly knownInput?: number;
543
+ readonly knownOutput?: number;
544
+ readonly knownCachedInput?: number;
521
545
  readonly availability?: string;
522
546
  readonly unavailableRoles?: ReadonlyArray<string>;
547
+ readonly invocations?: ReadonlyArray<{
548
+ readonly role?: string;
549
+ readonly agent?: string;
550
+ readonly taskId?: string;
551
+ readonly trackIndex?: number;
552
+ readonly round?: number;
553
+ readonly gateId?: string;
554
+ readonly seatId?: number;
555
+ readonly reviewerRole?: string;
556
+ readonly inputTokens?: number;
557
+ readonly outputTokens?: number;
558
+ readonly cachedInputTokens?: number;
559
+ readonly totalTokens?: number;
560
+ readonly elapsedMs?: number;
561
+ readonly availability?: string;
562
+ readonly unavailableReason?: string;
563
+ }>;
523
564
  };
524
565
  readonly residualRisks?: ReadonlyArray<string>;
525
566
  readonly rounds?: ReadonlyArray<{
@@ -4,13 +4,14 @@ import { Session } from '../types';
4
4
  import { OrchestrationStore } from './store';
5
5
  import { AdvisoryAttachmentJournal } from './advisory-attachment-journal';
6
6
  import type { ImageAttachment } from './types';
7
- import { type GroupDecisionSubmitDeps } from './gate-decision-submit';
7
+ import { type GateDecisionSubmitDeps, type GroupDecisionSubmitDeps } from './gate-decision-submit';
8
8
  import { type ContinuationCliResult, type ContinuationCliDeps } from './slash-routes/continuation';
9
9
  import { loadReviewerWizardData } from './slash-routes/reviewers';
10
10
  import { createShellEventEmitter } from './emit-shell-event';
11
11
  import { type AgentMentionIntent, type CommandIntentTarget } from './command-intent';
12
12
  import { type BrainstormPanelActiveAgent } from './brainstorm-quorum';
13
13
  import { type RunAuditBrowserResult } from './audit-runner';
14
+ import { type RunFlagCommandResult } from './flag-command';
14
15
  import { Tier } from './types';
15
16
  import type { ConversationEntry, PendingClarification } from './types';
16
17
  import { type GatePromptEnvelope, type DecryptedReviewSummaryInput } from './gate-prompts';
@@ -23,6 +24,7 @@ import type { LocalGemmaAdvisoryRunner } from '../planner/local-advisory';
23
24
  import type { LocalExecutor } from '../local-executor';
24
25
  import type { WorkItemInput, DisjointnessRejection, RevertManifestStore } from '../local-executor';
25
26
  import type { QuorumLoop } from './quorum-loop';
27
+ import { type InvocationUsageSnapshot } from '../reviewer/token-usage';
26
28
  import { ensureFreshContextStore } from './context-store';
27
29
  import type { EnsureFreshResult } from './context-store';
28
30
  import { contextItemOccurrence, type ContextDurabilityReceipt } from './context-items';
@@ -231,6 +233,7 @@ export { compactSessionContext, maybeFireCompactionSafetyValve, rehydrateSession
231
233
  export { routeSlashCommand, applySlashOutput, type SlashOutput, type ShellAction, } from './slash-router';
232
234
  export { buildAuditBrowserModel, routeAuditEntry, auditKindLabel, auditKindCopy, renderAuditBrowserText, renderAuditEntryText, ENTRY_DETAILS_UNAVAILABLE, NO_AUDIT_ENTRIES, AUDIT_LOG_TOO_LARGE, AUDIT_LOG_TITLE, AUDIT_BROWSER_MAX_HEADLINE, WIRE_PUBLIC_PLAINTEXT_KINDS, type AuditBrowserModel, type AuditEntryModel, type AuditReviewerVerdictModel, type AuditTaskAuthorizedModel, type DecryptContentFn, } from './audit-browser';
233
235
  export { runAuditBrowser, renderAuditResultText, AUDIT_BROWSER_UPGRADE_HINT, type RunAuditBrowserResult, type RunAuditBrowserDeps, } from './audit-runner';
236
+ export { buildAuditFlagIndex, runFlagCommand, renderFlagResultText, FLAG_NO_INDEX_LINE, type RunFlagCommandResult, type RunFlagCommandDeps, type FlagAppSyncClient, } from './flag-command';
234
237
  export { AuditLogPanel } from './components/AuditLogPanel';
235
238
  export { reducer } from './reducer';
236
239
  export { createOrchestrationStore, type OrchestrationStore } from './store';
@@ -385,7 +388,18 @@ export declare function handleSubscribedEvent(event: Event, store: Orchestration
385
388
  * and DROPS (never crashes the shell, never submits ciphertext). Async — the sync
386
389
  * subscription callback fires-and-forgets via `void`; every throw is caught here.
387
390
  */
388
- export declare function routeMobileUserPrompt(event: Event, sessionKeyResolver: ((sessionId: string) => Promise<string | null>) | undefined, submit: (text: string, images?: string[], options?: ShellSubmitOptions) => Promise<void>, updateEventStatus?: (input: UpdateEventStatusInput) => Promise<unknown>): Promise<void>;
391
+ export declare function routeMobileUserPrompt(event: Event, sessionKeyResolver: ((sessionId: string) => Promise<string | null>) | undefined, submit: (text: string, images?: string[], options?: ShellSubmitOptions) => Promise<void>, updateEventStatus?: (input: UpdateEventStatusInput) => Promise<unknown>): Promise<boolean>;
392
+ /**
393
+ * Route a mobile composer reply through the desktop's canonical gate state
394
+ * machine. Mobile always transports the text as an encrypted `USER_PROMPT`;
395
+ * only the desktop that owns the live prompt interprets it as a numbered
396
+ * option, a required note, `/cancel`, or ordinary free text.
397
+ *
398
+ * Returns `true` only when the active gate consumed the input. A non-numeric
399
+ * line against an awaiting-number gate remains an ordinary prompt and returns
400
+ * `false`, matching the desktop TTY behavior.
401
+ */
402
+ export declare function routeMobileGatePromptInput(deps: GateDecisionSubmitDeps, text: string): Promise<boolean>;
389
403
  /**
390
404
  * Build the single `(event) => void` callback the AppSync events subscription
391
405
  * invokes. Routes each delivered event to exactly ONE handler:
@@ -790,6 +804,14 @@ export interface HandleShellUserInputDeps extends StructuralSummaryRunDeps {
790
804
  * rewrite without live fetch / decrypt.
791
805
  */
792
806
  runAuditBrowserFn?: (taskId: string) => Promise<RunAuditBrowserResult>;
807
+ /**
808
+ * O1 C1 (#652) — Test seam for the async `/flag <entry-id>` dispatch.
809
+ * Defaults to `runFlagCommand` over `args.appsyncClient` + the retained
810
+ * `store` flag index + `args.session.sessionId`. Tests inject a stub to
811
+ * drive the PENDING-marker rewrite (+ the `AUDIT_FLAG_RECORDED` dispatch
812
+ * on the `flagged` arm) without live emit / keychain I/O.
813
+ */
814
+ runFlagCommandFn?: (entryId: string, note?: string) => Promise<RunFlagCommandResult>;
793
815
  /**
794
816
  * CP-4 §4.2 — Test seam for the pre-classify context-store refresh. Defaults
795
817
  * to the real `ensureFreshContextStore` (probe → recompute-if-stale → persist
@@ -843,10 +865,12 @@ export interface HandleShellUserInputDeps extends StructuralSummaryRunDeps {
843
865
  * could overlap and interleave the clarification state machine
844
866
  * (USER_INPUT bookkeeping + classify + PLANNER_DECISION), corrupting it.
845
867
  *
846
- * Slash commands (`text.startsWith('/')`, matching `handleShellUserInput`'s own
847
- * routing) bypass the queue: they're fast, never trigger a planner classify,
848
- * never touch clarification state, and include `/quit` — so they must stay
849
- * responsive instead of waiting behind a slow/hung planner turn.
868
+ * Slash commands (`text.trimStart().startsWith('/')`, matching
869
+ * `handleShellUserInput`'s own routing) bypass the queue: they're fast, never
870
+ * trigger a planner classify, never touch clarification state, and include
871
+ * `/quit` — so they must stay responsive instead of waiting behind a slow/hung
872
+ * planner turn. Leading paste whitespace is presentation noise, not permission
873
+ * to reinterpret an explicit command as natural language.
850
874
  *
851
875
  * The returned function preserves the caller-visible contract: it returns the
852
876
  * promise for THIS submission's processing (so an awaiting caller still gets the
@@ -936,6 +960,7 @@ export interface BrainstormTurnOwnership {
936
960
  authorAgent: BrainstormPanelActiveAgent;
937
961
  status: 'completed' | 'quota_walled' | 'failed';
938
962
  boundedBody: string;
963
+ usage: InvocationUsageSnapshot;
939
964
  }>;
940
965
  prompt: {
941
966
  kind: 'legacy_joined';
@@ -963,6 +963,14 @@ export declare class QuorumLoop {
963
963
  * This also makes concurrent B.1+B.2 safe (no per-op toggle race).
964
964
  */
965
965
  private recoverySeedingDepth;
966
+ /**
967
+ * Recovery B.1 and B.2 can overlap. A per-call two-row batch is therefore
968
+ * insufficient: two concurrent calls could each launch two implementors.
969
+ * These permits are owned by the QuorumLoop/session and cover the complete
970
+ * `launchRecoveredTrack` operation across every recovery entry path.
971
+ */
972
+ private activeRecoveryRelaunches;
973
+ private readonly recoveryRelaunchWaiters;
966
974
  /**
967
975
  * Every asynchronously delivered session coroutine that can reach a task
968
976
  * workspace: Class-B consumption, shadow recovery, and B.1/B.2 team
@@ -978,6 +986,10 @@ export declare class QuorumLoop {
978
986
  private startSessionWork;
979
987
  private admittedSessionWorkMayContinue;
980
988
  private trackTeamRecovery;
989
+ private acquireRecoveryRelaunchPermit;
990
+ private releaseRecoveryRelaunchPermit;
991
+ private cancelRecoveryRelaunchWaiters;
992
+ private withRecoveryRelaunchPermit;
981
993
  /**
982
994
  * §4.3 — TEAM Class B packets buffered while the recovery seed barrier is up
983
995
  * (`recoverySeedingDepth > 0`). Drained through the normal live handlers AFTER
@@ -1447,7 +1459,7 @@ export declare class QuorumLoop {
1447
1459
  * Gemini/Antigravity are out of scope (undefined). NEVER throws — a read/parse
1448
1460
  * failure yields `undefined` (segment simply omitted), never breaks the round.
1449
1461
  */
1450
- private extractImplementorTokens;
1462
+ private extractImplementorUsage;
1451
1463
  /**
1452
1464
  * PHASE-589/469 W1+W2 (LOCK #589-E/F/H) — on a TEAM track local hard-fail (any
1453
1465
  * of the 4 closed reasons: `verification_failure` / `out_of_scope_write` /
@@ -1529,6 +1541,35 @@ export declare class QuorumLoop {
1529
1541
  * `TaskGroupHalted` convergence is the authoritative group resolution).
1530
1542
  */
1531
1543
  reportTeamTrackUserResolved(taskId: string, postAction: PostDecisionAction): void;
1544
+ /**
1545
+ * H3 / Option B (#657) — emit the POSITIVE cancel-confirmation milestone on the
1546
+ * final-approval CANCEL path. Final-approval "Do not apply. Leave the task in its
1547
+ * current state" RETAINS the shadow and mutates nothing, so it triggers NEITHER
1548
+ * the promote (`promoted`) NOR the discard (`discarded`) milestone — the decision
1549
+ * was previously SILENT in non-TTY (a canceling user got no confirmation, and the
1550
+ * E2E harness had no positive signal to assert, so an unwired/hung Cancel was
1551
+ * indistinguishable from a correct one). This emits `not_applied` ("Not applied —
1552
+ * your tree is unchanged") so the user AND the harness get a positive terminal
1553
+ * that the Cancel actually resolved.
1554
+ *
1555
+ * SELF-GUARDING single source of truth: fires ONLY for a `final_approval_resolved`
1556
+ * postAction whose `decision` is NOT `approve` (Approve promotes via the
1557
+ * TaskAuthorized edge → `promoted`; Abort/Reject-restart discard → `discarded`; an
1558
+ * `open_new_round`/`continuation_switch_*` postAction keeps working, no terminal).
1559
+ * Reuses the SAME `approve`-vs-else discriminator the engine's
1560
+ * `handle_final_approval` + `postActionTeamTrackState` use
1561
+ * (`decision.trim().toLowerCase()`), so it can never diverge from the
1562
+ * Passed/Failed track classification.
1563
+ *
1564
+ * Fires for BOTH single-impl and team tracks (unlike the team-only
1565
+ * `reportTeamTrackUserResolved`) — every Cancel deserves the confirmation. Called
1566
+ * by the shell's `onTerminalDecision` (the shared TTY + non-TTY gate-resolution
1567
+ * chokepoint) after a successful `applyUserDecision`. `emitProgress` already wraps
1568
+ * the listener so a throwing consumer can NEVER break the decision path. NO
1569
+ * wire/schema change (progress is desktop-LOCAL) — `not_applied` carries ONLY the
1570
+ * taskId (SECURITY INVARIANT: no user content, mirrors `discarded`).
1571
+ */
1572
+ reportFinalApprovalCancelled(taskId: string, postAction: PostDecisionAction): void;
1532
1573
  /**
1533
1574
  * Authoritative TaskGroupHalted bridge from LocalExecutor. Calling this async
1534
1575
  * method publishes the terminal group fence synchronously (before its first
@@ -66,6 +66,9 @@ export type TaskProgressEvent = ({
66
66
  } | {
67
67
  phase: 'discarded';
68
68
  taskId?: string;
69
+ } | {
70
+ phase: 'not_applied';
71
+ taskId?: string;
69
72
  } | {
70
73
  phase: 'waiting_user';
71
74
  } | {
@@ -379,6 +379,7 @@ export type ConversationEntry = {
379
379
  serverDecision: string | null;
380
380
  };
381
381
  reviewSummary?: ReviewSummaryPanelModel;
382
+ queuedReviewSummaries?: Readonly<Record<string, ReviewSummaryPanelModel>>;
382
383
  } | {
383
384
  kind: 'gate-panel';
384
385
  id: string;
@@ -667,6 +668,57 @@ export interface OrchestrationState {
667
668
  * keyboard focus. Non-team / Companion / CP-1.a sessions never open it.
668
669
  */
669
670
  reviewerWizard: ReviewerWizardState | null;
671
+ /**
672
+ * `[ADDITIVE WIDENING — O1 C1 (#652)]` per
673
+ * PHASE-O1-C1-SAFETY-OBSERVABILITY-DESIGN.md §3.1 item 1: the shell used to
674
+ * render and DISCARD the `/audit` browser model; `/flag <entry-id>` needs
675
+ * session-scoped retention of the LAST-rendered listing's
676
+ * `entryId → {seqNum, kind, flaggable}` mapping to build the plaintext
677
+ * target locator without re-querying. `null` until the first successful
678
+ * `/audit <task-id>` render. Desktop-local only; row COORDINATES + closed
679
+ * enum labels only — never decrypted payload content.
680
+ */
681
+ auditFlagIndex: AuditFlagIndex | null;
682
+ }
683
+ /**
684
+ * One retained `/audit` listing row, keyed by its audit `entry_id` in
685
+ * {@link AuditFlagIndex.entries}. Shape-only: row coordinates + the plaintext
686
+ * wire kind + the precomputed flag-eligibility bit (derived from the decrypted
687
+ * `gate_resolved` outcome at render time — the decrypted payload itself is
688
+ * NOT retained).
689
+ */
690
+ export interface AuditFlagEntryRecord {
691
+ /**
692
+ * The flagged row's audit-table sort key — the zero-padded 16-digit
693
+ * `seqNum` string exactly as surfaced by `queryAudit`'s additive
694
+ * `AuditEntryWireRow.seqNum` projection (core-rs `types.rs`), or `null`
695
+ * when the row predates the projection (stale backend) — `/flag` then
696
+ * fails closed with an actionable message.
697
+ */
698
+ seqNum: string | null;
699
+ /** Plaintext snake_case wire kind (e.g. `gate_resolved`). */
700
+ kind: string;
701
+ /**
702
+ * True iff the entry is a decrypted `gate_resolved` row whose outcome is
703
+ * the quorum's approve consensus (`consensus_approve` / `proceed`) — the
704
+ * client-side defense-in-depth subset check of design §3.1 item 1 (the
705
+ * server independently enforces the same subset, §3.1 item 4).
706
+ */
707
+ flaggable: boolean;
708
+ }
709
+ /**
710
+ * The session-scoped retained `/audit` mapping (design §3.1 item 1). Replaced
711
+ * wholesale on each successful `/audit <task-id>` render (last render wins);
712
+ * `flagged` carries forward across renders so "already flagged" survives
713
+ * re-listing. Lives in {@link OrchestrationState.auditFlagIndex}.
714
+ */
715
+ export interface AuditFlagIndex {
716
+ /** The task whose listing was rendered — becomes `flaggedTaskId`. */
717
+ taskId: string;
718
+ /** `entryId → record` for every entry in the last-rendered listing. */
719
+ entries: Map<string, AuditFlagEntryRecord>;
720
+ /** Entry ids flagged this session (dedup no-op render on repeat `/flag`). */
721
+ flagged: Set<string>;
670
722
  }
671
723
  /**
672
724
  * Snapshot threaded into the interactive `/reviewer-setup` wizard when it
@@ -853,6 +905,12 @@ export type OrchestrationAction = {
853
905
  wizard: ReviewerWizardState;
854
906
  } | {
855
907
  type: 'REVIEWER_WIZARD_CLOSE';
908
+ } | {
909
+ type: 'AUDIT_FLAG_INDEX_RETAINED';
910
+ index: AuditFlagIndex;
911
+ } | {
912
+ type: 'AUDIT_FLAG_RECORDED';
913
+ entryId: string;
856
914
  } | {
857
915
  type: 'EXIT';
858
916
  };
@@ -15,6 +15,21 @@ export interface WorkspaceShutdownOperations {
15
15
  onPlannerFlushFailure?: (error: Error) => void;
16
16
  onWorkspaceFailure?: (error: Error) => void;
17
17
  }
18
+ export interface StartedWorkspaceShutdown {
19
+ /**
20
+ * Every teardown phase, including best-effort planner flush and hosted
21
+ * retirement. This is the operation governed by the shell's normal/signal
22
+ * responsiveness deadline.
23
+ */
24
+ completion: Promise<void>;
25
+ /**
26
+ * The local resource-retirement boundary. A normal process exit must await
27
+ * this even after the broader teardown deadline expires, otherwise a large
28
+ * authenticated snapshot deletion can be killed halfway through and leak its
29
+ * private workspace.
30
+ */
31
+ resourceRetirement: Promise<void>;
32
+ }
18
33
  /**
19
34
  * Start every independent shutdown fence before awaiting any one owner.
20
35
  *
@@ -24,4 +39,9 @@ export interface WorkspaceShutdownOperations {
24
39
  * LocalExecutor drains settle can the durable store be enumerated exactly once
25
40
  * without a late publisher recreating a group behind the cleanup pass.
26
41
  */
42
+ export declare function startWorkspaceShutdownOperations(operations: WorkspaceShutdownOperations): StartedWorkspaceShutdown;
43
+ /**
44
+ * Compatibility surface for callers that need the complete shutdown rather
45
+ * than its separately-observable local resource boundary.
46
+ */
27
47
  export declare function runWorkspaceShutdownOperations(operations: WorkspaceShutdownOperations): Promise<void>;
@@ -3,6 +3,7 @@ import { type SubprocessOutcome } from '../subprocess.js';
3
3
  import type { ReviewerVerdict } from '../types.js';
4
4
  import { type VerdictClassifier } from '../verdict-classifier.js';
5
5
  import type { BuiltCommand } from './claude.js';
6
+ import { type InvocationUsageSnapshot } from '../token-usage.js';
6
7
  /** Construction options. */
7
8
  export interface CodexReviewerProviderOptions {
8
9
  /** Override the `codex` executable path. Production callers pass nothing
@@ -58,15 +59,16 @@ export declare function buildCommand(executable: string, spec: ReviewerSpec, las
58
59
  export declare function buildVerdict(spec: ReviewerSpec, gateId: string, outcome: SubprocessOutcome, lastMessage: string, classifyVerdict?: VerdictClassifier): Promise<ReviewerVerdict>;
59
60
  /**
60
61
  * Sum `usage.input_tokens + usage.output_tokens` across every
61
- * `turn.completed` JSONL event in `stdout`. Returns `null` when no
62
- * `turn.completed` event reported any token count, so dashboards can
63
- * distinguish "no data" from a real zero.
62
+ * `turn.completed` JSONL event in `stdout`. Returns `null` when usage is absent,
63
+ * partial, or invalid so legacy call sites do not turn unknown values into a
64
+ * false complete total.
64
65
  *
65
66
  * Lenient: malformed JSONL lines are silently skipped (Codex's stream is
66
67
  * designed to be append-only, so partial flushes during timeout could
67
68
  * leave a final truncated line).
68
69
  */
69
70
  export declare function sumCodexTokens(stdout: string): number | null;
71
+ export declare function extractCodexUsageSnapshot(stdout: string, elapsedMs?: number | null): InvocationUsageSnapshot;
70
72
  /**
71
73
  * Generate a unique-per-spawn path for `--output-last-message`. Lives in
72
74
  * the OS temp dir; we own its lifecycle (create on codex's side, read +
@@ -1,14 +1,39 @@
1
1
  /**
2
- * [P2-0] Clamp an UNTYPED token field (from agent CLI JSON) to a finite,
3
- * non-negative INTEGER. Token fields come from untyped agent JSON — a string /
4
- * object / `NaN` / negative / fractional value would poison the per-task
5
- * accumulator (→ `NaN` render, or a reopened window showing a LOWER total),
6
- * violating the "token count is a number" invariant. Every numeric ingress
7
- * (`unwrapClaudeJson` usage reads, Codex `sumCodexTokens` per-event sum, the
8
- * QuorumLoop `addTaskTokens`) gates on this, so a malformed field contributes 0,
9
- * never corrupts the accumulator.
2
+ * [P2-0] Clamp an UNTYPED token field (from agent CLI JSON) to the same finite,
3
+ * bounded INTEGER contract the runtime persists. Token fields come from untyped
4
+ * agent JSON — a string / object / `NaN` / negative / fractional / over-contract
5
+ * value would poison the per-task accumulator or fabricate usage. Malformed
6
+ * fields contribute 0 to legacy additive counters and canonical snapshots mark
7
+ * reported invalid fields as `usage_invalid`.
10
8
  */
11
9
  export declare function finiteToken(n: unknown): number;
10
+ export type InvocationUsageAvailability = 'complete' | 'partial' | 'unavailable';
11
+ export interface InvocationUsageSnapshot {
12
+ /** Prompt/input tokens. `cachedInputTokens` is a subset, not an addend. */
13
+ inputTokens: number | null;
14
+ /** Output/completion tokens. */
15
+ outputTokens: number | null;
16
+ /** Cached input token subset when the provider exposes it. */
17
+ cachedInputTokens: number | null;
18
+ /** Backward-compatible aggregate: input + output, never cache-added. */
19
+ totalTokens: number | null;
20
+ /** End-to-end invocation elapsed time when known. */
21
+ elapsedMs: number | null;
22
+ availability: InvocationUsageAvailability;
23
+ /** Closed reason label when availability is unavailable/partial. */
24
+ unavailableReason: string | null;
25
+ }
26
+ export declare function tokenFieldAvailable(value: unknown): value is number;
27
+ export declare function usageSnapshotFromParts(args: {
28
+ inputTokens: number;
29
+ outputTokens: number;
30
+ cachedInputTokens?: number;
31
+ elapsedMs?: number | null;
32
+ inputTokensAvailable?: boolean;
33
+ outputTokensAvailable?: boolean;
34
+ cachedInputTokensAvailable?: boolean;
35
+ }): InvocationUsageSnapshot;
36
+ export declare function unavailableUsageSnapshot(reason: string, elapsedMs?: number | null): InvocationUsageSnapshot;
12
37
  /**
13
38
  * Locate the FIRST balanced top-level `{...}` in `stdout` that parses to an object
14
39
  * with `type === 'result'` — Claude may emit non-JSON warning / MCP lines before
@@ -31,12 +56,16 @@ export declare function firstResultEnvelope(stdout: string): Record<string, unkn
31
56
  * • `is_error === true` OR `result` not a non-empty string → `{ text:'', tokens:
32
57
  * undefined }`. The CALLER treats an empty/failed result as an agent FAILURE
33
58
  * (reviewer ESCALATE / resolver human-gate — same as today), NOT a silent pass.
34
- * • else → `{ text: result, tokens: (finiteToken(usage.input_tokens) +
35
- * finiteToken(usage.output_tokens)) || undefined }`. `finiteToken` clamps
36
- * absent/malformed usage to 0 (no `TypeError`, no `NaN`); cache tokens are
37
- * excluded in v1 (input+output only see design "Known v1 limitation").
59
+ * • else → `{ text: result, tokens: totalTokens || undefined }`. `finiteToken`
60
+ * clamps absent/malformed usage to 0 (no `TypeError`, no `NaN`). Claude's
61
+ * documented input total is uncached input plus cache-write/cache-read
62
+ * buckets, while `cachedInputTokens` remains the cached subset. The older
63
+ * `cached_input_tokens` alias is intentionally ignored unless backed by
64
+ * documented Claude result-envelope evidence.
38
65
  */
66
+ export declare function extractClaudeUsageSnapshot(stdout: string, elapsedMs?: number | null): InvocationUsageSnapshot;
39
67
  export declare function unwrapClaudeJson(stdout: string): {
40
68
  text: string;
41
69
  tokens?: number;
70
+ usage: InvocationUsageSnapshot;
42
71
  };
@@ -1,3 +1,4 @@
1
+ import type { InvocationUsageSnapshot } from './token-usage.js';
1
2
  /**
2
3
  * Agent kinds the reviewer subprocess layer can spawn.
3
4
  *
@@ -101,4 +102,6 @@ export interface ReviewerVerdict {
101
102
  latency_ms: number | null;
102
103
  /** ISO 8601 UTC timestamp string when the reviewer returned the verdict. */
103
104
  submitted_at: string;
105
+ /** Canonical input/output/cache/elapsed usage snapshot. Additive post-launch. */
106
+ usage?: InvocationUsageSnapshot;
104
107
  }
@@ -136,6 +136,14 @@ export interface SubstrateProc {
136
136
  readonly stdin: Writable | null;
137
137
  readonly stdout: Readable | null;
138
138
  readonly stderr: Readable | null;
139
+ /**
140
+ * The host command runner advertises this only when the returned process is
141
+ * itself wrapped by CodeVibe's authenticated POSIX process host. The Local
142
+ * Executor must then await that host's cleanup proof before retiring a
143
+ * workspace. Docker-backed substrate processes omit it because the
144
+ * container, rather than a host POSIX group, owns their lifecycle.
145
+ */
146
+ readonly hostProcessTreeOwnership?: "posix-host";
139
147
  kill(signal?: NodeJS.Signals): boolean;
140
148
  on(event: "close" | "error" | "exit", cb: (...a: unknown[]) => void): this;
141
149
  }
@@ -0,0 +1,2 @@
1
+ /** Convert the zero-based wire/persistence track index to its user-facing number. */
2
+ export declare function displayTrackNumber(trackIndex: number): number;
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@quantiya/codevibe-core",
3
- "version": "2.0.8",
3
+ "version": "2.0.9",
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",
@@ -331,8 +331,8 @@ ifeq ($(strip $(foreach prefix,$(NO_LOAD),\
331
331
  endif
332
332
 
333
333
  quiet_cmd_regen_makefile = ACTION Regenerating $@
334
- cmd_regen_makefile = cd $(srcdir); /opt/homebrew/lib/node_modules/npm/node_modules/node-gyp/gyp/gyp_main.py -fmake --ignore-environment "-Dlibrary=shared_library" "-Dvisibility=default" "-Dnode_root_dir=/Users/hendryyeh/Library/Caches/node-gyp/24.4.0" "-Dnode_gyp_dir=/opt/homebrew/lib/node_modules/npm/node_modules/node-gyp" "-Dnode_lib_file=/Users/hendryyeh/Library/Caches/node-gyp/24.4.0/<(target_arch)/node.lib" "-Dmodule_root_dir=/Users/hendryyeh/Workspace/CodeVibe/.agent-worktrees/claude-2/codevibe-codex-plugin-2x0147/node_modules/fs-ext" "-Dnode_engine=v8" "--depth=." "-Goutput_dir=." "--generator-output=build" -I/Users/hendryyeh/Workspace/CodeVibe/.agent-worktrees/claude-2/codevibe-codex-plugin-2x0147/node_modules/fs-ext/build/config.gypi -I/opt/homebrew/lib/node_modules/npm/node_modules/node-gyp/addon.gypi -I/Users/hendryyeh/Library/Caches/node-gyp/24.4.0/include/node/common.gypi "--toplevel-dir=." binding.gyp
335
- Makefile: $(srcdir)/binding.gyp $(srcdir)/../../../../../../../Library/Caches/node-gyp/24.4.0/include/node/common.gypi $(srcdir)/build/config.gypi $(srcdir)/../../../../../../../../../opt/homebrew/lib/node_modules/npm/node_modules/node-gyp/addon.gypi
334
+ cmd_regen_makefile = cd $(srcdir); /opt/homebrew/lib/node_modules/npm/node_modules/node-gyp/gyp/gyp_main.py -fmake --ignore-environment "-Dlibrary=shared_library" "-Dvisibility=default" "-Dnode_root_dir=/Users/hendryyeh/Library/Caches/node-gyp/24.4.0" "-Dnode_gyp_dir=/opt/homebrew/lib/node_modules/npm/node_modules/node-gyp" "-Dnode_lib_file=/Users/hendryyeh/Library/Caches/node-gyp/24.4.0/<(target_arch)/node.lib" "-Dmodule_root_dir=/Users/hendryyeh/Workspace/CodeVibe/codevibe-codex-plugin/node_modules/fs-ext" "-Dnode_engine=v8" "--depth=." "-Goutput_dir=." "--generator-output=build" -I/Users/hendryyeh/Workspace/CodeVibe/codevibe-codex-plugin/node_modules/fs-ext/build/config.gypi -I/opt/homebrew/lib/node_modules/npm/node_modules/node-gyp/addon.gypi -I/Users/hendryyeh/Library/Caches/node-gyp/24.4.0/include/node/common.gypi "--toplevel-dir=." binding.gyp
335
+ Makefile: $(srcdir)/binding.gyp $(srcdir)/../../../../../Library/Caches/node-gyp/24.4.0/include/node/common.gypi $(srcdir)/build/config.gypi $(srcdir)/../../../../../../../opt/homebrew/lib/node_modules/npm/node_modules/node-gyp/addon.gypi
336
336
  $(call do_cmd,regen_makefile)
337
337
 
338
338
  # "all" is a concatenation of the "all" targets from all the included