autopilot-ai 1.9.0 → 1.10.0
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/index.js +1704 -320
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -93,8 +93,8 @@ async function flushAndCloseForTesting() {
|
|
|
93
93
|
const s = currentStream;
|
|
94
94
|
currentStream = null;
|
|
95
95
|
currentDate = null;
|
|
96
|
-
await new Promise((
|
|
97
|
-
s.end(() =>
|
|
96
|
+
await new Promise((resolve15) => {
|
|
97
|
+
s.end(() => resolve15());
|
|
98
98
|
});
|
|
99
99
|
}
|
|
100
100
|
function isLogSinkEnabled() {
|
|
@@ -1333,10 +1333,10 @@ async function* readSSE(stream, signal, idleTimeoutMs, postFirstByteIdleTimeoutM
|
|
|
1333
1333
|
gotFirstByte = true;
|
|
1334
1334
|
buffer += decoder.decode(value, { stream: true });
|
|
1335
1335
|
buffer = buffer.replace(/\r\n/g, "\n");
|
|
1336
|
-
let
|
|
1337
|
-
while ((
|
|
1338
|
-
const event = buffer.slice(0,
|
|
1339
|
-
buffer = buffer.slice(
|
|
1336
|
+
let sep7;
|
|
1337
|
+
while ((sep7 = buffer.indexOf("\n\n")) !== -1) {
|
|
1338
|
+
const event = buffer.slice(0, sep7);
|
|
1339
|
+
buffer = buffer.slice(sep7 + 2);
|
|
1340
1340
|
const data = extractData(event);
|
|
1341
1341
|
if (data !== null) yield data;
|
|
1342
1342
|
}
|
|
@@ -2668,11 +2668,11 @@ function extractApiError(parsed, rawText) {
|
|
|
2668
2668
|
return null;
|
|
2669
2669
|
}
|
|
2670
2670
|
function sleep(ms, signal) {
|
|
2671
|
-
return new Promise((
|
|
2671
|
+
return new Promise((resolve15, reject) => {
|
|
2672
2672
|
if (signal?.aborted) return reject(new DOMException("aborted", "AbortError"));
|
|
2673
2673
|
const t = setTimeout(() => {
|
|
2674
2674
|
signal?.removeEventListener("abort", onAbort);
|
|
2675
|
-
|
|
2675
|
+
resolve15();
|
|
2676
2676
|
}, ms);
|
|
2677
2677
|
const onAbort = () => {
|
|
2678
2678
|
clearTimeout(t);
|
|
@@ -3419,9 +3419,9 @@ var require_node_gyp_build = __commonJS({
|
|
|
3419
3419
|
var debug = getFirst(path.join(dir, "build/Debug"), matchBuild);
|
|
3420
3420
|
if (debug) return debug;
|
|
3421
3421
|
}
|
|
3422
|
-
var prebuild =
|
|
3422
|
+
var prebuild = resolve15(dir);
|
|
3423
3423
|
if (prebuild) return prebuild;
|
|
3424
|
-
var nearby =
|
|
3424
|
+
var nearby = resolve15(path.dirname(process.execPath));
|
|
3425
3425
|
if (nearby) return nearby;
|
|
3426
3426
|
var target = [
|
|
3427
3427
|
"platform=" + platform8,
|
|
@@ -3437,7 +3437,7 @@ var require_node_gyp_build = __commonJS({
|
|
|
3437
3437
|
// eslint-disable-line
|
|
3438
3438
|
].filter(Boolean).join(" ");
|
|
3439
3439
|
throw new Error("No native build was found for " + target + "\n loaded from: " + dir + "\n");
|
|
3440
|
-
function
|
|
3440
|
+
function resolve15(dir2) {
|
|
3441
3441
|
var tuples = readdirSync2(path.join(dir2, "prebuilds")).map(parseTuple);
|
|
3442
3442
|
var tuple = tuples.filter(matchTuple(platform8, arch)).sort(compareTuples)[0];
|
|
3443
3443
|
if (!tuple) return;
|
|
@@ -3858,7 +3858,7 @@ function truncateForEmbedding(text) {
|
|
|
3858
3858
|
return text.slice(0, MAX_EMBED_CHARS);
|
|
3859
3859
|
}
|
|
3860
3860
|
async function sleep2(ms) {
|
|
3861
|
-
return new Promise((
|
|
3861
|
+
return new Promise((resolve15) => setTimeout(resolve15, ms));
|
|
3862
3862
|
}
|
|
3863
3863
|
async function fetchWithRetry(url, init, retries = 3) {
|
|
3864
3864
|
let lastError;
|
|
@@ -3882,11 +3882,11 @@ async function fetchWithRetry(url, init, retries = 3) {
|
|
|
3882
3882
|
}
|
|
3883
3883
|
throw lastError ?? new Error("embeddings request failed after retries");
|
|
3884
3884
|
}
|
|
3885
|
-
function parseOpenAiEmbeddingResponse(
|
|
3886
|
-
if (!
|
|
3885
|
+
function parseOpenAiEmbeddingResponse(json3) {
|
|
3886
|
+
if (!json3 || typeof json3 !== "object") {
|
|
3887
3887
|
throw new Error("embeddings response was not an object");
|
|
3888
3888
|
}
|
|
3889
|
-
const data =
|
|
3889
|
+
const data = json3.data;
|
|
3890
3890
|
if (!Array.isArray(data)) {
|
|
3891
3891
|
throw new Error("embeddings response contained no data array");
|
|
3892
3892
|
}
|
|
@@ -3939,8 +3939,8 @@ async function fetchEmbeddings(opts2) {
|
|
|
3939
3939
|
headers,
|
|
3940
3940
|
body: JSON.stringify({ model, input: texts })
|
|
3941
3941
|
});
|
|
3942
|
-
const
|
|
3943
|
-
return parseOpenAiEmbeddingResponse(
|
|
3942
|
+
const json3 = await res.json();
|
|
3943
|
+
return parseOpenAiEmbeddingResponse(json3);
|
|
3944
3944
|
}
|
|
3945
3945
|
function cosineSimilarity(a, b) {
|
|
3946
3946
|
if (a.length !== b.length) {
|
|
@@ -6576,15 +6576,15 @@ function makeErrorOutput(message2) {
|
|
|
6576
6576
|
return { content: message2, rawBytes: bytes, reducedBytes: bytes };
|
|
6577
6577
|
}
|
|
6578
6578
|
async function execOnce(command, cwd) {
|
|
6579
|
-
return new Promise((
|
|
6579
|
+
return new Promise((resolve15) => {
|
|
6580
6580
|
const child = spawn("bash", ["-lc", command], { cwd });
|
|
6581
6581
|
let out = "";
|
|
6582
6582
|
child.stdout.on("data", (d) => {
|
|
6583
6583
|
out += d.toString("utf8");
|
|
6584
6584
|
});
|
|
6585
6585
|
child.on("close", (code) => {
|
|
6586
|
-
if (code !== 0) return
|
|
6587
|
-
|
|
6586
|
+
if (code !== 0) return resolve15(void 0);
|
|
6587
|
+
resolve15(out.trim() || void 0);
|
|
6588
6588
|
});
|
|
6589
6589
|
});
|
|
6590
6590
|
}
|
|
@@ -6722,7 +6722,7 @@ async function runBash(args, ctx) {
|
|
|
6722
6722
|
if (pushGuard) return pushGuard;
|
|
6723
6723
|
const { shell, args: shellArgs, isPosix } = getShellCommand(ctx.shell);
|
|
6724
6724
|
const command = isPosix ? injectCoauthor(args.command, ctx.coauthor) : args.command;
|
|
6725
|
-
return new Promise((
|
|
6725
|
+
return new Promise((resolve15, reject) => {
|
|
6726
6726
|
logger.debug("bash:spawn", { command: args.command.slice(0, 200), cwd: ctx.cwd, shell });
|
|
6727
6727
|
const child = spawn(shell, [...shellArgs, command], {
|
|
6728
6728
|
cwd: ctx.cwd,
|
|
@@ -6786,7 +6786,7 @@ ${stderr}`
|
|
|
6786
6786
|
if (hint) parts.push(hint);
|
|
6787
6787
|
}
|
|
6788
6788
|
const raw = parts.join("\n");
|
|
6789
|
-
|
|
6789
|
+
resolve15({
|
|
6790
6790
|
content: raw,
|
|
6791
6791
|
rawBytes: Buffer.byteLength(raw, "utf8"),
|
|
6792
6792
|
reducedBytes: Buffer.byteLength(raw, "utf8")
|
|
@@ -8115,7 +8115,7 @@ import { tmpdir as tmpdir2 } from "os";
|
|
|
8115
8115
|
import { join as join17, dirname as dirname8 } from "path";
|
|
8116
8116
|
async function autoScroll(page) {
|
|
8117
8117
|
await page.evaluate(async () => {
|
|
8118
|
-
await new Promise((
|
|
8118
|
+
await new Promise((resolve15) => {
|
|
8119
8119
|
let totalHeight = 0;
|
|
8120
8120
|
const distance = 300;
|
|
8121
8121
|
const timer3 = setInterval(() => {
|
|
@@ -8124,12 +8124,12 @@ async function autoScroll(page) {
|
|
|
8124
8124
|
totalHeight += distance;
|
|
8125
8125
|
if (totalHeight >= scrollHeight) {
|
|
8126
8126
|
clearInterval(timer3);
|
|
8127
|
-
|
|
8127
|
+
resolve15();
|
|
8128
8128
|
}
|
|
8129
8129
|
}, 100);
|
|
8130
8130
|
setTimeout(() => {
|
|
8131
8131
|
clearInterval(timer3);
|
|
8132
|
-
|
|
8132
|
+
resolve15();
|
|
8133
8133
|
}, 1e4);
|
|
8134
8134
|
});
|
|
8135
8135
|
});
|
|
@@ -8538,9 +8538,9 @@ ${options.context}` : ""
|
|
|
8538
8538
|
});
|
|
8539
8539
|
const stats = await execute(command, ["stats", cellId], { cwd, timeoutMs: 3e4 }).catch(() => void 0);
|
|
8540
8540
|
const metrics = stats?.code === 0 ? parseHotcellStats(stats.stdout) : {};
|
|
8541
|
-
const
|
|
8542
|
-
const summary = typeof
|
|
8543
|
-
const tokensUsed = typeof
|
|
8541
|
+
const json3 = parsePrintOutput(execution.stdout);
|
|
8542
|
+
const summary = typeof json3?.text === "string" ? json3.text.trim() : execution.stdout.trim();
|
|
8543
|
+
const tokensUsed = typeof json3?.usage?.totalTokens === "number" ? json3.usage.totalTokens : 0;
|
|
8544
8544
|
const budgetExhausted = execution.code === 42;
|
|
8545
8545
|
const diagnostic = `${execution.stdout}
|
|
8546
8546
|
${execution.stderr}`.toLowerCase();
|
|
@@ -9684,8 +9684,8 @@ function waitForProcessStartTime(pid, jobId, exitPath) {
|
|
|
9684
9684
|
function readProcessStartTime(pid) {
|
|
9685
9685
|
if (process.platform === "linux") {
|
|
9686
9686
|
try {
|
|
9687
|
-
const
|
|
9688
|
-
const fieldsAfterCommand =
|
|
9687
|
+
const stat10 = readFileSync3(`/proc/${pid}/stat`, "utf8");
|
|
9688
|
+
const fieldsAfterCommand = stat10.slice(stat10.lastIndexOf(")") + 1).trim().split(/\s+/);
|
|
9689
9689
|
return fieldsAfterCommand[19] ? `linux:${fieldsAfterCommand[19]}` : null;
|
|
9690
9690
|
} catch {
|
|
9691
9691
|
return null;
|
|
@@ -11775,11 +11775,11 @@ async function runHook(hook, payload, signal) {
|
|
|
11775
11775
|
const start = Date.now();
|
|
11776
11776
|
const timeoutMs = hook.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
11777
11777
|
const env2 = { ...process.env, ...buildHookEnv(payload) };
|
|
11778
|
-
const
|
|
11778
|
+
const json3 = JSON.stringify(payload);
|
|
11779
11779
|
const id = hook.id ?? "anonymous";
|
|
11780
11780
|
let result2;
|
|
11781
11781
|
try {
|
|
11782
|
-
result2 = await spawnImpl(hook.command,
|
|
11782
|
+
result2 = await spawnImpl(hook.command, json3, env2, payload.cwd, timeoutMs, signal);
|
|
11783
11783
|
} catch (e) {
|
|
11784
11784
|
return {
|
|
11785
11785
|
id,
|
|
@@ -11850,7 +11850,7 @@ var init_runner = __esm({
|
|
|
11850
11850
|
init_logger();
|
|
11851
11851
|
DEFAULT_TIMEOUT_MS = 3e4;
|
|
11852
11852
|
STREAM_CAP_BYTES = 4 * 1024;
|
|
11853
|
-
defaultSpawn = (command, payloadJson, env2, cwd, timeoutMs, signal) => new Promise((
|
|
11853
|
+
defaultSpawn = (command, payloadJson, env2, cwd, timeoutMs, signal) => new Promise((resolve15) => {
|
|
11854
11854
|
const child = spawn4(command, {
|
|
11855
11855
|
shell: true,
|
|
11856
11856
|
cwd,
|
|
@@ -11865,7 +11865,7 @@ var init_runner = __esm({
|
|
|
11865
11865
|
settled = true;
|
|
11866
11866
|
clearTimeout(timer3);
|
|
11867
11867
|
signal?.removeEventListener("abort", onAbort);
|
|
11868
|
-
|
|
11868
|
+
resolve15({ exitCode, stdout, stderr, timedOut });
|
|
11869
11869
|
};
|
|
11870
11870
|
const onAbort = () => {
|
|
11871
11871
|
child.kill("SIGTERM");
|
|
@@ -12640,13 +12640,454 @@ var init_cli = __esm({
|
|
|
12640
12640
|
}
|
|
12641
12641
|
});
|
|
12642
12642
|
|
|
12643
|
+
// src/server/aster-workspaces.ts
|
|
12644
|
+
var aster_workspaces_exports = {};
|
|
12645
|
+
__export(aster_workspaces_exports, {
|
|
12646
|
+
AsterConfigError: () => AsterConfigError,
|
|
12647
|
+
findAsterWorkspace: () => findAsterWorkspace,
|
|
12648
|
+
loadAsterServerConfig: () => loadAsterServerConfig
|
|
12649
|
+
});
|
|
12650
|
+
import { readFile as readFile17, realpath as realpath3, stat as stat6 } from "fs/promises";
|
|
12651
|
+
import { isAbsolute as isAbsolute3, relative as relative5, sep as sep3 } from "path";
|
|
12652
|
+
async function loadAsterServerConfig(configPath3 = process.env.AUTOPILOT_ASTER_CONFIG || "/etc/autopilot/aster.json") {
|
|
12653
|
+
let raw;
|
|
12654
|
+
try {
|
|
12655
|
+
raw = JSON.parse(await readFile17(configPath3, "utf8"));
|
|
12656
|
+
} catch {
|
|
12657
|
+
throw new AsterConfigError();
|
|
12658
|
+
}
|
|
12659
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) throw new AsterConfigError();
|
|
12660
|
+
const record = raw;
|
|
12661
|
+
if (!Array.isArray(record.models) || record.models.length === 0 || record.models.length > 100) {
|
|
12662
|
+
throw new AsterConfigError();
|
|
12663
|
+
}
|
|
12664
|
+
const models = [...new Set(record.models)];
|
|
12665
|
+
if (models.some((model) => typeof model !== "string" || !model.trim() || model.length > 200)) {
|
|
12666
|
+
throw new AsterConfigError();
|
|
12667
|
+
}
|
|
12668
|
+
if (!Array.isArray(record.workspaces) || record.workspaces.length === 0 || record.workspaces.length > 100) {
|
|
12669
|
+
throw new AsterConfigError();
|
|
12670
|
+
}
|
|
12671
|
+
const approvalTtlMs = record.approvalTtlMs ?? 5 * 60 * 1e3;
|
|
12672
|
+
if (!Number.isInteger(approvalTtlMs) || approvalTtlMs < 1e3 || approvalTtlMs > 60 * 60 * 1e3) {
|
|
12673
|
+
throw new AsterConfigError();
|
|
12674
|
+
}
|
|
12675
|
+
const ids = /* @__PURE__ */ new Set();
|
|
12676
|
+
const roots = [];
|
|
12677
|
+
const workspaces = [];
|
|
12678
|
+
for (const entry of record.workspaces) {
|
|
12679
|
+
if (!entry || typeof entry !== "object" || Array.isArray(entry)) throw new AsterConfigError();
|
|
12680
|
+
const workspace = entry;
|
|
12681
|
+
if (typeof workspace.id !== "string" || !/^[a-zA-Z0-9][a-zA-Z0-9_-]{0,63}$/.test(workspace.id) || ids.has(workspace.id)) {
|
|
12682
|
+
throw new AsterConfigError();
|
|
12683
|
+
}
|
|
12684
|
+
if (typeof workspace.displayName !== "string" || !workspace.displayName.trim() || workspace.displayName.length > 100) {
|
|
12685
|
+
throw new AsterConfigError();
|
|
12686
|
+
}
|
|
12687
|
+
if (typeof workspace.rootPath !== "string" || !isAbsolute3(workspace.rootPath)) throw new AsterConfigError();
|
|
12688
|
+
let rootPath;
|
|
12689
|
+
try {
|
|
12690
|
+
rootPath = await realpath3(workspace.rootPath);
|
|
12691
|
+
if (!(await stat6(rootPath)).isDirectory()) throw new Error("not a directory");
|
|
12692
|
+
} catch {
|
|
12693
|
+
throw new AsterConfigError();
|
|
12694
|
+
}
|
|
12695
|
+
if (roots.some((root) => isWithin(root, rootPath) || isWithin(rootPath, root))) {
|
|
12696
|
+
throw new AsterConfigError();
|
|
12697
|
+
}
|
|
12698
|
+
ids.add(workspace.id);
|
|
12699
|
+
roots.push(rootPath);
|
|
12700
|
+
workspaces.push({ id: workspace.id, displayName: workspace.displayName.trim(), rootPath });
|
|
12701
|
+
}
|
|
12702
|
+
return { models, workspaces, approvalTtlMs };
|
|
12703
|
+
}
|
|
12704
|
+
function findAsterWorkspace(config2, workspaceId) {
|
|
12705
|
+
return config2.workspaces.find((workspace) => workspace.id === workspaceId);
|
|
12706
|
+
}
|
|
12707
|
+
function isWithin(parent, candidate) {
|
|
12708
|
+
const rel = relative5(parent, candidate);
|
|
12709
|
+
return rel === "" || rel !== ".." && !rel.startsWith(`..${sep3}`) && !isAbsolute3(rel);
|
|
12710
|
+
}
|
|
12711
|
+
var AsterConfigError;
|
|
12712
|
+
var init_aster_workspaces = __esm({
|
|
12713
|
+
"src/server/aster-workspaces.ts"() {
|
|
12714
|
+
"use strict";
|
|
12715
|
+
AsterConfigError = class extends Error {
|
|
12716
|
+
constructor(message2 = "Aster server configuration is unavailable or invalid") {
|
|
12717
|
+
super(message2);
|
|
12718
|
+
this.name = "AsterConfigError";
|
|
12719
|
+
}
|
|
12720
|
+
};
|
|
12721
|
+
}
|
|
12722
|
+
});
|
|
12723
|
+
|
|
12724
|
+
// src/server/aster-store.ts
|
|
12725
|
+
var aster_store_exports = {};
|
|
12726
|
+
__export(aster_store_exports, {
|
|
12727
|
+
AsterStore: () => AsterStore
|
|
12728
|
+
});
|
|
12729
|
+
import Database3 from "better-sqlite3";
|
|
12730
|
+
import { createHash as createHash3, randomBytes as randomBytes2, randomUUID as randomUUID5 } from "crypto";
|
|
12731
|
+
import { chmodSync as chmodSync2, mkdirSync as mkdirSync6 } from "fs";
|
|
12732
|
+
import { homedir as homedir18 } from "os";
|
|
12733
|
+
import { dirname as dirname12, join as join25 } from "path";
|
|
12734
|
+
function defaultAsterDbPath() {
|
|
12735
|
+
const root = process.env.XDG_STATE_HOME || join25(homedir18(), ".local", "state");
|
|
12736
|
+
return process.env.AUTOPILOT_ASTER_DB || join25(root, "autopilot", "aster.db");
|
|
12737
|
+
}
|
|
12738
|
+
function hashToken(token) {
|
|
12739
|
+
return createHash3("sha256").update(token).digest("hex");
|
|
12740
|
+
}
|
|
12741
|
+
function rowToConversation(row) {
|
|
12742
|
+
return {
|
|
12743
|
+
id: row.id,
|
|
12744
|
+
workspaceId: row.workspace_id,
|
|
12745
|
+
model: row.model,
|
|
12746
|
+
sessionId: row.session_id,
|
|
12747
|
+
worktreePath: row.worktree_path,
|
|
12748
|
+
cwd: row.cwd,
|
|
12749
|
+
branch: row.branch,
|
|
12750
|
+
status: row.status,
|
|
12751
|
+
activeRunId: row.active_run_id,
|
|
12752
|
+
lastEventSequence: row.last_event_sequence,
|
|
12753
|
+
oldestEventSequence: row.oldest_event_sequence,
|
|
12754
|
+
createdAt: row.created_at,
|
|
12755
|
+
updatedAt: row.updated_at
|
|
12756
|
+
};
|
|
12757
|
+
}
|
|
12758
|
+
function rowToApproval(row) {
|
|
12759
|
+
return {
|
|
12760
|
+
id: row.id,
|
|
12761
|
+
conversationId: row.conversation_id,
|
|
12762
|
+
runId: row.run_id,
|
|
12763
|
+
toolName: row.tool_name,
|
|
12764
|
+
explanation: row.explanation,
|
|
12765
|
+
arguments: JSON.parse(row.arguments_json),
|
|
12766
|
+
status: row.status,
|
|
12767
|
+
expiresAt: row.expires_at,
|
|
12768
|
+
decision: row.decision,
|
|
12769
|
+
createdAt: row.created_at,
|
|
12770
|
+
resolvedAt: row.resolved_at
|
|
12771
|
+
};
|
|
12772
|
+
}
|
|
12773
|
+
var VALID_SCOPES, AsterStore;
|
|
12774
|
+
var init_aster_store = __esm({
|
|
12775
|
+
"src/server/aster-store.ts"() {
|
|
12776
|
+
"use strict";
|
|
12777
|
+
VALID_SCOPES = /* @__PURE__ */ new Set([
|
|
12778
|
+
"workspaces:read",
|
|
12779
|
+
"models:read",
|
|
12780
|
+
"conversations:read",
|
|
12781
|
+
"conversations:write",
|
|
12782
|
+
"approvals:resolve"
|
|
12783
|
+
]);
|
|
12784
|
+
AsterStore = class {
|
|
12785
|
+
db;
|
|
12786
|
+
constructor(dbPath = defaultAsterDbPath()) {
|
|
12787
|
+
mkdirSync6(dirname12(dbPath), { recursive: true, mode: 448 });
|
|
12788
|
+
try {
|
|
12789
|
+
chmodSync2(dirname12(dbPath), 448);
|
|
12790
|
+
} catch {
|
|
12791
|
+
}
|
|
12792
|
+
this.db = new Database3(dbPath);
|
|
12793
|
+
this.db.pragma("journal_mode = WAL");
|
|
12794
|
+
this.db.pragma("foreign_keys = ON");
|
|
12795
|
+
this.db.pragma("busy_timeout = 5000");
|
|
12796
|
+
this.db.exec(`
|
|
12797
|
+
CREATE TABLE IF NOT EXISTS aster_credentials (
|
|
12798
|
+
id TEXT PRIMARY KEY,
|
|
12799
|
+
name TEXT NOT NULL,
|
|
12800
|
+
token_hash TEXT NOT NULL UNIQUE,
|
|
12801
|
+
workspace_ids_json TEXT NOT NULL,
|
|
12802
|
+
scopes_json TEXT NOT NULL,
|
|
12803
|
+
created_at INTEGER NOT NULL,
|
|
12804
|
+
expires_at INTEGER NOT NULL,
|
|
12805
|
+
revoked_at INTEGER,
|
|
12806
|
+
last_used_at INTEGER
|
|
12807
|
+
);
|
|
12808
|
+
CREATE TABLE IF NOT EXISTS aster_conversations (
|
|
12809
|
+
id TEXT PRIMARY KEY,
|
|
12810
|
+
workspace_id TEXT NOT NULL,
|
|
12811
|
+
model TEXT NOT NULL,
|
|
12812
|
+
session_id TEXT NOT NULL UNIQUE,
|
|
12813
|
+
worktree_path TEXT NOT NULL,
|
|
12814
|
+
cwd TEXT NOT NULL,
|
|
12815
|
+
branch TEXT NOT NULL,
|
|
12816
|
+
status TEXT NOT NULL,
|
|
12817
|
+
active_run_id TEXT,
|
|
12818
|
+
last_event_sequence INTEGER NOT NULL DEFAULT 0,
|
|
12819
|
+
created_at INTEGER NOT NULL,
|
|
12820
|
+
updated_at INTEGER NOT NULL
|
|
12821
|
+
);
|
|
12822
|
+
CREATE INDEX IF NOT EXISTS idx_aster_conversations_workspace ON aster_conversations(workspace_id, updated_at);
|
|
12823
|
+
CREATE TABLE IF NOT EXISTS aster_events (
|
|
12824
|
+
conversation_id TEXT NOT NULL REFERENCES aster_conversations(id) ON DELETE CASCADE,
|
|
12825
|
+
sequence INTEGER NOT NULL,
|
|
12826
|
+
run_id TEXT,
|
|
12827
|
+
event_type TEXT NOT NULL,
|
|
12828
|
+
data_json TEXT NOT NULL,
|
|
12829
|
+
created_at INTEGER NOT NULL,
|
|
12830
|
+
PRIMARY KEY(conversation_id, sequence)
|
|
12831
|
+
);
|
|
12832
|
+
CREATE INDEX IF NOT EXISTS idx_aster_events_run ON aster_events(run_id, sequence);
|
|
12833
|
+
CREATE TABLE IF NOT EXISTS aster_approvals (
|
|
12834
|
+
id TEXT PRIMARY KEY,
|
|
12835
|
+
conversation_id TEXT NOT NULL REFERENCES aster_conversations(id) ON DELETE CASCADE,
|
|
12836
|
+
run_id TEXT NOT NULL,
|
|
12837
|
+
tool_name TEXT NOT NULL,
|
|
12838
|
+
explanation TEXT NOT NULL,
|
|
12839
|
+
arguments_json TEXT NOT NULL,
|
|
12840
|
+
status TEXT NOT NULL,
|
|
12841
|
+
expires_at INTEGER NOT NULL,
|
|
12842
|
+
decision TEXT,
|
|
12843
|
+
created_at INTEGER NOT NULL,
|
|
12844
|
+
resolved_at INTEGER
|
|
12845
|
+
);
|
|
12846
|
+
CREATE INDEX IF NOT EXISTS idx_aster_approvals_pending ON aster_approvals(conversation_id, status, expires_at);
|
|
12847
|
+
CREATE TABLE IF NOT EXISTS aster_turn_idempotency (
|
|
12848
|
+
conversation_id TEXT NOT NULL REFERENCES aster_conversations(id) ON DELETE CASCADE,
|
|
12849
|
+
client_turn_id TEXT NOT NULL,
|
|
12850
|
+
request_text TEXT NOT NULL,
|
|
12851
|
+
accepted_text TEXT NOT NULL,
|
|
12852
|
+
run_id TEXT NOT NULL UNIQUE,
|
|
12853
|
+
response_status INTEGER NOT NULL,
|
|
12854
|
+
response_json TEXT NOT NULL,
|
|
12855
|
+
created_at INTEGER NOT NULL,
|
|
12856
|
+
PRIMARY KEY (conversation_id, client_turn_id)
|
|
12857
|
+
);
|
|
12858
|
+
`);
|
|
12859
|
+
try {
|
|
12860
|
+
chmodSync2(dbPath, 384);
|
|
12861
|
+
} catch {
|
|
12862
|
+
}
|
|
12863
|
+
}
|
|
12864
|
+
close() {
|
|
12865
|
+
this.db.close();
|
|
12866
|
+
}
|
|
12867
|
+
createCredential(input) {
|
|
12868
|
+
const name = input.name.trim();
|
|
12869
|
+
if (!name || name.length > 100) throw new Error("credential name must be 1 to 100 characters");
|
|
12870
|
+
const workspaceIds = [...new Set(input.workspaceIds)];
|
|
12871
|
+
if (workspaceIds.length === 0 || workspaceIds.length > 100 || workspaceIds.some((id2) => !/^[a-zA-Z0-9][a-zA-Z0-9_-]{0,63}$/.test(id2))) {
|
|
12872
|
+
throw new Error("credential must be scoped to valid workspace IDs");
|
|
12873
|
+
}
|
|
12874
|
+
const scopes = [...new Set(input.scopes)];
|
|
12875
|
+
if (scopes.length === 0 || scopes.some((scope) => !VALID_SCOPES.has(scope))) throw new Error("credential scopes are invalid");
|
|
12876
|
+
if (!Number.isSafeInteger(input.expiresAt) || input.expiresAt <= Date.now()) throw new Error("credential expiry must be in the future");
|
|
12877
|
+
const id = randomUUID5();
|
|
12878
|
+
const token = `aster_${randomBytes2(32).toString("base64url")}`;
|
|
12879
|
+
const now2 = Date.now();
|
|
12880
|
+
this.db.prepare(`INSERT INTO aster_credentials
|
|
12881
|
+
(id, name, token_hash, workspace_ids_json, scopes_json, created_at, expires_at)
|
|
12882
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)`).run(id, name, hashToken(token), JSON.stringify(workspaceIds), JSON.stringify(scopes), now2, input.expiresAt);
|
|
12883
|
+
return { id, name, workspaceIds, scopes, createdAt: now2, expiresAt: input.expiresAt, token };
|
|
12884
|
+
}
|
|
12885
|
+
authenticate(authorization, now2 = Date.now()) {
|
|
12886
|
+
const match = authorization?.match(/^Bearer (aster_[A-Za-z0-9_-]{40,})$/);
|
|
12887
|
+
if (!match) return void 0;
|
|
12888
|
+
const row = this.db.prepare("SELECT * FROM aster_credentials WHERE token_hash = ?").get(hashToken(match[1]));
|
|
12889
|
+
if (!row || row.revoked_at !== null || row.expires_at <= now2) return void 0;
|
|
12890
|
+
this.db.prepare("UPDATE aster_credentials SET last_used_at = ? WHERE id = ?").run(now2, row.id);
|
|
12891
|
+
return {
|
|
12892
|
+
credentialId: row.id,
|
|
12893
|
+
name: row.name,
|
|
12894
|
+
workspaceIds: JSON.parse(row.workspace_ids_json),
|
|
12895
|
+
scopes: JSON.parse(row.scopes_json),
|
|
12896
|
+
expiresAt: row.expires_at
|
|
12897
|
+
};
|
|
12898
|
+
}
|
|
12899
|
+
revokeCredential(id, now2 = Date.now()) {
|
|
12900
|
+
const result2 = this.db.prepare("UPDATE aster_credentials SET revoked_at = COALESCE(revoked_at, ?) WHERE id = ?").run(now2, id);
|
|
12901
|
+
return result2.changes > 0;
|
|
12902
|
+
}
|
|
12903
|
+
createConversation(input) {
|
|
12904
|
+
const now2 = Date.now();
|
|
12905
|
+
const create = this.db.transaction(() => {
|
|
12906
|
+
this.db.prepare(`INSERT INTO aster_conversations
|
|
12907
|
+
(id, workspace_id, model, session_id, worktree_path, cwd, branch, status, created_at, updated_at)
|
|
12908
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, 'ready', ?, ?)`).run(input.id, input.workspaceId, input.model, input.sessionId, input.worktreePath, input.cwd, input.branch, now2, now2);
|
|
12909
|
+
this.insertEvent(input.id, null, "status", { status: "ready" }, now2);
|
|
12910
|
+
});
|
|
12911
|
+
create.immediate();
|
|
12912
|
+
return this.getConversation(input.id);
|
|
12913
|
+
}
|
|
12914
|
+
getConversation(id) {
|
|
12915
|
+
const row = this.db.prepare("SELECT * FROM aster_conversations WHERE id = ?").get(id);
|
|
12916
|
+
return row ? rowToConversation(row) : void 0;
|
|
12917
|
+
}
|
|
12918
|
+
listActiveConversations() {
|
|
12919
|
+
const rows = this.db.prepare("SELECT * FROM aster_conversations WHERE active_run_id IS NOT NULL").all();
|
|
12920
|
+
return rows.map(rowToConversation);
|
|
12921
|
+
}
|
|
12922
|
+
lookupTurn(conversationId, clientTurnId, requestText) {
|
|
12923
|
+
const row = this.db.prepare("SELECT run_id, request_text, response_status, response_json FROM aster_turn_idempotency WHERE conversation_id = ? AND client_turn_id = ?").get(conversationId, clientTurnId);
|
|
12924
|
+
if (!row) return { kind: "absent" };
|
|
12925
|
+
if (row.request_text !== requestText) return { kind: "conflict" };
|
|
12926
|
+
return {
|
|
12927
|
+
kind: "replay",
|
|
12928
|
+
runId: row.run_id,
|
|
12929
|
+
responseStatus: row.response_status,
|
|
12930
|
+
response: JSON.parse(row.response_json)
|
|
12931
|
+
};
|
|
12932
|
+
}
|
|
12933
|
+
reserveTurn(input) {
|
|
12934
|
+
const reserve = this.db.transaction(() => {
|
|
12935
|
+
const existing = this.db.prepare("SELECT run_id, request_text, response_status, response_json FROM aster_turn_idempotency WHERE conversation_id = ? AND client_turn_id = ?").get(input.conversationId, input.clientTurnId);
|
|
12936
|
+
if (existing) {
|
|
12937
|
+
if (existing.request_text !== input.requestText) return { kind: "conflict" };
|
|
12938
|
+
return {
|
|
12939
|
+
kind: "replay",
|
|
12940
|
+
runId: existing.run_id,
|
|
12941
|
+
responseStatus: existing.response_status,
|
|
12942
|
+
response: JSON.parse(existing.response_json)
|
|
12943
|
+
};
|
|
12944
|
+
}
|
|
12945
|
+
const conversation = this.getConversation(input.conversationId);
|
|
12946
|
+
if (!conversation) return { kind: "not_found" };
|
|
12947
|
+
if (conversation.activeRunId) return { kind: "busy" };
|
|
12948
|
+
const now2 = Date.now();
|
|
12949
|
+
const updated = this.db.prepare("UPDATE aster_conversations SET status = 'running', active_run_id = ?, updated_at = ? WHERE id = ? AND active_run_id IS NULL").run(input.runId, now2, input.conversationId);
|
|
12950
|
+
if (updated.changes !== 1) return { kind: "busy" };
|
|
12951
|
+
this.insertEvent(input.conversationId, input.runId, "status", { status: "running" }, now2);
|
|
12952
|
+
this.db.prepare("INSERT INTO aster_turn_idempotency (conversation_id, client_turn_id, request_text, accepted_text, run_id, response_status, response_json, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?)").run(input.conversationId, input.clientTurnId, input.requestText, input.acceptedText, input.runId, 202, JSON.stringify(input.response), now2);
|
|
12953
|
+
return { kind: "reserved" };
|
|
12954
|
+
});
|
|
12955
|
+
return reserve.immediate();
|
|
12956
|
+
}
|
|
12957
|
+
beginTurn(conversationId, runId) {
|
|
12958
|
+
const update = this.db.transaction(() => {
|
|
12959
|
+
const conversation = this.getConversation(conversationId);
|
|
12960
|
+
if (!conversation || conversation.activeRunId) return false;
|
|
12961
|
+
const now2 = Date.now();
|
|
12962
|
+
const result2 = this.db.prepare(`UPDATE aster_conversations SET status = 'running', active_run_id = ?, updated_at = ?
|
|
12963
|
+
WHERE id = ? AND active_run_id IS NULL`).run(runId, now2, conversationId);
|
|
12964
|
+
if (result2.changes !== 1) return false;
|
|
12965
|
+
this.insertEvent(conversationId, runId, "status", { status: "running" }, now2);
|
|
12966
|
+
return true;
|
|
12967
|
+
});
|
|
12968
|
+
return update.immediate();
|
|
12969
|
+
}
|
|
12970
|
+
updateConversationStatus(conversationId, runId, status, data = {}) {
|
|
12971
|
+
const terminal = status === "completed" || status === "failed" || status === "cancelled" || status === "interrupted";
|
|
12972
|
+
const update = this.db.transaction(() => {
|
|
12973
|
+
const now2 = Date.now();
|
|
12974
|
+
const result2 = this.db.prepare(`UPDATE aster_conversations SET status = ?, active_run_id = ?, updated_at = ?
|
|
12975
|
+
WHERE id = ? AND active_run_id = ?`).run(status, terminal ? null : runId, now2, conversationId, runId);
|
|
12976
|
+
if (result2.changes !== 1) return false;
|
|
12977
|
+
this.insertEvent(conversationId, runId, "status", { status, ...data }, now2);
|
|
12978
|
+
return true;
|
|
12979
|
+
});
|
|
12980
|
+
return update.immediate();
|
|
12981
|
+
}
|
|
12982
|
+
appendEvent(conversationId, runId, type, data) {
|
|
12983
|
+
const append = this.db.transaction(() => this.insertEvent(conversationId, runId, type, data, Date.now()));
|
|
12984
|
+
return append.immediate();
|
|
12985
|
+
}
|
|
12986
|
+
eventsAfter(conversationId, after, limit = 200) {
|
|
12987
|
+
const rows = this.db.prepare(`SELECT * FROM aster_events WHERE conversation_id = ? AND sequence > ?
|
|
12988
|
+
ORDER BY sequence LIMIT ?`).all(conversationId, after, Math.max(1, Math.min(500, Math.trunc(limit))));
|
|
12989
|
+
return rows.map((row) => ({
|
|
12990
|
+
sequence: row.sequence,
|
|
12991
|
+
conversationId: row.conversation_id,
|
|
12992
|
+
runId: row.run_id ?? null,
|
|
12993
|
+
type: row.event_type,
|
|
12994
|
+
data: JSON.parse(row.data_json),
|
|
12995
|
+
createdAt: row.created_at
|
|
12996
|
+
}));
|
|
12997
|
+
}
|
|
12998
|
+
createApproval(input) {
|
|
12999
|
+
const id = randomUUID5();
|
|
13000
|
+
const now2 = Date.now();
|
|
13001
|
+
this.db.prepare(`INSERT INTO aster_approvals
|
|
13002
|
+
(id, conversation_id, run_id, tool_name, explanation, arguments_json, status, expires_at, created_at)
|
|
13003
|
+
VALUES (?, ?, ?, ?, ?, ?, 'pending', ?, ?)`).run(id, input.conversationId, input.runId, input.toolName, input.explanation, JSON.stringify(input.arguments), input.expiresAt, now2);
|
|
13004
|
+
return this.getApproval(id);
|
|
13005
|
+
}
|
|
13006
|
+
getApproval(id) {
|
|
13007
|
+
const row = this.db.prepare("SELECT * FROM aster_approvals WHERE id = ?").get(id);
|
|
13008
|
+
return row ? rowToApproval(row) : void 0;
|
|
13009
|
+
}
|
|
13010
|
+
pendingApprovalsForConversation(conversationId) {
|
|
13011
|
+
const rows = this.db.prepare("SELECT * FROM aster_approvals WHERE conversation_id = ? AND status = 'pending' ORDER BY created_at").all(conversationId);
|
|
13012
|
+
return rows.map(rowToApproval);
|
|
13013
|
+
}
|
|
13014
|
+
getConversationForApproval(approvalId) {
|
|
13015
|
+
const row = this.db.prepare(`SELECT c.* FROM aster_conversations c
|
|
13016
|
+
JOIN aster_approvals a ON a.conversation_id = c.id WHERE a.id = ?`).get(approvalId);
|
|
13017
|
+
return row ? rowToConversation(row) : void 0;
|
|
13018
|
+
}
|
|
13019
|
+
expireApproval(id, now2 = Date.now()) {
|
|
13020
|
+
const update = this.db.transaction(() => {
|
|
13021
|
+
const approval = this.getApproval(id);
|
|
13022
|
+
if (!approval) return void 0;
|
|
13023
|
+
if (approval.status === "pending" && approval.expiresAt <= now2) {
|
|
13024
|
+
this.db.prepare("UPDATE aster_approvals SET status = 'expired', resolved_at = ? WHERE id = ? AND status = 'pending'").run(now2, id);
|
|
13025
|
+
return this.getApproval(id);
|
|
13026
|
+
}
|
|
13027
|
+
return approval;
|
|
13028
|
+
});
|
|
13029
|
+
return update.immediate();
|
|
13030
|
+
}
|
|
13031
|
+
cancelPendingApprovals(conversationId, now2 = Date.now()) {
|
|
13032
|
+
const update = this.db.transaction(() => {
|
|
13033
|
+
const rows = this.db.prepare("SELECT * FROM aster_approvals WHERE conversation_id = ? AND status = 'pending'").all(conversationId);
|
|
13034
|
+
for (const row of rows) this.db.prepare("UPDATE aster_approvals SET status = 'cancelled', resolved_at = ? WHERE id = ? AND status = 'pending'").run(now2, row.id);
|
|
13035
|
+
return rows.map((row) => ({ ...rowToApproval(row), status: "cancelled", resolvedAt: now2 }));
|
|
13036
|
+
});
|
|
13037
|
+
return update.immediate();
|
|
13038
|
+
}
|
|
13039
|
+
expirePendingApprovals(now2 = Date.now()) {
|
|
13040
|
+
const update = this.db.transaction(() => {
|
|
13041
|
+
const rows = this.db.prepare("SELECT * FROM aster_approvals WHERE status = 'pending' AND expires_at <= ?").all(now2);
|
|
13042
|
+
for (const row of rows) this.db.prepare("UPDATE aster_approvals SET status = 'expired', resolved_at = ? WHERE id = ? AND status = 'pending'").run(now2, row.id);
|
|
13043
|
+
return rows.map((row) => ({ ...rowToApproval(row), status: "expired", resolvedAt: now2 }));
|
|
13044
|
+
});
|
|
13045
|
+
return update.immediate();
|
|
13046
|
+
}
|
|
13047
|
+
expireAllPendingApprovals(now2 = Date.now()) {
|
|
13048
|
+
const update = this.db.transaction(() => {
|
|
13049
|
+
const rows = this.db.prepare("SELECT * FROM aster_approvals WHERE status = 'pending'").all();
|
|
13050
|
+
for (const row of rows) this.db.prepare("UPDATE aster_approvals SET status = 'expired', resolved_at = ? WHERE id = ? AND status = 'pending'").run(now2, row.id);
|
|
13051
|
+
return rows.map((row) => ({ ...rowToApproval(row), status: "expired", resolvedAt: now2 }));
|
|
13052
|
+
});
|
|
13053
|
+
return update.immediate();
|
|
13054
|
+
}
|
|
13055
|
+
resolveApproval(id, decision, now2 = Date.now()) {
|
|
13056
|
+
const update = this.db.transaction(() => {
|
|
13057
|
+
const current = this.getApproval(id);
|
|
13058
|
+
if (!current) return { conflict: false, changed: false };
|
|
13059
|
+
if (current.status === "pending" && current.expiresAt <= now2) {
|
|
13060
|
+
this.db.prepare("UPDATE aster_approvals SET status = 'expired', resolved_at = ? WHERE id = ? AND status = 'pending'").run(now2, id);
|
|
13061
|
+
return { approval: this.getApproval(id), conflict: true, changed: true };
|
|
13062
|
+
}
|
|
13063
|
+
if (current.status !== "pending") {
|
|
13064
|
+
return { approval: current, conflict: current.decision !== decision, changed: false };
|
|
13065
|
+
}
|
|
13066
|
+
this.db.prepare("UPDATE aster_approvals SET status = ?, decision = ?, resolved_at = ? WHERE id = ? AND status = 'pending'").run(decision === "allow" ? "approved" : "denied", decision, now2, id);
|
|
13067
|
+
return { approval: this.getApproval(id), conflict: false, changed: true };
|
|
13068
|
+
});
|
|
13069
|
+
return update.immediate();
|
|
13070
|
+
}
|
|
13071
|
+
insertEvent(conversationId, runId, type, data, now2) {
|
|
13072
|
+
const row = this.db.prepare("SELECT last_event_sequence FROM aster_conversations WHERE id = ?").get(conversationId);
|
|
13073
|
+
if (!row) throw new Error("Conversation not found");
|
|
13074
|
+
const sequence = row.last_event_sequence + 1;
|
|
13075
|
+
this.db.prepare("UPDATE aster_conversations SET last_event_sequence = ?, updated_at = ? WHERE id = ?").run(sequence, now2, conversationId);
|
|
13076
|
+
this.db.prepare(`INSERT INTO aster_events (conversation_id, sequence, run_id, event_type, data_json, created_at)
|
|
13077
|
+
VALUES (?, ?, ?, ?, ?, ?)`).run(conversationId, sequence, runId, type, JSON.stringify(data), now2);
|
|
13078
|
+
return { sequence, conversationId, runId, type, data, createdAt: now2 };
|
|
13079
|
+
}
|
|
13080
|
+
};
|
|
13081
|
+
}
|
|
13082
|
+
});
|
|
13083
|
+
|
|
12643
13084
|
// src/remote/tui-auth.ts
|
|
12644
13085
|
var tui_auth_exports = {};
|
|
12645
13086
|
__export(tui_auth_exports, {
|
|
12646
13087
|
authGitHubForTui: () => authGitHubForTui
|
|
12647
13088
|
});
|
|
12648
13089
|
function sleep3(ms) {
|
|
12649
|
-
return new Promise((
|
|
13090
|
+
return new Promise((resolve15) => setTimeout(resolve15, ms));
|
|
12650
13091
|
}
|
|
12651
13092
|
async function* authGitHubForTui() {
|
|
12652
13093
|
yield { message: "Starting GitHub OAuth device flow..." };
|
|
@@ -13045,11 +13486,101 @@ function getOpenApiSpec() {
|
|
|
13045
13486
|
}
|
|
13046
13487
|
}
|
|
13047
13488
|
}
|
|
13489
|
+
},
|
|
13490
|
+
"/api/v1/health": {
|
|
13491
|
+
get: {
|
|
13492
|
+
summary: "Authenticated Aster capabilities and health",
|
|
13493
|
+
security: [{ asterBearer: [] }],
|
|
13494
|
+
responses: { "200": { description: "API version and supported capabilities; contains no host paths or secrets" }, "401": { description: "Invalid or expired credential" } }
|
|
13495
|
+
}
|
|
13496
|
+
},
|
|
13497
|
+
"/api/v1/workspaces": {
|
|
13498
|
+
get: {
|
|
13499
|
+
summary: "List credential-scoped workspaces",
|
|
13500
|
+
security: [{ asterBearer: [] }],
|
|
13501
|
+
responses: { "200": { description: "Configured workspace IDs and display names only" } }
|
|
13502
|
+
}
|
|
13503
|
+
},
|
|
13504
|
+
"/api/v1/models": {
|
|
13505
|
+
get: {
|
|
13506
|
+
summary: "List server-configured models",
|
|
13507
|
+
security: [{ asterBearer: [] }],
|
|
13508
|
+
responses: { "200": { description: "Configured model IDs" } }
|
|
13509
|
+
}
|
|
13510
|
+
},
|
|
13511
|
+
"/api/v1/conversations": {
|
|
13512
|
+
post: {
|
|
13513
|
+
summary: "Create a persistent conversation in an isolated workspace worktree",
|
|
13514
|
+
security: [{ asterBearer: [] }],
|
|
13515
|
+
requestBody: {
|
|
13516
|
+
required: true,
|
|
13517
|
+
content: { "application/json": { schema: {
|
|
13518
|
+
type: "object",
|
|
13519
|
+
required: ["workspaceId", "model"],
|
|
13520
|
+
properties: { workspaceId: { type: "string" }, model: { type: "string" } },
|
|
13521
|
+
additionalProperties: false
|
|
13522
|
+
}, example: { workspaceId: "default", model: "moonshotai/kimi-k2.6" } } }
|
|
13523
|
+
},
|
|
13524
|
+
responses: { "201": { description: "Created conversation with opaque ID" }, "400": { description: "Invalid workspace or model" } }
|
|
13525
|
+
}
|
|
13526
|
+
},
|
|
13527
|
+
"/api/v1/conversations/{conversationId}": {
|
|
13528
|
+
get: {
|
|
13529
|
+
summary: "Get conversation status without returning model/tool history or filesystem paths",
|
|
13530
|
+
security: [{ asterBearer: [] }],
|
|
13531
|
+
parameters: [{ name: "conversationId", in: "path", required: true, schema: { type: "string", format: "uuid" } }],
|
|
13532
|
+
responses: { "200": { description: "Conversation metadata and last event cursor" }, "404": { description: "Conversation not found or outside credential scope" } }
|
|
13533
|
+
}
|
|
13534
|
+
},
|
|
13535
|
+
"/api/v1/conversations/{conversationId}/turns": {
|
|
13536
|
+
post: {
|
|
13537
|
+
summary: "Append a user turn to the persistent conversation",
|
|
13538
|
+
security: [{ asterBearer: [] }],
|
|
13539
|
+
parameters: [{ name: "conversationId", in: "path", required: true, schema: { type: "string", format: "uuid" } }],
|
|
13540
|
+
requestBody: { required: true, content: { "application/json": { schema: { type: "object", required: ["text"], properties: { clientTurnId: { type: "string", minLength: 1, maxLength: 128, pattern: "^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$", description: "Optional stable idempotency key, scoped to this conversation. Omit only for non-idempotent legacy submission." }, text: { type: "string", maxLength: 2e4 } }, additionalProperties: false }, examples: { legacy: { summary: "Legacy text-only submission (do not automatically retry ambiguous requests)", value: { text: "Summarize the failing test and propose a fix." } }, idempotent: { summary: "Retry-safe submission", value: { clientTurnId: "turn-550e8400-e29b-41d4-a716-446655440000", text: "Summarize the failing test and propose a fix." } } } } } },
|
|
13541
|
+
responses: { "202": { description: "Turn accepted; keyed submissions replay the persisted response for the same ID and text" }, "400": { description: "clientTurnId, when present, or text is invalid" }, "409": { description: "conversation_busy or idempotency_conflict for a reused key with different text" } }
|
|
13542
|
+
}
|
|
13543
|
+
},
|
|
13544
|
+
"/api/v1/conversations/{conversationId}/events": {
|
|
13545
|
+
get: {
|
|
13546
|
+
summary: "Reconnectable per-conversation SSE stream",
|
|
13547
|
+
security: [{ asterBearer: [] }],
|
|
13548
|
+
parameters: [
|
|
13549
|
+
{ name: "conversationId", in: "path", required: true, schema: { type: "string", format: "uuid" } },
|
|
13550
|
+
{ name: "Last-Event-ID", in: "header", schema: { type: "integer" }, description: "Replay events with larger sequence IDs." },
|
|
13551
|
+
{ name: "after", in: "query", schema: { type: "integer", minimum: 0 }, description: "Alternative replay cursor." }
|
|
13552
|
+
],
|
|
13553
|
+
responses: { "200": { description: "Typed SSE events with monotonic IDs: status, assistant.delta, tool.activity, approval.required, approval.resolved, approval.expired, usage, completed, cancelled, failed." } }
|
|
13554
|
+
}
|
|
13555
|
+
},
|
|
13556
|
+
"/api/v1/conversations/{conversationId}/cancel": {
|
|
13557
|
+
post: {
|
|
13558
|
+
summary: "Idempotently cancel the active run and abort its agent signal",
|
|
13559
|
+
security: [{ asterBearer: [] }],
|
|
13560
|
+
parameters: [{ name: "conversationId", in: "path", required: true, schema: { type: "string", format: "uuid" } }],
|
|
13561
|
+
responses: { "200": { description: "Current conversation state" }, "404": { description: "Conversation not found" } }
|
|
13562
|
+
}
|
|
13563
|
+
},
|
|
13564
|
+
"/api/v1/approvals/{approvalId}": {
|
|
13565
|
+
get: {
|
|
13566
|
+
summary: "Review a pending approval action and exact arguments",
|
|
13567
|
+
security: [{ asterBearer: [] }],
|
|
13568
|
+
parameters: [{ name: "approvalId", in: "path", required: true, schema: { type: "string", format: "uuid" } }],
|
|
13569
|
+
responses: { "200": { description: "Pending action and exact arguments; secret-like actions are rejected before approval" }, "404": { description: "Approval not found" } }
|
|
13570
|
+
},
|
|
13571
|
+
post: {
|
|
13572
|
+
summary: "Allow or deny one pending action; resolution is single-use and idempotent",
|
|
13573
|
+
security: [{ asterBearer: [] }],
|
|
13574
|
+
parameters: [{ name: "approvalId", in: "path", required: true, schema: { type: "string", format: "uuid" } }],
|
|
13575
|
+
requestBody: { required: true, content: { "application/json": { schema: { type: "object", required: ["decision"], properties: { decision: { type: "string", enum: ["allow", "deny"] } }, additionalProperties: false }, example: { decision: "allow" } } } },
|
|
13576
|
+
responses: { "200": { description: "Resolved approval" }, "409": { description: "Conflicting prior resolution" }, "410": { description: "Approval expired; action was not executed" } }
|
|
13577
|
+
}
|
|
13048
13578
|
}
|
|
13049
13579
|
},
|
|
13050
13580
|
components: {
|
|
13051
13581
|
securitySchemes: {
|
|
13052
|
-
basicAuth: { type: "http", scheme: "basic" }
|
|
13582
|
+
basicAuth: { type: "http", scheme: "basic" },
|
|
13583
|
+
asterBearer: { type: "http", scheme: "bearer", bearerFormat: "revocable, workspace-scoped Autopilot credential" }
|
|
13053
13584
|
}
|
|
13054
13585
|
}
|
|
13055
13586
|
};
|
|
@@ -13082,18 +13613,18 @@ var init_openapi = __esm({
|
|
|
13082
13613
|
|
|
13083
13614
|
// src/runs/worktrees.ts
|
|
13084
13615
|
import { execFile as execFile3 } from "child_process";
|
|
13085
|
-
import { access as access2, chmod as chmod2, mkdir as mkdir10, realpath as
|
|
13086
|
-
import { homedir as
|
|
13087
|
-
import { isAbsolute as
|
|
13616
|
+
import { access as access2, chmod as chmod2, mkdir as mkdir10, realpath as realpath4 } from "fs/promises";
|
|
13617
|
+
import { homedir as homedir19 } from "os";
|
|
13618
|
+
import { isAbsolute as isAbsolute4, join as join26, parse, relative as relative6, resolve as resolve7, sep as sep4 } from "path";
|
|
13088
13619
|
function defaultWorktreesDir() {
|
|
13089
|
-
const dataHome = process.env.XDG_DATA_HOME ||
|
|
13090
|
-
return
|
|
13620
|
+
const dataHome = process.env.XDG_DATA_HOME || join26(homedir19(), ".local", "share");
|
|
13621
|
+
return join26(dataHome, "autopilot", "worktrees");
|
|
13091
13622
|
}
|
|
13092
13623
|
function isOutside(path) {
|
|
13093
|
-
return path === ".." || path.startsWith(`..${
|
|
13624
|
+
return path === ".." || path.startsWith(`..${sep4}`) || isAbsolute4(path);
|
|
13094
13625
|
}
|
|
13095
|
-
function
|
|
13096
|
-
const path =
|
|
13626
|
+
function isWithin2(parent, candidate) {
|
|
13627
|
+
const path = relative6(parent, candidate);
|
|
13097
13628
|
return path === "" || !isOutside(path);
|
|
13098
13629
|
}
|
|
13099
13630
|
function runGit(args, cwd) {
|
|
@@ -13121,24 +13652,24 @@ var init_worktrees = __esm({
|
|
|
13121
13652
|
if (runId === "." || runId === ".." || !/^[a-zA-Z0-9][a-zA-Z0-9._-]{0,127}$/.test(runId)) {
|
|
13122
13653
|
throw new Error("runId must contain only letters, numbers, dots, underscores, or hyphens");
|
|
13123
13654
|
}
|
|
13124
|
-
const source = await
|
|
13655
|
+
const source = await realpath4(sourceCwd);
|
|
13125
13656
|
const repositoryRoot = (await runGit(["rev-parse", "--show-toplevel"], source)).trim();
|
|
13126
|
-
const repository = await
|
|
13127
|
-
const sourceRelativePath =
|
|
13657
|
+
const repository = await realpath4(repositoryRoot);
|
|
13658
|
+
const sourceRelativePath = relative6(repository, source);
|
|
13128
13659
|
if (isOutside(sourceRelativePath)) {
|
|
13129
13660
|
throw new Error("sourceCwd must be inside the Git repository");
|
|
13130
13661
|
}
|
|
13131
13662
|
const configuredRoot = resolve7(this.rootDir);
|
|
13132
|
-
if (configuredRoot === parse(configuredRoot).root ||
|
|
13663
|
+
if (configuredRoot === parse(configuredRoot).root || isWithin2(repository, configuredRoot)) {
|
|
13133
13664
|
throw new Error("worktree root must be outside the source repository and not the filesystem root");
|
|
13134
13665
|
}
|
|
13135
13666
|
await mkdir10(configuredRoot, { recursive: true, mode: 448 });
|
|
13136
|
-
const worktreesRoot = await
|
|
13137
|
-
if (
|
|
13667
|
+
const worktreesRoot = await realpath4(configuredRoot);
|
|
13668
|
+
if (isWithin2(repository, worktreesRoot)) {
|
|
13138
13669
|
throw new Error("worktree root must be outside the source repository");
|
|
13139
13670
|
}
|
|
13140
13671
|
await chmod2(worktreesRoot, 448);
|
|
13141
|
-
const worktreePath =
|
|
13672
|
+
const worktreePath = join26(worktreesRoot, runId);
|
|
13142
13673
|
try {
|
|
13143
13674
|
await access2(worktreePath);
|
|
13144
13675
|
throw new Error(`worktree path already exists for run ${runId}`);
|
|
@@ -13158,10 +13689,10 @@ var init_worktrees = __esm({
|
|
|
13158
13689
|
}
|
|
13159
13690
|
/** Remove a newly-created, still-clean worktree when run initialization fails. */
|
|
13160
13691
|
async discard(worktree) {
|
|
13161
|
-
const root = await
|
|
13162
|
-
const target = await
|
|
13163
|
-
const repository = await
|
|
13164
|
-
if (target === root || !
|
|
13692
|
+
const root = await realpath4(this.rootDir);
|
|
13693
|
+
const target = await realpath4(worktree.worktreePath);
|
|
13694
|
+
const repository = await realpath4(worktree.repositoryRoot);
|
|
13695
|
+
if (target === root || !isWithin2(root, target)) {
|
|
13165
13696
|
throw new Error("worktree path is outside the configured worktree root");
|
|
13166
13697
|
}
|
|
13167
13698
|
if (worktree.branch !== `autopilot/run/${worktree.runId}`) {
|
|
@@ -13305,20 +13836,759 @@ var init_wake_scheduler = __esm({
|
|
|
13305
13836
|
}
|
|
13306
13837
|
});
|
|
13307
13838
|
|
|
13839
|
+
// src/server/aster-tools.ts
|
|
13840
|
+
import { realpath as realpath5 } from "fs/promises";
|
|
13841
|
+
import { isAbsolute as isAbsolute5, relative as relative7, sep as sep5 } from "path";
|
|
13842
|
+
function createAsterTools(workspaceRoot) {
|
|
13843
|
+
return ASTR_TOOLS.map((tool) => ({
|
|
13844
|
+
...tool,
|
|
13845
|
+
run: async (args, context) => {
|
|
13846
|
+
await assertAsterWorkspacePath(workspaceRoot, args.path, tool.name !== "read");
|
|
13847
|
+
return tool.run(args, { ...context, cwd: workspaceRoot });
|
|
13848
|
+
}
|
|
13849
|
+
}));
|
|
13850
|
+
}
|
|
13851
|
+
async function assertAsterWorkspacePath(workspaceRoot, value, allowMissing = false) {
|
|
13852
|
+
if (typeof value !== "string" || !value.trim()) throw new Error("workspace_path_invalid");
|
|
13853
|
+
const root = await realpath5(workspaceRoot);
|
|
13854
|
+
const candidate = resolvePath(root, value);
|
|
13855
|
+
assertInside(root, candidate);
|
|
13856
|
+
assertNotSensitive(root, candidate);
|
|
13857
|
+
let actual;
|
|
13858
|
+
try {
|
|
13859
|
+
actual = await realpath5(candidate);
|
|
13860
|
+
} catch (error) {
|
|
13861
|
+
if (!allowMissing || error.code !== "ENOENT") throw new Error("workspace_path_unavailable");
|
|
13862
|
+
let ancestor = candidate;
|
|
13863
|
+
while (true) {
|
|
13864
|
+
try {
|
|
13865
|
+
actual = await realpath5(ancestor);
|
|
13866
|
+
break;
|
|
13867
|
+
} catch (ancestorError) {
|
|
13868
|
+
if (ancestorError.code !== "ENOENT") throw new Error("workspace_path_unavailable");
|
|
13869
|
+
const parent = relative7(root, ancestor);
|
|
13870
|
+
if (!parent || parent === ".") throw new Error("workspace_path_unavailable");
|
|
13871
|
+
ancestor = ancestor.slice(0, ancestor.lastIndexOf(sep5));
|
|
13872
|
+
}
|
|
13873
|
+
}
|
|
13874
|
+
}
|
|
13875
|
+
assertInside(root, actual);
|
|
13876
|
+
assertNotSensitive(root, actual);
|
|
13877
|
+
return candidate;
|
|
13878
|
+
}
|
|
13879
|
+
function containsLikelyProviderSecret(value) {
|
|
13880
|
+
return /(?:sk-or-(?:v1-)?[A-Za-z0-9_-]{16,}|sk-[A-Za-z0-9_-]{24,}|gh[pousr]_[A-Za-z0-9]{20,}|github_pat_[A-Za-z0-9_]{20,}|(?:CLOUDFLARE|OPENROUTER|REQUESTY)_API_(?:KEY|TOKEN)\s*=)/i.test(value);
|
|
13881
|
+
}
|
|
13882
|
+
function redactLikelySecrets(value) {
|
|
13883
|
+
return value.replace(/sk-or-(?:v1-)?[A-Za-z0-9_-]{16,}/g, "[REDACTED_PROVIDER_SECRET]").replace(/sk-[A-Za-z0-9_-]{24,}/g, "[REDACTED_PROVIDER_SECRET]").replace(/gh[pousr]_[A-Za-z0-9]{20,}/g, "[REDACTED_PROVIDER_SECRET]").replace(/github_pat_[A-Za-z0-9_]{20,}/g, "[REDACTED_PROVIDER_SECRET]");
|
|
13884
|
+
}
|
|
13885
|
+
function assertInside(root, candidate) {
|
|
13886
|
+
const rel = relative7(root, candidate);
|
|
13887
|
+
if (rel === ".." || rel.startsWith(`..${sep5}`) || isAbsolute5(rel)) throw new Error("workspace_path_forbidden");
|
|
13888
|
+
}
|
|
13889
|
+
function assertNotSensitive(root, candidate) {
|
|
13890
|
+
const parts = relative7(root, candidate).split(sep5).filter(Boolean).map((part) => part.toLowerCase());
|
|
13891
|
+
if (parts.some((part) => part === ".env" || part.startsWith(".env.") || SENSITIVE_NAMES.has(part))) {
|
|
13892
|
+
throw new Error("workspace_sensitive_path_forbidden");
|
|
13893
|
+
}
|
|
13894
|
+
}
|
|
13895
|
+
var ASTR_TOOLS, SENSITIVE_NAMES;
|
|
13896
|
+
var init_aster_tools = __esm({
|
|
13897
|
+
"src/server/aster-tools.ts"() {
|
|
13898
|
+
"use strict";
|
|
13899
|
+
init_edit();
|
|
13900
|
+
init_read();
|
|
13901
|
+
init_write();
|
|
13902
|
+
init_paths();
|
|
13903
|
+
ASTR_TOOLS = [readTool, writeTool, editTool];
|
|
13904
|
+
SENSITIVE_NAMES = /* @__PURE__ */ new Set([
|
|
13905
|
+
".git",
|
|
13906
|
+
".ssh",
|
|
13907
|
+
".aws",
|
|
13908
|
+
".azure",
|
|
13909
|
+
".config",
|
|
13910
|
+
".netrc",
|
|
13911
|
+
".npmrc",
|
|
13912
|
+
".git-credentials",
|
|
13913
|
+
"id_rsa",
|
|
13914
|
+
"id_ed25519",
|
|
13915
|
+
"credentials",
|
|
13916
|
+
"secrets"
|
|
13917
|
+
]);
|
|
13918
|
+
}
|
|
13919
|
+
});
|
|
13920
|
+
|
|
13921
|
+
// src/server/aster-api.ts
|
|
13922
|
+
import { randomUUID as randomUUID6 } from "crypto";
|
|
13923
|
+
import { unlink as unlink2 } from "fs/promises";
|
|
13924
|
+
import { resolve as resolve8 } from "path";
|
|
13925
|
+
function parseCursor(header, query) {
|
|
13926
|
+
const value = (Array.isArray(header) ? header[0] : header) ?? query ?? "0";
|
|
13927
|
+
if (!/^\d{1,16}$/.test(value)) return void 0;
|
|
13928
|
+
const cursor = Number(value);
|
|
13929
|
+
return Number.isSafeInteger(cursor) ? cursor : void 0;
|
|
13930
|
+
}
|
|
13931
|
+
function publicConversation(conversation) {
|
|
13932
|
+
return {
|
|
13933
|
+
conversationId: conversation.id,
|
|
13934
|
+
workspaceId: conversation.workspaceId,
|
|
13935
|
+
model: conversation.model,
|
|
13936
|
+
status: conversation.status,
|
|
13937
|
+
activeRunId: conversation.activeRunId,
|
|
13938
|
+
lastEventId: conversation.lastEventSequence,
|
|
13939
|
+
createdAt: new Date(conversation.createdAt).toISOString(),
|
|
13940
|
+
updatedAt: new Date(conversation.updatedAt).toISOString()
|
|
13941
|
+
};
|
|
13942
|
+
}
|
|
13943
|
+
function publicApproval(approval) {
|
|
13944
|
+
return {
|
|
13945
|
+
approvalId: approval.id,
|
|
13946
|
+
conversationId: approval.conversationId,
|
|
13947
|
+
runId: approval.runId,
|
|
13948
|
+
status: approval.status,
|
|
13949
|
+
tool: approval.toolName,
|
|
13950
|
+
explanation: approval.explanation,
|
|
13951
|
+
arguments: approval.arguments,
|
|
13952
|
+
expiresAt: new Date(approval.expiresAt).toISOString(),
|
|
13953
|
+
decision: approval.decision
|
|
13954
|
+
};
|
|
13955
|
+
}
|
|
13956
|
+
async function readJsonBody(req) {
|
|
13957
|
+
const chunks = [];
|
|
13958
|
+
let bytes = 0;
|
|
13959
|
+
for await (const chunk of req) {
|
|
13960
|
+
const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
13961
|
+
bytes += buffer.length;
|
|
13962
|
+
if (bytes > MAX_BODY_BYTES) throw httpFailure(413, "request_too_large", "Request body exceeds 64 KiB");
|
|
13963
|
+
chunks.push(buffer);
|
|
13964
|
+
}
|
|
13965
|
+
let parsed;
|
|
13966
|
+
try {
|
|
13967
|
+
parsed = JSON.parse(Buffer.concat(chunks).toString("utf8"));
|
|
13968
|
+
} catch {
|
|
13969
|
+
throw httpFailure(400, "invalid_json", "Request body must be valid JSON");
|
|
13970
|
+
}
|
|
13971
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) throw httpFailure(400, "invalid_json", "Request body must be a JSON object");
|
|
13972
|
+
return parsed;
|
|
13973
|
+
}
|
|
13974
|
+
function httpFailure(status, code, message2) {
|
|
13975
|
+
return { status, code, message: message2 };
|
|
13976
|
+
}
|
|
13977
|
+
function isHttpFailure(error) {
|
|
13978
|
+
return !!error && typeof error === "object" && "status" in error && "code" in error && "message" in error;
|
|
13979
|
+
}
|
|
13980
|
+
function sendError(res, status, code, message2) {
|
|
13981
|
+
json(res, status, { error: { code, message: message2 } });
|
|
13982
|
+
}
|
|
13983
|
+
function json(res, status, data) {
|
|
13984
|
+
res.writeHead(status, { "Content-Type": "application/json; charset=utf-8", "Cache-Control": "no-store" });
|
|
13985
|
+
res.end(JSON.stringify(data));
|
|
13986
|
+
}
|
|
13987
|
+
var API_PREFIX, MAX_BODY_BYTES, MAX_USER_TURN_CHARS, CLIENT_TURN_ID_RE, MAX_APPROVAL_ARG_BYTES, CONVERSATION_ID_RE, ALLOWED_ASTER_TOOLS, AsterApi;
|
|
13988
|
+
var init_aster_api = __esm({
|
|
13989
|
+
"src/server/aster-api.ts"() {
|
|
13990
|
+
"use strict";
|
|
13991
|
+
init_system_prompt();
|
|
13992
|
+
init_executor();
|
|
13993
|
+
init_store();
|
|
13994
|
+
init_worktrees();
|
|
13995
|
+
init_sessions();
|
|
13996
|
+
init_version();
|
|
13997
|
+
init_aster_store();
|
|
13998
|
+
init_aster_workspaces();
|
|
13999
|
+
init_aster_tools();
|
|
14000
|
+
API_PREFIX = "/api/v1";
|
|
14001
|
+
MAX_BODY_BYTES = 64 * 1024;
|
|
14002
|
+
MAX_USER_TURN_CHARS = 2e4;
|
|
14003
|
+
CLIENT_TURN_ID_RE = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/;
|
|
14004
|
+
MAX_APPROVAL_ARG_BYTES = 24 * 1024;
|
|
14005
|
+
CONVERSATION_ID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
14006
|
+
ALLOWED_ASTER_TOOLS = ["read", "write", "edit"];
|
|
14007
|
+
AsterApi = class {
|
|
14008
|
+
store;
|
|
14009
|
+
recovery;
|
|
14010
|
+
waiters = /* @__PURE__ */ new Map();
|
|
14011
|
+
runtime;
|
|
14012
|
+
config;
|
|
14013
|
+
constructor(config2, runtime) {
|
|
14014
|
+
this.config = config2;
|
|
14015
|
+
this.runtime = runtime;
|
|
14016
|
+
}
|
|
14017
|
+
async handle(req, res) {
|
|
14018
|
+
const url = new URL(req.url ?? "/", `http://${req.headers.host ?? "localhost"}`);
|
|
14019
|
+
try {
|
|
14020
|
+
const store = this.getStore();
|
|
14021
|
+
const principal = store.authenticate(req.headers.authorization);
|
|
14022
|
+
if (!principal) {
|
|
14023
|
+
sendError(res, 401, "invalid_credential", "A valid Aster bearer credential is required");
|
|
14024
|
+
return;
|
|
14025
|
+
}
|
|
14026
|
+
await this.ensureRecovered(store);
|
|
14027
|
+
const config2 = await loadAsterServerConfig();
|
|
14028
|
+
const method = req.method ?? "GET";
|
|
14029
|
+
const path = url.pathname;
|
|
14030
|
+
if (path === `${API_PREFIX}/health` && method === "GET") {
|
|
14031
|
+
if (!this.hasScope(principal, "workspaces:read")) return sendError(res, 403, "insufficient_scope", "Credential lacks health/read scope");
|
|
14032
|
+
return json(res, 200, {
|
|
14033
|
+
status: "ok",
|
|
14034
|
+
service: "autopilot",
|
|
14035
|
+
apiVersion: "v1",
|
|
14036
|
+
serviceVersion: getAppVersion(),
|
|
14037
|
+
capabilities: ["workspaces", "conversations", "turns", "event-replay", "cancellation", "human-approvals"]
|
|
14038
|
+
});
|
|
14039
|
+
}
|
|
14040
|
+
if (path === `${API_PREFIX}/workspaces` && method === "GET") {
|
|
14041
|
+
if (!this.hasScope(principal, "workspaces:read")) return sendError(res, 403, "insufficient_scope", "Credential lacks workspace/read scope");
|
|
14042
|
+
return json(res, 200, {
|
|
14043
|
+
workspaces: config2.workspaces.filter((workspace) => principal.workspaceIds.includes(workspace.id)).map(({ id, displayName }) => ({ id, displayName }))
|
|
14044
|
+
});
|
|
14045
|
+
}
|
|
14046
|
+
if (path === `${API_PREFIX}/models` && method === "GET") {
|
|
14047
|
+
if (!this.hasScope(principal, "models:read")) return sendError(res, 403, "insufficient_scope", "Credential lacks model/read scope");
|
|
14048
|
+
return json(res, 200, { models: config2.models });
|
|
14049
|
+
}
|
|
14050
|
+
if (path === `${API_PREFIX}/conversations` && method === "POST") {
|
|
14051
|
+
if (!this.hasScope(principal, "conversations:write")) return sendError(res, 403, "insufficient_scope", "Credential lacks conversation/write scope");
|
|
14052
|
+
const body = await readJsonBody(req);
|
|
14053
|
+
if (Object.keys(body).some((key) => key !== "workspaceId" && key !== "model")) {
|
|
14054
|
+
return sendError(res, 400, "unsupported_field", "Conversation creation accepts only workspaceId and model; paths are server-configured");
|
|
14055
|
+
}
|
|
14056
|
+
if (typeof body.workspaceId !== "string" || !principal.workspaceIds.includes(body.workspaceId)) {
|
|
14057
|
+
return sendError(res, 403, "workspace_forbidden", "Workspace ID is not in this credential's scope");
|
|
14058
|
+
}
|
|
14059
|
+
const workspace = findAsterWorkspace(config2, body.workspaceId);
|
|
14060
|
+
if (!workspace) return sendError(res, 404, "workspace_not_found", "Workspace is not configured");
|
|
14061
|
+
if (typeof body.model !== "string" || !config2.models.includes(body.model)) {
|
|
14062
|
+
return sendError(res, 400, "model_not_allowed", "Select a model from the configured model list");
|
|
14063
|
+
}
|
|
14064
|
+
return await this.createConversation(res, store, workspace, body.model);
|
|
14065
|
+
}
|
|
14066
|
+
const eventsMatch = path.match(/^\/api\/v1\/conversations\/([^/]+)\/events$/);
|
|
14067
|
+
if (eventsMatch && method === "GET") {
|
|
14068
|
+
if (!this.hasScope(principal, "conversations:read")) return sendError(res, 403, "insufficient_scope", "Credential lacks conversation/read scope");
|
|
14069
|
+
const conversation = await this.authorizedConversation(store, principal, eventsMatch[1]);
|
|
14070
|
+
if (!conversation) return sendError(res, 404, "conversation_not_found", "Conversation not found");
|
|
14071
|
+
const cursor = parseCursor(req.headers["last-event-id"], url.searchParams.get("after"));
|
|
14072
|
+
if (cursor === void 0) return sendError(res, 400, "invalid_event_cursor", "Event cursor must be a non-negative integer");
|
|
14073
|
+
return this.streamEvents(req, res, store, conversation.id, cursor);
|
|
14074
|
+
}
|
|
14075
|
+
const turnMatch = path.match(/^\/api\/v1\/conversations\/([^/]+)\/turns$/);
|
|
14076
|
+
if (turnMatch && method === "POST") {
|
|
14077
|
+
if (!this.hasScope(principal, "conversations:write")) return sendError(res, 403, "insufficient_scope", "Credential lacks conversation/write scope");
|
|
14078
|
+
const conversation = await this.authorizedConversation(store, principal, turnMatch[1]);
|
|
14079
|
+
if (!conversation) return sendError(res, 404, "conversation_not_found", "Conversation not found");
|
|
14080
|
+
const body = await readJsonBody(req);
|
|
14081
|
+
if (Object.keys(body).some((key) => key !== "text" && key !== "clientTurnId")) return sendError(res, 400, "unsupported_field", "Turns accept text and an optional clientTurnId; workspace and model are fixed by the conversation");
|
|
14082
|
+
const hasClientTurnId = Object.hasOwn(body, "clientTurnId");
|
|
14083
|
+
const clientTurnId = hasClientTurnId && typeof body.clientTurnId === "string" ? body.clientTurnId : void 0;
|
|
14084
|
+
if (hasClientTurnId && (!clientTurnId || !CLIENT_TURN_ID_RE.test(clientTurnId))) return sendError(res, 400, "invalid_client_turn_id", "clientTurnId must be 1-128 ASCII letters, digits, '.', '_', ':', or '-' and start with a letter or digit");
|
|
14085
|
+
const requestText = typeof body.text === "string" ? body.text : "";
|
|
14086
|
+
const text = requestText.trim();
|
|
14087
|
+
if (!text || text.length > MAX_USER_TURN_CHARS) return sendError(res, 400, "invalid_turn", `Turn text must be 1-${MAX_USER_TURN_CHARS} characters`);
|
|
14088
|
+
if (containsLikelyProviderSecret(text)) return sendError(res, 400, "secret_input_rejected", "Provider credentials must not be sent in conversation text");
|
|
14089
|
+
return await this.appendTurn(res, store, conversation, clientTurnId, requestText, text);
|
|
14090
|
+
}
|
|
14091
|
+
const cancelMatch = path.match(/^\/api\/v1\/conversations\/([^/]+)\/cancel$/);
|
|
14092
|
+
if (cancelMatch && method === "POST") {
|
|
14093
|
+
if (!this.hasScope(principal, "conversations:write")) return sendError(res, 403, "insufficient_scope", "Credential lacks conversation/write scope");
|
|
14094
|
+
const conversation = await this.authorizedConversation(store, principal, cancelMatch[1]);
|
|
14095
|
+
if (!conversation) return sendError(res, 404, "conversation_not_found", "Conversation not found");
|
|
14096
|
+
return this.cancelConversation(res, store, conversation);
|
|
14097
|
+
}
|
|
14098
|
+
const conversationMatch = path.match(/^\/api\/v1\/conversations\/([^/]+)$/);
|
|
14099
|
+
if (conversationMatch && method === "GET") {
|
|
14100
|
+
if (!this.hasScope(principal, "conversations:read")) return sendError(res, 403, "insufficient_scope", "Credential lacks conversation/read scope");
|
|
14101
|
+
const conversation = await this.authorizedConversation(store, principal, conversationMatch[1]);
|
|
14102
|
+
if (!conversation) return sendError(res, 404, "conversation_not_found", "Conversation not found");
|
|
14103
|
+
return json(res, 200, publicConversation(conversation));
|
|
14104
|
+
}
|
|
14105
|
+
const approvalMatch = path.match(/^\/api\/v1\/approvals\/([^/]+)$/);
|
|
14106
|
+
if (approvalMatch && method === "GET") {
|
|
14107
|
+
if (!this.hasScope(principal, "conversations:read")) return sendError(res, 403, "insufficient_scope", "Credential lacks conversation/read scope");
|
|
14108
|
+
const approval = store.getApproval(approvalMatch[1]);
|
|
14109
|
+
if (!approval) return sendError(res, 404, "approval_not_found", "Approval not found");
|
|
14110
|
+
const conversation = store.getConversation(approval.conversationId);
|
|
14111
|
+
if (!conversation || !principal.workspaceIds.includes(conversation.workspaceId)) return sendError(res, 404, "approval_not_found", "Approval not found");
|
|
14112
|
+
return json(res, 200, publicApproval(approval));
|
|
14113
|
+
}
|
|
14114
|
+
if (approvalMatch && method === "POST") {
|
|
14115
|
+
if (!this.hasScope(principal, "approvals:resolve")) return sendError(res, 403, "insufficient_scope", "Credential lacks approval/resolve scope");
|
|
14116
|
+
const approval = store.getApproval(approvalMatch[1]);
|
|
14117
|
+
if (!approval) return sendError(res, 404, "approval_not_found", "Approval not found");
|
|
14118
|
+
const conversation = store.getConversation(approval.conversationId);
|
|
14119
|
+
if (!conversation || !principal.workspaceIds.includes(conversation.workspaceId)) return sendError(res, 404, "approval_not_found", "Approval not found");
|
|
14120
|
+
const body = await readJsonBody(req);
|
|
14121
|
+
if (Object.keys(body).some((key) => key !== "decision")) return sendError(res, 400, "unsupported_field", "Approval resolution accepts decision only");
|
|
14122
|
+
if (body.decision !== "allow" && body.decision !== "deny") return sendError(res, 400, "invalid_approval_decision", "decision must be allow or deny");
|
|
14123
|
+
return this.resolveApproval(res, store, approval.id, body.decision);
|
|
14124
|
+
}
|
|
14125
|
+
return sendError(res, 404, "not_found", "Aster endpoint not found");
|
|
14126
|
+
} catch (error) {
|
|
14127
|
+
if (isHttpFailure(error)) return sendError(res, error.status, error.code, error.message);
|
|
14128
|
+
if (error instanceof AsterConfigError) return sendError(res, 503, "aster_not_configured", "Aster workspaces/models are not configured");
|
|
14129
|
+
return sendError(res, 500, "internal_error", "Aster request could not be completed");
|
|
14130
|
+
}
|
|
14131
|
+
}
|
|
14132
|
+
close() {
|
|
14133
|
+
for (const [id, waiter] of this.waiters) {
|
|
14134
|
+
clearTimeout(waiter.timer);
|
|
14135
|
+
waiter.resolve("deny");
|
|
14136
|
+
this.waiters.delete(id);
|
|
14137
|
+
}
|
|
14138
|
+
this.store?.close();
|
|
14139
|
+
this.store = void 0;
|
|
14140
|
+
}
|
|
14141
|
+
getStore() {
|
|
14142
|
+
return this.store ??= new AsterStore();
|
|
14143
|
+
}
|
|
14144
|
+
hasScope(principal, scope) {
|
|
14145
|
+
return principal.scopes.includes(scope);
|
|
14146
|
+
}
|
|
14147
|
+
async authorizedConversation(store, principal, id) {
|
|
14148
|
+
if (!CONVERSATION_ID_RE.test(id)) return void 0;
|
|
14149
|
+
const conversation = store.getConversation(id);
|
|
14150
|
+
if (!conversation || !principal.workspaceIds.includes(conversation.workspaceId)) return void 0;
|
|
14151
|
+
const activeRunId = conversation.activeRunId;
|
|
14152
|
+
if (activeRunId) {
|
|
14153
|
+
const runs = new RunStore();
|
|
14154
|
+
try {
|
|
14155
|
+
const run = runs.getRun(activeRunId);
|
|
14156
|
+
if (run && ["interrupted_unknown", "cancelled", "failed", "completed"].includes(run.status)) {
|
|
14157
|
+
const status = run.status === "completed" ? "completed" : run.status === "cancelled" ? "cancelled" : "interrupted";
|
|
14158
|
+
store.updateConversationStatus(conversation.id, activeRunId, status, { reason: "run_recovered" });
|
|
14159
|
+
return store.getConversation(id);
|
|
14160
|
+
}
|
|
14161
|
+
} finally {
|
|
14162
|
+
runs.close();
|
|
14163
|
+
}
|
|
14164
|
+
}
|
|
14165
|
+
return conversation;
|
|
14166
|
+
}
|
|
14167
|
+
async ensureRecovered(store) {
|
|
14168
|
+
if (this.recovery) return this.recovery;
|
|
14169
|
+
this.recovery = (async () => {
|
|
14170
|
+
const runs = new RunStore();
|
|
14171
|
+
try {
|
|
14172
|
+
runs.reconcileInterruptedRuns();
|
|
14173
|
+
for (const approval of store.expireAllPendingApprovals()) {
|
|
14174
|
+
const run = runs.getRun(approval.runId);
|
|
14175
|
+
if (run && ["running", "waiting", "needs_input", "queued"].includes(run.status)) {
|
|
14176
|
+
runs.transition(run.id, "cancelled", "server_restarted_during_approval");
|
|
14177
|
+
runs.cancelTimersForRun(run.id);
|
|
14178
|
+
}
|
|
14179
|
+
store.updateConversationStatus(approval.conversationId, approval.runId, "interrupted", { reason: "approval_expired_on_restart" });
|
|
14180
|
+
store.appendEvent(approval.conversationId, approval.runId, "approval.expired", { approvalId: approval.id });
|
|
14181
|
+
}
|
|
14182
|
+
for (const conversation of store.listActiveConversations()) {
|
|
14183
|
+
const run = conversation.activeRunId ? runs.getRun(conversation.activeRunId) : void 0;
|
|
14184
|
+
if (!run || ["interrupted_unknown", "cancelled", "failed", "completed"].includes(run.status)) {
|
|
14185
|
+
const status = run?.status === "completed" ? "completed" : run?.status === "cancelled" ? "cancelled" : "interrupted";
|
|
14186
|
+
store.updateConversationStatus(conversation.id, conversation.activeRunId, status, { reason: "server_restart_recovery" });
|
|
14187
|
+
} else if (run.status === "needs_input") {
|
|
14188
|
+
runs.transition(run.id, "cancelled", "approval_expired_on_restart");
|
|
14189
|
+
store.updateConversationStatus(conversation.id, run.id, "interrupted", { reason: "approval_expired_on_restart" });
|
|
14190
|
+
}
|
|
14191
|
+
}
|
|
14192
|
+
} finally {
|
|
14193
|
+
runs.close();
|
|
14194
|
+
}
|
|
14195
|
+
})();
|
|
14196
|
+
return this.recovery;
|
|
14197
|
+
}
|
|
14198
|
+
async createConversation(res, store, workspace, model) {
|
|
14199
|
+
const id = randomUUID6();
|
|
14200
|
+
const sessionId = randomUUID6();
|
|
14201
|
+
const worktreeManager = new RunWorktreeManager();
|
|
14202
|
+
let worktree;
|
|
14203
|
+
try {
|
|
14204
|
+
worktree = await worktreeManager.create(id, workspace.rootPath);
|
|
14205
|
+
} catch {
|
|
14206
|
+
sendError(res, 409, "workspace_repository_unavailable", "Configured workspace must be an accessible Git repository");
|
|
14207
|
+
return;
|
|
14208
|
+
}
|
|
14209
|
+
const tools = createAsterTools(worktree.cwd);
|
|
14210
|
+
const messages = [
|
|
14211
|
+
{ role: "system", content: buildSystemPrompt({ cwd: worktree.cwd, tools, model, preferPullRequests: true }) },
|
|
14212
|
+
{ role: "system", content: "You are operating through Aster. Only read, write, and edit tools are available. All file access is confined to the selected workspace. Never request, reveal, store, or repeat provider credentials or secrets." }
|
|
14213
|
+
];
|
|
14214
|
+
const sessionFile = {
|
|
14215
|
+
id: sessionId,
|
|
14216
|
+
cwd: worktree.cwd,
|
|
14217
|
+
model,
|
|
14218
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
14219
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
14220
|
+
messages,
|
|
14221
|
+
title: "Aster conversation"
|
|
14222
|
+
};
|
|
14223
|
+
try {
|
|
14224
|
+
await saveSession(sessionFile);
|
|
14225
|
+
const conversation = store.createConversation({
|
|
14226
|
+
id,
|
|
14227
|
+
workspaceId: workspace.id,
|
|
14228
|
+
model,
|
|
14229
|
+
sessionId,
|
|
14230
|
+
worktreePath: worktree.worktreePath,
|
|
14231
|
+
cwd: worktree.cwd,
|
|
14232
|
+
branch: worktree.branch
|
|
14233
|
+
});
|
|
14234
|
+
store.appendEvent(id, null, "conversation.created", { conversationId: id, workspaceId: workspace.id, model });
|
|
14235
|
+
json(res, 201, publicConversation(conversation));
|
|
14236
|
+
} catch {
|
|
14237
|
+
await unlink2(resolve8(sessionsDir(), `${sessionId}.json`)).catch(() => {
|
|
14238
|
+
});
|
|
14239
|
+
await worktreeManager.discard(worktree).catch(() => {
|
|
14240
|
+
});
|
|
14241
|
+
sendError(res, 500, "conversation_create_failed", "Conversation could not be created");
|
|
14242
|
+
}
|
|
14243
|
+
}
|
|
14244
|
+
async appendTurn(res, store, conversation, clientTurnId, requestText, text) {
|
|
14245
|
+
const idempotent = clientTurnId !== void 0;
|
|
14246
|
+
if (clientTurnId !== void 0) {
|
|
14247
|
+
const previous = store.lookupTurn(conversation.id, clientTurnId, requestText);
|
|
14248
|
+
if (previous.kind === "replay") {
|
|
14249
|
+
json(res, previous.responseStatus, previous.response);
|
|
14250
|
+
return;
|
|
14251
|
+
}
|
|
14252
|
+
if (previous.kind === "conflict") {
|
|
14253
|
+
sendError(res, 409, "idempotency_conflict", "clientTurnId was already used with different text");
|
|
14254
|
+
return;
|
|
14255
|
+
}
|
|
14256
|
+
}
|
|
14257
|
+
const runId = randomUUID6();
|
|
14258
|
+
if (!idempotent && !store.beginTurn(conversation.id, runId)) {
|
|
14259
|
+
sendError(res, 409, "conversation_busy", "A turn is already active for this conversation");
|
|
14260
|
+
return;
|
|
14261
|
+
}
|
|
14262
|
+
const sessionPath = resolve8(sessionsDir(), conversation.sessionId + ".json");
|
|
14263
|
+
let sessionFile;
|
|
14264
|
+
try {
|
|
14265
|
+
sessionFile = await loadSession(sessionPath);
|
|
14266
|
+
} catch {
|
|
14267
|
+
if (!idempotent) store.updateConversationStatus(conversation.id, runId, "interrupted", { reason: "session_missing" });
|
|
14268
|
+
sendError(res, 409, "conversation_state_unavailable", "Conversation state is unavailable; no new turn was started");
|
|
14269
|
+
return;
|
|
14270
|
+
}
|
|
14271
|
+
if (sessionFile.cwd !== conversation.cwd) {
|
|
14272
|
+
if (!idempotent) store.updateConversationStatus(conversation.id, runId, "failed", { reason: "workspace_mismatch" });
|
|
14273
|
+
sendError(res, 500, "conversation_state_invalid", "Conversation workspace binding is invalid");
|
|
14274
|
+
return;
|
|
14275
|
+
}
|
|
14276
|
+
const acceptedResponse = {
|
|
14277
|
+
conversationId: conversation.id,
|
|
14278
|
+
runId,
|
|
14279
|
+
status: "running"
|
|
14280
|
+
};
|
|
14281
|
+
if (clientTurnId !== void 0) acceptedResponse.clientTurnId = clientTurnId;
|
|
14282
|
+
if (clientTurnId !== void 0) {
|
|
14283
|
+
const reservation = store.reserveTurn({
|
|
14284
|
+
conversationId: conversation.id,
|
|
14285
|
+
clientTurnId,
|
|
14286
|
+
requestText,
|
|
14287
|
+
acceptedText: text,
|
|
14288
|
+
runId,
|
|
14289
|
+
response: acceptedResponse
|
|
14290
|
+
});
|
|
14291
|
+
if (reservation.kind === "replay") {
|
|
14292
|
+
json(res, reservation.responseStatus, reservation.response);
|
|
14293
|
+
return;
|
|
14294
|
+
}
|
|
14295
|
+
if (reservation.kind === "conflict") {
|
|
14296
|
+
sendError(res, 409, "idempotency_conflict", "clientTurnId was already used with different text");
|
|
14297
|
+
return;
|
|
14298
|
+
}
|
|
14299
|
+
if (reservation.kind === "busy") {
|
|
14300
|
+
sendError(res, 409, "conversation_busy", "A turn is already active for this conversation");
|
|
14301
|
+
return;
|
|
14302
|
+
}
|
|
14303
|
+
if (reservation.kind === "not_found") {
|
|
14304
|
+
sendError(res, 404, "conversation_not_found", "Conversation not found");
|
|
14305
|
+
return;
|
|
14306
|
+
}
|
|
14307
|
+
}
|
|
14308
|
+
const previousLength = sessionFile.messages.length;
|
|
14309
|
+
sessionFile.messages.push({ role: "user", content: text });
|
|
14310
|
+
sessionFile.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
14311
|
+
try {
|
|
14312
|
+
await saveSession(sessionFile);
|
|
14313
|
+
const runStore = new RunStore();
|
|
14314
|
+
let run;
|
|
14315
|
+
try {
|
|
14316
|
+
run = runStore.createRun({
|
|
14317
|
+
id: runId,
|
|
14318
|
+
task: text,
|
|
14319
|
+
cwd: conversation.cwd,
|
|
14320
|
+
sessionId: conversation.sessionId,
|
|
14321
|
+
allowedTools: [...ALLOWED_ASTER_TOOLS]
|
|
14322
|
+
});
|
|
14323
|
+
runStore.transition(runId, "running");
|
|
14324
|
+
} finally {
|
|
14325
|
+
runStore.close();
|
|
14326
|
+
}
|
|
14327
|
+
const tools = createAsterTools(conversation.cwd);
|
|
14328
|
+
const executor = new ToolExecutor(tools);
|
|
14329
|
+
executor.unregister("expand_artifact");
|
|
14330
|
+
const allowedTools = new Set(tools.map((tool) => tool.name));
|
|
14331
|
+
const publishEvent = (type, data) => {
|
|
14332
|
+
store.appendEvent(conversation.id, runId, type, data);
|
|
14333
|
+
};
|
|
14334
|
+
const active = {
|
|
14335
|
+
runId,
|
|
14336
|
+
conversation,
|
|
14337
|
+
sessionFile,
|
|
14338
|
+
messages: sessionFile.messages,
|
|
14339
|
+
executor,
|
|
14340
|
+
allowedTools,
|
|
14341
|
+
maxToolIterations: run.maxToolIterations,
|
|
14342
|
+
maxRuntimeMs: run.maxRuntimeMs,
|
|
14343
|
+
askPermission: (request) => this.askForApproval(store, conversation, runId, request, publishEvent),
|
|
14344
|
+
publishEvent,
|
|
14345
|
+
finish: (status, reason) => {
|
|
14346
|
+
const updated = store.updateConversationStatus(conversation.id, runId, status, reason ? { reason } : {});
|
|
14347
|
+
if (updated) store.appendEvent(conversation.id, runId, status, reason ? { reason } : {});
|
|
14348
|
+
}
|
|
14349
|
+
};
|
|
14350
|
+
this.runtime.startTurn(active);
|
|
14351
|
+
json(res, 202, acceptedResponse);
|
|
14352
|
+
} catch {
|
|
14353
|
+
sessionFile.messages.length = previousLength;
|
|
14354
|
+
await saveSession(sessionFile).catch(() => {
|
|
14355
|
+
});
|
|
14356
|
+
const runs = new RunStore();
|
|
14357
|
+
try {
|
|
14358
|
+
const run = runs.getRun(runId);
|
|
14359
|
+
if (run && ["queued", "running", "waiting", "needs_input"].includes(run.status)) {
|
|
14360
|
+
runs.transition(runId, "failed", "aster_turn_start_failed");
|
|
14361
|
+
runs.cancelTimersForRun(runId);
|
|
14362
|
+
}
|
|
14363
|
+
} finally {
|
|
14364
|
+
runs.close();
|
|
14365
|
+
}
|
|
14366
|
+
store.updateConversationStatus(conversation.id, runId, "failed", { reason: "turn_start_failed" });
|
|
14367
|
+
store.appendEvent(conversation.id, runId, "failed", { code: "turn_start_failed" });
|
|
14368
|
+
if (idempotent) json(res, 202, acceptedResponse);
|
|
14369
|
+
else sendError(res, 500, "turn_start_failed", "Turn could not be started");
|
|
14370
|
+
}
|
|
14371
|
+
}
|
|
14372
|
+
async askForApproval(store, conversation, runId, request, publish) {
|
|
14373
|
+
if (request.tool.name !== "write" && request.tool.name !== "edit") return "deny";
|
|
14374
|
+
const argsJson = JSON.stringify(request.args);
|
|
14375
|
+
if (Buffer.byteLength(argsJson, "utf8") > MAX_APPROVAL_ARG_BYTES || containsLikelyProviderSecret(argsJson)) {
|
|
14376
|
+
publish("tool.activity", { tool: request.tool.name, activity: "rejected", reason: "unsafe_payload" });
|
|
14377
|
+
return "deny";
|
|
14378
|
+
}
|
|
14379
|
+
const workspaceRoot = conversation.cwd;
|
|
14380
|
+
try {
|
|
14381
|
+
await assertAsterWorkspacePath(workspaceRoot, request.args.path, request.tool.name === "write");
|
|
14382
|
+
} catch {
|
|
14383
|
+
publish("tool.activity", { tool: request.tool.name, activity: "rejected", reason: "workspace_path_forbidden" });
|
|
14384
|
+
return "deny";
|
|
14385
|
+
}
|
|
14386
|
+
const file = typeof request.args.path === "string" ? request.args.path.split(/[\\/]/).filter(Boolean).slice(-2).join("/") : "workspace file";
|
|
14387
|
+
const explanation = request.tool.name === "write" ? `Create or replace ${file} in this workspace` : `Edit ${file} in this workspace`;
|
|
14388
|
+
const config2 = await loadAsterServerConfig();
|
|
14389
|
+
const approval = store.createApproval({
|
|
14390
|
+
conversationId: conversation.id,
|
|
14391
|
+
runId,
|
|
14392
|
+
toolName: request.tool.name,
|
|
14393
|
+
explanation,
|
|
14394
|
+
arguments: request.args,
|
|
14395
|
+
expiresAt: Date.now() + config2.approvalTtlMs
|
|
14396
|
+
});
|
|
14397
|
+
const runs = new RunStore();
|
|
14398
|
+
try {
|
|
14399
|
+
const run = runs.getRun(runId);
|
|
14400
|
+
if (run?.status === "running") runs.transition(runId, "needs_input", `approval:${approval.id}`);
|
|
14401
|
+
} finally {
|
|
14402
|
+
runs.close();
|
|
14403
|
+
}
|
|
14404
|
+
store.updateConversationStatus(conversation.id, runId, "waiting_approval", { approvalId: approval.id });
|
|
14405
|
+
publish("approval.required", { approvalId: approval.id, tool: approval.toolName, explanation: approval.explanation, expiresAt: approval.expiresAt });
|
|
14406
|
+
return new Promise((resolveDecision) => {
|
|
14407
|
+
const timer3 = setTimeout(() => {
|
|
14408
|
+
void this.expireApproval(store, approval.id);
|
|
14409
|
+
}, config2.approvalTtlMs);
|
|
14410
|
+
timer3.unref();
|
|
14411
|
+
this.waiters.set(approval.id, { resolve: resolveDecision, timer: timer3 });
|
|
14412
|
+
});
|
|
14413
|
+
}
|
|
14414
|
+
async expireApproval(store, id) {
|
|
14415
|
+
const approval = store.expireApproval(id);
|
|
14416
|
+
if (!approval || approval.status !== "expired") return;
|
|
14417
|
+
await this.resumeAfterApproval(store, approval, "deny", "expired");
|
|
14418
|
+
}
|
|
14419
|
+
async resolveApproval(res, store, id, decision) {
|
|
14420
|
+
const result2 = store.resolveApproval(id, decision);
|
|
14421
|
+
if (!result2.approval) {
|
|
14422
|
+
sendError(res, 404, "approval_not_found", "Approval not found");
|
|
14423
|
+
return;
|
|
14424
|
+
}
|
|
14425
|
+
const approval = result2.approval;
|
|
14426
|
+
if (approval.status === "expired") {
|
|
14427
|
+
if (result2.changed) await this.resumeAfterApproval(store, approval, "deny", "expired");
|
|
14428
|
+
sendError(res, 410, "approval_expired", "Approval has expired; the action was not executed");
|
|
14429
|
+
return;
|
|
14430
|
+
}
|
|
14431
|
+
if (result2.conflict) {
|
|
14432
|
+
sendError(res, 409, "approval_already_resolved", "Approval was already resolved with a different decision");
|
|
14433
|
+
return;
|
|
14434
|
+
}
|
|
14435
|
+
if (approval.status === "approved" || approval.status === "denied") {
|
|
14436
|
+
if (result2.changed) await this.resumeAfterApproval(store, approval, decision, approval.status);
|
|
14437
|
+
json(res, 200, { approvalId: id, status: approval.status, decision: approval.decision });
|
|
14438
|
+
return;
|
|
14439
|
+
}
|
|
14440
|
+
sendError(res, 409, "approval_not_pending", "Approval is not pending");
|
|
14441
|
+
}
|
|
14442
|
+
async resumeAfterApproval(store, approval, decision, status) {
|
|
14443
|
+
const runs = new RunStore();
|
|
14444
|
+
try {
|
|
14445
|
+
const run = runs.getRun(approval.runId);
|
|
14446
|
+
if (run?.status === "needs_input") runs.transition(approval.runId, "running", `approval_${status}:${approval.id}`);
|
|
14447
|
+
} finally {
|
|
14448
|
+
runs.close();
|
|
14449
|
+
}
|
|
14450
|
+
store.updateConversationStatus(approval.conversationId, approval.runId, "running", { approvalId: approval.id, decision: status });
|
|
14451
|
+
store.appendEvent(approval.conversationId, approval.runId, status === "expired" ? "approval.expired" : "approval.resolved", {
|
|
14452
|
+
approvalId: approval.id,
|
|
14453
|
+
decision: status === "expired" ? "deny" : decision
|
|
14454
|
+
});
|
|
14455
|
+
const waiter = this.waiters.get(approval.id);
|
|
14456
|
+
if (waiter) {
|
|
14457
|
+
clearTimeout(waiter.timer);
|
|
14458
|
+
this.waiters.delete(approval.id);
|
|
14459
|
+
waiter.resolve(status === "expired" ? "deny" : decision);
|
|
14460
|
+
}
|
|
14461
|
+
}
|
|
14462
|
+
async cancelConversation(res, store, conversation) {
|
|
14463
|
+
const runId = conversation.activeRunId;
|
|
14464
|
+
if (!runId) {
|
|
14465
|
+
json(res, 200, publicConversation(conversation));
|
|
14466
|
+
return;
|
|
14467
|
+
}
|
|
14468
|
+
const approvals = store.cancelPendingApprovals(conversation.id);
|
|
14469
|
+
for (const approval of approvals) {
|
|
14470
|
+
const waiter = this.waiters.get(approval.id);
|
|
14471
|
+
if (waiter) {
|
|
14472
|
+
clearTimeout(waiter.timer);
|
|
14473
|
+
this.waiters.delete(approval.id);
|
|
14474
|
+
waiter.resolve("deny");
|
|
14475
|
+
}
|
|
14476
|
+
store.appendEvent(conversation.id, runId, "approval.resolved", { approvalId: approval.id, decision: "cancelled" });
|
|
14477
|
+
}
|
|
14478
|
+
const runs = new RunStore();
|
|
14479
|
+
try {
|
|
14480
|
+
const run = runs.getRun(runId);
|
|
14481
|
+
if (run && ["queued", "running", "waiting", "needs_input"].includes(run.status)) {
|
|
14482
|
+
runs.transition(runId, "cancelled", "cancelled_by_aster_client");
|
|
14483
|
+
runs.cancelTimersForRun(runId);
|
|
14484
|
+
}
|
|
14485
|
+
} finally {
|
|
14486
|
+
runs.close();
|
|
14487
|
+
}
|
|
14488
|
+
this.runtime.cancelRun(runId);
|
|
14489
|
+
store.updateConversationStatus(conversation.id, runId, "cancelled", { reason: "client_cancelled" });
|
|
14490
|
+
store.appendEvent(conversation.id, runId, "cancelled", { code: "cancelled_by_client" });
|
|
14491
|
+
const updated = store.getConversation(conversation.id);
|
|
14492
|
+
json(res, 200, updated ? publicConversation(updated) : { conversationId: conversation.id, status: "cancelled" });
|
|
14493
|
+
}
|
|
14494
|
+
streamEvents(req, res, store, conversationId, cursor) {
|
|
14495
|
+
res.writeHead(200, {
|
|
14496
|
+
"Content-Type": "text/event-stream; charset=utf-8",
|
|
14497
|
+
"Cache-Control": "no-cache, no-store",
|
|
14498
|
+
Connection: "keep-alive",
|
|
14499
|
+
"X-Accel-Buffering": "no"
|
|
14500
|
+
});
|
|
14501
|
+
let last = cursor;
|
|
14502
|
+
let closed = false;
|
|
14503
|
+
let polling = false;
|
|
14504
|
+
let lastHeartbeat = Date.now();
|
|
14505
|
+
const pump = async () => {
|
|
14506
|
+
if (closed || polling) return;
|
|
14507
|
+
polling = true;
|
|
14508
|
+
try {
|
|
14509
|
+
const events = store.eventsAfter(conversationId, last, 200);
|
|
14510
|
+
for (const event of events) {
|
|
14511
|
+
if (closed) break;
|
|
14512
|
+
res.write(`id: ${event.sequence}
|
|
14513
|
+
event: ${event.type}
|
|
14514
|
+
data: ${JSON.stringify(event.data)}
|
|
14515
|
+
|
|
14516
|
+
`);
|
|
14517
|
+
last = event.sequence;
|
|
14518
|
+
}
|
|
14519
|
+
if (!events.length && Date.now() - lastHeartbeat >= 15e3) {
|
|
14520
|
+
res.write(": heartbeat\n\n");
|
|
14521
|
+
lastHeartbeat = Date.now();
|
|
14522
|
+
}
|
|
14523
|
+
} catch {
|
|
14524
|
+
closed = true;
|
|
14525
|
+
res.end();
|
|
14526
|
+
} finally {
|
|
14527
|
+
polling = false;
|
|
14528
|
+
if (!closed) setTimeout(() => {
|
|
14529
|
+
void pump();
|
|
14530
|
+
}, 500).unref();
|
|
14531
|
+
}
|
|
14532
|
+
};
|
|
14533
|
+
res.on("close", () => {
|
|
14534
|
+
closed = true;
|
|
14535
|
+
});
|
|
14536
|
+
void pump();
|
|
14537
|
+
}
|
|
14538
|
+
async recover(store) {
|
|
14539
|
+
if (this.recovery) return this.recovery;
|
|
14540
|
+
this.recovery = (async () => {
|
|
14541
|
+
const runs = new RunStore();
|
|
14542
|
+
try {
|
|
14543
|
+
runs.reconcileInterruptedRuns();
|
|
14544
|
+
for (const approval of store.expireAllPendingApprovals()) {
|
|
14545
|
+
const run = runs.getRun(approval.runId);
|
|
14546
|
+
if (run && ["queued", "running", "waiting", "needs_input"].includes(run.status)) {
|
|
14547
|
+
runs.transition(run.id, "cancelled", "server_restarted_during_approval");
|
|
14548
|
+
runs.cancelTimersForRun(run.id);
|
|
14549
|
+
}
|
|
14550
|
+
store.updateConversationStatus(approval.conversationId, approval.runId, "interrupted", { reason: "server_restart_during_approval" });
|
|
14551
|
+
store.appendEvent(approval.conversationId, approval.runId, "approval.expired", { approvalId: approval.id });
|
|
14552
|
+
}
|
|
14553
|
+
for (const conversation of store.listActiveConversations()) {
|
|
14554
|
+
const run = conversation.activeRunId ? runs.getRun(conversation.activeRunId) : void 0;
|
|
14555
|
+
if (!run || ["interrupted_unknown", "cancelled", "failed", "completed"].includes(run.status)) {
|
|
14556
|
+
const status = run?.status === "completed" ? "completed" : run?.status === "cancelled" ? "cancelled" : "interrupted";
|
|
14557
|
+
store.updateConversationStatus(conversation.id, conversation.activeRunId, status, { reason: "server_restart_recovery" });
|
|
14558
|
+
} else if (run.status === "needs_input") {
|
|
14559
|
+
runs.transition(run.id, "cancelled", "approval_expired_on_restart");
|
|
14560
|
+
store.updateConversationStatus(conversation.id, run.id, "interrupted", { reason: "approval_expired_on_restart" });
|
|
14561
|
+
}
|
|
14562
|
+
}
|
|
14563
|
+
} finally {
|
|
14564
|
+
runs.close();
|
|
14565
|
+
}
|
|
14566
|
+
})();
|
|
14567
|
+
return this.recovery;
|
|
14568
|
+
}
|
|
14569
|
+
async requireConversation(store, principal, id) {
|
|
14570
|
+
if (!CONVERSATION_ID_RE.test(id)) return void 0;
|
|
14571
|
+
const conversation = store.getConversation(id);
|
|
14572
|
+
return conversation && principal.workspaceIds.includes(conversation.workspaceId) ? conversation : void 0;
|
|
14573
|
+
}
|
|
14574
|
+
};
|
|
14575
|
+
}
|
|
14576
|
+
});
|
|
14577
|
+
|
|
13308
14578
|
// src/server/routes.ts
|
|
13309
|
-
import { randomUUID as
|
|
14579
|
+
import { randomUUID as randomUUID7 } from "crypto";
|
|
13310
14580
|
import { URL as URL2 } from "url";
|
|
13311
|
-
import { readFile as
|
|
13312
|
-
import { resolve as
|
|
13313
|
-
function
|
|
14581
|
+
import { readFile as readFile18, unlink as unlink3 } from "fs/promises";
|
|
14582
|
+
import { resolve as resolve9, basename as basename5 } from "path";
|
|
14583
|
+
function json2(res, status, data) {
|
|
13314
14584
|
res.writeHead(status, { "Content-Type": "application/json" });
|
|
13315
14585
|
res.end(JSON.stringify(data));
|
|
13316
14586
|
}
|
|
13317
14587
|
function badRequest(res, message2) {
|
|
13318
|
-
|
|
14588
|
+
json2(res, 400, { error: message2 });
|
|
13319
14589
|
}
|
|
13320
14590
|
function notFound(res, message2) {
|
|
13321
|
-
|
|
14591
|
+
json2(res, 404, { error: message2 });
|
|
13322
14592
|
}
|
|
13323
14593
|
async function readBody(req) {
|
|
13324
14594
|
const chunks = [];
|
|
@@ -13336,9 +14606,9 @@ async function resolveFiles2(filePatterns, cwd) {
|
|
|
13336
14606
|
const resolved = /* @__PURE__ */ new Set();
|
|
13337
14607
|
for (const pattern of filePatterns) {
|
|
13338
14608
|
try {
|
|
13339
|
-
const
|
|
13340
|
-
if (
|
|
13341
|
-
resolved.add(
|
|
14609
|
+
const stat10 = await import("fs/promises").then((m) => m.stat(resolve9(cwd, pattern)));
|
|
14610
|
+
if (stat10.isFile()) {
|
|
14611
|
+
resolved.add(resolve9(cwd, pattern));
|
|
13342
14612
|
continue;
|
|
13343
14613
|
}
|
|
13344
14614
|
} catch {
|
|
@@ -13366,7 +14636,7 @@ async function buildUserMessage2(prompt, files, cwd) {
|
|
|
13366
14636
|
}
|
|
13367
14637
|
} else {
|
|
13368
14638
|
try {
|
|
13369
|
-
const content = await
|
|
14639
|
+
const content = await readFile18(filePath, "utf8");
|
|
13370
14640
|
const relPath = filePath.startsWith(cwd) ? filePath.slice(cwd.length + 1) : filePath;
|
|
13371
14641
|
fileContents.push(`
|
|
13372
14642
|
--- ${relPath} ---
|
|
@@ -13391,6 +14661,29 @@ ${content}
|
|
|
13391
14661
|
return text;
|
|
13392
14662
|
}
|
|
13393
14663
|
function setupRoutes(config2) {
|
|
14664
|
+
const asterApi = new AsterApi(config2, {
|
|
14665
|
+
startTurn: (turn) => {
|
|
14666
|
+
const active = {
|
|
14667
|
+
sessionFile: turn.sessionFile,
|
|
14668
|
+
messages: turn.messages,
|
|
14669
|
+
executor: turn.executor,
|
|
14670
|
+
sseClients: /* @__PURE__ */ new Set(),
|
|
14671
|
+
runId: turn.runId,
|
|
14672
|
+
allowedTools: turn.allowedTools,
|
|
14673
|
+
maxToolIterations: turn.maxToolIterations,
|
|
14674
|
+
maxRuntimeMs: turn.maxRuntimeMs,
|
|
14675
|
+
conversationId: turn.conversation.id,
|
|
14676
|
+
workspaceRoot: turn.conversation.worktreePath,
|
|
14677
|
+
askAsterPermission: turn.askPermission,
|
|
14678
|
+
publishAsterEvent: turn.publishEvent,
|
|
14679
|
+
finishAsterTurn: turn.finish
|
|
14680
|
+
};
|
|
14681
|
+
activeSessions.set(turn.sessionFile.id, active);
|
|
14682
|
+
activeRuns.set(turn.runId, active);
|
|
14683
|
+
launchAgentTurnForSession(active, config2, false);
|
|
14684
|
+
},
|
|
14685
|
+
cancelRun: (runId) => activeRuns.get(runId)?.controller?.abort(new Error("cancelled_by_aster_client"))
|
|
14686
|
+
});
|
|
13394
14687
|
const wakeScheduler = new RunWakeScheduler({
|
|
13395
14688
|
onWake: async (event) => {
|
|
13396
14689
|
const store = new RunStore();
|
|
@@ -13405,7 +14698,7 @@ function setupRoutes(config2) {
|
|
|
13405
14698
|
}
|
|
13406
14699
|
let active = activeRuns.get(run.id) ?? activeSessions.get(run.sessionId);
|
|
13407
14700
|
if (!active) {
|
|
13408
|
-
const sessionFile = await loadSession(
|
|
14701
|
+
const sessionFile = await loadSession(resolve9(sessionsDir(), `${run.sessionId}.json`));
|
|
13409
14702
|
const allowedTools = new Set(run.allowedTools);
|
|
13410
14703
|
allowedTools.add("wait_for");
|
|
13411
14704
|
const tools = ALL_TOOLS.filter((tool) => allowedTools.has(tool.name));
|
|
@@ -13435,9 +14728,13 @@ function setupRoutes(config2) {
|
|
|
13435
14728
|
const url = new URL2(req.url ?? "/", `http://${req.headers.host}`);
|
|
13436
14729
|
const method = req.method ?? "GET";
|
|
13437
14730
|
const pathname = url.pathname;
|
|
14731
|
+
if (pathname.startsWith("/api/v1/")) {
|
|
14732
|
+
await asterApi.handle(req, res);
|
|
14733
|
+
return;
|
|
14734
|
+
}
|
|
13438
14735
|
try {
|
|
13439
14736
|
if (pathname === "/" && method === "GET") {
|
|
13440
|
-
|
|
14737
|
+
json2(res, 200, { status: "ok", version: process.env.npm_package_version ?? "dev" });
|
|
13441
14738
|
return;
|
|
13442
14739
|
}
|
|
13443
14740
|
if (pathname === "/doc" && method === "GET") {
|
|
@@ -13448,13 +14745,13 @@ function setupRoutes(config2) {
|
|
|
13448
14745
|
const runPathMatch = pathname.match(/^\/runs\/([^/]+)(?:\/(events|cancel))?$/);
|
|
13449
14746
|
if (pathname === "/runs" && method === "GET") {
|
|
13450
14747
|
if (!process.env.KIMIFLARE_SERVER_PASSWORD) {
|
|
13451
|
-
|
|
14748
|
+
json2(res, 503, { error: "Set KIMIFLARE_SERVER_PASSWORD before accessing unattended runs" });
|
|
13452
14749
|
return;
|
|
13453
14750
|
}
|
|
13454
14751
|
const store = new RunStore();
|
|
13455
14752
|
try {
|
|
13456
14753
|
const limit = Number(url.searchParams.get("limit") ?? 50);
|
|
13457
|
-
|
|
14754
|
+
json2(res, 200, { runs: store.listRuns({ limit }) });
|
|
13458
14755
|
} finally {
|
|
13459
14756
|
store.close();
|
|
13460
14757
|
}
|
|
@@ -13462,7 +14759,7 @@ function setupRoutes(config2) {
|
|
|
13462
14759
|
}
|
|
13463
14760
|
if (pathname === "/runs" && method === "POST") {
|
|
13464
14761
|
if (!process.env.KIMIFLARE_SERVER_PASSWORD) {
|
|
13465
|
-
|
|
14762
|
+
json2(res, 503, { error: "Set KIMIFLARE_SERVER_PASSWORD before creating unattended runs" });
|
|
13466
14763
|
return;
|
|
13467
14764
|
}
|
|
13468
14765
|
const parsedBody = await readBody(req);
|
|
@@ -13474,7 +14771,7 @@ function setupRoutes(config2) {
|
|
|
13474
14771
|
const task = typeof body.task === "string" ? body.task.trim() : "";
|
|
13475
14772
|
let cwd;
|
|
13476
14773
|
try {
|
|
13477
|
-
cwd = typeof body.cwd === "string" ?
|
|
14774
|
+
cwd = typeof body.cwd === "string" ? resolve9(body.cwd) : process.cwd();
|
|
13478
14775
|
} catch {
|
|
13479
14776
|
badRequest(res, "cwd must be a valid path");
|
|
13480
14777
|
return;
|
|
@@ -13547,7 +14844,7 @@ function setupRoutes(config2) {
|
|
|
13547
14844
|
const executor = new ToolExecutor(tools);
|
|
13548
14845
|
const { makeSessionId: makeSessionId2 } = await Promise.resolve().then(() => (init_sessions(), sessions_exports));
|
|
13549
14846
|
const sessionId = makeSessionId2(task);
|
|
13550
|
-
const runId =
|
|
14847
|
+
const runId = randomUUID7();
|
|
13551
14848
|
const worktreeManager = new RunWorktreeManager();
|
|
13552
14849
|
let worktree;
|
|
13553
14850
|
if (useWorktree) {
|
|
@@ -13596,7 +14893,7 @@ function setupRoutes(config2) {
|
|
|
13596
14893
|
store.close();
|
|
13597
14894
|
}
|
|
13598
14895
|
} catch (error) {
|
|
13599
|
-
await
|
|
14896
|
+
await unlink3(resolve9(sessionsDir(), `${sessionId}.json`)).catch(() => {
|
|
13600
14897
|
});
|
|
13601
14898
|
if (worktree) {
|
|
13602
14899
|
await worktreeManager.discard(worktree).catch((cleanupError) => {
|
|
@@ -13618,7 +14915,7 @@ function setupRoutes(config2) {
|
|
|
13618
14915
|
activeSessions.set(sessionId, active);
|
|
13619
14916
|
activeRuns.set(run.id, active);
|
|
13620
14917
|
launchAgentTurnForSession(active, config2, false);
|
|
13621
|
-
|
|
14918
|
+
json2(res, 202, {
|
|
13622
14919
|
runId: run.id,
|
|
13623
14920
|
sessionId,
|
|
13624
14921
|
status: "running",
|
|
@@ -13628,7 +14925,7 @@ function setupRoutes(config2) {
|
|
|
13628
14925
|
}
|
|
13629
14926
|
if (runPathMatch) {
|
|
13630
14927
|
if (!process.env.KIMIFLARE_SERVER_PASSWORD) {
|
|
13631
|
-
|
|
14928
|
+
json2(res, 503, { error: "Set KIMIFLARE_SERVER_PASSWORD before accessing unattended runs" });
|
|
13632
14929
|
return;
|
|
13633
14930
|
}
|
|
13634
14931
|
const runId = runPathMatch[1];
|
|
@@ -13641,7 +14938,7 @@ function setupRoutes(config2) {
|
|
|
13641
14938
|
return;
|
|
13642
14939
|
}
|
|
13643
14940
|
if (suffix === "events" && method === "GET") {
|
|
13644
|
-
|
|
14941
|
+
json2(res, 200, { events: store.listEvents(runId) });
|
|
13645
14942
|
return;
|
|
13646
14943
|
}
|
|
13647
14944
|
if (suffix === "cancel" && method === "POST") {
|
|
@@ -13650,11 +14947,11 @@ function setupRoutes(config2) {
|
|
|
13650
14947
|
store.transition(runId, "cancelled", "cancelled_by_client");
|
|
13651
14948
|
store.cancelTimersForRun(runId);
|
|
13652
14949
|
}
|
|
13653
|
-
|
|
14950
|
+
json2(res, 200, { run: store.getRun(runId) });
|
|
13654
14951
|
return;
|
|
13655
14952
|
}
|
|
13656
14953
|
if (!suffix && method === "GET") {
|
|
13657
|
-
|
|
14954
|
+
json2(res, 200, { run });
|
|
13658
14955
|
return;
|
|
13659
14956
|
}
|
|
13660
14957
|
} finally {
|
|
@@ -13669,7 +14966,7 @@ function setupRoutes(config2) {
|
|
|
13669
14966
|
if (pathname === "/session" && method === "GET") {
|
|
13670
14967
|
const cwd = url.searchParams.get("cwd") ?? void 0;
|
|
13671
14968
|
const sessions = await listSessions(30, cwd);
|
|
13672
|
-
|
|
14969
|
+
json2(res, 200, { sessions });
|
|
13673
14970
|
return;
|
|
13674
14971
|
}
|
|
13675
14972
|
const sessionMatch = pathname.match(/^\/session\/([^/]+)$/);
|
|
@@ -13677,7 +14974,7 @@ function setupRoutes(config2) {
|
|
|
13677
14974
|
const sessionId = sessionMatch[1];
|
|
13678
14975
|
const active = activeSessions.get(sessionId);
|
|
13679
14976
|
if (active) {
|
|
13680
|
-
|
|
14977
|
+
json2(res, 200, {
|
|
13681
14978
|
id: active.sessionFile.id,
|
|
13682
14979
|
cwd: active.sessionFile.cwd,
|
|
13683
14980
|
model: active.sessionFile.model,
|
|
@@ -13688,8 +14985,8 @@ function setupRoutes(config2) {
|
|
|
13688
14985
|
return;
|
|
13689
14986
|
}
|
|
13690
14987
|
try {
|
|
13691
|
-
const file = await loadSession(
|
|
13692
|
-
|
|
14988
|
+
const file = await loadSession(resolve9(sessionsDir(), `${sessionId}.json`));
|
|
14989
|
+
json2(res, 200, {
|
|
13693
14990
|
id: file.id,
|
|
13694
14991
|
cwd: file.cwd,
|
|
13695
14992
|
model: file.model,
|
|
@@ -13707,11 +15004,11 @@ function setupRoutes(config2) {
|
|
|
13707
15004
|
const sessionId = sessionMatch[1];
|
|
13708
15005
|
activeSessions.delete(sessionId);
|
|
13709
15006
|
try {
|
|
13710
|
-
const { unlink:
|
|
13711
|
-
await
|
|
15007
|
+
const { unlink: unlink6 } = await import("fs/promises");
|
|
15008
|
+
await unlink6(resolve9(sessionsDir(), `${sessionId}.json`));
|
|
13712
15009
|
} catch {
|
|
13713
15010
|
}
|
|
13714
|
-
|
|
15011
|
+
json2(res, 200, { deleted: sessionId });
|
|
13715
15012
|
return;
|
|
13716
15013
|
}
|
|
13717
15014
|
if (pathname === "/prompt" && method === "POST") {
|
|
@@ -13755,7 +15052,7 @@ function setupRoutes(config2) {
|
|
|
13755
15052
|
};
|
|
13756
15053
|
activeSessions.set(sessionId, active);
|
|
13757
15054
|
launchAgentTurnForSession(active, config2, allowAll);
|
|
13758
|
-
|
|
15055
|
+
json2(res, 202, { sessionId, status: "started" });
|
|
13759
15056
|
return;
|
|
13760
15057
|
}
|
|
13761
15058
|
if (pathname === "/session/:id/prompt" && method === "POST") {
|
|
@@ -13780,13 +15077,13 @@ function setupRoutes(config2) {
|
|
|
13780
15077
|
const userContent = await buildUserMessage2(prompt, resolvedFiles, active.sessionFile.cwd);
|
|
13781
15078
|
active.messages.push({ role: "user", content: userContent });
|
|
13782
15079
|
launchAgentTurnForSession(active, config2, allowAll);
|
|
13783
|
-
|
|
15080
|
+
json2(res, 202, { sessionId, status: "started" });
|
|
13784
15081
|
return;
|
|
13785
15082
|
}
|
|
13786
15083
|
notFound(res, `unknown endpoint: ${method} ${pathname}`);
|
|
13787
15084
|
} catch (err) {
|
|
13788
15085
|
logger.error("server: request error", { error: err.message, path: pathname });
|
|
13789
|
-
|
|
15086
|
+
json2(res, 500, { error: err.message });
|
|
13790
15087
|
}
|
|
13791
15088
|
}
|
|
13792
15089
|
function cleanup() {
|
|
@@ -13799,13 +15096,17 @@ function setupRoutes(config2) {
|
|
|
13799
15096
|
}
|
|
13800
15097
|
activeRuns.clear();
|
|
13801
15098
|
activeSessions.clear();
|
|
15099
|
+
asterApi.close();
|
|
13802
15100
|
}
|
|
13803
15101
|
return { handleRequest, cleanup };
|
|
13804
15102
|
}
|
|
13805
15103
|
function launchAgentTurnForSession(active, config2, allowAll) {
|
|
13806
15104
|
void runAgentTurnForSession(active, config2, allowAll).catch((error) => {
|
|
13807
15105
|
const message2 = error instanceof Error ? error.message : String(error);
|
|
13808
|
-
logger.error("server: agent turn startup failed", {
|
|
15106
|
+
logger.error("server: agent turn startup failed", {
|
|
15107
|
+
sessionId: active.sessionFile.id,
|
|
15108
|
+
error: active.conversationId ? "aster_agent_start_failed" : redactLikelySecrets(message2)
|
|
15109
|
+
});
|
|
13809
15110
|
active.running = false;
|
|
13810
15111
|
active.controller = void 0;
|
|
13811
15112
|
if (!active.runId) return;
|
|
@@ -13814,7 +15115,7 @@ function launchAgentTurnForSession(active, config2, allowAll) {
|
|
|
13814
15115
|
try {
|
|
13815
15116
|
const run = store.getRun(active.runId);
|
|
13816
15117
|
if (run && ["queued", "running", "waiting"].includes(run.status)) {
|
|
13817
|
-
store.transition(run.id, "failed", `server_turn_start_failed:${message2}`.slice(0, 500));
|
|
15118
|
+
store.transition(run.id, "failed", active.conversationId ? "aster_agent_start_failed" : `server_turn_start_failed:${redactLikelySecrets(message2)}`.slice(0, 500));
|
|
13818
15119
|
store.cancelTimersForRun(run.id);
|
|
13819
15120
|
}
|
|
13820
15121
|
} finally {
|
|
@@ -13823,7 +15124,7 @@ function launchAgentTurnForSession(active, config2, allowAll) {
|
|
|
13823
15124
|
} catch (storeError) {
|
|
13824
15125
|
logger.error("server: failed to record agent startup failure", {
|
|
13825
15126
|
runId: active.runId,
|
|
13826
|
-
error: storeError instanceof Error ? storeError.message :
|
|
15127
|
+
error: active.conversationId ? "aster_run_state_update_failed" : storeError instanceof Error ? redactLikelySecrets(storeError.message) : "unknown"
|
|
13827
15128
|
});
|
|
13828
15129
|
}
|
|
13829
15130
|
});
|
|
@@ -13844,6 +15145,7 @@ async function runAgentTurnForSession(active, config2, allowAll) {
|
|
|
13844
15145
|
if (!run || run.status !== "running") {
|
|
13845
15146
|
runStore.close();
|
|
13846
15147
|
active.running = false;
|
|
15148
|
+
active.finishAsterTurn?.("failed", "run_not_active");
|
|
13847
15149
|
return;
|
|
13848
15150
|
}
|
|
13849
15151
|
const elapsed = run.startedAt ? Date.now() - run.startedAt : 0;
|
|
@@ -13853,11 +15155,15 @@ async function runAgentTurnForSession(active, config2, allowAll) {
|
|
|
13853
15155
|
}
|
|
13854
15156
|
const callbacks = {
|
|
13855
15157
|
onTextDelta: (delta) => {
|
|
15158
|
+
if (active.publishAsterEvent) active.publishAsterEvent("assistant.delta", { delta: redactLikelySecrets(delta) });
|
|
13856
15159
|
for (const client of active.sseClients) {
|
|
13857
15160
|
client.send("assistant.delta", { delta });
|
|
13858
15161
|
}
|
|
13859
15162
|
},
|
|
13860
15163
|
onToolCallFinalized: (call) => {
|
|
15164
|
+
if (active.publishAsterEvent) {
|
|
15165
|
+
active.publishAsterEvent("tool.activity", { tool: call.function.name, activity: "proposed" });
|
|
15166
|
+
}
|
|
13861
15167
|
for (const client of active.sseClients) {
|
|
13862
15168
|
client.send("tool.call", {
|
|
13863
15169
|
id: call.id,
|
|
@@ -13867,6 +15173,9 @@ async function runAgentTurnForSession(active, config2, allowAll) {
|
|
|
13867
15173
|
}
|
|
13868
15174
|
},
|
|
13869
15175
|
onToolResult: (result2) => {
|
|
15176
|
+
if (active.publishAsterEvent) {
|
|
15177
|
+
active.publishAsterEvent("tool.activity", { tool: result2.name, activity: result2.ok ? "completed" : "failed", ok: result2.ok });
|
|
15178
|
+
}
|
|
13870
15179
|
for (const client of active.sseClients) {
|
|
13871
15180
|
client.send("tool.result", {
|
|
13872
15181
|
toolCallId: result2.tool_call_id,
|
|
@@ -13877,6 +15186,14 @@ async function runAgentTurnForSession(active, config2, allowAll) {
|
|
|
13877
15186
|
}
|
|
13878
15187
|
},
|
|
13879
15188
|
onUsage: (usage) => {
|
|
15189
|
+
if (active.publishAsterEvent) {
|
|
15190
|
+
active.publishAsterEvent("usage", {
|
|
15191
|
+
promptTokens: usage.prompt_tokens,
|
|
15192
|
+
completionTokens: usage.completion_tokens,
|
|
15193
|
+
totalTokens: usage.total_tokens,
|
|
15194
|
+
...usage.cost !== void 0 ? { costUsd: usage.cost } : {}
|
|
15195
|
+
});
|
|
15196
|
+
}
|
|
13880
15197
|
for (const client of active.sseClients) {
|
|
13881
15198
|
client.send("usage.update", {
|
|
13882
15199
|
promptTokens: usage.prompt_tokens,
|
|
@@ -13895,6 +15212,7 @@ async function runAgentTurnForSession(active, config2, allowAll) {
|
|
|
13895
15212
|
if (result2.budgetExceededReason) throw new Error(result2.budgetExceededReason);
|
|
13896
15213
|
},
|
|
13897
15214
|
onWarning: (msg) => {
|
|
15215
|
+
if (active.publishAsterEvent) active.publishAsterEvent("status", { detail: redactLikelySecrets(msg) });
|
|
13898
15216
|
for (const client of active.sseClients) {
|
|
13899
15217
|
client.send("warning", { message: msg });
|
|
13900
15218
|
}
|
|
@@ -13902,9 +15220,11 @@ async function runAgentTurnForSession(active, config2, allowAll) {
|
|
|
13902
15220
|
onRunYield: (request) => {
|
|
13903
15221
|
if (!runStore || !active.runId || request.runId !== active.runId) return;
|
|
13904
15222
|
runStore.transition(active.runId, "waiting", `timer:${request.timerId}`);
|
|
15223
|
+
active.publishAsterEvent?.("status", { status: "waiting", condition: request.condition, wakeAt: request.wakeAt });
|
|
13905
15224
|
yielded = true;
|
|
13906
15225
|
},
|
|
13907
|
-
askPermission: async ({ tool, args }) => {
|
|
15226
|
+
askPermission: async ({ tool, args, sessionKey }) => {
|
|
15227
|
+
if (active.askAsterPermission) return active.askAsterPermission({ tool, args, sessionKey });
|
|
13908
15228
|
if (active.runId) {
|
|
13909
15229
|
if (active.allowedTools?.has(tool.name)) return "allow";
|
|
13910
15230
|
return "deny";
|
|
@@ -13984,11 +15304,13 @@ async function runAgentTurnForSession(active, config2, allowAll) {
|
|
|
13984
15304
|
const current = runStore.getRun(active.runId);
|
|
13985
15305
|
if (current?.status === "running" && !yielded) runStore.transition(active.runId, "completed");
|
|
13986
15306
|
}
|
|
15307
|
+
active.finishAsterTurn?.("completed");
|
|
13987
15308
|
for (const client of active.sseClients) {
|
|
13988
15309
|
client.send("session.completed", { sessionId: sessionFile.id });
|
|
13989
15310
|
}
|
|
13990
15311
|
} catch (err) {
|
|
13991
|
-
const
|
|
15312
|
+
const rawMessage = err.message;
|
|
15313
|
+
const message2 = active.conversationId ? "aster_agent_turn_failed" : redactLikelySecrets(rawMessage);
|
|
13992
15314
|
logger.error("server: agent turn failed", { sessionId: sessionFile.id, error: message2 });
|
|
13993
15315
|
if (runStore && active.runId) {
|
|
13994
15316
|
const current = runStore.getRun(active.runId);
|
|
@@ -13997,6 +15319,11 @@ async function runAgentTurnForSession(active, config2, allowAll) {
|
|
|
13997
15319
|
runStore.transition(active.runId, "failed", reason);
|
|
13998
15320
|
}
|
|
13999
15321
|
}
|
|
15322
|
+
if (active.finishAsterTurn) {
|
|
15323
|
+
const runStatus = active.runId ? runStore?.getRun(active.runId)?.status : void 0;
|
|
15324
|
+
const finalStatus = runStatus === "cancelled" ? "cancelled" : runStatus === "completed" ? "completed" : "failed";
|
|
15325
|
+
active.finishAsterTurn(finalStatus, finalStatus === "failed" ? "agent_turn_failed" : void 0);
|
|
15326
|
+
}
|
|
14000
15327
|
for (const client of active.sseClients) {
|
|
14001
15328
|
client.send("error", { message: message2, sessionId: sessionFile.id });
|
|
14002
15329
|
}
|
|
@@ -14026,6 +15353,8 @@ var init_routes = __esm({
|
|
|
14026
15353
|
init_store();
|
|
14027
15354
|
init_worktrees();
|
|
14028
15355
|
init_wake_scheduler();
|
|
15356
|
+
init_aster_api();
|
|
15357
|
+
init_aster_tools();
|
|
14029
15358
|
activeSessions = /* @__PURE__ */ new Map();
|
|
14030
15359
|
activeRuns = /* @__PURE__ */ new Map();
|
|
14031
15360
|
}
|
|
@@ -14040,16 +15369,19 @@ import { createServer } from "http";
|
|
|
14040
15369
|
async function startServer(opts2) {
|
|
14041
15370
|
const { handleRequest, cleanup } = setupRoutes(opts2.config);
|
|
14042
15371
|
const server = createServer((req, res) => {
|
|
14043
|
-
|
|
14044
|
-
|
|
14045
|
-
|
|
14046
|
-
|
|
15372
|
+
const isAsterApi = req.url?.startsWith("/api/v1/") === true;
|
|
15373
|
+
if (!isAsterApi) {
|
|
15374
|
+
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
15375
|
+
res.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
|
|
15376
|
+
res.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization, Last-Event-ID");
|
|
15377
|
+
}
|
|
15378
|
+
if (req.method === "OPTIONS" && !isAsterApi) {
|
|
14047
15379
|
res.writeHead(204);
|
|
14048
15380
|
res.end();
|
|
14049
15381
|
return;
|
|
14050
15382
|
}
|
|
14051
15383
|
const password = process.env.KIMIFLARE_SERVER_PASSWORD;
|
|
14052
|
-
if (password) {
|
|
15384
|
+
if (!isAsterApi && password) {
|
|
14053
15385
|
const auth = req.headers.authorization ?? "";
|
|
14054
15386
|
const expected = "Basic " + Buffer.from(`kimiflare:${password}`).toString("base64");
|
|
14055
15387
|
if (auth !== expected) {
|
|
@@ -14060,6 +15392,9 @@ async function startServer(opts2) {
|
|
|
14060
15392
|
}
|
|
14061
15393
|
void handleRequest(req, res);
|
|
14062
15394
|
});
|
|
15395
|
+
server.headersTimeout = 1e4;
|
|
15396
|
+
server.requestTimeout = 3e4;
|
|
15397
|
+
server.keepAliveTimeout = 5e3;
|
|
14063
15398
|
server.on("close", () => {
|
|
14064
15399
|
cleanup();
|
|
14065
15400
|
});
|
|
@@ -14076,13 +15411,13 @@ async function startServer(opts2) {
|
|
|
14076
15411
|
};
|
|
14077
15412
|
process.on("SIGINT", shutdown);
|
|
14078
15413
|
process.on("SIGTERM", shutdown);
|
|
14079
|
-
return new Promise((
|
|
15414
|
+
return new Promise((resolve15, reject) => {
|
|
14080
15415
|
server.listen(opts2.port, opts2.hostname, () => {
|
|
14081
15416
|
logger.info("server: listening", { hostname: opts2.hostname, port: opts2.port });
|
|
14082
15417
|
console.log(`kimiflare serve: http://${opts2.hostname}:${opts2.port}`);
|
|
14083
15418
|
console.log(` API docs: http://${opts2.hostname}:${opts2.port}/doc`);
|
|
14084
15419
|
console.log(` SSE stream: http://${opts2.hostname}:${opts2.port}/event`);
|
|
14085
|
-
|
|
15420
|
+
resolve15(server);
|
|
14086
15421
|
});
|
|
14087
15422
|
server.on("error", reject);
|
|
14088
15423
|
});
|
|
@@ -14132,9 +15467,9 @@ __export(db_exports, {
|
|
|
14132
15467
|
updateAccessedAt: () => updateAccessedAt,
|
|
14133
15468
|
updateMemoryEmbedding: () => updateMemoryEmbedding
|
|
14134
15469
|
});
|
|
14135
|
-
import
|
|
14136
|
-
import { dirname as
|
|
14137
|
-
import { mkdirSync as
|
|
15470
|
+
import Database4 from "better-sqlite3";
|
|
15471
|
+
import { dirname as dirname13 } from "path";
|
|
15472
|
+
import { mkdirSync as mkdirSync7, statSync as statSync4 } from "fs";
|
|
14138
15473
|
function initSchema(db) {
|
|
14139
15474
|
db.exec(`
|
|
14140
15475
|
CREATE TABLE IF NOT EXISTS memories (
|
|
@@ -14218,8 +15553,8 @@ function openMemoryDb(dbPath) {
|
|
|
14218
15553
|
if (dbInstance) {
|
|
14219
15554
|
dbInstance.close();
|
|
14220
15555
|
}
|
|
14221
|
-
|
|
14222
|
-
dbInstance = new
|
|
15556
|
+
mkdirSync7(dirname13(dbPath), { recursive: true });
|
|
15557
|
+
dbInstance = new Database4(dbPath);
|
|
14223
15558
|
dbInstance.pragma("journal_mode = WAL");
|
|
14224
15559
|
dbInstance.pragma("foreign_keys = ON");
|
|
14225
15560
|
initSchema(dbInstance);
|
|
@@ -15214,7 +16549,7 @@ var init_connection = __esm({
|
|
|
15214
16549
|
if (this.child) {
|
|
15215
16550
|
throw new Error("LSP connection already started");
|
|
15216
16551
|
}
|
|
15217
|
-
return new Promise((
|
|
16552
|
+
return new Promise((resolve15, reject) => {
|
|
15218
16553
|
const abortController = new AbortController();
|
|
15219
16554
|
const spawnTimer = setTimeout(() => {
|
|
15220
16555
|
abortController.abort();
|
|
@@ -15253,7 +16588,7 @@ var init_connection = __esm({
|
|
|
15253
16588
|
if (child.pid) {
|
|
15254
16589
|
clearTimeout(spawnTimer);
|
|
15255
16590
|
this.child = child;
|
|
15256
|
-
|
|
16591
|
+
resolve15();
|
|
15257
16592
|
}
|
|
15258
16593
|
});
|
|
15259
16594
|
} catch (err) {
|
|
@@ -15268,12 +16603,12 @@ var init_connection = __esm({
|
|
|
15268
16603
|
}
|
|
15269
16604
|
const id = this.nextId++;
|
|
15270
16605
|
const msg = { jsonrpc: "2.0", id, method, params };
|
|
15271
|
-
return new Promise((
|
|
16606
|
+
return new Promise((resolve15, reject) => {
|
|
15272
16607
|
const timer3 = setTimeout(() => {
|
|
15273
16608
|
this.pending.delete(id);
|
|
15274
16609
|
reject(toolTimeoutError(`LSP request '${method}'`, this.requestTimeoutMs));
|
|
15275
16610
|
}, this.requestTimeoutMs);
|
|
15276
|
-
const pending = { resolve:
|
|
16611
|
+
const pending = { resolve: resolve15, reject, signal, timer: timer3 };
|
|
15277
16612
|
this.pending.set(id, pending);
|
|
15278
16613
|
if (signal) {
|
|
15279
16614
|
const onAbort = () => {
|
|
@@ -15837,10 +17172,10 @@ var init_manager4 = __esm({
|
|
|
15837
17172
|
});
|
|
15838
17173
|
|
|
15839
17174
|
// src/lsp/adapter.ts
|
|
15840
|
-
import { relative as
|
|
17175
|
+
import { relative as relative8 } from "path";
|
|
15841
17176
|
function formatLocation(loc, cwd) {
|
|
15842
17177
|
const path = fromUri(loc.uri);
|
|
15843
|
-
const rel =
|
|
17178
|
+
const rel = relative8(cwd, path) || path;
|
|
15844
17179
|
return `${rel}:${loc.range.start.line + 1}:${loc.range.start.character + 1}`;
|
|
15845
17180
|
}
|
|
15846
17181
|
function formatLocations(locs, cwd) {
|
|
@@ -15877,7 +17212,7 @@ function formatWorkspaceSymbols(symbols, cwd) {
|
|
|
15877
17212
|
if (!symbols || symbols.length === 0) return "No symbols found.";
|
|
15878
17213
|
return symbols.map((s) => {
|
|
15879
17214
|
const path = fromUri(s.location.uri);
|
|
15880
|
-
const rel =
|
|
17215
|
+
const rel = relative8(cwd, path) || path;
|
|
15881
17216
|
const container = s.containerName ? ` in ${s.containerName}` : "";
|
|
15882
17217
|
return `${s.name} (${symbolKindName(s.kind)})${container} \u2014 ${rel}:${s.location.range.start.line + 1}:${s.location.range.start.character + 1}`;
|
|
15883
17218
|
}).join("\n");
|
|
@@ -15897,7 +17232,7 @@ function formatWorkspaceEdit(edit, cwd) {
|
|
|
15897
17232
|
if (edit.changes) {
|
|
15898
17233
|
for (const [uri, edits] of Object.entries(edit.changes)) {
|
|
15899
17234
|
const path = fromUri(uri);
|
|
15900
|
-
const rel =
|
|
17235
|
+
const rel = relative8(cwd, path) || path;
|
|
15901
17236
|
lines.push(`File: ${rel}`);
|
|
15902
17237
|
for (const e of edits) {
|
|
15903
17238
|
lines.push(` ${e.range.start.line + 1}:${e.range.start.character + 1}-${e.range.end.line + 1}:${e.range.end.character + 1}: ${e.newText}`);
|
|
@@ -15922,7 +17257,7 @@ var init_adapter = __esm({
|
|
|
15922
17257
|
});
|
|
15923
17258
|
|
|
15924
17259
|
// src/tools/lsp.ts
|
|
15925
|
-
import { relative as
|
|
17260
|
+
import { relative as relative9 } from "path";
|
|
15926
17261
|
function makeOutput2(content) {
|
|
15927
17262
|
const bytes = Buffer.byteLength(content, "utf8");
|
|
15928
17263
|
return { content, rawBytes: bytes, reducedBytes: bytes };
|
|
@@ -15930,7 +17265,7 @@ function makeOutput2(content) {
|
|
|
15930
17265
|
function resolveLspPath(args, ctx) {
|
|
15931
17266
|
const raw = typeof args.path === "string" ? args.path : "";
|
|
15932
17267
|
const resolved = resolvePath(ctx.cwd, raw);
|
|
15933
|
-
const rel =
|
|
17268
|
+
const rel = relative9(ctx.cwd, resolved);
|
|
15934
17269
|
if (isPathOutside(rel)) {
|
|
15935
17270
|
throw new Error(`Path outside workspace: ${raw}`);
|
|
15936
17271
|
}
|
|
@@ -16196,12 +17531,12 @@ __export(openrouter_catalog_exports, {
|
|
|
16196
17531
|
loadOpenRouterCatalog: () => loadOpenRouterCatalog,
|
|
16197
17532
|
mapOpenRouterModel: () => mapOpenRouterModel
|
|
16198
17533
|
});
|
|
16199
|
-
import { readFile as
|
|
16200
|
-
import { homedir as
|
|
16201
|
-
import { join as
|
|
17534
|
+
import { readFile as readFile19, writeFile as writeFile11, mkdir as mkdir11 } from "fs/promises";
|
|
17535
|
+
import { homedir as homedir20 } from "os";
|
|
17536
|
+
import { join as join28, dirname as dirname14 } from "path";
|
|
16202
17537
|
function catalogCachePath() {
|
|
16203
|
-
const xdg = process.env.XDG_CONFIG_HOME ||
|
|
16204
|
-
return
|
|
17538
|
+
const xdg = process.env.XDG_CONFIG_HOME || join28(homedir20(), ".config");
|
|
17539
|
+
return join28(xdg, "kimiflare", "openrouter-models.json");
|
|
16205
17540
|
}
|
|
16206
17541
|
function perMtok(perToken) {
|
|
16207
17542
|
const n = Number(perToken);
|
|
@@ -16252,7 +17587,7 @@ async function fetchOpenRouterCatalog(fetchImpl2 = fetch) {
|
|
|
16252
17587
|
}
|
|
16253
17588
|
async function readCache2() {
|
|
16254
17589
|
try {
|
|
16255
|
-
const raw = await
|
|
17590
|
+
const raw = await readFile19(catalogCachePath(), "utf8");
|
|
16256
17591
|
const parsed = JSON.parse(raw);
|
|
16257
17592
|
return parsed.version === CACHE_VERSION ? parsed : null;
|
|
16258
17593
|
} catch {
|
|
@@ -16262,7 +17597,7 @@ async function readCache2() {
|
|
|
16262
17597
|
async function writeCache2(models) {
|
|
16263
17598
|
const path = catalogCachePath();
|
|
16264
17599
|
try {
|
|
16265
|
-
await mkdir11(
|
|
17600
|
+
await mkdir11(dirname14(path), { recursive: true });
|
|
16266
17601
|
const cache = { version: CACHE_VERSION, fetchedAt: (/* @__PURE__ */ new Date()).toISOString(), models };
|
|
16267
17602
|
await writeFile11(path, JSON.stringify(cache, null, 2), "utf8");
|
|
16268
17603
|
} catch {
|
|
@@ -16311,12 +17646,12 @@ __export(requesty_catalog_exports, {
|
|
|
16311
17646
|
loadRequestyCatalog: () => loadRequestyCatalog,
|
|
16312
17647
|
mapRequestyModel: () => mapRequestyModel
|
|
16313
17648
|
});
|
|
16314
|
-
import { readFile as
|
|
16315
|
-
import { homedir as
|
|
16316
|
-
import { join as
|
|
17649
|
+
import { readFile as readFile20, writeFile as writeFile12, mkdir as mkdir12 } from "fs/promises";
|
|
17650
|
+
import { homedir as homedir21 } from "os";
|
|
17651
|
+
import { join as join29, dirname as dirname15 } from "path";
|
|
16317
17652
|
function catalogCachePath2() {
|
|
16318
|
-
const xdg = process.env.XDG_CONFIG_HOME ||
|
|
16319
|
-
return
|
|
17653
|
+
const xdg = process.env.XDG_CONFIG_HOME || join29(homedir21(), ".config");
|
|
17654
|
+
return join29(xdg, "kimiflare", "requesty-models.json");
|
|
16320
17655
|
}
|
|
16321
17656
|
function isSafeModelId(id) {
|
|
16322
17657
|
return typeof id === "string" && id.length > 0 && !CONTROL_CHARS.test(id);
|
|
@@ -16372,7 +17707,7 @@ async function fetchRequestyCatalog(fetchImpl2 = fetch) {
|
|
|
16372
17707
|
}
|
|
16373
17708
|
async function readCache3() {
|
|
16374
17709
|
try {
|
|
16375
|
-
const raw = await
|
|
17710
|
+
const raw = await readFile20(catalogCachePath2(), "utf8");
|
|
16376
17711
|
const parsed = JSON.parse(raw);
|
|
16377
17712
|
if (parsed.version !== CACHE_VERSION2) return null;
|
|
16378
17713
|
return {
|
|
@@ -16387,7 +17722,7 @@ async function readCache3() {
|
|
|
16387
17722
|
async function writeCache3(catalog) {
|
|
16388
17723
|
const path = catalogCachePath2();
|
|
16389
17724
|
try {
|
|
16390
|
-
await mkdir12(
|
|
17725
|
+
await mkdir12(dirname15(path), { recursive: true });
|
|
16391
17726
|
const cache = { version: CACHE_VERSION2, fetchedAt: (/* @__PURE__ */ new Date()).toISOString(), ...catalog };
|
|
16392
17727
|
await writeFile12(path, JSON.stringify(cache, null, 2), "utf8");
|
|
16393
17728
|
} catch {
|
|
@@ -16478,15 +17813,15 @@ var init_permissions = __esm({
|
|
|
16478
17813
|
});
|
|
16479
17814
|
|
|
16480
17815
|
// src/sdk/session.ts
|
|
16481
|
-
import { resolve as
|
|
16482
|
-
import { homedir as
|
|
16483
|
-
import { join as
|
|
17816
|
+
import { resolve as resolve10 } from "path";
|
|
17817
|
+
import { homedir as homedir22 } from "os";
|
|
17818
|
+
import { join as join30 } from "path";
|
|
16484
17819
|
import { existsSync as existsSync4 } from "fs";
|
|
16485
17820
|
async function createAgentSession(opts2) {
|
|
16486
17821
|
const config2 = await resolveSdkConfig(opts2);
|
|
16487
17822
|
if (usesRequesty(llmAuthFromConfig(config2))) await ensureRequestyCatalog();
|
|
16488
17823
|
else if (!resolveCustomEndpoint(config2)) await ensureOpenRouterCatalog();
|
|
16489
|
-
const cwd =
|
|
17824
|
+
const cwd = resolve10(opts2.cwd ?? process.cwd());
|
|
16490
17825
|
const tools = opts2.tools ?? ALL_TOOLS;
|
|
16491
17826
|
let hooks;
|
|
16492
17827
|
if (opts2.enableHooks) {
|
|
@@ -16497,7 +17832,7 @@ async function createAgentSession(opts2) {
|
|
|
16497
17832
|
let memoryManager = null;
|
|
16498
17833
|
const memoryEnabled = opts2.memoryEnabled ?? config2.memoryEnabled ?? false;
|
|
16499
17834
|
if (memoryEnabled) {
|
|
16500
|
-
const dbPath = config2.memoryDbPath ??
|
|
17835
|
+
const dbPath = config2.memoryDbPath ?? join30(homedir22(), ".local", "share", "kimiflare", "memory.db");
|
|
16501
17836
|
memoryManager = new MemoryManager({
|
|
16502
17837
|
dbPath,
|
|
16503
17838
|
...llmAuthFromConfig(config2),
|
|
@@ -16527,7 +17862,7 @@ async function createAgentSession(opts2) {
|
|
|
16527
17862
|
}
|
|
16528
17863
|
let sessionFile;
|
|
16529
17864
|
if (opts2.sessionId) {
|
|
16530
|
-
const filePath =
|
|
17865
|
+
const filePath = join30(sessionsDir(), `${opts2.sessionId}.json`);
|
|
16531
17866
|
try {
|
|
16532
17867
|
sessionFile = await loadSession(filePath);
|
|
16533
17868
|
} catch {
|
|
@@ -16690,8 +18025,8 @@ var init_session = __esm({
|
|
|
16690
18025
|
const parts = [{ type: "text", text }];
|
|
16691
18026
|
for (const img of options.images) {
|
|
16692
18027
|
if ("path" in img) {
|
|
16693
|
-
const { readFile:
|
|
16694
|
-
const data = await
|
|
18028
|
+
const { readFile: readFile30 } = await import("fs/promises");
|
|
18029
|
+
const data = await readFile30(img.path, "base64");
|
|
16695
18030
|
const mimeType = img.path.endsWith(".png") ? "image/png" : img.path.endsWith(".jpg") || img.path.endsWith(".jpeg") ? "image/jpeg" : "image/webp";
|
|
16696
18031
|
parts.push({ type: "image_url", image_url: { url: `data:${mimeType};base64,${data}` } });
|
|
16697
18032
|
} else {
|
|
@@ -16894,12 +18229,12 @@ var init_session = __esm({
|
|
|
16894
18229
|
toolName: req.tool.name,
|
|
16895
18230
|
args: req.args
|
|
16896
18231
|
});
|
|
16897
|
-
const decision = await new Promise((
|
|
16898
|
-
this.permissionResolvers.set(requestId,
|
|
18232
|
+
const decision = await new Promise((resolve15) => {
|
|
18233
|
+
this.permissionResolvers.set(requestId, resolve15);
|
|
16899
18234
|
setTimeout(() => {
|
|
16900
18235
|
if (this.permissionResolvers.has(requestId)) {
|
|
16901
18236
|
this.permissionResolvers.delete(requestId);
|
|
16902
|
-
|
|
18237
|
+
resolve15("deny");
|
|
16903
18238
|
}
|
|
16904
18239
|
}, 3e5);
|
|
16905
18240
|
});
|
|
@@ -16948,14 +18283,14 @@ var init_session = __esm({
|
|
|
16948
18283
|
this.lspManager?.notifyChange(path, content);
|
|
16949
18284
|
} else {
|
|
16950
18285
|
void import("fs/promises").then(
|
|
16951
|
-
({ readFile:
|
|
18286
|
+
({ readFile: readFile30 }) => readFile30(path, "utf8").then((c) => this.lspManager?.notifyChange(path, c)).catch(() => {
|
|
16952
18287
|
})
|
|
16953
18288
|
).catch(() => {
|
|
16954
18289
|
});
|
|
16955
18290
|
}
|
|
16956
18291
|
}
|
|
16957
18292
|
});
|
|
16958
|
-
if (["AGENTS.md", "KIMI.md", "KIMIFLARE.md", "AGENT.md"].some((name) => existsSync4(
|
|
18293
|
+
if (["AGENTS.md", "KIMI.md", "KIMIFLARE.md", "AGENT.md"].some((name) => existsSync4(join30(this.cwd, name)))) {
|
|
16959
18294
|
this.messages[0] = {
|
|
16960
18295
|
role: "system",
|
|
16961
18296
|
content: buildSystemPrompt({
|
|
@@ -17202,20 +18537,20 @@ var init_repo_info = __esm({
|
|
|
17202
18537
|
});
|
|
17203
18538
|
|
|
17204
18539
|
// src/agent/supervisor.ts
|
|
17205
|
-
import { readdir as readdir5, readFile as
|
|
17206
|
-
import { createHash as
|
|
17207
|
-
import { resolve as
|
|
18540
|
+
import { readdir as readdir5, readFile as readFile21, stat as stat7 } from "fs/promises";
|
|
18541
|
+
import { createHash as createHash4 } from "crypto";
|
|
18542
|
+
import { resolve as resolve11, join as join31 } from "path";
|
|
17208
18543
|
async function preReadFilesForWorkers(files, repoRoot, maxChars = DEFAULT_PRE_READ_MAX_CHARS) {
|
|
17209
18544
|
const results = [];
|
|
17210
18545
|
const filesRead = [];
|
|
17211
18546
|
let chars = 0;
|
|
17212
18547
|
for (const file of files) {
|
|
17213
18548
|
if (chars >= maxChars) break;
|
|
17214
|
-
const path =
|
|
18549
|
+
const path = resolve11(repoRoot, file);
|
|
17215
18550
|
try {
|
|
17216
|
-
const s = await
|
|
18551
|
+
const s = await stat7(path);
|
|
17217
18552
|
if (!s.isFile()) continue;
|
|
17218
|
-
const raw = await
|
|
18553
|
+
const raw = await readFile21(path, "utf8");
|
|
17219
18554
|
const remaining = maxChars - chars;
|
|
17220
18555
|
const content = raw.length > remaining ? raw.slice(0, remaining) + "\n\u2026 (truncated)" : raw;
|
|
17221
18556
|
results.push(`--- ${file} ---
|
|
@@ -17239,7 +18574,7 @@ ${results.join("\n\n")}`,
|
|
|
17239
18574
|
}
|
|
17240
18575
|
function getPreReadFilesFromMemory(cfg, repoRoot, limit = 10) {
|
|
17241
18576
|
if (!cfg.memoryEnabled) return [];
|
|
17242
|
-
const dbPath = cfg.memoryDbPath ??
|
|
18577
|
+
const dbPath = cfg.memoryDbPath ?? join31(repoRoot, ".kimiflare", "memory.db");
|
|
17243
18578
|
try {
|
|
17244
18579
|
const db = openMemoryDb(dbPath);
|
|
17245
18580
|
const files = getTopRelatedFiles(db, repoRoot, limit);
|
|
@@ -17249,7 +18584,7 @@ function getPreReadFilesFromMemory(cfg, repoRoot, limit = 10) {
|
|
|
17249
18584
|
}
|
|
17250
18585
|
}
|
|
17251
18586
|
function cacheKey(prompt, context, strategy) {
|
|
17252
|
-
return
|
|
18587
|
+
return createHash4("sha256").update(`${prompt}\0${context}\0${strategy}`).digest("hex");
|
|
17253
18588
|
}
|
|
17254
18589
|
function getCached(key) {
|
|
17255
18590
|
return decompositionCache.get(key);
|
|
@@ -18190,8 +19525,8 @@ async function runEmitMode(opts2) {
|
|
|
18190
19525
|
async function nextFollowUp() {
|
|
18191
19526
|
if (followUpQueue.length > 0) return followUpQueue.shift();
|
|
18192
19527
|
if (stdinClosed) return null;
|
|
18193
|
-
return new Promise((
|
|
18194
|
-
followUpResolver =
|
|
19528
|
+
return new Promise((resolve15) => {
|
|
19529
|
+
followUpResolver = resolve15;
|
|
18195
19530
|
});
|
|
18196
19531
|
}
|
|
18197
19532
|
const cwd = process.cwd();
|
|
@@ -18381,8 +19716,8 @@ ${conflicts.join("\n")}`,
|
|
|
18381
19716
|
return "allow";
|
|
18382
19717
|
}
|
|
18383
19718
|
if (opts2.multiTurn) {
|
|
18384
|
-
const choice = await new Promise((
|
|
18385
|
-
pendingPermissions.set(reqId,
|
|
19719
|
+
const choice = await new Promise((resolve15) => {
|
|
19720
|
+
pendingPermissions.set(reqId, resolve15);
|
|
18386
19721
|
});
|
|
18387
19722
|
if (choice === "allow" || choice === "allow_session") {
|
|
18388
19723
|
emit("PermissionGranted", { request_id: reqId });
|
|
@@ -18520,7 +19855,7 @@ async function runAttachMode(opts2) {
|
|
|
18520
19855
|
async function streamSse(url, sessionId, format) {
|
|
18521
19856
|
const eventSource = new EventSource(`${url}/event`);
|
|
18522
19857
|
let done = false;
|
|
18523
|
-
return new Promise((
|
|
19858
|
+
return new Promise((resolve15, reject) => {
|
|
18524
19859
|
eventSource.onmessage = (event) => {
|
|
18525
19860
|
try {
|
|
18526
19861
|
const data = JSON.parse(event.data);
|
|
@@ -18567,7 +19902,7 @@ async function streamSse(url, sessionId, format) {
|
|
|
18567
19902
|
done = true;
|
|
18568
19903
|
eventSource.close();
|
|
18569
19904
|
if (format === "text") process.stdout.write("\n");
|
|
18570
|
-
|
|
19905
|
+
resolve15(void 0);
|
|
18571
19906
|
break;
|
|
18572
19907
|
case "error":
|
|
18573
19908
|
done = true;
|
|
@@ -18580,7 +19915,7 @@ async function streamSse(url, sessionId, format) {
|
|
|
18580
19915
|
process.stdout.write(JSON.stringify({ event: "error", message: data.message }) + "\n");
|
|
18581
19916
|
}
|
|
18582
19917
|
process.exitCode = 1;
|
|
18583
|
-
|
|
19918
|
+
resolve15(void 0);
|
|
18584
19919
|
break;
|
|
18585
19920
|
}
|
|
18586
19921
|
} catch {
|
|
@@ -18606,7 +19941,7 @@ async function streamSseJson(url, sessionId) {
|
|
|
18606
19941
|
const toolResults = [];
|
|
18607
19942
|
let usage;
|
|
18608
19943
|
const eventSource = new EventSource(`${url}/event`);
|
|
18609
|
-
return new Promise((
|
|
19944
|
+
return new Promise((resolve15, reject) => {
|
|
18610
19945
|
eventSource.onmessage = (event) => {
|
|
18611
19946
|
try {
|
|
18612
19947
|
const data = JSON.parse(event.data);
|
|
@@ -18643,7 +19978,7 @@ async function streamSseJson(url, sessionId) {
|
|
|
18643
19978
|
2
|
|
18644
19979
|
) + "\n"
|
|
18645
19980
|
);
|
|
18646
|
-
|
|
19981
|
+
resolve15(void 0);
|
|
18647
19982
|
break;
|
|
18648
19983
|
case "error":
|
|
18649
19984
|
eventSource.close();
|
|
@@ -18651,7 +19986,7 @@ async function streamSseJson(url, sessionId) {
|
|
|
18651
19986
|
JSON.stringify({ error: data.message, sessionId }, null, 2) + "\n"
|
|
18652
19987
|
);
|
|
18653
19988
|
process.exitCode = 1;
|
|
18654
|
-
|
|
19989
|
+
resolve15(void 0);
|
|
18655
19990
|
break;
|
|
18656
19991
|
}
|
|
18657
19992
|
} catch {
|
|
@@ -19338,7 +20673,7 @@ __export(app_helpers_exports, {
|
|
|
19338
20673
|
});
|
|
19339
20674
|
import { execSync as execSync3, spawn as spawn6 } from "child_process";
|
|
19340
20675
|
import { existsSync as existsSync5, readFileSync as readFileSync5, statSync as statSync5 } from "fs";
|
|
19341
|
-
import { join as
|
|
20676
|
+
import { join as join32 } from "path";
|
|
19342
20677
|
import { platform as platform3 } from "os";
|
|
19343
20678
|
function buildFilePickerIgnoreList(cwd) {
|
|
19344
20679
|
const hardcoded = [
|
|
@@ -19410,7 +20745,7 @@ function buildFilePickerIgnoreList(cwd) {
|
|
|
19410
20745
|
];
|
|
19411
20746
|
const gitignorePatterns = [];
|
|
19412
20747
|
try {
|
|
19413
|
-
const gitignorePath =
|
|
20748
|
+
const gitignorePath = join32(cwd, ".gitignore");
|
|
19414
20749
|
const stats = statSync5(gitignorePath);
|
|
19415
20750
|
if (stats.size > MAX_GITIGNORE_SIZE) {
|
|
19416
20751
|
return hardcoded;
|
|
@@ -19587,10 +20922,10 @@ var init_app_helpers = __esm({
|
|
|
19587
20922
|
|
|
19588
20923
|
// src/remote/deploy-commute.ts
|
|
19589
20924
|
import { spawn as spawn7 } from "child_process";
|
|
19590
|
-
import { mkdtemp, readFile as
|
|
20925
|
+
import { mkdtemp, readFile as readFile22, writeFile as writeFile13, rm as rm2 } from "fs/promises";
|
|
19591
20926
|
import { tmpdir as tmpdir3 } from "os";
|
|
19592
|
-
import { join as
|
|
19593
|
-
import { randomBytes as
|
|
20927
|
+
import { join as join33 } from "path";
|
|
20928
|
+
import { randomBytes as randomBytes3 } from "crypto";
|
|
19594
20929
|
async function cfApiFetch(accountId, apiToken, path, init) {
|
|
19595
20930
|
const url = `${CF_API}/accounts/${encodeURIComponent(accountId)}${path}`;
|
|
19596
20931
|
const res = await fetch(url, {
|
|
@@ -19602,64 +20937,64 @@ async function cfApiFetch(accountId, apiToken, path, init) {
|
|
|
19602
20937
|
...init?.headers
|
|
19603
20938
|
}
|
|
19604
20939
|
});
|
|
19605
|
-
const
|
|
19606
|
-
return
|
|
20940
|
+
const json3 = await res.json();
|
|
20941
|
+
return json3;
|
|
19607
20942
|
}
|
|
19608
20943
|
async function listDurableObjectNamespaces(accountId, apiToken) {
|
|
19609
|
-
const
|
|
20944
|
+
const json3 = await cfApiFetch(
|
|
19610
20945
|
accountId,
|
|
19611
20946
|
apiToken,
|
|
19612
20947
|
"/workers/durable_objects/namespaces"
|
|
19613
20948
|
);
|
|
19614
|
-
if (!
|
|
20949
|
+
if (!json3.success || !json3.result) {
|
|
19615
20950
|
throw new Error(
|
|
19616
|
-
|
|
20951
|
+
json3.errors?.map((e) => e.message).join(", ") ?? "Failed to list DO namespaces"
|
|
19617
20952
|
);
|
|
19618
20953
|
}
|
|
19619
|
-
return
|
|
20954
|
+
return json3.result;
|
|
19620
20955
|
}
|
|
19621
20956
|
async function deleteDurableObjectNamespace(accountId, apiToken, namespaceId) {
|
|
19622
|
-
const
|
|
20957
|
+
const json3 = await cfApiFetch(
|
|
19623
20958
|
accountId,
|
|
19624
20959
|
apiToken,
|
|
19625
20960
|
`/workers/durable_objects/namespaces/${encodeURIComponent(namespaceId)}`,
|
|
19626
20961
|
{ method: "DELETE" }
|
|
19627
20962
|
);
|
|
19628
|
-
if (!
|
|
20963
|
+
if (!json3.success) {
|
|
19629
20964
|
throw new Error(
|
|
19630
|
-
|
|
20965
|
+
json3.errors?.map((e) => e.message).join(", ") ?? "Failed to delete DO namespace"
|
|
19631
20966
|
);
|
|
19632
20967
|
}
|
|
19633
20968
|
}
|
|
19634
20969
|
async function listContainerApplications(accountId, apiToken) {
|
|
19635
|
-
const
|
|
20970
|
+
const json3 = await cfApiFetch(
|
|
19636
20971
|
accountId,
|
|
19637
20972
|
apiToken,
|
|
19638
20973
|
"/containers/applications"
|
|
19639
20974
|
);
|
|
19640
|
-
if (!
|
|
20975
|
+
if (!json3.success || !json3.result) {
|
|
19641
20976
|
return [];
|
|
19642
20977
|
}
|
|
19643
|
-
return
|
|
20978
|
+
return json3.result;
|
|
19644
20979
|
}
|
|
19645
20980
|
async function deleteContainerApplication(accountId, apiToken, appId) {
|
|
19646
|
-
const
|
|
20981
|
+
const json3 = await cfApiFetch(
|
|
19647
20982
|
accountId,
|
|
19648
20983
|
apiToken,
|
|
19649
20984
|
`/containers/applications/${encodeURIComponent(appId)}`,
|
|
19650
20985
|
{ method: "DELETE" }
|
|
19651
20986
|
);
|
|
19652
|
-
if (!
|
|
20987
|
+
if (!json3.success) {
|
|
19653
20988
|
throw new Error(
|
|
19654
|
-
|
|
20989
|
+
json3.errors?.map((e) => e.message).join(", ") ?? "Failed to delete container application"
|
|
19655
20990
|
);
|
|
19656
20991
|
}
|
|
19657
20992
|
}
|
|
19658
20993
|
function generateSecret2() {
|
|
19659
|
-
return
|
|
20994
|
+
return randomBytes3(32).toString("hex");
|
|
19660
20995
|
}
|
|
19661
20996
|
function runCmd(cmd, args, opts2 = {}) {
|
|
19662
|
-
return new Promise((
|
|
20997
|
+
return new Promise((resolve15) => {
|
|
19663
20998
|
const child = spawn7(cmd, args, {
|
|
19664
20999
|
cwd: opts2.cwd,
|
|
19665
21000
|
env: { ...process.env, ...opts2.env ?? {} },
|
|
@@ -19680,11 +21015,11 @@ function runCmd(cmd, args, opts2 = {}) {
|
|
|
19680
21015
|
const timer3 = opts2.timeoutMs ? setTimeout(() => child.kill("SIGKILL"), opts2.timeoutMs) : null;
|
|
19681
21016
|
child.on("close", (code) => {
|
|
19682
21017
|
if (timer3) clearTimeout(timer3);
|
|
19683
|
-
|
|
21018
|
+
resolve15({ stdout, stderr, code: code ?? -1 });
|
|
19684
21019
|
});
|
|
19685
21020
|
child.on("error", (err) => {
|
|
19686
21021
|
if (timer3) clearTimeout(timer3);
|
|
19687
|
-
|
|
21022
|
+
resolve15({ stdout, stderr: stderr + String(err), code: -1 });
|
|
19688
21023
|
});
|
|
19689
21024
|
});
|
|
19690
21025
|
}
|
|
@@ -19820,8 +21155,8 @@ ${wranglerInstall.stderr.slice(-600)}`,
|
|
|
19820
21155
|
ok: true
|
|
19821
21156
|
};
|
|
19822
21157
|
yield { message: "Prerequisites ready", ok: true };
|
|
19823
|
-
const tmpRoot = await mkdtemp(
|
|
19824
|
-
const repoDir =
|
|
21158
|
+
const tmpRoot = await mkdtemp(join33(tmpdir3(), "kimiflare-commute-"));
|
|
21159
|
+
const repoDir = join33(tmpRoot, "kimiflare-commute");
|
|
19825
21160
|
yield { message: `Fetching worker source from GitHub (${COMMUTE_REPO})\u2026` };
|
|
19826
21161
|
const clone = await runCmd("git", ["clone", "--depth", "1", "--branch", COMMUTE_BRANCH, COMMUTE_REPO, repoDir], { timeoutMs: 6e4 });
|
|
19827
21162
|
if (clone.code !== 0) {
|
|
@@ -19830,8 +21165,8 @@ ${(clone.stderr || clone.stdout).slice(0, 400)}`, error: true };
|
|
|
19830
21165
|
throw new Error("clone failed");
|
|
19831
21166
|
}
|
|
19832
21167
|
yield { message: "Source fetched from GitHub", ok: true };
|
|
19833
|
-
const workerDir =
|
|
19834
|
-
const wranglerToml =
|
|
21168
|
+
const workerDir = join33(repoDir, "remote", "worker");
|
|
21169
|
+
const wranglerToml = join33(workerDir, "wrangler.toml");
|
|
19835
21170
|
yield { message: "Installing Worker dependencies (npm install)\u2026" };
|
|
19836
21171
|
const install = await runCmd("npm", ["install", "--no-audit", "--no-fund", "--loglevel=error"], {
|
|
19837
21172
|
cwd: workerDir,
|
|
@@ -19920,7 +21255,7 @@ ${(install.stderr || install.stdout).slice(-1200).trim()}`,
|
|
|
19920
21255
|
ok: true
|
|
19921
21256
|
};
|
|
19922
21257
|
yield { message: "Patching wrangler.toml\u2026" };
|
|
19923
|
-
let toml = await
|
|
21258
|
+
let toml = await readFile22(wranglerToml, "utf8");
|
|
19924
21259
|
toml = toml.replace(/^name\s*=\s*"[^"]+"/m, `name = "${workerName}"`);
|
|
19925
21260
|
toml = toml.replace(
|
|
19926
21261
|
/(\[\[kv_namespaces\]\][\s\S]*?binding\s*=\s*"OAUTH_KV"[\s\S]*?id\s*=\s*")[^"]+(")/,
|
|
@@ -22266,8 +23601,8 @@ __export(camouflage_view_exports, {
|
|
|
22266
23601
|
});
|
|
22267
23602
|
import { execFileSync as execFileSync3, spawn as spawn9 } from "child_process";
|
|
22268
23603
|
import { readdir as readdir6 } from "fs/promises";
|
|
22269
|
-
import { homedir as
|
|
22270
|
-
import { join as
|
|
23604
|
+
import { homedir as homedir23 } from "os";
|
|
23605
|
+
import { join as join34, resolve as resolve12 } from "path";
|
|
22271
23606
|
async function loadSdk() {
|
|
22272
23607
|
const sdk = await import("camouflage-tui").catch((err) => {
|
|
22273
23608
|
throw new CamouflageUnavailable(`camouflage-tui is not installed (${err.message})`);
|
|
@@ -22345,14 +23680,14 @@ async function runCamouflageView(opts2) {
|
|
|
22345
23680
|
}
|
|
22346
23681
|
}
|
|
22347
23682
|
function runShell(command) {
|
|
22348
|
-
return new Promise((
|
|
23683
|
+
return new Promise((resolve15) => {
|
|
22349
23684
|
const { shell, args } = getShellCommand();
|
|
22350
23685
|
const child = spawn9(shell, [...args, command], { env: process.env });
|
|
22351
23686
|
let out = "";
|
|
22352
23687
|
child.stdout?.on("data", (c) => out += c.toString("utf8"));
|
|
22353
23688
|
child.stderr?.on("data", (c) => out += c.toString("utf8"));
|
|
22354
|
-
child.on("close", (code) =>
|
|
22355
|
-
child.on("error", (err) =>
|
|
23689
|
+
child.on("close", (code) => resolve15({ ok: code === 0, output: out || (code === 0 ? "" : `Exit code: ${code}`) }));
|
|
23690
|
+
child.on("error", (err) => resolve15({ ok: false, output: err.message }));
|
|
22356
23691
|
});
|
|
22357
23692
|
}
|
|
22358
23693
|
function validateCommandName(name, existing) {
|
|
@@ -22403,8 +23738,8 @@ function truncateText(s, max) {
|
|
|
22403
23738
|
async function listMentionDir(cwd, query) {
|
|
22404
23739
|
const exact = query === "~" || query === "." || query === ".." || query.endsWith("/..");
|
|
22405
23740
|
const dir = exact ? `${query}/` : query.slice(0, query.lastIndexOf("/") + 1);
|
|
22406
|
-
const expanded = dir.startsWith("~") ?
|
|
22407
|
-
const target =
|
|
23741
|
+
const expanded = dir.startsWith("~") ? join34(homedir23(), dir.slice(1)) : dir;
|
|
23742
|
+
const target = resolve12(cwd, expanded || ".");
|
|
22408
23743
|
try {
|
|
22409
23744
|
const items = await readdir6(target, { withFileTypes: true });
|
|
22410
23745
|
const entries = items.filter((d) => !d.name.startsWith(".")).map((d) => ({ name: d.name, isDir: d.isDirectory() })).sort((a, b) => a.isDir !== b.isDir ? a.isDir ? -1 : 1 : a.name.localeCompare(b.name)).slice(0, 300).map((d) => `${dir}${d.name}${d.isDir ? "/" : ""}`);
|
|
@@ -22485,7 +23820,7 @@ function shortModel(id) {
|
|
|
22485
23820
|
return id.includes("/") ? id.slice(id.indexOf("/") + 1) : id;
|
|
22486
23821
|
}
|
|
22487
23822
|
function shortPath(p) {
|
|
22488
|
-
const home =
|
|
23823
|
+
const home = homedir23();
|
|
22489
23824
|
return p === home ? "~" : p.startsWith(home + "/") ? `~${p.slice(home.length)}` : p;
|
|
22490
23825
|
}
|
|
22491
23826
|
function gitBranch(cwd) {
|
|
@@ -25305,19 +26640,19 @@ function usePermissionController(getMode, onPlanModeBlocked) {
|
|
|
25305
26640
|
const onPlanModeBlockedRef = useRef3(onPlanModeBlocked);
|
|
25306
26641
|
onPlanModeBlockedRef.current = onPlanModeBlocked;
|
|
25307
26642
|
const askPermission = useCallback2(
|
|
25308
|
-
(req, askOpts) => new Promise((
|
|
26643
|
+
(req, askOpts) => new Promise((resolve15) => {
|
|
25309
26644
|
const outcome = decidePermission(req, getModeRef.current(), askOpts);
|
|
25310
26645
|
if (outcome.kind === "resolve") {
|
|
25311
|
-
|
|
26646
|
+
resolve15(outcome.decision);
|
|
25312
26647
|
return;
|
|
25313
26648
|
}
|
|
25314
26649
|
if (outcome.kind === "plan_blocked") {
|
|
25315
26650
|
onPlanModeBlockedRef.current(outcome.toolName);
|
|
25316
|
-
|
|
26651
|
+
resolve15(AUTO_DENY);
|
|
25317
26652
|
return;
|
|
25318
26653
|
}
|
|
25319
|
-
resolveRef.current =
|
|
25320
|
-
setPending({ tool: req.tool, args: req.args, resolve:
|
|
26654
|
+
resolveRef.current = resolve15;
|
|
26655
|
+
setPending({ tool: req.tool, args: req.args, resolve: resolve15 });
|
|
25321
26656
|
}),
|
|
25322
26657
|
[]
|
|
25323
26658
|
);
|
|
@@ -25956,15 +27291,15 @@ __export(openrouter_oauth_exports, {
|
|
|
25956
27291
|
pkceChallenge: () => pkceChallenge,
|
|
25957
27292
|
startLoopbackListener: () => startLoopbackListener
|
|
25958
27293
|
});
|
|
25959
|
-
import { createHash as
|
|
27294
|
+
import { createHash as createHash5, randomBytes as randomBytes4 } from "crypto";
|
|
25960
27295
|
import { createServer as createServer2 } from "http";
|
|
25961
27296
|
import { hostname } from "os";
|
|
25962
27297
|
function createPkce() {
|
|
25963
|
-
const verifier =
|
|
27298
|
+
const verifier = randomBytes4(32).toString("base64url");
|
|
25964
27299
|
return { verifier, challenge: pkceChallenge(verifier) };
|
|
25965
27300
|
}
|
|
25966
27301
|
function pkceChallenge(verifier) {
|
|
25967
|
-
return
|
|
27302
|
+
return createHash5("sha256").update(verifier).digest("base64url");
|
|
25968
27303
|
}
|
|
25969
27304
|
function openRouterAuthPageUrl() {
|
|
25970
27305
|
return (process.env.OPENROUTER_AUTH_URL?.trim() || "https://openrouter.ai/auth").replace(/\/+$/, "");
|
|
@@ -26047,9 +27382,9 @@ async function startLoopbackListener(opts2 = {}) {
|
|
|
26047
27382
|
rejectCode(e);
|
|
26048
27383
|
finish();
|
|
26049
27384
|
}
|
|
26050
|
-
await new Promise((
|
|
27385
|
+
await new Promise((resolve15, reject) => {
|
|
26051
27386
|
server.once("error", reject);
|
|
26052
|
-
server.listen(0, "127.0.0.1", () =>
|
|
27387
|
+
server.listen(0, "127.0.0.1", () => resolve15());
|
|
26053
27388
|
});
|
|
26054
27389
|
const address = server.address();
|
|
26055
27390
|
const port = typeof address === "object" && address ? address.port : 0;
|
|
@@ -26475,15 +27810,15 @@ var init_frontmatter = __esm({
|
|
|
26475
27810
|
});
|
|
26476
27811
|
|
|
26477
27812
|
// src/commands/loader.ts
|
|
26478
|
-
import { open, realpath as
|
|
26479
|
-
import { homedir as
|
|
26480
|
-
import { join as
|
|
27813
|
+
import { open, realpath as realpath6 } from "fs/promises";
|
|
27814
|
+
import { homedir as homedir24 } from "os";
|
|
27815
|
+
import { join as join35, relative as relative10, sep as sep6 } from "path";
|
|
26481
27816
|
function projectCommandsDir(cwd = process.cwd()) {
|
|
26482
|
-
return
|
|
27817
|
+
return join35(cwd, ".kimiflare", "commands");
|
|
26483
27818
|
}
|
|
26484
27819
|
function globalCommandsDir() {
|
|
26485
|
-
const xdg = process.env.XDG_CONFIG_HOME ||
|
|
26486
|
-
return
|
|
27820
|
+
const xdg = process.env.XDG_CONFIG_HOME || join35(homedir24(), ".config");
|
|
27821
|
+
return join35(xdg, "kimiflare", "commands");
|
|
26487
27822
|
}
|
|
26488
27823
|
async function loadCustomCommands(cwd = process.cwd()) {
|
|
26489
27824
|
const warnings = [];
|
|
@@ -26522,7 +27857,7 @@ async function loadCustomCommands(cwd = process.cwd()) {
|
|
|
26522
27857
|
async function resolveSafeDir(dir, source, cwd, warnings) {
|
|
26523
27858
|
let realDir;
|
|
26524
27859
|
try {
|
|
26525
|
-
realDir = await
|
|
27860
|
+
realDir = await realpath6(dir);
|
|
26526
27861
|
} catch (err) {
|
|
26527
27862
|
const code = err.code;
|
|
26528
27863
|
if (code !== "ENOENT" && code !== "ENOTDIR") {
|
|
@@ -26533,11 +27868,11 @@ async function resolveSafeDir(dir, source, cwd, warnings) {
|
|
|
26533
27868
|
if (source === "project") {
|
|
26534
27869
|
let realCwd;
|
|
26535
27870
|
try {
|
|
26536
|
-
realCwd = await
|
|
27871
|
+
realCwd = await realpath6(cwd);
|
|
26537
27872
|
} catch {
|
|
26538
27873
|
return null;
|
|
26539
27874
|
}
|
|
26540
|
-
const rel =
|
|
27875
|
+
const rel = relative10(realCwd, realDir);
|
|
26541
27876
|
if (rel !== "" && isPathOutside(rel)) {
|
|
26542
27877
|
warnings.push(`commands dir ${dir} escapes workspace via symlink \u2014 skipped`);
|
|
26543
27878
|
return null;
|
|
@@ -26612,10 +27947,10 @@ async function loadOne(file, rootDir, source, warnings) {
|
|
|
26612
27947
|
return cmd;
|
|
26613
27948
|
}
|
|
26614
27949
|
function filenameToCommandName(file, rootDir) {
|
|
26615
|
-
const rel =
|
|
27950
|
+
const rel = relative10(rootDir, file);
|
|
26616
27951
|
if (!rel || isPathOutside(rel)) return null;
|
|
26617
27952
|
const noExt = rel.replace(/\.md$/i, "");
|
|
26618
|
-
const parts = noExt.split(
|
|
27953
|
+
const parts = noExt.split(sep6).filter((p) => p.length > 0);
|
|
26619
27954
|
if (parts.length === 0) return null;
|
|
26620
27955
|
if (parts.some((p) => !/^[\w.-]+$/.test(p))) return null;
|
|
26621
27956
|
return parts.join("/");
|
|
@@ -26635,8 +27970,8 @@ var init_loader = __esm({
|
|
|
26635
27970
|
|
|
26636
27971
|
// src/commands/renderer.ts
|
|
26637
27972
|
import { exec } from "child_process";
|
|
26638
|
-
import { open as open2, realpath as
|
|
26639
|
-
import { isAbsolute as
|
|
27973
|
+
import { open as open2, realpath as realpath7 } from "fs/promises";
|
|
27974
|
+
import { isAbsolute as isAbsolute6, relative as relative11, resolve as resolvePathJoin, basename as pathBasename } from "path";
|
|
26640
27975
|
import { promisify as promisify3 } from "util";
|
|
26641
27976
|
function tokenizeArgs(s) {
|
|
26642
27977
|
return [...s.matchAll(ARG_TOKEN_RE)].map((match) => {
|
|
@@ -26727,7 +28062,7 @@ async function replaceFiles(prompt, warnings, cmd, cwd, maxFileBytes) {
|
|
|
26727
28062
|
return "" + trailing;
|
|
26728
28063
|
});
|
|
26729
28064
|
}
|
|
26730
|
-
const realCwd = await
|
|
28065
|
+
const realCwd = await realpath7(cwd).catch(() => cwd);
|
|
26731
28066
|
const replacements = await Promise.all(
|
|
26732
28067
|
matches.map(async (match) => {
|
|
26733
28068
|
const rawPath = match[1] ?? "";
|
|
@@ -26736,23 +28071,23 @@ async function replaceFiles(prompt, warnings, cmd, cwd, maxFileBytes) {
|
|
|
26736
28071
|
warnings.push(`file inclusion blocked: @${rawPath} \u2014 secret file pattern`);
|
|
26737
28072
|
return "";
|
|
26738
28073
|
}
|
|
26739
|
-
if (
|
|
28074
|
+
if (isAbsolute6(rawPath) || rawPath.startsWith("~")) {
|
|
26740
28075
|
warnings.push(`file inclusion skipped: @${rawPath} \u2014 outside workspace`);
|
|
26741
28076
|
return "";
|
|
26742
28077
|
}
|
|
26743
28078
|
const resolved = resolvePathJoin(cwd, rawPath);
|
|
26744
|
-
if (isPathOutside(
|
|
28079
|
+
if (isPathOutside(relative11(cwd, resolved))) {
|
|
26745
28080
|
warnings.push(`file inclusion skipped: @${rawPath} \u2014 outside workspace`);
|
|
26746
28081
|
return "";
|
|
26747
28082
|
}
|
|
26748
28083
|
let real;
|
|
26749
28084
|
try {
|
|
26750
|
-
real = await
|
|
28085
|
+
real = await realpath7(resolved);
|
|
26751
28086
|
} catch (error) {
|
|
26752
28087
|
warnings.push(`file inclusion failed: @${rawPath} \u2014 ${message(error)}`);
|
|
26753
28088
|
return "";
|
|
26754
28089
|
}
|
|
26755
|
-
if (isPathOutside(
|
|
28090
|
+
if (isPathOutside(relative11(realCwd, real))) {
|
|
26756
28091
|
warnings.push(`file inclusion skipped: @${rawPath} \u2014 symlink escapes workspace`);
|
|
26757
28092
|
return "";
|
|
26758
28093
|
}
|
|
@@ -27547,8 +28882,8 @@ var init_one_dark = __esm({
|
|
|
27547
28882
|
});
|
|
27548
28883
|
|
|
27549
28884
|
// src/ui/theme.ts
|
|
27550
|
-
function normalizeTheme(
|
|
27551
|
-
const obj =
|
|
28885
|
+
function normalizeTheme(json3) {
|
|
28886
|
+
const obj = json3;
|
|
27552
28887
|
const palette = obj.palette;
|
|
27553
28888
|
const normalizeDim = (v) => {
|
|
27554
28889
|
if (v === void 0) return void 0;
|
|
@@ -27691,11 +29026,11 @@ var init_wcag = __esm({
|
|
|
27691
29026
|
});
|
|
27692
29027
|
|
|
27693
29028
|
// src/ui/theme-loader.ts
|
|
27694
|
-
import { readFile as
|
|
27695
|
-
import { join as
|
|
27696
|
-
import { homedir as
|
|
29029
|
+
import { readFile as readFile23, readdir as readdir7 } from "fs/promises";
|
|
29030
|
+
import { join as join36 } from "path";
|
|
29031
|
+
import { homedir as homedir25 } from "os";
|
|
27697
29032
|
function projectThemesDir(cwd = process.cwd()) {
|
|
27698
|
-
return
|
|
29033
|
+
return join36(cwd, ".kimiflare", "themes");
|
|
27699
29034
|
}
|
|
27700
29035
|
function isHexColor(c) {
|
|
27701
29036
|
return /^#[0-9a-fA-F]{6}$/.test(c);
|
|
@@ -27784,26 +29119,26 @@ async function loadThemesFromDir(dir, source) {
|
|
|
27784
29119
|
return { themes, errors };
|
|
27785
29120
|
}
|
|
27786
29121
|
for (const file of files.filter((f) => f.endsWith(".json"))) {
|
|
27787
|
-
const path =
|
|
29122
|
+
const path = join36(dir, file);
|
|
27788
29123
|
let raw;
|
|
27789
29124
|
try {
|
|
27790
|
-
raw = await
|
|
29125
|
+
raw = await readFile23(path, "utf-8");
|
|
27791
29126
|
} catch (e) {
|
|
27792
29127
|
errors.push(`${path}: ${e instanceof Error ? e.message : String(e)}`);
|
|
27793
29128
|
continue;
|
|
27794
29129
|
}
|
|
27795
|
-
let
|
|
29130
|
+
let json3;
|
|
27796
29131
|
try {
|
|
27797
|
-
|
|
29132
|
+
json3 = JSON.parse(raw);
|
|
27798
29133
|
} catch (e) {
|
|
27799
29134
|
errors.push(`${path}: invalid JSON \u2014 ${e instanceof Error ? e.message : String(e)}`);
|
|
27800
29135
|
continue;
|
|
27801
29136
|
}
|
|
27802
|
-
if (!
|
|
29137
|
+
if (!json3 || typeof json3 !== "object") {
|
|
27803
29138
|
errors.push(`${path}: root must be an object`);
|
|
27804
29139
|
continue;
|
|
27805
29140
|
}
|
|
27806
|
-
const obj =
|
|
29141
|
+
const obj = json3;
|
|
27807
29142
|
const fileErrors = [];
|
|
27808
29143
|
if (typeof obj.name !== "string" || obj.name.length === 0) {
|
|
27809
29144
|
fileErrors.push("name is required");
|
|
@@ -27933,8 +29268,8 @@ var init_theme_loader = __esm({
|
|
|
27933
29268
|
"use strict";
|
|
27934
29269
|
init_wcag();
|
|
27935
29270
|
init_theme();
|
|
27936
|
-
USER_THEMES_DIR =
|
|
27937
|
-
process.env.XDG_CONFIG_HOME ||
|
|
29271
|
+
USER_THEMES_DIR = join36(
|
|
29272
|
+
process.env.XDG_CONFIG_HOME || join36(homedir25(), ".config"),
|
|
27938
29273
|
"kimiflare",
|
|
27939
29274
|
"themes"
|
|
27940
29275
|
);
|
|
@@ -31053,8 +32388,8 @@ var init_input_handlers = __esm({
|
|
|
31053
32388
|
|
|
31054
32389
|
// src/agent/jev-context.ts
|
|
31055
32390
|
import { execFileSync as execFileSync4 } from "child_process";
|
|
31056
|
-
import { readFile as
|
|
31057
|
-
import { basename as basename6, join as
|
|
32391
|
+
import { readFile as readFile24 } from "fs/promises";
|
|
32392
|
+
import { basename as basename6, join as join37, resolve as resolve13 } from "path";
|
|
31058
32393
|
function messageText(message2) {
|
|
31059
32394
|
if (typeof message2.content === "string") return message2.content.trim();
|
|
31060
32395
|
if (!Array.isArray(message2.content)) return "";
|
|
@@ -31071,12 +32406,12 @@ function projectRoot(cwd) {
|
|
|
31071
32406
|
timeout: 1e3
|
|
31072
32407
|
}).trim();
|
|
31073
32408
|
} catch {
|
|
31074
|
-
return
|
|
32409
|
+
return resolve13(cwd);
|
|
31075
32410
|
}
|
|
31076
32411
|
}
|
|
31077
32412
|
async function readPackageMetadata(root) {
|
|
31078
32413
|
try {
|
|
31079
|
-
const parsed = JSON.parse(await
|
|
32414
|
+
const parsed = JSON.parse(await readFile24(join37(root, "package.json"), "utf8"));
|
|
31080
32415
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return {};
|
|
31081
32416
|
const pkg = parsed;
|
|
31082
32417
|
const metadata = {};
|
|
@@ -31093,7 +32428,7 @@ async function readPackageMetadata(root) {
|
|
|
31093
32428
|
async function readLicenseEvidence(root) {
|
|
31094
32429
|
for (const name of ["LICENSE", "LICENSE.md", "LICENSE.txt", "LICENCE", "COPYING"]) {
|
|
31095
32430
|
try {
|
|
31096
|
-
const content = (await
|
|
32431
|
+
const content = (await readFile24(join37(root, name), "utf8")).trim();
|
|
31097
32432
|
if (content) return { source: `project license (${name})`, content: content.slice(0, 700) };
|
|
31098
32433
|
} catch {
|
|
31099
32434
|
}
|
|
@@ -31126,7 +32461,7 @@ async function buildJevContext(question, messages, cwd = process.cwd()) {
|
|
|
31126
32461
|
conversationRemaining -= source.length + content.length + 2;
|
|
31127
32462
|
}
|
|
31128
32463
|
}
|
|
31129
|
-
const currentDirectory = basename6(
|
|
32464
|
+
const currentDirectory = basename6(resolve13(cwd));
|
|
31130
32465
|
const escapedDirectory = currentDirectory.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
31131
32466
|
const namesCurrentDirectory = currentDirectory.length >= 5 && !/^(?:agent|app|code|lib|project|repo|src|test|tests|workspace)$/i.test(currentDirectory) && new RegExp(`\\b${escapedDirectory}\\b`, "i").test(question);
|
|
31132
32467
|
if (CURRENT_PROJECT_REFERENCE.test(question) || namesCurrentDirectory) {
|
|
@@ -31349,8 +32684,8 @@ var init_frontmatter2 = __esm({
|
|
|
31349
32684
|
});
|
|
31350
32685
|
|
|
31351
32686
|
// src/skills/loader.ts
|
|
31352
|
-
import { readFile as
|
|
31353
|
-
import { join as
|
|
32687
|
+
import { readFile as readFile25, readdir as readdir8, stat as stat8 } from "fs/promises";
|
|
32688
|
+
import { join as join38, extname } from "path";
|
|
31354
32689
|
function normalizeManifest(raw, filePath) {
|
|
31355
32690
|
const name = typeof raw.name === "string" ? raw.name : "";
|
|
31356
32691
|
const description = typeof raw.description === "string" ? raw.description : "";
|
|
@@ -31364,7 +32699,7 @@ function normalizeManifest(raw, filePath) {
|
|
|
31364
32699
|
return { name, description, match, scope, priority, enabled: enabled2 };
|
|
31365
32700
|
}
|
|
31366
32701
|
async function loadSkillFile(filePath) {
|
|
31367
|
-
const raw = await
|
|
32702
|
+
const raw = await readFile25(filePath, "utf-8");
|
|
31368
32703
|
const parsed = parseFrontmatter2(raw);
|
|
31369
32704
|
const manifest = normalizeManifest(parsed.data, filePath);
|
|
31370
32705
|
const body = parsed.content.trim();
|
|
@@ -31386,8 +32721,8 @@ async function loadSkillsFromDir(dirPath) {
|
|
|
31386
32721
|
const entries = await readdir8(dirPath);
|
|
31387
32722
|
const files = [];
|
|
31388
32723
|
for (const entry of entries) {
|
|
31389
|
-
const full =
|
|
31390
|
-
const s = await
|
|
32724
|
+
const full = join38(dirPath, entry);
|
|
32725
|
+
const s = await stat8(full);
|
|
31391
32726
|
if (s.isFile() && extname(entry) === ".md") {
|
|
31392
32727
|
files.push(full);
|
|
31393
32728
|
}
|
|
@@ -31414,12 +32749,12 @@ var init_loader2 = __esm({
|
|
|
31414
32749
|
});
|
|
31415
32750
|
|
|
31416
32751
|
// src/skills/manager.ts
|
|
31417
|
-
import { mkdir as mkdir13, writeFile as writeFile14, unlink as
|
|
31418
|
-
import { join as
|
|
32752
|
+
import { mkdir as mkdir13, writeFile as writeFile14, unlink as unlink4, readFile as readFile26 } from "fs/promises";
|
|
32753
|
+
import { join as join39 } from "path";
|
|
31419
32754
|
function getSkillDirs(cwd) {
|
|
31420
32755
|
return {
|
|
31421
|
-
projectDir:
|
|
31422
|
-
globalDir:
|
|
32756
|
+
projectDir: join39(cwd, ".kimiflare", "skills"),
|
|
32757
|
+
globalDir: join39(process.env.HOME ?? "", ".config", "kimiflare", "skills")
|
|
31423
32758
|
};
|
|
31424
32759
|
}
|
|
31425
32760
|
async function listAllSkills(cwd) {
|
|
@@ -31433,7 +32768,7 @@ async function listAllSkills(cwd) {
|
|
|
31433
32768
|
async function createSkill(opts2) {
|
|
31434
32769
|
const dirs = getSkillDirs(opts2.cwd);
|
|
31435
32770
|
const dir = opts2.scope === "project" ? dirs.projectDir : dirs.globalDir;
|
|
31436
|
-
const filepath =
|
|
32771
|
+
const filepath = join39(dir, `${opts2.name}.md`);
|
|
31437
32772
|
const frontmatter = {
|
|
31438
32773
|
name: opts2.name,
|
|
31439
32774
|
enabled: true,
|
|
@@ -31462,14 +32797,14 @@ async function deleteSkill(name, cwd) {
|
|
|
31462
32797
|
const all = await listAllSkills(cwd);
|
|
31463
32798
|
const skill = all.project.find((s) => s.name === name) ?? all.global.find((s) => s.name === name);
|
|
31464
32799
|
if (!skill) throw new Error(`skill "${name}" not found`);
|
|
31465
|
-
await
|
|
32800
|
+
await unlink4(skill.filePath);
|
|
31466
32801
|
return { filepath: skill.filePath };
|
|
31467
32802
|
}
|
|
31468
32803
|
async function setSkillEnabled(name, enabled2, cwd) {
|
|
31469
32804
|
const all = await listAllSkills(cwd);
|
|
31470
32805
|
const skill = all.project.find((s) => s.name === name) ?? all.global.find((s) => s.name === name);
|
|
31471
32806
|
if (!skill) throw new Error(`skill "${name}" not found`);
|
|
31472
|
-
const raw = await
|
|
32807
|
+
const raw = await readFile26(skill.filePath, "utf-8");
|
|
31473
32808
|
const parsed = parseFrontmatter2(raw);
|
|
31474
32809
|
parsed.data.enabled = enabled2;
|
|
31475
32810
|
const yaml = Object.entries(parsed.data).map(([k, v]) => {
|
|
@@ -31562,15 +32897,15 @@ var tui_report_exports = {};
|
|
|
31562
32897
|
__export(tui_report_exports, {
|
|
31563
32898
|
getCategoryReportText: () => getCategoryReportText
|
|
31564
32899
|
});
|
|
31565
|
-
import { readFile as
|
|
31566
|
-
import { join as
|
|
31567
|
-
import { homedir as
|
|
32900
|
+
import { readFile as readFile27 } from "fs/promises";
|
|
32901
|
+
import { join as join40 } from "path";
|
|
32902
|
+
import { homedir as homedir26 } from "os";
|
|
31568
32903
|
function usageDir3() {
|
|
31569
|
-
const xdg = process.env.XDG_DATA_HOME ||
|
|
31570
|
-
return
|
|
32904
|
+
const xdg = process.env.XDG_DATA_HOME || join40(homedir26(), ".local", "share");
|
|
32905
|
+
return join40(xdg, "kimiflare");
|
|
31571
32906
|
}
|
|
31572
32907
|
function usagePath4() {
|
|
31573
|
-
return
|
|
32908
|
+
return join40(usageDir3(), "usage.json");
|
|
31574
32909
|
}
|
|
31575
32910
|
function today3() {
|
|
31576
32911
|
return (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
@@ -31582,7 +32917,7 @@ function daysAgo2(n) {
|
|
|
31582
32917
|
}
|
|
31583
32918
|
async function loadLog3() {
|
|
31584
32919
|
try {
|
|
31585
|
-
const raw = await
|
|
32920
|
+
const raw = await readFile27(usagePath4(), "utf8");
|
|
31586
32921
|
return JSON.parse(raw);
|
|
31587
32922
|
} catch {
|
|
31588
32923
|
return { version: 1, days: [], sessions: [] };
|
|
@@ -31639,7 +32974,7 @@ var init_tui_report = __esm({
|
|
|
31639
32974
|
});
|
|
31640
32975
|
|
|
31641
32976
|
// src/ui/slash-commands.ts
|
|
31642
|
-
import { join as
|
|
32977
|
+
import { join as join41 } from "path";
|
|
31643
32978
|
import QRCode from "qrcode";
|
|
31644
32979
|
function executeFreshStart(ctx, planText, overrideMode, opts2 = {}) {
|
|
31645
32980
|
const oldSessionId = ctx.sessionIdRef.current;
|
|
@@ -32040,7 +33375,7 @@ auto (default) lets the coordinator assess substantial tasks and delegate indepe
|
|
|
32040
33375
|
const context = noContext ? [] : await buildJevContext(question.prompt, ctx.messagesRef.current, process.cwd());
|
|
32041
33376
|
const receipt = formatJevContextReceipt(context, showContext);
|
|
32042
33377
|
setResult(eventKey, { kind: "info", key: eventKey, text: `Jev is evaluating \xB7 ${receipt}` });
|
|
32043
|
-
await new Promise((
|
|
33378
|
+
await new Promise((resolve15) => setTimeout(resolve15, 0));
|
|
32044
33379
|
const answer = await askJev(apiKey, question, { context });
|
|
32045
33380
|
const presentation = presentJevAnswer(question, answer);
|
|
32046
33381
|
setResult(eventKey, {
|
|
@@ -32563,7 +33898,7 @@ ${lines.join("\n")}` }]);
|
|
|
32563
33898
|
void (async () => {
|
|
32564
33899
|
try {
|
|
32565
33900
|
const { sessionsDir: sessionsDir3 } = await Promise.resolve().then(() => (init_sessions(), sessions_exports));
|
|
32566
|
-
const file = await loadSession(
|
|
33901
|
+
const file = await loadSession(join41(sessionsDir3(), `${currentId}.json`));
|
|
32567
33902
|
const cps = file.checkpoints ?? [];
|
|
32568
33903
|
if (cps.length === 0) {
|
|
32569
33904
|
setEvents((e) => [...e, { kind: "info", key: mkKey2(), text: "no checkpoints in this session" }]);
|
|
@@ -32608,7 +33943,7 @@ ${lines.join("\n")}` }]);
|
|
|
32608
33943
|
try {
|
|
32609
33944
|
ctx.ensureSessionId();
|
|
32610
33945
|
const { sessionsDir: sessionsDir3 } = await Promise.resolve().then(() => (init_sessions(), sessions_exports));
|
|
32611
|
-
const filePath =
|
|
33946
|
+
const filePath = join41(sessionsDir3(), `${ctx.sessionIdRef.current}.json`);
|
|
32612
33947
|
await addCheckpoint(filePath, cp);
|
|
32613
33948
|
setEvents((e) => [...e, { kind: "info", key: mkKey2(), text: `checkpoint saved: "${label}"` }]);
|
|
32614
33949
|
} catch (e) {
|
|
@@ -33240,12 +34575,12 @@ project: ${projectSettingsPath(cwd)}`
|
|
|
33240
34575
|
|
|
33241
34576
|
// src/init/context-generator.ts
|
|
33242
34577
|
import { existsSync as existsSync6, statSync as statSync6 } from "fs";
|
|
33243
|
-
import { join as
|
|
34578
|
+
import { join as join42 } from "path";
|
|
33244
34579
|
function detectFlavor(cwd) {
|
|
33245
34580
|
for (const [flavor, signatures] of Object.entries(FLAVOR_SIGNATURES)) {
|
|
33246
34581
|
if (flavor === "generic") continue;
|
|
33247
34582
|
for (const sig of signatures) {
|
|
33248
|
-
const path =
|
|
34583
|
+
const path = join42(cwd, sig);
|
|
33249
34584
|
if (sig.includes("*")) {
|
|
33250
34585
|
try {
|
|
33251
34586
|
const parts = sig.split("*");
|
|
@@ -33266,14 +34601,14 @@ function detectFlavor(cwd) {
|
|
|
33266
34601
|
}
|
|
33267
34602
|
function findFile(cwd, candidates) {
|
|
33268
34603
|
for (const c of candidates) {
|
|
33269
|
-
if (existsSync6(
|
|
34604
|
+
if (existsSync6(join42(cwd, c))) return c;
|
|
33270
34605
|
}
|
|
33271
34606
|
return null;
|
|
33272
34607
|
}
|
|
33273
34608
|
function findSourceRoots(cwd) {
|
|
33274
34609
|
const roots = [];
|
|
33275
34610
|
for (const r of SOURCE_ROOT_CANDIDATES) {
|
|
33276
|
-
const p =
|
|
34611
|
+
const p = join42(cwd, r);
|
|
33277
34612
|
try {
|
|
33278
34613
|
const s = statSync6(p);
|
|
33279
34614
|
if (s.isDirectory()) roots.push(r);
|
|
@@ -33284,9 +34619,9 @@ function findSourceRoots(cwd) {
|
|
|
33284
34619
|
}
|
|
33285
34620
|
function findCiConfig(cwd) {
|
|
33286
34621
|
for (const c of CI_PATHS) {
|
|
33287
|
-
if (existsSync6(
|
|
34622
|
+
if (existsSync6(join42(cwd, c))) {
|
|
33288
34623
|
try {
|
|
33289
|
-
const s = statSync6(
|
|
34624
|
+
const s = statSync6(join42(cwd, c));
|
|
33290
34625
|
return s.isDirectory() ? c : c;
|
|
33291
34626
|
} catch {
|
|
33292
34627
|
}
|
|
@@ -33423,7 +34758,7 @@ function analyzeProject(cwd) {
|
|
|
33423
34758
|
ciConfig: findCiConfig(cwd),
|
|
33424
34759
|
readme: findFile(cwd, ["README.md", "README.rst", "README.txt", "Readme.md"]),
|
|
33425
34760
|
sourceRoots: findSourceRoots(cwd),
|
|
33426
|
-
hasGit: existsSync6(
|
|
34761
|
+
hasGit: existsSync6(join42(cwd, ".git"))
|
|
33427
34762
|
};
|
|
33428
34763
|
}
|
|
33429
34764
|
function bashDiscoveryCommands(profile) {
|
|
@@ -33588,7 +34923,7 @@ Aim for 100\u2013200 lines total. Use markdown tables where they save space.
|
|
|
33588
34923
|
}
|
|
33589
34924
|
function buildInitPrompt(cwd) {
|
|
33590
34925
|
const existingName = ["AGENTS.md", "KIMI.md", "KIMIFLARE.md", "AGENT.md"].find(
|
|
33591
|
-
(n) => existsSync6(
|
|
34926
|
+
(n) => existsSync6(join42(cwd, n))
|
|
33592
34927
|
);
|
|
33593
34928
|
const isRefresh = existingName !== void 0;
|
|
33594
34929
|
const targetFilename = existingName ?? "AGENTS.md";
|
|
@@ -33672,7 +35007,7 @@ var init_context_generator = __esm({
|
|
|
33672
35007
|
|
|
33673
35008
|
// src/init/run-init.ts
|
|
33674
35009
|
import { existsSync as existsSync7 } from "fs";
|
|
33675
|
-
import { join as
|
|
35010
|
+
import { join as join43 } from "path";
|
|
33676
35011
|
async function runInit(deps) {
|
|
33677
35012
|
const {
|
|
33678
35013
|
cfg,
|
|
@@ -33765,7 +35100,7 @@ async function runInit(deps) {
|
|
|
33765
35100
|
lspManagerRef.current.notifyChange(path, content);
|
|
33766
35101
|
} else {
|
|
33767
35102
|
void import("fs/promises").then(
|
|
33768
|
-
({ readFile:
|
|
35103
|
+
({ readFile: readFile30 }) => readFile30(path, "utf8").then((c) => lspManagerRef.current.notifyChange(path, c)).catch(() => {
|
|
33769
35104
|
})
|
|
33770
35105
|
);
|
|
33771
35106
|
}
|
|
@@ -33870,7 +35205,7 @@ async function runInit(deps) {
|
|
|
33870
35205
|
}
|
|
33871
35206
|
}
|
|
33872
35207
|
});
|
|
33873
|
-
if (existsSync7(
|
|
35208
|
+
if (existsSync7(join43(cwd, targetFilename))) {
|
|
33874
35209
|
if (cacheStableRef.current) {
|
|
33875
35210
|
messagesRef.current[1] = {
|
|
33876
35211
|
role: "system",
|
|
@@ -33962,11 +35297,11 @@ var init_run_init = __esm({
|
|
|
33962
35297
|
});
|
|
33963
35298
|
|
|
33964
35299
|
// src/skills/discovery.ts
|
|
33965
|
-
import { readdir as readdir9, stat as
|
|
33966
|
-
import { join as
|
|
35300
|
+
import { readdir as readdir9, stat as stat9, readFile as readFile28 } from "fs/promises";
|
|
35301
|
+
import { join as join44, extname as extname2 } from "path";
|
|
33967
35302
|
async function dirExists(path) {
|
|
33968
35303
|
try {
|
|
33969
|
-
const s = await
|
|
35304
|
+
const s = await stat9(path);
|
|
33970
35305
|
return s.isDirectory();
|
|
33971
35306
|
} catch {
|
|
33972
35307
|
return false;
|
|
@@ -33974,7 +35309,7 @@ async function dirExists(path) {
|
|
|
33974
35309
|
}
|
|
33975
35310
|
async function fileExists(path) {
|
|
33976
35311
|
try {
|
|
33977
|
-
const s = await
|
|
35312
|
+
const s = await stat9(path);
|
|
33978
35313
|
return s.isFile();
|
|
33979
35314
|
} catch {
|
|
33980
35315
|
return false;
|
|
@@ -33987,20 +35322,20 @@ async function scanSkillDir(dirPath, source) {
|
|
|
33987
35322
|
for (const entry of entries) {
|
|
33988
35323
|
if (!entry.isFile()) continue;
|
|
33989
35324
|
if (!SKILL_EXTENSIONS.has(extname2(entry.name))) continue;
|
|
33990
|
-
files.push({ filePath:
|
|
35325
|
+
files.push({ filePath: join44(dirPath, entry.name), source });
|
|
33991
35326
|
}
|
|
33992
35327
|
return files;
|
|
33993
35328
|
}
|
|
33994
35329
|
async function discoverSkills(cwd) {
|
|
33995
|
-
const agentsSkills = await scanSkillDir(
|
|
33996
|
-
const agentsMd = await fileExists(
|
|
33997
|
-
const githubSkills = await scanSkillDir(
|
|
33998
|
-
const kimiflareSkills = await scanSkillDir(
|
|
35330
|
+
const agentsSkills = await scanSkillDir(join44(cwd, ".agents", "skills"), "agents");
|
|
35331
|
+
const agentsMd = await fileExists(join44(cwd, "AGENTS.md")) ? [{ filePath: join44(cwd, "AGENTS.md"), source: "agents-md" }] : [];
|
|
35332
|
+
const githubSkills = await scanSkillDir(join44(cwd, ".github", "skills"), "github");
|
|
35333
|
+
const kimiflareSkills = await scanSkillDir(join44(cwd, ".kimiflare", "skills"), "kimiflare");
|
|
33999
35334
|
const ordered = [...agentsSkills, ...agentsMd, ...githubSkills, ...kimiflareSkills];
|
|
34000
35335
|
return ordered;
|
|
34001
35336
|
}
|
|
34002
35337
|
async function readSkillFile(filePath) {
|
|
34003
|
-
const bytes = await
|
|
35338
|
+
const bytes = await readFile28(filePath);
|
|
34004
35339
|
return { bytes, text: bytes.toString("utf-8") };
|
|
34005
35340
|
}
|
|
34006
35341
|
var SKILL_EXTENSIONS;
|
|
@@ -34012,9 +35347,9 @@ var init_discovery = __esm({
|
|
|
34012
35347
|
});
|
|
34013
35348
|
|
|
34014
35349
|
// src/skills/parser.ts
|
|
34015
|
-
import { createHash as
|
|
35350
|
+
import { createHash as createHash6 } from "crypto";
|
|
34016
35351
|
function sha2562(input) {
|
|
34017
|
-
return
|
|
35352
|
+
return createHash6("sha256").update(input).digest("hex");
|
|
34018
35353
|
}
|
|
34019
35354
|
function computeContentHash(rawText, parserVersion) {
|
|
34020
35355
|
return sha2562(`${rawText}
|
|
@@ -34206,7 +35541,7 @@ var init_skills = __esm({
|
|
|
34206
35541
|
|
|
34207
35542
|
// src/ui/run-startup-tasks.ts
|
|
34208
35543
|
import { existsSync as existsSync8 } from "fs";
|
|
34209
|
-
import { join as
|
|
35544
|
+
import { join as join45 } from "path";
|
|
34210
35545
|
function runStartupTasks(deps) {
|
|
34211
35546
|
const {
|
|
34212
35547
|
cfg,
|
|
@@ -34228,7 +35563,7 @@ function runStartupTasks(deps) {
|
|
|
34228
35563
|
}
|
|
34229
35564
|
});
|
|
34230
35565
|
if (cfg.memoryEnabled) {
|
|
34231
|
-
const dbPath = cfg.memoryDbPath ??
|
|
35566
|
+
const dbPath = cfg.memoryDbPath ?? join45(process.cwd(), ".kimiflare", "memory.db");
|
|
34232
35567
|
const manager = new MemoryManager({
|
|
34233
35568
|
dbPath,
|
|
34234
35569
|
...auth,
|
|
@@ -34260,7 +35595,7 @@ function runStartupTasks(deps) {
|
|
|
34260
35595
|
});
|
|
34261
35596
|
const cwd = process.cwd();
|
|
34262
35597
|
sessionStartRecallRef.current = manager.recall({ text: cwd, repoPath: cwd, limit: 5 });
|
|
34263
|
-
if (["AGENTS.md", "KIMI.md", "KIMIFLARE.md", "AGENT.md"].some((name) => existsSync8(
|
|
35598
|
+
if (["AGENTS.md", "KIMI.md", "KIMIFLARE.md", "AGENT.md"].some((name) => existsSync8(join45(cwd, name)))) {
|
|
34264
35599
|
const lastRefresh = manager.getLastKimiMdRefreshTime(cwd);
|
|
34265
35600
|
const driftCount = manager.countHighSignalMemoriesSince(cwd, lastRefresh);
|
|
34266
35601
|
if (driftCount >= 5) {
|
|
@@ -34271,7 +35606,7 @@ function runStartupTasks(deps) {
|
|
|
34271
35606
|
memoryManagerRef.current?.close();
|
|
34272
35607
|
memoryManagerRef.current = null;
|
|
34273
35608
|
}
|
|
34274
|
-
const skillDbPath = cfg.memoryDbPath ??
|
|
35609
|
+
const skillDbPath = cfg.memoryDbPath ?? join45(process.cwd(), ".kimiflare", "memory.db");
|
|
34275
35610
|
const skillDb = getMemoryDb() ?? openMemoryDb(skillDbPath);
|
|
34276
35611
|
initSkillsSchema(skillDb);
|
|
34277
35612
|
void indexSkills({
|
|
@@ -34624,9 +35959,9 @@ var init_run_compact = __esm({
|
|
|
34624
35959
|
|
|
34625
35960
|
// src/ui/bang-command.ts
|
|
34626
35961
|
import { spawn as spawn10 } from "child_process";
|
|
34627
|
-
import { mkdtemp as mkdtemp2, readFile as
|
|
35962
|
+
import { mkdtemp as mkdtemp2, readFile as readFile29, rm as rm3 } from "fs/promises";
|
|
34628
35963
|
import { tmpdir as tmpdir4, platform as platform7 } from "os";
|
|
34629
|
-
import { join as
|
|
35964
|
+
import { join as join46 } from "path";
|
|
34630
35965
|
function registerInkInstance(instance) {
|
|
34631
35966
|
inkInstance = instance;
|
|
34632
35967
|
}
|
|
@@ -34695,10 +36030,10 @@ function swallow() {
|
|
|
34695
36030
|
});
|
|
34696
36031
|
}
|
|
34697
36032
|
function spawnInherit(inv, cwd) {
|
|
34698
|
-
return new Promise((
|
|
36033
|
+
return new Promise((resolve15, reject) => {
|
|
34699
36034
|
const child = spawn10(inv.file, inv.args, { cwd, stdio: "inherit", env: inv.env ?? process.env });
|
|
34700
36035
|
child.once("error", reject);
|
|
34701
|
-
child.once("exit", (code, signal) =>
|
|
36036
|
+
child.once("exit", (code, signal) => resolve15({ code, signal }));
|
|
34702
36037
|
});
|
|
34703
36038
|
}
|
|
34704
36039
|
async function runBangCommand(command, cwd) {
|
|
@@ -34712,8 +36047,8 @@ async function runBangCommand(command, cwd) {
|
|
|
34712
36047
|
const origErr = stderr.write;
|
|
34713
36048
|
const readable = stdin.listeners("readable");
|
|
34714
36049
|
const wasRaw = stdin.isTTY ? stdin.isRaw : false;
|
|
34715
|
-
const logDir2 = isWindows ? null : await mkdtemp2(
|
|
34716
|
-
const logPath = logDir2 ?
|
|
36050
|
+
const logDir2 = isWindows ? null : await mkdtemp2(join46(tmpdir4(), "autopilot-bang-"));
|
|
36051
|
+
const logPath = logDir2 ? join46(logDir2, "out.log") : null;
|
|
34717
36052
|
const viaScript = logPath ? scriptInvocation(command, shell, logPath) : null;
|
|
34718
36053
|
inkInstance?.clear();
|
|
34719
36054
|
origOut.call(stdout, `
|
|
@@ -34754,7 +36089,7 @@ $ ${command}
|
|
|
34754
36089
|
}
|
|
34755
36090
|
let output = spawnError ? `failed to start: ${spawnError}` : null;
|
|
34756
36091
|
if (captured && logPath) {
|
|
34757
|
-
output = cleanTerminalOutput(await
|
|
36092
|
+
output = cleanTerminalOutput(await readFile29(logPath, "utf8").catch(() => ""));
|
|
34758
36093
|
}
|
|
34759
36094
|
if (logDir2) await rm3(logDir2, { recursive: true, force: true }).catch(() => {
|
|
34760
36095
|
});
|
|
@@ -34772,8 +36107,8 @@ var init_bang_command = __esm({
|
|
|
34772
36107
|
});
|
|
34773
36108
|
|
|
34774
36109
|
// src/commands/save.ts
|
|
34775
|
-
import { mkdir as mkdir14, writeFile as writeFile15, unlink as
|
|
34776
|
-
import { dirname as
|
|
36110
|
+
import { mkdir as mkdir14, writeFile as writeFile15, unlink as unlink5 } from "fs/promises";
|
|
36111
|
+
import { dirname as dirname17 } from "path";
|
|
34777
36112
|
async function saveCustomCommand(opts2) {
|
|
34778
36113
|
const dir = opts2.source === "project" ? projectCommandsDir(opts2.cwd) : globalCommandsDir();
|
|
34779
36114
|
const filepath = `${dir}/${opts2.name}.md`;
|
|
@@ -34784,12 +36119,12 @@ async function saveCustomCommand(opts2) {
|
|
|
34784
36119
|
if (opts2.effort) data.effort = opts2.effort;
|
|
34785
36120
|
const frontmatter = serializeFrontmatter(data);
|
|
34786
36121
|
const content = frontmatter + opts2.template;
|
|
34787
|
-
await mkdir14(
|
|
36122
|
+
await mkdir14(dirname17(filepath), { recursive: true });
|
|
34788
36123
|
await writeFile15(filepath, content, "utf8");
|
|
34789
36124
|
return { filepath };
|
|
34790
36125
|
}
|
|
34791
36126
|
async function deleteCustomCommand(cmd) {
|
|
34792
|
-
await
|
|
36127
|
+
await unlink5(cmd.filepath);
|
|
34793
36128
|
}
|
|
34794
36129
|
var init_save = __esm({
|
|
34795
36130
|
"src/commands/save.ts"() {
|
|
@@ -34908,8 +36243,8 @@ __export(app_exports, {
|
|
|
34908
36243
|
import React27, { useState as useState28, useRef as useRef9, useEffect as useEffect11, useCallback as useCallback9, useMemo as useMemo6 } from "react";
|
|
34909
36244
|
import { Box as Box41, Text as Text42, useApp, useInput as useInput20, useWindowSize as useWindowSize2, render } from "ink";
|
|
34910
36245
|
import { existsSync as existsSync9 } from "fs";
|
|
34911
|
-
import { join as
|
|
34912
|
-
import { homedir as
|
|
36246
|
+
import { join as join47, resolve as resolve14 } from "path";
|
|
36247
|
+
import { homedir as homedir27 } from "os";
|
|
34913
36248
|
import { jsx as jsx43, jsxs as jsxs40 } from "react/jsx-runtime";
|
|
34914
36249
|
function App({
|
|
34915
36250
|
initialCfg,
|
|
@@ -35231,8 +36566,8 @@ ${wcagWarnings.join("\n")}` }
|
|
|
35231
36566
|
const exactDirectory = query === "~" || query === "." || query === ".." || query.endsWith("/..");
|
|
35232
36567
|
const prefix = exactDirectory ? `${query.replace(/\/$/, "")}/` : slash >= 0 ? query.slice(0, slash + 1) : "";
|
|
35233
36568
|
const directory = exactDirectory ? query : slash >= 0 ? query.slice(0, slash + 1) : query;
|
|
35234
|
-
const expanded = directory.startsWith("~") ?
|
|
35235
|
-
const target =
|
|
36569
|
+
const expanded = directory.startsWith("~") ? join47(homedir27(), directory.slice(1)) : directory;
|
|
36570
|
+
const target = resolve14(cwd, expanded || ".");
|
|
35236
36571
|
const entries2 = await glob("*", {
|
|
35237
36572
|
cwd: target,
|
|
35238
36573
|
dot: false,
|
|
@@ -36208,7 +37543,7 @@ ${result2.output ?? "(output not captured)"}`,
|
|
|
36208
37543
|
}
|
|
36209
37544
|
}
|
|
36210
37545
|
turnCounterRef.current += 1;
|
|
36211
|
-
if (turnCounterRef.current % 15 === 0 && ["AGENTS.md", "KIMI.md", "KIMIFLARE.md", "AGENT.md"].some((name) => existsSync9(
|
|
37546
|
+
if (turnCounterRef.current % 15 === 0 && ["AGENTS.md", "KIMI.md", "KIMIFLARE.md", "AGENT.md"].some((name) => existsSync9(join47(process.cwd(), name))) && !kimiMdStale) {
|
|
36212
37547
|
setEvents((e) => [
|
|
36213
37548
|
...e,
|
|
36214
37549
|
{ kind: "info", key: mkKey(), text: "Tip: Rerunning /init occasionally helps autopilot (formerly kimiflare) stay accurate as your project evolves." }
|
|
@@ -36232,7 +37567,7 @@ ${result2.output ?? "(output not captured)"}`,
|
|
|
36232
37567
|
};
|
|
36233
37568
|
ensureSessionId();
|
|
36234
37569
|
const { sessionsDir: sessionsDir3 } = await Promise.resolve().then(() => (init_sessions(), sessions_exports));
|
|
36235
|
-
const filePath =
|
|
37570
|
+
const filePath = join47(sessionsDir3(), `${sessionIdRef.current}.json`);
|
|
36236
37571
|
await addCheckpoint(filePath, cp);
|
|
36237
37572
|
}
|
|
36238
37573
|
const summary = await generateContinuationSummary({
|
|
@@ -36600,7 +37935,7 @@ ${conflicts.join("\n")}` }
|
|
|
36600
37935
|
lspManagerRef.current.notifyChange(path, content2);
|
|
36601
37936
|
} else {
|
|
36602
37937
|
void import("fs/promises").then(
|
|
36603
|
-
({ readFile:
|
|
37938
|
+
({ readFile: readFile30 }) => readFile30(path, "utf8").then((c) => lspManagerRef.current.notifyChange(path, c)).catch(() => {
|
|
36604
37939
|
})
|
|
36605
37940
|
);
|
|
36606
37941
|
}
|
|
@@ -36786,7 +38121,7 @@ ${conflicts.join("\n")}` }
|
|
|
36786
38121
|
};
|
|
36787
38122
|
ensureSessionId();
|
|
36788
38123
|
const { sessionsDir: sessionsDir3 } = await Promise.resolve().then(() => (init_sessions(), sessions_exports));
|
|
36789
|
-
const filePath =
|
|
38124
|
+
const filePath = join47(sessionsDir3(), `${sessionIdRef.current}.json`);
|
|
36790
38125
|
await addCheckpoint(filePath, cp);
|
|
36791
38126
|
}
|
|
36792
38127
|
const summary = await generateContinuationSummary({
|
|
@@ -37202,7 +38537,7 @@ ${conflicts.join("\n")}` }
|
|
|
37202
38537
|
lsp: {
|
|
37203
38538
|
servers: cfg?.lspServers ?? {},
|
|
37204
38539
|
scope: lspScope,
|
|
37205
|
-
hasProjectDir: existsSync9(
|
|
38540
|
+
hasProjectDir: existsSync9(join47(process.cwd(), ".kimiflare"))
|
|
37206
38541
|
}
|
|
37207
38542
|
});
|
|
37208
38543
|
});
|
|
@@ -37259,7 +38594,7 @@ ${conflicts.join("\n")}` }
|
|
|
37259
38594
|
onCommandDelete: handleCommandDelete2,
|
|
37260
38595
|
lspServers: cfg?.lspServers ?? {},
|
|
37261
38596
|
lspScope,
|
|
37262
|
-
hasProjectDir: existsSync9(
|
|
38597
|
+
hasProjectDir: existsSync9(join47(process.cwd(), ".kimiflare")),
|
|
37263
38598
|
onLspSave: handleLspSave2,
|
|
37264
38599
|
themes: themeList(),
|
|
37265
38600
|
onPickTheme: handleThemePick,
|
|
@@ -37770,8 +39105,8 @@ async function resolveFiles(filePatterns, cwd) {
|
|
|
37770
39105
|
const resolved = /* @__PURE__ */ new Set();
|
|
37771
39106
|
for (const pattern of filePatterns) {
|
|
37772
39107
|
try {
|
|
37773
|
-
const
|
|
37774
|
-
if (
|
|
39108
|
+
const stat10 = await import("fs/promises").then((m) => m.stat(resolve6(cwd, pattern)));
|
|
39109
|
+
if (stat10.isFile()) {
|
|
37775
39110
|
resolved.add(resolve6(cwd, pattern));
|
|
37776
39111
|
continue;
|
|
37777
39112
|
}
|
|
@@ -38110,6 +39445,55 @@ program.command("cost").description("Show cost attribution by task type (require
|
|
|
38110
39445
|
await runCostCommand2({ ...cmdOpts, config: cfg });
|
|
38111
39446
|
});
|
|
38112
39447
|
program.addCommand(createRemoteCommand());
|
|
39448
|
+
var asterCommand = program.command("aster").description("Manage the Aster mobile API");
|
|
39449
|
+
var asterTokenCommand = asterCommand.command("token").description("Manage scoped Aster API credentials");
|
|
39450
|
+
asterTokenCommand.command("create").description("Create a 30-day credential scoped to selected workspace IDs").requiredOption("-n, --name <name>", "Credential label").requiredOption("-w, --workspace <id>", "Allowed workspace ID (repeatable)", (value, previous = []) => [...previous, value]).option("--expires-in-days <days>", "Expiry in days (1-365)", (value) => Number.parseInt(value, 10), 30).action(async (options) => {
|
|
39451
|
+
const { loadAsterServerConfig: loadAsterServerConfig2 } = await Promise.resolve().then(() => (init_aster_workspaces(), aster_workspaces_exports));
|
|
39452
|
+
const { AsterStore: AsterStore2 } = await Promise.resolve().then(() => (init_aster_store(), aster_store_exports));
|
|
39453
|
+
try {
|
|
39454
|
+
const config2 = await loadAsterServerConfig2();
|
|
39455
|
+
const workspaceIds = [...new Set(options.workspace)];
|
|
39456
|
+
if (!Number.isInteger(options.expiresInDays) || options.expiresInDays < 1 || options.expiresInDays > 365) {
|
|
39457
|
+
throw new Error("--expires-in-days must be an integer from 1 through 365");
|
|
39458
|
+
}
|
|
39459
|
+
if (workspaceIds.some((id) => !config2.workspaces.some((workspace) => workspace.id === id))) {
|
|
39460
|
+
throw new Error("credential includes an unknown workspace ID");
|
|
39461
|
+
}
|
|
39462
|
+
const store = new AsterStore2();
|
|
39463
|
+
try {
|
|
39464
|
+
const created = store.createCredential({
|
|
39465
|
+
name: options.name,
|
|
39466
|
+
workspaceIds,
|
|
39467
|
+
scopes: ["workspaces:read", "models:read", "conversations:read", "conversations:write", "approvals:resolve"],
|
|
39468
|
+
expiresAt: Date.now() + options.expiresInDays * 24 * 60 * 60 * 1e3
|
|
39469
|
+
});
|
|
39470
|
+
console.log(`Credential ID: ${created.id}`);
|
|
39471
|
+
console.log(`Workspace scope: ${created.workspaceIds.join(", ")}`);
|
|
39472
|
+
console.log(`Expires: ${new Date(created.expiresAt).toISOString()}`);
|
|
39473
|
+
console.log("Copy this token into the Aster app's Keychain now; it will not be shown again:");
|
|
39474
|
+
console.log(created.token);
|
|
39475
|
+
} finally {
|
|
39476
|
+
store.close();
|
|
39477
|
+
}
|
|
39478
|
+
} catch (error) {
|
|
39479
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
39480
|
+
process.exitCode = 2;
|
|
39481
|
+
}
|
|
39482
|
+
});
|
|
39483
|
+
asterTokenCommand.command("revoke").description("Revoke an Aster credential immediately").argument("<credential-id>").action(async (credentialId) => {
|
|
39484
|
+
const { AsterStore: AsterStore2 } = await Promise.resolve().then(() => (init_aster_store(), aster_store_exports));
|
|
39485
|
+
const store = new AsterStore2();
|
|
39486
|
+
try {
|
|
39487
|
+
if (!store.revokeCredential(credentialId)) {
|
|
39488
|
+
console.error("Aster credential not found");
|
|
39489
|
+
process.exitCode = 1;
|
|
39490
|
+
return;
|
|
39491
|
+
}
|
|
39492
|
+
console.log(`Revoked credential ${credentialId}`);
|
|
39493
|
+
} finally {
|
|
39494
|
+
store.close();
|
|
39495
|
+
}
|
|
39496
|
+
});
|
|
38113
39497
|
var logsCmd = program.command("logs").description("Inspect KimiFlare's structured logs (jsonl, one file per day, 7-day retention)");
|
|
38114
39498
|
logsCmd.command("path").description("Print today's log file path. Useful for tailing: tail -f $(autopilot logs path) | jq").action(async () => {
|
|
38115
39499
|
const { logPathFor: logPathFor2 } = await Promise.resolve().then(() => (init_log_sink(), log_sink_exports));
|
|
@@ -38386,11 +39770,11 @@ async function promptHidden(question) {
|
|
|
38386
39770
|
}
|
|
38387
39771
|
});
|
|
38388
39772
|
const rl = createInterface4({ input: process.stdin, output, terminal: true });
|
|
38389
|
-
return new Promise((
|
|
39773
|
+
return new Promise((resolve15) => {
|
|
38390
39774
|
rl.question(question, (answer) => {
|
|
38391
39775
|
rl.close();
|
|
38392
39776
|
process.stdout.write("\n");
|
|
38393
|
-
|
|
39777
|
+
resolve15(answer);
|
|
38394
39778
|
});
|
|
38395
39779
|
muted = true;
|
|
38396
39780
|
});
|