@kody-ade/kody-engine 0.4.117 → 0.4.119

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/bin/kody.js 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.119",
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,51 @@ 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
+ var DEFAULT_ALLOWED_ASSOCIATIONS = ["OWNER", "MEMBER", "COLLABORATOR"];
1245
+ function parseAccessConfig(raw) {
1246
+ if (raw === void 0 || raw === null) {
1247
+ return { allowedAssociations: [...DEFAULT_ALLOWED_ASSOCIATIONS] };
1248
+ }
1249
+ if (typeof raw !== "object") {
1250
+ throw new Error(`kody.config.json: access must be an object`);
1251
+ }
1252
+ const r = raw;
1253
+ if (r.allowedAssociations === void 0) {
1254
+ return { allowedAssociations: [...DEFAULT_ALLOWED_ASSOCIATIONS] };
1255
+ }
1256
+ if (!Array.isArray(r.allowedAssociations)) {
1257
+ throw new Error(`kody.config.json: access.allowedAssociations must be an array of strings`);
1258
+ }
1259
+ const valid = new Set(GITHUB_AUTHOR_ASSOCIATIONS);
1260
+ const out = [];
1261
+ for (const v of r.allowedAssociations) {
1262
+ if (typeof v !== "string") {
1263
+ throw new Error(`kody.config.json: access.allowedAssociations entries must be strings`);
1264
+ }
1265
+ const up = v.trim().toUpperCase();
1266
+ if (!valid.has(up)) {
1267
+ throw new Error(
1268
+ `kody.config.json: access.allowedAssociations contains "${v}" \u2014 must be one of ${GITHUB_AUTHOR_ASSOCIATIONS.join(", ")}`
1269
+ );
1270
+ }
1271
+ out.push(up);
1272
+ }
1273
+ return { allowedAssociations: out };
1274
+ }
1233
1275
  function parseQaConfig(raw) {
1234
1276
  if (!raw || typeof raw !== "object") return void 0;
1235
1277
  const r = raw;
@@ -2496,6 +2538,7 @@ function autoDispatch(opts) {
2496
2538
  const authorType = String(event.comment?.user?.type ?? "");
2497
2539
  if (!rawBody.toLowerCase().includes("@kody")) return null;
2498
2540
  if (authorLogin === "kody-bot" || authorType === "Bot") return null;
2541
+ if (!associationAllowed(event, opts?.config)) return null;
2499
2542
  const body = rawBody.toLowerCase();
2500
2543
  const targetNum = Number(event.issue?.number ?? 0);
2501
2544
  const isPr = !!event.issue?.pull_request;
@@ -2571,6 +2614,10 @@ function autoDispatchTyped(opts) {
2571
2614
  if (authorLogin === "kody-bot" || authorType === "Bot") {
2572
2615
  return { kind: "silent", reason: `bot-authored comment (${authorLogin || authorType})` };
2573
2616
  }
2617
+ if (!associationAllowed(event, opts?.config)) {
2618
+ const assoc = String(event.comment?.author_association ?? "").toUpperCase() || "<none>";
2619
+ return { kind: "silent", reason: `commenter association '${assoc}' not in access.allowedAssociations` };
2620
+ }
2574
2621
  const targetNum = Number(event.issue?.number ?? 0);
2575
2622
  const isPr = !!event.issue?.pull_request;
2576
2623
  if (!targetNum) {
@@ -2622,6 +2669,12 @@ function dispatchScheduledWatches(opts) {
2622
2669
  }
2623
2670
  return out;
2624
2671
  }
2672
+ function associationAllowed(event, config) {
2673
+ const allowed = config?.access?.allowedAssociations;
2674
+ if (!allowed || allowed.length === 0) return true;
2675
+ const assoc = String(event.comment?.author_association ?? "").toUpperCase();
2676
+ return allowed.includes(assoc);
2677
+ }
2625
2678
  function extractAfterTag(body) {
2626
2679
  const idx = body.indexOf("@kody");
2627
2680
  if (idx === -1) return body;
@@ -4635,13 +4688,23 @@ function parseJob(body) {
4635
4688
  if (!jobId) return { error: "jobId required" };
4636
4689
  const repo = typeof b.repo === "string" ? b.repo.trim() : "";
4637
4690
  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
4691
  const githubToken = typeof b.githubToken === "string" ? b.githubToken.trim() : "";
4643
4692
  if (!githubToken) return { error: "githubToken required" };
4644
- const job = { jobId, repo, issueNumber, githubToken };
4693
+ const mode = b.mode === "interactive" ? "interactive" : "issue";
4694
+ const job = { jobId, repo, githubToken, mode };
4695
+ if (mode === "issue") {
4696
+ const issueNumber = Number(b.issueNumber);
4697
+ if (!Number.isInteger(issueNumber) || issueNumber <= 0) {
4698
+ return { error: "issueNumber (positive integer) required for issue mode" };
4699
+ }
4700
+ job.issueNumber = issueNumber;
4701
+ } else {
4702
+ const sessionId = typeof b.sessionId === "string" ? b.sessionId.trim() : "";
4703
+ if (!sessionId) return { error: "sessionId required for interactive mode" };
4704
+ job.sessionId = sessionId;
4705
+ if (Number.isFinite(Number(b.idleExitMs))) job.idleExitMs = Number(b.idleExitMs);
4706
+ if (Number.isFinite(Number(b.hardCapMs))) job.hardCapMs = Number(b.hardCapMs);
4707
+ }
4645
4708
  if (typeof b.ref === "string" && b.ref.trim()) job.ref = b.ref.trim();
4646
4709
  if (typeof b.model === "string" && b.model.trim()) job.model = b.model.trim();
4647
4710
  if (typeof b.sessionId === "string" && b.sessionId.trim()) job.sessionId = b.sessionId.trim();
@@ -4658,16 +4721,21 @@ async function defaultRunJob(job) {
4658
4721
  fs19.rmSync(workdir, { recursive: true, force: true });
4659
4722
  fs19.mkdirSync(workdir, { recursive: true });
4660
4723
  const allSecrets = typeof job.allSecrets === "string" ? job.allSecrets : JSON.stringify(job.allSecrets ?? {});
4724
+ const interactive = job.mode === "interactive";
4661
4725
  const childEnv = {
4662
4726
  ...process.env,
4663
4727
  REPO: job.repo,
4664
4728
  REF: branch,
4665
4729
  GITHUB_TOKEN: job.githubToken,
4666
- ISSUE_NUMBER: String(job.issueNumber),
4730
+ // Issue mode bakes ISSUE_NUMBER → `kody run --issue N`. Interactive mode
4731
+ // leaves it empty and sets SESSION_ID so the engine boots a chat session.
4732
+ ISSUE_NUMBER: interactive ? "" : String(job.issueNumber),
4667
4733
  ALL_SECRETS: allSecrets,
4668
4734
  SESSION_ID: job.sessionId ?? "",
4669
4735
  DASHBOARD_URL: job.dashboardUrl ?? "",
4670
- MODEL: job.model ?? ""
4736
+ MODEL: job.model ?? "",
4737
+ ...interactive && job.idleExitMs ? { KODY_IDLE_EXIT_MS: String(job.idleExitMs) } : {},
4738
+ ...interactive && job.hardCapMs ? { KODY_HARD_CAP_MS: String(job.hardCapMs) } : {}
4671
4739
  };
4672
4740
  const run = (cmd, args, cwd) => new Promise((resolve4) => {
4673
4741
  const child = spawn3(cmd, args, { stdio: "inherit", env: childEnv, cwd });
@@ -4699,9 +4767,12 @@ async function defaultRunJob(job) {
4699
4767
  const authorEmail = process.env.GIT_AUTHOR_EMAIL ?? "kody-bot@users.noreply.github.com";
4700
4768
  await run("git", ["config", "user.name", authorName], workdir);
4701
4769
  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);
4770
+ const runArgs = interactive ? [] : ["run", "--issue", String(job.issueNumber)];
4771
+ process.stdout.write(
4772
+ `[runner-serve] job ${job.jobId}: ${interactive ? `interactive session ${job.sessionId}` : `running issue #${job.issueNumber}`}
4773
+ `
4774
+ );
4775
+ const runCode = await run("kody", runArgs, workdir);
4705
4776
  process.stdout.write(`[runner-serve] job ${job.jobId}: finished (exit ${runCode})
4706
4777
  `);
4707
4778
  process.exit(runCode);
@@ -5215,12 +5286,15 @@ var PoolRegistry = class {
5215
5286
  const job = {
5216
5287
  jobId: req.jobId,
5217
5288
  repo: `${owner}/${repo}`,
5218
- issueNumber: req.issueNumber,
5219
5289
  githubToken: this.cfg.githubToken,
5290
+ mode: req.mode ?? "issue",
5291
+ issueNumber: req.issueNumber,
5292
+ sessionId: req.sessionId,
5293
+ idleExitMs: req.idleExitMs,
5294
+ hardCapMs: req.hardCapMs,
5220
5295
  ref: req.ref,
5221
5296
  allSecrets,
5222
5297
  model: req.model,
5223
- sessionId: req.sessionId,
5224
5298
  dashboardUrl: req.dashboardUrl
5225
5299
  };
5226
5300
  return pm.claim(job);
@@ -5318,9 +5392,19 @@ function parseClaimRequest(body) {
5318
5392
  if (!jobId) return { error: "jobId required" };
5319
5393
  const repo = typeof b.repo === "string" ? b.repo.trim() : "";
5320
5394
  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 };
5395
+ const mode = b.mode === "interactive" ? "interactive" : "issue";
5396
+ const req = { jobId, repo, mode };
5397
+ if (mode === "issue") {
5398
+ const issueNumber = Number(b.issueNumber);
5399
+ if (!Number.isInteger(issueNumber) || issueNumber <= 0) return { error: "issueNumber required for issue mode" };
5400
+ req.issueNumber = issueNumber;
5401
+ } else {
5402
+ const sessionId = typeof b.sessionId === "string" ? b.sessionId.trim() : "";
5403
+ if (!sessionId) return { error: "sessionId required for interactive mode" };
5404
+ req.sessionId = sessionId;
5405
+ if (Number.isFinite(Number(b.idleExitMs))) req.idleExitMs = Number(b.idleExitMs);
5406
+ if (Number.isFinite(Number(b.hardCapMs))) req.hardCapMs = Number(b.hardCapMs);
5407
+ }
5324
5408
  if (typeof b.ref === "string" && b.ref.trim()) req.ref = b.ref.trim();
5325
5409
  if (typeof b.model === "string" && b.model.trim()) req.model = b.model.trim();
5326
5410
  if (typeof b.sessionId === "string" && b.sessionId.trim()) req.sessionId = b.sessionId.trim();
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
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.119",
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",
@@ -12,23 +12,6 @@
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
- "build": "tsup && node scripts/copy-assets.cjs",
21
- "check:modularity": "tsx scripts/check-script-modularity.ts",
22
- "pretest": "pnpm check:modularity",
23
- "test": "vitest run tests/unit tests/int --no-coverage",
24
- "test:e2e": "vitest run tests/e2e --no-coverage",
25
- "test:all": "vitest run tests --no-coverage",
26
- "typecheck": "tsc --noEmit",
27
- "lint": "biome check",
28
- "lint:fix": "biome check --write",
29
- "format": "biome format --write",
30
- "prepublishOnly": "pnpm build"
31
- },
32
15
  "dependencies": {
33
16
  "@actions/cache": "^6.0.0",
34
17
  "@anthropic-ai/claude-agent-sdk": "0.2.119",
@@ -50,5 +33,21 @@
50
33
  "url": "git+https://github.com/aharonyaircohen/kody-engine.git"
51
34
  },
52
35
  "homepage": "https://github.com/aharonyaircohen/kody-engine",
53
- "bugs": "https://github.com/aharonyaircohen/kody-engine/issues"
54
- }
36
+ "bugs": "https://github.com/aharonyaircohen/kody-engine/issues",
37
+ "scripts": {
38
+ "kody:run": "tsx bin/kody.ts",
39
+ "serve": "tsx bin/kody.ts serve",
40
+ "serve:vscode": "tsx bin/kody.ts serve vscode",
41
+ "serve:claude": "tsx bin/kody.ts serve claude",
42
+ "build": "tsup && node scripts/copy-assets.cjs",
43
+ "check:modularity": "tsx scripts/check-script-modularity.ts",
44
+ "pretest": "pnpm check:modularity",
45
+ "test": "vitest run tests/unit tests/int --no-coverage",
46
+ "test:e2e": "vitest run tests/e2e --no-coverage",
47
+ "test:all": "vitest run tests --no-coverage",
48
+ "typecheck": "tsc --noEmit",
49
+ "lint": "biome check",
50
+ "lint:fix": "biome check --write",
51
+ "format": "biome format --write"
52
+ }
53
+ }
@@ -90,4 +90,4 @@ jobs:
90
90
  INIT_MESSAGE: ${{ inputs.message }}
91
91
  MODEL: ${{ inputs.model }}
92
92
  DASHBOARD_URL: ${{ inputs.dashboardUrl }}
93
- run: npx -y -p @kody-ade/kody-engine@0.4.109 kody-engine
93
+ run: npx -y -p @kody-ade/kody-engine@0.4.119 kody-engine