@harperfast/agent 0.13.6-ink → 0.13.7-ink

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.
Files changed (2) hide show
  1. package/dist/agent.js +1288 -177
  2. package/package.json +2 -2
package/dist/agent.js CHANGED
@@ -1,8 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // agent.ts
4
- import "dotenv/config";
5
- import chalk5 from "chalk";
4
+ import chalk6 from "chalk";
6
5
 
7
6
  // agent/AgentManager.ts
8
7
  import { Agent as Agent3 } from "@openai/agents";
@@ -47,11 +46,24 @@ async function onceListener(name) {
47
46
  function emitToListeners(name, value, trigger) {
48
47
  const listeners = listenersMap[name];
49
48
  if (listeners) {
50
- for (const listener of listeners) {
49
+ const stableCopyOfListeners = listeners.slice();
50
+ for (const listener of stableCopyOfListeners) {
51
51
  listener(value, trigger);
52
52
  }
53
53
  }
54
54
  }
55
+ function addListener(name, callback) {
56
+ if (!listenersMap[name]) {
57
+ listenersMap[name] = [];
58
+ }
59
+ listenersMap[name].push(callback);
60
+ return () => {
61
+ const index = listenersMap[name].indexOf(callback);
62
+ if (index >= 0) {
63
+ listenersMap[name].splice(index, 1);
64
+ }
65
+ };
66
+ }
55
67
  function curryEmitToListeners(name, value, trigger) {
56
68
  return (e) => emitToListeners(name, value, trigger ?? e);
57
69
  }
@@ -68,7 +80,12 @@ var trackedState = {
68
80
  sessionPath: null,
69
81
  useFlexTier: false,
70
82
  maxTurns: 30,
71
- maxCost: null
83
+ maxCost: null,
84
+ autoApproveCodeInterpreter: false,
85
+ autoApprovePatches: false,
86
+ autoApproveShell: false,
87
+ monitorRateLimits: true,
88
+ rateLimitThreshold: 80
72
89
  };
73
90
 
74
91
  // lifecycle/defaultInstructions.ts
@@ -498,7 +515,7 @@ async function execute10({ skill }) {
498
515
 
499
516
  // tools/files/workspaceEditor.ts
500
517
  import { applyDiff } from "@openai/agents";
501
- import { existsSync as existsSync4, readFileSync as readFileSync4 } from "fs";
518
+ import { existsSync as existsSync5, readFileSync as readFileSync4 } from "fs";
502
519
  import { mkdir, rm, writeFile } from "fs/promises";
503
520
  import path3 from "path";
504
521
 
@@ -520,16 +537,53 @@ function normalizeDiff(diff) {
520
537
  import path2 from "path";
521
538
 
522
539
  // utils/files/aiignore.ts
523
- import { existsSync as existsSync3, readFileSync as readFileSync3 } from "fs";
540
+ import { existsSync as existsSync4, readFileSync as readFileSync3 } from "fs";
524
541
  import path from "path";
542
+
543
+ // utils/logger.ts
544
+ import { appendFileSync, existsSync as existsSync3, mkdirSync } from "fs";
545
+ import { homedir } from "os";
546
+ import { dirname as dirname2, join as join4 } from "path";
547
+ var ERROR_LOG_PATH = join4(homedir(), ".harper", "harper-agent-errors");
548
+ function logError(error) {
549
+ const message = error instanceof Error ? error.stack || error.message : String(error);
550
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString();
551
+ const logEntry = `[${timestamp}] ${message}
552
+
553
+ `;
554
+ try {
555
+ const dir = dirname2(ERROR_LOG_PATH);
556
+ if (!existsSync3(dir)) {
557
+ mkdirSync(dir, { recursive: true });
558
+ }
559
+ appendFileSync(ERROR_LOG_PATH, logEntry, "utf8");
560
+ } catch (err) {
561
+ console.error("Failed to write to error log:", err);
562
+ console.error("Original error:", error);
563
+ }
564
+ }
565
+ function setupGlobalErrorHandlers() {
566
+ process.on("uncaughtException", (error) => {
567
+ logError(error);
568
+ console.error("Uncaught Exception:", error);
569
+ process.exit(1);
570
+ });
571
+ process.on("unhandledRejection", (reason) => {
572
+ logError(reason);
573
+ console.error("Unhandled Rejection:", reason);
574
+ });
575
+ }
576
+
577
+ // utils/files/aiignore.ts
525
578
  var ignorePatterns = [];
526
579
  function loadAiIgnore() {
527
580
  const ignorePath = path.join(trackedState.cwd, ".aiignore");
528
- if (existsSync3(ignorePath)) {
581
+ if (existsSync4(ignorePath)) {
529
582
  try {
530
583
  const content = readFileSync3(ignorePath, "utf8");
531
584
  ignorePatterns = content.split(/\r?\n/).map((line) => line.trim()).filter((line) => line && !line.startsWith("#")).map((pattern) => pattern.endsWith("/") || pattern.endsWith("\\") ? pattern.slice(0, -1) : pattern);
532
585
  } catch (error) {
586
+ logError(error);
533
587
  console.error(`Error reading .aiignore: ${error}`);
534
588
  ignorePatterns = [];
535
589
  }
@@ -600,7 +654,7 @@ var WorkspaceEditor = class {
600
654
  async updateFile(operation) {
601
655
  try {
602
656
  const targetPath = resolvePath(this.root(), operation.path);
603
- if (!existsSync4(targetPath)) {
657
+ if (!existsSync5(targetPath)) {
604
658
  return { status: "failed", output: "Error: file not found at path " + targetPath };
605
659
  }
606
660
  const original = readFileSync4(targetPath, "utf8");
@@ -615,7 +669,7 @@ var WorkspaceEditor = class {
615
669
  async deleteFile(operation) {
616
670
  try {
617
671
  const targetPath = resolvePath(this.root(), operation.path);
618
- if (!existsSync4(targetPath)) {
672
+ if (!existsSync5(targetPath)) {
619
673
  return { status: "failed", output: "Error: file not found at path " + targetPath };
620
674
  }
621
675
  await rm(targetPath, { force: true });
@@ -683,7 +737,25 @@ async function needsApproval(runContext, operation, callId) {
683
737
  return false;
684
738
  }
685
739
  const autoApproved = getEnv("HARPER_AGENT_AUTO_APPROVE_PATCHES", "APPLY_PATCH_AUTO_APPROVE") === "1";
686
- return !autoApproved;
740
+ if (autoApproved) {
741
+ if (callId) {
742
+ emitToListeners("RegisterToolInfo", {
743
+ type: operation.type,
744
+ path: operation.path,
745
+ diff: operation.diff,
746
+ callId
747
+ });
748
+ }
749
+ return false;
750
+ }
751
+ emitToListeners("OpenApprovalViewer", {
752
+ type: operation.type,
753
+ path: operation.path,
754
+ diff: operation.diff,
755
+ mode: "ask",
756
+ callId
757
+ });
758
+ return true;
687
759
  } catch (err) {
688
760
  console.error("apply_patch approval step failed:", err);
689
761
  return false;
@@ -694,6 +766,7 @@ async function execute11(operation) {
694
766
  const needed = await requiredSkillForOperation(operation.path, operation.type);
695
767
  if (needed) {
696
768
  const content = await execute10({ skill: needed });
769
+ console.error(`Understanding ${needed} is necessary before applying this patch.`);
697
770
  return { status: "failed, skill guarded", output: content };
698
771
  }
699
772
  switch (operation.type) {
@@ -923,12 +996,28 @@ var codeInterpreterTool = tool17({
923
996
  execute: execute14,
924
997
  needsApproval: needsApproval2
925
998
  });
926
- async function needsApproval2(runContext, { code, language }, callId) {
999
+ async function needsApproval2(runContext, parameters, callId) {
927
1000
  if (callId && runContext.isToolApproved({ toolName: "code_interpreter", callId })) {
928
1001
  return false;
929
1002
  }
930
1003
  const autoApproved = getEnv("HARPER_AGENT_AUTO_APPROVE_CODE_INTERPRETER", "CODE_INTERPRETER_AUTO_APPROVE") === "1";
931
- return !autoApproved;
1004
+ if (autoApproved) {
1005
+ if (callId) {
1006
+ emitToListeners("RegisterToolInfo", {
1007
+ type: "code_interpreter",
1008
+ code: parameters.code,
1009
+ callId
1010
+ });
1011
+ }
1012
+ return false;
1013
+ }
1014
+ emitToListeners("OpenApprovalViewer", {
1015
+ type: "code_interpreter",
1016
+ code: parameters.code,
1017
+ mode: "ask",
1018
+ callId
1019
+ });
1020
+ return true;
932
1021
  }
933
1022
  async function execute14({ code, language }) {
934
1023
  const extension = language === "javascript" ? "js" : "py";
@@ -956,14 +1045,20 @@ import { tool as tool18 } from "@openai/agents";
956
1045
  import { z as z18 } from "zod";
957
1046
 
958
1047
  // utils/files/updateEnv.ts
959
- import { existsSync as existsSync5 } from "fs";
960
- import { readFileSync as readFileSync5, writeFileSync } from "fs";
961
- import { join as join4 } from "path";
1048
+ import { existsSync as existsSync6, mkdirSync as mkdirSync2, readFileSync as readFileSync5, writeFileSync } from "fs";
1049
+ import { homedir as homedir2 } from "os";
1050
+ import { dirname as dirname3, join as join5 } from "path";
962
1051
  function updateEnv(key, value) {
963
1052
  process.env[key] = value;
964
- const envPath = join4(trackedState.cwd, ".env");
1053
+ const topLevelEnvPath = join5(homedir2(), ".harper", "harper-agent-env");
1054
+ const localEnvPath = join5(trackedState.cwd, ".env");
1055
+ const envPath = existsSync6(topLevelEnvPath) || !existsSync6(localEnvPath) ? topLevelEnvPath : localEnvPath;
1056
+ const dir = dirname3(envPath);
1057
+ if (!existsSync6(dir)) {
1058
+ mkdirSync2(dir, { recursive: true });
1059
+ }
965
1060
  let envContent = "";
966
- if (existsSync5(envPath)) {
1061
+ if (existsSync6(envPath)) {
967
1062
  envContent = readFileSync5(envPath, "utf8");
968
1063
  }
969
1064
  const regex = new RegExp(`^${key}=.*`, "m");
@@ -998,6 +1093,8 @@ var setInterpreterAutoApproveTool = tool18({
998
1093
  }
999
1094
  try {
1000
1095
  updateEnv("HARPER_AGENT_AUTO_APPROVE_CODE_INTERPRETER", newValue);
1096
+ trackedState.autoApproveCodeInterpreter = autoApprove;
1097
+ emitToListeners("SettingsUpdated", void 0);
1001
1098
  return `HARPER_AGENT_AUTO_APPROVE_CODE_INTERPRETER has been set to ${newValue} in .env and current process.`;
1002
1099
  } catch (error) {
1003
1100
  return `Error updating .env file: ${error.message}`;
@@ -1026,6 +1123,8 @@ var setPatchAutoApproveTool = tool19({
1026
1123
  }
1027
1124
  try {
1028
1125
  updateEnv("HARPER_AGENT_AUTO_APPROVE_PATCHES", newValue);
1126
+ trackedState.autoApprovePatches = autoApprove;
1127
+ emitToListeners("SettingsUpdated", void 0);
1029
1128
  return `HARPER_AGENT_AUTO_APPROVE_PATCHES has been set to ${newValue} in .env and current process.`;
1030
1129
  } catch (error) {
1031
1130
  return `Error updating .env file: ${error.message}`;
@@ -1054,6 +1153,8 @@ var setShellAutoApproveTool = tool20({
1054
1153
  }
1055
1154
  try {
1056
1155
  updateEnv("HARPER_AGENT_AUTO_APPROVE_SHELL", newValue);
1156
+ trackedState.autoApproveShell = autoApprove;
1157
+ emitToListeners("SettingsUpdated", void 0);
1057
1158
  return `HARPER_AGENT_AUTO_APPROVE_SHELL has been set to ${newValue} in .env and current process.`;
1058
1159
  } catch (error) {
1059
1160
  return `Error updating .env file: ${error.message}`;
@@ -1242,7 +1343,23 @@ TIMEOUT`;
1242
1343
  const foundRiskyCommand = commands.find((command) => isRiskyCommand(command));
1243
1344
  const foundIgnoredInteraction = commands.find((command) => mentionsIgnoredPath(command));
1244
1345
  const autoApproved = getEnv("HARPER_AGENT_AUTO_APPROVE_SHELL", "SHELL_AUTO_APPROVE") === "1" && !foundRiskyCommand && !foundIgnoredInteraction;
1245
- return !autoApproved;
1346
+ if (autoApproved) {
1347
+ if (callId) {
1348
+ emitToListeners("RegisterToolInfo", {
1349
+ type: "shell",
1350
+ commands,
1351
+ callId
1352
+ });
1353
+ }
1354
+ return false;
1355
+ }
1356
+ emitToListeners("OpenApprovalViewer", {
1357
+ type: "shell",
1358
+ commands,
1359
+ mode: "ask",
1360
+ callId
1361
+ });
1362
+ return true;
1246
1363
  }
1247
1364
  });
1248
1365
 
@@ -1449,8 +1566,8 @@ import { z as z29 } from "zod";
1449
1566
  // utils/shell/harperProcess.ts
1450
1567
  import spawn from "cross-spawn";
1451
1568
  import { execSync } from "child_process";
1452
- import { homedir } from "os";
1453
- import { join as join5 } from "path";
1569
+ import { homedir as homedir3 } from "os";
1570
+ import { join as join6 } from "path";
1454
1571
  var HarperProcess = class {
1455
1572
  childProcess = null;
1456
1573
  externalPid = null;
@@ -1486,7 +1603,7 @@ var HarperProcess = class {
1486
1603
  this.stopTailingLogs();
1487
1604
  }
1488
1605
  startTailingLogs() {
1489
- const logPath = join5(homedir(), "hdb", "log", "hdb.log");
1606
+ const logPath = join6(homedir3(), "hdb", "log", "hdb.log");
1490
1607
  this.logTailProcess = spawn("tail", ["-f", logPath], {
1491
1608
  stdio: ["ignore", "pipe", "pipe"]
1492
1609
  });
@@ -1677,7 +1794,7 @@ var createNewHarperApplicationTool = tool30({
1677
1794
  import { tool as tool31 } from "@openai/agents";
1678
1795
  import { readFile as readFile2 } from "fs/promises";
1679
1796
  import { createRequire as createRequire2 } from "module";
1680
- import { dirname as dirname2, join as join6 } from "path";
1797
+ import { dirname as dirname4, join as join7 } from "path";
1681
1798
  import { z as z31 } from "zod";
1682
1799
  var ToolParameters18 = z31.object({
1683
1800
  schemaType: z31.enum(["app", "root"]).describe(
@@ -1691,8 +1808,8 @@ var getHarperConfigSchemaTool = tool31({
1691
1808
  async execute({ schemaType }) {
1692
1809
  try {
1693
1810
  return await readFile2(
1694
- join6(
1695
- dirname2(createRequire2(import.meta.url).resolve("harperdb")),
1811
+ join7(
1812
+ dirname4(createRequire2(import.meta.url).resolve("harperdb")),
1696
1813
  `config-${schemaType}.schema.json`
1697
1814
  ),
1698
1815
  "utf8"
@@ -1707,7 +1824,7 @@ var getHarperConfigSchemaTool = tool31({
1707
1824
  import { tool as tool32 } from "@openai/agents";
1708
1825
  import { readFile as readFile3 } from "fs/promises";
1709
1826
  import { createRequire as createRequire3 } from "module";
1710
- import { dirname as dirname3, join as join7 } from "path";
1827
+ import { dirname as dirname5, join as join8 } from "path";
1711
1828
  import { z as z32 } from "zod";
1712
1829
  var ToolParameters19 = z32.object({
1713
1830
  resourceFile: z32.enum([
@@ -1727,8 +1844,8 @@ var getHarperResourceInterfaceTool = tool32({
1727
1844
  async execute({ resourceFile }) {
1728
1845
  try {
1729
1846
  return await readFile3(
1730
- join7(
1731
- dirname3(createRequire3(import.meta.url).resolve("harperdb")),
1847
+ join8(
1848
+ dirname5(createRequire3(import.meta.url).resolve("harperdb")),
1732
1849
  "resources",
1733
1850
  `${resourceFile}.d.ts`
1734
1851
  ),
@@ -1744,7 +1861,7 @@ var getHarperResourceInterfaceTool = tool32({
1744
1861
  import { tool as tool33 } from "@openai/agents";
1745
1862
  import { readFile as readFile4 } from "fs/promises";
1746
1863
  import { createRequire as createRequire4 } from "module";
1747
- import { dirname as dirname4, join as join8 } from "path";
1864
+ import { dirname as dirname6, join as join9 } from "path";
1748
1865
  import { z as z33 } from "zod";
1749
1866
  var ToolParameters20 = z33.object({});
1750
1867
  var getHarperSchemaGraphQLTool = tool33({
@@ -1754,8 +1871,8 @@ var getHarperSchemaGraphQLTool = tool33({
1754
1871
  async execute() {
1755
1872
  try {
1756
1873
  return await readFile4(
1757
- join8(
1758
- dirname4(createRequire4(import.meta.url).resolve("harperdb")),
1874
+ join9(
1875
+ dirname6(createRequire4(import.meta.url).resolve("harperdb")),
1759
1876
  `schema.graphql`
1760
1877
  ),
1761
1878
  "utf8"
@@ -1842,7 +1959,7 @@ var readHarperLogsTool = tool35({
1842
1959
 
1843
1960
  // tools/harper/startHarperTool.ts
1844
1961
  import { tool as tool36 } from "@openai/agents";
1845
- import { existsSync as existsSync6 } from "fs";
1962
+ import { existsSync as existsSync7 } from "fs";
1846
1963
  import { basename, resolve } from "path";
1847
1964
  import { z as z36 } from "zod";
1848
1965
 
@@ -1869,7 +1986,7 @@ var startHarperTool = tool36({
1869
1986
  try {
1870
1987
  let effectiveDirectory = directoryName;
1871
1988
  const candidatePath = resolve(process.cwd(), directoryName);
1872
- if (!existsSync6(candidatePath)) {
1989
+ if (!existsSync7(candidatePath)) {
1873
1990
  const cwd = process.cwd();
1874
1991
  if (basename(cwd) === directoryName) {
1875
1992
  effectiveDirectory = cwd;
@@ -1907,6 +2024,110 @@ var stopHarperTool = tool37({
1907
2024
  }
1908
2025
  });
1909
2026
 
2027
+ // tools/plan/addPlanItemTool.ts
2028
+ import { tool as tool38 } from "@openai/agents";
2029
+ import { z as z38 } from "zod";
2030
+
2031
+ // ink/contexts/globalPlanContext.ts
2032
+ var globalPlanContext = {
2033
+ planDescription: "",
2034
+ planItems: [],
2035
+ progress: 0
2036
+ };
2037
+
2038
+ // tools/plan/addPlanItemTool.ts
2039
+ var AddPlanItemParameters = z38.object({
2040
+ text: z38.string().describe("The description of the task or milestone to add to the plan.")
2041
+ });
2042
+ var addPlanItemTool = tool38({
2043
+ name: "add_plan_item",
2044
+ description: "Add a new item to the plan.",
2045
+ parameters: AddPlanItemParameters,
2046
+ async execute({ text }) {
2047
+ const newItems = [
2048
+ ...globalPlanContext.planItems,
2049
+ {
2050
+ id: globalPlanContext.planItems.length + 1,
2051
+ text,
2052
+ status: "todo"
2053
+ }
2054
+ ];
2055
+ emitToListeners("SetPlanItems", newItems);
2056
+ return `Added plan item: ${text}`;
2057
+ }
2058
+ });
2059
+
2060
+ // tools/plan/setPlanDescriptionTool.ts
2061
+ import { tool as tool39 } from "@openai/agents";
2062
+ import { z as z39 } from "zod";
2063
+ var SetPlanDescriptionParameters = z39.object({
2064
+ description: z39.string().describe("A high-level description of the overall plan and goals.")
2065
+ });
2066
+ var setPlanDescriptionTool = tool39({
2067
+ name: "set_plan_description",
2068
+ description: "Set the high-level description for the current plan.",
2069
+ parameters: SetPlanDescriptionParameters,
2070
+ async execute({ description }) {
2071
+ emitToListeners("SetPlanDescription", description);
2072
+ return `Plan description updated to: ${description}`;
2073
+ }
2074
+ });
2075
+
2076
+ // tools/plan/setPlanItemsTool.ts
2077
+ import { tool as tool40 } from "@openai/agents";
2078
+ import { z as z40 } from "zod";
2079
+ var SetPlanItemsParameters = z40.object({
2080
+ items: z40.array(z40.string()).describe("An array of task descriptions to set as the plan items.")
2081
+ });
2082
+ var setPlanItemsTool = tool40({
2083
+ name: "set_plan_items",
2084
+ description: "Set multiple plan items at once, replacing any existing items.",
2085
+ parameters: SetPlanItemsParameters,
2086
+ async execute({ items }) {
2087
+ const newItems = items.map((text, index) => ({
2088
+ id: index + 1,
2089
+ text,
2090
+ status: "todo"
2091
+ }));
2092
+ emitToListeners("SetPlanItems", newItems);
2093
+ return `Set ${newItems.length} plan items.`;
2094
+ }
2095
+ });
2096
+
2097
+ // tools/plan/updatePlanItemTool.ts
2098
+ import { tool as tool41 } from "@openai/agents";
2099
+ import { z as z41 } from "zod";
2100
+ var UpdatePlanItemParameters = z41.object({
2101
+ id: z41.number().describe("The ID of the plan item to update."),
2102
+ text: z41.string().describe("The new description of the task."),
2103
+ status: z41.enum(["unchanged", "todo", "in-progress", "done", "not-needed"]).describe(
2104
+ "The new status of the task."
2105
+ )
2106
+ });
2107
+ var updatePlanItemTool = tool41({
2108
+ name: "update_plan_item",
2109
+ description: "Update an existing plan item.",
2110
+ parameters: UpdatePlanItemParameters,
2111
+ async execute({ id, text, status }) {
2112
+ const newItems = globalPlanContext.planItems.map((item) => {
2113
+ if (item.id === id) {
2114
+ return {
2115
+ ...item,
2116
+ text: text || item.text,
2117
+ status: status && status !== "unchanged" ? status : item.status
2118
+ };
2119
+ }
2120
+ return item;
2121
+ });
2122
+ const itemExists = globalPlanContext.planItems.some((item) => item.id === id);
2123
+ if (!itemExists) {
2124
+ return `Error: Plan item with ID ${id} not found.`;
2125
+ }
2126
+ emitToListeners("SetPlanItems", newItems);
2127
+ return `Updated plan item ${id}`;
2128
+ }
2129
+ });
2130
+
1910
2131
  // tools/factory.ts
1911
2132
  function createTools() {
1912
2133
  return [
@@ -1919,6 +2140,7 @@ function createTools() {
1919
2140
  browserNavigateTool,
1920
2141
  browserScreenshotTool,
1921
2142
  browserTypeTool,
2143
+ addPlanItemTool,
1922
2144
  changeCwdTool,
1923
2145
  checkHarperStatusTool,
1924
2146
  codeInterpreterTool,
@@ -1943,10 +2165,13 @@ function createTools() {
1943
2165
  readHarperLogsTool,
1944
2166
  setInterpreterAutoApproveTool,
1945
2167
  setPatchAutoApproveTool,
2168
+ setPlanDescriptionTool,
2169
+ setPlanItemsTool,
1946
2170
  setShellAutoApproveTool,
1947
2171
  shellTool,
1948
2172
  startHarperTool,
1949
- stopHarperTool
2173
+ stopHarperTool,
2174
+ updatePlanItemTool
1950
2175
  ];
1951
2176
  }
1952
2177
 
@@ -2037,9 +2262,9 @@ Stack: ${String(err.stack).split("\n").slice(0, 8).join("\n")}` : "";
2037
2262
 
2038
2263
  // utils/sessions/DiskSession.ts
2039
2264
  import { MemorySession } from "@openai/agents";
2040
- import { existsSync as existsSync7 } from "fs";
2265
+ import { existsSync as existsSync8 } from "fs";
2041
2266
  import { mkdir as mkdir2, readFile as readFile5, rename, writeFile as writeFile3 } from "fs/promises";
2042
- import { dirname as dirname5 } from "path";
2267
+ import { dirname as dirname7 } from "path";
2043
2268
  var DiskSession = class extends MemorySession {
2044
2269
  filePath;
2045
2270
  ready;
@@ -2074,7 +2299,7 @@ var DiskSession = class extends MemorySession {
2074
2299
  }
2075
2300
  }
2076
2301
  async loadStorage() {
2077
- if (existsSync7(this.filePath)) {
2302
+ if (existsSync8(this.filePath)) {
2078
2303
  try {
2079
2304
  const data = await readFile5(this.filePath, "utf-8");
2080
2305
  const parsed = JSON.parse(data);
@@ -2090,8 +2315,8 @@ var DiskSession = class extends MemorySession {
2090
2315
  async updateStorage(update) {
2091
2316
  const storage = await this.loadStorage();
2092
2317
  update(storage);
2093
- const dir = dirname5(this.filePath);
2094
- if (!existsSync7(dir)) {
2318
+ const dir = dirname7(this.filePath);
2319
+ if (!existsSync8(dir)) {
2095
2320
  await mkdir2(dir, { recursive: true });
2096
2321
  }
2097
2322
  const data = JSON.stringify(storage, null, 2);
@@ -2389,7 +2614,7 @@ function createSession(sessionPath = null) {
2389
2614
  }
2390
2615
 
2391
2616
  // agent/runAgentForOnePass.ts
2392
- import { run as run2 } from "@openai/agents";
2617
+ import { run as run2, system as system2 } from "@openai/agents";
2393
2618
 
2394
2619
  // ink/contexts/ActionsContext.tsx
2395
2620
  import { createContext as createContext2, useContext as useContext2, useEffect as useEffect2, useMemo as useMemo2, useState as useState2 } from "react";
@@ -2547,6 +2772,9 @@ var CostTracker = class {
2547
2772
  );
2548
2773
  return this.getTotalCost() + turnCost + compactionCost;
2549
2774
  }
2775
+ extractCachedTokens(inputTokenDetails) {
2776
+ return extractCachedTokens(inputTokenDetails);
2777
+ }
2550
2778
  recordTurn(model, usage, compactionModel) {
2551
2779
  const { turnCost, compactionCost, unknownPrices } = this.calculateUsageCosts(model, usage, compactionModel);
2552
2780
  this.totalInputTokens += usage.inputTokens;
@@ -2614,6 +2842,64 @@ var CostTracker = class {
2614
2842
  };
2615
2843
  var costTracker = new CostTracker();
2616
2844
 
2845
+ // utils/sessions/rateLimits.ts
2846
+ var RateLimitTracker = class {
2847
+ status = {
2848
+ limitRequests: null,
2849
+ limitTokens: null,
2850
+ remainingRequests: null,
2851
+ remainingTokens: null,
2852
+ resetRequests: null,
2853
+ resetTokens: null
2854
+ };
2855
+ updateFromHeaders(headers) {
2856
+ const getHeader = (name) => {
2857
+ const value = headers[name] || headers[name.toLowerCase()];
2858
+ return Array.isArray(value) ? value[0] : value;
2859
+ };
2860
+ const limitRequests = getHeader("x-ratelimit-limit-requests");
2861
+ const limitTokens = getHeader("x-ratelimit-limit-tokens");
2862
+ const remainingRequests = getHeader("x-ratelimit-remaining-requests");
2863
+ const remainingTokens = getHeader("x-ratelimit-remaining-tokens");
2864
+ const resetRequests = getHeader("x-ratelimit-reset-requests");
2865
+ const resetTokens = getHeader("x-ratelimit-reset-tokens");
2866
+ if (limitRequests) {
2867
+ this.status.limitRequests = parseInt(limitRequests, 10);
2868
+ }
2869
+ if (limitTokens) {
2870
+ this.status.limitTokens = parseInt(limitTokens, 10);
2871
+ }
2872
+ if (remainingRequests) {
2873
+ this.status.remainingRequests = parseInt(remainingRequests, 10);
2874
+ }
2875
+ if (remainingTokens) {
2876
+ this.status.remainingTokens = parseInt(remainingTokens, 10);
2877
+ }
2878
+ if (resetRequests) {
2879
+ this.status.resetRequests = resetRequests;
2880
+ }
2881
+ if (resetTokens) {
2882
+ this.status.resetTokens = resetTokens;
2883
+ }
2884
+ }
2885
+ isApproachingLimit(threshold) {
2886
+ const usage = this.getUsagePercentage();
2887
+ return {
2888
+ requests: usage.requests >= threshold,
2889
+ tokens: usage.tokens >= threshold
2890
+ };
2891
+ }
2892
+ getStatus() {
2893
+ return { ...this.status };
2894
+ }
2895
+ getUsagePercentage() {
2896
+ const requests = this.status.limitRequests && this.status.remainingRequests !== null ? 100 * (1 - this.status.remainingRequests / this.status.limitRequests) : 0;
2897
+ const tokens = this.status.limitTokens && this.status.remainingTokens !== null ? 100 * (1 - this.status.remainingTokens / this.status.limitTokens) : 0;
2898
+ return { requests, tokens };
2899
+ }
2900
+ };
2901
+ var rateLimitTracker = new RateLimitTracker();
2902
+
2617
2903
  // utils/strings/isTrue.ts
2618
2904
  function isTrue(v) {
2619
2905
  if (v === void 0) {
@@ -2625,6 +2911,7 @@ function isTrue(v) {
2625
2911
 
2626
2912
  // agent/showErrorToUser.ts
2627
2913
  function showErrorToUser(error, lastToolCallInfo) {
2914
+ logError(error);
2628
2915
  const err = error ?? {};
2629
2916
  const name = err.name || "Error";
2630
2917
  const message = err.message || String(err);
@@ -2663,15 +2950,106 @@ Last tool call: ${lastToolCallInfo}` : "";
2663
2950
  // agent/runAgentForOnePass.ts
2664
2951
  async function runAgentForOnePass(agent, session, input, controller) {
2665
2952
  let lastToolCallInfo = null;
2953
+ const toolInfoMap = /* @__PURE__ */ new Map();
2954
+ const removeToolListener = addListener("RegisterToolInfo", (info) => {
2955
+ toolInfoMap.set(info.callId, info);
2956
+ });
2666
2957
  try {
2667
2958
  let hasStartedResponse = false;
2668
- const stream = await run2(agent, input, {
2959
+ let adjustedInput = input;
2960
+ const noPlanYet = globalPlanContext.planItems.length === 0 && (!globalPlanContext.planDescription || globalPlanContext.planDescription.trim().length === 0);
2961
+ if (noPlanYet && (typeof input === "string" || Array.isArray(input))) {
2962
+ const planningInstruction = [
2963
+ "If there is no current plan, first establish one and keep it updated:",
2964
+ "- Use the tools to manage the plan:",
2965
+ " \u2022 set_plan_description(description)",
2966
+ " \u2022 set_plan_items(items: string[])",
2967
+ " \u2022 add_plan_item(text)",
2968
+ " \u2022 update_plan_item(id, text, status: 'todo' | 'in-progress' | 'done' | 'not-needed' | 'unchanged')",
2969
+ "- After setting the plan, as you progress, mark items as in-progress, done, or not-needed.",
2970
+ "- Keep the plan concise and actionable. Update statuses as you move forward."
2971
+ ].join("\n");
2972
+ if (typeof input === "string") {
2973
+ adjustedInput = [
2974
+ system2(planningInstruction),
2975
+ { type: "message", role: "user", content: input }
2976
+ ];
2977
+ } else {
2978
+ adjustedInput = [system2(planningInstruction), ...input];
2979
+ }
2980
+ }
2981
+ const stream = await run2(agent, adjustedInput, {
2669
2982
  session,
2670
2983
  stream: true,
2671
2984
  signal: controller.signal,
2672
2985
  maxTurns: trackedState.maxTurns
2673
2986
  });
2674
2987
  for await (const event of stream) {
2988
+ if (trackedState.monitorRateLimits) {
2989
+ const { requests, tokens } = rateLimitTracker.isApproachingLimit(trackedState.rateLimitThreshold);
2990
+ const veryCloseThreshold = Math.min(99, trackedState.rateLimitThreshold + 15);
2991
+ const veryClose = rateLimitTracker.isApproachingLimit(veryCloseThreshold);
2992
+ if (veryClose.requests || veryClose.tokens) {
2993
+ emitToListeners("SetInputMode", "approving");
2994
+ emitToListeners("PushNewMessages", [{
2995
+ type: "agent",
2996
+ text: "Rate limit nearly exhausted. Approve to continue or wait for reset.",
2997
+ version: 1
2998
+ }]);
2999
+ const approval = await new Promise((resolve2) => {
3000
+ const removeApprove = addListener("ApproveCurrentApproval", () => {
3001
+ removeApprove();
3002
+ removeDeny();
3003
+ resolve2("approved");
3004
+ });
3005
+ const removeDeny = addListener("DenyCurrentApproval", () => {
3006
+ removeApprove();
3007
+ removeDeny();
3008
+ resolve2("denied");
3009
+ });
3010
+ });
3011
+ if (approval === "denied") {
3012
+ emitToListeners("SetInputMode", "denied");
3013
+ emitToListeners("PushNewMessages", [{
3014
+ type: "agent",
3015
+ text: "Operation canceled due to rate limits.",
3016
+ version: 1
3017
+ }]);
3018
+ if (controller) {
3019
+ controller.abort();
3020
+ }
3021
+ process.exitCode = 1;
3022
+ await handleExit();
3023
+ }
3024
+ } else if (requests || tokens) {
3025
+ emitToListeners("PushNewMessages", [{ type: "agent", text: "Throttling to avoid rate limits\u2026", version: 1 }]);
3026
+ const status = rateLimitTracker.getStatus();
3027
+ let backoffMs = 1500;
3028
+ const parseReset = (s) => {
3029
+ if (!s) {
3030
+ return null;
3031
+ }
3032
+ const ms = /([0-9]+)ms/.exec(s)?.[1];
3033
+ if (ms) {
3034
+ return parseInt(ms, 10);
3035
+ }
3036
+ const sec = /([0-9]+)s/.exec(s)?.[1];
3037
+ if (sec) {
3038
+ return parseInt(sec, 10) * 1e3;
3039
+ }
3040
+ const min = /([0-9]+)m/.exec(s)?.[1];
3041
+ if (min) {
3042
+ return parseInt(min, 10) * 6e4;
3043
+ }
3044
+ return null;
3045
+ };
3046
+ const resets = [parseReset(status.resetRequests || void 0), parseReset(status.resetTokens || void 0)].filter(Boolean);
3047
+ if (resets.length > 0) {
3048
+ backoffMs = Math.max(backoffMs, Math.min(...resets));
3049
+ }
3050
+ await sleep(backoffMs);
3051
+ }
3052
+ }
2675
3053
  switch (event.type) {
2676
3054
  case "raw_model_stream_event":
2677
3055
  const data = event.data;
@@ -2698,6 +3076,19 @@ async function runAgentForOnePass(agent, session, input, controller) {
2698
3076
  emitToListeners("SetInputMode", "waiting");
2699
3077
  break;
2700
3078
  }
3079
+ const currentEstimatedCost = costTracker.getEstimatedTotalCost(
3080
+ stream.state.usage,
3081
+ trackedState.model,
3082
+ trackedState.compactionModel
3083
+ );
3084
+ const sessionStats2 = costTracker.getSessionStats();
3085
+ emitToListeners("UpdateCost", {
3086
+ totalCost: currentEstimatedCost,
3087
+ inputTokens: sessionStats2.inputTokens + stream.state.usage.inputTokens,
3088
+ outputTokens: sessionStats2.outputTokens + stream.state.usage.outputTokens,
3089
+ cachedInputTokens: sessionStats2.cachedInputTokens + costTracker.extractCachedTokens(stream.state.usage.inputTokensDetails),
3090
+ hasUnknownPrices: sessionStats2.hasUnknownPrices
3091
+ });
2701
3092
  break;
2702
3093
  case "run_item_stream_event":
2703
3094
  if (event.name === "tool_called") {
@@ -2711,17 +3102,20 @@ async function runAgentForOnePass(agent, session, input, controller) {
2711
3102
  args = JSON.stringify(item.operation);
2712
3103
  }
2713
3104
  const displayedArgs = args ? `(${args})` : "()";
3105
+ const callId = item.callId || item.id;
2714
3106
  emitToListeners("PushNewMessages", [{
2715
3107
  type: "tool",
2716
3108
  text: name,
2717
3109
  args: displayedArgs,
2718
- version: 1
3110
+ version: 1,
3111
+ callId
2719
3112
  }]);
2720
3113
  emitToListeners("AddActionItem", {
2721
3114
  kind: name === "apply_patch" || item.type === "apply_patch_call" ? "apply_patch" : name === "create_new_harper_application" ? "create_app" : "tool",
2722
3115
  title: name,
2723
3116
  detail: displayedArgs,
2724
- running: false
3117
+ running: false,
3118
+ callId
2725
3119
  });
2726
3120
  lastToolCallInfo = `${name}${displayedArgs}`;
2727
3121
  }
@@ -2755,31 +3149,59 @@ async function runAgentForOnePass(agent, session, input, controller) {
2755
3149
  trackedState.model,
2756
3150
  trackedState.compactionModel
2757
3151
  );
3152
+ const sessionStats = costTracker.getSessionStats();
2758
3153
  emitToListeners("UpdateCost", {
2759
- ...costTracker.getSessionStats(),
2760
- totalCost: estimatedTotalCost
3154
+ totalCost: estimatedTotalCost,
3155
+ inputTokens: sessionStats.inputTokens + stream.state.usage.inputTokens,
3156
+ outputTokens: sessionStats.outputTokens + stream.state.usage.outputTokens,
3157
+ cachedInputTokens: sessionStats.cachedInputTokens + costTracker.extractCachedTokens(stream.state.usage.inputTokensDetails),
3158
+ hasUnknownPrices: sessionStats.hasUnknownPrices
2761
3159
  });
2762
3160
  if (stream.interruptions?.length) {
2763
3161
  emitToListeners("SetThinking", false);
2764
3162
  emitToListeners("SetInputMode", "approving");
2765
3163
  for (const interruption of stream.interruptions) {
3164
+ const callId = interruption.callId || interruption.id;
3165
+ const toolName = interruption.toolName;
3166
+ const isModalTool = toolName === "apply_patch" || toolName === "code_interpreter" || toolName === "shell";
2766
3167
  const myApprovalId = actionId;
2767
3168
  emitToListeners("AddActionItem", {
2768
3169
  id: myApprovalId,
2769
- kind: "approval",
2770
- title: "approval",
3170
+ kind: isModalTool ? toolName === "apply_patch" ? "apply_patch" : "approval" : "approval",
3171
+ title: isModalTool ? toolName : "approval",
2771
3172
  detail: lastToolCallInfo ?? "awaiting approval",
2772
- running: true
3173
+ running: true,
3174
+ callId
2773
3175
  });
2774
- const newMessages = await onceListener("PushNewMessages");
2775
- let approved = false;
2776
- for (const newMessage of newMessages) {
2777
- if (newMessage.type === "user" && isTrue(newMessage.text)) {
2778
- approved = true;
2779
- }
2780
- newMessage.handled = true;
3176
+ const approvalPromise = new Promise((resolve2) => {
3177
+ const removeApprove = addListener("ApproveCurrentApproval", () => {
3178
+ removeApprove();
3179
+ removeDeny();
3180
+ resolve2("approved");
3181
+ });
3182
+ const removeDeny = addListener("DenyCurrentApproval", () => {
3183
+ removeApprove();
3184
+ removeDeny();
3185
+ resolve2("denied");
3186
+ });
3187
+ });
3188
+ let result;
3189
+ if (isModalTool) {
3190
+ result = await approvalPromise;
3191
+ } else {
3192
+ const textInputPromise = onceListener("PushNewMessages").then((messages) => {
3193
+ let approved = false;
3194
+ for (const newMessage of messages) {
3195
+ if (newMessage.type === "user" && isTrue(newMessage.text)) {
3196
+ approved = true;
3197
+ }
3198
+ newMessage.handled = true;
3199
+ }
3200
+ return approved ? "approved" : "denied";
3201
+ });
3202
+ result = await Promise.race([approvalPromise, textInputPromise]);
2781
3203
  }
2782
- if (approved) {
3204
+ if (result === "approved") {
2783
3205
  emitToListeners("SetInputMode", "approved");
2784
3206
  emitToListeners("UpdateActionItem", {
2785
3207
  id: myApprovalId,
@@ -2798,9 +3220,11 @@ async function runAgentForOnePass(agent, session, input, controller) {
2798
3220
  });
2799
3221
  stream.state.reject(interruption);
2800
3222
  }
3223
+ emitToListeners("CloseApprovalViewer", void 0);
2801
3224
  }
2802
3225
  emitToListeners("SetThinking", true);
2803
3226
  setTimeout(curryEmitToListeners("SetInputMode", "waiting"), 1e3);
3227
+ removeToolListener();
2804
3228
  return stream.state;
2805
3229
  } else {
2806
3230
  costTracker.recordTurn(
@@ -2810,6 +3234,7 @@ async function runAgentForOnePass(agent, session, input, controller) {
2810
3234
  );
2811
3235
  emitToListeners("UpdateCost", costTracker.getSessionStats());
2812
3236
  }
3237
+ removeToolListener();
2813
3238
  return null;
2814
3239
  } catch (error) {
2815
3240
  showErrorToUser(error, lastToolCallInfo);
@@ -2931,8 +3356,8 @@ import "react";
2931
3356
 
2932
3357
  // ink/components/ChatContent.tsx
2933
3358
  import { Spinner as Spinner2 } from "@inkjs/ui";
2934
- import { Box as Box10, Text as Text10, useInput as useInput3 } from "ink";
2935
- import React11, { useCallback as useCallback4, useEffect as useEffect7, useMemo as useMemo10, useRef as useRef2, useState as useState11 } from "react";
3359
+ import { Box as Box10, Text as Text10, useInput as useInput4 } from "ink";
3360
+ import React11, { useCallback as useCallback4, useEffect as useEffect8, useMemo as useMemo10, useRef as useRef2, useState as useState13 } from "react";
2936
3361
 
2937
3362
  // ink/contexts/ChatContext.tsx
2938
3363
  import { createContext as createContext3, useContext as useContext3, useMemo as useMemo3, useState as useState3 } from "react";
@@ -3496,6 +3921,19 @@ function ActionsView({ height, isFocused }) {
3496
3921
  if (key.downArrow) {
3497
3922
  setSelectedIndex((prev) => Math.min(actions.length - 1, prev + 1));
3498
3923
  }
3924
+ if (key.return) {
3925
+ const selected = actions[selectedIndex];
3926
+ if (selected && (selected.kind === "apply_patch" || selected.kind === "approval" || selected.title === "shell" || selected.title === "code_interpreter")) {
3927
+ if (selected.callId) {
3928
+ emitToListeners("OpenApprovalViewer", {
3929
+ type: selected.kind === "apply_patch" ? "update_file" : selected.title,
3930
+ mode: "info",
3931
+ callId: selected.callId,
3932
+ actionId: selected.id
3933
+ });
3934
+ }
3935
+ }
3936
+ }
3499
3937
  });
3500
3938
  const renderOverflowTop = useCallback2((count) => /* @__PURE__ */ jsxs3(Box3, { children: [
3501
3939
  /* @__PURE__ */ jsx6(Text3, { color: "gray", dimColor: true, children: "\u2502" }),
@@ -3650,15 +4088,6 @@ import "react";
3650
4088
 
3651
4089
  // ink/contexts/PlanContext.tsx
3652
4090
  import { createContext as createContext5, useContext as useContext5, useMemo as useMemo6, useState as useState8 } from "react";
3653
-
3654
- // ink/contexts/globalPlanContext.ts
3655
- var globalPlanContext = {
3656
- planDescription: "",
3657
- planItems: [],
3658
- progress: 0
3659
- };
3660
-
3661
- // ink/contexts/PlanContext.tsx
3662
4091
  import { jsx as jsx10 } from "react/jsx-runtime";
3663
4092
  var PlanContext = createContext5(globalPlanContext);
3664
4093
  var usePlan = () => {
@@ -3671,26 +4100,26 @@ var usePlan = () => {
3671
4100
  var PlanProvider = ({
3672
4101
  children
3673
4102
  }) => {
3674
- const [goal, setGoal] = useState8(globalPlanContext.planDescription);
3675
- const [progress, setProgress] = useState8(globalPlanContext.progress);
4103
+ const [planDescription, setPlanDescription] = useState8(globalPlanContext.planDescription);
3676
4104
  const [planItems, setPlanItems] = useState8(globalPlanContext.planItems);
4105
+ const [progress, setProgress] = useState8(globalPlanContext.progress);
4106
+ useListener("SetPlanDescription", (newGoal) => {
4107
+ globalPlanContext.planDescription = newGoal;
4108
+ setPlanDescription(newGoal);
4109
+ }, []);
3677
4110
  useListener("SetPlanItems", (planItems2) => {
3678
4111
  globalPlanContext.planItems = planItems2;
3679
- const completedCount = planItems2.filter((item) => item.completed).length;
4112
+ const completedCount = planItems2.filter((item) => item.status === "done" || item.status === "not-needed").length;
3680
4113
  const progress2 = planItems2.length === 0 ? 0 : Math.round(completedCount / planItems2.length * 100);
3681
4114
  globalPlanContext.progress = progress2;
3682
4115
  setPlanItems(planItems2);
3683
4116
  setProgress(progress2);
3684
4117
  }, []);
3685
- useListener("SetGoal", (newGoal) => {
3686
- globalPlanContext.planDescription = newGoal;
3687
- setGoal(newGoal);
3688
- }, []);
3689
4118
  const value = useMemo6(() => ({
3690
4119
  progress,
3691
- goal,
4120
+ planDescription,
3692
4121
  planItems
3693
- }), [progress, goal, planItems]);
4122
+ }), [progress, planDescription, planItems]);
3694
4123
  return /* @__PURE__ */ jsx10(PlanContext.Provider, { value, children });
3695
4124
  };
3696
4125
 
@@ -3700,10 +4129,17 @@ function PlanView() {
3700
4129
  const { planDescription, planItems, progress } = usePlan();
3701
4130
  return /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", flexGrow: 1, children: [
3702
4131
  /* @__PURE__ */ jsx11(Box6, { flexDirection: "column", marginBottom: 1, children: /* @__PURE__ */ jsx11(Text6, { italic: true, children: planDescription }) }),
3703
- /* @__PURE__ */ jsx11(Box6, { flexDirection: "column", flexGrow: 1, children: planItems.map((planItem) => /* @__PURE__ */ jsx11(Box6, { children: /* @__PURE__ */ jsxs6(Text6, { color: planItem.completed ? "green" : "white", children: [
3704
- planItem.completed ? " \u25CF " : " \u25CB ",
3705
- planItem.text
3706
- ] }) }, planItem.id)) }),
4132
+ /* @__PURE__ */ jsx11(Box6, { flexDirection: "column", flexGrow: 1, children: planItems.map((planItem) => /* @__PURE__ */ jsx11(Box6, { children: /* @__PURE__ */ jsxs6(
4133
+ Text6,
4134
+ {
4135
+ color: planItem.status === "done" ? "green" : planItem.status === "in-progress" ? "yellow" : planItem.status === "not-needed" ? "gray" : "white",
4136
+ dimColor: planItem.status === "not-needed",
4137
+ children: [
4138
+ planItem.status === "done" ? " \u25CF " : planItem.status === "in-progress" ? " \u25B6 " : planItem.status === "not-needed" ? " \u25CC " : " \u25CB ",
4139
+ planItem.text
4140
+ ]
4141
+ }
4142
+ ) }, planItem.id)) }),
3707
4143
  /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", marginTop: 1, children: [
3708
4144
  /* @__PURE__ */ jsxs6(Text6, { bold: true, children: [
3709
4145
  "PROGRESS: ",
@@ -3716,12 +4152,12 @@ function PlanView() {
3716
4152
  }
3717
4153
 
3718
4154
  // ink/components/SettingsView.tsx
3719
- import { Box as Box7, Text as Text7 } from "ink";
4155
+ import { Box as Box7, Text as Text7, useInput as useInput2 } from "ink";
3720
4156
  import path7 from "path";
3721
- import { useMemo as useMemo8 } from "react";
4157
+ import { useEffect as useEffect6, useMemo as useMemo8, useState as useState10 } from "react";
3722
4158
 
3723
4159
  // ink/contexts/SettingsContext.tsx
3724
- import { createContext as createContext6, useContext as useContext6, useMemo as useMemo7 } from "react";
4160
+ import { createContext as createContext6, useContext as useContext6, useMemo as useMemo7, useState as useState9 } from "react";
3725
4161
  import { jsx as jsx12 } from "react/jsx-runtime";
3726
4162
  var SettingsContext = createContext6(void 0);
3727
4163
  var useSettings = () => {
@@ -3734,22 +4170,128 @@ var useSettings = () => {
3734
4170
  var SettingsProvider = ({
3735
4171
  children
3736
4172
  }) => {
4173
+ const [version, setVersion] = useState9(0);
4174
+ useListener("SettingsUpdated", () => {
4175
+ setVersion((v) => v + 1);
4176
+ }, []);
3737
4177
  const value = useMemo7(() => ({
4178
+ version,
3738
4179
  model: trackedState.model,
3739
4180
  compactionModel: trackedState.compactionModel,
3740
4181
  sessionPath: trackedState.sessionPath,
3741
4182
  cwd: trackedState.cwd,
3742
4183
  useFlexTier: trackedState.useFlexTier,
3743
4184
  maxTurns: trackedState.maxTurns,
3744
- maxCost: trackedState.maxCost
3745
- }), []);
4185
+ maxCost: trackedState.maxCost,
4186
+ autoApproveCodeInterpreter: trackedState.autoApproveCodeInterpreter,
4187
+ autoApprovePatches: trackedState.autoApprovePatches,
4188
+ autoApproveShell: trackedState.autoApproveShell,
4189
+ monitorRateLimits: trackedState.monitorRateLimits,
4190
+ rateLimitThreshold: trackedState.rateLimitThreshold,
4191
+ rateLimitStatus: rateLimitTracker.getStatus()
4192
+ }), [version]);
3746
4193
  return /* @__PURE__ */ jsx12(SettingsContext.Provider, { value, children });
3747
4194
  };
3748
4195
 
3749
4196
  // ink/components/SettingsView.tsx
3750
4197
  import { jsx as jsx13, jsxs as jsxs7 } from "react/jsx-runtime";
3751
4198
  function SettingsView({ isDense = false }) {
3752
- const { model, compactionModel, sessionPath, cwd, useFlexTier, maxTurns, maxCost } = useSettings();
4199
+ const {
4200
+ model,
4201
+ compactionModel,
4202
+ sessionPath,
4203
+ cwd,
4204
+ useFlexTier,
4205
+ maxTurns,
4206
+ maxCost,
4207
+ autoApproveCodeInterpreter: initialAutoApproveCodeInterpreter,
4208
+ autoApprovePatches: initialAutoApprovePatches,
4209
+ autoApproveShell: initialAutoApproveShell,
4210
+ monitorRateLimits: initialMonitorRateLimits,
4211
+ rateLimitThreshold: initialRateLimitThreshold,
4212
+ rateLimitStatus
4213
+ } = useSettings();
4214
+ const { focusedArea } = useChat();
4215
+ const [autoApproveCodeInterpreter, setAutoApproveCodeInterpreter] = useState10(initialAutoApproveCodeInterpreter);
4216
+ const [autoApprovePatches, setAutoApprovePatches] = useState10(initialAutoApprovePatches);
4217
+ const [autoApproveShell, setAutoApproveShell] = useState10(initialAutoApproveShell);
4218
+ const [monitorRateLimits, setMonitorRateLimits] = useState10(initialMonitorRateLimits);
4219
+ useEffect6(() => {
4220
+ setAutoApproveCodeInterpreter(initialAutoApproveCodeInterpreter);
4221
+ }, [initialAutoApproveCodeInterpreter]);
4222
+ useEffect6(() => {
4223
+ setAutoApprovePatches(initialAutoApprovePatches);
4224
+ }, [initialAutoApprovePatches]);
4225
+ useEffect6(() => {
4226
+ setAutoApproveShell(initialAutoApproveShell);
4227
+ }, [initialAutoApproveShell]);
4228
+ useEffect6(() => {
4229
+ setMonitorRateLimits(initialMonitorRateLimits);
4230
+ }, [initialMonitorRateLimits]);
4231
+ const [selectedIndex, setSelectedIndex] = useState10(0);
4232
+ const selectableOptions = useMemo8(() => [
4233
+ {
4234
+ label: "Code Interpreter",
4235
+ value: autoApproveCodeInterpreter,
4236
+ envKey: "HARPER_AGENT_AUTO_APPROVE_CODE_INTERPRETER",
4237
+ setter: setAutoApproveCodeInterpreter
4238
+ },
4239
+ {
4240
+ label: "File Patches",
4241
+ value: autoApprovePatches,
4242
+ envKey: "HARPER_AGENT_AUTO_APPROVE_PATCHES",
4243
+ setter: setAutoApprovePatches
4244
+ },
4245
+ {
4246
+ label: "Shell Commands",
4247
+ value: autoApproveShell,
4248
+ envKey: "HARPER_AGENT_AUTO_APPROVE_SHELL",
4249
+ setter: setAutoApproveShell
4250
+ },
4251
+ {
4252
+ label: "Monitor Rate Limits",
4253
+ value: monitorRateLimits,
4254
+ envKey: "HARPER_AGENT_MONITOR_RATE_LIMITS",
4255
+ setter: (val) => {
4256
+ setMonitorRateLimits(val);
4257
+ updateEnv("HARPER_AGENT_MONITOR_RATE_LIMITS", val ? "true" : "false");
4258
+ emitToListeners("SettingsUpdated", void 0);
4259
+ }
4260
+ },
4261
+ {
4262
+ label: "<edit settings>",
4263
+ isAction: true,
4264
+ action: () => {
4265
+ bootstrapConfig(() => {
4266
+ });
4267
+ }
4268
+ }
4269
+ ], [autoApproveCodeInterpreter, autoApprovePatches, autoApproveShell, monitorRateLimits]);
4270
+ useInput2((_input, key) => {
4271
+ if (focusedArea !== "status") {
4272
+ return;
4273
+ }
4274
+ if (key.upArrow) {
4275
+ setSelectedIndex((prev) => prev > 0 ? prev - 1 : selectableOptions.length - 1);
4276
+ }
4277
+ if (key.downArrow) {
4278
+ setSelectedIndex((prev) => prev < selectableOptions.length - 1 ? prev + 1 : 0);
4279
+ }
4280
+ if (key.return || _input === " ") {
4281
+ const selected = selectableOptions[selectedIndex];
4282
+ if (!selected) {
4283
+ return;
4284
+ }
4285
+ if ("isAction" in selected && selected.isAction) {
4286
+ selected.action();
4287
+ } else if ("setter" in selected) {
4288
+ const newValue = !selected.value;
4289
+ selected.setter(newValue);
4290
+ updateEnv(selected.envKey, newValue ? "1" : "0");
4291
+ emitToListeners("SettingsUpdated", void 0);
4292
+ }
4293
+ }
4294
+ });
3753
4295
  const displayPath = useMemo8(() => {
3754
4296
  if (!sessionPath) {
3755
4297
  return null;
@@ -3783,24 +4325,67 @@ function SettingsView({ isDense = false }) {
3783
4325
  /* @__PURE__ */ jsx13(Box7, { width: 20, children: /* @__PURE__ */ jsx13(Text7, { children: "Max Turns:" }) }),
3784
4326
  /* @__PURE__ */ jsx13(Text7, { children: maxTurns })
3785
4327
  ] }),
4328
+ /* @__PURE__ */ jsxs7(Box7, { marginBottom, children: [
4329
+ /* @__PURE__ */ jsx13(Box7, { width: 20, children: /* @__PURE__ */ jsx13(Text7, { children: "Rate Limit Threshold:" }) }),
4330
+ /* @__PURE__ */ jsxs7(Text7, { children: [
4331
+ initialRateLimitThreshold,
4332
+ "%"
4333
+ ] })
4334
+ ] }),
4335
+ rateLimitStatus && rateLimitStatus.limitRequests !== null && /* @__PURE__ */ jsxs7(Box7, { marginBottom, flexDirection: "column", children: [
4336
+ /* @__PURE__ */ jsxs7(Box7, { children: [
4337
+ /* @__PURE__ */ jsx13(Box7, { width: 20, children: /* @__PURE__ */ jsx13(Text7, { children: "RPM Limit:" }) }),
4338
+ /* @__PURE__ */ jsxs7(Text7, { children: [
4339
+ rateLimitStatus.remainingRequests,
4340
+ " / ",
4341
+ rateLimitStatus.limitRequests,
4342
+ " (Reset:",
4343
+ " ",
4344
+ rateLimitStatus.resetRequests,
4345
+ ")"
4346
+ ] })
4347
+ ] }),
4348
+ /* @__PURE__ */ jsxs7(Box7, { children: [
4349
+ /* @__PURE__ */ jsx13(Box7, { width: 20, children: /* @__PURE__ */ jsx13(Text7, { children: "TPM Limit:" }) }),
4350
+ /* @__PURE__ */ jsxs7(Text7, { children: [
4351
+ rateLimitStatus.remainingTokens,
4352
+ " / ",
4353
+ rateLimitStatus.limitTokens,
4354
+ " (Reset: ",
4355
+ rateLimitStatus.resetTokens,
4356
+ ")"
4357
+ ] })
4358
+ ] })
4359
+ ] }),
3786
4360
  maxCost !== null && /* @__PURE__ */ jsxs7(Box7, { marginBottom, children: [
3787
4361
  /* @__PURE__ */ jsx13(Box7, { width: 20, children: /* @__PURE__ */ jsx13(Text7, { children: "Max Cost:" }) }),
3788
4362
  /* @__PURE__ */ jsxs7(Text7, { children: [
3789
4363
  "$",
3790
4364
  maxCost.toFixed(2)
3791
4365
  ] })
4366
+ ] }),
4367
+ /* @__PURE__ */ jsxs7(Box7, { marginTop: 1, flexDirection: "column", children: [
4368
+ /* @__PURE__ */ jsx13(Text7, { bold: true, children: "Auto-approvals (up/down & space to toggle):" }),
4369
+ selectableOptions.map((option, index) => {
4370
+ const isSelected = index === selectedIndex && focusedArea === "status";
4371
+ return /* @__PURE__ */ jsx13(Box7, { children: /* @__PURE__ */ jsxs7(Text7, { color: isSelected ? "cyan" : "white", children: [
4372
+ isSelected ? "> " : " ",
4373
+ option.label,
4374
+ "isAction" in option ? "" : `: ${option.value ? "ON" : "OFF"}`
4375
+ ] }) }, option.label);
4376
+ })
3792
4377
  ] })
3793
4378
  ] });
3794
4379
  }
3795
4380
 
3796
4381
  // ink/components/UserInput.tsx
3797
4382
  import { Box as Box9, Text as Text9 } from "ink";
3798
- import { useCallback as useCallback3, useState as useState10 } from "react";
4383
+ import { useCallback as useCallback3, useState as useState12 } from "react";
3799
4384
 
3800
4385
  // ink/components/BlinkingTextInput.tsx
3801
4386
  import chalk3 from "chalk";
3802
- import { Box as Box8, Text as Text8, useInput as useInput2 } from "ink";
3803
- import { useEffect as useEffect6, useMemo as useMemo9, useReducer, useState as useState9 } from "react";
4387
+ import { Box as Box8, Text as Text8, useInput as useInput3 } from "ink";
4388
+ import { useEffect as useEffect7, useMemo as useMemo9, useReducer, useState as useState11 } from "react";
3804
4389
  import { jsx as jsx14 } from "react/jsx-runtime";
3805
4390
  var reducer = (state, action) => {
3806
4391
  switch (action.type) {
@@ -3854,8 +4439,8 @@ function BlinkingTextInput({
3854
4439
  previousValue: defaultValue,
3855
4440
  cursorOffset: defaultValue.length
3856
4441
  });
3857
- const [isCursorVisible, setIsCursorVisible] = useState9(true);
3858
- useEffect6(() => {
4442
+ const [isCursorVisible, setIsCursorVisible] = useState11(true);
4443
+ useEffect7(() => {
3859
4444
  if (isDisabled) {
3860
4445
  setIsCursorVisible(false);
3861
4446
  return;
@@ -3865,7 +4450,7 @@ function BlinkingTextInput({
3865
4450
  }, 500);
3866
4451
  return () => clearInterval(interval);
3867
4452
  }, [isDisabled]);
3868
- useEffect6(() => {
4453
+ useEffect7(() => {
3869
4454
  if (state.value !== state.previousValue) {
3870
4455
  onChange?.(state.value);
3871
4456
  }
@@ -3876,7 +4461,7 @@ function BlinkingTextInput({
3876
4461
  }
3877
4462
  return suggestions.find((s) => s.startsWith(state.value))?.slice(state.value.length) || "";
3878
4463
  }, [state.value, suggestions]);
3879
- useInput2(
4464
+ useInput3(
3880
4465
  (input, key) => {
3881
4466
  if (key.upArrow || key.downArrow || key.ctrl && input === "c" || key.tab || key.shift && key.tab) {
3882
4467
  return;
@@ -3965,8 +4550,11 @@ var modeSuggestion = {
3965
4550
  };
3966
4551
  function UserInput() {
3967
4552
  const { userInputMode, focusedArea } = useChat();
3968
- const [resetKey, setResetKey] = useState10(0);
3969
- const [, setBlankLines] = useState10(0);
4553
+ const [resetKey, setResetKey] = useState12(0);
4554
+ const [, setBlankLines] = useState12(0);
4555
+ useListener("ClearUserInput", () => {
4556
+ setResetKey((prev) => prev + 1);
4557
+ }, []);
3970
4558
  const borderColor = focusedArea === "input" ? "cyan" : "gray";
3971
4559
  const placeholder = calculatePlaceholder(userInputMode);
3972
4560
  const onSubmitResetKey = useCallback3((value) => {
@@ -4030,12 +4618,12 @@ function ChatContent() {
4030
4618
  const { messages, isThinking, focusedArea, setFocusedArea } = useChat();
4031
4619
  const size = useTerminalSize();
4032
4620
  useMessageListener();
4033
- const [activeTab, setActiveTab] = useState11("settings");
4034
- const [selectedIndex, setSelectedIndex] = useState11(0);
4621
+ const [activeTab, setActiveTab] = useState13("settings");
4622
+ const [selectedIndex, setSelectedIndex] = useState13(0);
4035
4623
  const wrapCache = useRef2(
4036
4624
  /* @__PURE__ */ new Map()
4037
4625
  );
4038
- useInput3((input, key) => {
4626
+ useInput4((input, key) => {
4039
4627
  if (key.tab) {
4040
4628
  const focusOrder = ["input", "timeline", "status"];
4041
4629
  const currentIndex = focusOrder.indexOf(focusedArea);
@@ -4055,6 +4643,19 @@ function ChatContent() {
4055
4643
  if (key.downArrow) {
4056
4644
  setSelectedIndex((prev) => Math.min(Math.max(0, lineItems.length - 1), prev + 1));
4057
4645
  }
4646
+ if (key.return) {
4647
+ const selected = lineItems[selectedIndex];
4648
+ if (selected && selected.type === "tool" && (selected.toolName === "apply_patch" || selected.toolName === "code_interpreter" || selected.toolName === "shell")) {
4649
+ const msg = messages.find((m) => m.id === selected.messageId);
4650
+ if (msg && msg.callId) {
4651
+ emitToListeners("OpenApprovalViewer", {
4652
+ type: selected.toolName,
4653
+ mode: "info",
4654
+ callId: msg.callId
4655
+ });
4656
+ }
4657
+ }
4658
+ }
4058
4659
  }
4059
4660
  if (focusedArea === "status") {
4060
4661
  if (key.leftArrow || key.rightArrow) {
@@ -4161,12 +4762,12 @@ function ChatContent() {
4161
4762
  }
4162
4763
  return acc;
4163
4764
  }, [messages, availableTextWidth, labelWidthFor]);
4164
- useEffect7(() => {
4765
+ useEffect8(() => {
4165
4766
  if (lineItems.length > 0 && focusedArea !== "timeline") {
4166
4767
  setSelectedIndex(lineItems.length - 1);
4167
4768
  }
4168
4769
  }, [lineItems.length, focusedArea]);
4169
- useEffect7(() => {
4770
+ useEffect8(() => {
4170
4771
  if (lineItems.length > 0) {
4171
4772
  setSelectedIndex(lineItems.length - 1);
4172
4773
  }
@@ -4311,10 +4912,246 @@ function ChatContent() {
4311
4912
  ] });
4312
4913
  }
4313
4914
 
4915
+ // ink/components/DiffApprovalView.tsx
4916
+ import { Box as Box11, Text as Text11, useInput as useInput5 } from "ink";
4917
+ import { useMemo as useMemo12, useState as useState15 } from "react";
4918
+
4919
+ // ink/contexts/ApprovalContext.tsx
4920
+ import { createContext as createContext7, useCallback as useCallback5, useContext as useContext7, useMemo as useMemo11, useState as useState14 } from "react";
4921
+ import { jsx as jsx17 } from "react/jsx-runtime";
4922
+ var ApprovalContext = createContext7(void 0);
4923
+ var useApproval = () => {
4924
+ const context = useContext7(ApprovalContext);
4925
+ if (!context) {
4926
+ throw new Error("useApproval must be used within an ApprovalProvider");
4927
+ }
4928
+ return context;
4929
+ };
4930
+ var ApprovalProvider = ({ children }) => {
4931
+ const [payload, setPayload] = useState14(null);
4932
+ const [toolInfos] = useState14(/* @__PURE__ */ new Map());
4933
+ const registerToolInfo = useCallback5(
4934
+ (info) => {
4935
+ toolInfos.set(info.callId, info);
4936
+ },
4937
+ [toolInfos]
4938
+ );
4939
+ useListener("OpenApprovalViewer", (p) => {
4940
+ let finalPayload = { ...p, openedAt: Date.now() };
4941
+ if (p.mode === "info" && p.callId) {
4942
+ const info = toolInfos.get(p.callId);
4943
+ if (info) {
4944
+ finalPayload = { ...finalPayload, ...info };
4945
+ }
4946
+ } else if (p.callId) {
4947
+ registerToolInfo({
4948
+ callId: p.callId,
4949
+ type: p.type,
4950
+ path: p.path,
4951
+ diff: p.diff,
4952
+ code: p.code,
4953
+ commands: p.commands
4954
+ });
4955
+ }
4956
+ setPayload(finalPayload);
4957
+ }, [registerToolInfo, toolInfos]);
4958
+ useListener("RegisterToolInfo", (info) => {
4959
+ registerToolInfo(info);
4960
+ }, [registerToolInfo]);
4961
+ useListener("CloseApprovalViewer", () => {
4962
+ setPayload(null);
4963
+ }, []);
4964
+ const value = useMemo11(() => ({
4965
+ payload,
4966
+ setPayload,
4967
+ registerToolInfo
4968
+ }), [payload, registerToolInfo]);
4969
+ return /* @__PURE__ */ jsx17(ApprovalContext.Provider, { value, children });
4970
+ };
4971
+
4972
+ // ink/components/DiffApprovalView.tsx
4973
+ import { Fragment as Fragment2, jsx as jsx18, jsxs as jsxs10 } from "react/jsx-runtime";
4974
+ function DiffApprovalView() {
4975
+ const { payload } = useApproval();
4976
+ const size = useTerminalSize();
4977
+ const [scrollIndex, setScrollIndex] = useState15(0);
4978
+ const diffLines = useMemo12(() => {
4979
+ if (!payload?.diff) {
4980
+ return [];
4981
+ }
4982
+ return payload.diff.split("\n");
4983
+ }, [payload?.diff]);
4984
+ const wrappedLines = useMemo12(() => {
4985
+ const result = [];
4986
+ if (!payload) {
4987
+ return result;
4988
+ }
4989
+ if (payload.type === "delete_file") {
4990
+ result.push({ text: "Delete file: " + payload.path, color: "red" });
4991
+ return result;
4992
+ }
4993
+ if (payload.type === "code_interpreter" && payload.code) {
4994
+ const lines = payload.code.split("\n");
4995
+ for (const line of lines) {
4996
+ const wrapped = wrapText(line, size.columns - 4);
4997
+ for (const w of wrapped) {
4998
+ result.push({ text: w });
4999
+ }
5000
+ }
5001
+ return result;
5002
+ }
5003
+ if (payload.type === "shell" && payload.commands) {
5004
+ for (const cmd of payload.commands) {
5005
+ const wrapped = wrapText(`$ ${cmd}`, size.columns - 4);
5006
+ for (const w of wrapped) {
5007
+ result.push({ text: w, color: "yellow" });
5008
+ }
5009
+ }
5010
+ return result;
5011
+ }
5012
+ for (const line of diffLines) {
5013
+ let color;
5014
+ if (line.startsWith("+")) {
5015
+ color = "green";
5016
+ } else if (line.startsWith("-")) {
5017
+ color = "red";
5018
+ } else if (line.startsWith("@@")) {
5019
+ color = "cyan";
5020
+ }
5021
+ const wrapped = wrapText(line, size.columns - 4);
5022
+ for (const w of wrapped) {
5023
+ result.push({ text: w, color });
5024
+ }
5025
+ }
5026
+ return result;
5027
+ }, [diffLines, size.columns, payload]);
5028
+ const visibleHeight = size.rows - 6;
5029
+ useInput5((input, key) => {
5030
+ if (!payload) {
5031
+ return;
5032
+ }
5033
+ if (key.escape) {
5034
+ if (payload.mode === "ask") {
5035
+ emitToListeners("DenyCurrentApproval", void 0);
5036
+ }
5037
+ emitToListeners("CloseApprovalViewer", void 0);
5038
+ return;
5039
+ }
5040
+ if (key.return) {
5041
+ if (payload.mode === "ask") {
5042
+ const now = Date.now();
5043
+ if (payload.openedAt && now - payload.openedAt > 1e3) {
5044
+ emitToListeners("ApproveCurrentApproval", void 0);
5045
+ emitToListeners("CloseApprovalViewer", void 0);
5046
+ emitToListeners("ClearUserInput", void 0);
5047
+ }
5048
+ } else {
5049
+ emitToListeners("CloseApprovalViewer", void 0);
5050
+ }
5051
+ return;
5052
+ }
5053
+ if (payload.mode === "ask") {
5054
+ const now = Date.now();
5055
+ if (payload.openedAt && now - payload.openedAt > 1e3) {
5056
+ if (input === "y") {
5057
+ emitToListeners("ApproveCurrentApproval", void 0);
5058
+ emitToListeners("CloseApprovalViewer", void 0);
5059
+ emitToListeners("ClearUserInput", void 0);
5060
+ } else if (input === "n") {
5061
+ emitToListeners("DenyCurrentApproval", void 0);
5062
+ emitToListeners("CloseApprovalViewer", void 0);
5063
+ emitToListeners("ClearUserInput", void 0);
5064
+ }
5065
+ }
5066
+ }
5067
+ if (key.upArrow) {
5068
+ setScrollIndex((prev) => Math.max(0, prev - 1));
5069
+ }
5070
+ if (key.downArrow) {
5071
+ setScrollIndex((prev) => Math.min(Math.max(0, wrappedLines.length - visibleHeight), prev + 1));
5072
+ }
5073
+ if (key.pageUp) {
5074
+ setScrollIndex((prev) => Math.max(0, prev - visibleHeight));
5075
+ }
5076
+ if (key.pageDown) {
5077
+ setScrollIndex((prev) => Math.min(Math.max(0, wrappedLines.length - visibleHeight), prev + visibleHeight));
5078
+ }
5079
+ });
5080
+ if (!payload) {
5081
+ return null;
5082
+ }
5083
+ const canRespond = payload.mode === "ask" && payload.openedAt && Date.now() - payload.openedAt > 1e3;
5084
+ return /* @__PURE__ */ jsxs10(
5085
+ Box11,
5086
+ {
5087
+ position: "absolute",
5088
+ flexDirection: "column",
5089
+ width: size.columns,
5090
+ height: size.rows,
5091
+ backgroundColor: "black",
5092
+ borderStyle: "double",
5093
+ borderColor: "cyan",
5094
+ children: [
5095
+ /* @__PURE__ */ jsxs10(
5096
+ Box11,
5097
+ {
5098
+ paddingX: 1,
5099
+ borderStyle: "single",
5100
+ borderBottomColor: "gray",
5101
+ borderTop: false,
5102
+ borderLeft: false,
5103
+ borderRight: false,
5104
+ children: [
5105
+ /* @__PURE__ */ jsxs10(Text11, { bold: true, color: "cyan", children: [
5106
+ payload.mode === "ask" ? "APPROVE " : "VIEW ",
5107
+ payload.type.toUpperCase().replace("_", " ")
5108
+ ] }),
5109
+ /* @__PURE__ */ jsx18(Text11, { color: "gray", children: "\u2502" }),
5110
+ payload.path && /* @__PURE__ */ jsxs10(Fragment2, { children: [
5111
+ /* @__PURE__ */ jsxs10(Text11, { bold: true, children: [
5112
+ payload.type,
5113
+ ":"
5114
+ ] }),
5115
+ /* @__PURE__ */ jsx18(Text11, { children: payload.path })
5116
+ ] })
5117
+ ]
5118
+ }
5119
+ ),
5120
+ /* @__PURE__ */ jsx18(Box11, { flexGrow: 1, flexDirection: "column", paddingX: 1, children: wrappedLines.length === 0 ? /* @__PURE__ */ jsx18(Text11, { italic: true, color: "gray", children: "No diff content." }) : wrappedLines.slice(scrollIndex, scrollIndex + visibleHeight).map((line, i) => /* @__PURE__ */ jsx18(Text11, { color: line.color || "white", children: line.text }, i)) }),
5121
+ /* @__PURE__ */ jsxs10(
5122
+ Box11,
5123
+ {
5124
+ paddingX: 1,
5125
+ borderStyle: "single",
5126
+ borderTopColor: "gray",
5127
+ borderBottom: false,
5128
+ borderLeft: false,
5129
+ borderRight: false,
5130
+ children: [
5131
+ payload.mode === "ask" ? /* @__PURE__ */ jsxs10(Box11, { flexGrow: 1, children: [
5132
+ /* @__PURE__ */ jsx18(Text11, { color: canRespond ? "green" : "gray", children: "[Enter/y] Approve" }),
5133
+ /* @__PURE__ */ jsx18(Text11, {}),
5134
+ /* @__PURE__ */ jsx18(Text11, { color: canRespond ? "red" : "gray", children: "[Esc/n] Deny" }),
5135
+ !canRespond && /* @__PURE__ */ jsx18(Text11, { color: "yellow", children: "(Wait 1s...)" })
5136
+ ] }) : /* @__PURE__ */ jsx18(Box11, { flexGrow: 1, children: /* @__PURE__ */ jsx18(Text11, { color: "cyan", children: "[Enter/Esc] Close" }) }),
5137
+ /* @__PURE__ */ jsxs10(Text11, { color: "gray", children: [
5138
+ "Line ",
5139
+ scrollIndex + 1,
5140
+ "/",
5141
+ wrappedLines.length
5142
+ ] })
5143
+ ]
5144
+ }
5145
+ )
5146
+ ]
5147
+ }
5148
+ );
5149
+ }
5150
+
4314
5151
  // ink/configurationWizard/ConfigurationWizard.tsx
4315
- import { Box as Box16, useInput as useInput7 } from "ink";
5152
+ import { Box as Box18, useInput as useInput10 } from "ink";
4316
5153
  import { Step, Stepper } from "ink-stepper";
4317
- import { useState as useState13 } from "react";
5154
+ import { useState as useState17 } from "react";
4318
5155
 
4319
5156
  // utils/files/getEnvVarForProvider.ts
4320
5157
  function getEnvVarForProvider(provider) {
@@ -4338,17 +5175,17 @@ function updateEnvKeyForProvider(provider, key) {
4338
5175
 
4339
5176
  // ink/configurationWizard/ApiKeyStep.tsx
4340
5177
  import { PasswordInput } from "@inkjs/ui";
4341
- import { Box as Box11, Text as Text11, useInput as useInput4 } from "ink";
5178
+ import { Box as Box12, Text as Text12, useInput as useInput6 } from "ink";
4342
5179
  import { useStepperInput } from "ink-stepper";
4343
- import { useEffect as useEffect8 } from "react";
4344
- import { jsx as jsx17, jsxs as jsxs10 } from "react/jsx-runtime";
5180
+ import { useEffect as useEffect9 } from "react";
5181
+ import { jsx as jsx19, jsxs as jsxs11 } from "react/jsx-runtime";
4345
5182
  function ApiKeyStep({ provider, onConfirm, onBack }) {
4346
5183
  const { disableNavigation, enableNavigation } = useStepperInput();
4347
- useEffect8(() => {
5184
+ useEffect9(() => {
4348
5185
  disableNavigation();
4349
5186
  return () => enableNavigation();
4350
5187
  }, [disableNavigation, enableNavigation]);
4351
- useInput4((input, key) => {
5188
+ useInput6((input, key) => {
4352
5189
  if (key.escape) {
4353
5190
  onBack();
4354
5191
  }
@@ -4359,14 +5196,14 @@ function ApiKeyStep({ provider, onConfirm, onBack }) {
4359
5196
  Google: "Get your key at: https://aistudio.google.com/app/apikey",
4360
5197
  Ollama: ""
4361
5198
  };
4362
- return /* @__PURE__ */ jsxs10(Box11, { flexDirection: "column", children: [
4363
- /* @__PURE__ */ jsxs10(Text11, { children: [
5199
+ return /* @__PURE__ */ jsxs11(Box12, { flexDirection: "column", children: [
5200
+ /* @__PURE__ */ jsxs11(Text12, { children: [
4364
5201
  "Can you provide us with your ",
4365
5202
  provider,
4366
5203
  " API key?"
4367
5204
  ] }),
4368
- /* @__PURE__ */ jsx17(Text11, { dimColor: true, children: instructions[provider] }),
4369
- /* @__PURE__ */ jsx17(Box11, { marginTop: 1, children: /* @__PURE__ */ jsx17(
5205
+ /* @__PURE__ */ jsx19(Text12, { dimColor: true, children: instructions[provider] }),
5206
+ /* @__PURE__ */ jsx19(Box12, { marginTop: 1, children: /* @__PURE__ */ jsx19(
4370
5207
  PasswordInput,
4371
5208
  {
4372
5209
  onSubmit: (v) => {
@@ -4378,22 +5215,22 @@ function ApiKeyStep({ provider, onConfirm, onBack }) {
4378
5215
  }
4379
5216
  }
4380
5217
  ) }),
4381
- /* @__PURE__ */ jsx17(Box11, { marginTop: 1, children: /* @__PURE__ */ jsx17(Text11, { dimColor: true, children: "Press ESC to go back" }) })
5218
+ /* @__PURE__ */ jsx19(Box12, { marginTop: 1, children: /* @__PURE__ */ jsx19(Text12, { dimColor: true, children: "Press ESC to go back" }) })
4382
5219
  ] });
4383
5220
  }
4384
5221
 
4385
5222
  // ink/configurationWizard/ApiUrlStep.tsx
4386
- import { Box as Box12, Text as Text12, useInput as useInput5 } from "ink";
5223
+ import { Box as Box13, Text as Text13, useInput as useInput7 } from "ink";
4387
5224
  import { useStepperInput as useStepperInput2 } from "ink-stepper";
4388
- import { useEffect as useEffect9 } from "react";
4389
- import { jsx as jsx18, jsxs as jsxs11 } from "react/jsx-runtime";
5225
+ import { useEffect as useEffect10 } from "react";
5226
+ import { jsx as jsx20, jsxs as jsxs12 } from "react/jsx-runtime";
4390
5227
  function ApiUrlStep({ provider, onConfirm, onBack }) {
4391
5228
  const { disableNavigation, enableNavigation } = useStepperInput2();
4392
- useEffect9(() => {
5229
+ useEffect10(() => {
4393
5230
  disableNavigation();
4394
5231
  return () => enableNavigation();
4395
5232
  }, [disableNavigation, enableNavigation]);
4396
- useInput5((input, key) => {
5233
+ useInput7((input, key) => {
4397
5234
  if (key.escape) {
4398
5235
  onBack();
4399
5236
  }
@@ -4404,13 +5241,13 @@ function ApiUrlStep({ provider, onConfirm, onBack }) {
4404
5241
  Google: "",
4405
5242
  Ollama: "http://localhost:11434/api"
4406
5243
  };
4407
- return /* @__PURE__ */ jsxs11(Box12, { flexDirection: "column", children: [
4408
- /* @__PURE__ */ jsxs11(Text12, { children: [
5244
+ return /* @__PURE__ */ jsxs12(Box13, { flexDirection: "column", children: [
5245
+ /* @__PURE__ */ jsxs12(Text13, { children: [
4409
5246
  "Where are you hosting ",
4410
5247
  provider,
4411
5248
  "?"
4412
5249
  ] }),
4413
- /* @__PURE__ */ jsx18(Box12, { marginTop: 1, children: /* @__PURE__ */ jsx18(
5250
+ /* @__PURE__ */ jsx20(Box13, { marginTop: 1, children: /* @__PURE__ */ jsx20(
4414
5251
  BlinkingTextInput,
4415
5252
  {
4416
5253
  placeholder: defaultApi[provider] || "",
@@ -4423,7 +5260,84 @@ function ApiUrlStep({ provider, onConfirm, onBack }) {
4423
5260
  }
4424
5261
  }
4425
5262
  ) }),
4426
- /* @__PURE__ */ jsx18(Box12, { marginTop: 1, children: /* @__PURE__ */ jsx18(Text12, { dimColor: true, children: "Press ESC to go back" }) })
5263
+ /* @__PURE__ */ jsx20(Box13, { marginTop: 1, children: /* @__PURE__ */ jsx20(Text13, { dimColor: true, children: "Press ESC to go back" }) })
5264
+ ] });
5265
+ }
5266
+
5267
+ // ink/configurationWizard/EnvironmentSettingsStep.tsx
5268
+ import { MultiSelect } from "@inkjs/ui";
5269
+ import { Box as Box14, Text as Text14, useInput as useInput8 } from "ink";
5270
+ import { useStepperInput as useStepperInput3 } from "ink-stepper";
5271
+ import { useEffect as useEffect11 } from "react";
5272
+ import { jsx as jsx21, jsxs as jsxs13 } from "react/jsx-runtime";
5273
+ var SETTINGS = [
5274
+ {
5275
+ label: "Save Harper agent memory locally",
5276
+ value: "HARPER_AGENT_SESSION",
5277
+ defaultValue: "./harper-agent-memory.json"
5278
+ },
5279
+ {
5280
+ label: "Automatically approve code interpreter execution",
5281
+ value: "HARPER_AGENT_AUTO_APPROVE_CODE_INTERPRETER",
5282
+ defaultValue: "1"
5283
+ },
5284
+ {
5285
+ label: "Automatically approve file patches",
5286
+ value: "HARPER_AGENT_AUTO_APPROVE_PATCHES",
5287
+ defaultValue: "1"
5288
+ },
5289
+ {
5290
+ label: "Automatically approve shell commands",
5291
+ value: "HARPER_AGENT_AUTO_APPROVE_SHELL",
5292
+ defaultValue: "1"
5293
+ },
5294
+ {
5295
+ label: "Use flex tier for lower costs when possible",
5296
+ value: "HARPER_AGENT_FLEX_TIER",
5297
+ defaultValue: "true"
5298
+ }
5299
+ ];
5300
+ function EnvironmentSettingsStep({ onConfirm, onBack }) {
5301
+ const { disableNavigation, enableNavigation } = useStepperInput3();
5302
+ useEffect11(() => {
5303
+ disableNavigation();
5304
+ return () => enableNavigation();
5305
+ }, [disableNavigation, enableNavigation]);
5306
+ useInput8((_input, key) => {
5307
+ if (key.escape) {
5308
+ onBack();
5309
+ }
5310
+ });
5311
+ const options = SETTINGS.map((s) => ({
5312
+ label: s.label,
5313
+ value: s.value
5314
+ }));
5315
+ const defaultValues = SETTINGS.map((s) => s.value);
5316
+ const handleSubmit = (values) => {
5317
+ for (const setting of SETTINGS) {
5318
+ if (values.includes(setting.value)) {
5319
+ updateEnv(setting.value, setting.defaultValue);
5320
+ }
5321
+ }
5322
+ onConfirm();
5323
+ };
5324
+ return /* @__PURE__ */ jsxs13(Box14, { flexDirection: "column", children: [
5325
+ /* @__PURE__ */ jsx21(Text14, { children: "Additional Settings (all enabled by default):" }),
5326
+ /* @__PURE__ */ jsx21(
5327
+ MultiSelect,
5328
+ {
5329
+ options,
5330
+ defaultValue: defaultValues,
5331
+ onSubmit: handleSubmit
5332
+ }
5333
+ ),
5334
+ /* @__PURE__ */ jsxs13(Text14, { color: "gray", children: [
5335
+ "Press ",
5336
+ "<space>",
5337
+ " to toggle, ",
5338
+ "<enter>",
5339
+ " to confirm."
5340
+ ] })
4427
5341
  ] });
4428
5342
  }
4429
5343
 
@@ -4443,23 +5357,23 @@ var compactorModelsByProvider = {
4443
5357
 
4444
5358
  // ink/configurationWizard/ModelSelectionStep.tsx
4445
5359
  import { Select } from "@inkjs/ui";
4446
- import { Box as Box13, Text as Text13, useInput as useInput6 } from "ink";
4447
- import { useStepperInput as useStepperInput3 } from "ink-stepper";
4448
- import { useEffect as useEffect10, useState as useState12 } from "react";
4449
- import { jsx as jsx19, jsxs as jsxs12 } from "react/jsx-runtime";
5360
+ import { Box as Box15, Text as Text15, useInput as useInput9 } from "ink";
5361
+ import { useStepperInput as useStepperInput4 } from "ink-stepper";
5362
+ import { useEffect as useEffect12, useState as useState16 } from "react";
5363
+ import { jsx as jsx22, jsxs as jsxs14 } from "react/jsx-runtime";
4450
5364
  function ModelSelectionStep({
4451
5365
  title,
4452
5366
  models,
4453
5367
  onConfirm,
4454
5368
  onBack
4455
5369
  }) {
4456
- const { disableNavigation, enableNavigation } = useStepperInput3();
4457
- const [isCustom, setIsCustom] = useState12(false);
4458
- useEffect10(() => {
5370
+ const { disableNavigation, enableNavigation } = useStepperInput4();
5371
+ const [isCustom, setIsCustom] = useState16(false);
5372
+ useEffect12(() => {
4459
5373
  disableNavigation();
4460
5374
  return () => enableNavigation();
4461
5375
  }, [isCustom, disableNavigation, enableNavigation]);
4462
- useInput6((input, key) => {
5376
+ useInput9((input, key) => {
4463
5377
  if (key.escape) {
4464
5378
  if (isCustom) {
4465
5379
  setIsCustom(false);
@@ -4469,12 +5383,12 @@ function ModelSelectionStep({
4469
5383
  }
4470
5384
  });
4471
5385
  if (isCustom) {
4472
- return /* @__PURE__ */ jsxs12(Box13, { flexDirection: "column", children: [
4473
- /* @__PURE__ */ jsxs12(Text13, { children: [
5386
+ return /* @__PURE__ */ jsxs14(Box15, { flexDirection: "column", children: [
5387
+ /* @__PURE__ */ jsxs14(Text15, { children: [
4474
5388
  "Enter custom model name for: ",
4475
5389
  title
4476
5390
  ] }),
4477
- /* @__PURE__ */ jsx19(
5391
+ /* @__PURE__ */ jsx22(
4478
5392
  BlinkingTextInput,
4479
5393
  {
4480
5394
  onSubmit: (v) => {
@@ -4486,12 +5400,12 @@ function ModelSelectionStep({
4486
5400
  }
4487
5401
  }
4488
5402
  ),
4489
- /* @__PURE__ */ jsx19(Box13, { marginTop: 1, children: /* @__PURE__ */ jsx19(Text13, { dimColor: true, children: "Press ESC to go back to list" }) })
5403
+ /* @__PURE__ */ jsx22(Box15, { marginTop: 1, children: /* @__PURE__ */ jsx22(Text15, { dimColor: true, children: "Press ESC to go back to list" }) })
4490
5404
  ] });
4491
5405
  }
4492
- return /* @__PURE__ */ jsxs12(Box13, { flexDirection: "column", children: [
4493
- /* @__PURE__ */ jsx19(Text13, { children: title }),
4494
- /* @__PURE__ */ jsx19(
5406
+ return /* @__PURE__ */ jsxs14(Box15, { flexDirection: "column", children: [
5407
+ /* @__PURE__ */ jsx22(Text15, { children: title }),
5408
+ /* @__PURE__ */ jsx22(
4495
5409
  Select,
4496
5410
  {
4497
5411
  options: [
@@ -4507,15 +5421,15 @@ function ModelSelectionStep({
4507
5421
  }
4508
5422
  }
4509
5423
  ),
4510
- /* @__PURE__ */ jsx19(Box13, { marginTop: 1, children: /* @__PURE__ */ jsx19(Text13, { dimColor: true, children: "Press ESC to go back" }) })
5424
+ /* @__PURE__ */ jsx22(Box15, { marginTop: 1, children: /* @__PURE__ */ jsx22(Text15, { dimColor: true, children: "Press ESC to go back" }) })
4511
5425
  ] });
4512
5426
  }
4513
5427
 
4514
5428
  // ink/configurationWizard/ProviderStep.tsx
4515
5429
  import { Select as Select2 } from "@inkjs/ui";
4516
- import { Box as Box14, Text as Text14 } from "ink";
4517
- import { useStepperInput as useStepperInput4 } from "ink-stepper";
4518
- import { useEffect as useEffect11 } from "react";
5430
+ import { Box as Box16, Text as Text16 } from "ink";
5431
+ import { useStepperInput as useStepperInput5 } from "ink-stepper";
5432
+ import { useEffect as useEffect13 } from "react";
4519
5433
 
4520
5434
  // ink/configurationWizard/providers.ts
4521
5435
  var providers = [
@@ -4526,16 +5440,16 @@ var providers = [
4526
5440
  ];
4527
5441
 
4528
5442
  // ink/configurationWizard/ProviderStep.tsx
4529
- import { jsx as jsx20, jsxs as jsxs13 } from "react/jsx-runtime";
5443
+ import { jsx as jsx23, jsxs as jsxs15 } from "react/jsx-runtime";
4530
5444
  function ProviderStep({ onConfirm }) {
4531
- const { disableNavigation, enableNavigation } = useStepperInput4();
4532
- useEffect11(() => {
5445
+ const { disableNavigation, enableNavigation } = useStepperInput5();
5446
+ useEffect13(() => {
4533
5447
  disableNavigation();
4534
5448
  return () => enableNavigation();
4535
5449
  }, [disableNavigation, enableNavigation]);
4536
- return /* @__PURE__ */ jsxs13(Box14, { flexDirection: "column", children: [
4537
- /* @__PURE__ */ jsx20(Text14, { children: "What model provider would you like to use today?" }),
4538
- /* @__PURE__ */ jsx20(
5450
+ return /* @__PURE__ */ jsxs15(Box16, { flexDirection: "column", children: [
5451
+ /* @__PURE__ */ jsx23(Text16, { children: "What model provider would you like to use today?" }),
5452
+ /* @__PURE__ */ jsx23(
4539
5453
  Select2,
4540
5454
  {
4541
5455
  options: providers,
@@ -4546,9 +5460,9 @@ function ProviderStep({ onConfirm }) {
4546
5460
  }
4547
5461
 
4548
5462
  // ink/configurationWizard/StepperProgress.tsx
4549
- import { Box as Box15, Text as Text15 } from "ink";
4550
- import { Fragment as Fragment2 } from "react";
4551
- import { jsx as jsx21, jsxs as jsxs14 } from "react/jsx-runtime";
5463
+ import { Box as Box17, Text as Text17 } from "ink";
5464
+ import { Fragment as Fragment3 } from "react";
5465
+ import { jsx as jsx24, jsxs as jsxs16 } from "react/jsx-runtime";
4552
5466
  var markers = {
4553
5467
  completed: " \u2713 ",
4554
5468
  current: " \u25CF ",
@@ -4556,10 +5470,10 @@ var markers = {
4556
5470
  };
4557
5471
  var SEGMENT_WIDTH = 12;
4558
5472
  function StepperProgress({ steps, currentStep }) {
4559
- return /* @__PURE__ */ jsxs14(Box15, { flexDirection: "column", marginBottom: 1, children: [
4560
- /* @__PURE__ */ jsx21(Box15, { children: steps.map((step) => {
4561
- return /* @__PURE__ */ jsx21(Box15, { width: SEGMENT_WIDTH, justifyContent: "center", children: /* @__PURE__ */ jsx21(
4562
- Text15,
5473
+ return /* @__PURE__ */ jsxs16(Box17, { flexDirection: "column", marginBottom: 1, children: [
5474
+ /* @__PURE__ */ jsx24(Box17, { children: steps.map((step) => {
5475
+ return /* @__PURE__ */ jsx24(Box17, { width: SEGMENT_WIDTH, justifyContent: "center", children: /* @__PURE__ */ jsx24(
5476
+ Text17,
4563
5477
  {
4564
5478
  color: step.completed ? "green" : step.current ? "cyan" : "gray",
4565
5479
  bold: step.current,
@@ -4568,32 +5482,32 @@ function StepperProgress({ steps, currentStep }) {
4568
5482
  }
4569
5483
  ) }, step.name);
4570
5484
  }) }),
4571
- /* @__PURE__ */ jsx21(Box15, { children: steps.map((step, idx) => {
5485
+ /* @__PURE__ */ jsx24(Box17, { children: steps.map((step, idx) => {
4572
5486
  const marker = step.completed ? markers.completed : step.current ? markers.current : markers.pending;
4573
5487
  const beforeLineColor = step.completed || idx <= currentStep ? "green" : "gray";
4574
5488
  const afterLineColor = step.completed ? "green" : "gray";
4575
5489
  const markerColor = step.completed ? "green" : step.current ? "cyan" : "gray";
4576
- return /* @__PURE__ */ jsxs14(Fragment2, { children: [
4577
- /* @__PURE__ */ jsx21(Text15, { color: beforeLineColor, children: "\u2501".repeat(SEGMENT_WIDTH / 2 - 2) }),
4578
- /* @__PURE__ */ jsx21(Text15, { color: markerColor, bold: step.current, children: marker }),
4579
- /* @__PURE__ */ jsx21(Text15, { color: afterLineColor, children: "\u2501".repeat(SEGMENT_WIDTH / 2 - 1) })
5490
+ return /* @__PURE__ */ jsxs16(Fragment3, { children: [
5491
+ /* @__PURE__ */ jsx24(Text17, { color: beforeLineColor, children: "\u2501".repeat(SEGMENT_WIDTH / 2 - 2) }),
5492
+ /* @__PURE__ */ jsx24(Text17, { color: markerColor, bold: step.current, children: marker }),
5493
+ /* @__PURE__ */ jsx24(Text17, { color: afterLineColor, children: "\u2501".repeat(SEGMENT_WIDTH / 2 - 1) })
4580
5494
  ] }, step.name);
4581
5495
  }) })
4582
5496
  ] });
4583
5497
  }
4584
5498
 
4585
5499
  // ink/configurationWizard/ConfigurationWizard.tsx
4586
- import { jsx as jsx22, jsxs as jsxs15 } from "react/jsx-runtime";
5500
+ import { jsx as jsx25, jsxs as jsxs17 } from "react/jsx-runtime";
4587
5501
  function ConfigurationWizard({ onComplete }) {
4588
- const [provider, setProvider] = useState13("OpenAI");
4589
- useInput7((input, key) => {
5502
+ const [provider, setProvider] = useState17("OpenAI");
5503
+ useInput10((input, key) => {
4590
5504
  if (key.ctrl && input === "x") {
4591
5505
  emitToListeners("ExitUI", void 0);
4592
5506
  }
4593
5507
  });
4594
5508
  const models = modelsByProvider[provider];
4595
5509
  const compactorModels = compactorModelsByProvider[provider];
4596
- return /* @__PURE__ */ jsx22(Box16, { flexDirection: "column", padding: 1, minHeight: 10, children: /* @__PURE__ */ jsxs15(
5510
+ return /* @__PURE__ */ jsx25(Box18, { flexDirection: "column", padding: 1, minHeight: 10, children: /* @__PURE__ */ jsxs17(
4597
5511
  Stepper,
4598
5512
  {
4599
5513
  onComplete,
@@ -4601,7 +5515,7 @@ function ConfigurationWizard({ onComplete }) {
4601
5515
  keyboardNav: true,
4602
5516
  renderProgress: StepperProgress,
4603
5517
  children: [
4604
- /* @__PURE__ */ jsx22(Step, { name: "AI Provider", children: ({ goNext }) => /* @__PURE__ */ jsx22(
5518
+ /* @__PURE__ */ jsx25(Step, { name: "AI Provider", children: ({ goNext }) => /* @__PURE__ */ jsx25(
4605
5519
  ProviderStep,
4606
5520
  {
4607
5521
  onConfirm: (p) => {
@@ -4610,7 +5524,7 @@ function ConfigurationWizard({ onComplete }) {
4610
5524
  }
4611
5525
  }
4612
5526
  ) }),
4613
- /* @__PURE__ */ jsx22(Step, { name: provider !== "Ollama" ? "API Key" : "API", children: ({ goNext, goBack }) => provider !== "Ollama" ? /* @__PURE__ */ jsx22(
5527
+ /* @__PURE__ */ jsx25(Step, { name: provider !== "Ollama" ? "API Key" : "API", children: ({ goNext, goBack }) => provider !== "Ollama" ? /* @__PURE__ */ jsx25(
4614
5528
  ApiKeyStep,
4615
5529
  {
4616
5530
  provider,
@@ -4620,7 +5534,7 @@ function ConfigurationWizard({ onComplete }) {
4620
5534
  },
4621
5535
  onBack: goBack
4622
5536
  }
4623
- ) : /* @__PURE__ */ jsx22(
5537
+ ) : /* @__PURE__ */ jsx25(
4624
5538
  ApiUrlStep,
4625
5539
  {
4626
5540
  provider,
@@ -4631,7 +5545,7 @@ function ConfigurationWizard({ onComplete }) {
4631
5545
  onBack: goBack
4632
5546
  }
4633
5547
  ) }),
4634
- /* @__PURE__ */ jsx22(Step, { name: "Model", children: ({ goNext, goBack }) => /* @__PURE__ */ jsx22(
5548
+ /* @__PURE__ */ jsx25(Step, { name: "Model", children: ({ goNext, goBack }) => /* @__PURE__ */ jsx25(
4635
5549
  ModelSelectionStep,
4636
5550
  {
4637
5551
  title: "What model would you like to use?",
@@ -4643,7 +5557,7 @@ function ConfigurationWizard({ onComplete }) {
4643
5557
  onBack: goBack
4644
5558
  }
4645
5559
  ) }),
4646
- /* @__PURE__ */ jsx22(Step, { name: "Compactor", children: ({ goNext, goBack }) => /* @__PURE__ */ jsx22(
5560
+ /* @__PURE__ */ jsx25(Step, { name: "Compactor", children: ({ goNext, goBack }) => /* @__PURE__ */ jsx25(
4647
5561
  ModelSelectionStep,
4648
5562
  {
4649
5563
  title: "What model should we use for memory compaction?",
@@ -4654,6 +5568,15 @@ function ConfigurationWizard({ onComplete }) {
4654
5568
  },
4655
5569
  onBack: goBack
4656
5570
  }
5571
+ ) }),
5572
+ /* @__PURE__ */ jsx25(Step, { name: "Settings", children: ({ goNext, goBack }) => /* @__PURE__ */ jsx25(
5573
+ EnvironmentSettingsStep,
5574
+ {
5575
+ onConfirm: () => {
5576
+ goNext();
5577
+ },
5578
+ onBack: goBack
5579
+ }
4657
5580
  ) })
4658
5581
  ]
4659
5582
  }
@@ -4661,22 +5584,25 @@ function ConfigurationWizard({ onComplete }) {
4661
5584
  }
4662
5585
 
4663
5586
  // ink/main.tsx
4664
- import { jsx as jsx23 } from "react/jsx-runtime";
5587
+ import { jsx as jsx26, jsxs as jsxs18 } from "react/jsx-runtime";
4665
5588
  function bootstrapConfig(onComplete) {
4666
- render(/* @__PURE__ */ jsx23(MainConfig, { onComplete }));
5589
+ render(/* @__PURE__ */ jsx26(MainConfig, { onComplete }));
4667
5590
  }
4668
5591
  function MainConfig({ onComplete }) {
4669
5592
  const { exit } = useApp();
4670
5593
  useListener("ExitUI", () => exit(), [exit]);
4671
- return /* @__PURE__ */ jsx23(ConfigurationWizard, { onComplete });
5594
+ return /* @__PURE__ */ jsx26(ConfigurationWizard, { onComplete });
4672
5595
  }
4673
5596
  function bootstrapMain() {
4674
- render(/* @__PURE__ */ jsx23(MainChat, {}));
5597
+ render(/* @__PURE__ */ jsx26(MainChat, {}));
4675
5598
  }
4676
5599
  function MainChat() {
4677
5600
  const { exit } = useApp();
4678
5601
  useListener("ExitUI", () => exit(), [exit]);
4679
- return /* @__PURE__ */ jsx23(CostProvider, { children: /* @__PURE__ */ jsx23(PlanProvider, { children: /* @__PURE__ */ jsx23(ActionsProvider, { children: /* @__PURE__ */ jsx23(SettingsProvider, { children: /* @__PURE__ */ jsx23(ChatProvider, { children: /* @__PURE__ */ jsx23(ChatContent, {}) }) }) }) }) });
5602
+ return /* @__PURE__ */ jsx26(CostProvider, { children: /* @__PURE__ */ jsx26(PlanProvider, { children: /* @__PURE__ */ jsx26(ActionsProvider, { children: /* @__PURE__ */ jsx26(ApprovalProvider, { children: /* @__PURE__ */ jsx26(SettingsProvider, { children: /* @__PURE__ */ jsxs18(ChatProvider, { children: [
5603
+ /* @__PURE__ */ jsx26(ChatContent, {}),
5604
+ /* @__PURE__ */ jsx26(DiffApprovalView, {})
5605
+ ] }) }) }) }) }) });
4680
5606
  }
4681
5607
 
4682
5608
  // lifecycle/parseArgs.ts
@@ -4687,12 +5613,12 @@ import chalk4 from "chalk";
4687
5613
 
4688
5614
  // utils/package/getOwnPackageJson.ts
4689
5615
  import { readFileSync as readFileSync6 } from "fs";
4690
- import { join as join9 } from "path";
5616
+ import { join as join10 } from "path";
4691
5617
  import { fileURLToPath } from "url";
4692
5618
  var __dirname = fileURLToPath(new URL(".", import.meta.url));
4693
5619
  function getOwnPackageJson() {
4694
5620
  try {
4695
- const packageContents = readFileSync6(join9(__dirname, "../package.json"), "utf8");
5621
+ const packageContents = readFileSync6(join10(__dirname, "../package.json"), "utf8");
4696
5622
  return JSON.parse(packageContents);
4697
5623
  } catch {
4698
5624
  return { name: "@harperfast/agent", version: "0.0.0" };
@@ -4775,7 +5701,8 @@ function parseArgs() {
4775
5701
  ["compactionModel", ["--compaction-model", "-c", "compaction-model"]],
4776
5702
  ["sessionPath", ["--session", "-s", "session"]],
4777
5703
  ["maxTurns", ["--max-turns"]],
4778
- ["maxCost", ["--max-cost"]]
5704
+ ["maxCost", ["--max-cost"]],
5705
+ ["rateLimitThreshold", ["--rate-limit-threshold"]]
4779
5706
  ];
4780
5707
  let handled = false;
4781
5708
  for (const [key, prefixes] of flagPairs) {
@@ -4783,7 +5710,7 @@ function parseArgs() {
4783
5710
  if (arg === prefix) {
4784
5711
  if (args[i + 1]) {
4785
5712
  const val = stripQuotes(args[++i]);
4786
- if (key === "maxTurns" || key === "maxCost") {
5713
+ if (key === "maxTurns" || key === "maxCost" || key === "rateLimitThreshold") {
4787
5714
  trackedState[key] = parseFloat(val);
4788
5715
  } else {
4789
5716
  trackedState[key] = val;
@@ -4793,7 +5720,7 @@ function parseArgs() {
4793
5720
  break;
4794
5721
  } else if (arg.startsWith(`${prefix}=`)) {
4795
5722
  const val = stripQuotes(arg.slice(prefix.length + 1));
4796
- if (key === "maxTurns" || key === "maxCost") {
5723
+ if (key === "maxTurns" || key === "maxCost" || key === "rateLimitThreshold") {
4797
5724
  trackedState[key] = parseFloat(val);
4798
5725
  } else {
4799
5726
  trackedState[key] = val;
@@ -4811,6 +5738,8 @@ function parseArgs() {
4811
5738
  }
4812
5739
  if (arg === "--flex-tier") {
4813
5740
  trackedState.useFlexTier = true;
5741
+ } else if (arg === "--no-monitor-rate-limits") {
5742
+ trackedState.monitorRateLimits = false;
4814
5743
  }
4815
5744
  }
4816
5745
  if (!trackedState.model && process.env.HARPER_AGENT_MODEL) {
@@ -4828,6 +5757,12 @@ function parseArgs() {
4828
5757
  if (process.env.HARPER_AGENT_MAX_COST) {
4829
5758
  trackedState.maxCost = parseFloat(process.env.HARPER_AGENT_MAX_COST);
4830
5759
  }
5760
+ if (process.env.HARPER_AGENT_RATE_LIMIT_THRESHOLD) {
5761
+ trackedState.rateLimitThreshold = parseFloat(process.env.HARPER_AGENT_RATE_LIMIT_THRESHOLD);
5762
+ }
5763
+ if (process.env.HARPER_AGENT_MONITOR_RATE_LIMITS === "false") {
5764
+ trackedState.monitorRateLimits = false;
5765
+ }
4831
5766
  const sp = trackedState.sessionPath;
4832
5767
  if (sp) {
4833
5768
  trackedState.sessionPath = sp && !sp.startsWith("~") && !path8.isAbsolute(sp) ? path8.resolve(process.cwd(), sp) : sp;
@@ -4835,6 +5770,15 @@ function parseArgs() {
4835
5770
  if (!trackedState.useFlexTier && isTrue(process.env.HARPER_AGENT_FLEX_TIER)) {
4836
5771
  trackedState.useFlexTier = true;
4837
5772
  }
5773
+ if (isTrue(process.env.HARPER_AGENT_AUTO_APPROVE_CODE_INTERPRETER)) {
5774
+ trackedState.autoApproveCodeInterpreter = true;
5775
+ }
5776
+ if (isTrue(process.env.HARPER_AGENT_AUTO_APPROVE_PATCHES)) {
5777
+ trackedState.autoApprovePatches = true;
5778
+ }
5779
+ if (isTrue(process.env.HARPER_AGENT_AUTO_APPROVE_SHELL)) {
5780
+ trackedState.autoApproveShell = true;
5781
+ }
4838
5782
  if (!trackedState.model) {
4839
5783
  if (process.env.ANTHROPIC_API_KEY) {
4840
5784
  trackedState.model = "claude-3-7-sonnet-latest";
@@ -4865,6 +5809,159 @@ function parseArgs() {
4865
5809
  }
4866
5810
  }
4867
5811
 
5812
+ // utils/envLoader.ts
5813
+ import dotenv from "dotenv";
5814
+ import { existsSync as existsSync9 } from "fs";
5815
+ import { homedir as homedir4 } from "os";
5816
+ import { join as join11 } from "path";
5817
+ function loadEnv() {
5818
+ const topLevelEnvPath = join11(homedir4(), ".harper", "harper-agent-env");
5819
+ const localEnvPath = join11(process.cwd(), ".env");
5820
+ if (existsSync9(topLevelEnvPath)) {
5821
+ dotenv.config({ path: topLevelEnvPath, quiet: true });
5822
+ }
5823
+ if (existsSync9(localEnvPath)) {
5824
+ dotenv.config({ path: localEnvPath, override: true, quiet: true });
5825
+ }
5826
+ }
5827
+
5828
+ // utils/package/checkForUpdate.ts
5829
+ import { Select as Select3 } from "@inkjs/ui";
5830
+ import chalk5 from "chalk";
5831
+ import spawn2 from "cross-spawn";
5832
+ import { Box as Box19, render as render2, Text as Text18 } from "ink";
5833
+ import React21 from "react";
5834
+
5835
+ // utils/package/getLatestVersion.ts
5836
+ async function getLatestVersion(packageName) {
5837
+ try {
5838
+ const response = await fetch(`https://registry.npmjs.org/${packageName}/latest`, {
5839
+ signal: AbortSignal.timeout(1e3)
5840
+ // 1 second timeout
5841
+ });
5842
+ if (!response.ok) {
5843
+ return null;
5844
+ }
5845
+ const data = await response.json();
5846
+ return data.version;
5847
+ } catch {
5848
+ return null;
5849
+ }
5850
+ }
5851
+
5852
+ // utils/package/isVersionNewer.ts
5853
+ function isVersionNewer(latest, current) {
5854
+ const l = latest.split(".").map((x) => parseInt(x, 10));
5855
+ const c = current.split(".").map((x) => parseInt(x, 10));
5856
+ for (let i = 0; i < 3; i++) {
5857
+ let latestNumber = l[i];
5858
+ let currentNumber = c[i];
5859
+ if (latestNumber === void 0 || currentNumber === void 0 || isNaN(latestNumber) || isNaN(currentNumber)) {
5860
+ break;
5861
+ }
5862
+ if (latestNumber > currentNumber) {
5863
+ return true;
5864
+ }
5865
+ if (latestNumber < currentNumber) {
5866
+ return false;
5867
+ }
5868
+ }
5869
+ return false;
5870
+ }
5871
+
5872
+ // utils/package/checkForUpdate.ts
5873
+ async function checkForUpdate() {
5874
+ const pkg = getOwnPackageJson();
5875
+ const packageName = pkg.name;
5876
+ const packageVersion = pkg.version;
5877
+ if (process.env.HARPER_AGENT_SKIP_UPDATE) {
5878
+ return packageVersion;
5879
+ }
5880
+ try {
5881
+ const latestVersion = await getLatestVersion(packageName);
5882
+ if (latestVersion && isVersionNewer(latestVersion, packageVersion)) {
5883
+ const choice = await promptForUpdateChoice(packageName, packageVersion, latestVersion);
5884
+ if (choice === "later") {
5885
+ return packageVersion;
5886
+ }
5887
+ if (choice === "never") {
5888
+ updateEnv("HARPER_AGENT_SKIP_UPDATE", "1");
5889
+ return packageVersion;
5890
+ }
5891
+ let isGlobal = false;
5892
+ try {
5893
+ const globalRootResult = spawn2.sync("npm", ["root", "-g"], { encoding: "utf8" });
5894
+ const globalRoot = globalRootResult.stdout?.trim();
5895
+ if (globalRoot && process.argv[1] && process.argv[1].startsWith(globalRoot)) {
5896
+ isGlobal = true;
5897
+ }
5898
+ } catch {
5899
+ }
5900
+ if (isGlobal) {
5901
+ spawn2.sync("npm", ["install", "-g", `${packageName}@latest`], { stdio: "inherit" });
5902
+ const result2 = spawn2.sync("harper-agent", process.argv.slice(2), { stdio: "inherit" });
5903
+ process.exit(result2.status ?? 0);
5904
+ }
5905
+ const lsResult = spawn2.sync("npm", ["cache", "npx", "ls", packageName], { encoding: "utf8" });
5906
+ if (lsResult.stdout) {
5907
+ const keys = lsResult.stdout.split("\n").map((line) => line.trim()).filter((line) => line.includes(":")).filter((line) => {
5908
+ const [, pkgPart] = line.split(":");
5909
+ return pkgPart && pkgPart.trim().startsWith(`${packageName}@`);
5910
+ }).map((line) => line.split(":")[0].trim());
5911
+ if (keys.length > 0) {
5912
+ spawn2.sync("npm", ["cache", "npx", "rm", ...keys], { stdio: "inherit" });
5913
+ }
5914
+ }
5915
+ const result = spawn2.sync("npx", ["-y", `${packageName}@latest`, ...process.argv.slice(2)], { stdio: "inherit" });
5916
+ process.exit(result.status ?? 0);
5917
+ }
5918
+ } catch {
5919
+ }
5920
+ return packageVersion;
5921
+ }
5922
+ function promptForUpdateChoice(pkgName, currentVersion, latestVersion) {
5923
+ return new Promise((resolve2) => {
5924
+ const app = render2(
5925
+ React21.createElement(UpdatePrompt, {
5926
+ packageName: pkgName,
5927
+ currentVersion,
5928
+ latestVersion,
5929
+ onSelect: (c) => {
5930
+ resolve2(c);
5931
+ app.unmount();
5932
+ }
5933
+ })
5934
+ );
5935
+ });
5936
+ }
5937
+ function UpdatePrompt({ packageName, currentVersion, latestVersion, onSelect }) {
5938
+ const options = [
5939
+ {
5940
+ label: `Update right now (will run: npx -y @harperfast/agent@latest)`,
5941
+ value: "now"
5942
+ },
5943
+ { label: "Update later", value: "later" },
5944
+ { label: "Don\u2019t ask again", value: "never" }
5945
+ ];
5946
+ return React21.createElement(
5947
+ Box19,
5948
+ { flexDirection: "column", padding: 1 },
5949
+ React21.createElement(
5950
+ Text18,
5951
+ null,
5952
+ `${chalk5.yellow("Update available:")} ${chalk5.bold(packageName)} ${chalk5.dim(`v${currentVersion}`)} \u2192 ${chalk5.green(`v${latestVersion}`)}`
5953
+ ),
5954
+ React21.createElement(
5955
+ Box19,
5956
+ { marginTop: 1 },
5957
+ React21.createElement(Select3, {
5958
+ options,
5959
+ onChange: (v) => onSelect(v)
5960
+ })
5961
+ )
5962
+ );
5963
+ }
5964
+
4868
5965
  // utils/shell/ensureApiKey.ts
4869
5966
  function ensureApiKey() {
4870
5967
  const models = [
@@ -4905,8 +6002,22 @@ async function getStdin() {
4905
6002
 
4906
6003
  // agent.ts
4907
6004
  (async function() {
6005
+ setupGlobalErrorHandlers();
6006
+ loadEnv();
6007
+ const originalFetch = globalThis.fetch;
6008
+ globalThis.fetch = async (...args) => {
6009
+ const response = await originalFetch(...args);
6010
+ const headers = {};
6011
+ response.headers.forEach((value, key) => {
6012
+ headers[key] = value;
6013
+ });
6014
+ rateLimitTracker.updateFromHeaders(headers);
6015
+ emitToListeners("SettingsUpdated", void 0);
6016
+ return response;
6017
+ };
4908
6018
  process.on("SIGINT", handleExit);
4909
6019
  process.on("SIGTERM", handleExit);
6020
+ await checkForUpdate();
4910
6021
  parseArgs();
4911
6022
  if (!ensureApiKey()) {
4912
6023
  await new Promise((resolve2) => {
@@ -4915,7 +6026,7 @@ async function getStdin() {
4915
6026
  emitToListeners("ExitUI", void 0);
4916
6027
  parseArgs();
4917
6028
  if (!ensureApiKey()) {
4918
- console.log(chalk5.red("No key provided. Exiting."));
6029
+ console.log(chalk6.red("No key provided. Exiting."));
4919
6030
  process.exit(1);
4920
6031
  }
4921
6032
  }