@odla-ai/cli 0.25.8 → 0.25.9

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-PM5FBL3S.js";
6
+ } from "./chunk-HXUVCUEM.js";
7
7
 
8
8
  // src/bin.ts
9
9
  runCli().catch((err) => {
@@ -4035,13 +4035,13 @@ async function createConversionRegistry(config) {
4035
4035
  policies.set(policy.conversionId, Object.freeze(policy));
4036
4036
  }
4037
4037
  const outputCounts = /* @__PURE__ */ new Map();
4038
- const get2 = (id, kind) => {
4038
+ const get = (id, kind) => {
4039
4039
  const policy = policies.get(id);
4040
4040
  if (!policy || policy.output.kind !== kind) throw new CamelError("conversion_rejected", "Conversion policy is missing or has the wrong output kind.");
4041
4041
  return policy;
4042
4042
  };
4043
4043
  const checked = (source, id, kind) => {
4044
- const policy = get2(id, kind);
4044
+ const policy = get(id, kind);
4045
4045
  if (utf8Length(source.value) > policy.maximumSourceBytes) throw new CamelError("limit_exceeded", "Unsafe conversion input exceeds its byte bound.");
4046
4046
  return policy;
4047
4047
  };
@@ -5995,7 +5995,7 @@ Usage:
5995
5995
  odla-ai discuss reply <topic> --body "..." [--markup "..."] [--mutation-id <id>]
5996
5996
  odla-ai discuss resolve <topic> [--reopen] [--mutation-id <id>]
5997
5997
  odla-ai discuss who --q <text> [--app <id>] [--kinds user,pm:task] [--json]
5998
- odla-ai discuss watch [<topic>] [--by <authorId>] [--self <authorId>] [--interval <s>] [--timeout <s>] [--json]
5998
+ odla-ai discuss watch [<topic>] [--cursor <cursor>] [--by <authorId>] [--self <authorId>] [--interval <s>] [--timeout <s>] [--json|--jsonl]
5999
5999
  odla-ai agent jobs [--env dev] [--state pending|running|succeeded|dead_letter] [--limit 50] [--email <email>] [--json]
6000
6000
  odla-ai agent retry <job-id> [--env dev] [--email <email>] [--json]
6001
6001
  odla-ai o11y status [--app <id>] [--env prod] [--minutes 60] [--json]
@@ -8272,61 +8272,214 @@ async function discussWho(ctx, parsed) {
8272
8272
  });
8273
8273
  }
8274
8274
 
8275
+ // src/discuss-watch-http.ts
8276
+ var WatchRequestError = class extends Error {
8277
+ constructor(message2, retryable, status, code) {
8278
+ super(message2);
8279
+ this.retryable = retryable;
8280
+ this.status = status;
8281
+ this.code = code;
8282
+ this.name = "WatchRequestError";
8283
+ }
8284
+ retryable;
8285
+ status;
8286
+ code;
8287
+ };
8288
+ var WatchCheckpointError = class extends Error {
8289
+ constructor(cursor, streamId) {
8290
+ super("discussion cursor requires a new checkpoint");
8291
+ this.cursor = cursor;
8292
+ this.streamId = streamId;
8293
+ this.name = "WatchCheckpointError";
8294
+ }
8295
+ cursor;
8296
+ streamId;
8297
+ code = "checkpoint_required";
8298
+ };
8299
+ var WatchRemoteError = class extends Error {
8300
+ constructor(cursor, cause) {
8301
+ super("discussion watch dependency remained unavailable", { cause });
8302
+ this.cursor = cursor;
8303
+ this.name = "WatchRemoteError";
8304
+ }
8305
+ cursor;
8306
+ code = "remote_unavailable";
8307
+ };
8308
+ var WatchTimeoutError = class extends Error {
8309
+ constructor(cursor) {
8310
+ super("nothing new before the timeout");
8311
+ this.cursor = cursor;
8312
+ this.name = "WatchTimeoutError";
8313
+ }
8314
+ cursor;
8315
+ code = "watch_timeout";
8316
+ };
8317
+ function requestPath(topicId, app, cursor) {
8318
+ const params = new URLSearchParams();
8319
+ if (topicId) params.set("topic", topicId);
8320
+ if (app) params.set("app", app);
8321
+ if (cursor) params.set("cursor", cursor);
8322
+ return `/watch?${params}`;
8323
+ }
8324
+ async function getWatchPage(ctx, path) {
8325
+ let res;
8326
+ try {
8327
+ res = await ctx.doFetch(`${ctx.platformUrl}/registry/discuss${path}`, {
8328
+ headers: { authorization: `Bearer ${ctx.token}` }
8329
+ });
8330
+ } catch (error) {
8331
+ throw new WatchRequestError(
8332
+ `discuss watch request failed: ${error instanceof Error ? error.message : String(error)}`,
8333
+ true
8334
+ );
8335
+ }
8336
+ const data = await res.json().catch(() => ({}));
8337
+ if (res.status === 409 && data.code === "checkpoint_required") {
8338
+ throw new WatchCheckpointError(data.cursor, data.streamId);
8339
+ }
8340
+ if (!res.ok) {
8341
+ throw new WatchRequestError(
8342
+ `discuss watch failed: ${data.error ?? `registry returned ${res.status}`}`,
8343
+ res.status === 429 || res.status >= 500,
8344
+ res.status,
8345
+ data.code ?? (res.status === 401 || res.status === 403 ? "auth_failed" : void 0)
8346
+ );
8347
+ }
8348
+ return data;
8349
+ }
8350
+
8275
8351
  // src/discuss-watch.ts
8276
8352
  var DEFAULT_INTERVAL_MS = 15e3;
8277
- var DEFAULT_TIMEOUT_MS = 15 * 6e4;
8353
+ var MAX_CONSECUTIVE_FAILURES = 5;
8354
+ var MAX_BACKOFF_MS = 3e4;
8278
8355
  function numberOpt2(parsed, flag, fallback) {
8279
8356
  const raw = stringOpt(parsed.options[flag]);
8280
8357
  const value = raw == null ? NaN : Number(raw);
8281
8358
  return Number.isFinite(value) && value > 0 ? value : fallback;
8282
8359
  }
8283
- async function get(ctx, path) {
8284
- const res = await ctx.doFetch(`${ctx.platformUrl}/registry/discuss${path}`, {
8285
- headers: { authorization: `Bearer ${ctx.token}` }
8286
- });
8287
- const data = await res.json().catch(() => ({}));
8288
- if (!res.ok) throw new Error(`discuss watch failed: ${data.error ?? `registry returned ${res.status}`}`);
8289
- return data;
8360
+ function jsonl(ctx, parsed, value) {
8361
+ if (parsed.options.jsonl === true) ctx.out.log(JSON.stringify({ v: 1, ...value }));
8290
8362
  }
8291
8363
  async function discussWatch(ctx, topicId, parsed) {
8364
+ if (ctx.json && parsed.options.jsonl === true) throw new Error("--json and --jsonl cannot be combined");
8292
8365
  const sleep = ctx.sleep ?? ((ms) => new Promise((resolve11) => setTimeout(resolve11, ms)));
8293
8366
  const now = ctx.now ?? Date.now;
8294
- const intervalMs = numberOpt2(parsed, "interval", DEFAULT_INTERVAL_MS / 1e3) * 1e3;
8295
- const timeoutMs = numberOpt2(parsed, "timeout", DEFAULT_TIMEOUT_MS / 1e3) * 1e3;
8367
+ const intervalMs = (numberOpt2(parsed, "interval", DEFAULT_INTERVAL_MS / 1e3) ?? DEFAULT_INTERVAL_MS / 1e3) * 1e3;
8368
+ const timeoutSeconds = numberOpt2(parsed, "timeout");
8369
+ const deadline = timeoutSeconds === void 0 ? void 0 : now() + timeoutSeconds * 1e3;
8296
8370
  const by = stringOpt(parsed.options.by);
8297
8371
  const self = stringOpt(parsed.options.self);
8298
- const deadline = now() + timeoutMs;
8299
- const fresh = (post) => (!by || post.authorId === by) && (!self || post.authorId !== self);
8300
- let seen = /* @__PURE__ */ new Set();
8301
- let since = now();
8302
- if (topicId) {
8303
- const first = await get(ctx, `/topics/${encodeURIComponent(topicId)}`);
8304
- seen = new Set(first.posts.map((post) => post.id));
8305
- }
8372
+ const app = stringOpt(parsed.options.app);
8373
+ let cursor = stringOpt(parsed.options.cursor);
8374
+ let firstSuccess = true;
8375
+ let consecutiveFailures = 0;
8306
8376
  for (; ; ) {
8307
- if (topicId) {
8308
- const { posts } = await get(ctx, `/topics/${encodeURIComponent(topicId)}`);
8309
- const added = posts.filter((post) => !seen.has(post.id) && fresh(post));
8310
- if (added.length > 0) return report2(ctx, { found: true, posts: added });
8311
- for (const post of posts) seen.add(post.id);
8312
- } else {
8313
- const app = stringOpt(parsed.options.app);
8314
- const { topics } = await get(
8315
- ctx,
8316
- `/topics?state=all${app ? `&app=${encodeURIComponent(app)}` : ""}`
8317
- );
8318
- const active = topics.filter((topic) => topic.lastActivityAt > since);
8319
- if (active.length > 0) return report2(ctx, { found: true, topics: active });
8320
- since = now();
8377
+ let page;
8378
+ try {
8379
+ page = await getWatchPage(ctx, requestPath(topicId, app, cursor));
8380
+ consecutiveFailures = 0;
8381
+ } catch (error) {
8382
+ if (!(error instanceof WatchRequestError) || !error.retryable) {
8383
+ if (error instanceof WatchCheckpointError) {
8384
+ jsonl(ctx, parsed, {
8385
+ type: "status",
8386
+ state: "checkpoint_required",
8387
+ retryable: false,
8388
+ ...error.cursor ? { cursor: error.cursor } : {},
8389
+ ...error.streamId ? { streamId: error.streamId } : {}
8390
+ });
8391
+ }
8392
+ throw error;
8393
+ }
8394
+ consecutiveFailures++;
8395
+ jsonl(ctx, parsed, {
8396
+ type: "status",
8397
+ state: "degraded",
8398
+ retryable: true,
8399
+ attempt: consecutiveFailures,
8400
+ ...cursor ? { cursor } : {},
8401
+ ...error.status ? { status: error.status } : {}
8402
+ });
8403
+ if (consecutiveFailures >= MAX_CONSECUTIVE_FAILURES) {
8404
+ throw new WatchRemoteError(cursor, error);
8405
+ }
8406
+ if (deadline !== void 0 && now() >= deadline) {
8407
+ const result = report2(ctx, parsed, { found: false, cursor: cursor ?? "" });
8408
+ throw new WatchTimeoutError(result.cursor);
8409
+ }
8410
+ const base = Math.min(intervalMs, 1e3);
8411
+ const backoff = Math.min(MAX_BACKOFF_MS, base * 2 ** (consecutiveFailures - 1));
8412
+ await sleep(deadline === void 0 ? backoff : Math.min(backoff, Math.max(0, deadline - now())));
8413
+ continue;
8414
+ }
8415
+ cursor = page.cursor;
8416
+ const baseline = firstSuccess && page.events.length === 0;
8417
+ if (baseline) {
8418
+ jsonl(ctx, parsed, {
8419
+ type: "checkpoint",
8420
+ streamId: page.streamId,
8421
+ cursor,
8422
+ serverTime: page.serverTime
8423
+ });
8321
8424
  }
8322
- if (now() >= deadline) return report2(ctx, { found: false });
8323
- await sleep(Math.min(intervalMs, Math.max(0, deadline - now())));
8425
+ firstSuccess = false;
8426
+ const matching = page.events.filter((event) => {
8427
+ if (topicId && (event.type !== "message" || event.action !== "created")) return false;
8428
+ return (!by || event.actor.id === by) && (!self || event.actor.id !== self);
8429
+ });
8430
+ for (const event of matching) {
8431
+ jsonl(ctx, parsed, {
8432
+ type: "event",
8433
+ streamId: page.streamId,
8434
+ eventId: event.id,
8435
+ cursor: event.cursor,
8436
+ event
8437
+ });
8438
+ }
8439
+ if (matching.length > 0) {
8440
+ jsonl(ctx, parsed, {
8441
+ type: "checkpoint",
8442
+ streamId: page.streamId,
8443
+ cursor,
8444
+ serverTime: page.serverTime
8445
+ });
8446
+ const posts = topicId ? matching.filter((event) => event.type === "message").map((event) => event.payload) : void 0;
8447
+ const topics = topicId ? void 0 : matching.filter((event) => event.type === "activity").map((event) => event.payload);
8448
+ return report2(ctx, parsed, {
8449
+ found: true,
8450
+ cursor,
8451
+ events: matching,
8452
+ ...posts && posts.length > 0 ? { posts } : {},
8453
+ ...topics && topics.length > 0 ? { topics } : {}
8454
+ });
8455
+ }
8456
+ if (page.events.length > 0) {
8457
+ jsonl(ctx, parsed, {
8458
+ type: "checkpoint",
8459
+ streamId: page.streamId,
8460
+ cursor,
8461
+ serverTime: page.serverTime
8462
+ });
8463
+ } else if (!baseline) {
8464
+ jsonl(ctx, parsed, {
8465
+ type: "heartbeat",
8466
+ streamId: page.streamId,
8467
+ cursor,
8468
+ serverTime: page.serverTime
8469
+ });
8470
+ }
8471
+ if (page.hasMore) continue;
8472
+ if (deadline !== void 0 && now() >= deadline) {
8473
+ return report2(ctx, parsed, { found: false, cursor });
8474
+ }
8475
+ const wait2 = deadline === void 0 ? intervalMs : Math.min(intervalMs, Math.max(0, deadline - now()));
8476
+ await sleep(wait2);
8324
8477
  }
8325
8478
  }
8326
- function report2(ctx, result) {
8479
+ function report2(ctx, parsed, result) {
8327
8480
  if (ctx.json) {
8328
8481
  ctx.out.log(JSON.stringify(result, null, 2));
8329
- } else if (result.found) {
8482
+ } else if (parsed.options.jsonl !== true && result.found) {
8330
8483
  for (const post of result.posts ?? []) {
8331
8484
  const who = post.authorKind === "bot" ? `${post.authorId} (agent)` : post.authorId;
8332
8485
  ctx.out.log(`\u2014 ${who}
@@ -8338,13 +8491,6 @@ ${post.body}`);
8338
8491
  }
8339
8492
  return result;
8340
8493
  }
8341
- var WatchTimeoutError = class extends Error {
8342
- code = "watch_timeout";
8343
- constructor() {
8344
- super("nothing new before the timeout");
8345
- this.name = "WatchTimeoutError";
8346
- }
8347
- };
8348
8494
 
8349
8495
  // src/discuss-command.ts
8350
8496
  var ALLOWED = [
@@ -8366,6 +8512,8 @@ var ALLOWED = [
8366
8512
  "self",
8367
8513
  "interval",
8368
8514
  "timeout",
8515
+ "cursor",
8516
+ "jsonl",
8369
8517
  "mutation-id"
8370
8518
  ];
8371
8519
  function requireId(id, action) {
@@ -8413,7 +8561,7 @@ async function discussCommand(parsed, deps = {}) {
8413
8561
  return discussWho(ctx, parsed);
8414
8562
  case "watch": {
8415
8563
  const result = await discussWatch(ctx, id, parsed);
8416
- if (!result.found) throw new WatchTimeoutError();
8564
+ if (!result.found) throw new WatchTimeoutError(result.cursor);
8417
8565
  return;
8418
8566
  }
8419
8567
  default:
@@ -10377,7 +10525,12 @@ async function securityStatus(parsed, dependencies) {
10377
10525
  // src/cli.ts
10378
10526
  function exitCodeFor(err) {
10379
10527
  const code = err?.code;
10380
- return code === "handshake_pending" || code === "watch_timeout" ? 75 : 1;
10528
+ if (code === "handshake_pending" || code === "watch_timeout") return 75;
10529
+ if (code === "checkpoint_required") return 3;
10530
+ if (code === "remote_unavailable") return 6;
10531
+ if (code === "auth_failed") return 5;
10532
+ if (code === "invalid_cursor") return 2;
10533
+ return 1;
10381
10534
  }
10382
10535
  async function runCli(argv = process.argv.slice(2), dependencies = {}) {
10383
10536
  const runtime = {
@@ -10560,4 +10713,4 @@ export {
10560
10713
  exitCodeFor,
10561
10714
  runCli
10562
10715
  };
10563
- //# sourceMappingURL=chunk-PM5FBL3S.js.map
10716
+ //# sourceMappingURL=chunk-HXUVCUEM.js.map