@cjhyy/code-shell-core 0.7.0-beta.1 → 0.7.1

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 (146) hide show
  1. package/dist/arena/arena.d.ts +2 -0
  2. package/dist/arena/arena.js +30 -1
  3. package/dist/arena/phases/adjudication.d.ts +2 -1
  4. package/dist/arena/phases/adjudication.js +2 -1
  5. package/dist/arena/phases/build-consensus.d.ts +2 -1
  6. package/dist/arena/phases/build-consensus.js +3 -1
  7. package/dist/arena/phases/cross-review.d.ts +3 -1
  8. package/dist/arena/phases/cross-review.js +8 -2
  9. package/dist/arena/phases/debate-rounds.d.ts +2 -1
  10. package/dist/arena/phases/debate-rounds.js +4 -2
  11. package/dist/arena/phases/participant-research.d.ts +2 -1
  12. package/dist/arena/phases/participant-research.js +3 -1
  13. package/dist/arena/phases/planning-detail-expansion.d.ts +2 -1
  14. package/dist/arena/phases/planning-detail-expansion.js +2 -1
  15. package/dist/arena/planner.d.ts +2 -1
  16. package/dist/arena/planner.js +2 -1
  17. package/dist/arena/types.d.ts +5 -1
  18. package/dist/cc-orchestrator/agent-adapter.d.ts +2 -0
  19. package/dist/cc-orchestrator/agent-adapter.js +4 -0
  20. package/dist/cc-orchestrator/codex-session-history.d.ts +14 -1
  21. package/dist/cc-orchestrator/codex-session-history.js +64 -4
  22. package/dist/cc-orchestrator/external-agent-changes.js +22 -5
  23. package/dist/cc-orchestrator/external-agent-driver.d.ts +1 -1
  24. package/dist/cc-orchestrator/external-agent-driver.js +202 -38
  25. package/dist/cc-orchestrator/session-history.d.ts +35 -0
  26. package/dist/cc-orchestrator/session-history.js +96 -13
  27. package/dist/cli/agent-server-tcp.js +13 -2
  28. package/dist/context/manager.d.ts +3 -3
  29. package/dist/context/manager.js +6 -6
  30. package/dist/context/token-counter.js +5 -3
  31. package/dist/context/tool-result-storage.d.ts +6 -0
  32. package/dist/context/tool-result-storage.js +25 -4
  33. package/dist/credentials/access.d.ts +11 -1
  34. package/dist/credentials/access.js +77 -1
  35. package/dist/credentials/index.d.ts +3 -1
  36. package/dist/credentials/index.js +2 -0
  37. package/dist/credentials/oauth.d.ts +25 -0
  38. package/dist/credentials/oauth.js +179 -0
  39. package/dist/credentials/store.d.ts +2 -1
  40. package/dist/credentials/store.js +19 -8
  41. package/dist/credentials/types.d.ts +84 -1
  42. package/dist/credentials/types.js +16 -1
  43. package/dist/engine/engine.d.ts +67 -34
  44. package/dist/engine/engine.js +448 -247
  45. package/dist/engine/goal.d.ts +19 -0
  46. package/dist/engine/goal.js +16 -6
  47. package/dist/engine/input-attachments.js +156 -13
  48. package/dist/engine/run-image-input.d.ts +22 -0
  49. package/dist/engine/run-image-input.js +195 -0
  50. package/dist/engine/session-title.d.ts +2 -1
  51. package/dist/engine/session-title.js +4 -1
  52. package/dist/engine/steer-queue.d.ts +3 -1
  53. package/dist/engine/steer-queue.js +10 -2
  54. package/dist/engine/turn-loop.d.ts +48 -1
  55. package/dist/engine/turn-loop.js +307 -37
  56. package/dist/engine/types.d.ts +6 -2
  57. package/dist/git/worktree/crud.d.ts +3 -0
  58. package/dist/git/worktree/crud.js +32 -3
  59. package/dist/git/worktree/git-exec.d.ts +2 -2
  60. package/dist/git/worktree/git-exec.js +47 -11
  61. package/dist/git/worktree/query.d.ts +8 -7
  62. package/dist/git/worktree/query.js +27 -20
  63. package/dist/hooks/events.d.ts +3 -0
  64. package/dist/hooks/events.js +0 -3
  65. package/dist/hooks/goal-stop-hook.d.ts +44 -2
  66. package/dist/hooks/goal-stop-hook.js +775 -52
  67. package/dist/hooks/registry.js +3 -0
  68. package/dist/hooks/shell-runner.d.ts +12 -1
  69. package/dist/hooks/shell-runner.js +160 -9
  70. package/dist/index.d.ts +7 -6
  71. package/dist/index.js +6 -5
  72. package/dist/llm/client-base.js +12 -10
  73. package/dist/llm/types.d.ts +12 -5
  74. package/dist/plugins/pluginCommandHook.d.ts +4 -4
  75. package/dist/plugins/pluginCommandHook.js +111 -13
  76. package/dist/preset/index.js +14 -4
  77. package/dist/protocol/chat-session-manager.d.ts +13 -2
  78. package/dist/protocol/chat-session-manager.js +90 -18
  79. package/dist/protocol/chat-session.d.ts +12 -0
  80. package/dist/protocol/chat-session.js +30 -5
  81. package/dist/protocol/client.d.ts +5 -2
  82. package/dist/protocol/client.js +22 -1
  83. package/dist/protocol/server.d.ts +25 -11
  84. package/dist/protocol/server.js +291 -73
  85. package/dist/protocol/types.d.ts +36 -2
  86. package/dist/protocol/types.js +2 -0
  87. package/dist/services/dream-consolidation.d.ts +3 -0
  88. package/dist/services/dream-consolidation.js +4 -1
  89. package/dist/services/index.d.ts +1 -1
  90. package/dist/services/index.js +1 -1
  91. package/dist/services/oauth.d.ts +34 -10
  92. package/dist/services/oauth.js +233 -98
  93. package/dist/session/session-manager.d.ts +35 -6
  94. package/dist/session/session-manager.js +396 -27
  95. package/dist/session/transcript.d.ts +30 -1
  96. package/dist/session/transcript.js +119 -4
  97. package/dist/tool-system/builtin/agent-notifications.d.ts +11 -4
  98. package/dist/tool-system/builtin/agent-notifications.js +19 -7
  99. package/dist/tool-system/builtin/agent.js +5 -1
  100. package/dist/tool-system/builtin/arena.js +1 -0
  101. package/dist/tool-system/builtin/background-jobs.d.ts +28 -5
  102. package/dist/tool-system/builtin/background-jobs.js +109 -7
  103. package/dist/tool-system/builtin/background-work.d.ts +6 -1
  104. package/dist/tool-system/builtin/background-work.js +5 -1
  105. package/dist/tool-system/builtin/bash.d.ts +3 -5
  106. package/dist/tool-system/builtin/bash.js +10 -5
  107. package/dist/tool-system/builtin/browser-tools.d.ts +2 -2
  108. package/dist/tool-system/builtin/cron-list.definition.d.ts +3 -0
  109. package/dist/tool-system/builtin/cron-list.definition.js +6 -0
  110. package/dist/tool-system/builtin/cron.d.ts +1 -2
  111. package/dist/tool-system/builtin/cron.js +9 -7
  112. package/dist/tool-system/builtin/drive-claude-code.d.ts +7 -0
  113. package/dist/tool-system/builtin/drive-claude-code.js +307 -20
  114. package/dist/tool-system/builtin/edit.d.ts +2 -1
  115. package/dist/tool-system/builtin/edit.js +12 -4
  116. package/dist/tool-system/builtin/generate-video.d.ts +4 -0
  117. package/dist/tool-system/builtin/generate-video.js +138 -21
  118. package/dist/tool-system/builtin/glob.d.ts +2 -1
  119. package/dist/tool-system/builtin/glob.js +28 -3
  120. package/dist/tool-system/builtin/grep.d.ts +1 -0
  121. package/dist/tool-system/builtin/grep.js +82 -17
  122. package/dist/tool-system/builtin/index.d.ts +25 -11
  123. package/dist/tool-system/builtin/index.js +60 -5
  124. package/dist/tool-system/builtin/sleep.d.ts +1 -2
  125. package/dist/tool-system/builtin/sleep.definition.d.ts +8 -0
  126. package/dist/tool-system/builtin/sleep.definition.js +28 -0
  127. package/dist/tool-system/builtin/sleep.js +1 -22
  128. package/dist/tool-system/builtin/video-providers.d.ts +12 -15
  129. package/dist/tool-system/builtin/video-providers.js +1 -0
  130. package/dist/tool-system/builtin/view-image.d.ts +2 -2
  131. package/dist/tool-system/builtin/web-fetch.js +44 -3
  132. package/dist/tool-system/builtin/worktree.js +25 -7
  133. package/dist/tool-system/builtin/write.d.ts +2 -1
  134. package/dist/tool-system/builtin/write.js +14 -4
  135. package/dist/tool-system/context.d.ts +35 -5
  136. package/dist/tool-system/executor.js +24 -8
  137. package/dist/tool-system/mcp-manager.d.ts +20 -2
  138. package/dist/tool-system/mcp-manager.js +111 -12
  139. package/dist/tool-system/path-policy.d.ts +19 -0
  140. package/dist/tool-system/path-policy.js +62 -1
  141. package/dist/tool-system/permission.d.ts +43 -3
  142. package/dist/tool-system/permission.js +383 -30
  143. package/dist/tool-system/registry.d.ts +3 -2
  144. package/dist/tool-system/registry.js +52 -34
  145. package/dist/types.d.ts +38 -7
  146. package/package.json +1 -1
@@ -19,6 +19,8 @@ export declare class Arena {
19
19
  private config;
20
20
  private strategy;
21
21
  private limits;
22
+ private usage;
23
+ private readonly recordUsage;
22
24
  constructor(config: ArenaConfig);
23
25
  /**
24
26
  * Run a full arena session.
@@ -28,6 +28,7 @@ import { runDetailExpansion } from "./phases/planning-detail-expansion.js";
28
28
  import { withLanguage } from "./strategies/language-wrapper.js";
29
29
  import { ArenaLedger } from "./ledger.js";
30
30
  import { registerClaims } from "./phases/claim-registry.js";
31
+ import { addTokenUsage } from "../engine/session-usage.js";
31
32
  /** Bucket claims by status. Shared by the two consensus phases. */
32
33
  function summarizeClaims(all) {
33
34
  return {
@@ -41,6 +42,17 @@ export class Arena {
41
42
  config;
42
43
  strategy;
43
44
  limits;
45
+ usage = {
46
+ promptTokens: 0,
47
+ completionTokens: 0,
48
+ totalTokens: 0,
49
+ cacheReadTokens: 0,
50
+ cacheCreationTokens: 0,
51
+ };
52
+ recordUsage = (usage) => {
53
+ if (usage)
54
+ this.usage = addTokenUsage(this.usage, usage);
55
+ };
44
56
  constructor(config) {
45
57
  if (config.participants.length < 2) {
46
58
  throw new Error("Arena requires at least 2 participants");
@@ -63,13 +75,20 @@ export class Arena {
63
75
  * @param flags - Optional explicit overrides (mode, base, head)
64
76
  */
65
77
  async run(topic, flags) {
78
+ this.usage = {
79
+ promptTokens: 0,
80
+ completionTokens: 0,
81
+ totalTokens: 0,
82
+ cacheReadTokens: 0,
83
+ cacheCreationTokens: 0,
84
+ };
66
85
  const participantNames = this.config.participants.map((p) => p.name);
67
86
  const signal = this.config.signal;
68
87
  logger.info("arena.start", { mode: this.config.mode, participants: participantNames });
69
88
  signal?.throwIfAborted();
70
89
  // ─── Phase 0: Plan ────────────────────────────────────────
71
90
  const llmConfig = this.config.participants[0].llm;
72
- const plan = await planArena(topic, llmConfig, flags, signal);
91
+ const plan = await planArena(topic, llmConfig, flags, signal, this.recordUsage);
73
92
  this.config.onProgress?.({ type: "plan_resolved", plan });
74
93
  logger.info("arena.plan", {
75
94
  mode: plan.mode,
@@ -129,6 +148,7 @@ export class Arena {
129
148
  contextTools,
130
149
  signal,
131
150
  onProgress: this.config.onProgress,
151
+ onUsage: this.recordUsage,
132
152
  });
133
153
  const reports = researchResults.map((r) => r.report);
134
154
  const dossiers = researchResults.map((r) => r.dossier);
@@ -197,6 +217,7 @@ export class Arena {
197
217
  mode: "planning",
198
218
  signal,
199
219
  onProgress: this.config.onProgress,
220
+ onUsage: this.recordUsage,
200
221
  });
201
222
  signal?.throwIfAborted();
202
223
  // ─── Roadmap Consensus ───────────────────────────────────
@@ -212,6 +233,7 @@ export class Arena {
212
233
  claimSummary,
213
234
  signal,
214
235
  onProgress: this.config.onProgress,
236
+ onUsage: this.recordUsage,
215
237
  });
216
238
  signal?.throwIfAborted();
217
239
  // ─── Detail Expansion ────────────────────────────────────
@@ -229,6 +251,7 @@ export class Arena {
229
251
  contextTools,
230
252
  signal,
231
253
  onProgress: this.config.onProgress,
254
+ onUsage: this.recordUsage,
232
255
  });
233
256
  consensus.roadmapDetails = details;
234
257
  }
@@ -251,6 +274,7 @@ export class Arena {
251
274
  claims: allClaims,
252
275
  reviews,
253
276
  consensus,
277
+ usage: { ...this.usage },
254
278
  };
255
279
  }
256
280
  /**
@@ -271,6 +295,7 @@ export class Arena {
271
295
  limits: this.limits,
272
296
  signal,
273
297
  onProgress: this.config.onProgress,
298
+ onUsage: this.recordUsage,
274
299
  });
275
300
  signal?.throwIfAborted();
276
301
  // ─── Debate Rounds ───────────────────────────────────────
@@ -284,6 +309,7 @@ export class Arena {
284
309
  maxRounds: this.config.maxDiscussionRounds,
285
310
  signal,
286
311
  onProgress: this.config.onProgress,
312
+ onUsage: this.recordUsage,
287
313
  });
288
314
  signal?.throwIfAborted();
289
315
  // ─── Adjudication ────────────────────────────────────────
@@ -295,6 +321,7 @@ export class Arena {
295
321
  ledger,
296
322
  signal,
297
323
  onProgress: this.config.onProgress,
324
+ onUsage: this.recordUsage,
298
325
  });
299
326
  signal?.throwIfAborted();
300
327
  // ─── Consensus (claim-aware) ─────────────────────────────
@@ -314,6 +341,7 @@ export class Arena {
314
341
  claimSummary,
315
342
  signal,
316
343
  onProgress: this.config.onProgress,
344
+ onUsage: this.recordUsage,
317
345
  });
318
346
  return {
319
347
  topic,
@@ -328,6 +356,7 @@ export class Arena {
328
356
  debateRounds,
329
357
  adjudications,
330
358
  consensus,
359
+ usage: { ...this.usage },
331
360
  };
332
361
  }
333
362
  }
@@ -6,7 +6,7 @@
6
6
  *
7
7
  * Claims that were verified without contest get synthetic adjudication records.
8
8
  */
9
- import type { ArenaParticipant, ArenaStrategy, ClaimAdjudication, ArenaProgressEvent } from "../types.js";
9
+ import type { ArenaParticipant, ArenaStrategy, ClaimAdjudication, ArenaProgressEvent, ArenaUsageRecorder } from "../types.js";
10
10
  import type { ArenaLedger } from "../ledger.js";
11
11
  interface AdjudicationOptions {
12
12
  concluder: ArenaParticipant;
@@ -15,6 +15,7 @@ interface AdjudicationOptions {
15
15
  ledger: ArenaLedger;
16
16
  signal?: AbortSignal;
17
17
  onProgress?: (event: ArenaProgressEvent) => void;
18
+ onUsage?: ArenaUsageRecorder;
18
19
  }
19
20
  /**
20
21
  * Run adjudication phase. The moderator rules on contested claims.
@@ -17,7 +17,7 @@ import { logger } from "../../logging/logger.js";
17
17
  * Verified claims get synthetic adjudications.
18
18
  */
19
19
  export async function runAdjudication(options) {
20
- const { concluder, strategy, topic, ledger, signal, onProgress } = options;
20
+ const { concluder, strategy, topic, ledger, signal, onProgress, onUsage } = options;
21
21
  const allAdjudications = [];
22
22
  const v2 = isStrategyV2(strategy);
23
23
  // Claims that need moderator adjudication: contested or those with debate rounds
@@ -54,6 +54,7 @@ export async function runAdjudication(options) {
54
54
  messages: [{ role: "user", content: userContent }],
55
55
  signal,
56
56
  });
57
+ onUsage?.(response.usage);
57
58
  logger.info("arena.adjudication_response", {
58
59
  claimId: claim.claimId,
59
60
  stopReason: response.stopReason,
@@ -8,7 +8,7 @@
8
8
  * In both paths, the moderator must faithfully reflect aggregated results
9
9
  * (no new unsourced conclusions).
10
10
  */
11
- import type { ArenaParticipant, ArenaStrategy, ParticipantReport, FindingReview, ArenaConsensus, ClaimStatusSummary, ArenaProgressEvent } from "../types.js";
11
+ import type { ArenaParticipant, ArenaStrategy, ParticipantReport, FindingReview, ArenaConsensus, ClaimStatusSummary, ArenaProgressEvent, ArenaUsageRecorder } from "../types.js";
12
12
  interface ConsensusOptions {
13
13
  /** The participant that acts as moderator/concluder */
14
14
  concluder: ArenaParticipant;
@@ -20,6 +20,7 @@ interface ConsensusOptions {
20
20
  claimSummary?: ClaimStatusSummary;
21
21
  signal?: AbortSignal;
22
22
  onProgress?: (event: ArenaProgressEvent) => void;
23
+ onUsage?: ArenaUsageRecorder;
23
24
  }
24
25
  /**
25
26
  * Build structured consensus from participant reports, cross-reviews,
@@ -16,7 +16,7 @@ import { isStrategyV2 } from "../types.js";
16
16
  * and optionally adjudicated claim data.
17
17
  */
18
18
  export async function buildConsensus(options) {
19
- const { concluder, strategy, topic, reports, reviews, claimSummary, signal, onProgress } = options;
19
+ const { concluder, strategy, topic, reports, reviews, claimSummary, signal, onProgress, onUsage } = options;
20
20
  onProgress?.({ type: "consensus_start" });
21
21
  const client = await createLLMClient(concluder.llm, concluder.clientDefaults);
22
22
  const systemPrompt = strategy.consensusSystemPrompt();
@@ -41,6 +41,7 @@ export async function buildConsensus(options) {
41
41
  messages: [{ role: "user", content: userContent }],
42
42
  signal,
43
43
  });
44
+ onUsage?.(response.usage);
44
45
  logger.info("arena.consensus_raw_response", {
45
46
  text: response.text,
46
47
  stopReason: response.stopReason,
@@ -64,6 +65,7 @@ export async function buildConsensus(options) {
64
65
  ],
65
66
  signal,
66
67
  });
68
+ onUsage?.(retryResponse.usage);
67
69
  logger.info("arena.consensus_retry_response", {
68
70
  textLen: retryResponse.text?.length ?? 0,
69
71
  stopReason: retryResponse.stopReason,
@@ -7,7 +7,7 @@
7
7
  * V2 (VerificationReview): Reviewers get claim + evidence packet + digest data,
8
8
  * and produce ClaimChallenge records that feed into the claim state machine.
9
9
  */
10
- import type { ArenaParticipant, ArenaStrategy, ParticipantReport, FindingReview, ClaimChallenge, ArenaProgressEvent, ArenaExecutionLimits } from "../types.js";
10
+ import type { ArenaParticipant, ArenaStrategy, ParticipantReport, FindingReview, ClaimChallenge, ArenaProgressEvent, ArenaExecutionLimits, ArenaUsageRecorder } from "../types.js";
11
11
  import type { ArenaLedger } from "../ledger.js";
12
12
  interface CrossReviewOptions {
13
13
  participants: ArenaParticipant[];
@@ -16,6 +16,7 @@ interface CrossReviewOptions {
16
16
  reports: ParticipantReport[];
17
17
  signal?: AbortSignal;
18
18
  onProgress?: (event: ArenaProgressEvent) => void;
19
+ onUsage?: ArenaUsageRecorder;
19
20
  }
20
21
  /**
21
22
  * Run cross-review phase (V1). Each participant reviews the others' findings.
@@ -32,6 +33,7 @@ interface VerificationReviewOptions {
32
33
  mode?: "review" | "discussion" | "planning";
33
34
  signal?: AbortSignal;
34
35
  onProgress?: (event: ArenaProgressEvent) => void;
36
+ onUsage?: ArenaUsageRecorder;
35
37
  }
36
38
  interface VerificationReviewResult {
37
39
  reviews: FindingReview[];
@@ -17,7 +17,7 @@ import { logger } from "../../logging/logger.js";
17
17
  * Run cross-review phase (V1). Each participant reviews the others' findings.
18
18
  */
19
19
  export async function runCrossReview(options) {
20
- const { participants, strategy, topic, reports, signal, onProgress } = options;
20
+ const { participants, strategy, topic, reports, signal, onProgress, onUsage } = options;
21
21
  onProgress?.({ type: "cross_review_start", round: 1 });
22
22
  const allReviews = [];
23
23
  const tasks = participants.map(async (p) => {
@@ -36,6 +36,7 @@ export async function runCrossReview(options) {
36
36
  messages: [{ role: "user", content: userContent }],
37
37
  signal,
38
38
  });
39
+ onUsage?.(response.usage);
39
40
  logger.info("arena.cross_review_raw_response", {
40
41
  participant: p.name,
41
42
  text: response.text,
@@ -56,6 +57,7 @@ export async function runCrossReview(options) {
56
57
  ],
57
58
  signal,
58
59
  });
60
+ onUsage?.(retryResponse.usage);
59
61
  logger.info("arena.cross_review_retry", {
60
62
  participant: p.name,
61
63
  stopReason: retryResponse.stopReason,
@@ -76,7 +78,7 @@ export async function runCrossReview(options) {
76
78
  * Falls back to V1 cross-review if strategy doesn't implement V2.
77
79
  */
78
80
  export async function runVerificationReview(options) {
79
- const { participants, strategy, topic, reports, ledger, limits, mode, signal, onProgress } = options;
81
+ const { participants, strategy, topic, reports, ledger, limits, mode, signal, onProgress, onUsage } = options;
80
82
  // Select claims to review, prioritized by severity + confidence
81
83
  const allClaims = ledger.getAllClaims();
82
84
  const claimsToReview = selectClaimsForReview(allClaims, limits.maxClaimsForReview);
@@ -120,6 +122,7 @@ export async function runVerificationReview(options) {
120
122
  messages: [{ role: "user", content: userContent }],
121
123
  signal,
122
124
  });
125
+ onUsage?.(response.usage);
123
126
  logger.info("arena.verification_review_raw", {
124
127
  participant: p.name,
125
128
  stopReason: response.stopReason,
@@ -134,6 +137,7 @@ export async function runVerificationReview(options) {
134
137
  ],
135
138
  signal,
136
139
  });
140
+ onUsage?.(retryResponse.usage);
137
141
  response = retryResponse;
138
142
  }
139
143
  const challenges = isPlanningMerge
@@ -163,6 +167,7 @@ export async function runVerificationReview(options) {
163
167
  messages: [{ role: "user", content: userContent }],
164
168
  signal,
165
169
  });
170
+ onUsage?.(response.usage);
166
171
  if (response.stopReason === "length") {
167
172
  const retryResponse = await client.createMessage({
168
173
  systemPrompt,
@@ -173,6 +178,7 @@ export async function runVerificationReview(options) {
173
178
  ],
174
179
  signal,
175
180
  });
181
+ onUsage?.(retryResponse.usage);
176
182
  response = retryResponse;
177
183
  }
178
184
  const reviews = strategy.parseCrossReviewResponse(p.name, response.text);
@@ -7,7 +7,7 @@
7
7
  * 2. Convergence check: all stances are "support" or "narrow" → resolved
8
8
  * 3. If max rounds exhausted → stays contested for adjudication
9
9
  */
10
- import type { ArenaParticipant, ArenaStrategy, DebateRound, ArenaExecutionLimits, ArenaProgressEvent } from "../types.js";
10
+ import type { ArenaParticipant, ArenaStrategy, DebateRound, ArenaExecutionLimits, ArenaProgressEvent, ArenaUsageRecorder } from "../types.js";
11
11
  import type { ArenaLedger } from "../ledger.js";
12
12
  interface DebateOptions {
13
13
  participants: ArenaParticipant[];
@@ -18,6 +18,7 @@ interface DebateOptions {
18
18
  maxRounds: number;
19
19
  signal?: AbortSignal;
20
20
  onProgress?: (event: ArenaProgressEvent) => void;
21
+ onUsage?: ArenaUsageRecorder;
21
22
  }
22
23
  /**
23
24
  * Run debate rounds for all contested claims.
@@ -17,7 +17,7 @@ import { logger } from "../../logging/logger.js";
17
17
  * Returns empty array if no contested claims exist.
18
18
  */
19
19
  export async function runDebateRounds(options) {
20
- const { participants, strategy, topic, ledger, limits, maxRounds, signal, onProgress } = options;
20
+ const { participants, strategy, topic, ledger, limits, maxRounds, signal, onProgress, onUsage } = options;
21
21
  // Select contested claims, capped
22
22
  const contested = ledger.getClaimsByStatus("contested")
23
23
  .slice(0, limits.maxContestedClaimsForDebate);
@@ -44,6 +44,7 @@ export async function runDebateRounds(options) {
44
44
  maxRounds,
45
45
  signal,
46
46
  onProgress,
47
+ onUsage,
47
48
  });
48
49
  allDebateRounds.push(...claimDebateRounds);
49
50
  // Attach debate rounds to claim record
@@ -52,7 +53,7 @@ export async function runDebateRounds(options) {
52
53
  return allDebateRounds;
53
54
  }
54
55
  async function debateClaim(options) {
55
- const { claim, participants, strategy, v2, topic, ledger, maxRounds, signal, onProgress } = options;
56
+ const { claim, participants, strategy, v2, topic, ledger, maxRounds, signal, onProgress, onUsage } = options;
56
57
  // Identify debaters: claim owner + primary challenger
57
58
  const owner = participants.find((p) => p.name === claim.owner);
58
59
  const primaryChallenger = findPrimaryChallenger(claim, participants);
@@ -91,6 +92,7 @@ async function debateClaim(options) {
91
92
  messages: [{ role: "user", content: userContent }],
92
93
  signal,
93
94
  });
95
+ onUsage?.(response.usage);
94
96
  logger.info("arena.debate_turn", {
95
97
  participant: p.name,
96
98
  claimId: claim.claimId,
@@ -5,7 +5,7 @@
5
5
  * Phase 3 upgrade: now captures ToolTrace and builds EvidencePackets,
6
6
  * outputting a full ResearchDossier alongside the ParticipantReport.
7
7
  */
8
- import type { ArenaBaseContext, ArenaParticipant, ArenaStrategy, ParticipantReport, ResearchDossier, ArenaProgressEvent } from "../types.js";
8
+ import type { ArenaBaseContext, ArenaParticipant, ArenaStrategy, ParticipantReport, ResearchDossier, ArenaProgressEvent, ArenaUsageRecorder } from "../types.js";
9
9
  import type { ToolDefinition } from "../../types.js";
10
10
  /** Result of a single participant's research — includes both report and dossier */
11
11
  export interface ResearchResult {
@@ -23,6 +23,7 @@ interface ResearchOptions {
23
23
  /** AbortSignal — cancels in-flight LLM calls */
24
24
  signal?: AbortSignal;
25
25
  onProgress?: (event: ArenaProgressEvent) => void;
26
+ onUsage?: ArenaUsageRecorder;
26
27
  }
27
28
  /**
28
29
  * Run participant research phase in parallel.
@@ -23,7 +23,7 @@ export async function runParticipantResearch(options) {
23
23
  * Returns both reports (backward compat) and dossiers (evidence trail).
24
24
  */
25
25
  export async function runParticipantResearchWithDossiers(options) {
26
- const { participants, strategy, topic, baseContext, enableContextTools, contextTools, signal, onProgress } = options;
26
+ const { participants, strategy, topic, baseContext, enableContextTools, contextTools, signal, onProgress, onUsage } = options;
27
27
  const tools = enableContextTools ? (contextTools ?? CONTEXT_TOOLS) : undefined;
28
28
  const tasks = participants.map(async (p) => {
29
29
  onProgress?.({ type: "research_start", participant: p.name });
@@ -57,6 +57,7 @@ export async function runParticipantResearchWithDossiers(options) {
57
57
  tools,
58
58
  signal,
59
59
  });
60
+ onUsage?.(response.usage);
60
61
  const toolNames = (response.toolCalls ?? []).map((tc) => tc.toolName);
61
62
  const hasTools = response.toolCalls && response.toolCalls.length > 0;
62
63
  logger.info("arena.research_round", {
@@ -158,6 +159,7 @@ export async function runParticipantResearchWithDossiers(options) {
158
159
  },
159
160
  ],
160
161
  });
162
+ onUsage?.(forceResponse.usage);
161
163
  logger.info("arena.research_force_conclude_response", {
162
164
  participant: p.name,
163
165
  textLen: forceResponse.text?.length ?? 0,
@@ -9,7 +9,7 @@
9
9
  *
10
10
  * Tool access is available so the LLM can verify file paths and interfaces.
11
11
  */
12
- import type { ArenaParticipant, ArenaRoadmapPhase, ArenaRoadmapPhaseDetail, ArenaProgressEvent, ArenaExecutionLimits, ArenaStrategyPlanning } from "../types.js";
12
+ import type { ArenaParticipant, ArenaRoadmapPhase, ArenaRoadmapPhaseDetail, ArenaProgressEvent, ArenaExecutionLimits, ArenaStrategyPlanning, ArenaUsageRecorder } from "../types.js";
13
13
  import type { ToolDefinition } from "../../types.js";
14
14
  import type { ArenaLedger } from "../ledger.js";
15
15
  interface DetailExpansionOptions {
@@ -27,6 +27,7 @@ interface DetailExpansionOptions {
27
27
  contextTools?: ToolDefinition[];
28
28
  signal?: AbortSignal;
29
29
  onProgress?: (event: ArenaProgressEvent) => void;
30
+ onUsage?: ArenaUsageRecorder;
30
31
  }
31
32
  /**
32
33
  * Expand each roadmap phase into a repo-level implementation plan.
@@ -22,7 +22,7 @@ const MAX_EXPANSION_TOOL_ROUNDS = 3;
22
22
  * The number of phases expanded is capped by limits.maxExpandedPhasesPerRun.
23
23
  */
24
24
  export async function runDetailExpansion(options) {
25
- const { concluder, strategy, topic, phases, ledger, limits, signal, onProgress } = options;
25
+ const { concluder, strategy, topic, phases, ledger, limits, signal, onProgress, onUsage } = options;
26
26
  const tools = options.enableContextTools !== false
27
27
  ? (options.contextTools ?? CONTEXT_TOOLS)
28
28
  : undefined;
@@ -56,6 +56,7 @@ export async function runDetailExpansion(options) {
56
56
  tools: offerTools,
57
57
  signal,
58
58
  });
59
+ onUsage?.(response.usage);
59
60
  const hasTools = response.toolCalls && response.toolCalls.length > 0;
60
61
  logger.info("arena.detail_expansion_round", {
61
62
  phase: phase.title,
@@ -12,6 +12,7 @@
12
12
  * LLM call that outputs a holistic execution plan.
13
13
  */
14
14
  import type { LLMConfig } from "../types.js";
15
+ import type { ArenaUsageRecorder } from "./types.js";
15
16
  import type { ArenaPlan, ArenaMode } from "./types.js";
16
17
  /** Explicit flags the user may pass via CLI or tool args */
17
18
  export interface PlannerFlags {
@@ -24,4 +25,4 @@ export interface PlannerFlags {
24
25
  *
25
26
  * Priority: explicit flags > LLM plan > safe defaults.
26
27
  */
27
- export declare function planArena(topic: string, llmConfig: LLMConfig, flags?: PlannerFlags, signal?: AbortSignal): Promise<ArenaPlan>;
28
+ export declare function planArena(topic: string, llmConfig: LLMConfig, flags?: PlannerFlags, signal?: AbortSignal, onUsage?: ArenaUsageRecorder): Promise<ArenaPlan>;
@@ -58,7 +58,7 @@ Respond ONLY with JSON, no markdown fences:
58
58
  *
59
59
  * Priority: explicit flags > LLM plan > safe defaults.
60
60
  */
61
- export async function planArena(topic, llmConfig, flags, signal) {
61
+ export async function planArena(topic, llmConfig, flags, signal, onUsage) {
62
62
  // If mode and base are both explicit, we can skip the LLM call
63
63
  if (flags?.mode && flags?.base) {
64
64
  return buildExplicitPlan(topic, flags);
@@ -75,6 +75,7 @@ export async function planArena(topic, llmConfig, flags, signal) {
75
75
  maxTokens: 1024,
76
76
  signal,
77
77
  });
78
+ onUsage?.(response.usage);
78
79
  const plan = parsePlanResponse(response.text, topic);
79
80
  // Explicit flags override LLM output
80
81
  if (flags?.mode)
@@ -3,7 +3,9 @@
3
3
  *
4
4
  * V2 architecture: shared facts → independent research → findings → cross-review → consensus.
5
5
  */
6
- import type { ClientDefaults, LLMConfig } from "../types.js";
6
+ import type { ClientDefaults, LLMConfig, TokenUsage } from "../types.js";
7
+ /** Receives usage for each completed Arena provider request. */
8
+ export type ArenaUsageRecorder = (usage: TokenUsage | undefined) => void;
7
9
  /** Arena operating mode — determines prompts, output format, and default rounds */
8
10
  export type ArenaMode = "review" | "discussion" | "planning";
9
11
  /** Analysis lens — the perspective from which participants analyze */
@@ -337,6 +339,8 @@ export interface ArenaResultV2 {
337
339
  /** Claim adjudications (Phase 3+) */
338
340
  adjudications?: ClaimAdjudication[];
339
341
  consensus: ArenaConsensus;
342
+ /** Aggregate of every provider request made by this Arena run. */
343
+ usage: TokenUsage;
340
344
  }
341
345
  /**
342
346
  * ArenaStrategy — encapsulates mode-specific behavior for prompts and parsing.
@@ -2,6 +2,8 @@ export type PermissionMode = "default" | "acceptEdits" | "bypassPermissions";
2
2
  export interface BuildArgsOpts {
3
3
  prompt: string;
4
4
  resumeSessionId?: string;
5
+ /** Optional caller-specified model override. Passed through without validation; the CLI reports unknown models. */
6
+ model?: string;
5
7
  permissionMode: PermissionMode;
6
8
  cwd: string;
7
9
  imagePaths?: string[];
@@ -14,6 +14,8 @@ export const claudeAdapter = {
14
14
  buildArgs(opts) {
15
15
  // -p (print/headless) + stream-json REQUIRES --verbose (verified).
16
16
  const args = ["-p", opts.prompt, "--output-format", "stream-json", "--verbose"];
17
+ if (opts.model)
18
+ args.push("--model", opts.model);
17
19
  if (opts.resumeSessionId)
18
20
  args.push("--resume", opts.resumeSessionId);
19
21
  // Hard-disallow Workflow: driving CC unattended (esp. bypassPermissions),
@@ -82,6 +84,8 @@ export const codexAdapter = {
82
84
  else {
83
85
  args.push("--sandbox", opts.permissionMode === "acceptEdits" ? "workspace-write" : "read-only");
84
86
  }
87
+ if (opts.model)
88
+ args.push("--model", opts.model);
85
89
  if (opts.codexImageInputSupported) {
86
90
  for (const imagePath of opts.imagePaths ?? [])
87
91
  args.push("-i", imagePath);
@@ -1,4 +1,4 @@
1
- import type { HistoryMessage } from "./session-history.js";
1
+ import type { HistoryMessage, SessionTailEvent } from "./session-history.js";
2
2
  /**
3
3
  * Read the last `limit` user/assistant messages from a codex CLI session,
4
4
  * returning the SAME shape as the claude-side `readRecentHistory` so the room
@@ -23,3 +23,16 @@ export declare function readCodexRecentHistory(cwd: string, threadId: string, li
23
23
  hasMore: boolean;
24
24
  totalCount: number;
25
25
  };
26
+ /** Parse a bounded/raw Codex rollout snapshot. Shared with the desktop tail
27
+ * follower so its initial history and EOF cursor are atomic. */
28
+ export declare function parseCodexRecentHistory(raw: string, limit: number): {
29
+ messages: HistoryMessage[];
30
+ hasMore: boolean;
31
+ totalCount: number;
32
+ };
33
+ /** Parse one newly-appended Codex rollout JSONL line. `response_item` is the
34
+ * authoritative rendered stream; parallel `event_msg.agent_message` records
35
+ * are deliberately ignored to avoid duplicate assistant bubbles. */
36
+ export declare function parseCodexTranscriptLine(line: string): SessionTailEvent[];
37
+ /** Find the rollout file whose `session_meta` matches both `threadId` and `cwd`. */
38
+ export declare function findCodexRolloutFile(codexHome: string, cwd: string, threadId: string): string | undefined;
@@ -1,4 +1,4 @@
1
- import { readdirSync, readFileSync, statSync, existsSync, openSync, readSync, closeSync } from "node:fs";
1
+ import { readdirSync, readFileSync, statSync, existsSync, openSync, readSync, closeSync, } from "node:fs";
2
2
  import { join } from "node:path";
3
3
  import { homedir } from "node:os";
4
4
  /**
@@ -22,7 +22,7 @@ import { homedir } from "node:os";
22
22
  */
23
23
  export function readCodexRecentHistory(cwd, threadId, limit, codexHome = join(homedir(), ".codex")) {
24
24
  const empty = { messages: [], hasMore: false, totalCount: 0 };
25
- const file = findRolloutFile(codexHome, cwd, threadId);
25
+ const file = findCodexRolloutFile(codexHome, cwd, threadId);
26
26
  if (!file)
27
27
  return empty;
28
28
  let raw;
@@ -32,6 +32,11 @@ export function readCodexRecentHistory(cwd, threadId, limit, codexHome = join(ho
32
32
  catch {
33
33
  return empty;
34
34
  }
35
+ return parseCodexRecentHistory(raw, limit);
36
+ }
37
+ /** Parse a bounded/raw Codex rollout snapshot. Shared with the desktop tail
38
+ * follower so its initial history and EOF cursor are atomic. */
39
+ export function parseCodexRecentHistory(raw, limit) {
35
40
  const all = [];
36
41
  for (const line of raw.split("\n")) {
37
42
  if (!line.trim())
@@ -55,7 +60,11 @@ export function readCodexRecentHistory(cwd, threadId, limit, codexHome = join(ho
55
60
  all.push({ role: p.role, text: t });
56
61
  }
57
62
  else if (p.type === "function_call" || p.type === "custom_tool_call") {
58
- const tool = { name: typeof p.name === "string" ? p.name : "tool", summary: summaryOf(p), args: argsOf(p) };
63
+ const tool = {
64
+ name: typeof p.name === "string" ? p.name : "tool",
65
+ summary: summaryOf(p),
66
+ args: argsOf(p),
67
+ };
59
68
  const last = all[all.length - 1];
60
69
  // Attach the tool to the preceding assistant turn; otherwise start one.
61
70
  if (last && last.role === "assistant") {
@@ -70,6 +79,57 @@ export function readCodexRecentHistory(cwd, threadId, limit, codexHome = join(ho
70
79
  const start = Math.max(0, all.length - lim);
71
80
  return { messages: all.slice(start), hasMore: start > 0, totalCount: all.length };
72
81
  }
82
+ /** Parse one newly-appended Codex rollout JSONL line. `response_item` is the
83
+ * authoritative rendered stream; parallel `event_msg.agent_message` records
84
+ * are deliberately ignored to avoid duplicate assistant bubbles. */
85
+ export function parseCodexTranscriptLine(line) {
86
+ let d;
87
+ try {
88
+ d = JSON.parse(line);
89
+ }
90
+ catch {
91
+ return [];
92
+ }
93
+ if (d?.type === "event_msg" && d.payload?.type === "task_complete") {
94
+ return [{ type: "turn_end", reason: "completed" }];
95
+ }
96
+ if (d?.type !== "response_item" || !d.payload)
97
+ return [];
98
+ const p = d.payload;
99
+ if (p.type === "message" && (p.role === "user" || p.role === "assistant")) {
100
+ const text = textOf(p.content).trim();
101
+ if (!text || text.startsWith("<environment_context>"))
102
+ return [];
103
+ return [{ type: p.role === "user" ? "user" : "assistant", text }];
104
+ }
105
+ if (p.type === "function_call" || p.type === "custom_tool_call") {
106
+ return [
107
+ {
108
+ type: "tool",
109
+ id: typeof p.call_id === "string" ? p.call_id : undefined,
110
+ name: typeof p.name === "string" ? p.name : "tool",
111
+ summary: summaryOf(p),
112
+ args: argsOf(p),
113
+ },
114
+ ];
115
+ }
116
+ if (p.type === "function_call_output" || p.type === "custom_tool_call_output") {
117
+ const result = typeof p.output === "string"
118
+ ? p.output
119
+ : typeof p.content === "string"
120
+ ? p.content
121
+ : JSON.stringify(p.output ?? p.content ?? "");
122
+ return [
123
+ {
124
+ type: "tool_result",
125
+ id: typeof p.call_id === "string" ? p.call_id : undefined,
126
+ result: result.slice(0, 4000),
127
+ isError: Boolean(p.is_error),
128
+ },
129
+ ];
130
+ }
131
+ return [];
132
+ }
73
133
  /** Join the text of codex content parts (`input_text`/`output_text`). */
74
134
  function textOf(content) {
75
135
  if (typeof content === "string")
@@ -121,7 +181,7 @@ function argsOf(payload) {
121
181
  return undefined;
122
182
  }
123
183
  /** Find the rollout file whose `session_meta` matches both `threadId` and `cwd`. */
124
- function findRolloutFile(codexHome, cwd, threadId) {
184
+ export function findCodexRolloutFile(codexHome, cwd, threadId) {
125
185
  const root = join(codexHome, "sessions");
126
186
  if (!existsSync(root))
127
187
  return undefined;