autopilot-ai 1.9.0 → 1.10.1
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 +1888 -479
- 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;
|
|
@@ -19585,12 +20920,180 @@ var init_app_helpers = __esm({
|
|
|
19585
20920
|
}
|
|
19586
20921
|
});
|
|
19587
20922
|
|
|
19588
|
-
// src/
|
|
20923
|
+
// src/ui/bang-command.ts
|
|
19589
20924
|
import { spawn as spawn7 } from "child_process";
|
|
19590
|
-
import { mkdtemp, readFile as
|
|
19591
|
-
import { tmpdir as tmpdir3 } from "os";
|
|
19592
|
-
import { join as
|
|
19593
|
-
|
|
20925
|
+
import { mkdtemp, readFile as readFile22, rm as rm2 } from "fs/promises";
|
|
20926
|
+
import { tmpdir as tmpdir3, platform as platform4 } from "os";
|
|
20927
|
+
import { join as join33 } from "path";
|
|
20928
|
+
function registerInkInstance(instance) {
|
|
20929
|
+
inkInstance = instance;
|
|
20930
|
+
}
|
|
20931
|
+
function registerTerminalHandoff(h) {
|
|
20932
|
+
terminalHandoff = h;
|
|
20933
|
+
}
|
|
20934
|
+
function runCaptured(inv, cwd) {
|
|
20935
|
+
return new Promise((resolve15) => {
|
|
20936
|
+
const child = spawn7(inv.file, inv.args, { cwd, stdio: ["ignore", "pipe", "pipe"], env: inv.env ?? process.env });
|
|
20937
|
+
let output = "";
|
|
20938
|
+
child.stdout?.on("data", (c) => output += c.toString("utf8"));
|
|
20939
|
+
child.stderr?.on("data", (c) => output += c.toString("utf8"));
|
|
20940
|
+
child.once("error", (e) => resolve15({ code: 127, signal: null, output: `failed to start: ${e.message}` }));
|
|
20941
|
+
child.once("exit", (code, signal) => resolve15({ code, signal, output }));
|
|
20942
|
+
});
|
|
20943
|
+
}
|
|
20944
|
+
function isTerminalHandedOff() {
|
|
20945
|
+
return handedOff;
|
|
20946
|
+
}
|
|
20947
|
+
function parseBangCommand(input) {
|
|
20948
|
+
const t = input.trimStart();
|
|
20949
|
+
if (!t.startsWith("!")) return null;
|
|
20950
|
+
return t.slice(1).trim();
|
|
20951
|
+
}
|
|
20952
|
+
function scriptInvocation(command, shell, logPath, plat = platform4()) {
|
|
20953
|
+
if (plat === "darwin" || plat === "freebsd" || plat === "openbsd" || plat === "netbsd") {
|
|
20954
|
+
return { file: "script", args: ["-q", logPath, shell, "-c", command] };
|
|
20955
|
+
}
|
|
20956
|
+
if (plat === "linux") {
|
|
20957
|
+
return { file: "script", args: ["-q", "-e", "-c", command, logPath], env: { ...process.env, SHELL: shell } };
|
|
20958
|
+
}
|
|
20959
|
+
return null;
|
|
20960
|
+
}
|
|
20961
|
+
function cleanTerminalOutput(raw) {
|
|
20962
|
+
const text = raw.replace(/\x1b\][^\x07\x1b]*(?:\x07|\x1b\\)/g, "").replace(/\x1b\[[0-?]*[ -/]*[@-~]/g, "").replace(/\x1b[@-Z\\-_]/g, "").replace(/^Script (?:started|done) on .*$/gm, "");
|
|
20963
|
+
const lines = text.split(/\r?\n/).map((line) => {
|
|
20964
|
+
const segs = line.split("\r");
|
|
20965
|
+
let out = segs[segs.length - 1] ?? "";
|
|
20966
|
+
if (!out && segs.length > 1) out = segs.filter(Boolean).pop() ?? "";
|
|
20967
|
+
while (/[^\x08]\x08/.test(out)) out = out.replace(/[^\x08]\x08/, "");
|
|
20968
|
+
return out.replace(/[\x00-\x08\x0b-\x1f\x7f]/g, "");
|
|
20969
|
+
});
|
|
20970
|
+
return lines.join("\n").replace(/\n{3,}/g, "\n\n").trim();
|
|
20971
|
+
}
|
|
20972
|
+
function truncateMiddle2(text, max = MAX_CONTEXT_CHARS) {
|
|
20973
|
+
if (text.length <= max) return text;
|
|
20974
|
+
const half = Math.floor(max / 2);
|
|
20975
|
+
return `${text.slice(0, half)}
|
|
20976
|
+
|
|
20977
|
+
[\u2026 ${text.length - max} chars omitted \u2026]
|
|
20978
|
+
|
|
20979
|
+
${text.slice(-half)}`;
|
|
20980
|
+
}
|
|
20981
|
+
function formatBangContext(r) {
|
|
20982
|
+
const status = r.signal ? `killed by ${r.signal}` : `exit code ${r.exitCode ?? "?"}`;
|
|
20983
|
+
const body = r.output === null ? "(output was shown in the user's terminal but could not be captured)" : r.output ? truncateMiddle2(r.output) : "(no output)";
|
|
20984
|
+
return `[The user ran this shell command themselves with \`!\` in the autopilot prompt. This is context for you, not a new request.]
|
|
20985
|
+
$ ${r.command}
|
|
20986
|
+
(${status})
|
|
20987
|
+
${body}`;
|
|
20988
|
+
}
|
|
20989
|
+
function stopReading(stdin) {
|
|
20990
|
+
stdin.pause();
|
|
20991
|
+
const stream = stdin;
|
|
20992
|
+
try {
|
|
20993
|
+
if (stream._handle?.reading && stream._handle.readStop) {
|
|
20994
|
+
stream._handle.readStop();
|
|
20995
|
+
stream._handle.reading = false;
|
|
20996
|
+
if (stream._readableState) stream._readableState.reading = false;
|
|
20997
|
+
}
|
|
20998
|
+
} catch {
|
|
20999
|
+
}
|
|
21000
|
+
}
|
|
21001
|
+
function swallow() {
|
|
21002
|
+
return ((_chunk, encOrCb, cb) => {
|
|
21003
|
+
const done = typeof encOrCb === "function" ? encOrCb : cb;
|
|
21004
|
+
if (typeof done === "function") done();
|
|
21005
|
+
return true;
|
|
21006
|
+
});
|
|
21007
|
+
}
|
|
21008
|
+
function spawnInherit(inv, cwd) {
|
|
21009
|
+
return new Promise((resolve15, reject) => {
|
|
21010
|
+
const child = spawn7(inv.file, inv.args, { cwd, stdio: "inherit", env: inv.env ?? process.env });
|
|
21011
|
+
child.once("error", reject);
|
|
21012
|
+
child.once("exit", (code, signal) => resolve15({ code, signal }));
|
|
21013
|
+
});
|
|
21014
|
+
}
|
|
21015
|
+
async function runBangCommand(command, cwd) {
|
|
21016
|
+
const isWindows = platform4() === "win32";
|
|
21017
|
+
const shell = isWindows ? process.env.COMSPEC || "cmd.exe" : process.env.SHELL || "/bin/sh";
|
|
21018
|
+
const direct = isWindows ? { file: shell, args: ["/c", command] } : { file: shell, args: ["-c", command] };
|
|
21019
|
+
if (terminalHandoff && !await terminalHandoff.suspend()) {
|
|
21020
|
+
const r = await runCaptured(direct, cwd);
|
|
21021
|
+
return { command, exitCode: r.code, signal: r.signal, output: cleanTerminalOutput(r.output) };
|
|
21022
|
+
}
|
|
21023
|
+
const stdin = process.stdin;
|
|
21024
|
+
const stdout = process.stdout;
|
|
21025
|
+
const stderr = process.stderr;
|
|
21026
|
+
const origOut = stdout.write;
|
|
21027
|
+
const origErr = stderr.write;
|
|
21028
|
+
const readable = stdin.listeners("readable");
|
|
21029
|
+
const wasRaw = stdin.isTTY ? stdin.isRaw : false;
|
|
21030
|
+
const logDir2 = isWindows ? null : await mkdtemp(join33(tmpdir3(), "autopilot-bang-"));
|
|
21031
|
+
const logPath = logDir2 ? join33(logDir2, "out.log") : null;
|
|
21032
|
+
const viaScript = logPath ? scriptInvocation(command, shell, logPath) : null;
|
|
21033
|
+
inkInstance?.clear();
|
|
21034
|
+
origOut.call(stdout, `
|
|
21035
|
+
$ ${command}
|
|
21036
|
+
`);
|
|
21037
|
+
stdout.write = swallow();
|
|
21038
|
+
stderr.write = swallow();
|
|
21039
|
+
for (const l of readable) stdin.removeListener("readable", l);
|
|
21040
|
+
if (stdin.isTTY) stdin.setRawMode(false);
|
|
21041
|
+
stopReading(stdin);
|
|
21042
|
+
handedOff = true;
|
|
21043
|
+
let result2;
|
|
21044
|
+
let captured = false;
|
|
21045
|
+
let spawnError = null;
|
|
21046
|
+
try {
|
|
21047
|
+
if (viaScript) {
|
|
21048
|
+
try {
|
|
21049
|
+
result2 = await spawnInherit(viaScript, cwd);
|
|
21050
|
+
captured = true;
|
|
21051
|
+
} catch (e) {
|
|
21052
|
+
logger.warn("bang:script_unavailable", { error: e.message });
|
|
21053
|
+
result2 = await spawnInherit(direct, cwd);
|
|
21054
|
+
}
|
|
21055
|
+
} else {
|
|
21056
|
+
result2 = await spawnInherit(direct, cwd);
|
|
21057
|
+
}
|
|
21058
|
+
} catch (e) {
|
|
21059
|
+
result2 = { code: 127, signal: null };
|
|
21060
|
+
spawnError = e.message;
|
|
21061
|
+
logger.warn("bang:spawn_failed", { error: spawnError });
|
|
21062
|
+
} finally {
|
|
21063
|
+
handedOff = false;
|
|
21064
|
+
stdout.write = origOut;
|
|
21065
|
+
stderr.write = origErr;
|
|
21066
|
+
if (stdin.isTTY) stdin.setRawMode(wasRaw);
|
|
21067
|
+
for (const l of readable) stdin.addListener("readable", l);
|
|
21068
|
+
origOut.call(stdout, "\n");
|
|
21069
|
+
terminalHandoff?.resume();
|
|
21070
|
+
}
|
|
21071
|
+
let output = spawnError ? `failed to start: ${spawnError}` : null;
|
|
21072
|
+
if (captured && logPath) {
|
|
21073
|
+
output = cleanTerminalOutput(await readFile22(logPath, "utf8").catch(() => ""));
|
|
21074
|
+
}
|
|
21075
|
+
if (logDir2) await rm2(logDir2, { recursive: true, force: true }).catch(() => {
|
|
21076
|
+
});
|
|
21077
|
+
return { command, exitCode: result2.code, signal: result2.signal, output };
|
|
21078
|
+
}
|
|
21079
|
+
var inkInstance, handedOff, terminalHandoff, MAX_CONTEXT_CHARS;
|
|
21080
|
+
var init_bang_command = __esm({
|
|
21081
|
+
"src/ui/bang-command.ts"() {
|
|
21082
|
+
"use strict";
|
|
21083
|
+
init_logger();
|
|
21084
|
+
inkInstance = null;
|
|
21085
|
+
handedOff = false;
|
|
21086
|
+
terminalHandoff = null;
|
|
21087
|
+
MAX_CONTEXT_CHARS = 16e3;
|
|
21088
|
+
}
|
|
21089
|
+
});
|
|
21090
|
+
|
|
21091
|
+
// src/remote/deploy-commute.ts
|
|
21092
|
+
import { spawn as spawn8 } from "child_process";
|
|
21093
|
+
import { mkdtemp as mkdtemp2, readFile as readFile23, writeFile as writeFile13, rm as rm3 } from "fs/promises";
|
|
21094
|
+
import { tmpdir as tmpdir4 } from "os";
|
|
21095
|
+
import { join as join34 } from "path";
|
|
21096
|
+
import { randomBytes as randomBytes3 } from "crypto";
|
|
19594
21097
|
async function cfApiFetch(accountId, apiToken, path, init) {
|
|
19595
21098
|
const url = `${CF_API}/accounts/${encodeURIComponent(accountId)}${path}`;
|
|
19596
21099
|
const res = await fetch(url, {
|
|
@@ -19602,65 +21105,65 @@ async function cfApiFetch(accountId, apiToken, path, init) {
|
|
|
19602
21105
|
...init?.headers
|
|
19603
21106
|
}
|
|
19604
21107
|
});
|
|
19605
|
-
const
|
|
19606
|
-
return
|
|
21108
|
+
const json3 = await res.json();
|
|
21109
|
+
return json3;
|
|
19607
21110
|
}
|
|
19608
21111
|
async function listDurableObjectNamespaces(accountId, apiToken) {
|
|
19609
|
-
const
|
|
21112
|
+
const json3 = await cfApiFetch(
|
|
19610
21113
|
accountId,
|
|
19611
21114
|
apiToken,
|
|
19612
21115
|
"/workers/durable_objects/namespaces"
|
|
19613
21116
|
);
|
|
19614
|
-
if (!
|
|
21117
|
+
if (!json3.success || !json3.result) {
|
|
19615
21118
|
throw new Error(
|
|
19616
|
-
|
|
21119
|
+
json3.errors?.map((e) => e.message).join(", ") ?? "Failed to list DO namespaces"
|
|
19617
21120
|
);
|
|
19618
21121
|
}
|
|
19619
|
-
return
|
|
21122
|
+
return json3.result;
|
|
19620
21123
|
}
|
|
19621
21124
|
async function deleteDurableObjectNamespace(accountId, apiToken, namespaceId) {
|
|
19622
|
-
const
|
|
21125
|
+
const json3 = await cfApiFetch(
|
|
19623
21126
|
accountId,
|
|
19624
21127
|
apiToken,
|
|
19625
21128
|
`/workers/durable_objects/namespaces/${encodeURIComponent(namespaceId)}`,
|
|
19626
21129
|
{ method: "DELETE" }
|
|
19627
21130
|
);
|
|
19628
|
-
if (!
|
|
21131
|
+
if (!json3.success) {
|
|
19629
21132
|
throw new Error(
|
|
19630
|
-
|
|
21133
|
+
json3.errors?.map((e) => e.message).join(", ") ?? "Failed to delete DO namespace"
|
|
19631
21134
|
);
|
|
19632
21135
|
}
|
|
19633
21136
|
}
|
|
19634
21137
|
async function listContainerApplications(accountId, apiToken) {
|
|
19635
|
-
const
|
|
21138
|
+
const json3 = await cfApiFetch(
|
|
19636
21139
|
accountId,
|
|
19637
21140
|
apiToken,
|
|
19638
21141
|
"/containers/applications"
|
|
19639
21142
|
);
|
|
19640
|
-
if (!
|
|
21143
|
+
if (!json3.success || !json3.result) {
|
|
19641
21144
|
return [];
|
|
19642
21145
|
}
|
|
19643
|
-
return
|
|
21146
|
+
return json3.result;
|
|
19644
21147
|
}
|
|
19645
21148
|
async function deleteContainerApplication(accountId, apiToken, appId) {
|
|
19646
|
-
const
|
|
21149
|
+
const json3 = await cfApiFetch(
|
|
19647
21150
|
accountId,
|
|
19648
21151
|
apiToken,
|
|
19649
21152
|
`/containers/applications/${encodeURIComponent(appId)}`,
|
|
19650
21153
|
{ method: "DELETE" }
|
|
19651
21154
|
);
|
|
19652
|
-
if (!
|
|
21155
|
+
if (!json3.success) {
|
|
19653
21156
|
throw new Error(
|
|
19654
|
-
|
|
21157
|
+
json3.errors?.map((e) => e.message).join(", ") ?? "Failed to delete container application"
|
|
19655
21158
|
);
|
|
19656
21159
|
}
|
|
19657
21160
|
}
|
|
19658
21161
|
function generateSecret2() {
|
|
19659
|
-
return
|
|
21162
|
+
return randomBytes3(32).toString("hex");
|
|
19660
21163
|
}
|
|
19661
21164
|
function runCmd(cmd, args, opts2 = {}) {
|
|
19662
|
-
return new Promise((
|
|
19663
|
-
const child =
|
|
21165
|
+
return new Promise((resolve15) => {
|
|
21166
|
+
const child = spawn8(cmd, args, {
|
|
19664
21167
|
cwd: opts2.cwd,
|
|
19665
21168
|
env: { ...process.env, ...opts2.env ?? {} },
|
|
19666
21169
|
stdio: [opts2.input ? "pipe" : "ignore", "pipe", "pipe"]
|
|
@@ -19680,11 +21183,11 @@ function runCmd(cmd, args, opts2 = {}) {
|
|
|
19680
21183
|
const timer3 = opts2.timeoutMs ? setTimeout(() => child.kill("SIGKILL"), opts2.timeoutMs) : null;
|
|
19681
21184
|
child.on("close", (code) => {
|
|
19682
21185
|
if (timer3) clearTimeout(timer3);
|
|
19683
|
-
|
|
21186
|
+
resolve15({ stdout, stderr, code: code ?? -1 });
|
|
19684
21187
|
});
|
|
19685
21188
|
child.on("error", (err) => {
|
|
19686
21189
|
if (timer3) clearTimeout(timer3);
|
|
19687
|
-
|
|
21190
|
+
resolve15({ stdout, stderr: stderr + String(err), code: -1 });
|
|
19688
21191
|
});
|
|
19689
21192
|
});
|
|
19690
21193
|
}
|
|
@@ -19820,8 +21323,8 @@ ${wranglerInstall.stderr.slice(-600)}`,
|
|
|
19820
21323
|
ok: true
|
|
19821
21324
|
};
|
|
19822
21325
|
yield { message: "Prerequisites ready", ok: true };
|
|
19823
|
-
const tmpRoot = await
|
|
19824
|
-
const repoDir =
|
|
21326
|
+
const tmpRoot = await mkdtemp2(join34(tmpdir4(), "kimiflare-commute-"));
|
|
21327
|
+
const repoDir = join34(tmpRoot, "kimiflare-commute");
|
|
19825
21328
|
yield { message: `Fetching worker source from GitHub (${COMMUTE_REPO})\u2026` };
|
|
19826
21329
|
const clone = await runCmd("git", ["clone", "--depth", "1", "--branch", COMMUTE_BRANCH, COMMUTE_REPO, repoDir], { timeoutMs: 6e4 });
|
|
19827
21330
|
if (clone.code !== 0) {
|
|
@@ -19830,8 +21333,8 @@ ${(clone.stderr || clone.stdout).slice(0, 400)}`, error: true };
|
|
|
19830
21333
|
throw new Error("clone failed");
|
|
19831
21334
|
}
|
|
19832
21335
|
yield { message: "Source fetched from GitHub", ok: true };
|
|
19833
|
-
const workerDir =
|
|
19834
|
-
const wranglerToml =
|
|
21336
|
+
const workerDir = join34(repoDir, "remote", "worker");
|
|
21337
|
+
const wranglerToml = join34(workerDir, "wrangler.toml");
|
|
19835
21338
|
yield { message: "Installing Worker dependencies (npm install)\u2026" };
|
|
19836
21339
|
const install = await runCmd("npm", ["install", "--no-audit", "--no-fund", "--loglevel=error"], {
|
|
19837
21340
|
cwd: workerDir,
|
|
@@ -19920,7 +21423,7 @@ ${(install.stderr || install.stdout).slice(-1200).trim()}`,
|
|
|
19920
21423
|
ok: true
|
|
19921
21424
|
};
|
|
19922
21425
|
yield { message: "Patching wrangler.toml\u2026" };
|
|
19923
|
-
let toml = await
|
|
21426
|
+
let toml = await readFile23(wranglerToml, "utf8");
|
|
19924
21427
|
toml = toml.replace(/^name\s*=\s*"[^"]+"/m, `name = "${workerName}"`);
|
|
19925
21428
|
toml = toml.replace(
|
|
19926
21429
|
/(\[\[kv_namespaces\]\][\s\S]*?binding\s*=\s*"OAUTH_KV"[\s\S]*?id\s*=\s*")[^"]+(")/,
|
|
@@ -20004,7 +21507,7 @@ invocation_logs = true
|
|
|
20004
21507
|
};
|
|
20005
21508
|
await saveConfig(next);
|
|
20006
21509
|
yield { message: "Saved to ~/.config/kimiflare/config.json", ok: true };
|
|
20007
|
-
await
|
|
21510
|
+
await rm3(tmpRoot, { recursive: true, force: true }).catch(() => {
|
|
20008
21511
|
});
|
|
20009
21512
|
yield { message: "Setup complete \u2014 multi-agent is ready to use.", done: true };
|
|
20010
21513
|
return { workerEndpoint: workerUrl, workerApiKey };
|
|
@@ -21258,7 +22761,7 @@ var init_text_input = __esm({
|
|
|
21258
22761
|
import { useState as useState5 } from "react";
|
|
21259
22762
|
import { Box as Box5, Text as Text5 } from "ink";
|
|
21260
22763
|
import SelectInput3 from "ink-select-input";
|
|
21261
|
-
import { spawn as
|
|
22764
|
+
import { spawn as spawn9 } from "child_process";
|
|
21262
22765
|
import { jsx as jsx6, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
21263
22766
|
function LspWizard({ servers, currentScope, hasProjectDir, onDone, onSave }) {
|
|
21264
22767
|
const theme = useTheme();
|
|
@@ -21272,7 +22775,7 @@ function LspWizard({ servers, currentScope, hasProjectDir, onDone, onSave }) {
|
|
|
21272
22775
|
const runInstall = (command) => {
|
|
21273
22776
|
setInstallState({ status: "running", output: "Installing..." });
|
|
21274
22777
|
const { shell, args } = getShellCommand();
|
|
21275
|
-
const child =
|
|
22778
|
+
const child = spawn9(shell, [...args, command], {
|
|
21276
22779
|
env: process.env
|
|
21277
22780
|
});
|
|
21278
22781
|
let stdout = "";
|
|
@@ -21780,7 +23283,7 @@ var init_lsp_wizard = __esm({
|
|
|
21780
23283
|
});
|
|
21781
23284
|
|
|
21782
23285
|
// src/hooks/recommended.ts
|
|
21783
|
-
import { platform as
|
|
23286
|
+
import { platform as platform5 } from "os";
|
|
21784
23287
|
function getRecommendedHook(id) {
|
|
21785
23288
|
return RECOMMENDED_HOOKS.find((r) => r.id === id);
|
|
21786
23289
|
}
|
|
@@ -21788,7 +23291,7 @@ var isMac, RECOMMENDED_HOOKS;
|
|
|
21788
23291
|
var init_recommended = __esm({
|
|
21789
23292
|
"src/hooks/recommended.ts"() {
|
|
21790
23293
|
"use strict";
|
|
21791
|
-
isMac =
|
|
23294
|
+
isMac = platform5() === "darwin";
|
|
21792
23295
|
RECOMMENDED_HOOKS = [
|
|
21793
23296
|
// ── Stop notifications ───────────────────────────────────────────────
|
|
21794
23297
|
{
|
|
@@ -22264,10 +23767,10 @@ __export(camouflage_view_exports, {
|
|
|
22264
23767
|
toolSummary: () => toolSummary,
|
|
22265
23768
|
validateCommandName: () => validateCommandName
|
|
22266
23769
|
});
|
|
22267
|
-
import { execFileSync as execFileSync3, spawn as
|
|
23770
|
+
import { execFileSync as execFileSync3, spawn as spawn10 } from "child_process";
|
|
22268
23771
|
import { readdir as readdir6 } from "fs/promises";
|
|
22269
|
-
import { homedir as
|
|
22270
|
-
import { join as
|
|
23772
|
+
import { homedir as homedir23 } from "os";
|
|
23773
|
+
import { join as join35, resolve as resolve12 } from "path";
|
|
22271
23774
|
async function loadSdk() {
|
|
22272
23775
|
const sdk = await import("camouflage-tui").catch((err) => {
|
|
22273
23776
|
throw new CamouflageUnavailable(`camouflage-tui is not installed (${err.message})`);
|
|
@@ -22308,6 +23811,10 @@ async function runCamouflageView(opts2) {
|
|
|
22308
23811
|
cam.send("MentionCandidatesRegistered", { candidates: files.map((token) => ({ token, kind: "file" })) });
|
|
22309
23812
|
}
|
|
22310
23813
|
const view = new View(cam, sdk);
|
|
23814
|
+
registerTerminalHandoff({
|
|
23815
|
+
suspend: async () => sdk.suspendTerminal ? (await sdk.suspendTerminal(cam)).supported : false,
|
|
23816
|
+
resume: () => sdk.resumeTerminal?.(cam)
|
|
23817
|
+
});
|
|
22311
23818
|
cam.on("userInput", (text) => {
|
|
22312
23819
|
view.echoed.push(text.trim());
|
|
22313
23820
|
view.actions?.submit(text);
|
|
@@ -22328,6 +23835,7 @@ async function runCamouflageView(opts2) {
|
|
|
22328
23835
|
const finish = async () => {
|
|
22329
23836
|
if (closing) return;
|
|
22330
23837
|
closing = true;
|
|
23838
|
+
registerTerminalHandoff(null);
|
|
22331
23839
|
await cam.close().catch(() => void 0);
|
|
22332
23840
|
restoreConsole();
|
|
22333
23841
|
};
|
|
@@ -22345,14 +23853,14 @@ async function runCamouflageView(opts2) {
|
|
|
22345
23853
|
}
|
|
22346
23854
|
}
|
|
22347
23855
|
function runShell(command) {
|
|
22348
|
-
return new Promise((
|
|
23856
|
+
return new Promise((resolve15) => {
|
|
22349
23857
|
const { shell, args } = getShellCommand();
|
|
22350
|
-
const child =
|
|
23858
|
+
const child = spawn10(shell, [...args, command], { env: process.env });
|
|
22351
23859
|
let out = "";
|
|
22352
23860
|
child.stdout?.on("data", (c) => out += c.toString("utf8"));
|
|
22353
23861
|
child.stderr?.on("data", (c) => out += c.toString("utf8"));
|
|
22354
|
-
child.on("close", (code) =>
|
|
22355
|
-
child.on("error", (err) =>
|
|
23862
|
+
child.on("close", (code) => resolve15({ ok: code === 0, output: out || (code === 0 ? "" : `Exit code: ${code}`) }));
|
|
23863
|
+
child.on("error", (err) => resolve15({ ok: false, output: err.message }));
|
|
22356
23864
|
});
|
|
22357
23865
|
}
|
|
22358
23866
|
function validateCommandName(name, existing) {
|
|
@@ -22403,8 +23911,8 @@ function truncateText(s, max) {
|
|
|
22403
23911
|
async function listMentionDir(cwd, query) {
|
|
22404
23912
|
const exact = query === "~" || query === "." || query === ".." || query.endsWith("/..");
|
|
22405
23913
|
const dir = exact ? `${query}/` : query.slice(0, query.lastIndexOf("/") + 1);
|
|
22406
|
-
const expanded = dir.startsWith("~") ?
|
|
22407
|
-
const target =
|
|
23914
|
+
const expanded = dir.startsWith("~") ? join35(homedir23(), dir.slice(1)) : dir;
|
|
23915
|
+
const target = resolve12(cwd, expanded || ".");
|
|
22408
23916
|
try {
|
|
22409
23917
|
const items = await readdir6(target, { withFileTypes: true });
|
|
22410
23918
|
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 +23993,7 @@ function shortModel(id) {
|
|
|
22485
23993
|
return id.includes("/") ? id.slice(id.indexOf("/") + 1) : id;
|
|
22486
23994
|
}
|
|
22487
23995
|
function shortPath(p) {
|
|
22488
|
-
const home =
|
|
23996
|
+
const home = homedir23();
|
|
22489
23997
|
return p === home ? "~" : p.startsWith(home + "/") ? `~${p.slice(home.length)}` : p;
|
|
22490
23998
|
}
|
|
22491
23999
|
function gitBranch(cwd) {
|
|
@@ -22535,6 +24043,7 @@ var init_camouflage_view = __esm({
|
|
|
22535
24043
|
init_model_picker();
|
|
22536
24044
|
init_help_menu();
|
|
22537
24045
|
init_app_helpers();
|
|
24046
|
+
init_bang_command();
|
|
22538
24047
|
init_deploy_commute();
|
|
22539
24048
|
init_remote_dashboard();
|
|
22540
24049
|
init_worker_client();
|
|
@@ -25114,7 +26623,7 @@ var init_status = __esm({
|
|
|
25114
26623
|
// src/ui/permission.tsx
|
|
25115
26624
|
import { useState as useState9, useCallback } from "react";
|
|
25116
26625
|
import { Box as Box13, Text as Text14, useInput as useInput6 } from "ink";
|
|
25117
|
-
import { platform as
|
|
26626
|
+
import { platform as platform6 } from "os";
|
|
25118
26627
|
import { jsx as jsx15, jsxs as jsxs12 } from "react/jsx-runtime";
|
|
25119
26628
|
function formatSelection(label, shortcut) {
|
|
25120
26629
|
return `${label} [${MOD_KEY}+${shortcut}]`;
|
|
@@ -25278,7 +26787,7 @@ var init_permission = __esm({
|
|
|
25278
26787
|
{ value: { decision: "deny", scope: "once" }, label: "Something else", key: 3 }
|
|
25279
26788
|
];
|
|
25280
26789
|
DENY = { decision: "deny", scope: "once" };
|
|
25281
|
-
MOD_KEY =
|
|
26790
|
+
MOD_KEY = platform6() === "darwin" ? "\u2325" : "Alt";
|
|
25282
26791
|
}
|
|
25283
26792
|
});
|
|
25284
26793
|
|
|
@@ -25305,19 +26814,19 @@ function usePermissionController(getMode, onPlanModeBlocked) {
|
|
|
25305
26814
|
const onPlanModeBlockedRef = useRef3(onPlanModeBlocked);
|
|
25306
26815
|
onPlanModeBlockedRef.current = onPlanModeBlocked;
|
|
25307
26816
|
const askPermission = useCallback2(
|
|
25308
|
-
(req, askOpts) => new Promise((
|
|
26817
|
+
(req, askOpts) => new Promise((resolve15) => {
|
|
25309
26818
|
const outcome = decidePermission(req, getModeRef.current(), askOpts);
|
|
25310
26819
|
if (outcome.kind === "resolve") {
|
|
25311
|
-
|
|
26820
|
+
resolve15(outcome.decision);
|
|
25312
26821
|
return;
|
|
25313
26822
|
}
|
|
25314
26823
|
if (outcome.kind === "plan_blocked") {
|
|
25315
26824
|
onPlanModeBlockedRef.current(outcome.toolName);
|
|
25316
|
-
|
|
26825
|
+
resolve15(AUTO_DENY);
|
|
25317
26826
|
return;
|
|
25318
26827
|
}
|
|
25319
|
-
resolveRef.current =
|
|
25320
|
-
setPending({ tool: req.tool, args: req.args, resolve:
|
|
26828
|
+
resolveRef.current = resolve15;
|
|
26829
|
+
setPending({ tool: req.tool, args: req.args, resolve: resolve15 });
|
|
25321
26830
|
}),
|
|
25322
26831
|
[]
|
|
25323
26832
|
);
|
|
@@ -25956,15 +27465,15 @@ __export(openrouter_oauth_exports, {
|
|
|
25956
27465
|
pkceChallenge: () => pkceChallenge,
|
|
25957
27466
|
startLoopbackListener: () => startLoopbackListener
|
|
25958
27467
|
});
|
|
25959
|
-
import { createHash as
|
|
27468
|
+
import { createHash as createHash5, randomBytes as randomBytes4 } from "crypto";
|
|
25960
27469
|
import { createServer as createServer2 } from "http";
|
|
25961
27470
|
import { hostname } from "os";
|
|
25962
27471
|
function createPkce() {
|
|
25963
|
-
const verifier =
|
|
27472
|
+
const verifier = randomBytes4(32).toString("base64url");
|
|
25964
27473
|
return { verifier, challenge: pkceChallenge(verifier) };
|
|
25965
27474
|
}
|
|
25966
27475
|
function pkceChallenge(verifier) {
|
|
25967
|
-
return
|
|
27476
|
+
return createHash5("sha256").update(verifier).digest("base64url");
|
|
25968
27477
|
}
|
|
25969
27478
|
function openRouterAuthPageUrl() {
|
|
25970
27479
|
return (process.env.OPENROUTER_AUTH_URL?.trim() || "https://openrouter.ai/auth").replace(/\/+$/, "");
|
|
@@ -26047,9 +27556,9 @@ async function startLoopbackListener(opts2 = {}) {
|
|
|
26047
27556
|
rejectCode(e);
|
|
26048
27557
|
finish();
|
|
26049
27558
|
}
|
|
26050
|
-
await new Promise((
|
|
27559
|
+
await new Promise((resolve15, reject) => {
|
|
26051
27560
|
server.once("error", reject);
|
|
26052
|
-
server.listen(0, "127.0.0.1", () =>
|
|
27561
|
+
server.listen(0, "127.0.0.1", () => resolve15());
|
|
26053
27562
|
});
|
|
26054
27563
|
const address = server.address();
|
|
26055
27564
|
const port = typeof address === "object" && address ? address.port : 0;
|
|
@@ -26475,15 +27984,15 @@ var init_frontmatter = __esm({
|
|
|
26475
27984
|
});
|
|
26476
27985
|
|
|
26477
27986
|
// src/commands/loader.ts
|
|
26478
|
-
import { open, realpath as
|
|
26479
|
-
import { homedir as
|
|
26480
|
-
import { join as
|
|
27987
|
+
import { open, realpath as realpath6 } from "fs/promises";
|
|
27988
|
+
import { homedir as homedir24 } from "os";
|
|
27989
|
+
import { join as join36, relative as relative10, sep as sep6 } from "path";
|
|
26481
27990
|
function projectCommandsDir(cwd = process.cwd()) {
|
|
26482
|
-
return
|
|
27991
|
+
return join36(cwd, ".kimiflare", "commands");
|
|
26483
27992
|
}
|
|
26484
27993
|
function globalCommandsDir() {
|
|
26485
|
-
const xdg = process.env.XDG_CONFIG_HOME ||
|
|
26486
|
-
return
|
|
27994
|
+
const xdg = process.env.XDG_CONFIG_HOME || join36(homedir24(), ".config");
|
|
27995
|
+
return join36(xdg, "kimiflare", "commands");
|
|
26487
27996
|
}
|
|
26488
27997
|
async function loadCustomCommands(cwd = process.cwd()) {
|
|
26489
27998
|
const warnings = [];
|
|
@@ -26522,7 +28031,7 @@ async function loadCustomCommands(cwd = process.cwd()) {
|
|
|
26522
28031
|
async function resolveSafeDir(dir, source, cwd, warnings) {
|
|
26523
28032
|
let realDir;
|
|
26524
28033
|
try {
|
|
26525
|
-
realDir = await
|
|
28034
|
+
realDir = await realpath6(dir);
|
|
26526
28035
|
} catch (err) {
|
|
26527
28036
|
const code = err.code;
|
|
26528
28037
|
if (code !== "ENOENT" && code !== "ENOTDIR") {
|
|
@@ -26533,11 +28042,11 @@ async function resolveSafeDir(dir, source, cwd, warnings) {
|
|
|
26533
28042
|
if (source === "project") {
|
|
26534
28043
|
let realCwd;
|
|
26535
28044
|
try {
|
|
26536
|
-
realCwd = await
|
|
28045
|
+
realCwd = await realpath6(cwd);
|
|
26537
28046
|
} catch {
|
|
26538
28047
|
return null;
|
|
26539
28048
|
}
|
|
26540
|
-
const rel =
|
|
28049
|
+
const rel = relative10(realCwd, realDir);
|
|
26541
28050
|
if (rel !== "" && isPathOutside(rel)) {
|
|
26542
28051
|
warnings.push(`commands dir ${dir} escapes workspace via symlink \u2014 skipped`);
|
|
26543
28052
|
return null;
|
|
@@ -26612,10 +28121,10 @@ async function loadOne(file, rootDir, source, warnings) {
|
|
|
26612
28121
|
return cmd;
|
|
26613
28122
|
}
|
|
26614
28123
|
function filenameToCommandName(file, rootDir) {
|
|
26615
|
-
const rel =
|
|
28124
|
+
const rel = relative10(rootDir, file);
|
|
26616
28125
|
if (!rel || isPathOutside(rel)) return null;
|
|
26617
28126
|
const noExt = rel.replace(/\.md$/i, "");
|
|
26618
|
-
const parts = noExt.split(
|
|
28127
|
+
const parts = noExt.split(sep6).filter((p) => p.length > 0);
|
|
26619
28128
|
if (parts.length === 0) return null;
|
|
26620
28129
|
if (parts.some((p) => !/^[\w.-]+$/.test(p))) return null;
|
|
26621
28130
|
return parts.join("/");
|
|
@@ -26635,8 +28144,8 @@ var init_loader = __esm({
|
|
|
26635
28144
|
|
|
26636
28145
|
// src/commands/renderer.ts
|
|
26637
28146
|
import { exec } from "child_process";
|
|
26638
|
-
import { open as open2, realpath as
|
|
26639
|
-
import { isAbsolute as
|
|
28147
|
+
import { open as open2, realpath as realpath7 } from "fs/promises";
|
|
28148
|
+
import { isAbsolute as isAbsolute6, relative as relative11, resolve as resolvePathJoin, basename as pathBasename } from "path";
|
|
26640
28149
|
import { promisify as promisify3 } from "util";
|
|
26641
28150
|
function tokenizeArgs(s) {
|
|
26642
28151
|
return [...s.matchAll(ARG_TOKEN_RE)].map((match) => {
|
|
@@ -26727,7 +28236,7 @@ async function replaceFiles(prompt, warnings, cmd, cwd, maxFileBytes) {
|
|
|
26727
28236
|
return "" + trailing;
|
|
26728
28237
|
});
|
|
26729
28238
|
}
|
|
26730
|
-
const realCwd = await
|
|
28239
|
+
const realCwd = await realpath7(cwd).catch(() => cwd);
|
|
26731
28240
|
const replacements = await Promise.all(
|
|
26732
28241
|
matches.map(async (match) => {
|
|
26733
28242
|
const rawPath = match[1] ?? "";
|
|
@@ -26736,23 +28245,23 @@ async function replaceFiles(prompt, warnings, cmd, cwd, maxFileBytes) {
|
|
|
26736
28245
|
warnings.push(`file inclusion blocked: @${rawPath} \u2014 secret file pattern`);
|
|
26737
28246
|
return "";
|
|
26738
28247
|
}
|
|
26739
|
-
if (
|
|
28248
|
+
if (isAbsolute6(rawPath) || rawPath.startsWith("~")) {
|
|
26740
28249
|
warnings.push(`file inclusion skipped: @${rawPath} \u2014 outside workspace`);
|
|
26741
28250
|
return "";
|
|
26742
28251
|
}
|
|
26743
28252
|
const resolved = resolvePathJoin(cwd, rawPath);
|
|
26744
|
-
if (isPathOutside(
|
|
28253
|
+
if (isPathOutside(relative11(cwd, resolved))) {
|
|
26745
28254
|
warnings.push(`file inclusion skipped: @${rawPath} \u2014 outside workspace`);
|
|
26746
28255
|
return "";
|
|
26747
28256
|
}
|
|
26748
28257
|
let real;
|
|
26749
28258
|
try {
|
|
26750
|
-
real = await
|
|
28259
|
+
real = await realpath7(resolved);
|
|
26751
28260
|
} catch (error) {
|
|
26752
28261
|
warnings.push(`file inclusion failed: @${rawPath} \u2014 ${message(error)}`);
|
|
26753
28262
|
return "";
|
|
26754
28263
|
}
|
|
26755
|
-
if (isPathOutside(
|
|
28264
|
+
if (isPathOutside(relative11(realCwd, real))) {
|
|
26756
28265
|
warnings.push(`file inclusion skipped: @${rawPath} \u2014 symlink escapes workspace`);
|
|
26757
28266
|
return "";
|
|
26758
28267
|
}
|
|
@@ -27547,8 +29056,8 @@ var init_one_dark = __esm({
|
|
|
27547
29056
|
});
|
|
27548
29057
|
|
|
27549
29058
|
// src/ui/theme.ts
|
|
27550
|
-
function normalizeTheme(
|
|
27551
|
-
const obj =
|
|
29059
|
+
function normalizeTheme(json3) {
|
|
29060
|
+
const obj = json3;
|
|
27552
29061
|
const palette = obj.palette;
|
|
27553
29062
|
const normalizeDim = (v) => {
|
|
27554
29063
|
if (v === void 0) return void 0;
|
|
@@ -27691,11 +29200,11 @@ var init_wcag = __esm({
|
|
|
27691
29200
|
});
|
|
27692
29201
|
|
|
27693
29202
|
// src/ui/theme-loader.ts
|
|
27694
|
-
import { readFile as
|
|
27695
|
-
import { join as
|
|
27696
|
-
import { homedir as
|
|
29203
|
+
import { readFile as readFile24, readdir as readdir7 } from "fs/promises";
|
|
29204
|
+
import { join as join37 } from "path";
|
|
29205
|
+
import { homedir as homedir25 } from "os";
|
|
27697
29206
|
function projectThemesDir(cwd = process.cwd()) {
|
|
27698
|
-
return
|
|
29207
|
+
return join37(cwd, ".kimiflare", "themes");
|
|
27699
29208
|
}
|
|
27700
29209
|
function isHexColor(c) {
|
|
27701
29210
|
return /^#[0-9a-fA-F]{6}$/.test(c);
|
|
@@ -27784,26 +29293,26 @@ async function loadThemesFromDir(dir, source) {
|
|
|
27784
29293
|
return { themes, errors };
|
|
27785
29294
|
}
|
|
27786
29295
|
for (const file of files.filter((f) => f.endsWith(".json"))) {
|
|
27787
|
-
const path =
|
|
29296
|
+
const path = join37(dir, file);
|
|
27788
29297
|
let raw;
|
|
27789
29298
|
try {
|
|
27790
|
-
raw = await
|
|
29299
|
+
raw = await readFile24(path, "utf-8");
|
|
27791
29300
|
} catch (e) {
|
|
27792
29301
|
errors.push(`${path}: ${e instanceof Error ? e.message : String(e)}`);
|
|
27793
29302
|
continue;
|
|
27794
29303
|
}
|
|
27795
|
-
let
|
|
29304
|
+
let json3;
|
|
27796
29305
|
try {
|
|
27797
|
-
|
|
29306
|
+
json3 = JSON.parse(raw);
|
|
27798
29307
|
} catch (e) {
|
|
27799
29308
|
errors.push(`${path}: invalid JSON \u2014 ${e instanceof Error ? e.message : String(e)}`);
|
|
27800
29309
|
continue;
|
|
27801
29310
|
}
|
|
27802
|
-
if (!
|
|
29311
|
+
if (!json3 || typeof json3 !== "object") {
|
|
27803
29312
|
errors.push(`${path}: root must be an object`);
|
|
27804
29313
|
continue;
|
|
27805
29314
|
}
|
|
27806
|
-
const obj =
|
|
29315
|
+
const obj = json3;
|
|
27807
29316
|
const fileErrors = [];
|
|
27808
29317
|
if (typeof obj.name !== "string" || obj.name.length === 0) {
|
|
27809
29318
|
fileErrors.push("name is required");
|
|
@@ -27933,8 +29442,8 @@ var init_theme_loader = __esm({
|
|
|
27933
29442
|
"use strict";
|
|
27934
29443
|
init_wcag();
|
|
27935
29444
|
init_theme();
|
|
27936
|
-
USER_THEMES_DIR =
|
|
27937
|
-
process.env.XDG_CONFIG_HOME ||
|
|
29445
|
+
USER_THEMES_DIR = join37(
|
|
29446
|
+
process.env.XDG_CONFIG_HOME || join37(homedir25(), ".config"),
|
|
27938
29447
|
"kimiflare",
|
|
27939
29448
|
"themes"
|
|
27940
29449
|
);
|
|
@@ -31053,8 +32562,8 @@ var init_input_handlers = __esm({
|
|
|
31053
32562
|
|
|
31054
32563
|
// src/agent/jev-context.ts
|
|
31055
32564
|
import { execFileSync as execFileSync4 } from "child_process";
|
|
31056
|
-
import { readFile as
|
|
31057
|
-
import { basename as basename6, join as
|
|
32565
|
+
import { readFile as readFile25 } from "fs/promises";
|
|
32566
|
+
import { basename as basename6, join as join38, resolve as resolve13 } from "path";
|
|
31058
32567
|
function messageText(message2) {
|
|
31059
32568
|
if (typeof message2.content === "string") return message2.content.trim();
|
|
31060
32569
|
if (!Array.isArray(message2.content)) return "";
|
|
@@ -31071,12 +32580,12 @@ function projectRoot(cwd) {
|
|
|
31071
32580
|
timeout: 1e3
|
|
31072
32581
|
}).trim();
|
|
31073
32582
|
} catch {
|
|
31074
|
-
return
|
|
32583
|
+
return resolve13(cwd);
|
|
31075
32584
|
}
|
|
31076
32585
|
}
|
|
31077
32586
|
async function readPackageMetadata(root) {
|
|
31078
32587
|
try {
|
|
31079
|
-
const parsed = JSON.parse(await
|
|
32588
|
+
const parsed = JSON.parse(await readFile25(join38(root, "package.json"), "utf8"));
|
|
31080
32589
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return {};
|
|
31081
32590
|
const pkg = parsed;
|
|
31082
32591
|
const metadata = {};
|
|
@@ -31093,7 +32602,7 @@ async function readPackageMetadata(root) {
|
|
|
31093
32602
|
async function readLicenseEvidence(root) {
|
|
31094
32603
|
for (const name of ["LICENSE", "LICENSE.md", "LICENSE.txt", "LICENCE", "COPYING"]) {
|
|
31095
32604
|
try {
|
|
31096
|
-
const content = (await
|
|
32605
|
+
const content = (await readFile25(join38(root, name), "utf8")).trim();
|
|
31097
32606
|
if (content) return { source: `project license (${name})`, content: content.slice(0, 700) };
|
|
31098
32607
|
} catch {
|
|
31099
32608
|
}
|
|
@@ -31102,7 +32611,7 @@ async function readLicenseEvidence(root) {
|
|
|
31102
32611
|
}
|
|
31103
32612
|
async function buildJevContext(question, messages, cwd = process.cwd()) {
|
|
31104
32613
|
const entries = [];
|
|
31105
|
-
let remaining =
|
|
32614
|
+
let remaining = MAX_CONTEXT_CHARS2;
|
|
31106
32615
|
const add = (source, content, limit = remaining) => {
|
|
31107
32616
|
if (remaining <= 0 || !content.trim()) return;
|
|
31108
32617
|
const sourceText = source.slice(0, 160);
|
|
@@ -31126,7 +32635,7 @@ async function buildJevContext(question, messages, cwd = process.cwd()) {
|
|
|
31126
32635
|
conversationRemaining -= source.length + content.length + 2;
|
|
31127
32636
|
}
|
|
31128
32637
|
}
|
|
31129
|
-
const currentDirectory = basename6(
|
|
32638
|
+
const currentDirectory = basename6(resolve13(cwd));
|
|
31130
32639
|
const escapedDirectory = currentDirectory.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
31131
32640
|
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
32641
|
if (CURRENT_PROJECT_REFERENCE.test(question) || namesCurrentDirectory) {
|
|
@@ -31169,11 +32678,11 @@ function formatJevContextReceipt(entries, includeContent = false) {
|
|
|
31169
32678
|
"Chat/project context is reference data, not verified fact."
|
|
31170
32679
|
].join("\n");
|
|
31171
32680
|
}
|
|
31172
|
-
var
|
|
32681
|
+
var MAX_CONTEXT_CHARS2, MAX_CONVERSATION_CHARS, CURRENT_PROJECT_REFERENCE, LICENSE_QUESTION, CONVERSATION_REFERENCE, SECRET_PATTERNS3;
|
|
31173
32682
|
var init_jev_context = __esm({
|
|
31174
32683
|
"src/agent/jev-context.ts"() {
|
|
31175
32684
|
"use strict";
|
|
31176
|
-
|
|
32685
|
+
MAX_CONTEXT_CHARS2 = 6e3;
|
|
31177
32686
|
MAX_CONVERSATION_CHARS = 4e3;
|
|
31178
32687
|
CURRENT_PROJECT_REFERENCE = /\b(?:this|that|our|current|local|working)\s+(?:repo(?:sitory)?|project|workspace|codebase|harness)\b/i;
|
|
31179
32688
|
LICENSE_QUESTION = /\bopen[\s-]?source\b|\blicen[cs](?:e|ed|ing)?\b|\bspdx\b|\bcopyleft\b/i;
|
|
@@ -31349,8 +32858,8 @@ var init_frontmatter2 = __esm({
|
|
|
31349
32858
|
});
|
|
31350
32859
|
|
|
31351
32860
|
// src/skills/loader.ts
|
|
31352
|
-
import { readFile as
|
|
31353
|
-
import { join as
|
|
32861
|
+
import { readFile as readFile26, readdir as readdir8, stat as stat8 } from "fs/promises";
|
|
32862
|
+
import { join as join39, extname } from "path";
|
|
31354
32863
|
function normalizeManifest(raw, filePath) {
|
|
31355
32864
|
const name = typeof raw.name === "string" ? raw.name : "";
|
|
31356
32865
|
const description = typeof raw.description === "string" ? raw.description : "";
|
|
@@ -31364,7 +32873,7 @@ function normalizeManifest(raw, filePath) {
|
|
|
31364
32873
|
return { name, description, match, scope, priority, enabled: enabled2 };
|
|
31365
32874
|
}
|
|
31366
32875
|
async function loadSkillFile(filePath) {
|
|
31367
|
-
const raw = await
|
|
32876
|
+
const raw = await readFile26(filePath, "utf-8");
|
|
31368
32877
|
const parsed = parseFrontmatter2(raw);
|
|
31369
32878
|
const manifest = normalizeManifest(parsed.data, filePath);
|
|
31370
32879
|
const body = parsed.content.trim();
|
|
@@ -31386,8 +32895,8 @@ async function loadSkillsFromDir(dirPath) {
|
|
|
31386
32895
|
const entries = await readdir8(dirPath);
|
|
31387
32896
|
const files = [];
|
|
31388
32897
|
for (const entry of entries) {
|
|
31389
|
-
const full =
|
|
31390
|
-
const s = await
|
|
32898
|
+
const full = join39(dirPath, entry);
|
|
32899
|
+
const s = await stat8(full);
|
|
31391
32900
|
if (s.isFile() && extname(entry) === ".md") {
|
|
31392
32901
|
files.push(full);
|
|
31393
32902
|
}
|
|
@@ -31414,12 +32923,12 @@ var init_loader2 = __esm({
|
|
|
31414
32923
|
});
|
|
31415
32924
|
|
|
31416
32925
|
// src/skills/manager.ts
|
|
31417
|
-
import { mkdir as mkdir13, writeFile as writeFile14, unlink as
|
|
31418
|
-
import { join as
|
|
32926
|
+
import { mkdir as mkdir13, writeFile as writeFile14, unlink as unlink4, readFile as readFile27 } from "fs/promises";
|
|
32927
|
+
import { join as join40 } from "path";
|
|
31419
32928
|
function getSkillDirs(cwd) {
|
|
31420
32929
|
return {
|
|
31421
|
-
projectDir:
|
|
31422
|
-
globalDir:
|
|
32930
|
+
projectDir: join40(cwd, ".kimiflare", "skills"),
|
|
32931
|
+
globalDir: join40(process.env.HOME ?? "", ".config", "kimiflare", "skills")
|
|
31423
32932
|
};
|
|
31424
32933
|
}
|
|
31425
32934
|
async function listAllSkills(cwd) {
|
|
@@ -31433,7 +32942,7 @@ async function listAllSkills(cwd) {
|
|
|
31433
32942
|
async function createSkill(opts2) {
|
|
31434
32943
|
const dirs = getSkillDirs(opts2.cwd);
|
|
31435
32944
|
const dir = opts2.scope === "project" ? dirs.projectDir : dirs.globalDir;
|
|
31436
|
-
const filepath =
|
|
32945
|
+
const filepath = join40(dir, `${opts2.name}.md`);
|
|
31437
32946
|
const frontmatter = {
|
|
31438
32947
|
name: opts2.name,
|
|
31439
32948
|
enabled: true,
|
|
@@ -31462,14 +32971,14 @@ async function deleteSkill(name, cwd) {
|
|
|
31462
32971
|
const all = await listAllSkills(cwd);
|
|
31463
32972
|
const skill = all.project.find((s) => s.name === name) ?? all.global.find((s) => s.name === name);
|
|
31464
32973
|
if (!skill) throw new Error(`skill "${name}" not found`);
|
|
31465
|
-
await
|
|
32974
|
+
await unlink4(skill.filePath);
|
|
31466
32975
|
return { filepath: skill.filePath };
|
|
31467
32976
|
}
|
|
31468
32977
|
async function setSkillEnabled(name, enabled2, cwd) {
|
|
31469
32978
|
const all = await listAllSkills(cwd);
|
|
31470
32979
|
const skill = all.project.find((s) => s.name === name) ?? all.global.find((s) => s.name === name);
|
|
31471
32980
|
if (!skill) throw new Error(`skill "${name}" not found`);
|
|
31472
|
-
const raw = await
|
|
32981
|
+
const raw = await readFile27(skill.filePath, "utf-8");
|
|
31473
32982
|
const parsed = parseFrontmatter2(raw);
|
|
31474
32983
|
parsed.data.enabled = enabled2;
|
|
31475
32984
|
const yaml = Object.entries(parsed.data).map(([k, v]) => {
|
|
@@ -31525,9 +33034,9 @@ var init_plan_resolver = __esm({
|
|
|
31525
33034
|
|
|
31526
33035
|
// src/util/clipboard.ts
|
|
31527
33036
|
import { execSync as execSync5 } from "child_process";
|
|
31528
|
-
import { platform as
|
|
33037
|
+
import { platform as platform7 } from "os";
|
|
31529
33038
|
function writeToClipboard(text) {
|
|
31530
|
-
const os2 =
|
|
33039
|
+
const os2 = platform7();
|
|
31531
33040
|
try {
|
|
31532
33041
|
if (os2 === "darwin") {
|
|
31533
33042
|
execSync5("pbcopy", { input: text, timeout: 5e3 });
|
|
@@ -31562,15 +33071,15 @@ var tui_report_exports = {};
|
|
|
31562
33071
|
__export(tui_report_exports, {
|
|
31563
33072
|
getCategoryReportText: () => getCategoryReportText
|
|
31564
33073
|
});
|
|
31565
|
-
import { readFile as
|
|
31566
|
-
import { join as
|
|
31567
|
-
import { homedir as
|
|
33074
|
+
import { readFile as readFile28 } from "fs/promises";
|
|
33075
|
+
import { join as join41 } from "path";
|
|
33076
|
+
import { homedir as homedir26 } from "os";
|
|
31568
33077
|
function usageDir3() {
|
|
31569
|
-
const xdg = process.env.XDG_DATA_HOME ||
|
|
31570
|
-
return
|
|
33078
|
+
const xdg = process.env.XDG_DATA_HOME || join41(homedir26(), ".local", "share");
|
|
33079
|
+
return join41(xdg, "kimiflare");
|
|
31571
33080
|
}
|
|
31572
33081
|
function usagePath4() {
|
|
31573
|
-
return
|
|
33082
|
+
return join41(usageDir3(), "usage.json");
|
|
31574
33083
|
}
|
|
31575
33084
|
function today3() {
|
|
31576
33085
|
return (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
@@ -31582,7 +33091,7 @@ function daysAgo2(n) {
|
|
|
31582
33091
|
}
|
|
31583
33092
|
async function loadLog3() {
|
|
31584
33093
|
try {
|
|
31585
|
-
const raw = await
|
|
33094
|
+
const raw = await readFile28(usagePath4(), "utf8");
|
|
31586
33095
|
return JSON.parse(raw);
|
|
31587
33096
|
} catch {
|
|
31588
33097
|
return { version: 1, days: [], sessions: [] };
|
|
@@ -31639,7 +33148,7 @@ var init_tui_report = __esm({
|
|
|
31639
33148
|
});
|
|
31640
33149
|
|
|
31641
33150
|
// src/ui/slash-commands.ts
|
|
31642
|
-
import { join as
|
|
33151
|
+
import { join as join42 } from "path";
|
|
31643
33152
|
import QRCode from "qrcode";
|
|
31644
33153
|
function executeFreshStart(ctx, planText, overrideMode, opts2 = {}) {
|
|
31645
33154
|
const oldSessionId = ctx.sessionIdRef.current;
|
|
@@ -32040,7 +33549,7 @@ auto (default) lets the coordinator assess substantial tasks and delegate indepe
|
|
|
32040
33549
|
const context = noContext ? [] : await buildJevContext(question.prompt, ctx.messagesRef.current, process.cwd());
|
|
32041
33550
|
const receipt = formatJevContextReceipt(context, showContext);
|
|
32042
33551
|
setResult(eventKey, { kind: "info", key: eventKey, text: `Jev is evaluating \xB7 ${receipt}` });
|
|
32043
|
-
await new Promise((
|
|
33552
|
+
await new Promise((resolve15) => setTimeout(resolve15, 0));
|
|
32044
33553
|
const answer = await askJev(apiKey, question, { context });
|
|
32045
33554
|
const presentation = presentJevAnswer(question, answer);
|
|
32046
33555
|
setResult(eventKey, {
|
|
@@ -32563,7 +34072,7 @@ ${lines.join("\n")}` }]);
|
|
|
32563
34072
|
void (async () => {
|
|
32564
34073
|
try {
|
|
32565
34074
|
const { sessionsDir: sessionsDir3 } = await Promise.resolve().then(() => (init_sessions(), sessions_exports));
|
|
32566
|
-
const file = await loadSession(
|
|
34075
|
+
const file = await loadSession(join42(sessionsDir3(), `${currentId}.json`));
|
|
32567
34076
|
const cps = file.checkpoints ?? [];
|
|
32568
34077
|
if (cps.length === 0) {
|
|
32569
34078
|
setEvents((e) => [...e, { kind: "info", key: mkKey2(), text: "no checkpoints in this session" }]);
|
|
@@ -32608,7 +34117,7 @@ ${lines.join("\n")}` }]);
|
|
|
32608
34117
|
try {
|
|
32609
34118
|
ctx.ensureSessionId();
|
|
32610
34119
|
const { sessionsDir: sessionsDir3 } = await Promise.resolve().then(() => (init_sessions(), sessions_exports));
|
|
32611
|
-
const filePath =
|
|
34120
|
+
const filePath = join42(sessionsDir3(), `${ctx.sessionIdRef.current}.json`);
|
|
32612
34121
|
await addCheckpoint(filePath, cp);
|
|
32613
34122
|
setEvents((e) => [...e, { kind: "info", key: mkKey2(), text: `checkpoint saved: "${label}"` }]);
|
|
32614
34123
|
} catch (e) {
|
|
@@ -33240,12 +34749,12 @@ project: ${projectSettingsPath(cwd)}`
|
|
|
33240
34749
|
|
|
33241
34750
|
// src/init/context-generator.ts
|
|
33242
34751
|
import { existsSync as existsSync6, statSync as statSync6 } from "fs";
|
|
33243
|
-
import { join as
|
|
34752
|
+
import { join as join43 } from "path";
|
|
33244
34753
|
function detectFlavor(cwd) {
|
|
33245
34754
|
for (const [flavor, signatures] of Object.entries(FLAVOR_SIGNATURES)) {
|
|
33246
34755
|
if (flavor === "generic") continue;
|
|
33247
34756
|
for (const sig of signatures) {
|
|
33248
|
-
const path =
|
|
34757
|
+
const path = join43(cwd, sig);
|
|
33249
34758
|
if (sig.includes("*")) {
|
|
33250
34759
|
try {
|
|
33251
34760
|
const parts = sig.split("*");
|
|
@@ -33266,14 +34775,14 @@ function detectFlavor(cwd) {
|
|
|
33266
34775
|
}
|
|
33267
34776
|
function findFile(cwd, candidates) {
|
|
33268
34777
|
for (const c of candidates) {
|
|
33269
|
-
if (existsSync6(
|
|
34778
|
+
if (existsSync6(join43(cwd, c))) return c;
|
|
33270
34779
|
}
|
|
33271
34780
|
return null;
|
|
33272
34781
|
}
|
|
33273
34782
|
function findSourceRoots(cwd) {
|
|
33274
34783
|
const roots = [];
|
|
33275
34784
|
for (const r of SOURCE_ROOT_CANDIDATES) {
|
|
33276
|
-
const p =
|
|
34785
|
+
const p = join43(cwd, r);
|
|
33277
34786
|
try {
|
|
33278
34787
|
const s = statSync6(p);
|
|
33279
34788
|
if (s.isDirectory()) roots.push(r);
|
|
@@ -33284,9 +34793,9 @@ function findSourceRoots(cwd) {
|
|
|
33284
34793
|
}
|
|
33285
34794
|
function findCiConfig(cwd) {
|
|
33286
34795
|
for (const c of CI_PATHS) {
|
|
33287
|
-
if (existsSync6(
|
|
34796
|
+
if (existsSync6(join43(cwd, c))) {
|
|
33288
34797
|
try {
|
|
33289
|
-
const s = statSync6(
|
|
34798
|
+
const s = statSync6(join43(cwd, c));
|
|
33290
34799
|
return s.isDirectory() ? c : c;
|
|
33291
34800
|
} catch {
|
|
33292
34801
|
}
|
|
@@ -33423,7 +34932,7 @@ function analyzeProject(cwd) {
|
|
|
33423
34932
|
ciConfig: findCiConfig(cwd),
|
|
33424
34933
|
readme: findFile(cwd, ["README.md", "README.rst", "README.txt", "Readme.md"]),
|
|
33425
34934
|
sourceRoots: findSourceRoots(cwd),
|
|
33426
|
-
hasGit: existsSync6(
|
|
34935
|
+
hasGit: existsSync6(join43(cwd, ".git"))
|
|
33427
34936
|
};
|
|
33428
34937
|
}
|
|
33429
34938
|
function bashDiscoveryCommands(profile) {
|
|
@@ -33588,7 +35097,7 @@ Aim for 100\u2013200 lines total. Use markdown tables where they save space.
|
|
|
33588
35097
|
}
|
|
33589
35098
|
function buildInitPrompt(cwd) {
|
|
33590
35099
|
const existingName = ["AGENTS.md", "KIMI.md", "KIMIFLARE.md", "AGENT.md"].find(
|
|
33591
|
-
(n) => existsSync6(
|
|
35100
|
+
(n) => existsSync6(join43(cwd, n))
|
|
33592
35101
|
);
|
|
33593
35102
|
const isRefresh = existingName !== void 0;
|
|
33594
35103
|
const targetFilename = existingName ?? "AGENTS.md";
|
|
@@ -33672,7 +35181,7 @@ var init_context_generator = __esm({
|
|
|
33672
35181
|
|
|
33673
35182
|
// src/init/run-init.ts
|
|
33674
35183
|
import { existsSync as existsSync7 } from "fs";
|
|
33675
|
-
import { join as
|
|
35184
|
+
import { join as join44 } from "path";
|
|
33676
35185
|
async function runInit(deps) {
|
|
33677
35186
|
const {
|
|
33678
35187
|
cfg,
|
|
@@ -33765,7 +35274,7 @@ async function runInit(deps) {
|
|
|
33765
35274
|
lspManagerRef.current.notifyChange(path, content);
|
|
33766
35275
|
} else {
|
|
33767
35276
|
void import("fs/promises").then(
|
|
33768
|
-
({ readFile:
|
|
35277
|
+
({ readFile: readFile30 }) => readFile30(path, "utf8").then((c) => lspManagerRef.current.notifyChange(path, c)).catch(() => {
|
|
33769
35278
|
})
|
|
33770
35279
|
);
|
|
33771
35280
|
}
|
|
@@ -33870,7 +35379,7 @@ async function runInit(deps) {
|
|
|
33870
35379
|
}
|
|
33871
35380
|
}
|
|
33872
35381
|
});
|
|
33873
|
-
if (existsSync7(
|
|
35382
|
+
if (existsSync7(join44(cwd, targetFilename))) {
|
|
33874
35383
|
if (cacheStableRef.current) {
|
|
33875
35384
|
messagesRef.current[1] = {
|
|
33876
35385
|
role: "system",
|
|
@@ -33962,11 +35471,11 @@ var init_run_init = __esm({
|
|
|
33962
35471
|
});
|
|
33963
35472
|
|
|
33964
35473
|
// src/skills/discovery.ts
|
|
33965
|
-
import { readdir as readdir9, stat as
|
|
33966
|
-
import { join as
|
|
35474
|
+
import { readdir as readdir9, stat as stat9, readFile as readFile29 } from "fs/promises";
|
|
35475
|
+
import { join as join45, extname as extname2 } from "path";
|
|
33967
35476
|
async function dirExists(path) {
|
|
33968
35477
|
try {
|
|
33969
|
-
const s = await
|
|
35478
|
+
const s = await stat9(path);
|
|
33970
35479
|
return s.isDirectory();
|
|
33971
35480
|
} catch {
|
|
33972
35481
|
return false;
|
|
@@ -33974,7 +35483,7 @@ async function dirExists(path) {
|
|
|
33974
35483
|
}
|
|
33975
35484
|
async function fileExists(path) {
|
|
33976
35485
|
try {
|
|
33977
|
-
const s = await
|
|
35486
|
+
const s = await stat9(path);
|
|
33978
35487
|
return s.isFile();
|
|
33979
35488
|
} catch {
|
|
33980
35489
|
return false;
|
|
@@ -33987,20 +35496,20 @@ async function scanSkillDir(dirPath, source) {
|
|
|
33987
35496
|
for (const entry of entries) {
|
|
33988
35497
|
if (!entry.isFile()) continue;
|
|
33989
35498
|
if (!SKILL_EXTENSIONS.has(extname2(entry.name))) continue;
|
|
33990
|
-
files.push({ filePath:
|
|
35499
|
+
files.push({ filePath: join45(dirPath, entry.name), source });
|
|
33991
35500
|
}
|
|
33992
35501
|
return files;
|
|
33993
35502
|
}
|
|
33994
35503
|
async function discoverSkills(cwd) {
|
|
33995
|
-
const agentsSkills = await scanSkillDir(
|
|
33996
|
-
const agentsMd = await fileExists(
|
|
33997
|
-
const githubSkills = await scanSkillDir(
|
|
33998
|
-
const kimiflareSkills = await scanSkillDir(
|
|
35504
|
+
const agentsSkills = await scanSkillDir(join45(cwd, ".agents", "skills"), "agents");
|
|
35505
|
+
const agentsMd = await fileExists(join45(cwd, "AGENTS.md")) ? [{ filePath: join45(cwd, "AGENTS.md"), source: "agents-md" }] : [];
|
|
35506
|
+
const githubSkills = await scanSkillDir(join45(cwd, ".github", "skills"), "github");
|
|
35507
|
+
const kimiflareSkills = await scanSkillDir(join45(cwd, ".kimiflare", "skills"), "kimiflare");
|
|
33999
35508
|
const ordered = [...agentsSkills, ...agentsMd, ...githubSkills, ...kimiflareSkills];
|
|
34000
35509
|
return ordered;
|
|
34001
35510
|
}
|
|
34002
35511
|
async function readSkillFile(filePath) {
|
|
34003
|
-
const bytes = await
|
|
35512
|
+
const bytes = await readFile29(filePath);
|
|
34004
35513
|
return { bytes, text: bytes.toString("utf-8") };
|
|
34005
35514
|
}
|
|
34006
35515
|
var SKILL_EXTENSIONS;
|
|
@@ -34012,9 +35521,9 @@ var init_discovery = __esm({
|
|
|
34012
35521
|
});
|
|
34013
35522
|
|
|
34014
35523
|
// src/skills/parser.ts
|
|
34015
|
-
import { createHash as
|
|
35524
|
+
import { createHash as createHash6 } from "crypto";
|
|
34016
35525
|
function sha2562(input) {
|
|
34017
|
-
return
|
|
35526
|
+
return createHash6("sha256").update(input).digest("hex");
|
|
34018
35527
|
}
|
|
34019
35528
|
function computeContentHash(rawText, parserVersion) {
|
|
34020
35529
|
return sha2562(`${rawText}
|
|
@@ -34206,7 +35715,7 @@ var init_skills = __esm({
|
|
|
34206
35715
|
|
|
34207
35716
|
// src/ui/run-startup-tasks.ts
|
|
34208
35717
|
import { existsSync as existsSync8 } from "fs";
|
|
34209
|
-
import { join as
|
|
35718
|
+
import { join as join46 } from "path";
|
|
34210
35719
|
function runStartupTasks(deps) {
|
|
34211
35720
|
const {
|
|
34212
35721
|
cfg,
|
|
@@ -34228,7 +35737,7 @@ function runStartupTasks(deps) {
|
|
|
34228
35737
|
}
|
|
34229
35738
|
});
|
|
34230
35739
|
if (cfg.memoryEnabled) {
|
|
34231
|
-
const dbPath = cfg.memoryDbPath ??
|
|
35740
|
+
const dbPath = cfg.memoryDbPath ?? join46(process.cwd(), ".kimiflare", "memory.db");
|
|
34232
35741
|
const manager = new MemoryManager({
|
|
34233
35742
|
dbPath,
|
|
34234
35743
|
...auth,
|
|
@@ -34260,7 +35769,7 @@ function runStartupTasks(deps) {
|
|
|
34260
35769
|
});
|
|
34261
35770
|
const cwd = process.cwd();
|
|
34262
35771
|
sessionStartRecallRef.current = manager.recall({ text: cwd, repoPath: cwd, limit: 5 });
|
|
34263
|
-
if (["AGENTS.md", "KIMI.md", "KIMIFLARE.md", "AGENT.md"].some((name) => existsSync8(
|
|
35772
|
+
if (["AGENTS.md", "KIMI.md", "KIMIFLARE.md", "AGENT.md"].some((name) => existsSync8(join46(cwd, name)))) {
|
|
34264
35773
|
const lastRefresh = manager.getLastKimiMdRefreshTime(cwd);
|
|
34265
35774
|
const driftCount = manager.countHighSignalMemoriesSince(cwd, lastRefresh);
|
|
34266
35775
|
if (driftCount >= 5) {
|
|
@@ -34271,7 +35780,7 @@ function runStartupTasks(deps) {
|
|
|
34271
35780
|
memoryManagerRef.current?.close();
|
|
34272
35781
|
memoryManagerRef.current = null;
|
|
34273
35782
|
}
|
|
34274
|
-
const skillDbPath = cfg.memoryDbPath ??
|
|
35783
|
+
const skillDbPath = cfg.memoryDbPath ?? join46(process.cwd(), ".kimiflare", "memory.db");
|
|
34275
35784
|
const skillDb = getMemoryDb() ?? openMemoryDb(skillDbPath);
|
|
34276
35785
|
initSkillsSchema(skillDb);
|
|
34277
35786
|
void indexSkills({
|
|
@@ -34622,158 +36131,9 @@ var init_run_compact = __esm({
|
|
|
34622
36131
|
}
|
|
34623
36132
|
});
|
|
34624
36133
|
|
|
34625
|
-
// src/ui/bang-command.ts
|
|
34626
|
-
import { spawn as spawn10 } from "child_process";
|
|
34627
|
-
import { mkdtemp as mkdtemp2, readFile as readFile28, rm as rm3 } from "fs/promises";
|
|
34628
|
-
import { tmpdir as tmpdir4, platform as platform7 } from "os";
|
|
34629
|
-
import { join as join45 } from "path";
|
|
34630
|
-
function registerInkInstance(instance) {
|
|
34631
|
-
inkInstance = instance;
|
|
34632
|
-
}
|
|
34633
|
-
function isTerminalHandedOff() {
|
|
34634
|
-
return handedOff;
|
|
34635
|
-
}
|
|
34636
|
-
function parseBangCommand(input) {
|
|
34637
|
-
const t = input.trimStart();
|
|
34638
|
-
if (!t.startsWith("!")) return null;
|
|
34639
|
-
return t.slice(1).trim();
|
|
34640
|
-
}
|
|
34641
|
-
function scriptInvocation(command, shell, logPath, plat = platform7()) {
|
|
34642
|
-
if (plat === "darwin" || plat === "freebsd" || plat === "openbsd" || plat === "netbsd") {
|
|
34643
|
-
return { file: "script", args: ["-q", logPath, shell, "-c", command] };
|
|
34644
|
-
}
|
|
34645
|
-
if (plat === "linux") {
|
|
34646
|
-
return { file: "script", args: ["-q", "-e", "-c", command, logPath], env: { ...process.env, SHELL: shell } };
|
|
34647
|
-
}
|
|
34648
|
-
return null;
|
|
34649
|
-
}
|
|
34650
|
-
function cleanTerminalOutput(raw) {
|
|
34651
|
-
const text = raw.replace(/\x1b\][^\x07\x1b]*(?:\x07|\x1b\\)/g, "").replace(/\x1b\[[0-?]*[ -/]*[@-~]/g, "").replace(/\x1b[@-Z\\-_]/g, "").replace(/^Script (?:started|done) on .*$/gm, "");
|
|
34652
|
-
const lines = text.split(/\r?\n/).map((line) => {
|
|
34653
|
-
const segs = line.split("\r");
|
|
34654
|
-
let out = segs[segs.length - 1] ?? "";
|
|
34655
|
-
if (!out && segs.length > 1) out = segs.filter(Boolean).pop() ?? "";
|
|
34656
|
-
while (/[^\x08]\x08/.test(out)) out = out.replace(/[^\x08]\x08/, "");
|
|
34657
|
-
return out.replace(/[\x00-\x08\x0b-\x1f\x7f]/g, "");
|
|
34658
|
-
});
|
|
34659
|
-
return lines.join("\n").replace(/\n{3,}/g, "\n\n").trim();
|
|
34660
|
-
}
|
|
34661
|
-
function truncateMiddle2(text, max = MAX_CONTEXT_CHARS2) {
|
|
34662
|
-
if (text.length <= max) return text;
|
|
34663
|
-
const half = Math.floor(max / 2);
|
|
34664
|
-
return `${text.slice(0, half)}
|
|
34665
|
-
|
|
34666
|
-
[\u2026 ${text.length - max} chars omitted \u2026]
|
|
34667
|
-
|
|
34668
|
-
${text.slice(-half)}`;
|
|
34669
|
-
}
|
|
34670
|
-
function formatBangContext(r) {
|
|
34671
|
-
const status = r.signal ? `killed by ${r.signal}` : `exit code ${r.exitCode ?? "?"}`;
|
|
34672
|
-
const body = r.output === null ? "(output was shown in the user's terminal but could not be captured)" : r.output ? truncateMiddle2(r.output) : "(no output)";
|
|
34673
|
-
return `[The user ran this shell command themselves with \`!\` in the autopilot prompt. This is context for you, not a new request.]
|
|
34674
|
-
$ ${r.command}
|
|
34675
|
-
(${status})
|
|
34676
|
-
${body}`;
|
|
34677
|
-
}
|
|
34678
|
-
function stopReading(stdin) {
|
|
34679
|
-
stdin.pause();
|
|
34680
|
-
const stream = stdin;
|
|
34681
|
-
try {
|
|
34682
|
-
if (stream._handle?.reading && stream._handle.readStop) {
|
|
34683
|
-
stream._handle.readStop();
|
|
34684
|
-
stream._handle.reading = false;
|
|
34685
|
-
if (stream._readableState) stream._readableState.reading = false;
|
|
34686
|
-
}
|
|
34687
|
-
} catch {
|
|
34688
|
-
}
|
|
34689
|
-
}
|
|
34690
|
-
function swallow() {
|
|
34691
|
-
return ((_chunk, encOrCb, cb) => {
|
|
34692
|
-
const done = typeof encOrCb === "function" ? encOrCb : cb;
|
|
34693
|
-
if (typeof done === "function") done();
|
|
34694
|
-
return true;
|
|
34695
|
-
});
|
|
34696
|
-
}
|
|
34697
|
-
function spawnInherit(inv, cwd) {
|
|
34698
|
-
return new Promise((resolve14, reject) => {
|
|
34699
|
-
const child = spawn10(inv.file, inv.args, { cwd, stdio: "inherit", env: inv.env ?? process.env });
|
|
34700
|
-
child.once("error", reject);
|
|
34701
|
-
child.once("exit", (code, signal) => resolve14({ code, signal }));
|
|
34702
|
-
});
|
|
34703
|
-
}
|
|
34704
|
-
async function runBangCommand(command, cwd) {
|
|
34705
|
-
const isWindows = platform7() === "win32";
|
|
34706
|
-
const shell = isWindows ? process.env.COMSPEC || "cmd.exe" : process.env.SHELL || "/bin/sh";
|
|
34707
|
-
const direct = isWindows ? { file: shell, args: ["/c", command] } : { file: shell, args: ["-c", command] };
|
|
34708
|
-
const stdin = process.stdin;
|
|
34709
|
-
const stdout = process.stdout;
|
|
34710
|
-
const stderr = process.stderr;
|
|
34711
|
-
const origOut = stdout.write;
|
|
34712
|
-
const origErr = stderr.write;
|
|
34713
|
-
const readable = stdin.listeners("readable");
|
|
34714
|
-
const wasRaw = stdin.isTTY ? stdin.isRaw : false;
|
|
34715
|
-
const logDir2 = isWindows ? null : await mkdtemp2(join45(tmpdir4(), "autopilot-bang-"));
|
|
34716
|
-
const logPath = logDir2 ? join45(logDir2, "out.log") : null;
|
|
34717
|
-
const viaScript = logPath ? scriptInvocation(command, shell, logPath) : null;
|
|
34718
|
-
inkInstance?.clear();
|
|
34719
|
-
origOut.call(stdout, `
|
|
34720
|
-
$ ${command}
|
|
34721
|
-
`);
|
|
34722
|
-
stdout.write = swallow();
|
|
34723
|
-
stderr.write = swallow();
|
|
34724
|
-
for (const l of readable) stdin.removeListener("readable", l);
|
|
34725
|
-
if (stdin.isTTY) stdin.setRawMode(false);
|
|
34726
|
-
stopReading(stdin);
|
|
34727
|
-
handedOff = true;
|
|
34728
|
-
let result2;
|
|
34729
|
-
let captured = false;
|
|
34730
|
-
let spawnError = null;
|
|
34731
|
-
try {
|
|
34732
|
-
if (viaScript) {
|
|
34733
|
-
try {
|
|
34734
|
-
result2 = await spawnInherit(viaScript, cwd);
|
|
34735
|
-
captured = true;
|
|
34736
|
-
} catch (e) {
|
|
34737
|
-
logger.warn("bang:script_unavailable", { error: e.message });
|
|
34738
|
-
result2 = await spawnInherit(direct, cwd);
|
|
34739
|
-
}
|
|
34740
|
-
} else {
|
|
34741
|
-
result2 = await spawnInherit(direct, cwd);
|
|
34742
|
-
}
|
|
34743
|
-
} catch (e) {
|
|
34744
|
-
result2 = { code: 127, signal: null };
|
|
34745
|
-
spawnError = e.message;
|
|
34746
|
-
logger.warn("bang:spawn_failed", { error: spawnError });
|
|
34747
|
-
} finally {
|
|
34748
|
-
handedOff = false;
|
|
34749
|
-
stdout.write = origOut;
|
|
34750
|
-
stderr.write = origErr;
|
|
34751
|
-
if (stdin.isTTY) stdin.setRawMode(wasRaw);
|
|
34752
|
-
for (const l of readable) stdin.addListener("readable", l);
|
|
34753
|
-
origOut.call(stdout, "\n");
|
|
34754
|
-
}
|
|
34755
|
-
let output = spawnError ? `failed to start: ${spawnError}` : null;
|
|
34756
|
-
if (captured && logPath) {
|
|
34757
|
-
output = cleanTerminalOutput(await readFile28(logPath, "utf8").catch(() => ""));
|
|
34758
|
-
}
|
|
34759
|
-
if (logDir2) await rm3(logDir2, { recursive: true, force: true }).catch(() => {
|
|
34760
|
-
});
|
|
34761
|
-
return { command, exitCode: result2.code, signal: result2.signal, output };
|
|
34762
|
-
}
|
|
34763
|
-
var inkInstance, handedOff, MAX_CONTEXT_CHARS2;
|
|
34764
|
-
var init_bang_command = __esm({
|
|
34765
|
-
"src/ui/bang-command.ts"() {
|
|
34766
|
-
"use strict";
|
|
34767
|
-
init_logger();
|
|
34768
|
-
inkInstance = null;
|
|
34769
|
-
handedOff = false;
|
|
34770
|
-
MAX_CONTEXT_CHARS2 = 16e3;
|
|
34771
|
-
}
|
|
34772
|
-
});
|
|
34773
|
-
|
|
34774
36134
|
// src/commands/save.ts
|
|
34775
|
-
import { mkdir as mkdir14, writeFile as writeFile15, unlink as
|
|
34776
|
-
import { dirname as
|
|
36135
|
+
import { mkdir as mkdir14, writeFile as writeFile15, unlink as unlink5 } from "fs/promises";
|
|
36136
|
+
import { dirname as dirname17 } from "path";
|
|
34777
36137
|
async function saveCustomCommand(opts2) {
|
|
34778
36138
|
const dir = opts2.source === "project" ? projectCommandsDir(opts2.cwd) : globalCommandsDir();
|
|
34779
36139
|
const filepath = `${dir}/${opts2.name}.md`;
|
|
@@ -34784,12 +36144,12 @@ async function saveCustomCommand(opts2) {
|
|
|
34784
36144
|
if (opts2.effort) data.effort = opts2.effort;
|
|
34785
36145
|
const frontmatter = serializeFrontmatter(data);
|
|
34786
36146
|
const content = frontmatter + opts2.template;
|
|
34787
|
-
await mkdir14(
|
|
36147
|
+
await mkdir14(dirname17(filepath), { recursive: true });
|
|
34788
36148
|
await writeFile15(filepath, content, "utf8");
|
|
34789
36149
|
return { filepath };
|
|
34790
36150
|
}
|
|
34791
36151
|
async function deleteCustomCommand(cmd) {
|
|
34792
|
-
await
|
|
36152
|
+
await unlink5(cmd.filepath);
|
|
34793
36153
|
}
|
|
34794
36154
|
var init_save = __esm({
|
|
34795
36155
|
"src/commands/save.ts"() {
|
|
@@ -34908,8 +36268,8 @@ __export(app_exports, {
|
|
|
34908
36268
|
import React27, { useState as useState28, useRef as useRef9, useEffect as useEffect11, useCallback as useCallback9, useMemo as useMemo6 } from "react";
|
|
34909
36269
|
import { Box as Box41, Text as Text42, useApp, useInput as useInput20, useWindowSize as useWindowSize2, render } from "ink";
|
|
34910
36270
|
import { existsSync as existsSync9 } from "fs";
|
|
34911
|
-
import { join as
|
|
34912
|
-
import { homedir as
|
|
36271
|
+
import { join as join47, resolve as resolve14 } from "path";
|
|
36272
|
+
import { homedir as homedir27 } from "os";
|
|
34913
36273
|
import { jsx as jsx43, jsxs as jsxs40 } from "react/jsx-runtime";
|
|
34914
36274
|
function App({
|
|
34915
36275
|
initialCfg,
|
|
@@ -35231,8 +36591,8 @@ ${wcagWarnings.join("\n")}` }
|
|
|
35231
36591
|
const exactDirectory = query === "~" || query === "." || query === ".." || query.endsWith("/..");
|
|
35232
36592
|
const prefix = exactDirectory ? `${query.replace(/\/$/, "")}/` : slash >= 0 ? query.slice(0, slash + 1) : "";
|
|
35233
36593
|
const directory = exactDirectory ? query : slash >= 0 ? query.slice(0, slash + 1) : query;
|
|
35234
|
-
const expanded = directory.startsWith("~") ?
|
|
35235
|
-
const target =
|
|
36594
|
+
const expanded = directory.startsWith("~") ? join47(homedir27(), directory.slice(1)) : directory;
|
|
36595
|
+
const target = resolve14(cwd, expanded || ".");
|
|
35236
36596
|
const entries2 = await glob("*", {
|
|
35237
36597
|
cwd: target,
|
|
35238
36598
|
dot: false,
|
|
@@ -36208,7 +37568,7 @@ ${result2.output ?? "(output not captured)"}`,
|
|
|
36208
37568
|
}
|
|
36209
37569
|
}
|
|
36210
37570
|
turnCounterRef.current += 1;
|
|
36211
|
-
if (turnCounterRef.current % 15 === 0 && ["AGENTS.md", "KIMI.md", "KIMIFLARE.md", "AGENT.md"].some((name) => existsSync9(
|
|
37571
|
+
if (turnCounterRef.current % 15 === 0 && ["AGENTS.md", "KIMI.md", "KIMIFLARE.md", "AGENT.md"].some((name) => existsSync9(join47(process.cwd(), name))) && !kimiMdStale) {
|
|
36212
37572
|
setEvents((e) => [
|
|
36213
37573
|
...e,
|
|
36214
37574
|
{ kind: "info", key: mkKey(), text: "Tip: Rerunning /init occasionally helps autopilot (formerly kimiflare) stay accurate as your project evolves." }
|
|
@@ -36232,7 +37592,7 @@ ${result2.output ?? "(output not captured)"}`,
|
|
|
36232
37592
|
};
|
|
36233
37593
|
ensureSessionId();
|
|
36234
37594
|
const { sessionsDir: sessionsDir3 } = await Promise.resolve().then(() => (init_sessions(), sessions_exports));
|
|
36235
|
-
const filePath =
|
|
37595
|
+
const filePath = join47(sessionsDir3(), `${sessionIdRef.current}.json`);
|
|
36236
37596
|
await addCheckpoint(filePath, cp);
|
|
36237
37597
|
}
|
|
36238
37598
|
const summary = await generateContinuationSummary({
|
|
@@ -36600,7 +37960,7 @@ ${conflicts.join("\n")}` }
|
|
|
36600
37960
|
lspManagerRef.current.notifyChange(path, content2);
|
|
36601
37961
|
} else {
|
|
36602
37962
|
void import("fs/promises").then(
|
|
36603
|
-
({ readFile:
|
|
37963
|
+
({ readFile: readFile30 }) => readFile30(path, "utf8").then((c) => lspManagerRef.current.notifyChange(path, c)).catch(() => {
|
|
36604
37964
|
})
|
|
36605
37965
|
);
|
|
36606
37966
|
}
|
|
@@ -36786,7 +38146,7 @@ ${conflicts.join("\n")}` }
|
|
|
36786
38146
|
};
|
|
36787
38147
|
ensureSessionId();
|
|
36788
38148
|
const { sessionsDir: sessionsDir3 } = await Promise.resolve().then(() => (init_sessions(), sessions_exports));
|
|
36789
|
-
const filePath =
|
|
38149
|
+
const filePath = join47(sessionsDir3(), `${sessionIdRef.current}.json`);
|
|
36790
38150
|
await addCheckpoint(filePath, cp);
|
|
36791
38151
|
}
|
|
36792
38152
|
const summary = await generateContinuationSummary({
|
|
@@ -37202,7 +38562,7 @@ ${conflicts.join("\n")}` }
|
|
|
37202
38562
|
lsp: {
|
|
37203
38563
|
servers: cfg?.lspServers ?? {},
|
|
37204
38564
|
scope: lspScope,
|
|
37205
|
-
hasProjectDir: existsSync9(
|
|
38565
|
+
hasProjectDir: existsSync9(join47(process.cwd(), ".kimiflare"))
|
|
37206
38566
|
}
|
|
37207
38567
|
});
|
|
37208
38568
|
});
|
|
@@ -37259,7 +38619,7 @@ ${conflicts.join("\n")}` }
|
|
|
37259
38619
|
onCommandDelete: handleCommandDelete2,
|
|
37260
38620
|
lspServers: cfg?.lspServers ?? {},
|
|
37261
38621
|
lspScope,
|
|
37262
|
-
hasProjectDir: existsSync9(
|
|
38622
|
+
hasProjectDir: existsSync9(join47(process.cwd(), ".kimiflare")),
|
|
37263
38623
|
onLspSave: handleLspSave2,
|
|
37264
38624
|
themes: themeList(),
|
|
37265
38625
|
onPickTheme: handleThemePick,
|
|
@@ -37770,8 +39130,8 @@ async function resolveFiles(filePatterns, cwd) {
|
|
|
37770
39130
|
const resolved = /* @__PURE__ */ new Set();
|
|
37771
39131
|
for (const pattern of filePatterns) {
|
|
37772
39132
|
try {
|
|
37773
|
-
const
|
|
37774
|
-
if (
|
|
39133
|
+
const stat10 = await import("fs/promises").then((m) => m.stat(resolve6(cwd, pattern)));
|
|
39134
|
+
if (stat10.isFile()) {
|
|
37775
39135
|
resolved.add(resolve6(cwd, pattern));
|
|
37776
39136
|
continue;
|
|
37777
39137
|
}
|
|
@@ -38110,6 +39470,55 @@ program.command("cost").description("Show cost attribution by task type (require
|
|
|
38110
39470
|
await runCostCommand2({ ...cmdOpts, config: cfg });
|
|
38111
39471
|
});
|
|
38112
39472
|
program.addCommand(createRemoteCommand());
|
|
39473
|
+
var asterCommand = program.command("aster").description("Manage the Aster mobile API");
|
|
39474
|
+
var asterTokenCommand = asterCommand.command("token").description("Manage scoped Aster API credentials");
|
|
39475
|
+
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) => {
|
|
39476
|
+
const { loadAsterServerConfig: loadAsterServerConfig2 } = await Promise.resolve().then(() => (init_aster_workspaces(), aster_workspaces_exports));
|
|
39477
|
+
const { AsterStore: AsterStore2 } = await Promise.resolve().then(() => (init_aster_store(), aster_store_exports));
|
|
39478
|
+
try {
|
|
39479
|
+
const config2 = await loadAsterServerConfig2();
|
|
39480
|
+
const workspaceIds = [...new Set(options.workspace)];
|
|
39481
|
+
if (!Number.isInteger(options.expiresInDays) || options.expiresInDays < 1 || options.expiresInDays > 365) {
|
|
39482
|
+
throw new Error("--expires-in-days must be an integer from 1 through 365");
|
|
39483
|
+
}
|
|
39484
|
+
if (workspaceIds.some((id) => !config2.workspaces.some((workspace) => workspace.id === id))) {
|
|
39485
|
+
throw new Error("credential includes an unknown workspace ID");
|
|
39486
|
+
}
|
|
39487
|
+
const store = new AsterStore2();
|
|
39488
|
+
try {
|
|
39489
|
+
const created = store.createCredential({
|
|
39490
|
+
name: options.name,
|
|
39491
|
+
workspaceIds,
|
|
39492
|
+
scopes: ["workspaces:read", "models:read", "conversations:read", "conversations:write", "approvals:resolve"],
|
|
39493
|
+
expiresAt: Date.now() + options.expiresInDays * 24 * 60 * 60 * 1e3
|
|
39494
|
+
});
|
|
39495
|
+
console.log(`Credential ID: ${created.id}`);
|
|
39496
|
+
console.log(`Workspace scope: ${created.workspaceIds.join(", ")}`);
|
|
39497
|
+
console.log(`Expires: ${new Date(created.expiresAt).toISOString()}`);
|
|
39498
|
+
console.log("Copy this token into the Aster app's Keychain now; it will not be shown again:");
|
|
39499
|
+
console.log(created.token);
|
|
39500
|
+
} finally {
|
|
39501
|
+
store.close();
|
|
39502
|
+
}
|
|
39503
|
+
} catch (error) {
|
|
39504
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
39505
|
+
process.exitCode = 2;
|
|
39506
|
+
}
|
|
39507
|
+
});
|
|
39508
|
+
asterTokenCommand.command("revoke").description("Revoke an Aster credential immediately").argument("<credential-id>").action(async (credentialId) => {
|
|
39509
|
+
const { AsterStore: AsterStore2 } = await Promise.resolve().then(() => (init_aster_store(), aster_store_exports));
|
|
39510
|
+
const store = new AsterStore2();
|
|
39511
|
+
try {
|
|
39512
|
+
if (!store.revokeCredential(credentialId)) {
|
|
39513
|
+
console.error("Aster credential not found");
|
|
39514
|
+
process.exitCode = 1;
|
|
39515
|
+
return;
|
|
39516
|
+
}
|
|
39517
|
+
console.log(`Revoked credential ${credentialId}`);
|
|
39518
|
+
} finally {
|
|
39519
|
+
store.close();
|
|
39520
|
+
}
|
|
39521
|
+
});
|
|
38113
39522
|
var logsCmd = program.command("logs").description("Inspect KimiFlare's structured logs (jsonl, one file per day, 7-day retention)");
|
|
38114
39523
|
logsCmd.command("path").description("Print today's log file path. Useful for tailing: tail -f $(autopilot logs path) | jq").action(async () => {
|
|
38115
39524
|
const { logPathFor: logPathFor2 } = await Promise.resolve().then(() => (init_log_sink(), log_sink_exports));
|
|
@@ -38386,11 +39795,11 @@ async function promptHidden(question) {
|
|
|
38386
39795
|
}
|
|
38387
39796
|
});
|
|
38388
39797
|
const rl = createInterface4({ input: process.stdin, output, terminal: true });
|
|
38389
|
-
return new Promise((
|
|
39798
|
+
return new Promise((resolve15) => {
|
|
38390
39799
|
rl.question(question, (answer) => {
|
|
38391
39800
|
rl.close();
|
|
38392
39801
|
process.stdout.write("\n");
|
|
38393
|
-
|
|
39802
|
+
resolve15(answer);
|
|
38394
39803
|
});
|
|
38395
39804
|
muted = true;
|
|
38396
39805
|
});
|