@kody-ade/kody-engine 0.4.416 → 0.4.417

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/kody.js CHANGED
@@ -15,7 +15,7 @@ var init_package = __esm({
15
15
  "package.json"() {
16
16
  package_default = {
17
17
  name: "@kody-ade/kody-engine",
18
- version: "0.4.416",
18
+ version: "0.4.417",
19
19
  description: "kody \u2014 autonomous development engine. Single-session Claude Code agent behind a generic executor + declarative implementation profiles.",
20
20
  license: "MIT",
21
21
  type: "module",
@@ -52,7 +52,7 @@ var init_package = __esm({
52
52
  dependencies: {
53
53
  "@actions/cache": "^6.0.0",
54
54
  "@anthropic-ai/claude-agent-sdk": "0.2.119",
55
- "@kody-ade/agency-domain": "0.4.0",
55
+ "@kody-ade/agency-domain": "0.5.0",
56
56
  "@modelcontextprotocol/sdk": "^1.29.0",
57
57
  convex: "^1.17.0",
58
58
  zod: "^4.0.0"
@@ -3415,6 +3415,7 @@ async function runAgent(opts) {
3415
3415
  let outcomeKind = "generic_failed";
3416
3416
  let errorMessage2;
3417
3417
  let tokens = { input: 0, output: 0, cacheRead: 0, cacheCreate: 0 };
3418
+ let costUsd = 0;
3418
3419
  let messageCount = 0;
3419
3420
  let finalText = "";
3420
3421
  let getSubmitted;
@@ -3431,6 +3432,7 @@ async function runAgent(opts) {
3431
3432
  outcomeKind = "generic_failed";
3432
3433
  errorMessage2 = void 0;
3433
3434
  tokens = { input: 0, output: 0, cacheRead: 0, cacheCreate: 0 };
3435
+ costUsd = 0;
3434
3436
  messageCount = 0;
3435
3437
  let sawMutatingTool = false;
3436
3438
  let sawTerminalSuccess = false;
@@ -3555,6 +3557,7 @@ async function runAgent(opts) {
3555
3557
  };
3556
3558
  }
3557
3559
  queryOptions.settingSources = opts.settingSources ?? ["project", "local"];
3560
+ if (opts.abortController) queryOptions.abortController = opts.abortController;
3558
3561
  const stableBinary = ensureStableClaudeBinary();
3559
3562
  if (stableBinary) {
3560
3563
  const sdkBinaryPathOption = ["pathToClaudeCode", "Exec", "utable"].join("");
@@ -3683,6 +3686,8 @@ async function runAgent(opts) {
3683
3686
  }
3684
3687
  }
3685
3688
  if (m.type === "result") {
3689
+ const reportedCost = Number(m.total_cost_usd ?? 0);
3690
+ if (Number.isFinite(reportedCost) && reportedCost >= 0) costUsd = reportedCost;
3686
3691
  if (m.subtype === "success") {
3687
3692
  outcome = "completed";
3688
3693
  outcomeKind = "ok";
@@ -3760,6 +3765,7 @@ async function runAgent(opts) {
3760
3765
  ndjsonPath,
3761
3766
  durationMs: Date.now() - startedAt,
3762
3767
  tokens,
3768
+ costUsd,
3763
3769
  messageCount
3764
3770
  };
3765
3771
  }
@@ -13038,7 +13044,6 @@ function addDefinition(catalog, document) {
13038
13044
  }
13039
13045
  function add(collection, definition, revision) {
13040
13046
  const mutable = collection;
13041
- if (mutable.has(definition.id)) throw new Error(`Duplicate Agency Definition: ${definition.id}`);
13042
13047
  mutable.set(definition.id, { definition, revision });
13043
13048
  }
13044
13049
  function validateRelationships(catalog) {
@@ -13098,7 +13103,10 @@ var init_agencyModelRepository = __esm({
13098
13103
  async loadCatalog() {
13099
13104
  const documents = await this.backend.listAgencyDefinitions(this.tenantId);
13100
13105
  const catalog = emptyCatalog();
13101
- for (const document of documents) addDefinition(catalog, document);
13106
+ const ordered = [...documents].sort(
13107
+ (left, right) => left.createdAt.localeCompare(right.createdAt) || left.recordId.localeCompare(right.recordId)
13108
+ );
13109
+ for (const document of ordered) addDefinition(catalog, document);
13102
13110
  validateRelationships(catalog);
13103
13111
  return catalog;
13104
13112
  }
@@ -13325,7 +13333,19 @@ async function dispatchAgencyLoopsWith(input) {
13325
13333
  results.push({ loopId: record2.definition.id, decision: "skipped", reason });
13326
13334
  continue;
13327
13335
  }
13328
- const leaseUntil = new Date(input.now.getTime() + 15 * 6e4).toISOString();
13336
+ const failurePolicy = record2.definition.reconciliationPolicy.failure;
13337
+ const budget = policy.snapshot.policy.budget;
13338
+ const maxAttempts = Math.min(failurePolicy.maxAttempts, budget.maxRuns);
13339
+ const timeoutSeconds = Math.min(failurePolicy.timeoutSeconds, budget.maxDurationSeconds);
13340
+ const backoffBudgetSeconds = Array.from(
13341
+ { length: Math.max(0, maxAttempts - 1) },
13342
+ (_, index) => failurePolicy.backoffSeconds * 2 ** index
13343
+ ).reduce((sum, seconds) => sum + seconds, 0);
13344
+ const leaseSeconds = Math.min(
13345
+ budget.maxDurationSeconds,
13346
+ maxAttempts * timeoutSeconds + backoffBudgetSeconds
13347
+ );
13348
+ const leaseUntil = new Date(input.now.getTime() + leaseSeconds * 1e3).toISOString();
13329
13349
  const reservationId = `reservation-${randomUUID()}`;
13330
13350
  const correlationId = `corr-${randomUUID()}`;
13331
13351
  const trace = [policy.trace[0], ...target.intermediate, target.reference];
@@ -13365,40 +13385,82 @@ async function dispatchAgencyLoopsWith(input) {
13365
13385
  updatedAt: now
13366
13386
  });
13367
13387
  await repository.saveState(runningState, "loop", now);
13368
- const runId = `run-${randomUUID()}`;
13369
- const activeRun = createRun({
13370
- id: runId,
13371
- status: "running",
13372
- origin: { kind: "loop", id: record2.definition.id, revision: record2.revision },
13373
- target: target.reference,
13374
- trace,
13375
- effectivePolicy: policy.snapshot,
13376
- correlationId,
13377
- startedAt: now
13378
- });
13379
13388
  try {
13380
- await input.backend.createAgencyModelRun(
13381
- input.tenantId,
13382
- target.reference.kind,
13383
- target.reference.id,
13384
- activeRun,
13385
- now
13386
- );
13387
- const output = await input.run(target.job);
13388
- const succeeded = output.exitCode === 0;
13389
+ let attempts = 0;
13390
+ let tokens = 0;
13391
+ let costUsd = 0;
13392
+ let succeeded = false;
13393
+ let reason = "target failed";
13394
+ let finalRunId;
13395
+ const budgetDeadline = Date.now() + budget.maxDurationSeconds * 1e3;
13396
+ for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
13397
+ const remainingMilliseconds = budgetDeadline - Date.now();
13398
+ if (remainingMilliseconds <= 0) {
13399
+ reason = "policy duration budget exhausted";
13400
+ break;
13401
+ }
13402
+ attempts = attempt;
13403
+ const startedAt = (/* @__PURE__ */ new Date()).toISOString();
13404
+ const runId = `run-${randomUUID()}`;
13405
+ finalRunId = runId;
13406
+ const activeRun = createRun({
13407
+ id: runId,
13408
+ status: "running",
13409
+ origin: { kind: "loop", id: record2.definition.id, revision: record2.revision },
13410
+ target: target.reference,
13411
+ trace,
13412
+ effectivePolicy: policy.snapshot,
13413
+ correlationId,
13414
+ startedAt
13415
+ });
13416
+ await input.backend.createAgencyModelRun(
13417
+ input.tenantId,
13418
+ target.reference.kind,
13419
+ target.reference.id,
13420
+ activeRun,
13421
+ startedAt
13422
+ );
13423
+ const attemptResult = await runAttempt(
13424
+ input.run,
13425
+ target.job,
13426
+ Math.min(timeoutSeconds, remainingMilliseconds / 1e3)
13427
+ );
13428
+ tokens += attemptResult.usage?.tokens ?? 0;
13429
+ costUsd += attemptResult.usage?.costUsd ?? 0;
13430
+ const finishedAt2 = (/* @__PURE__ */ new Date()).toISOString();
13431
+ succeeded = attemptResult.exitCode === 0;
13432
+ reason = attemptResult.reason ?? (succeeded ? "target dispatched" : "target failed");
13433
+ const usage = {
13434
+ tokens: attemptResult.usage?.tokens ?? 0,
13435
+ costUsd: attemptResult.usage?.costUsd ?? 0,
13436
+ durationSeconds: Math.max(0, (Date.parse(finishedAt2) - Date.parse(startedAt)) / 1e3)
13437
+ };
13438
+ if (tokens > budget.maxTokens || costUsd > budget.maxCostUsd) {
13439
+ succeeded = false;
13440
+ reason = tokens > budget.maxTokens ? "policy token budget exhausted" : "policy cost budget exhausted";
13441
+ }
13442
+ await input.backend.finishAgencyModelRun(
13443
+ input.tenantId,
13444
+ terminalRun(activeRun, succeeded ? "succeeded" : "failed", finishedAt2, usage),
13445
+ finishedAt2
13446
+ );
13447
+ if (tokens > budget.maxTokens || costUsd > budget.maxCostUsd) break;
13448
+ if (succeeded || attempt === maxAttempts) break;
13449
+ const backoffMilliseconds = failurePolicy.backoffSeconds * 2 ** (attempt - 1) * 1e3;
13450
+ if (Date.now() + backoffMilliseconds >= budgetDeadline) {
13451
+ reason = "policy duration budget exhausted during retry backoff";
13452
+ break;
13453
+ }
13454
+ await wait(backoffMilliseconds);
13455
+ }
13389
13456
  const finishedAt = (/* @__PURE__ */ new Date()).toISOString();
13390
- await input.backend.finishAgencyModelRun(
13391
- input.tenantId,
13392
- terminalRun(activeRun, succeeded ? "succeeded" : "failed", finishedAt),
13393
- finishedAt
13394
- );
13395
13457
  await input.backend.finishAgencyDispatch(
13396
13458
  input.tenantId,
13397
13459
  decision.idempotencyKey,
13398
13460
  reservationId,
13399
- succeeded ? "dispatched" : "failed",
13461
+ succeeded ? "dispatched" : "dead-letter",
13400
13462
  finishedAt,
13401
- runId
13463
+ finalRunId
13402
13464
  );
13403
13465
  await repository.saveState(
13404
13466
  createLoopState2({
@@ -13413,19 +13475,17 @@ async function dispatchAgencyLoopsWith(input) {
13413
13475
  results.push({
13414
13476
  loopId: record2.definition.id,
13415
13477
  decision: succeeded ? "dispatched" : "failed",
13416
- reason: output.reason ?? (succeeded ? "target dispatched" : "target failed")
13478
+ reason: succeeded ? reason : `${reason}; dead-lettered after ${attempts} attempt${attempts === 1 ? "" : "s"}`
13417
13479
  });
13418
13480
  } catch (error) {
13419
13481
  const reason = error instanceof Error ? error.message : String(error);
13420
13482
  const finishedAt = (/* @__PURE__ */ new Date()).toISOString();
13421
- await input.backend.finishAgencyModelRun(input.tenantId, terminalRun(activeRun, "failed", finishedAt), finishedAt).catch(() => void 0);
13422
13483
  await input.backend.finishAgencyDispatch(
13423
13484
  input.tenantId,
13424
13485
  decision.idempotencyKey,
13425
13486
  reservationId,
13426
- "failed",
13427
- finishedAt,
13428
- runId
13487
+ "dead-letter",
13488
+ finishedAt
13429
13489
  );
13430
13490
  results.push({ loopId: record2.definition.id, decision: "failed", reason });
13431
13491
  }
@@ -13449,8 +13509,32 @@ function resolveTarget(loop, catalog) {
13449
13509
  job: target.kind === "workflow" ? { workflow: target.id, cliArgs: {}, flavor: "scheduled" } : { capability: target.id, cliArgs: {}, flavor: "scheduled" }
13450
13510
  };
13451
13511
  }
13452
- function terminalRun(active, status, finishedAt) {
13453
- return createRun({ ...active, status, finishedAt });
13512
+ function terminalRun(active, status, finishedAt, usage) {
13513
+ return createRun({ ...active, status, finishedAt, usage });
13514
+ }
13515
+ async function runAttempt(run, job, timeoutSeconds) {
13516
+ const abortController = new AbortController();
13517
+ let timer;
13518
+ try {
13519
+ return await Promise.race([
13520
+ run(job, abortController),
13521
+ new Promise((resolve17) => {
13522
+ timer = setTimeout(() => {
13523
+ abortController.abort();
13524
+ resolve17({ exitCode: 124, reason: `target timed out after ${formatSeconds(timeoutSeconds)}s` });
13525
+ }, timeoutSeconds * 1e3);
13526
+ })
13527
+ ]);
13528
+ } finally {
13529
+ if (timer) clearTimeout(timer);
13530
+ }
13531
+ }
13532
+ function formatSeconds(seconds) {
13533
+ return Number.isInteger(seconds) ? String(seconds) : seconds.toFixed(3).replace(/0+$/, "").replace(/\.$/, "");
13534
+ }
13535
+ async function wait(milliseconds) {
13536
+ if (milliseconds <= 0) return;
13537
+ await new Promise((resolve17) => setTimeout(resolve17, milliseconds));
13454
13538
  }
13455
13539
  function repositoryTenant(config) {
13456
13540
  const owner = config.github?.owner?.trim() || process.env.GITHUB_REPOSITORY?.split("/")[0]?.trim();
@@ -13474,7 +13558,14 @@ var init_dispatchAgencyLoops = __esm({
13474
13558
  tenantId: tenantId2,
13475
13559
  backend,
13476
13560
  now: /* @__PURE__ */ new Date(),
13477
- run: (job) => runJob(job, { cwd: ctx.cwd, config: ctx.config, verbose: ctx.verbose, quiet: ctx.quiet, chain: false })
13561
+ run: (job, abortController) => runJob(job, {
13562
+ cwd: ctx.cwd,
13563
+ config: ctx.config,
13564
+ verbose: ctx.verbose,
13565
+ quiet: ctx.quiet,
13566
+ chain: false,
13567
+ abortController
13568
+ })
13478
13569
  });
13479
13570
  ctx.data.agencyLoopDispatchResults = results;
13480
13571
  };
@@ -20686,6 +20777,7 @@ async function runImplementation(profileName, input) {
20686
20777
  isBackendHealthy: lm ? () => lm.isHealthy() : void 0,
20687
20778
  verbose: input.verbose,
20688
20779
  quiet: input.quiet,
20780
+ abortController: input.abortController,
20689
20781
  ndjsonDir,
20690
20782
  additionalDirectories: agentTaskArtifacts ? [agentTaskArtifacts.absDir] : void 0,
20691
20783
  allowedToolsOverride: profile.claudeCode.tools,
@@ -20795,6 +20887,10 @@ async function runImplementation(profileName, input) {
20795
20887
  reason: err instanceof Error ? err.message : String(err)
20796
20888
  });
20797
20889
  }
20890
+ ctx.output.usage = {
20891
+ tokens: agentResult.tokens ? agentResult.tokens.input + agentResult.tokens.output + agentResult.tokens.cacheRead + agentResult.tokens.cacheCreate : 0,
20892
+ costUsd: agentResult.costUsd ?? 0
20893
+ };
20798
20894
  emitEvent(input.cwd, {
20799
20895
  implementation: profileName,
20800
20896
  kind: "agent_end",
@@ -21637,6 +21733,7 @@ async function runCapabilityImplementationStep(valid, profileName, capabilityIde
21637
21733
  skipConfig: base.skipConfig,
21638
21734
  verbose: base.verbose,
21639
21735
  quiet: base.quiet,
21736
+ abortController: base.abortController,
21640
21737
  preloadedData: Object.keys(preloadedData).length > 0 ? preloadedData : void 0
21641
21738
  };
21642
21739
  const shouldApplyResolvedCapabilityArgs = valid.implementation === void 0 && resolvedCapability && profileName === resolvedCapability.implementation;
@@ -453,6 +453,7 @@ export interface Context {
453
453
  exitCode: number
454
454
  prUrl?: string
455
455
  reason?: string
456
+ usage?: { tokens: number; costUsd: number }
456
457
  /**
457
458
  * In-process hand-off to the next stage. A stage (e.g. `classify`) sets
458
459
  * this so the orchestrator runs the chosen sub-orchestrator
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kody-ade/kody-engine",
3
- "version": "0.4.416",
3
+ "version": "0.4.417",
4
4
  "description": "kody — autonomous development engine. Single-session Claude Code agent behind a generic executor + declarative implementation profiles.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -15,7 +15,7 @@
15
15
  "dependencies": {
16
16
  "@actions/cache": "^6.0.0",
17
17
  "@anthropic-ai/claude-agent-sdk": "0.2.119",
18
- "@kody-ade/agency-domain": "0.4.0",
18
+ "@kody-ade/agency-domain": "0.5.0",
19
19
  "@modelcontextprotocol/sdk": "^1.29.0",
20
20
  "convex": "^1.17.0",
21
21
  "zod": "^4.0.0"