@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.
- package/dist/bin/kody.js +303 -1
- 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.
|
|
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.
|
|
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",
|