@kody-ade/kody-engine 0.4.271 → 0.4.273

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 +262 -21
  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.271",
18
+ version: "0.4.273",
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",
@@ -2146,15 +2146,7 @@ function dispatchWorkflow(workflowFile, capability, issueNumber, repoSlug) {
2146
2146
  }
2147
2147
  }
2148
2148
  try {
2149
- gh([
2150
- "workflow",
2151
- "run",
2152
- workflowFile,
2153
- "-f",
2154
- `capability=${capability}`,
2155
- "-f",
2156
- `issue_number=${issueNumber}`
2157
- ]);
2149
+ gh(["workflow", "run", workflowFile, "-f", `capability=${capability}`, "-f", `issue_number=${issueNumber}`]);
2158
2150
  return { ok: true };
2159
2151
  } catch (err) {
2160
2152
  return { ok: false, error: err instanceof Error ? err.message : String(err) };
@@ -2189,6 +2181,132 @@ function isDispatchGated(capability, mode) {
2189
2181
  function trustRefusal(capabilitySlug) {
2190
2182
  return `Not dispatched: capability \`${capabilitySlug ?? "?"}\` is in ASK mode (not trusted for autonomy). Do NOT retry the dispatch. Instead notify the operator (use recommend_to_operator, or rely on the tracking issue that already @-mentions them), then submit_state. To let this capability act on its own, grant it Auto on the dashboard Trust page.`;
2191
2183
  }
2184
+ function dashboardBaseUrl() {
2185
+ const raw = process.env.KODY_CMS_DASHBOARD_URL?.trim() || process.env.KODY_DASHBOARD_URL?.trim() || process.env.DASHBOARD_URL?.trim() || "";
2186
+ if (!raw) return null;
2187
+ try {
2188
+ const parsed = new URL(raw);
2189
+ return parsed.origin;
2190
+ } catch {
2191
+ return null;
2192
+ }
2193
+ }
2194
+ function dashboardCmsToken() {
2195
+ return process.env.KODY_CMS_TOKEN?.trim() || process.env.KODY_DASHBOARD_TOKEN?.trim() || process.env.KODY_TOKEN?.trim() || process.env.GH_TOKEN?.trim() || process.env.GITHUB_TOKEN?.trim() || process.env.GH_PAT?.trim() || null;
2196
+ }
2197
+ function cmsHeaders(opts) {
2198
+ const token = dashboardCmsToken();
2199
+ const [owner, repo] = opts.repoSlug.split("/");
2200
+ if (!token) {
2201
+ return {
2202
+ ok: false,
2203
+ error: "missing_cms_token",
2204
+ message: "Set KODY_CMS_TOKEN, KODY_DASHBOARD_TOKEN, KODY_TOKEN, GH_TOKEN, GITHUB_TOKEN, or GH_PAT."
2205
+ };
2206
+ }
2207
+ if (!owner || !repo) {
2208
+ return { ok: false, error: "invalid_repo", message: `Invalid repo slug: ${opts.repoSlug}` };
2209
+ }
2210
+ return {
2211
+ ok: true,
2212
+ headers: {
2213
+ "Content-Type": "application/json",
2214
+ "x-kody-token": token,
2215
+ "x-kody-owner": owner,
2216
+ "x-kody-repo": repo
2217
+ }
2218
+ };
2219
+ }
2220
+ async function callDashboardCms(opts, path49, init = {}) {
2221
+ const baseUrl = dashboardBaseUrl();
2222
+ if (!baseUrl) {
2223
+ return {
2224
+ ok: false,
2225
+ error: "missing_dashboard_url",
2226
+ message: "Set KODY_CMS_DASHBOARD_URL or KODY_DASHBOARD_URL to the Dashboard origin."
2227
+ };
2228
+ }
2229
+ const headerResult = cmsHeaders(opts);
2230
+ if (!headerResult.ok) return headerResult;
2231
+ try {
2232
+ const res = await fetch(`${baseUrl}${path49}`, {
2233
+ ...init,
2234
+ headers: {
2235
+ ...headerResult.headers,
2236
+ ...init.headers
2237
+ }
2238
+ });
2239
+ const contentType = res.headers.get("content-type") ?? "";
2240
+ const data = contentType.includes("application/json") ? await res.json().catch(() => null) : await res.text().catch(() => "");
2241
+ if (!res.ok) {
2242
+ return {
2243
+ ok: false,
2244
+ status: res.status,
2245
+ error: stringFieldFromRecord(data, "error") ?? "cms_request_failed",
2246
+ message: stringFieldFromRecord(data, "message") ?? `Dashboard CMS request failed with ${res.status}.`,
2247
+ data
2248
+ };
2249
+ }
2250
+ return { ok: true, status: res.status, data };
2251
+ } catch (err) {
2252
+ return {
2253
+ ok: false,
2254
+ error: "cms_request_error",
2255
+ message: err instanceof Error ? err.message : String(err)
2256
+ };
2257
+ }
2258
+ }
2259
+ function cmsToolResponse(result) {
2260
+ return {
2261
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
2262
+ ...result.ok ? {} : { isError: true }
2263
+ };
2264
+ }
2265
+ function assertCmsWriteAllowed(opts) {
2266
+ if (isDispatchGated(opts.capabilitySlug, readCapabilityTrustMode(opts.state, opts.repoSlug, opts.capabilitySlug))) {
2267
+ return trustRefusal(opts.capabilitySlug);
2268
+ }
2269
+ return null;
2270
+ }
2271
+ function cmsQuery(args) {
2272
+ const params = new URLSearchParams();
2273
+ const q = stringArg(args.q);
2274
+ if (q) params.set("q", q);
2275
+ const limit = numberArg(args.limit);
2276
+ if (limit !== void 0) params.set("limit", String(limit));
2277
+ const offset = numberArg(args.offset);
2278
+ if (offset !== void 0) params.set("offset", String(offset));
2279
+ if (args.filters && typeof args.filters === "object" && !Array.isArray(args.filters)) {
2280
+ params.set("filters", JSON.stringify(args.filters));
2281
+ }
2282
+ if (Array.isArray(args.sort)) {
2283
+ const sort = args.sort.flatMap((entry) => {
2284
+ if (!entry || typeof entry !== "object") return [];
2285
+ const field = stringArg(entry.field);
2286
+ if (!field) return [];
2287
+ const direction = entry.direction === "asc" ? "asc" : "desc";
2288
+ return [`${field}:${direction}`];
2289
+ }).join(",");
2290
+ if (sort) params.set("sort", sort);
2291
+ }
2292
+ const value = params.toString();
2293
+ return value ? `?${value}` : "";
2294
+ }
2295
+ function stringArg(value) {
2296
+ return typeof value === "string" ? value.trim() : "";
2297
+ }
2298
+ function numberArg(value) {
2299
+ if (value === void 0 || value === null || value === "") return void 0;
2300
+ const n = Number(value);
2301
+ return Number.isFinite(n) ? n : void 0;
2302
+ }
2303
+ function documentArg(value) {
2304
+ if (!value || typeof value !== "object" || Array.isArray(value)) return {};
2305
+ return value;
2306
+ }
2307
+ function stringFieldFromRecord(value, field) {
2308
+ return value && typeof value === "object" && !Array.isArray(value) && typeof value[field] === "string" ? String(value[field]) : void 0;
2309
+ }
2192
2310
  function capabilityToolDefinitions(opts) {
2193
2311
  const workflowFile = opts.workflowFile ?? "kody.yml";
2194
2312
  const listTool = {
@@ -2337,10 +2455,7 @@ function capabilityToolDefinitions(opts) {
2337
2455
  handler: async (args) => {
2338
2456
  const capability = String(args.capability ?? args.executable ?? "");
2339
2457
  const issueNumber = Number(args.issueNumber);
2340
- if (isDispatchGated(
2341
- capability,
2342
- readCapabilityTrustMode(opts.state, opts.repoSlug, opts.capabilitySlug)
2343
- )) {
2458
+ if (isDispatchGated(capability, readCapabilityTrustMode(opts.state, opts.repoSlug, opts.capabilitySlug))) {
2344
2459
  return { content: [{ type: "text", text: trustRefusal(opts.capabilitySlug) }] };
2345
2460
  }
2346
2461
  const result = dispatchWorkflow(workflowFile, capability, issueNumber, opts.repoSlug);
@@ -2348,6 +2463,86 @@ function capabilityToolDefinitions(opts) {
2348
2463
  return { content: [{ type: "text", text }] };
2349
2464
  }
2350
2465
  };
2466
+ const cmsListCollectionsTool = {
2467
+ name: "cms_list_collections",
2468
+ description: "List configured Dashboard CMS collections and their supported operations. Read-only.",
2469
+ inputSchema: {},
2470
+ handler: async () => cmsToolResponse(await callDashboardCms(opts, "/api/kody/cms"))
2471
+ };
2472
+ const cmsListDocumentsTool = {
2473
+ name: "cms_list_documents",
2474
+ description: "List or search Dashboard CMS documents from one configured collection. Read-only.",
2475
+ inputSchema: {
2476
+ collection: z2.string().min(1).describe("CMS collection name."),
2477
+ q: z2.string().optional().describe("Optional search query."),
2478
+ filters: z2.record(z2.string(), z2.unknown()).optional().describe("Optional filter object keyed by field."),
2479
+ sort: z2.array(
2480
+ z2.object({
2481
+ field: z2.string().min(1),
2482
+ direction: z2.enum(["asc", "desc"]).default("desc")
2483
+ })
2484
+ ).optional(),
2485
+ limit: z2.number().int().min(1).max(100).optional(),
2486
+ offset: z2.number().int().min(0).optional()
2487
+ },
2488
+ handler: async (args) => {
2489
+ const collection = encodeURIComponent(stringArg(args.collection));
2490
+ return cmsToolResponse(await callDashboardCms(opts, `/api/kody/cms/${collection}${cmsQuery(args)}`));
2491
+ }
2492
+ };
2493
+ const cmsGetDocumentTool = {
2494
+ name: "cms_get_document",
2495
+ description: "Get one Dashboard CMS document by collection and id. Read-only.",
2496
+ inputSchema: {
2497
+ collection: z2.string().min(1).describe("CMS collection name."),
2498
+ id: z2.string().min(1).describe("Document id.")
2499
+ },
2500
+ handler: async (args) => {
2501
+ const collection = encodeURIComponent(stringArg(args.collection));
2502
+ const id = encodeURIComponent(stringArg(args.id));
2503
+ return cmsToolResponse(await callDashboardCms(opts, `/api/kody/cms/${collection}/${id}`));
2504
+ }
2505
+ };
2506
+ const cmsCreateDocumentTool = {
2507
+ name: "cms_create_document",
2508
+ description: "Create one Dashboard CMS document when the capability is trusted and the CMS collection allows create.",
2509
+ inputSchema: {
2510
+ collection: z2.string().min(1).describe("CMS collection name."),
2511
+ data: z2.record(z2.string(), z2.unknown()).describe("Document fields to create.")
2512
+ },
2513
+ handler: async (args) => {
2514
+ const refusal = assertCmsWriteAllowed(opts);
2515
+ if (refusal) return { content: [{ type: "text", text: refusal }] };
2516
+ const collection = encodeURIComponent(stringArg(args.collection));
2517
+ return cmsToolResponse(
2518
+ await callDashboardCms(opts, `/api/kody/cms/${collection}`, {
2519
+ method: "POST",
2520
+ body: JSON.stringify(documentArg(args.data))
2521
+ })
2522
+ );
2523
+ }
2524
+ };
2525
+ const cmsUpdateDocumentTool = {
2526
+ name: "cms_update_document",
2527
+ description: "Update one Dashboard CMS document when the capability is trusted and the CMS collection allows update.",
2528
+ inputSchema: {
2529
+ collection: z2.string().min(1).describe("CMS collection name."),
2530
+ id: z2.string().min(1).describe("Document id."),
2531
+ data: z2.record(z2.string(), z2.unknown()).describe("Partial document fields to update.")
2532
+ },
2533
+ handler: async (args) => {
2534
+ const refusal = assertCmsWriteAllowed(opts);
2535
+ if (refusal) return { content: [{ type: "text", text: refusal }] };
2536
+ const collection = encodeURIComponent(stringArg(args.collection));
2537
+ const id = encodeURIComponent(stringArg(args.id));
2538
+ return cmsToolResponse(
2539
+ await callDashboardCms(opts, `/api/kody/cms/${collection}/${id}`, {
2540
+ method: "PATCH",
2541
+ body: JSON.stringify(documentArg(args.data))
2542
+ })
2543
+ );
2544
+ }
2545
+ };
2351
2546
  return [
2352
2547
  listTool,
2353
2548
  syncTool,
@@ -2359,7 +2554,12 @@ function capabilityToolDefinitions(opts) {
2359
2554
  readThreadTool,
2360
2555
  ensureIssueTool,
2361
2556
  ensureCommentTool,
2362
- dispatchTool
2557
+ dispatchTool,
2558
+ cmsListCollectionsTool,
2559
+ cmsListDocumentsTool,
2560
+ cmsGetDocumentTool,
2561
+ cmsCreateDocumentTool,
2562
+ cmsUpdateDocumentTool
2363
2563
  ];
2364
2564
  }
2365
2565
  function buildCapabilityMcpServer(opts) {
@@ -2406,7 +2606,12 @@ var init_capabilityMcp = __esm({
2406
2606
  "read_thread",
2407
2607
  "ensure_issue",
2408
2608
  "ensure_comment",
2409
- "dispatch_workflow"
2609
+ "dispatch_workflow",
2610
+ "cms_list_collections",
2611
+ "cms_list_documents",
2612
+ "cms_get_document",
2613
+ "cms_create_document",
2614
+ "cms_update_document"
2410
2615
  ];
2411
2616
  }
2412
2617
  });
@@ -19470,7 +19675,15 @@ async function runCi(argv) {
19470
19675
  let manualWorkflowDispatch = false;
19471
19676
  let forceRunAction = null;
19472
19677
  let forceRunCliArgs = {};
19473
- if (!args.issueNumber && !autoFallback && eventName === "workflow_dispatch" && dispatchEventPath && fs45.existsSync(dispatchEventPath)) {
19678
+ const envForceAction = (process.env.KODY_FORCE_ACTION ?? "").trim();
19679
+ const envForceMessage = (process.env.KODY_FORCE_MESSAGE ?? "").trim();
19680
+ if (!args.issueNumber && !autoFallback && envForceAction) {
19681
+ forceRunAction = envForceAction;
19682
+ if (envForceAction === "goal-manager" && envForceMessage) {
19683
+ forceRunCliArgs = { goal: envForceMessage };
19684
+ }
19685
+ }
19686
+ if (!args.issueNumber && !autoFallback && !forceRunAction && eventName === "workflow_dispatch" && dispatchEventPath && fs45.existsSync(dispatchEventPath)) {
19474
19687
  try {
19475
19688
  const evt = JSON.parse(fs45.readFileSync(dispatchEventPath, "utf-8"));
19476
19689
  const issueInput = parseInt(String(evt?.inputs?.issue_number ?? ""), 10);
@@ -22241,6 +22454,9 @@ function parseJob(body) {
22241
22454
  }
22242
22455
  if (typeof b.ref === "string" && b.ref.trim()) job.ref = b.ref.trim();
22243
22456
  if (typeof b.model === "string" && b.model.trim()) job.model = b.model.trim();
22457
+ if (typeof b.reasoningEffort === "string" && b.reasoningEffort.trim()) job.reasoningEffort = b.reasoningEffort.trim();
22458
+ if (typeof b.action === "string" && b.action.trim()) job.action = b.action.trim();
22459
+ if (typeof b.message === "string" && b.message.trim()) job.message = b.message.trim();
22244
22460
  if (typeof b.sessionId === "string" && b.sessionId.trim()) job.sessionId = b.sessionId.trim();
22245
22461
  if (typeof b.dashboardUrl === "string" && b.dashboardUrl.trim()) job.dashboardUrl = b.dashboardUrl.trim();
22246
22462
  if (b.allSecrets && (typeof b.allSecrets === "object" || typeof b.allSecrets === "string")) {
@@ -22257,14 +22473,18 @@ async function defaultRunJob(job) {
22257
22473
  const allSecrets = typeof job.allSecrets === "string" ? job.allSecrets : JSON.stringify(job.allSecrets ?? {});
22258
22474
  const interactive = job.mode === "interactive";
22259
22475
  const scheduled = job.mode === "scheduled";
22476
+ const runMode = interactive ? "interactive" : scheduled ? "scheduled" : "issue";
22260
22477
  const childEnv = {
22261
22478
  ...process.env,
22262
22479
  REPO: job.repo,
22263
22480
  REF: branch,
22264
22481
  GITHUB_TOKEN: job.githubToken,
22482
+ KODY_RUN_MODE: runMode,
22265
22483
  // Scheduled mode drives the engine down the same path GitHub Actions' cron
22266
22484
  // takes (runScheduledFanOut → due capabilities/goals). Bare `kody` routes on this.
22267
22485
  ...scheduled ? { GITHUB_EVENT_NAME: "schedule" } : {},
22486
+ ...job.action ? { KODY_FORCE_ACTION: job.action } : {},
22487
+ ...job.message ? { KODY_FORCE_MESSAGE: job.message } : {},
22268
22488
  // GITHUB_REPOSITORY + GH_TOKEN are normally injected by GitHub Actions.
22269
22489
  // The engine's interactive mode needs GITHUB_REPOSITORY to resolve the
22270
22490
  // configured state repo and persist chat.ready / events (the durable signal
@@ -22272,13 +22492,14 @@ async function defaultRunJob(job) {
22272
22492
  // and the session never appears "ready". GH_TOKEN auths the `gh` CLI.
22273
22493
  GITHUB_REPOSITORY: job.repo,
22274
22494
  GH_TOKEN: job.githubToken,
22275
- // Issue mode bakes ISSUE_NUMBER `kody run --issue N`. Interactive mode
22276
- // leaves it empty and sets SESSION_ID so the engine boots a chat session.
22495
+ // Issue mode carries ISSUE_NUMBER. Interactive mode leaves it empty and
22496
+ // sets SESSION_ID so the engine boots a chat session.
22277
22497
  ISSUE_NUMBER: interactive || scheduled ? "" : String(job.issueNumber),
22278
22498
  ALL_SECRETS: allSecrets,
22279
22499
  SESSION_ID: job.sessionId ?? "",
22280
22500
  DASHBOARD_URL: job.dashboardUrl ?? "",
22281
22501
  MODEL: job.model ?? "",
22502
+ REASONING_EFFORT: job.reasoningEffort ?? "",
22282
22503
  ...interactive && job.idleExitMs ? { KODY_IDLE_EXIT_MS: String(job.idleExitMs) } : {},
22283
22504
  ...interactive && job.hardCapMs ? { KODY_HARD_CAP_MS: String(job.hardCapMs) } : {}
22284
22505
  };
@@ -22304,11 +22525,10 @@ async function defaultRunJob(job) {
22304
22525
  const authorEmail = process.env.GIT_AUTHOR_EMAIL ?? "kody-bot@users.noreply.github.com";
22305
22526
  await run("git", ["config", "user.name", authorName], workdir);
22306
22527
  await run("git", ["config", "user.email", authorEmail], workdir);
22307
- const runArgs = interactive || scheduled ? [] : ["run", "--issue", String(job.issueNumber)];
22308
22528
  const jobDesc = interactive ? `interactive session ${job.sessionId}` : scheduled ? "scheduled fan-out" : `running issue #${job.issueNumber}`;
22309
22529
  process.stdout.write(`[runner-serve] job ${job.jobId}: ${jobDesc}
22310
22530
  `);
22311
- const runCode = await run("kody", runArgs, workdir);
22531
+ const runCode = await run("kody", [], workdir);
22312
22532
  process.stdout.write(`[runner-serve] job ${job.jobId}: finished (exit ${runCode})
22313
22533
  `);
22314
22534
  process.exit(runCode);
@@ -22698,6 +22918,25 @@ function formatMs(ms) {
22698
22918
  }
22699
22919
 
22700
22920
  // src/entry.ts
22921
+ function envRunMode(env = process.env) {
22922
+ const result = { command: "help", errors: [] };
22923
+ const mode = (env.KODY_RUN_MODE ?? "").trim().toLowerCase();
22924
+ if (!mode) return null;
22925
+ if (mode === "chat" || mode === "interactive") {
22926
+ return { ...result, command: "chat", chatArgv: [] };
22927
+ }
22928
+ if (mode === "ci" || mode === "scheduled" || mode === "manual") {
22929
+ return { ...result, command: "ci", ciArgv: [] };
22930
+ }
22931
+ if (mode === "issue") {
22932
+ const issue = (env.ISSUE_NUMBER ?? "").trim();
22933
+ if (!issue) {
22934
+ return { ...result, errors: ["KODY_RUN_MODE=issue requires ISSUE_NUMBER"] };
22935
+ }
22936
+ return { ...result, command: "ci", ciArgv: ["--issue", issue] };
22937
+ }
22938
+ return { ...result, errors: [`unknown KODY_RUN_MODE: ${mode}`] };
22939
+ }
22701
22940
  var HELP_TEXT = `kody-engine \u2014 single-session autonomous engineer
22702
22941
 
22703
22942
  Usage:
@@ -22734,6 +22973,8 @@ Exit codes:
22734
22973
  function parseArgs(argv) {
22735
22974
  const result = { command: "help", errors: [] };
22736
22975
  if (argv.length === 0) {
22976
+ const mode = envRunMode();
22977
+ if (mode) return mode;
22737
22978
  if (process.env.SESSION_ID) return { ...result, command: "chat", chatArgv: [] };
22738
22979
  if (process.env.GITHUB_EVENT_NAME) return { ...result, command: "ci", ciArgv: [] };
22739
22980
  return result;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kody-ade/kody-engine",
3
- "version": "0.4.271",
3
+ "version": "0.4.273",
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",