@odla-ai/cli 0.25.7 → 0.25.8

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/index.cjs CHANGED
@@ -1080,11 +1080,6 @@ async function adminCommand(parsed) {
1080
1080
  });
1081
1081
  }
1082
1082
 
1083
- // src/app-export.ts
1084
- var import_node_fs5 = require("fs");
1085
- var import_node_stream = require("stream");
1086
- var import_promises = require("stream/promises");
1087
-
1088
1083
  // src/config.ts
1089
1084
  var import_node_fs4 = require("fs");
1090
1085
  var import_node_path4 = require("path");
@@ -1393,7 +1388,90 @@ function bothTenants(cfg) {
1393
1388
  return { sandbox: (0, import_apps.tenantIdFor)(cfg.app.id, "dev"), live: (0, import_apps.tenantIdFor)(cfg.app.id, "prod") };
1394
1389
  }
1395
1390
 
1391
+ // src/agent-command.ts
1392
+ async function agentCommand(parsed, deps = {}) {
1393
+ const action = parsed.positionals[1];
1394
+ if (action !== "jobs" && action !== "retry") {
1395
+ throw new Error(`unknown agent action "${action ?? ""}". Try "odla-ai agent jobs --json".`);
1396
+ }
1397
+ assertArgs(parsed, ["config", "env", "state", "limit", "json", "token", "email"], action === "jobs" ? 2 : 3);
1398
+ if (action === "retry" && (parsed.options.state !== void 0 || parsed.options.limit !== void 0)) {
1399
+ throw new Error('--state and --limit are supported only by "agent jobs"');
1400
+ }
1401
+ const cfg = await loadProjectConfig(stringOpt(parsed.options.config) ?? "odla.config.mjs");
1402
+ const { env, tenant } = resolveTenant(cfg, stringOpt(parsed.options.env));
1403
+ const doFetch = deps.fetch ?? fetch;
1404
+ const out = deps.stdout ?? console;
1405
+ const credential2 = await getDeveloperToken(
1406
+ cfg,
1407
+ {
1408
+ configPath: cfg.configPath,
1409
+ token: stringOpt(parsed.options.token),
1410
+ email: stringOpt(parsed.options.email),
1411
+ open: false
1412
+ },
1413
+ doFetch,
1414
+ out
1415
+ );
1416
+ const base = `${cfg.dbEndpoint}/app/${encodeURIComponent(tenant)}/admin/agent-jobs`;
1417
+ const headers = { authorization: `Bearer ${credential2}` };
1418
+ if (action === "retry") {
1419
+ const id = parsed.positionals[2];
1420
+ const res2 = await doFetch(`${base}/${encodeURIComponent(id)}/retry`, { method: "POST", headers });
1421
+ const body2 = await readJson(res2);
1422
+ if (!res2.ok) throw new Error(`agent retry failed (${res2.status}): ${errorMessage(body2)}`);
1423
+ const result2 = { v: 1, appId: cfg.app.id, env, tenant, ...body2 };
1424
+ if (parsed.options.json === true) out.log(JSON.stringify(result2, null, 2));
1425
+ else out.log(`${tenant}: requeued ${id}`);
1426
+ return;
1427
+ }
1428
+ const state2 = stringOpt(parsed.options.state);
1429
+ const allowed = ["pending", "running", "succeeded", "dead_letter"];
1430
+ if (state2 && !allowed.includes(state2)) {
1431
+ throw new Error(`--state must be one of ${allowed.join(", ")}`);
1432
+ }
1433
+ const url = new URL(base);
1434
+ if (state2) url.searchParams.set("state", state2);
1435
+ const limit = numberOpt(parsed.options.limit, "--limit");
1436
+ if (limit) url.searchParams.set("limit", String(limit));
1437
+ const res = await doFetch(url, { headers });
1438
+ const body = await readJson(res);
1439
+ if (!res.ok) throw new Error(`agent jobs failed (${res.status}): ${errorMessage(body)}`);
1440
+ const result = { v: 1, appId: cfg.app.id, env, tenant, jobs: body.jobs ?? [], summary: body.summary };
1441
+ if (parsed.options.json === true) {
1442
+ out.log(JSON.stringify(result, null, 2));
1443
+ return;
1444
+ }
1445
+ const summary = body.summary;
1446
+ out.log(
1447
+ `${tenant}: ${summary?.pending ?? 0} pending, ${summary?.running ?? 0} running, ${summary?.deadLetter ?? 0} dead-letter, ${summary?.succeeded ?? 0} succeeded`
1448
+ );
1449
+ for (const job of body.jobs ?? []) {
1450
+ out.log(
1451
+ [job.state, job.id, job.triggerId, `${job.entityNs}/${job.entityId}`, `attempts=${job.attempts}`, job.lastErrorCode ?? ""].filter(Boolean).join(" ")
1452
+ );
1453
+ }
1454
+ }
1455
+ async function readJson(res) {
1456
+ try {
1457
+ return await res.json();
1458
+ } catch {
1459
+ return {};
1460
+ }
1461
+ }
1462
+ function errorMessage(body) {
1463
+ const error = body.error;
1464
+ if (typeof error === "string") return error;
1465
+ if (error && typeof error === "object" && typeof error.message === "string") {
1466
+ return error.message;
1467
+ }
1468
+ return "request failed";
1469
+ }
1470
+
1396
1471
  // src/app-export.ts
1472
+ var import_node_fs5 = require("fs");
1473
+ var import_node_stream = require("stream");
1474
+ var import_promises = require("stream/promises");
1397
1475
  async function appExport(options) {
1398
1476
  const cfg = await loadProjectConfig(options.configPath);
1399
1477
  const out = options.stdout ?? console;
@@ -2371,6 +2449,7 @@ var CAPABILITIES = {
2371
2449
  "apply Google Calendar booking config, then drive state-bound consent, status, discovery, and disconnect flows (bookings run live through the platform proxy)",
2372
2450
  "validate integration contracts offline and smoke-test a provisioned db environment plus anonymous capability routes",
2373
2451
  "read one versioned o11y status envelope spanning application RED, current live-sync freshness/load, the protected commit-to-visible canary, collector ingest/scheduler trust, provider-owned runtime metrics, and a bounded machine verdict",
2452
+ "inspect durable agent wakeups as a versioned JSON envelope and explicitly requeue one dead-lettered job with a scoped environment credential",
2374
2453
  "run app-attributed hosted security discovery and independent validation without provider keys",
2375
2454
  "connect/revoke source-read-only GitHub sources and drive commit-pinned hosted security jobs without PATs or provider keys",
2376
2455
  "let admins manage system AI policy and credentials, inspect attributed usage, and read immutable admin changes through separate short-lived capability-only approvals"
@@ -6550,6 +6629,8 @@ Usage:
6550
6629
  odla-ai discuss resolve <topic> [--reopen] [--mutation-id <id>]
6551
6630
  odla-ai discuss who --q <text> [--app <id>] [--kinds user,pm:task] [--json]
6552
6631
  odla-ai discuss watch [<topic>] [--by <authorId>] [--self <authorId>] [--interval <s>] [--timeout <s>] [--json]
6632
+ odla-ai agent jobs [--env dev] [--state pending|running|succeeded|dead_letter] [--limit 50] [--email <email>] [--json]
6633
+ odla-ai agent retry <job-id> [--env dev] [--email <email>] [--json]
6553
6634
  odla-ai o11y status [--app <id>] [--env prod] [--minutes 60] [--json]
6554
6635
  odla-ai whoami [--json]
6555
6636
  odla-ai runbook ask "<question>" [--app <id>] [--all] [--json]
@@ -6597,6 +6678,8 @@ Usage:
6597
6678
  odla-ai version
6598
6679
 
6599
6680
  Commands:
6681
+ agent Inspect durable agent wakeups and explicitly requeue a
6682
+ dead-lettered job; JSON output is stable for remote operators.
6600
6683
  runbook odla's operational procedures, stored in the database and read at
6601
6684
  the moment they are followed. "ask" gives a written, cited answer;
6602
6685
  "search" the passages behind it; "get" the whole document.
@@ -8579,6 +8662,7 @@ var PM_ENTITIES = Object.fromEntries(
8579
8662
  ["goal", "conformance", "task", "kanban", "decision", "bug"].map((entity) => [entity, PM_ACTIONS])
8580
8663
  );
8581
8664
  var COMMAND_SURFACE = {
8665
+ agent: { jobs: {}, retry: {} },
8582
8666
  admin: {
8583
8667
  ai: {
8584
8668
  show: {},
@@ -10414,6 +10498,10 @@ async function runCli(argv = process.argv.slice(2), dependencies = {}) {
10414
10498
  await adminCommand(parsed);
10415
10499
  return;
10416
10500
  }
10501
+ if (command === "agent") {
10502
+ await agentCommand(parsed, runtime);
10503
+ return;
10504
+ }
10417
10505
  if (command === "app") {
10418
10506
  await appCommand(parsed, runtime);
10419
10507
  return;