@kody-ade/kody-engine 0.4.275 → 0.4.277

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 +324 -48
  2. package/package.json +1 -1
package/dist/bin/kody.js CHANGED
@@ -15,7 +15,7 @@ var init_package = __esm({
15
15
  "package.json"() {
16
16
  package_default = {
17
17
  name: "@kody-ade/kody-engine",
18
- version: "0.4.275",
18
+ version: "0.4.277",
19
19
  description: "kody \u2014 autonomous development engine. Single-session Claude Code agent behind a generic executor + declarative executable profiles.",
20
20
  license: "MIT",
21
21
  type: "module",
@@ -352,12 +352,25 @@ var init_issue = __esm({
352
352
  }
353
353
  });
354
354
 
355
+ // src/stateBranch.ts
356
+ var STATE_BRANCH;
357
+ var init_stateBranch = __esm({
358
+ "src/stateBranch.ts"() {
359
+ "use strict";
360
+ STATE_BRANCH = "kody-state";
361
+ }
362
+ });
363
+
355
364
  // src/stateRepo.ts
356
365
  import path2 from "path";
357
366
  function is404(err) {
358
367
  const msg = err instanceof Error ? err.message : String(err);
359
368
  return /HTTP 404/i.test(msg) || /Not Found/i.test(msg);
360
369
  }
370
+ function isAlreadyExists(err) {
371
+ const msg = err instanceof Error ? err.message : String(err);
372
+ return /HTTP 422/i.test(msg) || /Reference already exists/i.test(msg);
373
+ }
361
374
  function parseStateRepoSlug(slug2, field = "stateRepo") {
362
375
  const value = slug2.trim();
363
376
  let repoPath = value;
@@ -428,11 +441,37 @@ function apiPath(config, targetPath) {
428
441
  const parsed = parseStateRepo(config);
429
442
  return `/repos/${parsed.owner}/${parsed.repo}/contents/${targetPath}`;
430
443
  }
444
+ function branchApiPath(config, targetPath) {
445
+ return `${apiPath(config, targetPath)}?ref=${encodeURIComponent(STATE_BRANCH)}`;
446
+ }
447
+ function ensureStateBranch(config, cwd) {
448
+ const parsed = parseStateRepo(config);
449
+ try {
450
+ gh(["api", `/repos/${parsed.owner}/${parsed.repo}/git/ref/heads/${STATE_BRANCH}`], { cwd });
451
+ return;
452
+ } catch (err) {
453
+ if (!is404(err)) throw err;
454
+ }
455
+ const repoRaw = gh(["api", `/repos/${parsed.owner}/${parsed.repo}`], { cwd });
456
+ const defaultBranch = String(JSON.parse(repoRaw).default_branch ?? "").trim();
457
+ if (!defaultBranch) throw new Error(`stateRepo: ${parsed.owner}/${parsed.repo} default branch missing`);
458
+ const refRaw = gh(["api", `/repos/${parsed.owner}/${parsed.repo}/git/ref/heads/${defaultBranch}`], { cwd });
459
+ const sha = String(JSON.parse(refRaw).object?.sha ?? "").trim();
460
+ if (!sha) throw new Error(`stateRepo: ${parsed.owner}/${parsed.repo} ${defaultBranch} ref sha missing`);
461
+ try {
462
+ gh(["api", "--method", "POST", `/repos/${parsed.owner}/${parsed.repo}/git/refs`, "--input", "-"], {
463
+ cwd,
464
+ input: JSON.stringify({ ref: `refs/heads/${STATE_BRANCH}`, sha })
465
+ });
466
+ } catch (err) {
467
+ if (!isAlreadyExists(err)) throw err;
468
+ }
469
+ }
431
470
  function readStateText(config, cwd, filePath) {
432
471
  const targetPath = stateRepoPath(config, filePath);
433
472
  let raw = "";
434
473
  try {
435
- raw = gh(["api", apiPath(config, targetPath)], { cwd });
474
+ raw = gh(["api", branchApiPath(config, targetPath)], { cwd });
436
475
  } catch (err) {
437
476
  if (is404(err)) return null;
438
477
  throw err;
@@ -457,9 +496,11 @@ function readStateText(config, cwd, filePath) {
457
496
  }
458
497
  function writeStateText(config, cwd, filePath, content, message, sha) {
459
498
  const targetPath = stateRepoPath(config, filePath);
499
+ ensureStateBranch(config, cwd);
460
500
  const payload = {
461
501
  message,
462
- content: Buffer.from(content, "utf-8").toString("base64")
502
+ content: Buffer.from(content, "utf-8").toString("base64"),
503
+ branch: STATE_BRANCH
463
504
  };
464
505
  if (sha) payload.sha = sha;
465
506
  gh(["api", "--method", "PUT", apiPath(config, targetPath), "--input", "-"], {
@@ -490,7 +531,7 @@ function listStateDirectory(config, cwd, dirPath) {
490
531
  const targetPath = stateRepoPath(config, dirPath);
491
532
  let raw = "";
492
533
  try {
493
- raw = gh(["api", apiPath(config, targetPath)], { cwd });
534
+ raw = gh(["api", branchApiPath(config, targetPath)], { cwd });
494
535
  } catch (err) {
495
536
  if (is404(err)) return [];
496
537
  throw err;
@@ -502,6 +543,7 @@ var init_stateRepo = __esm({
502
543
  "src/stateRepo.ts"() {
503
544
  "use strict";
504
545
  init_issue();
546
+ init_stateBranch();
505
547
  }
506
548
  });
507
549
 
@@ -19579,6 +19621,68 @@ init_job();
19579
19621
  init_registry();
19580
19622
  init_runtimePaths();
19581
19623
  init_stateWorkspace();
19624
+
19625
+ // src/run-request.ts
19626
+ var RUN_REQUEST_ENV = "KODY_RUN_REQUEST_JSON";
19627
+ var INTENTS = /* @__PURE__ */ new Set(["continue", "manage", "run", "tick"]);
19628
+ var SOURCES = /* @__PURE__ */ new Set(["dashboard", "github", "schedule"]);
19629
+ var TARGET_TYPES = /* @__PURE__ */ new Set(["chat", "goal", "issue", "workflow"]);
19630
+ function isRecord(value) {
19631
+ return typeof value === "object" && value !== null && !Array.isArray(value);
19632
+ }
19633
+ function readString(value) {
19634
+ return typeof value === "string" ? value.trim() : "";
19635
+ }
19636
+ function parseTarget(input) {
19637
+ if (!isRecord(input)) return { error: "runRequest.target must be an object" };
19638
+ const type = readString(input.type);
19639
+ if (!TARGET_TYPES.has(type)) return { error: "runRequest.target.type is invalid" };
19640
+ if (type === "issue") {
19641
+ const id2 = Number(input.id);
19642
+ if (!Number.isInteger(id2) || id2 <= 0) return { error: "runRequest.target.id must be a positive issue number" };
19643
+ return { target: { type, id: id2 } };
19644
+ }
19645
+ const id = readString(input.id);
19646
+ if (!id) return { error: "runRequest.target.id is required" };
19647
+ return { target: { type, id } };
19648
+ }
19649
+ function parseRunRequest(input) {
19650
+ let body = input;
19651
+ if (typeof input === "string") {
19652
+ const raw = input.trim();
19653
+ if (!raw) return { error: `${RUN_REQUEST_ENV} is empty` };
19654
+ try {
19655
+ body = JSON.parse(raw);
19656
+ } catch {
19657
+ return { error: `${RUN_REQUEST_ENV} must be valid JSON` };
19658
+ }
19659
+ }
19660
+ if (!isRecord(body)) return { error: "runRequest must be an object" };
19661
+ const parsedTarget = parseTarget(body.target);
19662
+ if ("error" in parsedTarget) return parsedTarget;
19663
+ const intent = readString(body.intent);
19664
+ if (!INTENTS.has(intent)) return { error: "runRequest.intent is invalid" };
19665
+ const source = readString(body.source);
19666
+ if (!SOURCES.has(source)) return { error: "runRequest.source is invalid" };
19667
+ if (body.input !== void 0 && !isRecord(body.input)) {
19668
+ return { error: "runRequest.input must be an object when provided" };
19669
+ }
19670
+ return {
19671
+ request: {
19672
+ target: parsedTarget.target,
19673
+ intent,
19674
+ source,
19675
+ ...body.input !== void 0 ? { input: body.input } : {}
19676
+ }
19677
+ };
19678
+ }
19679
+ function readRunRequestFromEnv(env = process.env) {
19680
+ const raw = env[RUN_REQUEST_ENV];
19681
+ if (raw == null || raw.trim() === "") return null;
19682
+ return parseRunRequest(raw);
19683
+ }
19684
+
19685
+ // src/kody-cli.ts
19582
19686
  var CI_HELP = `kody ci \u2014 minimal-YAML autonomous engineer (CI preflight + run)
19583
19687
 
19584
19688
  Usage:
@@ -19637,6 +19741,30 @@ function parseCiArgs(argv) {
19637
19741
  }
19638
19742
  return result;
19639
19743
  }
19744
+ function routeRunRequest(request) {
19745
+ const { target, intent } = request;
19746
+ if (target.type === "chat" || target.type === "issue") return { kind: "ignore" };
19747
+ if (target.type === "goal") {
19748
+ if (intent !== "manage" && intent !== "run" && intent !== "tick") {
19749
+ return { kind: "error", error: `goal target does not support intent '${intent}'` };
19750
+ }
19751
+ return {
19752
+ kind: "action",
19753
+ action: "goal-manager",
19754
+ cliArgs: { goal: target.id }
19755
+ };
19756
+ }
19757
+ if (target.type === "workflow") {
19758
+ if (target.id === "scheduled-fanout") {
19759
+ return { kind: "fanout", force: intent === "run" };
19760
+ }
19761
+ if (intent !== "run" && intent !== "tick") {
19762
+ return { kind: "error", error: `workflow target does not support intent '${intent}'` };
19763
+ }
19764
+ return { kind: "action", action: target.id, cliArgs: {} };
19765
+ }
19766
+ return { kind: "error", error: "unsupported run request target" };
19767
+ }
19640
19768
  function unpackAllSecrets(env = process.env) {
19641
19769
  const raw = env.ALL_SECRETS;
19642
19770
  if (!raw) return 0;
@@ -19848,15 +19976,38 @@ async function runCi(argv) {
19848
19976
  let manualWorkflowDispatch = false;
19849
19977
  let forceRunAction = null;
19850
19978
  let forceRunCliArgs = {};
19979
+ let runRequestFanOut = false;
19980
+ let runRequestFanOutForce = false;
19981
+ const parsedRunRequest = readRunRequestFromEnv();
19982
+ if (parsedRunRequest && "error" in parsedRunRequest) {
19983
+ process.stderr.write(`[kody] ${parsedRunRequest.error}
19984
+ `);
19985
+ return 64;
19986
+ }
19987
+ if (!args.issueNumber && !autoFallback && parsedRunRequest && "request" in parsedRunRequest) {
19988
+ const route = routeRunRequest(parsedRunRequest.request);
19989
+ if (route.kind === "error") {
19990
+ process.stderr.write(`[kody] ${route.error}
19991
+ `);
19992
+ return 64;
19993
+ }
19994
+ if (route.kind === "fanout") {
19995
+ runRequestFanOut = true;
19996
+ runRequestFanOutForce = route.force;
19997
+ } else if (route.kind === "action") {
19998
+ forceRunAction = route.action;
19999
+ forceRunCliArgs = route.cliArgs;
20000
+ }
20001
+ }
19851
20002
  const envForceAction = (process.env.KODY_FORCE_ACTION ?? "").trim();
19852
20003
  const envForceMessage = (process.env.KODY_FORCE_MESSAGE ?? "").trim();
19853
- if (!args.issueNumber && !autoFallback && envForceAction) {
20004
+ if (!args.issueNumber && !autoFallback && !forceRunAction && !runRequestFanOut && envForceAction) {
19854
20005
  forceRunAction = envForceAction;
19855
20006
  if (envForceAction === "goal-manager" && envForceMessage) {
19856
20007
  forceRunCliArgs = { goal: envForceMessage };
19857
20008
  }
19858
20009
  }
19859
- if (!args.issueNumber && !autoFallback && !forceRunAction && eventName === "workflow_dispatch" && dispatchEventPath && fs46.existsSync(dispatchEventPath)) {
20010
+ if (!args.issueNumber && !autoFallback && !forceRunAction && !runRequestFanOut && eventName === "workflow_dispatch" && dispatchEventPath && fs46.existsSync(dispatchEventPath)) {
19860
20011
  try {
19861
20012
  const evt = JSON.parse(fs46.readFileSync(dispatchEventPath, "utf-8"));
19862
20013
  const issueInput = parseInt(String(evt?.inputs?.issue_number ?? ""), 10);
@@ -19948,6 +20099,9 @@ async function runCi(argv) {
19948
20099
  const ec = result.exitCode;
19949
20100
  return ec === 0 || ec === 1 || ec === 2 ? ec : 99;
19950
20101
  }
20102
+ if (!args.issueNumber && !autoFallback && runRequestFanOut) {
20103
+ return runScheduledFanOut(cwd, args, { force: runRequestFanOutForce });
20104
+ }
19951
20105
  if (!args.issueNumber && !autoFallback && (eventName === "schedule" || manualWorkflowDispatch)) {
19952
20106
  return runScheduledFanOut(cwd, args, { force: manualWorkflowDispatch });
19953
20107
  }
@@ -21571,6 +21725,10 @@ function parseChatArgs(argv, env = process.env) {
21571
21725
  else if (arg?.startsWith("--")) result.errors.push(`unknown arg: ${arg}`);
21572
21726
  else if (arg) result.errors.push(`unexpected positional: ${arg}`);
21573
21727
  }
21728
+ const runRequest = readRunRequestFromEnv(env);
21729
+ if (runRequest && "error" in runRequest) result.errors.push(runRequest.error);
21730
+ const chatTarget = runRequest && "request" in runRequest && runRequest.request.target.type === "chat" ? runRequest.request.target.id : void 0;
21731
+ result.sessionId = result.sessionId ?? chatTarget;
21574
21732
  result.sessionId = result.sessionId ?? env.SESSION_ID ?? void 0;
21575
21733
  result.initMessage = result.initMessage ?? env.INIT_MESSAGE ?? void 0;
21576
21734
  result.model = result.model ?? env.MODEL ?? void 0;
@@ -21764,7 +21922,11 @@ async function runCapabilityFallbackTick(deps) {
21764
21922
  const res = await deps.claim(owner, repo, {
21765
21923
  jobId: `sched-${owner}-${repo}-${clock()}`,
21766
21924
  repo: tag,
21767
- mode: "scheduled"
21925
+ runRequest: {
21926
+ target: { type: "workflow", id: "scheduled-fanout" },
21927
+ intent: "tick",
21928
+ source: "schedule"
21929
+ }
21768
21930
  });
21769
21931
  if (res.ok) {
21770
21932
  claimed++;
@@ -22339,7 +22501,7 @@ var PoolRegistry = class {
22339
22501
  jobId: req.jobId,
22340
22502
  repo: `${owner}/${repo}`,
22341
22503
  githubToken: this.cfg.githubToken,
22342
- mode: req.mode ?? "issue",
22504
+ runRequest: req.runRequest,
22343
22505
  issueNumber: req.issueNumber,
22344
22506
  sessionId: req.sessionId,
22345
22507
  idleExitMs: req.idleExitMs,
@@ -22425,24 +22587,78 @@ function parseClaimRequest(body) {
22425
22587
  const repo = typeof b.repo === "string" ? b.repo.trim() : "";
22426
22588
  if (!/^[^/\s]+\/[^/\s]+$/.test(repo)) return { error: "repo must be 'owner/name'" };
22427
22589
  const mode = b.mode === "interactive" ? "interactive" : b.mode === "scheduled" ? "scheduled" : "issue";
22428
- const req = { jobId, repo, mode };
22429
- if (mode === "issue") {
22430
- const issueNumber = Number(b.issueNumber);
22431
- if (!Number.isInteger(issueNumber) || issueNumber <= 0) return { error: "issueNumber required for issue mode" };
22432
- req.issueNumber = issueNumber;
22433
- } else if (mode === "interactive") {
22434
- const sessionId = typeof b.sessionId === "string" ? b.sessionId.trim() : "";
22435
- if (!sessionId) return { error: "sessionId required for interactive mode" };
22436
- req.sessionId = sessionId;
22590
+ const action = typeof b.action === "string" && b.action.trim() ? b.action.trim() : void 0;
22591
+ const message = typeof b.message === "string" && b.message.trim() ? b.message.trim() : void 0;
22592
+ const runRequest = b.runRequest !== void 0 ? parseRunRequest(b.runRequest) : synthesizeLegacyClaimRequest({
22593
+ mode,
22594
+ issueNumber: b.issueNumber,
22595
+ sessionId: b.sessionId,
22596
+ action,
22597
+ message
22598
+ });
22599
+ if ("error" in runRequest) return { error: runRequest.error };
22600
+ const req = { jobId, repo, runRequest: runRequest.request };
22601
+ if (req.runRequest.target.type === "issue") {
22602
+ req.issueNumber = req.runRequest.target.id;
22603
+ } else if (req.runRequest.target.type === "chat") {
22604
+ req.sessionId = req.runRequest.target.id;
22437
22605
  if (Number.isFinite(Number(b.idleExitMs))) req.idleExitMs = Number(b.idleExitMs);
22438
22606
  if (Number.isFinite(Number(b.hardCapMs))) req.hardCapMs = Number(b.hardCapMs);
22439
22607
  }
22440
22608
  if (typeof b.ref === "string" && b.ref.trim()) req.ref = b.ref.trim();
22441
22609
  if (typeof b.model === "string" && b.model.trim()) req.model = b.model.trim();
22442
- if (typeof b.sessionId === "string" && b.sessionId.trim()) req.sessionId = b.sessionId.trim();
22443
22610
  if (typeof b.dashboardUrl === "string" && b.dashboardUrl.trim()) req.dashboardUrl = b.dashboardUrl.trim();
22444
22611
  return { req };
22445
22612
  }
22613
+ function synthesizeLegacyClaimRequest(input) {
22614
+ if (input.mode === "issue") {
22615
+ const issueNumber = Number(input.issueNumber);
22616
+ if (!Number.isInteger(issueNumber) || issueNumber <= 0) return { error: "issueNumber required for issue mode" };
22617
+ return {
22618
+ request: {
22619
+ target: { type: "issue", id: issueNumber },
22620
+ intent: "run",
22621
+ source: "dashboard"
22622
+ }
22623
+ };
22624
+ }
22625
+ if (input.mode === "interactive") {
22626
+ const sessionId = typeof input.sessionId === "string" ? input.sessionId.trim() : "";
22627
+ if (!sessionId) return { error: "sessionId required for interactive mode" };
22628
+ return {
22629
+ request: {
22630
+ target: { type: "chat", id: sessionId },
22631
+ intent: "continue",
22632
+ source: "dashboard"
22633
+ }
22634
+ };
22635
+ }
22636
+ if (input.action === "goal-manager" && input.message) {
22637
+ return {
22638
+ request: {
22639
+ target: { type: "goal", id: input.message },
22640
+ intent: "manage",
22641
+ source: "dashboard"
22642
+ }
22643
+ };
22644
+ }
22645
+ if (input.action) {
22646
+ return {
22647
+ request: {
22648
+ target: { type: "workflow", id: input.action },
22649
+ intent: "run",
22650
+ source: "dashboard"
22651
+ }
22652
+ };
22653
+ }
22654
+ return {
22655
+ request: {
22656
+ target: { type: "workflow", id: "scheduled-fanout" },
22657
+ intent: "tick",
22658
+ source: "schedule"
22659
+ }
22660
+ };
22661
+ }
22446
22662
  async function poolServe() {
22447
22663
  const masterRaw = process.env.KODY_MASTER_KEY?.trim();
22448
22664
  if (!masterRaw) throw new Error("KODY_MASTER_KEY required for pool-serve");
@@ -22610,33 +22826,85 @@ function parseJob(body) {
22610
22826
  if (!/^[^/\s]+\/[^/\s]+$/.test(repo)) return { error: "repo must be 'owner/name'" };
22611
22827
  const githubToken = typeof b.githubToken === "string" ? b.githubToken.trim() : "";
22612
22828
  if (!githubToken) return { error: "githubToken required" };
22829
+ const action = typeof b.action === "string" && b.action.trim() ? b.action.trim() : void 0;
22830
+ const message = typeof b.message === "string" && b.message.trim() ? b.message.trim() : void 0;
22613
22831
  const mode = b.mode === "interactive" ? "interactive" : b.mode === "scheduled" ? "scheduled" : "issue";
22614
- const job = { jobId, repo, githubToken, mode };
22615
- if (mode === "issue") {
22616
- const issueNumber = Number(b.issueNumber);
22617
- if (!Number.isInteger(issueNumber) || issueNumber <= 0) {
22618
- return { error: "issueNumber (positive integer) required for issue mode" };
22619
- }
22620
- job.issueNumber = issueNumber;
22621
- } else if (mode === "interactive") {
22622
- const sessionId = typeof b.sessionId === "string" ? b.sessionId.trim() : "";
22623
- if (!sessionId) return { error: "sessionId required for interactive mode" };
22624
- job.sessionId = sessionId;
22832
+ const runRequest = b.runRequest !== void 0 ? parseRunRequest(b.runRequest) : synthesizeLegacyRunRequest({
22833
+ mode,
22834
+ issueNumber: b.issueNumber,
22835
+ sessionId: b.sessionId,
22836
+ action,
22837
+ message
22838
+ });
22839
+ if ("error" in runRequest) return { error: runRequest.error };
22840
+ const job = { jobId, repo, githubToken, runRequest: runRequest.request };
22841
+ if (job.runRequest.target.type === "issue") {
22842
+ job.issueNumber = job.runRequest.target.id;
22843
+ } else if (job.runRequest.target.type === "chat") {
22844
+ job.sessionId = job.runRequest.target.id;
22625
22845
  if (Number.isFinite(Number(b.idleExitMs))) job.idleExitMs = Number(b.idleExitMs);
22626
22846
  if (Number.isFinite(Number(b.hardCapMs))) job.hardCapMs = Number(b.hardCapMs);
22627
22847
  }
22628
22848
  if (typeof b.ref === "string" && b.ref.trim()) job.ref = b.ref.trim();
22629
22849
  if (typeof b.model === "string" && b.model.trim()) job.model = b.model.trim();
22630
22850
  if (typeof b.reasoningEffort === "string" && b.reasoningEffort.trim()) job.reasoningEffort = b.reasoningEffort.trim();
22631
- if (typeof b.action === "string" && b.action.trim()) job.action = b.action.trim();
22632
- if (typeof b.message === "string" && b.message.trim()) job.message = b.message.trim();
22633
- if (typeof b.sessionId === "string" && b.sessionId.trim()) job.sessionId = b.sessionId.trim();
22634
22851
  if (typeof b.dashboardUrl === "string" && b.dashboardUrl.trim()) job.dashboardUrl = b.dashboardUrl.trim();
22635
22852
  if (b.allSecrets && (typeof b.allSecrets === "object" || typeof b.allSecrets === "string")) {
22636
22853
  job.allSecrets = b.allSecrets;
22637
22854
  }
22638
22855
  return { job };
22639
22856
  }
22857
+ function synthesizeLegacyRunRequest(input) {
22858
+ if (input.mode === "issue") {
22859
+ const issueNumber = Number(input.issueNumber);
22860
+ if (!Number.isInteger(issueNumber) || issueNumber <= 0) {
22861
+ return { error: "issueNumber (positive integer) required for issue mode" };
22862
+ }
22863
+ return {
22864
+ request: {
22865
+ target: { type: "issue", id: issueNumber },
22866
+ intent: "run",
22867
+ source: "dashboard"
22868
+ }
22869
+ };
22870
+ }
22871
+ if (input.mode === "interactive") {
22872
+ const sessionId = typeof input.sessionId === "string" ? input.sessionId.trim() : "";
22873
+ if (!sessionId) return { error: "sessionId required for interactive mode" };
22874
+ return {
22875
+ request: {
22876
+ target: { type: "chat", id: sessionId },
22877
+ intent: "continue",
22878
+ source: "dashboard"
22879
+ }
22880
+ };
22881
+ }
22882
+ if (input.action === "goal-manager" && input.message) {
22883
+ return {
22884
+ request: {
22885
+ target: { type: "goal", id: input.message },
22886
+ intent: "manage",
22887
+ source: "dashboard"
22888
+ }
22889
+ };
22890
+ }
22891
+ if (input.action) {
22892
+ return {
22893
+ request: {
22894
+ target: { type: "workflow", id: input.action },
22895
+ intent: "run",
22896
+ source: "dashboard"
22897
+ }
22898
+ };
22899
+ }
22900
+ return {
22901
+ request: {
22902
+ target: { type: "workflow", id: "scheduled-fanout" },
22903
+ intent: "tick",
22904
+ source: "schedule"
22905
+ }
22906
+ };
22907
+ }
22640
22908
  async function defaultRunJob(job) {
22641
22909
  const workdir = process.env.RUNNER_WORKDIR ?? DEFAULT_WORKDIR;
22642
22910
  const branch = job.ref ?? "main";
@@ -22644,20 +22912,17 @@ async function defaultRunJob(job) {
22644
22912
  fs51.rmSync(workdir, { recursive: true, force: true });
22645
22913
  fs51.mkdirSync(workdir, { recursive: true });
22646
22914
  const allSecrets = typeof job.allSecrets === "string" ? job.allSecrets : JSON.stringify(job.allSecrets ?? {});
22647
- const interactive = job.mode === "interactive";
22648
- const scheduled = job.mode === "scheduled";
22649
- const runMode = interactive ? "interactive" : scheduled ? "scheduled" : "issue";
22915
+ const target = job.runRequest.target;
22916
+ const interactive = target.type === "chat";
22917
+ const scheduled = target.type === "goal" || target.type === "workflow";
22918
+ const issueNumber = target.type === "issue" ? target.id : void 0;
22919
+ const sessionId = target.type === "chat" ? target.id : void 0;
22650
22920
  const childEnv = {
22651
22921
  ...process.env,
22652
22922
  REPO: job.repo,
22653
22923
  REF: branch,
22654
22924
  GITHUB_TOKEN: job.githubToken,
22655
- KODY_RUN_MODE: runMode,
22656
- // Scheduled mode drives the engine down the same path GitHub Actions' cron
22657
- // takes (runScheduledFanOut → due capabilities/goals). Bare `kody` routes on this.
22658
- ...scheduled ? { GITHUB_EVENT_NAME: "schedule" } : {},
22659
- ...job.action ? { KODY_FORCE_ACTION: job.action } : {},
22660
- ...job.message ? { KODY_FORCE_MESSAGE: job.message } : {},
22925
+ [RUN_REQUEST_ENV]: JSON.stringify(job.runRequest),
22661
22926
  // GITHUB_REPOSITORY + GH_TOKEN are normally injected by GitHub Actions.
22662
22927
  // The engine's interactive mode needs GITHUB_REPOSITORY to resolve the
22663
22928
  // configured state repo and persist chat.ready / events (the durable signal
@@ -22665,11 +22930,9 @@ async function defaultRunJob(job) {
22665
22930
  // and the session never appears "ready". GH_TOKEN auths the `gh` CLI.
22666
22931
  GITHUB_REPOSITORY: job.repo,
22667
22932
  GH_TOKEN: job.githubToken,
22668
- // Issue mode carries ISSUE_NUMBER. Interactive mode leaves it empty and
22669
- // sets SESSION_ID so the engine boots a chat session.
22670
- ISSUE_NUMBER: interactive || scheduled ? "" : String(job.issueNumber),
22933
+ ISSUE_NUMBER: issueNumber ? String(issueNumber) : "",
22671
22934
  ALL_SECRETS: allSecrets,
22672
- SESSION_ID: job.sessionId ?? "",
22935
+ SESSION_ID: sessionId ?? "",
22673
22936
  DASHBOARD_URL: job.dashboardUrl ?? "",
22674
22937
  MODEL: job.model ?? "",
22675
22938
  REASONING_EFFORT: job.reasoningEffort ?? "",
@@ -22698,7 +22961,7 @@ async function defaultRunJob(job) {
22698
22961
  const authorEmail = process.env.GIT_AUTHOR_EMAIL ?? "kody-bot@users.noreply.github.com";
22699
22962
  await run("git", ["config", "user.name", authorName], workdir);
22700
22963
  await run("git", ["config", "user.email", authorEmail], workdir);
22701
- const jobDesc = interactive ? `interactive session ${job.sessionId}` : scheduled ? "scheduled fan-out" : `running issue #${job.issueNumber}`;
22964
+ const jobDesc = interactive ? `chat session ${sessionId}` : scheduled ? `${target.type} ${target.id}` : `issue #${issueNumber}`;
22702
22965
  process.stdout.write(`[runner-serve] job ${job.jobId}: ${jobDesc}
22703
22966
  `);
22704
22967
  const runCode = await run("kody", [], workdir);
@@ -22781,7 +23044,7 @@ async function runnerServe() {
22781
23044
  init_config();
22782
23045
  init_litellm();
22783
23046
  import { spawn as spawn9 } from "child_process";
22784
- function parseTarget(positional) {
23047
+ function parseTarget2(positional) {
22785
23048
  if (!Array.isArray(positional) || positional.length === 0) return "none";
22786
23049
  const first = String(positional[0]).toLowerCase();
22787
23050
  if (first === "vscode" || first === "code") return "vscode";
@@ -22796,7 +23059,7 @@ function buildProxyEnv(url) {
22796
23059
  };
22797
23060
  }
22798
23061
  async function serve(opts) {
22799
- const target = parseTarget(opts.args);
23062
+ const target = parseTarget2(opts.args);
22800
23063
  const model = parseProviderModel(opts.config.agent.model);
22801
23064
  const usesProxy = needsLitellmProxy(model);
22802
23065
  let handle = null;
@@ -23110,6 +23373,17 @@ function envRunMode(env = process.env) {
23110
23373
  }
23111
23374
  return { ...result, errors: [`unknown KODY_RUN_MODE: ${mode}`] };
23112
23375
  }
23376
+ function envRunRequest(env = process.env) {
23377
+ const result = { command: "help", errors: [] };
23378
+ const parsed = readRunRequestFromEnv(env);
23379
+ if (!parsed) return null;
23380
+ if ("error" in parsed) return { ...result, errors: [parsed.error] };
23381
+ const { target } = parsed.request;
23382
+ if (target.type === "chat") return { ...result, command: "chat", chatArgv: [] };
23383
+ if (target.type === "issue") return { ...result, command: "ci", ciArgv: ["--issue", String(target.id)] };
23384
+ if (target.type === "goal" || target.type === "workflow") return { ...result, command: "ci", ciArgv: [] };
23385
+ return { ...result, errors: [`unsupported runRequest target: ${target.type ?? "unknown"}`] };
23386
+ }
23113
23387
  var HELP_TEXT = `kody-engine \u2014 single-session autonomous engineer
23114
23388
 
23115
23389
  Usage:
@@ -23146,6 +23420,8 @@ Exit codes:
23146
23420
  function parseArgs(argv) {
23147
23421
  const result = { command: "help", errors: [] };
23148
23422
  if (argv.length === 0) {
23423
+ const runRequest = envRunRequest();
23424
+ if (runRequest) return runRequest;
23149
23425
  const mode = envRunMode();
23150
23426
  if (mode) return mode;
23151
23427
  if (process.env.SESSION_ID) return { ...result, command: "chat", chatArgv: [] };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kody-ade/kody-engine",
3
- "version": "0.4.275",
3
+ "version": "0.4.277",
4
4
  "description": "kody — autonomous development engine. Single-session Claude Code agent behind a generic executor + declarative executable profiles.",
5
5
  "license": "MIT",
6
6
  "type": "module",