@kody-ade/kody-engine 0.4.412 → 0.4.413

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 +303 -1
  2. package/package.json +1 -1
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.412",
18
+ version: "0.4.413",
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",
@@ -2476,6 +2476,35 @@ 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) {
2480
+ const result = await transport.mutation(anyApi.agencyModel.reserveDispatch, {
2481
+ tenantId: requireTenant(tenantId2),
2482
+ idempotencyKey: requireNonEmpty(idempotencyKey, "idempotencyKey"),
2483
+ loopId: requireNonEmpty(loopId, "loopId"),
2484
+ decision,
2485
+ leaseUntil,
2486
+ now
2487
+ });
2488
+ return result;
2489
+ },
2490
+ async recordSkippedAgencyDispatch(tenantId2, idempotencyKey, loopId, decision, now) {
2491
+ await transport.mutation(anyApi.agencyModel.recordSkippedDispatch, {
2492
+ tenantId: requireTenant(tenantId2),
2493
+ idempotencyKey: requireNonEmpty(idempotencyKey, "idempotencyKey"),
2494
+ loopId: requireNonEmpty(loopId, "loopId"),
2495
+ decision,
2496
+ now
2497
+ });
2498
+ },
2499
+ async finishAgencyDispatch(tenantId2, idempotencyKey, status, now, runId) {
2500
+ await transport.mutation(anyApi.agencyModel.finishDispatch, {
2501
+ tenantId: requireTenant(tenantId2),
2502
+ idempotencyKey: requireNonEmpty(idempotencyKey, "idempotencyKey"),
2503
+ status,
2504
+ now,
2505
+ ...runId ? { runId: requireNonEmpty(runId, "runId") } : {}
2506
+ });
2507
+ },
2479
2508
  async appendRunEvent(tenantId2, runId, goalId, event, time) {
2480
2509
  await transport.mutation(anyApi.runEvents.append, {
2481
2510
  tenantId: requireTenant(tenantId2),
@@ -12938,6 +12967,277 @@ var init_dispatchClassified = __esm({
12938
12967
  }
12939
12968
  });
12940
12969
 
12970
+ // src/goal/agencyModelRepository.ts
12971
+ import {
12972
+ createCapabilityDefinition,
12973
+ createGoalDefinition,
12974
+ createGoalState,
12975
+ createIntentDefinition,
12976
+ createLoopDefinition,
12977
+ createLoopState,
12978
+ createOperationDefinition,
12979
+ createRunOutput,
12980
+ createWorkflowDefinition
12981
+ } from "@kody-ade/agency-domain";
12982
+ function goalProgressFromOutputs(definition, outputs) {
12983
+ const required2 = definition.objective.requiredEvidence;
12984
+ if (required2.length === 0) return 1;
12985
+ const satisfied = new Set(
12986
+ outputs.filter((output) => output.kind === "evidence" && output.value === true).map((output) => output.key)
12987
+ );
12988
+ return required2.filter((key) => satisfied.has(key)).length / required2.length;
12989
+ }
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) {
13005
+ if (!document) return null;
13006
+ 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}`);
13009
+ }
13010
+ return document.kind === "goal" ? createGoalState(document.data) : createLoopState(document.data);
13011
+ }
13012
+ var AgencyModelRepository;
13013
+ var init_agencyModelRepository = __esm({
13014
+ "src/goal/agencyModelRepository.ts"() {
13015
+ "use strict";
13016
+ AgencyModelRepository = class {
13017
+ constructor(backend, tenantId2) {
13018
+ this.backend = backend;
13019
+ this.tenantId = tenantId2;
13020
+ }
13021
+ backend;
13022
+ 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
+ );
13029
+ return Promise.all(
13030
+ managed.map(async (document) => ({
13031
+ definition: parseManagedDefinition(document),
13032
+ state: parseState(await this.backend.getAgencyState(this.tenantId, document.recordId), document)
13033
+ }))
13034
+ );
13035
+ }
13036
+ async saveState(state, kind, updatedAt) {
13037
+ const data = kind === "goal" ? createGoalState(state) : createLoopState(state);
13038
+ await this.backend.putAgencyState(this.tenantId, state.definitionId, kind, 1, data, updatedAt);
13039
+ }
13040
+ async appendOutput(recordId, output) {
13041
+ await this.backend.appendAgencyOutput(this.tenantId, recordId, 1, createRunOutput(output));
13042
+ }
13043
+ async listOutputs(runId) {
13044
+ const documents = await this.backend.listAgencyOutputs(this.tenantId, runId);
13045
+ return documents.map((document) => {
13046
+ if (document.schemaVersion !== 1) {
13047
+ throw new Error(`Unsupported Agency Output schema: ${document.schemaVersion}`);
13048
+ }
13049
+ const output = createRunOutput(document.data);
13050
+ if (output.runId !== document.runId) throw new Error(`Agency Output does not match Run: ${document.recordId}`);
13051
+ return output;
13052
+ });
13053
+ }
13054
+ async refreshGoalProgress(record2, updatedAt) {
13055
+ if (!("executionRef" in record2.definition)) throw new Error("Only a Goal has progress");
13056
+ const previous = record2.state;
13057
+ if (previous && !("progress" in previous)) throw new Error("Goal Definition has Loop State");
13058
+ const state = createGoalState({
13059
+ definitionId: record2.definition.id,
13060
+ lifecycle: previous?.lifecycle ?? "draft",
13061
+ progress: goalProgressFromOutputs(record2.definition, await this.listOutputs()),
13062
+ blockers: previous?.blockers ?? [],
13063
+ updatedAt
13064
+ });
13065
+ await this.saveState(state, "goal", updatedAt);
13066
+ return state;
13067
+ }
13068
+ };
13069
+ }
13070
+ });
13071
+
13072
+ // src/goal/triggerDispatcher.ts
13073
+ function decideTrigger(input) {
13074
+ if (!input.state) return { kind: "skip", reason: "loop has no runtime state" };
13075
+ if (input.state.lifecycle !== "active") {
13076
+ return { kind: "skip", reason: `loop is ${input.state.lifecycle}` };
13077
+ }
13078
+ const trigger = input.definition.trigger;
13079
+ if (trigger.type === "manual") {
13080
+ if (!input.manualRequestId?.trim()) return { kind: "skip", reason: "manual trigger was not requested" };
13081
+ return {
13082
+ kind: "fire",
13083
+ reason: "manual trigger was requested",
13084
+ scheduledAt: input.now.toISOString(),
13085
+ idempotencyKey: `${input.definition.id}:manual:${input.manualRequestId.trim()}`
13086
+ };
13087
+ }
13088
+ if (trigger.type !== "schedule") {
13089
+ return { kind: "skip", reason: `${trigger.type} trigger is not enabled yet` };
13090
+ }
13091
+ const interval = parseInterval(trigger.every);
13092
+ const anchor = input.state.lastFiredAt ? Date.parse(input.state.lastFiredAt) : input.now.getTime() - interval;
13093
+ const dueAt = anchor + interval;
13094
+ if (input.now.getTime() < dueAt) {
13095
+ return { kind: "skip", reason: "scheduled trigger is not due", nextEligibleAt: new Date(dueAt).toISOString() };
13096
+ }
13097
+ const elapsedIntervals = Math.max(1, Math.floor((input.now.getTime() - anchor) / interval));
13098
+ const scheduledAt = new Date(anchor + elapsedIntervals * interval).toISOString();
13099
+ return {
13100
+ kind: "fire",
13101
+ reason: "scheduled trigger is due",
13102
+ scheduledAt,
13103
+ idempotencyKey: `${input.definition.id}:schedule:${scheduledAt}`
13104
+ };
13105
+ }
13106
+ function parseInterval(value) {
13107
+ const match = value.trim().match(/^(\d+)(m|h|d)$/);
13108
+ if (!match) throw new Error(`Unsupported schedule interval: ${value}`);
13109
+ const amount = Number(match[1]);
13110
+ if (!Number.isSafeInteger(amount) || amount < 1) throw new Error(`Unsupported schedule interval: ${value}`);
13111
+ const unit = match[2] === "m" ? 6e4 : match[2] === "h" ? 36e5 : 864e5;
13112
+ return amount * unit;
13113
+ }
13114
+ var init_triggerDispatcher = __esm({
13115
+ "src/goal/triggerDispatcher.ts"() {
13116
+ "use strict";
13117
+ }
13118
+ });
13119
+
13120
+ // src/scripts/dispatchAgencyLoops.ts
13121
+ import { createLoopState as createLoopState2 } from "@kody-ade/agency-domain";
13122
+ async function dispatchAgencyLoopsWith(input) {
13123
+ const repository = new AgencyModelRepository(input.backend, input.tenantId);
13124
+ const records = await repository.listManagedWork();
13125
+ const loops = records.filter(
13126
+ (record2) => "trigger" in record2.definition
13127
+ );
13128
+ const results = [];
13129
+ for (const record2 of loops) {
13130
+ const decision = decideTrigger({ definition: record2.definition, state: record2.state, now: input.now });
13131
+ const now = input.now.toISOString();
13132
+ if (decision.kind === "skip") {
13133
+ const key = `${record2.definition.id}:skip:${now}`;
13134
+ await input.backend.recordSkippedAgencyDispatch(input.tenantId, key, record2.definition.id, decision, now);
13135
+ if (record2.state && decision.nextEligibleAt) {
13136
+ await repository.saveState(
13137
+ createLoopState2({ ...record2.state, nextEligibleAt: decision.nextEligibleAt, updatedAt: now }),
13138
+ "loop",
13139
+ now
13140
+ );
13141
+ }
13142
+ results.push({ loopId: record2.definition.id, decision: "skipped", reason: decision.reason });
13143
+ continue;
13144
+ }
13145
+ const leaseUntil = new Date(input.now.getTime() + 15 * 6e4).toISOString();
13146
+ const reservation = await input.backend.reserveAgencyDispatch(
13147
+ input.tenantId,
13148
+ decision.idempotencyKey,
13149
+ record2.definition.id,
13150
+ decision,
13151
+ leaseUntil,
13152
+ now
13153
+ );
13154
+ if (!reservation.acquired) {
13155
+ results.push({ loopId: record2.definition.id, decision: "duplicate", reason: "trigger firing already reserved" });
13156
+ continue;
13157
+ }
13158
+ const runningState = createLoopState2({
13159
+ definitionId: record2.definition.id,
13160
+ lifecycle: record2.state?.lifecycle ?? "active",
13161
+ health: record2.state?.health ?? "unknown",
13162
+ failures: record2.state?.failures ?? 0,
13163
+ lastFiredAt: decision.scheduledAt,
13164
+ updatedAt: now
13165
+ });
13166
+ await repository.saveState(runningState, "loop", now);
13167
+ try {
13168
+ const output = await input.run(jobForTarget(record2.definition));
13169
+ const succeeded = output.exitCode === 0;
13170
+ await input.backend.finishAgencyDispatch(
13171
+ input.tenantId,
13172
+ decision.idempotencyKey,
13173
+ succeeded ? "dispatched" : "failed",
13174
+ (/* @__PURE__ */ new Date()).toISOString()
13175
+ );
13176
+ await repository.saveState(
13177
+ createLoopState2({
13178
+ ...runningState,
13179
+ health: succeeded ? "healthy" : "degraded",
13180
+ failures: succeeded ? 0 : runningState.failures + 1,
13181
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
13182
+ }),
13183
+ "loop",
13184
+ (/* @__PURE__ */ new Date()).toISOString()
13185
+ );
13186
+ results.push({
13187
+ loopId: record2.definition.id,
13188
+ decision: succeeded ? "dispatched" : "failed",
13189
+ reason: output.reason ?? (succeeded ? "target dispatched" : "target failed")
13190
+ });
13191
+ } catch (error) {
13192
+ const reason = error instanceof Error ? error.message : String(error);
13193
+ await input.backend.finishAgencyDispatch(input.tenantId, decision.idempotencyKey, "failed", (/* @__PURE__ */ new Date()).toISOString());
13194
+ results.push({ loopId: record2.definition.id, decision: "failed", reason });
13195
+ }
13196
+ }
13197
+ return results;
13198
+ }
13199
+ function jobForTarget(loop) {
13200
+ if (loop.targetRef.kind === "workflow") {
13201
+ return { workflow: loop.targetRef.id, cliArgs: {}, flavor: "scheduled" };
13202
+ }
13203
+ if (loop.targetRef.kind === "capability") {
13204
+ return { capability: loop.targetRef.id, cliArgs: {}, flavor: "scheduled" };
13205
+ }
13206
+ return {
13207
+ capability: "goal-manager",
13208
+ implementation: "goal-manager",
13209
+ cliArgs: { goal: loop.targetRef.id },
13210
+ flavor: "scheduled"
13211
+ };
13212
+ }
13213
+ function repositoryTenant(config) {
13214
+ const owner = config.github?.owner?.trim() || process.env.GITHUB_REPOSITORY?.split("/")[0]?.trim();
13215
+ const repo = config.github?.repo?.trim() || process.env.GITHUB_REPOSITORY?.split("/")[1]?.trim();
13216
+ return owner && repo ? `${owner}/${repo}` : null;
13217
+ }
13218
+ var dispatchAgencyLoops;
13219
+ var init_dispatchAgencyLoops = __esm({
13220
+ "src/scripts/dispatchAgencyLoops.ts"() {
13221
+ "use strict";
13222
+ init_agencyModelRepository();
13223
+ init_triggerDispatcher();
13224
+ init_job();
13225
+ init_state_backend();
13226
+ dispatchAgencyLoops = async (ctx) => {
13227
+ const tenantId2 = repositoryTenant(ctx.config);
13228
+ if (!tenantId2) throw new Error("Repository identity is required for Agency Loop dispatch");
13229
+ const backend = createStateBackendFromEnv();
13230
+ const results = await dispatchAgencyLoopsWith({
13231
+ tenantId: tenantId2,
13232
+ backend,
13233
+ now: /* @__PURE__ */ new Date(),
13234
+ run: (job) => runJob(job, { cwd: ctx.cwd, config: ctx.config, verbose: ctx.verbose, quiet: ctx.quiet, chain: false })
13235
+ });
13236
+ ctx.data.agencyLoopDispatchResults = results;
13237
+ };
13238
+ }
13239
+ });
13240
+
12941
13241
  // src/jobIdentity.ts
12942
13242
  function stableJobKey(job) {
12943
13243
  const capability = job.workflow ?? job.capability ?? job.action;
@@ -19526,6 +19826,7 @@ var init_scripts = __esm({
19526
19826
  init_dispatchCapabilityFileTicks();
19527
19827
  init_dispatchCapabilityTicks();
19528
19828
  init_dispatchClassified();
19829
+ init_dispatchAgencyLoops();
19529
19830
  init_dispatchNextTaskJob();
19530
19831
  init_ensurePr();
19531
19832
  init_evaluateAgencyBoundaries();
@@ -19646,6 +19947,7 @@ var init_scripts = __esm({
19646
19947
  diagMcp,
19647
19948
  warmupMcp,
19648
19949
  dispatchCapabilityTicks,
19950
+ dispatchAgencyLoops,
19649
19951
  dispatchCapabilityFileTicks,
19650
19952
  planTaskJobs,
19651
19953
  dispatchNextTaskJob,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kody-ade/kody-engine",
3
- "version": "0.4.412",
3
+ "version": "0.4.413",
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",