@odla-ai/cli 0.25.9 → 0.25.10

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.
package/README.md CHANGED
@@ -238,6 +238,14 @@ advancing the cursor. A restore or truncated history exits 3 and emits an
238
238
  explicit replacement checkpoint; an explicit timeout exits 75, and an
239
239
  exhausted remote retry exits 6.
240
240
 
241
+ `discuss read <topic> --json` follows the server's bounded forward post pages
242
+ before emitting one complete document. A long-lived agent can therefore watch
243
+ an event and then read the full topic rather than receiving an apparently
244
+ successful response capped to its oldest 200 posts. Use `--limit <n>
245
+ --offset <n>` to request one bounded page instead; complete reads fail closed
246
+ after 10,000 posts or three continuously changing scans rather than consuming
247
+ memory forever or claiming an inconsistent result.
248
+
241
249
  `code connect` is the first-party personal Code terminal runtime. Run the exact
242
250
  command copied from **Studio → Code → Terminal** while your shell is in the
243
251
  local Git checkout. `--platform`, `--app-id`, and `--env` make the requested
package/dist/bin.cjs CHANGED
@@ -6563,7 +6563,7 @@ Usage:
6563
6563
  odla-ai pm handoff --app <id> [--json]
6564
6564
  odla-ai discuss groups [--json]
6565
6565
  odla-ai discuss list [--app <id>] [--q <text>] [--state open|resolved|all] [--json]
6566
- odla-ai discuss read <topic> [--json]
6566
+ odla-ai discuss read <topic> [--limit <n> --offset <n>] [--json]
6567
6567
  odla-ai discuss post --app <id> --subject "..." --body "..." [--markup "... @[Label](kind/id)"] [--mutation-id <id>]
6568
6568
  odla-ai discuss reply <topic> --body "..." [--markup "..."] [--mutation-id <id>]
6569
6569
  odla-ai discuss resolve <topic> [--reopen] [--mutation-id <id>]
@@ -7409,22 +7409,59 @@ async function discussList(ctx, parsed) {
7409
7409
  }
7410
7410
  });
7411
7411
  }
7412
- async function discussRead(ctx, id) {
7413
- const result = await request(
7414
- ctx,
7415
- "GET",
7416
- `/topics/${encodeURIComponent(id)}`
7417
- );
7418
- emit(ctx, result, () => {
7419
- ctx.out.log(`${result.topic.subject} [${state(result.topic)}] ${result.topic.appId ?? ""}`);
7420
- for (const post of result.posts) {
7421
- const who = post.authorKind === "bot" ? `${post.authorId} (agent)` : post.authorId;
7422
- ctx.out.log(`
7423
- \u2014 ${who}`);
7424
- ctx.out.log(bodyWithRefs(post));
7425
- for (const file of post.attachments ?? []) ctx.out.log(` [attachment] ${file.name} (${file.size} bytes)`);
7412
+ async function discussRead(ctx, id, parsed) {
7413
+ const requestedLimit = stringOpt(parsed.options.limit);
7414
+ const requestedOffset = stringOpt(parsed.options.offset);
7415
+ if (requestedLimit !== void 0 || requestedOffset !== void 0) {
7416
+ const query = new URLSearchParams({
7417
+ limit: requestedLimit ?? "200",
7418
+ offset: requestedOffset ?? "0"
7419
+ });
7420
+ const page = await request(ctx, "GET", `/topics/${encodeURIComponent(id)}?${query}`);
7421
+ emit(ctx, page, () => renderRead(ctx, page.topic, page.posts));
7422
+ return;
7423
+ }
7424
+ for (let scan = 0; scan < 3; scan++) {
7425
+ const posts = /* @__PURE__ */ new Map();
7426
+ let topic = null;
7427
+ let offset = 0;
7428
+ for (; ; ) {
7429
+ const page = await request(
7430
+ ctx,
7431
+ "GET",
7432
+ `/topics/${encodeURIComponent(id)}?limit=200&offset=${offset}`
7433
+ );
7434
+ topic = page.topic;
7435
+ for (const post of page.posts) posts.set(post.id, post);
7436
+ if (posts.size > 1e4) throw new Error("discuss read failed: conversation exceeds 10000 posts");
7437
+ if (!page.page?.hasMore) break;
7438
+ if (page.page.nextOffset === null || page.page.nextOffset <= offset) {
7439
+ throw new Error("discuss read failed: registry returned a non-advancing post page");
7440
+ }
7441
+ offset = page.page.nextOffset;
7426
7442
  }
7427
- });
7443
+ const ordered = [...posts.values()].sort(
7444
+ (a, b) => a.createdAt - b.createdAt || (a.id < b.id ? -1 : a.id > b.id ? 1 : 0)
7445
+ );
7446
+ const expected = Number.isInteger(topic.replyCount) ? topic.replyCount + 1 : ordered.length;
7447
+ if (ordered.length === expected) {
7448
+ const result = { topic, posts: ordered };
7449
+ emit(ctx, result, () => renderRead(ctx, result.topic, result.posts));
7450
+ return;
7451
+ }
7452
+ if (offset === 0) throw new Error("discuss read failed: registry did not provide forward post pages");
7453
+ }
7454
+ throw new Error("discuss read failed: conversation changed during every complete-read attempt");
7455
+ }
7456
+ function renderRead(ctx, topic, posts) {
7457
+ ctx.out.log(`${topic.subject} [${state(topic)}] ${topic.appId ?? ""}`);
7458
+ for (const post of posts) {
7459
+ const who = post.authorKind === "bot" ? `${post.authorId} (agent)` : post.authorId;
7460
+ ctx.out.log(`
7461
+ \u2014 ${who}`);
7462
+ ctx.out.log(bodyWithRefs(post));
7463
+ for (const file of post.attachments ?? []) ctx.out.log(` [attachment] ${file.name} (${file.size} bytes)`);
7464
+ }
7428
7465
  }
7429
7466
  async function discussPost(ctx, parsed) {
7430
7467
  const appId = stringOpt(parsed.options.app);
@@ -7751,7 +7788,7 @@ async function discussCommand(parsed, deps = {}) {
7751
7788
  case "topics":
7752
7789
  return discussList(ctx, parsed);
7753
7790
  case "read":
7754
- return discussRead(ctx, requireId(id, "read"));
7791
+ return discussRead(ctx, requireId(id, "read"), parsed);
7755
7792
  case "post":
7756
7793
  return discussPost(ctx, parsed);
7757
7794
  case "reply":