@kody-ade/kody-engine 0.4.414 → 0.4.416

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.
Files changed (2) hide show
  1. package/dist/bin/kody.js +322 -78
  2. package/package.json +25 -26
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.414",
18
+ version: "0.4.416",
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",
@@ -50,9 +50,9 @@ var init_package = __esm({
50
50
  prepublishOnly: "pnpm typecheck && vitest run tests/unit tests/int --no-coverage && pnpm build && pnpm verify:package"
51
51
  },
52
52
  dependencies: {
53
- "@kody-ade/agency-domain": "0.1.1",
54
53
  "@actions/cache": "^6.0.0",
55
54
  "@anthropic-ai/claude-agent-sdk": "0.2.119",
55
+ "@kody-ade/agency-domain": "0.4.0",
56
56
  "@modelcontextprotocol/sdk": "^1.29.0",
57
57
  convex: "^1.17.0",
58
58
  zod: "^4.0.0"
@@ -1990,14 +1990,14 @@ function resolveImplementationCandidates(name, roots = getImplementationRoots())
1990
1990
  function listCapabilityActions(projectCapabilitiesRoot = getProjectCapabilitiesRoot()) {
1991
1991
  const seen = /* @__PURE__ */ new Set();
1992
1992
  const out = [];
1993
- const add = (action) => {
1993
+ const add2 = (action) => {
1994
1994
  if (!isSafeName(action.action) || !isSafeName(action.capability) || !isSafeName(action.implementation)) return;
1995
1995
  if (seen.has(action.action)) return;
1996
1996
  seen.add(action.action);
1997
1997
  out.push(action);
1998
1998
  };
1999
- for (const action of listFolderCapabilityActions(projectCapabilitiesRoot, "project-folder")) add(action);
2000
- for (const action of listBuiltinCapabilityActions(getBuiltinCapabilitiesRoot())) add(action);
1999
+ for (const action of listFolderCapabilityActions(projectCapabilitiesRoot, "project-folder")) add2(action);
2000
+ for (const action of listBuiltinCapabilityActions(getBuiltinCapabilitiesRoot())) add2(action);
2001
2001
  return out.sort((a, b) => a.action.localeCompare(b.action));
2002
2002
  }
2003
2003
  function resolveCapabilityAction(action, projectCapabilitiesRoot = getProjectCapabilitiesRoot()) {
@@ -2476,14 +2476,15 @@ function createStateBackendFromEnv(env = process.env, client) {
2476
2476
  });
2477
2477
  return Array.isArray(result) ? result : [];
2478
2478
  },
2479
- async reserveAgencyDispatch(tenantId2, idempotencyKey, loopId, decision, leaseUntil, now) {
2479
+ async reserveAgencyDispatch(tenantId2, reservation) {
2480
2480
  const result = await transport.mutation(anyApi.agencyModel.reserveDispatch, {
2481
2481
  tenantId: requireTenant(tenantId2),
2482
- idempotencyKey: requireNonEmpty(idempotencyKey, "idempotencyKey"),
2483
- loopId: requireNonEmpty(loopId, "loopId"),
2484
- decision,
2485
- leaseUntil,
2486
- now
2482
+ ...reservation,
2483
+ idempotencyKey: requireNonEmpty(reservation.idempotencyKey, "idempotencyKey"),
2484
+ loopId: requireNonEmpty(reservation.loopId, "loopId"),
2485
+ reservationId: requireNonEmpty(reservation.reservationId, "reservationId"),
2486
+ correlationId: requireNonEmpty(reservation.correlationId, "correlationId"),
2487
+ policyHash: requireNonEmpty(reservation.policyHash, "policyHash")
2487
2488
  });
2488
2489
  return result;
2489
2490
  },
@@ -2496,15 +2497,32 @@ function createStateBackendFromEnv(env = process.env, client) {
2496
2497
  now
2497
2498
  });
2498
2499
  },
2499
- async finishAgencyDispatch(tenantId2, idempotencyKey, status, now, runId) {
2500
+ async finishAgencyDispatch(tenantId2, idempotencyKey, reservationId, status, now, runId) {
2500
2501
  await transport.mutation(anyApi.agencyModel.finishDispatch, {
2501
2502
  tenantId: requireTenant(tenantId2),
2502
2503
  idempotencyKey: requireNonEmpty(idempotencyKey, "idempotencyKey"),
2504
+ reservationId: requireNonEmpty(reservationId, "reservationId"),
2503
2505
  status,
2504
2506
  now,
2505
2507
  ...runId ? { runId: requireNonEmpty(runId, "runId") } : {}
2506
2508
  });
2507
2509
  },
2510
+ async createAgencyModelRun(tenantId2, subjectType, subjectId, run, now) {
2511
+ await transport.mutation(anyApi.agencyModel.createRunRecord, {
2512
+ tenantId: requireTenant(tenantId2),
2513
+ subjectType,
2514
+ subjectId: requireNonEmpty(subjectId, "subjectId"),
2515
+ run,
2516
+ now
2517
+ });
2518
+ },
2519
+ async finishAgencyModelRun(tenantId2, run, now) {
2520
+ await transport.mutation(anyApi.agencyModel.finishRunRecord, {
2521
+ tenantId: requireTenant(tenantId2),
2522
+ run,
2523
+ now
2524
+ });
2525
+ },
2508
2526
  async appendRunEvent(tenantId2, runId, goalId, event, time) {
2509
2527
  await transport.mutation(anyApi.runEvents.append, {
2510
2528
  tenantId: requireTenant(tenantId2),
@@ -12969,6 +12987,7 @@ var init_dispatchClassified = __esm({
12969
12987
 
12970
12988
  // src/goal/agencyModelRepository.ts
12971
12989
  import {
12990
+ createAgentDefinition,
12972
12991
  createCapabilityDefinition,
12973
12992
  createGoalDefinition,
12974
12993
  createGoalState,
@@ -12977,7 +12996,8 @@ import {
12977
12996
  createLoopState,
12978
12997
  createOperationDefinition,
12979
12998
  createRunOutput,
12980
- createWorkflowDefinition
12999
+ createWorkflowDefinition,
13000
+ relationshipIssues
12981
13001
  } from "@kody-ade/agency-domain";
12982
13002
  function goalProgressFromOutputs(definition, outputs) {
12983
13003
  const required2 = definition.objective.requiredEvidence;
@@ -12987,28 +13007,65 @@ function goalProgressFromOutputs(definition, outputs) {
12987
13007
  );
12988
13008
  return required2.filter((key) => satisfied.has(key)).length / required2.length;
12989
13009
  }
12990
- function validateAllDefinitions(documents) {
12991
- for (const document of documents) {
12992
- if (document.schemaVersion !== 1) throw new Error(`Unsupported Agency Definition schema: ${document.schemaVersion}`);
12993
- if (document.kind === "intent") createIntentDefinition(document.data);
12994
- else if (document.kind === "operation") createOperationDefinition(document.data);
12995
- else if (document.kind === "goal") createGoalDefinition(document.data);
12996
- else if (document.kind === "loop") createLoopDefinition(document.data);
12997
- else if (document.kind === "workflow") createWorkflowDefinition(document.data);
12998
- else createCapabilityDefinition(document.data);
12999
- }
13000
- }
13001
- function parseManagedDefinition(document) {
13002
- return document.kind === "goal" ? createGoalDefinition(document.data) : createLoopDefinition(document.data);
13003
- }
13004
- function parseState(document, definition) {
13010
+ function parseState(document, definition, kind) {
13005
13011
  if (!document) return null;
13006
13012
  if (document.schemaVersion !== 1) throw new Error(`Unsupported Agency State schema: ${document.schemaVersion}`);
13007
- if (document.kind !== definition.kind || document.definitionId !== definition.recordId) {
13008
- throw new Error(`Agency State does not match Definition: ${definition.recordId}`);
13013
+ if (document.kind !== kind || document.definitionId !== definition.id) {
13014
+ throw new Error(`Agency State does not match Definition: ${definition.id}`);
13009
13015
  }
13010
13016
  return document.kind === "goal" ? createGoalState(document.data) : createLoopState(document.data);
13011
13017
  }
13018
+ function emptyCatalog() {
13019
+ return {
13020
+ intents: /* @__PURE__ */ new Map(),
13021
+ operations: /* @__PURE__ */ new Map(),
13022
+ goals: /* @__PURE__ */ new Map(),
13023
+ loops: /* @__PURE__ */ new Map(),
13024
+ workflows: /* @__PURE__ */ new Map(),
13025
+ capabilities: /* @__PURE__ */ new Map(),
13026
+ agents: /* @__PURE__ */ new Map()
13027
+ };
13028
+ }
13029
+ function addDefinition(catalog, document) {
13030
+ if (document.schemaVersion !== 1) throw new Error(`Unsupported Agency Definition schema: ${document.schemaVersion}`);
13031
+ if (document.kind === "intent") add(catalog.intents, createIntentDefinition(document.data), document.recordId);
13032
+ else if (document.kind === "operation") add(catalog.operations, createOperationDefinition(document.data), document.recordId);
13033
+ else if (document.kind === "goal") add(catalog.goals, createGoalDefinition(document.data), document.recordId);
13034
+ else if (document.kind === "loop") add(catalog.loops, createLoopDefinition(document.data), document.recordId);
13035
+ else if (document.kind === "workflow") add(catalog.workflows, createWorkflowDefinition(document.data), document.recordId);
13036
+ else if (document.kind === "capability") add(catalog.capabilities, createCapabilityDefinition(document.data), document.recordId);
13037
+ else add(catalog.agents, createAgentDefinition(document.data), document.recordId);
13038
+ }
13039
+ function add(collection, definition, revision) {
13040
+ const mutable = collection;
13041
+ if (mutable.has(definition.id)) throw new Error(`Duplicate Agency Definition: ${definition.id}`);
13042
+ mutable.set(definition.id, { definition, revision });
13043
+ }
13044
+ function validateRelationships(catalog) {
13045
+ const relationshipCatalog = {
13046
+ operations: [...catalog.operations.keys()],
13047
+ goals: [...catalog.goals.keys()],
13048
+ workflows: [...catalog.workflows.keys()],
13049
+ capabilities: [...catalog.capabilities.keys()]
13050
+ };
13051
+ const issues = [...catalog.goals.values(), ...catalog.loops.values()].flatMap(
13052
+ ({ definition }) => relationshipIssues(definition, relationshipCatalog).map((issue2) => `${definition.id}: ${issue2}`)
13053
+ );
13054
+ for (const { definition } of catalog.operations.values()) {
13055
+ for (const intentId of definition.intentIds) {
13056
+ if (!catalog.intents.has(intentId)) issues.push(`${definition.id}: Missing Intent "${intentId}"`);
13057
+ }
13058
+ }
13059
+ for (const { definition } of catalog.workflows.values()) {
13060
+ for (const step of definition.steps) {
13061
+ if (!catalog.capabilities.has(step.capabilityRef.id)) {
13062
+ issues.push(`${definition.id}: Missing Capability "${step.capabilityRef.id}"`);
13063
+ }
13064
+ }
13065
+ }
13066
+ if (issues.length > 0) throw new Error(`Invalid Agency relationships:
13067
+ ${issues.join("\n")}`);
13068
+ }
13012
13069
  var AgencyModelRepository;
13013
13070
  var init_agencyModelRepository = __esm({
13014
13071
  "src/goal/agencyModelRepository.ts"() {
@@ -13020,19 +13077,31 @@ var init_agencyModelRepository = __esm({
13020
13077
  }
13021
13078
  backend;
13022
13079
  tenantId;
13023
- async listManagedWork() {
13024
- const documents = await this.backend.listAgencyDefinitions(this.tenantId);
13025
- validateAllDefinitions(documents);
13026
- const managed = documents.filter(
13027
- (document) => document.kind === "goal" || document.kind === "loop"
13028
- );
13080
+ async listManagedWork(catalog) {
13081
+ const definitions = catalog ?? await this.loadCatalog();
13082
+ const managed = [
13083
+ ...Array.from(definitions.goals.values(), ({ definition, revision }) => ({ definition, revision, kind: "goal" })),
13084
+ ...Array.from(definitions.loops.values(), ({ definition, revision }) => ({ definition, revision, kind: "loop" }))
13085
+ ];
13029
13086
  return Promise.all(
13030
- managed.map(async (document) => ({
13031
- definition: parseManagedDefinition(document),
13032
- state: parseState(await this.backend.getAgencyState(this.tenantId, document.recordId), document)
13087
+ managed.map(async (record2) => ({
13088
+ definition: record2.definition,
13089
+ revision: record2.revision,
13090
+ state: parseState(
13091
+ await this.backend.getAgencyState(this.tenantId, record2.definition.id),
13092
+ record2.definition,
13093
+ record2.kind
13094
+ )
13033
13095
  }))
13034
13096
  );
13035
13097
  }
13098
+ async loadCatalog() {
13099
+ const documents = await this.backend.listAgencyDefinitions(this.tenantId);
13100
+ const catalog = emptyCatalog();
13101
+ for (const document of documents) addDefinition(catalog, document);
13102
+ validateRelationships(catalog);
13103
+ return catalog;
13104
+ }
13036
13105
  async saveState(state, kind, updatedAt) {
13037
13106
  const data = kind === "goal" ? createGoalState(state) : createLoopState(state);
13038
13107
  await this.backend.putAgencyState(this.tenantId, state.definitionId, kind, 1, data, updatedAt);
@@ -13117,18 +13186,106 @@ var init_triggerDispatcher = __esm({
13117
13186
  }
13118
13187
  });
13119
13188
 
13189
+ // src/goal/policyResolver.ts
13190
+ import { createHash as createHash2 } from "crypto";
13191
+ function resolveDispatchPolicy(input) {
13192
+ const operation = input.catalog.operations.get(input.owner.definition.operationId);
13193
+ if (!operation) throw new Error(`Dispatch blocked: Operation "${input.owner.definition.operationId}" is unresolved`);
13194
+ if (operation.definition.intentIds.length === 0) {
13195
+ throw new Error(`Dispatch blocked: Operation "${operation.definition.id}" has no Intent`);
13196
+ }
13197
+ const intents = operation.definition.intentIds.map((intentId) => {
13198
+ const intent = input.catalog.intents.get(intentId);
13199
+ if (!intent) throw new Error(`Dispatch blocked: Intent "${intentId}" is unresolved`);
13200
+ return intent;
13201
+ });
13202
+ const policy = mergePolicies(intents.map(({ definition }) => definition.policy));
13203
+ const constraints = intents.flatMap(({ definition }) => definition.constraints);
13204
+ const requiresApproval = assertAuthorized(policy, constraints, input.target);
13205
+ const snapshotValue = { policy, constraints };
13206
+ return {
13207
+ snapshot: {
13208
+ hash: createHash2("sha256").update(stableJson(snapshotValue)).digest("hex"),
13209
+ ...snapshotValue
13210
+ },
13211
+ operation,
13212
+ intents,
13213
+ trace: [
13214
+ pinned("trigger" in input.owner.definition ? "loop" : "goal", input.owner),
13215
+ input.target
13216
+ ],
13217
+ requiresApproval
13218
+ };
13219
+ }
13220
+ function mergePolicies(policies) {
13221
+ const approvalOrder = ["none", "risky-actions", "all-actions"];
13222
+ return {
13223
+ approval: policies.reduce(
13224
+ (strictest, policy) => approvalOrder.indexOf(policy.approval) > approvalOrder.indexOf(strictest) ? policy.approval : strictest,
13225
+ "none"
13226
+ ),
13227
+ authority: {
13228
+ allow: intersect(policies.map(({ authority }) => authority.allow)),
13229
+ deny: unique(policies.flatMap(({ authority }) => authority.deny))
13230
+ },
13231
+ budget: {
13232
+ maxRuns: Math.min(...policies.map(({ budget }) => budget.maxRuns)),
13233
+ maxTokens: Math.min(...policies.map(({ budget }) => budget.maxTokens)),
13234
+ maxCostUsd: Math.min(...policies.map(({ budget }) => budget.maxCostUsd)),
13235
+ maxDurationSeconds: Math.min(...policies.map(({ budget }) => budget.maxDurationSeconds))
13236
+ },
13237
+ maxConcurrentRuns: Math.min(...policies.map(({ maxConcurrentRuns }) => maxConcurrentRuns)),
13238
+ riskyActions: unique(policies.flatMap(({ riskyActions }) => riskyActions))
13239
+ };
13240
+ }
13241
+ function assertAuthorized(policy, constraints, target) {
13242
+ const action = `${target.kind}:${target.id}`;
13243
+ const matches = (patterns) => patterns.some((pattern) => pattern === "*" || pattern === target.id || pattern === action);
13244
+ if (matches(policy.authority.deny)) throw new Error(`Dispatch blocked: authority denies "${action}"`);
13245
+ if (!matches(policy.authority.allow)) throw new Error(`Dispatch blocked: authority does not allow "${action}"`);
13246
+ const matchingConstraints = constraints.filter(({ actions }) => matches(actions));
13247
+ const denied = matchingConstraints.find(({ effect }) => effect === "deny");
13248
+ if (denied) throw new Error(`Dispatch blocked by constraint "${denied.id}": ${denied.rule}`);
13249
+ const requiresApproval = policy.approval === "all-actions" || policy.approval === "risky-actions" && matches(policy.riskyActions) || matchingConstraints.some(({ effect }) => effect === "require-approval");
13250
+ return requiresApproval;
13251
+ }
13252
+ function pinned(kind, record2) {
13253
+ return { kind, id: record2.definition.id, revision: record2.revision };
13254
+ }
13255
+ function intersect(groups) {
13256
+ if (groups.length === 0) return [];
13257
+ if (groups.every((group) => group.includes("*"))) return ["*"];
13258
+ const candidates = unique(groups.flatMap((group) => group.filter((item) => item !== "*")));
13259
+ return candidates.filter((candidate) => groups.every((group) => group.includes("*") || group.includes(candidate)));
13260
+ }
13261
+ function unique(values) {
13262
+ return [...new Set(values)].sort();
13263
+ }
13264
+ function stableJson(value) {
13265
+ if (Array.isArray(value)) return `[${value.map(stableJson).join(",")}]`;
13266
+ if (value && typeof value === "object") {
13267
+ return `{${Object.entries(value).sort(([left], [right]) => left.localeCompare(right)).map(([key, item]) => `${JSON.stringify(key)}:${stableJson(item)}`).join(",")}}`;
13268
+ }
13269
+ return JSON.stringify(value);
13270
+ }
13271
+ var init_policyResolver = __esm({
13272
+ "src/goal/policyResolver.ts"() {
13273
+ "use strict";
13274
+ }
13275
+ });
13276
+
13120
13277
  // src/scripts/dispatchAgencyLoops.ts
13121
- import { createLoopState as createLoopState2 } from "@kody-ade/agency-domain";
13278
+ import { randomUUID } from "crypto";
13279
+ import {
13280
+ createLoopState as createLoopState2,
13281
+ createRun
13282
+ } from "@kody-ade/agency-domain";
13122
13283
  async function dispatchAgencyLoopsWith(input) {
13123
13284
  const repository = new AgencyModelRepository(input.backend, input.tenantId);
13124
- const records = await repository.listManagedWork();
13125
- const goals = new Map(
13126
- records.filter(
13127
- (record2) => "executionRef" in record2.definition
13128
- ).map((record2) => [record2.definition.id, record2.definition])
13129
- );
13130
- const loops = records.filter(
13131
- (record2) => "trigger" in record2.definition
13285
+ const catalog = await repository.loadCatalog();
13286
+ const records = await repository.listManagedWork(catalog);
13287
+ const loops = records.flatMap(
13288
+ (record2) => "trigger" in record2.definition ? [{ ...record2, definition: record2.definition, state: record2.state }] : []
13132
13289
  );
13133
13290
  const results = [];
13134
13291
  for (const record2 of loops) {
@@ -13147,17 +13304,56 @@ async function dispatchAgencyLoopsWith(input) {
13147
13304
  results.push({ loopId: record2.definition.id, decision: "skipped", reason: decision.reason });
13148
13305
  continue;
13149
13306
  }
13307
+ let target;
13308
+ let policy;
13309
+ try {
13310
+ target = resolveTarget(record2.definition, catalog);
13311
+ policy = resolveDispatchPolicy({
13312
+ catalog,
13313
+ owner: { definition: record2.definition, revision: record2.revision },
13314
+ target: target.reference
13315
+ });
13316
+ } catch (error) {
13317
+ const reason = error instanceof Error ? error.message : String(error);
13318
+ await input.backend.recordSkippedAgencyDispatch(
13319
+ input.tenantId,
13320
+ decision.idempotencyKey,
13321
+ record2.definition.id,
13322
+ { kind: "skip", reason, scheduledAt: decision.scheduledAt },
13323
+ now
13324
+ );
13325
+ results.push({ loopId: record2.definition.id, decision: "skipped", reason });
13326
+ continue;
13327
+ }
13150
13328
  const leaseUntil = new Date(input.now.getTime() + 15 * 6e4).toISOString();
13151
- const reservation = await input.backend.reserveAgencyDispatch(
13152
- input.tenantId,
13153
- decision.idempotencyKey,
13154
- record2.definition.id,
13329
+ const reservationId = `reservation-${randomUUID()}`;
13330
+ const correlationId = `corr-${randomUUID()}`;
13331
+ const trace = [policy.trace[0], ...target.intermediate, target.reference];
13332
+ const reservation = await input.backend.reserveAgencyDispatch(input.tenantId, {
13333
+ idempotencyKey: decision.idempotencyKey,
13334
+ loopId: record2.definition.id,
13155
13335
  decision,
13156
13336
  leaseUntil,
13337
+ reservationId,
13338
+ correlationId,
13339
+ policyHash: policy.snapshot.hash,
13340
+ effectivePolicy: policy.snapshot,
13341
+ definitionRefs: trace,
13342
+ maxConcurrentRuns: policy.snapshot.policy.maxConcurrentRuns,
13343
+ requiresApproval: policy.requiresApproval,
13344
+ approvalScopeKind: "loop",
13345
+ approvalScopeId: record2.definition.id,
13346
+ approvalAction: `${target.reference.kind}:${target.reference.id}`,
13157
13347
  now
13158
- );
13348
+ });
13159
13349
  if (!reservation.acquired) {
13160
- results.push({ loopId: record2.definition.id, decision: "duplicate", reason: "trigger firing already reserved" });
13350
+ const duplicate = reservation.reason === "duplicate";
13351
+ const reason = reservation.reason === "approval-required" ? "dispatch is waiting for approval" : reservation.reason === "concurrency-limit" ? "dispatch is waiting for policy capacity" : "trigger firing already reserved";
13352
+ results.push({
13353
+ loopId: record2.definition.id,
13354
+ decision: duplicate ? "duplicate" : "skipped",
13355
+ reason
13356
+ });
13161
13357
  continue;
13162
13358
  }
13163
13359
  const runningState = createLoopState2({
@@ -13169,14 +13365,40 @@ async function dispatchAgencyLoopsWith(input) {
13169
13365
  updatedAt: now
13170
13366
  });
13171
13367
  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
+ });
13172
13379
  try {
13173
- const output = await input.run(jobForTarget(record2.definition, goals));
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);
13174
13388
  const succeeded = output.exitCode === 0;
13389
+ const finishedAt = (/* @__PURE__ */ new Date()).toISOString();
13390
+ await input.backend.finishAgencyModelRun(
13391
+ input.tenantId,
13392
+ terminalRun(activeRun, succeeded ? "succeeded" : "failed", finishedAt),
13393
+ finishedAt
13394
+ );
13175
13395
  await input.backend.finishAgencyDispatch(
13176
13396
  input.tenantId,
13177
13397
  decision.idempotencyKey,
13398
+ reservationId,
13178
13399
  succeeded ? "dispatched" : "failed",
13179
- (/* @__PURE__ */ new Date()).toISOString()
13400
+ finishedAt,
13401
+ runId
13180
13402
  );
13181
13403
  await repository.saveState(
13182
13404
  createLoopState2({
@@ -13195,19 +13417,40 @@ async function dispatchAgencyLoopsWith(input) {
13195
13417
  });
13196
13418
  } catch (error) {
13197
13419
  const reason = error instanceof Error ? error.message : String(error);
13198
- await input.backend.finishAgencyDispatch(input.tenantId, decision.idempotencyKey, "failed", (/* @__PURE__ */ new Date()).toISOString());
13420
+ const finishedAt = (/* @__PURE__ */ new Date()).toISOString();
13421
+ await input.backend.finishAgencyModelRun(input.tenantId, terminalRun(activeRun, "failed", finishedAt), finishedAt).catch(() => void 0);
13422
+ await input.backend.finishAgencyDispatch(
13423
+ input.tenantId,
13424
+ decision.idempotencyKey,
13425
+ reservationId,
13426
+ "failed",
13427
+ finishedAt,
13428
+ runId
13429
+ );
13199
13430
  results.push({ loopId: record2.definition.id, decision: "failed", reason });
13200
13431
  }
13201
13432
  }
13202
13433
  return results;
13203
13434
  }
13204
- function jobForTarget(loop, goals) {
13205
- const target = loop.targetRef.kind === "goal" ? goals.get(loop.targetRef.id)?.executionRef : loop.targetRef;
13206
- if (!target) throw new Error(`Loop target Goal is missing: ${loop.targetRef.id}`);
13207
- if (target.kind === "workflow") {
13208
- return { workflow: target.id, cliArgs: {}, flavor: "scheduled" };
13209
- }
13210
- return { capability: target.id, cliArgs: {}, flavor: "scheduled" };
13435
+ function resolveTarget(loop, catalog) {
13436
+ const goal = loop.targetRef.kind === "goal" ? catalog.goals.get(loop.targetRef.id) : void 0;
13437
+ if (loop.targetRef.kind === "goal" && !goal) throw new Error(`Loop target Goal is missing: ${loop.targetRef.id}`);
13438
+ if (goal && goal.definition.operationId !== loop.operationId) {
13439
+ throw new Error(`Loop and target Goal must belong to the same Operation`);
13440
+ }
13441
+ const target = goal?.definition.executionRef ?? loop.targetRef;
13442
+ if (target.kind === "goal") throw new Error("Nested Goal target is invalid");
13443
+ const record2 = target.kind === "workflow" ? catalog.workflows.get(target.id) : catalog.capabilities.get(target.id);
13444
+ if (!record2) throw new Error(`Loop execution target is missing: ${target.kind}:${target.id}`);
13445
+ const reference = { kind: target.kind, id: target.id, revision: record2.revision };
13446
+ return {
13447
+ reference,
13448
+ intermediate: goal ? [{ kind: "goal", id: goal.definition.id, revision: goal.revision }] : [],
13449
+ job: target.kind === "workflow" ? { workflow: target.id, cliArgs: {}, flavor: "scheduled" } : { capability: target.id, cliArgs: {}, flavor: "scheduled" }
13450
+ };
13451
+ }
13452
+ function terminalRun(active, status, finishedAt) {
13453
+ return createRun({ ...active, status, finishedAt });
13211
13454
  }
13212
13455
  function repositoryTenant(config) {
13213
13456
  const owner = config.github?.owner?.trim() || process.env.GITHUB_REPOSITORY?.split("/")[0]?.trim();
@@ -13220,6 +13463,7 @@ var init_dispatchAgencyLoops = __esm({
13220
13463
  "use strict";
13221
13464
  init_agencyModelRepository();
13222
13465
  init_triggerDispatcher();
13466
+ init_policyResolver();
13223
13467
  init_job();
13224
13468
  init_state_backend();
13225
13469
  dispatchAgencyLoops = async (ctx) => {
@@ -15078,9 +15322,9 @@ var init_kodyVariables = __esm({
15078
15322
  });
15079
15323
 
15080
15324
  // src/backendVault.ts
15081
- import { createDecipheriv, createHash as createHash2 } from "crypto";
15325
+ import { createDecipheriv, createHash as createHash3 } from "crypto";
15082
15326
  function cacheKey(owner, repo, masterKey) {
15083
- const keyHash = createHash2("sha256").update(masterKey).digest("hex").slice(0, 16);
15327
+ const keyHash = createHash3("sha256").update(masterKey).digest("hex").slice(0, 16);
15084
15328
  return `${owner}/${repo}:${keyHash}`.toLowerCase();
15085
15329
  }
15086
15330
  function decryptVault(payload, masterKey) {
@@ -15729,7 +15973,7 @@ var init_notifyTerminal = __esm({
15729
15973
  });
15730
15974
 
15731
15975
  // src/scripts/openAgencyModelReviewPr.ts
15732
- import { createHash as createHash3 } from "crypto";
15976
+ import { createHash as createHash4 } from "crypto";
15733
15977
  function parseAgencyModelProposal(raw) {
15734
15978
  const text2 = raw.trim();
15735
15979
  const jsonText = (text2.match(/^```(?:json)?\s*\n([\s\S]*?)\n```$/i)?.[1] ?? text2).trim();
@@ -15781,7 +16025,7 @@ function normalizeBundleFiles(bundle) {
15781
16025
  });
15782
16026
  }
15783
16027
  function buildProposalId(issueNumber, bundle, sourceLabel) {
15784
- const digest = createHash3("sha256").update(JSON.stringify({ issueNumber, sourceLabel, title: bundle.title, files: normalizeBundleFiles(bundle) })).digest("hex").slice(0, 16);
16028
+ const digest = createHash4("sha256").update(JSON.stringify({ issueNumber, sourceLabel, title: bundle.title, files: normalizeBundleFiles(bundle) })).digest("hex").slice(0, 16);
15785
16029
  return `issue-${issueNumber}-${digest}`;
15786
16030
  }
15787
16031
  function isDryRun(ctx) {
@@ -17853,9 +18097,9 @@ var init_runFlow = __esm({
17853
18097
  });
17854
18098
 
17855
18099
  // src/scripts/previewBuildHelpers.ts
17856
- import { createDecipheriv as createDecipheriv2, createHash as createHash4, hkdfSync as hkdfSync2 } from "crypto";
18100
+ import { createDecipheriv as createDecipheriv2, createHash as createHash5, hkdfSync as hkdfSync2 } from "crypto";
17857
18101
  function shortHash(s) {
17858
- return createHash4("sha256").update(s).digest("hex").slice(0, 6);
18102
+ return createHash5("sha256").update(s).digest("hex").slice(0, 6);
17859
18103
  }
17860
18104
  function previewAppName(repo, pr) {
17861
18105
  const [owner, name] = repo.split("/");
@@ -17888,7 +18132,7 @@ function formatPreviewComment(args) {
17888
18132
  ].join("\n");
17889
18133
  }
17890
18134
  function defaultImageTag(repo, ref) {
17891
- return createHash4("sha256").update(`${repo}@${ref}`).digest("hex").slice(0, 12);
18135
+ return createHash5("sha256").update(`${repo}@${ref}`).digest("hex").slice(0, 12);
17892
18136
  }
17893
18137
  var init_previewBuildHelpers = __esm({
17894
18138
  "src/scripts/previewBuildHelpers.ts"() {
@@ -25313,7 +25557,7 @@ init_config();
25313
25557
  init_fetchRepoMcp();
25314
25558
 
25315
25559
  // src/servers/mcpHttpServer.ts
25316
- import { randomUUID } from "crypto";
25560
+ import { randomUUID as randomUUID2 } from "crypto";
25317
25561
  import { createServer as createServer4 } from "http";
25318
25562
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
25319
25563
  import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
@@ -25322,7 +25566,7 @@ function buildMcpHttpServer(opts) {
25322
25566
  const transports = /* @__PURE__ */ new Map();
25323
25567
  for (const route of opts.routes) {
25324
25568
  const transport = new StreamableHTTPServerTransport({
25325
- sessionIdGenerator: () => randomUUID()
25569
+ sessionIdGenerator: () => randomUUID2()
25326
25570
  });
25327
25571
  transports.set(route.path, transport);
25328
25572
  routes.set(route.path, route.name);
@@ -25894,7 +26138,7 @@ init_config();
25894
26138
 
25895
26139
  // src/definition-hydration.ts
25896
26140
  init_state_backend();
25897
- import { createHash as createHash5 } from "crypto";
26141
+ import { createHash as createHash6 } from "crypto";
25898
26142
  import * as fs50 from "fs";
25899
26143
  import * as path51 from "path";
25900
26144
  var SLUG_RE2 = /^[a-z0-9][a-z0-9_-]{0,63}$/;
@@ -25915,7 +26159,7 @@ function normalizeDefinitionBundle(bundle) {
25915
26159
  return { schemaVersion: 1, files };
25916
26160
  }
25917
26161
  function definitionVersion(bundle) {
25918
- return `sha256:${createHash5("sha256").update(JSON.stringify(normalizeDefinitionBundle(bundle))).digest("hex")}`;
26162
+ return `sha256:${createHash6("sha256").update(JSON.stringify(normalizeDefinitionBundle(bundle))).digest("hex")}`;
25919
26163
  }
25920
26164
  function verifyDefinition(definition) {
25921
26165
  if (!SLUG_RE2.test(definition.slug)) throw new Error(`invalid definition slug: ${definition.slug}`);
@@ -26016,12 +26260,12 @@ import { createServer as createServer5 } from "http";
26016
26260
 
26017
26261
  // src/pool/agency-loop-tick.ts
26018
26262
  function normalizeRepositories(repositories) {
26019
- const unique = /* @__PURE__ */ new Set();
26263
+ const unique2 = /* @__PURE__ */ new Set();
26020
26264
  for (const raw of repositories) {
26021
26265
  const repo = raw.trim().toLowerCase();
26022
- if (/^[^/\s]+\/[^/\s]+$/.test(repo)) unique.add(repo);
26266
+ if (/^[^/\s]+\/[^/\s]+$/.test(repo)) unique2.add(repo);
26023
26267
  }
26024
- return [...unique].sort();
26268
+ return [...unique2].sort();
26025
26269
  }
26026
26270
  async function runAgencyLoopTick(deps) {
26027
26271
  const repositories = normalizeRepositories(await deps.discover());
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kody-ade/kody-engine",
3
- "version": "0.4.414",
3
+ "version": "0.4.416",
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",
@@ -12,32 +12,10 @@
12
12
  "templates",
13
13
  "kody.config.schema.json"
14
14
  ],
15
- "scripts": {
16
- "kody:run": "tsx bin/kody.ts",
17
- "serve": "tsx bin/kody.ts serve",
18
- "serve:vscode": "tsx bin/kody.ts serve vscode",
19
- "serve:claude": "tsx bin/kody.ts serve claude",
20
- "clean:dist": "node scripts/clean-dist.cjs",
21
- "build": "pnpm clean:dist && tsup && node scripts/copy-assets.cjs",
22
- "check:modularity": "tsx scripts/check-script-modularity.ts",
23
- "pretest": "pnpm check:modularity",
24
- "test": "vitest run tests/unit tests/int --coverage",
25
- "posttest": "tsx scripts/check-coverage-floor.ts",
26
- "test:smoke": "vitest run tests/smoke --no-coverage",
27
- "test:e2e": "vitest run tests/e2e --no-coverage",
28
- "test:all": "vitest run tests --no-coverage",
29
- "typecheck": "tsc --noEmit",
30
- "lint": "biome check",
31
- "lint:fix": "biome check --write",
32
- "format": "biome format --write",
33
- "verify:package": "node scripts/verify-package-tarball.cjs",
34
- "brain:publish": "docker buildx build --platform linux/amd64 -f runner/Dockerfile.brain -t ghcr.io/${KODY_BRAIN_GHCR_OWNER:-aharonyaircohen}/kody-brain:latest --push runner",
35
- "prepublishOnly": "pnpm typecheck && vitest run tests/unit tests/int --no-coverage && pnpm build && pnpm verify:package"
36
- },
37
15
  "dependencies": {
38
- "@kody-ade/agency-domain": "0.1.1",
39
16
  "@actions/cache": "^6.0.0",
40
17
  "@anthropic-ai/claude-agent-sdk": "0.2.119",
18
+ "@kody-ade/agency-domain": "0.4.0",
41
19
  "@modelcontextprotocol/sdk": "^1.29.0",
42
20
  "convex": "^1.17.0",
43
21
  "zod": "^4.0.0"
@@ -59,5 +37,26 @@
59
37
  "url": "git+https://github.com/aharonyaircohen/kody-engine.git"
60
38
  },
61
39
  "homepage": "https://github.com/aharonyaircohen/kody-engine",
62
- "bugs": "https://github.com/aharonyaircohen/kody-engine/issues"
63
- }
40
+ "bugs": "https://github.com/aharonyaircohen/kody-engine/issues",
41
+ "scripts": {
42
+ "kody:run": "tsx bin/kody.ts",
43
+ "serve": "tsx bin/kody.ts serve",
44
+ "serve:vscode": "tsx bin/kody.ts serve vscode",
45
+ "serve:claude": "tsx bin/kody.ts serve claude",
46
+ "clean:dist": "node scripts/clean-dist.cjs",
47
+ "build": "pnpm clean:dist && tsup && node scripts/copy-assets.cjs",
48
+ "check:modularity": "tsx scripts/check-script-modularity.ts",
49
+ "pretest": "pnpm check:modularity",
50
+ "test": "vitest run tests/unit tests/int --coverage",
51
+ "posttest": "tsx scripts/check-coverage-floor.ts",
52
+ "test:smoke": "vitest run tests/smoke --no-coverage",
53
+ "test:e2e": "vitest run tests/e2e --no-coverage",
54
+ "test:all": "vitest run tests --no-coverage",
55
+ "typecheck": "tsc --noEmit",
56
+ "lint": "biome check",
57
+ "lint:fix": "biome check --write",
58
+ "format": "biome format --write",
59
+ "verify:package": "node scripts/verify-package-tarball.cjs",
60
+ "brain:publish": "docker buildx build --platform linux/amd64 -f runner/Dockerfile.brain -t ghcr.io/${KODY_BRAIN_GHCR_OWNER:-aharonyaircohen}/kody-brain:latest --push runner"
61
+ }
62
+ }