kimiflare 0.46.0 → 0.47.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1928,6 +1928,447 @@ var init_code_mode = __esm({
1928
1928
  }
1929
1929
  });
1930
1930
 
1931
+ // src/agent/session-state.ts
1932
+ function emptySessionState(task = "") {
1933
+ return {
1934
+ task,
1935
+ user_constraints: [],
1936
+ repo_facts: [],
1937
+ files_touched: [],
1938
+ files_modified: [],
1939
+ confirmed_findings: [],
1940
+ open_questions: [],
1941
+ recent_failures: [],
1942
+ decisions: [],
1943
+ next_actions: [],
1944
+ artifact_index: {}
1945
+ };
1946
+ }
1947
+ function serializeArtifactStore(store) {
1948
+ const MAX_ARTIFACT_CHARS = 5e4;
1949
+ const out = [];
1950
+ for (const a of store.list()) {
1951
+ out.push({
1952
+ id: a.id,
1953
+ type: a.type,
1954
+ summary: a.summary,
1955
+ raw: a.raw.slice(0, MAX_ARTIFACT_CHARS),
1956
+ source: a.source,
1957
+ path: a.path,
1958
+ lineRange: a.lineRange,
1959
+ ts: a.ts
1960
+ });
1961
+ }
1962
+ return out;
1963
+ }
1964
+ function deserializeArtifactStore(data) {
1965
+ const store = new ArtifactStore();
1966
+ for (const a of data) {
1967
+ store.add({
1968
+ id: a.id,
1969
+ type: a.type,
1970
+ summary: a.summary,
1971
+ raw: a.raw,
1972
+ source: a.source,
1973
+ path: a.path,
1974
+ lineRange: a.lineRange,
1975
+ ts: a.ts
1976
+ });
1977
+ }
1978
+ return store;
1979
+ }
1980
+ function formatRecalledArtifacts(recalled) {
1981
+ if (recalled.length === 0) return "";
1982
+ const lines = ["[recalled artifacts]"];
1983
+ for (const { id, artifact } of recalled) {
1984
+ lines.push(`--- artifact:${id} (${artifact.type} from ${artifact.source}) ---`);
1985
+ lines.push(artifact.raw);
1986
+ }
1987
+ return lines.join("\n");
1988
+ }
1989
+ function serializeSessionState(state) {
1990
+ const lines = [];
1991
+ lines.push(`task: ${state.task || "(none)"}`);
1992
+ if (state.user_constraints.length) lines.push(`constraints:
1993
+ ${state.user_constraints.map((c) => " - " + c).join("\n")}`);
1994
+ if (state.repo_facts.length) lines.push(`repo_facts:
1995
+ ${state.repo_facts.map((f) => " - " + f).join("\n")}`);
1996
+ if (state.files_touched.length) lines.push(`files_touched: ${state.files_touched.join(", ")}`);
1997
+ if (state.files_modified.length) lines.push(`files_modified: ${state.files_modified.join(", ")}`);
1998
+ if (state.confirmed_findings.length) lines.push(`findings:
1999
+ ${state.confirmed_findings.map((f) => " - " + f).join("\n")}`);
2000
+ if (state.open_questions.length) lines.push(`open_questions:
2001
+ ${state.open_questions.map((q) => " - " + q).join("\n")}`);
2002
+ if (state.recent_failures.length) lines.push(`recent_failures:
2003
+ ${state.recent_failures.map((f) => " - " + f).join("\n")}`);
2004
+ if (state.decisions.length) lines.push(`decisions:
2005
+ ${state.decisions.map((d) => " - " + d).join("\n")}`);
2006
+ if (state.next_actions.length) lines.push(`next_actions:
2007
+ ${state.next_actions.map((a) => " - " + a).join("\n")}`);
2008
+ if (Object.keys(state.artifact_index).length) {
2009
+ lines.push("artifact_index:");
2010
+ for (const [id, meta] of Object.entries(state.artifact_index)) {
2011
+ lines.push(` ${id}: [${meta.type}] ${meta.summary}`);
2012
+ }
2013
+ }
2014
+ return lines.join("\n");
2015
+ }
2016
+ function buildSessionStateMessage(state) {
2017
+ return {
2018
+ role: "system",
2019
+ content: `[compiled session state]
2020
+ ${serializeSessionState(state)}`
2021
+ };
2022
+ }
2023
+ var ArtifactStore;
2024
+ var init_session_state = __esm({
2025
+ "src/agent/session-state.ts"() {
2026
+ "use strict";
2027
+ ArtifactStore = class {
2028
+ artifacts = /* @__PURE__ */ new Map();
2029
+ maxArtifacts;
2030
+ maxTotalChars;
2031
+ constructor(opts2) {
2032
+ this.maxArtifacts = opts2?.maxArtifacts ?? 200;
2033
+ this.maxTotalChars = opts2?.maxTotalChars ?? 5e5;
2034
+ }
2035
+ add(a) {
2036
+ while (this.totalChars() + a.raw.length > this.maxTotalChars && this.artifacts.size > 0) {
2037
+ this.evictOldest();
2038
+ }
2039
+ while (this.artifacts.size >= this.maxArtifacts) {
2040
+ this.evictOldest();
2041
+ }
2042
+ this.artifacts.set(a.id, a);
2043
+ }
2044
+ get(id) {
2045
+ return this.artifacts.get(id);
2046
+ }
2047
+ has(id) {
2048
+ return this.artifacts.has(id);
2049
+ }
2050
+ list() {
2051
+ return [...this.artifacts.values()].sort((a, b) => a.ts < b.ts ? -1 : 1);
2052
+ }
2053
+ recall(ids) {
2054
+ const out = [];
2055
+ for (const id of ids) {
2056
+ const a = this.artifacts.get(id);
2057
+ if (a) out.push({ id, artifact: a });
2058
+ }
2059
+ return out;
2060
+ }
2061
+ size() {
2062
+ return this.artifacts.size;
2063
+ }
2064
+ totalChars() {
2065
+ let sum = 0;
2066
+ for (const a of this.artifacts.values()) {
2067
+ sum += a.raw.length;
2068
+ }
2069
+ return sum;
2070
+ }
2071
+ evictOldest() {
2072
+ let oldest;
2073
+ for (const a of this.artifacts.values()) {
2074
+ if (!oldest || a.ts < oldest.ts) oldest = a;
2075
+ }
2076
+ if (oldest) this.artifacts.delete(oldest.id);
2077
+ }
2078
+ };
2079
+ }
2080
+ });
2081
+
2082
+ // src/agent/compaction.ts
2083
+ function approxTokens2(n) {
2084
+ return Math.round(n / 4);
2085
+ }
2086
+ function estimateMessageTokens(m) {
2087
+ let chars = 0;
2088
+ if (typeof m.content === "string") {
2089
+ chars = m.content.length;
2090
+ } else if (Array.isArray(m.content)) {
2091
+ chars = m.content.map((p) => p.type === "text" ? p.text.length : 0).reduce((a, b) => a + b, 0);
2092
+ }
2093
+ if (m.reasoning_content) chars += m.reasoning_content.length;
2094
+ if (m.tool_calls) {
2095
+ for (const tc of m.tool_calls) {
2096
+ chars += tc.function.name.length + tc.function.arguments.length;
2097
+ }
2098
+ }
2099
+ return approxTokens2(chars);
2100
+ }
2101
+ function estimatePromptTokens(messages) {
2102
+ return messages.reduce((sum, m) => sum + estimateMessageTokens(m), 0);
2103
+ }
2104
+ function groupIntoTurns(messages) {
2105
+ const prefix = [];
2106
+ let i = 0;
2107
+ while (i < messages.length && messages[i].role === "system") {
2108
+ prefix.push(messages[i]);
2109
+ i++;
2110
+ }
2111
+ const turns = [];
2112
+ while (i < messages.length) {
2113
+ if (messages[i].role !== "user") {
2114
+ i++;
2115
+ continue;
2116
+ }
2117
+ const user = messages[i];
2118
+ i++;
2119
+ if (i >= messages.length || messages[i].role !== "assistant") {
2120
+ continue;
2121
+ }
2122
+ const assistant = messages[i];
2123
+ i++;
2124
+ const tools = [];
2125
+ while (i < messages.length && messages[i].role === "tool") {
2126
+ tools.push(messages[i]);
2127
+ i++;
2128
+ }
2129
+ turns.push({ user, assistant, tools });
2130
+ }
2131
+ return { prefix, turns };
2132
+ }
2133
+ function makeArtifactId(type, index) {
2134
+ return `${type}_${Date.now()}_${index}`;
2135
+ }
2136
+ function extractArtifactsFromTurn(turn, startIndex, store) {
2137
+ const artifacts = [];
2138
+ const stateDelta = {
2139
+ files_touched: [],
2140
+ files_modified: [],
2141
+ confirmed_findings: [],
2142
+ recent_failures: [],
2143
+ decisions: [],
2144
+ next_actions: []
2145
+ };
2146
+ const toolCalls = turn.assistant.tool_calls ?? [];
2147
+ for (let ti = 0; ti < turn.tools.length; ti++) {
2148
+ const tm = turn.tools[ti];
2149
+ const tc = toolCalls[ti];
2150
+ const name = tm.name ?? tc?.function.name ?? "unknown";
2151
+ const content = typeof tm.content === "string" ? tm.content : "";
2152
+ let type = "tool_result";
2153
+ let summary = `${name} result`;
2154
+ let path;
2155
+ if (name === "read") {
2156
+ type = "read_slice";
2157
+ try {
2158
+ const args = tc ? JSON.parse(tc.function.arguments) : {};
2159
+ path = args.path;
2160
+ summary = `read ${path ?? "file"}`;
2161
+ if (path && !stateDelta.files_touched.includes(path)) stateDelta.files_touched.push(path);
2162
+ } catch {
2163
+ summary = "read file";
2164
+ }
2165
+ } else if (name === "bash") {
2166
+ type = "bash_log";
2167
+ try {
2168
+ const args = tc ? JSON.parse(tc.function.arguments) : {};
2169
+ const cmd = args.command ?? "";
2170
+ summary = `bash: ${cmd.slice(0, 60)}`;
2171
+ if (content.includes("Error") || content.includes("error") || content.includes("FAIL")) {
2172
+ stateDelta.recent_failures.push(`bash failed: ${cmd.slice(0, 80)}`);
2173
+ }
2174
+ } catch {
2175
+ summary = "bash command";
2176
+ }
2177
+ } else if (name === "grep") {
2178
+ type = "grep_result";
2179
+ summary = `grep results (${content.split("\n").length} lines)`;
2180
+ } else if (name === "web_fetch") {
2181
+ type = "web_fetch";
2182
+ try {
2183
+ const args = tc ? JSON.parse(tc.function.arguments) : {};
2184
+ summary = `web_fetch: ${args.url ?? "url"}`;
2185
+ } catch {
2186
+ summary = "web_fetch";
2187
+ }
2188
+ } else if (name === "write" || name === "edit") {
2189
+ try {
2190
+ const args = tc ? JSON.parse(tc.function.arguments) : {};
2191
+ path = args.path;
2192
+ if (path && !stateDelta.files_modified.includes(path)) stateDelta.files_modified.push(path);
2193
+ if (path && !stateDelta.files_touched.includes(path)) stateDelta.files_touched.push(path);
2194
+ } catch {
2195
+ }
2196
+ continue;
2197
+ } else if (name === "glob") {
2198
+ try {
2199
+ const args = tc ? JSON.parse(tc.function.arguments) : {};
2200
+ summary = `glob: ${args.pattern ?? ""}`;
2201
+ } catch {
2202
+ summary = "glob";
2203
+ }
2204
+ } else if (name === "tasks_set") {
2205
+ try {
2206
+ const args = tc ? JSON.parse(tc.function.arguments) : {};
2207
+ const tasks = args.tasks ?? [];
2208
+ const inProgress = tasks.filter((t) => t.status === "in_progress").map((t) => t.title);
2209
+ const pending = tasks.filter((t) => t.status === "pending").map((t) => t.title);
2210
+ if (inProgress.length) stateDelta.next_actions.push(...inProgress);
2211
+ if (pending.length) stateDelta.next_actions.push(...pending);
2212
+ summary = `tasks_set: ${tasks.length} tasks`;
2213
+ } catch {
2214
+ summary = "tasks_set";
2215
+ }
2216
+ }
2217
+ const maxRaw = 5e4;
2218
+ const raw = content.length > maxRaw ? content.slice(0, maxRaw) + `
2219
+ ...[${content.length - maxRaw} chars truncated]` : content;
2220
+ const artifact = {
2221
+ id: makeArtifactId(type, startIndex + ti),
2222
+ type,
2223
+ summary,
2224
+ raw,
2225
+ source: name,
2226
+ path,
2227
+ ts: (/* @__PURE__ */ new Date()).toISOString()
2228
+ };
2229
+ artifacts.push(artifact);
2230
+ if (!content.includes("Error") && !content.includes("error") && content.length > 0 && content.length < 2e3) {
2231
+ stateDelta.confirmed_findings.push(`${name}: ${content.slice(0, 200)}`);
2232
+ }
2233
+ }
2234
+ const assistantText = typeof turn.assistant.content === "string" ? turn.assistant.content : "";
2235
+ if (assistantText.length > 0) {
2236
+ const decisionPatterns = [
2237
+ /(?:decided?|will|plan to|going to|should|need to)\s+(.{10,200})/gi,
2238
+ /(?:let's|let us)\s+(.{10,200})/gi
2239
+ ];
2240
+ for (const pattern of decisionPatterns) {
2241
+ let match;
2242
+ while ((match = pattern.exec(assistantText)) !== null) {
2243
+ const decision = match[1].trim().replace(/\.$/, "");
2244
+ if (decision.length > 10 && !stateDelta.decisions.includes(decision)) {
2245
+ stateDelta.decisions.push(decision);
2246
+ }
2247
+ }
2248
+ }
2249
+ }
2250
+ return { artifacts, stateDelta };
2251
+ }
2252
+ function mergeState(state, delta) {
2253
+ const mergeArr = (a, b) => {
2254
+ if (!b) return a;
2255
+ const set = new Set(a);
2256
+ for (const item of b) set.add(item);
2257
+ return [...set];
2258
+ };
2259
+ return {
2260
+ ...state,
2261
+ task: state.task || delta.task || "",
2262
+ user_constraints: mergeArr(state.user_constraints, delta.user_constraints),
2263
+ repo_facts: mergeArr(state.repo_facts, delta.repo_facts),
2264
+ files_touched: mergeArr(state.files_touched, delta.files_touched),
2265
+ files_modified: mergeArr(state.files_modified, delta.files_modified),
2266
+ confirmed_findings: mergeArr(state.confirmed_findings, delta.confirmed_findings),
2267
+ open_questions: mergeArr(state.open_questions, delta.open_questions),
2268
+ recent_failures: mergeArr(state.recent_failures, delta.recent_failures),
2269
+ decisions: mergeArr(state.decisions, delta.decisions),
2270
+ next_actions: mergeArr(state.next_actions, delta.next_actions),
2271
+ artifact_index: { ...state.artifact_index, ...delta.artifact_index }
2272
+ };
2273
+ }
2274
+ function shouldCompact(opts2) {
2275
+ const tokenThreshold = opts2.tokenThreshold ?? 8e4;
2276
+ const turnThreshold = opts2.turnThreshold ?? 12;
2277
+ const tokens = estimatePromptTokens(opts2.messages);
2278
+ const { turns } = groupIntoTurns(opts2.messages);
2279
+ return tokens > tokenThreshold || turns.length > turnThreshold;
2280
+ }
2281
+ function compactMessages(opts2) {
2282
+ const keepLastTurns = opts2.keepLastTurns ?? 4;
2283
+ const { prefix, turns } = groupIntoTurns(opts2.messages);
2284
+ const tokensBefore = estimatePromptTokens(opts2.messages);
2285
+ if (turns.length <= keepLastTurns) {
2286
+ return {
2287
+ newMessages: opts2.messages,
2288
+ newState: opts2.state,
2289
+ metrics: {
2290
+ estimatedTokensBefore: tokensBefore,
2291
+ estimatedTokensAfter: tokensBefore,
2292
+ archivedArtifacts: 0,
2293
+ recalledArtifacts: 0,
2294
+ rawTurnsRemoved: 0,
2295
+ rawTurnsKept: turns.length
2296
+ }
2297
+ };
2298
+ }
2299
+ const toCompact = turns.slice(0, turns.length - keepLastTurns);
2300
+ const toKeep = turns.slice(turns.length - keepLastTurns);
2301
+ let newState = { ...opts2.state };
2302
+ let archivedCount = 0;
2303
+ for (let i = 0; i < toCompact.length; i++) {
2304
+ const turn = toCompact[i];
2305
+ const { artifacts, stateDelta } = extractArtifactsFromTurn(turn, i, opts2.store);
2306
+ for (const artifact of artifacts) {
2307
+ opts2.store.add(artifact);
2308
+ archivedCount++;
2309
+ newState.artifact_index[artifact.id] = {
2310
+ type: artifact.type,
2311
+ summary: artifact.summary,
2312
+ source: artifact.source,
2313
+ path: artifact.path
2314
+ };
2315
+ }
2316
+ newState = mergeState(newState, stateDelta);
2317
+ if (!newState.task && typeof turn.user.content === "string") {
2318
+ newState.task = turn.user.content.slice(0, 200);
2319
+ }
2320
+ }
2321
+ const workingMemory = [];
2322
+ for (const turn of toKeep) {
2323
+ workingMemory.push(turn.user);
2324
+ workingMemory.push(turn.assistant);
2325
+ for (const tm of turn.tools) {
2326
+ workingMemory.push(tm);
2327
+ }
2328
+ }
2329
+ const stateMsg = buildSessionStateMessage(newState);
2330
+ const newMessages = [...prefix, stateMsg, ...workingMemory];
2331
+ const tokensAfter = estimatePromptTokens(newMessages);
2332
+ const metrics = {
2333
+ estimatedTokensBefore: tokensBefore,
2334
+ estimatedTokensAfter: tokensAfter,
2335
+ archivedArtifacts: archivedCount,
2336
+ recalledArtifacts: 0,
2337
+ rawTurnsRemoved: toCompact.length,
2338
+ rawTurnsKept: toKeep.length
2339
+ };
2340
+ return { newMessages, newState, metrics };
2341
+ }
2342
+ function recallArtifacts(messages, store, state) {
2343
+ const text = messages.map((m) => typeof m.content === "string" ? m.content : "").join(" ");
2344
+ const ids = [];
2345
+ for (const [id, meta] of Object.entries(state.artifact_index)) {
2346
+ if (meta.path && text.includes(meta.path)) {
2347
+ ids.push(id);
2348
+ }
2349
+ }
2350
+ for (const failure of state.recent_failures) {
2351
+ const keyword = failure.split(":")[0];
2352
+ if (!keyword) continue;
2353
+ const lowerKeyword = keyword.toLowerCase();
2354
+ if (!text.toLowerCase().includes(lowerKeyword)) continue;
2355
+ for (const [id, meta] of Object.entries(state.artifact_index)) {
2356
+ if (meta.source === "bash" && !ids.includes(id) && meta.summary.toLowerCase().includes(lowerKeyword)) {
2357
+ ids.push(id);
2358
+ }
2359
+ }
2360
+ }
2361
+ const uniqueIds = [...new Set(ids)].slice(0, 5);
2362
+ const recalled = store.recall(uniqueIds);
2363
+ return { ids: uniqueIds, recalled };
2364
+ }
2365
+ var init_compaction = __esm({
2366
+ "src/agent/compaction.ts"() {
2367
+ "use strict";
2368
+ init_session_state();
2369
+ }
2370
+ });
2371
+
1931
2372
  // src/agent/loop.ts
1932
2373
  function isHighSignalMemory(memory) {
1933
2374
  return memory.topicKey === "project_dependencies" || memory.topicKey === "project_tsconfig" || memory.topicKey === "project_entry_point" || memory.category === "instruction" || memory.category === "preference" || memory.category === "event" && memory.importance >= 3;
@@ -2065,6 +2506,12 @@ Use console.log() to return results. Only console.log output will be sent back t
2065
2506
  if (opts2.keepLastImageTurns !== void 0) {
2066
2507
  apiMessages = stripOldImages(apiMessages, opts2.keepLastImageTurns);
2067
2508
  }
2509
+ const promptTokens = estimatePromptTokens(apiMessages);
2510
+ if (promptTokens > MAX_PROMPT_TOKENS) {
2511
+ throw new Error(
2512
+ `kimiflare: context window exceeded (~${promptTokens.toLocaleString()} tokens). Run /compact to summarize older turns, or /clear to start fresh.`
2513
+ );
2514
+ }
2068
2515
  logger.debug("turn:api_request", { sessionId: opts2.sessionId, messageCount: apiMessages.length });
2069
2516
  const events = runKimi({
2070
2517
  accountId: opts2.accountId,
@@ -2269,10 +2716,15 @@ Use console.log() to return results. Only console.log output will be sent back t
2269
2716
  const warningPrefix = sandboxResult.warnings?.length ? `Warning: ${sandboxResult.warnings.join(" ")}
2270
2717
 
2271
2718
  ` : "";
2272
- const resultContent = sandboxResult.error ? `${warningPrefix}Error: ${sandboxResult.error}
2719
+ let resultContent = sandboxResult.error ? `${warningPrefix}Error: ${sandboxResult.error}
2273
2720
 
2274
2721
  Output:
2275
2722
  ${sandboxResult.output}` : `${warningPrefix}${sandboxResult.output}`;
2723
+ if (resultContent.length > MAX_TOOL_CONTENT_CHARS) {
2724
+ resultContent = resultContent.slice(0, MAX_TOOL_CONTENT_CHARS) + `
2725
+
2726
+ [truncated: ${resultContent.length - MAX_TOOL_CONTENT_CHARS} chars omitted]`;
2727
+ }
2276
2728
  const result = {
2277
2729
  tool_call_id: tc.id,
2278
2730
  name: "execute_code",
@@ -2297,12 +2749,18 @@ ${sandboxResult.output}` : `${warningPrefix}${sandboxResult.output}`;
2297
2749
  { cwd: opts2.cwd, signal: opts2.signal, onTasks: opts2.callbacks.onTasks, coauthor: opts2.coauthor, memoryManager: opts2.memoryManager, sessionId: opts2.sessionId, githubToken: opts2.githubToken },
2298
2750
  opts2.onFileChange
2299
2751
  );
2752
+ let content2 = result.content;
2753
+ if (content2.length > MAX_TOOL_CONTENT_CHARS) {
2754
+ content2 = content2.slice(0, MAX_TOOL_CONTENT_CHARS) + `
2755
+
2756
+ [truncated: ${content2.length - MAX_TOOL_CONTENT_CHARS} chars omitted]`;
2757
+ }
2300
2758
  logger.debug("turn:tool_end", { sessionId: opts2.sessionId, tool: tc.function.name, toolCallId: tc.id, ok: result.ok });
2301
2759
  toolResults.push(result);
2302
2760
  opts2.messages.push({
2303
2761
  role: "tool",
2304
2762
  tool_call_id: result.tool_call_id,
2305
- content: sanitizeString(result.content),
2763
+ content: sanitizeString(content2),
2306
2764
  name: result.name
2307
2765
  });
2308
2766
  opts2.callbacks.onToolResult?.(result);
@@ -2401,7 +2859,7 @@ function validateToolArguments(raw) {
2401
2859
  return "{}";
2402
2860
  }
2403
2861
  }
2404
- var BudgetExhaustedError, codeModeApiCache, driftAccumulator, DRIFT_THRESHOLD;
2862
+ var BudgetExhaustedError, codeModeApiCache, driftAccumulator, DRIFT_THRESHOLD, MAX_PROMPT_TOKENS, MAX_TOOL_CONTENT_CHARS;
2405
2863
  var init_loop = __esm({
2406
2864
  "src/agent/loop.ts"() {
2407
2865
  "use strict";
@@ -2412,6 +2870,7 @@ var init_loop = __esm({
2412
2870
  init_extractors();
2413
2871
  init_strip_reasoning();
2414
2872
  init_code_mode();
2873
+ init_compaction();
2415
2874
  init_logger();
2416
2875
  BudgetExhaustedError = class extends Error {
2417
2876
  constructor(message2 = "Cumulative input token budget exhausted") {
@@ -2422,6 +2881,8 @@ var init_loop = __esm({
2422
2881
  codeModeApiCache = /* @__PURE__ */ new Map();
2423
2882
  driftAccumulator = /* @__PURE__ */ new Map();
2424
2883
  DRIFT_THRESHOLD = 5;
2884
+ MAX_PROMPT_TOKENS = 24e4;
2885
+ MAX_TOOL_CONTENT_CHARS = 1e4;
2425
2886
  }
2426
2887
  });
2427
2888
 
@@ -3977,6 +4438,13 @@ function reduceToolOutput(toolName, raw, args, store, config = DEFAULT_REDUCER_C
3977
4438
  hint = r.hint;
3978
4439
  break;
3979
4440
  }
4441
+ case "glob": {
4442
+ const r = reduceGlob(raw, config.glob);
4443
+ reduced = r.body;
4444
+ wasReduced = r.wasReduced;
4445
+ hint = r.hint;
4446
+ break;
4447
+ }
3980
4448
  case "web_fetch": {
3981
4449
  const r = reduceWebFetch(raw, args, config.webFetch);
3982
4450
  reduced = r.body;
@@ -4022,9 +4490,13 @@ function reduceToolOutput(toolName, raw, args, store, config = DEFAULT_REDUCER_C
4022
4490
  hint = r.hint;
4023
4491
  break;
4024
4492
  }
4025
- default:
4026
- reduced = raw;
4493
+ default: {
4494
+ const r = reduceDefault(raw, config.defaultMaxChars);
4495
+ reduced = r.body;
4496
+ wasReduced = r.wasReduced;
4497
+ hint = r.hint;
4027
4498
  break;
4499
+ }
4028
4500
  }
4029
4501
  if (!wasReduced) {
4030
4502
  return { content: reduced, rawBytes, reducedBytes: rawBytes, artifactId };
@@ -4326,6 +4798,34 @@ function reduceBrowser(raw, cfg) {
4326
4798
  hint: "Browser page content truncated. Use a more specific selector or narrower URL scope."
4327
4799
  };
4328
4800
  }
4801
+ function reduceGlob(raw, cfg) {
4802
+ const lines = raw.split("\n").filter((l) => l.trim() !== "");
4803
+ if (lines.length <= cfg.maxFiles && raw.length <= cfg.maxOutputChars) {
4804
+ return { body: raw, wasReduced: false };
4805
+ }
4806
+ let result = raw;
4807
+ if (lines.length > cfg.maxFiles) {
4808
+ result = lines.slice(0, cfg.maxFiles).join("\n");
4809
+ }
4810
+ if (result.length > cfg.maxOutputChars) {
4811
+ result = result.slice(0, cfg.maxOutputChars);
4812
+ }
4813
+ return {
4814
+ body: result,
4815
+ wasReduced: true,
4816
+ hint: `Glob output truncated (${lines.length} files total). Use a more specific pattern (e.g., "src/**/*.ts") to narrow results.`
4817
+ };
4818
+ }
4819
+ function reduceDefault(raw, maxChars) {
4820
+ if (raw.length <= maxChars) {
4821
+ return { body: raw, wasReduced: false };
4822
+ }
4823
+ return {
4824
+ body: raw.slice(0, maxChars),
4825
+ wasReduced: true,
4826
+ hint: "Output truncated. Use a more specific query or call expand_artifact if you need the full content."
4827
+ };
4828
+ }
4329
4829
  var DEFAULT_REDUCER_CONFIG;
4330
4830
  var init_reducer = __esm({
4331
4831
  "src/tools/reducer.ts"() {
@@ -4351,6 +4851,10 @@ var init_reducer = __esm({
4351
4851
  maxOutputChars: 4e3,
4352
4852
  dedupeConsecutiveLines: true
4353
4853
  },
4854
+ glob: {
4855
+ maxFiles: 1e3,
4856
+ maxOutputChars: 2e4
4857
+ },
4354
4858
  webFetch: {
4355
4859
  maxChars: 2e3,
4356
4860
  maxHeadingChars: 500
@@ -4368,7 +4872,8 @@ var init_reducer = __esm({
4368
4872
  lsp: {
4369
4873
  maxLines: 50,
4370
4874
  maxOutputChars: 3e3
4371
- }
4875
+ },
4876
+ defaultMaxChars: 1e4
4372
4877
  };
4373
4878
  }
4374
4879
  });
@@ -4396,7 +4901,13 @@ function makeExpandArtifactTool(store) {
4396
4901
  if (!raw) {
4397
4902
  return `Artifact "${args.artifact_id}" not found. It may have been evicted from memory. Re-run the original tool to regenerate the output.`;
4398
4903
  }
4399
- return raw;
4904
+ const MAX_EXPAND_CHARS = 2e4;
4905
+ if (raw.length <= MAX_EXPAND_CHARS) {
4906
+ return raw;
4907
+ }
4908
+ return raw.slice(0, MAX_EXPAND_CHARS) + `
4909
+
4910
+ [truncated: ${raw.length - MAX_EXPAND_CHARS} chars omitted. The artifact is very large; consider using a more specific tool query instead of expanding the full content.]`;
4400
4911
  }
4401
4912
  };
4402
4913
  }
@@ -4672,1634 +5183,1193 @@ async function checkForUpdate(force = false) {
4672
5183
  }
4673
5184
  }
4674
5185
  const latestVersion = await fetchLatestVersion();
4675
- if (!latestVersion) {
4676
- return { hasUpdate: false, localVersion, latestVersion: null };
4677
- }
4678
- const hasUpdate = isNewer(localVersion, latestVersion);
4679
- await writeCache({ checkedAt: Date.now(), latestVersion });
4680
- return { hasUpdate, localVersion, latestVersion };
4681
- }
4682
- var CACHE_TTL_MS, NPM_REGISTRY;
4683
- var init_update_check = __esm({
4684
- "src/util/update-check.ts"() {
4685
- "use strict";
4686
- init_version();
4687
- CACHE_TTL_MS = 60 * 60 * 1e3;
4688
- NPM_REGISTRY = "https://registry.npmjs.org/kimiflare/latest";
4689
- }
4690
- });
4691
-
4692
- // src/remote/session-store.ts
4693
- import { readFile as readFile8, writeFile as writeFile7, mkdir as mkdir7, readdir as readdir2 } from "fs/promises";
4694
- import { homedir as homedir6 } from "os";
4695
- import { join as join11 } from "path";
4696
- function remoteDir() {
4697
- const xdg = process.env.XDG_DATA_HOME || join11(homedir6(), ".config");
4698
- return join11(xdg, "kimiflare", "remote");
4699
- }
4700
- async function saveRemoteSession(session) {
4701
- const dir = remoteDir();
4702
- await mkdir7(dir, { recursive: true });
4703
- const path = join11(dir, `${session.sessionId}.json`);
4704
- await writeFile7(path, JSON.stringify(session, null, 2) + "\n", "utf8");
4705
- }
4706
- async function loadRemoteSession(sessionId) {
4707
- try {
4708
- const path = join11(remoteDir(), `${sessionId}.json`);
4709
- const raw = await readFile8(path, "utf8");
4710
- return JSON.parse(raw);
4711
- } catch {
4712
- return null;
4713
- }
4714
- }
4715
- async function listRemoteSessions() {
4716
- const dir = remoteDir();
4717
- try {
4718
- const files = await readdir2(dir);
4719
- const sessions = [];
4720
- for (const file of files) {
4721
- if (!file.endsWith(".json")) continue;
4722
- try {
4723
- const raw = await readFile8(join11(dir, file), "utf8");
4724
- sessions.push(JSON.parse(raw));
4725
- } catch {
4726
- }
4727
- }
4728
- return sessions.sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime());
4729
- } catch {
4730
- return [];
4731
- }
4732
- }
4733
- async function getMostRecentRemoteSession() {
4734
- const sessions = await listRemoteSessions();
4735
- return sessions[0] ?? null;
4736
- }
4737
- var init_session_store = __esm({
4738
- "src/remote/session-store.ts"() {
4739
- "use strict";
4740
- }
4741
- });
4742
-
4743
- // src/remote/deploy.ts
4744
- import { execSync } from "child_process";
4745
- import { join as join12, dirname as dirname7 } from "path";
4746
- import { fileURLToPath as fileURLToPath3 } from "url";
4747
- import { randomBytes } from "crypto";
4748
- function generateSecret() {
4749
- return randomBytes(32).toString("hex");
4750
- }
4751
- function runCapture(cmd, cwd) {
4752
- return execSync(cmd, { cwd, encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] }).trim();
4753
- }
4754
- async function* deployForTui() {
4755
- yield { message: "Checking prerequisites..." };
4756
- try {
4757
- runCapture("wrangler --version");
4758
- } catch {
4759
- yield { message: "wrangler not found. Install: npm install -g wrangler", error: true };
4760
- yield { message: "Then run: wrangler login", error: true };
4761
- throw new Error("wrangler not installed");
4762
- }
4763
- yield { message: "wrangler OK" };
4764
- try {
4765
- runCapture("wrangler whoami");
4766
- } catch {
4767
- yield { message: "wrangler not authenticated. Run: wrangler login", error: true };
4768
- throw new Error("wrangler not authenticated");
4769
- }
4770
- yield { message: "wrangler authenticated" };
4771
- try {
4772
- runCapture("docker --version");
4773
- } catch {
4774
- yield { message: "Docker not found. Install: https://docs.docker.com/get-docker/", error: true };
4775
- throw new Error("docker not installed");
4776
- }
4777
- yield { message: "Docker OK" };
4778
- yield { message: "Building remote agent bundle..." };
4779
- try {
4780
- runCapture("npm run build:remote-agent", join12(REMOTE_DIR, ".."));
4781
- yield { message: "Agent bundle built" };
4782
- } catch (err) {
4783
- yield { message: `Build failed: ${err instanceof Error ? err.message : String(err)}`, error: true };
4784
- throw err;
4785
- }
4786
- yield { message: "Deploying Worker to Cloudflare..." };
4787
- try {
4788
- runCapture("wrangler deploy", WORKER_DIR);
4789
- yield { message: "Worker deployed" };
4790
- } catch (err) {
4791
- yield { message: `Deploy failed: ${err instanceof Error ? err.message : String(err)}`, error: true };
4792
- throw err;
4793
- }
4794
- let workerUrl;
4795
- try {
4796
- const info = runCapture("wrangler info", WORKER_DIR);
4797
- const match = info.match(/https:\/\/[^\s]+\.workers\.dev/);
4798
- if (match) workerUrl = match[0];
4799
- } catch {
4800
- }
4801
- if (!workerUrl) {
4802
- yield { message: "Could not auto-detect Worker URL", error: true };
4803
- throw new Error("Worker URL not found");
4804
- }
4805
- yield { message: `Worker URL: ${workerUrl}` };
4806
- const authSecret = generateSecret();
4807
- const cfg = await loadConfig();
4808
- const cfToken = process.env.CF_API_TOKEN ?? cfg?.apiToken;
4809
- if (!cfToken) {
4810
- yield { message: "CF_API_TOKEN not found. Set CF_API_TOKEN env var or apiToken in config", error: true };
4811
- throw new Error("CF_API_TOKEN missing");
4812
- }
4813
- yield { message: "Setting Worker secrets..." };
4814
- try {
4815
- execSync(`wrangler secret put REMOTE_AUTH_SECRET`, {
4816
- cwd: WORKER_DIR,
4817
- input: authSecret,
4818
- stdio: ["pipe", "pipe", "pipe"]
4819
- });
4820
- execSync(`wrangler secret put CF_API_TOKEN`, {
4821
- cwd: WORKER_DIR,
4822
- input: cfToken,
4823
- stdio: ["pipe", "pipe", "pipe"]
4824
- });
4825
- yield { message: "Secrets set" };
4826
- } catch (err) {
4827
- yield { message: `Secret setup failed: ${err instanceof Error ? err.message : String(err)}`, error: true };
4828
- throw err;
4829
- }
4830
- const imageTag = "ghcr.io/sinameraji/kimiflare-remote-agent:latest";
4831
- yield { message: "Building container image..." };
4832
- try {
4833
- runCapture(`docker build -t ${imageTag} .`, REMOTE_DIR);
4834
- yield { message: "Image built" };
4835
- } catch (err) {
4836
- yield { message: `Image build failed: ${err instanceof Error ? err.message : String(err)}`, error: true };
4837
- throw err;
4838
- }
4839
- yield { message: `Pushing ${imageTag}...` };
4840
- try {
4841
- runCapture(`docker push ${imageTag}`, REMOTE_DIR);
4842
- yield { message: "Image pushed" };
4843
- } catch (err) {
4844
- yield { message: `Push failed: ${err instanceof Error ? err.message : String(err)}`, error: true };
4845
- yield { message: "Make sure you're logged into ghcr.io: docker login ghcr.io -u USERNAME -p GITHUB_TOKEN", error: true };
4846
- throw err;
4847
- }
4848
- const nextCfg = {
4849
- ...cfg ?? { accountId: "", apiToken: "", model: "@cf/moonshotai/kimi-k2.6" },
4850
- remoteWorkerUrl: workerUrl,
4851
- remoteAuthSecret: authSecret
4852
- };
4853
- await saveConfig(nextCfg);
4854
- yield { message: "Config saved" };
4855
- yield { message: "Remote infrastructure ready!", done: true };
4856
- return { workerUrl, authSecret };
4857
- }
4858
- async function runDeploy() {
4859
- console.log("kimiflare remote deploy\n");
4860
- try {
4861
- for await (const step of deployForTui()) {
4862
- console.log(step.message);
4863
- if (step.done) break;
4864
- if (step.error) process.exit(1);
4865
- }
4866
- console.log("\nDeploy complete!");
4867
- } catch (err) {
4868
- console.error(`Deploy failed: ${err instanceof Error ? err.message : String(err)}`);
4869
- process.exit(1);
4870
- }
4871
- }
4872
- async function checkDeployStatus() {
4873
- let wrangler = false;
4874
- let wranglerAuth = false;
4875
- let docker = false;
4876
- let workerUrl;
4877
- try {
4878
- execSync("wrangler --version", { stdio: "pipe" });
4879
- wrangler = true;
4880
- } catch {
4881
- }
4882
- if (wrangler) {
4883
- try {
4884
- execSync("wrangler whoami", { stdio: "pipe" });
4885
- wranglerAuth = true;
4886
- } catch {
4887
- }
4888
- }
4889
- try {
4890
- execSync("docker --version", { stdio: "pipe" });
4891
- docker = true;
4892
- } catch {
4893
- }
4894
- const cfg = await loadConfig();
4895
- if (cfg?.remoteWorkerUrl) {
4896
- try {
4897
- const res = await fetch(`${cfg.remoteWorkerUrl}/health`, { signal: AbortSignal.timeout(5e3) });
4898
- if (res.ok) workerUrl = cfg.remoteWorkerUrl;
4899
- } catch {
4900
- }
5186
+ if (!latestVersion) {
5187
+ return { hasUpdate: false, localVersion, latestVersion: null };
4901
5188
  }
4902
- return { wrangler, wranglerAuth, docker, workerUrl };
5189
+ const hasUpdate = isNewer(localVersion, latestVersion);
5190
+ await writeCache({ checkedAt: Date.now(), latestVersion });
5191
+ return { hasUpdate, localVersion, latestVersion };
4903
5192
  }
4904
- var __dirname2, REMOTE_DIR, WORKER_DIR;
4905
- var init_deploy = __esm({
4906
- "src/remote/deploy.ts"() {
4907
- "use strict";
4908
- init_config();
4909
- __dirname2 = dirname7(fileURLToPath3(import.meta.url));
4910
- REMOTE_DIR = join12(__dirname2, "..", "..", "..", "remote");
4911
- WORKER_DIR = join12(REMOTE_DIR, "worker");
4912
- }
4913
- });
4914
-
4915
- // src/cost-attribution/types.ts
4916
- var ALL_CATEGORIES;
4917
- var init_types = __esm({
4918
- "src/cost-attribution/types.ts"() {
5193
+ var CACHE_TTL_MS, NPM_REGISTRY;
5194
+ var init_update_check = __esm({
5195
+ "src/util/update-check.ts"() {
4919
5196
  "use strict";
4920
- ALL_CATEGORIES = [
4921
- "reading-source-code",
4922
- "reading-documentation",
4923
- "reading-configuration",
4924
- "reading-web-content",
4925
- "reading-data",
4926
- "reading-logs-output",
4927
- "writing-source-code",
4928
- "writing-documentation",
4929
- "writing-configuration",
4930
- "writing-tests",
4931
- "editing-source-code",
4932
- "editing-documentation",
4933
- "editing-configuration",
4934
- "running-tests",
4935
- "running-git-commands",
4936
- "running-build-scripts",
4937
- "running-deploy-commands",
4938
- "running-shell-commands",
4939
- "searching-code",
4940
- "searching-web",
4941
- "exploring-codebase",
4942
- "other"
4943
- ];
5197
+ init_version();
5198
+ CACHE_TTL_MS = 60 * 60 * 1e3;
5199
+ NPM_REGISTRY = "https://registry.npmjs.org/kimiflare/latest";
4944
5200
  }
4945
5201
  });
4946
5202
 
4947
- // src/cost-attribution/report.ts
4948
- function aggregate(sessions, filter) {
4949
- const map = /* @__PURE__ */ new Map();
4950
- for (const s of sessions) {
4951
- const cat = s.category ?? "other";
4952
- if (filter && cat !== filter) continue;
4953
- const entry = map.get(cat) ?? { cost: 0, tokens: 0, sessions: 0 };
4954
- entry.cost += s.cost;
4955
- entry.tokens += s.promptTokens + s.completionTokens;
4956
- entry.sessions += 1;
4957
- map.set(cat, entry);
5203
+ // src/remote/session-store.ts
5204
+ import { readFile as readFile8, writeFile as writeFile7, mkdir as mkdir7, readdir as readdir2 } from "fs/promises";
5205
+ import { homedir as homedir6 } from "os";
5206
+ import { join as join11 } from "path";
5207
+ function remoteDir() {
5208
+ const xdg = process.env.XDG_DATA_HOME || join11(homedir6(), ".config");
5209
+ return join11(xdg, "kimiflare", "remote");
5210
+ }
5211
+ async function saveRemoteSession(session) {
5212
+ const dir = remoteDir();
5213
+ await mkdir7(dir, { recursive: true });
5214
+ const path = join11(dir, `${session.sessionId}.json`);
5215
+ await writeFile7(path, JSON.stringify(session, null, 2) + "\n", "utf8");
5216
+ }
5217
+ async function loadRemoteSession(sessionId) {
5218
+ try {
5219
+ const path = join11(remoteDir(), `${sessionId}.json`);
5220
+ const raw = await readFile8(path, "utf8");
5221
+ return JSON.parse(raw);
5222
+ } catch {
5223
+ return null;
4958
5224
  }
4959
- return map;
4960
5225
  }
4961
- function buildReport(opts2) {
4962
- const thisMap = aggregate(opts2.sessions, opts2.categoryFilter);
4963
- const prevMap = opts2.previousSessions ? aggregate(opts2.previousSessions, opts2.categoryFilter) : /* @__PURE__ */ new Map();
4964
- const categories = [];
4965
- for (const cat of ALL_CATEGORIES) {
4966
- const thisPeriod = thisMap.get(cat) ?? { cost: 0, tokens: 0, sessions: 0 };
4967
- const lastPeriod = prevMap.get(cat) ?? { cost: 0, tokens: 0, sessions: 0 };
4968
- if (thisPeriod.sessions === 0 && lastPeriod.sessions === 0) continue;
4969
- const changePct = lastPeriod.cost > 0 ? Math.round((thisPeriod.cost - lastPeriod.cost) / lastPeriod.cost * 1e3) / 10 : thisPeriod.cost > 0 ? 100 : 0;
4970
- categories.push({
4971
- category: cat,
4972
- thisPeriod,
4973
- lastPeriod,
4974
- changePct
4975
- });
5226
+ async function listRemoteSessions() {
5227
+ const dir = remoteDir();
5228
+ try {
5229
+ const files = await readdir2(dir);
5230
+ const sessions = [];
5231
+ for (const file of files) {
5232
+ if (!file.endsWith(".json")) continue;
5233
+ try {
5234
+ const raw = await readFile8(join11(dir, file), "utf8");
5235
+ sessions.push(JSON.parse(raw));
5236
+ } catch {
5237
+ }
5238
+ }
5239
+ return sessions.sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime());
5240
+ } catch {
5241
+ return [];
4976
5242
  }
4977
- categories.sort((a, b) => b.thisPeriod.cost - a.thisPeriod.cost);
4978
- const topSessions = opts2.sessions.filter((s) => !opts2.categoryFilter || s.category === opts2.categoryFilter).sort((a, b) => b.cost - a.cost).slice(0, 5).map((s) => ({
4979
- sessionId: s.id,
4980
- date: s.date,
4981
- cost: s.cost,
4982
- category: s.category ?? "other",
4983
- summary: s.summary,
4984
- isCurrentSession: opts2.currentSessionId ? s.id === opts2.currentSessionId : void 0
4985
- }));
4986
- return {
4987
- period: { start: opts2.startDate, end: opts2.endDate },
4988
- categories,
4989
- topSessions,
4990
- reconciliation: opts2.reconciliation ?? { status: "local-only", localCost: 0 }
4991
- };
4992
5243
  }
4993
- var init_report = __esm({
4994
- "src/cost-attribution/report.ts"() {
5244
+ async function getMostRecentRemoteSession() {
5245
+ const sessions = await listRemoteSessions();
5246
+ return sessions[0] ?? null;
5247
+ }
5248
+ var init_session_store = __esm({
5249
+ "src/remote/session-store.ts"() {
4995
5250
  "use strict";
4996
- init_types();
4997
5251
  }
4998
5252
  });
4999
5253
 
5000
- // src/cost-attribution/renderer.ts
5001
- function fmtCost(n) {
5002
- return n === 0 ? "$0.00" : `$${n.toFixed(2)}`;
5003
- }
5004
- function arrow(changePct) {
5005
- if (changePct > 5) return "\u2191";
5006
- if (changePct < -5) return "\u2193";
5007
- return "\u2192";
5008
- }
5009
- function pad(str, width) {
5010
- return str.padEnd(width).slice(0, width);
5011
- }
5012
- function padLeft(str, width) {
5013
- return str.padStart(width).slice(-width);
5254
+ // src/remote/deploy.ts
5255
+ import { execSync } from "child_process";
5256
+ import { join as join12, dirname as dirname7 } from "path";
5257
+ import { fileURLToPath as fileURLToPath3 } from "url";
5258
+ import { randomBytes } from "crypto";
5259
+ function generateSecret() {
5260
+ return randomBytes(32).toString("hex");
5014
5261
  }
5015
- function categoryLabel(cat) {
5016
- return cat;
5262
+ function runCapture(cmd, cwd) {
5263
+ return execSync(cmd, { cwd, encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] }).trim();
5017
5264
  }
5018
- function renderTerminal(report) {
5019
- const lines = [];
5020
- const catWidth = 22;
5021
- const numWidth = 12;
5022
- const arrWidth = 3;
5023
- const totalWidth = catWidth + 1 + numWidth + 1 + numWidth + 1 + arrWidth;
5024
- lines.push(`Period: ${report.period.start} \u2192 ${report.period.end}`);
5025
- lines.push("");
5026
- lines.push(
5027
- `${pad("Category", catWidth)} ${padLeft("This period", numWidth)} ${padLeft("Last period", numWidth)} ${pad("", arrWidth)}`
5028
- );
5029
- lines.push("\u2500".repeat(totalWidth));
5030
- let totalThis = 0;
5031
- let totalLast = 0;
5032
- for (const entry of report.categories) {
5033
- totalThis += entry.thisPeriod.cost;
5034
- totalLast += entry.lastPeriod.cost;
5035
- const label = pad(categoryLabel(entry.category), catWidth);
5036
- const thisStr = padLeft(fmtCost(entry.thisPeriod.cost), numWidth);
5037
- const lastStr = padLeft(fmtCost(entry.lastPeriod.cost), numWidth);
5038
- const arr = pad(arrow(entry.changePct), arrWidth);
5039
- lines.push(`${label} ${thisStr} ${lastStr} ${arr}`);
5265
+ async function* deployForTui() {
5266
+ yield { message: "Checking prerequisites..." };
5267
+ try {
5268
+ runCapture("wrangler --version");
5269
+ } catch {
5270
+ yield { message: "wrangler not found. Install: npm install -g wrangler", error: true };
5271
+ yield { message: "Then run: wrangler login", error: true };
5272
+ throw new Error("wrangler not installed");
5040
5273
  }
5041
- lines.push("\u2500".repeat(totalWidth));
5042
- lines.push(
5043
- `${pad("Total", catWidth)} ${padLeft(fmtCost(totalThis), numWidth)} ${padLeft(fmtCost(totalLast), numWidth)} ${pad("", arrWidth)}`
5044
- );
5045
- if (report.topSessions.length > 0) {
5046
- lines.push("");
5047
- lines.push("Top sessions this period:");
5048
- for (const s of report.topSessions) {
5049
- const day = new Date(s.date).toLocaleDateString("en-US", { weekday: "short" });
5050
- const cat = s.category;
5051
- const sum = s.summary ? ` \u2014 ${s.summary}` : "";
5052
- const cur = s.isCurrentSession ? " (current)" : "";
5053
- lines.push(` ${fmtCost(s.cost).padStart(6)} ${day} ${cat}${sum}${cur}`);
5054
- }
5274
+ yield { message: "wrangler OK" };
5275
+ try {
5276
+ runCapture("wrangler whoami");
5277
+ } catch {
5278
+ yield { message: "wrangler not authenticated. Run: wrangler login", error: true };
5279
+ throw new Error("wrangler not authenticated");
5055
5280
  }
5056
- lines.push("");
5057
- const rec = report.reconciliation;
5058
- switch (rec.status) {
5059
- case "verified":
5060
- lines.push(`Verified against Cloudflare: \u2713 (within ${rec.driftPct?.toFixed(1) ?? "0"}%)`);
5061
- break;
5062
- case "drift":
5063
- lines.push(`Verified against Cloudflare: \u2717 (drift ${rec.driftPct?.toFixed(1) ?? "?"}%)`);
5064
- break;
5065
- case "error":
5066
- lines.push(`Cloudflare reconciliation: \u26A0 ${rec.message ?? "API error"}`);
5067
- break;
5068
- case "local-only":
5069
- lines.push("Local-only report (Cloudflare reconciliation skipped).");
5070
- break;
5281
+ yield { message: "wrangler authenticated" };
5282
+ try {
5283
+ runCapture("docker --version");
5284
+ } catch {
5285
+ yield { message: "Docker not found. Install: https://docs.docker.com/get-docker/", error: true };
5286
+ throw new Error("docker not installed");
5287
+ }
5288
+ yield { message: "Docker OK" };
5289
+ yield { message: "Building remote agent bundle..." };
5290
+ try {
5291
+ runCapture("npm run build:remote-agent", join12(REMOTE_DIR, ".."));
5292
+ yield { message: "Agent bundle built" };
5293
+ } catch (err) {
5294
+ yield { message: `Build failed: ${err instanceof Error ? err.message : String(err)}`, error: true };
5295
+ throw err;
5296
+ }
5297
+ yield { message: "Deploying Worker to Cloudflare..." };
5298
+ try {
5299
+ runCapture("wrangler deploy", WORKER_DIR);
5300
+ yield { message: "Worker deployed" };
5301
+ } catch (err) {
5302
+ yield { message: `Deploy failed: ${err instanceof Error ? err.message : String(err)}`, error: true };
5303
+ throw err;
5304
+ }
5305
+ let workerUrl;
5306
+ try {
5307
+ const info = runCapture("wrangler info", WORKER_DIR);
5308
+ const match = info.match(/https:\/\/[^\s]+\.workers\.dev/);
5309
+ if (match) workerUrl = match[0];
5310
+ } catch {
5071
5311
  }
5072
- return lines.join("\n");
5073
- }
5074
- function renderJson(report) {
5075
- return JSON.stringify(report, null, 2);
5076
- }
5077
- var init_renderer = __esm({
5078
- "src/cost-attribution/renderer.ts"() {
5079
- "use strict";
5312
+ if (!workerUrl) {
5313
+ yield { message: "Could not auto-detect Worker URL", error: true };
5314
+ throw new Error("Worker URL not found");
5080
5315
  }
5081
- });
5082
-
5083
- // src/cost-attribution/reconcile.ts
5084
- function cacheKey(opts2) {
5085
- return `${opts2.gatewayId ?? "none"}:${opts2.startDate}:${opts2.endDate}`;
5086
- }
5087
- async function reconcileWithCloudflare(opts2) {
5088
- if (!opts2.accountId || !opts2.apiToken) {
5089
- return { status: "local-only", localCost: opts2.localCost, message: "Missing Cloudflare credentials" };
5316
+ yield { message: `Worker URL: ${workerUrl}` };
5317
+ const authSecret = generateSecret();
5318
+ const cfg = await loadConfig();
5319
+ const cfToken = process.env.CF_API_TOKEN ?? cfg?.apiToken;
5320
+ if (!cfToken) {
5321
+ yield { message: "CF_API_TOKEN not found. Set CF_API_TOKEN env var or apiToken in config", error: true };
5322
+ throw new Error("CF_API_TOKEN missing");
5090
5323
  }
5091
- const key = cacheKey(opts2);
5092
- const cached = cache.get(key);
5093
- if (cached && cached.expires > Date.now()) {
5094
- return { ...cached.result, localCost: opts2.localCost };
5324
+ yield { message: "Setting Worker secrets..." };
5325
+ try {
5326
+ execSync(`wrangler secret put REMOTE_AUTH_SECRET`, {
5327
+ cwd: WORKER_DIR,
5328
+ input: authSecret,
5329
+ stdio: ["pipe", "pipe", "pipe"]
5330
+ });
5331
+ execSync(`wrangler secret put CF_API_TOKEN`, {
5332
+ cwd: WORKER_DIR,
5333
+ input: cfToken,
5334
+ stdio: ["pipe", "pipe", "pipe"]
5335
+ });
5336
+ yield { message: "Secrets set" };
5337
+ } catch (err) {
5338
+ yield { message: `Secret setup failed: ${err instanceof Error ? err.message : String(err)}`, error: true };
5339
+ throw err;
5095
5340
  }
5341
+ const imageTag = "ghcr.io/sinameraji/kimiflare-remote-agent:latest";
5342
+ yield { message: "Building container image..." };
5096
5343
  try {
5097
- const result = {
5098
- status: "local-only",
5099
- localCost: opts2.localCost,
5100
- message: "Cloudflare reconciliation not yet implemented"
5101
- };
5102
- cache.set(key, { result, expires: Date.now() + 60 * 60 * 1e3 });
5103
- return result;
5344
+ runCapture(`docker build -t ${imageTag} .`, REMOTE_DIR);
5345
+ yield { message: "Image built" };
5104
5346
  } catch (err) {
5105
- const message2 = err instanceof Error ? err.message : "Unknown error";
5106
- return { status: "error", localCost: opts2.localCost, message: message2 };
5347
+ yield { message: `Image build failed: ${err instanceof Error ? err.message : String(err)}`, error: true };
5348
+ throw err;
5107
5349
  }
5108
- }
5109
- var cache;
5110
- var init_reconcile = __esm({
5111
- "src/cost-attribution/reconcile.ts"() {
5112
- "use strict";
5113
- cache = /* @__PURE__ */ new Map();
5350
+ yield { message: `Pushing ${imageTag}...` };
5351
+ try {
5352
+ runCapture(`docker push ${imageTag}`, REMOTE_DIR);
5353
+ yield { message: "Image pushed" };
5354
+ } catch (err) {
5355
+ yield { message: `Push failed: ${err instanceof Error ? err.message : String(err)}`, error: true };
5356
+ yield { message: "Make sure you're logged into ghcr.io: docker login ghcr.io -u USERNAME -p GITHUB_TOKEN", error: true };
5357
+ throw err;
5114
5358
  }
5115
- });
5116
-
5117
- // src/cost-attribution/heuristic.ts
5118
- function extOf(path) {
5119
- const idx = path.lastIndexOf(".");
5120
- return idx >= 0 ? path.slice(idx).toLowerCase() : "";
5121
- }
5122
- function basename2(path) {
5123
- const idx = Math.max(path.lastIndexOf("/"), path.lastIndexOf("\\"));
5124
- return idx >= 0 ? path.slice(idx + 1) : path;
5125
- }
5126
- function classifyFile(path) {
5127
- const base = basename2(path);
5128
- const ext = extOf(path);
5129
- if (TEST_PATTERNS.test(base)) return "writing-tests";
5130
- if (base.toLowerCase().startsWith("readme")) return "reading-documentation";
5131
- if (SOURCE_EXTS.has(ext)) return null;
5132
- if (DOC_EXTS.has(ext)) return "reading-documentation";
5133
- if (CONFIG_EXTS.has(ext)) return "reading-configuration";
5134
- if (DATA_EXTS.has(ext)) return "reading-data";
5135
- return null;
5136
- }
5137
- function classifyWriteFile(path) {
5138
- const base = basename2(path);
5139
- const ext = extOf(path);
5140
- if (TEST_PATTERNS.test(base)) return "writing-tests";
5141
- if (base.toLowerCase().startsWith("readme") || DOC_EXTS.has(ext)) return "writing-documentation";
5142
- if (CONFIG_EXTS.has(ext)) return "writing-configuration";
5143
- if (SOURCE_EXTS.has(ext)) return "writing-source-code";
5144
- return null;
5145
- }
5146
- function classifyEditFile(path) {
5147
- const base = basename2(path);
5148
- const ext = extOf(path);
5149
- if (base.toLowerCase().startsWith("readme") || DOC_EXTS.has(ext)) return "editing-documentation";
5150
- if (CONFIG_EXTS.has(ext)) return "editing-configuration";
5151
- if (SOURCE_EXTS.has(ext)) return "editing-source-code";
5152
- return null;
5153
- }
5154
- function classifyBash(command) {
5155
- if (TEST_COMMANDS.test(command)) return "running-tests";
5156
- if (GIT_COMMANDS.test(command)) return "running-git-commands";
5157
- if (BUILD_COMMANDS.test(command)) return "running-build-scripts";
5158
- if (DEPLOY_COMMANDS.test(command)) return "running-deploy-commands";
5159
- return "running-shell-commands";
5359
+ const nextCfg = {
5360
+ ...cfg ?? { accountId: "", apiToken: "", model: "@cf/moonshotai/kimi-k2.6" },
5361
+ remoteWorkerUrl: workerUrl,
5362
+ remoteAuthSecret: authSecret
5363
+ };
5364
+ await saveConfig(nextCfg);
5365
+ yield { message: "Config saved" };
5366
+ yield { message: "Remote infrastructure ready!", done: true };
5367
+ return { workerUrl, authSecret };
5160
5368
  }
5161
- function classifyToolCall(tool) {
5162
- const args = tool.arguments ?? {};
5163
- const path = typeof args.path === "string" ? args.path : typeof args.file_path === "string" ? args.file_path : "";
5164
- const command = typeof args.command === "string" ? args.command : "";
5165
- switch (tool.name) {
5166
- case "read_file":
5167
- case "read": {
5168
- const cat = classifyFile(path);
5169
- if (cat) return { category: cat, confidence: 0.8 };
5170
- if (SOURCE_EXTS.has(extOf(path))) return { category: "reading-source-code", confidence: 0.8 };
5171
- return null;
5172
- }
5173
- case "create_file":
5174
- case "write_file":
5175
- case "write": {
5176
- const cat = classifyWriteFile(path);
5177
- if (cat) return { category: cat, confidence: 0.9 };
5178
- return null;
5179
- }
5180
- case "str_replace":
5181
- case "edit": {
5182
- const cat = classifyEditFile(path);
5183
- if (cat) return { category: cat, confidence: 0.85 };
5184
- return null;
5185
- }
5186
- case "bash": {
5187
- const cat = classifyBash(command);
5188
- if (cat) return { category: cat, confidence: 0.9 };
5189
- return null;
5190
- }
5191
- case "web_fetch":
5192
- return { category: "reading-web-content", confidence: 0.85 };
5193
- case "grep":
5194
- case "glob": {
5195
- const pattern = typeof args.pattern === "string" ? args.pattern : path;
5196
- const isSource = SOURCE_EXTS.has(extOf(pattern)) || /\.(ts|js|py|go|rs)\b/.test(pattern);
5197
- return { category: isSource ? "searching-code" : "searching-code", confidence: 0.75 };
5369
+ async function runDeploy() {
5370
+ console.log("kimiflare remote deploy\n");
5371
+ try {
5372
+ for await (const step of deployForTui()) {
5373
+ console.log(step.message);
5374
+ if (step.done) break;
5375
+ if (step.error) process.exit(1);
5198
5376
  }
5199
- case "execute_code":
5200
- return { category: "other", confidence: 0.6 };
5201
- default:
5202
- return null;
5377
+ console.log("\nDeploy complete!");
5378
+ } catch (err) {
5379
+ console.error(`Deploy failed: ${err instanceof Error ? err.message : String(err)}`);
5380
+ process.exit(1);
5203
5381
  }
5204
5382
  }
5205
- function classifyTurn(turn) {
5206
- const signals = [];
5207
- for (const tool of turn.toolCalls) {
5208
- const result = classifyToolCall(tool);
5209
- if (result) {
5210
- signals.push({
5211
- category: result.category,
5212
- weight: turn.tokens ?? 1,
5213
- confidence: result.confidence
5214
- });
5383
+ async function checkDeployStatus() {
5384
+ let wrangler = false;
5385
+ let wranglerAuth = false;
5386
+ let docker = false;
5387
+ let workerUrl;
5388
+ try {
5389
+ execSync("wrangler --version", { stdio: "pipe" });
5390
+ wrangler = true;
5391
+ } catch {
5392
+ }
5393
+ if (wrangler) {
5394
+ try {
5395
+ execSync("wrangler whoami", { stdio: "pipe" });
5396
+ wranglerAuth = true;
5397
+ } catch {
5215
5398
  }
5216
5399
  }
5217
- return signals;
5218
- }
5219
- function classifySession(turns, opts2) {
5220
- const scores = /* @__PURE__ */ new Map();
5221
- for (const turn of turns) {
5222
- const signals = classifyTurn(turn);
5223
- for (const s of signals) {
5224
- scores.set(s.category, (scores.get(s.category) ?? 0) + s.weight * s.confidence);
5400
+ try {
5401
+ execSync("docker --version", { stdio: "pipe" });
5402
+ docker = true;
5403
+ } catch {
5404
+ }
5405
+ const cfg = await loadConfig();
5406
+ if (cfg?.remoteWorkerUrl) {
5407
+ try {
5408
+ const res = await fetch(`${cfg.remoteWorkerUrl}/health`, { signal: AbortSignal.timeout(5e3) });
5409
+ if (res.ok) workerUrl = cfg.remoteWorkerUrl;
5410
+ } catch {
5225
5411
  }
5226
5412
  }
5227
- const totalTurns = opts2?.totalTurns ?? turns.length;
5228
- const totalToolCalls = opts2?.totalToolCalls ?? turns.reduce((sum, t) => sum + t.toolCalls.length, 0);
5229
- if (totalTurns < 3 && totalToolCalls < 5) {
5230
- scores.set("other", (scores.get("other") ?? 0) + 0.6);
5413
+ return { wrangler, wranglerAuth, docker, workerUrl };
5414
+ }
5415
+ var __dirname2, REMOTE_DIR, WORKER_DIR;
5416
+ var init_deploy = __esm({
5417
+ "src/remote/deploy.ts"() {
5418
+ "use strict";
5419
+ init_config();
5420
+ __dirname2 = dirname7(fileURLToPath3(import.meta.url));
5421
+ REMOTE_DIR = join12(__dirname2, "..", "..", "..", "remote");
5422
+ WORKER_DIR = join12(REMOTE_DIR, "worker");
5423
+ }
5424
+ });
5425
+
5426
+ // src/cost-attribution/types.ts
5427
+ var ALL_CATEGORIES;
5428
+ var init_types = __esm({
5429
+ "src/cost-attribution/types.ts"() {
5430
+ "use strict";
5431
+ ALL_CATEGORIES = [
5432
+ "reading-source-code",
5433
+ "reading-documentation",
5434
+ "reading-configuration",
5435
+ "reading-web-content",
5436
+ "reading-data",
5437
+ "reading-logs-output",
5438
+ "writing-source-code",
5439
+ "writing-documentation",
5440
+ "writing-configuration",
5441
+ "writing-tests",
5442
+ "editing-source-code",
5443
+ "editing-documentation",
5444
+ "editing-configuration",
5445
+ "running-tests",
5446
+ "running-git-commands",
5447
+ "running-build-scripts",
5448
+ "running-deploy-commands",
5449
+ "running-shell-commands",
5450
+ "searching-code",
5451
+ "searching-web",
5452
+ "exploring-codebase",
5453
+ "other"
5454
+ ];
5231
5455
  }
5232
- if (scores.size === 0) {
5233
- return { category: "other", confidence: 0.6, classifiedBy: "heuristic", summary: "Short or ambiguous session" };
5456
+ });
5457
+
5458
+ // src/cost-attribution/report.ts
5459
+ function aggregate(sessions, filter) {
5460
+ const map = /* @__PURE__ */ new Map();
5461
+ for (const s of sessions) {
5462
+ const cat = s.category ?? "other";
5463
+ if (filter && cat !== filter) continue;
5464
+ const entry = map.get(cat) ?? { cost: 0, tokens: 0, sessions: 0 };
5465
+ entry.cost += s.cost;
5466
+ entry.tokens += s.promptTokens + s.completionTokens;
5467
+ entry.sessions += 1;
5468
+ map.set(cat, entry);
5234
5469
  }
5235
- let bestCategory = "other";
5236
- let bestScore = -1;
5237
- let totalScore = 0;
5238
- for (const [cat, score] of scores) {
5239
- totalScore += score;
5240
- if (score > bestScore) {
5241
- bestScore = score;
5242
- bestCategory = cat;
5243
- }
5470
+ return map;
5471
+ }
5472
+ function buildReport(opts2) {
5473
+ const thisMap = aggregate(opts2.sessions, opts2.categoryFilter);
5474
+ const prevMap = opts2.previousSessions ? aggregate(opts2.previousSessions, opts2.categoryFilter) : /* @__PURE__ */ new Map();
5475
+ const categories = [];
5476
+ for (const cat of ALL_CATEGORIES) {
5477
+ const thisPeriod = thisMap.get(cat) ?? { cost: 0, tokens: 0, sessions: 0 };
5478
+ const lastPeriod = prevMap.get(cat) ?? { cost: 0, tokens: 0, sessions: 0 };
5479
+ if (thisPeriod.sessions === 0 && lastPeriod.sessions === 0) continue;
5480
+ const changePct = lastPeriod.cost > 0 ? Math.round((thisPeriod.cost - lastPeriod.cost) / lastPeriod.cost * 1e3) / 10 : thisPeriod.cost > 0 ? 100 : 0;
5481
+ categories.push({
5482
+ category: cat,
5483
+ thisPeriod,
5484
+ lastPeriod,
5485
+ changePct
5486
+ });
5244
5487
  }
5245
- const confidence = totalScore > 0 ? bestScore / totalScore : 0;
5488
+ categories.sort((a, b) => b.thisPeriod.cost - a.thisPeriod.cost);
5489
+ const topSessions = opts2.sessions.filter((s) => !opts2.categoryFilter || s.category === opts2.categoryFilter).sort((a, b) => b.cost - a.cost).slice(0, 5).map((s) => ({
5490
+ sessionId: s.id,
5491
+ date: s.date,
5492
+ cost: s.cost,
5493
+ category: s.category ?? "other",
5494
+ summary: s.summary,
5495
+ isCurrentSession: opts2.currentSessionId ? s.id === opts2.currentSessionId : void 0
5496
+ }));
5246
5497
  return {
5247
- category: bestCategory,
5248
- confidence: Math.round(confidence * 100) / 100,
5249
- classifiedBy: "heuristic"
5498
+ period: { start: opts2.startDate, end: opts2.endDate },
5499
+ categories,
5500
+ topSessions,
5501
+ reconciliation: opts2.reconciliation ?? { status: "local-only", localCost: 0 }
5250
5502
  };
5251
5503
  }
5252
- var SOURCE_EXTS, DOC_EXTS, CONFIG_EXTS, DATA_EXTS, TEST_PATTERNS, TEST_COMMANDS, GIT_COMMANDS, BUILD_COMMANDS, DEPLOY_COMMANDS;
5253
- var init_heuristic = __esm({
5254
- "src/cost-attribution/heuristic.ts"() {
5504
+ var init_report = __esm({
5505
+ "src/cost-attribution/report.ts"() {
5255
5506
  "use strict";
5256
- SOURCE_EXTS = /* @__PURE__ */ new Set([
5257
- ".ts",
5258
- ".tsx",
5259
- ".js",
5260
- ".jsx",
5261
- ".py",
5262
- ".go",
5263
- ".rs",
5264
- ".java",
5265
- ".kt",
5266
- ".swift",
5267
- ".cpp",
5268
- ".c",
5269
- ".h",
5270
- ".hpp",
5271
- ".cs",
5272
- ".rb",
5273
- ".php",
5274
- ".scala",
5275
- ".clj",
5276
- ".erl",
5277
- ".ex",
5278
- ".exs",
5279
- ".elm",
5280
- ".hs",
5281
- ".lua",
5282
- ".r",
5283
- ".m",
5284
- ".mm",
5285
- ".sh",
5286
- ".bash",
5287
- ".zsh",
5288
- ".fish",
5289
- ".ps1",
5290
- ".vim",
5291
- ".el"
5292
- ]);
5293
- DOC_EXTS = /* @__PURE__ */ new Set([
5294
- ".md",
5295
- ".txt",
5296
- ".rst",
5297
- ".adoc",
5298
- ".org"
5299
- ]);
5300
- CONFIG_EXTS = /* @__PURE__ */ new Set([
5301
- ".json",
5302
- ".yaml",
5303
- ".yml",
5304
- ".toml",
5305
- ".ini",
5306
- ".conf",
5307
- ".cfg",
5308
- ".env",
5309
- ".envrc",
5310
- ".properties",
5311
- ".xml",
5312
- ".plist"
5313
- ]);
5314
- DATA_EXTS = /* @__PURE__ */ new Set([
5315
- ".csv",
5316
- ".sql",
5317
- ".db",
5318
- ".sqlite",
5319
- ".parquet",
5320
- ".jsonl",
5321
- ".ndjson",
5322
- ".tsv",
5323
- ".psv"
5324
- ]);
5325
- TEST_PATTERNS = /\.(test|spec)\./;
5326
- TEST_COMMANDS = /\b(npm test|pytest|jest|vitest|mocha|ava|tap|cargo test|go test|dotnet test|gradle test|mvn test|rake test|bundle exec rspec)\b/;
5327
- GIT_COMMANDS = /\b(git commit|git merge|git rebase|git diff|git log|git blame|git cherry-pick|git revert)\b/;
5328
- BUILD_COMMANDS = /\b(npm run build|make|cargo build|go build|gradle build|mvn compile|dotnet build|yarn build|pnpm build|webpack|vite build|tsc|esbuild|rollup)\b/;
5329
- DEPLOY_COMMANDS = /\b(docker|kubectl|helm|wrangler deploy|terraform apply|pulumi up|serverless deploy|aws deploy|gcloud deploy|fly deploy|vercel deploy|netlify deploy)\b/;
5507
+ init_types();
5330
5508
  }
5331
5509
  });
5332
5510
 
5333
- // src/cost-attribution/classify-from-session.ts
5334
- import { readFile as readFile9 } from "fs/promises";
5335
- import { join as join13 } from "path";
5336
- import { homedir as homedir7 } from "os";
5337
- function sessionsDir() {
5338
- const xdg = process.env.XDG_DATA_HOME || join13(homedir7(), ".local", "share");
5339
- return join13(xdg, "kimiflare", "sessions");
5511
+ // src/cost-attribution/renderer.ts
5512
+ function fmtCost(n) {
5513
+ return n === 0 ? "$0.00" : `$${n.toFixed(2)}`;
5340
5514
  }
5341
- function parseToolCalls(calls) {
5342
- return calls.map((c) => {
5343
- let args = {};
5344
- try {
5345
- args = JSON.parse(c.function.arguments);
5346
- } catch {
5347
- }
5348
- return { name: c.function.name, arguments: args };
5349
- });
5515
+ function arrow(changePct) {
5516
+ if (changePct > 5) return "\u2191";
5517
+ if (changePct < -5) return "\u2193";
5518
+ return "\u2192";
5350
5519
  }
5351
- async function classifyFromSessionFile(sessionId) {
5352
- try {
5353
- const raw = await readFile9(join13(sessionsDir(), `${sessionId}.json`), "utf8");
5354
- const session = JSON.parse(raw);
5355
- const messages = session.messages ?? [];
5356
- const turns = [];
5357
- for (const m of messages) {
5358
- if (m.role === "assistant" && m.tool_calls && m.tool_calls.length > 0) {
5359
- turns.push({ toolCalls: parseToolCalls(m.tool_calls), tokens: 100 });
5360
- }
5520
+ function pad(str, width) {
5521
+ return str.padEnd(width).slice(0, width);
5522
+ }
5523
+ function padLeft(str, width) {
5524
+ return str.padStart(width).slice(-width);
5525
+ }
5526
+ function categoryLabel(cat) {
5527
+ return cat;
5528
+ }
5529
+ function renderTerminal(report) {
5530
+ const lines = [];
5531
+ const catWidth = 22;
5532
+ const numWidth = 12;
5533
+ const arrWidth = 3;
5534
+ const totalWidth = catWidth + 1 + numWidth + 1 + numWidth + 1 + arrWidth;
5535
+ lines.push(`Period: ${report.period.start} \u2192 ${report.period.end}`);
5536
+ lines.push("");
5537
+ lines.push(
5538
+ `${pad("Category", catWidth)} ${padLeft("This period", numWidth)} ${padLeft("Last period", numWidth)} ${pad("", arrWidth)}`
5539
+ );
5540
+ lines.push("\u2500".repeat(totalWidth));
5541
+ let totalThis = 0;
5542
+ let totalLast = 0;
5543
+ for (const entry of report.categories) {
5544
+ totalThis += entry.thisPeriod.cost;
5545
+ totalLast += entry.lastPeriod.cost;
5546
+ const label = pad(categoryLabel(entry.category), catWidth);
5547
+ const thisStr = padLeft(fmtCost(entry.thisPeriod.cost), numWidth);
5548
+ const lastStr = padLeft(fmtCost(entry.lastPeriod.cost), numWidth);
5549
+ const arr = pad(arrow(entry.changePct), arrWidth);
5550
+ lines.push(`${label} ${thisStr} ${lastStr} ${arr}`);
5551
+ }
5552
+ lines.push("\u2500".repeat(totalWidth));
5553
+ lines.push(
5554
+ `${pad("Total", catWidth)} ${padLeft(fmtCost(totalThis), numWidth)} ${padLeft(fmtCost(totalLast), numWidth)} ${pad("", arrWidth)}`
5555
+ );
5556
+ if (report.topSessions.length > 0) {
5557
+ lines.push("");
5558
+ lines.push("Top sessions this period:");
5559
+ for (const s of report.topSessions) {
5560
+ const day = new Date(s.date).toLocaleDateString("en-US", { weekday: "short" });
5561
+ const cat = s.category;
5562
+ const sum = s.summary ? ` \u2014 ${s.summary}` : "";
5563
+ const cur = s.isCurrentSession ? " (current)" : "";
5564
+ lines.push(` ${fmtCost(s.cost).padStart(6)} ${day} ${cat}${sum}${cur}`);
5361
5565
  }
5362
- const totalToolCalls = turns.reduce((sum, t) => sum + t.toolCalls.length, 0);
5363
- const result = classifySession(turns, { totalTurns: turns.length, totalToolCalls });
5364
- return {
5365
- category: result.category,
5366
- confidence: result.confidence,
5367
- classifiedBy: result.classifiedBy,
5368
- summary: result.summary
5369
- };
5370
- } catch {
5371
- return { category: "other", confidence: 0.5, classifiedBy: "heuristic", summary: "Session file unavailable" };
5372
5566
  }
5567
+ lines.push("");
5568
+ const rec = report.reconciliation;
5569
+ switch (rec.status) {
5570
+ case "verified":
5571
+ lines.push(`Verified against Cloudflare: \u2713 (within ${rec.driftPct?.toFixed(1) ?? "0"}%)`);
5572
+ break;
5573
+ case "drift":
5574
+ lines.push(`Verified against Cloudflare: \u2717 (drift ${rec.driftPct?.toFixed(1) ?? "?"}%)`);
5575
+ break;
5576
+ case "error":
5577
+ lines.push(`Cloudflare reconciliation: \u26A0 ${rec.message ?? "API error"}`);
5578
+ break;
5579
+ case "local-only":
5580
+ lines.push("Local-only report (Cloudflare reconciliation skipped).");
5581
+ break;
5582
+ }
5583
+ return lines.join("\n");
5373
5584
  }
5374
- var init_classify_from_session = __esm({
5375
- "src/cost-attribution/classify-from-session.ts"() {
5585
+ function renderJson(report) {
5586
+ return JSON.stringify(report, null, 2);
5587
+ }
5588
+ var init_renderer = __esm({
5589
+ "src/cost-attribution/renderer.ts"() {
5376
5590
  "use strict";
5377
- init_heuristic();
5378
5591
  }
5379
5592
  });
5380
5593
 
5381
- // src/cost-attribution/cli.ts
5382
- var cli_exports = {};
5383
- __export(cli_exports, {
5384
- runCostCommand: () => runCostCommand
5385
- });
5386
- import { readFile as readFile10 } from "fs/promises";
5387
- import { join as join14 } from "path";
5388
- import { homedir as homedir8 } from "os";
5389
- function usageDir() {
5390
- const xdg = process.env.XDG_DATA_HOME || join14(homedir8(), ".local", "share");
5391
- return join14(xdg, "kimiflare");
5392
- }
5393
- function usagePath() {
5394
- return join14(usageDir(), "usage.json");
5395
- }
5396
- function today() {
5397
- return (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
5398
- }
5399
- function daysAgo(n) {
5400
- const d = /* @__PURE__ */ new Date();
5401
- d.setDate(d.getDate() - n);
5402
- return d.toISOString().slice(0, 10);
5403
- }
5404
- async function loadLog() {
5405
- try {
5406
- const raw = await readFile10(usagePath(), "utf8");
5407
- return JSON.parse(raw);
5408
- } catch {
5409
- return { version: 1, days: [], sessions: [] };
5410
- }
5411
- }
5412
- function filterSessions(sessions, start, end) {
5413
- return sessions.filter((s) => s.date >= start && s.date <= end);
5594
+ // src/cost-attribution/reconcile.ts
5595
+ function cacheKey(opts2) {
5596
+ return `${opts2.gatewayId ?? "none"}:${opts2.startDate}:${opts2.endDate}`;
5414
5597
  }
5415
- async function runCostCommand(opts2) {
5416
- const log2 = await loadLog();
5417
- let startDate;
5418
- let endDate;
5419
- let prevStart;
5420
- let prevEnd;
5421
- if (opts2.month) {
5422
- startDate = daysAgo(30);
5423
- endDate = today();
5424
- prevStart = daysAgo(60);
5425
- prevEnd = daysAgo(31);
5426
- } else if (opts2.day) {
5427
- startDate = today();
5428
- endDate = today();
5429
- prevStart = daysAgo(1);
5430
- prevEnd = daysAgo(1);
5431
- } else {
5432
- startDate = daysAgo(7);
5433
- endDate = today();
5434
- prevStart = daysAgo(14);
5435
- prevEnd = daysAgo(8);
5436
- }
5437
- if (opts2.session) {
5438
- const session = log2.sessions.find((s) => s.id === opts2.session);
5439
- if (!session) {
5440
- console.error(`Session ${opts2.session} not found.`);
5441
- process.exit(1);
5442
- }
5443
- console.log(JSON.stringify(session, null, 2));
5444
- return;
5598
+ async function reconcileWithCloudflare(opts2) {
5599
+ if (!opts2.accountId || !opts2.apiToken) {
5600
+ return { status: "local-only", localCost: opts2.localCost, message: "Missing Cloudflare credentials" };
5445
5601
  }
5446
- const sessions = filterSessions(log2.sessions, startDate, endDate);
5447
- const prevSessions = filterSessions(log2.sessions, prevStart, prevEnd);
5448
- for (const s of sessions) {
5449
- if (!s.category || opts2.reclassify) {
5450
- const result = await classifyFromSessionFile(s.id);
5451
- s.category = result.category;
5452
- s.confidence = result.confidence;
5453
- s.classifiedBy = result.classifiedBy;
5454
- s.summary = result.summary;
5455
- s.classifiedAt = (/* @__PURE__ */ new Date()).toISOString();
5456
- }
5602
+ const key = cacheKey(opts2);
5603
+ const cached = cache.get(key);
5604
+ if (cached && cached.expires > Date.now()) {
5605
+ return { ...cached.result, localCost: opts2.localCost };
5457
5606
  }
5458
- const categoryFilter = opts2.category;
5459
- const localCost = sessions.reduce((sum, s) => sum + s.cost, 0);
5460
- const reconciliation = opts2.localOnly ? { status: "local-only", localCost } : await reconcileWithCloudflare({
5461
- localCost,
5462
- accountId: opts2.config.accountId,
5463
- apiToken: opts2.config.apiToken,
5464
- gatewayId: opts2.config.aiGatewayId,
5465
- startDate,
5466
- endDate
5467
- });
5468
- const report = buildReport({
5469
- startDate,
5470
- endDate,
5471
- sessions,
5472
- previousSessions: prevSessions,
5473
- reconciliation,
5474
- categoryFilter
5475
- });
5476
- if (opts2.json) {
5477
- console.log(renderJson(report));
5478
- } else {
5479
- console.log(renderTerminal(report));
5607
+ try {
5608
+ const result = {
5609
+ status: "local-only",
5610
+ localCost: opts2.localCost,
5611
+ message: "Cloudflare reconciliation not yet implemented"
5612
+ };
5613
+ cache.set(key, { result, expires: Date.now() + 60 * 60 * 1e3 });
5614
+ return result;
5615
+ } catch (err) {
5616
+ const message2 = err instanceof Error ? err.message : "Unknown error";
5617
+ return { status: "error", localCost: opts2.localCost, message: message2 };
5480
5618
  }
5481
5619
  }
5482
- var init_cli = __esm({
5483
- "src/cost-attribution/cli.ts"() {
5620
+ var cache;
5621
+ var init_reconcile = __esm({
5622
+ "src/cost-attribution/reconcile.ts"() {
5484
5623
  "use strict";
5485
- init_report();
5486
- init_renderer();
5487
- init_reconcile();
5488
- init_classify_from_session();
5624
+ cache = /* @__PURE__ */ new Map();
5489
5625
  }
5490
5626
  });
5491
5627
 
5492
- // src/cloud/auth.ts
5493
- var auth_exports = {};
5494
- __export(auth_exports, {
5495
- CLOUD_API_URL: () => CLOUD_API_URL,
5496
- POLL_INTERVAL_MS: () => POLL_INTERVAL_MS,
5497
- POLL_TIMEOUT_MS: () => POLL_TIMEOUT_MS,
5498
- authenticateDevice: () => authenticateDevice,
5499
- clearCloudCredentials: () => clearCloudCredentials,
5500
- fetchCloudUsage: () => fetchCloudUsage,
5501
- generateDeviceCodes: () => generateDeviceCodes,
5502
- loadCloudCredentials: () => loadCloudCredentials,
5503
- pollForToken: () => pollForToken,
5504
- registerDevice: () => registerDevice,
5505
- saveCloudCredentials: () => saveCloudCredentials
5506
- });
5507
- import { readFile as readFile11, writeFile as writeFile8 } from "fs/promises";
5508
- import { homedir as homedir9 } from "os";
5509
- import { join as join15 } from "path";
5510
- function cloudCredPath() {
5511
- const xdg = process.env.XDG_CONFIG_HOME || join15(homedir9(), ".config");
5512
- return join15(xdg, "kimiflare", "cloud.json");
5628
+ // src/cost-attribution/heuristic.ts
5629
+ function extOf(path) {
5630
+ const idx = path.lastIndexOf(".");
5631
+ return idx >= 0 ? path.slice(idx).toLowerCase() : "";
5513
5632
  }
5514
- function generateCode() {
5515
- const chars = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789";
5516
- let out = "";
5517
- for (let i = 0; i < 8; i++) {
5518
- out += chars[Math.floor(Math.random() * chars.length)];
5519
- }
5520
- return out;
5633
+ function basename2(path) {
5634
+ const idx = Math.max(path.lastIndexOf("/"), path.lastIndexOf("\\"));
5635
+ return idx >= 0 ? path.slice(idx + 1) : path;
5521
5636
  }
5522
- function generateDeviceId() {
5523
- const arr = new Uint8Array(16);
5524
- crypto.getRandomValues(arr);
5525
- return Array.from(arr, (b) => b.toString(16).padStart(2, "0")).join("");
5637
+ function classifyFile(path) {
5638
+ const base = basename2(path);
5639
+ const ext = extOf(path);
5640
+ if (TEST_PATTERNS.test(base)) return "writing-tests";
5641
+ if (base.toLowerCase().startsWith("readme")) return "reading-documentation";
5642
+ if (SOURCE_EXTS.has(ext)) return null;
5643
+ if (DOC_EXTS.has(ext)) return "reading-documentation";
5644
+ if (CONFIG_EXTS.has(ext)) return "reading-configuration";
5645
+ if (DATA_EXTS.has(ext)) return "reading-data";
5646
+ return null;
5526
5647
  }
5527
- function generateDeviceCodes() {
5528
- const deviceCode = `device-${generateCode()}-${Date.now()}`;
5529
- const userCode = `${generateCode()}-${generateCode()}`;
5530
- const authUrl = `${CLOUD_API_URL}/auth?code=${encodeURIComponent(userCode)}`;
5531
- const deviceId = generateDeviceId();
5532
- return { deviceCode, userCode, authUrl, deviceId };
5648
+ function classifyWriteFile(path) {
5649
+ const base = basename2(path);
5650
+ const ext = extOf(path);
5651
+ if (TEST_PATTERNS.test(base)) return "writing-tests";
5652
+ if (base.toLowerCase().startsWith("readme") || DOC_EXTS.has(ext)) return "writing-documentation";
5653
+ if (CONFIG_EXTS.has(ext)) return "writing-configuration";
5654
+ if (SOURCE_EXTS.has(ext)) return "writing-source-code";
5655
+ return null;
5533
5656
  }
5534
- async function registerDevice(codes) {
5535
- const registerRes = await fetch(`${CLOUD_API_URL}/auth/device`, {
5536
- method: "POST",
5537
- headers: { "Content-Type": "application/json" },
5538
- body: JSON.stringify({ device_code: codes.deviceCode, user_code: codes.userCode, device_id: codes.deviceId })
5539
- });
5540
- if (!registerRes.ok) {
5541
- const err = await registerRes.json().catch(() => ({}));
5542
- throw new Error(`Failed to register device: ${err.error || registerRes.statusText}`);
5543
- }
5657
+ function classifyEditFile(path) {
5658
+ const base = basename2(path);
5659
+ const ext = extOf(path);
5660
+ if (base.toLowerCase().startsWith("readme") || DOC_EXTS.has(ext)) return "editing-documentation";
5661
+ if (CONFIG_EXTS.has(ext)) return "editing-configuration";
5662
+ if (SOURCE_EXTS.has(ext)) return "editing-source-code";
5663
+ return null;
5544
5664
  }
5545
- async function pollForToken(deviceCode, deviceId) {
5546
- const pollRes = await fetch(`${CLOUD_API_URL}/auth/poll`, {
5547
- method: "POST",
5548
- headers: { "Content-Type": "application/json" },
5549
- body: JSON.stringify({ device_code: deviceCode })
5550
- });
5551
- if (!pollRes.ok) return null;
5552
- const pollData = await pollRes.json();
5553
- if (pollData.status === "approved" && pollData.access_token) {
5554
- const creds = {
5555
- accessToken: pollData.access_token,
5556
- expiresAt: Math.floor(Date.now() / 1e3) + 7 * 24 * 60 * 60,
5557
- // 7 days
5558
- deviceId
5559
- };
5560
- await saveCloudCredentials(creds);
5561
- return creds;
5665
+ function classifyBash(command) {
5666
+ if (TEST_COMMANDS.test(command)) return "running-tests";
5667
+ if (GIT_COMMANDS.test(command)) return "running-git-commands";
5668
+ if (BUILD_COMMANDS.test(command)) return "running-build-scripts";
5669
+ if (DEPLOY_COMMANDS.test(command)) return "running-deploy-commands";
5670
+ return "running-shell-commands";
5671
+ }
5672
+ function classifyToolCall(tool) {
5673
+ const args = tool.arguments ?? {};
5674
+ const path = typeof args.path === "string" ? args.path : typeof args.file_path === "string" ? args.file_path : "";
5675
+ const command = typeof args.command === "string" ? args.command : "";
5676
+ switch (tool.name) {
5677
+ case "read_file":
5678
+ case "read": {
5679
+ const cat = classifyFile(path);
5680
+ if (cat) return { category: cat, confidence: 0.8 };
5681
+ if (SOURCE_EXTS.has(extOf(path))) return { category: "reading-source-code", confidence: 0.8 };
5682
+ return null;
5683
+ }
5684
+ case "create_file":
5685
+ case "write_file":
5686
+ case "write": {
5687
+ const cat = classifyWriteFile(path);
5688
+ if (cat) return { category: cat, confidence: 0.9 };
5689
+ return null;
5690
+ }
5691
+ case "str_replace":
5692
+ case "edit": {
5693
+ const cat = classifyEditFile(path);
5694
+ if (cat) return { category: cat, confidence: 0.85 };
5695
+ return null;
5696
+ }
5697
+ case "bash": {
5698
+ const cat = classifyBash(command);
5699
+ if (cat) return { category: cat, confidence: 0.9 };
5700
+ return null;
5701
+ }
5702
+ case "web_fetch":
5703
+ return { category: "reading-web-content", confidence: 0.85 };
5704
+ case "grep":
5705
+ case "glob": {
5706
+ const pattern = typeof args.pattern === "string" ? args.pattern : path;
5707
+ const isSource = SOURCE_EXTS.has(extOf(pattern)) || /\.(ts|js|py|go|rs)\b/.test(pattern);
5708
+ return { category: isSource ? "searching-code" : "searching-code", confidence: 0.75 };
5709
+ }
5710
+ case "execute_code":
5711
+ return { category: "other", confidence: 0.6 };
5712
+ default:
5713
+ return null;
5562
5714
  }
5563
- return null;
5564
5715
  }
5565
- async function fetchCloudUsage(token, deviceId) {
5566
- const headers = { Authorization: `Bearer ${token}` };
5567
- if (deviceId) headers["X-Device-ID"] = deviceId;
5568
- const res = await fetch(`${CLOUD_API_URL}/v1/usage`, { headers });
5569
- if (!res.ok) return null;
5570
- const data = await res.json();
5571
- if (typeof data.remaining !== "number" || typeof data.input_token_limit !== "number" || typeof data.input_tokens_used !== "number" || typeof data.expires_at !== "string") {
5572
- return null;
5716
+ function classifyTurn(turn) {
5717
+ const signals = [];
5718
+ for (const tool of turn.toolCalls) {
5719
+ const result = classifyToolCall(tool);
5720
+ if (result) {
5721
+ signals.push({
5722
+ category: result.category,
5723
+ weight: turn.tokens ?? 1,
5724
+ confidence: result.confidence
5725
+ });
5726
+ }
5573
5727
  }
5574
- return {
5575
- input_token_limit: data.input_token_limit,
5576
- input_tokens_used: data.input_tokens_used,
5577
- remaining: data.remaining,
5578
- expires_at: data.expires_at
5579
- };
5728
+ return signals;
5580
5729
  }
5581
- async function loadCloudCredentials() {
5582
- try {
5583
- const raw = await readFile11(cloudCredPath(), "utf8");
5584
- const parsed = JSON.parse(raw);
5585
- if (parsed.expiresAt && parsed.expiresAt > Date.now() / 1e3 && parsed.accessToken) {
5586
- return parsed;
5730
+ function classifySession(turns, opts2) {
5731
+ const scores = /* @__PURE__ */ new Map();
5732
+ for (const turn of turns) {
5733
+ const signals = classifyTurn(turn);
5734
+ for (const s of signals) {
5735
+ scores.set(s.category, (scores.get(s.category) ?? 0) + s.weight * s.confidence);
5587
5736
  }
5588
- } catch {
5589
5737
  }
5590
- return null;
5591
- }
5592
- async function saveCloudCredentials(creds) {
5593
- const p = cloudCredPath();
5594
- await writeFile8(p, JSON.stringify(creds, null, 2), "utf8");
5595
- }
5596
- async function clearCloudCredentials() {
5597
- try {
5598
- const { unlink: unlink5 } = await import("fs/promises");
5599
- await unlink5(cloudCredPath());
5600
- } catch {
5738
+ const totalTurns = opts2?.totalTurns ?? turns.length;
5739
+ const totalToolCalls = opts2?.totalToolCalls ?? turns.reduce((sum, t) => sum + t.toolCalls.length, 0);
5740
+ if (totalTurns < 3 && totalToolCalls < 5) {
5741
+ scores.set("other", (scores.get("other") ?? 0) + 0.6);
5601
5742
  }
5602
- }
5603
- async function authenticateDevice(onStatus) {
5604
- const codes = generateDeviceCodes();
5605
- await registerDevice(codes);
5606
- onStatus({ url: codes.authUrl, userCode: codes.userCode, polling: false });
5607
- const startTime = Date.now();
5608
- while (Date.now() - startTime < POLL_TIMEOUT_MS) {
5609
- await new Promise((r) => setTimeout(r, POLL_INTERVAL_MS));
5610
- onStatus({ url: codes.authUrl, userCode: codes.userCode, polling: true });
5611
- const creds = await pollForToken(codes.deviceCode, codes.deviceId);
5612
- if (creds) return creds;
5743
+ if (scores.size === 0) {
5744
+ return { category: "other", confidence: 0.6, classifiedBy: "heuristic", summary: "Short or ambiguous session" };
5613
5745
  }
5614
- throw new Error("Authentication timed out. Please try again.");
5746
+ let bestCategory = "other";
5747
+ let bestScore = -1;
5748
+ let totalScore = 0;
5749
+ for (const [cat, score] of scores) {
5750
+ totalScore += score;
5751
+ if (score > bestScore) {
5752
+ bestScore = score;
5753
+ bestCategory = cat;
5754
+ }
5755
+ }
5756
+ const confidence = totalScore > 0 ? bestScore / totalScore : 0;
5757
+ return {
5758
+ category: bestCategory,
5759
+ confidence: Math.round(confidence * 100) / 100,
5760
+ classifiedBy: "heuristic"
5761
+ };
5615
5762
  }
5616
- var CLOUD_API_URL, POLL_INTERVAL_MS, POLL_TIMEOUT_MS;
5617
- var init_auth = __esm({
5618
- "src/cloud/auth.ts"() {
5763
+ var SOURCE_EXTS, DOC_EXTS, CONFIG_EXTS, DATA_EXTS, TEST_PATTERNS, TEST_COMMANDS, GIT_COMMANDS, BUILD_COMMANDS, DEPLOY_COMMANDS;
5764
+ var init_heuristic = __esm({
5765
+ "src/cost-attribution/heuristic.ts"() {
5619
5766
  "use strict";
5620
- CLOUD_API_URL = "https://api.kimiflare.com";
5621
- POLL_INTERVAL_MS = 5e3;
5622
- POLL_TIMEOUT_MS = 15 * 60 * 1e3;
5767
+ SOURCE_EXTS = /* @__PURE__ */ new Set([
5768
+ ".ts",
5769
+ ".tsx",
5770
+ ".js",
5771
+ ".jsx",
5772
+ ".py",
5773
+ ".go",
5774
+ ".rs",
5775
+ ".java",
5776
+ ".kt",
5777
+ ".swift",
5778
+ ".cpp",
5779
+ ".c",
5780
+ ".h",
5781
+ ".hpp",
5782
+ ".cs",
5783
+ ".rb",
5784
+ ".php",
5785
+ ".scala",
5786
+ ".clj",
5787
+ ".erl",
5788
+ ".ex",
5789
+ ".exs",
5790
+ ".elm",
5791
+ ".hs",
5792
+ ".lua",
5793
+ ".r",
5794
+ ".m",
5795
+ ".mm",
5796
+ ".sh",
5797
+ ".bash",
5798
+ ".zsh",
5799
+ ".fish",
5800
+ ".ps1",
5801
+ ".vim",
5802
+ ".el"
5803
+ ]);
5804
+ DOC_EXTS = /* @__PURE__ */ new Set([
5805
+ ".md",
5806
+ ".txt",
5807
+ ".rst",
5808
+ ".adoc",
5809
+ ".org"
5810
+ ]);
5811
+ CONFIG_EXTS = /* @__PURE__ */ new Set([
5812
+ ".json",
5813
+ ".yaml",
5814
+ ".yml",
5815
+ ".toml",
5816
+ ".ini",
5817
+ ".conf",
5818
+ ".cfg",
5819
+ ".env",
5820
+ ".envrc",
5821
+ ".properties",
5822
+ ".xml",
5823
+ ".plist"
5824
+ ]);
5825
+ DATA_EXTS = /* @__PURE__ */ new Set([
5826
+ ".csv",
5827
+ ".sql",
5828
+ ".db",
5829
+ ".sqlite",
5830
+ ".parquet",
5831
+ ".jsonl",
5832
+ ".ndjson",
5833
+ ".tsv",
5834
+ ".psv"
5835
+ ]);
5836
+ TEST_PATTERNS = /\.(test|spec)\./;
5837
+ TEST_COMMANDS = /\b(npm test|pytest|jest|vitest|mocha|ava|tap|cargo test|go test|dotnet test|gradle test|mvn test|rake test|bundle exec rspec)\b/;
5838
+ GIT_COMMANDS = /\b(git commit|git merge|git rebase|git diff|git log|git blame|git cherry-pick|git revert)\b/;
5839
+ BUILD_COMMANDS = /\b(npm run build|make|cargo build|go build|gradle build|mvn compile|dotnet build|yarn build|pnpm build|webpack|vite build|tsc|esbuild|rollup)\b/;
5840
+ DEPLOY_COMMANDS = /\b(docker|kubectl|helm|wrangler deploy|terraform apply|pulumi up|serverless deploy|aws deploy|gcloud deploy|fly deploy|vercel deploy|netlify deploy)\b/;
5623
5841
  }
5624
5842
  });
5625
5843
 
5626
- // src/remote/tui-auth.ts
5627
- var tui_auth_exports = {};
5628
- __export(tui_auth_exports, {
5629
- authGitHubForTui: () => authGitHubForTui
5630
- });
5631
- function sleep2(ms) {
5632
- return new Promise((resolve2) => setTimeout(resolve2, ms));
5633
- }
5634
- async function* authGitHubForTui() {
5635
- yield { message: "Starting GitHub OAuth device flow..." };
5636
- const deviceRes = await fetch(GITHUB_DEVICE_AUTH_URL, {
5637
- method: "POST",
5638
- headers: {
5639
- Accept: "application/json",
5640
- "Content-Type": "application/x-www-form-urlencoded"
5641
- },
5642
- body: new URLSearchParams({ client_id: CLIENT_ID, scope: "repo" })
5643
- });
5644
- if (!deviceRes.ok) {
5645
- yield { message: `Failed to request device code: ${deviceRes.status}`, error: true };
5646
- throw new Error("Device code request failed");
5647
- }
5648
- const deviceData = await deviceRes.json();
5649
- yield {
5650
- message: `Open ${deviceData.verification_uri} and enter code: ${deviceData.user_code}`,
5651
- url: deviceData.verification_uri,
5652
- code: deviceData.user_code
5653
- };
5654
- const startTime = Date.now();
5655
- const expiresIn = deviceData.expires_in * 1e3;
5656
- const interval = deviceData.interval * 1e3;
5657
- while (Date.now() - startTime < expiresIn) {
5658
- await sleep2(interval);
5659
- const tokenRes = await fetch(GITHUB_ACCESS_TOKEN_URL, {
5660
- method: "POST",
5661
- headers: {
5662
- Accept: "application/json",
5663
- "Content-Type": "application/x-www-form-urlencoded"
5664
- },
5665
- body: new URLSearchParams({
5666
- client_id: CLIENT_ID,
5667
- device_code: deviceData.device_code,
5668
- grant_type: "urn:ietf:params:oauth:grant-type:device_code"
5669
- })
5670
- });
5671
- if (!tokenRes.ok) continue;
5672
- const tokenData = await tokenRes.json();
5673
- if (tokenData.error === "authorization_pending") {
5674
- continue;
5675
- }
5676
- if (tokenData.error === "slow_down") {
5677
- await sleep2(interval * 2);
5678
- continue;
5679
- }
5680
- if (tokenData.error) {
5681
- yield { message: `OAuth error: ${tokenData.error}`, error: true };
5682
- throw new Error(tokenData.error);
5844
+ // src/cost-attribution/classify-from-session.ts
5845
+ import { readFile as readFile9 } from "fs/promises";
5846
+ import { join as join13 } from "path";
5847
+ import { homedir as homedir7 } from "os";
5848
+ function sessionsDir() {
5849
+ const xdg = process.env.XDG_DATA_HOME || join13(homedir7(), ".local", "share");
5850
+ return join13(xdg, "kimiflare", "sessions");
5851
+ }
5852
+ function parseToolCalls(calls) {
5853
+ return calls.map((c) => {
5854
+ let args = {};
5855
+ try {
5856
+ args = JSON.parse(c.function.arguments);
5857
+ } catch {
5683
5858
  }
5684
- if (tokenData.access_token) {
5685
- const cfg = await loadConfig() ?? {
5686
- accountId: "",
5687
- apiToken: "",
5688
- model: "@cf/moonshotai/kimi-k2.6"
5689
- };
5690
- await saveConfig({
5691
- ...cfg,
5692
- githubOAuthToken: tokenData.access_token,
5693
- githubRefreshToken: tokenData.refresh_token,
5694
- githubTokenExpiry: tokenData.expires_in ? Date.now() + tokenData.expires_in * 1e3 : void 0
5695
- });
5696
- yield { message: "GitHub authentication successful!", done: true };
5697
- return;
5859
+ return { name: c.function.name, arguments: args };
5860
+ });
5861
+ }
5862
+ async function classifyFromSessionFile(sessionId) {
5863
+ try {
5864
+ const raw = await readFile9(join13(sessionsDir(), `${sessionId}.json`), "utf8");
5865
+ const session = JSON.parse(raw);
5866
+ const messages = session.messages ?? [];
5867
+ const turns = [];
5868
+ for (const m of messages) {
5869
+ if (m.role === "assistant" && m.tool_calls && m.tool_calls.length > 0) {
5870
+ turns.push({ toolCalls: parseToolCalls(m.tool_calls), tokens: 100 });
5871
+ }
5698
5872
  }
5873
+ const totalToolCalls = turns.reduce((sum, t) => sum + t.toolCalls.length, 0);
5874
+ const result = classifySession(turns, { totalTurns: turns.length, totalToolCalls });
5875
+ return {
5876
+ category: result.category,
5877
+ confidence: result.confidence,
5878
+ classifiedBy: result.classifiedBy,
5879
+ summary: result.summary
5880
+ };
5881
+ } catch {
5882
+ return { category: "other", confidence: 0.5, classifiedBy: "heuristic", summary: "Session file unavailable" };
5699
5883
  }
5700
- yield { message: "Device flow expired. Please try again.", error: true };
5701
- throw new Error("Device flow expired");
5702
5884
  }
5703
- var GITHUB_DEVICE_AUTH_URL, GITHUB_ACCESS_TOKEN_URL, CLIENT_ID;
5704
- var init_tui_auth = __esm({
5705
- "src/remote/tui-auth.ts"() {
5885
+ var init_classify_from_session = __esm({
5886
+ "src/cost-attribution/classify-from-session.ts"() {
5706
5887
  "use strict";
5707
- init_config();
5708
- GITHUB_DEVICE_AUTH_URL = "https://github.com/login/device/code";
5709
- GITHUB_ACCESS_TOKEN_URL = "https://github.com/login/oauth/access_token";
5710
- CLIENT_ID = process.env.KIMIFLARE_GITHUB_CLIENT_ID ?? "Ov23liM7lJX1xE2V1sVK";
5888
+ init_heuristic();
5711
5889
  }
5712
5890
  });
5713
5891
 
5714
- // src/agent/supervisor.ts
5715
- var TurnSupervisor;
5716
- var init_supervisor = __esm({
5717
- "src/agent/supervisor.ts"() {
5718
- "use strict";
5719
- init_loop();
5720
- init_logger();
5721
- TurnSupervisor = class {
5722
- currentTurn = null;
5723
- _phase = "idle";
5724
- _killRequested = false;
5725
- get phase() {
5726
- return this._phase;
5727
- }
5728
- get isRunning() {
5729
- return this._phase !== "idle";
5730
- }
5731
- get killRequested() {
5732
- return this._killRequested;
5733
- }
5734
- startTurn(opts2, callbacks) {
5735
- if (this.isRunning) {
5736
- logger.warn("supervisor:start_rejected", { reason: "turn_already_running", phase: this._phase });
5737
- throw new Error("TurnSupervisor: turn already in progress");
5738
- }
5739
- this._phase = "streaming";
5740
- this._killRequested = false;
5741
- logger.debug("supervisor:turn_start", { sessionId: opts2.sessionId });
5742
- this.currentTurn = runAgentTurn(opts2).then(async () => {
5743
- this._phase = "idle";
5744
- if (this._killRequested) {
5745
- logger.debug("supervisor:turn_killed", { sessionId: opts2.sessionId });
5746
- } else {
5747
- logger.debug("supervisor:turn_done", { sessionId: opts2.sessionId });
5748
- }
5749
- await callbacks?.onDone?.();
5750
- }).catch(async (error) => {
5751
- this._phase = "idle";
5752
- const err = error;
5753
- logger.warn("supervisor:turn_error", {
5754
- sessionId: opts2.sessionId,
5755
- error: err.message ?? String(err),
5756
- name: err.name
5757
- });
5758
- await callbacks?.onError?.(err);
5759
- }).finally(() => {
5760
- this.currentTurn = null;
5761
- this._killRequested = false;
5762
- });
5763
- }
5764
- /** Request that the current turn be killed. This does NOT directly abort
5765
- * the turn — the caller must abort the AbortScope that was passed to
5766
- * `startTurn`. This method only records the intent so the supervisor
5767
- * knows the turn was intentionally killed rather than failing. */
5768
- killTurn() {
5769
- if (!this.isRunning) return;
5770
- this._killRequested = true;
5771
- logger.debug("supervisor:kill_requested", { phase: this._phase });
5772
- }
5773
- };
5774
- }
5892
+ // src/cost-attribution/cli.ts
5893
+ var cli_exports = {};
5894
+ __export(cli_exports, {
5895
+ runCostCommand: () => runCostCommand
5775
5896
  });
5776
-
5777
- // src/agent/compact.ts
5778
- function indexOfNthUserFromEnd(messages, n) {
5779
- let seen = 0;
5780
- for (let i = messages.length - 1; i >= 0; i--) {
5781
- if (messages[i].role === "user") {
5782
- seen++;
5783
- if (seen === n) return i;
5784
- }
5785
- }
5786
- return -1;
5897
+ import { readFile as readFile10 } from "fs/promises";
5898
+ import { join as join14 } from "path";
5899
+ import { homedir as homedir8 } from "os";
5900
+ function usageDir() {
5901
+ const xdg = process.env.XDG_DATA_HOME || join14(homedir8(), ".local", "share");
5902
+ return join14(xdg, "kimiflare");
5787
5903
  }
5788
- async function compactMessages(opts2) {
5789
- const keep = opts2.keepLastTurns ?? 4;
5790
- const messages = opts2.messages;
5791
- let prefixEnd = 0;
5792
- while (prefixEnd < messages.length && messages[prefixEnd].role === "system") {
5793
- prefixEnd++;
5794
- }
5795
- const prefix = messages.slice(0, prefixEnd);
5796
- if (prefix.length === 0) {
5797
- return { summary: "", newMessages: messages, replacedCount: 0 };
5904
+ function usagePath() {
5905
+ return join14(usageDir(), "usage.json");
5906
+ }
5907
+ function today() {
5908
+ return (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
5909
+ }
5910
+ function daysAgo(n) {
5911
+ const d = /* @__PURE__ */ new Date();
5912
+ d.setDate(d.getDate() - n);
5913
+ return d.toISOString().slice(0, 10);
5914
+ }
5915
+ async function loadLog() {
5916
+ try {
5917
+ const raw = await readFile10(usagePath(), "utf8");
5918
+ return JSON.parse(raw);
5919
+ } catch {
5920
+ return { version: 1, days: [], sessions: [] };
5798
5921
  }
5799
- const cutoffUserIdx = indexOfNthUserFromEnd(messages, keep);
5800
- const firstKeepIdx = cutoffUserIdx >= 0 ? cutoffUserIdx : messages.length;
5801
- const toSummarize = messages.slice(prefixEnd, firstKeepIdx);
5802
- const toKeep = messages.slice(firstKeepIdx);
5803
- if (toSummarize.length === 0) {
5804
- return { summary: "", newMessages: messages, replacedCount: 0 };
5922
+ }
5923
+ function filterSessions(sessions, start, end) {
5924
+ return sessions.filter((s) => s.date >= start && s.date <= end);
5925
+ }
5926
+ async function runCostCommand(opts2) {
5927
+ const log2 = await loadLog();
5928
+ let startDate;
5929
+ let endDate;
5930
+ let prevStart;
5931
+ let prevEnd;
5932
+ if (opts2.month) {
5933
+ startDate = daysAgo(30);
5934
+ endDate = today();
5935
+ prevStart = daysAgo(60);
5936
+ prevEnd = daysAgo(31);
5937
+ } else if (opts2.day) {
5938
+ startDate = today();
5939
+ endDate = today();
5940
+ prevStart = daysAgo(1);
5941
+ prevEnd = daysAgo(1);
5942
+ } else {
5943
+ startDate = daysAgo(7);
5944
+ endDate = today();
5945
+ prevStart = daysAgo(14);
5946
+ prevEnd = daysAgo(8);
5805
5947
  }
5806
- const transcript = toSummarize.map((m) => {
5807
- const contentStr = typeof m.content === "string" ? m.content : m.content?.map((p) => p.type === "text" ? p.text : "[image]").join(" ") ?? "";
5808
- if (m.role === "tool") {
5809
- const snippet = contentStr.slice(0, 500);
5810
- return `[tool ${m.name ?? ""}] ${snippet}`;
5948
+ if (opts2.session) {
5949
+ const session = log2.sessions.find((s) => s.id === opts2.session);
5950
+ if (!session) {
5951
+ console.error(`Session ${opts2.session} not found.`);
5952
+ process.exit(1);
5811
5953
  }
5812
- if (m.role === "assistant") {
5813
- const calls = m.tool_calls ? ` (tool_calls: ${m.tool_calls.map((c) => c.function.name).join(", ")})` : "";
5814
- return `[assistant]${calls} ${contentStr}`;
5954
+ console.log(JSON.stringify(session, null, 2));
5955
+ return;
5956
+ }
5957
+ const sessions = filterSessions(log2.sessions, startDate, endDate);
5958
+ const prevSessions = filterSessions(log2.sessions, prevStart, prevEnd);
5959
+ for (const s of sessions) {
5960
+ if (!s.category || opts2.reclassify) {
5961
+ const result = await classifyFromSessionFile(s.id);
5962
+ s.category = result.category;
5963
+ s.confidence = result.confidence;
5964
+ s.classifiedBy = result.classifiedBy;
5965
+ s.summary = result.summary;
5966
+ s.classifiedAt = (/* @__PURE__ */ new Date()).toISOString();
5815
5967
  }
5816
- return `[${m.role}] ${contentStr}`;
5817
- }).join("\n");
5818
- let summary = "";
5819
- const events = runKimi({
5820
- accountId: opts2.accountId,
5821
- apiToken: opts2.apiToken,
5822
- model: opts2.model,
5823
- messages: [
5824
- { role: "system", content: SUMMARY_SYSTEM },
5825
- { role: "user", content: `Summarize this session so it can be replaced by your summary:
5826
-
5827
- ${transcript}` }
5828
- ],
5829
- signal: opts2.signal,
5830
- temperature: 0.1,
5831
- reasoningEffort: "low",
5832
- gateway: opts2.gateway,
5833
- idleTimeoutMs: 6e4
5968
+ }
5969
+ const categoryFilter = opts2.category;
5970
+ const localCost = sessions.reduce((sum, s) => sum + s.cost, 0);
5971
+ const reconciliation = opts2.localOnly ? { status: "local-only", localCost } : await reconcileWithCloudflare({
5972
+ localCost,
5973
+ accountId: opts2.config.accountId,
5974
+ apiToken: opts2.config.apiToken,
5975
+ gatewayId: opts2.config.aiGatewayId,
5976
+ startDate,
5977
+ endDate
5834
5978
  });
5835
- for await (const ev of events) {
5836
- if (ev.type === "text") summary += ev.delta;
5979
+ const report = buildReport({
5980
+ startDate,
5981
+ endDate,
5982
+ sessions,
5983
+ previousSessions: prevSessions,
5984
+ reconciliation,
5985
+ categoryFilter
5986
+ });
5987
+ if (opts2.json) {
5988
+ console.log(renderJson(report));
5989
+ } else {
5990
+ console.log(renderTerminal(report));
5837
5991
  }
5838
- const summaryMsg = {
5839
- role: "user",
5840
- content: `[compacted summary of earlier turns]
5841
- ${summary.trim()}`
5842
- };
5843
- return {
5844
- summary: summary.trim(),
5845
- newMessages: [...prefix, summaryMsg, ...toKeep],
5846
- replacedCount: toSummarize.length
5847
- };
5848
5992
  }
5849
- var SUMMARY_SYSTEM;
5850
- var init_compact = __esm({
5851
- "src/agent/compact.ts"() {
5993
+ var init_cli = __esm({
5994
+ "src/cost-attribution/cli.ts"() {
5852
5995
  "use strict";
5853
- init_client();
5854
- SUMMARY_SYSTEM = `You are summarizing a terminal coding session so it can fit back into a short context window. Produce a dense summary that captures:
5855
- - The user's goal(s) and what they've asked for.
5856
- - Files read or modified, with paths.
5857
- - Tools run (bash commands, edits) and the outcome of each.
5858
- - Decisions made and open questions.
5859
- - Any constraints or preferences the user has stated.
5860
-
5861
- Do not include speculation. Do not include chat-style pleasantries. Use short bullet form. Aim for ~400-800 tokens.`;
5996
+ init_report();
5997
+ init_renderer();
5998
+ init_reconcile();
5999
+ init_classify_from_session();
5862
6000
  }
5863
6001
  });
5864
6002
 
5865
- // src/agent/session-state.ts
5866
- function emptySessionState(task = "") {
5867
- return {
5868
- task,
5869
- user_constraints: [],
5870
- repo_facts: [],
5871
- files_touched: [],
5872
- files_modified: [],
5873
- confirmed_findings: [],
5874
- open_questions: [],
5875
- recent_failures: [],
5876
- decisions: [],
5877
- next_actions: [],
5878
- artifact_index: {}
5879
- };
6003
+ // src/cloud/auth.ts
6004
+ var auth_exports = {};
6005
+ __export(auth_exports, {
6006
+ CLOUD_API_URL: () => CLOUD_API_URL,
6007
+ POLL_INTERVAL_MS: () => POLL_INTERVAL_MS,
6008
+ POLL_TIMEOUT_MS: () => POLL_TIMEOUT_MS,
6009
+ authenticateDevice: () => authenticateDevice,
6010
+ clearCloudCredentials: () => clearCloudCredentials,
6011
+ fetchCloudUsage: () => fetchCloudUsage,
6012
+ generateDeviceCodes: () => generateDeviceCodes,
6013
+ loadCloudCredentials: () => loadCloudCredentials,
6014
+ pollForToken: () => pollForToken,
6015
+ registerDevice: () => registerDevice,
6016
+ saveCloudCredentials: () => saveCloudCredentials
6017
+ });
6018
+ import { readFile as readFile11, writeFile as writeFile8 } from "fs/promises";
6019
+ import { homedir as homedir9 } from "os";
6020
+ import { join as join15 } from "path";
6021
+ function cloudCredPath() {
6022
+ const xdg = process.env.XDG_CONFIG_HOME || join15(homedir9(), ".config");
6023
+ return join15(xdg, "kimiflare", "cloud.json");
5880
6024
  }
5881
- function serializeArtifactStore(store) {
5882
- const MAX_ARTIFACT_CHARS = 5e4;
5883
- const out = [];
5884
- for (const a of store.list()) {
5885
- out.push({
5886
- id: a.id,
5887
- type: a.type,
5888
- summary: a.summary,
5889
- raw: a.raw.slice(0, MAX_ARTIFACT_CHARS),
5890
- source: a.source,
5891
- path: a.path,
5892
- lineRange: a.lineRange,
5893
- ts: a.ts
5894
- });
6025
+ function generateCode() {
6026
+ const chars = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789";
6027
+ let out = "";
6028
+ for (let i = 0; i < 8; i++) {
6029
+ out += chars[Math.floor(Math.random() * chars.length)];
5895
6030
  }
5896
6031
  return out;
5897
6032
  }
5898
- function deserializeArtifactStore(data) {
5899
- const store = new ArtifactStore();
5900
- for (const a of data) {
5901
- store.add({
5902
- id: a.id,
5903
- type: a.type,
5904
- summary: a.summary,
5905
- raw: a.raw,
5906
- source: a.source,
5907
- path: a.path,
5908
- lineRange: a.lineRange,
5909
- ts: a.ts
5910
- });
5911
- }
5912
- return store;
6033
+ function generateDeviceId() {
6034
+ const arr = new Uint8Array(16);
6035
+ crypto.getRandomValues(arr);
6036
+ return Array.from(arr, (b) => b.toString(16).padStart(2, "0")).join("");
5913
6037
  }
5914
- function formatRecalledArtifacts(recalled) {
5915
- if (recalled.length === 0) return "";
5916
- const lines = ["[recalled artifacts]"];
5917
- for (const { id, artifact } of recalled) {
5918
- lines.push(`--- artifact:${id} (${artifact.type} from ${artifact.source}) ---`);
5919
- lines.push(artifact.raw);
6038
+ function generateDeviceCodes() {
6039
+ const deviceCode = `device-${generateCode()}-${Date.now()}`;
6040
+ const userCode = `${generateCode()}-${generateCode()}`;
6041
+ const authUrl = `${CLOUD_API_URL}/auth?code=${encodeURIComponent(userCode)}`;
6042
+ const deviceId = generateDeviceId();
6043
+ return { deviceCode, userCode, authUrl, deviceId };
6044
+ }
6045
+ async function registerDevice(codes) {
6046
+ const registerRes = await fetch(`${CLOUD_API_URL}/auth/device`, {
6047
+ method: "POST",
6048
+ headers: { "Content-Type": "application/json" },
6049
+ body: JSON.stringify({ device_code: codes.deviceCode, user_code: codes.userCode, device_id: codes.deviceId })
6050
+ });
6051
+ if (!registerRes.ok) {
6052
+ const err = await registerRes.json().catch(() => ({}));
6053
+ throw new Error(`Failed to register device: ${err.error || registerRes.statusText}`);
5920
6054
  }
5921
- return lines.join("\n");
5922
6055
  }
5923
- function serializeSessionState(state) {
5924
- const lines = [];
5925
- lines.push(`task: ${state.task || "(none)"}`);
5926
- if (state.user_constraints.length) lines.push(`constraints:
5927
- ${state.user_constraints.map((c) => " - " + c).join("\n")}`);
5928
- if (state.repo_facts.length) lines.push(`repo_facts:
5929
- ${state.repo_facts.map((f) => " - " + f).join("\n")}`);
5930
- if (state.files_touched.length) lines.push(`files_touched: ${state.files_touched.join(", ")}`);
5931
- if (state.files_modified.length) lines.push(`files_modified: ${state.files_modified.join(", ")}`);
5932
- if (state.confirmed_findings.length) lines.push(`findings:
5933
- ${state.confirmed_findings.map((f) => " - " + f).join("\n")}`);
5934
- if (state.open_questions.length) lines.push(`open_questions:
5935
- ${state.open_questions.map((q) => " - " + q).join("\n")}`);
5936
- if (state.recent_failures.length) lines.push(`recent_failures:
5937
- ${state.recent_failures.map((f) => " - " + f).join("\n")}`);
5938
- if (state.decisions.length) lines.push(`decisions:
5939
- ${state.decisions.map((d) => " - " + d).join("\n")}`);
5940
- if (state.next_actions.length) lines.push(`next_actions:
5941
- ${state.next_actions.map((a) => " - " + a).join("\n")}`);
5942
- if (Object.keys(state.artifact_index).length) {
5943
- lines.push("artifact_index:");
5944
- for (const [id, meta] of Object.entries(state.artifact_index)) {
5945
- lines.push(` ${id}: [${meta.type}] ${meta.summary}`);
5946
- }
6056
+ async function pollForToken(deviceCode, deviceId) {
6057
+ const pollRes = await fetch(`${CLOUD_API_URL}/auth/poll`, {
6058
+ method: "POST",
6059
+ headers: { "Content-Type": "application/json" },
6060
+ body: JSON.stringify({ device_code: deviceCode })
6061
+ });
6062
+ if (!pollRes.ok) return null;
6063
+ const pollData = await pollRes.json();
6064
+ if (pollData.status === "approved" && pollData.access_token) {
6065
+ const creds = {
6066
+ accessToken: pollData.access_token,
6067
+ expiresAt: Math.floor(Date.now() / 1e3) + 7 * 24 * 60 * 60,
6068
+ // 7 days
6069
+ deviceId
6070
+ };
6071
+ await saveCloudCredentials(creds);
6072
+ return creds;
5947
6073
  }
5948
- return lines.join("\n");
6074
+ return null;
5949
6075
  }
5950
- function buildSessionStateMessage(state) {
6076
+ async function fetchCloudUsage(token, deviceId) {
6077
+ const headers = { Authorization: `Bearer ${token}` };
6078
+ if (deviceId) headers["X-Device-ID"] = deviceId;
6079
+ const res = await fetch(`${CLOUD_API_URL}/v1/usage`, { headers });
6080
+ if (!res.ok) return null;
6081
+ const data = await res.json();
6082
+ if (typeof data.remaining !== "number" || typeof data.input_token_limit !== "number" || typeof data.input_tokens_used !== "number" || typeof data.expires_at !== "string") {
6083
+ return null;
6084
+ }
5951
6085
  return {
5952
- role: "system",
5953
- content: `[compiled session state]
5954
- ${serializeSessionState(state)}`
6086
+ input_token_limit: data.input_token_limit,
6087
+ input_tokens_used: data.input_tokens_used,
6088
+ remaining: data.remaining,
6089
+ expires_at: data.expires_at
5955
6090
  };
5956
- }
5957
- var ArtifactStore;
5958
- var init_session_state = __esm({
5959
- "src/agent/session-state.ts"() {
5960
- "use strict";
5961
- ArtifactStore = class {
5962
- artifacts = /* @__PURE__ */ new Map();
5963
- maxArtifacts;
5964
- maxTotalChars;
5965
- constructor(opts2) {
5966
- this.maxArtifacts = opts2?.maxArtifacts ?? 200;
5967
- this.maxTotalChars = opts2?.maxTotalChars ?? 5e5;
5968
- }
5969
- add(a) {
5970
- while (this.totalChars() + a.raw.length > this.maxTotalChars && this.artifacts.size > 0) {
5971
- this.evictOldest();
5972
- }
5973
- while (this.artifacts.size >= this.maxArtifacts) {
5974
- this.evictOldest();
5975
- }
5976
- this.artifacts.set(a.id, a);
5977
- }
5978
- get(id) {
5979
- return this.artifacts.get(id);
5980
- }
5981
- has(id) {
5982
- return this.artifacts.has(id);
5983
- }
5984
- list() {
5985
- return [...this.artifacts.values()].sort((a, b) => a.ts < b.ts ? -1 : 1);
5986
- }
5987
- recall(ids) {
5988
- const out = [];
5989
- for (const id of ids) {
5990
- const a = this.artifacts.get(id);
5991
- if (a) out.push({ id, artifact: a });
5992
- }
5993
- return out;
5994
- }
5995
- size() {
5996
- return this.artifacts.size;
5997
- }
5998
- totalChars() {
5999
- let sum = 0;
6000
- for (const a of this.artifacts.values()) {
6001
- sum += a.raw.length;
6002
- }
6003
- return sum;
6004
- }
6005
- evictOldest() {
6006
- let oldest;
6007
- for (const a of this.artifacts.values()) {
6008
- if (!oldest || a.ts < oldest.ts) oldest = a;
6009
- }
6010
- if (oldest) this.artifacts.delete(oldest.id);
6011
- }
6012
- };
6091
+ }
6092
+ async function loadCloudCredentials() {
6093
+ try {
6094
+ const raw = await readFile11(cloudCredPath(), "utf8");
6095
+ const parsed = JSON.parse(raw);
6096
+ if (parsed.expiresAt && parsed.expiresAt > Date.now() / 1e3 && parsed.accessToken) {
6097
+ return parsed;
6098
+ }
6099
+ } catch {
6013
6100
  }
6014
- });
6015
-
6016
- // src/agent/compaction.ts
6017
- function approxTokens2(n) {
6018
- return Math.round(n / 4);
6101
+ return null;
6019
6102
  }
6020
- function estimateMessageTokens(m) {
6021
- let chars = 0;
6022
- if (typeof m.content === "string") {
6023
- chars = m.content.length;
6024
- } else if (Array.isArray(m.content)) {
6025
- chars = m.content.map((p) => p.type === "text" ? p.text.length : 0).reduce((a, b) => a + b, 0);
6103
+ async function saveCloudCredentials(creds) {
6104
+ const p = cloudCredPath();
6105
+ await writeFile8(p, JSON.stringify(creds, null, 2), "utf8");
6106
+ }
6107
+ async function clearCloudCredentials() {
6108
+ try {
6109
+ const { unlink: unlink5 } = await import("fs/promises");
6110
+ await unlink5(cloudCredPath());
6111
+ } catch {
6026
6112
  }
6027
- if (m.reasoning_content) chars += m.reasoning_content.length;
6028
- if (m.tool_calls) {
6029
- for (const tc of m.tool_calls) {
6030
- chars += tc.function.name.length + tc.function.arguments.length;
6031
- }
6113
+ }
6114
+ async function authenticateDevice(onStatus) {
6115
+ const codes = generateDeviceCodes();
6116
+ await registerDevice(codes);
6117
+ onStatus({ url: codes.authUrl, userCode: codes.userCode, polling: false });
6118
+ const startTime = Date.now();
6119
+ while (Date.now() - startTime < POLL_TIMEOUT_MS) {
6120
+ await new Promise((r) => setTimeout(r, POLL_INTERVAL_MS));
6121
+ onStatus({ url: codes.authUrl, userCode: codes.userCode, polling: true });
6122
+ const creds = await pollForToken(codes.deviceCode, codes.deviceId);
6123
+ if (creds) return creds;
6032
6124
  }
6033
- return approxTokens2(chars);
6125
+ throw new Error("Authentication timed out. Please try again.");
6034
6126
  }
6035
- function estimatePromptTokens(messages) {
6036
- return messages.reduce((sum, m) => sum + estimateMessageTokens(m), 0);
6127
+ var CLOUD_API_URL, POLL_INTERVAL_MS, POLL_TIMEOUT_MS;
6128
+ var init_auth = __esm({
6129
+ "src/cloud/auth.ts"() {
6130
+ "use strict";
6131
+ CLOUD_API_URL = "https://api.kimiflare.com";
6132
+ POLL_INTERVAL_MS = 5e3;
6133
+ POLL_TIMEOUT_MS = 15 * 60 * 1e3;
6134
+ }
6135
+ });
6136
+
6137
+ // src/remote/tui-auth.ts
6138
+ var tui_auth_exports = {};
6139
+ __export(tui_auth_exports, {
6140
+ authGitHubForTui: () => authGitHubForTui
6141
+ });
6142
+ function sleep2(ms) {
6143
+ return new Promise((resolve2) => setTimeout(resolve2, ms));
6037
6144
  }
6038
- function groupIntoTurns(messages) {
6039
- const prefix = [];
6040
- let i = 0;
6041
- while (i < messages.length && messages[i].role === "system") {
6042
- prefix.push(messages[i]);
6043
- i++;
6145
+ async function* authGitHubForTui() {
6146
+ yield { message: "Starting GitHub OAuth device flow..." };
6147
+ const deviceRes = await fetch(GITHUB_DEVICE_AUTH_URL, {
6148
+ method: "POST",
6149
+ headers: {
6150
+ Accept: "application/json",
6151
+ "Content-Type": "application/x-www-form-urlencoded"
6152
+ },
6153
+ body: new URLSearchParams({ client_id: CLIENT_ID, scope: "repo" })
6154
+ });
6155
+ if (!deviceRes.ok) {
6156
+ yield { message: `Failed to request device code: ${deviceRes.status}`, error: true };
6157
+ throw new Error("Device code request failed");
6044
6158
  }
6045
- const turns = [];
6046
- while (i < messages.length) {
6047
- if (messages[i].role !== "user") {
6048
- i++;
6159
+ const deviceData = await deviceRes.json();
6160
+ yield {
6161
+ message: `Open ${deviceData.verification_uri} and enter code: ${deviceData.user_code}`,
6162
+ url: deviceData.verification_uri,
6163
+ code: deviceData.user_code
6164
+ };
6165
+ const startTime = Date.now();
6166
+ const expiresIn = deviceData.expires_in * 1e3;
6167
+ const interval = deviceData.interval * 1e3;
6168
+ while (Date.now() - startTime < expiresIn) {
6169
+ await sleep2(interval);
6170
+ const tokenRes = await fetch(GITHUB_ACCESS_TOKEN_URL, {
6171
+ method: "POST",
6172
+ headers: {
6173
+ Accept: "application/json",
6174
+ "Content-Type": "application/x-www-form-urlencoded"
6175
+ },
6176
+ body: new URLSearchParams({
6177
+ client_id: CLIENT_ID,
6178
+ device_code: deviceData.device_code,
6179
+ grant_type: "urn:ietf:params:oauth:grant-type:device_code"
6180
+ })
6181
+ });
6182
+ if (!tokenRes.ok) continue;
6183
+ const tokenData = await tokenRes.json();
6184
+ if (tokenData.error === "authorization_pending") {
6049
6185
  continue;
6050
6186
  }
6051
- const user = messages[i];
6052
- i++;
6053
- if (i >= messages.length || messages[i].role !== "assistant") {
6187
+ if (tokenData.error === "slow_down") {
6188
+ await sleep2(interval * 2);
6054
6189
  continue;
6055
6190
  }
6056
- const assistant = messages[i];
6057
- i++;
6058
- const tools = [];
6059
- while (i < messages.length && messages[i].role === "tool") {
6060
- tools.push(messages[i]);
6061
- i++;
6191
+ if (tokenData.error) {
6192
+ yield { message: `OAuth error: ${tokenData.error}`, error: true };
6193
+ throw new Error(tokenData.error);
6194
+ }
6195
+ if (tokenData.access_token) {
6196
+ const cfg = await loadConfig() ?? {
6197
+ accountId: "",
6198
+ apiToken: "",
6199
+ model: "@cf/moonshotai/kimi-k2.6"
6200
+ };
6201
+ await saveConfig({
6202
+ ...cfg,
6203
+ githubOAuthToken: tokenData.access_token,
6204
+ githubRefreshToken: tokenData.refresh_token,
6205
+ githubTokenExpiry: tokenData.expires_in ? Date.now() + tokenData.expires_in * 1e3 : void 0
6206
+ });
6207
+ yield { message: "GitHub authentication successful!", done: true };
6208
+ return;
6062
6209
  }
6063
- turns.push({ user, assistant, tools });
6064
6210
  }
6065
- return { prefix, turns };
6066
- }
6067
- function makeArtifactId(type, index) {
6068
- return `${type}_${Date.now()}_${index}`;
6211
+ yield { message: "Device flow expired. Please try again.", error: true };
6212
+ throw new Error("Device flow expired");
6069
6213
  }
6070
- function extractArtifactsFromTurn(turn, startIndex, store) {
6071
- const artifacts = [];
6072
- const stateDelta = {
6073
- files_touched: [],
6074
- files_modified: [],
6075
- confirmed_findings: [],
6076
- recent_failures: [],
6077
- decisions: [],
6078
- next_actions: []
6079
- };
6080
- const toolCalls = turn.assistant.tool_calls ?? [];
6081
- for (let ti = 0; ti < turn.tools.length; ti++) {
6082
- const tm = turn.tools[ti];
6083
- const tc = toolCalls[ti];
6084
- const name = tm.name ?? tc?.function.name ?? "unknown";
6085
- const content = typeof tm.content === "string" ? tm.content : "";
6086
- let type = "tool_result";
6087
- let summary = `${name} result`;
6088
- let path;
6089
- if (name === "read") {
6090
- type = "read_slice";
6091
- try {
6092
- const args = tc ? JSON.parse(tc.function.arguments) : {};
6093
- path = args.path;
6094
- summary = `read ${path ?? "file"}`;
6095
- if (path && !stateDelta.files_touched.includes(path)) stateDelta.files_touched.push(path);
6096
- } catch {
6097
- summary = "read file";
6098
- }
6099
- } else if (name === "bash") {
6100
- type = "bash_log";
6101
- try {
6102
- const args = tc ? JSON.parse(tc.function.arguments) : {};
6103
- const cmd = args.command ?? "";
6104
- summary = `bash: ${cmd.slice(0, 60)}`;
6105
- if (content.includes("Error") || content.includes("error") || content.includes("FAIL")) {
6106
- stateDelta.recent_failures.push(`bash failed: ${cmd.slice(0, 80)}`);
6107
- }
6108
- } catch {
6109
- summary = "bash command";
6214
+ var GITHUB_DEVICE_AUTH_URL, GITHUB_ACCESS_TOKEN_URL, CLIENT_ID;
6215
+ var init_tui_auth = __esm({
6216
+ "src/remote/tui-auth.ts"() {
6217
+ "use strict";
6218
+ init_config();
6219
+ GITHUB_DEVICE_AUTH_URL = "https://github.com/login/device/code";
6220
+ GITHUB_ACCESS_TOKEN_URL = "https://github.com/login/oauth/access_token";
6221
+ CLIENT_ID = process.env.KIMIFLARE_GITHUB_CLIENT_ID ?? "Ov23liM7lJX1xE2V1sVK";
6222
+ }
6223
+ });
6224
+
6225
+ // src/agent/supervisor.ts
6226
+ var TurnSupervisor;
6227
+ var init_supervisor = __esm({
6228
+ "src/agent/supervisor.ts"() {
6229
+ "use strict";
6230
+ init_loop();
6231
+ init_logger();
6232
+ TurnSupervisor = class {
6233
+ currentTurn = null;
6234
+ _phase = "idle";
6235
+ _killRequested = false;
6236
+ get phase() {
6237
+ return this._phase;
6110
6238
  }
6111
- } else if (name === "grep") {
6112
- type = "grep_result";
6113
- summary = `grep results (${content.split("\n").length} lines)`;
6114
- } else if (name === "web_fetch") {
6115
- type = "web_fetch";
6116
- try {
6117
- const args = tc ? JSON.parse(tc.function.arguments) : {};
6118
- summary = `web_fetch: ${args.url ?? "url"}`;
6119
- } catch {
6120
- summary = "web_fetch";
6239
+ get isRunning() {
6240
+ return this._phase !== "idle";
6121
6241
  }
6122
- } else if (name === "write" || name === "edit") {
6123
- try {
6124
- const args = tc ? JSON.parse(tc.function.arguments) : {};
6125
- path = args.path;
6126
- if (path && !stateDelta.files_modified.includes(path)) stateDelta.files_modified.push(path);
6127
- if (path && !stateDelta.files_touched.includes(path)) stateDelta.files_touched.push(path);
6128
- } catch {
6242
+ get killRequested() {
6243
+ return this._killRequested;
6129
6244
  }
6130
- continue;
6131
- } else if (name === "glob") {
6132
- try {
6133
- const args = tc ? JSON.parse(tc.function.arguments) : {};
6134
- summary = `glob: ${args.pattern ?? ""}`;
6135
- } catch {
6136
- summary = "glob";
6245
+ startTurn(opts2, callbacks) {
6246
+ if (this.isRunning) {
6247
+ logger.warn("supervisor:start_rejected", { reason: "turn_already_running", phase: this._phase });
6248
+ throw new Error("TurnSupervisor: turn already in progress");
6249
+ }
6250
+ this._phase = "streaming";
6251
+ this._killRequested = false;
6252
+ logger.debug("supervisor:turn_start", { sessionId: opts2.sessionId });
6253
+ this.currentTurn = runAgentTurn(opts2).then(async () => {
6254
+ this._phase = "idle";
6255
+ if (this._killRequested) {
6256
+ logger.debug("supervisor:turn_killed", { sessionId: opts2.sessionId });
6257
+ } else {
6258
+ logger.debug("supervisor:turn_done", { sessionId: opts2.sessionId });
6259
+ }
6260
+ await callbacks?.onDone?.();
6261
+ }).catch(async (error) => {
6262
+ this._phase = "idle";
6263
+ const err = error;
6264
+ logger.warn("supervisor:turn_error", {
6265
+ sessionId: opts2.sessionId,
6266
+ error: err.message ?? String(err),
6267
+ name: err.name
6268
+ });
6269
+ await callbacks?.onError?.(err);
6270
+ }).finally(() => {
6271
+ this.currentTurn = null;
6272
+ this._killRequested = false;
6273
+ });
6137
6274
  }
6138
- } else if (name === "tasks_set") {
6139
- try {
6140
- const args = tc ? JSON.parse(tc.function.arguments) : {};
6141
- const tasks = args.tasks ?? [];
6142
- const inProgress = tasks.filter((t) => t.status === "in_progress").map((t) => t.title);
6143
- const pending = tasks.filter((t) => t.status === "pending").map((t) => t.title);
6144
- if (inProgress.length) stateDelta.next_actions.push(...inProgress);
6145
- if (pending.length) stateDelta.next_actions.push(...pending);
6146
- summary = `tasks_set: ${tasks.length} tasks`;
6147
- } catch {
6148
- summary = "tasks_set";
6275
+ /** Request that the current turn be killed. This does NOT directly abort
6276
+ * the turn — the caller must abort the AbortScope that was passed to
6277
+ * `startTurn`. This method only records the intent so the supervisor
6278
+ * knows the turn was intentionally killed rather than failing. */
6279
+ killTurn() {
6280
+ if (!this.isRunning) return;
6281
+ this._killRequested = true;
6282
+ logger.debug("supervisor:kill_requested", { phase: this._phase });
6149
6283
  }
6150
- }
6151
- const maxRaw = 5e4;
6152
- const raw = content.length > maxRaw ? content.slice(0, maxRaw) + `
6153
- ...[${content.length - maxRaw} chars truncated]` : content;
6154
- const artifact = {
6155
- id: makeArtifactId(type, startIndex + ti),
6156
- type,
6157
- summary,
6158
- raw,
6159
- source: name,
6160
- path,
6161
- ts: (/* @__PURE__ */ new Date()).toISOString()
6162
6284
  };
6163
- artifacts.push(artifact);
6164
- if (!content.includes("Error") && !content.includes("error") && content.length > 0 && content.length < 2e3) {
6165
- stateDelta.confirmed_findings.push(`${name}: ${content.slice(0, 200)}`);
6166
- }
6167
6285
  }
6168
- const assistantText = typeof turn.assistant.content === "string" ? turn.assistant.content : "";
6169
- if (assistantText.length > 0) {
6170
- const decisionPatterns = [
6171
- /(?:decided?|will|plan to|going to|should|need to)\s+(.{10,200})/gi,
6172
- /(?:let's|let us)\s+(.{10,200})/gi
6173
- ];
6174
- for (const pattern of decisionPatterns) {
6175
- let match;
6176
- while ((match = pattern.exec(assistantText)) !== null) {
6177
- const decision = match[1].trim().replace(/\.$/, "");
6178
- if (decision.length > 10 && !stateDelta.decisions.includes(decision)) {
6179
- stateDelta.decisions.push(decision);
6180
- }
6181
- }
6286
+ });
6287
+
6288
+ // src/agent/compact.ts
6289
+ function indexOfNthUserFromEnd(messages, n) {
6290
+ let seen = 0;
6291
+ for (let i = messages.length - 1; i >= 0; i--) {
6292
+ if (messages[i].role === "user") {
6293
+ seen++;
6294
+ if (seen === n) return i;
6182
6295
  }
6183
6296
  }
6184
- return { artifacts, stateDelta };
6185
- }
6186
- function mergeState(state, delta) {
6187
- const mergeArr = (a, b) => {
6188
- if (!b) return a;
6189
- const set = new Set(a);
6190
- for (const item of b) set.add(item);
6191
- return [...set];
6192
- };
6193
- return {
6194
- ...state,
6195
- task: state.task || delta.task || "",
6196
- user_constraints: mergeArr(state.user_constraints, delta.user_constraints),
6197
- repo_facts: mergeArr(state.repo_facts, delta.repo_facts),
6198
- files_touched: mergeArr(state.files_touched, delta.files_touched),
6199
- files_modified: mergeArr(state.files_modified, delta.files_modified),
6200
- confirmed_findings: mergeArr(state.confirmed_findings, delta.confirmed_findings),
6201
- open_questions: mergeArr(state.open_questions, delta.open_questions),
6202
- recent_failures: mergeArr(state.recent_failures, delta.recent_failures),
6203
- decisions: mergeArr(state.decisions, delta.decisions),
6204
- next_actions: mergeArr(state.next_actions, delta.next_actions),
6205
- artifact_index: { ...state.artifact_index, ...delta.artifact_index }
6206
- };
6207
- }
6208
- function shouldCompact(opts2) {
6209
- const tokenThreshold = opts2.tokenThreshold ?? 8e4;
6210
- const turnThreshold = opts2.turnThreshold ?? 12;
6211
- const tokens = estimatePromptTokens(opts2.messages);
6212
- const { turns } = groupIntoTurns(opts2.messages);
6213
- return tokens > tokenThreshold || turns.length > turnThreshold;
6297
+ return -1;
6214
6298
  }
6215
- function compactMessages2(opts2) {
6216
- const keepLastTurns = opts2.keepLastTurns ?? 4;
6217
- const { prefix, turns } = groupIntoTurns(opts2.messages);
6218
- const tokensBefore = estimatePromptTokens(opts2.messages);
6219
- if (turns.length <= keepLastTurns) {
6220
- return {
6221
- newMessages: opts2.messages,
6222
- newState: opts2.state,
6223
- metrics: {
6224
- estimatedTokensBefore: tokensBefore,
6225
- estimatedTokensAfter: tokensBefore,
6226
- archivedArtifacts: 0,
6227
- recalledArtifacts: 0,
6228
- rawTurnsRemoved: 0,
6229
- rawTurnsKept: turns.length
6230
- }
6231
- };
6299
+ async function compactMessages2(opts2) {
6300
+ const keep = opts2.keepLastTurns ?? 4;
6301
+ const messages = opts2.messages;
6302
+ let prefixEnd = 0;
6303
+ while (prefixEnd < messages.length && messages[prefixEnd].role === "system") {
6304
+ prefixEnd++;
6232
6305
  }
6233
- const toCompact = turns.slice(0, turns.length - keepLastTurns);
6234
- const toKeep = turns.slice(turns.length - keepLastTurns);
6235
- let newState = { ...opts2.state };
6236
- let archivedCount = 0;
6237
- for (let i = 0; i < toCompact.length; i++) {
6238
- const turn = toCompact[i];
6239
- const { artifacts, stateDelta } = extractArtifactsFromTurn(turn, i, opts2.store);
6240
- for (const artifact of artifacts) {
6241
- opts2.store.add(artifact);
6242
- archivedCount++;
6243
- newState.artifact_index[artifact.id] = {
6244
- type: artifact.type,
6245
- summary: artifact.summary,
6246
- source: artifact.source,
6247
- path: artifact.path
6248
- };
6249
- }
6250
- newState = mergeState(newState, stateDelta);
6251
- if (!newState.task && typeof turn.user.content === "string") {
6252
- newState.task = turn.user.content.slice(0, 200);
6253
- }
6306
+ const prefix = messages.slice(0, prefixEnd);
6307
+ if (prefix.length === 0) {
6308
+ return { summary: "", newMessages: messages, replacedCount: 0 };
6254
6309
  }
6255
- const workingMemory = [];
6256
- for (const turn of toKeep) {
6257
- workingMemory.push(turn.user);
6258
- workingMemory.push(turn.assistant);
6259
- for (const tm of turn.tools) {
6260
- workingMemory.push(tm);
6261
- }
6310
+ const cutoffUserIdx = indexOfNthUserFromEnd(messages, keep);
6311
+ const firstKeepIdx = cutoffUserIdx >= 0 ? cutoffUserIdx : messages.length;
6312
+ const toSummarize = messages.slice(prefixEnd, firstKeepIdx);
6313
+ const toKeep = messages.slice(firstKeepIdx);
6314
+ if (toSummarize.length === 0) {
6315
+ return { summary: "", newMessages: messages, replacedCount: 0 };
6262
6316
  }
6263
- const stateMsg = buildSessionStateMessage(newState);
6264
- const newMessages = [...prefix, stateMsg, ...workingMemory];
6265
- const tokensAfter = estimatePromptTokens(newMessages);
6266
- const metrics = {
6267
- estimatedTokensBefore: tokensBefore,
6268
- estimatedTokensAfter: tokensAfter,
6269
- archivedArtifacts: archivedCount,
6270
- recalledArtifacts: 0,
6271
- rawTurnsRemoved: toCompact.length,
6272
- rawTurnsKept: toKeep.length
6273
- };
6274
- return { newMessages, newState, metrics };
6275
- }
6276
- function recallArtifacts(messages, store, state) {
6277
- const text = messages.map((m) => typeof m.content === "string" ? m.content : "").join(" ");
6278
- const ids = [];
6279
- for (const [id, meta] of Object.entries(state.artifact_index)) {
6280
- if (meta.path && text.includes(meta.path)) {
6281
- ids.push(id);
6317
+ const transcript = toSummarize.map((m) => {
6318
+ const contentStr = typeof m.content === "string" ? m.content : m.content?.map((p) => p.type === "text" ? p.text : "[image]").join(" ") ?? "";
6319
+ if (m.role === "tool") {
6320
+ const snippet = contentStr.slice(0, 500);
6321
+ return `[tool ${m.name ?? ""}] ${snippet}`;
6282
6322
  }
6283
- }
6284
- for (const failure of state.recent_failures) {
6285
- const keyword = failure.split(":")[0];
6286
- if (!keyword) continue;
6287
- const lowerKeyword = keyword.toLowerCase();
6288
- if (!text.toLowerCase().includes(lowerKeyword)) continue;
6289
- for (const [id, meta] of Object.entries(state.artifact_index)) {
6290
- if (meta.source === "bash" && !ids.includes(id) && meta.summary.toLowerCase().includes(lowerKeyword)) {
6291
- ids.push(id);
6292
- }
6323
+ if (m.role === "assistant") {
6324
+ const calls = m.tool_calls ? ` (tool_calls: ${m.tool_calls.map((c) => c.function.name).join(", ")})` : "";
6325
+ return `[assistant]${calls} ${contentStr}`;
6293
6326
  }
6327
+ return `[${m.role}] ${contentStr}`;
6328
+ }).join("\n");
6329
+ let summary = "";
6330
+ const events = runKimi({
6331
+ accountId: opts2.accountId,
6332
+ apiToken: opts2.apiToken,
6333
+ model: opts2.model,
6334
+ messages: [
6335
+ { role: "system", content: SUMMARY_SYSTEM },
6336
+ { role: "user", content: `Summarize this session so it can be replaced by your summary:
6337
+
6338
+ ${transcript}` }
6339
+ ],
6340
+ signal: opts2.signal,
6341
+ temperature: 0.1,
6342
+ reasoningEffort: "low",
6343
+ gateway: opts2.gateway,
6344
+ idleTimeoutMs: 6e4
6345
+ });
6346
+ for await (const ev of events) {
6347
+ if (ev.type === "text") summary += ev.delta;
6294
6348
  }
6295
- const uniqueIds = [...new Set(ids)].slice(0, 5);
6296
- const recalled = store.recall(uniqueIds);
6297
- return { ids: uniqueIds, recalled };
6349
+ const summaryMsg = {
6350
+ role: "user",
6351
+ content: `[compacted summary of earlier turns]
6352
+ ${summary.trim()}`
6353
+ };
6354
+ return {
6355
+ summary: summary.trim(),
6356
+ newMessages: [...prefix, summaryMsg, ...toKeep],
6357
+ replacedCount: toSummarize.length
6358
+ };
6298
6359
  }
6299
- var init_compaction = __esm({
6300
- "src/agent/compaction.ts"() {
6360
+ var SUMMARY_SYSTEM;
6361
+ var init_compact = __esm({
6362
+ "src/agent/compact.ts"() {
6301
6363
  "use strict";
6302
- init_session_state();
6364
+ init_client();
6365
+ SUMMARY_SYSTEM = `You are summarizing a terminal coding session so it can fit back into a short context window. Produce a dense summary that captures:
6366
+ - The user's goal(s) and what they've asked for.
6367
+ - Files read or modified, with paths.
6368
+ - Tools run (bash commands, edits) and the outcome of each.
6369
+ - Decisions made and open questions.
6370
+ - Any constraints or preferences the user has stated.
6371
+
6372
+ Do not include speculation. Do not include chat-style pleasantries. Use short bullet form. Aim for ~400-800 tokens.`;
6303
6373
  }
6304
6374
  });
6305
6375
 
@@ -7936,6 +8006,9 @@ function parseBlocks(src) {
7936
8006
  }
7937
8007
  out.push({ kind: "paragraph", text: paraLines.join("\n") });
7938
8008
  }
8009
+ while (out.length > 0 && out[out.length - 1].kind === "blank") {
8010
+ out.pop();
8011
+ }
7939
8012
  return out;
7940
8013
  }
7941
8014
  function renderInline(src, theme) {
@@ -8382,7 +8455,7 @@ function StatusBar({ model, usage, sessionUsage, thinking, turnStartedAt, mode,
8382
8455
  ] }),
8383
8456
  /* @__PURE__ */ jsx7(Text6, { children: " " }),
8384
8457
  thinking ? /* @__PURE__ */ jsxs6(Text6, { color: theme.spinner, children: [
8385
- /* @__PURE__ */ jsx7(Spinner3, { type: "dots" }),
8458
+ /* @__PURE__ */ jsx7(Spinner3, { type: "dots2" }),
8386
8459
  " ",
8387
8460
  phaseLabel,
8388
8461
  elapsed ? ` \xB7 ${elapsed}` : "",
@@ -8700,7 +8773,7 @@ function TaskRow({ task }) {
8700
8773
  if (task.status === "in_progress") {
8701
8774
  return /* @__PURE__ */ jsxs10(Text10, { color: theme.accent, bold: true, children: [
8702
8775
  " ",
8703
- /* @__PURE__ */ jsx11(Spinner4, { type: "dots" }),
8776
+ /* @__PURE__ */ jsx11(Spinner4, { type: "line" }),
8704
8777
  " ",
8705
8778
  task.title
8706
8779
  ] });
@@ -16373,7 +16446,7 @@ ${wcagWarnings.join("\n")}` }
16373
16446
  if (!shouldCompact({ messages })) return messages;
16374
16447
  if (compiledContextRef.current) {
16375
16448
  const store = artifactStoreRef.current;
16376
- const result = compactMessages2({
16449
+ const result = compactMessages({
16377
16450
  messages,
16378
16451
  state: sessionStateRef.current,
16379
16452
  store
@@ -16418,7 +16491,7 @@ ${wcagWarnings.join("\n")}` }
16418
16491
  }
16419
16492
  if (cfg && !signal.aborted) {
16420
16493
  try {
16421
- const result = await compactMessages({
16494
+ const result = await compactMessages2({
16422
16495
  accountId: cfg.accountId,
16423
16496
  apiToken: cfg.apiToken,
16424
16497
  model: cfg.model,
@@ -16589,7 +16662,7 @@ ${wcagWarnings.join("\n")}` }
16589
16662
  try {
16590
16663
  if (compiledContextRef.current) {
16591
16664
  const store = artifactStoreRef.current;
16592
- const result = compactMessages2({
16665
+ const result = compactMessages({
16593
16666
  messages: messagesRef.current,
16594
16667
  state: sessionStateRef.current,
16595
16668
  store
@@ -16618,7 +16691,7 @@ ${wcagWarnings.join("\n")}` }
16618
16691
  await saveSessionSafe();
16619
16692
  }
16620
16693
  } else {
16621
- const result = await compactMessages({
16694
+ const result = await compactMessages2({
16622
16695
  accountId: cfg.accountId,
16623
16696
  apiToken: cfg.apiToken,
16624
16697
  model: cfg.model,
@@ -17021,6 +17094,10 @@ ${wcagWarnings.join("\n")}` }
17021
17094
  return true;
17022
17095
  }
17023
17096
  if (c === "/clear") {
17097
+ if (busy) {
17098
+ setEvents((e) => [...e, { kind: "info", key: mkKey(), text: "can't /clear while model is running \u2014 press Esc to interrupt first" }]);
17099
+ return true;
17100
+ }
17024
17101
  if (cacheStableRef.current && messagesRef.current.length >= 2) {
17025
17102
  messagesRef.current = [messagesRef.current[0], messagesRef.current[1]];
17026
17103
  } else {
@@ -17030,6 +17107,15 @@ ${wcagWarnings.join("\n")}` }
17030
17107
  sessionStateRef.current = emptySessionState();
17031
17108
  artifactStoreRef.current = new ArtifactStore();
17032
17109
  executorRef.current.clearArtifacts();
17110
+ if (flushTimeoutRef.current) {
17111
+ clearTimeout(flushTimeoutRef.current);
17112
+ flushTimeoutRef.current = null;
17113
+ }
17114
+ pendingTextRef.current.clear();
17115
+ activeAsstIdRef.current = null;
17116
+ pendingToolCallsRef.current.clear();
17117
+ usageRef.current = null;
17118
+ turnCounterRef.current = 0;
17033
17119
  setEvents([]);
17034
17120
  setUsage(null);
17035
17121
  setSessionUsage(null);
@@ -18254,7 +18340,7 @@ ${lines.join("\n")}` }]);
18254
18340
  if (shouldCompact({ messages: messagesRef.current })) {
18255
18341
  if (compiledContextRef.current) {
18256
18342
  const store = artifactStoreRef.current;
18257
- const result = compactMessages2({
18343
+ const result = compactMessages({
18258
18344
  messages: messagesRef.current,
18259
18345
  state: sessionStateRef.current,
18260
18346
  store
@@ -18274,7 +18360,7 @@ ${lines.join("\n")}` }]);
18274
18360
  }
18275
18361
  } else {
18276
18362
  try {
18277
- const result = await compactMessages({
18363
+ const result = await compactMessages2({
18278
18364
  accountId: cfg.accountId,
18279
18365
  apiToken: cfg.apiToken,
18280
18366
  model: cfg.model,