@harperfast/agent 0.16.6 → 0.16.7

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.
@@ -1,14 +1,15 @@
1
- #!/usr/bin/env node
2
-
3
1
  // ink/emitters/listener.ts
4
2
  import React, { useCallback, useEffect } from "react";
5
3
  var listenersMap = {};
4
+ var emittedMap = {};
6
5
  function useListener(name, listener, deps) {
7
6
  const listenerRef = React.useRef(listener);
8
7
  React.useEffect(() => {
9
8
  listenerRef.current = listener;
10
9
  }, [listener]);
11
- const callback = useCallback((newValue, trigger) => listenerRef.current(newValue, trigger), [deps]);
10
+ const callback = useCallback((newValue, trigger) => {
11
+ return listenerRef.current(newValue, trigger);
12
+ }, [deps]);
12
13
  useEffect(() => {
13
14
  if (!listenersMap[name]) {
14
15
  listenersMap[name] = [];
@@ -38,6 +39,7 @@ async function onceListener(name) {
38
39
  });
39
40
  }
40
41
  function emitToListeners(name, value, trigger) {
42
+ emittedMap[name] = value;
41
43
  const listeners = listenersMap[name];
42
44
  if (listeners) {
43
45
  const stableCopyOfListeners = listeners.slice();
@@ -46,6 +48,9 @@ function emitToListeners(name, value, trigger) {
46
48
  }
47
49
  }
48
50
  }
51
+ function lastEmittedValue(name) {
52
+ return emittedMap[name];
53
+ }
49
54
  function addListener(name, callback) {
50
55
  if (!listenersMap[name]) {
51
56
  listenersMap[name] = [];
@@ -62,151 +67,6 @@ function curryEmitToListeners(name, value, trigger) {
62
67
  return (e) => emitToListeners(name, value, trigger ?? e);
63
68
  }
64
69
 
65
- // utils/sessions/rateLimits.ts
66
- var RateLimitTracker = class {
67
- status = {
68
- limitRequests: null,
69
- limitTokens: null,
70
- remainingRequests: null,
71
- remainingTokens: null,
72
- resetRequests: null,
73
- resetTokens: null,
74
- retryAfter: null
75
- };
76
- updateFromHeaders(headers) {
77
- const normalizedHeaders = {};
78
- for (const key of Object.keys(headers)) {
79
- normalizedHeaders[key.toLowerCase()] = headers[key];
80
- }
81
- const getHeader = (name) => {
82
- const value = normalizedHeaders[name.toLowerCase()];
83
- return Array.isArray(value) ? value[0] : value;
84
- };
85
- const limitRequests = getHeader("x-ratelimit-limit-requests") || getHeader("anthropic-ratelimit-requests-limit") || getHeader("x-ratelimit-limit") || getHeader("ratelimit-limit") || getHeader("x-request-limit");
86
- const limitTokens = getHeader("x-ratelimit-limit-tokens") || getHeader("anthropic-ratelimit-tokens-limit") || getHeader("x-token-limit");
87
- const remainingRequests = getHeader("x-ratelimit-remaining-requests") || getHeader("anthropic-ratelimit-requests-remaining") || getHeader("x-ratelimit-remaining") || getHeader("ratelimit-remaining") || getHeader("x-request-remaining");
88
- const remainingTokens = getHeader("x-ratelimit-remaining-tokens") || getHeader("anthropic-ratelimit-tokens-remaining") || getHeader("x-token-remaining");
89
- const resetRequests = getHeader("x-ratelimit-reset-requests") || getHeader("anthropic-ratelimit-requests-reset") || getHeader("x-ratelimit-reset") || getHeader("ratelimit-reset") || getHeader("x-request-reset");
90
- const resetTokens = getHeader("x-ratelimit-reset-tokens") || getHeader("anthropic-ratelimit-tokens-reset") || getHeader("x-token-reset");
91
- const retryAfter = getHeader("retry-after");
92
- if (limitRequests) {
93
- this.status.limitRequests = parseInt(limitRequests, 10);
94
- }
95
- if (limitTokens) {
96
- this.status.limitTokens = parseInt(limitTokens, 10);
97
- }
98
- if (remainingRequests) {
99
- this.status.remainingRequests = parseInt(remainingRequests, 10);
100
- }
101
- if (remainingTokens) {
102
- this.status.remainingTokens = parseInt(remainingTokens, 10);
103
- }
104
- if (resetRequests) {
105
- this.status.resetRequests = resetRequests;
106
- }
107
- if (resetTokens) {
108
- this.status.resetTokens = resetTokens;
109
- }
110
- if (retryAfter) {
111
- this.status.retryAfter = parseInt(retryAfter, 10);
112
- }
113
- }
114
- isApproachingLimit(threshold) {
115
- const usage = this.getUsagePercentage();
116
- return {
117
- requests: usage.requests >= threshold,
118
- tokens: usage.tokens >= threshold
119
- };
120
- }
121
- getStatus() {
122
- return { ...this.status };
123
- }
124
- getUsagePercentage() {
125
- const requests = this.status.limitRequests && this.status.remainingRequests !== null ? 100 * (1 - this.status.remainingRequests / this.status.limitRequests) : 0;
126
- const tokens = this.status.limitTokens && this.status.remainingTokens !== null ? 100 * (1 - this.status.remainingTokens / this.status.limitTokens) : 0;
127
- return { requests, tokens };
128
- }
129
- reset() {
130
- this.status = {
131
- limitRequests: null,
132
- limitTokens: null,
133
- remainingRequests: null,
134
- remainingTokens: null,
135
- resetRequests: null,
136
- resetTokens: null,
137
- retryAfter: null
138
- };
139
- }
140
- };
141
- var rateLimitTracker = new RateLimitTracker();
142
-
143
- // lifecycle/patchFetch.ts
144
- var originalFetch = globalThis.fetch;
145
- if (originalFetch) {
146
- globalThis.fetch = async (...args) => {
147
- const response = await originalFetch(...args);
148
- const headers = {};
149
- response.headers.forEach((value, key) => {
150
- headers[key] = value;
151
- });
152
- const commonHeaders = [
153
- "x-ratelimit-limit-requests",
154
- "x-ratelimit-limit-tokens",
155
- "x-ratelimit-remaining-requests",
156
- "x-ratelimit-remaining-tokens",
157
- "x-ratelimit-reset-requests",
158
- "x-ratelimit-reset-tokens",
159
- "anthropic-ratelimit-requests-limit",
160
- "anthropic-ratelimit-requests-remaining",
161
- "anthropic-ratelimit-requests-reset",
162
- "anthropic-ratelimit-tokens-limit",
163
- "anthropic-ratelimit-tokens-remaining",
164
- "anthropic-ratelimit-tokens-reset",
165
- "retry-after"
166
- ];
167
- for (const key of commonHeaders) {
168
- if (!headers[key]) {
169
- const val = response.headers.get(key);
170
- if (val) {
171
- headers[key] = val;
172
- }
173
- }
174
- }
175
- rateLimitTracker.updateFromHeaders(headers);
176
- emitToListeners("SettingsUpdated", void 0);
177
- const retryAfter = response.headers.get("retry-after");
178
- if (retryAfter) {
179
- const seconds = parseInt(retryAfter, 10);
180
- if (!isNaN(seconds) && seconds > 0) {
181
- emitToListeners("PushNewMessages", [{
182
- type: "interrupted",
183
- text: `Rate limit reached. Sleeping for ${seconds} seconds (Retry-After)...`,
184
- version: 1
185
- }]);
186
- await new Promise((resolve2) => setTimeout(resolve2, seconds * 1e3));
187
- }
188
- }
189
- return response;
190
- };
191
- }
192
-
193
- // agent.ts
194
- import chalk7 from "chalk";
195
-
196
- // agent/AgentManager.ts
197
- import { Agent as Agent3 } from "@openai/agents";
198
-
199
- // ink/contexts/globalPlanContext.ts
200
- var globalPlanContext = {
201
- planDescription: "",
202
- planItems: [],
203
- progress: 0
204
- };
205
-
206
- // lifecycle/defaultInstructions.ts
207
- import { existsSync as existsSync2 } from "fs";
208
- import { join as join2 } from "path";
209
-
210
70
  // utils/files/harperApp.ts
211
71
  import { existsSync } from "fs";
212
72
  import path, { dirname, join } from "path";
@@ -274,110 +134,6 @@ function bootstrapTrackedState() {
274
134
  };
275
135
  }
276
136
 
277
- // lifecycle/defaultInstructions.ts
278
- function defaultInstructions() {
279
- const harperAppExists = existsSync2(join2(trackedState.cwd, "config.yaml"));
280
- const vibing = harperAppExists ? "updating" : "creating";
281
- return `You are working on ${vibing} a harper app with the user.`;
282
- }
283
-
284
- // lifecycle/getModel.ts
285
- import { anthropic } from "@ai-sdk/anthropic";
286
- import { google } from "@ai-sdk/google";
287
- import { openai } from "@ai-sdk/openai";
288
- import { aisdk } from "@openai/agents-extensions/ai-sdk";
289
- import { createOllama, ollama } from "ollama-ai-provider-v2";
290
-
291
- // agent/defaults.ts
292
- var defaultModelToken = "default";
293
- var defaultOpenAIModel = "gpt-5.2";
294
- var defaultOpenAICompactionModel = "gpt-5-nano";
295
- var defaultAnthropicModel = "claude-4-6-opus-latest";
296
- var defaultAnthropicCompactionModel = "claude-4-5-haiku-latest";
297
- var defaultGoogleModel = "gemini-3-pro";
298
- var defaultGoogleCompactionModel = "gemini-2.5-flash-lite";
299
- var defaultOllamaModel = "ollama-qwen3.5";
300
- var defaultOllamaCompactionModel = "ollama-qwen3.5:2b";
301
- var defaultModels = [
302
- defaultOpenAIModel,
303
- defaultAnthropicModel,
304
- defaultGoogleModel,
305
- defaultOllamaModel
306
- ];
307
- var defaultCompactionModels = [
308
- defaultOpenAICompactionModel,
309
- defaultAnthropicCompactionModel,
310
- defaultGoogleCompactionModel,
311
- defaultOllamaCompactionModel
312
- ];
313
-
314
- // utils/ollama/normalizeOllamaBaseUrl.ts
315
- function normalizeOllamaBaseUrl(baseUrl) {
316
- let url = baseUrl.trim();
317
- if (!url.startsWith("http://") && !url.startsWith("https://")) {
318
- url = `http://${url}`;
319
- }
320
- const urlObj = new URL(url);
321
- if (!urlObj.port) {
322
- urlObj.port = "11434";
323
- }
324
- let pathname = urlObj.pathname;
325
- if (pathname.endsWith("/")) {
326
- pathname = pathname.slice(0, -1);
327
- }
328
- if (!pathname.endsWith("/api")) {
329
- pathname += "/api";
330
- }
331
- urlObj.pathname = pathname;
332
- return urlObj.toString().replace(/\/$/, "");
333
- }
334
-
335
- // lifecycle/getModel.ts
336
- function isOpenAIModel(modelName) {
337
- if (!modelName || modelName === defaultOpenAIModel) {
338
- return true;
339
- }
340
- return !modelName.startsWith("claude-") && !modelName.startsWith("gemini-") && !modelName.startsWith("ollama-") && !modelName.includes(":");
341
- }
342
- function getProvider(modelName) {
343
- if (modelName.startsWith("claude-")) {
344
- return "Anthropic";
345
- }
346
- if (modelName.startsWith("gemini-")) {
347
- return "Google";
348
- }
349
- if (modelName.startsWith("ollama-") || modelName.includes(":")) {
350
- return "Ollama";
351
- }
352
- return "OpenAI";
353
- }
354
- function getModel(modelName) {
355
- if (modelName.startsWith("claude-")) {
356
- return aisdk(anthropic(modelName));
357
- }
358
- if (modelName.startsWith("gemini-")) {
359
- return aisdk(google(modelName));
360
- }
361
- if (modelName.startsWith("ollama-") || modelName.includes(":")) {
362
- const ollamaBaseUrl = process.env.OLLAMA_BASE_URL ? normalizeOllamaBaseUrl(process.env.OLLAMA_BASE_URL) : void 0;
363
- const ollamaProvider = ollamaBaseUrl ? createOllama({ baseURL: ollamaBaseUrl, compatibility: "strict" }) : ollama;
364
- return aisdk(ollamaProvider(getModelName(modelName)));
365
- }
366
- return aisdk(openai(modelName));
367
- }
368
- function getModelName(modelName) {
369
- if (modelName.startsWith("claude-")) {
370
- return modelName;
371
- }
372
- if (modelName.startsWith("gemini-")) {
373
- return modelName;
374
- }
375
- if (modelName.startsWith("ollama-")) {
376
- return modelName.slice(7);
377
- }
378
- return modelName;
379
- }
380
-
381
137
  // lifecycle/handleExit.ts
382
138
  import { getGlobalTraceProvider } from "@openai/agents";
383
139
 
@@ -385,7 +141,7 @@ import { getGlobalTraceProvider } from "@openai/agents";
385
141
  import spawn from "cross-spawn";
386
142
  import { execSync } from "child_process";
387
143
  import { homedir } from "os";
388
- import { join as join3 } from "path";
144
+ import { join as join2 } from "path";
389
145
  var HarperProcess = class {
390
146
  childProcess = null;
391
147
  externalPid = null;
@@ -421,7 +177,7 @@ var HarperProcess = class {
421
177
  this.stopTailingLogs();
422
178
  }
423
179
  startTailingLogs() {
424
- const logPath = join3(homedir(), "hdb", "log", "hdb.log");
180
+ const logPath = join2(homedir(), "hdb", "log", "hdb.log");
425
181
  this.logTailProcess = spawn("tail", ["-f", logPath], {
426
182
  stdio: ["ignore", "pipe", "pipe"]
427
183
  });
@@ -501,38 +257,181 @@ async function handleExit() {
501
257
  process.exit(0);
502
258
  }
503
259
 
504
- // lifecycle/readAgentSkillsRoot.ts
505
- import { existsSync as existsSync3, readFileSync } from "fs";
506
- import { join as join4 } from "path";
507
-
508
- // lifecycle/agentsSkillReference.ts
509
- var agentsSkillDir = ".agents/skills/harper-best-practices";
510
- var agentsSkillReference = `${agentsSkillDir}/SKILL.md`;
260
+ // utils/logger.ts
261
+ import { appendFileSync, existsSync as existsSync2, mkdirSync } from "fs";
262
+ import { homedir as homedir2 } from "os";
263
+ import { dirname as dirname2, join as join3 } from "path";
264
+ var ERROR_LOG_PATH = join3(homedir2(), ".harper", "harper-agent-errors");
265
+ function logError(error) {
266
+ const message = error instanceof Error ? error.stack || error.message : String(error);
267
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString();
268
+ const logEntry = `[${timestamp}] ${message}
511
269
 
512
- // lifecycle/readAgentSkillsRoot.ts
513
- function readAgentSkillsRoot() {
514
- const agentsFile = join4(trackedState.cwd, agentsSkillReference);
515
- const agentsMdExists = existsSync3(agentsFile);
516
- if (agentsMdExists) {
517
- return readFileSync(agentsFile, "utf8");
270
+ `;
271
+ try {
272
+ const dir = dirname2(ERROR_LOG_PATH);
273
+ if (!existsSync2(dir)) {
274
+ mkdirSync(dir, { recursive: true });
275
+ }
276
+ appendFileSync(ERROR_LOG_PATH, logEntry, "utf8");
277
+ } catch (err) {
278
+ console.error("Failed to write to error log:", err);
279
+ console.error("Original error:", error);
518
280
  }
519
281
  }
282
+ function setupGlobalErrorHandlers() {
283
+ process.on("uncaughtException", (error) => {
284
+ logError(error);
285
+ console.error("Uncaught Exception:", error);
286
+ process.exit(1);
287
+ });
288
+ process.on("unhandledRejection", (reason) => {
289
+ logError(reason);
290
+ console.error("Unhandled Rejection:", reason);
291
+ });
292
+ }
520
293
 
521
- // tools/browser/browserClickTool.ts
522
- import { tool } from "@openai/agents";
523
- import { z } from "zod";
294
+ // agent/AgentManager.ts
295
+ import { Agent as Agent3 } from "@openai/agents";
524
296
 
525
- // tools/browser/browserManager.ts
526
- var browser = null;
527
- var page = null;
528
- var logs = [];
529
- async function getBrowser() {
530
- if (!browser) {
531
- let puppeteer;
532
- try {
533
- puppeteer = await import("puppeteer");
534
- } catch {
535
- throw new Error(
297
+ // lifecycle/defaultInstructions.ts
298
+ import { existsSync as existsSync3 } from "fs";
299
+ import { join as join4 } from "path";
300
+ function defaultInstructions() {
301
+ const harperAppExists = existsSync3(join4(trackedState.cwd, "config.yaml"));
302
+ const vibing = harperAppExists ? "updating" : "creating";
303
+ return `You are working on ${vibing} a harper app with the user.`;
304
+ }
305
+
306
+ // lifecycle/getModel.ts
307
+ import { anthropic } from "@ai-sdk/anthropic";
308
+ import { google } from "@ai-sdk/google";
309
+ import { openai } from "@ai-sdk/openai";
310
+ import { aisdk } from "@openai/agents-extensions/ai-sdk";
311
+ import { createOllama, ollama } from "ollama-ai-provider-v2";
312
+
313
+ // agent/defaults.ts
314
+ var defaultModelToken = "default";
315
+ var defaultOpenAIModel = "gpt-5.2";
316
+ var defaultOpenAICompactionModel = "gpt-5-nano";
317
+ var defaultAnthropicModel = "claude-4-6-opus-latest";
318
+ var defaultAnthropicCompactionModel = "claude-4-5-haiku-latest";
319
+ var defaultGoogleModel = "gemini-3-pro";
320
+ var defaultGoogleCompactionModel = "gemini-2.5-flash-lite";
321
+ var defaultOllamaModel = "ollama-qwen3.5";
322
+ var defaultOllamaCompactionModel = "ollama-qwen3.5:2b";
323
+ var defaultModels = [
324
+ defaultOpenAIModel,
325
+ defaultAnthropicModel,
326
+ defaultGoogleModel,
327
+ defaultOllamaModel
328
+ ];
329
+ var defaultCompactionModels = [
330
+ defaultOpenAICompactionModel,
331
+ defaultAnthropicCompactionModel,
332
+ defaultGoogleCompactionModel,
333
+ defaultOllamaCompactionModel
334
+ ];
335
+
336
+ // utils/ollama/normalizeOllamaBaseUrl.ts
337
+ function normalizeOllamaBaseUrl(baseUrl) {
338
+ let url = baseUrl.trim();
339
+ if (!url.startsWith("http://") && !url.startsWith("https://")) {
340
+ url = `http://${url}`;
341
+ }
342
+ const urlObj = new URL(url);
343
+ if (!urlObj.port) {
344
+ urlObj.port = "11434";
345
+ }
346
+ let pathname = urlObj.pathname;
347
+ if (pathname.endsWith("/")) {
348
+ pathname = pathname.slice(0, -1);
349
+ }
350
+ if (!pathname.endsWith("/api")) {
351
+ pathname += "/api";
352
+ }
353
+ urlObj.pathname = pathname;
354
+ return urlObj.toString().replace(/\/$/, "");
355
+ }
356
+
357
+ // lifecycle/getModel.ts
358
+ function isOpenAIModel(modelName) {
359
+ if (!modelName || modelName === defaultOpenAIModel) {
360
+ return true;
361
+ }
362
+ return !modelName.startsWith("claude-") && !modelName.startsWith("gemini-") && !modelName.startsWith("ollama-") && !modelName.includes(":");
363
+ }
364
+ function getProvider(modelName) {
365
+ if (modelName.startsWith("claude-")) {
366
+ return "Anthropic";
367
+ }
368
+ if (modelName.startsWith("gemini-")) {
369
+ return "Google";
370
+ }
371
+ if (modelName.startsWith("ollama-") || modelName.includes(":")) {
372
+ return "Ollama";
373
+ }
374
+ return "OpenAI";
375
+ }
376
+ function getModel(modelName) {
377
+ if (modelName.startsWith("claude-")) {
378
+ return aisdk(anthropic(modelName));
379
+ }
380
+ if (modelName.startsWith("gemini-")) {
381
+ return aisdk(google(modelName));
382
+ }
383
+ if (modelName.startsWith("ollama-") || modelName.includes(":")) {
384
+ const ollamaBaseUrl = process.env.OLLAMA_BASE_URL ? normalizeOllamaBaseUrl(process.env.OLLAMA_BASE_URL) : void 0;
385
+ const ollamaProvider = ollamaBaseUrl ? createOllama({ baseURL: ollamaBaseUrl, compatibility: "strict" }) : ollama;
386
+ return aisdk(ollamaProvider(getModelName(modelName)));
387
+ }
388
+ return aisdk(openai(modelName));
389
+ }
390
+ function getModelName(modelName) {
391
+ if (modelName.startsWith("claude-")) {
392
+ return modelName;
393
+ }
394
+ if (modelName.startsWith("gemini-")) {
395
+ return modelName;
396
+ }
397
+ if (modelName.startsWith("ollama-")) {
398
+ return modelName.slice(7);
399
+ }
400
+ return modelName;
401
+ }
402
+
403
+ // lifecycle/readAgentSkillsRoot.ts
404
+ import { existsSync as existsSync4, readFileSync } from "fs";
405
+ import { join as join5 } from "path";
406
+
407
+ // lifecycle/agentsSkillReference.ts
408
+ var agentsSkillDir = ".agents/skills/harper-best-practices";
409
+ var agentsSkillReference = `${agentsSkillDir}/SKILL.md`;
410
+
411
+ // lifecycle/readAgentSkillsRoot.ts
412
+ function readAgentSkillsRoot() {
413
+ const agentsFile = join5(trackedState.cwd, agentsSkillReference);
414
+ const agentsMdExists = existsSync4(agentsFile);
415
+ if (agentsMdExists) {
416
+ return readFileSync(agentsFile, "utf8");
417
+ }
418
+ }
419
+
420
+ // tools/browser/browserClickTool.ts
421
+ import { tool } from "@openai/agents";
422
+ import { z } from "zod";
423
+
424
+ // tools/browser/browserManager.ts
425
+ var browser = null;
426
+ var page = null;
427
+ var logs = [];
428
+ async function getBrowser() {
429
+ if (!browser) {
430
+ let puppeteer;
431
+ try {
432
+ puppeteer = await import("puppeteer");
433
+ } catch {
434
+ throw new Error(
536
435
  "Puppeteer is not installed. Browser tools require puppeteer. Please install it with `npm install puppeteer`."
537
436
  );
538
437
  }
@@ -814,21 +713,21 @@ function getEnv(newKey, oldKey) {
814
713
  import { tool as tool10 } from "@openai/agents";
815
714
  import { readdirSync, readFileSync as readFileSync2 } from "fs";
816
715
  import { createRequire } from "module";
817
- import { dirname as dirname2, join as join5 } from "path";
716
+ import { dirname as dirname3, join as join6 } from "path";
818
717
  import { z as z10 } from "zod";
819
718
  var require2 = createRequire(import.meta.url);
820
- var harperSkillsModuleDir = dirname2(
719
+ var harperSkillsModuleDir = dirname3(
821
720
  require2.resolve("@harperfast/skills/package.json")
822
721
  );
823
- var harperBestPracticesDir = join5(
722
+ var harperBestPracticesDir = join6(
824
723
  harperSkillsModuleDir,
825
724
  "harper-best-practices"
826
725
  );
827
- var skillRootFile = join5(
726
+ var skillRootFile = join6(
828
727
  harperBestPracticesDir,
829
728
  "SKILL.md"
830
729
  );
831
- var rulesDir = join5(
730
+ var rulesDir = join6(
832
731
  harperBestPracticesDir,
833
732
  "rules"
834
733
  );
@@ -864,7 +763,7 @@ async function execute10({ skill }) {
864
763
  return "No skill requested.";
865
764
  }
866
765
  try {
867
- const filePath = join5(rulesDir, `${skill}.md`);
766
+ const filePath = join6(rulesDir, `${skill}.md`);
868
767
  const content = readFileSync2(filePath, "utf8");
869
768
  agentManager.session?.addSkillRead?.(skill);
870
769
  return content;
@@ -899,42 +798,6 @@ import path3 from "path";
899
798
  // utils/files/aiignore.ts
900
799
  import { existsSync as existsSync5, readFileSync as readFileSync3 } from "fs";
901
800
  import path2 from "path";
902
-
903
- // utils/logger.ts
904
- import { appendFileSync, existsSync as existsSync4, mkdirSync } from "fs";
905
- import { homedir as homedir2 } from "os";
906
- import { dirname as dirname3, join as join6 } from "path";
907
- var ERROR_LOG_PATH = join6(homedir2(), ".harper", "harper-agent-errors");
908
- function logError(error) {
909
- const message = error instanceof Error ? error.stack || error.message : String(error);
910
- const timestamp = (/* @__PURE__ */ new Date()).toISOString();
911
- const logEntry = `[${timestamp}] ${message}
912
-
913
- `;
914
- try {
915
- const dir = dirname3(ERROR_LOG_PATH);
916
- if (!existsSync4(dir)) {
917
- mkdirSync(dir, { recursive: true });
918
- }
919
- appendFileSync(ERROR_LOG_PATH, logEntry, "utf8");
920
- } catch (err) {
921
- console.error("Failed to write to error log:", err);
922
- console.error("Original error:", error);
923
- }
924
- }
925
- function setupGlobalErrorHandlers() {
926
- process.on("uncaughtException", (error) => {
927
- logError(error);
928
- console.error("Uncaught Exception:", error);
929
- process.exit(1);
930
- });
931
- process.on("unhandledRejection", (reason) => {
932
- logError(reason);
933
- console.error("Unhandled Rejection:", reason);
934
- });
935
- }
936
-
937
- // utils/files/aiignore.ts
938
801
  var ignorePatterns = [];
939
802
  function loadAiIgnore() {
940
803
  const ignorePath = path2.join(trackedState.cwd, ".aiignore");
@@ -1165,11 +1028,11 @@ function pickExistingSkill(candidates) {
1165
1028
  }
1166
1029
  return null;
1167
1030
  }
1168
- async function requiredSkillForOperation(path10, type) {
1031
+ async function requiredSkillForOperation(path9, type) {
1169
1032
  if (type === "delete_file") {
1170
1033
  return null;
1171
1034
  }
1172
- const p = normalizedPath(path10);
1035
+ const p = normalizedPath(path9);
1173
1036
  const read = await getSkillsRead();
1174
1037
  if (p.includes("/resources/") || p.startsWith("resources/") || p.endsWith("resources.ts") || p.endsWith("resources.js")) {
1175
1038
  if (!read.includes("automatic-apis")) {
@@ -1282,9 +1145,9 @@ import { z as z12 } from "zod";
1282
1145
  var ToolParameters11 = z12.object({
1283
1146
  path: z12.string().describe("Directory to switch into. Can be absolute or relative to current workspace.")
1284
1147
  });
1285
- async function execute12({ path: path10 }) {
1148
+ async function execute12({ path: path9 }) {
1286
1149
  try {
1287
- const target = resolvePath(trackedState.cwd, path10);
1150
+ const target = resolvePath(trackedState.cwd, path9);
1288
1151
  const stat = statSync(target);
1289
1152
  if (!stat.isDirectory()) {
1290
1153
  return `Path is not a directory: ${target}`;
@@ -2281,14 +2144,14 @@ var hitHarperAPITool = tool35({
2281
2144
  }
2282
2145
  return false;
2283
2146
  },
2284
- async execute({ path: path10 = "/openapi", port, method = "GET", body }) {
2147
+ async execute({ path: path9 = "/openapi", port, method = "GET", body }) {
2285
2148
  try {
2286
2149
  const effectivePort = port ?? (harperProcess.running ? harperProcess.httpPort : void 0);
2287
2150
  if (!effectivePort) {
2288
2151
  return `Error: No Harper application is currently running and no port was specified.`;
2289
2152
  }
2290
2153
  const response = await fetch(
2291
- `http://localhost:${effectivePort}${path10.startsWith("/") ? "" : "/"}${path10}`,
2154
+ `http://localhost:${effectivePort}${path9.startsWith("/") ? "" : "/"}${path9}`,
2292
2155
  {
2293
2156
  method,
2294
2157
  headers: body ? { "Content-Type": "application/json" } : {},
@@ -2393,29 +2256,35 @@ var stopHarperTool = tool38({
2393
2256
  }
2394
2257
  });
2395
2258
 
2396
- // tools/plan/addPlanItemTool.ts
2259
+ // tools/plan/createAddPlanItemTool.ts
2397
2260
  import { tool as tool39 } from "@openai/agents";
2398
2261
  import { z as z39 } from "zod";
2399
2262
  var AddPlanItemParameters = z39.object({
2400
2263
  text: z39.string().describe("The description of the task or milestone to add to the plan.")
2401
2264
  });
2402
- var addPlanItemTool = tool39({
2403
- name: "add_plan_item",
2404
- description: "Add a new item to the plan.",
2405
- parameters: AddPlanItemParameters,
2406
- async execute({ text }) {
2407
- const newItems = [
2408
- ...globalPlanContext.planItems,
2409
- {
2410
- id: globalPlanContext.planItems.length + 1,
2411
- text,
2412
- status: "todo"
2413
- }
2414
- ];
2415
- emitToListeners("SetPlanItems", newItems);
2416
- return `Added plan item: ${text}`;
2417
- }
2418
- });
2265
+ function createAddPlanItemTool(session) {
2266
+ return tool39({
2267
+ name: "add_plan_item",
2268
+ description: "Add a new item to the plan.",
2269
+ parameters: AddPlanItemParameters,
2270
+ async execute({ text }) {
2271
+ const planContext = await session.getPlanState();
2272
+ const existingPlanItems = planContext.planItems;
2273
+ const newItems = [
2274
+ ...existingPlanItems,
2275
+ {
2276
+ id: existingPlanItems.length + 1,
2277
+ text,
2278
+ status: "todo"
2279
+ }
2280
+ ];
2281
+ planContext.planItems = newItems;
2282
+ session.setPlanState(planContext);
2283
+ emitToListeners("SetPlanItems", newItems);
2284
+ return `Added plan item: ${text}`;
2285
+ }
2286
+ });
2287
+ }
2419
2288
 
2420
2289
  // tools/plan/setPlanDescriptionTool.ts
2421
2290
  import { tool as tool40 } from "@openai/agents";
@@ -2423,15 +2292,20 @@ import { z as z40 } from "zod";
2423
2292
  var SetPlanDescriptionParameters = z40.object({
2424
2293
  description: z40.string().describe("A high-level description of the overall plan and goals.")
2425
2294
  });
2426
- var setPlanDescriptionTool = tool40({
2427
- name: "set_plan_description",
2428
- description: "Set the high-level description for the current plan.",
2429
- parameters: SetPlanDescriptionParameters,
2430
- async execute({ description }) {
2431
- emitToListeners("SetPlanDescription", description);
2432
- return `Plan description updated to: ${description}`;
2433
- }
2434
- });
2295
+ function createSetPlanDescriptionTool(session) {
2296
+ return tool40({
2297
+ name: "set_plan_description",
2298
+ description: "Set the high-level description for the current plan.",
2299
+ parameters: SetPlanDescriptionParameters,
2300
+ async execute({ description }) {
2301
+ const planContext = await session.getPlanState();
2302
+ planContext.planDescription = description;
2303
+ session.setPlanState(planContext);
2304
+ emitToListeners("SetPlanDescription", description);
2305
+ return `Plan description updated to: ${description}`;
2306
+ }
2307
+ });
2308
+ }
2435
2309
 
2436
2310
  // tools/plan/setPlanItemsTool.ts
2437
2311
  import { tool as tool41 } from "@openai/agents";
@@ -2439,20 +2313,25 @@ import { z as z41 } from "zod";
2439
2313
  var SetPlanItemsParameters = z41.object({
2440
2314
  items: z41.array(z41.string()).describe("An array of task descriptions to set as the plan items.")
2441
2315
  });
2442
- var setPlanItemsTool = tool41({
2443
- name: "set_plan_items",
2444
- description: "Set multiple plan items at once, replacing any existing items.",
2445
- parameters: SetPlanItemsParameters,
2446
- async execute({ items }) {
2447
- const newItems = items.map((text, index) => ({
2448
- id: index + 1,
2449
- text,
2450
- status: "todo"
2451
- }));
2452
- emitToListeners("SetPlanItems", newItems);
2453
- return `Set ${newItems.length} plan items.`;
2454
- }
2455
- });
2316
+ function createSetPlanItemsTool(session) {
2317
+ return tool41({
2318
+ name: "set_plan_items",
2319
+ description: "Set multiple plan items at once, replacing any existing items.",
2320
+ parameters: SetPlanItemsParameters,
2321
+ async execute({ items }) {
2322
+ const planContext = await session.getPlanState();
2323
+ const newItems = items.map((text, index) => ({
2324
+ id: index + 1,
2325
+ text,
2326
+ status: "todo"
2327
+ }));
2328
+ planContext.planItems = newItems;
2329
+ session.setPlanState(planContext);
2330
+ emitToListeners("SetPlanItems", newItems);
2331
+ return `Set ${newItems.length} plan items.`;
2332
+ }
2333
+ });
2334
+ }
2456
2335
 
2457
2336
  // tools/plan/updatePlanItemTool.ts
2458
2337
  import { tool as tool42 } from "@openai/agents";
@@ -2464,32 +2343,51 @@ var UpdatePlanItemParameters = z42.object({
2464
2343
  "The new status of the task."
2465
2344
  )
2466
2345
  });
2467
- var updatePlanItemTool = tool42({
2468
- name: "update_plan_item",
2469
- description: "Update an existing plan item.",
2470
- parameters: UpdatePlanItemParameters,
2471
- async execute({ id, text, status }) {
2472
- const newItems = globalPlanContext.planItems.map((item) => {
2473
- if (item.id === id) {
2474
- return {
2475
- ...item,
2476
- text: text || item.text,
2477
- status: status && status !== "unchanged" ? status : item.status
2478
- };
2479
- }
2480
- return item;
2481
- });
2482
- const itemExists = globalPlanContext.planItems.some((item) => item.id === id);
2483
- if (!itemExists) {
2484
- return `Error: Plan item with ID ${id} not found.`;
2346
+ function createUpdatePlanItemTool(session) {
2347
+ return tool42({
2348
+ name: "update_plan_item",
2349
+ description: "Update an existing plan item.",
2350
+ parameters: UpdatePlanItemParameters,
2351
+ async execute({ id, text, status }) {
2352
+ const planContext = await session.getPlanState();
2353
+ const planItems = planContext?.planItems ?? [];
2354
+ const itemExists = planItems.some((item) => item.id === id);
2355
+ if (!itemExists) {
2356
+ return `Error: Plan item with ID ${id} not found.`;
2357
+ }
2358
+ const newItems = planItems.map((item) => {
2359
+ if (item.id === id) {
2360
+ return {
2361
+ ...item,
2362
+ text: text || item.text,
2363
+ status: status && status !== "unchanged" ? status : item.status
2364
+ };
2365
+ }
2366
+ return item;
2367
+ });
2368
+ planContext.planItems = newItems;
2369
+ session.setPlanState(planContext);
2370
+ emitToListeners("SetPlanItems", newItems);
2371
+ return `Updated plan item ${id}`;
2485
2372
  }
2486
- emitToListeners("SetPlanItems", newItems);
2487
- return `Updated plan item ${id}`;
2488
- }
2489
- });
2373
+ });
2374
+ }
2490
2375
 
2491
2376
  // tools/factory.ts
2492
- function createTools() {
2377
+ function createSharedTools(session) {
2378
+ return [
2379
+ collectFeedbackTool,
2380
+ createAddPlanItemTool(session),
2381
+ createSetPlanDescriptionTool(session),
2382
+ createSetPlanItemsTool(session),
2383
+ createUpdatePlanItemTool(session),
2384
+ getHarperConfigSchemaTool,
2385
+ getHarperResourceInterfaceTool,
2386
+ getHarperSchemaGraphQLTool,
2387
+ getHarperSkillTool
2388
+ ];
2389
+ }
2390
+ function createCLITools(_session) {
2493
2391
  return [
2494
2392
  browserClickTool,
2495
2393
  browserCloseTool,
@@ -2500,19 +2398,13 @@ function createTools() {
2500
2398
  browserNavigateTool,
2501
2399
  browserScreenshotTool,
2502
2400
  browserTypeTool,
2503
- addPlanItemTool,
2504
2401
  changeCwdTool,
2505
2402
  checkHarperStatusTool,
2506
- collectFeedbackTool,
2507
2403
  codeInterpreterTool,
2508
2404
  createApplyPatchTool(),
2509
2405
  createNewHarperApplicationTool,
2510
2406
  egrepTool,
2511
2407
  findTool,
2512
- getHarperConfigSchemaTool,
2513
- getHarperResourceInterfaceTool,
2514
- getHarperSchemaGraphQLTool,
2515
- getHarperSkillTool,
2516
2408
  gitAddTool,
2517
2409
  gitBranchTool,
2518
2410
  gitCommitTool,
@@ -2526,13 +2418,10 @@ function createTools() {
2526
2418
  readHarperLogsTool,
2527
2419
  setInterpreterAutoApproveTool,
2528
2420
  setPatchAutoApproveTool,
2529
- setPlanDescriptionTool,
2530
- setPlanItemsTool,
2531
2421
  setShellAutoApproveTool,
2532
2422
  shellTool,
2533
2423
  startHarperTool,
2534
- stopHarperTool,
2535
- updatePlanItemTool
2424
+ stopHarperTool
2536
2425
  ];
2537
2426
  }
2538
2427
 
@@ -2829,7 +2718,11 @@ var DiskSession = class extends MemorySession {
2829
2718
  await this.ready;
2830
2719
  const sessionId = await this.getSessionId();
2831
2720
  const storage = await this.loadStorage();
2832
- return storage.plan?.[sessionId] ?? null;
2721
+ return storage.plan?.[sessionId] ?? {
2722
+ planDescription: "",
2723
+ planItems: [],
2724
+ progress: 0
2725
+ };
2833
2726
  }
2834
2727
  async setPlanState(state) {
2835
2728
  await this.ready;
@@ -3123,14 +3016,17 @@ var MemoryCompactionSession = class {
3123
3016
  }
3124
3017
  if (this.planStateLocal) {
3125
3018
  const existing = base ?? { planDescription: "", planItems: [], progress: 0 };
3126
- const merged = {
3019
+ return {
3127
3020
  ...existing,
3128
3021
  ...this.planStateLocal,
3129
3022
  planItems: Array.isArray(this.planStateLocal.planItems) ? this.planStateLocal.planItems : existing.planItems
3130
3023
  };
3131
- return merged;
3132
3024
  }
3133
- return base;
3025
+ return base || {
3026
+ planDescription: "",
3027
+ planItems: [],
3028
+ progress: 0
3029
+ };
3134
3030
  }
3135
3031
  async setPlanState(state) {
3136
3032
  const u = this.underlyingSession;
@@ -3454,51 +3350,129 @@ var CostTracker = class {
3454
3350
  };
3455
3351
  var costTracker = new CostTracker();
3456
3352
 
3457
- // utils/strings/isTrue.ts
3458
- function isTrue(v) {
3459
- if (v === void 0) {
3460
- return false;
3461
- }
3462
- const val = v.trim().toLowerCase();
3463
- return val === "true" || val === "y" || val === "1" || val === "yes" || val === "on" || val === "approved" || val === "approve";
3464
- }
3465
-
3466
- // agent/showErrorToUser.ts
3467
- function showErrorToUser(error, lastToolCallInfo) {
3468
- logError(error);
3469
- const err = error ?? {};
3470
- const name = err.name || "Error";
3471
- const message = err.message || String(err);
3472
- const code = err.code ? ` code=${err.code}` : "";
3473
- const status = err.status || err.statusCode || err.response?.status;
3474
- const statusStr = status ? ` status=${status}` : "";
3475
- const callIdMatch = message.match(/function call\s+(call_[A-Za-z0-9_-]+)/i);
3476
- const callId = callIdMatch?.[1];
3477
- const isNoToolOutput = /No tool output found for function call/i.test(message || "");
3478
- const hint = isNoToolOutput ? `
3479
- Hint: A tool likely threw or returned no result. Ensure tools always return a structured object (e.g., { status, output }) and never throw. If this followed a tool call${callId ? ` (${callId})` : ""}${lastToolCallInfo ? `: ${lastToolCallInfo}` : ""}, review that tool's implementation and logs.` : "";
3480
- let responseDataSnippet = "";
3481
- const data = err.response?.data ?? err.data;
3482
- if (data) {
3483
- try {
3484
- const s = typeof data === "string" ? data : JSON.stringify(data);
3485
- responseDataSnippet = `
3486
- Response data: ${s.slice(0, 500)}${s.length > 500 ? "\u201A\xC4\xB6" : ""}`;
3487
- } catch {
3488
- }
3489
- }
3490
- const stack = err.stack ? `
3491
- Stack: ${String(err.stack).split("\n").slice(0, 8).join("\n")}` : "";
3492
- const lastTool = lastToolCallInfo ? `
3493
- Last tool call: ${lastToolCallInfo}` : "";
3494
- const composed = `${name}:${code}${statusStr} ${message}${hint}${lastTool}${responseDataSnippet}${stack}`;
3495
- emitToListeners("SetInputMode", "denied");
3496
- setTimeout(curryEmitToListeners("SetInputMode", "waiting"), 1e3);
3497
- emitToListeners("PushNewMessages", [{
3498
- type: "agent",
3499
- text: composed,
3500
- version: 1
3501
- }]);
3353
+ // utils/sessions/rateLimits.ts
3354
+ var RateLimitTracker = class {
3355
+ status = {
3356
+ limitRequests: null,
3357
+ limitTokens: null,
3358
+ remainingRequests: null,
3359
+ remainingTokens: null,
3360
+ resetRequests: null,
3361
+ resetTokens: null,
3362
+ retryAfter: null
3363
+ };
3364
+ updateFromHeaders(headers) {
3365
+ const normalizedHeaders = {};
3366
+ for (const key of Object.keys(headers)) {
3367
+ normalizedHeaders[key.toLowerCase()] = headers[key];
3368
+ }
3369
+ const getHeader = (name) => {
3370
+ const value = normalizedHeaders[name.toLowerCase()];
3371
+ return Array.isArray(value) ? value[0] : value;
3372
+ };
3373
+ const limitRequests = getHeader("x-ratelimit-limit-requests") || getHeader("anthropic-ratelimit-requests-limit") || getHeader("x-ratelimit-limit") || getHeader("ratelimit-limit") || getHeader("x-request-limit");
3374
+ const limitTokens = getHeader("x-ratelimit-limit-tokens") || getHeader("anthropic-ratelimit-tokens-limit") || getHeader("x-token-limit");
3375
+ const remainingRequests = getHeader("x-ratelimit-remaining-requests") || getHeader("anthropic-ratelimit-requests-remaining") || getHeader("x-ratelimit-remaining") || getHeader("ratelimit-remaining") || getHeader("x-request-remaining");
3376
+ const remainingTokens = getHeader("x-ratelimit-remaining-tokens") || getHeader("anthropic-ratelimit-tokens-remaining") || getHeader("x-token-remaining");
3377
+ const resetRequests = getHeader("x-ratelimit-reset-requests") || getHeader("anthropic-ratelimit-requests-reset") || getHeader("x-ratelimit-reset") || getHeader("ratelimit-reset") || getHeader("x-request-reset");
3378
+ const resetTokens = getHeader("x-ratelimit-reset-tokens") || getHeader("anthropic-ratelimit-tokens-reset") || getHeader("x-token-reset");
3379
+ const retryAfter = getHeader("retry-after");
3380
+ if (limitRequests) {
3381
+ this.status.limitRequests = parseInt(limitRequests, 10);
3382
+ }
3383
+ if (limitTokens) {
3384
+ this.status.limitTokens = parseInt(limitTokens, 10);
3385
+ }
3386
+ if (remainingRequests) {
3387
+ this.status.remainingRequests = parseInt(remainingRequests, 10);
3388
+ }
3389
+ if (remainingTokens) {
3390
+ this.status.remainingTokens = parseInt(remainingTokens, 10);
3391
+ }
3392
+ if (resetRequests) {
3393
+ this.status.resetRequests = resetRequests;
3394
+ }
3395
+ if (resetTokens) {
3396
+ this.status.resetTokens = resetTokens;
3397
+ }
3398
+ if (retryAfter) {
3399
+ this.status.retryAfter = parseInt(retryAfter, 10);
3400
+ }
3401
+ }
3402
+ isApproachingLimit(threshold) {
3403
+ const usage = this.getUsagePercentage();
3404
+ return {
3405
+ requests: usage.requests >= threshold,
3406
+ tokens: usage.tokens >= threshold
3407
+ };
3408
+ }
3409
+ getStatus() {
3410
+ return { ...this.status };
3411
+ }
3412
+ getUsagePercentage() {
3413
+ const requests = this.status.limitRequests && this.status.remainingRequests !== null ? 100 * (1 - this.status.remainingRequests / this.status.limitRequests) : 0;
3414
+ const tokens = this.status.limitTokens && this.status.remainingTokens !== null ? 100 * (1 - this.status.remainingTokens / this.status.limitTokens) : 0;
3415
+ return { requests, tokens };
3416
+ }
3417
+ reset() {
3418
+ this.status = {
3419
+ limitRequests: null,
3420
+ limitTokens: null,
3421
+ remainingRequests: null,
3422
+ remainingTokens: null,
3423
+ resetRequests: null,
3424
+ resetTokens: null,
3425
+ retryAfter: null
3426
+ };
3427
+ }
3428
+ };
3429
+ var rateLimitTracker = new RateLimitTracker();
3430
+
3431
+ // utils/strings/isTrue.ts
3432
+ function isTrue(v) {
3433
+ if (v === void 0) {
3434
+ return false;
3435
+ }
3436
+ const val = v.trim().toLowerCase();
3437
+ return val === "true" || val === "y" || val === "1" || val === "yes" || val === "on" || val === "approved" || val === "approve";
3438
+ }
3439
+
3440
+ // agent/showErrorToUser.ts
3441
+ function showErrorToUser(error, lastToolCallInfo) {
3442
+ logError(error);
3443
+ const err = error ?? {};
3444
+ const name = err.name || "Error";
3445
+ const message = err.message || String(err);
3446
+ const code = err.code ? ` code=${err.code}` : "";
3447
+ const status = err.status || err.statusCode || err.response?.status;
3448
+ const statusStr = status ? ` status=${status}` : "";
3449
+ const callIdMatch = message.match(/function call\s+(call_[A-Za-z0-9_-]+)/i);
3450
+ const callId = callIdMatch?.[1];
3451
+ const isNoToolOutput = /No tool output found for function call/i.test(message || "");
3452
+ const hint = isNoToolOutput ? `
3453
+ Hint: A tool likely threw or returned no result. Ensure tools always return a structured object (e.g., { status, output }) and never throw. If this followed a tool call${callId ? ` (${callId})` : ""}${lastToolCallInfo ? `: ${lastToolCallInfo}` : ""}, review that tool's implementation and logs.` : "";
3454
+ let responseDataSnippet = "";
3455
+ const data = err.response?.data ?? err.data;
3456
+ if (data) {
3457
+ try {
3458
+ const s = typeof data === "string" ? data : JSON.stringify(data);
3459
+ responseDataSnippet = `
3460
+ Response data: ${s.slice(0, 500)}${s.length > 500 ? "\u201A\xC4\xB6" : ""}`;
3461
+ } catch {
3462
+ }
3463
+ }
3464
+ const stack = err.stack ? `
3465
+ Stack: ${String(err.stack).split("\n").slice(0, 8).join("\n")}` : "";
3466
+ const lastTool = lastToolCallInfo ? `
3467
+ Last tool call: ${lastToolCallInfo}` : "";
3468
+ const composed = `${name}:${code}${statusStr} ${message}${hint}${lastTool}${responseDataSnippet}${stack}`;
3469
+ emitToListeners("SetInputMode", "denied");
3470
+ setTimeout(curryEmitToListeners("SetInputMode", "waiting"), 1e3);
3471
+ emitToListeners("PushNewMessages", [{
3472
+ type: "agent",
3473
+ text: composed,
3474
+ version: 1
3475
+ }]);
3502
3476
  }
3503
3477
 
3504
3478
  // agent/runAgentForOnePass.ts
@@ -3506,8 +3480,9 @@ async function runAgentForOnePass(agent, session, input, controller, isPrompt) {
3506
3480
  let lastToolCallInfo = null;
3507
3481
  try {
3508
3482
  let hasStartedResponse = false;
3483
+ const planContext = await session.getPlanState();
3509
3484
  let adjustedInput = input;
3510
- const noPlanYet = globalPlanContext.planItems.length === 0 && (!globalPlanContext.planDescription || globalPlanContext.planDescription.trim().length === 0);
3485
+ const noPlanYet = planContext.planItems.length === 0 && (!planContext?.planDescription || planContext?.planDescription.trim().length === 0);
3511
3486
  if (noPlanYet && typeof input === "string") {
3512
3487
  const planningInstruction = [
3513
3488
  isPrompt ? "The following request is from a non-interactive prompt. You MUST establish a comprehensive plan first, then execute it autonomously until completion." : "If there is no current plan, first establish one and keep it updated:",
@@ -3861,6 +3836,65 @@ async function runAgentForOnePass(agent, session, input, controller, isPrompt) {
3861
3836
  }
3862
3837
  }
3863
3838
 
3839
+ // agent/translateSessionItemsToMessages.ts
3840
+ function translateSessionItemsToMessages(items) {
3841
+ const messages = [];
3842
+ if (items.length > 0) {
3843
+ let id = 0;
3844
+ for (const item of items) {
3845
+ if (item.type === "message" && item.role === "user") {
3846
+ messages.push({
3847
+ id: id++,
3848
+ type: "user",
3849
+ text: item.content,
3850
+ version: 1
3851
+ });
3852
+ } else if (item.type === "message" && item.role === "assistant") {
3853
+ if (typeof item.content === "string") {
3854
+ messages.push({
3855
+ id: id++,
3856
+ type: "agent",
3857
+ text: item.content,
3858
+ version: 1
3859
+ });
3860
+ } else if (Array.isArray(item.content)) {
3861
+ for (const part of item.content) {
3862
+ if (part.type === "text" || part.type === "output_text") {
3863
+ messages.push({
3864
+ id: id++,
3865
+ type: "agent",
3866
+ text: part.text,
3867
+ version: 1
3868
+ });
3869
+ } else if (part.type === "tool_call" || part.type === "function_call") {
3870
+ const args = typeof part.arguments === "string" ? part.arguments : part.arguments ? JSON.stringify(part.arguments) : "";
3871
+ const displayedArgs = args ? `(${args})` : "()";
3872
+ messages.push({
3873
+ id: id++,
3874
+ type: "tool",
3875
+ text: part.name,
3876
+ args: displayedArgs,
3877
+ version: 1
3878
+ });
3879
+ }
3880
+ }
3881
+ }
3882
+ } else if (item.type === "function_call") {
3883
+ const args = typeof item.arguments === "string" ? item.arguments : item.arguments ? JSON.stringify(item.arguments) : "";
3884
+ const displayedArgs = args ? `(${args})` : "()";
3885
+ messages.push({
3886
+ id: id++,
3887
+ type: "tool",
3888
+ text: item.name,
3889
+ args: displayedArgs,
3890
+ version: 1
3891
+ });
3892
+ }
3893
+ }
3894
+ }
3895
+ return messages;
3896
+ }
3897
+
3864
3898
  // agent/AgentManager.ts
3865
3899
  var AgentManager = class _AgentManager {
3866
3900
  isInitialized = false;
@@ -3870,121 +3904,39 @@ var AgentManager = class _AgentManager {
3870
3904
  agent = null;
3871
3905
  session = null;
3872
3906
  initialMessages = [];
3873
- static instantiateAgent(tools) {
3907
+ static instantiateAgent(tools, instructions) {
3874
3908
  return new Agent3({
3875
3909
  name: "Harper Agent",
3876
3910
  model: isOpenAIModel(trackedState.model) ? trackedState.model : getModel(trackedState.model),
3877
3911
  modelSettings: getModelSettings(trackedState.model),
3878
- instructions: readAgentSkillsRoot() || defaultInstructions(),
3912
+ instructions: instructions || readAgentSkillsRoot() || defaultInstructions(),
3879
3913
  tools
3880
3914
  });
3881
3915
  }
3882
- async initialize() {
3916
+ async initialize(agent, session) {
3883
3917
  if (this.isInitialized) {
3884
3918
  return;
3885
3919
  }
3886
- this.agent = _AgentManager.instantiateAgent(createTools());
3887
- this.session = createSession(trackedState.sessionPath);
3920
+ this.session = session || createSession(trackedState.sessionPath);
3921
+ this.agent = agent || _AgentManager.instantiateAgent([
3922
+ ...createSharedTools(this.session),
3923
+ ...createCLITools(this.session)
3924
+ ]);
3888
3925
  try {
3889
3926
  const plan = await this.session?.getPlanState?.();
3890
3927
  if (plan && typeof plan === "object") {
3891
3928
  if (typeof plan.planDescription === "string") {
3892
- globalPlanContext.planDescription = plan.planDescription;
3893
3929
  emitToListeners("SetPlanDescription", plan.planDescription);
3894
3930
  }
3895
3931
  if (Array.isArray(plan.planItems)) {
3896
- globalPlanContext.planItems = plan.planItems;
3897
- const completedCount = plan.planItems.filter(
3898
- (it) => it?.status === "done" || it?.status === "not-needed"
3899
- ).length;
3900
- const progress = plan.planItems.length === 0 ? 0 : Math.round(completedCount / plan.planItems.length * 100);
3901
- globalPlanContext.progress = progress;
3902
3932
  emitToListeners("SetPlanItems", plan.planItems);
3903
3933
  }
3904
3934
  }
3905
3935
  } catch {
3906
3936
  }
3907
- try {
3908
- addListener("SetPlanDescription", async (desc) => {
3909
- try {
3910
- await this.session?.setPlanState?.({ planDescription: desc });
3911
- } catch {
3912
- }
3913
- });
3914
- addListener("SetPlanItems", async (items) => {
3915
- if (Array.isArray(items)) {
3916
- globalPlanContext.planItems = items;
3917
- const completedCount = items.filter((it) => it?.status === "done" || it?.status === "not-needed").length;
3918
- globalPlanContext.progress = items.length > 0 ? Math.round(completedCount / items.length * 100) : 0;
3919
- }
3920
- try {
3921
- const completedCount = Array.isArray(items) ? items.filter((it) => it?.status === "done" || it?.status === "not-needed").length : 0;
3922
- const progress = Array.isArray(items) && items.length > 0 ? Math.round(completedCount / items.length * 100) : 0;
3923
- await this.session?.setPlanState?.({ planItems: items, progress });
3924
- } catch {
3925
- }
3926
- });
3927
- } catch {
3928
- }
3929
3937
  if (trackedState.sessionPath) {
3930
3938
  const items = await this.session.getItems();
3931
- if (items.length > 0) {
3932
- const messages = [];
3933
- let id = 0;
3934
- for (const item of items) {
3935
- if (item.type === "message" && item.role === "user") {
3936
- messages.push({
3937
- id: id++,
3938
- type: "user",
3939
- text: item.content,
3940
- version: 1
3941
- });
3942
- } else if (item.type === "message" && item.role === "assistant") {
3943
- if (typeof item.content === "string") {
3944
- messages.push({
3945
- id: id++,
3946
- type: "agent",
3947
- text: item.content,
3948
- version: 1
3949
- });
3950
- } else if (Array.isArray(item.content)) {
3951
- for (const part of item.content) {
3952
- if (part.type === "text" || part.type === "output_text") {
3953
- messages.push({
3954
- id: id++,
3955
- type: "agent",
3956
- text: part.text,
3957
- version: 1
3958
- });
3959
- } else if (part.type === "tool_call" || part.type === "function_call") {
3960
- const args = typeof part.arguments === "string" ? part.arguments : part.arguments ? JSON.stringify(part.arguments) : "";
3961
- const displayedArgs = args ? `(${args})` : "()";
3962
- messages.push({
3963
- id: id++,
3964
- type: "tool",
3965
- text: part.name,
3966
- args: displayedArgs,
3967
- version: 1
3968
- });
3969
- }
3970
- }
3971
- }
3972
- } else if (item.type === "function_call") {
3973
- const args = typeof item.arguments === "string" ? item.arguments : item.arguments ? JSON.stringify(item.arguments) : "";
3974
- const displayedArgs = args ? `(${args})` : "()";
3975
- messages.push({
3976
- id: id++,
3977
- type: "tool",
3978
- text: item.name,
3979
- args: displayedArgs,
3980
- version: 1
3981
- });
3982
- }
3983
- }
3984
- if (messages.length > 0) {
3985
- this.initialMessages = messages;
3986
- }
3987
- }
3939
+ this.initialMessages = translateSessionItemsToMessages(items);
3988
3940
  }
3989
3941
  this.isInitialized = true;
3990
3942
  if (trackedState.prompt?.trim?.()?.length) {
@@ -4053,7 +4005,8 @@ var AgentManager = class _AgentManager {
4053
4005
  }
4054
4006
  }
4055
4007
  if (trackedState.autonomous && !this.resumeState) {
4056
- const planItems = globalPlanContext.planItems;
4008
+ const planState = await this.session.getPlanState();
4009
+ const planItems = planState.planItems;
4057
4010
  const hasPlan = planItems.length > 0;
4058
4011
  const allDone = hasPlan && planItems.every((item) => item.status === "done" || item.status === "not-needed");
4059
4012
  if (allDone) {
@@ -4105,2833 +4058,32 @@ var AgentManager = class _AgentManager {
4105
4058
  };
4106
4059
  var agentManager = new AgentManager();
4107
4060
 
4108
- // ink/main.tsx
4109
- import { render, useApp } from "ink";
4110
- import "react";
4111
-
4112
- // ink/components/ChatContent.tsx
4113
- import { Spinner as Spinner2 } from "@inkjs/ui";
4114
- import { Box as Box10, Text as Text10, useInput as useInput4 } from "ink";
4115
- import React11, { useCallback as useCallback5, useEffect as useEffect8, useMemo as useMemo10, useRef as useRef2, useState as useState13 } from "react";
4116
-
4117
- // ink/contexts/ChatContext.tsx
4118
- import { createContext as createContext2, useContext as useContext2, useMemo as useMemo2, useState as useState2 } from "react";
4119
- import { jsx as jsx2 } from "react/jsx-runtime";
4120
- var ChatContext = createContext2(void 0);
4121
- var useChat = () => {
4122
- const context = useContext2(ChatContext);
4123
- if (!context) {
4124
- throw new Error("useChat must be used within a ChatProvider");
4125
- }
4126
- return context;
4127
- };
4128
- var initialMessages = [];
4129
- function getInitialMessages() {
4130
- if (initialMessages.length === 0) {
4131
- initialMessages = agentManager.initialMessages ? agentManager.initialMessages.map((m, index) => ({
4132
- ...m,
4133
- id: index,
4134
- version: m.version ?? 1
4135
- })) : [{
4136
- id: 0,
4137
- type: "agent",
4138
- text: 'What shall we build today? (Type "/clear" to reset, "/skills" to add skills, or "/exit" to quit)',
4139
- version: 1
4140
- }];
4141
- }
4142
- return initialMessages;
4143
- }
4144
- var ChatProvider = ({
4145
- children
4146
- }) => {
4147
- const [messages, setMessages] = useState2(getInitialMessages());
4148
- const [userInputMode, setUserInputMode] = useState2("waiting");
4149
- const [isThinking, setIsThinking] = useState2(false);
4150
- const [isCompacting, setIsCompacting] = useState2(false);
4151
- const [pullingState, setPullingState] = useState2(null);
4152
- const [focusedArea, setFocusedArea] = useState2("input");
4153
- useListener("PushNewMessages", (messages2) => {
4154
- setMessages((prev) => {
4155
- return prev.concat(
4156
- messages2.map((message, index) => ({ ...message, id: prev.length + index, version: 1 }))
4157
- );
4158
- });
4159
- }, []);
4160
- useListener("SetInputMode", (newInputMode) => {
4161
- setUserInputMode(newInputMode);
4162
- }, []);
4163
- useListener("SetThinking", (value2) => {
4164
- setIsThinking(Boolean(value2));
4165
- }, []);
4166
- useListener("SetCompacting", (value2) => {
4167
- setIsCompacting(Boolean(value2));
4168
- }, []);
4169
- useListener("SetPulling", (value2) => {
4170
- setPullingState(value2);
4171
- }, []);
4172
- useListener("InterruptThought", () => {
4173
- setIsThinking(false);
4174
- }, []);
4175
- useListener("UpdateLastMessageText", (text) => {
4176
- setMessages((prev) => {
4177
- if (prev.length === 0) {
4178
- return prev;
4179
- }
4180
- const lastIndex = [...prev].reverse().findIndex((m) => m.type === "agent");
4181
- if (lastIndex === -1) {
4182
- return prev;
4183
- }
4184
- const actualIndex = prev.length - 1 - lastIndex;
4185
- const last = prev[actualIndex];
4186
- const updated = [...prev];
4187
- updated[actualIndex] = {
4188
- ...last,
4189
- text: last.text + text,
4190
- version: last.version + 1
4191
- };
4192
- return updated;
4193
- });
4194
- }, []);
4195
- useListener("ClearChatHistory", () => {
4196
- agentManager.session?.clearSession();
4197
- setMessages([{
4198
- id: 0,
4199
- type: "agent",
4200
- text: "Chat cleared. What shall we build today?",
4201
- version: 1
4202
- }]);
4203
- }, []);
4204
- const value = useMemo2(() => ({
4205
- messages,
4206
- userInputMode,
4207
- isThinking,
4208
- isCompacting,
4209
- pullingState,
4210
- focusedArea,
4211
- setFocusedArea
4212
- }), [messages, userInputMode, isThinking, isCompacting, pullingState, focusedArea]);
4213
- return /* @__PURE__ */ jsx2(ChatContext.Provider, { value, children });
4214
- };
4215
-
4216
- // ink/bindings/useMessageListener.ts
4217
- var hasShownInterruptHint = false;
4218
- function useMessageListener() {
4219
- const { userInputMode, isThinking } = useChat();
4220
- useListener("InterruptThought", () => {
4221
- agentManager.interrupt();
4222
- emitToListeners("PushNewMessages", [
4223
- {
4224
- type: "interrupted",
4225
- text: "- thought interrupted -",
4226
- version: 1
4227
- }
4228
- ]);
4229
- }, []);
4230
- useListener("PushNewMessages", async (messages) => {
4231
- for (const message of messages) {
4232
- if ((message.type === "user" || message.type === "prompt") && message.text) {
4233
- const lowerText = message.text.toLowerCase();
4234
- if (lowerText === "exit" || lowerText === "bye") {
4235
- await handleExit();
4236
- }
4237
- if (isThinking) {
4238
- if (!hasShownInterruptHint) {
4239
- hasShownInterruptHint = true;
4240
- emitToListeners("PushNewMessages", [{
4241
- type: "interrupted",
4242
- text: "- to interrupt the current thinking, press escape -",
4243
- version: 1
4244
- }]);
4245
- }
4246
- agentManager.enqueueUserInput(message.text);
4247
- message.handled = true;
4248
- } else if (!message.handled) {
4249
- void agentManager.runTask(message.text, message.type === "prompt");
4250
- }
4251
- }
4252
- }
4253
- }, [userInputMode, isThinking]);
4254
- }
4255
-
4256
- // ink/constants/footerHeight.ts
4257
- var footerHeight = 2;
4258
-
4259
- // ink/contexts/ApprovalContext.tsx
4260
- import { createContext as createContext3, useCallback as useCallback2, useContext as useContext3, useMemo as useMemo3, useState as useState3 } from "react";
4261
- import { jsx as jsx3 } from "react/jsx-runtime";
4262
- var ApprovalContext = createContext3(void 0);
4263
- var useApproval = () => {
4264
- const context = useContext3(ApprovalContext);
4265
- if (!context) {
4266
- throw new Error("useApproval must be used within an ApprovalProvider");
4267
- }
4268
- return context;
4269
- };
4270
- var ApprovalProvider = ({ children }) => {
4271
- const [payload, setPayload] = useState3(null);
4272
- const [toolInfos] = useState3(/* @__PURE__ */ new Map());
4273
- const registerToolInfo = useCallback2(
4274
- (info) => {
4275
- toolInfos.set(info.callId, info);
4276
- },
4277
- [toolInfos]
4278
- );
4279
- useListener("OpenApprovalViewer", (p) => {
4280
- let finalPayload = { ...p, openedAt: Date.now() };
4281
- if (p.mode === "info" && p.callId) {
4282
- const info = toolInfos.get(p.callId);
4283
- if (info) {
4284
- finalPayload = { ...finalPayload, ...info };
4285
- }
4286
- } else if (p.callId) {
4287
- registerToolInfo({
4288
- callId: p.callId,
4289
- type: p.type,
4290
- path: p.path,
4291
- diff: p.diff,
4292
- code: p.code,
4293
- commands: p.commands
4294
- });
4295
- }
4296
- setPayload(finalPayload);
4297
- }, [registerToolInfo, toolInfos]);
4298
- useListener("CloseApprovalViewer", () => {
4299
- setPayload(null);
4300
- }, []);
4301
- const value = useMemo3(() => ({
4302
- payload,
4303
- setPayload,
4304
- registerToolInfo
4305
- }), [payload, registerToolInfo]);
4306
- return /* @__PURE__ */ jsx3(ApprovalContext.Provider, { value, children });
4307
- };
4308
-
4309
- // ink/contexts/PlanContext.tsx
4310
- import { createContext as createContext4, useContext as useContext4, useMemo as useMemo4, useState as useState4 } from "react";
4311
- import { jsx as jsx4 } from "react/jsx-runtime";
4312
- var PlanContext = createContext4(globalPlanContext);
4313
- var usePlan = () => {
4314
- const context = useContext4(PlanContext);
4315
- if (!context) {
4316
- throw new Error("usePlan must be used within a PlanProvider");
4317
- }
4318
- return context;
4319
- };
4320
- var PlanProvider = ({
4321
- children
4322
- }) => {
4323
- const [planDescription, setPlanDescription] = useState4(globalPlanContext.planDescription);
4324
- const [planItems, setPlanItems] = useState4(globalPlanContext.planItems);
4325
- const [progress, setProgress] = useState4(globalPlanContext.progress);
4326
- useListener("SetPlanDescription", (newGoal) => {
4327
- globalPlanContext.planDescription = newGoal;
4328
- setPlanDescription(newGoal);
4329
- }, []);
4330
- useListener("SetPlanItems", (planItems2) => {
4331
- globalPlanContext.planItems = planItems2;
4332
- const completedCount = planItems2.filter((item) => item.status === "done" || item.status === "not-needed").length;
4333
- const progress2 = planItems2.length === 0 ? 0 : Math.round(completedCount / planItems2.length * 100);
4334
- globalPlanContext.progress = progress2;
4335
- setPlanItems(planItems2);
4336
- setProgress(progress2);
4337
- }, []);
4338
- const value = useMemo4(() => ({
4339
- progress,
4340
- planDescription,
4341
- planItems
4342
- }), [progress, planDescription, planItems]);
4343
- return /* @__PURE__ */ jsx4(PlanContext.Provider, { value, children });
4061
+ export {
4062
+ useListener,
4063
+ emitToListeners,
4064
+ lastEmittedValue,
4065
+ curryEmitToListeners,
4066
+ rateLimitTracker,
4067
+ trackedState,
4068
+ resetTrackedState,
4069
+ defaultModelToken,
4070
+ defaultOpenAIModel,
4071
+ defaultOpenAICompactionModel,
4072
+ defaultAnthropicModel,
4073
+ defaultAnthropicCompactionModel,
4074
+ defaultGoogleModel,
4075
+ defaultGoogleCompactionModel,
4076
+ defaultOllamaModel,
4077
+ defaultOllamaCompactionModel,
4078
+ isOpenAIModel,
4079
+ handleExit,
4080
+ setupGlobalErrorHandlers,
4081
+ updateEnv,
4082
+ fetchOllamaModels,
4083
+ excludeFalsy,
4084
+ useActions,
4085
+ ActionsProvider,
4086
+ isTrue,
4087
+ AgentManager,
4088
+ agentManager
4344
4089
  };
4345
-
4346
- // ink/library/useTerminalSize.ts
4347
- import { useStdout } from "ink";
4348
- import { useEffect as useEffect3, useState as useState5 } from "react";
4349
- function useTerminalSize() {
4350
- const { stdout } = useStdout();
4351
- const [size, setSize] = useState5({
4352
- rows: stdout?.rows ?? 24,
4353
- columns: stdout?.columns ?? 80
4354
- });
4355
- useEffect3(() => {
4356
- if (!stdout || !stdout.isTTY) {
4357
- return;
4358
- }
4359
- const handleResize = () => {
4360
- setSize({
4361
- rows: stdout.rows ?? 24,
4362
- columns: stdout.columns ?? 80
4363
- });
4364
- };
4365
- stdout.on("resize", handleResize);
4366
- return () => {
4367
- stdout.off("resize", handleResize);
4368
- };
4369
- }, [stdout]);
4370
- return size;
4371
- }
4372
-
4373
- // ink/library/wrapText.ts
4374
- function wrapText(text, width) {
4375
- if (width <= 1) {
4376
- return text ? [text] : [""];
4377
- }
4378
- const hardLines = text.split("\n");
4379
- const allWrappedLines = [];
4380
- for (const hardLine of hardLines) {
4381
- if (hardLine.length === 0) {
4382
- allWrappedLines.push("");
4383
- continue;
4384
- }
4385
- const words = hardLine.split(/(\s+)/);
4386
- let currentLine = "";
4387
- for (const token of words) {
4388
- if (token.length === 0) {
4389
- continue;
4390
- }
4391
- if (currentLine.length + token.length <= width) {
4392
- currentLine += token;
4393
- } else {
4394
- if (currentLine) {
4395
- allWrappedLines.push(currentLine.trimEnd());
4396
- }
4397
- if (token.trim().length === 0) {
4398
- currentLine = "";
4399
- } else if (token.length > width) {
4400
- for (let i = 0; i < token.length; i += width) {
4401
- const chunk = token.slice(i, i + width);
4402
- if (chunk.length === width) {
4403
- allWrappedLines.push(chunk);
4404
- } else {
4405
- currentLine = chunk;
4406
- }
4407
- }
4408
- } else {
4409
- currentLine = token;
4410
- }
4411
- }
4412
- }
4413
- if (currentLine) {
4414
- allWrappedLines.push(currentLine.trimEnd());
4415
- }
4416
- }
4417
- return allWrappedLines.length ? allWrappedLines : [""];
4418
- }
4419
-
4420
- // ink/components/ActionsView.tsx
4421
- import { Box as Box3, Text as Text3, useInput } from "ink";
4422
- import { useCallback as useCallback3, useEffect as useEffect5, useState as useState7 } from "react";
4423
-
4424
- // ink/components/ActionItemRow.tsx
4425
- import { Spinner } from "@inkjs/ui";
4426
- import { Box, Text } from "ink";
4427
- import { memo } from "react";
4428
- import { jsx as jsx5, jsxs } from "react/jsx-runtime";
4429
- var ActionItemRow = memo(
4430
- ({ item, isSelected, isFocused, width }) => {
4431
- const statusColor = item.running ? "yellow" : item.status === "approved" || item.status === "succeeded" || typeof item.exitCode === "number" && item.exitCode === 0 ? "green" : item.status === "denied" || item.status === "failed" || typeof item.exitCode === "number" && item.exitCode !== 0 ? "red" : "cyan";
4432
- const selectionColor = isFocused ? "cyan" : "gray";
4433
- const pipe = /* @__PURE__ */ jsx5(Text, { color: isSelected ? selectionColor : "gray", bold: isSelected, children: isSelected ? "\u2503 " : "\u2502 " });
4434
- const statusLabel = item.running ? " " : item.kind === "approval" ? item.status === "approved" ? " approved" : item.status === "denied" ? " denied" : "" : typeof item.exitCode === "number" ? ` ${item.exitCode}` : "";
4435
- const statusLabelWidth = item.running ? 2 : statusLabel.length;
4436
- const pipeWidth = 3;
4437
- const nameWithSpace = `${item.title} `;
4438
- const availableForDetail = width - pipeWidth - nameWithSpace.length - statusLabelWidth;
4439
- const fullDetail = item.detail || "";
4440
- let displayedDetail = fullDetail;
4441
- if (availableForDetail < fullDetail.length) {
4442
- if (availableForDetail > 3) {
4443
- displayedDetail = fullDetail.slice(0, availableForDetail - 3) + "...";
4444
- } else {
4445
- displayedDetail = fullDetail.slice(0, Math.max(0, availableForDetail));
4446
- }
4447
- }
4448
- return /* @__PURE__ */ jsxs(Box, { children: [
4449
- pipe,
4450
- /* @__PURE__ */ jsxs(Box, { flexGrow: 1, children: [
4451
- /* @__PURE__ */ jsx5(Text, { color: statusColor, bold: true, children: item.title }),
4452
- /* @__PURE__ */ jsxs(Text, { color: "gray", children: [
4453
- " ",
4454
- displayedDetail
4455
- ] })
4456
- ] }),
4457
- /* @__PURE__ */ jsx5(Box, { children: item.running ? /* @__PURE__ */ jsxs(Box, { children: [
4458
- /* @__PURE__ */ jsx5(Text, { color: "yellow" }),
4459
- /* @__PURE__ */ jsx5(Spinner, { type: "dots" })
4460
- ] }) : /* @__PURE__ */ jsx5(Text, { color: statusColor, bold: true, children: statusLabel }) })
4461
- ] });
4462
- }
4463
- );
4464
-
4465
- // ink/components/VirtualList.tsx
4466
- import { Box as Box2, Text as Text2 } from "ink";
4467
- import { forwardRef, useEffect as useEffect4, useImperativeHandle, useMemo as useMemo5, useRef, useState as useState6 } from "react";
4468
- import { jsx as jsx6, jsxs as jsxs2 } from "react/jsx-runtime";
4469
- function getDefaultKey(item, index) {
4470
- if (item && typeof item === "object") {
4471
- if ("id" in item && (typeof item.id === "string" || typeof item.id === "number")) {
4472
- return String(item.id);
4473
- }
4474
- if ("key" in item && (typeof item.key === "string" || typeof item.key === "number")) {
4475
- return String(item.key);
4476
- }
4477
- }
4478
- return String(index);
4479
- }
4480
- var VirtualListInner = (props, ref) => {
4481
- const {
4482
- items,
4483
- renderItem,
4484
- getItemHeight: getItemHeightProp,
4485
- itemHeight: fixedItemHeight,
4486
- selectedIndex = 0,
4487
- keyExtractor,
4488
- height: propHeight,
4489
- reservedLines = 0,
4490
- showOverflowIndicators = true,
4491
- overflowIndicatorThreshold = 1,
4492
- renderOverflowTop,
4493
- renderOverflowBottom,
4494
- renderFiller,
4495
- onViewportChange
4496
- } = props;
4497
- const { rows: terminalRows } = useTerminalSize();
4498
- const resolvedHeight = useMemo5(() => {
4499
- if (typeof propHeight === "number") {
4500
- return propHeight;
4501
- }
4502
- if (typeof propHeight === "string" && propHeight.endsWith("%")) {
4503
- const percent = parseFloat(propHeight) / 100;
4504
- return Math.floor(terminalRows * percent);
4505
- }
4506
- return Math.max(1, terminalRows - reservedLines);
4507
- }, [propHeight, terminalRows, reservedLines]);
4508
- const getItemHeight = useMemo5(() => {
4509
- if (getItemHeightProp) {
4510
- return getItemHeightProp;
4511
- }
4512
- if (fixedItemHeight) {
4513
- return () => fixedItemHeight;
4514
- }
4515
- return () => 1;
4516
- }, [getItemHeightProp, fixedItemHeight]);
4517
- const indicatorLines = showOverflowIndicators ? 2 : 0;
4518
- const availableHeight = Math.max(0, resolvedHeight - indicatorLines);
4519
- const [viewportOffset, setViewportOffset] = useState6(0);
4520
- const lastItemsLength = useRef(items.length);
4521
- const viewportOffsetRef = useRef(viewportOffset);
4522
- useEffect4(() => {
4523
- viewportOffsetRef.current = viewportOffset;
4524
- }, [viewportOffset]);
4525
- const clampedSelectedIndex = Math.max(0, Math.min(selectedIndex, items.length - 1));
4526
- const { visibleCount, visibleItems } = useMemo5(() => {
4527
- if (availableHeight <= 0) {
4528
- return { visibleCount: 0, visibleItems: [] };
4529
- }
4530
- let currentHeight = 0;
4531
- let count = 0;
4532
- const visibleItems2 = [];
4533
- for (let i = viewportOffset; i < items.length; i++) {
4534
- const h = getItemHeight(items[i], i);
4535
- if (currentHeight + h > availableHeight && count > 0) {
4536
- break;
4537
- }
4538
- currentHeight += h;
4539
- count++;
4540
- visibleItems2.push(items[i]);
4541
- }
4542
- return { visibleCount: count, visibleItems: visibleItems2 };
4543
- }, [items, viewportOffset, availableHeight, getItemHeight]);
4544
- useEffect4(() => {
4545
- if (items.length === 0 || availableHeight <= 0) {
4546
- lastItemsLength.current = items.length;
4547
- return;
4548
- }
4549
- const currentViewportOffset = viewportOffsetRef.current;
4550
- const isSelectingLast = clampedSelectedIndex === items.length - 1;
4551
- const wasAtBottom = lastItemsLength.current > 0 && currentViewportOffset + visibleCount >= lastItemsLength.current - 1;
4552
- lastItemsLength.current = items.length;
4553
- if (clampedSelectedIndex < currentViewportOffset) {
4554
- setViewportOffset(clampedSelectedIndex);
4555
- return;
4556
- }
4557
- let heightToSelected = 0;
4558
- let isVisible = false;
4559
- for (let i = currentViewportOffset; i <= clampedSelectedIndex; i++) {
4560
- const h = getItemHeight(items[i], i);
4561
- if (heightToSelected + h > availableHeight) {
4562
- isVisible = false;
4563
- break;
4564
- }
4565
- heightToSelected += h;
4566
- if (i === clampedSelectedIndex) {
4567
- isVisible = true;
4568
- }
4569
- }
4570
- if (!isVisible || isSelectingLast || wasAtBottom) {
4571
- let tempOffset = clampedSelectedIndex;
4572
- let hSum2 = 0;
4573
- while (tempOffset >= 0) {
4574
- const h = getItemHeight(items[tempOffset], tempOffset);
4575
- if (hSum2 + h > availableHeight) {
4576
- tempOffset++;
4577
- break;
4578
- }
4579
- hSum2 += h;
4580
- if (tempOffset === 0) {
4581
- break;
4582
- }
4583
- tempOffset--;
4584
- }
4585
- const finalOffset = Math.max(0, tempOffset);
4586
- if (finalOffset !== currentViewportOffset) {
4587
- setViewportOffset(finalOffset);
4588
- }
4589
- }
4590
- }, [clampedSelectedIndex, items, availableHeight, getItemHeight, visibleCount]);
4591
- const viewport = useMemo5(
4592
- () => ({
4593
- offset: viewportOffset,
4594
- visibleCount,
4595
- totalCount: items.length
4596
- }),
4597
- [viewportOffset, visibleCount, items.length]
4598
- );
4599
- const isInitialMount = useRef(true);
4600
- useEffect4(() => {
4601
- if (isInitialMount.current) {
4602
- if (items.length > 0 && availableHeight > 0) {
4603
- isInitialMount.current = false;
4604
- let tempOffset = items.length - 1;
4605
- let hSum2 = 0;
4606
- while (tempOffset >= 0) {
4607
- const h = getItemHeight(items[tempOffset], tempOffset);
4608
- if (hSum2 + h > availableHeight) {
4609
- tempOffset++;
4610
- break;
4611
- }
4612
- hSum2 += h;
4613
- if (tempOffset === 0) {
4614
- break;
4615
- }
4616
- tempOffset--;
4617
- }
4618
- setViewportOffset(Math.max(0, tempOffset));
4619
- }
4620
- }
4621
- }, [availableHeight, items.length, getItemHeight]);
4622
- useEffect4(() => {
4623
- onViewportChange?.(viewport);
4624
- }, [viewport, onViewportChange]);
4625
- useImperativeHandle(
4626
- ref,
4627
- () => ({
4628
- scrollToIndex: (index, alignment = "auto") => {
4629
- const clampedIndex = Math.max(0, Math.min(index, items.length - 1));
4630
- let newOffset = viewportOffset;
4631
- switch (alignment) {
4632
- case "top":
4633
- newOffset = clampedIndex;
4634
- break;
4635
- case "center": {
4636
- let hSum2 = 0;
4637
- let tempOffset = clampedIndex;
4638
- while (tempOffset >= 0 && hSum2 < availableHeight / 2) {
4639
- hSum2 += getItemHeight(items[tempOffset], tempOffset);
4640
- tempOffset--;
4641
- }
4642
- newOffset = tempOffset + 1;
4643
- break;
4644
- }
4645
- case "bottom": {
4646
- let hSum2 = 0;
4647
- let tempOffset = clampedIndex;
4648
- while (tempOffset >= 0 && hSum2 < availableHeight) {
4649
- const h = getItemHeight(items[tempOffset], tempOffset);
4650
- if (hSum2 + h > availableHeight) {
4651
- tempOffset++;
4652
- break;
4653
- }
4654
- hSum2 += h;
4655
- if (tempOffset === 0) {
4656
- break;
4657
- }
4658
- tempOffset--;
4659
- }
4660
- newOffset = tempOffset;
4661
- break;
4662
- }
4663
- case "auto":
4664
- default: {
4665
- if (clampedIndex < viewportOffset) {
4666
- newOffset = clampedIndex;
4667
- } else {
4668
- let heightToSelected = 0;
4669
- let isVisible = false;
4670
- for (let i = viewportOffset; i <= clampedIndex; i++) {
4671
- const h = getItemHeight(items[i], i);
4672
- if (heightToSelected + h > availableHeight) {
4673
- isVisible = false;
4674
- break;
4675
- }
4676
- heightToSelected += h;
4677
- if (i === clampedIndex) {
4678
- isVisible = true;
4679
- }
4680
- }
4681
- if (!isVisible) {
4682
- let hSum2 = 0;
4683
- let tempOffset = clampedIndex;
4684
- while (tempOffset >= 0) {
4685
- const h = getItemHeight(items[tempOffset], tempOffset);
4686
- if (hSum2 + h > availableHeight) {
4687
- tempOffset++;
4688
- break;
4689
- }
4690
- hSum2 += h;
4691
- if (tempOffset === 0) {
4692
- break;
4693
- }
4694
- tempOffset--;
4695
- }
4696
- newOffset = tempOffset;
4697
- }
4698
- }
4699
- }
4700
- }
4701
- setViewportOffset(Math.max(0, newOffset));
4702
- },
4703
- getViewport: () => viewport
4704
- }),
4705
- [items, viewportOffset, availableHeight, getItemHeight, viewport]
4706
- );
4707
- const overflowTop = viewportOffset;
4708
- let itemsFittingBelow = 0;
4709
- let hSum = 0;
4710
- for (let i = viewportOffset; i < items.length; i++) {
4711
- const h = getItemHeight(items[i], i);
4712
- if (hSum + h > availableHeight) {
4713
- break;
4714
- }
4715
- hSum += h;
4716
- itemsFittingBelow++;
4717
- }
4718
- const overflowBottom = Math.max(0, items.length - viewportOffset - itemsFittingBelow);
4719
- const defaultOverflowTop = (count) => {
4720
- if (count < overflowIndicatorThreshold) {
4721
- return null;
4722
- }
4723
- return /* @__PURE__ */ jsx6(Box2, { paddingLeft: 2, children: /* @__PURE__ */ jsxs2(Text2, { dimColor: true, children: [
4724
- "\u25B2 ",
4725
- count,
4726
- " more"
4727
- ] }) });
4728
- };
4729
- const defaultOverflowBottom = (count) => {
4730
- if (count < overflowIndicatorThreshold) {
4731
- return null;
4732
- }
4733
- return /* @__PURE__ */ jsx6(Box2, { paddingLeft: 2, children: /* @__PURE__ */ jsxs2(Text2, { dimColor: true, children: [
4734
- "\u25BC ",
4735
- count,
4736
- " more"
4737
- ] }) });
4738
- };
4739
- const topIndicator = renderOverflowTop ?? defaultOverflowTop;
4740
- const bottomIndicator = renderOverflowBottom ?? defaultOverflowBottom;
4741
- return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", height: resolvedHeight, children: [
4742
- showOverflowIndicators && /* @__PURE__ */ jsx6(Box2, { height: 1, children: topIndicator(overflowTop) }),
4743
- /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", flexGrow: 1, children: [
4744
- visibleItems.map((item, idx) => {
4745
- const actualIndex = viewportOffset + idx;
4746
- const key = keyExtractor ? keyExtractor(item, actualIndex) : getDefaultKey(item, actualIndex);
4747
- const itemProps = {
4748
- item,
4749
- index: actualIndex,
4750
- isSelected: actualIndex === clampedSelectedIndex
4751
- };
4752
- return /* @__PURE__ */ jsx6(Box2, { height: getItemHeight(item, actualIndex), overflow: "hidden", children: renderItem(itemProps) }, key);
4753
- }),
4754
- renderFiller && hSum < availableHeight && /* @__PURE__ */ jsx6(Box2, { flexDirection: "column", flexGrow: 1, children: renderFiller(availableHeight - hSum) })
4755
- ] }),
4756
- showOverflowIndicators && /* @__PURE__ */ jsx6(Box2, { height: 1, children: bottomIndicator(overflowBottom) })
4757
- ] });
4758
- };
4759
- var VirtualList = forwardRef(VirtualListInner);
4760
-
4761
- // ink/components/ActionsView.tsx
4762
- import { jsx as jsx7, jsxs as jsxs3 } from "react/jsx-runtime";
4763
- function ActionsView({ height, isFocused }) {
4764
- const { actions } = useActions();
4765
- const [selectedIndex, setSelectedIndex] = useState7(0);
4766
- const size = useTerminalSize();
4767
- const timelineWidth = Math.floor(size.columns * 0.65);
4768
- const statusWidth = size.columns - timelineWidth;
4769
- useEffect5(() => {
4770
- if (actions.length > 0) {
4771
- setSelectedIndex(actions.length - 1);
4772
- }
4773
- }, [actions.length]);
4774
- useInput((_, key) => {
4775
- if (!isFocused) {
4776
- return;
4777
- }
4778
- if (key.upArrow) {
4779
- setSelectedIndex((prev) => Math.max(0, prev - 1));
4780
- }
4781
- if (key.downArrow) {
4782
- setSelectedIndex((prev) => Math.min(actions.length - 1, prev + 1));
4783
- }
4784
- if (key.return) {
4785
- const selected = actions[selectedIndex];
4786
- if (selected && (selected.kind === "apply_patch" || selected.kind === "approval" || selected.title === "shell" || selected.title === "code_interpreter")) {
4787
- if (selected.callId) {
4788
- emitToListeners("OpenApprovalViewer", {
4789
- type: selected.kind === "apply_patch" ? "update_file" : selected.title,
4790
- mode: "info",
4791
- callId: selected.callId,
4792
- actionId: selected.id
4793
- });
4794
- }
4795
- }
4796
- }
4797
- });
4798
- const renderOverflowTop = useCallback3((count) => /* @__PURE__ */ jsxs3(Box3, { children: [
4799
- /* @__PURE__ */ jsx7(Text3, { color: "gray", dimColor: true, children: "\u2502" }),
4800
- count > 0 && /* @__PURE__ */ jsxs3(Text3, { dimColor: true, children: [
4801
- " ",
4802
- "\u25B2 ",
4803
- count,
4804
- " more"
4805
- ] })
4806
- ] }), []);
4807
- const renderOverflowBottom = useCallback3((count) => /* @__PURE__ */ jsxs3(Box3, { children: [
4808
- /* @__PURE__ */ jsx7(Text3, { color: "gray", dimColor: true, children: "\u2502" }),
4809
- count > 0 && /* @__PURE__ */ jsxs3(Text3, { dimColor: true, children: [
4810
- " ",
4811
- "\u25BC ",
4812
- count,
4813
- " more"
4814
- ] })
4815
- ] }), []);
4816
- const renderFiller = useCallback3((h) => /* @__PURE__ */ jsx7(Box3, { flexDirection: "column", children: Array.from({ length: h }).map((_, i) => /* @__PURE__ */ jsx7(Box3, { children: /* @__PURE__ */ jsx7(Text3, { color: "gray", dimColor: true, children: "\u2502" }) }, i)) }), []);
4817
- const getItemHeight = useCallback3((_item) => 1, []);
4818
- if (actions.length === 0) {
4819
- return /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", flexGrow: 1, height, children: [
4820
- /* @__PURE__ */ jsx7(Box3, { children: /* @__PURE__ */ jsx7(Text3, { italic: true, color: "gray", children: "No actions yet." }) }),
4821
- Array.from({ length: height - 1 }).map((_, i) => /* @__PURE__ */ jsx7(Box3, { children: /* @__PURE__ */ jsx7(Text3, {}) }, i))
4822
- ] });
4823
- }
4824
- return /* @__PURE__ */ jsx7(Box3, { flexDirection: "column", flexGrow: 1, children: /* @__PURE__ */ jsx7(
4825
- VirtualList,
4826
- {
4827
- items: actions,
4828
- getItemHeight,
4829
- height,
4830
- selectedIndex,
4831
- renderOverflowTop,
4832
- renderOverflowBottom,
4833
- renderFiller,
4834
- renderItem: ({ item, isSelected }) => /* @__PURE__ */ jsx7(ActionItemRow, { item, isSelected, isFocused, width: statusWidth - 2 })
4835
- }
4836
- ) });
4837
- }
4838
-
4839
- // ink/components/CostView.tsx
4840
- import { Box as Box4, Text as Text4 } from "ink";
4841
- import "react";
4842
-
4843
- // ink/contexts/CostContext.tsx
4844
- import { createContext as createContext5, useContext as useContext5, useMemo as useMemo6, useState as useState8 } from "react";
4845
- import { jsx as jsx8 } from "react/jsx-runtime";
4846
- var CostContext = createContext5(void 0);
4847
- var useCost = () => {
4848
- const context = useContext5(CostContext);
4849
- if (!context) {
4850
- throw new Error("useCost must be used within a CostProvider");
4851
- }
4852
- return context;
4853
- };
4854
- var CostProvider = ({
4855
- children
4856
- }) => {
4857
- const [cost, setCost] = useState8({
4858
- totalCost: 0,
4859
- inputTokens: 0,
4860
- outputTokens: 0,
4861
- cachedInputTokens: 0,
4862
- hasUnknownPrices: false
4863
- });
4864
- useListener("UpdateCost", (newCost) => {
4865
- setCost(newCost);
4866
- }, []);
4867
- const value = useMemo6(() => ({
4868
- cost
4869
- }), [cost]);
4870
- return /* @__PURE__ */ jsx8(CostContext.Provider, { value, children });
4871
- };
4872
-
4873
- // ink/components/CostView.tsx
4874
- import { jsx as jsx9, jsxs as jsxs4 } from "react/jsx-runtime";
4875
- function CostView({ isDense = false }) {
4876
- const { cost } = useCost();
4877
- return /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", paddingLeft: 1, paddingTop: isDense ? 0 : 1, children: [
4878
- /* @__PURE__ */ jsx9(Box4, { marginBottom: isDense ? 0 : 1, children: /* @__PURE__ */ jsx9(Text4, { bold: true, underline: true, color: "cyan", children: "Session Cost Breakdown" }) }),
4879
- /* @__PURE__ */ jsxs4(Box4, { children: [
4880
- /* @__PURE__ */ jsx9(Box4, { width: 20, children: /* @__PURE__ */ jsx9(Text4, { children: "Total Cost:" }) }),
4881
- /* @__PURE__ */ jsxs4(Text4, { color: "green", children: [
4882
- "$",
4883
- cost.totalCost.toFixed(4)
4884
- ] }),
4885
- cost.hasUnknownPrices && /* @__PURE__ */ jsx9(Text4, { color: "yellow", children: "(est.)" })
4886
- ] }),
4887
- /* @__PURE__ */ jsxs4(Box4, { children: [
4888
- /* @__PURE__ */ jsx9(Box4, { width: 20, children: /* @__PURE__ */ jsx9(Text4, { children: "Input Tokens:" }) }),
4889
- /* @__PURE__ */ jsx9(Text4, { children: cost.inputTokens.toLocaleString() })
4890
- ] }),
4891
- /* @__PURE__ */ jsxs4(Box4, { children: [
4892
- /* @__PURE__ */ jsx9(Box4, { width: 20, children: /* @__PURE__ */ jsx9(Text4, { children: "Cached Tokens:" }) }),
4893
- /* @__PURE__ */ jsx9(Text4, { color: "gray", children: cost.cachedInputTokens.toLocaleString() })
4894
- ] }),
4895
- /* @__PURE__ */ jsxs4(Box4, { children: [
4896
- /* @__PURE__ */ jsx9(Box4, { width: 20, children: /* @__PURE__ */ jsx9(Text4, { children: "Output Tokens:" }) }),
4897
- /* @__PURE__ */ jsx9(Text4, { children: cost.outputTokens.toLocaleString() })
4898
- ] })
4899
- ] });
4900
- }
4901
-
4902
- // ink/components/MessageLineItem.tsx
4903
- import { Box as Box5, Text as Text5 } from "ink";
4904
- import { memo as memo2 } from "react";
4905
- import { Fragment, jsx as jsx10, jsxs as jsxs5 } from "react/jsx-runtime";
4906
- var MessageLineItem = memo2(
4907
- ({
4908
- item,
4909
- isSelected,
4910
- isFocused,
4911
- indent = 0
4912
- }) => {
4913
- const color = messageTypeToColor(item.type);
4914
- const label = item.type === "interrupted" ? "" : item.type.toUpperCase();
4915
- const selectionColor = isFocused ? "cyan" : "gray";
4916
- return /* @__PURE__ */ jsxs5(Box5, { children: [
4917
- /* @__PURE__ */ jsx10(Text5, { color: isSelected ? selectionColor : "gray", dimColor: !isSelected, bold: isSelected, children: isSelected ? "\u2503 " : "\u2502 " }),
4918
- /* @__PURE__ */ jsx10(Box5, { paddingLeft: indent, children: item.isFirstLine ? /* @__PURE__ */ jsxs5(Text5, { children: [
4919
- label && /* @__PURE__ */ jsxs5(Text5, { bold: true, color, children: [
4920
- label,
4921
- item.type === "tool" ? ": " : item.isArgsLine ? " args: " : ": "
4922
- ] }),
4923
- item.type === "tool" && item.toolName ? /* @__PURE__ */ jsx10(Text5, { children: item.text.startsWith(item.toolName) ? /* @__PURE__ */ jsxs5(Fragment, { children: [
4924
- /* @__PURE__ */ jsx10(Text5, { color: "white", bold: true, children: item.toolName }),
4925
- /* @__PURE__ */ jsx10(Text5, { dimColor: true, italic: true, children: item.text.slice(item.toolName.length) })
4926
- ] }) : /* @__PURE__ */ jsx10(Text5, { dimColor: true, italic: true, children: item.text.startsWith(" ") ? item.text : ` ${item.text}` }) }) : /* @__PURE__ */ jsx10(Text5, { dimColor: !!item.isArgsLine, italic: !!item.isArgsLine, children: item.text })
4927
- ] }) : /* @__PURE__ */ jsx10(Text5, { children: item.type === "tool" && item.toolName ? /* @__PURE__ */ jsx10(Text5, { dimColor: true, italic: true, children: item.text }) : /* @__PURE__ */ jsx10(Text5, { dimColor: !!item.isArgsLine, italic: !!item.isArgsLine, children: item.text }) }) })
4928
- ] });
4929
- }
4930
- );
4931
- function messageTypeToColor(type) {
4932
- switch (type) {
4933
- case "prompt":
4934
- case "user":
4935
- return "green";
4936
- case "agent":
4937
- return "cyan";
4938
- case "interrupted":
4939
- return "gray";
4940
- default:
4941
- return "magenta";
4942
- }
4943
- }
4944
-
4945
- // ink/components/PlanView.tsx
4946
- import { ProgressBar } from "@inkjs/ui";
4947
- import { Box as Box6, Text as Text6 } from "ink";
4948
- import "react";
4949
- import { jsx as jsx11, jsxs as jsxs6 } from "react/jsx-runtime";
4950
- function PlanView() {
4951
- const { planDescription, planItems, progress } = usePlan();
4952
- return /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", flexGrow: 1, children: [
4953
- /* @__PURE__ */ jsx11(Box6, { flexDirection: "column", marginBottom: 1, children: /* @__PURE__ */ jsx11(Text6, { italic: true, children: planDescription }) }),
4954
- /* @__PURE__ */ jsx11(Box6, { flexDirection: "column", flexGrow: 1, children: planItems.map((planItem) => /* @__PURE__ */ jsx11(Box6, { children: /* @__PURE__ */ jsxs6(
4955
- Text6,
4956
- {
4957
- color: planItem.status === "done" ? "green" : planItem.status === "in-progress" ? "yellow" : planItem.status === "not-needed" ? "gray" : "white",
4958
- dimColor: planItem.status === "not-needed",
4959
- children: [
4960
- planItem.status === "done" ? " \u25CF " : planItem.status === "in-progress" ? " \u25B6 " : planItem.status === "not-needed" ? " \u25CC " : " \u25CB ",
4961
- planItem.text
4962
- ]
4963
- }
4964
- ) }, planItem.id)) }),
4965
- /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", marginTop: 1, children: [
4966
- /* @__PURE__ */ jsxs6(Text6, { bold: true, children: [
4967
- "PROGRESS: ",
4968
- progress,
4969
- "%"
4970
- ] }),
4971
- /* @__PURE__ */ jsx11(ProgressBar, { value: progress })
4972
- ] })
4973
- ] });
4974
- }
4975
-
4976
- // ink/components/SettingsView.tsx
4977
- import { Box as Box7, Text as Text7, useInput as useInput2 } from "ink";
4978
- import path9 from "path";
4979
- import { useEffect as useEffect6, useMemo as useMemo8, useState as useState10 } from "react";
4980
-
4981
- // ink/contexts/SettingsContext.tsx
4982
- import { createContext as createContext6, useContext as useContext6, useMemo as useMemo7, useState as useState9 } from "react";
4983
- import { jsx as jsx12 } from "react/jsx-runtime";
4984
- var SettingsContext = createContext6(void 0);
4985
- var useSettings = () => {
4986
- const context = useContext6(SettingsContext);
4987
- if (!context) {
4988
- throw new Error("useSettings must be used within a SettingsProvider");
4989
- }
4990
- return context;
4991
- };
4992
- var SettingsProvider = ({
4993
- children
4994
- }) => {
4995
- const [version, setVersion] = useState9(0);
4996
- useListener("SettingsUpdated", () => {
4997
- setVersion((v) => v + 1);
4998
- }, []);
4999
- const value = useMemo7(() => ({
5000
- version,
5001
- model: trackedState.model,
5002
- compactionModel: trackedState.compactionModel,
5003
- sessionPath: trackedState.sessionPath,
5004
- cwd: trackedState.cwd,
5005
- useFlexTier: trackedState.useFlexTier,
5006
- currentTurn: trackedState.currentTurn,
5007
- maxTurns: trackedState.maxTurns,
5008
- maxCost: trackedState.maxCost,
5009
- autoApproveCodeInterpreter: trackedState.autoApproveCodeInterpreter,
5010
- autoApprovePatches: trackedState.autoApprovePatches,
5011
- autoApproveShell: trackedState.autoApproveShell,
5012
- monitorRateLimits: trackedState.monitorRateLimits,
5013
- rateLimitThreshold: trackedState.rateLimitThreshold,
5014
- rateLimitStatus: rateLimitTracker.getStatus()
5015
- }), [version]);
5016
- return /* @__PURE__ */ jsx12(SettingsContext.Provider, { value, children });
5017
- };
5018
-
5019
- // ink/components/SettingsView.tsx
5020
- import { jsx as jsx13, jsxs as jsxs7 } from "react/jsx-runtime";
5021
- function SettingsView({ isDense = false }) {
5022
- const {
5023
- model,
5024
- compactionModel,
5025
- sessionPath,
5026
- cwd,
5027
- useFlexTier,
5028
- currentTurn,
5029
- maxTurns,
5030
- maxCost,
5031
- autoApproveCodeInterpreter: initialAutoApproveCodeInterpreter,
5032
- autoApprovePatches: initialAutoApprovePatches,
5033
- autoApproveShell: initialAutoApproveShell,
5034
- monitorRateLimits: initialMonitorRateLimits,
5035
- rateLimitThreshold: initialRateLimitThreshold,
5036
- rateLimitStatus
5037
- } = useSettings();
5038
- const { focusedArea } = useChat();
5039
- const [autoApproveCodeInterpreter, setAutoApproveCodeInterpreter] = useState10(initialAutoApproveCodeInterpreter);
5040
- const [autoApprovePatches, setAutoApprovePatches] = useState10(initialAutoApprovePatches);
5041
- const [autoApproveShell, setAutoApproveShell] = useState10(initialAutoApproveShell);
5042
- const [monitorRateLimits, setMonitorRateLimits] = useState10(initialMonitorRateLimits);
5043
- useEffect6(() => {
5044
- setAutoApproveCodeInterpreter(initialAutoApproveCodeInterpreter);
5045
- }, [initialAutoApproveCodeInterpreter]);
5046
- useEffect6(() => {
5047
- setAutoApprovePatches(initialAutoApprovePatches);
5048
- }, [initialAutoApprovePatches]);
5049
- useEffect6(() => {
5050
- setAutoApproveShell(initialAutoApproveShell);
5051
- }, [initialAutoApproveShell]);
5052
- useEffect6(() => {
5053
- setMonitorRateLimits(initialMonitorRateLimits);
5054
- }, [initialMonitorRateLimits]);
5055
- const [selectedIndex, setSelectedIndex] = useState10(0);
5056
- const selectableOptions = useMemo8(() => [
5057
- {
5058
- label: "Code Interpreter",
5059
- value: autoApproveCodeInterpreter,
5060
- envKey: "HARPER_AGENT_AUTO_APPROVE_CODE_INTERPRETER",
5061
- setter: setAutoApproveCodeInterpreter
5062
- },
5063
- {
5064
- label: "File Patches",
5065
- value: autoApprovePatches,
5066
- envKey: "HARPER_AGENT_AUTO_APPROVE_PATCHES",
5067
- setter: setAutoApprovePatches
5068
- },
5069
- {
5070
- label: "Shell Commands",
5071
- value: autoApproveShell,
5072
- envKey: "HARPER_AGENT_AUTO_APPROVE_SHELL",
5073
- setter: setAutoApproveShell
5074
- },
5075
- {
5076
- label: "Monitor Rate Limits",
5077
- value: monitorRateLimits,
5078
- envKey: "HARPER_AGENT_MONITOR_RATE_LIMITS",
5079
- setter: (val) => {
5080
- setMonitorRateLimits(val);
5081
- updateEnv("HARPER_AGENT_MONITOR_RATE_LIMITS", val ? "true" : "false");
5082
- emitToListeners("SettingsUpdated", void 0);
5083
- }
5084
- },
5085
- {
5086
- label: "<edit settings>",
5087
- isAction: true,
5088
- action: bootstrapConfig
5089
- }
5090
- ], [autoApproveCodeInterpreter, autoApprovePatches, autoApproveShell, monitorRateLimits]);
5091
- useInput2((_input, key) => {
5092
- if (focusedArea !== "status") {
5093
- return;
5094
- }
5095
- if (key.upArrow) {
5096
- setSelectedIndex((prev) => prev > 0 ? prev - 1 : selectableOptions.length - 1);
5097
- }
5098
- if (key.downArrow) {
5099
- setSelectedIndex((prev) => prev < selectableOptions.length - 1 ? prev + 1 : 0);
5100
- }
5101
- if (key.return || _input === " ") {
5102
- const selected = selectableOptions[selectedIndex];
5103
- if (!selected) {
5104
- return;
5105
- }
5106
- if ("isAction" in selected && selected.isAction) {
5107
- selected.action();
5108
- } else if ("setter" in selected) {
5109
- const newValue = !selected.value;
5110
- selected.setter(newValue);
5111
- updateEnv(selected.envKey, newValue ? "1" : "0");
5112
- emitToListeners("SettingsUpdated", void 0);
5113
- }
5114
- }
5115
- });
5116
- const displayPath = useMemo8(() => {
5117
- if (!sessionPath) {
5118
- return null;
5119
- }
5120
- const relative = path9.relative(cwd, sessionPath);
5121
- if (!relative.startsWith("..") && !path9.isAbsolute(relative)) {
5122
- return `./${relative}`;
5123
- }
5124
- return sessionPath;
5125
- }, [cwd, sessionPath]);
5126
- const marginBottom = isDense ? 0 : 1;
5127
- return /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", flexGrow: 1, paddingLeft: 1, children: [
5128
- /* @__PURE__ */ jsx13(Box7, { marginBottom, children: /* @__PURE__ */ jsx13(Text7, { bold: true, underline: true, color: "cyan", children: "Configuration" }) }),
5129
- /* @__PURE__ */ jsxs7(Box7, { marginBottom, children: [
5130
- /* @__PURE__ */ jsx13(Box7, { width: 20, children: /* @__PURE__ */ jsx13(Text7, { children: "Model:" }) }),
5131
- /* @__PURE__ */ jsx13(Text7, { children: model })
5132
- ] }),
5133
- /* @__PURE__ */ jsxs7(Box7, { marginBottom, children: [
5134
- /* @__PURE__ */ jsx13(Box7, { width: 20, children: /* @__PURE__ */ jsx13(Text7, { children: "Compaction Model:" }) }),
5135
- /* @__PURE__ */ jsx13(Text7, { children: compactionModel })
5136
- ] }),
5137
- displayPath && /* @__PURE__ */ jsxs7(Box7, { marginBottom, children: [
5138
- /* @__PURE__ */ jsx13(Box7, { width: 20, children: /* @__PURE__ */ jsx13(Text7, { children: "Session Path:" }) }),
5139
- /* @__PURE__ */ jsx13(Text7, { children: displayPath })
5140
- ] }),
5141
- /* @__PURE__ */ jsxs7(Box7, { marginBottom, children: [
5142
- /* @__PURE__ */ jsx13(Box7, { width: 20, children: /* @__PURE__ */ jsx13(Text7, { children: "Flex Tier:" }) }),
5143
- /* @__PURE__ */ jsx13(Text7, { children: useFlexTier ? "Yes" : "No" })
5144
- ] }),
5145
- /* @__PURE__ */ jsxs7(Box7, { marginBottom, children: [
5146
- /* @__PURE__ */ jsx13(Box7, { width: 20, children: /* @__PURE__ */ jsx13(Text7, { children: "Current Turn:" }) }),
5147
- /* @__PURE__ */ jsxs7(Text7, { children: [
5148
- currentTurn,
5149
- " / ",
5150
- maxTurns
5151
- ] })
5152
- ] }),
5153
- /* @__PURE__ */ jsxs7(Box7, { marginBottom, children: [
5154
- /* @__PURE__ */ jsx13(Box7, { width: 20, children: /* @__PURE__ */ jsx13(Text7, { children: "Rate Limit @:" }) }),
5155
- /* @__PURE__ */ jsxs7(Text7, { children: [
5156
- initialRateLimitThreshold,
5157
- "%"
5158
- ] })
5159
- ] }),
5160
- rateLimitStatus && /* @__PURE__ */ jsxs7(Box7, { marginBottom, flexDirection: "column", children: [
5161
- rateLimitStatus.retryAfter !== null && /* @__PURE__ */ jsxs7(Box7, { children: [
5162
- /* @__PURE__ */ jsx13(Box7, { width: 20, children: /* @__PURE__ */ jsx13(Text7, { color: "red", children: "Retry After:" }) }),
5163
- /* @__PURE__ */ jsxs7(Text7, { color: "red", children: [
5164
- rateLimitStatus.retryAfter,
5165
- "s"
5166
- ] })
5167
- ] }),
5168
- (rateLimitStatus.limitRequests !== null || rateLimitStatus.remainingRequests !== null) && /* @__PURE__ */ jsxs7(Box7, { children: [
5169
- /* @__PURE__ */ jsx13(Box7, { width: 20, children: /* @__PURE__ */ jsx13(Text7, { children: "RPM Limit:" }) }),
5170
- /* @__PURE__ */ jsxs7(Text7, { children: [
5171
- rateLimitStatus.remainingRequests ?? "?",
5172
- " / ",
5173
- rateLimitStatus.limitRequests ?? "?",
5174
- " (Reset:",
5175
- " ",
5176
- rateLimitStatus.resetRequests ?? "?",
5177
- ")"
5178
- ] })
5179
- ] }),
5180
- rateLimitStatus.remainingTokens !== null && /* @__PURE__ */ jsxs7(Box7, { children: [
5181
- /* @__PURE__ */ jsx13(Box7, { width: 20, children: /* @__PURE__ */ jsx13(Text7, { children: "TPM Limit:" }) }),
5182
- /* @__PURE__ */ jsxs7(Text7, { children: [
5183
- rateLimitStatus.remainingTokens ?? "?",
5184
- " / ",
5185
- rateLimitStatus.limitTokens ?? "?",
5186
- " (Reset:",
5187
- " ",
5188
- rateLimitStatus.resetTokens ?? "?",
5189
- ")"
5190
- ] })
5191
- ] })
5192
- ] }),
5193
- maxCost !== null && /* @__PURE__ */ jsxs7(Box7, { marginBottom, children: [
5194
- /* @__PURE__ */ jsx13(Box7, { width: 20, children: /* @__PURE__ */ jsx13(Text7, { children: "Max Cost:" }) }),
5195
- /* @__PURE__ */ jsxs7(Text7, { children: [
5196
- "$",
5197
- maxCost.toFixed(2)
5198
- ] })
5199
- ] }),
5200
- /* @__PURE__ */ jsxs7(Box7, { marginTop: 1, flexDirection: "column", children: [
5201
- /* @__PURE__ */ jsx13(Text7, { bold: true, children: "Auto-approvals (up/down & space to toggle):" }),
5202
- selectableOptions.map((option, index) => {
5203
- const isSelected = index === selectedIndex && focusedArea === "status";
5204
- return /* @__PURE__ */ jsx13(Box7, { children: /* @__PURE__ */ jsxs7(Text7, { color: isSelected ? "cyan" : "white", children: [
5205
- isSelected ? "> " : " ",
5206
- option.label,
5207
- "isAction" in option ? "" : `: ${option.value ? "ON" : "OFF"}`
5208
- ] }) }, option.label);
5209
- })
5210
- ] })
5211
- ] });
5212
- }
5213
-
5214
- // ink/components/UserInput.tsx
5215
- import { Box as Box9, Text as Text9 } from "ink";
5216
- import { exec as exec4 } from "child_process";
5217
- import { promisify as promisify13 } from "util";
5218
- import { useCallback as useCallback4, useState as useState12 } from "react";
5219
-
5220
- // ink/components/BlinkingTextInput.tsx
5221
- import chalk3 from "chalk";
5222
- import { Box as Box8, Text as Text8, useInput as useInput3 } from "ink";
5223
- import { useEffect as useEffect7, useMemo as useMemo9, useReducer, useState as useState11 } from "react";
5224
- import { jsx as jsx14 } from "react/jsx-runtime";
5225
- var reducer = (state, action) => {
5226
- switch (action.type) {
5227
- case "move-cursor-left":
5228
- return {
5229
- ...state,
5230
- cursorOffset: Math.max(0, state.cursorOffset - 1)
5231
- };
5232
- case "move-cursor-right":
5233
- return {
5234
- ...state,
5235
- cursorOffset: Math.min(state.value.length, state.cursorOffset + 1)
5236
- };
5237
- case "insert":
5238
- return {
5239
- ...state,
5240
- previousValue: state.value,
5241
- value: state.value.slice(0, state.cursorOffset) + action.text + state.value.slice(state.cursorOffset),
5242
- cursorOffset: state.cursorOffset + action.text.length
5243
- };
5244
- case "newline":
5245
- return {
5246
- ...state,
5247
- previousValue: state.value,
5248
- value: state.value.slice(0, state.cursorOffset) + "\n" + state.value.slice(state.cursorOffset),
5249
- cursorOffset: state.cursorOffset + 1
5250
- };
5251
- case "delete": {
5252
- const newCursorOffset = Math.max(0, state.cursorOffset - 1);
5253
- return {
5254
- ...state,
5255
- previousValue: state.value,
5256
- value: state.value.slice(0, newCursorOffset) + state.value.slice(state.cursorOffset),
5257
- cursorOffset: newCursorOffset
5258
- };
5259
- }
5260
- default:
5261
- return state;
5262
- }
5263
- };
5264
- function BlinkingTextInput({
5265
- isDisabled = false,
5266
- defaultValue = "",
5267
- placeholder = "",
5268
- suggestions = [],
5269
- onChange,
5270
- onSubmit
5271
- }) {
5272
- const [state, dispatch] = useReducer(reducer, {
5273
- value: defaultValue,
5274
- previousValue: defaultValue,
5275
- cursorOffset: defaultValue.length
5276
- });
5277
- const [isCursorVisible, setIsCursorVisible] = useState11(true);
5278
- useEffect7(() => {
5279
- if (isDisabled) {
5280
- setIsCursorVisible(false);
5281
- return;
5282
- }
5283
- const interval = setInterval(() => {
5284
- setIsCursorVisible((prev) => !prev);
5285
- }, 500);
5286
- return () => clearInterval(interval);
5287
- }, [isDisabled]);
5288
- useEffect7(() => {
5289
- if (state.value !== state.previousValue) {
5290
- onChange?.(state.value);
5291
- }
5292
- }, [state.value, state.previousValue, onChange]);
5293
- const suggestion = useMemo9(() => {
5294
- if (state.value.length === 0) {
5295
- return "";
5296
- }
5297
- return suggestions.find((s) => s.startsWith(state.value))?.slice(state.value.length) || "";
5298
- }, [state.value, suggestions]);
5299
- useInput3(
5300
- (input, key) => {
5301
- if (key.upArrow || key.downArrow || key.ctrl && input === "c" || key.tab || key.shift && key.tab) {
5302
- return;
5303
- }
5304
- setIsCursorVisible(true);
5305
- if (key.return) {
5306
- if (key.meta) {
5307
- dispatch({ type: "newline" });
5308
- return;
5309
- }
5310
- if (suggestion) {
5311
- onSubmit?.(state.value + suggestion);
5312
- } else {
5313
- onSubmit?.(state.value);
5314
- }
5315
- return;
5316
- }
5317
- if (key.leftArrow) {
5318
- dispatch({ type: "move-cursor-left" });
5319
- } else if (key.rightArrow) {
5320
- dispatch({ type: "move-cursor-right" });
5321
- } else if (key.backspace || key.delete) {
5322
- dispatch({ type: "delete" });
5323
- } else if (input) {
5324
- dispatch({ type: "insert", text: input });
5325
- }
5326
- },
5327
- { isActive: !isDisabled }
5328
- );
5329
- const renderedValue = useMemo9(() => {
5330
- if (isDisabled) {
5331
- const lines2 = (state.value || (placeholder ? chalk3.dim(placeholder) : "")).split("\n");
5332
- return /* @__PURE__ */ jsx14(Box8, { flexDirection: "column", children: lines2.map((line, i) => /* @__PURE__ */ jsx14(Box8, { children: /* @__PURE__ */ jsx14(Text8, { children: line }) }, i)) });
5333
- }
5334
- if (state.value.length === 0) {
5335
- let displayContent = "";
5336
- if (placeholder && placeholder.length > 0) {
5337
- displayContent = (isCursorVisible ? chalk3.inverse(placeholder[0]) : placeholder[0]) + chalk3.dim(placeholder.slice(1));
5338
- } else {
5339
- displayContent = isCursorVisible ? chalk3.inverse(" ") : " ";
5340
- }
5341
- return /* @__PURE__ */ jsx14(Box8, { flexGrow: 1, minWidth: 1, children: /* @__PURE__ */ jsx14(Text8, { wrap: "end", children: displayContent }) });
5342
- }
5343
- const lines = state.value.split("\n");
5344
- const result = [];
5345
- let totalOffset = 0;
5346
- lines.forEach((line, lineIndex) => {
5347
- let lineContent = "";
5348
- for (let i = 0; i < line.length; i++) {
5349
- const char = line[i];
5350
- if (totalOffset === state.cursorOffset) {
5351
- lineContent += isCursorVisible ? chalk3.inverse(char) : char;
5352
- } else {
5353
- lineContent += char;
5354
- }
5355
- totalOffset++;
5356
- }
5357
- if (totalOffset === state.cursorOffset) {
5358
- if (lineIndex === lines.length - 1 && suggestion) {
5359
- lineContent += (isCursorVisible ? chalk3.inverse(suggestion[0]) : suggestion[0]) + chalk3.dim(suggestion.slice(1));
5360
- } else {
5361
- lineContent += isCursorVisible ? chalk3.inverse(" ") : " ";
5362
- }
5363
- } else if (lineIndex === lines.length - 1 && suggestion) {
5364
- lineContent += chalk3.dim(suggestion);
5365
- }
5366
- result.push(
5367
- /* @__PURE__ */ jsx14(Box8, { flexGrow: 1, children: /* @__PURE__ */ jsx14(Text8, { wrap: "end", children: lineContent }) }, lineIndex)
5368
- );
5369
- if (lineIndex < lines.length - 1) {
5370
- totalOffset++;
5371
- }
5372
- });
5373
- return /* @__PURE__ */ jsx14(Box8, { flexDirection: "column", flexGrow: 1, children: result });
5374
- }, [state.value, state.cursorOffset, suggestion, isCursorVisible, isDisabled, placeholder]);
5375
- return /* @__PURE__ */ jsx14(Box8, { flexGrow: 1, minWidth: 1, children: renderedValue });
5376
- }
5377
-
5378
- // ink/components/UserInput.tsx
5379
- import { jsx as jsx15, jsxs as jsxs8 } from "react/jsx-runtime";
5380
- var execAsync4 = promisify13(exec4);
5381
- var modeSuggestion = {
5382
- approved: ["/clear", "/skills", "/exit"],
5383
- approving: ["yes", "approve", "no", "deny"],
5384
- denied: [],
5385
- waiting: ["/clear", "/skills", "/exit"]
5386
- };
5387
- function UserInput() {
5388
- const { userInputMode, focusedArea } = useChat();
5389
- const [resetKey, setResetKey] = useState12(0);
5390
- const [, setBlankLines] = useState12(0);
5391
- useListener("ClearUserInput", () => {
5392
- setResetKey((prev) => prev + 1);
5393
- }, []);
5394
- const borderColor = focusedArea === "input" ? "cyan" : "gray";
5395
- const placeholder = calculatePlaceholder(userInputMode);
5396
- const onSubmitResetKey = useCallback4(async (value) => {
5397
- if (value.length) {
5398
- const trimmedValue = value.trim();
5399
- if (trimmedValue === "/clear") {
5400
- setResetKey((prev) => prev + 1);
5401
- emitToListeners("ClearChatHistory", void 0);
5402
- return;
5403
- }
5404
- if (trimmedValue === "/exit" || trimmedValue === "exit") {
5405
- await handleExit();
5406
- return;
5407
- }
5408
- if (trimmedValue === "/skills") {
5409
- setResetKey((prev) => prev + 1);
5410
- emitToListeners("PushNewMessages", [{
5411
- type: "user",
5412
- text: "/skills",
5413
- version: 1
5414
- }, {
5415
- type: "agent",
5416
- text: "Installing skills...",
5417
- version: 1
5418
- }]);
5419
- try {
5420
- const { stdout, stderr } = await execAsync4("npx skills add harperfast/skills");
5421
- emitToListeners(
5422
- "UpdateLastMessageText",
5423
- `
5424
-
5425
- Skills installation result:
5426
- ${stdout}${stderr ? `
5427
- Errors:
5428
- ${stderr}` : ""}`
5429
- );
5430
- } catch (error) {
5431
- emitToListeners("UpdateLastMessageText", `
5432
-
5433
- Failed to install skills: ${error.message}`);
5434
- }
5435
- return;
5436
- }
5437
- setResetKey((prev) => prev + 1);
5438
- emitToListeners("PushNewMessages", [{ type: "user", text: trimmedValue, version: 1 }]);
5439
- setBlankLines(0);
5440
- } else {
5441
- setBlankLines((value2) => {
5442
- value2 += 1;
5443
- if (value2 === 2) {
5444
- void handleExit();
5445
- }
5446
- return value2;
5447
- });
5448
- }
5449
- }, []);
5450
- return /* @__PURE__ */ jsxs8(
5451
- Box9,
5452
- {
5453
- minHeight: footerHeight,
5454
- borderStyle: "bold",
5455
- borderTop: false,
5456
- borderColor,
5457
- flexDirection: "row",
5458
- alignItems: "flex-start",
5459
- gap: 1,
5460
- children: [
5461
- /* @__PURE__ */ jsx15(Box9, { marginLeft: 1, height: 1, children: /* @__PURE__ */ jsx15(Text9, { bold: true, color: borderColor, children: "\u276F" }) }),
5462
- /* @__PURE__ */ jsx15(Box9, { flexGrow: 1, children: /* @__PURE__ */ jsx15(
5463
- BlinkingTextInput,
5464
- {
5465
- placeholder,
5466
- onSubmit: onSubmitResetKey,
5467
- suggestions: modeSuggestion[userInputMode],
5468
- isDisabled: focusedArea !== "input"
5469
- },
5470
- resetKey
5471
- ) })
5472
- ]
5473
- }
5474
- );
5475
- }
5476
- function calculatePlaceholder(mode) {
5477
- const prefix = " ";
5478
- switch (mode) {
5479
- case "denied":
5480
- return prefix + "OK! Let's find another way.";
5481
- case "approving":
5482
- return prefix + "Do you approve? yes / no";
5483
- default:
5484
- case "approved":
5485
- case "waiting":
5486
- return "";
5487
- }
5488
- }
5489
-
5490
- // ink/components/ChatContent.tsx
5491
- import { jsx as jsx16, jsxs as jsxs9 } from "react/jsx-runtime";
5492
- function ChatContent() {
5493
- const { messages, isThinking, isCompacting, pullingState, focusedArea, setFocusedArea } = useChat();
5494
- const { payload } = useApproval();
5495
- const size = useTerminalSize();
5496
- useMessageListener();
5497
- const [activeTab, setActiveTab] = useState13("settings");
5498
- const [userHasSwitchedTab, setUserHasSwitchedTab] = useState13(false);
5499
- const { planDescription, planItems } = usePlan();
5500
- useEffect8(() => {
5501
- if (!userHasSwitchedTab && activeTab === "settings") {
5502
- const hasPlan = planDescription.trim().length > 0 || planItems.length > 0;
5503
- if (hasPlan) {
5504
- setActiveTab("planDescription");
5505
- }
5506
- }
5507
- }, [planDescription, planItems, userHasSwitchedTab, activeTab]);
5508
- const [selectedIndex, setSelectedIndex] = useState13(0);
5509
- const wrapCache = useRef2(
5510
- /* @__PURE__ */ new Map()
5511
- );
5512
- useInput4((input, key) => {
5513
- if (key.tab) {
5514
- const focusOrder = ["input", "timeline", "status"];
5515
- const currentIndex = focusOrder.indexOf(focusedArea);
5516
- if (key.shift || input === "\x1B[Z") {
5517
- const nextIndex = (currentIndex - 1 + focusOrder.length) % focusOrder.length;
5518
- setFocusedArea(focusOrder[nextIndex]);
5519
- } else {
5520
- const nextIndex = (currentIndex + 1) % focusOrder.length;
5521
- setFocusedArea(focusOrder[nextIndex]);
5522
- }
5523
- return;
5524
- }
5525
- if (focusedArea === "timeline") {
5526
- if (key.upArrow) {
5527
- setSelectedIndex((prev) => Math.max(0, prev - 1));
5528
- }
5529
- if (key.downArrow) {
5530
- setSelectedIndex((prev) => Math.min(Math.max(0, lineItems.length - 1), prev + 1));
5531
- }
5532
- if (key.return) {
5533
- const selected = lineItems[selectedIndex];
5534
- if (selected && selected.type === "tool" && (selected.toolName === "apply_patch" || selected.toolName === "code_interpreter" || selected.toolName === "shell")) {
5535
- const msg = messages.find((m) => m.id === selected.messageId);
5536
- if (msg && msg.callId) {
5537
- emitToListeners("OpenApprovalViewer", {
5538
- type: selected.toolName,
5539
- mode: "info",
5540
- callId: msg.callId
5541
- });
5542
- }
5543
- }
5544
- }
5545
- }
5546
- if (focusedArea === "status") {
5547
- if (key.leftArrow || key.rightArrow) {
5548
- const tabNames = ["settings", "planDescription", "actions"];
5549
- const currentIndex = tabNames.indexOf(activeTab);
5550
- if (key.leftArrow) {
5551
- const nextIndex = (currentIndex - 1 + tabNames.length) % tabNames.length;
5552
- const newTab = tabNames[nextIndex];
5553
- setActiveTab(newTab);
5554
- setUserHasSwitchedTab(true);
5555
- } else {
5556
- const nextIndex = (currentIndex + 1) % tabNames.length;
5557
- const newTab = tabNames[nextIndex];
5558
- setActiveTab(newTab);
5559
- setUserHasSwitchedTab(true);
5560
- }
5561
- }
5562
- }
5563
- if (key.ctrl && input === "x") {
5564
- void handleExit();
5565
- }
5566
- if (key.escape && isThinking && focusedArea === "input" && !payload) {
5567
- emitToListeners("InterruptThought", void 0);
5568
- }
5569
- });
5570
- const contentHeight = size.rows - footerHeight;
5571
- const timelineWidth = Math.floor(size.columns * 0.65);
5572
- const statusWidth = size.columns - timelineWidth;
5573
- const labelWidthFor = useCallback5((type) => {
5574
- if (type === "agent") {
5575
- return 7;
5576
- }
5577
- if (type === "prompt") {
5578
- return 7;
5579
- }
5580
- if (type === "user") {
5581
- return 6;
5582
- }
5583
- if (type === "interrupted") {
5584
- return 0;
5585
- }
5586
- return 6;
5587
- }, []);
5588
- const availableTextWidth = timelineWidth - 4;
5589
- const pullingHeight = pullingState ? 1 : 0;
5590
- const lineItems = useMemo10(() => {
5591
- const acc = [];
5592
- for (const msg of messages) {
5593
- const labelWidth = labelWidthFor(msg.type);
5594
- const firstLineWidth = Math.max(1, availableTextWidth - labelWidth);
5595
- let entry = wrapCache.current.get(msg.id);
5596
- if (!entry || entry.version !== msg.version || entry.width !== firstLineWidth) {
5597
- const textLines = wrapText(msg.text ?? "", firstLineWidth);
5598
- const argsLines = msg.args ? wrapText(String(msg.args), firstLineWidth) : [];
5599
- const msgLineItems = [];
5600
- if (msg.type === "tool") {
5601
- const toolName = msg.text ?? "";
5602
- const toolArgs = msg.args ?? "";
5603
- const toolNameWithSpace = `${toolName} `;
5604
- const availableForArgs = firstLineWidth - toolNameWithSpace.length;
5605
- let displayedArgs = toolArgs;
5606
- if (availableForArgs < toolArgs.length) {
5607
- if (availableForArgs > 3) {
5608
- displayedArgs = toolArgs.slice(0, availableForArgs - 3) + "...";
5609
- } else {
5610
- displayedArgs = toolArgs.slice(0, Math.max(0, availableForArgs));
5611
- }
5612
- }
5613
- msgLineItems.push({
5614
- key: `${msg.id}:tool:0`,
5615
- messageId: msg.id,
5616
- type: msg.type,
5617
- text: `${toolNameWithSpace}${displayedArgs}`,
5618
- isFirstLine: true,
5619
- toolName
5620
- });
5621
- } else {
5622
- textLines.forEach((txt, idx) => {
5623
- msgLineItems.push({
5624
- key: `${msg.id}:text:${idx}`,
5625
- messageId: msg.id,
5626
- type: msg.type,
5627
- text: txt,
5628
- isFirstLine: idx === 0
5629
- });
5630
- });
5631
- if (argsLines.length > 0) {
5632
- argsLines.forEach((txt, idx) => {
5633
- msgLineItems.push({
5634
- key: `${msg.id}:args:${idx}`,
5635
- messageId: msg.id,
5636
- type: msg.type,
5637
- text: txt,
5638
- isFirstLine: idx === 0 && textLines.length === 0,
5639
- isArgsLine: true
5640
- });
5641
- });
5642
- }
5643
- }
5644
- entry = { version: msg.version, width: firstLineWidth, lineItems: msgLineItems };
5645
- wrapCache.current.set(msg.id, entry);
5646
- }
5647
- acc.push(...entry.lineItems);
5648
- }
5649
- if (wrapCache.current.size > messages.length * 2) {
5650
- const messageIds = new Set(messages.map((m) => m.id));
5651
- for (const id of wrapCache.current.keys()) {
5652
- if (!messageIds.has(id)) {
5653
- wrapCache.current.delete(id);
5654
- }
5655
- }
5656
- }
5657
- return acc;
5658
- }, [messages, availableTextWidth, labelWidthFor]);
5659
- useEffect8(() => {
5660
- if (lineItems.length > 0 && focusedArea !== "timeline") {
5661
- setSelectedIndex(lineItems.length - 1);
5662
- }
5663
- }, [lineItems.length, focusedArea]);
5664
- useEffect8(() => {
5665
- if (lineItems.length > 0) {
5666
- setSelectedIndex(lineItems.length - 1);
5667
- }
5668
- }, [size.rows, size.columns]);
5669
- const tabs = useMemo10(() => [
5670
- { name: "settings", label: "SETTINGS" },
5671
- { name: "planDescription", label: "PLAN" },
5672
- { name: "actions", label: "ACTIONS" }
5673
- ], []);
5674
- const timelineTitle = "TIMELINE:";
5675
- const timelineHeaderWidth = timelineWidth - 1;
5676
- const showSpinner = isCompacting || isThinking || Boolean(pullingState);
5677
- const timelineDashes = timelineHeaderWidth - timelineTitle.length - (showSpinner ? 5 : 0);
5678
- const tabsTotalWidth = tabs.reduce((acc, t) => acc + t.label.length + 2, 0) + (tabs.length - 1);
5679
- const statusDashes = Math.max(0, statusWidth - tabsTotalWidth - 2);
5680
- const timelineColor = focusedArea === "timeline" ? "cyan" : "gray";
5681
- const statusColor = focusedArea === "status" ? "cyan" : "gray";
5682
- const dividerColor = focusedArea === "timeline" || focusedArea === "status" ? "cyan" : "gray";
5683
- const timelineBottomColor = focusedArea === "timeline" || focusedArea === "input" ? "cyan" : "gray";
5684
- const statusBottomColor = focusedArea === "status" || focusedArea === "input" ? "cyan" : "gray";
5685
- const junctionLeftColor = focusedArea === "timeline" || focusedArea === "input" ? "cyan" : "gray";
5686
- const junctionMiddleColor = focusedArea === "timeline" || focusedArea === "status" || focusedArea === "input" ? "cyan" : "gray";
5687
- const junctionRightColor = focusedArea === "status" || focusedArea === "input" ? "cyan" : "gray";
5688
- const timelinePipeFiller = useCallback5((count) => /* @__PURE__ */ jsx16(Box10, { flexDirection: "column", children: Array.from({ length: count }).map((_, i) => /* @__PURE__ */ jsx16(Box10, { children: /* @__PURE__ */ jsx16(Text10, { color: "gray", dimColor: true, children: "\u2502" }) }, i)) }), []);
5689
- return /* @__PURE__ */ jsxs9(Box10, { flexDirection: "column", height: size.rows, padding: 0, children: [
5690
- /* @__PURE__ */ jsxs9(Box10, { flexDirection: "row", height: 1, children: [
5691
- /* @__PURE__ */ jsx16(Text10, { color: timelineColor, children: "\u256D" }),
5692
- /* @__PURE__ */ jsx16(Text10, { bold: true, color: timelineColor, children: timelineTitle }),
5693
- showSpinner && /* @__PURE__ */ jsx16(Box10, { paddingLeft: 2, paddingRight: 1, children: /* @__PURE__ */ jsx16(Spinner2, { type: "clock" }) }),
5694
- /* @__PURE__ */ jsx16(Text10, { color: timelineColor, children: "\u2500".repeat(Math.max(0, timelineDashes)) }),
5695
- /* @__PURE__ */ jsx16(Text10, { color: dividerColor, children: "\u252C" }),
5696
- tabs.map((tab, i) => /* @__PURE__ */ jsxs9(React11.Fragment, { children: [
5697
- /* @__PURE__ */ jsx16(
5698
- Text10,
5699
- {
5700
- color: activeTab === tab.name ? "black" : statusColor,
5701
- backgroundColor: activeTab === tab.name ? statusColor : "",
5702
- bold: activeTab === tab.name,
5703
- children: ` ${tab.label} `
5704
- }
5705
- ),
5706
- i < tabs.length - 1 && /* @__PURE__ */ jsx16(Text10, { color: statusColor, children: "|" })
5707
- ] }, tab.name)),
5708
- /* @__PURE__ */ jsxs9(Text10, { color: statusColor, children: [
5709
- "\u2500".repeat(statusDashes),
5710
- "\u256E"
5711
- ] })
5712
- ] }),
5713
- /* @__PURE__ */ jsxs9(Box10, { flexDirection: "row", height: contentHeight - 2 - pullingHeight, children: [
5714
- /* @__PURE__ */ jsx16(
5715
- Box10,
5716
- {
5717
- flexDirection: "column",
5718
- width: timelineWidth,
5719
- paddingLeft: 0,
5720
- paddingRight: 1,
5721
- children: /* @__PURE__ */ jsx16(Box10, { flexDirection: "column", flexGrow: 1, children: /* @__PURE__ */ jsx16(
5722
- VirtualList,
5723
- {
5724
- items: lineItems,
5725
- itemHeight: 1,
5726
- height: contentHeight - 2 - pullingHeight,
5727
- selectedIndex,
5728
- renderOverflowTop: useCallback5((count) => /* @__PURE__ */ jsxs9(Box10, { children: [
5729
- /* @__PURE__ */ jsx16(Text10, { color: "gray", dimColor: true, children: "\u2502" }),
5730
- count > 0 && /* @__PURE__ */ jsxs9(Text10, { dimColor: true, children: [
5731
- " ",
5732
- "\u25B2 ",
5733
- count,
5734
- " more"
5735
- ] })
5736
- ] }), []),
5737
- renderOverflowBottom: useCallback5((count) => /* @__PURE__ */ jsxs9(Box10, { children: [
5738
- /* @__PURE__ */ jsx16(Text10, { color: "gray", dimColor: true, children: "\u2502" }),
5739
- count > 0 && /* @__PURE__ */ jsxs9(Text10, { dimColor: true, children: [
5740
- " ",
5741
- "\u25BC ",
5742
- count,
5743
- " more"
5744
- ] })
5745
- ] }), []),
5746
- renderFiller: timelinePipeFiller,
5747
- keyExtractor: (it) => it.key,
5748
- renderItem: useCallback5(
5749
- ({ item, isSelected }) => /* @__PURE__ */ jsx16(
5750
- MessageLineItem,
5751
- {
5752
- item,
5753
- isSelected,
5754
- isFocused: focusedArea === "timeline",
5755
- indent: item.isFirstLine ? 0 : labelWidthFor(item.type)
5756
- }
5757
- ),
5758
- [labelWidthFor, focusedArea]
5759
- )
5760
- }
5761
- ) })
5762
- }
5763
- ),
5764
- activeTab !== "shell" && activeTab !== "actions" && /* @__PURE__ */ jsx16(
5765
- Box10,
5766
- {
5767
- flexDirection: "column",
5768
- width: 1,
5769
- borderStyle: "round",
5770
- borderColor: dividerColor,
5771
- borderTop: false,
5772
- borderBottom: false,
5773
- borderRight: false
5774
- }
5775
- ),
5776
- /* @__PURE__ */ jsx16(
5777
- Box10,
5778
- {
5779
- flexDirection: "column",
5780
- width: activeTab === "shell" || activeTab === "actions" ? statusWidth : statusWidth - 1,
5781
- borderStyle: "round",
5782
- borderColor: statusColor,
5783
- borderTop: false,
5784
- borderBottom: false,
5785
- borderLeft: false,
5786
- paddingLeft: activeTab === "shell" || activeTab === "actions" ? 0 : 1,
5787
- paddingRight: 1,
5788
- children: /* @__PURE__ */ jsxs9(Box10, { flexDirection: "column", flexGrow: 1, marginTop: 0, children: [
5789
- activeTab === "settings" && /* @__PURE__ */ jsxs9(Box10, { flexDirection: "column", children: [
5790
- /* @__PURE__ */ jsx16(CostView, { isDense: true }),
5791
- /* @__PURE__ */ jsx16(Box10, { marginTop: 1, children: /* @__PURE__ */ jsx16(SettingsView, { isDense: true }) })
5792
- ] }),
5793
- activeTab === "planDescription" && /* @__PURE__ */ jsx16(PlanView, {}),
5794
- activeTab === "actions" && /* @__PURE__ */ jsx16(
5795
- ActionsView,
5796
- {
5797
- height: contentHeight - 2 - pullingHeight,
5798
- isFocused: focusedArea === "status"
5799
- }
5800
- )
5801
- ] })
5802
- }
5803
- )
5804
- ] }),
5805
- /* @__PURE__ */ jsxs9(Box10, { flexDirection: "column", children: [
5806
- pullingState && /* @__PURE__ */ jsx16(Box10, { paddingLeft: 2, paddingRight: 2, marginBottom: 0, children: /* @__PURE__ */ jsxs9(Text10, { color: "yellow", children: [
5807
- `\uF019 Downloading `,
5808
- /* @__PURE__ */ jsx16(Text10, { bold: true, children: pullingState.modelName }),
5809
- ` from Ollama... `,
5810
- /* @__PURE__ */ jsx16(Text10, { dimColor: true, children: pullingState.status === "pulling manifest" ? "initializing" : pullingState.status }),
5811
- pullingState.total > 0 && /* @__PURE__ */ jsxs9(Text10, { children: [
5812
- ` [${"=".repeat(Math.floor(pullingState.completed / pullingState.total * 20))}${" ".repeat(20 - Math.floor(pullingState.completed / pullingState.total * 20))}] `,
5813
- Math.round(pullingState.completed / pullingState.total * 100),
5814
- "%"
5815
- ] })
5816
- ] }) }),
5817
- /* @__PURE__ */ jsxs9(Box10, { flexDirection: "row", height: 1, children: [
5818
- /* @__PURE__ */ jsx16(Text10, { color: junctionLeftColor, children: "\u2522" }),
5819
- /* @__PURE__ */ jsx16(Text10, { color: timelineBottomColor, children: "\u2501".repeat(timelineWidth - 1) }),
5820
- /* @__PURE__ */ jsx16(Text10, { color: junctionMiddleColor, children: "\u2537" }),
5821
- /* @__PURE__ */ jsx16(Text10, { color: statusBottomColor, children: "\u2501".repeat(Math.max(0, statusWidth - 2)) }),
5822
- /* @__PURE__ */ jsx16(Text10, { color: junctionRightColor, children: "\u252A" })
5823
- ] })
5824
- ] }),
5825
- /* @__PURE__ */ jsx16(UserInput, {})
5826
- ] });
5827
- }
5828
-
5829
- // ink/components/DiffApprovalView.tsx
5830
- import { Box as Box11, Text as Text11, useInput as useInput5 } from "ink";
5831
- import { useMemo as useMemo11, useState as useState14 } from "react";
5832
- import { Fragment as Fragment2, jsx as jsx17, jsxs as jsxs10 } from "react/jsx-runtime";
5833
- function DiffApprovalView() {
5834
- const { payload } = useApproval();
5835
- const size = useTerminalSize();
5836
- const [scrollIndex, setScrollIndex] = useState14(0);
5837
- const diffLines = useMemo11(() => {
5838
- if (!payload?.diff) {
5839
- return [];
5840
- }
5841
- return payload.diff.split("\n");
5842
- }, [payload?.diff]);
5843
- const wrappedLines = useMemo11(() => {
5844
- const result = [];
5845
- if (!payload) {
5846
- return result;
5847
- }
5848
- if (payload.type === "delete_file") {
5849
- result.push({ text: "Delete file: " + payload.path, color: "red" });
5850
- return result;
5851
- }
5852
- if (payload.type === "overwrite_file") {
5853
- for (const line of diffLines) {
5854
- let color;
5855
- if (line.startsWith("+")) {
5856
- color = "green";
5857
- } else if (line.startsWith("-")) {
5858
- color = "red";
5859
- }
5860
- const wrapped = wrapText(line, size.columns - 4);
5861
- for (const w of wrapped) {
5862
- result.push({ text: w, color });
5863
- }
5864
- }
5865
- return result;
5866
- }
5867
- if (payload.type === "code_interpreter" && payload.code) {
5868
- const lines = payload.code.split("\n");
5869
- for (const line of lines) {
5870
- const wrapped = wrapText(line, size.columns - 4);
5871
- for (const w of wrapped) {
5872
- result.push({ text: w });
5873
- }
5874
- }
5875
- return result;
5876
- }
5877
- if (payload.type === "shell" && payload.commands) {
5878
- for (const cmd of payload.commands) {
5879
- const wrapped = wrapText(`$ ${cmd}`, size.columns - 4);
5880
- for (const w of wrapped) {
5881
- result.push({ text: w, color: "yellow" });
5882
- }
5883
- }
5884
- return result;
5885
- }
5886
- for (const line of diffLines) {
5887
- let color;
5888
- if (line.startsWith("+")) {
5889
- color = "green";
5890
- } else if (line.startsWith("-")) {
5891
- color = "red";
5892
- } else if (line.startsWith("@@")) {
5893
- color = "cyan";
5894
- }
5895
- const wrapped = wrapText(line, size.columns - 4);
5896
- for (const w of wrapped) {
5897
- result.push({ text: w, color });
5898
- }
5899
- }
5900
- return result;
5901
- }, [diffLines, size.columns, payload]);
5902
- const visibleHeight = size.rows - 6;
5903
- useInput5((input, key) => {
5904
- if (!payload) {
5905
- return;
5906
- }
5907
- if (key.escape) {
5908
- if (payload.mode === "ask") {
5909
- emitToListeners("DenyCurrentApproval", void 0);
5910
- }
5911
- emitToListeners("CloseApprovalViewer", void 0);
5912
- return;
5913
- }
5914
- if (key.return) {
5915
- if (payload.mode === "ask") {
5916
- const now = Date.now();
5917
- if (payload.openedAt && now - payload.openedAt > 1e3) {
5918
- emitToListeners("ApproveCurrentApproval", void 0);
5919
- emitToListeners("CloseApprovalViewer", void 0);
5920
- emitToListeners("ClearUserInput", void 0);
5921
- }
5922
- } else {
5923
- emitToListeners("CloseApprovalViewer", void 0);
5924
- }
5925
- return;
5926
- }
5927
- if (payload.mode === "ask") {
5928
- const now = Date.now();
5929
- if (payload.openedAt && now - payload.openedAt > 1e3) {
5930
- if (input === "y") {
5931
- emitToListeners("ApproveCurrentApproval", void 0);
5932
- emitToListeners("CloseApprovalViewer", void 0);
5933
- emitToListeners("ClearUserInput", void 0);
5934
- } else if (input === "n") {
5935
- emitToListeners("DenyCurrentApproval", void 0);
5936
- emitToListeners("CloseApprovalViewer", void 0);
5937
- emitToListeners("ClearUserInput", void 0);
5938
- }
5939
- }
5940
- }
5941
- if (key.upArrow) {
5942
- setScrollIndex((prev) => Math.max(0, prev - 1));
5943
- }
5944
- if (key.downArrow) {
5945
- setScrollIndex((prev) => Math.min(Math.max(0, wrappedLines.length - visibleHeight), prev + 1));
5946
- }
5947
- if (key.pageUp) {
5948
- setScrollIndex((prev) => Math.max(0, prev - visibleHeight));
5949
- }
5950
- if (key.pageDown) {
5951
- setScrollIndex((prev) => Math.min(Math.max(0, wrappedLines.length - visibleHeight), prev + visibleHeight));
5952
- }
5953
- });
5954
- if (!payload) {
5955
- return null;
5956
- }
5957
- const canRespond = payload.mode === "ask" && payload.openedAt && Date.now() - payload.openedAt > 1e3;
5958
- return /* @__PURE__ */ jsxs10(
5959
- Box11,
5960
- {
5961
- position: "absolute",
5962
- flexDirection: "column",
5963
- width: size.columns,
5964
- height: size.rows,
5965
- backgroundColor: "black",
5966
- borderStyle: "double",
5967
- borderColor: "cyan",
5968
- children: [
5969
- /* @__PURE__ */ jsxs10(
5970
- Box11,
5971
- {
5972
- paddingX: 1,
5973
- borderStyle: "single",
5974
- borderBottomColor: "gray",
5975
- borderTop: false,
5976
- borderLeft: false,
5977
- borderRight: false,
5978
- children: [
5979
- /* @__PURE__ */ jsxs10(Text11, { bold: true, color: "cyan", children: [
5980
- payload.mode === "ask" ? "APPROVE " : "VIEW ",
5981
- payload.type.toUpperCase().replace("_", " ")
5982
- ] }),
5983
- /* @__PURE__ */ jsx17(Text11, { color: "gray", children: "\u2502" }),
5984
- payload.path && /* @__PURE__ */ jsxs10(Fragment2, { children: [
5985
- /* @__PURE__ */ jsxs10(Text11, { bold: true, children: [
5986
- payload.type,
5987
- ":"
5988
- ] }),
5989
- /* @__PURE__ */ jsx17(Text11, { children: payload.path })
5990
- ] })
5991
- ]
5992
- }
5993
- ),
5994
- /* @__PURE__ */ jsx17(Box11, { flexGrow: 1, flexDirection: "column", paddingX: 1, children: wrappedLines.length === 0 ? /* @__PURE__ */ jsx17(Text11, { italic: true, color: "gray", children: "No diff content." }) : wrappedLines.slice(scrollIndex, scrollIndex + visibleHeight).map((line, i) => /* @__PURE__ */ jsx17(Text11, { color: line.color || "white", children: line.text }, i)) }),
5995
- /* @__PURE__ */ jsxs10(
5996
- Box11,
5997
- {
5998
- paddingX: 1,
5999
- borderStyle: "single",
6000
- borderTopColor: "gray",
6001
- borderBottom: false,
6002
- borderLeft: false,
6003
- borderRight: false,
6004
- children: [
6005
- payload.mode === "ask" ? /* @__PURE__ */ jsxs10(Box11, { flexGrow: 1, children: [
6006
- /* @__PURE__ */ jsx17(Text11, { color: canRespond ? "green" : "gray", children: "[Enter/y] Approve" }),
6007
- /* @__PURE__ */ jsx17(Text11, {}),
6008
- /* @__PURE__ */ jsx17(Text11, { color: canRespond ? "red" : "gray", children: "[Esc/n] Deny" }),
6009
- !canRespond && /* @__PURE__ */ jsx17(Text11, { color: "yellow", children: "(Wait 1s...)" })
6010
- ] }) : /* @__PURE__ */ jsx17(Box11, { flexGrow: 1, children: /* @__PURE__ */ jsx17(Text11, { color: "cyan", children: "[Enter/Esc] Close" }) }),
6011
- /* @__PURE__ */ jsxs10(Text11, { color: "gray", children: [
6012
- "Line ",
6013
- scrollIndex + 1,
6014
- "/",
6015
- wrappedLines.length
6016
- ] })
6017
- ]
6018
- }
6019
- )
6020
- ]
6021
- }
6022
- );
6023
- }
6024
-
6025
- // ink/configurationWizard/ConfigurationWizard.tsx
6026
- import { Box as Box18, useInput as useInput10 } from "ink";
6027
- import { Step, Stepper } from "ink-stepper";
6028
- import { useEffect as useEffect14, useState as useState16 } from "react";
6029
-
6030
- // utils/files/getEnvVarForProvider.ts
6031
- function getEnvVarForProvider(provider) {
6032
- switch (provider) {
6033
- case "Anthropic":
6034
- return "ANTHROPIC_API_KEY";
6035
- case "Google":
6036
- return "GOOGLE_GENERATIVE_AI_API_KEY";
6037
- case "OpenAI":
6038
- return "OPENAI_API_KEY";
6039
- case "Ollama":
6040
- return "OLLAMA_BASE_URL";
6041
- }
6042
- }
6043
-
6044
- // utils/files/updateEnvKeyForProvider.ts
6045
- function updateEnvKeyForProvider(provider, key) {
6046
- const envVar = getEnvVarForProvider(provider);
6047
- return updateEnv(envVar, key);
6048
- }
6049
-
6050
- // ink/configurationWizard/ApiKeyStep.tsx
6051
- import { PasswordInput } from "@inkjs/ui";
6052
- import { Box as Box12, Text as Text12, useInput as useInput6 } from "ink";
6053
- import { useStepperInput } from "ink-stepper";
6054
- import { useEffect as useEffect9 } from "react";
6055
- import { jsx as jsx18, jsxs as jsxs11 } from "react/jsx-runtime";
6056
- function ApiKeyStep({ provider, onConfirm, onBack }) {
6057
- const { disableNavigation, enableNavigation } = useStepperInput();
6058
- useEffect9(() => {
6059
- disableNavigation();
6060
- return () => enableNavigation();
6061
- }, [disableNavigation, enableNavigation]);
6062
- useInput6((input, key) => {
6063
- if (key.escape) {
6064
- onBack();
6065
- }
6066
- });
6067
- const instructions = {
6068
- OpenAI: "Get your key at: https://platform.openai.com/api-keys",
6069
- Anthropic: "Get your key at: https://console.anthropic.com/settings/keys",
6070
- Google: "Get your key at: https://aistudio.google.com/app/apikey",
6071
- Ollama: ""
6072
- };
6073
- return /* @__PURE__ */ jsxs11(Box12, { flexDirection: "column", children: [
6074
- /* @__PURE__ */ jsxs11(Text12, { children: [
6075
- "Can you provide us with your ",
6076
- provider,
6077
- " API key?"
6078
- ] }),
6079
- /* @__PURE__ */ jsx18(Text12, { dimColor: true, children: instructions[provider] }),
6080
- /* @__PURE__ */ jsx18(Box12, { marginTop: 1, children: /* @__PURE__ */ jsx18(
6081
- PasswordInput,
6082
- {
6083
- onSubmit: (v) => {
6084
- if (v === "exit") {
6085
- emitToListeners("ExitUI", void 0);
6086
- } else {
6087
- onConfirm(v);
6088
- }
6089
- }
6090
- }
6091
- ) }),
6092
- /* @__PURE__ */ jsx18(Box12, { marginTop: 1, children: /* @__PURE__ */ jsx18(Text12, { dimColor: true, children: "Press ESC to go back" }) })
6093
- ] });
6094
- }
6095
-
6096
- // ink/configurationWizard/ApiUrlStep.tsx
6097
- import { Box as Box13, Text as Text13, useInput as useInput7 } from "ink";
6098
- import { useStepperInput as useStepperInput2 } from "ink-stepper";
6099
- import { useEffect as useEffect10 } from "react";
6100
- import { jsx as jsx19, jsxs as jsxs12 } from "react/jsx-runtime";
6101
- function ApiUrlStep({ provider, onConfirm, onBack }) {
6102
- const { disableNavigation, enableNavigation } = useStepperInput2();
6103
- useEffect10(() => {
6104
- disableNavigation();
6105
- return () => enableNavigation();
6106
- }, [disableNavigation, enableNavigation]);
6107
- useInput7((input, key) => {
6108
- if (key.escape) {
6109
- onBack();
6110
- }
6111
- });
6112
- const defaultApi = {
6113
- OpenAI: "",
6114
- Anthropic: "",
6115
- Google: "",
6116
- Ollama: "http://localhost:11434/api"
6117
- };
6118
- return /* @__PURE__ */ jsxs12(Box13, { flexDirection: "column", children: [
6119
- /* @__PURE__ */ jsxs12(Text13, { children: [
6120
- "Where are you hosting ",
6121
- provider,
6122
- "?"
6123
- ] }),
6124
- /* @__PURE__ */ jsx19(Box13, { marginTop: 1, children: /* @__PURE__ */ jsx19(
6125
- BlinkingTextInput,
6126
- {
6127
- placeholder: defaultApi[provider] || "",
6128
- onSubmit: (v) => {
6129
- if (v === "exit") {
6130
- emitToListeners("ExitUI", void 0);
6131
- } else {
6132
- onConfirm(v || defaultApi[provider] || "");
6133
- }
6134
- }
6135
- }
6136
- ) }),
6137
- /* @__PURE__ */ jsx19(Box13, { marginTop: 1, children: /* @__PURE__ */ jsx19(Text13, { dimColor: true, children: "Press ESC to go back" }) })
6138
- ] });
6139
- }
6140
-
6141
- // ink/configurationWizard/EnvironmentSettingsStep.tsx
6142
- import { MultiSelect } from "@inkjs/ui";
6143
- import { Box as Box14, Text as Text14, useInput as useInput8 } from "ink";
6144
- import { useStepperInput as useStepperInput3 } from "ink-stepper";
6145
- import { useEffect as useEffect11 } from "react";
6146
- import { jsx as jsx20, jsxs as jsxs13 } from "react/jsx-runtime";
6147
- var SETTINGS = [
6148
- {
6149
- label: "Save Harper agent memory locally",
6150
- value: "HARPER_AGENT_SESSION",
6151
- defaultValue: "./harper-agent-memory.json"
6152
- },
6153
- {
6154
- label: "Automatically approve code interpreter execution",
6155
- value: "HARPER_AGENT_AUTO_APPROVE_CODE_INTERPRETER",
6156
- defaultValue: "1"
6157
- },
6158
- {
6159
- label: "Automatically approve file patches",
6160
- value: "HARPER_AGENT_AUTO_APPROVE_PATCHES",
6161
- defaultValue: "1"
6162
- },
6163
- {
6164
- label: "Automatically approve shell commands",
6165
- value: "HARPER_AGENT_AUTO_APPROVE_SHELL",
6166
- defaultValue: "1"
6167
- },
6168
- {
6169
- label: "Use flex tier for lower costs when possible",
6170
- value: "HARPER_AGENT_FLEX_TIER",
6171
- defaultValue: "true"
6172
- }
6173
- ];
6174
- function EnvironmentSettingsStep({ onConfirm, onBack }) {
6175
- const { disableNavigation, enableNavigation } = useStepperInput3();
6176
- useEffect11(() => {
6177
- disableNavigation();
6178
- return () => enableNavigation();
6179
- }, [disableNavigation, enableNavigation]);
6180
- useInput8((_input, key) => {
6181
- if (key.escape) {
6182
- onBack();
6183
- }
6184
- });
6185
- const options = SETTINGS.map((s) => ({
6186
- label: s.label,
6187
- value: s.value
6188
- }));
6189
- const defaultValues = SETTINGS.map((s) => s.value);
6190
- const handleSubmit = (values) => {
6191
- for (const setting of SETTINGS) {
6192
- if (values.includes(setting.value)) {
6193
- updateEnv(setting.value, setting.defaultValue);
6194
- }
6195
- }
6196
- onConfirm();
6197
- };
6198
- return /* @__PURE__ */ jsxs13(Box14, { flexDirection: "column", children: [
6199
- /* @__PURE__ */ jsx20(Text14, { children: "Additional Settings (all enabled by default):" }),
6200
- /* @__PURE__ */ jsx20(
6201
- MultiSelect,
6202
- {
6203
- options,
6204
- defaultValue: defaultValues,
6205
- onSubmit: handleSubmit
6206
- }
6207
- ),
6208
- /* @__PURE__ */ jsxs13(Text14, { color: "gray", children: [
6209
- "Press ",
6210
- "<space>",
6211
- " to toggle, ",
6212
- "<enter>",
6213
- " to confirm."
6214
- ] })
6215
- ] });
6216
- }
6217
-
6218
- // ink/configurationWizard/modelsByProvider.ts
6219
- var modelsByProvider = {
6220
- OpenAI: [defaultOpenAIModel, "gpt-5.0", defaultOpenAICompactionModel],
6221
- Anthropic: [defaultAnthropicModel, "claude-4-5-sonnet-latest", defaultAnthropicCompactionModel],
6222
- Google: [defaultGoogleModel, "gemini-3-flash", "gemini-2.5-flash", defaultGoogleCompactionModel],
6223
- Ollama: [defaultOllamaModel, "ollama-qwen3.5:27b", defaultOllamaCompactionModel]
6224
- };
6225
- var compactorModelsByProvider = {
6226
- OpenAI: modelsByProvider.OpenAI.slice().reverse(),
6227
- Anthropic: modelsByProvider.Anthropic.slice().reverse(),
6228
- Google: modelsByProvider.Google.slice().reverse(),
6229
- Ollama: modelsByProvider.Ollama.slice().reverse()
6230
- };
6231
-
6232
- // ink/configurationWizard/ModelSelectionStep.tsx
6233
- import { Select } from "@inkjs/ui";
6234
- import { Box as Box15, Text as Text15, useInput as useInput9 } from "ink";
6235
- import { useStepperInput as useStepperInput4 } from "ink-stepper";
6236
- import { useEffect as useEffect12, useState as useState15 } from "react";
6237
- import { jsx as jsx21, jsxs as jsxs14 } from "react/jsx-runtime";
6238
- function ModelSelectionStep({
6239
- title,
6240
- models,
6241
- onConfirm,
6242
- onBack
6243
- }) {
6244
- const { disableNavigation, enableNavigation } = useStepperInput4();
6245
- const [isCustom, setIsCustom] = useState15(false);
6246
- useEffect12(() => {
6247
- disableNavigation();
6248
- return () => enableNavigation();
6249
- }, [isCustom, disableNavigation, enableNavigation]);
6250
- useInput9((input, key) => {
6251
- if (key.escape) {
6252
- if (isCustom) {
6253
- setIsCustom(false);
6254
- } else {
6255
- onBack();
6256
- }
6257
- }
6258
- });
6259
- if (isCustom) {
6260
- return /* @__PURE__ */ jsxs14(Box15, { flexDirection: "column", children: [
6261
- /* @__PURE__ */ jsxs14(Text15, { children: [
6262
- "Enter custom model name for: ",
6263
- title
6264
- ] }),
6265
- /* @__PURE__ */ jsx21(
6266
- BlinkingTextInput,
6267
- {
6268
- onSubmit: (v) => {
6269
- if (v === "exit") {
6270
- emitToListeners("ExitUI", void 0);
6271
- } else {
6272
- onConfirm(v);
6273
- }
6274
- }
6275
- }
6276
- ),
6277
- /* @__PURE__ */ jsx21(Box15, { marginTop: 1, children: /* @__PURE__ */ jsx21(Text15, { dimColor: true, children: "Press ESC to go back to list" }) })
6278
- ] });
6279
- }
6280
- return /* @__PURE__ */ jsxs14(Box15, { flexDirection: "column", children: [
6281
- /* @__PURE__ */ jsx21(Text15, { children: title }),
6282
- /* @__PURE__ */ jsx21(
6283
- Select,
6284
- {
6285
- options: [
6286
- ...models.map((m) => ({ label: m, value: m })),
6287
- { label: "Other...", value: "other" }
6288
- ],
6289
- onChange: (v) => {
6290
- if (v === "other") {
6291
- setIsCustom(true);
6292
- } else {
6293
- onConfirm(v);
6294
- }
6295
- }
6296
- }
6297
- ),
6298
- /* @__PURE__ */ jsx21(Box15, { marginTop: 1, children: /* @__PURE__ */ jsx21(Text15, { dimColor: true, children: "Press ESC to go back" }) })
6299
- ] });
6300
- }
6301
-
6302
- // ink/configurationWizard/ProviderStep.tsx
6303
- import { Select as Select2 } from "@inkjs/ui";
6304
- import { Box as Box16, Text as Text16 } from "ink";
6305
- import { useStepperInput as useStepperInput5 } from "ink-stepper";
6306
- import { useEffect as useEffect13 } from "react";
6307
-
6308
- // ink/configurationWizard/providers.ts
6309
- var providers = [
6310
- { label: "OpenAI", value: "OpenAI" },
6311
- { label: "Anthropic", value: "Anthropic" },
6312
- { label: "Google", value: "Google" },
6313
- { label: "Ollama", value: "Ollama" }
6314
- ];
6315
-
6316
- // ink/configurationWizard/ProviderStep.tsx
6317
- import { jsx as jsx22, jsxs as jsxs15 } from "react/jsx-runtime";
6318
- function ProviderStep({ onConfirm }) {
6319
- const { disableNavigation, enableNavigation } = useStepperInput5();
6320
- useEffect13(() => {
6321
- disableNavigation();
6322
- return () => enableNavigation();
6323
- }, [disableNavigation, enableNavigation]);
6324
- return /* @__PURE__ */ jsxs15(Box16, { flexDirection: "column", children: [
6325
- /* @__PURE__ */ jsx22(Text16, { children: "What model provider would you like to use today?" }),
6326
- /* @__PURE__ */ jsx22(
6327
- Select2,
6328
- {
6329
- options: providers,
6330
- onChange: (v) => onConfirm(v)
6331
- }
6332
- )
6333
- ] });
6334
- }
6335
-
6336
- // ink/configurationWizard/StepperProgress.tsx
6337
- import { Box as Box17, Text as Text17 } from "ink";
6338
- import { Fragment as Fragment3 } from "react";
6339
- import { jsx as jsx23, jsxs as jsxs16 } from "react/jsx-runtime";
6340
- var markers = {
6341
- completed: " \u2713 ",
6342
- current: " \u25CF ",
6343
- pending: " \u25CB "
6344
- };
6345
- var SEGMENT_WIDTH = 12;
6346
- function StepperProgress({ steps, currentStep }) {
6347
- return /* @__PURE__ */ jsxs16(Box17, { flexDirection: "column", marginBottom: 1, children: [
6348
- /* @__PURE__ */ jsx23(Box17, { children: steps.map((step) => {
6349
- return /* @__PURE__ */ jsx23(Box17, { width: SEGMENT_WIDTH, justifyContent: "center", children: /* @__PURE__ */ jsx23(
6350
- Text17,
6351
- {
6352
- color: step.completed ? "green" : step.current ? "cyan" : "gray",
6353
- bold: step.current,
6354
- dimColor: !step.completed && !step.current,
6355
- children: step.name
6356
- }
6357
- ) }, step.name);
6358
- }) }),
6359
- /* @__PURE__ */ jsx23(Box17, { children: steps.map((step, idx) => {
6360
- const marker = step.completed ? markers.completed : step.current ? markers.current : markers.pending;
6361
- const beforeLineColor = step.completed || idx <= currentStep ? "green" : "gray";
6362
- const afterLineColor = step.completed ? "green" : "gray";
6363
- const markerColor = step.completed ? "green" : step.current ? "cyan" : "gray";
6364
- return /* @__PURE__ */ jsxs16(Fragment3, { children: [
6365
- /* @__PURE__ */ jsx23(Text17, { color: beforeLineColor, children: "\u2501".repeat(SEGMENT_WIDTH / 2 - 2) }),
6366
- /* @__PURE__ */ jsx23(Text17, { color: markerColor, bold: step.current, children: marker }),
6367
- /* @__PURE__ */ jsx23(Text17, { color: afterLineColor, children: "\u2501".repeat(SEGMENT_WIDTH / 2 - 1) })
6368
- ] }, step.name);
6369
- }) })
6370
- ] });
6371
- }
6372
-
6373
- // ink/configurationWizard/ConfigurationWizard.tsx
6374
- import { jsx as jsx24, jsxs as jsxs17 } from "react/jsx-runtime";
6375
- function ConfigurationWizard({ onComplete }) {
6376
- const [provider, setProvider] = useState16("OpenAI");
6377
- const [ollamaModels, setOllamaModels] = useState16([]);
6378
- useEffect14(() => {
6379
- if (provider === "Ollama") {
6380
- fetchOllamaModels().then((models2) => {
6381
- if (models2.length > 0) {
6382
- setOllamaModels(models2);
6383
- }
6384
- });
6385
- }
6386
- }, [provider]);
6387
- useInput10((input, key) => {
6388
- if (key.ctrl && input === "x") {
6389
- emitToListeners("ExitUI", void 0);
6390
- }
6391
- });
6392
- const models = provider === "Ollama" && ollamaModels.length > 0 ? [.../* @__PURE__ */ new Set([...ollamaModels, ...modelsByProvider[provider]])] : modelsByProvider[provider];
6393
- const compactorModels = provider === "Ollama" && ollamaModels.length > 0 ? [.../* @__PURE__ */ new Set([...ollamaModels, ...compactorModelsByProvider[provider]])] : compactorModelsByProvider[provider];
6394
- return /* @__PURE__ */ jsx24(Box18, { flexDirection: "column", padding: 1, minHeight: 10, children: /* @__PURE__ */ jsxs17(
6395
- Stepper,
6396
- {
6397
- onComplete,
6398
- onCancel: curryEmitToListeners("ExitUI", void 0),
6399
- keyboardNav: true,
6400
- renderProgress: StepperProgress,
6401
- children: [
6402
- /* @__PURE__ */ jsx24(Step, { name: "AI Provider", children: ({ goNext }) => /* @__PURE__ */ jsx24(
6403
- ProviderStep,
6404
- {
6405
- onConfirm: (p) => {
6406
- setProvider(p);
6407
- goNext();
6408
- }
6409
- }
6410
- ) }),
6411
- /* @__PURE__ */ jsx24(Step, { name: provider !== "Ollama" ? "API Key" : "API", children: ({ goNext, goBack }) => provider !== "Ollama" ? /* @__PURE__ */ jsx24(
6412
- ApiKeyStep,
6413
- {
6414
- provider,
6415
- onConfirm: (key) => {
6416
- updateEnvKeyForProvider(provider, key);
6417
- goNext();
6418
- },
6419
- onBack: goBack
6420
- }
6421
- ) : /* @__PURE__ */ jsx24(
6422
- ApiUrlStep,
6423
- {
6424
- provider,
6425
- onConfirm: (key) => {
6426
- updateEnvKeyForProvider(provider, key);
6427
- goNext();
6428
- },
6429
- onBack: goBack
6430
- }
6431
- ) }),
6432
- /* @__PURE__ */ jsx24(Step, { name: "Model", children: ({ goNext, goBack }) => /* @__PURE__ */ jsx24(
6433
- ModelSelectionStep,
6434
- {
6435
- title: "What model would you like to use?",
6436
- models,
6437
- onConfirm: (m) => {
6438
- const finalModelName = provider === "Ollama" && !m.startsWith("ollama-") && !m.includes(":") ? `ollama-${m}` : m;
6439
- updateEnv("HARPER_AGENT_MODEL", finalModelName);
6440
- goNext();
6441
- },
6442
- onBack: goBack
6443
- }
6444
- ) }),
6445
- /* @__PURE__ */ jsx24(Step, { name: "Compactor", children: ({ goNext, goBack }) => /* @__PURE__ */ jsx24(
6446
- ModelSelectionStep,
6447
- {
6448
- title: "What model should we use for memory compaction?",
6449
- models: compactorModels,
6450
- onConfirm: (m) => {
6451
- const finalModelName = provider === "Ollama" && !m.startsWith("ollama-") && !m.includes(":") ? `ollama-${m}` : m;
6452
- updateEnv("HARPER_AGENT_COMPACTION_MODEL", finalModelName);
6453
- goNext();
6454
- },
6455
- onBack: goBack
6456
- }
6457
- ) }),
6458
- /* @__PURE__ */ jsx24(Step, { name: "Settings", children: ({ goNext, goBack }) => /* @__PURE__ */ jsx24(
6459
- EnvironmentSettingsStep,
6460
- {
6461
- onConfirm: () => {
6462
- goNext();
6463
- },
6464
- onBack: goBack
6465
- }
6466
- ) })
6467
- ]
6468
- }
6469
- ) });
6470
- }
6471
-
6472
- // ink/main.tsx
6473
- import { jsx as jsx25, jsxs as jsxs18 } from "react/jsx-runtime";
6474
- function bootstrapConfig() {
6475
- return new Promise((resolve2) => {
6476
- render(/* @__PURE__ */ jsx25(MainConfig, { onComplete: resolve2 }));
6477
- });
6478
- }
6479
- function MainConfig({ onComplete }) {
6480
- const { exit } = useApp();
6481
- useListener("ExitUI", () => exit(), [exit]);
6482
- return /* @__PURE__ */ jsx25(ConfigurationWizard, { onComplete });
6483
- }
6484
- function bootstrapMain() {
6485
- render(/* @__PURE__ */ jsx25(MainChat, {}));
6486
- }
6487
- function MainChat() {
6488
- const { exit } = useApp();
6489
- useListener("ExitUI", () => exit(), [exit]);
6490
- return /* @__PURE__ */ jsx25(CostProvider, { children: /* @__PURE__ */ jsx25(PlanProvider, { children: /* @__PURE__ */ jsx25(ActionsProvider, { children: /* @__PURE__ */ jsx25(ApprovalProvider, { children: /* @__PURE__ */ jsx25(SettingsProvider, { children: /* @__PURE__ */ jsxs18(ChatProvider, { children: [
6491
- /* @__PURE__ */ jsx25(ChatContent, {}),
6492
- /* @__PURE__ */ jsx25(DiffApprovalView, {})
6493
- ] }) }) }) }) }) });
6494
- }
6495
-
6496
- // utils/models/deprecations.ts
6497
- import chalk4 from "chalk";
6498
- var DEPRECATION_RULES = [
6499
- // Redirect any gpt-4o variants (including dated and -mini) to gpt-5-nano
6500
- {
6501
- match: (name) => name.toLowerCase().startsWith("gpt-4o"),
6502
- replacement: "gpt-5-nano",
6503
- reason: "OpenAI gpt-4o family is deprecated in this agent"
6504
- }
6505
- ];
6506
- function getDeprecatedReplacement(modelName) {
6507
- if (!modelName) {
6508
- return null;
6509
- }
6510
- for (const rule of DEPRECATION_RULES) {
6511
- if (rule.match(modelName)) {
6512
- return { replacement: rule.replacement, rule };
6513
- }
6514
- }
6515
- return null;
6516
- }
6517
- function warnAndPersistRedirect(original, envKey, replacement, reason) {
6518
- const reasonSuffix = reason ? ` Reason: ${reason}.` : "";
6519
- console.warn(
6520
- chalk4.yellow(
6521
- `Warning: model "${original}" is deprecated and will be redirected to "${replacement}". Your environment setting (${envKey}) has been updated.${reasonSuffix}`
6522
- )
6523
- );
6524
- updateEnv(envKey, replacement);
6525
- }
6526
-
6527
- // utils/shell/cli.ts
6528
- import chalk5 from "chalk";
6529
-
6530
- // utils/package/getOwnPackageJson.ts
6531
- import { readFileSync as readFileSync7 } from "fs";
6532
- import { join as join11 } from "path";
6533
- import { fileURLToPath as fileURLToPath2 } from "url";
6534
- var __dirname2 = fileURLToPath2(new URL(".", import.meta.url));
6535
- function getOwnPackageJson() {
6536
- try {
6537
- const packageContents = readFileSync7(join11(__dirname2, "../package.json"), "utf8");
6538
- return JSON.parse(packageContents);
6539
- } catch {
6540
- return { name: "@harperfast/agent", version: "0.0.0" };
6541
- }
6542
- }
6543
-
6544
- // utils/shell/cli.ts
6545
- function isHelpRequest(args) {
6546
- const helpVariants = ["--help", "-h", "help"];
6547
- return args.some((arg) => helpVariants.includes(arg.toLowerCase()));
6548
- }
6549
- function isVersionRequest(args) {
6550
- const versionVariants = ["--version", "-v", "version"];
6551
- return args.some((arg) => versionVariants.includes(arg.toLowerCase()));
6552
- }
6553
- function handleHelp() {
6554
- console.log(`
6555
- ${chalk5.bold("harper-agent")} - AI to help you with Harper app creation and modification
6556
-
6557
- ${chalk5.bold("USAGE")}
6558
- $ harper-agent [options]
6559
- $ harper-agent [command]
6560
-
6561
- ${chalk5.bold("OPTIONS")}
6562
- -h, --help Show help information
6563
- -v, --version Show version information
6564
- -m, --model Specify the model to use (e.g., ${defaultOpenAIModel}, ${defaultAnthropicModel}, ${defaultOllamaModel})
6565
- Can also be set via HARPER_AGENT_MODEL environment variable.
6566
- For Ollama, use the ollama- prefix (e.g., ${defaultOllamaCompactionModel}).
6567
- -c, --compaction-model Specify the compaction model to use (defaults to ${defaultOpenAICompactionModel}).
6568
- Can also be set via HARPER_AGENT_COMPACTION_MODEL environment variable.
6569
- -s, --session Specify a path to a SQLite database file to persist the chat session.
6570
- Can also be set via HARPER_AGENT_SESSION environment variable.
6571
- --max-turns Specify the maximum number of turns for the agent run.
6572
- Can also be set via HARPER_AGENT_MAX_TURNS environment variable.
6573
- --max-cost Specify the maximum cost (in USD) for the agent run.
6574
- If exceeded, the agent will exit with a non-zero code.
6575
- Can also be set via HARPER_AGENT_MAX_COST environment variable.
6576
- --flex-tier Force the use of the flex service tier for lower costs but potentially
6577
- more errors under high system load.
6578
- Can also be set via HARPER_AGENT_FLEX_TIER=true environment variable.
6579
- -p, --prompt Specify a prompt to be executed autonomously until completion.
6580
-
6581
- ${chalk5.bold("COMMANDS")}
6582
- --help Show help information
6583
- --version Show version information
6584
-
6585
- ${chalk5.bold("EXAMPLES")}
6586
- $ harper-agent --help
6587
- $ harper-agent --version
6588
- $ harper-agent
6589
- `);
6590
- process.exit(0);
6591
- }
6592
- function handleVersion() {
6593
- const pkg = getOwnPackageJson();
6594
- console.log(pkg.version);
6595
- process.exit(0);
6596
- }
6597
-
6598
- // lifecycle/parseArgs.ts
6599
- function stripQuotes(str) {
6600
- if (str.startsWith('"') && str.endsWith('"') || str.startsWith("'") && str.endsWith("'")) {
6601
- return str.slice(1, -1);
6602
- }
6603
- return str;
6604
- }
6605
- function parseArgs() {
6606
- const args = process.argv.slice(2);
6607
- if (isHelpRequest(args)) {
6608
- handleHelp();
6609
- }
6610
- if (isVersionRequest(args)) {
6611
- handleVersion();
6612
- }
6613
- for (let i = 0; i < args.length; i++) {
6614
- const arg = args[i];
6615
- const flagPairs = [
6616
- ["model", ["--model", "-m", "model"]],
6617
- ["compactionModel", ["--compaction-model", "-c", "compaction-model"]],
6618
- ["sessionPath", ["--session", "-s", "session"]],
6619
- ["maxTurns", ["--max-turns"]],
6620
- ["maxCost", ["--max-cost"]],
6621
- ["rateLimitThreshold", ["--rate-limit-threshold"]],
6622
- ["prompt", ["--prompt", "-p"]]
6623
- ];
6624
- let handled = false;
6625
- for (const [key, prefixes] of flagPairs) {
6626
- for (const prefix of prefixes) {
6627
- if (arg === prefix) {
6628
- if (args[i + 1]) {
6629
- const val = stripQuotes(args[++i]);
6630
- if (key === "maxTurns" || key === "maxCost" || key === "rateLimitThreshold") {
6631
- trackedState[key] = parseFloat(val);
6632
- } else {
6633
- trackedState[key] = val;
6634
- }
6635
- }
6636
- handled = true;
6637
- break;
6638
- } else if (arg.startsWith(`${prefix}=`)) {
6639
- const val = stripQuotes(arg.slice(prefix.length + 1));
6640
- if (key === "maxTurns" || key === "maxCost" || key === "rateLimitThreshold") {
6641
- trackedState[key] = parseFloat(val);
6642
- } else {
6643
- trackedState[key] = val;
6644
- }
6645
- handled = true;
6646
- break;
6647
- }
6648
- }
6649
- if (handled) {
6650
- break;
6651
- }
6652
- }
6653
- if (handled) {
6654
- continue;
6655
- }
6656
- if (arg === "--flex-tier") {
6657
- trackedState.useFlexTier = true;
6658
- } else if (arg === "--no-monitor-rate-limits") {
6659
- trackedState.monitorRateLimits = false;
6660
- } else if (arg === "--autonomous" || arg === "-a") {
6661
- trackedState.autonomous = true;
6662
- }
6663
- }
6664
- if (!trackedState.model && process.env.HARPER_AGENT_MODEL) {
6665
- trackedState.model = process.env.HARPER_AGENT_MODEL;
6666
- }
6667
- if (!trackedState.compactionModel && process.env.HARPER_AGENT_COMPACTION_MODEL) {
6668
- trackedState.compactionModel = process.env.HARPER_AGENT_COMPACTION_MODEL;
6669
- }
6670
- if (!trackedState.sessionPath && process.env.HARPER_AGENT_SESSION) {
6671
- trackedState.sessionPath = process.env.HARPER_AGENT_SESSION;
6672
- }
6673
- if (process.env.HARPER_AGENT_MAX_TURNS) {
6674
- trackedState.maxTurns = parseFloat(process.env.HARPER_AGENT_MAX_TURNS);
6675
- }
6676
- if (process.env.HARPER_AGENT_MAX_COST) {
6677
- trackedState.maxCost = parseFloat(process.env.HARPER_AGENT_MAX_COST);
6678
- }
6679
- if (process.env.HARPER_AGENT_RATE_LIMIT_THRESHOLD) {
6680
- trackedState.rateLimitThreshold = parseFloat(process.env.HARPER_AGENT_RATE_LIMIT_THRESHOLD);
6681
- }
6682
- if (process.env.HARPER_AGENT_MONITOR_RATE_LIMITS === "false") {
6683
- trackedState.monitorRateLimits = false;
6684
- }
6685
- if (!trackedState.useFlexTier && isTrue(process.env.HARPER_AGENT_FLEX_TIER)) {
6686
- trackedState.useFlexTier = true;
6687
- }
6688
- if (isTrue(process.env.HARPER_AGENT_AUTO_APPROVE_CODE_INTERPRETER)) {
6689
- trackedState.autoApproveCodeInterpreter = true;
6690
- }
6691
- if (isTrue(process.env.HARPER_AGENT_AUTO_APPROVE_PATCHES)) {
6692
- trackedState.autoApprovePatches = true;
6693
- }
6694
- if (isTrue(process.env.HARPER_AGENT_AUTO_APPROVE_SHELL)) {
6695
- trackedState.autoApproveShell = true;
6696
- }
6697
- if (isTrue(process.env.HARPER_AGENT_AUTONOMOUS)) {
6698
- trackedState.autonomous = true;
6699
- }
6700
- if (!trackedState.model || trackedState.model === defaultModelToken) {
6701
- if (process.env.ANTHROPIC_API_KEY) {
6702
- trackedState.model = defaultAnthropicModel;
6703
- } else if (process.env.GOOGLE_GENERATIVE_AI_API_KEY) {
6704
- trackedState.model = defaultGoogleModel;
6705
- } else if (process.env.OLLAMA_BASE_URL) {
6706
- trackedState.model = defaultOllamaModel;
6707
- } else {
6708
- trackedState.model = defaultOpenAIModel;
6709
- }
6710
- }
6711
- if (!trackedState.compactionModel || trackedState.compactionModel === defaultModelToken) {
6712
- const m = trackedState.model;
6713
- if (m.startsWith("claude-")) {
6714
- trackedState.compactionModel = defaultAnthropicCompactionModel;
6715
- } else if (m.startsWith("gemini-")) {
6716
- trackedState.compactionModel = defaultGoogleCompactionModel;
6717
- } else if (m.startsWith("ollama-")) {
6718
- trackedState.compactionModel = defaultOllamaCompactionModel;
6719
- } else {
6720
- trackedState.compactionModel = defaultOpenAICompactionModel;
6721
- }
6722
- }
6723
- if (!isOpenAIModel(trackedState.model)) {
6724
- process.env.OPENAI_AGENTS_DISABLE_TRACING = process.env.OPENAI_AGENTS_DISABLE_TRACING || "1";
6725
- }
6726
- const maybeRedirect = (current, envKey) => {
6727
- const hit = getDeprecatedReplacement(current);
6728
- if (hit) {
6729
- const { replacement, rule } = hit;
6730
- warnAndPersistRedirect(current, envKey, replacement, rule.reason);
6731
- return replacement;
6732
- }
6733
- return current;
6734
- };
6735
- trackedState.model = maybeRedirect(trackedState.model, "HARPER_AGENT_MODEL");
6736
- trackedState.compactionModel = maybeRedirect(trackedState.compactionModel, "HARPER_AGENT_COMPACTION_MODEL");
6737
- }
6738
-
6739
- // utils/envLoader.ts
6740
- import dotenv from "dotenv";
6741
- import { existsSync as existsSync11 } from "fs";
6742
- import { homedir as homedir4 } from "os";
6743
- import { join as join12 } from "path";
6744
- function loadEnv() {
6745
- const topLevelEnvPath = join12(homedir4(), ".harper", "harper-agent-env");
6746
- const localEnvPath = join12(process.cwd(), ".env");
6747
- if (existsSync11(topLevelEnvPath)) {
6748
- dotenv.config({ path: topLevelEnvPath, quiet: true });
6749
- }
6750
- if (existsSync11(localEnvPath)) {
6751
- dotenv.config({ path: localEnvPath, override: true, quiet: true });
6752
- }
6753
- }
6754
-
6755
- // utils/package/checkForUpdate.ts
6756
- import { Select as Select3 } from "@inkjs/ui";
6757
- import chalk6 from "chalk";
6758
- import spawn2 from "cross-spawn";
6759
- import { Box as Box19, render as render2, Text as Text18 } from "ink";
6760
- import React21 from "react";
6761
-
6762
- // utils/package/getLatestVersion.ts
6763
- async function getLatestVersion(packageName) {
6764
- try {
6765
- const response = await fetch(`https://registry.npmjs.org/${packageName}/latest`, {
6766
- signal: AbortSignal.timeout(1e3)
6767
- // 1 second timeout
6768
- });
6769
- if (!response.ok) {
6770
- return null;
6771
- }
6772
- const data = await response.json();
6773
- return data.version;
6774
- } catch {
6775
- return null;
6776
- }
6777
- }
6778
-
6779
- // utils/package/isVersionNewer.ts
6780
- function isVersionNewer(latest, current) {
6781
- const l = latest.split(".").map((x) => parseInt(x, 10));
6782
- const c = current.split(".").map((x) => parseInt(x, 10));
6783
- for (let i = 0; i < 3; i++) {
6784
- let latestNumber = l[i];
6785
- let currentNumber = c[i];
6786
- if (latestNumber === void 0 || currentNumber === void 0 || isNaN(latestNumber) || isNaN(currentNumber)) {
6787
- break;
6788
- }
6789
- if (latestNumber > currentNumber) {
6790
- return true;
6791
- }
6792
- if (latestNumber < currentNumber) {
6793
- return false;
6794
- }
6795
- }
6796
- return false;
6797
- }
6798
-
6799
- // utils/package/checkForUpdate.ts
6800
- async function checkForUpdate() {
6801
- const pkg = getOwnPackageJson();
6802
- const packageName = pkg.name;
6803
- const packageVersion = pkg.version;
6804
- if (process.env.HARPER_AGENT_SKIP_UPDATE) {
6805
- return packageVersion;
6806
- }
6807
- try {
6808
- const latestVersion = await getLatestVersion(packageName);
6809
- if (latestVersion && isVersionNewer(latestVersion, packageVersion)) {
6810
- const choice = await promptForUpdateChoice(packageName, packageVersion, latestVersion);
6811
- if (choice === "later") {
6812
- return packageVersion;
6813
- }
6814
- if (choice === "never") {
6815
- updateEnv("HARPER_AGENT_SKIP_UPDATE", "1");
6816
- return packageVersion;
6817
- }
6818
- let isGlobal = false;
6819
- try {
6820
- const globalRootResult = spawn2.sync("npm", ["root", "-g"], { encoding: "utf8" });
6821
- const globalRoot = globalRootResult.stdout?.trim();
6822
- if (globalRoot && process.argv[1] && process.argv[1].startsWith(globalRoot)) {
6823
- isGlobal = true;
6824
- }
6825
- } catch {
6826
- }
6827
- if (isGlobal) {
6828
- spawn2.sync("npm", ["install", "-g", `${packageName}@latest`], { stdio: "inherit" });
6829
- const result2 = spawn2.sync("harper-agent", process.argv.slice(2), { stdio: "inherit" });
6830
- process.exit(result2.status ?? 0);
6831
- }
6832
- const lsResult = spawn2.sync("npm", ["cache", "npx", "ls", packageName], { encoding: "utf8" });
6833
- if (lsResult.stdout) {
6834
- const keys = lsResult.stdout.split("\n").map((line) => line.trim()).filter((line) => line.includes(":")).filter((line) => {
6835
- const [, pkgPart] = line.split(":");
6836
- return pkgPart && pkgPart.trim().startsWith(`${packageName}@`);
6837
- }).map((line) => line.split(":")[0].trim());
6838
- if (keys.length > 0) {
6839
- spawn2.sync("npm", ["cache", "npx", "rm", ...keys], { stdio: "inherit" });
6840
- }
6841
- }
6842
- const result = spawn2.sync("npx", ["-y", `${packageName}@latest`, ...process.argv.slice(2)], { stdio: "inherit" });
6843
- process.exit(result.status ?? 0);
6844
- }
6845
- } catch {
6846
- }
6847
- return packageVersion;
6848
- }
6849
- function promptForUpdateChoice(pkgName, currentVersion, latestVersion) {
6850
- return new Promise((resolve2) => {
6851
- const app = render2(
6852
- React21.createElement(UpdatePrompt, {
6853
- packageName: pkgName,
6854
- currentVersion,
6855
- latestVersion,
6856
- onSelect: (c) => {
6857
- resolve2(c);
6858
- app.unmount();
6859
- }
6860
- })
6861
- );
6862
- });
6863
- }
6864
- function UpdatePrompt({ packageName, currentVersion, latestVersion, onSelect }) {
6865
- const options = [
6866
- {
6867
- label: `Update right now (will run: npx -y @harperfast/agent@latest)`,
6868
- value: "now"
6869
- },
6870
- { label: "Update later", value: "later" },
6871
- { label: "Don\u2019t ask again", value: "never" }
6872
- ];
6873
- return React21.createElement(
6874
- Box19,
6875
- { flexDirection: "column", padding: 1 },
6876
- React21.createElement(
6877
- Text18,
6878
- null,
6879
- `${chalk6.yellow("Update available:")} ${chalk6.bold(packageName)} ${chalk6.dim(`v${currentVersion}`)} \u2192 ${chalk6.green(`v${latestVersion}`)}`
6880
- ),
6881
- React21.createElement(
6882
- Box19,
6883
- { marginTop: 1 },
6884
- React21.createElement(Select3, {
6885
- options,
6886
- onChange: (v) => onSelect(v)
6887
- })
6888
- )
6889
- );
6890
- }
6891
-
6892
- // utils/shell/ensureApiKey.ts
6893
- function ensureApiKey() {
6894
- const models = [
6895
- trackedState.model,
6896
- trackedState.compactionModel
6897
- ].filter(excludeFalsy);
6898
- const requiredEnvVars = /* @__PURE__ */ new Set();
6899
- for (const model of models) {
6900
- if (model.startsWith("claude-")) {
6901
- requiredEnvVars.add("ANTHROPIC_API_KEY");
6902
- } else if (model.startsWith("gemini-")) {
6903
- requiredEnvVars.add("GOOGLE_GENERATIVE_AI_API_KEY");
6904
- } else if (model.startsWith("ollama-") || model.includes(":")) {
6905
- } else {
6906
- requiredEnvVars.add("OPENAI_API_KEY");
6907
- }
6908
- }
6909
- for (const envVar of requiredEnvVars) {
6910
- if (!process.env[envVar]) {
6911
- return false;
6912
- }
6913
- }
6914
- return true;
6915
- }
6916
-
6917
- // agent.ts
6918
- (async function() {
6919
- setupGlobalErrorHandlers();
6920
- loadEnv();
6921
- process.on("SIGINT", handleExit);
6922
- process.on("SIGTERM", handleExit);
6923
- await checkForUpdate();
6924
- parseArgs();
6925
- if (!ensureApiKey()) {
6926
- resetTrackedState();
6927
- await bootstrapConfig();
6928
- emitToListeners("ExitUI", void 0);
6929
- parseArgs();
6930
- if (!ensureApiKey()) {
6931
- console.log(chalk7.red("No key provided. Exiting."));
6932
- process.exit(1);
6933
- }
6934
- }
6935
- await agentManager.initialize();
6936
- bootstrapMain();
6937
- })();