@odla-ai/cli 0.25.9 → 0.25.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.
package/dist/bin.js CHANGED
@@ -3,7 +3,7 @@ import {
3
3
  exitCodeFor,
4
4
  redactSecrets,
5
5
  runCli
6
- } from "./chunk-HXUVCUEM.js";
6
+ } from "./chunk-FYSV7POZ.js";
7
7
 
8
8
  // src/bin.ts
9
9
  runCli().catch((err) => {
@@ -5990,7 +5990,7 @@ Usage:
5990
5990
  odla-ai pm handoff --app <id> [--json]
5991
5991
  odla-ai discuss groups [--json]
5992
5992
  odla-ai discuss list [--app <id>] [--q <text>] [--state open|resolved|all] [--json]
5993
- odla-ai discuss read <topic> [--json]
5993
+ odla-ai discuss read <topic> [--limit <n> --offset <n>] [--json]
5994
5994
  odla-ai discuss post --app <id> --subject "..." --body "..." [--markup "... @[Label](kind/id)"] [--mutation-id <id>]
5995
5995
  odla-ai discuss reply <topic> --body "..." [--markup "..."] [--mutation-id <id>]
5996
5996
  odla-ai discuss resolve <topic> [--reopen] [--mutation-id <id>]
@@ -8208,22 +8208,59 @@ async function discussList(ctx, parsed) {
8208
8208
  }
8209
8209
  });
8210
8210
  }
8211
- async function discussRead(ctx, id) {
8212
- const result = await request(
8213
- ctx,
8214
- "GET",
8215
- `/topics/${encodeURIComponent(id)}`
8216
- );
8217
- emit(ctx, result, () => {
8218
- ctx.out.log(`${result.topic.subject} [${state(result.topic)}] ${result.topic.appId ?? ""}`);
8219
- for (const post of result.posts) {
8220
- const who = post.authorKind === "bot" ? `${post.authorId} (agent)` : post.authorId;
8221
- ctx.out.log(`
8222
- \u2014 ${who}`);
8223
- ctx.out.log(bodyWithRefs(post));
8224
- for (const file of post.attachments ?? []) ctx.out.log(` [attachment] ${file.name} (${file.size} bytes)`);
8211
+ async function discussRead(ctx, id, parsed) {
8212
+ const requestedLimit = stringOpt(parsed.options.limit);
8213
+ const requestedOffset = stringOpt(parsed.options.offset);
8214
+ if (requestedLimit !== void 0 || requestedOffset !== void 0) {
8215
+ const query = new URLSearchParams({
8216
+ limit: requestedLimit ?? "200",
8217
+ offset: requestedOffset ?? "0"
8218
+ });
8219
+ const page = await request(ctx, "GET", `/topics/${encodeURIComponent(id)}?${query}`);
8220
+ emit(ctx, page, () => renderRead(ctx, page.topic, page.posts));
8221
+ return;
8222
+ }
8223
+ for (let scan = 0; scan < 3; scan++) {
8224
+ const posts = /* @__PURE__ */ new Map();
8225
+ let topic = null;
8226
+ let offset = 0;
8227
+ for (; ; ) {
8228
+ const page = await request(
8229
+ ctx,
8230
+ "GET",
8231
+ `/topics/${encodeURIComponent(id)}?limit=200&offset=${offset}`
8232
+ );
8233
+ topic = page.topic;
8234
+ for (const post of page.posts) posts.set(post.id, post);
8235
+ if (posts.size > 1e4) throw new Error("discuss read failed: conversation exceeds 10000 posts");
8236
+ if (!page.page?.hasMore) break;
8237
+ if (page.page.nextOffset === null || page.page.nextOffset <= offset) {
8238
+ throw new Error("discuss read failed: registry returned a non-advancing post page");
8239
+ }
8240
+ offset = page.page.nextOffset;
8225
8241
  }
8226
- });
8242
+ const ordered = [...posts.values()].sort(
8243
+ (a, b) => a.createdAt - b.createdAt || (a.id < b.id ? -1 : a.id > b.id ? 1 : 0)
8244
+ );
8245
+ const expected = Number.isInteger(topic.replyCount) ? topic.replyCount + 1 : ordered.length;
8246
+ if (ordered.length === expected) {
8247
+ const result = { topic, posts: ordered };
8248
+ emit(ctx, result, () => renderRead(ctx, result.topic, result.posts));
8249
+ return;
8250
+ }
8251
+ if (offset === 0) throw new Error("discuss read failed: registry did not provide forward post pages");
8252
+ }
8253
+ throw new Error("discuss read failed: conversation changed during every complete-read attempt");
8254
+ }
8255
+ function renderRead(ctx, topic, posts) {
8256
+ ctx.out.log(`${topic.subject} [${state(topic)}] ${topic.appId ?? ""}`);
8257
+ for (const post of posts) {
8258
+ const who = post.authorKind === "bot" ? `${post.authorId} (agent)` : post.authorId;
8259
+ ctx.out.log(`
8260
+ \u2014 ${who}`);
8261
+ ctx.out.log(bodyWithRefs(post));
8262
+ for (const file of post.attachments ?? []) ctx.out.log(` [attachment] ${file.name} (${file.size} bytes)`);
8263
+ }
8227
8264
  }
8228
8265
  async function discussPost(ctx, parsed) {
8229
8266
  const appId = stringOpt(parsed.options.app);
@@ -8550,7 +8587,7 @@ async function discussCommand(parsed, deps = {}) {
8550
8587
  case "topics":
8551
8588
  return discussList(ctx, parsed);
8552
8589
  case "read":
8553
- return discussRead(ctx, requireId(id, "read"));
8590
+ return discussRead(ctx, requireId(id, "read"), parsed);
8554
8591
  case "post":
8555
8592
  return discussPost(ctx, parsed);
8556
8593
  case "reply":
@@ -8877,6 +8914,7 @@ function statusVerdict(reads) {
8877
8914
  sourceHttp(reasons, "canary", reads.canary);
8878
8915
  sourceHttp(reasons, "collector", reads.collector);
8879
8916
  sourceHttp(reasons, "provider", reads.provider);
8917
+ sourceHttp(reasons, "provider", reads.providerHistory, "provider_history_");
8880
8918
  const liveStatus = String(reads.liveSync.body.status ?? "");
8881
8919
  if (liveStatus === "partial") {
8882
8920
  reasons.push({
@@ -8948,6 +8986,14 @@ function statusVerdict(reads) {
8948
8986
  severity: "degraded"
8949
8987
  });
8950
8988
  }
8989
+ const providerHistoryStatus = String(reads.providerHistory.body.status ?? "");
8990
+ if (providerHistoryStatus && providerHistoryStatus !== "observed" && providerHistoryStatus !== "no_data") {
8991
+ reasons.push({
8992
+ source: "provider",
8993
+ code: `provider_history_${providerHistoryStatus}`,
8994
+ severity: "degraded"
8995
+ });
8996
+ }
8951
8997
  return {
8952
8998
  status: reasons.some((reason) => reason.severity === "unhealthy") ? "unhealthy" : reasons.length > 0 ? "degraded" : "healthy",
8953
8999
  reasons
@@ -8967,11 +9013,11 @@ function collectorReason(read3, fallback) {
8967
9013
  );
8968
9014
  return typeof first?.code === "string" && first.code ? first.code : fallback;
8969
9015
  }
8970
- function sourceHttp(reasons, source, read3) {
9016
+ function sourceHttp(reasons, source, read3, prefix = "") {
8971
9017
  if (read3.httpStatus >= 200 && read3.httpStatus < 300) return;
8972
9018
  reasons.push({
8973
9019
  source,
8974
- code: `http_${read3.httpStatus}`,
9020
+ code: `${prefix}http_${read3.httpStatus}`,
8975
9021
  severity: read3.httpStatus >= 500 && read3.httpStatus !== 501 ? "unhealthy" : "degraded"
8976
9022
  });
8977
9023
  }
@@ -9013,7 +9059,7 @@ async function o11yCommand(parsed, deps = {}) {
9013
9059
  const base = `${cfg.platformUrl}/o11y/${encodeURIComponent(appId)}`;
9014
9060
  const query = new URLSearchParams({ env, minutes: String(minutes) });
9015
9061
  const headers = { authorization: `Bearer ${token}` };
9016
- const [application, liveSync, canary, collector, provider] = await Promise.all([
9062
+ const [application, liveSync, canary, collector, provider, providerHistory] = await Promise.all([
9017
9063
  read2(`${base}/metrics/red?${query}`, headers, doFetch),
9018
9064
  read2(
9019
9065
  `${base}/live-sync/health?${query}`,
@@ -9026,17 +9072,19 @@ async function o11yCommand(parsed, deps = {}) {
9026
9072
  doFetch
9027
9073
  ),
9028
9074
  read2(`${base}/self-health?${query}`, headers, doFetch),
9029
- read2(`${base}/provider/cloudflare?${query}`, headers, doFetch)
9075
+ read2(`${base}/provider/cloudflare?${query}`, headers, doFetch),
9076
+ read2(`${base}/provider/cloudflare/history?${query}`, headers, doFetch)
9030
9077
  ]);
9031
9078
  const verdict = statusVerdict({
9032
9079
  application,
9033
9080
  liveSync,
9034
9081
  canary,
9035
9082
  collector,
9036
- provider
9083
+ provider,
9084
+ providerHistory
9037
9085
  });
9038
9086
  const status = {
9039
- schemaVersion: 4,
9087
+ schemaVersion: 5,
9040
9088
  scope: { appId, env, minutes },
9041
9089
  observedAt: (/* @__PURE__ */ new Date()).toISOString(),
9042
9090
  verdict,
@@ -9044,7 +9092,8 @@ async function o11yCommand(parsed, deps = {}) {
9044
9092
  liveSync,
9045
9093
  canary,
9046
9094
  collector,
9047
- provider
9095
+ provider,
9096
+ providerHistory
9048
9097
  };
9049
9098
  if (parsed.options.json === true) {
9050
9099
  out.log(JSON.stringify(status, null, 2));
@@ -9104,6 +9153,11 @@ function printStatus2(status, out) {
9104
9153
  out.log(
9105
9154
  `cloudflare ${status.provider.httpStatus} ${String(status.provider.body.status ?? status.provider.body.error ?? "unavailable")} ${numeric3(providerMetrics.requests)} invocations ${numeric3(providerMetrics.errors)} runtime errors`
9106
9155
  );
9156
+ const providerPoints = Array.isArray(status.providerHistory.body.points) ? status.providerHistory.body.points.length : 0;
9157
+ const providerFreshness = record6(status.providerHistory.body.freshness) ? status.providerHistory.body.freshness : {};
9158
+ out.log(
9159
+ `cloudflare-history ${status.providerHistory.httpStatus} ${String(status.providerHistory.body.status ?? status.providerHistory.body.error ?? "unavailable")} ${providerPoints} snapshots ${optionalAge(providerFreshness.ageMs)} old`
9160
+ );
9107
9161
  out.log(
9108
9162
  `verdict ${status.verdict.status} ${status.verdict.reasons.map((reason) => `${reason.source}:${reason.code}`).join(", ") || "all required signals healthy"}`
9109
9163
  );
@@ -9122,6 +9176,10 @@ function numeric3(value) {
9122
9176
  function optionalNumeric(value) {
9123
9177
  return typeof value === "number" && Number.isFinite(value) ? `${value} ms` : "\u2014";
9124
9178
  }
9179
+ function optionalAge(value) {
9180
+ if (typeof value !== "number" || !Number.isFinite(value)) return "unknown";
9181
+ return `${Math.max(0, Math.round(value / 1e3))}s`;
9182
+ }
9125
9183
 
9126
9184
  // src/record.ts
9127
9185
  import { appendFileSync } from "fs";
@@ -10713,4 +10771,4 @@ export {
10713
10771
  exitCodeFor,
10714
10772
  runCli
10715
10773
  };
10716
- //# sourceMappingURL=chunk-HXUVCUEM.js.map
10774
+ //# sourceMappingURL=chunk-FYSV7POZ.js.map