@kody-ade/kody-engine 0.4.394 → 0.4.396
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 +283 -206
- package/package.json +25 -24
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.396",
|
|
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",
|
|
@@ -2549,6 +2549,222 @@ var init_registry = __esm({
|
|
|
2549
2549
|
}
|
|
2550
2550
|
});
|
|
2551
2551
|
|
|
2552
|
+
// src/chat/convex-client.ts
|
|
2553
|
+
import { ConvexHttpClient } from "convex/browser";
|
|
2554
|
+
function isPlainObject2(value) {
|
|
2555
|
+
if (value === null || typeof value !== "object") return false;
|
|
2556
|
+
const proto = Object.getPrototypeOf(value);
|
|
2557
|
+
return proto === Object.prototype || proto === null;
|
|
2558
|
+
}
|
|
2559
|
+
function deepMapKeys(value, mapKey) {
|
|
2560
|
+
if (Array.isArray(value)) return value.map((item) => deepMapKeys(item, mapKey));
|
|
2561
|
+
if (isPlainObject2(value)) {
|
|
2562
|
+
return Object.fromEntries(Object.entries(value).map(([key, item]) => [mapKey(key), deepMapKeys(item, mapKey)]));
|
|
2563
|
+
}
|
|
2564
|
+
return value;
|
|
2565
|
+
}
|
|
2566
|
+
function deepEscapeKeys(value) {
|
|
2567
|
+
return deepMapKeys(value, (k) => NEEDS_ESCAPE.test(k) ? `${ESCAPE_CHAR}${k}` : k);
|
|
2568
|
+
}
|
|
2569
|
+
function deepUnescapeKeys(value) {
|
|
2570
|
+
return deepMapKeys(value, (k) => k.startsWith(ESCAPE_CHAR) ? k.slice(1) : k);
|
|
2571
|
+
}
|
|
2572
|
+
function injectServiceKey(args, serviceKey = process.env.KODY_SERVICE_KEY) {
|
|
2573
|
+
if (!serviceKey) return args;
|
|
2574
|
+
if (args === void 0) return { serviceKey };
|
|
2575
|
+
if (typeof args !== "object" || args === null || Array.isArray(args)) return args;
|
|
2576
|
+
return { ...args, serviceKey };
|
|
2577
|
+
}
|
|
2578
|
+
function withEscapedKeys(client, serviceKey = process.env.KODY_SERVICE_KEY) {
|
|
2579
|
+
return new Proxy(client, {
|
|
2580
|
+
get(target, prop, receiver) {
|
|
2581
|
+
if (CALL_METHODS.includes(prop)) {
|
|
2582
|
+
const method = Reflect.get(target, prop, target);
|
|
2583
|
+
return async (fn, args) => {
|
|
2584
|
+
const authed = injectServiceKey(args, serviceKey);
|
|
2585
|
+
const result = await method.call(target, fn, authed === void 0 ? void 0 : deepEscapeKeys(authed));
|
|
2586
|
+
return deepUnescapeKeys(result);
|
|
2587
|
+
};
|
|
2588
|
+
}
|
|
2589
|
+
const value = Reflect.get(target, prop, receiver);
|
|
2590
|
+
return typeof value === "function" ? value.bind(target) : value;
|
|
2591
|
+
}
|
|
2592
|
+
});
|
|
2593
|
+
}
|
|
2594
|
+
function createConvexClientFromEnv(env = process.env) {
|
|
2595
|
+
const url = env.CONVEX_URL?.trim();
|
|
2596
|
+
if (!url) return null;
|
|
2597
|
+
return withEscapedKeys(new ConvexHttpClient(url), env.KODY_SERVICE_KEY);
|
|
2598
|
+
}
|
|
2599
|
+
var ESCAPE_CHAR, NEEDS_ESCAPE, CALL_METHODS;
|
|
2600
|
+
var init_convex_client = __esm({
|
|
2601
|
+
"src/chat/convex-client.ts"() {
|
|
2602
|
+
"use strict";
|
|
2603
|
+
ESCAPE_CHAR = "~";
|
|
2604
|
+
NEEDS_ESCAPE = /^[$_~]/;
|
|
2605
|
+
CALL_METHODS = ["query", "mutation", "action"];
|
|
2606
|
+
}
|
|
2607
|
+
});
|
|
2608
|
+
|
|
2609
|
+
// src/state-backend.ts
|
|
2610
|
+
import { anyApi } from "convex/server";
|
|
2611
|
+
function requireTenant(tenantId) {
|
|
2612
|
+
const value = tenantId.trim();
|
|
2613
|
+
if (!/^[^/\s]+\/[^/\s]+$/.test(value)) throw new Error("tenantId must be an owner/repository pair");
|
|
2614
|
+
return value;
|
|
2615
|
+
}
|
|
2616
|
+
function requireNonEmpty(value, name) {
|
|
2617
|
+
const normalized = value.trim();
|
|
2618
|
+
if (!normalized) throw new Error(`${name} must not be empty`);
|
|
2619
|
+
return normalized;
|
|
2620
|
+
}
|
|
2621
|
+
function createStateBackendFromEnv(env = process.env, client) {
|
|
2622
|
+
const url = env.CONVEX_URL?.trim();
|
|
2623
|
+
const serviceKey = env.KODY_SERVICE_KEY?.trim();
|
|
2624
|
+
if (!url || !serviceKey) throw new Error("CONVEX_URL and KODY_SERVICE_KEY are required");
|
|
2625
|
+
const transport = client ?? createConvexClientFromEnv(env);
|
|
2626
|
+
return {
|
|
2627
|
+
async get(tenantId, taskKey, kind) {
|
|
2628
|
+
const result = await transport.query(anyApi.taskState.get, {
|
|
2629
|
+
tenantId: requireTenant(tenantId),
|
|
2630
|
+
taskKey: requireNonEmpty(taskKey, "taskKey"),
|
|
2631
|
+
kind: requireNonEmpty(kind, "kind")
|
|
2632
|
+
});
|
|
2633
|
+
return result ?? null;
|
|
2634
|
+
},
|
|
2635
|
+
async save(tenantId, taskKey, kind, doc, expectedUpdatedAt) {
|
|
2636
|
+
await transport.mutation(anyApi.taskState.save, {
|
|
2637
|
+
tenantId: requireTenant(tenantId),
|
|
2638
|
+
taskKey: requireNonEmpty(taskKey, "taskKey"),
|
|
2639
|
+
kind: requireNonEmpty(kind, "kind"),
|
|
2640
|
+
doc,
|
|
2641
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2642
|
+
...expectedUpdatedAt ? { expectedUpdatedAt } : {}
|
|
2643
|
+
});
|
|
2644
|
+
},
|
|
2645
|
+
async getRepoDoc(tenantId, kind) {
|
|
2646
|
+
const result = await transport.query(anyApi.repoDocs.get, {
|
|
2647
|
+
tenantId: requireTenant(tenantId),
|
|
2648
|
+
kind: requireNonEmpty(kind, "kind")
|
|
2649
|
+
});
|
|
2650
|
+
return result ?? null;
|
|
2651
|
+
},
|
|
2652
|
+
async listRepoDocs(tenantId, prefix) {
|
|
2653
|
+
const result = await transport.query(anyApi.repoDocs.listByPrefix, {
|
|
2654
|
+
tenantId: requireTenant(tenantId),
|
|
2655
|
+
prefix: requireNonEmpty(prefix, "prefix")
|
|
2656
|
+
});
|
|
2657
|
+
return Array.isArray(result) ? result : [];
|
|
2658
|
+
},
|
|
2659
|
+
async saveRepoDoc(tenantId, kind, doc, expectedUpdatedAt) {
|
|
2660
|
+
await transport.mutation(anyApi.repoDocs.save, {
|
|
2661
|
+
tenantId: requireTenant(tenantId),
|
|
2662
|
+
kind: requireNonEmpty(kind, "kind"),
|
|
2663
|
+
doc,
|
|
2664
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2665
|
+
...expectedUpdatedAt ? { expectedUpdatedAt } : {}
|
|
2666
|
+
});
|
|
2667
|
+
},
|
|
2668
|
+
async getGoal(tenantId, goalId) {
|
|
2669
|
+
const result = await transport.query(anyApi.goals.get, {
|
|
2670
|
+
tenantId: requireTenant(tenantId),
|
|
2671
|
+
goalId: requireNonEmpty(goalId, "goalId")
|
|
2672
|
+
});
|
|
2673
|
+
return result ?? null;
|
|
2674
|
+
},
|
|
2675
|
+
async listGoals(tenantId) {
|
|
2676
|
+
const result = await transport.query(anyApi.goals.list, { tenantId: requireTenant(tenantId) });
|
|
2677
|
+
return Array.isArray(result) ? result : [];
|
|
2678
|
+
},
|
|
2679
|
+
async saveGoal(tenantId, goalId, state, updatedAt, expectedUpdatedAt) {
|
|
2680
|
+
await transport.mutation(anyApi.goals.save, {
|
|
2681
|
+
tenantId: requireTenant(tenantId),
|
|
2682
|
+
goalId: requireNonEmpty(goalId, "goalId"),
|
|
2683
|
+
state,
|
|
2684
|
+
updatedAt,
|
|
2685
|
+
...expectedUpdatedAt ? { expectedUpdatedAt } : {}
|
|
2686
|
+
});
|
|
2687
|
+
},
|
|
2688
|
+
async appendDailyLog(tenantId, stream, date, entry) {
|
|
2689
|
+
await transport.mutation(anyApi.dailyLogs.append, {
|
|
2690
|
+
tenantId: requireTenant(tenantId),
|
|
2691
|
+
stream,
|
|
2692
|
+
date: requireNonEmpty(date, "date"),
|
|
2693
|
+
entry
|
|
2694
|
+
});
|
|
2695
|
+
},
|
|
2696
|
+
async saveAgencyRun(tenantId, runId, subjectType, subjectId, run, updatedAt) {
|
|
2697
|
+
await transport.mutation(anyApi.agencyRuns.save, {
|
|
2698
|
+
tenantId: requireTenant(tenantId),
|
|
2699
|
+
runId: requireNonEmpty(runId, "runId"),
|
|
2700
|
+
subjectType,
|
|
2701
|
+
subjectId: requireNonEmpty(subjectId, "subjectId"),
|
|
2702
|
+
run,
|
|
2703
|
+
updatedAt
|
|
2704
|
+
});
|
|
2705
|
+
},
|
|
2706
|
+
async appendRunEvent(tenantId, runId, goalId, event, time) {
|
|
2707
|
+
await transport.mutation(anyApi.runEvents.append, {
|
|
2708
|
+
tenantId: requireTenant(tenantId),
|
|
2709
|
+
runId: requireNonEmpty(runId, "runId"),
|
|
2710
|
+
...goalId ? { goalId: requireNonEmpty(goalId, "goalId") } : {},
|
|
2711
|
+
event,
|
|
2712
|
+
time: requireNonEmpty(time, "time")
|
|
2713
|
+
});
|
|
2714
|
+
},
|
|
2715
|
+
async getManifest(tenantId, kind) {
|
|
2716
|
+
const result = await transport.query(anyApi.manifests.get, {
|
|
2717
|
+
tenantId: requireTenant(tenantId),
|
|
2718
|
+
kind: requireNonEmpty(kind, "kind")
|
|
2719
|
+
});
|
|
2720
|
+
return result ?? null;
|
|
2721
|
+
},
|
|
2722
|
+
async saveReport(tenantId, slug, runId, title, body, meta, updatedAt) {
|
|
2723
|
+
await transport.mutation(anyApi.reports.save, {
|
|
2724
|
+
tenantId: requireTenant(tenantId),
|
|
2725
|
+
slug: requireNonEmpty(slug, "slug"),
|
|
2726
|
+
runId: requireNonEmpty(runId, "runId"),
|
|
2727
|
+
title,
|
|
2728
|
+
body,
|
|
2729
|
+
meta,
|
|
2730
|
+
updatedAt
|
|
2731
|
+
});
|
|
2732
|
+
},
|
|
2733
|
+
async listIntents(tenantId) {
|
|
2734
|
+
const result = await transport.query(anyApi.intents.list, { tenantId: requireTenant(tenantId) });
|
|
2735
|
+
return Array.isArray(result) ? result : [];
|
|
2736
|
+
},
|
|
2737
|
+
async getIntent(tenantId, intentId) {
|
|
2738
|
+
const result = await transport.query(anyApi.intents.get, {
|
|
2739
|
+
tenantId: requireTenant(tenantId),
|
|
2740
|
+
intentId: requireNonEmpty(intentId, "intentId")
|
|
2741
|
+
});
|
|
2742
|
+
return result ?? null;
|
|
2743
|
+
},
|
|
2744
|
+
async saveIntent(tenantId, intentId, intent, updatedAt) {
|
|
2745
|
+
await transport.mutation(anyApi.intents.save, {
|
|
2746
|
+
tenantId: requireTenant(tenantId),
|
|
2747
|
+
intentId: requireNonEmpty(intentId, "intentId"),
|
|
2748
|
+
intent,
|
|
2749
|
+
updatedAt
|
|
2750
|
+
});
|
|
2751
|
+
},
|
|
2752
|
+
async appendIntentDecision(tenantId, intentId, decision) {
|
|
2753
|
+
await transport.mutation(anyApi.intents.appendDecision, {
|
|
2754
|
+
tenantId: requireTenant(tenantId),
|
|
2755
|
+
intentId: requireNonEmpty(intentId, "intentId"),
|
|
2756
|
+
decision
|
|
2757
|
+
});
|
|
2758
|
+
}
|
|
2759
|
+
};
|
|
2760
|
+
}
|
|
2761
|
+
var init_state_backend = __esm({
|
|
2762
|
+
"src/state-backend.ts"() {
|
|
2763
|
+
"use strict";
|
|
2764
|
+
init_convex_client();
|
|
2765
|
+
}
|
|
2766
|
+
});
|
|
2767
|
+
|
|
2552
2768
|
// src/trustPolicy.ts
|
|
2553
2769
|
function trustSubjectKey(subject) {
|
|
2554
2770
|
return `${subject.kind}:${subject.id}`;
|
|
@@ -2583,6 +2799,20 @@ function readTrustModeOverride(stateOrRepoSlug, repoSlugOrSubject, maybeSubject)
|
|
|
2583
2799
|
return null;
|
|
2584
2800
|
}
|
|
2585
2801
|
}
|
|
2802
|
+
async function readTrustModeOverrideAsync(repoSlug, subject, state) {
|
|
2803
|
+
if (!subject.id) return null;
|
|
2804
|
+
const backendConfigured = Boolean(
|
|
2805
|
+
process.env.CONVEX_URL?.trim() && process.env.KODY_SERVICE_KEY?.trim() && /^[^/\s]+\/[^/\s]+$/.test(repoSlug)
|
|
2806
|
+
);
|
|
2807
|
+
if (backendConfigured) {
|
|
2808
|
+
const stored = await createStateBackendFromEnv().getManifest(repoSlug, "capability-trust");
|
|
2809
|
+
return stored ? parseTrustModeOverride(JSON.stringify(stored.doc), subject) : null;
|
|
2810
|
+
}
|
|
2811
|
+
if (process.env.GITHUB_ACTIONS === "true") {
|
|
2812
|
+
throw new Error("Convex backend is required for trust policy in GitHub Actions");
|
|
2813
|
+
}
|
|
2814
|
+
return readTrustModeOverride(state, repoSlug, subject);
|
|
2815
|
+
}
|
|
2586
2816
|
function defaultStateForRepoSlug(repoSlug) {
|
|
2587
2817
|
const [owner, repo] = repoSlug.split("/");
|
|
2588
2818
|
return { repo: `${owner}/kody-state`, path: repo ?? repoSlug };
|
|
@@ -2592,6 +2822,7 @@ var init_trustPolicy = __esm({
|
|
|
2592
2822
|
"src/trustPolicy.ts"() {
|
|
2593
2823
|
"use strict";
|
|
2594
2824
|
init_stateRepo();
|
|
2825
|
+
init_state_backend();
|
|
2595
2826
|
TRUST_FILE_PATH = "state/trust.json";
|
|
2596
2827
|
}
|
|
2597
2828
|
});
|
|
@@ -3777,196 +4008,6 @@ var init_agents = __esm({
|
|
|
3777
4008
|
}
|
|
3778
4009
|
});
|
|
3779
4010
|
|
|
3780
|
-
// src/chat/convex-client.ts
|
|
3781
|
-
import { ConvexHttpClient } from "convex/browser";
|
|
3782
|
-
function isPlainObject2(value) {
|
|
3783
|
-
if (value === null || typeof value !== "object") return false;
|
|
3784
|
-
const proto = Object.getPrototypeOf(value);
|
|
3785
|
-
return proto === Object.prototype || proto === null;
|
|
3786
|
-
}
|
|
3787
|
-
function deepMapKeys(value, mapKey) {
|
|
3788
|
-
if (Array.isArray(value)) return value.map((item) => deepMapKeys(item, mapKey));
|
|
3789
|
-
if (isPlainObject2(value)) {
|
|
3790
|
-
return Object.fromEntries(Object.entries(value).map(([key, item]) => [mapKey(key), deepMapKeys(item, mapKey)]));
|
|
3791
|
-
}
|
|
3792
|
-
return value;
|
|
3793
|
-
}
|
|
3794
|
-
function deepEscapeKeys(value) {
|
|
3795
|
-
return deepMapKeys(value, (k) => NEEDS_ESCAPE.test(k) ? `${ESCAPE_CHAR}${k}` : k);
|
|
3796
|
-
}
|
|
3797
|
-
function deepUnescapeKeys(value) {
|
|
3798
|
-
return deepMapKeys(value, (k) => k.startsWith(ESCAPE_CHAR) ? k.slice(1) : k);
|
|
3799
|
-
}
|
|
3800
|
-
function injectServiceKey(args, serviceKey = process.env.KODY_SERVICE_KEY) {
|
|
3801
|
-
if (!serviceKey) return args;
|
|
3802
|
-
if (args === void 0) return { serviceKey };
|
|
3803
|
-
if (typeof args !== "object" || args === null || Array.isArray(args)) return args;
|
|
3804
|
-
return { ...args, serviceKey };
|
|
3805
|
-
}
|
|
3806
|
-
function withEscapedKeys(client, serviceKey = process.env.KODY_SERVICE_KEY) {
|
|
3807
|
-
return new Proxy(client, {
|
|
3808
|
-
get(target, prop, receiver) {
|
|
3809
|
-
if (CALL_METHODS.includes(prop)) {
|
|
3810
|
-
const method = Reflect.get(target, prop, target);
|
|
3811
|
-
return async (fn, args) => {
|
|
3812
|
-
const authed = injectServiceKey(args, serviceKey);
|
|
3813
|
-
const result = await method.call(target, fn, authed === void 0 ? void 0 : deepEscapeKeys(authed));
|
|
3814
|
-
return deepUnescapeKeys(result);
|
|
3815
|
-
};
|
|
3816
|
-
}
|
|
3817
|
-
const value = Reflect.get(target, prop, receiver);
|
|
3818
|
-
return typeof value === "function" ? value.bind(target) : value;
|
|
3819
|
-
}
|
|
3820
|
-
});
|
|
3821
|
-
}
|
|
3822
|
-
function createConvexClientFromEnv(env = process.env) {
|
|
3823
|
-
const url = env.CONVEX_URL?.trim();
|
|
3824
|
-
if (!url) return null;
|
|
3825
|
-
return withEscapedKeys(new ConvexHttpClient(url), env.KODY_SERVICE_KEY);
|
|
3826
|
-
}
|
|
3827
|
-
var ESCAPE_CHAR, NEEDS_ESCAPE, CALL_METHODS;
|
|
3828
|
-
var init_convex_client = __esm({
|
|
3829
|
-
"src/chat/convex-client.ts"() {
|
|
3830
|
-
"use strict";
|
|
3831
|
-
ESCAPE_CHAR = "~";
|
|
3832
|
-
NEEDS_ESCAPE = /^[$_~]/;
|
|
3833
|
-
CALL_METHODS = ["query", "mutation", "action"];
|
|
3834
|
-
}
|
|
3835
|
-
});
|
|
3836
|
-
|
|
3837
|
-
// src/state-backend.ts
|
|
3838
|
-
import { anyApi } from "convex/server";
|
|
3839
|
-
function requireTenant(tenantId) {
|
|
3840
|
-
const value = tenantId.trim();
|
|
3841
|
-
if (!/^[^/\s]+\/[^/\s]+$/.test(value)) throw new Error("tenantId must be an owner/repository pair");
|
|
3842
|
-
return value;
|
|
3843
|
-
}
|
|
3844
|
-
function requireNonEmpty(value, name) {
|
|
3845
|
-
const normalized = value.trim();
|
|
3846
|
-
if (!normalized) throw new Error(`${name} must not be empty`);
|
|
3847
|
-
return normalized;
|
|
3848
|
-
}
|
|
3849
|
-
function createStateBackendFromEnv(env = process.env, client) {
|
|
3850
|
-
const url = env.CONVEX_URL?.trim();
|
|
3851
|
-
const serviceKey = env.KODY_SERVICE_KEY?.trim();
|
|
3852
|
-
if (!url || !serviceKey) throw new Error("CONVEX_URL and KODY_SERVICE_KEY are required");
|
|
3853
|
-
const transport = client ?? createConvexClientFromEnv(env);
|
|
3854
|
-
return {
|
|
3855
|
-
async get(tenantId, taskKey, kind) {
|
|
3856
|
-
const result = await transport.query(anyApi.taskState.get, {
|
|
3857
|
-
tenantId: requireTenant(tenantId),
|
|
3858
|
-
taskKey: requireNonEmpty(taskKey, "taskKey"),
|
|
3859
|
-
kind: requireNonEmpty(kind, "kind")
|
|
3860
|
-
});
|
|
3861
|
-
return result ?? null;
|
|
3862
|
-
},
|
|
3863
|
-
async save(tenantId, taskKey, kind, doc, expectedUpdatedAt) {
|
|
3864
|
-
await transport.mutation(anyApi.taskState.save, {
|
|
3865
|
-
tenantId: requireTenant(tenantId),
|
|
3866
|
-
taskKey: requireNonEmpty(taskKey, "taskKey"),
|
|
3867
|
-
kind: requireNonEmpty(kind, "kind"),
|
|
3868
|
-
doc,
|
|
3869
|
-
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3870
|
-
...expectedUpdatedAt ? { expectedUpdatedAt } : {}
|
|
3871
|
-
});
|
|
3872
|
-
},
|
|
3873
|
-
async getRepoDoc(tenantId, kind) {
|
|
3874
|
-
const result = await transport.query(anyApi.repoDocs.get, {
|
|
3875
|
-
tenantId: requireTenant(tenantId),
|
|
3876
|
-
kind: requireNonEmpty(kind, "kind")
|
|
3877
|
-
});
|
|
3878
|
-
return result ?? null;
|
|
3879
|
-
},
|
|
3880
|
-
async listRepoDocs(tenantId, prefix) {
|
|
3881
|
-
const result = await transport.query(anyApi.repoDocs.listByPrefix, {
|
|
3882
|
-
tenantId: requireTenant(tenantId),
|
|
3883
|
-
prefix: requireNonEmpty(prefix, "prefix")
|
|
3884
|
-
});
|
|
3885
|
-
return Array.isArray(result) ? result : [];
|
|
3886
|
-
},
|
|
3887
|
-
async saveRepoDoc(tenantId, kind, doc, expectedUpdatedAt) {
|
|
3888
|
-
await transport.mutation(anyApi.repoDocs.save, {
|
|
3889
|
-
tenantId: requireTenant(tenantId),
|
|
3890
|
-
kind: requireNonEmpty(kind, "kind"),
|
|
3891
|
-
doc,
|
|
3892
|
-
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3893
|
-
...expectedUpdatedAt ? { expectedUpdatedAt } : {}
|
|
3894
|
-
});
|
|
3895
|
-
},
|
|
3896
|
-
async getGoal(tenantId, goalId) {
|
|
3897
|
-
const result = await transport.query(anyApi.goals.get, {
|
|
3898
|
-
tenantId: requireTenant(tenantId),
|
|
3899
|
-
goalId: requireNonEmpty(goalId, "goalId")
|
|
3900
|
-
});
|
|
3901
|
-
return result ?? null;
|
|
3902
|
-
},
|
|
3903
|
-
async listGoals(tenantId) {
|
|
3904
|
-
const result = await transport.query(anyApi.goals.list, { tenantId: requireTenant(tenantId) });
|
|
3905
|
-
return Array.isArray(result) ? result : [];
|
|
3906
|
-
},
|
|
3907
|
-
async saveGoal(tenantId, goalId, state, updatedAt, expectedUpdatedAt) {
|
|
3908
|
-
await transport.mutation(anyApi.goals.save, {
|
|
3909
|
-
tenantId: requireTenant(tenantId),
|
|
3910
|
-
goalId: requireNonEmpty(goalId, "goalId"),
|
|
3911
|
-
state,
|
|
3912
|
-
updatedAt,
|
|
3913
|
-
...expectedUpdatedAt ? { expectedUpdatedAt } : {}
|
|
3914
|
-
});
|
|
3915
|
-
},
|
|
3916
|
-
async appendDailyLog(tenantId, stream, date, entry) {
|
|
3917
|
-
await transport.mutation(anyApi.dailyLogs.append, {
|
|
3918
|
-
tenantId: requireTenant(tenantId),
|
|
3919
|
-
stream,
|
|
3920
|
-
date: requireNonEmpty(date, "date"),
|
|
3921
|
-
entry
|
|
3922
|
-
});
|
|
3923
|
-
},
|
|
3924
|
-
async saveReport(tenantId, slug, runId, title, body, meta, updatedAt) {
|
|
3925
|
-
await transport.mutation(anyApi.reports.save, {
|
|
3926
|
-
tenantId: requireTenant(tenantId),
|
|
3927
|
-
slug: requireNonEmpty(slug, "slug"),
|
|
3928
|
-
runId: requireNonEmpty(runId, "runId"),
|
|
3929
|
-
title,
|
|
3930
|
-
body,
|
|
3931
|
-
meta,
|
|
3932
|
-
updatedAt
|
|
3933
|
-
});
|
|
3934
|
-
},
|
|
3935
|
-
async listIntents(tenantId) {
|
|
3936
|
-
const result = await transport.query(anyApi.intents.list, { tenantId: requireTenant(tenantId) });
|
|
3937
|
-
return Array.isArray(result) ? result : [];
|
|
3938
|
-
},
|
|
3939
|
-
async getIntent(tenantId, intentId) {
|
|
3940
|
-
const result = await transport.query(anyApi.intents.get, {
|
|
3941
|
-
tenantId: requireTenant(tenantId),
|
|
3942
|
-
intentId: requireNonEmpty(intentId, "intentId")
|
|
3943
|
-
});
|
|
3944
|
-
return result ?? null;
|
|
3945
|
-
},
|
|
3946
|
-
async saveIntent(tenantId, intentId, intent, updatedAt) {
|
|
3947
|
-
await transport.mutation(anyApi.intents.save, {
|
|
3948
|
-
tenantId: requireTenant(tenantId),
|
|
3949
|
-
intentId: requireNonEmpty(intentId, "intentId"),
|
|
3950
|
-
intent,
|
|
3951
|
-
updatedAt
|
|
3952
|
-
});
|
|
3953
|
-
},
|
|
3954
|
-
async appendIntentDecision(tenantId, intentId, decision) {
|
|
3955
|
-
await transport.mutation(anyApi.intents.appendDecision, {
|
|
3956
|
-
tenantId: requireTenant(tenantId),
|
|
3957
|
-
intentId: requireNonEmpty(intentId, "intentId"),
|
|
3958
|
-
decision
|
|
3959
|
-
});
|
|
3960
|
-
}
|
|
3961
|
-
};
|
|
3962
|
-
}
|
|
3963
|
-
var init_state_backend = __esm({
|
|
3964
|
-
"src/state-backend.ts"() {
|
|
3965
|
-
"use strict";
|
|
3966
|
-
init_convex_client();
|
|
3967
|
-
}
|
|
3968
|
-
});
|
|
3969
|
-
|
|
3970
4011
|
// src/task-artifacts.ts
|
|
3971
4012
|
import fs10 from "fs";
|
|
3972
4013
|
import path12 from "path";
|
|
@@ -7010,15 +7051,36 @@ function upsertRunIndexRowBestEffort(config, cwd, row) {
|
|
|
7010
7051
|
`);
|
|
7011
7052
|
}
|
|
7012
7053
|
}
|
|
7054
|
+
async function upsertRunIndexRowBestEffortAsync(config, cwd, row) {
|
|
7055
|
+
if (!row) return;
|
|
7056
|
+
const tenantId = tenantIdForRun(config);
|
|
7057
|
+
const backendConfigured = Boolean(process.env.CONVEX_URL?.trim() && process.env.KODY_SERVICE_KEY?.trim() && tenantId);
|
|
7058
|
+
if (backendConfigured) {
|
|
7059
|
+
const backend = createStateBackendFromEnv();
|
|
7060
|
+
await backend.saveAgencyRun(
|
|
7061
|
+
tenantId,
|
|
7062
|
+
row.id,
|
|
7063
|
+
row.subjectType,
|
|
7064
|
+
row.subjectId,
|
|
7065
|
+
row,
|
|
7066
|
+
row.updatedAt
|
|
7067
|
+
);
|
|
7068
|
+
return;
|
|
7069
|
+
}
|
|
7070
|
+
if (process.env.GITHUB_ACTIONS === "true") {
|
|
7071
|
+
throw new Error("Convex backend is required for the run index in GitHub Actions");
|
|
7072
|
+
}
|
|
7073
|
+
upsertRunIndexRowBestEffort(config, cwd, row);
|
|
7074
|
+
}
|
|
7013
7075
|
function stageRunIndexFinalization(data, row) {
|
|
7014
7076
|
if (!row) return;
|
|
7015
7077
|
const rows = stagedRunIndexRows(data);
|
|
7016
7078
|
rows[row.id] = row;
|
|
7017
7079
|
}
|
|
7018
|
-
function
|
|
7080
|
+
async function finalizeStagedRunIndexRowsAsync(config, cwd, data, result) {
|
|
7019
7081
|
const rows = stagedRunIndexRows(data);
|
|
7020
7082
|
for (const row of Object.values(rows)) {
|
|
7021
|
-
|
|
7083
|
+
await upsertRunIndexRowBestEffortAsync(config, cwd, finalizedRunIndexRow(row, result));
|
|
7022
7084
|
}
|
|
7023
7085
|
data[STAGED_RUN_INDEX_ROWS_KEY] = {};
|
|
7024
7086
|
}
|
|
@@ -7216,6 +7278,10 @@ function isConflict(err) {
|
|
|
7216
7278
|
const msg = err instanceof Error ? err.message : String(err);
|
|
7217
7279
|
return /HTTP 409/i.test(msg) || /HTTP 422/i.test(msg) || /does not match|is at|but expected/i.test(msg);
|
|
7218
7280
|
}
|
|
7281
|
+
function tenantIdForRun(config) {
|
|
7282
|
+
if (config.github?.owner && config.github.repo) return `${config.github.owner}/${config.github.repo}`;
|
|
7283
|
+
return process.env.GITHUB_REPOSITORY?.trim() || void 0;
|
|
7284
|
+
}
|
|
7219
7285
|
function recordValue2(value) {
|
|
7220
7286
|
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
7221
7287
|
}
|
|
@@ -7232,6 +7298,7 @@ var RUN_INDEX_PATH, MAX_RUNS, STAGED_RUN_INDEX_ROWS_KEY;
|
|
|
7232
7298
|
var init_runIndex = __esm({
|
|
7233
7299
|
"src/runIndex.ts"() {
|
|
7234
7300
|
"use strict";
|
|
7301
|
+
init_state_backend();
|
|
7235
7302
|
init_stateRepo();
|
|
7236
7303
|
RUN_INDEX_PATH = "runs/index.json";
|
|
7237
7304
|
MAX_RUNS = 200;
|
|
@@ -8221,9 +8288,14 @@ async function flushGoalRunLogEventsAsync(config, cwd, data) {
|
|
|
8221
8288
|
for (const [goalId, log2] of Object.entries(goalRunLogs(data))) {
|
|
8222
8289
|
if (log2.events.length === 0) continue;
|
|
8223
8290
|
const enrichedEvents = log2.events.map((event) => enrichGoalRunLogEvent(config, data, log2.path, event));
|
|
8291
|
+
const row = runIndexRowFromGoalEvents(goalId, log2.path, enrichedEvents);
|
|
8292
|
+
if (!row) continue;
|
|
8224
8293
|
for (const event of enrichedEvents) {
|
|
8225
8294
|
await backend.appendDailyLog(tenantId, "events", event.time.slice(0, 10), event);
|
|
8295
|
+
await backend.appendRunEvent(tenantId, row.id, goalId, event, event.time);
|
|
8226
8296
|
}
|
|
8297
|
+
await upsertRunIndexRowBestEffortAsync(config, cwd, row);
|
|
8298
|
+
stageRunIndexFinalization(data, row);
|
|
8227
8299
|
log2.events = [];
|
|
8228
8300
|
}
|
|
8229
8301
|
}
|
|
@@ -8921,7 +8993,9 @@ function decodeGoal(doc) {
|
|
|
8921
8993
|
async function fetchGoalStateAsync(config, goalId, cwd) {
|
|
8922
8994
|
const tenantId = backendTenant(config);
|
|
8923
8995
|
if (backendEnabled(config) && tenantId) {
|
|
8924
|
-
|
|
8996
|
+
const fromBackend = decodeGoal(await createStateBackendFromEnv().getGoal(tenantId, goalId));
|
|
8997
|
+
if (fromBackend) return fromBackend;
|
|
8998
|
+
return fetchGoalStateLegacy(config, goalId, cwd);
|
|
8925
8999
|
}
|
|
8926
9000
|
if (backendRequired()) throw new Error("Convex backend is required for goal state in GitHub Actions");
|
|
8927
9001
|
return fetchGoalStateLegacy(config, goalId, cwd);
|
|
@@ -10306,12 +10380,12 @@ function scalarFacts(facts) {
|
|
|
10306
10380
|
async function autonomyBlockReason(ctx, goalId, goal, goalState, dispatch2, options = {}) {
|
|
10307
10381
|
if (ctx.data.jobForce === true) return null;
|
|
10308
10382
|
const selfKind = managedModelKind(goal);
|
|
10309
|
-
const selfMode = selfKind === "Goal" ? firstTrustOverride(ctx, subjectCandidates("goal", goalId, goalState)) : null;
|
|
10383
|
+
const selfMode = selfKind === "Goal" ? await firstTrustOverride(ctx, subjectCandidates("goal", goalId, goalState)) : null;
|
|
10310
10384
|
if (selfKind === "Goal" && (selfMode === "ask" || selfMode !== "auto" && goal.runWithoutApproval !== true)) {
|
|
10311
10385
|
return `Run without approval is off for ${managedModelKind(goal)} ${goalId}`;
|
|
10312
10386
|
}
|
|
10313
10387
|
if (dispatch2.workflow && selfKind === "Goal") {
|
|
10314
|
-
const workflowMode = firstTrustOverride(ctx, [{ kind: "workflow", id: dispatch2.workflow }]);
|
|
10388
|
+
const workflowMode = await firstTrustOverride(ctx, [{ kind: "workflow", id: dispatch2.workflow }]);
|
|
10315
10389
|
const workflow = workflowMode === "auto" ? null : readWorkflowDefinition(ctx.config, ctx.cwd, dispatch2.workflow);
|
|
10316
10390
|
if (workflowMode === "ask" || workflowMode !== "auto" && workflow && workflow.runWithoutApproval !== true) {
|
|
10317
10391
|
return `Run without approval is off for workflow ${dispatch2.workflow}`;
|
|
@@ -10322,7 +10396,7 @@ async function autonomyBlockReason(ctx, goalId, goal, goalState, dispatch2, opti
|
|
|
10322
10396
|
const target = await fetchGoalStateAsync(ctx.config, targetGoal, ctx.cwd);
|
|
10323
10397
|
const targetManaged = target ? managedGoalFromState(expandManagedGoalState(target)) : null;
|
|
10324
10398
|
const targetIsGoal = targetManaged ? managedModelKind(targetManaged) === "Goal" : false;
|
|
10325
|
-
const targetMode = targetManaged && targetIsGoal ? firstTrustOverride(ctx, subjectCandidates("goal", targetGoal, target)) : null;
|
|
10399
|
+
const targetMode = targetManaged && targetIsGoal ? await firstTrustOverride(ctx, subjectCandidates("goal", targetGoal, target)) : null;
|
|
10326
10400
|
if (targetIsGoal && (targetMode === "ask" || targetMode !== "auto" && targetManaged && targetManaged.runWithoutApproval !== true)) {
|
|
10327
10401
|
return `Run without approval is off for goal ${targetGoal}`;
|
|
10328
10402
|
}
|
|
@@ -10366,11 +10440,14 @@ function subjectCandidates(kind, id, state) {
|
|
|
10366
10440
|
}
|
|
10367
10441
|
return [...ids].map((candidate) => ({ kind, id: candidate }));
|
|
10368
10442
|
}
|
|
10369
|
-
function firstTrustOverride(ctx, subjects) {
|
|
10370
|
-
|
|
10443
|
+
async function firstTrustOverride(ctx, subjects) {
|
|
10444
|
+
const backendConfigured = Boolean(
|
|
10445
|
+
process.env.CONVEX_URL?.trim() && process.env.KODY_SERVICE_KEY?.trim()
|
|
10446
|
+
);
|
|
10447
|
+
if (!ctx.config.state && !backendConfigured) return null;
|
|
10371
10448
|
const repoSlug = ctx.config.github?.owner && ctx.config.github?.repo ? `${ctx.config.github.owner}/${ctx.config.github.repo}` : "";
|
|
10372
10449
|
for (const subject of subjects) {
|
|
10373
|
-
const mode =
|
|
10450
|
+
const mode = await readTrustModeOverrideAsync(repoSlug, subject, ctx.config.state);
|
|
10374
10451
|
if (mode) return mode;
|
|
10375
10452
|
}
|
|
10376
10453
|
return null;
|
|
@@ -21056,8 +21133,8 @@ async function runImplementation(profileName, input) {
|
|
|
21056
21133
|
const stageStartedAt = Date.now();
|
|
21057
21134
|
let finishRunIndex = null;
|
|
21058
21135
|
emitEvent(input.cwd, { implementation: profileName, kind: "stage_start" });
|
|
21059
|
-
const finishAndEnd = (out) => {
|
|
21060
|
-
finishRunIndex?.(out);
|
|
21136
|
+
const finishAndEnd = async (out) => {
|
|
21137
|
+
await finishRunIndex?.(out);
|
|
21061
21138
|
emitEvent(input.cwd, {
|
|
21062
21139
|
implementation: profileName,
|
|
21063
21140
|
kind: "stage_end",
|
|
@@ -21148,7 +21225,7 @@ async function runImplementation(profileName, input) {
|
|
|
21148
21225
|
if (reasoningEffort) ctx.data.jobReasoningEffort = reasoningEffort;
|
|
21149
21226
|
const runIndexStartedAt = new Date(stageStartedAt).toISOString();
|
|
21150
21227
|
if (!input.skipConfig) {
|
|
21151
|
-
|
|
21228
|
+
await upsertRunIndexRowBestEffortAsync(
|
|
21152
21229
|
config,
|
|
21153
21230
|
input.cwd,
|
|
21154
21231
|
runIndexRowFromJobContext({
|
|
@@ -21160,10 +21237,10 @@ async function runImplementation(profileName, input) {
|
|
|
21160
21237
|
updatedAt: runIndexStartedAt
|
|
21161
21238
|
})
|
|
21162
21239
|
);
|
|
21163
|
-
finishRunIndex = (out) => {
|
|
21240
|
+
finishRunIndex = async (out) => {
|
|
21164
21241
|
const finishedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
21165
21242
|
const status = statusFromExitCode(out.exitCode);
|
|
21166
|
-
|
|
21243
|
+
await upsertRunIndexRowBestEffortAsync(
|
|
21167
21244
|
config,
|
|
21168
21245
|
input.cwd,
|
|
21169
21246
|
runIndexRowFromJobContext({
|
|
@@ -21176,7 +21253,7 @@ async function runImplementation(profileName, input) {
|
|
|
21176
21253
|
reason: out.reason
|
|
21177
21254
|
})
|
|
21178
21255
|
);
|
|
21179
|
-
|
|
21256
|
+
await finalizeStagedRunIndexRowsAsync(config, input.cwd, ctx.data, {
|
|
21180
21257
|
status,
|
|
21181
21258
|
updatedAt: finishedAt,
|
|
21182
21259
|
reason: out.reason
|
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.396",
|
|
4
4
|
"description": "kody — autonomous development engine. Single-session Claude Code agent behind a generic executor + declarative implementation profiles.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -12,6 +12,28 @@
|
|
|
12
12
|
"templates",
|
|
13
13
|
"kody.config.schema.json"
|
|
14
14
|
],
|
|
15
|
+
"scripts": {
|
|
16
|
+
"kody:run": "tsx bin/kody.ts",
|
|
17
|
+
"serve": "tsx bin/kody.ts serve",
|
|
18
|
+
"serve:vscode": "tsx bin/kody.ts serve vscode",
|
|
19
|
+
"serve:claude": "tsx bin/kody.ts serve claude",
|
|
20
|
+
"clean:dist": "node scripts/clean-dist.cjs",
|
|
21
|
+
"build": "pnpm clean:dist && tsup && node scripts/copy-assets.cjs",
|
|
22
|
+
"check:modularity": "tsx scripts/check-script-modularity.ts",
|
|
23
|
+
"pretest": "pnpm check:modularity",
|
|
24
|
+
"test": "vitest run tests/unit tests/int --coverage",
|
|
25
|
+
"posttest": "tsx scripts/check-coverage-floor.ts",
|
|
26
|
+
"test:smoke": "vitest run tests/smoke --no-coverage",
|
|
27
|
+
"test:e2e": "vitest run tests/e2e --no-coverage",
|
|
28
|
+
"test:all": "vitest run tests --no-coverage",
|
|
29
|
+
"typecheck": "tsc --noEmit",
|
|
30
|
+
"lint": "biome check",
|
|
31
|
+
"lint:fix": "biome check --write",
|
|
32
|
+
"format": "biome format --write",
|
|
33
|
+
"verify:package": "node scripts/verify-package-tarball.cjs",
|
|
34
|
+
"brain:publish": "docker buildx build --platform linux/amd64 -f runner/Dockerfile.brain -t ghcr.io/${KODY_BRAIN_GHCR_OWNER:-aharonyaircohen}/kody-brain:latest --push runner",
|
|
35
|
+
"prepublishOnly": "pnpm typecheck && vitest run tests/unit tests/int --no-coverage && pnpm build && pnpm verify:package"
|
|
36
|
+
},
|
|
15
37
|
"dependencies": {
|
|
16
38
|
"@actions/cache": "^6.0.0",
|
|
17
39
|
"@anthropic-ai/claude-agent-sdk": "0.2.119",
|
|
@@ -36,26 +58,5 @@
|
|
|
36
58
|
"url": "git+https://github.com/aharonyaircohen/kody-engine.git"
|
|
37
59
|
},
|
|
38
60
|
"homepage": "https://github.com/aharonyaircohen/kody-engine",
|
|
39
|
-
"bugs": "https://github.com/aharonyaircohen/kody-engine/issues"
|
|
40
|
-
|
|
41
|
-
"kody:run": "tsx bin/kody.ts",
|
|
42
|
-
"serve": "tsx bin/kody.ts serve",
|
|
43
|
-
"serve:vscode": "tsx bin/kody.ts serve vscode",
|
|
44
|
-
"serve:claude": "tsx bin/kody.ts serve claude",
|
|
45
|
-
"clean:dist": "node scripts/clean-dist.cjs",
|
|
46
|
-
"build": "pnpm clean:dist && tsup && node scripts/copy-assets.cjs",
|
|
47
|
-
"check:modularity": "tsx scripts/check-script-modularity.ts",
|
|
48
|
-
"pretest": "pnpm check:modularity",
|
|
49
|
-
"test": "vitest run tests/unit tests/int --coverage",
|
|
50
|
-
"posttest": "tsx scripts/check-coverage-floor.ts",
|
|
51
|
-
"test:smoke": "vitest run tests/smoke --no-coverage",
|
|
52
|
-
"test:e2e": "vitest run tests/e2e --no-coverage",
|
|
53
|
-
"test:all": "vitest run tests --no-coverage",
|
|
54
|
-
"typecheck": "tsc --noEmit",
|
|
55
|
-
"lint": "biome check",
|
|
56
|
-
"lint:fix": "biome check --write",
|
|
57
|
-
"format": "biome format --write",
|
|
58
|
-
"verify:package": "node scripts/verify-package-tarball.cjs",
|
|
59
|
-
"brain:publish": "docker buildx build --platform linux/amd64 -f runner/Dockerfile.brain -t ghcr.io/${KODY_BRAIN_GHCR_OWNER:-aharonyaircohen}/kody-brain:latest --push runner"
|
|
60
|
-
}
|
|
61
|
-
}
|
|
61
|
+
"bugs": "https://github.com/aharonyaircohen/kody-engine/issues"
|
|
62
|
+
}
|