opencode-codex-memory 0.6.0 → 0.6.2

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.
@@ -13,6 +13,7 @@ import { claudeImportStatus, resolveClaudeHome } from "../src/claude-import.js";
13
13
  import { formatDiagnosticLine, getDiscoveryStatus, getRecentDiagnostics, } from "../src/diagnostics.js";
14
14
  import { isPluginShuttingDown } from "../src/lifecycle.js";
15
15
  import { getAgentHealth } from "../src/agent-health.js";
16
+ import { activeProviderCapacityBackoffs } from "../src/ratelimit.js";
16
17
  function isSymlinkedRoot() {
17
18
  try {
18
19
  assertMemoryRootSafe();
@@ -200,7 +201,8 @@ function fmtWatermarkMs(ms) {
200
201
  }
201
202
  export const memory_inspect = tool({
202
203
  description: "Inspect the current memory state. Returns: stage1_outputs count, stage-1 job status " +
203
- "breakdown and recent errors, Phase 2 job status (including last error / retry time), " +
204
+ "breakdown, failure classes (backoff / provider_capacity / other_exhausted), recent errors, " +
205
+ "Phase 2 job status (including last error / retry time), " +
204
206
  "last discovery outcome, pipeline diagnostics, memory_summary token estimate " +
205
207
  "(on-disk; injection caps at ~2500), a listing of the memories directory, the " +
206
208
  "effective plugin options, and any configuration warnings. Use it to verify " +
@@ -244,9 +246,20 @@ export const memory_inspect = tool({
244
246
  const stage1StatusParts = Object.entries(stage1Jobs.by_status)
245
247
  .sort(([a], [b]) => a.localeCompare(b))
246
248
  .map(([s, c]) => `${s}=${c}`);
249
+ const fc = stage1Jobs.by_failure_class;
250
+ const failureParts = [
251
+ fc.backoff > 0 ? `backoff=${fc.backoff}` : "",
252
+ fc.provider_capacity > 0 ? `provider_capacity=${fc.provider_capacity}` : "",
253
+ fc.other_exhausted > 0 ? `other_exhausted=${fc.other_exhausted}` : "",
254
+ ].filter(Boolean);
247
255
  const stage1Lines = [
248
256
  `stage1_jobs: ${stage1StatusParts.length > 0 ? stage1StatusParts.join(" ") : "none"}`,
249
- ...stage1Jobs.recent_errors.map((e) => ` stage1_error ${e.session_id} (${e.status}): ${e.last_error.slice(0, 200)}${e.retry_at ? ` retry_at=${fmtUnixSec(e.retry_at)}` : ""}`),
257
+ `stage1_failures: ${failureParts.length > 0 ? failureParts.join(" ") : "none"}`,
258
+ ...stage1Jobs.recent_errors.map((e) => {
259
+ const klass = e.failure_class ? `, ${e.failure_class}` : "";
260
+ const retry = e.retry_at ? ` retry_at=${fmtUnixSec(e.retry_at)}` : "";
261
+ return ` stage1_error ${e.session_id} (${e.status}${klass}): ${e.last_error.slice(0, 200)}${retry}`;
262
+ }),
250
263
  ];
251
264
  const discovery = getDiscoveryStatus();
252
265
  const discoveryLine = discovery
@@ -260,6 +273,10 @@ export const memory_inspect = tool({
260
273
  `phase2_in_flight: ${isPhase2InFlight()}`,
261
274
  `plugin_shutting_down: ${isPluginShuttingDown()}`,
262
275
  ];
276
+ const capacityBackoffs = activeProviderCapacityBackoffs();
277
+ const capacityLines = capacityBackoffs.length > 0
278
+ ? capacityBackoffs.map((b) => `provider_capacity_backoff ${b.scope}: retry_at=${fmtUnixSec(b.retry_at)}`)
279
+ : ["provider_capacity_backoff: none"];
263
280
  const diagnostics = getRecentDiagnostics(12);
264
281
  const diagnosticLines = diagnostics.length > 0
265
282
  ? ["recent_events:", ...diagnostics.map((e) => ` ${formatDiagnosticLine(e)}`)]
@@ -271,6 +288,7 @@ export const memory_inspect = tool({
271
288
  discoveryLine,
272
289
  eligibilityHint,
273
290
  ...processLines,
291
+ ...capacityLines,
274
292
  `memory_summary_chars: ${summaryChars}`,
275
293
  `memory_summary_tokens_est: ${summaryTokens} (on disk; injection caps at ~2500)`,
276
294
  `memories_dir_entries: ${listing.length}`,
@@ -289,6 +307,7 @@ export const memory_inspect = tool({
289
307
  metadata: {
290
308
  stage1_count: outputs.length,
291
309
  stage1_jobs: stage1Jobs.by_status,
310
+ stage1_failures: stage1Jobs.by_failure_class,
292
311
  stage1_recent_errors: stage1Jobs.recent_errors,
293
312
  phase2_status: phase2?.status ?? null,
294
313
  phase2_last_error: phase2?.last_error ?? null,
@@ -296,6 +315,7 @@ export const memory_inspect = tool({
296
315
  phase2_last_attempt_finished_at: phase2?.finished_at ?? null,
297
316
  phase2_last_success_watermark: phase2?.last_success_watermark ?? null,
298
317
  phase2_last_success_finished_at: phase2?.success_finished_at ?? null,
318
+ provider_capacity_backoffs: capacityBackoffs,
299
319
  // Back-compat aliases used by earlier inspect consumers.
300
320
  phase2_last_finished_at: phase2?.success_finished_at ?? null,
301
321
  discovery,
@@ -20,7 +20,9 @@ export const memory_read = tool({
20
20
  }
21
21
  const stat = fs.lstatSync(fullPath);
22
22
  if (stat.isDirectory()) {
23
- const entries = fs.readdirSync(fullPath);
23
+ const entries = visibleEntries(fullPath)
24
+ .sort((a, b) => comparePathNames(a.name, b.name))
25
+ .map((e) => e.name);
24
26
  return {
25
27
  output: `Directory ${args.path}/\n` + entries.map((e) => `- ${e}`).join("\n") + "\n(use memory_list for sorted, typed listings)",
26
28
  metadata: { kind: "directory", entries },
@@ -194,7 +196,7 @@ function parseDateArg(value, endOfDay) {
194
196
  function collectSearchFiles(start, prefix) {
195
197
  const files = [];
196
198
  const walk = (dir, rel) => {
197
- const entries = visibleEntries(dir).sort((a, b) => a.name.localeCompare(b.name));
199
+ const entries = visibleEntries(dir).sort((a, b) => comparePathNames(a.name, b.name));
198
200
  for (const { name, isDir } of entries) {
199
201
  const abs = path.join(dir, name);
200
202
  const relPath = rel ? `${rel}/${name}` : name;
@@ -366,7 +368,21 @@ export const memory_search = tool({
366
368
  }
367
369
  const rangeLabel = timeFiltered ? ` in ${args.since ?? "..."}..${args.until ?? "..."}` : "";
368
370
  if (queries.length === 0) {
369
- const listing = files.slice(0, args.max_results).map((f) => {
371
+ let startIndex = 0;
372
+ if (args.cursor !== undefined) {
373
+ startIndex = Number.parseInt(args.cursor, 10);
374
+ if (!Number.isInteger(startIndex) || startIndex < 0 || String(startIndex) !== args.cursor.trim()) {
375
+ return { output: `memory_search error: invalid cursor "${args.cursor}" (must be a non-negative integer).` };
376
+ }
377
+ if (startIndex > files.length) {
378
+ return { output: `memory_search error: cursor ${startIndex} exceeds result count ${files.length}.` };
379
+ }
380
+ }
381
+ const endIndex = Math.min(startIndex + (args.max_results ?? SEARCH_MAX_RESULTS), files.length);
382
+ const page = files.slice(startIndex, endIndex);
383
+ const nextCursor = endIndex < files.length ? String(endIndex) : null;
384
+ const truncated = nextCursor !== null;
385
+ const listing = page.map((f) => {
370
386
  let content = "";
371
387
  try {
372
388
  content = readRegularFileNoFollow(f.abs).content.toString("utf8");
@@ -375,11 +391,25 @@ export const memory_search = tool({
375
391
  }
376
392
  return `${new Date(f.ts).toISOString()} ${f.rel} — ${firstContentLine(content)}`;
377
393
  });
378
- if (listing.length === 0)
394
+ if (files.length === 0)
379
395
  return { output: `No time-anchored memory files${rangeLabel}.` };
396
+ if (listing.length === 0) {
397
+ return {
398
+ output: `No memory files at cursor ${startIndex}${rangeLabel}.`,
399
+ metadata: { count: 0, next_cursor: nextCursor, truncated, since: args.since, until: args.until },
400
+ };
401
+ }
380
402
  return {
381
- output: `${listing.length} memory file(s)${rangeLabel}:\n${listing.join("\n")}`,
382
- metadata: { count: listing.length, since: args.since, until: args.until },
403
+ output: `${listing.length} of ${files.length} memory file(s)${rangeLabel}` +
404
+ `${truncated ? ` (more available; pass cursor=${nextCursor})` : ""}:\n` +
405
+ listing.join("\n"),
406
+ metadata: {
407
+ count: listing.length,
408
+ next_cursor: nextCursor,
409
+ truncated,
410
+ since: args.since,
411
+ until: args.until,
412
+ },
383
413
  };
384
414
  }
385
415
  const caseSensitive = args.case_sensitive ?? true;
@@ -402,7 +432,7 @@ export const memory_search = tool({
402
432
  continue; // binary, like codex's InvalidData skip
403
433
  searchFileContent(f, content.split(/\r?\n/), queries, preparedQueries, mode, args.line_count ?? 1, args.context_lines ?? 0, caseSensitive, normalized, all);
404
434
  }
405
- all.sort((a, b) => a.path.localeCompare(b.path) || a.match_line_number - b.match_line_number);
435
+ all.sort((a, b) => comparePathNames(a.path, b.path) || a.match_line_number - b.match_line_number);
406
436
  let startIndex = 0;
407
437
  if (args.cursor !== undefined) {
408
438
  startIndex = Number.parseInt(args.cursor, 10);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-codex-memory",
3
- "version": "0.6.0",
3
+ "version": "0.6.2",
4
4
  "description": "Persistent memory plugin for opencode — ports codex's two-phase memory system (extraction → consolidation → injection → citation feedback)",
5
5
  "type": "module",
6
6
  "main": "./dist/src/index.js",