@kody-ade/kody-engine 0.2.32 → 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 +345 -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
|
}
|
|
@@ -1440,20 +1460,20 @@ function defaultCommitMessage(mode, data) {
|
|
|
1440
1460
|
}
|
|
1441
1461
|
|
|
1442
1462
|
// src/scripts/composePrompt.ts
|
|
1443
|
-
import * as
|
|
1444
|
-
import * as
|
|
1463
|
+
import * as fs9 from "fs";
|
|
1464
|
+
import * as path8 from "path";
|
|
1445
1465
|
var MUSTACHE = /\{\{\s*([a-zA-Z0-9_.-]+)\s*\}\}/g;
|
|
1446
1466
|
var composePrompt = async (ctx, profile) => {
|
|
1447
1467
|
const explicit = ctx.data.promptTemplate;
|
|
1448
1468
|
const mode = ctx.args.mode;
|
|
1449
1469
|
const candidates = [
|
|
1450
|
-
explicit ?
|
|
1451
|
-
mode ?
|
|
1452
|
-
|
|
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")
|
|
1453
1473
|
].filter(Boolean);
|
|
1454
1474
|
let templatePath = "";
|
|
1455
1475
|
for (const c of candidates) {
|
|
1456
|
-
if (
|
|
1476
|
+
if (fs9.existsSync(c)) {
|
|
1457
1477
|
templatePath = c;
|
|
1458
1478
|
break;
|
|
1459
1479
|
}
|
|
@@ -1461,7 +1481,7 @@ var composePrompt = async (ctx, profile) => {
|
|
|
1461
1481
|
if (!templatePath) {
|
|
1462
1482
|
throw new Error(`profile at ${profile.dir}: no prompt template found (tried ${candidates.join(", ")})`);
|
|
1463
1483
|
}
|
|
1464
|
-
const template =
|
|
1484
|
+
const template = fs9.readFileSync(templatePath, "utf-8");
|
|
1465
1485
|
const tokens = {
|
|
1466
1486
|
...stringifyAll(ctx.args, "args."),
|
|
1467
1487
|
...stringifyAll(ctx.data, ""),
|
|
@@ -1936,7 +1956,7 @@ function ensureFeatureBranch(issueNumber, title, defaultBranch, cwd) {
|
|
|
1936
1956
|
|
|
1937
1957
|
// src/gha.ts
|
|
1938
1958
|
import { execFileSync as execFileSync7 } from "child_process";
|
|
1939
|
-
import * as
|
|
1959
|
+
import * as fs10 from "fs";
|
|
1940
1960
|
function getRunUrl() {
|
|
1941
1961
|
const server = process.env.GITHUB_SERVER_URL;
|
|
1942
1962
|
const repo = process.env.GITHUB_REPOSITORY;
|
|
@@ -1947,10 +1967,10 @@ function getRunUrl() {
|
|
|
1947
1967
|
function reactToTriggerComment(cwd) {
|
|
1948
1968
|
if (process.env.GITHUB_EVENT_NAME !== "issue_comment") return;
|
|
1949
1969
|
const eventPath = process.env.GITHUB_EVENT_PATH;
|
|
1950
|
-
if (!eventPath || !
|
|
1970
|
+
if (!eventPath || !fs10.existsSync(eventPath)) return;
|
|
1951
1971
|
let event = null;
|
|
1952
1972
|
try {
|
|
1953
|
-
event = JSON.parse(
|
|
1973
|
+
event = JSON.parse(fs10.readFileSync(eventPath, "utf-8"));
|
|
1954
1974
|
} catch {
|
|
1955
1975
|
return;
|
|
1956
1976
|
}
|
|
@@ -2176,35 +2196,35 @@ function tryPostPr2(prNumber, body, cwd) {
|
|
|
2176
2196
|
|
|
2177
2197
|
// src/scripts/initFlow.ts
|
|
2178
2198
|
import { execFileSync as execFileSync9 } from "child_process";
|
|
2179
|
-
import * as
|
|
2180
|
-
import * as
|
|
2199
|
+
import * as fs12 from "fs";
|
|
2200
|
+
import * as path10 from "path";
|
|
2181
2201
|
|
|
2182
2202
|
// src/registry.ts
|
|
2183
|
-
import * as
|
|
2184
|
-
import * as
|
|
2203
|
+
import * as fs11 from "fs";
|
|
2204
|
+
import * as path9 from "path";
|
|
2185
2205
|
function getExecutablesRoot() {
|
|
2186
|
-
const here =
|
|
2206
|
+
const here = path9.dirname(new URL(import.meta.url).pathname);
|
|
2187
2207
|
const candidates = [
|
|
2188
|
-
|
|
2208
|
+
path9.join(here, "executables"),
|
|
2189
2209
|
// dev: src/
|
|
2190
|
-
|
|
2210
|
+
path9.join(here, "..", "executables"),
|
|
2191
2211
|
// built: dist/bin → dist/executables
|
|
2192
|
-
|
|
2212
|
+
path9.join(here, "..", "src", "executables")
|
|
2193
2213
|
// fallback
|
|
2194
2214
|
];
|
|
2195
2215
|
for (const c of candidates) {
|
|
2196
|
-
if (
|
|
2216
|
+
if (fs11.existsSync(c) && fs11.statSync(c).isDirectory()) return c;
|
|
2197
2217
|
}
|
|
2198
2218
|
return candidates[0];
|
|
2199
2219
|
}
|
|
2200
2220
|
function listExecutables(root = getExecutablesRoot()) {
|
|
2201
|
-
if (!
|
|
2202
|
-
const entries =
|
|
2221
|
+
if (!fs11.existsSync(root)) return [];
|
|
2222
|
+
const entries = fs11.readdirSync(root, { withFileTypes: true });
|
|
2203
2223
|
const out = [];
|
|
2204
2224
|
for (const ent of entries) {
|
|
2205
2225
|
if (!ent.isDirectory()) continue;
|
|
2206
|
-
const profilePath =
|
|
2207
|
-
if (
|
|
2226
|
+
const profilePath = path9.join(root, ent.name, "profile.json");
|
|
2227
|
+
if (fs11.existsSync(profilePath) && fs11.statSync(profilePath).isFile()) {
|
|
2208
2228
|
out.push({ name: ent.name, profilePath });
|
|
2209
2229
|
}
|
|
2210
2230
|
}
|
|
@@ -2212,8 +2232,8 @@ function listExecutables(root = getExecutablesRoot()) {
|
|
|
2212
2232
|
}
|
|
2213
2233
|
function hasExecutable(name, root = getExecutablesRoot()) {
|
|
2214
2234
|
if (!isSafeName(name)) return false;
|
|
2215
|
-
const profilePath =
|
|
2216
|
-
return
|
|
2235
|
+
const profilePath = path9.join(root, name, "profile.json");
|
|
2236
|
+
return fs11.existsSync(profilePath) && fs11.statSync(profilePath).isFile();
|
|
2217
2237
|
}
|
|
2218
2238
|
function isSafeName(name) {
|
|
2219
2239
|
return /^[a-z][a-z0-9-]*$/.test(name) && !name.includes("..");
|
|
@@ -2242,9 +2262,9 @@ function parseGenericFlags(argv) {
|
|
|
2242
2262
|
|
|
2243
2263
|
// src/scripts/initFlow.ts
|
|
2244
2264
|
function detectPackageManager(cwd) {
|
|
2245
|
-
if (
|
|
2246
|
-
if (
|
|
2247
|
-
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";
|
|
2248
2268
|
return "npm";
|
|
2249
2269
|
}
|
|
2250
2270
|
function qualityCommandsFor(pm) {
|
|
@@ -2365,22 +2385,22 @@ function performInit(cwd, force) {
|
|
|
2365
2385
|
const pm = detectPackageManager(cwd);
|
|
2366
2386
|
const ownerRepo = detectOwnerRepo(cwd);
|
|
2367
2387
|
const defaultBranch = defaultBranchFromGit(cwd);
|
|
2368
|
-
const configPath =
|
|
2369
|
-
if (
|
|
2388
|
+
const configPath = path10.join(cwd, "kody.config.json");
|
|
2389
|
+
if (fs12.existsSync(configPath) && !force) {
|
|
2370
2390
|
skipped.push("kody.config.json");
|
|
2371
2391
|
} else {
|
|
2372
2392
|
const cfg = makeConfig(pm, ownerRepo, defaultBranch);
|
|
2373
|
-
|
|
2393
|
+
fs12.writeFileSync(configPath, `${JSON.stringify(cfg, null, 2)}
|
|
2374
2394
|
`);
|
|
2375
2395
|
wrote.push("kody.config.json");
|
|
2376
2396
|
}
|
|
2377
|
-
const workflowDir =
|
|
2378
|
-
const workflowPath =
|
|
2379
|
-
if (
|
|
2397
|
+
const workflowDir = path10.join(cwd, ".github", "workflows");
|
|
2398
|
+
const workflowPath = path10.join(workflowDir, "kody2.yml");
|
|
2399
|
+
if (fs12.existsSync(workflowPath) && !force) {
|
|
2380
2400
|
skipped.push(".github/workflows/kody2.yml");
|
|
2381
2401
|
} else {
|
|
2382
|
-
|
|
2383
|
-
|
|
2402
|
+
fs12.mkdirSync(workflowDir, { recursive: true });
|
|
2403
|
+
fs12.writeFileSync(workflowPath, WORKFLOW_TEMPLATE);
|
|
2384
2404
|
wrote.push(".github/workflows/kody2.yml");
|
|
2385
2405
|
}
|
|
2386
2406
|
for (const exe of listExecutables()) {
|
|
@@ -2391,12 +2411,12 @@ function performInit(cwd, force) {
|
|
|
2391
2411
|
continue;
|
|
2392
2412
|
}
|
|
2393
2413
|
if (profile.kind !== "scheduled" || !profile.schedule) continue;
|
|
2394
|
-
const target =
|
|
2395
|
-
if (
|
|
2414
|
+
const target = path10.join(workflowDir, `kody2-${exe.name}.yml`);
|
|
2415
|
+
if (fs12.existsSync(target) && !force) {
|
|
2396
2416
|
skipped.push(`.github/workflows/kody2-${exe.name}.yml`);
|
|
2397
2417
|
continue;
|
|
2398
2418
|
}
|
|
2399
|
-
|
|
2419
|
+
fs12.writeFileSync(target, renderScheduledWorkflow(exe.name, profile.schedule));
|
|
2400
2420
|
wrote.push(`.github/workflows/kody2-${exe.name}.yml`);
|
|
2401
2421
|
}
|
|
2402
2422
|
return { wrote, skipped };
|
|
@@ -2903,8 +2923,8 @@ REVIEW_POSTED=https://github.com/${ctx.config.github.owner}/${ctx.config.github.
|
|
|
2903
2923
|
|
|
2904
2924
|
// src/scripts/releaseFlow.ts
|
|
2905
2925
|
import { execFileSync as execFileSync11, spawnSync } from "child_process";
|
|
2906
|
-
import * as
|
|
2907
|
-
import * as
|
|
2926
|
+
import * as fs13 from "fs";
|
|
2927
|
+
import * as path11 from "path";
|
|
2908
2928
|
function bumpVersion(current, bump) {
|
|
2909
2929
|
const m = current.match(/^(\d+)\.(\d+)\.(\d+)(.*)$/);
|
|
2910
2930
|
if (!m) throw new Error(`cannot parse version '${current}' (expected x.y.z[-suffix])`);
|
|
@@ -2920,12 +2940,12 @@ function bumpVersion(current, bump) {
|
|
|
2920
2940
|
return `${major}.${minor}.${patch}`;
|
|
2921
2941
|
}
|
|
2922
2942
|
function updateVersionInFile(file, newVersion, cwd) {
|
|
2923
|
-
const abs =
|
|
2924
|
-
if (!
|
|
2925
|
-
const content =
|
|
2943
|
+
const abs = path11.join(cwd, file);
|
|
2944
|
+
if (!fs13.existsSync(abs)) return false;
|
|
2945
|
+
const content = fs13.readFileSync(abs, "utf-8");
|
|
2926
2946
|
const updated = content.replace(/"version"\s*:\s*"[^"]+"/, `"version": "${newVersion}"`);
|
|
2927
2947
|
if (updated === content) return false;
|
|
2928
|
-
|
|
2948
|
+
fs13.writeFileSync(abs, updated);
|
|
2929
2949
|
return true;
|
|
2930
2950
|
}
|
|
2931
2951
|
function generateChangelog(cwd, newVersion, lastTag) {
|
|
@@ -2973,19 +2993,19 @@ function generateChangelog(cwd, newVersion, lastTag) {
|
|
|
2973
2993
|
return parts.join("\n");
|
|
2974
2994
|
}
|
|
2975
2995
|
function prependChangelog(cwd, entry) {
|
|
2976
|
-
const p =
|
|
2996
|
+
const p = path11.join(cwd, "CHANGELOG.md");
|
|
2977
2997
|
const header = "# Changelog\n\nAll notable changes to this project will be documented in this file.\n\n";
|
|
2978
|
-
if (
|
|
2979
|
-
const prior =
|
|
2998
|
+
if (fs13.existsSync(p)) {
|
|
2999
|
+
const prior = fs13.readFileSync(p, "utf-8");
|
|
2980
3000
|
if (/^#\s*Changelog\b/m.test(prior)) {
|
|
2981
3001
|
const idx = prior.indexOf("\n", prior.indexOf("# Changelog"));
|
|
2982
|
-
|
|
3002
|
+
fs13.writeFileSync(p, `${prior.slice(0, idx + 1)}
|
|
2983
3003
|
${entry}${prior.slice(idx + 1)}`);
|
|
2984
3004
|
} else {
|
|
2985
|
-
|
|
3005
|
+
fs13.writeFileSync(p, `${header}${entry}${prior}`);
|
|
2986
3006
|
}
|
|
2987
3007
|
} else {
|
|
2988
|
-
|
|
3008
|
+
fs13.writeFileSync(p, `${header}${entry}`);
|
|
2989
3009
|
}
|
|
2990
3010
|
}
|
|
2991
3011
|
function git3(args, cwd, timeout = 6e4) {
|
|
@@ -3036,13 +3056,13 @@ var releaseFlow = async (ctx) => {
|
|
|
3036
3056
|
};
|
|
3037
3057
|
async function runPrepare(args) {
|
|
3038
3058
|
const { cwd, bump, dryRun, versionFiles, ctx } = args;
|
|
3039
|
-
const pkgPath =
|
|
3040
|
-
if (!
|
|
3059
|
+
const pkgPath = path11.join(cwd, "package.json");
|
|
3060
|
+
if (!fs13.existsSync(pkgPath)) {
|
|
3041
3061
|
ctx.output.exitCode = 99;
|
|
3042
3062
|
ctx.output.reason = "release prepare: package.json not found";
|
|
3043
3063
|
return;
|
|
3044
3064
|
}
|
|
3045
|
-
const pkg = JSON.parse(
|
|
3065
|
+
const pkg = JSON.parse(fs13.readFileSync(pkgPath, "utf-8"));
|
|
3046
3066
|
if (typeof pkg.version !== "string") {
|
|
3047
3067
|
ctx.output.exitCode = 99;
|
|
3048
3068
|
ctx.output.reason = "release prepare: package.json has no version";
|
|
@@ -3113,8 +3133,8 @@ Merge this and then run \`kody2 release --mode finalize\`.`;
|
|
|
3113
3133
|
}
|
|
3114
3134
|
async function runFinalize(args) {
|
|
3115
3135
|
const { cwd, dryRun, timeoutMs, releaseCfg, ctx } = args;
|
|
3116
|
-
const pkgPath =
|
|
3117
|
-
const pkg = JSON.parse(
|
|
3136
|
+
const pkgPath = path11.join(cwd, "package.json");
|
|
3137
|
+
const pkg = JSON.parse(fs13.readFileSync(pkgPath, "utf-8"));
|
|
3118
3138
|
if (typeof pkg.version !== "string") {
|
|
3119
3139
|
ctx.output.exitCode = 99;
|
|
3120
3140
|
ctx.output.reason = "release finalize: package.json has no version";
|
|
@@ -3792,7 +3812,7 @@ var watchStalePrsFlow = async (ctx) => {
|
|
|
3792
3812
|
};
|
|
3793
3813
|
|
|
3794
3814
|
// src/scripts/writeRunSummary.ts
|
|
3795
|
-
import * as
|
|
3815
|
+
import * as fs14 from "fs";
|
|
3796
3816
|
var writeRunSummary = async (ctx, profile) => {
|
|
3797
3817
|
const summaryPath = process.env.GITHUB_STEP_SUMMARY;
|
|
3798
3818
|
if (!summaryPath) return;
|
|
@@ -3814,7 +3834,7 @@ var writeRunSummary = async (ctx, profile) => {
|
|
|
3814
3834
|
if (reason) lines.push(`- **Reason:** ${reason}`);
|
|
3815
3835
|
lines.push("");
|
|
3816
3836
|
try {
|
|
3817
|
-
|
|
3837
|
+
fs14.appendFileSync(summaryPath, `${lines.join("\n")}
|
|
3818
3838
|
`);
|
|
3819
3839
|
} catch {
|
|
3820
3840
|
}
|
|
@@ -3962,9 +3982,9 @@ async function runExecutable(profileName, input) {
|
|
|
3962
3982
|
data: {},
|
|
3963
3983
|
output: { exitCode: 0 }
|
|
3964
3984
|
};
|
|
3965
|
-
const ndjsonDir =
|
|
3985
|
+
const ndjsonDir = path12.join(input.cwd, ".kody2");
|
|
3966
3986
|
const invokeAgent = async (prompt) => {
|
|
3967
|
-
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);
|
|
3968
3988
|
const syntheticPath = ctx.data.syntheticPluginPath;
|
|
3969
3989
|
const pluginPaths = [...externalPlugins, ...syntheticPath ? [syntheticPath] : []];
|
|
3970
3990
|
return runAgent({
|
|
@@ -3980,6 +4000,7 @@ async function runExecutable(profileName, input) {
|
|
|
3980
4000
|
mcpServers: profile.claudeCode.mcpServers,
|
|
3981
4001
|
pluginPaths: pluginPaths.length > 0 ? pluginPaths : void 0,
|
|
3982
4002
|
maxTurns: profile.claudeCode.maxTurns,
|
|
4003
|
+
maxThinkingTokens: profile.claudeCode.maxThinkingTokens,
|
|
3983
4004
|
systemPromptAppend: profile.claudeCode.systemPromptAppend,
|
|
3984
4005
|
settingSources: profile.claudeCode.settingSources
|
|
3985
4006
|
});
|
|
@@ -4030,17 +4051,17 @@ async function runExecutable(profileName, input) {
|
|
|
4030
4051
|
}
|
|
4031
4052
|
}
|
|
4032
4053
|
function resolveProfilePath(profileName) {
|
|
4033
|
-
const here =
|
|
4054
|
+
const here = path12.dirname(new URL(import.meta.url).pathname);
|
|
4034
4055
|
const candidates = [
|
|
4035
|
-
|
|
4056
|
+
path12.join(here, "executables", profileName, "profile.json"),
|
|
4036
4057
|
// same-dir sibling (dev)
|
|
4037
|
-
|
|
4058
|
+
path12.join(here, "..", "executables", profileName, "profile.json"),
|
|
4038
4059
|
// up one (prod: dist/bin → dist/executables)
|
|
4039
|
-
|
|
4060
|
+
path12.join(here, "..", "src", "executables", profileName, "profile.json")
|
|
4040
4061
|
// fallback
|
|
4041
4062
|
];
|
|
4042
4063
|
for (const c of candidates) {
|
|
4043
|
-
if (
|
|
4064
|
+
if (fs15.existsSync(c)) return c;
|
|
4044
4065
|
}
|
|
4045
4066
|
return candidates[0];
|
|
4046
4067
|
}
|
|
@@ -4216,9 +4237,9 @@ function resolveAuthToken(env = process.env) {
|
|
|
4216
4237
|
return token;
|
|
4217
4238
|
}
|
|
4218
4239
|
function detectPackageManager2(cwd) {
|
|
4219
|
-
if (
|
|
4220
|
-
if (
|
|
4221
|
-
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";
|
|
4222
4243
|
return "npm";
|
|
4223
4244
|
}
|
|
4224
4245
|
function shellOut(cmd, args, cwd, stream = true) {
|
|
@@ -4298,11 +4319,11 @@ function configureGitIdentity(cwd) {
|
|
|
4298
4319
|
}
|
|
4299
4320
|
function postFailureTail(issueNumber, cwd, reason) {
|
|
4300
4321
|
if (!issueNumber) return;
|
|
4301
|
-
const logPath =
|
|
4322
|
+
const logPath = path13.join(cwd, ".kody2", "last-run.jsonl");
|
|
4302
4323
|
let tail = "";
|
|
4303
4324
|
try {
|
|
4304
|
-
if (
|
|
4305
|
-
const content =
|
|
4325
|
+
if (fs16.existsSync(logPath)) {
|
|
4326
|
+
const content = fs16.readFileSync(logPath, "utf-8");
|
|
4306
4327
|
tail = content.slice(-3e3);
|
|
4307
4328
|
}
|
|
4308
4329
|
} catch {
|
|
@@ -4327,7 +4348,7 @@ async function runCi(argv) {
|
|
|
4327
4348
|
return 0;
|
|
4328
4349
|
}
|
|
4329
4350
|
const args = parseCiArgs(argv);
|
|
4330
|
-
const cwd = args.cwd ?
|
|
4351
|
+
const cwd = args.cwd ? path13.resolve(args.cwd) : process.cwd();
|
|
4331
4352
|
let earlyConfig;
|
|
4332
4353
|
try {
|
|
4333
4354
|
earlyConfig = loadConfig(cwd);
|
|
@@ -4420,23 +4441,23 @@ var DEFAULT_MODEL = "claude/claude-haiku-4-5-20251001";
|
|
|
4420
4441
|
var CHAT_HELP = `kody2 chat \u2014 dashboard-driven chat session
|
|
4421
4442
|
|
|
4422
4443
|
Usage:
|
|
4423
|
-
kody2 chat [--session <id>] [--
|
|
4444
|
+
kody2 chat [--session <id>] [--model <provider/model>]
|
|
4424
4445
|
[--dashboard-url <url>] [--cwd <path>] [--verbose|--quiet]
|
|
4425
4446
|
|
|
4426
|
-
All inputs may also come from env: SESSION_ID,
|
|
4427
|
-
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).
|
|
4428
4450
|
|
|
4429
4451
|
Exit codes:
|
|
4430
|
-
0
|
|
4431
|
-
64 bad inputs
|
|
4432
|
-
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)
|
|
4433
4455
|
`;
|
|
4434
4456
|
function parseChatArgs(argv, env = process.env) {
|
|
4435
4457
|
const result = { errors: [] };
|
|
4436
4458
|
for (let i = 0; i < argv.length; i++) {
|
|
4437
4459
|
const arg = argv[i];
|
|
4438
4460
|
if (arg === "--session") result.sessionId = argv[++i];
|
|
4439
|
-
else if (arg === "--message") result.initMessage = argv[++i];
|
|
4440
4461
|
else if (arg === "--model") result.model = argv[++i];
|
|
4441
4462
|
else if (arg === "--dashboard-url") result.dashboardUrl = argv[++i];
|
|
4442
4463
|
else if (arg === "--cwd") result.cwd = argv[++i];
|
|
@@ -4447,34 +4468,18 @@ function parseChatArgs(argv, env = process.env) {
|
|
|
4447
4468
|
else if (arg) result.errors.push(`unexpected positional: ${arg}`);
|
|
4448
4469
|
}
|
|
4449
4470
|
result.sessionId = result.sessionId ?? env.SESSION_ID ?? void 0;
|
|
4450
|
-
result.initMessage = result.initMessage ?? env.INIT_MESSAGE ?? void 0;
|
|
4451
4471
|
result.model = result.model ?? env.MODEL ?? void 0;
|
|
4452
4472
|
result.dashboardUrl = result.dashboardUrl ?? env.DASHBOARD_URL ?? void 0;
|
|
4453
|
-
for (const key of ["sessionId", "
|
|
4473
|
+
for (const key of ["sessionId", "model", "dashboardUrl"]) {
|
|
4454
4474
|
const v = result[key];
|
|
4455
4475
|
if (typeof v === "string" && v.trim() === "") result[key] = void 0;
|
|
4456
4476
|
}
|
|
4457
|
-
if (!result.
|
|
4458
|
-
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");
|
|
4459
4480
|
}
|
|
4460
4481
|
return result;
|
|
4461
4482
|
}
|
|
4462
|
-
function commitChatFiles(cwd, sessionId, verbose) {
|
|
4463
|
-
const sessionFile = path16.relative(cwd, sessionFilePath(cwd, sessionId));
|
|
4464
|
-
const eventsFile = path16.relative(cwd, eventsFilePath(cwd, sessionId));
|
|
4465
|
-
const paths = [sessionFile, eventsFile].filter((p) => fs19.existsSync(path16.join(cwd, p)));
|
|
4466
|
-
if (paths.length === 0) return;
|
|
4467
|
-
const opts = { cwd, stdio: verbose ? "inherit" : "pipe" };
|
|
4468
|
-
try {
|
|
4469
|
-
execFileSync16("git", ["add", ...paths], opts);
|
|
4470
|
-
execFileSync16("git", ["commit", "--quiet", "-m", `chat: reply for ${sessionId}`], opts);
|
|
4471
|
-
execFileSync16("git", ["push", "--quiet", "origin", "HEAD"], opts);
|
|
4472
|
-
} catch (err) {
|
|
4473
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
4474
|
-
process.stderr.write(`[kody2:chat] commit/push skipped: ${msg}
|
|
4475
|
-
`);
|
|
4476
|
-
}
|
|
4477
|
-
}
|
|
4478
4483
|
function tryLoadConfig(cwd) {
|
|
4479
4484
|
try {
|
|
4480
4485
|
return loadConfig(cwd);
|
|
@@ -4482,11 +4487,6 @@ function tryLoadConfig(cwd) {
|
|
|
4482
4487
|
return null;
|
|
4483
4488
|
}
|
|
4484
4489
|
}
|
|
4485
|
-
function buildSink(cwd, sessionId, dashboardUrl) {
|
|
4486
|
-
const sinks = [new FileSink(eventsFilePath(cwd, sessionId))];
|
|
4487
|
-
if (dashboardUrl) sinks.push(new HttpSink(dashboardUrl, sessionId));
|
|
4488
|
-
return new TeeSink(sinks);
|
|
4489
|
-
}
|
|
4490
4490
|
async function runChat(argv) {
|
|
4491
4491
|
if (argv.includes("--help") || argv.includes("-h")) {
|
|
4492
4492
|
process.stdout.write(CHAT_HELP);
|
|
@@ -4500,8 +4500,9 @@ async function runChat(argv) {
|
|
|
4500
4500
|
${CHAT_HELP}`);
|
|
4501
4501
|
return 64;
|
|
4502
4502
|
}
|
|
4503
|
-
const cwd = args.cwd ?
|
|
4503
|
+
const cwd = args.cwd ? path14.resolve(args.cwd) : process.cwd();
|
|
4504
4504
|
const sessionId = args.sessionId;
|
|
4505
|
+
const dashboardUrl = args.dashboardUrl;
|
|
4505
4506
|
const unpackedSecrets = unpackAllSecrets();
|
|
4506
4507
|
if (unpackedSecrets > 0) {
|
|
4507
4508
|
process.stdout.write(`\u2192 kody2: unpacked ${unpackedSecrets} secret(s) from ALL_SECRETS
|
|
@@ -4527,13 +4528,20 @@ ${CHAT_HELP}`);
|
|
|
4527
4528
|
return 99;
|
|
4528
4529
|
}
|
|
4529
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
|
+
}
|
|
4530
4539
|
let litellm = null;
|
|
4531
4540
|
try {
|
|
4532
4541
|
litellm = await startLitellmIfNeeded(model, cwd);
|
|
4533
4542
|
} catch (err) {
|
|
4534
4543
|
const msg = err instanceof Error ? err.message : String(err);
|
|
4535
|
-
|
|
4536
|
-
await sink2.emit({
|
|
4544
|
+
await sink.emit({
|
|
4537
4545
|
event: "chat.error",
|
|
4538
4546
|
payload: { sessionId, error: `litellm startup failed: ${msg}` },
|
|
4539
4547
|
runId: makeRunId(sessionId, "error"),
|
|
@@ -4541,21 +4549,33 @@ ${CHAT_HELP}`);
|
|
|
4541
4549
|
});
|
|
4542
4550
|
return 99;
|
|
4543
4551
|
}
|
|
4544
|
-
|
|
4545
|
-
if (args.initMessage) seedInitialMessage(sessionFile, args.initMessage);
|
|
4546
|
-
const sink = buildSink(cwd, sessionId, args.dashboardUrl);
|
|
4552
|
+
let pull;
|
|
4547
4553
|
try {
|
|
4548
|
-
|
|
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
|
+
`);
|
|
4566
|
+
try {
|
|
4567
|
+
const result = await runChatSession({
|
|
4549
4568
|
sessionId,
|
|
4550
|
-
sessionFile,
|
|
4551
4569
|
cwd,
|
|
4552
4570
|
model,
|
|
4553
4571
|
litellmUrl: litellm?.url ?? null,
|
|
4554
4572
|
sink,
|
|
4573
|
+
pull,
|
|
4555
4574
|
verbose: args.verbose,
|
|
4556
4575
|
quiet: args.quiet
|
|
4557
4576
|
});
|
|
4558
|
-
|
|
4577
|
+
process.stdout.write(`\u2192 kody2 chat: exited (${result.reason ?? "ok"}) after ${result.turnsProcessed} turn(s)
|
|
4578
|
+
`);
|
|
4559
4579
|
return result.exitCode;
|
|
4560
4580
|
} finally {
|
|
4561
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
|
+
}
|