@kody-ade/kody-engine 0.4.12 → 0.4.13

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
@@ -3,7 +3,7 @@
3
3
  // package.json
4
4
  var package_default = {
5
5
  name: "@kody-ade/kody-engine",
6
- version: "0.4.12",
6
+ version: "0.4.13",
7
7
  description: "kody \u2014 autonomous development engine. Single-session Claude Code agent behind a generic executor + declarative executable profiles.",
8
8
  license: "MIT",
9
9
  type: "module",
@@ -388,7 +388,15 @@ async function runAgent(opts) {
388
388
  ...process.env,
389
389
  SKIP_HOOKS: "1",
390
390
  HUSKY: "0",
391
- CI: process.env.CI ?? "1"
391
+ CI: process.env.CI ?? "1",
392
+ // MCP servers are spawned asynchronously by the SDK. With the default
393
+ // non-blocking behavior, the SDK announces its tool list at session
394
+ // init while servers are still in `pending`, so their tools never
395
+ // reach the model. Block until each MCP completes its handshake (or
396
+ // the timeout below elapses) so the tool list is complete on first
397
+ // turn.
398
+ MCP_CONNECTION_NONBLOCKING: process.env.MCP_CONNECTION_NONBLOCKING ?? "false",
399
+ MCP_TIMEOUT: process.env.MCP_TIMEOUT ?? "60000"
392
400
  };
393
401
  if (opts.litellmUrl) {
394
402
  env.ANTHROPIC_BASE_URL = opts.litellmUrl;
@@ -1253,7 +1261,7 @@ function coerceBare(spec, value) {
1253
1261
  }
1254
1262
 
1255
1263
  // src/executor.ts
1256
- import { execFileSync as execFileSync27, spawn as spawn4 } from "child_process";
1264
+ import { execFileSync as execFileSync27, spawn as spawn5 } from "child_process";
1257
1265
  import * as fs26 from "fs";
1258
1266
  import * as path23 from "path";
1259
1267
 
@@ -3904,10 +3912,14 @@ function ensurePr(opts) {
3904
3912
  const title = buildPrTitle(effectiveOpts.issueNumber, effectiveOpts.issueTitle, effectiveOpts.draft);
3905
3913
  const body = buildPrBody(effectiveOpts);
3906
3914
  if (existing) {
3915
+ const stripped = existing.url.replace(/^https:\/\/github\.com\//, "");
3916
+ const [owner, repo] = stripped.split("/");
3907
3917
  try {
3908
- gh2(["pr", "edit", String(existing.number), "--body-file", "-"], { input: body, cwd: opts.cwd });
3918
+ gh2(["api", "--method", "PATCH", `repos/${owner}/${repo}/pulls/${existing.number}`, "-f", `body=${body}`], {
3919
+ cwd: opts.cwd
3920
+ });
3909
3921
  } catch (err) {
3910
- throw new Error(`gh pr edit #${existing.number} failed: ${err instanceof Error ? err.message : String(err)}`);
3922
+ throw new Error(`gh api PATCH #${existing.number} failed: ${err instanceof Error ? err.message : String(err)}`);
3911
3923
  }
3912
3924
  return { url: existing.url, number: existing.number, draft: opts.draft, action: "updated" };
3913
3925
  }
@@ -3966,15 +3978,19 @@ var ensureMemorizePr = async (ctx) => {
3966
3978
  const body = buildBody(ctx, branch, datestamp);
3967
3979
  const existing = findExistingPr(branch, ctx.cwd);
3968
3980
  if (existing) {
3981
+ const stripped = existing.url.replace(/^https:\/\/github\.com\//, "");
3982
+ const [owner, repo] = stripped.split("/");
3969
3983
  try {
3970
- gh2(["pr", "edit", String(existing.number), "--body-file", "-"], { input: body, cwd: ctx.cwd });
3984
+ gh2(["api", "--method", "PATCH", `repos/${owner}/${repo}/pulls/${existing.number}`, "-f", `body=${body}`], {
3985
+ cwd: ctx.cwd
3986
+ });
3971
3987
  ctx.output.prUrl = existing.url;
3972
3988
  ctx.data.prResult = { url: existing.url, number: existing.number, action: "updated" };
3973
3989
  process.stdout.write(`[kody memorize] updated PR ${existing.url}
3974
3990
  `);
3975
3991
  } catch (err) {
3976
3992
  ctx.output.exitCode = 4;
3977
- ctx.output.reason = `gh pr edit #${existing.number} failed: ${err instanceof Error ? err.message : String(err)}`;
3993
+ ctx.output.reason = `gh api PATCH #${existing.number} failed: ${err instanceof Error ? err.message : String(err)}`;
3978
3994
  }
3979
3995
  return;
3980
3996
  }
@@ -7277,6 +7293,151 @@ function sleep2(ms) {
7277
7293
  return new Promise((res) => setTimeout(res, ms));
7278
7294
  }
7279
7295
 
7296
+ // src/scripts/warmupMcp.ts
7297
+ import { spawn as spawn4 } from "child_process";
7298
+ var PER_SERVER_TIMEOUT_MS = 6e4;
7299
+ var PER_REQUEST_TIMEOUT_MS = 2e4;
7300
+ var warmupMcp = async (_ctx, profile) => {
7301
+ const servers = profile.claudeCode.mcpServers ?? [];
7302
+ if (servers.length === 0) return;
7303
+ for (const s of servers) {
7304
+ const start = Date.now();
7305
+ try {
7306
+ const result = await warmupOne(s.command, s.args ?? [], s.env);
7307
+ const ms = Date.now() - start;
7308
+ process.stderr.write(`[kody warmup] ${s.name}: ${result.toolCount} tools (${ms}ms)
7309
+ `);
7310
+ } catch (err) {
7311
+ const ms = Date.now() - start;
7312
+ const reason = err instanceof Error ? err.message : String(err);
7313
+ process.stderr.write(`[kody warmup] ${s.name} FAILED after ${ms}ms: ${reason}
7314
+ `);
7315
+ }
7316
+ }
7317
+ };
7318
+ async function warmupOne(command, args, env) {
7319
+ const child = spawn4(command, args, {
7320
+ stdio: ["pipe", "pipe", "pipe"],
7321
+ env: env ? { ...process.env, ...env } : process.env
7322
+ });
7323
+ let stderrBuf = "";
7324
+ child.stderr.on("data", (b) => {
7325
+ stderrBuf += b.toString("utf8");
7326
+ if (stderrBuf.length > 4096) stderrBuf = stderrBuf.slice(-4096);
7327
+ });
7328
+ const overallDeadline = Date.now() + PER_SERVER_TIMEOUT_MS;
7329
+ const lines = lineStream(child.stdout);
7330
+ let nextId = 1;
7331
+ const send = (method, params) => {
7332
+ const id = nextId++;
7333
+ const payload = JSON.stringify({ jsonrpc: "2.0", id, method, params }) + "\n";
7334
+ child.stdin.write(payload);
7335
+ return id;
7336
+ };
7337
+ const notify = (method, params) => {
7338
+ const payload = JSON.stringify({ jsonrpc: "2.0", method, params }) + "\n";
7339
+ child.stdin.write(payload);
7340
+ };
7341
+ const awaitResponse = async (id) => {
7342
+ const reqDeadline = Math.min(Date.now() + PER_REQUEST_TIMEOUT_MS, overallDeadline);
7343
+ while (Date.now() < reqDeadline) {
7344
+ const line = await lines.next(reqDeadline - Date.now());
7345
+ if (line === null) break;
7346
+ let msg = null;
7347
+ try {
7348
+ msg = JSON.parse(line);
7349
+ } catch {
7350
+ continue;
7351
+ }
7352
+ if (msg && msg.id === id) return msg;
7353
+ }
7354
+ throw new Error(`request id=${id} timed out (stderr tail: ${stderrBuf.trim().slice(-300) || "(empty)"})`);
7355
+ };
7356
+ try {
7357
+ const initId = send("initialize", {
7358
+ protocolVersion: "2024-11-05",
7359
+ capabilities: {},
7360
+ clientInfo: { name: "kody-warmup", version: "0.1.0" }
7361
+ });
7362
+ const initResp = await awaitResponse(initId);
7363
+ if (initResp.error) throw new Error(`initialize error: ${initResp.error.message}`);
7364
+ notify("notifications/initialized");
7365
+ const listId = send("tools/list");
7366
+ const listResp = await awaitResponse(listId);
7367
+ if (listResp.error) throw new Error(`tools/list error: ${listResp.error.message}`);
7368
+ const tools = listResp.result?.tools;
7369
+ const toolCount = Array.isArray(tools) ? tools.length : 0;
7370
+ if (toolCount === 0) throw new Error("tools/list returned 0 tools");
7371
+ return { toolCount };
7372
+ } finally {
7373
+ try {
7374
+ child.kill("SIGTERM");
7375
+ } catch {
7376
+ }
7377
+ setTimeout(() => {
7378
+ try {
7379
+ child.kill("SIGKILL");
7380
+ } catch {
7381
+ }
7382
+ }, 2e3).unref();
7383
+ }
7384
+ }
7385
+ function lineStream(stream) {
7386
+ let buf = "";
7387
+ const queue = [];
7388
+ let waiter = null;
7389
+ let ended = false;
7390
+ const tryDeliver = () => {
7391
+ if (waiter && queue.length > 0) {
7392
+ const w = waiter;
7393
+ waiter = null;
7394
+ w(queue.shift());
7395
+ } else if (waiter && ended) {
7396
+ const w = waiter;
7397
+ waiter = null;
7398
+ w(null);
7399
+ }
7400
+ };
7401
+ stream.on("data", (chunk) => {
7402
+ buf += typeof chunk === "string" ? chunk : chunk.toString("utf8");
7403
+ let idx;
7404
+ while ((idx = buf.indexOf("\n")) >= 0) {
7405
+ const line = buf.slice(0, idx).replace(/\r$/, "");
7406
+ buf = buf.slice(idx + 1);
7407
+ if (line.length > 0) queue.push(line);
7408
+ }
7409
+ tryDeliver();
7410
+ });
7411
+ stream.on("end", () => {
7412
+ if (buf.length > 0) {
7413
+ queue.push(buf);
7414
+ buf = "";
7415
+ }
7416
+ ended = true;
7417
+ tryDeliver();
7418
+ });
7419
+ return {
7420
+ next: (timeoutMs) => new Promise((resolve4) => {
7421
+ if (queue.length > 0) {
7422
+ resolve4(queue.shift());
7423
+ return;
7424
+ }
7425
+ if (ended) {
7426
+ resolve4(null);
7427
+ return;
7428
+ }
7429
+ waiter = resolve4;
7430
+ const t = setTimeout(() => {
7431
+ if (waiter === resolve4) {
7432
+ waiter = null;
7433
+ resolve4(null);
7434
+ }
7435
+ }, Math.max(0, timeoutMs));
7436
+ t.unref?.();
7437
+ })
7438
+ };
7439
+ }
7440
+
7280
7441
  // src/scripts/watchStalePrsFlow.ts
7281
7442
  function readWatchConfig(ctx) {
7282
7443
  const cfg = ctx.config.watch;
@@ -7462,6 +7623,7 @@ var preflightScripts = {
7462
7623
  skipAgent,
7463
7624
  classifyByLabel,
7464
7625
  diagMcp,
7626
+ warmupMcp,
7465
7627
  dispatchJobTicks,
7466
7628
  dispatchJobFileTicks
7467
7629
  };
@@ -7840,7 +8002,7 @@ async function runShellEntry(entry, ctx, profile) {
7840
8002
  env[`KODY_CFG_${k}`] = v;
7841
8003
  }
7842
8004
  const timeoutMs = resolveShellTimeoutMs(entry);
7843
- const child = spawn4("bash", [shellPath, ...positional], {
8005
+ const child = spawn5("bash", [shellPath, ...positional], {
7844
8006
  cwd: ctx.cwd,
7845
8007
  env,
7846
8008
  stdio: ["pipe", "pipe", "pipe"],
@@ -81,6 +81,7 @@
81
81
  { "script": "discoverQaContext" },
82
82
  { "script": "loadQaGuide" },
83
83
  { "script": "loadConventions" },
84
+ { "script": "warmupMcp" },
84
85
  { "script": "composePrompt" }
85
86
  ],
86
87
  "postflight": [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kody-ade/kody-engine",
3
- "version": "0.4.12",
3
+ "version": "0.4.13",
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",