autopilot-ai 1.9.0 → 1.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js 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((resolve14) => {
97
- s.end(() => resolve14());
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 sep5;
1337
- while ((sep5 = buffer.indexOf("\n\n")) !== -1) {
1338
- const event = buffer.slice(0, sep5);
1339
- buffer = buffer.slice(sep5 + 2);
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((resolve14, reject) => {
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
- resolve14();
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 = resolve14(dir);
3422
+ var prebuild = resolve15(dir);
3423
3423
  if (prebuild) return prebuild;
3424
- var nearby = resolve14(path.dirname(process.execPath));
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 resolve14(dir2) {
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((resolve14) => setTimeout(resolve14, ms));
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(json2) {
3886
- if (!json2 || typeof json2 !== "object") {
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 = json2.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 json2 = await res.json();
3943
- return parseOpenAiEmbeddingResponse(json2);
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((resolve14) => {
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 resolve14(void 0);
6587
- resolve14(out.trim() || void 0);
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((resolve14, reject) => {
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
- resolve14({
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((resolve14) => {
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
- resolve14();
8127
+ resolve15();
8128
8128
  }
8129
8129
  }, 100);
8130
8130
  setTimeout(() => {
8131
8131
  clearInterval(timer3);
8132
- resolve14();
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 json2 = parsePrintOutput(execution.stdout);
8542
- const summary = typeof json2?.text === "string" ? json2.text.trim() : execution.stdout.trim();
8543
- const tokensUsed = typeof json2?.usage?.totalTokens === "number" ? json2.usage.totalTokens : 0;
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 stat9 = readFileSync3(`/proc/${pid}/stat`, "utf8");
9688
- const fieldsAfterCommand = stat9.slice(stat9.lastIndexOf(")") + 1).trim().split(/\s+/);
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 json2 = JSON.stringify(payload);
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, json2, env2, payload.cwd, timeoutMs, signal);
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((resolve14) => {
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
- resolve14({ exitCode, stdout, stderr, timedOut });
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((resolve14) => setTimeout(resolve14, ms));
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 realpath3 } from "fs/promises";
13086
- import { homedir as homedir18 } from "os";
13087
- import { isAbsolute as isAbsolute3, join as join25, parse, relative as relative5, resolve as resolve7, sep as sep3 } from "path";
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 || join25(homedir18(), ".local", "share");
13090
- return join25(dataHome, "autopilot", "worktrees");
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(`..${sep3}`) || isAbsolute3(path);
13624
+ return path === ".." || path.startsWith(`..${sep4}`) || isAbsolute4(path);
13094
13625
  }
13095
- function isWithin(parent, candidate) {
13096
- const path = relative5(parent, candidate);
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 realpath3(sourceCwd);
13655
+ const source = await realpath4(sourceCwd);
13125
13656
  const repositoryRoot = (await runGit(["rev-parse", "--show-toplevel"], source)).trim();
13126
- const repository = await realpath3(repositoryRoot);
13127
- const sourceRelativePath = relative5(repository, source);
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 || isWithin(repository, configuredRoot)) {
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 realpath3(configuredRoot);
13137
- if (isWithin(repository, worktreesRoot)) {
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 = join25(worktreesRoot, runId);
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 realpath3(this.rootDir);
13162
- const target = await realpath3(worktree.worktreePath);
13163
- const repository = await realpath3(worktree.repositoryRoot);
13164
- if (target === root || !isWithin(root, target)) {
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 randomUUID5 } from "crypto";
14579
+ import { randomUUID as randomUUID7 } from "crypto";
13310
14580
  import { URL as URL2 } from "url";
13311
- import { readFile as readFile17, unlink as unlink2 } from "fs/promises";
13312
- import { resolve as resolve8, basename as basename5 } from "path";
13313
- function json(res, status, data) {
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
- json(res, 400, { error: message2 });
14588
+ json2(res, 400, { error: message2 });
13319
14589
  }
13320
14590
  function notFound(res, message2) {
13321
- json(res, 404, { error: message2 });
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 stat9 = await import("fs/promises").then((m) => m.stat(resolve8(cwd, pattern)));
13340
- if (stat9.isFile()) {
13341
- resolved.add(resolve8(cwd, pattern));
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 readFile17(filePath, "utf8");
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(resolve8(sessionsDir(), `${run.sessionId}.json`));
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
- json(res, 200, { status: "ok", version: process.env.npm_package_version ?? "dev" });
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
- json(res, 503, { error: "Set KIMIFLARE_SERVER_PASSWORD before accessing unattended runs" });
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
- json(res, 200, { runs: store.listRuns({ limit }) });
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
- json(res, 503, { error: "Set KIMIFLARE_SERVER_PASSWORD before creating unattended runs" });
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" ? resolve8(body.cwd) : process.cwd();
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 = randomUUID5();
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 unlink2(resolve8(sessionsDir(), `${sessionId}.json`)).catch(() => {
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
- json(res, 202, {
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
- json(res, 503, { error: "Set KIMIFLARE_SERVER_PASSWORD before accessing unattended runs" });
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
- json(res, 200, { events: store.listEvents(runId) });
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
- json(res, 200, { run: store.getRun(runId) });
14950
+ json2(res, 200, { run: store.getRun(runId) });
13654
14951
  return;
13655
14952
  }
13656
14953
  if (!suffix && method === "GET") {
13657
- json(res, 200, { run });
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
- json(res, 200, { sessions });
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
- json(res, 200, {
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(resolve8(sessionsDir(), `${sessionId}.json`));
13692
- json(res, 200, {
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: unlink5 } = await import("fs/promises");
13711
- await unlink5(resolve8(sessionsDir(), `${sessionId}.json`));
15007
+ const { unlink: unlink6 } = await import("fs/promises");
15008
+ await unlink6(resolve9(sessionsDir(), `${sessionId}.json`));
13712
15009
  } catch {
13713
15010
  }
13714
- json(res, 200, { deleted: sessionId });
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
- json(res, 202, { sessionId, status: "started" });
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
- json(res, 202, { sessionId, status: "started" });
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
- json(res, 500, { error: err.message });
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", { sessionId: active.sessionFile.id, error: message2 });
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 : String(storeError)
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 message2 = err.message;
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
- res.setHeader("Access-Control-Allow-Origin", "*");
14044
- res.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
14045
- res.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization, Last-Event-ID");
14046
- if (req.method === "OPTIONS") {
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((resolve14, reject) => {
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
- resolve14(server);
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 Database3 from "better-sqlite3";
14136
- import { dirname as dirname12 } from "path";
14137
- import { mkdirSync as mkdirSync6, statSync as statSync4 } from "fs";
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
- mkdirSync6(dirname12(dbPath), { recursive: true });
14222
- dbInstance = new Database3(dbPath);
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((resolve14, reject) => {
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
- resolve14();
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((resolve14, reject) => {
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: resolve14, reject, signal, timer: timer3 };
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 relative6 } from "path";
17175
+ import { relative as relative8 } from "path";
15841
17176
  function formatLocation(loc, cwd) {
15842
17177
  const path = fromUri(loc.uri);
15843
- const rel = relative6(cwd, path) || path;
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 = relative6(cwd, path) || path;
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 = relative6(cwd, path) || path;
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 relative7 } from "path";
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 = relative7(ctx.cwd, resolved);
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 readFile18, writeFile as writeFile11, mkdir as mkdir11 } from "fs/promises";
16200
- import { homedir as homedir19 } from "os";
16201
- import { join as join27, dirname as dirname13 } from "path";
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 || join27(homedir19(), ".config");
16204
- return join27(xdg, "kimiflare", "openrouter-models.json");
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 readFile18(catalogCachePath(), "utf8");
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(dirname13(path), { recursive: true });
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 readFile19, writeFile as writeFile12, mkdir as mkdir12 } from "fs/promises";
16315
- import { homedir as homedir20 } from "os";
16316
- import { join as join28, dirname as dirname14 } from "path";
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 || join28(homedir20(), ".config");
16319
- return join28(xdg, "kimiflare", "requesty-models.json");
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 readFile19(catalogCachePath2(), "utf8");
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(dirname14(path), { recursive: true });
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 resolve9 } from "path";
16482
- import { homedir as homedir21 } from "os";
16483
- import { join as join29 } from "path";
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 = resolve9(opts2.cwd ?? process.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 ?? join29(homedir21(), ".local", "share", "kimiflare", "memory.db");
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 = join29(sessionsDir(), `${opts2.sessionId}.json`);
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: readFile29 } = await import("fs/promises");
16694
- const data = await readFile29(img.path, "base64");
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((resolve14) => {
16898
- this.permissionResolvers.set(requestId, resolve14);
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
- resolve14("deny");
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: readFile29 }) => readFile29(path, "utf8").then((c) => this.lspManager?.notifyChange(path, c)).catch(() => {
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(join29(this.cwd, name)))) {
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 readFile20, stat as stat6 } from "fs/promises";
17206
- import { createHash as createHash3 } from "crypto";
17207
- import { resolve as resolve10, join as join30 } from "path";
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 = resolve10(repoRoot, file);
18549
+ const path = resolve11(repoRoot, file);
17215
18550
  try {
17216
- const s = await stat6(path);
18551
+ const s = await stat7(path);
17217
18552
  if (!s.isFile()) continue;
17218
- const raw = await readFile20(path, "utf8");
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 ?? join30(repoRoot, ".kimiflare", "memory.db");
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 createHash3("sha256").update(`${prompt}\0${context}\0${strategy}`).digest("hex");
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((resolve14) => {
18194
- followUpResolver = resolve14;
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((resolve14) => {
18385
- pendingPermissions.set(reqId, resolve14);
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((resolve14, reject) => {
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
- resolve14(void 0);
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
- resolve14(void 0);
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((resolve14, reject) => {
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
- resolve14(void 0);
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
- resolve14(void 0);
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 join31 } from "path";
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 = join31(cwd, ".gitignore");
20748
+ const gitignorePath = join32(cwd, ".gitignore");
19414
20749
  const stats = statSync5(gitignorePath);
19415
20750
  if (stats.size > MAX_GITIGNORE_SIZE) {
19416
20751
  return hardcoded;
@@ -19587,10 +20922,10 @@ var init_app_helpers = __esm({
19587
20922
 
19588
20923
  // src/remote/deploy-commute.ts
19589
20924
  import { spawn as spawn7 } from "child_process";
19590
- import { mkdtemp, readFile as readFile21, writeFile as writeFile13, rm as rm2 } from "fs/promises";
20925
+ import { mkdtemp, readFile as readFile22, writeFile as writeFile13, rm as rm2 } from "fs/promises";
19591
20926
  import { tmpdir as tmpdir3 } from "os";
19592
- import { join as join32 } from "path";
19593
- import { randomBytes as randomBytes2 } from "crypto";
20927
+ import { join as join33 } from "path";
20928
+ import { randomBytes as randomBytes3 } from "crypto";
19594
20929
  async function cfApiFetch(accountId, apiToken, path, init) {
19595
20930
  const url = `${CF_API}/accounts/${encodeURIComponent(accountId)}${path}`;
19596
20931
  const res = await fetch(url, {
@@ -19602,64 +20937,64 @@ async function cfApiFetch(accountId, apiToken, path, init) {
19602
20937
  ...init?.headers
19603
20938
  }
19604
20939
  });
19605
- const json2 = await res.json();
19606
- return json2;
20940
+ const json3 = await res.json();
20941
+ return json3;
19607
20942
  }
19608
20943
  async function listDurableObjectNamespaces(accountId, apiToken) {
19609
- const json2 = await cfApiFetch(
20944
+ const json3 = await cfApiFetch(
19610
20945
  accountId,
19611
20946
  apiToken,
19612
20947
  "/workers/durable_objects/namespaces"
19613
20948
  );
19614
- if (!json2.success || !json2.result) {
20949
+ if (!json3.success || !json3.result) {
19615
20950
  throw new Error(
19616
- json2.errors?.map((e) => e.message).join(", ") ?? "Failed to list DO namespaces"
20951
+ json3.errors?.map((e) => e.message).join(", ") ?? "Failed to list DO namespaces"
19617
20952
  );
19618
20953
  }
19619
- return json2.result;
20954
+ return json3.result;
19620
20955
  }
19621
20956
  async function deleteDurableObjectNamespace(accountId, apiToken, namespaceId) {
19622
- const json2 = await cfApiFetch(
20957
+ const json3 = await cfApiFetch(
19623
20958
  accountId,
19624
20959
  apiToken,
19625
20960
  `/workers/durable_objects/namespaces/${encodeURIComponent(namespaceId)}`,
19626
20961
  { method: "DELETE" }
19627
20962
  );
19628
- if (!json2.success) {
20963
+ if (!json3.success) {
19629
20964
  throw new Error(
19630
- json2.errors?.map((e) => e.message).join(", ") ?? "Failed to delete DO namespace"
20965
+ json3.errors?.map((e) => e.message).join(", ") ?? "Failed to delete DO namespace"
19631
20966
  );
19632
20967
  }
19633
20968
  }
19634
20969
  async function listContainerApplications(accountId, apiToken) {
19635
- const json2 = await cfApiFetch(
20970
+ const json3 = await cfApiFetch(
19636
20971
  accountId,
19637
20972
  apiToken,
19638
20973
  "/containers/applications"
19639
20974
  );
19640
- if (!json2.success || !json2.result) {
20975
+ if (!json3.success || !json3.result) {
19641
20976
  return [];
19642
20977
  }
19643
- return json2.result;
20978
+ return json3.result;
19644
20979
  }
19645
20980
  async function deleteContainerApplication(accountId, apiToken, appId) {
19646
- const json2 = await cfApiFetch(
20981
+ const json3 = await cfApiFetch(
19647
20982
  accountId,
19648
20983
  apiToken,
19649
20984
  `/containers/applications/${encodeURIComponent(appId)}`,
19650
20985
  { method: "DELETE" }
19651
20986
  );
19652
- if (!json2.success) {
20987
+ if (!json3.success) {
19653
20988
  throw new Error(
19654
- json2.errors?.map((e) => e.message).join(", ") ?? "Failed to delete container application"
20989
+ json3.errors?.map((e) => e.message).join(", ") ?? "Failed to delete container application"
19655
20990
  );
19656
20991
  }
19657
20992
  }
19658
20993
  function generateSecret2() {
19659
- return randomBytes2(32).toString("hex");
20994
+ return randomBytes3(32).toString("hex");
19660
20995
  }
19661
20996
  function runCmd(cmd, args, opts2 = {}) {
19662
- return new Promise((resolve14) => {
20997
+ return new Promise((resolve15) => {
19663
20998
  const child = spawn7(cmd, args, {
19664
20999
  cwd: opts2.cwd,
19665
21000
  env: { ...process.env, ...opts2.env ?? {} },
@@ -19680,11 +21015,11 @@ function runCmd(cmd, args, opts2 = {}) {
19680
21015
  const timer3 = opts2.timeoutMs ? setTimeout(() => child.kill("SIGKILL"), opts2.timeoutMs) : null;
19681
21016
  child.on("close", (code) => {
19682
21017
  if (timer3) clearTimeout(timer3);
19683
- resolve14({ stdout, stderr, code: code ?? -1 });
21018
+ resolve15({ stdout, stderr, code: code ?? -1 });
19684
21019
  });
19685
21020
  child.on("error", (err) => {
19686
21021
  if (timer3) clearTimeout(timer3);
19687
- resolve14({ stdout, stderr: stderr + String(err), code: -1 });
21022
+ resolve15({ stdout, stderr: stderr + String(err), code: -1 });
19688
21023
  });
19689
21024
  });
19690
21025
  }
@@ -19820,8 +21155,8 @@ ${wranglerInstall.stderr.slice(-600)}`,
19820
21155
  ok: true
19821
21156
  };
19822
21157
  yield { message: "Prerequisites ready", ok: true };
19823
- const tmpRoot = await mkdtemp(join32(tmpdir3(), "kimiflare-commute-"));
19824
- const repoDir = join32(tmpRoot, "kimiflare-commute");
21158
+ const tmpRoot = await mkdtemp(join33(tmpdir3(), "kimiflare-commute-"));
21159
+ const repoDir = join33(tmpRoot, "kimiflare-commute");
19825
21160
  yield { message: `Fetching worker source from GitHub (${COMMUTE_REPO})\u2026` };
19826
21161
  const clone = await runCmd("git", ["clone", "--depth", "1", "--branch", COMMUTE_BRANCH, COMMUTE_REPO, repoDir], { timeoutMs: 6e4 });
19827
21162
  if (clone.code !== 0) {
@@ -19830,8 +21165,8 @@ ${(clone.stderr || clone.stdout).slice(0, 400)}`, error: true };
19830
21165
  throw new Error("clone failed");
19831
21166
  }
19832
21167
  yield { message: "Source fetched from GitHub", ok: true };
19833
- const workerDir = join32(repoDir, "remote", "worker");
19834
- const wranglerToml = join32(workerDir, "wrangler.toml");
21168
+ const workerDir = join33(repoDir, "remote", "worker");
21169
+ const wranglerToml = join33(workerDir, "wrangler.toml");
19835
21170
  yield { message: "Installing Worker dependencies (npm install)\u2026" };
19836
21171
  const install = await runCmd("npm", ["install", "--no-audit", "--no-fund", "--loglevel=error"], {
19837
21172
  cwd: workerDir,
@@ -19920,7 +21255,7 @@ ${(install.stderr || install.stdout).slice(-1200).trim()}`,
19920
21255
  ok: true
19921
21256
  };
19922
21257
  yield { message: "Patching wrangler.toml\u2026" };
19923
- let toml = await readFile21(wranglerToml, "utf8");
21258
+ let toml = await readFile22(wranglerToml, "utf8");
19924
21259
  toml = toml.replace(/^name\s*=\s*"[^"]+"/m, `name = "${workerName}"`);
19925
21260
  toml = toml.replace(
19926
21261
  /(\[\[kv_namespaces\]\][\s\S]*?binding\s*=\s*"OAUTH_KV"[\s\S]*?id\s*=\s*")[^"]+(")/,
@@ -22266,8 +23601,8 @@ __export(camouflage_view_exports, {
22266
23601
  });
22267
23602
  import { execFileSync as execFileSync3, spawn as spawn9 } from "child_process";
22268
23603
  import { readdir as readdir6 } from "fs/promises";
22269
- import { homedir as homedir22 } from "os";
22270
- import { join as join33, resolve as resolve11 } from "path";
23604
+ import { homedir as homedir23 } from "os";
23605
+ import { join as join34, resolve as resolve12 } from "path";
22271
23606
  async function loadSdk() {
22272
23607
  const sdk = await import("camouflage-tui").catch((err) => {
22273
23608
  throw new CamouflageUnavailable(`camouflage-tui is not installed (${err.message})`);
@@ -22345,14 +23680,14 @@ async function runCamouflageView(opts2) {
22345
23680
  }
22346
23681
  }
22347
23682
  function runShell(command) {
22348
- return new Promise((resolve14) => {
23683
+ return new Promise((resolve15) => {
22349
23684
  const { shell, args } = getShellCommand();
22350
23685
  const child = spawn9(shell, [...args, command], { env: process.env });
22351
23686
  let out = "";
22352
23687
  child.stdout?.on("data", (c) => out += c.toString("utf8"));
22353
23688
  child.stderr?.on("data", (c) => out += c.toString("utf8"));
22354
- child.on("close", (code) => resolve14({ ok: code === 0, output: out || (code === 0 ? "" : `Exit code: ${code}`) }));
22355
- child.on("error", (err) => resolve14({ ok: false, output: err.message }));
23689
+ child.on("close", (code) => resolve15({ ok: code === 0, output: out || (code === 0 ? "" : `Exit code: ${code}`) }));
23690
+ child.on("error", (err) => resolve15({ ok: false, output: err.message }));
22356
23691
  });
22357
23692
  }
22358
23693
  function validateCommandName(name, existing) {
@@ -22403,8 +23738,8 @@ function truncateText(s, max) {
22403
23738
  async function listMentionDir(cwd, query) {
22404
23739
  const exact = query === "~" || query === "." || query === ".." || query.endsWith("/..");
22405
23740
  const dir = exact ? `${query}/` : query.slice(0, query.lastIndexOf("/") + 1);
22406
- const expanded = dir.startsWith("~") ? join33(homedir22(), dir.slice(1)) : dir;
22407
- const target = resolve11(cwd, expanded || ".");
23741
+ const expanded = dir.startsWith("~") ? join34(homedir23(), dir.slice(1)) : dir;
23742
+ const target = resolve12(cwd, expanded || ".");
22408
23743
  try {
22409
23744
  const items = await readdir6(target, { withFileTypes: true });
22410
23745
  const entries = items.filter((d) => !d.name.startsWith(".")).map((d) => ({ name: d.name, isDir: d.isDirectory() })).sort((a, b) => a.isDir !== b.isDir ? a.isDir ? -1 : 1 : a.name.localeCompare(b.name)).slice(0, 300).map((d) => `${dir}${d.name}${d.isDir ? "/" : ""}`);
@@ -22485,7 +23820,7 @@ function shortModel(id) {
22485
23820
  return id.includes("/") ? id.slice(id.indexOf("/") + 1) : id;
22486
23821
  }
22487
23822
  function shortPath(p) {
22488
- const home = homedir22();
23823
+ const home = homedir23();
22489
23824
  return p === home ? "~" : p.startsWith(home + "/") ? `~${p.slice(home.length)}` : p;
22490
23825
  }
22491
23826
  function gitBranch(cwd) {
@@ -25305,19 +26640,19 @@ function usePermissionController(getMode, onPlanModeBlocked) {
25305
26640
  const onPlanModeBlockedRef = useRef3(onPlanModeBlocked);
25306
26641
  onPlanModeBlockedRef.current = onPlanModeBlocked;
25307
26642
  const askPermission = useCallback2(
25308
- (req, askOpts) => new Promise((resolve14) => {
26643
+ (req, askOpts) => new Promise((resolve15) => {
25309
26644
  const outcome = decidePermission(req, getModeRef.current(), askOpts);
25310
26645
  if (outcome.kind === "resolve") {
25311
- resolve14(outcome.decision);
26646
+ resolve15(outcome.decision);
25312
26647
  return;
25313
26648
  }
25314
26649
  if (outcome.kind === "plan_blocked") {
25315
26650
  onPlanModeBlockedRef.current(outcome.toolName);
25316
- resolve14(AUTO_DENY);
26651
+ resolve15(AUTO_DENY);
25317
26652
  return;
25318
26653
  }
25319
- resolveRef.current = resolve14;
25320
- setPending({ tool: req.tool, args: req.args, resolve: resolve14 });
26654
+ resolveRef.current = resolve15;
26655
+ setPending({ tool: req.tool, args: req.args, resolve: resolve15 });
25321
26656
  }),
25322
26657
  []
25323
26658
  );
@@ -25956,15 +27291,15 @@ __export(openrouter_oauth_exports, {
25956
27291
  pkceChallenge: () => pkceChallenge,
25957
27292
  startLoopbackListener: () => startLoopbackListener
25958
27293
  });
25959
- import { createHash as createHash4, randomBytes as randomBytes3 } from "crypto";
27294
+ import { createHash as createHash5, randomBytes as randomBytes4 } from "crypto";
25960
27295
  import { createServer as createServer2 } from "http";
25961
27296
  import { hostname } from "os";
25962
27297
  function createPkce() {
25963
- const verifier = randomBytes3(32).toString("base64url");
27298
+ const verifier = randomBytes4(32).toString("base64url");
25964
27299
  return { verifier, challenge: pkceChallenge(verifier) };
25965
27300
  }
25966
27301
  function pkceChallenge(verifier) {
25967
- return createHash4("sha256").update(verifier).digest("base64url");
27302
+ return createHash5("sha256").update(verifier).digest("base64url");
25968
27303
  }
25969
27304
  function openRouterAuthPageUrl() {
25970
27305
  return (process.env.OPENROUTER_AUTH_URL?.trim() || "https://openrouter.ai/auth").replace(/\/+$/, "");
@@ -26047,9 +27382,9 @@ async function startLoopbackListener(opts2 = {}) {
26047
27382
  rejectCode(e);
26048
27383
  finish();
26049
27384
  }
26050
- await new Promise((resolve14, reject) => {
27385
+ await new Promise((resolve15, reject) => {
26051
27386
  server.once("error", reject);
26052
- server.listen(0, "127.0.0.1", () => resolve14());
27387
+ server.listen(0, "127.0.0.1", () => resolve15());
26053
27388
  });
26054
27389
  const address = server.address();
26055
27390
  const port = typeof address === "object" && address ? address.port : 0;
@@ -26475,15 +27810,15 @@ var init_frontmatter = __esm({
26475
27810
  });
26476
27811
 
26477
27812
  // src/commands/loader.ts
26478
- import { open, realpath as realpath4 } from "fs/promises";
26479
- import { homedir as homedir23 } from "os";
26480
- import { join as join34, relative as relative8, sep as sep4 } from "path";
27813
+ import { open, realpath as realpath6 } from "fs/promises";
27814
+ import { homedir as homedir24 } from "os";
27815
+ import { join as join35, relative as relative10, sep as sep6 } from "path";
26481
27816
  function projectCommandsDir(cwd = process.cwd()) {
26482
- return join34(cwd, ".kimiflare", "commands");
27817
+ return join35(cwd, ".kimiflare", "commands");
26483
27818
  }
26484
27819
  function globalCommandsDir() {
26485
- const xdg = process.env.XDG_CONFIG_HOME || join34(homedir23(), ".config");
26486
- return join34(xdg, "kimiflare", "commands");
27820
+ const xdg = process.env.XDG_CONFIG_HOME || join35(homedir24(), ".config");
27821
+ return join35(xdg, "kimiflare", "commands");
26487
27822
  }
26488
27823
  async function loadCustomCommands(cwd = process.cwd()) {
26489
27824
  const warnings = [];
@@ -26522,7 +27857,7 @@ async function loadCustomCommands(cwd = process.cwd()) {
26522
27857
  async function resolveSafeDir(dir, source, cwd, warnings) {
26523
27858
  let realDir;
26524
27859
  try {
26525
- realDir = await realpath4(dir);
27860
+ realDir = await realpath6(dir);
26526
27861
  } catch (err) {
26527
27862
  const code = err.code;
26528
27863
  if (code !== "ENOENT" && code !== "ENOTDIR") {
@@ -26533,11 +27868,11 @@ async function resolveSafeDir(dir, source, cwd, warnings) {
26533
27868
  if (source === "project") {
26534
27869
  let realCwd;
26535
27870
  try {
26536
- realCwd = await realpath4(cwd);
27871
+ realCwd = await realpath6(cwd);
26537
27872
  } catch {
26538
27873
  return null;
26539
27874
  }
26540
- const rel = relative8(realCwd, realDir);
27875
+ const rel = relative10(realCwd, realDir);
26541
27876
  if (rel !== "" && isPathOutside(rel)) {
26542
27877
  warnings.push(`commands dir ${dir} escapes workspace via symlink \u2014 skipped`);
26543
27878
  return null;
@@ -26612,10 +27947,10 @@ async function loadOne(file, rootDir, source, warnings) {
26612
27947
  return cmd;
26613
27948
  }
26614
27949
  function filenameToCommandName(file, rootDir) {
26615
- const rel = relative8(rootDir, file);
27950
+ const rel = relative10(rootDir, file);
26616
27951
  if (!rel || isPathOutside(rel)) return null;
26617
27952
  const noExt = rel.replace(/\.md$/i, "");
26618
- const parts = noExt.split(sep4).filter((p) => p.length > 0);
27953
+ const parts = noExt.split(sep6).filter((p) => p.length > 0);
26619
27954
  if (parts.length === 0) return null;
26620
27955
  if (parts.some((p) => !/^[\w.-]+$/.test(p))) return null;
26621
27956
  return parts.join("/");
@@ -26635,8 +27970,8 @@ var init_loader = __esm({
26635
27970
 
26636
27971
  // src/commands/renderer.ts
26637
27972
  import { exec } from "child_process";
26638
- import { open as open2, realpath as realpath5 } from "fs/promises";
26639
- import { isAbsolute as isAbsolute4, relative as relative9, resolve as resolvePathJoin, basename as pathBasename } from "path";
27973
+ import { open as open2, realpath as realpath7 } from "fs/promises";
27974
+ import { isAbsolute as isAbsolute6, relative as relative11, resolve as resolvePathJoin, basename as pathBasename } from "path";
26640
27975
  import { promisify as promisify3 } from "util";
26641
27976
  function tokenizeArgs(s) {
26642
27977
  return [...s.matchAll(ARG_TOKEN_RE)].map((match) => {
@@ -26727,7 +28062,7 @@ async function replaceFiles(prompt, warnings, cmd, cwd, maxFileBytes) {
26727
28062
  return "" + trailing;
26728
28063
  });
26729
28064
  }
26730
- const realCwd = await realpath5(cwd).catch(() => cwd);
28065
+ const realCwd = await realpath7(cwd).catch(() => cwd);
26731
28066
  const replacements = await Promise.all(
26732
28067
  matches.map(async (match) => {
26733
28068
  const rawPath = match[1] ?? "";
@@ -26736,23 +28071,23 @@ async function replaceFiles(prompt, warnings, cmd, cwd, maxFileBytes) {
26736
28071
  warnings.push(`file inclusion blocked: @${rawPath} \u2014 secret file pattern`);
26737
28072
  return "";
26738
28073
  }
26739
- if (isAbsolute4(rawPath) || rawPath.startsWith("~")) {
28074
+ if (isAbsolute6(rawPath) || rawPath.startsWith("~")) {
26740
28075
  warnings.push(`file inclusion skipped: @${rawPath} \u2014 outside workspace`);
26741
28076
  return "";
26742
28077
  }
26743
28078
  const resolved = resolvePathJoin(cwd, rawPath);
26744
- if (isPathOutside(relative9(cwd, resolved))) {
28079
+ if (isPathOutside(relative11(cwd, resolved))) {
26745
28080
  warnings.push(`file inclusion skipped: @${rawPath} \u2014 outside workspace`);
26746
28081
  return "";
26747
28082
  }
26748
28083
  let real;
26749
28084
  try {
26750
- real = await realpath5(resolved);
28085
+ real = await realpath7(resolved);
26751
28086
  } catch (error) {
26752
28087
  warnings.push(`file inclusion failed: @${rawPath} \u2014 ${message(error)}`);
26753
28088
  return "";
26754
28089
  }
26755
- if (isPathOutside(relative9(realCwd, real))) {
28090
+ if (isPathOutside(relative11(realCwd, real))) {
26756
28091
  warnings.push(`file inclusion skipped: @${rawPath} \u2014 symlink escapes workspace`);
26757
28092
  return "";
26758
28093
  }
@@ -27547,8 +28882,8 @@ var init_one_dark = __esm({
27547
28882
  });
27548
28883
 
27549
28884
  // src/ui/theme.ts
27550
- function normalizeTheme(json2) {
27551
- const obj = json2;
28885
+ function normalizeTheme(json3) {
28886
+ const obj = json3;
27552
28887
  const palette = obj.palette;
27553
28888
  const normalizeDim = (v) => {
27554
28889
  if (v === void 0) return void 0;
@@ -27691,11 +29026,11 @@ var init_wcag = __esm({
27691
29026
  });
27692
29027
 
27693
29028
  // src/ui/theme-loader.ts
27694
- import { readFile as readFile22, readdir as readdir7 } from "fs/promises";
27695
- import { join as join35 } from "path";
27696
- import { homedir as homedir24 } from "os";
29029
+ import { readFile as readFile23, readdir as readdir7 } from "fs/promises";
29030
+ import { join as join36 } from "path";
29031
+ import { homedir as homedir25 } from "os";
27697
29032
  function projectThemesDir(cwd = process.cwd()) {
27698
- return join35(cwd, ".kimiflare", "themes");
29033
+ return join36(cwd, ".kimiflare", "themes");
27699
29034
  }
27700
29035
  function isHexColor(c) {
27701
29036
  return /^#[0-9a-fA-F]{6}$/.test(c);
@@ -27784,26 +29119,26 @@ async function loadThemesFromDir(dir, source) {
27784
29119
  return { themes, errors };
27785
29120
  }
27786
29121
  for (const file of files.filter((f) => f.endsWith(".json"))) {
27787
- const path = join35(dir, file);
29122
+ const path = join36(dir, file);
27788
29123
  let raw;
27789
29124
  try {
27790
- raw = await readFile22(path, "utf-8");
29125
+ raw = await readFile23(path, "utf-8");
27791
29126
  } catch (e) {
27792
29127
  errors.push(`${path}: ${e instanceof Error ? e.message : String(e)}`);
27793
29128
  continue;
27794
29129
  }
27795
- let json2;
29130
+ let json3;
27796
29131
  try {
27797
- json2 = JSON.parse(raw);
29132
+ json3 = JSON.parse(raw);
27798
29133
  } catch (e) {
27799
29134
  errors.push(`${path}: invalid JSON \u2014 ${e instanceof Error ? e.message : String(e)}`);
27800
29135
  continue;
27801
29136
  }
27802
- if (!json2 || typeof json2 !== "object") {
29137
+ if (!json3 || typeof json3 !== "object") {
27803
29138
  errors.push(`${path}: root must be an object`);
27804
29139
  continue;
27805
29140
  }
27806
- const obj = json2;
29141
+ const obj = json3;
27807
29142
  const fileErrors = [];
27808
29143
  if (typeof obj.name !== "string" || obj.name.length === 0) {
27809
29144
  fileErrors.push("name is required");
@@ -27933,8 +29268,8 @@ var init_theme_loader = __esm({
27933
29268
  "use strict";
27934
29269
  init_wcag();
27935
29270
  init_theme();
27936
- USER_THEMES_DIR = join35(
27937
- process.env.XDG_CONFIG_HOME || join35(homedir24(), ".config"),
29271
+ USER_THEMES_DIR = join36(
29272
+ process.env.XDG_CONFIG_HOME || join36(homedir25(), ".config"),
27938
29273
  "kimiflare",
27939
29274
  "themes"
27940
29275
  );
@@ -31053,8 +32388,8 @@ var init_input_handlers = __esm({
31053
32388
 
31054
32389
  // src/agent/jev-context.ts
31055
32390
  import { execFileSync as execFileSync4 } from "child_process";
31056
- import { readFile as readFile23 } from "fs/promises";
31057
- import { basename as basename6, join as join36, resolve as resolve12 } from "path";
32391
+ import { readFile as readFile24 } from "fs/promises";
32392
+ import { basename as basename6, join as join37, resolve as resolve13 } from "path";
31058
32393
  function messageText(message2) {
31059
32394
  if (typeof message2.content === "string") return message2.content.trim();
31060
32395
  if (!Array.isArray(message2.content)) return "";
@@ -31071,12 +32406,12 @@ function projectRoot(cwd) {
31071
32406
  timeout: 1e3
31072
32407
  }).trim();
31073
32408
  } catch {
31074
- return resolve12(cwd);
32409
+ return resolve13(cwd);
31075
32410
  }
31076
32411
  }
31077
32412
  async function readPackageMetadata(root) {
31078
32413
  try {
31079
- const parsed = JSON.parse(await readFile23(join36(root, "package.json"), "utf8"));
32414
+ const parsed = JSON.parse(await readFile24(join37(root, "package.json"), "utf8"));
31080
32415
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return {};
31081
32416
  const pkg = parsed;
31082
32417
  const metadata = {};
@@ -31093,7 +32428,7 @@ async function readPackageMetadata(root) {
31093
32428
  async function readLicenseEvidence(root) {
31094
32429
  for (const name of ["LICENSE", "LICENSE.md", "LICENSE.txt", "LICENCE", "COPYING"]) {
31095
32430
  try {
31096
- const content = (await readFile23(join36(root, name), "utf8")).trim();
32431
+ const content = (await readFile24(join37(root, name), "utf8")).trim();
31097
32432
  if (content) return { source: `project license (${name})`, content: content.slice(0, 700) };
31098
32433
  } catch {
31099
32434
  }
@@ -31126,7 +32461,7 @@ async function buildJevContext(question, messages, cwd = process.cwd()) {
31126
32461
  conversationRemaining -= source.length + content.length + 2;
31127
32462
  }
31128
32463
  }
31129
- const currentDirectory = basename6(resolve12(cwd));
32464
+ const currentDirectory = basename6(resolve13(cwd));
31130
32465
  const escapedDirectory = currentDirectory.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
31131
32466
  const namesCurrentDirectory = currentDirectory.length >= 5 && !/^(?:agent|app|code|lib|project|repo|src|test|tests|workspace)$/i.test(currentDirectory) && new RegExp(`\\b${escapedDirectory}\\b`, "i").test(question);
31132
32467
  if (CURRENT_PROJECT_REFERENCE.test(question) || namesCurrentDirectory) {
@@ -31349,8 +32684,8 @@ var init_frontmatter2 = __esm({
31349
32684
  });
31350
32685
 
31351
32686
  // src/skills/loader.ts
31352
- import { readFile as readFile24, readdir as readdir8, stat as stat7 } from "fs/promises";
31353
- import { join as join37, extname } from "path";
32687
+ import { readFile as readFile25, readdir as readdir8, stat as stat8 } from "fs/promises";
32688
+ import { join as join38, extname } from "path";
31354
32689
  function normalizeManifest(raw, filePath) {
31355
32690
  const name = typeof raw.name === "string" ? raw.name : "";
31356
32691
  const description = typeof raw.description === "string" ? raw.description : "";
@@ -31364,7 +32699,7 @@ function normalizeManifest(raw, filePath) {
31364
32699
  return { name, description, match, scope, priority, enabled: enabled2 };
31365
32700
  }
31366
32701
  async function loadSkillFile(filePath) {
31367
- const raw = await readFile24(filePath, "utf-8");
32702
+ const raw = await readFile25(filePath, "utf-8");
31368
32703
  const parsed = parseFrontmatter2(raw);
31369
32704
  const manifest = normalizeManifest(parsed.data, filePath);
31370
32705
  const body = parsed.content.trim();
@@ -31386,8 +32721,8 @@ async function loadSkillsFromDir(dirPath) {
31386
32721
  const entries = await readdir8(dirPath);
31387
32722
  const files = [];
31388
32723
  for (const entry of entries) {
31389
- const full = join37(dirPath, entry);
31390
- const s = await stat7(full);
32724
+ const full = join38(dirPath, entry);
32725
+ const s = await stat8(full);
31391
32726
  if (s.isFile() && extname(entry) === ".md") {
31392
32727
  files.push(full);
31393
32728
  }
@@ -31414,12 +32749,12 @@ var init_loader2 = __esm({
31414
32749
  });
31415
32750
 
31416
32751
  // src/skills/manager.ts
31417
- import { mkdir as mkdir13, writeFile as writeFile14, unlink as unlink3, readFile as readFile25 } from "fs/promises";
31418
- import { join as join38 } from "path";
32752
+ import { mkdir as mkdir13, writeFile as writeFile14, unlink as unlink4, readFile as readFile26 } from "fs/promises";
32753
+ import { join as join39 } from "path";
31419
32754
  function getSkillDirs(cwd) {
31420
32755
  return {
31421
- projectDir: join38(cwd, ".kimiflare", "skills"),
31422
- globalDir: join38(process.env.HOME ?? "", ".config", "kimiflare", "skills")
32756
+ projectDir: join39(cwd, ".kimiflare", "skills"),
32757
+ globalDir: join39(process.env.HOME ?? "", ".config", "kimiflare", "skills")
31423
32758
  };
31424
32759
  }
31425
32760
  async function listAllSkills(cwd) {
@@ -31433,7 +32768,7 @@ async function listAllSkills(cwd) {
31433
32768
  async function createSkill(opts2) {
31434
32769
  const dirs = getSkillDirs(opts2.cwd);
31435
32770
  const dir = opts2.scope === "project" ? dirs.projectDir : dirs.globalDir;
31436
- const filepath = join38(dir, `${opts2.name}.md`);
32771
+ const filepath = join39(dir, `${opts2.name}.md`);
31437
32772
  const frontmatter = {
31438
32773
  name: opts2.name,
31439
32774
  enabled: true,
@@ -31462,14 +32797,14 @@ async function deleteSkill(name, cwd) {
31462
32797
  const all = await listAllSkills(cwd);
31463
32798
  const skill = all.project.find((s) => s.name === name) ?? all.global.find((s) => s.name === name);
31464
32799
  if (!skill) throw new Error(`skill "${name}" not found`);
31465
- await unlink3(skill.filePath);
32800
+ await unlink4(skill.filePath);
31466
32801
  return { filepath: skill.filePath };
31467
32802
  }
31468
32803
  async function setSkillEnabled(name, enabled2, cwd) {
31469
32804
  const all = await listAllSkills(cwd);
31470
32805
  const skill = all.project.find((s) => s.name === name) ?? all.global.find((s) => s.name === name);
31471
32806
  if (!skill) throw new Error(`skill "${name}" not found`);
31472
- const raw = await readFile25(skill.filePath, "utf-8");
32807
+ const raw = await readFile26(skill.filePath, "utf-8");
31473
32808
  const parsed = parseFrontmatter2(raw);
31474
32809
  parsed.data.enabled = enabled2;
31475
32810
  const yaml = Object.entries(parsed.data).map(([k, v]) => {
@@ -31562,15 +32897,15 @@ var tui_report_exports = {};
31562
32897
  __export(tui_report_exports, {
31563
32898
  getCategoryReportText: () => getCategoryReportText
31564
32899
  });
31565
- import { readFile as readFile26 } from "fs/promises";
31566
- import { join as join39 } from "path";
31567
- import { homedir as homedir25 } from "os";
32900
+ import { readFile as readFile27 } from "fs/promises";
32901
+ import { join as join40 } from "path";
32902
+ import { homedir as homedir26 } from "os";
31568
32903
  function usageDir3() {
31569
- const xdg = process.env.XDG_DATA_HOME || join39(homedir25(), ".local", "share");
31570
- return join39(xdg, "kimiflare");
32904
+ const xdg = process.env.XDG_DATA_HOME || join40(homedir26(), ".local", "share");
32905
+ return join40(xdg, "kimiflare");
31571
32906
  }
31572
32907
  function usagePath4() {
31573
- return join39(usageDir3(), "usage.json");
32908
+ return join40(usageDir3(), "usage.json");
31574
32909
  }
31575
32910
  function today3() {
31576
32911
  return (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
@@ -31582,7 +32917,7 @@ function daysAgo2(n) {
31582
32917
  }
31583
32918
  async function loadLog3() {
31584
32919
  try {
31585
- const raw = await readFile26(usagePath4(), "utf8");
32920
+ const raw = await readFile27(usagePath4(), "utf8");
31586
32921
  return JSON.parse(raw);
31587
32922
  } catch {
31588
32923
  return { version: 1, days: [], sessions: [] };
@@ -31639,7 +32974,7 @@ var init_tui_report = __esm({
31639
32974
  });
31640
32975
 
31641
32976
  // src/ui/slash-commands.ts
31642
- import { join as join40 } from "path";
32977
+ import { join as join41 } from "path";
31643
32978
  import QRCode from "qrcode";
31644
32979
  function executeFreshStart(ctx, planText, overrideMode, opts2 = {}) {
31645
32980
  const oldSessionId = ctx.sessionIdRef.current;
@@ -32040,7 +33375,7 @@ auto (default) lets the coordinator assess substantial tasks and delegate indepe
32040
33375
  const context = noContext ? [] : await buildJevContext(question.prompt, ctx.messagesRef.current, process.cwd());
32041
33376
  const receipt = formatJevContextReceipt(context, showContext);
32042
33377
  setResult(eventKey, { kind: "info", key: eventKey, text: `Jev is evaluating \xB7 ${receipt}` });
32043
- await new Promise((resolve14) => setTimeout(resolve14, 0));
33378
+ await new Promise((resolve15) => setTimeout(resolve15, 0));
32044
33379
  const answer = await askJev(apiKey, question, { context });
32045
33380
  const presentation = presentJevAnswer(question, answer);
32046
33381
  setResult(eventKey, {
@@ -32563,7 +33898,7 @@ ${lines.join("\n")}` }]);
32563
33898
  void (async () => {
32564
33899
  try {
32565
33900
  const { sessionsDir: sessionsDir3 } = await Promise.resolve().then(() => (init_sessions(), sessions_exports));
32566
- const file = await loadSession(join40(sessionsDir3(), `${currentId}.json`));
33901
+ const file = await loadSession(join41(sessionsDir3(), `${currentId}.json`));
32567
33902
  const cps = file.checkpoints ?? [];
32568
33903
  if (cps.length === 0) {
32569
33904
  setEvents((e) => [...e, { kind: "info", key: mkKey2(), text: "no checkpoints in this session" }]);
@@ -32608,7 +33943,7 @@ ${lines.join("\n")}` }]);
32608
33943
  try {
32609
33944
  ctx.ensureSessionId();
32610
33945
  const { sessionsDir: sessionsDir3 } = await Promise.resolve().then(() => (init_sessions(), sessions_exports));
32611
- const filePath = join40(sessionsDir3(), `${ctx.sessionIdRef.current}.json`);
33946
+ const filePath = join41(sessionsDir3(), `${ctx.sessionIdRef.current}.json`);
32612
33947
  await addCheckpoint(filePath, cp);
32613
33948
  setEvents((e) => [...e, { kind: "info", key: mkKey2(), text: `checkpoint saved: "${label}"` }]);
32614
33949
  } catch (e) {
@@ -33240,12 +34575,12 @@ project: ${projectSettingsPath(cwd)}`
33240
34575
 
33241
34576
  // src/init/context-generator.ts
33242
34577
  import { existsSync as existsSync6, statSync as statSync6 } from "fs";
33243
- import { join as join41 } from "path";
34578
+ import { join as join42 } from "path";
33244
34579
  function detectFlavor(cwd) {
33245
34580
  for (const [flavor, signatures] of Object.entries(FLAVOR_SIGNATURES)) {
33246
34581
  if (flavor === "generic") continue;
33247
34582
  for (const sig of signatures) {
33248
- const path = join41(cwd, sig);
34583
+ const path = join42(cwd, sig);
33249
34584
  if (sig.includes("*")) {
33250
34585
  try {
33251
34586
  const parts = sig.split("*");
@@ -33266,14 +34601,14 @@ function detectFlavor(cwd) {
33266
34601
  }
33267
34602
  function findFile(cwd, candidates) {
33268
34603
  for (const c of candidates) {
33269
- if (existsSync6(join41(cwd, c))) return c;
34604
+ if (existsSync6(join42(cwd, c))) return c;
33270
34605
  }
33271
34606
  return null;
33272
34607
  }
33273
34608
  function findSourceRoots(cwd) {
33274
34609
  const roots = [];
33275
34610
  for (const r of SOURCE_ROOT_CANDIDATES) {
33276
- const p = join41(cwd, r);
34611
+ const p = join42(cwd, r);
33277
34612
  try {
33278
34613
  const s = statSync6(p);
33279
34614
  if (s.isDirectory()) roots.push(r);
@@ -33284,9 +34619,9 @@ function findSourceRoots(cwd) {
33284
34619
  }
33285
34620
  function findCiConfig(cwd) {
33286
34621
  for (const c of CI_PATHS) {
33287
- if (existsSync6(join41(cwd, c))) {
34622
+ if (existsSync6(join42(cwd, c))) {
33288
34623
  try {
33289
- const s = statSync6(join41(cwd, c));
34624
+ const s = statSync6(join42(cwd, c));
33290
34625
  return s.isDirectory() ? c : c;
33291
34626
  } catch {
33292
34627
  }
@@ -33423,7 +34758,7 @@ function analyzeProject(cwd) {
33423
34758
  ciConfig: findCiConfig(cwd),
33424
34759
  readme: findFile(cwd, ["README.md", "README.rst", "README.txt", "Readme.md"]),
33425
34760
  sourceRoots: findSourceRoots(cwd),
33426
- hasGit: existsSync6(join41(cwd, ".git"))
34761
+ hasGit: existsSync6(join42(cwd, ".git"))
33427
34762
  };
33428
34763
  }
33429
34764
  function bashDiscoveryCommands(profile) {
@@ -33588,7 +34923,7 @@ Aim for 100\u2013200 lines total. Use markdown tables where they save space.
33588
34923
  }
33589
34924
  function buildInitPrompt(cwd) {
33590
34925
  const existingName = ["AGENTS.md", "KIMI.md", "KIMIFLARE.md", "AGENT.md"].find(
33591
- (n) => existsSync6(join41(cwd, n))
34926
+ (n) => existsSync6(join42(cwd, n))
33592
34927
  );
33593
34928
  const isRefresh = existingName !== void 0;
33594
34929
  const targetFilename = existingName ?? "AGENTS.md";
@@ -33672,7 +35007,7 @@ var init_context_generator = __esm({
33672
35007
 
33673
35008
  // src/init/run-init.ts
33674
35009
  import { existsSync as existsSync7 } from "fs";
33675
- import { join as join42 } from "path";
35010
+ import { join as join43 } from "path";
33676
35011
  async function runInit(deps) {
33677
35012
  const {
33678
35013
  cfg,
@@ -33765,7 +35100,7 @@ async function runInit(deps) {
33765
35100
  lspManagerRef.current.notifyChange(path, content);
33766
35101
  } else {
33767
35102
  void import("fs/promises").then(
33768
- ({ readFile: readFile29 }) => readFile29(path, "utf8").then((c) => lspManagerRef.current.notifyChange(path, c)).catch(() => {
35103
+ ({ readFile: readFile30 }) => readFile30(path, "utf8").then((c) => lspManagerRef.current.notifyChange(path, c)).catch(() => {
33769
35104
  })
33770
35105
  );
33771
35106
  }
@@ -33870,7 +35205,7 @@ async function runInit(deps) {
33870
35205
  }
33871
35206
  }
33872
35207
  });
33873
- if (existsSync7(join42(cwd, targetFilename))) {
35208
+ if (existsSync7(join43(cwd, targetFilename))) {
33874
35209
  if (cacheStableRef.current) {
33875
35210
  messagesRef.current[1] = {
33876
35211
  role: "system",
@@ -33962,11 +35297,11 @@ var init_run_init = __esm({
33962
35297
  });
33963
35298
 
33964
35299
  // src/skills/discovery.ts
33965
- import { readdir as readdir9, stat as stat8, readFile as readFile27 } from "fs/promises";
33966
- import { join as join43, extname as extname2 } from "path";
35300
+ import { readdir as readdir9, stat as stat9, readFile as readFile28 } from "fs/promises";
35301
+ import { join as join44, extname as extname2 } from "path";
33967
35302
  async function dirExists(path) {
33968
35303
  try {
33969
- const s = await stat8(path);
35304
+ const s = await stat9(path);
33970
35305
  return s.isDirectory();
33971
35306
  } catch {
33972
35307
  return false;
@@ -33974,7 +35309,7 @@ async function dirExists(path) {
33974
35309
  }
33975
35310
  async function fileExists(path) {
33976
35311
  try {
33977
- const s = await stat8(path);
35312
+ const s = await stat9(path);
33978
35313
  return s.isFile();
33979
35314
  } catch {
33980
35315
  return false;
@@ -33987,20 +35322,20 @@ async function scanSkillDir(dirPath, source) {
33987
35322
  for (const entry of entries) {
33988
35323
  if (!entry.isFile()) continue;
33989
35324
  if (!SKILL_EXTENSIONS.has(extname2(entry.name))) continue;
33990
- files.push({ filePath: join43(dirPath, entry.name), source });
35325
+ files.push({ filePath: join44(dirPath, entry.name), source });
33991
35326
  }
33992
35327
  return files;
33993
35328
  }
33994
35329
  async function discoverSkills(cwd) {
33995
- const agentsSkills = await scanSkillDir(join43(cwd, ".agents", "skills"), "agents");
33996
- const agentsMd = await fileExists(join43(cwd, "AGENTS.md")) ? [{ filePath: join43(cwd, "AGENTS.md"), source: "agents-md" }] : [];
33997
- const githubSkills = await scanSkillDir(join43(cwd, ".github", "skills"), "github");
33998
- const kimiflareSkills = await scanSkillDir(join43(cwd, ".kimiflare", "skills"), "kimiflare");
35330
+ const agentsSkills = await scanSkillDir(join44(cwd, ".agents", "skills"), "agents");
35331
+ const agentsMd = await fileExists(join44(cwd, "AGENTS.md")) ? [{ filePath: join44(cwd, "AGENTS.md"), source: "agents-md" }] : [];
35332
+ const githubSkills = await scanSkillDir(join44(cwd, ".github", "skills"), "github");
35333
+ const kimiflareSkills = await scanSkillDir(join44(cwd, ".kimiflare", "skills"), "kimiflare");
33999
35334
  const ordered = [...agentsSkills, ...agentsMd, ...githubSkills, ...kimiflareSkills];
34000
35335
  return ordered;
34001
35336
  }
34002
35337
  async function readSkillFile(filePath) {
34003
- const bytes = await readFile27(filePath);
35338
+ const bytes = await readFile28(filePath);
34004
35339
  return { bytes, text: bytes.toString("utf-8") };
34005
35340
  }
34006
35341
  var SKILL_EXTENSIONS;
@@ -34012,9 +35347,9 @@ var init_discovery = __esm({
34012
35347
  });
34013
35348
 
34014
35349
  // src/skills/parser.ts
34015
- import { createHash as createHash5 } from "crypto";
35350
+ import { createHash as createHash6 } from "crypto";
34016
35351
  function sha2562(input) {
34017
- return createHash5("sha256").update(input).digest("hex");
35352
+ return createHash6("sha256").update(input).digest("hex");
34018
35353
  }
34019
35354
  function computeContentHash(rawText, parserVersion) {
34020
35355
  return sha2562(`${rawText}
@@ -34206,7 +35541,7 @@ var init_skills = __esm({
34206
35541
 
34207
35542
  // src/ui/run-startup-tasks.ts
34208
35543
  import { existsSync as existsSync8 } from "fs";
34209
- import { join as join44 } from "path";
35544
+ import { join as join45 } from "path";
34210
35545
  function runStartupTasks(deps) {
34211
35546
  const {
34212
35547
  cfg,
@@ -34228,7 +35563,7 @@ function runStartupTasks(deps) {
34228
35563
  }
34229
35564
  });
34230
35565
  if (cfg.memoryEnabled) {
34231
- const dbPath = cfg.memoryDbPath ?? join44(process.cwd(), ".kimiflare", "memory.db");
35566
+ const dbPath = cfg.memoryDbPath ?? join45(process.cwd(), ".kimiflare", "memory.db");
34232
35567
  const manager = new MemoryManager({
34233
35568
  dbPath,
34234
35569
  ...auth,
@@ -34260,7 +35595,7 @@ function runStartupTasks(deps) {
34260
35595
  });
34261
35596
  const cwd = process.cwd();
34262
35597
  sessionStartRecallRef.current = manager.recall({ text: cwd, repoPath: cwd, limit: 5 });
34263
- if (["AGENTS.md", "KIMI.md", "KIMIFLARE.md", "AGENT.md"].some((name) => existsSync8(join44(cwd, name)))) {
35598
+ if (["AGENTS.md", "KIMI.md", "KIMIFLARE.md", "AGENT.md"].some((name) => existsSync8(join45(cwd, name)))) {
34264
35599
  const lastRefresh = manager.getLastKimiMdRefreshTime(cwd);
34265
35600
  const driftCount = manager.countHighSignalMemoriesSince(cwd, lastRefresh);
34266
35601
  if (driftCount >= 5) {
@@ -34271,7 +35606,7 @@ function runStartupTasks(deps) {
34271
35606
  memoryManagerRef.current?.close();
34272
35607
  memoryManagerRef.current = null;
34273
35608
  }
34274
- const skillDbPath = cfg.memoryDbPath ?? join44(process.cwd(), ".kimiflare", "memory.db");
35609
+ const skillDbPath = cfg.memoryDbPath ?? join45(process.cwd(), ".kimiflare", "memory.db");
34275
35610
  const skillDb = getMemoryDb() ?? openMemoryDb(skillDbPath);
34276
35611
  initSkillsSchema(skillDb);
34277
35612
  void indexSkills({
@@ -34624,9 +35959,9 @@ var init_run_compact = __esm({
34624
35959
 
34625
35960
  // src/ui/bang-command.ts
34626
35961
  import { spawn as spawn10 } from "child_process";
34627
- import { mkdtemp as mkdtemp2, readFile as readFile28, rm as rm3 } from "fs/promises";
35962
+ import { mkdtemp as mkdtemp2, readFile as readFile29, rm as rm3 } from "fs/promises";
34628
35963
  import { tmpdir as tmpdir4, platform as platform7 } from "os";
34629
- import { join as join45 } from "path";
35964
+ import { join as join46 } from "path";
34630
35965
  function registerInkInstance(instance) {
34631
35966
  inkInstance = instance;
34632
35967
  }
@@ -34695,10 +36030,10 @@ function swallow() {
34695
36030
  });
34696
36031
  }
34697
36032
  function spawnInherit(inv, cwd) {
34698
- return new Promise((resolve14, reject) => {
36033
+ return new Promise((resolve15, reject) => {
34699
36034
  const child = spawn10(inv.file, inv.args, { cwd, stdio: "inherit", env: inv.env ?? process.env });
34700
36035
  child.once("error", reject);
34701
- child.once("exit", (code, signal) => resolve14({ code, signal }));
36036
+ child.once("exit", (code, signal) => resolve15({ code, signal }));
34702
36037
  });
34703
36038
  }
34704
36039
  async function runBangCommand(command, cwd) {
@@ -34712,8 +36047,8 @@ async function runBangCommand(command, cwd) {
34712
36047
  const origErr = stderr.write;
34713
36048
  const readable = stdin.listeners("readable");
34714
36049
  const wasRaw = stdin.isTTY ? stdin.isRaw : false;
34715
- const logDir2 = isWindows ? null : await mkdtemp2(join45(tmpdir4(), "autopilot-bang-"));
34716
- const logPath = logDir2 ? join45(logDir2, "out.log") : null;
36050
+ const logDir2 = isWindows ? null : await mkdtemp2(join46(tmpdir4(), "autopilot-bang-"));
36051
+ const logPath = logDir2 ? join46(logDir2, "out.log") : null;
34717
36052
  const viaScript = logPath ? scriptInvocation(command, shell, logPath) : null;
34718
36053
  inkInstance?.clear();
34719
36054
  origOut.call(stdout, `
@@ -34754,7 +36089,7 @@ $ ${command}
34754
36089
  }
34755
36090
  let output = spawnError ? `failed to start: ${spawnError}` : null;
34756
36091
  if (captured && logPath) {
34757
- output = cleanTerminalOutput(await readFile28(logPath, "utf8").catch(() => ""));
36092
+ output = cleanTerminalOutput(await readFile29(logPath, "utf8").catch(() => ""));
34758
36093
  }
34759
36094
  if (logDir2) await rm3(logDir2, { recursive: true, force: true }).catch(() => {
34760
36095
  });
@@ -34772,8 +36107,8 @@ var init_bang_command = __esm({
34772
36107
  });
34773
36108
 
34774
36109
  // src/commands/save.ts
34775
- import { mkdir as mkdir14, writeFile as writeFile15, unlink as unlink4 } from "fs/promises";
34776
- import { dirname as dirname16 } from "path";
36110
+ import { mkdir as mkdir14, writeFile as writeFile15, unlink as unlink5 } from "fs/promises";
36111
+ import { dirname as dirname17 } from "path";
34777
36112
  async function saveCustomCommand(opts2) {
34778
36113
  const dir = opts2.source === "project" ? projectCommandsDir(opts2.cwd) : globalCommandsDir();
34779
36114
  const filepath = `${dir}/${opts2.name}.md`;
@@ -34784,12 +36119,12 @@ async function saveCustomCommand(opts2) {
34784
36119
  if (opts2.effort) data.effort = opts2.effort;
34785
36120
  const frontmatter = serializeFrontmatter(data);
34786
36121
  const content = frontmatter + opts2.template;
34787
- await mkdir14(dirname16(filepath), { recursive: true });
36122
+ await mkdir14(dirname17(filepath), { recursive: true });
34788
36123
  await writeFile15(filepath, content, "utf8");
34789
36124
  return { filepath };
34790
36125
  }
34791
36126
  async function deleteCustomCommand(cmd) {
34792
- await unlink4(cmd.filepath);
36127
+ await unlink5(cmd.filepath);
34793
36128
  }
34794
36129
  var init_save = __esm({
34795
36130
  "src/commands/save.ts"() {
@@ -34908,8 +36243,8 @@ __export(app_exports, {
34908
36243
  import React27, { useState as useState28, useRef as useRef9, useEffect as useEffect11, useCallback as useCallback9, useMemo as useMemo6 } from "react";
34909
36244
  import { Box as Box41, Text as Text42, useApp, useInput as useInput20, useWindowSize as useWindowSize2, render } from "ink";
34910
36245
  import { existsSync as existsSync9 } from "fs";
34911
- import { join as join46, resolve as resolve13 } from "path";
34912
- import { homedir as homedir26 } from "os";
36246
+ import { join as join47, resolve as resolve14 } from "path";
36247
+ import { homedir as homedir27 } from "os";
34913
36248
  import { jsx as jsx43, jsxs as jsxs40 } from "react/jsx-runtime";
34914
36249
  function App({
34915
36250
  initialCfg,
@@ -35231,8 +36566,8 @@ ${wcagWarnings.join("\n")}` }
35231
36566
  const exactDirectory = query === "~" || query === "." || query === ".." || query.endsWith("/..");
35232
36567
  const prefix = exactDirectory ? `${query.replace(/\/$/, "")}/` : slash >= 0 ? query.slice(0, slash + 1) : "";
35233
36568
  const directory = exactDirectory ? query : slash >= 0 ? query.slice(0, slash + 1) : query;
35234
- const expanded = directory.startsWith("~") ? join46(homedir26(), directory.slice(1)) : directory;
35235
- const target = resolve13(cwd, expanded || ".");
36569
+ const expanded = directory.startsWith("~") ? join47(homedir27(), directory.slice(1)) : directory;
36570
+ const target = resolve14(cwd, expanded || ".");
35236
36571
  const entries2 = await glob("*", {
35237
36572
  cwd: target,
35238
36573
  dot: false,
@@ -36208,7 +37543,7 @@ ${result2.output ?? "(output not captured)"}`,
36208
37543
  }
36209
37544
  }
36210
37545
  turnCounterRef.current += 1;
36211
- if (turnCounterRef.current % 15 === 0 && ["AGENTS.md", "KIMI.md", "KIMIFLARE.md", "AGENT.md"].some((name) => existsSync9(join46(process.cwd(), name))) && !kimiMdStale) {
37546
+ if (turnCounterRef.current % 15 === 0 && ["AGENTS.md", "KIMI.md", "KIMIFLARE.md", "AGENT.md"].some((name) => existsSync9(join47(process.cwd(), name))) && !kimiMdStale) {
36212
37547
  setEvents((e) => [
36213
37548
  ...e,
36214
37549
  { kind: "info", key: mkKey(), text: "Tip: Rerunning /init occasionally helps autopilot (formerly kimiflare) stay accurate as your project evolves." }
@@ -36232,7 +37567,7 @@ ${result2.output ?? "(output not captured)"}`,
36232
37567
  };
36233
37568
  ensureSessionId();
36234
37569
  const { sessionsDir: sessionsDir3 } = await Promise.resolve().then(() => (init_sessions(), sessions_exports));
36235
- const filePath = join46(sessionsDir3(), `${sessionIdRef.current}.json`);
37570
+ const filePath = join47(sessionsDir3(), `${sessionIdRef.current}.json`);
36236
37571
  await addCheckpoint(filePath, cp);
36237
37572
  }
36238
37573
  const summary = await generateContinuationSummary({
@@ -36600,7 +37935,7 @@ ${conflicts.join("\n")}` }
36600
37935
  lspManagerRef.current.notifyChange(path, content2);
36601
37936
  } else {
36602
37937
  void import("fs/promises").then(
36603
- ({ readFile: readFile29 }) => readFile29(path, "utf8").then((c) => lspManagerRef.current.notifyChange(path, c)).catch(() => {
37938
+ ({ readFile: readFile30 }) => readFile30(path, "utf8").then((c) => lspManagerRef.current.notifyChange(path, c)).catch(() => {
36604
37939
  })
36605
37940
  );
36606
37941
  }
@@ -36786,7 +38121,7 @@ ${conflicts.join("\n")}` }
36786
38121
  };
36787
38122
  ensureSessionId();
36788
38123
  const { sessionsDir: sessionsDir3 } = await Promise.resolve().then(() => (init_sessions(), sessions_exports));
36789
- const filePath = join46(sessionsDir3(), `${sessionIdRef.current}.json`);
38124
+ const filePath = join47(sessionsDir3(), `${sessionIdRef.current}.json`);
36790
38125
  await addCheckpoint(filePath, cp);
36791
38126
  }
36792
38127
  const summary = await generateContinuationSummary({
@@ -37202,7 +38537,7 @@ ${conflicts.join("\n")}` }
37202
38537
  lsp: {
37203
38538
  servers: cfg?.lspServers ?? {},
37204
38539
  scope: lspScope,
37205
- hasProjectDir: existsSync9(join46(process.cwd(), ".kimiflare"))
38540
+ hasProjectDir: existsSync9(join47(process.cwd(), ".kimiflare"))
37206
38541
  }
37207
38542
  });
37208
38543
  });
@@ -37259,7 +38594,7 @@ ${conflicts.join("\n")}` }
37259
38594
  onCommandDelete: handleCommandDelete2,
37260
38595
  lspServers: cfg?.lspServers ?? {},
37261
38596
  lspScope,
37262
- hasProjectDir: existsSync9(join46(process.cwd(), ".kimiflare")),
38597
+ hasProjectDir: existsSync9(join47(process.cwd(), ".kimiflare")),
37263
38598
  onLspSave: handleLspSave2,
37264
38599
  themes: themeList(),
37265
38600
  onPickTheme: handleThemePick,
@@ -37770,8 +39105,8 @@ async function resolveFiles(filePatterns, cwd) {
37770
39105
  const resolved = /* @__PURE__ */ new Set();
37771
39106
  for (const pattern of filePatterns) {
37772
39107
  try {
37773
- const stat9 = await import("fs/promises").then((m) => m.stat(resolve6(cwd, pattern)));
37774
- if (stat9.isFile()) {
39108
+ const stat10 = await import("fs/promises").then((m) => m.stat(resolve6(cwd, pattern)));
39109
+ if (stat10.isFile()) {
37775
39110
  resolved.add(resolve6(cwd, pattern));
37776
39111
  continue;
37777
39112
  }
@@ -38110,6 +39445,55 @@ program.command("cost").description("Show cost attribution by task type (require
38110
39445
  await runCostCommand2({ ...cmdOpts, config: cfg });
38111
39446
  });
38112
39447
  program.addCommand(createRemoteCommand());
39448
+ var asterCommand = program.command("aster").description("Manage the Aster mobile API");
39449
+ var asterTokenCommand = asterCommand.command("token").description("Manage scoped Aster API credentials");
39450
+ asterTokenCommand.command("create").description("Create a 30-day credential scoped to selected workspace IDs").requiredOption("-n, --name <name>", "Credential label").requiredOption("-w, --workspace <id>", "Allowed workspace ID (repeatable)", (value, previous = []) => [...previous, value]).option("--expires-in-days <days>", "Expiry in days (1-365)", (value) => Number.parseInt(value, 10), 30).action(async (options) => {
39451
+ const { loadAsterServerConfig: loadAsterServerConfig2 } = await Promise.resolve().then(() => (init_aster_workspaces(), aster_workspaces_exports));
39452
+ const { AsterStore: AsterStore2 } = await Promise.resolve().then(() => (init_aster_store(), aster_store_exports));
39453
+ try {
39454
+ const config2 = await loadAsterServerConfig2();
39455
+ const workspaceIds = [...new Set(options.workspace)];
39456
+ if (!Number.isInteger(options.expiresInDays) || options.expiresInDays < 1 || options.expiresInDays > 365) {
39457
+ throw new Error("--expires-in-days must be an integer from 1 through 365");
39458
+ }
39459
+ if (workspaceIds.some((id) => !config2.workspaces.some((workspace) => workspace.id === id))) {
39460
+ throw new Error("credential includes an unknown workspace ID");
39461
+ }
39462
+ const store = new AsterStore2();
39463
+ try {
39464
+ const created = store.createCredential({
39465
+ name: options.name,
39466
+ workspaceIds,
39467
+ scopes: ["workspaces:read", "models:read", "conversations:read", "conversations:write", "approvals:resolve"],
39468
+ expiresAt: Date.now() + options.expiresInDays * 24 * 60 * 60 * 1e3
39469
+ });
39470
+ console.log(`Credential ID: ${created.id}`);
39471
+ console.log(`Workspace scope: ${created.workspaceIds.join(", ")}`);
39472
+ console.log(`Expires: ${new Date(created.expiresAt).toISOString()}`);
39473
+ console.log("Copy this token into the Aster app's Keychain now; it will not be shown again:");
39474
+ console.log(created.token);
39475
+ } finally {
39476
+ store.close();
39477
+ }
39478
+ } catch (error) {
39479
+ console.error(error instanceof Error ? error.message : String(error));
39480
+ process.exitCode = 2;
39481
+ }
39482
+ });
39483
+ asterTokenCommand.command("revoke").description("Revoke an Aster credential immediately").argument("<credential-id>").action(async (credentialId) => {
39484
+ const { AsterStore: AsterStore2 } = await Promise.resolve().then(() => (init_aster_store(), aster_store_exports));
39485
+ const store = new AsterStore2();
39486
+ try {
39487
+ if (!store.revokeCredential(credentialId)) {
39488
+ console.error("Aster credential not found");
39489
+ process.exitCode = 1;
39490
+ return;
39491
+ }
39492
+ console.log(`Revoked credential ${credentialId}`);
39493
+ } finally {
39494
+ store.close();
39495
+ }
39496
+ });
38113
39497
  var logsCmd = program.command("logs").description("Inspect KimiFlare's structured logs (jsonl, one file per day, 7-day retention)");
38114
39498
  logsCmd.command("path").description("Print today's log file path. Useful for tailing: tail -f $(autopilot logs path) | jq").action(async () => {
38115
39499
  const { logPathFor: logPathFor2 } = await Promise.resolve().then(() => (init_log_sink(), log_sink_exports));
@@ -38386,11 +39770,11 @@ async function promptHidden(question) {
38386
39770
  }
38387
39771
  });
38388
39772
  const rl = createInterface4({ input: process.stdin, output, terminal: true });
38389
- return new Promise((resolve14) => {
39773
+ return new Promise((resolve15) => {
38390
39774
  rl.question(question, (answer) => {
38391
39775
  rl.close();
38392
39776
  process.stdout.write("\n");
38393
- resolve14(answer);
39777
+ resolve15(answer);
38394
39778
  });
38395
39779
  muted = true;
38396
39780
  });