@kody-ade/kody-engine 0.4.117 → 0.4.118

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 +92 -16
  2. package/package.json +1 -1
package/dist/bin/kody.js CHANGED
@@ -880,7 +880,7 @@ var init_loadPriorArt = __esm({
880
880
  // package.json
881
881
  var package_default = {
882
882
  name: "@kody-ade/kody-engine",
883
- version: "0.4.117",
883
+ version: "0.4.118",
884
884
  description: "kody \u2014 autonomous development engine. Single-session Claude Code agent behind a generic executor + declarative executable profiles.",
885
885
  license: "MIT",
886
886
  type: "module",
@@ -1227,9 +1227,43 @@ function loadConfig(projectDir = process.cwd()) {
1227
1227
  classify: parseClassifyConfig(raw.classify),
1228
1228
  release: parseReleaseConfig(raw.release),
1229
1229
  jobs: parseJobsConfig(raw.jobs),
1230
+ access: parseAccessConfig(raw.access),
1230
1231
  qa: parseQaConfig(raw.qa)
1231
1232
  };
1232
1233
  }
1234
+ var GITHUB_AUTHOR_ASSOCIATIONS = [
1235
+ "OWNER",
1236
+ "MEMBER",
1237
+ "COLLABORATOR",
1238
+ "CONTRIBUTOR",
1239
+ "FIRST_TIME_CONTRIBUTOR",
1240
+ "FIRST_TIMER",
1241
+ "MANNEQUIN",
1242
+ "NONE"
1243
+ ];
1244
+ function parseAccessConfig(raw) {
1245
+ if (!raw || typeof raw !== "object") return void 0;
1246
+ const r = raw;
1247
+ if (r.allowedAssociations === void 0) return void 0;
1248
+ if (!Array.isArray(r.allowedAssociations)) {
1249
+ throw new Error(`kody.config.json: access.allowedAssociations must be an array of strings`);
1250
+ }
1251
+ const valid = new Set(GITHUB_AUTHOR_ASSOCIATIONS);
1252
+ const out = [];
1253
+ for (const v of r.allowedAssociations) {
1254
+ if (typeof v !== "string") {
1255
+ throw new Error(`kody.config.json: access.allowedAssociations entries must be strings`);
1256
+ }
1257
+ const up = v.trim().toUpperCase();
1258
+ if (!valid.has(up)) {
1259
+ throw new Error(
1260
+ `kody.config.json: access.allowedAssociations contains "${v}" \u2014 must be one of ${GITHUB_AUTHOR_ASSOCIATIONS.join(", ")}`
1261
+ );
1262
+ }
1263
+ out.push(up);
1264
+ }
1265
+ return out.length > 0 ? { allowedAssociations: out } : void 0;
1266
+ }
1233
1267
  function parseQaConfig(raw) {
1234
1268
  if (!raw || typeof raw !== "object") return void 0;
1235
1269
  const r = raw;
@@ -2496,6 +2530,7 @@ function autoDispatch(opts) {
2496
2530
  const authorType = String(event.comment?.user?.type ?? "");
2497
2531
  if (!rawBody.toLowerCase().includes("@kody")) return null;
2498
2532
  if (authorLogin === "kody-bot" || authorType === "Bot") return null;
2533
+ if (!associationAllowed(event, opts?.config)) return null;
2499
2534
  const body = rawBody.toLowerCase();
2500
2535
  const targetNum = Number(event.issue?.number ?? 0);
2501
2536
  const isPr = !!event.issue?.pull_request;
@@ -2571,6 +2606,10 @@ function autoDispatchTyped(opts) {
2571
2606
  if (authorLogin === "kody-bot" || authorType === "Bot") {
2572
2607
  return { kind: "silent", reason: `bot-authored comment (${authorLogin || authorType})` };
2573
2608
  }
2609
+ if (!associationAllowed(event, opts?.config)) {
2610
+ const assoc = String(event.comment?.author_association ?? "").toUpperCase() || "<none>";
2611
+ return { kind: "silent", reason: `commenter association '${assoc}' not in access.allowedAssociations` };
2612
+ }
2574
2613
  const targetNum = Number(event.issue?.number ?? 0);
2575
2614
  const isPr = !!event.issue?.pull_request;
2576
2615
  if (!targetNum) {
@@ -2622,6 +2661,12 @@ function dispatchScheduledWatches(opts) {
2622
2661
  }
2623
2662
  return out;
2624
2663
  }
2664
+ function associationAllowed(event, config) {
2665
+ const allowed = config?.access?.allowedAssociations;
2666
+ if (!allowed || allowed.length === 0) return true;
2667
+ const assoc = String(event.comment?.author_association ?? "").toUpperCase();
2668
+ return allowed.includes(assoc);
2669
+ }
2625
2670
  function extractAfterTag(body) {
2626
2671
  const idx = body.indexOf("@kody");
2627
2672
  if (idx === -1) return body;
@@ -4635,13 +4680,23 @@ function parseJob(body) {
4635
4680
  if (!jobId) return { error: "jobId required" };
4636
4681
  const repo = typeof b.repo === "string" ? b.repo.trim() : "";
4637
4682
  if (!/^[^/\s]+\/[^/\s]+$/.test(repo)) return { error: "repo must be 'owner/name'" };
4638
- const issueNumber = Number(b.issueNumber);
4639
- if (!Number.isInteger(issueNumber) || issueNumber <= 0) {
4640
- return { error: "issueNumber (positive integer) required" };
4641
- }
4642
4683
  const githubToken = typeof b.githubToken === "string" ? b.githubToken.trim() : "";
4643
4684
  if (!githubToken) return { error: "githubToken required" };
4644
- const job = { jobId, repo, issueNumber, githubToken };
4685
+ const mode = b.mode === "interactive" ? "interactive" : "issue";
4686
+ const job = { jobId, repo, githubToken, mode };
4687
+ if (mode === "issue") {
4688
+ const issueNumber = Number(b.issueNumber);
4689
+ if (!Number.isInteger(issueNumber) || issueNumber <= 0) {
4690
+ return { error: "issueNumber (positive integer) required for issue mode" };
4691
+ }
4692
+ job.issueNumber = issueNumber;
4693
+ } else {
4694
+ const sessionId = typeof b.sessionId === "string" ? b.sessionId.trim() : "";
4695
+ if (!sessionId) return { error: "sessionId required for interactive mode" };
4696
+ job.sessionId = sessionId;
4697
+ if (Number.isFinite(Number(b.idleExitMs))) job.idleExitMs = Number(b.idleExitMs);
4698
+ if (Number.isFinite(Number(b.hardCapMs))) job.hardCapMs = Number(b.hardCapMs);
4699
+ }
4645
4700
  if (typeof b.ref === "string" && b.ref.trim()) job.ref = b.ref.trim();
4646
4701
  if (typeof b.model === "string" && b.model.trim()) job.model = b.model.trim();
4647
4702
  if (typeof b.sessionId === "string" && b.sessionId.trim()) job.sessionId = b.sessionId.trim();
@@ -4658,16 +4713,21 @@ async function defaultRunJob(job) {
4658
4713
  fs19.rmSync(workdir, { recursive: true, force: true });
4659
4714
  fs19.mkdirSync(workdir, { recursive: true });
4660
4715
  const allSecrets = typeof job.allSecrets === "string" ? job.allSecrets : JSON.stringify(job.allSecrets ?? {});
4716
+ const interactive = job.mode === "interactive";
4661
4717
  const childEnv = {
4662
4718
  ...process.env,
4663
4719
  REPO: job.repo,
4664
4720
  REF: branch,
4665
4721
  GITHUB_TOKEN: job.githubToken,
4666
- ISSUE_NUMBER: String(job.issueNumber),
4722
+ // Issue mode bakes ISSUE_NUMBER → `kody run --issue N`. Interactive mode
4723
+ // leaves it empty and sets SESSION_ID so the engine boots a chat session.
4724
+ ISSUE_NUMBER: interactive ? "" : String(job.issueNumber),
4667
4725
  ALL_SECRETS: allSecrets,
4668
4726
  SESSION_ID: job.sessionId ?? "",
4669
4727
  DASHBOARD_URL: job.dashboardUrl ?? "",
4670
- MODEL: job.model ?? ""
4728
+ MODEL: job.model ?? "",
4729
+ ...interactive && job.idleExitMs ? { KODY_IDLE_EXIT_MS: String(job.idleExitMs) } : {},
4730
+ ...interactive && job.hardCapMs ? { KODY_HARD_CAP_MS: String(job.hardCapMs) } : {}
4671
4731
  };
4672
4732
  const run = (cmd, args, cwd) => new Promise((resolve4) => {
4673
4733
  const child = spawn3(cmd, args, { stdio: "inherit", env: childEnv, cwd });
@@ -4699,9 +4759,12 @@ async function defaultRunJob(job) {
4699
4759
  const authorEmail = process.env.GIT_AUTHOR_EMAIL ?? "kody-bot@users.noreply.github.com";
4700
4760
  await run("git", ["config", "user.name", authorName], workdir);
4701
4761
  await run("git", ["config", "user.email", authorEmail], workdir);
4702
- process.stdout.write(`[runner-serve] job ${job.jobId}: running issue #${job.issueNumber}
4703
- `);
4704
- const runCode = await run("kody", ["run", "--issue", String(job.issueNumber)], workdir);
4762
+ const runArgs = interactive ? [] : ["run", "--issue", String(job.issueNumber)];
4763
+ process.stdout.write(
4764
+ `[runner-serve] job ${job.jobId}: ${interactive ? `interactive session ${job.sessionId}` : `running issue #${job.issueNumber}`}
4765
+ `
4766
+ );
4767
+ const runCode = await run("kody", runArgs, workdir);
4705
4768
  process.stdout.write(`[runner-serve] job ${job.jobId}: finished (exit ${runCode})
4706
4769
  `);
4707
4770
  process.exit(runCode);
@@ -5215,12 +5278,15 @@ var PoolRegistry = class {
5215
5278
  const job = {
5216
5279
  jobId: req.jobId,
5217
5280
  repo: `${owner}/${repo}`,
5218
- issueNumber: req.issueNumber,
5219
5281
  githubToken: this.cfg.githubToken,
5282
+ mode: req.mode ?? "issue",
5283
+ issueNumber: req.issueNumber,
5284
+ sessionId: req.sessionId,
5285
+ idleExitMs: req.idleExitMs,
5286
+ hardCapMs: req.hardCapMs,
5220
5287
  ref: req.ref,
5221
5288
  allSecrets,
5222
5289
  model: req.model,
5223
- sessionId: req.sessionId,
5224
5290
  dashboardUrl: req.dashboardUrl
5225
5291
  };
5226
5292
  return pm.claim(job);
@@ -5318,9 +5384,19 @@ function parseClaimRequest(body) {
5318
5384
  if (!jobId) return { error: "jobId required" };
5319
5385
  const repo = typeof b.repo === "string" ? b.repo.trim() : "";
5320
5386
  if (!/^[^/\s]+\/[^/\s]+$/.test(repo)) return { error: "repo must be 'owner/name'" };
5321
- const issueNumber = Number(b.issueNumber);
5322
- if (!Number.isInteger(issueNumber) || issueNumber <= 0) return { error: "issueNumber required" };
5323
- const req = { jobId, repo, issueNumber };
5387
+ const mode = b.mode === "interactive" ? "interactive" : "issue";
5388
+ const req = { jobId, repo, mode };
5389
+ if (mode === "issue") {
5390
+ const issueNumber = Number(b.issueNumber);
5391
+ if (!Number.isInteger(issueNumber) || issueNumber <= 0) return { error: "issueNumber required for issue mode" };
5392
+ req.issueNumber = issueNumber;
5393
+ } else {
5394
+ const sessionId = typeof b.sessionId === "string" ? b.sessionId.trim() : "";
5395
+ if (!sessionId) return { error: "sessionId required for interactive mode" };
5396
+ req.sessionId = sessionId;
5397
+ if (Number.isFinite(Number(b.idleExitMs))) req.idleExitMs = Number(b.idleExitMs);
5398
+ if (Number.isFinite(Number(b.hardCapMs))) req.hardCapMs = Number(b.hardCapMs);
5399
+ }
5324
5400
  if (typeof b.ref === "string" && b.ref.trim()) req.ref = b.ref.trim();
5325
5401
  if (typeof b.model === "string" && b.model.trim()) req.model = b.model.trim();
5326
5402
  if (typeof b.sessionId === "string" && b.sessionId.trim()) req.sessionId = b.sessionId.trim();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kody-ade/kody-engine",
3
- "version": "0.4.117",
3
+ "version": "0.4.118",
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",