@odla-ai/cli 0.25.7 → 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/README.md +24 -0
- package/dist/bin.cjs +294 -53
- package/dist/bin.cjs.map +1 -1
- package/dist/bin.js +1 -1
- package/dist/{chunk-5U3EXWCR.js → chunk-HXUVCUEM.js} +295 -54
- package/dist/chunk-HXUVCUEM.js.map +1 -0
- package/dist/index.cjs +294 -53
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +3 -2
- package/dist/index.d.ts +3 -2
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/skills/odla/SKILL.md +4 -0
- package/dist/chunk-5U3EXWCR.js.map +0 -1
package/README.md
CHANGED
|
@@ -193,6 +193,10 @@ npx odla-ai code connect --env prod --once # bounded enrollment + heartbeat proo
|
|
|
193
193
|
npx odla-ai provision --dry-run
|
|
194
194
|
npx odla-ai provision --email owner@example.com --write-dev-vars --push-secrets
|
|
195
195
|
npx odla-ai smoke --env dev
|
|
196
|
+
npx odla-ai agent jobs --env dev --state dead_letter --json
|
|
197
|
+
npx odla-ai agent retry <job-id> --env dev --json
|
|
198
|
+
npx odla-ai discuss watch <topic-id> --jsonl
|
|
199
|
+
npx odla-ai discuss watch <topic-id> --cursor <saved-cursor> --jsonl
|
|
196
200
|
npx odla-ai secrets push --env dev
|
|
197
201
|
npx odla-ai secrets set clerk_webhook_secret --env dev --stdin
|
|
198
202
|
npx odla-ai secrets set-clerk-key --env dev --from-env CLERK_SECRET_KEY
|
|
@@ -214,6 +218,26 @@ npx odla-ai skill install
|
|
|
214
218
|
npx odla-ai version
|
|
215
219
|
```
|
|
216
220
|
|
|
221
|
+
`agent jobs` is the remote-operator view of commit-trigger delivery. It reads
|
|
222
|
+
content-free lifecycle metadata—job id, trigger, entity reference, state,
|
|
223
|
+
attempts, timing, and bounded error/status codes—using an audience-bound
|
|
224
|
+
developer token for an owner of the selected environment. `agent retry` accepts one exact
|
|
225
|
+
dead-lettered id and resets its attempt lease; it does not replay successful or
|
|
226
|
+
currently running work. Successful receipts are retained for seven days (and
|
|
227
|
+
bounded to the newest 1,000 per tenant); unresolved dead letters remain until
|
|
228
|
+
an operator requeues them.
|
|
229
|
+
|
|
230
|
+
`discuss watch` waits at a server-issued snapshot boundary instead of comparing
|
|
231
|
+
client clocks or repeatedly reading a topic's oldest 200 posts. It is unbounded
|
|
232
|
+
unless `--timeout <seconds>` is supplied. `--jsonl` emits versioned
|
|
233
|
+
`checkpoint`, `event`, `heartbeat`, and degraded `status` records; persist the
|
|
234
|
+
latest cursor only after consuming its record and pass it back with `--cursor`
|
|
235
|
+
after a restart. Event IDs are stable for at-least-once deduplication. Transient
|
|
236
|
+
429/5xx/network failures retry with bounded exponential backoff without
|
|
237
|
+
advancing the cursor. A restore or truncated history exits 3 and emits an
|
|
238
|
+
explicit replacement checkpoint; an explicit timeout exits 75, and an
|
|
239
|
+
exhausted remote retry exits 6.
|
|
240
|
+
|
|
217
241
|
`code connect` is the first-party personal Code terminal runtime. Run the exact
|
|
218
242
|
command copied from **Studio → Code → Terminal** while your shell is in the
|
|
219
243
|
local Git checkout. `--platform`, `--app-id`, and `--env` make the requested
|
package/dist/bin.cjs
CHANGED
|
@@ -1020,11 +1020,6 @@ async function adminCommand(parsed) {
|
|
|
1020
1020
|
});
|
|
1021
1021
|
}
|
|
1022
1022
|
|
|
1023
|
-
// src/app-export.ts
|
|
1024
|
-
var import_node_fs5 = require("fs");
|
|
1025
|
-
var import_node_stream = require("stream");
|
|
1026
|
-
var import_promises = require("stream/promises");
|
|
1027
|
-
|
|
1028
1023
|
// src/config.ts
|
|
1029
1024
|
var import_node_fs4 = require("fs");
|
|
1030
1025
|
var import_node_path4 = require("path");
|
|
@@ -1333,7 +1328,90 @@ function bothTenants(cfg) {
|
|
|
1333
1328
|
return { sandbox: (0, import_apps.tenantIdFor)(cfg.app.id, "dev"), live: (0, import_apps.tenantIdFor)(cfg.app.id, "prod") };
|
|
1334
1329
|
}
|
|
1335
1330
|
|
|
1331
|
+
// src/agent-command.ts
|
|
1332
|
+
async function agentCommand(parsed, deps = {}) {
|
|
1333
|
+
const action = parsed.positionals[1];
|
|
1334
|
+
if (action !== "jobs" && action !== "retry") {
|
|
1335
|
+
throw new Error(`unknown agent action "${action ?? ""}". Try "odla-ai agent jobs --json".`);
|
|
1336
|
+
}
|
|
1337
|
+
assertArgs(parsed, ["config", "env", "state", "limit", "json", "token", "email"], action === "jobs" ? 2 : 3);
|
|
1338
|
+
if (action === "retry" && (parsed.options.state !== void 0 || parsed.options.limit !== void 0)) {
|
|
1339
|
+
throw new Error('--state and --limit are supported only by "agent jobs"');
|
|
1340
|
+
}
|
|
1341
|
+
const cfg = await loadProjectConfig(stringOpt(parsed.options.config) ?? "odla.config.mjs");
|
|
1342
|
+
const { env, tenant } = resolveTenant(cfg, stringOpt(parsed.options.env));
|
|
1343
|
+
const doFetch = deps.fetch ?? fetch;
|
|
1344
|
+
const out = deps.stdout ?? console;
|
|
1345
|
+
const credential2 = await getDeveloperToken(
|
|
1346
|
+
cfg,
|
|
1347
|
+
{
|
|
1348
|
+
configPath: cfg.configPath,
|
|
1349
|
+
token: stringOpt(parsed.options.token),
|
|
1350
|
+
email: stringOpt(parsed.options.email),
|
|
1351
|
+
open: false
|
|
1352
|
+
},
|
|
1353
|
+
doFetch,
|
|
1354
|
+
out
|
|
1355
|
+
);
|
|
1356
|
+
const base = `${cfg.dbEndpoint}/app/${encodeURIComponent(tenant)}/admin/agent-jobs`;
|
|
1357
|
+
const headers = { authorization: `Bearer ${credential2}` };
|
|
1358
|
+
if (action === "retry") {
|
|
1359
|
+
const id = parsed.positionals[2];
|
|
1360
|
+
const res2 = await doFetch(`${base}/${encodeURIComponent(id)}/retry`, { method: "POST", headers });
|
|
1361
|
+
const body2 = await readJson(res2);
|
|
1362
|
+
if (!res2.ok) throw new Error(`agent retry failed (${res2.status}): ${errorMessage(body2)}`);
|
|
1363
|
+
const result2 = { v: 1, appId: cfg.app.id, env, tenant, ...body2 };
|
|
1364
|
+
if (parsed.options.json === true) out.log(JSON.stringify(result2, null, 2));
|
|
1365
|
+
else out.log(`${tenant}: requeued ${id}`);
|
|
1366
|
+
return;
|
|
1367
|
+
}
|
|
1368
|
+
const state2 = stringOpt(parsed.options.state);
|
|
1369
|
+
const allowed = ["pending", "running", "succeeded", "dead_letter"];
|
|
1370
|
+
if (state2 && !allowed.includes(state2)) {
|
|
1371
|
+
throw new Error(`--state must be one of ${allowed.join(", ")}`);
|
|
1372
|
+
}
|
|
1373
|
+
const url = new URL(base);
|
|
1374
|
+
if (state2) url.searchParams.set("state", state2);
|
|
1375
|
+
const limit = numberOpt(parsed.options.limit, "--limit");
|
|
1376
|
+
if (limit) url.searchParams.set("limit", String(limit));
|
|
1377
|
+
const res = await doFetch(url, { headers });
|
|
1378
|
+
const body = await readJson(res);
|
|
1379
|
+
if (!res.ok) throw new Error(`agent jobs failed (${res.status}): ${errorMessage(body)}`);
|
|
1380
|
+
const result = { v: 1, appId: cfg.app.id, env, tenant, jobs: body.jobs ?? [], summary: body.summary };
|
|
1381
|
+
if (parsed.options.json === true) {
|
|
1382
|
+
out.log(JSON.stringify(result, null, 2));
|
|
1383
|
+
return;
|
|
1384
|
+
}
|
|
1385
|
+
const summary = body.summary;
|
|
1386
|
+
out.log(
|
|
1387
|
+
`${tenant}: ${summary?.pending ?? 0} pending, ${summary?.running ?? 0} running, ${summary?.deadLetter ?? 0} dead-letter, ${summary?.succeeded ?? 0} succeeded`
|
|
1388
|
+
);
|
|
1389
|
+
for (const job of body.jobs ?? []) {
|
|
1390
|
+
out.log(
|
|
1391
|
+
[job.state, job.id, job.triggerId, `${job.entityNs}/${job.entityId}`, `attempts=${job.attempts}`, job.lastErrorCode ?? ""].filter(Boolean).join(" ")
|
|
1392
|
+
);
|
|
1393
|
+
}
|
|
1394
|
+
}
|
|
1395
|
+
async function readJson(res) {
|
|
1396
|
+
try {
|
|
1397
|
+
return await res.json();
|
|
1398
|
+
} catch {
|
|
1399
|
+
return {};
|
|
1400
|
+
}
|
|
1401
|
+
}
|
|
1402
|
+
function errorMessage(body) {
|
|
1403
|
+
const error = body.error;
|
|
1404
|
+
if (typeof error === "string") return error;
|
|
1405
|
+
if (error && typeof error === "object" && typeof error.message === "string") {
|
|
1406
|
+
return error.message;
|
|
1407
|
+
}
|
|
1408
|
+
return "request failed";
|
|
1409
|
+
}
|
|
1410
|
+
|
|
1336
1411
|
// src/app-export.ts
|
|
1412
|
+
var import_node_fs5 = require("fs");
|
|
1413
|
+
var import_node_stream = require("stream");
|
|
1414
|
+
var import_promises = require("stream/promises");
|
|
1337
1415
|
async function appExport(options) {
|
|
1338
1416
|
const cfg = await loadProjectConfig(options.configPath);
|
|
1339
1417
|
const out = options.stdout ?? console;
|
|
@@ -2311,6 +2389,7 @@ var CAPABILITIES = {
|
|
|
2311
2389
|
"apply Google Calendar booking config, then drive state-bound consent, status, discovery, and disconnect flows (bookings run live through the platform proxy)",
|
|
2312
2390
|
"validate integration contracts offline and smoke-test a provisioned db environment plus anonymous capability routes",
|
|
2313
2391
|
"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",
|
|
2392
|
+
"inspect durable agent wakeups as a versioned JSON envelope and explicitly requeue one dead-lettered job with a scoped environment credential",
|
|
2314
2393
|
"run app-attributed hosted security discovery and independent validation without provider keys",
|
|
2315
2394
|
"connect/revoke source-read-only GitHub sources and drive commit-pinned hosted security jobs without PATs or provider keys",
|
|
2316
2395
|
"let admins manage system AI policy and credentials, inspect attributed usage, and read immutable admin changes through separate short-lived capability-only approvals"
|
|
@@ -4529,13 +4608,13 @@ async function createConversionRegistry(config) {
|
|
|
4529
4608
|
policies.set(policy.conversionId, Object.freeze(policy));
|
|
4530
4609
|
}
|
|
4531
4610
|
const outputCounts = /* @__PURE__ */ new Map();
|
|
4532
|
-
const
|
|
4611
|
+
const get = (id, kind) => {
|
|
4533
4612
|
const policy = policies.get(id);
|
|
4534
4613
|
if (!policy || policy.output.kind !== kind) throw new CamelError("conversion_rejected", "Conversion policy is missing or has the wrong output kind.");
|
|
4535
4614
|
return policy;
|
|
4536
4615
|
};
|
|
4537
4616
|
const checked = (source, id, kind) => {
|
|
4538
|
-
const policy =
|
|
4617
|
+
const policy = get(id, kind);
|
|
4539
4618
|
if (utf8Length(source.value) > policy.maximumSourceBytes) throw new CamelError("limit_exceeded", "Unsafe conversion input exceeds its byte bound.");
|
|
4540
4619
|
return policy;
|
|
4541
4620
|
};
|
|
@@ -6489,7 +6568,9 @@ Usage:
|
|
|
6489
6568
|
odla-ai discuss reply <topic> --body "..." [--markup "..."] [--mutation-id <id>]
|
|
6490
6569
|
odla-ai discuss resolve <topic> [--reopen] [--mutation-id <id>]
|
|
6491
6570
|
odla-ai discuss who --q <text> [--app <id>] [--kinds user,pm:task] [--json]
|
|
6492
|
-
odla-ai discuss watch [<topic>] [--by <authorId>] [--self <authorId>] [--interval <s>] [--timeout <s>] [--json]
|
|
6571
|
+
odla-ai discuss watch [<topic>] [--cursor <cursor>] [--by <authorId>] [--self <authorId>] [--interval <s>] [--timeout <s>] [--json|--jsonl]
|
|
6572
|
+
odla-ai agent jobs [--env dev] [--state pending|running|succeeded|dead_letter] [--limit 50] [--email <email>] [--json]
|
|
6573
|
+
odla-ai agent retry <job-id> [--env dev] [--email <email>] [--json]
|
|
6493
6574
|
odla-ai o11y status [--app <id>] [--env prod] [--minutes 60] [--json]
|
|
6494
6575
|
odla-ai whoami [--json]
|
|
6495
6576
|
odla-ai runbook ask "<question>" [--app <id>] [--all] [--json]
|
|
@@ -6537,6 +6618,8 @@ Usage:
|
|
|
6537
6618
|
odla-ai version
|
|
6538
6619
|
|
|
6539
6620
|
Commands:
|
|
6621
|
+
agent Inspect durable agent wakeups and explicitly requeue a
|
|
6622
|
+
dead-lettered job; JSON output is stable for remote operators.
|
|
6540
6623
|
runbook odla's operational procedures, stored in the database and read at
|
|
6541
6624
|
the moment they are followed. "ask" gives a written, cited answer;
|
|
6542
6625
|
"search" the passages behind it; "get" the whole document.
|
|
@@ -7390,61 +7473,214 @@ async function discussWho(ctx, parsed) {
|
|
|
7390
7473
|
});
|
|
7391
7474
|
}
|
|
7392
7475
|
|
|
7476
|
+
// src/discuss-watch-http.ts
|
|
7477
|
+
var WatchRequestError = class extends Error {
|
|
7478
|
+
constructor(message2, retryable, status, code) {
|
|
7479
|
+
super(message2);
|
|
7480
|
+
this.retryable = retryable;
|
|
7481
|
+
this.status = status;
|
|
7482
|
+
this.code = code;
|
|
7483
|
+
this.name = "WatchRequestError";
|
|
7484
|
+
}
|
|
7485
|
+
retryable;
|
|
7486
|
+
status;
|
|
7487
|
+
code;
|
|
7488
|
+
};
|
|
7489
|
+
var WatchCheckpointError = class extends Error {
|
|
7490
|
+
constructor(cursor, streamId) {
|
|
7491
|
+
super("discussion cursor requires a new checkpoint");
|
|
7492
|
+
this.cursor = cursor;
|
|
7493
|
+
this.streamId = streamId;
|
|
7494
|
+
this.name = "WatchCheckpointError";
|
|
7495
|
+
}
|
|
7496
|
+
cursor;
|
|
7497
|
+
streamId;
|
|
7498
|
+
code = "checkpoint_required";
|
|
7499
|
+
};
|
|
7500
|
+
var WatchRemoteError = class extends Error {
|
|
7501
|
+
constructor(cursor, cause) {
|
|
7502
|
+
super("discussion watch dependency remained unavailable", { cause });
|
|
7503
|
+
this.cursor = cursor;
|
|
7504
|
+
this.name = "WatchRemoteError";
|
|
7505
|
+
}
|
|
7506
|
+
cursor;
|
|
7507
|
+
code = "remote_unavailable";
|
|
7508
|
+
};
|
|
7509
|
+
var WatchTimeoutError = class extends Error {
|
|
7510
|
+
constructor(cursor) {
|
|
7511
|
+
super("nothing new before the timeout");
|
|
7512
|
+
this.cursor = cursor;
|
|
7513
|
+
this.name = "WatchTimeoutError";
|
|
7514
|
+
}
|
|
7515
|
+
cursor;
|
|
7516
|
+
code = "watch_timeout";
|
|
7517
|
+
};
|
|
7518
|
+
function requestPath(topicId, app, cursor) {
|
|
7519
|
+
const params = new URLSearchParams();
|
|
7520
|
+
if (topicId) params.set("topic", topicId);
|
|
7521
|
+
if (app) params.set("app", app);
|
|
7522
|
+
if (cursor) params.set("cursor", cursor);
|
|
7523
|
+
return `/watch?${params}`;
|
|
7524
|
+
}
|
|
7525
|
+
async function getWatchPage(ctx, path) {
|
|
7526
|
+
let res;
|
|
7527
|
+
try {
|
|
7528
|
+
res = await ctx.doFetch(`${ctx.platformUrl}/registry/discuss${path}`, {
|
|
7529
|
+
headers: { authorization: `Bearer ${ctx.token}` }
|
|
7530
|
+
});
|
|
7531
|
+
} catch (error) {
|
|
7532
|
+
throw new WatchRequestError(
|
|
7533
|
+
`discuss watch request failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
7534
|
+
true
|
|
7535
|
+
);
|
|
7536
|
+
}
|
|
7537
|
+
const data = await res.json().catch(() => ({}));
|
|
7538
|
+
if (res.status === 409 && data.code === "checkpoint_required") {
|
|
7539
|
+
throw new WatchCheckpointError(data.cursor, data.streamId);
|
|
7540
|
+
}
|
|
7541
|
+
if (!res.ok) {
|
|
7542
|
+
throw new WatchRequestError(
|
|
7543
|
+
`discuss watch failed: ${data.error ?? `registry returned ${res.status}`}`,
|
|
7544
|
+
res.status === 429 || res.status >= 500,
|
|
7545
|
+
res.status,
|
|
7546
|
+
data.code ?? (res.status === 401 || res.status === 403 ? "auth_failed" : void 0)
|
|
7547
|
+
);
|
|
7548
|
+
}
|
|
7549
|
+
return data;
|
|
7550
|
+
}
|
|
7551
|
+
|
|
7393
7552
|
// src/discuss-watch.ts
|
|
7394
7553
|
var DEFAULT_INTERVAL_MS = 15e3;
|
|
7395
|
-
var
|
|
7554
|
+
var MAX_CONSECUTIVE_FAILURES = 5;
|
|
7555
|
+
var MAX_BACKOFF_MS = 3e4;
|
|
7396
7556
|
function numberOpt2(parsed, flag, fallback) {
|
|
7397
7557
|
const raw = stringOpt(parsed.options[flag]);
|
|
7398
7558
|
const value = raw == null ? NaN : Number(raw);
|
|
7399
7559
|
return Number.isFinite(value) && value > 0 ? value : fallback;
|
|
7400
7560
|
}
|
|
7401
|
-
|
|
7402
|
-
|
|
7403
|
-
headers: { authorization: `Bearer ${ctx.token}` }
|
|
7404
|
-
});
|
|
7405
|
-
const data = await res.json().catch(() => ({}));
|
|
7406
|
-
if (!res.ok) throw new Error(`discuss watch failed: ${data.error ?? `registry returned ${res.status}`}`);
|
|
7407
|
-
return data;
|
|
7561
|
+
function jsonl(ctx, parsed, value) {
|
|
7562
|
+
if (parsed.options.jsonl === true) ctx.out.log(JSON.stringify({ v: 1, ...value }));
|
|
7408
7563
|
}
|
|
7409
7564
|
async function discussWatch(ctx, topicId, parsed) {
|
|
7565
|
+
if (ctx.json && parsed.options.jsonl === true) throw new Error("--json and --jsonl cannot be combined");
|
|
7410
7566
|
const sleep = ctx.sleep ?? ((ms) => new Promise((resolve11) => setTimeout(resolve11, ms)));
|
|
7411
7567
|
const now = ctx.now ?? Date.now;
|
|
7412
|
-
const intervalMs = numberOpt2(parsed, "interval", DEFAULT_INTERVAL_MS / 1e3) * 1e3;
|
|
7413
|
-
const
|
|
7568
|
+
const intervalMs = (numberOpt2(parsed, "interval", DEFAULT_INTERVAL_MS / 1e3) ?? DEFAULT_INTERVAL_MS / 1e3) * 1e3;
|
|
7569
|
+
const timeoutSeconds = numberOpt2(parsed, "timeout");
|
|
7570
|
+
const deadline = timeoutSeconds === void 0 ? void 0 : now() + timeoutSeconds * 1e3;
|
|
7414
7571
|
const by = stringOpt(parsed.options.by);
|
|
7415
7572
|
const self = stringOpt(parsed.options.self);
|
|
7416
|
-
const
|
|
7417
|
-
|
|
7418
|
-
let
|
|
7419
|
-
let
|
|
7420
|
-
if (topicId) {
|
|
7421
|
-
const first = await get(ctx, `/topics/${encodeURIComponent(topicId)}`);
|
|
7422
|
-
seen = new Set(first.posts.map((post) => post.id));
|
|
7423
|
-
}
|
|
7573
|
+
const app = stringOpt(parsed.options.app);
|
|
7574
|
+
let cursor = stringOpt(parsed.options.cursor);
|
|
7575
|
+
let firstSuccess = true;
|
|
7576
|
+
let consecutiveFailures = 0;
|
|
7424
7577
|
for (; ; ) {
|
|
7425
|
-
|
|
7426
|
-
|
|
7427
|
-
|
|
7428
|
-
|
|
7429
|
-
|
|
7430
|
-
|
|
7431
|
-
|
|
7432
|
-
|
|
7433
|
-
|
|
7434
|
-
|
|
7435
|
-
|
|
7436
|
-
|
|
7437
|
-
|
|
7438
|
-
|
|
7578
|
+
let page;
|
|
7579
|
+
try {
|
|
7580
|
+
page = await getWatchPage(ctx, requestPath(topicId, app, cursor));
|
|
7581
|
+
consecutiveFailures = 0;
|
|
7582
|
+
} catch (error) {
|
|
7583
|
+
if (!(error instanceof WatchRequestError) || !error.retryable) {
|
|
7584
|
+
if (error instanceof WatchCheckpointError) {
|
|
7585
|
+
jsonl(ctx, parsed, {
|
|
7586
|
+
type: "status",
|
|
7587
|
+
state: "checkpoint_required",
|
|
7588
|
+
retryable: false,
|
|
7589
|
+
...error.cursor ? { cursor: error.cursor } : {},
|
|
7590
|
+
...error.streamId ? { streamId: error.streamId } : {}
|
|
7591
|
+
});
|
|
7592
|
+
}
|
|
7593
|
+
throw error;
|
|
7594
|
+
}
|
|
7595
|
+
consecutiveFailures++;
|
|
7596
|
+
jsonl(ctx, parsed, {
|
|
7597
|
+
type: "status",
|
|
7598
|
+
state: "degraded",
|
|
7599
|
+
retryable: true,
|
|
7600
|
+
attempt: consecutiveFailures,
|
|
7601
|
+
...cursor ? { cursor } : {},
|
|
7602
|
+
...error.status ? { status: error.status } : {}
|
|
7603
|
+
});
|
|
7604
|
+
if (consecutiveFailures >= MAX_CONSECUTIVE_FAILURES) {
|
|
7605
|
+
throw new WatchRemoteError(cursor, error);
|
|
7606
|
+
}
|
|
7607
|
+
if (deadline !== void 0 && now() >= deadline) {
|
|
7608
|
+
const result = report2(ctx, parsed, { found: false, cursor: cursor ?? "" });
|
|
7609
|
+
throw new WatchTimeoutError(result.cursor);
|
|
7610
|
+
}
|
|
7611
|
+
const base = Math.min(intervalMs, 1e3);
|
|
7612
|
+
const backoff = Math.min(MAX_BACKOFF_MS, base * 2 ** (consecutiveFailures - 1));
|
|
7613
|
+
await sleep(deadline === void 0 ? backoff : Math.min(backoff, Math.max(0, deadline - now())));
|
|
7614
|
+
continue;
|
|
7615
|
+
}
|
|
7616
|
+
cursor = page.cursor;
|
|
7617
|
+
const baseline = firstSuccess && page.events.length === 0;
|
|
7618
|
+
if (baseline) {
|
|
7619
|
+
jsonl(ctx, parsed, {
|
|
7620
|
+
type: "checkpoint",
|
|
7621
|
+
streamId: page.streamId,
|
|
7622
|
+
cursor,
|
|
7623
|
+
serverTime: page.serverTime
|
|
7624
|
+
});
|
|
7625
|
+
}
|
|
7626
|
+
firstSuccess = false;
|
|
7627
|
+
const matching = page.events.filter((event) => {
|
|
7628
|
+
if (topicId && (event.type !== "message" || event.action !== "created")) return false;
|
|
7629
|
+
return (!by || event.actor.id === by) && (!self || event.actor.id !== self);
|
|
7630
|
+
});
|
|
7631
|
+
for (const event of matching) {
|
|
7632
|
+
jsonl(ctx, parsed, {
|
|
7633
|
+
type: "event",
|
|
7634
|
+
streamId: page.streamId,
|
|
7635
|
+
eventId: event.id,
|
|
7636
|
+
cursor: event.cursor,
|
|
7637
|
+
event
|
|
7638
|
+
});
|
|
7639
|
+
}
|
|
7640
|
+
if (matching.length > 0) {
|
|
7641
|
+
jsonl(ctx, parsed, {
|
|
7642
|
+
type: "checkpoint",
|
|
7643
|
+
streamId: page.streamId,
|
|
7644
|
+
cursor,
|
|
7645
|
+
serverTime: page.serverTime
|
|
7646
|
+
});
|
|
7647
|
+
const posts = topicId ? matching.filter((event) => event.type === "message").map((event) => event.payload) : void 0;
|
|
7648
|
+
const topics = topicId ? void 0 : matching.filter((event) => event.type === "activity").map((event) => event.payload);
|
|
7649
|
+
return report2(ctx, parsed, {
|
|
7650
|
+
found: true,
|
|
7651
|
+
cursor,
|
|
7652
|
+
events: matching,
|
|
7653
|
+
...posts && posts.length > 0 ? { posts } : {},
|
|
7654
|
+
...topics && topics.length > 0 ? { topics } : {}
|
|
7655
|
+
});
|
|
7439
7656
|
}
|
|
7440
|
-
if (
|
|
7441
|
-
|
|
7657
|
+
if (page.events.length > 0) {
|
|
7658
|
+
jsonl(ctx, parsed, {
|
|
7659
|
+
type: "checkpoint",
|
|
7660
|
+
streamId: page.streamId,
|
|
7661
|
+
cursor,
|
|
7662
|
+
serverTime: page.serverTime
|
|
7663
|
+
});
|
|
7664
|
+
} else if (!baseline) {
|
|
7665
|
+
jsonl(ctx, parsed, {
|
|
7666
|
+
type: "heartbeat",
|
|
7667
|
+
streamId: page.streamId,
|
|
7668
|
+
cursor,
|
|
7669
|
+
serverTime: page.serverTime
|
|
7670
|
+
});
|
|
7671
|
+
}
|
|
7672
|
+
if (page.hasMore) continue;
|
|
7673
|
+
if (deadline !== void 0 && now() >= deadline) {
|
|
7674
|
+
return report2(ctx, parsed, { found: false, cursor });
|
|
7675
|
+
}
|
|
7676
|
+
const wait2 = deadline === void 0 ? intervalMs : Math.min(intervalMs, Math.max(0, deadline - now()));
|
|
7677
|
+
await sleep(wait2);
|
|
7442
7678
|
}
|
|
7443
7679
|
}
|
|
7444
|
-
function report2(ctx, result) {
|
|
7680
|
+
function report2(ctx, parsed, result) {
|
|
7445
7681
|
if (ctx.json) {
|
|
7446
7682
|
ctx.out.log(JSON.stringify(result, null, 2));
|
|
7447
|
-
} else if (result.found) {
|
|
7683
|
+
} else if (parsed.options.jsonl !== true && result.found) {
|
|
7448
7684
|
for (const post of result.posts ?? []) {
|
|
7449
7685
|
const who = post.authorKind === "bot" ? `${post.authorId} (agent)` : post.authorId;
|
|
7450
7686
|
ctx.out.log(`\u2014 ${who}
|
|
@@ -7456,13 +7692,6 @@ ${post.body}`);
|
|
|
7456
7692
|
}
|
|
7457
7693
|
return result;
|
|
7458
7694
|
}
|
|
7459
|
-
var WatchTimeoutError = class extends Error {
|
|
7460
|
-
code = "watch_timeout";
|
|
7461
|
-
constructor() {
|
|
7462
|
-
super("nothing new before the timeout");
|
|
7463
|
-
this.name = "WatchTimeoutError";
|
|
7464
|
-
}
|
|
7465
|
-
};
|
|
7466
7695
|
|
|
7467
7696
|
// src/discuss-command.ts
|
|
7468
7697
|
var ALLOWED = [
|
|
@@ -7484,6 +7713,8 @@ var ALLOWED = [
|
|
|
7484
7713
|
"self",
|
|
7485
7714
|
"interval",
|
|
7486
7715
|
"timeout",
|
|
7716
|
+
"cursor",
|
|
7717
|
+
"jsonl",
|
|
7487
7718
|
"mutation-id"
|
|
7488
7719
|
];
|
|
7489
7720
|
function requireId(id, action) {
|
|
@@ -7531,7 +7762,7 @@ async function discussCommand(parsed, deps = {}) {
|
|
|
7531
7762
|
return discussWho(ctx, parsed);
|
|
7532
7763
|
case "watch": {
|
|
7533
7764
|
const result = await discussWatch(ctx, id, parsed);
|
|
7534
|
-
if (!result.found) throw new WatchTimeoutError();
|
|
7765
|
+
if (!result.found) throw new WatchTimeoutError(result.cursor);
|
|
7535
7766
|
return;
|
|
7536
7767
|
}
|
|
7537
7768
|
default:
|
|
@@ -8519,6 +8750,7 @@ var PM_ENTITIES = Object.fromEntries(
|
|
|
8519
8750
|
["goal", "conformance", "task", "kanban", "decision", "bug"].map((entity) => [entity, PM_ACTIONS])
|
|
8520
8751
|
);
|
|
8521
8752
|
var COMMAND_SURFACE = {
|
|
8753
|
+
agent: { jobs: {}, retry: {} },
|
|
8522
8754
|
admin: {
|
|
8523
8755
|
ai: {
|
|
8524
8756
|
show: {},
|
|
@@ -10294,7 +10526,12 @@ async function securityStatus(parsed, dependencies) {
|
|
|
10294
10526
|
// src/cli.ts
|
|
10295
10527
|
function exitCodeFor(err) {
|
|
10296
10528
|
const code = err?.code;
|
|
10297
|
-
|
|
10529
|
+
if (code === "handshake_pending" || code === "watch_timeout") return 75;
|
|
10530
|
+
if (code === "checkpoint_required") return 3;
|
|
10531
|
+
if (code === "remote_unavailable") return 6;
|
|
10532
|
+
if (code === "auth_failed") return 5;
|
|
10533
|
+
if (code === "invalid_cursor") return 2;
|
|
10534
|
+
return 1;
|
|
10298
10535
|
}
|
|
10299
10536
|
async function runCli(argv = process.argv.slice(2), dependencies = {}) {
|
|
10300
10537
|
const runtime = {
|
|
@@ -10334,6 +10571,10 @@ async function runCli(argv = process.argv.slice(2), dependencies = {}) {
|
|
|
10334
10571
|
await adminCommand(parsed);
|
|
10335
10572
|
return;
|
|
10336
10573
|
}
|
|
10574
|
+
if (command === "agent") {
|
|
10575
|
+
await agentCommand(parsed, runtime);
|
|
10576
|
+
return;
|
|
10577
|
+
}
|
|
10337
10578
|
if (command === "app") {
|
|
10338
10579
|
await appCommand(parsed, runtime);
|
|
10339
10580
|
return;
|