@kody-ade/kody-engine 0.2.34 → 0.2.35
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 +330 -340
- 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.35",
|
|
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,115 +50,81 @@ var package_default = {
|
|
|
50
50
|
};
|
|
51
51
|
|
|
52
52
|
// src/chat-cli.ts
|
|
53
|
-
import
|
|
53
|
+
import { execFileSync as execFileSync16 } from "child_process";
|
|
54
|
+
import * as fs19 from "fs";
|
|
55
|
+
import * as path16 from "path";
|
|
54
56
|
|
|
55
|
-
// src/chat/
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
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
|
-
};
|
|
57
|
+
// src/chat/events.ts
|
|
58
|
+
import * as fs from "fs";
|
|
59
|
+
import * as path from "path";
|
|
60
|
+
function eventsFilePath(cwd, sessionId) {
|
|
61
|
+
return path.join(cwd, ".kody", "events", `${sessionId}.jsonl`);
|
|
91
62
|
}
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
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 };
|
|
63
|
+
var FileSink = class {
|
|
64
|
+
constructor(file) {
|
|
65
|
+
this.file = file;
|
|
100
66
|
}
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
67
|
+
file;
|
|
68
|
+
async emit(event) {
|
|
69
|
+
fs.mkdirSync(path.dirname(this.file), { recursive: true });
|
|
70
|
+
fs.appendFileSync(this.file, `${JSON.stringify(event)}
|
|
71
|
+
`);
|
|
72
|
+
}
|
|
73
|
+
};
|
|
104
74
|
var HttpSink = class {
|
|
105
|
-
constructor(baseUrl, sessionId,
|
|
75
|
+
constructor(baseUrl, sessionId, logger = {
|
|
106
76
|
warn: (m) => process.stderr.write(`[kody2:chat] ${m}
|
|
107
77
|
`)
|
|
108
78
|
}) {
|
|
79
|
+
this.baseUrl = baseUrl;
|
|
109
80
|
this.sessionId = sessionId;
|
|
110
|
-
this.fetchFn = fetchFn;
|
|
111
81
|
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;
|
|
119
82
|
}
|
|
83
|
+
baseUrl;
|
|
120
84
|
sessionId;
|
|
121
|
-
fetchFn;
|
|
122
85
|
logger;
|
|
123
|
-
origin;
|
|
124
|
-
token;
|
|
125
86
|
async emit(event) {
|
|
126
|
-
const url =
|
|
127
|
-
url.pathname = "/api/kody/events/ingest";
|
|
128
|
-
url.searchParams.set("sessionId", this.sessionId);
|
|
129
|
-
url.searchParams.set("token", this.token);
|
|
87
|
+
const url = withSessionParam(this.baseUrl, this.sessionId);
|
|
130
88
|
try {
|
|
131
|
-
const res = await
|
|
89
|
+
const res = await fetch(url, {
|
|
132
90
|
method: "POST",
|
|
133
|
-
headers: {
|
|
134
|
-
"content-type": "application/json",
|
|
135
|
-
authorization: `Bearer ${this.token}`
|
|
136
|
-
},
|
|
91
|
+
headers: { "content-type": "application/json" },
|
|
137
92
|
body: JSON.stringify(event),
|
|
138
93
|
signal: AbortSignal.timeout(5e3)
|
|
139
94
|
});
|
|
140
95
|
if (!res.ok) {
|
|
141
|
-
this.logger.warn(`HttpSink POST ${url
|
|
96
|
+
this.logger.warn(`HttpSink POST ${url} \u2192 ${res.status}`);
|
|
142
97
|
}
|
|
143
98
|
} catch (err) {
|
|
144
|
-
this.logger.warn(
|
|
145
|
-
`HttpSink POST ${url.pathname} failed: ${err instanceof Error ? err.message : String(err)}`
|
|
146
|
-
);
|
|
99
|
+
this.logger.warn(`HttpSink POST ${url} failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
147
100
|
}
|
|
148
101
|
}
|
|
149
102
|
};
|
|
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
|
+
}
|
|
150
116
|
function makeRunId(sessionId, suffix) {
|
|
151
117
|
return `chat-${sessionId}-${suffix}`;
|
|
152
118
|
}
|
|
153
119
|
|
|
154
120
|
// src/agent.ts
|
|
155
|
-
import * as
|
|
156
|
-
import * as
|
|
121
|
+
import * as fs3 from "fs";
|
|
122
|
+
import * as path3 from "path";
|
|
157
123
|
import { query } from "@anthropic-ai/claude-agent-sdk";
|
|
158
124
|
|
|
159
125
|
// src/config.ts
|
|
160
|
-
import * as
|
|
161
|
-
import * as
|
|
126
|
+
import * as fs2 from "fs";
|
|
127
|
+
import * as path2 from "path";
|
|
162
128
|
var LITELLM_DEFAULT_PORT = 4e3;
|
|
163
129
|
var LITELLM_DEFAULT_URL = `http://localhost:${LITELLM_DEFAULT_PORT}`;
|
|
164
130
|
function parseProviderModel(s) {
|
|
@@ -176,13 +142,13 @@ function needsLitellmProxy(model) {
|
|
|
176
142
|
return model.provider !== "claude" && model.provider !== "anthropic";
|
|
177
143
|
}
|
|
178
144
|
function loadConfig(projectDir = process.cwd()) {
|
|
179
|
-
const configPath =
|
|
180
|
-
if (!
|
|
145
|
+
const configPath = path2.join(projectDir, "kody.config.json");
|
|
146
|
+
if (!fs2.existsSync(configPath)) {
|
|
181
147
|
throw new Error(`kody.config.json not found at ${configPath}`);
|
|
182
148
|
}
|
|
183
149
|
let raw;
|
|
184
150
|
try {
|
|
185
|
-
raw = JSON.parse(
|
|
151
|
+
raw = JSON.parse(fs2.readFileSync(configPath, "utf-8"));
|
|
186
152
|
} catch (err) {
|
|
187
153
|
const msg = err instanceof Error ? err.message : String(err);
|
|
188
154
|
throw new Error(`kody.config.json is invalid JSON: ${msg}`);
|
|
@@ -359,10 +325,10 @@ function formatBytes(bytes) {
|
|
|
359
325
|
// src/agent.ts
|
|
360
326
|
var DEFAULT_ALLOWED_TOOLS = ["Bash", "Edit", "Read", "Write", "Glob", "Grep"];
|
|
361
327
|
async function runAgent(opts) {
|
|
362
|
-
const ndjsonDir = opts.ndjsonDir ??
|
|
363
|
-
|
|
364
|
-
const ndjsonPath =
|
|
365
|
-
const fullLog =
|
|
328
|
+
const ndjsonDir = opts.ndjsonDir ?? path3.join(opts.cwd, ".kody2");
|
|
329
|
+
fs3.mkdirSync(ndjsonDir, { recursive: true });
|
|
330
|
+
const ndjsonPath = path3.join(ndjsonDir, "last-run.jsonl");
|
|
331
|
+
const fullLog = fs3.createWriteStream(ndjsonPath, { flags: "w" });
|
|
366
332
|
const env = {
|
|
367
333
|
...process.env,
|
|
368
334
|
SKIP_HOOKS: "1",
|
|
@@ -393,6 +359,9 @@ async function runAgent(opts) {
|
|
|
393
359
|
if (typeof opts.maxTurns === "number" && opts.maxTurns > 0) {
|
|
394
360
|
queryOptions.maxTurns = opts.maxTurns;
|
|
395
361
|
}
|
|
362
|
+
if (typeof opts.maxThinkingTokens === "number" && opts.maxThinkingTokens > 0) {
|
|
363
|
+
queryOptions.maxThinkingTokens = opts.maxThinkingTokens;
|
|
364
|
+
}
|
|
396
365
|
if (typeof opts.systemPromptAppend === "string" && opts.systemPromptAppend.length > 0) {
|
|
397
366
|
queryOptions.systemPrompt = { type: "preset", preset: "claude_code", append: opts.systemPromptAppend };
|
|
398
367
|
}
|
|
@@ -434,6 +403,53 @@ async function runAgent(opts) {
|
|
|
434
403
|
return { outcome, finalText, error: errorMessage, ndjsonPath };
|
|
435
404
|
}
|
|
436
405
|
|
|
406
|
+
// src/chat/session.ts
|
|
407
|
+
import * as fs4 from "fs";
|
|
408
|
+
import * as path4 from "path";
|
|
409
|
+
function sessionFilePath(cwd, sessionId) {
|
|
410
|
+
return path4.join(cwd, ".kody", "sessions", `${sessionId}.jsonl`);
|
|
411
|
+
}
|
|
412
|
+
function readSession(file) {
|
|
413
|
+
if (!fs4.existsSync(file)) return [];
|
|
414
|
+
const raw = fs4.readFileSync(file, "utf-8").trim();
|
|
415
|
+
if (!raw) return [];
|
|
416
|
+
const turns = [];
|
|
417
|
+
for (const line of raw.split("\n")) {
|
|
418
|
+
if (!line.trim()) continue;
|
|
419
|
+
try {
|
|
420
|
+
const parsed = JSON.parse(line);
|
|
421
|
+
if (parsed.role !== "user" && parsed.role !== "assistant") continue;
|
|
422
|
+
if (typeof parsed.content !== "string") continue;
|
|
423
|
+
turns.push(parsed);
|
|
424
|
+
} catch {
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
return turns;
|
|
428
|
+
}
|
|
429
|
+
function appendTurn(file, turn) {
|
|
430
|
+
fs4.mkdirSync(path4.dirname(file), { recursive: true });
|
|
431
|
+
const line = JSON.stringify({
|
|
432
|
+
role: turn.role,
|
|
433
|
+
content: turn.content,
|
|
434
|
+
timestamp: turn.timestamp,
|
|
435
|
+
toolCalls: turn.toolCalls ?? []
|
|
436
|
+
});
|
|
437
|
+
fs4.appendFileSync(file, `${line}
|
|
438
|
+
`);
|
|
439
|
+
}
|
|
440
|
+
function seedInitialMessage(file, message) {
|
|
441
|
+
if (!message.trim()) return false;
|
|
442
|
+
const turns = readSession(file);
|
|
443
|
+
const lastUser = [...turns].reverse().find((t) => t.role === "user");
|
|
444
|
+
if (lastUser && lastUser.content === message) return false;
|
|
445
|
+
appendTurn(file, {
|
|
446
|
+
role: "user",
|
|
447
|
+
content: message,
|
|
448
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
449
|
+
});
|
|
450
|
+
return true;
|
|
451
|
+
}
|
|
452
|
+
|
|
437
453
|
// src/chat/loop.ts
|
|
438
454
|
var CHAT_SYSTEM_PROMPT = [
|
|
439
455
|
"You are Kody, an AI assistant for the Kody Operations Dashboard. Reply to the user's",
|
|
@@ -442,15 +458,20 @@ var CHAT_SYSTEM_PROMPT = [
|
|
|
442
458
|
"read repository code or execute small checks when it helps you answer \u2014 otherwise",
|
|
443
459
|
"reply directly. Do not invent file paths, commit SHAs, or command output."
|
|
444
460
|
].join("\n");
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
const
|
|
453
|
-
|
|
461
|
+
async function runChatTurn(opts) {
|
|
462
|
+
const turns = readSession(opts.sessionFile);
|
|
463
|
+
if (turns.length === 0) {
|
|
464
|
+
const error = "session file is empty \u2014 nothing to reply to";
|
|
465
|
+
await emit(opts.sink, "chat.error", opts.sessionId, "error", { error });
|
|
466
|
+
return { exitCode: 64, error };
|
|
467
|
+
}
|
|
468
|
+
const lastTurn = turns[turns.length - 1];
|
|
469
|
+
if (lastTurn.role !== "user") {
|
|
470
|
+
const error = "last turn is not a user message \u2014 assistant already replied";
|
|
471
|
+
await emit(opts.sink, "chat.error", opts.sessionId, "error", { error });
|
|
472
|
+
return { exitCode: 64, error };
|
|
473
|
+
}
|
|
474
|
+
const prompt = buildPrompt(turns, opts.systemPrompt ?? CHAT_SYSTEM_PROMPT);
|
|
454
475
|
const invoke = opts.invokeAgent ?? ((p) => runAgent({
|
|
455
476
|
prompt: p,
|
|
456
477
|
model: opts.model,
|
|
@@ -459,68 +480,34 @@ async function runChatSession(opts) {
|
|
|
459
480
|
verbose: opts.verbose,
|
|
460
481
|
quiet: opts.quiet
|
|
461
482
|
}));
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
}
|
|
475
|
-
let response;
|
|
476
|
-
try {
|
|
477
|
-
response = await opts.pull(since, pullTimeoutMs);
|
|
478
|
-
} catch (err) {
|
|
479
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
480
|
-
await emit(opts.sink, "chat.error", opts.sessionId, "error", { error: `pull failed: ${msg}` });
|
|
481
|
-
return { exitCode: 99, turnsProcessed, reason: `pull failed: ${msg}` };
|
|
482
|
-
}
|
|
483
|
-
if (response.turns.length === 0) {
|
|
484
|
-
if (now() - lastActivityAt > idleTimeoutMs) {
|
|
485
|
-
await emit(opts.sink, "chat.done", opts.sessionId, "done", {
|
|
486
|
-
sessionId: opts.sessionId,
|
|
487
|
-
reason: "idle-timeout"
|
|
488
|
-
});
|
|
489
|
-
return { exitCode: 0, turnsProcessed, reason: "idle-timeout" };
|
|
490
|
-
}
|
|
491
|
-
continue;
|
|
492
|
-
}
|
|
493
|
-
const newUserTurns = response.turns.filter((t) => t.role === "user");
|
|
494
|
-
for (const t of newUserTurns) history.push(t);
|
|
495
|
-
since = response.nextSince;
|
|
496
|
-
if (newUserTurns.length === 0) continue;
|
|
497
|
-
lastActivityAt = now();
|
|
498
|
-
const prompt = buildPrompt(history, systemPrompt);
|
|
499
|
-
let result;
|
|
500
|
-
try {
|
|
501
|
-
result = await invoke(prompt);
|
|
502
|
-
} catch (err) {
|
|
503
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
504
|
-
await emit(opts.sink, "chat.error", opts.sessionId, "error", { error: msg });
|
|
505
|
-
return { exitCode: 99, turnsProcessed, reason: msg };
|
|
506
|
-
}
|
|
507
|
-
if (result.outcome !== "completed") {
|
|
508
|
-
const error = result.error ?? "agent did not complete";
|
|
509
|
-
await emit(opts.sink, "chat.error", opts.sessionId, "error", { error });
|
|
510
|
-
return { exitCode: 99, turnsProcessed, reason: error };
|
|
511
|
-
}
|
|
512
|
-
const reply = result.finalText.trim();
|
|
513
|
-
const replyTimestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
514
|
-
history.push({ role: "assistant", content: reply, timestamp: replyTimestamp });
|
|
515
|
-
turnsProcessed++;
|
|
516
|
-
lastActivityAt = now();
|
|
517
|
-
await emit(opts.sink, "chat.message", opts.sessionId, `message-${turnsProcessed}`, {
|
|
518
|
-
sessionId: opts.sessionId,
|
|
519
|
-
role: "assistant",
|
|
520
|
-
content: reply,
|
|
521
|
-
timestamp: replyTimestamp
|
|
522
|
-
});
|
|
483
|
+
let result;
|
|
484
|
+
try {
|
|
485
|
+
result = await invoke(prompt);
|
|
486
|
+
} catch (err) {
|
|
487
|
+
const error = err instanceof Error ? err.message : String(err);
|
|
488
|
+
await emit(opts.sink, "chat.error", opts.sessionId, "error", { error });
|
|
489
|
+
return { exitCode: 99, error };
|
|
490
|
+
}
|
|
491
|
+
if (result.outcome !== "completed") {
|
|
492
|
+
const error = result.error ?? "agent did not complete";
|
|
493
|
+
await emit(opts.sink, "chat.error", opts.sessionId, "error", { error });
|
|
494
|
+
return { exitCode: 99, error };
|
|
523
495
|
}
|
|
496
|
+
const reply = result.finalText.trim();
|
|
497
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
498
|
+
appendTurn(opts.sessionFile, {
|
|
499
|
+
role: "assistant",
|
|
500
|
+
content: reply,
|
|
501
|
+
timestamp: now
|
|
502
|
+
});
|
|
503
|
+
await emit(opts.sink, "chat.message", opts.sessionId, "message", {
|
|
504
|
+
sessionId: opts.sessionId,
|
|
505
|
+
role: "assistant",
|
|
506
|
+
content: reply,
|
|
507
|
+
timestamp: now
|
|
508
|
+
});
|
|
509
|
+
await emit(opts.sink, "chat.done", opts.sessionId, "done", { sessionId: opts.sessionId });
|
|
510
|
+
return { exitCode: 0, reply };
|
|
524
511
|
}
|
|
525
512
|
function buildPrompt(turns, systemPrompt) {
|
|
526
513
|
const header = `System: ${systemPrompt}`;
|
|
@@ -542,11 +529,11 @@ async function emit(sink, type, sessionId, suffix, payload) {
|
|
|
542
529
|
|
|
543
530
|
// src/kody2-cli.ts
|
|
544
531
|
import { execFileSync as execFileSync15 } from "child_process";
|
|
545
|
-
import * as
|
|
546
|
-
import * as
|
|
532
|
+
import * as fs18 from "fs";
|
|
533
|
+
import * as path15 from "path";
|
|
547
534
|
|
|
548
535
|
// src/dispatch.ts
|
|
549
|
-
import * as
|
|
536
|
+
import * as fs5 from "fs";
|
|
550
537
|
function autoDispatch(opts) {
|
|
551
538
|
const explicit = opts?.explicit;
|
|
552
539
|
if (explicit?.issueNumber && explicit.issueNumber > 0) {
|
|
@@ -558,10 +545,10 @@ function autoDispatch(opts) {
|
|
|
558
545
|
}
|
|
559
546
|
const eventName = process.env.GITHUB_EVENT_NAME;
|
|
560
547
|
const eventPath = process.env.GITHUB_EVENT_PATH;
|
|
561
|
-
if (!eventName || !eventPath || !
|
|
548
|
+
if (!eventName || !eventPath || !fs5.existsSync(eventPath)) return null;
|
|
562
549
|
let event = {};
|
|
563
550
|
try {
|
|
564
|
-
event = JSON.parse(
|
|
551
|
+
event = JSON.parse(fs5.readFileSync(eventPath, "utf-8"));
|
|
565
552
|
} catch {
|
|
566
553
|
return null;
|
|
567
554
|
}
|
|
@@ -630,14 +617,14 @@ function extractFeedback(afterTag) {
|
|
|
630
617
|
}
|
|
631
618
|
|
|
632
619
|
// src/executor.ts
|
|
633
|
-
import * as
|
|
634
|
-
import * as
|
|
620
|
+
import * as fs17 from "fs";
|
|
621
|
+
import * as path14 from "path";
|
|
635
622
|
|
|
636
623
|
// src/litellm.ts
|
|
637
624
|
import { execFileSync, spawn } from "child_process";
|
|
638
|
-
import * as
|
|
625
|
+
import * as fs6 from "fs";
|
|
639
626
|
import * as os from "os";
|
|
640
|
-
import * as
|
|
627
|
+
import * as path5 from "path";
|
|
641
628
|
async function checkLitellmHealth(url) {
|
|
642
629
|
try {
|
|
643
630
|
const response = await fetch(`${url}/health`, { signal: AbortSignal.timeout(3e3) });
|
|
@@ -677,20 +664,20 @@ async function startLitellmIfNeeded(model, projectDir, url = LITELLM_DEFAULT_URL
|
|
|
677
664
|
throw new Error("litellm not installed \u2014 run: pip install 'litellm[proxy]'");
|
|
678
665
|
}
|
|
679
666
|
}
|
|
680
|
-
const configPath =
|
|
681
|
-
|
|
667
|
+
const configPath = path5.join(os.tmpdir(), `kody2-litellm-${Date.now()}.yaml`);
|
|
668
|
+
fs6.writeFileSync(configPath, generateLitellmConfigYaml(model));
|
|
682
669
|
const portMatch = url.match(/:(\d+)/);
|
|
683
670
|
const port = portMatch ? portMatch[1] : "4000";
|
|
684
671
|
const args = cmd === "litellm" ? ["--config", configPath, "--port", port] : ["-m", "litellm", "--config", configPath, "--port", port];
|
|
685
672
|
const dotenvVars = readDotenvApiKeys(projectDir);
|
|
686
|
-
const logPath =
|
|
687
|
-
const outFd =
|
|
673
|
+
const logPath = path5.join(os.tmpdir(), `kody2-litellm-${Date.now()}.log`);
|
|
674
|
+
const outFd = fs6.openSync(logPath, "w");
|
|
688
675
|
const child = spawn(cmd, args, {
|
|
689
676
|
stdio: ["ignore", outFd, outFd],
|
|
690
677
|
detached: true,
|
|
691
678
|
env: stripBlockingEnv({ ...process.env, ...dotenvVars })
|
|
692
679
|
});
|
|
693
|
-
|
|
680
|
+
fs6.closeSync(outFd);
|
|
694
681
|
for (let i = 0; i < 30; i++) {
|
|
695
682
|
await new Promise((r) => setTimeout(r, 2e3));
|
|
696
683
|
if (await checkLitellmHealth(url)) {
|
|
@@ -707,7 +694,7 @@ async function startLitellmIfNeeded(model, projectDir, url = LITELLM_DEFAULT_URL
|
|
|
707
694
|
}
|
|
708
695
|
let logTail = "";
|
|
709
696
|
try {
|
|
710
|
-
logTail =
|
|
697
|
+
logTail = fs6.readFileSync(logPath, "utf-8").slice(-2e3);
|
|
711
698
|
} catch {
|
|
712
699
|
}
|
|
713
700
|
try {
|
|
@@ -718,10 +705,10 @@ async function startLitellmIfNeeded(model, projectDir, url = LITELLM_DEFAULT_URL
|
|
|
718
705
|
${logTail}`);
|
|
719
706
|
}
|
|
720
707
|
function readDotenvApiKeys(projectDir) {
|
|
721
|
-
const dotenvPath =
|
|
722
|
-
if (!
|
|
708
|
+
const dotenvPath = path5.join(projectDir, ".env");
|
|
709
|
+
if (!fs6.existsSync(dotenvPath)) return {};
|
|
723
710
|
const result = {};
|
|
724
|
-
for (const rawLine of
|
|
711
|
+
for (const rawLine of fs6.readFileSync(dotenvPath, "utf-8").split("\n")) {
|
|
725
712
|
const line = rawLine.trim();
|
|
726
713
|
if (!line || line.startsWith("#")) continue;
|
|
727
714
|
const match = line.match(/^([A-Z_][A-Z0-9_]*_API_KEY)=(.*)$/);
|
|
@@ -744,8 +731,8 @@ function stripBlockingEnv(env) {
|
|
|
744
731
|
}
|
|
745
732
|
|
|
746
733
|
// src/profile.ts
|
|
747
|
-
import * as
|
|
748
|
-
import * as
|
|
734
|
+
import * as fs7 from "fs";
|
|
735
|
+
import * as path6 from "path";
|
|
749
736
|
var VALID_INPUT_TYPES = /* @__PURE__ */ new Set(["int", "string", "bool", "enum"]);
|
|
750
737
|
var VALID_PERMISSION_MODES = /* @__PURE__ */ new Set(["default", "acceptEdits", "plan", "bypassPermissions"]);
|
|
751
738
|
var ProfileError = class extends Error {
|
|
@@ -758,12 +745,12 @@ var ProfileError = class extends Error {
|
|
|
758
745
|
profilePath;
|
|
759
746
|
};
|
|
760
747
|
function loadProfile(profilePath) {
|
|
761
|
-
if (!
|
|
748
|
+
if (!fs7.existsSync(profilePath)) {
|
|
762
749
|
throw new ProfileError(profilePath, "file not found");
|
|
763
750
|
}
|
|
764
751
|
let raw;
|
|
765
752
|
try {
|
|
766
|
-
raw = JSON.parse(
|
|
753
|
+
raw = JSON.parse(fs7.readFileSync(profilePath, "utf-8"));
|
|
767
754
|
} catch (err) {
|
|
768
755
|
throw new ProfileError(profilePath, `invalid JSON: ${err instanceof Error ? err.message : String(err)}`);
|
|
769
756
|
}
|
|
@@ -787,7 +774,7 @@ function loadProfile(profilePath) {
|
|
|
787
774
|
outputContract: r.outputContract,
|
|
788
775
|
inputArtifacts: parseInputArtifacts(profilePath, r.input),
|
|
789
776
|
outputArtifacts: parseOutputArtifacts(profilePath, r.output),
|
|
790
|
-
dir:
|
|
777
|
+
dir: path6.dirname(profilePath)
|
|
791
778
|
};
|
|
792
779
|
return profile;
|
|
793
780
|
}
|
|
@@ -853,6 +840,7 @@ function parseClaudeCode(p, raw) {
|
|
|
853
840
|
model: typeof r.model === "string" ? r.model : "inherit",
|
|
854
841
|
permissionMode,
|
|
855
842
|
maxTurns: typeof r.maxTurns === "number" ? r.maxTurns : null,
|
|
843
|
+
maxThinkingTokens: typeof r.maxThinkingTokens === "number" ? r.maxThinkingTokens : null,
|
|
856
844
|
systemPromptAppend: typeof r.systemPromptAppend === "string" ? r.systemPromptAppend : null,
|
|
857
845
|
tools,
|
|
858
846
|
hooks: Array.isArray(r.hooks) ? r.hooks : [],
|
|
@@ -966,21 +954,21 @@ function parseScriptList(p, key, raw) {
|
|
|
966
954
|
}
|
|
967
955
|
|
|
968
956
|
// src/scripts/buildSyntheticPlugin.ts
|
|
969
|
-
import * as
|
|
957
|
+
import * as fs8 from "fs";
|
|
970
958
|
import * as os2 from "os";
|
|
971
|
-
import * as
|
|
959
|
+
import * as path7 from "path";
|
|
972
960
|
function getPluginsCatalogRoot() {
|
|
973
|
-
const here =
|
|
961
|
+
const here = path7.dirname(new URL(import.meta.url).pathname);
|
|
974
962
|
const candidates = [
|
|
975
|
-
|
|
963
|
+
path7.join(here, "..", "plugins"),
|
|
976
964
|
// dev: src/scripts → src/plugins
|
|
977
|
-
|
|
965
|
+
path7.join(here, "..", "..", "plugins"),
|
|
978
966
|
// built: dist/scripts → dist/plugins
|
|
979
|
-
|
|
967
|
+
path7.join(here, "..", "..", "src", "plugins")
|
|
980
968
|
// fallback
|
|
981
969
|
];
|
|
982
970
|
for (const c of candidates) {
|
|
983
|
-
if (
|
|
971
|
+
if (fs8.existsSync(c) && fs8.statSync(c).isDirectory()) return c;
|
|
984
972
|
}
|
|
985
973
|
return candidates[0];
|
|
986
974
|
}
|
|
@@ -990,50 +978,50 @@ var buildSyntheticPlugin = async (ctx, profile) => {
|
|
|
990
978
|
if (!needsSynthetic) return;
|
|
991
979
|
const catalog = getPluginsCatalogRoot();
|
|
992
980
|
const runId = `${profile.name}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
993
|
-
const root =
|
|
994
|
-
|
|
981
|
+
const root = path7.join(os2.tmpdir(), `kody2-synth-${runId}`);
|
|
982
|
+
fs8.mkdirSync(path7.join(root, ".claude-plugin"), { recursive: true });
|
|
995
983
|
if (cc.skills.length > 0) {
|
|
996
|
-
const dst =
|
|
997
|
-
|
|
984
|
+
const dst = path7.join(root, "skills");
|
|
985
|
+
fs8.mkdirSync(dst, { recursive: true });
|
|
998
986
|
for (const name of cc.skills) {
|
|
999
|
-
const src =
|
|
1000
|
-
if (!
|
|
1001
|
-
copyDir(src,
|
|
987
|
+
const src = path7.join(catalog, "skills", name);
|
|
988
|
+
if (!fs8.existsSync(src)) throw new Error(`buildSyntheticPlugin: skill not found in catalog: ${name}`);
|
|
989
|
+
copyDir(src, path7.join(dst, name));
|
|
1002
990
|
}
|
|
1003
991
|
}
|
|
1004
992
|
if (cc.commands.length > 0) {
|
|
1005
|
-
const dst =
|
|
1006
|
-
|
|
993
|
+
const dst = path7.join(root, "commands");
|
|
994
|
+
fs8.mkdirSync(dst, { recursive: true });
|
|
1007
995
|
for (const name of cc.commands) {
|
|
1008
|
-
const src =
|
|
1009
|
-
if (!
|
|
1010
|
-
|
|
996
|
+
const src = path7.join(catalog, "commands", `${name}.md`);
|
|
997
|
+
if (!fs8.existsSync(src)) throw new Error(`buildSyntheticPlugin: command not found in catalog: ${name}`);
|
|
998
|
+
fs8.copyFileSync(src, path7.join(dst, `${name}.md`));
|
|
1011
999
|
}
|
|
1012
1000
|
}
|
|
1013
1001
|
if (cc.subagents.length > 0) {
|
|
1014
|
-
const dst =
|
|
1015
|
-
|
|
1002
|
+
const dst = path7.join(root, "agents");
|
|
1003
|
+
fs8.mkdirSync(dst, { recursive: true });
|
|
1016
1004
|
for (const name of cc.subagents) {
|
|
1017
|
-
const src =
|
|
1018
|
-
if (!
|
|
1019
|
-
|
|
1005
|
+
const src = path7.join(catalog, "agents", `${name}.md`);
|
|
1006
|
+
if (!fs8.existsSync(src)) throw new Error(`buildSyntheticPlugin: subagent not found in catalog: ${name}`);
|
|
1007
|
+
fs8.copyFileSync(src, path7.join(dst, `${name}.md`));
|
|
1020
1008
|
}
|
|
1021
1009
|
}
|
|
1022
1010
|
if (cc.hooks.length > 0) {
|
|
1023
|
-
const dst =
|
|
1024
|
-
|
|
1011
|
+
const dst = path7.join(root, "hooks");
|
|
1012
|
+
fs8.mkdirSync(dst, { recursive: true });
|
|
1025
1013
|
const merged = { hooks: {} };
|
|
1026
1014
|
for (const name of cc.hooks) {
|
|
1027
|
-
const src =
|
|
1028
|
-
if (!
|
|
1029
|
-
const parsed = JSON.parse(
|
|
1015
|
+
const src = path7.join(catalog, "hooks", `${name}.json`);
|
|
1016
|
+
if (!fs8.existsSync(src)) throw new Error(`buildSyntheticPlugin: hook not found in catalog: ${name}`);
|
|
1017
|
+
const parsed = JSON.parse(fs8.readFileSync(src, "utf-8"));
|
|
1030
1018
|
for (const [event, entries] of Object.entries(parsed.hooks ?? {})) {
|
|
1031
1019
|
if (!Array.isArray(entries)) continue;
|
|
1032
1020
|
if (!merged.hooks[event]) merged.hooks[event] = [];
|
|
1033
1021
|
merged.hooks[event].push(...entries);
|
|
1034
1022
|
}
|
|
1035
1023
|
}
|
|
1036
|
-
|
|
1024
|
+
fs8.writeFileSync(path7.join(dst, "hooks.json"), `${JSON.stringify(merged, null, 2)}
|
|
1037
1025
|
`);
|
|
1038
1026
|
}
|
|
1039
1027
|
const manifest = {
|
|
@@ -1044,17 +1032,17 @@ var buildSyntheticPlugin = async (ctx, profile) => {
|
|
|
1044
1032
|
if (cc.skills.length > 0) manifest.skills = ["./skills/"];
|
|
1045
1033
|
if (cc.commands.length > 0) manifest.commands = ["./commands/"];
|
|
1046
1034
|
if (cc.subagents.length > 0) manifest.agents = cc.subagents.map((n) => `./agents/${n}.md`);
|
|
1047
|
-
|
|
1035
|
+
fs8.writeFileSync(path7.join(root, ".claude-plugin", "plugin.json"), `${JSON.stringify(manifest, null, 2)}
|
|
1048
1036
|
`);
|
|
1049
1037
|
ctx.data.syntheticPluginPath = root;
|
|
1050
1038
|
};
|
|
1051
1039
|
function copyDir(src, dst) {
|
|
1052
|
-
|
|
1053
|
-
for (const ent of
|
|
1054
|
-
const s =
|
|
1055
|
-
const d =
|
|
1040
|
+
fs8.mkdirSync(dst, { recursive: true });
|
|
1041
|
+
for (const ent of fs8.readdirSync(src, { withFileTypes: true })) {
|
|
1042
|
+
const s = path7.join(src, ent.name);
|
|
1043
|
+
const d = path7.join(dst, ent.name);
|
|
1056
1044
|
if (ent.isDirectory()) copyDir(s, d);
|
|
1057
|
-
else if (ent.isFile())
|
|
1045
|
+
else if (ent.isFile()) fs8.copyFileSync(s, d);
|
|
1058
1046
|
}
|
|
1059
1047
|
}
|
|
1060
1048
|
|
|
@@ -1120,18 +1108,18 @@ function formatMissesForFeedback(misses) {
|
|
|
1120
1108
|
}
|
|
1121
1109
|
|
|
1122
1110
|
// src/prompt.ts
|
|
1123
|
-
import * as
|
|
1124
|
-
import * as
|
|
1111
|
+
import * as fs9 from "fs";
|
|
1112
|
+
import * as path8 from "path";
|
|
1125
1113
|
var CONVENTIONS_PER_FILE_MAX_BYTES = 3e4;
|
|
1126
1114
|
var CONVENTION_FILES = ["CLAUDE.md", "AGENTS.md"];
|
|
1127
1115
|
function loadProjectConventions(projectDir) {
|
|
1128
1116
|
const out = [];
|
|
1129
1117
|
for (const rel of CONVENTION_FILES) {
|
|
1130
|
-
const abs =
|
|
1131
|
-
if (!
|
|
1118
|
+
const abs = path8.join(projectDir, rel);
|
|
1119
|
+
if (!fs9.existsSync(abs)) continue;
|
|
1132
1120
|
let content;
|
|
1133
1121
|
try {
|
|
1134
|
-
content =
|
|
1122
|
+
content = fs9.readFileSync(abs, "utf-8");
|
|
1135
1123
|
} catch {
|
|
1136
1124
|
continue;
|
|
1137
1125
|
}
|
|
@@ -1252,8 +1240,8 @@ import { execFileSync as execFileSync4 } from "child_process";
|
|
|
1252
1240
|
|
|
1253
1241
|
// src/commit.ts
|
|
1254
1242
|
import { execFileSync as execFileSync3 } from "child_process";
|
|
1255
|
-
import * as
|
|
1256
|
-
import * as
|
|
1243
|
+
import * as fs10 from "fs";
|
|
1244
|
+
import * as path9 from "path";
|
|
1257
1245
|
var FORBIDDEN_PATH_PREFIXES = [
|
|
1258
1246
|
".kody/",
|
|
1259
1247
|
".kody-engine/",
|
|
@@ -1308,18 +1296,18 @@ function tryGit(args, cwd) {
|
|
|
1308
1296
|
}
|
|
1309
1297
|
function abortUnfinishedGitOps(cwd) {
|
|
1310
1298
|
const aborted = [];
|
|
1311
|
-
const gitDir =
|
|
1312
|
-
if (!
|
|
1313
|
-
if (
|
|
1299
|
+
const gitDir = path9.join(cwd ?? process.cwd(), ".git");
|
|
1300
|
+
if (!fs10.existsSync(gitDir)) return aborted;
|
|
1301
|
+
if (fs10.existsSync(path9.join(gitDir, "MERGE_HEAD"))) {
|
|
1314
1302
|
if (tryGit(["merge", "--abort"], cwd)) aborted.push("merge");
|
|
1315
1303
|
}
|
|
1316
|
-
if (
|
|
1304
|
+
if (fs10.existsSync(path9.join(gitDir, "CHERRY_PICK_HEAD"))) {
|
|
1317
1305
|
if (tryGit(["cherry-pick", "--abort"], cwd)) aborted.push("cherry-pick");
|
|
1318
1306
|
}
|
|
1319
|
-
if (
|
|
1307
|
+
if (fs10.existsSync(path9.join(gitDir, "REVERT_HEAD"))) {
|
|
1320
1308
|
if (tryGit(["revert", "--abort"], cwd)) aborted.push("revert");
|
|
1321
1309
|
}
|
|
1322
|
-
if (
|
|
1310
|
+
if (fs10.existsSync(path9.join(gitDir, "rebase-merge")) || fs10.existsSync(path9.join(gitDir, "rebase-apply"))) {
|
|
1323
1311
|
if (tryGit(["rebase", "--abort"], cwd)) aborted.push("rebase");
|
|
1324
1312
|
}
|
|
1325
1313
|
try {
|
|
@@ -1361,7 +1349,7 @@ function normalizeCommitMessage(raw) {
|
|
|
1361
1349
|
function commitAndPush(branch, agentMessage, cwd) {
|
|
1362
1350
|
const allChanged = listChangedFiles(cwd);
|
|
1363
1351
|
const allowedFiles = allChanged.filter((f) => !isForbiddenPath(f));
|
|
1364
|
-
const mergeHeadExists =
|
|
1352
|
+
const mergeHeadExists = fs10.existsSync(path9.join(cwd ?? process.cwd(), ".git", "MERGE_HEAD"));
|
|
1365
1353
|
if (allowedFiles.length === 0 && !mergeHeadExists) {
|
|
1366
1354
|
return { committed: false, pushed: false, sha: "", message: "" };
|
|
1367
1355
|
}
|
|
@@ -1456,20 +1444,20 @@ function defaultCommitMessage(mode, data) {
|
|
|
1456
1444
|
}
|
|
1457
1445
|
|
|
1458
1446
|
// src/scripts/composePrompt.ts
|
|
1459
|
-
import * as
|
|
1460
|
-
import * as
|
|
1447
|
+
import * as fs11 from "fs";
|
|
1448
|
+
import * as path10 from "path";
|
|
1461
1449
|
var MUSTACHE = /\{\{\s*([a-zA-Z0-9_.-]+)\s*\}\}/g;
|
|
1462
1450
|
var composePrompt = async (ctx, profile) => {
|
|
1463
1451
|
const explicit = ctx.data.promptTemplate;
|
|
1464
1452
|
const mode = ctx.args.mode;
|
|
1465
1453
|
const candidates = [
|
|
1466
|
-
explicit ?
|
|
1467
|
-
mode ?
|
|
1468
|
-
|
|
1454
|
+
explicit ? path10.join(profile.dir, explicit) : null,
|
|
1455
|
+
mode ? path10.join(profile.dir, "prompts", `${mode}.md`) : null,
|
|
1456
|
+
path10.join(profile.dir, "prompt.md")
|
|
1469
1457
|
].filter(Boolean);
|
|
1470
1458
|
let templatePath = "";
|
|
1471
1459
|
for (const c of candidates) {
|
|
1472
|
-
if (
|
|
1460
|
+
if (fs11.existsSync(c)) {
|
|
1473
1461
|
templatePath = c;
|
|
1474
1462
|
break;
|
|
1475
1463
|
}
|
|
@@ -1477,7 +1465,7 @@ var composePrompt = async (ctx, profile) => {
|
|
|
1477
1465
|
if (!templatePath) {
|
|
1478
1466
|
throw new Error(`profile at ${profile.dir}: no prompt template found (tried ${candidates.join(", ")})`);
|
|
1479
1467
|
}
|
|
1480
|
-
const template =
|
|
1468
|
+
const template = fs11.readFileSync(templatePath, "utf-8");
|
|
1481
1469
|
const tokens = {
|
|
1482
1470
|
...stringifyAll(ctx.args, "args."),
|
|
1483
1471
|
...stringifyAll(ctx.data, ""),
|
|
@@ -1952,7 +1940,7 @@ function ensureFeatureBranch(issueNumber, title, defaultBranch, cwd) {
|
|
|
1952
1940
|
|
|
1953
1941
|
// src/gha.ts
|
|
1954
1942
|
import { execFileSync as execFileSync7 } from "child_process";
|
|
1955
|
-
import * as
|
|
1943
|
+
import * as fs12 from "fs";
|
|
1956
1944
|
function getRunUrl() {
|
|
1957
1945
|
const server = process.env.GITHUB_SERVER_URL;
|
|
1958
1946
|
const repo = process.env.GITHUB_REPOSITORY;
|
|
@@ -1963,10 +1951,10 @@ function getRunUrl() {
|
|
|
1963
1951
|
function reactToTriggerComment(cwd) {
|
|
1964
1952
|
if (process.env.GITHUB_EVENT_NAME !== "issue_comment") return;
|
|
1965
1953
|
const eventPath = process.env.GITHUB_EVENT_PATH;
|
|
1966
|
-
if (!eventPath || !
|
|
1954
|
+
if (!eventPath || !fs12.existsSync(eventPath)) return;
|
|
1967
1955
|
let event = null;
|
|
1968
1956
|
try {
|
|
1969
|
-
event = JSON.parse(
|
|
1957
|
+
event = JSON.parse(fs12.readFileSync(eventPath, "utf-8"));
|
|
1970
1958
|
} catch {
|
|
1971
1959
|
return;
|
|
1972
1960
|
}
|
|
@@ -2192,35 +2180,35 @@ function tryPostPr2(prNumber, body, cwd) {
|
|
|
2192
2180
|
|
|
2193
2181
|
// src/scripts/initFlow.ts
|
|
2194
2182
|
import { execFileSync as execFileSync9 } from "child_process";
|
|
2195
|
-
import * as
|
|
2196
|
-
import * as
|
|
2183
|
+
import * as fs14 from "fs";
|
|
2184
|
+
import * as path12 from "path";
|
|
2197
2185
|
|
|
2198
2186
|
// src/registry.ts
|
|
2199
|
-
import * as
|
|
2200
|
-
import * as
|
|
2187
|
+
import * as fs13 from "fs";
|
|
2188
|
+
import * as path11 from "path";
|
|
2201
2189
|
function getExecutablesRoot() {
|
|
2202
|
-
const here =
|
|
2190
|
+
const here = path11.dirname(new URL(import.meta.url).pathname);
|
|
2203
2191
|
const candidates = [
|
|
2204
|
-
|
|
2192
|
+
path11.join(here, "executables"),
|
|
2205
2193
|
// dev: src/
|
|
2206
|
-
|
|
2194
|
+
path11.join(here, "..", "executables"),
|
|
2207
2195
|
// built: dist/bin → dist/executables
|
|
2208
|
-
|
|
2196
|
+
path11.join(here, "..", "src", "executables")
|
|
2209
2197
|
// fallback
|
|
2210
2198
|
];
|
|
2211
2199
|
for (const c of candidates) {
|
|
2212
|
-
if (
|
|
2200
|
+
if (fs13.existsSync(c) && fs13.statSync(c).isDirectory()) return c;
|
|
2213
2201
|
}
|
|
2214
2202
|
return candidates[0];
|
|
2215
2203
|
}
|
|
2216
2204
|
function listExecutables(root = getExecutablesRoot()) {
|
|
2217
|
-
if (!
|
|
2218
|
-
const entries =
|
|
2205
|
+
if (!fs13.existsSync(root)) return [];
|
|
2206
|
+
const entries = fs13.readdirSync(root, { withFileTypes: true });
|
|
2219
2207
|
const out = [];
|
|
2220
2208
|
for (const ent of entries) {
|
|
2221
2209
|
if (!ent.isDirectory()) continue;
|
|
2222
|
-
const profilePath =
|
|
2223
|
-
if (
|
|
2210
|
+
const profilePath = path11.join(root, ent.name, "profile.json");
|
|
2211
|
+
if (fs13.existsSync(profilePath) && fs13.statSync(profilePath).isFile()) {
|
|
2224
2212
|
out.push({ name: ent.name, profilePath });
|
|
2225
2213
|
}
|
|
2226
2214
|
}
|
|
@@ -2228,8 +2216,8 @@ function listExecutables(root = getExecutablesRoot()) {
|
|
|
2228
2216
|
}
|
|
2229
2217
|
function hasExecutable(name, root = getExecutablesRoot()) {
|
|
2230
2218
|
if (!isSafeName(name)) return false;
|
|
2231
|
-
const profilePath =
|
|
2232
|
-
return
|
|
2219
|
+
const profilePath = path11.join(root, name, "profile.json");
|
|
2220
|
+
return fs13.existsSync(profilePath) && fs13.statSync(profilePath).isFile();
|
|
2233
2221
|
}
|
|
2234
2222
|
function isSafeName(name) {
|
|
2235
2223
|
return /^[a-z][a-z0-9-]*$/.test(name) && !name.includes("..");
|
|
@@ -2258,9 +2246,9 @@ function parseGenericFlags(argv) {
|
|
|
2258
2246
|
|
|
2259
2247
|
// src/scripts/initFlow.ts
|
|
2260
2248
|
function detectPackageManager(cwd) {
|
|
2261
|
-
if (
|
|
2262
|
-
if (
|
|
2263
|
-
if (
|
|
2249
|
+
if (fs14.existsSync(path12.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
|
|
2250
|
+
if (fs14.existsSync(path12.join(cwd, "yarn.lock"))) return "yarn";
|
|
2251
|
+
if (fs14.existsSync(path12.join(cwd, "bun.lockb"))) return "bun";
|
|
2264
2252
|
return "npm";
|
|
2265
2253
|
}
|
|
2266
2254
|
function qualityCommandsFor(pm) {
|
|
@@ -2381,22 +2369,22 @@ function performInit(cwd, force) {
|
|
|
2381
2369
|
const pm = detectPackageManager(cwd);
|
|
2382
2370
|
const ownerRepo = detectOwnerRepo(cwd);
|
|
2383
2371
|
const defaultBranch = defaultBranchFromGit(cwd);
|
|
2384
|
-
const configPath =
|
|
2385
|
-
if (
|
|
2372
|
+
const configPath = path12.join(cwd, "kody.config.json");
|
|
2373
|
+
if (fs14.existsSync(configPath) && !force) {
|
|
2386
2374
|
skipped.push("kody.config.json");
|
|
2387
2375
|
} else {
|
|
2388
2376
|
const cfg = makeConfig(pm, ownerRepo, defaultBranch);
|
|
2389
|
-
|
|
2377
|
+
fs14.writeFileSync(configPath, `${JSON.stringify(cfg, null, 2)}
|
|
2390
2378
|
`);
|
|
2391
2379
|
wrote.push("kody.config.json");
|
|
2392
2380
|
}
|
|
2393
|
-
const workflowDir =
|
|
2394
|
-
const workflowPath =
|
|
2395
|
-
if (
|
|
2381
|
+
const workflowDir = path12.join(cwd, ".github", "workflows");
|
|
2382
|
+
const workflowPath = path12.join(workflowDir, "kody2.yml");
|
|
2383
|
+
if (fs14.existsSync(workflowPath) && !force) {
|
|
2396
2384
|
skipped.push(".github/workflows/kody2.yml");
|
|
2397
2385
|
} else {
|
|
2398
|
-
|
|
2399
|
-
|
|
2386
|
+
fs14.mkdirSync(workflowDir, { recursive: true });
|
|
2387
|
+
fs14.writeFileSync(workflowPath, WORKFLOW_TEMPLATE);
|
|
2400
2388
|
wrote.push(".github/workflows/kody2.yml");
|
|
2401
2389
|
}
|
|
2402
2390
|
for (const exe of listExecutables()) {
|
|
@@ -2407,12 +2395,12 @@ function performInit(cwd, force) {
|
|
|
2407
2395
|
continue;
|
|
2408
2396
|
}
|
|
2409
2397
|
if (profile.kind !== "scheduled" || !profile.schedule) continue;
|
|
2410
|
-
const target =
|
|
2411
|
-
if (
|
|
2398
|
+
const target = path12.join(workflowDir, `kody2-${exe.name}.yml`);
|
|
2399
|
+
if (fs14.existsSync(target) && !force) {
|
|
2412
2400
|
skipped.push(`.github/workflows/kody2-${exe.name}.yml`);
|
|
2413
2401
|
continue;
|
|
2414
2402
|
}
|
|
2415
|
-
|
|
2403
|
+
fs14.writeFileSync(target, renderScheduledWorkflow(exe.name, profile.schedule));
|
|
2416
2404
|
wrote.push(`.github/workflows/kody2-${exe.name}.yml`);
|
|
2417
2405
|
}
|
|
2418
2406
|
return { wrote, skipped };
|
|
@@ -2919,8 +2907,8 @@ REVIEW_POSTED=https://github.com/${ctx.config.github.owner}/${ctx.config.github.
|
|
|
2919
2907
|
|
|
2920
2908
|
// src/scripts/releaseFlow.ts
|
|
2921
2909
|
import { execFileSync as execFileSync11, spawnSync } from "child_process";
|
|
2922
|
-
import * as
|
|
2923
|
-
import * as
|
|
2910
|
+
import * as fs15 from "fs";
|
|
2911
|
+
import * as path13 from "path";
|
|
2924
2912
|
function bumpVersion(current, bump) {
|
|
2925
2913
|
const m = current.match(/^(\d+)\.(\d+)\.(\d+)(.*)$/);
|
|
2926
2914
|
if (!m) throw new Error(`cannot parse version '${current}' (expected x.y.z[-suffix])`);
|
|
@@ -2936,12 +2924,12 @@ function bumpVersion(current, bump) {
|
|
|
2936
2924
|
return `${major}.${minor}.${patch}`;
|
|
2937
2925
|
}
|
|
2938
2926
|
function updateVersionInFile(file, newVersion, cwd) {
|
|
2939
|
-
const abs =
|
|
2940
|
-
if (!
|
|
2941
|
-
const content =
|
|
2927
|
+
const abs = path13.join(cwd, file);
|
|
2928
|
+
if (!fs15.existsSync(abs)) return false;
|
|
2929
|
+
const content = fs15.readFileSync(abs, "utf-8");
|
|
2942
2930
|
const updated = content.replace(/"version"\s*:\s*"[^"]+"/, `"version": "${newVersion}"`);
|
|
2943
2931
|
if (updated === content) return false;
|
|
2944
|
-
|
|
2932
|
+
fs15.writeFileSync(abs, updated);
|
|
2945
2933
|
return true;
|
|
2946
2934
|
}
|
|
2947
2935
|
function generateChangelog(cwd, newVersion, lastTag) {
|
|
@@ -2989,19 +2977,19 @@ function generateChangelog(cwd, newVersion, lastTag) {
|
|
|
2989
2977
|
return parts.join("\n");
|
|
2990
2978
|
}
|
|
2991
2979
|
function prependChangelog(cwd, entry) {
|
|
2992
|
-
const p =
|
|
2980
|
+
const p = path13.join(cwd, "CHANGELOG.md");
|
|
2993
2981
|
const header = "# Changelog\n\nAll notable changes to this project will be documented in this file.\n\n";
|
|
2994
|
-
if (
|
|
2995
|
-
const prior =
|
|
2982
|
+
if (fs15.existsSync(p)) {
|
|
2983
|
+
const prior = fs15.readFileSync(p, "utf-8");
|
|
2996
2984
|
if (/^#\s*Changelog\b/m.test(prior)) {
|
|
2997
2985
|
const idx = prior.indexOf("\n", prior.indexOf("# Changelog"));
|
|
2998
|
-
|
|
2986
|
+
fs15.writeFileSync(p, `${prior.slice(0, idx + 1)}
|
|
2999
2987
|
${entry}${prior.slice(idx + 1)}`);
|
|
3000
2988
|
} else {
|
|
3001
|
-
|
|
2989
|
+
fs15.writeFileSync(p, `${header}${entry}${prior}`);
|
|
3002
2990
|
}
|
|
3003
2991
|
} else {
|
|
3004
|
-
|
|
2992
|
+
fs15.writeFileSync(p, `${header}${entry}`);
|
|
3005
2993
|
}
|
|
3006
2994
|
}
|
|
3007
2995
|
function git3(args, cwd, timeout = 6e4) {
|
|
@@ -3052,13 +3040,13 @@ var releaseFlow = async (ctx) => {
|
|
|
3052
3040
|
};
|
|
3053
3041
|
async function runPrepare(args) {
|
|
3054
3042
|
const { cwd, bump, dryRun, versionFiles, ctx } = args;
|
|
3055
|
-
const pkgPath =
|
|
3056
|
-
if (!
|
|
3043
|
+
const pkgPath = path13.join(cwd, "package.json");
|
|
3044
|
+
if (!fs15.existsSync(pkgPath)) {
|
|
3057
3045
|
ctx.output.exitCode = 99;
|
|
3058
3046
|
ctx.output.reason = "release prepare: package.json not found";
|
|
3059
3047
|
return;
|
|
3060
3048
|
}
|
|
3061
|
-
const pkg = JSON.parse(
|
|
3049
|
+
const pkg = JSON.parse(fs15.readFileSync(pkgPath, "utf-8"));
|
|
3062
3050
|
if (typeof pkg.version !== "string") {
|
|
3063
3051
|
ctx.output.exitCode = 99;
|
|
3064
3052
|
ctx.output.reason = "release prepare: package.json has no version";
|
|
@@ -3129,8 +3117,8 @@ Merge this and then run \`kody2 release --mode finalize\`.`;
|
|
|
3129
3117
|
}
|
|
3130
3118
|
async function runFinalize(args) {
|
|
3131
3119
|
const { cwd, dryRun, timeoutMs, releaseCfg, ctx } = args;
|
|
3132
|
-
const pkgPath =
|
|
3133
|
-
const pkg = JSON.parse(
|
|
3120
|
+
const pkgPath = path13.join(cwd, "package.json");
|
|
3121
|
+
const pkg = JSON.parse(fs15.readFileSync(pkgPath, "utf-8"));
|
|
3134
3122
|
if (typeof pkg.version !== "string") {
|
|
3135
3123
|
ctx.output.exitCode = 99;
|
|
3136
3124
|
ctx.output.reason = "release finalize: package.json has no version";
|
|
@@ -3840,7 +3828,7 @@ var watchStalePrsFlow = async (ctx) => {
|
|
|
3840
3828
|
};
|
|
3841
3829
|
|
|
3842
3830
|
// src/scripts/writeRunSummary.ts
|
|
3843
|
-
import * as
|
|
3831
|
+
import * as fs16 from "fs";
|
|
3844
3832
|
var writeRunSummary = async (ctx, profile) => {
|
|
3845
3833
|
const summaryPath = process.env.GITHUB_STEP_SUMMARY;
|
|
3846
3834
|
if (!summaryPath) return;
|
|
@@ -3862,7 +3850,7 @@ var writeRunSummary = async (ctx, profile) => {
|
|
|
3862
3850
|
if (reason) lines.push(`- **Reason:** ${reason}`);
|
|
3863
3851
|
lines.push("");
|
|
3864
3852
|
try {
|
|
3865
|
-
|
|
3853
|
+
fs16.appendFileSync(summaryPath, `${lines.join("\n")}
|
|
3866
3854
|
`);
|
|
3867
3855
|
} catch {
|
|
3868
3856
|
}
|
|
@@ -4010,9 +3998,9 @@ async function runExecutable(profileName, input) {
|
|
|
4010
3998
|
data: {},
|
|
4011
3999
|
output: { exitCode: 0 }
|
|
4012
4000
|
};
|
|
4013
|
-
const ndjsonDir =
|
|
4001
|
+
const ndjsonDir = path14.join(input.cwd, ".kody2");
|
|
4014
4002
|
const invokeAgent = async (prompt) => {
|
|
4015
|
-
const externalPlugins = (profile.claudeCode.plugins ?? []).map((p) =>
|
|
4003
|
+
const externalPlugins = (profile.claudeCode.plugins ?? []).map((p) => path14.isAbsolute(p) ? p : path14.resolve(profile.dir, p)).filter((p) => p.length > 0);
|
|
4016
4004
|
const syntheticPath = ctx.data.syntheticPluginPath;
|
|
4017
4005
|
const pluginPaths = [...externalPlugins, ...syntheticPath ? [syntheticPath] : []];
|
|
4018
4006
|
return runAgent({
|
|
@@ -4028,6 +4016,7 @@ async function runExecutable(profileName, input) {
|
|
|
4028
4016
|
mcpServers: profile.claudeCode.mcpServers,
|
|
4029
4017
|
pluginPaths: pluginPaths.length > 0 ? pluginPaths : void 0,
|
|
4030
4018
|
maxTurns: profile.claudeCode.maxTurns,
|
|
4019
|
+
maxThinkingTokens: profile.claudeCode.maxThinkingTokens,
|
|
4031
4020
|
systemPromptAppend: profile.claudeCode.systemPromptAppend,
|
|
4032
4021
|
settingSources: profile.claudeCode.settingSources
|
|
4033
4022
|
});
|
|
@@ -4078,17 +4067,17 @@ async function runExecutable(profileName, input) {
|
|
|
4078
4067
|
}
|
|
4079
4068
|
}
|
|
4080
4069
|
function resolveProfilePath(profileName) {
|
|
4081
|
-
const here =
|
|
4070
|
+
const here = path14.dirname(new URL(import.meta.url).pathname);
|
|
4082
4071
|
const candidates = [
|
|
4083
|
-
|
|
4072
|
+
path14.join(here, "executables", profileName, "profile.json"),
|
|
4084
4073
|
// same-dir sibling (dev)
|
|
4085
|
-
|
|
4074
|
+
path14.join(here, "..", "executables", profileName, "profile.json"),
|
|
4086
4075
|
// up one (prod: dist/bin → dist/executables)
|
|
4087
|
-
|
|
4076
|
+
path14.join(here, "..", "src", "executables", profileName, "profile.json")
|
|
4088
4077
|
// fallback
|
|
4089
4078
|
];
|
|
4090
4079
|
for (const c of candidates) {
|
|
4091
|
-
if (
|
|
4080
|
+
if (fs17.existsSync(c)) return c;
|
|
4092
4081
|
}
|
|
4093
4082
|
return candidates[0];
|
|
4094
4083
|
}
|
|
@@ -4264,9 +4253,9 @@ function resolveAuthToken(env = process.env) {
|
|
|
4264
4253
|
return token;
|
|
4265
4254
|
}
|
|
4266
4255
|
function detectPackageManager2(cwd) {
|
|
4267
|
-
if (
|
|
4268
|
-
if (
|
|
4269
|
-
if (
|
|
4256
|
+
if (fs18.existsSync(path15.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
|
|
4257
|
+
if (fs18.existsSync(path15.join(cwd, "yarn.lock"))) return "yarn";
|
|
4258
|
+
if (fs18.existsSync(path15.join(cwd, "bun.lockb"))) return "bun";
|
|
4270
4259
|
return "npm";
|
|
4271
4260
|
}
|
|
4272
4261
|
function shellOut(cmd, args, cwd, stream = true) {
|
|
@@ -4346,11 +4335,11 @@ function configureGitIdentity(cwd) {
|
|
|
4346
4335
|
}
|
|
4347
4336
|
function postFailureTail(issueNumber, cwd, reason) {
|
|
4348
4337
|
if (!issueNumber) return;
|
|
4349
|
-
const logPath =
|
|
4338
|
+
const logPath = path15.join(cwd, ".kody2", "last-run.jsonl");
|
|
4350
4339
|
let tail = "";
|
|
4351
4340
|
try {
|
|
4352
|
-
if (
|
|
4353
|
-
const content =
|
|
4341
|
+
if (fs18.existsSync(logPath)) {
|
|
4342
|
+
const content = fs18.readFileSync(logPath, "utf-8");
|
|
4354
4343
|
tail = content.slice(-3e3);
|
|
4355
4344
|
}
|
|
4356
4345
|
} catch {
|
|
@@ -4375,7 +4364,7 @@ async function runCi(argv) {
|
|
|
4375
4364
|
return 0;
|
|
4376
4365
|
}
|
|
4377
4366
|
const args = parseCiArgs(argv);
|
|
4378
|
-
const cwd = args.cwd ?
|
|
4367
|
+
const cwd = args.cwd ? path15.resolve(args.cwd) : process.cwd();
|
|
4379
4368
|
let earlyConfig;
|
|
4380
4369
|
try {
|
|
4381
4370
|
earlyConfig = loadConfig(cwd);
|
|
@@ -4468,23 +4457,23 @@ var DEFAULT_MODEL = "claude/claude-haiku-4-5-20251001";
|
|
|
4468
4457
|
var CHAT_HELP = `kody2 chat \u2014 dashboard-driven chat session
|
|
4469
4458
|
|
|
4470
4459
|
Usage:
|
|
4471
|
-
kody2 chat [--session <id>] [--model <provider/model>]
|
|
4460
|
+
kody2 chat [--session <id>] [--message <text>] [--model <provider/model>]
|
|
4472
4461
|
[--dashboard-url <url>] [--cwd <path>] [--verbose|--quiet]
|
|
4473
4462
|
|
|
4474
|
-
All inputs may also come from env: SESSION_ID, MODEL, DASHBOARD_URL.
|
|
4475
|
-
CLI flags take precedence over env. SESSION_ID
|
|
4476
|
-
(the runner long-polls the dashboard for user turns and pushes events back).
|
|
4463
|
+
All inputs may also come from env: SESSION_ID, INIT_MESSAGE, MODEL, DASHBOARD_URL.
|
|
4464
|
+
CLI flags take precedence over env. SESSION_ID is required.
|
|
4477
4465
|
|
|
4478
4466
|
Exit codes:
|
|
4479
|
-
0
|
|
4480
|
-
64 bad inputs
|
|
4481
|
-
99 runtime failure (agent crash,
|
|
4467
|
+
0 reply emitted successfully
|
|
4468
|
+
64 bad inputs (missing session, empty history)
|
|
4469
|
+
99 runtime failure (agent crash, LiteLLM failure)
|
|
4482
4470
|
`;
|
|
4483
4471
|
function parseChatArgs(argv, env = process.env) {
|
|
4484
4472
|
const result = { errors: [] };
|
|
4485
4473
|
for (let i = 0; i < argv.length; i++) {
|
|
4486
4474
|
const arg = argv[i];
|
|
4487
4475
|
if (arg === "--session") result.sessionId = argv[++i];
|
|
4476
|
+
else if (arg === "--message") result.initMessage = argv[++i];
|
|
4488
4477
|
else if (arg === "--model") result.model = argv[++i];
|
|
4489
4478
|
else if (arg === "--dashboard-url") result.dashboardUrl = argv[++i];
|
|
4490
4479
|
else if (arg === "--cwd") result.cwd = argv[++i];
|
|
@@ -4495,18 +4484,34 @@ function parseChatArgs(argv, env = process.env) {
|
|
|
4495
4484
|
else if (arg) result.errors.push(`unexpected positional: ${arg}`);
|
|
4496
4485
|
}
|
|
4497
4486
|
result.sessionId = result.sessionId ?? env.SESSION_ID ?? void 0;
|
|
4487
|
+
result.initMessage = result.initMessage ?? env.INIT_MESSAGE ?? void 0;
|
|
4498
4488
|
result.model = result.model ?? env.MODEL ?? void 0;
|
|
4499
4489
|
result.dashboardUrl = result.dashboardUrl ?? env.DASHBOARD_URL ?? void 0;
|
|
4500
|
-
for (const key of ["sessionId", "model", "dashboardUrl"]) {
|
|
4490
|
+
for (const key of ["sessionId", "initMessage", "model", "dashboardUrl"]) {
|
|
4501
4491
|
const v = result[key];
|
|
4502
4492
|
if (typeof v === "string" && v.trim() === "") result[key] = void 0;
|
|
4503
4493
|
}
|
|
4504
|
-
if (!result.errors.includes("__HELP__")) {
|
|
4505
|
-
|
|
4506
|
-
if (!result.dashboardUrl) result.errors.push("--dashboard-url <url> (or DASHBOARD_URL env) is required");
|
|
4494
|
+
if (!result.sessionId && !result.errors.includes("__HELP__")) {
|
|
4495
|
+
result.errors.push("--session <id> (or SESSION_ID env) is required");
|
|
4507
4496
|
}
|
|
4508
4497
|
return result;
|
|
4509
4498
|
}
|
|
4499
|
+
function commitChatFiles(cwd, sessionId, verbose) {
|
|
4500
|
+
const sessionFile = path16.relative(cwd, sessionFilePath(cwd, sessionId));
|
|
4501
|
+
const eventsFile = path16.relative(cwd, eventsFilePath(cwd, sessionId));
|
|
4502
|
+
const paths = [sessionFile, eventsFile].filter((p) => fs19.existsSync(path16.join(cwd, p)));
|
|
4503
|
+
if (paths.length === 0) return;
|
|
4504
|
+
const opts = { cwd, stdio: verbose ? "inherit" : "pipe" };
|
|
4505
|
+
try {
|
|
4506
|
+
execFileSync16("git", ["add", ...paths], opts);
|
|
4507
|
+
execFileSync16("git", ["commit", "--quiet", "-m", `chat: reply for ${sessionId}`], opts);
|
|
4508
|
+
execFileSync16("git", ["push", "--quiet", "origin", "HEAD"], opts);
|
|
4509
|
+
} catch (err) {
|
|
4510
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
4511
|
+
process.stderr.write(`[kody2:chat] commit/push skipped: ${msg}
|
|
4512
|
+
`);
|
|
4513
|
+
}
|
|
4514
|
+
}
|
|
4510
4515
|
function tryLoadConfig(cwd) {
|
|
4511
4516
|
try {
|
|
4512
4517
|
return loadConfig(cwd);
|
|
@@ -4514,6 +4519,11 @@ function tryLoadConfig(cwd) {
|
|
|
4514
4519
|
return null;
|
|
4515
4520
|
}
|
|
4516
4521
|
}
|
|
4522
|
+
function buildSink(cwd, sessionId, dashboardUrl) {
|
|
4523
|
+
const sinks = [new FileSink(eventsFilePath(cwd, sessionId))];
|
|
4524
|
+
if (dashboardUrl) sinks.push(new HttpSink(dashboardUrl, sessionId));
|
|
4525
|
+
return new TeeSink(sinks);
|
|
4526
|
+
}
|
|
4517
4527
|
async function runChat(argv) {
|
|
4518
4528
|
if (argv.includes("--help") || argv.includes("-h")) {
|
|
4519
4529
|
process.stdout.write(CHAT_HELP);
|
|
@@ -4527,9 +4537,8 @@ async function runChat(argv) {
|
|
|
4527
4537
|
${CHAT_HELP}`);
|
|
4528
4538
|
return 64;
|
|
4529
4539
|
}
|
|
4530
|
-
const cwd = args.cwd ?
|
|
4540
|
+
const cwd = args.cwd ? path16.resolve(args.cwd) : process.cwd();
|
|
4531
4541
|
const sessionId = args.sessionId;
|
|
4532
|
-
const dashboardUrl = args.dashboardUrl;
|
|
4533
4542
|
const unpackedSecrets = unpackAllSecrets();
|
|
4534
4543
|
if (unpackedSecrets > 0) {
|
|
4535
4544
|
process.stdout.write(`\u2192 kody2: unpacked ${unpackedSecrets} secret(s) from ALL_SECRETS
|
|
@@ -4555,20 +4564,13 @@ ${CHAT_HELP}`);
|
|
|
4555
4564
|
return 99;
|
|
4556
4565
|
}
|
|
4557
4566
|
}
|
|
4558
|
-
let sink;
|
|
4559
|
-
try {
|
|
4560
|
-
sink = new HttpSink(dashboardUrl, sessionId);
|
|
4561
|
-
} catch (err) {
|
|
4562
|
-
process.stderr.write(`error: ${err instanceof Error ? err.message : String(err)}
|
|
4563
|
-
`);
|
|
4564
|
-
return 64;
|
|
4565
|
-
}
|
|
4566
4567
|
let litellm = null;
|
|
4567
4568
|
try {
|
|
4568
4569
|
litellm = await startLitellmIfNeeded(model, cwd);
|
|
4569
4570
|
} catch (err) {
|
|
4570
4571
|
const msg = err instanceof Error ? err.message : String(err);
|
|
4571
|
-
|
|
4572
|
+
const sink2 = buildSink(cwd, sessionId, args.dashboardUrl);
|
|
4573
|
+
await sink2.emit({
|
|
4572
4574
|
event: "chat.error",
|
|
4573
4575
|
payload: { sessionId, error: `litellm startup failed: ${msg}` },
|
|
4574
4576
|
runId: makeRunId(sessionId, "error"),
|
|
@@ -4576,33 +4578,21 @@ ${CHAT_HELP}`);
|
|
|
4576
4578
|
});
|
|
4577
4579
|
return 99;
|
|
4578
4580
|
}
|
|
4579
|
-
|
|
4581
|
+
const sessionFile = sessionFilePath(cwd, sessionId);
|
|
4582
|
+
if (args.initMessage) seedInitialMessage(sessionFile, args.initMessage);
|
|
4583
|
+
const sink = buildSink(cwd, sessionId, args.dashboardUrl);
|
|
4580
4584
|
try {
|
|
4581
|
-
|
|
4582
|
-
} catch (err) {
|
|
4583
|
-
process.stderr.write(`error: ${err instanceof Error ? err.message : String(err)}
|
|
4584
|
-
`);
|
|
4585
|
-
try {
|
|
4586
|
-
litellm?.kill();
|
|
4587
|
-
} catch {
|
|
4588
|
-
}
|
|
4589
|
-
return 64;
|
|
4590
|
-
}
|
|
4591
|
-
process.stdout.write(`\u2192 kody2 chat: session ${sessionId}, model ${model.provider}/${model.model}
|
|
4592
|
-
`);
|
|
4593
|
-
try {
|
|
4594
|
-
const result = await runChatSession({
|
|
4585
|
+
const result = await runChatTurn({
|
|
4595
4586
|
sessionId,
|
|
4587
|
+
sessionFile,
|
|
4596
4588
|
cwd,
|
|
4597
4589
|
model,
|
|
4598
4590
|
litellmUrl: litellm?.url ?? null,
|
|
4599
4591
|
sink,
|
|
4600
|
-
pull,
|
|
4601
4592
|
verbose: args.verbose,
|
|
4602
4593
|
quiet: args.quiet
|
|
4603
4594
|
});
|
|
4604
|
-
|
|
4605
|
-
`);
|
|
4595
|
+
commitChatFiles(cwd, sessionId, args.verbose ?? false);
|
|
4606
4596
|
return result.exitCode;
|
|
4607
4597
|
} finally {
|
|
4608
4598
|
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.35",
|
|
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
|
+
}
|