@kody-ade/kody-engine 0.4.471 → 0.4.473
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.
|
|
18
|
+
version: "0.4.473",
|
|
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",
|
|
@@ -53,7 +53,6 @@ var init_package = __esm({
|
|
|
53
53
|
dependencies: {
|
|
54
54
|
"@actions/cache": "^6.0.0",
|
|
55
55
|
"@anthropic-ai/claude-agent-sdk": "0.2.119",
|
|
56
|
-
"@kody-ade/agency-domain": "0.5.1",
|
|
57
56
|
"@kody-ade/engine-contracts": "0.1.0",
|
|
58
57
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
59
58
|
ajv: "^8.18.0",
|
|
@@ -2059,14 +2058,14 @@ function resolveImplementationCandidates(name, roots = getRuntimeProfileRootsFor
|
|
|
2059
2058
|
function listCapabilityActions(projectCapabilitiesRoot = getProjectCapabilitiesRoot()) {
|
|
2060
2059
|
const seen = /* @__PURE__ */ new Set();
|
|
2061
2060
|
const out = [];
|
|
2062
|
-
const
|
|
2061
|
+
const add = (action) => {
|
|
2063
2062
|
if (!isSafeName(action.action) || !isSafeName(action.capability) || !isSafeName(action.implementation)) return;
|
|
2064
2063
|
if (seen.has(action.action)) return;
|
|
2065
2064
|
seen.add(action.action);
|
|
2066
2065
|
out.push(action);
|
|
2067
2066
|
};
|
|
2068
|
-
for (const action of listFolderCapabilityActions(projectCapabilitiesRoot, "project-folder"))
|
|
2069
|
-
for (const action of listBuiltinCapabilityActions(getBuiltinCapabilitiesRoot()))
|
|
2067
|
+
for (const action of listFolderCapabilityActions(projectCapabilitiesRoot, "project-folder")) add(action);
|
|
2068
|
+
for (const action of listBuiltinCapabilityActions(getBuiltinCapabilitiesRoot())) add(action);
|
|
2070
2069
|
return out.sort((a, b) => a.action.localeCompare(b.action));
|
|
2071
2070
|
}
|
|
2072
2071
|
function resolveCapabilityAction(action, projectCapabilitiesRoot = getProjectCapabilitiesRoot()) {
|
|
@@ -2317,7 +2316,7 @@ var init_convex_client = __esm({
|
|
|
2317
2316
|
|
|
2318
2317
|
// src/kody-api-client.ts
|
|
2319
2318
|
import { getFunctionName } from "convex/server";
|
|
2320
|
-
function
|
|
2319
|
+
function resolveKodyApiUrl(env) {
|
|
2321
2320
|
return (env.KODY_API_URL?.trim() || env.KODY_DASHBOARD_URL?.trim() || env.DASHBOARD_URL?.trim() || DEFAULT_KODY_API_URL).replace(/\/$/, "");
|
|
2322
2321
|
}
|
|
2323
2322
|
function oidcRequestUrl(env) {
|
|
@@ -2356,7 +2355,7 @@ function operationName(fn) {
|
|
|
2356
2355
|
async function callKodyApi(kind, fn, args, env) {
|
|
2357
2356
|
const call = async (forceToken) => {
|
|
2358
2357
|
const token = await githubOidcToken(env, forceToken);
|
|
2359
|
-
return fetch(`${
|
|
2358
|
+
return fetch(`${resolveKodyApiUrl(env)}/api/kody/engine/backend`, {
|
|
2360
2359
|
method: "POST",
|
|
2361
2360
|
headers: {
|
|
2362
2361
|
Authorization: `Bearer ${token}`,
|
|
@@ -2386,7 +2385,7 @@ function createKodyApiBackendClient(env = process.env) {
|
|
|
2386
2385
|
}
|
|
2387
2386
|
async function readRuntimeSecretFromKody(name, env = process.env) {
|
|
2388
2387
|
const token = await githubOidcToken(env);
|
|
2389
|
-
const response = await fetch(`${
|
|
2388
|
+
const response = await fetch(`${resolveKodyApiUrl(env)}/api/kody/engine/secret`, {
|
|
2390
2389
|
method: "POST",
|
|
2391
2390
|
headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json" },
|
|
2392
2391
|
body: JSON.stringify({ name }),
|
|
@@ -2399,7 +2398,7 @@ async function readRuntimeSecretFromKody(name, env = process.env) {
|
|
|
2399
2398
|
}
|
|
2400
2399
|
async function readPreviewContextFromKody(env = process.env) {
|
|
2401
2400
|
const token = await githubOidcToken(env);
|
|
2402
|
-
const response = await fetch(`${
|
|
2401
|
+
const response = await fetch(`${resolveKodyApiUrl(env)}/api/kody/engine/preview-context`, {
|
|
2403
2402
|
method: "POST",
|
|
2404
2403
|
headers: { Authorization: `Bearer ${token}` },
|
|
2405
2404
|
signal: AbortSignal.timeout(3e4)
|
|
@@ -2411,7 +2410,7 @@ var DEFAULT_KODY_API_URL, OIDC_AUDIENCE, cachedToken;
|
|
|
2411
2410
|
var init_kody_api_client = __esm({
|
|
2412
2411
|
"src/kody-api-client.ts"() {
|
|
2413
2412
|
"use strict";
|
|
2414
|
-
DEFAULT_KODY_API_URL = "https://dashboard-
|
|
2413
|
+
DEFAULT_KODY_API_URL = "https://kody-dashboard-aguy.vercel.app";
|
|
2415
2414
|
OIDC_AUDIENCE = "kody-api";
|
|
2416
2415
|
cachedToken = null;
|
|
2417
2416
|
}
|
|
@@ -2419,12 +2418,11 @@ var init_kody_api_client = __esm({
|
|
|
2419
2418
|
|
|
2420
2419
|
// src/state-backend.ts
|
|
2421
2420
|
import { anyApi } from "convex/server";
|
|
2422
|
-
function
|
|
2421
|
+
function serializeLoopDispatchDecision(decision) {
|
|
2423
2422
|
return {
|
|
2424
2423
|
kind: decision.kind,
|
|
2425
2424
|
reason: decision.reason,
|
|
2426
|
-
...decision.scheduledAt ? { scheduledAt: decision.scheduledAt } : {}
|
|
2427
|
-
...decision.nextEligibleAt ? { nextEligibleAt: decision.nextEligibleAt } : {}
|
|
2425
|
+
...decision.scheduledAt ? { scheduledAt: decision.scheduledAt } : {}
|
|
2428
2426
|
};
|
|
2429
2427
|
}
|
|
2430
2428
|
function requireTenant(tenantId2) {
|
|
@@ -2539,52 +2537,11 @@ function createStateBackendFromEnv(env = process.env, client) {
|
|
|
2539
2537
|
updatedAt
|
|
2540
2538
|
});
|
|
2541
2539
|
},
|
|
2542
|
-
async
|
|
2543
|
-
const result = await transport.query(anyApi.agencyModel.listDefinitions, {
|
|
2544
|
-
tenantId: requireTenant(tenantId2)
|
|
2545
|
-
});
|
|
2546
|
-
return Array.isArray(result) ? result : [];
|
|
2547
|
-
},
|
|
2548
|
-
async getAgencyState(tenantId2, kind, definitionId2) {
|
|
2549
|
-
const result = await transport.query(anyApi.agencyModel.getState, {
|
|
2550
|
-
tenantId: requireTenant(tenantId2),
|
|
2551
|
-
kind,
|
|
2552
|
-
definitionId: requireNonEmpty(definitionId2, "definitionId")
|
|
2553
|
-
});
|
|
2554
|
-
return result ?? null;
|
|
2555
|
-
},
|
|
2556
|
-
async putAgencyState(tenantId2, definitionId2, kind, schemaVersion, data, updatedAt) {
|
|
2557
|
-
await transport.mutation(anyApi.agencyModel.putState, {
|
|
2558
|
-
tenantId: requireTenant(tenantId2),
|
|
2559
|
-
definitionId: requireNonEmpty(definitionId2, "definitionId"),
|
|
2560
|
-
kind,
|
|
2561
|
-
schemaVersion,
|
|
2562
|
-
data,
|
|
2563
|
-
updatedAt
|
|
2564
|
-
});
|
|
2565
|
-
},
|
|
2566
|
-
async appendAgencyOutput(tenantId2, recordId, schemaVersion, data) {
|
|
2567
|
-
await transport.mutation(anyApi.agencyModel.appendOutput, {
|
|
2568
|
-
tenantId: requireTenant(tenantId2),
|
|
2569
|
-
envelope: {
|
|
2570
|
-
schemaVersion,
|
|
2571
|
-
recordId: requireNonEmpty(recordId, "recordId"),
|
|
2572
|
-
data
|
|
2573
|
-
}
|
|
2574
|
-
});
|
|
2575
|
-
},
|
|
2576
|
-
async listAgencyOutputs(tenantId2, runId) {
|
|
2577
|
-
const result = await transport.query(anyApi.agencyModel.listOutputs, {
|
|
2578
|
-
tenantId: requireTenant(tenantId2),
|
|
2579
|
-
...runId ? { runId: requireNonEmpty(runId, "runId") } : {}
|
|
2580
|
-
});
|
|
2581
|
-
return Array.isArray(result) ? result : [];
|
|
2582
|
-
},
|
|
2583
|
-
async reserveAgencyDispatch(tenantId2, reservation) {
|
|
2540
|
+
async reserveLoopDispatch(tenantId2, reservation) {
|
|
2584
2541
|
const result = await transport.mutation(anyApi.agencyModel.reserveDispatch, {
|
|
2585
2542
|
tenantId: requireTenant(tenantId2),
|
|
2586
2543
|
...reservation,
|
|
2587
|
-
decision:
|
|
2544
|
+
decision: serializeLoopDispatchDecision(reservation.decision),
|
|
2588
2545
|
idempotencyKey: requireNonEmpty(reservation.idempotencyKey, "idempotencyKey"),
|
|
2589
2546
|
loopId: requireNonEmpty(reservation.loopId, "loopId"),
|
|
2590
2547
|
reservationId: requireNonEmpty(reservation.reservationId, "reservationId"),
|
|
@@ -2593,16 +2550,7 @@ function createStateBackendFromEnv(env = process.env, client) {
|
|
|
2593
2550
|
});
|
|
2594
2551
|
return result;
|
|
2595
2552
|
},
|
|
2596
|
-
async
|
|
2597
|
-
await transport.mutation(anyApi.agencyModel.recordSkippedDispatch, {
|
|
2598
|
-
tenantId: requireTenant(tenantId2),
|
|
2599
|
-
idempotencyKey: requireNonEmpty(idempotencyKey, "idempotencyKey"),
|
|
2600
|
-
loopId: requireNonEmpty(loopId, "loopId"),
|
|
2601
|
-
decision: serializeAgencyDispatchDecision(decision),
|
|
2602
|
-
now
|
|
2603
|
-
});
|
|
2604
|
-
},
|
|
2605
|
-
async finishAgencyDispatch(tenantId2, idempotencyKey, reservationId, status, now, runId) {
|
|
2553
|
+
async finishLoopDispatch(tenantId2, idempotencyKey, reservationId, status, now, runId) {
|
|
2606
2554
|
await transport.mutation(anyApi.agencyModel.finishDispatch, {
|
|
2607
2555
|
tenantId: requireTenant(tenantId2),
|
|
2608
2556
|
idempotencyKey: requireNonEmpty(idempotencyKey, "idempotencyKey"),
|
|
@@ -2612,7 +2560,7 @@ function createStateBackendFromEnv(env = process.env, client) {
|
|
|
2612
2560
|
...runId ? { runId: requireNonEmpty(runId, "runId") } : {}
|
|
2613
2561
|
});
|
|
2614
2562
|
},
|
|
2615
|
-
async
|
|
2563
|
+
async createAgencyRun(tenantId2, subjectType, subjectId, run, now) {
|
|
2616
2564
|
await transport.mutation(anyApi.agencyModel.createRunRecord, {
|
|
2617
2565
|
tenantId: requireTenant(tenantId2),
|
|
2618
2566
|
subjectType,
|
|
@@ -2621,7 +2569,7 @@ function createStateBackendFromEnv(env = process.env, client) {
|
|
|
2621
2569
|
now
|
|
2622
2570
|
});
|
|
2623
2571
|
},
|
|
2624
|
-
async
|
|
2572
|
+
async finishAgencyRun(tenantId2, run, now) {
|
|
2625
2573
|
await transport.mutation(anyApi.agencyModel.finishRunRecord, {
|
|
2626
2574
|
tenantId: requireTenant(tenantId2),
|
|
2627
2575
|
run,
|
|
@@ -13086,678 +13034,6 @@ var init_dispatch = __esm({
|
|
|
13086
13034
|
}
|
|
13087
13035
|
});
|
|
13088
13036
|
|
|
13089
|
-
// src/goal/agencyModelRepository.ts
|
|
13090
|
-
import {
|
|
13091
|
-
createAgentDefinition,
|
|
13092
|
-
createCapabilityDefinition,
|
|
13093
|
-
createGoalDefinition,
|
|
13094
|
-
createGoalState,
|
|
13095
|
-
createIntentDefinition,
|
|
13096
|
-
createLoopDefinition,
|
|
13097
|
-
createLoopState,
|
|
13098
|
-
createOperationDefinition,
|
|
13099
|
-
createRunOutput,
|
|
13100
|
-
createWorkflowDefinition,
|
|
13101
|
-
relationshipIssues
|
|
13102
|
-
} from "@kody-ade/agency-domain";
|
|
13103
|
-
function goalProgressFromOutputs(definition, revision, outputs) {
|
|
13104
|
-
const required2 = definition.objective.requiredEvidence;
|
|
13105
|
-
if (required2.length === 0) return 1;
|
|
13106
|
-
const satisfied = new Set(
|
|
13107
|
-
outputs.filter(
|
|
13108
|
-
(output) => output.kind === "evidence" && output.value === true && output.parentRef?.kind === "goal" && output.parentRef.id === definition.id && output.parentRef.revision === revision
|
|
13109
|
-
).map((output) => output.key)
|
|
13110
|
-
);
|
|
13111
|
-
return required2.filter((key) => satisfied.has(key)).length / required2.length;
|
|
13112
|
-
}
|
|
13113
|
-
function definitionId(document) {
|
|
13114
|
-
const data = document.data;
|
|
13115
|
-
if (typeof data.id !== "string" || !data.id.trim()) {
|
|
13116
|
-
throw new Error(`Agency Definition is missing id: ${document.recordId}`);
|
|
13117
|
-
}
|
|
13118
|
-
return data.id;
|
|
13119
|
-
}
|
|
13120
|
-
function parseState(document, definition, kind) {
|
|
13121
|
-
if (!document) return null;
|
|
13122
|
-
if (document.schemaVersion !== 1) throw new Error(`Unsupported Agency State schema: ${document.schemaVersion}`);
|
|
13123
|
-
if (document.kind !== kind || document.definitionId !== definition.id) {
|
|
13124
|
-
throw new Error(`Agency State does not match Definition: ${definition.id}`);
|
|
13125
|
-
}
|
|
13126
|
-
return document.kind === "goal" ? createGoalState(document.data) : createLoopState(document.data);
|
|
13127
|
-
}
|
|
13128
|
-
function emptyCatalog() {
|
|
13129
|
-
return {
|
|
13130
|
-
intents: /* @__PURE__ */ new Map(),
|
|
13131
|
-
operations: /* @__PURE__ */ new Map(),
|
|
13132
|
-
goals: /* @__PURE__ */ new Map(),
|
|
13133
|
-
loops: /* @__PURE__ */ new Map(),
|
|
13134
|
-
workflows: /* @__PURE__ */ new Map(),
|
|
13135
|
-
capabilities: /* @__PURE__ */ new Map(),
|
|
13136
|
-
agents: /* @__PURE__ */ new Map()
|
|
13137
|
-
};
|
|
13138
|
-
}
|
|
13139
|
-
function addDefinition(catalog, document) {
|
|
13140
|
-
if (document.schemaVersion !== 1) throw new Error(`Unsupported Agency Definition schema: ${document.schemaVersion}`);
|
|
13141
|
-
if (document.kind === "intent") add(catalog.intents, createIntentDefinition(document.data), document.recordId);
|
|
13142
|
-
else if (document.kind === "operation")
|
|
13143
|
-
add(catalog.operations, createOperationDefinition(document.data), document.recordId);
|
|
13144
|
-
else if (document.kind === "goal") add(catalog.goals, createGoalDefinition(document.data), document.recordId);
|
|
13145
|
-
else if (document.kind === "loop") add(catalog.loops, createLoopDefinition(document.data), document.recordId);
|
|
13146
|
-
else if (document.kind === "workflow")
|
|
13147
|
-
add(catalog.workflows, createWorkflowDefinition(document.data), document.recordId);
|
|
13148
|
-
else if (document.kind === "capability")
|
|
13149
|
-
add(catalog.capabilities, createCapabilityDefinition(document.data), document.recordId);
|
|
13150
|
-
else add(catalog.agents, createAgentDefinition(document.data), document.recordId);
|
|
13151
|
-
}
|
|
13152
|
-
function add(collection, definition, revision) {
|
|
13153
|
-
const mutable = collection;
|
|
13154
|
-
mutable.set(definition.id, { definition, revision });
|
|
13155
|
-
}
|
|
13156
|
-
function validateRelationships(catalog) {
|
|
13157
|
-
const relationshipCatalog = {
|
|
13158
|
-
operations: [...catalog.operations.keys()],
|
|
13159
|
-
goals: [...catalog.goals.keys()],
|
|
13160
|
-
workflows: [...catalog.workflows.keys()],
|
|
13161
|
-
capabilities: [...catalog.capabilities.keys()]
|
|
13162
|
-
};
|
|
13163
|
-
const issues = [...catalog.goals.values(), ...catalog.loops.values()].flatMap(
|
|
13164
|
-
({ definition }) => relationshipIssues(definition, relationshipCatalog).map((issue2) => `${definition.id}: ${issue2}`)
|
|
13165
|
-
);
|
|
13166
|
-
for (const { definition } of catalog.operations.values()) {
|
|
13167
|
-
for (const intentId of definition.intentIds) {
|
|
13168
|
-
if (!catalog.intents.has(intentId)) issues.push(`${definition.id}: Missing Intent "${intentId}"`);
|
|
13169
|
-
}
|
|
13170
|
-
}
|
|
13171
|
-
for (const { definition } of catalog.workflows.values()) {
|
|
13172
|
-
for (const step of definition.steps) {
|
|
13173
|
-
if (!catalog.capabilities.has(step.capabilityRef.id)) {
|
|
13174
|
-
issues.push(`${definition.id}: Missing Capability "${step.capabilityRef.id}"`);
|
|
13175
|
-
}
|
|
13176
|
-
}
|
|
13177
|
-
}
|
|
13178
|
-
if (issues.length > 0) throw new Error(`Invalid Agency relationships:
|
|
13179
|
-
${issues.join("\n")}`);
|
|
13180
|
-
}
|
|
13181
|
-
var AgencyModelRepository;
|
|
13182
|
-
var init_agencyModelRepository = __esm({
|
|
13183
|
-
"src/goal/agencyModelRepository.ts"() {
|
|
13184
|
-
"use strict";
|
|
13185
|
-
AgencyModelRepository = class {
|
|
13186
|
-
constructor(backend, tenantId2) {
|
|
13187
|
-
this.backend = backend;
|
|
13188
|
-
this.tenantId = tenantId2;
|
|
13189
|
-
}
|
|
13190
|
-
backend;
|
|
13191
|
-
tenantId;
|
|
13192
|
-
async listManagedWork(catalog) {
|
|
13193
|
-
const definitions = catalog ?? await this.loadCatalog();
|
|
13194
|
-
const managed = [
|
|
13195
|
-
...Array.from(definitions.goals.values(), ({ definition, revision }) => ({
|
|
13196
|
-
definition,
|
|
13197
|
-
revision,
|
|
13198
|
-
kind: "goal"
|
|
13199
|
-
})),
|
|
13200
|
-
...Array.from(definitions.loops.values(), ({ definition, revision }) => ({
|
|
13201
|
-
definition,
|
|
13202
|
-
revision,
|
|
13203
|
-
kind: "loop"
|
|
13204
|
-
}))
|
|
13205
|
-
];
|
|
13206
|
-
return Promise.all(
|
|
13207
|
-
managed.map(async (record2) => ({
|
|
13208
|
-
definition: record2.definition,
|
|
13209
|
-
revision: record2.revision,
|
|
13210
|
-
state: parseState(
|
|
13211
|
-
await this.backend.getAgencyState(this.tenantId, record2.kind, record2.definition.id),
|
|
13212
|
-
record2.definition,
|
|
13213
|
-
record2.kind
|
|
13214
|
-
)
|
|
13215
|
-
}))
|
|
13216
|
-
);
|
|
13217
|
-
}
|
|
13218
|
-
async loadCatalog() {
|
|
13219
|
-
const documents = await this.backend.listAgencyDefinitions(this.tenantId);
|
|
13220
|
-
const catalog = emptyCatalog();
|
|
13221
|
-
const ordered = [...documents].sort(
|
|
13222
|
-
(left, right) => left.createdAt.localeCompare(right.createdAt) || left.recordId.localeCompare(right.recordId)
|
|
13223
|
-
);
|
|
13224
|
-
const latest = /* @__PURE__ */ new Map();
|
|
13225
|
-
for (const document of ordered) {
|
|
13226
|
-
latest.set(`${document.kind}:${definitionId(document)}`, document);
|
|
13227
|
-
}
|
|
13228
|
-
for (const document of latest.values()) addDefinition(catalog, document);
|
|
13229
|
-
validateRelationships(catalog);
|
|
13230
|
-
return catalog;
|
|
13231
|
-
}
|
|
13232
|
-
async saveState(state, kind, updatedAt) {
|
|
13233
|
-
const data = kind === "goal" ? createGoalState(state) : createLoopState(state);
|
|
13234
|
-
await this.backend.putAgencyState(this.tenantId, state.definitionId, kind, 1, data, updatedAt);
|
|
13235
|
-
}
|
|
13236
|
-
async appendOutput(recordId, output) {
|
|
13237
|
-
await this.backend.appendAgencyOutput(this.tenantId, recordId, 1, createRunOutput(output));
|
|
13238
|
-
}
|
|
13239
|
-
async listOutputs(runId) {
|
|
13240
|
-
const documents = await this.backend.listAgencyOutputs(this.tenantId, runId);
|
|
13241
|
-
return documents.map((document) => {
|
|
13242
|
-
if (document.schemaVersion !== 1) {
|
|
13243
|
-
throw new Error(`Unsupported Agency Output schema: ${document.schemaVersion}`);
|
|
13244
|
-
}
|
|
13245
|
-
const output = createRunOutput(document.data);
|
|
13246
|
-
if (output.runId !== document.runId) throw new Error(`Agency Output does not match Run: ${document.recordId}`);
|
|
13247
|
-
return output;
|
|
13248
|
-
});
|
|
13249
|
-
}
|
|
13250
|
-
async refreshGoalProgress(record2, updatedAt) {
|
|
13251
|
-
if (!("executionRef" in record2.definition)) throw new Error("Only a Goal has progress");
|
|
13252
|
-
const previous = record2.state;
|
|
13253
|
-
if (previous && !("progress" in previous)) throw new Error("Goal Definition has Loop State");
|
|
13254
|
-
const state = createGoalState({
|
|
13255
|
-
definitionId: record2.definition.id,
|
|
13256
|
-
lifecycle: previous?.lifecycle ?? "draft",
|
|
13257
|
-
progress: goalProgressFromOutputs(record2.definition, record2.revision, await this.listOutputs()),
|
|
13258
|
-
blockers: previous?.blockers ?? [],
|
|
13259
|
-
updatedAt
|
|
13260
|
-
});
|
|
13261
|
-
await this.saveState(state, "goal", updatedAt);
|
|
13262
|
-
return state;
|
|
13263
|
-
}
|
|
13264
|
-
};
|
|
13265
|
-
}
|
|
13266
|
-
});
|
|
13267
|
-
|
|
13268
|
-
// src/goal/policyResolver.ts
|
|
13269
|
-
import { createHash as createHash5 } from "crypto";
|
|
13270
|
-
function resolveDispatchPolicy(input) {
|
|
13271
|
-
const operation = input.catalog.operations.get(input.owner.definition.operationId);
|
|
13272
|
-
if (!operation) throw new Error(`Dispatch blocked: Operation "${input.owner.definition.operationId}" is unresolved`);
|
|
13273
|
-
if (operation.definition.intentIds.length === 0) {
|
|
13274
|
-
throw new Error(`Dispatch blocked: Operation "${operation.definition.id}" has no Intent`);
|
|
13275
|
-
}
|
|
13276
|
-
const intents = operation.definition.intentIds.map((intentId) => {
|
|
13277
|
-
const intent = input.catalog.intents.get(intentId);
|
|
13278
|
-
if (!intent) throw new Error(`Dispatch blocked: Intent "${intentId}" is unresolved`);
|
|
13279
|
-
return intent;
|
|
13280
|
-
});
|
|
13281
|
-
const policy = mergePolicies(intents.map(({ definition }) => definition.policy));
|
|
13282
|
-
const constraints = intents.flatMap(({ definition }) => definition.constraints);
|
|
13283
|
-
const requiresApproval = assertAuthorized(policy, constraints, input.target);
|
|
13284
|
-
const snapshotValue = { policy, constraints };
|
|
13285
|
-
return {
|
|
13286
|
-
snapshot: {
|
|
13287
|
-
hash: createHash5("sha256").update(stableJson(snapshotValue)).digest("hex"),
|
|
13288
|
-
...snapshotValue
|
|
13289
|
-
},
|
|
13290
|
-
operation,
|
|
13291
|
-
intents,
|
|
13292
|
-
trace: [pinned("trigger" in input.owner.definition ? "loop" : "goal", input.owner), input.target],
|
|
13293
|
-
requiresApproval
|
|
13294
|
-
};
|
|
13295
|
-
}
|
|
13296
|
-
function mergePolicies(policies) {
|
|
13297
|
-
const approvalOrder = ["none", "risky-actions", "all-actions"];
|
|
13298
|
-
return {
|
|
13299
|
-
approval: policies.reduce(
|
|
13300
|
-
(strictest, policy) => approvalOrder.indexOf(policy.approval) > approvalOrder.indexOf(strictest) ? policy.approval : strictest,
|
|
13301
|
-
"none"
|
|
13302
|
-
),
|
|
13303
|
-
authority: {
|
|
13304
|
-
allow: intersect(policies.map(({ authority }) => authority.allow)),
|
|
13305
|
-
deny: unique(policies.flatMap(({ authority }) => authority.deny))
|
|
13306
|
-
},
|
|
13307
|
-
budget: {
|
|
13308
|
-
maxRuns: Math.min(...policies.map(({ budget }) => budget.maxRuns)),
|
|
13309
|
-
maxTokens: Math.min(...policies.map(({ budget }) => budget.maxTokens)),
|
|
13310
|
-
maxCostUsd: Math.min(...policies.map(({ budget }) => budget.maxCostUsd)),
|
|
13311
|
-
maxDurationSeconds: Math.min(...policies.map(({ budget }) => budget.maxDurationSeconds))
|
|
13312
|
-
},
|
|
13313
|
-
maxConcurrentRuns: Math.min(...policies.map(({ maxConcurrentRuns }) => maxConcurrentRuns)),
|
|
13314
|
-
riskyActions: unique(policies.flatMap(({ riskyActions }) => riskyActions))
|
|
13315
|
-
};
|
|
13316
|
-
}
|
|
13317
|
-
function assertAuthorized(policy, constraints, target) {
|
|
13318
|
-
const action = `${target.kind}:${target.id}`;
|
|
13319
|
-
const matches = (patterns) => patterns.some((pattern) => pattern === "*" || pattern === target.id || pattern === action);
|
|
13320
|
-
if (matches(policy.authority.deny)) throw new Error(`Dispatch blocked: authority denies "${action}"`);
|
|
13321
|
-
if (!matches(policy.authority.allow)) throw new Error(`Dispatch blocked: authority does not allow "${action}"`);
|
|
13322
|
-
const matchingConstraints = constraints.filter(({ actions }) => matches(actions));
|
|
13323
|
-
const denied = matchingConstraints.find(({ effect }) => effect === "deny");
|
|
13324
|
-
if (denied) throw new Error(`Dispatch blocked by constraint "${denied.id}": ${denied.rule}`);
|
|
13325
|
-
const requiresApproval = policy.approval === "all-actions" || policy.approval === "risky-actions" && matches(policy.riskyActions) || matchingConstraints.some(({ effect }) => effect === "require-approval");
|
|
13326
|
-
return requiresApproval;
|
|
13327
|
-
}
|
|
13328
|
-
function pinned(kind, record2) {
|
|
13329
|
-
return { kind, id: record2.definition.id, revision: record2.revision };
|
|
13330
|
-
}
|
|
13331
|
-
function intersect(groups) {
|
|
13332
|
-
if (groups.length === 0) return [];
|
|
13333
|
-
if (groups.every((group) => group.includes("*"))) return ["*"];
|
|
13334
|
-
const candidates = unique(groups.flatMap((group) => group.filter((item) => item !== "*")));
|
|
13335
|
-
return candidates.filter((candidate) => groups.every((group) => group.includes("*") || group.includes(candidate)));
|
|
13336
|
-
}
|
|
13337
|
-
function unique(values) {
|
|
13338
|
-
return [...new Set(values)].sort();
|
|
13339
|
-
}
|
|
13340
|
-
function stableJson(value) {
|
|
13341
|
-
if (Array.isArray(value)) return `[${value.map(stableJson).join(",")}]`;
|
|
13342
|
-
if (value && typeof value === "object") {
|
|
13343
|
-
return `{${Object.entries(value).sort(([left], [right]) => left.localeCompare(right)).map(([key, item]) => `${JSON.stringify(key)}:${stableJson(item)}`).join(",")}}`;
|
|
13344
|
-
}
|
|
13345
|
-
return JSON.stringify(value);
|
|
13346
|
-
}
|
|
13347
|
-
var init_policyResolver = __esm({
|
|
13348
|
-
"src/goal/policyResolver.ts"() {
|
|
13349
|
-
"use strict";
|
|
13350
|
-
}
|
|
13351
|
-
});
|
|
13352
|
-
|
|
13353
|
-
// src/goal/triggerDispatcher.ts
|
|
13354
|
-
function decideTrigger(input) {
|
|
13355
|
-
if (!input.state) return { kind: "skip", reason: "loop has no runtime state" };
|
|
13356
|
-
if (input.state.lifecycle !== "active") {
|
|
13357
|
-
return { kind: "skip", reason: `loop is ${input.state.lifecycle}` };
|
|
13358
|
-
}
|
|
13359
|
-
const trigger = input.definition.trigger;
|
|
13360
|
-
if (input.manualRequestId?.trim()) {
|
|
13361
|
-
return {
|
|
13362
|
-
kind: "fire",
|
|
13363
|
-
reason: "manual trigger was requested",
|
|
13364
|
-
scheduledAt: input.now.toISOString(),
|
|
13365
|
-
idempotencyKey: `${input.definition.id}:manual:${input.manualRequestId.trim()}`
|
|
13366
|
-
};
|
|
13367
|
-
}
|
|
13368
|
-
if (trigger.type === "manual") {
|
|
13369
|
-
return { kind: "skip", reason: "manual trigger was not requested" };
|
|
13370
|
-
}
|
|
13371
|
-
if (trigger.type !== "schedule") {
|
|
13372
|
-
return { kind: "skip", reason: `${trigger.type} trigger is not enabled yet` };
|
|
13373
|
-
}
|
|
13374
|
-
const interval = parseInterval(trigger.every);
|
|
13375
|
-
const anchor = input.state.lastFiredAt ? Date.parse(input.state.lastFiredAt) : input.now.getTime() - interval;
|
|
13376
|
-
const dueAt = anchor + interval;
|
|
13377
|
-
if (input.now.getTime() < dueAt) {
|
|
13378
|
-
return { kind: "skip", reason: "scheduled trigger is not due", nextEligibleAt: new Date(dueAt).toISOString() };
|
|
13379
|
-
}
|
|
13380
|
-
const elapsedIntervals = Math.max(1, Math.floor((input.now.getTime() - anchor) / interval));
|
|
13381
|
-
const scheduledAt = new Date(anchor + elapsedIntervals * interval).toISOString();
|
|
13382
|
-
return {
|
|
13383
|
-
kind: "fire",
|
|
13384
|
-
reason: "scheduled trigger is due",
|
|
13385
|
-
scheduledAt,
|
|
13386
|
-
idempotencyKey: `${input.definition.id}:schedule:${scheduledAt}`
|
|
13387
|
-
};
|
|
13388
|
-
}
|
|
13389
|
-
function parseInterval(value) {
|
|
13390
|
-
const match = value.trim().match(/^(\d+)(m|h|d)$/);
|
|
13391
|
-
if (!match) throw new Error(`Unsupported schedule interval: ${value}`);
|
|
13392
|
-
const amount = Number(match[1]);
|
|
13393
|
-
if (!Number.isSafeInteger(amount) || amount < 1) throw new Error(`Unsupported schedule interval: ${value}`);
|
|
13394
|
-
const unit = match[2] === "m" ? 6e4 : match[2] === "h" ? 36e5 : 864e5;
|
|
13395
|
-
return amount * unit;
|
|
13396
|
-
}
|
|
13397
|
-
var init_triggerDispatcher = __esm({
|
|
13398
|
-
"src/goal/triggerDispatcher.ts"() {
|
|
13399
|
-
"use strict";
|
|
13400
|
-
}
|
|
13401
|
-
});
|
|
13402
|
-
|
|
13403
|
-
// src/scripts/dispatchAgencyLoops.ts
|
|
13404
|
-
import { randomUUID } from "crypto";
|
|
13405
|
-
import {
|
|
13406
|
-
createLoopState as createLoopState2,
|
|
13407
|
-
createRun
|
|
13408
|
-
} from "@kody-ade/agency-domain";
|
|
13409
|
-
async function dispatchAgencyLoopsWith(input) {
|
|
13410
|
-
const repository = new AgencyModelRepository(input.backend, input.tenantId);
|
|
13411
|
-
const catalog = await repository.loadCatalog();
|
|
13412
|
-
const records = await repository.listManagedWork(catalog);
|
|
13413
|
-
const loops = records.flatMap(
|
|
13414
|
-
(record2) => "trigger" in record2.definition ? [{ ...record2, definition: record2.definition, state: record2.state }] : []
|
|
13415
|
-
);
|
|
13416
|
-
const results = [];
|
|
13417
|
-
for (const record2 of loops) {
|
|
13418
|
-
if (input.manualRequest && record2.definition.id !== input.manualRequest.loopId) {
|
|
13419
|
-
continue;
|
|
13420
|
-
}
|
|
13421
|
-
const decision = decideTrigger({
|
|
13422
|
-
definition: record2.definition,
|
|
13423
|
-
state: record2.state,
|
|
13424
|
-
now: input.now,
|
|
13425
|
-
...input.manualRequest ? { manualRequestId: input.manualRequest.requestId } : {}
|
|
13426
|
-
});
|
|
13427
|
-
const now = input.now.toISOString();
|
|
13428
|
-
if (decision.kind === "skip") {
|
|
13429
|
-
const key = `${record2.definition.id}:skip:${now}`;
|
|
13430
|
-
await input.backend.recordSkippedAgencyDispatch(input.tenantId, key, record2.definition.id, decision, now);
|
|
13431
|
-
if (record2.state && decision.nextEligibleAt) {
|
|
13432
|
-
await repository.saveState(
|
|
13433
|
-
createLoopState2({ ...record2.state, nextEligibleAt: decision.nextEligibleAt, updatedAt: now }),
|
|
13434
|
-
"loop",
|
|
13435
|
-
now
|
|
13436
|
-
);
|
|
13437
|
-
}
|
|
13438
|
-
results.push({ loopId: record2.definition.id, decision: "skipped", reason: decision.reason });
|
|
13439
|
-
continue;
|
|
13440
|
-
}
|
|
13441
|
-
let target;
|
|
13442
|
-
let policy;
|
|
13443
|
-
try {
|
|
13444
|
-
target = resolveTarget(record2.definition, catalog);
|
|
13445
|
-
policy = resolveDispatchPolicy({
|
|
13446
|
-
catalog,
|
|
13447
|
-
owner: { definition: record2.definition, revision: record2.revision },
|
|
13448
|
-
target: target.reference
|
|
13449
|
-
});
|
|
13450
|
-
} catch (error) {
|
|
13451
|
-
const reason = error instanceof Error ? error.message : String(error);
|
|
13452
|
-
await input.backend.recordSkippedAgencyDispatch(
|
|
13453
|
-
input.tenantId,
|
|
13454
|
-
decision.idempotencyKey,
|
|
13455
|
-
record2.definition.id,
|
|
13456
|
-
{ kind: "skip", reason, scheduledAt: decision.scheduledAt },
|
|
13457
|
-
now
|
|
13458
|
-
);
|
|
13459
|
-
results.push({ loopId: record2.definition.id, decision: "skipped", reason });
|
|
13460
|
-
continue;
|
|
13461
|
-
}
|
|
13462
|
-
const failurePolicy = record2.definition.reconciliationPolicy.failure;
|
|
13463
|
-
const budget = policy.snapshot.policy.budget;
|
|
13464
|
-
const maxAttempts = Math.min(failurePolicy.maxAttempts, budget.maxRuns);
|
|
13465
|
-
const timeoutSeconds = Math.min(failurePolicy.timeoutSeconds, budget.maxDurationSeconds);
|
|
13466
|
-
const backoffBudgetSeconds = Array.from(
|
|
13467
|
-
{ length: Math.max(0, maxAttempts - 1) },
|
|
13468
|
-
(_, index) => failurePolicy.backoffSeconds * 2 ** index
|
|
13469
|
-
).reduce((sum, seconds) => sum + seconds, 0);
|
|
13470
|
-
const leaseSeconds = Math.min(budget.maxDurationSeconds, maxAttempts * timeoutSeconds + backoffBudgetSeconds);
|
|
13471
|
-
const leaseUntil = new Date(input.now.getTime() + leaseSeconds * 1e3).toISOString();
|
|
13472
|
-
const reservationId = `reservation-${randomUUID()}`;
|
|
13473
|
-
const correlationId = `corr-${randomUUID()}`;
|
|
13474
|
-
const trace = [policy.trace[0], ...target.intermediate, target.reference];
|
|
13475
|
-
const reservation = await input.backend.reserveAgencyDispatch(input.tenantId, {
|
|
13476
|
-
idempotencyKey: decision.idempotencyKey,
|
|
13477
|
-
loopId: record2.definition.id,
|
|
13478
|
-
decision,
|
|
13479
|
-
leaseUntil,
|
|
13480
|
-
reservationId,
|
|
13481
|
-
correlationId,
|
|
13482
|
-
policyHash: policy.snapshot.hash,
|
|
13483
|
-
effectivePolicy: policy.snapshot,
|
|
13484
|
-
definitionRefs: trace,
|
|
13485
|
-
maxConcurrentRuns: policy.snapshot.policy.maxConcurrentRuns,
|
|
13486
|
-
requiresApproval: policy.requiresApproval,
|
|
13487
|
-
approvalScopeKind: "loop",
|
|
13488
|
-
approvalScopeId: record2.definition.id,
|
|
13489
|
-
approvalAction: `${target.reference.kind}:${target.reference.id}`,
|
|
13490
|
-
now
|
|
13491
|
-
});
|
|
13492
|
-
if (!reservation.acquired) {
|
|
13493
|
-
const duplicate = reservation.reason === "duplicate";
|
|
13494
|
-
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";
|
|
13495
|
-
results.push({
|
|
13496
|
-
loopId: record2.definition.id,
|
|
13497
|
-
decision: duplicate ? "duplicate" : "skipped",
|
|
13498
|
-
reason
|
|
13499
|
-
});
|
|
13500
|
-
continue;
|
|
13501
|
-
}
|
|
13502
|
-
const runningState = createLoopState2({
|
|
13503
|
-
definitionId: record2.definition.id,
|
|
13504
|
-
lifecycle: record2.state?.lifecycle ?? "active",
|
|
13505
|
-
health: record2.state?.health ?? "unknown",
|
|
13506
|
-
failures: record2.state?.failures ?? 0,
|
|
13507
|
-
lastFiredAt: decision.scheduledAt,
|
|
13508
|
-
updatedAt: now
|
|
13509
|
-
});
|
|
13510
|
-
await repository.saveState(runningState, "loop", now);
|
|
13511
|
-
try {
|
|
13512
|
-
let attempts = 0;
|
|
13513
|
-
let tokens = 0;
|
|
13514
|
-
let costUsd = 0;
|
|
13515
|
-
let succeeded = false;
|
|
13516
|
-
let reason = "target failed";
|
|
13517
|
-
let finalRunId;
|
|
13518
|
-
let finalCapabilityResults = [];
|
|
13519
|
-
const budgetDeadline = Date.now() + budget.maxDurationSeconds * 1e3;
|
|
13520
|
-
for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
|
|
13521
|
-
const remainingMilliseconds = budgetDeadline - Date.now();
|
|
13522
|
-
if (remainingMilliseconds <= 0) {
|
|
13523
|
-
reason = "policy duration budget exhausted";
|
|
13524
|
-
break;
|
|
13525
|
-
}
|
|
13526
|
-
attempts = attempt;
|
|
13527
|
-
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
13528
|
-
const runId = `run-${randomUUID()}`;
|
|
13529
|
-
finalRunId = runId;
|
|
13530
|
-
const activeRun = createRun({
|
|
13531
|
-
id: runId,
|
|
13532
|
-
status: "running",
|
|
13533
|
-
origin: { kind: "loop", id: record2.definition.id, revision: record2.revision },
|
|
13534
|
-
target: target.reference,
|
|
13535
|
-
trace,
|
|
13536
|
-
effectivePolicy: policy.snapshot,
|
|
13537
|
-
correlationId,
|
|
13538
|
-
startedAt
|
|
13539
|
-
});
|
|
13540
|
-
await input.backend.createAgencyModelRun(
|
|
13541
|
-
input.tenantId,
|
|
13542
|
-
target.reference.kind,
|
|
13543
|
-
target.reference.id,
|
|
13544
|
-
activeRun,
|
|
13545
|
-
startedAt
|
|
13546
|
-
);
|
|
13547
|
-
const attemptResult = await runAttempt(
|
|
13548
|
-
input.run,
|
|
13549
|
-
target.job,
|
|
13550
|
-
Math.min(timeoutSeconds, remainingMilliseconds / 1e3)
|
|
13551
|
-
);
|
|
13552
|
-
tokens += attemptResult.usage?.tokens ?? 0;
|
|
13553
|
-
costUsd += attemptResult.usage?.costUsd ?? 0;
|
|
13554
|
-
const finishedAt2 = (/* @__PURE__ */ new Date()).toISOString();
|
|
13555
|
-
succeeded = attemptResult.exitCode === 0;
|
|
13556
|
-
finalCapabilityResults = attemptResult.capabilityResults ?? [];
|
|
13557
|
-
reason = attemptResult.reason ?? (succeeded ? "target dispatched" : "target failed");
|
|
13558
|
-
const usage = {
|
|
13559
|
-
tokens: attemptResult.usage?.tokens ?? 0,
|
|
13560
|
-
costUsd: attemptResult.usage?.costUsd ?? 0,
|
|
13561
|
-
durationSeconds: Math.max(0, (Date.parse(finishedAt2) - Date.parse(startedAt)) / 1e3)
|
|
13562
|
-
};
|
|
13563
|
-
if (tokens > budget.maxTokens || costUsd > budget.maxCostUsd) {
|
|
13564
|
-
succeeded = false;
|
|
13565
|
-
reason = tokens > budget.maxTokens ? "policy token budget exhausted" : "policy cost budget exhausted";
|
|
13566
|
-
}
|
|
13567
|
-
await input.backend.finishAgencyModelRun(
|
|
13568
|
-
input.tenantId,
|
|
13569
|
-
terminalRun(activeRun, succeeded ? "succeeded" : "failed", finishedAt2, usage),
|
|
13570
|
-
finishedAt2
|
|
13571
|
-
);
|
|
13572
|
-
if (tokens > budget.maxTokens || costUsd > budget.maxCostUsd) break;
|
|
13573
|
-
if (succeeded || attempt === maxAttempts) break;
|
|
13574
|
-
const backoffMilliseconds = failurePolicy.backoffSeconds * 2 ** (attempt - 1) * 1e3;
|
|
13575
|
-
if (Date.now() + backoffMilliseconds >= budgetDeadline) {
|
|
13576
|
-
reason = "policy duration budget exhausted during retry backoff";
|
|
13577
|
-
break;
|
|
13578
|
-
}
|
|
13579
|
-
await wait(backoffMilliseconds);
|
|
13580
|
-
}
|
|
13581
|
-
const finishedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
13582
|
-
const goalRecord = record2.definition.targetRef.kind === "goal" ? records.find(
|
|
13583
|
-
(candidate) => "executionRef" in candidate.definition && candidate.definition.id === record2.definition.targetRef.id
|
|
13584
|
-
) : void 0;
|
|
13585
|
-
if (succeeded && finalRunId) {
|
|
13586
|
-
await appendCapabilityOutputs(
|
|
13587
|
-
repository,
|
|
13588
|
-
finalRunId,
|
|
13589
|
-
target.reference,
|
|
13590
|
-
goalRecord ? {
|
|
13591
|
-
kind: "goal",
|
|
13592
|
-
id: goalRecord.definition.id,
|
|
13593
|
-
revision: goalRecord.revision
|
|
13594
|
-
} : {
|
|
13595
|
-
kind: "loop",
|
|
13596
|
-
id: record2.definition.id,
|
|
13597
|
-
revision: record2.revision
|
|
13598
|
-
},
|
|
13599
|
-
finalCapabilityResults,
|
|
13600
|
-
finishedAt
|
|
13601
|
-
);
|
|
13602
|
-
}
|
|
13603
|
-
if (succeeded && goalRecord) {
|
|
13604
|
-
await repository.refreshGoalProgress(goalRecord, finishedAt);
|
|
13605
|
-
}
|
|
13606
|
-
await input.backend.finishAgencyDispatch(
|
|
13607
|
-
input.tenantId,
|
|
13608
|
-
decision.idempotencyKey,
|
|
13609
|
-
reservationId,
|
|
13610
|
-
succeeded ? "dispatched" : "dead-letter",
|
|
13611
|
-
finishedAt,
|
|
13612
|
-
finalRunId
|
|
13613
|
-
);
|
|
13614
|
-
await repository.saveState(
|
|
13615
|
-
createLoopState2({
|
|
13616
|
-
...runningState,
|
|
13617
|
-
health: succeeded ? "healthy" : "degraded",
|
|
13618
|
-
failures: succeeded ? 0 : runningState.failures + 1,
|
|
13619
|
-
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
13620
|
-
}),
|
|
13621
|
-
"loop",
|
|
13622
|
-
(/* @__PURE__ */ new Date()).toISOString()
|
|
13623
|
-
);
|
|
13624
|
-
results.push({
|
|
13625
|
-
loopId: record2.definition.id,
|
|
13626
|
-
decision: succeeded ? "dispatched" : "failed",
|
|
13627
|
-
reason: succeeded ? reason : `${reason}; dead-lettered after ${attempts} attempt${attempts === 1 ? "" : "s"}`
|
|
13628
|
-
});
|
|
13629
|
-
} catch (error) {
|
|
13630
|
-
const reason = error instanceof Error ? error.message : String(error);
|
|
13631
|
-
const finishedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
13632
|
-
await input.backend.finishAgencyDispatch(
|
|
13633
|
-
input.tenantId,
|
|
13634
|
-
decision.idempotencyKey,
|
|
13635
|
-
reservationId,
|
|
13636
|
-
"dead-letter",
|
|
13637
|
-
finishedAt
|
|
13638
|
-
);
|
|
13639
|
-
results.push({ loopId: record2.definition.id, decision: "failed", reason });
|
|
13640
|
-
}
|
|
13641
|
-
}
|
|
13642
|
-
return results;
|
|
13643
|
-
}
|
|
13644
|
-
function resolveTarget(loop, catalog) {
|
|
13645
|
-
const goal = loop.targetRef.kind === "goal" ? catalog.goals.get(loop.targetRef.id) : void 0;
|
|
13646
|
-
if (loop.targetRef.kind === "goal" && !goal) throw new Error(`Loop target Goal is missing: ${loop.targetRef.id}`);
|
|
13647
|
-
if (goal && goal.definition.operationId !== loop.operationId) {
|
|
13648
|
-
throw new Error(`Loop and target Goal must belong to the same Operation`);
|
|
13649
|
-
}
|
|
13650
|
-
const target = goal?.definition.executionRef ?? loop.targetRef;
|
|
13651
|
-
if (target.kind === "goal") throw new Error("Nested Goal target is invalid");
|
|
13652
|
-
const record2 = target.kind === "workflow" ? catalog.workflows.get(target.id) : catalog.capabilities.get(target.id);
|
|
13653
|
-
if (!record2) throw new Error(`Loop execution target is missing: ${target.kind}:${target.id}`);
|
|
13654
|
-
const reference = { kind: target.kind, id: target.id, revision: record2.revision };
|
|
13655
|
-
return {
|
|
13656
|
-
reference,
|
|
13657
|
-
intermediate: goal ? [{ kind: "goal", id: goal.definition.id, revision: goal.revision }] : [],
|
|
13658
|
-
job: target.kind === "workflow" ? { workflow: target.id, cliArgs: {}, flavor: "scheduled" } : { capability: target.id, cliArgs: {}, flavor: "scheduled" }
|
|
13659
|
-
};
|
|
13660
|
-
}
|
|
13661
|
-
function terminalRun(active, status, finishedAt, usage) {
|
|
13662
|
-
return createRun({ ...active, status, finishedAt, usage });
|
|
13663
|
-
}
|
|
13664
|
-
async function runAttempt(run, job, timeoutSeconds) {
|
|
13665
|
-
const abortController = new AbortController();
|
|
13666
|
-
let timer;
|
|
13667
|
-
try {
|
|
13668
|
-
return await Promise.race([
|
|
13669
|
-
run(job, abortController).catch((error) => ({
|
|
13670
|
-
exitCode: 99,
|
|
13671
|
-
reason: error instanceof Error ? error.message : String(error)
|
|
13672
|
-
})),
|
|
13673
|
-
new Promise((resolve19) => {
|
|
13674
|
-
timer = setTimeout(() => {
|
|
13675
|
-
abortController.abort();
|
|
13676
|
-
resolve19({ exitCode: 124, reason: `target timed out after ${formatSeconds(timeoutSeconds)}s` });
|
|
13677
|
-
}, timeoutSeconds * 1e3);
|
|
13678
|
-
})
|
|
13679
|
-
]);
|
|
13680
|
-
} finally {
|
|
13681
|
-
if (timer) clearTimeout(timer);
|
|
13682
|
-
}
|
|
13683
|
-
}
|
|
13684
|
-
async function appendCapabilityOutputs(repository, runId, producer, parentRef, results, createdAt) {
|
|
13685
|
-
for (const result of results) {
|
|
13686
|
-
const outputs = [
|
|
13687
|
-
...Object.entries(result.facts).map(([key, value]) => ({ kind: "fact", key, value })),
|
|
13688
|
-
...Object.entries(result.evidence ?? {}).map(([key, value]) => ({
|
|
13689
|
-
kind: "evidence",
|
|
13690
|
-
key,
|
|
13691
|
-
value
|
|
13692
|
-
})),
|
|
13693
|
-
...result.artifacts.map((artifact, index) => ({
|
|
13694
|
-
kind: "artifact",
|
|
13695
|
-
key: artifact.label || `artifact-${index + 1}`,
|
|
13696
|
-
value: artifact
|
|
13697
|
-
}))
|
|
13698
|
-
];
|
|
13699
|
-
for (const output of outputs) {
|
|
13700
|
-
await repository.appendOutput(`output-${randomUUID()}`, {
|
|
13701
|
-
...output,
|
|
13702
|
-
runId,
|
|
13703
|
-
producer: { kind: producer.kind, id: producer.id },
|
|
13704
|
-
parentRef,
|
|
13705
|
-
contract: "capability-result/v1",
|
|
13706
|
-
createdAt
|
|
13707
|
-
});
|
|
13708
|
-
}
|
|
13709
|
-
}
|
|
13710
|
-
}
|
|
13711
|
-
function formatSeconds(seconds) {
|
|
13712
|
-
return Number.isInteger(seconds) ? String(seconds) : seconds.toFixed(3).replace(/0+$/, "").replace(/\.$/, "");
|
|
13713
|
-
}
|
|
13714
|
-
async function wait(milliseconds) {
|
|
13715
|
-
if (milliseconds <= 0) return;
|
|
13716
|
-
await new Promise((resolve19) => setTimeout(resolve19, milliseconds));
|
|
13717
|
-
}
|
|
13718
|
-
function repositoryTenant(config) {
|
|
13719
|
-
const owner = config.github?.owner?.trim() || process.env.GITHUB_REPOSITORY?.split("/")[0]?.trim();
|
|
13720
|
-
const repo = config.github?.repo?.trim() || process.env.GITHUB_REPOSITORY?.split("/")[1]?.trim();
|
|
13721
|
-
return owner && repo ? `${owner}/${repo}` : null;
|
|
13722
|
-
}
|
|
13723
|
-
var dispatchAgencyLoops;
|
|
13724
|
-
var init_dispatchAgencyLoops = __esm({
|
|
13725
|
-
"src/scripts/dispatchAgencyLoops.ts"() {
|
|
13726
|
-
"use strict";
|
|
13727
|
-
init_agencyModelRepository();
|
|
13728
|
-
init_policyResolver();
|
|
13729
|
-
init_triggerDispatcher();
|
|
13730
|
-
init_job();
|
|
13731
|
-
init_state_backend();
|
|
13732
|
-
dispatchAgencyLoops = async (ctx) => {
|
|
13733
|
-
const tenantId2 = repositoryTenant(ctx.config);
|
|
13734
|
-
if (!tenantId2) throw new Error("Repository identity is required for Agency Loop dispatch");
|
|
13735
|
-
const backend = createStateBackendFromEnv();
|
|
13736
|
-
const requestedLoopId = typeof ctx.args.loop === "string" ? ctx.args.loop.trim() : "";
|
|
13737
|
-
const results = await dispatchAgencyLoopsWith({
|
|
13738
|
-
tenantId: tenantId2,
|
|
13739
|
-
backend,
|
|
13740
|
-
now: /* @__PURE__ */ new Date(),
|
|
13741
|
-
...requestedLoopId ? {
|
|
13742
|
-
manualRequest: {
|
|
13743
|
-
loopId: requestedLoopId,
|
|
13744
|
-
requestId: process.env.GITHUB_RUN_ID?.trim() || `local-${randomUUID()}`
|
|
13745
|
-
}
|
|
13746
|
-
} : {},
|
|
13747
|
-
run: (job, abortController) => runJob(job, {
|
|
13748
|
-
cwd: ctx.cwd,
|
|
13749
|
-
config: ctx.config,
|
|
13750
|
-
verbose: ctx.verbose,
|
|
13751
|
-
quiet: ctx.quiet,
|
|
13752
|
-
chain: false,
|
|
13753
|
-
abortController
|
|
13754
|
-
})
|
|
13755
|
-
});
|
|
13756
|
-
ctx.data.agencyLoopDispatchResults = results;
|
|
13757
|
-
};
|
|
13758
|
-
}
|
|
13759
|
-
});
|
|
13760
|
-
|
|
13761
13037
|
// src/scripts/dispatchCapabilityFileTicks.ts
|
|
13762
13038
|
var dispatchCapabilityFileTicks;
|
|
13763
13039
|
var init_dispatchCapabilityFileTicks = __esm({
|
|
@@ -13876,72 +13152,6 @@ var init_dispatchClassified = __esm({
|
|
|
13876
13152
|
}
|
|
13877
13153
|
});
|
|
13878
13154
|
|
|
13879
|
-
// src/jobIdentity.ts
|
|
13880
|
-
function stableJobKey(job) {
|
|
13881
|
-
const capability = job.workflow ?? job.capability ?? job.action;
|
|
13882
|
-
const implementation = job.implementation ?? capability ?? "unknown";
|
|
13883
|
-
if (job.flavor === "scheduled" && job.capability) return `scheduled:${job.capability}:${implementation}`;
|
|
13884
|
-
const target = typeof job.target === "number" ? job.target : targetFromCliArgs(job.cliArgs);
|
|
13885
|
-
const work = capability && implementation && implementation !== capability ? `${capability}:${implementation}` : capability ?? implementation;
|
|
13886
|
-
return target === void 0 ? `${job.flavor}:${work}` : `${job.flavor}:${work}:${target}`;
|
|
13887
|
-
}
|
|
13888
|
-
function targetFromCliArgs(cliArgs) {
|
|
13889
|
-
if (!cliArgs) return void 0;
|
|
13890
|
-
for (const key of ["issue", "pr", "target", "issue_number"]) {
|
|
13891
|
-
const value = cliArgs[key];
|
|
13892
|
-
if (typeof value === "number" && Number.isFinite(value)) return value;
|
|
13893
|
-
}
|
|
13894
|
-
return void 0;
|
|
13895
|
-
}
|
|
13896
|
-
var init_jobIdentity = __esm({
|
|
13897
|
-
"src/jobIdentity.ts"() {
|
|
13898
|
-
"use strict";
|
|
13899
|
-
}
|
|
13900
|
-
});
|
|
13901
|
-
|
|
13902
|
-
// src/scripts/dispatchNextTaskJob.ts
|
|
13903
|
-
function taskJobToJob(job, issueArg) {
|
|
13904
|
-
const target = typeof job.target === "number" ? job.target : typeof issueArg === "number" ? issueArg : void 0;
|
|
13905
|
-
return {
|
|
13906
|
-
capability: job.capability ?? job.implementation,
|
|
13907
|
-
implementation: job.implementation,
|
|
13908
|
-
...job.reason ? { why: job.reason } : {},
|
|
13909
|
-
...job.agent ? { agent: job.agent } : {},
|
|
13910
|
-
...job.schedule ? { schedule: job.schedule } : {},
|
|
13911
|
-
...typeof target === "number" ? { target, cliArgs: { issue: target } } : { cliArgs: {} },
|
|
13912
|
-
flavor: job.flavor ?? "instant"
|
|
13913
|
-
};
|
|
13914
|
-
}
|
|
13915
|
-
function isJob(input) {
|
|
13916
|
-
if (!input || typeof input !== "object" || Array.isArray(input)) return false;
|
|
13917
|
-
const job = input;
|
|
13918
|
-
return (typeof job.capability === "string" || typeof job.action === "string") && (job.flavor === "instant" || job.flavor === "scheduled") && (!job.cliArgs || typeof job.cliArgs === "object" && !Array.isArray(job.cliArgs));
|
|
13919
|
-
}
|
|
13920
|
-
var dispatchNextTaskJob;
|
|
13921
|
-
var init_dispatchNextTaskJob = __esm({
|
|
13922
|
-
"src/scripts/dispatchNextTaskJob.ts"() {
|
|
13923
|
-
"use strict";
|
|
13924
|
-
init_jobIdentity();
|
|
13925
|
-
init_state();
|
|
13926
|
-
dispatchNextTaskJob = async (ctx, profile) => {
|
|
13927
|
-
const state = ctx.data.taskState ?? emptyState();
|
|
13928
|
-
const ids = Array.isArray(ctx.data.plannedTaskJobIds) ? ctx.data.plannedTaskJobIds.filter((id) => typeof id === "string") : void 0;
|
|
13929
|
-
const next = nextPendingTaskJob(state, ids);
|
|
13930
|
-
ctx.skipAgent = true;
|
|
13931
|
-
if (!next) {
|
|
13932
|
-
ctx.output.exitCode = 0;
|
|
13933
|
-
ctx.output.reason = "all planned task jobs are complete";
|
|
13934
|
-
return;
|
|
13935
|
-
}
|
|
13936
|
-
const plannedJobs = Array.isArray(ctx.data.plannedTaskJobs) ? ctx.data.plannedTaskJobs.filter(isJob) : [];
|
|
13937
|
-
ctx.output.nextJob = plannedJobs.find((job) => stableJobKey(job) === next.id) ?? taskJobToJob(next, ctx.args.issue);
|
|
13938
|
-
if (typeof ctx.args.issue === "number") {
|
|
13939
|
-
ctx.output.afterNextJob = { action: profile.action ?? profile.name, cliArgs: { issue: ctx.args.issue } };
|
|
13940
|
-
}
|
|
13941
|
-
};
|
|
13942
|
-
}
|
|
13943
|
-
});
|
|
13944
|
-
|
|
13945
13155
|
// src/loopDefinitions.ts
|
|
13946
13156
|
import * as fs36 from "fs";
|
|
13947
13157
|
import * as path34 from "path";
|
|
@@ -13976,15 +13186,15 @@ function readLoopDefinition(cwd, id) {
|
|
|
13976
13186
|
try {
|
|
13977
13187
|
const loop = normalizeLoopDefinition(JSON.parse(fs36.readFileSync(filePath, "utf8")));
|
|
13978
13188
|
if (loop?.id === id) return loop;
|
|
13979
|
-
process.stderr.write(`[kody] invalid
|
|
13189
|
+
process.stderr.write(`[kody] invalid Loop definition: ${filePath}
|
|
13980
13190
|
`);
|
|
13981
13191
|
} catch {
|
|
13982
|
-
process.stderr.write(`[kody] unreadable
|
|
13192
|
+
process.stderr.write(`[kody] unreadable Loop definition: ${filePath}
|
|
13983
13193
|
`);
|
|
13984
13194
|
}
|
|
13985
13195
|
}
|
|
13986
13196
|
process.stderr.write(
|
|
13987
|
-
`[kody]
|
|
13197
|
+
`[kody] Loop not found: ${id} (${roots.map((root) => path34.join(root, "loops", id, "loop.json")).join(", ")})
|
|
13988
13198
|
`
|
|
13989
13199
|
);
|
|
13990
13200
|
return null;
|
|
@@ -14003,7 +13213,7 @@ function listLoopDefinitions(cwd) {
|
|
|
14003
13213
|
const loop = normalizeLoopDefinition(JSON.parse(fs36.readFileSync(filePath, "utf8")));
|
|
14004
13214
|
if (loop?.id === id) byId.set(id, loop);
|
|
14005
13215
|
} catch {
|
|
14006
|
-
process.stderr.write(`[kody] unreadable
|
|
13216
|
+
process.stderr.write(`[kody] unreadable Loop definition: ${filePath}
|
|
14007
13217
|
`);
|
|
14008
13218
|
}
|
|
14009
13219
|
}
|
|
@@ -14047,11 +13257,88 @@ var init_loopDefinitions = __esm({
|
|
|
14047
13257
|
}
|
|
14048
13258
|
});
|
|
14049
13259
|
|
|
14050
|
-
// src/scripts/
|
|
14051
|
-
import { randomUUID
|
|
13260
|
+
// src/scripts/dispatchLoops.ts
|
|
13261
|
+
import { randomUUID } from "crypto";
|
|
13262
|
+
function assertLoopDispatchesSucceeded(results) {
|
|
13263
|
+
const failed = results.filter((result) => result.status === "failed");
|
|
13264
|
+
if (failed.length === 0) return;
|
|
13265
|
+
throw new Error(
|
|
13266
|
+
`Loop dispatch failed: ${failed.map((result) => `${result.loopId}: ${result.reason}`).join("; ")}`
|
|
13267
|
+
);
|
|
13268
|
+
}
|
|
13269
|
+
async function dispatchLoopsWith(input) {
|
|
13270
|
+
const results = [];
|
|
13271
|
+
for (const loop of input.loops) {
|
|
13272
|
+
const slot = loopDispatchSlot(loop, input.now, input.force, input.nonce());
|
|
13273
|
+
if (!slot) continue;
|
|
13274
|
+
const reservationId = `reservation-${input.nonce()}`;
|
|
13275
|
+
const idempotencyKey = `${loop.id}:${slot}`;
|
|
13276
|
+
const claimed = await input.backend.reserveLoopDispatch(input.tenantId, {
|
|
13277
|
+
idempotencyKey,
|
|
13278
|
+
loopId: loop.id,
|
|
13279
|
+
decision: {
|
|
13280
|
+
kind: "fire",
|
|
13281
|
+
reason: input.force ? "manual Loop run requested" : "local Loop schedule is due",
|
|
13282
|
+
scheduledAt: slot
|
|
13283
|
+
},
|
|
13284
|
+
leaseUntil: new Date(input.now.getTime() + LOOP_DISPATCH_LEASE_MS).toISOString(),
|
|
13285
|
+
reservationId,
|
|
13286
|
+
correlationId: `corr-${input.nonce()}`,
|
|
13287
|
+
policyHash: `loop:${loop.id}`,
|
|
13288
|
+
effectivePolicy: { source: "repository" },
|
|
13289
|
+
definitionRefs: [{ kind: "loop", id: loop.id }],
|
|
13290
|
+
maxConcurrentRuns: 1,
|
|
13291
|
+
requiresApproval: false,
|
|
13292
|
+
approvalScopeKind: "loop",
|
|
13293
|
+
approvalScopeId: loop.id,
|
|
13294
|
+
approvalAction: `${loop.target.kind}:${loop.target.id}`,
|
|
13295
|
+
now: input.now.toISOString()
|
|
13296
|
+
});
|
|
13297
|
+
if (!claimed.acquired) {
|
|
13298
|
+
results.push({ loopId: loop.id, status: "skipped", reason: claimed.reason ?? "already claimed" });
|
|
13299
|
+
continue;
|
|
13300
|
+
}
|
|
13301
|
+
const runId = `loop-${loop.id}-${input.nonce()}`;
|
|
13302
|
+
const startedAt = input.now.toISOString();
|
|
13303
|
+
const running = {
|
|
13304
|
+
id: runId,
|
|
13305
|
+
status: "running",
|
|
13306
|
+
target: loop.target,
|
|
13307
|
+
agent: "kody",
|
|
13308
|
+
startedAt
|
|
13309
|
+
};
|
|
13310
|
+
await input.backend.createAgencyRun(input.tenantId, "loop", loop.id, running, startedAt);
|
|
13311
|
+
let exitCode;
|
|
13312
|
+
let reason;
|
|
13313
|
+
try {
|
|
13314
|
+
const result = await input.run(loopJob(loop), runId);
|
|
13315
|
+
exitCode = result.exitCode;
|
|
13316
|
+
reason = result.reason ?? (exitCode === 0 ? "dispatched" : "target failed");
|
|
13317
|
+
} catch (error) {
|
|
13318
|
+
exitCode = 1;
|
|
13319
|
+
reason = error instanceof Error ? error.message : String(error);
|
|
13320
|
+
}
|
|
13321
|
+
const finishedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
13322
|
+
const succeeded = exitCode === 0;
|
|
13323
|
+
await input.backend.finishAgencyRun(
|
|
13324
|
+
input.tenantId,
|
|
13325
|
+
{
|
|
13326
|
+
...running,
|
|
13327
|
+
status: succeeded ? "succeeded" : "failed",
|
|
13328
|
+
finishedAt,
|
|
13329
|
+
...succeeded ? { output: { summary: reason } } : { error: reason }
|
|
13330
|
+
},
|
|
13331
|
+
finishedAt
|
|
13332
|
+
);
|
|
13333
|
+
const status = succeeded ? "dispatched" : "failed";
|
|
13334
|
+
await input.backend.finishLoopDispatch(input.tenantId, idempotencyKey, reservationId, status, finishedAt, runId);
|
|
13335
|
+
results.push({ loopId: loop.id, status, reason });
|
|
13336
|
+
}
|
|
13337
|
+
return results;
|
|
13338
|
+
}
|
|
14052
13339
|
function selectRunnableLoops(loops, now, options) {
|
|
14053
13340
|
return loops.filter(
|
|
14054
|
-
(loop) => loop.enabled &&
|
|
13341
|
+
(loop) => loop.enabled && (!options.loopId || loop.id === options.loopId) && (options.force || loop.trigger.type === "schedule" && dueSlot(loop, now) !== null)
|
|
14055
13342
|
);
|
|
14056
13343
|
}
|
|
14057
13344
|
function loopDispatchSlot(loop, now, force, nonce) {
|
|
@@ -14093,21 +13380,22 @@ function loopJob(loop) {
|
|
|
14093
13380
|
);
|
|
14094
13381
|
return loop.target.kind === "workflow" ? { workflow: loop.target.id, cliArgs, flavor: "scheduled" } : { capability: loop.target.id, cliArgs, flavor: "scheduled" };
|
|
14095
13382
|
}
|
|
14096
|
-
function
|
|
13383
|
+
function repositoryTenant(config) {
|
|
14097
13384
|
const [envOwner, envRepo] = (process.env.GITHUB_REPOSITORY ?? "").split("/");
|
|
14098
13385
|
const owner = config.github?.owner?.trim() || envOwner?.trim();
|
|
14099
13386
|
const repo = config.github?.repo?.trim() || envRepo?.trim();
|
|
14100
13387
|
return owner && repo ? `${owner}/${repo}` : null;
|
|
14101
13388
|
}
|
|
14102
|
-
var
|
|
14103
|
-
var
|
|
14104
|
-
"src/scripts/
|
|
13389
|
+
var LOOP_DISPATCH_LEASE_MS, dispatchLoops;
|
|
13390
|
+
var init_dispatchLoops = __esm({
|
|
13391
|
+
"src/scripts/dispatchLoops.ts"() {
|
|
14105
13392
|
"use strict";
|
|
14106
13393
|
init_job();
|
|
14107
13394
|
init_loopDefinitions();
|
|
14108
13395
|
init_state_backend();
|
|
14109
|
-
|
|
14110
|
-
|
|
13396
|
+
LOOP_DISPATCH_LEASE_MS = 6 * 60 * 60 * 1e3;
|
|
13397
|
+
dispatchLoops = async (ctx) => {
|
|
13398
|
+
const tenantId2 = repositoryTenant(ctx.config);
|
|
14111
13399
|
if (!tenantId2) throw new Error("Repository identity is required for Loop dispatch");
|
|
14112
13400
|
const now = /* @__PURE__ */ new Date();
|
|
14113
13401
|
const force = ctx.data.jobForce === true;
|
|
@@ -14119,53 +13407,94 @@ var init_dispatchSimpleLoops = __esm({
|
|
|
14119
13407
|
process.stdout.write(`\u2192 kody: Loop scheduler found ${due.length} runnable Loop(s)${force ? " (manual)" : ""}
|
|
14120
13408
|
`);
|
|
14121
13409
|
const backend = createStateBackendFromEnv();
|
|
14122
|
-
const results =
|
|
14123
|
-
|
|
14124
|
-
|
|
14125
|
-
|
|
14126
|
-
|
|
14127
|
-
|
|
14128
|
-
|
|
14129
|
-
|
|
14130
|
-
loopId: loop.id,
|
|
14131
|
-
decision: {
|
|
14132
|
-
kind: "fire",
|
|
14133
|
-
reason: force ? "manual Loop run requested" : "local Loop schedule is due",
|
|
14134
|
-
scheduledAt: slot
|
|
14135
|
-
},
|
|
14136
|
-
leaseUntil: new Date(now.getTime() + 6 * 60 * 60 * 1e3).toISOString(),
|
|
14137
|
-
reservationId,
|
|
14138
|
-
correlationId: `corr-${randomUUID2()}`,
|
|
14139
|
-
policyHash: `loop:${loop.id}`,
|
|
14140
|
-
effectivePolicy: { source: "repository" },
|
|
14141
|
-
definitionRefs: [{ kind: "loop", id: loop.id }],
|
|
14142
|
-
maxConcurrentRuns: 1,
|
|
14143
|
-
requiresApproval: false,
|
|
14144
|
-
approvalScopeKind: "loop",
|
|
14145
|
-
approvalScopeId: loop.id,
|
|
14146
|
-
approvalAction: `${loop.target.kind}:${loop.target.id}`,
|
|
14147
|
-
now: now.toISOString()
|
|
14148
|
-
});
|
|
14149
|
-
if (!claimed.acquired) {
|
|
14150
|
-
results.push({ loopId: loop.id, status: "skipped", reason: claimed.reason ?? "already claimed" });
|
|
14151
|
-
continue;
|
|
14152
|
-
}
|
|
14153
|
-
const result = await runJob(loopJob(loop), {
|
|
13410
|
+
const results = await dispatchLoopsWith({
|
|
13411
|
+
loops: due,
|
|
13412
|
+
tenantId: tenantId2,
|
|
13413
|
+
backend,
|
|
13414
|
+
now,
|
|
13415
|
+
force,
|
|
13416
|
+
nonce: randomUUID,
|
|
13417
|
+
run: (job, parentRunId) => runJob(job, {
|
|
14154
13418
|
cwd: ctx.cwd,
|
|
14155
13419
|
config: ctx.config,
|
|
14156
13420
|
verbose: ctx.verbose,
|
|
14157
13421
|
quiet: ctx.quiet,
|
|
14158
|
-
chain: false
|
|
14159
|
-
|
|
14160
|
-
|
|
14161
|
-
|
|
14162
|
-
results.push({ loopId: loop.id, status, reason: result.reason ?? status });
|
|
14163
|
-
}
|
|
13422
|
+
chain: false,
|
|
13423
|
+
preloadedData: { parentRunId }
|
|
13424
|
+
})
|
|
13425
|
+
});
|
|
14164
13426
|
for (const result of results) {
|
|
14165
13427
|
process.stdout.write(`\u2192 kody: Loop ${result.loopId} ${result.status}: ${result.reason}
|
|
14166
13428
|
`);
|
|
14167
13429
|
}
|
|
14168
|
-
ctx.data.
|
|
13430
|
+
ctx.data.loopDispatchResults = results;
|
|
13431
|
+
assertLoopDispatchesSucceeded(results);
|
|
13432
|
+
};
|
|
13433
|
+
}
|
|
13434
|
+
});
|
|
13435
|
+
|
|
13436
|
+
// src/jobIdentity.ts
|
|
13437
|
+
function stableJobKey(job) {
|
|
13438
|
+
const capability = job.workflow ?? job.capability ?? job.action;
|
|
13439
|
+
const implementation = job.implementation ?? capability ?? "unknown";
|
|
13440
|
+
if (job.flavor === "scheduled" && job.capability) return `scheduled:${job.capability}:${implementation}`;
|
|
13441
|
+
const target = typeof job.target === "number" ? job.target : targetFromCliArgs(job.cliArgs);
|
|
13442
|
+
const work = capability && implementation && implementation !== capability ? `${capability}:${implementation}` : capability ?? implementation;
|
|
13443
|
+
return target === void 0 ? `${job.flavor}:${work}` : `${job.flavor}:${work}:${target}`;
|
|
13444
|
+
}
|
|
13445
|
+
function targetFromCliArgs(cliArgs) {
|
|
13446
|
+
if (!cliArgs) return void 0;
|
|
13447
|
+
for (const key of ["issue", "pr", "target", "issue_number"]) {
|
|
13448
|
+
const value = cliArgs[key];
|
|
13449
|
+
if (typeof value === "number" && Number.isFinite(value)) return value;
|
|
13450
|
+
}
|
|
13451
|
+
return void 0;
|
|
13452
|
+
}
|
|
13453
|
+
var init_jobIdentity = __esm({
|
|
13454
|
+
"src/jobIdentity.ts"() {
|
|
13455
|
+
"use strict";
|
|
13456
|
+
}
|
|
13457
|
+
});
|
|
13458
|
+
|
|
13459
|
+
// src/scripts/dispatchNextTaskJob.ts
|
|
13460
|
+
function taskJobToJob(job, issueArg) {
|
|
13461
|
+
const target = typeof job.target === "number" ? job.target : typeof issueArg === "number" ? issueArg : void 0;
|
|
13462
|
+
return {
|
|
13463
|
+
capability: job.capability ?? job.implementation,
|
|
13464
|
+
implementation: job.implementation,
|
|
13465
|
+
...job.reason ? { why: job.reason } : {},
|
|
13466
|
+
...job.agent ? { agent: job.agent } : {},
|
|
13467
|
+
...job.schedule ? { schedule: job.schedule } : {},
|
|
13468
|
+
...typeof target === "number" ? { target, cliArgs: { issue: target } } : { cliArgs: {} },
|
|
13469
|
+
flavor: job.flavor ?? "instant"
|
|
13470
|
+
};
|
|
13471
|
+
}
|
|
13472
|
+
function isJob(input) {
|
|
13473
|
+
if (!input || typeof input !== "object" || Array.isArray(input)) return false;
|
|
13474
|
+
const job = input;
|
|
13475
|
+
return (typeof job.capability === "string" || typeof job.action === "string") && (job.flavor === "instant" || job.flavor === "scheduled") && (!job.cliArgs || typeof job.cliArgs === "object" && !Array.isArray(job.cliArgs));
|
|
13476
|
+
}
|
|
13477
|
+
var dispatchNextTaskJob;
|
|
13478
|
+
var init_dispatchNextTaskJob = __esm({
|
|
13479
|
+
"src/scripts/dispatchNextTaskJob.ts"() {
|
|
13480
|
+
"use strict";
|
|
13481
|
+
init_jobIdentity();
|
|
13482
|
+
init_state();
|
|
13483
|
+
dispatchNextTaskJob = async (ctx, profile) => {
|
|
13484
|
+
const state = ctx.data.taskState ?? emptyState();
|
|
13485
|
+
const ids = Array.isArray(ctx.data.plannedTaskJobIds) ? ctx.data.plannedTaskJobIds.filter((id) => typeof id === "string") : void 0;
|
|
13486
|
+
const next = nextPendingTaskJob(state, ids);
|
|
13487
|
+
ctx.skipAgent = true;
|
|
13488
|
+
if (!next) {
|
|
13489
|
+
ctx.output.exitCode = 0;
|
|
13490
|
+
ctx.output.reason = "all planned task jobs are complete";
|
|
13491
|
+
return;
|
|
13492
|
+
}
|
|
13493
|
+
const plannedJobs = Array.isArray(ctx.data.plannedTaskJobs) ? ctx.data.plannedTaskJobs.filter(isJob) : [];
|
|
13494
|
+
ctx.output.nextJob = plannedJobs.find((job) => stableJobKey(job) === next.id) ?? taskJobToJob(next, ctx.args.issue);
|
|
13495
|
+
if (typeof ctx.args.issue === "number") {
|
|
13496
|
+
ctx.output.afterNextJob = { action: profile.action ?? profile.name, cliArgs: { issue: ctx.args.issue } };
|
|
13497
|
+
}
|
|
14169
13498
|
};
|
|
14170
13499
|
}
|
|
14171
13500
|
});
|
|
@@ -15892,9 +15221,9 @@ var init_kodyVariables = __esm({
|
|
|
15892
15221
|
});
|
|
15893
15222
|
|
|
15894
15223
|
// src/backendVault.ts
|
|
15895
|
-
import { createDecipheriv, createHash as
|
|
15224
|
+
import { createDecipheriv, createHash as createHash5 } from "crypto";
|
|
15896
15225
|
function cacheKey(owner, repo, masterKey) {
|
|
15897
|
-
const keyHash =
|
|
15226
|
+
const keyHash = createHash5("sha256").update(masterKey).digest("hex").slice(0, 16);
|
|
15898
15227
|
return `${owner}/${repo}:${keyHash}`.toLowerCase();
|
|
15899
15228
|
}
|
|
15900
15229
|
function decryptVault(payload, masterKey) {
|
|
@@ -16681,7 +16010,7 @@ var init_notifyTerminal = __esm({
|
|
|
16681
16010
|
});
|
|
16682
16011
|
|
|
16683
16012
|
// src/scripts/openAgencyModelReviewPr.ts
|
|
16684
|
-
import { createHash as
|
|
16013
|
+
import { createHash as createHash6 } from "crypto";
|
|
16685
16014
|
function parseAgencyModelProposal(raw) {
|
|
16686
16015
|
const text2 = raw.trim();
|
|
16687
16016
|
const jsonText = (text2.match(/^```(?:json)?\s*\n([\s\S]*?)\n```$/i)?.[1] ?? text2).trim();
|
|
@@ -16733,7 +16062,7 @@ function normalizeBundleFiles(bundle) {
|
|
|
16733
16062
|
});
|
|
16734
16063
|
}
|
|
16735
16064
|
function buildProposalId(issueNumber, bundle, sourceLabel) {
|
|
16736
|
-
const digest =
|
|
16065
|
+
const digest = createHash6("sha256").update(JSON.stringify({ issueNumber, sourceLabel, title: bundle.title, files: normalizeBundleFiles(bundle) })).digest("hex").slice(0, 16);
|
|
16737
16066
|
return `issue-${issueNumber}-${digest}`;
|
|
16738
16067
|
}
|
|
16739
16068
|
function isDryRun(ctx) {
|
|
@@ -19063,9 +18392,9 @@ var init_reviewFlow = __esm({
|
|
|
19063
18392
|
});
|
|
19064
18393
|
|
|
19065
18394
|
// src/scripts/previewBuildHelpers.ts
|
|
19066
|
-
import { createDecipheriv as createDecipheriv2, createHash as
|
|
18395
|
+
import { createDecipheriv as createDecipheriv2, createHash as createHash7, hkdfSync as hkdfSync2 } from "crypto";
|
|
19067
18396
|
function shortHash(s) {
|
|
19068
|
-
return
|
|
18397
|
+
return createHash7("sha256").update(s).digest("hex").slice(0, 6);
|
|
19069
18398
|
}
|
|
19070
18399
|
function previewAppName(repo, pr) {
|
|
19071
18400
|
const [owner, name] = repo.split("/");
|
|
@@ -19098,7 +18427,7 @@ function formatPreviewComment(args) {
|
|
|
19098
18427
|
].join("\n");
|
|
19099
18428
|
}
|
|
19100
18429
|
function defaultImageTag(repo, ref) {
|
|
19101
|
-
return
|
|
18430
|
+
return createHash7("sha256").update(`${repo}@${ref}`).digest("hex").slice(0, 12);
|
|
19102
18431
|
}
|
|
19103
18432
|
var init_previewBuildHelpers = __esm({
|
|
19104
18433
|
"src/scripts/previewBuildHelpers.ts"() {
|
|
@@ -20985,12 +20314,11 @@ var init_scripts = __esm({
|
|
|
20985
20314
|
init_diagMcp();
|
|
20986
20315
|
init_discoverQaContext();
|
|
20987
20316
|
init_dispatch();
|
|
20988
|
-
init_dispatchAgencyLoops();
|
|
20989
20317
|
init_dispatchCapabilityFileTicks();
|
|
20990
20318
|
init_dispatchCapabilityTicks();
|
|
20991
20319
|
init_dispatchClassified();
|
|
20320
|
+
init_dispatchLoops();
|
|
20992
20321
|
init_dispatchNextTaskJob();
|
|
20993
|
-
init_dispatchSimpleLoops();
|
|
20994
20322
|
init_ensurePr();
|
|
20995
20323
|
init_evaluateAgencyBoundaries();
|
|
20996
20324
|
init_failOnceTaskJob();
|
|
@@ -21116,8 +20444,7 @@ var init_scripts = __esm({
|
|
|
21116
20444
|
diagMcp,
|
|
21117
20445
|
warmupMcp,
|
|
21118
20446
|
dispatchCapabilityTicks,
|
|
21119
|
-
|
|
21120
|
-
dispatchSimpleLoops,
|
|
20447
|
+
dispatchLoops,
|
|
21121
20448
|
dispatchCapabilityFileTicks,
|
|
21122
20449
|
planTaskJobs,
|
|
21123
20450
|
dispatchNextTaskJob,
|
|
@@ -22582,6 +21909,7 @@ async function runJob(job, base) {
|
|
|
22582
21909
|
updatedAt: startedAt,
|
|
22583
21910
|
workflow: workflowIdentity,
|
|
22584
21911
|
kodyRunId: valid.workflowRunId,
|
|
21912
|
+
parentRunId: typeof base.preloadedData?.parentRunId === "string" ? base.preloadedData.parentRunId : void 0,
|
|
22585
21913
|
sourceType: "job"
|
|
22586
21914
|
};
|
|
22587
21915
|
const persistRun = Boolean(base.config && !base.skipConfig && hasStateBackendConfig());
|
|
@@ -24413,6 +23741,7 @@ import { createHash as createHash2 } from "crypto";
|
|
|
24413
23741
|
import * as fs16 from "fs";
|
|
24414
23742
|
import * as path17 from "path";
|
|
24415
23743
|
var SLUG_RE = /^[a-z0-9][a-z0-9_-]{0,127}$/;
|
|
23744
|
+
var REPOSITORY_OWNED_NAMESPACES = ["loops"];
|
|
24416
23745
|
function assertSafeDefinitionPath(filePath) {
|
|
24417
23746
|
const segments = filePath.split("/");
|
|
24418
23747
|
if (!filePath || filePath.startsWith("/") || filePath.includes("\\") || filePath.includes("\0") || segments.some((segment) => !segment || segment === "." || segment === "..")) {
|
|
@@ -24469,6 +23798,13 @@ function writeDefinition(root, kind, definition) {
|
|
|
24469
23798
|
}
|
|
24470
23799
|
writeBundle(path17.join(root, "capabilities", definition.slug), bundle);
|
|
24471
23800
|
}
|
|
23801
|
+
function preserveRepositoryDefinitions(root, staging) {
|
|
23802
|
+
for (const namespace of REPOSITORY_OWNED_NAMESPACES) {
|
|
23803
|
+
const source = path17.join(root, namespace);
|
|
23804
|
+
if (!fs16.existsSync(source)) continue;
|
|
23805
|
+
fs16.cpSync(source, path17.join(staging, namespace), { recursive: true });
|
|
23806
|
+
}
|
|
23807
|
+
}
|
|
24472
23808
|
async function hydrateDefinitions(options) {
|
|
24473
23809
|
const root = path17.join(options.cwd, ".kody-engine", "definitions");
|
|
24474
23810
|
const staging = `${root}.tmp-${process.pid}-${Date.now()}`;
|
|
@@ -24507,6 +23843,7 @@ async function hydrateDefinitions(options) {
|
|
|
24507
23843
|
writeDefinition(staging, "asset", definition);
|
|
24508
23844
|
versions[`asset:${definition.slug}`] = definition.version;
|
|
24509
23845
|
}
|
|
23846
|
+
preserveRepositoryDefinitions(root, staging);
|
|
24510
23847
|
const manifest = {
|
|
24511
23848
|
schemaVersion: 1,
|
|
24512
23849
|
tenantId: options.tenantId,
|
|
@@ -26841,7 +26178,7 @@ init_config();
|
|
|
26841
26178
|
init_fetchRepoMcp();
|
|
26842
26179
|
|
|
26843
26180
|
// src/servers/mcpHttpServer.ts
|
|
26844
|
-
import { randomUUID as
|
|
26181
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
26845
26182
|
import { createServer as createServer4 } from "http";
|
|
26846
26183
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
26847
26184
|
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
@@ -26850,7 +26187,7 @@ function buildMcpHttpServer(opts) {
|
|
|
26850
26187
|
const transports = /* @__PURE__ */ new Map();
|
|
26851
26188
|
for (const route of opts.routes) {
|
|
26852
26189
|
const transport = new StreamableHTTPServerTransport({
|
|
26853
|
-
sessionIdGenerator: () =>
|
|
26190
|
+
sessionIdGenerator: () => randomUUID2()
|
|
26854
26191
|
});
|
|
26855
26192
|
transports.set(route.path, transport);
|
|
26856
26193
|
routes.set(route.path, route.name);
|
|
@@ -27428,12 +26765,12 @@ import { createServer as createServer5 } from "http";
|
|
|
27428
26765
|
|
|
27429
26766
|
// src/pool/agency-loop-tick.ts
|
|
27430
26767
|
function normalizeRepositories(repositories) {
|
|
27431
|
-
const
|
|
26768
|
+
const unique = /* @__PURE__ */ new Set();
|
|
27432
26769
|
for (const raw of repositories) {
|
|
27433
26770
|
const repo = raw.trim().toLowerCase();
|
|
27434
|
-
if (/^[^/\s]+\/[^/\s]+$/.test(repo))
|
|
26771
|
+
if (/^[^/\s]+\/[^/\s]+$/.test(repo)) unique.add(repo);
|
|
27435
26772
|
}
|
|
27436
|
-
return [...
|
|
26773
|
+
return [...unique].sort();
|
|
27437
26774
|
}
|
|
27438
26775
|
async function runAgencyLoopTick(deps) {
|
|
27439
26776
|
const repositories = normalizeRepositories(await deps.discover());
|