@kody-ade/kody-engine 0.4.607 → 0.4.611

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/README.md CHANGED
@@ -113,7 +113,7 @@ model key at runtime; user secrets are not copied into Actions. `KODY_TOKEN`
113
113
  remains an optional GitHub authentication fallback when Kody must trigger
114
114
  downstream workflows or modify `.github/workflows/*`.
115
115
 
116
- The consumer workflow listens on `issue_comment` for `@kody ...` dispatch and `workflow_dispatch` for manual runs, chat mode, and scheduled wakeups.
116
+ The consumer workflow listens on `issue_comment` for `@kody ...` dispatch and `workflow_dispatch` for manual runs and chat mode. Convex owns scheduled Loop wakeups.
117
117
 
118
118
  ## Commands
119
119
 
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.607",
18
+ version: "0.4.611",
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",
@@ -3158,6 +3158,22 @@ function createStateBackendFromEnv(env = process.env, client) {
3158
3158
  });
3159
3159
  return Array.isArray(result) ? result : [];
3160
3160
  },
3161
+ async replaceLoopWakeRegistrations(tenantId2, loops, updatedAt) {
3162
+ await transport.mutation(anyApi.loopWakes.replaceRegistrations, {
3163
+ tenantId: requireTenant(tenantId2),
3164
+ loops,
3165
+ updatedAt: requireNonEmpty(updatedAt, "updatedAt")
3166
+ });
3167
+ },
3168
+ async markLoopWakeExecution(tenantId2, wakeId, status, detail, updatedAt) {
3169
+ await transport.mutation(anyApi.loopWakes.markExecution, {
3170
+ tenantId: requireTenant(tenantId2),
3171
+ wakeId: requireNonEmpty(wakeId, "wakeId"),
3172
+ status,
3173
+ detail,
3174
+ updatedAt: requireNonEmpty(updatedAt, "updatedAt")
3175
+ });
3176
+ },
3161
3177
  async get(tenantId2, taskKey, kind) {
3162
3178
  const result = await transport.query(anyApi.taskState.get, {
3163
3179
  tenantId: requireTenant(tenantId2),
@@ -3176,6 +3192,26 @@ function createStateBackendFromEnv(env = process.env, client) {
3176
3192
  ...expectedUpdatedAt ? { expectedUpdatedAt } : {}
3177
3193
  });
3178
3194
  },
3195
+ async getAgentState(tenantId2, agent) {
3196
+ const result = await transport.query(anyApi.agentStates.get, {
3197
+ tenantId: requireTenant(tenantId2),
3198
+ agent: requireNonEmpty(agent, "agent")
3199
+ });
3200
+ return result ?? null;
3201
+ },
3202
+ async saveAgentState(tenantId2, state, expectedRevision) {
3203
+ await transport.mutation(anyApi.agentStates.save, {
3204
+ tenantId: requireTenant(tenantId2),
3205
+ state,
3206
+ ...expectedRevision === void 0 ? {} : { expectedRevision }
3207
+ });
3208
+ },
3209
+ async resetAgentState(tenantId2, agent) {
3210
+ await transport.mutation(anyApi.agentStates.reset, {
3211
+ tenantId: requireTenant(tenantId2),
3212
+ agent: requireNonEmpty(agent, "agent")
3213
+ });
3214
+ },
3179
3215
  async getRepoDoc(tenantId2, kind) {
3180
3216
  const result = await transport.query(anyApi.repoDocs.get, {
3181
3217
  tenantId: requireTenant(tenantId2),
@@ -14457,7 +14493,7 @@ function assertLoopDispatchesSucceeded(results) {
14457
14493
  async function dispatchLoopsWith(input) {
14458
14494
  const results = [];
14459
14495
  for (const loop of input.loops) {
14460
- const slot = loopDispatchSlot(loop, input.now, input.force, input.nonce());
14496
+ const slot = input.scheduledFor ?? loopDispatchSlot(loop, input.now, input.force, input.nonce());
14461
14497
  if (!slot) continue;
14462
14498
  const reservationId = `reservation-${input.nonce()}`;
14463
14499
  const idempotencyKey = `${loop.id}:${slot}`;
@@ -14466,7 +14502,7 @@ async function dispatchLoopsWith(input) {
14466
14502
  loopId: loop.id,
14467
14503
  decision: {
14468
14504
  kind: "fire",
14469
- reason: input.force ? "manual Loop run requested" : "local Loop schedule is due",
14505
+ reason: input.scheduledFor ? "Convex scheduled Loop run" : input.force ? "manual Loop run requested" : "local Loop schedule is due",
14470
14506
  scheduledAt: slot
14471
14507
  },
14472
14508
  leaseUntil: new Date(input.now.getTime() + LOOP_DISPATCH_LEASE_MS).toISOString(),
@@ -14586,6 +14622,19 @@ function mergeLoopDefinitions(repositoryLoops, runtimeLoops) {
14586
14622
  }
14587
14623
  return [...byId.values()].sort((left, right) => left.id.localeCompare(right.id));
14588
14624
  }
14625
+ async function syncLoopWakeRegistrations(backend, tenantId2, loops, updatedAt, log2) {
14626
+ try {
14627
+ await backend.replaceLoopWakeRegistrations(
14628
+ tenantId2,
14629
+ loops.filter((loop) => loop.enabled && loop.trigger.type === "schedule"),
14630
+ updatedAt
14631
+ );
14632
+ return true;
14633
+ } catch {
14634
+ log2("\u2192 kody: Convex Loop wake registration backfill skipped; existing Loop execution continues");
14635
+ return false;
14636
+ }
14637
+ }
14589
14638
  function loopDispatchSlot(loop, now, force, nonce) {
14590
14639
  return force ? `manual:${now.toISOString()}:${nonce}` : dueSlot(loop, now);
14591
14640
  }
@@ -14643,12 +14692,25 @@ var init_dispatchLoops = __esm({
14643
14692
  const tenantId2 = repositoryTenant(ctx.config);
14644
14693
  if (!tenantId2) throw new Error("Repository identity is required for Loop dispatch");
14645
14694
  const now = /* @__PURE__ */ new Date();
14646
- const force = ctx.data.jobForce === true;
14695
+ const scheduledFor = typeof ctx.data.scheduledFor === "string" ? ctx.data.scheduledFor.trim() : "";
14696
+ const force = ctx.data.jobForce === true && !scheduledFor;
14647
14697
  const requestedLoopId = typeof ctx.args.loop === "string" ? ctx.args.loop.trim() : "";
14648
14698
  const backend = createStateBackendFromEnv();
14699
+ const wakeId = typeof ctx.data.wakeId === "string" ? ctx.data.wakeId : "";
14700
+ if (wakeId) {
14701
+ await backend.markLoopWakeExecution(tenantId2, wakeId, "running", "Engine started Loop", now.toISOString());
14702
+ }
14649
14703
  const loops = mergeLoopDefinitions(listLoopDefinitions(ctx.cwd), await backend.listLoops(tenantId2));
14704
+ await syncLoopWakeRegistrations(
14705
+ backend,
14706
+ tenantId2,
14707
+ loops,
14708
+ now.toISOString(),
14709
+ (message) => process.stderr.write(`${message}
14710
+ `)
14711
+ );
14650
14712
  const due = selectRunnableLoops(loops, now, {
14651
- force,
14713
+ force: force || Boolean(scheduledFor),
14652
14714
  ...requestedLoopId ? { loopId: requestedLoopId } : {}
14653
14715
  });
14654
14716
  process.stdout.write(`\u2192 kody: Loop scheduler found ${due.length} runnable Loop(s)${force ? " (manual)" : ""}
@@ -14659,6 +14721,7 @@ var init_dispatchLoops = __esm({
14659
14721
  backend,
14660
14722
  now,
14661
14723
  force,
14724
+ scheduledFor: scheduledFor || void 0,
14662
14725
  nonce: randomUUID,
14663
14726
  run: (job, parentRunId, loopId) => runJob(job, {
14664
14727
  cwd: ctx.cwd,
@@ -14674,6 +14737,16 @@ var init_dispatchLoops = __esm({
14674
14737
  `);
14675
14738
  }
14676
14739
  ctx.data.loopDispatchResults = results;
14740
+ if (wakeId) {
14741
+ const failed = results.find((result) => result.status === "failed" || result.status === "blocked");
14742
+ await backend.markLoopWakeExecution(
14743
+ tenantId2,
14744
+ wakeId,
14745
+ failed ? "failed" : "succeeded",
14746
+ failed?.reason ?? "Loop completed",
14747
+ (/* @__PURE__ */ new Date()).toISOString()
14748
+ );
14749
+ }
14677
14750
  assertLoopDispatchesSucceeded(results);
14678
14751
  };
14679
14752
  }
@@ -16442,14 +16515,105 @@ ${truncate2(issue2.body, FINDING_BODY_MAX_BYTES)}`;
16442
16515
  }
16443
16516
  });
16444
16517
 
16445
- // src/scripts/kodyVariables.ts
16518
+ // src/scripts/loadLiveAgent.ts
16446
16519
  import * as fs43 from "fs";
16520
+ function tenant(config) {
16521
+ const [envOwner, envRepo] = (process.env.GITHUB_REPOSITORY ?? "").split("/");
16522
+ const owner = config.github?.owner?.trim() || envOwner;
16523
+ const repo = config.github?.repo?.trim() || envRepo;
16524
+ if (!owner || !repo) throw new Error("Repository identity is required for live Agent execution");
16525
+ return `${owner}/${repo}`;
16526
+ }
16527
+ function frontmatter(raw) {
16528
+ const match = /^---\n([\s\S]*?)\n---/.exec(raw);
16529
+ if (!match) return {};
16530
+ const result = {};
16531
+ for (const line of match[1].split("\n")) {
16532
+ const at = line.indexOf(":");
16533
+ if (at < 0) continue;
16534
+ const key = line.slice(0, at).trim();
16535
+ const value = line.slice(at + 1).trim();
16536
+ result[key] = value.startsWith("[") ? value.slice(1, -1).split(",").map((entry) => entry.trim()).filter(Boolean) : value;
16537
+ }
16538
+ return result;
16539
+ }
16540
+ function guidanceBody(row, agent) {
16541
+ if (typeof row.doc?.body !== "string") return null;
16542
+ const metadata = frontmatter(row.doc.body);
16543
+ const audience = Array.isArray(metadata.agent) ? metadata.agent : [metadata.agent ?? "*"];
16544
+ if (!audience.includes("*") && !audience.includes(agent)) return null;
16545
+ return row.doc.body.replace(/^---\n[\s\S]*?\n---\n?/, "").trim();
16546
+ }
16547
+ function intentBody(row) {
16548
+ if (typeof row.doc?.body !== "string") return null;
16549
+ return row.doc.body.replace(/^---\n[\s\S]*?\n---\n?/, "").trim() || null;
16550
+ }
16551
+ var loadLiveAgent;
16552
+ var init_loadLiveAgent = __esm({
16553
+ "src/scripts/loadLiveAgent.ts"() {
16554
+ "use strict";
16555
+ init_definition_paths();
16556
+ init_agents();
16557
+ init_state_backend();
16558
+ loadLiveAgent = async (ctx, profile) => {
16559
+ const agent = String(ctx.args.agent ?? ctx.data.jobAgent ?? "").trim();
16560
+ if (!agent) throw new Error("loadLiveAgent: agent is required");
16561
+ const file = resolveAgentFile2(ctx.cwd, agent, agentsRoot(ctx.cwd));
16562
+ const raw = fs43.existsSync(file) ? fs43.readFileSync(file, "utf8") : "";
16563
+ const metadata = frontmatter(raw);
16564
+ const assignedIntent = typeof metadata.primaryIntent === "string" ? metadata.primaryIntent : "";
16565
+ const requestedIntent = String(ctx.args.intent ?? "").trim();
16566
+ const intent = requestedIntent || assignedIntent;
16567
+ if (!intent || requestedIntent && assignedIntent !== requestedIntent) {
16568
+ throw new Error(`Live Agent '${agent}' does not have the requested primary Intent`);
16569
+ }
16570
+ const backend = createStateBackendFromEnv();
16571
+ const tenantId2 = tenant(ctx.config);
16572
+ const [stateRow, intentRow, policies, constraints, context] = await Promise.all([
16573
+ backend.getAgentState(tenantId2, agent),
16574
+ backend.getRepoDoc(tenantId2, `intent:${intent}`),
16575
+ backend.listRepoDocs(tenantId2, "policy:"),
16576
+ backend.listRepoDocs(tenantId2, "constraint:"),
16577
+ backend.listRepoDocs(tenantId2, "context:")
16578
+ ]);
16579
+ if (!stateRow) throw new Error(`Live Agent '${agent}' has no AgentState`);
16580
+ const state = stateRow.state;
16581
+ const selectedIntentBody = intentRow ? intentBody(intentRow) : null;
16582
+ if (!selectedIntentBody) throw new Error(`Primary Intent '${intent}' is missing`);
16583
+ const render = (rows) => rows.map((row) => guidanceBody(row, agent)).filter(Boolean).join("\n\n") || "None assigned.";
16584
+ ctx.data.agentIdentity = loadAgentIdentity(ctx.cwd, agent);
16585
+ ctx.data.liveAgentIntent = selectedIntentBody;
16586
+ ctx.data.liveAgentPolicies = render(policies);
16587
+ ctx.data.liveAgentConstraints = render(constraints);
16588
+ ctx.data.liveAgentContext = render(context);
16589
+ ctx.data.liveAgentCapabilities = Array.isArray(metadata.capabilities) ? metadata.capabilities.map((slug) => `- ${slug}`).join("\n") : "None assigned.";
16590
+ ctx.data.liveAgentSlug = agent;
16591
+ ctx.data.liveAgentPreviousRevision = Number(state.revision ?? 0);
16592
+ ctx.data.jobState = {
16593
+ state: {
16594
+ version: 1,
16595
+ rev: Number(state.revision ?? 0),
16596
+ cursor: String(state.cursor ?? "idle"),
16597
+ data: state.data ?? {},
16598
+ done: false
16599
+ }
16600
+ };
16601
+ ctx.data.jobStateJson = JSON.stringify(state, null, 2);
16602
+ ctx.data.capabilityTools = ["start_capability"];
16603
+ ctx.data.capabilityToolMode = "lock";
16604
+ profile.claudeCode.enableSubmitTool = true;
16605
+ };
16606
+ }
16607
+ });
16608
+
16609
+ // src/scripts/kodyVariables.ts
16610
+ import * as fs44 from "fs";
16447
16611
  import * as path41 from "path";
16448
16612
  function readKodyVariables(cwd) {
16449
16613
  const full = path41.join(cwd, KODY_VARIABLES_REL_PATH);
16450
16614
  let raw;
16451
16615
  try {
16452
- raw = fs43.readFileSync(full, "utf-8");
16616
+ raw = fs44.readFileSync(full, "utf-8");
16453
16617
  } catch {
16454
16618
  return {};
16455
16619
  }
@@ -16474,7 +16638,7 @@ var init_kodyVariables = __esm({
16474
16638
  });
16475
16639
 
16476
16640
  // src/scripts/loadQaContext.ts
16477
- import * as fs44 from "fs";
16641
+ import * as fs45 from "fs";
16478
16642
  import * as path42 from "path";
16479
16643
  function parseSlugList(value) {
16480
16644
  const inner = value.startsWith("[") && value.endsWith("]") ? value.slice(1, -1) : value;
@@ -16505,17 +16669,17 @@ function readProfileAgents(raw) {
16505
16669
  }
16506
16670
  function readProfile(cwd) {
16507
16671
  const dir = path42.join(cwd, CONTEXT_DIR_REL_PATH);
16508
- if (!fs44.existsSync(dir)) return "";
16672
+ if (!fs45.existsSync(dir)) return "";
16509
16673
  let entries;
16510
16674
  try {
16511
- entries = fs44.readdirSync(dir).filter((f) => f.endsWith(".md")).sort();
16675
+ entries = fs45.readdirSync(dir).filter((f) => f.endsWith(".md")).sort();
16512
16676
  } catch {
16513
16677
  return "";
16514
16678
  }
16515
16679
  const blocks = [];
16516
16680
  for (const file of entries) {
16517
16681
  try {
16518
- const raw = fs44.readFileSync(path42.join(dir, file), "utf-8");
16682
+ const raw = fs45.readFileSync(path42.join(dir, file), "utf-8");
16519
16683
  const { agent, body } = readProfileAgents(raw);
16520
16684
  if (!agent.includes(QA_AGENT) && !agent.includes(ALL_AGENTS)) continue;
16521
16685
  blocks.push(`## ${file}
@@ -16565,7 +16729,7 @@ var init_loadQaContext = __esm({
16565
16729
 
16566
16730
  // src/scripts/loadSimpleCapability.ts
16567
16731
  import { randomUUID as randomUUID2 } from "crypto";
16568
- import * as fs45 from "fs";
16732
+ import * as fs46 from "fs";
16569
16733
  import * as os6 from "os";
16570
16734
  import * as path43 from "path";
16571
16735
  function registerCapabilitySubagents(profile, toolRoot, toolFiles) {
@@ -16580,7 +16744,7 @@ function registerCapabilitySubagents(profile, toolRoot, toolFiles) {
16580
16744
  profile.subagentTemplates = {
16581
16745
  ...profile.subagentTemplates ?? {},
16582
16746
  ...Object.fromEntries(
16583
- subagentFiles.map(({ name, file }) => [name, fs45.readFileSync(path43.join(toolRoot, file), "utf-8")])
16747
+ subagentFiles.map(({ name, file }) => [name, fs46.readFileSync(path43.join(toolRoot, file), "utf-8")])
16584
16748
  )
16585
16749
  };
16586
16750
  if (!profile.claudeCode.tools.includes("Agent")) {
@@ -16621,10 +16785,10 @@ function scalar(value) {
16621
16785
  return value;
16622
16786
  }
16623
16787
  function listFiles(root) {
16624
- if (!fs45.existsSync(root)) return [];
16788
+ if (!fs46.existsSync(root)) return [];
16625
16789
  const files = [];
16626
16790
  const visit = (dir) => {
16627
- for (const entry of fs45.readdirSync(dir, { withFileTypes: true })) {
16791
+ for (const entry of fs46.readdirSync(dir, { withFileTypes: true })) {
16628
16792
  const absolute = path43.join(dir, entry.name);
16629
16793
  if (entry.isSymbolicLink()) continue;
16630
16794
  if (entry.isDirectory()) visit(absolute);
@@ -16716,7 +16880,7 @@ var init_loadSimpleCapability = __esm({
16716
16880
  ...skillFiles.flatMap((file) => [
16717
16881
  `### ${file}`,
16718
16882
  "",
16719
- fs45.readFileSync(path43.join(skillRoot, file), "utf-8"),
16883
+ fs46.readFileSync(path43.join(skillRoot, file), "utf-8"),
16720
16884
  ""
16721
16885
  ])
16722
16886
  ] : [],
@@ -16756,7 +16920,7 @@ var init_loadSimpleCapability = __esm({
16756
16920
  });
16757
16921
 
16758
16922
  // src/taskContext.ts
16759
- import * as fs46 from "fs";
16923
+ import * as fs47 from "fs";
16760
16924
  import * as path44 from "path";
16761
16925
  function buildTaskContext(args) {
16762
16926
  return {
@@ -16773,9 +16937,9 @@ function buildTaskContext(args) {
16773
16937
  function persistTaskContext(cwd, ctx) {
16774
16938
  try {
16775
16939
  const dir = runtimeStatePath(cwd, "agent-runs", ctx.runId);
16776
- fs46.mkdirSync(dir, { recursive: true });
16940
+ fs47.mkdirSync(dir, { recursive: true });
16777
16941
  const file = path44.join(dir, "task-context.json");
16778
- fs46.writeFileSync(file, `${JSON.stringify(ctx, null, 2)}
16942
+ fs47.writeFileSync(file, `${JSON.stringify(ctx, null, 2)}
16779
16943
  `);
16780
16944
  return file;
16781
16945
  } catch (err) {
@@ -17696,7 +17860,7 @@ var init_parseReproOutput = __esm({
17696
17860
  });
17697
17861
 
17698
17862
  // src/scripts/parseSimpleCapabilityOutput.ts
17699
- import * as fs47 from "fs";
17863
+ import * as fs48 from "fs";
17700
17864
  function acceptAuthoritativeCapabilityOutput(ctx, profile, output) {
17701
17865
  ctx.data.agentDone = true;
17702
17866
  delete ctx.data.agentFailureReason;
@@ -17713,11 +17877,11 @@ function stringList2(value) {
17713
17877
  return Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
17714
17878
  }
17715
17879
  function readOutputFile(outputPath) {
17716
- if (!outputPath || !fs47.existsSync(outputPath)) return { found: false };
17880
+ if (!outputPath || !fs48.existsSync(outputPath)) return { found: false };
17717
17881
  try {
17718
- return { found: true, value: JSON.parse(fs47.readFileSync(outputPath, "utf-8")) };
17882
+ return { found: true, value: JSON.parse(fs48.readFileSync(outputPath, "utf-8")) };
17719
17883
  } finally {
17720
- fs47.rmSync(outputPath, { force: true });
17884
+ fs48.rmSync(outputPath, { force: true });
17721
17885
  }
17722
17886
  }
17723
17887
  function parseOutput(text2) {
@@ -18339,7 +18503,7 @@ var init_postResearchComment = __esm({
18339
18503
  });
18340
18504
 
18341
18505
  // src/scripts/prepareBrowserAuth.ts
18342
- import * as fs48 from "fs";
18506
+ import * as fs49 from "fs";
18343
18507
  import * as os7 from "os";
18344
18508
  import * as path45 from "path";
18345
18509
  function appendAuthMessage(ctx, message) {
@@ -18385,8 +18549,8 @@ async function githubJson(url, token, checkName) {
18385
18549
  throw new Error(`GitHub ${checkName} check failed`);
18386
18550
  }
18387
18551
  function writeKodyStorageState(input) {
18388
- const directory = fs48.mkdtempSync(path45.join(os7.tmpdir(), "kody-browser-auth-"));
18389
- fs48.chmodSync(directory, 448);
18552
+ const directory = fs49.mkdtempSync(path45.join(os7.tmpdir(), "kody-browser-auth-"));
18553
+ fs49.chmodSync(directory, 448);
18390
18554
  const file = path45.join(directory, "storage-state.json");
18391
18555
  const now = Date.now();
18392
18556
  const repoEntry = {
@@ -18418,7 +18582,7 @@ function writeKodyStorageState(input) {
18418
18582
  }
18419
18583
  ]
18420
18584
  };
18421
- fs48.writeFileSync(file, JSON.stringify(storageState), { mode: 384 });
18585
+ fs49.writeFileSync(file, JSON.stringify(storageState), { mode: 384 });
18422
18586
  return { directory, file, auth };
18423
18587
  }
18424
18588
  function parseSetCookie(value, hostname) {
@@ -18448,10 +18612,10 @@ function parseSetCookie(value, hostname) {
18448
18612
  }
18449
18613
  function writeCookieStorageState(targetUrl, setCookies) {
18450
18614
  const target = new URL(targetUrl);
18451
- const directory = fs48.mkdtempSync(path45.join(os7.tmpdir(), "kody-browser-auth-"));
18452
- fs48.chmodSync(directory, 448);
18615
+ const directory = fs49.mkdtempSync(path45.join(os7.tmpdir(), "kody-browser-auth-"));
18616
+ fs49.chmodSync(directory, 448);
18453
18617
  const file = path45.join(directory, "storage-state.json");
18454
- fs48.writeFileSync(
18618
+ fs49.writeFileSync(
18455
18619
  file,
18456
18620
  JSON.stringify({
18457
18621
  cookies: setCookies.map((cookie) => parseSetCookie(cookie, target.hostname)),
@@ -18472,9 +18636,9 @@ function currentStorageStatePath(args) {
18472
18636
  function browserSessionCookieHeader(profile, targetUrl) {
18473
18637
  const playwright = profile.claudeCode.mcpServers.find((server) => server.name === "playwright");
18474
18638
  const storagePath = currentStorageStatePath(playwright?.args ?? []);
18475
- if (!storagePath || !fs48.existsSync(storagePath)) return void 0;
18639
+ if (!storagePath || !fs49.existsSync(storagePath)) return void 0;
18476
18640
  const hostname = new URL(targetUrl).hostname;
18477
- const state = JSON.parse(fs48.readFileSync(storagePath, "utf-8"));
18641
+ const state = JSON.parse(fs49.readFileSync(storagePath, "utf-8"));
18478
18642
  const cookies = (state.cookies ?? []).filter((cookie) => {
18479
18643
  const domain = cookie.domain.replace(/^\./, "");
18480
18644
  return hostname === domain || hostname.endsWith(`.${domain}`);
@@ -18541,9 +18705,9 @@ async function prepareAccountModelSettings(ctx, profile, input) {
18541
18705
  return true;
18542
18706
  }
18543
18707
  function mergeStorageStates(existingPath, nextPath) {
18544
- if (existingPath === nextPath || !fs48.existsSync(existingPath)) return;
18545
- const existing = JSON.parse(fs48.readFileSync(existingPath, "utf-8"));
18546
- const next = JSON.parse(fs48.readFileSync(nextPath, "utf-8"));
18708
+ if (existingPath === nextPath || !fs49.existsSync(existingPath)) return;
18709
+ const existing = JSON.parse(fs49.readFileSync(existingPath, "utf-8"));
18710
+ const next = JSON.parse(fs49.readFileSync(nextPath, "utf-8"));
18547
18711
  const cookies = /* @__PURE__ */ new Map();
18548
18712
  for (const cookie of [...existing.cookies ?? [], ...next.cookies ?? []]) {
18549
18713
  cookies.set(`${cookie.name}\0${cookie.domain}\0${cookie.path}`, cookie);
@@ -18557,7 +18721,7 @@ function mergeStorageStates(existingPath, nextPath) {
18557
18721
  }
18558
18722
  origins.set(entry.origin, { origin: entry.origin, localStorage: [...localStorage.values()] });
18559
18723
  }
18560
- fs48.writeFileSync(
18724
+ fs49.writeFileSync(
18561
18725
  nextPath,
18562
18726
  JSON.stringify({ cookies: [...cookies.values()], origins: [...origins.values()] }),
18563
18727
  { mode: 384 }
@@ -18654,7 +18818,7 @@ async function prepareKodyRepositoryBrowserAuth(ctx, profile, input) {
18654
18818
  configurePlaywright(profile, state.file);
18655
18819
  const authDirectory = state.directory;
18656
18820
  registerRuntimeCleanup(ctx, () => {
18657
- fs48.rmSync(authDirectory, { recursive: true, force: true });
18821
+ fs49.rmSync(authDirectory, { recursive: true, force: true });
18658
18822
  });
18659
18823
  appendAuthMessage(
18660
18824
  ctx,
@@ -18662,7 +18826,7 @@ async function prepareKodyRepositoryBrowserAuth(ctx, profile, input) {
18662
18826
  );
18663
18827
  return true;
18664
18828
  } catch (error) {
18665
- if (state) fs48.rmSync(state.directory, { recursive: true, force: true });
18829
+ if (state) fs49.rmSync(state.directory, { recursive: true, force: true });
18666
18830
  const reason = error instanceof Error ? error.message : String(error);
18667
18831
  appendAuthMessage(
18668
18832
  ctx,
@@ -18689,11 +18853,11 @@ async function prepareEmailPasswordBrowserAuth(ctx, profile, input) {
18689
18853
  state = writeCookieStorageState(input.targetUrl, cookies);
18690
18854
  configurePlaywright(profile, state.file);
18691
18855
  const authDirectory = state.directory;
18692
- registerRuntimeCleanup(ctx, () => fs48.rmSync(authDirectory, { recursive: true, force: true }));
18856
+ registerRuntimeCleanup(ctx, () => fs49.rmSync(authDirectory, { recursive: true, force: true }));
18693
18857
  ctx.data.qaAuthBlock = "Auth: the app is already signed in through an engine-provided browser session. The login credentials are not available to you; never request, reveal, or report them.";
18694
18858
  return true;
18695
18859
  } catch (error) {
18696
- if (state) fs48.rmSync(state.directory, { recursive: true, force: true });
18860
+ if (state) fs49.rmSync(state.directory, { recursive: true, force: true });
18697
18861
  const reason = error instanceof Error ? error.message : String(error);
18698
18862
  ctx.data.qaAuthBlock = `Auth: the engine could not prepare the app login (${reason}). Note this authenticated surface as a gap.`;
18699
18863
  return false;
@@ -20607,7 +20771,7 @@ var init_tickShellRunner = __esm({
20607
20771
  });
20608
20772
 
20609
20773
  // src/scripts/runScheduledImplementationTick.ts
20610
- import * as fs49 from "fs";
20774
+ import * as fs50 from "fs";
20611
20775
  import * as path48 from "path";
20612
20776
  var runScheduledImplementationTick;
20613
20777
  var init_runScheduledImplementationTick = __esm({
@@ -20636,7 +20800,7 @@ var init_runScheduledImplementationTick = __esm({
20636
20800
  return;
20637
20801
  }
20638
20802
  const shellPath = path48.join(profile.dir, shell);
20639
- if (!fs49.existsSync(shellPath)) {
20803
+ if (!fs50.existsSync(shellPath)) {
20640
20804
  ctx.output.exitCode = 99;
20641
20805
  ctx.output.reason = `runScheduledImplementationTick: shell not found: ${shell} (looked in ${profile.dir})`;
20642
20806
  return;
@@ -20668,13 +20832,13 @@ var init_runScheduledImplementationTick = __esm({
20668
20832
 
20669
20833
  // src/scripts/runSimpleCapabilityScript.ts
20670
20834
  import { spawnSync as spawnSync3 } from "child_process";
20671
- import * as fs50 from "fs";
20835
+ import * as fs51 from "fs";
20672
20836
  function formatDuration2(timeoutMs) {
20673
20837
  return timeoutMs % 6e4 === 0 ? `${timeoutMs / 6e4} minutes` : `${timeoutMs}ms`;
20674
20838
  }
20675
20839
  function isRegularFile2(filePath) {
20676
20840
  try {
20677
- const stat = fs50.lstatSync(filePath);
20841
+ const stat = fs51.lstatSync(filePath);
20678
20842
  return stat.isFile() && !stat.isSymbolicLink();
20679
20843
  } catch {
20680
20844
  return false;
@@ -20753,7 +20917,7 @@ var init_runSimpleCapabilityScript = __esm({
20753
20917
  });
20754
20918
 
20755
20919
  // src/scripts/runTickScript.ts
20756
- import * as fs51 from "fs";
20920
+ import * as fs52 from "fs";
20757
20921
  import * as path49 from "path";
20758
20922
  var runTickScript;
20759
20923
  var init_runTickScript = __esm({
@@ -20787,7 +20951,7 @@ var init_runTickScript = __esm({
20787
20951
  return;
20788
20952
  }
20789
20953
  const scriptPath = path49.isAbsolute(tickScript) ? tickScript : path49.join(ctx.cwd, tickScript);
20790
- if (!fs51.existsSync(scriptPath)) {
20954
+ if (!fs52.existsSync(scriptPath)) {
20791
20955
  ctx.output.exitCode = 99;
20792
20956
  ctx.output.reason = `runTickScript: tickScript not found: ${scriptPath}`;
20793
20957
  return;
@@ -20835,6 +20999,41 @@ var init_saveManagedGoalState = __esm({
20835
20999
  }
20836
21000
  });
20837
21001
 
21002
+ // src/scripts/saveLiveAgentState.ts
21003
+ var saveLiveAgentState;
21004
+ var init_saveLiveAgentState = __esm({
21005
+ "src/scripts/saveLiveAgentState.ts"() {
21006
+ "use strict";
21007
+ init_state_backend();
21008
+ saveLiveAgentState = async (ctx, _profile, agentResult) => {
21009
+ const agent = String(ctx.data.liveAgentSlug ?? "");
21010
+ const previousRevision = Number(ctx.data.liveAgentPreviousRevision ?? 0);
21011
+ const next = ctx.data.nextJobState;
21012
+ if (!agent || !next || typeof next.cursor !== "string" || !next.cursor) {
21013
+ throw new Error(String(ctx.data.nextStateParseError ?? "Live Agent did not submit valid continuation state"));
21014
+ }
21015
+ const [envOwner, envRepo] = (process.env.GITHUB_REPOSITORY ?? "").split("/");
21016
+ const owner = ctx.config.github?.owner?.trim() || envOwner;
21017
+ const repo = ctx.config.github?.repo?.trim() || envRepo;
21018
+ if (!owner || !repo) throw new Error("Repository identity is required for live Agent state");
21019
+ const summary = (agentResult?.finalText ?? "").trim().slice(0, 1e3);
21020
+ await createStateBackendFromEnv().saveAgentState(
21021
+ `${owner}/${repo}`,
21022
+ {
21023
+ version: 1,
21024
+ agent,
21025
+ revision: previousRevision + 1,
21026
+ cursor: next.cursor,
21027
+ summary,
21028
+ data: next.data && typeof next.data === "object" ? next.data : {},
21029
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
21030
+ },
21031
+ previousRevision
21032
+ );
21033
+ };
21034
+ }
21035
+ });
21036
+
20838
21037
  // src/scripts/setCommentTarget.ts
20839
21038
  var setCommentTarget;
20840
21039
  var init_setCommentTarget = __esm({
@@ -21952,7 +22151,7 @@ var init_warmupMcp = __esm({
21952
22151
  });
21953
22152
 
21954
22153
  // src/scripts/writeAgentRunSummary.ts
21955
- import * as fs52 from "fs";
22154
+ import * as fs53 from "fs";
21956
22155
  var writeAgentRunSummary;
21957
22156
  var init_writeAgentRunSummary = __esm({
21958
22157
  "src/scripts/writeAgentRunSummary.ts"() {
@@ -21978,7 +22177,7 @@ var init_writeAgentRunSummary = __esm({
21978
22177
  if (reason) lines.push(`- **Reason:** ${reason}`);
21979
22178
  lines.push("");
21980
22179
  try {
21981
- fs52.appendFileSync(summaryPath, `${lines.join("\n")}
22180
+ fs53.appendFileSync(summaryPath, `${lines.join("\n")}
21982
22181
  `);
21983
22182
  } catch {
21984
22183
  }
@@ -22135,6 +22334,7 @@ var init_scripts = __esm({
22135
22334
  init_loadIssueStateComment();
22136
22335
  init_loadJobFromFile();
22137
22336
  init_loadLinkedFinding();
22337
+ init_loadLiveAgent();
22138
22338
  init_loadMemoryContext();
22139
22339
  init_loadPriorArt();
22140
22340
  init_loadQaContext();
@@ -22183,6 +22383,7 @@ var init_scripts = __esm({
22183
22383
  init_runSimpleCapabilityScript();
22184
22384
  init_runTickScript();
22185
22385
  init_saveManagedGoalState();
22386
+ init_saveLiveAgentState();
22186
22387
  init_saveTaskState();
22187
22388
  init_setCommentTarget();
22188
22389
  init_setLifecycleLabel();
@@ -22222,6 +22423,7 @@ var init_scripts = __esm({
22222
22423
  loadConventions,
22223
22424
  loadCoverageRules,
22224
22425
  loadLinkedFinding,
22426
+ loadLiveAgent,
22225
22427
  loadMemoryContext,
22226
22428
  loadPriorArt,
22227
22429
  loadQaContext,
@@ -22257,6 +22459,7 @@ var init_scripts = __esm({
22257
22459
  saveManagedGoalState
22258
22460
  };
22259
22461
  postflightScripts = {
22462
+ saveLiveAgentState,
22260
22463
  parseSimpleCapabilityOutput,
22261
22464
  parseAgentResult: parseAgentResult2,
22262
22465
  parseIssueStateFromAgentResult,
@@ -22316,7 +22519,7 @@ var init_scripts = __esm({
22316
22519
  });
22317
22520
 
22318
22521
  // src/stateWorkspace.ts
22319
- import * as fs53 from "fs";
22522
+ import * as fs54 from "fs";
22320
22523
  import * as path51 from "path";
22321
22524
  function tenantId(config) {
22322
22525
  const owner = config.github?.owner?.trim() || process.env.GITHUB_REPOSITORY?.split("/")[0]?.trim();
@@ -22325,8 +22528,8 @@ function tenantId(config) {
22325
22528
  }
22326
22529
  function writeRuntimeFile(cwd, relativePath, content) {
22327
22530
  const target = path51.join(cwd, RUNTIME_ROOT, relativePath);
22328
- fs53.mkdirSync(path51.dirname(target), { recursive: true });
22329
- fs53.writeFileSync(target, content, "utf8");
22531
+ fs54.mkdirSync(path51.dirname(target), { recursive: true });
22532
+ fs54.writeFileSync(target, content, "utf8");
22330
22533
  }
22331
22534
  function record(value) {
22332
22535
  return value && typeof value === "object" && !Array.isArray(value) ? value : null;
@@ -22347,8 +22550,8 @@ function memoryIndex(docs) {
22347
22550
  if (lines.length === 0) return "";
22348
22551
  return ["# Kody memory index", "", "One line per backend memory document.", "", ...lines, ""].join("\n");
22349
22552
  }
22350
- async function hydratePrefix(backend, tenant, cwd, prefix) {
22351
- const docs = await backend.listRepoDocs(tenant, prefix);
22553
+ async function hydratePrefix(backend, tenant2, cwd, prefix) {
22554
+ const docs = await backend.listRepoDocs(tenant2, prefix);
22352
22555
  for (const doc of docs) {
22353
22556
  const slug = doc.kind.slice(prefix.length);
22354
22557
  if (!/^[a-z0-9][a-z0-9_-]{0,63}$/.test(slug)) continue;
@@ -22361,8 +22564,8 @@ async function hydratePrefix(backend, tenant, cwd, prefix) {
22361
22564
  if (index) writeRuntimeFile(cwd, "memory/INDEX.md", index);
22362
22565
  }
22363
22566
  }
22364
- async function hydrateSingleton(backend, tenant, cwd, kind, relativePath) {
22365
- const doc = await backend.getRepoDoc(tenant, kind);
22567
+ async function hydrateSingleton(backend, tenant2, cwd, kind, relativePath) {
22568
+ const doc = await backend.getRepoDoc(tenant2, kind);
22366
22569
  if (!doc) return;
22367
22570
  const body = stringField6(doc.doc, "body");
22368
22571
  if (body !== null) {
@@ -22372,8 +22575,8 @@ async function hydrateSingleton(backend, tenant, cwd, kind, relativePath) {
22372
22575
  writeRuntimeFile(cwd, relativePath, `${JSON.stringify(doc.doc, null, 2)}
22373
22576
  `);
22374
22577
  }
22375
- async function hydrateWorkflows(backend, tenant, cwd) {
22376
- for (const workflow of await backend.listWorkflows(tenant)) {
22578
+ async function hydrateWorkflows(backend, tenant2, cwd) {
22579
+ for (const workflow of await backend.listWorkflows(tenant2)) {
22377
22580
  if (!/^[a-z0-9][a-z0-9_-]{0,79}$/.test(workflow.workflowId)) continue;
22378
22581
  writeRuntimeFile(
22379
22582
  cwd,
@@ -22384,25 +22587,25 @@ async function hydrateWorkflows(backend, tenant, cwd) {
22384
22587
  }
22385
22588
  }
22386
22589
  async function hydrateStateWorkspace(config, cwd, backendOverride) {
22387
- const tenant = tenantId(config);
22590
+ const tenant2 = tenantId(config);
22388
22591
  const configured = hasStateBackendConfig();
22389
- if (!tenant || !configured) {
22592
+ if (!tenant2 || !configured) {
22390
22593
  if (process.env.GITHUB_ACTIONS === "true")
22391
22594
  throw new Error("Kody backend access is required for runtime workspace documents");
22392
22595
  return;
22393
22596
  }
22394
- const key = `${path51.resolve(cwd)}|${tenant}`;
22597
+ const key = `${path51.resolve(cwd)}|${tenant2}`;
22395
22598
  if (hydratedWorkspaces.has(key)) return;
22396
22599
  const backend = backendOverride ?? createStateBackendFromEnv();
22397
22600
  const root = path51.join(cwd, RUNTIME_ROOT);
22398
- fs53.rmSync(root, { recursive: true, force: true });
22601
+ fs54.rmSync(root, { recursive: true, force: true });
22399
22602
  await Promise.all([
22400
- hydratePrefix(backend, tenant, cwd, "context:"),
22401
- hydratePrefix(backend, tenant, cwd, "memory:"),
22402
- hydrateSingleton(backend, tenant, cwd, "instructions", "instructions.md"),
22403
- hydrateSingleton(backend, tenant, cwd, "system-prompt", "system-prompt.md"),
22404
- hydrateSingleton(backend, tenant, cwd, "variables", "variables.json"),
22405
- hydrateWorkflows(backend, tenant, cwd)
22603
+ hydratePrefix(backend, tenant2, cwd, "context:"),
22604
+ hydratePrefix(backend, tenant2, cwd, "memory:"),
22605
+ hydrateSingleton(backend, tenant2, cwd, "instructions", "instructions.md"),
22606
+ hydrateSingleton(backend, tenant2, cwd, "system-prompt", "system-prompt.md"),
22607
+ hydrateSingleton(backend, tenant2, cwd, "variables", "variables.json"),
22608
+ hydrateWorkflows(backend, tenant2, cwd)
22406
22609
  ]);
22407
22610
  hydratedWorkspaces.add(key);
22408
22611
  }
@@ -22482,7 +22685,7 @@ var init_tools = __esm({
22482
22685
 
22483
22686
  // src/executor.ts
22484
22687
  import { spawn as spawn8 } from "child_process";
22485
- import * as fs54 from "fs";
22688
+ import * as fs55 from "fs";
22486
22689
  import * as os8 from "os";
22487
22690
  import * as path52 from "path";
22488
22691
  function isMutatingPostflight(scriptName) {
@@ -23269,7 +23472,7 @@ function resolveProfilePath(profileName, cwd = process.cwd()) {
23269
23472
  // fallback
23270
23473
  ];
23271
23474
  for (const c of candidates) {
23272
- if (fs54.existsSync(c)) return c;
23475
+ if (fs55.existsSync(c)) return c;
23273
23476
  }
23274
23477
  return candidates[0];
23275
23478
  }
@@ -23385,7 +23588,7 @@ function resolveShellTimeoutMs(entry) {
23385
23588
  async function runShellEntry(entry, ctx, profile) {
23386
23589
  const shellName = entry.shell;
23387
23590
  const shellPath = path52.join(profile.dir, shellName);
23388
- if (!fs54.existsSync(shellPath)) {
23591
+ if (!fs55.existsSync(shellPath)) {
23389
23592
  ctx.skipAgent = true;
23390
23593
  ctx.output.exitCode = 99;
23391
23594
  ctx.output.reason = `shell script not found: ${shellName} (looked in ${profile.dir})`;
@@ -23459,9 +23662,9 @@ async function runShellEntry(entry, ctx, profile) {
23459
23662
  }
23460
23663
  let sideChannelText = "";
23461
23664
  try {
23462
- if (fs54.existsSync(outputFile)) {
23463
- sideChannelText = fs54.readFileSync(outputFile, "utf-8");
23464
- fs54.rmSync(outputFile, { force: true });
23665
+ if (fs55.existsSync(outputFile)) {
23666
+ sideChannelText = fs55.readFileSync(outputFile, "utf-8");
23667
+ fs55.rmSync(outputFile, { force: true });
23465
23668
  }
23466
23669
  } catch {
23467
23670
  }
@@ -26194,7 +26397,7 @@ async function hydrateDefinitionsFromEnv(cwd = process.cwd(), env = process.env)
26194
26397
 
26195
26398
  // src/kody-cli.ts
26196
26399
  import { execFileSync as execFileSync24 } from "child_process";
26197
- import * as fs55 from "fs";
26400
+ import * as fs56 from "fs";
26198
26401
  import * as path53 from "path";
26199
26402
 
26200
26403
  // src/app-auth.ts
@@ -26242,26 +26445,6 @@ async function ghApp(jwt, apiPath, method = "GET") {
26242
26445
  }
26243
26446
  return await res.json();
26244
26447
  }
26245
- async function ghAppPage(authToken, apiPath) {
26246
- const res = await fetch(`${GH_API}${apiPath}`, {
26247
- headers: {
26248
- Authorization: `Bearer ${authToken}`,
26249
- Accept: "application/vnd.github+json",
26250
- "X-GitHub-Api-Version": "2022-11-28",
26251
- "User-Agent": "kody-engine"
26252
- }
26253
- });
26254
- if (!res.ok) {
26255
- const body = await res.text().catch(() => "");
26256
- throw new Error(
26257
- `GitHub App API GET ${apiPath} \u2192 ${res.status} ${res.statusText}${body ? `: ${body.slice(0, 200)}` : ""}`
26258
- );
26259
- }
26260
- return {
26261
- data: await res.json(),
26262
- hasNext: /rel="next"/.test(res.headers.get("link") ?? "")
26263
- };
26264
- }
26265
26448
  function readAppCreds(env = process.env) {
26266
26449
  const appId = env.KODY_APP_ID?.trim();
26267
26450
  const privateKey = env.KODY_APP_PRIVATE_KEY;
@@ -26286,36 +26469,6 @@ async function mintAppInstallationToken(creds) {
26286
26469
  const tok = await ghApp(jwt, `/app/installations/${installationId}/access_tokens`, "POST");
26287
26470
  return tok.token;
26288
26471
  }
26289
- async function discoverAppRepositories(creds) {
26290
- const jwt = buildAppJwt(creds.appId, creds.privateKey);
26291
- const installations = [];
26292
- for (let page = 1; ; page++) {
26293
- const result = await ghAppPage(jwt, `/app/installations?per_page=100&page=${page}`);
26294
- installations.push(...result.data.filter((item) => Number.isInteger(item.id) && item.id > 0));
26295
- if (!result.hasNext) break;
26296
- }
26297
- const byRepo = /* @__PURE__ */ new Map();
26298
- for (const installation of installations) {
26299
- const token = await mintAppInstallationToken({
26300
- appId: creds.appId,
26301
- privateKey: creds.privateKey,
26302
- installationId: String(installation.id)
26303
- });
26304
- for (let page = 1; ; page++) {
26305
- const result = await ghAppPage(
26306
- token,
26307
- `/installation/repositories?per_page=100&page=${page}`
26308
- );
26309
- for (const repository of result.data.repositories ?? []) {
26310
- const repo = repository.full_name?.trim();
26311
- if (!repo || !/^[^/\s]+\/[^/\s]+$/.test(repo)) continue;
26312
- byRepo.set(repo.toLowerCase(), { repo, token });
26313
- }
26314
- if (!result.hasNext) break;
26315
- }
26316
- }
26317
- return [...byRepo.values()].sort((left, right) => left.repo.localeCompare(right.repo));
26318
- }
26319
26472
 
26320
26473
  // src/kody-cli.ts
26321
26474
  init_capabilityFolders();
@@ -26891,7 +27044,8 @@ function routeRunRequest(request) {
26891
27044
  return {
26892
27045
  kind: "action",
26893
27046
  action: "loop-scheduler",
26894
- cliArgs: { loop: target.id }
27047
+ cliArgs: { loop: target.id },
27048
+ ...typeof request.input?.scheduledFor === "string" ? { scheduledFor: request.input.scheduledFor } : {}
26895
27049
  };
26896
27050
  }
26897
27051
  if (target.type === "workflow") {
@@ -26997,9 +27151,9 @@ async function resolveAuthToken(env = process.env) {
26997
27151
  return void 0;
26998
27152
  }
26999
27153
  function detectPackageManager2(cwd) {
27000
- if (fs55.existsSync(path53.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
27001
- if (fs55.existsSync(path53.join(cwd, "yarn.lock"))) return "yarn";
27002
- if (fs55.existsSync(path53.join(cwd, "bun.lockb"))) return "bun";
27154
+ if (fs56.existsSync(path53.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
27155
+ if (fs56.existsSync(path53.join(cwd, "yarn.lock"))) return "yarn";
27156
+ if (fs56.existsSync(path53.join(cwd, "bun.lockb"))) return "bun";
27003
27157
  return "npm";
27004
27158
  }
27005
27159
  function shouldChainScheduledWatch(match) {
@@ -27102,8 +27256,8 @@ function postFailureTail(issueNumber, cwd, reason) {
27102
27256
  const logPath = lastRunLogPath(cwd);
27103
27257
  let tail = "";
27104
27258
  try {
27105
- if (fs55.existsSync(logPath)) {
27106
- const content = fs55.readFileSync(logPath, "utf-8");
27259
+ if (fs56.existsSync(logPath)) {
27260
+ const content = fs56.readFileSync(logPath, "utf-8");
27107
27261
  tail = content.slice(-3e3);
27108
27262
  }
27109
27263
  } catch {
@@ -27159,6 +27313,8 @@ async function runCi(argv) {
27159
27313
  let forceRunAction = null;
27160
27314
  let forceRunCliArgs = {};
27161
27315
  let forceWorkflowRunId;
27316
+ let forceRunScheduledFor;
27317
+ let forceRunWakeId;
27162
27318
  let forceRunTargetKind = "action";
27163
27319
  let runRequestFanOut = false;
27164
27320
  let runRequestFanOutForce = false;
@@ -27183,6 +27339,8 @@ async function runCi(argv) {
27183
27339
  forceRunAction = route.action;
27184
27340
  forceRunCliArgs = route.cliArgs;
27185
27341
  forceWorkflowRunId = route.workflowRunId;
27342
+ forceRunScheduledFor = route.scheduledFor;
27343
+ if (route.scheduledFor) forceRunWakeId = parsedRunRequest.request.requestId;
27186
27344
  } else if (route.kind === "workflow") {
27187
27345
  forceRunAction = route.workflow;
27188
27346
  forceRunTargetKind = "workflow";
@@ -27198,9 +27356,9 @@ async function runCi(argv) {
27198
27356
  forceRunCliArgs = { goal: envForceMessage };
27199
27357
  }
27200
27358
  }
27201
- if (!args.issueNumber && !autoFallback && !forceRunAction && !runRequestFanOut && eventName === "workflow_dispatch" && dispatchEventPath && fs55.existsSync(dispatchEventPath)) {
27359
+ if (!args.issueNumber && !autoFallback && !forceRunAction && !runRequestFanOut && eventName === "workflow_dispatch" && dispatchEventPath && fs56.existsSync(dispatchEventPath)) {
27202
27360
  try {
27203
- const evt = JSON.parse(fs55.readFileSync(dispatchEventPath, "utf-8"));
27361
+ const evt = JSON.parse(fs56.readFileSync(dispatchEventPath, "utf-8"));
27204
27362
  const inputs = objectValue2(evt.inputs);
27205
27363
  const issueInput = parseInt(String(inputs?.issue_number ?? ""), 10);
27206
27364
  const sessionInput = String(inputs?.sessionId ?? "");
@@ -27326,7 +27484,8 @@ async function runCi(argv) {
27326
27484
  cwd,
27327
27485
  config,
27328
27486
  verbose: args.verbose,
27329
- quiet: args.quiet
27487
+ quiet: args.quiet,
27488
+ ...forceRunScheduledFor ? { preloadedData: { scheduledFor: forceRunScheduledFor, wakeId: forceRunWakeId } } : {}
27330
27489
  }
27331
27490
  );
27332
27491
  const ec = result.exitCode;
@@ -27615,7 +27774,7 @@ init_repoWorkspace();
27615
27774
 
27616
27775
  // src/scripts/brainTurnLog.ts
27617
27776
  init_runtimePaths();
27618
- import * as fs56 from "fs";
27777
+ import * as fs57 from "fs";
27619
27778
  import * as path54 from "path";
27620
27779
  import posixPath4 from "path/posix";
27621
27780
  var live = /* @__PURE__ */ new Map();
@@ -27624,8 +27783,8 @@ function brainEventsFilePath(dir, chatId) {
27624
27783
  }
27625
27784
  function lastPersistedSeq(dir, chatId) {
27626
27785
  const p = brainEventsFilePath(dir, chatId);
27627
- if (!fs56.existsSync(p)) return 0;
27628
- const lines = fs56.readFileSync(p, "utf-8").split("\n").filter(Boolean);
27786
+ if (!fs57.existsSync(p)) return 0;
27787
+ const lines = fs57.readFileSync(p, "utf-8").split("\n").filter(Boolean);
27629
27788
  if (lines.length === 0) return 0;
27630
27789
  try {
27631
27790
  return JSON.parse(lines[lines.length - 1]).seq || 0;
@@ -27635,9 +27794,9 @@ function lastPersistedSeq(dir, chatId) {
27635
27794
  }
27636
27795
  function readSince(dir, chatId, since) {
27637
27796
  const p = brainEventsFilePath(dir, chatId);
27638
- if (!fs56.existsSync(p)) return [];
27797
+ if (!fs57.existsSync(p)) return [];
27639
27798
  const out = [];
27640
- for (const line of fs56.readFileSync(p, "utf-8").split("\n")) {
27799
+ for (const line of fs57.readFileSync(p, "utf-8").split("\n")) {
27641
27800
  if (!line) continue;
27642
27801
  try {
27643
27802
  const rec = JSON.parse(line);
@@ -27663,12 +27822,12 @@ function beginTurn(dir, chatId) {
27663
27822
  };
27664
27823
  live.set(chatId, state);
27665
27824
  const p = brainEventsFilePath(dir, chatId);
27666
- fs56.mkdirSync(path54.dirname(p), { recursive: true });
27825
+ fs57.mkdirSync(path54.dirname(p), { recursive: true });
27667
27826
  return (event) => {
27668
27827
  state.seq += 1;
27669
27828
  const rec = { seq: state.seq, turn, ts: Date.now(), event };
27670
27829
  try {
27671
- fs56.appendFileSync(p, `${JSON.stringify(rec)}
27830
+ fs57.appendFileSync(p, `${JSON.stringify(rec)}
27672
27831
  `);
27673
27832
  } catch (err) {
27674
27833
  process.stderr.write(
@@ -27707,7 +27866,7 @@ function endTurnIfUnterminated(dir, chatId, errMessage) {
27707
27866
  event: { type: "error", error: errMessage || "turn ended unexpectedly", chatId }
27708
27867
  };
27709
27868
  try {
27710
- fs56.appendFileSync(brainEventsFilePath(dir, chatId), `${JSON.stringify(rec)}
27869
+ fs57.appendFileSync(brainEventsFilePath(dir, chatId), `${JSON.stringify(rec)}
27711
27870
  `);
27712
27871
  } catch {
27713
27872
  }
@@ -29694,55 +29853,6 @@ async function brainTerminalAgent(options) {
29694
29853
 
29695
29854
  // src/servers/pool-serve.ts
29696
29855
  import { createServer as createServer5 } from "http";
29697
-
29698
- // src/pool/agency-loop-tick.ts
29699
- function normalizeRepositories(repositories) {
29700
- const unique = /* @__PURE__ */ new Set();
29701
- for (const raw of repositories) {
29702
- const repo = raw.trim().toLowerCase();
29703
- if (/^[^/\s]+\/[^/\s]+$/.test(repo)) unique.add(repo);
29704
- }
29705
- return [...unique].sort();
29706
- }
29707
- async function runAgencyLoopTick(deps) {
29708
- const repositories = normalizeRepositories(await deps.discover());
29709
- if (repositories.length === 0) {
29710
- deps.log("no consumer agencies discovered \u2014 nothing to tick");
29711
- return { discovered: 0, claimed: 0 };
29712
- }
29713
- deps.log(
29714
- `running scheduled fan-out for ${repositories.length} consumer agenc${repositories.length === 1 ? "y" : "ies"}`
29715
- );
29716
- const clock = deps.now ?? Date.now;
29717
- let claimed = 0;
29718
- for (const repository of repositories) {
29719
- const [owner, repo] = repository.split("/");
29720
- try {
29721
- const jobId = `sched-${owner}-${repo}-${clock()}`;
29722
- const result = await deps.claim(owner, repo, {
29723
- jobId,
29724
- repo: repository,
29725
- runRequest: {
29726
- requestId: jobId,
29727
- target: { type: "workflow", id: "scheduled-fanout" },
29728
- intent: "tick",
29729
- source: "schedule"
29730
- }
29731
- });
29732
- if (result.ok) {
29733
- claimed++;
29734
- deps.log(`[${repository}] scheduled fan-out claimed ${result.machineId}`);
29735
- } else {
29736
- deps.log(`[${repository}] scheduled fan-out skipped: ${result.reason ?? "runner unavailable"}`);
29737
- }
29738
- } catch (error) {
29739
- deps.log(`[${repository}] scheduled fan-out error: ${error instanceof Error ? error.message : String(error)}`);
29740
- }
29741
- }
29742
- return { discovered: repositories.length, claimed };
29743
- }
29744
-
29745
- // src/servers/pool-serve.ts
29746
29856
  init_keys();
29747
29857
 
29748
29858
  // src/pool/registry.ts
@@ -30378,30 +30488,6 @@ async function poolServe() {
30378
30488
  const tick = setInterval(() => {
30379
30489
  registry.resyncAll().catch((err) => log(`resync tick failed: ${err instanceof Error ? err.message : String(err)}`));
30380
30490
  }, refillMs);
30381
- const discoverAgencies = async () => {
30382
- if (!appCreds) return registry.activeRepos();
30383
- const repositories = await discoverAppRepositories(appCreds);
30384
- for (const access of repositories) repoTokens.set(access.repo.toLowerCase(), access.token);
30385
- return [.../* @__PURE__ */ new Set([...repositories.map((access) => access.repo), ...registry.activeRepos()])];
30386
- };
30387
- let agencyTickInFlight = null;
30388
- const runLoopTick = () => {
30389
- if (agencyTickInFlight) return agencyTickInFlight;
30390
- agencyTickInFlight = runAgencyLoopTick({
30391
- discover: discoverAgencies,
30392
- claim: (owner, repo, req) => registry.claim(owner, repo, req),
30393
- log
30394
- }).catch((err) => log(`agency Loop tick failed: ${err instanceof Error ? err.message : String(err)}`)).finally(() => {
30395
- agencyTickInFlight = null;
30396
- });
30397
- return agencyTickInFlight;
30398
- };
30399
- const loopTickEnabled = (process.env.POOL_LOOP_TICK ?? process.env.POOL_CAPABILITY_TICK ?? "1") !== "0";
30400
- const loopTickMs = envInt2(
30401
- process.env.POOL_LOOP_TICK_MS ? "POOL_LOOP_TICK_MS" : "POOL_CAPABILITY_TICK_MS",
30402
- 15 * 6e4
30403
- );
30404
- const loopTick = loopTickEnabled ? setInterval(() => void runLoopTick(), loopTickMs) : null;
30405
30491
  const server = createServer5(async (req, res) => {
30406
30492
  try {
30407
30493
  if (!req.method || !req.url) return sendJson2(res, 400, { error: "bad request" });
@@ -30455,11 +30541,9 @@ async function poolServe() {
30455
30541
  resolve24();
30456
30542
  });
30457
30543
  });
30458
- if (loopTickEnabled) void runLoopTick();
30459
30544
  const shutdown = (signal) => {
30460
30545
  log(`${signal} \u2014 shutting down`);
30461
30546
  clearInterval(tick);
30462
- if (loopTick) clearInterval(loopTick);
30463
30547
  server.close(() => process.exit(0));
30464
30548
  };
30465
30549
  process.once("SIGINT", () => shutdown("SIGINT"));
@@ -30471,7 +30555,7 @@ async function poolServe() {
30471
30555
 
30472
30556
  // src/servers/runner-serve.ts
30473
30557
  import { spawn as spawn10 } from "child_process";
30474
- import * as fs57 from "fs";
30558
+ import * as fs58 from "fs";
30475
30559
  import { createServer as createServer6 } from "http";
30476
30560
  var DEFAULT_PORT2 = 8080;
30477
30561
  var DEFAULT_WORKDIR = "/workspace/repo";
@@ -30547,8 +30631,8 @@ async function defaultRunJob(job) {
30547
30631
  const workdir = process.env.RUNNER_WORKDIR ?? DEFAULT_WORKDIR;
30548
30632
  const branch = job.ref ?? "main";
30549
30633
  const authUrl = `https://x-access-token:${job.githubToken}@github.com/${job.repo}.git`;
30550
- fs57.rmSync(workdir, { recursive: true, force: true });
30551
- fs57.mkdirSync(workdir, { recursive: true });
30634
+ fs58.rmSync(workdir, { recursive: true, force: true });
30635
+ fs58.mkdirSync(workdir, { recursive: true });
30552
30636
  const allSecrets = typeof job.allSecrets === "string" ? job.allSecrets : JSON.stringify(job.allSecrets ?? {});
30553
30637
  const target = job.runRequest.target;
30554
30638
  const interactive = target.type === "chat";
@@ -31136,7 +31220,7 @@ async function main(argv = process.argv.slice(2)) {
31136
31220
  const args = parseArgs(argv);
31137
31221
  const cwdFlag = argv.indexOf("--cwd");
31138
31222
  const definitionCwd = cwdFlag >= 0 && argv[cwdFlag + 1] ? argv[cwdFlag + 1] : process.cwd();
31139
- const shouldHydrate = !(args.command === "server" && args.serverName === "brain-serve") && !hasExplicitDefinitionsRoot(definitionCwd) && (Boolean(process.env.CONVEX_URL?.trim()) || process.env.GITHUB_ACTIONS === "true" && Boolean(process.env.GITHUB_EVENT_NAME));
31223
+ const shouldHydrate = args.command !== "help" && args.command !== "version" && !(args.command === "server" && args.serverName === "brain-serve") && !hasExplicitDefinitionsRoot(definitionCwd) && (Boolean(process.env.CONVEX_URL?.trim()) || process.env.GITHUB_ACTIONS === "true" && Boolean(process.env.GITHUB_EVENT_NAME));
31140
31224
  if (shouldHydrate) {
31141
31225
  try {
31142
31226
  await hydrateDefinitionsFromEnv(definitionCwd);
@@ -0,0 +1,9 @@
1
+ # Live Agent
2
+
3
+ ## Purpose
4
+
5
+ Run one continuation cycle for a selected persistent Agent.
6
+
7
+ ## Instructions
8
+
9
+ Use the `live-agent` implementation to load the Agent, its assigned Intent and resources, then persist the next AgentState.
@@ -0,0 +1,32 @@
1
+ {
2
+ "id": "live-agent",
3
+ "action": "live-agent",
4
+ "purpose": "Run one continuation cycle for a selected persistent Agent.",
5
+ "inputSchema": {
6
+ "type": "object",
7
+ "properties": {
8
+ "agent": {
9
+ "type": "string",
10
+ "description": "Selected Agent slug."
11
+ },
12
+ "intent": {
13
+ "type": "string",
14
+ "description": "Primary Intent slug recorded by the Loop."
15
+ }
16
+ },
17
+ "required": ["agent"],
18
+ "additionalProperties": false
19
+ },
20
+ "outputSchema": {
21
+ "type": "object",
22
+ "properties": {
23
+ "cursor": { "type": "string" },
24
+ "data": { "type": "object", "additionalProperties": true }
25
+ },
26
+ "additionalProperties": true
27
+ },
28
+ "effects": ["agent-state"],
29
+ "permissions": [],
30
+ "success": "The Agent completes one cycle and persists its continuation state.",
31
+ "failure": "The Agent cycle fails or its continuation state cannot be persisted."
32
+ }
@@ -0,0 +1,30 @@
1
+ {
2
+ "name": "live-agent",
3
+ "role": "primitive",
4
+ "kind": "oneshot",
5
+ "inputs": [
6
+ { "name": "agent", "flag": "--agent", "type": "string", "required": true, "describe": "Live Agent slug." },
7
+ { "name": "intent", "flag": "--intent", "type": "string", "required": false, "describe": "Primary Intent slug recorded by the Loop." }
8
+ ],
9
+ "claudeCode": {
10
+ "model": "inherit",
11
+ "permissionMode": "default",
12
+ "maxTurns": 100,
13
+ "maxThinkingTokens": null,
14
+ "systemPromptAppend": null,
15
+ "enableSubmitTool": true,
16
+ "tools": ["mcp__kody-capability__start_capability", "mcp__kody-submit__submit_state"],
17
+ "hooks": [], "skills": [], "commands": [], "subagents": [], "plugins": [], "mcpServers": []
18
+ },
19
+ "cliTools": [],
20
+ "scripts": {
21
+ "preflight": [{ "script": "loadLiveAgent" }, { "script": "composePrompt" }],
22
+ "postflight": [
23
+ { "script": "parseJobStateFromAgentResult", "with": { "fenceLabel": "kody-agent-next-state" } },
24
+ { "script": "saveLiveAgentState" },
25
+ { "script": "appendCompanyActivity" }
26
+ ]
27
+ },
28
+ "inputArtifacts": [],
29
+ "outputArtifacts": []
30
+ }
@@ -0,0 +1,38 @@
1
+ You are a persistent Kody Agent completing one scheduled cycle.
2
+
3
+ ## Identity
4
+
5
+ {{agentIdentity}}
6
+
7
+ ## Primary Intent
8
+
9
+ {{liveAgentIntent}}
10
+
11
+ ## Policies
12
+
13
+ {{liveAgentPolicies}}
14
+
15
+ ## Constraints
16
+
17
+ {{liveAgentConstraints}}
18
+
19
+ ## Context
20
+
21
+ {{liveAgentContext}}
22
+
23
+ ## Assigned Capabilities
24
+
25
+ {{liveAgentCapabilities}}
26
+
27
+ ## Previous continuation
28
+
29
+ ```json
30
+ {{jobStateJson}}
31
+ ```
32
+
33
+ Inspect current conditions, decide the best next action toward the Intent, and use only assigned capabilities. Do not create work merely to appear active. If waiting is correct, record what is being awaited.
34
+
35
+ As your final action, call `submit_state` exactly once with:
36
+ - `cursor`: the next continuation cursor;
37
+ - `data`: compact durable continuation data;
38
+ - `done`: always `false` for a live Agent.
File without changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kody-ade/kody-engine",
3
- "version": "0.4.607",
3
+ "version": "0.4.611",
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,30 @@
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
+ "verify:live-release": "tsx scripts/live-release-gate.ts",
29
+ "test:runtime-services": "node --test \"tests/runtime-services/*.test.mjs\"",
30
+ "test:all": "vitest run tests --no-coverage",
31
+ "typecheck": "tsc --noEmit",
32
+ "lint": "biome check",
33
+ "lint:fix": "biome check --write",
34
+ "format": "biome format --write",
35
+ "verify:package": "node scripts/verify-package-tarball.cjs",
36
+ "brain:publish": "docker buildx build --platform linux/amd64 -f runner/Dockerfile.brain --build-arg KODY_ENGINE_REF=$(git rev-parse HEAD) -t ghcr.io/${KODY_BRAIN_GHCR_OWNER:-aharonyaircohen}/kody-brain:latest --push runner",
37
+ "prepublishOnly": "pnpm typecheck && pnpm test:runtime-services && pnpm build && pnpm verify:package"
38
+ },
15
39
  "dependencies": {
16
40
  "@actions/cache": "^6.0.0",
17
41
  "@anthropic-ai/claude-agent-sdk": "0.2.119",
@@ -38,28 +62,5 @@
38
62
  "url": "git+https://github.com/aharonyaircohen/kody-engine.git"
39
63
  },
40
64
  "homepage": "https://github.com/aharonyaircohen/kody-engine",
41
- "bugs": "https://github.com/aharonyaircohen/kody-engine/issues",
42
- "scripts": {
43
- "kody:run": "tsx bin/kody.ts",
44
- "serve": "tsx bin/kody.ts serve",
45
- "serve:vscode": "tsx bin/kody.ts serve vscode",
46
- "serve:claude": "tsx bin/kody.ts serve claude",
47
- "clean:dist": "node scripts/clean-dist.cjs",
48
- "build": "pnpm clean:dist && tsup && node scripts/copy-assets.cjs",
49
- "check:modularity": "tsx scripts/check-script-modularity.ts",
50
- "pretest": "pnpm check:modularity",
51
- "test": "vitest run tests/unit tests/int --coverage",
52
- "posttest": "tsx scripts/check-coverage-floor.ts",
53
- "test:smoke": "vitest run tests/smoke --no-coverage",
54
- "test:e2e": "vitest run tests/e2e --no-coverage",
55
- "verify:live-release": "tsx scripts/live-release-gate.ts",
56
- "test:runtime-services": "node --test \"tests/runtime-services/*.test.mjs\"",
57
- "test:all": "vitest run tests --no-coverage",
58
- "typecheck": "tsc --noEmit",
59
- "lint": "biome check",
60
- "lint:fix": "biome check --write",
61
- "format": "biome format --write",
62
- "verify:package": "node scripts/verify-package-tarball.cjs",
63
- "brain:publish": "docker buildx build --platform linux/amd64 -f runner/Dockerfile.brain --build-arg KODY_ENGINE_REF=$(git rev-parse HEAD) -t ghcr.io/${KODY_BRAIN_GHCR_OWNER:-aharonyaircohen}/kody-brain:latest --push runner"
64
- }
65
- }
65
+ "bugs": "https://github.com/aharonyaircohen/kody-engine/issues"
66
+ }
@@ -50,8 +50,6 @@ on:
50
50
  types: [created]
51
51
  pull_request:
52
52
  types: [opened, synchronize, closed]
53
- schedule:
54
- - cron: '7/15 * * * *'
55
53
 
56
54
  jobs:
57
55
  run: