@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
package/hooks/common.sh CHANGED
@@ -5,13 +5,12 @@
5
5
 
6
6
  # ─── Reviewer-subprocess short-circuit ───────────────────────────────
7
7
  #
8
- # When $QUORUM_REVIEWER_SUBPROCESS is set, the current `codex`
9
- # invocation is a reviewer subprocess spawned by Quorum 2.0's
10
- # `CodexReviewerProvider` (codevibe-core-rs/crates/codevibe-reviewer/
11
- # src/providers/codex.rs). Reviewer subprocesses use
12
- # `codex exec --sandbox read-only --ephemeral` and have their own
13
- # ephemeral session id that MUST NOT interact with the user's
14
- # primary Codex session state.
8
+ # When $QUORUM_REVIEWER_SUBPROCESS is set, the current `codex` invocation is an
9
+ # internal CodeVibe subprocess and MUST NOT interact with the user's primary
10
+ # Codex session state. This isolation marker is dominant: an inherited
11
+ # $CODEVIBE_CODEX_PRIMARY_COMPANION value must never bypass it. The private
12
+ # launcher removes QUORUM only at the exact primary Codex handoff; reviewers and
13
+ # implementors add/inherit QUORUM again and therefore always stop here.
15
14
  #
16
15
  # Without this guard, every reviewer spawn would fire SessionStart →
17
16
  # the plugin's resume/create logic creates a ghost backend session →
@@ -23,16 +22,14 @@
23
22
  # prevents the same bug class for Codex.
24
23
  #
25
24
  # `common.sh` is sourced by every hook script (session-start.sh,
26
- # user-prompt.sh, pre-tool-use.sh, post-tool-use.sh, stop.sh), so
27
- # `exit 0` here propagates to the hook script Codex CLI sees a
28
- # clean hook success and continues normally. Zero-impact for normal
29
- # user sessions (the env var is never set in the 1.0 code path).
25
+ # user-prompt.sh, pre-tool-use.sh, post-tool-use.sh, stop.sh), so `exit 0` here
26
+ # propagates to the hook script and Codex sees a clean hook success.
30
27
  #
31
28
  # This change belongs on 1.0 main because it protects the 1.0
32
29
  # primary-session-isolation invariant. The env var name is the same
33
30
  # `QUORUM_*` scope used by the Claude and Gemini plugins' matching
34
31
  # guards.
35
- if [ -n "$QUORUM_REVIEWER_SUBPROCESS" ]; then
32
+ if [ -n "${QUORUM_REVIEWER_SUBPROCESS:-}" ]; then
36
33
  exit 0
37
34
  fi
38
35
 
@@ -56,7 +53,7 @@ fi
56
53
  #
57
54
  # The right behavior for an unsupported environment is a silent no-op.
58
55
  # `exit 0` propagates a clean hook success to Codex.
59
- if [ -z "$CODEVIBE_CODEX_TMUX_SESSION" ]; then
56
+ if [ -z "${CODEVIBE_CODEX_TMUX_SESSION:-}" ]; then
60
57
  exit 0
61
58
  fi
62
59
 
@@ -139,7 +139,7 @@ case "${1:-}" in
139
139
  echo "Install Codex CLI with: npm install -g @openai/codex" >&2
140
140
  exit 127
141
141
  fi
142
- exec codex "$@"
142
+ exec env -u QUORUM_REVIEWER_SUBPROCESS codex "$@"
143
143
  ;;
144
144
  esac
145
145
 
@@ -379,7 +379,7 @@ if [ -n "$TMUX" ]; then
379
379
  # leaves _CV_RC at its 0 default. printf's `|| true` keeps a
380
380
  # disk-full failure from clobbering diagnostics.
381
381
  _CV_RC=0
382
- codex "$@" || _CV_RC=$?
382
+ env -u QUORUM_REVIEWER_SUBPROCESS codex "$@" || _CV_RC=$?
383
383
  printf '%s' "$_CV_RC" > "$_CV_CODEX_EXIT_FILE" 2>/dev/null || true
384
384
  exit "$_CV_RC"
385
385
  fi
@@ -390,7 +390,7 @@ if [ ! -t 0 ] || [ ! -t 1 ]; then
390
390
  _CV_AGENT_INVOKED="true"
391
391
  _CV_AGENT_STARTED_AT="$(date +%s)"
392
392
  _CV_RC=0
393
- codex "$@" || _CV_RC=$?
393
+ env -u QUORUM_REVIEWER_SUBPROCESS codex "$@" || _CV_RC=$?
394
394
  printf '%s' "$_CV_RC" > "$_CV_CODEX_EXIT_FILE" 2>/dev/null || true
395
395
  exit "$_CV_RC"
396
396
  fi
@@ -399,6 +399,7 @@ fi
399
399
  log "Starting session log watcher server..."
400
400
  export CODEX_WORKING_DIRECTORY="$WORKING_DIR"
401
401
  export CODEVIBE_CODEX_TMUX_SESSION="$SESSION_NAME"
402
+ export CODEVIBE_CODEX_PRIMARY_COMPANION=1
402
403
  export CODEVIBE_CODEX_PLUGIN_DIR="$PLUGIN_DIR"
403
404
 
404
405
  # Install hooks.json for Codex CLI with absolute paths (idempotent)
@@ -523,11 +524,12 @@ _CV_HOOKS_REASON=""
523
524
  #
524
525
  # Design notes:
525
526
  #
526
- # - "already installed" detection: a structured walk of the JSON
527
- # looking for our specific hook-script filenames at expected event
528
- # keys. Replaces the prior `grep -q "codevibe-codex"` substring
529
- # check, which false-positived on unrelated user paths containing
530
- # that string.
527
+ # - "already installed" detection: a structured comparison of the exact
528
+ # current hook commands at every expected event key. An older install path
529
+ # is owned but stale, so it is replaced instead of silently pinning hooks to
530
+ # a prior checkout/package version. This also replaces the former
531
+ # `grep -q "codevibe-codex"` substring test, which false-positived on
532
+ # unrelated user paths containing that string.
531
533
  #
532
534
  # - Merge dedupe: filters our previously-installed entries out of
533
535
  # each event key's array, then appends the fresh template entries.
@@ -586,12 +588,13 @@ if INSTALLER_OUTPUT=$(CV_EXISTING="$CODEX_HOOKS_FILE" \
586
588
  if (filtered.length === 0) return null;
587
589
  return { ...entry, hooks: filtered };
588
590
  };
589
- // Does this matcher entry contain at least one of our hooks anywhere
590
- // in its inner hooks[]? Used by the "already installed" walk only —
591
- // it does NOT decide whether the entry survives a strip.
592
- const entryContainsOurs = (entry) =>
593
- entry && typeof entry === "object" && Array.isArray(entry.hooks) &&
594
- entry.hooks.some((h) => h && isOurCommand(h.command));
591
+ const ownedCommands = (entries) => entries
592
+ .flatMap((entry) => entry && typeof entry === "object" && Array.isArray(entry.hooks)
593
+ ? entry.hooks
594
+ : [])
595
+ .map((hook) => hook && hook.command)
596
+ .filter(isOurCommand)
597
+ .sort();
595
598
 
596
599
  let existingObj = null;
597
600
  let existed = false;
@@ -618,8 +621,9 @@ if INSTALLER_OUTPUT=$(CV_EXISTING="$CODEX_HOOKS_FILE" \
618
621
  nextObj.hooks && typeof nextObj.hooks === "object") ? nextObj.hooks : {};
619
622
  let allPresent = existed;
620
623
  for (const k of Object.keys(nextHooks)) {
621
- const arr = Array.isArray(existingHooks[k]) ? existingHooks[k] : [];
622
- if (!arr.some(entryContainsOurs)) {
624
+ const existingArr = Array.isArray(existingHooks[k]) ? existingHooks[k] : [];
625
+ const nextArr = Array.isArray(nextHooks[k]) ? nextHooks[k] : [];
626
+ if (JSON.stringify(ownedCommands(existingArr)) !== JSON.stringify(ownedCommands(nextArr))) {
623
627
  allPresent = false;
624
628
  break;
625
629
  }
@@ -752,7 +756,7 @@ done
752
756
  # wrapper's cleanup trap can report it via `wrapper_exited` telemetry —
753
757
  # tmux's own attach exit code is independent of the inner process exit.
754
758
  tmux new-session -d -s "$SESSION_NAME" -x "$(tput cols)" -y "$(tput lines)" \
755
- "export CODEVIBE_CODEX_TMUX_SESSION='$SESSION_NAME'; export ENVIRONMENT='$ENVIRONMENT'; $CODEX_CMD; printf '%s' \"\$?\" > '$_CV_CODEX_EXIT_FILE'; exit"
759
+ "unset QUORUM_REVIEWER_SUBPROCESS; export CODEVIBE_CODEX_TMUX_SESSION='$SESSION_NAME'; export CODEVIBE_CODEX_PRIMARY_COMPANION=1; export ENVIRONMENT='$ENVIRONMENT'; $CODEX_CMD; printf '%s' \"\$?\" > '$_CV_CODEX_EXIT_FILE'; exit"
756
760
  _CV_TMUX_STARTED="true"
757
761
  _CV_AGENT_INVOKED="true"
758
762
  _CV_AGENT_STARTED_AT="$(date +%s)"
@@ -0,0 +1,127 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ // tmux pipe-pane must execute a command string in the tmux server. Keep the
5
+ // file-open security and hard byte bound in this tiny helper, independently of
6
+ // the observer's tmux control path. The observer owns both files and passes
7
+ // their expected identities; this process refuses path substitution, reports
8
+ // ready/done through the verified state descriptor, and never grows pane.log
9
+ // past maxBytes even if `tmux pipe-pane -t` later fails.
10
+ const fs = require('fs');
11
+
12
+ const [
13
+ , ,
14
+ filePath,
15
+ expectedDevText,
16
+ expectedInoText,
17
+ statePath,
18
+ expectedStateDevText,
19
+ expectedStateInoText,
20
+ token,
21
+ maxBytesText,
22
+ ] = process.argv;
23
+ const expectedDev = Number(expectedDevText);
24
+ const expectedIno = Number(expectedInoText);
25
+ const expectedStateDev = Number(expectedStateDevText);
26
+ const expectedStateIno = Number(expectedStateInoText);
27
+ const maxBytes = Number(maxBytesText);
28
+
29
+ if (
30
+ !filePath
31
+ || !statePath
32
+ || !/^[a-f0-9]{32}$/.test(token || '')
33
+ || !Number.isSafeInteger(expectedDev)
34
+ || !Number.isSafeInteger(expectedIno)
35
+ || !Number.isSafeInteger(expectedStateDev)
36
+ || !Number.isSafeInteger(expectedStateIno)
37
+ || !Number.isSafeInteger(maxBytes)
38
+ || maxBytes <= 0
39
+ ) {
40
+ process.exit(64);
41
+ }
42
+
43
+ const openVerified = (target, flags, expectedDevice, expectedInode) => {
44
+ const fd = fs.openSync(target, flags | (fs.constants.O_NOFOLLOW || 0));
45
+ const stat = fs.fstatSync(fd);
46
+ if (!stat.isFile() || stat.dev !== expectedDevice || stat.ino !== expectedInode) {
47
+ fs.closeSync(fd);
48
+ throw new Error('identity mismatch');
49
+ }
50
+ return fd;
51
+ };
52
+
53
+ let paneFd;
54
+ let stateFd;
55
+ try {
56
+ paneFd = openVerified(
57
+ filePath,
58
+ fs.constants.O_WRONLY | fs.constants.O_APPEND,
59
+ expectedDev,
60
+ expectedIno,
61
+ );
62
+ stateFd = openVerified(
63
+ statePath,
64
+ fs.constants.O_WRONLY,
65
+ expectedStateDev,
66
+ expectedStateIno,
67
+ );
68
+ } catch {
69
+ if (paneFd !== undefined) try { fs.closeSync(paneFd); } catch { /* ignore */ }
70
+ if (stateFd !== undefined) try { fs.closeSync(stateFd); } catch { /* ignore */ }
71
+ process.exit(66);
72
+ }
73
+
74
+ const writeState = (state, code = '') => {
75
+ const value = Buffer.from(`${state} ${token} ${process.pid}${code === '' ? '' : ` ${code}`}\n`, 'utf8');
76
+ fs.ftruncateSync(stateFd, 0);
77
+ let written = 0;
78
+ while (written < value.length) {
79
+ const count = fs.writeSync(stateFd, value, written, value.length - written, written);
80
+ if (count <= 0) throw new Error('short writer-state write');
81
+ written += count;
82
+ }
83
+ fs.fsyncSync(stateFd);
84
+ };
85
+
86
+ let finished = false;
87
+ const finish = (code) => {
88
+ if (finished) return;
89
+ finished = true;
90
+ process.stdin.pause();
91
+ try { fs.closeSync(paneFd); } catch { /* ignore */ }
92
+ try { writeState('done', code); } catch { /* observer will retain the bounded mirror */ }
93
+ try { fs.closeSync(stateFd); } catch { /* ignore */ }
94
+ process.exit(code);
95
+ };
96
+
97
+ try {
98
+ writeState('ready');
99
+ if (fs.fstatSync(paneFd).size >= maxBytes) finish(0);
100
+ } catch {
101
+ finish(67);
102
+ }
103
+
104
+ process.stdin.on('data', (chunk) => {
105
+ if (finished) return;
106
+ try {
107
+ const size = fs.fstatSync(paneFd).size;
108
+ const remaining = Math.max(0, maxBytes - size);
109
+ const input = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
110
+ const length = Math.min(remaining, input.length);
111
+ let written = 0;
112
+ while (written < length) {
113
+ const count = fs.writeSync(paneFd, input, written, length - written);
114
+ if (count <= 0) throw new Error('short pane-mirror write');
115
+ written += count;
116
+ }
117
+ if (length < input.length || size + written >= maxBytes) finish(0);
118
+ } catch {
119
+ finish(67);
120
+ }
121
+ });
122
+ process.stdin.on('end', () => finish(0));
123
+ process.stdin.on('close', () => finish(0));
124
+ process.stdin.on('error', () => finish(68));
125
+ process.on('SIGHUP', () => finish(0));
126
+ process.on('SIGINT', () => finish(0));
127
+ process.on('SIGTERM', () => finish(0));
@@ -1,8 +1,30 @@
1
+ import WebSocket from 'ws';
1
2
  import { CreateEventInput, CreateSessionInput, UpdateSessionInput, UpdateEventStatusInput, Event, Session, EventSource, DeviceKey, GrantSessionKeyInput, UpdateReviewerPolicyInput, UserReviewerPolicySnapshot } from '../types';
2
3
  import type { PostDecisionAction } from '../orchestration-shell/types';
4
+ import type { InvocationUsageSnapshot } from '../reviewer/token-usage';
3
5
  import type { CreateTaskGroupInput, CreateTaskGroupResult, SubmitMergeGateVerdictInput, SubmitMergeGateVerdictResult, SubmitTrackVerificationOutcomeInput, SubmitTrackVerificationOutcomeResult, ClaimGroupDecisionInput, ClaimGroupDecisionResult } from '../local-executor/task-group-types';
4
6
  import type { ReviewerVerdict, ReviewerRole, AgentKind as ReviewerAgentKind } from '../reviewer/types';
5
7
  import type { PlannerDecision } from '../planner/types';
8
+ /**
9
+ * Dispose a watcher/subscription WebSocket without crashing the process
10
+ * (#657 D-1 fix-forward — the clean-quit session/admission leak).
11
+ *
12
+ * `ws.close()` on a still-CONNECTING socket aborts the handshake and emits an
13
+ * ASYNC 'error' ("WebSocket was closed before the connection was established")
14
+ * on a LATER tick. Every prior disposal site paired `close(1000)` with
15
+ * `removeAllListeners()` — in either order — so that late emission always
16
+ * found a listener-less socket and crashed Node with an unhandled 'error'
17
+ * mid-teardown, BEFORE `retireHostedSession` could land the session INACTIVE
18
+ * mutation. Net effect: every clean `/quit` whose Class B watcher (or any
19
+ * other watcher) was mid-(re)connect leaked an ACTIVE session row AND an
20
+ * `activeSessionAdmissionCount` slot (#101 class), hard-capping the
21
+ * 19-scenario GA baseline after exactly the free headroom.
22
+ *
23
+ * Order is load-bearing: strip stale handlers FIRST, re-arm a swallow-all
24
+ * 'error' handler, THEN close. Exported for the regression test only — not
25
+ * part of the public SDK surface.
26
+ */
27
+ export declare function disposeWatcherSocket(ws: WebSocket, subscriptionId?: string): void;
6
28
  /**
7
29
  * One assigned reviewer seat returned by `getInReviewAssignments`. Mirrors the
8
30
  * engine `InReviewAssignment` serde (camelCase) in
@@ -101,6 +123,24 @@ export interface RecordExecutionEventInput {
101
123
  /** Free-form JSON; encrypted before crossing AppSync. */
102
124
  payload: unknown;
103
125
  dedupKeyHex: string;
126
+ /**
127
+ * O1 C1 (#652) §3.1 item 3 — the plaintext flag-target locator, REQUIRED
128
+ * when `kind = 'FLAG_BAD_APPROVAL'` and absent otherwise (non-flag events
129
+ * stay byte-identical to the pre-O1-C1 wire). Row COORDINATES only — zero
130
+ * content. Field names mirror the Rust handler DTO
131
+ * (`RecordExecutionEventInput` in core-rs `types.rs`, camelCase serde) —
132
+ * the wire-shape source of truth. The server rejects a request whose
133
+ * `taskId` ≠ `flaggedTaskId` (the per-task dedup-partition pin).
134
+ */
135
+ flaggedTaskId?: string;
136
+ /**
137
+ * The flagged row's audit-table sort key — the zero-padded 16-digit
138
+ * `seqNum` string exactly as surfaced by `queryAudit`'s additive
139
+ * `AuditEntryWireRow.seqNum` projection.
140
+ */
141
+ flaggedSeqNum?: string;
142
+ /** The flagged entry's `entry_id` (also the dedup-key derivation input). */
143
+ flaggedEntryId?: string;
104
144
  }
105
145
  /**
106
146
  * PHASE-CP-1-F.1 Part A (LOCK L2) — tolerant `AWSJSON!` object read.
@@ -180,8 +220,29 @@ export interface TaskReviewSummaryDurations {
180
220
  }
181
221
  export interface TaskReviewSummaryTokenUsage {
182
222
  knownTotal?: number;
223
+ knownInput?: number;
224
+ knownOutput?: number;
225
+ knownCachedInput?: number;
183
226
  availability: string;
184
227
  unavailableRoles: string[];
228
+ invocations?: TaskReviewSummaryTokenInvocation[];
229
+ }
230
+ export interface TaskReviewSummaryTokenInvocation {
231
+ role: string;
232
+ agent: string;
233
+ taskId?: string;
234
+ trackIndex?: number;
235
+ round?: number;
236
+ gateId?: string;
237
+ seatId?: number;
238
+ reviewerRole?: string;
239
+ inputTokens?: number;
240
+ outputTokens?: number;
241
+ cachedInputTokens?: number;
242
+ totalTokens?: number;
243
+ elapsedMs?: number;
244
+ availability: string;
245
+ unavailableReason?: string;
185
246
  }
186
247
  /** One round in the per-round summary (decrypted). */
187
248
  export interface TaskReviewSummaryRound {
@@ -862,6 +923,7 @@ export declare class AppSyncClient {
862
923
  sessionId: string;
863
924
  roundNumber: number;
864
925
  rawOutput: string;
926
+ usage?: InvocationUsageSnapshot;
865
927
  }, sessionKeyBase64: string): Promise<{
866
928
  gateId: string;
867
929
  workflowState: string;
@@ -4,3 +4,11 @@
4
4
  * the raw process argv (node, script, "keychain", subcommand, flags…).
5
5
  */
6
6
  export declare function runKeychainCli(argv: string[]): Promise<number>;
7
+ /**
8
+ * Advisory nudge for `codevibe login` / `codevibe status` (GA follow-up to #642):
9
+ * when this machine still holds file-backed credentials, has NOT migrated, and the
10
+ * OS keyring is actually usable, print the `keychain migrate` suggestion. Purely
11
+ * additive output — never throws, never mutates state, silent on any ambiguity
12
+ * (see migrate-nudge.ts invariants).
13
+ */
14
+ export declare function maybePrintKeychainMigrationNudge(print?: (line: string) => void): Promise<void>;