@kody-ade/kody-engine 0.4.138 → 0.4.139

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
@@ -312,6 +312,52 @@ var init_verifyMcp = __esm({
312
312
  }
313
313
  });
314
314
 
315
+ // src/submitMcp.ts
316
+ var submitMcp_exports = {};
317
+ __export(submitMcp_exports, {
318
+ buildSubmitMcpServer: () => buildSubmitMcpServer
319
+ });
320
+ import { createSdkMcpServer as createSdkMcpServer2, tool as tool2 } from "@anthropic-ai/claude-agent-sdk";
321
+ import { z as z2 } from "zod";
322
+ function buildSubmitMcpServer() {
323
+ let submitted;
324
+ const submitTool = tool2(
325
+ "submit_state",
326
+ "Persist this tick's next state. Call this EXACTLY ONCE, at the very end, when you've finished your work \u2014 it is the ONLY way your decision is saved. Pass your next `cursor` (string), your next `data` (object \u2014 carry prior data forward and mutate what you acted on), and `done` (boolean). After calling it you are finished; do not take further actions.",
327
+ {
328
+ cursor: z2.string().describe('The next cursor value (e.g. "idle"). Must be a non-empty string.'),
329
+ data: z2.record(z2.string(), z2.unknown()).describe("The next `data` object. Carry forward prior data and mutate only what you acted on this tick."),
330
+ done: z2.boolean().describe("true only if this duty is permanently finished; evergreen duties stay false.")
331
+ },
332
+ async (args) => {
333
+ submitted = {
334
+ cursor: String(args.cursor ?? ""),
335
+ data: args.data ?? {},
336
+ done: Boolean(args.done)
337
+ };
338
+ return {
339
+ content: [
340
+ {
341
+ type: "text",
342
+ text: "State recorded. You are done for this tick \u2014 no further action needed."
343
+ }
344
+ ]
345
+ };
346
+ }
347
+ );
348
+ const server = createSdkMcpServer2({
349
+ name: "kody-submit",
350
+ version: "0.1.0",
351
+ tools: [submitTool]
352
+ });
353
+ return { server, getSubmitted: () => submitted };
354
+ }
355
+ var init_submitMcp = __esm({
356
+ "src/submitMcp.ts"() {
357
+ "use strict";
358
+ }
359
+ });
360
+
315
361
  // src/issue.ts
316
362
  import { execFileSync } from "child_process";
317
363
  function ghToken() {
@@ -880,7 +926,7 @@ var init_loadPriorArt = __esm({
880
926
  // package.json
881
927
  var package_default = {
882
928
  name: "@kody-ade/kody-engine",
883
- version: "0.4.138",
929
+ version: "0.4.139",
884
930
  description: "kody \u2014 autonomous development engine. Single-session Claude Code agent behind a generic executor + declarative executable profiles.",
885
931
  license: "MIT",
886
932
  type: "module",
@@ -1514,6 +1560,7 @@ async function runAgent(opts) {
1514
1560
  const turnTimeoutMs = resolveTurnTimeoutMs(opts);
1515
1561
  let ndjsonWriteFailed = false;
1516
1562
  let ndjsonWriteError;
1563
+ let getSubmitted;
1517
1564
  try {
1518
1565
  const queryOptions = {
1519
1566
  model: opts.model.model,
@@ -1541,6 +1588,12 @@ async function runAgent(opts) {
1541
1588
  });
1542
1589
  mcpEntries.push(["kody-verify", verifyServer]);
1543
1590
  }
1591
+ if (opts.enableSubmitTool) {
1592
+ const { buildSubmitMcpServer: buildSubmitMcpServer2 } = await Promise.resolve().then(() => (init_submitMcp(), submitMcp_exports));
1593
+ const submitHandle = buildSubmitMcpServer2();
1594
+ getSubmitted = submitHandle.getSubmitted;
1595
+ mcpEntries.push(["kody-submit", submitHandle.server]);
1596
+ }
1544
1597
  if (mcpEntries.length > 0) {
1545
1598
  queryOptions.mcpServers = Object.fromEntries(mcpEntries);
1546
1599
  }
@@ -1706,10 +1759,12 @@ async function runAgent(opts) {
1706
1759
  `);
1707
1760
  }
1708
1761
  const finalText = resultTexts.join("\n\n---\n\n");
1762
+ const submittedState = getSubmitted?.();
1709
1763
  return {
1710
1764
  outcome,
1711
1765
  outcomeKind,
1712
1766
  finalText,
1767
+ ...submittedState ? { submittedState } : {},
1713
1768
  error: errorMessage,
1714
1769
  ndjsonPath,
1715
1770
  durationMs: Date.now() - startedAt,
@@ -3186,6 +3241,7 @@ function parseClaudeCode(p, raw) {
3186
3241
  systemPromptAppend: typeof r.systemPromptAppend === "string" ? r.systemPromptAppend : null,
3187
3242
  cacheable: r.cacheable === true,
3188
3243
  enableVerifyTool: r.enableVerifyTool === true,
3244
+ enableSubmitTool: r.enableSubmitTool === true,
3189
3245
  verifyAttempts: typeof r.verifyAttempts === "number" && r.verifyAttempts > 0 ? r.verifyAttempts : null,
3190
3246
  tools,
3191
3247
  hooks: Array.isArray(r.hooks) ? r.hooks : [],
@@ -9313,6 +9369,17 @@ var parseJobStateFromAgentResult = async (ctx, _profile, agentResult, args) => {
9313
9369
  }
9314
9370
  const loaded = ctx.data.jobState;
9315
9371
  const prevRev = loaded?.state.rev ?? 0;
9372
+ const submitted = agentResult.submittedState;
9373
+ if (submitted && typeof submitted.cursor === "string" && submitted.cursor.length > 0) {
9374
+ ctx.data.nextJobState = {
9375
+ version: 1,
9376
+ rev: prevRev + 1,
9377
+ cursor: submitted.cursor,
9378
+ data: submitted.data ?? {},
9379
+ done: Boolean(submitted.done)
9380
+ };
9381
+ return;
9382
+ }
9316
9383
  const result = extractNextStateFromText(agentResult.finalText, fenceLabel, prevRev);
9317
9384
  if (result.error) {
9318
9385
  ctx.data.nextStateParseError = result.error.startsWith("missing `") ? `agent did not emit a \`${fenceLabel}\` fenced block` : result.error;
@@ -12512,21 +12579,21 @@ function firstRequiredFailure(results, tools) {
12512
12579
  }
12513
12580
  return null;
12514
12581
  }
12515
- function verifyOne(tool2, cwd) {
12516
- const result = { name: tool2.name, present: false, verified: false };
12517
- let present = runShell(tool2.install.checkCommand, cwd);
12518
- if (!present && tool2.install.installCommand) {
12519
- runShell(tool2.install.installCommand, cwd, 12e4);
12520
- present = runShell(tool2.install.checkCommand, cwd);
12582
+ function verifyOne(tool3, cwd) {
12583
+ const result = { name: tool3.name, present: false, verified: false };
12584
+ let present = runShell(tool3.install.checkCommand, cwd);
12585
+ if (!present && tool3.install.installCommand) {
12586
+ runShell(tool3.install.installCommand, cwd, 12e4);
12587
+ present = runShell(tool3.install.checkCommand, cwd);
12521
12588
  }
12522
12589
  result.present = present;
12523
12590
  if (!present) {
12524
- result.error = `tool "${tool2.name}" not on PATH (check: ${tool2.install.checkCommand})`;
12591
+ result.error = `tool "${tool3.name}" not on PATH (check: ${tool3.install.checkCommand})`;
12525
12592
  return result;
12526
12593
  }
12527
- const verified = runShell(tool2.verify, cwd);
12594
+ const verified = runShell(tool3.verify, cwd);
12528
12595
  result.verified = verified;
12529
- if (!verified) result.error = `tool "${tool2.name}" failed verify: ${tool2.verify}`;
12596
+ if (!verified) result.error = `tool "${tool3.name}" failed verify: ${tool3.verify}`;
12530
12597
  return result;
12531
12598
  }
12532
12599
  function runShell(cmd, cwd, timeoutMs = 3e4) {
@@ -12666,6 +12733,7 @@ async function runExecutable(profileName, input) {
12666
12733
  systemPromptAppend: [DISCIPLINE, profile.claudeCode.systemPromptAppend, taskArtifacts?.promptAddendum].filter((s) => typeof s === "string" && s.length > 0).join("\n\n") || void 0,
12667
12734
  cacheable: profile.claudeCode.cacheable,
12668
12735
  enableVerifyTool: profile.claudeCode.enableVerifyTool,
12736
+ enableSubmitTool: profile.claudeCode.enableSubmitTool,
12669
12737
  verifyToolMaxAttempts: profile.claudeCode.verifyAttempts ?? null,
12670
12738
  verifyConfig: profile.claudeCode.enableVerifyTool ? config : void 0,
12671
12739
  executableName: profileName,
@@ -24,7 +24,8 @@
24
24
  "maxTurns": 20,
25
25
  "maxThinkingTokens": null,
26
26
  "systemPromptAppend": null,
27
- "tools": ["Bash", "Read"],
27
+ "enableSubmitTool": true,
28
+ "tools": ["Bash", "Read", "mcp__kody-submit"],
28
29
  "hooks": [],
29
30
  "skills": [],
30
31
  "commands": [],
@@ -32,23 +32,25 @@ This is the state you wrote at the end of the previous tick (or `null` if this i
32
32
  2. **Re-read the job body.** It may have changed since the last tick.
33
33
  3. **Execute exactly the work the body's `## Job` section describes**, subject to its `## Allowed Commands` and `## Restrictions`. Use the `## State` section to interpret and update `data`.
34
34
  4. **Optionally post a short narration** wherever the job tells you to (typically a PR comment alongside the action). Keep it terse.
35
- 5. **Emit the new state** at the very end of your response using the fenced block below. Do not include `version` or `rev` — the postflight script manages those.
35
+ 5. **Submit the new state** by calling the `submit_state` tool (see contract below). Do not include `version` or `rev` — the postflight script manages those.
36
36
 
37
37
  ## Output contract (MANDATORY, exactly once, at the end)
38
38
 
39
- End your response with a single fenced block using the `kody-job-next-state` language tag:
39
+ Call the **`submit_state`** tool exactly once, as the final step, with your next state:
40
40
 
41
- ````
42
- ```kody-job-next-state
43
- {
44
- "cursor": "<your-next-cursor>",
45
- "data": { ... },
46
- "done": <true|false>
47
- }
48
- ```
49
- ````
41
+ - `cursor` — your next cursor (string, e.g. `"idle"`).
42
+ - `data` — your next `data` object. Carry forward prior `data` and mutate only what you acted on this tick.
43
+ - `done` — `true` only if the duty is permanently finished; evergreen duties stay `false`.
44
+
45
+ This is the ONLY way your decision is saved. If you don't call it, the tick fails and the state is NOT updated — on the next wake you'll see the same prior state and can retry.
50
46
 
51
- If you fail to emit this block, or the JSON is invalid, the tick fails and the gist state is NOT updated. On the next wake you'll see the same prior state and can retry.
47
+ > Backstop (legacy): if the `submit_state` tool is unavailable, end your reply with the same JSON in a single fenced block tagged `kody-job-next-state` instead:
48
+ >
49
+ > ````
50
+ > ```kody-job-next-state
51
+ > { "cursor": "<next>", "data": { ... }, "done": <true|false> }
52
+ > ```
53
+ > ````
52
54
 
53
55
  ## Rules
54
56
 
@@ -228,6 +228,13 @@ export interface ClaudeCodeSpec {
228
228
  * Default false.
229
229
  */
230
230
  enableVerifyTool?: boolean
231
+ /**
232
+ * Opt-in: expose an in-process `submit_state` tool the agent calls to
233
+ * persist its next state, instead of relying on a trailing fenced
234
+ * `kody-job-next-state` block it must remember to emit. Used by job-tick.
235
+ * The fenced block stays supported as a fallback. Default false.
236
+ */
237
+ enableSubmitTool?: boolean
231
238
  /**
232
239
  * Hard cap on verify-tool invocations per agent session when
233
240
  * `enableVerifyTool` is true. Default 4 (≈3 fix iterations after the
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kody-ade/kody-engine",
3
- "version": "0.4.138",
3
+ "version": "0.4.139",
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",