@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.
Files changed (2) hide show
  1. package/dist/bin/kody.js +283 -206
  2. 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.394",
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 finalizeStagedRunIndexRows(config, cwd, data, result) {
7080
+ async function finalizeStagedRunIndexRowsAsync(config, cwd, data, result) {
7019
7081
  const rows = stagedRunIndexRows(data);
7020
7082
  for (const row of Object.values(rows)) {
7021
- upsertRunIndexRowBestEffort(config, cwd, finalizedRunIndexRow(row, result));
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
- return decodeGoal(await createStateBackendFromEnv().getGoal(tenantId, goalId));
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
- if (!ctx.config.state) return null;
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 = readTrustModeOverride(ctx.config.state, repoSlug, subject);
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
- upsertRunIndexRowBestEffort(
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
- upsertRunIndexRowBestEffort(
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
- finalizeStagedRunIndexRows(config, input.cwd, ctx.data, {
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.394",
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
- "scripts": {
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
+ }