@kody-ade/kody-engine 0.2.31 → 0.2.33
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/kody2.js +350 -325
- package/dist/executables/types.ts +2 -0
- package/package.json +15 -14
package/dist/bin/kody2.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.2.
|
|
6
|
+
version: "0.2.33",
|
|
7
7
|
description: "kody2 \u2014 autonomous development engine. Single-session Claude Code agent behind a generic executor + declarative executable profiles.",
|
|
8
8
|
license: "MIT",
|
|
9
9
|
type: "module",
|
|
@@ -50,81 +50,115 @@ var package_default = {
|
|
|
50
50
|
};
|
|
51
51
|
|
|
52
52
|
// src/chat-cli.ts
|
|
53
|
-
import
|
|
54
|
-
import * as fs19 from "fs";
|
|
55
|
-
import * as path16 from "path";
|
|
53
|
+
import * as path14 from "path";
|
|
56
54
|
|
|
57
|
-
// src/chat/
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
55
|
+
// src/chat/pull.ts
|
|
56
|
+
var PullError = class extends Error {
|
|
57
|
+
constructor(message, status) {
|
|
58
|
+
super(message);
|
|
59
|
+
this.status = status;
|
|
60
|
+
this.name = "PullError";
|
|
61
|
+
}
|
|
62
|
+
status;
|
|
63
|
+
};
|
|
64
|
+
function createPullClient(opts) {
|
|
65
|
+
const fetchFn = opts.fetchFn ?? fetch;
|
|
66
|
+
const parsed = parseUrl(opts.baseUrl);
|
|
67
|
+
const token = opts.token ?? parsed.token;
|
|
68
|
+
if (!token) {
|
|
69
|
+
throw new PullError("session token not provided (expected inline ?token= in dashboardUrl)");
|
|
70
|
+
}
|
|
71
|
+
return async function pull(since, timeoutMs) {
|
|
72
|
+
const url = new URL(parsed.origin);
|
|
73
|
+
url.pathname = "/api/kody/chat/pull";
|
|
74
|
+
url.searchParams.set("sessionId", opts.sessionId);
|
|
75
|
+
url.searchParams.set("since", String(since));
|
|
76
|
+
url.searchParams.set("timeoutMs", String(timeoutMs));
|
|
77
|
+
url.searchParams.set("token", token);
|
|
78
|
+
const abort = AbortSignal.timeout(timeoutMs + 1e4);
|
|
79
|
+
const res = await fetchFn(url.toString(), {
|
|
80
|
+
method: "GET",
|
|
81
|
+
headers: { authorization: `Bearer ${token}` },
|
|
82
|
+
signal: abort
|
|
83
|
+
});
|
|
84
|
+
if (!res.ok) {
|
|
85
|
+
const body = await res.text().catch(() => "");
|
|
86
|
+
throw new PullError(`pull ${url.pathname} \u2192 ${res.status}: ${body.slice(0, 200)}`, res.status);
|
|
87
|
+
}
|
|
88
|
+
const data = await res.json();
|
|
89
|
+
return data;
|
|
90
|
+
};
|
|
62
91
|
}
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
`);
|
|
92
|
+
function parseUrl(baseUrl) {
|
|
93
|
+
try {
|
|
94
|
+
const u = new URL(baseUrl);
|
|
95
|
+
const token = u.searchParams.get("token");
|
|
96
|
+
const origin = `${u.protocol}//${u.host}${u.pathname !== "/" ? u.pathname : ""}`.replace(/\/$/, "");
|
|
97
|
+
return { origin: origin || u.origin, token };
|
|
98
|
+
} catch {
|
|
99
|
+
return { origin: baseUrl, token: null };
|
|
72
100
|
}
|
|
73
|
-
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// src/chat/events.ts
|
|
74
104
|
var HttpSink = class {
|
|
75
|
-
constructor(baseUrl, sessionId, logger = {
|
|
105
|
+
constructor(baseUrl, sessionId, token, fetchFn = fetch, logger = {
|
|
76
106
|
warn: (m) => process.stderr.write(`[kody2:chat] ${m}
|
|
77
107
|
`)
|
|
78
108
|
}) {
|
|
79
|
-
this.baseUrl = baseUrl;
|
|
80
109
|
this.sessionId = sessionId;
|
|
110
|
+
this.fetchFn = fetchFn;
|
|
81
111
|
this.logger = logger;
|
|
112
|
+
const parsed = parseUrl(baseUrl);
|
|
113
|
+
this.origin = parsed.origin;
|
|
114
|
+
const resolved = token ?? parsed.token;
|
|
115
|
+
if (!resolved) {
|
|
116
|
+
throw new Error("HttpSink: session token not provided (expected inline ?token= in baseUrl)");
|
|
117
|
+
}
|
|
118
|
+
this.token = resolved;
|
|
82
119
|
}
|
|
83
|
-
baseUrl;
|
|
84
120
|
sessionId;
|
|
121
|
+
fetchFn;
|
|
85
122
|
logger;
|
|
123
|
+
origin;
|
|
124
|
+
token;
|
|
86
125
|
async emit(event) {
|
|
87
|
-
const url =
|
|
126
|
+
const url = new URL(this.origin);
|
|
127
|
+
url.pathname = "/api/kody/events/ingest";
|
|
128
|
+
url.searchParams.set("sessionId", this.sessionId);
|
|
129
|
+
url.searchParams.set("token", this.token);
|
|
88
130
|
try {
|
|
89
|
-
const res = await
|
|
131
|
+
const res = await this.fetchFn(url.toString(), {
|
|
90
132
|
method: "POST",
|
|
91
|
-
headers: {
|
|
133
|
+
headers: {
|
|
134
|
+
"content-type": "application/json",
|
|
135
|
+
authorization: `Bearer ${this.token}`
|
|
136
|
+
},
|
|
92
137
|
body: JSON.stringify(event),
|
|
93
138
|
signal: AbortSignal.timeout(5e3)
|
|
94
139
|
});
|
|
95
140
|
if (!res.ok) {
|
|
96
|
-
this.logger.warn(`HttpSink POST ${url} \u2192 ${res.status}`);
|
|
141
|
+
this.logger.warn(`HttpSink POST ${url.pathname} \u2192 ${res.status}`);
|
|
97
142
|
}
|
|
98
143
|
} catch (err) {
|
|
99
|
-
this.logger.warn(
|
|
144
|
+
this.logger.warn(
|
|
145
|
+
`HttpSink POST ${url.pathname} failed: ${err instanceof Error ? err.message : String(err)}`
|
|
146
|
+
);
|
|
100
147
|
}
|
|
101
148
|
}
|
|
102
149
|
};
|
|
103
|
-
var TeeSink = class {
|
|
104
|
-
constructor(sinks) {
|
|
105
|
-
this.sinks = sinks;
|
|
106
|
-
}
|
|
107
|
-
sinks;
|
|
108
|
-
async emit(event) {
|
|
109
|
-
await Promise.all(this.sinks.map((s) => s.emit(event)));
|
|
110
|
-
}
|
|
111
|
-
};
|
|
112
|
-
function withSessionParam(baseUrl, sessionId) {
|
|
113
|
-
const joiner = baseUrl.includes("?") ? "&" : "?";
|
|
114
|
-
return `${baseUrl}${joiner}sessionId=${encodeURIComponent(sessionId)}`;
|
|
115
|
-
}
|
|
116
150
|
function makeRunId(sessionId, suffix) {
|
|
117
151
|
return `chat-${sessionId}-${suffix}`;
|
|
118
152
|
}
|
|
119
153
|
|
|
120
154
|
// src/agent.ts
|
|
121
|
-
import * as
|
|
122
|
-
import * as
|
|
155
|
+
import * as fs2 from "fs";
|
|
156
|
+
import * as path2 from "path";
|
|
123
157
|
import { query } from "@anthropic-ai/claude-agent-sdk";
|
|
124
158
|
|
|
125
159
|
// src/config.ts
|
|
126
|
-
import * as
|
|
127
|
-
import * as
|
|
160
|
+
import * as fs from "fs";
|
|
161
|
+
import * as path from "path";
|
|
128
162
|
var LITELLM_DEFAULT_PORT = 4e3;
|
|
129
163
|
var LITELLM_DEFAULT_URL = `http://localhost:${LITELLM_DEFAULT_PORT}`;
|
|
130
164
|
function parseProviderModel(s) {
|
|
@@ -142,13 +176,13 @@ function needsLitellmProxy(model) {
|
|
|
142
176
|
return model.provider !== "claude" && model.provider !== "anthropic";
|
|
143
177
|
}
|
|
144
178
|
function loadConfig(projectDir = process.cwd()) {
|
|
145
|
-
const configPath =
|
|
146
|
-
if (!
|
|
179
|
+
const configPath = path.join(projectDir, "kody.config.json");
|
|
180
|
+
if (!fs.existsSync(configPath)) {
|
|
147
181
|
throw new Error(`kody.config.json not found at ${configPath}`);
|
|
148
182
|
}
|
|
149
183
|
let raw;
|
|
150
184
|
try {
|
|
151
|
-
raw = JSON.parse(
|
|
185
|
+
raw = JSON.parse(fs.readFileSync(configPath, "utf-8"));
|
|
152
186
|
} catch (err) {
|
|
153
187
|
const msg = err instanceof Error ? err.message : String(err);
|
|
154
188
|
throw new Error(`kody.config.json is invalid JSON: ${msg}`);
|
|
@@ -325,10 +359,10 @@ function formatBytes(bytes) {
|
|
|
325
359
|
// src/agent.ts
|
|
326
360
|
var DEFAULT_ALLOWED_TOOLS = ["Bash", "Edit", "Read", "Write", "Glob", "Grep"];
|
|
327
361
|
async function runAgent(opts) {
|
|
328
|
-
const ndjsonDir = opts.ndjsonDir ??
|
|
329
|
-
|
|
330
|
-
const ndjsonPath =
|
|
331
|
-
const fullLog =
|
|
362
|
+
const ndjsonDir = opts.ndjsonDir ?? path2.join(opts.cwd, ".kody2");
|
|
363
|
+
fs2.mkdirSync(ndjsonDir, { recursive: true });
|
|
364
|
+
const ndjsonPath = path2.join(ndjsonDir, "last-run.jsonl");
|
|
365
|
+
const fullLog = fs2.createWriteStream(ndjsonPath, { flags: "w" });
|
|
332
366
|
const env = {
|
|
333
367
|
...process.env,
|
|
334
368
|
SKIP_HOOKS: "1",
|
|
@@ -359,6 +393,9 @@ async function runAgent(opts) {
|
|
|
359
393
|
if (typeof opts.maxTurns === "number" && opts.maxTurns > 0) {
|
|
360
394
|
queryOptions.maxTurns = opts.maxTurns;
|
|
361
395
|
}
|
|
396
|
+
if (typeof opts.maxThinkingTokens === "number" && opts.maxThinkingTokens > 0) {
|
|
397
|
+
queryOptions.maxThinkingTokens = opts.maxThinkingTokens;
|
|
398
|
+
}
|
|
362
399
|
if (typeof opts.systemPromptAppend === "string" && opts.systemPromptAppend.length > 0) {
|
|
363
400
|
queryOptions.systemPrompt = { type: "preset", preset: "claude_code", append: opts.systemPromptAppend };
|
|
364
401
|
}
|
|
@@ -400,53 +437,6 @@ async function runAgent(opts) {
|
|
|
400
437
|
return { outcome, finalText, error: errorMessage, ndjsonPath };
|
|
401
438
|
}
|
|
402
439
|
|
|
403
|
-
// src/chat/session.ts
|
|
404
|
-
import * as fs4 from "fs";
|
|
405
|
-
import * as path4 from "path";
|
|
406
|
-
function sessionFilePath(cwd, sessionId) {
|
|
407
|
-
return path4.join(cwd, ".kody", "sessions", `${sessionId}.jsonl`);
|
|
408
|
-
}
|
|
409
|
-
function readSession(file) {
|
|
410
|
-
if (!fs4.existsSync(file)) return [];
|
|
411
|
-
const raw = fs4.readFileSync(file, "utf-8").trim();
|
|
412
|
-
if (!raw) return [];
|
|
413
|
-
const turns = [];
|
|
414
|
-
for (const line of raw.split("\n")) {
|
|
415
|
-
if (!line.trim()) continue;
|
|
416
|
-
try {
|
|
417
|
-
const parsed = JSON.parse(line);
|
|
418
|
-
if (parsed.role !== "user" && parsed.role !== "assistant") continue;
|
|
419
|
-
if (typeof parsed.content !== "string") continue;
|
|
420
|
-
turns.push(parsed);
|
|
421
|
-
} catch {
|
|
422
|
-
}
|
|
423
|
-
}
|
|
424
|
-
return turns;
|
|
425
|
-
}
|
|
426
|
-
function appendTurn(file, turn) {
|
|
427
|
-
fs4.mkdirSync(path4.dirname(file), { recursive: true });
|
|
428
|
-
const line = JSON.stringify({
|
|
429
|
-
role: turn.role,
|
|
430
|
-
content: turn.content,
|
|
431
|
-
timestamp: turn.timestamp,
|
|
432
|
-
toolCalls: turn.toolCalls ?? []
|
|
433
|
-
});
|
|
434
|
-
fs4.appendFileSync(file, `${line}
|
|
435
|
-
`);
|
|
436
|
-
}
|
|
437
|
-
function seedInitialMessage(file, message) {
|
|
438
|
-
if (!message.trim()) return false;
|
|
439
|
-
const turns = readSession(file);
|
|
440
|
-
const lastUser = [...turns].reverse().find((t) => t.role === "user");
|
|
441
|
-
if (lastUser && lastUser.content === message) return false;
|
|
442
|
-
appendTurn(file, {
|
|
443
|
-
role: "user",
|
|
444
|
-
content: message,
|
|
445
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
446
|
-
});
|
|
447
|
-
return true;
|
|
448
|
-
}
|
|
449
|
-
|
|
450
440
|
// src/chat/loop.ts
|
|
451
441
|
var CHAT_SYSTEM_PROMPT = [
|
|
452
442
|
"You are Kody, an AI assistant for the Kody Operations Dashboard. Reply to the user's",
|
|
@@ -455,20 +445,15 @@ var CHAT_SYSTEM_PROMPT = [
|
|
|
455
445
|
"read repository code or execute small checks when it helps you answer \u2014 otherwise",
|
|
456
446
|
"reply directly. Do not invent file paths, commit SHAs, or command output."
|
|
457
447
|
].join("\n");
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
const
|
|
466
|
-
|
|
467
|
-
const error = "last turn is not a user message \u2014 assistant already replied";
|
|
468
|
-
await emit(opts.sink, "chat.error", opts.sessionId, "error", { error });
|
|
469
|
-
return { exitCode: 64, error };
|
|
470
|
-
}
|
|
471
|
-
const prompt = buildPrompt(turns, opts.systemPrompt ?? CHAT_SYSTEM_PROMPT);
|
|
448
|
+
var DEFAULT_IDLE_TIMEOUT_MS = 3 * 60 * 1e3;
|
|
449
|
+
var DEFAULT_HARD_TIMEOUT_MS = 5 * 60 * 60 * 1e3;
|
|
450
|
+
var DEFAULT_PULL_TIMEOUT_MS = 25e3;
|
|
451
|
+
async function runChatSession(opts) {
|
|
452
|
+
const now = opts.now ?? (() => Date.now());
|
|
453
|
+
const idleTimeoutMs = opts.idleTimeoutMs ?? DEFAULT_IDLE_TIMEOUT_MS;
|
|
454
|
+
const hardTimeoutMs = opts.hardTimeoutMs ?? DEFAULT_HARD_TIMEOUT_MS;
|
|
455
|
+
const pullTimeoutMs = opts.pullTimeoutMs ?? DEFAULT_PULL_TIMEOUT_MS;
|
|
456
|
+
const systemPrompt = opts.systemPrompt ?? CHAT_SYSTEM_PROMPT;
|
|
472
457
|
const invoke = opts.invokeAgent ?? ((p) => runAgent({
|
|
473
458
|
prompt: p,
|
|
474
459
|
model: opts.model,
|
|
@@ -477,34 +462,68 @@ async function runChatTurn(opts) {
|
|
|
477
462
|
verbose: opts.verbose,
|
|
478
463
|
quiet: opts.quiet
|
|
479
464
|
}));
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
465
|
+
const history = [];
|
|
466
|
+
let since = 0;
|
|
467
|
+
let lastActivityAt = now();
|
|
468
|
+
const startedAt = now();
|
|
469
|
+
let turnsProcessed = 0;
|
|
470
|
+
while (true) {
|
|
471
|
+
if (now() - startedAt > hardTimeoutMs) {
|
|
472
|
+
await emit(opts.sink, "chat.done", opts.sessionId, "done", {
|
|
473
|
+
sessionId: opts.sessionId,
|
|
474
|
+
reason: "hard-timeout"
|
|
475
|
+
});
|
|
476
|
+
return { exitCode: 0, turnsProcessed, reason: "hard-timeout" };
|
|
477
|
+
}
|
|
478
|
+
let response;
|
|
479
|
+
try {
|
|
480
|
+
response = await opts.pull(since, pullTimeoutMs);
|
|
481
|
+
} catch (err) {
|
|
482
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
483
|
+
await emit(opts.sink, "chat.error", opts.sessionId, "error", { error: `pull failed: ${msg}` });
|
|
484
|
+
return { exitCode: 99, turnsProcessed, reason: `pull failed: ${msg}` };
|
|
485
|
+
}
|
|
486
|
+
if (response.turns.length === 0) {
|
|
487
|
+
if (now() - lastActivityAt > idleTimeoutMs) {
|
|
488
|
+
await emit(opts.sink, "chat.done", opts.sessionId, "done", {
|
|
489
|
+
sessionId: opts.sessionId,
|
|
490
|
+
reason: "idle-timeout"
|
|
491
|
+
});
|
|
492
|
+
return { exitCode: 0, turnsProcessed, reason: "idle-timeout" };
|
|
493
|
+
}
|
|
494
|
+
continue;
|
|
495
|
+
}
|
|
496
|
+
const newUserTurns = response.turns.filter((t) => t.role === "user");
|
|
497
|
+
for (const t of newUserTurns) history.push(t);
|
|
498
|
+
since = response.nextSince;
|
|
499
|
+
if (newUserTurns.length === 0) continue;
|
|
500
|
+
lastActivityAt = now();
|
|
501
|
+
const prompt = buildPrompt(history, systemPrompt);
|
|
502
|
+
let result;
|
|
503
|
+
try {
|
|
504
|
+
result = await invoke(prompt);
|
|
505
|
+
} catch (err) {
|
|
506
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
507
|
+
await emit(opts.sink, "chat.error", opts.sessionId, "error", { error: msg });
|
|
508
|
+
return { exitCode: 99, turnsProcessed, reason: msg };
|
|
509
|
+
}
|
|
510
|
+
if (result.outcome !== "completed") {
|
|
511
|
+
const error = result.error ?? "agent did not complete";
|
|
512
|
+
await emit(opts.sink, "chat.error", opts.sessionId, "error", { error });
|
|
513
|
+
return { exitCode: 99, turnsProcessed, reason: error };
|
|
514
|
+
}
|
|
515
|
+
const reply = result.finalText.trim();
|
|
516
|
+
const replyTimestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
517
|
+
history.push({ role: "assistant", content: reply, timestamp: replyTimestamp });
|
|
518
|
+
turnsProcessed++;
|
|
519
|
+
lastActivityAt = now();
|
|
520
|
+
await emit(opts.sink, "chat.message", opts.sessionId, `message-${turnsProcessed}`, {
|
|
521
|
+
sessionId: opts.sessionId,
|
|
522
|
+
role: "assistant",
|
|
523
|
+
content: reply,
|
|
524
|
+
timestamp: replyTimestamp
|
|
525
|
+
});
|
|
492
526
|
}
|
|
493
|
-
const reply = result.finalText.trim();
|
|
494
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
495
|
-
appendTurn(opts.sessionFile, {
|
|
496
|
-
role: "assistant",
|
|
497
|
-
content: reply,
|
|
498
|
-
timestamp: now
|
|
499
|
-
});
|
|
500
|
-
await emit(opts.sink, "chat.message", opts.sessionId, "message", {
|
|
501
|
-
sessionId: opts.sessionId,
|
|
502
|
-
role: "assistant",
|
|
503
|
-
content: reply,
|
|
504
|
-
timestamp: now
|
|
505
|
-
});
|
|
506
|
-
await emit(opts.sink, "chat.done", opts.sessionId, "done", { sessionId: opts.sessionId });
|
|
507
|
-
return { exitCode: 0, reply };
|
|
508
527
|
}
|
|
509
528
|
function buildPrompt(turns, systemPrompt) {
|
|
510
529
|
const header = `System: ${systemPrompt}`;
|
|
@@ -526,11 +545,11 @@ async function emit(sink, type, sessionId, suffix, payload) {
|
|
|
526
545
|
|
|
527
546
|
// src/kody2-cli.ts
|
|
528
547
|
import { execFileSync as execFileSync15 } from "child_process";
|
|
529
|
-
import * as
|
|
530
|
-
import * as
|
|
548
|
+
import * as fs16 from "fs";
|
|
549
|
+
import * as path13 from "path";
|
|
531
550
|
|
|
532
551
|
// src/dispatch.ts
|
|
533
|
-
import * as
|
|
552
|
+
import * as fs3 from "fs";
|
|
534
553
|
function autoDispatch(opts) {
|
|
535
554
|
const explicit = opts?.explicit;
|
|
536
555
|
if (explicit?.issueNumber && explicit.issueNumber > 0) {
|
|
@@ -542,10 +561,10 @@ function autoDispatch(opts) {
|
|
|
542
561
|
}
|
|
543
562
|
const eventName = process.env.GITHUB_EVENT_NAME;
|
|
544
563
|
const eventPath = process.env.GITHUB_EVENT_PATH;
|
|
545
|
-
if (!eventName || !eventPath || !
|
|
564
|
+
if (!eventName || !eventPath || !fs3.existsSync(eventPath)) return null;
|
|
546
565
|
let event = {};
|
|
547
566
|
try {
|
|
548
|
-
event = JSON.parse(
|
|
567
|
+
event = JSON.parse(fs3.readFileSync(eventPath, "utf-8"));
|
|
549
568
|
} catch {
|
|
550
569
|
return null;
|
|
551
570
|
}
|
|
@@ -614,14 +633,14 @@ function extractFeedback(afterTag) {
|
|
|
614
633
|
}
|
|
615
634
|
|
|
616
635
|
// src/executor.ts
|
|
617
|
-
import * as
|
|
618
|
-
import * as
|
|
636
|
+
import * as fs15 from "fs";
|
|
637
|
+
import * as path12 from "path";
|
|
619
638
|
|
|
620
639
|
// src/litellm.ts
|
|
621
640
|
import { execFileSync, spawn } from "child_process";
|
|
622
|
-
import * as
|
|
641
|
+
import * as fs4 from "fs";
|
|
623
642
|
import * as os from "os";
|
|
624
|
-
import * as
|
|
643
|
+
import * as path3 from "path";
|
|
625
644
|
async function checkLitellmHealth(url) {
|
|
626
645
|
try {
|
|
627
646
|
const response = await fetch(`${url}/health`, { signal: AbortSignal.timeout(3e3) });
|
|
@@ -661,20 +680,20 @@ async function startLitellmIfNeeded(model, projectDir, url = LITELLM_DEFAULT_URL
|
|
|
661
680
|
throw new Error("litellm not installed \u2014 run: pip install 'litellm[proxy]'");
|
|
662
681
|
}
|
|
663
682
|
}
|
|
664
|
-
const configPath =
|
|
665
|
-
|
|
683
|
+
const configPath = path3.join(os.tmpdir(), `kody2-litellm-${Date.now()}.yaml`);
|
|
684
|
+
fs4.writeFileSync(configPath, generateLitellmConfigYaml(model));
|
|
666
685
|
const portMatch = url.match(/:(\d+)/);
|
|
667
686
|
const port = portMatch ? portMatch[1] : "4000";
|
|
668
687
|
const args = cmd === "litellm" ? ["--config", configPath, "--port", port] : ["-m", "litellm", "--config", configPath, "--port", port];
|
|
669
688
|
const dotenvVars = readDotenvApiKeys(projectDir);
|
|
670
|
-
const logPath =
|
|
671
|
-
const outFd =
|
|
689
|
+
const logPath = path3.join(os.tmpdir(), `kody2-litellm-${Date.now()}.log`);
|
|
690
|
+
const outFd = fs4.openSync(logPath, "w");
|
|
672
691
|
const child = spawn(cmd, args, {
|
|
673
692
|
stdio: ["ignore", outFd, outFd],
|
|
674
693
|
detached: true,
|
|
675
694
|
env: stripBlockingEnv({ ...process.env, ...dotenvVars })
|
|
676
695
|
});
|
|
677
|
-
|
|
696
|
+
fs4.closeSync(outFd);
|
|
678
697
|
for (let i = 0; i < 30; i++) {
|
|
679
698
|
await new Promise((r) => setTimeout(r, 2e3));
|
|
680
699
|
if (await checkLitellmHealth(url)) {
|
|
@@ -691,7 +710,7 @@ async function startLitellmIfNeeded(model, projectDir, url = LITELLM_DEFAULT_URL
|
|
|
691
710
|
}
|
|
692
711
|
let logTail = "";
|
|
693
712
|
try {
|
|
694
|
-
logTail =
|
|
713
|
+
logTail = fs4.readFileSync(logPath, "utf-8").slice(-2e3);
|
|
695
714
|
} catch {
|
|
696
715
|
}
|
|
697
716
|
try {
|
|
@@ -702,10 +721,10 @@ async function startLitellmIfNeeded(model, projectDir, url = LITELLM_DEFAULT_URL
|
|
|
702
721
|
${logTail}`);
|
|
703
722
|
}
|
|
704
723
|
function readDotenvApiKeys(projectDir) {
|
|
705
|
-
const dotenvPath =
|
|
706
|
-
if (!
|
|
724
|
+
const dotenvPath = path3.join(projectDir, ".env");
|
|
725
|
+
if (!fs4.existsSync(dotenvPath)) return {};
|
|
707
726
|
const result = {};
|
|
708
|
-
for (const rawLine of
|
|
727
|
+
for (const rawLine of fs4.readFileSync(dotenvPath, "utf-8").split("\n")) {
|
|
709
728
|
const line = rawLine.trim();
|
|
710
729
|
if (!line || line.startsWith("#")) continue;
|
|
711
730
|
const match = line.match(/^([A-Z_][A-Z0-9_]*_API_KEY)=(.*)$/);
|
|
@@ -728,8 +747,8 @@ function stripBlockingEnv(env) {
|
|
|
728
747
|
}
|
|
729
748
|
|
|
730
749
|
// src/profile.ts
|
|
731
|
-
import * as
|
|
732
|
-
import * as
|
|
750
|
+
import * as fs5 from "fs";
|
|
751
|
+
import * as path4 from "path";
|
|
733
752
|
var VALID_INPUT_TYPES = /* @__PURE__ */ new Set(["int", "string", "bool", "enum"]);
|
|
734
753
|
var VALID_PERMISSION_MODES = /* @__PURE__ */ new Set(["default", "acceptEdits", "plan", "bypassPermissions"]);
|
|
735
754
|
var ProfileError = class extends Error {
|
|
@@ -742,12 +761,12 @@ var ProfileError = class extends Error {
|
|
|
742
761
|
profilePath;
|
|
743
762
|
};
|
|
744
763
|
function loadProfile(profilePath) {
|
|
745
|
-
if (!
|
|
764
|
+
if (!fs5.existsSync(profilePath)) {
|
|
746
765
|
throw new ProfileError(profilePath, "file not found");
|
|
747
766
|
}
|
|
748
767
|
let raw;
|
|
749
768
|
try {
|
|
750
|
-
raw = JSON.parse(
|
|
769
|
+
raw = JSON.parse(fs5.readFileSync(profilePath, "utf-8"));
|
|
751
770
|
} catch (err) {
|
|
752
771
|
throw new ProfileError(profilePath, `invalid JSON: ${err instanceof Error ? err.message : String(err)}`);
|
|
753
772
|
}
|
|
@@ -771,7 +790,7 @@ function loadProfile(profilePath) {
|
|
|
771
790
|
outputContract: r.outputContract,
|
|
772
791
|
inputArtifacts: parseInputArtifacts(profilePath, r.input),
|
|
773
792
|
outputArtifacts: parseOutputArtifacts(profilePath, r.output),
|
|
774
|
-
dir:
|
|
793
|
+
dir: path4.dirname(profilePath)
|
|
775
794
|
};
|
|
776
795
|
return profile;
|
|
777
796
|
}
|
|
@@ -837,6 +856,7 @@ function parseClaudeCode(p, raw) {
|
|
|
837
856
|
model: typeof r.model === "string" ? r.model : "inherit",
|
|
838
857
|
permissionMode,
|
|
839
858
|
maxTurns: typeof r.maxTurns === "number" ? r.maxTurns : null,
|
|
859
|
+
maxThinkingTokens: typeof r.maxThinkingTokens === "number" ? r.maxThinkingTokens : null,
|
|
840
860
|
systemPromptAppend: typeof r.systemPromptAppend === "string" ? r.systemPromptAppend : null,
|
|
841
861
|
tools,
|
|
842
862
|
hooks: Array.isArray(r.hooks) ? r.hooks : [],
|
|
@@ -950,21 +970,21 @@ function parseScriptList(p, key, raw) {
|
|
|
950
970
|
}
|
|
951
971
|
|
|
952
972
|
// src/scripts/buildSyntheticPlugin.ts
|
|
953
|
-
import * as
|
|
973
|
+
import * as fs6 from "fs";
|
|
954
974
|
import * as os2 from "os";
|
|
955
|
-
import * as
|
|
975
|
+
import * as path5 from "path";
|
|
956
976
|
function getPluginsCatalogRoot() {
|
|
957
|
-
const here =
|
|
977
|
+
const here = path5.dirname(new URL(import.meta.url).pathname);
|
|
958
978
|
const candidates = [
|
|
959
|
-
|
|
979
|
+
path5.join(here, "..", "plugins"),
|
|
960
980
|
// dev: src/scripts → src/plugins
|
|
961
|
-
|
|
981
|
+
path5.join(here, "..", "..", "plugins"),
|
|
962
982
|
// built: dist/scripts → dist/plugins
|
|
963
|
-
|
|
983
|
+
path5.join(here, "..", "..", "src", "plugins")
|
|
964
984
|
// fallback
|
|
965
985
|
];
|
|
966
986
|
for (const c of candidates) {
|
|
967
|
-
if (
|
|
987
|
+
if (fs6.existsSync(c) && fs6.statSync(c).isDirectory()) return c;
|
|
968
988
|
}
|
|
969
989
|
return candidates[0];
|
|
970
990
|
}
|
|
@@ -974,50 +994,50 @@ var buildSyntheticPlugin = async (ctx, profile) => {
|
|
|
974
994
|
if (!needsSynthetic) return;
|
|
975
995
|
const catalog = getPluginsCatalogRoot();
|
|
976
996
|
const runId = `${profile.name}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
977
|
-
const root =
|
|
978
|
-
|
|
997
|
+
const root = path5.join(os2.tmpdir(), `kody2-synth-${runId}`);
|
|
998
|
+
fs6.mkdirSync(path5.join(root, ".claude-plugin"), { recursive: true });
|
|
979
999
|
if (cc.skills.length > 0) {
|
|
980
|
-
const dst =
|
|
981
|
-
|
|
1000
|
+
const dst = path5.join(root, "skills");
|
|
1001
|
+
fs6.mkdirSync(dst, { recursive: true });
|
|
982
1002
|
for (const name of cc.skills) {
|
|
983
|
-
const src =
|
|
984
|
-
if (!
|
|
985
|
-
copyDir(src,
|
|
1003
|
+
const src = path5.join(catalog, "skills", name);
|
|
1004
|
+
if (!fs6.existsSync(src)) throw new Error(`buildSyntheticPlugin: skill not found in catalog: ${name}`);
|
|
1005
|
+
copyDir(src, path5.join(dst, name));
|
|
986
1006
|
}
|
|
987
1007
|
}
|
|
988
1008
|
if (cc.commands.length > 0) {
|
|
989
|
-
const dst =
|
|
990
|
-
|
|
1009
|
+
const dst = path5.join(root, "commands");
|
|
1010
|
+
fs6.mkdirSync(dst, { recursive: true });
|
|
991
1011
|
for (const name of cc.commands) {
|
|
992
|
-
const src =
|
|
993
|
-
if (!
|
|
994
|
-
|
|
1012
|
+
const src = path5.join(catalog, "commands", `${name}.md`);
|
|
1013
|
+
if (!fs6.existsSync(src)) throw new Error(`buildSyntheticPlugin: command not found in catalog: ${name}`);
|
|
1014
|
+
fs6.copyFileSync(src, path5.join(dst, `${name}.md`));
|
|
995
1015
|
}
|
|
996
1016
|
}
|
|
997
1017
|
if (cc.subagents.length > 0) {
|
|
998
|
-
const dst =
|
|
999
|
-
|
|
1018
|
+
const dst = path5.join(root, "agents");
|
|
1019
|
+
fs6.mkdirSync(dst, { recursive: true });
|
|
1000
1020
|
for (const name of cc.subagents) {
|
|
1001
|
-
const src =
|
|
1002
|
-
if (!
|
|
1003
|
-
|
|
1021
|
+
const src = path5.join(catalog, "agents", `${name}.md`);
|
|
1022
|
+
if (!fs6.existsSync(src)) throw new Error(`buildSyntheticPlugin: subagent not found in catalog: ${name}`);
|
|
1023
|
+
fs6.copyFileSync(src, path5.join(dst, `${name}.md`));
|
|
1004
1024
|
}
|
|
1005
1025
|
}
|
|
1006
1026
|
if (cc.hooks.length > 0) {
|
|
1007
|
-
const dst =
|
|
1008
|
-
|
|
1027
|
+
const dst = path5.join(root, "hooks");
|
|
1028
|
+
fs6.mkdirSync(dst, { recursive: true });
|
|
1009
1029
|
const merged = { hooks: {} };
|
|
1010
1030
|
for (const name of cc.hooks) {
|
|
1011
|
-
const src =
|
|
1012
|
-
if (!
|
|
1013
|
-
const parsed = JSON.parse(
|
|
1031
|
+
const src = path5.join(catalog, "hooks", `${name}.json`);
|
|
1032
|
+
if (!fs6.existsSync(src)) throw new Error(`buildSyntheticPlugin: hook not found in catalog: ${name}`);
|
|
1033
|
+
const parsed = JSON.parse(fs6.readFileSync(src, "utf-8"));
|
|
1014
1034
|
for (const [event, entries] of Object.entries(parsed.hooks ?? {})) {
|
|
1015
1035
|
if (!Array.isArray(entries)) continue;
|
|
1016
1036
|
if (!merged.hooks[event]) merged.hooks[event] = [];
|
|
1017
1037
|
merged.hooks[event].push(...entries);
|
|
1018
1038
|
}
|
|
1019
1039
|
}
|
|
1020
|
-
|
|
1040
|
+
fs6.writeFileSync(path5.join(dst, "hooks.json"), `${JSON.stringify(merged, null, 2)}
|
|
1021
1041
|
`);
|
|
1022
1042
|
}
|
|
1023
1043
|
const manifest = {
|
|
@@ -1028,17 +1048,17 @@ var buildSyntheticPlugin = async (ctx, profile) => {
|
|
|
1028
1048
|
if (cc.skills.length > 0) manifest.skills = ["./skills/"];
|
|
1029
1049
|
if (cc.commands.length > 0) manifest.commands = ["./commands/"];
|
|
1030
1050
|
if (cc.subagents.length > 0) manifest.agents = cc.subagents.map((n) => `./agents/${n}.md`);
|
|
1031
|
-
|
|
1051
|
+
fs6.writeFileSync(path5.join(root, ".claude-plugin", "plugin.json"), `${JSON.stringify(manifest, null, 2)}
|
|
1032
1052
|
`);
|
|
1033
1053
|
ctx.data.syntheticPluginPath = root;
|
|
1034
1054
|
};
|
|
1035
1055
|
function copyDir(src, dst) {
|
|
1036
|
-
|
|
1037
|
-
for (const ent of
|
|
1038
|
-
const s =
|
|
1039
|
-
const d =
|
|
1056
|
+
fs6.mkdirSync(dst, { recursive: true });
|
|
1057
|
+
for (const ent of fs6.readdirSync(src, { withFileTypes: true })) {
|
|
1058
|
+
const s = path5.join(src, ent.name);
|
|
1059
|
+
const d = path5.join(dst, ent.name);
|
|
1040
1060
|
if (ent.isDirectory()) copyDir(s, d);
|
|
1041
|
-
else if (ent.isFile())
|
|
1061
|
+
else if (ent.isFile()) fs6.copyFileSync(s, d);
|
|
1042
1062
|
}
|
|
1043
1063
|
}
|
|
1044
1064
|
|
|
@@ -1104,18 +1124,18 @@ function formatMissesForFeedback(misses) {
|
|
|
1104
1124
|
}
|
|
1105
1125
|
|
|
1106
1126
|
// src/prompt.ts
|
|
1107
|
-
import * as
|
|
1108
|
-
import * as
|
|
1127
|
+
import * as fs7 from "fs";
|
|
1128
|
+
import * as path6 from "path";
|
|
1109
1129
|
var CONVENTIONS_PER_FILE_MAX_BYTES = 3e4;
|
|
1110
1130
|
var CONVENTION_FILES = ["CLAUDE.md", "AGENTS.md"];
|
|
1111
1131
|
function loadProjectConventions(projectDir) {
|
|
1112
1132
|
const out = [];
|
|
1113
1133
|
for (const rel of CONVENTION_FILES) {
|
|
1114
|
-
const abs =
|
|
1115
|
-
if (!
|
|
1134
|
+
const abs = path6.join(projectDir, rel);
|
|
1135
|
+
if (!fs7.existsSync(abs)) continue;
|
|
1116
1136
|
let content;
|
|
1117
1137
|
try {
|
|
1118
|
-
content =
|
|
1138
|
+
content = fs7.readFileSync(abs, "utf-8");
|
|
1119
1139
|
} catch {
|
|
1120
1140
|
continue;
|
|
1121
1141
|
}
|
|
@@ -1236,8 +1256,8 @@ import { execFileSync as execFileSync4 } from "child_process";
|
|
|
1236
1256
|
|
|
1237
1257
|
// src/commit.ts
|
|
1238
1258
|
import { execFileSync as execFileSync3 } from "child_process";
|
|
1239
|
-
import * as
|
|
1240
|
-
import * as
|
|
1259
|
+
import * as fs8 from "fs";
|
|
1260
|
+
import * as path7 from "path";
|
|
1241
1261
|
var FORBIDDEN_PATH_PREFIXES = [
|
|
1242
1262
|
".kody/",
|
|
1243
1263
|
".kody-engine/",
|
|
@@ -1292,18 +1312,18 @@ function tryGit(args, cwd) {
|
|
|
1292
1312
|
}
|
|
1293
1313
|
function abortUnfinishedGitOps(cwd) {
|
|
1294
1314
|
const aborted = [];
|
|
1295
|
-
const gitDir =
|
|
1296
|
-
if (!
|
|
1297
|
-
if (
|
|
1315
|
+
const gitDir = path7.join(cwd ?? process.cwd(), ".git");
|
|
1316
|
+
if (!fs8.existsSync(gitDir)) return aborted;
|
|
1317
|
+
if (fs8.existsSync(path7.join(gitDir, "MERGE_HEAD"))) {
|
|
1298
1318
|
if (tryGit(["merge", "--abort"], cwd)) aborted.push("merge");
|
|
1299
1319
|
}
|
|
1300
|
-
if (
|
|
1320
|
+
if (fs8.existsSync(path7.join(gitDir, "CHERRY_PICK_HEAD"))) {
|
|
1301
1321
|
if (tryGit(["cherry-pick", "--abort"], cwd)) aborted.push("cherry-pick");
|
|
1302
1322
|
}
|
|
1303
|
-
if (
|
|
1323
|
+
if (fs8.existsSync(path7.join(gitDir, "REVERT_HEAD"))) {
|
|
1304
1324
|
if (tryGit(["revert", "--abort"], cwd)) aborted.push("revert");
|
|
1305
1325
|
}
|
|
1306
|
-
if (
|
|
1326
|
+
if (fs8.existsSync(path7.join(gitDir, "rebase-merge")) || fs8.existsSync(path7.join(gitDir, "rebase-apply"))) {
|
|
1307
1327
|
if (tryGit(["rebase", "--abort"], cwd)) aborted.push("rebase");
|
|
1308
1328
|
}
|
|
1309
1329
|
try {
|
|
@@ -1345,7 +1365,7 @@ function normalizeCommitMessage(raw) {
|
|
|
1345
1365
|
function commitAndPush(branch, agentMessage, cwd) {
|
|
1346
1366
|
const allChanged = listChangedFiles(cwd);
|
|
1347
1367
|
const allowedFiles = allChanged.filter((f) => !isForbiddenPath(f));
|
|
1348
|
-
const mergeHeadExists =
|
|
1368
|
+
const mergeHeadExists = fs8.existsSync(path7.join(cwd ?? process.cwd(), ".git", "MERGE_HEAD"));
|
|
1349
1369
|
if (allowedFiles.length === 0 && !mergeHeadExists) {
|
|
1350
1370
|
return { committed: false, pushed: false, sha: "", message: "" };
|
|
1351
1371
|
}
|
|
@@ -1394,6 +1414,11 @@ var commitAndPush2 = async (ctx, profile) => {
|
|
|
1394
1414
|
ctx.data.commitResult = { committed: false, pushed: false };
|
|
1395
1415
|
return;
|
|
1396
1416
|
}
|
|
1417
|
+
if (ctx.data.agentDone === false) {
|
|
1418
|
+
ctx.data.commitResult = { committed: false, pushed: false, skippedReason: "agentDone=false" };
|
|
1419
|
+
ctx.data.hasCommitsAhead = hasCommitsAhead(branch, ctx.config.git.defaultBranch, ctx.cwd);
|
|
1420
|
+
return;
|
|
1421
|
+
}
|
|
1397
1422
|
const kind = profile.name;
|
|
1398
1423
|
if (kind === "resolve") {
|
|
1399
1424
|
try {
|
|
@@ -1435,20 +1460,20 @@ function defaultCommitMessage(mode, data) {
|
|
|
1435
1460
|
}
|
|
1436
1461
|
|
|
1437
1462
|
// src/scripts/composePrompt.ts
|
|
1438
|
-
import * as
|
|
1439
|
-
import * as
|
|
1463
|
+
import * as fs9 from "fs";
|
|
1464
|
+
import * as path8 from "path";
|
|
1440
1465
|
var MUSTACHE = /\{\{\s*([a-zA-Z0-9_.-]+)\s*\}\}/g;
|
|
1441
1466
|
var composePrompt = async (ctx, profile) => {
|
|
1442
1467
|
const explicit = ctx.data.promptTemplate;
|
|
1443
1468
|
const mode = ctx.args.mode;
|
|
1444
1469
|
const candidates = [
|
|
1445
|
-
explicit ?
|
|
1446
|
-
mode ?
|
|
1447
|
-
|
|
1470
|
+
explicit ? path8.join(profile.dir, explicit) : null,
|
|
1471
|
+
mode ? path8.join(profile.dir, "prompts", `${mode}.md`) : null,
|
|
1472
|
+
path8.join(profile.dir, "prompt.md")
|
|
1448
1473
|
].filter(Boolean);
|
|
1449
1474
|
let templatePath = "";
|
|
1450
1475
|
for (const c of candidates) {
|
|
1451
|
-
if (
|
|
1476
|
+
if (fs9.existsSync(c)) {
|
|
1452
1477
|
templatePath = c;
|
|
1453
1478
|
break;
|
|
1454
1479
|
}
|
|
@@ -1456,7 +1481,7 @@ var composePrompt = async (ctx, profile) => {
|
|
|
1456
1481
|
if (!templatePath) {
|
|
1457
1482
|
throw new Error(`profile at ${profile.dir}: no prompt template found (tried ${candidates.join(", ")})`);
|
|
1458
1483
|
}
|
|
1459
|
-
const template =
|
|
1484
|
+
const template = fs9.readFileSync(templatePath, "utf-8");
|
|
1460
1485
|
const tokens = {
|
|
1461
1486
|
...stringifyAll(ctx.args, "args."),
|
|
1462
1487
|
...stringifyAll(ctx.data, ""),
|
|
@@ -1931,7 +1956,7 @@ function ensureFeatureBranch(issueNumber, title, defaultBranch, cwd) {
|
|
|
1931
1956
|
|
|
1932
1957
|
// src/gha.ts
|
|
1933
1958
|
import { execFileSync as execFileSync7 } from "child_process";
|
|
1934
|
-
import * as
|
|
1959
|
+
import * as fs10 from "fs";
|
|
1935
1960
|
function getRunUrl() {
|
|
1936
1961
|
const server = process.env.GITHUB_SERVER_URL;
|
|
1937
1962
|
const repo = process.env.GITHUB_REPOSITORY;
|
|
@@ -1942,10 +1967,10 @@ function getRunUrl() {
|
|
|
1942
1967
|
function reactToTriggerComment(cwd) {
|
|
1943
1968
|
if (process.env.GITHUB_EVENT_NAME !== "issue_comment") return;
|
|
1944
1969
|
const eventPath = process.env.GITHUB_EVENT_PATH;
|
|
1945
|
-
if (!eventPath || !
|
|
1970
|
+
if (!eventPath || !fs10.existsSync(eventPath)) return;
|
|
1946
1971
|
let event = null;
|
|
1947
1972
|
try {
|
|
1948
|
-
event = JSON.parse(
|
|
1973
|
+
event = JSON.parse(fs10.readFileSync(eventPath, "utf-8"));
|
|
1949
1974
|
} catch {
|
|
1950
1975
|
return;
|
|
1951
1976
|
}
|
|
@@ -2171,35 +2196,35 @@ function tryPostPr2(prNumber, body, cwd) {
|
|
|
2171
2196
|
|
|
2172
2197
|
// src/scripts/initFlow.ts
|
|
2173
2198
|
import { execFileSync as execFileSync9 } from "child_process";
|
|
2174
|
-
import * as
|
|
2175
|
-
import * as
|
|
2199
|
+
import * as fs12 from "fs";
|
|
2200
|
+
import * as path10 from "path";
|
|
2176
2201
|
|
|
2177
2202
|
// src/registry.ts
|
|
2178
|
-
import * as
|
|
2179
|
-
import * as
|
|
2203
|
+
import * as fs11 from "fs";
|
|
2204
|
+
import * as path9 from "path";
|
|
2180
2205
|
function getExecutablesRoot() {
|
|
2181
|
-
const here =
|
|
2206
|
+
const here = path9.dirname(new URL(import.meta.url).pathname);
|
|
2182
2207
|
const candidates = [
|
|
2183
|
-
|
|
2208
|
+
path9.join(here, "executables"),
|
|
2184
2209
|
// dev: src/
|
|
2185
|
-
|
|
2210
|
+
path9.join(here, "..", "executables"),
|
|
2186
2211
|
// built: dist/bin → dist/executables
|
|
2187
|
-
|
|
2212
|
+
path9.join(here, "..", "src", "executables")
|
|
2188
2213
|
// fallback
|
|
2189
2214
|
];
|
|
2190
2215
|
for (const c of candidates) {
|
|
2191
|
-
if (
|
|
2216
|
+
if (fs11.existsSync(c) && fs11.statSync(c).isDirectory()) return c;
|
|
2192
2217
|
}
|
|
2193
2218
|
return candidates[0];
|
|
2194
2219
|
}
|
|
2195
2220
|
function listExecutables(root = getExecutablesRoot()) {
|
|
2196
|
-
if (!
|
|
2197
|
-
const entries =
|
|
2221
|
+
if (!fs11.existsSync(root)) return [];
|
|
2222
|
+
const entries = fs11.readdirSync(root, { withFileTypes: true });
|
|
2198
2223
|
const out = [];
|
|
2199
2224
|
for (const ent of entries) {
|
|
2200
2225
|
if (!ent.isDirectory()) continue;
|
|
2201
|
-
const profilePath =
|
|
2202
|
-
if (
|
|
2226
|
+
const profilePath = path9.join(root, ent.name, "profile.json");
|
|
2227
|
+
if (fs11.existsSync(profilePath) && fs11.statSync(profilePath).isFile()) {
|
|
2203
2228
|
out.push({ name: ent.name, profilePath });
|
|
2204
2229
|
}
|
|
2205
2230
|
}
|
|
@@ -2207,8 +2232,8 @@ function listExecutables(root = getExecutablesRoot()) {
|
|
|
2207
2232
|
}
|
|
2208
2233
|
function hasExecutable(name, root = getExecutablesRoot()) {
|
|
2209
2234
|
if (!isSafeName(name)) return false;
|
|
2210
|
-
const profilePath =
|
|
2211
|
-
return
|
|
2235
|
+
const profilePath = path9.join(root, name, "profile.json");
|
|
2236
|
+
return fs11.existsSync(profilePath) && fs11.statSync(profilePath).isFile();
|
|
2212
2237
|
}
|
|
2213
2238
|
function isSafeName(name) {
|
|
2214
2239
|
return /^[a-z][a-z0-9-]*$/.test(name) && !name.includes("..");
|
|
@@ -2237,9 +2262,9 @@ function parseGenericFlags(argv) {
|
|
|
2237
2262
|
|
|
2238
2263
|
// src/scripts/initFlow.ts
|
|
2239
2264
|
function detectPackageManager(cwd) {
|
|
2240
|
-
if (
|
|
2241
|
-
if (
|
|
2242
|
-
if (
|
|
2265
|
+
if (fs12.existsSync(path10.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
|
|
2266
|
+
if (fs12.existsSync(path10.join(cwd, "yarn.lock"))) return "yarn";
|
|
2267
|
+
if (fs12.existsSync(path10.join(cwd, "bun.lockb"))) return "bun";
|
|
2243
2268
|
return "npm";
|
|
2244
2269
|
}
|
|
2245
2270
|
function qualityCommandsFor(pm) {
|
|
@@ -2360,22 +2385,22 @@ function performInit(cwd, force) {
|
|
|
2360
2385
|
const pm = detectPackageManager(cwd);
|
|
2361
2386
|
const ownerRepo = detectOwnerRepo(cwd);
|
|
2362
2387
|
const defaultBranch = defaultBranchFromGit(cwd);
|
|
2363
|
-
const configPath =
|
|
2364
|
-
if (
|
|
2388
|
+
const configPath = path10.join(cwd, "kody.config.json");
|
|
2389
|
+
if (fs12.existsSync(configPath) && !force) {
|
|
2365
2390
|
skipped.push("kody.config.json");
|
|
2366
2391
|
} else {
|
|
2367
2392
|
const cfg = makeConfig(pm, ownerRepo, defaultBranch);
|
|
2368
|
-
|
|
2393
|
+
fs12.writeFileSync(configPath, `${JSON.stringify(cfg, null, 2)}
|
|
2369
2394
|
`);
|
|
2370
2395
|
wrote.push("kody.config.json");
|
|
2371
2396
|
}
|
|
2372
|
-
const workflowDir =
|
|
2373
|
-
const workflowPath =
|
|
2374
|
-
if (
|
|
2397
|
+
const workflowDir = path10.join(cwd, ".github", "workflows");
|
|
2398
|
+
const workflowPath = path10.join(workflowDir, "kody2.yml");
|
|
2399
|
+
if (fs12.existsSync(workflowPath) && !force) {
|
|
2375
2400
|
skipped.push(".github/workflows/kody2.yml");
|
|
2376
2401
|
} else {
|
|
2377
|
-
|
|
2378
|
-
|
|
2402
|
+
fs12.mkdirSync(workflowDir, { recursive: true });
|
|
2403
|
+
fs12.writeFileSync(workflowPath, WORKFLOW_TEMPLATE);
|
|
2379
2404
|
wrote.push(".github/workflows/kody2.yml");
|
|
2380
2405
|
}
|
|
2381
2406
|
for (const exe of listExecutables()) {
|
|
@@ -2386,12 +2411,12 @@ function performInit(cwd, force) {
|
|
|
2386
2411
|
continue;
|
|
2387
2412
|
}
|
|
2388
2413
|
if (profile.kind !== "scheduled" || !profile.schedule) continue;
|
|
2389
|
-
const target =
|
|
2390
|
-
if (
|
|
2414
|
+
const target = path10.join(workflowDir, `kody2-${exe.name}.yml`);
|
|
2415
|
+
if (fs12.existsSync(target) && !force) {
|
|
2391
2416
|
skipped.push(`.github/workflows/kody2-${exe.name}.yml`);
|
|
2392
2417
|
continue;
|
|
2393
2418
|
}
|
|
2394
|
-
|
|
2419
|
+
fs12.writeFileSync(target, renderScheduledWorkflow(exe.name, profile.schedule));
|
|
2395
2420
|
wrote.push(`.github/workflows/kody2-${exe.name}.yml`);
|
|
2396
2421
|
}
|
|
2397
2422
|
return { wrote, skipped };
|
|
@@ -2898,8 +2923,8 @@ REVIEW_POSTED=https://github.com/${ctx.config.github.owner}/${ctx.config.github.
|
|
|
2898
2923
|
|
|
2899
2924
|
// src/scripts/releaseFlow.ts
|
|
2900
2925
|
import { execFileSync as execFileSync11, spawnSync } from "child_process";
|
|
2901
|
-
import * as
|
|
2902
|
-
import * as
|
|
2926
|
+
import * as fs13 from "fs";
|
|
2927
|
+
import * as path11 from "path";
|
|
2903
2928
|
function bumpVersion(current, bump) {
|
|
2904
2929
|
const m = current.match(/^(\d+)\.(\d+)\.(\d+)(.*)$/);
|
|
2905
2930
|
if (!m) throw new Error(`cannot parse version '${current}' (expected x.y.z[-suffix])`);
|
|
@@ -2915,12 +2940,12 @@ function bumpVersion(current, bump) {
|
|
|
2915
2940
|
return `${major}.${minor}.${patch}`;
|
|
2916
2941
|
}
|
|
2917
2942
|
function updateVersionInFile(file, newVersion, cwd) {
|
|
2918
|
-
const abs =
|
|
2919
|
-
if (!
|
|
2920
|
-
const content =
|
|
2943
|
+
const abs = path11.join(cwd, file);
|
|
2944
|
+
if (!fs13.existsSync(abs)) return false;
|
|
2945
|
+
const content = fs13.readFileSync(abs, "utf-8");
|
|
2921
2946
|
const updated = content.replace(/"version"\s*:\s*"[^"]+"/, `"version": "${newVersion}"`);
|
|
2922
2947
|
if (updated === content) return false;
|
|
2923
|
-
|
|
2948
|
+
fs13.writeFileSync(abs, updated);
|
|
2924
2949
|
return true;
|
|
2925
2950
|
}
|
|
2926
2951
|
function generateChangelog(cwd, newVersion, lastTag) {
|
|
@@ -2968,19 +2993,19 @@ function generateChangelog(cwd, newVersion, lastTag) {
|
|
|
2968
2993
|
return parts.join("\n");
|
|
2969
2994
|
}
|
|
2970
2995
|
function prependChangelog(cwd, entry) {
|
|
2971
|
-
const p =
|
|
2996
|
+
const p = path11.join(cwd, "CHANGELOG.md");
|
|
2972
2997
|
const header = "# Changelog\n\nAll notable changes to this project will be documented in this file.\n\n";
|
|
2973
|
-
if (
|
|
2974
|
-
const prior =
|
|
2998
|
+
if (fs13.existsSync(p)) {
|
|
2999
|
+
const prior = fs13.readFileSync(p, "utf-8");
|
|
2975
3000
|
if (/^#\s*Changelog\b/m.test(prior)) {
|
|
2976
3001
|
const idx = prior.indexOf("\n", prior.indexOf("# Changelog"));
|
|
2977
|
-
|
|
3002
|
+
fs13.writeFileSync(p, `${prior.slice(0, idx + 1)}
|
|
2978
3003
|
${entry}${prior.slice(idx + 1)}`);
|
|
2979
3004
|
} else {
|
|
2980
|
-
|
|
3005
|
+
fs13.writeFileSync(p, `${header}${entry}${prior}`);
|
|
2981
3006
|
}
|
|
2982
3007
|
} else {
|
|
2983
|
-
|
|
3008
|
+
fs13.writeFileSync(p, `${header}${entry}`);
|
|
2984
3009
|
}
|
|
2985
3010
|
}
|
|
2986
3011
|
function git3(args, cwd, timeout = 6e4) {
|
|
@@ -3031,13 +3056,13 @@ var releaseFlow = async (ctx) => {
|
|
|
3031
3056
|
};
|
|
3032
3057
|
async function runPrepare(args) {
|
|
3033
3058
|
const { cwd, bump, dryRun, versionFiles, ctx } = args;
|
|
3034
|
-
const pkgPath =
|
|
3035
|
-
if (!
|
|
3059
|
+
const pkgPath = path11.join(cwd, "package.json");
|
|
3060
|
+
if (!fs13.existsSync(pkgPath)) {
|
|
3036
3061
|
ctx.output.exitCode = 99;
|
|
3037
3062
|
ctx.output.reason = "release prepare: package.json not found";
|
|
3038
3063
|
return;
|
|
3039
3064
|
}
|
|
3040
|
-
const pkg = JSON.parse(
|
|
3065
|
+
const pkg = JSON.parse(fs13.readFileSync(pkgPath, "utf-8"));
|
|
3041
3066
|
if (typeof pkg.version !== "string") {
|
|
3042
3067
|
ctx.output.exitCode = 99;
|
|
3043
3068
|
ctx.output.reason = "release prepare: package.json has no version";
|
|
@@ -3108,8 +3133,8 @@ Merge this and then run \`kody2 release --mode finalize\`.`;
|
|
|
3108
3133
|
}
|
|
3109
3134
|
async function runFinalize(args) {
|
|
3110
3135
|
const { cwd, dryRun, timeoutMs, releaseCfg, ctx } = args;
|
|
3111
|
-
const pkgPath =
|
|
3112
|
-
const pkg = JSON.parse(
|
|
3136
|
+
const pkgPath = path11.join(cwd, "package.json");
|
|
3137
|
+
const pkg = JSON.parse(fs13.readFileSync(pkgPath, "utf-8"));
|
|
3113
3138
|
if (typeof pkg.version !== "string") {
|
|
3114
3139
|
ctx.output.exitCode = 99;
|
|
3115
3140
|
ctx.output.reason = "release finalize: package.json has no version";
|
|
@@ -3787,7 +3812,7 @@ var watchStalePrsFlow = async (ctx) => {
|
|
|
3787
3812
|
};
|
|
3788
3813
|
|
|
3789
3814
|
// src/scripts/writeRunSummary.ts
|
|
3790
|
-
import * as
|
|
3815
|
+
import * as fs14 from "fs";
|
|
3791
3816
|
var writeRunSummary = async (ctx, profile) => {
|
|
3792
3817
|
const summaryPath = process.env.GITHUB_STEP_SUMMARY;
|
|
3793
3818
|
if (!summaryPath) return;
|
|
@@ -3809,7 +3834,7 @@ var writeRunSummary = async (ctx, profile) => {
|
|
|
3809
3834
|
if (reason) lines.push(`- **Reason:** ${reason}`);
|
|
3810
3835
|
lines.push("");
|
|
3811
3836
|
try {
|
|
3812
|
-
|
|
3837
|
+
fs14.appendFileSync(summaryPath, `${lines.join("\n")}
|
|
3813
3838
|
`);
|
|
3814
3839
|
} catch {
|
|
3815
3840
|
}
|
|
@@ -3957,9 +3982,9 @@ async function runExecutable(profileName, input) {
|
|
|
3957
3982
|
data: {},
|
|
3958
3983
|
output: { exitCode: 0 }
|
|
3959
3984
|
};
|
|
3960
|
-
const ndjsonDir =
|
|
3985
|
+
const ndjsonDir = path12.join(input.cwd, ".kody2");
|
|
3961
3986
|
const invokeAgent = async (prompt) => {
|
|
3962
|
-
const externalPlugins = (profile.claudeCode.plugins ?? []).map((p) =>
|
|
3987
|
+
const externalPlugins = (profile.claudeCode.plugins ?? []).map((p) => path12.isAbsolute(p) ? p : path12.resolve(profile.dir, p)).filter((p) => p.length > 0);
|
|
3963
3988
|
const syntheticPath = ctx.data.syntheticPluginPath;
|
|
3964
3989
|
const pluginPaths = [...externalPlugins, ...syntheticPath ? [syntheticPath] : []];
|
|
3965
3990
|
return runAgent({
|
|
@@ -3975,6 +4000,7 @@ async function runExecutable(profileName, input) {
|
|
|
3975
4000
|
mcpServers: profile.claudeCode.mcpServers,
|
|
3976
4001
|
pluginPaths: pluginPaths.length > 0 ? pluginPaths : void 0,
|
|
3977
4002
|
maxTurns: profile.claudeCode.maxTurns,
|
|
4003
|
+
maxThinkingTokens: profile.claudeCode.maxThinkingTokens,
|
|
3978
4004
|
systemPromptAppend: profile.claudeCode.systemPromptAppend,
|
|
3979
4005
|
settingSources: profile.claudeCode.settingSources
|
|
3980
4006
|
});
|
|
@@ -4025,17 +4051,17 @@ async function runExecutable(profileName, input) {
|
|
|
4025
4051
|
}
|
|
4026
4052
|
}
|
|
4027
4053
|
function resolveProfilePath(profileName) {
|
|
4028
|
-
const here =
|
|
4054
|
+
const here = path12.dirname(new URL(import.meta.url).pathname);
|
|
4029
4055
|
const candidates = [
|
|
4030
|
-
|
|
4056
|
+
path12.join(here, "executables", profileName, "profile.json"),
|
|
4031
4057
|
// same-dir sibling (dev)
|
|
4032
|
-
|
|
4058
|
+
path12.join(here, "..", "executables", profileName, "profile.json"),
|
|
4033
4059
|
// up one (prod: dist/bin → dist/executables)
|
|
4034
|
-
|
|
4060
|
+
path12.join(here, "..", "src", "executables", profileName, "profile.json")
|
|
4035
4061
|
// fallback
|
|
4036
4062
|
];
|
|
4037
4063
|
for (const c of candidates) {
|
|
4038
|
-
if (
|
|
4064
|
+
if (fs15.existsSync(c)) return c;
|
|
4039
4065
|
}
|
|
4040
4066
|
return candidates[0];
|
|
4041
4067
|
}
|
|
@@ -4211,9 +4237,9 @@ function resolveAuthToken(env = process.env) {
|
|
|
4211
4237
|
return token;
|
|
4212
4238
|
}
|
|
4213
4239
|
function detectPackageManager2(cwd) {
|
|
4214
|
-
if (
|
|
4215
|
-
if (
|
|
4216
|
-
if (
|
|
4240
|
+
if (fs16.existsSync(path13.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
|
|
4241
|
+
if (fs16.existsSync(path13.join(cwd, "yarn.lock"))) return "yarn";
|
|
4242
|
+
if (fs16.existsSync(path13.join(cwd, "bun.lockb"))) return "bun";
|
|
4217
4243
|
return "npm";
|
|
4218
4244
|
}
|
|
4219
4245
|
function shellOut(cmd, args, cwd, stream = true) {
|
|
@@ -4293,11 +4319,11 @@ function configureGitIdentity(cwd) {
|
|
|
4293
4319
|
}
|
|
4294
4320
|
function postFailureTail(issueNumber, cwd, reason) {
|
|
4295
4321
|
if (!issueNumber) return;
|
|
4296
|
-
const logPath =
|
|
4322
|
+
const logPath = path13.join(cwd, ".kody2", "last-run.jsonl");
|
|
4297
4323
|
let tail = "";
|
|
4298
4324
|
try {
|
|
4299
|
-
if (
|
|
4300
|
-
const content =
|
|
4325
|
+
if (fs16.existsSync(logPath)) {
|
|
4326
|
+
const content = fs16.readFileSync(logPath, "utf-8");
|
|
4301
4327
|
tail = content.slice(-3e3);
|
|
4302
4328
|
}
|
|
4303
4329
|
} catch {
|
|
@@ -4322,7 +4348,7 @@ async function runCi(argv) {
|
|
|
4322
4348
|
return 0;
|
|
4323
4349
|
}
|
|
4324
4350
|
const args = parseCiArgs(argv);
|
|
4325
|
-
const cwd = args.cwd ?
|
|
4351
|
+
const cwd = args.cwd ? path13.resolve(args.cwd) : process.cwd();
|
|
4326
4352
|
let earlyConfig;
|
|
4327
4353
|
try {
|
|
4328
4354
|
earlyConfig = loadConfig(cwd);
|
|
@@ -4415,23 +4441,23 @@ var DEFAULT_MODEL = "claude/claude-haiku-4-5-20251001";
|
|
|
4415
4441
|
var CHAT_HELP = `kody2 chat \u2014 dashboard-driven chat session
|
|
4416
4442
|
|
|
4417
4443
|
Usage:
|
|
4418
|
-
kody2 chat [--session <id>] [--
|
|
4444
|
+
kody2 chat [--session <id>] [--model <provider/model>]
|
|
4419
4445
|
[--dashboard-url <url>] [--cwd <path>] [--verbose|--quiet]
|
|
4420
4446
|
|
|
4421
|
-
All inputs may also come from env: SESSION_ID,
|
|
4422
|
-
CLI flags take precedence over env. SESSION_ID
|
|
4447
|
+
All inputs may also come from env: SESSION_ID, MODEL, DASHBOARD_URL.
|
|
4448
|
+
CLI flags take precedence over env. SESSION_ID and DASHBOARD_URL are required
|
|
4449
|
+
(the runner long-polls the dashboard for user turns and pushes events back).
|
|
4423
4450
|
|
|
4424
4451
|
Exit codes:
|
|
4425
|
-
0
|
|
4426
|
-
64 bad inputs
|
|
4427
|
-
99 runtime failure (agent crash, LiteLLM failure)
|
|
4452
|
+
0 session exited cleanly (idle or hard timeout)
|
|
4453
|
+
64 bad inputs
|
|
4454
|
+
99 runtime failure (agent crash, pull failure, LiteLLM failure)
|
|
4428
4455
|
`;
|
|
4429
4456
|
function parseChatArgs(argv, env = process.env) {
|
|
4430
4457
|
const result = { errors: [] };
|
|
4431
4458
|
for (let i = 0; i < argv.length; i++) {
|
|
4432
4459
|
const arg = argv[i];
|
|
4433
4460
|
if (arg === "--session") result.sessionId = argv[++i];
|
|
4434
|
-
else if (arg === "--message") result.initMessage = argv[++i];
|
|
4435
4461
|
else if (arg === "--model") result.model = argv[++i];
|
|
4436
4462
|
else if (arg === "--dashboard-url") result.dashboardUrl = argv[++i];
|
|
4437
4463
|
else if (arg === "--cwd") result.cwd = argv[++i];
|
|
@@ -4442,34 +4468,18 @@ function parseChatArgs(argv, env = process.env) {
|
|
|
4442
4468
|
else if (arg) result.errors.push(`unexpected positional: ${arg}`);
|
|
4443
4469
|
}
|
|
4444
4470
|
result.sessionId = result.sessionId ?? env.SESSION_ID ?? void 0;
|
|
4445
|
-
result.initMessage = result.initMessage ?? env.INIT_MESSAGE ?? void 0;
|
|
4446
4471
|
result.model = result.model ?? env.MODEL ?? void 0;
|
|
4447
4472
|
result.dashboardUrl = result.dashboardUrl ?? env.DASHBOARD_URL ?? void 0;
|
|
4448
|
-
for (const key of ["sessionId", "
|
|
4473
|
+
for (const key of ["sessionId", "model", "dashboardUrl"]) {
|
|
4449
4474
|
const v = result[key];
|
|
4450
4475
|
if (typeof v === "string" && v.trim() === "") result[key] = void 0;
|
|
4451
4476
|
}
|
|
4452
|
-
if (!result.
|
|
4453
|
-
result.errors.push("--session <id> (or SESSION_ID env) is required");
|
|
4477
|
+
if (!result.errors.includes("__HELP__")) {
|
|
4478
|
+
if (!result.sessionId) result.errors.push("--session <id> (or SESSION_ID env) is required");
|
|
4479
|
+
if (!result.dashboardUrl) result.errors.push("--dashboard-url <url> (or DASHBOARD_URL env) is required");
|
|
4454
4480
|
}
|
|
4455
4481
|
return result;
|
|
4456
4482
|
}
|
|
4457
|
-
function commitChatFiles(cwd, sessionId, verbose) {
|
|
4458
|
-
const sessionFile = path16.relative(cwd, sessionFilePath(cwd, sessionId));
|
|
4459
|
-
const eventsFile = path16.relative(cwd, eventsFilePath(cwd, sessionId));
|
|
4460
|
-
const paths = [sessionFile, eventsFile].filter((p) => fs19.existsSync(path16.join(cwd, p)));
|
|
4461
|
-
if (paths.length === 0) return;
|
|
4462
|
-
const opts = { cwd, stdio: verbose ? "inherit" : "pipe" };
|
|
4463
|
-
try {
|
|
4464
|
-
execFileSync16("git", ["add", ...paths], opts);
|
|
4465
|
-
execFileSync16("git", ["commit", "--quiet", "-m", `chat: reply for ${sessionId}`], opts);
|
|
4466
|
-
execFileSync16("git", ["push", "--quiet", "origin", "HEAD"], opts);
|
|
4467
|
-
} catch (err) {
|
|
4468
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
4469
|
-
process.stderr.write(`[kody2:chat] commit/push skipped: ${msg}
|
|
4470
|
-
`);
|
|
4471
|
-
}
|
|
4472
|
-
}
|
|
4473
4483
|
function tryLoadConfig(cwd) {
|
|
4474
4484
|
try {
|
|
4475
4485
|
return loadConfig(cwd);
|
|
@@ -4477,11 +4487,6 @@ function tryLoadConfig(cwd) {
|
|
|
4477
4487
|
return null;
|
|
4478
4488
|
}
|
|
4479
4489
|
}
|
|
4480
|
-
function buildSink(cwd, sessionId, dashboardUrl) {
|
|
4481
|
-
const sinks = [new FileSink(eventsFilePath(cwd, sessionId))];
|
|
4482
|
-
if (dashboardUrl) sinks.push(new HttpSink(dashboardUrl, sessionId));
|
|
4483
|
-
return new TeeSink(sinks);
|
|
4484
|
-
}
|
|
4485
4490
|
async function runChat(argv) {
|
|
4486
4491
|
if (argv.includes("--help") || argv.includes("-h")) {
|
|
4487
4492
|
process.stdout.write(CHAT_HELP);
|
|
@@ -4495,8 +4500,9 @@ async function runChat(argv) {
|
|
|
4495
4500
|
${CHAT_HELP}`);
|
|
4496
4501
|
return 64;
|
|
4497
4502
|
}
|
|
4498
|
-
const cwd = args.cwd ?
|
|
4503
|
+
const cwd = args.cwd ? path14.resolve(args.cwd) : process.cwd();
|
|
4499
4504
|
const sessionId = args.sessionId;
|
|
4505
|
+
const dashboardUrl = args.dashboardUrl;
|
|
4500
4506
|
const unpackedSecrets = unpackAllSecrets();
|
|
4501
4507
|
if (unpackedSecrets > 0) {
|
|
4502
4508
|
process.stdout.write(`\u2192 kody2: unpacked ${unpackedSecrets} secret(s) from ALL_SECRETS
|
|
@@ -4522,13 +4528,20 @@ ${CHAT_HELP}`);
|
|
|
4522
4528
|
return 99;
|
|
4523
4529
|
}
|
|
4524
4530
|
}
|
|
4531
|
+
let sink;
|
|
4532
|
+
try {
|
|
4533
|
+
sink = new HttpSink(dashboardUrl, sessionId);
|
|
4534
|
+
} catch (err) {
|
|
4535
|
+
process.stderr.write(`error: ${err instanceof Error ? err.message : String(err)}
|
|
4536
|
+
`);
|
|
4537
|
+
return 64;
|
|
4538
|
+
}
|
|
4525
4539
|
let litellm = null;
|
|
4526
4540
|
try {
|
|
4527
4541
|
litellm = await startLitellmIfNeeded(model, cwd);
|
|
4528
4542
|
} catch (err) {
|
|
4529
4543
|
const msg = err instanceof Error ? err.message : String(err);
|
|
4530
|
-
|
|
4531
|
-
await sink2.emit({
|
|
4544
|
+
await sink.emit({
|
|
4532
4545
|
event: "chat.error",
|
|
4533
4546
|
payload: { sessionId, error: `litellm startup failed: ${msg}` },
|
|
4534
4547
|
runId: makeRunId(sessionId, "error"),
|
|
@@ -4536,21 +4549,33 @@ ${CHAT_HELP}`);
|
|
|
4536
4549
|
});
|
|
4537
4550
|
return 99;
|
|
4538
4551
|
}
|
|
4539
|
-
|
|
4540
|
-
|
|
4541
|
-
|
|
4552
|
+
let pull;
|
|
4553
|
+
try {
|
|
4554
|
+
pull = createPullClient({ baseUrl: dashboardUrl, sessionId });
|
|
4555
|
+
} catch (err) {
|
|
4556
|
+
process.stderr.write(`error: ${err instanceof Error ? err.message : String(err)}
|
|
4557
|
+
`);
|
|
4558
|
+
try {
|
|
4559
|
+
litellm?.kill();
|
|
4560
|
+
} catch {
|
|
4561
|
+
}
|
|
4562
|
+
return 64;
|
|
4563
|
+
}
|
|
4564
|
+
process.stdout.write(`\u2192 kody2 chat: session ${sessionId}, model ${model.provider}/${model.model}
|
|
4565
|
+
`);
|
|
4542
4566
|
try {
|
|
4543
|
-
const result = await
|
|
4567
|
+
const result = await runChatSession({
|
|
4544
4568
|
sessionId,
|
|
4545
|
-
sessionFile,
|
|
4546
4569
|
cwd,
|
|
4547
4570
|
model,
|
|
4548
4571
|
litellmUrl: litellm?.url ?? null,
|
|
4549
4572
|
sink,
|
|
4573
|
+
pull,
|
|
4550
4574
|
verbose: args.verbose,
|
|
4551
4575
|
quiet: args.quiet
|
|
4552
4576
|
});
|
|
4553
|
-
|
|
4577
|
+
process.stdout.write(`\u2192 kody2 chat: exited (${result.reason ?? "ok"}) after ${result.turnsProcessed} turn(s)
|
|
4578
|
+
`);
|
|
4554
4579
|
return result.exitCode;
|
|
4555
4580
|
} finally {
|
|
4556
4581
|
try {
|
|
@@ -87,6 +87,8 @@ export interface ClaudeCodeSpec {
|
|
|
87
87
|
permissionMode: "default" | "acceptEdits" | "plan" | "bypassPermissions"
|
|
88
88
|
/** null = unbounded. */
|
|
89
89
|
maxTurns: number | null
|
|
90
|
+
/** Extended-thinking token budget. null = SDK default. */
|
|
91
|
+
maxThinkingTokens: number | null
|
|
90
92
|
/** Text appended on top of Claude Code's baseline system prompt. */
|
|
91
93
|
systemPromptAppend: string | null
|
|
92
94
|
/** SDK built-in tools this executable is allowed to use (capability pack). */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kody-ade/kody-engine",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.33",
|
|
4
4
|
"description": "kody2 — autonomous development engine. Single-session Claude Code agent behind a generic executor + declarative executable profiles.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -12,6 +12,18 @@
|
|
|
12
12
|
"templates",
|
|
13
13
|
"kody.config.schema.json"
|
|
14
14
|
],
|
|
15
|
+
"scripts": {
|
|
16
|
+
"kody2": "tsx bin/kody2.ts",
|
|
17
|
+
"build": "tsup && node scripts/copy-assets.cjs",
|
|
18
|
+
"test": "vitest run tests/unit tests/int --no-coverage",
|
|
19
|
+
"test:e2e": "vitest run tests/e2e --no-coverage",
|
|
20
|
+
"test:all": "vitest run tests --no-coverage",
|
|
21
|
+
"typecheck": "tsc --noEmit",
|
|
22
|
+
"lint": "biome check",
|
|
23
|
+
"lint:fix": "biome check --write",
|
|
24
|
+
"format": "biome format --write",
|
|
25
|
+
"prepublishOnly": "pnpm build"
|
|
26
|
+
},
|
|
15
27
|
"dependencies": {
|
|
16
28
|
"@anthropic-ai/claude-agent-sdk": "0.2.92"
|
|
17
29
|
},
|
|
@@ -31,16 +43,5 @@
|
|
|
31
43
|
"url": "git+https://github.com/aharonyaircohen/kody-engine.git"
|
|
32
44
|
},
|
|
33
45
|
"homepage": "https://github.com/aharonyaircohen/kody-engine",
|
|
34
|
-
"bugs": "https://github.com/aharonyaircohen/kody-engine/issues"
|
|
35
|
-
|
|
36
|
-
"kody2": "tsx bin/kody2.ts",
|
|
37
|
-
"build": "tsup && node scripts/copy-assets.cjs",
|
|
38
|
-
"test": "vitest run tests/unit tests/int --no-coverage",
|
|
39
|
-
"test:e2e": "vitest run tests/e2e --no-coverage",
|
|
40
|
-
"test:all": "vitest run tests --no-coverage",
|
|
41
|
-
"typecheck": "tsc --noEmit",
|
|
42
|
-
"lint": "biome check",
|
|
43
|
-
"lint:fix": "biome check --write",
|
|
44
|
-
"format": "biome format --write"
|
|
45
|
-
}
|
|
46
|
-
}
|
|
46
|
+
"bugs": "https://github.com/aharonyaircohen/kody-engine/issues"
|
|
47
|
+
}
|