codeep 2.23.0 → 2.24.0

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.
@@ -259,7 +259,7 @@ export async function summarizeEarlierHistory(history, maxChars = 16000) {
259
259
  if (dropped.length === 0)
260
260
  return '';
261
261
  const key = createHash('sha256')
262
- .update(dropped.map(m => `${m.role}:${m.content}`).join(''))
262
+ .update(dropped.map(m => `${m.role}:${m.content}`).join('\u0000'))
263
263
  .digest('hex');
264
264
  const cached = earlierSummaryCache.get(key);
265
265
  if (cached)
@@ -507,7 +507,12 @@ additionalTools, runtime) {
507
507
  if (errorText.includes('tools') || errorText.includes('function') || response.status === 400) {
508
508
  return await agentChatFallback(messages, systemPrompt, onChunk, abortSignal, dynamicTimeout, additionalTools, runtime);
509
509
  }
510
- throw new Error(`API error: ${response.status} - ${errorText}`);
510
+ // ApiError, not Error: runAgent's retry loop refuses to retry a 4xx by
511
+ // checking `err instanceof ApiError && err.status`. A bare Error made
512
+ // that guard silently inapplicable, so an expired key was retried once
513
+ // per iteration until the budget ran out and the run reported "Exceeded
514
+ // maximum of N iterations" — the one explanation unrelated to the cause.
515
+ throw new ApiError(`API error: ${response.status} - ${errorText}`, response.status);
511
516
  }
512
517
  if (useStreaming && response.body) {
513
518
  if (protocol === 'openai')
@@ -658,7 +663,9 @@ export async function agentChatFallback(messages, systemPrompt, onChunk, abortSi
658
663
  });
659
664
  if (!response.ok) {
660
665
  const error = await response.text();
661
- throw new Error(`API error: ${response.status} - ${error}`);
666
+ // Same reasoning as the native-tools path: the status has to survive, or
667
+ // the caller cannot tell "your key is wrong" from "the network hiccuped".
668
+ throw new ApiError(`API error: ${response.status} - ${error}`, response.status);
662
669
  }
663
670
  let content;
664
671
  if (onChunk && response.body) {
@@ -1,4 +1,4 @@
1
- import { buildFixPlan, summariseFixPlan } from './reviewFix.js';
1
+ import { buildFixPlan, summariseFixPlan, describeAgentActivity } from './reviewFix.js';
2
2
  // Headless `codeep review` — a non-interactive entry point around the
3
3
  // deterministic reviewer in codeReview.ts. No API key, no TUI: it scans, prints
4
4
  // a report (markdown or JSON), and exits non-zero when issues at/above a chosen
@@ -214,6 +214,18 @@ function defaultDeps() {
214
214
  */
215
215
  async function runFixPlan(plan, context) {
216
216
  try {
217
+ // Populate the key cache before anything asks for it. `getApiKey` is
218
+ // synchronous and reads the cache alone — it does not consult the
219
+ // environment — so without this every request goes out with an empty
220
+ // bearer token and the provider answers 401. The AI review path next door
221
+ // has always done this; the fix path never did, which is why a CI run with
222
+ // a perfectly good key in the environment spent its whole iteration budget
223
+ // being rejected.
224
+ const { loadAllApiKeys, isConfigured } = await import('../config/index.js');
225
+ await loadAllApiKeys();
226
+ if (!isConfigured()) {
227
+ return `${summariseFixPlan(plan)} No API key is configured for the current provider, so the fix agent could not start.`;
228
+ }
217
229
  const { runAgent } = await import('./agent.js');
218
230
  // No cast here. `as never` on this call once hid the fact that
219
231
  // personalityOverride did not exist, which would have run the CI fix with
@@ -230,11 +242,12 @@ async function runFixPlan(plan, context) {
230
242
  const edited = new Set(result.actions
231
243
  .filter(a => a.type === 'write' || a.type === 'edit')
232
244
  .map(a => a.target));
245
+ const activity = describeAgentActivity(result.actions);
233
246
  if (!result.success) {
234
- return `${summariseFixPlan(plan)} The run did not finish: ${result.error ?? 'unknown error'}.`;
247
+ return `${summariseFixPlan(plan)} The run did not finish: ${result.error ?? 'unknown error'}. ${activity}`;
235
248
  }
236
249
  if (edited.size === 0) {
237
- return `${summariseFixPlan(plan)} Nothing was changed — the agent judged the findings not mechanically fixable.`;
250
+ return `${summariseFixPlan(plan)} Nothing was changed. ${activity}`;
238
251
  }
239
252
  return `${summariseFixPlan(plan)} Edited ${edited.size} file${edited.size === 1 ? '' : 's'}: ${[...edited].join(', ')}.`;
240
253
  }
@@ -63,3 +63,21 @@ export declare function buildFixPlan(issues: ReviewIssue[], options?: FixPlanOpt
63
63
  export declare function formatFixPrompt(issues: ReviewIssue[]): string;
64
64
  /** A one-line summary for the pull request body the action opens. */
65
65
  export declare function summariseFixPlan(plan: FixPlan): string;
66
+ /**
67
+ * What the agent actually did, in one line.
68
+ *
69
+ * A fix that changes nothing is the hardest outcome to act on, because the
70
+ * summary that reports it — "the run did not finish", "nothing was changed" —
71
+ * says what did not happen and never what did. Debugging one such run through
72
+ * CI cost two releases and forty minutes of guessing at whether the agent
73
+ * could not find the file, could not write, or was being refused a tool.
74
+ *
75
+ * Counting the action log answers that in the message itself. Failures are
76
+ * called out separately from successes, and one failing detail is quoted,
77
+ * because a refusal reason is usually the whole explanation.
78
+ */
79
+ export declare function describeAgentActivity(actions: {
80
+ type: string;
81
+ result: string;
82
+ details?: string;
83
+ }[]): string;
@@ -139,3 +139,35 @@ export function summariseFixPlan(plan) {
139
139
  ].filter(Boolean);
140
140
  return `Attempting ${parts.join(' and ')} across ${plan.files.length} file${plan.files.length === 1 ? '' : 's'}.`;
141
141
  }
142
+ /**
143
+ * What the agent actually did, in one line.
144
+ *
145
+ * A fix that changes nothing is the hardest outcome to act on, because the
146
+ * summary that reports it — "the run did not finish", "nothing was changed" —
147
+ * says what did not happen and never what did. Debugging one such run through
148
+ * CI cost two releases and forty minutes of guessing at whether the agent
149
+ * could not find the file, could not write, or was being refused a tool.
150
+ *
151
+ * Counting the action log answers that in the message itself. Failures are
152
+ * called out separately from successes, and one failing detail is quoted,
153
+ * because a refusal reason is usually the whole explanation.
154
+ */
155
+ export function describeAgentActivity(actions) {
156
+ if (actions.length === 0)
157
+ return 'It made no tool calls at all.';
158
+ const byType = new Map();
159
+ for (const action of actions) {
160
+ const tally = byType.get(action.type) ?? { ok: 0, failed: 0 };
161
+ if (action.result === 'error')
162
+ tally.failed++;
163
+ else
164
+ tally.ok++;
165
+ byType.set(action.type, tally);
166
+ }
167
+ const parts = [...byType.entries()]
168
+ .sort((a, b) => (b[1].ok + b[1].failed) - (a[1].ok + a[1].failed))
169
+ .map(([type, { ok, failed }]) => (failed ? `${ok + failed} ${type} (${failed} failed)` : `${ok} ${type}`));
170
+ const firstFailure = actions.find(a => a.result === 'error' && a.details);
171
+ const why = firstFailure ? ` First failure: ${firstFailure.details.slice(0, 200)}` : '';
172
+ return `It made ${actions.length} tool call${actions.length === 1 ? '' : 's'}: ${parts.join(', ')}.${why}`;
173
+ }
package/dist/version.d.ts CHANGED
@@ -1 +1 @@
1
- export declare const VERSION = "2.23.0";
1
+ export declare const VERSION = "2.24.0";
package/dist/version.js CHANGED
@@ -1,4 +1,4 @@
1
1
  // AUTO-GENERATED by scripts/gen-version.js — do not edit by hand.
2
2
  // Baked from package.json at build time so the bun-compiled binary reports
3
3
  // the right version (it has no package.json on disk to read at runtime).
4
- export const VERSION = '2.23.0';
4
+ export const VERSION = '2.24.0';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codeep",
3
- "version": "2.23.0",
3
+ "version": "2.24.0",
4
4
  "description": "AI-powered coding assistant built for the terminal. Multiple LLM providers, project-aware context, and a seamless development workflow.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",