autopilot-ai 1.9.0 → 1.10.1

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